//! 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; /// Get current active generated nftables ruleset. async fn get_active_nftables_ruleset(&self) -> Result; /// Check if desired routes have drift against live/active state. async fn has_route_drift(&self, _routes: &[Route]) -> Result { Ok(false) } } /// In-memory simulated network engine for tests and non-root execution. #[derive(Debug, Clone, Default)] pub struct SimulatedNetworkEngine { active_routes: Arc>>, active_ruleset: Arc>, forwarding: Arc>, } 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 = 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 { let fw = self.forwarding.read().await; Ok(*fw) } async fn get_active_nftables_ruleset(&self) -> Result { let active = self.active_ruleset.read().await; Ok(active.clone()) } async fn has_route_drift(&self, routes: &[Route]) -> Result { let enabled_routes: Vec = 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, pub rule_count: usize, pub nat_enabled: bool, pub live_ruleset: Option, 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 { Ok(false) } pub async fn get_live_ruleset(&self) -> Result { 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 { 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::detect() } async fn get_active_nftables_ruleset(&self) -> Result { self.fallback.get_active_nftables_ruleset().await } }