从udp_sendmsg到ip_output发包过程

从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标记的场景下才会设置这个标记。

转载自:

https://blog.packagecloud.io/eng/2017/02/06/monitoring-tuning-linux-networking-stack-sending-data/#udp_sendmsg

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,
			       &current->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;
}

🥴🥴🥴🥴🥴🥴

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值