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.05` ≈ fill 0.549 at k=5 ≈ ~3,200 entries on the 1KB
402    /// filter. Conceptually distinct from future autoscaling hysteresis
403    /// setpoints — same unit, different knobs.
404    #[serde(default = "BloomConfig::default_max_inbound_fpr")]
405    pub max_inbound_fpr: f64,
406}
407
408impl Default for BloomConfig {
409    fn default() -> Self {
410        Self {
411            update_debounce_ms: 500,
412            max_inbound_fpr: 0.05,
413        }
414    }
415}
416
417impl BloomConfig {
418    fn default_update_debounce_ms() -> u64 {
419        500
420    }
421    fn default_max_inbound_fpr() -> f64 {
422        0.05
423    }
424}
425
426/// Session/data plane (`node.session.*`).
427#[derive(Debug, Clone, Serialize, Deserialize)]
428pub struct SessionConfig {
429    /// Default SessionDatagram TTL (`node.session.default_ttl`).
430    #[serde(default = "SessionConfig::default_ttl")]
431    pub default_ttl: u8,
432    /// Queue depth per dest during session establishment (`node.session.pending_packets_per_dest`).
433    #[serde(default = "SessionConfig::default_pending_packets_per_dest")]
434    pub pending_packets_per_dest: usize,
435    /// Max destinations with pending packets (`node.session.pending_max_destinations`).
436    #[serde(default = "SessionConfig::default_pending_max_destinations")]
437    pub pending_max_destinations: usize,
438    /// Idle session timeout in seconds (`node.session.idle_timeout_secs`).
439    /// Established sessions with no application data for this duration are
440    /// removed. MMP reports do not count as activity for this timer.
441    #[serde(default = "SessionConfig::default_idle_timeout_secs")]
442    pub idle_timeout_secs: u64,
443    /// Number of initial data packets per session that include COORDS_PRESENT
444    /// for transit cache warmup (`node.session.coords_warmup_packets`).
445    /// Also used as the reset count on CoordsRequired receipt.
446    #[serde(default = "SessionConfig::default_coords_warmup_packets")]
447    pub coords_warmup_packets: u8,
448    /// Minimum interval (ms) between standalone CoordsWarmup responses to
449    /// CoordsRequired/PathBroken signals, per destination
450    /// (`node.session.coords_response_interval_ms`).
451    #[serde(default = "SessionConfig::default_coords_response_interval_ms")]
452    pub coords_response_interval_ms: u64,
453}
454
455impl Default for SessionConfig {
456    fn default() -> Self {
457        Self {
458            default_ttl: 64,
459            pending_packets_per_dest: 16,
460            pending_max_destinations: 256,
461            idle_timeout_secs: 90,
462            coords_warmup_packets: 5,
463            coords_response_interval_ms: 2000,
464        }
465    }
466}
467
468impl SessionConfig {
469    fn default_ttl() -> u8 {
470        64
471    }
472    fn default_pending_packets_per_dest() -> usize {
473        16
474    }
475    fn default_pending_max_destinations() -> usize {
476        256
477    }
478    fn default_idle_timeout_secs() -> u64 {
479        90
480    }
481    fn default_coords_warmup_packets() -> u8 {
482        5
483    }
484    fn default_coords_response_interval_ms() -> u64 {
485        2000
486    }
487}
488
489/// Session-layer Metrics Measurement Protocol (`node.session_mmp.*`).
490///
491/// Separate from link-layer `node.mmp.*` to allow independent mode/interval
492/// configuration per layer. Session reports consume bandwidth on every transit
493/// link, so operators may want a lighter mode (e.g., Lightweight) for sessions
494/// while running Full mode on links.
495#[derive(Debug, Clone, Serialize, Deserialize)]
496pub struct SessionMmpConfig {
497    /// Operating mode (`node.session_mmp.mode`).
498    #[serde(default)]
499    pub mode: MmpMode,
500
501    /// Periodic operator log interval in seconds (`node.session_mmp.log_interval_secs`).
502    #[serde(default = "SessionMmpConfig::default_log_interval_secs")]
503    pub log_interval_secs: u64,
504
505    /// OWD trend ring buffer size (`node.session_mmp.owd_window_size`).
506    #[serde(default = "SessionMmpConfig::default_owd_window_size")]
507    pub owd_window_size: usize,
508}
509
510impl Default for SessionMmpConfig {
511    fn default() -> Self {
512        Self {
513            mode: MmpMode::default(),
514            log_interval_secs: DEFAULT_LOG_INTERVAL_SECS,
515            owd_window_size: DEFAULT_OWD_WINDOW_SIZE,
516        }
517    }
518}
519
520impl SessionMmpConfig {
521    fn default_log_interval_secs() -> u64 {
522        DEFAULT_LOG_INTERVAL_SECS
523    }
524    fn default_owd_window_size() -> usize {
525        DEFAULT_OWD_WINDOW_SIZE
526    }
527}
528
529/// Control socket configuration (`node.control.*`).
530#[derive(Debug, Clone, Serialize, Deserialize)]
531pub struct ControlConfig {
532    /// Enable the control socket (`node.control.enabled`).
533    #[serde(default = "ControlConfig::default_enabled")]
534    pub enabled: bool,
535    /// Unix socket path (`node.control.socket_path`).
536    #[serde(default = "ControlConfig::default_socket_path")]
537    pub socket_path: String,
538}
539
540impl Default for ControlConfig {
541    fn default() -> Self {
542        Self {
543            enabled: true,
544            socket_path: Self::default_socket_path(),
545        }
546    }
547}
548
549impl ControlConfig {
550    fn default_enabled() -> bool {
551        true
552    }
553
554    /// Default control socket path.
555    ///
556    /// On Unix, returns the shared `/run/fips`, `XDG_RUNTIME_DIR`, then `/tmp`
557    /// fallback used by fipsctl and fipstop. On Windows, returns a TCP port
558    /// number as a string since Windows does not support Unix domain sockets;
559    /// the control socket listens on localhost at this port.
560    fn default_socket_path() -> String {
561        #[cfg(unix)]
562        {
563            super::resolve_default_socket("control.sock")
564        }
565        #[cfg(windows)]
566        {
567            "21210".to_string()
568        }
569    }
570}
571
572const DEFAULT_PACKET_CHANNEL_CAPACITY: usize = 4096;
573
574/// Internal buffers (`node.buffers.*`).
575#[derive(Debug, Clone, Serialize, Deserialize)]
576pub struct BuffersConfig {
577    /// Transport→Node bulk packet capacity (`node.buffers.packet_channel`).
578    ///
579    /// Priority/control packets use a reserved lane. This bounds bulk packet
580    /// backlog in packet units rather than receive-batch channel items.
581    #[serde(default = "BuffersConfig::default_packet_channel")]
582    pub packet_channel: usize,
583    /// TUN→Node outbound channel capacity (`node.buffers.tun_channel`).
584    #[serde(default = "BuffersConfig::default_tun_channel")]
585    pub tun_channel: usize,
586    /// DNS→Node identity channel capacity (`node.buffers.dns_channel`).
587    #[serde(default = "BuffersConfig::default_dns_channel")]
588    pub dns_channel: usize,
589}
590
591impl Default for BuffersConfig {
592    fn default() -> Self {
593        Self {
594            packet_channel: DEFAULT_PACKET_CHANNEL_CAPACITY,
595            tun_channel: 1024,
596            dns_channel: 64,
597        }
598    }
599}
600
601impl BuffersConfig {
602    fn default_packet_channel() -> usize {
603        DEFAULT_PACKET_CHANNEL_CAPACITY
604    }
605    fn default_tun_channel() -> usize {
606        1024
607    }
608    fn default_dns_channel() -> usize {
609        64
610    }
611}
612
613// ============================================================================
614// ECN Congestion Signaling
615// ============================================================================
616
617/// Rekey / session rekeying configuration (`node.rekey.*`).
618///
619/// Controls periodic full rekey for both FMP (link layer) and FSP
620/// (session layer) Noise sessions. Rekeying provides true forward secrecy
621/// with fresh DH randomness, nonce reset, and session index rotation.
622///
623/// Keep the packet-count default high for packet-tunnel workloads. A low value
624/// such as 65k packets can force multi-hundred-Mbit tunnels to rekey every few
625/// seconds, which creates avoidable cutover churn and can dominate throughput.
626/// Operators can still lower `node.rekey.after_messages` for CI stress tests or
627/// very conservative deployments; the time-based `after_secs` default remains
628/// the normal production rekey cadence.
629const DEFAULT_REKEY_AFTER_MESSAGES: u64 = 1 << 48;
630
631#[derive(Debug, Clone, Serialize, Deserialize)]
632pub struct RekeyConfig {
633    /// Enable periodic rekey (`node.rekey.enabled`).
634    #[serde(default = "RekeyConfig::default_enabled")]
635    pub enabled: bool,
636
637    /// Initiate rekey after this many seconds (`node.rekey.after_secs`).
638    #[serde(default = "RekeyConfig::default_after_secs")]
639    pub after_secs: u64,
640
641    /// Initiate rekey after this many messages sent (`node.rekey.after_messages`).
642    #[serde(default = "RekeyConfig::default_after_messages")]
643    pub after_messages: u64,
644}
645
646impl Default for RekeyConfig {
647    fn default() -> Self {
648        Self {
649            enabled: true,
650            after_secs: 120,
651            after_messages: DEFAULT_REKEY_AFTER_MESSAGES,
652        }
653    }
654}
655
656impl RekeyConfig {
657    fn default_enabled() -> bool {
658        true
659    }
660    fn default_after_secs() -> u64 {
661        120
662    }
663    fn default_after_messages() -> u64 {
664        DEFAULT_REKEY_AFTER_MESSAGES
665    }
666}
667
668/// ECN congestion signaling configuration (`node.ecn.*`).
669///
670/// Controls the FMP CE relay chain: transit nodes detect congestion on outgoing
671/// links and set the CE flag in forwarded datagrams. The destination marks
672/// IPv6 ECN-CE on ECN-capable packets before TUN delivery.
673#[derive(Debug, Clone, Serialize, Deserialize)]
674pub struct EcnConfig {
675    /// Enable ECN congestion signaling (`node.ecn.enabled`).
676    #[serde(default = "EcnConfig::default_enabled")]
677    pub enabled: bool,
678
679    /// Loss rate threshold for marking CE (`node.ecn.loss_threshold`).
680    /// When the outgoing link's loss rate meets or exceeds this value,
681    /// the transit node sets CE on forwarded datagrams.
682    #[serde(default = "EcnConfig::default_loss_threshold")]
683    pub loss_threshold: f64,
684
685    /// ETX threshold for marking CE (`node.ecn.etx_threshold`).
686    /// When the outgoing link's ETX meets or exceeds this value,
687    /// the transit node sets CE on forwarded datagrams.
688    #[serde(default = "EcnConfig::default_etx_threshold")]
689    pub etx_threshold: f64,
690}
691
692impl Default for EcnConfig {
693    fn default() -> Self {
694        Self {
695            enabled: true,
696            loss_threshold: 0.05,
697            etx_threshold: 3.0,
698        }
699    }
700}
701
702impl EcnConfig {
703    fn default_enabled() -> bool {
704        true
705    }
706    fn default_loss_threshold() -> f64 {
707        0.05
708    }
709    fn default_etx_threshold() -> f64 {
710        3.0
711    }
712}
713
714// ============================================================================
715// Node Configuration (Root)
716// ============================================================================
717
718/// Node configuration (`node.*`).
719#[derive(Debug, Clone, Serialize, Deserialize)]
720pub struct NodeConfig {
721    /// Identity configuration (`node.identity.*`).
722    #[serde(default)]
723    pub identity: IdentityConfig,
724
725    /// Leaf-only mode (`node.leaf_only`).
726    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
727    pub leaf_only: bool,
728
729    /// RX loop maintenance tick period in seconds (`node.tick_interval_secs`).
730    #[serde(default = "NodeConfig::default_tick_interval_secs")]
731    pub tick_interval_secs: u64,
732
733    /// Initial RTT estimate for new links in ms (`node.base_rtt_ms`).
734    #[serde(default = "NodeConfig::default_base_rtt_ms")]
735    pub base_rtt_ms: u64,
736
737    /// Link heartbeat send interval in seconds (`node.heartbeat_interval_secs`).
738    #[serde(default = "NodeConfig::default_heartbeat_interval_secs")]
739    pub heartbeat_interval_secs: u64,
740
741    /// Link dead timeout in seconds (`node.link_dead_timeout_secs`).
742    /// Peers silent for this duration are removed.
743    #[serde(default = "NodeConfig::default_link_dead_timeout_secs")]
744    pub link_dead_timeout_secs: u64,
745
746    /// Accelerated link dead timeout in seconds, used in place of
747    /// `link_dead_timeout_secs` while a recent `transport.send` returned
748    /// a local-side errno (`NetworkUnreachable` / `HostUnreachable` /
749    /// `AddrNotAvailable`) — direct evidence our outbound path is broken
750    /// right now (interface vanished, default route flapped, etc.). No
751    /// reason to wait the full receive-silence window when the kernel
752    /// already told us we can't send. Steady-state behavior is unchanged
753    /// because the signal is cleared on the next successful send.
754    /// (`node.fast_link_dead_timeout_secs`)
755    #[serde(default = "NodeConfig::default_fast_link_dead_timeout_secs")]
756    pub fast_link_dead_timeout_secs: u64,
757
758    /// Resource limits (`node.limits.*`).
759    #[serde(default)]
760    pub limits: LimitsConfig,
761
762    /// Connected UDP fast path (`node.connected_udp.*`).
763    #[serde(default)]
764    pub connected_udp: ConnectedUdpConfig,
765
766    /// Rate limiting (`node.rate_limit.*`).
767    #[serde(default)]
768    pub rate_limit: RateLimitConfig,
769
770    /// Retry/backoff (`node.retry.*`).
771    #[serde(default)]
772    pub retry: RetryConfig,
773
774    /// Cache parameters (`node.cache.*`).
775    #[serde(default)]
776    pub cache: CacheConfig,
777
778    /// Discovery protocol (`node.discovery.*`).
779    #[serde(default)]
780    pub discovery: DiscoveryConfig,
781
782    /// Spanning tree (`node.tree.*`).
783    #[serde(default)]
784    pub tree: TreeConfig,
785
786    /// Routing strategy (`node.routing.*`).
787    #[serde(default)]
788    pub routing: RoutingConfig,
789
790    /// Bloom filter (`node.bloom.*`).
791    #[serde(default)]
792    pub bloom: BloomConfig,
793
794    /// Session/data plane (`node.session.*`).
795    #[serde(default)]
796    pub session: SessionConfig,
797
798    /// Internal buffers (`node.buffers.*`).
799    #[serde(default)]
800    pub buffers: BuffersConfig,
801
802    /// Control socket (`node.control.*`).
803    #[serde(default)]
804    pub control: ControlConfig,
805
806    /// Metrics Measurement Protocol — link layer (`node.mmp.*`).
807    #[serde(default)]
808    pub mmp: MmpConfig,
809
810    /// Metrics Measurement Protocol — session layer (`node.session_mmp.*`).
811    #[serde(default)]
812    pub session_mmp: SessionMmpConfig,
813
814    /// ECN congestion signaling (`node.ecn.*`).
815    #[serde(default)]
816    pub ecn: EcnConfig,
817
818    /// Rekey / session rekeying (`node.rekey.*`).
819    #[serde(default)]
820    pub rekey: RekeyConfig,
821
822    /// Enable daemon-oriented system files such as `/etc/fips/hosts` and
823    /// `/etc/fips/peers.{allow,deny}`. Embedded endpoints disable this.
824    #[serde(default = "NodeConfig::default_system_files_enabled")]
825    pub system_files_enabled: bool,
826
827    /// Enable off-task Unix encrypt/decrypt worker pools (`node.worker_pools_enabled`).
828    /// Embedded/mobile endpoints can disable this to keep crypto/send work inline
829    /// with the rx loop when platform extension sandboxes make OS-thread pools
830    /// unsuitable.
831    #[serde(default = "NodeConfig::default_worker_pools_enabled")]
832    pub worker_pools_enabled: bool,
833
834    /// Log level (`node.log_level`). Case-insensitive.
835    /// Valid values: trace, debug, info, warn, error. Default: info.
836    #[serde(default)]
837    pub log_level: Option<String>,
838}
839
840impl Default for NodeConfig {
841    fn default() -> Self {
842        Self {
843            identity: IdentityConfig::default(),
844            leaf_only: false,
845            tick_interval_secs: 1,
846            base_rtt_ms: 100,
847            heartbeat_interval_secs: 10,
848            link_dead_timeout_secs: 30,
849            fast_link_dead_timeout_secs: 5,
850            limits: LimitsConfig::default(),
851            connected_udp: ConnectedUdpConfig::default(),
852            rate_limit: RateLimitConfig::default(),
853            retry: RetryConfig::default(),
854            cache: CacheConfig::default(),
855            discovery: DiscoveryConfig::default(),
856            tree: TreeConfig::default(),
857            routing: RoutingConfig::default(),
858            bloom: BloomConfig::default(),
859            session: SessionConfig::default(),
860            buffers: BuffersConfig::default(),
861            control: ControlConfig::default(),
862            mmp: MmpConfig::default(),
863            session_mmp: SessionMmpConfig::default(),
864            ecn: EcnConfig::default(),
865            rekey: RekeyConfig::default(),
866            system_files_enabled: true,
867            worker_pools_enabled: true,
868            log_level: None,
869        }
870    }
871}
872
873impl NodeConfig {
874    /// Get the log level as a tracing Level. Default: INFO.
875    pub fn log_level(&self) -> tracing::Level {
876        match self
877            .log_level
878            .as_deref()
879            .map(|s| s.to_lowercase())
880            .as_deref()
881        {
882            Some("trace") => tracing::Level::TRACE,
883            Some("debug") => tracing::Level::DEBUG,
884            Some("warn") | Some("warning") => tracing::Level::WARN,
885            Some("error") => tracing::Level::ERROR,
886            _ => tracing::Level::INFO,
887        }
888    }
889
890    fn default_tick_interval_secs() -> u64 {
891        1
892    }
893    fn default_base_rtt_ms() -> u64 {
894        100
895    }
896    fn default_heartbeat_interval_secs() -> u64 {
897        10
898    }
899    fn default_link_dead_timeout_secs() -> u64 {
900        30
901    }
902    fn default_fast_link_dead_timeout_secs() -> u64 {
903        5
904    }
905    fn default_system_files_enabled() -> bool {
906        true
907    }
908    fn default_worker_pools_enabled() -> bool {
909        true
910    }
911}
912
913#[cfg(test)]
914mod tests;