問題描述
你能否講解一下TCP
的三次握手與四次揮手呢?
面試官如果從整體到局部入手,那我們就先講講整個三次握手和四次揮手的過程,但不要忘記,講的同時應該適當體現你對該知識點掌握的深度和廣度,具體怎麼說,我們後面慢慢道來。
三次握手
所謂的握手
即一次發包到接收的過程,可能從客戶端發送到服務端,也可能從服務端發送到客戶端。
過程描述
先上一張TCP報文結構
圖,待會我們會回來看這張圖:
TCP報文結構
先上三次握手的流程圖:
三次握手
接下來我們來詳細講解下上圖的過程:
- 客戶主機發起連接請求,設置
SYN
標誌位爲1
,同時客戶端隨機
選擇了一個初始序號client_isn
,並且存放在TCP報文
字段的序號
中,如下圖:
第一次握手:SYN報文
- 接下來,當服務端接收到該報文後,會爲其分配
TCP 緩存和變量
(這使得TCP容易受到被稱爲SYN 洪泛攻擊
的拒絕服務攻擊)緊接着,服務端會返回一個SYNACK 報文
到客戶端,其中SYN
標誌位爲1
,確認號
設置爲client_isn + 1
,並且選一個自己的初始序號server_isn
,並放置在序號
字段中,如下圖:
第二次握手:SYNACK報文
- 當收到服務器發來的
SYNACK
報文段後,客戶端也需要給該連接分配緩存和變量,然後再次發送一個確認報文給服務端,其中,SYN
標誌位設置爲0
,將確認號
設置爲server_isn + 1
,另外,此次報文可以攜帶負載數據:
第三次握手:ACK報文
細節拓展
- 三次握手的狀態轉換圖(建議達到能默寫下來的熟練程度)
三次握手狀態圖
- 服務器爲什麼要使用特殊的初始序號
server_isn
?這爲什麼是必要的呢?
這個細節和問題深究第3題
是一致的,服務器使用特定的初始序列號 server_isn
(從源
和目的地IP
和端口
的散列
中獲取)可以用來抵禦SYN洪水攻擊。具體爲什麼,以及什麼是SYN 洪泛攻擊
,問題深究部分我們會再詳談。
爲了下文描述方便,我們將三次握手分別稱爲:SYN 報文
、SYNACK 報文
、ACK 報文
問題深究
1.爲什麼要三次握手而不是兩次?
簡單來說,三次握手的目的是爲了讓雙方驗證各自的接收能力和發送能力。
- 第一次握手,A 發送
SYN
到B
,B
接收到了後,能確認什麼呢? 顯然,B
能確認A
的發送
能力和B
的接收
能力; - 第二次握手,
B
發送SYNACK
到A
,A
接收到後,能確認什麼呢?A
能確認B
的發送能力和A
自己的接收能力,此外,A
收到了SYNACK
,那麼說明前面A
發的SYN
成功到達B
的手中,所以也能確認A
自己的發送
能力和B
的接收
能力;至此,A
已經確認了雙方各自的發送能力和接收能力都是OK
的,因此轉爲ESTABLISHED
狀態; - 第三次握手,
A
發送ACK
到B
,B
接收後,能確認什麼呢?
直接的,B
能確認A
的發送
能力和B
的接收
能力,另外由於B
能收到ACK
說明前面發送的SYNACK
已經成功被接受了,說明能確認A
的接收
能力和B
的發送
能力。
如果使用兩次握手,就不能確認上述所說的四種能力,那麼就會導致問題。
假定不採用第三次報文握手,那麼只要B發出確認,新的連接就建立了。
現假定一種異常情況,即A
發出的SYN
報文段並沒有丟失,而是在某些網絡節點長時間滯留了,以致延誤到連接釋放後的某個時間纔到達B
。本來這是一個早已失效的報文段。但B
收到此失效的連接請求報文段後,卻誤以爲是A
又發出一次新的連接請求,於是就向A
發出確認報文段,同意建立連接。
由於現在A
並沒有發出建立連接的請求,因此不會理睬B
的確認,也不會向B
發送數據,但B
卻以爲新的運輸連接已經建立了,並一直等待A
發來的數據。B
的許多資源就這樣白白浪費了。
2.兩個TCP建立請求相互之間同時發起時會發生什麼?建立幾個連接?
首先理解題意,我們知道三次握手
正是建立TCP連接
的過程,我們假設 A 是客戶端,B 是服務端:
rfc793-同時啓動
這裏先給大家講一下上圖中一些符號的含義:
- 右箭頭 (-->) :從 A 發送到 B 的 TCP 報文段,且 B 接收到了;
- 左箭頭 (<--) :從 B 發送到 A 的 TCP 報文段,且 A 接收到了;
- 省略號 (…) :TCP 報文段仍在網絡中(delayed);
- 丟失 ("XXX") :TCP 報文段丟失或者被拒絕。
- 註釋會放在括號中;
- TCP 狀態代表了處於中間的報文段到達之後的狀態(AFTER);
- 報文段的內容只顯示了序列號(SEQ)、控制符(CTL)和 ACK,其餘內容被省略。
接下來我們詳細來看看上圖中,兩個請求同時相互發起時,兩個TCP
均會經歷如下狀態的轉換:
同步請求的狀態轉換
上述的狀態轉換圖可以跟前面的三次握手的轉換圖進行對比理解,同時發起的兩個請求最終只會建立一個連接
。
SYN-RECEIVED
跟SYN-RCVD
是一樣的,前者rcf793
中的描述方法,後者是《計算機網絡-自頂向下方法》中的使用方法。
3. 客戶端正在和服務端建立 TCP 連接,然而當服務器變 SYN-RCVD 後,此時一箇舊的 SYN 報文 又到達了,服務器會如何處理?
其實這道題更加深挖了TCP 建立連接
的過程,我們可以在rfc793
中瞭解到詳細信息。
rfc793-RST
從上圖可以看到,第三行就是舊的SYN 連接
到達服務器時,第四行是服務器照常返回,第五行是客戶端給服務端發送RST 報文
,將服務端重置爲LISTEN
。
我們需要從上圖瞭解到的一點是,服務端在SYN_RECEIVED
狀態下,接收到舊的SYN 報文
時是不能作出判斷的,而是照常返回,當客戶端接收到該報文後發現異常,纔會發送RST 報文
,重置連接。
關於RST 報文
,我一開始也很疑惑,直到看到rfc793 原文
:
rfc793-page33
確實說明了TCP B
不能檢測這個舊的SYN 報文
是否正確,所以正常返回。而客戶端收到會進行檢測,發現是舊的報文,就會返回RST 報文
。
4.第三次握手失敗了怎麼辦?
這個問題在網上找到的答案質量參差不齊,翻閱了rfc793
,仔細研究後,最終整理出以下答案:
首先考慮失敗的情況:
ACK報文丟失導致第三次握手失敗
當客戶端收到服務端的SYNACK
應答後,其狀態變爲ESTABLISHED
,並會發送ACK
包給服務端,準備發送數據了。如果此時ACK
在網絡中丟失(如上圖所示),過了超時計時器後,那麼服務端會重新發送SYNACK
包,重傳次數根據/proc/sys/net/ipv4/tcp_synack_retries
來指定,默認是5
次。如果重傳指定次數到了後,仍然未收到ACK
應答,那麼一段時間後,Server
自動關閉這個連接。
問題就在這裏,客戶端已經認爲連接建立,而服務端則可能處在SYN-RCVD
或者CLOSED
,接下來我們需要考慮這兩種情況下服務端的應答:
- 服務端處於
CLOSED
,當接收到連接已經關閉的請求時,服務端會返回RST 報文
,客戶端接收到後就會關閉連接,如果需要的話則會重連,那麼那就是另一個三次握手了。 - 服務端處於
SYN-RCVD
,此時如果接收到正常的ACK 報文
,那麼很好,連接恢復,繼續傳輸數據;如果接收到寫入數據等請求呢?注意了,此時寫入數據等請求也是帶着ACK 報文
的,實際上也能恢復連接,使服務器恢復到ESTABLISHED
狀態,繼續傳輸數據。
這個結論也可以在STACKFLOW
上找到驗證:
What if a TCP handshake segment is lost?
上圖圈住的部分:
總的來說,如果一個ACK 報文
丟失了,但它的下一個數據包沒有丟失,那麼連接正常,否則,連接會被重置。
5.知道SYN攻擊嗎?如何防範?
所謂SYN 洪泛攻擊
,就是利用SYNACK 報文
的時候,服務器會爲客戶端請求分配緩存,那麼黑客(攻擊者),就可以使用一批虛假的ip
向服務器大量地發建立TCP 連接
的請求,服務器爲這些虛假ip
分配了緩存後,處在SYN_RCVD
狀態,存放在半連接隊列
中;另外,服務器發送的請求又不可能得到回覆(ip都是假的,能回覆就有鬼了),只能不斷地重發請求
,直到達到設定的時間/次數後,纔會關閉。
服務器不斷爲這些半開連接
分配資源(但從未使用),導致服務器的連接資源被消耗殆盡,不過所幸,我們可以使用SYN Cookie
進行有效地防禦。
所謂的SYN Cookie
防禦系統,與前面接收到SYN 報文
就分配緩存不同,此時暫不分配資源;同時利用SYN 報文
的源
和目的地IP
和端口
,以及服務器存儲的一個祕密數
,使用它們進行散列,得到server_isn
,然後附着在SYNACK 報文
中發送給客戶端,接下來就是對ACK 報文
進行判斷,如果其返回的ack
字段正好等於server_isn + 1
,說明這是一個合法的ACK
,那麼服務器纔會爲其生成一個具有套接字的全開的連接。
SYN Cookie 防禦
當然這種方案也有一定缺點
,最明顯的就是服務器不保存連接的半開狀態
,就喪失
了重發SYN-ACK消息
的能力,這一方面會降低正常用戶的連接成功率,另一方面會導致某些情況下正常通信的雙方會對連接是否成功打開產生誤解,如客戶端發給服務端的第三次握手消息(ACK
)半路遺失,客戶端認爲連接成功了,服務端認爲沒收到ACK
,連接沒成功,這種情況就需要上層應用採取策略特別處理了。
6.(ISN)是固定的嗎?
不固定,client_isn
是隨機生成的,而server_isn
則需要根據SYN 報文
中的源、ip和端口
,加上服務器本身的密碼數
進行相同的散列得到,顯然這也不是固定的。
7.三次握手過程中可以攜帶數據嗎?
講過程的時候其實已經講了,第三次握手是可以攜帶數據的
,而前兩次不行。
8. 關於 https 的認證過程?
限於篇幅,此處暫時不講,留意後續文章。
四次揮手
和握手
類似,每次揮手
也代表一次報文的發出和接收。
過程描述
因爲自頂向上這本書裏面的圖比較簡略,這裏採用謝希仁版本的計算機網絡中的圖:
計算機網絡-謝希仁-四次揮手
首先,當前客戶端和服務器的狀態都爲ESTABLISHED
,接下來我們詳細講解下上圖的過程:
- 客戶主機發起連接釋放的請求,設置
FIN
爲1
,當然,序號seq
也會帶上,這裏假設爲u
;發送完畢後,客戶端進入FIN-WAIT-1
狀態。
第一次揮手:FIN報文
- 服務端接收到
FIN 報文
後,會返回一個ACK 報文
回去,此時設置ACK
爲1
,確認號
爲u + 1
;表明自己接受到了客戶端關閉連接的請求,但還沒有準備好關閉連接。發送完畢後,服務器端進入CLOSE-WAIT
狀態,客戶端接收到這個確認包之後,進入FIN-WAIT-2
狀態,等待服務器端關閉連接。
第二次揮手:ACK報文
- 服務器端準備好關閉連接時,向客戶端發送結束連接請求,
FIN
置爲1
;發送完畢後,服務器端進入LAST-ACK
狀態,等待來自客戶端的最後一個ACK
。
第三次揮手:FIN報文
- 客戶端接收到服務端傳來的
FIN 報文
後,知道服務器已經準備好關閉了,發送一個確認包,並進入TIME-WAIT
狀態,等待可能出現的要求重傳的ACK 報文
;服務器端接收到這個確認包之後,關閉連接,進入CLOSED
狀態。
客戶端等待了某個固定時間(兩個最大段生命週期,2MSL
,2 Maximum Segment Lifetime)之後,沒有收到服務器端的ACK
,認爲服務器端已經正常關閉連接,於是自己也關閉連接,進入CLOSED
狀態。
第四次揮手:ACK報文
這裏我們再來看下rfc793
中關於四次揮手的簡單例子:
rfc793-正常的關閉例子
細節拓展
- 四次揮手重要的是
TIME-WAIT
狀態,爲什麼需要這個狀態呢?
要確保服務器是否已經收到了我們的ACK 報文
,如果沒有收到的話,服務器會重新發FIN 報文
給客戶端,那麼客戶端再次收到FIN 報文
之後,就知道之前的 ACK 報文
丟失了,就會再次發送ACK 報文
。
問題深究
1.爲什麼握手只要三次,揮手卻要四次?
關鍵就在中間兩步。
- 建立連接時,當服務器收到客戶端的
SYN 報文
後,可以直接發送SYNACK 報文
。其中ACK
是用來應答的,SYN
是用來同步的。 - 但是關閉連接時,當服務器收到
FIN 報文
時,很可能並不會立即關閉SOCKET
,所以只能先回復一個ACK 報文
,告訴客戶端,“你發的FIN 報文
我收到了”。只有等到服務器所有的報文都發送/接收完了,我才能發送FIN 報文
,因此不能一起發送,需要四次握手。
2.爲什麼 TIME_WAIT 狀態需要經過 2MSL 才能轉換到 CLOSE 狀態?
- 第一,爲了保證客戶端發送的最後一個
ACK 報文
能夠到達服務器。我們必須假設網絡是不可靠的,ACK 報文
可能丟失。如果服務端發出FIN 報文
後沒有收到ACK 報文
,就會重發FIN 報文
,此時處於TIME-WAIT
狀態的客戶端就會重發ACK 報文
。當然,客戶端也不能無限久的等待這個可能存在的FIN 報文
,因爲如果服務端正常接收到了ACK 報文
後是不會再發FIN 報文
的。因此,客戶端需要設置一個計時器,那麼等待多久最合適呢?所謂的MSL
(Maximum Segment Lifetime)指一個報文在網絡中最大的存活時間,2MSL就是一個發送和一個回覆所需的最大時間。如果直到2MSL
時間後,客戶端都沒有再次收到FIN 報文
,那麼客戶端推斷ACK 報文
已經被服務器成功接收,所以結束TCP 連接
。 - 第二,防止已失效的連接請求報文段出現在新的連接中。客戶端在發送完最後一個
ACK 報文
後,再經過時間2MSL
,就可以使由於網絡不通暢產生的滯留報文段失效。這樣下一個新的連接中就不會出現舊的連接請求報文。