基于Linux的4over6隧道驱动
Linux中的设备分为字符设备、块设备和网络设备。4over6隧道设备是一种软件设备,同时也是一种网络设备。作为一个模块,它既可被动态地连接到正在运行的内核,也可以动态地解除连接。图2为4over6隧道虚拟设备内核驱动的结构。从图中可以看到,4over6隧道虚拟设备内核驱动主要包括基本隧道、隧道协议以及特定隧道。
内核模块加载与内核模块卸载这两个功能模块是让整个隧道正常工作的前提,前者进行一些隧道虚拟设备的初始化工作,后者进行与之对应的清理工作。隧道协议注册模块产生的隧道协议实体是报文解封装模块的基础,基本隧道实体注册模块产生的基本隧道实体是隧道创建模块的基础,而由隧道创建模块产生的特定隧道是隧道错误处理、报文封装及解封装等模块的共同基础。
隧道实体的内核定义
4over6隧道设备实质上是一种IPv6隧道,在Linux Kernel 2.4.20中,将IP隧道用结构ip_tunnel来表示,但是该结构的定义无法满足IPv6隧道的需求。参考struct ip_tunnel的定义,本文将IPv6隧道在内核中用结构ip6_tnl 来定义,其定义如下:
struct ip6_tnl {
struct ip6_tnl *next;
struct net_device *dev;
int recursion;
struct ip6_tnl_parm parms;};
其中next为指向下一个IPv6隧道实体的指针,dev指向该隧道实体所包含的虚拟设备对象,recursion为数据报递归封装的深度,parms则是隧道实体的属性对象,包括隧道名称、下一报头、隧道起始地址以及流标签等属性的IPv6隧道报头。

图2 4over6隧道虚拟设备内核驱动的结构
4over6隧道协议实体定义为:
struct inet6_protocol tunnel46_protocol = {
.handler = ip46_rcv,
.err_handler = ip46_err,
.flags = INET6_PROTO_NOPOLICY|INET6_PROTO_FINAL};
其中,ip46_rcv是隧道报文的接收处理函数,专门负责将收到的IPv6数据包进行重组(如果数据包在进入隧道端点时进行了分段处理),并解封装还原成为IPv4数据包后转交给上层模块来处理;ip46_err是隧道报文传输过程中的错误处理函数。在隧道设备的初始化的过程中,调用inet6_add_protocol函数,将该协议实体注册到Linux IPv6内核协议栈中。
由于在隧道设备实际运行时,隧道实体的定位与查找比较频繁,为了提高访问速度,将隧道实体以哈希表的形式进行组织。其中哈希表大小(HASHSIZE)为32,哈希函数以128位的IPv6地址为参数,将其划分为4个部分(每一部分为32位),HASH(IPv6地址) = Part1 ^ Part2 ^ Part3 ^ Part4 & (HASHSIZE - 1)。当一个起始地址被分别定位为local和remote的隧道时,通过TUNNEL(HASH(local)^HASH(Remote))便能确定其所在的桶。
隧道实体的配置接口
隧道实体为内核中的数据结构,而管理员在用户态下对其进行配置,因此需要一种用户态与内核态信息交互的机制。由于内核态和用户态使用不同的内存定义,所以二者之间不能直接访问对方的内存。而应该使用Linux中的用户和内核态内存交互函数:
unsigned long copy_from_user(void *to, const void *from, unsigned long n);
unsigned long copy_to_user (void * to, void * from, unsigned long len);
这两个函数均返回不能被复制的字节数,因此,如果完全复制成功,则返回值为0。4over6隧道实体在内核中主要体现为虚拟网络设备,因此配置程序采用传统网络设备的配置方法,即用ioctl系统调用来配置隧道。ioctl系统调用为设备驱动程序执行“命令”提供了一个设备特定的入口点。在用户空间内调用的ioctl函数一般具有如下原型:
int ioctl(int fd, int cmd, …)
通常原型中的“…”代表可变数目的参数表,cmd为命令字。在实际系统中,系统调用不会真正使用可变数目的参数,而是必须有精确定义的参数个数。每一个设备都可以定义自己的ioctl命令字,命令编号的范围是由SIOCDEVPRIVATE到SIOCDEVPRIVATE + 15。如果是上述这些命令,则会调用相关接口驱动程序的dev->do_ioctl。该函数接收和通用ioctl函数相同的struct ifreq *指针,其原型如下:
int (*do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd);
针对IPv6隧道,它定义了4个命令字,这些命令字分别是SIOCGETTUNNEL、SIOCADDTUNNEL、SIOCCHGTUNNEL、SIOCDELTUNNEL。其中SIOCGETTUNNEL命令用于获取隧道设备的相关属性信息,其他3个命令分别对应创建、更新以及删除隧道。用户空间通过ioctl系统调用,最终调用到内核中定义的函数ip6ip6_tnl_ioctl。管理员提供的信息通过copy_from_user函数,从用户态复制到内核态,而配置程序的反馈信息则通过copy_to_user函数,由内核态复制到用户态应用程序。
编程实现
基于上述设计思想,本文对4over6隧道驱动进行了编程实现。编程实现的操作系统采用Red Hat Linux 9 + Kernel 2.4.20作为源码,以GNU GCC为集成环境,开发语言采用C语言。
由于该驱动为内核态驱动,故以内核模块的方式实现并加载。基本隧道实体以哈希表的形式进行组织,提高了隧道访问速度。
隧道虚拟设备调用ip46_tnl_xmit函数对到达隧道入口处的原始IPv4报文进行封装,使其进入隧道;解封装函数ip46_rcv对到达隧道出口处的报文进行解封装处理,并通过4over6隧道协议实体注册到内核IPv6协议栈中;函数ip46_err对隧道传输中所出现的错误进行处理,并翻译为ICMPv4报文,发送给隧道入口节点;隧道配置接口定义为ip46_tnl_ioctl函数,它针对特定的命令执行相应的配置操作。
通过在Linux系统下加载并运行该4over6隧道驱动,跨越校园IPv6网络的两个IPv4主机之间不但能够成功地运行Web、FTP、Telnet等各种不同类型的应用,而且延时、丢包率、吞吐量等性能指标都接近甚至优于这两台IPv4主机之间直接互访的性能。
(作者单位为华东理工大学信息化办公室)
来源:《中国教育网络》2010年4月刊