项目挑战:用 C 语言从零实现 eBPF 网络追踪器 (C-pwru)
项目挑战:用 C 语言从零实现 eBPF 网络追踪器 (C-pwru)
项目挑战:用 C 语言从零实现 eBPF 网络追踪器 (C-pwru)
pwru 是一个神奇的工具,它能告诉你在 Linux 内核里“包去哪了”。虽然原版是用 Go 编写的,但作为追求极致性能和底层掌控力的开发者,我决定发起一个挑战:用纯 C 语言和 libbpf 实现一个功能对标的 C-pwru。
本文是该项目的里程碑计划书(TODO List)。
1. 项目目标
实现一个命令行工具,能够:
- 自动识别内核中所有处理
sk_buff的函数。 - 支持按 IP、端口、协议过滤流量。
- 实时打印数据包经过的内核函数名及其处理时间。
- (进阶)支持打印内核堆栈(Stack Trace)。
2. 核心技术栈
- 内核态: eBPF (C 语言)
- 用户态: C11 + libbpf
- 编译链: LLVM/Clang
- 内核特性: BTF (BPF Type Format), Kprobes, RingBuffer
3. 阶段性里程碑 (TODO)
阶段一:环境搭建与“Hello skb”
- 搭建
libbpf-bootstrap开发环境。 - 编写基础 eBPF 程序,挂载到单个已知函数(如
ip_rcv)。 - 成功在用户态捕获并打印
sk_buff结构体的内存地址。
阶段二:解析与过滤 (内核态逻辑)
- 实现
skb深度解析:从sk_buff中提取源/目的 IP 和端口。 - 实现用户态配置下发:通过 BPF Map 传递过滤条件。
- 引入
bpf_ringbuf替代perf_event_array以获得更高性能。
阶段三:BTF 魔法 (自动化核心)
- 在用户态利用
libbpf加载/sys/kernel/debug/btf/vmlinux。 - 算法实现:遍历 BTF 类型树,筛选出所有第一个参数类型为
struct sk_buff *的函数。 - 验证筛选结果,确保能找回像
ip_local_deliver这里的核心函数。
阶段四:动态批量挂载
- 实现循环逻辑,将同一个 BPF 程序动态附加(Attach)到筛选出的几百个函数上。
- 解决“参数偏移”问题:处理某些函数
skb不在第一个参数的情况。 - 处理挂载失败的优雅降级逻辑。
阶段五:体验与性能优化
- 增加输出美化:类似于
tcpdump的实时滚动视图。 - 实现内核堆栈抓取:使用
bpf_get_stackid并在用户态还原符号。 - 压力测试:在高并发流量下测试对系统的性能影响。
4. 关键代码片段预研 (用户态 BTF 搜索)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 伪代码:如何寻找带 sk_buff 参数的函数
struct btf *btf = btf__load_vmlinux_btf();
__u32 type_id, skb_type_id;
// 1. 找到 sk_buff 结构体的 ID
skb_type_id = btf__find_by_name_kind(btf, "sk_buff", BTF_KIND_STRUCT);
// 2. 遍历所有函数
for (i = 1; i <= btf__type_cnt(btf); i++) {
const struct btf_type *t = btf__type_by_id(btf, i);
if (!btf_is_func(t)) continue;
// 检查参数...
}
5. 结语
这将是一段漫长但充满收获的旅程。每一行 C 代码都将带我更深入地接触 Linux 网络协议栈的跳动脉搏。
Stay hungry, Stay eBPF.
[ ] 最后更新日期:2025-12-19
This post is licensed under CC BY 4.0 by the author.