use crate::seed::TunnelKind;
use crate::tunnel::TunnelError;
use std::io::{Read, Write};
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpStream};
use std::time::Duration;
pub const LINK_INSTALLER: Ipv4Addr = Ipv4Addr::new(10, 45, 0, 1);
pub const LINK_VM: Ipv4Addr = Ipv4Addr::new(10, 45, 0, 2);
pub const LINK_PREFIX: u8 = 30;
pub const LINK_PORT: u16 = 51821;
pub const LINK_IFACE: &str = "wg-installer";
pub const LINK_MTU: usize = 1380;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Reach {
Tunnel,
}
pub fn reach(kind: TunnelKind) -> Result<Reach, TunnelError> {
match kind {
TunnelKind::None => Err(TunnelError::InstallerLinkNone),
TunnelKind::Wireguard | TunnelKind::Mullvad => Ok(Reach::Tunnel),
TunnelKind::Tailscale => Err(TunnelError::Unimplemented {
kind: TunnelKind::Tailscale,
missing: "a one-time installer node on the tailnet (a pure-Rust Tailscale coordination client does not exist in the suite)",
}),
}
}
pub fn vm_bind(r: Reach) -> Ipv4Addr {
match r {
Reach::Tunnel => LINK_VM,
}
}
pub trait Wire: Read + Write {}
impl<T: Read + Write> Wire for T {}
pub trait Path: Send {
fn connect(&mut self, timeout: Duration) -> Result<Box<dyn Wire + '_>, String>;
fn gone(&mut self, wait: Duration) -> Result<bool, String>;
fn describe(&self) -> String;
}
pub struct DirectPath {
pub at: SocketAddrV4,
}
impl Path for DirectPath {
fn connect(&mut self, timeout: Duration) -> Result<Box<dyn Wire + '_>, String> {
let s = TcpStream::connect_timeout(&SocketAddr::V4(self.at), timeout).map_err(|e| format!("could not be reached: {}: {e}", self.at))?;
let _ = s.set_read_timeout(Some(timeout));
let _ = s.set_write_timeout(Some(timeout));
Ok(Box::new(s))
}
fn gone(&mut self, wait: Duration) -> Result<bool, String> {
let t0 = std::time::Instant::now();
while t0.elapsed() < wait {
if TcpStream::connect_timeout(&SocketAddr::V4(self.at), Duration::from_secs(2)).is_err() {
return Ok(true);
}
std::thread::sleep(Duration::from_millis(250));
}
Ok(false)
}
fn describe(&self) -> String {
format!("{} (direct mutual TLS)", self.at)
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct LinkKeys {
pub installer_private: [u8; 32],
pub vm_public: [u8; 32],
}
impl std::fmt::Debug for LinkKeys {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("LinkKeys { .. }")
}
}
#[cfg(feature = "installer-wireguard")]
pub use wg::WgPath;
#[cfg(feature = "installer-wireguard")]
mod wg {
use super::*;
use boringtun::noise::{Tunn, TunnResult};
use boringtun::x25519::{PublicKey, StaticSecret};
use smoltcp::iface::{Config, Interface, SocketHandle, SocketSet};
use smoltcp::phy::{Device, DeviceCapabilities, Medium};
use smoltcp::socket::tcp;
use smoltcp::wire::{HardwareAddress, IpAddress, IpCidr};
use std::collections::VecDeque;
use std::net::UdpSocket;
use std::time::Instant;
#[derive(Default)]
struct Queues {
rx: VecDeque<Vec<u8>>,
tx: VecDeque<Vec<u8>>,
}
struct Rx(Vec<u8>);
struct Tx<'a>(&'a mut VecDeque<Vec<u8>>);
impl smoltcp::phy::RxToken for Rx {
fn consume<R, F: FnOnce(&[u8]) -> R>(self, f: F) -> R {
f(&self.0)
}
}
impl smoltcp::phy::TxToken for Tx<'_> {
fn consume<R, F: FnOnce(&mut [u8]) -> R>(self, len: usize, f: F) -> R {
let mut buf = vec![0u8; len];
let r = f(&mut buf);
self.0.push_back(buf);
r
}
}
impl Device for Queues {
type RxToken<'a> = Rx;
type TxToken<'a> = Tx<'a>;
fn receive(&mut self, _t: smoltcp::time::Instant) -> Option<(Rx, Tx<'_>)> {
let p = self.rx.pop_front()?;
Some((Rx(p), Tx(&mut self.tx)))
}
fn transmit(&mut self, _t: smoltcp::time::Instant) -> Option<Tx<'_>> {
Some(Tx(&mut self.tx))
}
fn capabilities(&self) -> DeviceCapabilities {
let mut c = DeviceCapabilities::default();
c.medium = Medium::Ip;
c.max_transmission_unit = LINK_MTU;
c
}
}
struct Engine {
endpoint: SocketAddrV4,
udp: UdpSocket,
tunn: Tunn,
iface: Interface,
dev: Queues,
sockets: SocketSet<'static>,
epoch: Instant,
next_port: u16,
net: Vec<u8>,
}
type Shared = std::sync::Arc<std::sync::Mutex<Engine>>;
static LINKS: std::sync::Mutex<Vec<Link>> = std::sync::Mutex::new(Vec::new());
type Link = ((SocketAddrV4, [u8; 32], [u8; 32]), Shared, std::sync::Arc<Turn>);
#[derive(Default)]
struct Turn {
q: std::sync::Mutex<TurnQueue>,
moved: std::sync::Condvar,
}
#[derive(Default)]
struct TurnQueue {
next: u64,
serving: u64,
given_up: std::collections::BTreeSet<u64>,
}
struct Held(std::sync::Arc<Turn>);
impl Drop for Held {
fn drop(&mut self) {
let mut q = self.0.q.lock().unwrap_or_else(|p| p.into_inner());
q.serving += 1;
loop {
let at = q.serving;
if !q.given_up.remove(&at) {
break;
}
q.serving += 1;
}
self.0.moved.notify_all();
}
}
const TURN_WAIT: Duration = crate::bootstrap::REQUEST_BUDGET;
fn take_turn(turn: &std::sync::Arc<Turn>, within: Duration) -> Option<Held> {
let mut q = turn.q.lock().unwrap_or_else(|p| p.into_inner());
let mine = q.next;
q.next += 1;
let until = Instant::now() + within;
while q.serving != mine {
let left = until.saturating_duration_since(Instant::now());
if left.is_zero() {
q.given_up.insert(mine);
return None;
}
q = turn.moved.wait_timeout(q, left).unwrap_or_else(|p| p.into_inner()).0;
}
Some(Held(turn.clone()))
}
fn lock(e: &Shared) -> std::sync::MutexGuard<'_, Engine> {
e.lock().unwrap_or_else(|p| p.into_inner())
}
pub struct WgPath {
endpoint: SocketAddrV4,
keys: LinkKeys,
engine: Shared,
turn: std::sync::Arc<Turn>,
}
fn now(epoch: Instant) -> smoltcp::time::Instant {
smoltcp::time::Instant::from_millis(epoch.elapsed().as_millis() as i64)
}
fn tunn_of(keys: &LinkKeys, index: u32) -> Tunn {
Tunn::new(StaticSecret::from(keys.installer_private), PublicKey::from(keys.vm_public), None, Some(15), index, None)
}
impl Engine {
fn new(endpoint: SocketAddrV4, keys: &LinkKeys) -> Result<Engine, String> {
let udp = UdpSocket::bind("0.0.0.0:0").map_err(|e| format!("the installer's link socket: {e}"))?;
udp.connect(endpoint).map_err(|e| format!("the installer's link to {endpoint}: {e}"))?;
udp.set_read_timeout(Some(Duration::from_millis(5))).map_err(|e| e.to_string())?;
let epoch = Instant::now();
let mut dev = Queues::default();
let mut cfg = Config::new(HardwareAddress::Ip);
cfg.random_seed = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_nanos() as u64).unwrap_or(7);
let mut iface = Interface::new(cfg, &mut dev, now(epoch));
iface.update_ip_addrs(|a| {
let _ = a.push(IpCidr::new(IpAddress::Ipv4(LINK_INSTALLER), LINK_PREFIX));
});
let seed_port = 40000 + (cfg_port_seed() % 20000) as u16;
Ok(Engine { endpoint, udp, tunn: tunn_of(keys, 1), iface, dev, sockets: SocketSet::new(Vec::new()), epoch, next_port: seed_port, net: vec![0u8; 65536] })
}
fn pump(&mut self) {
if let TunnResult::WriteToNetwork(p) = self.tunn.update_timers(&mut self.net) {
let _ = self.udp.send(p);
}
let mut dgram = [0u8; 2048];
for _ in 0..64 {
let n = match self.udp.recv(&mut dgram) {
Ok(n) => n,
Err(_) => break,
};
let mut first = true;
loop {
let r = self.tunn.decapsulate(if first { Some(std::net::IpAddr::V4(*self.endpoint.ip())) } else { None }, if first { &dgram[..n] } else { &[] }, &mut self.net);
first = false;
match r {
TunnResult::WriteToNetwork(p) => {
let _ = self.udp.send(p);
}
TunnResult::WriteToTunnelV4(p, src) => {
if src == LINK_VM {
self.dev.rx.push_back(p.to_vec());
}
break;
}
_ => break,
}
}
}
let t = now(self.epoch);
self.iface.poll(t, &mut self.dev, &mut self.sockets);
while let Some(p) = self.dev.tx.pop_front() {
if let TunnResult::WriteToNetwork(out) = self.tunn.encapsulate(&p, &mut self.net) {
let _ = self.udp.send(out);
}
}
}
}
impl WgPath {
pub fn new(endpoint: SocketAddrV4, keys: LinkKeys) -> Result<WgPath, String> {
let id = (endpoint, keys.installer_private, keys.vm_public);
let mut links = LINKS.lock().unwrap_or_else(|p| p.into_inner());
let (engine, turn) = match links.iter().find(|(k, _, _)| *k == id) {
Some((_, e, t)) => (e.clone(), t.clone()),
None => {
let e: Shared = std::sync::Arc::new(std::sync::Mutex::new(Engine::new(endpoint, &keys)?));
let t = std::sync::Arc::new(Turn::default());
links.push((id, e.clone(), t.clone()));
(e, t)
}
};
Ok(WgPath { endpoint, keys, engine, turn })
}
fn try_connect(&mut self, timeout: Duration) -> Result<SocketHandle, String> {
let sock = tcp::Socket::new(tcp::SocketBuffer::new(vec![0u8; 65536]), tcp::SocketBuffer::new(vec![0u8; 65536]));
let h = {
let mut e = lock(&self.engine);
let h = e.sockets.add(sock);
e.next_port = if e.next_port >= 60000 { 40000 } else { e.next_port + 1 };
let port = e.next_port;
let Engine { iface, sockets, .. } = &mut *e;
let cx = iface.context();
let s = sockets.get_mut::<tcp::Socket>(h);
if let Err(err) = s.connect(cx, (IpAddress::Ipv4(LINK_VM), crate::bootstrap::PORT), port) {
sockets.remove(h);
return Err(format!("the link's TCP to {LINK_VM}: {err:?}"));
}
h
};
let t0 = Instant::now();
loop {
{
let mut e = lock(&self.engine);
e.pump();
let s = e.sockets.get_mut::<tcp::Socket>(h);
match s.state() {
tcp::State::Established => return Ok(h),
tcp::State::Closed | tcp::State::TimeWait => {
e.sockets.remove(h);
return Err(format!("could not be reached: the VM at {} refused the bootstrap listener inside the link", self.endpoint));
}
_ => {}
}
if t0.elapsed() > timeout {
e.sockets.get_mut::<tcp::Socket>(h).abort();
e.pump();
e.sockets.remove(h);
let hs = match e.tunn.stats().0 {
Some(ago) => format!("the link handshook {} s ago", ago.as_secs()),
None => "no WireGuard handshake yet".to_string(),
};
return Err(format!("could not be reached: {} over the installer's link ({hs})", self.endpoint));
}
}
std::thread::yield_now();
}
}
fn fresh_session(&self) {
let mut e = lock(&self.engine);
let Engine { tunn, udp, net, .. } = &mut *e;
if let TunnResult::WriteToNetwork(p) = tunn.format_handshake_initiation(net, true) {
let _ = udp.send(p);
}
}
}
fn cfg_port_seed() -> u64 {
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.subsec_nanos() as u64).unwrap_or(1)
}
struct LinkStream {
engine: Shared,
h: SocketHandle,
timeout: Duration,
_turn: Option<Held>,
}
impl Read for LinkStream {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let t0 = Instant::now();
loop {
{
let mut e = lock(&self.engine);
e.pump();
let s = e.sockets.get_mut::<tcp::Socket>(self.h);
if s.can_recv() {
let n = s.recv_slice(buf).map_err(|e| std::io::Error::other(format!("{e:?}")))?;
if n > 0 {
return Ok(n);
}
} else if !s.may_recv() {
return Ok(0);
}
}
if t0.elapsed() > self.timeout {
return Err(std::io::Error::new(std::io::ErrorKind::TimedOut, "nothing from the VM over the link"));
}
std::thread::yield_now();
}
}
}
impl Write for LinkStream {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let t0 = Instant::now();
loop {
{
let mut e = lock(&self.engine);
e.pump();
let s = e.sockets.get_mut::<tcp::Socket>(self.h);
if s.can_send() {
let n = s.send_slice(buf).map_err(|e| std::io::Error::other(format!("{e:?}")))?;
if n > 0 {
return Ok(n);
}
} else if !s.may_send() {
return Err(std::io::Error::new(std::io::ErrorKind::BrokenPipe, "the VM closed the connection"));
}
}
if t0.elapsed() > self.timeout {
return Err(std::io::Error::new(std::io::ErrorKind::TimedOut, "the link would not take the bytes"));
}
std::thread::yield_now();
}
}
fn flush(&mut self) -> std::io::Result<()> {
let t0 = Instant::now();
loop {
{
let mut e = lock(&self.engine);
e.pump();
let s = e.sockets.get_mut::<tcp::Socket>(self.h);
if s.send_queue() == 0 || !s.may_send() {
return Ok(());
}
}
if t0.elapsed() > self.timeout {
return Err(std::io::Error::new(std::io::ErrorKind::TimedOut, "the link would not drain"));
}
std::thread::yield_now();
}
}
}
impl Drop for LinkStream {
fn drop(&mut self) {
let mut e = lock(&self.engine);
e.sockets.get_mut::<tcp::Socket>(self.h).close();
for _ in 0..20 {
e.pump();
if !e.sockets.get_mut::<tcp::Socket>(self.h).is_open() {
break;
}
}
e.sockets.remove(self.h);
}
}
impl Path for WgPath {
fn connect(&mut self, timeout: Duration) -> Result<Box<dyn Wire + '_>, String> {
let turn = take_turn(&self.turn, TURN_WAIT);
let t0 = Instant::now();
let mut fresh = false;
loop {
match self.try_connect(timeout.saturating_sub(t0.elapsed()).min(if fresh { timeout } else { Duration::from_secs(4) })) {
Ok(h) => return Ok(Box::new(LinkStream { engine: self.engine.clone(), h, timeout, _turn: turn })),
Err(e) if fresh || t0.elapsed() >= timeout => return Err(e),
Err(e) if e.starts_with("could not be reached: the VM at") => return Err(e),
Err(_) => {
fresh = true;
self.fresh_session();
}
}
}
}
fn gone(&mut self, wait: Duration) -> Result<bool, String> {
let t0 = Instant::now();
let mut index = 2u32;
let mut dgram = [0u8; 2048];
let mut net = vec![0u8; 2048];
let udp = UdpSocket::bind("0.0.0.0:0").map_err(|e| format!("the installer's probe socket: {e}"))?;
udp.connect(self.endpoint).map_err(|e| format!("the installer's probe to {}: {e}", self.endpoint))?;
udp.set_read_timeout(Some(Duration::from_millis(5))).map_err(|e| e.to_string())?;
while t0.elapsed() < wait {
index += 1;
let mut t = tunn_of(&self.keys, index);
let mut buf = [0u8; 256];
let init = match t.format_handshake_initiation(&mut buf, true) {
TunnResult::WriteToNetwork(p) => p.to_vec(),
other => return Err(format!("boringtun: {other:?}")),
};
let _ = udp.send(&init);
let round = Instant::now();
let mut answered = false;
while round.elapsed() < Duration::from_secs(3) {
if let Ok(n) = udp.recv(&mut dgram) {
if matches!(t.decapsulate(Some(std::net::IpAddr::V4(*self.endpoint.ip())), &dgram[..n], &mut net), TunnResult::WriteToNetwork(_) | TunnResult::Done) {
answered = true;
break;
}
}
}
if !answered {
return Ok(true);
}
std::thread::sleep(Duration::from_millis(500));
}
Ok(false)
}
fn describe(&self) -> String {
format!("{} (the installer's WireGuard link, {LINK_INSTALLER} → {LINK_VM})", self.endpoint)
}
}
#[cfg(test)]
mod start_over_tests {
use super::*;
fn installer_public(k: &LinkKeys) -> [u8; 32] {
PublicKey::from(&StaticSecret::from(k.installer_private)).to_bytes()
}
fn packet_for_the_installer() -> Vec<u8> {
let mut p = vec![0u8; 20];
p[0] = 0x45;
p[3] = 20;
p[8] = 64;
p[9] = 6;
p[12..16].copy_from_slice(&LINK_VM.octets());
p[16..20].copy_from_slice(&LINK_INSTALLER.octets());
p
}
fn vm_answers(vm_udp: &UdpSocket, vm: &mut Tunn) -> usize {
let (mut dgram, mut out) = ([0u8; 2048], vec![0u8; 2048]);
let mut handled = 0;
while let Ok((n, from)) = vm_udp.recv_from(&mut dgram) {
handled += 1;
let mut first = true;
loop {
let r = vm.decapsulate(None, if first { &dgram[..n] } else { &[] }, &mut out);
first = false;
match r {
TunnResult::WriteToNetwork(p) => {
let _ = vm_udp.send_to(p, from);
}
_ => break,
}
}
}
handled
}
fn handshake(path: &WgPath, vm_udp: &UdpSocket, vm: &mut Tunn) {
{
let mut e = lock(&path.engine);
let Engine { tunn, udp, net, .. } = &mut *e;
if let TunnResult::WriteToNetwork(p) = tunn.format_handshake_initiation(net, false) {
udp.send(p).unwrap();
}
}
let t0 = Instant::now();
while lock(&path.engine).tunn.stats().0.is_none() && t0.elapsed() < Duration::from_secs(5) {
vm_answers(vm_udp, vm);
lock(&path.engine).pump();
}
assert!(lock(&path.engine).tunn.stats().0.is_some(), "the link never handshook");
vm_answers(vm_udp, vm); }
fn engine_opens(path: &WgPath, vm: &mut Tunn) -> bool {
let mut sealed = vec![0u8; 2048];
let sealed = match vm.encapsulate(&packet_for_the_installer(), &mut sealed) {
TunnResult::WriteToNetwork(p) => p.to_vec(),
other => panic!("the VM could not seal: {other:?}"),
};
let mut e = lock(&path.engine);
let Engine { tunn, net, endpoint, .. } = &mut *e;
matches!(tunn.decapsulate(Some(std::net::IpAddr::V4(*endpoint.ip())), &sealed, net), TunnResult::WriteToTunnelV4(..))
}
#[test]
fn a_reader_that_starts_the_session_over_keeps_the_session_the_other_readers_use() {
let (vm_private, k) = ([3u8; 32], LinkKeys { installer_private: [9u8; 32], vm_public: PublicKey::from(&StaticSecret::from([3u8; 32])).to_bytes() });
let vm_udp = UdpSocket::bind("127.0.0.1:0").unwrap();
vm_udp.set_read_timeout(Some(Duration::from_millis(50))).unwrap();
let at = match vm_udp.local_addr().unwrap() {
SocketAddr::V4(a) => a,
_ => unreachable!(),
};
let mut vm = Tunn::new(StaticSecret::from(vm_private), PublicKey::from(installer_public(&k)), None, None, 7, None);
let reader = WgPath::new(at, k.clone()).unwrap();
handshake(&reader, &vm_udp, &mut vm);
assert!(engine_opens(&reader, &mut vm), "the link is up: the engine opens what the VM seals");
let other = WgPath::new(at, k.clone()).unwrap();
other.fresh_session();
assert!(engine_opens(&reader, &mut vm), "a reader starting the session over threw away the session another reader's open connection uses: what the VM sealed under it is unreadable now");
vm = Tunn::new(StaticSecret::from(vm_private), PublicKey::from(installer_public(&k)), None, None, 11, None);
other.fresh_session();
let t0 = Instant::now();
while t0.elapsed() < Duration::from_secs(5) {
vm_answers(&vm_udp, &mut vm);
lock(&other.engine).pump();
if vm.stats().0.is_some() && vm_answers(&vm_udp, &mut vm) == 0 && engine_opens(&other, &mut vm) {
return;
}
}
panic!("after starting over, the engine never reached the VM that had forgotten the session");
}
}
}
#[cfg(feature = "wireguard")]
pub struct VmLink {
handle: Option<wg_appliance_core::TunnelHandle>,
pub bind: Ipv4Addr,
}
#[cfg(feature = "wireguard")]
impl VmLink {
pub fn up(&self) -> bool {
self.handle.is_some()
}
pub fn handshaken(&self) -> bool {
self.handle.as_ref().map(|h| h.handshaken()).unwrap_or(false)
}
pub fn drop_installer(&mut self) {
if let Some(mut h) = self.handle.take() {
h.shutdown();
}
}
}
#[cfg(feature = "wireguard")]
pub fn vm_link(seed: &crate::seed::Seed) -> Result<VmLink, TunnelError> {
use wg_appliance_core::{addr, dataplane, decode_key, PeerSpec, TunDevice, TunnelSpec};
match reach(seed.tunnel)? {
Reach::Tunnel => {
let l = seed.installer_link.as_ref().ok_or_else(|| TunnelError::NotConfigured { kind: seed.tunnel, what: "the seed carries no [installer_link]".into() })?;
let private = decode_key(&l.vm_private).map_err(|e| TunnelError::Failed(format!("installer_link.vm_private: {}", e.0)))?;
let installer = decode_key(&l.installer_pubkey).map_err(|e| TunnelError::Failed(format!("installer_link.installer_pubkey: {}", e.0)))?;
let tun = TunDevice::create(LINK_IFACE, LINK_MTU as u32 + 40).map_err(|e| TunnelError::Failed(format!("{LINK_IFACE}: {e}")))?;
addr::assign_ipv4(LINK_IFACE, LINK_VM, LINK_PREFIX).map_err(|e| TunnelError::Failed(format!("{LINK_IFACE} {LINK_VM}/{LINK_PREFIX}: {e}")))?;
addr::link_up(LINK_IFACE).map_err(|e| TunnelError::Failed(format!("{LINK_IFACE} up: {e}")))?;
let spec = TunnelSpec {
private_key: private,
listen_port: Some(l.port),
peers: vec![PeerSpec { public_key: installer, preshared_key: None, endpoint: None, allowed_ips: vec![(LINK_INSTALLER, 32)], persistent_keepalive: None }],
};
let handle = dataplane::spawn_named(tun, &spec, &["installer"]).map_err(|e| TunnelError::Failed(format!("{LINK_IFACE}: {e}")))?;
Ok(VmLink { handle: Some(handle), bind: LINK_VM })
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_path_is_the_tunnel_kinds_and_tailscale_is_refused_by_name() {
assert!(reach(TunnelKind::None).unwrap_err().to_string().starts_with("REFUSED [installer-link-none]"), "WireGuard + mutual TLS always");
assert_eq!(reach(TunnelKind::Wireguard).unwrap(), Reach::Tunnel);
assert_eq!(reach(TunnelKind::Mullvad).unwrap(), Reach::Tunnel, "mullvad is only the exit; the mesh is WireGuard");
let e = reach(TunnelKind::Tailscale).unwrap_err().to_string();
assert!(e.starts_with("REFUSED [tunnel-tailscale-unimplemented]"), "{e}");
assert_eq!(vm_bind(Reach::Tunnel), LINK_VM, "never the public interface in tunnel mode");
assert!(!crate::in_overlay(LINK_VM) && !crate::in_overlay(LINK_INSTALLER), "the link is not the mesh");
assert!(format!("{:?}", LinkKeys { installer_private: [7; 32], vm_public: [1; 32] }).contains(".."), "no key in Debug");
}
}
#[cfg(all(test, feature = "installer-wireguard"))]
mod shared_link_tests {
use super::*;
use boringtun::noise::{Tunn, TunnResult};
use boringtun::x25519::{PublicKey, StaticSecret};
use smoltcp::iface::{Config, Interface, SocketSet};
use smoltcp::phy::{Device, DeviceCapabilities, Medium};
use smoltcp::socket::tcp;
use smoltcp::wire::{HardwareAddress, IpAddress, IpCidr};
use std::collections::VecDeque;
use std::net::UdpSocket;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Instant;
#[derive(Default)]
struct Q {
rx: VecDeque<Vec<u8>>,
tx: VecDeque<Vec<u8>>,
}
struct Rx(Vec<u8>);
struct Tx<'a>(&'a mut VecDeque<Vec<u8>>);
impl smoltcp::phy::RxToken for Rx {
fn consume<R, F: FnOnce(&[u8]) -> R>(self, f: F) -> R {
f(&self.0)
}
}
impl smoltcp::phy::TxToken for Tx<'_> {
fn consume<R, F: FnOnce(&mut [u8]) -> R>(self, len: usize, f: F) -> R {
let mut b = vec![0u8; len];
let r = f(&mut b);
self.0.push_back(b);
r
}
}
impl Device for Q {
type RxToken<'a> = Rx;
type TxToken<'a> = Tx<'a>;
fn receive(&mut self, _t: smoltcp::time::Instant) -> Option<(Rx, Tx<'_>)> {
let p = self.rx.pop_front()?;
Some((Rx(p), Tx(&mut self.tx)))
}
fn transmit(&mut self, _t: smoltcp::time::Instant) -> Option<Tx<'_>> {
Some(Tx(&mut self.tx))
}
fn capabilities(&self) -> DeviceCapabilities {
let mut c = DeviceCapabilities::default();
c.medium = Medium::Ip;
c.max_transmission_unit = LINK_MTU;
c
}
}
fn fake_vm(vm: [u8; 32], installer_public: [u8; 32], stop: Arc<AtomicBool>) -> SocketAddrV4 {
let udp = UdpSocket::bind("127.0.0.1:0").unwrap();
udp.set_read_timeout(Some(Duration::from_millis(2))).unwrap();
let at = match udp.local_addr().unwrap() {
SocketAddr::V4(a) => a,
_ => unreachable!(),
};
std::thread::spawn(move || {
let mut tunn = Tunn::new(StaticSecret::from(vm), PublicKey::from(installer_public), None, None, 7, None);
let epoch = Instant::now();
let now = || smoltcp::time::Instant::from_millis(epoch.elapsed().as_millis() as i64);
let mut dev = Q::default();
let mut iface = Interface::new(Config::new(HardwareAddress::Ip), &mut dev, now());
iface.update_ip_addrs(|a| {
let _ = a.push(IpCidr::new(IpAddress::Ipv4(LINK_VM), LINK_PREFIX));
});
let mut sockets = SocketSet::new(Vec::new());
let mut listeners = Vec::new();
for _ in 0..4 {
let mut s = tcp::Socket::new(tcp::SocketBuffer::new(vec![0u8; 8192]), tcp::SocketBuffer::new(vec![0u8; 8192]));
s.listen(crate::bootstrap::PORT).unwrap();
listeners.push((sockets.add(s), Vec::<u8>::new()));
}
let mut endpoint: Option<SocketAddr> = None;
let (mut net, mut dgram) = (vec![0u8; 65536], [0u8; 2048]);
while !stop.load(Ordering::SeqCst) {
if let TunnResult::WriteToNetwork(p) = tunn.update_timers(&mut net) {
if let Some(e) = endpoint {
let _ = udp.send_to(p, e);
}
}
while let Ok((n, from)) = udp.recv_from(&mut dgram) {
let mut first = true;
loop {
let r = tunn.decapsulate(None, if first { &dgram[..n] } else { &[] }, &mut net);
first = false;
match r {
TunnResult::WriteToNetwork(p) => {
endpoint = Some(from);
let _ = udp.send_to(p, from);
}
TunnResult::WriteToTunnelV4(p, _) => {
endpoint = Some(from);
dev.rx.push_back(p.to_vec());
break;
}
TunnResult::Done => {
endpoint = Some(from);
break;
}
_ => break,
}
}
}
iface.poll(now(), &mut dev, &mut sockets);
for (h, got) in listeners.iter_mut() {
let s = sockets.get_mut::<tcp::Socket>(*h);
if s.can_recv() {
let _ = s.recv(|b| {
got.extend_from_slice(b);
(b.len(), ())
});
}
if let Some(i) = got.iter().position(|b| *b == b'\n') {
if s.can_send() {
let line: Vec<u8> = got.drain(..=i).collect();
let mut out = b"ok ".to_vec();
out.extend_from_slice(&line);
let _ = s.send_slice(&out);
s.close();
}
}
if !s.is_open() {
got.clear();
let _ = s.listen(crate::bootstrap::PORT);
}
}
iface.poll(now(), &mut dev, &mut sockets);
while let Some(p) = dev.tx.pop_front() {
if let (TunnResult::WriteToNetwork(out), Some(e)) = (tunn.encapsulate(&p, &mut net), endpoint) {
let _ = udp.send_to(out, e);
}
}
std::thread::sleep(Duration::from_millis(1));
}
});
at
}
fn fake_door_vm(vm: [u8; 32], installer_public: [u8; 32], stop: Arc<AtomicBool>, serve_for: Duration) -> SocketAddrV4 {
let udp = UdpSocket::bind("127.0.0.1:0").unwrap();
udp.set_read_timeout(Some(Duration::from_millis(2))).unwrap();
let at = match udp.local_addr().unwrap() {
SocketAddr::V4(a) => a,
_ => unreachable!(),
};
std::thread::spawn(move || {
let mut tunn = Tunn::new(StaticSecret::from(vm), PublicKey::from(installer_public), None, None, 9, None);
let epoch = Instant::now();
let now = || smoltcp::time::Instant::from_millis(epoch.elapsed().as_millis() as i64);
let mut dev = Q::default();
let mut iface = Interface::new(Config::new(HardwareAddress::Ip), &mut dev, now());
iface.update_ip_addrs(|a| {
let _ = a.push(IpCidr::new(IpAddress::Ipv4(LINK_VM), LINK_PREFIX));
});
let mut sockets = SocketSet::new(Vec::new());
let mut listeners = Vec::new();
for _ in 0..4 {
let mut s = tcp::Socket::new(tcp::SocketBuffer::new(vec![0u8; 8192]), tcp::SocketBuffer::new(vec![0u8; 8192]));
s.listen(crate::bootstrap::PORT).unwrap();
listeners.push(sockets.add(s));
}
let mut queue: VecDeque<smoltcp::iface::SocketHandle> = VecDeque::new();
let mut got: Vec<u8> = Vec::new();
let mut answer_at: Option<Instant> = None;
let mut endpoint: Option<SocketAddr> = None;
let (mut net, mut dgram) = (vec![0u8; 65536], [0u8; 2048]);
while !stop.load(Ordering::SeqCst) {
if let TunnResult::WriteToNetwork(p) = tunn.update_timers(&mut net) {
if let Some(e) = endpoint {
let _ = udp.send_to(p, e);
}
}
while let Ok((n, from)) = udp.recv_from(&mut dgram) {
let mut first = true;
loop {
let r = tunn.decapsulate(None, if first { &dgram[..n] } else { &[] }, &mut net);
first = false;
match r {
TunnResult::WriteToNetwork(p) => {
endpoint = Some(from);
let _ = udp.send_to(p, from);
}
TunnResult::WriteToTunnelV4(p, _) => {
endpoint = Some(from);
dev.rx.push_back(p.to_vec());
break;
}
TunnResult::Done => {
endpoint = Some(from);
break;
}
_ => break,
}
}
}
iface.poll(now(), &mut dev, &mut sockets);
for h in &listeners {
let s = sockets.get_mut::<tcp::Socket>(*h);
if s.state() == tcp::State::Established && !queue.contains(h) {
queue.push_back(*h);
}
}
if let Some(&h) = queue.front() {
let s = sockets.get_mut::<tcp::Socket>(h);
if s.can_recv() {
let _ = s.recv(|b| {
got.extend_from_slice(b);
(b.len(), ())
});
}
if answer_at.is_none() && got.contains(&b'\n') {
answer_at = Some(Instant::now() + serve_for);
}
if answer_at.is_some_and(|t| Instant::now() >= t) && s.can_send() {
let i = got.iter().position(|b| *b == b'\n').unwrap();
let mut out = b"ok ".to_vec();
out.extend(got.drain(..=i));
let _ = s.send_slice(&out);
s.close();
answer_at = None;
}
}
for h in &listeners {
let s = sockets.get_mut::<tcp::Socket>(*h);
if !s.is_open() {
if queue.front() == Some(h) {
got.clear();
answer_at = None;
}
queue.retain(|q| q != h);
let _ = s.listen(crate::bootstrap::PORT);
}
}
iface.poll(now(), &mut dev, &mut sockets);
while let Some(p) = dev.tx.pop_front() {
if let (TunnResult::WriteToNetwork(out), Some(e)) = (tunn.encapsulate(&p, &mut net), endpoint) {
let _ = udp.send_to(out, e);
}
}
std::thread::sleep(Duration::from_millis(1));
}
});
at
}
#[test]
fn a_reader_queued_at_the_one_door_is_not_charged_for_another_readers_exchange() {
let (vm, k) = keys();
let installer_public = PublicKey::from(&StaticSecret::from(k.installer_private)).to_bytes();
let stop = Arc::new(AtomicBool::new(false));
let at = fake_door_vm(vm, installer_public, stop.clone(), Duration::from_secs(3));
let timeout = Duration::from_millis(4500);
let exchange = |who: &str, s: &mut Box<dyn Wire + '_>| -> Result<String, String> {
s.write_all(format!("{who}\n").as_bytes()).map_err(|e| format!("{who}'s request: {e}"))?;
s.flush().map_err(|e| format!("{who}'s request: {e}"))?;
let mut got = Vec::new();
s.read_to_end(&mut got).map_err(|e| format!("{who}'s answer: {e}"))?;
Ok(String::from_utf8_lossy(&got).to_string())
};
{
let mut path = WgPath::new(at, k.clone()).unwrap();
let mut s = path.connect(timeout).expect("the first connect");
assert_eq!(exchange("warm", &mut s).as_deref(), Ok("ok warm\n"));
}
let (b, a) = std::thread::scope(|sc| {
let (b_up, b_is_up) = std::sync::mpsc::channel();
let (a_up, a_is_up) = std::sync::mpsc::channel::<()>();
let (kb, ex) = (&k, &exchange);
let hb = sc.spawn(move || {
let mut path = WgPath::new(at, kb.clone())?;
let mut s = path.connect(timeout)?;
let _ = b_up.send(());
let _ = a_is_up.recv_timeout(Duration::from_millis(500));
ex("b", &mut s)
});
let _ = b_is_up.recv();
let a = (|| {
let mut path = WgPath::new(at, k.clone())?;
let mut s = path.connect(timeout)?;
let _ = a_up.send(());
std::thread::sleep(Duration::from_millis(100));
exchange("a", &mut s)
})();
(hb.join().unwrap(), a)
});
stop.store(true, Ordering::SeqCst);
assert_eq!(b.as_deref(), Ok("ok b\n"), "reader B, served first");
assert_eq!(a.as_deref(), Ok("ok a\n"), "reader A lost its answer: its timeout ran while the door served reader B");
}
fn keys() -> ([u8; 32], LinkKeys) {
let (vm, installer) = ([3u8; 32], [5u8; 32]);
let vm_public = PublicKey::from(&StaticSecret::from(vm)).to_bytes();
let installer_public = PublicKey::from(&StaticSecret::from(installer)).to_bytes();
let _ = installer_public;
(vm, LinkKeys { installer_private: installer, vm_public })
}
#[test]
fn two_readers_of_one_vm_in_one_process_both_get_their_answers() {
let (vm, k) = keys();
let installer_public = PublicKey::from(&StaticSecret::from(k.installer_private)).to_bytes();
let stop = Arc::new(AtomicBool::new(false));
let at = fake_vm(vm, installer_public, stop.clone());
let mut a = WgPath::new(at, k.clone()).unwrap();
let mut sa = a.connect(Duration::from_secs(10)).expect("reader A connects");
sa.write_all(b"reader-").unwrap();
sa.flush().unwrap();
let (got_a, got_b) = std::thread::scope(|sc| {
let kb = &k;
let hb = sc.spawn(move || {
let mut b = WgPath::new(at, kb.clone()).unwrap();
let mut sb = b.connect(Duration::from_secs(10)).expect("reader B connects once A is done");
sb.write_all(b"b\n").unwrap();
sb.flush().unwrap();
let mut got = Vec::new();
let _ = sb.read_to_end(&mut got);
String::from_utf8_lossy(&got).to_string()
});
std::thread::sleep(Duration::from_millis(300));
assert!(!hb.is_finished(), "reader B waits for A's turn at the door");
sa.write_all(b"a\n").unwrap();
sa.flush().unwrap();
let mut got = Vec::new();
let r = sa.read_to_end(&mut got);
drop(sa);
((String::from_utf8_lossy(&got).to_string(), r), hb.join().unwrap())
});
stop.store(true, Ordering::SeqCst);
assert_eq!(got_a.0, "ok reader-a\n", "reader A lost its answer to reader B's session: {:?}", got_a.1);
assert_eq!(got_b, "ok b\n", "reader B's answer");
}
}