O Poder do eBPF no Kernel Linux com Rust

A engenharia de software vive um momento de inflexão com a popularização do eBPF (Extended Berkeley Packet Filter). Ele permite a execução de programas em sandbox diretamente no kernel do sistema operacional, revolucionando as áreas de observabilidade, redes e segurança sem a necessidade de alterar o código-fonte do kernel ou carregar módulos adicionais.

Neste tutorial técnico avançado, exploraremos como criar uma aplicação eBPF utilizando Rust e o framework Aya, combinando a segurança de memória do Rust com o desempenho nativo do kernel.

1. Por que eBPF com Rust?

Historicamente, o desenvolvimento eBPF dependia de C e do framework bcc/libbpf. Com a chegada do projeto Aya, agora podemos escrever tanto o programa em user-space quanto o código em kernel-space (eBPF) inteiramente em Rust. O Rust oferece tipagem forte, ausência de null pointers e uma excelente ergonomia de desenvolvimento.

2. Preparando o Ambiente

Primeiro, instale o Rust e as ferramentas do Aya:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup install stable
rustup toolchain install nightly --component rust-src
cargo install bpf-linker
cargo install cargo-generate

3. Criando o Projeto com Aya

Vamos gerar nosso projeto utilizando o template do Aya:

cargo generate -d program_type=xdp https://github.com/aya-rs/aya-template

Nota: XDP (eXpress Data Path) é um tipo de programa eBPF focado em processamento de pacotes de rede em altíssima velocidade.

4. Analisando o Código do Kernel (eBPF)

Abra o arquivo gerado em myapp-ebpf/src/main.rs. Aqui está um exemplo de um programa XDP simples que loga quando um pacote é interceptado:

#![no_std]
#![no_main]

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

#[xdp]
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, "Novo pacote interceptado!");
    Ok(xdp_action::XDP_PASS)
}

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

Neste snippet: - @xdp: Macro que define o ponto de entrada. - info!: Macro do aya-log que envia logs para o user-space. - XDP_PASS: Retorno que diz ao kernel para continuar processando o pacote normalmente.

5. Código do User-Space (Control Plane)

O programa de user-space carrega o bytecode eBPF no kernel. Arquivo myapp/src/main.rs:

use aya::{Bpf, programs::{Xdp, XdpFlags}};
use aya_log::BpfLogger;
use std::{convert::TryInto, env, net::Ipv4Addr};
use tokio::signal;

#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
    let mut bpf = Bpf::load(include_bytes_aligned!(
        "../../target/bpfel-unknown-none/debug/myapp"
    ))?;

    BpfLogger::init(&mut bpf)?;

    let program: &mut Xdp = bpf.program_mut("myapp").unwrap().try_into()?;
    program.load()?;
    program.attach("eth0", XdpFlags::default())
        .context("falha ao atrelar à interface eth0")?;

    println!("Programa eBPF rodando! Pressione Ctrl-C para sair.");
    signal::ctrl_c().await?;
    println!("Saindo...");
    Ok(())
}

6. Conclusão

A combinação de eBPF e Rust com o framework Aya altera as regras do jogo. Temos agora segurança no nível de compilação, abstrações modernas, e zero overhead em tempo de execução no nível do kernel. Isso abre portas para firewalls customizados super-rápidos, middlewares de rede complexos e telemetria profunda - tudo escrito em uma das linguagens de programação mais amadas da atualidade.