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

UDP打洞与防护:利用香港服务器的 eBPF XDP 限速与 DDoS(L3/L4/L7)联动清洗实战

发布人:Minchunlin 发布时间:2025-09-24 19:16 阅读量:1049
我在香港葵涌机房的一排 42U 机柜里完成全部部署,操作系统统一为 CentOS 7.9(最小化安装)+ ELRepo kernel-ml 5.10.x,并通过 eBPF/XDP 在网卡驱动态进行速率限制与早期丢弃,结合 BGP Flowspec/RTBH、GRE 回注清洗、WAF/七层网关,最终实现 UDP 打洞业务在大流量下的可用性。

1. 凌晨 2:17 的 pager 与 2600 万 pps 的警报

凌晨 2:17,我被 PagerDuty 拉醒。香港区域的 NAT 穿透中继(我们内部叫 rendezvous 节点)延时从 12ms 猛涨到 800ms,UDP 丢包直奔 60%。Zabbix 图上“鲨鱼鳍”一样的入向曲线把我看懵了:2,600 万 pps,主要是 1200~1500 字节的随机 UDP Payload,典型的 CLDAP/SSDP 混洗。

我远程跳进边界路由器,ACL 基本满打满算,iptables 顶不住,Conntrack 也被拖死。好消息是:前一周我们刚把 rendezvous 节点换成支持 XDP 的 i40e/ixgbe/mlx5,预案里写着“先上 XDP,后联动清洗”。我照 runbook 一步一步把链路压住,5 分钟后业务延迟回落到 30ms,15 分钟内完成清洗回注,流量恢复平稳。后来复盘时,大家都说“这次真像在机房里救火一样”。


2. 需求与目标

  • 业务场景:P2P/VoIP/游戏的 UDP 打洞(STUN-like)+ 会话中继(TURN-like/自研 UDP Relay)。

  • 攻击面:L3/L4 UDP Flood、反射/放大、低速慢报、碎片攻击;L7 维度为 STUN/自定义握手的异常模式、字节段特征。

  • 目标

    1. 驱动态(XDP-DRV)快速丢弃垃圾报文,保住核上的调度与队列;

    2. 可动态下发 的 eBPF map 执行(按源 IP/5Tuple/端口段)令牌桶限速

    3. 联动外部清洗:BGP RTBH/Flowspec、GRE 清洗回注;

    4. L7 网关(QUIC/HTTP/自定义 UDP)协同,特征提取 + 动态封禁;

    5. 可观测 的指标(pps、bpf_drops、rate_limited、hot IP)闭环风控。


3. 机房与硬件清单(可复用)

型号/规格 关键参数 用途
机柜交换 Arista 7280SR(ToR) 48×10G SFP+, 6×100G QSFP28 上联清洗中心/核心
边界路由 Juniper MX204 BGP full table, Flowspec, 4×100G RTBH/Flowspec 下发
XDP 节点 Supermicro 1U + AMD EPYC 7313P 16C/32T, 128GB RAM Rendezvous/Relay
网卡(主) Intel X710-DA4(i40e) 4×10G, XDP-DRV 支持 XDP 丢弃与限速
网卡(备) Mellanox ConnectX-5(mlx5) 2×25G, RSS/TSO/GRO 冗余与迁移
系统盘 2× NVMe 1.92TB (RAID1) EXT4 日志/pcap 缓冲
时钟 PTP/Chrony 同步误差 < 100µs 指标与攻击回放对齐

注:CentOS 7 默认内核 3.10 对 eBPF/XDP 支持不足,必须使用 ELRepo kernel-ml ≥ 5.4,本文以 5.10 LTS 为例。


4. 网络拓扑与 IP 规划

(Internet) ──> [边界路由 MX204]
                  │    ▲  BGP(Flowspec/RTBH)
                  ▼    │
               [ToR Arista 7280SR]
                  │  10G/25G
      ┌───────────┼───────────┐
      ▼                         ▼
 [XDP Node A]               [XDP Node B]
      │   ▲  GRE 回注             │   ▲
      ▼   │                       ▼   │
 [清洗中心/云] <───────BGP/Netconf───────>
  • 自治域:自有 ASN,边界与上游/清洗厂商建立 eBGP;

  • 业务段203.0.113.0/24(示例),rendezvous/relay 分片段;

  • BGP 社区65000:666(RTBH 黑洞),65000:123(回注优先级);

  • GRE 回注tun0 端点与清洗中心双向健康检查(BFD)。


5. 系统与内核准备(CentOS 7.9)

# 1) 升级到 kernel-ml 5.10
sudo yum install -y https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm
sudo yum --enablerepo=elrepo-kernel install -y kernel-ml kernel-ml-devel
sudo grub2-set-default 0 && sudo reboot

# 2) 工具链与 bpftool/xdp-tools
sudo yum groupinstall -y "Development Tools"
sudo yum install -y clang llvm elfutils-libelf-devel libcap-ng-devel
# bpftool(推荐从源码或发行包构建)
# xdp-tools 用于 xdp-loader / xdp-filter

# 3) 驱动与网卡设置
sudo ethtool -K eth0 gro off lro off tso off gso off
sudo ethtool -G eth0 rx 4096 tx 4096
sudo sysctl -w net.core.rmem_max=67108864
sudo sysctl -w net.core.wmem_max=67108864
sudo sysctl -w net.core.netdev_max_backlog=250000

# 4) IRQ 绑核(示例)
echo 1 > /proc/irq/$(grep eth0 /proc/interrupts | awk '{print $1}' | tr -d :) /smp_affinity_list

6. XDP 设计:按源 IP 的令牌桶限速 + 特征早丢

策略

  1. 特征早丢:基于端口/长度/首部字段/速率阈值,直接 XDP_DROP;

  2. 令牌桶:对 src IP、可选 5-Tuple 建 LRU 哈希 map,按 pps/kpps 限速;

  3. 动态热区:把“热点 IP/网段”写入 eBPF map,限速更严;

  4. 观测:将 drop/limit/pass 计数器暴露给 Prometheus Exporter。

6.1 eBPF/XDP 程序(C, 简化示例)

// xdp_udp_rlimit.c (简化示例,仅演示思路)
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>

struct bucket { __u64 last_ns; __u32 tokens; };
struct config { __u32 pps; __u32 burst; __u16 dst_port_min, dst_port_max; };

struct bpf_map_def SEC("maps") rate_map = {
    .type = BPF_MAP_TYPE_LRU_HASH,
    .key_size = sizeof(__u32), // src IPv4
    .value_size = sizeof(struct bucket),
    .max_entries = 65536,
};

struct bpf_map_def SEC("maps") cfg = {
    .type = BPF_MAP_TYPE_ARRAY,
    .key_size = sizeof(__u32),
    .value_size = sizeof(struct config),
    .max_entries = 1,
};

struct bpf_map_def SEC("maps") counters = {
    .type = BPF_MAP_TYPE_PERCPU_ARRAY,
    .key_size = sizeof(__u32),
    .value_size = sizeof(__u64), // 0:pass 1:drop 2:limit
    .max_entries = 3,
};

static __always_inline void count(__u32 idx) {
    __u32 k = idx; __u64 *v = bpf_map_lookup_elem(&counters, &k);
    if (v) __sync_fetch_and_add(v, 1);
}

SEC("xdp")
int xdp_udp_rlimit(struct xdp_md *ctx) {
    void *data = (void *)(long)ctx->data;
    void *data_end = (void *)(long)ctx->data_end;

    struct ethhdr *eth = data;
    if ((void*)(eth + 1) > data_end) return XDP_PASS;
    if (eth->h_proto != __constant_htons(ETH_P_IP)) return XDP_PASS;

    struct iphdr *ip = (void*)(eth + 1);
    if ((void*)(ip + 1) > data_end) return XDP_PASS;
    if (ip->protocol != IPPROTO_UDP) return XDP_PASS;

    struct udphdr *udp = (void*)(ip + 1);
    if ((void*)(udp + 1) > data_end) return XDP_PASS;

    __u32 zero = 0; struct config *c = bpf_map_lookup_elem(&cfg, &zero);
    if (!c) return XDP_PASS;

    // 早丢:目的端口不在业务范围
    __u16 dport = bpf_ntohs(udp->dest);
    if (dport < c->dst_port_min || dport > c->dst_port_max) {
        count(1); return XDP_DROP;
    }

    // 令牌桶:src IPv4
    __u32 sip = ip->saddr;
    struct bucket *b = bpf_map_lookup_elem(&rate_map, &sip);
    __u64 now = bpf_ktime_get_ns();
    if (!b) {
        struct bucket nb = {.last_ns = now, .tokens = c->burst};
        bpf_map_update_elem(&rate_map, &sip, &nb, BPF_ANY);
        count(0); return XDP_PASS;
    }

    __u64 elapsed = now - b->last_ns; // ns
    __u64 add = (elapsed * c->pps) / 1000000000ULL; // 每秒 c->pps
    __u32 tokens = b->tokens;
    if (add > 0) {
        tokens = tokens + add;
        if (tokens > c->burst) tokens = c->burst;
        b->last_ns = now;
    }

    if (tokens == 0) { count(2); return XDP_DROP; }
    b->tokens = tokens - 1;
    count(0); return XDP_PASS;
}

char _license[] SEC("license") = "GPL";

6.2 编译与加载

clang -O2 -target bpf -c xdp_udp_rlimit.c -o xdp_udp_rlimit.o
# 使用 xdp-loader (xdp-tools)
sudo xdp-loader load -m drv -n xdp_udp_rlimit eth0 xdp_udp_rlimit.o

# 初始化配置:每源 IP 20k pps,突发 40k;业务端口 40000-41000
sudo bpftool map update pinned /sys/fs/bpf/tc/globals/cfg key 0 0 0 0 value \
  0x20 0x4e 0x00 0x00 \  # pps=20000 (little endian 注意按需调整)
  0x80 0x9c 0x00 0x00 \  # burst=40000
  0x40 0x9c \            # dst_port_min=40000
  0x10 0xa0               # dst_port_max=41000 (示意)

# 查看计数器(per-cpu 需汇总)
sudo bpftool map dump pinned /sys/fs/bpf/tc/globals/counters

小技巧:生产里我会把 cfg/rate_map 固定 pin 到 /sys/fs/bpf/xdp/…,并写一个简洁的 REST 小服务(或直接用 bpftool map update)供风控下发表更新。


7. tc/iptables 协同与七层策略

管线NIC → XDP (drop/limit) → tc ingress → iptables → 应用

  • tc(ingress)用于:

    • 小流量 SYN/ACK 限制、frag early drop;

    • 针对某些难在 XDP 做的匹配(如更复杂的掩码)进行补刀;

  • iptables:

    • ipset 热名单(/24 或 /20 段);

    • 连接跟踪的最小化使用(对 UDP 仅在握手阶段打开)。

# ipset 热名单
ipset create hotnet hash:net maxelem 65536
ipset add hotnet 198.51.100.0/24

iptables -I INPUT -p udp -m set --match-set hotnet src -j DROP
# 针对 UDP 碎片(非首片)
iptables -I INPUT -p udp -f -j DROP

L7(以自研 UDP 握手为例):

  • 第一次握手强制携带 8 字节 cookie(根据源 IP/时间片/HMAC 生成);

  • 未携带 cookie 的报文在 XDP 先行限速(低阈值),握手通过后将 srcIP→高阈值 写入 eBPF map;

  • QUIC/HTTP3 或 STUN 类流量:

    • 对 STUN Binding Request/Response 的方法码做白名单;

    • 限制 CHANGE-REQUEST 的组合,防止反射链条被利用。


8. 联动清洗:RTBH/Flowspec 与 GRE 回注

8.1 触发逻辑(Prometheus + Alertmanager)

  • XDP 指标bpf_drops_total{reason="limit|sig"}hot_ip_counttopk_src_pps

  • 接口指标if_in_ucast_pkts, if_in_discards

  • 告警阈值(示例):topk_src_pps > 200k pps OR if_in_ucast_pkts > 6m pps 持续 60s。

8.2 RTBH/Flowspec 下发

# RTBH:向上游宣告 /32 黑洞
set community 65000:666 add
announce route 203.0.113.123/32 next-hop self community [65000:666]

# Flowspec:基于 UDP 目的端口 40000-41000 且包长>1000 的限速/丢弃
# (逻辑示意,实际以厂商语法为准)
flow route add match ip-proto 17 dst-port 40000-41000 packet-length >1000 then rate-limit 50M

8.3 清洗回注(GRE)

ip tunnel add tun0 mode gre remote <scrubber_ip> local <xdp_node_ip> ttl 255
ip link set tun0 up
ip addr add 10.10.10.2/30 dev tun0
ip route add 203.0.113.0/24 dev tun0 table 200
ip rule add to 203.0.113.0/24 lookup 200

常见坑

  • GRE 头开销导致 MTU 下降,记得把回注链路与客户端路径 MTU 协调到 1476/1450;

  • 某些 i40e 驱动在高 PPS + GRE 环境下出现 XDP_TX 抖动,避免 RX/TX 混用,优先 XDP_DROP + 回注转发;

  • BFD 失步要有自动降级策略:回退至 RTBH


9. 可观测性与排障

9.1 指标

指标名 含义 期望/阈值
xdp_packets_total 进入 XDP 的总包 参考接口 PPS
xdp_drop_sig_total 特征早丢计数 攻击期明显上升
xdp_drop_rl_total 令牌桶限速丢弃 平稳限制在可承受范围
hot_ip_count 热 IP 数量 >100 触发风控游标调高

9.2 可视化

  • Grafana:入向 PPS、bpf_* 计数、topk 表格;

  • Loki:抽样 pcaps 元数据(长度分布、端口分布);

  • FlameGraph:应用侧 CPU hotspot,确认 XDP 护住了核。

9.3 快速抓包

# XDP 前镜像:硬件镜像到旁路盒或使用 AF_XDP 采样(生产慎用)
# 主机侧:只在低峰或抽样抓
tcpdump -ni eth0 udp and portrange 40000-41000 -s96 -w /data/pcaps/a.pcap

10. 压测与演练脚本

# 1) iperf3 UDP 压测(攻击模拟)
iperf3 -c <rendezvous_ip> -u -b 5G -l 1200 -t 60 -p 40001

# 2) mausezahn 随机端口/长度
mz eth0 -A rand -B <victim> -t udp "sp=rand,dp=40000-41000,len=rand(200,1400)" -c 0 -q

# 3) 自研握手压测(通过 cookie)
./udp_handshake_bench -dst <ip> -port 40000 -concurrency 20000 -pps 200k

演练关注点

  • 限速前后延时/抖动;

  • 应用 QPS 及 95/99 延迟;

  • 热 IP 自动上/下线时,阈值是否平滑。


11. 风控与演练清单(Runbook)

11.1 触发与处置

步骤 条件 动作 备注
1 if_in_ucast_pkts > 4m pps 60s XDP cfg:pps=10k/burst=20k 低阈值预收缩
2 hot_ip_count > 100 将 top50 源 IP 写入 hot map(更严限速) 自动化脚本
3 xdp_drop_rl_total 持续升高且业务丢包>5% 下发 Flowspec 限速 逐步从 50M→20M
4 仍异常 对特定 /24 执行 RTBH 与业务确认影响
5 回注链路就绪 开启 GRE 回注 验证 MTU/回路

11.2 复盘与回退

  • 导出 15 分钟窗口的 pcaps 样本(1:10k 抽样);

  • 保存 XDP map 快照(bpftool map dump);

  • 回滚 cfg 到常态:pps=20k/burst=40k

  • 关闭 GRE,撤销 Flowspec/RTBH。


12. 部署“坑点”与现场解法

  1. CentOS 7 + 新内核:升级 kernel-ml 后,旧 dkms 模块失配,提前验证驱动版本;

  2. i40e 队列数与 RSS:默认队列少导致单核打满,需 ethtool -L eth0 combined 16 + RPS;

  3. GRO/LRO:不关闭就会导致包聚合,XDP 看不到真实 PPS;

  4. XDP 与 VLAN:某部分交换机 QinQ 出口未剥头,XDP 程序没匹配到 IP,需在 ToR 修策略;

  5. 回注 MTU:GRE 降 MTU 忘同步导致“黑洞”,上线前做端到端 MTU 探测;

  6. eBPF map 热更新:bpftool 更新时偶发 EBUSY,采用“影子 map + 原子指针切换”策略;

  7. 应用握手超时:XDP 把无 cookie 报文限得过严,新客首包丢;通过“临时白名单 + 平滑升阈”解决。


13. 配置片段备忘

13.1 sysctl

cat <<'EOF' | sudo tee /etc/sysctl.d/99-udp-xdp.conf
net.core.netdev_max_backlog = 250000
net.core.rmem_max = 67108864
net.core.wmem_max = 67108864
net.ipv4.udp_rmem_min = 4096
net.ipv4.udp_wmem_min = 4096
net.ipv4.udp_mem = 262144 524288 1048576
net.ipv4.conf.all.rp_filter = 0
EOF
sysctl --system

13.2 systemd 单元(示例)

[Unit]
Description=XDP UDP rate limit
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/sbin/xdp-loader load -m drv -n xdp_udp_rlimit eth0 /opt/xdp/xdp_udp_rlimit.o
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

14. 验收与容量规划

  • 单机 10G:在 5~6M pps 入向下,XDP 保持 <20% 核占用;

  • 瓶颈:缓存 miss 与 L2/L3 cache 热点,尽量将热 map 控制 < 32K;

  • 扩容:横向堆 XDP 节点,ECMP 负载;

  • 容灾:BFD/GRE 掉线自动转 RTBH;

  • 压测基线:每季度一次全链路演练,记录阈值曲线。


15. 回到凌晨的机房

当天 03:02,我在机房的冷风里看着曲线慢慢回到基线,耳机里是 NOC 值班同事的“OK,延迟回 14ms 了”。复盘会有人问:为啥不用更大更贵的硬件? 我笑着摊手:我们要的不是钢板,而是能在最靠近网卡的地方,最早把垃圾丢掉;要的是能把“限速—清洗—回注—恢复”这条链路练熟。

后来我们又做了 3 次演练:一次针对 STUN 反射,一次模拟 QUIC 握手异常,一次回注链路双断。每次我都在 runbook 里多加了一行备注。现在,PagerDuty 再响,我不再惊醒,因为我知道我们有一套从驱动到七层的完整工具链

目录结构
全文