4. eBPF程序类型详解
eBPF 技术的核心价值在于其程序类型(Program Type)与挂载点(Attachment Point)的多样化设计。不同的程序类型决定了 eBPF 虚拟机在内核中的执行上下文、可访问的数据结构以及触发时机。本章将深入剖析五种最关键的 eBPF 程序类型:XDP、TC、Socket Filter、kprobe 与 tracepoint,并对比其在内核数据路径中的位置与适用场景。
4.1 程序类型与挂载点概览
eBPF 程序不能独立运行,必须挂载到内核预定义的钩子(Hook)上。下表列出了本章重点讨论的五种程序类型及其核心特征:
| 程序类型 | 挂载点层级 | 触发时机 | 主要用途 | 可访问数据 |
|---|---|---|---|---|
| XDP | 网卡驱动层 | 数据包到达网卡,未分配 sk_buff | 高速包过滤、DDoS 防护、负载均衡 | 原始报文(xdp_md) |
| TC | 内核协议栈层 | 数据包进入/离开协议栈(已分配 sk_buff) | 流量整形、QoS、隧道封装 | sk_buff 结构 |
| Socket Filter | Socket 层 | 数据包到达 Socket 接收队列 | 应用层过滤、协议分析 | Socket 缓冲区数据 |
| kprobe | 内核指令级 | 内核函数入口/出口/指定偏移 | 动态追踪、性能分析、内核调试 | 函数参数、寄存器、栈 |
| tracepoint | 内核静态插桩 | 内核预定义事件(如系统调用、调度) | 稳定监控、可观测性 | 结构化事件上下文 |
4.2 XDP(eXpress Data Path)
XDP 是 eBPF 在网络数据路径中最早、最快的挂载点。它在网卡驱动层直接注入 eBPF 程序,在数据包尚未被内核协议栈处理之前即做出决策。
4.2.1 执行位置与数据流
下图展示了 XDP 程序在数据包接收路径中的位置:
4.2.2 XDP 返回码与动作
XDP 程序通过返回码决定数据包的命运,这是其高性能的关键:
- XDP_DROP:在驱动层直接丢弃数据包,适用于 DDoS 清洗。
- XDP_PASS:允许数据包继续进入内核协议栈,分配 sk_buff。
- XDP_TX:将数据包从接收网卡直接转发出去,实现快速转发。
- XDP_REDIRECT:将数据包重定向到其他网卡或 AF_XDP Socket。
- XDP_ABORTED:程序执行出错,数据包被丢弃并记录异常。
4.3 TC(Traffic Control)
TC 是 Linux 内核流量控制子系统的 eBPF 扩展。与 XDP 不同,TC 程序挂载在协议栈内部,拥有完整的 sk_buff 结构,可以访问 L3/L4 头部信息。
4.3.1 TC 的挂载点:ingress 与 egress
TC 支持两个方向的挂载:
- TC ingress:数据包从协议栈进入 Socket 层之前执行。
- TC egress:数据包从 Socket 层发出、离开协议栈之前执行。
4.3.2 TC 程序示例
以下是一个简单的 TC egress 程序,用于丢弃特定端口的出站数据包:
// tc_egress_drop.c
#include <linux/bpf.h>
#include <linux/pkt_cls.h>
#include <bpf/bpf_helpers.h>
SEC("classifier")
int tc_egress_drop(struct __sk_buff *skb) {
void *data = (void *)(long)skb->data;
void *data_end = (void *)(long)skb->data_end;
struct ethhdr *eth = data;
if (data + sizeof(*eth) > data_end)
return TC_ACT_OK;
// 检查 IP 协议
if (eth->h_proto != __constant_htons(ETH_P_IP))
return TC_ACT_OK;
struct iphdr *ip = data + sizeof(*eth);
if (data + sizeof(*eth) + sizeof(*ip) > data_end)
return TC_ACT_OK;
// 丢弃目标端口 8080 的 TCP 数据包
if (ip->protocol == IPPROTO_TCP) {
struct tcphdr *tcp = (void *)ip + sizeof(*ip);
if (data + sizeof(*eth) + sizeof(*ip) + sizeof(*tcp) > data_end)
return TC_ACT_OK;
if (tcp->dest == __constant_htons(8080))
return TC_ACT_SHOT; // 丢弃
}
return TC_ACT_OK;
}
4.4 Socket Filter
Socket Filter 是 eBPF 最早的应用之一,起源于经典的 BPF(cBPF)包过滤机制。它挂载在 Socket 层,允许用户空间程序对接收或发送的数据包进行过滤。
4.4.1 挂载方式
Socket Filter 通过 setsockopt() 系统调用挂载到指定的 Socket 上:
// 用户态挂载 Socket Filter
int prog_fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, prog, insns_cnt, "GPL");
if (prog_fd < 0) {
perror("bpf_prog_load");
exit(1);
}
// 挂载到 Socket
setsockopt(sock_fd, SOL_SOCKET, SO_ATTACH_BPF, &prog_fd, sizeof(prog_fd));
4.4.2 典型应用
- tcpdump:底层使用 BPF 指令进行包过滤。
- 应用层防火墙:在用户态 Socket 层面实现白名单/黑名单。
- 协议嗅探:只接收特定协议的数据包,减少用户态唤醒次数。
4.5 kprobe(动态探针)
kprobe 允许 eBPF 程序动态挂载到内核函数的任意位置,包括函数入口(kprobe)、函数返回(kretprobe)以及函数内部的指定偏移。这是内核动态追踪的核心机制。
4.5.1 挂载点类型
| 探针类型 | 触发时机 | 访问能力 | 稳定性 |
|---|---|---|---|
| kprobe | 函数入口或指定指令偏移 | 函数参数、寄存器、全局变量 | 依赖内核版本,函数可能被内联 |
| kretprobe | 函数返回前 | 返回值、函数执行时间 | 依赖内核版本,需处理并发 |
| fentry/fexit | 函数入口/出口(BPF 原生) | 更高效的参数/返回值访问 | 需要 BTF 支持,更稳定 |
4.5.2 使用场景
- 性能分析:追踪
tcp_sendmsg、tcp_recvmsg等网络函数延迟。 - 内核调试:在不重新编译内核的情况下,动态注入调试逻辑。
- 安全监控:监控敏感系统调用或文件操作。
4.6 tracepoint(静态探针)
tracepoint 是内核预定义的静态插桩点,由内核开发者在内核源代码中显式声明。与 kprobe 相比,tracepoint 具有更好的稳定性和安全性,因为其参数格式是固定的。
4.6.1 常见网络相关 tracepoint
| tracepoint 名称 | 触发事件 | 可访问参数 |
|---|---|---|
net:netif_receive_skb |
数据包到达协议栈 | skb 地址、协议、设备 |
net:net_dev_queue |
数据包进入设备队列 | skb 地址、队列号 |
syscalls:sys_enter_connect |
connect 系统调用入口 | fd、sockaddr、addrlen |
syscalls:sys_exit_connect |
connect 系统调用返回 | 返回值 |
tcp:tcp_rcv_established |
TCP 接收已建立连接的数据 | sk、skb、tcp 状态 |
4.6.2 tracepoint 程序示例
// tracepoint_net.c
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
SEC("tracepoint/net/netif_receive_skb")
int trace_netif_receive_skb(struct trace_event_raw_net_dev_template *ctx) {
// 记录数据包接收事件
bpf_printk("Packet received on device: %s\n", ctx->name);
return 0;
}
4.7 程序类型选择决策树
在实际项目中,选择合适的 eBPF 程序类型至关重要。以下决策树可帮助快速判断:
4.8 总结与最佳实践
- XDP:追求极致性能,适合 DDoS 防护、负载均衡、高速转发。
- TC:需要完整协议栈上下文,适合网络策略、隧道、QoS。
- Socket Filter:应用层过滤,适合 tcpdump 类工具。
- kprobe:动态追踪,适合调试和临时监控。
- tracepoint:稳定可观测,适合生产环境监控。
在实际部署中,建议优先使用 tracepoint 和 XDP/TC 等稳定接口,仅在必要时使用 kprobe。同时,注意 eBPF 程序的兼容性:XDP 需要网卡驱动支持,TC 需要内核开启 BPF 直连支持,tracepoint 依赖内核版本。