吴 华,邓 波
(中国电子科技集团公司第三十研究所,四川 成都 610041)
随着互联网应用日益普及,越来越多的电子设备可以接入互联网,导致可用IPv4 地址资源愈发紧缺。为解决这一难题,本文引入了网络地址转换技术[1](Network Address Translation,NAT),让原本无法上网并且使用内部因特网互联协议(Internet Protocol,IP)地址的电子设备可以成功连接互联网。
Linux 操作系统是一个开源的操作系统,具有高效性、灵活性和良好的网络性能等特点,大量应用于各类电子设备。Netfilter 是Linux 3.10 内核的一个子系统,可以完成数据包过滤、连接跟踪、地址转换等重要功能,是一个结构合理、功能强大、扩展性强的网络架构[2-3]。本文将分析Linux 操作系统的Netfilter 框架和NAT 工作原理,并讨论其具体的功能实现进。此外,本文基于Netfilter 框架和Linux系统,设计了一种新的进程间通信方式ukcomm。
Netfilter 在传输控制协议/因特网互联协议(Transmission Control protocol/Internet Protocol,TCP/IP)协议栈中定义了5 个检查点(hook)和相应的数据结构,规定了在检查点上对这些数据结构引用的过程。用户只需要在检查点注册一些处理函数(钩子函数),编写符合自己需求的过滤规则,就可对从检查点获取的数据包进行修改、丢弃或发送到用户空间等处理[4]。
Linux 系统的Netfilter 部署于内核态,它在TCP/IP 协议栈的数据转发/处理流程中提供了5 个钩子函数挂接点[5],如图1中5个虚线椭圆框图所示。
图1 Netfilter 框架流程
这5 个钩子函数挂接点具体功能如下文所述。
(1)PRE_ROUTING:位于被路由代码处理之前,因此所有收到的包在进一步接受任何处理之前都必须经过该钩子函数进行处理,可实现非法网络包的快速丢弃。
(2)LOCAL_IN:所有地址指向本网卡的传入包都需要经过该钩子函数的处理,在此,iptables模块提供的INPUT 规则列表来筛选传入的数据包。
(3)FORWARD:所有需要在内外网接口、不同网卡间转发的报文都需经过该钩子函数的处理。
(4)LOCAL_OUT:本网卡发送的报文首先经过该钩子函数挂接点进行处理,在此,iptables 模块提供OUTPUT 规则列表来筛选外发的数据包。
(5)POST_ROUTING:是在所有外发包通过网络离开本网卡之前访问它们的最后机会,可以对本单元主动发送或者转发的报文进行最后的过滤/NAT 等处理。
网络接口驱动收到数据包后,会先进行循环冗余校核(Cyclic Redundancy Check,CRC)校验和报文合法性检测;通过检测的数据包会先进入PRE_ROUTING 检查点,由协议栈查询路由决定数据包是发送到本地处理还是转发给其他网络设备;发送到本地处理的数据包将进入LOCAL_IN 检查点;需要转发的数据包将进入FORWARD 检查点,然后通过POST_ROUTING 检查点后由网络接口驱动输出;而需要本地处理的数据包将继续通过LOCAL_OUT检查点和POST_ROUTING 检查点后进入本地网络处理[6-8]。
每一个检查点注册的钩子函数,都必须返回一个值来通知内核,由内核进行相应的处理。钩子函数的返回值定义如下文所述。
(1)NF_DROP:数据包丢弃,内核释放为他分配的资源;
(2)NF_ACCEPT:接收数据包,内核继续传送报文;
(3)NF_STOLEN:数据包由钩子函数处理,内核不再继续传送;
(4)NF_QUEUE:数据包发送到用户程序处理,内核不再进行操作;
(5)NF_REPEAT:再次调用该钩子函数。
NAT 技术是指替换IP 报文头部的地址信息。该技术通常部署在网络出口位置,实现私有IP 地址与公网IP 地址间的转换[9]。
根据开放式系统互联参考模型(Open System Interconnect Reference Model,OSI)的分层结构,网络层将数据封装成数据包时,包含两个重要的信息:源IP 地址(发送数据包的网络设备接口IP 地址)及目的IP 地址(接收数据包的网络设备接口IP 地址)[10]。具有NAT 功能的网络设备收到数据包后,按既定的规则对IP 数据包中的源IP 地址或目的IP地址进行转换,然后将数据包转发至外网或内网。
Linux 系统支持模块化机制,因此可以利用可加载模块对Netfilter 进行扩展[11]。
NAT 地址转换模块就是编写为一个内核态模块程序,程序编译成ko 文件格式,通过Linux 系统自带的insmod 和rmmod 命令动态地加载进内核或从内核卸载。
NAT 地址转换模块向上接收应用程序下发的NAT 地址转换指示,向下依赖于Linux 操作系统提供的Netfilter 实现报文获取。应用程序与NAT 地址转换模块之间的通信通过新设计的ukComm 实现。如图2 所示。
图2 NAT 功能模块工作位置
Linux 系统中用户态和内核态之间的通信方式主要包括系统调用、文件系统和NetLink 套接字等方式[12-13]。然而,在Linux 下要实现用户态程序调用设备中自开发内核模块的某种功能,不可能直接采用系统调用方式;但如果基于Netlink 这种异步通信方式又比较复杂;此外,如果每个内核模块开发自己的为字符设备驱动,则代码非常冗余。为此,特开发ukComm 模块,为所有需要通过同步方式调用内核态模块功能的用户态/内核态程序开发一套公用的调用接口。
ukComm 模块基于伪字符设备驱动,通过ioctl实现用户态到内核态的统一同步调用接口,由用户态与内核态两个部分组成,软件架构如图3 所示。
图3 ukComm 软件模块架构
ukComm 用户态:为用户态程序提供统一调用接口,接口形式为int uk_ioctl(int cmd,char*buf),通过不同的cmd 编码即可实现调用内核态的不同“函数”,而buf 用于实现信息的读取或者写入。
ukComm 内核态:为内核态程序提供统一调用接口,接口形式为int uk_ioctl_reg (int cmd,ioctl_func func),内核态程序通过该接口注册对应某命令(cmd)的处理函数。
ukComm 通信处理流程如图4 所示。
图4 ukComm 通信处理流程
在系统初始化过程中,各个支持通过ukComm向用户态提供“函数调用”接口的内核模块调用uk_ioctl_reg 函数到ukComm 内核态模块注册自己所支持的ioctl 命令以及所对应处理函数。随后系统运行中,用户态的程序通过uk_ioctl 调用内核态程序提供的“函数”,其总体处理流程如下:
(1)ukComm 用户态程序打开ukComm 内核态模块提供的伪字符设备,如果未能成功打开该伪字符设备则返回失败,否则获取到文件句柄,继续下一步处理;
(2)ukComm 用户态程序调用ioctl 函数,传入基于上述步骤中得到的文件句柄、用户程序给出的命令(cmd)和参数信息(buf);
(3)ukComm 内核态程序基于cmd 查找对应该处理函数,如果查询不到则返回失败,否则调用该处理函数,该处理函数的返回值作为本次ioctl 的返回值;
(4)ukComm 用户态程序关闭本次打开伪字符设备的文件句柄,返回ioctl 函数的返回值。
NAT 地址转换模块通过ukComm 接收应用程序的指示,将应用程序下发的地址转换映射关系保存在一个数组nat_addr_maps[]中,其结构如下:
利 用Netfilter 可扩展 的框架,在PRE_ROUTING 检查点可以对除本地发出的数据包外的所有数据进行处理;在POST_ROUTING 检查点对本地发出的报文进行处理。
本文对来自内网的数据,在POST_ROUTING检查点创建钩子函数对源地址进行转换;对来自外网的数据,在PRE_ROUTING 检查点创建钩子函数对目的地址进行转换。
Netfilter 提供了非常简单的接口函数,编程人员可以非常方便地将自己写的钩子函数添加到Netfilter 中而被其调用。Netfilter 提供了int nf_register_hook(struct nf_hook_ops *ops)接口函数来在某一个检查点注册一个钩子函数,ops 只是一个数据结构。本文所用的两个检查点钩子函数的注册处理如下:
内核模块的退出处理可以调用nf_unregister_ hook(&nfInputHook) 和nf_unregister_hook(&nfOutput Hook)完成两个钩子函数的卸载。
以nf_hookfn 函数为模块编写钩子函数input_hook_func 和output_hook_func,nf_hookfn 函数的原型为:
nf_hookfn 函数的5 个参数由NF_HOOK 宏进行传递:第1 个参数用于指定注册钩子函数的检查点;第2 个参数用于指向一个sk_buff 数据结构[14-16],即数据包的地址;第3 个参数和第4 个参数用于描述网络接口,参数in 用来描述数据包到达的接口,参数out 用来描述数据包离开的接口。通常情况下,参数in 用于PRE_ROUTING 检查点的钩子函数,参数out 用于POST_ROUTING 检查点的钩子函数,两个参数中只有一个被提供。第5 个参数函数okfn 是当对应检查点的钩子函数注册为空时,Netfilter 调用的处理函数,也是钩子函数返回NF_ACCEPT 时Netfilter 调用的处理函数。
钩子函数的具体实现如下:
其中find_innet_addr 和find_outnet_addr 都是从地址转换映射表nat_addr_maps 中查找对应的转换IP,若映射表中为查询到对应的转换IP 则无需进行转换,直接将输入的参数返回。
本文通过分析Netfilter 的框架和Linux 系统可动态加载的内核模块机制,利用基于伪字符设备驱动设计的ukComm 模块,实现了一个NAT 地址转换模块。该模块可根据用户下发的策略,进行源地址转换和目的地址转换,有助于缓解IP 地址不足的问题,还能有效避免来自网络外部的攻击,达到隐藏并保护网络内部IP 的目的。此外,NAT 功能模块采用Netfilter 框架,具有良好的代码结构,易于维护和扩展,运行在Linux 内核态,所以运行非常高效。