eBPF (扩展的伯克利数据包过滤器)是一项强大的 Linux 内核技术,允许在内核空间运行用户定义的程序,而无需修改内核源代码或加载内核模块. 它源自 BPF(Berkeley Packet Filter),但大大扩展了其功能.
主要用途:
-
网络: 数据包过滤、流量控制、负载均衡
-
安全: 系统调用监控、入侵检测
-
性能分析: 跟踪内核和应用程序行为
-
观测性: 收集各种系统和应用指标
优势:
-
高性能: 直接在内核中执行,开销小
-
安全: eBPF 程序在执行前经过验证
-
灵活: 可以动态加载和更新程序
eBPF 技术正在迅速发展,被广泛应用于云原生、网络、安全等领域,相信众多的编程爱好者也会对 eBPF 技术产生浓厚的兴趣并且尝试使用它,但是在第一步搭建开发环境时可能会遇到一些困难,止步不前了. 的确是这样, eBPF 的开发环境搭建起来并不是那么容易,不同的 Linux 发行版、内核版本、工具链等都会对 eBPF 的开发环境产生影响,因此需要一些技巧和经验.
主要基于 eunomia-bpf 的 bpf-developer-tutorial 作为主要参考资料,实践和整理. 例子主要基于 eunomia-bpf 的工具进行编译和运行,这里只是将它的例子中的用户态的逻辑转换成 go 语言的程序.
-
ebpf 简介:https://ebpf.io/
-
ebpf 内核相关文档:https://prototype-kernel.readthedocs.io/en/latest/bpf/
-
ebpf 中文入门指南:https://www.ebpf.top/post/ebpf_intro
-
大量的 ebpf 参考资料:https://github.com/zoidbergwill/awesome-ebpf
-
ebpf 与 Linux 版本:https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md
-
bpftrace 教程,对于最简单的应用来说,bpftrace 可能是最方便的:https://eunomia.dev/zh/tutorials/bpftrace-tutorial/
-
BCC 开发各类小工具的例子:https://github.com/iovisor/bcc/blob/master/docs/tutorial_bcc_python_developer.md
-
libbpf 的一些例子:https://github.com/libbpf/libbpf-bootstrap
-
基于 libbpf 和 eunomia-bpf 的教程:https://github.com/eunomia-bpf/bpf-developer-tutorial
-
Linux 代码库中一些例子:https://github.com/torvalds/linux/tree/master/samples/bpf
-
cilium/ebpf - 这是一个广泛使用的 Go eBPF 库,提供了加载和运行 eBPF 程序的功能.
-
iovisor/gobpf - 另一个流行的 eBPF 库, Go bindings for BCC for creating eBPF programs.
-
libbpfgo:libbpfgo 是一个用于 Linux eBPF 项目的 Go 库. 它是围绕 libbpf 项目构建的一个薄层 Go 包装器.
-
dropbox/goebpf: Dropbox 开发的 Go eBPF 库,专注于简单性和可用性.
eBPF CO-RE (Compile Once – Run Everywhere) 是 eBPF 技术的一个重要进展,旨在解决 eBPF 程序的可移植性问题. CO-RE 允许开发者编写一次 eBPF 程序,然后在不同的 Linux 内核版本上运行,而无需重新编译.
CO-RE 的主要包括以下特性:
-
BTF (BPF Type Format): 提供了内核数据结构的详细类型信息.
-
重定位: 允许在运行时动态调整 eBPF 程序中的内存访问.
-
内核符号和类型信息: 通过 BTF 提供,使 eBPF 程序能够适应不同的内核版本.
传统 eBPF 的限制
-
可移植性差: 需要为每个目标内核版本重新编译.
-
依赖内核头文件: 编译时需要完整的内核头文件.
-
版本特定: eBPF 程序往往只能在特定的内核版本上运行.
CO-RE eBPF 的优势
-
提高可移植性: 一次编译,到处运行.
-
减少依赖: 不再需要完整的内核头文件.
-
适应性强: 能够适应不同的内核版本和配置.
-
简化部署: 无需为每个目标系统维护多个版本的 eBPF 程序.
-
使用最新的 libbpf: 确保使用最新版本的 libbpf,以获得最新的 CO-RE 支持.
-
利用 BTF 信息: 在编译时使用-g 选项保留调试信息.
-
使用 BPF skeleton: 利用 bpftool gen skeleton 生成 skeleton 头文件,简化 eBPF 程序的加载和管理.
-
避免硬编码偏移: 使用 BPF helpers 和 CO-RE 重定位来访问内核结构,而不是硬编码偏移.
-
测试多个内核版本: 在不同版本的内核上测试你的程序,确保 CO-RE 正常工作.
注意事项
-
CO-RE 并不能解决所有的兼容性问题. 某些内核 API 的变化可能仍然需要代码适配.
-
确保目标系统的内核支持并启用了 BTF.
-
某些非常底层或特定于版本的功能可能仍然需要传统的 eBPF 开发方
开发、构建和分发 eBPF 一直以来都是一个高门槛的工作,使用 BCC、bpftrace 等工具开发效率高、可移植性好,但是分发部署时需要安装 LLVM、Clang 等编译环境,每次运行的时候执行本地或远程编译过程,资源消耗较大,而且不容易安装相关的依赖;使用原生的 CO-RE libbpf 时又需要编写不少用户态加载代码来帮助 eBPF 程序正确加载和从内核中获取上报的信息,同时对于 eBPF 程序的分发、管理也没有很好地解决方案.
eunomia-bpf 是一个开源的 eBPF 动态加载运行时和开发工具链,是为了简化 eBPF 程序的开发、构建、分发、运行而设计的,基于 libbpf 的 CO-RE 轻量级开发框架.
使用 eunomia-bpf ,可以:
-
在编写 eBPF 程序或工具时只编写内核态代码,自动获取内核态导出信息,并作为模块动态加载;
-
使用 WASM 进行用户态交互程序的开发,在 WASM 虚拟机内部控制整个 eBPF 程序的加载和执行,以及处理相关数据;
-
eunomia-bpf 可以将预编译的 eBPF 程序打包为通用的 JSON 或 WASM 模块,跨架构和内核版本进行分发,无需重新编译即可动态加载运行.
eunomia-bpf 由一个编译工具链和一个运行时库组成, 对比传统的 BCC、原生 libbpf 等框架,大幅简化了 eBPF 程序的开发流程,在大多数时候只需编写内核态代码,即可轻松构建、打包、发布完整的 eBPF 应用,同时内核态 eBPF 代码保证和主流的 libbpf,libbpfgo,libbpf-rs 等开发框架的 100% 兼容性. 需要编写用户态代码的时候,也可以借助 Webassembly 实现通过多种语言进行用户态开发. 和 bpftrace 等脚本工具相比, eunomia-bpf 保留了类似的便捷性, 同时不仅局限于 trace 方面, 可以用于更多的场景, 如网络、安全等等.
前面的一些例子都是抄eunomia-bpf的bpf-developer-tutorial, 有时间会尝试补充一些相关的学习资料.
也许会使用cilium/ebpf库来实现其中的一些例子.
Cilium/eBPF 是一个用 Go 语言编写的库,提供了一套用于处理 eBPF 程序的 API. 它允许开发者以一种安全、高效的方式在 Go 程序中加载、管理和与 eBPF 程序交互.
主要特性包括:
-
eBPF 程序加载:支持加载预编译的 eBPF 对象文件.
-
映射管理:提供了创建、访问和管理 eBPF 映射的功能.
-
程序附加:支持将 eBPF 程序附加到各种内核钩子点.
-
BTF (BPF Type Format) 支持:增强了跨内核版本的兼容性.
-
CO-RE (Compile Once – Run Everywhere) 支持:允许编写一次代码,在不同内核版本上运行.
-
错误处理:提供详细的错误信息,便于调试.
-
安全性:实现了多种安全检查,以防止不安全的操作.
主要应用场景包括:
-
网络监控和过滤
-
系统调用跟踪
-
性能分析
-
安全策略实施
-
自定义网络协议实现
注意事项
-
使用 Cilium/eBPF 需要较新的 Linux 内核版本(推荐 5.4+).
-
某些功能可能需要特定的内核配置选项.
-
开发 eBPF 程序通常需要 root 权限.
Cilium/eBPF 为 Go 开发者提供了一个强大的工具,用于利用 eBPF 技术进行系统级编程和网络编程. 它简化了 eBPF 程序的开发和管理过程,同时保持了高性能和灵活性.