Linux 内核参数导致连接数暴增,我用 ss + iptables 三分钟定位了 SYN Flood 元凶
Linux 内核参数导致连接数暴增,我用 ss + iptables 三分钟定位了 SYN Flood 元凶
凌晨两点,监控告警把我炸醒了。
不是业务异常,不是内存爆表,而是连接数 —— 平时稳定在 2000 左右的 ESTABLISHED,突然飙到 65000+。我第一反应是业务放量了,但 QPS 根本没涨。不对劲。
先看现场:这根本不是正常流量
登录服务器,ss -s扫一眼:
$ ss-sTotal:65012TCP:64800(estab120, closed64680, orphaned0, synrecv0, timewait0/0), ports0诡异。ESTABLISHED 只有 120,但 closed 状态有 64680。而且 timewait 是 0。
再细看一下连接状态分布:
$ ss-tan|awk'{print $1}'|sort|uniq-c|sort-rn64680SYN-RECV120ESTAB8TIME-WAITSYN-RECV 爆了。六万多个半开连接,这不是业务放量,这是 SYN Flood。
三步定位,用了不到三分钟
第一步:确认攻击特征
$ ss-tanstate syn-recv|awk'{print $5}'|cut-d:-f1|sort|uniq-c|sort-rn|head1200192.168.1.1011200192.168.1.1021200192.168.1.103来源 IP 分散但规律 —— 典型的伪造源地址 SYN Flood。每个 IP 发几百个 SYN,不贪多,就是想把你半连接队列塞满。
第二步:看内核队列是不是真的满了
$netstat-tan|grepSYN_RECV|wc-l64680$cat/proc/sys/net/ipv4/tcp_max_syn_backlog1024tcp_max_syn_backlog默认 1024,但 SYN-RECV 堆了六万多。怎么做到的?
因为 SYN Flood 的目标不是填满tcp_max_syn_backlog,而是利用 TCP 三次握手的机制:攻击者发 SYN,服务器回 SYN+ACK 并分配资源进入 SYN-RECV 状态,等待客户端回 ACK。伪造的源地址永远不会回 ACK,这些半开连接就一直在那占着,直到超时。
内核的超时时间由tcp_synack_retries和tcp_abort_on_overflow控制,默认能撑好几分钟。队列满了之后,正常用户的 SYN 进来直接被丢,连不上服务器。
第三步:确认正常业务受影响
$tail-n100/var/log/nginx/access.log|grep-c'"-" 502'47Nginx 日志里一堆 502,后端连不上。确认了,这不是"看着吓人但没事"的情况,是真的在影响业务。
止血:iptables 先做 SYN Cookie,再封异常源
SYN Cookie 是 Linux 内核自带的防御机制,原理简单但有效:收到 SYN 时不立即分配资源,而是把连接信息编码成一个 Cookie 放在 SYN+ACK 的序列号里。只有客户端真的回 ACK(携带 Cookie),服务器才验证 Cookie 并建立连接。伪造源地址的客户端不会回 ACK,也就不会消耗服务器资源。
启用 SYN Cookie:
$echo1>/proc/sys/net/ipv4/tcp_syncookies这行命令生效后,SYN-RECV 数量开始下降。因为新的 SYN 不再分配半开连接,正常用户只要回 ACK 就能连上。
但还不够。旧的六万多个 SYN-RECV 还在那占着,而且攻击没停。需要封异常来源。
iptables 限制 SYN 速率:
# 限制每个源 IP 的 SYN 速率:每秒最多 5 个新连接$ iptables-IINPUT-ptcp--syn-mlimit--limit5/second --limit-burst10-jACCEPT $ iptables-IINPUT-ptcp--syn-jDROP这两条的顺序很重要。第一条允许每秒 5 个、突发 10 个的 SYN,第二条把超限的 DROP。合法用户的正常连接不会触发限流,伪造源的洪水被挡住。
执行完 30 秒后再看:
$ ss-sTotal:328TCP:300(estab180, closed0, synrecv120, timewait0/0), ports0SYN-RECV 从 64680 降到 120,ESTAB 恢复正常。业务 502 停止。
长期方案:内核参数调优脚本
临时改proc重启会丢,写进sysctl.conf:
# /etc/sysctl.d/99-synflood.conf# 启用 SYN Cookie(关键)net.ipv4.tcp_syncookies=1# 扩大半连接队列net.ipv4.tcp_max_syn_backlog=65536# 缩短 SYN 重试次数,快速释放无效半连接net.ipv4.tcp_synack_retries=2# 开启 TCP Fast Open,减少握手 RTTnet.ipv4.tcp_fastopen=3生效:
$sysctl--system我还写了一个一键诊断脚本,放在巡检里:
#!/bin/bash# synflood-check.sh —— SYN Flood 快速诊断THRESHOLD=5000SYN_COUNT=$(ss-tanstate syn-recv2>/dev/null|wc-l)if["$SYN_COUNT"-gt"$THRESHOLD"];thenecho"[ALERT] SYN-RECV count:$SYN_COUNT(threshold:$THRESHOLD)"echo"Top source IPs:"ss-tanstate syn-recv|awk'{print $5}'|cut-d:-f1|sort|uniq-c|sort-rn|head-5echo""echo"Mitigation commands:"echo" sysctl -w net.ipv4.tcp_syncookies=1"echo" iptables -I INPUT -p tcp --syn -m limit --limit 5/second --limit-burst 10 -j ACCEPT"echo" iptables -I INPUT -p tcp --syn -j DROP"elseecho"[OK] SYN-RECV count:$SYN_COUNT"fi踩坑记录:三个容易搞错的地方
坑一:tcp_syncookies 默认没开
很多发行版为了"兼容性"默认关掉了 SYN Cookie。我查了一下,这台服务器的tcp_syncookies默认是 0。如果提前打开,这次攻击根本堆不到六万连接。
坑二:iptables 规则顺序写反
iptables 是链式匹配,先匹配的先执行。如果 DROP 写在 ACCEPT 前面,连第一条的速率限制都触发不了,所有 SYN 直接进黑洞。
正确的顺序必须是 ACCEPT 在前,DROP 在后。
坑三:只看连接总数,不看状态分布
刚开始我netstat -an | wc -l看到六万连接,还以为是业务增长。后来分状态看才发现是 SYN-RECV 异常。以后看连接数,一定要ss -s先看状态分布。
写在最后
SYN Flood 不是新攻击手段,但生产环境里真碰上的时候,能不能三分钟止血,取决于你平时有没有把tcp_syncookies打开、有没有现成的一键诊断脚本。
这次事件之后,我把tcp_syncookies=1写进了基线镜像,巡检脚本里也加了 SYN-RECV 监控。花十分钟做预防,省得凌晨两点爬起来救火。
如果你也在运维 Linux 服务器,建议现在就检查三个参数:
sysctlnet.ipv4.tcp_syncookiessysctlnet.ipv4.tcp_max_syn_backlogsysctlnet.ipv4.tcp_synack_retriestcp_syncookies如果不是 1,建议打开。免费的防御,不用白不用。
