1.概述
在分析LINUX2.4.x网络安全的实现之前先简单介绍一下它里面包含的几个重要概念:netfilter、iptables、match、target、nf_sockopt_ops、网络安全功能点的实现。详细解释会在后面的分析中讲到。
首先是netfilter,它定义了协议栈中的检查点和在检查点上引用的数据结构,以及在检查点上对这些结构引用的过程。iptables定义了实现网络安全功能的规则的组织以及对规则的操作。一个规则中包含零个或多个match和一个target,规则组织沿用了LINUX2.2.x中的chain,rule的概念,但是增加了table的概念,这三者的关系是:table是实现某项功能所有规则的总和,chain是在某个检查点上所引用规则的集合,rule是一个单独的规则。match在规则中用于匹配数据包中的各项参数,每个match匹配特定的参数,所以一个规则中可以有多个match,这包括系统已定义的match,也包括通过内核模块另外添加的match。target在规则中决定如何处理匹配到的数据包,因此在target中实现了具体的网络安全功能。nf_sockopt_ops是在系统调用get/setssockopt中引用的数据结构,实现用户空间对规则的添加、删除、修改、查询等动作。以上的结构在使用之前必须先注册到系统中才能被引用。
LINUX2.4.x网络安全实现了包过滤,地址转换(包含了LINUX2.2.x中的地址伪装和透明代理功能并有其他扩展功能),连接跟踪(这是实现地址转换的基础,在它里面实现了对连接状态的记录和监控,与状态检测类似),Mangle(这是LINUX2.4.x新增的一个功能,它对数据包进行检查但不做禁止、丢弃或允许的判断)。实现这些功能点需要分别注册netfilter,iptables,match,target,nf_sockopt_ops的数据结构。如果实现其他新的功能,只需定义相应的结构并将它注册到系统中,并且通过用户空间的配置工具(这个配置工具也须支持新的结构)把它加入到规则中就可以了。这些结构在规则中自动被引用。
2.netfilter
netfilter定义了协议栈中的检查点和检查点上引用的数据结构以及对这些数据结构引用的过程。首先看看在检查点上引用的数据结构,如图所示:
图2.1 nf_hoo_ops数据结构的组织
图中ns_hook_ops就是在检查点上引用的结构。每个协议栈预先定义的8个链表数组用于保存这些结构,这些链表与协议栈中的检查点一一对应。在实际的应用中,这8个链表并不一定都被使用,比如在IPV4中,只定义了5个检查点,分别对应前5个链表。nf_hook_ops结构如下:
struct nf_hook_ops
{
struct list_head list;
nf_hookfn hook; /* 函数指针 */
int pf; /* 结构对应的协议栈号 */
int hooknum; /* 结构对应的检查点号*/
int priority; /* 结构的优先值 */
}; |
nf_register_hook函数将ns_hook_ops结构注册到这些链表上,链表的索引由结构中hooknum指定。同一链表上的结构按优先值由小到大排列。在检查点上引用这些结构时,以它们在链表上的先后顺序引用。
检查点由宏NF_HOOK定义。在检查点上,函数nf_hook_slow调用函数nf_iterate遍历对应链表并调用链表上的结构ns_hook_ops中定义的函数。如果结构中的函数返回NF_ACCEPT,则继续调用下一个结构中的函数;如果结构中的函数返回NF_DROP或NF_STOLEN或NF_QUEUE,则将这个值返回给nf_hook_slow;如果结构中的函数返回NF_REPEAT,则重复调用此结构上的函数;如果到了链表上的最后一个结构,则把这个结构中函数的返回值返回给ns_hook_slow。在ns_hook_slow中判断nf_iterate的返回值,如果是NF_ACCEPT,则允许数据包通过,并将数据包传递给协议栈中的下一个函数;如果是NF_DROP,则释放数据包,协议栈流程中断;如果是NF_STOLEN,同样中断协议栈的流程,但是没有释放这个数据包;如果是NF_QUEUE,则将这个包发送到用户空间处理,同时中断协议栈的流程。
检查点分布在协议栈的流程中,下图是IPV4中的检查点:
图2.2 IPV4中的检查点
图中检查点的名称如下:
检查点编号 检查点名称 检查点所在文件名
1 NF_IP_PRE_ROUTING ip_input.c
2 NF_IP_LOCAL_IN ip_input.c
3 NF_IP_FORWARD ip_forward.c
4 NF_IP_POST_ROUTING ip_output.c
5 NF_IP_LOCAL_OUT ip_output.c
表2.1 IPV4中检查点的名称
图中,ROUTE(1)处对收到的包做路由查找并判断这个包是需要转发的包还是发往本机上层的包,ROUTE(2)处查找发出包的路由。NF_IP_PRE_ROUTING处对所有传入IP层的数据包进行检查,在这之前,有关数据包的版本、长度、校验和等正确性检查已经完成。NF_IP_LOCAL_IN对发往本机上层的数据包进行检查。请注意这两个检查点与LINUX2.2.x中检查点的区别,在LINUX2.2.x没有区分发往本机上层包和需要转发的包,所以在做完地址解伪装之后又调用了一次路由查找函数,为解伪装之后的包查找路由。NF_IP_FORWARD处检查需要转发的数据包。NF_IP_POST_ROUTING处对所有向链路层传递的数据包进行检查,注意在此处数据包的路由已经确定。NF_IP_LOCAL_OUT对本机发出的包进行检查,此处的路由还没有确定,所以可以做目的地址转换。实现某个网络安全功能可能需要在多个检查点上注册相应的结构,在后面的分析中我们可以看到具体的例子。
3. iptables
iptables实现对规则的管理和访问。它里面有几个重要的数据结构ipt_entry,ipt_match,ipt_target,ipt_table,用于构造规则表。还有一个重要的函数ipt_do_table,用于遍历规则表并处理规则表上的结构。
ipt_entry是规则的数据结构,如下:
struct ipt_entry { struct ipt_ip ip; unsigned int nfcache; u_int16_t target_offset; /* target在规则中的偏移 */ u_int16_t next_offset; /* 下一条规则的偏移 */ unsigned int comefrom; struct ipt_counters counters; /* 匹配规则的数据包的统计计数 */ unsigned char elems[0]; }; |
在ipt_entry中ipt_ip是一个基本的match,它是固定的,用于匹配数据包的源地址/源端口、目的地址/目的端口、协议等。其他的match按需要添加,个数并不固定,所以在ipt_entry有一个变长的字符数组保存规则中match的指针,这些指针指向系统中注册的match。每个规则有一个target,决定数据包完全匹配规则后怎样处理这个数据包,它也是一个指向系统注册的target的指针,并且也放在前面提到的变长字符数组中。ipt_entry中的target_offset是target在规则中的偏移,偏移是从规则的起始地址到target所在位置的长度,还有一个变量next_offset指示下一条规则偏移,它其实就是本条规则的长度。
前面提到在iptables中沿用了LINUX2.2.x中的chain和rule的概念,那么在ipt_entry中如何区分chain和rule的哪?
我们知道chain是某个检查点上检查的规则的集合。除了默认的chain外,还可以创建新的chain。在iptables中,同一个chain里的规则是连续存放的。默认的chain的最后一条规则的target是chain的policy。用户创建的chain的最后一条规则的target的调用返回值是NF_RETURN,遍历过程将返回原来的chain。规则中的target也可以指定跳转到某个用户创建的chain上,这时它的target是ipt_stardard_target,并且这个target的verdict值大于0。如果在用户创建的chain上没有找到匹配的规则,遍历过程将返回到原来chain的下一条规则上。
ipt_match用于匹配数据包的参数,如TCP数据包中的标志位,ICMP协议中的类型等,每个match所感兴趣的参数都不一样,所以一条规则可能有多个match。ipt_target决定在数据包完全匹配规则后应做什么样的处理。这两个在使用之间都必须先注册到系统的链表中才能被规则引用。对这两个数据结构不做过多分析,读者可以自行参考源代码。
ipt_table是规则表的数据结构,如下:
struct ipt_table { struct list_head list; char name[IPT_TABLE_MAXNAMELEN]; struct ipt_replace table; /* 用户空间传递的规则表 */ unsigned int valid_hooks; /* 有效的检查点置位*/ rwlock_t lock; struct ipt_table_info private; /* 规则表在内核中的存储结构 */ struct module *me; }; |
在ipt_table中,ipt_replace是用户空间配置程序传递给内核的规则表,这个规则表不能直接使用,必须先根据它里面包含的match和target的名称将match和target转换成在内核注册的match和target的指针,还有一项重要的工作是检查规则表中是否有循环,如果有循环,要给用户空间的配置程序报告错误。转换之后的规则表存储在ipt_table_info中。valid_hooks指示与这个表相关的检查点,并把相应的位置为1。一个table中可以有多个chain,chain分为系统默认的chain(与table注册的检查点对应)和用户创建的chain。所有的table都注册放在一个链表中,而chain和rule则用偏移值next_offset连接成一个单向链表。用户空间的配置工具在添加、删除规则之前先把内核中的规则表取到用户空间,然后在用户空间做添加或删除的动作,然后再将修改过的规则表传递到内核空间,由内核空间的函数完成后续的转换和检查。
函数ipt_do_table遍历table上的规则,其实这个函数的指针就保存在nf_hook_ops结构中,并在检查点上被调用。调用这个函数时须指定它遍历的table的指针和调用它的检查点的值。检查点的值用来定位table中默认chain的位置,前面我们提到,默认的chain是和检查点对应的,在检查点上检查对应chain的规则。遍历规则,如果找到匹配的规则,则调用这条规则的target中定义的函数,并将它的返回值返回给调用ipt_do_table的函数,如果没有找到匹配的规则,则调用默认chain上最后一条规则的target定义的函数,这个函数的返回值就是这个chain的policy。
4. nf_sockopt_ops
前面提到LINUX2.4.x网络安全框架支持多种协议。规则的配置和查询通过系统调用get/setsockopt完成。在调用这两个系统调用时,不同协议使用的参数不同,所以每个实现网络安全功能的协议栈都定义了自己的nf_sockopt_ops结构并把它注册系统的链表中。在调用get/setsockopt时根据不同的参数决定引用哪一个nf_sockopt_ops来完成真正的工作。
5.网络安全功能点的实现
在LINUX2.4.x中实现网络安全的功能点需要做以下几件事情:一是定义nf_hook_ops结构,并将它注册到netfilter中;二是定义iptable,match,target结构,并将它注册到iptables中,如果需要还须注册nf_sockopt_ops结构以便处理特殊的get/setsockopt参数。下图就是IPV4中的功能点注册到netfilter中的nf_hook_ops结构:
图5.1 IPV4的功能点在各检查点上注册的结构
(图中conntrack代表连接跟踪;Filter代表包过滤;NAT(src)代表源地址转换,NAT(dst)代表目的地址转换;Mangle是LINUX2.4.x中新增的一个功能,完成对数据包的检查,但是不对数据包做禁止或放行的判断,与Filter不同。Mangle在LINUX
2.4.18
之前的实现中只在NF_IP_PRE_ROUTING,NF_IP_LOCAL_OUT两个检查点上注册了nf_hook_ops结构,在LINUX2.4.18之后的实现中在五个检查点上都注册了nf_look_ops结构。)
图中在每个检查点上,nf_hook_ops结构按调用的先后顺序从上而下排列。可以看到相同的功能点在不同的检查点上它的调用顺序并不相同,这与功能点所做的动作有关。比如在NF_IP_LOCAL_IN上假设Conntrack在Filter之前,如果数据包的状态在Conntrack中被记录而在Filter中被禁止,那么与这个数据包相关的状态就不会完整,浪费了一个Conntrack的结构,所以应该先调用Filter,如果它的返回值是NF_ACCEPT才调用Conntrack。
功能点上注册的ipt_table,ipt_match,ipt_target,nf_sockopt_ops结构如下表所示:
功能点名称 | ipt_table | ipt_match | ipt_target | nf_sockopt_ops |
Filter | packet_filter |
|
|
|
Nat | nat_table |
| ipt_snat_reg |
|
Conntrack |
|
|
| so_getorigdst |
Mangle | packet_mangler |
|
|
|
表5.1 功能点注册的数据结构
值得指出的是连接跟踪(Conntrack)没有注册任何规则表,说明它不需要规则来决定是否要作连接跟踪。同时它又注册了一个nf_sockopt_ops结构,这个结构处理参数SO_ORIGINAL_DST,用于得到透明代理的目的地址。有关地址转换和连接跟踪的详细分析会在以后的文章中介绍。
6. 小结
LINUX2.4.x中的网络安全实现比LINUX2.2.x有了明显的进步。首先是IPV4协议栈中的检查点的安排更加合理,避免在实现各功能点时不必要的函数调用。其次它的架构可扩展性很强。把功能实现和框架分离,使功能实现时不必修改框架而只是注册相应的结构就可完成。还有就是在这个网络安全框架中实现的功能也比LINUX2.2.x丰富,支持的协议栈的数量也比LINUX2.2.x多。在这里我们只分析了LINUX2.4.x网络安全实现的大概情况,具体的某个功能的实现会在后面的文章中分析,通过这些分析,我们将学会如何在LINUX2.4.x的网路安全框架中添加自己想要实现的功能,如何利用现有实现中的资源而有避免与现有的实现冲突。
作者简介:林风,独立撰稿人。熟悉LINUX网络安全技术。比较感兴趣的方向是网络协议栈的实现。写文章,是为整理思路,发现问题,与更多人分享经验,知识,或者教训。邮件地址是droplet@163.net,欢迎批评,鼓励或指正。