Skip to main content

rns_net/
driver.rs

1//! Driver loop: receives events, drives the TransportEngine, dispatches actions.
2
3use std::collections::{HashMap, HashSet};
4use std::io;
5use std::sync::atomic::{AtomicU64, Ordering};
6use std::sync::{Arc, Mutex};
7use std::time::{Duration, Instant};
8
9type MemoryRollup = (f64, f64, f64, f64, f64, f64, f64, f64);
10#[cfg(feature = "iface-backbone")]
11type BackboneCandidateOrder = (
12    std::cmp::Reverse<u8>,
13    u8,
14    u8,
15    u8,
16    std::cmp::Reverse<u32>,
17    std::cmp::Reverse<u64>,
18    [u8; 32],
19);
20
21use rns_core::packet::RawPacket;
22use rns_core::transport::announce_verify_queue::{AnnounceVerifyQueue, OverflowPolicy};
23use rns_core::transport::tables::PathEntry;
24use rns_core::transport::types::{InterfaceId, TransportAction, TransportConfig};
25use rns_core::transport::TransportEngine;
26use rns_crypto::{OsRng, Rng};
27
28#[cfg(feature = "hooks")]
29use crate::provider_bridge::ProviderBridge;
30#[cfg(feature = "hooks")]
31use rns_hooks::{create_hook_slots, EngineAccess, HookContext, HookManager, HookPoint, HookSlot};
32
33#[cfg(feature = "hooks")]
34use crate::event::BackbonePeerHookEvent;
35use crate::event::{
36    BackbonePeerPoolMemberStatus, BackbonePeerPoolStatus, BackbonePeerStateEntry, BlackholeInfo,
37    DrainStatus, Event, EventReceiver, InterfaceStatsResponse, KnownDestinationEntry,
38    LifecycleState, LocalDestinationEntry, NextHopResponse, PathTableEntry, QueryRequest,
39    QueryResponse, RateTableEntry, RuntimeConfigApplyMode, RuntimeConfigEntry, RuntimeConfigError,
40    RuntimeConfigErrorCode, RuntimeConfigSource, RuntimeConfigValue, SingleInterfaceStat,
41    TrafficDetail,
42};
43use crate::holepunch::orchestrator::{HolePunchManager, HolePunchManagerAction};
44use crate::ifac;
45#[cfg(all(feature = "iface-auto", test))]
46use crate::interface::auto::AutoRuntime;
47#[cfg(feature = "iface-auto")]
48use crate::interface::auto::AutoRuntimeConfigHandle;
49#[cfg(feature = "iface-backbone")]
50use crate::interface::backbone::{
51    start_client_with_ifac, BackboneClientConfig, BackboneClientRuntime,
52    BackboneClientRuntimeConfigHandle, BackbonePeerStateHandle, BackboneRuntimeConfigHandle,
53};
54#[cfg(all(feature = "iface-backbone", target_os = "linux", test))]
55use crate::interface::backbone::{BackboneAbuseConfig, BackboneServerRuntime};
56#[cfg(all(feature = "iface-i2p", test))]
57use crate::interface::i2p::I2pRuntime;
58#[cfg(feature = "iface-i2p")]
59use crate::interface::i2p::I2pRuntimeConfigHandle;
60#[cfg(all(feature = "iface-pipe", test))]
61use crate::interface::pipe::PipeRuntime;
62#[cfg(feature = "iface-pipe")]
63use crate::interface::pipe::PipeRuntimeConfigHandle;
64#[cfg(feature = "iface-rnode")]
65use crate::interface::rnode::{
66    validate_sub_config, RNodeRuntime, RNodeRuntimeConfigHandle, RNodeSubConfig,
67};
68#[cfg(feature = "iface-tcp")]
69use crate::interface::tcp::TcpClientRuntimeConfigHandle;
70#[cfg(all(feature = "iface-tcp", test))]
71use crate::interface::tcp_server::TcpServerRuntime;
72#[cfg(feature = "iface-tcp")]
73use crate::interface::tcp_server::TcpServerRuntimeConfigHandle;
74#[cfg(all(feature = "iface-udp", test))]
75use crate::interface::udp::UdpRuntime;
76#[cfg(feature = "iface-udp")]
77use crate::interface::udp::UdpRuntimeConfigHandle;
78use crate::interface::{InterfaceEntry, InterfaceStats};
79use crate::link_manager::{LinkManager, LinkManagerAction};
80use crate::time;
81
82const DEFAULT_KNOWN_DESTINATIONS_TTL: f64 = 48.0 * 60.0 * 60.0;
83const DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES: usize = 8192;
84const DEFAULT_RATE_LIMITER_TTL_SECS: f64 = 48.0 * 60.0 * 60.0;
85const DEFAULT_TICK_INTERVAL_MS: u64 = 1000;
86const DEFAULT_KNOWN_DESTINATIONS_CLEANUP_INTERVAL_TICKS: u32 = 3600;
87const KNOWN_DESTINATION_CLEANUP_BATCH_SIZE: usize = 64;
88const DEFAULT_ANNOUNCE_CACHE_CLEANUP_INTERVAL_TICKS: u32 = 3600;
89const DEFAULT_ANNOUNCE_CACHE_CLEANUP_BATCH_SIZE: usize = 10_000;
90const DEFAULT_DISCOVERY_CLEANUP_INTERVAL_TICKS: u32 = 3600;
91const DEFAULT_MANAGEMENT_ANNOUNCE_INTERVAL_SECS: f64 = 3600.0;
92const DEFAULT_LINK_TEARDOWN_FLUSH: Duration = Duration::from_millis(150);
93const SEND_RETRY_BACKOFF_MIN: Duration = Duration::from_millis(25);
94const SEND_RETRY_BACKOFF_MAX: Duration = Duration::from_millis(1000);
95
96/// Default announce-rate controls applied to transport-node interfaces.
97#[derive(Debug, Clone, Copy, PartialEq)]
98pub struct AnnounceRateDefaults {
99    pub target: Option<f64>,
100    pub penalty: f64,
101    pub grace: u32,
102}
103
104impl Default for AnnounceRateDefaults {
105    fn default() -> Self {
106        Self {
107            target: Some(3600.0),
108            penalty: 0.0,
109            grace: 5,
110        }
111    }
112}
113
114mod dispatch;
115mod events;
116mod interface_management;
117mod lifecycle;
118mod queries;
119mod runtime_config;
120
121#[cfg(test)]
122mod tests;
123
124fn inject_transport_header(raw: &[u8], next_hop: &[u8; 16]) -> Vec<u8> {
125    if raw.len() < 18 {
126        return raw.to_vec();
127    }
128
129    let new_flags = (rns_core::constants::HEADER_2 << 6)
130        | (rns_core::constants::TRANSPORT_TRANSPORT << 4)
131        | (raw[0] & 0x0F);
132
133    let mut new_raw = Vec::with_capacity(raw.len() + 16);
134    new_raw.push(new_flags);
135    new_raw.push(raw[1]);
136    new_raw.extend_from_slice(next_hop);
137    new_raw.extend_from_slice(&raw[2..]);
138    new_raw
139}
140
141fn recover_mutex_guard<'a, T>(mutex: &'a Mutex<T>, label: &str) -> std::sync::MutexGuard<'a, T> {
142    match mutex.lock() {
143        Ok(guard) => guard,
144        Err(poisoned) => {
145            log::error!("recovering from poisoned mutex: {}", label);
146            poisoned.into_inner()
147        }
148    }
149}
150
151#[derive(Debug, Clone, Copy)]
152pub(crate) struct RuntimeConfigDefaults {
153    pub(crate) tick_interval_ms: u64,
154    pub(crate) known_destinations_ttl: f64,
155    pub(crate) rate_limiter_ttl_secs: f64,
156    pub(crate) known_destinations_cleanup_interval_ticks: u32,
157    pub(crate) announce_cache_cleanup_interval_ticks: u32,
158    pub(crate) announce_cache_cleanup_batch_size: usize,
159    pub(crate) discovery_cleanup_interval_ticks: u32,
160    pub(crate) management_announce_interval_secs: f64,
161    pub(crate) direct_connect_policy: crate::event::HolePunchPolicy,
162    #[cfg(feature = "hooks")]
163    pub(crate) provider_queue_max_events: usize,
164    #[cfg(feature = "hooks")]
165    pub(crate) provider_queue_max_bytes: usize,
166}
167
168#[cfg(feature = "iface-backbone")]
169#[derive(Debug, Clone)]
170pub(crate) struct BackboneDiscoveryRuntime {
171    pub(crate) discoverable: bool,
172    pub(crate) config: crate::discovery::DiscoveryConfig,
173    pub(crate) transport_enabled: bool,
174    pub(crate) ifac_netname: Option<String>,
175    pub(crate) ifac_netkey: Option<String>,
176}
177
178#[cfg(feature = "iface-backbone")]
179#[derive(Debug, Clone)]
180pub(crate) struct BackboneDiscoveryRuntimeHandle {
181    pub(crate) interface_name: String,
182    pub(crate) current: BackboneDiscoveryRuntime,
183    pub(crate) startup: BackboneDiscoveryRuntime,
184}
185
186#[cfg(feature = "iface-tcp")]
187#[derive(Debug, Clone)]
188pub(crate) struct TcpServerDiscoveryRuntime {
189    pub(crate) discoverable: bool,
190    pub(crate) config: crate::discovery::DiscoveryConfig,
191    pub(crate) transport_enabled: bool,
192    pub(crate) ifac_netname: Option<String>,
193    pub(crate) ifac_netkey: Option<String>,
194}
195
196#[cfg(feature = "iface-tcp")]
197#[derive(Debug, Clone)]
198pub(crate) struct TcpServerDiscoveryRuntimeHandle {
199    pub(crate) interface_name: String,
200    pub(crate) current: TcpServerDiscoveryRuntime,
201    pub(crate) startup: TcpServerDiscoveryRuntime,
202}
203
204#[derive(Debug, Clone, PartialEq, Eq)]
205pub(crate) struct IfacRuntimeConfig {
206    pub(crate) netname: Option<String>,
207    pub(crate) netkey: Option<String>,
208    pub(crate) size: usize,
209}
210
211#[cfg(feature = "iface-backbone")]
212impl BackboneDiscoveryRuntimeHandle {
213    pub(crate) fn from_parts(
214        interface_name: String,
215        startup_config: crate::discovery::DiscoveryConfig,
216        transport_enabled: bool,
217        ifac_netname: Option<String>,
218        ifac_netkey: Option<String>,
219        discoverable: bool,
220    ) -> Self {
221        let startup = BackboneDiscoveryRuntime {
222            discoverable,
223            config: startup_config,
224            transport_enabled,
225            ifac_netname,
226            ifac_netkey,
227        };
228        Self {
229            interface_name,
230            current: startup.clone(),
231            startup,
232        }
233    }
234}
235
236#[cfg(feature = "iface-tcp")]
237impl TcpServerDiscoveryRuntimeHandle {
238    pub(crate) fn from_parts(
239        interface_name: String,
240        startup_config: crate::discovery::DiscoveryConfig,
241        transport_enabled: bool,
242        ifac_netname: Option<String>,
243        ifac_netkey: Option<String>,
244        discoverable: bool,
245    ) -> Self {
246        let startup = TcpServerDiscoveryRuntime {
247            discoverable,
248            config: startup_config,
249            transport_enabled,
250            ifac_netname,
251            ifac_netkey,
252        };
253        Self {
254            interface_name,
255            current: startup.clone(),
256            startup,
257        }
258    }
259}
260
261impl IfacRuntimeConfig {
262    pub(crate) fn from_parts(netname: Option<String>, netkey: Option<String>, size: usize) -> Self {
263        Self {
264            netname,
265            netkey,
266            size,
267        }
268    }
269}
270
271#[cfg(feature = "iface-backbone")]
272#[derive(Debug, Clone)]
273pub struct BackbonePeerPoolSettings {
274    pub max_connected: usize,
275    pub failure_threshold: usize,
276    pub failure_window: Duration,
277    pub cooldown: Duration,
278}
279
280#[cfg(feature = "iface-backbone")]
281pub(crate) const BACKBONE_PEER_POOL_CONFIGURED_DEFAULT_PRIORITY: u8 = 60;
282#[cfg(feature = "iface-backbone")]
283pub(crate) const BACKBONE_PEER_POOL_DISCOVERED_PRIORITY: u8 = 40;
284
285#[cfg(feature = "iface-backbone")]
286pub(crate) struct BackbonePeerPoolCandidateConfig {
287    pub(crate) client: BackboneClientConfig,
288    pub(crate) mode: u8,
289    pub(crate) gravity: i64,
290    pub(crate) recursive_prs: bool,
291    pub(crate) announces_from_internal: bool,
292    pub(crate) announces_to_internal: Option<bool>,
293    pub(crate) ingress_control: rns_core::transport::types::IngressControlConfig,
294    pub(crate) ifac_runtime: IfacRuntimeConfig,
295    pub(crate) ifac_enabled: bool,
296    pub(crate) interface_type_name: String,
297    pub(crate) source: BackbonePeerPoolCandidateSource,
298    pub(crate) priority: u8,
299    pub(crate) discovery: Option<BackbonePeerPoolDiscoveryCandidate>,
300}
301
302#[cfg(feature = "iface-backbone")]
303#[derive(Debug, Clone, Copy, PartialEq, Eq)]
304pub(crate) enum BackbonePeerPoolCandidateSource {
305    Configured,
306    Discovered,
307}
308
309#[cfg(feature = "iface-backbone")]
310impl BackbonePeerPoolCandidateSource {
311    fn as_str(self) -> &'static str {
312        match self {
313            Self::Configured => "configured",
314            Self::Discovered => "discovered",
315        }
316    }
317}
318
319#[cfg(feature = "iface-backbone")]
320#[derive(Debug, Clone)]
321pub(crate) struct BackbonePeerPoolDiscoveryCandidate {
322    pub(crate) discovery_hash: [u8; 32],
323    pub(crate) status: crate::discovery::DiscoveredStatus,
324    pub(crate) hops: u8,
325    pub(crate) stamp_value: u32,
326    pub(crate) last_heard: f64,
327}
328
329#[cfg(feature = "iface-backbone")]
330struct BackbonePeerPool {
331    settings: BackbonePeerPoolSettings,
332    candidates: Vec<BackbonePeerPoolCandidate>,
333}
334
335#[cfg(feature = "iface-backbone")]
336struct BackbonePeerPoolCandidate {
337    config: BackbonePeerPoolCandidateConfig,
338    active_id: Option<InterfaceId>,
339    failures: Vec<f64>,
340    retry_after: Option<f64>,
341    cooldown_until: Option<f64>,
342    last_error: Option<String>,
343}
344
345/// Thin wrapper providing `EngineAccess` for a `TransportEngine` + Driver interfaces.
346#[cfg(feature = "hooks")]
347struct EngineRef<'a> {
348    engine: &'a TransportEngine,
349    interfaces: &'a HashMap<InterfaceId, InterfaceEntry>,
350    link_manager: &'a LinkManager,
351    now: f64,
352}
353
354#[cfg(feature = "hooks")]
355impl<'a> EngineAccess for EngineRef<'a> {
356    fn has_path(&self, dest: &[u8; 16]) -> bool {
357        self.engine.has_path(dest)
358    }
359    fn hops_to(&self, dest: &[u8; 16]) -> Option<u8> {
360        self.engine.hops_to(dest)
361    }
362    fn next_hop(&self, dest: &[u8; 16]) -> Option<[u8; 16]> {
363        self.engine.next_hop(dest)
364    }
365    fn is_blackholed(&self, identity: &[u8; 16]) -> bool {
366        self.engine.is_blackholed(identity, self.now)
367    }
368    fn interface_name(&self, id: u64) -> Option<String> {
369        self.interfaces
370            .get(&InterfaceId(id))
371            .map(|e| e.info.name.clone())
372    }
373    fn interface_mode(&self, id: u64) -> Option<u8> {
374        self.interfaces.get(&InterfaceId(id)).map(|e| e.info.mode)
375    }
376    fn identity_hash(&self) -> Option<[u8; 16]> {
377        self.engine.identity_hash().copied()
378    }
379    fn announce_rate(&self, id: u64) -> Option<i32> {
380        self.interfaces
381            .get(&InterfaceId(id))
382            .map(|e| (e.stats.outgoing_announce_freq() * 1000.0) as i32)
383    }
384    fn link_state(&self, link_hash: &[u8; 16]) -> Option<u8> {
385        use rns_core::link::types::LinkState;
386        self.link_manager.link_state(link_hash).map(|s| match s {
387            LinkState::Pending => 0,
388            LinkState::Handshake => 1,
389            LinkState::Active => 2,
390            LinkState::Stale => 3,
391            LinkState::Closed => 4,
392        })
393    }
394}
395
396/// Extract the 16-byte destination hash from a raw packet header.
397///
398/// HEADER_1 (raw[0] & 0x40 == 0): dest at bytes 2..18
399/// HEADER_2 (raw[0] & 0x40 != 0): dest at bytes 18..34 (after transport ID)
400#[cfg(any(test, feature = "hooks"))]
401fn extract_dest_hash(raw: &[u8]) -> [u8; 16] {
402    let mut dest = [0u8; 16];
403    if raw.is_empty() {
404        return dest;
405    }
406    let is_header2 = raw[0] & 0x40 != 0;
407    let start = if is_header2 { 18 } else { 2 };
408    let end = start + 16;
409    if raw.len() >= end {
410        dest.copy_from_slice(&raw[start..end]);
411    }
412    dest
413}
414
415/// Execute a hook chain on disjoint Driver fields (avoids &mut self borrow conflict).
416#[cfg(feature = "hooks")]
417fn run_hook_inner(
418    programs: &mut [rns_hooks::LoadedProgram],
419    hook_manager: &Option<HookManager>,
420    engine_access: &dyn EngineAccess,
421    ctx: &HookContext,
422    now: f64,
423    provider_events_enabled: bool,
424) -> Option<rns_hooks::ExecuteResult> {
425    if programs.is_empty() {
426        return None;
427    }
428    let mgr = hook_manager.as_ref()?;
429    mgr.run_chain_with_provider_events(programs, ctx, engine_access, now, provider_events_enabled)
430}
431
432#[cfg(feature = "hooks")]
433fn backbone_peer_hook_context(event: &BackbonePeerHookEvent) -> HookContext<'_> {
434    HookContext::BackbonePeer {
435        server_interface_id: event.server_interface_id.0,
436        peer_interface_id: event.peer_interface_id.map(|id| id.0),
437        peer_ip: event.peer_ip,
438        peer_port: event.peer_port,
439        connected_for: event.connected_for,
440        had_received_data: event.had_received_data,
441        penalty_level: event.penalty_level,
442        blacklist_for: event.blacklist_for,
443    }
444}
445
446/// Convert a Vec of ActionWire into TransportActions for dispatch.
447#[cfg(feature = "hooks")]
448fn convert_injected_actions(actions: Vec<rns_hooks::ActionWire>) -> Vec<TransportAction> {
449    actions
450        .into_iter()
451        .map(|a| {
452            use rns_hooks::ActionWire;
453            match a {
454                ActionWire::SendOnInterface { interface, raw } => {
455                    TransportAction::SendOnInterface {
456                        interface: InterfaceId(interface),
457                        raw: raw.into(),
458                    }
459                }
460                ActionWire::BroadcastOnAllInterfaces {
461                    raw,
462                    exclude,
463                    has_exclude,
464                } => TransportAction::BroadcastOnAllInterfaces {
465                    raw: raw.into(),
466                    exclude: if has_exclude != 0 {
467                        Some(InterfaceId(exclude))
468                    } else {
469                        None
470                    },
471                },
472                ActionWire::DeliverLocal {
473                    destination_hash,
474                    raw,
475                    packet_hash,
476                    receiving_interface,
477                } => TransportAction::DeliverLocal {
478                    destination_hash,
479                    raw: raw.into(),
480                    packet_hash,
481                    receiving_interface: InterfaceId(receiving_interface),
482                },
483                ActionWire::PathUpdated {
484                    destination_hash,
485                    hops,
486                    next_hop,
487                    interface,
488                } => TransportAction::PathUpdated {
489                    destination_hash,
490                    hops,
491                    next_hop,
492                    interface: InterfaceId(interface),
493                },
494                ActionWire::CacheAnnounce { packet_hash, raw } => TransportAction::CacheAnnounce {
495                    packet_hash,
496                    raw: raw.into(),
497                },
498                ActionWire::TunnelEstablished {
499                    tunnel_id,
500                    interface,
501                } => TransportAction::TunnelEstablished {
502                    tunnel_id,
503                    interface: InterfaceId(interface),
504                },
505                ActionWire::TunnelSynthesize {
506                    interface,
507                    data,
508                    dest_hash,
509                } => TransportAction::TunnelSynthesize {
510                    interface: InterfaceId(interface),
511                    data,
512                    dest_hash,
513                },
514                ActionWire::ForwardToLocalClients {
515                    raw,
516                    exclude,
517                    has_exclude,
518                } => TransportAction::ForwardToLocalClients {
519                    raw: raw.into(),
520                    exclude: if has_exclude != 0 {
521                        Some(InterfaceId(exclude))
522                    } else {
523                        None
524                    },
525                },
526                ActionWire::ForwardPlainBroadcast {
527                    raw,
528                    to_local,
529                    exclude,
530                    has_exclude,
531                } => TransportAction::ForwardPlainBroadcast {
532                    raw: raw.into(),
533                    to_local: to_local != 0,
534                    exclude: if has_exclude != 0 {
535                        Some(InterfaceId(exclude))
536                    } else {
537                        None
538                    },
539                },
540                ActionWire::AnnounceReceived {
541                    destination_hash,
542                    identity_hash,
543                    public_key,
544                    name_hash,
545                    random_hash,
546                    app_data,
547                    hops,
548                    receiving_interface,
549                } => TransportAction::AnnounceReceived {
550                    destination_hash,
551                    identity_hash,
552                    public_key,
553                    name_hash,
554                    random_hash,
555                    ratchet: None,
556                    app_data,
557                    hops,
558                    receiving_interface: InterfaceId(receiving_interface),
559                    rx: rns_core::transport::RxMetadata::default(),
560                },
561            }
562        })
563        .collect()
564}
565
566/// Infer the interface type string from a dynamic interface's name.
567/// Dynamic interfaces (TCP server clients, backbone peers, auto peers, local server clients)
568/// include their type in the name prefix set at construction.
569fn infer_interface_type(name: &str) -> String {
570    if name.starts_with("TCPServerInterface") {
571        "TCPServerClientInterface".to_string()
572    } else if name.starts_with("BackboneInterface") {
573        "BackboneInterface".to_string()
574    } else if name.starts_with("LocalInterface") {
575        "LocalServerClientInterface".to_string()
576    } else if name.starts_with("WeaveInterfacePeer[") {
577        "WeaveInterfacePeer".to_string()
578    } else {
579        // AutoInterface peers use "{group_name}:{peer_addr}" format where
580        // group_name is the config section name (typically "AutoInterface" or similar).
581        "AutoInterface".to_string()
582    }
583}
584
585pub use crate::common::callbacks::Callbacks;
586
587#[derive(Clone)]
588struct SharedAnnounceRecord {
589    name_hash: [u8; 10],
590    identity_prv_key: [u8; 64],
591    app_data: Option<Vec<u8>>,
592}
593
594#[derive(Debug, Clone)]
595pub(crate) struct KnownDestinationState {
596    announced: crate::destination::AnnouncedIdentity,
597    was_used: bool,
598    last_used_at: Option<f64>,
599    retained: bool,
600}
601
602struct KnownDestinationCleanup {
603    candidates: Vec<[u8; 16]>,
604    cursor: usize,
605    started_at: Instant,
606    removed: usize,
607}
608
609#[derive(Debug, Clone, Copy)]
610pub(crate) struct TrafficSample {
611    sampled_at: f64,
612    rxb: u64,
613    txb: u64,
614    rx_packets: u64,
615    tx_packets: u64,
616    arxb: u64,
617    atxb: u64,
618    prxb: u64,
619    ptxb: u64,
620}
621
622impl TrafficSample {
623    fn from_stats(sampled_at: f64, stats: &crate::interface::InterfaceStats) -> Self {
624        Self {
625            sampled_at,
626            rxb: stats.rxb,
627            txb: stats.txb,
628            rx_packets: stats.rx_packets,
629            tx_packets: stats.tx_packets,
630            arxb: stats.arxb,
631            atxb: stats.atxb,
632            prxb: stats.prxb,
633            ptxb: stats.ptxb,
634        }
635    }
636}
637
638/// The driver loop. Owns the engine and all interface entries.
639pub(crate) struct ManagedInterface {
640    pub(crate) parent_id: InterfaceId,
641    pub(crate) type_name: String,
642    pub(crate) control: Option<crate::interface::ListenerControl>,
643    pub(crate) static_ids: Vec<InterfaceId>,
644}
645
646pub struct Driver {
647    pub(crate) tracked_link_send: Option<([u8; 32], crate::link_send::Completion)>,
648    pub(crate) pending_link_frames: std::collections::VecDeque<PendingLinkFrame>,
649    pub(crate) engine: TransportEngine,
650    pub(crate) interfaces: HashMap<InterfaceId, InterfaceEntry>,
651    /// Parent listener for dynamically spawned interfaces.
652    pub(crate) dynamic_interface_parents: HashMap<InterfaceId, InterfaceId>,
653    /// Previous cumulative counters used to calculate current one-second rates.
654    pub(crate) traffic_samples: HashMap<InterfaceId, TrafficSample>,
655    pub(crate) rng: OsRng,
656    pub(crate) rx: EventReceiver,
657    pub(crate) callbacks: Box<dyn Callbacks>,
658    pub(crate) started: f64,
659    pub(crate) lifecycle_state: LifecycleState,
660    pub(crate) drain_started_at: Option<Instant>,
661    pub(crate) drain_deadline: Option<Instant>,
662    pub(crate) listener_controls: Vec<crate::interface::ListenerControl>,
663    pub(crate) interface_management: Option<crate::node::InterfaceManagementConfig>,
664    pub(crate) managed_interfaces: HashMap<String, ManagedInterface>,
665    pub(crate) retired_interface_parents: HashSet<InterfaceId>,
666    pub(crate) announce_cache: Option<crate::announce_cache::AnnounceCache>,
667    /// Destination hash for rnstransport.tunnel.synthesize (PLAIN).
668    pub(crate) tunnel_synth_dest: [u8; 16],
669    /// Transport identity (optional, needed for tunnel synthesis).
670    pub(crate) transport_identity: Option<rns_crypto::identity::Identity>,
671    /// Link manager: handles link lifecycle, request/response.
672    pub(crate) link_manager: LinkManager,
673    /// Management configuration for ACL checks.
674    pub(crate) management_config: crate::management::ManagementConfig,
675    /// Last time management announces were emitted.
676    pub(crate) last_management_announce: f64,
677    /// Whether initial management announce has been sent (delayed 5s after start).
678    pub(crate) initial_announce_sent: bool,
679    /// Cache of known announced identities and lifecycle state, keyed by destination hash.
680    pub(crate) known_destinations: HashMap<[u8; 16], KnownDestinationState>,
681    /// Store for received remote ratchets, if persistence/use is enabled.
682    pub(crate) ratchet_store: Option<Arc<dyn crate::storage::RatchetStore>>,
683    /// TTL for received ratchets, in seconds.
684    pub(crate) ratchet_expiry_secs: f64,
685    /// TTL for known destinations without an active path, in seconds.
686    pub(crate) known_destinations_ttl: f64,
687    /// Maximum number of retained known destinations.
688    pub(crate) known_destinations_max_entries: usize,
689    /// TTL for announce rate-limiter entries without an active path, in seconds.
690    pub(crate) rate_limiter_ttl_secs: f64,
691    /// Destination hash for rnstransport.path.request (PLAIN).
692    pub(crate) path_request_dest: [u8; 16],
693    /// Proof strategies per destination hash.
694    /// Maps dest_hash → (strategy, optional signing identity for generating proofs).
695    pub(crate) proof_strategies: HashMap<
696        [u8; 16],
697        (
698            rns_core::types::ProofStrategy,
699            Option<rns_crypto::identity::Identity>,
700        ),
701    >,
702    /// Tracked sent packets for proof matching: packet_hash → (dest_hash, sent_time).
703    pub(crate) sent_packets: HashMap<[u8; 32], ([u8; 16], f64)>,
704    /// Completed proofs for probe polling: packet_hash → (rtt_seconds, received_time).
705    pub(crate) completed_proofs: HashMap<[u8; 32], (f64, f64)>,
706    /// Locally registered destinations: hash → dest_type.
707    pub(crate) local_destinations: HashMap<[u8; 16], u8>,
708    /// Latest explicit SINGLE announces to replay after shared-client reconnect.
709    shared_announces: HashMap<[u8; 16], SharedAnnounceRecord>,
710    /// Shared local interfaces that went down and should replay announces on reconnect.
711    shared_reconnect_pending: HashMap<InterfaceId, bool>,
712    /// Hole-punch manager for direct P2P connections.
713    pub(crate) holepunch_manager: HolePunchManager,
714    /// Event sender for worker threads to send results back to the driver loop.
715    pub(crate) event_tx: crate::event::EventSender,
716    /// Maximum queued outbound frames per interface writer worker.
717    pub(crate) interface_writer_queue_capacity: usize,
718    /// Linux mark inherited by dynamically created IP underlay sockets.
719    pub(crate) underlay_mark: Option<u32>,
720    /// Default announce-rate controls for interfaces on transport nodes.
721    pub(crate) announce_rate_defaults: AnnounceRateDefaults,
722    /// Default ingress/egress controls for interfaces on transport nodes.
723    pub(crate) ingress_control_defaults: rns_core::transport::types::IngressControlConfig,
724    /// Shared timer interval used by the node timer thread.
725    pub(crate) tick_interval_ms: Arc<AtomicU64>,
726    /// Runtime-config handles for backbone server interfaces, keyed by config name.
727    #[cfg(feature = "iface-backbone")]
728    pub(crate) backbone_runtime: HashMap<String, BackboneRuntimeConfigHandle>,
729    /// Live peer-state handles for backbone server interfaces, keyed by config name.
730    #[cfg(feature = "iface-backbone")]
731    pub(crate) backbone_peer_state: HashMap<String, BackbonePeerStateHandle>,
732    /// Runtime-config handles for backbone client interfaces, keyed by config name.
733    #[cfg(feature = "iface-backbone")]
734    pub(crate) backbone_client_runtime: HashMap<String, BackboneClientRuntimeConfigHandle>,
735    /// Runtime-config state for backbone discovery metadata, keyed by config name.
736    #[cfg(feature = "iface-backbone")]
737    pub(crate) backbone_discovery_runtime: HashMap<String, BackboneDiscoveryRuntimeHandle>,
738    /// Ordered outbound Backbone peer pool, if enabled.
739    #[cfg(feature = "iface-backbone")]
740    backbone_peer_pool: Option<BackbonePeerPool>,
741    /// Shared allocator for listener-created and discovery-created dynamic interfaces.
742    pub(crate) next_dynamic_interface_id: Arc<AtomicU64>,
743    /// Runtime-config handles for TCP server interfaces, keyed by config name.
744    #[cfg(feature = "iface-tcp")]
745    pub(crate) tcp_server_runtime: HashMap<String, TcpServerRuntimeConfigHandle>,
746    /// Runtime-config handles for TCP client interfaces, keyed by config name.
747    #[cfg(feature = "iface-tcp")]
748    pub(crate) tcp_client_runtime: HashMap<String, TcpClientRuntimeConfigHandle>,
749    /// Runtime-config state for TCP server discovery metadata, keyed by config name.
750    #[cfg(feature = "iface-tcp")]
751    pub(crate) tcp_server_discovery_runtime: HashMap<String, TcpServerDiscoveryRuntimeHandle>,
752    /// Runtime-config handles for UDP interfaces, keyed by config name.
753    #[cfg(feature = "iface-udp")]
754    pub(crate) udp_runtime: HashMap<String, UdpRuntimeConfigHandle>,
755    /// Runtime-config handles for Auto interfaces, keyed by config name.
756    #[cfg(feature = "iface-auto")]
757    pub(crate) auto_runtime: HashMap<String, AutoRuntimeConfigHandle>,
758    /// Runtime-config handles for I2P interfaces, keyed by config name.
759    #[cfg(feature = "iface-i2p")]
760    pub(crate) i2p_runtime: HashMap<String, I2pRuntimeConfigHandle>,
761    /// Runtime-config handles for Pipe interfaces, keyed by config name.
762    #[cfg(feature = "iface-pipe")]
763    pub(crate) pipe_runtime: HashMap<String, PipeRuntimeConfigHandle>,
764    /// Runtime-config handles for RNode interfaces, keyed by config name.
765    #[cfg(feature = "iface-rnode")]
766    pub(crate) rnode_runtime: HashMap<String, RNodeRuntimeConfigHandle>,
767    /// Startup/default interface metadata for generic cross-cutting runtime config.
768    pub(crate) interface_runtime_defaults:
769        HashMap<String, rns_core::transport::types::InterfaceInfo>,
770    /// Current IFAC runtime config for static interfaces that support IFAC mutation.
771    pub(crate) interface_ifac_runtime: HashMap<String, IfacRuntimeConfig>,
772    /// Startup/default IFAC runtime config for static interfaces.
773    pub(crate) interface_ifac_runtime_defaults: HashMap<String, IfacRuntimeConfig>,
774    /// Storage for discovered interfaces.
775    pub(crate) discovered_interfaces: crate::discovery::DiscoveredInterfaceStorage,
776    /// Required stamp value for accepting discovered interfaces.
777    pub(crate) discovery_required_value: u8,
778    /// Bounded cache of discovery payloads with previously rejected stamps.
779    pub(crate) discovery_stamp_cache: crate::discovery::DiscoveryStampCache,
780    /// Name hash for interface discovery announces ("rnstransport.discovery.interface").
781    pub(crate) discovery_name_hash: [u8; 10],
782    /// Destination hash for the probe responder (if respond_to_probes is enabled).
783    pub(crate) probe_responder_hash: Option<[u8; 16]>,
784    /// Whether interface discovery is enabled.
785    pub(crate) discover_interfaces: bool,
786    /// Explicit mode for interfaces created from discovery announces.
787    pub(crate) autoconnect_interface_mode: Option<u8>,
788    /// Gravity for interfaces created from discovery announces.
789    pub(crate) autoconnect_interface_gravity: i64,
790    /// Internal announce propagation override for discovered interfaces.
791    pub(crate) autoconnect_announces_to_internal: Option<bool>,
792    /// Allow auto-connecting discovered interfaces from unverified implementations.
793    pub(crate) autoconnect_unverified_implementations: bool,
794    /// Announcer for discoverable interfaces (None if nothing to announce).
795    pub(crate) interface_announcer: Option<crate::discovery::InterfaceAnnouncer>,
796    /// Shared async announce verification queue.
797    pub(crate) announce_verify_queue: Arc<Mutex<AnnounceVerifyQueue>>,
798    /// Whether inbound announces should be verified off the driver thread.
799    pub(crate) async_announce_verification: bool,
800    /// Tick counter for periodic discovery cleanup (every ~3600 ticks = ~1 hour).
801    pub(crate) discovery_cleanup_counter: u32,
802    /// Runtime-configurable discovery cleanup interval.
803    pub(crate) discovery_cleanup_interval_ticks: u32,
804    /// Tick counter for periodic MEMSTATS logging (every 300 ticks = ~5 min).
805    pub(crate) memory_stats_counter: u32,
806    /// Tick counter for periodic memory/cache cleanup (every ~3600 ticks = ~1 hour).
807    pub(crate) cache_cleanup_counter: u32,
808    /// Tick counter for incremental announce-cache cleanup scheduling.
809    pub(crate) announce_cache_cleanup_counter: u32,
810    /// Runtime-configurable cleanup interval for known destinations.
811    pub(crate) known_destinations_cleanup_interval_ticks: u32,
812    /// Incremental known-destination cleanup pass, if one is active.
813    known_destination_cleanup: Option<KnownDestinationCleanup>,
814    /// Low-priority ratchet filesystem cleanup associated with the last pass.
815    ratchet_cleanup_handle: Option<std::thread::JoinHandle<()>>,
816    /// Count of known-destination cap evictions since start.
817    pub(crate) known_destinations_cap_evict_count: usize,
818    /// Runtime-configurable interval for starting announce cache cleanup.
819    pub(crate) announce_cache_cleanup_interval_ticks: u32,
820    /// When set, announce cache cleanup is in progress (contains active packet hashes).
821    pub(crate) cache_cleanup_active_hashes: Option<Vec<[u8; 32]>>,
822    /// Directory iterator for incremental announce cache cleanup.
823    pub(crate) cache_cleanup_entries: Option<std::fs::ReadDir>,
824    /// Running total of files removed during current cache cleanup cycle.
825    pub(crate) cache_cleanup_removed: usize,
826    /// Runtime-configurable announce cache cleanup batch size.
827    pub(crate) announce_cache_cleanup_batch_size: usize,
828    /// Runtime-configurable management announce interval.
829    pub(crate) management_announce_interval_secs: f64,
830    /// Startup/default runtime-config values.
831    pub(crate) runtime_config_defaults: RuntimeConfigDefaults,
832    /// Hook slots for the programmable hook system (one per HookPoint).
833    #[cfg(feature = "hooks")]
834    pub(crate) hook_slots: [HookSlot; HookPoint::COUNT],
835    /// Hook manager. None if initialization failed.
836    #[cfg(feature = "hooks")]
837    pub(crate) hook_manager: Option<HookManager>,
838    #[cfg(feature = "hooks")]
839    pub(crate) provider_bridge: Option<ProviderBridge>,
840}
841
842impl Driver {
843    /// Create a new driver.
844    pub fn new(
845        config: TransportConfig,
846        rx: EventReceiver,
847        tx: crate::event::EventSender,
848        callbacks: Box<dyn Callbacks>,
849    ) -> Self {
850        let announce_queue_max_entries = config.announce_queue_max_entries;
851        let tunnel_synth_dest = rns_core::destination::destination_hash(
852            "rnstransport",
853            &["tunnel", "synthesize"],
854            None,
855        );
856        let path_request_dest =
857            rns_core::destination::destination_hash("rnstransport", &["path", "request"], None);
858        let discovery_name_hash = crate::discovery::discovery_name_hash();
859        let mut engine = TransportEngine::new(config);
860        engine.register_destination(tunnel_synth_dest, rns_core::constants::DESTINATION_PLAIN);
861        // Register path request destination so inbound path requests are delivered locally
862        engine.register_destination(path_request_dest, rns_core::constants::DESTINATION_PLAIN);
863        // Note: discovery destination is NOT registered as local — it's a SINGLE destination
864        // whose hash depends on the sender's identity. We match it by name_hash instead.
865        let mut local_destinations = HashMap::new();
866        local_destinations.insert(tunnel_synth_dest, rns_core::constants::DESTINATION_PLAIN);
867        local_destinations.insert(path_request_dest, rns_core::constants::DESTINATION_PLAIN);
868        let runtime_config_defaults = RuntimeConfigDefaults {
869            tick_interval_ms: DEFAULT_TICK_INTERVAL_MS,
870            known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
871            rate_limiter_ttl_secs: DEFAULT_RATE_LIMITER_TTL_SECS,
872            known_destinations_cleanup_interval_ticks:
873                DEFAULT_KNOWN_DESTINATIONS_CLEANUP_INTERVAL_TICKS,
874            announce_cache_cleanup_interval_ticks: DEFAULT_ANNOUNCE_CACHE_CLEANUP_INTERVAL_TICKS,
875            announce_cache_cleanup_batch_size: DEFAULT_ANNOUNCE_CACHE_CLEANUP_BATCH_SIZE,
876            discovery_cleanup_interval_ticks: DEFAULT_DISCOVERY_CLEANUP_INTERVAL_TICKS,
877            management_announce_interval_secs: DEFAULT_MANAGEMENT_ANNOUNCE_INTERVAL_SECS,
878            direct_connect_policy: crate::event::HolePunchPolicy::default(),
879            #[cfg(feature = "hooks")]
880            provider_queue_max_events: crate::provider_bridge::ProviderBridgeConfig::default()
881                .queue_max_events,
882            #[cfg(feature = "hooks")]
883            provider_queue_max_bytes: crate::provider_bridge::ProviderBridgeConfig::default()
884                .queue_max_bytes,
885        };
886        Driver {
887            tracked_link_send: None,
888            pending_link_frames: std::collections::VecDeque::new(),
889            engine,
890            interfaces: HashMap::new(),
891            dynamic_interface_parents: HashMap::new(),
892            traffic_samples: HashMap::new(),
893            rng: OsRng,
894            rx,
895            callbacks,
896            started: time::now(),
897            lifecycle_state: LifecycleState::Active,
898            drain_started_at: None,
899            drain_deadline: None,
900            listener_controls: Vec::new(),
901            interface_management: None,
902            managed_interfaces: HashMap::new(),
903            retired_interface_parents: HashSet::new(),
904            announce_cache: None,
905            tunnel_synth_dest,
906            transport_identity: None,
907            link_manager: LinkManager::new(),
908            management_config: Default::default(),
909            last_management_announce: 0.0,
910            initial_announce_sent: false,
911            known_destinations: HashMap::new(),
912            ratchet_store: None,
913            ratchet_expiry_secs: rns_core::constants::RATCHET_EXPIRY as f64,
914            known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
915            known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
916            rate_limiter_ttl_secs: DEFAULT_RATE_LIMITER_TTL_SECS,
917            path_request_dest,
918            proof_strategies: HashMap::new(),
919            sent_packets: HashMap::new(),
920            completed_proofs: HashMap::new(),
921            local_destinations,
922            shared_announces: HashMap::new(),
923            shared_reconnect_pending: HashMap::new(),
924            holepunch_manager: HolePunchManager::new(
925                vec![],
926                rns_core::holepunch::ProbeProtocol::Rnsp,
927                None,
928            ),
929            event_tx: tx,
930            interface_writer_queue_capacity: crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
931            underlay_mark: None,
932            announce_rate_defaults: AnnounceRateDefaults::default(),
933            ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(),
934            tick_interval_ms: Arc::new(AtomicU64::new(DEFAULT_TICK_INTERVAL_MS)),
935            #[cfg(feature = "iface-backbone")]
936            backbone_runtime: HashMap::new(),
937            #[cfg(feature = "iface-backbone")]
938            backbone_peer_state: HashMap::new(),
939            #[cfg(feature = "iface-backbone")]
940            backbone_client_runtime: HashMap::new(),
941            #[cfg(feature = "iface-backbone")]
942            backbone_discovery_runtime: HashMap::new(),
943            #[cfg(feature = "iface-backbone")]
944            backbone_peer_pool: None,
945            next_dynamic_interface_id: Arc::new(AtomicU64::new(10000)),
946            #[cfg(feature = "iface-tcp")]
947            tcp_server_runtime: HashMap::new(),
948            #[cfg(feature = "iface-tcp")]
949            tcp_client_runtime: HashMap::new(),
950            #[cfg(feature = "iface-tcp")]
951            tcp_server_discovery_runtime: HashMap::new(),
952            #[cfg(feature = "iface-udp")]
953            udp_runtime: HashMap::new(),
954            #[cfg(feature = "iface-auto")]
955            auto_runtime: HashMap::new(),
956            #[cfg(feature = "iface-i2p")]
957            i2p_runtime: HashMap::new(),
958            #[cfg(feature = "iface-pipe")]
959            pipe_runtime: HashMap::new(),
960            #[cfg(feature = "iface-rnode")]
961            rnode_runtime: HashMap::new(),
962            interface_runtime_defaults: HashMap::new(),
963            interface_ifac_runtime: HashMap::new(),
964            interface_ifac_runtime_defaults: HashMap::new(),
965            discovered_interfaces: crate::discovery::DiscoveredInterfaceStorage::new(
966                std::env::temp_dir().join("rns-discovered-interfaces"),
967            ),
968            discovery_required_value: crate::discovery::DEFAULT_STAMP_VALUE,
969            discovery_stamp_cache: crate::discovery::DiscoveryStampCache::default(),
970            discovery_name_hash,
971            probe_responder_hash: None,
972            discover_interfaces: false,
973            autoconnect_interface_mode: None,
974            autoconnect_interface_gravity: 0,
975            autoconnect_announces_to_internal: None,
976            autoconnect_unverified_implementations: false,
977            interface_announcer: None,
978            announce_verify_queue: Arc::new(Mutex::new(AnnounceVerifyQueue::new(
979                announce_queue_max_entries,
980            ))),
981            async_announce_verification: false,
982            discovery_cleanup_counter: 0,
983            discovery_cleanup_interval_ticks: runtime_config_defaults
984                .discovery_cleanup_interval_ticks,
985            memory_stats_counter: 0,
986            cache_cleanup_counter: 0,
987            announce_cache_cleanup_counter: 0,
988            known_destinations_cleanup_interval_ticks: runtime_config_defaults
989                .known_destinations_cleanup_interval_ticks,
990            known_destination_cleanup: None,
991            ratchet_cleanup_handle: None,
992            known_destinations_cap_evict_count: 0,
993            announce_cache_cleanup_interval_ticks: runtime_config_defaults
994                .announce_cache_cleanup_interval_ticks,
995            cache_cleanup_active_hashes: None,
996            cache_cleanup_entries: None,
997            cache_cleanup_removed: 0,
998            announce_cache_cleanup_batch_size: runtime_config_defaults
999                .announce_cache_cleanup_batch_size,
1000            management_announce_interval_secs: runtime_config_defaults
1001                .management_announce_interval_secs,
1002            runtime_config_defaults,
1003            #[cfg(feature = "hooks")]
1004            hook_slots: create_hook_slots(),
1005            #[cfg(feature = "hooks")]
1006            hook_manager: HookManager::new().ok(),
1007            #[cfg(feature = "hooks")]
1008            provider_bridge: None,
1009        }
1010    }
1011
1012    pub fn set_announce_verify_queue_config(
1013        &mut self,
1014        max_entries: usize,
1015        max_bytes: usize,
1016        max_stale_secs: f64,
1017        overflow_policy: OverflowPolicy,
1018    ) {
1019        self.announce_verify_queue = Arc::new(Mutex::new(AnnounceVerifyQueue::with_limits(
1020            max_entries,
1021            max_bytes,
1022            max_stale_secs,
1023            overflow_policy,
1024        )));
1025    }
1026
1027    pub fn set_announce_rate_defaults(&mut self, defaults: AnnounceRateDefaults) {
1028        self.announce_rate_defaults = defaults;
1029    }
1030
1031    pub fn set_ingress_control_defaults(
1032        &mut self,
1033        defaults: rns_core::transport::types::IngressControlConfig,
1034    ) {
1035        self.ingress_control_defaults = defaults;
1036    }
1037
1038    pub(crate) fn apply_announce_rate_defaults(
1039        &self,
1040        info: &mut rns_core::transport::types::InterfaceInfo,
1041    ) {
1042        if !self.engine.transport_enabled() || info.announce_rate_target.is_some() {
1043            return;
1044        }
1045
1046        let Some(target) = self.announce_rate_defaults.target else {
1047            return;
1048        };
1049
1050        info.announce_rate_target = Some(target);
1051        if info.announce_rate_grace == 0 {
1052            info.announce_rate_grace = self.announce_rate_defaults.grace;
1053        }
1054        if info.announce_rate_penalty == 0.0 {
1055            info.announce_rate_penalty = self.announce_rate_defaults.penalty;
1056        }
1057    }
1058
1059    pub(crate) fn apply_ingress_control_defaults(
1060        &self,
1061        info: &mut rns_core::transport::types::InterfaceInfo,
1062    ) {
1063        if !self.engine.transport_enabled() {
1064            return;
1065        }
1066
1067        let baseline = if info.ingress_control.enabled {
1068            rns_core::transport::types::IngressControlConfig::enabled()
1069        } else {
1070            rns_core::transport::types::IngressControlConfig::disabled()
1071        };
1072        let defaults = self.ingress_control_defaults;
1073        let ic = &mut info.ingress_control;
1074
1075        if ic.max_held_announces == baseline.max_held_announces {
1076            ic.max_held_announces = defaults.max_held_announces;
1077        }
1078        if ic.burst_hold == baseline.burst_hold {
1079            ic.burst_hold = defaults.burst_hold;
1080        }
1081        if ic.burst_freq_new == baseline.burst_freq_new {
1082            ic.burst_freq_new = defaults.burst_freq_new;
1083        }
1084        if ic.burst_freq == baseline.burst_freq {
1085            ic.burst_freq = defaults.burst_freq;
1086        }
1087        if ic.pr_burst_freq_new == baseline.pr_burst_freq_new {
1088            ic.pr_burst_freq_new = defaults.pr_burst_freq_new;
1089        }
1090        if ic.pr_burst_freq == baseline.pr_burst_freq {
1091            ic.pr_burst_freq = defaults.pr_burst_freq;
1092        }
1093        if ic.new_time == baseline.new_time {
1094            ic.new_time = defaults.new_time;
1095        }
1096        if ic.burst_penalty == baseline.burst_penalty {
1097            ic.burst_penalty = defaults.burst_penalty;
1098        }
1099        if ic.held_release_interval == baseline.held_release_interval {
1100            ic.held_release_interval = defaults.held_release_interval;
1101        }
1102        if ic.egress_enabled == baseline.egress_enabled {
1103            ic.egress_enabled = defaults.egress_enabled;
1104        }
1105        if ic.egress_pr_freq == baseline.egress_pr_freq {
1106            ic.egress_pr_freq = defaults.egress_pr_freq;
1107        }
1108    }
1109
1110    fn wrap_interface_writer(
1111        &self,
1112        interface_id: InterfaceId,
1113        interface_name: &str,
1114        writer: Box<dyn crate::interface::Writer>,
1115    ) -> (
1116        Box<dyn crate::interface::Writer>,
1117        crate::interface::AsyncWriterMetrics,
1118    ) {
1119        crate::interface::wrap_async_writer(
1120            writer,
1121            interface_id,
1122            interface_name,
1123            self.event_tx.clone(),
1124            self.interface_writer_queue_capacity,
1125        )
1126    }
1127
1128    #[cfg(feature = "hooks")]
1129    fn provider_events_enabled(&self) -> bool {
1130        self.provider_bridge.is_some()
1131    }
1132
1133    #[cfg(feature = "hooks")]
1134    fn run_backbone_peer_hook(
1135        &mut self,
1136        attach_point: &str,
1137        point: HookPoint,
1138        event: &BackbonePeerHookEvent,
1139    ) {
1140        let ctx = backbone_peer_hook_context(event);
1141        let now = time::now();
1142        let engine_ref = EngineRef {
1143            engine: &self.engine,
1144            interfaces: &self.interfaces,
1145            link_manager: &self.link_manager,
1146            now,
1147        };
1148        let provider_events_enabled = self.provider_events_enabled();
1149        if let Some(ref e) = run_hook_inner(
1150            &mut self.hook_slots[point as usize].programs,
1151            &self.hook_manager,
1152            &engine_ref,
1153            &ctx,
1154            now,
1155            provider_events_enabled,
1156        ) {
1157            self.forward_hook_side_effects(attach_point, e);
1158        }
1159    }
1160
1161    #[cfg(feature = "iface-backbone")]
1162    fn make_discoverable_interface(
1163        runtime: &BackboneDiscoveryRuntimeHandle,
1164    ) -> crate::discovery::DiscoverableInterface {
1165        crate::discovery::DiscoverableInterface {
1166            interface_name: runtime.interface_name.clone(),
1167            config: runtime.current.config.clone(),
1168            transport_enabled: runtime.current.transport_enabled,
1169            ifac_netname: runtime.current.ifac_netname.clone(),
1170            ifac_netkey: runtime.current.ifac_netkey.clone(),
1171        }
1172    }
1173
1174    #[cfg(feature = "iface-backbone")]
1175    fn sync_backbone_discovery_runtime(
1176        &mut self,
1177        interface_name: &str,
1178    ) -> Result<(), RuntimeConfigError> {
1179        let handle = self
1180            .backbone_discovery_runtime
1181            .get(interface_name)
1182            .ok_or(RuntimeConfigError {
1183                code: RuntimeConfigErrorCode::NotFound,
1184                message: format!("backbone interface '{}' not found", interface_name),
1185            })?
1186            .clone();
1187
1188        if handle.current.discoverable {
1189            let iface = Self::make_discoverable_interface(&handle);
1190            if let Some(announcer) = self.interface_announcer.as_mut() {
1191                announcer.upsert_interface(iface);
1192            } else if let Some(identity) = self.transport_identity.as_ref() {
1193                self.interface_announcer = Some(crate::discovery::InterfaceAnnouncer::new(
1194                    *identity.hash(),
1195                    vec![iface],
1196                ));
1197            }
1198        } else if let Some(announcer) = self.interface_announcer.as_mut() {
1199            announcer.remove_interface(interface_name);
1200            if announcer.is_empty() {
1201                self.interface_announcer = None;
1202            }
1203        }
1204
1205        Ok(())
1206    }
1207
1208    #[cfg(feature = "iface-tcp")]
1209    fn make_tcp_server_discoverable_interface(
1210        runtime: &TcpServerDiscoveryRuntimeHandle,
1211    ) -> crate::discovery::DiscoverableInterface {
1212        crate::discovery::DiscoverableInterface {
1213            interface_name: runtime.interface_name.clone(),
1214            config: runtime.current.config.clone(),
1215            transport_enabled: runtime.current.transport_enabled,
1216            ifac_netname: runtime.current.ifac_netname.clone(),
1217            ifac_netkey: runtime.current.ifac_netkey.clone(),
1218        }
1219    }
1220
1221    #[cfg(feature = "iface-tcp")]
1222    fn sync_tcp_server_discovery_runtime(
1223        &mut self,
1224        interface_name: &str,
1225    ) -> Result<(), RuntimeConfigError> {
1226        let handle = self
1227            .tcp_server_discovery_runtime
1228            .get(interface_name)
1229            .ok_or(RuntimeConfigError {
1230                code: RuntimeConfigErrorCode::NotFound,
1231                message: format!("tcp server interface '{}' not found", interface_name),
1232            })?
1233            .clone();
1234
1235        if handle.current.discoverable {
1236            let iface = Self::make_tcp_server_discoverable_interface(&handle);
1237            if let Some(announcer) = self.interface_announcer.as_mut() {
1238                announcer.upsert_interface(iface);
1239            } else if let Some(identity) = self.transport_identity.as_ref() {
1240                self.interface_announcer = Some(crate::discovery::InterfaceAnnouncer::new(
1241                    *identity.hash(),
1242                    vec![iface],
1243                ));
1244            }
1245        } else if let Some(announcer) = self.interface_announcer.as_mut() {
1246            announcer.remove_interface(interface_name);
1247            if announcer.is_empty() {
1248                self.interface_announcer = None;
1249            }
1250        }
1251
1252        Ok(())
1253    }
1254
1255    #[cfg(feature = "hooks")]
1256    fn update_hook_program<F>(
1257        &mut self,
1258        name: &str,
1259        attach_point: &str,
1260        mut update: F,
1261    ) -> Result<(), String>
1262    where
1263        F: FnMut(&mut rns_hooks::LoadedProgram),
1264    {
1265        let point_idx = crate::config::parse_hook_point(attach_point)
1266            .ok_or_else(|| format!("unknown hook point '{}'", attach_point))?;
1267        let program = self.hook_slots[point_idx]
1268            .programs
1269            .iter_mut()
1270            .find(|program| program.name == name)
1271            .ok_or_else(|| format!("hook '{}' not found at point '{}'", name, attach_point))?;
1272        update(program);
1273        Ok(())
1274    }
1275
1276    pub(crate) fn set_tick_interval_handle(&mut self, tick_interval_ms: Arc<AtomicU64>) {
1277        self.tick_interval_ms = tick_interval_ms;
1278    }
1279
1280    #[allow(dead_code)]
1281    pub(crate) fn set_packet_hashlist_max_entries(&mut self, max_entries: usize) {
1282        self.engine.set_packet_hashlist_max_entries(max_entries);
1283    }
1284
1285    #[cfg(feature = "hooks")]
1286    fn forward_hook_side_effects(&mut self, attach_point: &str, exec: &rns_hooks::ExecuteResult) {
1287        if !exec.injected_actions.is_empty() {
1288            self.dispatch_all(convert_injected_actions(exec.injected_actions.clone()));
1289        }
1290        if let Some(ref bridge) = self.provider_bridge {
1291            for event in &exec.provider_events {
1292                bridge.emit_event(
1293                    attach_point,
1294                    event.hook_name.clone(),
1295                    event.payload_type.clone(),
1296                    event.payload.clone(),
1297                );
1298            }
1299        }
1300    }
1301
1302    #[cfg(feature = "hooks")]
1303    fn collect_hook_side_effects(
1304        &mut self,
1305        attach_point: &str,
1306        exec: &rns_hooks::ExecuteResult,
1307        out: &mut Vec<TransportAction>,
1308    ) {
1309        if !exec.injected_actions.is_empty() {
1310            out.extend(convert_injected_actions(exec.injected_actions.clone()));
1311        }
1312        if let Some(ref bridge) = self.provider_bridge {
1313            for event in &exec.provider_events {
1314                bridge.emit_event(
1315                    attach_point,
1316                    event.hook_name.clone(),
1317                    event.payload_type.clone(),
1318                    event.payload.clone(),
1319                );
1320            }
1321        }
1322    }
1323
1324    /// Set the probe addresses, protocol, and optional device for hole punching.
1325    pub fn set_probe_config(
1326        &mut self,
1327        addrs: Vec<std::net::SocketAddr>,
1328        protocol: rns_core::holepunch::ProbeProtocol,
1329        device: Option<String>,
1330    ) {
1331        self.holepunch_manager = HolePunchManager::new(addrs, protocol, device);
1332        self.holepunch_manager.set_underlay_mark(self.underlay_mark);
1333    }
1334}
1335
1336pub(crate) struct PendingLinkFrame {
1337    interface: InterfaceId,
1338    data: Vec<u8>,
1339    is_announce: bool,
1340    completion: Option<crate::link_send::Completion>,
1341}