Skip to content

Using aya-tool

Source Code

Full code for the example in this chapter is available here.

Very often you will need to use type definitions that your running Linux kernel uses in its source code. For example, you might need a definition of task_struct, because you are about to write a BPF program which receives an information about new scheduled process/task. Aya doesn't provide any definition of this structure. What should be done to get that definition? And we also need that definition in Rust, not in C.

That's what aya-tool is designed for. It's a tool which allows to generate Rust bindings for specific kernel structures.

It can be installed with the following commands:

cargo install bindgen-cli
cargo install --git https://github.com/aya-rs/aya -- aya-tool

Ensure that you have bpftool and bindgen installed in your system, aya-tool is not going to work without it.

The syntax of the command is:

$ aya-tool
aya-tool

USAGE:
    aya-tool <SUBCOMMAND>

OPTIONS:
    -h, --help    Print help information

SUBCOMMANDS:
    generate    Generate Rust bindings to Kernel types using bpftool
    help        Print this message or the help of the given subcommand(s)

Let's assume that we want to generate Rust definition of task_struct. Let's also assume that your project is called myapp. Your userspace part is in myapp subdirectory, your eBPF part is in myapp-ebpf. We need to generate the bindings for the eBPF part, which can be done with:

aya-tool generate task_struct > myapp-ebpf/src/vmlinux.rs

Generating for multiple types

You can also specify multiple types to generate, for example:

aya-tool generate task_struct dentry > vmlinux.rs

But in the following example, we will focus only on task_struct.

Then we can use vmlinux as a module with mod vmlinux in our eBPF program, like here:

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

#[allow(clippy::all)]
#[allow(dead_code)]
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
#[allow(non_upper_case_globals)]
#[rustfmt::skip]
mod vmlinux;

use aya_ebpf::{
    cty::{c_int, c_ulong},
    macros::{lsm, map},
    maps::HashMap,
    programs::LsmContext,
};

use vmlinux::task_struct;

#[map]
static PROCESSES: HashMap<i32, i32> = HashMap::with_max_entries(32768, 0);

#[lsm(hook = "task_alloc")]
pub fn task_alloc(ctx: LsmContext) -> i32 {
    match unsafe { try_task_alloc(ctx) } {
        Ok(ret) => ret,
        Err(ret) => ret,
    }
}

unsafe fn try_task_alloc(ctx: LsmContext) -> Result<i32, i32> {
    let task: *const task_struct = ctx.arg(0);
    let _clone_flags: c_ulong = ctx.arg(1);
    let retval: c_int = ctx.arg(2);

    // Save the PID of a new process in map.
    let pid = (*task).pid;
    PROCESSES.insert(&pid, &pid, 0).map_err(|e| e as i32)?;

    // Handle results of previous LSM programs.
    if retval != 0 {
        return Ok(retval);
    }

    Ok(0)
}

#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
    loop {}
}

Portability and different kernel versions

Structures generated by aya-tool are portable across different Linux kernel versions thanks to mechanism called BPF CO-RE. The structures are not simply generated from kernel headers. However, the target kernel (regardless of version) should have CONFIG_DEBUG_INFO_BTF option enabled.