iptables

来源:百度文库 编辑:神马文学网 时间:2024/03/29 15:26:04
内核Iptables实现机制:(linux-2.4.20-8内核)2007-06-29 14:49

1.   iptables内核与应用层之间的交互介绍

Iptables内核与应用层之间的交互是通过五个命令来实现的:SO_GET_INFO、SO_GET_ENTRIES、SO_SET_REPLACE、SO_SET_ADD_COUNTERS(libiptc/libiptc.c文件中)、SO_IP_POOL(ippool/ippool.c文件中)

对于全局变量ipt_tables、ipt_match、ipt_target所指向的双向循环链表都共用一个信号量ipt_mutex,通过DECLARE_MUTEX(ipt_mutex);来申明该信号量,因为这些双向循环结构都为应用进程通过系统调用所访问,而对于内核中的软中断只是访问已注册的表、匹配、目标对象,所以必须申明信号量。

1.1   命令SO_GET_INFO

将内核中有关表的规则库的信息传递给用户进程(即struct ipt_table_info信息)。

重要的函数及数据结构

static inline struct ipt_table *

find_table_lock(const char *name, int *error, struct semaphore *mutex)

根据给定的表名name,在ipt_tables所指向的表链中寻找对应表的入口地址。若找到对应表,则返回对应表的入口地址,且保证信号量ipt_mutex是上锁的,否则返回空。与该函数相似的还有find_match_lockfind_target_lock,它们都调用到函数

static void *

find_inlist_lock(struct list_head *head,const char *name,const char *prefix,int *error,

struct semaphore *mutex)

1.2   命令SO_GET_ENTRIES

将内核中对应表的规则库内容传递给用户进程,在将对应表的规则库内容拷贝给用户空间时,如果是多cpu由于每个cpu都有相同规则库内容的拷贝,在每个cpu对应规则库中的每个规则所记录的数据包通过的分组与字节个数(struct ipt_entry中的counters)是不同的,所以当在将内核中对应表的规则库内容传递给用户进程时,必须首先将通过每条规则的数据包分组与字节个数汇总起来记录在counters数组中。

重要的函数、宏及数据结构

#define IPT_ENTRY_ITERATE(entries, size, fn, args...)  …… ip_tables.h中)

用来遍历表中的每个规则,遍历每个规则调用fn函数,若fn函数返回非零就停止遍历,返回零就继续遍历下一条规则,##argsfn函数所对应得参数列表。通过该宏定义也可以看到如何在宏定义中定义可变参数列表,以及在定义体中如何引用可变参数列表

__ret = fn(__entry , ## args);       

/*在分配存储空间,使得存储空间首地址对其在SMP_CACHE_BYTES边界位置上*/

#define SMP_ALIGN(x) (((x) + SMP_CACHE_BYTES-1) & ~(SMP_CACHE_BYTES-1))

#ifdef CONFIG_SMP/*对多处理器的处理*/

#define TABLE_OFFSET(t,p) (SMP_ALIGN((t)->size)*(p))

#else

#define TABLE_OFFSET(t,p) 0

#endif

为什么每个cpu所对应规则库信息的首地址对齐在SMP_CACHE_BYTES边界位置上?这主要是为了提高每个CPU的处理效率考虑的。在struct ipt_table_info结构体最后一个字段entries的申明方式char entries[0] ____cacheline_aligned;

#define ____cacheline_aligned      __attribute__((aligned(SMP_CACHE_BYTES)))  

这个申明使得entries所指向的规则存储空间以SMP_CACHE_BYTES对齐。

static int

copy_entries_to_user(unsigned int total_size,struct ipt_table *table,void *userptr)

将对应表的规则库内容拷贝给用户空间,其具体步骤如下:

①将规则库中的通过每条规则的数据包分组与字节个数(struct ipt_entry中的counters)汇总起来记录在counters数组中。

②将对应表的规则库内容拷贝给用户空间。

③将汇总后得到的有关通过每条规则的数据包分组与字节总个数counters传递到用户空间的规则计数信息中去。

④由于在内核中规则的匹配struct ipt_entry_match中的union区域是u.kernel.match而在用户应用进程中的union区域是u.user.name,所以传递给应用进程的有关规则信息中的匹配必须转换成match name,至于规则中的target与匹配类似。

1. 命令SO_SET_REPLACE

用于用户空间将其所新定义的规则库信息载入内核空间。

该命令在内核中主要实现了主要功能:

1.对用户空间所新定义的规则库信息进行正确性检验。

2.repl->entries所表示的新的表规则信息载入到内核中去,而内核里原有的旧规则信息被删除。

3.将旧规则信息中所记录的数据包通过计数重新累计,并记录在用户空间的数据指针repl->counters所指向的存储空间中。通过命令SO_GET_ENTRIES已经在前面获得过规则的数据包通过个数,为什么这里还需要重新获得数据包通过个数?原因前面通过命令SO_GET_ENTRIES获得内核中的旧规则信息之后,内核中对应的规则还有数据包流过,为了保持规则中所记录的数据包通过个数的最新记录,必须再重新计算获得。等重新计算完之后,内核中旧的规则信息也被更替为用户空间所新定义的规则库信息,重新计算得到的规则通过个数也返回给用户空间的数据指针repl->counters所指向的存储空间中,在用户空间中结合counter_map所记录的计数映射信息重新计算最新规则的数据包通过个数,然后通过命令SO_SET_ADD_COUNTERS,将其同内核中所记录的规则数据包通过个数进行相加。

重要的函数、宏及数据结构

static int  do_replace(void *user, unsigned int len)

如果是多cpu,必须为每个cpu分配同一规则库信息的相同拷贝,且保证每个cpu所对应的规则库信息首地址必须在SMP_CACHE_BYTES边界上对齐。

static int

translate_table(const char *name,

              unsigned int valid_hooks,

              struct ipt_table_info *newinfo,

              unsigned int size,

              unsigned int number,

              const unsigned int *hook_entries,

              const unsigned int *underflows)

其具体实现的步骤如下:

通过check_entry_size_and_hooks来检验每条规则的大小、首地址边界是否对齐,以及每条内建链的首规则相对偏移量的合法性。

通过mark_source_chains标记每条规则中的comefrom标志位,以及检验规则库中是否出现规则递归跳转。

③通过check_entry检验规则中每一部分的合法性,包括struct ipt_ipstruct ipt_entry_matchstruct ipt_entry_target部分的合法性。其中分别调用check_matchcheck_target检验ipt_entry_matchipt_entry_target的合法性,都是通过结构体中的checkentry单元来检验其合法性,并且都要对对应模块的引用数进行递增操作,因为新定义的规则中有引用到对应模块的操作。

static struct ipt_table_info *

replace_table(struct ipt_table *table,

             unsigned int num_counters,

             struct ipt_table_info *newinfo,

             int *error)

用新的规则信息代替旧的规则库信息,并且返回旧的规则库信息。

1. 命令SO_SET_ADD_COUNTERS(可参见上一个命令介绍)

2.   iptables初始化过程

通过宏LIST_HEAD来申明struct list_head类型的静态全局变量ipt_targetipt_matchipt_tables,且在iptables模块初始化过程中往ipt_target所指向的双向循环链表中添加ipt_standard_targetipt_error_target,往ipt_match所指向的双向循环链表中添加tcp_matchstructudp_matchstructicmp_matchstruct

重要的函数、宏及数据结构

3.   iptables内核中的规则遍历过程

表的数据结构为struct ipt_table,通过ipt_register_table来注册表。在ipt_register_table会先增加ip_tables模块的引用数,因为在iptables_filter.c中调用ipt_register_table进行表注册,也就是iptables_filter模块引用了它,所以要增加ip_tables模块的引用数。与之相反,ipt_unregister_table就是注销表模块,会使ip_tables模块的引用数减一。

每个表都会通过nf_register_hook在钩子节点上注册函数,例如filter表会在INPUTFORWARD链上注册ipt_hook函数,OUTPUT链上注册ipt_local_out_hook函数,这两个函数都调用到ipt_do_table函数。

重要的函数、宏及数据结构

unsigned int

ipt_do_table(struct sk_buff **pskb,

            unsigned int hook,

            const struct net_device *in,

            const struct net_device *out,

            struct ipt_table *table,

            void *userdata)

其实现的主要步骤:

1)获得对应cpu的规则库信息入口地址,table_base便为对应cpu的规则库信息入口地址,通过cpu_number_map(smp_processor_id())获得cpu编号。

2)开始遍历钩子节点对应链上的各个规则。首先检验是否符合规则中struct ipt_ip部分的约定,然后检验各个struct ipt_entry_match部分,最后检验ipt_entry_target。对ipt_entry_target即规则的最后动作处理稍微有点复杂,分成几种情况:

①若为standard target类型的情况:

àverdict<0:当verdict=IPT_RETURN时,直接返回verdict,可能是ACCEPTDROPQUEUE(虽然在netfilter中还包含STOLENREPEAT,但是在iptables命令中却不包含这两个处理动作),当verdict==IPT_RETURN时,要返回到父链,而此时在返回位置所在规则的comefrom记录上个back规则的相对偏移量,从而通过规则的comefrom来实现规则跳转的回归。

àverdict>0verdict为自定义链起始位置的相对偏移量,而且这个自定义链的起始位置

是第二条规则,不是targeterror target类型的首规则。

②非standard target类型的情况:

àerror target类型的规则,因为遍历规则库每条自定义链的首规则不会遍历到,那么只有可能是表的最后一条规则才可以被遍历到,不过这好像也不大可能。因为每条内建链的末尾规则为默认策略为ACCEPTDROP的规则,而每条自定义链的末尾规则是RETURN规则。

à用户扩展的内核实现模块:这时verdict= t->u.kernel.target->target(pskb,hook,in, out,t->data,

userdata); 该函数的返回值的意思(在standard target该字段为空):

IPT_CONTINUE#define IPT_CONTINUE 0xFFFFFFFF)表示继续该链中下一条规则的检验。

IPT_CONTINUE则返回值为verdict,verdictnetfilter钩子节点函数的返回值(即NF_DROPNF_ACCEPTNF_STOLENNF_QUEUENF_REPEAT之一)。

3back记录返回规则(如果内建链遇到RETURN,则跳转到链的缺省策略的规则,如果自定义链遇到RETURN,则跳转到父内建链)。通过ADD_COUNTER记录通过规则的数据报个数及字节总数(数据报的字节总数不包含链路层数据长度)