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