Rust Aya 框架编写 eBPF 程序

目录
  • 1. 前言
  • 2. Rust 开发环境搭建
    • 2.1 创建 VM 虚拟机
    • 2.2 安装 Rust 开发环境
    • 2.3 安装 bpf-linker 依赖 和 bpftool 工具
  • 3. Aya 向导创建 eBPF 程序
    • 3.1 使用向导创建项目
    • 3.2 编译 eBPF 程序
    • 3.3 运行用户空间程序
  • 4. 总结
  • 参考

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 的环境。

$ 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 工具管理,我们可以通过以下命令快速安装该工具:

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

一般情况下我们选择默认选项安装。整个安装过程会下载一个脚本完成 rustup 工具安装,同时也会安装最新的 Rust 稳定版本。如果安装顺利,我们可以在最后看到如下的信息:

...
stable-x86_64-unknown-linux-gnu installed - rustc 1.65.0 (897e37553 2022-11-02)

Rust is installed now. Great!

To get started you may need to restart your current shell.
This would reload your PATH environment variable to include
Cargo's bin directory ($HOME/.cargo/bin).

To configure your current shell, run:
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”

$ source "$HOME/.cargo/env"
$ rustup install stable # rustup 命令已经默认安装
info: syncing channel updates for 'stable-x86_64-unknown-linux-gnu'

stable-x86_64-unknown-linux-gnu unchanged - rustc 1.65.0 (897e37553 2022-11-02)

info: checking for self-updates
$ rustup toolchain install nightly --component rust-src
...
info: installing component 'rustfmt'

nightly-x86_64-unknown-linux-gnu installed - rustc 1.67.0-nightly (09508489e 2022-11-04)

info: checking for self-updates

$ rustup toolchain list
stable-x86_64-unknown-linux-gnu (default)
nightly-x86_64-unknown-linux-gnu

安装 nightly 以后我们可以使用 rustup toolchain list 查看本地开发环境的开发工具链。

2.3 安装 bpf-linker 依赖 和 bpftool 工具

为了使用 Aya,我们还需要安装依赖包 bpf-linker,但其依赖与 LLVM/Clang 等工具,因此我们也需要提前安装:

$ sudo apt-get update
$ sudo apt-get install llvm clang -y
$ cargo install bpf-linker

最后,为了生成内核数据结构的绑定,我们还必须安装 bpftool,可以从发行版中安装或从源代码中构建,这里我选用发行版安装方式(基于 Ubuntu 22.04),源码安装可参考 bpftool 仓库说明文档

$ 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,因此我们需要在运行程序向导前提前安装:

$ cargo install cargo-generate

我在安装 cargo-generate 过程中遇到了如下的错误,主要是由于依赖 openssl 库问题导致,如果你也遇到类似问题可参考 cargo-generate 安装指南 和 Rust OpenSSL 文档,如果一切顺利,则可忽略此处的提示。

...
warning: build failed, waiting for other jobs to finish...
error: failed to compile `cargo-generate v0.16.0`, intermediate artifacts can be found at `/tmp/cargo-install8NrREg
...

$ sudo apt install openssl pkg-config libssl-dev gcc m4 ca-certificates make perl -y
# 重新安装即可

在完成依赖后,我们就可以使用向导来创建 eBPF 项目,这里以 XDP 类型程序为例:

$ cargo generate https://github.com/aya-rs/aya-template

这里我们输入项目名称 myapp,eBPF 程序类型选择 xdp,完成相关设定后,向导会自动帮我们创建一个名为 myapp 的 Rust 项目,项目包括了一个最简单的 XDP 类型的 eBPF 程序及相对应的用户空间程序。 myapp 目录的整体夹头如下所示:

├── Cargo.lock
├── Cargo.toml
├── README.md
├── myapp # 用户空间程序
│   ├── Cargo.toml
│   └── src
│   └── main.rs
├── myapp-common # eBPF 程序与用户空间程序复用的代码库
│   ├── Cargo.toml
│   └── src
│   └── lib.rs
├── myapp-ebpf # eBPF 程序
│   ├── Cargo.lock
│   ├── Cargo.toml
│   ├── rust-toolchain.toml
│   └── src
│   └── main.rs
└── xtask # build 相关的代码
├── Cargo.toml
└── src
├── build_ebpf.rs
├── main.rs
└── run.rs

8 directories, 15 files

生成的 eBPF 程序位于 myapp-ebpf/src 目录下,文件名为 main.rs,完整内容如下所示:

$ cat myapp-ebpf/src/main.rs
#![no_std]
#![no_main]

use aya_bpf::{
bindings::xdp_action,
macros::xdp,
programs::XdpContext,
};
use aya_log_ebpf::info;

#[xdp(name="myapp")]
pub fn myapp(ctx: XdpContext) -> u32 {
match try_myapp(ctx) {
Ok(ret) => ret,
Err(_) => xdp_action::XDP_ABORTED,
}
}

fn try_myapp(ctx: XdpContext) -> Result<u32, u32> {
info!(&ctx, "received a packet"); // 每接受到一个数据包则打印一个日志
Ok(xdp_action::XDP_PASS)
}

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
unsafe { core::hint::unreachable_unchecked() }
}

3.2 编译 eBPF 程序

首先,我们使用 cargo 工具编译 eBPF 对应的程序:

$ cd myapp
$ cargo xtask build-ebpf
...
Compiling myapp-ebpf v0.1.0 (/home/ubuntu/myapp/myapp-ebpf)
Running `rustc --crate-name myapp --edition=2021 src/main.rs --error-format=json \
--json=diagnostic-rendered-ansi,artifacts,future-incompat --crate-type bin \
--emit=dep-info,link -C opt-level=3 -C panic=abort -C lto -C codegen-units=1
-C metadata=dd6140d48c387b43 -C extra-filename=-dd6140d48c387b43 \
--out-dir \
...
-Z unstable-options \
Finished dev [optimized] target(s) in 11.76s 

编译完成后,对应的程序保存在 target 目录下:

~/myapp$ ls -hl target/bpfel-unknown-none/debug/
...
-rw-rw-r-- 2 ubuntu ubuntu 3.5K Nov 6 22:24 myapp

~/myapp$ file target/bpfel-unknown-none/debug/myapp
target/bpfel-unknown-none/debug/myapp: ELF 64-bit LSB relocatable, eBPF, version 1 (SYSV), not stripped

/myapp$ llvm-objdump -S target/bpfel-unknown-none/debug/myapp

target/bpfel-unknown-none/debug/myapp: file format elf64-bpf

Disassembly of section xdp/myapp:

0000000000000000 <myapp>:
...
242: bf 61 00 00 00 00 00 00 r1 = r6
243: 18 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r2 = 0 ll
245: 18 03 00 00 ff ff ff ff 00 00 00 00 00 00 00 00 r3 = 4294967295 ll
247: bf 04 00 00 00 00 00 00 r4 = r0
248: b7 05 00 00 aa 00 00 00 r5 = 170
249: 85 00 00 00 19 00 00 00 call 25

至此,已经完成了 eBPF 程序的编译工作,接着我们需要继续编译用户空间代码。

3.3 运行用户空间程序

我们可以直接使用 cargo 命令来运行用户空间程序:

$ RUST_LOG=info cargo xtask run
...
Finished dev [unoptimized + debuginfo] target(s) in 8.38s
Error: failed to attach the XDP program with default flags - try changing XdpFlags::default() to XdpFlags::SKB_MODE

Caused by:
unknown network interface eth0

RUST_LOG=info 为设置日志级别的环境变量,默认为 warn,但向导生成的代码打印的日志级别默认为 info,因此需要运行时制定,否则可能会出现程序运行查看不到日志的情况。

cargo xtask run 命令会直接编译用户空间代码并运行,但是运行过程中我们发现出现错误 unknown network interface eth0,这是因为默认生成的程序指定将 XDP 程序加载到 eth0 网卡,而我们的 VM 默认网卡不为 eth0 导致,这里我们明确制定网卡使用 lo 测试,再次运行结果如下:

$ RUST_LOG=info cargo xtask run -- --iface lo
...
Finished dev [optimized] target(s) in 0.19s
Finished dev [unoptimized + debuginfo] target(s) in 0.12s
[2022-11-05T16:25:27Z INFO myapp] Waiting for Ctrl-C...

这次可以发现用户空间程序已经正常运行,并且将对应的 eBPF 程序加载至内核中。

$ sudo bpftool prog list
42: 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 show
1: lo: <LOOPBACK,UP,LOWER_UP> 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 # <=== 加载的 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 等程序类型的文档还在完善中,如果你有兴趣也欢迎投入到相关的建设中。

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

参考

到此这篇关于Rust Aya 框架编写 eBPF 程序的文章就介绍到这了,更多相关Rust编写 eBPF 程序内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Rust 模式匹配示例详解

    目录 函数 斐波那契数列函数 小结 函数 今天学习的内容是 Rust 中的函数. 函数,和 if 条件控制,while.loop.for 循环控制,在底层都是使用跳转实现的,所以函数也属于流程控制的一种.可以这样理解,当函数执行时,程序的执行主体就进入了函数内部,当函数执行完毕,则又返还回去,这就是函数的流程控制. Rust 中使用函数和 Typescript 比较像,需要关注参数的类型和返回值的类型, 再就是有一点 Rust 语言的特性,返回值可以是一个表达式. 参数的类型注解和变量是一样的,

  • Rust for循环语法糖背后的API场景分析

    目录 1.拿走所有权的for循环 2.只读for循环 3.读写for循环 Rust中for循环实质上是一个语法糖,in后面的对象要求是一个迭代器,for循环就是对这个迭代器循环调用next,而in前面的名称就是每一次迭代后返回的结果,如果next返回Option::None则退出循环.了解这一点后我们可以自己编写自己的迭代器类型,然后使用for循环进行迭代. rust有三种for循环,分别用于不同的场景. 1.拿走所有权的for循环 形式如:for item in collection(集合或容

  • Rust实现一个表达式Parser小结

    目录 正文 lexer parser traversal 说在最后 正文 在 src/lib.rs 补上一个函数和一个 smoke test, 如下 pub use traversal::{eval, format}; pub fn build_ast(expr: &str) -> Result<Node, String> { let root = syntax(lex(expr)?)?; Ok(root) } #[cfg(test)] mod tests { use super

  • Rust语言中的String和HashMap使用示例详解

    目录 String 新建字符串 更新字符串 使用 + 运算符或 format! 宏拼接字符串 索引字符串 字符串 slice 遍历字符串 HashMap 新建 HashMap HashMap 和 ownership 访问 HashMap 中的值 更新 HashMap 直接覆盖 新插入 更新旧值 总结 String 字符串是比很多开发者所理解的更为复杂的数据结构.加上 UTF-8 的不定长编码等原因,Rust 中的字符串并不如其它语言中那么好理解. Rust 的核心语言中只有一种字符串类型:str

  • Rust Aya 框架编写 eBPF 程序

    目录 1. 前言 2. Rust 开发环境搭建 2.1 创建 VM 虚拟机 2.2 安装 Rust 开发环境 2.3 安装 bpf-linker 依赖 和 bpftool 工具 3. Aya 向导创建 eBPF 程序 3.1 使用向导创建项目 3.2 编译 eBPF 程序 3.3 运行用户空间程序 4. 总结 参考 1. 前言 Linux 内核 6.1 版本中有一个非常引人注意的变化:引入了对 Rust 编程语言的支持.Rust 是一种系统编程语言,Rust 通过提供非常强大的编译时保证和对内存

  • 采用React编写小程序的Remax框架的编译流程解析(推荐)

    Remax是蚂蚁开源的一个用React来开发小程序的框架,采用运行时无语法限制的方案.整体研究下来主要分为三大部分:运行时原理.模板渲染原理.编译流程:看了下现有大部分文章主要集中在Reamx的运行时和模板渲染原理上,而对整个React代码编译为小程序的流程介绍目前还没有看到,本文即是来补充这个空白. 关于模板渲染原理看这篇文章:https://www.jb51.net/article/132635.htm 关于remax运行时原理看这篇文章:https://www.jb51.net/artic

  • 如何在Python中编写并发程序

    GIL 在Python中,由于历史原因(GIL),使得Python中多线程的效果非常不理想.GIL使得任何时刻Python只能利用一个CPU核,并且它的调度算法简单粗暴:多线程中,让每个线程运行一段时间t,然后强行挂起该线程,继而去运行其他线程,如此周而复始,直到所有线程结束. 这使得无法有效利用计算机系统中的"局部性",频繁的线程切换也对缓存不是很友好,造成资源的浪费. 据说Python官方曾经实现了一个去除GIL的Python解释器,但是其效果还不如有GIL的解释器,遂放弃.后来P

  • Python Nose框架编写测试用例方法

    1. 关于Nose nose项目是于2005年发布的,也就是 py.test改名后的一年.它是由 Jason Pellerin 编写的,支持与 py.test 相同的测试习惯做法,但是这个包更容易安装和维护. nose的口号是:扩展unittest,nose让测试更简单! nose官网:http://nose.readthedocs.io/en/latest/index.html 使用nose框架进行Python项目的自动化测试,可以参考:http://www.cnblogs.com/liaof

  • PHP隐形一句话后门,和ThinkPHP框架加密码程序(base64_decode)

    今天一个客户的服务器频繁被写入: mm.php 内容为: 复制代码 代码如下: <?eval($_POST[c]);?> 最后查到某文件内的第一行为以下代码: 复制代码 代码如下: fputs(fopen(base64_decode("bW0ucGhw"),"w"),base64_decode("PD9ldmFsKCRfUE9TVFtjXSk7Pz4=")); base64_decode("bW0ucGhw") /

  • Python编写屏幕截图程序方法

    正在编写的程序用的很多Windows下的操作,查了很多资料.看到剪切板的操作时,想起以前想要做的一个小程序,当时也没做,现在正好顺手写完. 功能:按printscreen键进行截图的时候,数据保存在剪切板里面,很不方便.比如游戏的时候截一个瞬间的图片,但你不能退出游戏保存图片,不方便多次截图.而我也不喜欢安装各种软件,所以准备写这个工具. 思路:一个是自定义快捷键,截图,保存.考虑到很可能各种冲突,取消.然后还是用按printscreen来截图,然后从剪切板读取图片数据,保存.想法是,先监听键盘

  • Android 驱动编写LED-NDK程序

    1. 首先编写LINUX内核模块LED #include <linux/kernel.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/slab.h> #include <linux/device.h> #include <asm/io.h> #include <asm/uaccess.h> #include <linux/cdev.

  • Eclipse下编写java程序突然不会自动生成R.java文件和包的解决办法

    下面给大家介绍几种比较常见的解决办法,具体内容如下: 1.有时候eclipse不自动编译,把project clean一下,让R.java重新生成 2.选择菜单 Project >> Clean ,前提是勾选上 Bulid Automatically(自动构建部署) , 点Clean后会重新构建项目,因为一般情况下,R.java文件在这个时候会重新更新生成一边,如果工程有错,就不会自动生成. 3.选择工程,右键 Android Tools >> Fix Project Proper

  • python基于twisted框架编写简单聊天室

    本文实例为大家分享了使用python的twisted框架编写一个简单的聊天室具体代码,供大家参考,具体内容如下 下面是基本架构 代码: # -*- coding:utf-8 -*- from twisted.internet.protocol import Factory from twisted.protocols.basic import LineReceiver from twisted.internet import reactor user = {} class ChatReci(Li

  • pycharm编写spark程序,导入pyspark包的3中实现方法

    一种方法: File --> Default Setting --> 选中Project Interpreter中的一个python版本-->点击右边锯齿形图标(设置)-->选择more-->选择刚才选中的那个python版本-->点击最下方编辑(也就是增加到这个python版本下)-->点击➕-->选中spark安装目录下的python目录-->一路OK. 再次在python文件中写入如下 from pyspark import SparkConf

随机推荐