use anyhow::Result; use std::net::Ipv4Addr; use tracing::info; /// Configuration for creating a TUN device. pub struct TunConfig { pub name: String, pub address: Ipv4Addr, pub netmask: Ipv4Addr, pub mtu: u16, } impl Default for TunConfig { fn default() -> Self { Self { name: "smartvpn0".to_string(), address: Ipv4Addr::new(10, 8, 0, 1), netmask: Ipv4Addr::new(255, 255, 255, 0), mtu: 1420, } } } /// Create and configure a TUN device. /// Returns an async TUN device handle. pub fn create_tun(config: &TunConfig) -> Result { let mut tun_config = tun::Configuration::default(); tun_config .tun_name(&config.name) .address(config.address) .netmask(config.netmask) .mtu(config.mtu as u16) .up(); #[cfg(target_os = "linux")] tun_config.platform_config(|p| { p.ensure_root_privileges(true); }); let device = tun::create_as_async(&tun_config)?; info!( "TUN device {} created: addr={}, mtu={}", config.name, config.address, config.mtu ); Ok(device) } /// Set up routing: add a route for the VPN subnet through the TUN device. pub async fn add_route(subnet: &str, device_name: &str) -> Result<()> { let output = tokio::process::Command::new("ip") .args(["route", "add", subnet, "dev", device_name]) .output() .await?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); // Ignore "File exists" errors (route already set) if !stderr.contains("File exists") { anyhow::bail!("Failed to add route: {}", stderr); } } info!("Added route {} via {}", subnet, device_name); Ok(()) } /// Remove a route. pub async fn remove_route(subnet: &str, device_name: &str) -> Result<()> { let output = tokio::process::Command::new("ip") .args(["route", "del", subnet, "dev", device_name]) .output() .await?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); tracing::warn!("Failed to remove route (may not exist): {}", stderr); } Ok(()) }