文/朱尚明 李京 党齐民
铺设IPv4/IPv6“双行道”
文/朱尚明 李京 党齐民
在Linux系统下加载并运行4over6隧道驱动,有效解决IPv4网络通过IPv6主干网的互联互通。
目前,大多数隧道机制都集中在解决IPv6边缘网络通过IPv4主干网互通的问题,而关于IPv4网络如何通过IPv6主干网互通问题的解决方案却很少提及。而且,由于当前IPv6网络还处于起步阶段,IPv6业务的大规模部署还有待时日,造成IPv6骨干网中的大量带宽闲置,利用隧道穿越IPv6骨干网来传输现有IPv4业务的数据也可以实现IPv6带宽资源的充分利用。因此大量的IPv4网络通过IPv6骨干网实现互联是目前研究的一个热点,4over6隧道就是一种用于解决现存的IPv4网络通过IPv6主干网互联互通的机制。
4over6隧道机制主要包括两方面的功能:控制平面和数据平面。控制平面解决的问题是如何通过隧道端点发现机制来建立4over6隧道,而数据平面则主要关注包括封装和解封装的分组转发处理,如图1所示。
4over6控制平面处理
4over6机制中,控制平面主要解决隧道端点发现和网络可达性信息传输的问题。4over6控制平面通过对边界网关协议多协议扩展MP-BGP,再进行4over6扩展来实现。当4over6路由器的IPv4路由信息发生变化时,路由管理模块通知BGP协议,启动了4over6功能的BGP协议发送Update分组到其他对端路由器。对端4over6路由器收到Update分组后,在更新本地维护的封装表的同时,更新本地IPv4路由表,把相应目的I P v 4地址的出接口设置为本地4over6虚接口。控制平面的数据流动方向见图1的Control flow方向。
4over6数据平面处理
4over6数据平面主要包括3个部分:1.入口PE路由器将接收到的IPv4分组用IPv6头部进行封装;2.封装后的分组在IPv6骨干网中进行传输;3.出口PE路由器将分组解封装为原IPv4分组格式。基于4over6分组传输的特点,数据分组的封装和解封装在边界路由器PE(双栈路由器)上进行。在实现时,每个4over6 PE路由器维护一个4over6虚接口,该虚接口通过维护4over6封装表来处理分组的封装和解封装。该封装表的每个表项中包含了目的IPv4网络的地址和掩码,以及需要转发到的出口PE路由器的4over6虚接口的IPv6地址。
当IPv4数据分组到达入口边界路由器PE1时,PE1通过查找转发表,发现并进入本地虚接口进行处理。在4over6虚接口处理中,通过查找4over6封装表对分组进行封装,封装的目的IPv6地址是出口边界路由器PE2的虚接口IPv6地址,源地址为PE1的虚接口IPv6地址。经过封装后的分组通过IPv6骨干网络传输到出口边界路由器PE2。在收到分组后,PE2对分组进行解封装,再通过查找IPv4转发表将原始分组转发给相应的IPv4网络。数据平面的数据流动方向见图1的Data flow方向。
图1 4over6机制
Linux中的设备分为字符设备、块设备和网络设备。4over6隧道设备是一种软件设备,同时也是一种网络设备。作为一个模块,它既可被动态地连接到正在运行的内核,也可以动态地解除连接。图2为4over6隧道虚拟设备内核驱动的结构(见下页)。从图中可以看到,4over6隧道虚拟设备内核驱动主要包括基本隧道、隧道协议以及特定隧道。
内核模块加载与内核模块卸载这两个功能模块是让整个隧道正常工作的前提,前者进行一些隧道虚拟设备的初始化工作,后者进行与之对应的清理工作。隧道协议注册模块产生的隧道协议实体是报文解封装模块的基础,基本隧道实体注册模块产生的基本隧道实体是隧道创建模块的基础,而由隧道创建模块产生的特定隧道是隧道错误处理、报文封装及解封装等模块的共同基础。
隧道实体的内核定义
4over6隧道设备实质上是一种IPv6隧道,在Linux Kernel 2.4.20中,将IP隧道用结构ip_tunnel来表示,但是该结构的定义无法满足IPv6隧道的需求。参考struct ip_tunnel的定义,本文将IPv6隧道在内核
图2 4over6隧道虚拟设备内核驱动的结构
中用结构ip6_tnl 来定义,其定义如下:
其中next为指向下一个IPv6隧道实体的指针,dev指向该隧道实体所包含的虚拟设备对象,recursion为数据报递归封装的深度,parms则是隧道实体的属性对象,包括隧道名称、下一报头、隧道起始地址以及流标签等属性的IPv6隧道报头。
4over6隧道协议实体定义为:
其中,ip46_rcv是隧道报文的接收处理函数,专门负责将收到的IPv6数据包进行重组(如果数据包在进入隧道端点时进行了分段处理),并解封装还原成为IPv4数据包后转交给上层模块来处理;ip46_err是隧道报文传输过程中的错误处理函数。在隧道设备的初始化的过程中,调用inet6_add_protocol函数,将该协议实体注册到Linux IPv6内核协议栈中。
由于在隧道设备实际运行时,隧道实体的定位与查找比较频繁,为了提高访问速度,将隧道实体以哈希表的形式进行组织。其中哈希表大小(HASHSIZE)为32,哈希函数以128位的IPv6地址为参数,将其划分为4个部分(每一部分为32位),HASH(IPv6地址) = Part1 ^ Part2 ^ Part3 ^ Par&t4(HASHSIZE - 1)。当一个起始地址被分别定位为local和remote的隧道时,通过TUNNEL(HASH(local)^HASH(Remote))便能确定其所在的桶。
隧道实体的配置接口
隧道实体为内核中的数据结构,而管理员在用户态下对其进行配置,因此需要一种用户态与内核态信息交互的机制。由于内核态和用户态使用不同的内存定义,所以二者之间不能直接访问对方的内存。而应该使用Linux中的用户和内核态内存交互函数:
这两个函数均返回不能被复制的字节数,因此,如果完全复制成功,则返回值为0。4over6隧道实体在内核中主要体现为虚拟网络设备,因此配置程序采用传统网络设备的配置方法,即用ioctl系统调用来配置隧道。ioctl系统调用为设备驱动程序执行“命令”提供了一个设备特定的入口点。在用户空间内调用的ioctl函数一般具有如下原型:
通常原型中的“…”代表可变数目的参数表,cmd为命令字。在实际系统中,系统调用不会真正使用可变数目的参数,而是必须有精确定义的参数个数。每一个设备都可以定义自己的ioctl命令字,命令编号的范围是由SIOCDEVPRIVATE到SIOCDEVPRIVATE + 15。如果是上述这些命令,则会调用相关接口驱动程序的dev->do_ioctl。该函数接收和通用ioctl函数相同的struct ifreq *指针,其原型如下:
针对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主机之间直接互访的性能。
(作者单位为华东理工大学信息化办公室)