netfilter源码学习(7)——connection track处理(3)

900阅读 0评论2013-11-08 491357604
分类:LINUX

作者:gfree.wind@gmail.com
博客: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中:
  1.     ret = nf_register_hooks(ipv4_conntrack_ops,
  2.                 ARRAY_SIZE(ipv4_conntrack_ops));
  3.     if (ret < 0) {
  4.         pr_err("nf_conntrack_ipv4: can't register hooks.\n");
  5.         goto cleanup_ipv4;
  6.     }
这里将ipv4_conntrack_ops注册到netfilter的各挂载点。

查看ipv4_conntrack_ops的定义:
  1.     static struct nf_hook_ops ipv4_conntrack_ops[] __read_mostly = {
  2.     {
  3.         .hook        = ipv4_conntrack_in,
  4.         .owner        = THIS_MODULE,
  5.         .pf        = NFPROTO_IPV4,
  6.         .hooknum    = NF_INET_PRE_ROUTING,
  7.         .priority    = NF_IP_PRI_CONNTRACK,
  8.     },
  9.     {
  10.         .hook        = ipv4_conntrack_local,
  11.         .owner        = THIS_MODULE,
  12.         .pf        = NFPROTO_IPV4,
  13.         .hooknum    = NF_INET_LOCAL_OUT,
  14.         .priority    = NF_IP_PRI_CONNTRACK,
  15.     },
  16.     {
  17.         .hook        = ipv4_confirm,
  18.         .owner        = THIS_MODULE,
  19.         .pf        = NFPROTO_IPV4,
  20.         .hooknum    = NF_INET_POST_ROUTING,
  21.         .priority    = NF_IP_PRI_CONNTRACK_CONFIRM,
  22.     },
  23.     {
  24.         .hook        = ipv4_confirm,
  25.         .owner        = THIS_MODULE,
  26.         .pf        = NFPROTO_IPV4,
  27.         .hooknum    = NF_INET_LOCAL_IN,
  28.         .priority    = NF_IP_PRI_CONNTRACK_CONFIRM,
  29.     },
  30. };
回忆一下netfilter的框架的主要变量:
  1. 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:
  1. static unsigned int ipv4_conntrack_local(unsigned int hooknum,
  2.                      struct sk_buff *skb,
  3.                      const struct net_device *in,
  4.                      const struct net_device *out,
  5.                      int (*okfn)(struct sk_buff *))
  6. {
  7.     /* root is playing with raw sockets. */
  8.     /* 
  9.     做sanity check,数据包的长度不符合IP层的规定。
  10.     这样的包不可能是kernel产生的,那么一定是root用户在使用raw socket搞得。
  11.     */
  12.     if (skb->len < sizeof(struct iphdr) ||
  13.      ip_hdrlen(skb) < sizeof(struct iphdr))
  14.         return NF_ACCEPT;
  15.     return nf_conntrack_in(dev_net(out), PF_INET, hooknum, skb);
  16. }
nf_conntrack_in这个函数在http://blog.chinaunix.net/uid-23629988-id-3056758.html已经学习过了,它的作用基本上就是通过数据包的三层和四层信息,建立一个新的connection track或者更新已有的connection track的信息和状态的。

下面看ipv4_confirm
  1. static unsigned int ipv4_confirm(unsigned int hooknum,
  2.                  struct sk_buff *skb,
  3.                  const struct net_device *in,
  4.                  const struct net_device *out,
  5.                  int (*okfn)(struct sk_buff *))
  6. {
  7.     struct nf_conn *ct;
  8.     enum ip_conntrack_info ctinfo;
  9.     const struct nf_conn_help *help;
  10.     const struct nf_conntrack_helper *helper;
  11.     unsigned int ret;

  12.     /* This is where we call the helper: as the packet goes out. */
  13.     /* 得到connection track 信息*/
  14.     ct = nf_ct_get(skb, &ctinfo);
  15.     if (!ct || ctinfo == IP_CT_RELATED_REPLY)
  16.         goto out;

     /* 如果该connection track还有helper扩展,需要执行其回调函数,一般可以用于ALG */
  1.     help = nfct_help(ct);
  2.     if (!help)
  3.         goto out;

  4.     /* rcu_read_lock()ed by nf_hook_slow */
  5.     helper = rcu_dereference(help->helper);
  6.     if (!helper)
  7.         goto out;

  8.     ret = helper->help(skb, skb_network_offset(skb) + ip_hdrlen(skb),
  9.              ct, ctinfo);
  10.     if (ret != NF_ACCEPT) {
  11.         nf_log_packet(NFPROTO_IPV4, hooknum, skb, in, out, NULL,
  12.              "nf_ct_%s: dropping packet", helper->name);
  13.         return ret;
  14.     }

  15.     /* adjust seqs for loopback traffic only in outgoing direction */
  16.     if (test_bit(IPS_SEQ_ADJUST_BIT, &ct->status) &&
  17.      !nf_is_loopback_packet(skb)) {
  18.         typeof(nf_nat_seq_adjust_hook) seq_adjust;

  19.         seq_adjust = rcu_dereference(nf_nat_seq_adjust_hook);
  20.         if (!seq_adjust || !seq_adjust(skb, ct, ctinfo)) {
  21.             NF_CT_STAT_INC_ATOMIC(nf_ct_net(ct), drop);
  22.             return NF_DROP;
  23.         }
  24.     }
  25. out:
  26.     /* We've seen it coming out the other side: confirm it */
  27.     return nf_conntrack_confirm(skb);
  28. }
进入nf_conntrack_confirm,
  1. /* Confirm a connection: returns NF_DROP if packet must be dropped. */
  2. static inline int nf_conntrack_confirm(struct sk_buff *skb)
  3. {
  4.     struct nf_conn *ct = (struct nf_conn *)skb->nfct;
  5.     int ret = NF_ACCEPT;

  6.     if (ct && !nf_ct_is_untracked(ct)) {
  7.         if (!nf_ct_is_confirmed(ct))
  8.             ret = __nf_conntrack_confirm(skb);
  9.         if (likely(ret == NF_ACCEPT))
  10.             nf_ct_deliver_cached_events(ct);
  11.     }
  12.     return ret;
  13. }
如果connection没有被confirmed,则将connection从unconfirmed链表中移到confirmed链表中。并启动timer作为过期处理。

当本地发送一个数据包的时候,在经过LOCAL_OUT和POST_ROUTE挂载点,由connection track的hook函数,根据数据包的信息,创建或更新了对应的connection track信息。
上一篇:TCP/IP源码学习(51)——socket与VFS的write关联
下一篇:Linux内核初始化流程笔记