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