fips_core/node/mod.rs
1//! FIPS Node Entity
2//!
3//! Top-level structure representing a running FIPS instance. The Node
4//! holds all state required for mesh routing: identity, tree state,
5//! Bloom filters, coordinate caches, transports, links, and peers.
6
7mod accessors_impl;
8mod acl;
9mod bloom;
10mod core_impl;
11mod discovery_rate_limit;
12mod endpoint_channels;
13mod endpoint_event;
14mod endpoint_traffic;
15mod error;
16mod handlers;
17mod identity_cache;
18mod io_impl;
19mod lifecycle;
20mod link_registry;
21mod packet_mover2_integration;
22mod peer_lifecycle;
23mod peer_runtime;
24mod rate_limit;
25mod recent_requests;
26mod retry;
27mod route_impl;
28mod routing;
29mod routing_error_rate_limit;
30pub(crate) mod session;
31mod session_access_impl;
32mod session_registry;
33pub(crate) mod session_wire;
34mod state;
35pub(crate) mod stats;
36pub(crate) mod stats_history;
37mod support_state;
38#[cfg(test)]
39mod tests;
40mod tree;
41pub(crate) mod wire;
42
43pub use endpoint_event::ExternalPacketIo;
44pub use error::NodeError;
45pub use identity_cache::NodeDeliveredPacket;
46pub use state::NodeState;
47
48pub(crate) use endpoint_channels::{
49 ENDPOINT_STALE_DATA_DROP_MS, EndpointDataBatchRx, EndpointDataBatchTx, EndpointDataBulkBody,
50 EndpointDataBulkBodyBuilder, NodeEndpointControlCommand, NodeEndpointDataBatch,
51 endpoint_data_batch_channel,
52};
53pub(in crate::node) use endpoint_event::EndpointEventRuntime;
54pub(crate) use endpoint_event::{
55 EndpointDataDelivery, EndpointDataIo, EndpointDirectSink, EndpointEventReceiver,
56 EndpointEventSender, NodeEndpointEvent, NodeEndpointPeer, NodeEndpointRelayStatus,
57 UpdatePeersOutcome,
58};
59pub use endpoint_event::{
60 FipsEndpointDirectDeliveryError, FipsEndpointDirectPacketBatch, FipsEndpointDirectPacketRun,
61 FipsEndpointDirectPacketRunMeta, FipsEndpointDirectSink, FipsEndpointDirectSourceRun,
62};
63pub(crate) use endpoint_traffic::{PendingEndpointData, PendingSessionTrafficQueues};
64pub(in crate::node) use identity_cache::IdentityCache;
65pub(in crate::node) use link_registry::{LinkRegistry, PendingConnect, TransportDropTracker};
66pub(in crate::node) use peer_lifecycle::*;
67pub(in crate::node) use peer_runtime::*;
68pub(crate) use recent_requests::{RecentDiscoveryRequests, RecentResponseForward};
69pub(in crate::node) use session_registry::*;
70pub(in crate::node) use support_state::{
71 BootstrapTransports, DiscoveryFallbackTransit, LocalSendFailures, SessionDirectDegradation,
72};
73
74use self::discovery_rate_limit::{DiscoveryBackoff, DiscoveryForwardRateLimiter};
75use self::rate_limit::HandshakeRateLimiter;
76use self::routing::{LearnedRouteTable, LearnedRouteTableSnapshot};
77use self::routing_error_rate_limit::RoutingErrorRateLimiter;
78use self::wire::{FLAG_CE, FLAG_KEY_EPOCH};
79use crate::bloom::{BloomFilter, BloomState};
80use crate::cache::CoordCache;
81use crate::config::{NostrDiscoveryPolicy, PeerConfig, RoutingMode};
82use crate::node::session::SessionEntry;
83use crate::node::session_wire::{FSP_PHASE_ESTABLISHED, FspCommonPrefix};
84use crate::packet_mover2::{
85 AdmissionConfig, PacketMover2FastIngressRx, PacketMover2LiveNode,
86 PacketMover2TransportSendWorkerPool,
87};
88use crate::peer::{ActivePeer, PeerConnection};
89#[cfg(any(target_os = "linux", target_os = "macos"))]
90use crate::transport::ethernet::EthernetTransport;
91use crate::transport::tcp::TcpTransport;
92use crate::transport::tor::TorTransport;
93use crate::transport::udp::UdpTransport;
94#[cfg(feature = "webrtc-transport")]
95use crate::transport::webrtc::WebRtcTransport;
96use crate::transport::{
97 Link, LinkId, PacketRx, PacketTx, TransportAddr, TransportError, TransportHandle, TransportId,
98};
99use crate::tree::TreeState;
100use crate::upper::hosts::HostMap;
101use crate::upper::icmp_rate_limit::IcmpRateLimiter;
102use crate::upper::tun::{TunError, TunOutboundRx, TunState, TunTx};
103use crate::utils::index::{IndexAllocator, SessionIndex};
104use crate::{
105 Config, ConfigError, FipsAddress, Identity, IdentityError, LinkMessageType, NodeAddr,
106 PeerIdentity, encode_npub,
107};
108use rand::Rng;
109use std::collections::{HashMap, HashSet, VecDeque};
110use std::fmt;
111use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
112use std::sync::{Arc, Condvar, Mutex as StdMutex};
113use std::thread::JoinHandle;
114use thiserror::Error;
115use tracing::{debug, warn};
116
117type PacketMover2Node = PacketMover2LiveNode;
118
119const LOCAL_SEND_FAILURE_FAST_DEAD_WINDOW: std::time::Duration = std::time::Duration::from_secs(3);
120#[cfg(test)]
121pub(crate) const ENDPOINT_EVENT_TEST_PAYLOAD_LEN: usize = 512;
122const SESSION_DIRECT_DEGRADED_HOLD_MS: u64 = 20_000;
123const SESSION_DIRECT_DEGRADED_MIN_SAMPLE: u64 = 16;
124const SESSION_DIRECT_DEGRADED_LOSS_THRESHOLD: f64 = 0.08;
125const SESSION_DIRECT_RECOVERY_LOSS_THRESHOLD: f64 = 0.02;
126const SESSION_DIRECT_MIN_EXCLUSIVE_TRUST_MS: u64 = 6_500;
127const ROUTING_FALLBACK_MIN_COST_ADVANTAGE: f64 = 0.25;
128const ENDPOINT_EVENT_BACKLOG_HIGH_WATER: usize = 4096;
129
130/// Half-range of the symmetric jitter applied to per-session rekey timers.
131///
132/// Each FMP/FSP session draws an offset uniformly from
133/// `[-REKEY_JITTER_SECS, +REKEY_JITTER_SECS]` seconds at construction and
134/// after each cutover. This preserves the configured mean interval while
135/// reducing dual-initiation bursts in symmetric-start meshes.
136pub(crate) const REKEY_JITTER_SECS: i64 = 15;
137
138/// A running FIPS node instance.
139///
140/// This is the top-level container holding all node state.
141///
142/// ## Peer Lifecycle
143///
144/// Peers go through two phases:
145/// 1. **Connection phase** (`connections`): Handshake in progress, indexed by LinkId
146/// 2. **Active phase** (`peers`): Authenticated, indexed by NodeAddr
147///
148/// The link registry dispatches incoming packets to the right connection before
149/// authentication completes.
150// Discovery lookup constants moved to config: node.discovery.attempt_timeouts_secs, node.discovery.ttl
151pub struct Node {
152 // === Identity ===
153 /// This node's cryptographic identity.
154 identity: Identity,
155
156 /// Random epoch generated at startup for peer restart detection.
157 /// Exchanged inside Noise handshake messages so peers can detect restarts.
158 startup_epoch: [u8; 8],
159
160 /// Instant when the node was created, for uptime reporting.
161 started_at: std::time::Instant,
162
163 // === Configuration ===
164 /// Loaded configuration.
165 config: Config,
166
167 // === State ===
168 /// Node operational state.
169 state: NodeState,
170
171 /// Whether this is a leaf-only node.
172 is_leaf_only: bool,
173
174 // === Spanning Tree ===
175 /// Local spanning tree state.
176 tree_state: TreeState,
177
178 // === Bloom Filter ===
179 /// Local Bloom filter state.
180 bloom_state: BloomState,
181
182 // === Routing ===
183 /// Address -> coordinates cache (from session setup and discovery).
184 coord_cache: CoordCache,
185 /// Locally learned reverse-path next-hop hints.
186 learned_routes: LearnedRouteTable,
187 /// Destinations whose direct first-hop path is temporarily suspect because
188 /// session-layer MMP observed sustained loss while using that direct path.
189 session_direct_degradation: SessionDirectDegradation,
190 /// Recent discovery requests for dedup and reverse-path forwarding.
191 recent_requests: RecentDiscoveryRequests,
192 /// Per-destination path MTU lookup, keyed by FipsAddress (mirrors
193 /// `coord_cache.entries[*].path_mtu`). Sync read-only access from
194 /// the TUN reader/writer threads at TCP MSS clamp time so the
195 /// SYN/SYN-ACK clamp can use the smaller of the local-egress floor
196 /// and the learned per-destination path MTU.
197 path_mtu_lookup: Arc<std::sync::RwLock<HashMap<crate::FipsAddress, u16>>>,
198
199 // === Transports & Links ===
200 /// Active transports (owned by Node).
201 transports: HashMap<TransportId, TransportHandle>,
202 /// Short-lived cache for numeric UDP candidate -> compatible local transport.
203 /// This avoids repeated interface scans and route-probe sockets while direct
204 /// retry code is reconsidering the same LAN/NAT hints.
205 udp_transport_resolution_cache: lifecycle::UdpTransportResolutionCache,
206 /// Per-transport kernel drop tracking for congestion detection.
207 transport_drops: TransportDropTracker,
208 /// Per-transport wildcard socket-local drop tracking for observability.
209 transport_socket_drops: TransportDropTracker,
210 /// Per-transport Linux namespace receive-buffer error tracking for observability.
211 transport_namespace_drops: TransportDropTracker,
212 /// Active links plus reverse address dispatch index.
213 links: LinkRegistry,
214
215 // === Packet Channel ===
216 /// Packet sender for transports.
217 packet_tx: Option<PacketTx>,
218 /// Packet receiver (for event loop).
219 packet_rx: Option<PacketRx>,
220
221 // === Packet Mover2 ===
222 /// Canonical packet_mover2 dataplane state owned by the node.
223 #[allow(dead_code)]
224 packet_mover2: PacketMover2Node,
225 /// Pre-routed established FMP packets accepted directly from UDP receive.
226 packet_mover2_fast_ingress_rx: Option<PacketMover2FastIngressRx>,
227 /// Bounded PM2 bulk UDP send executor used by the live daemon path.
228 packet_mover2_transport_send_worker: PacketMover2TransportSendWorkerPool,
229
230 // === Peer Lifecycle ===
231 /// Pending handshake connections plus authenticated peers.
232 peers: PeerLifecycleRegistry,
233
234 // === End-to-End Sessions ===
235 /// Session table for end-to-end encrypted sessions.
236 /// Keyed by remote NodeAddr.
237 sessions: SessionRegistry,
238
239 // === Identity Cache ===
240 /// Maps FipsAddress prefix bytes (bytes 1-15) to cached peer identity data.
241 /// Enables reverse lookup from IPv6 destination to session/routing identity.
242 identity_cache: IdentityCache,
243
244 // === Pending TUN Packets ===
245 /// TUN packets and endpoint payloads queued while waiting for session establishment.
246 pending_session_traffic: PendingSessionTrafficQueues,
247 // === Pending Discovery Lookups ===
248 /// Tracks in-flight discovery lookups and owns dedupe/cap admission.
249 pending_lookups: handlers::discovery::PendingDiscoveryLookups,
250
251 // === Resource Limits ===
252 /// Maximum connections (0 = unlimited).
253 max_connections: usize,
254 /// Maximum peers (0 = unlimited).
255 max_peers: usize,
256 /// Maximum links (0 = unlimited).
257 max_links: usize,
258
259 // === Counters ===
260 /// Next link ID to allocate.
261 next_link_id: u64,
262 /// Next transport ID to allocate.
263 next_transport_id: u32,
264
265 // === Node Statistics ===
266 /// Routing, forwarding, discovery, and error signal counters.
267 stats: stats::NodeStats,
268
269 /// Time-series history of node-level metrics (1s/1m rings).
270 stats_history: stats_history::StatsHistory,
271
272 // === TUN Interface ===
273 /// TUN device state.
274 tun_state: TunState,
275 /// TUN interface name (for cleanup).
276 tun_name: Option<String>,
277 /// TUN packet sender channel.
278 tun_tx: Option<TunTx>,
279 /// Receiver for outbound packets from the TUN reader.
280 tun_outbound_rx: Option<TunOutboundRx>,
281 /// App-owned packet sink used by embedded/no-TUN integrations.
282 external_packet_tx: Option<tokio::sync::mpsc::Sender<NodeDeliveredPacket>>,
283 /// Endpoint control receiver used by embedded/no-daemon integrations.
284 endpoint_control_rx: Option<tokio::sync::mpsc::Receiver<NodeEndpointControlCommand>>,
285 /// Endpoint data batch receiver used by embedded/no-daemon integrations.
286 endpoint_data_rx: Option<EndpointDataBatchRx>,
287 /// Endpoint data event delivery runtime used by embedded/no-daemon integrations.
288 endpoint_events: EndpointEventRuntime,
289 /// TUN reader thread handle.
290 tun_reader_handle: Option<JoinHandle<()>>,
291 /// TUN writer thread handle.
292 tun_writer_handle: Option<JoinHandle<()>>,
293 /// Shutdown pipe: writing to this fd unblocks the TUN reader thread on macOS.
294 /// On Linux, deleting the interface via netlink serves the same purpose.
295 #[cfg(target_os = "macos")]
296 tun_shutdown_fd: Option<std::os::unix::io::RawFd>,
297
298 // === DNS Responder ===
299 /// Receiver for resolved identities from the DNS responder.
300 dns_identity_rx: Option<crate::upper::dns::DnsIdentityRx>,
301 /// DNS responder task handle.
302 dns_task: Option<tokio::task::JoinHandle<()>>,
303
304 // === Index-Based Session Dispatch ===
305 /// Allocator for session indices.
306 index_allocator: IndexAllocator,
307 /// Pending outbound handshakes by our sender_idx.
308 /// Tracks which LinkId corresponds to which session index.
309 pending_outbound: PendingOutboundHandshakes,
310
311 // === Rate Limiting ===
312 /// Rate limiter for msg1 processing (DoS protection).
313 msg1_rate_limiter: HandshakeRateLimiter,
314 /// Rate limiter for ICMP Packet Too Big messages.
315 icmp_rate_limiter: IcmpRateLimiter,
316 /// Rate limiter for routing error signals (CoordsRequired / PathBroken).
317 routing_error_rate_limiter: RoutingErrorRateLimiter,
318 /// Rate limiter for source-side CoordsRequired/PathBroken responses.
319 coords_response_rate_limiter: RoutingErrorRateLimiter,
320 /// Backoff for failed discovery lookups (originator-side).
321 discovery_backoff: DiscoveryBackoff,
322 /// Rate limiter for forwarded discovery requests (transit-side).
323 discovery_forward_limiter: DiscoveryForwardRateLimiter,
324
325 // === Pending Transport Connects ===
326 /// Links waiting for transport-level connection establishment before
327 /// sending handshake msg1. For connection-oriented transports (TCP, Tor),
328 /// the transport connect runs in the background; the tick handler polls
329 /// connection_state() and initiates the handshake when connected.
330 pending_connects: Vec<PendingConnect>,
331
332 // === Connection Retry ===
333 /// Retry state for peers whose outbound connections have failed.
334 /// Keyed by NodeAddr. Entries are created when a handshake times out
335 /// or fails, and removed on successful promotion or when max retries
336 /// are exhausted.
337 retry_pending: retry::PendingRouteRetries,
338
339 /// Optional Nostr/STUN overlay discovery coordinator for `udp:nat` peers.
340 nostr_discovery: Option<Arc<crate::discovery::nostr::NostrDiscovery>>,
341 /// mDNS / DNS-SD responder + browser for local-link peer discovery.
342 /// Identity is unverified at this layer — the Noise XX handshake
343 /// initiated against an mDNS-observed endpoint is what proves the
344 /// peer holds the matching private key.
345 lan_discovery: Option<Arc<crate::discovery::lan::LanDiscovery>>,
346 /// Same-host JSON registry under `~/.fips/instances`. Records are
347 /// loopback routing hints only; peer identity is still verified by the
348 /// Noise handshake.
349 local_instance_registry: Option<crate::discovery::local::LocalInstanceRegistry>,
350 local_instance_started_at_ms: Option<u64>,
351 last_local_instance_publish_ms: Option<u64>,
352 last_local_instance_scan_ms: Option<u64>,
353 /// Wall-clock ms when Nostr discovery successfully started, used to
354 /// schedule the one-shot startup advert sweep after a settle delay.
355 /// `None` until discovery comes up; remains `None` if discovery is
356 /// disabled or failed to start.
357 nostr_discovery_started_at_ms: Option<u64>,
358 /// Whether the one-shot startup advert sweep has run. Set to true
359 /// after the first sweep fires (under `policy: open`); thereafter
360 /// only the per-tick `queue_open_discovery_retries` continues.
361 startup_open_discovery_sweep_done: bool,
362 /// Per-peer UDP transports adopted from NAT traversal handoff plus the
363 /// originating peer npub for protocol-mismatch cooldown bookkeeping.
364 bootstrap_transports: BootstrapTransports,
365 /// Peers that should not be used as reply-learned fallback transit for
366 /// other destinations. Direct lookups to the peer are still permitted.
367 discovery_fallback_transit: DiscoveryFallbackTransit,
368
369 // === Periodic Parent Re-evaluation ===
370 /// Timestamp of last periodic parent re-evaluation (for pacing).
371 last_parent_reeval: Option<crate::time::Instant>,
372
373 // === Congestion Logging ===
374 /// Timestamp of last congestion detection log (rate-limited to 5s).
375 last_congestion_log: Option<std::time::Instant>,
376
377 // === Mesh Size Estimate ===
378 /// Cached estimated mesh size (computed once per tick from bloom filters).
379 estimated_mesh_size: Option<u64>,
380 /// Timestamp of last mesh size log emission.
381 last_mesh_size_log: Option<std::time::Instant>,
382
383 // === Bloom Self-Plausibility ===
384 /// Rate-limit state for the self-plausibility WARN. Fires at most
385 /// once per 60s globally when our own outgoing FilterAnnounce has
386 /// an FPR above `node.bloom.max_inbound_fpr`, signalling either
387 /// aggregation drift or an ingress bypass.
388 last_self_warn: Option<std::time::Instant>,
389
390 // === Local Outbound Liveness ===
391 /// Set per peer when a `transport.send` returned a local-side io error
392 /// (`NetworkUnreachable` / `HostUnreachable` / `AddrNotAvailable`),
393 /// cleared on the next successful send to that peer. Used by
394 /// `check_link_heartbeats` to compress only that peer's dead-timeout to
395 /// `fast_link_dead_timeout_secs` while its outbound is observed broken.
396 local_send_failures: LocalSendFailures,
397 /// Set when the rx loop could not complete its 1s maintenance work
398 /// inside the watchdog timeout. Link-dead detection may be valid during
399 /// overload, but traversal cooldown should not punish a path just because
400 /// our own scheduler/worker queue was late.
401 last_rx_loop_maintenance_timeout_at: Option<std::time::Instant>,
402
403 // === Display Names ===
404 /// Human-readable names for configured peers (alias or short npub).
405 /// Populated at startup from peer config.
406 peer_aliases: HashMap<NodeAddr, String>,
407 /// Scheduler weight for explicitly configured peers. Built when config
408 /// changes so the packet hot path only does a NodeAddr hash lookup.
409 configured_peer_send_weights: ConfiguredPeerSendWeights,
410
411 /// Reloadable peer ACL state from standard allow/deny files.
412 peer_acl: acl::PeerAclReloader,
413
414 // === Host Map ===
415 /// Static hostname → npub mapping for DNS resolution.
416 /// Built at construction from peer aliases and /etc/fips/hosts.
417 host_map: Arc<HostMap>,
418}
419
420impl fmt::Debug for Node {
421 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
422 f.debug_struct("Node")
423 .field("node_addr", self.node_addr())
424 .field("state", &self.state)
425 .field("is_leaf_only", &self.is_leaf_only)
426 .field("connections", &self.connection_count())
427 .field("peers", &self.peer_count())
428 .field("links", &self.link_count())
429 .field("transports", &self.transport_count())
430 .finish()
431 }
432}