香港CN2服务器的DDoS盲区:国内探测全绿、SSH已断——conntrack溢出与ksoftirqd单核打满的六个自救技巧

先讲个真实场景

客户在群里刷屏:“香港机器挂了吗?我这边 ping 一直 12ms,监控全绿,但 Web 502、SSH 也连不上。”

登录带外 KVM 一看:uptime 是 47,top 里 ksoftirqd/0 100%,ss -s 显示 SYN-RECV 三万条,dmesg 里狂刷 nf_conntrack: table full, dropping packet。带宽图确实没动——因为攻击根本没走 CN2 那条线。

这就是香港CN2服务器最常见的误判:你监控的是回程,攻击进来的是去程,两条路根本不是一条。

盲区一:CN2 GIA 只保回程,你要看的是国际出口 RX

CN2 GIA(AS4809)在国内段是小容量精品线路,香港机房给你的“CN2”通常只覆盖香港→大陆方向的优化路由。攻击者从海外肉鸡打你的香港 IP,流量走的是国际出口(HKIX / PCCW / NTT 那一侧),压根不经过 CN2。

结果就是:你在国内各省 ping 值漂亮得不像话,业务却已经躺了。

# 别只看 netstat -i,它不显示 ring buffer 溢出
cat /proc/net/dev | awk 'NR>2 {print $1, $2, $4, $5}' | column -t

# 真正要看驱动的丢包计数,不同网卡前缀不一样
ethtool -S eth0 | grep -Ei 'drop|miss|no_buffer|fifo|discard'

# ixgbe 关键字:rx_no_buffer_count / rx_missed_errors
# bnxt:rx_discards_phy / rx_total_discard_pkts
# virtio:rx_drops

判据:如果 /proc/net/dev 里 RX 速率只有几十 Mbps,但 ethtool -S 的 rx_no_buffer_count 每秒涨几千,说明流量已经打满 NIC 的 RX ring,内核根本没机会计数——这是最容易被监控漏掉的形态。

顺手把采样粒度压到 1 秒,别用 5 分钟平均值糊弄自己:

sar -n DEV 1 60 | awk '$5>10000 {print}'   # 第5列是 rxbyt/s

盲区二:nf_conntrack 被半开连接撑爆,SSH 也跟着死

SYN flood 最阴的地方不是带宽,是 conntrack 表。每个 SYN 进来都建一条 SYN_SENT/SYN_RECV 状态的表项,默认要挂 60 秒才过期。10 万 pps 的 SYN 打过来,nf_conntrack_max 那点容量几秒钟就满,然后 内核开始无差别丢包——包括你自己的 SSH 会话。

dmesg -T | grep -i conntrack | tail -5
conntrack -C                                  # 当前表项数
sysctl net.netfilter.nf_conntrack_max
sysctl net.netfilter.nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_syn_recv   # 默认 60

三板斧,按顺序上:

1. 先把 SYN_RECV 的超时砍下来

sysctl -w net.netfilter.nf_conntrack_tcp_timeout_syn_recv=10
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_syn_sent=10

香港到大陆 RTT 撑死 60ms,SYN-ACK 重传 3 次也就几百毫秒,10 秒纯属宽裕。这一条能把攻击下的表项驻留时间压到 1/6,单机抗 SYN 能力直接翻几倍,比加内存便宜多了。

2. 对攻击面端口直接 NOTRACK,绕过 conntrack

# 只对入口网卡、只对 SYN,且不碰已建立的连接
iptables -t raw -A PREROUTING -i eth0 -p tcp --syn -m conntrack --ctstate NEW -j NOTRACK

# 注意:NOTRACK 后连接状态跟踪失效,别对需要 SNAT/DNAT 的业务口用
# 如果你的服务在后面做 DNAT,把 -d 限死在具体 IP 上

这一步是把防护前移到 raw 表,流量连 conntrack 的门都进不去。代价是该连接后续没状态跟踪,所以只建议用在纯入口的清洗面或静态服务上。

3. 确认 syncookies 真的开了

sysctl net.ipv4.tcp_syncookies
# 1 = 队列满时才启用;2 = 无条件启用
sysctl -w net.ipv4.tcp_syncookies=2
sysctl -w net.ipv4.tcp_synack_retries=2   # 降低 SYN-ACK 重传,加快半连接释放
sysctl -w net.ipv4.tcp_max_syn_backlog=65535

反常识点:开了 syncookies 之后,tcp_max_syn_backlog 对纯 SYN flood 基本失效,因为内核不再真实分配半连接队列。真正起作用的是 tcp_synack_retries 和上面那个 conntrack 超时。别在这上面浪费调优时间。

还有 net.ipv4.tcp_abort_on_overflow——默认 0 是对的,千万别改成 1,那等于告诉攻击者你的队列满了,直接 RST 回击,暴露存活状态。

盲区三:ksoftirqd 单核打满,多核 CPU 空转

1G 网卡单队列,几十万 pps 的小包全砸在 CPU0 上,ksoftirqd/0 100%,其他 15 个核在睡觉。这时候 SSH 的握手包排队都排不进去。

top -H -p $(pgrep -d, ksoftirqd)
# 看 ksoftirqd/0 是不是接近 100%

RPS/RFS 是软件层唯一的解法(硬件多队列网卡直接 XPS 绑核,另说):

# 8 核机器,把 eth0 rx-0 的中断处理分散到 CPU0-7
echo ff > /sys/class/net/eth0/queues/rx-0/rps_cpus
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries
for q in /sys/class/net/eth0/queues/rx-*/rps_flow_cnt; do echo 4096 > $q; done

坑点:RPS 的 CPU 掩码是十六进制位图,ff 代表 CPU0-7。如果你已经把网卡中断用 /proc/irq/*/smp_affinity 绑到某个核,RPS 目标集合要错开,否则软中断还是堆在一个核上打转。设完必须重新确认 /proc/net/softnet_stat 第三列(dropped)没在涨。

盲区四:ingress 限速不是防DDoS,是保内核的命

真正的清洗得上游做,但机器上有个动作必须做:把 UDP 反射类流量在进入协议栈前限速,保住 ksoftirqd 不被彻底打死,给上游调度留出时间窗口。

modprobe ifb numifbs=1
ip link set dev ifb0 up

tc qdisc add dev eth0 handle ffff: ingress
# 只把 UDP 甩进 ifb,TCP 业务流量不受影响
tc filter add dev eth0 parent ffff: protocol ip u32 \
  match ip protocol 17 0xff \
  action mirred egress redirect dev ifb0

tc qdisc add dev ifb0 root handle 1: htb default 10
tc class add dev ifb0 parent 1: classid 1:10 htb rate 20mbit burst 200k
tc filter add dev ifb0 parent 1: protocol ip prio 1 u32 \
  match ip dport 123 0xffff action police rate 100kbit burst 20k drop

DNS(53)、NTP(123)、SSDP(1900)、CLDAP(389)、Memcached(11211) 这几个反射源端口,挨个加 police。20mbit 的总配额足够真实 UDP 业务跑,但能让 50Gbps 的反射流量在 ifb 里被拍死,CPU0 能喘口气。

盲区五:本地 blackhole 路由会连自己一起黑

有人喜欢用 ip route add blackhole <攻击IP>。注意这是主机路由表,黑的是你的出向流量。如果你 blackhole 了整个 /24,你自己的回包也出不去,等于自宫。

ip route add blackhole 203.0.113.77/32   # 只黑单 IP,或者干脆别本地做

正确姿势是推给上游做 RTBH(RFC 3882)或 FlowSpec(RFC 5575)。FlowSpec 比 RTBH 精细得多,能按五元组丢,不用把整个 IP 黑洞化,业务还能活着。跟机房谈的时候直接报术语,别被“高防”两个字糊弄——能不能跑 FlowSpec、清洗触发阈值是多少、BGP 会话有几个 next-hop,这三个问题问出来对方就知道你懂。

顺带一提,像轻云互联这类香港CN2资源,BGP 会话和清洗阈值是可以在订购时明确写进服务描述的,比事后扯皮强。真打起来,上游愿不愿意帮你推 FlowSpec,往往比机器配置更决定结局。

盲区六:先抓一份攻击指纹,再谈防护

别一上来就调参数。5 秒钟抓一份流量,看清楚是 SYN 还是 ACK 还是 UDP 反射,方向完全不一样。

# 无 tcpdump 可用时的应急方案
timeout 5 tcpdump -i eth0 -nn -c 20000 -w /tmp/cap.pcap
tcpdump -nn -r /tmp/cap.pcap | awk '{print $5}' | cut -d. -f5 | sort | uniq -c | sort -rn | head

# 看源 IP 分布,判断是单点还是散列反射
tcpdump -nn -r /tmp/cap.pcap 'udp' | awk '{print $3}' | cut -d. -f1-4 | sort | uniq -c | sort -rn | head -20

# 看 TCP flags 组合,SYN flood 是 [S],ACK flood 是 [.]
tcpdump -nn -r /tmp/cap.pcap 'tcp' | awk '{print $6}' | sort | uniq -c | sort -rn

关键判断:源 IP 高度分散且集中在某个 UDP 端口 → 反射放大,直接找对应协议端口 police;源 IP 集中且是 TCP SYN → 单点肉鸡,可以精确封;源 IP 分散且是 TCP SYN → 僵尸网络,只能上游清洗 + 本地 syncookies 硬扛。

抓包时记得限 -c 和 -s,否则在 PPS 打满的机器上 tcpdump 自己就是压死骆驼的最后一根稻草。

小结成一张排查顺序表

  • 国内 ping 正常但业务挂 → 立刻看 ethtool -S 的 rx_drops,别信 CN2 延迟
  • SSH 连不上但 KVM 能进 → dmesg | grep conntrack,砍 syn_recv 超时
  • CPU 空转但响应慢 → top -H 看 ksoftirqd,上 RPS/RFS 错开绑核
  • UDP 反射打进来 → ifb + police,先保内核再谈清洗
  • 要上游介入 → 直接问 FlowSpec 支持,别满足于“我们有高防”

单机手段的天花板就在 1Mpps 上下,超了只能靠上游。但把上面这六条做扎实,至少能让你在被打的前 30 秒里还留着一条 SSH,把现场证据抓下来——这比什么都重要。