连接跟踪的源代码分析

 

关于连接跟踪

 

连接跟踪(CONNTRACK),就是跟踪并且记录连接状态。Linux为每一个经过网络堆栈的数据包,生成一个新的连接记录项(Connection entry)。此后,所有属于此连接的数据包都被唯一地分配给这个连接,并标识连接的状态。连接跟踪是防火墙模块的状态检测的基础,同时也是地址转换中实现SNATDNAT的前提。

 

1.    连接跟踪的相关结构

Netfilter中的连接记录项,即是对一个连接的产生,传输及终止进行跟踪记录。由所有记录项产生的表则称为连接跟踪表,这个表将存储所有连接的状态,它在算法上采用了hash算法。整个hash表用一个ip_conntrack_hash的全局指针来表示:

unsigned int ip_conntrack_htable_size = 0; //由多少条链构成hash

int ip_conntrack_max = 0; //hash表的大小 后面将根据内存的大小计算

struct list_head *ip_conntrack_hash; //全局hash

 

每个hash的节点同时又是一个链表的头部。节点的结构体ip_conntrack_tuple_hash

/* Connections have two entries in the hash table: one for each way */

struct ip_conntrack_tuple_hash

{

    struct list_head list;//链表

    struct ip_conntrack_tuple tuple;//描述数据包的多元组

    /* this == &ctrack->tuplehash[DIRECTION(this)]. */

    struct ip_conntrack *ctrack;

};

socket编程的角度来说,使用 ip地址+端口的方式来标识一个连接。Netfilter延续了这种方式,使用ip_conntrack_tuple封装了连接的目的和来源。可以通过一个skbuff(网络数据包)得到这个元组。

 

 

  1. /* The protocol-specific manipulable parts of the tuple: always in
  2.    network order! */
  3. union ip_conntrack_manip_proto
  4. {
  5.     /* Add other protocols here. */
  6.     u_int32_t all;
  7.     struct {
  8.         u_int16_t port;
  9.     } tcp;
  10.     struct {
  11.         u_int16_t port;
  12.     } udp;
  13.     struct {
  14.         u_int16_t id;
  15.     } icmp;
  16.     struct {
  17.         u_int32_t key;
  18.     } gre;
  19. };
  20. /* The manipulable part of the tuple. */
  21. struct ip_conntrack_manip
  22. {
  23.     u_int32_t ip;
  24.     union ip_conntrack_manip_proto u;
  25. };
  26. /* This contains the information to distinguish a connection. */
  27. struct ip_conntrack_tuple
  28. {
  29.     struct ip_conntrack_manip src; //源端
  30.     /* These are the parts of the tuple which are fixed. */
  31.     struct {
  32.         u_int32_t ip;
  33.         union {
  34.             /* Add other protocols here. */
  35.             u_int32_t all;
  36.             struct {
  37.                 u_int16_t port;
  38.             } tcp;
  39.             struct {
  40.                 u_int16_t port;
  41.             } udp;
  42.             struct {
  43.                 u_int8_t type, code;
  44.             } icmp;
  45.             struct {
  46.                 u_int32_t key;
  47.             } gre;
  48.         } u;
  49.         /* The protocol. */
  50.         u_int16_t protonum;
  51.     } dst;//目的端
  52. };  

注意tuple可以完全标识一个唯一的连接,但是它不能完整的描述一个连接的信息,描述一个连接的信息,使用ip_conntrack结构体,ip_conntrack中有tuplehash[IP_CT_DIR_MAX],这个数组中包含初始应答两个成员(tuplehash[IP_CT_DIR_ORIGINAL]tuplehash[IP_CT_DIR_REPLY]),所以,当一个数据包进入连接跟踪模块后,先根据这个数据包的套接字对转换成一个初始的”tuple,赋值给tuplehash[IP_CT_DIR_ORIGINAL],然后对这个数据包取反,计算出应答tuple,赋值给tuplehash[IP_CT_DIR_REPLY]

struct ip_conntrack

{

//…

/* These are my tuples; original and reply */

    struct ip_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];

//…

};

 

2.    连接跟踪的具体过程  

连接跟踪是在ip_conntrack_standalone.c 中进行初始化的,并完成向Netfilter注册相关的钩子。

模块的init函数会调用init_or_cleanup(),在init_or_cleanup()中首先调用了ip_conntrack_init(),这个函数主要负责初始化连接跟踪的变量及相关的数据结构例如,连接跟踪表的大小。而后init_or_cleanup()函数又注册了几个非常重要的钩子。如下:

static int init_or_cleanup(int init)

{ 

    ip_conntrack_init();

    nf_register_hook(&ip_conntrack_in_ops);

    nf_register_hook(&ip_conntrack_local_out_ops);

    nf_register_hook(&ip_conntrack_out_ops);

    nf_register_hook(&ip_conntrack_local_in_ops);

}

钩子的结构体具体如下:

/* Connection tracking may drop packets, but never alters them, so

   make it the first hook. */

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 };

/* Refragmenter; last chance. */

static struct nf_hook_ops ip_conntrack_out_ops

= { { NULL, NULL }, ip_refrag, PF_INET, NF_IP_POST_ROUTING, NF_IP_PRI_LAST };

static struct nf_hook_ops ip_conntrack_local_in_ops

= { { NULL, NULL }, ip_confirm, PF_INET, NF_IP_LOCAL_IN, NF_IP_PRI_LAST-1 };  

 

由上面的代码可以看出输入的数据包将首先经过ip_conntrack_in()函数(NF_IP_PRI_CONNTRACK优先级比较高)。在ip_conntrack_in的主要任务为判断该数据包是否已经有连接跟踪,如果没有,则为这个数据包分配ip_conntrack,并初始化它。在ip_conntrack_in()函数会进一步调用resolve_normal_ct()函数,resolve_normal_ct()为连接跟踪中最重要的一个函数,它主要完成了ip_conntrack_in()函数提供的功能。

  1. /* On success, returns conntrack ptr, sets skb->nfct and ctinfo */
  2. //nfct字段将在ip_conntrack_in中被检测,表示当前数据包是否已被处理过
  3. static inline struct ip_conntrack *
  4. resolve_normal_ct(struct sk_buff *skb,
  5.           struct ip_conntrack_protocol *proto,
  6.           int *set_reply,
  7.           unsigned int hooknum,
  8.           enum ip_conntrack_info *ctinfo)
  9. {
  10.     struct ip_conntrack_tuple tuple;
  11.     struct ip_conntrack_tuple_hash *h;
  12.     IP_NF_ASSERT((skb->nh.iph->frag_off & htons(IP_OFFSET)) == 0);
  13.     //从Skbuff中提取tuple信息
  14.     if (!ip_ct_get_tuple(skb->nh.iph, skb->len, &tuple, proto))
  15.         return NULL;
  16.     /* look for tuple match */
  17.     //在全局的连接跟踪链表中查询该tuple
  18. h = ip_conntrack_find_get(&tuple, NULL);
  19.     if (!h) {
  20.         //如果不存在,初始化一个
  21.         h = init_conntrack(&tuple, proto, skb);
  22.         if (!h)
  23.             return NULL;
  24.         if (IS_ERR(h))
  25.             return (void *)h;
  26.         if (ipctinit_callback)
  27.             ipctinit_callback(h->ctrack);
  28.     }
  29.     /* It exists; we have (non-exclusive) reference. */
  30.     //判断连接方向
  31.     if (DIRECTION(h) == IP_CT_DIR_REPLY) {
  32.         *ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY;
  33.         /* Please set reply bit if this packet OK */
  34.         *set_reply = 1;
  35.     } else {
  36.         /* Once we've had two way comms, always ESTABLISHED. */
  37.         if (test_bit(IPS_SEEN_REPLY_BIT, &h->ctrack->status)) {
  38.             DEBUGP("ip_conntrack_in: normal packet for %p/n",
  39.                    h->ctrack);
  40.                 *ctinfo = IP_CT_ESTABLISHED;
  41.         } else if (test_bit(IPS_EXPECTED_BIT, &h->ctrack->status)) {
  42.             DEBUGP("ip_conntrack_in: related packet for %p/n",
  43.                    h->ctrack);
  44.             *ctinfo = IP_CT_RELATED;
  45.         } else {
  46.             DEBUGP("ip_conntrack_in: new packet for %p/n",
  47.                    h->ctrack);
  48.             *ctinfo = IP_CT_NEW;
  49.         }
  50.         *set_reply = 0;
  51.     }
  52.     //每个sk_buff都将与ip_conntrack的一个状态关联,所以从sk_buff可以得到相应ip_conntrack的状态,即数据包的状态
  53.     skb->nfct = &h->ctrack->infos[*ctinfo];
  54.     return h->ctrack;
  55. }

 

初始化一个连接跟踪时,会调用init_conntrack()函数

 

 

  1. /* Allocate a new conntrack: we return -ENOMEM if classification
  2.    failed due to stress.  Otherwise it really is unclassifiable. */
  3. static struct ip_conntrack_tuple_hash *
  4. init_conntrack(const struct ip_conntrack_tuple *tuple,
  5.            struct ip_conntrack_protocol *protocol,
  6.            struct sk_buff *skb)
  7. {
  8.     struct ip_conntrack *conntrack;
  9.     struct ip_conntrack_tuple repl_tuple;
  10.     size_t hash;
  11.     struct ip_conntrack_expect *expected;
  12.     int i;
  13.     static unsigned int drop_next = 0;
  14.     //计算hash值的随机数种子
  15.     if (!ip_conntrack_hash_rnd_initted) {
  16.         get_random_bytes(&ip_conntrack_hash_rnd, 4);
  17.         ip_conntrack_hash_rnd_initted = 1;
  18.     }
  19.     
  20.     //计算hash值
  21.     hash = hash_conntrack(tuple);
  22.     //判断连接跟踪表是否已满
  23.     if (ip_conntrack_max &
  24.         atomic_read(&ip_conntrack_count) >= ip_conntrack_max) {
  25.         /* Try dropping from random chain, or else from the
  26.                    chain about to put into (in case they're trying to
  27.                    bomb one hash chain). */
  28.         unsigned int next = (drop_next++)%ip_conntrack_htable_size;
  29.         if (!early_drop(&ip_conntrack_hash[next])
  30.             && !early_drop(&ip_conntrack_hash[hash])) {
  31.             if (net_ratelimit())
  32.                 printk(KERN_WARNING
  33.                        "ip_conntrack: table full, dropping"
  34.                        " packet./n");
  35.             return ERR_PTR(-ENOMEM);
  36.         }
  37.     }
  38.     //计算反向的tuple值
  39.     if (!invert_tuple(&repl_tuple, tuple, protocol)) {
  40.         DEBUGP("Can't invert tuple./n");
  41.         return NULL;
  42.     }
  43.     //为连接分配空间
  44.     conntrack = kmem_cache_alloc(ip_conntrack_cachep, GFP_ATOMIC);
  45.     if (!conntrack) {
  46.         DEBUGP("Can't allocate conntrack./n");
  47.         return ERR_PTR(-ENOMEM);
  48.     }
  49.     memset(conntrack, 0, sizeof(*conntrack));
  50.     //设置计数器
  51.     atomic_set(&conntrack->ct_general.use, 1);
  52.     conntrack->ct_general.destroy = destroy_conntrack;
  53.     //设置正反方向的tuple
  54. conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple = *tuple;
  55.     conntrack->tuplehash[IP_CT_DIR_ORIGINAL].ctrack = conntrack;
  56.     conntrack->tuplehash[IP_CT_DIR_REPLY].tuple = repl_tuple;
  57.     conntrack->tuplehash[IP_CT_DIR_REPLY].ctrack = conntrack;
  58.     for (i=0; i < IP_CT_NUMBER; i++)
  59.         conntrack->infos[i].master = &conntrack->ct_general;
  60.     if (!protocol->new(conntrack, skb->nh.iph, skb->len)) {
  61.         kmem_cache_free(ip_conntrack_cachep, conntrack);
  62.         return NULL;
  63.     }
  64.     /* Don't set timer yet: wait for confirmation */
  65.     //初始化计数器
  66.     init_timer(&conntrack->timeout);
  67.     conntrack->timeout.data = (unsigned long)conntrack;
  68.     conntrack->timeout.function = death_by_timeout;
  69.     INIT_LIST_HEAD(&conntrack->sibling_list);
  70.     WRITE_LOCK(&ip_conntrack_lock);
  71.     /* Need finding and deleting of expected ONLY if we win race */
  72.     //判断是不是期待的子连接
  73.     READ_LOCK(&ip_conntrack_expect_tuple_lock);
  74.     expected = LIST_FIND(&ip_conntrack_expect_list, expect_cmp,
  75.                  struct ip_conntrack_expect *, tuple);
  76.     READ_UNLOCK(&ip_conntrack_expect_tuple_lock);
  77.     /* If master is not in hash table yet (ie. packet hasn't left
  78.        this machine yet), how can other end know about expected?
  79.        Hence these are not the droids you are looking for (if
  80.        master ct never got confirmed, we'd hold a reference to it
  81.        and weird things would happen to future packets). */
  82.     //主连接不在hash表中,这应该不是个正确被期待的子连接
  83. if (expected && !is_confirmed(expected->expectant))
  84.         expected = NULL;
  85.     //为一个主连接查找是否由注册的的helper函数
  86.     /* Look up the conntrack helper for master connections only */
  87.     if (!expected)
  88.         conntrack->helper = ip_ct_find_helper(&repl_tuple);
  89.     //等待已经超时了
  90.     /* If the expectation is dying, then this is a looser. */
  91.     if (expected
  92.         && expected->expectant->helper->timeout
  93.         && ! del_timer(&expected->timeout))
  94.         expected = NULL;
  95.     //是个被期待的子连接
  96.     if (expected) {
  97.         DEBUGP("conntrack: expectation arrives ct=%p exp=%p/n",
  98.             conntrack, expected);
  99.         /* Welcome, Mr. Bond.  We've been expecting you... */
  100.         __set_bit(IPS_EXPECTED_BIT, &conntrack->status);
  101.         conntrack->master = expected;
  102.         expected->sibling = conntrack;
  103.         //已经等到了,需要了从等待链表中删掉该连接
  104. LIST_DELETE(&ip_conntrack_expect_list, expected);
  105.         expected->expectant->expecting--;
  106.         nf_conntrack_get(&master_ct(conntrack)->infos[0]);
  107.     }
  108.     //没有直接将该连接加入hash表,而是加入unconfirmed链表
  109.     /* Overload tuple linked list to put us in unconfirmed list. */
  110.     list_add(&conntrack->tuplehash[IP_CT_DIR_ORIGINAL].list,
  111.              &unconfirmed);
  112.     atomic_inc(&ip_conntrack_count);
  113.     WRITE_UNLOCK(&ip_conntrack_lock);
  114.     if (expected && expected->expectfn)
  115.         expected->expectfn(conntrack);
  116.     return &conntrack->tuplehash[IP_CT_DIR_ORIGINAL];
  117. }

    由上面 的list_add(&conntrack->tuplehash[IP_CT_DIR_ORIGINAL].list, &unconfirmed);

可以看出,在init_conntrack函数中,并没有将一个新建连接的tuple放入全局的连接跟踪表(ip_conntrack_hash)中,那是在什么地方完成的这个任务呢,答案是__ip_conntrack_confirm函数,在Init_or_cleanup函数在NF_IP_POST_ROUTING中注册了ip_refrag函数,而在这个函数中最终会调用__ip_conntrack_confirm函数将tuple加入全局的连接跟踪表,同时把对应项从unconfirmed的队列中删除,代码如下:

 

 

  1. /* Confirm a connection given skb->nfct; places it in hash table */
  2. Int  __ip_conntrack_confirm(struct nf_ct_info *nfct)
  3. {
  4.     unsigned int hash, repl_hash;
  5.     struct ip_conntrack *ct;
  6.     enum ip_conntrack_info ctinfo;
  7.     ct = __ip_conntrack_get(nfct, &ctinfo);
  8.     /* ipt_REJECT uses ip_conntrack_attach to attach related
  9.        ICMP/TCP RST packets in other direction.  Actual packet
  10.        which created connection will be IP_CT_NEW or for an
  11.        expected connection, IP_CT_RELATED. */
  12.     if (CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL)
  13.         return NF_ACCEPT;
  14.     //计算正反tuple的hash值
  15.     hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
  16.     repl_hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
  17.     /* We're not in hash table, and we refuse to set up related
  18.        connections for unconfirmed conns.  But packet copies and
  19.        REJECT will give spurious warnings here. */
  20.     /* IP_NF_ASSERT(atomic_read(&ct->ct_general.use) == 1); */
  21.     /* No external references means noone else could have
  22.            confirmed us. */
  23.     IP_NF_ASSERT(!is_confirmed(ct));
  24.     DEBUGP("Confirming conntrack %p/n", ct);
  25.     //给全局连接跟踪表加写锁
  26.     WRITE_LOCK(&ip_conntrack_lock);
  27.     /* See if there's one in the list already, including reverse:
  28.            NAT could have grabbed it without realizing, since we're
  29.            not in the hash.  If there is, we lost race. */
  30.     if (!LIST_FIND(&ip_conntrack_hash[hash],
  31.                conntrack_tuple_cmp,
  32.                struct ip_conntrack_tuple_hash *,
  33.                &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple, NULL)
  34.         && !LIST_FIND(&ip_conntrack_hash[repl_hash],
  35.               conntrack_tuple_cmp,
  36.               struct ip_conntrack_tuple_hash *,
  37.               &ct->tuplehash[IP_CT_DIR_REPLY].tuple, NULL)) {
  38.         /* Remove from unconfirmed list */
  39.         list_del(&ct->tuplehash[IP_CT_DIR_ORIGINAL].list);
  40.       //将正反方向的TUPLE都加入全局跟踪表中
  41.         list_prepend(&ip_conntrack_hash[hash],
  42.                  &ct->tuplehash[IP_CT_DIR_ORIGINAL]);
  43.         list_prepend(&ip_conntrack_hash[repl_hash],
  44.                  &ct->tuplehash[IP_CT_DIR_REPLY]);
  45.         /* Timer relative to confirmation time, not original
  46.            setting time, otherwise we'd get timer wrap in
  47.            weird delay cases. */
  48.         ct->timeout.expires += jiffies;
  49.         add_timer(&ct->timeout);
  50.         atomic_inc(&ct->ct_general.use);
  51.         set_bit(IPS_CONFIRMED_BIT, &ct->status);
  52.         WRITE_UNLOCK(&ip_conntrack_lock);
  53.         return NF_ACCEPT;
  54.     }
  55.     WRITE_UNLOCK(&ip_conntrack_lock);
  56.     return NF_DROP;
  57. }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值