通用TUN/TAP设备驱动

原文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 
#include 

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 
#include 

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 
#include 

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设备驱动)