Skip to main content

fips_core/config/
node.rs

1//! Node configuration subsections.
2//!
3//! All the `node.*` configuration parameters: resource limits, rate limiting,
4//! retry/backoff, cache sizing, discovery, spanning tree, bloom filters,
5//! session management, and internal buffers.
6
7use serde::{Deserialize, Serialize};
8
9use super::IdentityConfig;
10use crate::mmp::{DEFAULT_LOG_INTERVAL_SECS, DEFAULT_OWD_WINDOW_SIZE, MmpConfig, MmpMode};
11
12// ============================================================================
13// Node Configuration Subsections
14// ============================================================================
15
16/// Resource limits (`node.limits.*`).
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct LimitsConfig {
19    /// Max handshake-phase connections (`node.limits.max_connections`).
20    #[serde(default = "LimitsConfig::default_max_connections")]
21    pub max_connections: usize,
22    /// Max authenticated peers (`node.limits.max_peers`).
23    #[serde(default = "LimitsConfig::default_max_peers")]
24    pub max_peers: usize,
25    /// Max active links (`node.limits.max_links`).
26    #[serde(default = "LimitsConfig::default_max_links")]
27    pub max_links: usize,
28    /// Max pending inbound handshakes (`node.limits.max_pending_inbound`).
29    #[serde(default = "LimitsConfig::default_max_pending_inbound")]
30    pub max_pending_inbound: usize,
31}
32
33impl Default for LimitsConfig {
34    fn default() -> Self {
35        Self {
36            max_connections: 256,
37            max_peers: 128,
38            max_links: 256,
39            max_pending_inbound: 1000,
40        }
41    }
42}
43
44impl LimitsConfig {
45    fn default_max_connections() -> usize {
46        256
47    }
48    fn default_max_peers() -> usize {
49        128
50    }
51    fn default_max_links() -> usize {
52        256
53    }
54    fn default_max_pending_inbound() -> usize {
55        1000
56    }
57}
58
59/// Rate limiting (`node.rate_limit.*`).
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct RateLimitConfig {
62    /// Token bucket burst capacity (`node.rate_limit.handshake_burst`).
63    #[serde(default = "RateLimitConfig::default_handshake_burst")]
64    pub handshake_burst: u32,
65    /// Tokens/sec refill rate (`node.rate_limit.handshake_rate`).
66    #[serde(default = "RateLimitConfig::default_handshake_rate")]
67    pub handshake_rate: f64,
68    /// Stale handshake cleanup timeout in seconds (`node.rate_limit.handshake_timeout_secs`).
69    #[serde(default = "RateLimitConfig::default_handshake_timeout_secs")]
70    pub handshake_timeout_secs: u64,
71    /// Initial handshake resend interval in ms (`node.rate_limit.handshake_resend_interval_ms`).
72    /// Handshake messages are resent with exponential backoff within the timeout window.
73    #[serde(default = "RateLimitConfig::default_handshake_resend_interval_ms")]
74    pub handshake_resend_interval_ms: u64,
75    /// Handshake resend backoff multiplier (`node.rate_limit.handshake_resend_backoff`).
76    #[serde(default = "RateLimitConfig::default_handshake_resend_backoff")]
77    pub handshake_resend_backoff: f64,
78    /// Max handshake resends per attempt (`node.rate_limit.handshake_max_resends`).
79    #[serde(default = "RateLimitConfig::default_handshake_max_resends")]
80    pub handshake_max_resends: u32,
81}
82
83impl Default for RateLimitConfig {
84    fn default() -> Self {
85        Self {
86            handshake_burst: 100,
87            handshake_rate: 10.0,
88            handshake_timeout_secs: 30,
89            handshake_resend_interval_ms: 1000,
90            handshake_resend_backoff: 2.0,
91            handshake_max_resends: 5,
92        }
93    }
94}
95
96impl RateLimitConfig {
97    fn default_handshake_burst() -> u32 {
98        100
99    }
100    fn default_handshake_rate() -> f64 {
101        10.0
102    }
103    fn default_handshake_timeout_secs() -> u64 {
104        30
105    }
106    fn default_handshake_resend_interval_ms() -> u64 {
107        1000
108    }
109    fn default_handshake_resend_backoff() -> f64 {
110        2.0
111    }
112    fn default_handshake_max_resends() -> u32 {
113        5
114    }
115}
116
117/// Retry/backoff configuration (`node.retry.*`).
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct RetryConfig {
120    /// Max connection retry attempts (`node.retry.max_retries`).
121    #[serde(default = "RetryConfig::default_max_retries")]
122    pub max_retries: u32,
123    /// Base backoff interval in seconds (`node.retry.base_interval_secs`).
124    #[serde(default = "RetryConfig::default_base_interval_secs")]
125    pub base_interval_secs: u64,
126    /// Cap on exponential backoff in seconds (`node.retry.max_backoff_secs`).
127    #[serde(default = "RetryConfig::default_max_backoff_secs")]
128    pub max_backoff_secs: u64,
129}
130
131impl Default for RetryConfig {
132    fn default() -> Self {
133        Self {
134            max_retries: 5,
135            base_interval_secs: 5,
136            max_backoff_secs: 300,
137        }
138    }
139}
140
141impl RetryConfig {
142    fn default_max_retries() -> u32 {
143        5
144    }
145    fn default_base_interval_secs() -> u64 {
146        5
147    }
148    fn default_max_backoff_secs() -> u64 {
149        300
150    }
151}
152
153/// Cache parameters (`node.cache.*`).
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct CacheConfig {
156    /// Max entries in coord cache (`node.cache.coord_size`).
157    #[serde(default = "CacheConfig::default_coord_size")]
158    pub coord_size: usize,
159    /// Coord cache entry TTL in seconds (`node.cache.coord_ttl_secs`).
160    #[serde(default = "CacheConfig::default_coord_ttl_secs")]
161    pub coord_ttl_secs: u64,
162    /// Max entries in identity cache (`node.cache.identity_size`).
163    #[serde(default = "CacheConfig::default_identity_size")]
164    pub identity_size: usize,
165}
166
167impl Default for CacheConfig {
168    fn default() -> Self {
169        Self {
170            coord_size: 50_000,
171            coord_ttl_secs: 300,
172            identity_size: 10_000,
173        }
174    }
175}
176
177impl CacheConfig {
178    fn default_coord_size() -> usize {
179        50_000
180    }
181    fn default_coord_ttl_secs() -> u64 {
182        300
183    }
184    fn default_identity_size() -> usize {
185        10_000
186    }
187}
188
189mod discovery;
190
191pub use discovery::{
192    DiscoveryConfig, NostrDiscoveryConfig, NostrDiscoveryPolicy, NostrPeerfindingSource,
193};
194
195/// Spanning tree (`node.tree.*`).
196#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct TreeConfig {
198    /// Per-peer TreeAnnounce rate limit in ms (`node.tree.announce_min_interval_ms`).
199    #[serde(default = "TreeConfig::default_announce_min_interval_ms")]
200    pub announce_min_interval_ms: u64,
201    /// Hysteresis factor for cost-based parent re-selection (`node.tree.parent_hysteresis`).
202    ///
203    /// Only switch parents when the candidate's effective_depth is better than
204    /// `current_effective_depth * (1.0 - parent_hysteresis)`. Range: 0.0-1.0.
205    /// Set to 0.0 to disable hysteresis (switch on any improvement).
206    #[serde(default = "TreeConfig::default_parent_hysteresis")]
207    pub parent_hysteresis: f64,
208    /// Hold-down period after parent switch in seconds (`node.tree.hold_down_secs`).
209    ///
210    /// After switching parents, suppress re-evaluation for this duration to allow
211    /// MMP metrics to stabilize on the new link. Set to 0 to disable.
212    #[serde(default = "TreeConfig::default_hold_down_secs")]
213    pub hold_down_secs: u64,
214    /// Periodic parent re-evaluation interval in seconds (`node.tree.reeval_interval_secs`).
215    ///
216    /// How often to re-evaluate parent selection based on current MMP link costs,
217    /// independent of TreeAnnounce traffic. Catches link degradation after the
218    /// tree has stabilized. Set to 0 to disable.
219    #[serde(default = "TreeConfig::default_reeval_interval_secs")]
220    pub reeval_interval_secs: u64,
221    /// Flap dampening: max parent switches before extended hold-down (`node.tree.flap_threshold`).
222    #[serde(default = "TreeConfig::default_flap_threshold")]
223    pub flap_threshold: u32,
224    /// Flap dampening: window in seconds for counting switches (`node.tree.flap_window_secs`).
225    #[serde(default = "TreeConfig::default_flap_window_secs")]
226    pub flap_window_secs: u64,
227    /// Flap dampening: extended hold-down duration in seconds (`node.tree.flap_dampening_secs`).
228    #[serde(default = "TreeConfig::default_flap_dampening_secs")]
229    pub flap_dampening_secs: u64,
230}
231
232impl Default for TreeConfig {
233    fn default() -> Self {
234        Self {
235            announce_min_interval_ms: 500,
236            parent_hysteresis: 0.2,
237            hold_down_secs: 30,
238            reeval_interval_secs: 60,
239            flap_threshold: 4,
240            flap_window_secs: 60,
241            flap_dampening_secs: 120,
242        }
243    }
244}
245
246impl TreeConfig {
247    fn default_announce_min_interval_ms() -> u64 {
248        500
249    }
250    fn default_parent_hysteresis() -> f64 {
251        0.2
252    }
253    fn default_hold_down_secs() -> u64 {
254        30
255    }
256    fn default_reeval_interval_secs() -> u64 {
257        60
258    }
259    fn default_flap_threshold() -> u32 {
260        4
261    }
262    fn default_flap_window_secs() -> u64 {
263        60
264    }
265    fn default_flap_dampening_secs() -> u64 {
266        120
267    }
268}
269
270/// Routing strategy selection (`node.routing.*`).
271#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct RoutingConfig {
273    /// Next-hop selection mode (`node.routing.mode`).
274    #[serde(default)]
275    pub mode: RoutingMode,
276    /// TTL for learned reverse-path routes in seconds (`node.routing.learned_ttl_secs`).
277    #[serde(default = "RoutingConfig::default_learned_ttl_secs")]
278    pub learned_ttl_secs: u64,
279    /// Maximum locally observed next-hop candidates kept per destination for
280    /// reply-learned multipath/exploration
281    /// (`node.routing.max_learned_routes_per_dest`).
282    #[serde(default = "RoutingConfig::default_max_learned_routes_per_dest")]
283    pub max_learned_routes_per_dest: usize,
284    /// Every N learned-route selections, try the coordinate/bloom/tree route
285    /// instead so new paths can be discovered (`0` disables fallback exploration).
286    #[serde(default = "RoutingConfig::default_learned_fallback_explore_interval")]
287    pub learned_fallback_explore_interval: u64,
288}
289
290impl Default for RoutingConfig {
291    fn default() -> Self {
292        Self {
293            mode: RoutingMode::default(),
294            learned_ttl_secs: 300,
295            max_learned_routes_per_dest: 4,
296            learned_fallback_explore_interval: 16,
297        }
298    }
299}
300
301impl RoutingConfig {
302    fn default_learned_ttl_secs() -> u64 {
303        300
304    }
305
306    fn default_max_learned_routes_per_dest() -> usize {
307        4
308    }
309
310    fn default_learned_fallback_explore_interval() -> u64 {
311        16
312    }
313}
314
315/// Daemon routing mode.
316#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
317#[serde(rename_all = "snake_case")]
318pub enum RoutingMode {
319    /// Current FIPS behavior: bloom-assisted greedy tree routing.
320    #[default]
321    Tree,
322    /// Prefer locally learned reverse paths before falling back to tree routing.
323    ///
324    /// Learned routes are populated only from local evidence: inbound
325    /// SessionDatagrams and verified LookupResponses.
326    ReplyLearned,
327}
328
329impl std::fmt::Display for RoutingMode {
330    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
331        match self {
332            RoutingMode::Tree => write!(f, "tree"),
333            RoutingMode::ReplyLearned => write!(f, "reply_learned"),
334        }
335    }
336}
337
338/// Bloom filter (`node.bloom.*`).
339#[derive(Debug, Clone, Serialize, Deserialize)]
340pub struct BloomConfig {
341    /// Debounce interval for filter updates in ms (`node.bloom.update_debounce_ms`).
342    #[serde(default = "BloomConfig::default_update_debounce_ms")]
343    pub update_debounce_ms: u64,
344    /// Antipoison cap: reject inbound FilterAnnounce whose FPR exceeds
345    /// this value (`node.bloom.max_inbound_fpr`). Valid range `(0.0, 1.0)`.
346    /// Default `0.10` ≈ fill 0.631 at k=5 ≈ ~1,630 entries on the 1 KB
347    /// filter. This leaves headroom for legitimate aggregates near the
348    /// fixed-filter operating ceiling while still rejecting saturated or
349    /// poisoned filters. Conceptually distinct from future autoscaling
350    /// hysteresis setpoints — same unit, different knobs.
351    #[serde(default = "BloomConfig::default_max_inbound_fpr")]
352    pub max_inbound_fpr: f64,
353}
354
355impl Default for BloomConfig {
356    fn default() -> Self {
357        Self {
358            update_debounce_ms: 500,
359            max_inbound_fpr: 0.10,
360        }
361    }
362}
363
364impl BloomConfig {
365    fn default_update_debounce_ms() -> u64 {
366        500
367    }
368    fn default_max_inbound_fpr() -> f64 {
369        0.10
370    }
371}
372
373/// Session/data plane (`node.session.*`).
374#[derive(Debug, Clone, Serialize, Deserialize)]
375pub struct SessionConfig {
376    /// Default SessionDatagram TTL (`node.session.default_ttl`).
377    #[serde(default = "SessionConfig::default_ttl")]
378    pub default_ttl: u8,
379    /// Queue depth per dest during session establishment (`node.session.pending_packets_per_dest`).
380    #[serde(default = "SessionConfig::default_pending_packets_per_dest")]
381    pub pending_packets_per_dest: usize,
382    /// Max destinations with pending packets (`node.session.pending_max_destinations`).
383    #[serde(default = "SessionConfig::default_pending_max_destinations")]
384    pub pending_max_destinations: usize,
385    /// Idle session timeout in seconds (`node.session.idle_timeout_secs`).
386    /// Established sessions with no application data for this duration are
387    /// removed. MMP reports do not count as activity for this timer.
388    #[serde(default = "SessionConfig::default_idle_timeout_secs")]
389    pub idle_timeout_secs: u64,
390    /// Number of initial data packets per session that include COORDS_PRESENT
391    /// for transit cache warmup (`node.session.coords_warmup_packets`).
392    /// Also used as the reset count on CoordsRequired receipt.
393    #[serde(default = "SessionConfig::default_coords_warmup_packets")]
394    pub coords_warmup_packets: u8,
395    /// Minimum interval (ms) between standalone CoordsWarmup responses to
396    /// CoordsRequired/PathBroken signals, per destination
397    /// (`node.session.coords_response_interval_ms`).
398    #[serde(default = "SessionConfig::default_coords_response_interval_ms")]
399    pub coords_response_interval_ms: u64,
400}
401
402impl Default for SessionConfig {
403    fn default() -> Self {
404        Self {
405            default_ttl: 64,
406            pending_packets_per_dest: 16,
407            pending_max_destinations: 256,
408            idle_timeout_secs: 90,
409            coords_warmup_packets: 5,
410            coords_response_interval_ms: 2000,
411        }
412    }
413}
414
415impl SessionConfig {
416    fn default_ttl() -> u8 {
417        64
418    }
419    fn default_pending_packets_per_dest() -> usize {
420        16
421    }
422    fn default_pending_max_destinations() -> usize {
423        256
424    }
425    fn default_idle_timeout_secs() -> u64 {
426        90
427    }
428    fn default_coords_warmup_packets() -> u8 {
429        5
430    }
431    fn default_coords_response_interval_ms() -> u64 {
432        2000
433    }
434}
435
436/// Session-layer Metrics Measurement Protocol (`node.session_mmp.*`).
437///
438/// Separate from link-layer `node.mmp.*` to allow independent mode/interval
439/// configuration per layer. Session reports consume bandwidth on every transit
440/// link, so operators may want a lighter mode (e.g., Lightweight) for sessions
441/// while running Full mode on links.
442#[derive(Debug, Clone, Serialize, Deserialize)]
443pub struct SessionMmpConfig {
444    /// Operating mode (`node.session_mmp.mode`).
445    #[serde(default)]
446    pub mode: MmpMode,
447
448    /// Periodic operator log interval in seconds (`node.session_mmp.log_interval_secs`).
449    #[serde(default = "SessionMmpConfig::default_log_interval_secs")]
450    pub log_interval_secs: u64,
451
452    /// OWD trend ring buffer size (`node.session_mmp.owd_window_size`).
453    #[serde(default = "SessionMmpConfig::default_owd_window_size")]
454    pub owd_window_size: usize,
455}
456
457impl Default for SessionMmpConfig {
458    fn default() -> Self {
459        Self {
460            mode: MmpMode::default(),
461            log_interval_secs: DEFAULT_LOG_INTERVAL_SECS,
462            owd_window_size: DEFAULT_OWD_WINDOW_SIZE,
463        }
464    }
465}
466
467impl SessionMmpConfig {
468    fn default_log_interval_secs() -> u64 {
469        DEFAULT_LOG_INTERVAL_SECS
470    }
471    fn default_owd_window_size() -> usize {
472        DEFAULT_OWD_WINDOW_SIZE
473    }
474}
475
476/// Control socket configuration (`node.control.*`).
477#[derive(Debug, Clone, Serialize, Deserialize)]
478pub struct ControlConfig {
479    /// Enable the control socket (`node.control.enabled`).
480    #[serde(default = "ControlConfig::default_enabled")]
481    pub enabled: bool,
482    /// Unix socket path (`node.control.socket_path`).
483    #[serde(default = "ControlConfig::default_socket_path")]
484    pub socket_path: String,
485}
486
487impl Default for ControlConfig {
488    fn default() -> Self {
489        Self {
490            enabled: true,
491            socket_path: Self::default_socket_path(),
492        }
493    }
494}
495
496impl ControlConfig {
497    fn default_enabled() -> bool {
498        true
499    }
500
501    /// Default control socket path.
502    ///
503    /// On Unix, returns the shared `/run/fips`, `XDG_RUNTIME_DIR`, then `/tmp`
504    /// fallback used by fipsctl and fipstop. On Windows, returns a TCP port
505    /// number as a string since Windows does not support Unix domain sockets;
506    /// the control socket listens on localhost at this port.
507    fn default_socket_path() -> String {
508        #[cfg(unix)]
509        {
510            super::resolve_default_socket("control.sock")
511        }
512        #[cfg(windows)]
513        {
514            "21210".to_string()
515        }
516    }
517}
518
519const DEFAULT_PACKET_CHANNEL_CAPACITY: usize = 16_384;
520
521/// Internal buffers (`node.buffers.*`).
522#[derive(Debug, Clone, Serialize, Deserialize)]
523pub struct BuffersConfig {
524    /// Transport→Node bulk packet capacity (`node.buffers.packet_channel`).
525    ///
526    /// Priority/control packets use a reserved lane. This bounds bulk packet
527    /// backlog in packet units rather than receive-batch channel items.
528    #[serde(default = "BuffersConfig::default_packet_channel")]
529    pub packet_channel: usize,
530    /// TUN→Node outbound channel capacity (`node.buffers.tun_channel`).
531    #[serde(default = "BuffersConfig::default_tun_channel")]
532    pub tun_channel: usize,
533    /// DNS→Node identity channel capacity (`node.buffers.dns_channel`).
534    #[serde(default = "BuffersConfig::default_dns_channel")]
535    pub dns_channel: usize,
536}
537
538impl Default for BuffersConfig {
539    fn default() -> Self {
540        Self {
541            packet_channel: DEFAULT_PACKET_CHANNEL_CAPACITY,
542            tun_channel: 1024,
543            dns_channel: 64,
544        }
545    }
546}
547
548impl BuffersConfig {
549    fn default_packet_channel() -> usize {
550        DEFAULT_PACKET_CHANNEL_CAPACITY
551    }
552    fn default_tun_channel() -> usize {
553        1024
554    }
555    fn default_dns_channel() -> usize {
556        64
557    }
558}
559
560// ============================================================================
561// ECN Congestion Signaling
562// ============================================================================
563
564/// Rekey / session rekeying configuration (`node.rekey.*`).
565///
566/// Controls periodic full rekey for both FMP (link layer) and FSP
567/// (session layer) Noise sessions. Rekeying provides true forward secrecy
568/// with fresh DH randomness, nonce reset, and session index rotation.
569///
570/// Keep the packet-count default high for packet-tunnel workloads. A low value
571/// such as 65k packets can force multi-hundred-Mbit tunnels to rekey every few
572/// seconds, which creates avoidable cutover churn and can dominate throughput.
573/// Operators can still lower `node.rekey.after_messages` for CI stress tests or
574/// very conservative deployments; the time-based `after_secs` default remains
575/// the normal production rekey cadence.
576const DEFAULT_REKEY_AFTER_MESSAGES: u64 = 1 << 48;
577
578#[derive(Debug, Clone, Serialize, Deserialize)]
579pub struct RekeyConfig {
580    /// Enable periodic rekey (`node.rekey.enabled`).
581    #[serde(default = "RekeyConfig::default_enabled")]
582    pub enabled: bool,
583
584    /// Initiate rekey after this many seconds (`node.rekey.after_secs`).
585    #[serde(default = "RekeyConfig::default_after_secs")]
586    pub after_secs: u64,
587
588    /// Initiate rekey after this many messages sent (`node.rekey.after_messages`).
589    #[serde(default = "RekeyConfig::default_after_messages")]
590    pub after_messages: u64,
591}
592
593impl Default for RekeyConfig {
594    fn default() -> Self {
595        Self {
596            enabled: true,
597            after_secs: 120,
598            after_messages: DEFAULT_REKEY_AFTER_MESSAGES,
599        }
600    }
601}
602
603impl RekeyConfig {
604    fn default_enabled() -> bool {
605        true
606    }
607    fn default_after_secs() -> u64 {
608        120
609    }
610    fn default_after_messages() -> u64 {
611        DEFAULT_REKEY_AFTER_MESSAGES
612    }
613}
614
615/// ECN congestion signaling configuration (`node.ecn.*`).
616///
617/// Controls the FMP CE relay chain: transit nodes detect congestion on outgoing
618/// links and set the CE flag in forwarded datagrams. The destination marks
619/// IPv6 ECN-CE on ECN-capable packets before TUN delivery.
620#[derive(Debug, Clone, Serialize, Deserialize)]
621pub struct EcnConfig {
622    /// Enable ECN congestion signaling (`node.ecn.enabled`).
623    #[serde(default = "EcnConfig::default_enabled")]
624    pub enabled: bool,
625
626    /// Loss rate threshold for marking CE (`node.ecn.loss_threshold`).
627    /// When the outgoing link's loss rate meets or exceeds this value,
628    /// the transit node sets CE on forwarded datagrams.
629    #[serde(default = "EcnConfig::default_loss_threshold")]
630    pub loss_threshold: f64,
631
632    /// ETX threshold for marking CE (`node.ecn.etx_threshold`).
633    /// When the outgoing link's ETX meets or exceeds this value,
634    /// the transit node sets CE on forwarded datagrams.
635    #[serde(default = "EcnConfig::default_etx_threshold")]
636    pub etx_threshold: f64,
637}
638
639impl Default for EcnConfig {
640    fn default() -> Self {
641        Self {
642            enabled: true,
643            loss_threshold: 0.05,
644            etx_threshold: 3.0,
645        }
646    }
647}
648
649impl EcnConfig {
650    fn default_enabled() -> bool {
651        true
652    }
653    fn default_loss_threshold() -> f64 {
654        0.05
655    }
656    fn default_etx_threshold() -> f64 {
657        3.0
658    }
659}
660
661// ============================================================================
662// Node Configuration (Root)
663// ============================================================================
664
665/// Node configuration (`node.*`).
666#[derive(Debug, Clone, Serialize, Deserialize)]
667pub struct NodeConfig {
668    /// Identity configuration (`node.identity.*`).
669    #[serde(default)]
670    pub identity: IdentityConfig,
671
672    /// Leaf-only mode (`node.leaf_only`).
673    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
674    pub leaf_only: bool,
675
676    /// RX loop maintenance tick period in seconds (`node.tick_interval_secs`).
677    #[serde(default = "NodeConfig::default_tick_interval_secs")]
678    pub tick_interval_secs: u64,
679
680    /// Initial RTT estimate for new links in ms (`node.base_rtt_ms`).
681    #[serde(default = "NodeConfig::default_base_rtt_ms")]
682    pub base_rtt_ms: u64,
683
684    /// Link heartbeat send interval in seconds (`node.heartbeat_interval_secs`).
685    #[serde(default = "NodeConfig::default_heartbeat_interval_secs")]
686    pub heartbeat_interval_secs: u64,
687
688    /// Link dead timeout in seconds (`node.link_dead_timeout_secs`).
689    /// Peers silent for this duration are removed.
690    #[serde(default = "NodeConfig::default_link_dead_timeout_secs")]
691    pub link_dead_timeout_secs: u64,
692
693    /// Accelerated link dead timeout in seconds, used in place of
694    /// `link_dead_timeout_secs` while a recent `transport.send` returned
695    /// a local-side errno (`NetworkUnreachable` / `HostUnreachable` /
696    /// `AddrNotAvailable`) — direct evidence our outbound path is broken
697    /// right now (interface vanished, default route flapped, etc.). No
698    /// reason to wait the full receive-silence window when the kernel
699    /// already told us we can't send. Steady-state behavior is unchanged
700    /// because the signal is cleared on the next successful send.
701    /// (`node.fast_link_dead_timeout_secs`)
702    #[serde(default = "NodeConfig::default_fast_link_dead_timeout_secs")]
703    pub fast_link_dead_timeout_secs: u64,
704
705    /// Resource limits (`node.limits.*`).
706    #[serde(default)]
707    pub limits: LimitsConfig,
708
709    /// Rate limiting (`node.rate_limit.*`).
710    #[serde(default)]
711    pub rate_limit: RateLimitConfig,
712
713    /// Retry/backoff (`node.retry.*`).
714    #[serde(default)]
715    pub retry: RetryConfig,
716
717    /// Cache parameters (`node.cache.*`).
718    #[serde(default)]
719    pub cache: CacheConfig,
720
721    /// Discovery protocol (`node.discovery.*`).
722    #[serde(default)]
723    pub discovery: DiscoveryConfig,
724
725    /// Spanning tree (`node.tree.*`).
726    #[serde(default)]
727    pub tree: TreeConfig,
728
729    /// Routing strategy (`node.routing.*`).
730    #[serde(default)]
731    pub routing: RoutingConfig,
732
733    /// Bloom filter (`node.bloom.*`).
734    #[serde(default)]
735    pub bloom: BloomConfig,
736
737    /// Session/data plane (`node.session.*`).
738    #[serde(default)]
739    pub session: SessionConfig,
740
741    /// Internal buffers (`node.buffers.*`).
742    #[serde(default)]
743    pub buffers: BuffersConfig,
744
745    /// Control socket (`node.control.*`).
746    #[serde(default)]
747    pub control: ControlConfig,
748
749    /// Metrics Measurement Protocol — link layer (`node.mmp.*`).
750    #[serde(default)]
751    pub mmp: MmpConfig,
752
753    /// Metrics Measurement Protocol — session layer (`node.session_mmp.*`).
754    #[serde(default)]
755    pub session_mmp: SessionMmpConfig,
756
757    /// ECN congestion signaling (`node.ecn.*`).
758    #[serde(default)]
759    pub ecn: EcnConfig,
760
761    /// Rekey / session rekeying (`node.rekey.*`).
762    #[serde(default)]
763    pub rekey: RekeyConfig,
764
765    /// Enable daemon-oriented system files such as `/etc/fips/hosts` and
766    /// `/etc/fips/peers.{allow,deny}`. Embedded endpoints disable this.
767    #[serde(default = "NodeConfig::default_system_files_enabled")]
768    pub system_files_enabled: bool,
769
770    /// Log level (`node.log_level`). Case-insensitive.
771    /// Valid values: trace, debug, info, warn, error. Default: info.
772    #[serde(default)]
773    pub log_level: Option<String>,
774}
775
776impl Default for NodeConfig {
777    fn default() -> Self {
778        Self {
779            identity: IdentityConfig::default(),
780            leaf_only: false,
781            tick_interval_secs: 1,
782            base_rtt_ms: 100,
783            heartbeat_interval_secs: 10,
784            link_dead_timeout_secs: 30,
785            fast_link_dead_timeout_secs: 5,
786            limits: LimitsConfig::default(),
787            rate_limit: RateLimitConfig::default(),
788            retry: RetryConfig::default(),
789            cache: CacheConfig::default(),
790            discovery: DiscoveryConfig::default(),
791            tree: TreeConfig::default(),
792            routing: RoutingConfig::default(),
793            bloom: BloomConfig::default(),
794            session: SessionConfig::default(),
795            buffers: BuffersConfig::default(),
796            control: ControlConfig::default(),
797            mmp: MmpConfig::default(),
798            session_mmp: SessionMmpConfig::default(),
799            ecn: EcnConfig::default(),
800            rekey: RekeyConfig::default(),
801            system_files_enabled: true,
802            log_level: None,
803        }
804    }
805}
806
807impl NodeConfig {
808    /// Get the log level as a tracing Level. Default: INFO.
809    pub fn log_level(&self) -> tracing::Level {
810        match self
811            .log_level
812            .as_deref()
813            .map(|s| s.to_lowercase())
814            .as_deref()
815        {
816            Some("trace") => tracing::Level::TRACE,
817            Some("debug") => tracing::Level::DEBUG,
818            Some("warn") | Some("warning") => tracing::Level::WARN,
819            Some("error") => tracing::Level::ERROR,
820            _ => tracing::Level::INFO,
821        }
822    }
823
824    fn default_tick_interval_secs() -> u64 {
825        1
826    }
827    fn default_base_rtt_ms() -> u64 {
828        100
829    }
830    fn default_heartbeat_interval_secs() -> u64 {
831        10
832    }
833    fn default_link_dead_timeout_secs() -> u64 {
834        30
835    }
836    fn default_fast_link_dead_timeout_secs() -> u64 {
837        5
838    }
839    fn default_system_files_enabled() -> bool {
840        true
841    }
842}
843
844#[cfg(test)]
845mod tests;