1. 程式人生 > >TCP狀態機:當服務端主動發FIN進TIME_WAIT,客戶端源埠複用會發生什麼

TCP狀態機:當服務端主動發FIN進TIME_WAIT,客戶端源埠複用會發生什麼

0X01

正常情況下TCP連線會通過4次揮手進行拆鏈(也有通過RST拆除連線的可能,見為什麼伺服器突然回覆RST——小心網路中的安全裝置),下圖TCP狀態機展示了TCP連線的狀態變化過程:

我們重點看4次揮手的過程:

  1. 想要拆除連線的一方A傳送FIN報文,自身進入到FIN_WAIT_1狀態;
  2. 被拆除連線的一方B接收到FIN報文,發ACK,自身進入到CLOSE_WAIT狀態;
  3. A收到ACK,進入FIN_WAIT_2狀態;
  4. B傳送FIN,自身進入LAST_ACK狀態;
  5. A收到FIN,傳送ACK,自身進入TIME_WAIT狀態;
  6. B收到ACK報文,B上的這個socket關閉,埠釋放;
  7. A等待2MSL後socket關閉,釋放埠。

從以上連線拆除過程我們可以看到:主動傳送第一個FIN報文的一方會進入TIME_WAIT狀態;進入TIME_WAIT狀態的一方需要等待2MSL時間才會釋放埠,在2MSL時間內,這個socket對應的四元組(源目IP、源目埠)處於凍結狀態。

TIME_WAIT狀態的作用主要有兩個:

  1. 避免拆鏈報文在鏈路中丟失造成連線關閉異常:在第6步,B沒有收到ACK報文的時候會認為A沒有收到FIN包,進而會重傳第4步的FIN,如果這個時候沒有TIME_WAIT狀態,A側socket已經關閉,A會針對B傳送的FIN包響應RST,有可能導致B連線異常。
  2. 避免亂序到來的業務報文在新生成的socket連線中引發混亂:假設在拆鏈前有TCP報文由於中間網路傳輸原因導致在第7步完成之後才到達,如果沒有TIME_WAIT狀態而A和B又使用同樣的4元組新建了一個新的socket,那麼迷路的資料包就會進入到新的socket中進行處理,可能導致業務異常。

通過TIME_WAIT狀態可以很好的規避上面提到的兩個問題,TIME_WAIT狀態的老化時間是2MSL,MSL是最大分段生存時間,表示的是一個TCP分段可能在網路上存在的最大時間。2倍MSL的設計可以很好的滿足報文在A、B之間一來一回的最大需要消耗的時間,最大程度上避免上述兩個問題。在CentOS系統中,MSL的時間一般是30S。

0X02

下圖抓包截圖展示了一個完整的連線拆除又複用同樣的埠新建連線的過程。

在圖中server 192.168.221.1執行Web服務,監聽82埠,client 192.168.252.2 使用31387埠連線server(抓包截圖從揮手前開始擷取)。可以看到在編號為3的報文中伺服器主動拆除連線,伺服器和客戶端互動完4個完整的揮手報文後,客戶端立即使用相同的源埠和伺服器的監聽埠建立新的連線。下面逐報文對整個互動過程進行分析:

  1. server→client(PSH、ACK):伺服器推送資料最後一個分段給客戶端
  2. client→server(ACK):客戶端對第1個報文進行接收確認
  3. server→client(FIN、ACK):伺服器傳送揮手報文給客戶端協商斷開連線,這是四次揮手的第一步。同時ACK標誌位置位,由於第2個報文中沒有載荷資料,所以ack值=第2個報文的seq。此時伺服器進入FIN_WAIT_1狀態
  4. client→server(ACK):客戶端對接收到的FIN包進行響應,這是四次揮手的第二步。其中seq值不變,ack=第3個報文的seq+1(因為FIN報文在邏輯上佔一個長度)。此時客戶端進入CLOSE_WAIT狀態,伺服器收到ACK報文後進入FIN_WAIT_2狀態
  5. client→server(FIN、ACK):客戶端給伺服器傳送FIN包,這是四次揮手的第三步。其seq和ack的值和第4個報文相同。此時客戶端進入LAST_ACK狀態,等待伺服器響應ACK報文後即可關閉連線
  6. server→client(ACK):伺服器收到客戶端傳送的FIN包後會立即給客戶端傳送ACK包,這是四次揮手的最後一步。其中seq=第3個報文中的seq+1,ack=第5個報文中的seq+1。客戶端收到ACK後會立即close該四元組對應的socket,而此時伺服器在傳送ACK後會進入TIME_WAIT狀態,伺服器側對應的TCP四元組會被凍結2MSL
  7. client→server(SYN):客戶端連線拆除後立即使用同一個源埠31387向伺服器的82埠發起新的SYN連線握手報文
  8. server→client(ACK):通過seq和ack可以看出伺服器重傳第6個報文。由於伺服器對應的四元組仍然在TIME_WAIT狀態中,因此對於接受到的報文會認為是迷路的資料包或者客戶端沒有收到伺服器傳送的最後一個揮手的ACK報文,所以伺服器重新向客戶端傳送該ACK報文
  9. client→server(RST):客戶端向伺服器傳送一個RST報文,其中seq為server揮手ack包(第6和第8個報文)的ack值。這是因為對伺服器側而言,對應的四元組仍然處於TIME_WAIT狀態,而客戶端側並不存在這個四元組的socket資訊,客戶端正準備使用這個四元組新建連線。這是前文為什麼伺服器突然回覆RST——小心網路中的安全裝置中TCP傳送RST的第三種情況:TCP接收到一個數據段,但是這個資料段所標識的連線不存在。於是客戶端使用ACK報文中的ack值作為seq,傳送RST報文給伺服器

可以看到當客戶端傳送完RST後,客戶端再次進行了SYN報文的重傳,而此次即使仍然複用之前的四元組,客戶端和伺服器的TCP三次握手正常建立。這是因為當伺服器收到RST報文後,無論處在TCP的哪個狀態,都會立即進入close狀態,進而伺服器側對應被TIME_WAIT狀態凍結的四元組得以被釋放,客戶端側的複用就成功了。

0X03

如上所述的業務場景是某應用系統使用反向代理地址連線後端伺服器的抓包。伺服器主動拆鏈+客戶端立即複用源埠,這是一種危險的實現,如果客戶端沒有RST或者伺服器端識別不了RST則很有可能在2MSL時間內,客戶端使用被凍結的4元組進行連線建立的操作都會失敗。對於伺服器主動拆鏈的場景應該保證終端可用源埠儘可能的多,儘量避免立即埠複用的情況。此外對於伺服器主動拆鏈的場景應該儘可能調短伺服器的MSL時間,避免大量TIME_WAIT狀態的連線存在影響伺服器效能