Skip to main content

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