我们先对文章内容的总结:
tcp_max_syn_backlog
、net.core.somaxconn
和listen(fd, backlog)
的backlog
三者最小值决定。net.core.somaxconn
和listen(fd, backlog)
的backlog
两者最小值决定。net.core.somaxconn
和tcp_max_syn_backlog
参数来增加队列大小。somaxconn
参数来增加队列大小,并根据tcp_abort_on_overflow
参数决定是丢弃ACK包还是发送RST包给客户端。hping3是一个基于C语言编写的网络性能测试工具,由Salvatore Sanfilippo开发。它能够模拟各种类型的网络包,对服务器进行压力测试,并提供丰富的选项来定制测试。hping3不仅适用于HTTP协议,还支持TCP、UDP、ICMP等多种协议,使其成为一个多功能的网络性能测试工具。
hping3的使用非常灵活,基本的命令格式如下:
hping3 [options] destination
其中,[options]是可选的参数,destination是目标服务器的地址。下面是一些常用的选项:
-2
或 --tcp
:模拟TCP连接。-4
或 --udp
:模拟UDP数据包。-I
或 --interface
:指定网络接口。-i
或 --interval
:设置数据包发送间隔。-p
或 --port
:设置目标端口。--flood
:尽可能快地发送数据包。-c
或 --count
:发送指定数量的数据包。例如,要对一个Web服务器的80端口进行TCP压力测试,可以使用以下命令:
hping3 --tcp -p 80 -c 1000 example.com
这个命令将会模拟1000个TCP连接到example.com的80端口。
hping3由于其多功能性,可以用于多种网络性能测试场景:
hping3是一个功能强大的网络性能测试工具,它为网络管理员和安全专家提供了一个强大的测试平台,以评估网络设备和应用程序的性能和安全性。
“SYN队列”并不是真正的队列,而是将两条信息组合起来作为队列:ehash:这是一个哈希表,保存所有 ESTABLISHED 和 SYN_RECV 状态连接;Accept队列(struct request_sock_queue)中的qlen字段:“SYN队列”中的连接数,实际上是ehash中SYN_RECV状态的连接数。监听socket的struct sock中的sk_ack_backlog保存着accept队列中的连接数。
在深入探讨半连接队列最大长度控制之前,我们首先需要理解几个关键概念:
accept()
调用来完成连接的最终建立。listen()
函数的backlog
参数:在调用listen()
函数时,可以指定一个backlog
参数,它表示全连接队列的最大长度。/proc/sys/net/core/somaxconn
:这个参数定义了系统中所有监听套接字可以接受的最大半连接数。/proc/sys/net/ipv4/tcp_max_syn_backlog
:这个参数定义了每个TCP套接字可以接受的最大半连接数。半连接队列最大长度控制的最大大小不是直接由内核参数决定的,而是受net.ipv4.tcp_max_syn_backlog和net.core.somaxconn等参数的影响。这些参数的影响可能因不同的操作系统而异。在CentOS中,SYN队列的最大大小有以下计算公式:
Max SYN Queue Size = roundup_pow_of_two( max(min(somaxconn, backlog, sysctl_max_syn_backlog), 8) + 1)
roundup_pow_of_two(Num) 表示将 Num 向上舍入到 2 次方。例如,当 Num 为 6、7 或 8 时,roundup_pow_of_two(Num) 始终返回 8。
因此,如果我们将 somaxconn 设置为 64,tcp_max_syn_backlog 设置为 128,listen() 函数的 backlog 设置为 256,那么 CentOS 中 SYN 队列的最大大小将为 256。
在Ubuntu中,SYN队列的大小必须小于接受队列的最大大小,并且小于或等于net.ipv4.tcp_max_syn_backlog的0.75倍。我们可以将其转换为以下公式:
Max SYN Queue Size = min(min(somaxconn, backlog), 0.75 * tcp_max_syn_backlog + 1)
如果我们将 somaxconn 设置为 64,tcp_max_syn_backlog 设置为 512,backlog 设置为 256,则接受队列的最大大小为 64,小于 tcp_max_syn_backlog 的 0.75 倍,因此 SYN 队列的最大大小为 64。
如果我们将 somaxconn 设置为 1024,tcp_max_syn_backlog 设置为 256,backlog 设置为 512,则接受队列的最大大小为 512,大于 tcp_max_syn_backlog,因此 SYN 队列的最大大小为 193。
首先,我们需要确定调用listen()
时传入的backlog
参数。在 Nginx 中,listen()
函数的 backlog
参数默认值是 511。这个值定义了 Nginx 监听套接字可以接受的最大半连接数。然而,值得注意的是,这个 backlog
参数的大小最终会受到内核参数 net.core.somaxconn
的限制,该内核参数定义了系统中所有监听套接字可以接受的最大半连接数,默认值通常是 128。
因此,尽管 Nginx 默认的 backlog
参数设置为 511,实际可使用的最大值可能会受到 somaxconn
内核参数的影响。
TCP半连接队列的长度 不能用全连接队列一样使用ss命令直接查看,但是我们可以根据TCP半连接的特点-SYN_RECV
状态的 TCP 连接,来统计系统当前TCP半连接队列的长度。
在深入的分析之前我们先进行故障的模拟
就按照上面计算的参数设置
vim /etc/sysctl.conf
net.core.somaxconn=64net.ipv4.tcp_max_syn_backlog = 512net.ipv4.tcp_syncookies = 0
net.ipv4.tcp_syncookies 这个参数很重要,将在后面进行讲解
sysctl -p
sysctl -pnet.core.somaxconn = 64net.ipv4.tcp_max_syn_backlog = 512net.ipv4.tcp_syncookies = 0
vim /etc/nginx/conf.d/bingo.conf
listen 8080 default backlog=256;
reload nginx
nginx -s reloadss -lnp | grep 8080tcp LISTEN 0 64 0.0.0.0:8080 0.0.0.0:* users:(("nginx",pid=12817,fd=6),("nginx",pid=12816,fd=6))
#-S:这个选项指定hping3发送的是TCP SYN包。TCP SYN包用于建立TCP连接的第一阶段,即同步序列编号。#-p 8080:这个选项指定源端口为8080#--flood:这个选项允许hping3以尽可能快的速度发送数据包,不关心关于往返时间或数据包丢失的信息。这是一种“洪水”模式,用于测试目标系统的处理能力。hping3 -S -p 8080 --flood 192.168.222.169HPING 192.168.222.169 (ens33 192.168.222.169): S set, 40 headers + 0 data byteshping in flood mode, no replies will be shown
查看系统当前
root@adming-virtual-machine:~# netstat -antup | grep SYN_RECV |wc -l64root@adming-virtual-machine:~# netstat -antup | grep SYN_RECV |wc -l64root@adming-virtual-machine:~# netstat -antup | grep SYN_RECV |wc -l34root@adming-virtual-machine:~# netstat -antup | grep SYN_RECV |wc -l63root@adming-virtual-machine:~# netstat -antup | grep SYN_RECV |wc -l64root@adming-virtual-machine:~# netstat -antup | grep SYN_RECV |wc -l64
可以看到跟上面我们计算的一样,半连接的最大队列长度为64
因为半连接队列溢出被丢弃连接
root@adming-virtual-machine:~# netstat -s | grep -i listen 51769826 SYNs to LISTEN sockets droppedroot@adming-virtual-machine:~# netstat -s | grep -i listen 51769862 SYNs to LISTEN sockets droppedroot@adming-virtual-machine:~# netstat -s | grep -i listen 51769871 SYNs to LISTEN sockets droppedroot@adming-virtual-machine:~# netstat -s | grep -i listen 51769876 SYNs to LISTEN sockets droppedroot@adming-virtual-machine:~# netstat -s | grep -i listen 51769898 SYNs to LISTEN sockets dropped
上面输出的数值是累计值,表示共有多少个 TCP 连接因为半连接队列溢出而被丢弃。隔几秒执行几次,如果有上升的趋势,说明当前存在半连接队列溢出的现象。
SYN队列溢出时服务器的行为主要由该net.ipv4.tcp_syncookies
选项决定。
由于SYN队列的大小总是有限的,一些攻击者可能会尝试通过发送大量SYN数据包来攻击服务器,试图耗尽服务器的SYN队列以阻止合法连接的建立。这通常称为 SYN 洪水攻击。
SYN Cookie机制就是为了解决这个问题而诞生的。简单来说,启用该机制后,Linux在收到SYN包时会根据时间戳、四元组等信息计算出一个Cookie,然后作为SYN-ACK包的序列号返回给客户端。客户端返回ACK中的序号加一,服务器只需要减一就可以反转原来的Cookie。因此,服务器不再需要将连接请求放入SYN队列中。
不过,SYN Cookie机制也有一些缺点:
net.ipv4.tcp_timestamps
),并使用 32 位时间戳的低 6 位来存储这些 TCP 选项,但 TCP Timestamps 需要客户端和服务器的共同支持才能实现。真正启用。因此,该net.ipv4.tcp_syncookies
选项当前具有三个可能的值:
net.ipv4.tcp_syncookies = 0
,表示关闭SYN Cookie机制。如果SYN队列已满,新到达的SYN报文将被丢弃。net.ipv4.tcp_syncookies = 1
,这意味着SYN Cookie机制只有在SYN队列满时才正式启用。net.ipv4.tcp_syncookies = 2
,表示无条件启用SYN Cookie机制。 在不同类型和版本的操作系统中, 的默认值net.ipv4.tcp_syncookies
可能不同。我们可以运行以下命令来检查当前值:
sysctl -n net.ipv4.tcp_syncookies
考虑到 SYN Cookie 的潜在副作用,我们一般建议仅在 SYN 队列满时(设置net.ipv4.tcp_syncookies
为 1)才启用 SYN Cookie,并优先考虑尽可能增加 SYN 队列的最大大小。运行以下命令修改该选项:
sysctl -w net.ipv4.tcp_syncookies=1
当客户端向服务器端发起一个 SYN 消息以初始化 TCP 连接时,服务器端会将这个待完成的连接请求放入半连接队列(也称为 SYN 队列)。如果服务器端检测到该队列已达到其最大容量,那么它将拒绝(即丢弃)新的连接请求。
至于服务器端如何判断半连接队列是否达到其承载极限,这不仅涉及到控制半连接队列长度的参数,还与特定的内核参数有关,即 /proc/sys/net/ipv4/tcp_syncookies
。这个参数的作用主要是为了防御 SYN Flood 这类拒绝服务攻击的手段。SYN Flood 攻击通过发送大量的 SYN 请求来耗尽服务器的半连接队列,从而使得合法的用户无法建立新的连接。
tcp_syncookies
在半连接队列满了之后起作用,允许服务器使用一种特殊的机制来处理新的 SYN 请求,即便队列已满。这种方式不需要为每个半连接分配一个完整的数据结构,而是使用一种简化的“cookie”来快速验证连接请求,从而允许合法的连接在半连接队列溢出的情况下仍然能够建立。
判断是否 Drop SYN 请求的流程图:
tcp_syncookies
功能未被激活,那么服务器将开始丢弃新的连接请求。tcp_syncookies
和 max_syn_backlog
参数: 若 tcp_syncookies
未启用,且当前半连接队列的长度已经接近 max_syn_backlog
所设置的上限,具体来说,如果 max_syn_backlog
的值减去当前半连接队列的长度的结果小于 max_syn_backlog
值的四分之一(max_syn_backlog >> 2
),那么新的 SYN 请求也将被丢弃。SYN 攻击是一种常见的拒绝服务攻击(DoS),通过发送大量的 SYN 包来耗尽服务器的资源,导致正常的连接请求无法被处理。以下是几种可以采取的策略来防御 SYN 攻击:
tcp_max_syn_backlog
参数:这个参数限定了服务器可以跟踪的未完成的 TCP 连接的数量。我们可以通过以下命令查看和设置该参数:
sysctl net.ipv4.tcp_max_syn_backlog sysctl -w net.ipv4.tcp_max_syn_backlog=1024somaxconn
参数:somaxconn
是 listen()
调用中的 backlog
参数的最大值,决定了全连接队列的最大长度。修改它可以通过直接编辑 /etc/sysctl.conf
文件:
sudo echo 'net.core.somaxconn = 1024' >> /etc/sysctl.conf sysctl -p /etc/sysctl.confbacklog
参数:对于 Nginx,我们需要在 Nginx 配置文件中每个 listen
指令后添加 backlog
参数:
server { listen 80 backlog=1024; ... }
修改配置后,重启 Nginx 以应用更改:
sudo systemctl restart nginx 启用 tcp_syncookies
:当 tcp_max_syn_backlog
参数值的连接队列满了时,syncookies
允许内核用一种更轻量的方式继续处理连接请求。启用它:
sysctl -w net.ipv4.tcp_syncookies=1
将该设置写入 /etc/sysctl.conf
以持久化配置:
echo 'net.ipv4.tcp_syncookies = 1' | sudo tee -a /etc/sysctl.conf
sysctl -p /etc/sysctl.conf
调整 tcp_syn_retries
和 tcp_synack_retries
参数:这些参数控制了内核在放弃连接前发送 SYN+ACK 包的次数。减少重传次数可以加快连接的超时过程:
sysctl -w net.ipv4.tcp_syn_retries=1
sysctl -w net.ipv4.tcp_synack_retries=1
同样,将这些更改添加到 /etc/sysctl.conf
中以持久化:
echo 'net.ipv4.tcp_syn_retries = 1' | sudo tee -a /etc/sysctl.conf
echo 'net.ipv4.tcp_synack_retries = 1' | sudo tee -a /etc/sysctl.conf
sysctl -p /etc/sysctl.conf
在实际生产环境中,半连接队列和全连接队列溢出的问题虽然可能在服务器的监控指标中不显眼,但它们对服务稳定性的潜在威胁却不容忽视。当这些队列溢出时,服务器可能表面上看起来运行正常,如 CPU 使用率、内存使用量和网络连接数等关键指标均显示正常,但实际上客户端的业务请求却可能遭受持续性的干扰。特别是在高负载环境下,如果上游服务采用的是短连接策略,那么这种风险更是会急剧增加。
推荐阅读博文
https://github.com/torvalds/linux
https://www.emqx.com/en/blog/emqx-performance-tuning-tcp-syn-queue-and-accept-queue
https://arthurchiao.art/blog/tcp-listen-a-tale-of-two-queues/
我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。