TCP三次握手背的滾瓜亂熟,那意外丟包呢?故意不回復(fù) ACK 呢?
一、序
當(dāng)我們聊到 TCP 協(xié)議的時候,聊的最多的就是三次握手與四次揮手,但是你有沒有想過,三次握手或者四次揮手時,如果發(fā)生異常了,是如何處理的?又是由誰來處理?
TCP 作為一個靠譜的協(xié)議,在傳輸數(shù)據(jù)的前后,需要在雙端之間建立連接,并在雙端各自維護連接的狀態(tài)。TCP 并沒有什么特別之處,在面對著多變的網(wǎng)絡(luò)情況,也只能通過不斷的重傳和各種算法來保證可靠性。
建立連接前,TCP 會通過三次握手來保證雙端狀態(tài)正確,然后就可以正常傳輸數(shù)據(jù)了,在數(shù)據(jù)傳輸完畢后,又通過四次揮手來保證雙端合理的斷開連接并回收各自的資源。
我們在學(xué)習(xí) TCP 建連和斷連時,都是一個標(biāo)準(zhǔn)的流程,但是網(wǎng)絡(luò)是多變的,很多時候并不像教科書那樣標(biāo)準(zhǔn),那么今天就來聊聊 TCP 三次握手出現(xiàn)異常的時候,是如何處理的。
二、TCP 三次握手
1. 簡單理解三次握手
雖然是說三次握手的異常情況,我們還是先來了解一下三次握手。
在通過 TCP 傳輸數(shù)據(jù)時,第一步就是要先建立一個連接。TCP 建立連接的過程,就是我們常說的三次握手。
我們經(jīng)常將三次握手,描述成「請求 → 應(yīng)答 → 應(yīng)答之應(yīng)答」。
至于 TCP 握手為什么是三次,其實就是要讓雙端都經(jīng)歷一次「請求 → 應(yīng)答」的過程,來確認(rèn)對方還在。網(wǎng)絡(luò)情況是多變的,雙端都需要一次自己主動發(fā)起的請求和對方回復(fù)的應(yīng)答過程,來確保對方和網(wǎng)絡(luò)是正常的。
下面這張圖,是比較經(jīng)典的 TCP 三次握手的消息和雙端狀態(tài)的變化。
我們先來解釋一下這張圖:
- 在初始時,雙端處于 CLOSE 狀態(tài),服務(wù)端為了提供服務(wù),會主動監(jiān)聽某個端口,進入 LISTEN 狀態(tài)。
- 客戶端主動發(fā)送連接的「SYN」包,之后進入 SYN-SENT 狀態(tài),服務(wù)端在收到客戶端發(fā)來的「SYN」包后,回復(fù)「SYN,ACK」包,之后進入 SYN-RCVD 狀態(tài)。
- 客戶端收到服務(wù)端發(fā)來的「SYN,ACK」包后,可以確認(rèn)對方存在,此時回復(fù)「ACK」包,并進入 ESTABLISHED 狀態(tài)。
- 服務(wù)端收到最后一個「ACK」包后,也進入 ESTABLISHED 狀態(tài)。
正常的三次握手之后,雙端都進入 ESTABLISHED 狀態(tài),在此之后,就是正常的數(shù)據(jù)傳輸過程。
2. TCP 握手的異常情況
三次握手的正常發(fā)包和應(yīng)答,以及雙端的狀態(tài)扭轉(zhuǎn)我們已經(jīng)講了,接下來就來看看在這三次握手的過程中,出現(xiàn)的異常情況。
(1) 客戶端第一個「SYN」包丟了。
如果客戶端第一個「SYN」包丟了,也就是服務(wù)端根本就不知道客戶端曾經(jīng)發(fā)過包,那么處理流程主要在客戶端。
而在 TCP 協(xié)議中,某端的一組「請求-應(yīng)答」中,在一定時間范圍內(nèi),只要沒有收到應(yīng)答的「ACK」包,無論是請求包對方?jīng)]有收到,還是對方的應(yīng)答包自己沒有收到,均認(rèn)為是丟包了,會觸發(fā)超時重傳機制。
所以此時會進入重傳「SYN」包。根據(jù)《TCP/IP詳解卷Ⅰ:協(xié)議》中的描述,此時會嘗試三次,間隔時間分別是 5.8s、24s、48s,三次時間大約是 76s 左右,而大多數(shù)伯克利系統(tǒng)將建立一個新連接的最長時間,限制為 75s。
也就是說三次握手第一個「SYN」包丟了,會重傳,總的嘗試時間是 75s。
(2) 服務(wù)端收到「SYN」并回復(fù)的「SYN,ACK」包丟了。
此時服務(wù)端已經(jīng)收到了數(shù)據(jù)包并回復(fù),如果這個回復(fù)的「SYN,ACK」包丟了,站在客戶端的角度,會認(rèn)為是最開始的那個「SYN」丟了,那么就繼續(xù)重傳,就是我們前面說的「錯誤 1 流程」。
而對服務(wù)端而言,如果發(fā)送的「SYN,ACK」包丟了,在超時時間內(nèi)沒有收到客戶端發(fā)來的「ACK」包,也會觸發(fā)重傳,此時客戶端處于 SYN_RCVD 狀態(tài),會依次等待 3s、6s、12s 后,重新發(fā)送「SYN,ACK」包。
而這個「SYN,ACK」包的重傳次數(shù),不同的操作系統(tǒng)下有不同的配置,例如在 Linux 下可以通過 tcp_synack_retries 進行配置,默認(rèn)值為 5。如果這個重試次數(shù)內(nèi),仍未收到「ACK」應(yīng)答包,那么服務(wù)端會自動關(guān)閉這個連接。
同時由于客戶端在沒有收到「SYN,ACK」時,也會進行重傳,當(dāng)客戶端重傳的「SYN」收到后,會立即重新發(fā)送「SYN,ACK」包。
(3) 客戶端最后一次回復(fù)「SYN,ACK」的「ACK」包丟了。
如果最后一個「ACK」包丟了,服務(wù)端因為收不到「ACK」會走重傳機制,而客戶端此時進入 ESTABLISHED 狀態(tài)。
多數(shù)情況下,客戶端進入 ESTABLISHED 狀態(tài)后,則認(rèn)為連接已建立,會立即發(fā)送數(shù)據(jù)。但是服務(wù)端因為沒有收到最后一個「ACK」包,依然處于 SYN-RCVD 狀態(tài)。
那么這里的關(guān)鍵,就在于服務(wù)端在處于 SYN-RCVD 狀態(tài)下,收到客戶端的數(shù)據(jù)包后如何處理?
這也是比較有爭議的地方,有些資料里會寫到當(dāng)服務(wù)端處于 SYN-RCVD 狀態(tài)下,收到客戶端的數(shù)據(jù)包后,會直接回復(fù) RTS 包響應(yīng),表示服務(wù)端錯誤,并進入 CLOSE 狀態(tài)。
但是這樣的設(shè)定有些過于嚴(yán)格,試想一下,服務(wù)端還在通過三次握手階段確定對方是否真實存在,此時對方的數(shù)據(jù)已經(jīng)發(fā)來了,那肯定是存在的。
所以當(dāng)服務(wù)端處于 SYN-RCVD 狀態(tài)下時,接收到客戶端真實發(fā)送來的數(shù)據(jù)包時,會認(rèn)為連接已建立,并進入 ESTABLISHED 狀態(tài)。
那么實際情況,為什么會這樣呢?
當(dāng)客戶端在 ESTABLISHED 狀態(tài)下,開始發(fā)送數(shù)據(jù)包時,會攜帶上一個「ACK」的確認(rèn)序號,所以哪怕客戶端響應(yīng)的「ACK」包丟了,服務(wù)端在收到這個數(shù)據(jù)包時,能夠通過包內(nèi) ACK 的確認(rèn)序號,正常進入 ESTABLISHED 狀態(tài)。
(4) 客戶端故意不發(fā)最后一次「SYN」包。
前面一直在說正常的異常邏輯,雙方都還算友善,按規(guī)矩做事,出現(xiàn)異常主要也是因為網(wǎng)絡(luò)等客觀問題,接下來說一個惡意的情況。
如果客戶端是惡意的,在發(fā)送「SYN」包后,并收到「SYN,ACK」后就不回復(fù)了,那么服務(wù)端此時處于一種半連接的狀態(tài),雖然服務(wù)端會通過 tcp_synack_retries配置重試的次數(shù),不會無限等待下去,但是這也是有一個時間周期的。
如果短時間內(nèi)存在大量的這種惡意連接,對服務(wù)端來說壓力就會很大,這就是所謂的 SYN FLOOD 攻擊。
這就屬于安全攻防的范疇了,今天就不討論了,有興趣可以自行了解。
三、小結(jié)時刻
今天我們聊了 TCP 在建立連接的三次握手階段,出現(xiàn)異常,如何處理的一些事兒。
大多數(shù)情況下,都是依賴超時重傳來保證 TCP 的可靠性,但是重傳的次數(shù),狀態(tài)的轉(zhuǎn)換,這些細(xì)節(jié),就是本文的主題。