本文永久链接: https://www.xtplayer.cn/linux/network/request-sock-tcp-possible-syn-flooding-on-port/

TCP 三次握手

  1. 客户端发送 SYN(进入 SYNC_SENT 状态)

  2. 服务端返回 SYN+ACK(进入 SYNC_RECV 状态)

  3. 客户端发送 ACK(进入 ESTABLISHED 状态)

如果客户端在第 3 步时不发送 ACK 给服务端,那么服务端的 socket 就会处于 SYNC_RECV 状态。

TCP/IP backlog 参数

backlog 其实是一个连接队列,在 Linux kernel 2.2 之前,backlog 大小包括半连接状态和全连接状态两种队列大小。

  • 半连接状态为:服务器处于 Listen 状态时收到客户端 SYN 报文时放入半连接队列中,即 SYN queue(服务器端口状态为:SYN_RCVD)。

  • 全连接状态为:TCP 的连接状态从服务器(SYN+ACK)响应客户端后,到客户端的 ACK 报文到达服务器之前,则一直保留在半连接状态中。当服务器接收到客户端的 ACK 报文后,该条目将从半连接队列搬到全连接队列尾部,即 accept queue(服务器端口状态为:ESTABLISHED)。

在 Linux kernel 2.2 之后,分离为两个 backlog 来分别限制半连接(SYN_RCVD 状态)队列大小和全连接(ESTABLISHED 状态)队列大小。

SYN queue 队列长度由 /proc/sys/net/ipv4/tcp_max_syn_backlog 指定,默认为 2048。

Accept queue 队列长度由 /proc/sys/net/core/somaxconn 和使用 listen 函数时传入的参数,二者取最小值,默认为 128。在 Linux kernel 2.4.25 之前,是写死在代码常量 SOMAXCONN 。在 Linux kernel 2.4.25 之后,在配置文件 /proc/sys/net/core/somaxconn 中直接修改,或者在 /etc/sysctl.conf 中配置 net.core.somaxconn = 128

  • 可以通过 ss 命令来显示
[root@localhost ~]# ss -l
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:http *:*
LISTEN 0 128 :::ssh :::*
LISTEN 0 128 *:ssh *:*
LISTEN 0 100 ::1:smtp :::*
LISTEN 0 100 127.0.0.1:smtp *:*

LISTEN 状态,其中 Send-Q 即为 Accept queue 的最大值,Recv-Q 则表示 Accept queue 中等待被服务器 accept()。

另外客户端 connect() 返回不代表 TCP 连接建立成功,有可能此时 accept queue 已满,系统会直接丢弃后续 ACK 请求。客户端误以为连接已建立,开始调用等待至超时,服务器则等待 ACK 超时,会重传 SYN+ACK 给客户端,重传次数由 net.ipv4.tcp_synack_retries 限制,默认为 5 ,表示重发 5 次,每次等待 30~40 秒,即半连接默认时间大约为 180 秒,可以在 tcp 被洪水攻击是临时启用这个参数。

  • 查看 SYN queue 溢出
[root@localhost ~]# netstat -s | grep LISTEN
102324 SYNs to LISTEN sockets dropped
  • 查看 Accept queue 溢出
[root@localhost ~]# netstat -s | grep TCPBacklogDrop
TCPBacklogDrop: 2334

排查步骤

此处省略排查步骤。

内核调优

根据 TCP 三次握手TCP/IP backlog 参数 可以知道通过调节内核的一些参数可以解决队列溢出的问题。

# vim /etc/sysctl.conf
net.core.somaxconn = 2048
net.ipv4.tcp_max_syn_backlog = 81920
net.ipv4.tcp_syncookies = 1
net.core.netdev_max_backlog = 1000

# 保存退出后,执行:sysctl -p

问题重现

正常情况,当内核参数经过调优后,SYN flooding 的问题即可解决。但是当内核参数经过调优后,查看系统日志依然有 request_sock_tcp possible syn flooding on port

后期经过查阅资料发现,The application's socket listen backlog is applied when the application makes the listen() system call against its socket.,也就是在程序通过 listen() 系统调用时,可以对 socket listen backlog 做限制。

会不会是程序限制的 backlog 太小,导致队列溢出呢?通过查看程序代码,果然是 backlog 太小导致的。通过调整程序的 backlog 大小 request_sock_tcp possible syn flooding on port 不再出现。

参考链接

https://access.redhat.com/solutions/30453