80 lines
2.2 KiB
Rust
80 lines
2.2 KiB
Rust
|
|
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<tun::AsyncDevice> {
|
||
|
|
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(())
|
||
|
|
}
|