chore: sync konduit-platform
This commit is contained in:
@@ -13,6 +13,12 @@ pub struct RouteManager {
|
||||
tun_routes: Vec<(Ipv4Addr, u8)>,
|
||||
server_ip: Option<Ipv4Addr>,
|
||||
wifi_gateway: Option<Ipv4Addr>,
|
||||
// Physical interface name the server host route must be pinned to. Without
|
||||
// this, if the VPN's assigned tun subnet happens to contain the physical
|
||||
// gateway IP, `route add`'s gateway-only resolution can pick the tun
|
||||
// interface instead of the physical NIC, looping the server route back
|
||||
// into the tunnel it's meant to bypass.
|
||||
wifi_iface: Option<String>,
|
||||
}
|
||||
|
||||
impl RouteManager {
|
||||
@@ -27,17 +33,23 @@ impl RouteManager {
|
||||
tun_routes: Vec::new(),
|
||||
server_ip: None,
|
||||
wifi_gateway: None,
|
||||
wifi_iface: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_default_gateway() -> Result<Ipv4Addr> {
|
||||
/// Returns the physical default gateway and the friendly name of the
|
||||
/// adapter it's reachable on. The interface name must be used to pin any
|
||||
/// route meant to bypass the tunnel — relying on gateway-only resolution
|
||||
/// lets `route add` route it via the TUN adapter instead if the VPN's
|
||||
/// assigned subnet happens to contain this gateway IP.
|
||||
fn get_default_gateway() -> Result<(Ipv4Addr, String)> {
|
||||
let adapters = ipconfig::get_adapters()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to enumerate adapters: {}", e))?;
|
||||
for adapter in &adapters {
|
||||
for gw in adapter.gateways() {
|
||||
if let IpAddr::V4(gw4) = gw {
|
||||
if !gw4.is_loopback() && !gw4.is_unspecified() {
|
||||
return Ok(*gw4);
|
||||
return Ok((*gw4, adapter.friendly_name().to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,29 +57,26 @@ impl RouteManager {
|
||||
anyhow::bail!("No default gateway found")
|
||||
}
|
||||
|
||||
fn prefix_to_mask(prefix: u8) -> Ipv4Addr {
|
||||
let bits = if prefix == 0 {
|
||||
0u32
|
||||
} else {
|
||||
!0u32 << (32 - prefix)
|
||||
};
|
||||
let [a, b, c, d] = bits.to_be_bytes();
|
||||
Ipv4Addr::new(a, b, c, d)
|
||||
}
|
||||
|
||||
// Add/delete server host route through WiFi gateway via `route` command.
|
||||
// Specifying gateway on delete ensures we only remove our entry, not any other path.
|
||||
fn wifi_route_add(dest: Ipv4Addr, prefix: u8, gateway: Ipv4Addr) {
|
||||
let mask = Self::prefix_to_mask(prefix);
|
||||
let out = std::process::Command::new("route")
|
||||
// Add/delete the server host route through the physical WiFi/Ethernet
|
||||
// interface, pinned explicitly by adapter name via `netsh` rather than
|
||||
// `route add`'s gateway-only resolution — same rationale and technique as
|
||||
// `tun_route_add` below: without an explicit interface, an IP collision
|
||||
// between the VPN's assigned tun subnet and the physical gateway can make
|
||||
// this route resolve onto the TUN adapter instead, looping the server
|
||||
// route back into the tunnel it's meant to bypass.
|
||||
fn wifi_route_add(iface: &str, dest: Ipv4Addr, prefix: u8, gateway: Ipv4Addr) {
|
||||
let prefix_str = format!("{}/{}", dest, prefix);
|
||||
let out = std::process::Command::new("netsh")
|
||||
.args([
|
||||
"interface",
|
||||
"ipv4",
|
||||
"add",
|
||||
&dest.to_string(),
|
||||
"MASK",
|
||||
&mask.to_string(),
|
||||
&gateway.to_string(),
|
||||
"METRIC",
|
||||
"1",
|
||||
"route",
|
||||
&prefix_str,
|
||||
iface,
|
||||
&format!("nexthop={}", gateway),
|
||||
"metric=1",
|
||||
"store=active",
|
||||
])
|
||||
.creation_flags(CREATE_NO_WINDOW)
|
||||
.output();
|
||||
@@ -76,25 +85,19 @@ impl RouteManager {
|
||||
let stdout = String::from_utf8_lossy(&o.stdout);
|
||||
if !o.status.success() {
|
||||
// "already exists" is acceptable — a previous run may have left it
|
||||
warn!("route add {}/{} via {}: {}", dest, prefix, gateway, stdout.trim());
|
||||
warn!("netsh add route {}/{} via {} on {}: {}", dest, prefix, gateway, iface, stdout.trim());
|
||||
}
|
||||
}
|
||||
Err(e) => warn!("route add {}/{} via {}: {}", dest, prefix, gateway, e),
|
||||
Err(e) => warn!("netsh add route {}/{} via {} on {}: {}", dest, prefix, gateway, iface, e),
|
||||
}
|
||||
}
|
||||
|
||||
fn wifi_route_delete(dest: Ipv4Addr, prefix: u8, gateway: Ipv4Addr) {
|
||||
let mask = Self::prefix_to_mask(prefix);
|
||||
let _ = std::process::Command::new("route")
|
||||
.args([
|
||||
"delete",
|
||||
&dest.to_string(),
|
||||
"MASK",
|
||||
&mask.to_string(),
|
||||
&gateway.to_string(),
|
||||
])
|
||||
fn wifi_route_delete(iface: &str, dest: Ipv4Addr, prefix: u8) {
|
||||
let prefix_str = format!("{}/{}", dest, prefix);
|
||||
let _ = std::process::Command::new("netsh")
|
||||
.args(["interface", "ipv4", "delete", "route", &prefix_str, iface])
|
||||
.creation_flags(CREATE_NO_WINDOW)
|
||||
.status();
|
||||
.output();
|
||||
}
|
||||
|
||||
// Add/delete routes through the TUN interface using netsh.
|
||||
@@ -169,8 +172,9 @@ impl RouteManager {
|
||||
) -> Result<()> {
|
||||
info!("Setting up Windows VPN routes...");
|
||||
|
||||
let current_gateway = Self::get_default_gateway()?;
|
||||
info!("Current gateway: {}", current_gateway);
|
||||
let (current_gateway, wifi_iface) = Self::get_default_gateway()?;
|
||||
info!("Current gateway: {} (interface {})", current_gateway, wifi_iface);
|
||||
self.wifi_iface = Some(wifi_iface.clone());
|
||||
|
||||
let server_ip: Ipv4Addr = {
|
||||
let host = server_addr.split(':').next().unwrap_or(server_addr);
|
||||
@@ -189,7 +193,7 @@ impl RouteManager {
|
||||
// even if the add below fails because the route already exists.
|
||||
self.server_ip = Some(server_ip);
|
||||
self.wifi_gateway = Some(current_gateway);
|
||||
Self::wifi_route_add(server_ip, 32, current_gateway);
|
||||
Self::wifi_route_add(&wifi_iface, server_ip, 32, current_gateway);
|
||||
|
||||
let is_full_tunnel = routes.iter().any(|r| r == "0.0.0.0/0");
|
||||
|
||||
@@ -242,8 +246,10 @@ impl RouteManager {
|
||||
}
|
||||
|
||||
pub async fn ensure_server_route(&mut self) -> Result<()> {
|
||||
if let (Some(server_ip), Some(gateway)) = (self.server_ip, self.wifi_gateway) {
|
||||
Self::wifi_route_add(server_ip, 32, gateway);
|
||||
if let (Some(server_ip), Some(gateway), Some(iface)) =
|
||||
(self.server_ip, self.wifi_gateway, self.wifi_iface.as_deref())
|
||||
{
|
||||
Self::wifi_route_add(iface, server_ip, 32, gateway);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -280,8 +286,9 @@ impl RouteManager {
|
||||
}
|
||||
// Always clean up the server host route — a previous unclean exit may have
|
||||
// left it behind even if our add was skipped.
|
||||
if let (Some(server_ip), Some(gateway)) = (self.server_ip.take(), self.wifi_gateway.take()) {
|
||||
Self::wifi_route_delete(server_ip, 32, gateway);
|
||||
self.wifi_gateway.take();
|
||||
if let (Some(server_ip), Some(iface)) = (self.server_ip.take(), self.wifi_iface.take()) {
|
||||
Self::wifi_route_delete(&iface, server_ip, 32);
|
||||
}
|
||||
info!("Windows VPN routes removed");
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user