chore: sync konduit-platform

This commit is contained in:
Eugen Kaparulin
2026-06-08 09:11:15 +03:00
parent ee7898cfac
commit d3e6d89b6b
12 changed files with 2070 additions and 0 deletions

View File

@@ -0,0 +1,198 @@
use anyhow::Result;
use std::net::IpAddr;
use tracing::{info, warn};
pub struct MacOsNetManager {
pub tun_name: String,
server_host: String,
original_gateway: String,
primary_service: String,
}
impl MacOsNetManager {
/// Discover the current default gateway and primary network service, then return a manager.
/// Uses blocking `std::process::Command` — call from a sync context or `spawn_blocking`.
pub fn new(tun_name: String, server_endpoint: &str) -> Result<Self> {
let server_host = server_endpoint
.split(':')
.next()
.unwrap_or(server_endpoint)
.to_string();
let route_out = std::process::Command::new("route")
.args(["-n", "get", "default"])
.output()?;
let route_text = String::from_utf8_lossy(&route_out.stdout).into_owned();
let (original_gateway, iface) = Self::parse_default_route(&route_text)?;
let svc_out = std::process::Command::new("networksetup")
.args(["-listnetworkserviceorder"])
.output()?;
let svc_text = String::from_utf8_lossy(&svc_out.stdout).into_owned();
let primary_service = Self::parse_primary_service(&svc_text, &iface)
.unwrap_or_else(|_| "Wi-Fi".to_string());
info!(
"MacOsNetManager: gateway={}, service={}, tun={}",
original_gateway, primary_service, tun_name
);
Ok(Self {
tun_name,
server_host,
original_gateway,
primary_service,
})
}
// Always adds split-default (0/1 + 128/1) for full-tunnel mode.
// Split-tunnel (per-route from ServerConfig) is not yet supported on macOS.
pub async fn setup_vpn_routes(&self) -> Result<()> {
run("route", &["add", "-host", &self.server_host, &self.original_gateway]).await;
run("route", &["add", "-net", "0.0.0.0/1", "-interface", &self.tun_name]).await;
run("route", &["add", "-net", "128.0.0.0/1", "-interface", &self.tun_name]).await;
info!("macOS VPN routes added via {}", self.tun_name);
Ok(())
}
/// Configure DNS for the primary network service.
pub async fn setup_dns(&self, dns_ips: &[IpAddr], _assigned_ip: IpAddr) -> Result<()> {
if dns_ips.is_empty() {
return Ok(());
}
let ip_strings: Vec<String> = dns_ips.iter().map(|ip| ip.to_string()).collect();
let mut args: Vec<&str> = vec!["-setdnsservers", self.primary_service.as_str()];
args.extend(ip_strings.iter().map(|s| s.as_str()));
run("networksetup", &args).await;
info!("macOS DNS configured: {:?}", dns_ips);
Ok(())
}
/// Remove split-default routes so traffic falls through to WiFi during reconnect.
pub async fn suspend_default_route(&self) -> Result<()> {
run("route", &["delete", "-net", "0.0.0.0/1"]).await;
run("route", &["delete", "-net", "128.0.0.0/1"]).await;
info!("macOS default route suspended");
Ok(())
}
/// Re-add split-default routes after successful reconnect.
pub async fn resume_default_route(&self) -> Result<()> {
run("route", &["add", "-net", "0.0.0.0/1", "-interface", &self.tun_name]).await;
run("route", &["add", "-net", "128.0.0.0/1", "-interface", &self.tun_name]).await;
info!("macOS default route resumed via {}", self.tun_name);
Ok(())
}
// NOTE: original_gateway is captured once at construction time.
// If the WiFi gateway changes (network roaming), this route may go via
// the wrong gateway until the next reconnect that creates a new MacOsNetManager.
pub async fn ensure_server_route(&self) -> Result<()> {
run("route", &["delete", "-host", &self.server_host]).await;
run("route", &["add", "-host", &self.server_host, &self.original_gateway]).await;
Ok(())
}
/// Remove all VPN routes.
pub async fn restore_routes(&self) -> Result<()> {
run("route", &["delete", "-host", &self.server_host]).await;
run("route", &["delete", "-net", "0.0.0.0/1"]).await;
run("route", &["delete", "-net", "128.0.0.0/1"]).await;
info!("macOS VPN routes removed");
Ok(())
}
/// Revert DNS to automatic (empty = use DHCP-provided DNS).
pub async fn restore_dns(&self) -> Result<()> {
run("networksetup", &["-setdnsservers", &self.primary_service, "Empty"]).await;
info!("macOS DNS restored to automatic");
Ok(())
}
fn parse_default_route(output: &str) -> Result<(String, String)> {
let gateway = output
.lines()
.find(|l| l.trim_start().starts_with("gateway:"))
.and_then(|l| l.splitn(2, ':').nth(1))
.map(|s| s.trim().to_string())
.ok_or_else(|| anyhow::anyhow!("Could not find default gateway in route output"))?;
let iface = output
.lines()
.find(|l| l.trim_start().starts_with("interface:"))
.and_then(|l| l.splitn(2, ':').nth(1))
.map(|s| s.trim().to_string())
.ok_or_else(|| anyhow::anyhow!("Could not find default interface in route output"))?;
Ok((gateway, iface))
}
fn parse_primary_service(output: &str, iface: &str) -> Result<String> {
let lines: Vec<&str> = output.lines().collect();
for i in 0..lines.len().saturating_sub(1) {
if lines[i + 1].contains(&format!("Device: {}", iface)) {
let name = lines[i].trim();
let name = name.splitn(2, ") ").nth(1).unwrap_or(name.trim());
return Ok(name.to_string());
}
}
anyhow::bail!("Could not find network service for interface {}", iface)
}
}
async fn run(cmd: &str, args: &[&str]) {
match tokio::process::Command::new(cmd).args(args).output().await {
Ok(out) if out.status.success() => {}
Ok(out) => warn!(
"{} {} failed: {}",
cmd,
args.join(" "),
String::from_utf8_lossy(&out.stderr).trim()
),
Err(e) => warn!("Failed to run {}: {}", cmd, e),
}
}
#[cfg(test)]
mod tests {
use super::*;
const ROUTE_OUTPUT: &str = " route to: default
destination: default
mask: default
gateway: 192.168.1.1
interface: en0
flags: <UP,GATEWAY,DONE,STATIC,PRCLONING,GLOBAL>
recvpipe sendpipe ssthresh rtt,msec rtt,var hopcount mtu expire
0 0 0 35 128 0 1500 0 ";
const SERVICES_OUTPUT: &str = "An asterisk (*) denotes that a network service is disabled.
(1) Wi-Fi
(Hardware Port: Wi-Fi, Device: en0)
(2) Bluetooth PAN
(Hardware Port: Bluetooth PAN, Device: en1)
(3) Thunderbolt Bridge
(Hardware Port: Thunderbolt Bridge, Device: bridge0)
";
#[test]
fn parses_gateway_and_interface() {
let (gw, iface) = MacOsNetManager::parse_default_route(ROUTE_OUTPUT).unwrap();
assert_eq!(gw, "192.168.1.1");
assert_eq!(iface, "en0");
}
#[test]
fn parses_primary_service() {
let svc = MacOsNetManager::parse_primary_service(SERVICES_OUTPUT, "en0").unwrap();
assert_eq!(svc, "Wi-Fi");
}
#[test]
fn parse_service_unknown_iface_returns_err() {
let result = MacOsNetManager::parse_primary_service(SERVICES_OUTPUT, "en99");
assert!(result.is_err());
}
}