0X01
正常狀況下TCP鏈接會經過4次揮手進行拆鏈(也有經過RST拆除鏈接的可能,見爲何服務器忽然回覆RST——當心網絡中的安全設備),下圖TCP狀態機展現了TCP鏈接的狀態變化過程:html

咱們重點看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
下圖抓包截圖展現了一個完整的鏈接拆除又複用一樣的端口新建鏈接的過程。socket

在圖中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狀態的鏈接存在影響服務器性能。代理