Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Sockets

eBPF program type description

Program example

You can follow the basic XDP example to make a project structure.

E.g.: cargo generate --name hello-sockops -d program_type=sock_ops https://github.com/aya-rs/aya-template

Below is the eBPF part of the program (hello-sockops-ebpf/src/main.rs) commented:

#![allow(unused)]
#![no_std]
#![no_main]

fn main() {
use aya_ebpf::{EbpfContext, macros::sock_ops, programs::SockOpsContext};
use aya_log_ebpf::{info, warn};

enum SockOpsResult {
    // From https://docs.ebpf.io:
    //   Regardless of the type of operation,
    //   the program should always return 1 on success.
    //   A negative integer indicate a operation is not supported.
    Ok = 1,
    #[allow(dead_code)]
    Err = 2, // Some other number to indicate error
}

// Cannot do `cargo add libc` due to no_std
// use libc::AF_INET;
const AF_INET: u32 = 2;

#[sock_ops]
pub fn socket_ops(ctx: SockOpsContext) -> u32 {
    try_socket_ops(ctx) as u32
}

fn try_socket_ops(ctx: SockOpsContext) -> SockOpsResult {
    // ctx.op() specifies the type of operation this eBPF program should perform.
    // So, sock_ops program will look like this most of the time:
    // ```
    // match ctx.op() {
    //     aya_ebpf::bindings::BPF_SOCK_OPS_NEEDS_ECN => { ... }
    //     ...
    // }
    // ```
    // The program just dumps information about captured INET socket operation
    info!(&ctx, "started..");
    if ctx.family() == AF_INET {
        let local_ipv4_addr = ctx.local_ip4();
        let local_port = ctx.local_port();
        let pid = ctx.pid();
        warn!(
            ctx,
            "PID {} | from {}:{} | op {}",
            pid,
            local_ipv4_addr,
            local_port,
            ctx.op()
        );
    }
    SockOpsResult::Ok
}

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

The following userspace code loads the above program:

use anyhow::Context as _;

fn main() -> anyhow::Result<()> {
    let mut ebpf_sockops = aya::Ebpf::load(aya::include_bytes_aligned!(
        concat!(env!("OUT_DIR"), "/hello-sockops")))?;

    env_logger::init();
    let mut logger = match aya_log::EbpfLogger::init(&mut ebpf_sockops) {
        Err(e) => {
            log::warn!("failed to initialize eBPF logger: {e}");
            None
        }
        Ok(logger) => Some(logger),
    };

    let program_name = "socket_ops";
    let program: &mut aya::programs::SockOps = ebpf_sockops
        .program_mut(program_name)
        .expect(format!("no eBPF program named {program_name}").as_str())
        .try_into()?;
    program.load()?;
    let root_cg_file = std::fs::File::open("/sys/fs/cgroup")?;
    program
        .attach(root_cg_file, aya::programs::CgroupAttachMode::Single)
        .context("failed to attach the SockOps to root cgroup")?;

    loop {
        std::thread::sleep(std::time::Duration::from_millis(100));
        if let Some(logger) = logger.as_mut() {
            logger.flush();
        }
    }
}

Try to start the program with RUST_LOG=warn cargo run and run curl goo.gle in another terminal to see information on the new socket logged by the eBPF program.