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