服务器连不上了?连接耗尽型DDoS防护实战

有一类 DDoS 攻击看不到大流量,几十 Mbps 就能把服务器打到拒绝服务:它打的是连接资源而不是带宽。本文用 9 个问答讲清 连接耗尽型 DDoS的攻击对象、判定方法、可复制的应急配置、连接阈值与云清洗阈值该怎么设。

Q:什么是连接耗尽型攻击?和流量洪峰有什么不同?

它的目标不是塞满你的出口带宽,而是占满服务器维护连接所需的资源。典型资源有五类:

  • 内核连接跟踪表(nf_conntrack):每个新建连接占一条表项,默认上限通常只有 65536。
  • 文件描述符:Nginx 的连接上限受 worker_connections 与 ulimit 双重约束。
  • 半开队列:tcp_max_syn_backlog 与 somaxconn 溢出的 SYN 直接被丢弃。
  • 本地端口:大量短连接(尤其回源、代理场景)会把 TIME_WAIT 堆起来,最终无端口可用。
  • 内存:每条连接在内核与用户态都有固定开销,连接数上去了内存跟着涨。

判定特征很好记:入向带宽不高、CPU 不一定高、日志里请求数也不多,但服务就是不可用。所以只盯带宽监控的站,往往被打穿了还没发现。

Q:怎么快速判断是不是连接耗尽型?

三条命令看三个地方,两分钟内能定性:

# 1) 连接跟踪表是否接近上限,是否已在丢包
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
dmesg | grep -i "nf_conntrack"

# 2) 连接状态汇总:TIME_WAIT / SYN_RECV 是否异常堆积
ss -s
ss -ant state syn-recv | wc -l
ss -ant state time-wait | wc -l

# 3) 半开队列是否溢出
netstat -s | grep -iE "listen|overflow"

判据:conntrack_count 逼近 max,或 dmesg 出现 table full,就是连接耗尽型。如果同时看到 SYN_RECV 数量上千、possible SYN flooding 提示,说明攻击打的是半开队列;如果 TIME_WAIT 大量堆积,则可能是回源段被打或上游连接没复用。

Q:conntrack 表满了为什么会连 SSH 都进不去?

因为丢包发生在协议栈最前面。表满之后,内核对新连接的处理是直接丢弃而不是拒绝,客户端表现是「一直连不上、超时」,而不是「连接被拒绝」,所以看日志也看不到明显报错。

已建立的连接(比如你之前开着的 SSH 会话)通常还能继续用,新建连接全部失败。应急时如果要保住管理通道,可以让管理端口绕过连接跟踪:

# 让 SSH 端口绕过 conntrack,保证带外管理可用
iptables -t raw -A PREROUTING -p tcp --dport 22 -j NOTRACK
iptables -t raw -A OUTPUT     -p tcp --sport 22 -j NOTRACK

注意 NOTRACK 会让该端口失去状态机保护,依赖 ESTABLISHED 的规则不再匹配,且源 IP 不能再用 conntrack 维度做限制。只在应急期间使用,事后评估是否保留。更稳妥的做法是提前准备控制台或带外管理通道。

Q:应急时最该先调哪几个参数?

按收益排序,先缩短超时回收资源,再抬高上限扩容:

# /etc/sysctl.d/99-antiexhaust.conf
# 缩短超时,快速回收被占用的表项
net.netfilter.nf_conntrack_tcp_timeout_established = 600
net.netfilter.nf_conntrack_tcp_timeout_syn_recv    = 30
net.netfilter.nf_conntrack_tcp_timeout_time_wait   = 30

# 抬高上限,给正常业务留余量
net.netfilter.nf_conntrack_max = 1048576
net.ipv4.tcp_max_syn_backlog   = 8192
net.core.somaxconn             = 32768
net.ipv4.tcp_syncookies        = 1
net.ipv4.tcp_max_tw_buckets    = 262144

# 上游回源场景:允许复用 TIME_WAIT,扩大本地端口范围
net.ipv4.tcp_tw_reuse        = 1
net.ipv4.ip_local_port_range = 10240 65000
sysctl -p /etc/sysctl.d/99-antiexhaust.conf
sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max

三个参数值得单独说明:把 established 超时从默认 86400 秒降到 600 秒是收益最高的动作,能把僵尸表项回收速度提升百倍以上;tcp_syncookies 是兜底开关,队列满时仍能完成握手,代价是牺牲部分连接信息;somaxconn 必须和应用的 backlog 同步调大,只改内核不改 Nginx 或应用侧,瓶颈只是换了个位置。

Q:Nginx 层怎么限制并发,避免被单 IP 拖垮?

Nginx 有两个不同层级的连接限制:单 IP 与整站。两个一起用才有效,只配单 IP 会被分布式攻击绕过:

# nginx.conf 全局段
worker_rlimit_nofile 65535;

# events 段
events {
    worker_connections 10240;   # 单进程上限,实际并发 ≈ worker_processes × 该值
}

# http 段
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name        zone=perserver:10m;

server {
    limit_conn perip     50;      # 单 IP 并发连接
    limit_conn perserver 2000;    # 整站并发连接
    limit_conn_status 429;
    keepalive_timeout 15;         # 缩短空闲保持,释放表项
    keepalive_requests 100;
}

要点:worker_connections 与 worker_rlimit_nofile 要一起调,后者不够时前者调大无效;limit_conn 的 key 不接受空值,不能像 limit_req 那样用空 key 给白名单免限;静态资源尽量交给 CDN,源站连接数会立刻下降一个量级。

Q:连接阈值设多少才合理?

不要抄网上的数字,先测自己的基线。方法是取业务高峰与低谷各一段,记录稳定并发连接数:

# 采样 10 个点,估算正常并发连接基线
for i in $(seq 10); do ss -ant state established | wc -l; sleep 30; done

# 更细的采样:按秒看新建连接速率
sar -n SOCK 1 60 | tail -20

有了基线再设阈值,建议分三档,全部设成告警而不是直接封禁:

  • 单 IP 并发连接:超过基线人均值的 5 到 10 倍。浏览器同域并发通常不超过 6,可取 50 作为初值。
  • 单 IP 新建速率:正常用户不会每秒新建几十个连接,超过 20 个/秒即视为可疑。
  • 整体 conntrack 使用率:低于 60% 属正常,超过 80% 必须告警,这是最灵敏的指标。

核心原则:阈值先设成告警观察一到两周,再切换为自动处置。直接上自动封禁,很容易在大促或热点事件期间误杀正常用户。

Q:云防护或高防的清洗阈值该怎么设?

清洗阈值设错是常见问题:设太低会误洗,正常业务丢包;设太高则攻击穿透,源站照旧被打。做法是分两档并绑定动作:

  • 告警阈值:约等于正常峰值的 1.5 倍,只发通知,不做处置。
  • 清洗阈值:约等于正常峰值的 3 倍,同时不低于服务商套餐的起调下限。
  • 触发动作:告警后观察 5 分钟,若持续上涨再自动清洗,避免压力测试或热点造成的抖动误触。

要特别注意:连接耗尽型攻击可能完全不触发带宽清洗阈值,因为它的流量很小。所以除了带宽阈值,必须另外配置连接数阈值与新建连接速率阈值,否则高防会一直觉得「一切正常」,源站已经拒绝服务了。

Q:还有什么补充建议?

  • 监控三项必看:conntrack 使用率、文件描述符使用率、SYN_RECV 数量。这三个指标能覆盖绝大多数连接耗尽场景,比带宽曲线敏感得多。
  • 提前留出冗余:conntrack 上限与 FD 上限建议按正常峰值的 5 倍配置,攻击来的时候没时间慢慢调。
  • 区分攻击与自身泄漏:如果连接数是缓慢上涨且不回落,优先排查应用未正确关闭连接或 keepalive 配置过长,别一律当成攻击。
  • 回源段的连接也要限,上行链路被打满时表现和入向被攻击很相似,容易误判。

一句话总结:连接耗尽型攻击防的是「资源上限」而不是「流量大小」,把超时调短、上限留足、阈值分档,三类动作做到位就能挡住大部分场景。