baryl 0.0.2

Public SDK for Baryl, a full-system emulation and introspection engine
//! The guest's network, from the host side of it.
//!
//! Reached as `ctl.subs.net`. Two levels, and you pick one:
//!
//! - **Sockets.** [`NetRef::tcp_connect`], [`NetRef::tcp_listen`],
//!   [`NetRef::udp_open`] and the calls around them. A TCP stack on the host
//!   side owns the handshake, the retransmissions and the buffering, and you
//!   send and receive bytes. This is how you talk *to* something running in the
//!   guest.
//! - **Frames.** [`NetRef::dequeue_guest_tx`] and
//!   [`NetRef::enqueue_guest_rx`], which take and place whole wire-ready
//!   Ethernet frames under that stack. This is how you relay traffic somewhere
//!   else, or fuzz a driver with frames no stack would have produced.
//!
//! An address is a `u32`, four octets read big-endian; a MAC is six bytes.
//! Nothing here blocks — bytes leave on the next poll — and nothing parsed
//! crosses, so which stack is behind these calls is not your concern and a
//! change to it does not recompile you.
//!
//! Every call is safe to make on a machine with no network at all: an unbound
//! handle answers 0, an empty result, or a socket that never opened.

// Bindgen output cannot satisfy the workspace lints; the allow stops here.
mod generated {
    #![allow(non_camel_case_types, non_upper_case_globals, dead_code)]
    include!("generated.rs");
}
pub use generated::*;

/// A frame event: your state, the `Control`, and the frame's bytes, borrowed
/// for the call. `#[net(guest_tx)]` and `#[net(guest_rx)]` write one for you.
///
/// The frame is `const`. To change what the guest sees, enqueue the frame you
/// want with [`NetRef::enqueue_guest_rx`]; there is no editing this one.
pub type NetFrameCb =
    unsafe extern "C" fn(*mut core::ffi::c_void, *mut core::ffi::c_void, *const u8, u64);

/// The tick: your state, the `Control`, and the guest's own clock in
/// nanoseconds. Every timer in this subsystem runs on that clock, not the
/// host's. `#[net(poll)]` writes one for you.
pub type NetPollCb = unsafe extern "C" fn(*mut core::ffi::c_void, *mut core::ffi::c_void, u64);

/// A socket held on the host side of the guest's network.
///
/// `Copy` and one word wide: a table index and the generation it was handed out
/// at. Keep one past its close and the generation no longer matches, so every
/// call on it is refused rather than reaching whoever holds the slot next.
///
/// Survives a checkpoint. A socket taken before a save is the same socket after
/// the restore.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct NetSocket(u32);

impl NetSocket {
    /// No socket. What a refused open answers, and what every call treats as a
    /// no-op — so an open that failed needs no branch before the code that
    /// would have used it.
    pub const NONE: NetSocket = NetSocket(BARYL_NET_SOCKET_NONE);

    /// Whether this is a socket at all, as against [`NONE`](Self::NONE). Says
    /// nothing about what state it is in — see [`NetRef::tcp_state`].
    pub fn is_open(self) -> bool {
        self != NetSocket::NONE
    }
}

/// What a TCP socket is doing, in the five states worth telling apart.
///
/// The stack's own state machine has more; these are the answers to "is it up,
/// is it still coming up, is it waiting for someone, is it going away, is it
/// gone".
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum TcpState {
    /// Gone, or never was. Also what a stale handle and a machine with no
    /// network answer.
    Closed,
    /// Waiting for the guest to connect.
    Listening,
    /// The handshake is in flight; not yet writable.
    Opening,
    /// Up. Send and receive.
    Established,
    /// Shutting down; the stack is finishing it.
    Closing,
}

impl TcpState {
    /// A state word this build does not recognize reads as `Closed`, which is
    /// also what an unbound handle answers.
    fn from_word(state: u32) -> TcpState {
        match state {
            BARYL_TCP_LISTENING => TcpState::Listening,
            BARYL_TCP_OPENING => TcpState::Opening,
            BARYL_TCP_ESTABLISHED => TcpState::Established,
            BARYL_TCP_CLOSING => TcpState::Closing,
            _ => TcpState::Closed,
        }
    }
}

impl NetVtable {
    /// A table with no calls in it. Start from this when filling one in, so a
    /// call you did not implement reads as absent rather than as garbage.
    pub const ABSENT: NetVtable = NetVtable {
        guest_ip: None,
        guest_mac: None,
        dequeue_guest_tx: None,
        enqueue_guest_rx: None,
        tcp_connect: None,
        tcp_listen: None,
        tcp_state: None,
        tcp_send: None,
        tcp_receive: None,
        tcp_remote_endpoint: None,
        tcp_close: None,
        udp_open: None,
        udp_send_to: None,
        udp_receive_from: None,
        udp_close: None,
        take_rx_ring_dropped: None,
        take_tx_ring_dropped: None,
    };
}

/// Every call the net subsystem answers.
///
/// # Examples
///
/// ```ignore
/// // Connect to a service in the guest and read its banner.
/// #[core(first_ring_three)]
/// fn probe(&mut self, t: &mut Control) {
///     let net = &t.subs.net;
///     self.sock = net.tcp_connect(net.guest_ip(), 22, 40000);
/// }
///
/// #[net(poll)]
/// fn poll(&mut self, t: &mut Control, _virtual_ns: u64) {
///     if t.subs.net.tcp_state(self.sock) != TcpState::Established {
///         return;   // still opening, or never opened
///     }
///     let mut buf = [0u8; 256];
///     if let Some(bytes) = t.subs.net.tcp_receive(self.sock, &mut buf) {
///         baryl::logging::info!("banner: {:?}", core::str::from_utf8(bytes));
///         t.subs.net.tcp_close(self.sock);
///     }
/// }
/// ```
impl NetRef {
    /// The vtable, or `None` when nothing is bound to this handle.
    fn table(&self) -> Option<&NetVtable> {
        // SAFETY: `vtable` is the net `.so`'s static table, valid for the process.
        unsafe { self.vtable.as_ref() }
    }

    /// The guest's own address on its local network, four octets big-endian.
    /// 0 on a machine with no network.
    pub fn guest_ip(&self) -> u32 {
        self.table()
            .and_then(|v| v.guest_ip)
            .map_or(0, |f| unsafe { f(*self) })
    }

    /// The MAC the guest's NIC filters on — the destination for a frame you
    /// build yourself. `None` before the NIC is up, and on a machine with no
    /// network.
    pub fn guest_mac(&self) -> Option<[u8; 6]> {
        let f = self.table().and_then(|v| v.guest_mac)?;
        let mut out = [0u8; 6];
        // SAFETY: `out` is this frame's own six bytes, which is what the slot takes.
        (unsafe { f(*self, out.as_mut_ptr()) } == 0).then_some(out)
    }

    /// Take one frame the guest sent, out from under the stack.
    ///
    /// **The frame you take here is one the stack will not see.** That is the
    /// point — it is how a relay carries traffic somewhere else — but it means
    /// draining the ring unconditionally cuts the guest off from the host
    /// stack entirely. Call it from `#[net(guest_tx)]`, which fires before the
    /// stack reads the ring, and take only the frames you mean to.
    ///
    /// `None` once the ring is empty, and for a frame `out` was too small to
    /// hold.
    pub fn dequeue_guest_tx<'a>(&self, out: &'a mut [u8]) -> Option<&'a [u8]> {
        let f = self.table().and_then(|v| v.dequeue_guest_tx)?;
        // SAFETY: `out` bounds the write, and its length is what the slot is told.
        let n = unsafe { f(*self, out.as_mut_ptr(), out.len() as u64) } as usize;
        (n != 0).then(|| &out[..n])
    }

    /// Put one wire-ready Ethernet frame on the guest's receive ring, verbatim.
    ///
    /// Nothing checks it. A malformed frame, a bad checksum, a length that
    /// disagrees with the header — all of it reaches the guest's driver exactly
    /// as given.
    ///
    /// Silently dropped if the ring is full; watch
    /// [`take_rx_ring_dropped`](Self::take_rx_ring_dropped) to see that
    /// happening.
    pub fn enqueue_guest_rx(&self, frame: &[u8]) {
        if let Some(f) = self.table().and_then(|v| v.enqueue_guest_rx) {
            unsafe { f(*self, frame.as_ptr(), frame.len() as u64) };
        }
    }

    /// Open a connection to something listening in the guest.
    ///
    /// Returns immediately with a socket in [`TcpState::Opening`] — the stack
    /// owns the handshake and its retransmissions, so poll
    /// [`tcp_state`](Self::tcp_state) until it reaches
    /// [`Established`](TcpState::Established) before sending.
    ///
    /// [`NetSocket::NONE`] when the open was refused, and on a machine with no
    /// network.
    pub fn tcp_connect(&self, remote_ip: u32, remote_port: u16, local_port: u16) -> NetSocket {
        self.table()
            .and_then(|v| v.tcp_connect)
            .map_or(NetSocket::NONE, |f| {
                NetSocket(unsafe { f(*self, remote_ip, remote_port, local_port) })
            })
    }

    /// Wait for the guest to connect to `local_ip:local_port`.
    ///
    /// The socket sits in [`TcpState::Listening`] until it does, then moves to
    /// [`Established`](TcpState::Established). A guest connecting to an address
    /// nothing is listening on is refused outright, so it fails fast rather
    /// than retransmitting into silence.
    ///
    /// [`NetSocket::NONE`] on a machine with no network.
    pub fn tcp_listen(&self, local_ip: u32, local_port: u16) -> NetSocket {
        self.table()
            .and_then(|v| v.tcp_listen)
            .map_or(NetSocket::NONE, |f| {
                NetSocket(unsafe { f(*self, local_ip, local_port) })
            })
    }

    /// What this socket is doing. [`TcpState::Closed`] for a stale handle,
    /// [`NetSocket::NONE`], and a machine with no network.
    pub fn tcp_state(&self, s: NetSocket) -> TcpState {
        self.table()
            .and_then(|v| v.tcp_state)
            .map_or(TcpState::Closed, |f| TcpState::from_word(unsafe { f(*self, s.0) }))
    }

    /// Hand `buf` to the socket, and answer how much of it was taken.
    ///
    /// **Short writes are normal.** The answer is under `buf.len()` whenever
    /// the send buffer has less room than that, and 0 on a socket that is not
    /// established. Nothing blocks and nothing queues on your behalf — resend
    /// the remainder on a later poll.
    pub fn tcp_send(&self, s: NetSocket, buf: &[u8]) -> usize {
        self.table().and_then(|v| v.tcp_send).map_or(0, |f| unsafe {
            f(*self, s.0, buf.as_ptr(), buf.len() as u64) as usize
        })
    }

    /// Take whatever the socket has received into `out`, and answer the part of
    /// it that was filled.
    ///
    /// A stream, not messages: what arrives in one call is however much had
    /// turned up, which bears no relation to how the other end wrote it.
    /// `None` when nothing had arrived — that is the ordinary case on most
    /// polls, not an error.
    pub fn tcp_receive<'a>(&self, s: NetSocket, out: &'a mut [u8]) -> Option<&'a [u8]> {
        let f = self.table().and_then(|v| v.tcp_receive)?;
        // SAFETY: `out` bounds the write, and its length is what the slot is told.
        let n = unsafe { f(*self, s.0, out.as_mut_ptr(), out.len() as u64) } as usize;
        (n != 0).then(|| &out[..n])
    }

    /// The address and port at the other end — which is how a listening socket
    /// finds out who connected to it.
    ///
    /// `None` on a socket that has no far end yet: one still listening, one
    /// still opening, a stale handle.
    pub fn tcp_remote_endpoint(&self, s: NetSocket) -> Option<NetEndpoint> {
        let f = self.table().and_then(|v| v.tcp_remote_endpoint)?;
        let mut out = NetEndpoint::default();
        // SAFETY: `out` is this frame's own struct, which is what the slot fills.
        (unsafe { f(*self, s.0, &raw mut out) } == 0).then_some(out)
    }

    /// Close the sending half and let the stack finish the shutdown in its own
    /// time.
    ///
    /// The handle stops resolving immediately — every later call on it is a
    /// no-op — so drop it here rather than polling for the close to complete.
    pub fn tcp_close(&self, s: NetSocket) {
        if let Some(f) = self.table().and_then(|v| v.tcp_close) {
            unsafe { f(*self, s.0) };
        }
    }

    /// Bind a datagram socket at `local_ip:local_port` on the guest's network.
    ///
    /// [`NetSocket::NONE`] when the bind was refused, and on a machine with no
    /// network.
    pub fn udp_open(&self, local_ip: u32, local_port: u16) -> NetSocket {
        self.table()
            .and_then(|v| v.udp_open)
            .map_or(NetSocket::NONE, |f| {
                NetSocket(unsafe { f(*self, local_ip, local_port) })
            })
    }

    /// Send one datagram to `remote_ip:remote_port`.
    ///
    /// All or nothing, unlike [`tcp_send`](Self::tcp_send): the answer is
    /// `payload.len()` or 0, never a part of it.
    pub fn udp_send_to(
        &self,
        s: NetSocket,
        remote_ip: u32,
        remote_port: u16,
        payload: &[u8],
    ) -> usize {
        self.table()
            .and_then(|v| v.udp_send_to)
            .map_or(0, |f| unsafe {
                f(
                    *self,
                    s.0,
                    remote_ip,
                    remote_port,
                    payload.as_ptr(),
                    payload.len() as u64,
                ) as usize
            })
    }

    /// One datagram and the address it came from, or `None` when none had
    /// arrived.
    ///
    /// One datagram per call, never two run together — the `from` it answers
    /// belongs to the bytes it answers.
    pub fn udp_receive_from<'a>(
        &self,
        s: NetSocket,
        out: &'a mut [u8],
    ) -> Option<(NetEndpoint, &'a [u8])> {
        let f = self.table().and_then(|v| v.udp_receive_from)?;
        let mut from = NetEndpoint::default();
        // SAFETY: `out` bounds the write, and `from` is this frame's own struct.
        let n =
            unsafe { f(*self, s.0, &raw mut from, out.as_mut_ptr(), out.len() as u64) } as usize;
        (n != 0).then(|| (from, &out[..n]))
    }

    /// Give up the datagram socket. The handle stops resolving immediately.
    pub fn udp_close(&self, s: NetSocket) {
        if let Some(f) = self.table().and_then(|v| v.udp_close) {
            unsafe { f(*self, s.0) };
        }
    }

    /// How many host→guest frames have been dropped since this was last asked
    /// — and clears the count, so each drop is reported once.
    ///
    /// A non-zero answer means the guest is not draining its receive ring as
    /// fast as you are filling it, and frames you enqueued never reached it.
    pub fn take_rx_ring_dropped(&self) -> u64 {
        self.table()
            .and_then(|v| v.take_rx_ring_dropped)
            .map_or(0, |f| unsafe { f(*self) })
    }

    /// The same for guest→host: frames the guest sent that were lost because
    /// the ring was full or the frame was oversized. Read and cleared.
    pub fn take_tx_ring_dropped(&self) -> u64 {
        self.table()
            .and_then(|v| v.take_tx_ring_dropped)
            .map_or(0, |f| unsafe { f(*self) })
    }
}