1. 描述¶
TUN/TAP 为用户空间程序提供数据包的接收和传输。它可以被看作是一个简单的点对点或以太网设备,但它不是从物理介质接收数据包,而是从用户空间程序接收;它也不是通过物理介质发送数据包,而是将数据包写入用户空间程序。
为了使用该驱动程序,程序必须打开 /dev/net/tun 并发出相应的 ioctl() 来向内核注册网络设备。根据选择的选项,网络设备将显示为 tunXX 或 tapXX。当程序关闭文件描述符时,网络设备和所有相应的路由都将消失。
根据所选设备的类型,用户空间程序必须读取/写入 IP 数据包(使用 tun)或以太网帧(使用 tap)。具体使用哪种取决于 ioctl() 中给出的标志。
Universal TUN/TAP driver 上的软件包包含两个简单的示例,说明如何使用 tun 和 tap 设备。这两个程序都像两个网络接口之间的桥梁。br_select.c - 基于 select 系统调用的桥梁。br_sigio.c - 基于异步 I/O 和 SIGIO 信号的桥梁。然而,最好的例子是 VTun http://vtun.sourceforge.net :))
2. 配置¶
创建设备节点
mkdir /dev/net (if it doesn't exist already) mknod /dev/net/tun c 10 200设置权限
e.g. chmod 0666 /dev/net/tun允许非 root 用户访问该设备没有坏处,因为创建网络设备或连接到不属于该用户的网络设备需要 CAP_NET_ADMIN 权限。如果要创建持久设备并将它们的所有权赋予非特权用户,则需要使这些用户可以使用 /dev/net/tun 设备。
驱动程序模块自动加载
确保在内核中启用了“内核模块加载器”——模块自动加载支持。内核应该在第一次访问时加载它。
手动加载
手动插入模块
modprobe tun如果您采用后一种方式,则每次需要时都必须加载该模块;如果您采用前一种方式,则在打开 /dev/net/tun 时会自动加载该模块。
3. 程序接口¶
3.1 网络设备分配¶
char *dev
应该是带有格式字符串的设备名称(例如“tun%d”),但(据我所知)这可以是任何有效的网络设备名称。请注意,字符指针会被真实设备名称覆盖(例如“tun0”)。
#include <linux/if.h> #include <linux/if_tun.h> int tun_alloc(char *dev) { struct ifreq ifr; int fd, err; if( (fd = open("/dev/net/tun", O_RDWR)) < 0 ) return tun_alloc_old(dev); 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 ) strscpy_pad(ifr.ifr_name, dev, IFNAMSIZ); if( (err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0 ){ close(fd); return err; } strcpy(dev, ifr.ifr_name); return fd; }
3.2 帧格式¶
如果未设置标志 IFF_NO_PI,则每个帧格式为
Flags [2 bytes] Proto [2 bytes] Raw protocol(IP, IPv6, etc) frame.
3.3 多队列 tuntap 接口¶
从 3.8 版本开始,Linux 支持多队列 tuntap,它可以使用多个文件描述符(队列)来并行发送或接收数据包。设备分配与以前相同,如果用户想要创建多个队列,则必须多次使用 IFF_MULTI_QUEUE 标志调用 TUNSETIFF 并使用相同的设备名称。
char *dev
应该是设备的名称,queues 是要创建的队列的数量,fds 用于存储并将创建的文件描述符(队列)返回给调用者。每个文件描述符都充当用户空间可以访问的队列接口。
#include <linux/if.h> #include <linux/if_tun.h> int tun_alloc_mq(char *dev, int queues, int *fds) { struct ifreq ifr; int fd, err, i; if (!dev) return -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 * IFF_MULTI_QUEUE - Create a queue of multiqueue device */ ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_MULTI_QUEUE; strcpy(ifr.ifr_name, dev); for (i = 0; i < queues; i++) { if ((fd = open("/dev/net/tun", O_RDWR)) < 0) goto err; err = ioctl(fd, TUNSETIFF, (void *)&ifr); if (err) { close(fd); goto err; } fds[i] = fd; } return 0; err: for (--i; i >= 0; i--) close(fds[i]); return err; }
引入了一个新的 ioctl(TUNSETQUEUE) 来启用或禁用队列。使用 IFF_DETACH_QUEUE 标志调用它时,队列将被禁用。当使用 IFF_ATTACH_QUEUE 标志调用它时,队列将被启用。在通过 TUNSETIFF 创建队列后,默认情况下会启用队列。
fd 是我们要启用或禁用的文件描述符(队列),当 enable 为 true 时,我们启用它,否则禁用它。
#include <linux/if.h> #include <linux/if_tun.h> int tun_set_queue(int fd, int enable) { struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); if (enable) ifr.ifr_flags = IFF_ATTACH_QUEUE; else ifr.ifr_flags = IFF_DETACH_QUEUE; return ioctl(fd, TUNSETQUEUE, (void *)&ifr); }
通用 TUN/TAP 设备驱动程序常见问题解答¶
-
TUN/TAP 驱动程序支持哪些平台?
目前,该驱动程序已为 3 个 Unix 系统编写
Linux 内核 2.2.x、2.4.x
FreeBSD 3.x、4.x、5.x
Solaris 2.6、7.0、8.0
-
TUN/TAP 驱动程序用于什么?
如上所述,TUN/TAP 驱动程序的主要目的是隧道传输。它被 VTun (http://vtun.sourceforge.net) 使用。
另一个使用 TUN/TAP 的有趣应用程序是 pipsecd (http://perso.enst.fr/~beyssac/pipsec/),这是一个用户空间 IPSec 实现,可以使用完整的内核路由(与 FreeS/WAN 不同)。
-
虚拟网络设备实际上是如何工作的?
虚拟网络设备可以被看作是一个简单的点对点或以太网设备,但它不是从物理介质接收数据包,而是从用户空间程序接收;它也不是通过物理介质发送数据包,而是将数据包发送到用户空间程序。
假设您在 tap0 上配置了 IPv6,那么每当内核向 tap0 发送 IPv6 数据包时,它都会传递给应用程序(例如 VTun)。该应用程序加密、压缩数据包并通过 TCP 或 UDP 将其发送到另一端。另一端的应用程序解压缩并解密接收到的数据,然后将数据包写入 TAP 设备,内核像处理来自真实物理设备的数据包一样处理该数据包。
-
TUN 驱动程序和 TAP 驱动程序有什么区别?
TUN 处理 IP 帧。TAP 处理以太网帧。
这意味着当您使用 tun 时,您必须读取/写入 IP 数据包;当您使用 tap 时,必须读取/写入以太网帧。
-
BPF 和 TUN/TAP 驱动程序有什么区别?
BPF 是一种高级数据包过滤器。它可以附加到现有的网络接口。它不提供虚拟网络接口。TUN/TAP 驱动程序确实提供了虚拟网络接口,并且可以将 BPF 附加到此接口。
-
TAP 驱动程序是否支持内核以太网桥接?
是的。Linux 和 FreeBSD 驱动程序支持以太网桥接。