LINUX2.2.x 的正式内核中实现了一个网络安全的框架,并在此框架基础上实现了包过滤,地址转换和透明代理的功能。其中实现包过滤功能的模块叫做ipchains。地址转换功能和透明代理功能由ipchains的规则触发,但它们在实现上是分开的。地址转换的实现和透明代理的实现相互影响,不能同时起作用。这是一个系列文章,后续文章包括《ip_masq_ftp的实现》、《ip_masq_mod的实现》、《ICMP 协议的地址伪装》、《linux2.2.x网络安全实现问答》、《linux2.2.x状态检测的实现》,后两篇即将发表。下面将分别讨论它们是如何实现的。
1. 网络安全框架
网络安全框架实现的要点是它在协议栈的一些位置上安排一些检查点,当收到的包或发出的包走到这些检查点时,就调用相应的检查函数匹配相应的规则,并决定对包的处理。IP协议栈的检查点的位置如下:
在图1.1中可以看到在IP协议栈的IP层,对收到的包调用call_in_firewall检查(并在检查后调用ip_route_input判断这个包是需要转发的包,还是发往本机上层的包),对转发的包调用call_fw_firewall检查,对发出的包调用call_out_firewall检查(如果是本机上层发出的包,在这之前会调用ip_route_output查找发出路由)。只有真正转发的包才会走遍三个检查点。对于发给本机或本机发出的包,只会走过一个检查点。
需要说明的是,在IP协议栈中,在图中(1)的位置,会判断包是否匹配透明代理规则(由call_in_firewall检查),如果是,转发的包将会被发送到防火墙的上层,由上层协议处理。图中(2)的位置会判断包是否是地址转换的包,如果是,将会做反向的地址转换。图(3),(4)是判断是否需要做地址转换(其中(3)处理ICMP的差错报文,处理之后会跳过call_fw_forward的检查;(4)处理转发包的地址转换,进入转换函数的条件由call_fw_forward的检查得出)。
在检查点上,调用函数的结构是firewall_ops,结构中包含协议及其优先级别。其结构如图所示:
如图1.2所示,每个协议族都会注册自己的firewall_ops(在2.2.x内核中只实现了IP协议族的firewall_ops),相同协议的多个firewall_ops按优先级从大到小链接成一个单向链表。firewall_ops的注册和注销由函数register_firewall和unregister_firewall完成。IP协议族的firewall_ops是ipfw_ops(在ip_fw.c文件中)。
firewall_ops 中定义的函数fw_input由call_in_firewall调用,fw_forward由call_fw_firewall 调用,fw_output 由 call_out_firewall 调用,所以如果要实现一个简单的防火墙,只需实现 firewall_ops 中的三个函数,并将它注册到系统的firewall_ops表中。它们会在检查点上由内核自动调用。
在检查点上的返回值有以下几种:
名称 取值 说明
FW_QUEUE 0 将包发往用户空间
FW_BLOCK 1 禁止包通过
FW_ACCEPT 2 允许包通过
FW_REJECT -1 禁止包通过并给源端发ICMP消息
FW_REDIRECT 3 需要做透明代理的包
FW_MASQUERADE 4 需要做地址伪装的包
FW_SKIP 5 跳过函数和规则
[表1.1]
在图1.2中可以看到,每个协议族都有一个对应的policy(检查点上缺省的返回值),如果所有的firewall_ops的检查都没有被匹配,则call_xx_firewall返回这个policy(默认为FW_ACCEPT)
2. 包过滤
包过滤( packet filter): 包过滤完成的功能是对进出防火墙的包进行禁止或放行的操作。它匹配源地址/源端口,目的地址/目的端口等参数,对同一连接的不同方向上的包它匹配到的规则不同,并且每次匹配它都遍历规则表。
在2.2.x中,包过滤的功能由ipchains实现(这个名称既代表一个用户空间的配置工具,也代表内核的包过滤实现)。首先它定义了一个firewall_ops结构,其中有在检查点上调用的函数,如下:
struct firewall_ops ipfw_ops=
{
NULL,
ipfw_forward_check,
ipfw_input_check,
ipfw_output_check,
PF_INET,
0 /* We don't even allow a fall through so we are last */
};
这些函数都调用同一个函数ip_fw_check对包做规则检查,不同之处在于其检查规则的集合不同。下面就分析它的规则是如何组织的。
规则的主要结构有两个,一个是chain,代表在input,forward,output三个检查点上所要检查的规则的总和; 另一个是rule,代表在每一个chain上所要匹配的规则。chain和rule都可以动态添加或删除,但是内核中缺省的三个chain(input chain, forward chain,output chain)不能被删除。其示意图如下:
如图2.1所示,input链在call_in_firewall时检查,forward链在call_fw_firewall时检查,output链在call_out_firewall时检查。每个链上的规则组成一个单向链表,按先后加入的顺序排列。
规则检查的选项有协议号,源地址/源端口,目的地址/目的端口,收到包的接口等。规则的组织如下图所示:
规则匹配时先确定在哪个检查点上,如果在call_in_firewall检查点上,匹配从input 链进入;如果在call_fw_firewall检查点上,匹配从forward链进入;如果在call_out_firewall检查点上,匹配从output链进入。在链上有一个规则的链表,还有一个缺省的policy(只有缺省链的policy有效),这个policy在所有规则没有匹配的情况下生效。 规则匹配的结果有两种,一个是指向一个新创建的链(不是缺省的链),或者是一个动作。如果是指向动作,匹配成功,返回。如果指向一个链,则跳到这个链上,匹配这个链的规则,同时也记录上一个链和上一条规则的后续规则(在ip_reent的prechain和prerule中)用于检策规则中的死循环,并且在链上的所有规则都没有匹配到的情况下,返回原来的链上的下一条规则。
3. 地址转换
地址转换( network address translation): 地址转换完成的功能是对进出防火墙的包修改其源地址/源端口或目的地址/目的端口,并将返回包做反向转换。示意图如下:
(图中的伪装结构在创建时总是以转发方向的源地址、源端口为源地址、源端口;转发方向的目的地址、目的端口为目的地址,目的端口;伪装地址一般是本机外网地址,伪装端口动态得到。所以回应包的目的地址就是伪装结构中的伪装地址,目的端口是伪装端口,在图中用紫色标识)
2.2.x实现的地址转换也被称作地址伪装(地址转换有很多概念,如静态转换,动态转换,地址伪装等,这些都统称为地址转换)。它是通过一个或多个地址(一般是INTERNET地址)的一个端口范围(缺省是61000-65096)将内部地址的请求映射到这个地址和端口范围上,使内部地址可以访问INTERNET。但它并不只实现这一个功能,它在这个基础上,还有一些扩展的功能。
如图3.2所示,在地址伪装(实现了源地址的地址伪装)的基础上还有一些扩展模块。其中ip_masq_mod是一些扩展的功能模块,如portfw实现目的地址转换(用于虚拟主机);ip_masq_app实现对应用协议中的动态地址和端口转换,如ftp协议中所传的地址和端口;ip_masq_user实现应用程序对内核中的地址伪装的数据结构控制。
实现这些功能的要点是在地址伪装的流程中加入一些检查点,在这些检查点上调用注册的地址伪装模块或应用协议地址伪装模块的相应的函数,完成所需的工作。流程如下:
如图3.3所示,在转发包的地址伪装处理流程中ip_masq_out_create调用注册的地址伪装模块的mmod_out_create函数;ip_masq_app_pkt_out调用注册的应用协议地址伪装模块的pkt_out函数。中间的处理是将包的源地址,源端口改为伪装地址,伪装端口。 对收到包的地址解伪装处理流程中ip_masq_in_create调用注册的地址伪装模块的mmod_in_create函数;ip_masq_app_pkt_in调用注册的应用协议地址伪装模块的pkt_in函数。中间的处理是将包的目的地址,目的端口改为伪装结构中的源地址,源端口。地址伪装模块和应用协议地址伪装模块的数据结构如图所示:
所有注册的ip_masq_app放在一个哈希表中,用端口,协议等做键值;所有注册的ip_masq_mod放在一个单向链表。注册和注销函数分别在ip_masq_mod.c和ip_masq_app.c中定义。
伪装结构放在三个哈希表中,示意图如下:
其中ip_masq_s_table在ip_fw_masquerade中使用,ip_masq_m_tabe在ip_fw_demasquerade中使用。ip_masq_d_table在代码中没有使用。
每一个做地址伪装的双向连接(包括TCP协议的双向,UDP协议的双向,ICMP协议的请求应答包)或单向请求都需创建一个地址伪装结构。