eBPF约束
内核态约束
1. 内核态eBPF无法使用C语言标准库。因为不支持malloc,所以无法扩展skb空间且无法直接从内核态拷贝整个报文到用户态。
2. 内核态eBPF无法获取当前时间,bpf_ktime_get_ns函数返回系统启动后运行纳秒数,不包括系统暂停时间。
https://www.man7.org/linux/man-pages/man7/bpf-helpers.7.html
3. 内核态和用户态只能通过全局变量(本质也是map)和map来完成数据交互。推荐使用pinning map即持久化map,对应目录是/sys/fs/bpf/,方便用户态加载。
4. 内核态实时上报数据必须用BPF_MAP_TYPE_PERF_EVENT_ARRAY。写法参考cilium中上报trace和drop事件的map,定义在bpf/lib/events.h。
5. 因为不支持C语言指针转成GO语言指针,所以内核态带指针的结构体无法转成GO结构体。
6. bpf_map_lookup_elem函数第2个参数key,如果是结构体,需要类似用struct test t = {0};再设置属性方式初始化(告知校验器padding和属性值),否则加载eBPF到网口可能会失败。
7. BPF_MAP_TYPE_ARRAY类型map,key类型必须是uint32,不能是其他。
8. eBPF map想要持久化,必须设置pinning是1即LIBBPF_PIN_BY_NAME。
9. eBPF map只支持有限循环,5.17内核以下版本使用#pragma unroll加在for上展开,5.17以上版本使用bpf_loop定义有限次循环。
10. 当eBPF map中key/value数据结构发生变更时,需要删除map重建。
用户态约束
1. 用户态cilium/ebpf库中,bpf2go生成的go结构体首字母小写即开头是bpf,后面是变量名改成驼峰,只有main包才能访问。如果是单个main函数,那么通过go channel把别的包数据传递给main包,不是直接调用方法,可以避免循环依赖;如果是多个main函数,那么基于bpf2go再次把c转成go完成内嵌,不同main函数之间没有依赖关系。
2. 用户态cilium/ebpf库中,bpf2go使用反射把eBPF map内的数据转为go结构体,完成内核态与用户态之间传输。用户态手动定义结构体的问题在于,除了首字母大写,go-binary库不是简单的memcpy,可能会让go结构体成员读到的值是错误的。推荐自动生成Go结构体,bpf2go -type参数指定C结构体类型,C代码中加上struct key类型 *unused_xxx __attribute__((unused));