近来,没怎么学习 eBPF,就给 pwru 做下贡献。

  1. Replace objs with collection
  2. Support tracing tc-bpf
  3. Support tracking skb clones
  4. Accelerate attaching/detaching kprobes WIP

Replace objs with collection

该 PR 重构了一下 pwru 中管理 bpf 对象的代码,将 bpf 对象的管理从 struct 改成 collection

因为 --backend kprobe-multi--output-skb 会生成多个 struct,导致 pwru 为了兼容这些选项而生成了 getter 代码;getter 就是根据不同的选项从对应的 struct 中获取 bpf 对象。

然后,go-ebpf 库除了 bpf2go 生成 struct 之外,还提供了 collection 的方式来管理 bpf 对象。对于 pwru 动态获取 bpf 对象的需求,collection 更加适合。

使用 collection 方式后,pwru 不需要再为了兼容 --backend kprobe-mult--output-skb 而生成 getter 代码了,可以直接从 collection 中获取 bpf 对象。

最终,效果显著:

1
2
3
# git diff --stat 88ccb274abd158d7b4ec82a051bf2bdc0572fefa 6639252d68ba99600f0ec7636c75ad34f7dcf210
...
59 files changed, 14 insertions(+), 22480 deletions(-)

Support tracing tc-bpf

该 PR 支持了 tc-bpf 的跟踪。意即,跟踪 tc-bpf 就像其它 kprobe 一样,能够跟踪 tc-bpf 处理 skb 的事件。

效果如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# ./pwru --output-limit-lines 10  --output-meta --output-tuple --filter-trace-tc --filter-func '.*udp' icmp
2023/10/21 15:22:05 Attaching kprobes (via kprobe-multi)...
1 / 1 [------------------------------------------------------------------------------------------------------------] 100.00% ? p/s
2023/10/21 15:22:05 Attached (ignored 0)
2023/10/21 15:22:05 Listening for events..
               SKB    CPU          PROCESS                     FUNC
0xffff940944c67900      3        [<empty>]                    dummy netns=4026531840 mark=0x0 iface=2(enp0s1) proto=0x0800 mtu=1500 len=98 192.168.64.1:0->192.168.64.2:0(icmp)
0xffff940944c67c00      3           [pwru]                    dummy netns=4026531840 mark=0x0 iface=2(enp0s1) proto=0x0800 mtu=1500 len=98 192.168.64.1:0->192.168.64.2:0(icmp)
0xffff940944c67e00      3        [<empty>]                    dummy netns=4026531840 mark=0x0 iface=2(enp0s1) proto=0x0800 mtu=1500 len=98 192.168.64.1:0->192.168.64.2:0(icmp)
0xffff9408f1c97c00      3        [<empty>]                    dummy netns=4026531840 mark=0x0 iface=2(enp0s1) proto=0x0800 mtu=1500 len=98 192.168.64.1:0->192.168.64.2:0(icmp)
0xffff9408f1c97600      3        [<empty>]                    dummy netns=4026531840 mark=0x0 iface=2(enp0s1) proto=0x0800 mtu=1500 len=98 192.168.64.1:0->192.168.64.2:0(icmp)
0xffff940944c67700      3        [<empty>]                    dummy netns=4026531840 mark=0x0 iface=2(enp0s1) proto=0x0800 mtu=1500 len=98 192.168.64.1:0->192.168.64.2:0(icmp)
0xffff940944c67b00      3        [<empty>]                    dummy netns=4026531840 mark=0x0 iface=2(enp0s1) proto=0x0800 mtu=1500 len=98 192.168.64.1:0->192.168.64.2:0(icmp)
0xffff940944c67f00      3        [<empty>]                    dummy netns=4026531840 mark=0x0 iface=2(enp0s1) proto=0x0800 mtu=1500 len=98 192.168.64.1:0->192.168.64.2:0(icmp)
0xffff940944c67400      3        [<empty>]                    dummy netns=4026531840 mark=0x0 iface=2(enp0s1) proto=0x0800 mtu=1500 len=98 192.168.64.1:0->192.168.64.2:0(icmp)
0xffff940944c67900      3        [<empty>]                    dummy netns=4026531840 mark=0x0 iface=2(enp0s1) proto=0x0800 mtu=1500 len=98 192.168.64.1:0->192.168.64.2:0(icmp)
2023/10/21 15:22:08 Printed 10 events, exiting program..

其中关键的 bpf 代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
SEC("fentry/tc")
int BPF_PROG(fentry_tc, struct sk_buff *skb) {
    struct event_t event = {};

    if (!handle_everything(skb, ctx, &event))
        return BPF_OK;

    event.skb_addr = (u64) skb;
    event.addr = bpf_get_func_ip(ctx);
    bpf_map_push_elem(&events, &event, BPF_EXIST);

    return BPF_OK;
}

跟踪 tc-bpf 中的 skb 的参数为什么是 struct sk_buff 而不是 struct __sk_buff 呢?详情请查看 eBPF Talk: trace tc-bpf 程序

Support tracking skb clones

该 PR 是为了支持需求 Track SKB clones,即跟踪 skb 的克隆。

只有在指定 --filter-track-skb 选项后,才会跟踪 skb 的克隆。当然,得当前内核支持 fexit 才行。

经过一番讨论后,最终决定使用 fexit 来跟踪 skb_copy()skb_clone() 这两个函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
static __always_inline int
track_skb_clone(u64 old, u64 new) {
    if (bpf_map_lookup_elem(&skb_addresses, &old))
        bpf_map_update_elem(&skb_addresses, &new, &TRUE, BPF_ANY);

    return BPF_OK;
}

SEC("fexit/skb_clone")
int BPF_PROG(fexit_skb_clone, u64 old, gfp_t mask, u64 new) {
    return track_skb_clone(old, new);
}

SEC("fexit/skb_copy")
int BPF_PROG(fexit_skb_copy, u64 old, gfp_t mask, u64 new) {
    return track_skb_clone(old, new);
}

Accelerate attaching/detaching kprobes

该 PR 还在审核中,因为 GitHub Actions 中的 bpf-next 没跑成功。

该 PR 主要是为了加速 pwrukprobe 的挂载和卸载。将 kprobe 的挂载和卸载方式从串行改成并行。

并行化后,pwrukprobe 的挂载和卸载的速度提升了一丢丢:

1
2
3
4
5
6
7
8
2023/10/25 14:16:03 Attaching kprobes (via kprobe)...
1462 / 1462 [----------------------------------------------------------------------------------------------------] 100.00% 342 p/s
2023/10/25 14:16:07 Attached (ignored 0)
2023/10/25 14:16:07 Listening for events..
               SKB    CPU          PROCESS                     FUNC
^C2023/10/25 14:16:08 Received signal, exiting program..
2023/10/25 14:16:08 Detaching kprobes...
1462 / 1462 [-----------------------------------------------------------------------------------------------------] 100.00% 35 p/s

对比串行化的挂载和卸载:

1
2
3
4
5
6
7
8
2023/10/25 14:17:27 Attaching kprobes (via kprobe)...
1462 / 1462 [----------------------------------------------------------------------------------------------------] 100.00% 282 p/s
2023/10/25 14:17:32 Attached (ignored 0)
2023/10/25 14:17:32 Listening for events..
               SKB    CPU          PROCESS                     FUNC
^C2023/10/25 14:17:33 Received signal, exiting program..
2023/10/25 14:17:33 Detaching kprobes...
1462 / 1462 [-----------------------------------------------------------------------------------------------------] 100.00% 21 p/s

当然,这是为了支持需求 Would be nice if probe unloading was faster

Add XDP support

未来,还会支持 XDP 的跟踪。详情请查看 Add XDP support

不过,目前 pwru 的 PR 都没有合并,因为 bpf-next GitHub Actions 没跑成功。所以,还是先等等吧。

小结

本文介绍了最近给 pwru 做的贡献,主要是重构了一下 pwru 中管理 bpf 对象的代码,支持了 tc-bpf 的跟踪,支持了跟踪 skb 的克隆,加速了 kprobe 的挂载和卸载。