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