从udp_sendmsg到ip_output发包过程:
udp_sendmsg->udp_send_skb->ip_send_skb->ip_local_out->ip_local_out_sk->__ip_local_out->__ip_local_out_sk->dst_output_sk->ip_output;在dst_output_sk函数中调用了skb_dst(skb)->output(sk, skb);
一、udp_sendmsg
UDP socket在传输层调用的发送函数为udp_sendmsg,这个函数内容好多。
首先获取发送的目的地址和目的端口,然后处理控制信息,接着选路,最后生成skb,将数据发送出去。发送报文时的skb就是在这个函数中生成的。
这个函数中有两个变量需要重点关注:corkreq、up->pending,这两个变量决定了udp_sendmsg函数的流程走向。
corkreq:表示是否使用缓冲机制,是否阻塞,意思就是把多个短的数据合成一个长的数据发送。
up->pending:表示当前sock还有数据没有发送到ip层,一般在设置了CORK标记的场景下才会设置这个标记。
转载自:
http://arthurchiao.art/blog/tuning-stack-tx-zh/#chap_5.1
http://blog.chinaunix.net/uid-14528823-id-4468600.html
int udp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
{
struct inet_sock *inet = inet_sk(sk);
struct udp_sock *up = udp_sk(sk);
struct flowi4 fl4_stack;
struct flowi4 *fl4;
int ulen = len;
struct ipcm_cookie ipc;
struct rtable *rt = NULL;
int free = 0;
int connected = 0;
__be32 daddr, faddr, saddr;
__be16 dport;
u8 tos;
/*获取pcflag标志确定该套接字是普通的UDP套接字还是轻量级套接字*/
int err, is_udplite = IS_UDPLITE(sk);
int corkreq = up->corkflag || msg->msg_flags&MSG_MORE;
int (*getfrag)(void *, char *, int, int, int, struct sk_buff *);
struct sk_buff *skb;
struct ip_options_data opt_copy;
/*UDP数据报最长为64KB*/
if (len > 0xFFFF)
return -EMSGSIZE;
/*
* Check the flags.
*/
/*UDP不支持发送带外数据,如果发送标志中设置了MSG_OOB,则返回*/
if (msg->msg_flags & MSG_OOB) /* Mirror BSD error message compatibility */
return -EOPNOTSUPP;
ipc.opt = NULL;
ipc.tx_flags = 0;
ipc.ttl = 0;
ipc.tos = -1;
getfrag = is_udplite ? udplite_getfrag : ip_generic_getfrag;
fl4 = &inet->cork.fl.u.ip4;
/*当前的sock有等待发送的数据,直接将数据追加*/
if (up->pending) {
/*
* There are pending frames.
* The socket lock must be held while it's corked.
*/
lock_sock(sk);
if (likely(up->pending)) {
if (unlikely(up->pending != AF_INET)) {
release_sock(sk);
return -EINVAL;
}
goto do_append_data;
}
release_sock(sk);
}
/*UDP数据报长度,包括UDP data + UDP header*/
ulen += sizeof(struct udphdr);
/*
* Get and verify the address.
*/
/*获取目的IP地址和端口:目的地址和端口有两个可能的来源:
1. 如果之前socket已经建立,那socket本身就存储了目标地址;
2. 地址通过msghdr传入,通常为调用sendto发送UDP数据*/
if (msg->msg_name) {
/*地址信息保存到usin中*/
DECLARE_SOCKADDR(struct sockaddr_in *, usin, msg->msg_name);
if (msg->msg_namelen < sizeof(*usin))
return -EINVAL;
if (usin->sin_family != AF_INET) {
if (usin->sin_family != AF_UNSPEC)
return -EAFNOSUPPORT;
}
daddr = usin->sin_addr.s_addr;
dport = usin->sin_port;
if (dport == 0)
return -EINVAL;
} else {
/*msg没有目的地址的情况:通常为先调用了connect,然后调用send发送UDP数据,
UDP套接字调用connetc之后,UDP传输控制块状态为TCP_ESTABLISHED*/
if (sk->sk_state != TCP_ESTABLISHED)
/*即没有指明目的地址,又没有建立connect连接,则返错。*/
return -EDESTADDRREQ;
daddr = inet->inet_daddr;
dport = inet->inet_dport;
/* Open fast path for connected socket.
Route will not be used, if at least one option is set.
*/
connected = 1;
}
/*获取存储在 socket 上的源地址、发送网络设备索引(device index)和时间戳选项*/
ipc.addr = inet->inet_saddr;
ipc.oif = sk->sk_bound_dev_if;
sock_tx_timestamp(sk, &ipc.tx_flags);
/*msg中控制信息处理*/
if (msg->msg_controllen) {
/*调用ip_cmsg_send处理控制信息,包括IP选项等...*/
err = ip_cmsg_send(sock_net(sk), msg, &ipc,
sk->sk_family == AF_INET6);
if (unlikely(err)) {
kfree(ipc.opt);
return err;
}
if (ipc.opt)
free = 1;
connected = 0;
}
/*如果发送的数据中没有IP选项控制信息,则从正在使用的socket中获取IP选项信息*/
if (!ipc.opt) {
struct ip_options_rcu *inet_opt;
rcu_read_lock();
inet_opt = rcu_dereference(inet->inet_opt);
if (inet_opt) {
memcpy(&opt_copy, inet_opt,
sizeof(*inet_opt) + inet_opt->opt.optlen);
ipc.opt = &opt_copy.opt;
}
rcu_read_unlock();
}
saddr = ipc.addr;
ipc.addr = faddr = daddr;
/*源路由选项处理*/
if (ipc.opt && ipc.opt->opt.srr) {
if (!daddr)
return -EINVAL;
faddr = ipc.opt->opt.faddr;
connected = 0;
}
tos = get_rttos(&ipc, inet);
if (sock_flag(sk, SOCK_LOCALROUTE) ||
(msg->msg_flags & MSG_DONTROUTE) ||
(ipc.opt && ipc.opt->opt.is_strictroute)) {
tos |= RTO_ONLINK;
connected = 0;
}
/*多播报文处理*/
if (ipv4_is_multicast(daddr)) {
if (!ipc.oif)
ipc.oif = inet->mc_index;
if (!saddr)
saddr = inet->mc_addr;
connected = 0;
} else if (!ipc.oif)
ipc.oif = inet->uc_index;
/*路由相关处理,获取对应的路由缓存*/
if (connected)
rt = (struct rtable *)sk_dst_check(sk, 0);
if (!rt) {
struct net *net = sock_net(sk);
fl4 = &fl4_stack;
flowi4_init_output(fl4, ipc.oif, sk->sk_mark, tos,
RT_SCOPE_UNIVERSE, sk->sk_protocol,
inet_sk_flowi_flags(sk),
faddr, saddr, dport, inet->inet_sport);
security_sk_classify_flow(sk, flowi4_to_flowi(fl4));
rt = ip_route_output_flow(net, fl4, sk);
if (IS_ERR(rt)) {
err = PTR_ERR(rt);
rt = NULL;
if (err == -ENETUNREACH)
IP_INC_STATS(net, IPSTATS_MIB_OUTNOROUTES);
goto out;
}
err = -EACCES;
if ((rt->rt_flags & RTCF_BROADCAST) &&
!sock_flag(sk, SOCK_BROADCAST))
goto out;
if (connected)
sk_dst_set(sk, dst_clone(&rt->dst));
}
if (msg->msg_flags&MSG_CONFIRM)
goto do_confirm;
back_from_confirm:
saddr = fl4->saddr;
if (!ipc.addr)
daddr = ipc.addr = fl4->daddr;
/* Lockless fast path for the non-corking case. */
/*快速路径(大部分情况下都没有使用CORK,直接新建skb,并直接发送*/
if (!corkreq) {
/*将sock相应的skb队列中的所有skb合并成一个数据报文(skb),实际使用skb_shinfo->frag_list将所有skb连接起来。
为什么要这样?这里将所有skb都合并后,可能导致这个包的size大于mtu,那到IP层的时候还会进行进一步分片?
原因是:udp是面向数据报文的,报文必须完整,属于同一个报文的数据必须要放到同一个skb中,否则对端无法知道这是同一个报文。
那IP层怎么处理呢?就不考虑同一个报文的问题?IP层分片会携带相关头信息,对端会根据这些信息进行重组,重组后对传输层来说就是一个报文。
分片其实就是IP层应该负责的。此时IP分片实际就是将原来skb->frag_list中的skb摘出来,不会做其它的操作,效率很高。*/
skb = ip_make_skb(sk, fl4, getfrag, msg, ulen,
sizeof(struct udphdr), &ipc, &rt,
msg->msg_flags);
err = PTR_ERR(skb);
if (!IS_ERR_OR_NULL(skb))
err = udp_send_skb(skb, fl4);
goto out;
}
lock_sock(sk);
if (unlikely(up->pending)) {
/* The socket is already corked while preparing it. */
/* ... which is an evident application bug. --ANK */
release_sock(sk);
net_dbg_ratelimited("cork app bug 2\n");
err = -EINVAL;
goto out;
}
/*
* Now cork the socket to pend data.
*/
/*缓存目的地址、目的端口、源地址和源端口信息,便于在发送处理时方便获取信息。*/
fl4 = &inet->cork.fl.u.ip4;
fl4->daddr = daddr;
fl4->saddr = saddr;
fl4->fl4_dport = dport;
fl4->fl4_sport = inet->inet_sport;
/*设置AF_INET标记,表明正在处理UDP数据包*/
up->pending = AF_INET;
do_append_data:
up->len += ulen;
/*调用IP层接口函数ip_append_data,进入IP层处理,主要工作为:
将数据拷贝到适合的skb(利用发送队列中现有的或新创建)中,可能有两种情况: 1. 放入skb的线性
区(skb->data)中,或者放入skb_shared_info的分片(frag)中,同时还需要考虑MTU对skb数据进行分割。*/
err = ip_append_data(sk, fl4, getfrag, msg, ulen,
sizeof(struct udphdr), &ipc, &rt,
corkreq ? msg->msg_flags|MSG_MORE : msg->msg_flags);
if (err)
udp_flush_pending_frames(sk);
else if (!corkreq)
/*发送UDP数据报*/
err = udp_push_pending_frames(sk);
else if (unlikely(skb_queue_empty(&sk->sk_write_queue)))
up->pending = 0;
release_sock(sk);
out:
ip_rt_put(rt);
if (free)
kfree(ipc.opt);
if (!err)
return len;
/*
* ENOBUFS = no kernel mem, SOCK_NOSPACE = no sndbuf space. Reporting
* ENOBUFS might not be good (it's not tunable per se), but otherwise
* we don't have a good statistic (IpOutDiscards but it can be too many
* things). We could add another new stat but at least for now that
* seems like overkill.
*/
if (err == -ENOBUFS || test_bit(SOCK_NOSPACE, &sk->sk_socket->flags)) {
UDP_INC_STATS_USER(sock_net(sk),
UDP_MIB_SNDBUFERRORS, is_udplite);
}
return err;
do_confirm:
dst_confirm(&rt->dst);
if (!(msg->msg_flags&MSG_PROBE) || len)
goto back_from_confirm;
err = 0;
goto out;
}
二、ip_make_skb
这个函数中调用__ip_append_data将要发送的数据组织成一个个skb;
调用__ip_make_skb将sock相应的skb队列中的所有skb合并成一个数据报文(skb),使用skb_shinfo->frag_list将所有skb连接起来。
这两个函数的分析后面都有。
struct sk_buff *ip_make_skb(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_cork cork;
struct sk_buff_head queue;
int err;
if (flags & MSG_PROBE)
return NULL;
__skb_queue_head_init(&queue);
cork.flags = 0;
cork.addr = 0;
cork.opt = NULL;
err = ip_setup_cork(sk, &cork, ipc, rtp);
if (err)
return ERR_PTR(err);
err = __ip_append_data(sk, fl4, &queue, &cork,
¤t->task_frag, getfrag,
from, length, transhdrlen, flags);
if (err) {
__ip_flush_pending_frames(sk, &queue, &cork);
return ERR_PTR(err);
}
return __ip_make_skb(sk, fl4, &queue, &cork);
}
三、udp_send_skb
主要作用:
1.skb生成UDP头;
2.处理校验和:软件校验和、硬件校验和、无校验和(如果禁用);
3.调用ip_send_skb将skb发往ip层处理;
4.更新发送成功或失败的统计计数器。
static int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4)
{
struct sock *sk = skb->sk;
struct inet_sock *inet = inet_sk(sk);
struct udphdr *uh;
int err = 0;
int is_udplite = IS_UDPLITE(sk);
int offset = skb_transport_offset(skb);
int len = skb->len - offset;
__wsum csum = 0;
/*
* Create a UDP header
*/
/*生成udp头*/
uh = udp_hdr(skb);
uh->source = inet->inet_sport;
uh->dest = fl4->fl4_dport;
uh->len = htons(len);
uh->check = 0;
/*处理轻量级UDP校验和*/
if (is_udplite) /* UDP-Lite */
csum = udplite_csum(skb);
/*如果socket校验和选项被关闭,(setsockopt带SO_NO_CHECK参数),将不进行校验*/
else if (sk->sk_no_check_tx) { /* UDP csum disabled */
skb->ip_summed = CHECKSUM_NONE;
goto send;
}
/*如果硬件支持校验和,将调用udp4_hwcsum来设置它。如果数据包是分段的,内核将在
软件中生成校验和,在udp4_hwcsum源码中可以看到这一点。*/
else if (skb->ip_summed == CHECKSUM_PARTIAL) { /* UDP hardware csum */
udp4_hwcsum(skb, fl4->saddr, fl4->daddr);
goto send;
}
/*调用udp_csum生成校验和*/
else
csum = udp_csum(skb);
/* add protocol-dependent pseudo-header */
/*添加伪头,生成校验字*/
uh->check = csum_tcpudp_magic(fl4->saddr, fl4->daddr, len,
sk->sk_protocol, csum);
/*如果校验和为 0,则根据RFC 768,校验为全 1*/
if (uh->check == 0)
uh->check = CSUM_MANGLED_0;
send:
err = ip_send_skb(sock_net(sk), skb);
/*发送失败,并且错误是ENOBUFS(没有内存)、且错误queue(inet->recverr)没有启用,更新NDBUFERRORS*/
if (err) {
if (err == -ENOBUFS && !inet->recverr) {
UDP_INC_STATS_USER(sock_net(sk),
UDP_MIB_SNDBUFERRORS, is_udplite);
err = 0;
}
}
/*发送成功,更新OUTDATAGRAMS统计*/
else
UDP_INC_STATS_USER(sock_net(sk),
UDP_MIB_OUTDATAGRAMS, is_udplite);
return err;
}
1. ip_send_skb
这个函数很简单,直接调用 ip_local_out发送skb,如果调用失败,更新相应的统计计数。
int ip_send_skb(struct net *net, struct sk_buff *skb)
{
int err;
err = ip_local_out(skb);
if (err) {
if (err > 0)
err = net_xmit_errno(err);
if (err)
IP_INC_STATS(net, IPSTATS_MIB_OUTDISCARDS);
}
return err;
}
net_xmit_errno:将底层错误转换为IP和UDP层所能理解的错误,如果发成错误,更新IP协议的OUTDISCARDS统计。
2. ip_local_out
static inline int ip_local_out(struct sk_buff *skb)
{
return ip_local_out_sk(skb->sk, skb);
}
3. ip_local_out_sk
在这个函数中调用__ip_local_out发送skb。
int ip_local_out_sk(struct sock *sk, struct sk_buff *skb)
{
int err;
err = __ip_local_out(skb);
if (likely(err == 1))
err = dst_output_sk(sk, skb);
return err;
}
4. __ip_local_out
int __ip_local_out(struct sk_buff *skb)
{
return __ip_local_out_sk(skb->sk, skb);
}
5. __ip_local_out_sk
1.设置IP数据包的总长度;
2.计算校验和;
3.通过IP层NF_INET_LOCAL_OUT hook函数,放行后调用des_output_sk函数继续发送skb。
注意这里如果程序中没有定义CONFIG_NETFILTER,nf_hook()是直接返回1的,所以在ip_local_out_sk函数中会继续调用显示调用dst_output_sk函数发送skb。
int __ip_local_out_sk(struct sock *sk, struct sk_buff *skb)
{
struct iphdr *iph = ip_hdr(skb);
iph->tot_len = htons(skb->len);
ip_send_check(iph);
return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT, sk, skb, NULL,
skb_dst(skb)->dev, dst_output_sk);
}
6. dst_output_sk
/* Output packet to network from transport. */
static inline int dst_output_sk(struct sock *sk, struct sk_buff *skb)
{
return skb_dst(skb)->output(sk, skb);
}
这个output函数指针是在哪里设置的了?
在udp_sendmsg函数中调用ip_route_output_flow设置的,调用关系为:
ip_route_output_flow->__ip_route_output_key->__mkroute_output,在__mkroute_output函数中设置rth->dst.output = ip_output;
ip_output函数就放到网络层继续分析了。
四、ip_append_data
参考:Understanding Linux Network Internals.pdf
https://blog.csdn.net/xiaoyu_750516366/article/details/84981102
http://blog.chinaunix.net/uid-14528823-id-4462540.html
这个函数的内容也很多,主要作用:
将传输层要发送的数据组织成一个个skb,将skb挂到sk->sk_write_queue的队列上。函数重点是如何将数据组织成对应的skb。
主要依据是MSG_MORE标记、设备是否支持分散/聚合IO。一个skb对应一个IP报文。
先看一下执行ip_append_data函数后,skb的组织结构。
1.就一个SKB能装下所有数据的时候,不需要分片,最简单。
2.需要分片,没有设置MSG_MORE,设备不支持NETIF_F_SG
3.需要分片,设置MSG_MORE,设备不支持NETIF_F_SG
4.设备支持NETIF_F_SG(MSG_MORE设置与否不管)
5.设备不支持NETIF_F_SG(MSG_MORE设置与否不管)
6.多个frags时,设备支持NETIF_F_SG(MSG_MORE设置与否不管)
7.多个skb共用一个分页
8.几个小函数对比
9.关于间须fraggap
10.ip_append_data函数主要流程。
cork结构和 ipcm_cookie结构比较重要,还没来得及仔细分析,先把网友的粘贴过来了,感谢前人努力。
struct cork
inet_sock中的cork成员非常关键,它影响了多次连续的ip_append_data()调用过程中该函数的执行流程。
struct inet_sock {
...
struct {
// 可取下面的IPCORK_OPT和IPCORK_ALLFRAG两个值的组合
unsigned int flags;
// 记录一个IP片段可以容纳的数据量,其实就是mtu,之所以记录是为了不用每次都计算一遍
unsigned int fragsize;
// 保存了IP选项和路由信息
struct ip_options *opt;
struct rtable *rt;
// 当前IP报文(注意不是IP片段)中已经放入的数据长度,初始化时为0
int length; /* Total length of all frames */
__be32 addr;
struct flowi fl;
} cork;
};
#define IPCORK_OPT 1 /* ip-options has been held in ipcork.opt */
#define IPCORK_ALLFRAG 2 /* always fragment (for ipv6 for now) */
struct ipcm_cookie
该结构作为ip_append_data()的一个入参,让高层协议将一些控制信息传递给ip_append_data()。
struct ipcm_cookie
{
// IP地址,UDP调用ip_append_data()时传递的是目的地址
__be32 addr;
// 出口设备的网络设备索引
int oif;
// IP选项
struct ip_options *opt;
};
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.
*/
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;
if (flags&MSG_PROBE)
return 0;
if (skb_queue_empty(&sk->sk_write_queue)) {
err = ip_setup_cork(sk, &inet->cork.base, ipc, rtp);
if (err)
return err;
}
/*只有第一个skb需要设置传输层头,不为空时,表示sk_write_queue已有sbk,
所以后面skb不用添加传输层头,transhdrlen设置为0*/
else {
transhdrlen = 0;
}
/*调用__ip_append_data函数完成主要功能*/
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
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;
struct ip_options *opt = cork->opt;
int hh_len;
int exthdrlen;
int mtu;
int copy;
int err;
int offset = 0;
unsigned int maxfraglen, fragheaderlen, maxnonfragsize;
int csummode = CHECKSUM_NONE;
struct rtable *rt = (struct rtable *)cork->dst;
u32 tskey = 0;
skb = skb_peek_tail(queue);
/*exthdrlen:扩展首部,是否启用扩展首部是由路由项决定的。*/
exthdrlen = !skb ? rt->dst.header_len : 0;
mtu = cork->fragsize;
if (cork->tx_flags & SKBTX_ANY_SW_TSTAMP &&
sk->sk_tsflags & SOF_TIMESTAMPING_OPT_ID)
tskey = sk->sk_tskey++;
/*L2层首部长度*/
hh_len = LL_RESERVED_SPACE(rt->dst.dev);
/*IP层首部长度,包括选项部分*/
fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0);
/*8字节对齐后,每个IP分配的最大长度,包括IP首部*/
maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen;
maxnonfragsize = ip_sk_ignore_df(sk) ? 0xFFFF : mtu;
/*长度判断,由于IP首部的total字段占4个字节,所以一个IP报文的长度(包括IP首部)最大就是0XFFFF,
多次调用ip_append_data函数后,可能会出现超过该值。*/
if (cork->length + length > maxnonfragsize - fragheaderlen) {
ip_local_error(sk, EMSGSIZE, fl4->daddr, inet->inet_dport,
mtu - (opt ? opt->optlen : 0));
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->dst.dev->features & NETIF_F_V4_CSUM &&
!exthdrlen)
csummode = CHECKSUM_PARTIAL;
/*更新sock中缓存的报文长度*/
cork->length += length;
/*这种情况下,调用ip_ufo_append_data处理,先忽略*/
if ((((length + fragheaderlen) > mtu) || (skb && skb_is_gso(skb))) &&
(sk->sk_protocol == IPPROTO_UDP) &&
(rt->dst.dev->features & NETIF_F_UFO) && !rt->dst.header_len &&
(sk->sk_type == SOCK_DGRAM) && !sk->sk_no_check_tx) {
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.
*/
/*sk_write_queue队列为空,即第一次填充数据,直接去分配skb*/
if (!skb)
goto alloc_new_skb;
/*这个大循环中,将length长数据填充到skb中。*/
while (length > 0) {
/* Check if the remaining data fits into current packet. */
/*判断最后一个skb的剩余空间是否能够容纳当前数据*/
copy = mtu - skb->len;
/*最后一个skb不用考虑8字节对齐,如果最后一个skb装不下,就需要考虑8字节对齐(后面还要新生成skb,它就不是最后一个skb了),
更新容纳量为maxfraglen - skb->len*/
if (copy < length)
copy = maxfraglen - skb->len;
/*copy==0,表示该skb装满了,并且是8字节对齐的,一点都不能装了,此时fraggap=0,直接分配新skb装数据;
copy<=0,表示*该skb装满了,但是没有8字节对齐,需要将sbk最后的fraggap长度数据剪切到新的skb中,再在新skb中装数据*/
if (copy <= 0) {
char *data;
unsigned int datalen;/*本次待拷贝的数据量*/
unsigned int fraglen;/*分片的长度*/
unsigned int fraggap;/*间隙长度*/
unsigned int alloclen;/*分配的skb缓冲区大小*/
struct sk_buff *skb_prev;
alloc_new_skb:
skb_prev = skb;
/*计算间隙fraggap的大小*/
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(间隙长度)*/
datalen = length + fraggap;
/*新生成一个skb也装不下,待添加的数据长度datalen更新为最大分片长度maxfraglen - fragheaderlen*/
if (datalen > mtu - fragheaderlen)
datalen = maxfraglen - fragheaderlen;
/*分片的长度*/
fraglen = datalen + fragheaderlen;
/*分配的skb缓冲区大小:
1.当设置了MSG_MORE标志(表示后面还有数据到达),并且设备不支持分散/聚合IO时,直接分配MTU大小的缓存;
2.否则,分配刚好够用的缓存区长度fraglen*/
if ((flags & MSG_MORE) &&
!(rt->dst.dev->features&NETIF_F_SG))
alloclen = mtu;
else
alloclen = fraglen;
/*第一个skb需要再加上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.
*/
/* 最有一个skb需要加上IPsec相关的尾部长度*/
if (datalen == length + fraggap)
alloclen += rt->dst.trailer_len;
/*transhdrlen不为0,表示分配的是第一个skb,需要考虑l4层首部,此时调用sock_alloc_send_skb分配内存*/
if (transhdrlen) {
skb = sock_alloc_send_skb(sk,
alloclen + hh_len + 15,
(flags & MSG_DONTWAIT), &err);
}
/*后面分配的skb调用sock_wmalloc处理*/
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))
err = -ENOBUFS;
}
if (!skb)
goto error;
/*
* Fill in the control structures
*/
/*校验和相关*/
skb->ip_summed = csummode;
skb->csum = 0;
/*预留l2头部长度*/
skb_reserve(skb, hh_len);
/* only the initial fragment is time stamped */
skb_shinfo(skb)->tx_flags = cork->tx_flags;
cork->tx_flags = 0;
skb_shinfo(skb)->tskey = tskey;
tskey = 0;
/*
* Find where to start putting bytes.
*/
/*skb->tail += len,是数据区扩大len字节,为存放三层头首部和数据预留空间*/
data = skb_put(skb, fraglen + exthdrlen);
/*设置IP头、传输层头指针位置*/
skb_set_network_header(skb, exthdrlen);
skb->transport_header = (skb->network_header +
fragheaderlen);
/*设置data指针位置,就是数据开始存储的地方*/
data += fragheaderlen + exthdrlen;
/*完成上一个skb的fraggap数据剪切到当前skb,并且重新计算两个skb的校验和。*/
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);
}
/*调用getfrag拷贝copy字节数据内容到skb中*/
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;
length -= datalen - fraggap;
transhdrlen = 0;
exthdrlen = 0;
csummode = CHECKSUM_NONE;
/*
* Put the packet on the pending queue.
*/
/*skb添加到sk_write_queue*/
__skb_queue_tail(queue, skb);
continue;
}
/*最后一个skb的剩余空间可以装下剩余数据*/
if (copy > length)
copy = length;
/*设备不支持分散/聚合IO时,将数据拷贝到原来skb的线性缓冲区*/
if (!(rt->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;
}
}
/*设备支持分散/缓冲IO时,将数据拷贝到skb的frags数组中。--后面这段没有分析,直接拷贝的别人的分析,后面自己再分析一下*/
else {
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;
}
五、udp_push_pending_frames
将输出队列上的多个片段合成一个完整的ip数据报文,并通过ip_output输出。调用ip_finish_skb构造skb;调用udp_send_skb发送skb。
1. udp_push_pending_frames
/*
* Push out all pending data as one UDP datagram. Socket is locked.
*/
int udp_push_pending_frames(struct sock *sk)
{
struct udp_sock *up = udp_sk(sk);
struct inet_sock *inet = inet_sk(sk);
struct flowi4 *fl4 = &inet->cork.fl.u.ip4;
struct sk_buff *skb;
int err = 0;
skb = ip_finish_skb(sk, fl4);
if (!skb)
goto out;
err = udp_send_skb(skb, fl4);
out:
up->len = 0;
up->pending = 0;
return err;
}
2. ip_finish_skb
static inline struct sk_buff *ip_finish_skb(struct sock *sk, struct flowi4 *fl4)
{
return __ip_make_skb(sk, fl4, &sk->sk_write_queue, &inet_sk(sk)->cork.base);
}
3. __ip_make_skb
将sock相应的skb队列中的所有skb合并成一个数据报文(skb),使用skb_shinfo->frag_list将所有skb连接起来。填充IP头,对skb的组织进行了修改:
https://www.cnblogs.com/codestack/p/9265886.html
http://blog.chinaunix.net/uid-14528823-id-4468600.html
这个图就对应着函数中while ((tmp_skb = __skb_dequeue(queue)) != NULL)操作。
/*
* Combined all pending IP fragments on the socket as one IP datagram
* and push them out.
*/
struct sk_buff *__ip_make_skb(struct sock *sk,
struct flowi4 *fl4,
struct sk_buff_head *queue,
struct inet_cork *cork)
{
struct sk_buff *skb, *tmp_skb;
struct sk_buff **tail_skb;
struct inet_sock *inet = inet_sk(sk);
struct net *net = sock_net(sk);
struct ip_options *opt = NULL;
struct rtable *rt = (struct rtable *)cork->dst;
struct iphdr *iph;
__be16 df = 0;
__u8 ttl;
/*从sk_write_queue链表中获取skb*/
skb = __skb_dequeue(queue);
if (!skb)
goto out;
tail_skb = &(skb_shinfo(skb)->frag_list);
/* move skb->data to ip header from ext header */
/*调整skb->data到ip头*/
if (skb->data < skb_network_header(skb))
__skb_pull(skb, skb_network_offset(skb));
while ((tmp_skb = __skb_dequeue(queue)) != NULL) {
__skb_pull(tmp_skb, skb_network_header_len(skb));
*tail_skb = tmp_skb;
tail_skb = &(tmp_skb->next);
skb->len += tmp_skb->len;//skb总长度增加
skb->data_len += tmp_skb->len;//skb的data长度增加
skb->truesize += tmp_skb->truesize;
tmp_skb->destructor = NULL;
tmp_skb->sk = NULL;
}
/* Unless user demanded real pmtu discovery (IP_PMTUDISC_DO), we allow
* to fragment the frame generated here. No matter, what transforms
* how transforms change size of the packet, it will come out.
*/
/*在不启用路由MTU时,允许对输出数据报进行分片*/
skb->ignore_df = ip_sk_ignore_df(sk);
/* DF bit is set when we want to see DF on outgoing frames.
* If ignore_df is set too, we still allow to fragment this frame
* locally. */
/*如果启用了路由MTU发现功能,或者输出数据报的长度小于MTU且本地传输控制块输出的IP
数据报不能分片,则给IP首部添加禁止分片标志*/
if (inet->pmtudisc == IP_PMTUDISC_DO ||
inet->pmtudisc == IP_PMTUDISC_PROBE ||
(skb->len <= dst_mtu(&rt->dst) &&
ip_dont_fragment(sk, &rt->dst)))
df = htons(IP_DF);
/*如果IP选项信息已经保存到传输控制块中,则获取IP选项信息指针,用于构建IP首部中的选项*/
if (cork->flags & IPCORK_OPT)
opt = cork->opt;
/*ttl获取*/
if (cork->ttl != 0)
ttl = cork->ttl;
else if (rt->rt_type == RTN_MULTICAST)
ttl = inet->mc_ttl;
else
ttl = ip_select_ttl(inet, &rt->dst);
/*ip头信息填充*/
iph = ip_hdr(skb);
iph->version = 4;
iph->ihl = 5;
iph->tos = (cork->tos != -1) ? cork->tos : inet->tos;
iph->frag_off = df;
iph->ttl = ttl;
iph->protocol = sk->sk_protocol;
ip_copy_addrs(iph, fl4);
ip_select_ident(net, skb, sk);
if (opt) {
iph->ihl += opt->optlen>>2;
ip_options_build(skb, opt, cork->addr, rt, 0);
}
skb->priority = (cork->tos != -1) ? cork->priority: sk->sk_priority;
skb->mark = sk->sk_mark;
/*
* Steal rt from cork.dst to avoid a pair of atomic_inc/atomic_dec
* on dst refcount
*/
cork->dst = NULL;
skb_dst_set(skb, &rt->dst);
if (iph->protocol == IPPROTO_ICMP)
icmp_out_count(net, ((struct icmphdr *)
skb_transport_header(skb))->type);
ip_cork_release(cork);
out:
return skb;
}
??????