在讲解此函数前 有个概念需要再次说明一下,即分片重叠的问题
比如在没有接收到新来的分片时候 分片队列的分布如下:
当在接收到一个分片的时候,遍历链表(已经升序排序好)后找到插入位置后根据重叠与非重叠 有如下情况:
(1)不发生重叠的正常情况:
比如新分片的offset=300 len=99
则插入情况如下:
(2)发生前重叠
比如新分片的offset=280 len=119
则插入的情况如下:
(3)发送后重叠
比如新分片的offet=300 len=280
则插入的情况如下:
以上图就是对代码中覆盖部分的总结,根据图我们来看下面代码的重叠处理部分会容易理解一些
/* Add new segment to existing queue. */
static int ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
{
struct sk_buff *prev, *next;
struct net_device *dev;
int flags, offset;
int ihl, end;
int err = -ENOENT;
//碎片重组已经完成
if (qp->q.last_in & INET_FRAG_COMPLETE)
goto err;
/*
IPCB(skb)->flags只有在本机发送IPv4分片时被置位,那么这里的检查应该是预防收到本机自己发出的IP分片。
*/
if (!(IPCB(skb)->flags & IPSKB_FRAG_COMPLETE) &&//检查skb中的分片标志
//关于ip_frag_too_far:该函数主要保证了来自同一个peer(相同的源地址)不会占用过多的IP分片队列
unlikely(ip_frag_too_far(qp)) &&
//重新初始化该队列
unlikely(err = ip_frag_reinit(qp)))
{
ipq_kill(qp);
goto err;
}
//计算碎片的偏移量和标志
offset = ntohs(ip_hdr(skb)->frag_off);
flags = offset & ~IP_OFFSET;
offset &= IP_OFFSET;
offset <<= 3; /* offset is in 8-byte chunks */
//获得ip头的长度
ihl = ip_hdrlen(skb);
/* Determine the position of this fragment. */
//获得报文总的长度
end = offset + skb->len - ihl;
err = -EINVAL;
//如果是最后一个分片
if ((flags & IP_MF) == 0)
{
/* If we already have some bits beyond end
* or have different end, the segment is corrrupted.
*/
//末端小于之前获得的总长度 则出现问题
//之前已经收到了一个最后分片,且这次判断的末端不等于之前获得的值 则出错
if (end < qp->q.len ||
((qp->q.last_in & INET_FRAG_LAST_IN) && end != qp->q.len))
goto err;
qp->q.last_in |= INET_FRAG_LAST_IN; //标志这是最后一个分片
qp->q.len = end;//更新总长度
}
else //不是最后一个分片
{
//检查是否8字节对齐
if (end&7)
{
//除最后一个分片外 其余分片均为8字节对齐
end &= ~7;
if (skb->ip_summed != CHECKSUM_UNNECESSARY)//没有设置跳过校验和计算
skb->ip_summed = CHECKSUM_NONE;//传输层自己计算校验和
}
//此分片尾部是否超过之前获得的报文总长度
if (end > qp->q.len)
{
/* Some bits beyond end -> corruption. */
if (qp->q.last_in & INET_FRAG_LAST_IN)//最后一个分片已经接受到 在来的分片是错误的
goto err;
qp->q.len = end;//更新报文的总长度
}
}
//表示空的ip分片
if (end == offset)
goto err;
err = -ENOMEM;
//将skb->data移到ip首部以后
if (pskb_pull(skb, ihl) == NULL)
goto err;
err = pskb_trim_rcsum(skb, end - offset);
if (err)
goto err;
/* 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?
*/
//遍历ipq中的fragments链表,每个skb的cb成员记录着当前skb进行ip重组的
//时候所需要的偏移,fragments中的skb都是按照offset升序排好的,所以,找到
//第一项offset大于当前IP包偏移的数据包就可以了
prev = NULL;
for (next = qp->q.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.
*/
//在上面的循环中找到了插入的位置 但可能发生重叠,
if (prev)
{
//若没发生重叠 很显然offset的大小 应该在FRAG_CB(prev)->offset + prev->len 之后也就是i值会小于0
/*若发生了重叠 offset的大小 在FRAG_CB(prev)->offset和FRAG_CB(prev)->offset + prev->len之间
则i的值会大于0
从上面的循环可知,此分片应该插入到prev指向的位置后面比如 prev的offset=100 len=150
若此分片的offset的值 在100+150的后面 i值就会小于0 没有重叠发生
若此分片的offset的值 在100,350之间 i的值会大于0 发生了重叠
插入位置 前重叠
\|/
------------------------------------------------------
分片1 | (与分片2发生重叠)分片2 | 分片3 |
------------------------------------------------------
*/
int i = (FRAG_CB(prev)->offset + prev->len) - offset;
if (i > 0)//发生了重叠
{
offset += i; //将偏移完后移动i 跑到prev报文区间的最右边
err = -EINVAL;
if (end <= offset) //此分片移动后的offset是否超过了此分片的最大长度
goto err;//偏移超过了分片的最大长度 出错
err = -ENOMEM;
if (!pskb_pull(skb, i))//将skb的数据指针减少i个字节 改变偏移值
goto err;
if (skb->ip_summed != CHECKSUM_UNNECESSARY)//没有设置跳过校验和计算
skb->ip_summed = CHECKSUM_NONE;//传输层自己计算校验和
}
}
err = -ENOMEM;
//此分片与队列中存在的分片发生后面的重叠 后重叠
/* 插入位置
\|/
------------------------------------------------------------------------
分片1 | 分片2 | (重叠与分片3甚至后面的分片发生重叠) 分片3 |
------------------------------------------------------------------------
*/
//此分片的最大报文长度 大于后面分片的起始位置 发生后重叠
while (next && FRAG_CB(next)->offset < end)
{
//计算重叠的字节数
int i = end - FRAG_CB(next)->offset; /* overlap is 'i' bytes */
if (i < next->len)//是否只重叠了此next分片
{
/* Eat head of the next overlapped fragment
* and leave the loop. The next ones cannot overlap.
*/
//将覆盖的i个字节在next分片中剔除
//将data指针减少i个字节
if (!pskb_pull(next, i))
goto err;
FRAG_CB(next)->offset += i;//更新next新的偏移 往后移动i个字节
qp->q.meat -= i;
if (next->ip_summed != CHECKSUM_UNNECESSARY)//没有设置跳过校验和计算
next->ip_summed = CHECKSUM_NONE;//传输层自己计算校验和
break;
}
else
{
//覆盖了后面的整个分片 则将覆盖的分片释放掉
struct sk_buff *free_it = next;
/* Old fragment is completely overridden with
* new one drop it.
*/
next = next->next;
if (prev)
prev->next = next;
else
qp->q.fragments = next;
qp->q.meat -= free_it->len;
//释放覆盖的整个分片
frag_kfree_skb(qp->q.net, free_it, NULL);
}
}
//设置新的偏移值
FRAG_CB(skb)->offset = offset;
/* Insert this fragment in the chain of fragments. */
//将skb加入到队列中
skb->next = next;
if (prev)
prev->next = skb;
else
qp->q.fragments = skb;
dev = skb->dev;
if (dev)
{
qp->iif = dev->ifindex;//设置设备号
skb->dev = NULL;
}
qp->q.stamp = skb->tstamp;//记录这个包接收的时间戳
qp->q.meat += skb->len; //增加已经获得的报文的长度
atomic_add(skb->truesize, &qp->q.net->mem);
//说明是第一个分片 设置第一个分片的标志
if (offset == 0)
qp->q.last_in |= INET_FRAG_FIRST_IN;
//如果收到了第一个分片和最后一个分片 并且报文长度也也等于原始的ip报文长度
//则对所有的分片进行组装
if (qp->q.last_in == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) &&
qp->q.meat == qp->q.len)
return ip_frag_reasm(qp, prev, dev);//根据所有的分段创建一个新的数据包
//ip分片还未完全收齐
write_lock(&ip4_frags.lock);
list_move_tail(&qp->q.lru_list, &qp->q.net->lru_list);
write_unlock(&ip4_frags.lock);
return -EINPROGRESS;
err:
kfree_skb(skb);
return err;
}