历经千辛万苦,给 eCapture 支持的 pcap-filter PR feat: Support pcap-filter expression for pcap mode 终于合并了:eCapture v0.7.4发布,支持Pcap Filter包过滤语法。感谢 @CFC4N 大佬的认可。

支持 pcap-filter 特性并不困难,参考 eBPF Talk: tc-dump 支持 pcap-filter。但是,eCapture 需要支持 arm64 服务器和 Android;所以,支持 pcap-filter 特性的过程并不顺利。

eCapture 的实现方式

迅速在 amd64 VM 上完成了 POC。

参考 tc-dump,先在 bpf 代码里留个桩:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// ${ECAPTURE}/kern/tc.h

// filter_pcap_ebpf_l2 is a stub to inject pcap-filter.
static __noinline bool filter_pcap_ebpf_l2(void *_skb, void *__skb,
                                           void *___skb, void *data,
                                           void* data_end) {
    return data != data_end && _skb == __skb && __skb == ___skb;
}

static __always_inline bool filter_pcap_l2(struct __sk_buff *skb, void *data,
                                           void *data_end) {
    return filter_pcap_ebpf_l2((void *) skb, (void *) skb, (void *) skb, data,
                               data_end);
}

而后,在 Go 里使用 elibpcap 库将 pcap-filter 注入到 bpf 的桩上:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// ${ECAPTURE}/module/probe_pcap.go

func injectPcapFilter(progSpec *ebpf.ProgramSpec, pcapFilter string) (*ebpf.ProgramSpec, error) {
    if pcapFilter == "" {
        return progSpec, nil
    }

    var err error
    progSpec.Instructions, err = elibpcap.Inject(pcapFilter, progSpec.Instructions, elibpcap.Options{
        AtBpf2Bpf:  "filter_pcap_ebpf_l2",
        DirectRead: true,
        L2Skb:      true,
    })
    if err != nil {
        return nil, fmt.Errorf("failed to inject pcap filter: %w", err)
    }

    return progSpec, nil
}

func prepareInsnPatchers(m *manager.Manager, ebpfFuncs []string, pcapFilter string) []manager.InstructionPatcherFunc {
    preparePatcher := func(ebpfFunc string) manager.InstructionPatcherFunc {
        return func(m *manager.Manager) error {
            progSpecs, ok, err := m.GetProgramSpec(manager.ProbeIdentificationPair{EbpfFuncName: ebpfFunc})
            if err != nil || !ok || len(progSpecs) == 0 {
                return fmt.Errorf("failed to get program spec for %s: %w", ebpfFunc, err)
            }

            for _, progSpec := range progSpecs {
                progSpec, err = injectPcapFilter(progSpec, pcapFilter)
                if err != nil {
                    return fmt.Errorf("failed to inject pcap filter for %s: %w", ebpfFunc, err)
                }
            }

            return nil
        }
    }

    insnPatchers := make([]manager.InstructionPatcherFunc, 0, len(ebpfFuncs))
    for _, ebpfFunc := range ebpfFuncs {
        fn := ebpfFunc
        insnPatchers = append(insnPatchers, preparePatcher(fn))
    }

    return insnPatchers
}

最后,将 ./ecapture tls -m pcap -i eth0 --pcapfile=ecapture.pcapng tcp port 443 pcap 模式的命令行的剩余参数当作 pcap-filter 表达式:

1
2
3
4
5
// ${ECAPTURE}/cli/cmd/gotls.go

    if goc.PcapFilter == "" && len(args) != 0 {
        goc.PcapFilter = strings.Join(args, " ")
    }

就这么简单,就像将大象装进冰箱一样。。。

抓破头的 __noinline__ undefined 问题

在提交 PR 之后,遇到了不少问题,其中最让人头疼的是 __noinline__ undefined 问题。

在 CI 里编译 bpf 代码时,遇到了如下错误:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
2024-02-03T08:04:11.0855280Z In file included from kern/boringssl_a_14_kern.c:72:
2024-02-03T08:04:11.0856614Z In file included from ./kern/openssl.h:16:
2024-02-03T08:04:11.0858122Z ./kern/tc.h:116:8: error: use of undeclared identifier '__noinline__'
2024-02-03T08:04:11.0859355Z static __noinline bool filter_pcap_ebpf_l2(void *_skb, void *__skb,
2024-02-03T08:04:11.0860510Z        ^
2024-02-03T08:04:11.0862171Z ./kern/bpf/bpf_helpers.h:44:35: note: expanded from macro '__noinline'
2024-02-03T08:04:11.0863233Z #define __noinline __attribute__((noinline))
2024-02-03T08:04:11.0864452Z                                   ^
2024-02-03T08:04:11.0866148Z /lib/modules/6.2.0-1019-azure/build/include/linux/compiler_attributes.h:234:56: note: expanded from macro 'noinline'
2024-02-03T08:04:11.0867810Z #define   noinline                      __attribute__((__noinline__))
2024-02-03T08:04:11.0869504Z                                                        ^
2024-02-03T08:04:11.0900704Z 1 error generated.

直接搜索 error: use of undeclared identifier '__noinline__' 的话,大概率会搜索到如下资料:

而查看 compiler_attributes.h 的话,会发现 noinline 的定义:

1
2
3
4
5
6
7
8
9
// ${KERNEL}/include/linux/compiler_attributes.h

/*
 * Note the missing underscores.
 *
 *   gcc: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-noinline-function-attribute
 * clang: mentioned
 */
#define   noinline                      __attribute__((__noinline__))

不过,不要被这些资料给蒙蔽了。

随后,按照 CFC4N 大佬的提示,当我 make nocore 时,就复现了这个错误。

而后,我就想到了一个大聪明的办法,将 noinline 给 undef 了:

1
2
3
4
5
// ${ECAPTURE}/kern/ecapture.h

#if defined(noinline)
#undef noinline
#endif

undef 了之后,bpf 代码里使用的 __noinline 就会被展开成 __attribute__((noinline)),而不是 __attribute__((__attribute__((noinline)))) 了。

问题解决!!!

小结

eCapture 支持 pcap-filter 特性的 PR 经历了不少波折,但是最终还是合并了。这也是 eCapture 项目的一个里程碑。