系统定义了一个大小为IPQ_HASHSZ=64的ipq_hash表,static struct ipq *ipq_hash[IPQ_HASHSZ];每个数组元素就是一个具有相同hash值的链表。每个链表上的节点就代表着同一个链接的IP的碎片。
ip_frage_mem
存储分片的内存,其初始化
什么情况,或者说什么协议会尝试发送这么长的数据?常见的有UDP和ICMP,需要特别注意的是,TCP一般不会。
为什么TCP不会造成IP分片呢?原因是TCP自身支持分段:当TCP要传输长度超过MSS(Maxitum Segment Size)的数据时,会先对数据进行分段,正常情况下,MSS小于MTU,因此,TCP一般不会造成IP分片。
而UDP和ICMP就不支持这种分段功能了,UDP和ICMP认为网络层可以传输无限长(实际上有65535的限制)的数据,当这两种协议发送数据时,它们不考虑数据长度,仅在其头部添加UDP或ICMP首部,然后直接交给网络层就万事大吉了。接着网络层IP协议对这种“身长头短”的数据进行分片,不要指望IP能很“智能”地识别传给它的数据上层头部在哪里,载荷又在哪里,它会直接将整个的数据切成N个分片,这样做的结果是,只有第一个分片具有UDP或者ICMP首部,而其它分片则没有。
相关代码实现:
//处理一个传过来的IP数据报
struct sk_buff *ip_defrag(struct sk_buff *skb)
{
struct iphdr *iph = skb->nh.iph;
struct ipq *qp;
struct net_device *dev;
IP_INC_STATS_BH(IpReasmReqds);
/* Start by cleaning up the memory. */
//如果用于分片处理的内存空间大于系统规定的最大值256k,那么要进行清洗ip_evictor
if (atomic_read(&ip_frag_mem) > sysctl_ipfrag_high_thresh)
ip_evictor();
//指定IP包对应设备dev
dev = skb->dev;
/* Lookup (or create) queue header */
if ((qp = ip_find(iph)) != NULL) { //根据HASH值,定位该包在分片链中的位置:
struct sk_buff *ret = NULL;
spin_lock(&qp->lock);
//将该分片插入到分片队列中:设置设备,增加meat分片总长度,如果偏移为0则说明是第一个包,置上 FIRST_IN标志。
ip_frag_queue(qp, skb);
//检查该分片是否是最后一个分片(分片是否都到齐了,包长度)如果是则进行分片重组调用 ip_frag_reasm函数
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);
return ret;
}
IP_INC_STATS_BH(IpReasmFails);
kfree_skb(skb);
return NULL;
}
// 碎片的重组过程
static struct sk_buff *ip_frag_reasm(struct ipq *qp, struct net_device *dev)
{
struct sk_buff *skb;
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);
ihlen = head->nh.iph->ihl*4; //取队列头IP包头长度
len = ihlen + qp->len; //总长度
if(len > 65535)
goto out_oversize;
//为新的包分配sk_buff结构,填入相应的值:设置新的IP总长度(不能超过65535字节);帧头位置、IP头位置、选项数据
skb = dev_alloc_skb(len);
if (!skb)
goto out_nomem;
/* Fill in the basic details. */
skb->mac.raw = skb->data;
skb->nh.raw = skb->data;
FRAG_CB(skb)->h = FRAG_CB(head)->h; //复制IP选项信息
skb->ip_summed = head->ip_summed;
skb->csum = 0;
//拷贝原始的IP头(分片队列的第一个分片中有记录)到新的skb结构
memcpy(skb_put(skb, ihlen), head->nh.iph, ihlen);
//循环拷贝:将分片链上的分片skb数据拷贝到新的skb结构中。
for (fp=head; fp; fp = fp->next) {
memcpy(skb_put(skb, fp->len), fp->data, fp->len);
//增加校样值
if (skb->ip_summed != fp->ip_summed)
skb->ip_summed = CHECKSUM_NONE;
else if (skb->ip_summed == CHECKSUM_HW)
skb->csum = csum_add(skb->csum, fp->csum);
}
//设置目的地址(克隆)、包类型、协议、设备。
skb->dst = dst_clone(head->dst);
skb->pkt_type = head->pkt_type;
skb->protocol = head->protocol;
skb->dev = dev;
/*
* Clearly bogus, because security markings of the individual
* fragments should have been checked for consistency before
* gluing, and intermediate coalescing of fragments may have
* taken place in ip_defrag() before ip_glue() ever got called.
* If we're not going to do the consistency checking, we might
* as well take the value associated with the first fragment.
* --rct
*/
skb->security = head->security;
//进行防火墙处理
#ifdef CONFIG_NETFILTER
/* Connection association is same as fragment (if any). */
skb->nfct = head->nfct;
nf_conntrack_get(skb->nfct);
#ifdef CONFIG_NETFILTER_DEBUG
skb->nf_debug = head->nf_debug;
#endif
#endif
/* Done with all fragments. Fixup the new IP header. */
//重新设置IP头、将3位标志和13位偏移设置为0、计算总长度。
iph = skb->nh.iph;
iph->frag_off = 0;
iph->tot_len = htons(len);
IP_INC_STATS_BH(IpReasmOKs);
return skb;
out_nomem:
NETDEBUG(printk(KERN_ERR
"IP: queue_glue: no memory for gluing queue %p\n",
qp));
goto out_fail;
out_oversize:
if (net_ratelimit())
printk(KERN_INFO
"Oversized IP packet from %d.%d.%d.%d.\n",
NIPQUAD(qp->saddr));
out_fail:
IP_INC_STATS_BH(IpReasmFails);
return NULL;
}
/在队列中增加新的分片处理过程. */
static void ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
{
struct iphdr *iph = skb->nh.iph;
struct sk_buff *prev, *next;
int flags, offset;
int ihl, end;
if (qp->last_in & COMPLETE)
goto err;
//根据该包的标志位与偏移量:如果是最后一个分片包(但不一定分片完全到齐),设置分片队列长度为原 包的长度;如果是比当前分片靠后的包,改变分片队列的长度;如果是靠前的包。
offset = ntohs(iph->frag_off); //取片偏移量描述字
flags = offset & ~IP_OFFSET; //取片标志
offset &= IP_OFFSET; //求片偏移量
offset <<= 3; /* offset is in 8-byte chunks */
ihl = iph->ihl * 4;
/* Determine the position of this fragment. */
end = offset + (ntohs(iph->tot_len) - ihl); //求该片段尾部的数据偏移量
/* Is this the final fragment? */
if ((flags & IP_MF) == 0) { //最后一片段
/* If we already have some bits beyond end
* or have different end, the segment is corrrupted.
*/
if (end < qp->len || //最后一个片段的未尾小于队列内最大的未尾
((qp->last_in & LAST_IN) && end != qp->len))
goto err;
qp->last_in |= LAST_IN;
qp->len = end; //设取队列最大未尾
} else { //是中间某个片段
if (end&7) { //如果片段尾部不在8字节上对齐
end &= ~7;
if (skb->ip_summed != CHECKSUM_UNNECESSARY)
skb->ip_summed = CHECKSUM_NONE; 不计算校验和
}
if (end > qp->len) { ///该片段比队列内其它成员位置要大
/* Some bits beyond end -> corruption. */
if (qp->last_in & LAST_IN)
goto err;
qp->len = end;
}
}
if (end == offset) //片段的数据区长度为零
goto err;
/* Point into the IP datagram 'data' part. */
skb_pull(skb, (skb->nh.raw+ihl) - skb->data); //删除IP包的头部
skb_trim(skb, end - offset); //去队尾部可能的衬垫
/* Find out which fragments are in front and at the back of us
* in the chain of fragments so far. We must know where to put
* this fragment, right?
*/
prev = NULL; //扫描重组队列中的包片段,取偏移大于或等于当前包偏移的前一成员作为插入位置
for(next = qp->fragments; next != NULL; next = next->next) {
if (FRAG_CB(next)->offset >= offset)
break; /* bingo! */
prev = next;
}
//当前偏移的包
/* We found where to put this one. Check for overlap with
* preceding fragment, and, if needed, align things so that
* any overlaps are eliminated.
*/
//如果该分片不是第一个分片(prev!=null),先消除与前一分片重叠:求prev尾部与当前偏移之差,(该偏移包,不一定就是紧接着的那一个包);再消除与后一分片的重叠,求当前偏移与next重叠之差,如果当前包尾部小于后一包尾部,后一包起点后移,后一分片交叠部分清除,减少分片统计总长度meat;如果后一分片完全包含在此分片中,清除它next,减少分片统计总长度meat。
if (prev) {
int i = (FRAG_CB(prev)->offset + prev->len) - offset; //求prev尾部与当前偏移之差
if (i > 0) { //插入点成员尾部大于当前包开始, 说明当前包与前一包重叠
offset += i; //当前包起点后移
if (end <= offset)
goto err;
skb_pull(skb, i); //删除当前包前部i字节.
if (skb->ip_summed != CHECKSUM_UNNECESSARY)
skb->ip_summed = CHECKSUM_NONE;
}
}
// next是当前包的后一包
while (next && FRAG_CB(next)->offset < end) { //后一包与当前包有重叠
int i = end - FRAG_CB(next)->offset; /* overlap is 'i' bytes */
if (i < next->len) { //当前包尾部小于后一包尾部
/* Eat head of the next overlapped fragment
* and leave the loop. The next ones cannot overlap.
*/
FRAG_CB(next)->offset += i; //后一包起点后移
skb_pull(next, i); //删除后一包i字节
qp->meat -= i; //meat为队列已容纳的总字节数
if (next->ip_summed != CHECKSUM_UNNECESSARY)
next->ip_summed = CHECKSUM_NONE;
break;
} else { //当前包尾部大于或等于后一包尾部, 则删除后一包
struct sk_buff *free_it = next;
/* Old fragmnet is completely overridden with
* new one drop it.
*/
next = next->next;
if (prev)
prev->next = next;
else //说明next包是队列首包
qp->fragments = next;
qp->meat -= free_it->len;
frag_kfree_skb(free_it);
}
}
FRAG_CB(skb)->offset = offset; //在skb的cb[]块上记录当前包代表的数据位移
/* Insert this fragment in the chain of fragments. */
//将该分片插入到分片队列中:设置设备,增加meat分片总长度,如果偏移为0则说明是第一个包,置上FIRST_IN标志。
skb->next = next;
if (prev)
prev->next = skb;
else
qp->fragments = skb; //作为首包
if (skb->dev)
qp->iif = skb->dev->ifindex; //取包的输入设号号
skb->dev = NULL;
qp->meat += skb->len;
atomic_add(skb->truesize, &ip_frag_mem); //truesize为包描述结构与数据区总长
if (offset == 0) //首包
qp->last_in |= FIRST_IN;
return;
err:
kfree_skb(skb);
}