然后在第二个终端 ping 一下 10.1.1.0/24 网段的 IP,比如 10.1.1.3,看到:
[root@localhost ~]# ping -c 4 10.1.1.3 PING 10.1.1.3 (10.1.1.3) 56(84) bytes of data. 64 bytes from 10.1.1.3: icmp_seq=1 ttl=64 time=0.133 ms 64 bytes from 10.1.1.3: icmp_seq=2 ttl=64 time=0.188 ms 64 bytes from 10.1.1.3: icmp_seq=3 ttl=64 time=0.092 ms 64 bytes from 10.1.1.3: icmp_seq=4 ttl=64 time=0.110 ms --- 10.1.1.3 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3290ms rtt min/avg/max/mdev = 0.092/0.130/0.188/0.038 ms由于 tun0 接口建好之后,会生成一条到本网段 10.1.1.0/24 的默认路由,根据默认路由,数据包会走 tun0 口,所以能 ping 通,可以用 route -n 查看。
再看 tcpdump 抓包终端,成功显示 ICMP 的 request 包和 reply 包。
[root@localhost ~]# tcpdump -nnt -i tun0 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on tun0, link-type RAW (Raw IP), capture size 262144 bytes IP 10.1.1.2 > 10.1.1.3: ICMP echo request, id 3250, seq 1, length 64 IP 10.1.1.3 > 10.1.1.2: ICMP echo reply, id 3250, seq 1, length 64 IP 10.1.1.2 > 10.1.1.3: ICMP echo request, id 3250, seq 2, length 64 IP 10.1.1.3 > 10.1.1.2: ICMP echo reply, id 3250, seq 2, length 64再看程序 taptun.c 的输出:
[root@localhost coding]# ./taptun Open tun/tap device: tun0 for reading... Read 48 bytes from tun/tap device Write 48 bytes to tun/tap device Read 48 bytes from tun/tap device Write 48 bytes to tun/tap deviceok,以上便验证了程序的正确性。
03 总结通过这个小例子,让我们知道了基于 tap/tun 编程的流程,对 tap/tun 又加深了一层理解。
使用 tap/tun 设备需要包含头文件 #include <linux/if_tun.h>,以下是完整代码。
/****************************************************************************** * File Name: taptun.c * Author: 公众号: CloudDeveloper * Created Time: 2019年02月23日 星期六 21时28分24秒 *****************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <assert.h> #include <net/if.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <sys/types.h> #include <linux/if_tun.h> int tun_alloc(char *dev, int flags) { assert(dev != NULL); struct ifreq ifr; int fd, err; char *clonedev = "/dev/net/tun"; if ((fd = open(clonedev, O_RDWR)) < 0) { return fd; } memset(&ifr, 0, sizeof(ifr)); ifr.ifr_flags = flags; if (*dev != '\0') { strncpy(ifr.ifr_name, dev, IFNAMSIZ); } if ((err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0) { close(fd); return err; } // 一旦设备开启成功,系统会给设备分配一个名称,对于tun设备,一般为tunX,X为从0开始的编号; // 对于tap设备,一般为tapX strcpy(dev, ifr.ifr_name); return fd; } int main() { int tun_fd, nread; char buffer[4096]; char tun_name[IFNAMSIZ]; tun_name[0] = '\0'; /* Flags: IFF_TUN - TUN device (no Ethernet headers) * IFF_TAP - TAP device * IFF_NO_PI - Do not provide packet information */ tun_fd = tun_alloc(tun_name, IFF_TUN | IFF_NO_PI); if (tun_fd < 0) { perror("Allocating interface"); exit(1); } printf("Open tun/tap device: %s for reading...\n", tun_name); while (1) { unsigned char ip[4]; // 收包 nread = read(tun_fd, buffer, sizeof(buffer)); if (nread < 0) { perror("Reading from interface"); close(tun_fd); exit(1); } printf("Read %d bytes from tun/tap device\n", nread); // 简单对收到的包调换一下顺序 memcpy(ip, &buffer[12], 4); memcpy(&buffer[12], &buffer[16], 4); memcpy(&buffer[16], ip, 4); buffer[20] = 0; *((unsigned short *)&buffer[22]) += 8; // 发包 nread = write(tun_fd, buffer, nread); printf("Write %d bytes to tun/tap device, that's %s\n", nread, buffer); } return 0; }