文章目錄 - 1. TCP封包段結構
- 2. 三次握手
- 3. SYN洪泛攻擊
- 4. 為什麼是“三次”
TCP是面向連接配接的(connection-oriented),即收發雙方在發送資料之前,必須首先建立一個連接配接,這樣在連接配接斷開之前,就一直使用這個連接配接傳輸資料。建立連接配接包括參數的設定、記憶體空間的配置設定,收發雙方參數的協商等,這一過程需要經過三次成功的溝通,一般叫做“三次握手” (a three-way handshake)。
用通俗的話來講,這三次溝通就是:
- 發起方:“你好,請問我可以跟你建立一個連接配接嗎?”(發送請求,等待回複)
- 接收方:“好啊,我準備好了,來吧。”
- 發起方:“好的,謝謝,我現在開始向你發送資料了。”
當然,在具體的實作過程中,還包含許多細節,以下一一闡述。
1. TCP封包段結構
要了解三次握手的過程中發送了什麼封包,首先得知道TCP封包段由哪些字段構成,其中哪些字段在這個過程中起了關鍵作用。

我們重點關注以下幾個字段:
- Sequence number:序列号,用來标記一個封包段的序号,封包段首位元組的位元組流編号
- Acknowledgment number:确認号,隻有ACK标志位為1時,确認序号字段才有效
- ACK:用于訓示确認号是有效的
- SYN:同步序列編号(Synchronize Sequence Numbers)
2. 三次握手
接下來更為仔細地觀察一條TCP連接配接是如何建立的。
假設運作在一台主機(客戶)上的程序想與另一台主機(伺服器)上的程序建立一條連接配接。客戶中的TCP會以如下的方式與伺服器中的TCP建立一條TCP連接配接:
- 第一步:用戶端的TCP向伺服器端的TCP發送一個特殊的TCP封包段,這個封包段不包含應用層資料,且其首部的SYN被置為1,這個特殊的封包段稱為SYN封包段。并且,客戶機随機選擇一個初始序号(client_isn, initial sequence number),并将該值放在序列号字段下。用戶端發送SYN封包段,并進入SYN_SENT狀态,等待伺服器确認。
- 第二步:一旦服務端收到該TCP SYN封包段(從SYN标記位為1可以判斷),會為該連接配接配置設定TCP緩存(buffers)和變量,并發送一個允許連接配接的封包段 (connection-granted segment)給客戶TCP。這個封包段也不包含應用層資料,并且SYN同樣被置為1,此外ACK标記位也為1,确認号為client_isn+1,并且選擇一個初始序号server_isn作為序列号字段的值。這個封包段被稱為SYNACK封包段。此時伺服器進入SYN_RECV狀态。
- 第三步:用戶端收到SYNACK封包段(通過其中的SYN,ACK,确認号可以判斷)之後,也為該連接配接配置設定緩存和變量。然後客戶機會再次向服務端發送一個确認封包,ACK标記位為1,确認号為server_isn+1,(這次SYN為0,SYN隻在前兩次握手中置為1),這次的封包段可以攜帶來自應用層的資料。此時用戶端進入ESTABLISHED狀态。服務端收到這個封包段後,也進入ESTABLISHED狀态,此時連接配接就算完全建立好了,雙方可以互相發送資料。
3. SYN洪泛攻擊
在上面的讨論中我們知道,伺服器收到一個SYN封包段時,配置設定并初始化連接配接變量和緩存,然後發送一個SYNACK進行響應。在收到來自用戶端的ACK封包段之前,連接配接并沒有完全建立,我們稱它為半開連接配接 (half-open connection)。如果客戶不發送ACK以完成三次握手的第三步,那麼伺服器會在一定時間内終止該半開連接配接,并回收配置設定的資源。
在這樣的協定下,很容易被一種叫做SYN洪泛攻擊 (SYN flood attack) 的拒絕服務攻擊 (Denial of Service (DoS) attack) 侵襲。攻擊者向伺服器發送大量的TCP SYN封包段,而不完成三次握手的第三步,這樣伺服器不斷為這些半開連接配接配置設定資源,導緻伺服器的連接配接資源消耗殆盡。
目前有一種防禦機制可以抵禦這種攻擊,稱為SYN cookie。
這種機制的思想在于,在收到SYN之後不馬上進行配置設定資源(因為怕了),而是在第三步時判斷連接配接的發起者是否為一個合法使用者,如果是,再配置設定資源并建立連接配接。
首先,當伺服器收到一個SYN時,不馬上配置設定資源,而是按如下方式生成一個初始的序列号:該序列号是 “SYN封包段中的源和目的IP位址與端口号以及一個隻有伺服器自己知道的秘密數 (secret number) ” 的hash值,也就是說,隻有知道這個秘密數,才可能算出這個序列号(這個初始序列号就稱為“cookie”)。然後伺服器就發送包含這個初始序列号的SYNACK。需要注意的是,伺服器此時不維護任何關于該SYN的狀态資訊,甚至不用記住這個cookie值。是以如果客戶沒有傳回一個ACK,那麼對伺服器來說就當什麼時都沒發生,現在SYN洪泛攻擊就做不成了。
那麼合法使用者是怎樣完成第三個步驟的呢?其實并沒有什麼改變,任然按照原來的方式進行,發送一個ACK給伺服器。此時需要動點手腳的是服務端,服務端怎麼判斷這個ACK封包是對之前的SYNACK的确認呢?很簡單,因為之前的SYNACK的序列号是根據“SYN封包段中的源和目的IP位址與端口号以及一個隻有伺服器自己知道的秘密數”算出來的,那麼這次如果還是那個使用者的話,那麼源和目的IP位址與端口号是不會變的,然後秘密數服務端也知道,用原來的hash函數一算,就得出來了該序列号,然後加1,看是不是跟這個ACK封包的确認号相等,如果相等,那說明這個ACK對應之前的SYNACK,是合法的,于是建立一個連接配接。
4. 為什麼是“三次”
首先,為什麼是三次握手而不是四次或者更多?這個問題是比較簡單的,因為既然三次能夠解決的問題,為什麼非要用四次來浪費資源?
但其實問題的重點在于,為什麼不能隻用兩次?第三次握手去掉不行嗎?
對于應對SYN洪泛攻擊的改進版的“三次握手”來說(見上文),第三次握手肯定是必須的,這個顯而易見。
那如果不考慮攻擊呢?兩次握手就能搞定嗎?
謝希仁版《計算機網絡》對這個問題進行了讨論。總的來說,三次握手是為了防止當已失效的連接配接請求封包段突然又傳到服務端,造成雙方的不一緻,導緻資源的浪費。
“已失效的連接配接請求封包段”指的是這樣的情況,用戶端發出一個SYN封包段,由于阻塞或者其他原因在網絡中滞留,以至于用戶端認為丢包了(其實并沒有丢),于是重新發出一個SYN封包段,假設這一次順利完成了,那麼雙方建立連接配接。這看起來似乎沒什麼問題,但網絡中有一個隐患,就是那個還在網絡中傳輸的SYN封包段,如果這個SYN在連接配接期間被服務端收到了,那服務端隻會無視它,這樣就萬事大吉了,但如果是在連接配接釋放之後被收到呢?此時服務端認為有人向他發出連接配接請求,于是響應一個SYNACK回去,如果采用兩次握手的話,那麼伺服器認為此時連接配接已經建立好了。但是當用戶端收到這個SYNACK時,如果他并沒有發起連接配接,那麼他不會理睬這個SYNACK,就當沒事發生過(如果用戶端此時正好發起連接配接,那其實他也不會理睬這個SYNACK,因為确認号不對啊。)。那問題就大了,這時候伺服器以為連接配接好了,向用戶端發送資料,而用戶端處于CLOSED狀态,會丢棄這些包,這樣就很浪費了。并且還有一個尴尬的問題,就是這個時候當用戶端打算發起連接配接時,服務端又不理睬了,在這裡尬這,他們就别想互發資料了。當然這些問題似乎不是不可解決的,當用戶端發現服務端老是向自己發資料,而自己總是丢棄,可能會向服務端發一個RST(封包段的RST标記号為1),強制服務端關閉連接配接。但資源總歸是浪費了一會了。而用三次握手就不會出現這樣的問題。