tun/tap 设备
{Back to Index}
Table of Contents
虚拟网络设备也归内核的网络设备管理子系统管理,对于 Linux 内核网络设备管理模块来说,虚拟设备和物理设备没有区别,都是网络设备,都能配置 IP , 从网络设备来的数据,都会转发给协议栈,协议栈过来的数据,也会交由网络设备发送出去,至于是怎么发送出去的,发到哪里去,那是设备驱动的事情。
所以说,和物理设备一样, 虚拟网络设备的一端也是协议栈,而另一端是什么取决于虚拟网络设备的驱动实现。
tun/tap 设备 1 一端连着协议层, 另一端连接用户层的应用程序 ,协议栈发给 tun/tap 的数据包能被这个应用程序读取到,并且应用程序能直接向 tun/tap 写数据。
用户层程序通过 tun 设备只能读写 IP 数据包,而通过 tap 设备能读写链路层数据包,处理数据包的格式不一样。
tun/tap 设备可以将协议栈中的部分数据包转发给用户空间的应用程序,给用户空间的程序一个处理数据包的机会。 于是比较常用的数据压缩,加密等功能就可以在应用程序里面做进去,常用的场景是 VPN ,包括 tunnel 以及应用层的 IPSec 等。
1 使用 ip tuntap
命令创建
ip tuntap add name tap0 mode tap ip tuntap del name tap0 mode tap # 删除时必须同时指定 mode ,因为可以创建同名的 tun 或 tap 设备
这里给出通过 C 程序对 tun 设备进行监听的代码,如果需要监听 tap 设备,将 flag 设为 IFF_TAP
即可:
// tun.c #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> #include<stdlib.h> #include<stdio.h> int tun_alloc(char *dev, int flags) { 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) { /* 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); } if ((err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0) { close(fd); return err; } strcpy(dev, ifr.ifr_name); printf("Open tun device: %s for reading...\n", ifr.ifr_name); return fd; } int main(int argc, char *argv[]) { char tun_name[IFNAMSIZ]; if( argc == 2 ) { strcpy(tun_name, argv[1]); } else if( argc > 2 ) { printf("Too many arguments supplied.\n"); exit(1); } else { printf("Tun device name expected.\n"); exit(1); } int tun_fd, nread; char buffer[1500]; /* 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); } while (1) { 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 device %s\n", nread, tun_name); } return 0; }
ip tuntap add name mytun mode tun ip l set mytun up gcc tun.c -o tun ping -I mytun localhost & # 放到后台 ./tun mytun # 开始监听 # 可以观察到 # Open tun device: mytun for reading... # Read 84 bytes from tun device mytun
2 使用 socat 创建
socat -u TUN:10.0.6.1/24,tun-type=tap,up EXEC:'hexdump -C' # 监听 tap 设备 socat -u TUN:10.0.7.1/24,iff-no-pi,up EXEC:'hexdump -C' # 监听 tun 设备
使用 socat 可以搭建简易的隧道。
2.1 基于 tap 的隧道 2
# server socat tcp-l:40839,reuseaddr TUN:172.0.0.1/24,iff-no-pi,up,tun-type=tap # socat 默认将添加路由,否则,需手工添加 172.0.0.0/24 网段路由,经由生成的 tap 设备 # client socat tcp:10.74.68.100:40839 TUN:172.0.0.2/24,iff-no-pi,up,tun-type=tap
Figure 1: 使用 tap 设备搭建隧道
从下图的抓包中可见,底层 TCP 连接上承载了二层数据,即 TCP 的 payload 为以太网帧 (Ethernet over IP) :
Figure 2: socat + tap
2.2 基于 tun 的隧道 3
原理和使用 tap 类似,底层 TCP 连接上承载了以太网数据,即 TCP 的 payload 为 IP 数据报文 (IP over IP) 。
3 使用 pytun 创建
from pytun import TunTapDevice tun = TunTapDevice() tun.addr = '10.8.0.1' tun.netmask = '255.255.255.0' tun.mtu = 1500 tun.up() print("tun device (%s) created" % tun.name) while True: try: buf = tun.read(tun.mtu) print ("read from %s: %s" % (tun.name, buf)) except Exception as e: print(e) tun.close() exit()