1. 程式人生 > >TCP傳送函式tcp_transmit_skb

TCP傳送函式tcp_transmit_skb

上一篇介紹了TCP協議層和套接字層的介面tcp_sendmsg函式是將使用者地址空間資料複製到核心地址空間,接下來的工作是交給tcp_transmit_skb函式向IP層傳送資料包,tcp_transmit_skb傳送的資料包有

(1)重傳資料包tcp_retransmit_skb。

(2)探測路由最大傳送單元資料包。

(3)傳送復位連線資料包

(4)傳送連線請求資料包

(5)傳送回答ACK資料包

(6)視窗探測資料包

應用層、TCP層、IP層之間介面關係如下圖:

應用層_TCP層_IP層之間介面函式關係

通過上圖我們知道無論是應用層引數的資料包、TCP協議連線管理的資料包、重傳資料包都是通過tcp_transmit_skb最後呼叫ip_queue_xmit傳送給IP層,下面分析tcp_transmit_skb函式。

1、初始化區域性變數

tcp_transmit_skb傳送TCP資料段,就要初始化TCP協議頭等資料結構,主要如下:

inet:初始化AF_INET地址族套接字struct inet_sock *inet。

tp:TCP選項結構,包含TCP配置和連線資訊。

tch:TCP控制緩衝區,用於構造TCP協議頭

th:TCP協議頭資料結構。

icsk:inet連線控制套接字。

static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,
			    gfp_t gfp_mask)
{
	//連線控制套接字
	const struct inet_connection_sock *icsk = inet_csk(sk);
	//AF_INET地址族套接字
	struct inet_sock *inet;
	//TCP選項結構
	struct tcp_sock *tp;
	//TCP控制緩衝區
	struct tcp_skb_cb *tcb;
	struct tcp_out_options opts;
	unsigned tcp_options_size, tcp_header_size;
	struct tcp_md5sig_key *md5;
	struct tcphdr *th;
	int err;

...
}

2、克隆Socket Buffer

檢視clone_it是否要克隆Socket Buffer,應用Socket Buffer可能正被其他程序使用,就要克隆一個份。

...

//如果還有其他程序使用Socket Buffer
	//就要克隆Socket Buffer
	if (likely(clone_it)) {
		if (unlikely(skb_cloned(skb)))
			skb = pskb_copy(skb, gfp_mask);
		else
			skb = skb_clone(skb, gfp_mask);
		if (unlikely(!skb))
			return -ENOBUFS;
	}

...

3、構建TCP協議選項

檢視資料包是否是一個SYN包(TCPCB_FLAG_SYN)如果是就呼叫tcp_syn_options構建SYN資料段的選項資料,包括時間戳、視窗大小、選擇回答(SACK),否則呼叫tcp_establishe_options構架常規TCP選項,並返回TCP選項長度。TCP協議頭總的長度等於TCP選項長度+TCP協議頭長度。

...

//獲取AF_INIT協議組套接字
	inet = inet_sk(sk);
	//TCP選項結構體
	tp = tcp_sk(sk);
	//TCP控制緩衝區協議頭
	tcb = TCP_SKB_CB(skb);
	memset(&opts, 0, sizeof(opts));
	//是否是SYN請求資料包
	if (unlikely(tcb->flags & TCPCB_FLAG_SYN))
		//構建TCP選項包括時間戳、視窗大小、選擇回答SACK
		tcp_options_size = tcp_syn_options(sk, skb, &opts, &md5);
	else
		//構建常規TCP選項
		tcp_options_size = tcp_established_options(sk, skb, &opts,
							   &md5);
	//tCP頭部長度包括選擇長度+ TCP頭部
	tcp_header_size = tcp_options_size + sizeof(struct tcphdr);
...

3、阻塞控制

確定網路上有多少資料包最好,大多數情況下是按照保守情況處理,網路上有多少資料包做好的詳細資訊是用收到的SACK的資訊來確認,tp->pachets_out確定傳送佇列中是否為空,阻塞控制計算方法:傳送佇列上的資料包+必須快速重傳資料包-留在網路上的資料包,如果等於0表示不會阻塞,這時傳送時間標誌。

...

//網路阻塞控制管理
	//阻塞控制計算 傳送佇列資料包數-留在網路上的資料+ 重傳資料包
	if (tcp_packets_in_flight(tp) == 0)
		//設定傳送資料包事件標誌
		tcp_ca_event(sk, CA_EVENT_TX_START);
...

阻塞控制計算方法:傳送佇列上的資料包+必須快速重傳資料包-留在網路上的資料包

static inline unsigned int tcp_packets_in_flight(const struct tcp_sock *tp)
{
    return tp->packets_out - tcp_left_out(tp) + tp->retrans_out;
}

4、構建TCP協議頭

構建TCP協議頭主要的資料域:源埠、目的埠、資料段初始序列號,tcp_select_window計算視窗大小,如果是SYN請求包就不需要計算視窗大小。

...

/* Build TCP header and checksum it. */
	//構建TCP協議頭
	th = tcp_hdr(skb);
	th->source		= inet->inet_sport;
	th->dest		= inet->inet_dport;
	th->seq			= htonl(tcb->seq);
	th->ack_seq		= htonl(tp->rcv_nxt);
	*(((__be16 *)th) + 6)	= htons(((tcp_header_size >> 2) << 12) |
					tcb->flags);
	//SYN包不需要計算視窗
	if (unlikely(tcb->flags & TCPCB_FLAG_SYN)) {
		/* RFC1323: The window in SYN & SYN/ACK segments
		 * is never scaled.
		 */
		th->window	= htons(min(tp->rcv_wnd, 65535U));
	} else {
		//計算視窗大小
		th->window	= htons(tcp_select_window(sk));
	}
	th->check		= 0;
	th->urg_ptr		= 0;

...

5、傳送資料包

傳送資料包到IP層

...

//傳送資料包到IP層,
	//函式指標實際指向ip_queue_ximit
	err = icsk->icsk_af_ops->queue_xmit(skb);
	if (likely(err <= 0))
		return err;

...

6、傳送過程狀態機切換

TCP傳送過程狀態機切換圖

如上圖傳送過程狀態機切換,通過這張圖可以直達TCP協議從連線建立到關閉的過程,FIN包後有兩個FIN_WAIT狀態,因為TCP連線上雙向全雙工的,當傳送一個FIN包進入FIN_WAIT1狀態,當收到對端的ACK進入FIN_WAIT2狀態此時不能再發送資料包了,只能接受對端的資料包,當收到對端的FIN包在傳送ACK給對端進入TIME_WAIT狀態,此時TCP套接字不會立即關閉,此時再去繫結這個埠就會提示此埠已經繫結,因為要保證對端收到ACK,假如對端沒有收到ACK就會再次傳送FIN包,有了超時時間就能再次收到對端的FIN然後回覆ACK,這個TIME_WAIT時間一般是2min。

 

tcp_transmit_skb:

static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,
			    gfp_t gfp_mask)
{
	//連線控制套接字
	const struct inet_connection_sock *icsk = inet_csk(sk);
	//AF_INET地址族套接字
	struct inet_sock *inet;
	//TCP選項結構
	struct tcp_sock *tp;
	//TCP控制緩衝區
	struct tcp_skb_cb *tcb;
	struct tcp_out_options opts;
	unsigned tcp_options_size, tcp_header_size;
	struct tcp_md5sig_key *md5;
	struct tcphdr *th;
	int err;

	BUG_ON(!skb || !tcp_skb_pcount(skb));

	/* If congestion control is doing timestamping, we must
	 * take such a timestamp before we potentially clone/copy.
	 */
	if (icsk->icsk_ca_ops->flags & TCP_CONG_RTT_STAMP)
		__net_timestamp(skb);

	//如果還有其他程序使用Socket Buffer
	//就要克隆Socket Buffer
	if (likely(clone_it)) {
		if (unlikely(skb_cloned(skb)))
			skb = pskb_copy(skb, gfp_mask);
		else
			skb = skb_clone(skb, gfp_mask);
		if (unlikely(!skb))
			return -ENOBUFS;
	}

	//獲取AF_INIT協議組套接字
	inet = inet_sk(sk);
	//TCP選項結構體
	tp = tcp_sk(sk);
	//TCP控制緩衝區協議頭
	tcb = TCP_SKB_CB(skb);
	memset(&opts, 0, sizeof(opts));
	//是否是SYN請求資料包
	if (unlikely(tcb->flags & TCPCB_FLAG_SYN))
		//構建TCP選項包括時間戳、視窗大小、選擇回答SACK
		tcp_options_size = tcp_syn_options(sk, skb, &opts, &md5);
	else
		//構建常規TCP選項
		tcp_options_size = tcp_established_options(sk, skb, &opts,
							   &md5);
	//tCP頭部長度包括選擇長度+ TCP頭部
	tcp_header_size = tcp_options_size + sizeof(struct tcphdr);

	//網路阻塞控制管理
	//阻塞控制計算 傳送佇列資料包數-留在網路上的資料+ 重傳資料包
	if (tcp_packets_in_flight(tp) == 0)
		//設定傳送資料包事件標誌
		tcp_ca_event(sk, CA_EVENT_TX_START);
	//是指skb->data指標
	skb_push(skb, tcp_header_size);
	//設定ksb->transport_header指標
	skb_reset_transport_header(skb);
	//設定skb所屬套接字
	skb_set_owner_w(skb, sk);

	/* Build TCP header and checksum it. */
	//構建TCP協議頭
	th = tcp_hdr(skb);
	th->source		= inet->inet_sport;
	th->dest		= inet->inet_dport;
	th->seq			= htonl(tcb->seq);
	th->ack_seq		= htonl(tp->rcv_nxt);
	*(((__be16 *)th) + 6)	= htons(((tcp_header_size >> 2) << 12) |
					tcb->flags);
	//SYN包不需要計算視窗
	if (unlikely(tcb->flags & TCPCB_FLAG_SYN)) {
		/* RFC1323: The window in SYN & SYN/ACK segments
		 * is never scaled.
		 */
		th->window	= htons(min(tp->rcv_wnd, 65535U));
	} else {
		//計算視窗大小
		th->window	= htons(tcp_select_window(sk));
	}
	th->check		= 0;
	th->urg_ptr		= 0;

	/* The urg_mode check is necessary during a below snd_una win probe */
	if (unlikely(tcp_urg_mode(tp) && before(tcb->seq, tp->snd_up))) {
		if (before(tp->snd_up, tcb->seq + 0x10000)) {
			th->urg_ptr = htons(tp->snd_up - tcb->seq);
			th->urg = 1;
		} else if (after(tcb->seq + 0xFFFF, tp->snd_nxt)) {
			th->urg_ptr = htons(0xFFFF);
			th->urg = 1;
		}
	}

	tcp_options_write((__be32 *)(th + 1), tp, &opts);
	if (likely((tcb->flags & TCPCB_FLAG_SYN) == 0))
		TCP_ECN_send(sk, skb, tcp_header_size);

#ifdef CONFIG_TCP_MD5SIG
	/* Calculate the MD5 hash, as we have all we need now */
	if (md5) {
		sk_nocaps_add(sk, NETIF_F_GSO_MASK);
		tp->af_specific->calc_md5_hash(opts.hash_location,
					       md5, sk, NULL, skb);
	}
#endif

	icsk->icsk_af_ops->send_check(sk, skb);

	if (likely(tcb->flags & TCPCB_FLAG_ACK))
		tcp_event_ack_sent(sk, tcp_skb_pcount(skb));

	if (skb->len != tcp_header_size)
		tcp_event_data_sent(tp, skb, sk);

	if (after(tcb->end_seq, tp->snd_nxt) || tcb->seq == tcb->end_seq)
		TCP_ADD_STATS(sock_net(sk), TCP_MIB_OUTSEGS,
			      tcp_skb_pcount(skb));

	//傳送資料包到IP層,
	//函式指標實際指向ip_queue_ximit
	err = icsk->icsk_af_ops->queue_xmit(skb);
	if (likely(err <= 0))
		return err;

	tcp_enter_cwr(sk, 1);

	return net_xmit_eval(err);
}