use anyhow::{Context, Result};
use crate::config::Role;
pub struct InterfaceManager {
name: String,
}
impl InterfaceManager {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
}
}
pub fn create(&self, role: Role, ip: &str, listen_port: u16) -> Result<()> {
self.create_interface()?;
self.configure_ip(ip)?;
match role {
Role::Server => {
self.set_listen_port(listen_port)?;
}
Role::Client => {
}
}
self.bring_up()?;
Ok(())
}
fn create_interface(&self) -> Result<()> {
if self.interface_exists() {
tracing::info!("Interface {} already exists, skipping creation", self.name);
return Ok(());
}
let status = std::process::Command::new("ip")
.args(["link", "add", &self.name, "type", "wireguard"])
.status()
.context("Failed to execute 'ip link add'")?;
if !status.success() {
return Err(anyhow::anyhow!(
"Failed to create WireGuard interface '{}'. Check if wireguard module is loaded.",
self.name
));
}
tracing::info!("Created WireGuard interface: {}", self.name);
Ok(())
}
fn configure_ip(&self, ip: &str) -> Result<()> {
let _ = std::process::Command::new("ip")
.args(["addr", "flush", "dev", &self.name])
.status();
let status = std::process::Command::new("ip")
.args(["addr", "add", ip, "dev", &self.name])
.status()
.context("Failed to execute 'ip addr add'")?;
if !status.success() {
return Err(anyhow::anyhow!(
"Failed to configure IP {} on interface {}",
ip,
self.name
));
}
tracing::info!("Configured IP {} on interface {}", ip, self.name);
Ok(())
}
fn set_listen_port(&self, port: u16) -> Result<()> {
let status = std::process::Command::new("wg")
.args(["set", &self.name, "listen-port", &port.to_string()])
.status()
.context("Failed to execute 'wg set listen-port'")?;
if !status.success() {
return Err(anyhow::anyhow!(
"Failed to set listen port {} on interface {}",
port,
self.name
));
}
tracing::info!("Set listen port {} on interface {}", port, self.name);
Ok(())
}
pub fn set_private_key(&self, private_key: &str) -> Result<()> {
let status = std::process::Command::new("wg")
.args(["set", &self.name, "private-key", private_key])
.status()
.context("Failed to execute 'wg set private-key'")?;
if !status.success() {
return Err(anyhow::anyhow!(
"Failed to set private key on interface {}",
self.name
));
}
tracing::info!("Set private key on interface {}", self.name);
Ok(())
}
pub fn add_peer(&self, public_key: &str, endpoint: Option<&str>, allowed_ips: &[&str]) -> Result<()> {
let mut args = vec![
"set".to_string(),
self.name.clone(),
"peer".to_string(),
public_key.to_string(),
];
if let Some(ep) = endpoint {
args.push("endpoint".to_string());
args.push(ep.to_string());
}
if !allowed_ips.is_empty() {
let joined = allowed_ips.join(",");
args.push("allowed-ips".to_string());
args.push(joined);
}
let status = std::process::Command::new("wg")
.args(&args)
.status()
.context("Failed to execute 'wg set peer'")?;
if !status.success() {
return Err(anyhow::anyhow!(
"Failed to add peer {} to interface {}",
public_key,
self.name
));
}
tracing::info!("Added peer {} to interface {}", public_key, self.name);
Ok(())
}
pub fn remove_peer(&self, public_key: &str) -> Result<()> {
let status = std::process::Command::new("wg")
.args(["set", &self.name, "peer", public_key, "remove"])
.status()
.context("Failed to execute 'wg set peer remove'")?;
if !status.success() {
return Err(anyhow::anyhow!(
"Failed to remove peer {} from interface {}",
public_key,
self.name
));
}
tracing::info!("Removed peer {} from interface {}", public_key, self.name);
Ok(())
}
fn bring_up(&self) -> Result<()> {
let status = std::process::Command::new("ip")
.args(["link", "set", &self.name, "up"])
.status()
.context("Failed to execute 'ip link set up'")?;
if !status.success() {
return Err(anyhow::anyhow!(
"Failed to bring up interface {}",
self.name
));
}
tracing::info!("Brought up interface: {}", self.name);
Ok(())
}
pub fn bring_down(&self) -> Result<()> {
let status = std::process::Command::new("ip")
.args(["link", "set", &self.name, "down"])
.status()
.context("Failed to execute 'ip link set down'")?;
if !status.success() {
tracing::warn!("Failed to bring down interface {}", self.name);
} else {
tracing::info!("Brought down interface: {}", self.name);
}
Ok(())
}
pub fn delete(&self) -> Result<()> {
self.bring_down()?;
let status = std::process::Command::new("ip")
.args(["link", "delete", &self.name])
.status()
.context("Failed to execute 'ip link delete'")?;
if !status.success() {
tracing::warn!("Failed to delete interface {}", self.name);
} else {
tracing::info!("Deleted interface: {}", self.name);
}
Ok(())
}
fn interface_exists(&self) -> bool {
std::process::Command::new("ip")
.args(["link", "show", &self.name])
.status()
.map(|s| s.success())
.unwrap_or(false)
}
pub fn is_up(&self) -> bool {
std::process::Command::new("ip")
.args(["link", "show", &self.name])
.output()
.map(|o| {
let stdout = String::from_utf8_lossy(&o.stdout);
stdout.contains("state UP")
})
.unwrap_or(false)
}
pub fn add_route(&self, destination: &str, gateway: &str) -> Result<()> {
let status = std::process::Command::new("ip")
.args(["route", "add", destination, "via", gateway, "dev", &self.name])
.status()
.context("Failed to execute 'ip route add'")?;
if !status.success() {
tracing::warn!("Failed to add route {} via {}", destination, gateway);
}
Ok(())
}
}