msn: [email protected]
来源:http://yfydz.cublog.cn
1. 前言 对于多连接协议,netfilter需要对其进行特殊的跟踪和NAT以提供动态的对子连接的支持,详见“防火墙为什么要对多连接协议进行特殊处理”一文。netfilter对这些多连接协议的跟踪和NAT和匹配、目标或IP层协议那样进行模块化的跟踪和NAT处理。 以下内核代码说明都使用Linux-2.4.26版本的内核代码。 2. 协议连接跟踪 2.1 ip_conntrack_helper结构 netfilter中对每个要进行跟踪的多连接协议定义了以下的连接辅助结构,每个多连接协议的连接跟踪处理就是要填写这样一个结构: include/linux/netfilter_ipv4/ip_conntrack_helper.h struct ip_conntrack_helper { struct list_head list; /* Internal use. */ const char *name; /* name of the module */ unsigned char flags; /* Flags (see above) */ struct module *me; /* pointer to self */ unsigned int max_expected; /* Maximum number of concurrent * expected connections */ unsigned int timeout; /* timeout for expecteds */ /* Mask of things we will help (compared against server response) */ struct ip_conntrack_tuple tuple; struct ip_conntrack_tuple mask; /* Function to call when data passes; return verdict, or -1 to invalidate. */ int (*help)(const struct iphdr *, size_t len, struct ip_conntrack *ct, enum ip_conntrack_info conntrackinfo); }; 结构中包括以下参数: struct list_head list:将该结构挂接到多连接协议跟踪链表helpers中, helpers链表在ip_conntrack_core.c文件中定义: static LIST_HEAD(helpers); 注意是static的,只在该文件范围有效; const char *name:协议名称,字符串常量 unsigned char flags:关于本连接跟踪模块的一些标志; struct module *me:指向模块本身,统计模块是否被使用 unsigned int max_expected:子连接的数量,这只是表示主连接在每个时刻所拥有的的子连接的数量,而不是主连接的整个生存期内总共生成的子连接的数量,如 FTP,不论传多少个文件,建立多少个子连接,每个时刻主连接最多只有一个子连接,一个子连接结束前按协议是不能再派生出第二个子连接的,所以该值为1; unsigned int timeoout:超时,指在多少时间范围内子连接没有建立的话子连接跟踪失效 struct ip_conntrack_tuple tuple, mask:这两个参数用来描述子连接,判断一个新来的连接是否是主连接期待的子连接,之所以要有mask参数,是因为子连接的某些参数不能确定,如被动模式的FTP传输,只能得到子连接的目的端口而不能确定源端口,所以源端口部分要用mask来进行泛匹配; 结构中包括以下函数: (*help):连接跟踪基本函数,解析主连接的通信内容,提取出关于子连接的信息,将子连接信息填充到一个struct ip_conntrack_expect结构中,然后将此结构通过调用函数ip_conntrack_expect_related()把子连接的信息添加到系统的期待子连接链表ip_conntrack_expect_list中。返回值是NF_ACCEPT或NF_DROP,或-1表示协议数据非法。该函数在ip_conntrack_in()函数中调用。 2.2 期待的连接的结构 期待的连接结构用来描述所期待的连接,但该连接目前是不存在的,是一个虚构的连接,只是netfilter根据主连接的信息解析出来的可能的子连接的信息,struct ip_conntrack_helper结构中的(*help)成员函数的目的就是建立一个struct ip_conntrack_expect结构: struct ip_conntrack_expect { /* Internal linked list (global expectation list) */ struct list_head list; /* reference count */ atomic_t use; /* expectation list for this master */ struct list_head expected_list; /* The conntrack of the master connection */ struct ip_conntrack *expectant; /* The conntrack of the sibling connection, set after * expectation arrived */ struct ip_conntrack *sibling; /* Tuple saved for conntrack */ struct ip_conntrack_tuple ct_tuple; /* Timer function; deletes the expectation. */ struct timer_list timeout; /* Data filled out by the conntrack helpers follow: */ /* We expect this tuple, with the following mask */ struct ip_conntrack_tuple tuple, mask; /* Function to call after setup and insertion */ int (*expectfn)(struct ip_conntrack *new); /* At which sequence number did this expectation occur */ u_int32_t seq; union ip_conntrack_expect_proto proto; union ip_conntrack_expect_help help; }; 结构中包括以下参数: struct list_head list:将该结构挂接到期待连接链表 ip_conntrack_expect_list中; atomic_t use:该期待连接结构的使用次数; struct list_head expected_list:主连接的期待的子连接的链表; struct ip_conntrack *expectant:期待连接对应的主连接; struct ip_conntrack *sibling:期待连接对应的真实的子连接; struct ip_conntrack_tuple ct_tuple:连接的tuple值 struct timer_list timeout:定时器 struct ip_conntrack_tuple tuple, mask:期待连接相关的tuple和mask; u_int32_t seq:TCP协议时,主连接中描述子连接的数据起始处对应的序列号值; union ip_conntrack_expect_proto proto:跟踪各个多连接IP层协议相关的数据; union ip_conntrack_expect_help help:跟踪各个多连接应用层协议相关的数据; 结构中包括以下函数: int (*expectfn)(struct ip_conntrack *new):期待连接相关函数,在ip_conntrack_core.c文件的init_conntrack()函数中调用: ... if (expected && expected->expectfn) expected->expectfn(conntrack); ... 2.3 多连接协议的连接跟踪过程 2.3.1 过程简述 新连接的数据包进入netfilter系统后先要建立对应连接,检查是否是期待的连接,如果与某期待连接符合,说明该连接是子连接,将连接和主连接建立相关的联系,并对连接设置相关标志,表明是RELATED的连接;否则,说明是主连接,则查找与这个连接对应的协议的连接辅助模块helper,如果找到,执行helper中的(*help)函数检查是否能提取出相关的子连接信息,生成期待的连接信息添加到期待连接链表中。 2.3.2 连接helper查找 在ip_conntrack_core.c文件的init_conntrack()函数中先查找期待连接是否存在,然后调用ip_ct_find_helper()函数来找到与该协议对应的helper函数,注意是用反向的tuple来查找的: ... // 查找是否期待连接 expected = LIST_FIND(&ip_conntrack_expect_list, expect_cmp, struct ip_conntrack_expect *, tuple); ... // 查找连接对应的helper if (!expected) conntrack->helper = ip_ct_find_helper(&repl_tuple); ... 在查找helper之前先检查该连接是否存在对应的期待连接,如果是存在期待连接,说明是子连接,子连接就不再去找helper了,这就避免了 phrack63的那篇文章中描述的防火墙穿透方法。但这种限制也带来一个问题,比如H.323协议,主连接是H.225协议,会派生出一个H.245的连接,由H.245连接再派生出一个连接进行数据传输,这种情况下H.245的helper就不能通过ip_ct_find_helper()函数来获取了,为解决这个问题,H.323跟踪NAT模块的作者使用了一个很巧妙的方法,就是通过期待连接结构的(*expectfn)函数来解决,在该函数中直接将H.245的helper直接赋值到该连接中。 2.3.3 (*help)函数执行 (*help)函数对主连接的每个合法数据包都会进行检查的,在ip_conntrack_core.c文件的ip_conntrack_in()函数中调用: ... if (ret != NF_DROP && ct->helper) { ret = ct->helper->help((*pskb)->nh.iph, (*pskb)->len, ct, ctinfo); if (ret == -1) { // 协议数据格式错误时help函数返回-1,清空该包对应的nfct,则该包在状态检测时将被视为非法包 /* Invalid */ nf_conntrack_put((*pskb)->nfct); (*pskb)->nfct = NULL; return NF_ACCEPT; } } ... 3. 多连接协议NAT 3.1 ip_nat_helper结构 netfilter中对每个要进行NAT的多连接协议定义了以下结构,每个多连接协议的连接跟踪处理就是要填写这样一个结构: struct ip_nat_helper { struct list_head list; /* Internal use */ const char *name; /* name of the module */ unsigned char flags; /* Flags (see above) */ struct module *me; /* pointer to self */ /* Mask of things we will help: vs. tuple from server */ struct ip_conntrack_tuple tuple; struct ip_conntrack_tuple mask; /* Helper function: returns verdict */ unsigned int (*help)(struct ip_conntrack *ct, struct ip_conntrack_expect *exp, struct ip_nat_info *info, enum ip_conntrack_info ctinfo, unsigned int hooknum, struct sk_buff **pskb); /* Returns verdict and sets up NAT for this connection */ unsigned int (*expect)(struct sk_buff **pskb, unsigned int hooknum, struct ip_conntrack *ct, struct ip_nat_info *info); }; 结构中包括以下参数: struct list_head list:将该结构挂接到多连接协议NAT链表helpers中,helpers链表在ip_nat_core.c文件中定义: LIST_HEAD(helpers); 注意该定义不是static的,而是全局有效的,连接跟踪的helpers则是static的,只在该文件中起作用,而且屏蔽了ip_nat_core.c中的helpers,所以在此两个文件外的helpers是指nat的helpers; const char *name:协议名称,字符串常量 unsigned char flags:关于本连接跟踪模块的一些标志; struct module *me:指向模块本身,统计模块是否被使用 struct ip_conntrack_tuple tuple, mask:这两个参数用来描述进行了NAT后的子连接,同样因为子连接的某些参数不能确定,要用mask来进行泛匹配; 结构中包括以下函数: (*help):多协议连接NAT操作基本函数,由于作NAT后要修改IP地址以及端口,因此原来的主连接中描述子连接的信息必须进行修改, (*help)函数的功能就要要找到一个空闲的tuple对应新的子连接,修改期待的子连接,然后修改主连接的通信内容,修改关于IP地址和端口部分的描述信息为空闲tuple的信息,由于修改了应用层数据,数据的校验和必须重新计算,而且如果数据长度发生变化,会引起TCP序列号的变化,在连接的协议相关数据中会记录这些变化,对后续的所有数据都要进行相应的调整;该函数在do_bindings()函数中调用; (*expect):该函数建立子连接对应的NAT相关信息,主连接的NAT相关信息是通过iptables的NAT规则建立的;该函数在ip_nat_statndalone.c的call_expect()函数中调用 在NAT的基本函数ip_nat_fn()中,先调用call_expect(),最后调用的do_bindings(),所以(*expect)函数先调用,(*help)函数后调用。 3.2 多连接协议的NAT过程 2.3.1 过程简述 不论是SNAT还是DNAT,其对应的netfilter的nf_hook_ops节点都要执行ip_nat_fn(),此时与数据包对应的连接已经建立,对于连接的后续包,只是把连接的NAT信息绑定到该包(do_binding()函数);如果是新连接,如果是子连接,则调用协议相关 nat_helper的(*expect)函数建立子连接的相关信息;否不论是SNAT还是DNAT都会调用ip_nat_setup_info()函数建立与该连接对应的NAT信息,所以在NAT规则中是不需要加状态是NEW的匹配的,最后将连接的NAT信息绑定到数据包。 在NAT信息的绑定过程中,会检查当前的数据包是否属于期待的要进行NAT修改的包,具体是用exp_for_packet()函数进行检查,该函数中调用相应传输层协议的(*exp_matches_pkt)函数,该函数只在TCP的协议中定义,但UDP没有,没有定义此函数时 exp_for_packet()始终返回要求检查;如果要修改,将调用相应nat_helper的(*help)函数来修改数据,修改期待的子连接。 2.3.2 nat helper查找 在ip_nat_core.c文件的ip_nat_setup_info()函数中 ... /* If there's a helper, assign it; based on new tuple. */ if (!conntrack->master) info->helper = LIST_FIND(&helpers, helper_cmp, struct ip_nat_helper *, &reply); ... static inline int helper_cmp(const struct ip_nat_helper *helper, const struct ip_conntrack_tuple *tuple) { return ip_ct_tuple_mask_cmp(tuple, &helper->tuple, &helper->mask); } 3.3.3 (*expect)函数执行 在ip_nat_standalone.c文件的ip_nat_fn()函数中调用call_expect()函数,在call_expect()函数中调用(*expect)函数: static inline int call_expect(struct ip_conntrack *master, struct sk_buff **pskb, unsigned int hooknum, struct ip_conntrack *ct, struct ip_nat_info *info) { return master->nat.info.helper->expect(pskb, hooknum, ct, info); } static unsigned int ip_nat_fn(unsigned int hooknum, struct sk_buff **pskb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { ... case IP_CT_NEW: ... if (ct->master && master_ct(ct)->nat.info.helper && master_ct(ct)->nat.info.helper->expect) { ret = call_expect(master_ct(ct), pskb, hooknum, ct, info); } else { #ifdef CONFIG_IP_NF_NAT_LOCAL /* LOCAL_IN hook doesn't have a chain! */ if (hooknum == NF_IP_LOCAL_IN) ret = alloc_null_binding(ct, info, hooknum); else #endif ret = ip_nat_rule_find(pskb, hooknum, in, out, ct, info); } ... 3.3.3 (*help)函数执行 在ip_nat_core.c文件的do_binding()函数中: ... // 判断是否是期待的修改点 if (exp_for_packet(exp, pskb)) { /* FIXME: May be true multiple times in the * case of UDP!! */ // 因为UDP没有序列号,没办法指示修改点,不象TCP可以用序列号表示,所以所有UDP包 // 都会执行help DEBUGP("calling nat helper (exp=%p) for packet\n", exp); // 修改数据参数 ret = helper->help(ct, exp, info, ctinfo, hooknum, pskb); if (ret != NF_ACCEPT) { READ_UNLOCK(&ip_conntrack_lock); return ret; } helper_called = 1; } } /* Helper might want to manip the packet even when there is no * matching expectation for this packet */ if (!helper_called && helper->flags & IP_NAT_HELPER_F_ALWAYS) { DEBUGP("calling nat helper for packet without expectation\n"); ret = helper->help(ct, NULL, info, ctinfo, hooknum, pskb); if (ret != NF_ACCEPT) { READ_UNLOCK(&ip_conntrack_lock); return ret; } } ... 有两处地方调用了(*help)函数,第一处(*help)执行了后面的(*help)就不会执行,但即使前面的(*help)没有执行,由于很多helper的flags都设置为0,所以后面那次(*help)基本都不会执行。 4. 结论 netfilter对多连接协议跟踪和NAT处理很好地实现了模块化,只要按固定的格式编写跟踪和NAT程序就能支持新的多连接协议,现在 netfilter已经可以支持很多多连接协议,如FTP、TFTP、IRC、TALK、H.323、SIP、MMS、QUAKE3等。