Chinaunix首页 | 论坛 | 博客
  • 博客访问: 16810
  • 博文数量: 7
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 30
  • 用 户 组: 普通用户
  • 注册时间: 2012-07-25 14:14
文章分类
文章存档

2014年(3)

2012年(4)

我的朋友

分类: LINUX

2014-02-26 10:31:57

作者:gfree.wind@gmail.com
博客:blog.focus-linux.net   linuxfocus.blog.chinaunix.net
 
 
本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
======================================================================================================

在前面的netfilter代码学习的过程中,正好碰到了kernel处理IPv4分片的函数。那么就这个线索继续下去吧。

ip_defrag用于处理kernel收到的IP分片的函数:
  1. /*
  2. 参数skb毫无疑问为收到的IP分片skb,而user用于表明调用者的身份,参加枚举ip_defrag_users。比如本地收到的IP分片时,user为IP_DEFRAG_LOCAL_DELIVER。 
  3. */
  4. int ip_defrag(struct sk_buff *skb, u32 user)
  5. {
  6.     struct ipq *qp;
  7.     struct net *net;

     /* 得到net名称空间 */
  1.     net = skb->dev ? dev_net(skb->dev) : dev_net(skb_dst(skb)->dev);
  2.     IP_INC_STATS_BH(net, IPSTATS_MIB_REASMREQDS);

  3.     /* Start by cleaning up the memory. */
  4.     /* 
  5.     IP分片占用的内存已经超过了设定的最高阀值,需要回收内存。
  6.     这个是必不可少的。因为所以的未重组的IP分片都保存在内存中。
  7.     */
  8.     if (atomic_read(&net->ipv4.frags.mem) > net->ipv4.frags.high_thresh)
  9.         ip_evictor(net);

  10.     /* Lookup (or create) queue header */
  11.     /* 查找对应的IP分片队列 */
  12.     if ((qp = ip_find(net, ip_hdr(skb), user)) != NULL) {
  13.         int ret;

  14.         spin_lock(&qp->q.lock);
  15.         /* 将该IP分片加入到该队列,如果可能的话,就对IP分片进行重组 */
  16.         ret = ip_frag_queue(qp, skb);

  17.         spin_unlock(&qp->q.lock);
  18.         ipq_put(qp);
  19.         return ret;
  20.     }

     /* 无法创建新的IP分片队列,说明内存不足 */
  1.     IP_INC_STATS_BH(net, IPSTATS_MIB_REASMFAILS);
  2.     kfree_skb(skb);
  3.     return -ENOMEM;
  4. }
接下来看ip_find:
  1. static inline struct ipq *ip_find(struct net *net, struct iphdr *iph, u32 user)
  2. {
  3.     struct inet_frag_queue *q;
  4.     struct ip4_create_arg arg;
  5.     unsigned int hash;

  6.     arg.iph = iph;
  7.     arg.user = user;
     /* 
     这里对获得了ip4_frags.lock的读锁,何时释放的呢?
     答案是在inet_frag_find这个函数中。
     这种锁的使用风格,我很不喜欢。为什么kernel会使用这种方式呢?
     */
  1.     read_lock(&ip4_frags.lock);
  2.     /* 
  3.     对于IP分片来说,使用IP头部信息中的identifier,源地址,目的地址,以及协议来计算hash值。一般来说,这     四个值基本上可以保证了IP分片的队列信息的唯一性。不过由于NAT设备的使用,就有可能将不同的分片队列混在     一起。在计算hash值上,还使用ip4_frags.rnd这一随机值。
  4.     */
  5.     hash = ipqhashfn(iph->id, iph->saddr, iph->daddr, iph->protocol);

  6.     q = inet_frag_find(&net->ipv4.frags, &ip4_frags, &arg, hash);
  7.     if (q == NULL)
  8.         goto out_nomem;
     
     /* 内核中实际上维护的变量类型为struct ipq,需要从其成员变量q,获得原来的struct ipq类型的地址 */
  1.     return container_of(q, struct ipq, q);

  2. out_nomem:
  3.     LIMIT_NETDEBUG(KERN_ERR "ip_frag_create: no memory left !\n");
  4.     return NULL;
  5. }
然后inet_frag_find:
  1. struct inet_frag_queue *inet_frag_find(struct netns_frags *nf,
  2.         struct inet_frags *f, void *key, unsigned int hash)
  3.     __releases(&f->lock)  // 这里看上去有些怪异。但__release为一个宏。其值或为空,或为一个attribut                           // e扩展,所以可以这样写。
  4. {
  5.     struct inet_frag_queue *q;
  6.     struct hlist_node *n;

  7.     hlist_for_each_entry(q, n, &f->hash[hash], list) {
  8.         /* net名称空间相等,且匹配函数返回true,则表示为正确的分片队列 */
  9.         if (q->net == nf && f->match(q, key)) {
  10.             atomic_inc(&q->refcnt);
  11.             read_unlock(&f->lock);
  12.             return q;
  13.         }
  14.     }
  15.     read_unlock(&f->lock);
     
     /* 
     没有找到正确的IP分片队列,需要重新创建一个新的IP分片队列。
     这个函数很简单,申请一个新的队列节点,计算其hash值,并将其添加到hash表中。
     */
  1.     return inet_frag_create(nf, f, key);
  2. }
IPv4的匹配函数很简单:
  1. static int ip4_frag_match(struct inet_frag_queue *q, void *a)
  2. {
  3.     struct ipq *qp;
  4.     struct ip4_create_arg *arg = a;

  5.     qp = container_of(q, struct ipq, q);
  6.     /* 
  7.     比较indentifier,源地址,目的地址,协议,以及分片队列的所有者。
  8.     这说明不同所有者,维护了不同的分片队列,它们之间互不影响。
  9.     */
  10.     return (qp->id == arg->iph->id &&
  11.             qp->saddr == arg->iph->saddr &&
  12.             qp->daddr == arg->iph->daddr &&
  13.             qp->protocol == arg->iph->protocol &&
  14.             qp->user == arg->user);
  15. }
大致看一下inet_frag_create:
  1. static struct inet_frag_queue *inet_frag_create(struct netns_frags *nf,
  2.         struct inet_frags *f, void *arg)
  3. {
  4.     struct inet_frag_queue *q;

  5.     q = inet_frag_alloc(nf, f, arg);
  6.     if (q == NULL)
  7.         return NULL;

  8.     return inet_frag_intern(nf, q, f, arg);
  9. }

  1. static struct inet_frag_queue *inet_frag_alloc(struct netns_frags *nf,
  2.         struct inet_frags *f, void *arg)
  3. {
  4.     struct inet_frag_queue *q;

  5.     q = kzalloc(f->qsize, GFP_ATOMIC);
  6.     if (q == NULL)
  7.         return NULL;
     /* 
     因为需要同时支持IPv4和IPv6分片,所以这里使用一个回调函数。并且这种方式分隔了一些细节问题。
     对于IPv4来说,该回调为ip4_frag_init。
     */
  1.     f->constructor(q, arg);
  2.     /* 增加内存使用量统计 */
  3.     atomic_add(f->qsize, &nf->mem);
  4.     /* 设置定时器,因为分片需要使用定时器清理过期的分片信息 */
  5.     setup_timer(&q->timer, f->frag_expire, (unsigned long)q);
  6.     spin_lock_init(&q->lock);
  7.     atomic_set(&q->refcnt, 1);
  8.     q->net = nf;

  9.     return q;
  10. }
新的分片队列的真正的添加函数
  1. static struct inet_frag_queue *inet_frag_intern(struct netns_frags *nf,
  2.         struct inet_frag_queue *qp_in, struct inet_frags *f,
  3.         void *arg)
  4. {
  5.     struct inet_frag_queue *qp;
  6. #ifdef CONFIG_SMP
  7.     struct hlist_node *n;
  8. #endif
  9.     unsigned int hash;

  10.     write_lock(&f->lock);
  11.     /*
  12.      * While we stayed w/o the lock other CPU could update
  13.      * the rnd seed, so we need to re-calculate the hash
  14.      * chain. Fortunatelly the qp_in can be used to get one.
  15.      */
  16.     /* 按注释所说,当拿到锁的时候,rnd随机值可能已经发生了变化,所以需要重新计算hash值 */
  17.     hash = f->hashfn(qp_in);
  18. #ifdef CONFIG_SMP
  19.     /* With SMP race we have to recheck hash table, because
  20.      * such entry could be created on other cpu, while we
  21.      * promoted read lock to write lock.
  22.      */
  23.     /* 
  24.     对于SMP的情况下,很可能其它CPU已经添加了该队列,所以需要重新检查。
  25.     内核代码写的就是细致。
  26.     */
  27.     hlist_for_each_entry(qp, n, &f->hash[hash], list) {
  28.         if (qp->net == nf && f->match(qp, arg)) {
  29.             /* 
  30.             其它CPU真的已经添加了该节点,那么我们只需要增加其计数器,并设置其标志位。
  31.             目前还没有细致看,大概看的结果是设置标志位INET_FRAG_COMPLETE是避免该队列被删除。
  32.             */
  33.             atomic_inc(&qp->refcnt);
  34.             write_unlock(&f->lock);
  35.             qp_in->last_in |= INET_FRAG_COMPLETE;
  36.             inet_frag_put(qp_in, f);
  37.             return qp;
  38.         }
  39.     }
  40. #endif

  41.     qp = qp_in;
  42.     /* 修改定时器 */
  43.     if (!mod_timer(&qp->timer, jiffies + nf->timeout))
  44.         atomic_inc(&qp->refcnt);
     
     /* 加新的队列节点添加到hash表中 */
  1.     atomic_inc(&qp->refcnt);
  2.     hlist_add_head(&qp->list, &f->hash[hash]);
  3.     list_add_tail(&qp->lru_list, &nf->lru_list);
  4.     nf->nqueues++;
  5.     write_unlock(&f->lock);
  6.     return qp;
  7. }

未完待续。。。
阅读(792) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~