#TCP你學得會# 之 client重用鏈接之時

    好吧,原本已經寫好的博文在我點擊保存以後仍是莫名丟失了,人生就是這樣,到處均可能充滿了狗血,但連滾帶爬也不能屈服,因而深夜從新來過,文思可能不如上一次那麼流暢,但好在是技術文章,主要看氣質吧。併發

    下面是華麗的分割線less

============================================================socket

    繼續前面的話題。tcp

    咱們已經知道,當client端斷開鏈接(close)以後,server端進入CLOSE_WAIT狀態,若是server端不進行任何動做的話這個狀態能夠一直持續下去,而client端的鏈接則最終會由於超時而銷燬。函數

    那麼,隨之而來,新的問題又來了,若是一段時間以後client端複用了以前的四元組信息發起新的鏈接請求,又會是什麼樣的情形呢?spa

    下面先經過抓包來直觀地感覺一下這個過程(爲了便於分析tcp報文中的sequence number,特別取消了Wireshark中的相對序列號分析功能,Edit--->Preferences--->Protocols--->TCP--->Analyze TCP sequence numbers):code

    上圖的抓包能夠分爲這麼幾個階段:
server

    1)client端向server端發起鏈接併發送若干數據段(No. 1  -  No. 22);ip

    2)client端關閉鏈接,server端對數據和FIN分別迴應ACK後沒有後續動做,這時server端將維持在CLOSE_WAIT狀態,而client端則在FIN_WAIT_2狀態下暫停一段時間後超時退出(No.23  -  No.25);
原型

    3)client端複用以前的四元組從新發起鏈接,server端以ACK進行迴應,且應答號與SYN中的初始序列號不一致,而是延續了上一個鏈接的應答號,這觸發了client端的RST(No.26  -  No.28);

    4)約1s以後client端協議棧嘗試從新創建鏈接併成功;

    

    下面咱們就去Kernel裏面扒一扒對應的實現吧,重點集中在如下三個場景:

  • 處於CLOSE_WAIT狀態的鏈接收到一個複用的新鏈接請求時會怎麼樣?

  • 處於SYN_SENT狀態的鏈接收到一個序列號不匹配的應答時會怎麼樣?

  • 處於CLOSE_WAIT狀態的鏈接收到RST時會怎樣?

    Kernel版本3.6.1。

    協議棧中處理tcp協議的入口函數是tcp_v4_rcv,其原型以下:

int tcp_v4_rcv(struct sk_buff *skb);

    函數tcp_v4_rcv只有一個參數,就是接收報文的sk_buff結構,在該函數中會以四元組信息(sip, sport, dip, dport)來計算hash值,並根據計算結果分別在established表和listen表中查找對應的struct sock* 結構。因爲client端複用了四元組信息,且server端的鏈接並無銷燬,所以可以匹配到原有的struct sock* 結構,該結構中的狀態信息sk_state將做爲後續處理的重要依據。接着流程轉入tcp_v4_do_rcv函數:

int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb);

    能夠看到,tcp_v4_do_rcv函數的參數除了存放報文內容的sk_buff結構外,就是上一步中查到的struct sock* 結構啦,這個函數內只處理了TCP_ESTABLISHED和TCP_LISTEN兩種狀態,更多的內容交付給了tcp_rcv_state_process函數:

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
			  const struct tcphdr *th, unsigned int len)

    tcp_rcv_state_process函數中的內容可就豐富多了,全部的狀態幾乎都在這裏等待檢閱,這也是本文的重點所在,對照上面總結的三個場景來分別進行分析。

    1) 處於CLOSE_WAIT狀態的鏈接收到一個複用的新鏈接請求時會怎麼樣:

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
			  const struct tcphdr *th, unsigned int len)
{
    ... ...
    /* step 7: process the segment text */
    switch (sk->sk_state) {
        case TCP_CLOSE_WAIT:
	case TCP_CLOSING:
	case TCP_LAST_ACK:
            if (!before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt))/*報文中的序列號晚於指望接收的序列號*/
                break;
	case TCP_FIN_WAIT1:
	case TCP_FIN_WAIT2:
	    /* RFC 793 says to queue data in these states,
	    * RFC 1122 says we MUST send a reset.
	    * BSD 4.4 also does reset.
	    */
	    if (sk->sk_shutdown & RCV_SHUTDOWN) {
                if (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&
                    after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt)) {
                    NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
                    tcp_reset(sk);
                    return 1;
		}
	    }
	/* Fall through */
	case TCP_ESTABLISHED:
	    tcp_data_queue(sk, skb);
	    queued = 1;
	    break;
    }

    /* tcp_data could move socket to TIME-WAIT */
    if (sk->sk_state != TCP_CLOSE) {
        tcp_data_snd_check(sk);
        tcp_ack_snd_check(sk);
    }

    if (!queued) {
discard:
        __kfree_skb(skb);
    }
    return 0;
}

    能夠看到,在處於CLOSE_WAIT狀態的鏈接上收到一個報文的序列號晚於(大於)指望接收的序列號時,會以ACK來進行迴應。

    2) 處於SYN_SENT狀態的鏈接收到一個序列號不匹配的應答時會怎麼樣:

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
			  const struct tcphdr *th, unsigned int len)
{
    ... ...
    case TCP_SYN_SENT:
        queued = tcp_rcv_synsent_state_process(sk, skb, th, len);
	if (queued >= 0)
	    return queued;

	/* Do step6 onward by hand. */
	tcp_urg(sk, skb, th);
	__kfree_skb(skb);
	tcp_data_snd_check(sk);
	return 0;
    }
    ... ...
}


static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
					 const struct tcphdr *th, unsigned int len)
{
    ... ...
    if (th->ack) {
        /* rfc793:
	* "If the state is SYN-SENT then
	*    first check the ACK bit
	*      If the ACK bit is set
	*	  If SEG.ACK =< ISS, or SEG.ACK > SND.NXT, send
	*        a reset (unless the RST bit is set, if so drop
	*        the segment and return)"
	*/
	if (!after(TCP_SKB_CB(skb)->ack_seq, tp->snd_una) ||
	    after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt))
	    goto reset_and_undo;

	if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr &&
            !between(tp->rx_opt.rcv_tsecr, tp->retrans_stamp,
            tcp_time_stamp)) {
            NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSACTIVEREJECTED);
            goto reset_and_undo;
        }

	/* Now ACK is acceptable.
        *
	* "If the RST bit is set
	*    If the ACK was acceptable then signal the user "error:
	*    connection reset", drop the segment, enter CLOSED state,
	*    delete TCB, and return."
	*/
    ... ...
    /* "fifth, if neither of the SYN or RST bits is set then
    * drop the segment and return."
    */

discard_and_undo:
    tcp_clear_options(&tp->rx_opt);
    tp->rx_opt.mss_clamp = saved_clamp;
    goto discard;

reset_and_undo:
    tcp_clear_options(&tp->rx_opt);
    tp->rx_opt.mss_clamp = saved_clamp;
    return 1;
}

    tcp_rcv_synsent_state_process函數中的註釋很明確的說明了當鏈接處在SYN_SENT狀態時首先檢查ACK位,若是該 ACK位置位而且報文中的應答號不大於初始序列號,則觸發RST。

    3) 處於CLOSE_WAIT狀態的鏈接收到RST時會怎樣:

    在tcp_rcv_state_process函數中有一段調用:

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
			  const struct tcphdr *th, unsigned int len)
{
    ... ...
    if (!tcp_validate_incoming(sk, skb, th, 0))
	return 0;
    ... ...
}


static bool tcp_validate_incoming(struct sock *sk, struct sk_buff *skb,
				  const struct tcphdr *th, int syn_inerr)
{
    ... ...
    /* Step 2: check RST bit */
    if (th->rst) {
	/* RFC 5961 3.2 :
	* If sequence number exactly matches RCV.NXT, then
	*     RESET the connection
	* else
	*     Send a challenge ACK
	*/
	if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt)
	    tcp_reset(sk);
	else
	    tcp_send_challenge_ack(sk);
	goto discard;
	}
    ... ...
}

    能夠看到,當收到RST且報文中攜帶的序列號與指望序列號一致時,會調用tcp_reset函數,這個函數在以前的文章中也有分析過,其中會對處於CLOSE_WAIT狀態的鏈接賦予EPIPE錯誤碼,並觸發SIGPIPE信號。

    至此,整個流程就合攏了。


    PS.  聰明的你必定知道tcp端口號是如何複用的,沒錯,就是bind函數咯。

相關文章
相關標籤/搜索