#![allow(dead_code)]
pub mod connector;
pub mod docker;
pub mod host;
pub mod native;
pub mod profile;
pub mod state;
use connector::{select_connector, Connector, Preference};
use serde::{Deserialize, Serialize};
use std::sync::atomic::{AtomicBool, Ordering};
static VERBOSE: AtomicBool = AtomicBool::new(false);
pub fn set_verbose(v: bool) {
VERBOSE.store(v, Ordering::Relaxed);
}
pub fn verbose() -> bool {
VERBOSE.load(Ordering::Relaxed)
}
pub fn vlog(msg: &str) {
if verbose() {
eprintln!(" [vpn] {msg}");
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Backend {
Native,
Docker,
Host,
}
impl Backend {
pub fn label(&self) -> &'static str {
match self {
Backend::Native => "native",
Backend::Docker => "docker",
Backend::Host => "host",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PeerStatus {
pub ip: String,
pub last_handshake_secs: Option<u64>,
pub reachable: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectionInfo {
pub backend: Backend,
pub address: String, pub link: String, pub peers: Vec<PeerStatus>,
pub host_routable: bool,
#[serde(default)]
pub proxy: Option<String>, #[serde(default)]
pub relay: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MeshAccess {
Host,
Proxy(String), }
#[derive(Debug)]
pub enum NetError {
NoApiKey,
Fetch(String),
NoBackend,
Backend(String),
Profile(String),
Refused(String),
}
impl std::fmt::Display for NetError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
NetError::NoApiKey => write!(f, "p2p requires ZAKURO_API_KEY (set it and retry)"),
NetError::Fetch(e) => write!(f, "failed to fetch WireGuard profile: {}", e),
NetError::NoBackend => {
let hint = if cfg!(target_os = "macos") {
"install WireGuard (`brew install wireguard-tools wireguard-go`) then re-run with `sudo`, or start Docker Desktop"
} else {
"install WireGuard (`sudo apt install wireguard-tools`) and run as root, or install Docker"
};
write!(f, "no usable backend: {hint}")
}
NetError::Backend(e) => write!(f, "tunnel backend error: {}", e),
NetError::Profile(e) => write!(f, "invalid WireGuard profile: {}", e),
NetError::Refused(e) => write!(f, "{}", e),
}
}
}
impl std::error::Error for NetError {}
#[cfg(not(test))]
pub(crate) fn host_ops_allowed(_what: &str) -> Result<(), NetError> {
Ok(())
}
#[cfg(test)]
pub(crate) fn host_ops_allowed(what: &str) -> Result<(), NetError> {
if std::env::var("ZAKURO_TEST_REAL_VPN").as_deref() == Ok("1") {
return Ok(());
}
Err(NetError::Refused(format!(
"{what}: refused in unit tests (set ZAKURO_TEST_REAL_VPN=1 to allow)"
)))
}
fn access_of(info: &ConnectionInfo) -> Result<MeshAccess, NetError> {
match (&info.proxy, info.host_routable) {
(_, true) => Ok(MeshAccess::Host),
(Some(p), false) => Ok(MeshAccess::Proxy(p.clone())),
(None, false) => Err(NetError::Backend(
"tunnel up but host cannot route and no proxy available".into(),
)),
}
}
fn mesh_handshake_ok(info: &ConnectionInfo) -> bool {
use std::time::{Duration, SystemTime, UNIX_EPOCH};
if info.backend == Backend::Host {
return local_mesh().is_some();
}
if host_ops_allowed("wg show latest-handshakes").is_err() {
return false;
}
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
for attempt in 0..5 {
if attempt > 0 {
std::thread::sleep(Duration::from_millis(1200));
}
let out = match info.backend {
Backend::Native => std::process::Command::new("wg")
.args(["show", "zakuro0", "latest-handshakes"])
.output()
.ok(),
Backend::Docker => std::process::Command::new("docker")
.args([
"exec",
&info.link,
"wg",
"show",
"zakuro0",
"latest-handshakes",
])
.output()
.ok(),
Backend::Host => None, };
let text = match out {
Some(o) if o.status.success() => String::from_utf8_lossy(&o.stdout).into_owned(),
_ => continue,
};
let fresh = text
.lines()
.filter_map(|l| l.split_whitespace().nth(1))
.filter_map(|s| s.parse::<u64>().ok())
.any(|hs| hs > 0 && now.saturating_sub(hs) < 300);
if fresh {
return true;
}
}
false
}
fn ensure_with(
verify_fn: &dyn Fn(&ConnectionInfo) -> bool,
connect_fn: &dyn Fn() -> Result<ConnectionInfo, NetError>,
saved: Option<&ConnectionInfo>,
) -> Result<MeshAccess, NetError> {
if let Some(s) = saved {
if verify_fn(s) {
return access_of(s);
}
}
let info = connect_fn()?;
if !verify_fn(&info) {
return Err(NetError::Backend(
"mesh probe failed — tunnel is up but no handshake with the mesh server".into(),
));
}
access_of(&info)
}
pub fn ensure(pref: Preference) -> Result<MeshAccess, NetError> {
let saved = status()?; ensure_with(&mesh_handshake_ok, &|| connect(pref), saved.as_ref())
}
pub fn is_mesh_ip(host: &str) -> bool {
let p: Vec<&str> = host.split('.').collect();
p.len() == 4 && p[0] == "10" && p[1] == "13" && p[2] == "13" && p[3].parse::<u8>().is_ok()
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LocalMesh {
pub ip: String,
pub interface: String,
}
pub(crate) fn pick_local_mesh(addrs: &[(String, std::net::IpAddr)]) -> Option<LocalMesh> {
let rank = |name: &str| match name {
"zakuro0" => 0,
n if n.starts_with("wg") => 1,
_ => 2,
};
addrs
.iter()
.filter(|(_, ip)| ip.is_ipv4() && is_mesh_ip(&ip.to_string()))
.min_by_key(|(name, _)| rank(name))
.map(|(name, ip)| LocalMesh {
ip: ip.to_string(),
interface: name.clone(),
})
}
pub(crate) fn local_mesh_from(
overrides: [(&str, Option<String>); 2],
addrs: &[(String, std::net::IpAddr)],
) -> Option<LocalMesh> {
for (var, value) in overrides {
if let Some(ip) = value.filter(|v| !v.is_empty()) {
return Some(LocalMesh {
ip,
interface: var.to_string(),
});
}
}
pick_local_mesh(addrs)
}
pub fn local_mesh() -> Option<LocalMesh> {
local_mesh_from(
[
("ZAKURO_MESH_IP", std::env::var("ZAKURO_MESH_IP").ok()),
(
"ZAKURO_WIREGUARD_IP",
std::env::var("ZAKURO_WIREGUARD_IP").ok(),
),
],
&host_addresses(),
)
}
#[cfg(unix)]
fn host_addresses() -> Vec<(String, std::net::IpAddr)> {
ifaces::Interface::get_all()
.map(|all| {
all.into_iter()
.filter_map(|i| i.addr.map(|a| (i.name, a.ip())))
.collect()
})
.unwrap_or_default()
}
#[cfg(not(unix))]
fn host_addresses() -> Vec<(String, std::net::IpAddr)> {
Vec::new()
}
pub const RELAY_FIRST_PORT: u16 = 9000;
pub const RELAY_LAST_PORT: u16 = 9010;
pub const RELAY_VERSION: u32 = 1;
pub fn relay_covers(port: u16) -> bool {
(RELAY_FIRST_PORT..=RELAY_LAST_PORT).contains(&port)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Route {
Host {
ip: String,
interface: String,
},
Proxy {
mesh_ip: String,
connect_proxy: String,
relay: bool,
},
None,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RouteKind {
Host,
Proxy,
None,
}
impl RouteKind {
pub fn label(self) -> &'static str {
match self {
RouteKind::Host => "host",
RouteKind::Proxy => "proxy",
RouteKind::None => "none",
}
}
}
impl Route {
pub fn kind(&self) -> RouteKind {
match self {
Route::Host { .. } => RouteKind::Host,
Route::Proxy { .. } => RouteKind::Proxy,
Route::None => RouteKind::None,
}
}
pub fn is_proxy(&self) -> bool {
matches!(self, Route::Proxy { .. })
}
pub fn advertised_ip(&self, port: u16) -> Option<String> {
match self {
Route::Host { ip, .. } => Some(ip.clone()),
Route::Proxy {
mesh_ip,
relay: true,
..
} if relay_covers(port) => Some(mesh_ip.clone()),
Route::Proxy { .. } | Route::None => None,
}
}
}
pub(crate) fn route_from(
local: Option<LocalMesh>,
saved: Option<&ConnectionInfo>,
proxy_alive: &dyn Fn(&str) -> bool,
) -> Route {
if let Some(m) = local {
return Route::Host {
ip: m.ip,
interface: m.interface,
};
}
match saved {
Some(s) if !s.host_routable => match &s.proxy {
Some(p) if proxy_alive(p) => Route::Proxy {
mesh_ip: s
.address
.split('/')
.next()
.unwrap_or(&s.address)
.to_string(),
connect_proxy: p.clone(),
relay: s.relay == Some(RELAY_VERSION),
},
_ => Route::None,
},
_ => Route::None,
}
}
pub fn route() -> Route {
route_from(local_mesh(), state::load().as_ref(), &proxy_accepts)
}
fn proxy_accepts(addr: &str) -> bool {
addr.parse::<std::net::SocketAddr>().is_ok_and(|a| {
std::net::TcpStream::connect_timeout(&a, std::time::Duration::from_millis(300)).is_ok()
})
}
pub(crate) fn proxy_addr_from(
local: Option<&LocalMesh>,
saved: Option<ConnectionInfo>,
) -> Option<String> {
if local.is_some() {
return None;
}
saved.filter(|s| !s.host_routable).and_then(|s| s.proxy)
}
pub fn mesh_proxy_addr() -> Option<String> {
proxy_addr_from(local_mesh().as_ref(), state::load())
}
pub fn mesh_proxy() -> Option<ureq::Proxy> {
mesh_proxy_addr().and_then(|p| ureq::Proxy::new(&format!("http://{}", p)).ok())
}
pub fn sync_mesh_peer_key() {
crate::credentials::load_into_env();
let Ok(api_key) = std::env::var("ZAKURO_API_KEY") else {
return;
};
if api_key.trim().is_empty() {
return;
}
let api_url = crate::credentials::default_api_url();
let endpoint = format!("{}/api/broker/config/mesh", api_url.trim_end_matches('/'));
let resp = match ureq::get(&endpoint)
.config()
.timeout_global(Some(std::time::Duration::from_secs(10)))
.http_status_as_error(false)
.build()
.header("X-Broker-Api-Key", &api_key)
.call()
{
Ok(r) => r,
Err(e) => {
vlog(&format!("mesh peer key: request failed ({e})"));
return;
}
};
let status = resp.status().as_u16();
if status != 200 {
vlog(&format!(
"mesh peer key: hub answered HTTP {status}; not stored"
));
return;
}
let Ok(text) = resp.into_body().read_to_string() else {
return;
};
let key = serde_json::from_str::<serde_json::Value>(&text)
.ok()
.and_then(|v| {
v.get("peer_key")
.and_then(|k| k.as_str())
.map(str::to_string)
})
.filter(|k| !k.trim().is_empty());
match key {
Some(k) => match crate::credentials::save_mesh_peer_key(&k) {
Ok(()) => vlog("mesh peer key stored"),
Err(e) => eprintln!(" ⚠ could not store the mesh peer key: {e}"),
},
None => vlog("mesh peer key: hub response carried no peer_key"),
}
}
pub fn mesh_agent(timeout: std::time::Duration) -> ureq::Agent {
agent_via(timeout, mesh_proxy_addr())
}
fn agent_via(timeout: std::time::Duration, proxy: Option<String>) -> ureq::Agent {
let mut cfg = ureq::Agent::config_builder()
.timeout_connect(Some(timeout))
.timeout_global(Some(timeout));
if let Some(p) = proxy {
if let Ok(proxy) = ureq::Proxy::new(&format!("http://{}", p)) {
cfg = cfg.proxy(Some(proxy));
}
}
ureq::Agent::new_with_config(cfg.build())
}
#[derive(Debug)]
pub(crate) enum ConnectPlan {
Reuse(ConnectionInfo),
UseHost {
mesh: LocalMesh,
sidecar_left_up: bool,
},
RecreateSidecar { reason: &'static str },
CheckShared(ConnectionInfo),
Fresh,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SidecarCheck {
Current,
Outdated(&'static str),
AskHub,
}
pub(crate) fn sidecar_outdated(
labels: Option<&docker::SidecarLabels>,
this_node: Option<&str>,
) -> SidecarCheck {
let Some(labels) = labels else {
return SidecarCheck::Outdated("has no readable zakuro labels");
};
let Some(node) = labels.node.as_deref() else {
return SidecarCheck::Outdated("predates per-device mesh identities");
};
let shared = node == docker::NODE_SHARED;
if !shared && this_node.is_some_and(|this| this != node) {
return SidecarCheck::Outdated("carries another device's mesh identity");
}
if labels.mode == docker::Mode::Proxy && labels.relay != Some(RELAY_VERSION) {
return SidecarCheck::Outdated("predates the mesh relay");
}
if shared && this_node.is_some() {
return SidecarCheck::AskHub;
}
SidecarCheck::Current
}
fn node_key_vanished(labels: Option<&docker::SidecarLabels>, this_node: Option<&str>) -> bool {
this_node.is_none()
&& labels
.and_then(|l| l.node.as_deref())
.is_some_and(|n| n != docker::NODE_SHARED)
}
fn key_hint(
plan: &ConnectPlan,
labels: Option<&docker::SidecarLabels>,
this_node: Option<&str>,
) -> Option<&'static str> {
match plan {
ConnectPlan::Reuse(_)
| ConnectPlan::RecreateSidecar { .. }
| ConnectPlan::CheckShared(_) => node_key_vanished(labels, this_node)
.then_some("⚠ this device's node key is missing; run zc login to recreate it"),
ConnectPlan::UseHost { .. } | ConnectPlan::Fresh => None,
}
}
pub(crate) fn plan_connect(
pref: Preference,
existing: Option<ConnectionInfo>,
labels: Option<&docker::SidecarLabels>,
host: Option<LocalMesh>,
this_node: Option<&str>,
) -> ConnectPlan {
let host = host.filter(|_| pref == Preference::Auto);
let Some(e) = existing else {
return match host {
Some(mesh) => ConnectPlan::UseHost {
mesh,
sidecar_left_up: false,
},
None => ConnectPlan::Fresh,
};
};
match (e.backend, host) {
(Backend::Native, _) => ConnectPlan::Reuse(e),
(Backend::Docker, _) if e.host_routable => sidecar_plan(e, labels, this_node),
(Backend::Docker, Some(mesh)) => ConnectPlan::UseHost {
mesh,
sidecar_left_up: true,
},
(Backend::Docker, None) => sidecar_plan(e, labels, this_node),
(Backend::Host, Some(mesh)) => ConnectPlan::UseHost {
mesh,
sidecar_left_up: false,
},
(Backend::Host, None) => ConnectPlan::Fresh,
}
}
fn sidecar_plan(
e: ConnectionInfo,
labels: Option<&docker::SidecarLabels>,
this_node: Option<&str>,
) -> ConnectPlan {
match sidecar_outdated(labels, this_node) {
SidecarCheck::Current => ConnectPlan::Reuse(e),
SidecarCheck::Outdated(reason) => ConnectPlan::RecreateSidecar { reason },
SidecarCheck::AskHub => ConnectPlan::CheckShared(e),
}
}
fn check_shared(
info: ConnectionInfo,
fetch: &dyn Fn() -> Result<profile::WgProfile, NetError>,
recreate: &dyn Fn(&profile::WgProfile) -> Result<ConnectionInfo, NetError>,
) -> Result<ConnectionInfo, NetError> {
match fetch() {
Ok(p) if p.node.is_some() => {
eprintln!(
" ↻ this device now has its own mesh identity on the hub; recreating the zakuro-wg sidecar"
);
recreate(&p)
}
Ok(_) => Ok(info),
Err(e) => {
vlog(&format!(
"could not ask the hub for this device's mesh identity ({e}); keeping the zakuro-wg sidecar"
));
Ok(info)
}
}
}
pub fn connect(pref: Preference) -> Result<ConnectionInfo, NetError> {
let existing = status()?;
let labels = match &existing {
Some(e) if e.backend == Backend::Docker => docker::DockerConnector.sidecar_labels(),
_ => None,
};
let this_node = profile::device_fingerprint();
let plan = plan_connect(
pref,
existing,
labels.as_ref(),
local_mesh(),
this_node.as_deref(),
);
if let Some(hint) = key_hint(&plan, labels.as_ref(), this_node.as_deref()) {
eprintln!(" {hint}");
}
let info = match plan {
ConnectPlan::Reuse(info) => info,
ConnectPlan::UseHost {
mesh,
sidecar_left_up,
} => {
if sidecar_left_up {
eprintln!(
" the zakuro-wg container is still running but no longer used; `docker rm -f zakuro-wg` removes it"
);
}
host::record(&mesh)
}
ConnectPlan::RecreateSidecar { reason } => {
eprintln!(" ↻ the zakuro-wg container {reason}; recreating it (the mesh drops once)");
docker::DockerConnector.connect(&profile::fetch_wg_profile()?)?
}
ConnectPlan::CheckShared(info) => check_shared(info, &profile::fetch_wg_profile, &|p| {
docker::DockerConnector.connect(p)
})?,
ConnectPlan::Fresh => {
let profile = profile::fetch_wg_profile()?;
select_connector(pref)?.connect(&profile)?
}
};
sync_mesh_peer_key();
Ok(info)
}
pub fn status() -> Result<Option<ConnectionInfo>, NetError> {
if let Some(saved) = state::load() {
let c = select_for_backend(saved.backend);
if let Ok(Some(info)) = c.status() {
return Ok(Some(info));
}
return Ok(None);
}
if let Ok(Some(info)) = docker::DockerConnector.status() {
return Ok(Some(info));
}
if let Ok(Some(info)) = native::NativeConnector.status() {
return Ok(Some(info));
}
Ok(None)
}
pub fn conf() -> Result<String, NetError> {
profile::fetch_wg_profile()?.to_conf()
}
pub fn disconnect() -> Result<(), NetError> {
if let Some(saved) = state::load() {
select_for_backend(saved.backend).disconnect()?;
} else {
let _ = docker::DockerConnector.disconnect();
let _ = native::NativeConnector.disconnect();
}
Ok(())
}
fn select_for_backend(b: Backend) -> Box<dyn connector::Connector> {
match b {
Backend::Native => Box::new(native::NativeConnector),
Backend::Docker => Box::new(docker::DockerConnector),
Backend::Host => Box::new(host::HostConnector),
}
}
fn parse_pref(args: &[String]) -> Preference {
if args.iter().any(|a| a == "--native") {
Preference::Native
} else if args.iter().any(|a| a == "--docker") {
Preference::Docker
} else {
Preference::Auto
}
}
fn render(info: &ConnectionInfo) -> String {
let reachable = info.peers.iter().filter(|p| p.reachable).count();
let access = match &info.proxy {
Some(p) if !info.host_routable => format!("proxy {}", p),
_ => "host".to_string(),
};
format!(
"connected to zakuro mesh — {} · {} · {} peer(s), {} reachable · access: {}",
info.address,
info.backend.label(),
info.peers.len(),
reachable,
access,
)
}
fn connect_line(info: &ConnectionInfo) -> String {
match info.backend {
Backend::Host => format!("Using the host VPN ({}, {})", info.link, info.address),
Backend::Native | Backend::Docker => render(info),
}
}
fn disconnect_message(saved: Option<&ConnectionInfo>) -> String {
match saved {
Some(info) if info.backend == Backend::Host => format!(
"forgot the host VPN connection ({}, {}); the tunnel itself is managed outside zc and stays up",
info.link, info.address
),
_ => "disconnected from zakuro mesh".to_string(),
}
}
pub fn run_cli(args: &[String]) {
use colored::Colorize;
set_verbose(args.iter().any(|a| a == "--verbose" || a == "-v"));
let sub = args.first().map(|s| s.as_str()).unwrap_or("status");
match sub {
"connect" | "up" => match connect(parse_pref(args)) {
Ok(info) => println!(" {} {}", "✓".green(), connect_line(&info)),
Err(e) => {
eprintln!(" {} {}", "✗".red(), e);
std::process::exit(1);
}
},
"ensure" => match ensure(parse_pref(args)) {
Ok(MeshAccess::Host) => {
println!(" {} mesh verified via host tunnel", "✓".green())
}
Ok(MeshAccess::Proxy(p)) => {
println!(" {} mesh verified via container proxy {}", "✓".green(), p)
}
Err(e) => {
eprintln!(" {} {}", "✗".red(), e);
std::process::exit(1);
}
},
"disconnect" | "down" => {
let saved = state::load();
match disconnect() {
Ok(()) => println!(" {} {}", "✓".green(), disconnect_message(saved.as_ref())),
Err(e) => {
eprintln!(" {} {}", "✗".red(), e);
std::process::exit(1);
}
}
}
"status" => match status() {
Ok(Some(info)) => println!(" {}", render(&info)),
Ok(None) => println!(" not connected (local mode)"),
Err(e) => {
eprintln!(" {} {}", "✗".red(), e);
std::process::exit(1);
}
},
"conf" => match conf() {
Ok(text) => print!("{}", text),
Err(e) => {
eprintln!(" {} {}", "✗".red(), e);
std::process::exit(1);
}
},
other => {
eprintln!(
"usage: zc vpn [connect [--native|--docker] | ensure | disconnect | status | conf] (got '{}')",
other
);
std::process::exit(1);
}
}
}
#[cfg(test)]
pub(crate) mod fixtures {
use super::Route;
pub(crate) fn host_route() -> Route {
Route::Host {
ip: "10.13.13.7".into(),
interface: "utun4".into(),
}
}
pub(crate) fn proxy_route() -> Route {
Route::Proxy {
mesh_ip: "10.13.13.7".into(),
connect_proxy: "127.0.0.1:18888".into(),
relay: true,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vpn::connector::Preference;
#[test]
fn parse_pref_reads_flags() {
assert_eq!(parse_pref(&["connect".into()]), Preference::Auto);
assert_eq!(
parse_pref(&["connect".into(), "--native".into()]),
Preference::Native
);
assert_eq!(
parse_pref(&["connect".into(), "--docker".into()]),
Preference::Docker
);
}
const SAMPLE_PROFILE: &str = r#"{
"interface": { "private_key": "MKCl4DM7FbX1sFUiFd8VqKzdIGUMwJ28rTgFYezsclc=", "address": "10.13.13.6/24" },
"peer": { "public_key": "/Lzp+YIUBrNgdABzyR221uhfx2uOWi4m5ZAdgxXzhFs=",
"endpoint": "144.202.121.242:51822", "allowed_ips": "10.13.13.0/24",
"persistent_keepalive": 25 }
}"#;
#[test]
fn connection_info_proxy_roundtrip_and_default() {
let legacy = r#"{"backend":"Docker","address":"10.13.13.6/24","link":"zakuro-wg",
"peers":[],"host_routable":false}"#;
let info: ConnectionInfo = serde_json::from_str(legacy).unwrap();
assert!(info.proxy.is_none());
assert!(info.relay.is_none());
let with = ConnectionInfo {
proxy: Some("127.0.0.1:18888".into()),
..info
};
let back: ConnectionInfo =
serde_json::from_str(&serde_json::to_string(&with).unwrap()).unwrap();
assert_eq!(back.proxy.as_deref(), Some("127.0.0.1:18888"));
}
#[test]
fn ensure_ladder_reuse_host_reuse_proxy_then_connect() {
use std::cell::Cell;
let native_saved = ConnectionInfo {
backend: Backend::Native,
address: "10.13.13.6/24".into(),
link: "zakuro0".into(),
peers: vec![],
host_routable: true,
proxy: None,
relay: None,
};
let r = ensure_with(&|_| true, &|| Err(NetError::NoBackend), Some(&native_saved)).unwrap();
assert!(matches!(r, MeshAccess::Host));
let saved = ConnectionInfo {
backend: Backend::Docker,
address: "10.13.13.6/24".into(),
link: "zakuro-wg".into(),
peers: vec![],
host_routable: false,
proxy: Some("127.0.0.1:18888".into()),
relay: None,
};
let r = ensure_with(&|_| true, &|| Err(NetError::NoBackend), Some(&saved)).unwrap();
assert!(matches!(r, MeshAccess::Proxy(ref p) if p == "127.0.0.1:18888"));
let called = Cell::new(false);
let err = ensure_with(
&|_| false,
&|| {
called.set(true);
Ok(saved.clone())
},
Some(&saved),
)
.unwrap_err();
assert!(called.get());
assert!(format!("{err}").contains("mesh probe failed"));
let r = ensure_with(&|_| true, &|| Ok(saved.clone()), None).unwrap();
assert!(matches!(r, MeshAccess::Proxy(_)));
}
#[test]
fn mesh_ip_detection() {
assert!(is_mesh_ip("10.13.13.4"));
assert!(is_mesh_ip("10.13.13.254"));
assert!(!is_mesh_ip("100.82.173.52")); assert!(!is_mesh_ip("192.168.0.23"));
assert!(!is_mesh_ip("10.13.14.4"));
assert!(!is_mesh_ip("localhost"));
}
#[test]
fn mesh_agent_uses_connect_proxy_when_state_has_one() {
use std::io::{Read, Write};
use std::net::TcpListener;
let peer = TcpListener::bind("127.0.0.1:0").unwrap();
let peer_port = peer.local_addr().unwrap().port();
std::thread::spawn(move || {
for s in peer.incoming().flatten() {
let mut s = s;
let mut b = [0u8; 1024];
let _ = s.read(&mut b);
let _ = s.write_all(
b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nok",
);
}
});
let proxy = TcpListener::bind("127.0.0.1:0").unwrap();
let proxy_addr = proxy.local_addr().unwrap();
std::thread::spawn(move || {
for c in proxy.incoming().flatten() {
let mut c = c;
let mut req = Vec::new();
let mut b = [0u8; 256];
loop {
match c.read(&mut b) {
Ok(0) | Err(_) => break,
Ok(n) => {
req.extend_from_slice(&b[..n]);
if req.windows(4).any(|w| w == b"\r\n\r\n") {
break;
}
}
}
}
if !String::from_utf8_lossy(&req).starts_with("CONNECT 10.13.13.9:9000") {
continue;
}
c.write_all(b"HTTP/1.1 200 Connection established\r\n\r\n")
.unwrap();
let mut up = std::net::TcpStream::connect(("127.0.0.1", peer_port)).unwrap();
let mut c2 = c.try_clone().unwrap();
let mut up2 = up.try_clone().unwrap();
std::thread::spawn(move || {
let _ = std::io::copy(&mut c2, &mut up);
});
let _ = std::io::copy(&mut up2, &mut c);
}
});
let _env = crate::credentials::HOME_ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
let prev_state_dir = std::env::var_os("ZAKURO_STATE_DIR");
let dir = std::env::temp_dir().join(format!("zc-vpn-agent-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
std::env::set_var("ZAKURO_STATE_DIR", &dir);
state::save(&ConnectionInfo {
backend: Backend::Docker,
address: "10.13.13.6/24".into(),
link: "zakuro-wg".into(),
peers: vec![],
host_routable: false,
proxy: Some(proxy_addr.to_string()),
relay: None,
})
.unwrap();
let agent = agent_via(
std::time::Duration::from_secs(3),
proxy_addr_from(None, state::load()),
);
let body = agent
.get("http://10.13.13.9:9000/health")
.call()
.unwrap()
.into_body()
.read_to_string()
.unwrap();
assert_eq!(body, "ok");
match prev_state_dir {
Some(v) => std::env::set_var("ZAKURO_STATE_DIR", v),
None => std::env::remove_var("ZAKURO_STATE_DIR"),
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn conf_renders_wg_quick_from_profile_file() {
let dir = std::env::temp_dir().join(format!("zc-vpn-conf-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("wg.json");
std::fs::write(&path, SAMPLE_PROFILE).unwrap();
std::env::set_var("ZAKURO_ALLOW_FILE_PROFILE", "1");
std::env::set_var("ZAKURO_WG_PROFILE_FILE", &path);
let conf = super::conf().expect("conf renders");
assert!(conf.contains("[Interface]"));
assert!(conf.contains("Address = 10.13.13.6/24"));
assert!(conf.contains("[Peer]"));
assert!(conf.contains("Endpoint = 144.202.121.242:51822"));
std::env::remove_var("ZAKURO_ALLOW_FILE_PROFILE");
std::env::remove_var("ZAKURO_WG_PROFILE_FILE");
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn host_ops_are_refused_in_unit_tests() {
let err = host_ops_allowed("wg-quick down").unwrap_err();
assert!(matches!(err, NetError::Refused(_)));
assert!(err.to_string().contains("ZAKURO_TEST_REAL_VPN"), "{err}");
}
const THIS_NODE: &str = "0123456789abcdef";
fn docker_proxy() -> ConnectionInfo {
ConnectionInfo {
backend: Backend::Docker,
address: "10.13.13.7/24".into(),
link: "zakuro-wg".into(),
peers: vec![],
host_routable: false,
proxy: Some("127.0.0.1:18888".into()),
relay: Some(RELAY_VERSION),
}
}
fn sidecar(node: Option<&str>) -> docker::SidecarLabels {
docker::SidecarLabels {
mode: docker::Mode::Proxy,
address: "10.13.13.7/24".into(),
proxy: Some("127.0.0.1:18888".into()),
node: node.map(str::to_string),
relay: Some(RELAY_VERSION),
}
}
fn hub_profile(node: Option<&str>) -> profile::WgProfile {
let mut p: profile::WgProfile = serde_json::from_str(SAMPLE_PROFILE).unwrap();
p.node = node.map(str::to_string);
p
}
fn plan(
existing: Option<ConnectionInfo>,
labels: Option<&docker::SidecarLabels>,
host: Option<LocalMesh>,
) -> ConnectPlan {
plan_connect(Preference::Auto, existing, labels, host, Some(THIS_NODE))
}
fn native() -> ConnectionInfo {
ConnectionInfo {
backend: Backend::Native,
link: "zakuro0".into(),
host_routable: true,
proxy: None,
relay: None,
..docker_proxy()
}
}
#[test]
fn connect_starts_fresh_with_nothing_up() {
assert!(matches!(plan(None, None, None), ConnectPlan::Fresh));
assert!(matches!(
plan_connect(Preference::Auto, None, None, None, None),
ConnectPlan::Fresh
));
}
#[test]
fn a_sidecar_from_before_per_device_identities_is_recreated() {
let old = sidecar(None);
let p = plan(Some(docker_proxy()), Some(&old), None);
assert!(
matches!(p, ConnectPlan::RecreateSidecar { reason } if reason.contains("per-device")),
"{p:?}"
);
let p = plan_connect(
Preference::Auto,
Some(docker_proxy()),
Some(&old),
None,
None,
);
assert!(matches!(p, ConnectPlan::RecreateSidecar { .. }), "{p:?}");
}
#[test]
fn a_sidecar_with_another_devices_identity_is_recreated() {
let other = sidecar(Some("fedcba9876543210"));
let p = plan(Some(docker_proxy()), Some(&other), None);
assert!(
matches!(p, ConnectPlan::RecreateSidecar { reason } if reason.contains("another device")),
"{p:?}"
);
}
#[test]
fn a_sidecar_whose_labels_cannot_be_read_is_recreated() {
let p = plan(Some(docker_proxy()), None, None);
assert!(matches!(p, ConnectPlan::RecreateSidecar { .. }), "{p:?}");
}
#[test]
fn a_sidecar_with_this_devices_identity_is_reused_without_asking_the_hub() {
let current = sidecar(Some(THIS_NODE));
assert_eq!(
sidecar_outdated(Some(¤t), Some(THIS_NODE)),
SidecarCheck::Current
);
let p = plan(Some(docker_proxy()), Some(¤t), None);
assert!(
matches!(p, ConnectPlan::Reuse(_)),
"never CheckShared: {p:?}"
);
}
#[test]
fn a_current_sidecar_and_a_native_tunnel_are_reused() {
let current = sidecar(Some(THIS_NODE));
assert!(matches!(
plan(Some(docker_proxy()), Some(¤t), None),
ConnectPlan::Reuse(_)
));
assert!(matches!(
plan(Some(native()), None, None),
ConnectPlan::Reuse(_)
));
assert!(matches!(
plan_connect(Preference::Auto, Some(native()), None, None, None),
ConnectPlan::Reuse(_)
));
}
#[test]
fn a_per_device_sidecar_is_kept_when_the_node_key_vanished() {
let mine = sidecar(Some(THIS_NODE));
assert_eq!(sidecar_outdated(Some(&mine), None), SidecarCheck::Current);
assert!(matches!(
plan_connect(
Preference::Auto,
Some(docker_proxy()),
Some(&mine),
None,
None
),
ConnectPlan::Reuse(_)
));
assert!(
node_key_vanished(Some(&mine), None),
"connect() prints the zc login hint"
);
assert!(!node_key_vanished(Some(&mine), Some(THIS_NODE)));
assert!(
!node_key_vanished(Some(&sidecar(Some(docker::NODE_SHARED))), None),
"a shared sidecar never carried this device's key"
);
assert!(!node_key_vanished(None, None));
}
#[test]
fn without_a_node_key_a_shared_sidecar_is_current_and_the_hub_is_not_asked() {
let shared = sidecar(Some(docker::NODE_SHARED));
assert_eq!(sidecar_outdated(Some(&shared), None), SidecarCheck::Current);
let p = plan_connect(
Preference::Auto,
Some(docker_proxy()),
Some(&shared),
None,
None,
);
assert!(matches!(p, ConnectPlan::Reuse(_)), "{p:?}");
}
#[test]
fn with_a_node_key_a_shared_sidecar_asks_the_hub() {
let shared = sidecar(Some(docker::NODE_SHARED));
assert_eq!(
sidecar_outdated(Some(&shared), Some(THIS_NODE)),
SidecarCheck::AskHub
);
let p = plan(Some(docker_proxy()), Some(&shared), None);
assert!(matches!(p, ConnectPlan::CheckShared(_)), "{p:?}");
}
#[test]
fn a_shared_sidecar_is_kept_while_the_hub_answers_the_shared_profile() {
use std::cell::Cell;
let shared = sidecar(Some(docker::NODE_SHARED));
let ConnectPlan::CheckShared(live) = plan(Some(docker_proxy()), Some(&shared), None) else {
panic!("a shared sidecar with a node key asks the hub");
};
let (fetches, recreates) = (Cell::new(0), Cell::new(0));
let info = check_shared(
live,
&|| {
fetches.set(fetches.get() + 1);
Ok(hub_profile(None))
},
&|_| {
recreates.set(recreates.get() + 1);
Err(NetError::NoBackend)
},
)
.expect("the live sidecar is kept");
assert_eq!(fetches.get(), 1, "one profile fetch per connect");
assert_eq!(recreates.get(), 0, "no recreate while the hub is old");
assert_eq!(info.address, docker_proxy().address);
}
#[test]
fn a_shared_sidecar_is_kept_when_the_hub_cannot_be_asked() {
use std::cell::Cell;
let (fetches, recreates) = (Cell::new(0), Cell::new(0));
let info = check_shared(
docker_proxy(),
&|| {
fetches.set(fetches.get() + 1);
Err(NetError::Fetch("connection refused".into()))
},
&|_| {
recreates.set(recreates.get() + 1);
Err(NetError::NoBackend)
},
)
.expect("a failed fetch never fails the connect");
assert_eq!(fetches.get(), 1);
assert_eq!(recreates.get(), 0);
assert_eq!(info.address, docker_proxy().address);
}
#[test]
fn a_shared_sidecar_is_recreated_from_the_profile_that_names_this_device() {
use std::cell::{Cell, RefCell};
let (fetches, recreates) = (Cell::new(0), Cell::new(0));
let built_from = RefCell::new(None);
let info = check_shared(
docker_proxy(),
&|| {
fetches.set(fetches.get() + 1);
Ok(hub_profile(Some(THIS_NODE)))
},
&|p| {
recreates.set(recreates.get() + 1);
*built_from.borrow_mut() = p.node.clone();
Ok(ConnectionInfo {
address: p.interface.address.clone(),
..docker_proxy()
})
},
)
.expect("recreated");
assert_eq!(fetches.get(), 1, "the profile that answered is reused");
assert_eq!(recreates.get(), 1);
assert_eq!(built_from.borrow().as_deref(), Some(THIS_NODE));
assert_eq!(
info.address, "10.13.13.6/24",
"connect returns the recreated sidecar's connection"
);
}
fn ip(s: &str) -> std::net::IpAddr {
s.parse().unwrap()
}
#[test]
fn local_mesh_is_found_by_address_on_a_utun() {
let addrs = vec![
("lo0".to_string(), ip("127.0.0.1")),
("en0".to_string(), ip("192.168.0.23")),
("utun4".to_string(), ip("10.13.13.7")),
];
assert_eq!(
pick_local_mesh(&addrs),
Some(LocalMesh {
ip: "10.13.13.7".into(),
interface: "utun4".into()
})
);
}
#[test]
fn local_mesh_prefers_zakuro0_then_wg_names() {
let addrs = vec![
("utun3".to_string(), ip("10.13.13.9")),
("wg0".to_string(), ip("10.13.13.8")),
("zakuro0".to_string(), ip("10.13.13.7")),
];
assert_eq!(pick_local_mesh(&addrs).unwrap().interface, "zakuro0");
assert_eq!(pick_local_mesh(&addrs[..2]).unwrap().interface, "wg0");
}
#[test]
fn a_route_via_another_vpn_is_not_host() {
let addrs = vec![
("utun4".to_string(), ip("10.100.0.2")),
("en0".to_string(), ip("192.168.1.5")),
];
assert_eq!(pick_local_mesh(&addrs), None);
}
#[test]
fn local_mesh_ignores_other_subnets_and_ipv6() {
let addrs = vec![
("utun3".to_string(), ip("fe80::1")),
("utun5".to_string(), ip("10.13.14.7")),
("tailscale0".to_string(), ip("100.82.173.52")),
];
assert_eq!(pick_local_mesh(&addrs), None);
}
#[test]
fn local_mesh_env_overrides_keep_their_precedence() {
let utun = vec![("utun4".to_string(), ip("10.13.13.7"))];
let over = |mesh: Option<&str>, wg: Option<&str>| {
[
("ZAKURO_MESH_IP", mesh.map(str::to_string)),
("ZAKURO_WIREGUARD_IP", wg.map(str::to_string)),
]
};
assert_eq!(
local_mesh_from(over(Some("10.13.13.42"), Some("10.13.13.43")), &utun),
Some(LocalMesh {
ip: "10.13.13.42".into(),
interface: "ZAKURO_MESH_IP".into()
})
);
assert_eq!(
local_mesh_from(over(None, Some("10.13.13.43")), &utun),
Some(LocalMesh {
ip: "10.13.13.43".into(),
interface: "ZAKURO_WIREGUARD_IP".into()
})
);
assert_eq!(
local_mesh_from(over(Some(""), None), &utun)
.unwrap()
.interface,
"utun4",
"an empty override is no override"
);
assert_eq!(local_mesh_from(over(None, None), &[]), None);
}
fn utun() -> LocalMesh {
LocalMesh {
ip: "10.13.13.7".into(),
interface: "utun4".into(),
}
}
#[test]
fn route_host_beats_proxy() {
let r = route_from(Some(utun()), Some(&docker_proxy()), &|_| {
panic!("a host tunnel needs no proxy probe")
});
assert_eq!(
r,
Route::Host {
ip: "10.13.13.7".into(),
interface: "utun4".into()
}
);
}
#[test]
fn route_proxy_needs_a_live_connect_proxy() {
let saved = docker_proxy();
assert_eq!(
route_from(None, Some(&saved), &|p| p == "127.0.0.1:18888"),
Route::Proxy {
mesh_ip: "10.13.13.7".into(),
connect_proxy: "127.0.0.1:18888".into(),
relay: true
}
);
assert_eq!(
route_from(None, Some(&saved), &|_| false),
Route::None,
"a stopped sidecar is no route"
);
}
#[test]
fn route_proxy_reports_a_sidecar_without_the_relay() {
let old = ConnectionInfo {
relay: None,
..docker_proxy()
};
assert!(matches!(
route_from(None, Some(&old), &|_| true),
Route::Proxy { relay: false, .. }
));
}
#[test]
fn route_none_without_a_tunnel() {
assert_eq!(route_from(None, None, &|_| true), Route::None);
let gone = ConnectionInfo {
backend: Backend::Native,
link: "zakuro0".into(),
host_routable: true,
proxy: None,
..docker_proxy()
};
assert_eq!(
route_from(None, Some(&gone), &|_| true),
Route::None,
"a recorded native tunnel whose interface is gone"
);
}
#[test]
fn route_kinds_carry_the_summary_labels() {
assert_eq!(fixtures::host_route().kind().label(), "host");
let proxy = fixtures::proxy_route();
assert_eq!(proxy.kind().label(), "proxy");
assert!(proxy.is_proxy());
assert_eq!(Route::None.kind().label(), "none");
}
#[test]
fn the_connect_proxy_is_used_only_without_a_host_tunnel() {
assert_eq!(
proxy_addr_from(None, Some(docker_proxy())).as_deref(),
Some("127.0.0.1:18888")
);
assert_eq!(
proxy_addr_from(Some(&utun()), Some(docker_proxy())),
None,
"host wins"
);
assert_eq!(proxy_addr_from(None, None), None);
}
#[test]
fn proxy_accepts_only_a_listening_address() {
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap().to_string();
assert!(proxy_accepts(&addr));
drop(listener);
assert!(!proxy_accepts(&addr));
assert!(!proxy_accepts("not an address"));
}
#[test]
fn relay_covers_the_agents_broker_range() {
assert!(relay_covers(9000) && relay_covers(9010));
assert!(!relay_covers(8999) && !relay_covers(9011) && !relay_covers(54321));
}
#[test]
fn a_proxy_sidecar_without_the_relay_is_recreated() {
let old = docker::SidecarLabels {
relay: None,
..sidecar(Some(THIS_NODE))
};
let p = plan(Some(docker_proxy()), Some(&old), None);
assert!(
matches!(p, ConnectPlan::RecreateSidecar { reason } if reason.contains("relay")),
"{p:?}"
);
}
#[test]
fn a_shared_sidecar_without_the_relay_is_recreated_without_asking_the_hub() {
let old = docker::SidecarLabels {
relay: None,
..sidecar(Some(docker::NODE_SHARED))
};
assert_eq!(
sidecar_outdated(Some(&old), Some(THIS_NODE)),
SidecarCheck::Outdated("predates the mesh relay"),
"not AskHub"
);
let p = plan(Some(docker_proxy()), Some(&old), None);
assert!(
matches!(p, ConnectPlan::RecreateSidecar { reason } if reason.contains("relay")),
"{p:?}"
);
assert_eq!(
sidecar_outdated(Some(&old), None),
SidecarCheck::Outdated("predates the mesh relay"),
"with no node key too"
);
}
#[test]
fn a_hostnet_sidecar_needs_no_relay() {
let hostnet = docker::SidecarLabels {
mode: docker::Mode::HostNet,
proxy: None,
relay: None,
..sidecar(Some(THIS_NODE))
};
assert_eq!(
sidecar_outdated(Some(&hostnet), Some(THIS_NODE)),
SidecarCheck::Current
);
let shared = docker::SidecarLabels {
node: Some(docker::NODE_SHARED.into()),
..hostnet
};
assert_eq!(
sidecar_outdated(Some(&shared), Some(THIS_NODE)),
SidecarCheck::AskHub,
"the relay rule is proxy-only, so a shared hostnet sidecar still asks the hub"
);
}
#[test]
fn connect_uses_a_host_tunnel_instead_of_starting_the_sidecar() {
let p = plan(None, None, Some(utun()));
assert!(
matches!(&p, ConnectPlan::UseHost { mesh, sidecar_left_up: false } if mesh.interface == "utun4"),
"{p:?}"
);
}
#[test]
fn connect_prefers_a_host_tunnel_over_a_running_sidecar() {
let current = sidecar(Some(THIS_NODE));
let p = plan(Some(docker_proxy()), Some(¤t), Some(utun()));
assert!(
matches!(
p,
ConnectPlan::UseHost {
sidecar_left_up: true,
..
}
),
"{p:?}"
);
let p = plan(Some(docker_proxy()), Some(&sidecar(None)), Some(utun()));
assert!(
matches!(
p,
ConnectPlan::UseHost {
sidecar_left_up: true,
..
}
),
"{p:?}"
);
}
#[test]
fn zcs_own_native_tunnel_stays_native() {
assert!(matches!(
plan(Some(native()), None, Some(utun())),
ConnectPlan::Reuse(i) if i.backend == Backend::Native
));
}
#[test]
fn an_explicit_backend_skips_the_host_tunnel() {
for pref in [Preference::Docker, Preference::Native] {
let p = plan_connect(pref, None, None, Some(utun()), Some(THIS_NODE));
assert!(matches!(p, ConnectPlan::Fresh), "{pref:?}: {p:?}");
}
let current = sidecar(Some(THIS_NODE));
let p = plan_connect(
Preference::Docker,
Some(docker_proxy()),
Some(¤t),
Some(utun()),
Some(THIS_NODE),
);
assert!(
matches!(&p, ConnectPlan::Reuse(i) if i.backend == Backend::Docker),
"--docker keeps the sidecar: {p:?}"
);
}
#[test]
fn an_explicit_backend_replaces_a_recorded_host_connection() {
for pref in [Preference::Docker, Preference::Native] {
let recorded = host::info_for(&utun());
let p = plan_connect(pref, Some(recorded), None, Some(utun()), Some(THIS_NODE));
assert!(matches!(p, ConnectPlan::Fresh), "{pref:?}: {p:?}");
}
}
#[test]
fn a_recorded_host_connection_follows_the_host_tunnel() {
let recorded = host::info_for(&utun());
let moved = LocalMesh {
ip: "10.13.13.8".into(),
interface: "utun5".into(),
};
let p = plan(Some(recorded.clone()), None, Some(moved));
assert!(
matches!(&p, ConnectPlan::UseHost { mesh, sidecar_left_up: false } if mesh.ip == "10.13.13.8"),
"{p:?}"
);
assert!(matches!(
plan(Some(recorded), None, None),
ConnectPlan::Fresh
));
}
#[test]
fn a_hostnet_sidecar_still_needs_this_devices_identity() {
let hostnet = ConnectionInfo {
host_routable: true,
proxy: None,
relay: None,
..docker_proxy()
};
let labels = docker::SidecarLabels {
mode: docker::Mode::HostNet,
proxy: None,
relay: None,
..sidecar(None)
};
let p = plan(Some(hostnet), Some(&labels), Some(utun()));
assert!(matches!(p, ConnectPlan::RecreateSidecar { .. }), "{p:?}");
}
#[test]
fn without_a_node_key_a_shared_hostnet_sidecar_is_reused() {
let hostnet = ConnectionInfo {
host_routable: true,
proxy: None,
relay: None,
..docker_proxy()
};
let labels = docker::SidecarLabels {
mode: docker::Mode::HostNet,
proxy: None,
relay: None,
..sidecar(Some(docker::NODE_SHARED))
};
let zakuro0 = LocalMesh {
ip: "10.13.13.7".into(),
interface: "zakuro0".into(),
};
let p = plan_connect(
Preference::Auto,
Some(hostnet),
Some(&labels),
Some(zakuro0),
None,
);
assert!(
matches!(&p, ConnectPlan::Reuse(i) if i.backend == Backend::Docker),
"{p:?}"
);
}
#[test]
fn a_host_tunnel_is_recorded_as_a_routable_host_connection() {
let info = host::info_for(&utun());
assert_eq!(info.backend, Backend::Host);
assert_eq!(
(info.address.as_str(), info.link.as_str()),
("10.13.13.7", "utun4")
);
assert!(info.host_routable && info.proxy.is_none() && info.relay.is_none());
assert_eq!(
connect_line(&info),
"Using the host VPN (utun4, 10.13.13.7)"
);
assert_eq!(Backend::Host.label(), "host");
assert_eq!(access_of(&info).unwrap(), MeshAccess::Host);
let json = serde_json::to_value(&info).unwrap();
assert_eq!(json["backend"], "Host");
let back: ConnectionInfo = serde_json::from_value(json).unwrap();
assert_eq!(back.backend, Backend::Host);
assert_eq!(connect_line(&docker_proxy()), render(&docker_proxy()));
}
#[test]
fn the_host_path_prints_no_node_key_hint() {
let mine = sidecar(Some(THIS_NODE));
let p = plan_connect(
Preference::Auto,
Some(docker_proxy()),
Some(&mine),
Some(utun()),
None,
);
assert!(
matches!(
p,
ConnectPlan::UseHost {
sidecar_left_up: true,
..
}
),
"{p:?}"
);
assert_eq!(key_hint(&p, Some(&mine), None), None);
let p = plan_connect(
Preference::Auto,
Some(docker_proxy()),
Some(&mine),
None,
None,
);
assert!(matches!(p, ConnectPlan::Reuse(_)), "{p:?}");
assert_eq!(
key_hint(&p, Some(&mine), None),
Some("⚠ this device's node key is missing; run zc login to recreate it")
);
}
#[test]
fn disconnect_says_a_host_tunnel_is_only_forgotten() {
assert_eq!(
disconnect_message(Some(&host::info_for(&utun()))),
"forgot the host VPN connection (utun4, 10.13.13.7); the tunnel itself is managed outside zc and stays up"
);
for owned in [Some(docker_proxy()), Some(native()), None] {
assert_eq!(
disconnect_message(owned.as_ref()),
"disconnected from zakuro mesh"
);
}
}
#[test]
fn the_host_route_advertises_the_host_address_on_any_port() {
let host = fixtures::host_route();
assert_eq!(host.advertised_ip(9000).as_deref(), Some("10.13.13.7"));
assert_eq!(host.advertised_ip(54321).as_deref(), Some("10.13.13.7"));
}
#[test]
fn the_proxy_route_advertises_the_container_address_only_inside_the_relay_range() {
let proxy = fixtures::proxy_route();
assert_eq!(proxy.advertised_ip(9000).as_deref(), Some("10.13.13.7"));
assert_eq!(proxy.advertised_ip(9010).as_deref(), Some("10.13.13.7"));
assert_eq!(proxy.advertised_ip(9011), None, "out of the relay range");
assert_eq!(proxy.advertised_ip(54321), None, "out of the relay range");
}
#[test]
fn a_proxy_route_without_the_relay_advertises_nothing() {
let without_relay = match fixtures::proxy_route() {
Route::Proxy {
mesh_ip,
connect_proxy,
..
} => Route::Proxy {
mesh_ip,
connect_proxy,
relay: false,
},
other => panic!("expected the Proxy route fixture, got {other:?}"),
};
assert_eq!(without_relay.advertised_ip(9000), None);
}
#[test]
fn no_route_advertises_nothing() {
assert_eq!(Route::None.advertised_ip(9000), None);
}
}