越剧演员何英照片:内核Iptables实现机制

来源:百度文库 编辑:九乡新闻网 时间:2024/04/26 01:40:25
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_lock、find_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函数返回非零就停止遍历,返回零就继续遍历下一条规则,##args为fn函数所对应得参数列表。通过该宏定义也可以看到如何在宏定义中定义可变参数列表,以及在定义体中如何引用可变参数列表
__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_ip、struct ipt_entry_match、struct ipt_entry_target部分的合法性。其中分别调用check_match、check_target检验ipt_entry_match、ipt_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_target、ipt_match、ipt_tables,且在iptables模块初始化过程中往ipt_target所指向的双向循环链表中添加ipt_standard_target、ipt_error_target,往ipt_match所指向的双向循环链表中添加tcp_matchstruct、udp_matchstruct、icmp_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表会在INPUT、FORWARD链上注册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:当verdict!=IPT_RETURN时,直接返回verdict,可能是ACCEPT、DROP、QUEUE(虽然在netfilter中还包含STOLEN、REPEAT,但是在iptables命令中却不包含这两个处理动作),当verdict==IPT_RETURN时,要返回到父链,而此时在返回位置所在规则的comefrom记录上个back规则的相对偏移量,从而通过规则的comefrom来实现规则跳转的回归。
àverdict>0:verdict为自定义链起始位置的相对偏移量,而且这个自定义链的起始位置
是第二条规则,不是target为error target类型的首规则。
②非standard target类型的情况:
àerror target类型的规则,因为遍历规则库每条自定义链的首规则不会遍历到,那么只有可能是表的最后一条规则才可以被遍历到,不过这好像也不大可能。因为每条内建链的末尾规则为默认策略为ACCEPT或DROP的规则,而每条自定义链的末尾规则是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,verdict为netfilter钩子节点函数的返回值(即NF_DROP、NF_ACCEPT、NF_STOLEN、NF_QUEUE、NF_REPEAT之一)。
(3)back记录返回规则(如果内建链遇到RETURN,则跳转到链的缺省策略的规则,如果自定义链遇到RETURN,则跳转到父内建链)。通过ADD_COUNTER记录通过规则的数据报个数及字节总数(数据报的字节总数不包含链路层数据长度)
4.   内核层如何实现ipt_matchipt_target类型的模块扩展:
主要扩展实现的数据结构:struct ipt_entry_match、struct ipt_entry_target、struct ipt_match、struct ipt_target
先介绍这两个数据结构:
(1)结构体struct ipt_match
checkentry:原型  int (*checkentry)(const char *tablename,const struct ipt_ip *ip,
void *matchinfo,unsigned int matchinfosize,unsigned int hook_mask);
if (m->u.kernel.match->checkentry
                   && !m->u.kernel.match->checkentry(name, ip, m->data,
                     m->u.match_size - sizeof(*m),
                     hookmask)) {/*对规则struct ipt_entry_match中的数据部分进行正确性检验*/
tablename:规则将要插入表的表名
ip:指向规则struct ipt_ip部分的指针
matchinfo:指向struct ipt_entry_match数据部分的指针
matchinfosize:struct ipt_entry_match数据部分的长度
hook_mask:规则来自的钩子节点即e->comefrom
函数返回零说明匹配中的数据内容含有错误
什么时候调用该函数?通过SO_SET_REPLACE命令将规则信息载入内核时,会对每个规则进行正确性检验,而检验每条规则是通过check_entry来实现的,check_entry分别调用到check_match、check_target,在check_match函数中调用到上述checkentry,对于struct ipt_target情况是在check_target函数中调用到。
内核扩展模块如何实现该函数?
主要是用于检验匹配模块的数据部分的正确性。
destroy:原型void (*destroy)(void *matchinfo, unsigned int matchinfosize);
什么时候调用该函数?
cleanup_entry会对规则中的每一个ipt_entry_match、ipt_entry_target进行清理工作,该函数用来减少对应模块的引用数,并且调用destroy来对一些预先分配的资源进行释放。
match:原型  int (*match)(const struct sk_buff *skb,
                   const struct net_device *in,
                   const struct net_device *out,
                   const void *matchinfo,
                   int offset,
const void *hdr,
u_int16_t datalen,
                   int *hotdrop);
主要是对数据报依据规则中指定匹配的信息内容进行过滤。如果返回非0表示匹配成功,返回0且hotdrop设为1,则表示该报文应当立刻丢弃。
matchinfo:规则中指定匹配的信息内容部分指针
offset:非零表示分片,零表示首分片
hdr:ip数据报的协议内容部分指针
datalen:ip数据报的协议内容长度
hotdrop:hotdrop一旦设置为1,表示该报文应当立刻丢弃
为什么设置hotdrop?因为在检验数据报时,发现数据报中存在一些错误或异常特征(可能是黑客的攻击特征)应立即丢弃,例如tcp_match中一发现是分片(黑客经常以分片的形式逃避防火墙的检验)便立即丢弃。
什么时候调用该函数?
iptables在遍历规则中的每个struct ipt_entry_match部分都要进行match匹配检验。
(2)结构体struct ipt_target
函数checkentry、destroy实现功能差不多
主要的不同在于函数target
target:原形unsigned int (*target)(struct sk_buff **pskb,
                            unsigned int hooknum,
                            const struct net_device *in,
                            const struct net_device *out,
                            const void *targinfo,
                            void *userdata);
该函数的返回值代表的意思不同(在standard target该字段为空),返回IPT_CONTINUE(#define IPT_CONTINUE 0xFFFFFFFF)表示继续该链中下一条规则的检验,非IPT_CONTINUE则返回值为verdict,verdict为netfilter钩子节点函数的返回值(即NF_DROP、NF_ACCEPT、NF_STOLEN、NF_QUEUE、NF_REPEAT之一)。
什么时候调用该函数?
iptables遍历完规则之后的规则动作处理(非标准的target会调用到)。