tcp的半連接與完全連接隊列(二)
隊列及參數
server端的半連接隊列(syn隊列)
在三次握手協議中,服務器維護一個半連接隊列,該隊列為每個客戶端的SYN包開設一個條目(服務端在接收到SYN包的時候,就已經創建了request_sock結構,存儲在半連接隊列中),該條目表明服務器已收到SYN包,並向客戶發出確認,正在等待客戶的確認包(會進行第二次握手發送SYN+ACK 的包加以確認)。這些條目所標識的連接在服務器處於Syn_RECV狀態,當服務器收到客戶的確認包時,刪除該條目,服務器進入ESTABLISHED狀態。
該隊列為SYN 隊列,長度為 max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog) ,在機器的tcp_max_syn_backlog值在/proc/sys/net/ipv4/tcp_max_syn_backlog下配置。
server端的完全連接隊列(accpet隊列)
當第三次握手時,當server接收到ACK 報之後, 會進入一個新的叫 accept 的隊列,該隊列的長度為 min(backlog, somaxconn),默認情況下,somaxconn 的值為 128,表示最多有 129 的 ESTAB 的連接等待 accept(),而 backlog 的值則應該是由 int listen(int sockfd, int backlog) 中的第二個參數指定,listen 裏面的 backlog 可以有我們的應用程序去定義的。
當Client發送SYN包之後掛了(syn flood攻擊
)
Client發送SYN包給Server後掛了,Server回給Client的SYN-ACK一直沒收到Client的ACK確認,這個時候這個連接既沒建立起來,也不能算失敗。這就需要一個超時時間讓Server將這個連接斷開,否則這個連接就會一直占用Server的SYN連接隊列中的一個位置,大量這樣的連接就會將Server的SYN連接隊列耗盡,讓正常的連接無法得到處理。
目前,Linux下默認會進行5次重發SYN-ACK包,重試的間隔時間從1s開始,下次的重試間隔時間是前一次的雙倍,5次的重試時間間隔為1s, 2s, 4s, 8s, 16s,總共31s,第5次發出後還要等32s都知道第5次也超時了,所以,總共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 63s,TCP才會把斷開這個連接。由於,SYN超時需要63秒,那麽就給攻擊者一個攻擊服務器的機會,攻擊者在短時間內發送大量的SYN包給Server(俗稱 SYN flood 攻擊),用於耗盡Server的SYN隊列。對於應對SYN 過多的問題,linux提供了幾個TCP參數:tcp_syncookies、tcp_synack_retries、tcp_max_syn_backlog、tcp_abort_on_overflow 來調整應對。
net.ipv4.tcp_synack_retries #內核放棄連接之前發送SYN+ACK包的數量
net.ipv4.tcp_syn_retries #內核放棄建立連接之前發送SYN包的數量為了應對SYNflooding(即客戶端只發送SYN包發起握手而不回應ACK完成連接建立,填滿server端的半連接隊列,讓它無法處理正常的握手請求),Linux實現了一種稱為SYNcookie的機制,通過net.ipv4.tcp_syncookies控制,設置為1表示開啟。簡單說SYNcookie就是將連接信息編碼在ISN(initialsequencenumber)中返回給客戶端,這時server不需要將半連接保存在隊列中,而是利用客戶端隨後發來的ACK帶回的ISN還原連接信息,以完成連接的建立,避免了半連接隊列被攻擊SYN包填滿。
當syn隊列滿的情況(tcp_abort_on_overflow
)
對於SYN半連接隊列的大小是由(/proc/sys/net/ipv4/tcp_max_syn_backlog)這個內核參數控制的,有些內核似乎也受listen的backlog參數影響,取得是兩個值的最小值。當這個隊列滿了,不開啟syncookies的時候,Server會丟棄新來的SYN包,而Client端在多次重發SYN包得不到響應而返回(connection time out
)錯誤。但是,當Server端開啟了syncookies=1,那麽SYN半連接隊列就沒有邏輯上的最大值了,並且/proc/sys/net/ipv4/tcp_max_syn_backlog設置的值也會被忽略。
Client端在多次重發SYN包得不到響應而返回
connection time out
錯誤
查看
netstat -s | grep LISTEN
4375 SYNs to LISTEN sockets dropped
當accept隊列滿的情況
當accept隊列滿了之後,即使client繼續向server發送ACK的包,也會不被響應,此時ListenOverflows+1,同時server通過/proc/sys/net/ipv4/tcp_abort_on_overflow來決定如何返回,0表示直接丟棄該ACK,1表示發送RST通知client;相應的,client則會分別返回read timeout
或者 connection reset by peer
。
client則會分別返回
read timeout
或者connection reset by peer
查看
root@b5dbe93bcb04:/opt# netstat -s | grep listen
22438 times the listen queue of a socket overflowed
accept隊列滿了,對 syn隊列也有影響,在代碼 net/ipv4/tcp_ipv4.c :
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
/*tcp_syncookies為2 進行syn cookie
tcp_syncookies為1 且request隊列滿了 進行syn cookie處理
tcp_syncookies為0 且request隊列滿了 將該syn報文drop掉
*/
if ((sysctl_tcp_syncookies == 2 ||
inet_csk_reqsk_queue_is_full(sk)) && !isn) {
want_cookie = tcp_syn_flood_action(sk, skb, "TCP");
if (!want_cookie)
goto drop;
}
/* Accept backlog is full. If we have already queued enough
* of warm entries in syn queue, drop request.
*/
if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) {
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
goto drop;
}
accept隊列大多數情況下會比較小,所以會出現SYN 隊列沒有滿,而ACCEPT 隊列滿了的情況,此時會按照tcp_aborton_overflow來決定直接丟棄,還是返回拒絕RST。 而如果啟用了syncookies,那麽syncookies會開啟,限制SYN包進入的速度。
當系統丟棄最後的 ACK,而系統中還有一個 net.ipv4.tcp_synack_retries 設置時,Linux 會重新發送 SYN ACK 包。而客戶端收到多個 SYN ACK 包,則會認為之前的 ACK 丟包了。於是促使客戶端再次發送 ACK ,在 accept隊列有空閑的時候最終完成連接。若 accept隊列始終滿員,則最終客戶端收到 RST 包。
doc
-
Linux SYN Backlog and somaxconn
-
TCP/IP協議中backlog參數
-
TCP SOCKET中backlog參數的用途是什麽?
-
了解tcp底層知識
-
再理解tcp backlog
-
Linux TCP隊列相關參數的總結
-
TCP全連接隊列和半連接隊列已滿之後的連接建立過程抓包分析
-
linux、mysql、nginx、tomcat 環境下壓力測試的主要調試參數
-
從TCP三次握手說起–淺析TCP協議中的疑難雜癥(1)
-
從TCP三次握手說起–淺析TCP協議中的疑難雜癥(2)
tcp的半連接與完全連接隊列(二)