1. 程式人生 > >TCP半連線佇列和全連線

TCP半連線佇列和全連線

概述

  如上圖所示, 在TCP三次握手中,伺服器維護一個半連線佇列(sync queue) 和一個全連線佇列(accept queue)。  當服務端接收到客戶端第一次SYN握手請求時,將建立的request_sock結構,儲存在半連線佇列中(向客戶端傳送SYN+ACK,並期待客戶端響應ACK),此時的連線在伺服器端出於SYN_RECV狀態。當服務端收到客戶端最後的ACK確認時,將半連線中的相應條目刪除,然後將相應的連線放入 全連線佇列中, 此時服務端連線狀態為ESTABLISHED。 進入全連線佇列中的連線等待accept()呼叫取用。 

既然是佇列,肯定就有大小,那麼當這兩個佇列滿了沒有空間了怎麼辦呢? 例如如果我們listen()後不去accept() ,那麼全連線佇列肯定會滿的。 我們下面分別對於這兩個佇列結合試驗進行描述。

試驗環境: 

CentOS Linux release 7.5.1804 (Core)
Linux version 3.10.0-229.4.2.el7.x86_64

syns queue 半連線佇列

首先說一下 SYN flooding攻擊,為了應對SYN flooding(即客戶端只發送SYN包發起握手而不迴應ACK完成連線建立,快速填滿server端的半連線佇列,讓它無法處理正常的握手請求),Linux實現了一種稱為SYNcookie的機制,通過net.ipv4.tcp_syncookies控制,設定為1表示開啟。簡單說SYNcookie就是將連線資訊編碼在ISN(initialsequencenumber)中返回給客戶端,這時server不需要將半連線儲存在佇列中,而是利用客戶端隨後發來的ACK帶回的ISN還原連線資訊,以完成連線的建立,避免了半連線佇列被攻擊SYN包填滿。 也就是說,如果開啟了syncookies的話(通過 TCP引數 net.ipv4.tcp_syncookies配置 ),半連線佇列就相當於是無限大的了。在我的環境中就是預設開啟的。 

如果我們將syncookies關閉的話,半連線佇列的長度將為 max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog) ,,此時對半連線填滿時的處理策略是 server將 丟棄請求連線的SYN,不回覆SYN+ACK,這樣就會造成client收不到握手響應,始終處在SYN_SENT狀態,經過幾次重傳後,客戶端 connect() 呼叫失敗。

accept queue 全連線佇列

全連線佇列的長度為 min(backlog, somaxconn),預設情況下,somaxconn 的值為 128(/proc/sys/net/core/somaxconn),表示最多有 129 的 ESTAB 的連線等待 accept(),而 backlog 的值則是由 int listen(int sockfd, int backlog) 中的第二個引數指定,listen 裡面的 backlog 可以有我們的應用程式去定義。 當全連線佇列滿了後的處理策略基於TCP引數net.ipv4.tcp_abort_on_overflow,在我的機器上預設為0。 
  • tcp_abort_on_overflow 關閉時:

  當server收到最後一次ACK時,希望將連線從半連線佇列中取出放入全連線佇列,但是此時全連線佇列已滿,此時的策略是 將最後接收到的ACK丟棄,並且根據net.ipv4.tcp_synack_retries定義的次數重新向client傳送SYN+ACK, client在接收到重傳的SYN+ACK後會認為之前的ACK丟失了進而重傳ACK,這樣在下次重新接收到ACK後,如果全連線佇列有空間了,連線就可以正確完成建立。 如果重傳了規定次數後全連線佇列中依舊沒有空間,那麼server會簡單終止這次連線(這裡簡單終止的意思是server並沒有像client傳送RST表明連線無法建立,而是直接丟棄了,這樣就會導致在client中的連線處在ESTABLISHED狀態,並一直如此,後面的實驗會有涉及,我很困惑為什麼要這樣設計? 還是我沒有正確理解 !)。

  • tcp_abort_on_overflow 開啟時
  在收到握手的最後一次ACK後,在全連線中如果沒有空間,直接向client回覆RST,表示連線無法建立。