协议转换代理服务器设计——虚拟网卡实现技术
北京华环电子---任晓亮,2016/09/24
一. 虚拟网关概念
二. 网卡的工作流程
三. 内核配置支持虚拟网卡
四. 应用层代码
五. 使用案例
六. 参考文档
https://www.kernel.org/doc/Documentation/networking/tuntap.txt
方案: SDH - -----》dcc监控通道------》数据ip封装 ------》虚拟网卡 ---》linux 协议栈 -----》以太网传输
一. 概念
再上次交流中,我们知道了用户空间跟内核空间的数据交互的n多种方法,这一节我们在来讲一个方法,在linux下,要实现 内核空间 和 用户空间 数据的交互,有多种方式:可以通用socket创建特殊套接字,利用套接字实现数据交互;通过proc文件系统创建文件来进行数据交互;还可以使用设备文件的方式,访问设备文件会调用设备驱动相应的例程,设备驱动本身就是 内核空间 和 用户空间 的一个接口,Tun/tap驱动就是利用设备文件实现用户空间 和 内核空间 的数据交互。
Tun/Tap都是虚拟网卡,没有直接映射到物理网卡,是一种纯软件的实现。Tun是三层虚拟设备,能够处理三层即IP包,Tap是二层设备,能处理链路层网络包如以太网包。使用虚拟网络设备,可以实现隧道,如OpenVPN的实现。
二. 工作流程
Tun/tap 驱动程序实现了虚拟网卡的功能,tun表示虚拟的是点对点设备,tap表示虚拟的是以太网设备,这两种设备针对网络包实施不同的封装。 利用tun/tap 驱动,可以将tcp/ip协议栈处理好的网络分包传给任何一个使用tun/tap驱动的进程,由进程重新处理后再发到物理链路中。
做为虚拟网卡驱动,Tun/Tap驱动程序的数据接收和发送并不直接和真实网卡打交道, 他在Linux内核中添加了一个TUN/TAP虚拟网络设备的驱动程序和一个与之相关连的字符设备 /dev/net/tun,字符设备tun作为用户空间和内核空间交换数据的接口。当内核将数据包发送到虚拟网络设备时,数据包被保存在设备相关的一个队列中,直到用户空间程序通过打开的字符设备tun的描述符读取时,它才会被拷贝到用户空间的缓冲区中,其效果就相当于,数据包直接发送到了用户空间。通过系统调用write发送数据包时其原理与此类似。
上面的图中,左右两边分别为两台机器。一台有一块物理网卡配置了IP:172.16.1.11,这台机器的系统里有一个Tun(以Tun为例,不讲Tap了)设备,配置了IP:192.168.1.11;另一台的一块物理网卡配置了IP:172.16.1.12,系统里有一个Tun设备并配置了IP:192.168.1.12。
左边Linux系统里有一个Application,绑定到端口地址为: 192.168.1.11:5000,右边Linux也有一个Application,绑定到端口地址为:192.168.1.12:5000,显然它们绑定的都是Tun设备的IP,接着它们就通过这两个地址通信了。
假设左边的Application要给右边的Application发送一个数据包,流程是这样的:
左边的Application并不知道什么虚拟网络设备,它只知道往"192.168.1.12:5000"这个地址发送,左边主机系统首先按正常的发包过程处理,比如判断目的主机是不是位于同一网段等等,然后数据包就在Linux的网络协议栈中穿行。Tun设备并不是真实的物理网卡,它不知道把数据包往哪里送,但是这些经过了Linux网络协议栈的数据可以从Tun设备的文件描述符中读取到,图中的“User Program”就是监听这个描述符等待读取数据的。这个“UserProgram”程序绑定的端口地址是:172.16.1.11:6000,每当它从Tun设备读到数据的时候,就把这些数据从物理网卡发送出去,目标地址是右边的:172.168.1.12:6000。
数据到达右边的系统,经过网络协议栈之后到达“UserProgram”应用进程,“User Program”将接收到的数据往Tun设备对应的文件描述符写入。对于Tun设备来说,“写入”就像是物理网卡接受到数据包一样,因此这些接收到的数据又进入了Linux的网络协议栈,最终到达右边的Application。
可能有人迷糊了,这里的Application是指各种各样的用户程序,如ping工具。“User Program”是用来辅助Tun设备来实现隧道功能的,可以想象成是OpenVPN进程,没有它隧道就废了。
三. 内核配置驱动支持
DeviceDrivers => Network device support => Universal TUN/TAP device driversupport
四. 数据结构参数说明
struct tun_struct {
char name[8]; //设备名
unsigned long flags; //区分tun和tap设备
struct fasync_struct *fasync; //文件异步通知结构
wait_queue_head_t read_wait;//等待队列
struct net_device dev; //linux网络设备结构
struct sk_buff_head txq;//网络缓冲区队列
struct net_device_stats stats; //网卡状态信息结构
};
为了使用TUN/TAP设备,我们必须包含特定的头文件linux/if_tun.h,如5行所示。在21行,我们打开了字符设备/dev/net/tun。接下来我们需要为ioctl的TUNSETIFF命令初始化一个结构体ifr,一般的时候我们只需要关心其中的两个成员ifr_name,ifr_flags。ifr_name定义了要创建或者是打开的虚拟网络设备的名字,如果它为空或者是此网络设备不存在,内核将新建一个虚拟网络设备,并返回新建的虚拟网络设备的名字,同时文件描述符fd也将和此网络设备建立起关联。如果并没有指定网络设备的名字,内核将根据其类型自动选择tunXX和tapXX作为其名字。ifr_flags用来描述网络设备的一些属性,比如说是点对点设备还是以太网设备。详细的选项解释如下:
§ IFF_TUN:创建一个点对点设备
§ IFF_TAP:创建一个以太网设备
§ IFF_NO_PI:不包含包信息,默认的每个数据包当传到用户空间时,都将包含一个附加的包头来保存包信息
§ IFF_ONE_QUEUE:采用单一队列模式,即当数据包队列满的时候,由虚拟网络设备自已丢弃以后的数据包直到数据包队列再有空闲。
配置的时候,IFF_TUN和IFF_TAP必须择一,其他选项则可任意组合。其中IFF_NO_PI没有开启时所附加的包信息头如下:
Struct tun_pi
{
unsigned short flags;
unsigned short proto;
};
目前,flags只在收取数据包的时候有效,当它的TUN_PKT_STRIP标志被置时,表示当前的用户空间缓冲区太小,以致数据包被截断。proto成员表示发送/接收的数据包的协议。
上面代码中的文件描述符fd除了支持TUN_SETIFF和其他的常规ioctl命令外,还支持以下命令:
§ TUNSETNOCSUM:不做校验和校验。参数为int型的bool值。
§ TUNSETPERSIST:
把对应网络设备设置成持续模式,默认的虚拟网络设备,当其相关的文件符被关闭时,也将会伴随着与之相关的路由等信息同时消失。如果设置成持续模式,那么它将会被保留供以后使用。参数为int型的bool值。
§ TUNSETOWNER:设置网络设备的属主。参数类型为uid_t。
§ TUNSETLINK:设置网络设备的链路类型,此命令只有在虚拟网络设备关闭的情况下有效。参数为int型。