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