0X01
正常情況下TCP連接會通過4次揮手進行拆鏈(也有通過RST拆除連接的可能,見為什么服務器突然回復RST——小心網絡中的安全設備),下圖TCP狀態機展示了TCP連接的狀態變化過程:

我們重點看4次揮手的過程:
- 想要拆除連接的一方A發送FIN報文,自身進入到FIN_WAIT_1狀態;
- 被拆除連接的一方B接收到FIN報文,發ACK,自身進入到CLOSE_WAIT狀態;
- A收到ACK,進入FIN_WAIT_2狀態;
- B發送FIN,自身進入LAST_ACK狀態;
- A收到FIN,發送ACK,自身進入TIME_WAIT狀態;
- B收到ACK報文,B上的這個socket關閉,端口釋放;
- A等待2MSL后socket關閉,釋放端口。
從以上連接拆除過程我們可以看到:主動發送第一個FIN報文的一方會進入TIME_WAIT狀態;進入TIME_WAIT狀態的一方需要等待2MSL時間才會釋放端口,在2MSL時間內,這個socket對應的四元組(源目IP、源目端口)處於凍結狀態。
TIME_WAIT狀態的作用主要有兩個:
- 避免拆鏈報文在鏈路中丟失造成連接關閉異常:在第6步,B沒有收到ACK報文的時候會認為A沒有收到FIN包,進而會重傳第4步的FIN,如果這個時候沒有TIME_WAIT狀態,A側socket已經關閉,A會針對B發送的FIN包響應RST,有可能導致B連接異常。
- 避免亂序到來的業務報文在新生成的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個完整的揮手報文后,客戶端立即使用相同的源端口和服務器的監聽端口建立新的連接。下面逐報文對整個交互過程進行分析:
- server→client(PSH、ACK):服務器推送數據最后一個分段給客戶端
- client→server(ACK):客戶端對第1個報文進行接收確認
- server→client(FIN、ACK):服務器發送揮手報文給客戶端協商斷開連接,這是四次揮手的第一步。同時ACK標志位置位,由於第2個報文中沒有載荷數據,所以ack值=第2個報文的seq。此時服務器進入FIN_WAIT_1狀態
- client→server(ACK):客戶端對接收到的FIN包進行響應,這是四次揮手的第二步。其中seq值不變,ack=第3個報文的seq+1(因為FIN報文在邏輯上占一個長度)。此時客戶端進入CLOSE_WAIT狀態,服務器收到ACK報文后進入FIN_WAIT_2狀態
- client→server(FIN、ACK):客戶端給服務器發送FIN包,這是四次揮手的第三步。其seq和ack的值和第4個報文相同。此時客戶端進入LAST_ACK狀態,等待服務器響應ACK報文后即可關閉連接
- server→client(ACK):服務器收到客戶端發送的FIN包后會立即給客戶端發送ACK包,這是四次揮手的最后一步。其中seq=第3個報文中的seq+1,ack=第5個報文中的seq+1。客戶端收到ACK后會立即close該四元組對應的socket,而此時服務器在發送ACK后會進入TIME_WAIT狀態,服務器側對應的TCP四元組會被凍結2MSL
- client→server(SYN):客戶端連接拆除后立即使用同一個源端口31387向服務器的82端口發起新的SYN連接握手報文
- server→client(ACK):通過seq和ack可以看出服務器重傳第6個報文。由於服務器對應的四元組仍然在TIME_WAIT狀態中,因此對於接受到的報文會認為是迷路的數據包或者客戶端沒有收到服務器發送的最后一個揮手的ACK報文,所以服務器重新向客戶端發送該ACK報文
- 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狀態的連接存在影響服務器性能。
