当一个ip报文超过MTU的时候,发送端会进行fragment,接收端反过来需要进行defrag
发送端:
在函数ip_finish_output中会判断是否进行
fragment,以udp发送10k的数据为例:
-
static int ip_finish_output(struct sk_buff *skb)
-
{
-
#if defined(CONFIG_NETFILTER) && defined(CONFIG_XFRM)
-
/* Policy lookup after SNAT yielded a new policy */
-
if (skb_dst(skb)->xfrm != NULL) {
-
IPCB(skb)->flags |= IPSKB_REROUTED;
-
return dst_output(skb);
-
}
-
#endif
-
if (skb->len > ip_skb_dst_mtu(skb) && !skb_is_gso(skb))//skb->len=10008,mtu=1500,gso=0
-
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 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 = skb_rtable(skb);
-
int err = 0;
-
-
dev = rt->dst.dev;
-
-
/*
-
* Point into the IP datagram header.
-
*/
-
-
iph = ip_hdr(skb);
-
-
if (unlikely((iph->frag_off & htons(IP_DF)) && !skb->local_df)) { //如果置了不可分片的标志位,则发送icmp信息
-
IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGFAILS);
-
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
-
htonl(ip_skb_dst_mtu(skb)));
-
kfree_skb(skb);
-
return -EMSGSIZE;
-
}
-
-
/*
-
* Setup starting values.
-
*/
-
-
hlen = iph->ihl * 4;
-
mtu = dst_mtu(&rt->dst) - hlen; /* Size of data space */ //1480
-
#ifdef CONFIG_BRIDGE_NETFILTER
-
if (skb->nf_bridge)
-
mtu -= nf_bridge_mtu_reduction(skb);
-
#endif
-
IPCB(skb)->flags |= IPSKB_FRAG_COMPLETE;
-
-
/* When frag_list is given, use it. First, check its validity:
-
* some transformers could create wrong frag_list or break existing
-
* one, it is not prohibited. In this case fall back to copying.
-
*
-
* LATER: this step can be merged to real generation of fragments,
-
* we can switch to copy when see the first bad fragment.
-
*/
-
if (skb_has_frag_list(skb)) { //如果有frag list,说明上层有可能已经做了一些事情,可以走fast流程
-
struct sk_buff *frag, *frag2;
-
int first_len = skb_pagelen(skb); //skb的线性区和shinfo中的frags的总大小,不包括fraglist,1500
-
-
if (first_len - hlen > mtu ||
-
((first_len - hlen) & 7) ||
-
ip_is_fragment(iph) ||
-
skb_cloned(skb))
-
goto slow_path;
-
-
skb_walk_frags(skb, frag) {
-
/* Correct geometry. */
-
if (frag->len > mtu ||
-
((frag->len & 7) && frag->next) ||
-
skb_headroom(frag) < hlen)
-
goto slow_path_clean;
-
-
/* Partially cloned skb? */
-
if (skb_shared(frag))
-
goto slow_path_clean;
-
-
BUG_ON(frag->sk);
-
if (skb->sk) {
-
frag->sk = skb->sk; //后续每个frag都将独立,因此赋值相应的分量
-
frag->destructor = sock_wfree;
-
}
-
skb->truesize -= frag->truesize; //如果fraglist中的skb满足条件的话,从总的skb大小中减掉
-
}
-
-
/* Everything is OK. */
-
-
err = 0;
-
offset = 0;
-
frag = skb_shinfo(skb)->frag_list;
-
skb_frag_list_init(skb);
-
skb->data_len = first_len - skb_headlen(skb); //当前skb不再包含fraglist的大小
-
skb->len = first_len;
-
iph->tot_len = htons(first_len);
-
iph->frag_off = htons(IP_MF); //设置第一个分片的标志,没有offset
-
ip_send_check(iph);
-
for (;;) {
-
/* Prepare header of the next frame,
-
* before previous one went down. */
-
if (frag) {
-
frag->ip_summed = CHECKSUM_NONE;
-
skb_reset_transport_header(frag);
-
__skb_push(frag, hlen);
-
skb_reset_network_header(frag);
-
memcpy(skb_network_header(frag), iph, hlen); //把上一个skb的ip头拷贝到这一个skb
-
iph = ip_hdr(frag);
-
iph->tot_len = htons(frag->len);
-
ip_copy_metadata(frag, skb);
-
if (offset == 0)
-
ip_options_fragment(frag);
-
offset += skb->len - hlen; //offset为L3层看到的负载,不包含L3的头
-
iph->frag_off = htons(offset>>3);
-
if (frag->next != NULL)
-
iph->frag_off |= htons(IP_MF); //除非是最后一个分片,否则都置为MF标志
-
/* Ready, complete checksum */
-
ip_send_check(iph);
-
}
-
-
err = output(skb); //对每一个skb进行发送 :ip_finish_output2
-
-
if (!err)
-
IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGCREATES);
-
if (err || !frag)
-
break;
-
-
skb = frag;
-
frag = skb->next;
-
skb->next = NULL;
-
}
-
-
if (err == 0) {
-
IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGOKS);
-
return 0; //如果一切顺利的话就到这
-
}
-
-
while (frag) {
-
skb = frag->next;
-
kfree_skb(frag);
-
frag = skb;
-
}
-
IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGFAILS);
-
return err;
-
-
slow_path_clean:
-
skb_walk_frags(skb, frag2) {
-
if (frag2 == frag)
-
break;
-
frag2->sk = NULL;
-
frag2->destructor = NULL;
-
skb->truesize += frag2->truesize;
-
}
-
}
-
-
slow_path:
-
left = skb->len - hlen; /* Space per frame */
-
ptr = hlen; /* Where to start from */
-
-
/* 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->dst.dev, nf_bridge_pad(skb));
-
-
/*
-
* Fragment the datagram.
-
*/
-
-
offset = (ntohs(iph->frag_off) & IP_OFFSET) << 3;
-
not_last_frag = iph->frag_off & htons(IP_MF);
-
-
/*
-
* Keep copying data until we run out.
-
*/
-
-
while (left > 0) {
-
len = left;
-
/* IF: it doesn't fit, use 'mtu' - the data space left */
-
if (len > mtu)
-
len = mtu;
-
/* IF: we are not sending up to and including the packet end
-
then align the next start on an eight byte boundary */
-
if (len < left) {
-
len &= ~7;
-
}
-
/*
-
* Allocate buffer.
-
*/
-
-
if ((skb2 = alloc_skb(len+hlen+ll_rs, GFP_ATOMIC)) == NULL) { //分配一个新的skb,数据都放在线性区
-
NETDEBUG(KERN_INFO "IP: frag: no memory for new fragment!\n");
-
err = -ENOMEM;
-
goto fail;
-
}
-
-
/*
-
* Set up data on packet
-
*/
-
-
ip_copy_metadata(skb2, skb);
-
skb_reserve(skb2, ll_rs);
-
skb_put(skb2, len + hlen);
-
skb_reset_network_header(skb2); //data和network_header指向L3的头
-
skb2->transport_header = skb2->network_header + hlen;
-
-
/*
-
* Charge the memory for the fragment to any owner
-
* it might possess
-
*/
-
-
if (skb->sk)
-
skb_set_owner_w(skb2, skb->sk);
-
-
/*
-
* Copy the packet header into the new buffer.
-
*/
-
-
skb_copy_from_linear_data(skb, skb_network_header(skb2), hlen); //拷贝L3头
-
-
/*
-
* Copy a block of the IP datagram.
-
*/
-
if (skb_copy_bits(skb, ptr, skb_transport_header(skb2), len)) //拷贝负载数据,可能从skb的线性区、frag或者list中获取
-
BUG(); //由ptr即offset决定
-
left -= len;
-
-
/*
-
/*
-
* Fill in the new header fields.
-
*/
-
iph = ip_hdr(skb2);
-
iph->frag_off = htons((offset >> 3));
-
-
/* ANK: dirty, but effective trick. Upgrade options only if
-
* the segment to be fragmented was THE FIRST (otherwise,
-
* options are already fixed) and make it ONCE
-
* on the initial skb, so that all the following fragments
-
* will inherit fixed options.
-
*/
-
if (offset == 0)
-
ip_options_fragment(skb);
-
-
/*
-
* Added AC : If we are fragmenting a fragment that's not the
-
* last fragment then keep MF on each bit
-
*/
-
if (left > 0 || not_last_frag)
-
iph->frag_off |= htons(IP_MF);
-
ptr += len;
-
offset += len;
-
-
/*
-
* Put this fragment into the sending queue.
-
*/
-
iph->tot_len = htons(len + hlen);
-
-
ip_send_check(iph);
-
-
err = output(skb2); //fast path中,入参skb是第一个发送的分片,slow path中入参skb是不会动的,它只负责提供数据
-
if (err)
-
goto fail;
-
-
IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGCREATES);
-
}
-
kfree_skb(skb);
-
IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGOKS);
-
return err;
-
-
fail:
-
kfree_skb(skb);
-
IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGFAILS);
-
return err;
-
}
简单总结:ip_fragment根据上层传入的情况做不同的处理,如果上层已经基本分片过的,则拷贝下L3头,对每一个分片调发包函数,否则创建skb结构,拷贝数据,然后对每一个分片调发包函数
接收端:
发送10K的数据的时候,接收端tcpdump输出如下:
-
22:39:47.521105 IP 100.100.100.11.51389 > 100.100.100.2.8888: UDP, length 10000
-
22:39:47.521189 IP 100.100.100.11 > 100.100.100.2: ip-proto-17
-
22:39:47.521329 IP 100.100.100.11 > 100.100.100.2: ip-proto-17
-
22:39:47.521450 IP 100.100.100.11 > 100.100.100.2: ip-proto-17
-
22:39:47.521567 IP 100.100.100.11 > 100.100.100.2: ip-proto-17
-
22:39:47.521697 IP 100.100.100.11 > 100.100.100.2: ip-proto-17
-
22:39:47.521791 IP 100.100.100.11 > 100.100.100.2: ip-proto-17
第一个报文和第二个报文的详细信息如下:
-
22:45:34.400082 IP 100.100.100.11.37617 > 100.100.100.2.8888: UDP, length 10000
-
0x0000: 60eb 6945 291b 3c97 0ecd a46d 0800 4500 `.iE).<....m..E.
-
0x0010: 05dc 8e6c 2000 4011 35cf 6464 640b 6464 ...l..@.5.ddd.dd
-
0x0020: 6402 92f1 22b8 2718 cfa2 6767 6767 6767 d...".'...gggggg //0x2718=10008=负载+一个UDP头
-
22:45:34.400161 IP 100.100.100.11 > 100.100.100.2: ip-proto-17
-
0x0000: 60eb 6945 291b 3c97 0ecd a46d 0800 4500 `.iE).<....m..E.
-
0x0010: 05dc 8e6c 20b9 4011 3516 6464 640b 6464 ...l..@.5.ddd.dd
-
0x0020: 6402 6767 6767 6767 6767 6767 6767 6767 d.gggggggggggggg //不包含UDP头
对于ip分片而言,IP的头结构中有两个字节相关,即加红的字段,16bit中高三位为标记为,低13位为偏移/8
因此20b9表示标记位为001(表示MF),偏移为0xb9×8=1480(MTU-IP头的长度),即第一个分片包含了1480个字节(包括8字节UDP头)。
看代码是如何分辨一个skb是否是一个分片:
-
static inline bool ip_is_fragment(const struct iphdr *iph)
-
{
-
return (iph->frag_off & htons(IP_MF | IP_OFFSET)) != 0;
-
}
在收包函数ip_local_deliver中会进行判断,如果是的话,会调用ip_defrag进行分片重组:
-
int ip_defrag(struct sk_buff *skb, u32 user)
-
{
-
struct ipq *qp;
-
struct net *net;
-
-
net = skb->dev ? dev_net(skb->dev) : dev_net(skb_dst(skb)->dev);
-
IP_INC_STATS_BH(net, IPSTATS_MIB_REASMREQDS);
-
-
/* Start by cleaning up the memory. */
-
if (atomic_read(&net->ipv4.frags.mem) > net->ipv4.frags.high_thresh)
-
ip_evictor(net);
-
-
/* Lookup (or create) queue header */
-
if ((qp = ip_find(net, ip_hdr(skb), user)) != NULL) { //找到和该skb匹配的项,没有的话说明是第一个,新建一个
-
int ret;
-
-
spin_lock(&qp->q.lock);
-
-
ret = ip_frag_queue(qp, skb);
-
-
spin_unlock(&qp->q.lock);
-
ipq_put(qp);
-
return ret;
-
}
-
-
IP_INC_STATS_BH(net, IPSTATS_MIB_REASMFAILS);
-
kfree_skb(skb);
-
return -ENOMEM;
-
}
涉及的数据结构如下:
找到或者新建完ipq的数据结构后,把当前的skb放到对应的数据结构:
-
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;
-
u8 ecn;
-
-
if (qp->q.last_in & INET_FRAG_COMPLETE) //当ipq需要释放的时候才会设置这个标志位
-
goto err;
-
-
if (!(IPCB(skb)->flags & IPSKB_FRAG_COMPLETE) &&
-
unlikely(ip_frag_too_far(qp)) &&
-
unlikely(err = ip_frag_reinit(qp))) {
-
ipq_kill(qp);
-
goto err;
-
}
-
-
ecn = ip4_frag_ecn(ip_hdr(skb)->tos);
-
offset = ntohs(ip_hdr(skb)->frag_off);
-
flags = offset & ~IP_OFFSET;
-
offset &= IP_OFFSET;
-
offset <<= 3; /* offset is in 8-byte chunks */
-
ihl = ip_hdrlen(skb);
-
-
/* Determine the position of this fragment. */
-
end = offset + skb->len - ihl;
-
err = -EINVAL;
-
-
/* 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->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 {
-
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;
-
}
-
}
-
if (end == offset)
-
goto err;
-
err = -ENOMEM;
-
if (pskb_pull(skb, ihl) == NULL) //去掉skb的头
-
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?
-
*/
-
prev = qp->q.fragments_tail;
-
if (!prev || FRAG_CB(prev)->offset < offset) { //第一个情况prev为空,则表示当前skb为第一个;后面的情况的话当前skb放到最后
-
next = NULL;
-
goto found;
-
}
-
prev = NULL;
-
for (next = qp->q.fragments; next != NULL; next = next->next) { //fragments以offset从小到大排列
-
if (FRAG_CB(next)->offset >= offset)
-
break; /* */
-
prev = next;
-
}
-
-
found:
-
/* 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) {
-
int i = (FRAG_CB(prev)->offset + prev->len) - offset; //处理当前skb和prev的重叠部分,以前面的为准,改变skb
-
-
if (i > 0) {
-
offset += i;
-
err = -EINVAL;
-
if (end <= offset)
-
goto err;
-
err = -ENOMEM;
-
if (!pskb_pull(skb, i))
-
goto err;
-
if (skb->ip_summed != CHECKSUM_UNNECESSARY)
-
skb->ip_summed = CHECKSUM_NONE;
-
}
-
}
-
-
err = -ENOMEM;
-
-
while (next && FRAG_CB(next)->offset < end) { //处理当前skb和next的重叠部分,以当前skb为准,改变next
-
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.
-
*/
-
if (!pskb_pull(next, i))
-
goto err;
-
FRAG_CB(next)->offset += 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);
-
}
-
}
-
-
FRAG_CB(skb)->offset = offset;
-
-
/* Insert this fragment in the chain of fragments. */
-
skb->next = next;
-
if (!next)
-
qp->q.fragments_tail = skb; //插入到链表
-
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;
-
qp->ecn |= ecn;
-
atomic_add(skb->truesize, &qp->q.net->mem);
-
if (offset == 0)
-
qp->q.last_in |= INET_FRAG_FIRST_IN; //如果offset为0,表示第一个分片,置标志位
-
-
if (qp->q.last_in == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) && //第一个分片和最后一个分片到了
-
qp->q.meat == qp->q.len) //如果最后一个分片到了,则len已经得到包的总大小了,meat是每个skb加入的时候增加的,两者相等说明包齐了
-
return ip_frag_reasm(qp, prev, dev); //重组
-
-
write_lock(&ip4_frags.lock);
-
list_move_tail(&qp->q.lru_list, &qp->q.net->lru_list); //更新当前ipq的位置,降低被释放的优先级
-
write_unlock(&ip4_frags.lock);
-
return -EINPROGRESS; //返回非零的话,不会再往下处理了
-
err:
-
kfree_skb(skb);
-
return err;
-
}
需要关注ip_frag_reasm函数的参数,由于ip_frag_queue函数的传入参数为skb,而且没有返回参数,而后续的操作必须有sk_buff的数据结构,因此不管最后调用ip_frag_queue时的skb是哪一个分片,返回的时候必须代表整个数据包。而这个过程是函数在
ip_frag_reasm完成的:
-
static int ip_frag_reasm(struct ipq *qp, struct sk_buff *prev,
-
struct net_device *dev)
-
{
-
struct net *net = container_of(qp->q.net, struct net, ipv4.frags);
-
struct iphdr *iph;
-
struct sk_buff *fp, *head = qp->q.fragments;
-
int len;
-
int ihlen;
-
int err;
-
u8 ecn;
-
-
ipq_kill(qp);
-
-
ecn = ip4_frag_ecn_table[qp->ecn];
-
if (unlikely(ecn == 0xff)) {
-
err = -EINVAL;
-
goto out_fail;
-
}
-
/* Make the one we just received the head. */
-
if (prev) {
-
head = prev->next; //后续流程保证head即最初传入的skb包含的是一个分片的内容
-
fp = skb_clone(head, GFP_ATOMIC);
-
if (!fp)
-
goto out_nomem;
-
-
fp->next = head->next;
-
if (!fp->next)
-
qp->q.fragments_tail = fp;
-
prev->next = fp;
-
-
skb_morph(head, qp->q.fragments);
-
head->next = qp->q.fragments->next;
-
-
kfree_skb(qp->q.fragments);
-
qp->q.fragments = head;
-
}
-
-
WARN_ON(head == NULL);
-
WARN_ON(FRAG_CB(head)->offset != 0);
-
-
/* Allocate a new buffer for the datagram. */
-
ihlen = ip_hdrlen(head);
-
len = ihlen + qp->q.len;
-
err = -E2BIG;
-
if (len > 65535)
-
goto out_oversize;
-
-
/* Head of list must not be cloned. */
-
if (skb_cloned(head) && pskb_expand_head(head, 0, 0, GFP_ATOMIC))
-
goto out_nomem;
-
-
/* If the first fragment is fragmented itself, we split
-
* it to two chunks: the first with data and paged part
-
* and the second, holding only fragments. */
-
if (skb_has_frag_list(head)) {
-
struct sk_buff *clone;
-
int i, plen = 0;
-
-
if ((clone = alloc_skb(0, GFP_ATOMIC)) == NULL)
-
goto out_nomem;
-
clone->next = head->next;
-
head->next = clone;
-
skb_shinfo(clone)->frag_list = skb_shinfo(head)->frag_list;
-
skb_frag_list_init(head);
-
for (i=0; i<skb_shinfo(head)->nr_frags; i++)
-
plen += skb_shinfo(head)->frags[i].size;
-
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, &qp->q.net->mem);
-
}
-
-
skb_shinfo(head)->frag_list = head->next;//相当与把fragments对应的链表搬到当前skb上了
-
skb_push(head, head->data - skb_network_header(head)); //第一个分片对应的skb需要包含IP头
-
-
for (fp=head->next; fp; fp = fp->next) {
-
head->data_len += fp->len; //更新当前skb的分量,因为它不再只代表自己一个分片,而是整个包
-
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(head->truesize, &qp->q.net->mem);
-
-
head->next = NULL;
-
head->dev = dev;
-
head->tstamp = qp->q.stamp;
-
-
iph = ip_hdr(head);
-
iph->frag_off = 0; //重组完,改变相应的标志
-
iph->tot_len = htons(len);
-
iph->tos |= ecn;
-
IP_INC_STATS_BH(net, IPSTATS_MIB_REASMOKS);
-
qp->q.fragments = NULL;
-
qp->q.fragments_tail = NULL;
-
return 0;
-
-
out_nomem:
-
LIMIT_NETDEBUG(KERN_ERR "IP: queue_glue: no memory for gluing "
-
"queue %p\n", qp);
-
err = -ENOMEM;
-
goto out_fail;
-
out_oversize:
-
if (net_ratelimit())
-
printk(KERN_INFO "Oversized IP packet from %pI4.\n",
-
&qp->saddr);
-
out_fail:
-
IP_INC_STATS_BH(net, IPSTATS_MIB_REASMFAILS);
-
return err;
-
}
简单的来说,重组过程就是先把所有的分片skb加到qp->q.fragments对应的链表中,当分片都到的时候,再把该链表中的所有skb都移到一个统一的skb上的frag_list上,更新这个统一skb的分量,然后交由函数ip_local_deliver_finish继续处理。
阅读(4303) | 评论(0) | 转发(0) |