Files
nx9-wg/crates/nx9-wg-network/src/engine.rs
T

215 lines
6.3 KiB
Rust

//! Network and firewall synchronization engine.
use crate::error::Result;
use crate::forwarding::IpForwardingStatus;
use crate::nftables::NftablesRulesetBuilder;
use ipnet::IpNet;
use nx9_wg_core::types::firewall::FirewallRule;
use nx9_wg_core::types::network::Route;
use std::sync::Arc;
use tokio::sync::RwLock;
/// Network Engine abstraction for route table reconciliation and nftables rule synchronization.
#[async_trait::async_trait]
pub trait NetworkEngine: Send + Sync {
/// Reconcile destination routes in the kernel routing table.
async fn sync_routes(&self, routes: &[Route]) -> Result<()>;
/// Synchronize the dedicated `table inet nx9_wg` nftables ruleset and NAT masquerade.
async fn sync_firewall(
&self,
rules: &[FirewallRule],
enable_nat: bool,
wg_subnets: &[IpNet],
) -> Result<()>;
/// Inspect kernel IP packet forwarding status.
async fn get_forwarding_status(&self) -> Result<IpForwardingStatus>;
/// Get current active generated nftables ruleset.
async fn get_active_nftables_ruleset(&self) -> Result<String>;
/// Check if desired routes have drift against live/active state.
async fn has_route_drift(&self, _routes: &[Route]) -> Result<bool> {
Ok(false)
}
}
/// In-memory simulated network engine for tests and non-root execution.
#[derive(Debug, Clone, Default)]
pub struct SimulatedNetworkEngine {
active_routes: Arc<RwLock<Vec<Route>>>,
active_ruleset: Arc<RwLock<String>>,
forwarding: Arc<RwLock<IpForwardingStatus>>,
}
impl SimulatedNetworkEngine {
pub fn new() -> Self {
Self {
active_routes: Arc::new(RwLock::new(Vec::new())),
active_ruleset: Arc::new(RwLock::new(String::new())),
forwarding: Arc::new(RwLock::new(IpForwardingStatus {
ipv4_enabled: true,
ipv6_enabled: true,
})),
}
}
pub async fn set_forwarding_status(&self, status: IpForwardingStatus) {
let mut fw = self.forwarding.write().await;
*fw = status;
}
}
#[async_trait::async_trait]
impl NetworkEngine for SimulatedNetworkEngine {
async fn sync_routes(&self, routes: &[Route]) -> Result<()> {
let enabled_routes: Vec<Route> = routes.iter().filter(|r| r.enabled).cloned().collect();
let mut active = self.active_routes.write().await;
*active = enabled_routes;
tracing::debug!(count = active.len(), "Simulated routes synchronized");
Ok(())
}
async fn sync_firewall(
&self,
rules: &[FirewallRule],
enable_nat: bool,
wg_subnets: &[IpNet],
) -> Result<()> {
let ruleset = NftablesRulesetBuilder::build(rules, enable_nat, wg_subnets);
let mut active = self.active_ruleset.write().await;
*active = ruleset;
tracing::debug!("Simulated nftables ruleset updated");
Ok(())
}
async fn get_forwarding_status(&self) -> Result<IpForwardingStatus> {
let fw = self.forwarding.read().await;
Ok(*fw)
}
async fn get_active_nftables_ruleset(&self) -> Result<String> {
let active = self.active_ruleset.read().await;
Ok(active.clone())
}
async fn has_route_drift(&self, routes: &[Route]) -> Result<bool> {
let enabled_routes: Vec<Route> = routes.iter().filter(|r| r.enabled).cloned().collect();
let active = self.active_routes.read().await;
Ok(enabled_routes != *active)
}
}
/// Linux Native Network Engine with RTNETLINK and direct procfs forwarding.
#[cfg(target_os = "linux")]
pub use crate::native_linux::{
FirewallDiagnostics, NativeLinuxNetworkEngine, NativeLinuxNftablesEngine,
};
/// Fallback Simulated Network Engine for non-Linux platforms and unit testing.
#[cfg(not(target_os = "linux"))]
#[derive(Debug, Clone, Default)]
pub struct NativeLinuxNetworkEngine {
fallback: SimulatedNetworkEngine,
}
#[cfg(not(target_os = "linux"))]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct FirewallDiagnostics {
pub table_exists: bool,
pub table_name: String,
pub family: String,
pub chain_count: usize,
pub chains: Vec<String>,
pub rule_count: usize,
pub nat_enabled: bool,
pub live_ruleset: Option<String>,
pub kernel_status: String,
}
#[cfg(not(target_os = "linux"))]
#[derive(Debug, Clone, Default)]
pub struct NativeLinuxNftablesEngine {
fallback: SimulatedNetworkEngine,
}
#[cfg(not(target_os = "linux"))]
impl NativeLinuxNftablesEngine {
pub fn new() -> Self {
Self {
fallback: SimulatedNetworkEngine::new(),
}
}
pub async fn table_exists(&self) -> Result<bool> {
Ok(false)
}
pub async fn get_live_ruleset(&self) -> Result<String> {
self.fallback.get_active_nftables_ruleset().await
}
pub async fn apply_ruleset(&self, ruleset: &str) -> Result<()> {
Ok(())
}
pub async fn delete_table(&self) -> Result<()> {
Ok(())
}
pub async fn diagnose(
&self,
_desired_rules: &[FirewallRule],
desired_nat: bool,
) -> Result<FirewallDiagnostics> {
Ok(FirewallDiagnostics {
table_exists: false,
table_name: "nx9_wg".to_string(),
family: "inet".to_string(),
chain_count: 0,
chains: Vec::new(),
rule_count: 0,
nat_enabled: desired_nat,
live_ruleset: None,
kernel_status: "simulated".to_string(),
})
}
}
#[cfg(not(target_os = "linux"))]
impl NativeLinuxNetworkEngine {
pub fn new() -> Self {
Self {
fallback: SimulatedNetworkEngine::new(),
}
}
}
#[cfg(not(target_os = "linux"))]
#[async_trait::async_trait]
impl NetworkEngine for NativeLinuxNetworkEngine {
async fn sync_routes(&self, routes: &[Route]) -> Result<()> {
self.fallback.sync_routes(routes).await
}
async fn sync_firewall(
&self,
rules: &[FirewallRule],
enable_nat: bool,
wg_subnets: &[IpNet],
) -> Result<()> {
self.fallback
.sync_firewall(rules, enable_nat, wg_subnets)
.await
}
async fn get_forwarding_status(&self) -> Result<IpForwardingStatus> {
IpForwardingStatus::detect()
}
async fn get_active_nftables_ruleset(&self) -> Result<String> {
self.fallback.get_active_nftables_ruleset().await
}
}