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下配置。php

server端的徹底鏈接隊列(accpet隊列)

當第三次握手時,當server接收到ACK 報以後, 會進入一個新的叫 accept 的隊列,該隊列的長度爲 min(backlog, somaxconn),默認狀況下,somaxconn 的值爲 128,表示最多有 129 的 ESTAB 的鏈接等待 accept(),而 backlog 的值則應該是由 int listen(int sockfd, int backlog) 中的第二個參數指定,listen 裏面的 backlog 能夠有咱們的應用程序去定義的。html

當Client發送SYN包以後掛了(syn flood攻擊)

Client發送SYN包給Server後掛了,Server回給Client的SYN-ACK一直沒收到Client的ACK確認,這個時候這個鏈接既沒創建起來,也不能算失敗。這就須要一個超時時間讓Server將這個鏈接斷開,不然這個鏈接就會一直佔用Server的SYN鏈接隊列中的一個位置,大量這樣的鏈接就會將Server的SYN鏈接隊列耗盡,讓正常的鏈接沒法獲得處理。mysql

目前,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 來調整應對。linux

net.ipv4.tcp_synack_retries #內核放棄鏈接以前發送SYN+ACK包的數量
net.ipv4.tcp_syn_retries #內核放棄創建鏈接以前發送SYN包的數量nginx

爲了應對SYNflooding(即客戶端只發送SYN包發起握手而不迴應ACK完成鏈接創建,填滿server端的半鏈接隊列,讓它沒法處理正常的握手請求),Linux實現了一種稱爲SYNcookie的機制,經過net.ipv4.tcp_syncookies控制,設置爲1表示開啓。簡單說SYNcookie就是將鏈接信息編碼在ISN(initialsequencenumber)中返回給客戶端,這時server不須要將半鏈接保存在隊列中,而是利用客戶端隨後發來的ACK帶回的ISN還原鏈接信息,以完成鏈接的創建,避免了半鏈接隊列被攻擊SYN包填滿。sql

當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設置的值也會被忽略。segmentfault

Client端在屢次重發SYN包得不到響應而返回connection time out錯誤tomcat

查看服務器

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 peercookie

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

相關文章
相關標籤/搜索