香港服务器上的CentOS系统如何通过内核sysctl参数强化TCP安全,抵御大规模DDoS攻击?

我要先说一句大实话:靠 sysctl 并不能“无脑硬扛”一切 DDoS,但在“上游清洗/黑洞未就位、硬件设备来不及调”的那几分钟里,内核参数就是把服务从“立即崩盘”拉回“能喘上气”的最短杠杆。下面这篇,是我在香港机房值夜班时,真刀真枪调过的那份 SOP 和复盘。
香港葵涌机房的2:13 分,PagerDuty 把我从椅子上“弹”起来:
- SYN flood 突增,接入层 Nginx 连接建立率骤降,p99 超 8s,业务 502 断续出现。
CDN 走的是业务分流(并非全站强制回源保护),攻击点是直连 IP 的 L4 SYN 洪泛,峰值约 1.1–1.4 Mpps / 8–12 Gbps,源地址明显被伪造,ASN 分布集中在亚洲几家廉价云段。清洗线路要和运营商确认,BGP Flowspec 需要上游批准;这些都得花时间。第一响应只有一条路:先稳住 Linux 内核的队列与 TCP 行为。
1)现场环境与基线(我们到底在什么“车”上开)
| 项目 | 配置/版本 |
|---|---|
| 机房/链路 | 香港 HGC + PCCW 双上联,10G 接入,默认开本地 ACL |
| 服务器 | Dell R740(2 × Intel Xeon Silver 4210R,128GB RAM) |
| 网卡 | Intel X710-10G(ixgbe 驱动)单口上行 |
| OS | CentOS 7.9,内核 3.10.0-1160.el7.x86_64 |
| 角色 | L4/L7 接入(Nginx + Envoy),后端 gRPC |
| 内核调优基线 | tuned-profiles throughput-performance,其余接近默认 |
| 业务特点 | 短连接占比高,峰值新建连接 180k cps,报文小,RTT 亚太混合 |
问题征象(攻击初期 2–3 分钟内):
- netstat -s 中 listen queue overflows 快速增长
- ss -s 显示大量 SYN-RECV 与 orphaned 连接
- dmesg 有 TCP: Possible SYN flooding on port 443 日志
- 应用日志 accept() 超时,Nginx recv() failed (104: Connection reset by peer) 间歇出现
2)调优思路(顺序重要)
优先级:
网卡/驱动与收包队列 → 内核收包 backlog → TCP 三次握手与半连接 → Listen 队列与应用 backlog → 连接生命周期与内存水位 → 异常/片段/ICMP 行为 → 观察与回滚。
目标:
- 在 CPU 可承受范围内 更快丢弃垃圾、更少阻塞正当连接
- 让关键队列有“弹性”,但不被无限放大拖垮内存
- 把“危险的默认值”改成攻击期更稳妥的行为
3)核心 sysctl 参数与推荐值(CentOS 7 / 3.10 内核实测)
注:下面“默认值”指 CentOS 7 典型默认/接近默认;不同环境会略有差异。不要照抄到生产就跑,请先在压测或灰度机验证。加粗的为在本场景最关键的几项。
A. 队列与握手抗性
| 参数 | 默认 | 建议(攻击期) | 说明 |
|---|---|---|---|
| net.core.somaxconn | 128 | 32768 | 上调监听上限,配合应用 backlog(如 Nginx backlog=) |
| net.ipv4.tcp_max_syn_backlog | 256/1024 | 16384–65535 | 半连接队列增大,配合 syncookies |
| net.ipv4.tcp_syncookies | 1(有时 0) | 1 | 必须启用,内核在半连接溢出时使用 |
| net.ipv4.tcp_synack_retries | 5 | 3 | 降低重试,减少 SYN-ACK 压力 |
| net.ipv4.tcp_syn_retries | 6 | 3–4 | 降低主动发起方重试;服务端影响较小 |
| net.core.netdev_max_backlog | 1000 | 50000–100000 | NIC → 内核队列的积压上限,防突发丢包 |
B. 连接生命周期与内存
| 参数 | 默认 | 建议(攻击期) | 说明 |
|---|---|---|---|
| net.ipv4.tcp_fin_timeout | 60 | 20–30 | 缩短 FIN-WAIT-2 停留时间 |
| net.ipv4.tcp_max_orphans | 16384 | 65536–262144 | 孤儿连接上限,避免内核过早杀连接 |
| net.ipv4.tcp_orphan_retries | 7 | 3–4 | 孤儿重试次数 |
| net.ipv4.ip_local_port_range | 32768 61000 | 20000 65000 | 视 SNAT/主动连接需要扩大 |
| net.core.rmem_max / wmem_max | 212992 | 16M / 16M | 上调最大缓冲(结合 tcp_rmem/wmem) |
| net.ipv4.tcp_rmem | 4096 87380 6291456 |
4096 131072 16777216 |
自适应接收窗口 |
| net.ipv4.tcp_wmem | 4096 16384 4194304 |
4096 65536 16777216 |
自适应发送窗口 |
C. 协议安全与异常行为
| 参数 | 默认 | 建议(攻击期) | 说明 |
|---|---|---|---|
| net.ipv4.tcp_rfc1337 | 0 | 1 | 修复 TIME-WAIT 相关 RST 攻击 |
| net.ipv4.tcp_timestamps | 1 | 1(保留) | 关闭会失去 RTT 估计;一般保持 1 |
| net.ipv4.tcp_challenge_ack_limit | 100 | 1000–5000 | 抗 blind spoofing,提升 ACK 限额 |
| net.ipv4.icmp_echo_ignore_broadcasts | 0/1 | 1 | 忽略广播 ICMP |
| net.ipv4.icmp_ignore_bogus_error_responses | 0 | 1 | 忽略异常 ICMP 错误 |
| net.ipv4.conf.all.accept_redirects | 1 | 0 | 拒绝 ICMP 重定向 |
| net.ipv4.conf.all.send_redirects | 1 | 0 | 不发送重定向 |
| net.ipv4.conf.all.accept_source_route | 0/1 | 0 | 禁止源路由 |
| net.ipv4.conf.all.rp_filter | 0 | 2(loose) | 多上联/清洗回注时避免 strict=1 导致丢包 |
| net.ipv4.conf.all.log_martians | 0 | 1 | 记录异常源地址 |
| net.ipv4.ipfrag_high_thresh / low_thresh | 4MB/3MB | 256MB / 192MB | 提升碎片缓冲水位,抗 fragment 洪泛(视内存) |
⚠️ 不要启用:net.ipv4.tcp_tw_recycle(新内核已移除,会在 NAT/多路径场景下误杀),net.ipv4.tcp_abort_on_overflow=1(把拥塞直接 RST 给用户,极端情况下才考虑短时开启)。
4)一次性落地的配置文件
在 CentOS 7 上,我把临时参数集中写到了一个文件里,便于切换和回滚:
/etc/sysctl.d/99-ddos-hardening.conf
# Queue & handshake
net.core.somaxconn = 32768
net.core.netdev_max_backlog = 100000
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_synack_retries = 3
net.ipv4.tcp_syn_retries = 4
# Lifecycle & memory
net.ipv4.tcp_fin_timeout = 25
net.ipv4.tcp_max_orphans = 262144
net.ipv4.tcp_orphan_retries = 3
net.ipv4.ip_local_port_range = 20000 65000
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 131072 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
# Protocol safety
net.ipv4.tcp_rfc1337 = 1
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_challenge_ack_limit = 5000
# ICMP & routing
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
# Loose rp_filter,适配清洗回注/不对称路由
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2
net.ipv4.conf.eth0.rp_filter = 2
net.ipv4.conf.all.log_martians = 1
# IP fragments
net.ipv4.ipfrag_low_thresh = 201326592 # 192MB
net.ipv4.ipfrag_high_thresh = 268435456 # 256MB
应用与验证:
# 备份现有
sudo cp /etc/sysctl.conf /etc/sysctl.conf.bak.$(date +%F_%H%M)
# 加载新增文件
sudo sysctl --system
# spot check
sysctl net.core.somaxconn
sysctl net.ipv4.tcp_max_syn_backlog
sysctl net.ipv4.tcp_syncookies
5)NIC / 中断层的两把“螺丝刀”(非 sysctl,但决定能不能吃下包)
(1)增大 ring buffer & 开多队列收包:
# 查看 ring
ethtool -g eth0
# 典型把 RX/TX 都调到网卡上限(如 4096/4096)
ethtool -G eth0 rx 4096 tx 4096
# 开启多队列 RSS(X710 默认支持),核对 ethtool -l
ethtool -l eth0
(2)中断亲和 / RPS:
# 查看中断分布
cat /proc/interrupts | egrep 'eth0|ixgbe'
# 把网卡队列中断 pin 到不同 CPU(示例:队列0→CPU0,队列1→CPU1...)
echo 1 > /proc/irq/$(grep eth0-TxRx-0 -n /proc/interrupts | awk '{print $1}' | tr -d :) /smp_affinity
echo 2 > /proc/irq/$(grep eth0-TxRx-1 -n /proc/interrupts | awk '{print $1}' | tr -d :) /smp_affinity
# ...按位掩码设置
若 gro/gso/tso 导致 CPU 抖动,可短期关闭 gro 试验丢包改善(权衡吞吐):
ethtool -K eth0 gro off
6)应用层与内核配合(以 Nginx 为例)
# /etc/nginx/nginx.conf
events {
worker_connections 65535; # 与 net.core.somaxconn 对齐
multi_accept on;
use epoll;
accept_mutex off; # 攻击期更快 accept
}
http {
server {
listen 443 backlog=32768 ssl reuseport; # reuseport 拆分 accept 锁
# 其它 SSL/路由配置...
}
}
reuseport 在多 worker 下能把 accept 队列拆散,减少锁竞争;但需要与 somaxconn、backlog、worker_processes 一起看。
7)观测:怎么知道“救回来没”
即时观察:
watch -n1 'ss -s'
netstat -s | egrep -i "listen|SYN|cookies|backlog"
watch -n1 'tail -n +1 /proc/net/netstat /proc/net/snmp | egrep "(ListenOverflows|ListenDrops|Syncookies|OutOfWindowIcmps|InCsumErrors)"'
dmesg --follow | egrep -i "SYN flooding|martian"
期望变化(5–10 分钟内):
- TCP: Possible SYN flooding 仍可能出现,但ListenOverflows 不再线性飙升
- SyncookiesSent 上升,但 Established 比例恢复
- 应用的 502/超时明显下降,p99 从 >8s 回落到 <1.5s(我那次 12 分钟后基本稳定)
8)结果对比(本次处置的关键指标)
| 指标 | 调整前(峰值) | 调整后(稳定 10 分钟) |
|---|---|---|
| 入向包速(pps) | 1.3 Mpps | 1.3 Mpps(攻击未停) |
ListenOverflows |
每秒 +4k | 每秒 <50 |
SyncookiesSent |
少量 | 持续 5–8k/s |
| 新建连接成功率 | 40–55% | >95% |
| Nginx 502 比例 | 18% | <0.5% |
| p99 延迟 | 8–12s | 0.9–1.4s |
| CPU(总/软中断) | 70% / 35% | 65% / 28% |
没有“降流”,但让正当流量能排上队、握上手。随后上游 Flowspec 生效、ACL 精准封段,整体才彻底回到常态。
9)部署中遇到的坑 & 现场解法
rp_filter=1(strict)把清洗回注的流量当作伪造给干掉了
现象:上游清洗开启后,丢包反而加重。
解决:切到 rp_filter=2(loose),并针对回注接口明确设置。这个在多上联/不对称路由时非常关键。
只拉大 somaxconn 忘了应用 backlog
现象:内核 OK,但 Nginx 仍报 accept queue full。
解决:应用层 listen backlog 同步拉大,并启用 reuseport。
盲目关 tcp_timestamps 导致 RTT 估计失真 & 重传暴涨
现象:延迟指标古怪、吞吐下降。
解决:在我们的场景保留 tcp_timestamps=1,把重心放在 syncookies、syn_backlog。
被“优化建议”误导去开 tcp_tw_recycle
结论:坚决不要。在 NAT 背后会出现诡异连接复用导致的间歇失败;而且新内核已经移除了该选项。
tcp_abort_on_overflow=1 的副作用
它会在队列满时直接给客户端 RST——看似“清爽”,但用户体验极差,除非压测验证、并仅在极端短时使用。
IP 碎片洪泛撑爆 ipfrag
拉高 ipfrag_high/low_thresh 后配合上游过滤,效果明显;但要监控内存水位,避免 OOM。
10)回滚与分层预案
秒级回滚:
sysctl -w key=oldvalue 逐项恢复;或 mv 99-ddos-hardening.conf{,.bak}; sysctl --system
分层预案:
上游:BGP Flowspec/RTBH、接入 ACL
服务器:sysctl + NIC 队列 + 应用 backlog
应用:灰度降级、只读模式、动态开关某些重流控逻辑
11)可直接复用的最小化“急救清单”(攻防拉锯的前 10 分钟)
# 1. 开启 cookies + 拉大半连接/监听
sysctl -w net.ipv4.tcp_syncookies=1
sysctl -w net.ipv4.tcp_max_syn_backlog=65535
sysctl -w net.core.somaxconn=32768
sysctl -w net.core.netdev_max_backlog=100000
# 2. 降低握手重试,避免积压
sysctl -w net.ipv4.tcp_synack_retries=3
sysctl -w net.ipv4.tcp_syn_retries=4
# 3. 连接生命周期与孤儿控制
sysctl -w net.ipv4.tcp_fin_timeout=25
sysctl -w net.ipv4.tcp_max_orphans=262144
sysctl -w net.ipv4.tcp_orphan_retries=3
# 4. 安全项(不破路由)
sysctl -w net.ipv4.tcp_rfc1337=1
sysctl -w net.ipv4.tcp_challenge_ack_limit=5000
sysctl -w net.ipv4.conf.all.rp_filter=2
sysctl -w net.ipv4.conf.default.rp_filter=2
# 5. 观察关键计数器
watch -n1 'netstat -s | egrep -i "listen|SYN|cookie|backlog"'
同时联系上游开清洗/ACL/黑洞;sysctl 是争取时间,不是终局武器。
12)结尾:把“夜里救火”写进白天的 Runbook
那天凌晨 2:41,我们把队列稳住,上游也把几段 ASN 封了。机房走廊的安全门“咔哒”一声,保安巡检路过,冲我点了点头。
我把这套参数做成 Ansible role,写进了 “香港接入层 DDoS 紧急处置 Runbook”,并加了三个醒目的注释:
优先保证路由正确(rp_filter=2),否则清洗回注也是白搭
syncookies 必开,backlog“三件套”对齐(syn_backlog / somaxconn / 应用 backlog)
所有改动都要可回滚,并且配套观测项
后来再遇到类似的突发,我们从“手忙脚乱”变成了“有条不紊”。sysctl 不会替你打赢所有仗,但它足够快、足够可靠,能把你和你的用户一起,从溺水边缘拖回到能继续游的地方。
附:参数速查表(打印贴在机柜门内侧)
| 分类 | 关键参数(建议值) |
|---|---|
| 队列 | somaxconn=32768、netdev_max_backlog=100000、tcp_max_syn_backlog=65535 |
| 握手 | tcp_syncookies=1、tcp_synack_retries=3、tcp_syn_retries=4 |
| 生命周期 | tcp_fin_timeout=25、tcp_max_orphans=262144、tcp_orphan_retries=3 |
| 内存 | rmem_max/wmem_max=16M、tcp_rmem=4k 128k 16M、tcp_wmem=4k 64k 16M |
| 安全 | tcp_rfc1337=1、tcp_challenge_ack_limit=1000–5000、rp_filter=2 |
| 其它 | ipfrag_high/low_thresh=256M/192M、icmp_* 与 redirects 全部收紧 |