要理解SYN Proxy,必须先看清SYN Flood打垮服务器的本质。攻击者发送大量SYN包,服务器为每个SYN分配一个处于SYN_RCVD状态的连接槽,并回复SYN-ACK等待对方最后的ACK确认。槽位被虚假的“半开连接”耗尽后,真实用户的SYN包就被内核直接丢弃。SYN Proxy不是去硬扛这些半开连接,而是在TCP协议层直接接管三次握手,让攻击流量根本触碰不到后端服务器的连接表。它像一个站在门口的代理人,由它先和来访者完成整个三次握手,确认对方是“活人”之后,才把这条干净连接转交给后方的真实服务。

SYN Proxy的核心工作流程拆解

SYN Proxy本质上是一个运行在内核协议栈Netfilter框架中的状态机。当一个SYN包到达时,代理模块不把它直接转发给上层应用,而是拦截下来,由自己构造一个SYN-ACK包发回去。这个SYN-ACK的序列号不是随机生成的,而是通过一种叫做SYN Cookie的加密算法计算出来的。具体来说,它会根据源IP、源端口、目的IP、目的端口、时间戳以及一个只有服务器知道的密钥,通过哈希运算得到一个值,把这个值作为SYN-ACK的初始序列号发回给客户端。此时,服务器端不分配任何内存结构,不记录任何连接状态。

如果客户端是真实的,它会收到这个SYN-ACK,然后回复一个ACK。这个ACK包里的确认号字段,恰好就是刚才发出去的序列号加一。代理模块截获这个ACK后,重新用相同的算法和密钥计算一次哈希,验证确认号是否合法。验证通过,说明对方确实完成了三次握手,是一个真实存在的客户端。这时代理才在本地创建一个真正的连接条目,然后代替客户端向真实服务器发起一个新的三次握手,或者直接把这个已验证的TCP连接无缝移交给后端。整个过程里,攻击者发来的海量SYN包只会触发代理模块的哈希计算,而哈希计算消耗的CPU资源极低,远小于维护一个半开连接表项的开销。

SYN Cookie与SYN Proxy的关系与区别

很多人把SYN Cookie和SYN Proxy混为一谈,实际上它们是两种机制。SYN Cookie是SYN Proxy实现无状态验证的技术手段,它只解决“不分配资源也能完成三次握手”的问题。开启SYN Cookie后,服务器对每个SYN都回复带Cookie的SYN-ACK,连接信息编码在序列号里,直到收到合法ACK才创建连接。但SYN Cookie有局限性:它丢弃了TCP选项协商能力,比如窗口缩放、时间戳、选择确认等高性能TCP特性在Cookie模式下都会被忽略,连接建立后会退化成最基础的TCP传输模式,吞吐性能明显下降。

SYN Proxy则更聪明。它分为两种工作模式:一种是纯Cookie模式,行为和SYN Cookie类似,适合攻击正在进行中的紧急情况;另一种是全代理模式,代理模块在验证完客户端的ACK后,会记住客户端原本在SYN包中携带的TCP选项,然后由代理模块向真实服务器重新发起一个完整的、带优化选项的TCP连接。这样一来,客户端到代理、代理到服务器两端都可以使用窗口缩放和时间戳等现代TCP特性,整体转发性能不会因为抗攻击而大幅下降。理解了这层差异,才能在生产环境里做出正确选择。

Linux内核中SYN Proxy的实现位置与触发条件

Linux内核从2.6版本开始就在Netfilter中集成了SYN Proxy功能,具体实现在iptables的SYNPROXY目标模块中。它不是一个独立的服务进程,而是直接工作在网络层和传输层之间的钩子点上。数据包经过PREROUTING链时,如果匹配到SYNPROXY规则,内核就不会把这个包交给常规的TCP协议栈处理,而是进入SYN Proxy的专门处理路径。

SYNPROXY模块的触发是条件性的。它需要配合connlimit、hashlimit或recent等匹配模块来设定阈值。例如,可以配置为“当同一源IP的SYN包速率超过每秒20个时,对该IP的后续SYN包启用SYN Proxy”,或者“当系统半开连接总数超过5000时,对所有新到达的SYN包启用SYN Proxy”。这种分级响应策略避免了正常流量也走代理路径带来的额外延迟,只在攻击迹象出现时才激活防护。内核模块在处理时会检查连接跟踪表,如果发现某个SYN包对应的连接状态异常,直接由SYNPROXY生成Cookie应答,攻击包在PREROUTING阶段就被消化掉,根本不会进入INPUT链和应用层。

服务器启用SYN Proxy的详细操作步骤

启用SYN Proxy之前,需要确认内核版本和Netfilter模块是否就绪。运行以下命令检查SYNPROXY模块是否可用:

modprobe nf_synproxy_core
lsmod | grep synproxy

如果没有报错且能看到nf_synproxy_core和xt_SYNPROXY两个模块,说明内核支持。接下来需要在iptables规则中插入SYNPROXY目标。一个典型的基础配置如下:

# 关闭连接跟踪对SYN包的限制,避免和synproxy冲突
sysctl -w net.netfilter.nf_conntrack_tcp_loose=0

# 在INPUT链中,对进入的SYN包使用SYNPROXY
iptables -t raw -A PREROUTING -p tcp -m tcp --syn --dport 80 -j CT --notrack
iptables -A INPUT -p tcp -m tcp --dport 80 -m state --state INVALID,UNTRACKED -j SYNPROXY --sack-perm --timestamp --wscale 7 --mss 1460
iptables -A INPUT -p tcp -m tcp --dport 80 -m state --state ESTABLISHED -j ACCEPT

第一条规则在raw表的PREROUTING链上对目标端口80的SYN包打上NOTRACK标记,告诉连接跟踪系统不要处理这些包,避免连接跟踪表被攻击填满。第二条规则是关键:匹配状态为INVALID或UNTRACKED的包,交给SYNPROXY处理。参数--sack-perm开启选择确认,--timestamp开启时间戳,--wscale 7设置窗口缩放因子为7即128倍,--mss 1460设置最大分段大小。这些参数要根据实际网络环境调整,wscale值过大会在长肥网络上造成窗口溢出,过小则限制吞吐。第三条规则放行已经通过SYNPROXY验证并转为ESTABLISHED状态的连接。

如果服务器同时运行了多个服务,需要对每个端口分别配置。对于HTTPS的443端口,把--dport 80替换为--dport 443即可。如果想让SYNPROXY对所有端口生效,可以去掉--dport限制,但这样做风险较高,可能干扰一些特殊协议的连接建立。

进阶配置:基于阈值的动态启用

全量开启SYNPROXY会让所有连接都走代理路径,正常用户的连接也会增加一次Cookie验证的延迟。更精细的做法是利用hashlimit模块做速率限制,只对异常流量启用代理。以下规则实现了“每秒超过15个SYN包的源IP,其后续SYN包被SYNPROXY接管”:

iptables -N SYNFLOOD_CHECK
iptables -A SYNFLOOD_CHECK -p tcp -m tcp --syn --dport 80 \
  -m hashlimit --hashlimit-name synflood --hashlimit-above 15/sec \
  --hashlimit-burst 30 --hashlimit-mode srcip -j DROP

iptables -t raw -A PREROUTING -p tcp -m tcp --syn --dport 80 -j CT --notrack
iptables -A INPUT -p tcp -m tcp --dport 80 -m state --state INVALID,UNTRACKED \
  -m hashlimit --hashlimit-name synflood --hashlimit-above 15/sec \
  --hashlimit-burst 30 --hashlimit-mode srcip -j SYNPROXY --sack-perm --timestamp --wscale 7 --mss 1460
iptables -A INPUT -p tcp -m tcp --dport 80 -m state --state ESTABLISHED -j ACCEPT

这套规则的精妙之处在于:正常用户的前几个SYN包如果被hashlimit放行,会走常规TCP协议栈,享受完整的TCP选项协商;一旦某个IP的SYN速率超过阈值,后续SYN包被SYNPROXY接管,攻击流量被Cookie机制过滤,而该IP如果后续恢复正常速率,又会自动切回常规路径。整个过程对客户端完全透明。

SYN Proxy在高并发场景下的性能调优

SYNPROXY模块本身消耗CPU资源很少,但高并发下有几个内核参数需要同步调整。首先是连接跟踪表的大小,虽然SYNPROXY绕过了连接跟踪,但通过代理建立的连接最终还是会创建跟踪条目。通过以下参数增大跟踪表容量:

sysctl -w net.netfilter.nf_conntrack_max=2097152
sysctl -w net.nf_conntrack_max=2097152

其次是SYN队列和Accept队列的长度。SYNPROXY在向后端发起真实连接时,如果后端服务器的SYN队列过小,代理发起的SYN包也可能被丢弃。调整somaxconn和tcp_max_syn_backlog:

sysctl -w net.core.somaxconn=65535
sysctl -w net.ipv4.tcp_max_syn_backlog=8192

另外,SYNPROXY在Cookie计算中依赖时间戳来防止重放攻击,内核参数net.ipv4.tcp_timestamps必须保持开启状态,否则Cookie的有效期控制会失效。检查并确保:

sysctl -w net.ipv4.tcp_timestamps=1

在高带宽环境下,SYNPROXY的--mss参数需要谨慎设置。如果设置过小,TCP分段增多,CPU软中断处理负担上升;设置过大,可能在某些路径上导致IP分片。一般建议设置为路径MTU减去40字节,即1460对应标准以太网的1500 MTU。如果后端使用隧道封装或VXLAN,需要根据实际封装开销减小MSS值。

SYN Proxy的局限性及与其他防护手段的配合

SYN Proxy不是银弹。它只能防护SYN Flood这一种攻击形式,对ACK Flood、HTTP慢速攻击、TLS握手耗尽等无能为力。而且SYN Proxy在处理完三次握手后,连接就被移交给真实服务,如果攻击者完成握手后发送的是HTTP层的恶意请求,SYN Proxy完全感知不到。因此它必须与WAF、限速模块、行为分析等上层防护配合使用。

另一个容易被忽略的问题是:SYN Proxy在验证客户端ACK时,依赖的是Cookie中的加密哈希。如果攻击者具备抓包能力,能够收到SYN-ACK并正确回复ACK,那么SYN Proxy会认为这是合法客户端并建立连接。这意味着SYN Proxy对反射放大攻击中的“真实反射器”无能为力——那些被利用的反射服务器本身就是真实的TCP栈,它们能正确完成三次握手。这种情况下需要在应用层做进一步的验证,比如要求客户端先完成一个TLS握手或返回一个HTTP Challenge。

还有一个生产环境中的常见坑:SYNPROXY与某些负载均衡设备的健康检查机制不兼容。负载均衡器通常发送SYN包来探测后端服务是否存活,如果SYNPROXY回复了带Cookie的SYN-ACK,而负载均衡器不完成三次握手就认为探测失败,会导致后端被错误地标记为Down。解决方法是将负载均衡器的探测IP加入白名单,绕过SYNPROXY规则。

SYN Proxy在容器化和云原生环境中的部署要点

在Kubernetes环境中,Pod的网络流量通常经过iptables或IPVS的NAT转换。SYNPROXY规则需要配置在宿主机的网络命名空间中,并且要注意规则的作用点。由于Service的ClusterIP是虚拟IP,数据包经过DNAT后目的地址会改变,SYNPROXY规则必须匹配DNAT之后的真实目的地址和端口。通常将规则放在宿主机的INPUT链上,并匹配NodePort或直接路由到Pod的流量。

如果使用了Service Mesh的Sidecar模式,流量路径更加复杂。Envoy或Linkerd-proxy会拦截进出Pod的流量,SYN Flood攻击可能在Sidecar层就被消耗掉一部分,但也可能导致Sidecar本身的连接表被打满。在这种情况下,SYNPROXY应该部署在Sidecar之前的宿主机网络层,作为第一道防线,保护Sidecar和后端Pod两层。

云服务商提供的负载均衡器通常自带SYN Flood防护能力,但如果流量已经到达后端实例,说明云防护可能被绕过或者攻击规模在云防护阈值以下。在实例内部署SYNPROXY作为纵深防御的一环,是成熟运维体系的标配。需要注意的是,云环境的底层网络可能有MTU限制或封装开销,MSS参数要根据云厂商文档调整,通常AWS和阿里云的标准MTU为1500,但使用VPC对等连接或VPN网关时可能降至1400甚至更低。

SYN Proxy技术从提出至今已超过二十年,但它依然是四层抗DDoS体系中最可靠、最轻量的一环。它的价值不在于技术有多新颖,而在于它精准地抓住了SYN Flood攻击的根本弱点:攻击者可以伪造源IP发送SYN,却无法收到路由到伪造IP的SYN-ACK,自然也就无法完成三次握手。这个逻辑上的不对等,正是防御者可以永远利用的优势。在生产环境中正确部署SYN Proxy,配合合理的阈值策略和内核调优,可以让服务器在数十万PPS的SYN Flood攻击下依然保持对正常用户的稳定服务。