转自:http://linux.chinaunix.net/ebook/doc/2009/07/06/1121764.shtml Chinaunix网友:Minit
一、引言
本文分析了linux下netfilter/iptables的实现机制,主要集中于分析用户态与内核态之间规则的关系,以及在此基础上对用户规则的解析的生成,然后分析了扩展模块的实现原理,并介绍了如何写一个扩展模块。本文分析的内核源码为2.6.21.2,相应的iptables程序的版本为1.3.7。由于自身水平有限,且相关的参考资料较少,不能保证文中的分析结论一定正确,如果读者发现问题,望不吝与作者联系。
二、用户态规则与内核态规则的关系
在Netfilter/iptables实现机制中,实际对数据包进行过滤的是内核态的规则,但为了用户能够对内核态的规则进行操作,需要将内核态的规则信息读取到用户空间,对用户空间的规则进行修改后,再根据用户态的规则信息设置内核态的规则信息。用户从内核态中获取规则信息的方式如图2-1所示,在该图中表示了保存规则的结构--表在用户态和内核态间的对应关系.
在内核态中,表ipt_table中的private成员指向的xt_table_info保存了该表所管理的规则。用户通过getsockopt()从内核态获取表的基本信息及规则内容,并将其保存在用户态中。
在用户态中,表iptc_handle_t保存了表管理的规则,在该结构中分别由ipt_getinfo保存表的基本信息,由ipt_get_entries保存表中的规则,在后面会对这些结构体进行详细介绍。为了便于用户对规则的操作,需要将表iptc_handle_t内的规则信息由函数parse_table()进行相应的处理,其会将规则按照链表形式分类存放,存放的格式如图2-1所示,有关用户态中链和规则的结构会在后面介绍。
图2-1 用户获取内核态规则信息
当从内核态将表的内容读取到用户空间后,用户就可以对表的内容进行修改,如在表内添加、删除、修改自定义子链等,在已有链内添加、删除、修改规则等。当对表的内容修改完后就对这些内容进行相应处理,然后通过setsockopt()更新内核态对应的表空间的内容。
为了方便将用户态的表内容更新到内核态,需要对表内的所有规则进行相应的处理,函数iptcc_compile_table_prep()主要计算表内链与规则对应的偏移值,然后将所有规则通过函数iptcc_compile_table()添加到结构体ipt_replace中。内核就是根据ipt_replace的内容来更新其表空间的内容的,前面计算的偏移值主要是为了明确规则所处的位置。用户操作内核态表内容的计数值是通过ipt_counters_info结构来进行通信的,内核根据从用户态传来的该结构体的值更新相应的内容。
用户定制的命令由函数do_command()处理,该函数会根据用户的需求修改表iptc_handle_t的内容,当对表的内容进行修改后,会同步更新内核态表ipt_table的内容,图2-2表示了用户设置内核态规则的流程。
图2-2 用户设置内核态规则信息
三、用户态规则的管理
1. 表的结构:
用户态中表结构iptc_handle_t如下图3-1所示,其中的info指向ipt_getinfo结构,entries指向ipt_get_entries,这两个结构体的内容都是内核态传递过来的,其中ipt_get_info保存了表的基本信息,ipt_get_entries保存了该表所管理的规则信息,该结构中的entrytable所指向的内存位置保存了所有的规则ipt_entry。当在用户态中对表内容进行处理后,会把规则内容组织到链中保存,其中chains指向了这些链。chain_iterator_cur和rule_iterator_cur分别指向了当前遍历到的链与规则。
iptc_handle_t结构的内容实质上与内核态中表结构ipt_table所表示的内容是一致的,只是其所存储的空间位置不一样。
图3-1用户态中表的结构
2.表中链的结构:
表iptc_handle_t中的chains指向该表所管理的所有链,每一个链用一个chain_head结构保存,该结构体的内容如下图3-2所示,有关该结构体内各个成员的具体作用在此就不做详细分析,其中counters记录了该链的统计值,counter_map主要表示了对该链的统计值的操作,其在向内核态中设置相应的统计值时起作用。变量rules指向了该链所管理的所有规则,这些规则以链表的方式保存,一条规则用一个rule_head结构保存。
图3-2 用户态中链的结构
3.链中规则的结构及遍历:
规则rule_head的结构如下图3-3所示,在一条规则中,chain指针指向该规则所属的链,而jump指针指向该规则将要跳向的其他链。iptcc_rule_type表示了该规则所属的类型,如标准操作(ACCEPT、DROP等)、扩展动作(SNAT等)以及跳向其他链等等类型。为了方便内核态与用户态的信息交互,需要在交互前对表的内容进行处理,处理中就利用了链以及规则中的index,offset等变量值,有关处理的细节在此就不做详细分析。
图3-3 用户态中规则的结构
为了满足用户定制的策略,需要对表中的规则进行修改,这就需要实现对表中的规则的遍历,实现遍历的函数主要有:iptc_first_chain()、iptc_next_chain()、iptc_first_rule()、iptc_next_rule(),通过这些函数可以找到表中的第一条链,当前链的下一条链,链中的第一条规则,当前规则的下一条规则。
四、用户态规则的解析
用户定制的命令实际上是对iptc_handle_t指向的表里面的内容的操作,如下图4-1就表示了命令解析的流程,函数generate_entry()会按照用户制定的规则匹配条件及相应动作生成一条规则rule_head,详细生成的流程会在下一节描述。在解析用户命令时,则根据用户指定的表、链及动作,对表的内容进行操作。如添加一条规则时,就会调用iptc_append_entry()将规则rule_head加到相应的链中去,iptc_delete_entry()及iptc_insert_entry()则实现对规则的删除与插入。
图4-1 用户态规则的解析
五、用户态规则的生成
前面提到了在解析规则时,会将用户制定的匹配条件及动作转化为一条规则,转化的详细流程如下图5-1所示。从图中可以看出,用户态中规则的存储方式与内核中规则的存储方式是一样的,都是顺序存储。标准匹配条件部分会放在ipt_entry结构中,扩展匹配条件部分的转化相对较复杂,首先会调用函数find_match()在全局链表iptables_matches中查找相应的扩展模块,如果找到了就复制一个该扩展模块到链表matches中,matches实际上就保存了这条规则中指定的扩展匹配模块,这两个链表在后面讲述扩展模块时还会详细介绍。然后调用函数find_target()设置该规则对应的动作,如果不是标准动作则需要查找全局链表iptables_targets,以找到该动作对应的iptables_target。
当前面已经把规则的不同部分作了处理后,下一步就通过函数generate_entry()将这些内容整合成一条规则,规则的开始部分标准匹配条件ipt_entry,然后将matches指向的多个iptables_match中的ipt_entry_match结合在一起,形成规则的扩展匹配条件,最后根据前面处理的动作部分设置ipt_entry_target,形成规则的动作处理。动作处理中涉及到标准动作与扩展动作的不同处理,处理的具体细节在此不做详细分析。最后形成的规则如下图所示。
图5-1 用户态规则的生成
六、扩展模块
1.扩展匹配模块
扩展匹配模块的结构如图6-1所示,在该结构中有一个变量m指向匹配模块ipt_entry_match,该结构与内核态中的结构是一样的。用户态中的扩展匹配模块的存储与管理方式与内核态是相似的,模块的注册调用函数register_match()实现,该函数将用户设定的iptables_match模块添加到全局变量iptables_matches指向的双向链表中,该链表如图6-2所示。
在扩展匹配模块中定义了一些函数,这些函数主要用于处理跟该模块相关的规则,如其中parse()主要处理与该模块相关规则的参数信息,该函数的使用在后面小节会涉及到,这些函数的具体细节,在此不做分析。
图6-1 iptables_match结构
图6-2 iptables_match的存储
2.扩展动作模块
扩展动作模块的结构如图6-3所示,该结构与扩展匹配模块的结构相似,其由全局变量iptables_targets指向的链表保存,链表的形式如图6-4所示。
图6-3 iptables_target结构
图6-4 iptables_target的存储
七、用户态与内核态之间参数的传递
在扩展模块中参数在用户态与内核态间的传递中,扩展模块的parse()会首先将用户定制的匹配参数放置在用户空间的结构体ipt_XX_info(XX表示扩展模块的名字,该命名法为通用的),ipt_XX_info的内存位置与扩展模块中ipt_entry_match(ipt_entry_target)的data内存位置相同,故这两者实际保存用户空间中相同的内容。
当把扩展模块ipt_entry_match(ipt_entry_target)的内容传到内核中后,提取内核中扩展模块的data内容,并将其强制转化为内核空间的ipt_XX_info类型的结构,这样的话,内核态的扩展模块就可以通过ipt_XX_info结构获得用户设置的参数,然后就可以调用该模块的处理函数如match()(或target())处理。图7-1表示了以扩展匹配模块为例的参数传递过程。
图7-1 用户态向内核态传递参数
八、扩展模块的应用
通过前面的描述,可以看出扩展一个匹配模块需要做的工作。下面以扩展匹配模块为例,扩展动作模块的操作类似,主要包括以下内容:
1. 用户空间:
(1).定义一个iptables_match模块并初始化相应的参数值,在模块的加载函数中调用register_match()将iptables_match模块注册到全局链表iptables_matches中;
(2).定义用户态与内核态共用的数据结构ipt_XX_info,其主要用于交互数据;
2. 内核空间:
(1).定义一个ipt_match模块并初始化相应的参数值,在模块的加载函数中调用xt_register_match()将ipt_match模块注册到xt_af的相应链表中;
(2). 当完成前面的工作后,就需要修改程序的编译文件及内核配置文件,以目前我分析的内核版本2.6为例,则需要修改如下文件:
(a).在iptables/extensions/Makefile文件的PF_EXT_SLIB中新加模块名XX;
(b).在linux/net/ipv4/netfilter/Makefile中新加一行:obj-$(CONFIG_IP_NF_MATCH_XX) += ipt_XX.o
(c).在linux/net/ipv4/netfilter/Kconfig中新加以下行:
config IP_NF_XX
tristate "……"
depends on ……
help
…...
To compile it as a module, choose M here. If unsure, say N.
完成以上步骤后,重新配置内核并选取该模块,编译后即可使用该扩展模块了。
九、总结
本文主要目的是分析netfilter/iptables实现机制的大框架,很多细节问题并未涉及到,希望通过此分析文档对进一步了解该机制提供帮助,并为使用该机制提供指导。由于自身水平有限,其中存在的问题望读者谅解并不吝指正。