use anyhow::{Context, Result};
use std::sync::Arc;
use tokio::sync::Notify;
use crate::config::{Config, Role};
use super::interface::InterfaceManager;
pub struct Tunnel {
interface: InterfaceManager,
role: Role,
config: Config,
shutdown: Arc<Notify>,
}
impl Tunnel {
pub fn new(config: Config) -> Result<Self> {
let interface_name = config.interface().to_string();
let role = config.role();
Ok(Self {
interface: InterfaceManager::new(&interface_name),
role,
config,
shutdown: Arc::new(Notify::new()),
})
}
pub async fn start(&self) -> Result<()> {
tracing::info!("Starting WireGuard tunnel as {:?}", self.role);
match self.role {
Role::Client => self.start_client().await,
Role::Server => self.start_server().await,
}
}
async fn start_client(&self) -> Result<()> {
if let Config::Client(config) = &self.config {
self.interface.create(
Role::Client,
&config.local_ip,
0, )?;
let key_path = crate::config::client_key_path();
let private_key = std::fs::read_to_string(&key_path)
.context(format!("Failed to read private key from {}", key_path.display()))?;
self.interface.set_private_key(private_key.trim())?;
let endpoint = format!("{}:{}", config.server_address, config.server_port);
self.interface.add_peer(
&config.server_ip.split('/').next().unwrap_or(""),
Some(&endpoint),
&["0.0.0.0/0", "::/0"],
)?;
tracing::info!(
"Client tunnel started, connecting to {}:{}",
config.server_address,
config.server_port
);
}
Ok(())
}
async fn start_server(&self) -> Result<()> {
if let Config::Server(config) = &self.config {
self.interface.create(
Role::Server,
&config.ip,
config.listen_port,
)?;
let key_path = crate::config::server_key_path();
let private_key = std::fs::read_to_string(&key_path)
.context(format!("Failed to read private key from {}", key_path.display()))?;
self.interface.set_private_key(private_key.trim())?;
for client in &config.allowed_clients {
self.interface.add_peer(
&client.pubkey,
None, &["10.0.0.2/32"], )?;
tracing::info!("Added client peer: {} ({})", client.id, client.pubkey);
}
tracing::info!("Server tunnel started on port {}", config.listen_port);
}
Ok(())
}
pub async fn stop(&self) -> Result<()> {
tracing::info!("Stopping WireGuard tunnel");
self.interface.bring_down()?;
self.interface.delete()?;
tracing::info!("WireGuard tunnel stopped");
Ok(())
}
pub fn is_active(&self) -> bool {
self.interface.is_up()
}
pub fn stats(&self) -> TunnelStats {
TunnelStats {
is_active: self.interface.is_up(),
bytes_sent: 0,
bytes_received: 0,
packets_sent: 0,
packets_received: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct TunnelStats {
pub is_active: bool,
pub bytes_sent: u64,
pub bytes_received: u64,
pub packets_sent: u64,
pub packets_received: u64,
}