Linux4.12 tun/tap简介

什么是tun/tap?

TUN/TAP虚拟网络设备为用户空间程序提供了网络数据包的发送和接收能力。他既可以当做点对点设备(TUN),也可以当做以太网设备(TAP)。

TUN/TAP虚拟网络设备的原理比较简单,他在Linux内核中添加了一个TUN/TAP虚拟网络设备的驱动程序和一个与之相关连的字符设备/dev/net/tun,字符设备tun作为用户空间和内核空间交换数据的接口。当内核将数据包发送到虚拟网络设备时,数据包被保存在设备相关的一个队列中,直到用户空间程序通过打开的字符设备tun的描述符读取时,它才会被拷贝到用户空间的缓冲区中,其效果就相当于,数据包直接发送到了用户空间。通过系统调用write发送数据包时其原理与此类似。

值得注意的是:一次read系统调用,有且只有一个数据包被传送到用户空间,并且当用户空间的缓冲区比较小时,数据包将被截断,剩余部分将永久地消失,write系统调用与read类似,每次只发送一个数据包。所以在编写此类程序的时候,请用足够大的缓冲区,直接调用系统调用read/write,避免采用C语言的带缓存的IO函数。

tun/tap的实现

tun/tap设备驱动初始化也是从init开始,主要进行操作如下:

1.注册netlink用以提供用户空间查看和设置网卡的接口。
2.注册miscdev设备,注册完之后将在系统中生成一个“/dev/net/tun”文件,同字符设备类似,
当应用程序使用open系统调用打开这个文件时,将生成file文件对象,而其file_operations将指向tun_fops。
3.注册网络事件监听回调tun_notifier_block。

tun/tap设备创建

1.用户态调用open函数打开字符设备。
2.然后调用ioctl通知内核创建tun/tap虚拟设备。
3.内核收到请求后会调用tun_chr_ioctl --> __tun_chr_ioctl。
4.__tun_chr_ioctl通过一系列参数检查会调用tun_set_iff进行处理。
5.tun_set_iff函数中申请net_device结构,并net_device私有数据内存大小为struct tun_struct结构体大小。
6.对申请的net_device进行初始化,根据虚拟接口类型对dev->netdev_ops进行赋值,tun赋值为tun_netdev_ops,tap赋值为tap_netdev_ops。
7.私有数据字段用tun变量保存,对tun各字段进行赋值,然后调用tun_attach将tun跟tfile进行关联。
8.将dev注册至net_device列表。

读取数据包流程

1.用户态通过read函数调用内核注册的tun_chr_read_iter函数。
2.tun_chr_read_iter函数根据用户空间传递的文件描述符查到到对应的tfile,tun等信息然后调用tun_do_read读取skb。
3.tun_do_read函数中先调用tun_ring_recv读取skb,如果当前没有skb读取,则调用schedule等待下次调度时再读取。
4.然后调用tun_put_user将读取到的skb进行一些处理(主要是vlan处理),然后再拷贝到用户空间,通过read返回。

数据包写入流程

1.用户态通过write函数调用内核注册的tun_chr_write_iter函数。
2.tun_chr_write_iter函数根据用户空间传递的文件描述符查到到对应的tfile,tun等信息然后调用tun_get_user处理。
3.tun_get_user函数首先进行一些非法检查,然后根据tun->flags对数据进行一些处理,然后申请skb,将数据封装进入skb,根据接口类型tun/tap对skb相关字段进行设置。

4.设置skb的网络层头部字段,然后调用netif_rx_ni,最终会进入netif_receive_skb进入协议栈。

数据结构关系图

tun/tap收发包流程


使用实例

用户态代码如下:

#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/if_tun.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int tun_create(char *dev, int flags)
{
    struct ifreq ifr;
    int fd, err;

    assert(dev != NULL);

    if ((fd = open("/dev/net/tun", 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;
    }
    strcpy(dev, ifr.ifr_name);

    return fd;
}

int main(int argc, char *argv[])
{
        int i = 0;
        int tun, ret;
        char tun_name[IFNAMSIZ];
        unsigned char buf[4096] = {0};

        tun_name[0] = '\0';
        tun = tun_create(tun_name, IFF_TUN | IFF_NO_PI);
        if (tun < 0) {
                perror("tun_create");
                return 1;
        }
        printf("TUN name is %s\n", tun_name);

        while (1) {
                unsigned char ip[4];

                ret = read(tun, buf, sizeof(buf));
                if (ret < 0)
                        break;
                printf("read %d bytes\n", ret);
                memcpy(ip, &buf[12], 4);
                memcpy(&buf[12], &buf[16], 4);
                memcpy(&buf[16], ip, 4);
                buf[20] = 0;
                *((unsigned short*)&buf[22]) += 8;
                ret = write(tun, buf, ret);
                printf("write %d bytes\n", ret);
        }

        return 0;
}

上述代码简单地处理了ICMP的ECHO包,并回应以ECHO REPLY。

编译 make tuntap

执行./tuntap

再开启另一个终端执行

ifconfig tun0 0.0.0.0 up

route add 10.10.10.1 dev tun0

ping 10.10.10.1

查看结果如下:

PING 10.10.10.1 (10.10.10.1) 56(84) bytes of data.
64 bytes from 10.10.10.1: icmp_seq=1 ttl=64 time=0.056 ms
64 bytes from 10.10.10.1: icmp_seq=2 ttl=64 time=0.079 ms
64 bytes from 10.10.10.1: icmp_seq=3 ttl=64 time=0.050 ms

切换到执行tuntap终端查看如下:

read 84 bytes
write 84 bytes
read 84 bytes
write 84 bytes
read 84 bytes
write 84 bytes

符合代码逻辑

参考文档:

http://blog.chinaunix.net/uid-28541347-id-5765256.html

https://blog.csdn.net/hunanchenxingyu/article/details/28422867

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux TUN/TAP设备是一种虚拟网络设备,它能够模拟一个网络接口。通过TUN/TAP设备,用户空间的程序可以像操作物理网络设备一样,发送和接收网络数据包。 TUN/TAP设备主要有两种模式:TUN模式和TAP模式。TUN模式主要用于IP层协议,TAP模式主要用于以太网层协议。两种模式的差异在于数据包的处理方式不同。 在Linux内核中,TUN/TAP设备的实现位于`drivers/net/tun.c`文件中。该文件中定义了一个名为`tun_net`的网络设备对象,并实现了`tun_chr_write_iter()`、`tun_chr_read_iter()`等函数,用于处理用户空间和内核空间之间的数据交互。 当用户空间的程序打开TUN/TAP设备时,会创建一个名为`tunX`的虚拟接口,其中`X`是一个数字,表示设备的编号。内核会将数据包发送到该虚拟接口,然后用户空间的程序可以通过读取该接口的文件描述符来接收数据包。同样地,用户空间的程序可以通过写入该接口的文件描述符来发送数据包。 TUN/TAP设备的实现使用了内核中的网络协议栈,因此它能够与其他网络设备无缝交互。用户空间的程序可以使用标准的套接字接口来与TUN/TAP设备进行通信,实现虚拟网络设备和物理网络设备之间的数据交换。 总之,TUN/TAP设备是一个非常有用的工具,它可以用于各种网络应用程序,如虚拟私有网络(VPN)和网络隧道。通过了解TUN/TAP设备的实现,我们可以更好地理解网络协议栈和Linux内核的工作原理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值