精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

從一次線上問題說起,詳解 TCP 半連接隊列、全連接隊列

網絡 通信技術
某次大促值班 ing,對系統穩(wěn)定性有著充分信心、心態(tài)穩(wěn)如老狗的筆者突然收到上游反饋有萬分幾的概率請求我們 endpoint 會出現 Connection timeout 。

[[431611]]

本文轉載自微信公眾號「云巔論劍」,作者黃剛。轉載本文請聯系云巔論劍公眾號。

前言

某次大促值班 ing,對系統穩(wěn)定性有著充分信心、心態(tài)穩(wěn)如老狗的筆者突然收到上游反饋有萬分幾的概率請求我們 endpoint 會出現 Connection timeout 。此時系統側的 apiserver 集群水位在 40%,離極限水位還有著很大的距離,當時通過緊急擴容 apiserver 集群后錯誤率降為了 0。事后進行了詳細的問題排查,定位分析到問題根因出現在系統連接隊列被打滿導致,之前筆者對 TCP 半連接隊列、全連接隊列不太了解,只依稀記得 《TCP/IP 詳解》中好像有好像提到過這兩個名詞。

目前網上相關資料都比較零散,并且有些是過時或錯誤的結論,筆者在調查問題時踩了很多坑。痛定思痛,筆者查閱了大量資料并做了眾多實驗進行驗證,梳理了這篇 TCP 半連接隊列、全連接詳解,當你細心閱讀完這篇文章后相信你可以對 TCP 半連接隊列、全連接隊列有更充分的認識。

本篇文章將結合理論知識、內核代碼、操作實驗為你呈現如下內容:

  • 半連接隊列、全連接隊列介紹
  • 常用命令介紹
  • 全連接隊列實戰(zhàn) —— 最大長度控制、全連接隊列溢出實驗、實驗結果分析...
  • 半連接隊列實戰(zhàn) —— 最大長度控制、半連接隊列溢出實驗、實驗結果分析...
  • ...

半連接隊列、全連接隊列

在 TCP 三次握手的過程中,Linux 內核會維護兩個隊列,分別是:

  • 半連接隊列 (SYN Queue)
  • 全連接隊列 (Accept Queue)

正常的 TCP 三次握手過程:

1、Client 端向 Server 端發(fā)送 SYN 發(fā)起握手,Client 端進入 SYN_SENT 狀態(tài)

2、Server 端收到 Client 端的 SYN 請求后,Server 端進入 SYN_RECV 狀態(tài),此時內核會將連接存儲到半連接隊列(SYN Queue),并向 Client 端回復 SYN+ACK

3、Client 端收到 Server 端的 SYN+ACK 后,Client 端回復 ACK 并進入 ESTABLISHED 狀態(tài)

4、Server 端收到 Client 端的 ACK 后,內核將連接從半連接隊列(SYN Queue)中取出,添加到全連接隊列(Accept Queue),Server 端進入 ESTABLISHED 狀態(tài)

5、Server 端應用進程調用 accept 函數時,將連接從全連接隊列(Accept Queue)中取出

半連接隊列和全連接隊列都有長度大小限制,超過限制時內核會將連接 Drop 丟棄或者返回 RST 包。

相關指標查看

ss 命令

通過 ss 命令可以查看到全連接隊列的信息

  1. # -n 不解析服務名稱 
  2. # -t 只顯示 tcp sockets 
  3. # -l 顯示正在監(jiān)聽(LISTEN)的 sockets 
  4.  
  5. $ ss -lnt 
  6. State      Recv-Q Send-Q    Local Address:Port         Peer Address:Port 
  7. LISTEN     0      128       [::]:2380                  [::]:* 
  8. LISTEN     0      128       [::]:80                    [::]:* 
  9. LISTEN     0      128       [::]:8080                  [::]:* 
  10. LISTEN     0      128       [::]:8090                  [::]:* 
  11.  
  12. $ ss -nt 
  13. State      Recv-Q Send-Q    Local Address:Port         Peer Address:Port 
  14. ESTAB      0      0         [::ffff:33.9.95.134]:80                   [::ffff:33.51.103.59]:47452 
  15. ESTAB      0      536       [::ffff:33.9.95.134]:80                  [::ffff:33.43.108.144]:37656 
  16. ESTAB      0      0         [::ffff:33.9.95.134]:80                   [::ffff:33.51.103.59]:38130 
  17. ESTAB      0      536       [::ffff:33.9.95.134]:80                   [::ffff:33.51.103.59]:38280 
  18. ESTAB      0      0         [::ffff:33.9.95.134]:80                   [:: 

對于 LISTEN 狀態(tài)的 socket

  • Recv-Q:當前全連接隊列的大小,即已完成三次握手等待應用程序 accept() 的 TCP 鏈接
  • Send-Q:全連接隊列的最大長度,即全連接隊列的大小

對于非 LISTEN 狀態(tài)的 socket

  • Recv-Q:已收到但未被應用程序讀取的字節(jié)數
  • Send-Q:已發(fā)送但未收到確認的字節(jié)數

相關內核代碼:

  1. // https://github.com/torvalds/linux/blob/master/net/ipv4/tcp_diag.c 
  2. static void tcp_diag_get_info(struct sock *sk, struct inet_diag_msg *r, 
  3.             void *_info) 
  4.   struct tcp_info *info = _info; 
  5.  
  6.   if (inet_sk_state_load(sk) == TCP_LISTEN) { // socket 狀態(tài)是 LISTEN 時 
  7.     r->idiag_rqueue = READ_ONCE(sk->sk_ack_backlog);  // 當前全連接隊列大小 
  8.     r->idiag_wqueue = READ_ONCE(sk->sk_max_ack_backlog); // 全連接隊列最大長度 
  9.   } else if (sk->sk_type == SOCK_STREAM) {    // socket 狀態(tài)不是 LISTEN 時 
  10.     const struct tcp_sock *tp = tcp_sk(sk); 
  11.  
  12.     r->idiag_rqueue = max_t(int, READ_ONCE(tp->rcv_nxt) - 
  13.                READ_ONCE(tp->copied_seq), 0);    // 已收到但未被應用程序讀取的字節(jié)數 
  14.     r->idiag_wqueue = READ_ONCE(tp->write_seq) - tp->snd_una;   // 已發(fā)送但未收到確認的字節(jié)數 
  15.   } 
  16.   if (info) 
  17.     tcp_get_info(sk, info); 

netstat 命令

通過 netstat -s 命令可以查看 TCP 半連接隊列、全連接隊列的溢出情況

  1. $ netstat -s | grep -i "listen" 
  2.     189088 times the listen queue of a socket overflowed 
  3.     30140232 SYNs to LISTEN sockets dropped 

上面輸出的數值是累計值,分別表示有多少 TCP socket 鏈接因為全連接隊列、半連接隊列滿了而被丟棄

  • 189088 times the listen queue of a socket overflowed 代表有 189088 次全連接隊列溢出
  • 30140232 SYNs to LISTEN sockets dropped 代表有 30140232 次半連接隊列溢出

在排查線上問題時,如果一段時間內相關數值一直在上升,則表明半連接隊列、全連接隊列有溢出情況

實戰(zhàn) —— 全連接隊列

全連接隊列最大長度控制

TCP 全連接隊列的最大長度由 min(somaxconn, backlog) 控制,其中:

  • somaxconn 是 Linux 內核參數,由 /proc/sys/net/core/somaxconn 指定
  • backlog 是 TCP 協議中 listen 函數的參數之一,即 int listen(int sockfd, int backlog) 函數中的 backlog 大小。在 Golang 中,listen 的 backlog 參數使用的是 /proc/sys/net/core/somaxconn 文件中的值。

相關內核代碼:

  1. // https://github.com/torvalds/linux/blob/master/net/socket.c 
  2.  
  3. /* 
  4.  *  Perform a listen. Basically, we allow the protocol to do anything 
  5.  *  necessary for a listen, and if that works, we mark the socket as 
  6.  *  ready for listening. 
  7.  */ 
  8. int __sys_listen(int fd, int backlog) 
  9.   struct socket *sock; 
  10.   int err, fput_needed; 
  11.   int somaxconn; 
  12.  
  13.   sock = sockfd_lookup_light(fd, &err, &fput_needed); 
  14.   if (sock) { 
  15.     somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;  // /proc/sys/net/core/somaxconn 
  16.     if ((unsigned int)backlog > somaxconn) 
  17.       backlog = somaxconn;   // TCP 全連接隊列最大長度 min(somaxconn, backlog) 
  18.  
  19.     err = security_socket_listen(sock, backlog); 
  20.     if (!err) 
  21.       err = sock->ops->listen(sock, backlog); 
  22.  
  23.     fput_light(sock->file, fput_needed); 
  24.   } 
  25.   return err; 

實驗

服務端 server 代碼

  1. package main 
  2.  
  3. import ( 
  4.   "log" 
  5.   "net" 
  6.   "time" 
  7.  
  8. func main() { 
  9.   l, err := net.Listen("tcp"":8888"
  10.   if err != nil { 
  11.     log.Printf("failed to listen due to %v", err) 
  12.   } 
  13.   defer l.Close() 
  14.   log.Println("listen :8888 success"
  15.  
  16.   for { 
  17.     time.Sleep(time.Second * 100) 
  18.   } 

在測試環(huán)境查看 somaxconn 的值為 128

  1. $ cat /proc/sys/net/core/somaxconn 
  2. 128 

啟動服務端,通過 ss -lnt | grep :8888 確認全連接隊列大小

  1. LISTEN     0      128       [::]:8888                  [::]:* 

全連接隊列最大長度為 128

現在更新 somaxconn 值為 1024,再重新啟動服務端。

1、更新 /etc/sysctl.conf 文件,該文件為內核參數配置文件

a.新增一行 net.core.somaxconn=1024

2、執(zhí)行 sysctl -p 使配置生效

  1. $ sudo sysctl -p 
  2. net.core.somaxconn = 1024 

3、檢查 /proc/sys/net/core/somaxconn 文件,確認 somaxconn 為更新后的 1024

  1. $ cat /proc/sys/net/core/somaxconn 
  2. 1024 

重新啟動服務端, 通過 ss -lnt | grep :8888 確認全連接隊列大小

  1. $ ss -lnt | grep 8888 
  2. LISTEN     0      1024      [::]:8888                  [::]:* 

可以看到,現在全鏈接隊列最大長度為 1024,成功更新。

全連接隊列溢出

下面來驗證下全連接隊列溢出會發(fā)生什么情況,可以通過讓服務端應用只負責 Listen 對應端口而不執(zhí)行 accept() TCP 連接,使 TCP 全連接隊列溢出。

實驗物料

服務端 server 代碼

  1. // server 端監(jiān)聽 8888 tcp 端口 
  2.  
  3. package main 
  4.  
  5. import ( 
  6.   "log" 
  7.   "net" 
  8.   "time" 
  9.  
  10. func main() { 
  11.   l, err := net.Listen("tcp"":8888"
  12.   if err != nil { 
  13.     log.Printf("failed to listen due to %v", err) 
  14.   } 
  15.   defer l.Close() 
  16.   log.Println("listen :8888 success"
  17.  
  18.   for { 
  19.     time.Sleep(time.Second * 100) 
  20.   } 

客戶端 client 代碼

  1. // client 端并發(fā)請求 10 次 server 端,成功建立 tcp 連接后向 server 端發(fā)送數據 
  2. package main 
  3.  
  4. import ( 
  5.   "context" 
  6.   "log" 
  7.   "net" 
  8.   "os" 
  9.   "os/signal" 
  10.   "sync" 
  11.   "syscall" 
  12.   "time" 
  13.  
  14. var wg sync.WaitGroup 
  15.  
  16. func establishConn(ctx context.Context, i int) { 
  17.   defer wg.Done() 
  18.   conn, err := net.DialTimeout("tcp"":8888"time.Second*5) 
  19.   if err != nil { 
  20.     log.Printf("%d, dial error: %v", i, err) 
  21.     return 
  22.   } 
  23.   log.Printf("%d, dial success", i) 
  24.   _, err = conn.Write([]byte("hello world")) 
  25.   if err != nil { 
  26.     log.Printf("%d, send error: %v", i, err) 
  27.     return 
  28.   } 
  29.   select { 
  30.   case <-ctx.Done(): 
  31.     log.Printf("%d, dail close", i) 
  32.   } 
  33.  
  34. func main() { 
  35.   ctx, cancel := context.WithCancel(context.Background()) 
  36.   for i := 0; i < 10; i++ { 
  37.     wg.Add(1) 
  38.     go establishConn(ctx, i) 
  39.   } 
  40.  
  41.   go func() { 
  42.     sc := make(chan os.Signal, 1) 
  43.     signal.Notify(sc, syscall.SIGINT) 
  44.     select { 
  45.     case <-sc: 
  46.       cancel() 
  47.     } 
  48.   }() 
  49.  
  50.   wg.Wait() 
  51.   log.Printf("client exit"

為了方便實驗,將 somaxconn 全連接隊列最大長度更新為 5:

1、更新 /etc/sysctl.conf 文件,將 net.core.somaxconn 更新為 5

2、執(zhí)行 sysctl -p 使配置生效

  1. $ sudo sysctl -p 
  2. net.core.somaxconn = 5 

實驗結果

客戶端日志輸出

  1. 2021/10/11 17:24:48 8, dial success 
  2. 2021/10/11 17:24:48 3, dial success 
  3. 2021/10/11 17:24:48 4, dial success 
  4. 2021/10/11 17:24:48 6, dial success 
  5. 2021/10/11 17:24:48 5, dial success 
  6. 2021/10/11 17:24:48 2, dial success 
  7. 2021/10/11 17:24:48 1, dial success 
  8. 2021/10/11 17:24:48 0, dial success 
  9. 2021/10/11 17:24:48 7, dial success 
  10. 2021/10/11 17:24:53 9, dial error: dial tcp 33.9.192.157:8888: i/o timeout 

客戶端 socket 情況

  1. tcp        0      0 33.9.192.155:40372      33.9.192.157:8888       ESTABLISHED 
  2. tcp        0      0 33.9.192.155:40376      33.9.192.157:8888       ESTABLISHED 
  3. tcp        0      0 33.9.192.155:40370      33.9.192.157:8888       ESTABLISHED 
  4. tcp        0      0 33.9.192.155:40366      33.9.192.157:8888       ESTABLISHED 
  5. tcp        0      0 33.9.192.155:40374      33.9.192.157:8888       ESTABLISHED 
  6. tcp        0      0 33.9.192.155:40368      33.9.192.157:8888       ESTABLISHED 

服務端 socket 情況

  1. tcp6      11      0 33.9.192.157:8888       33.9.192.155:40376      ESTABLISHED 
  2. tcp6      11      0 33.9.192.157:8888       33.9.192.155:40370      ESTABLISHED 
  3. tcp6      11      0 33.9.192.157:8888       33.9.192.155:40368      ESTABLISHED 
  4. tcp6      11      0 33.9.192.157:8888       33.9.192.155:40372      ESTABLISHED 
  5. tcp6      11      0 33.9.192.157:8888       33.9.192.155:40374      ESTABLISHED 
  6. tcp6      11      0 33.9.192.157:8888       33.9.192.155:40366      ESTABLISHED 
  7.  
  8. tcp    LISTEN     6      5      [::]:8888               [::]:*                   users:(("main",pid=84244,fd=3)) 

抓包結果

對客戶端、服務端抓包后,發(fā)現出現了三種情況,分別是:

  • client 成功與 server 端建立 tcp socket 連接,發(fā)送數據成功
  • client 認為成功與 server 端建立 tcp socket 連接,發(fā)送數據失敗,一直在 RETRY;server 端認為 tcp 連接未建立,一直在發(fā)送 SYN+ACK
  • client 向 server 發(fā)送 SYN 未得到響應,一直在 RETRY

全連接隊列實驗結果分析

上述實驗結果出現了三種情況,我們分別對抓包內容進行分析

情況一:Client 成功與 Server 端建立 tcp socket 鏈接,發(fā)送數據成功

上圖可以看到如下請求:

  • Client 端向 Server 端發(fā)送 SYN 發(fā)起握手
  • Server 端收到 Client 端 SYN 后,向 Client 端回復 SYN+ACK,socket 連接存儲到半連接隊列(SYN Queue)
  • Client 端收到 Server 端 SYN+ACK 后,向 Server 端回復 ACK,Client 端進入 ESTABLISHED 狀態(tài)
  • Server 端收到 Client 端 ACK 后,進入 ESTABLISHED 狀態(tài),socket 連接存儲到全連接隊列(Accept Queue)
  • Client 端向 Server 端發(fā)送數據 [PSH, ACK],Server 端確認接收到數據 [ACK]

這種情況就是正常的請求,即全連接隊列、半連接隊列未滿,client 成功與 server 建立了 tcp 鏈接,并成功發(fā)送數據。

情況二:Client 認為成功與 Server 端建立 tcp socket 連接,后續(xù)發(fā)送數據失敗,持續(xù) RETRY;Server 端認為 TCP 連接未建立,一直在發(fā)送SYN+ACK

上圖可以看到如下請求:

  • Client 端向 Server 端發(fā)送 SYN 發(fā)起握手
  • Server 端收到 Client 端 SYN 后,向 Client 端回復 SYN+ACK,socket 連接存儲到半連接隊列(SYN Queue)
  • Client 端收到 Server 端 SYN+ACK 后,向 Server 端回復 ACK,Client 端進入 ESTABLISHED狀態(tài)(重要:此時僅僅是 Client 端認為 tcp 連接建立成功)
  • 由于 Client 端認為 TCP 連接已經建立完成,所以向 Server 端發(fā)送數據 [PSH,ACK],但是一直未收到 Server 端的確認 ACK,所以一直在 RETRY
  • Server 端一直在 RETRY 發(fā)送 SYN+ACK

為什么會出現上述情況?Server 端為什么一直在 RETRY 發(fā)送 SYN+ACK?Server 端不是已經收到了 Client 端的 ACK 確認了嗎?

上述情況是由于 Server 端 socket 連接進入了半連接隊列,在收到 Client 端 ACK 后,本應將 socket 連接存儲到全連接隊列,但是全連接隊列已滿,所以 Server 端 DROP 了該 ACK 請求。

之所以 Server 端一直在 RETRY 發(fā)送 SYN+ACK,是因為 DROP 了 client 端的 ACK 請求,所以 socket 連接仍舊在半連接隊列中,等待 Client 端回復 ACK。

tcp_abort_on_overflow 參數控制

全連接隊列滿DROP 請求是默認行為,可以通過設置 /proc/sys/net/ipv4/tcp_abort_on_overflow 使 Server 端在全連接隊列滿時,向 Client 端發(fā)送 RST 報文。

tcp_abort_on_overflow 有兩種可選值:

  • 0:如果全連接隊列滿了,Server 端 DROP Client 端回復的 ACK
  • 1:如果全連接隊列滿了,Server 端向 Client 端發(fā)送 RST 報文,終止 TCP socket 鏈接 (TODO:后續(xù)有時間補充下該實驗)

為什么實驗結果中當前全連接隊列大小 > 全連接隊列最大長度配置?

上述結果中可以看到 Listen 狀態(tài)的 socket 鏈接:

  • Recv-Q 當前全連接隊列的大小是 6
  • Send-Q 全連接隊列最大長度是 5
  1. State      Recv-Q Send-Q    Local Address:Port         Peer Address:Port 
  2. LISTEN     6      5         [::]:8888                  [::]:* 

為什么全連接隊列大小 > 全連接隊列最大長度配置呢?

經過多次實驗發(fā)現,能夠進入全連接隊列的 Socket 最大數量始終比配置的全連接隊列最大長度 + 1。

結合其他文章以及內核代碼,發(fā)現內核在判斷全連接隊列是否滿的情況下,使用的是 > 而非 >= (具體是為什么沒有找到相關資源 : ) )。

相關內核代碼:

  1. /* Note: If you think the test should be: 
  2.  *  return READ_ONCE(sk->sk_ack_backlog) >= READ_ONCE(sk->sk_max_ack_backlog); 
  3.  * Then please take a look at commit 64a146513f8f ("[NET]: Revert incorrect accept queue backlog changes."
  4.  */ 
  5. static inline bool sk_acceptq_is_full(const struct sock *sk) 
  6.   return READ_ONCE(sk->sk_ack_backlog) > READ_ONCE(sk->sk_max_ack_backlog); 

情況三:Client 向 Server 發(fā)送 SYN 未得到相應,一直在 RETRY

圖片上圖可以看到如下請求:

  • Client 端向 Server 端發(fā)送 SYN 發(fā)起握手,未得到 Server 回應,一直在 RETRY

(這種情況涉及到半連接隊列,這里先給上述情況發(fā)生的原因結論,具體內容將在下文半連接隊列中展開。)

發(fā)生上述情況的原因由以下兩方面導致:

1、開啟了 /proc/sys/net/ipv4/tcp_syncookies 功能

2、全連接隊列滿了

實戰(zhàn) —— 半連接隊列

半連接隊列最大長度控制

翻閱了很多博文,查找關于半連接隊列最大長度控制的相關內容,大多含糊其辭或不準確,經過不懈努力,最終找到了比較確切的內容(相關博文鏈接在附錄中)。

很多博文中說半連接隊列最大長度由 /proc/sys/net/ipv4/tcp_max_syn_backlog 參數指定,實際上只有在 linux 內核版本小于 2.6.20 時,半連接隊列才等于 backlog 的大小。

這塊的源碼比較復雜,這里給一下大體的計算方式,詳細的內容可以參考附錄中的相關博文。半連接隊列長度的計算過程:

  1. backlog = min(somaxconn, backlog) 
  2. nr_table_entries = backlog 
  3. nr_table_entries = min(backlog, sysctl_max_syn_backlog) 
  4. nr_table_entries = max(nr_table_entries, 8) 
  5. // roundup_pow_of_two: 將參數向上取整到最小的 2^n,注意這里存在一個 +1 
  6. nr_table_entries = roundup_pow_of_two(nr_table_entries + 1) 
  7. max_qlen_log = max(3, log2(nr_table_entries)) 
  8. max_queue_length = 2^max_qlen_log 

可以看到,半連接隊列的長度由三個參數指定:

  • 調用 listen 時,傳入的 backlog
  • /proc/sys/net/core/somaxconn 默認值為 128
  • /proc/sys/net/ipv4/tcp_max_syn_backlog 默認值為 1024

我們假設 listen 傳入的 backlog = 128 (Golang 中調用 listen 時傳遞的 backlog 參數使用的是 /proc/sys/net/core/somaxconn),其他配置采用默認值,來計算下半連接隊列的最大長度

  1. backlog = min(somaxconn, backlog) = min(128, 128) = 128 
  2. nr_table_entries = backlog = 128 
  3. nr_table_entries = min(backlog, sysctl_max_syn_backlog) = min(128, 1024) = 128 
  4. nr_table_entries = max(nr_table_entries, 8) = max(128, 8) = 128 
  5. nr_table_entries = roundup_pow_of_two(nr_table_entries + 1) = 256 
  6. max_qlen_log = max(3, log2(nr_table_entries)) = max(3, 8) = 8 
  7. max_queue_length = 2^max_qlen_log = 2^8 = 256 

可以得到半隊列大小是 256。

判斷是否 Drop SYN 請求

當 Client 端向 Server 端發(fā)送 SYN 報文后,Server 端會將該 socket 連接存儲到半連接隊列(SYN Queue),如果 Server 端判斷半連接隊列滿了則會將連接 Drop 丟棄。

那么 Server 端是如何判斷半連接隊列是否滿的呢?除了上面一小節(jié)提到的半連接隊列最大長度控制外,還和 /proc/sys/net/ipv4/tcp_syncookies 參數有關。(tcp_syncookies 的作用是為了防止 SYN Flood 攻擊的,下文會給出相關鏈接介紹)

流程圖

判斷是否 Drop SYN 請求的流程圖:

上圖是整理了多份資料后,整理出來的判斷是否 Drop SYN 請求的流程圖。

注意:第一個判斷條件 「當前半連接隊列是否已超過半連接隊列最大長度」在不同內核版本中的判斷不一樣,Linux4.19.91 內核判斷的是當前半連接隊列長度是否 >= 全連接隊列最大長度。

相關內核代碼:

  1. static inline int inet_csk_reqsk_queue_is_full(const struct sock *sk) 
  2.   return inet_csk_reqsk_queue_len(sk) >= sk->sk_max_ack_backlog; 

我們假設如下參數,來計算下當 Client 端只發(fā)送 SYN 包,理論上 Server 端何時會 Drop SYN 請求:

  • 調用 listen 時傳入的 backlog = 1024
  • /proc/sys/net/core/somaxconn 值為 1024
  • /proc/sys/net/ipv4/tcp_max_syn_backlog 值為 128

當 /proc/sys/net/ipv4/tcp_syncookies 值為 0 時

  • 計算出的半連接隊列最大長度為 256
  • 當半連接隊列長度增長至 96 后,再新增 SYN 請求,就會觸發(fā) Drop SYN 請求

當 /proc/sys/net/ipv4/tcp_syncookies 值為 1 時

1.計算出的半連接隊列最大長度為 256

2.由于開啟了 tcp_syncookies

  • 當全連接隊列未滿時,永遠不會 Drop 請求 (注意:經實驗發(fā)現這個理論是錯誤的,實驗發(fā)現只要半連接隊列的大小 > 全連接隊列最大長度就會觸發(fā) Drop SYN 請求)
  • 當全連接隊列滿了后,即全連接隊列大小到 1024 后,就會觸發(fā) Drop SYN 請求

PS:/proc/sys/net/ipv4/tcp_syncookies 的取值還可以為 2,筆者沒有詳細實驗。

回顧全連接隊列實驗結果

在上文全連接隊列實驗中,有一類實驗結果是:client 向 Server 發(fā)送 SYN 未得到響應,一直在 RETRY。

發(fā)生上述情況的原因由以下兩方面導致:

1. 開啟了 /proc/sys/net/ipv4/tcp_syncookies 功能

2. 全連接隊列滿了

半連接隊列溢出實驗

上文我們已經知道如何計算理論上半連接隊列何時會溢出,下面我們來具體實驗下

(Golang 調用 listen 時傳入的 backlog 值為 somaxconn)

實驗一:syncookies=0,somaxconn=1024,tcp_max_syn_backlog=128

理論上:

  • 計算出的半連接隊列最大長度為 256
  • 當半連接隊列長度增長至 96 后,后續(xù) SYN 請求就會觸發(fā) Drop

將相關參數的配置更新

  1. $ sudo sysctl -p 
  2. net.core.somaxconn = 1024 
  3. net.ipv4.tcp_max_syn_backlog = 128 
  4. net.ipv4.tcp_syncookies = 0 

啟動服務端 Server 監(jiān)聽 8888 端口(代碼參考全連接隊列實驗物料)

客戶端 Client 發(fā)起 SYN Flood 攻擊:

  1. $ sudo hping3 -S 33.9.192.157 -p 8888 --flood 
  2. HPING 33.9.192.157 (eth0 33.9.192.157): S set, 40 headers + 0 data bytes 
  3. hping in flood mode, no replies will be shown 

查看服務端 Server 8888端口處于 SYN_RECV 狀態(tài)的 socket 最大個數:

  1. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  2. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  3. 96 
  4.  
  5. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  6. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  7. 96 

實驗結果符合預期,當半連接隊列長度增長至 96 后,后續(xù) SYN 請求就會觸發(fā) Drop。

實驗二:syncookies = 0,somaxconn=128,tcp_max_syn_backlog=512

理論上:

  • 計算出的半連接隊列最大長度為 256,由于筆者實驗機器上的內核版本是 4.19.91,所以當半連接隊列長度 >= 全連接隊列最大長度時,內核就認為半連接隊列溢出了
  • 所以當半連接隊列長度增長至 128 后,后續(xù) SYN 請求就會觸發(fā) DROP

將相關參數的配置更新

  1. $ sudo sysctl -p 
  2. net.core.somaxconn = 128 
  3. net.ipv4.tcp_max_syn_backlog = 512 
  4. net.ipv4.tcp_syncookies = 0 

啟動服務端 Server 監(jiān)聽 8888 端口(代碼參考全連接隊列實驗物料)

客戶端 Client 發(fā)起 SYN Flood 攻擊:

  1. $ sudo hping3 -S 33.9.192.157 -p 8888 --flood 
  2. HPING 33.9.192.157 (eth0 33.9.192.157): S set, 40 headers + 0 data bytes 
  3. hping in flood mode, no replies will be shown 

查看服務端 Server 8888端口處于 SYN_RECV 狀態(tài)的 socket 最大個數:

  1. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  2. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  3. 128 
  4.  
  5. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  6. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  7. 128 

實驗結果符合預期,當半連接隊列長度增長至 128 后,后續(xù) SYN 請求就會觸發(fā) Drop

實驗三:syncookies = 1,somaxconn=128,tcp_max_syn_backlog=512

理論上:

  • 當全連接隊列未滿,syncookies = 1,理論上 SYN 請求永遠不會被 Drop

將相關參數的配置更新

  1. $ sudo sysctl -p 
  2. net.core.somaxconn = 128 
  3. net.ipv4.tcp_max_syn_backlog = 512 
  4. net.ipv4.tcp_syncookies = 1 

啟動服務端 Server 監(jiān)聽 8888 端口(代碼參考全連接隊列實驗物料)

客戶端 Client 發(fā)起 SYN Flood 攻擊:

  1. $ sudo hping3 -S 33.9.192.157 -p 8888 --flood 
  2. HPING 33.9.192.157 (eth0 33.9.192.157): S set, 40 headers + 0 data bytes 
  3. hping in flood mode, no replies will be shown 

查看服務端 Server 8888端口處于 SYN_RECV 狀態(tài)的 socket 最大個數:

  1. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  2. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  3. 128 
  4.  
  5. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  6. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  7. 128 

實驗發(fā)現即使syncookies=1,當半連接隊列長度 > 全連接隊列最大長度時,就會觸發(fā) DROP SYN 請求!!!(TODO:有時間閱讀下相關內核源碼,再分析下)

繼續(xù)做實驗,將 somaxconn 更新為 5

  1. $ sudo sysctl -p 
  2. net.core.somaxconn = 5 
  3. net.ipv4.tcp_max_syn_backlog = 512 
  4. net.ipv4.tcp_syncookies = 1 

發(fā)起 SYN Flood 攻擊后,查看服務端 Server 8888端口處于 SYN_RECV 狀態(tài)的 socket 最大個數:

  1. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  2. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  3.  
  4. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  5. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 

確實 即使 syncookies=1,當半連接隊列長度 > 全連接最大長度時,就會觸發(fā) DROP SYN 請求。

實驗四:syncookies = 1,somaxconn=256,tcp_max_syn_backlog=128

理論上:

  • 當半連接隊列大小到 256 后,后觸發(fā) DROP SYN 請求

將相關參數的配置更新

  1. $ sudo sysctl -p 
  2. net.core.somaxconn = 256 
  3. net.ipv4.tcp_max_syn_backlog = 128 
  4. net.ipv4.tcp_syncookies = 1 

啟動服務端 Server 監(jiān)聽 8888 端口(代碼參考全連接隊列實驗物料)。

客戶端 Client 發(fā)起 SYN Flood 攻擊:

  1. $ sudo hping3 -S 33.9.192.157 -p 8888 --flood 
  2. HPING 33.9.192.157 (eth0 33.9.192.157): S set, 40 headers + 0 data bytes 
  3. hping in flood mode, no replies will be shown 

查看服務端 Server 8888端口處于 SYN_RECV 狀態(tài)的 socket 最大個數:

  1. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  2. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  3. 256 
  4.  
  5. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  6. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  7. 256 

實驗結果符合預期,當半連接隊列長度增長至 256 后,后續(xù) SYN 請求就會觸發(fā) Drop。

回顧線上問題

再回顧值班時遇到的 Connection timeout 問題,當時相關系統參數配置為:

  • net.core.somaxconn = 128
  • net.ipv4.tcp_max_syn_backlog = 512
  • net.ipv4.tcp_syncookies = 1
  • net.ipv4.tcp_abort_on_overflow = 0

所以出現 Connection timeout 有兩種可能情況:

1、半連接隊列未滿,全連接隊列滿,Client 端向 Server 端發(fā)起 SYN 被 DROP (參考全連接隊列實驗結果情況三分析、半連接隊列溢出實驗情況三)

2、全連接隊列未滿,半連接隊列大小超過全鏈接隊列最大長度(參考半連接隊列溢出實驗情況三、半連接隊列溢出實驗情況四)

問題的最快修復方式是將 net.core.somaxconn 調大,以及 net.ipv4.tcp_abort_on_overflow 設置為 1,net.ipv4.tcp_abort_on_overflow 設置為 1 是為了讓 client fail fast。

總結

半連接隊列溢出、全連接隊列溢出這類問題很容易被忽略,同時這類問題又很致命。當半連接隊列、全連接隊列溢出時 Server 端,從監(jiān)控上來看系統 cpu 水位、內存水位、網絡連接數等一切正常,然而卻會持續(xù)影響 Client 端業(yè)務請求。對于高負載上游使用短連接的情況,出現這類問題的可能性更大。

本文詳細梳理了 TCP 半連接隊列、全連接隊列的理論知識,同時結合 Linux 相關內核代碼以及詳細的動手實驗,講解了 TCP 半連接隊列、全連接隊列的相關原理、溢出判斷、問題分析等內容,希望大家在閱讀后可以對 TCP 半連接隊列、全連接隊列有更充分的認識。

PS:可以去線上檢查下服務器的相關參數喲~

附錄

這里羅列下相關參考博文資料:

Linux 源碼

  • https://github.com/torvalds/linux

Linux 詭異的半連接隊列長度

  • https://www.cnblogs.com/zengkefu/p/5606696.html

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么

  • https://www.cnblogs.com/xiaolincoding/p/12995358.html

一次 HTTP connect-timeout 排查

  • https://www.jianshu.com/p/3b9c4216b822

Connection Reset 排查

  • https://cjting.me/2019/08/28/tcp-queue/

深入淺出 TCP 中的 SYN-Cookies

  • https://segmentfault.com/a/1190000019292140

 

責任編輯:武曉燕 來源: 云巔論劍
相關推薦

2019-09-16 09:29:01

TCP全連接隊列半連接隊列

2015-04-23 18:46:38

TCPTCP協議

2023-04-06 07:53:56

Redis連接問題K8s

2018-07-05 14:25:01

TCP握手原理

2020-10-14 14:31:37

LinuxTCP連接

2024-01-19 19:22:45

TCPTIME_WAIT

2021-03-17 09:51:31

網絡編程TCP網絡協議

2020-01-18 14:11:13

數據庫線程技術

2019-11-17 22:11:11

TCPSYN隊列Accept隊列

2021-11-23 21:21:07

線上排查服務

2020-11-16 07:19:17

線上函數性能

2021-12-12 18:12:13

Hbase線上問題

2020-02-17 10:10:43

TCP三次握手四次揮手

2010-07-07 10:45:22

TCP UDP協議

2020-08-24 07:34:39

網絡超時請求

2021-10-14 20:33:16

TCP連接關閉

2023-11-29 12:12:24

Oceanbase數據庫

2012-07-02 13:26:28

電線連接

2014-08-22 09:10:46

2020-10-21 08:17:11

隊列數據
點贊
收藏

51CTO技術棧公眾號

久久综合九色综合久| 一本在线免费视频| а√天堂8资源中文在线| av成人老司机| 国产福利视频一区| 精品国产视频在线观看| 欧洲在线一区| 69久久夜色精品国产69蝌蚪网| 男人添女人下部视频免费| 欧美理论在线观看| 国产精品888| 日本国产精品视频| 亚洲色图综合区| 国产一区二区区别| 欧美成人激情免费网| 激情五月婷婷久久| 成年人黄色大片在线| 国产精品久久久久毛片软件| 狠狠色综合色区| 国产口爆吞精一区二区| 免费日韩av片| 久久久亚洲成人| 久久国产精品国语对白| 亚洲最大在线| 亚洲国内精品视频| 免费看的av网站| 成人涩涩视频| 狠狠色狠色综合曰曰| 一本大道东京热无码aⅴ| jyzzz在线观看视频| 91浏览器在线视频| 国产一区二区三区高清| 精品人妻少妇嫩草av无码专区| 久久久久久久欧美精品| 2021国产精品视频| 久久久久99精品成人片毛片| 欧美肥老太太性生活| 亚洲人成网站免费播放| av无码av天天av天天爽| 99re91这里只有精品| 欧美一区二区视频在线观看2022 | 国产永久免费高清在线观看视频| 成熟亚洲日本毛茸茸凸凹| 成人免费淫片aa视频免费| 中文字幕在线2018| 免费久久精品视频| 国产精品久久久久久久久久新婚 | 日韩精品另类天天更新| 婷婷丁香花五月天| 972aa.com艺术欧美| 国语精品免费视频| 午夜性色福利视频| 99久久精品国产观看| 国产v亚洲v天堂无码| 亚洲乱码精品久久久久..| 国产成人午夜99999| 666精品在线| а√天堂资源在线| 丁香婷婷深情五月亚洲| 99国产视频| 黄色www视频| 99精品黄色片免费大全| 美女一区视频| 国产视频精品久久| 中文字幕色av一区二区三区| 中文字幕在线观看一区二区三区| 国产在线高清视频| 亚洲最色的网站| 国产综合av在线| 国产伦精品一区二区三区视频金莲| 日韩欧美亚洲国产一区| 欧美日韩亚洲自拍| 成人在线视频区| 欧美成人精品二区三区99精品| fc2成人免费视频| 国产精品片aa在线观看| 一本一本久久a久久精品牛牛影视| 国产精品理论在线| 女同性一区二区三区人了人一| 欧美老少配视频| 国产欧美日韩另类| 天堂精品中文字幕在线| 成人性生交大片免费观看嘿嘿视频| 国产免费视频一区二区三区| 成年人午夜久久久| 欧美一区二区三区精美影视| 老司机午夜在线| 午夜日韩在线电影| www欧美激情| 97超碰成人| 亚洲视频在线观看免费| www.av免费| 免费亚洲婷婷| 99国产高清| www亚洲人| 亚洲午夜免费电影| 国产又黄又猛又粗| gogo人体一区| 日韩资源在线观看| 国产午夜精品无码| 捆绑调教一区二区三区| 激情五月综合色婷婷一区二区| 波多野结衣在线网站| 亚洲在线成人精品| 国产日韩欧美久久| 要久久爱电视剧全集完整观看| 日韩中文第一页| 黄网在线观看视频| 国产一本一道久久香蕉| 欧美一区二区影视| av3级在线| 69久久夜色精品国产69蝌蚪网| 美国黄色一级毛片| 国产精品www.| 国产综合视频在线观看| 狠狠狠综合7777久夜色撩人| 亚洲一区二区三区影院| 爱爱爱爱免费视频| 欧美女王vk| 91精品国产91久久| 亚洲风情第一页| 综合自拍亚洲综合图不卡区| 国产精品天天av精麻传媒| 精品素人av| 久久99久久99精品中文字幕| 国产有码在线观看| 中日韩免费视频中文字幕| 女性隐私黄www网站视频| 综合成人在线| 久精品免费视频| 国产免费无遮挡| 18成人在线观看| 天天干天天操天天玩| 婷婷综合电影| 国产91精品久久久久久| 天天操天天干天天插| 亚洲国产aⅴ天堂久久| 青娱乐精品在线| 在线观看国产精品入口| 成人观看高清在线观看免费| 在线观看av黄网站永久| 欧美丝袜自拍制服另类| 欧美丰满老妇熟乱xxxxyyy| 日韩精品亚洲一区| 色一情一乱一伦一区二区三区| 电影网一区二区| 亚洲精品资源美女情侣酒店| 日韩欧美成人一区二区三区| av资源网一区| 国产免费黄色av| 亚洲都市激情| 国产成人短视频| 国产一级网站视频在线| 欧美这里有精品| 日本免费www| 久久99久久久欧美国产| 日韩第一页在线观看| 伊人久久综合网另类网站| 久久影院免费观看| 亚洲精品免费在线观看视频| 亚洲国产精品自拍| 久久无码人妻精品一区二区三区| 欧美亚洲三区| 亚洲草草视频| 欧美a在线观看| 久久久久亚洲精品国产| 天天操天天舔天天干| 色综合视频在线观看| 亚洲无人区码一码二码三码的含义| 免费看欧美美女黄的网站| 国产高清免费在线| 精品av导航| 国产精品久久久久久久久久| 免费网站看v片在线a| 日韩亚洲欧美成人一区| 丰满少妇乱子伦精品看片| 国产欧美一区二区精品久导航 | 成人网在线播放| 国产精品免费成人| 91精品一区国产高清在线gif | 国产精品www网站| 久草资源在线观看| 亚洲高清色综合| 中文字幕人妻一区二区三区视频 | 操日韩av在线电影| 欧美一级免费片| 色悠悠亚洲一区二区| 日韩在线观看免| 99久久精品免费看| 老司机午夜性大片| 一区二区黄色| 国产福利片一区二区| 欧美顶级毛片在线播放| 国产原创欧美精品| 男人av在线播放| 久久亚洲精品国产亚洲老地址| 无码国产色欲xxxx视频| 欧美一区午夜精品| 999视频在线| 一级特黄大欧美久久久| 中文字幕黄色网址| 99国内精品久久| 日韩av影视大全| 日韩高清不卡在线| 日韩五码在线观看| 婷婷亚洲最大| 日产精品久久久一区二区| 88久久精品| 国产精品一区久久久| 久草在线中文最新视频| 久久av红桃一区二区小说| 精品乱码一区二区三四区视频| 精品国一区二区三区| 在线观看国产一区二区三区| 欧美日韩亚洲国产一区| 国产乱国产乱老熟300| 中文字幕的久久| 91精品人妻一区二区| 成人av电影在线观看| 一级日本黄色片| 九九在线精品视频| 天天碰免费视频| 性欧美videos另类喷潮| 日韩小视频网站| 欧美在线免费一级片| 中文字幕一区二区三区5566| 啪啪亚洲精品| 欧美亚洲爱爱另类综合| 美国成人xxx| 成人看片在线| 136国产福利精品导航网址应用| 国产在线播放不卡| 日本午夜精品久久久久| 国产精品免费视频xxxx| 日韩在线短视频| 国产精品大片wwwwww| 亚洲第一二三四区| 国产成人涩涩涩视频在线观看| 中文字幕在线看片| 亚洲3p在线观看| 爱啪啪综合导航| 国内免费久久久久久久久久久| 欧美1—12sexvideos| 色综合久综合久久综合久鬼88| www免费在线观看| 免费91在线视频| 97影院秋霞午夜在线观看| 欧美剧在线观看| 黄污视频在线观看| 韩国视频理论视频久久| 久久影院午夜精品| 欧美中文字幕在线| 欧美性xxx| 国产精品视频精品视频| 久久青草免费| 97在线中文字幕| 国产精品传媒| 蜜桃在线一区二区三区精品| 妖精视频一区二区三区| 亚洲va韩国va欧美va精四季| 欧美好骚综合网| 成人毛片100部免费看| 狠色狠色综合久久| 国模无码视频一区二区三区| 美女日韩在线中文字幕| 日韩爱爱小视频| 国产剧情一区二区三区| 亚洲欧洲日韩综合| 91美女视频网站| 亚洲毛片亚洲毛片亚洲毛片| 亚洲日本va午夜在线影院| 欧美日韩三级在线观看| 狠狠色狠色综合曰曰| 怡春院在线视频| 欧美成人精品福利| 精品三级久久久久久久电影聊斋| www.欧美三级电影.com| 蜜臀av在线| 国产99久久精品一区二区| 精品久久99| 国产一区二区三区黄| 欧美午夜精彩| 91成人综合网| 日韩av中文字幕一区二区三区| 北条麻妃亚洲一区| 91一区二区在线观看| 日本黄区免费视频观看| 亚洲一区二区免费视频| 国产情侣小视频| 日韩欧美色综合网站| 福利片在线观看| 欧美激情亚洲国产| 777午夜精品电影免费看| 成人情视频高清免费观看电影| 国产欧美日韩精品一区二区免费| dy888午夜| 久久精品1区| 95视频在线观看| 国产精品国产三级国产三级人妇 | 日产欧产美韩系列久久99| 少妇性l交大片7724com| 国产日韩三级在线| 国产主播在线播放| 欧美人妇做爰xxxⅹ性高电影| 日本人妻丰满熟妇久久久久久| 日韩中文字幕国产| 吞精囗交69激情欧美| 高清国产在线一区| 久久久久久久久国产一区| 国内外免费激情视频| 成人黄色777网| www色aa色aawww| 欧美曰成人黄网| 三区在线观看| 久久久久久97| 日韩中文字幕在线一区| 少妇特黄a一区二区三区| 国产精品亚洲产品| 久久免费精品国产| 一区二区三区中文字幕精品精品| 中文字幕欧美人妻精品| 亚洲欧洲中文天堂| 精品日韩在线观看| 免费在线观看黄视频| 欧美午夜精品理论片a级按摩| 欧美特黄一级视频| 欧美大片免费观看在线观看网站推荐| 成人久久网站| 日韩伦理一区二区三区av在线| 伊人成人在线| 亚洲无av在线中文字幕| 亚洲综合第一区| 高跟丝袜一区二区三区| 内射后入在线观看一区| 欧美激情国产日韩精品一区18| 国产精品国产三级在线观看| 亚洲一区二区三区精品在线观看| 视频一区国产视频| 9.1成人看片免费版| 欧美性猛交xxxx富婆弯腰| 天堂成人在线观看| 97精品国产97久久久久久| 99久久香蕉| 欧美久久久久久久久久久久久| 风流少妇一区二区| 日本少妇在线观看| 亚洲国产第一页| 午夜影院在线观看国产主播| 免费成人av网站| 爽爽淫人综合网网站| 瑟瑟视频在线观看| 在线观看国产一区二区| 浮生影视网在线观看免费| 国产精品免费电影| 忘忧草精品久久久久久久高清| wwwwwxxxx日本| 亚洲精品成人悠悠色影视| 国产sm主人调教女m视频| 欧美激情区在线播放| 欧美精品中文| 热久久精品免费视频| 国产精品久线在线观看| 99久久精品国产色欲| 久久久久久91| 欧美美乳视频| 91丝袜超薄交口足| 亚洲一区二区欧美| 久久久久国产精品嫩草影院| 国产精品国语对白| 中文字幕一区二区三三| 亚洲精品第二页| 在线中文字幕一区| 黄色精品免费看| 九九九九精品| 日本aⅴ亚洲精品中文乱码| 久久久久亚洲av片无码| 精品盗摄一区二区三区| 欧美大片1688| 亚洲永久激情精品| 成人精品在线视频观看| 日韩一级在线视频| 久久中文字幕一区| 全球av集中精品导航福利| 15—17女人毛片| 亚洲综合久久av| 国产69精品久久app免费版| 91青草视频久久| 香蕉亚洲视频| 国产老头老太做爰视频| 亚洲精品久久在线| 疯狂欧洲av久久成人av电影| 18禁免费观看网站| 中文字幕制服丝袜成人av| 人妻偷人精品一区二区三区| 国产精品久久久久久网站| 精品91久久久久| 少妇视频一区二区| 国产视频综合在线| 欧美成年网站| 国产九九在线视频|