这篇文章提供了关于 eBPF 应用程序开发的指南。正如标题所示,文章主要关注 eBPF 201 的概念,而不是提供另一篇关于 eBPF 技术是什么的入门级文章。我们提供了简短的介绍,但主要关注需要部署生产 eBPF 应用程序的开发团队的下一组概念和最佳实践。我们将探讨使 BPF 应用程序可以在多个内核版本和环境中部署和维护的编程语言和工具链。
BPF/eBPF 是由轻量级的隔离虚拟机和一组辅助函数组成的操作系统内核技术。在本文中,我们关注Linux[1]内核中的 eBPF,尽管它也适用于其他操作系统平台,如 Windows 和 FreeBSD。
这项技术使用户能够在内核中运行提供的程序,以扩展内核的功能。验证程序在加载到内核之前会检查 BPF 程序,以确保它们不会危及内核的可靠性。这些程序会被 JIT(Just-In-Time)编译成本机指令,因此它们足够高效,可以在性能要求最高的情况下执行,例如网络数据包处理。
总体的好处是以一种安全、更灵活且开发速度更快的方式为操作系统内核提供可编程性和可扩展性,而不是开发内核模块或直接增强主要内核功能本身。
下图中的图表显示了 Linux 内核中的 BPF 虚拟机、BPF 程序可以附加的挂钩点以及 BPF 程序可用的各种映射和辅助函数。
如果你对 eBPF 还不熟悉,eBPF 详尽介绍[2]将为你提供一个很好的基础,然后再阅读本文的其余部分。
BPF 已经从早期作为 Linux 内核的数据包过滤引擎(图 2)的时代走来了很长的路。在过去的十年中,eBPF VM 已经成为一种通用执行引擎,由Clang[3]工具链支持,具有许多不同用途的程序类型和辅助函数。最近,重点放在了开发者和操作用户体验上,由 BTF、CO-RE 和 libbpf 支持。
BPF 功能已经在从 3.15 到最新的 6.x 版本的许多内核中引入。在任何给定的内核版本中找出可用的功能集可能是困难的。BCC 项目维护了一个有用的BPF 功能按 Linux 内核版本排序的列表[4],它可以帮助你确定在给定的 Linux 内核中的功能可用性。
以下是开发人员在学习 eBPF 应用程序开发时常遇到的一些常见问题。在本文中,我们试图提供这些问题的一些答案,同时注意到这个领域仍在不断发展,一些内容将在未来发生变化。
Q:用于 eBPF 应用程序的编程语言和开发工具有哪些选择,以及每种选择的利弊是什么?
A:这将在本文的选择您的 eBPF 应用程序栈[5]部分中进行讨论。
Q:我们了解 eBPF 的概念,但迄今为止还没有进行任何内核开发。为了有效地开发 eBPF 应用程序,我们需要了解内核源代码的相关部分和开发环境吗?
A:开发内核经验不是开发基于 eBPF 的应用程序所必需的。然而,强烈建议对内核中相关的功能领域有很好的了解。例如,当开发网络应用程序时,您应该了解与网络套接字、内核数据包路径、netfilter 功能等相关的一些内核内部内容。
Q:在跨目标方式开发 eBPF 应用程序时,其中开发涉及多个开发系统和内核版本,同时存在多个目标系统和平台,旨在实现跨所有这些平台的可移植性和可维护性,我们需要了解哪些内容?
A:这将在本文的eBPF 应用程序跨开发、可移植性、CO-RE 和内核 API 稳定性[6]部分中进行讨论。
Q:有哪些实际的分发 eBPF 程序和在单个系统中并发安装来自多个供应商的程序的操作模型?
A:在撰写本文时,这是一个新兴的开发领域。目前,eBPF 程序通常被打包为较大项目中的不可分割的子组件,例如Cilium[7]或Pixie[8]。结合和安装多个独立的基于 eBPF 的应用程序可能会导致不一致或错误的行为,特别是如果在使用的 eBPF 附加点中存在重叠(如在最近的案例[9]中所示)。
新项目,如libxdp[10]和bpfd[11],正在添加功能以解决此类操作问题。我们期望在未来的文章中更详细地介绍这些项目。
Q:上游核心 eBPF 开发模型是什么,我们如何跟踪仍在开发中的 eBPF 功能?
A:新的 eBPF 内核基础设施和功能是在 Linux 内核的“bpf-next” git 存储库中开发的,最终合并到主要的 Linux 存储库中,作为正式的 Linux 内核发布的一部分。该过程在BPF 开发 FAQ[12]中有更详细的描述。
此外,内核 BPF 函数越来越多地作为kfuncs[13](内核函数)添加。它们提供了 eBPF 应用程序可以调用的 API,但相对于内核 BPF 辅助函数的稳定性较低,后者被认为是内核 ABI 的一部分。正在使用的 kfunc API 将得到支持和维护,但不属于内核 ABI。
Q:在编写 eBPF 应用程序时,需要注意哪些软件许可要求?
A:Linux 内核 eBPF 运行时的组件使用 GPLv2 许可证。这包括参考解释器、验证器、JIT 编译器和 BPF 助手等组件。所有 kfuncs 和许多(但不是全部)BPF 助手都是 GPL 许可的,这意味着 BPF 应用程序通常(尽管不总是)需要以 GPL-v2 兼容许可证发布。请注意,没有兼容许可归属的 BPF 验证器将不允许加载 BPF 应用程序。为了启用一些宽松的下游代码重用,应用程序可以考虑使用双重许可,此外将其 BPF 应用程序许可为诸如 MIT、BSD-2-Clause 等宽松许可证。有关更多详细信息,请参阅 Linux 内核文档的BPF 许可指南[14]。
Q:eBPF 技术是否促进或与内核数据平面卸载技术交汇?
A:对于 Linux 内核功能,特别是网络功能的硬件卸载到智能网卡是一个广泛的话题,涵盖了许多超出本博客范围的技术。暂时简要说明一下,您确实可以利用 eBPF 技术来进行硬件卸载。例如,像ConnectX-6[15]这样的某些智能网卡支持将基于 XDP 的 eBPF 程序从主机 CPU 卸载到网卡上的 CPU。
在启动新的 eBPF 项目时,开发团队需要决定使用哪种软件栈来开发计划中的应用程序。有多种编程语言和可用的库,具有不同的成熟度和与最新内核 BPF 功能的特性相匹配。
以下是 eBPF 应用程序的常见选择,也是新项目的不错选择:
这些应用程序开发栈如图 3 所示。
还有一种低代码选项,团队可以利用可用工具,如 iproute2 的 tc、bpftool 和 bpftrace 来处理计划中应用程序的用户空间和/或内核 BPF 程序。
需要注意的一个关键点是,以这种方式列出这些选项并不排除在这些选项以及其他选项之间混合和匹配的可能性。例如,您可以使用 C 编写内核空间 eBPF 程序,然后在用户空间使用 Golang 或 Rust 编写相应的程序,通常有很多理由这样做。还有更多的选项,包括通过直接调用内核 ebpf 系统调用[19]或使用像BCC[20]这样的框架编写自己的自定义 eBPF 加载程序。但通常情况下,本文列出的选项将更适合新项目,我们将讨论这些选项以阐明一些核心概念。
这种软件栈选择通常是最全面的,支持最新的 eBPF 功能和工具。
libbpf[21]是一个 C 库,提供 eBPF 实用函数和定义,用户空间程序可以使用它来管理内核空间的 eBPF 程序。这个应用程序栈选项是最新的,是上游 Linux 源代码仓库的一部分,与最新的 eBPF 功能保持一致,并用于测试新的内核 eBPF 功能。这个库和内核 BPF 代码由内核开发人员共同编写、审查和测试,作为引入新的 eBPF 功能的一部分。libbpf 还是CO-RE(Compile Once Run Everywhere)[22]功能的官方实现,该功能在提供 eBPF 程序可移植性方面显著改进了开发人员的体验,后文将详细讨论。
在 Linux 源代码仓库中,libbpf 位于tools/lib/bpf[23]。API 文档可在https://libbpf.readthedocs.io/en/latest/api.html 到。
与内核源代码分开维护的libbpf 的镜像[24]应该用于包含在应用程序中。该 API 包括一些关键的定义,如 eBPF 帮助函数的原型定义,用户空间类型定义以匹配相应的内核类型,并用于加载和附加程序以设置 eBPF 映射的函数。libbpf 库于 2022 年中发布了 1.0 版本[25],并且正在积极开发,最近发布了 1.2 版本[26]。
libbpf-bootstrap[27]是 libbpf 的伴随仓库之一,提供了使用 libbpf 功能的一组有用的模板和示例程序[28]。这是一个快速编写用户空间程序的良好起点,该程序将与最新的 eBPF 功能一起使用。libbpf-bootstrap 仓库还说明了使用 bpftool 实用程序准备 eBPF 骨架程序的方法。骨架是一组由用户空间程序使用的类型和函数的定义,以便轻松打开、加载、附加和销毁 eBPF 程序对象。
1. 这经常是编写 eBPF 应用程序时的一个不错的栈选择。
在我们看来,这种应用程序栈是开发团队采用的一个可靠选择,特别是如果需要使用 C 语言用户空间程序或者需要最新的 eBPF 功能,这些功能可能并不总是通过其他用户空间加载器和其他编程语言和工具的实用程序获得。
举例来说,在撰写本文时,这是作者用来管理、加载和附加使用新 eBPF kfuncs(内核函数)的 eBPF 应用程序的几个可行选项之一。
2. 在应用程序仓库中将 libbpf 和 bpftool 作为 git 子模块包含。
我们还注意到,开发人员通常应通过将 libbpf 包含为其应用程序程序仓库中的 Git 子模块来使用 libbpf。这可以确保他们始终使用 libbpf 的最新发布版本,而不是他们可能在开发系统上拥有的内核源代码版本,或者由 Linux 发行版捆绑的版本。
3. 考虑使用 libbpf-bootstrap 作为应用程序程序的参考仓库。
在作者看来,libbpf-bootstrap 是用于新的 eBPF 项目或基于 libbpf 的应用程序的参考仓库。它作为使用 libbpf 的最佳实践示例进行维护(例如,使用 libbpf 和 bpftool 作为 Git 子模块)。
当在 Golang 中编写 eBPF 应用程序的用户空间部分时,这是一个不错的选择。例如,当 eBPF 应用程序的用户空间部分是 Kubernetes Operator 或 CRD 控制器时,通常会使用 Golang 编写。
Cilium ebpf 库[29]是一个独立的纯 Golang eBPF 实用工具库,独立于 Cilium 项目的其他应用程序,比如 Cilium Kubernetes CNI 插件。libbpfgo[30]是另一个此类 eBPF 实用函数库,用户空间 Golang 应用程序可以使用它来加载 eBPF 对象文件(从任何语言编译而来),附加到各种 eBPF 钩子点等等。
libbpfgo 使用围绕我们在前一节中讨论的 C 语言 libbpf 库的 Golang 包装器调用,因此有可能支持更多最新的核心 eBPF 功能。Cilium eBPF 库不是 libbpf 库的包装器,背后的社区略显多样化(相对于 libbpfgo),包括供应商 Isovalent 和 CloudFlare。
Aya[31]是一个用于 BPF 应用程序开发的纯 Rust 库,旨在与 libbpf 具有功能相当。与上文描述的 Cilium eBPF 库类似,Aya 不包含围绕 C 语言 libbpf 库的包装函数。正如前面提到的,用于 eBPF 内核程序的编程语言和工具链与应用程序的用户空间程序所使用的语言和工具无关。我们将在未来的一篇文章中详细描述这个选项,届时我们将介绍Aya[32]和bpfd[33]项目。
最佳实践的 eBPF 应用程序开发从一开始就考虑了跨平台开发、可移植性和可维护性。最近的 eBPF 内核增强功能,比如CO-RE(编译一次,随处运行)[34]大大改进了这一领域的开发体验。
图 4 显示了一个支持不同开发环境和不同目标系统的开发模型。开发环境使用了前面讨论的 C 与 libbpf。
在图中,一个开发人员正在使用运行内核 5.6 的开发计算机,而另一位开发人员则使用内核 5.10。这两位开发人员可以在两个不同的目标系统上进行测试,第一个目标系统运行带有内核 5.2 的 Debian,而第二个目标系统运行Red Hat 企业 Linux[35],内核已升级到 5.16 版本。
直到最近,这种开发模型无法用于 eBPF 应用程序,特别是那些与在内核版本之间更改的原始内核数据类型和结构进行交互的应用程序。因此,eBPF 应用程序必须在加载到目标系统时即时编译(使用其他加载功能,如BCC[36]的内置 CLANG/ LLVM)以获取实际目标系统内核的内核头文件和类型信息。
然而,被称为 CO-RE(Compile Once Run Everywhere)的新 eBPF 基础架构使单个 eBPF 二进制对象能够在不同的内核版本上加载和运行,无需重新编译或复杂的解决方法。请参考本文[37]及其附带的CO-RE 指南[38],其中提供了有关 CO-RE 和开发人员最佳实践的出色详细信息。
1. 在可能的情况下,使用支持 CO-RE 的 eBPF 加载器,该加载器支持基于内核数据类型的重定位能力。
本文前一节中讨论的 3 种应用程序堆栈选项都支持 CO-RE。其他 eBPF 加载器选项,如 BCC 框架,目前不支持 CO-RE。
2. 遵循 CO-RE 最佳实践。
这包括使用编译器/Clang 属性,例如__attribute__((preserve_access_index))
,以确保 eBPF 程序使用的内核数据类型的可重定位性,使用诸如BPF_CORE_READ()
宏之类的助手,以有效地访问内核数据,即使有多层指针间接引用,以及使用 CO-RE 功能来以编程方式检测内核版本和内核配置信息,以处理更复杂的内核数据类型更改,如CO-RE 指南[39]中所述。
3. 考虑使用 vmlinux.h
来简化包含头文件,而不是开发系统中打包的内核头文件。
vmlinux.h
文件是可以通过 bpftool 工具生成的文件,用于包括内核映像中的所有数据类型。这是一个方便的单个头文件,供 eBPF 程序从中包括,而不是一组广泛而有些特殊的内核头文件,可能会因开发人员的系统而异。我们建议创建此文件的精简版本,只包括应用程序实际需要的内核定义。这可以确保:
4. 针对所有目标内核版本和相关子系统配置测试 eBPF 应用程序。
即使遵循所有 CO-RE 最佳实践,也很重要测试任何 eBPF 应用程序,测试所有或一组宽泛的内核版本以及相关内核子系统和模块配置的变化,以确保在所有目标部署中具有完全的可移植性和正确性。
5. 确定应用程序所需的最低内核版本。
首先,阅读BPF 功能文档[40],以确定包含所需功能的内核版本范围。例如:
然后,您可以选择编写您的应用程序,仅使用内核版本中可用的 eBPF 函数,或者使用 BPF 功能探测来在运行时检测功能的可用性。
您的 BPF 程序可以检查内核版本:
#include <bpf/bpf_helpers.h>extern int LINUX_KERNEL_VERSION __kconfig;
int probe_kernel()
{
if (LINUX_KERNEL_VERSION > KERNEL_VERSION(4, 18, 0)) {
/* 我们在支持的内核版本上 */
} else {
/* 记录错误并优雅退出 */
}
...
}
您的 BPF 程序还可以探测单个内核功能或结构定义:
extern bool CONFIG_LWTUNNEL_BPF __kconfig __weak;if (CONFIG_LWTUNNEL_BPF) {
/* 从BPF配置lwtunnel */
}
if (bpf_core_type_exists(struct bpf_ringbuf)) {
/* 使用ringbuf而不是perf缓冲区 */
}
下图显示了用户空间应用程序及其嵌入式 BPF 程序的视图。该应用程序链接到 libbpf,libbpf 代表应用程序创建 BPF 映射并加载程序。
在此图中,我们可以看到 BPF 程序连接到网络数据路径中的tc
钩子。用户空间应用程序和 BPF 程序共享访问由内核中的 BPF 子系统管理的映射。BPF 程序还使用助手函数从其他内核子系统中访问数据。
这里讨论的主题最好通过示例应用程序来演示。我们将使用 DNS 跟踪实用程序来突出显示主要要点。
libbpf-bootstrap[41]为使用 libbpf 的 C 项目提供了一个很好的起点。在这里,我们通过复制 libbpf-bootstrap 的 Makefile 和项目布局,并根据自己的需求进行调整,创建了一个 dns-trace 实用程序项目:
.
├── Makefile
├── dns-trace.bpf.c # BPF代码
├── dns-trace.c # 用户空间代码
├── dns-trace.h # 共享定义
├── libbpf # libbpf git子模块
└── .output
├── dns-trace.skel.h # 骨架,由bpftool生成
└── vmlinux.h # 所有内核类型定义,由bpftool生成
dns-trace 实用程序有一个 BPF 部分,用于拦截 DNS 数据包,以及一个用户空间部分,用于解码 DNS 消息并报告各种指标。BPF 程序捕获的数据以事件流的形式通过 BPF 环形缓冲区发送到用户空间程序。struct dns_event
在 BPF 和用户空间程序中都被使用。请注意确保类型定义对两个编译单元都可以解析。
__u32
和 libbpf 头文件。<vmlinux.h>
时具有访问 UAPI 导出的内核类型的能力,同时还包括了所有内核类型定义。任何要包括在两个编译单元中的结构都需要使用 UAPI 导出的内核类型并避免内核的内部类型定义。
struct dns_event {
__u64 duration;
char ifname[IFNAMSIZ];
__u32 srcip;
__u32 dstip;
__u16 length;
unsigned char payload[MAXMSG];
__u16 id;
__u16 flags;
};
BPF 程序连接到net_dev_queue
内核跟踪点,以拦截主机上运行的任何程序发送或接收的所有数据包。跟踪点上下文包括一个指向保存数据包数据的struct sk_buff
的指针。我们只对访问sk_buff->data
和sk_buff->len
字段感兴趣,因此我们可以使用 CO-RE 来访问它们。BPF 程序定义了自己的struct sk_buff
的私有版本,其中只包含我们需要的字段。该结构使用preserve_access_index
属性进行注释,以便在加载 BPF 程序时进行 CO-RE 重定位。
struct sk_buff {
unsigned char *data;
unsigned int len;
} __attribute__((preserve_access_index));struct trace_event_raw_net_dev_template {
struct sk_buff *skbaddr;
} __attribute__((preserve_access_index));
此项目本地的struct sk_buff
定义清晰地说明了我们实际需要的结构的子集。C 代码使用BPF_CORE_READ()
宏来访问struct sk_buff
字段,再次在加载 BPF 程序时启用 CO-RE 重定位到运行的内核。
SEC("tracepoint/net/net_dev_queue")
int trace_net_packets(struct trace_event_raw_net_dev_template *ctx) {
unsigned char *data = BPF_CORE_READ(ctx, skbaddr, data);
unsigned int len = BPF_CORE_READ(ctx, skbaddr, len); do_trace(data, len);
return BPF_OK;
}
用户空间应用程序通过生成的 BPF 程序骨架使用 libbpf 来加载 BPF 程序并访问 BPF 程序中定义的映射。骨架由项目 Makefile 中的bpftool gen skeleton
生成。骨架派生自 BPF 程序,因此所有映射应该在 BPF C 代码中定义,然后通过用户空间代码中的骨架访问器引用。
生成的骨架提供了加载 BPF 代码和附加 BPF 程序的函数:
/* Load and verify BPF object file */
skel = dns_trace_bpf__open();
if (!skel) {
fprintf(stderr, "Failed to open and load BPF skeleton\n");
return 1;
}/* Load & verify BPF programs */
err = dns_trace_bpf__load(skel);
if (err) {
fprintf(stderr, "Failed to load and verify BPF skeleton\n");
goto cleanup;
}
/* Attach tracepoints */
err = dns_trace_bpf__attach(skel);
if (err) {
fprintf(stderr, "Failed to attach BPF skeleton\n");
goto cleanup;
}
从用户空间程序中设置和轮询 BPF 环形缓冲区所需的代码非常少:
struct ring_buffer* ringbuf =
ring_buffer__new(bpf_map__fd(skel->maps.dns_events), process_event, NULL, NULL);
while (!exiting) {
ring_buffer__poll(ringbuf, 100);
}
应用程序使用与 BPF 程序共享的struct dns_event
定义从 BPF 程序的环形缓冲区接收事件。
BPF 子系统和支持库拥有大量文档资源,但它们分散在不同的位置。以下是一些有用的链接,可帮助您访问最相关的文档:
需要注意的是,BPF 领域没有单一的权威指南,而且仍有很多未记录的知识。
原文来自 RedHat 的博客 BPF application development: Beyond the basics [55] 本文为机翻+人工矫正。
笔者的eCapture[56]项目,用户空间加载器使用ebpfmanager[57]类库,包装了 cilium/ebpf 类库,也是纯 Go 的加载器。在这里也推荐给大家。
美团信息安全部招研发专家,欢迎投递简历。其他方向也可私聊博主沟通投递。
Linux: https://developers.redhat.com/topics/linux/
[2]eBPF详尽介绍: https://lwn.net/Articles/740157/
[3]Clang: https://developers.redhat.com/products/gcc-clang-llvm-go-rust/overview
[4]BPF功能按Linux内核版本排序的列表: https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md
[5]选择您的eBPF应用程序栈: https://developers.redhat.com/articles/2023/04/28/ebpf-application-development-beyond-basics#choosing_your_ebpf_application_stack
[6]eBPF应用程序跨开发、可移植性、CO-RE和内核API稳定性: https://developers.redhat.com/articles/2023/04/28/ebpf-application-development-beyond-basics#ebpf_application_cross_development__portability__co_re__and_kernel_api_stability
[7]Cilium: https://cilium.io/
[8]Pixie: https://github.com/redhat-et/bpf-docs/blob/main/app-dev/px.dev
[9]最近的案例: https://youtu.be/u0PGas8D24w?t=178
[10]libxdp: https://github.com/xdp-project/xdp-tools/tree/master/lib/libxdp
[11]bpfd: https://github.com/redhat-et/bpfd
[12]BPF开发FAQ: https://docs.kernel.org/bpf/bpf_devel_QA.html#q-how-do-the-changes-make-their-way-into-linux
[13]kfuncs: https://docs.kernel.org/bpf/kfuncs.html
[14]BPF许可指南: https://docs.kernel.org/bpf/bpf_licensing.html
[15]ConnectX-6: https://developer.nvidia.com/blog/accelerating-with-xdp-over-mellanox-connectx-nics/
[16]C: https://developers.redhat.com/topics/c
[17]Golang: https://developers.redhat.com/topics/go
[18]Rust: https://developers.redhat.com/topics/rust
[19]内核ebpf系统调用: https://docs.kernel.org/userspace-api/ebpf/syscall.html
[20]BCC: https://github.com/iovisor/bcc
[21]libbpf: https://github.com/libbpf/libbpf
[22]CO-RE(Compile Once Run Everywhere): https://nakryiko.com/posts/bpf-core-reference-guide/
[23]tools/lib/bpf: https://elixir.bootlin.com/linux/v6.0.11/source/tools/lib/bpf
[24]libbpf的镜像: https://github.com/libbpf/libbpf
[25]于2022年中发布了1.0版本: https://nakryiko.com/posts/libbpf-v1/
[26]最近发布了1.2版本: https://github.com/libbpf/libbpf/releases/tag/v1.2.0
[27]libbpf-bootstrap: https://github.com/libbpf/libbpf-bootstrap
[28]一组有用的模板和示例程序: https://nakryiko.com/posts/libbpf-bootstrap/
[29]Cilium ebpf库: https://github.com/cilium/ebpf
[30]libbpfgo: https://github.com/aquasecurity/libbpfgo
[31]Aya: https://aya-rs.dev/book/
[32]Aya: https://aya-rs.dev/book/
[33]bpfd: https://github.com/redhat-et/bpfd
[34]CO-RE(编译一次,随处运行): https://nakryiko.com/posts/bpf-core-reference-guide/
[35]Red Hat企业Linux: https://developers.redhat.com/products/rhel/download#rhel3ways
[36]BCC: https://github.com/iovisor/bcc
[37]本文: https://nakryiko.com/posts/bpf-portability-and-co-re/
[38]CO-RE指南: https://nakryiko.com/posts/bpf-core-reference-guide/
[39]CO-RE指南: https://nakryiko.com/posts/bpf-core-reference-guide/
[40]BPF功能文档: https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md
[41]libbpf-bootstrap: https://github.com/libbpf/libbpf-bootstrap
[42]eBPF文档: https://ebpf.foundation/what-is-ebpf/
[43]BPF文档: https://docs.kernel.org/bpf/index.html
[44]BPF映射: https://docs.kernel.org/bpf/maps.html
[45]程序类型: https://docs.kernel.org/bpf/libbpf/program_types.html#program-types-and-elf
[46]BPF kfuncs: https://docs.kernel.org/bpf/kfuncs.html
[47]bpf-helpers: https://man7.org/linux/man-pages/man7/bpf-helpers.7.html
[48]libbpf: https://libbpf.readthedocs.io/en/latest/api.html
[49]libxdp: https://github.com/xdp-project/xdp-tools/tree/master/lib/libxdp
[50]Aya: https://aya-rs.dev/
[51]Cilium ebpf: https://pkg.go.dev/github.com/cilium/ebpf
[52]BPF CO-RE参考指南: https://nakryiko.com/posts/bpf-core-reference-guide/
[53]libbpf-bootstrap: https://github.com/libbpf/libbpf-bootstrap
[54]实用BPF示例: https://github.com/xdp-project/bpf-examples
[55]BPF application development: Beyond the basics : https://developers.redhat.com/articles/2023/10/19/ebpf-application-development-beyond-basics
[56]eCapture: https://github.com/gojue/ecapture
[57]ebpfmanager: https://github.com/gojue/ebpfmanager
[58]Java高级开发: https://job.meituan.com/web/position/detail?jobUnionId=2039344677
[59]C/C++技术专家(零信任方向): https://job.meituan.com/web/position/detail?jobUnionId=1601389658