上一篇 下一篇 分享链接 返回 返回顶部

DDoS 防护分几层?基于香港服务器的清洗中心 + ACL + 行为识别的组合拳

发布人:Minchunlin 发布时间:2025-09-28 10:31 阅读量:805


香港机房的NOC凌晨4点给我打来电话:“源站80/443抖动,带宽墙外爆了,峰值3xx Gbps,疑似DNS/CLDAP反射混合流。”我一边穿鞋一边远程进跳板机,内心很平静:这不是第一次了,但这次我要把临时方案彻底变成标准化的三层防护——清洗中心 + ACL + 行为识别。下面就是那一夜之后,我搭出来并反复演练的实战体系与细节。

目标与方案概览(分几层?为什么这么分)

防护分三层(自外向内):

  • 清洗中心(L3/L4): 在香港同城或近场部署具备大带宽与BGP的清洗节点,承接攻击流量、做大流量黑洞/流量牵引、GRE 回注干净流。
  • ACL 层(边界/ToR/宿主机): 以无状态规则为主(丢弃已知放大端口、异常包、碎片、畸形TCP),在不开启或尽量减少连接跟踪(conntrack)的前提下降噪,避免CPU和内核表被打爆。
  • 行为识别(L7 / WAF / 网关): OpenResty(NGINX+Lua) 实现滑动窗口限速 + 动态封禁 + JS/Cookie Challenge,配合Fail2ban把恶意源写入ipset,必要时联动CDN/WAF。
  • 组合拳的好处:外层吃带宽和脏流,中层降噪、阻断已知坏模式,内层把“像人但不是人”的流量精准挑出来。把最贵的资源(CPU、连接跟踪、应用线程)留给真用户。

1. 现场拓扑与硬件清单

1.1 拓扑(文字示意)

Internet
   │
(上游ISP/IX, 支持RTBH/Flowspec)
   │  BGP
[清洗中心集群:Scrubber-01/02]  -- GRE -->  [源站VPC/机架: HK-Rack-A/B]
        │                                    │
     BGP引流                               ACL(ToR/宿主)
        │                                    │
   大流量过滤                          OpenResty 行为识别 (L7)
        │                                    │
     回注干净流                              应用/业务

1.2 硬件与系统参数

角色 规格建议 网卡 OS 备注
清洗节点 ×2 Xeon Gold 6330 同级/或 EPYC 7xx2,128–256GB RAM Intel X710/XL710 10/25G 或 Mellanox CX4/5 40/100G CentOS 7.9(产品线要求),可选 ELRepo 内核 5.4+ 多队列RSS,SR-IOV可选;RAID1 SSD 系统盘
源站/网关 16–32核,64–128GB Intel X520/X710 10G CentOS 7.9 NGINX/OpenResty、iptables/ipset、Fail2ban
ToR/边界 厂商交换机/路由器(支持ACL/Flowspec更佳) 能跑sFlow/NetFlow便于告警

注:CentOS 7默认3.10内核,XDP体验一般;若追求高性能包处理,可通过ELRepo升级至5.4/5.10 LTS(我线下是用5.4,稳定)。

2. 前期准备(BGP、前缀与GRE)

申请/24或可被上游单独牵引的/32业务IP集合;与上游协商RTBH community(不同运营商值不同,以下示例用65535:666占位)。

在清洗中心与上游建立BGP;与源站侧打通GRE(两端固定公网IP),建议TTL=255,MTU 1436–1476(取决于外链MTU)。

选配Flowspec:若上游支持,能下发临时基于五元组/端口的限流/丢弃策略,反应极快。

3. 清洗中心部署(L3/L4)

3.1 安装基础组件

# FRRouting for BGP
yum install -y frr frr-pythontools

# ExaBGP 用于自动化注入RTBH/引流前缀
pip install exabgp

# FastNetMon 社区版(或商业版)用于阈值检测->BGP动作
# 社区版编译略,生产可用商业版更稳。

3.2 FRR 基础BGP配置(/etc/frr/frr.conf)

frr defaults traditional
log syslog informational
service integrated-vtysh-config
!
router bgp 65010
 bgp router-id 103.11.22.33
 neighbor 203.0.113.1 remote-as 64512
 neighbor 203.0.113.1 ebgp-multihop 2
 neighbor 203.0.113.1 timers 10 30
 !
 address-family ipv4 unicast
  network 198.51.100.0/24
 exit-address-family
!
line vty

真实AS与对端按现场改。network宣告你需要可牵引的前缀。

3.3 GRE 回注(清洗中心侧)

# 假设清洗节点公网IP:103.11.22.33;源站公网IP:103.11.22.44
ip tunnel add gre-clean mode gre local 103.11.22.33 remote 103.11.22.44 ttl 255
ip addr add 169.254.100.1/30 dev gre-clean
ip link set gre-clean mtu 1436 up

# 将被清洗的目标/32 指向 GRE
ip route add 203.0.113.10/32 dev gre-clean

坑1:MTU。GRE叠加头部~24字节,若不调,会出现间歇性访问异常、重传高。经验值:1436–1460,并在回注侧做MSS Clamping(见后文)。

3.4 iptables(清洗中心)无状态过滤模板

# 常用ipset
ipset create blacklist hash:ip timeout 600
ipset create whitelist hash:ip
# 放大端口(可按需微调,部分业务要放行DNS/UDP)
ipset create amp_ports hash:ip,port
for p in 19 53 123 389 11211 1900 10074 10080 6379; do
  ipset add amp_ports 0.0.0.0,$p
done

# 基础链
iptables -t raw -A PREROUTING -p udp -j CT --notrack
iptables -A INPUT -m set --match-set blacklist src -j DROP
iptables -A INPUT -m state --state INVALID -j DROP
iptables -A INPUT -f -j DROP                           # 丢碎片
iptables -A INPUT -p udp -m set --match-set amp_ports dst,port -j DROP
iptables -A INPUT -p tcp --tcp-flags ALL NONE -j DROP  # Null
iptables -A INPUT -p tcp ! --syn -m state --state NEW -j DROP # 异常新建
# 业务端口白名单
iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 6 --name SSH -j DROP
iptables -A INPUT -p icmp -j ACCEPT
iptables -A INPUT -j DROP

3.5 FastNetMon + ExaBGP 联动(示意)

FastNetMon检测某个**/32** PPS/bps超阈值 → 调用ExaBGP脚本 → 注入RTBH或把该/32引流到清洗中心;

恢复后撤销。

ExaBGP 简例(/etc/exabgp/rtbh.conf):

neighbor 203.0.113.1 {
  router-id 103.11.22.33;
  local-address 103.11.22.33;
  local-as 65010;
  peer-as 64512;
  api {
    processes [ rtbh ];
  }
}
process rtbh {
  run /usr/local/bin/rtbh.py;
}

rtbh.py(伪代码):

#!/usr/bin/env python3
import sys
for line in sys.stdin:
    victim = line.strip()
    # 注入黑洞前缀,打上上游约定的community(示例值)
    print(f"announce route {victim}/32 next-hop self community [ 65535:666 ]")
    sys.stdout.flush()

坑2:Community 值一定与ISP确认,否则黑洞无效或误影响其它路由。

4. ACL 层(边界/ToR/宿主机)

4.1 ToR/边界(Cisco 风格示例,按设备语法调整)

ip access-list extended DDOS-EDGE
 deny   ip any 0.0.0.0 255.0.0.0
 deny   ip any 10.0.0.0 0.255.255.255
 deny   ip any 172.16.0.0 0.15.255.255
 deny   ip any 192.168.0.0 0.0.255.255
 deny   udp any any eq 19
 deny   udp any any eq 123
 deny   udp any any eq 11211
 deny   udp any any eq 1900
 permit tcp any host 203.0.113.10 eq 80
 permit tcp any host 203.0.113.10 eq 443
 permit icmp any any
 deny   ip any any log

4.2 宿主机 ACL(更细致、避免conntrack踩雷)

# 1) 允许已建立连接
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# 2) SYNPROXY 保护 80/443
modprobe nf_synproxy_core
iptables -t raw -A PREROUTING -p tcp -m tcp --syn -j CT --notrack
iptables -A INPUT -p tcp -m tcp --dport 80 -m synproxy --synproxy-options mss timestamp wscale sack-perm -j SYNPROXY
iptables -A INPUT -p tcp -m tcp --dport 443 -m synproxy --synproxy-options mss timestamp wscale sack-perm -j SYNPROXY

# 3) UDP 流量不入跟踪,直接策略化处理
iptables -t raw -A PREROUTING -p udp -j CT --notrack
iptables -A INPUT -p udp -m length --length 0:32 -j DROP    # 异常极短包
iptables -A INPUT -p udp -m multiport --dports 19,123,11211,1900,389 -j DROP

# 4) 基于ipset的黑白名单
ipset create wl hash:ip
ipset create bl hash:ip timeout 3600
iptables -A INPUT -m set --match-set wl src -j ACCEPT
iptables -A INPUT -m set --match-set bl src -j DROP

# 5) 兜底
iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
iptables -A INPUT -p icmp -j ACCEPT
iptables -A INPUT -j DROP

5. 行为识别(L7):OpenResty + Fail2ban + ipset

5.1 OpenResty 安装与基本配置

# 安装 OpenResty(NGINX+LuaJIT)
yum install -y openresty openresty-resty

nginx.conf(核心片段)

worker_processes auto;
events { worker_connections  65535; use epoll; }

http {
    lua_shared_dict rl_store 128m;
    lua_shared_dict ban_store 32m;

    # 记录特殊日志,便于Fail2ban读
    log_format antibot '$remote_addr|$http_user_agent|$request|$status|$request_time|$upstream_response_time|$http_x_forwarded_for|AB:$upstream_addr';

    server {
        listen 80 reuseport;
        server_name _;

        # 行为识别:滑动窗口限速 + 动态封禁 + 简单挑战
        access_by_lua_block {
            local ban = ngx.shared.ban_store
            local rl  = ngx.shared.rl_store
            local ip  = ngx.var.remote_addr
            local ua  = ngx.req.get_headers()["User-Agent"] or ""
            local uri = ngx.var.uri

            -- 动态黑名单
            if ban:get(ip) then
                return ngx.exit(ngx.HTTP_FORBIDDEN)
            end

            -- 低成本挑战:无JS Cookie的首访,要求获取_cookie后再访问
            local ck = ngx.var.cookie_abtk
            if not ck then
                ngx.header["Set-Cookie"] = "abtk=" .. ngx.time() .. "; Path=/; HttpOnly"
                return ngx.redirect("/__probe", 302)
            end

            -- 细粒度限速:IP+URI 5秒100次
            local key = ip .. "|" .. uri
            local cur, err = rl:incr(key, 1, 0, 5)
            if not cur then rl:set(key, 1, 5); cur = 1 end
            if cur > 100 then
                rl:set("ban:"..ip, true, 600)
                ban:set(ip, true, 600)  -- 10分钟
                ngx.log(ngx.ERR, "ABUSE:", ip, "|", ua)
                return ngx.exit(429)
            end
        }

        location = /__probe { return 204; }

        location / {
            access_log /var/log/nginx/antibot.log antibot;  # 给Fail2ban看
            proxy_pass http://backend_pool;                 # 你的上游应用
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}

5.2 Fail2ban 将“行为命中”同步进 ipset

过滤器(/etc/fail2ban/filter.d/antibot.conf)

[Definition]
failregex = ^<HOST>\|.*\|.*\|(?:429|403)\|.*\|.*\|.*\|AB:.*$
ignoreregex =

监狱配置(/etc/fail2ban/jail.d/antibot.local)

[antibot]
enabled  = true
port     = http,https
filter   = antibot
logpath  = /var/log/nginx/antibot.log
maxretry = 1
findtime = 10
bantime  = 3600
action   = iptables[name=HTTP, port=http, protocol=tcp]
           iptables-allports[name=ALL, protocol=all]
           ipset[name=bl, banip]

效果:OpenResty命中行为阈值→写特定日志→Fail2ban立刻把源IP扔进ipset:bl,在ACL层即被丢弃,压力不落到应用线程。

6. 系统内核与网卡调优(关键,不做等于白搭)

6.1 sysctl(/etc/sysctl.d/99-ddos.conf)

net.core.somaxconn = 65535
net.core.netdev_max_backlog = 250000
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_syncookies = 1
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.ipv4.conf.all.rp_filter = 0
net.netfilter.nf_conntrack_max = 2097152
net.netfilter.nf_conntrack_buckets = 524288
net.ipv4.tcp_mtu_probing = 1

应用:

sysctl --system

6.2 网卡与中断

# 按CPU核数调多队列
ethtool -L eth0 combined 16

# 关闭大包分段以降低突发延迟(按业务评估)
ethtool -K eth0 gro off lro off gso off tso off

# RPS/XPS 绑定,分散中断
echo ffff > /sys/class/net/eth0/queues/rx-0/rps_cpus
echo ffff > /sys/class/net/eth0/queues/tx-0/xps_cpus

# IRQ 亲和
for i in /proc/irq/*/eth0*/smp_affinity; do echo 55 > $i; done

6.3 GRE 端MSS Clamping(源站侧)

iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
 -j TCPMSS --clamp-mss-to-pmtu

7. 参数表(落地可对标)

类别 参数 建议值 说明
L3/L4 检测阈值 PPS 200k–500k/主机 视业务峰值调
L3/L4 丢弃端口 UDP 19, 123, 11211, 1900, 389, 6379 放大常见端口
L7 限速 IP+URI 5秒100次 滑动窗口
动态封禁 时长 10–60分钟 Fail2ban + ipset
conntrack nf_conntrack_max ≥ 2M 并调buckets
BGP 心跳 keepalive/hold 10/30s 故障更快收敛
GRE MTU mtu 1436–1460 配合MSS钳制

8. 验证与演练

  • 基线抓包:tcpdump -ni eth0 "udp and (port 123 or port 1900)" 确认降噪效果;
  • 压测(实验室用hping3模拟SYN/UDP洪泛,严禁对外网):验证SYNPROXY回源连接是否稳定;
  • 拉闸演练:手动触发ExaBGP注入/撤销RTBH,观察上游牵引/回切延迟;
  • 回注路径:从清洗中心到源站的GRE流量是否稳定,MSS设定是否避免分片。

9. 真实填坑记录(那一夜之后)

  • GRE MTU导致间歇502:刚开始MTU按默认1500,回注后偶发502/超时。抓包发现外层分片。把GRE接口调到1436并启用MSS clamp后恢复。
  • conntrack 爆表:一度尝试在INPUT前做复杂mangle标记,结果conntrack入表过多导致丢包。改成raw表CT --notrack处理UDP,并提升nf_conntrack_max,症状立消。
  • 单核瓶颈:iptables串行命中+网卡队列少,顶不住。开启多队列RSS、合理IRQ亲和,并用reuseport让NGINX多进程分流,CPU均衡。
  • 上游RTBH社区号误配:第一次注入没生效,马上联系NOC确认社区值。经验:把值写进Runbook,并在ExaBGP脚本里做白名单校验。
  • Fail2ban 过度封禁:早期规则把429和404都抓了,误杀SEO爬虫。修正filter,只针对429/403且带自定义标识的日志行,另把Google/Bing ASN加入白名单。

10. 运维Runbook(最小闭环)

  • 检测到异常峰值 → 检查清洗中心接口PPS → 若阈值超标,触发引流/RTBH。
  • 确认GRE回注稳定(MSS/RTT/丢包)→ 观察源站可用性。
  • ACL层审计:放大端口命中量、碎片包计数;必要时临时开Flowspec。
  • L7 行为看板:OpenResty限速命中、Fail2ban新增IP/分钟、Top URI。
  • 恢复:流量归于常态,渐进撤销RTBH/Flowspec;保留ACL与L7策略。
  • 复盘:导出netflow/sflow与NGINX日志,更新黑白名单与阈值。

11. 完整配置清单(可复制起步)

11.1 /etc/sysctl.d/99-ddos.conf

(见6.1)

11.2 清洗中心 iptables/ipset 模板

(见3.4)

11.3 宿主机 ACL + SYNPROXY 模板

(见4.2)

11.4 OpenResty antibot 片段 + Fail2ban 规则

(见5.1、5.2)

第二天早上七点,机房的空调还在呼呼作响。我一边喝着难喝的咖啡,一边看Grafana上平稳下来的曲线,心里很踏实。那晚之后,我们把所有操作写进Runbook、把脚本固化成Ansible角色、把NOC的RTBH社区号贴在机柜门上——不是因为恐惧攻击,而是尊重系统边界。

三层防护不是一劳永逸,它更像一支训练有素的队伍:清洗中心扛大旗,ACL守城门,行为识别做最后的“盘问”。当下次告警响起,我希望你也能像我一样,按下脚本,面不改色。

附录:快速排障清单(Checklist)

  •  GRE 端口可达、MTU与MSS正确
  •  BGP对等UP,RTBH/引流社区号正确
  •  ACL命中增加,CPU未飙升,conntrack未爆
  •  OpenResty限速有效、Fail2ban同步到ipset
  •  白名单(监控、CDN、搜索引擎)生效
  •  回切计划与回退窗口明确
目录结构
全文