IP重组实现分析

本文深入解析IP包重组机制,涵盖分片重组、超时处理及垃圾回收等关键环节。详细阐述了skb结构、ipq队列及ip碎片处理流程,包括ip_find、ip_frag_queue与ip_frag_reasm函数的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

IP重组过程包括分片重组,重组超时处理,垃圾回收,

这里主要分析分片重组过程

#define FRAG_CB(skb) ((struct ipfrag_skb_cb*)((skb)->cb))

Skb中的cb成员是skb中保存碎片控制信息,结构如下:

struct ipfrag_skb_cb

{

       struct inet_skb_parm      h;

       int                 offset;

};

其中的inet_skb_parm结构主要用来标识IP包的处理状态,offset记录碎片的偏移

 

Ipq结构

 

struct ipq {

       struct hlist_node list;

       struct list_head lru_list; 

       u32         user;

       __be32           saddr;

       __be32           daddr;

       __be16           id;

       u8           protocol;

       u8           last_in;

#define COMPLETE            4

#define FIRST_IN        2

#define LAST_IN                1

 

       struct sk_buff  *fragments;   

       int          len;        

       int          meat;

       spinlock_t       lock;

       atomic_t  refcnt;

       struct timer_list timer;  

       ktime_t          stamp;

       int             iif;

       unsigned int    rid;

       struct inet_peer *peer;

};

 

struct list_head lru_list;//最新使用的链表

#define COMPLETE            4  //状态标识,数据包完整到达

#define FIRST_IN        2     //状态标识,第一个包到达

#define LAST_IN                1  //最后一个数据包到达

struct sk_buff *fragments; //ip碎片链表

int          len; //偏移数据总长

int          meat; //所有碎片长度的总和

struct timer_list timer; //超时时间

int             iif; //接收数据的网卡的index

ktime_t          stamp;//最后一个时间片的时间戳

 

ipq队列长度为64.

ipq_hash是内核中的IP碎片处理队列,其中队列中每一个成员都是一个链表头,每个链表项保存一系列可重组的IP包序列

struct sk_buff *ip_defrag(struct sk_buff *skb, u32 user)

{

       struct ipq *qp;

       struct net_device *dev;

 

       IP_INC_STATS_BH(IPSTATS_MIB_REASMREQDS);

 

       //查看已经为碎片分配的内存是否已经超过了事先定义的上限,

       // sysctl_ipfrag_high_thresh初始化如下:

       // int sysctl_ipfrag_high_thresh __read_mostly = 256*1024

       //所以,装载碎片的内存上限是256K

       if (atomic_read(&ip_frag_mem) > sysctl_ipfrag_high_thresh)

      

       //释放当前缓冲区中未能重组的数据包

       ip_evictor();

      

       dev = skb->dev;

 

       //在ipq_hash哈希表中查找相对应的ipq项

       if ((qp = ip_find(ip_hdr(skb), user)) != NULL) {

              struct sk_buff *ret = NULL;

              spin_lock(&qp->lock);

              //将skb碎片包插入ipq中的碎片包链表

              ip_frag_queue(qp, skb);

             

              //如果是第一个碎片包或者最后一个碎片包,并且

              //偏移长度总和等于碎片长度总和,进入重组函数

              if (qp->last_in == (FIRST_IN|LAST_IN) &&

                  qp->meat == qp->len)

                     ret = ip_frag_reasm(qp, dev);

              spin_unlock(&qp->lock);

              ipq_put(qp, NULL);

              return ret;

       }

       IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);

       kfree_skb(skb);

       return NULL;

}

 

 

ip_find函数

 

static inline struct ipq *ip_find(struct iphdr *iph, u32 user)

{

       __be16 id = iph->id;

       __be32 saddr = iph->saddr;

       __be32 daddr = iph->daddr;

       __u8 protocol = iph->protocol;

       unsigned int hash;

       struct ipq *qp;

       struct hlist_node *n;

 

       read_lock(&ipfrag_lock);

 

       //根据源目的IP,id,协议号算出哈希值

       hash = ipqhashfn(id, saddr, daddr, protocol);

      

       //根据哈希值,在ipq_hash表中找到相应的链表头,进行遍历

       hlist_for_each_entry(qp, n, &ipq_hash[hash], list) {

 

              //这里可以看出匹配的条件是源目的ip,id,协议号以及user相等,

              //其中比较有意思的是user项,原来2.4.x内核中是没有这个项的,

              //user实际上是个枚举值,指明了重组发生的位置,枚举类型定义如下:

              /--------------------------------------------------------

              enum ip_defrag_users

              {

              IP_DEFRAG_LOCAL_DELIVER,

              IP_DEFRAG_CALL_RA_CHAIN,

              IP_DEFRAG_CONNTRACK_IN,

              IP_DEFRAG_CONNTRACK_OUT,

              IP_DEFRAG_VS_IN,

              IP_DEFRAG_VS_OUT,

              IP_DEFRAG_VS_FWD

              };

 

              目前的user值应该是IP_DEFRAG_LOCAL_DELIVER,还可能是在

              CONNTRACK或者其他处理过程中调用的重组函数

              -----------------------------------------------------/

              if (qp->id == id            &&

                  qp->saddr == saddr       &&

                  qp->daddr == daddr      &&

                  qp->protocol == protocol &&

                  qp->user == user) {

                     atomic_inc(&qp->refcnt);

                     read_unlock(&ipfrag_lock);

                     return qp;

              }

       }

       read_unlock(&ipfrag_lock);

 

       return ip_frag_create(iph, user);

}

 

 

ip_frag_queue函数

 

这个函数将收到的skb插到ipq结构的fragments碎片包链表中

 

static void ip_frag_queue(struct ipq *qp, struct sk_buff *skb)

{

       struct sk_buff *prev, *next;

       int flags, offset;

       int ihl, end;

      

       //不考虑碎片重组已经完成的情况

       if (qp->last_in & COMPLETE)

              goto err;

 

       //检查skb中的分片标志

       if (!(IPCB(skb)->flags & IPSKB_FRAG_COMPLETE) &&

       //检查碎片是否间隔过远,如果中间间隔超过64个报文无法完成重组,协议

       //栈就不再支持

           unlikely(ip_frag_too_far(qp)) && unlikely(ip_frag_reinit(qp))) {

              ipq_kill(qp);

              goto err;

       }

       //计算碎片包的偏移量

       offset = ntohs(ip_hdr(skb)->frag_off);

       flags = offset & ~IP_OFFSET;

       offset &= IP_OFFSET;

       //偏移量是8字节的倍数,所以左移3

       offset <<= 3;        

   

       //取得IP头的长度

       ihl = ip_hdrlen(skb);

 

      //本数据包插入后报文长度等于本数据包的偏移量加上数据包长度再减去IP头长度

       end = offset + skb->len - ihl;

 

       //如果是最后一个碎片包

       if ((flags & IP_MF) == 0) {

       //如果完成数据包插入以后整个数据包长度小于偏移数据总长

       if (end < qp->len ||

                  ((qp->last_in & LAST_IN) && end != qp->len))

                     goto err;

 

              //设置ipq标志并得出数据偏移总长

              qp->last_in |= LAST_IN;

              qp->len = end;

       } else {

              //检查报文除去IP头之外的数据部分长度是否8字节对齐

                 if (end&7) {

                     end &= ~7;

                     if (skb->ip_summed != CHECKSUM_UNNECESSARY)

                            skb->ip_summed = CHECKSUM_NONE;

              }

             

              if (end > qp->len) {

                    

                     if (qp->last_in & LAST_IN)

                            goto err;

                     qp->len = end;

              }

       }

       if (end == offset)

              goto err;

  

       //将skb data指针挪到ip首部以后

       if (pskb_pull(skb, ihl) == NULL)

              goto err;

 

       if (pskb_trim_rcsum(skb, end-offset))

              goto err;

 

       prev = NULL;

      

    //遍历ipq中的fragments链表,每个skb的cb成员记录着当前skb进行ip重组的

    //时候所需要的偏移,fragments中的skb都是按照offset升序排好的,所以,找到

    //第一项offset大于当前IP包偏移的数据包就可以了

    for (next = qp->fragments; next != NULL; next = next->next) {

              if (FRAG_CB(next)->offset >= offset)

                     break;    

              prev = next;

       }

 

    //如果发生了重叠的情况,则以后数据包的偏移量都要减小,累加值也需要减小

       if (prev) {

              int i = (FRAG_CB(prev)->offset + prev->len) - offset;

 

              if (i > 0) {

                     offset += i;

                     if (end <= offset)

                            goto err;

                     if (!pskb_pull(skb, i))

                            goto err;

                     if (skb->ip_summed != CHECKSUM_UNNECESSARY)

                            skb->ip_summed = CHECKSUM_NONE;

              }

       }

×××××××××××××××××××××××××××××××××××××

 

       FRAG_CB(skb)->offset = offset;

 

       //将当前skb插入ipq fragments链表,上面省去部分代码,实际上除了第一个报文

      //以外,所有的碎片包都要使用skb_pull剥离掉ip头以后将剩余部分插入

       skb->next = next;

       if (prev)

              prev->next = skb;

       else

              qp->fragments = skb;

 

 

       if (skb->dev)

              qp->iif = skb->dev->ifindex;

       skb->dev = NULL;

       qp->stamp = skb->tstamp;

      

qp->meat += skb->len;

       atomic_add(skb->truesize, &ip_frag_mem);

       if (offset == 0)

              qp->last_in |= FIRST_IN;

 

       write_lock(&ipfrag_lock);

       list_move_tail(&qp->lru_list, &ipq_lru_list);

       write_unlock(&ipfrag_lock);

 

       return;

 

err:

       kfree_skb(skb);

}

static struct sk_buff *ip_frag_reasm(struct ipq *qp, struct net_device *dev)

{

       struct iphdr *iph;

       struct sk_buff *fp, *head = qp->fragments;

       int len;

       int ihlen;

 

       ipq_kill(qp);

 

       BUG_TRAP(head != NULL);

       BUG_TRAP(FRAG_CB(head)->offset == 0);

      

       //取得ipq中fragments头节点的ip头长度

       ihlen = ip_hdrlen(head);

 

       //如果把头节点的IP首部长度加上ipq结构中的碎片总长度相加,就得到了重组之

       //后报文的长度

       len = ihlen + qp->len;

 

       if (len > 65535)

              goto out_oversize;

 

       //头节点必须没有被克隆过

       if (skb_cloned(head) && pskb_expand_head(head, 0, 0, GFP_ATOMIC))

              goto out_nomem;

 

       //碎片中第一个节点如果是分片的,需要特殊处理,这里的”分片”并不是指IP包的

       //碎片,而是指skb存储结构离散分布,并不在一个连续的内存空间内

 

       if (skb_shinfo(head)->frag_list) {

              struct sk_buff *clone;

              int i, plen = 0;

              //如果头节点是分片的,那么需要重新申请一个skb,并且把这个新的skb放到

              //第一个skb end指针之后skb_shared_info结构的frag_list链表上

              if ((clone = alloc_skb(0, GFP_ATOMIC)) == NULL)

                     goto out_nomem;

              clone->next = head->next;

              head->next = clone;

 

              //把head原来的分片放在新申请的skb的frag_list里面

              skb_shinfo(clone)->frag_list = skb_shinfo(head)->frag_list;

              skb_shinfo(head)->frag_list = NULL;

 

              //计算head中总的分片长度

              for (i=0; i<skb_shinfo(head)->nr_frags; i++)

                     plen += skb_shinfo(head)->frags[i].size;

             

              //实际上最后生成了一个自身数据为0.不包含任何数据,但是

              //这个新的的frag_list中却包含了所有的分片

              clone->len = clone->data_len = head->data_len - plen;

              head->data_len -= clone->len;

              head->len -= clone->len;

              clone->csum = 0;

              clone->ip_summed = head->ip_summed;

              atomic_add(clone->truesize, &ip_frag_mem);

       }

    

       //把head以后所有的碎片都当作是head frag_list里面的分片来处理

       skb_shinfo(head)->frag_list = head->next;

 

 

       skb_push(head, head->data - skb_network_header(head));

       atomic_sub(head->truesize, &ip_frag_mem);

 

       //协议栈的处理会通过skb_linearize()函数将head报文的frag_list链表里面的数据包

       //都合并成一个报文,所以将链表里面所有skb的len和data_len,以及true_size都

       //和head中相应的值相加,最后得到了合并后数据包的长度

       for (fp=head->next; fp; fp = fp->next) {

              head->data_len += fp->len;

              head->len += fp->len;

              if (head->ip_summed != fp->ip_summed)

                     head->ip_summed = CHECKSUM_NONE;

              else if (head->ip_summed == CHECKSUM_COMPLETE)

                     head->csum = csum_add(head->csum, fp->csum);

              head->truesize += fp->truesize;

              atomic_sub(fp->truesize, &ip_frag_mem);

       }

 

       head->next = NULL;

       head->dev = dev;

       head->tstamp = qp->stamp;

 

       iph = ip_hdr(head);

       iph->frag_off = 0;

       iph->tot_len = htons(len);

       IP_INC_STATS_BH(IPSTATS_MIB_REASMOKS);

       qp->fragments = NULL;

       return head;

 

      //省去部分代码

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值