msn: [email protected]
来源:http://yfydz.cublog.cn
1. iptables规则中的state匹配 在2.4/2.6内核的Linux中的防火墙代码netfilter中实现了状态检测(stateful inspection)检测技术,在命令行接口的iptables命令是通过匹配“-m state”来实现,“-m state”匹配中定义了四种状态: NEW,表示新连接; ESTABLISHED,表示已经建立的连接; RELATED,表示相关的子连接; INVALID,表示非法状态。 所以在制定iptables规则时,只需要定义单向的规则,再加上如下两条规则,就可以保证连接回应包能正确通过防火墙而不必加反向规则: iptables -I FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT iptables -I FORWARD -m state --state INVALID -j DROP (如果是保护本机,将FORWARD改为INPUT) 2. 内核中的state匹配的实现 (以下的内核代码基于2.4.26内核) 和匹配state对应的内核部分实现是ipt_state.c(net/ipv4/netfilter),匹配部分的代码非常简单,就是根据函数ip_conntrack_get()的返回值来判断数据包的状态类型: if (!ip_conntrack_get((struct sk_buff *)skb, &ctinfo)) statebit = IPT_STATE_INVALID; else statebit = IPT_STATE_BIT(ctinfo); 3. skb与ip_conntrack的关联 再跟踪到ip_conntrack_get()函数(net/ipv4/netfilter_ipv4 /ip_conntrack_core.c),该函数有两个参数,一个是指向sk_buff数据包的指针skb,另一个是枚举类型enum ip_conntrack_info的指针,实际上是一个返回参数,将返回数据包的状态类型;该函数的返回值是连接结构struct ip_conntrack的指针,表示该包属于哪个连接,如果连接不存在,返回空,该包也将被认为是非法包: struct ip_conntrack * ip_conntrack_get(struct sk_buff *skb, enum ip_conntrack_info *ctinfo) { if (skb->nfct) return __ip_conntrack_get(skb->nfct, ctinfo); return NULL; } ip_conntrack_get()函数本身也很简单,实际上是__ip_conntrack_get()函数(net/ipv4 /netfilter_ipv4/ip_conntrack_core.c)的包裹函数,判断skb的nfct是否非空,非空则将其和枚举指针 ctinfo传给__ip_conntrack_get()函数,nfct是sk_buff中用于描述netfilter conntrack信息的struct nf_ct_info指针,在内核配置了CONFIG_NETFILTER后有效。struct nf_ct_info结构在include/linux/sk_buff.h中定义: struct nf_conntrack { atomic_t use; void (*destroy)(struct nf_conntrack *); }; struct nf_ct_info { struct nf_conntrack *master; }; 在__ip_conntrack_get()函数中,通过nfct->master获取连接指针ct,而nfct相对于ct成员infos的偏移就是连接状态: static inline struct ip_conntrack * __ip_conntrack_get(struct nf_ct_info *nfct, enum ip_conntrack_info *ctinfo) { struct ip_conntrack *ct = (struct ip_conntrack *)nfct->master; /* ctinfo is the index of the nfct inside the conntrack */ *ctinfo = nfct - ct->infos; IP_NF_ASSERT(*ctinfo >= 0 && *ctinfo < IP_CT_NUMBER); return ct; } 由此可见,netfilter的状态检测过程很简洁,如果skb包中的nfct指针设置了的话,可以很快地确定该skb包属于哪个连接,如果不属于任何连接则是非法包。现在的焦点是skb中的nfct值是如何设置的? 4. netfilter如何实现中skb与ip_conntrack连接的关联 nfct值是在resolve_normal_ct()函数(net/ipv4/netfilter/ip_conntrack_core.c)中定义的,该函 数被ip_conntrack_in()函数(net/ipv4/netfilter/ip_conntrack_core.c)调用,在 ip_conntrack_local()函数(net/ipv4/netfilter/ip_conntrack_standalone.c)中调用了 ip_conntrack_in()函数,而这两个函数分别是PREROUTING链和OUTPUT链的起始函数,由下面两个结构 定义(net/ipv4/netfilter/ip_conntrack_standalone.c): static struct nf_hook_ops ip_conntrack_in_ops = { { NULL, NULL }, ip_conntrack_in, PF_INET, NF_IP_PRE_ROUTING, NF_IP_PRI_CONNTRACK }; static struct nf_hook_ops ip_conntrack_local_out_ops = { { NULL, NULL }, ip_conntrack_local, PF_INET, NF_IP_LOCAL_OUT, NF_IP_PRI_CONNTRACK }; 这说明netfilter是在连接的第一个包时就建立连接了,这个连接可能是由外部发起的,也可能是由本机发起的,后续包查找连接HASH表找到相应的连接,然后赋值给skb结构中的nfct,这就是resolve_normal_ct()函数的工作: /* On success, returns conntrack ptr, sets skb->nfct and ctinfo */ static inline struct ip_conntrack * resolve_normal_ct(struct sk_buff *skb, // 数据包 struct ip_conntrack_protocol *proto, // 连接协议跟踪,该结构对应协议特殊处理过程,如TCP的状态转换等 int *set_reply, // 返回值,表示该包是否是响应方的包 unsigned int hooknum, enum ip_conntrack_info *ctinfo) { struct ip_conntrack_tuple tuple; struct ip_conntrack_tuple_hash *h; IP_NF_ASSERT((skb->nh.iph->frag_off & htons(IP_OFFSET)) == 0); // 确定该包对应的tuple,对TCP/UDP协议来说就是地址协议端口的5元组 if (!get_tuple(skb->nh.iph, skb->len, &tuple, proto)) return NULL; /* look for tuple match */ // 根据tuple值查找连接HASH h = ip_conntrack_find_get(&tuple, NULL); if (!h) { // 在当前连接表中没找到,说明是新连接的包,初始化新连接 h = init_conntrack(&tuple, proto, skb); if (!h) return NULL; // 该包非法返回NULL,其他错误会返回错误号的负值 if (IS_ERR(h)) return (void *)h; } /* It exists; we have (non-exclusive) reference. */ if (DIRECTION(h) == IP_CT_DIR_REPLY) { // 连接回应方向包 *ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY; /* Please set reply bit if this packet OK */ // 这个包是响应方的包 *set_reply = 1; } else { // 连接原始方向的包 /* Once we've had two way comms, always ESTABLISHED. */ if (test_bit(IPS_SEEN_REPLY_BIT, &h->ctrack->status)) { DEBUGP("ip_conntrack_in: normal packet for %p\n", h->ctrack); // 如果不是第一个包,设置ESTABLISHED属性,这个位标志表示发现了响应方发的包, // 表示连接可以建立,该标志在ip_conntrack_in()函数中设置 *ctinfo = IP_CT_ESTABLISHED; } else if (test_bit(IPS_EXPECTED_BIT, &h->ctrack->status)) { DEBUGP("ip_conntrack_in: related packet for %p\n", h->ctrack); // 该标志表示是子连接的包,如FTP的数据连接 // 该标志在init_conntrack()函数中设置 *ctinfo = IP_CT_RELATED; } else { DEBUGP("ip_conntrack_in: new packet for %p\n", h->ctrack); // 没有任何标志,是连接的第一个包,设置为NEW属性 *ctinfo = IP_CT_NEW; } // 这个包是发起方的包 *set_reply = 0; } // infos是数量为IP_CT_NUMBER(=5)的数组,分别对应连接发起方向的 // NEW(2)/ESTABLISHED(0)/RELATED(1) // 和连接响应方向的ESTABLISHED(3)/RELATED(4) // 每个skb包都会划归为其中一类, 也就是ctinfo的值 // 每个skb包根据其类型,将其nfct值设为相应infos项的地址 skb->nfct = &h->ctrack->infos[*ctinfo]; return h->ctrack; } 在init_conntrack()函数中将连接的infos值初始化为: ... for (i=0; i < IP_CT_NUMBER; i++) conntrack->infos[i].master = &conntrack->ct_general; ... 也就是infos各项的值都是相同的,没有发现在其他地方重新赋值,所以把所有项实际都是相同的,指向同一个连接。设计之初可能是考虑要区分,但实现时还是只用到一个就行了,在其他地方基本只用到infos[0]进行处理,infos的用处目前只用来判断状态,也就是每一项相对于 infos[0]的偏移值。 最后讨论一下为什么在__ip_conntrack_get()函数中nfct->master值就是连接的地址: __ip_conntrack_get(struct nf_ct_info *nfct, enum ip_conntrack_info *ctinfo) { struct ip_conntrack *ct = (struct ip_conntrack *)nfct->master; ... skb的nfct值是infos数组中某一项的地址,而这些项中的master成员都初始化为连接中ct_general的地址,而ct_general参数是连接结构的第一项参数,所以其地址和连接结构的地址是相同,在Linux内核中,类似用法很多。 5. 结论 在每个skb数据包进入netfilter架构时,netfilter就会给其建立连接,通过简单方法就能找到连接,能区分出发起方、响应方以及第一个包还是后续包,并能识别子连接,从而实现状态检测。 连接结构中的infos数组就目前而言似乎不是很必要,单值就可以,然后增加一个连接状态参数,而不是通过infos的偏移来判断。