SYN Flood 攻击之所以廉价且高效,是因为它利用了 TCP 三次握手的不对称性。攻击者只需发送一个伪造源 IP 的 SYN 包,服务器就必须分配一个传输控制块(TCB)来维护这个半连接状态,并回复 SYN-ACK。如果这个连接迟迟得不到最终 ACK 的确认,这些半连接就会像无人认领的快递一样,迅速堆满服务器的仓库。这个仓库就是连接表,当它被耗尽时,正常的业务请求就无法建立新的连接,服务也就瘫痪了。很多人第一反应是加大连接表的大小,但这治标不治本,更大的表只是意味着攻击者需要多花几秒钟才能填满它。真正有效的思路,是让那些迟迟无法完成握手的半连接尽快消失,给合法连接腾出位置。限制 SYN 重试次数,就是实现这个目标最直接、成本最低的手段。

TCP 握手中的重试逻辑

要理解如何通过限制重试来缓解攻击,必须先搞清楚服务器在发送第一个 SYN-ACK 之后发生了什么。当服务器收到客户端的 SYN 包,它会将这个连接放入半连接队列,并发送一个 SYN-ACK 包。如果这个 SYN-ACK 包丢失了,或者因为客户端是伪造的而永远无法到达,服务器不会无限期地等待。它会启动一个重传定时器,按照一定的间隔重新发送 SYN-ACK 包。这个重试次数和重试间隔,就是我们可以调控的关键参数。在 Linux 系统中,这个行为由两个核心参数控制:tcp_synack_retries 和 tcp_syn_retries。前者控制的是作为服务端时,SYN-ACK 包的重传次数;后者控制的是作为客户端发起连接时,SYN 包的重传次数。对于防御 SYN Flood 而言,我们需要重点关注的是 tcp_synack_retries。

参数的具体作用机制

tcp_synack_retries 的默认值通常是 5。这意味着服务器在发送第一个 SYN-ACK 后,如果没有收到 ACK,它会进行最多 5 次重试。重试的间隔并非固定,而是呈指数级增长,初始间隔一般为 1 秒,之后每次翻倍,即 1 秒、2 秒、4 秒、8 秒、16 秒,加上最后一次重试后的等待时间,一个半连接从诞生到被系统判定为失败并清除,总共可能需要耗费约 63 秒甚至更久。你可以想象一下,在遭受攻击时,每一秒都有数以万计的伪造 SYN 包涌入,而每一个伪造的半连接都要在服务器的连接表中存活超过一分钟。这种时间尺度上的巨大不对等,是连接表被迅速撑爆的根本原因。攻击者用极短的时间成本,换取了服务器长时间的资源占用。

调整参数带来的直接变化

将这个值从 5 降低到 1 或者 0,效果是立竿见影的。如果将 tcp_synack_retries 设置为 1,重试间隔依然是指数退避的第一次值,也就是 1 秒后重试一次,如果再过 2 秒左右还没收到 ACK,这个半连接就会被丢弃。整个半连接的存活时间从 60 多秒骤降到 3 到 4 秒。这意味着攻击者必须持续以极高的速率发送伪造 SYN 包,才能勉强维持对连接表的占用压力。一旦攻击流量出现任何波动或减弱,连接表就会在几秒内被迅速清空,服务立刻恢复正常。如果将其设置为 0,服务器甚至不会进行任何重试,第一个 SYN-ACK 发出后,如果在很短的超时时间内未收到 ACK,连接即被销毁。这种配置虽然极端,但对于某些明确只服务于内网低延迟环境,或者前端已有专业抗 DDoS 设备进行流量清洗的场景,是一种非常干脆的优化。

调整参数的操作方法

修改这个参数非常简单,不需要重新编译内核,也不需要安装额外的软件包。你可以直接通过 sysctl 命令或修改配置文件来完成。临时生效的命令是:

sysctl -w net.ipv4.tcp_synack_retries=1

如果要永久生效,需要将配置写入 /etc/sysctl.conf 或 /etc/sysctl.d/ 目录下的自定义配置文件中,例如:

echo "net.ipv4.tcp_synack_retries = 1" >> /etc/sysctl.conf
sysctl -p

在执行这些操作时,建议不要直接设置为 0,除非你完全理解其后果。设置为 1 是一个在安全性和兼容性之间取得良好平衡的选择。对于大多数 Web 服务器而言,如果客户端在 3 秒内都无法完成三次握手,说明网络质量极差,这种连接即使最终建立起来,其用户体验也会非常糟糕,放弃它并不可惜。

不能忽视的客户端参数 tcp_syn_retries

虽然防御 SYN Flood 主要靠调整 tcp_synack_retries,但 tcp_syn_retries 同样值得关注。这个参数决定了服务器作为客户端向外发起连接时,SYN 包的重试次数。默认值通常是 6,重试间隔同样是指数退避,总耗时大约 127 秒。如果你的服务器上运行着大量的反向代理、爬虫程序或者数据库中间件,它们需要频繁地向内部其他服务发起连接。当后端服务出现故障或网络抖动时,过高的 tcp_syn_retries 会导致大量的本地端口和内存被这些挂起的出站连接占用。一旦本地端口资源耗尽,即使服务器本身没有遭受外部攻击,它也无法再向外建立任何新的连接,表现为依赖外部 API 或数据库的业务全部中断。将 tcp_syn_retries 从 6 降低到 3 或 2,可以将出站连接失败的检测时间从 2 分钟缩短到十几秒,让应用程序能更快地重试或切换到备用节点,极大地提高了系统在局部故障下的自愈速度。

重试次数与半连接队列的协同关系

限制重试次数并不是一个孤立的动作,它必须和半连接队列的大小配合才能发挥最大效用。tcp_max_syn_backlog 参数决定了半连接队列的最大长度。如果这个队列本身就很小,即使重试次数再低,攻击者也能轻易填满它。反之,如果队列很大但重试次数很高,队列依然会被长时间占满。正确的思路是,适当增大 tcp_max_syn_backlog 的值,使其能够容纳瞬间的正常业务突发流量,同时大幅降低 tcp_synack_retries,确保队列的周转速度极快。这样一来,半连接队列就像一个拥有超快水流循环系统的蓄水池,进水和出水速度都很快,攻击者想用少量的 SYN 包就堵死这个池子就变得极其困难。你还可以启用 tcp_syncookies 机制,当半连接队列被填满时,系统不再为新的 SYN 包分配 TCB 资源,而是通过加密算法将连接信息编码进 SYN-ACK 的序列号中发回。如果后续能收到合法的 ACK,再还原出连接。tcp_syncookies 与限制重试次数相结合,构成了两层防护:重试次数限制加速了队列的清理,syncookies 则在队列溢出时提供了最后的兜底保护,避免了合法请求被直接丢弃。

对业务和用户体验的潜在影响

任何安全策略的调整都需要评估对正常用户的影响。将 tcp_synack_retries 降低到 1,意味着那些处于信号极弱、网络丢包严重环境下的用户,比如在地下室、电梯里或者使用高延迟卫星网络的用户,建立连接的成功率会有所下降。但现实情况是,现代移动网络和宽带网络的延迟和丢包率已经控制得相当好,三次握手通常能在几百毫秒内完成。如果一个用户所处的网络环境差到连第一个 SYN-ACK 和它的一次重传都收不到,那么即使连接建立成功,后续的数据传输也会频繁超时和重传,应用层面的体验基本不可用。因此,放弃这部分极端边缘的用户,来换取整个服务在遭受攻击时的稳定性,从业务连续性角度看是完全值得的。如果你的业务恰好是面向网络基础设施较差地区的用户,那么可以考虑将值设为 2,在防御效果和兼容性之间做一个折中。

从攻击者视角看防御效果

站在攻击者的角度,他们发动 SYN Flood 时,最希望看到的是服务器资源被线性地、持久地消耗。他们发包的成本极低,而服务器维持半连接的成本很高。当你把重试次数从 5 降到 1,服务器维持半连接的时间窗口缩短了 90% 以上。这意味着攻击者要达到同样的占满连接表的效果,需要将攻击流量提升数倍甚至数十倍。这直接拉高了攻击者的带宽成本和资源门槛。很多低强度的、脚本小子级别的攻击,在这种配置面前会直接失效。即使面对大流量攻击,更快的队列周转速度也为运维人员争取了宝贵的响应时间,可以在连接表被真正打满之前,通过流量分析、特征提取等手段在更上层进行封堵。这种内核参数的调整,本质上是改变了攻防双方的成本不对称性,让防守方不再那么被动。

与其他缓解手段的比较

业界常见的 SYN Flood 防御手段包括增大连接表、启用 syncookies、缩短超时时间、部署专业防火墙或清洗中心,以及这里讨论的限制重试次数。增大连接表是最笨拙的方法,属于硬扛。专业防火墙效果好但成本高,有延迟。syncookies 是很好的兜底方案,但它会丢失部分 TCP 选项信息,比如窗口扩大因子和时间戳,对高性能传输有一定影响。限制重试次数则是一种零成本、高收益的内核级优化,它没有任何硬件依赖,不损耗服务器计算资源,也不影响已建立连接的性能。它唯一的代价就是对极端恶劣网络环境的容忍度降低了一点。将限制重试次数与 syncookies 配合使用,是目前业界公认的最具性价比的软件层面防御组合。

监控与验证

调整参数后,必须通过监控来验证效果。你可以使用 netstat 或 ss 命令来观察半连接状态的变化。

ss -s

这个命令会显示当前处于 SYN-RECV 状态的连接数量。在调整参数前,如果遭受攻击,这个数字会持续攀升并维持在高位。调整之后,你会看到这个数字虽然可能瞬间冲高,但会在几秒内迅速回落。你还可以通过抓包来观察服务器发送 SYN-ACK 重传包的行为变化。

tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn|tcp-ack) == (tcp-syn|tcp-ack)'

观察同一个源 IP 或不同源 IP 的 SYN-ACK 重传次数,可以直观地确认参数是否生效。此外,持续监控应用程序的连接超时错误日志也很重要。如果调整后,业务日志中突然出现大量连接超时的报错,且这些报错集中在某个特定时段,那就需要排查是否是正常的业务高峰期导致了部分用户连接被误伤,从而考虑微调参数。

进阶的组合策略

单靠一个参数并不能解决所有问题,但它是构建防御体系的基石。一个完善的 TCP 协议栈抗攻击配置,通常包含以下几个方面的调整:将 tcp_synack_retries 设为 1 或 2,将 tcp_syn_retries 设为 2 或 3,启用 tcp_syncookies 并设为 1,适当增大 tcp_max_syn_backlog,同时减小 tcp_fin_timeout 来加速真正连接关闭时的端口回收。如果有高并发短连接业务,还可以开启 tcp_tw_reuse 和调整 tcp_timestamps。这一套组合拳打下来,服务器的 TCP 协议栈会变得非常“薄情寡义”,它不再对任何迟迟无法完成握手的客户端抱有不切实际的期待,而是快速失败、快速回收资源。这种设计哲学,正是大规模分布式系统在网络层追求高可用性的核心思想。

总结

通过限制 SYN 重试次数来缓解连接表耗尽,本质上是一种通过降低系统对异常连接的容忍度来换取整体可用性的策略。它不花钱、不增加硬件、不影响正常业务性能,只需要修改一两个内核参数,就能让服务器在面对 SYN Flood 攻击时,从被动挨打变为主动清理。这种调整让半连接的生存周期从分钟级压缩到秒级,极大地改变了攻防双方的成本不对称性。当然,任何安全措施都不是银弹,它需要结合半连接队列调优、syncookies 机制以及上层的流量清洗手段,共同构成纵深防御体系。但无论如何,这都应该是你加固 Linux 服务器时,最先执行的操作之一。