Menu
Public repository / 4c7bc641cb39d5136d09721a545df0be928891bf
AkurAI-VPN / crates/akurai-node/src/tunnel.rs
Latest change cc029b11b1de07576e9017b960da31d9143666d4 - feat(dataplane): working encrypted overlay mesh (akurai-sys TUN + node pump + relay) by Claude Code
//! The data plane — a hub-routed encrypted overlay pump.
//!
//! `run` opens the `akurai0` TUN, assigns the node's overlay IP and a
//! `100.88.0.0/16`-ONLY route (never a default route — the host's internet path
//! is inviolable), connects a UDP socket to the relay, and runs two blocking
//! pumps that share a per-peer session table:
//!
//! - **TUN → UDP**: read an IPv4 packet, look up the destination peer, encrypt
//! with its Noise session (initiating a handshake if none exists), and send a
//! `Data` frame to the relay, which forwards it to the peer.
//! - **UDP → TUN**: receive a relay frame; complete handshakes (`HandshakeInit`
//! / `HandshakeResp`) or decrypt a `Data` frame and write the inner packet to
//! the TUN.
//!
//! End-to-end: the relay only ever forwards ciphertext. Fail-closed: a packet to
//! an unknown destination, or from an unverified peer, is dropped.
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::fs::File;
use std::io::{self, Read, Write};
use std::net::{Ipv4Addr, SocketAddr, UdpSocket};
use std::process::Command;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use akurai_common::frame::{Frame, FrameKind};
use akurai_transport::noise::{self, Initiator};
use akurai_transport::session::Session;
use akurai_transport::Keypair;
use crate::peers::PeerTable;
/// Runtime configuration for the data-plane daemon.
pub struct TunnelConfig {
pub iface: String,
pub overlay_ip: Ipv4Addr,
pub overlay_cidr: String,
pub mtu: u16,
pub relay: SocketAddr,
pub keypair: Keypair,
pub peers: PeerTable,
}
/// Per-peer Noise session state, shared between the two pump threads.
#[derive(Default)]
struct SessionTable {
/// Completed sessions keyed by the peer's overlay IP.
established: HashMap<Ipv4Addr, Session>,
/// Handshakes we initiated and are awaiting a response for.
pending: HashMap<Ipv4Addr, Initiator>,
}
/// 32 bytes of OS entropy for an ephemeral handshake secret.
fn rand32() -> io::Result<[u8; 32]> {
let mut b = [0u8; 32];
File::open("/dev/urandom")?.read_exact(&mut b)?;
Ok(b)
}
/// Bring up the overlay interface with the node's address and the overlay-only
/// route. Uses `ip` (safe `std::process::Command`); adds NO default route.
fn setup_interface(cfg: &TunnelConfig) -> io::Result<()> {
let ip = |args: &[&str]| -> io::Result<()> {
let status = Command::new("ip").args(args).status()?;
if !status.success() {
return Err(io::Error::other(format!("`ip {}` failed", args.join(" "))));
}
Ok(())
};
let addr = format!("{}/32", cfg.overlay_ip);
let mtu = cfg.mtu.to_string();
ip(&["addr", "add", &addr, "dev", &cfg.iface])?;
ip(&["link", "set", &cfg.iface, "mtu", &mtu])?;
ip(&["link", "set", &cfg.iface, "up"])?;
// Overlay-scoped route ONLY. Never 0.0.0.0/0.
ip(&["route", "add", &cfg.overlay_cidr, "dev", &cfg.iface])?;
Ok(())
}
/// Run the data plane. Opens the TUN, brings up the interface, connects to the
/// relay, and blocks running the two pumps until the process is killed.
pub fn run(cfg: TunnelConfig) -> io::Result<()> {
let tun = Arc::new(akurai_sys::create_tun(&cfg.iface)?);
setup_interface(&cfg)?;
let sock = Arc::new(UdpSocket::bind(("0.0.0.0", 0))?);
sock.connect(cfg.relay)?;
let sessions = Arc::new(Mutex::new(SessionTable::default()));
let peers = Arc::new(cfg.peers);
let kp = Arc::new(cfg.keypair);
let my_ip = cfg.overlay_ip;
// Teach the relay our endpoint, and keep it (and any NAT mapping) fresh.
let keepalive_sock = Arc::clone(&sock);
let _keepalive = thread::spawn(move || loop {
if let Some(f) = Frame::new(
FrameKind::Keepalive,
my_ip,
Ipv4Addr::UNSPECIFIED,
Vec::new(),
) {
let _ = keepalive_sock.send(&f.encode());
}
thread::sleep(Duration::from_secs(20));
});
// UDP → TUN pump (handshakes + decrypt).
let udp = {
let tun = Arc::clone(&tun);
let sock = Arc::clone(&sock);
let sessions = Arc::clone(&sessions);
let peers = Arc::clone(&peers);
let kp = Arc::clone(&kp);
thread::spawn(move || udp_pump(tun, sock, sessions, peers, kp, my_ip))
};
// TUN → UDP pump (encrypt + initiate). Runs on this thread; blocks forever.
tun_pump(tun, sock, sessions, peers, kp, my_ip)?;
let _ = udp.join();
Ok(())
}
/// Read packets from the TUN, encrypt to the destination peer (initiating a
/// handshake on first contact), and send `Data` frames to the relay.
fn tun_pump(
tun: Arc<File>,
sock: Arc<UdpSocket>,
sessions: Arc<Mutex<SessionTable>>,
peers: Arc<PeerTable>,
kp: Arc<Keypair>,
my_ip: Ipv4Addr,
) -> io::Result<()> {
let mut buf = [0u8; 2048];
loop {
let mut tunref: &File = &tun;
let n = match tunref.read(&mut buf) {
Ok(0) => return Ok(()),
Ok(n) => n,
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
};
let pkt = &buf[..n];
// IPv4 only (version nibble 4, ≥20-byte header).
if n < 20 || (pkt[0] >> 4) != 4 {
continue;
}
let dest = Ipv4Addr::new(pkt[16], pkt[17], pkt[18], pkt[19]);
let Some(peer) = peers.get(&dest) else {
continue; // fail-closed: unknown destination dropped
};
// Decide what to emit while holding the lock; send after releasing it.
let mut to_send: Option<Vec<u8>> = None;
{
let mut tbl = sessions.lock().unwrap();
if let Some(sess) = tbl.established.get_mut(&dest) {
let ct = sess.encrypt(pkt);
to_send = Frame::new(FrameKind::Data, my_ip, dest, ct).map(|f| f.encode());
} else if let Entry::Vacant(slot) = tbl.pending.entry(dest) {
// First contact: initiate a handshake and drop this packet (the
// upper layer — ICMP/TCP — retransmits once we have a session).
if let Ok(eph) = rand32() {
let (state, msg1) = noise::initiate(&kp, &peer.public_key, &eph);
slot.insert(state);
to_send =
Frame::new(FrameKind::HandshakeInit, my_ip, dest, msg1).map(|f| f.encode());
}
}
}
if let Some(bytes) = to_send {
let _ = sock.send(&bytes);
}
}
}
/// Receive relay frames: complete handshakes, or decrypt `Data` and write the
/// inner packet to the TUN. Survives malformed datagrams (logs nothing, continues).
fn udp_pump(
tun: Arc<File>,
sock: Arc<UdpSocket>,
sessions: Arc<Mutex<SessionTable>>,
peers: Arc<PeerTable>,
kp: Arc<Keypair>,
my_ip: Ipv4Addr,
) {
let mut buf = [0u8; 4096];
loop {
let n = match sock.recv(&mut buf) {
Ok(n) => n,
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(_) => return,
};
let Some(frame) = Frame::decode(&buf[..n]) else {
continue; // malformed datagram dropped
};
match frame.kind {
FrameKind::HandshakeInit => {
// A peer wants to reach us. Respond only if we can authenticate
// the initiator's static key against our peer table (fail-closed).
let Ok(eph) = rand32() else { continue };
if let Some((keys, msg2, init_pub)) = noise::respond(&kp, &eph, &frame.payload) {
match peers.get(&frame.src) {
Some(p) if p.public_key == init_pub => {
sessions
.lock()
.unwrap()
.established
.insert(frame.src, Session::new(keys));
if let Some(reply) =
Frame::new(FrameKind::HandshakeResp, my_ip, frame.src, msg2)
{
let _ = sock.send(&reply.encode());
}
}
_ => {} // unknown / mismatched peer: drop
}
}
}
FrameKind::HandshakeResp => {
let mut tbl = sessions.lock().unwrap();
if let Some(state) = tbl.pending.remove(&frame.src) {
if let Some(keys) = noise::finalize(state, &frame.payload) {
tbl.established.insert(frame.src, Session::new(keys));
}
}
}
FrameKind::Data => {
let mut tbl = sessions.lock().unwrap();
if let Some(sess) = tbl.established.get_mut(&frame.src) {
if let Some(pt) = sess.decrypt(&frame.payload) {
drop(tbl);
let mut tunref: &File = &tun;
let _ = tunref.write_all(&pt);
}
}
}
FrameKind::Keepalive => {} // nodes ignore; only the relay learns from these
}
}
}