eBPF 网络可观测性:用 Rust 编写高性能探针
eBPF(extended Berkeley Packet Filter)允许在内核中安全地运行沙箱程序,而无需修改内核源码或加载模块。结合 Rust 的安全性保证,eBPF 程序开发变得更加可靠。
使用 Aya 框架编写 eBPF 程序:
#![no_std]
#![no_main]
use aya_bpf::{
bindings::TC_ACT_OK,
programs::TcContext,
macros::classifier,
};
#[classifier]
pub fn tc_egress(ctx: TcContext) -> i32 {
// 解析以太网头
let eth_proto = ctx.load::<u16>(12).unwrap_or(0);
if eth_proto != 0x0800 {
return TC_ACT_OK; // 非IPv4,放行
}
// 解析IP头
let ip_proto = ctx.load::<u8>(23).unwrap_or(0);
if ip_proto != 6 {
return TC_ACT_OK; // 非TCP,放行
}
// 记录TCP流量
let src_port = ctx.load::<u16>(34).unwrap_or(0);
let dst_port = ctx.load::<u16>(36).unwrap_or(0);
unsafe {
TCP_COUNTER.increment(1);
}
TC_ACT_OK
}
用户空间程序加载和附加 eBPF 程序:
use aya::{Bpf, programs::TcAttachType};
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
// 加载编译好的 eBPF 字节码
let mut bpf = Bpf::load(include_bytes_aligned!(
"../target/bpfel-unknown-none/release/ebpf-prog"
))?;
// 附加到网络接口
let program = bpf.program_mut("tc_egress")
.ok_or_else(|| anyhow!("program not found"))?;
program.load()?;
let link = program.attach("eth0", TcAttachType::Egress)?;
// 读取计数器
loop {
let count = unsafe { TCP_COUNTER.get(0) };
println!("TCP packets: {}", count);
tokio::time::sleep(Duration::from_secs(1)).await;
}
}
Aya 的优势在于 eBPF 程序和用户空间程序都用 Rust 编写,共享类型定义,避免了 C 版本中常见的头文件同步问题。在我们的生产环境中,这套 eBPF 探针每秒处理 100 万个包,CPU 开销不到 2%。