agave-xdp 4.2.1

Agave XDP implementation
Documentation
#![cfg(target_os = "linux")]
#![allow(dead_code)]

pub use aya::test_helpers::NetNsGuard;
use {
    agave_xdp::netlink::MacAddress,
    std::{
        ffi::{CString, OsString},
        os::unix::ffi::OsStringExt,
        path::{Path, PathBuf},
        process::{Command, Output},
        sync::OnceLock,
        thread,
        time::{Duration, Instant},
    },
};

pub const LEFT_IFACE: &str = "axdp0";
pub const RIGHT_IFACE: &str = "axdp1";
pub const GRE_IFACE: &str = "gxdp0";

#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct TestLinks {
    pub left_if_index: u32,
    pub right_if_index: u32,
    pub left_ip: std::net::Ipv4Addr,
    pub right_ip: std::net::Ipv4Addr,
    pub left_mac: MacAddress,
    pub right_mac: MacAddress,
}

#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct TestGreTunnel {
    pub if_index: u32,
    pub local_ip: std::net::Ipv4Addr,
    pub remote_ip: std::net::Ipv4Addr,
    pub overlay_ip: std::net::Ipv4Addr,
}

pub fn setup_veth_pair() -> TestLinks {
    let left_ip = std::net::Ipv4Addr::new(10, 0, 0, 1);
    let right_ip = std::net::Ipv4Addr::new(10, 0, 0, 2);
    let left_mac = MacAddress([0x02, 0xaa, 0xbb, 0xcc, 0xdd, 0x01]);
    let right_mac = MacAddress([0x02, 0xaa, 0xbb, 0xcc, 0xdd, 0x02]);

    run_ip(&[
        "link",
        "add",
        LEFT_IFACE,
        "type",
        "veth",
        "peer",
        "name",
        RIGHT_IFACE,
    ]);
    set_link_mac(LEFT_IFACE, &left_mac.to_string());
    set_link_mac(RIGHT_IFACE, &right_mac.to_string());
    add_ipv4_addr(&format!("{left_ip}/24"), LEFT_IFACE);
    add_ipv4_addr(&format!("{right_ip}/24"), RIGHT_IFACE);
    set_link_up(LEFT_IFACE);
    set_link_up(RIGHT_IFACE);

    TestLinks {
        left_if_index: if_index(LEFT_IFACE),
        right_if_index: if_index(RIGHT_IFACE),
        left_ip,
        right_ip,
        left_mac,
        right_mac,
    }
}

pub fn setup_gre_tunnel(underlay: &TestLinks) -> TestGreTunnel {
    let local = underlay.left_ip.to_string();
    let remote = underlay.right_ip.to_string();
    let overlay_ip = std::net::Ipv4Addr::new(192, 0, 2, 1);

    run_ip(&[
        "tunnel", "add", GRE_IFACE, "mode", "gre", "local", &local, "remote", &remote, "ttl", "64",
    ]);
    add_ipv4_addr(&format!("{overlay_ip}/32"), GRE_IFACE);
    set_link_up(GRE_IFACE);

    TestGreTunnel {
        if_index: if_index(GRE_IFACE),
        local_ip: underlay.left_ip,
        remote_ip: underlay.right_ip,
        overlay_ip,
    }
}

pub fn add_route(destination: &str, via: std::net::Ipv4Addr, dev: &str) {
    let via = via.to_string();
    run_ip(&["route", "replace", destination, "via", &via, "dev", dev]);
}

pub fn add_route_to_dev(destination: &str, dev: &str) {
    run_ip(&["route", "replace", destination, "dev", dev]);
}

pub fn add_route_to_dev_with_src(destination: &str, dev: &str, src: std::net::Ipv4Addr) {
    let src = src.to_string();
    run_ip(&["route", "replace", destination, "dev", dev, "src", &src]);
}

pub fn delete_link(dev: &str) {
    run_ip(&["link", "del", dev]);
}

#[allow(dead_code)]
pub fn delete_route(destination: &str) {
    run_ip(&["route", "del", destination]);
}

pub fn replace_neighbor(ip: std::net::Ipv4Addr, mac: MacAddress, dev: &str) {
    let ip = ip.to_string();
    let mac = mac.to_string();
    run_ip(&[
        "neigh",
        "replace",
        &ip,
        "lladdr",
        &mac,
        "dev",
        dev,
        "nud",
        "permanent",
    ]);
}

pub fn delete_neighbor(ip: std::net::Ipv4Addr, dev: &str) {
    let ip = ip.to_string();
    run_ip(&["neigh", "del", &ip, "dev", dev]);
}

#[allow(dead_code)]
pub fn wait_until<T, F>(description: &str, timeout: Duration, mut predicate: F) -> T
where
    F: FnMut() -> Option<T>,
{
    let start = Instant::now();
    loop {
        if let Some(value) = predicate() {
            return value;
        }

        if start.elapsed() >= timeout {
            panic!("timed out waiting for {description}");
        }

        thread::sleep(Duration::from_millis(10));
    }
}

fn set_link_mac(dev: &str, mac: &str) {
    run_ip(&["link", "set", "dev", dev, "address", mac]);
}

fn set_link_up(dev: &str) {
    run_ip(&["link", "set", "dev", dev, "up"]);
}

fn add_ipv4_addr(addr: &str, dev: &str) {
    run_ip(&["addr", "add", addr, "dev", dev]);
}

pub fn if_index(dev: &str) -> u32 {
    let dev = CString::new(dev).expect("interface name must not contain NUL");
    let index = unsafe { libc::if_nametoindex(dev.as_ptr()) };
    assert_ne!(index, 0, "failed to resolve ifindex for interface");
    index
}

fn run_ip(args: &[&str]) {
    run_command(ip_command(), args);
}

fn run_command(program: &Path, args: &[&str]) {
    let Output {
        status,
        stdout,
        stderr,
    } = Command::new(program)
        .args(args)
        .output()
        .unwrap_or_else(|err| panic!("failed to run {program:?} {args:?}: {err}"));
    if status.success() {
        return;
    }

    panic!(
        "{program:?} {args:?} failed: {}
stdout:
{}
stderr:
{}",
        status,
        OsString::from_vec(stdout).display(),
        OsString::from_vec(stderr).display(),
    );
}

fn ip_command() -> &'static PathBuf {
    static IP_COMMAND: OnceLock<PathBuf> = OnceLock::new();
    IP_COMMAND.get_or_init(|| {
        let mut candidates = std::env::var_os("IP")
            .into_iter()
            .map(PathBuf::from)
            .chain([
                PathBuf::from("/usr/sbin/ip"),
                PathBuf::from("/sbin/ip"),
                PathBuf::from("ip"),
            ]);

        candidates
            .find(|path| path == Path::new("ip") || path.exists())
            .unwrap_or_else(|| PathBuf::from("ip"))
    })
}