1、基本原理
UDP发包流程中,当没有cork的情况下,会走过udp_sendmsg到达ip_append_data,该接口是IP层提供的UDP和RAW Socket的发包接口,同时,TCP中用于发送ACK和RST报文的接口ip_send_reply最终也会调用此接口
该接口的主要作用是:将数据拷贝到适合的skb(利用发送队列中现有的或新创建)中,可能有两种情况:
1)放入skb的线性区(skb->data)中;
2)或者放入skb_shared_info的分片(frag)中(当开启SG特性或使用UFO时,都会用到)
另外,还需要考虑MTU对skb数据进行分割,为IP层的分片做准备。
2、基本流程
1)如果需要发送的数据长度>MTU,且启用GSO,而且网卡启用UFO,则调用ip_ufo_append_data接口进行处理,默认处理是创建新的page,拷贝数据,并将其链入到skb中的分片(skb_shinfo(skb)->frags)中。
2)如果sock发送队列为空,或者现有的skb中的空余空间(相对于MTU来说)不足以存放本次需要发送的数据,则新创建skb,并将这次需要发送的新数据拷贝拷新创建的skb中。同时,还需要判断原有的skb中的数据是否超过了MTU,如果超过,那么在拷贝新数据之前,还需要将原有skb进行分段,即将超出MTU的部分数据拷贝到新skb中。
3)否则,直接利用现有发送队列中的skb,然后判断网卡硬件是否支持SG特性,如果不支持,则将需要发送的新数据拷贝到现有skb的线性数据区(skb->data)中;如果网卡支持SG特性,则将需要发送的新数据拷贝到SG相关的分片(分散聚集IO页面数组)中。
代码调用流程大致如下:
udp_sendmsg-->
ip_append_data-->
__ip_append_data-->
ip_ufo_append_data //UFO相关处理
sock_alloc_send_skb/sock_wmalloc //分配新skb
getfrag //拷贝发送数据到skb中
3、代码分析
ip_append_data():
-
/*
-
* ip_append_data() and ip_append_page() can make one large IP datagram
-
* from many pieces of data. Each pieces will be holded on the socket
-
* until ip_push_pending_frames() is called. Each piece can be a page
-
* or non-page data.
-
*
-
* Not only UDP, other transport protocols - e.g. raw sockets - can use
-
* this interface potentially.
-
*
-
* LATER: length must be adjusted by pad at tail, when it is required.
-
*/
-
/*
-
* 主要用作UDP和Raw socket的输出接口,但TCP中用于发送ACK和RST的函数ip_send_reply()最终也调用了此接口。
-
* 主要作用是将数据拷贝到适合的skb(利用发送队列中现有的或新创建)中,可能有两种情况: 放入skb的线性
-
* 区(skb->data)中,或者放入skb_shared_info的分片(frag)中,同时还需要考虑MTU对skb数据进行分割,为IP层的分片做准备。
-
*/
-
int ip_append_data(struct sock *sk, struct flowi4 *fl4,
-
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 **rtp,
-
unsigned int flags)
-
{
-
struct inet_sock *inet = inet_sk(sk);
-
int err;
-
/*MSG_PROBE标记表示探测,并非要发送实际数据*/
-
if (flags&MSG_PROBE)
-
return 0;
-
/*如果传输控制块(sock)的的输出队列为空,则需要设置一些临时信息,如果不为空,那就可以使用上次发送时的相关信息*/
-
if (skb_queue_empty(&sk->sk_write_queue)) {
-
/*填充cork相关数据结构*/
-
err = ip_setup_cork(sk, &inet->cork.base, ipc, rtp);
-
if (err)
-
return err;
-
} else {
-
transhdrlen = 0;
-
}
-
/*实际append操作*/
-
return __ip_append_data(sk, fl4, &sk->sk_write_queue, &inet->cork.base,
-
sk_page_frag(sk), getfrag,
-
from, length, transhdrlen, flags);
-
}
ip_append_data()-->__ip_append_data():
-
static int __ip_append_data(struct sock *sk,
-
struct flowi4 *fl4,
-
struct sk_buff_head *queue,
-
struct inet_cork *cork,
-
struct page_frag *pfrag,
-
int getfrag(void *from, char *to, int offset,
-
int len, int odd, struct sk_buff *skb),
-
void *from, int length, int transhdrlen,
-
unsigned int flags)
-
{
-
struct inet_sock *inet = inet_sk(sk);
-
struct sk_buff *skb;
-
/*从cork结构中读取ip_option信息*/
-
struct ip_options *opt = cork->opt;
-
int hh_len;
-
/*用于记录IPsec中扩展首部的长度,未启用IPsec是为0*/
-
int exthdrlen;
-
int mtu;
-
int copy; /*当前需要拷贝的数据大小。取决于skb中线性区剩余的空间大小和非线性区的大小*/
-
int err;
-
int offset = 0;
-
unsigned int maxfraglen, fragheaderlen;
-
int csummode = CHECKSUM_NONE;
-
struct rtable *rt = (struct rtable *)cork->dst;
-
/*从sock发送队列末尾取skb*/
-
skb = skb_peek_tail(queue);
-
-
exthdrlen = !skb ? rt->dst.header_len : 0;
-
mtu = cork->fragsize;
-
/*获取链路层首部的长度*/
-
hh_len = LL_RESERVED_SPACE(rt->dst.dev);
-
/*获取IP首部(包括IP选项)的长度*/
-
fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0);
-
/*IP数据包中数据的最大长度,通过mtu计算,并进行8字节对齐,目的是提升计算效率*/
-
maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen;
-
/*输出的报文长度不能超过IP数据报能容纳的最大长度(64K)*/
-
if (cork->length + length > 0xFFFF - fragheaderlen) {
-
ip_local_error(sk, EMSGSIZE, fl4->daddr, inet->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.
-
*/
-
/*如果没有分片,且硬件支持计算校验和,则设置CHECKSUM_PARTIAL标记,由硬件来计算校验和*/
-
if (transhdrlen &&
-
length + fragheaderlen <= mtu &&
-
rt->dst.dev->features & NETIF_F_V4_CSUM &&
-
!exthdrlen)
-
csummode = CHECKSUM_PARTIAL;
-
/*增加cork阻塞的长度*/
-
cork->length += length;
-
if (((length > mtu) || (skb && skb_is_gso(skb))) &&
-
(sk->sk_protocol == IPPROTO_UDP) &&
-
(rt->dst.dev->features & NETIF_F_UFO) && !rt->dst.header_len) {
-
/*
-
* UFO处理,需要满足上述几个条件,主要为:数据长度>mtu + 启用GSO + 网卡启用UFO.
-
* 默认处理是创建新的page,拷贝数据,并将其链入到skb中的分片中(skb_shared_info,SG相关)
-
*/
-
err = ip_ufo_append_data(sk, queue, getfrag, from, length,
-
hh_len, fragheaderlen, transhdrlen,
-
maxfraglen, 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.
-
*/
-
/*如果输出队列中没有skb,那说明队列为空,需要新创建skb来发送数据*/
-
if (!skb)
-
goto alloc_new_skb;
-
/*
-
* 存在skb,且没有进入UFO流程,那么这里就要开始进行分段了,length为需要发送的数据长度
-
* UDP层的分段实际是在这里完成的。当length 大于该skb中剩余的空间大小(copy)时,则需要分配新
-
* 的skb中来存放length中剩余的数据,新分配的skb会也被链入sock的发送队列链表中,最后会通过
-
* ip_make_skb,将sock发送链表中的skb中都链入到skb->frag_list中
-
*/
-
while (length > 0) {
-
/* Check if the remaining data fits into current packet. */
-
/*计算当前skb中还能放多少数据,通过mtu-skb的数据长度计算,因为没有开启UFO,每个数据包长度不能大于mtu*/
-
copy = mtu - skb->len;
-
/*
-
* 如果skb的剩余空间不足以存放完这次需要放入的数据长度length,则将当前skb填满即可,剩余数据留下一个skb发送
-
* 其中maxfraglen是8字节对齐后的mtu。
-
*/
-
if (copy < length)
-
copy = maxfraglen - skb->len;
-
/*
-
* 这里分两种情况:1.copy < 0,这表示:当前skb中的数据本身就已经大于MTU了(可能由于老版本中ip_ufo_append_data流程中UFO标记没有设置导致),
-
* 那就需要对原来的skb重新进行分段了,需要新分配skb来容纳原来skb中的数据
-
* 2.copy==0,这表示当前skb不足以容纳length长度的数据,并且原来的skb中的数据已经填满了(此时数据包刚好为mtu,所以copy刚好为0),此时
-
* 需要分配新的skb来装剩下的数据,直至copy>0(表示当前skb中在装完length长度的数据后,还剩余空间,表明这已经是最后一个分段了)循环
-
* 结束,以这种方式达到分段的目的。
-
*/
-
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:/*3种原因需要新分配skb: 1.原有的skb数据区空间不足2.sock的输出队列为空3.原来的skb的数据已经大于mtu了,需要重新分段*/
-
skb_prev = skb;
-
/*由于原有的skb数据区空间不足,而需要分配新skb,计算不足的大小*/
-
if (skb_prev)
-
fraggap = skb_prev->len - maxfraglen;
-
/*由于sock的输出队列为空*/
-
else
-
fraggap = 0;
-
-
/*
-
* If remaining data exceeds the mtu,
-
* we know we need more fragment(s).
-
*/
-
/*这次需要新分配的数据区大小length加上原来skb中不足的大小,为新skb需要分配的数据区大小*/
-
datalen = length + fraggap;
-
/*如果新skb需要分配的数据区大小超过了mtu,那这次还是只能分配mtu的大小,剩余数据需要通过循环分配新skb来容纳*/
-
if (datalen > mtu - fragheaderlen)
-
datalen = maxfraglen - fragheaderlen;
-
/*数据报分片大小需要加上IP首部的长度*/
-
fraglen = datalen + fragheaderlen;
-
/*如果设置了MSG_MORE标记,表明需要等待新数据,一直到超过mtu为止,则设置"分配空间大小"为mtu*/
-
if ((flags & MSG_MORE) &&
-
!(rt->dst.dev->features&NETIF_F_SG))
-
alloclen = mtu;
-
/*否则需要分配的空间大小为数据报分片大小*/
-
else
-
alloclen = fraglen;
-
/*再加上IPsec相关的头部长度*/
-
alloclen += exthdrlen;
-
-
/* 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->dst.trailer_len;
-
/*
-
* 根据是否存在传输层首部,确定分配skb的方法:
-
* 如果存在,则说明该分片为分片组中的第一个分片,那就需要考虑更多的情况,比如:发送是否超时、是否发生未处理的致命错误、
-
* 发送通道是否已经关闭等;当不存在传输层首部时,说明不是第一个分片,则不需考虑这些情况。
-
*/
-
if (transhdrlen) {
-
/*分配skb,并进行相关处理*/
-
skb = sock_alloc_send_skb(sk,
-
alloclen + hh_len + 15,
-
(flags & MSG_DONTWAIT), &err);
-
} else {/*不是第一个分片(分段),直接分片新的skb*/
-
skb = NULL;
-
if (atomic_read(&sk->sk_wmem_alloc) <=
-
2 * sk->sk_sndbuf)
-
/*分配skb*/
-
skb = sock_wmalloc(sk,
-
alloclen + hh_len + 15, 1,
-
sk->sk_allocation);
-
if (unlikely(skb == NULL))
-
err = -ENOBUFS;
-
else
-
/* only the initial fragment is
-
time stamped */
-
cork->tx_flags = 0;
-
}
-
if (skb == NULL)
-
goto error;
-
-
/*
-
* Fill in the control structures
-
*/
-
/*初始化skb中的相关成员*/
-
skb->ip_summed = csummode;
-
skb->csum = 0;
-
/*留出链路层头部大小*/
-
skb_reserve(skb, hh_len);
-
/*设置tx_flags*/
-
skb_shinfo(skb)->tx_flags = cork->tx_flags;
-
-
/*
-
* Find where to start putting bytes.
-
*/
-
/*在skb中预留存放三层首部和数据的空间*/
-
data = skb_put(skb, fraglen + exthdrlen);
-
/*设置IP头指针位置*/
-
skb_set_network_header(skb, exthdrlen);
-
/*计算传输层头部长度*/
-
skb->transport_header = (skb->network_header +
-
fragheaderlen);
-
/*计算数据存入的位置*/
-
data += fragheaderlen + exthdrlen;
-
/*
-
* 如果上一个skb的数据大于mtu(8字节对齐),那么需要对其进行分段处理,即将一个skb拆开,
-
* 将上一个skb中超出的数据和传输层首部并 复制到当前的skb中,并重新计算校验和。
-
*/
-
if (fraggap) {
-
skb->csum = skb_copy_and_csum_bits(
-
skb_prev, maxfraglen,
-
data + transhdrlen, fraggap, 0);
-
/*上一个skb的校验和也需要重新计算*/
-
skb_prev->csum = csum_sub(skb_prev->csum,
-
skb->csum);
-
/*拷贝新数据后,再次移动data指针,更新数据写入的位置*/
-
data += fraggap;
-
/*已8字节对齐的MTU大小截取上一个skb,多余的数据已经拷贝到新的skb中了,需要截掉*/
-
pskb_trim_unique(skb_prev, maxfraglen);
-
}
-
/*拷贝新数据到数据区*/
-
copy = datalen - transhdrlen - fraggap;
-
/*getfrag为传入的函数指针,udp默认为ip_generic_getfrag,用于从用户态拷贝数据到内核中(skb的数据区)*/
-
if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) {
-
err = -EFAULT;
-
kfree_skb(skb);
-
goto error;
-
}
-
/*更新下次拷贝相关的数据*/
-
offset += copy;
-
length -= datalen - fraggap;
-
/*由于传输层首部已经拷贝过了,所以相关变量置0*/
-
transhdrlen = 0;
-
exthdrlen = 0;
-
csummode = CHECKSUM_NONE;
-
-
/*
-
* Put the packet on the pending queue.
-
*/
-
/*复制完数据的skb添加到sock输出队列的末尾*/
-
__skb_queue_tail(queue, skb);
-
continue;
-
}
-
/*如果当前skb中的剩余数据区大于要拷贝的数据长度,那说明这次要发送的数据可以直接放入当前的skb中,直接拷贝即可。*/
-
if (copy > length)
-
copy = length;
-
/*如果硬件不支持SG(分散聚集特性,使用skb中的非线性区(shared_info))*/
-
if (!(rt->dst.dev->features&NETIF_F_SG)) {
-
unsigned int off;
-
-
off = skb->len;
-
/*将数据拷贝到skb中的线性区*/
-
if (getfrag(from, skb_put(skb, copy),
-
offset, copy, off, skb) < 0) {
-
__skb_trim(skb, off);
-
err = -EFAULT;
-
goto error;
-
}
-
} else {/*如果硬件支持SG,则将数据拷贝到skb的非线性区(shared_info)的frags[]数组指向的page中,注意:UFO时也会使用这个*/
-
int i = skb_shinfo(skb)->nr_frags;
-
-
err = -ENOMEM;
-
/*在分片列表(frags)中使用原有分片(返回相应分片的指针)或分配新页来存放数据*/
-
if (!sk_page_frag_refill(sk, pfrag))
-
goto error;
-
/*
-
* 如果传输控制块(sock)中的缓存页pfrag,不是当前skb->shared_info中的最后一个分片(分散聚集IO页面)所在的页面,则直接使用该页面,
-
* 将其添加 到分片列表(分散聚集IO页面数组)中,否则说明传输控制块(sock)中的缓存页pfrag就是分散聚集IO页面的最后一个页面,
-
* 则直接向其中拷贝数据即可。
-
*/
-
if (!skb_can_coalesce(skb, i, pfrag->page,
-
pfrag->offset)) {
-
err = -EMSGSIZE;
-
if (i == MAX_SKB_FRAGS)
-
goto error;
-
-
__skb_fill_page_desc(skb, i, pfrag->page,
-
pfrag->offset, 0);
-
skb_shinfo(skb)->nr_frags = ++i;
-
get_page(pfrag->page);
-
}
-
copy = min_t(int, copy, pfrag->size - pfrag->offset);
-
/*拷贝数据至skb中非线性区分片(分散聚集IO页面)中*/
-
if (getfrag(from,
-
page_address(pfrag->page) + pfrag->offset,
-
offset, copy, skb->len, skb) < 0)
-
goto error_efault;
-
/*移动相应数据指针*/
-
pfrag->offset += copy;
-
/*增加分片大小*/
-
skb_frag_size_add(&skb_shinfo(skb)->frags[i - 1], copy);
-
/*增加skb数据相关大小*/
-
skb->len += copy;
-
skb->data_len += copy;
-
skb->truesize += copy;
-
/*增加sock发送缓存区已分配数据大小*/
-
atomic_add(copy, &sk->sk_wmem_alloc);
-
}
-
offset += copy;
-
/*length减去已经拷贝的大小,如果拷完了,则结束循环,否则继续拷贝*/
-
length -= copy;
-
}
-
-
return 0;
-
-
error_efault:
-
err = -EFAULT;
-
error:
-
cork->length -= length;
-
IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTDISCARDS);
-
return err;
-
}
阅读(4962) | 评论(0) | 转发(1) |