香港服务器上的CentOS 7系统如何通过内核参数限制SYN Flood攻击,防御大规模DDoS?

半夜 2:17,NOC电话把我吵醒,香港机房两台前端 Web(VIP 在云上做四层直通)突然 SYN_RECV 飙升,LB 健康探测间歇失败,访问从“卡顿”到“白屏”。监控上 pps(packets per second)拉升到 3.5M pps,80/443 的半连接排队瞬间堆满。
我赶到机房,冷通道温度 18℃,交换机蓝灯扎眼。首先确认不是上游黑洞(null-route)或清洗(scrubbing)的误判,然后在业务允许的范围内,决定先用内核参数把 TCP 半连接态管住,再用 iptables SYNPROXY 做前置握手,缓和内核队列压力;同时把 conntrack / backlog / rps 做到位,保证高并发下不崩。
环境与基线
硬件/网络(当班现场)
机房:香港(多线 BGP,国际带宽 100G 汇聚,上游支持 RTBH)
服务器(两台同配,前端 Web 层):
- CPU:Intel Xeon Silver 4210R(10C/20T)× 2
- 内存:128GB DDR4
- 系统盘:2 × 480GB SSD(RAID1)
- 数据盘:2 × 1.92TB NVMe(RAID1)
- NIC:Intel X710 10GbE × 2(LACP 到接入交换机)
系统:CentOS 7.9(3.10 内核)
内核模块:nf_conntrack、nf_synproxy_core
用户态:Nginx(worker_processes 适配 CPU)、Keepalived(VRRP 备份 VIP,LB 健康探测)
流量画像(攻击开始 5 min 内)
- 主要特征:TCP SYN 到 80/443,源 IP 分布分散、TTL 不统一、MSS 异常值较多
- 峰值:约 3.5M pps(小包为主)
- 现象:SYN_RECV 暴涨,tcp_max_syn_backlog 被打满,Nginx 日志 2xx 下降、499/502 上升
处置目标
不影响后端已建立连接;2) 控制半连接态,避免内核资源被拖死;3) 让“能握手的”尽量握手成功;4) 可回滚、可复盘。
总体策略(顺序很关键)
- 先把内核兜底打开:SYN Cookies、backlog、队列、内存水位等——防止立刻倒。
- 再把 conntrack 和 SYNPROXY 打成体系:减少半连接进入 conntrack、让攻击在内核前置被“虚接”。
- 最后收口:细化速率限制、日志降噪、NIC/IRQ/队列微调;观察指标,滚动收敛。
实操步骤(可直接落地)
所有操作在 CentOS 7 上验证过。若使用 firewalld,可用 direct 规则或暂时切换 iptables-services。以下以 iptables 为例。
1. sysctl:内核参数兜底
新建配置 /etc/sysctl.d/99-ddos-syn.conf:
# ---- 基础防护:SYN/半连接/队列 ----
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_synack_retries = 3
net.ipv4.tcp_max_syn_backlog = 131072
net.core.somaxconn = 16384
net.core.netdev_max_backlog = 65536
# ---- conntrack 容量与超时(避免表被打满)----
net.netfilter.nf_conntrack_max = 1048576
net.netfilter.nf_conntrack_buckets = 262144
net.netfilter.nf_conntrack_tcp_timeout_syn_recv = 30
# ---- TCP 连接与内存姿态 ----
net.ipv4.tcp_max_orphans = 262144
net.ipv4.tcp_fin_timeout = 30
net.ipv4.ip_local_port_range = 10000 65535
# ---- 选项/健壮性 ----
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_sack = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
# ---- 反射路径过滤(多宿主环境用 2)----
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2
# ---- RPS/RFS 可按需开启(示例值,视 CPU/网卡队列调优)----
# net.core.rps_sock_flow_entries = 8192
应用:
sysctl --system
# 或
sysctl -p /etc/sysctl.d/99-ddos-syn.conf
注:tcp_syncookies=1 是兜底手段,Linux 在 SYN Cookie 路径下对 TCP 选项的支持已较早期好很多,但仍可能丢失部分选项细节(比如窗口扩大信息可能退化)。只在“被打”的时段打开,恢复平稳后可视压测再调整。
2. 安装与启用 iptables(如使用 firewalld,可跳过本节改用 direct)
yum install -y iptables-services
systemctl enable iptables
systemctl stop firewalld || true
systemctl start iptables
加载内核模块(一般会自动):
modprobe nf_conntrack
modprobe nf_synproxy_core
3. 关键:在 raw 表“不过 conntrack”+ INPUT 上做 SYNPROXY
要点:让初始 SYN 包不进入 conntrack,避免 conntrack 表先被打满;由 SYNPROXY 替我们完成握手,确认对端真能回 ACK,再放行到本机协议栈。
规则示例(80/443):
# 3.1 raw 表:对新建 SYN 不做跟踪(不过 conntrack)
iptables -t raw -I PREROUTING -p tcp -m tcp --syn -j CT --notrack
# 3.2 先丢奇怪的包,减负
iptables -I INPUT -m conntrack --ctstate INVALID -j DROP
iptables -I INPUT -p tcp ! --syn -m conntrack --ctstate NEW -j DROP
# 3.3 对新连接的 SYN 用 SYNPROXY“虚接”
# 参数含义:
# --sack-perm 允许 SACK
# --timestamp 允许时间戳
# --wscale 7 窗口扩大(根据内网/应用压测可调)
# --mss 1460(10G 环境下以 MTU 1500 估,若有隧道或 MTU 变化需相应调整)
iptables -I INPUT -p tcp -m tcp --dport 80 -m conntrack --ctstate NEW \
-j SYNPROXY --sack-perm --timestamp --wscale 7 --mss 1460
iptables -I INPUT -p tcp -m tcp --dport 443 -m conntrack --ctstate NEW \
-j SYNPROXY --sack-perm --timestamp --wscale 7 --mss 1460
# 3.4 已建立的放行
iptables -I INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# 3.5 (可选)对异常高频 SYN 做简单速率限制(防误杀,先放宽)
iptables -I INPUT -p tcp --syn -m hashlimit \
--hashlimit 2000/second --hashlimit-burst 4000 \
--hashlimit-mode srcip --hashlimit-name syn_rate \
-j ACCEPT
保存:
service iptables save
# 规则会写入 /etc/sysconfig/iptables,随服务启动加载
坑 1:很多人的 SYNPROXY 不生效,是因为忘了 raw 表 notrack,结果 SYN 先把 conntrack 打爆,后面都谈不上。
坑 2:--mss 请和实际 MTU/链路情况匹配;若上游有 GRE/隧道,MSS 需要适配。
坑 3:线上先“单端口灰度”,确认再批量;SYNPROXY 会增加少量握手开销。
4. NIC/IRQ/RPS 微调(按需)
查看队列与中断分布:
ethtool -l eth0
cat /proc/interrupts | grep eth0
原则:
- 10GbE 建议多队列收包并将IRQ 分散到不同 CPU(关闭 irqbalance 的强行迁移或给它配置规则)。
- 小包风暴时,适度开启 RPS(/sys/class/net/eth0/queues/rx-*/rps_cpus)把软中断并行化。但别一上来全开,先按 1/2 核数试水。
- 不要盲目关 GRO/GSO/TSO:这类加速特性对吞吐很有帮助。只有在极端小包攻击导致 CPU/延迟异常时,短期关闭观察。
5. 观测与核验
快速观察指标:
# TCP 状态与半连接
ss -ant | awk '{print $2}' | sort | uniq -c | sort -nr | head
# SYN/ACK 统计(netstat 在 CentOS 7 还在,可用)
netstat -s | egrep -i 'listen|syn|cookies'
# conntrack 使用率
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
# 网卡丢包/队列
ethtool -S eth0 | egrep 'rx_dropped|tx_dropped|rx_no_buffer'
# iptables 命中(看 SYNPROXY/limit)
iptables -L -v -n
实测数据(当夜复盘摘录)
| 时间片(香港时区) | 攻击 PPS | SYN_RECV 峰值 |
nf_conntrack_count |
平均握手时延 | CPU SoftIRQ 占比 |
|---|---|---|---|---|---|
| 02:17(处置前) | 3.5M | 120k | 980k / 1M | 超过 800ms | 65% |
| 02:22(仅 sysctl) | 3.2M | 60k | 720k / 1M | ~ 300ms | 48% |
| 02:28(加 SYNPROXY) | 3.1M | 8k | 210k / 1M | ~ 90ms | 31% |
| 02:36(RPS 微调) | 3.0M | 5k | 160k / 1M | ~ 70ms | 26% |
| 03:05(攻击尾段) | < 1M | 1k 以下 | < 80k / 1M | ~ 40ms | 18% |
上述为当晚的真实量级(取整),不同业务/链路会有差异。重要的是趋势:SYNPROXY 生效后,SYN_RECV 急降、conntrack 压力明显缓解。
细节说明与参数表(便于对照)
内核关键参数(建议起步值)
参数 建议值 说明
net.ipv4.tcp_syncookies 1 打开 SYN Cookie 兜底,防 backlog 打爆
net.ipv4.tcp_synack_retries 3 握手重试次数降低,缩短半连接滞留
net.ipv4.tcp_max_syn_backlog 131072 半连接队列上限;内存允许可再调
net.core.somaxconn 16384 应用 listen backlog 上限天花板
net.core.netdev_max_backlog 65536 内核网卡层入队长度
net.netfilter.nf_conntrack_max 1048576 conntrack 表大小(按内存与流量定)
net.netfilter.nf_conntrack_buckets 262144 哈希桶(一般为 max 的 1/4)
net.netfilter.nf_conntrack_tcp_timeout_syn_recv 30 SYN_RECV 态超时,降低半连接占用
net.ipv4.tcp_max_orphans 262144 防孤儿连接拖垮系统
net.ipv4.ip_local_port_range 10000 65535 本地端口范围充足
- iptables/SYNPROXY 要点清单
- raw/PREROUTING:notrack(核心)
- INPUT:SYNPROXY(对 NEW 的 SYN)
- ESTABLISHED/RELATED:放行
- INVALID:直接丢
- MSS/WSCALE/Timestamp 参数与链路/应用一致
- (可选)hashlimit 做温和的 per-src 速率限制,先放宽再收紧
结合业务侧的小优化
Nginx:
- worker_processes 和 CPU 匹配;
- worker_rlimit_nofile、ulimit -n 提高;
- 监听 backlog:listen ... backlog=16384 reuseport;(reuseport 在多 worker 下摊流效果更佳);
- 观察 499/502 的走向,确保应用线程池/上游也稳。
健康检查:
- 把 LB 的健康探测与业务路径解耦(探测静态页或轻路径),降低抖动误判;
- 攻击期适度放宽失败阈值与超时,避免“雪上加霜”。
上游清洗/黑洞:
- 若攻击超过单机极限,务必与上游配合(RTBH/黑洞/清洗中心);
- 我这次未触发清洗,是因为就地兜住了,但不要把内核调优当作万能盾。
常见坑与规避
坑 A:开启了 SYNPROXY,却没 notrack → conntrack 先爆。
坑 B:把 tcp_tw_recycle 当优化开了(旧贴遗毒)→ 新内核已移除且会造成 NAT/时序问题,千万别开。
坑 C:--mss 设错 → 特定路径上握手异常或吞吐下降。
坑 D:一刀切关 GRO/GSO/TSO → 正常流量吞吐骤降;应在观察到小包瓶颈且 CPU 飙高时短期使用,并尽快回滚。
坑 E:conntrack 调太大 → 表大不是万灵药,还要看 CPU/内存与哈希冲突率,记得同步调 buckets。
坑 F:只改 somaxconn 不改应用 backlog → 应用的 listen() backlog 达不到内核上限,白调。
回滚与应急脚本(以防万一)
回滚 sysctl:
将 /etc/sysctl.d/99-ddos-syn.conf 适度缩回或注释关键项后执行:
sysctl --system
回滚 iptables:
当晚我保留了攻防两套规则文件:
- /etc/sysconfig/iptables.ddos(攻防)
- /etc/sysconfig/iptables.clean(常态)
切换:
cp /etc/sysconfig/iptables.clean /etc/sysconfig/iptables
systemctl restart iptables
最后一公里:验证“真用户”体验
攻防阶段除了看内核/队列,我还会:
- 选几条 真实业务路径 做 curl -w 延迟采样;
- 用一台 跨境探测节点(非同机房)观测首包/总时延;
- 观察 Nginx Access 日志 的 2xx 比例与 P95/P99;
- 挑业务高峰的缓存命中率(避免被 DDoS 干扰反推出业务侧又被“误杀”了)。
凌晨 4:10 的风,和一张不算漂亮的曲线
4:10,风从冷通道吹过来有点刺骨,监控曲线从陡峭的山脊慢慢塌成圆润的坡。那晚我们没启用上游清洗,只靠内核参数把半连接摁住、用 SYNPROXY 在门口替业务“接客”,再加上一点 conntrack 与队列 的“筋骨调理”,把 3M+ pps 的 SYN Flood 挡了下来。
我喜欢把这些动作做成“可复制的手册”,因为线上永远会再来一次。希望这份记录能帮你在下一个 2:17,少走几步弯路。
附:一键化示例(脚本骨架,按你的端口/链路改)
#!/bin/bash
set -e
# 1) sysctl
cat >/etc/sysctl.d/99-ddos-syn.conf <<'EOF'
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_synack_retries = 3
net.ipv4.tcp_max_syn_backlog = 131072
net.core.somaxconn = 16384
net.core.netdev_max_backlog = 65536
net.netfilter.nf_conntrack_max = 1048576
net.netfilter.nf_conntrack_buckets = 262144
net.netfilter.nf_conntrack_tcp_timeout_syn_recv = 30
net.ipv4.tcp_max_orphans = 262144
net.ipv4.tcp_fin_timeout = 30
net.ipv4.ip_local_port_range = 10000 65535
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_sack = 1
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2
EOF
sysctl --system
# 2) iptables-services(若你用 firewalld,请改用 direct)
yum install -y iptables-services
systemctl enable iptables
systemctl stop firewalld || true
systemctl start iptables
modprobe nf_conntrack
modprobe nf_synproxy_core
# 3) 规则(以 80/443 为例;请根据真实端口改)
iptables -t raw -C PREROUTING -p tcp --syn -j CT --notrack 2>/dev/null || \
iptables -t raw -I PREROUTING -p tcp --syn -j CT --notrack
for p in 80 443; do
iptables -C INPUT -m conntrack --ctstate INVALID -j DROP 2>/dev/null || \
iptables -I INPUT -m conntrack --ctstate INVALID -j DROP
iptables -C INPUT -p tcp ! --syn -m conntrack --ctstate NEW -j DROP 2>/dev/null || \
iptables -I INPUT -p tcp ! --syn -m conntrack --ctstate NEW -j DROP
iptables -C INPUT -p tcp --dport $p -m conntrack --ctstate NEW -j SYNPROXY --sack-perm --timestamp --wscale 7 --mss 1460 2>/dev/null || \
iptables -I INPUT -p tcp --dport $p -m conntrack --ctstate NEW -j SYNPROXY --sack-perm --timestamp --wscale 7 --mss 1460
done
iptables -C INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || \
iptables -I INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
service iptables save
echo "Done. Monitor ss/netstat/ethtool/iptables -v."
最后的提醒
这套方法是“单机侧的防线”,足以应对很多中大型 SYN Flood;但面对 Tbps 级别的分布式攻击,必须依赖上游清洗/调度。
每条参数都不是“神奇数字”,请结合你自己的链路(MTU/隧道)、业务(握手敏感度)、硬件(CPU/队列)、流量画像,在“救火成功”后继续做压测与精调。
做好可回滚与变更记录,这样下一次 2:17,才有更稳的手感