WEBKT

彻底榨干网卡性能:基于 eBPF/XDP 的极速流量过滤与 XDP_REDIRECT 转发实战

5 0 0 0

在每秒数百万包(Mpps)的高并发网络场景下,传统的 Linux 内核网络栈会面临巨大的性能瓶颈。由于 sk_buff 结构体的分配、上下文切换、软中断(softirq)以及内核协议栈(IP/TCP/UDP)的层层解析,即使是简单的丢包(Drop)或转发(Forward)操作,也会消耗大量的 CPU 资源。

DPDK(Data Plane Development Kit)曾经是解决这一瓶颈的主流方案,但它完全绕过了内核,导致无法直接利用内核现有的安全路由、路由表和工具链。

XDP(eXpress Data Path) 提供了另一种优雅的解法。作为内核网络栈的第一关,XDP 允许我们在网卡驱动层(通过 eBPF 虚拟机)直接处理数据包。此时,数据包还没有分配 sk_buff,更没有进入协议栈。

本文将深入探讨如何利用 eBPF/XDP 实现高性能动态 IP 黑名单过滤,并通过 XDP_REDIRECT 技术将合法流量以接近线速(Wire-speed)的速度重定向到指定的网卡。


一、 XDP 的工作模式与核心动作

XDP 程序在网卡驱动接收到数据包的瞬间执行,它支持以下 5 种返回值(Action):

  1. XDP_ABORTED:程序出错,丢弃数据包并触发 trace_xdp_exception
  2. XDP_DROP:在极早期直接丢弃数据包。这是防御 DDoS 攻击、实现高性能防火墙的核心。
  3. XDP_PASS:将数据包送往传统的 Linux 内核协议栈,继续走普通的网络流程。
  4. XDP_TX:将数据包从当前接收网卡原路发送回去(通常伴随 MAC/IP 地址的修改,常用于负载均衡)。
  5. XDP_REDIRECT:绕过本地协议栈,将数据包重定向到另一张网卡(通过 devmap)或者用户态 Socket(通过 AF_XDP)。

下面我们主要实现基于 XDP_DROP 的动态黑名单过滤,以及基于 XDP_REDIRECT 的流量转发。


二、 架构设计与数据流向

整个系统的逻辑拓扑如下:

               +-------------------------------------------+
               |                 Linux Host                |
               |                                           |
               |   +-------------+       +-------------+   |
[ 外部流量 ] --> |   |    eth0     |       |    eth1     |   |
               |   +------+------+       +------+------+   |
               |          |                     ^          |
               |     [XDP Program]              |          |
               |          | (Lookup Map)        |          |
               |          +---------------------+          |
               |          |  Matches Blacklist             |
               |          v                                |
               |      [XDP_DROP]                           |
               +-------------------------------------------+
  • 入站接口 (eth0):挂载我们的 XDP eBPF 程序。
  • 黑名单 Map (blacklist_map):存储需要拦截的 IPv4 地址。如果源 IP 匹配,直接返回 XDP_DROP
  • 转发目标 Map (tx_port):一个 BPF_MAP_TYPE_DEVMAP 类型的 Map,存储目标网卡(如 eth1)的索引。匹配通过的流量通过 XDP_REDIRECT 转发到 eth1

三、 内核态 eBPF 代码实现 (xdp_filter_redirect.c)

编写 eBPF 程序时,验证器(Verifier)限制是最棘手的问题。我们必须时刻保证指针边界检查,确保每一次内存访问都是安全的,否则程序将无法加载进内核。

#include <linux/bpf.h>
#include <linux/in.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>

/* 定义黑名单 Map:Key 是 IPv4 地址,Value 是命中计数 */
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 65536);
    __type(key, __be32);
    __type(value, __u64);
} blacklist_map SEC(".maps");

/* 定义设备重定向 Map:存储目标网卡的 ifindex */
struct {
    __uint(type, BPF_MAP_TYPE_DEVMAP);
    __uint(max_entries, 8);
    __type(key, __u32);
    __type(value, __u32);
} tx_port SEC(".maps");

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

    // 1. 解析以太网首部 (Ethernet Header)
    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end) {
        return XDP_PASS;
    }

    // 2. 仅处理 IPv4 协议
    if (eth->h_proto != bpf_htons(ETH_P_IP)) {
        return XDP_PASS;
    }

    // 3. 解析 IP 首部 (IP Header)
    struct iphdr *iph = (void *)(eth + 1);
    if ((void *)(iph + 1) > data_end) {
        return XDP_PASS;
    }

    __be32 src_ip = iph->saddr;

    // 4. 查询黑名单 Map
    __u64 *counter = bpf_map_lookup_elem(&blacklist_map, &src_ip);
    if (counter) {
        // 原子操作自增计数器,防止并发写冲突
        __sync_fetch_and_add(counter, 1);
        return XDP_DROP; // 匹配黑名单,直接丢弃
    }

    // 5. 流量重定向:将数据包重定向到 tx_port Map 中 key 为 0 的网卡
    // 如果 Map 中没有配置对应的网卡,它会自动回退或返回错误
    int err = bpf_redirect_map(&tx_port, 0, 0);
    if (err == XDP_REDIRECT) {
        return XDP_REDIRECT;
    }

    // 默认通过
    return XDP_PASS;
}

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

关键细节解析:

  1. 边界检查 (void *)(eth + 1) > data_end:这是 eBPF 验证器的硬性要求。在解引用指针获取协议字段前,必须证明目标地址在 datadata_end 之间,否则会报 invalid access to packet 错误。
  2. BPF_MAP_TYPE_DEVMAP:一种专用于网络设备重定向的 Map。相较于早期版本的 bpf_redirect(ifindex, flags),配合 bpf_redirect_map 机制能让内核批量(bulk)分发数据包,性能大幅提升。
  3. __sync_fetch_and_add:eBPF 运行在多核并发环境下,对于 Map 内的统计数据更新,必须使用 CPU 原子指令以避免脏写。

四、 编译与加载部署

1. 编译 eBPF 字节码

我们需要使用支持 bpf 后端的 clang 编译器将 C 代码编译为 ELF 格式的 eBPF 字节码:

clang -g -O2 -target bpf -D__TARGET_ARCH_x86 -c xdp_filter_redirect.c -o xdp_filter_redirect.o

2. 加载 eBPF 程序到网卡

我们将编译好的字节码挂载到输入网卡(例如 eth0)。使用经典的 iproute2 工具包(ip link):

# 以原生驱动模式(native)挂载 XDP。如果网卡驱动不支持,可以使用 xdpgeneric 替代
ip link set dev eth0 xdp object xdp_filter_redirect.o section xdp

注:xdp 表示硬件/驱动级,性能最强;xdpgeneric 是软件模拟模式,不依赖驱动,常用于开发调试。

检查挂载状态:

ip link show dev eth0

输出中看到 xdp/prog 字样,说明挂载成功。


五、 控制面配置:填充 BPF Maps

单纯加载内核态程序是不够的,我们还需要在用户态动态控制黑名单列表以及重定向目标。我们可以使用 bpftool 命令行工具,也可以基于 Go (cilium/ebpf) 或 C (libbpf) 编写控制面程序。

这里我们以直观的 bpftool 为例进行操作。

1. 定位 BPF Maps

首先,找出加载的 Maps 对应的 ID:

bpftool map show

假设我们找到了这两个 Map 的 ID 分别为 10 (blacklist_map) 和 11 (tx_port)。

2. 配置重定向目标网卡 (tx_port)

假设我们希望把从 eth0 进来的干净流量重定向到 eth1
首先获取 eth1 的网络接口索引(ifindex):

cat /sys/class/net/eth1/ifindex
# 假设输出为 3

tx_port Map 的 key 0 写入值 3

bpftool map update id 11 key 0 0 0 0 value 3 0 0 0

注意:bpftool 在更新 key/value 时接收的是十六进制字节数组,输入时需注意大小端与对齐。

3. 动态下发黑名单

加入需要封禁的 IP(例如 192.168.1.100,十六进制为 c0 a8 01 64):

bpftool map update id 10 key 0xc0 0xa8 0x01 0x64 value 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

此时,所有来自 192.168.1.100 的数据包都会在网卡驱动层被直接 XDP_DROP,不会占用操作系统任何协议栈开销。


六、 生产环境避坑指南与调优

在实际生产部署中,往往会遇到一些深水区的性能与机制问题,以下是踩坑总结:

  1. 单队列与多队列网卡(RSS)
    XDP 程序的执行是跟 CPU 绑定的。如果网卡开启了多队列,数据包会分发到不同的队列,并由不同的 CPU 核心处理。务必确保每个接收队列上都正常工作,并且中断亲和性(IRQ Affinity)分配合理。

  2. XDP_REDIRECT 转发后的 MAC 地址冲突
    使用 XDP_REDIRECT 将数据包从 eth0 直接甩给 eth1 送出时,数据包的以太网源/目的 MAC 地址并不会自动改变。如果对端交换机或路由器校验了 MAC 地址,数据包可能会被无情丢弃。
    解决方案:在 XDP 程序返回 XDP_REDIRECT 前,直接在内核态修改以太网帧头部的 h_sourceh_dest

  3. 内核绕过导致的“网络不通”
    一旦通过 XDP_REDIRECT 转发,Linux 内核协议栈就完全感知不到这些包。这意味着原生的 iptablesnftablestcpdump 将无法抓取到这些转发流量。调试时建议结合 bpftool prediction 或在 BPF 代码中插入 bpf_printk,通过 /sys/kernel/debug/tracing/trace_pipe 查看输出。

七、 性能表现

在主流的 Intel 10GbE/40GbE 网卡物理服务器上,挂载上述 XDP 程序的单核吞吐量通常可以轻松突破 12Mpps - 14Mpps(丢包测试),这已经触及了物理网卡的极限线速。相较于在 iptables 中配置 raw 表,吞吐性能提升了数倍,CPU 消耗却下降了一个数量级,这正是 eBPF 赋予现代 Linux 网络架构的绝对实力。

KernelCraft eBPFXDP网络性能优化

评论点评