go服务无法防御syn flood,因其发生在tcp三次握手完成前,攻击包卡在内核半连接队列,未到达net.listen或http.server层;必须通过启用tcp_syncookies、调大tcp_max_syn_backlog、减小tcp_synack_retries及iptables限速等内核与网络层措施协同防御。

Go服务本身无法直接拦截SYN Flood——因为这类攻击在TCP三次握手完成前就卡死在内核连接队列,net.Listen甚至都收不到连接,更别提走到Go应用层逻辑。
为什么Go代码里加限流/超时没用
SYN Flood的恶意包根本不会触发listener.Accept()。服务器收到大量伪造源IP的SYN后,内核会把它们塞进半连接队列(SYN queue),等待ACK;但攻击者不发ACK,队列就一直被占着。只要队列满,后续所有连接(包括正常用户)都会被丢弃,你的Go程序连日志都打不出来。
- 你在
http.Server里设ReadTimeout或用context.WithTimeout,对SYN Flood完全无效——请求压根没建立成功 - 用
gobreaker熔断、rate.Limiter限流,也起不到作用——这些都在HTTP handler里生效,而SYN Flood连handler的边都碰不到 - 试图在
Accept()后做IP计数?晚了。半连接阶段不分配文件描述符,你根本拿不到客户端地址
必须在Linux内核和网络边界动手
防御SYN Flood是系统层问题,得靠内核参数+防火墙规则组合出手,不是改Go代码能解决的。
- 立刻启用
net.ipv4.tcp_syncookies=1:让内核用加密cookie代替队列存储,避免资源耗尽。临时生效:sysctl -w net.ipv4.tcp_syncookies=1;永久写入/etc/sysctl.conf - 调小重试次数:
net.ipv4.tcp_synack_retries=2,减少每个半连接占用时间 - 增大队列容量:
net.ipv4.tcp_max_syn_backlog=4096(默认常为128或256,太小) - 用
iptables做速率限制:只允许单IP每秒最多5个SYN包,超出直接DROPiptables -A INPUT -p tcp --syn -m limit --limit 5/s --limit-burst 20 -j ACCEPTiptables -A INPUT -p tcp --syn -j DROP
Go服务能做的有限但关键补充
内核和防火墙是第一道防线,Go层虽不能防SYN Flood,但能防止它引发的次生灾害——比如攻击退潮后大量超时连接堆积、或应用层HTTP Flood紧随其后。
- 给
http.Server设硬性超时:ReadTimeout、WriteTimeout、IdleTimeout全配齐,避免慢连接长期占着goroutine - 用
net.ListenConfig控制底层socket选项,例如开启SO_REUSEPORT(需Go 1.19+)分散连接压力:&net.ListenConfig{Control: func(fd uintptr) { syscall.SetsockoptInt32(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEPORT, 1) }} - 如果用
gnet等框架,启用TCPKeepAlive和TCPNoDelay,加快异常连接释放速度 - 务必记录
Accept()失败日志(如accept: too many open files),这是SYN Flood已突破内核防线的明确信号
真正容易被忽略的是:SYN Flood常是大规模DDoS的第一波试探,后面大概率跟着HTTP Flood或UDP反射攻击。只调内核参数不监控流量基线,等于守住了门却忘了关窗——ss -s和netstat -s | grep -i "syn"得加入巡检脚本,否则你根本不知道攻击是否正在发生。











