本文以 linux 2.6.27.62 中 UDP 发包过程中重要的一个 IP 层的函数来分析 IP 层是如何分片的。
科普一下,什么是 IP 包分片,在某一个链路上,比如在以太网链路上,每次所能发送最大的包是有限制的,叫做 MTU,也就是 IP 层要想发包,每次包大小必须不大于 MTU,见上一篇文章,但传输层很有可能发送大于这个值的数据,此时 IP 层会对这些数据(可以称为 IP 包)进行分片,然后在收到时,在 IP 层再进行重组,形成一个 IP 包,交给传输层。
代码如下:
int ip_append_data(struct sock *sk, int getfrag(void *from, char *to, int offset, int len, int odd, struct sk_buff *skb), void *from, int length, int transhdrlen, struct ipcm_cookie *ipc, struct rtable *rt, unsigned int flags) { struct inet_sock *inet = inet_sk(sk); struct sk_buff *skb; struct ip_options *opt = NULL; int hh_len; int exthdrlen; int mtu; int copy; int err; int offset = 0; unsigned int maxfraglen, fragheaderlen; int csummode = CHECKSUM_NONE; // 如果只是为了探测,则不发包,直接返回 if (flags&MSG_PROBE) return 0; // 检查发送队列是否为空,如果为空,则表示这是 IP 包的第一个分片 if (skb_queue_empty(&sk->sk_write_queue)) { /* * setup for corking. */ opt = ipc->opt; if (opt) { if (inet->cork.opt == NULL) { inet->cork.opt = kmalloc(sizeof(struct ip_options) + 40, sk->sk_allocation); if (unlikely(inet->cork.opt == NULL)) return -ENOBUFS; } memcpy(inet->cork.opt, opt, sizeof(struct ip_options)+opt->optlen); inet->cork.flags |= IPCORK_OPT; inet->cork.addr = ipc->addr; } dst_hold(&rt->u.dst); inet->cork.fragsize = mtu = inet->pmtudisc == IP_PMTUDISC_PROBE ? rt->u.dst.dev->mtu : dst_mtu(rt->u.dst.path); inet->cork.dst = &rt->u.dst; inet->cork.length = 0; sk->sk_sndmsg_page = NULL; sk->sk_sndmsg_off = 0; if ((exthdrlen = rt->u.dst.header_len) != 0) { length += exthdrlen; transhdrlen += exthdrlen; } } else { rt = (struct rtable *)inet->cork.dst; if (inet->cork.flags & IPCORK_OPT) opt = inet->cork.opt; transhdrlen = 0; // 如果该 IP 包里还有分片,那么就会忽略掉此次的传输层头信息,直接添加到上一个 IP 包 exthdrlen = 0; mtu = inet->cork.fragsize; } // 计算 L2 层头部长度,即链路层,以太网为 1500 hh_len = LL_RESERVED_SPACE(rt->u.dst.dev); // 计算该层,即 IP 层头部长度 fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0); // 计算该分片,如果不是最后一片,那么它的载荷最大为多少,8 字节对齐的原因,见上一篇《IP层分析》一文 // maxfraglen 表示如果不是最后一个分片的分片的最大长度 maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen; // 该 IP 包的载荷是否超过了最大限制,总大小为什么是 0xFFFF,见上一文 if (inet->cork.length + length > 0xFFFF - fragheaderlen) { ip_local_error(sk, EMSGSIZE, rt->rt_dst, inet->dport, mtu-exthdrlen); return -EMSGSIZE; } /* * transhdrlen > 0 means that this is the first fragment and we wish * it won't be fragmented in the future. */ if (transhdrlen && length + fragheaderlen <= mtu && rt->u.dst.dev->features & NETIF_F_V4_CSUM && !exthdrlen) csummode = CHECKSUM_PARTIAL; // 让硬件,即网卡计算校验和 // 更新该 IP 包已累积的数据的长度,cork 相当于软木塞,使小的数据包可以积累成为一个大的 IP 包 inet->cork.length += length; if (((length> mtu) || !skb_queue_empty(&sk->sk_write_queue)) && (sk->sk_protocol == IPPROTO_UDP) && (rt->u.dst.dev->features & NETIF_F_UFO)) { err = ip_ufo_append_data(sk, getfrag, from, length, hh_len, fragheaderlen, transhdrlen, mtu, flags); if (err) goto error; return 0; } /* So, what's going on in the loop below? * * We use calculated fragment length to generate chained skb, * each of segments is IP fragment ready for sending to network after * adding appropriate IP header. */ // 取出该 IP 包的最后一个 sk_buff,即最后一个分片 if ((skb = skb_peek_tail(&sk->sk_write_queue)) == NULL) goto alloc_new_skb; // 下面开始 IP 分片的主逻辑 while (length > 0) { /* Check if the remaining data fits into current packet. */ // 检查最后一个分片的剩余空间是否可以满足当前的包,最后一个分片的大小因为不需要满足 8 字节对齐 // 所以它的大小有可能,大于 maxfraglen,但肯定小于 mtu. 所以如果 copy 如果可以满足 length,那么 // 就不用申请新的分片,直接填充到最后一个分片中。但如果 copy 不能满足 length (copy < length), // 那么就需要新和分片,此时上次的最后一个分片的大小就需要做 8 字节对齐处理。所以 copy 记录了能够从 // length 中拷贝的数据的大小 copy = mtu - skb->len; if (copy < length) copy = maxfraglen - skb->len; // 如果最后一个分片不能满足此次请求,并且 skb->len >= maxfraglen时,此时 copy <= 0, 也就是最后一个 // 分片有可能需要作处理,移动最后没有 8 字节对齐的部分 if (copy <= 0) { char *data; unsigned int datalen; unsigned int fraglen; unsigned int fraggap; unsigned int alloclen; struct sk_buff *skb_prev; alloc_new_skb: // 取出上一个分片,因为上一个分片在处理时有可能被当作最后一个分片处理,长度可能不是 8 的倍数,此处要处理这种情况 skb_prev = skb; if (skb_prev) fraggap = skb_prev->len - maxfraglen; // 计算最后一个分片是否需要做字节对齐处理 else fraggap = 0; /* * If remaining data exceeds the mtu, * we know we need more fragment(s). */ // 计算需要拷贝到新的分片中的数据长度 datalen = length + fraggap; // 如果不能当作最后一个分片全部处理掉,那么说明还需要更多的分片,此时将要新申请的分片就需要做对齐处理了 if (datalen > mtu - fragheaderlen) datalen = maxfraglen - fragheaderlen; // 将要填充的分片的长度 fraglen = datalen + fragheaderlen; if ((flags & MSG_MORE) && !(rt->u.dst.dev->features&NETIF_F_SG)) alloclen = mtu; else alloclen = datalen + fragheaderlen; /* The last fragment gets additional space at tail. * Note, with MSG_MORE we overallocate on fragments, * because we have no idea what fragment will be * the last. */ // 如果可以在新的分片中全部处理掉,即不需要更多的分片,将作为最后一个分片处理 if (datalen == length + fraggap) alloclen += rt->u.dst.trailer_len; // 如果是第一个分片 if (transhdrlen) { skb = sock_alloc_send_skb(sk, alloclen + hh_len + 15, (flags & MSG_DONTWAIT), &err); } else { skb = NULL; if (atomic_read(&sk->sk_wmem_alloc) <= 2 * sk->sk_sndbuf) skb = sock_wmalloc(sk, alloclen + hh_len + 15, 1, sk->sk_allocation); if (unlikely(skb == NULL)) err = -ENOBUFS; } if (skb == NULL) goto error; /* * Fill in the control structures */ skb->ip_summed = csummode; skb->csum = 0; // 保留 L2 层,即链路层长度,该保留动作不会影响 skb->len, skb->len 只记录了 IP 层数据的长度,包括 IP 头信息 skb_reserve(skb, hh_len); /* * Find where to start putting bytes. */ data = skb_put(skb, fraglen); skb_set_network_header(skb, exthdrlen); skb->transport_header = (skb->network_header + fragheaderlen); data += fragheaderlen; // 处理上次最后一个分片中需要字节对齐的部分 if (fraggap) { skb->csum = skb_copy_and_csum_bits( skb_prev, maxfraglen, data + transhdrlen, fraggap, 0); skb_prev->csum = csum_sub(skb_prev->csum, skb->csum); data += fraggap; pskb_trim_unique(skb_prev, maxfraglen); } // 计算能够从用户数据中拷贝的字节数,如果是第一个分片,传进来的载荷其实是包含传输层头大小的 copy = datalen - transhdrlen - fraggap; // 拷贝到新的分片中 if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) { err = -EFAULT; kfree_skb(skb); goto error; } // 计算偏移 offset += copy; // 计算此次处理掉的用户数据的字节数,datalen 是可能包含传输头信息的,传输头也相当于被处理掉了 length -= datalen - fraggap; transhdrlen = 0; exthdrlen = 0; csummode = CHECKSUM_NONE; /* * Put the packet on the pending queue. */ __skb_queue_tail(&sk->sk_write_queue, skb); continue; } // 如果最后一个分片能够满足请求 if (copy > length) copy = length; // 如果不支持离散聚合 I/O if (!(rt->u.dst.dev->features&NETIF_F_SG)) { unsigned int off; // 拷贝传输层的数据到分片中 off = skb->len; if (getfrag(from, skb_put(skb, copy), offset, copy, off, skb) < 0) { __skb_trim(skb, off); err = -EFAULT; goto error; } } else { int i = skb_shinfo(skb)->nr_frags; skb_frag_t *frag = &skb_shinfo(skb)->frags[i-1]; struct page *page = sk->sk_sndmsg_page; int off = sk->sk_sndmsg_off; unsigned int left; if (page && (left = PAGE_SIZE - off) > 0) { if (copy >= left) copy = left; if (page != frag->page) { if (i == MAX_SKB_FRAGS) { err = -EMSGSIZE; goto error; } get_page(page); skb_fill_page_desc(skb, i, page, sk->sk_sndmsg_off, 0); frag = &skb_shinfo(skb)->frags[i]; } } else if (i < MAX_SKB_FRAGS) { if (copy > PAGE_SIZE) copy = PAGE_SIZE; page = alloc_pages(sk->sk_allocation, 0); if (page == NULL) { err = -ENOMEM; goto error; } sk->sk_sndmsg_page = page; sk->sk_sndmsg_off = 0; skb_fill_page_desc(skb, i, page, 0, 0); frag = &skb_shinfo(skb)->frags[i]; } else { err = -EMSGSIZE; goto error; } if (getfrag(from, page_address(frag->page)+frag->page_offset+frag->size, offset, copy, skb->len, skb) < 0) { err = -EFAULT; goto error; } sk->sk_sndmsg_off += copy; frag->size += copy; skb->len += copy; skb->data_len += copy; skb->truesize += copy; atomic_add(copy, &sk->sk_wmem_alloc); } offset += copy; length -= copy; } return 0; error: inet->cork.length -= length; IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTDISCARDS); return err; }
原文地址:http://blog.csdn.net/henzox/article/details/43233157