SYN洪水攻击至今仍是DDoS防护中最棘手的一类,因为它利用的是TCP三次握手本身的设计缺陷,而不是简单的带宽耗尽。攻击者发送大量伪造源IP的SYN包,服务器为每个SYN分配半连接资源并回复SYN-ACK,但永远等不到最后的ACK,半连接队列被迅速填满,正常用户的握手请求被丢弃。要处置这个问题,不能只靠加带宽,核心在于减少半连接资源占用、识别伪造流量、以及在极端情况下快速释放资源。
先看一个最直接的处置动作:调整内核TCP参数。Linux服务器上,半连接队列长度由net.ipv4.tcp_max_syn_backlog控制,默认值通常只有1024或2048,攻击开始后几秒就会被占满。可以将其调高到65535,同时把net.core.somaxconn也调高,让应用层accept队列有足够空间。但单纯调大队列只是延缓问题,攻击流量足够大时依然会耗尽。更关键的是开启net.ipv4.tcp_syncookies,这个机制让服务器在SYN队列满时不再为新的SYN分配半连接资源,而是根据源IP、端口、时间戳和一个密钥计算出一个cookie,直接作为SYN-ACK的序列号发回。如果客户端是真实的,它会带着这个cookie返回ACK,服务器验证通过后再创建连接。这样半连接队列永远不会被伪造SYN占满,正常用户不受影响。syncookies的代价是轻微增加CPU计算,但在攻击期间这是最有效的保命手段。
第二个层面是缩短半连接的存活时间。net.ipv4.tcp_synack_retries控制服务器在未收到ACK时重发SYN-ACK的次数,默认值是5,意味着一个半连接可能存活几十秒甚至更久。攻击期间应将其降到1或0,让无效半连接在几秒内就被清除。对应的net.ipv4.tcp_abort_on_overflow可以设为1,当accept队列满时直接发送RST重置新连接,避免应用层堆积。这些参数组合起来,能让服务器在遭受SYN洪水时快速腾出资源,而不是被慢慢拖死。
但内核参数只是被动防守,真正有效的处置必须识别并阻断攻击流量。SYN洪水最显著的特征是大量SYN包没有后续ACK,且源IP往往是伪造的。在服务器前端部署iptables或nftables时,可以利用hashlimit模块对单个源IP的SYN包速率做限制。例如,限制每个源IP每秒最多发送20个SYN包,超过就丢弃。攻击者如果伪造大量不同源IP,这个规则效果有限,但能挡住单一源IP的洪水。更实用的做法是在上游防火墙或运营商层面做源IP信誉过滤,丢弃明显伪造的地址段,比如保留地址、未分配地址、以及不符合入站方向的源地址。BCP38入口过滤是网络运营商应当实施的,但作为被攻击方,你只能要求上游协助,或者在自有设备上对入站流量做uRPF检查,丢弃源地址不可达的包。
对于大规模分布式SYN洪水,单机防护几乎不可能完全解决,必须依赖清洗服务或专业抗DDoS设备。清洗服务的工作原理是把流量牵引到清洗中心,清洗中心在TCP握手前先代替服务器完成握手,验证客户端真实性,只有完成握手的流量才会被转发回源站。这个过程对正常用户透明,但对伪造源IP的SYN包,清洗中心发出的SYN-ACK不会得到回应,连接无法建立,攻击流量被丢弃。选择清洗服务时,要关注其对SYN洪水的具体处理能力,而不是只看总带宽。有些服务商只做简单的限速和黑洞,对SYN洪水效果很差,因为攻击流量可能远小于带宽上限,但半连接数已经打满。
如果暂时没有清洗服务,可以在服务器前端部署一层反向代理或负载均衡器,由它来承接SYN洪水。Nginx或HAProxy本身不能完全防御SYN洪水,但配合内核syncookies和连接队列调优,可以承受比后端应用高得多的握手压力。更进一步的方案是使用DPDK或XDP技术在内核旁路处理SYN包,例如用AF_PACKET或eBPF程序在网卡驱动层直接过滤SYN洪水,性能比传统内核协议栈高一个数量级。这种方式需要一定的开发能力,但在自建防护体系中非常有效。
还有一个经常被忽略的点:SYN洪水攻击的变种。攻击者可能使用SYN-ACK洪水、ACK洪水、或者混合型攻击,这些不占用半连接队列,但会消耗带宽和CPU。处置时要先通过抓包确认攻击类型。用tcpdump抓取入站流量,统计SYN包的比例和源IP分布。如果SYN包占比超过70%且源IP高度分散,基本可以确定是SYN洪水。如果SYN包带有明显相同的TCP选项或窗口大小,可以提取这些特征做指纹过滤。例如,某些攻击工具生成的SYN包TCP窗口大小固定为65535,或者MSS值异常,这些特征可以在iptables中用u32模块匹配并丢弃,效率远高于逐包检查。
具体到iptables规则,可以这样写一条基于hashlimit的SYN限速规则:
iptables -A INPUT -p tcp --syn -m hashlimit --hashlimit-name synflood --hashlimit-above 20/sec --hashlimit-burst 30 --hashlimit-mode srcip -j DROP
这条规则对每个源IP的SYN包速率做限制,超过每秒20个就丢弃。但要注意,hashlimit模块在高流量下会消耗较多CPU,如果攻击流量达到数百万pps,iptables本身就会成为瓶颈。这时应该把过滤前移到硬件防火墙或者使用XDP程序。下面是一个简单的XDP过滤思路:在网卡驱动层解析以太网、IP、TCP头,如果是SYN包且源IP在短时间内出现次数超过阈值,直接XDP_DROP。XDP程序运行在内核网络栈之前,单核可以处理数百万pps,适合大流量场景。
对于已经在遭受攻击的服务器,还有一个应急手段:临时将服务切换到TCP代理模式。例如,在服务器前端快速部署一个轻量级的TCP代理,代理先完成三次握手,再把请求转发给后端。攻击者伪造的SYN包无法完成握手,代理不会向后端转发任何东西,后端压力骤降。这个代理可以用Go或C写一个简单的程序,监听80或443端口,完成握手后建立到后端的连接。虽然代理本身也要面对SYN洪水,但配合syncookies和内核调优,代理的抗压能力远强于直接暴露的后端。
从长期防护角度看,SYN洪水的处置必须形成体系。第一层是网络边缘的入口过滤,丢弃伪造源地址;第二层是清洗设备或服务,完成握手验证;第三层是服务器内核参数调优,开启syncookies并缩短重试时间;第四层是应用层架构,使用反向代理和连接池减少握手压力。四层缺一不可。只调内核参数,大规模攻击下依然会打满带宽或CPU;只靠清洗服务,源站自身配置不当也会在清洗后出现性能瓶颈。
另外要纠正一个常见误区:很多人认为SYN洪水只是消耗半连接队列,带宽占用不大,所以不需要高防带宽。实际上,攻击者完全可以发送大尺寸SYN包,或者在SYN包中填充大量数据,让单个包达到MTU上限,从而在消耗半连接队列的同时也消耗带宽。处置时不能只看连接数,还要监控入站带宽和pps。如果pps达到网卡处理上限,即使半连接队列没满,正常流量也无法进入。这时候需要的是硬件层面的过滤和清洗,而不是软件调优。
还有一个值得注意的细节:syncookies在开启后,服务器返回的SYN-ACK序列号是计算出来的,不保存在本地,这意味着TCP窗口缩放、选择性确认等选项无法协商,可能影响高带宽场景下的传输性能。但在攻击期间,保活比性能更重要。攻击结束后可以关闭syncookies恢复默认。对于长期面临SYN洪水威胁的业务,建议在负载均衡器上开启syncookies,后端服务器保持默认配置,这样既不影响后端性能,又能利用负载均衡器的抗压能力。
最后总结一套可立即执行的处置清单:第一,抓包确认攻击类型和特征;第二,开启tcp_syncookies,调高tcp_max_syn_backlog,降低tcp_synack_retries;第三,在防火墙或服务器上对SYN包做源IP限速;第四,联系上游运营商做入口过滤,丢弃伪造源地址;第五,如果攻击规模超过服务器处理能力,立即接入清洗服务;第六,攻击结束后复盘,提取攻击特征并更新防护策略。SYN洪水防护不是一次性配置,而是一个持续对抗的过程,攻击者会不断变化手法,防护策略也要随之迭代。
