Skip to main content

rns_net/
driver.rs

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