IP 碎片重组

IP 碎片重组

内核中的IP重组函数.
struct sk_buff * ip_defrag(struct sk_buff * skb, u32 user)
{
  	......
  	//如果内核范围超出限制
  	if (atomic_read(&ip_frag_mem) > sysctl_ipfrag_high_thresh)
            	ip_evictor(); //回收内存到限制之内
  	......
  	if ((qp = ip_find(iph, user)) != NULL) { //查找或者创建一个队列头
       	......
       	ip_frag_queue(qp, skb); //收集IP碎片
           if (qp->last_in == (FIRST_IN|LAST_IN) && qp->meat == qp->len) //IP碎片收集完毕
                    	ret = ip_frag_reasm(qp, dev); //把收集的碎片重组,主要是更新头结点
           ......
           return ret; //返回新重组后的数据报头
      }
      ......
      return NULL; //队列头创建失败
}
我们看到主要有三个函数最重要,我们一个一个看.
首先看一下数据结构
struct ipq {
    	struct hlist_node list;     	//hash list
    	......
    	struct sk_buff  *fragments; 	// linked list of received fragments
    	int         	len;        	// 应该接收数据长度
    	int         	meat;      	// 实际接收数据长度
    	......
};
static inline struct ipq * ip_find(struct iphdr * iph, u32 user)
{
  	......
  	//根据ip中的信息,计算出一个hash值
  	hash = ipqhashfn(id, saddr, daddr, protocol);
  	//在hash表中查找相关队列头
  	hlist_for_each_entry(qp, n, &ipq_hash[hash], list) {
       	if(......) //找到匹配
            	return qp;
  	}
  	......
  	//没找到,创建一个然后放入hash表中
  	return ip_frag_create(iph, user);
}
//好好看看,不太难
static void ip_frag_queue(struct ipq * qp, struct sk_buff * skb)
{
  	struct sk_buff * prev, * next;
  	int flags, offset;
  	int ihl, end;
  	......
  	offset = ntohs(skb->nh.iph->frag_off);
  	flags = offset & ~IP_OFFSET;     	// 获取标志位
  	offset &= IP_OFFSET;
  	offset <<= 3;                     	// 确定偏移位置
  	ihl = skb->nh.iph->ihl * 4;       	// ip 头长度
  	end = offset + skb->len - ihl;     	// 确定这个IP包数据在完整包中的结束点

  	if ((flags & IP_MF) == 0) { //是最后一个IP包吗
        	......
        	qp->last_in |= LAST_IN; //也许没有受到全部的包,但这个报一定是那最后一个,所以加标志
        	......
  	} else {
        	......
        	if (end > qp->len) {
                    	.......
                    	qp->len = end; //记录最后的位置
        	}
  	}
  	.......
  	//找到这个数据报在全部数据报中的位置
  	prev = NULL;
  	for(next = qp->fragments; next != NULL; next = next->next) {
            	if (FRAG_CB(next)->offset >= offset)
                    	break;  /* bingo! */
            	prev = next;
  	}
     
  	if (prev) { //有上个位置的包
            	//计算复盖的长度
            	int i = (FRAG_CB(prev)->offset + prev->len) - offset;

            	if (i > 0) { //有复盖
                    	offset += i; //调整这个包的偏移
                    	.......
            	}
    	}
    	//有下个位置的包
    	while (next && FRAG_CB(next)->offset < end) {
            	int i = end - FRAG_CB(next)->offset; //计算复盖的长度
            	// 没有复盖全部的next包
            	if (i < next->len) {
                  	......
                  	FRAG_CB(next)->offset += i; //调整next的偏移
                  	qp->meat -= i; //调整实际数据长度
                  	......
            	} else {
                  	struct sk_buff * free_it = next;
                  	......
                  	frag_kfree_skb(free_it, NULL); //释放掉她
            	}
     	}
     	//记录这个偏移
     	FRAG_CB(skb)->offset = offset;
     	//插入这个数据包
     	skb->next = next;
     	if (prev)
            	prev->next = skb;
     	else
            	qp->fragments = skb;
     	......
     	qp->meat += skb->len; //记录实际接收数据的长度
     	......
}
//重组一个新包,并不实际拷贝数据,因为包已经被连接好 skb->netx->next->next
static struct sk_buff * ip_frag_reasm(struct ipq * qp, struct net_device * dev)
{       
     	......
         struct sk_buff * fp, * head = qp->fragments;
         ......
         ihlen = head->nh.iph->ihl * 4; //IP头长度
     	len = ihlen + qp->len;     	//头加数据的总长度
     	......
     	skb_shinfo(head)->frag_list = head->next;	//指向头下的一个数据包
     	skb_push(head, head->data - head->nh.raw); //调整指针
    	......
     	for (fp = head->next; fp; fp = fp->next) {   //长度收集
            	head->data_len += fp->len;
            	head->len += fp->len;
            	.......
            	head->truesize += fp->truesize;
            	atomic_sub(fp->truesize, &ip_frag_mem); //使用内存调整
     	}
     	head->next = NULL; //next NULL
     	.......
     	qp->fragments = NULL; // set NULL
     	return head;
     	.......
}
IP 分片
ip_output->ip_finish_output 中
......
if (skb->len > dst_mtu(skb->dst) && !skb_is_gso(skb))  //包长度超过mtu 且没有开启 gso (gso参考: http://lwn.net/Articles/189970/)
       return ip_fragment(skb, ip_finish_output2); //分片
  	 else
       return ip_finish_output2(skb);

int ip_fragment(struct sk_buff *skb, int (*output)(struct sk_buff*))
{
     struct iphdr *iph;
     int raw = 0;
      int ptr;
      struct net_device *dev;
    struct sk_buff *skb2;
      unsigned int mtu, hlen, left, len, ll_rs;
      int offset;
      __be16 not_last_frag;
      struct rtable *rt = (struct rtable*)skb->dst; //路由项
      int err = 0;
      dev = rt->u.dst.dev;
      iph = skb->nh.iph; //获取ip头

      //设置了不分片标志
      if (unlikely((iph->frag_off & htons(IP_DF)) && !skb->local_df)) {
            IP_INC_STATS(IPSTATS_MIB_FRAGFAILS);
            icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(dst_mtu(&rt->u.dst)));
            kfree_skb(skb);
            return -EMSGSIZE;
      }
      hlen = iph->ihl * 4; //ip头长度
      mtu = dst_mtu(&rt->u.dst) - hlen; //没有ip头的mtu大小
      IPCB(skb)->flags |= IPSKB_FRAG_COMPLETE;

      if (skb_shinfo(skb)->frag_list) { //如果skb已经被分片了
            struct sk_buff *frag;
            int first_len = skb_pagelen(skb); //返回skb数据长度
            //数据长度大于mtu 或  没有8字节对齐 或 ip头中标志了分片 或 skb被cloned
            if (first_len - hlen > mtu || ((first_len - hlen) & 7) || (iph->frag_off & htons(IP_MF|IP_OFFSET)) || skb_cloned(skb))
                  goto slow_path; //进入慢速路径

            //循环每一个碎片
            for (frag = skb_shinfo(skb)->frag_list; frag; frag = frag->next) {
                  //长度检查
                  if (frag->len > mtu || ((frag->len & 7) && frag->next) || skb_headroom(frag) < hlen)
                        goto slow_path;

                  /* Partially cloned skb? */
                  if (skb_shared(frag)) //碎片被共享
                        goto slow_path;

                  BUG_ON(frag->sk);
                  if (skb->sk) { //skb持有 sock结构
                        sock_hold(skb->sk);
                        frag->sk = skb->sk;
                        frag->destructor = sock_wfree;
                        skb->truesize -= frag->truesize; //长度减少
                  }
             }
             /* Everything is OK. Generate! */
             err = 0;
             offset = 0;
             frag = skb_shinfo(skb)->frag_list; //碎片头
             skb_shinfo(skb)->frag_list = NULL; //skb 碎片头指针清空
             skb->data_len = first_len - skb_headlen(skb);
             skb->len = first_len; //更新长度
             iph->tot_len = htons(first_len);
             iph->frag_off = htons(IP_MF); //设置标志
             ip_send_check(iph); //计算skb的校验和

             for (;;) {
                   /* Prepare header of the next frame, before previous one went down. */
                   if (frag) { //碎片
                         frag->ip_summed = CHECKSUM_NONE;
                         frag->h.raw = frag->data; //传输头
                         frag->nh.raw = __skb_push(frag, hlen); //留出网络头空间
                         memcpy(frag->nh.raw, iph, hlen); //copy skb中的ip头到这个碎片中当作网络头
                         iph = frag->nh.iph;
                         iph->tot_len = htons(frag->len); //更新长度
                         ip_copy_metadata(frag, skb); //copy skb中的一些其他字段的值到碎片
                         if (offset == 0) //第一个碎片
                               ip_options_fragment(frag); //构建选项

                         offset += skb->len - hlen; //更新偏移
                         iph->frag_off = htons(offset>>3); //ip头中保存偏移
                         if (frag->next != NULL) //还有下一个碎片
                               iph->frag_off |= htons(IP_MF);

                         /* Ready, complete checksum */
                         ip_send_check(iph); //计算校验和
                   }
                   err = output(skb);//发送数据包

                   if (!err)
                         IP_INC_STATS(IPSTATS_MIB_FRAGCREATES);

                   if (err || !frag)
                         break;

                   //循环到下一个
                   skb = frag;
                   frag = skb->next;
                   skb->next = NULL;

             }
             if (err == 0) { //正确
                   IP_INC_STATS(IPSTATS_MIB_FRAGOKS);
                   return 0;
             }

             while (frag) { //发送不正确
                   skb = frag->next;
                   kfree_skb(frag);
                   frag = skb;
             }
             IP_INC_STATS(IPSTATS_MIB_FRAGFAILS);
             return err;
       }
slow_path: //慢速路径
      left = skb->len - hlen;     	//ip头后面数据长度
      ptr = raw + hlen;           	// 0 + ip头长度
#ifdef CONFIG_BRIDGE_NETFILTER
      /* for bridged IP traffic encapsulated inside f.e. a vlan header,                       	
       * we need to make room for the encapsulating header */
      ll_rs = LL_RESERVED_SPACE_EXTRA(rt->u.dst.dev, nf_bridge_pad(skb));
      mtu -= nf_bridge_pad(skb);
#else
      ll_rs = LL_RESERVED_SPACE(rt->u.dst.dev); //预留头空间
#endif

      /* Fragment the datagram. */
      offset = (ntohs(iph->frag_off) & IP_OFFSET) << 3; //碎片偏移
      not_last_frag = iph->frag_off & htons(IP_MF); //是否是最后一个碎片

      while(left > 0) {
            len = left;
            /* IF: it doesn't fit, use 'mtu' - the data space left */
            if (len > mtu) //数据包长度 > mtu
                  len = mtu;

            /* IF: we are not sending upto and including the packet end then align the next start on an eight byte boundary */
            if (len < left) {
                  len &= ~7; //8 字节对齐
            }
            //分配碎片
            if ((skb2 = alloc_skb(len + hlen + ll_rs, GFP_ATOMIC)) == NULL) {
                  NETDEBUG(KERN_INFO "IP: frag: no memory for new fragment!\n");
                  err = -ENOMEM;
                  goto fail;

            }
            ip_copy_metadata(skb2, skb); //copy skb中的其他数据到skb2
            skb_reserve(skb2, ll_rs); //skb2->data += ll_rs 保留空间
            skb_put(skb2, len + hlen); //设置skb长度
            skb2->nh.raw = skb2->data; //指向网络头
            skb2->h.raw = skb2->data + hlen; //指向传输头

            if (skb->sk) //skb属于一个 sock
                  skb_set_owner_w(skb2, skb->sk);

            memcpy(skb2->nh.raw, skb->data, hlen); //copy网络头

            if (skb_copy_bits(skb, ptr, skb2->h.raw, len))//copy网络头后面的数据 长度是len,不超过mtu
                  BUG();

            left -= len; //总长度减少
            iph = skb2->nh.iph; //获取ip头
            iph->frag_off = htons((offset >> 3)); //设置碎片偏移

            if (offset == 0) //第一个碎片,构建ip选项
                  ip_options_fragment(skb);

            if (left > 0 || not_last_frag) //还有碎片需要处理
                  iph->frag_off |= htons(IP_MF);
               
            ptr += len;//更新copy位置
            offset += len;//偏移增加
            iph->tot_len = htons(len + hlen); //更新 ip头中长度
            ip_send_check(iph); //计算校验和
            err = output(skb2); //发送数据包
            if (err)
                  goto fail;

            IP_INC_STATS(IPSTATS_MIB_FRAGCREATES);
      }
      kfree_skb(skb);
      IP_INC_STATS(IPSTATS_MIB_FRAGOKS);
      return err;
fail:

      kfree_skb(skb);
      IP_INC_STATS(IPSTATS_MIB_FRAGFAILS);
      return err;
}

  

posted on 2013-08-27 10:29  SuperKing  阅读(1378)  评论(0编辑  收藏  举报

导航