[关闭]
@zwh8800 2017-08-23T10:17:05.000000Z 字数 3440 阅读 193069

[转]Linux的TUN/TAP编程

blog 归档 Linux TUN/TAP 网络编程


TUN/TAP虚拟网络设备为用户空间程序提供了网络数据包的发送和接收能力。他既可以当做点对点设备(TUN),也可以当做以太网设备(TAP)。实际上,不仅Linux支持TUN/TAP虚拟网络设备,其他UNIX也是支持的,他们之间只有少许差别。


原网址:https://blog.chinaunix.net/uid-317451-id-92474.html

原理简介

TUN/TAP虚拟网络设备的原理比较简单,他在Linux内核中添加了一个TUN/TAP虚拟网络设备的驱动程序和一个与之相关连的字符设备/dev/net/tun,字符设备tun作为用户空间和内核空间交换数据的接口。当内核将数据包发送到虚拟网络设备时,数据包被保存在设备相关的一个队列中,直到用户空间程序通过打开的字符设备tun的描述符读取时,它才会被拷贝到用户空间的缓冲区中,其效果就相当于,数据包直接发送到了用户空间。通过系统调用write发送数据包时其原理与此类似。值得注意的是:一次read系统调用,有且只有一个数据包被传送到用户空间,并且当用户空间的缓冲区比较小时,数据包将被截断,剩余部分将永久地消失,write系统调用与read类似,每次只发送一个数据包。所以在编写此类程序的时候,请用足够大的缓冲区,直接调用系统调用read/write,避免采用C语言的带缓存的IO函数。

准备工作

首先你需要一个能工作的Linux操作系统,并且内核支持TUN/TAP虚拟网络设备,如果没有,请在内核中选中:

Device Drivers => Network device support => Universal TUN/TAP device driver support

你可以选择编译进内核或者是编译成模块,然后重新编译内核并用新内核启动。如果你编译的是模块,那么在下步开始之前,你需要手工加载它。

  1. root@gentux ~ # modprobe tun

开始编程

从代码开始,

  1. #include <linux/if_tun.h>
  2. int tun_create(char* dev, int flags) {
  3. struct ifreq ifr;
  4. int fd, err;
  5. assert(dev != NULL);
  6. if ((fd = open(“/ dev / net / tun”, O_RDWR)) < 0) return fd;
  7. memset(&ifr, 0, sizeof(ifr));
  8. ifr.ifr_flags |= flags;
  9. if (*dev != \0’) strncpy(ifr.ifr_name, dev, IFNAMSIZ);
  10. if ((err = ioctl(fd, TUNSETIFF, (void*)&ifr)) < 0) {
  11. close(fd);
  12. return err;
  13. }
  14. strcpy(dev, ifr.ifr_name);
  15. return fd;
  16. }

为了使用TUN/TAP设备,我们必须包含特定的头文件linux/if_tun.h,如12行所示。在21行,我们打开了字符设备/dev/net/tun。接下来我们需要为ioctl的TUNSETIFF命令初始化一个结构体ifr,一般的时候我们只需要关心其中的两个成员ifr_name, ifr_flags。ifr_name定义了要创建或者是打开的虚拟网络设备的名字,如果它为空或者是此网络设备不存在,内核将新建一个虚拟网络设备,并返回新建的虚拟网络设备的名字,同时文件描述符fd也将和此网络设备建立起关联。如果并没有指定网络设备的名字,内核将根据其类型自动选择tunXX和tapXX作为其名字。ifr_flags用来描述网络设备的一些属性,比如说是点对点设备还是以太网设备。详细的选项解释如下:

配置的时候,IFF_TUN和IFF_TAP必须择一,其他选项则可任意组合。其中IFF_NO_PI没有开启时所附加的包信息头如下:

  1. struct tun_pi {
  2. unsigned short flags;
  3. unsigned short proto;
  4. };

目前,flags只在收取数据包的时候有效,当它的TUN_PKT_STRIP标志被置时,表示当前的用户空间缓冲区太小,以致数据包被截断。proto成员表示发送/接收的数据包的协议。

上面代码中的文件描述符fd除了支持TUN_SETIFF和其他的常规ioctl命令外,还支持以下命令:

一个小实例

  1. int main(int argc, char *argv[]) {
  2. int tun, ret;
  3. char tun_name[IFNAMSIZ];
  4. unsigned char buf[4096];
  5. tun_name[0] = \0’;
  6. tun = tun_create(tun_name, IFF_TUN | IFF_NO_PI);
  7. if (tun < 0) {
  8. perror(“tun_create”);
  9. return 1;
  10. }
  11. printf(“TUN name is % s\n”, tun_name);
  12. while (1) {
  13. unsigned char ip[4];
  14. ret = read(tun, buf, sizeof(buf));
  15. if (ret < 0) break;
  16. memcpy(ip, &buf[12], 4);
  17. memcpy(&buf[12], &buf[16], 4);
  18. memcpy(&buf[16], ip, 4);
  19. buf[20] = 0;
  20. *((unsigned short *)&buf[22]) += 8;
  21. printf(“read % d bytes\n”, ret);
  22. ret = write(tun, buf, ret);
  23. printf(“write % d bytes\n”, ret);
  24. }
  25. return 0;
  26. }

以上代码简答地处理了ICMP的ECHO包,并回应以ECHO REPLY。

首先运行这个程序:

  1. root@gentux test # ./a.out
  2. TUN name is tun0

接着在另外一个终端运行如下命令:

  1. root@gentux linux-2.6.15-gentoo # ifconfig tun0 0.0.0.0 up
  2. root@gentux linux-2.6.15-gentoo # route add 10.10.10.1 dev tun0
  3. root@gentux linux-2.6.15-gentoo # ping 10.10.10.1
  4. PING 10.10.10.1 (10.10.10.1) 56(84) bytes of data.
  5. 64 bytes from 10.10.10.1: icmp_seq=1 ttl=64 time=1.09 ms
  6. 64 bytes from 10.10.10.1: icmp_seq=2 ttl=64 time=5.18 ms
  7. 64 bytes from 10.10.10.1: icmp_seq=3 ttl=64 time=3.37 ms 10.10.10.1 ping statistics
  8. 3 packets transmitted, 3 received, 0% packet loss, time 2011ms
  9. rtt min/avg/max/mdev = 1.097/3.218/5.181/1.671 ms

可见,我们顺利地接受到了回应包,这时,切回到前一个终端下:

  1. read 84 bytes
  2. write 84 bytes
  3. read 84 bytes
  4. write 84 bytes
  5. read 84 bytes
  6. write 84 bytes

一切正如我们所预想的那样。

TUN/TAP能做什么?

hoho,问这个问题似乎有些傻,你说一个网卡能做什么?我可以告诉你两个基于此的开源项目:vtunopenvpn,至于其他的应用,请自由发挥你的想像力吧!

参考资料:

2.6.15内核源码

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注