当遇上 cilium/pwru
时,我便放弃维护自己的 skbtracer
了。
挖坑
之前,学习了 eBPF Talk: 全局变量实战指南,就打算在开源项目 GitHub cilium/pwru 上一展身手:
2023 年 2 月 4 日提了 PR,很快就被合并了。
而后 2 月 24 日有人提了个 issue:
气人的是,3 月 9 日晚上跑 ./pwru --filter-dst-ip 1.1.1.1 --output-tuple
的时候,遇到同样的问题。
既然遇上了,那就不能放过它。
分析
使用 bpf_printk()
打印日志,很快就定位出有问题的代码位置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
static __always_inline bool
config_tuple_empty() {
if (!addr_empty(cfg->saddr) || !addr_empty(cfg->daddr)) {
return false;
}
if (cfg->l4_proto || cfg->sport || cfg->dport || cfg->port) {
return false;
}
return true;
}
static __always_inline bool
filter_l3_and_l4(struct sk_buff *skb) {
if (config_tuple_empty()) {
bpf_printk("pwru, config is empty\n");
return true;
}
// ...
}
|
只有 --filter-dst-ip
or --filter-src-ip
时,这里判断的 config 就为空。
奇怪的地方就在这里,明明指定了 --filter-dst-ip
。
分析过程如下(就不详细展开了):
bpftool prog dump jited id ${PROG ID}
。
bpftool prog dump xlated id ${PROG ID}
。
llvm-objdump -S kprobepwru_bpfel.o
查看 BPF 指令。
发现真相:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
; if (cfg->mark && BPF_CORE_READ(skb, mark) != cfg->mark) {
95: LdXMemW dst: r1 src: rfp off: -8 imm: 0
; if (cfg->mark && BPF_CORE_READ(skb, mark) != cfg->mark) {
96: LdXMemW dst: r2 src: r6 off: 4 imm: 0
; if (cfg->mark && BPF_CORE_READ(skb, mark) != cfg->mark) {
97: JNEReg dst: r1 off: 446 src: r2
; if (cfg->l4_proto || cfg->sport || cfg->dport || cfg->port) {
98: LdXMemB dst: r1 src: r6 off: 41 imm: 0
; if (cfg->l4_proto || cfg->sport || cfg->dport || cfg->port) {
99: JNEImm dst: r1 off: 10 imm: 0
; if (cfg->l4_proto || cfg->sport || cfg->dport || cfg->port) {
100: LoadMapValue dst: r1, fd: 0 off: 0 <.rodata>
102: LdXMemH dst: r2 src: r1 off: 42 imm: 0
; if (cfg->l4_proto || cfg->sport || cfg->dport || cfg->port) {
103: JNEImm dst: r2 off: 6 imm: 0
|
检查 addr
的代码被编译器优化掉了。
证明:addr_empty()
有问题。
1
2
3
4
|
static __always_inline bool
addr_empty(union addr addr) {
return addr.v6addr.d1 == 0 && addr.v6addr.d2 == 0;
}
|
填坑
尝试填坑的过程都是泪。
- 将
union addr
改成 struct addr
。
- 意识到传的实参是
cfg->saddr
时,将 union addr
改成 union addr *
。
以上两个尝试都失败了。
回头看看 cfg
的定义:
1
2
|
static volatile const struct config CFG;
#define cfg (&CFG)
|
Emm,“天才”般的写法。
将 addr_empty()
改为:
1
2
|
#define addr_empty(addr) \
((addr).v6addr.d1 == 0 && (addr).v6addr.d2 == 0)
|
就阔以了。
这么改动的原因,也是尝试填坑失败的原因,如下:
- 将全局变量
CFG
的一部分 union addr
传给函数时,编译器认为这部分内存不是 volatile
的,所以将 addr_empty()
函数调用给优化掉了。
- 编译器不允许将
volatile const structc config CFG
的一部分使用指针提供给函数使用。
- 宏不是函数,不存在传参问题。
提了如下 PR 进行修复:
总结
要么不挖坑,要么挖坑的同时得有能力填坑。
发现是自己破坏了 pwru
,心里有点难过;毕竟这是大家都可以使用的软件。
还好是自己修复的,拿回了点心理补偿。