ferranet 0.2.0

A modern, async-first, zero-copy datalink-layer (L2) networking library
Documentation
//! Shared helpers for the integration test suites (`veth` and `hw`).

#![allow(dead_code)] // each test binary uses a different subset.

use std::os::fd::{AsRawFd, BorrowedFd};
use std::process::{Command, Stdio};

/// Runs an external command, suppressing its output, returning whether it succeeded.
pub fn run(cmd: &str, args: &[&str]) -> bool {
    Command::new(cmd)
        .args(args)
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .status()
        .map(|s| s.success())
        .unwrap_or(false)
}

/// Blocks until `fd` is readable or `ms` elapse; returns whether it became readable.
pub fn wait_readable(fd: BorrowedFd<'_>, ms: i32) -> bool {
    let mut pfd = libc::pollfd { fd: fd.as_raw_fd(), events: libc::POLLIN, revents: 0 };
    // SAFETY: a single valid pollfd is passed with count 1.
    let rc = unsafe { libc::poll(&mut pfd, 1, ms) };
    rc > 0 && pfd.revents & libc::POLLIN != 0
}

/// Builds a test frame: broadcast destination, a fixed source, an experimental EtherType, and the
/// given payload.
pub fn make_frame(payload: &[u8]) -> Vec<u8> {
    let mut frame = Vec::new();
    frame.extend_from_slice(&[0xff; 6]); // dst: broadcast
    frame.extend_from_slice(&[0x02, 0x00, 0x00, 0x00, 0x00, 0x01]); // src
    frame.extend_from_slice(&[0x88, 0xb5]); // IEEE local experimental EtherType
    frame.extend_from_slice(payload);
    frame
}

/// Builds an 802.1Q VLAN-tagged frame carrying `payload` on VLAN `vid`.
pub fn make_vlan_frame(vid: u16, payload: &[u8]) -> Vec<u8> {
    let mut frame = Vec::new();
    frame.extend_from_slice(&[0xff; 6]); // dst: broadcast
    frame.extend_from_slice(&[0x02, 0x00, 0x00, 0x00, 0x00, 0x01]); // src
    frame.extend_from_slice(&[0x81, 0x00]); // TPID 0x8100 (802.1Q)
    frame.extend_from_slice(&(vid & 0x0fff).to_be_bytes()); // TCI: PCP 0, VID
    frame.extend_from_slice(&[0x88, 0xb5]); // inner EtherType
    frame.extend_from_slice(payload);
    frame
}

/// Returns `true` if `haystack` contains `needle` as a contiguous subsequence.
pub fn contains(haystack: &[u8], needle: &[u8]) -> bool {
    needle.len() <= haystack.len() && haystack.windows(needle.len()).any(|w| w == needle)
}

/// The interface pair for hardware tests, from `$FERRANET_HW_IFACE_A` / `$FERRANET_HW_IFACE_B`.
///
/// Returns `None` (so tests skip) unless both are set. For a self-contained run, point them at two
/// cabled ports (each ideally in its own netns) or use the same port in NIC loopback mode.
pub fn hw_ifaces() -> Option<(String, String)> {
    let a = std::env::var("FERRANET_HW_IFACE_A").ok()?;
    let b = std::env::var("FERRANET_HW_IFACE_B").ok()?;
    Some((a, b))
}