215 lines
6.3 KiB
Rust
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
|
|
}
|
|
}
|