How to Use eBPF with Rust (aya)

Use the `aya` crate to write eBPF programs in Rust by compiling them with `clang` and loading them into the kernel via the `aya` runtime, which handles map management, program attachment, and event polling.

Use the aya crate to write eBPF programs in Rust by compiling them with clang and loading them into the kernel via the aya runtime, which handles map management, program attachment, and event polling. You write the eBPF logic in a separate Rust file annotated with #[no_mangle] and #[naked], compile it to an ELF object, and then load that object in your userspace Rust application using the Bpf struct to attach programs to tracepoints, kprobes, or XDP hooks.

Here is a minimal example of an eBPF program that counts function calls:

// bpf/src/main.rs
#![no_main]
use aya_ebpf::{
    macros::tracepoint,
    programs::TracePointContext,
};

#[tracepoint("syscalls", "sys_enter_openat")]
pub fn count_openat(ctx: TracePointContext) -> u32 {
    // Logic here runs in kernel space
    1
}

Compile this program using aya's build tool or clang directly, then load it in your userspace application:

// app/src/main.rs
use aya::{include_bytes_aligned, Bpf};
use aya_log::EbpfLogger;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize logging
    EbpfLogger::init()?;

    // Load the compiled eBPF object
    let mut bpf = Bpf::load(include_bytes_aligned!(
        "../../target/bpfel-bpf/bpf.o"
    ))?;

    // Attach the program to the tracepoint
    let program = bpf.program_mut("count_openat").unwrap();
    program.load()?;
    program.attach("syscalls", "sys_enter_openat")?;

    println!("eBPF program attached. Waiting for events...");
    loop {
        std::thread::sleep(std::time::Duration::from_secs(1));
    }
}

To set up the project, add aya, aya-ebpf, and aya-log to your Cargo.toml. The aya-ebpf crate provides the kernel-side macros, while the main aya crate handles userspace interaction. You must ensure your system has the necessary kernel headers and clang installed, as aya relies on the LLVM toolchain to compile the Rust eBPF code into BPF bytecode. The include_bytes_aligned! macro is crucial for loading the ELF object correctly, ensuring the data is aligned for the kernel.

For more complex scenarios involving maps (like hash maps or ring buffers), define them in the eBPF program using #[map] attributes and access them from userspace via bpf.map_mut(). Always test your eBPF programs on a system with a recent kernel (5.8+) to ensure feature compatibility, as eBPF support evolves rapidly.