Menu
Public repository / 7f6c3458d5c2eef04d0906dafd2688e8eba775cb
AkurAI-VPN / crates/akurai-relay/src/forward.rs
Latest change cc029b11b1de07576e9017b960da31d9143666d4 - feat(dataplane): working encrypted overlay mesh (akurai-sys TUN + node pump + relay) by Claude Code
//! The relay forward loop — a ciphertext-only hub.
//!
//! **Invariant: the relay never decrypts anything.** It reads only the outer
//! [`akurai_common::Frame`] envelope (magic, type, source/destination overlay
//! IP) and copies the datagram to the destination's last-known UDP endpoint. It
//! holds no payload keys and links no crypto crate.
//!
//! Endpoint learning: every frame teaches the relay that its `src` overlay IP is
//! currently reachable at the UDP address it arrived from (so a roaming node's
//! mapping updates on its next keepalive). A `Data`/handshake frame for a
//! destination the relay has not yet learned is dropped — there is nowhere to
//! send it and the relay never buffers unbounded state.
use std::collections::HashMap;
use std::io;
use std::net::{Ipv4Addr, SocketAddr, UdpSocket};
use std::sync::atomic::{AtomicU64, Ordering};
use akurai_common::frame::{Frame, FrameKind};
/// Default relay UDP port (overridable via `RELAY_PORT`).
const DEFAULT_PORT: u16 = 51820;
/// Forwarding counters, for operational visibility (and the e2e test's proof
/// that traffic actually traversed the hub).
#[derive(Default)]
pub struct Stats {
pub learned: AtomicU64,
pub forwarded: AtomicU64,
pub dropped_unknown: AtomicU64,
pub malformed: AtomicU64,
}
/// Run the relay forward loop on `RELAY_PORT` (default 51820), forever.
pub fn run() -> io::Result<()> {
let port: u16 = std::env::var("RELAY_PORT")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(DEFAULT_PORT);
let sock = UdpSocket::bind(("0.0.0.0", port))?;
eprintln!("akurai-relay: ciphertext-only hub on 0.0.0.0:{port}");
let mut table: HashMap<Ipv4Addr, SocketAddr> = HashMap::new();
let stats = Stats::default();
let mut buf = [0u8; 4096];
loop {
let (n, from) = match sock.recv_from(&mut buf) {
Ok(v) => v,
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
};
forward_one(&sock, &mut table, &stats, &buf[..n], from);
}
}
/// Process a single received datagram: learn the source endpoint, then forward
/// to the destination (or drop). Factored out so it is unit-testable without a
/// live socket loop. Returns the action taken.
fn forward_one(
sock: &UdpSocket,
table: &mut HashMap<Ipv4Addr, SocketAddr>,
stats: &Stats,
datagram: &[u8],
from: SocketAddr,
) -> Action {
let Some(frame) = Frame::decode(datagram) else {
stats.malformed.fetch_add(1, Ordering::Relaxed);
return Action::Malformed;
};
// Learn: this source overlay IP is reachable at `from` (roaming updates here).
if !frame.src.is_unspecified() && table.insert(frame.src, from) != Some(from) {
stats.learned.fetch_add(1, Ordering::Relaxed);
}
// Keepalives only teach the relay; they are not forwarded.
if frame.kind == FrameKind::Keepalive {
return Action::Learned(frame.src);
}
// Forward to the destination's last-known endpoint, if known.
match table.get(&frame.dest).copied() {
Some(dest_addr) => {
let _ = sock.send_to(datagram, dest_addr);
stats.forwarded.fetch_add(1, Ordering::Relaxed);
Action::Forwarded {
dest: frame.dest,
to: dest_addr,
}
}
None => {
stats.dropped_unknown.fetch_add(1, Ordering::Relaxed);
Action::DroppedUnknown(frame.dest)
}
}
}
/// What `forward_one` did with a datagram (for tests + tracing).
#[derive(Debug, PartialEq, Eq)]
enum Action {
Malformed,
Learned(Ipv4Addr),
Forwarded { dest: Ipv4Addr, to: SocketAddr },
DroppedUnknown(Ipv4Addr),
}
#[cfg(test)]
mod tests {
use super::*;
fn ip(d: u8) -> Ipv4Addr {
Ipv4Addr::new(100, 88, 0, d)
}
fn addr(p: u16) -> SocketAddr {
SocketAddr::from(([127, 0, 0, 1], p))
}
// A throwaway bound socket; send_to to a dropped port is harmless for tests.
fn sock() -> UdpSocket {
UdpSocket::bind(("127.0.0.1", 0)).unwrap()
}
#[test]
fn learns_endpoint_from_keepalive_and_does_not_forward_it() {
let s = sock();
let mut t = HashMap::new();
let st = Stats::default();
let ka = Frame::new(FrameKind::Keepalive, ip(2), Ipv4Addr::UNSPECIFIED, vec![])
.unwrap()
.encode();
let action = forward_one(&s, &mut t, &st, &ka, addr(5000));
assert_eq!(action, Action::Learned(ip(2)));
assert_eq!(t.get(&ip(2)), Some(&addr(5000)));
assert_eq!(st.learned.load(Ordering::Relaxed), 1);
}
#[test]
fn forwards_data_to_a_learned_destination() {
let s = sock();
let mut t = HashMap::new();
let st = Stats::default();
// B (100.88.0.3) is known at port 6000.
t.insert(ip(3), addr(6000));
// A→B data frame arriving from A's socket.
let data = Frame::new(FrameKind::Data, ip(2), ip(3), vec![0xAB; 16])
.unwrap()
.encode();
let action = forward_one(&s, &mut t, &st, &data, addr(5000));
assert_eq!(
action,
Action::Forwarded {
dest: ip(3),
to: addr(6000)
}
);
// Forwarding also learned A's endpoint.
assert_eq!(t.get(&ip(2)), Some(&addr(5000)));
assert_eq!(st.forwarded.load(Ordering::Relaxed), 1);
}
#[test]
fn drops_data_for_an_unknown_destination() {
let s = sock();
let mut t = HashMap::new();
let st = Stats::default();
let data = Frame::new(FrameKind::Data, ip(2), ip(9), vec![0; 8])
.unwrap()
.encode();
assert_eq!(
forward_one(&s, &mut t, &st, &data, addr(5000)),
Action::DroppedUnknown(ip(9))
);
assert_eq!(st.dropped_unknown.load(Ordering::Relaxed), 1);
}
#[test]
fn roaming_updates_the_endpoint() {
let s = sock();
let mut t = HashMap::new();
let st = Stats::default();
let ka1 = Frame::new(FrameKind::Keepalive, ip(2), Ipv4Addr::UNSPECIFIED, vec![])
.unwrap()
.encode();
forward_one(&s, &mut t, &st, &ka1, addr(5000));
forward_one(&s, &mut t, &st, &ka1, addr(7777)); // same node, new address
assert_eq!(t.get(&ip(2)), Some(&addr(7777)));
}
#[test]
fn rejects_malformed_datagram() {
let s = sock();
let mut t = HashMap::new();
let st = Stats::default();
assert_eq!(
forward_one(&s, &mut t, &st, b"not a frame", addr(5000)),
Action::Malformed
);
assert_eq!(st.malformed.load(Ordering::Relaxed), 1);
}
}