從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;
}

🥴🥴🥴🥴🥴🥴

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章