上次对tun/tap做了介绍,本次使用“编程”做进一步的使用理解。
1、创建接口
创建新接口和(重新)附加到持久接口的代码本质上是相同的;不同之处在于前者必须由 root 运行(更准确地说,由具有 CAP_NET_ADMIN 能力的用户运行),而后者如果满足某些条件,可以由普通用户运行。让我们从创建新接口开始。
首先,设备 /dev/net/tun 都必须以读/写方式打开。该设备也称为克隆设备,因为它用作创建任何 tun/tap 虚拟接口的起点。该操作(与任何 open() 调用一样)返回一个文件描述符。但这还不足以开始使用它与接口进行通信。
创建接口的下一步是进行ioctl() 系统调用,其参数是上一步中的描述符、TUNSETIFF 常量以及指向包含描述虚拟接口的参数(基本上是其名称和所需的操作模式 - tun 或 tap)的数据结构的指针。作为一种变体,虚拟接口的名称可以不指定,在这种情况下,内核将通过尝试分配该类型的“下一个”设备来选择一个名称(例如,如果 tap2 已经存在,内核将尝试分配 tap3,依此类推)。所有这些都必须由 root 完成(或由具有 CAP_NET_ADMIN 功能的用户完成 - 我不会再重复这一点;假设它适用于我说“必须由 root 运行”的所有地方)。
如果 ioctl() 成功,则创建虚拟接口,并且我们拥有的文件描述符现在与其关联,并可用于通信。
创建虚拟接口的基本代码在内核源代码树的 Documentation/networking/tuntap.txt 文件中。稍加修改,我们就可以编写一个创建虚拟接口的简单函数:
#include <linux /if.h>#include <linux /if_tun.h>int tun_alloc(char *dev, int flags) { struct ifreq ifr; int fd, err; char *clonedev = "/dev/net/tun"; /* Arguments taken by the function: * * char *dev: the name of an interface (or '\0'). MUST have enough * space to hold the interface name if '\0' is passed * int flags: interface flags (eg, IFF_TUN etc.) */ /* open the clone device */ if( (fd = open(clonedev, O_RDWR)) < 0 ) { return fd; } /* preparation of the struct ifr, of type "struct ifreq" */ memset(&ifr, 0, sizeof(ifr)); ifr.ifr_flags = flags; /* IFF_TUN or IFF_TAP, plus maybe IFF_NO_PI */ if (*dev) { /* if a device name was specified, put it in the structure; otherwise, * the kernel will try to allocate the "next" device of the * specified type */ strncpy(ifr.ifr_name, dev, IFNAMSIZ); } /* try to create the device */ if( (err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0 ) { close(fd); return err; } /* if the operation was successful, write back the name of the * interface to the variable "dev", so the caller can know * it. Note that the caller MUST reserve space in *dev (see calling * code below) */ strcpy(dev, ifr.ifr_name); /* this is the special file descriptor that the caller will use to talk * with the virtual interface */ return fd;}
tun_alloc() 函数有两个参数:
char *dev 包含接口的名称(例如 tap0、tun2 等)。可以使用任何名称,但最好选择一个能表明接口类型的名称。实际上,通常使用 tunX 或 tapX 等名称。如果 *dev 为 '\0',则内核将尝试创建请求类型的“第一个”可用接口(例如 tap0,但如果已经存在,则为 tap1,依此类推)。
int flags 包含告诉内核我们想要哪种接口(tun 或 tap)的标志。基本上,它可以采用值 IFF_TUN 来指示 TUN 设备(数据包中没有以太网报头),或者采用 IFF_TAP 来指示 TAP 设备(数据包中有以太网报头)。
此外,另一个标志 IFF_NO_PI 可以与基值进行或运算。IFF_NO_PI 告诉内核不提供数据包信息。 IFF_NO_PI 的目的是告诉内核数据包将是“纯”IP 数据包,没有添加任何字节。否则(如果未设置 IFF_NO_PI),则会在数据包开头添加 4 个额外字节(2 个标志字节和 2 个协议字节)。IFF_NO_PI 不需要在接口创建和重新连接时间之间匹配。另请注意,当使用 Wireshark 捕获接口上的流量时,这 4 个字节永远不会显示。
2、例子
2.1 程序例子
... /* tunclient.c */ char tun_name[IFNAMSIZ]; /* Connect to the device */ strcpy(tun_name, "tun77"); tun_fd = tun_alloc(tun_name, IFF_TUN | IFF_NO_PI); /* tun interface */ if(tun_fd < 0){ perror("Allocating interface"); exit(1); } /* Now read data coming from the kernel */ while(1) { /* Note that "buffer" should be at least the MTU size of the interface, eg 1500 bytes */ nread = read(tun_fd,buffer,sizeof(buffer)); if(nread < 0) { perror("Reading from interface"); close(tun_fd); exit(1); } /* Do whatever with the data */ printf("Read %d bytes from device %s\n", nread, tun_name); } ...
2.2 为tun设备分配ip
ip addr add 192.168.99.167/24 dev tun77ip link set dev tun77 up
这步之后的效果如下:
此时ifconfig:
2.3 使用 tcpdump监控报文
执行ping操作后发现tcpdump并没有任何打印信息(那8个字节并不是ICMP的报文),即没有任何流量经过该接口。这种现象是符合预期的,因为当ping该接口的IP地址时,操作系统会认为报文不需要在"线路"上进行传输,由内核负责回应ping请求(当ping其他接口的IP地址时的现象也是一样的)。tcpdump抓包是在网络协议栈外进行的,ping本地IP地址时的报文会在协议层面处理,因此无法抓到报文。
2.4 让 tcpdump监控到报文
#尝试ping一下192.168.99.167/24网段的IP,#由于我们的程序中收到数据包后,啥都没干,相当于把数据包丢弃了,#所以这里的ping根本收不到返回包,#但在前两个窗口中可以看到这里发出去的四个icmp echo请求包,#说明数据包正确的发送到了应用程序里面,只是应用程序没有处理该包
本次例子就到这里。
如需测试代码可在后台留言“tun/tap icmp”
欢迎关注:
参考文章:
https://backreference.org/2010/03/26/tuntap-interface-tutorial/
https://blog.youkuaiyun.com/sld880311/article/details/77854651