香港特惠服务器
三网CN2带宽,提供30M至100M大带宽,保障CN2/CMIN2/CU/PCCW四大运营商线路稳定接入

| 角色 | 型号/版本 | 备注 |
|---|---|---|
| 机型 | 1× AMD EPYC 7402P / 2× Intel Xeon Silver 4310 | 大核和 cache 有助于 XDP 并发 |
| 内存 | 128–256 GB | AF_XDP 场景更吃内存;我们本文不需要 AF_XDP |
| 系统 | Ubuntu 22.04.4 LTS(Jammy) | 建议 HWE 内核获取更新的 XDP 能力 |
| 内核 | 5.15(默认)/ 建议 6.8(HWE) | linux-image-generic-hwe-22.04 |
| 网卡(首选) | Mellanox ConnectX-5/6 (mlx5) 25/100GbE | 原生支持 XDP driver 模式、特性成熟 |
| 网卡(可选) | Intel XL710/XXV710 (i40e)、E810 (ice) | 支持 XDP,但细节与版本要看驱动 |
| 交换机侧 | 25G/100G 上联 | 尽量开 RSS,保证哈希均匀 |
# 1) 更新系统 + 安装 HWE 内核(建议)
sudo apt update
sudo apt install -y linux-image-generic-hwe-22.04 linux-headers-generic-hwe-22.04
# 重启后确认内核版本 >= 6.8
uname -r
# 2) 开发工具与 bpftool / xdp-tools
sudo apt install -y clang llvm make gcc libelf-dev libbpf-dev bpftool git pkg-config
# Jammy 多数源已有 xdp-tools;没有就从源码装
sudo apt install -y xdp-tools || true
# 关闭 GRO/LRO,避免聚合干扰入口判断(XDP 在 L2/L3 前,但保持一致性)
sudo ethtool -K $ETH gro off lro off gso off tso off tx-nocache-copy on
# 调大 RX Ring,提升爆发抗性
sudo ethtool -G $ETH rx 4096 tx 4096
# 合理配置队列(根据核心数/RSS 哈希情况调整)
sudo ethtool -L $ETH combined 16
# 打开 BPF JIT(一般默认开着)
echo 1 | sudo tee /proc/sys/net/core/bpf_jit_enable
echo 1 | sudo tee /proc/sys/net/core/bpf_jit_kallsyms
# 网络栈缓冲(XDP DROP 后仍需兼顾合法流量)
sudo sysctl -w net.core.netdev_max_backlog=250000
sudo sysctl -w net.core.rmem_max=67108864
sudo sysctl -w net.core.wmem_max=67108864
白名单优先:源 IP 白名单、业务端口白名单(例如 TCP/80、443,UDP 是否开放 443 取决于 QUIC)。
// SPDX-License-Identifier: GPL-2.0
// clang -O2 -g -target bpf -c xdp_ddos_kern.c -o xdp_ddos_kern.o
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/tcp.h>
// ------ 配置参数(可运行时修改) ------
struct cfg {
__u32 udp_pps_limit; // 单源 UDP 每秒阈值
__u32 syn_pps_limit; // 单源 SYN 每秒阈值
__u32 drop_frag; // 是否丢弃分片
__u32 allow_udp_quic; // 是否允许 UDP/443
};
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, __u32);
__type(value, struct cfg);
} conf SEC(".maps");
// 白名单:源 IP -> 1
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 10240);
__type(key, __u32); // ipv4 src
__type(value, __u8);
} whitelist SEC(".maps");
// 黑名单:源 IP -> 1
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 65536);
__type(key, __u32);
__type(value, __u8);
} blacklist SEC(".maps");
// 允许的 TCP 端口(目标端口)
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 64);
__type(key, __u16); // dst port
__type(value, __u8);
} allow_tcp_ports SEC(".maps");
// 允许的 UDP 端口(目标端口)
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 64);
__type(key, __u16);
__type(value, __u8);
} allow_udp_ports SEC(".maps");
// 单源 UDP 计数(滑动秒)
struct counter {
__u64 sec;
__u32 cnt;
};
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 262144);
__type(key, __u32); // ipv4 src
__type(value, struct counter);
} udp_rate SEC(".maps");
// 单源 SYN 计数(滑动秒)
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 262144);
__type(key, __u32);
__type(value, struct counter);
} syn_rate SEC(".maps");
// 统计
enum stat_key {
STAT_PASS = 0,
STAT_DROP_BLACK,
STAT_DROP_BADPORT_UDP,
STAT_DROP_FRAG,
STAT_DROP_UDP_RL,
STAT_DROP_SYN_RL,
STAT_DROP_NOT_ALLOWED_TCP,
__STAT_MAX
};
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, __STAT_MAX);
__type(key, __u32);
__type(value, __u64);
} stats SEC(".maps");
static __always_inline void inc_stat(__u32 k) {
__u64 *v = bpf_map_lookup_elem(&stats, &k);
if (v) __sync_fetch_and_add(v, 1);
}
SEC("xdp")
int xdp_ddos_main(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
if ((void*)(eth + 1) > data_end) return XDP_ABORTED;
if (bpf_ntohs(eth->h_proto) != ETH_P_IP)
goto PASS; // 只做 IPv4,IPv6 可另写
struct iphdr *ip = (void*)(eth + 1);
if ((void*)(ip + 1) > data_end) return XDP_ABORTED;
__u32 src = ip->saddr;
// 黑名单
__u8 *blk = bpf_map_lookup_elem(&blacklist, &src);
if (blk) { inc_stat(STAT_DROP_BLACK); return XDP_DROP; }
// 白名单放行
__u8 *w = bpf_map_lookup_elem(&whitelist, &src);
if (w) goto PASS;
// 分片处理(非首片 or MF)
__u32 key0 = 0;
struct cfg *cfg = bpf_map_lookup_elem(&conf, &key0);
if (cfg && cfg->drop_frag) {
__u16 frag_off = bpf_ntohs(ip->frag_off);
if ((frag_off & 0x1FFF) || (frag_off & 0x2000)) { // offset!=0 或 MF 位
inc_stat(STAT_DROP_FRAG);
return XDP_DROP;
}
}
if (ip->protocol == IPPROTO_UDP) {
struct udphdr *uh = (void*)(ip + 1);
if ((void*)(uh + 1) > data_end) return XDP_ABORTED;
__u16 dport = bpf_ntohs(uh->dest);
// 端口白名单
__u8 *ok = bpf_map_lookup_elem(&allow_udp_ports, &dport);
if (!ok) {
// 可选:仅允许 QUIC 443
if (!(cfg && cfg->allow_udp_quic && dport == 443)) {
inc_stat(STAT_DROP_BADPORT_UDP);
return XDP_DROP;
}
}
// 单源 UDP 限速
if (cfg && cfg->udp_pps_limit) {
__u64 now_ns = bpf_ktime_get_ns();
__u64 now_s = now_ns / 1000000000ULL;
struct counter *c = bpf_map_lookup_elem(&udp_rate, &src);
struct counter init = {.sec = now_s, .cnt = 0};
if (!c) {
bpf_map_update_elem(&udp_rate, &src, &init, BPF_ANY);
c = bpf_map_lookup_elem(&udp_rate, &src);
if (!c) goto PASS;
}
if (c->sec != now_s) { c->sec = now_s; c->cnt = 0; }
__u32 n = __sync_add_and_fetch(&c->cnt, 1);
if (n > cfg->udp_pps_limit) { inc_stat(STAT_DROP_UDP_RL); return XDP_DROP; }
}
goto PASS;
}
if (ip->protocol == IPPROTO_TCP) {
struct tcphdr *th = (void*)(ip + 1);
if ((void*)(th + 1) > data_end) return XDP_ABORTED;
__u16 dport = bpf_ntohs(th->dest);
// 端口白名单
__u8 *ok = bpf_map_lookup_elem(&allow_tcp_ports, &dport);
if (!ok) { inc_stat(STAT_DROP_NOT_ALLOWED_TCP); return XDP_DROP; }
// SYN flood 限速
if (cfg && cfg->syn_pps_limit) {
if (th->syn && !th->ack) {
__u64 now_ns = bpf_ktime_get_ns();
__u64 now_s = now_ns / 1000000000ULL;
struct counter *c = bpf_map_lookup_elem(&syn_rate, &src);
struct counter init = {.sec = now_s, .cnt = 0};
if (!c) {
bpf_map_update_elem(&syn_rate, &src, &init, BPF_ANY);
c = bpf_map_lookup_elem(&syn_rate, &src);
if (!c) goto PASS;
}
if (c->sec != now_s) { c->sec = now_s; c->cnt = 0; }
__u32 n = __sync_add_and_fetch(&c->cnt, 1);
if (n > cfg->syn_pps_limit) { inc_stat(STAT_DROP_SYN_RL); return XDP_DROP; }
}
}
goto PASS;
}
PASS:
inc_stat(STAT_PASS);
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
// gcc loader.c -o loader -lbpf -lelf -O2
#define _GNU_SOURCE
#include <bpf/libbpf.h>
#include <stdio.h>
#include <net/if.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
struct cfg {
__u32 udp_pps_limit;
__u32 syn_pps_limit;
__u32 drop_frag;
__u32 allow_udp_quic;
};
static int update_port_map(int map_fd, int port) {
__u16 key = port;
__u8 one = 1;
return bpf_map_update_elem(map_fd, &key, &one, BPF_ANY);
}
int main(int argc, char **argv) {
const char *ifname = argc > 1 ? argv[1] : "ens5f0";
const char *objfile = argc > 2 ? argv[2] : "xdp_ddos_kern.o";
int ifindex = if_nametoindex(ifname);
if (!ifindex) { perror("if_nametoindex"); return 1; }
struct bpf_object *obj = NULL;
struct bpf_program *prog;
int prog_fd, err;
struct bpf_object_open_opts open_opts = {};
obj = bpf_object__open_file(objfile, &open_opts);
if (!obj) { fprintf(stderr, "open %s failed\n", objfile); return 1; }
// 默认取第一个 XDP 程序
bpf_object__for_each_program(prog, obj) {
bpf_program__set_type(prog, BPF_PROG_TYPE_XDP);
break;
}
if ((err = bpf_object__load(obj))) {
fprintf(stderr, "bpf load: %s\n", strerror(-err));
return 1;
}
prog = bpf_object__next_program(obj, NULL);
prog_fd = bpf_program__fd(prog);
__u32 flags = XDP_FLAGS_DRV_MODE; // driver mode
err = bpf_xdp_attach(ifindex, prog_fd, flags, NULL);
if (err) {
fprintf(stderr, "attach driver mode failed (%s), try generic...\n", strerror(-err));
flags = XDP_FLAGS_SKB_MODE;
if ((err = bpf_xdp_attach(ifindex, prog_fd, flags, NULL))) {
fprintf(stderr, "attach generic failed: %s\n", strerror(-err));
return 1;
}
}
// 获取 map 句柄
int conf_fd = bpf_object__find_map_fd_by_name(obj, "conf");
int allow_tcp_fd = bpf_object__find_map_fd_by_name(obj, "allow_tcp_ports");
int allow_udp_fd = bpf_object__find_map_fd_by_name(obj, "allow_udp_ports");
// 默认配置:允许 TCP/80、TCP/443;UDP/443 依据 QUIC 开关;限速阈值按业务体感设置
struct cfg cfg = {
.udp_pps_limit = 1500, // 单源 UDP
.syn_pps_limit = 1200, // 单源 SYN
.drop_frag = 1,
.allow_udp_quic = 0 // 默认关;如果你的站点有 QUIC,就设 1
};
__u32 k0 = 0;
bpf_map_update_elem(conf_fd, &k0, &cfg, BPF_ANY);
update_port_map(allow_tcp_fd, 80);
update_port_map(allow_tcp_fd, 443);
// 如果要开 QUIC:
// update_port_map(allow_udp_fd, 443);
printf("XDP loaded on %s (flags=%s)\n", ifname,
flags==XDP_FLAGS_DRV_MODE ? "driver" : "generic");
printf("Press Ctrl+C to detach.\n");
for (;;) pause();
}
构建与上线
clang -O2 -g -target bpf -c xdp_ddos_kern.c -o xdp_ddos_kern.o
gcc loader.c -o loader -lbpf -lelf -O2
sudo ./loader ens5f0 xdp_ddos_kern.o
# 成功后,程序常驻;Ctrl+C 回滚卸载(或使用 ip link / bpftool 卸载)
# 看程序
sudo bpftool prog show | grep xdp
# 实时统计(per-CPU 累加器,取和)
for i in 0 1 2 3 4 5 6; do
echo $i | sudo bpftool map lookup name stats key hex 00 00 00 00 | sed 's/.*value: //'
done
# 接口硬件计数
watch -n1 "ip -s link show dev ens5f0; echo; ethtool -S ens5f0 | egrep 'rx|tx|drop|xdp'"
# 性能火焰图(可选)
sudo perf top -a
# 1) 打开/关闭 UDP/443(QUIC)
# 写 conf[0].allow_udp_quic = 1 或 0
printf "key: 00 00 00 00 value: 01 00 00 00 B0 04 00 00 01 00 00 00 00 00 00 00" | \
sudo bpftool map update name conf key hex 00 00 00 00 value hex -
# 注:上面的十六进制只是示例,实际请用 bpftool 更直观的 JSON 方式或写个小工具封装。
# 2) 增加 TCP 端口白名单(例如临时开放 8443 做灰度)
PORT_HEX=$(printf "%04x" 8443 | sed 's/../& /g')
printf "key: $PORT_HEX value: 01" | sudo bpftool map update name allow_tcp_ports key hex - value hex 01
# 3) 黑名单添加
SRCIP_HEX=$(printf "%02x %02x %02x %02x" 203 0 113 25) # 203.0.113.25
printf "key: $SRCIP_HEX value: 01" | sudo bpftool map update name blacklist key hex - value hex 01
| 指标 | 上线前 | XDP 上线后(5 分钟) | 备注 |
|---|---|---|---|
| 入向 PPS(峰值) | 12.5 Mpps | 12.5 Mpps | 入向不变(物理口) |
| 进入内核栈 PPS | ~11.8 Mpps | 2.1 Mpps | 绝大部分在 XDP 层被丢弃 |
| 业务 TCP 建连失败率 | 37% | < 1% | 关键用户恢复 |
| 机器 CPU(系统态) | 85% | 42% | XDP 提前拦截,内核栈压力下降 |
| 丢弃原因分布(XDP 统计) | — | 黑名单 8%、端口不允许 61%、UDP 限速 22%、SYN 限速 7%、分片 2% | 来自 stats map 汇总 |
| 平均处理延迟(p99) | 飙升 | 恢复正常 | 见业务 APM |
# 例:使用 pktgen 或 MoonGen 打 UDP 到 80/udp(我们默认不放行,会被 XDP 丢)
# 机器 A:nsenter + pktgen 配置脚本,发 8 Mpps
# 机器 B(被测):观察 XDP stats 的 STAT_DROP_BADPORT_UDP 快速上涨
watch -n0.5 "sudo bpftool map lookup name stats key 02 00 00 00"
# Makefile
CC=clang
CFLAGS=-O2 -g -target bpf
all: xdp_ddos_kern.o loader
xdp_ddos_kern.o: xdp_ddos_kern.c
$(CC) $(CFLAGS) -c $< -o $@
loader: loader.c
gcc -O2 -lelf -lbpf loader.c -o loader
clean:
rm -f xdp_ddos_kern.o loader