1.什么是eBPF?

eBPF(扩展伯克利数据包过滤器)是一个 Linux 内核子系统,可让您在内核内运行沙盒程序,而无需修改内核源代码或加载内核模块。程序在执行前经过内核字节码验证器的验证,确保安全。

对于网络,eBPF 程序附加到钩点在内核的网络堆栈中,可以检查、修改、重定向或丢弃数据包。关键优势在于iptables或者内核模块是性能和可编程性:eBPF 程序被 JIT 编译为本机代码,并且可以通过共享状态地图(内核和用户空间之间共享的键值存储)。

地点延迟使用案例
XDPNIC驱动程序,在分配sk_buff之前最低DDoS 丢弃、负载平衡
TC 入口/出口sk_buff分配后低的流量整形、标记、重定向
插座过滤器套接字接收路径中等的tcpdump 式过滤
k探针/跟踪点内核函数的进入/退出各不相同可观察性、追踪性

2.XDP挂钩点

XDP(eXpress 数据路径)程序在网络堆栈中最早的可能点运行 - 在 NIC 驱动程序内部,在内核分配一个sk_buff。这意味着:

  • 原生 XDP:驱动程序原生支持 XDP(Intel i40e、Mellanox mlx5 等)。最快 - 在驱动程序上下文中运行。
  • 通用XDP:没有本机支持的驱动程序的后备。之后运行sk_buff分配——仍然比 iptables 快,但不如原生。
  • 卸载XDP:程序在 NIC ASIC 本身上运行。需要 SmartNIC 硬件(例如 Netronome)。零 CPU 成本。

XDP 程序返回五个判决之一:

返回码行动
XDP_DROP立即丢弃数据包 — 最低延迟丢弃
XDP_PASS传递到普通网络堆栈
XDP_TX从同一接口传回(反弹)
XDP_REDIRECT重定向到另一个接口或 AF_XDP 套接字
XDP_ABORTED错误路径 — 随跟踪事件一起丢弃

3. XDP丢包示例

以下程序丢弃来自存储在 eBPF 映射中的源 IP 的所有 UDP 数据包,从而允许用户空间控制平面在运行时更新阻止列表。

// xdp_drop_udp.c — Drop UDP from IPs in a BPF map
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <bpf/bpf_helpers.h>

// BPF map: src IP → drop flag (1 = drop)
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 1024);
    __type(key, __u32);    // source IPv4 address
    __type(value, __u32);  // 1 = block
} blocklist SEC(".maps");

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

    // Parse Ethernet header
    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;

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

    // Check blocklist map
    __u32 src = ip->saddr;
    __u32 *val = bpf_map_lookup_elem(&blocklist, &src);
    if (val && *val == 1) return XDP_DROP;

    return XDP_PASS;
}

char _license[] SEC("license") = "GPL";
边界检查是强制性的。eBPF 验证器拒绝访问超出内存范围的程序data_end。每个指针算术运算后面都必须进行边界检查,否则程序将无法加载。

加载并附加ip:

# Compile
clang -O2 -target bpf -c xdp_drop_udp.c -o xdp_drop_udp.o

# Attach to interface (native XDP)
ip link set eth0 xdp obj xdp_drop_udp.o sec xdp

# Add an IP to the blocklist via bpftool
bpftool map update name blocklist key 0x01 0x02 0x03 0x04 value 0x01 0x00 0x00 0x00

# Remove XDP program
ip link set eth0 xdp off

4. AF_XDP:内核绕过

AF_XDP是一个套接字系列,与 XDP 相结合XDP_REDIRECTverdict,将数据包直接传送到用户空间内存区域 (UMEM),无需内核参与每个数据包。这是 eBPF 生态系统对 DPDK 内核旁路模型的回答。

关键部件:

  • UMEM:用户空间注册的内存区域分为帧。通过共享内存在内核和用户空间之间共享。
  • 戒指:每个套接字有四个无锁环:填充(用户空间→带有空闲帧的内核),完成(内核→带有TX完成帧的用户空间),RX环(内核→带有接收帧的用户空间),TX环(用户空间→带有要发送帧的内核)。
  • 零拷贝模式:如果驱动程序支持,则传输帧时无需任何副本 - 只需进行指针传递。

AF_XDP 非常适合以线速进行自定义数据包处理,而无需 DPDK 的操作复杂性(基本使用不需要大页、无需 CPU 固定)。

5. tc BPF:流量整形和过滤

tc(流量控制)BPF 程序附加在clsactqdisc 并且可以在入口或出口上运行。与 XDP 不同,他们看到完整的sk_buff并且可以访问套接字元数据、VLAN 和隧道标头。

// tc_mark.c — Mark packets with DSCP EF (46) for VoIP traffic on port 5060
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <bpf/bpf_helpers.h>

SEC("classifier")
int tc_mark_voip(struct __sk_buff *skb) {
    void *data     = (void *)(long)skb->data;
    void *data_end = (void *)(long)skb->data_end;

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

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

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

    // Mark SIP traffic (port 5060) with DSCP EF (46 = 0xB8 in TOS byte)
    if (udp->dest == __constant_htons(5060) || udp->source == __constant_htons(5060)) {
        // DSCP EF = 46, shifted left 2 bits in TOS field = 184 (0xB8)
        bpf_skb_store_bytes(skb, offsetof(struct iphdr, tos) + sizeof(struct ethhdr),
                            &((__u8){184}), 1, BPF_F_RECOMPUTE_CSUM);
    }
    return TC_ACT_OK;
}

char _license[] SEC("license") = "GPL";
# Attach tc BPF program
tc qdisc add dev eth0 clsact
tc filter add dev eth0 egress bpf da obj tc_mark.o sec classifier

6. eBPF 映射的速率限制

eBPF 映射支持有状态处理。以下模式使用存储在BPF_MAP_TYPE_LRU_HASH:

// Conceptual token bucket per source IP — checks tokens, drops if exceeded
struct ratelimit_entry {
    __u64 tokens;        // current token count
    __u64 last_update;   // nanoseconds timestamp
};

struct {
    __uint(type, BPF_MAP_TYPE_LRU_HASH);
    __uint(max_entries, 65536);
    __type(key, __u32);                     // source IP
    __type(value, struct ratelimit_entry);
} rate_map SEC(".maps");

// In XDP program:
// 1. bpf_ktime_get_ns() — get current time
// 2. Lookup entry for src IP
// 3. Refill tokens: tokens += (elapsed_ns / 1e9) * rate_pps
// 4. If tokens >= 1: decrement and XDP_PASS
// 5. Else: XDP_DROP

7. bpftool & bpftrace 自省

使用实时 eBPF 程序的两个基本工具:

# bpftool — inspect loaded programs and maps
bpftool prog list                         # list all loaded eBPF programs
bpftool prog show id 42                   # details for program ID 42
bpftool prog dump xlated id 42            # disassemble to eBPF bytecode
bpftool prog dump jited id 42            # dump JIT-compiled native code
bpftool map list                          # list all BPF maps
bpftool map dump name blocklist           # dump all entries in map "blocklist"
bpftool map update name blocklist \
    key 192 168 1 100 value 1 0 0 0       # add entry (network byte order)
# bpftrace — DTrace-style one-liners for kernel tracing
# Count XDP drops per second
bpftrace -e 'tracepoint:xdp:xdp_exception { @drops[args->action] = count(); } interval:s:1 { print(@drops); clear(@drops); }'

# Trace tcp_retransmit_skb — show retransmit events with comm name
bpftrace -e 'kprobe:tcp_retransmit_skb { printf("%s retransmit\n", comm); }'

# Histogram of packet sizes on eth0
bpftrace -e 'tracepoint:net:netif_receive_skb /args->name == "eth0"/ { @size = hist(args->len); }'

8. 比较:eBPF/XDP vs DPDK vs RDMA

特征电子BPF/XDPDPDKRDMA
内核参与最小(驱动程序中的 XDP)无(完全旁路)无(RDMA 网卡)
内存模型标准+AF_XDP UMEM需要大页注册内存区域
最大吞吐量~100 Gbps 原生 XDP>100 Gbps200+ Gbps (InfiniBand)
CPU使用率低(事件驱动)高(忙轮询核心)接近零(卸载)
操作复杂性低——标准工具高 — 专用核心、大页高——面料管理
使用案例DDoS 缓解、LB、可观测性虚拟路由器、NFV、数据包生成存储 (NVMe-oF)、HPC MPI
语言受限 C/RustC/铁锈动词 API (C)
经验法则:从 eBPF/XDP 开始——它与现有的内核工具集成,不需要特殊的硬件或大页面,并且可以处理 100 Gbps 以下的大多数高性能网络用例。仅当您需要专用 CPU 内核并且无法容忍任何内核调度开销时,才迁移到 DPDK。