历经千辛万苦,给 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 项目的一个里程碑。