如何与Linux tun驱动程序进行接口交互

29
我在解决这个问题上遇到了困难 - 我正在尝试编写一个与Linux隧道驱动程序交互的程序。在非常基本的层面上,我只是想创建一个能够在网络隧道上传输数据的应用程序。然而,我完全不知道如何正确设置隧道驱动程序以实现这一目标。
我正在Ubuntu 9.04上进行开发,并且已经加载了隧道驱动内核模块。
存在着设备"/dev/net/tun",但是没有"/dev/tunX"设备。我无法使用"ifconfig"命令创建这些设备 - 例如,每当我运行"/sbin/ifconfig tun0 up"时,都会出现以下错误:
tun0: ERROR while getting interface flags: No such device.
如果我尝试查看"/dev/net/tun"设备,会出现以下错误:
cat: /dev/net/tun: File descriptor in bad state.
通过一个小程序尝试打开"/dev/tunX",基本上是一个简单的
tun_fd = open( "/dev/tun0", O_RDWR )

返回-1:应用程序正在以root身份运行,但仍无法打开此隧道设备。可以打开`/dev/net/tun`,但似乎没有生成新的`/dev/tunX`设备来替代使用。
所以,总结一下-如何编写一个希望使用Linux隧道驱动程序的应用程序?任何见解将不胜感激。
谢谢; ~Robert
4个回答

33

没有/dev/tunX设备文件。相反,您可以打开/dev/net/tun并通过ioctl()配置它以“指向”tun0。为了展示基本过程,我将使用命令行工具ip tun tap创建TUN接口,然后展示从该TUN设备读取的C代码。因此,要通过命令行创建tun接口:

ip addr show # my eth0 inet address is 10.0.2.15/24 as Im running on a VirtualBox vm with Ubuntu 18.04 guest
sudo ip tuntap add mode tun dev tun0
sudo ip addr add 10.0.3.0/24 dev tun0  # give it an address (that does not conflict with existing IP)
sudo ip link set dev tun0 up  # bring the if up
ip route get 10.0.3.50  # check that packets to 10.0.3.x are going through tun0
# 10.0.3.50 dev tun0 src 10.0.3.0 uid 1000 
ping 10.0.3.50 # leave this running in another shell to be able to see the effect of the next example, nobody is responding to the ping

创建了tun0,所有目标IP地址为10.0.3.x的数据包将被路由到tun0

要从用户空间程序读取/写入此接口的数据包,您需要使用ioctl()/dev/net/tun设备文件进行交互。以下是一个示例,它将读取到达tun0接口的数据包并打印其大小:

#include <fcntl.h>     /* O_RDWR */
#include <stdio.h>     /* perror(), printf(), fprintf() */
#include <stdlib.h>    /* exit(), malloc(), free() */
#include <string.h>    /* memset(), memcpy() */
#include <sys/ioctl.h> /* ioctl() */
#include <unistd.h>    /* read(), close() */

/* includes for struct ifreq, etc */
#include <linux/if.h>
#include <linux/if_tun.h>
#include <sys/socket.h>
#include <sys/types.h>

int tun_open(char* devname)
{
    struct ifreq ifr;
    int fd, err;

    if ((fd = open("/dev/net/tun", O_RDWR)) == -1) {
        perror("open /dev/net/tun");
        exit(1);
    }
    memset(&ifr, 0, sizeof(ifr));
    ifr.ifr_flags = IFF_TUN;
    strncpy(ifr.ifr_name, devname, IFNAMSIZ); // devname = "tun0" or "tun1", etc

    /* ioctl will use ifr.if_name as the name of TUN
     * interface to open: "tun0", etc. */
    if ((err = ioctl(fd, TUNSETIFF, (void*)&ifr)) == -1) {
        perror("ioctl TUNSETIFF");
        close(fd);
        exit(1);
    }

    /* After the ioctl call the fd is "connected" to tun device specified
     * by devname ("tun0", "tun1", etc)*/

    return fd;
}


int main(int argc, char* argv[])
{
    int fd, nbytes;
    char buf[1600];

    fd = tun_open("tun0"); /* devname = ifr.if_name = "tun0" */
    printf("Device tun0 opened\n");
    while (1) {
        nbytes = read(fd, buf, sizeof(buf));
        printf("Read %d bytes from tun0\n", nbytes);
    }
    return 0;
}
如果你运行着ping 10.0.3.1或者ping 10.0.3.40命令,你会周期性地看到输出Read 88 bytes from tun0。 你也可以使用UDP协议的netcat进行测试,命令为nc -u 10.0.3.3 2222,输入文本后按下回车。 如果没有任何输出,则很可能是tun0设备的IP地址范围无法访问/路由。请确保ip route get 10.0.3.4 显示 10.0.3.4 dev tun0,表示Linux内核知道将数据包发送到tun0设备。 要删除tun0,请执行以下操作。
sudo ip link set dev tun0 down
sudo ip tuntap del mode tun dev tun0

这个答案缺少 #include <unistd.h> 语句,用于 read()close() 函数。 - user3629249
如果我运行你的C代码,实际上没有任何数据包被读取以响应ping tun接口。 - Peter
1
我刚试了一下,在Ubuntu 18.04上至少可以工作。@Peter请确保您分配给tun0接口的IP地址/范围是合理的,并且可达/可路由(我在示例中使用的IP可能与您的IP计划冲突)。 - RubenLaguna

22

阅读/usr/src/linux/Documentation/networking/tuntap.rst

您应该打开/dev/net/tun设备。接着,在打开的文件描述符上进行ioctl操作将会创建一个名为tun0(或任何您想要的名称)的网络接口。Linux的网络接口并不对应于任何/dev/*设备。


@rmrobins;你是如何让它正常工作的?我认为我遇到了与你最初提出的问题非常相似的问题。我可以看到/dev/net/tun设备,但打开它并没有产生网络接口。我一直在尝试使用br_select.c和br_sigio.c示例。 - simon
如上所述,打开/dev/net/tun。然后,将使用ioctl创建实际的接口本身。ioctl称为TUNSETIFF,参数类型为struct ifreq。ifreq结构的标志应设置为IFF_TUN。一旦ioctl返回,ifreq结构的ifr_name字段将设置为已打开接口的名称。希望这可以帮助! - rmrobins

3

0

另一种实现方式是使用C11标准库中的stdio.h里的fopenfclosefread函数,而不是在unistd.h中使用的API。

#include <linux/if.h>
#include <linux/if_tun.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

FILE* tun_alloc(char *dev)
{
   struct ifreq ifr;
   FILE *fp;
   int err;

   // open in binary read+write mode
   if ((fp = fopen("/dev/net/tun", "r+b")) == 0)
   {
      perror("failed to open tun dev\n");
      exit(1);
   }

   memset(&ifr, 0, sizeof(ifr));

   /* Flags: IFF_TUN   - TUN device (no Ethernet headers)
    *        IFF_TAP   - TAP device
    *
    *        IFF_NO_PI - Do not provide packet information
    */
   ifr.ifr_flags = IFF_TUN;
   if (*dev)
      strncpy(ifr.ifr_name, dev, IFNAMSIZ);

   if ((err = ioctl(fileno(fp), TUNSETIFF, (void *)&ifr)) < 0)
   {
      fclose(fp);
      perror("failed to set mode!\n");
      exit(1);
   }
   strcpy(dev, ifr.ifr_name);
   return fp;
}

int main(void)
{
   char dev_name[IFNAMSIZ] = "tun0";
   char recv_buff[80];
   FILE* fp = tun_alloc(dev_name);

   // turn off buffering
   setbuf(fp, NULL); 

   while (1)
   {
      printf("reading from TUN device: %s\n", dev_name);
      size_t n = fread(recv_buff, sizeof(recv_buff[0]), sizeof(recv_buff), fp);
      printf("read bytes: %li\n", n);
   }
   return EXIT_SUCCESS;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接