pktkit 0.1.5

Zero-copy L2/L3 packet handling toolkit. Frames, packets, hubs, adapters, NAT, virtual TCP/IP, WireGuard, OpenVPN, QEMU networking, TUN/TAP, AF_XDP — all gated behind opt-in cargo features.
Documentation
//! Raw `bpf(2)` plumbing.
//!
//! We call `bpf(cmd, &attr, sizeof attr)` directly rather than
//! depend on libbpf: the programs in this module are hand-encoded, so an ELF
//! loader would be the only thing libbpf brought to the table.
//!
//! `bpf_attr` is a union in `<linux/bpf.h>`. The kernel reads only as many
//! bytes as the command needs and requires any tail beyond the struct it knows
//! about to be zero, so declaring one `#[repr(C)]` prefix struct per command is
//! both sufficient and forward-compatible.

use std::io;

use crate::Result;

// --- bpf(2) command numbers ------------------------------------------------

pub const BPF_MAP_CREATE: i32 = 0;
pub const BPF_MAP_LOOKUP_ELEM: i32 = 1;
pub const BPF_MAP_UPDATE_ELEM: i32 = 2;
pub const BPF_MAP_DELETE_ELEM: i32 = 3;
pub const BPF_PROG_LOAD: i32 = 5;
pub const BPF_PROG_TEST_RUN: i32 = 10;
pub const BPF_LINK_CREATE: i32 = 28;

// --- program / attach types ------------------------------------------------

pub const BPF_PROG_TYPE_XDP: u32 = 6;
/// `bpf_attach_type::BPF_XDP`.
pub const BPF_ATTACH_TYPE_XDP: u32 = 37;

/// `bpf_attr` for `BPF_MAP_CREATE`.
#[repr(C)]
#[derive(Default)]
pub struct MapCreateAttr {
    pub map_type: u32,
    pub key_size: u32,
    pub value_size: u32,
    pub max_entries: u32,
    pub map_flags: u32,
}

/// `bpf_attr` for the element commands. `key`/`value` are userspace pointers
/// the kernel dereferences.
#[repr(C)]
#[derive(Default)]
pub struct MapElemAttr {
    pub map_fd: u32,
    pub _pad: u32,
    pub key: u64,
    /// `value` for update, `next_key` for iteration; unused by delete.
    pub value: u64,
    pub flags: u64,
}

/// `bpf_attr` for `BPF_PROG_LOAD`.
#[repr(C)]
#[derive(Default)]
pub struct ProgLoadAttr {
    pub prog_type: u32,
    pub insn_cnt: u32,
    pub insns: u64,
    pub license: u64,
    pub log_level: u32,
    pub log_size: u32,
    pub log_buf: u64,
    pub kern_version: u32,
    pub prog_flags: u32,
    pub prog_name: [u8; 16],
    pub prog_ifindex: u32,
    pub expected_attach_type: u32,
}

/// `bpf_attr` for `BPF_PROG_TEST_RUN`. `data_in` is a userspace pointer the
/// kernel reads; `retval` and `duration` are written back.
#[repr(C)]
#[derive(Default)]
pub struct ProgTestRunAttr {
    pub prog_fd: u32,
    pub retval: u32,
    pub data_size_in: u32,
    pub data_size_out: u32,
    pub data_in: u64,
    pub data_out: u64,
    pub repeat: u32,
    pub duration: u32,
    pub ctx_size_in: u32,
    pub ctx_size_out: u32,
    pub ctx_in: u64,
    pub ctx_out: u64,
    pub flags: u32,
    pub cpu: u32,
    pub batch_size: u32,
    /// Spelled out so it is zeroed: see the size assertions below.
    pub _pad: u32,
}

// The kernel rejects a command with `EINVAL` unless every byte past the last
// field it knows is zero. Padding the compiler adds is never initialized, so
// a struct with any would pass or fail on whatever the stack last held. Each
// size below is the sum of its fields: no padding anywhere.
const _: () = {
    assert!(std::mem::size_of::<MapCreateAttr>() == 5 * 4);
    assert!(std::mem::size_of::<MapElemAttr>() == 2 * 4 + 3 * 8);
    assert!(std::mem::size_of::<ProgLoadAttr>() == 8 * 4 + 3 * 8 + 16);
    assert!(std::mem::size_of::<ProgTestRunAttr>() == 12 * 4 + 4 * 8);
    assert!(std::mem::size_of::<LinkCreateAttr>() == 4 * 4);
};

/// `bpf_attr` for `BPF_LINK_CREATE` against an XDP target.
#[repr(C)]
#[derive(Default)]
pub struct LinkCreateAttr {
    pub prog_fd: u32,
    pub target_ifindex: u32,
    pub attach_type: u32,
    pub flags: u32,
}

/// `bpf(2)` with a `#[repr(C)]` attr struct, sized automatically.
///
/// # Safety
/// `attr` must be the struct `cmd` expects, with valid pointers inside it.
pub unsafe fn bpf_cmd<T>(cmd: i32, attr: &mut T) -> Result<i32> {
    // SAFETY: `attr` is a live `&mut T`, so the pointer and size are valid;
    // the caller guarantees `T` is the struct `cmd` expects.
    unsafe { crate::syscall::bpf(cmd, attr as *mut T as *mut u8, std::mem::size_of::<T>()) }
}

/// Wrap an errno with context, since a bare `EINVAL` from `bpf(2)` is close to
/// useless when debugging.
pub fn ctx_err(what: &str, e: io::Error) -> io::Error {
    io::Error::new(e.kind(), format!("xdp: {what}: {e}"))
}