Linux内核分析 - 网络[八]:IP协议
ip_frag_reasm函数实现IP分片的重组 ip_frag_reasm传入的参数是prev,而重组完成后ip_defrag会将skb替换成重 组后的新的skb,而在之前的操作中,skb插入了qp->q.fragments中,并且prev->next即为skb,因此第一步就是让skb变 成qp->q.fragments,即IP分片的头部。 if (prev) { head = prev->next; fp = skb_clone(head, GFP_ATOMIC); if (!fp) goto out_nomem; fp->next = head->next; prev->next = fp; skb_morph(head, qp->q.fragments); head->next = qp->q.fragments->next; kfree_skb(qp->q.fragments); qp->q.fragments = head; } 下面图示说明了上面代码段作用,skb是IP分片3,通过skb_clone拷贝一份3_copy替代之前的分片3,再通过 skb_morph拷贝q.fragments到原始IP分片3,替代分片1,并释放分片1: 获取IP报头长度 ihlen,head就是ip_defrag传入参数中的skb,并且它已经成为了IP分片队列的头部;len为整个IP报头+报文的总长度,qp- >q.len是未分片前IP报文的长度。 ihlen = ip_hdrlen(head); len = ihlen + qp->q.len; 此时head就是skb,并且它的skb->data存储了第一个IP分片的内容,其它IP分片的 内容将存储在紧接skb的空间 – frag_list;skb_push将skb->data回归原位,即未处理IP报头前的位置,因为之前的IP分片 处理会调用skb_pull移走IP报头,将它回归原位是因为skb即将作为重组后的报文而被处理,那里会真正的skb_pull移走IP报头 ,再交由上层协议处理。 skb_shinfo(head)->frag_list = head->next; skb_push(head, head->data - skb_network_header(head)); 上面所说的frag_list是struct skb_shared_info的 一个属性,在分配skb时分配在其后空间,通过skb_shinfo(skb)进行引用。下面分配skb大小size和skb_shared_info大小的代码 摘自[net/core/skbuff.c] size = SKB_DATA_ALIGN(size); data = kmalloc_node_track_caller(size + sizeof(struct skb_shared_info), gfp_mask, node); 这里要弄清楚sk_buff中线性存储区和paged buffer的区别,线性存储区就是存储报文,如果是分 片后的,则只是第一个分片的内容;而paged buffer则存储其余分片的内容。而skb->data_len则表示paged buffer中内容长 度,而skb->len则是paged buffer + linear buffer。下面这段代码就是根据余下的分片增加data_len和len计数。 for (fp=head->next; fp; fp = fp->next) { head->data_len += fp->len; head->len += fp->len; …… } IP分片已经重组完成,分片从q.fragments链表移到了frag_list上,因此head->next和qp->q.fragments置为 NULL。偏移量frag_off置0,总长度tot_len置为所有分片的长度和,这样,skb就相当于没有分片的完整的大数据包,继续向上 传递。 head->next = NULL; head->dev = dev; …… iph = ip_hdr(head); iph->frag_off = 0; iph->tot_len = htons(len); IP_INC_STATS_BH(net, IPSTATS_MIB_REASMOKS); qp->q.fragments = NULL; (编辑:源码网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |