ebpf(extended berkeley packet filter)是一种高性能的内核虚拟机,可以运行在内核空间中,以收集系统和网络信息。随着计算机技术的不断发展,ebpf 的功能日益强大,并且已经成为各种效率高效的在线诊断和跟踪系统,以及构建安全的网络、服务网格的重要组成部分。
webassembly(wasm)最初是以浏览器安全沙盒为目的开发的,发展到目前为止,webassembly 已经成为一个用于云原生软件组件的高性能、跨平台和多语言软件沙箱环境,wasm 轻量级容器也非常适合作为下一代无服务器平台运行时,或在边缘计算等资源受限的场景高效执行。
现在,借助 wasm-bpf 编译工具链和运行时,我们可以使用 wasm 将 ebpf 程序编写为跨平台的模块,使用 c/c++ 和 rust 编写程序。通过在 webassembly 中使用 ebpf 程序,我们不仅让 wasm 应用获得 ebpf 的高性能、对系统接口的访问能力,还可以让 ebpf 程序享受到 wasm 的沙箱、灵活性、跨平台性、和动态加载的能力,并且使用 wasm 的 oci 镜像来方便、快捷地分发和管理 ebpf 程序。例如,可以类似 docker 一样,从云端一行命令获取 wasm 轻量级容器镜像,并运行任意 ebpf 程序。通过结合这两种技术,我们将会给 ebpf 和 wasm 生态来一个全新的开发体验!
使用 wasm-bpf 工具链在 wasm 中编写、动态加载、分发运行 ebpf 程序 在前两篇短文中,我们已经介绍了 wasm-bpf 的设计思路,以及如何使用 c/c++ 在 wasm 中编写 ebpf 程序:
wasm-bpf: 架起 webassembly 和 ebpf 内核可编程的桥梁 在 webassembly 中使用 c/c++ 和 libbpf 编写 ebpf 程序 基于 wasm,我们可以使用多种语言构建 ebpf 应用,并以统一、轻量级的方式管理和发布。以我们构建的示例应用 bootstrap.wasm 为例,使用 c/c++ 构建的镜像大小最小仅为 ~90k,很容易通过网络分发,并可以在不到 100ms 的时间内在另一台机器上动态部署、加载和运行,并且保留轻量级容器的隔离特性。运行时不需要内核特定版本头文件、llvm、clang 等依赖,也不需要做任何消耗资源的重量级的编译工作。对于 rust 而言,编译产物会稍大一点,大约在 2m 左右。
本文将以 rust 语言为例,讨论:
使用 rust 编写 ebpf 程序并编译为 wasm 模块 使用 oci 镜像发布、部署、管理 ebpf 程序,获得类似 docker 的体验 我们在仓库中提供了几个示例程序,分别对应于可观测、网络、安全等多种场景。
编写 ebpf 程序并编译为 wasm 的大致流程 一般说来,在非 wasm 沙箱的用户态空间,使用 libbpf-bootstrap 脚手架,可以快速、轻松地使用 c/c++构建 bpf 应用程序。编译、构建和运行 ebpf 程序(无论是采用什么语言),通常包含以下几个步骤:
编写内核态 ebpf 程序的代码,一般使用 c/c++ 或 rust 语言 使用 clang 编译器或者相关工具链编译 ebpf 程序(要实现跨内核版本移植的话,需要包含 btf 信息)。 在用户态的开发程序中,编写对应的加载、控制、挂载、数据处理逻辑; 在实际运行的阶段,从用户态将 ebpf 程序加载进入内核,并实际执行。 使用 rust 编写 ebpf 程序并编译为 wasm rust 可能是 webassembly 生态系统中支持最好的语言。rust 不仅支持几个 webassembly 编译目标,而且 wasmtime、spin、wagi 和其他许多 webassembly 工具都是用 rust 编写的。因此,我们也提供了 rust 的开发示例:
wasm 和 wasi 的 rust 生态系统非常棒 许多 wasm 工具都是用 rust 编写的,这意味着有大量的代码可以复用。 spin 通常在对其他语言的支持之前就有rust的功能支持 wasmtime 是用 rust编写的,通常在其他运行时之前就有最先进的功能。 可以在 webassembly 中使用许多现成的 rust 库。 由于 cargo 的灵活构建系统,一些 crates 甚至有特殊的功能标志来启用wasm的功能(例如chrono)。 由于 rust 的内存管理技术,与同类语言相比,rust 的二进制大小很小。 我们同样提供了一个 rust 的 ebpf sdk,可以使用 rust 编写 ebpf 的用户态程序并编译为 wasm。借助 aya-rs 提供的相关工具链支持,内核态的 ebpf 程序也可以用 rust 进行编写,不过在这里,我们还是复用之前使用 c 语言编写的内核态程序。
首先,我们需要使用 rust 提供的 wasi 工具链,创建一个新的项目:
rustup target add wasm32-wasicargo new rust-helloworld 之后,可以使用 makefile 运行 make 完成整个编译流程,并生成 bootstrap.bpf.o ebpf 字节码文件。
使用 wit-bindgen 生成类型信息,用于内核态和 wasm 模块之间通信 wit-bindgen 项目是一套着眼于 webassembly,并使用组件模型的语言的绑定生成器。绑定是用 *.wit 文件描述的,文件中描述了 wasm 模块导入、导出的函数和接口。我们可以 wit-bindgen 它来生成多种语言的类型定义,以便在内核态的 ebpf 和用户态的 wasm 模块之间传递数据。
我们首先需要在 cargo.toml 配置文件中加入 wasm-bpf-binding 和 wit-bindgen-guest-rust 依赖:
wasm-bpf-binding = { path = wasm-bpf-binding } 这个包提供了 wasm-bpf 由运行时提供给 wasm 模块,用于加载和控制 ebpf 程序的函数的绑定。
wasm-bpf-binding 在 wasm-bpf 仓库中有提供。 [dependencies]wit-bindgen-guest-rust = { git = https://github.com/bytecodealliance/wit-bindgen, version = 0.3.0 }[patch.crates-io]wit-component = {git = https://github.com/bytecodealliance/wasm-tools, version = 0.5.0, rev = 9640d187a73a516c42b532cf2a10ba5403df5946}wit-parser = {git = https://github.com/bytecodealliance/wasm-tools, version = 0.5.0, rev = 9640d187a73a516c42b532cf2a10ba5403df5946} 这个包支持用 wit 文件为 rust 客户程序生成绑定。使用这个包的情况下,我们不需要再手动运行 wit-bindgen。
接下来,我们使用 btf2wit 工具,从 btf 信息生成 wit 文件。可以使用 cargo install btf2wit 安装我们提供的 btf2wit 工具,并编译生成 wit 信息:
cd btfclang -target bpf -g event-def.c -c -o event.def.obtf2wit event.def.o -o event-def.witcp *.wit ../wit/ 其中 event-def.c 是包含了我们需要的结构体信息的的 c 程序文件。只有在导出符号中用到的结构体才会被记录在 btf 中。 对于 c 结构体生成的 wit 信息,大致如下:
default world host { record event { pid: s32, ppid: s32, exit-code: u32, --pad0: list, duration-ns: u64, comm: list, filename: list, exit-event: s8, }} wit-bindgen-guest-rust 会为 wit 文件夹中的所有类型信息,自动生成 rust 的类型,例如:
#[repr(c, packed)]#[derive(debug, copy, clone)]struct event { pid: i32, ppid: i32, exit_code: u32, __pad0: [u8; 4], duration_ns: u64, comm: [u8; 16], filename: [u8; 127], exit_event: u8,} 编写用户态加载和处理代码 为了在 wasi 上运行,需要为 main.rs 添加 #![no_main] 属性,并且 main 函数需要采用类似如下的形态:
#[export_name = __main_argc_argv]fn main(_env_json: u32, _str_len: i32) -> i32 { return 0;} 用户态加载和挂载代码,和 c/c++ 中类似:
let obj_ptr = binding::wasm_load_bpf_object(bpf_object.as_ptr() as u32, bpf_object.len() as i32); if obj_ptr == 0 { println!(failed to load bpf object); return 1; } let attach_result = binding::wasm_attach_bpf_program( obj_ptr, handle_exec.as_ptr() as u32, .as_ptr() as u32, ); ... polling ring buffer:
let map_fd = binding::wasm_bpf_map_fd_by_name(obj_ptr, rb.as_ptr() as u32); if map_fd < 0 { println!(failed to get map fd: {}, map_fd); return 1; } // binding::wasm let buffer = [0u8; 256]; loop { // polling the buffer binding::wasm_bpf_buffer_poll( obj_ptr, map_fd, handle_event as i32, 0, buffer.as_ptr() as u32, buffer.len() as i32, 100, ); } 使用 handler 接收返回值:
extern c fn handle_event(_ctx: u32, data: u32, _data_sz: u32) { let event_slice = unsafe { slice::from_raw_parts(data as *const event, 1) }; let event = &event_slice[0]; let pid = event.pid; let ppid = event.ppid; let exit_code = event.exit_code; if event.exit_event == 1 { print!( {:<8} {:<5} {:<16} {:<7} {:<7} [{}], time, exit, unsafe { cstr::from_ptr(event.comm.as_ptr() as *const i8) } .to_str() .unwrap(), pid, ppid, exit_code ); ...} 接下来即可使用 cargo 编译运行:
$ cargo build --target wasi32-wasm$ sudo wasm-bpf ./target/wasm32-wasi/debug/rust-helloworld.wasmtime exec sh 180245 33666 /bin/shtime exec which 180246 180245 /usr/bin/whichtime exit which 180246 180245 [0] (1ms)time exit sh 180245 33666 [0] (3ms)time exec sh 180247 33666 /bin/shtime exec ps 180248 180247 /usr/bin/pstime exit ps 180248 180247 [0] (23ms)time exit sh 180247 33666 [0] (25ms)time exec sh 180249 33666 /bin/shtime exec cpuusage.sh 180250 180249 /root/.vscode-server-insiders/bin/a7d49b0f35f50e460835a55d20a00a735d1665a3/out/vs/base/node/cpuusage.sh 使用 oci 镜像发布和管理 ebpf 程序 开放容器协议 (oci) 是一个轻量级,开放的治理结构,为容器技术定义了规范和标准。在 linux 基金会的支持下成立,由各大软件企业构成,致力于围绕容器格式和运行时创建开放的行业标准。其中包括了使用 container registries 进行工作的 api,正式名称为 oci 分发规范 (又名“distribution-spec”)。
docker 也宣布推出与 webassembly 集成 (docker+wasm) 的首个技术预览版,并表示公司已加入字节码联盟 (bytecode alliance),成为投票成员。docker+wasm 让开发者能够更容易地快速构建面向 wasm 运行时的应用程序。
借助于 wasm 的相关生态,可以非常方便地发布、下载和管理 ebpf 程序,例如,使用 wasm-to-oci 工具,可以将 wasm 程序打包为 oci 镜像,获取类似 docker 的体验:
wasm-to-oci push testdata/hello.wasm .azurecr.io/wasm-to-oci:v1wasm-to-oci pull .azurecr.io/wasm-to-oci:v1 --out test.wasm 我们也将其集成到了 eunomia-bpf 的 ecli 工具中,可以一行命令从云端的 github packages 中下载并运行 ebpf 程序,或通过 github packages 发布:
# push to github packagesecli push https://ghcr.io/eunomia-bpf/sigsnoop:latest# pull from github packagesecli pull https://ghcr.io/eunomia-bpf/sigsnoop:latest# run ebpf programecli run https://ghcr.io/eunomia-bpf/sigsnoop:latest 我们已经在 lmp 项目的 ebpf hub 中,有一些创建符合 oci 标准的 wasm-ebpf 应用程序,并利用 oras 简化扩展 ebpf 应用开发,分发、加载、运行能力的尝试[11],以及基于 wasm 同时使用多种不同语言开发 ebpf 的用户态数据处理插件的实践。基于最新的 wasm-bpf 框架,有更多的探索性工作可以继续展开,我们希望尝试构建一个完整的针对 ebpf 和 wasm 程序的包管理系统,以及更多的可以探索的应用场景。
总结 本文以 rust 语言为例,讨论了使用 rust 编写 ebpf 程序并编译为 wasm 模块以及使用 oci 镜像发布、部署、管理 ebpf 程序,获得类似 docker 的体验。更完整的代码,请参考我们的 github 仓库:https://github.com/eunomia-bpf/wasm-bpf.
接下来,我们会继续完善在 wasm 中使用多种语言开发和运行 ebpf 程序的体验,提供更完善的示例和用户态开发库/工具链,以及更具体的应用场景。
空间光调制器的作用_空间光调制器使用指导
离心泵的6种工艺保护线有哪些
AI赋能中国医疗 落地的同时伴随着新的商机
空气正离子检测仪的优势特点及技术参数
世界经济论坛宣布:网络七名新成员 成为中国五家 全球十六家份子
在WebAssembly中使用Rust编写eBPF程序并发布OCI镜像
数字化转型的实践路径
小米手环惊传漏洞!骇客可透过蓝牙直接控制手环
转子流量计和孔板流量计的区别
电源热设计基础:对热阻的认识
如何制作CNC绘图仪
35 kV数字化变电站设计方案探讨
基于PWM控制器的新型CMOS误差放大器设计流程概述
荣耀上市迷云,正式回应,疑点重重
比特大陆可能任命新的CEO取代联合创始人
物联网实现智能管理和应用,将取代人工抄水表
早上八点停机,下午两点开机,挑战全网烘缸锥度轴维修速度
电磁阀符号及含义详解
PROFINET转借款人CANOPEN总线网关连接汇川变频器解决方案
光通讯大会上,赛灵思展示了一系列专门面向5G的解决方案