TCP 的三次握手,四次揮手和重要的細節—乾貨滿滿,建議細讀

最近把我的博客搭建好了,連接在這裏:tobe的囈語,文章會先在博客和公衆號更新~ 你們多多收藏啊php

上一次講了 UDP 協議,從此次開始,就要講 TCP 協議了,由於 TCP 協議涉及到的東西不少,一篇文章歸納不完,因此我把 TCP 協議的內容分紅好幾個部分,逐個擊破。html

TCP 報文段結構

一談到 TCP 協議,你們最早想到的詞就是「面向鏈接」和「可靠」。沒錯,TCP 協議的設計就是爲了可以在客戶端和服務器之間創建起一個可靠鏈接。linux

在講鏈接過程以前,咱們先來看看 TCP 的報文段結構,經過這個結構,咱們能夠知道 TCP 可以提供什麼信息:安全

這裏有幾點是須要注意的:服務器

  • TCP 協議須要一個四元組(源IP,源端口,目的IP,目的端口)來肯定鏈接,這要和 UDP 協議區分開。多說一句,IP 地址位於 IP 報文段,TCP 報文段是不含 IP 地址信息的。
  • 基本 TCP 頭部的長度是 20 字節,可是因爲「選項」的長度是不肯定的,因此須要「首部長度」字段明確給出頭部長度。這裏要注意的是,首部長度字段的單位是 32bit,也就是 4 字節,因此該字段的最小值是 5。
  • 標橙色的字段(確認序號,接收窗口大小,ECE,ACK)用於「回覆」對方,舉個例子,服務器收到對方的數據包後,不單獨發一個數據包來回應,而是稍微等一下,把確認信息附在下一個發往客戶端的數據幀上,也就是捎帶技術。
  • 窗口大小是一個 16 位無符號數,也就是說窗口被限制在了 65535 字節,也就限制了 TCP 的吞吐量性能,這對一些高速以及高延遲的網絡不太友好(能夠想一想爲何)。所幸 TCP 額外提供了窗口縮放(Window Scale)選項,容許對這個值進行縮放。

下面是 8 個標誌位的含義,有的協議比較舊,可能沒有前兩個標誌位:cookie

標誌位雖然不少,可是若是放到具體場景裏來看的話,就很容易理解他們的做用了。網絡

TCP 三次握手

三次握手就是爲了在客戶端和服務器間創建鏈接,這個過程並不複雜,但裏面有不少細節須要注意。app

這張圖就是握手的過程,能夠看到客戶端與服務器之間一共傳遞了三次消息,這三次握手其實就是兩臺機器之間互相確認狀態,咱們來一點一點看。dom

第一次握手

首先是客戶端發起鏈接,第一個數據包將 SYN 置位(也就是 SYN = 1),代表這個數據包是 SYN 報文段(也被稱爲段 1)。這一次發送的目的是告訴服務器,本身的初始序列號client_isn ,還有一個隱含的信息在圖裏沒有表現出來,那就是告知服務端本身想鏈接的端口號。除了這些,客戶端還會發送一些選項,不過這跟三次握手沒多大關係,暫且按下不表。tcp

段 1 裏最須要注意的就是這個client_isn ,也就是初始序列號。「RFC0793^1」指出:

When new connections are created, an initial sequence number (ISN) generator is employed which selects a new 32 bit ISN. The generator is bound to a (possibly fictitious) 32 bit clock whose low order bit is incremented roughly every 4 microseconds. Thus, the ISN cycles approximately every 4.55 hours.

翻譯過來就是,初始序列號是一個 32 位的(虛擬)計數器,並且這個計數器每 4 微秒加 1,也就是說,ISN 的值每 4.55 小時循環一次。這個舉措是爲了防止序列號重疊

但即便這樣仍是會有安全隱患——由於初始 ISN 仍然是可預測的,惡意程序可能會分析 ISN ,而後根據先前使用的 ISN 預測後續 TCP 鏈接的 ISN,而後進行攻擊,一個著名的例子就是「The Mitnick attack^2」 。這裏摘一段原文:

Mitnick sent SYN request to X-Terminal and received SYN/ACK response. Then he sent RESET response to keep the X-Terminal from being filled up. He repeated this for twenty times. He found there is a pattern between two successive TCP sequence numbers. It turned out that the numbers were not random at all. The latter number was greater than the previous one by 128000.

因此爲了讓初始序列號更難預測,現代系統經常使用半隨機的方法選擇初始序列號,詳細的方法就不在這裏展開了。

第二次握手

當服務器接收到客戶端的鏈接請求後,就會向客戶端發送 ACK 表示本身收到了鏈接請求,並且,服務器還得把本身的初始序列號告訴客戶端,這實際上是兩個步驟,可是發送一個數據包就能夠完成,用的就是前面說的捎帶技術。圖裏的 ACK = client_isn + 1 是指確認號字段的值,要注意和 ACK 標誌位區分開。

ACK 字段其實也有很多須要注意的點,不過這個跟滑動窗口一塊講比較直觀,這裏就先不提了。

這裏重點強調一下,當一個 SYN 報文段到達的時候,服務器會檢查處於 SYN_RCVD 狀態的鏈接數目是否超過了 tcp_max_syn_backlog 這個參數,若是超過了,服務器就會拒絕鏈接。固然,這個也會被黑客所利用,「SYN Flood」就是個很好的例子。由於服務器在回覆 SYN-ACK 後,會等待客戶端的 ACK ,若是必定時間內沒有收到,認爲是丟包了,就重發 SYN-ACK,重複幾回後纔會斷開這個鏈接,linux 可能要一分鐘纔會斷開,因此攻擊者若是製造一大批 SYN 請求而不回覆,服務器的 SYN 隊列很快就被耗盡,這一段時間裏,正常的鏈接也會得不到響應。

服務器的這種狀態稱爲靜默(muted)。爲了抵禦 SYN Flood 攻擊,服務器能夠採用「SYN cookies」,這種思想是,當 SYN 到達時,並不直接爲其分配內存,而是把這條鏈接的信息編碼並保存在 SYN-ACK 報文段的序列號字段,若是客戶端回覆了,服務器再從 ACK 字段裏解算出 SYN 報文的重要信息(有點黑魔法的感受了),驗證成功後才爲該鏈接分配內存。這樣,服務器不會響應攻擊者的請求,正常鏈接則不會受到影響。

但 SYN cookies 自己有一些限制,並不適合做爲默認選項,有興趣能夠自行 Google。

第三次握手

這是創建 TCP 鏈接的最後一步,通過前兩次握手,客戶端(服務器)已經知道對方的滑動窗口大小初始序列號等信息了,這不就完了嗎?爲何還要第三次握手?

這是由於服務器雖然把數據包發出去了,但他還不知道客戶端是否收到了這個包,因此服務器須要等待客戶端返回一個 ACK,代表客戶端收到了數據,至此,鏈接完成。

鏈接創建後,進入傳輸數據的階段,這裏就涉及到不少不少技術,我會另寫文章。

四次揮手

有了三次握手的基礎,四次揮手就比較容易理解了:

四次揮手的過程其實很簡單,就是服務器和客戶端互相發送 FIN 和 ACK 報文段,告知對方要斷開鏈接。

四次揮手裏值得關注的一點就是 TIME_WAIT 狀態,也就是說主動關閉鏈接的一方,即便收到了對方的 FIN 報文,也還要等待 2MSL 的時間纔會完全關閉這條鏈接。(這裏面的 MSL 指的是最大段生成期,指的是報文段在網絡中被容許存在的最長時間。)可爲何不直接關閉鏈接呢

一個緣由是,第四次揮手的 ACK 報文段不必定到達了服務器,爲了避免讓服務器一直處於 LAST_ACK 狀態(服務器會重發 FIN,直到收到 ACK),客戶端還得等一下子,看看是否須要重發。假如真的丟包了,服務器發送 FIN ,這個 FIN 報文到達客戶端時不會超過 2MSL(一來一回最多 2MSL),這時候客戶端這邊的 TCP 還沒關掉,還能重發 ACK。

另外一個緣由是,通過 2MSL 以後,網絡中與該鏈接相關的包都已經消失了,不會干擾新鏈接。咱們來看一個例子:假如客戶端向服務器創建了新的鏈接舊鏈接中某些延遲的數據堅持到了新鏈接創建完畢,並且序列號恰好還在滑動窗口內,服務器就誤把它當成新鏈接的數據包接收,以下圖所示:

2MSL 機制就避免了這種狀況。

關於 TIME_WAIT 還有不少有意思的地方,我以爲能夠單獨再寫一篇文章了,這裏就再也不多說。

感受寫的有點亂了,由於 TCP 的知識確實是有點多,但願各位讀者不要介意。

若是本文對你有幫助,歡迎關注個人公衆號 tobe的囈語 ,帶你深刻計算機的世界~ 公衆號後臺回覆關鍵詞【計算機】有驚喜哦~

相關文章
相關標籤/搜索