TCP 半连接队列和全连接队列满了,怎么破?( 四 )


TCP 半连接队列和全连接队列满了,怎么破?

文章插图
从上面的代码中,我们可以算出 max_qlen_log 是 8,于是代入到 检测半连接队列是否满的函数 reqsk_queue_is_full :
TCP 半连接队列和全连接队列满了,怎么破?

文章插图
也就是 qlen >> 8 什么时候为 1 就代表半连接队列满了 。这计算并不难,很明显是当 qlen 为 256 时,256 >> 8 = 1 。
至此,总算知道为什么上面模拟测试 SYN 攻击的时候,服务端处于 SYN_RECV 连接最大只有 256 个 。
可见,半连接队列最大值不是单单由 max_syn_backlog 决定,还跟 somaxconn 和 backlog 有关系 。
在 Linux 2.6.32 内核版本,它们之间的关系,总体可以概况为:
TCP 半连接队列和全连接队列满了,怎么破?

文章插图
  • 当 max_syn_backlog > min(somaxconn, backlog) 时,半连接队列最大值 max_qlen_log = min(somaxconn, backlog) * 2;
  • 当 max_syn_backlog < min(somaxconn, backlog) 时,半连接队列最大值 max_qlen_log = max_syn_backlog * 2;
5、半连接队列最大值 max_qlen_log 就表示服务端处于 SYN_REVC 状态的最大个数吗?
依然很遗憾,并不是 。
max_qlen_log 是理论半连接队列最大值,并不一定代表服务端处于 SYN_REVC 状态的最大个数 。
在前面我们在分析 TCP 第一次握手(收到 SYN 包)时会被丢弃的三种条件:
  • 如果半连接队列满了,并且没有开启 tcp_syncookies,则会丢弃;
  • 若全连接队列满了,且没有重传 SYN+ACK 包的连接请求多于 1 个,则会丢弃;
  • 如果没有开启 tcp_syncookies,并且 max_syn_backlog 减去 当前半连接队列长度小于 (max_syn_backlog >> 2),则会丢弃;
假设条件1当前半连接队列的长度 「没有超过」理论的半连接队列最大值 max_qlen_log,那么如果条件 3 成立,则依然会丢弃 SYN 包,也就会使得服务端处于 SYN_REVC 状态的最大个数不会是理论值 max_qlen_log 。
似乎很难理解,我们继续接着做实验,实验见真知 。
服务端环境如下:
TCP 半连接队列和全连接队列满了,怎么破?

文章插图
配置完后,服务端要重启 Nginx,因为全连接队列最大和半连接队列最大值是在 listen 函数初始化 。
根据前面的源码分析,我们可以计算出半连接队列 max_qlen_log 的最大值为 256:
TCP 半连接队列和全连接队列满了,怎么破?

文章插图
客户端执行 hping3 发起 SYN 攻击:
TCP 半连接队列和全连接队列满了,怎么破?

文章插图
服务端执行如下命令,查看处于 SYN_RECV 状态的最大个数:
TCP 半连接队列和全连接队列满了,怎么破?

文章插图
可以发现,服务端处于 SYN_RECV 状态的最大个数并不是 max_qlen_log 变量的值 。
这就是前面所说的原因:如果当前半连接队列的长度 「没有超过」理论半连接队列最大值 max_qlen_log,那么如果条件 3 成立,则依然会丢弃 SYN 包,也就会使得服务端处于 SYN_REVC 状态的最大个数不会是理论值 max_qlen_log 。
我们来分析一波条件 3 :
TCP 半连接队列和全连接队列满了,怎么破?

文章插图
从上面的分析,可以得知如果触发「当前半连接队列长度 > 192」条件,TCP 第一次握手的 SYN 包是会被丢弃的 。
在前面我们测试的结果,服务端处于 SYN_RECV 状态的最大个数是 193,正好是触发了条件 3,所以处于 SYN_RECV 状态的个数还没到「理论半连接队列最大值 256」,就已经把 SYN 包丢弃了 。
所以,服务端处于 SYN_RECV 状态的最大个数分为如下两种情况:
  • 如果「当前半连接队列」没超过「理论半连接队列最大值」,但是超过 max_syn_backlog - (max_syn_backlog >> 2),那么处于 SYN_RECV 状态的最大个数就是 max_syn_backlog - (max_syn_backlog >> 2);
  • 如果「当前半连接队列」超过「理论半连接队列最大值」,那么处于 SYN_RECV 状态的最大个数就是「理论半连接队列最大值」;
6、每个 Linux 内核版本「理论」半连接最大值计算方式会不同 。
在上面我们是针对 Linux 2.6.32 版本分析的「理论」半连接最大值的算法,可能每个版本有些不同 。
比如在 Linux 5.0.0 的时候,「理论」半连接最大值就是全连接队列最大值,但依然还是有队列溢出的三个条件:
TCP 半连接队列和全连接队列满了,怎么破?

文章插图


推荐阅读