一、介绍
1.1 BPF程序分类
BPF(Berkeley Packet Filter)程序通过动态加载到内核或用户态,实现高效的事件处理和数据观测,根据其核心功能可分为跟踪类和网络类两大类。以下是具体描述:
(1)跟踪类 BPF 程序(Tracing BPF)
跟踪类 BPF 程序的核心功能是监控系统事件、收集运行时数据,用于性能分析、调试、安全审计等场景。它们通过挂载到内核或用户态的 “钩子点”,捕获事件发生时的上下文信息(如函数参数、返回值、内存状态等)。
-
kprobe/kretprobe
- 挂载到内核函数的入口(kprobe)或返回处(kretprobe),用于跟踪内核函数的调用情况。
- 应用:分析系统调用耗时(如
sys_read)、定位内核漏洞(如空指针引用)、监控进程创建(do_fork)等。
-
uprobe/uretprobe
- 类似 kprobe,但作用于用户态程序的函数(如动态链接库函数、自定义程序函数)。
- 应用:调试用户态程序逻辑(如跟踪
malloc内存分配)、监控应用性能瓶颈(如数据库查询函数耗时)。
-
tracepoint
- 内核预先定义的静态跟踪点(由内核开发者在关键逻辑处埋点),比 kprobe 更稳定(无内核版本兼容问题),但覆盖范围有限。
- 应用:跟踪进程调度(
sched_switch)、文件系统操作(sys_enter_open)、网络包收发(net_dev_xmit)等。
-
perf event
- 与性能计数器(PMU,Performance Monitoring Unit)结合,跟踪硬件事件(如 CPU 缓存命中 / 失效)或软件事件(如进程切换)。
- 应用:分析 CPU 使用率、缓存命中率、指令执行效率等底层性能指标。
-
raw tracepoint
- 比 tracepoint 更轻量,直接访问内核内部数据结构,不经过内核事件过滤逻辑,适合需要更高性能的场景,但兼容性较差(依赖内核内部实现)。
-
cgroup 跟踪
- 监控 cgroup(控制组)相关事件,如进程加入 / 退出 cgroup、资源限制触发(如内存阈值)。
- 应用:容器资源监控(如 Docker 容器的 CPU / 内存使用趋势)。
(2)网络类 BPF 程序(Networking BPF)
网络类 BPF 程序专注于网络数据包的处理,包括过滤、转发、修改、监控等,运行在内核网络栈的关键节点,替代传统的 iptables、tc(流量控制)等工具,提供更高性能和灵活性。
-
XDP(eXpress Data Path)
- 运行在网络设备驱动的最早阶段(数据包刚进入内核时),是性能最高的网络 BPF 程序。
- 功能:在数据包到达协议栈前进行过滤(丢弃恶意包)、转发(实现高性能负载均衡)、修改(如修改源 IP)。
- 优势:绕过大部分内核网络栈,处理速率可达 100Gbps 以上,适合 DDoS 防护、高性能网关等场景。
-
tc BPF(Traffic Control BPF)
- 挂载到 tc(Linux 流量控制子系统)的钩子点(如
cls_bpf用于分类,act_bpf用于动作),在网络栈的不同阶段处理数据包。 - 应用:流量整形(限速)、基于数据包内容的路由(如根据 HTTP 头部转发)、流量统计(按端口 / 协议计数)。
- 挂载到 tc(Linux 流量控制子系统)的钩子点(如
-
Socket Filter BPF
- 作用于用户态 socket,过滤进入 socket 的数据包(仅保留感兴趣的包),替代传统的
recvfrom+ 用户态过滤逻辑。 - 应用:抓包工具(如
tcpdump底层基于 BPF 过滤)、减少用户态与内核态的数据传输量(只传匹配的包)。
- 作用于用户态 socket,过滤进入 socket 的数据包(仅保留感兴趣的包),替代传统的
-
LSM BPF(Linux Security Module BPF)
- 虽属于安全类,但常涉及网络行为控制(如限制进程的网络连接),通过 LSM 钩子监控系统调用(如
connect、bind)。 - 应用:防火墙规则(允许 / 禁止特定进程访问特定 IP)、网络访问审计(记录所有出站连接)。
- 虽属于安全类,但常涉及网络行为控制(如限制进程的网络连接),通过 LSM 钩子监控系统调用(如
(3)两类程序的核心区别
| 维度 | 跟踪类 BPF | 网络类 BPF |
|---|---|---|
| 核心目标 | 事件监控与数据收集 | 网络数据包处理与控制 |
| 钩子点位置 | 内核 / 用户态函数、跟踪点 | 网络栈(驱动、协议栈、socket) |
| 典型工具 | bpftrace、bcc(分析工具) | xdp-tools、tc-bpf(网络工具) |
| 输出内容 | 事件日志、性能指标 | 处理后的数据包、流量统计 |
1.2 BPF程序类
| 序号 | 名称 | 关键字 | 说明 |
|---|---|---|---|
| 1 | 套接字过滤器程序 | NPF_PROG_TYPE_SOCKET_FILTER | 用于过滤套接字上的网络数据包,通常用于数据包捕获(如 libpcap)。 |
| 2 | kprobe 程序 | kprobe/kretprobe | 用于动态跟踪内核函数调用,kprobe 跟踪函数入口,kretprobe 跟踪函数返回。 |
| 3 | 跟踪点程序 | BPF_PROG_TPYE_TRACEPOINT | 基于内核预定义的静态跟踪点,用于性能分析或调试,无需修改内核代码。 |
| 4 | XDP 程序 | BPF_PROG_TPYE_XDP | 在网络驱动层早期处理数据包(eXpress Data Path),用于高性能数据包过滤、转发或流量控制。 |
| 5 | Perf 事件程序 | BPF_PROG_TPYE_PERF_EVENT | 用于监控内核或用户态的性能事件(如 CPU 周期、缓存命中率等),支持自定义计数器。 |
| 6 | cgroup 套接字程序 | BPF_PROG_TPYE_CGROUP_SKB | 在 cgroup 层级过滤套接字数据包,用于网络流量控制或安全策略。 |
| 7 | cgroup 打开套接字程序 | BPF_PROG_TPYE_CGROUP_SOCK | 在套接字创建时(socket() 系统调用)应用 cgroup 策略,用于限制或监控套接字行为。 |
| 8 | 套接字选项程序 | BPF_PROG_TPYE_CGROUP_OPS | 拦截并修改套接字选项(如 setsockopt()/getsockopt()),用于自定义套接字行为。 |
| 9 | 套接字映射程序 | BPF_PROG_TPYE_SK_SKB | 处理从套接字发送或接收的数据包(SKB),用于数据包修改或负载均衡。 |
| 10 | cgroup 设备程序 | BPF_PROG_TPYE_CGROUP_DEVICE | 控制 cgroup 内设备的访问权限(如允许/禁止读写特定设备文件)。 |
| 11 | 套接字消息传递程序 | BPF_PROG_TPYE_SK_MSG | 处理套接字上的消息(如 sendmsg()/recvmsg()),用于消息过滤或修改。 |
| 12 | 原始跟踪点程序 | BPF_PROG_TPYE_RAW_TRACE | 提供对原始跟踪点数据的低级访问,适用于需要精细控制跟踪数据的场景。 |
| 13 | cgroup 套接字地址程序 | BPF_PROG_TPYE_CGROUP_SOCK_ADDR | 过滤或修改套接字地址(如 bind()/connect() 的目标地址),用于网络地址控制。 |
| 14 | 套接字重用端口程序 | SO_REUSEPORT | 允许多个套接字绑定到同一端口,结合 BPF 实现负载均衡或高并发服务(如 Web 服务器)。 |
| 15 | 流量解析程序 | BPF_PROG_TPYE_FLOW_DISSECTOR | 解析网络流量中的协议字段(如 HTTP 头、DNS 查询),用于深度包检测(DPI)或流量分类。 |
二、常用BPF程序类型案例
2.1 套接字过滤器程序
功能:过滤 TCP 数据包,仅打印目的端口为 80 的包。
代码 (sock_filter.c):
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
SEC("socket")
int socket_filter(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 0;
struct iphdr *ip = data + sizeof(*eth);
if (data + sizeof(*eth) + sizeof(*ip) > data_end) return 0;
if (ip->protocol != IPPROTO_TCP) return 0;
struct tcphdr *tcp = data + sizeof(*eth) + sizeof(*ip);
if (data + sizeof(*eth) + sizeof(*ip) + sizeof(*tcp) > data_end) return 0;
if (tcp->dest == htons(80)) {
bpf_printk("TCP packet to port 80\n");
}
return 0;
}
char _license[] SEC("license") = "GPL";
编译 & 运行:
clang -O2 -target bpf -c sock_filter.c -o sock_filter.o
sudo bpftool prog load sock_filter.o /sys/fs/bpf/sock_filter
sudo bpftool net attach socket_filter id <prog_id>
输出:
TCP packet to port 80
2.2 kprobe 程序
功能:跟踪 do_sys_open 系统调用。
代码 (kprobe_demo.c):
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
SEC("kprobe/do_sys_open")
int kprobe__do_sys_open(struct pt_regs *ctx) {
const char *filename = (const char *)PT_REGS_PARM1(ctx);
bpf_printk("File opened: %s\n", filename);
return 0;
}
char _license[] SEC("license") = "GPL";
编译 & 运行:
clang -O2 -target bpf -c kprobe_demo.c -o kprobe_demo.o
sudo bpftool prog load kprobe_demo.o /sys/fs/bpf/kprobe_demo
sudo bpftool perf attach pinned /sys/fs/bpf/kprobe_demo
输出:
File opened: /etc/passwd
2.3 跟踪点程序
功能:跟踪 syscalls:sys_enter_openat 跟踪点。
代码 (tracepoint_demo.c):
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
SEC("tracepoint/syscalls/sys_enter_openat")
int tracepoint__sys_enter_openat(struct trace_event_raw_sys_enter *ctx) {
const char *filename = (const char *)ctx->args[1];
bpf_printk("openat called: %s\n", filename);
return 0;
}
char _license[] SEC("license") = "GPL";
编译 & 运行:
clang -O2 -target bpf -c tracepoint_demo.c -o tracepoint_demo.o
sudo bpftool prog load tracepoint_demo.o /sys/fs/bpf/tracepoint_demo
sudo bpftool perf attach pinned /sys/fs/bpf/tracepoint_demo
输出:
openat called: /proc/cpuinfo
2.4 XDP 程序
功能:丢弃所有 ICMP 数据包。
代码 (xdp_drop_icmp.c):
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/icmp.h>
SEC("xdp")
int xdp_drop_icmp(struct xdp_md *ctx) {
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
if (data + sizeof(*eth) > data_end) return XDP_PASS;
struct iphdr *ip = data + sizeof(*eth);
if (data + sizeof(*eth) + sizeof(*ip) > data_end) return XDP_PASS;
if (ip->protocol == IPPROTO_ICMP) {
bpf_printk("Dropped ICMP packet\n");
return XDP_DROP;
}
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
编译 & 运行:
clang -O2 -target bpf -c xdp_drop_icmp.c -o xdp_drop_icmp.o
sudo bpftool net attach xdp id <prog_id> dev eth0
输出:
Dropped ICMP packet
2.5 Perf 事件程序
功能:统计 CPU 周期。
代码 (perf_event.c):
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
SEC("perf_event")
int perf_event_handler(struct bpf_perf_event_data *ctx) {
bpf_printk("CPU cycle event\n");
return 0;
}
char _license[] SEC("license") = "GPL";
编译 & 运行:
clang -O2 -target bpf -c perf_event.c -o perf_event.o
sudo bpftool prog load perf_event.o /sys/fs/bpf/perf_event
sudo bpftool perf attach pinned /sys/fs/bpf/perf_event
输出:
CPU cycle event
2.6 cgroup 套接字程序
功能:禁止 cgroup 内所有 TCP 流量。
代码 (cgroup_skb.c):
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
SEC("cgroup_skb/egress")
int cgroup_skb_egress(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 1;
struct iphdr *ip = data + sizeof(*eth);
if (data + sizeof(*eth) + sizeof(*ip) > data_end) return 1;
if (ip->protocol == IPPROTO_TCP) {
bpf_printk("Blocking TCP packet\n");
return 0; // DROP
}
return 1; // PASS
}
char _license[] SEC("license") = "GPL";
编译 & 运行:
clang -O2 -target bpf -c cgroup_skb.c -o cgroup_skb.o
sudo bpftool prog load cgroup_skb.o /sys/fs/bpf/cgroup_skb
sudo bpftool cgroup attach /sys/fs/cgroup/unified/ id <prog_id>
输出:
Blocking TCP packet
2.7 cgroup 打开套接字程序
功能:禁止 cgroup 内创建新套接字。
代码 (cgroup_sock.c):
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
SEC("cgroup/sock_create")
int cgroup_sock_create(struct bpf_sock *sk) {
bpf_printk("Socket creation blocked\n");
return 0; // BLOCK
}
char _license[] SEC("license") = "GPL";
编译 & 运行:
clang -O2 -target bpf -c cgroup_sock.c -o cgroup_sock.o
sudo bpftool prog load cgroup_sock.o /sys/fs/bpf/cgroup_sock
sudo bpftool cgroup attach /sys/fs/cgroup/unified/ id <prog_id>
输出:
Socket creation blocked
2.8 套接字选项程序
功能:修改 SO_REUSEADDR 选项。
代码 (cgroup_sockopt.c):
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
SEC("cgroup/setsockopt")
int cgroup_setsockopt(struct bpf_sockopt *ctx) {
if (ctx->level == SOL_SOCKET && ctx->optname == SO_REUSEADDR) {
bpf_printk("Setting SO_REUSEADDR=1\n");
*(int *)ctx->optval = 1;
}
return 0;
}
char _license[] SEC("license") = "GPL";
编译 & 运行:
clang -O2 -target bpf -c cgroup_sockopt.c -o cgroup_sockopt.o
sudo bpftool prog load cgroup_sockopt.o /sys/fs/bpf/cgroup_sockopt
sudo bpftool cgroup attach /sys/fs/cgroup/unified/ id <prog_id>
输出:
Setting SO_REUSEADDR=1
2.9 套接字映射程序
功能:修改套接字数据包。
代码 (sk_skb.c):
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
SEC("sk_skb/stream_parser")
int sk_skb_parser(struct __sk_buff *skb) {
bpf_printk("Processing socket data\n");
return 0;
}
char _license[] SEC("license") = "GPL";
编译 & 运行:
clang -O2 -target bpf -c sk_skb.c -o sk_skb.o
sudo bpftool prog load sk_skb.o /sys/fs/bpf/sk_skb
sudo bpftool net attach sk_skb id <prog_id>
输出:
Processing socket data
2.10 cgroup 设备程序
功能:禁止访问 /dev/sda。
代码 (cgroup_device.c):
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
SEC("cgroup/device")
int cgroup_device(struct bpf_cgroup_dev_ctx *ctx) {
if (ctx->major == 8 && ctx->minor == 0) { // /dev/sda
bpf_printk("Blocking access to /dev/sda\n");
return 0; // BLOCK
}
return 1; // ALLOW
}
char _license[] SEC("license") = "GPL";
编译 & 运行:
clang -O2 -target bpf -c cgroup_device.c -o cgroup_device.o
sudo bpftool prog load cgroup_device.o /sys/fs/bpf/cgroup_device
sudo bpftool cgroup attach /sys/fs/cgroup/unified/ id <prog_id>
输出:
Blocking access to /dev/sda
2.11 套接字消息传递程序
功能:过滤 sendmsg() 消息。
代码 (sk_msg.c):
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
SEC("sk_msg")
int sk_msg_handler(struct sk_msg_md *msg) {
bpf_printk("Intercepted sendmsg\n");
return 0;
}
char _license[] SEC("license") = "GPL";
编译 & 运行:
clang -O2 -target bpf -c sk_msg.c -o sk_msg.o
sudo bpftool prog load sk_msg.o /sys/fs/bpf/sk_msg
sudo bpftool net attach sk_msg id <prog_id>
输出:
Intercepted sendmsg
2.12 原始跟踪点程序
功能:跟踪 sys_enter 事件。
代码 (raw_tracepoint.c):
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
SEC("raw_tracepoint/sys_enter")
int raw_tracepoint_handler(struct bpf_raw_tracepoint_args *ctx) {
bpf_printk("Raw tracepoint triggered\n");
return 0;
}
char _license[] SEC("license") = "GPL";
编译 & 运行:
clang -O2 -target bpf -c raw_tracepoint.c -o raw_tracepoint.o
sudo bpftool prog load raw_tracepoint.o /sys/fs/bpf/raw_tracepoint
sudo bpftool perf attach pinned /sys/fs/bpf/raw_tracepoint
输出:
Raw tracepoint triggered
2.13 cgroup 套接字地址程序
功能:阻止绑定到 0.0.0.0:80。
代码 (cgroup_sock_addr.c):
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/in.h>
SEC("cgroup/bind4")
int cgroup_bind4(struct bpf_sock_addr *ctx) {
if (ctx->user_port == htons(80) && ctx->user_ip4 == 0) {
bpf_printk("Blocking bind to 0.0.0.0:80\n");
return 0; // BLOCK
}
return 1; // ALLOW
}
char _license[] SEC("license") = "GPL";
编译 & 运行:
clang -O2 -target bpf -c cgroup_sock_addr.c -o cgroup_sock_addr.o
sudo bpftool prog load cgroup_sock_addr.o /sys/fs/bpf/cgroup_sock_addr
sudo bpftool cgroup attach /sys/fs/cgroup/unified/ id <prog_id>
输出:
Blocking bind to 0.0.0.0:80
2.14 套接字重用端口程序
功能:自定义 SO_REUSEPORT 负载均衡。
代码 (reuseport.c):
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
SEC("sockops")
int sockops_handler(struct bpf_sock_ops *ctx) {
if (ctx->op == BPF_SOCK_OPS_REUSEPORT_SELECT) {
ctx->reuseport_index = 0; // Always select first socket
bpf_printk("Reuseport selection\n");
}
return 0;
}
char _license[] SEC("license") = "GPL";
编译 & 运行:
clang -O2 -target bpf -c reuseport.c -o reuseport.o
sudo bpftool prog load reuseport.o /sys/fs/bpf/reuseport
sudo bpftool net attach sockops id <prog_id>
输出:
Reuseport selection
2.15 流量解析程序
功能:解析 HTTP 流量。
代码 (flow_dissector.c):
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
SEC("flow_dissector")
int flow_dissector_handler(struct __sk_buff *skb) {
bpf_printk("Parsing network flow\n");
return 0;
}
char _license[] SEC("license") = "GPL";
编译 & 运行:
clang -O2 -target bpf -c flow_dissector.c -o flow_dissector.o
sudo bpftool prog load flow_dissector.o /sys/fs/bpf/flow_dissector
sudo bpftool net attach flow_dissector id <prog_id>
输出:
Parsing network flow
42

被折叠的 条评论
为什么被折叠?



