原文Universal TUN/TAP device driver
1. 描述
TUN/TAP为用户空间提供分组接收和传输。既可以看作一个Point-to-Point设备,也可以看做一个Ethernet设备。它从用户空间程序接收分组,而不是从物理媒介;将分组写入用户空间程序而不是通过物理媒介。
一个程序打开/dev/net/tun字符型文件,并向内核发出ioctl()来注册一个网络设备。根据选项,这个网络设备将显示为tunXX或者tapXX。当程序关闭文件描述符时,这个网络设备和所有对应的路由都将关闭。
用户空间程序read/write IP数据包(通过tun)还是以太网帧(通过tap),取决于选择的设备类型。正在使用哪一个取决于ioctl()设置的flag。
http://vtun.sourceforge.net/tun下的软件包包含两个简单的示例,介绍如何使用tun和tap设备。两个示例的工作原理都像在两个网络接口之间的桥一样。
- br_select.c - 基于选择系统调用的桥
- br_sigio.c - 基于async io和SIGIO信号的桥
另外,最好的例子来源于VTun(http://vtun.sourceforge.net) :))
2. 配置
创建设备节点:
mkdir /dev/net
mknod /dev/net/tun c 10 200
设置权限:
chmod 0666 /dev/net/tun
驱动程序模块自动加载:
确保内核启用了“内核模块加载器” - 模块自动加载支持。 内核应该在第一次访问时加载它。
手动加载:
insert the module by hand:
modprobe tun
后一种方法,每次当你需要使用模块的时候,你都需要加载它。另一种方法将在你打开/dev/net/tun的时候自动加载。
3. 编程接口
3.1 网络设备分配
设备的名称char *dev
是具有格式的字符串(e.g. "tun%d"),也可以是任何有效的网络设备名称。注意,字符指针将会被真实网络设备名覆盖。
#include <linux/if.h>
#include <linux/if_tun.h>
int tun_alloc(char *dev)
{
struct ifreq ifr;
int fd, err;
//获取/dev/net/tun的文件描述符
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 )
strncpy(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 er;
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为真时,我们启用它,否则我们禁用它
#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);
}