eunomia-bpf 0.3.0 发布:只需编写内核态代码,轻松构建、打包、发布完整的 eBPF 应用
eunomia-bpf 简介
eBPF 源于 BPF,本质上是处于内核中的一个高效与灵活的虚拟机组件,以一种安全的方式在许多内核 hook 点执行字节码,开发者可基于 eBPF 开发性能分析工具、软件定义网络、安全等诸多场景。但是,目前对于开发和使用 eBPF 应用而言还可能存在一些不够方便的地方:
- 搭建和开发 eBPF 程序是一个门槛比较高、比较复杂的工作,必须同时关注内核态和用户态两个方面的交互和信息处理,有时还要配置环境和编写对应的构建脚本;
- 目前不同用户态语言如 C、Go、Rust 等编写的工具难以兼容、难以统一管理,多种开发生态难以整合:如何跨架构、跨语言和内核版本,使用标准化的方式方便又快捷的打包、分发、发布二进制 eBPF 程序,同时还需要能很方便地动态调整 eBPF 程序的挂载点、参数等等?
- 如何更方便地使用 eBPF 的工具:有没有可能从云端一行命令拉下来就使用,类似 docker 那样?或者把 eBPF 程序作为服务运行,通过 HTTP 请求和 URL 即可热更新、动态插拔运行任意一个 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(Wasm) 实现通过多种语言进行用户态开发。和 bpftrace 等脚本工具相比, eunomia-bpf 保留了类似的便捷性, 同时不仅局限于 trace 方面, 可以用于更多的场景, 如网络、安全等等。
- eunomia-bpf 项目 Github 地址: https://github.com/eunomia-bpf/eunomia-bpf
- gitee 镜像: https://gitee.com/anolis/eunomia
我们发布了最新的 0.3 版本, 对于整体的开发和使用流程进行了优化,同时也支持了更多的 eBPF 程序和 maps 类型。
运行时优化:增强功能性, 增加多种程序类型
-
只需编写内核态代码, 即可获得对应的输出信息, 以可读、规整的方式打印到标准输出. 以一个简单的 eBPF 程序, 跟踪所有 open 类型系统调用的 opensnoop 为例:
头文件 opensnoop.h
#ifndef __OPENSNOOP_H #define __OPENSNOOP_H #define TASK_COMM_LEN 16 #define NAME_MAX 255 #define INVALID_UID ((uid_t)-1) // used for export event struct event { /* user terminology for pid: */ unsigned long long ts; int pid; int uid; int ret; int flags; char comm[TASK_COMM_LEN]; char fname[NAME_MAX]; }; #endif /* __OPENSNOOP_H */
内核态代码 opensnoop.bpf.c
#include <vmlinux.h> #include <bpf/bpf_helpers.h> #include "opensnoop.h" struct args_t { const char *fname; int flags; }; /// Process ID to trace const volatile int pid_target = 0; /// Thread ID to trace const volatile int tgid_target = 0; /// @description User ID to trace const volatile int uid_target = 0; /// @cmdarg {"default": false, "short": "f", "long": "failed"} const volatile bool targ_failed = false; struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 10240); __type(key, u32); __type(value, struct args_t); } start SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); __uint(key_size, sizeof(u32)); __uint(value_size, sizeof(u32)); } events SEC(".maps"); static __always_inline bool valid_uid(uid_t uid) { return uid != INVALID_UID; } static __always_inline bool trace_allowed(u32 tgid, u32 pid) { u32 uid; /* filters */ if (tgid_target && tgid_target != tgid) return false; if (pid_target && pid_target != pid) return false; if (valid_uid(uid_target)) { uid = (u32)bpf_get_current_uid_gid(); if (uid_target != uid) { return false; } } return true; } SEC("tracepoint/syscalls/sys_enter_open") int tracepoint__syscalls__sys_enter_open(struct trace_event_raw_sys_enter* ctx) { u64 id = bpf_get_current_pid_tgid(); /* use kernel terminology here for tgid/pid: */ u32 tgid = id >> 32; u32 pid = id; /* s