SYN Flood攻击瞄准的是TCP三次握手的结构性缺陷。正常的TCP连接建立过程,客户端先发送SYN包,服务端回复SYN-ACK,并把这个半连接状态存入一个叫SYN队列(半连接队列)的内存结构中,最后客户端回复ACK完成握手,连接从SYN队列移入Accept队列。攻击者利用的就是这个时间差和资源不对等——大量发送SYN包,但对服务端的SYN-ACK故意不回应,或者伪造源IP让回应包石沉大海。服务端只能傻等,每个半连接条目都会消耗内存和CPU,直到队列被撑爆,新的正常请求根本无法进来。

从内核视角看,一个SYN包到达网卡,经过软中断处理,进入协议栈的tcp_v4_rcv函数,最终在tcp_conn_request里创建request_sock结构体,挂入半连接队列。这个过程中,内核不仅要分配内存,还要计算序列号、设置重传定时器。攻击流量一大,软中断占满CPU,内存分配失败,系统直接雪崩。更隐蔽的是,现代服务器动辄几十GB内存,表面看资源没用满,但半连接队列的大小根本不是由系统总内存决定的,而是受限于一个内核参数somaxconn和应用程序listen时传入的backlog参数,二者取最小值。默认情况下,这个值通常只有128或256,攻击者用极低的带宽就能打满。

理解这个机制后,防御思路就清晰了:要么让半连接队列变大,要么让半连接条目尽快失效释放,要么在握手完成前不分配任何资源。Linux内核提供了一套完整的参数组合拳,远比简单调大backlog有效。

net.core.somaxconn:扩大全局队列上限

这个参数定义了系统中每个监听套接字的最大半连接队列长度上限。无论应用程序在listen()调用时传入多大的backlog,最终生效值不会超过somaxconn。很多运维人员只在应用层调大了backlog,却忽略了内核层的这个天花板,结果毫无效果。检查当前值用sysctl net.core.somaxconn,临时调整用sysctl -w net.core.somaxconn=65535,永久生效需写入/etc/sysctl.conf。建议值至少8192,高并发场景直接给65535。但光调大这个值只是治标,队列再大也架不住攻击者无限填充,必须配合其他参数做快速淘汰。

net.ipv4.tcp_max_syn_backlog:独立于somaxconn的补充防线

很多人混淆这个参数和somaxconn的关系。实际上,tcp_max_syn_backlog是在somaxconn基础上,当系统检测到SYN Flood行为时,内核会启用额外的哈希表来容纳更多的半连接请求,这个参数控制的就是这张“应急”哈希表的大小。它的生效前提是内核启用了SYN Cookies之外的半连接扩展机制。即使somaxconn设得很大,如果tcp_max_syn_backlog偏小,应急哈希表很快耗尽,照样丢包。建议值8192以上,和somaxconn保持一致或更大。检查命令:sysctl net.ipv4.tcp_max_syn_backlog。

net.ipv4.tcp_synack_retries:减少重试次数,快速释放半连接

服务端发出SYN-ACK后,如果没收到客户端的ACK,内核会按照一定间隔进行重传。tcp_synack_retries控制的就是重传次数。默认值是5,意味着从第一次发出SYN-ACK到最后放弃,总共会经历大约3分钟(指数退避,1秒、2秒、4秒、8秒、16秒、32秒,累加约63秒,实际总时长受RTO影响可能更长)。攻击者打过来的SYN包根本不会回应ACK,这些半连接条目就要在队列里白白占用3分钟。把这个值降到1甚至0,重试总时长骤降到几秒,队列腾空速度成倍提升。对于内网低延迟服务,直接设0毫无问题;公网服务设1比较稳妥,避免丢包导致正常连接失败。设置方式:sysctl -w net.ipv4.tcp_synack_retries=1。

net.ipv4.tcp_syncookies:零资源消耗的终极武器

SYN Cookies是整个防御体系中最硬核的机制,也是理解门槛最高的一个。它的核心思想彻底颠覆了传统握手流程:当半连接队列满了之后,服务端不再为新的SYN包分配request_sock结构体,而是把本应存储在内存中的连接信息,通过一个加密哈希算法编码进SYN-ACK包的初始序列号里,发回给客户端。如果客户端是真实的,它回应的ACK包里会携带这个序列号加1的值,服务端解码验证通过后,直接创建完整的连接,全程不需要半连接队列。攻击者发来的SYN包,服务端只做了几次CPU计算就完事了,零内存占用。

启用SYN Cookies非常简单:sysctl -w net.ipv4.tcp_syncookies=1。但有一个关键细节必须搞清楚——SYN Cookies只有在半连接队列满了之后才会触发,它不是默认替代正常流程,而是作为最后的兜底手段。所以前面的somaxconn和tcp_max_syn_backlog依然重要,它们决定了触发SYN Cookies的阈值。另一个常见误区是认为SYN Cookies会导致性能下降或功能缺失,比如不支持TCP窗口缩放、SACK等高级选项。这在内核2.6.26之后已经通过TCP Cookie Transactions的改进得到解决,现代内核的SYN Cookies完全支持这些选项,性能损耗几乎可以忽略。

net.ipv4.tcp_abort_on_overflow:溢出的最后处理策略

这个参数控制的是当Accept队列(全连接队列)溢出时内核的行为。设为0,内核会直接丢弃客户端发来的ACK包,让客户端以为丢包了,触发客户端重传,给服务端争取处理时间;设为1,内核直接发RST包重置连接,客户端立刻报错。在SYN Flood场景下,这个参数影响的是那些已经完成握手但还没来得及被应用accept的连接。保持默认值0是最优选择,让重传机制发挥作用,避免误伤正常连接。

net.core.netdev_max_backlog:网卡层的缓冲防线

流量到达网卡后,如果协议栈处理不过来,数据包会暂存在一个per-CPU的输入队列中,这个队列的长度由netdev_max_backlog控制。默认值通常是1000,在SYN Flood攻击下,这个值瞬间被填满,后续包直接被丢弃,连进入协议栈的机会都没有。虽然丢包发生在更早的阶段,但效果一样——正常请求也被拦在门外。把这个值调到3000甚至更高,给协议栈争取更多的处理时间窗口。调整命令:sysctl -w net.core.netdev_max_backlog=3000。

net.ipv4.tcp_fastopen:减少握手开销的进阶方案

TCP Fast Open(TFO)允许客户端在发送SYN包时直接携带数据,服务端验证通过后可以立即处理,省去一次往返。对于已经建立过连接的客户端,TFO能显著降低延迟,同时客观上减少了半连接状态的维持时间。启用TFO需要客户端和服务端同时支持,服务端设置:sysctl -w net.ipv4.tcp_fastopen=3(1为客户端启用,2为服务端启用,3为两者都启用)。虽然TFO不是直接防御SYN Flood的手段,但它缩短了握手流程,间接降低了攻击面。

组合调优实例与验证

下面给出一个生产环境验证过的完整配置方案,适用于Web服务器、API网关等高并发场景。将以下内容写入/etc/sysctl.conf:

# 扩大半连接队列
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535

# 快速释放无效半连接
net.ipv4.tcp_synack_retries = 1

# 启用SYN Cookies兜底
net.ipv4.tcp_syncookies = 1

# 扩大网卡接收队列
net.core.netdev_max_backlog = 5000

# 启用TCP Fast Open
net.ipv4.tcp_fastopen = 3

# 全连接队列溢出时不发RST
net.ipv4.tcp_abort_on_overflow = 0

执行sysctl -p使其生效。验证SYN Cookies是否启用:sysctl net.ipv4.tcp_syncookies,输出应为1。查看半连接队列溢出情况:netstat -s | grep "SYNs to LISTEN",如果这个计数器持续增长,说明队列仍然不够大或者攻击流量远超预期,需要进一步调整。更精细的监控可以用ss -s查看当前半连接数量,或者用nstat -az | grep TcpExtListenOverflows实时观察溢出事件。

这套参数组合的核心逻辑是分层防御:先尽力扩大队列容纳能力(somaxconn、tcp_max_syn_backlog),再加速无效条目的淘汰速度(tcp_synack_retries),最后用SYN Cookies做零成本的最终兜底。三层递进,既保证了正常连接的性能,又能在攻击流量下维持服务可用性。单独调整任何一个参数都是片面的,必须理解它们之间的协作关系,才能根据自身业务特点做出精准调优。