diff --git a/src/bin/fips-gateway.rs b/src/bin/fips-gateway.rs index 0dbbffd..810d1e6 100644 --- a/src/bin/fips-gateway.rs +++ b/src/bin/fips-gateway.rs @@ -4,18 +4,18 @@ //! DNS-allocated virtual IPs and kernel nftables NAT. use clap::Parser; +use fips::Config; #[cfg(target_os = "linux")] use fips::gateway::{control, dns, nat, net, pool}; use fips::version; -use fips::Config; use std::path::PathBuf; -use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::time::Instant; -use tokio::signal::unix::{signal, SignalKind}; -use tokio::sync::{mpsc, watch, Mutex}; +use tokio::signal::unix::{SignalKind, signal}; +use tokio::sync::{Mutex, mpsc, watch}; use tracing::{error, info, warn}; -use tracing_subscriber::{fmt, EnvFilter}; +use tracing_subscriber::{EnvFilter, fmt}; /// FIPS outbound LAN gateway #[derive(Parser, Debug)] @@ -60,7 +60,11 @@ async fn main() { config } Err(e) => { - error!("Failed to load config from {}: {}", config_path.display(), e); + error!( + "Failed to load config from {}: {}", + config_path.display(), + e + ); std::process::exit(1); } } @@ -151,12 +155,16 @@ async fn main() { 0x00, 0x00, // NSCOUNT = 0 0x00, 0x00, // ARCOUNT = 0 // QNAME: "test.fips" - 0x04, b't', b'e', b's', b't', 0x04, b'f', b'i', b'p', b's', 0x00, - 0x00, 0x1C, // QTYPE = AAAA (28) + 0x04, b't', b'e', b's', b't', 0x04, b'f', b'i', b'p', b's', 0x00, 0x00, + 0x1C, // QTYPE = AAAA (28) 0x00, 0x01, // QCLASS = IN (1) ]; - let bind_addr = if upstream_addr.is_ipv4() { "0.0.0.0:0" } else { "[::]:0" }; + let bind_addr = if upstream_addr.is_ipv4() { + "0.0.0.0:0" + } else { + "[::]:0" + }; let sock = match tokio::net::UdpSocket::bind(bind_addr).await { Ok(s) => s, Err(e) => { @@ -174,11 +182,8 @@ async fn main() { } let mut buf = [0u8; 512]; - match tokio::time::timeout( - std::time::Duration::from_secs(3), - sock.recv_from(&mut buf), - ) - .await + match tokio::time::timeout(std::time::Duration::from_secs(3), sock.recv_from(&mut buf)) + .await { Ok(Ok(_)) => { info!(upstream = %upstream, "DNS upstream is reachable"); @@ -225,10 +230,7 @@ async fn main() { }; // Network setup - let mut net_setup = net::NetSetup::new( - gw_config.lan_interface.clone(), - gw_config.pool.clone(), - ); + let mut net_setup = net::NetSetup::new(gw_config.lan_interface.clone(), gw_config.pool.clone()); // Add pool route if let Err(e) = net_setup.add_pool_route().await { @@ -272,8 +274,7 @@ async fn main() { // --- Snapshot channel for control socket --- - let (snapshot_tx, snapshot_rx) = - watch::channel::>(None); + let (snapshot_tx, snapshot_rx) = watch::channel::>(None); let start_time = Instant::now(); // --- Start control socket --- diff --git a/src/bin/fips.rs b/src/bin/fips.rs index 9aeb6bd..84b2d25 100644 --- a/src/bin/fips.rs +++ b/src/bin/fips.rs @@ -3,12 +3,12 @@ //! Loads configuration and creates the top-level node instance. use clap::Parser; -use fips::config::{resolve_identity, IdentitySource}; +use fips::config::{IdentitySource, resolve_identity}; use fips::version; use fips::{Config, Node}; use std::path::PathBuf; use tracing::{debug, error, info, warn}; -use tracing_subscriber::{fmt, EnvFilter}; +use tracing_subscriber::{EnvFilter, fmt}; /// FIPS mesh network daemon #[derive(Parser, Debug)] @@ -34,7 +34,11 @@ async fn main() { match Config::load_file(config_path) { Ok(config) => (config, vec![config_path.clone()]), Err(e) => { - eprintln!("Failed to load configuration from {}: {}", config_path.display(), e); + eprintln!( + "Failed to load configuration from {}: {}", + config_path.display(), + e + ); std::process::exit(1); } } @@ -54,10 +58,7 @@ async fn main() { .with_default_directive(log_level.into()) .from_env_lossy(); - fmt() - .with_env_filter(filter) - .with_target(true) - .init(); + fmt().with_env_filter(filter).with_target(true).init(); info!("FIPS {} starting", version::short_version()); @@ -79,8 +80,12 @@ async fn main() { }; match &resolved.source { IdentitySource::Config => info!("Using identity from configuration"), - IdentitySource::KeyFile(p) => info!(path = %p.display(), "Loaded persistent identity from key file"), - IdentitySource::Generated(p) => info!(path = %p.display(), "Generated persistent identity, saved to key file"), + IdentitySource::KeyFile(p) => { + info!(path = %p.display(), "Loaded persistent identity from key file") + } + IdentitySource::Generated(p) => { + info!(path = %p.display(), "Generated persistent identity, saved to key file") + } IdentitySource::Ephemeral => info!("Using ephemeral identity (new keypair each start)"), } @@ -116,8 +121,9 @@ async fn main() { // stop() drops the packet channel, causing run_rx_loop to exit. #[cfg(unix)] let shutdown = async { - use tokio::signal::unix::{signal, SignalKind}; - let mut sigterm = signal(SignalKind::terminate()).expect("failed to register SIGTERM handler"); + use tokio::signal::unix::{SignalKind, signal}; + let mut sigterm = + signal(SignalKind::terminate()).expect("failed to register SIGTERM handler"); tokio::select! { _ = tokio::signal::ctrl_c() => {}, _ = sigterm.recv() => {}, diff --git a/src/bin/fipstop/app.rs b/src/bin/fipstop/app.rs index 191e2f4..93dd763 100644 --- a/src/bin/fipstop/app.rs +++ b/src/bin/fipstop/app.rs @@ -193,7 +193,10 @@ impl App { return; } let state = self.table_states.entry(self.active_tab).or_default(); - let i = state.selected().map(|s| (s + 1).min(count - 1)).unwrap_or(0); + let i = state + .selected() + .map(|s| (s + 1).min(count - 1)) + .unwrap_or(0); state.select(Some(i)); } diff --git a/src/bin/fipstop/main.rs b/src/bin/fipstop/main.rs index 33f759b..fee82b8 100644 --- a/src/bin/fipstop/main.rs +++ b/src/bin/fipstop/main.rs @@ -154,7 +154,9 @@ fn main() { let cli = Cli::parse(); let socket_path = cli.socket.unwrap_or_else(default_socket_path); - let gateway_socket_path = cli.gateway_socket.unwrap_or_else(default_gateway_socket_path); + let gateway_socket_path = cli + .gateway_socket + .unwrap_or_else(default_gateway_socket_path); let refresh = Duration::from_secs(cli.refresh); // Install panic hook that restores terminal before printing panic diff --git a/src/bin/fipstop/ui/gateway.rs b/src/bin/fipstop/ui/gateway.rs index 0a18aa8..4e9f614 100644 --- a/src/bin/fipstop/ui/gateway.rs +++ b/src/bin/fipstop/ui/gateway.rs @@ -34,8 +34,8 @@ fn draw_summary(frame: &mut Frame, app: &App, area: Rect) { let data = match app.data.get(&Tab::Gateway) { Some(d) => d, None => { - let msg = Paragraph::new(" Waiting for data...") - .style(Style::default().fg(Color::DarkGray)); + let msg = + Paragraph::new(" Waiting for data...").style(Style::default().fg(Color::DarkGray)); frame.render_widget(msg, area); return; } @@ -49,25 +49,41 @@ fn draw_summary(frame: &mut Frame, app: &App, area: Rect) { .split(area); // Pool and info section - let block = Block::default() - .borders(Borders::ALL) - .title(" Gateway "); + let block = Block::default().borders(Borders::ALL).title(" Gateway "); let inner = block.inner(chunks[0]); frame.render_widget(block, chunks[0]); let total = data.get("pool_total").and_then(|v| v.as_u64()).unwrap_or(0); - let allocated = data.get("pool_allocated").and_then(|v| v.as_u64()).unwrap_or(0); - let active = data.get("pool_active").and_then(|v| v.as_u64()).unwrap_or(0); - let draining = data.get("pool_draining").and_then(|v| v.as_u64()).unwrap_or(0); + let allocated = data + .get("pool_allocated") + .and_then(|v| v.as_u64()) + .unwrap_or(0); + let active = data + .get("pool_active") + .and_then(|v| v.as_u64()) + .unwrap_or(0); + let draining = data + .get("pool_draining") + .and_then(|v| v.as_u64()) + .unwrap_or(0); let free = data.get("pool_free").and_then(|v| v.as_u64()).unwrap_or(0); - let nat = data.get("nat_mappings").and_then(|v| v.as_u64()).unwrap_or(0); + let nat = data + .get("nat_mappings") + .and_then(|v| v.as_u64()) + .unwrap_or(0); let dns = helpers::str_field(data, "dns_listen"); - let uptime_secs = data.get("uptime_secs").and_then(|v| v.as_u64()).unwrap_or(0); + let uptime_secs = data + .get("uptime_secs") + .and_then(|v| v.as_u64()) + .unwrap_or(0); let pool_cidr = helpers::str_field(data, "pool_cidr"); let lan_iface = helpers::str_field(data, "lan_interface"); let dns_upstream = helpers::str_field(data, "dns_upstream"); let dns_ttl = data.get("dns_ttl").and_then(|v| v.as_u64()).unwrap_or(0); - let grace = data.get("pool_grace_period").and_then(|v| v.as_u64()).unwrap_or(0); + let grace = data + .get("pool_grace_period") + .and_then(|v| v.as_u64()) + .unwrap_or(0); let label = Style::default().fg(Color::DarkGray); let count = Style::default() @@ -201,9 +217,9 @@ fn draw_mappings(frame: &mut Frame, app: &mut App, area: Rect) { .collect(); let widths = [ - Constraint::Min(18), // Virtual IP - Constraint::Min(30), // DNS Name - Constraint::Min(26), // Mesh Addr + Constraint::Min(18), // Virtual IP + Constraint::Min(30), // DNS Name + Constraint::Min(26), // Mesh Addr Constraint::Length(10), // State Constraint::Length(10), // Sessions Constraint::Length(10), // Age diff --git a/src/config/mod.rs b/src/config/mod.rs index ee2df6b..d536d9a 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -30,15 +30,18 @@ use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; use thiserror::Error; +#[cfg(feature = "gateway")] +pub use gateway::{ConntrackConfig, GatewayConfig, GatewayDnsConfig}; pub use node::{ BloomConfig, BuffersConfig, CacheConfig, ControlConfig, DiscoveryConfig, LimitsConfig, NodeConfig, RateLimitConfig, RekeyConfig, RetryConfig, SessionConfig, SessionMmpConfig, TreeConfig, }; -#[cfg(feature = "gateway")] -pub use gateway::{ConntrackConfig, GatewayConfig, GatewayDnsConfig}; pub use peer::{ConnectPolicy, PeerAddress, PeerConfig}; -pub use transport::{BleConfig, DirectoryServiceConfig, EthernetConfig, TcpConfig, TorConfig, TransportInstances, TransportsConfig, UdpConfig}; +pub use transport::{ + BleConfig, DirectoryServiceConfig, EthernetConfig, TcpConfig, TorConfig, TransportInstances, + TransportsConfig, UdpConfig, +}; /// Default config filename. const CONFIG_FILENAME: &str = "fips.yaml"; @@ -553,10 +556,7 @@ node: override_config.node.identity.nsec = Some("override_nsec".to_string()); base.merge(override_config); - assert_eq!( - base.node.identity.nsec, - Some("override_nsec".to_string()) - ); + assert_eq!(base.node.identity.nsec, Some("override_nsec".to_string())); } #[test] @@ -573,9 +573,8 @@ node: #[test] fn test_create_identity_from_nsec() { let mut config = Config::new(); - config.node.identity.nsec = Some( - "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20".to_string(), - ); + config.node.identity.nsec = + Some("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20".to_string()); let identity = config.create_identity().unwrap(); assert!(!identity.npub().is_empty()); @@ -674,9 +673,11 @@ node: assert!(paths.iter().any(|p| p.ends_with("fips.yaml"))); // Should include /etc/fips - assert!(paths - .iter() - .any(|p| p.starts_with("/etc/fips") && p.ends_with("fips.yaml"))); + assert!( + paths + .iter() + .any(|p| p.starts_with("/etc/fips") && p.ends_with("fips.yaml")) + ); } #[test] @@ -761,16 +762,21 @@ node: #[test] fn test_key_file_path_derivation() { let config_path = PathBuf::from("/etc/fips/fips.yaml"); - assert_eq!(key_file_path(&config_path), PathBuf::from("/etc/fips/fips.key")); - assert_eq!(pub_file_path(&config_path), PathBuf::from("/etc/fips/fips.pub")); + assert_eq!( + key_file_path(&config_path), + PathBuf::from("/etc/fips/fips.key") + ); + assert_eq!( + pub_file_path(&config_path), + PathBuf::from("/etc/fips/fips.pub") + ); } #[test] fn test_resolve_identity_from_config() { let mut config = Config::new(); - config.node.identity.nsec = Some( - "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20".to_string(), - ); + config.node.identity.nsec = + Some("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20".to_string()); let resolved = resolve_identity(&config, &[]).unwrap(); assert!(matches!(resolved.source, IdentitySource::Config)); @@ -817,11 +823,7 @@ node: let config_path = temp_dir.path().join("fips.yaml"); let key_path = temp_dir.path().join("fips.key"); - fs::write( - &config_path, - "node:\n identity:\n persistent: true\n", - ) - .unwrap(); + fs::write(&config_path, "node:\n identity:\n persistent: true\n").unwrap(); // Write a key file let identity = crate::Identity::generate(); @@ -841,11 +843,7 @@ node: let temp_dir = TempDir::new().unwrap(); let config_path = temp_dir.path().join("fips.yaml"); - fs::write( - &config_path, - "node:\n identity:\n persistent: true\n", - ) - .unwrap(); + fs::write(&config_path, "node:\n identity:\n persistent: true\n").unwrap(); let config = Config::load_file(&config_path).unwrap(); let resolved = resolve_identity(&config, std::slice::from_ref(&config_path)).unwrap(); @@ -906,8 +904,7 @@ transports: assert_eq!(config.transports.udp.len(), 2); - let instances: std::collections::HashMap<_, _> = - config.transports.udp.iter().collect(); + let instances: std::collections::HashMap<_, _> = config.transports.udp.iter().collect(); // Named instances have Some(name) assert!(instances.contains_key(&Some("main"))); diff --git a/src/config/node.rs b/src/config/node.rs index 4367177..1499030 100644 --- a/src/config/node.rs +++ b/src/config/node.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use super::IdentityConfig; -use crate::mmp::{MmpConfig, MmpMode, DEFAULT_LOG_INTERVAL_SECS, DEFAULT_OWD_WINDOW_SIZE}; +use crate::mmp::{DEFAULT_LOG_INTERVAL_SECS, DEFAULT_OWD_WINDOW_SIZE, MmpConfig, MmpMode}; // ============================================================================ // Node Configuration Subsections @@ -42,10 +42,18 @@ impl Default for LimitsConfig { } impl LimitsConfig { - fn default_max_connections() -> usize { 256 } - fn default_max_peers() -> usize { 128 } - fn default_max_links() -> usize { 256 } - fn default_max_pending_inbound() -> usize { 1000 } + fn default_max_connections() -> usize { + 256 + } + fn default_max_peers() -> usize { + 128 + } + fn default_max_links() -> usize { + 256 + } + fn default_max_pending_inbound() -> usize { + 1000 + } } /// Rate limiting (`node.rate_limit.*`). @@ -86,12 +94,24 @@ impl Default for RateLimitConfig { } impl RateLimitConfig { - fn default_handshake_burst() -> u32 { 100 } - fn default_handshake_rate() -> f64 { 10.0 } - fn default_handshake_timeout_secs() -> u64 { 30 } - fn default_handshake_resend_interval_ms() -> u64 { 1000 } - fn default_handshake_resend_backoff() -> f64 { 2.0 } - fn default_handshake_max_resends() -> u32 { 5 } + fn default_handshake_burst() -> u32 { + 100 + } + fn default_handshake_rate() -> f64 { + 10.0 + } + fn default_handshake_timeout_secs() -> u64 { + 30 + } + fn default_handshake_resend_interval_ms() -> u64 { + 1000 + } + fn default_handshake_resend_backoff() -> f64 { + 2.0 + } + fn default_handshake_max_resends() -> u32 { + 5 + } } /// Retry/backoff configuration (`node.retry.*`). @@ -119,9 +139,15 @@ impl Default for RetryConfig { } impl RetryConfig { - fn default_max_retries() -> u32 { 5 } - fn default_base_interval_secs() -> u64 { 5 } - fn default_max_backoff_secs() -> u64 { 300 } + fn default_max_retries() -> u32 { + 5 + } + fn default_base_interval_secs() -> u64 { + 5 + } + fn default_max_backoff_secs() -> u64 { + 300 + } } /// Cache parameters (`node.cache.*`). @@ -149,9 +175,15 @@ impl Default for CacheConfig { } impl CacheConfig { - fn default_coord_size() -> usize { 50_000 } - fn default_coord_ttl_secs() -> u64 { 300 } - fn default_identity_size() -> usize { 10_000 } + fn default_coord_size() -> usize { + 50_000 + } + fn default_coord_ttl_secs() -> u64 { + 300 + } + fn default_identity_size() -> usize { + 10_000 + } } /// Discovery protocol (`node.discovery.*`). @@ -205,14 +237,30 @@ impl Default for DiscoveryConfig { } impl DiscoveryConfig { - fn default_ttl() -> u8 { 64 } - fn default_timeout_secs() -> u64 { 10 } - fn default_recent_expiry_secs() -> u64 { 10 } - fn default_backoff_base_secs() -> u64 { 30 } - fn default_backoff_max_secs() -> u64 { 300 } - fn default_forward_min_interval_secs() -> u64 { 2 } - fn default_retry_interval_secs() -> u64 { 5 } - fn default_max_attempts() -> u8 { 2 } + fn default_ttl() -> u8 { + 64 + } + fn default_timeout_secs() -> u64 { + 10 + } + fn default_recent_expiry_secs() -> u64 { + 10 + } + fn default_backoff_base_secs() -> u64 { + 30 + } + fn default_backoff_max_secs() -> u64 { + 300 + } + fn default_forward_min_interval_secs() -> u64 { + 2 + } + fn default_retry_interval_secs() -> u64 { + 5 + } + fn default_max_attempts() -> u8 { + 2 + } } /// Spanning tree (`node.tree.*`). @@ -267,13 +315,27 @@ impl Default for TreeConfig { } impl TreeConfig { - fn default_announce_min_interval_ms() -> u64 { 500 } - fn default_parent_hysteresis() -> f64 { 0.2 } - fn default_hold_down_secs() -> u64 { 30 } - fn default_reeval_interval_secs() -> u64 { 60 } - fn default_flap_threshold() -> u32 { 4 } - fn default_flap_window_secs() -> u64 { 60 } - fn default_flap_dampening_secs() -> u64 { 120 } + fn default_announce_min_interval_ms() -> u64 { + 500 + } + fn default_parent_hysteresis() -> f64 { + 0.2 + } + fn default_hold_down_secs() -> u64 { + 30 + } + fn default_reeval_interval_secs() -> u64 { + 60 + } + fn default_flap_threshold() -> u32 { + 4 + } + fn default_flap_window_secs() -> u64 { + 60 + } + fn default_flap_dampening_secs() -> u64 { + 120 + } } /// Bloom filter (`node.bloom.*`). @@ -286,12 +348,16 @@ pub struct BloomConfig { impl Default for BloomConfig { fn default() -> Self { - Self { update_debounce_ms: 500 } + Self { + update_debounce_ms: 500, + } } } impl BloomConfig { - fn default_update_debounce_ms() -> u64 { 500 } + fn default_update_debounce_ms() -> u64 { + 500 + } } /// Session/data plane (`node.session.*`). @@ -337,12 +403,24 @@ impl Default for SessionConfig { } impl SessionConfig { - fn default_ttl() -> u8 { 64 } - fn default_pending_packets_per_dest() -> usize { 16 } - fn default_pending_max_destinations() -> usize { 256 } - fn default_idle_timeout_secs() -> u64 { 90 } - fn default_coords_warmup_packets() -> u8 { 5 } - fn default_coords_response_interval_ms() -> u64 { 2000 } + fn default_ttl() -> u8 { + 64 + } + fn default_pending_packets_per_dest() -> usize { + 16 + } + fn default_pending_max_destinations() -> usize { + 256 + } + fn default_idle_timeout_secs() -> u64 { + 90 + } + fn default_coords_warmup_packets() -> u8 { + 5 + } + fn default_coords_response_interval_ms() -> u64 { + 2000 + } } /// Session-layer Metrics Measurement Protocol (`node.session_mmp.*`). @@ -377,8 +455,12 @@ impl Default for SessionMmpConfig { } impl SessionMmpConfig { - fn default_log_interval_secs() -> u64 { DEFAULT_LOG_INTERVAL_SECS } - fn default_owd_window_size() -> usize { DEFAULT_OWD_WINDOW_SIZE } + fn default_log_interval_secs() -> u64 { + DEFAULT_LOG_INTERVAL_SECS + } + fn default_owd_window_size() -> usize { + DEFAULT_OWD_WINDOW_SIZE + } } /// Control socket configuration (`node.control.*`). @@ -402,7 +484,9 @@ impl Default for ControlConfig { } impl ControlConfig { - fn default_enabled() -> bool { true } + fn default_enabled() -> bool { + true + } fn default_socket_path() -> String { if let Ok(runtime_dir) = std::env::var("XDG_RUNTIME_DIR") { @@ -440,9 +524,15 @@ impl Default for BuffersConfig { } impl BuffersConfig { - fn default_packet_channel() -> usize { 1024 } - fn default_tun_channel() -> usize { 1024 } - fn default_dns_channel() -> usize { 64 } + fn default_packet_channel() -> usize { + 1024 + } + fn default_tun_channel() -> usize { + 1024 + } + fn default_dns_channel() -> usize { + 64 + } } // ============================================================================ @@ -480,9 +570,15 @@ impl Default for RekeyConfig { } impl RekeyConfig { - fn default_enabled() -> bool { true } - fn default_after_secs() -> u64 { 120 } - fn default_after_messages() -> u64 { 1 << 16 } + fn default_enabled() -> bool { + true + } + fn default_after_secs() -> u64 { + 120 + } + fn default_after_messages() -> u64 { + 1 << 16 + } } /// ECN congestion signaling configuration (`node.ecn.*`). @@ -520,9 +616,15 @@ impl Default for EcnConfig { } impl EcnConfig { - fn default_enabled() -> bool { true } - fn default_loss_threshold() -> f64 { 0.05 } - fn default_etx_threshold() -> f64 { 3.0 } + fn default_enabled() -> bool { + true + } + fn default_loss_threshold() -> f64 { + 0.05 + } + fn default_etx_threshold() -> f64 { + 3.0 + } } // ============================================================================ @@ -650,7 +752,12 @@ impl Default for NodeConfig { impl NodeConfig { /// Get the log level as a tracing Level. Default: INFO. pub fn log_level(&self) -> tracing::Level { - match self.log_level.as_deref().map(|s| s.to_lowercase()).as_deref() { + match self + .log_level + .as_deref() + .map(|s| s.to_lowercase()) + .as_deref() + { Some("trace") => tracing::Level::TRACE, Some("debug") => tracing::Level::DEBUG, Some("warn") | Some("warning") => tracing::Level::WARN, @@ -659,10 +766,18 @@ impl NodeConfig { } } - fn default_tick_interval_secs() -> u64 { 1 } - fn default_base_rtt_ms() -> u64 { 100 } - fn default_heartbeat_interval_secs() -> u64 { 10 } - fn default_link_dead_timeout_secs() -> u64 { 30 } + fn default_tick_interval_secs() -> u64 { + 1 + } + fn default_base_rtt_ms() -> u64 { + 100 + } + fn default_heartbeat_interval_secs() -> u64 { + 10 + } + fn default_link_dead_timeout_secs() -> u64 { + 30 + } } #[cfg(test)] diff --git a/src/config/transport.rs b/src/config/transport.rs index a2e7bd3..9ba3674 100644 --- a/src/config/transport.rs +++ b/src/config/transport.rs @@ -112,15 +112,12 @@ impl TransportInstances { /// Named instances have `Some(name)`. pub fn iter(&self) -> impl Iterator, &T)> { match self { - TransportInstances::Single(config) => { - vec![(None, config)].into_iter() - } - TransportInstances::Named(map) => { - map.iter() - .map(|(k, v)| (Some(k.as_str()), v)) - .collect::>() - .into_iter() - } + TransportInstances::Single(config) => vec![(None, config)].into_iter(), + TransportInstances::Named(map) => map + .iter() + .map(|(k, v)| (Some(k.as_str()), v)) + .collect::>() + .into_iter(), } } } @@ -306,7 +303,8 @@ impl TcpConfig { /// Get the connect timeout in milliseconds. pub fn connect_timeout_ms(&self) -> u64 { - self.connect_timeout_ms.unwrap_or(DEFAULT_TCP_CONNECT_TIMEOUT_MS) + self.connect_timeout_ms + .unwrap_or(DEFAULT_TCP_CONNECT_TIMEOUT_MS) } /// Whether TCP_NODELAY is enabled. Default: true. @@ -331,7 +329,8 @@ impl TcpConfig { /// Get the maximum number of inbound connections. Default: 256. pub fn max_inbound_connections(&self) -> usize { - self.max_inbound_connections.unwrap_or(DEFAULT_TCP_MAX_INBOUND) + self.max_inbound_connections + .unwrap_or(DEFAULT_TCP_MAX_INBOUND) } } @@ -447,12 +446,16 @@ pub struct DirectoryServiceConfig { impl DirectoryServiceConfig { /// Path to the hostname file. Default: "/var/lib/tor/fips_onion_service/hostname". pub fn hostname_file(&self) -> &str { - self.hostname_file.as_deref().unwrap_or(DEFAULT_HOSTNAME_FILE) + self.hostname_file + .as_deref() + .unwrap_or(DEFAULT_HOSTNAME_FILE) } /// Local bind address for the listener. Default: "127.0.0.1:8443". pub fn bind_addr(&self) -> &str { - self.bind_addr.as_deref().unwrap_or(DEFAULT_DIRECTORY_BIND_ADDR) + self.bind_addr + .as_deref() + .unwrap_or(DEFAULT_DIRECTORY_BIND_ADDR) } } @@ -464,12 +467,16 @@ impl TorConfig { /// Get the SOCKS5 proxy address. Default: "127.0.0.1:9050". pub fn socks5_addr(&self) -> &str { - self.socks5_addr.as_deref().unwrap_or(DEFAULT_TOR_SOCKS5_ADDR) + self.socks5_addr + .as_deref() + .unwrap_or(DEFAULT_TOR_SOCKS5_ADDR) } /// Get the control port address. Default: "/run/tor/control". pub fn control_addr(&self) -> &str { - self.control_addr.as_deref().unwrap_or(DEFAULT_TOR_CONTROL_ADDR) + self.control_addr + .as_deref() + .unwrap_or(DEFAULT_TOR_CONTROL_ADDR) } /// Get the control auth string. Default: "cookie". @@ -479,12 +486,15 @@ impl TorConfig { /// Get the cookie file path. Default: "/var/run/tor/control.authcookie". pub fn cookie_path(&self) -> &str { - self.cookie_path.as_deref().unwrap_or(DEFAULT_TOR_COOKIE_PATH) + self.cookie_path + .as_deref() + .unwrap_or(DEFAULT_TOR_COOKIE_PATH) } /// Get the connect timeout in milliseconds. Default: 120000. pub fn connect_timeout_ms(&self) -> u64 { - self.connect_timeout_ms.unwrap_or(DEFAULT_TOR_CONNECT_TIMEOUT_MS) + self.connect_timeout_ms + .unwrap_or(DEFAULT_TOR_CONNECT_TIMEOUT_MS) } /// Get the default MTU. Default: 1400. @@ -494,7 +504,8 @@ impl TorConfig { /// Get the max inbound connections. Default: 64. pub fn max_inbound_connections(&self) -> usize { - self.max_inbound_connections.unwrap_or(DEFAULT_TOR_MAX_INBOUND) + self.max_inbound_connections + .unwrap_or(DEFAULT_TOR_MAX_INBOUND) } } diff --git a/src/gateway/control.rs b/src/gateway/control.rs index 8e41be7..7bd8ce8 100644 --- a/src/gateway/control.rs +++ b/src/gateway/control.rs @@ -107,7 +107,10 @@ impl GatewayControlSocket { let group_name = CString::new("fips").unwrap(); let grp = unsafe { libc::getgrnam(group_name.as_ptr()) }; if grp.is_null() { - debug!("'fips' group not found, skipping chown for {}", path.display()); + debug!( + "'fips' group not found, skipping chown for {}", + path.display() + ); return; } let gid = unsafe { (*grp).gr_gid }; diff --git a/src/gateway/dns.rs b/src/gateway/dns.rs index 8409e2f..7f840aa 100644 --- a/src/gateway/dns.rs +++ b/src/gateway/dns.rs @@ -7,7 +7,7 @@ //! The daemon resolver populates its identity cache as a side effect //! of resolution, which is required for fips0 routing to work. -use simple_dns::{Packet, PacketFlag, RCODE, ResourceRecord, CLASS, rdata}; +use simple_dns::{CLASS, Packet, PacketFlag, RCODE, ResourceRecord, rdata}; use simple_dns::{QTYPE, TYPE}; use std::net::{Ipv6Addr, SocketAddr}; @@ -241,28 +241,27 @@ async fn handle_query( } }; - if let Err(e) = upstream_socket.send_to(&upstream_query_bytes, upstream).await { + if let Err(e) = upstream_socket + .send_to(&upstream_query_bytes, upstream) + .await + { warn!(error = %e, "Failed to forward query to daemon"); return build_servfail(&query); } let mut resp_buf = vec![0u8; MAX_DNS_SIZE]; - let resp_len = match tokio::time::timeout( - UPSTREAM_TIMEOUT, - upstream_socket.recv(&mut resp_buf), - ) - .await - { - Ok(Ok(len)) => len, - Ok(Err(e)) => { - warn!(error = %e, "Upstream recv error"); - return build_servfail(&query); - } - Err(_) => { - warn!("Upstream DNS timeout"); - return build_servfail(&query); - } - }; + let resp_len = + match tokio::time::timeout(UPSTREAM_TIMEOUT, upstream_socket.recv(&mut resp_buf)).await { + Ok(Ok(len)) => len, + Ok(Err(e)) => { + warn!(error = %e, "Upstream recv error"); + return build_servfail(&query); + } + Err(_) => { + warn!("Upstream DNS timeout"); + return build_servfail(&query); + } + }; let upstream_response = match Packet::parse(&resp_buf[..resp_len]) { Ok(p) => p, diff --git a/src/gateway/nat.rs b/src/gateway/nat.rs index a31a9fc..0fe1b68 100644 --- a/src/gateway/nat.rs +++ b/src/gateway/nat.rs @@ -11,9 +11,7 @@ use rustables::expr::{ Cmp, CmpOp, HighLevelPayload, IPv6HeaderField, Immediate, Masquerade, Meta, MetaType, Nat, NatType, NetworkHeaderField, Register, }; -use rustables::{ - Batch, Chain, ChainType, Hook, HookClass, MsgType, ProtocolFamily, Rule, Table, -}; +use rustables::{Batch, Chain, ChainType, Hook, HookClass, MsgType, ProtocolFamily, Rule, Table}; const TABLE_NAME: &str = "fips_gateway"; const PREROUTING_CHAIN: &str = "prerouting"; @@ -100,10 +98,13 @@ impl NatManager { virtual_ip: Ipv6Addr, mesh_addr: Ipv6Addr, ) -> Result<(), NatError> { - self.mappings.insert(virtual_ip, NatMapping { + self.mappings.insert( virtual_ip, - mesh_addr, - }); + NatMapping { + virtual_ip, + mesh_addr, + }, + ); self.rebuild()?; debug!( @@ -129,7 +130,9 @@ impl NatManager { pub fn cleanup(self) -> Result<(), NatError> { let mut batch = Batch::new(); batch.add(&self.table, MsgType::Del); - batch.send().map_err(|e| NatError::Nftables(e.to_string()))?; + batch + .send() + .map_err(|e| NatError::Nftables(e.to_string()))?; info!("Deleted nftables table '{TABLE_NAME}'"); Ok(()) @@ -171,10 +174,8 @@ impl NatManager { .with_expr(Meta::new(MetaType::NfProto)) .with_expr(Cmp::new(CmpOp::Eq, [libc::NFPROTO_IPV6 as u8])) .with_expr( - HighLevelPayload::Network(NetworkHeaderField::IPv6( - IPv6HeaderField::Daddr, - )) - .build(), + HighLevelPayload::Network(NetworkHeaderField::IPv6(IPv6HeaderField::Daddr)) + .build(), ) .with_expr(Cmp::new(CmpOp::Eq, mapping.virtual_ip.octets())) .with_expr(Immediate::new_data( @@ -193,10 +194,8 @@ impl NatManager { .with_expr(Meta::new(MetaType::NfProto)) .with_expr(Cmp::new(CmpOp::Eq, [libc::NFPROTO_IPV6 as u8])) .with_expr( - HighLevelPayload::Network(NetworkHeaderField::IPv6( - IPv6HeaderField::Saddr, - )) - .build(), + HighLevelPayload::Network(NetworkHeaderField::IPv6(IPv6HeaderField::Saddr)) + .build(), ) .with_expr(Cmp::new(CmpOp::Eq, mapping.mesh_addr.octets())) .with_expr(Immediate::new_data( @@ -212,7 +211,9 @@ impl NatManager { batch.add(&snat_rule, MsgType::Add); } - batch.send().map_err(|e| NatError::Nftables(e.to_string()))?; + batch + .send() + .map_err(|e| NatError::Nftables(e.to_string()))?; Ok(()) } } diff --git a/src/gateway/net.rs b/src/gateway/net.rs index 25a8691..99f6a05 100644 --- a/src/gateway/net.rs +++ b/src/gateway/net.rs @@ -30,9 +30,7 @@ pub fn check_ipv6_forwarding() { } /// Check that a network interface exists using rtnetlink. -pub async fn check_interface_exists( - name: &str, -) -> Result { +pub async fn check_interface_exists(name: &str) -> Result { let index = rustables::iface_index(name) .map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, e.to_string()))?; debug!(interface = %name, index, "Interface found"); @@ -80,9 +78,9 @@ impl NetSetup { debug!(cidr = %self.pool_cidr, "Pool route already exists"); return Ok(()); } - return Err(std::io::Error::other( - format!("Failed to add pool route: {stderr}"), - )); + return Err(std::io::Error::other(format!( + "Failed to add pool route: {stderr}" + ))); } self.route_added = true; @@ -94,7 +92,15 @@ impl NetSetup { pub async fn add_proxy_ndp(&mut self, addr: Ipv6Addr) -> Result<(), std::io::Error> { let addr_str = addr.to_string(); let output = tokio::process::Command::new("ip") - .args(["-6", "neigh", "add", "proxy", &addr_str, "dev", &self.lan_interface]) + .args([ + "-6", + "neigh", + "add", + "proxy", + &addr_str, + "dev", + &self.lan_interface, + ]) .output() .await?; @@ -104,9 +110,9 @@ impl NetSetup { debug!(addr = %addr, "Proxy NDP entry already exists"); return Ok(()); } - return Err(std::io::Error::other( - format!("Failed to add proxy NDP: {stderr}"), - )); + return Err(std::io::Error::other(format!( + "Failed to add proxy NDP: {stderr}" + ))); } self.proxy_entries.push(addr); @@ -118,7 +124,15 @@ impl NetSetup { pub async fn remove_proxy_ndp(&mut self, addr: Ipv6Addr) -> Result<(), std::io::Error> { let addr_str = addr.to_string(); let output = tokio::process::Command::new("ip") - .args(["-6", "neigh", "del", "proxy", &addr_str, "dev", &self.lan_interface]) + .args([ + "-6", + "neigh", + "del", + "proxy", + &addr_str, + "dev", + &self.lan_interface, + ]) .output() .await?; diff --git a/src/gateway/pool.rs b/src/gateway/pool.rs index dcd4e9d..90d1948 100644 --- a/src/gateway/pool.rs +++ b/src/gateway/pool.rs @@ -215,11 +215,7 @@ impl VirtualIpPool { /// Periodic tick — drives state transitions. Returns events for /// the NAT and network modules. - pub fn tick( - &mut self, - now: Instant, - conntrack: &dyn ConntrackQuerier, - ) -> Vec { + pub fn tick(&mut self, now: Instant, conntrack: &dyn ConntrackQuerier) -> Vec { let mut events = Vec::new(); let mut to_free = Vec::new(); let ttl = std::time::Duration::from_secs(self.ttl_secs); @@ -227,9 +223,7 @@ impl VirtualIpPool { for (node_addr, mapping) in &mut self.mappings { // Query conntrack for active sessions - let sessions = conntrack - .active_sessions(mapping.virtual_ip) - .unwrap_or(0); + let sessions = conntrack.active_sessions(mapping.virtual_ip).unwrap_or(0); mapping.session_count = sessions; match mapping.state { @@ -462,7 +456,10 @@ mod tests { pool.allocate(make_node_addr(i), make_mesh_addr(i), "test.fips") .unwrap(); } - assert!(pool.allocate(make_node_addr(4), make_mesh_addr(4), "test.fips").is_err()); + assert!( + pool.allocate(make_node_addr(4), make_mesh_addr(4), "test.fips") + .is_err() + ); } #[test] @@ -485,7 +482,10 @@ mod tests { let events = pool.tick(later, &ct); assert!(events.is_empty()); assert_eq!(pool.mappings.len(), 1); - assert_eq!(pool.mappings.values().next().unwrap().state, MappingState::Draining); + assert_eq!( + pool.mappings.values().next().unwrap().state, + MappingState::Draining + ); // Tick after grace period — freed let after_grace = later + std::time::Duration::from_secs(2); @@ -541,7 +541,8 @@ mod tests { assert_eq!(status.free, 255); assert_eq!(status.allocated, 0); - pool.allocate(make_node_addr(1), make_mesh_addr(1), "test.fips").unwrap(); + pool.allocate(make_node_addr(1), make_mesh_addr(1), "test.fips") + .unwrap(); let status = pool.status(); assert_eq!(status.allocated, 1); assert_eq!(status.free, 254); diff --git a/src/mmp/receiver.rs b/src/mmp/receiver.rs index ad0cfcc..0582431 100644 --- a/src/mmp/receiver.rs +++ b/src/mmp/receiver.rs @@ -7,9 +7,10 @@ use std::time::{Duration, Instant}; use crate::mmp::algorithms::{JitterEstimator, OwdTrendDetector}; use crate::mmp::report::ReceiverReport; -use crate::mmp::{COLD_START_SAMPLES, DEFAULT_COLD_START_INTERVAL_MS, - DEFAULT_OWD_WINDOW_SIZE, MAX_REPORT_INTERVAL_MS, - MIN_REPORT_INTERVAL_MS}; +use crate::mmp::{ + COLD_START_SAMPLES, DEFAULT_COLD_START_INTERVAL_MS, DEFAULT_OWD_WINDOW_SIZE, + MAX_REPORT_INTERVAL_MS, MIN_REPORT_INTERVAL_MS, +}; /// Grace period after rekey before resuming jitter calculation. /// @@ -234,8 +235,7 @@ impl ReceiverState { self.owd_seq = 0; self.last_sender_timestamp = 0; self.last_recv_time = None; - self.rekey_jitter_grace_until = - Some(now + Duration::from_secs(REKEY_JITTER_GRACE_SECS)); + self.rekey_jitter_grace_until = Some(now + Duration::from_secs(REKEY_JITTER_GRACE_SECS)); self.ecn_ce_count = 0; self.interval_has_data = false; // Keep cumulative_packets_recv, cumulative_bytes_recv (lifetime stats) @@ -287,14 +287,14 @@ impl ReceiverState { // We can't get absolute µs from Instant, but we can compute the delta // between consecutive transits using relative Instant differences. // Skip during post-rekey grace period to avoid drain-window spikes. - let in_grace = self.rekey_jitter_grace_until + let in_grace = self + .rekey_jitter_grace_until .is_some_and(|deadline| now < deadline); if !in_grace { self.rekey_jitter_grace_until = None; // clear expired grace if let Some(prev_recv) = self.last_recv_time { let recv_delta_us = now.duration_since(prev_recv).as_micros() as i64; - let send_delta_us = - sender_us - (self.last_sender_timestamp as i64 * 1000); + let send_delta_us = sender_us - (self.last_sender_timestamp as i64 * 1000); let transit_delta = (recv_delta_us - send_delta_us) as i32; self.jitter.update(transit_delta); } @@ -324,7 +324,8 @@ impl ReceiverState { } // Dwell time: ms between last frame reception and report generation - let dwell_time = self.last_recv_time + let dwell_time = self + .last_recv_time .map(|t| now.duration_since(t).as_millis() as u16) .unwrap_or(0); @@ -604,7 +605,10 @@ mod tests { // 6th sample: steady state, floor is MIN_REPORT_INTERVAL_MS (1000ms) // 50ms SRTT → 50ms receiver interval (1× SRTT), clamped to 1000ms r.update_report_interval_from_srtt(50_000); - assert_eq!(r.report_interval(), Duration::from_millis(MIN_REPORT_INTERVAL_MS)); + assert_eq!( + r.report_interval(), + Duration::from_millis(MIN_REPORT_INTERVAL_MS) + ); // 3s SRTT → 3000ms, within [1000, 5000] r.update_report_interval_from_srtt(3_000_000); @@ -634,8 +638,8 @@ mod tests { assert_eq!(r.jitter_us(), 0); // After grace expires, jitter updates resume - let after_grace = t0 + Duration::from_secs(2) - + Duration::from_secs(REKEY_JITTER_GRACE_SECS + 1); + let after_grace = + t0 + Duration::from_secs(2) + Duration::from_secs(REKEY_JITTER_GRACE_SECS + 1); r.record_recv(2, 200, 100, false, after_grace); r.record_recv(3, 300, 100, false, after_grace + Duration::from_millis(100)); // Now jitter should be updating (non-zero or zero depending on timing) diff --git a/src/mmp/sender.rs b/src/mmp/sender.rs index f4a69cc..ac7e0a6 100644 --- a/src/mmp/sender.rs +++ b/src/mmp/sender.rs @@ -6,8 +6,10 @@ use std::time::{Duration, Instant}; use crate::mmp::report::SenderReport; -use crate::mmp::{COLD_START_SAMPLES, DEFAULT_COLD_START_INTERVAL_MS, - MAX_REPORT_INTERVAL_MS, MIN_REPORT_INTERVAL_MS}; +use crate::mmp::{ + COLD_START_SAMPLES, DEFAULT_COLD_START_INTERVAL_MS, MAX_REPORT_INTERVAL_MS, + MIN_REPORT_INTERVAL_MS, +}; /// Per-peer sender-side MMP state. /// @@ -123,7 +125,9 @@ impl SenderState { match self.last_report_time { None => true, // Never sent a report — send immediately Some(last) => { - let effective = self.report_interval.mul_f64(self.send_failure_backoff_multiplier()); + let effective = self + .report_interval + .mul_f64(self.send_failure_backoff_multiplier()); now.duration_since(last) >= effective } } @@ -327,11 +331,17 @@ mod tests { // 6th sample: now in steady state, floor is MIN_REPORT_INTERVAL_MS (1000ms) // 50ms RTT → 100ms sender interval (2× SRTT), clamped to 1000ms s.update_report_interval_from_srtt(50_000); - assert_eq!(s.report_interval(), Duration::from_millis(MIN_REPORT_INTERVAL_MS)); + assert_eq!( + s.report_interval(), + Duration::from_millis(MIN_REPORT_INTERVAL_MS) + ); // 3s RTT → 6s, clamped to max 5s s.update_report_interval_from_srtt(3_000_000); - assert_eq!(s.report_interval(), Duration::from_millis(MAX_REPORT_INTERVAL_MS)); + assert_eq!( + s.report_interval(), + Duration::from_millis(MAX_REPORT_INTERVAL_MS) + ); } #[test] diff --git a/src/node/handlers/handshake.rs b/src/node/handlers/handshake.rs index 877f18b..18bafe2 100644 --- a/src/node/handlers/handshake.rs +++ b/src/node/handlers/handshake.rs @@ -1,12 +1,10 @@ //! Handshake handlers and connection promotion. -use crate::node::{Node, NodeError}; -use crate::peer::{ - cross_connection_winner, ActivePeer, PeerConnection, PromotionResult, -}; -use crate::transport::{Link, LinkDirection, LinkId, ReceivedPacket}; -use crate::node::wire::{build_msg2, Msg1Header, Msg2Header}; use crate::PeerIdentity; +use crate::node::wire::{Msg1Header, Msg2Header, build_msg2}; +use crate::node::{Node, NodeError}; +use crate::peer::{ActivePeer, PeerConnection, PromotionResult, cross_connection_winner}; +use crate::transport::{Link, LinkDirection, LinkId, ReceivedPacket}; use std::time::Duration; use tracing::{debug, info, warn}; @@ -63,8 +61,7 @@ impl Node { { if link.direction() == LinkDirection::Inbound { // Check if this link belongs to an already-promoted active peer - let is_active_peer = self.peers.values() - .any(|p| p.link_id() == existing_link_id); + let is_active_peer = self.peers.values().any(|p| p.link_id() == existing_link_id); if is_active_peer { // Possible restart — fall through to decrypt and check epoch @@ -100,8 +97,7 @@ impl Node { // peer, this may be a rekey msg1 (same epoch) or a // restart (different epoch). Set possible_restart to enable // the epoch/rekey check below. - let is_active_peer = self.peers.values() - .any(|p| p.link_id() == existing_link_id); + let is_active_peer = self.peers.values().any(|p| p.link_id() == existing_link_id); if is_active_peer { possible_restart = true; } else { @@ -126,7 +122,12 @@ impl Node { let our_keypair = self.identity.keypair(); let noise_msg1 = &packet.data[header.noise_msg1_offset..]; - let msg2_response = match conn.receive_handshake_init(our_keypair, self.startup_epoch, noise_msg1, packet.timestamp_ms) { + let msg2_response = match conn.receive_handshake_init( + our_keypair, + self.startup_epoch, + noise_msg1, + packet.timestamp_ms, + ) { Ok(m) => m, Err(e) => { self.msg1_rate_limiter.complete_handshake(); @@ -162,9 +163,7 @@ impl Node { // If we fell through from the addr_to_link check above with // possible_restart=true, we now have the decrypted epoch from msg1. // Compare it against the stored epoch for this peer. - if possible_restart - && let Some(existing_peer) = self.peers.get(&peer_node_addr) - { + if possible_restart && let Some(existing_peer) = self.peers.get(&peer_node_addr) { let new_epoch = conn.remote_epoch(); let existing_epoch = existing_peer.remote_epoch(); @@ -192,10 +191,8 @@ impl Node { // During simultaneous connection, both sides promote // within the same tick and the peer's msg1 arrives // immediately — a genuine rekey can't fire that fast. - let session_age_secs = existing_peer - .session_established_at() - .elapsed() - .as_secs(); + let session_age_secs = + existing_peer.session_established_at().elapsed().as_secs(); if self.config.node.rekey.enabled && existing_peer.has_session() && existing_peer.is_healthy() @@ -272,7 +269,8 @@ impl Node { }; // Send msg2 response using the new handshake - let wire_msg2 = build_msg2(our_new_index, header.sender_idx, &msg2_response); + let wire_msg2 = + build_msg2(our_new_index, header.sender_idx, &msg2_response); if let Some(transport) = self.transports.get(&packet.transport_id) { match transport.send(&packet.remote_addr, &wire_msg2).await { Ok(_) => { @@ -401,7 +399,8 @@ impl Node { // Clean up on failure self.connections.remove(&link_id); self.links.remove(&link_id); - self.addr_to_link.remove(&(packet.transport_id, packet.remote_addr)); + self.addr_to_link + .remove(&(packet.transport_id, packet.remote_addr)); let _ = self.index_allocator.free(our_index); self.msg1_rate_limiter.complete_handshake(); return; @@ -434,7 +433,10 @@ impl Node { self.bloom_state.mark_update_needed(node_addr); self.reset_discovery_backoff(); } - PromotionResult::CrossConnectionWon { loser_link_id, node_addr } => { + PromotionResult::CrossConnectionWon { + loser_link_id, + node_addr, + } => { // Store msg2 on peer for resend on duplicate msg1 if let Some(peer) = self.peers.get_mut(&node_addr) { peer.set_handshake_msg2(wire_msg2.clone()); @@ -552,9 +554,7 @@ impl Node { // Find peer with rekey in progress for this index let peer_addr = self.peers.iter().find_map(|(addr, peer)| { - if peer.rekey_in_progress() - && peer.rekey_our_index() == Some(header.receiver_idx) - { + if peer.rekey_in_progress() && peer.rekey_our_index() == Some(header.receiver_idx) { Some(*addr) } else { None @@ -568,15 +568,12 @@ impl Node { if let Some(peer) = self.peers.get_mut(&peer_node_addr) { match peer.complete_rekey_msg2(noise_msg2) { Ok(session) => { - let our_index = peer.rekey_our_index() - .unwrap_or(header.receiver_idx); + let our_index = peer.rekey_our_index().unwrap_or(header.receiver_idx); peer.set_pending_session(session, our_index, header.sender_idx); if let Some(transport_id) = peer.transport_id() { - self.peers_by_index.insert( - (transport_id, our_index.as_u32()), - peer_node_addr, - ); + self.peers_by_index + .insert((transport_id, our_index.as_u32()), peer_node_addr); } debug!( @@ -679,15 +676,17 @@ impl Node { let outbound_our_index = conn.our_index(); let outbound_session = conn.take_session(); - let (outbound_session, outbound_our_index) = - match (outbound_session, outbound_our_index) { - (Some(s), Some(idx)) => (s, idx), - _ => { - warn!(peer = %self.peer_display_name(&peer_node_addr), "Incomplete outbound connection"); - self.pending_outbound.remove(&key); - return; - } - }; + let (outbound_session, outbound_our_index) = match ( + outbound_session, + outbound_our_index, + ) { + (Some(s), Some(idx)) => (s, idx), + _ => { + warn!(peer = %self.peer_display_name(&peer_node_addr), "Incomplete outbound connection"); + self.pending_outbound.remove(&key); + return; + } + }; if let Some(peer) = self.peers.get_mut(&peer_node_addr) { let suppressed = peer.replay_suppressed_count(); @@ -700,13 +699,12 @@ impl Node { // Update peers_by_index: remove old inbound index, add outbound let transport_id = peer.transport_id().unwrap(); if let Some(old_idx) = old_our_index { - self.peers_by_index.remove(&(transport_id, old_idx.as_u32())); + self.peers_by_index + .remove(&(transport_id, old_idx.as_u32())); let _ = self.index_allocator.free(old_idx); } - self.peers_by_index.insert( - (transport_id, outbound_our_index.as_u32()), - peer_node_addr, - ); + self.peers_by_index + .insert((transport_id, outbound_our_index.as_u32()), peer_node_addr); if suppressed > 0 { debug!( @@ -791,7 +789,10 @@ impl Node { self.bloom_state.mark_update_needed(node_addr); self.reset_discovery_backoff(); } - PromotionResult::CrossConnectionWon { loser_link_id, node_addr } => { + PromotionResult::CrossConnectionWon { + loser_link_id, + node_addr, + } => { // Close the losing TCP connection (no-op for connectionless) if let Some(loser_link) = self.links.get(&loser_link_id) { let loser_tid = loser_link.transport_id(); @@ -803,10 +804,8 @@ impl Node { // Clean up the losing connection's link self.remove_link(&loser_link_id); // Ensure addr_to_link points to the winning link - self.addr_to_link.insert( - (packet.transport_id, packet.remote_addr.clone()), - link_id, - ); + self.addr_to_link + .insert((packet.transport_id, packet.remote_addr.clone()), link_id); debug!( peer = %self.peer_display_name(&node_addr), loser_link_id = %loser_link_id, @@ -873,30 +872,31 @@ impl Node { .take_session() .ok_or(NodeError::NoSession(link_id))?; - let our_index = connection.our_index().ok_or_else(|| { - NodeError::PromotionFailed { + let our_index = connection + .our_index() + .ok_or_else(|| NodeError::PromotionFailed { link_id, reason: "missing our_index".into(), - } - })?; - let their_index = connection.their_index().ok_or_else(|| { - NodeError::PromotionFailed { + })?; + let their_index = connection + .their_index() + .ok_or_else(|| NodeError::PromotionFailed { link_id, reason: "missing their_index".into(), - } - })?; - let transport_id = connection.transport_id().ok_or_else(|| { - NodeError::PromotionFailed { + })?; + let transport_id = connection + .transport_id() + .ok_or_else(|| NodeError::PromotionFailed { link_id, reason: "missing transport_id".into(), - } - })?; - let current_addr = connection.source_addr().ok_or_else(|| { - NodeError::PromotionFailed { + })?; + let current_addr = connection + .source_addr() + .ok_or_else(|| NodeError::PromotionFailed { link_id, reason: "missing source_addr".into(), - } - })?.clone(); + })? + .clone(); let link_stats = connection.link_stats().clone(); let remote_epoch = connection.remote_epoch(); @@ -908,11 +908,8 @@ impl Node { let existing_link_id = existing_peer.link_id(); // Determine which connection wins - let this_wins = cross_connection_winner( - self.identity.node_addr(), - &peer_node_addr, - is_outbound, - ); + let this_wins = + cross_connection_winner(self.identity.node_addr(), &peer_node_addr, is_outbound); if this_wins { // This connection wins, replace the existing peer @@ -923,8 +920,7 @@ impl Node { if let (Some(old_tid), Some(old_idx)) = (old_peer.transport_id(), old_peer.our_index()) { - self.peers_by_index - .remove(&(old_tid, old_idx.as_u32())); + self.peers_by_index.remove(&(old_tid, old_idx.as_u32())); let _ = self.index_allocator.free(old_idx); } @@ -942,7 +938,9 @@ impl Node { &self.config.node.mmp, remote_epoch, ); - new_peer.set_tree_announce_min_interval_ms(self.config.node.tree.announce_min_interval_ms); + new_peer.set_tree_announce_min_interval_ms( + self.config.node.tree.announce_min_interval_ms, + ); self.peers.insert(peer_node_addr, new_peer); self.peers_by_index @@ -1008,13 +1006,17 @@ impl Node { // Normal promotion if self.max_peers > 0 && self.peers.len() >= self.max_peers { let _ = self.index_allocator.free(our_index); - return Err(NodeError::MaxPeersExceeded { max: self.max_peers }); + return Err(NodeError::MaxPeersExceeded { + max: self.max_peers, + }); } // Preserve tree announce rate-limit state from old peer (if reconnecting). // Without this, reconnection resets the rate limit window to zero, // allowing an immediate announce that can feed an announce loop. - let old_announce_ts = self.peers.get(&peer_node_addr) + let old_announce_ts = self + .peers + .get(&peer_node_addr) .map(|p| p.last_tree_announce_sent_ms()); let mut new_peer = ActivePeer::with_session( @@ -1031,7 +1033,8 @@ impl Node { &self.config.node.mmp, remote_epoch, ); - new_peer.set_tree_announce_min_interval_ms(self.config.node.tree.announce_min_interval_ms); + new_peer + .set_tree_announce_min_interval_ms(self.config.node.tree.announce_min_interval_ms); if let Some(ts) = old_announce_ts { new_peer.set_last_tree_announce_sent_ms(ts); } diff --git a/src/node/handlers/rekey.rs b/src/node/handlers/rekey.rs index ae528dd..44f4c8b 100644 --- a/src/node/handlers/rekey.rs +++ b/src/node/handlers/rekey.rs @@ -5,11 +5,11 @@ //! 2. Drain window expiry (clean up previous session after cutover) //! 3. Initiator-side cutover (first send after handshake completion) +use crate::NodeAddr; use crate::node::Node; use crate::node::wire::build_msg1; use crate::noise::HandshakeState; use crate::protocol::{SessionDatagram, SessionSetup}; -use crate::NodeAddr; use tracing::{debug, trace, warn}; /// Keep previous session alive for this long after cutover. @@ -69,7 +69,8 @@ impl Node { } let elapsed = peer.session_established_at().elapsed().as_secs(); - let counter = peer.noise_session() + let counter = peer + .noise_session() .map(|s| s.current_send_counter()) .unwrap_or(0); @@ -88,9 +89,10 @@ impl Node { debug_assert!( peer.transport_id().is_some() && peer.our_index().is_some() - && self.peers_by_index.contains_key( - &(peer.transport_id().unwrap(), peer.our_index().unwrap().as_u32()) - ), + && self.peers_by_index.contains_key(&( + peer.transport_id().unwrap(), + peer.our_index().unwrap().as_u32() + )), "peers_by_index should contain pre-registered new index after cutover" ); debug!( @@ -106,7 +108,8 @@ impl Node { && let Some(old_our_index) = peer.complete_drain() { if let Some(transport_id) = peer.transport_id() { - self.peers_by_index.remove(&(transport_id, old_our_index.as_u32())); + self.peers_by_index + .remove(&(transport_id, old_our_index.as_u32())); } let _ = self.index_allocator.free(old_our_index); trace!( @@ -208,7 +211,8 @@ impl Node { } // Register in pending_outbound for msg2 dispatch (maps to existing link) - self.pending_outbound.insert((transport_id, our_index.as_u32()), link_id); + self.pending_outbound + .insert((transport_id, our_index.as_u32()), link_id); } /// Resend pending rekey msg1s and abandon timed-out rekeys. diff --git a/src/node/lifecycle.rs b/src/node/lifecycle.rs index 69087be..77beb95 100644 --- a/src/node/lifecycle.rs +++ b/src/node/lifecycle.rs @@ -1,11 +1,11 @@ //! Node lifecycle management: start, stop, and peer connection initiation. use super::{Node, NodeError, NodeState}; +use crate::node::wire::build_msg1; use crate::peer::PeerConnection; use crate::protocol::{Disconnect, DisconnectReason}; -use crate::transport::{packet_channel, Link, LinkDirection, LinkId, TransportAddr, TransportId}; -use crate::upper::tun::{run_tun_reader, shutdown_tun_interface, TunDevice, TunState}; -use crate::node::wire::build_msg1; +use crate::transport::{Link, LinkDirection, LinkId, TransportAddr, TransportId, packet_channel}; +use crate::upper::tun::{TunDevice, TunState, run_tun_reader, shutdown_tun_interface}; use crate::{NodeAddr, PeerIdentity}; use std::thread; use std::time::Duration; @@ -50,7 +50,10 @@ impl Node { return; } - debug!(count = peer_configs.len(), "Initiating static peer connections"); + debug!( + count = peer_configs.len(), + "Initiating static peer connections" + ); for peer_config in peer_configs { if let Err(e) = self.initiate_peer_connection(&peer_config).await { @@ -67,14 +70,16 @@ impl Node { /// Initiate a connection to a single peer. /// /// Creates a link, starts the Noise handshake, and sends the first message. - pub(super) async fn initiate_peer_connection(&mut self, peer_config: &crate::config::PeerConfig) -> Result<(), NodeError> { + pub(super) async fn initiate_peer_connection( + &mut self, + peer_config: &crate::config::PeerConfig, + ) -> Result<(), NodeError> { // Parse the peer's npub to get their identity - let peer_identity = PeerIdentity::from_npub(&peer_config.npub).map_err(|e| { - NodeError::InvalidPeerNpub { + let peer_identity = + PeerIdentity::from_npub(&peer_config.npub).map_err(|e| NodeError::InvalidPeerNpub { npub: peer_config.npub.clone(), reason: e.to_string(), - } - })?; + })?; let peer_node_addr = *peer_identity.node_addr(); @@ -158,7 +163,10 @@ impl Node { (tid, TransportAddr::from_string(&addr.addr)) }; - match self.initiate_connection(transport_id, remote_addr, peer_identity).await { + match self + .initiate_connection(transport_id, remote_addr, peer_identity) + .await + { Ok(()) => return Ok(()), Err(e) => { debug!( @@ -197,7 +205,9 @@ impl Node { ) -> Result<(), NodeError> { let peer_node_addr = *peer_identity.node_addr(); - let is_connection_oriented = self.transports.get(&transport_id) + let is_connection_oriented = self + .transports + .get(&transport_id) .map(|t| t.transport_type().connection_oriented) .unwrap_or(false); @@ -258,7 +268,8 @@ impl Node { Ok(()) } else { // Connectionless: proceed with immediate handshake - self.start_handshake(link_id, transport_id, remote_addr, peer_identity).await + self.start_handshake(link_id, transport_id, remote_addr, peer_identity) + .await } } @@ -295,16 +306,17 @@ impl Node { // Start the Noise handshake and get message 1 let our_keypair = self.identity.keypair(); - let noise_msg1 = match connection.start_handshake(our_keypair, self.startup_epoch, current_time_ms) { - Ok(msg) => msg, - Err(e) => { - // Clean up the index and link - let _ = self.index_allocator.free(our_index); - self.links.remove(&link_id); - self.addr_to_link.remove(&(transport_id, remote_addr)); - return Err(NodeError::HandshakeFailed(e.to_string())); - } - }; + let noise_msg1 = + match connection.start_handshake(our_keypair, self.startup_epoch, current_time_ms) { + Ok(msg) => msg, + Err(e) => { + // Clean up the index and link + let _ = self.index_allocator.free(our_index); + self.links.remove(&link_id); + self.addr_to_link.remove(&(transport_id, remote_addr)); + return Err(NodeError::HandshakeFailed(e.to_string())); + } + }; // Set index and transport info on the connection connection.set_our_index(our_index); @@ -328,7 +340,8 @@ impl Node { connection.set_handshake_msg1(wire_msg1.clone(), current_time_ms + resend_interval); // Track in pending_outbound for msg2 dispatch - self.pending_outbound.insert((transport_id, our_index.as_u32()), link_id); + self.pending_outbound + .insert((transport_id, our_index.as_u32()), link_id); self.connections.insert(link_id, connection); // Send the wire format handshake message @@ -419,7 +432,10 @@ impl Node { remote_addr = %remote_addr, "Auto-connecting to discovered peer" ); - if let Err(e) = self.initiate_connection(transport_id, remote_addr, identity).await { + if let Err(e) = self + .initiate_connection(transport_id, remote_addr, identity) + .await + { warn!(error = %e, "Failed to auto-connect to discovered peer"); } } @@ -481,12 +497,15 @@ impl Node { ); // Start the handshake now that the transport is connected - if let Err(e) = self.start_handshake( - pending.link_id, - pending.transport_id, - pending.remote_addr.clone(), - pending.peer_identity, - ).await { + if let Err(e) = self + .start_handshake( + pending.link_id, + pending.transport_id, + pending.remote_addr.clone(), + pending.peer_identity, + ) + .await + { warn!( link_id = %pending.link_id, error = %e, @@ -583,7 +602,7 @@ impl Node { // Calculate max MSS for TCP clamping let effective_mtu = self.effective_ipv6_mtu(); let max_mss = effective_mtu.saturating_sub(40).saturating_sub(20); // IPv6 + TCP headers - + info!("effective MTU: {} bytes", effective_mtu); debug!(" max TCP MSS: {} bytes", max_mss); @@ -594,11 +613,9 @@ impl Node { let (shutdown_read_fd, shutdown_write_fd) = { let mut fds = [0i32; 2]; if unsafe { libc::pipe(fds.as_mut_ptr()) } < 0 { - return Err(NodeError::Tun( - crate::upper::tun::TunError::Configure( - "failed to create shutdown pipe".into(), - ), - )); + return Err(NodeError::Tun(crate::upper::tun::TunError::Configure( + "failed to create shutdown pipe".into(), + ))); } (fds[0], fds[1]) }; @@ -622,11 +639,26 @@ impl Node { let transport_mtu = self.transport_mtu(); #[cfg(target_os = "macos")] let reader_handle = thread::spawn(move || { - run_tun_reader(device, mtu, our_addr, reader_tun_tx, outbound_tx, transport_mtu, shutdown_read_fd); + run_tun_reader( + device, + mtu, + our_addr, + reader_tun_tx, + outbound_tx, + transport_mtu, + shutdown_read_fd, + ); }); #[cfg(not(target_os = "macos"))] let reader_handle = thread::spawn(move || { - run_tun_reader(device, mtu, our_addr, reader_tun_tx, outbound_tx, transport_mtu); + run_tun_reader( + device, + mtu, + our_addr, + reader_tun_tx, + outbound_tx, + transport_mtu, + ); }); self.tun_state = TunState::Active; @@ -636,7 +668,9 @@ impl Node { self.tun_reader_handle = Some(reader_handle); self.tun_writer_handle = Some(writer_handle); #[cfg(target_os = "macos")] - { self.tun_shutdown_fd = Some(shutdown_write_fd); } + { + self.tun_shutdown_fd = Some(shutdown_write_fd); + } } Err(e) => { self.tun_state = TunState::Failed; @@ -653,11 +687,19 @@ impl Node { let dns_channel_size = self.config.node.buffers.dns_channel; let (identity_tx, identity_rx) = tokio::sync::mpsc::channel(dns_channel_size); let dns_ttl = self.config.dns.ttl(); - let base_hosts = crate::upper::hosts::HostMap::from_peer_configs(self.config.peers()); - let hosts_path = std::path::PathBuf::from(crate::upper::hosts::DEFAULT_HOSTS_PATH); - let reloader = crate::upper::hosts::HostMapReloader::new(base_hosts, hosts_path); + let base_hosts = + crate::upper::hosts::HostMap::from_peer_configs(self.config.peers()); + let hosts_path = + std::path::PathBuf::from(crate::upper::hosts::DEFAULT_HOSTS_PATH); + let reloader = + crate::upper::hosts::HostMapReloader::new(base_hosts, hosts_path); info!(bind = %bind, hosts = reloader.hosts().len(), "DNS responder started for .fips domain (auto-reload enabled)"); - let handle = tokio::spawn(crate::upper::dns::run_dns_responder(socket, identity_tx, dns_ttl, reloader)); + let handle = tokio::spawn(crate::upper::dns::run_dns_responder( + socket, + identity_tx, + dns_ttl, + reloader, + )); self.dns_identity_rx = Some(identity_rx); self.dns_task = Some(handle); } @@ -693,7 +735,8 @@ impl Node { } // Send disconnect notifications to all active peers before closing transports - self.send_disconnect_to_all_peers(DisconnectReason::Shutdown).await; + self.send_disconnect_to_all_peers(DisconnectReason::Shutdown) + .await; // Shutdown transports (they're packet producers) let transport_ids: Vec<_> = self.transports.keys().cloned().collect(); @@ -767,7 +810,9 @@ impl Node { let plaintext = disconnect.encode(); // Collect node_addrs to avoid borrow conflict with send helper - let peer_addrs: Vec = self.peers.iter() + let peer_addrs: Vec = self + .peers + .iter() .filter(|(_, peer)| peer.can_send() && peer.has_session()) .map(|(addr, _)| *addr) .collect(); @@ -782,7 +827,10 @@ impl Node { let mut sent = 0usize; for node_addr in &peer_addrs { - match self.send_encrypted_link_message(node_addr, &plaintext).await { + match self + .send_encrypted_link_message(node_addr, &plaintext) + .await + { Ok(()) => sent += 1, Err(e) => { debug!( @@ -847,8 +895,8 @@ impl Node { /// /// Removes the peer and suppresses auto-reconnect. pub(crate) fn api_disconnect(&mut self, npub: &str) -> Result { - let peer_identity = PeerIdentity::from_npub(npub) - .map_err(|e| format!("invalid npub '{npub}': {e}"))?; + let peer_identity = + PeerIdentity::from_npub(npub).map_err(|e| format!("invalid npub '{npub}': {e}"))?; let node_addr = *peer_identity.node_addr(); if !self.peers.contains_key(&node_addr) { diff --git a/src/node/mod.rs b/src/node/mod.rs index c5525b5..b5f0c76 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -5,40 +5,43 @@ //! Bloom filters, coordinate caches, transports, links, and peers. mod bloom; +mod discovery_rate_limit; mod handlers; mod lifecycle; -mod retry; -mod discovery_rate_limit; mod rate_limit; +mod retry; mod routing_error_rate_limit; pub(crate) mod session; pub(crate) mod session_wire; -pub(crate) mod wire; pub(crate) mod stats; -mod tree; #[cfg(test)] mod tests; +mod tree; +pub(crate) mod wire; -use crate::bloom::BloomState; -use crate::cache::CoordCache; -use crate::utils::index::IndexAllocator; -use crate::node::session::SessionEntry; -use crate::peer::{ActivePeer, PeerConnection}; use self::discovery_rate_limit::{DiscoveryBackoff, DiscoveryForwardRateLimiter}; use self::rate_limit::HandshakeRateLimiter; use self::routing_error_rate_limit::RoutingErrorRateLimiter; +use self::wire::{ + FLAG_CE, FLAG_KEY_EPOCH, FLAG_SP, build_encrypted, build_established_header, + prepend_inner_header, +}; +use crate::bloom::BloomState; +use crate::cache::CoordCache; +use crate::node::session::SessionEntry; +use crate::peer::{ActivePeer, PeerConnection}; +use crate::transport::ethernet::EthernetTransport; +use crate::transport::tcp::TcpTransport; +use crate::transport::tor::TorTransport; +use crate::transport::udp::UdpTransport; use crate::transport::{ Link, LinkId, PacketRx, PacketTx, TransportAddr, TransportError, TransportHandle, TransportId, }; -use crate::transport::udp::UdpTransport; -use crate::transport::tcp::TcpTransport; -use crate::transport::tor::TorTransport; -use crate::transport::ethernet::EthernetTransport; use crate::tree::TreeState; use crate::upper::hosts::HostMap; use crate::upper::icmp_rate_limit::IcmpRateLimiter; use crate::upper::tun::{TunError, TunOutboundRx, TunState, TunTx}; -use self::wire::{build_encrypted, build_established_header, prepend_inner_header, FLAG_CE, FLAG_KEY_EPOCH, FLAG_SP}; +use crate::utils::index::IndexAllocator; use crate::{Config, ConfigError, Identity, IdentityError, NodeAddr, PeerIdentity}; use rand::Rng; use std::collections::{HashMap, VecDeque}; @@ -105,7 +108,11 @@ pub enum NodeError { SendFailed { node_addr: NodeAddr, reason: String }, #[error("mtu exceeded forwarding to {node_addr}: packet {packet_size} > mtu {mtu}")] - MtuExceeded { node_addr: NodeAddr, packet_size: usize, mtu: u16 }, + MtuExceeded { + node_addr: NodeAddr, + packet_size: usize, + mtu: u16, + }, #[error("config error: {0}")] Config(#[from] ConfigError), @@ -546,10 +553,7 @@ impl Node { coords_response_rate_limiter: RoutingErrorRateLimiter::with_interval( std::time::Duration::from_millis(coords_response_interval_ms), ), - discovery_backoff: DiscoveryBackoff::with_params( - backoff_base_secs, - backoff_max_secs, - ), + discovery_backoff: DiscoveryBackoff::with_params(backoff_base_secs, backoff_max_secs), discovery_forward_limiter: DiscoveryForwardRateLimiter::with_interval( std::time::Duration::from_secs(forward_min_interval_secs), ), @@ -784,7 +788,9 @@ impl Node { #[cfg(any(not(feature = "ble"), test))] if !ble_instances.is_empty() { #[cfg(not(test))] - tracing::warn!("BLE transport configured but 'ble' feature not enabled at compile time"); + tracing::warn!( + "BLE transport configured but 'ble' feature not enabled at compile time" + ); } } @@ -845,13 +851,9 @@ impl Node { /// (TransportId, TransportAddr) pair by finding the BLE transport /// instance matching the adapter name. #[cfg(target_os = "linux")] - fn resolve_ble_addr( - &self, - addr_str: &str, - ) -> Result<(TransportId, TransportAddr), NodeError> { + fn resolve_ble_addr(&self, addr_str: &str) -> Result<(TransportId, TransportAddr), NodeError> { let ta = TransportAddr::from_string(addr_str); - let adapter = crate::transport::ble::addr::adapter_from_addr(&ta) - .ok_or_else(|| { + let adapter = crate::transport::ble::addr::adapter_from_addr(&ta).ok_or_else(|| { NodeError::NoTransportForType(format!( "invalid BLE address format '{}': expected 'adapter/mac'", addr_str @@ -862,9 +864,7 @@ impl Node { let transport_id = self .transports .iter() - .find(|(_, handle)| { - handle.transport_type().name == "ble" && handle.is_operational() - }) + .find(|(_, handle)| handle.transport_type().name == "ble" && handle.is_operational()) .map(|(id, _)| *id) .ok_or_else(|| { NodeError::NoTransportForType(format!( @@ -1060,9 +1060,10 @@ impl Node { let now = std::time::Instant::now(); let should_log = match self.last_mesh_size_log { None => true, - Some(last) => now.duration_since(last) >= std::time::Duration::from_secs( - self.config.node.mmp.log_interval_secs, - ), + Some(last) => { + now.duration_since(last) + >= std::time::Duration::from_secs(self.config.node.mmp.log_interval_secs) + } }; if should_log { tracing::debug!( @@ -1111,7 +1112,6 @@ impl Node { self.tun_name.as_deref() } - // === Resource Limits === /// Set the maximum number of connections (handshake phase). @@ -1192,14 +1192,17 @@ impl Node { /// Add a link. pub fn add_link(&mut self, link: Link) -> Result<(), NodeError> { if self.max_links > 0 && self.links.len() >= self.max_links { - return Err(NodeError::MaxLinksExceeded { max: self.max_links }); + return Err(NodeError::MaxLinksExceeded { + max: self.max_links, + }); } let link_id = link.link_id(); let transport_id = link.transport_id(); let remote_addr = link.remote_addr().clone(); self.links.insert(link_id, link); - self.addr_to_link.insert((transport_id, remote_addr), link_id); + self.addr_to_link + .insert((transport_id, remote_addr), link_id); Ok(()) } @@ -1214,8 +1217,14 @@ impl Node { } /// Find link ID by transport address. - pub fn find_link_by_addr(&self, transport_id: TransportId, addr: &TransportAddr) -> Option { - self.addr_to_link.get(&(transport_id, addr.clone())).copied() + pub fn find_link_by_addr( + &self, + transport_id: TransportId, + addr: &TransportAddr, + ) -> Option { + self.addr_to_link + .get(&(transport_id, addr.clone())) + .copied() } /// Remove a link. @@ -1361,11 +1370,14 @@ impl Node { pub(crate) fn register_identity(&mut self, node_addr: NodeAddr, pubkey: secp256k1::PublicKey) { let mut prefix = [0u8; 15]; prefix.copy_from_slice(&node_addr.as_bytes()[0..15]); - self.identity_cache.insert(prefix, (node_addr, pubkey, Self::now_ms())); + self.identity_cache + .insert(prefix, (node_addr, pubkey, Self::now_ms())); // LRU eviction let max = self.config.node.cache.identity_size; if self.identity_cache.len() > max - && let Some(oldest_key) = self.identity_cache.iter() + && let Some(oldest_key) = self + .identity_cache + .iter() .min_by_key(|(_, (_, _, ts))| *ts) .map(|(k, _)| *k) { @@ -1374,7 +1386,10 @@ impl Node { } /// Look up a destination by FipsAddress prefix (bytes 1-15 of the IPv6 address). - pub(crate) fn lookup_by_fips_prefix(&mut self, prefix: &[u8; 15]) -> Option<(NodeAddr, secp256k1::PublicKey)> { + pub(crate) fn lookup_by_fips_prefix( + &mut self, + prefix: &[u8; 15], + ) -> Option<(NodeAddr, secp256k1::PublicKey)> { if let Some(entry) = self.identity_cache.get_mut(prefix) { entry.2 = Self::now_ms(); // LRU touch Some((entry.0, entry.1)) @@ -1413,9 +1428,7 @@ impl Node { /// has declared us as their parent (making them our child). pub(crate) fn is_tree_peer(&self, peer_addr: &NodeAddr) -> bool { // Peer is our parent - if !self.tree_state.is_root() - && self.tree_state.my_declaration().parent_id() == peer_addr - { + if !self.tree_state.is_root() && self.tree_state.my_declaration().parent_id() == peer_addr { return true; } // Peer is our child (their declaration names us as parent) @@ -1464,7 +1477,10 @@ impl Node { .duration_since(std::time::UNIX_EPOCH) .map(|d| d.as_millis() as u64) .unwrap_or(0); - let dest_coords = self.coord_cache.get_and_touch(dest_node_addr, now_ms)?.clone(); + let dest_coords = self + .coord_cache + .get_and_touch(dest_node_addr, now_ms)? + .clone(); // 3. Bloom filter candidates — requires dest_coords for loop-free selection. // If no candidate is strictly closer, fall through to tree routing. @@ -1566,7 +1582,8 @@ impl Node { node_addr: &NodeAddr, plaintext: &[u8], ) -> Result<(), NodeError> { - self.send_encrypted_link_message_with_ce(node_addr, plaintext, false).await + self.send_encrypted_link_message_with_ce(node_addr, plaintext, false) + .await } /// Like `send_encrypted_link_message` but allows setting the FMP CE flag. @@ -1578,7 +1595,9 @@ impl Node { plaintext: &[u8], ce_flag: bool, ) -> Result<(), NodeError> { - let peer = self.peers.get_mut(node_addr) + let peer = self + .peers + .get_mut(node_addr) .ok_or(NodeError::PeerNotFound(*node_addr))?; let their_index = peer.their_index().ok_or_else(|| NodeError::SendFailed { @@ -1589,18 +1608,19 @@ impl Node { node_addr: *node_addr, reason: "no transport_id".into(), })?; - let remote_addr = peer.current_addr().cloned().ok_or_else(|| NodeError::SendFailed { - node_addr: *node_addr, - reason: "no current_addr".into(), - })?; + let remote_addr = peer + .current_addr() + .cloned() + .ok_or_else(|| NodeError::SendFailed { + node_addr: *node_addr, + reason: "no current_addr".into(), + })?; // Prepend 4-byte session-relative timestamp (inner header) let timestamp_ms = peer.session_elapsed_ms(); // MMP: read spin bit value before entering session borrow - let sp_flag = peer.mmp() - .map(|mmp| mmp.spin_bit.tx_bit()) - .unwrap_or(false); + let sp_flag = peer.mmp().map(|mmp| mmp.spin_bit.tx_bit()).unwrap_or(false); let mut flags = if sp_flag { FLAG_SP } else { 0 }; if ce_flag { flags |= FLAG_CE; @@ -1609,10 +1629,12 @@ impl Node { flags |= FLAG_KEY_EPOCH; } - let session = peer.noise_session_mut().ok_or_else(|| NodeError::SendFailed { - node_addr: *node_addr, - reason: "no noise session".into(), - })?; + let session = peer + .noise_session_mut() + .ok_or_else(|| NodeError::SendFailed { + node_addr: *node_addr, + reason: "no noise session".into(), + })?; // Inner plaintext: [timestamp:4 LE][msg_type][payload...] let inner_plaintext = prepend_inner_header(timestamp_ms, plaintext); @@ -1623,18 +1645,24 @@ impl Node { let header = build_established_header(their_index, counter, flags, payload_len); // Encrypt with AAD binding to the outer header - let ciphertext = session.encrypt_with_aad(&inner_plaintext, &header).map_err(|e| NodeError::SendFailed { - node_addr: *node_addr, - reason: format!("encryption failed: {}", e), - })?; + let ciphertext = session + .encrypt_with_aad(&inner_plaintext, &header) + .map_err(|e| NodeError::SendFailed { + node_addr: *node_addr, + reason: format!("encryption failed: {}", e), + })?; let wire_packet = build_encrypted(&header, &ciphertext); // Re-borrow peer for stats update after sending - let transport = self.transports.get(&transport_id) + let transport = self + .transports + .get(&transport_id) .ok_or(NodeError::TransportNotFound(transport_id))?; - let bytes_sent = transport.send(&remote_addr, &wire_packet).await + let bytes_sent = transport + .send(&remote_addr, &wire_packet) + .await .map_err(|e| match e { TransportError::MtuExceeded { packet_size, mtu } => NodeError::MtuExceeded { node_addr: *node_addr, diff --git a/src/node/rate_limit.rs b/src/node/rate_limit.rs index d95c9cd..5189333 100644 --- a/src/node/rate_limit.rs +++ b/src/node/rate_limit.rs @@ -284,7 +284,14 @@ mod tests { let hi = (expected * 1.2).max(expected + 0.5).min(10.0); let tokens = bucket.tokens(); - assert!((lo..=hi).contains(&tokens), "tokens: {}, expected ~{:.2} (range {:.2}..={:.2})", tokens, expected, lo, hi); + assert!( + (lo..=hi).contains(&tokens), + "tokens: {}, expected ~{:.2} (range {:.2}..={:.2})", + tokens, + expected, + lo, + hi + ); } #[test] diff --git a/src/node/tests/ble.rs b/src/node/tests/ble.rs index 656e486..a88f997 100644 --- a/src/node/tests/ble.rs +++ b/src/node/tests/ble.rs @@ -6,12 +6,12 @@ use super::*; use crate::config::BleConfig; +use crate::transport::ble::BleTransport; use crate::transport::ble::addr::BleAddr; use crate::transport::ble::io::{MockBleIo, MockBleStream}; -use crate::transport::ble::BleTransport; -use crate::transport::{packet_channel, Transport, TransportHandle, TransportId}; +use crate::transport::{Transport, TransportHandle, TransportId, packet_channel}; use spanning_tree::{ - cleanup_nodes, drain_all_packets, initiate_handshake, verify_tree_convergence, TestNode, + TestNode, cleanup_nodes, drain_all_packets, initiate_handshake, verify_tree_convergence, }; use std::collections::HashMap; use std::sync::{Arc, Mutex as StdMutex}; @@ -44,8 +44,8 @@ async fn make_test_node_ble(node_num: u8) -> TestNode { adapter: Some("hci0".to_string()), mtu: Some(2048), accept_connections: Some(true), - scan: Some(false), // no auto-scan in tests - advertise: Some(false), // no advertising in tests + scan: Some(false), // no auto-scan in tests + advertise: Some(false), // no advertising in tests auto_connect: Some(false), ..Default::default() }; @@ -91,7 +91,11 @@ async fn wire_ble_connection(nodes: &[TestNode], i: usize, j: usize, bank: &Stre bank.lock().unwrap().insert(key, stream_i); // Inject stream_j into node j's accept loop so it sees the inbound. - let transport_j = nodes[j].node.transports.get(&nodes[j].transport_id).unwrap(); + let transport_j = nodes[j] + .node + .transports + .get(&nodes[j].transport_id) + .unwrap(); match transport_j { TransportHandle::Ble(t) => { t.io().inject_inbound(stream_j).await; @@ -103,7 +107,11 @@ async fn wire_ble_connection(nodes: &[TestNode], i: usize, j: usize, bank: &Stre /// Install a connect handler on node `i` that draws from the stream bank. fn install_connect_handler(nodes: &[TestNode], i: usize, bank: &StreamBank) { let bank = Arc::clone(bank); - let transport_i = nodes[i].node.transports.get(&nodes[i].transport_id).unwrap(); + let transport_i = nodes[i] + .node + .transports + .get(&nodes[i].transport_id) + .unwrap(); match transport_i { TransportHandle::Ble(t) => { t.io().set_connect_handler(move |addr, _psm| { @@ -125,7 +133,11 @@ fn install_connect_handler(nodes: &[TestNode], i: usize, bank: &StreamBank) { /// BLE send_async fails fast if no connection exists, so connections must /// be pre-established before initiating handshakes. async fn establish_ble_connection(nodes: &[TestNode], i: usize, j: usize) { - let transport = nodes[i].node.transports.get(&nodes[i].transport_id).unwrap(); + let transport = nodes[i] + .node + .transports + .get(&nodes[i].transport_id) + .unwrap(); transport.connect(&nodes[j].addr).await.unwrap(); // Let the background connect task complete tokio::task::yield_now().await; @@ -288,6 +300,11 @@ async fn test_ble_discovery() { node.transports .insert(transport_id, TransportHandle::Ble(transport)); - let mut nodes = vec![TestNode { node, transport_id, packet_rx, addr: ta }]; + let mut nodes = vec![TestNode { + node, + transport_id, + packet_rx, + addr: ta, + }]; cleanup_nodes(&mut nodes).await; } diff --git a/src/node/tests/mod.rs b/src/node/tests/mod.rs index 003cb44..9898e42 100644 --- a/src/node/tests/mod.rs +++ b/src/node/tests/mod.rs @@ -4,9 +4,9 @@ use crate::transport::{LinkDirection, TransportAddr, packet_channel}; use crate::utils::index::SessionIndex; use std::time::Duration; -mod bloom; #[cfg(target_os = "linux")] mod ble; +mod bloom; mod disconnect; mod discovery; mod ethernet; diff --git a/src/transport/ble/addr.rs b/src/transport/ble/addr.rs index d0ab952..bfbe77e 100644 --- a/src/transport/ble/addr.rs +++ b/src/transport/ble/addr.rs @@ -16,9 +16,9 @@ pub struct BleAddr { impl BleAddr { /// Parse a BLE address from the `"adapter/AA:BB:CC:DD:EE:FF"` format. pub fn parse(s: &str) -> Result { - let (adapter, mac_str) = s - .split_once('/') - .ok_or_else(|| TransportError::InvalidAddress(format!("missing '/' in BLE address: {s}")))?; + let (adapter, mac_str) = s.split_once('/').ok_or_else(|| { + TransportError::InvalidAddress(format!("missing '/' in BLE address: {s}")) + })?; if adapter.is_empty() { return Err(TransportError::InvalidAddress("empty adapter name".into())); @@ -75,11 +75,7 @@ impl BleAddr { /// Convert to a bluer L2CAP `SocketAddr` with the given PSM. pub fn to_socket_addr(&self, psm: u16) -> bluer::l2cap::SocketAddr { - bluer::l2cap::SocketAddr::new( - self.to_bluer_address(), - bluer::AddressType::LePublic, - psm, - ) + bluer::l2cap::SocketAddr::new(self.to_bluer_address(), bluer::AddressType::LePublic, psm) } } diff --git a/src/transport/ble/io.rs b/src/transport/ble/io.rs index b05b910..454ea95 100644 --- a/src/transport/ble/io.rs +++ b/src/transport/ble/io.rs @@ -54,9 +54,7 @@ pub trait BleScanner: Send { /// Wait for the next discovered device. /// /// Returns `None` when scanning is stopped. - fn next( - &mut self, - ) -> impl std::future::Future> + Send; + fn next(&mut self) -> impl std::future::Future> + Send; } /// Core BLE I/O operations. @@ -117,7 +115,9 @@ mod bluer_impl { use crate::transport::TransportError; use bluer::l2cap::{SeqPacket, SeqPacketListener, Socket, SocketAddr}; - use bluer::{adv::Advertisement, AdapterEvent, AddressType, DiscoveryFilter, DiscoveryTransport}; + use bluer::{ + AdapterEvent, AddressType, DiscoveryFilter, DiscoveryTransport, adv::Advertisement, + }; use futures::StreamExt; use std::collections::{BTreeSet, HashSet}; use std::pin::Pin; @@ -138,10 +138,7 @@ mod bluer_impl { /// Map a std::io::Error to a TransportError. fn map_io_err(context: &str, e: std::io::Error) -> TransportError { - TransportError::Io(std::io::Error::new( - e.kind(), - format!("{}: {}", context, e), - )) + TransportError::Io(std::io::Error::new(e.kind(), format!("{}: {}", context, e))) } // ---------------------------------------------------------------- @@ -159,20 +156,25 @@ mod bluer_impl { impl BluerStream { /// Construct from a connected SeqPacket, querying MTU values. pub fn new(conn: SeqPacket, remote: BleAddr) -> Result { - let send_mtu = conn - .send_mtu() - .map_err(|e| map_io_err("send_mtu", e))? as u16; - let recv_mtu = conn - .recv_mtu() - .map_err(|e| map_io_err("recv_mtu", e))? as u16; + let send_mtu = conn.send_mtu().map_err(|e| map_io_err("send_mtu", e))? as u16; + let recv_mtu = conn.recv_mtu().map_err(|e| map_io_err("recv_mtu", e))? as u16; // Log negotiated PHY for diagnostics (2M vs 1M) match conn.as_ref().phy() { - Ok(phy) => debug!(addr = %remote, phy, send_mtu, recv_mtu, "BLE connection established"), - Err(_) => debug!(addr = %remote, send_mtu, recv_mtu, "BLE connection established (PHY query unsupported)"), + Ok(phy) => { + debug!(addr = %remote, phy, send_mtu, recv_mtu, "BLE connection established") + } + Err(_) => { + debug!(addr = %remote, send_mtu, recv_mtu, "BLE connection established (PHY query unsupported)") + } } - Ok(Self { conn, remote, send_mtu, recv_mtu }) + Ok(Self { + conn, + remote, + send_mtu, + recv_mtu, + }) } } @@ -250,8 +252,7 @@ mod bluer_impl { if let Ok(device) = self.adapter.device(addr) { match device.uuids().await { Ok(Some(uuids)) if uuids.contains(&FIPS_SERVICE_UUID) => { - let ble_addr = - BleAddr::from_bluer(addr, &self.adapter_name); + let ble_addr = BleAddr::from_bluer(addr, &self.adapter_name); debug!(addr = %ble_addr, "BLE scanner: FIPS peer found"); return Some(ble_addr); } @@ -359,11 +360,7 @@ mod bluer_impl { }) } - async fn connect( - &self, - addr: &BleAddr, - psm: u16, - ) -> Result { + async fn connect(&self, addr: &BleAddr, psm: u16) -> Result { let target_sa = addr.to_socket_addr(psm); let socket = Socket::::new_seq_packet() @@ -507,11 +504,7 @@ pub struct MockBleStream { impl MockBleStream { /// Create a linked pair of mock streams simulating an L2CAP connection. - pub fn pair( - addr_a: BleAddr, - addr_b: BleAddr, - mtu: u16, - ) -> (Self, Self) { + pub fn pair(addr_a: BleAddr, addr_b: BleAddr, mtu: u16) -> (Self, Self) { let (tx_a, rx_a) = tokio::sync::mpsc::channel(64); let (tx_b, rx_b) = tokio::sync::mpsc::channel(64); let stream_a = Self { diff --git a/src/transport/ble/mod.rs b/src/transport/ble/mod.rs index c5369f8..842037b 100644 --- a/src/transport/ble/mod.rs +++ b/src/transport/ble/mod.rs @@ -36,11 +36,11 @@ use io::{BleIo, BleScanner, BleStream}; use pool::{BleConnection, ConnectionPool}; use stats::BleStats; +use secp256k1::XOnlyPublicKey; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::Mutex; use tokio::task::JoinHandle; -use secp256k1::XOnlyPublicKey; use tracing::{debug, info, trace, warn}; /// Default FIPS L2CAP PSM (Protocol Service Multiplexer). @@ -58,7 +58,6 @@ pub type DefaultBleTransport = BleTransport; #[cfg(any(not(feature = "ble"), test))] pub type DefaultBleTransport = BleTransport; - // ============================================================================ // BLE Transport // ============================================================================ @@ -490,8 +489,7 @@ impl BleTransport { match pubkey_exchange(&stream, our_pubkey).await { Ok(peer_pubkey) => { debug!(addr = %addr_clone, "BLE outbound pubkey exchange complete"); - discovery_buffer - .add_peer_with_pubkey(&ble_addr, peer_pubkey); + discovery_buffer.add_peer_with_pubkey(&ble_addr, peer_pubkey); } Err(e) => { warn!( @@ -1077,16 +1075,13 @@ mod tests { fn make_transport( io: MockBleIo, - ) -> (BleTransport, tokio::sync::mpsc::Receiver) { + ) -> ( + BleTransport, + tokio::sync::mpsc::Receiver, + ) { let (tx, rx) = tokio::sync::mpsc::channel(64); let config = BleConfig::default(); - let transport = BleTransport::new( - TransportId::new(1), - None, - config, - io, - tx, - ); + let transport = BleTransport::new(TransportId::new(1), None, config, io, tx); (transport, rx) } @@ -1177,7 +1172,10 @@ mod tests { let io = MockBleIo::new("hci0", test_addr(1)); let (transport, _rx) = make_transport(io); let addr = test_addr(2).to_transport_addr(); - assert_eq!(transport.connection_state_sync(&addr), ConnectionState::None); + assert_eq!( + transport.connection_state_sync(&addr), + ConnectionState::None + ); } /// Verify that the cross-probe tie-breaker follows the same convention diff --git a/src/transport/ble/pool.rs b/src/transport/ble/pool.rs index 1d63dc9..ffdf45d 100644 --- a/src/transport/ble/pool.rs +++ b/src/transport/ble/pool.rs @@ -151,9 +151,7 @@ impl ConnectionPool { .min_by_key(|(_, c)| c.established_at) .map(|(addr, _)| addr.clone()) .ok_or_else(|| { - TransportError::NotSupported( - "BLE pool full: all connections are static".into(), - ) + TransportError::NotSupported("BLE pool full: all connections are static".into()) }) } else { // Non-static peer evicts oldest non-static @@ -163,9 +161,7 @@ impl ConnectionPool { .min_by_key(|(_, c)| c.established_at) .map(|(addr, _)| addr.clone()) .ok_or_else(|| { - TransportError::NotSupported( - "BLE pool full: all connections are static".into(), - ) + TransportError::NotSupported("BLE pool full: all connections are static".into()) }) } } diff --git a/src/transport/ble/stats.rs b/src/transport/ble/stats.rs index cc0e89f..bae7461 100644 --- a/src/transport/ble/stats.rs +++ b/src/transport/ble/stats.rs @@ -108,7 +108,6 @@ impl BleStats { self.scan_results.fetch_add(1, Ordering::Relaxed); } - /// Take a snapshot of all counters. pub fn snapshot(&self) -> BleStatsSnapshot { BleStatsSnapshot { diff --git a/src/transport/ethernet/mod.rs b/src/transport/ethernet/mod.rs index ae1734e..759b774 100644 --- a/src/transport/ethernet/mod.rs +++ b/src/transport/ethernet/mod.rs @@ -253,12 +253,16 @@ impl EthernetTransport { if let Some(task) = self.beacon_task.take() { task.abort(); #[cfg(not(target_os = "macos"))] - { let _ = task.await; } + { + let _ = task.await; + } } if let Some(task) = self.recv_task.take() { task.abort(); #[cfg(not(target_os = "macos"))] - { let _ = task.await; } + { + let _ = task.await; + } } // Drop socket diff --git a/src/transport/ethernet/socket.rs b/src/transport/ethernet/socket.rs index e665d60..77aba72 100644 --- a/src/transport/ethernet/socket.rs +++ b/src/transport/ethernet/socket.rs @@ -41,7 +41,11 @@ mod async_impl { Ok(Self { inner: async_fd }) } - pub async fn send_to(&self, data: &[u8], dest_mac: &[u8; 6]) -> Result { + pub async fn send_to( + &self, + data: &[u8], + dest_mac: &[u8; 6], + ) -> Result { loop { let mut guard = self .inner @@ -57,10 +61,7 @@ mod async_impl { } } - pub async fn recv_from( - &self, - buf: &mut [u8], - ) -> Result<(usize, [u8; 6]), TransportError> { + pub async fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, [u8; 6]), TransportError> { loop { let mut guard = self .inner @@ -136,7 +137,10 @@ mod async_impl { loop { // Drain any buffered frames from the previous read while let Some(result) = super::platform::parse_next_frame( - &parse_buf, &mut parse_offset, parse_len, &mut read_buf, + &parse_buf, + &mut parse_offset, + parse_len, + &mut read_buf, ) { match result { Ok((n, mac)) => { @@ -207,22 +211,24 @@ mod async_impl { }) } - pub async fn send_to(&self, data: &[u8], dest_mac: &[u8; 6]) -> Result { + pub async fn send_to( + &self, + data: &[u8], + dest_mac: &[u8; 6], + ) -> Result { let socket = Arc::clone(&self.inner); let data = data.to_vec(); let dest = *dest_mac; tokio::task::spawn_blocking(move || { - socket.send_to(&data, &dest) + socket + .send_to(&data, &dest) .map_err(|e| TransportError::SendFailed(format!("{}", e))) }) .await .map_err(|e| TransportError::SendFailed(format!("spawn_blocking: {}", e)))? } - pub async fn recv_from( - &self, - buf: &mut [u8], - ) -> Result<(usize, [u8; 6]), TransportError> { + pub async fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, [u8; 6]), TransportError> { let mut rx = self.rx.lock().await; match rx.recv().await { Some((data, mac)) => { diff --git a/src/transport/ethernet/socket_linux.rs b/src/transport/ethernet/socket_linux.rs index 3aa5e71..806e326 100644 --- a/src/transport/ethernet/socket_linux.rs +++ b/src/transport/ethernet/socket_linux.rs @@ -248,7 +248,10 @@ fn get_mac_addr(fd: RawFd, if_index: i32) -> Result<[u8; 6], TransportError> { // Use if_indextoname to get the name let mut name_buf = [0u8; libc::IFNAMSIZ]; let ret = unsafe { - libc::if_indextoname(if_index as libc::c_uint, name_buf.as_mut_ptr() as *mut libc::c_char) + libc::if_indextoname( + if_index as libc::c_uint, + name_buf.as_mut_ptr() as *mut libc::c_char, + ) }; if ret.is_null() { return Err(TransportError::StartFailed(format!( @@ -259,7 +262,10 @@ fn get_mac_addr(fd: RawFd, if_index: i32) -> Result<[u8; 6], TransportError> { } // Copy name into ifreq - let name_len = name_buf.iter().position(|&b| b == 0).unwrap_or(name_buf.len()); + let name_len = name_buf + .iter() + .position(|&b| b == 0) + .unwrap_or(name_buf.len()); let copy_len = name_len.min(libc::IFNAMSIZ - 1); unsafe { std::ptr::copy_nonoverlapping( @@ -299,7 +305,10 @@ fn get_if_mtu(fd: RawFd, if_index: i32) -> Result { // Get the interface name from index let mut name_buf = [0u8; libc::IFNAMSIZ]; let ret = unsafe { - libc::if_indextoname(if_index as libc::c_uint, name_buf.as_mut_ptr() as *mut libc::c_char) + libc::if_indextoname( + if_index as libc::c_uint, + name_buf.as_mut_ptr() as *mut libc::c_char, + ) }; if ret.is_null() { return Err(TransportError::StartFailed(format!( @@ -309,7 +318,10 @@ fn get_if_mtu(fd: RawFd, if_index: i32) -> Result { ))); } - let name_len = name_buf.iter().position(|&b| b == 0).unwrap_or(name_buf.len()); + let name_len = name_buf + .iter() + .position(|&b| b == 0) + .unwrap_or(name_buf.len()); let copy_len = name_len.min(libc::IFNAMSIZ - 1); unsafe { std::ptr::copy_nonoverlapping( diff --git a/src/transport/ethernet/socket_macos.rs b/src/transport/ethernet/socket_macos.rs index ec428ef..3ae3671 100644 --- a/src/transport/ethernet/socket_macos.rs +++ b/src/transport/ethernet/socket_macos.rs @@ -42,7 +42,9 @@ impl FdGuard { impl Drop for FdGuard { fn drop(&mut self) { - unsafe { libc::close(self.0); } + unsafe { + libc::close(self.0); + } } } @@ -99,7 +101,8 @@ impl PacketSocket { unsafe { libc::close(guard.0) }; std::mem::forget(guard); // don't double-close via FdGuard return Err(TransportError::StartFailed(format!( - "pipe() failed: {}", std::io::Error::last_os_error() + "pipe() failed: {}", + std::io::Error::last_os_error() ))); } @@ -157,7 +160,11 @@ impl PacketSocket { /// Signal the reader thread to stop by writing to the shutdown pipe. pub fn request_shutdown(&self) { unsafe { - libc::write(self.shutdown_write_fd, b"x".as_ptr() as *const libc::c_void, 1); + libc::write( + self.shutdown_write_fd, + b"x".as_ptr() as *const libc::c_void, + 1, + ); } } @@ -387,7 +394,9 @@ fn bind_to_interface(fd: RawFd, interface: &str) -> Result<(), TransportError> { let ret = unsafe { libc::ioctl(fd, BIOCSETIF, ifreq.as_ptr()) }; if ret < 0 { return Err(TransportError::StartFailed(format!( - "BIOCSETIF({}) failed: {}", interface, std::io::Error::last_os_error() + "BIOCSETIF({}) failed: {}", + interface, + std::io::Error::last_os_error() ))); } Ok(()) @@ -399,7 +408,8 @@ fn set_bpf_immediate(fd: RawFd) -> Result<(), TransportError> { let ret = unsafe { libc::ioctl(fd, BIOCIMMEDIATE, &enable) }; if ret < 0 { return Err(TransportError::StartFailed(format!( - "BIOCIMMEDIATE failed: {}", std::io::Error::last_os_error() + "BIOCIMMEDIATE failed: {}", + std::io::Error::last_os_error() ))); } Ok(()) @@ -411,7 +421,8 @@ fn set_bpf_hdrcmplt(fd: RawFd) -> Result<(), TransportError> { let ret = unsafe { libc::ioctl(fd, BIOCSHDRCMPLT, &enable) }; if ret < 0 { return Err(TransportError::StartFailed(format!( - "BIOCSHDRCMPLT failed: {}", std::io::Error::last_os_error() + "BIOCSHDRCMPLT failed: {}", + std::io::Error::last_os_error() ))); } Ok(()) @@ -425,10 +436,30 @@ fn install_ethertype_filter(fd: RawFd, ethertype: u16) -> Result<(), TransportEr // accept: ret #65535 ; accept entire packet // reject: ret #0 ; drop let filter = [ - BpfInsn { code: 0x28, jt: 0, jf: 0, k: 12 }, // ldh [12] - BpfInsn { code: 0x15, jt: 0, jf: 1, k: ethertype as u32 }, // jeq #ethertype - BpfInsn { code: 0x06, jt: 0, jf: 0, k: 0xFFFF }, // ret #65535 - BpfInsn { code: 0x06, jt: 0, jf: 0, k: 0 }, // ret #0 + BpfInsn { + code: 0x28, + jt: 0, + jf: 0, + k: 12, + }, // ldh [12] + BpfInsn { + code: 0x15, + jt: 0, + jf: 1, + k: ethertype as u32, + }, // jeq #ethertype + BpfInsn { + code: 0x06, + jt: 0, + jf: 0, + k: 0xFFFF, + }, // ret #65535 + BpfInsn { + code: 0x06, + jt: 0, + jf: 0, + k: 0, + }, // ret #0 ]; let prog = BpfProgram { @@ -439,7 +470,8 @@ fn install_ethertype_filter(fd: RawFd, ethertype: u16) -> Result<(), TransportEr let ret = unsafe { libc::ioctl(fd, BIOCSETF, &prog) }; if ret < 0 { return Err(TransportError::StartFailed(format!( - "BIOCSETF failed: {}", std::io::Error::last_os_error() + "BIOCSETF failed: {}", + std::io::Error::last_os_error() ))); } Ok(()) @@ -451,7 +483,8 @@ fn get_bpf_buflen(fd: RawFd) -> Result { let ret = unsafe { libc::ioctl(fd, BIOCGBLEN, &mut buflen) }; if ret < 0 { return Err(TransportError::StartFailed(format!( - "BIOCGBLEN failed: {}", std::io::Error::last_os_error() + "BIOCGBLEN failed: {}", + std::io::Error::last_os_error() ))); } Ok(buflen as usize) @@ -480,7 +513,8 @@ fn get_mac_addr(interface: &str) -> Result<[u8; 6], TransportError> { let ret = unsafe { libc::getifaddrs(&mut addrs) }; if ret != 0 { return Err(TransportError::StartFailed(format!( - "getifaddrs() failed: {}", std::io::Error::last_os_error() + "getifaddrs() failed: {}", + std::io::Error::last_os_error() ))); } @@ -509,7 +543,8 @@ fn get_mac_addr(interface: &str) -> Result<[u8; 6], TransportError> { cur = unsafe { (*cur).ifa_next }; } Err(TransportError::StartFailed(format!( - "MAC address not found for interface: {}", interface + "MAC address not found for interface: {}", + interface ))) })(); @@ -756,7 +791,10 @@ mod tests { libc::close(read_fd); libc::close(write_fd); } - assert!(!readable, "pipe read end should not be readable before write"); + assert!( + !readable, + "pipe read end should not be readable before write" + ); } #[test] @@ -790,23 +828,19 @@ fn get_if_mtu(if_index: i32) -> Result { // Get interface name from index let mut name_buf = [0i8; libc::IFNAMSIZ]; - let ret = unsafe { - libc::if_indextoname(if_index as libc::c_uint, name_buf.as_mut_ptr()) - }; + let ret = unsafe { libc::if_indextoname(if_index as libc::c_uint, name_buf.as_mut_ptr()) }; if ret.is_null() { unsafe { libc::close(sock) }; return Err(TransportError::StartFailed(format!( - "if_indextoname({}) failed: {}", if_index, std::io::Error::last_os_error() + "if_indextoname({}) failed: {}", + if_index, + std::io::Error::last_os_error() ))); } let mut ifr: libc::ifreq = unsafe { std::mem::zeroed() }; unsafe { - std::ptr::copy_nonoverlapping( - name_buf.as_ptr(), - ifr.ifr_name.as_mut_ptr(), - libc::IFNAMSIZ, - ); + std::ptr::copy_nonoverlapping(name_buf.as_ptr(), ifr.ifr_name.as_mut_ptr(), libc::IFNAMSIZ); } let ret = unsafe { libc::ioctl(sock, SIOCGIFMTU, &mut ifr) }; diff --git a/src/transport/mod.rs b/src/transport/mod.rs index 639d95b..0970212 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -4,27 +4,27 @@ //! underlying communication mechanisms (UDP, Ethernet, Tor, etc.) over //! which FIPS links are established. -pub mod udp; pub mod tcp; pub mod tor; +pub mod udp; pub mod ethernet; #[cfg(target_os = "linux")] pub mod ble; -use secp256k1::XOnlyPublicKey; -use udp::UdpTransport; -use tcp::TcpTransport; -use tor::control::TorMonitoringInfo; -use tor::TorTransport; -use ethernet::EthernetTransport; #[cfg(target_os = "linux")] use ble::DefaultBleTransport; +use ethernet::EthernetTransport; +use secp256k1::XOnlyPublicKey; use std::fmt; use std::net::SocketAddr; use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use tcp::TcpTransport; use thiserror::Error; +use tor::TorTransport; +use tor::control::TorMonitoringInfo; +use udp::UdpTransport; // ============================================================================ // Packet Channel Types @@ -1066,7 +1066,7 @@ impl TransportHandle { /// Poll `connection_state()` to check when the connection is ready. pub async fn connect(&self, addr: &TransportAddr) -> Result<(), TransportError> { match self { - TransportHandle::Udp(_) => Ok(()), // connectionless + TransportHandle::Udp(_) => Ok(()), // connectionless TransportHandle::Ethernet(_) => Ok(()), // connectionless TransportHandle::Tcp(t) => t.connect_async(addr).await, TransportHandle::Tor(t) => t.connect_async(addr).await, @@ -1600,10 +1600,8 @@ mod tests { let addr_b = TransportAddr::from_string("10.0.0.1:5000"); let addr_unknown = TransportAddr::from_string("172.16.0.1:6000"); - let transport = PerLinkMtuTransport::new( - 1280, - vec![(addr_a.clone(), 512), (addr_b.clone(), 247)], - ); + let transport = + PerLinkMtuTransport::new(1280, vec![(addr_a.clone(), 512), (addr_b.clone(), 247)]); // Known addresses return their per-link MTU assert_eq!(transport.link_mtu(&addr_a), 512); diff --git a/src/upper/tun.rs b/src/upper/tun.rs index 61703ff..d34ec81 100644 --- a/src/upper/tun.rs +++ b/src/upper/tun.rs @@ -123,7 +123,9 @@ impl TunDevice { // Read the actual device name (on macOS this is the kernel-assigned utun* name) let actual_name = { use tun::AbstractDevice; - device.tun_name().map_err(|e| TunError::Configure(format!("failed to get device name: {}", e)))? + device + .tun_name() + .map_err(|e| TunError::Configure(format!("failed to get device name: {}", e)))? }; // Configure address and bring up via platform-specific method @@ -329,7 +331,14 @@ pub fn run_tun_reader( loop { match device.read_packet(&mut buf) { Ok(n) if n > 0 => { - if !handle_tun_packet(&mut buf[..n], max_mss, &name, our_addr, &tun_tx, &outbound_tx) { + if !handle_tun_packet( + &mut buf[..n], + max_mss, + &name, + our_addr, + &tun_tx, + &outbound_tx, + ) { break; } } @@ -355,7 +364,9 @@ struct ShutdownFd(std::os::unix::io::RawFd); #[cfg(target_os = "macos")] impl Drop for ShutdownFd { fn drop(&mut self) { - unsafe { libc::close(self.0); } + unsafe { + libc::close(self.0); + } } } @@ -397,7 +408,13 @@ pub fn run_tun_reader( libc::FD_SET(tun_fd, &mut read_fds); libc::FD_SET(shutdown_fd, &mut read_fds); - let ret = libc::select(nfds, &mut read_fds, std::ptr::null_mut(), std::ptr::null_mut(), std::ptr::null_mut()); + let ret = libc::select( + nfds, + &mut read_fds, + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + ); if ret < 0 { let err = std::io::Error::last_os_error(); if err.kind() == std::io::ErrorKind::Interrupted { @@ -418,7 +435,14 @@ pub fn run_tun_reader( loop { match device.read_packet(&mut buf) { Ok(n) if n > 0 => { - if !handle_tun_packet(&mut buf[..n], max_mss, &name, our_addr, &tun_tx, &outbound_tx) { + if !handle_tun_packet( + &mut buf[..n], + max_mss, + &name, + our_addr, + &tun_tx, + &outbound_tx, + ) { return; // _shutdown_fd closes on drop } } @@ -474,7 +498,7 @@ fn handle_tun_packet( tun_tx: &TunTx, outbound_tx: &TunOutboundTx, ) -> bool { - use super::icmp::{build_dest_unreachable, should_send_icmp_error, DestUnreachableCode}; + use super::icmp::{DestUnreachableCode, build_dest_unreachable, should_send_icmp_error}; use super::tcp_mss::clamp_tcp_mss; log_ipv6_packet(packet); @@ -495,11 +519,8 @@ fn handle_tun_packet( } else { // Non-FIPS destination: send ICMPv6 Destination Unreachable if should_send_icmp_error(packet) - && let Some(response) = build_dest_unreachable( - packet, - DestUnreachableCode::NoRoute, - our_addr.to_ipv6(), - ) + && let Some(response) = + build_dest_unreachable(packet, DestUnreachableCode::NoRoute, our_addr.to_ipv6()) { trace!(name = %name, len = response.len(), "Sending ICMPv6 Destination Unreachable (non-FIPS destination)"); if tun_tx.send(response).is_err() { @@ -574,7 +595,7 @@ impl std::fmt::Debug for TunDevice { mod platform { use super::TunError; use futures::TryStreamExt; - use rtnetlink::{new_connection, Handle, LinkUnspec, RouteMessageBuilder}; + use rtnetlink::{Handle, LinkUnspec, RouteMessageBuilder, new_connection}; use std::net::Ipv6Addr; use tracing::debug; @@ -652,7 +673,13 @@ mod platform { // Add ip6 rule to ensure fd00::/8 uses the main table, preventing other // routing software (e.g. Tailscale) from intercepting FIPS traffic via // catch-all rules in auxiliary routing tables. - let mut rule_req = handle.rule().add().v6().destination_prefix(fd_prefix, 8).table_id(254).priority(5265); + let mut rule_req = handle + .rule() + .add() + .v6() + .destination_prefix(fd_prefix, 8) + .table_id(254) + .priority(5265); rule_req.message_mut().header.action = 1.into(); // FR_ACT_TO_TBL if let Err(e) = rule_req.execute().await { debug!("ip6 rule for fd00::/8 not added (may already exist): {e}"); @@ -713,7 +740,11 @@ mod platform { /// Configure a network interface with an IPv6 address using ifconfig/route. pub async fn configure_interface(name: &str, addr: Ipv6Addr, mtu: u16) -> Result<(), TunError> { // Add IPv6 address with /128 prefix - run_cmd("ifconfig", &[name, "inet6", &addr.to_string(), "prefixlen", "128"]).await?; + run_cmd( + "ifconfig", + &[name, "inet6", &addr.to_string(), "prefixlen", "128"], + ) + .await?; // Set MTU run_cmd("ifconfig", &[name, "mtu", &mtu.to_string()]).await?; @@ -722,7 +753,19 @@ mod platform { run_cmd("ifconfig", &[name, "up"]).await?; // Add route for fd00::/8 (FIPS address space) via this interface - run_cmd("route", &["add", "-inet6", "-prefixlen", "8", "fd00::", "-interface", name]).await?; + run_cmd( + "route", + &[ + "add", + "-inet6", + "-prefixlen", + "8", + "fd00::", + "-interface", + name, + ], + ) + .await?; Ok(()) }