本文地址: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 程序加载至内核中。

  1. $ sudo bpftool prog list
  2. 42: xdp name myapp tag 2929f83b3be0f64b gpl
  3. loaded_at 2022-11-06T22:42:54+0800 uid 0
  4. xlated 2016B jited 1151B memlock 4096B map_ids 14,13,15
  5. $ ip link show
  6. 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 xdpgeneric qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
  7. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  8. prog/xdp id 42 # <=== 加载的 eBPF 程序 id 42

我们启动网卡在 lo 网卡的 ping 包验证:

我们可以看到当我们在另外一个窗口在本地端口运行 ping -c 1 127.0.0.1 命令的同时,在运行用户空间 myapp 的程序日志中打印了对应的日志 received a packet

至此,我们就完成了整个基于 Aya 最简单 XDP 程序的验证,如果你打算进阶一步打印报文日志或者对特定包进行对齐,则可以参考 Aya Book 中对应的章节。

4. 总结

通过对于 Aya 整个过程中的使用,我们可以发现使用 Aya 开发 eBPF 程序的确给我们带来了诸多的便利,通过向导搭建了整个项目的基本框架,并且实现了编译、加载等相关的管理工作,特别是对于新手入门更加友好。默认生成的用户空间代码与 eBPF 代码实现了一定程度的代码复用,特别是日志相关的打印更加便捷。同时,该项目当前的文档还不是特别完整,诸如 Probe/Tracepoint/XDP 等程序类型的文档还在完善中,如果你有兴趣也欢迎投入到相关的建设中。更多介绍可参考 Aya: Rust 风格的 eBPF 伙伴

同时,也期待 libbpf-bootstrap 项目能够早日实现 Aya 向导式的 eBPF 程序代码创建,这对于编写 eBPF 相关的程序的确提供了快速上手的体验。

参考

  1. 一篇 Rust 的 30 分钟介绍

  2. https://aya-rs.dev/

  3. LWN: Aya: writing BPF in Rust 2021-6-15

  4. Aya: your tRusty eBPF companion 2022-6-22 【翻译】Aya: Rust 风格的 eBPF 伙伴

  5. Adding BPF target support to the Rust compiler

  6. Rust and Tell - Berlin - Aya: Extending the Linux Kernel with eBPF and Rust by Michal Rostecki 2022-10-24

  7. Writing an eBPF/XDP load-balancer in Rust

  8. Wanting to use BPF with Rust (Part 1)

Rust Aya 编写 eBPF 程序的更多相关文章

  1. BumbleBee: 如丝般顺滑构建、交付和运行 eBPF 程序

    本文地址:https://www.ebpf.top/post/bumblebee 1. 前言 不久前,Solo.io 公司在官网博客宣布了开源了一个名称为 BumbleBee 的新项目.该项目专注于简 ...

  2. CSharpGL(11)用C#直接编写GLSL程序

    CSharpGL(11)用C#直接编写GLSL程序 +BIT祝威+悄悄在此留下版了个权的信息说: 2016-08-13 由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了.CSharp ...

  3. 在Linux上编写C#程序

    自从C#开源之后,在Linux编写C#程序就成了可能.Mono-project就是开源版本的C#维护项目.在Linux平台上使用的C#开发工具为monodevelop.安装方式如下: 首先需要安装一些 ...

  4. 35.按要求编写Java程序: (1)编写一个接口:InterfaceA,只含有一个方法int method(int n); (2)编写一个类:ClassA来实现接口InterfaceA,实现int method(int n)接口方 法时,要求计算1到n的和; (3)编写另一个类:ClassB来实现接口InterfaceA,实现int method(int n)接口 方法时,要求计算n的阶乘(n

      35.按要求编写Java程序: (1)编写一个接口:InterfaceA,只含有一个方法int method(int n): (2)编写一个类:ClassA来实现接口InterfaceA,实现in ...

  5. 如何让VS2013编写的程序

    总体分c++程序和c#程序 1.c++程序 这个用C++编写的程序可以经过设置后在XP下运行,主要的“平台工具集”里修改就可以. 额外说明:(1)程序必须为Dotnet 4.0及以下版本.(XP只支持 ...

  6. 编写一个程序,求s=1+(1+2)+(1+2+3)+…+(1+2+3+…+n)的值

    编写一个程序,求s=1+(1+2)+(1+2+3)+…+(1+2+3+…+n)的值 1 #import <Foundation/Foundation.h>  2   3 int main( ...

  7. 在Salesforce中通过编写C#程序调用dataloadercliq的bat文件取触发调用data loader来批量处理数据

    通过这篇文章 http://www.cnblogs.com/mingmingruyuedlut/p/3413903.html 我们已经知道了Data Loader可以对Salesforce的Objec ...

  8. 转 : 用Delphi编写安装程序

    http://www.okbase.net/doc/details/931  还没有亲自验证过,仅收藏 当你完成一个应用软件的开发后,那么你还需要为该软件做一个规范化的安装程序,这是程序设计的最后一步 ...

  9. 初学编写JAVA程序

    一.编写JAVA程序 编写JAVA程序,输出一行文本信息:“Hello world”,选择编辑器eclipse,打开之后编写程序 public class Hello{ public static v ...

随机推荐

  1. 解决eclipse中的Java文件,使用idea打开的乱码问题

    吐槽: 在克隆一些Github上面资源的时候,使用idea打开,会出现乱码的情况 而使用eclipse打开,这种情况就会消失.「是因为eclipse使用的是GBK编码,idea使用的是utf-8」 这 ...

  2. 「题解报告」CF1067A Array Without Local Maximums

    大佬们的题解都太深奥了,直接把转移方程放出来让其他大佬们感性理解,蒟蒻们很难理解,所以我就写了一篇让像我一样的蒟蒻能看懂的题解 原题传送门 动态规划三部曲:确定状态,转移方程,初始状态和答案. --神 ...

  3. 三 单例模式【Singleton Pattern】  来自CBF4LIFE 的设计模式

    这个模式是很有意思,而且比较简单,但是我还是要说因为它使用的是如此的广泛,如此的有人缘,单例就是单一.独苗的意思,那什么是独一份呢?你的思维是独一份,除此之外还有什么不能山寨的呢?我们举个比较难复制的 ...

  4. JavaScript之数组常用API

    这篇文章主要帮助大家简单理解数组的一些常用API用法,许多小伙伴常用方法记不住?别急,看完下面的介绍您一定就会明白各个方法是如何用的了.该文章适合新手小白看,大佬可以多多指点️! 1.数组的创建以及A ...

  5. 使用 Elastic Stack 分析地理空间数据 (二)

    文章转载自:https://blog.csdn.net/UbuntuTouch/article/details/106546064 在之前的文章 "Observability:使用 Elas ...

  6. Logstash:如何处理 Logstash pipeline 错误信息

    转载自:https://elasticstack.blog.csdn.net/article/details/114290663 在我们使用 Logstash 的时候经常会出现一些错误.比如当我们使用 ...

  7. 30分钟掌握 Webpack

    本文基于:峰华前端工程师--30分钟掌握Webpack 为什么使用 Webpack 在我们进行传统网页开发中,会在 index.html 中引入大量的 js 和 css 文件,不仅可能会导致命名冲突, ...

  8. js基础知识--BOM

    之前说过,在js的 运行环境为浏览器时,js就主要有三部分组成: ECMAScript核心语法.BOM.DOM.今天就和大家详细说一下BOM的一些基础知识. BOM BOM通常被称为浏览器对象模型,主 ...

  9. C++ 中std::的使用

    std是一个类(输入输出标准),它包括了cin成员和cout成员,using name space std ;以后才能使用它的成员.#include<iostream.h>中不存在类std ...

  10. MySQL实战,SQL语句

    student数据库 student学生表,course课程表表,sc成绩表 -- 1.找出成绩为95分的学生的姓名 SELECT Sname FROM student WHERE Sno IN( S ...