作者:gfree.wind@gmail.com
博客:blog.focus-linux.net linuxfocus.blog.chinaunix.net
博客:blog.focus-linux.net linuxfocus.blog.chinaunix.net
本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
======================================================================================================
今天学习一下netfilter如何进行的connection track处理。
按照以前的学习,netfilter本身提供了一个方便的框架,通过在各个挂载点注册hook来实现各种功能。那么作为netfilter的一个功能,connection track自然也是通过这一统一框架来实现的。
在函数nf_conntrack_l3proto_ipv4_init中:
- ret = nf_register_hooks(ipv4_conntrack_ops,
- ARRAY_SIZE(ipv4_conntrack_ops));
- if (ret < 0) {
- pr_err("nf_conntrack_ipv4: can't register hooks.\n");
- goto cleanup_ipv4;
- }
这里将ipv4_conntrack_ops注册到netfilter的各挂载点。
查看ipv4_conntrack_ops的定义:
- static struct nf_hook_ops ipv4_conntrack_ops[] __read_mostly = {
- {
- .hook = ipv4_conntrack_in,
- .owner = THIS_MODULE,
- .pf = NFPROTO_IPV4,
- .hooknum = NF_INET_PRE_ROUTING,
- .priority = NF_IP_PRI_CONNTRACK,
- },
- {
- .hook = ipv4_conntrack_local,
- .owner = THIS_MODULE,
- .pf = NFPROTO_IPV4,
- .hooknum = NF_INET_LOCAL_OUT,
- .priority = NF_IP_PRI_CONNTRACK,
- },
- {
- .hook = ipv4_confirm,
- .owner = THIS_MODULE,
- .pf = NFPROTO_IPV4,
- .hooknum = NF_INET_POST_ROUTING,
- .priority = NF_IP_PRI_CONNTRACK_CONFIRM,
- },
- {
- .hook = ipv4_confirm,
- .owner = THIS_MODULE,
- .pf = NFPROTO_IPV4,
- .hooknum = NF_INET_LOCAL_IN,
- .priority = NF_IP_PRI_CONNTRACK_CONFIRM,
- },
- };
回忆一下netfilter的框架的主要变量:
- struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS];
其中行索引为协议,而列索引为netfilter的挂载点。
下面以本机发送数据包的流程为例,那么会经过两个netfilter的挂载点NF_INET_LOCAL_OUT和NF_INET_POST_ROUTING。
那么根据前面ipv4_conntrack_ops,在NF_INET_LOCAL_OUT挂载点,所有的IPv4的数据包,都要经过hook函数ipv4_conntrack_local,然后在NF_INET_POST_ROUTING挂载点,经过hook函数ipv4_confirm。
这样,先看ipv4_conntrack_local:
- static unsigned int ipv4_conntrack_local(unsigned int hooknum,
- struct sk_buff *skb,
- const struct net_device *in,
- const struct net_device *out,
- int (*okfn)(struct sk_buff *))
- {
- /* root is playing with raw sockets. */
- /*
- 做sanity check,数据包的长度不符合IP层的规定。
- 这样的包不可能是kernel产生的,那么一定是root用户在使用raw socket搞得。
- */
- if (skb->len < sizeof(struct iphdr) ||
- ip_hdrlen(skb) < sizeof(struct iphdr))
- return NF_ACCEPT;
- return nf_conntrack_in(dev_net(out), PF_INET, hooknum, skb);
- }
nf_conntrack_in这个函数在http://blog.chinaunix.net/uid-23629988-id-3056758.html已经学习过了,它的作用基本上就是通过数据包的三层和四层信息,建立一个新的connection track或者更新已有的connection track的信息和状态的。
下面看ipv4_confirm:
- static unsigned int ipv4_confirm(unsigned int hooknum,
- struct sk_buff *skb,
- const struct net_device *in,
- const struct net_device *out,
- int (*okfn)(struct sk_buff *))
- {
- struct nf_conn *ct;
- enum ip_conntrack_info ctinfo;
- const struct nf_conn_help *help;
- const struct nf_conntrack_helper *helper;
- unsigned int ret;
- /* This is where we call the helper: as the packet goes out. */
- /* 得到connection track 信息*/
- ct = nf_ct_get(skb, &ctinfo);
- if (!ct || ctinfo == IP_CT_RELATED_REPLY)
- goto out;
/* 如果该connection track还有helper扩展,需要执行其回调函数,一般可以用于ALG */
- help = nfct_help(ct);
- if (!help)
- goto out;
- /* rcu_read_lock()ed by nf_hook_slow */
- helper = rcu_dereference(help->helper);
- if (!helper)
- goto out;
- ret = helper->help(skb, skb_network_offset(skb) + ip_hdrlen(skb),
- ct, ctinfo);
- if (ret != NF_ACCEPT) {
- nf_log_packet(NFPROTO_IPV4, hooknum, skb, in, out, NULL,
- "nf_ct_%s: dropping packet", helper->name);
- return ret;
- }
- /* adjust seqs for loopback traffic only in outgoing direction */
- if (test_bit(IPS_SEQ_ADJUST_BIT, &ct->status) &&
- !nf_is_loopback_packet(skb)) {
- typeof(nf_nat_seq_adjust_hook) seq_adjust;
- seq_adjust = rcu_dereference(nf_nat_seq_adjust_hook);
- if (!seq_adjust || !seq_adjust(skb, ct, ctinfo)) {
- NF_CT_STAT_INC_ATOMIC(nf_ct_net(ct), drop);
- return NF_DROP;
- }
- }
- out:
- /* We've seen it coming out the other side: confirm it */
- return nf_conntrack_confirm(skb);
- }
进入nf_conntrack_confirm,
- /* Confirm a connection: returns NF_DROP if packet must be dropped. */
- static inline int nf_conntrack_confirm(struct sk_buff *skb)
- {
- struct nf_conn *ct = (struct nf_conn *)skb->nfct;
- int ret = NF_ACCEPT;
- if (ct && !nf_ct_is_untracked(ct)) {
- if (!nf_ct_is_confirmed(ct))
- ret = __nf_conntrack_confirm(skb);
- if (likely(ret == NF_ACCEPT))
- nf_ct_deliver_cached_events(ct);
- }
- return ret;
- }
如果connection没有被confirmed,则将connection从unconfirmed链表中移到confirmed链表中。并启动timer作为过期处理。
当本地发送一个数据包的时候,在经过LOCAL_OUT和POST_ROUTE挂载点,由connection track的hook函数,根据数据包的信息,创建或更新了对应的connection track信息。