Rust Aya 编写 eBPF 程序

打印 上一主题 下一主题

主题 1021|帖子 1021|积分 3063


本文地址:https://www.ebpf.top/post/ebpf_rust_aya
1. 前言

Linux 内核 6.1 版本中有一个非常引人注意的变化:引入了对 Rust 编程语言的支持。Rust 是一种系统编程语言,Rust 通过提供非常强大的编译时保证和对内存生命周期的明确控制。在内核开发中引入 Rust 语言,将会为内核开发的早期带来更多的安全保障。eBPF 是在内核中基于事件运行用户自定义程序的技术,其验证器机制可以保障运行在内核中 eBPF 程序的安全性。
Rust 与 eBPF 有着一个共同的目标:保证内核安全,只是两者侧重的维度有所不同。
尽管使用 Rust 编写 eBPF 程序多数情况下都需要通过不安全的方式在内核进行内存读写,但是基于 Rust 和 Aya ,的确能够给我们带来一个快速和高效的开发体验,这包括自动生成整个程序框架(eBPF 程序及对应的用户空间代码)、参数检查确认、错误处理、统一的相关构建和管理方式等等 。
Aya 是一个以可操作性和开发者体验为重点的 eBPF 库,完全是在 Rust 基础上建立的,只使用 libc 包来执行系统调用。Aya 官方仓库地址为 https://github.com/aya-rs/aya/,当前版本为  v0.1.11,项目还处于偏早期。基于 Aya 库开发 eBPF 程序可以给我们带来以下的便利:

  • 基于 Rust 的 Cargo 工具来管理、构建和测试项目;
  • 支持 CO-RE 直接生成与 Rust 与内核文件的绑定;
  • 用户工具代码(Rust)与运行在的内核中的 eBPF 代码轻松共享代码;
  • 对于 LLVM、libbpf、bcc 等完全没有任何依赖;
本文仅是基于 Aya 编写 eBPF 程序及用户空间程序的生成和测试的过程记录,不涉及到对于生成 Rust 代码的详细解读。
2. Rust 开发环境搭建

2.1 创建 VM 虚拟机

为了使用 Rust 进行 eBPF 程序编写,那么我们首先需要在本地搭建一个 Rust 开发环境。这里我仍然采用 multipass 工具快速搭建一个 Ubuntu 22.04 LTS 的环境。
  1. $ multipass launch --name rust-aya -d 20G
复制代码
默认磁盘为 5G,比较容易造成磁盘空间满,因此这里将磁盘空间大小设置为 20G,你可以根据自己的情况调整。
对于已经创建的 mulipass 实例可以在创建后进行调整,则需要 multipass 版本大于 1.10,而且需要调整的实例处于停止状态,详细可参见调整实例配置,例如 multipass set local.rust-aya.cpus=4 或 multipass set local.rust-aya.memory=8G 分别用于调整实例的 CPU 和 MEM 大小。
2.2 安装 Rust 开发环境

通常情况下,Rust 开发环境推荐通过 rustup 工具管理,我们可以通过以下命令快速安装该工具:
  1. $ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
复制代码
一般情况下我们选择默认选项安装。整个安装过程会下载一个脚本完成 rustup 工具安装,同时也会安装最新的 Rust 稳定版本。如果安装顺利,我们可以在最后看到如下的信息:
  1. ...
  2.         stable-x86_64-unknown-linux-gnu installed - rustc 1.65.0 (897e37553 2022-11-02)
  3. Rust is installed now. Great!
  4. To get started you may need to restart your current shell.
  5. This would reload your PATH environment variable to include
  6. Cargo's bin directory ($HOME/.cargo/bin).
  7. To configure your current shell, run:
  8. source "$HOME/.cargo/env"
复制代码
在 rustup 工具安装完成后,我们可以使用其安装 Rust 稳定版(实际上默认已经安装)和 nightly ,其中 nightly 为开发者体验新功能的发布通道,Rust 2021 年开始支持编译 eBPF,当前使用 Aya 需要基于 Rust Nightly 版本。
Rust 有 3 个发布通道:

  • Nightly
  • Beta
  • Stable(稳定版)
大部分 Rust 开发者主要采用稳定版通道,不过希望实验新功能的开发者可能会使用 nightly 或 beta 版。详情参见附录 G:Rust 是如何开发的与 “Nightly Rust”
  1. $ source "$HOME/.cargo/env"
  2. $ rustup install stable  # rustup 命令已经默认安装
  3. info: syncing channel updates for 'stable-x86_64-unknown-linux-gnu'
  4.   stable-x86_64-unknown-linux-gnu unchanged - rustc 1.65.0 (897e37553 2022-11-02)
  5. info: checking for self-updates
  6. $ rustup toolchain install nightly --component rust-src
  7. ...
  8. info: installing component 'rustfmt'
  9.   nightly-x86_64-unknown-linux-gnu installed - rustc 1.67.0-nightly (09508489e 2022-11-04)
  10. info: checking for self-updates
  11. $ rustup toolchain list
  12. stable-x86_64-unknown-linux-gnu (default)
  13. nightly-x86_64-unknown-linux-gnu
复制代码
安装 nightly 以后我们可以使用 rustup toolchain list 查看本地开发环境的开发工具链。
2.3 安装 bpf-linker 依赖 和 bpftool 工具

为了使用 Aya,我们还需要安装依赖包  bpf-linker,但其依赖与  LLVM/Clang 等工具,因此我们也需要提前安装:
  1. $ sudo apt-get update
  2. $ sudo apt-get install llvm clang -y
  3. $ cargo install bpf-linker
复制代码
最后,为了生成内核数据结构的绑定,我们还必须安装 bpftool,可以从发行版中安装或从源代码中构建,这里我选用发行版安装方式(基于 Ubuntu 22.04),源码安装可参考 bpftool 仓库说明文档
  1. $ sudo apt install linux-tools-common linux-tools-5.15.0-52-generic linux-cloud-tools-5.15.0-52-generic -y
复制代码
支持我们完成了基于 Aya 开发的整个环境及依赖的安装。
3. Aya 向导创建 eBPF 程序

3.1 使用向导创建项目

Aya 提供了一套模版向导用于创建 eBPF 对应的程序类型,向导创建依赖于 cargo-generate,因此我们需要在运行程序向导前提前安装:
  1. $ cargo install cargo-generate
复制代码
我在安装 cargo-generate 过程中遇到了如下的错误,主要是由于依赖 openssl 库问题导致,如果你也遇到类似问题可参考 cargo-generate 安装指南Rust OpenSSL 文档,如果一切顺利,则可忽略此处的提示。
  1. ...
  2. warning: build failed, waiting for other jobs to finish...
  3. error: failed to compile `cargo-generate v0.16.0`, intermediate artifacts can be found at `/tmp/cargo-install8NrREg
  4. ...
  5. $ sudo apt install openssl pkg-config libssl-dev gcc m4 ca-certificates make perl -y
  6. # 重新安装即可
复制代码
在完成依赖后,我们就可以使用向导来创建 eBPF 项目,这里以 XDP 类型程序为例:
  1. $ cargo generate https://github.com/aya-rs/aya-template
复制代码

这里我们输入项目名称 myapp,eBPF 程序类型选择 xdp,完成相关设定后,向导会自动帮我们创建一个名为 myapp 的 Rust 项目,项目包括了一个最简单的 XDP 类型的 eBPF 程序及相对应的用户空间程序。 myapp 目录的整体夹头如下所示:
  1. ├── Cargo.lock
  2. ├── Cargo.toml
  3. ├── README.md
  4. ├── myapp  # 用户空间程序
  5. │   ├── Cargo.toml
  6. │   └── src
  7. │       └── main.rs
  8. ├── myapp-common  # eBPF 程序与用户空间程序复用的代码库
  9. │   ├── Cargo.toml
  10. │   └── src
  11. │       └── lib.rs
  12. ├── myapp-ebpf  # eBPF 程序
  13. │   ├── Cargo.lock
  14. │   ├── Cargo.toml
  15. │   ├── rust-toolchain.toml
  16. │   └── src
  17. │       └── main.rs
  18. └── xtask  # build 相关的代码
  19.     ├── Cargo.toml
  20.     └── src
  21.         ├── build_ebpf.rs
  22.         ├── main.rs
  23.         └── run.rs
  24. 8 directories, 15 files
复制代码
生成的 eBPF 程序位于 myapp-ebpf/src 目录下,文件名为 main.rs,完整内容如下所示:
  1. $ cat myapp-ebpf/src/main.rs
  2. #![no_std]
  3. #![no_main]
  4. use aya_bpf::{
  5.     bindings::xdp_action,
  6.     macros::xdp,
  7.     programs::XdpContext,
  8. };
  9. use aya_log_ebpf::info;
  10. #[xdp(name="myapp")]
  11. pub fn myapp(ctx: XdpContext) -> u32 {
  12.     match try_myapp(ctx) {
  13.         Ok(ret) => ret,
  14.         Err(_) => xdp_action::XDP_ABORTED,
  15.     }
  16. }
  17. fn try_myapp(ctx: XdpContext) -> Result<u32, u32> {
  18.     info!(&ctx, "received a packet"); // 每接受到一个数据包则打印一个日志
  19.     Ok(xdp_action::XDP_PASS)
  20. }
  21. #[panic_handler]
  22. fn panic(_info: &core::panic::PanicInfo) -> ! {
  23.     unsafe { core::hint::unreachable_unchecked() }
  24. }
复制代码
3.2 编译 eBPF 程序

首先,我们使用 cargo 工具编译 eBPF 对应的程序:
  1. $ cd myapp
  2. $ cargo xtask build-ebpf
  3. ...
  4.    Compiling myapp-ebpf v0.1.0 (/home/ubuntu/myapp/myapp-ebpf)
  5.      Running `rustc --crate-name myapp --edition=2021 src/main.rs --error-format=json \
  6.      --json=diagnostic-rendered-ansi,artifacts,future-incompat --crate-type bin \
  7.      --emit=dep-info,link -C opt-level=3 -C panic=abort -C lto -C codegen-units=1
  8.      -C metadata=dd6140d48c387b43 -C extra-filename=-dd6140d48c387b43 \
  9.      --out-dir \
  10.                 ...
  11.      -Z unstable-options \
  12.     Finished dev [optimized] target(s) in 11.76s
复制代码
编译完成后,对应的程序保存在 target 目录下:
  1. ~/myapp$ ls -hl target/bpfel-unknown-none/debug/
  2. ...
  3. -rw-rw-r-- 2 ubuntu ubuntu 3.5K Nov  6 22:24 myapp
  4. ~/myapp$ file target/bpfel-unknown-none/debug/myapp
  5. target/bpfel-unknown-none/debug/myapp: ELF 64-bit LSB relocatable, eBPF, version 1 (SYSV), not stripped
  6. /myapp$ llvm-objdump -S target/bpfel-unknown-none/debug/myapp
  7. target/bpfel-unknown-none/debug/myapp:        file format elf64-bpf
  8. Disassembly of section xdp/myapp:
  9. 0000000000000000 <myapp>:
  10. ...
  11.      242:        bf 61 00 00 00 00 00 00        r1 = r6
  12.      243:        18 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00        r2 = 0 ll
  13.      245:        18 03 00 00 ff ff ff ff 00 00 00 00 00 00 00 00        r3 = 4294967295 ll
  14.      247:        bf 04 00 00 00 00 00 00        r4 = r0
  15.      248:        b7 05 00 00 aa 00 00 00        r5 = 170
  16.      249:        85 00 00 00 19 00 00 00        call 25
复制代码
至此,已经完成了 eBPF 程序的编译工作,接着我们需要继续编译用户空间代码。
3.3 运行用户空间程序

我们可以直接使用 cargo 命令来运行用户空间程序:
  1. $ RUST_LOG=info cargo xtask run
  2. ...
  3.     Finished dev [unoptimized + debuginfo] target(s) in 8.38s
  4. Error: failed to attach the XDP program with default flags - try changing XdpFlags::default() to XdpFlags::SKB_MODE
  5. Caused by:
  6.     unknown network interface eth0
复制代码
RUST_LOG=info 为设置日志级别的环境变量,默认为 warn,但向导生成的代码打印的日志级别默认为 info,因此需要运行时制定,否则可能会出现程序运行查看不到日志的情况。
cargo xtask run 命令会直接编译用户空间代码并运行,但是运行过程中我们发现出现错误 unknown network interface eth0,这是因为默认生成的程序指定将 XDP 程序加载到 eth0 网卡,而我们的 VM 默认网卡不为 eth0 导致,这里我们明确制定网卡使用 lo 测试,再次运行结果如下:
  1. $ RUST_LOG=info cargo xtask run -- --iface lo
  2. ...
  3.     Finished dev [optimized] target(s) in 0.19s
  4.     Finished dev [unoptimized + debuginfo] target(s) in 0.12s
  5. [2022-11-05T16:25:27Z INFO  myapp] Waiting for Ctrl-C...
复制代码
这次可以发现用户空间程序已经正常运行,并且将对应的 eBPF 程序加载至内核中。
[code]$ sudo bpftool prog list42: xdp  name myapp  tag 2929f83b3be0f64b  gpl        loaded_at 2022-11-06T22:42:54+0800  uid 0        xlated 2016B  jited 1151B  memlock 4096B  map_ids 14,13,15        $ ip link show1: lo:  mtu 65536 xdpgeneric qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00    prog/xdp id 42 #

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

慢吞云雾缓吐愁

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表