____ _______ __ _____ _ _
| _ \| ____\ \/ / | ____|_ _| |_ ___ _ __ ___(_) ___ _ __ ___
| |_) | _| \ / | _| \ \/ / __/ _ \ '_ \/ __| |/ _ \| '_ \/ __|
| _ <| |___ / \ | |___ > <| || __/ | | \__ \ | (_) | | | \__ \
|_| \_\_____/_/\_\ |_____/_/\_\\__\___|_| |_|___/_|\___/|_| |_|___|
Rex 是一个安全且可用的内核扩展框架,允许在 eBPF 的位置加载和执行 Rust 内核扩展程序。与基于 eBPF 的框架(如 Aya)不同,Rex 程序无需经过内核验证器,而是使用 Rust 的安全子集实现,Rust 编译器执行所需的安全检查并直接生成本地代码。这种方法避免了过度严格的验证要求(例如,程序复杂度限制)以及由此产生的晦涩的验证错误,同时可能在本机编译器后端(即 LLVM)中提供比 eBPF 后端 + 内核 JIT 方法更好的优化机会。
Rex 目前支持以下特性:
kprobe、perf_event、tracepoint、xdp 和 tc。以下示例实现了一个 kprobe 程序,它附加到选定的系统调用,并向指定进程(通过其 pid 指定)的系统调用注入一个错误(由 errno 指定)。完整示例(包括加载器程序)可在 samples/error_injector 中找到。
#![no_std]
#![no_main]
use rex::kprobe::kprobe;
use rex::map::RexHashMap;
use rex::pt_regs::PtRegs;
use rex::rex_kprobe;
use rex::rex_map;
use rex::Result;
#[allow(non_upper_case_globals)]
#[rex_map]
static pid_to_errno: RexHashMap<i32, u64> = RexHashMap::new(1, 0);
#[rex_kprobe]
pub fn err_injector(obj: &kprobe, ctx: &mut PtRegs) -> Result {
obj.bpf_get_current_task()
.map(|t| t.get_pid())
.and_then(|p| obj.bpf_map_lookup_elem(&pid_to_errno, &p).cloned())
.map(|e| obj.bpf_override_return(ctx, e))
.ok_or(0)
}
更多示例程序可在 samples 中找到。
你可以在此找到详细指南 here。
其他设计文档可在 docs 中找到。
现有的 eBPF 扩展依赖内核内的 eBPF 验证器来提供安全保证。这不幸地导致了可用性问题,即安全的程序被验证器拒绝,包括但不限于:
Rex 旨在通过直接利用 安全 Rust 的安全保证来解决这些问题。开发者可以按照安全 Rust 中可用的任何方式(有 少量限制)实现其程序,不再需要担心程序复杂度、代码生成器,或者找到(通常是反直觉的)表达相同逻辑的方式来迎合验证器。
我们通过实现 BPF Memcached Cache (BMC)(一种用于 Memcached 加速的先进扩展程序)来展示这一点。作为一个复杂的 eBPF 程序,BMC 被迫分割成多个通过 BPF 尾调用连接的组件,并使用笨拙的循环/分支实现来通过验证器,而这些在 其 Rex 实现 中完全不需要。
例如,我们展示 BPF-BMC 中缓存失效逻辑的代码,该代码在数据包负载中搜索 SET 命令:
// 在负载中搜索 SET 命令
for (unsigned int off = 0;
off < BMC_MAX_PACKET_LENGTH && payload + off + 1 <= data_end;
off++) {
if (set_found == 0 && payload[off] == 's' &&
payload + off + 3 <= data_end && payload[off + 1] == 'e' &&
payload[off + 2] == 't') {
off += 3;
set_found = 1;
}
...
}
该代码不仅为通过验证器而引入了循环中的额外约束(off < BMC_MAX_PACKET_LENGTH),还使用了重复的样板代码来检查数据包尾部(data_end)以及繁琐的逻辑来匹配数据包中的 "set" 字符串。
在 Rex 中借助安全 Rust 的强大功能,这些负担都不需要,因为 Rex 没有复杂度限制,并且为实现提供了更多自由:
let set_iter = payload.windows(4).enumerate().filter_map(|(i, v)| {
if v == b"set " {
Some(i)
} else {
None
}
});
BMC 在 Rex 中的完整实现可在 samples/bmc 找到。
Rex 采用 GPLv2 许可证。本仓库中的子模块(Linux、Rust、LLVM)采用其各自的许可证。更多详情请参见相应的许可证文件。此外,memcached 基准测试采用 MIT 许可证。