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::{AtomicBool, 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<rns_core::stamp::StampValue>,
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, ReceivedAnnounce, TransportAction, TransportConfig};
25use rns_core::transport::TransportEngine;
26use rns_crypto::{OsRng, Rng};
27
28#[cfg(feature = "hooks")]
29use crate::provider_bridge::ProviderBridge;
30use crate::stamper::PathRequestTarget;
31#[cfg(feature = "hooks")]
32use rns_hooks::{create_hook_slots, EngineAccess, HookContext, HookManager, HookPoint, HookSlot};
33
34#[cfg(feature = "hooks")]
35use crate::event::BackbonePeerHookEvent;
36use crate::event::{
37    BackbonePeerPoolMemberStatus, BackbonePeerPoolStatus, BackbonePeerStateEntry, BlackholeInfo,
38    DrainStatus, Event, EventReceiver, InterfaceStatsResponse, KnownDestinationEntry,
39    LifecycleState, LocalDestinationEntry, NextHopResponse, PathTableEntry, QueryRequest,
40    QueryResponse, RateTableEntry, RuntimeConfigApplyMode, RuntimeConfigEntry, RuntimeConfigError,
41    RuntimeConfigErrorCode, RuntimeConfigSource, RuntimeConfigValue, SingleInterfaceStat,
42    TrafficDetail,
43};
44use crate::holepunch::orchestrator::{HolePunchManager, HolePunchManagerAction};
45use crate::ifac;
46#[cfg(all(feature = "iface-auto", test))]
47use crate::interface::auto::AutoRuntime;
48#[cfg(feature = "iface-auto")]
49use crate::interface::auto::AutoRuntimeConfigHandle;
50#[cfg(feature = "iface-backbone")]
51use crate::interface::backbone::{
52    start_client_with_ifac, BackboneClientConfig, BackboneClientRuntime,
53    BackboneClientRuntimeConfigHandle, BackbonePeerStateHandle, BackboneRuntimeConfigHandle,
54};
55#[cfg(all(feature = "iface-backbone", target_os = "linux", test))]
56use crate::interface::backbone::{BackboneAbuseConfig, BackboneServerRuntime};
57#[cfg(all(feature = "iface-i2p", test))]
58use crate::interface::i2p::I2pRuntime;
59#[cfg(feature = "iface-i2p")]
60use crate::interface::i2p::I2pRuntimeConfigHandle;
61#[cfg(all(feature = "iface-pipe", test))]
62use crate::interface::pipe::PipeRuntime;
63#[cfg(feature = "iface-pipe")]
64use crate::interface::pipe::PipeRuntimeConfigHandle;
65#[cfg(feature = "iface-rnode")]
66use crate::interface::rnode::{
67    validate_sub_config, RNodeRuntime, RNodeRuntimeConfigHandle, RNodeSubConfig,
68};
69#[cfg(feature = "iface-tcp")]
70use crate::interface::tcp::TcpClientRuntimeConfigHandle;
71#[cfg(all(feature = "iface-tcp", test))]
72use crate::interface::tcp_server::TcpServerRuntime;
73#[cfg(feature = "iface-tcp")]
74use crate::interface::tcp_server::TcpServerRuntimeConfigHandle;
75#[cfg(all(feature = "iface-udp", test))]
76use crate::interface::udp::UdpRuntime;
77#[cfg(feature = "iface-udp")]
78use crate::interface::udp::UdpRuntimeConfigHandle;
79use crate::interface::{InterfaceEntry, InterfaceStats};
80use crate::link_manager::{LinkManager, LinkManagerAction};
81use crate::time;
82
83const DEFAULT_KNOWN_DESTINATIONS_TTL: f64 = 48.0 * 60.0 * 60.0;
84const DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES: usize = 8192;
85const DEFAULT_RATE_LIMITER_TTL_SECS: f64 = 48.0 * 60.0 * 60.0;
86const DEFAULT_TICK_INTERVAL_MS: u64 = 1000;
87const DEFAULT_KNOWN_DESTINATIONS_CLEANUP_INTERVAL_TICKS: u32 = 3600;
88const KNOWN_DESTINATION_CLEANUP_BATCH_SIZE: usize = 64;
89const DEFAULT_ANNOUNCE_CACHE_CLEANUP_INTERVAL_TICKS: u32 = 3600;
90const DEFAULT_ANNOUNCE_CACHE_CLEANUP_BATCH_SIZE: usize = 10_000;
91const DEFAULT_DISCOVERY_CLEANUP_INTERVAL_TICKS: u32 = 3600;
92const DEFAULT_MANAGEMENT_ANNOUNCE_INTERVAL_SECS: f64 = 3600.0;
93const DEFAULT_LINK_TEARDOWN_FLUSH: Duration = Duration::from_millis(150);
94const SEND_RETRY_BACKOFF_MIN: Duration = Duration::from_millis(25);
95const SEND_RETRY_BACKOFF_MAX: Duration = Duration::from_millis(1000);
96
97/// Default announce-rate controls applied to transport-node interfaces.
98#[derive(Debug, Clone, Copy, PartialEq)]
99pub struct AnnounceRateDefaults {
100    pub target: Option<f64>,
101    pub penalty: f64,
102    pub grace: u32,
103}
104
105impl Default for AnnounceRateDefaults {
106    fn default() -> Self {
107        Self {
108            target: Some(3600.0),
109            penalty: 0.0,
110            grace: 5,
111        }
112    }
113}
114
115mod announce_check;
116pub(crate) use announce_check::{check_pending_announce, AnnounceCheck};
117mod dispatch;
118mod events;
119mod hook_events;
120mod interface_management;
121mod lifecycle;
122mod link_stamps;
123mod queries;
124mod resource_preparation;
125mod rpc_service;
126pub(crate) use rpc_service::{RpcListener, RpcService};
127mod runtime_config;
128#[cfg(any(target_os = "linux", target_os = "android"))]
129mod tick_source;
130
131#[cfg(test)]
132pub(crate) mod tests;
133
134fn inject_transport_header(raw: &[u8], next_hop: &[u8; 16]) -> Vec<u8> {
135    if raw.len() < 18 {
136        return raw.to_vec();
137    }
138
139    let new_flags = (rns_core::constants::HEADER_2 << 6)
140        | (rns_core::constants::TRANSPORT_TRANSPORT << 4)
141        | (raw[0] & 0x0F);
142
143    let mut new_raw = Vec::with_capacity(raw.len() + 16);
144    new_raw.push(new_flags);
145    new_raw.push(raw[1]);
146    new_raw.extend_from_slice(next_hop);
147    new_raw.extend_from_slice(&raw[2..]);
148    new_raw
149}
150
151fn recover_mutex_guard<'a, T>(mutex: &'a Mutex<T>, label: &str) -> std::sync::MutexGuard<'a, T> {
152    match mutex.lock() {
153        Ok(guard) => guard,
154        Err(poisoned) => {
155            log::error!("recovering from poisoned mutex: {}", label);
156            poisoned.into_inner()
157        }
158    }
159}
160
161#[derive(Debug, Clone, Copy)]
162pub(crate) struct RuntimeConfigDefaults {
163    pub(crate) tick_interval_ms: u64,
164    pub(crate) known_destinations_ttl: f64,
165    pub(crate) rate_limiter_ttl_secs: f64,
166    pub(crate) known_destinations_cleanup_interval_ticks: u32,
167    pub(crate) announce_cache_cleanup_interval_ticks: u32,
168    pub(crate) announce_cache_cleanup_batch_size: usize,
169    pub(crate) discovery_cleanup_interval_ticks: u32,
170    pub(crate) management_announce_interval_secs: f64,
171    pub(crate) direct_connect_policy: crate::event::HolePunchPolicy,
172    /// `Transport.STAMP_DESTINATIONS` default.
173    pub(crate) stamp_destinations: bool,
174    /// `Transport.STAMP_PRS` default.
175    pub(crate) stamp_path_requests: bool,
176    /// `Transport.STAMPS_ENFORCED` default.
177    pub(crate) stamps_enforced: bool,
178    /// `Transport.LR_STAMPS_ENFORCED` default.
179    pub(crate) lr_stamps_enforced: bool,
180    /// `Transport.STAMP_LRS` default.
181    pub(crate) stamp_link_requests: bool,
182    /// `Transport.ALWAYS_STAMP_LRS` default.
183    pub(crate) always_stamp_link_requests: bool,
184    #[cfg(feature = "hooks")]
185    pub(crate) provider_queue_max_events: usize,
186    #[cfg(feature = "hooks")]
187    pub(crate) provider_queue_max_bytes: usize,
188}
189
190#[cfg(feature = "iface-backbone")]
191#[derive(Debug, Clone)]
192pub(crate) struct BackboneDiscoveryRuntime {
193    pub(crate) discoverable: bool,
194    pub(crate) config: crate::discovery::DiscoveryConfig,
195    pub(crate) transport_enabled: bool,
196    pub(crate) ifac_netname: Option<String>,
197    pub(crate) ifac_netkey: Option<String>,
198}
199
200#[cfg(feature = "iface-backbone")]
201#[derive(Debug, Clone)]
202pub(crate) struct BackboneDiscoveryRuntimeHandle {
203    pub(crate) interface_name: String,
204    pub(crate) current: BackboneDiscoveryRuntime,
205    pub(crate) startup: BackboneDiscoveryRuntime,
206}
207
208#[cfg(feature = "iface-tcp")]
209#[derive(Debug, Clone)]
210pub(crate) struct TcpServerDiscoveryRuntime {
211    pub(crate) discoverable: bool,
212    pub(crate) config: crate::discovery::DiscoveryConfig,
213    pub(crate) transport_enabled: bool,
214    pub(crate) ifac_netname: Option<String>,
215    pub(crate) ifac_netkey: Option<String>,
216}
217
218#[cfg(feature = "iface-tcp")]
219#[derive(Debug, Clone)]
220pub(crate) struct TcpServerDiscoveryRuntimeHandle {
221    pub(crate) interface_name: String,
222    pub(crate) current: TcpServerDiscoveryRuntime,
223    pub(crate) startup: TcpServerDiscoveryRuntime,
224}
225
226#[derive(Debug, Clone, PartialEq, Eq)]
227pub(crate) struct IfacRuntimeConfig {
228    pub(crate) netname: Option<String>,
229    pub(crate) netkey: Option<String>,
230    pub(crate) size: usize,
231}
232
233#[cfg(feature = "iface-backbone")]
234impl BackboneDiscoveryRuntimeHandle {
235    pub(crate) fn from_parts(
236        interface_name: String,
237        startup_config: crate::discovery::DiscoveryConfig,
238        transport_enabled: bool,
239        ifac_netname: Option<String>,
240        ifac_netkey: Option<String>,
241        discoverable: bool,
242    ) -> Self {
243        let startup = BackboneDiscoveryRuntime {
244            discoverable,
245            config: startup_config,
246            transport_enabled,
247            ifac_netname,
248            ifac_netkey,
249        };
250        Self {
251            interface_name,
252            current: startup.clone(),
253            startup,
254        }
255    }
256}
257
258#[cfg(feature = "iface-tcp")]
259impl TcpServerDiscoveryRuntimeHandle {
260    pub(crate) fn from_parts(
261        interface_name: String,
262        startup_config: crate::discovery::DiscoveryConfig,
263        transport_enabled: bool,
264        ifac_netname: Option<String>,
265        ifac_netkey: Option<String>,
266        discoverable: bool,
267    ) -> Self {
268        let startup = TcpServerDiscoveryRuntime {
269            discoverable,
270            config: startup_config,
271            transport_enabled,
272            ifac_netname,
273            ifac_netkey,
274        };
275        Self {
276            interface_name,
277            current: startup.clone(),
278            startup,
279        }
280    }
281}
282
283impl IfacRuntimeConfig {
284    pub(crate) fn from_parts(netname: Option<String>, netkey: Option<String>, size: usize) -> Self {
285        Self {
286            netname,
287            netkey,
288            size,
289        }
290    }
291}
292
293#[cfg(feature = "iface-backbone")]
294#[derive(Debug, Clone)]
295pub struct BackbonePeerPoolSettings {
296    pub max_connected: usize,
297    pub failure_threshold: usize,
298    pub failure_window: Duration,
299    pub cooldown: Duration,
300}
301
302#[cfg(feature = "iface-backbone")]
303pub(crate) const BACKBONE_PEER_POOL_CONFIGURED_DEFAULT_PRIORITY: u8 = 60;
304#[cfg(feature = "iface-backbone")]
305pub(crate) const BACKBONE_PEER_POOL_DISCOVERED_PRIORITY: u8 = 40;
306
307#[cfg(feature = "iface-backbone")]
308pub(crate) struct BackbonePeerPoolCandidateConfig {
309    pub(crate) client: BackboneClientConfig,
310    pub(crate) mode: u8,
311    pub(crate) gravity: i64,
312    pub(crate) recursive_prs: bool,
313    pub(crate) announces_from_internal: bool,
314    pub(crate) announces_to_internal: Option<bool>,
315    pub(crate) ingress_control: rns_core::transport::types::IngressControlConfig,
316    pub(crate) stamp_costs: rns_core::transport::types::InterfaceStampCosts,
317    pub(crate) ifac_runtime: IfacRuntimeConfig,
318    pub(crate) ifac_enabled: bool,
319    pub(crate) interface_type_name: String,
320    pub(crate) source: BackbonePeerPoolCandidateSource,
321    pub(crate) priority: u8,
322    pub(crate) discovery: Option<BackbonePeerPoolDiscoveryCandidate>,
323}
324
325#[cfg(feature = "iface-backbone")]
326#[derive(Debug, Clone, Copy, PartialEq, Eq)]
327pub(crate) enum BackbonePeerPoolCandidateSource {
328    Configured,
329    Discovered,
330}
331
332#[cfg(feature = "iface-backbone")]
333impl BackbonePeerPoolCandidateSource {
334    fn as_str(self) -> &'static str {
335        match self {
336            Self::Configured => "configured",
337            Self::Discovered => "discovered",
338        }
339    }
340}
341
342#[cfg(feature = "iface-backbone")]
343#[derive(Debug, Clone)]
344pub(crate) struct BackbonePeerPoolDiscoveryCandidate {
345    pub(crate) discovery_hash: [u8; 32],
346    pub(crate) status: crate::discovery::DiscoveredStatus,
347    pub(crate) hops: u8,
348    pub(crate) stamp_value: rns_core::stamp::StampValue,
349    pub(crate) last_heard: f64,
350}
351
352#[cfg(feature = "iface-backbone")]
353struct BackbonePeerPool {
354    settings: BackbonePeerPoolSettings,
355    candidates: Vec<BackbonePeerPoolCandidate>,
356}
357
358#[cfg(feature = "iface-backbone")]
359struct BackbonePeerPoolCandidate {
360    config: BackbonePeerPoolCandidateConfig,
361    active_id: Option<InterfaceId>,
362    failures: Vec<f64>,
363    /// Failed attempts since the last successful connect; drives the
364    /// reconnect backoff of configured peers.
365    consecutive_failures: u32,
366    retry_after: Option<f64>,
367    cooldown_until: Option<f64>,
368    last_error: Option<String>,
369    /// Stops the active connection's reader when the pool evicts it.
370    control: Option<crate::interface::ListenerControl>,
371}
372
373/// Thin wrapper providing `EngineAccess` for a `TransportEngine` + Driver interfaces.
374#[cfg(feature = "hooks")]
375struct EngineRef<'a> {
376    engine: &'a TransportEngine,
377    interfaces: &'a crate::interface::InterfaceMap<InterfaceEntry>,
378    link_manager: &'a LinkManager,
379    now: f64,
380}
381
382#[cfg(feature = "hooks")]
383impl<'a> EngineAccess for EngineRef<'a> {
384    fn has_path(&self, dest: &[u8; 16]) -> bool {
385        self.engine.has_path(dest)
386    }
387    fn hops_to(&self, dest: &[u8; 16]) -> Option<u8> {
388        self.engine.hops_to(dest)
389    }
390    fn next_hop(&self, dest: &[u8; 16]) -> Option<[u8; 16]> {
391        self.engine.next_hop(dest)
392    }
393    fn is_blackholed(&self, identity: &[u8; 16]) -> bool {
394        self.engine.is_blackholed(identity, self.now)
395    }
396    fn interface_name(&self, id: u64) -> Option<String> {
397        self.interfaces
398            .get(&InterfaceId(id))
399            .map(|e| e.info.name.clone())
400    }
401    fn interface_mode(&self, id: u64) -> Option<u8> {
402        self.interfaces.get(&InterfaceId(id)).map(|e| e.info.mode)
403    }
404    fn identity_hash(&self) -> Option<[u8; 16]> {
405        self.engine.identity_hash().copied()
406    }
407    fn announce_rate(&self, id: u64) -> Option<i32> {
408        self.interfaces
409            .get(&InterfaceId(id))
410            .map(|e| (e.stats.outgoing_announce_freq() * 1000.0) as i32)
411    }
412    fn link_state(&self, link_hash: &[u8; 16]) -> Option<u8> {
413        use rns_core::link::types::LinkState;
414        self.link_manager.link_state(link_hash).map(|s| match s {
415            LinkState::Pending => 0,
416            LinkState::Handshake => 1,
417            LinkState::Active => 2,
418            LinkState::Stale => 3,
419            LinkState::Closed => 4,
420        })
421    }
422}
423
424/// Extract the 16-byte destination hash from a raw packet header.
425///
426/// HEADER_1 (raw[0] & 0x40 == 0): dest at bytes 2..18
427/// HEADER_2 (raw[0] & 0x40 != 0): dest at bytes 18..34 (after transport ID)
428#[cfg(any(test, feature = "hooks"))]
429fn extract_dest_hash(raw: &[u8]) -> [u8; 16] {
430    let mut dest = [0u8; 16];
431    if raw.is_empty() {
432        return dest;
433    }
434    let is_header2 = raw[0] & 0x40 != 0;
435    let start = if is_header2 { 18 } else { 2 };
436    let end = start + 16;
437    if raw.len() >= end {
438        dest.copy_from_slice(&raw[start..end]);
439    }
440    dest
441}
442
443/// Execute a hook chain on disjoint Driver fields (avoids &mut self borrow conflict).
444#[cfg(feature = "hooks")]
445fn run_hook_inner(
446    programs: &mut [rns_hooks::LoadedProgram],
447    hook_manager: &Option<HookManager>,
448    engine_access: &dyn EngineAccess,
449    ctx: &HookContext,
450    now: f64,
451    provider_events_enabled: bool,
452) -> Option<rns_hooks::ExecuteResult> {
453    if programs.is_empty() {
454        return None;
455    }
456    let mgr = hook_manager.as_ref()?;
457    mgr.run_chain_with_provider_events(programs, ctx, engine_access, now, provider_events_enabled)
458}
459
460/// What a packet hook point does after its hook chain ran.
461///
462/// Only hook points that hold the raw packet bytes before acting on them apply
463/// a `Modify` verdict (`PreIngress`, `SendOnInterface`,
464/// `BroadcastOnAllInterfaces`, `DeliverLocal`). Every other hook point treats
465/// `Modify` as `Continue` and ignores the replacement bytes.
466#[cfg(feature = "hooks")]
467pub(crate) enum PacketHookOutcome {
468    /// Carry on with the bytes the hook point already holds.
469    Keep,
470    /// Stop processing this packet.
471    Drop,
472    /// Carry on with the replacement bytes, decoded with the same decoder the
473    /// normal path uses. Hashes and header fields come from the replacement.
474    Replace(RawPacket),
475}
476
477/// Validate replacement bytes from a `Modify` verdict: they must be non-empty,
478/// fit within `max_len`, and decode as a packet.
479#[cfg(feature = "hooks")]
480fn validate_hook_replacement(data: &[u8], max_len: usize) -> Result<RawPacket, &'static str> {
481    if data.is_empty() {
482        return Err("replacement is empty");
483    }
484    if data.len() > max_len {
485        return Err("replacement exceeds the packet size limit");
486    }
487    RawPacket::unpack(data).map_err(|_| "replacement does not decode as a packet")
488}
489
490#[cfg(feature = "hooks")]
491fn backbone_peer_hook_context(event: &BackbonePeerHookEvent) -> HookContext<'_> {
492    HookContext::BackbonePeer {
493        server_interface_id: event.server_interface_id.0,
494        peer_interface_id: event.peer_interface_id.map(|id| id.0),
495        peer_ip: event.peer_ip,
496        peer_port: event.peer_port,
497        connected_for: event.connected_for,
498        had_received_data: event.had_received_data,
499        penalty_level: event.penalty_level,
500        blacklist_for: event.blacklist_for,
501    }
502}
503
504/// Convert a Vec of ActionWire into TransportActions for dispatch.
505#[cfg(feature = "hooks")]
506fn convert_injected_actions(actions: Vec<rns_hooks::ActionWire>) -> Vec<TransportAction> {
507    actions
508        .into_iter()
509        .map(|a| {
510            use rns_hooks::ActionWire;
511            match a {
512                ActionWire::SendOnInterface { interface, raw } => {
513                    TransportAction::SendOnInterface {
514                        interface: InterfaceId(interface),
515                        raw: raw.into(),
516                    }
517                }
518                ActionWire::BroadcastOnAllInterfaces {
519                    raw,
520                    exclude,
521                    has_exclude,
522                } => TransportAction::BroadcastOnAllInterfaces {
523                    raw: raw.into(),
524                    exclude: if has_exclude != 0 {
525                        Some(InterfaceId(exclude))
526                    } else {
527                        None
528                    },
529                },
530                ActionWire::DeliverLocal {
531                    destination_hash,
532                    raw,
533                    packet_hash,
534                    receiving_interface,
535                } => TransportAction::DeliverLocal {
536                    destination_hash,
537                    raw: raw.into(),
538                    packet_hash,
539                    receiving_interface: InterfaceId(receiving_interface),
540                },
541                ActionWire::PathUpdated {
542                    destination_hash,
543                    hops,
544                    next_hop,
545                    interface,
546                } => TransportAction::PathUpdated {
547                    destination_hash,
548                    hops,
549                    next_hop,
550                    interface: InterfaceId(interface),
551                },
552                ActionWire::CacheAnnounce { packet_hash, raw } => TransportAction::CacheAnnounce {
553                    packet_hash,
554                    raw: raw.into(),
555                },
556                ActionWire::TunnelEstablished {
557                    tunnel_id,
558                    interface,
559                } => TransportAction::TunnelEstablished {
560                    tunnel_id,
561                    interface: InterfaceId(interface),
562                },
563                ActionWire::TunnelSynthesize {
564                    interface,
565                    data,
566                    dest_hash,
567                } => TransportAction::TunnelSynthesize {
568                    interface: InterfaceId(interface),
569                    data,
570                    dest_hash,
571                },
572                ActionWire::ForwardToLocalClients {
573                    raw,
574                    exclude,
575                    has_exclude,
576                } => TransportAction::ForwardToLocalClients {
577                    raw: raw.into(),
578                    exclude: if has_exclude != 0 {
579                        Some(InterfaceId(exclude))
580                    } else {
581                        None
582                    },
583                },
584                ActionWire::ForwardPlainBroadcast {
585                    raw,
586                    to_local,
587                    exclude,
588                    has_exclude,
589                } => TransportAction::ForwardPlainBroadcast {
590                    raw: raw.into(),
591                    to_local: to_local != 0,
592                    exclude: if has_exclude != 0 {
593                        Some(InterfaceId(exclude))
594                    } else {
595                        None
596                    },
597                },
598                ActionWire::AnnounceReceived {
599                    destination_hash,
600                    identity_hash,
601                    public_key,
602                    name_hash,
603                    random_hash,
604                    app_data,
605                    hops,
606                    receiving_interface,
607                } => TransportAction::AnnounceReceived(Box::new(ReceivedAnnounce {
608                    destination_hash,
609                    identity_hash,
610                    public_key,
611                    name_hash,
612                    random_hash,
613                    ratchet: None,
614                    app_data,
615                    stamp: None,
616                    stamp_value: rns_core::stamp::StampValue::ZERO,
617                    lr_cost: None,
618                    hops,
619                    receiving_interface: InterfaceId(receiving_interface),
620                    rx: rns_core::transport::RxMetadata::default(),
621                })),
622            }
623        })
624        .collect()
625}
626
627/// Infer the interface type string from a dynamic interface's name.
628/// Dynamic interfaces (TCP server clients, backbone peers, auto peers, local server clients)
629/// include their type in the name prefix set at construction.
630fn infer_interface_type(name: &str) -> String {
631    if name.starts_with("TCPServerInterface") {
632        "TCPServerClientInterface".to_string()
633    } else if name.starts_with("BackboneInterface") {
634        "BackboneInterface".to_string()
635    } else if name.starts_with("LocalInterface") {
636        "LocalServerClientInterface".to_string()
637    } else if name.starts_with("WeaveInterfacePeer[") {
638        "WeaveInterfacePeer".to_string()
639    } else {
640        // AutoInterface peers use "{group_name}:{peer_addr}" format where
641        // group_name is the config section name (typically "AutoInterface" or similar).
642        "AutoInterface".to_string()
643    }
644}
645
646pub use crate::common::callbacks::Callbacks;
647
648#[derive(Clone)]
649struct SharedAnnounceRecord {
650    name_hash: [u8; 10],
651    identity_prv_key: [u8; 64],
652    app_data: Option<Vec<u8>>,
653}
654
655/// Cache of path-response announces for local destinations:
656/// (destination, path-request tag) → (created, raw announce).
657type LocalPathResponseCache = HashMap<([u8; 16], Vec<u8>), (f64, Vec<u8>)>;
658
659#[derive(Debug, Clone)]
660pub(crate) struct KnownDestinationState {
661    announced: crate::destination::AnnouncedIdentity,
662    was_used: bool,
663    last_used_at: Option<f64>,
664    retained: bool,
665    stamp: Option<[u8; rns_core::constants::STAMP_LENGTH]>,
666    stamp_value: rns_core::stamp::StampValue,
667    lr_cost: Option<rns_core::stamp::StampCost>,
668}
669
670struct KnownDestinationCleanup {
671    candidates: Vec<[u8; 16]>,
672    cursor: usize,
673    started_at: Instant,
674    removed: usize,
675}
676
677#[derive(Debug, Clone, Copy)]
678pub(crate) struct TrafficSample {
679    sampled_at: f64,
680    rxb: u64,
681    txb: u64,
682    rx_packets: u64,
683    tx_packets: u64,
684    arxb: u64,
685    atxb: u64,
686    prxb: u64,
687    ptxb: u64,
688}
689
690impl TrafficSample {
691    fn from_stats(sampled_at: f64, stats: &crate::interface::InterfaceStats) -> Self {
692        Self {
693            sampled_at,
694            rxb: stats.rxb,
695            txb: stats.txb,
696            rx_packets: stats.rx_packets,
697            tx_packets: stats.tx_packets,
698            arxb: stats.arxb,
699            atxb: stats.atxb,
700            prxb: stats.prxb,
701            ptxb: stats.ptxb,
702        }
703    }
704}
705
706/// The driver loop. Owns the engine and all interface entries.
707pub(crate) struct ManagedInterface {
708    pub(crate) parent_id: InterfaceId,
709    pub(crate) type_name: String,
710    pub(crate) control: Option<crate::interface::ListenerControl>,
711    pub(crate) static_ids: Vec<InterfaceId>,
712}
713
714/// Interval of the transport-state snapshot, as the persistence thread keeps.
715pub(crate) const TRANSPORT_STATE_PERSIST_INTERVAL: Duration = Duration::from_secs(12 * 60 * 60);
716
717/// Deadlines and pending jobs of the timer and housekeeping work the driver
718/// runs itself when [`crate::engine::FOLD_TIMERS`] is set.
719pub(crate) struct FoldedTimers {
720    pub(crate) next_tick: Instant,
721    pub(crate) next_persist: Instant,
722    /// Where transport state is persisted, on transport nodes.
723    pub(crate) persist_dir: Option<std::path::PathBuf>,
724    pub(crate) jobs: std::collections::VecDeque<FoldedJob>,
725    /// Whether a timerfd loop source delivers the tick.
726    pub(crate) ticks_from_source: bool,
727    /// Announces taken from the verification queue, checked one per idle
728    /// turn when CPU work is folded.
729    pub(crate) announces: std::collections::VecDeque<(
730        rns_core::transport::announce_verify_queue::AnnounceVerifyKey,
731        rns_core::transport::announce_verify_queue::PendingAnnounce,
732    )>,
733    /// Whether the verification queue may hold announces not yet taken.
734    pub(crate) announces_queued: bool,
735    /// Outgoing resources compressed on the driver loop, oldest first.
736    pub(crate) compressions: std::collections::VecDeque<resource_preparation::FoldedCompression>,
737    pub(crate) next_compression_id: u64,
738}
739
740pub(crate) enum FoldedJob {
741    PersistTransportState,
742    CleanRatchets {
743        known_destinations: HashSet<[u8; 16]>,
744        now: f64,
745        expiry: f64,
746    },
747}
748
749impl FoldedTimers {
750    fn new() -> Self {
751        let now = Instant::now();
752        FoldedTimers {
753            next_tick: now + Duration::from_millis(DEFAULT_TICK_INTERVAL_MS),
754            next_persist: now + TRANSPORT_STATE_PERSIST_INTERVAL,
755            persist_dir: None,
756            jobs: std::collections::VecDeque::new(),
757            ticks_from_source: false,
758            announces: std::collections::VecDeque::new(),
759            announces_queued: false,
760            compressions: std::collections::VecDeque::new(),
761            next_compression_id: 0,
762        }
763    }
764}
765
766pub struct Driver {
767    resource_preparation: Option<resource_preparation::Worker>,
768    pub(crate) tracked_link_send: Option<([u8; 32], crate::link_send::Completion)>,
769    pub(crate) pending_link_frames: std::collections::VecDeque<PendingLinkFrame>,
770    pub(crate) engine: TransportEngine,
771    pub(crate) interfaces: crate::interface::InterfaceMap<InterfaceEntry>,
772    /// Parent listener for dynamically spawned interfaces.
773    pub(crate) dynamic_interface_parents: HashMap<InterfaceId, InterfaceId>,
774    /// Previous cumulative counters used to calculate current one-second rates.
775    pub(crate) traffic_samples: HashMap<InterfaceId, TrafficSample>,
776    pub(crate) rng: OsRng,
777    pub(crate) rx: EventReceiver,
778    pub(crate) callbacks: Box<dyn Callbacks>,
779    pub(crate) started: f64,
780    pub(crate) lifecycle_state: LifecycleState,
781    /// Whether `lifecycle_state` is still `Active`, shared with the node
782    /// handle so that each API call can check it without a driver query.
783    pub(crate) accepting_work: Arc<AtomicBool>,
784    /// This node is a client of a shared instance, which sends the path
785    /// requests that rediscover paths for it
786    /// (`Transport.owner.is_connected_to_shared_instance`).
787    pub(crate) connected_to_shared_instance: bool,
788    pub(crate) drain_started_at: Option<Instant>,
789    pub(crate) drain_deadline: Option<Instant>,
790    pub(crate) listener_controls: Vec<crate::interface::ListenerControl>,
791    pub(crate) interface_management: Option<crate::node::InterfaceManagementConfig>,
792    pub(crate) managed_interfaces: HashMap<String, ManagedInterface>,
793    pub(crate) retired_interface_parents: HashSet<InterfaceId>,
794    /// Writers that may hold frames queued without a wakeup.
795    pub(crate) writers_to_wake: Vec<InterfaceId>,
796    /// Ingress burst state last published to the event queue, per interface.
797    pub(crate) published_ingress_bursts: crate::interface::InterfaceMap<(Option<f64>, Option<f64>)>,
798    pub(crate) announce_cache: Option<crate::announce_cache::AnnounceCache>,
799    /// Destination hash for rnstransport.tunnel.synthesize (PLAIN).
800    pub(crate) tunnel_synth_dest: [u8; 16],
801    /// Transport identity (optional, needed for tunnel synthesis).
802    pub(crate) transport_identity: Option<rns_crypto::identity::Identity>,
803    /// Link manager: handles link lifecycle, request/response.
804    pub(crate) link_manager: LinkManager,
805    /// Management configuration for ACL checks.
806    pub(crate) management_config: crate::management::ManagementConfig,
807    /// Last time management announces were emitted.
808    pub(crate) last_management_announce: f64,
809    /// Whether initial management announce has been sent (delayed 5s after start).
810    pub(crate) initial_announce_sent: bool,
811    /// Cache of known announced identities and lifecycle state, keyed by destination hash.
812    pub(crate) known_destinations: HashMap<[u8; 16], KnownDestinationState>,
813    /// Store for received remote ratchets, if persistence/use is enabled.
814    pub(crate) ratchet_store: Option<Arc<dyn crate::storage::RatchetStore>>,
815    /// TTL for received ratchets, in seconds.
816    pub(crate) ratchet_expiry_secs: f64,
817    /// TTL for known destinations without an active path, in seconds.
818    pub(crate) known_destinations_ttl: f64,
819    /// Maximum number of retained known destinations.
820    pub(crate) known_destinations_max_entries: usize,
821    /// TTL for announce rate-limiter entries without an active path, in seconds.
822    pub(crate) rate_limiter_ttl_secs: f64,
823    /// Destination hash for rnstransport.path.request (PLAIN).
824    pub(crate) path_request_dest: [u8; 16],
825    /// Proof strategies per destination hash.
826    /// Maps dest_hash → (strategy, optional signing identity for generating proofs).
827    pub(crate) proof_strategies: HashMap<
828        [u8; 16],
829        (
830            rns_core::types::ProofStrategy,
831            Option<rns_crypto::identity::Identity>,
832        ),
833    >,
834    /// Tracked sent packets for proof matching: packet_hash → (dest_hash, sent_time).
835    pub(crate) sent_packets: HashMap<[u8; 32], ([u8; 16], f64)>,
836    /// Local destinations whose single packets are delivered decrypted, with
837    /// the identity they are decrypted with when they have no ratchets.
838    pub(crate) decrypted_destinations: HashMap<[u8; 16], Arc<rns_crypto::identity::Identity>>,
839    /// Reused buffer for single-packet proofs the proof pool finished.
840    pub(crate) packet_proofs: Vec<crate::common::link_manager::ProofOutcome>,
841    /// Reused transport action buffers (see [`crate::action_pool`]).
842    pub(crate) transport_actions: crate::action_pool::ActionPool<TransportAction>,
843    /// Reused link manager action buffers.
844    pub(crate) link_actions: crate::action_pool::ActionPool<LinkManagerAction>,
845    /// Completed proofs for probe polling: packet_hash → (rtt_seconds, received_time).
846    /// Only probes ([`QueryRequest::SendProbe`]) are recorded: a proof for
847    /// every other packet would be kept two minutes for nobody to poll.
848    pub(crate) completed_proofs: HashMap<[u8; 32], (f64, f64)>,
849    /// Probes sent and not yet proved, whose proofs `completed_proofs`
850    /// records.
851    pub(crate) pending_probes: std::collections::HashSet<[u8; 32]>,
852    /// Parsed signing keys of destinations whose packet proofs were checked.
853    /// A destination's hash follows from its identity, so its key never
854    /// changes.
855    pub(crate) proof_keys: HashMap<[u8; 16], rns_crypto::ed25519::Ed25519PublicKey>,
856    /// Locally registered destinations: hash → dest_type.
857    pub(crate) local_destinations: HashMap<[u8; 16], u8>,
858    /// Latest explicit SINGLE announces to replay after shared-client reconnect.
859    shared_announces: HashMap<[u8; 16], SharedAnnounceRecord>,
860    /// Shared local interfaces that went down and should replay announces on reconnect.
861    shared_reconnect_pending: HashMap<InterfaceId, bool>,
862    /// Path-response announces recently sent for local destinations, keyed by
863    /// (destination, path-request tag), replayed byte-for-byte for a repeated
864    /// tag within `LOCAL_PATH_RESPONSE_WINDOW`: (created, raw announce).
865    pub(crate) local_path_responses: LocalPathResponseCache,
866    /// Hole-punch manager for direct P2P connections.
867    pub(crate) holepunch_manager: HolePunchManager,
868    /// Event sender for worker threads to send results back to the driver loop.
869    pub(crate) event_tx: crate::event::EventSender,
870    /// Grinds own path request and announce stamps off the driver thread.
871    /// Replaced by the node's shared instance at startup.
872    pub(crate) stamper: Arc<crate::stamper::Stamper>,
873    /// `Transport.STAMP_DESTINATIONS`, shared with the node handle whose
874    /// announce API reads it; settable as `global.stamp_destinations`.
875    pub(crate) stamp_destinations: Arc<AtomicBool>,
876    /// The `[stamps]` config section this node started with. Link request
877    /// stamping reads `stamp_lrs`, `always_stamp_lrs` and `lr_stamp_timeout`
878    /// from it; the other
879    /// options are applied to the engine, the stamper and the node handle at
880    /// startup.
881    pub(crate) stamp_settings: crate::config::StampsSection,
882    /// Inbound packets whose hop count reaches this limit are dropped and
883    /// counted as `hop_limiter_hits` (`Transport.HOP_LIMITER`, set from
884    /// `[reticulum] hop_limiter`).
885    pub(crate) hop_limiter: u8,
886    /// Maximum queued outbound frames per interface writer worker.
887    pub(crate) interface_writer_queue_capacity: usize,
888    /// Linux mark inherited by dynamically created IP underlay sockets.
889    pub(crate) underlay_mark: Option<u32>,
890    /// Default announce-rate controls for interfaces on transport nodes.
891    pub(crate) announce_rate_defaults: AnnounceRateDefaults,
892    /// Default ingress/egress controls for interfaces on transport nodes.
893    pub(crate) ingress_control_defaults: rns_core::transport::types::IngressControlConfig,
894    /// Shared timer interval used by the node timer thread.
895    pub(crate) tick_interval_ms: Arc<AtomicU64>,
896    /// Runtime-config handles for backbone server interfaces, keyed by config name.
897    #[cfg(feature = "iface-backbone")]
898    pub(crate) backbone_runtime: HashMap<String, BackboneRuntimeConfigHandle>,
899    /// Live peer-state handles for backbone server interfaces, keyed by config name.
900    #[cfg(feature = "iface-backbone")]
901    pub(crate) backbone_peer_state: HashMap<String, BackbonePeerStateHandle>,
902    /// Runtime-config handles for backbone client interfaces, keyed by config name.
903    #[cfg(feature = "iface-backbone")]
904    pub(crate) backbone_client_runtime: HashMap<String, BackboneClientRuntimeConfigHandle>,
905    /// Runtime-config state for backbone discovery metadata, keyed by config name.
906    #[cfg(feature = "iface-backbone")]
907    pub(crate) backbone_discovery_runtime: HashMap<String, BackboneDiscoveryRuntimeHandle>,
908    /// Ordered outbound Backbone peer pool, if enabled.
909    #[cfg(feature = "iface-backbone")]
910    backbone_peer_pool: Option<BackbonePeerPool>,
911    /// Shared allocator for listener-created and discovery-created dynamic interfaces.
912    pub(crate) next_dynamic_interface_id: Arc<AtomicU64>,
913    /// Runtime-config handles for TCP server interfaces, keyed by config name.
914    #[cfg(feature = "iface-tcp")]
915    pub(crate) tcp_server_runtime: HashMap<String, TcpServerRuntimeConfigHandle>,
916    /// Runtime-config handles for TCP client interfaces, keyed by config name.
917    #[cfg(feature = "iface-tcp")]
918    pub(crate) tcp_client_runtime: HashMap<String, TcpClientRuntimeConfigHandle>,
919    /// Runtime-config state for TCP server discovery metadata, keyed by config name.
920    #[cfg(feature = "iface-tcp")]
921    pub(crate) tcp_server_discovery_runtime: HashMap<String, TcpServerDiscoveryRuntimeHandle>,
922    /// Runtime-config handles for UDP interfaces, keyed by config name.
923    #[cfg(feature = "iface-udp")]
924    pub(crate) udp_runtime: HashMap<String, UdpRuntimeConfigHandle>,
925    /// Runtime-config handles for Auto interfaces, keyed by config name.
926    #[cfg(feature = "iface-auto")]
927    pub(crate) auto_runtime: HashMap<String, AutoRuntimeConfigHandle>,
928    /// Runtime-config handles for I2P interfaces, keyed by config name.
929    #[cfg(feature = "iface-i2p")]
930    pub(crate) i2p_runtime: HashMap<String, I2pRuntimeConfigHandle>,
931    /// Runtime-config handles for Pipe interfaces, keyed by config name.
932    #[cfg(feature = "iface-pipe")]
933    pub(crate) pipe_runtime: HashMap<String, PipeRuntimeConfigHandle>,
934    /// Runtime-config handles for RNode interfaces, keyed by config name.
935    #[cfg(feature = "iface-rnode")]
936    pub(crate) rnode_runtime: HashMap<String, RNodeRuntimeConfigHandle>,
937    /// Startup/default interface metadata for generic cross-cutting runtime config.
938    pub(crate) interface_runtime_defaults:
939        HashMap<String, rns_core::transport::types::InterfaceInfo>,
940    /// Current IFAC runtime config for static interfaces that support IFAC mutation.
941    pub(crate) interface_ifac_runtime: HashMap<String, IfacRuntimeConfig>,
942    /// Startup/default IFAC runtime config for static interfaces.
943    pub(crate) interface_ifac_runtime_defaults: HashMap<String, IfacRuntimeConfig>,
944    /// Storage for discovered interfaces.
945    pub(crate) discovered_interfaces: crate::discovery::DiscoveredInterfaceStorage,
946    /// Required stamp value for accepting discovered interfaces.
947    pub(crate) discovery_required_value: rns_core::stamp::StampCost,
948    /// Bounded cache of discovery payloads with previously rejected stamps.
949    pub(crate) discovery_stamp_cache: crate::discovery::DiscoveryStampCache,
950    /// Name hash for interface discovery announces ("rnstransport.discovery.interface").
951    pub(crate) discovery_name_hash: [u8; 10],
952    /// Destination hash for the probe responder (if respond_to_probes is enabled).
953    pub(crate) probe_responder_hash: Option<[u8; 16]>,
954    /// Whether interface discovery is enabled.
955    pub(crate) discover_interfaces: bool,
956    /// Explicit mode for interfaces created from discovery announces.
957    pub(crate) autoconnect_interface_mode: Option<u8>,
958    /// Gravity for interfaces created from discovery announces.
959    pub(crate) autoconnect_interface_gravity: i64,
960    /// Internal announce propagation override for discovered interfaces.
961    pub(crate) autoconnect_announces_to_internal: Option<bool>,
962    /// Allow auto-connecting discovered interfaces from unverified implementations.
963    pub(crate) autoconnect_unverified_implementations: bool,
964    /// Announcer for discoverable interfaces (None if nothing to announce).
965    pub(crate) interface_announcer: Option<crate::discovery::InterfaceAnnouncer>,
966    /// Shared async announce verification queue.
967    pub(crate) announce_verify_queue: Arc<Mutex<AnnounceVerifyQueue>>,
968    /// Wakes the verification thread when announces are queued.
969    pub(crate) announce_verify_signal: Arc<WorkSignal>,
970    /// Whether inbound announces should be verified off the driver thread.
971    pub(crate) async_announce_verification: bool,
972    /// Tick counter for periodic discovery cleanup (every ~3600 ticks = ~1 hour).
973    pub(crate) discovery_cleanup_counter: u32,
974    /// Runtime-configurable discovery cleanup interval.
975    pub(crate) discovery_cleanup_interval_ticks: u32,
976    /// Tick counter for periodic MEMSTATS logging (every 300 ticks = ~5 min).
977    pub(crate) memory_stats_counter: u32,
978    /// When to republish the link/interface statistics snapshot.
979    pub(crate) state_snapshot_schedule: crate::state_snapshot::PublishSchedule,
980    /// Tick counter for periodic memory/cache cleanup (every ~3600 ticks = ~1 hour).
981    pub(crate) cache_cleanup_counter: u32,
982    /// Tick counter for incremental announce-cache cleanup scheduling.
983    pub(crate) announce_cache_cleanup_counter: u32,
984    /// Runtime-configurable cleanup interval for known destinations.
985    pub(crate) known_destinations_cleanup_interval_ticks: u32,
986    /// Incremental known-destination cleanup pass, if one is active.
987    known_destination_cleanup: Option<KnownDestinationCleanup>,
988    /// Low-priority ratchet filesystem cleanup associated with the last pass.
989    ratchet_cleanup_handle: Option<std::thread::JoinHandle<()>>,
990    /// Timers and housekeeping the driver runs itself when they are folded
991    /// (see [`crate::engine::FOLD_TIMERS`]).
992    pub(crate) folded: FoldedTimers,
993    /// The RPC servers, when the driver serves them itself (see
994    /// [`crate::engine::FOLD_SERVICES`]).
995    pub(crate) rpc_service: Option<rpc_service::RpcService>,
996    /// The probe server, when it runs on the driver loop (with services
997    /// folded); otherwise the node runs it on a thread of its own.
998    pub(crate) probe_server: Option<crate::holepunch::probe::FoldedProbeServer>,
999    /// Count of known-destination cap evictions since start.
1000    pub(crate) known_destinations_cap_evict_count: usize,
1001    /// Runtime-configurable interval for starting announce cache cleanup.
1002    pub(crate) announce_cache_cleanup_interval_ticks: u32,
1003    /// When set, announce cache cleanup is in progress (contains active packet hashes).
1004    /// Directory iterator for incremental announce cache cleanup.
1005    /// Running total of files removed during current cache cleanup cycle.
1006    /// Runtime-configurable announce cache cleanup batch size.
1007    pub(crate) announce_cache_cleanup_batch_size: usize,
1008    /// Runtime-configurable management announce interval.
1009    pub(crate) management_announce_interval_secs: f64,
1010    /// Startup/default runtime-config values.
1011    pub(crate) runtime_config_defaults: RuntimeConfigDefaults,
1012    /// Hook slots for the programmable hook system (one per HookPoint).
1013    #[cfg(feature = "hooks")]
1014    pub(crate) hook_slots: [HookSlot; HookPoint::COUNT],
1015    /// Hook manager. None if initialization failed.
1016    #[cfg(feature = "hooks")]
1017    pub(crate) hook_manager: Option<HookManager>,
1018    #[cfg(feature = "hooks")]
1019    pub(crate) provider_bridge: Option<ProviderBridge>,
1020    /// Per hook point count of `Modify` replacements that failed validation
1021    /// and were therefore treated as `Drop`.
1022    #[cfg(feature = "hooks")]
1023    pub(crate) hook_rejected_modifications: [u64; HookPoint::COUNT],
1024}
1025
1026impl Driver {
1027    /// Create a new driver.
1028    pub fn new(
1029        config: TransportConfig,
1030        rx: EventReceiver,
1031        tx: crate::event::EventSender,
1032        callbacks: Box<dyn Callbacks>,
1033    ) -> Self {
1034        let announce_queue_max_entries = config.announce_queue_max_entries;
1035        let tunnel_synth_dest = rns_core::destination::destination_hash(
1036            "rnstransport",
1037            &["tunnel", "synthesize"],
1038            None,
1039        );
1040        let path_request_dest =
1041            rns_core::destination::destination_hash("rnstransport", &["path", "request"], None);
1042        let discovery_name_hash = crate::discovery::discovery_name_hash();
1043        let mut engine = TransportEngine::new(config);
1044        engine.seed_table_hashing(&mut OsRng);
1045        engine.register_destination(tunnel_synth_dest, rns_core::constants::DESTINATION_PLAIN);
1046        // Register path request destination so inbound path requests are delivered locally
1047        engine.register_destination(path_request_dest, rns_core::constants::DESTINATION_PLAIN);
1048        // Note: discovery destination is NOT registered as local — it's a SINGLE destination
1049        // whose hash depends on the sender's identity. We match it by name_hash instead.
1050        let mut local_destinations = HashMap::new();
1051        local_destinations.insert(tunnel_synth_dest, rns_core::constants::DESTINATION_PLAIN);
1052        local_destinations.insert(path_request_dest, rns_core::constants::DESTINATION_PLAIN);
1053        let runtime_config_defaults = RuntimeConfigDefaults {
1054            tick_interval_ms: DEFAULT_TICK_INTERVAL_MS,
1055            known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
1056            rate_limiter_ttl_secs: DEFAULT_RATE_LIMITER_TTL_SECS,
1057            known_destinations_cleanup_interval_ticks:
1058                DEFAULT_KNOWN_DESTINATIONS_CLEANUP_INTERVAL_TICKS,
1059            announce_cache_cleanup_interval_ticks: DEFAULT_ANNOUNCE_CACHE_CLEANUP_INTERVAL_TICKS,
1060            announce_cache_cleanup_batch_size: DEFAULT_ANNOUNCE_CACHE_CLEANUP_BATCH_SIZE,
1061            discovery_cleanup_interval_ticks: DEFAULT_DISCOVERY_CLEANUP_INTERVAL_TICKS,
1062            management_announce_interval_secs: DEFAULT_MANAGEMENT_ANNOUNCE_INTERVAL_SECS,
1063            direct_connect_policy: crate::event::HolePunchPolicy::default(),
1064            stamp_destinations: rns_core::constants::STAMP_DESTINATIONS,
1065            stamp_path_requests: rns_core::constants::STAMP_PRS,
1066            stamps_enforced: rns_core::constants::STAMPS_ENFORCED,
1067            lr_stamps_enforced: rns_core::constants::LR_STAMPS_ENFORCED,
1068            stamp_link_requests: rns_core::constants::STAMP_LRS,
1069            always_stamp_link_requests: rns_core::constants::ALWAYS_STAMP_LRS,
1070            #[cfg(feature = "hooks")]
1071            provider_queue_max_events: crate::provider_bridge::ProviderBridgeConfig::default()
1072                .queue_max_events,
1073            #[cfg(feature = "hooks")]
1074            provider_queue_max_bytes: crate::provider_bridge::ProviderBridgeConfig::default()
1075                .queue_max_bytes,
1076        };
1077        Driver {
1078            tracked_link_send: None,
1079            pending_link_frames: std::collections::VecDeque::new(),
1080            engine,
1081            interfaces: Default::default(),
1082            dynamic_interface_parents: HashMap::new(),
1083            traffic_samples: HashMap::new(),
1084            rng: OsRng,
1085            rx,
1086            callbacks,
1087            started: time::now(),
1088            lifecycle_state: LifecycleState::Active,
1089            accepting_work: Arc::new(AtomicBool::new(true)),
1090            connected_to_shared_instance: false,
1091            drain_started_at: None,
1092            drain_deadline: None,
1093            listener_controls: Vec::new(),
1094            interface_management: None,
1095            managed_interfaces: HashMap::new(),
1096            retired_interface_parents: HashSet::new(),
1097            published_ingress_bursts: Default::default(),
1098            writers_to_wake: Vec::new(),
1099            announce_cache: None,
1100            tunnel_synth_dest,
1101            transport_identity: None,
1102            link_manager: {
1103                let mut link_manager = LinkManager::new();
1104                link_manager.set_link_request_costs(tx.link_request_costs().clone());
1105                link_manager
1106            },
1107            management_config: Default::default(),
1108            last_management_announce: 0.0,
1109            initial_announce_sent: false,
1110            known_destinations: HashMap::new(),
1111            ratchet_store: None,
1112            ratchet_expiry_secs: rns_core::constants::RATCHET_EXPIRY as f64,
1113            known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
1114            known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
1115            rate_limiter_ttl_secs: DEFAULT_RATE_LIMITER_TTL_SECS,
1116            path_request_dest,
1117            proof_strategies: HashMap::new(),
1118            sent_packets: HashMap::new(),
1119            packet_proofs: Vec::new(),
1120            decrypted_destinations: HashMap::new(),
1121            transport_actions: crate::action_pool::ActionPool::new(),
1122            link_actions: crate::action_pool::ActionPool::new(),
1123            completed_proofs: HashMap::new(),
1124            pending_probes: std::collections::HashSet::new(),
1125            proof_keys: HashMap::new(),
1126            local_destinations,
1127            shared_announces: HashMap::new(),
1128            shared_reconnect_pending: HashMap::new(),
1129            local_path_responses: HashMap::new(),
1130            holepunch_manager: {
1131                let mut manager =
1132                    HolePunchManager::new(vec![], rns_core::holepunch::ProbeProtocol::Rnsp, None);
1133                manager.set_event_sender(tx.clone());
1134                manager
1135            },
1136            stamper: Arc::new(crate::stamper::Stamper::new(tx.clone(), None)),
1137            stamp_destinations: Arc::new(AtomicBool::new(rns_core::constants::STAMP_DESTINATIONS)),
1138            stamp_settings: crate::config::StampsSection::default(),
1139            hop_limiter: rns_core::constants::HOP_LIMITER,
1140            event_tx: tx,
1141            interface_writer_queue_capacity: crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
1142            underlay_mark: None,
1143            announce_rate_defaults: AnnounceRateDefaults::default(),
1144            ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(),
1145            tick_interval_ms: Arc::new(AtomicU64::new(DEFAULT_TICK_INTERVAL_MS)),
1146            #[cfg(feature = "iface-backbone")]
1147            backbone_runtime: HashMap::new(),
1148            #[cfg(feature = "iface-backbone")]
1149            backbone_peer_state: HashMap::new(),
1150            #[cfg(feature = "iface-backbone")]
1151            backbone_client_runtime: HashMap::new(),
1152            #[cfg(feature = "iface-backbone")]
1153            backbone_discovery_runtime: HashMap::new(),
1154            #[cfg(feature = "iface-backbone")]
1155            backbone_peer_pool: None,
1156            next_dynamic_interface_id: Arc::new(AtomicU64::new(10000)),
1157            #[cfg(feature = "iface-tcp")]
1158            tcp_server_runtime: HashMap::new(),
1159            #[cfg(feature = "iface-tcp")]
1160            tcp_client_runtime: HashMap::new(),
1161            #[cfg(feature = "iface-tcp")]
1162            tcp_server_discovery_runtime: HashMap::new(),
1163            #[cfg(feature = "iface-udp")]
1164            udp_runtime: HashMap::new(),
1165            #[cfg(feature = "iface-auto")]
1166            auto_runtime: HashMap::new(),
1167            #[cfg(feature = "iface-i2p")]
1168            i2p_runtime: HashMap::new(),
1169            #[cfg(feature = "iface-pipe")]
1170            pipe_runtime: HashMap::new(),
1171            #[cfg(feature = "iface-rnode")]
1172            rnode_runtime: HashMap::new(),
1173            interface_runtime_defaults: HashMap::new(),
1174            interface_ifac_runtime: HashMap::new(),
1175            interface_ifac_runtime_defaults: HashMap::new(),
1176            discovered_interfaces: crate::discovery::DiscoveredInterfaceStorage::new(
1177                std::env::temp_dir().join("rns-discovered-interfaces"),
1178            ),
1179            discovery_required_value: crate::discovery::DEFAULT_STAMP_VALUE,
1180            discovery_stamp_cache: crate::discovery::DiscoveryStampCache::default(),
1181            discovery_name_hash,
1182            probe_responder_hash: None,
1183            discover_interfaces: false,
1184            autoconnect_interface_mode: None,
1185            autoconnect_interface_gravity: 0,
1186            autoconnect_announces_to_internal: None,
1187            autoconnect_unverified_implementations: false,
1188            interface_announcer: None,
1189            announce_verify_queue: Arc::new(Mutex::new(AnnounceVerifyQueue::new(
1190                announce_queue_max_entries,
1191            ))),
1192            announce_verify_signal: Arc::default(),
1193            async_announce_verification: false,
1194            discovery_cleanup_counter: 0,
1195            discovery_cleanup_interval_ticks: runtime_config_defaults
1196                .discovery_cleanup_interval_ticks,
1197            memory_stats_counter: 0,
1198            state_snapshot_schedule: Default::default(),
1199            cache_cleanup_counter: 0,
1200            announce_cache_cleanup_counter: 0,
1201            known_destinations_cleanup_interval_ticks: runtime_config_defaults
1202                .known_destinations_cleanup_interval_ticks,
1203            known_destination_cleanup: None,
1204            ratchet_cleanup_handle: None,
1205            folded: FoldedTimers::new(),
1206            rpc_service: None,
1207            probe_server: None,
1208            resource_preparation: None,
1209            known_destinations_cap_evict_count: 0,
1210            announce_cache_cleanup_interval_ticks: runtime_config_defaults
1211                .announce_cache_cleanup_interval_ticks,
1212            announce_cache_cleanup_batch_size: runtime_config_defaults
1213                .announce_cache_cleanup_batch_size,
1214            management_announce_interval_secs: runtime_config_defaults
1215                .management_announce_interval_secs,
1216            runtime_config_defaults,
1217            #[cfg(feature = "hooks")]
1218            hook_slots: create_hook_slots(),
1219            #[cfg(feature = "hooks")]
1220            hook_manager: HookManager::new().ok(),
1221            #[cfg(feature = "hooks")]
1222            provider_bridge: None,
1223            #[cfg(feature = "hooks")]
1224            hook_rejected_modifications: [0; HookPoint::COUNT],
1225        }
1226    }
1227
1228    pub fn set_announce_verify_queue_config(
1229        &mut self,
1230        max_entries: usize,
1231        max_bytes: usize,
1232        max_stale_secs: f64,
1233        overflow_policy: OverflowPolicy,
1234    ) {
1235        self.announce_verify_queue = Arc::new(Mutex::new(AnnounceVerifyQueue::with_limits(
1236            max_entries,
1237            max_bytes,
1238            max_stale_secs,
1239            overflow_policy,
1240        )));
1241    }
1242
1243    pub fn set_announce_rate_defaults(&mut self, defaults: AnnounceRateDefaults) {
1244        self.announce_rate_defaults = defaults;
1245    }
1246
1247    pub fn set_ingress_control_defaults(
1248        &mut self,
1249        defaults: rns_core::transport::types::IngressControlConfig,
1250    ) {
1251        self.ingress_control_defaults = defaults;
1252    }
1253
1254    pub(crate) fn apply_announce_rate_defaults(
1255        &self,
1256        info: &mut rns_core::transport::types::InterfaceInfo,
1257    ) {
1258        if !self.engine.transport_enabled() || info.announce_rate_target.is_some() {
1259            return;
1260        }
1261
1262        let Some(target) = self.announce_rate_defaults.target else {
1263            return;
1264        };
1265
1266        info.announce_rate_target = Some(target);
1267        if info.announce_rate_grace == 0 {
1268            info.announce_rate_grace = self.announce_rate_defaults.grace;
1269        }
1270        if info.announce_rate_penalty == 0.0 {
1271            info.announce_rate_penalty = self.announce_rate_defaults.penalty;
1272        }
1273    }
1274
1275    pub(crate) fn apply_ingress_control_defaults(
1276        &self,
1277        info: &mut rns_core::transport::types::InterfaceInfo,
1278    ) {
1279        if !self.engine.transport_enabled() {
1280            return;
1281        }
1282
1283        let baseline = if info.ingress_control.enabled {
1284            rns_core::transport::types::IngressControlConfig::enabled()
1285        } else {
1286            rns_core::transport::types::IngressControlConfig::disabled()
1287        };
1288        let defaults = self.ingress_control_defaults;
1289        let ic = &mut info.ingress_control;
1290
1291        if ic.max_held_announces == baseline.max_held_announces {
1292            ic.max_held_announces = defaults.max_held_announces;
1293        }
1294        if ic.burst_hold == baseline.burst_hold {
1295            ic.burst_hold = defaults.burst_hold;
1296        }
1297        if ic.burst_freq_new == baseline.burst_freq_new {
1298            ic.burst_freq_new = defaults.burst_freq_new;
1299        }
1300        if ic.burst_freq == baseline.burst_freq {
1301            ic.burst_freq = defaults.burst_freq;
1302        }
1303        if ic.pr_burst_freq_new == baseline.pr_burst_freq_new {
1304            ic.pr_burst_freq_new = defaults.pr_burst_freq_new;
1305        }
1306        if ic.pr_burst_freq == baseline.pr_burst_freq {
1307            ic.pr_burst_freq = defaults.pr_burst_freq;
1308        }
1309        if ic.new_time == baseline.new_time {
1310            ic.new_time = defaults.new_time;
1311        }
1312        if ic.burst_penalty == baseline.burst_penalty {
1313            ic.burst_penalty = defaults.burst_penalty;
1314        }
1315        if ic.held_release_interval == baseline.held_release_interval {
1316            ic.held_release_interval = defaults.held_release_interval;
1317        }
1318        if ic.egress_enabled == baseline.egress_enabled {
1319            ic.egress_enabled = defaults.egress_enabled;
1320        }
1321        if ic.egress_pr_freq == baseline.egress_pr_freq {
1322            ic.egress_pr_freq = defaults.egress_pr_freq;
1323        }
1324    }
1325
1326    fn wrap_interface_writer(
1327        &self,
1328        interface_id: InterfaceId,
1329        interface_name: &str,
1330        writer: Box<dyn crate::interface::Writer>,
1331    ) -> (
1332        Box<dyn crate::interface::Writer>,
1333        crate::interface::AsyncWriterMetrics,
1334    ) {
1335        crate::interface::wrap_async_writer(
1336            writer,
1337            interface_id,
1338            interface_name,
1339            self.event_tx.clone(),
1340            self.interface_writer_queue_capacity,
1341        )
1342    }
1343
1344    #[cfg(feature = "hooks")]
1345    fn provider_events_enabled(&self) -> bool {
1346        self.provider_bridge.is_some()
1347    }
1348
1349    /// Whether any program is attached at `point`. Per-packet hook points
1350    /// check this before building a hook context, which reads the clock.
1351    #[cfg(feature = "hooks")]
1352    pub(crate) fn hooks_attached(&self, point: HookPoint) -> bool {
1353        !self.hook_slots[point as usize].programs.is_empty()
1354    }
1355
1356    #[cfg(feature = "hooks")]
1357    fn run_backbone_peer_hook(
1358        &mut self,
1359        attach_point: &str,
1360        point: HookPoint,
1361        event: &BackbonePeerHookEvent,
1362    ) {
1363        let ctx = backbone_peer_hook_context(event);
1364        let now = time::now();
1365        let engine_ref = EngineRef {
1366            engine: &self.engine,
1367            interfaces: &self.interfaces,
1368            link_manager: &self.link_manager,
1369            now,
1370        };
1371        let provider_events_enabled = self.provider_events_enabled();
1372        if let Some(ref e) = run_hook_inner(
1373            &mut self.hook_slots[point as usize].programs,
1374            &self.hook_manager,
1375            &engine_ref,
1376            &ctx,
1377            now,
1378            provider_events_enabled,
1379        ) {
1380            self.forward_hook_side_effects(attach_point, e);
1381        }
1382    }
1383
1384    #[cfg(feature = "iface-backbone")]
1385    fn make_discoverable_interface(
1386        runtime: &BackboneDiscoveryRuntimeHandle,
1387    ) -> crate::discovery::DiscoverableInterface {
1388        crate::discovery::DiscoverableInterface {
1389            interface_name: runtime.interface_name.clone(),
1390            config: runtime.current.config.clone(),
1391            transport_enabled: runtime.current.transport_enabled,
1392            ifac_netname: runtime.current.ifac_netname.clone(),
1393            ifac_netkey: runtime.current.ifac_netkey.clone(),
1394        }
1395    }
1396
1397    #[cfg(feature = "iface-backbone")]
1398    fn sync_backbone_discovery_runtime(
1399        &mut self,
1400        interface_name: &str,
1401    ) -> Result<(), RuntimeConfigError> {
1402        let handle = self
1403            .backbone_discovery_runtime
1404            .get(interface_name)
1405            .ok_or(RuntimeConfigError {
1406                code: RuntimeConfigErrorCode::NotFound,
1407                message: format!("backbone interface '{}' not found", interface_name),
1408            })?
1409            .clone();
1410
1411        if handle.current.discoverable {
1412            let iface = Self::make_discoverable_interface(&handle);
1413            if let Some(announcer) = self.interface_announcer.as_mut() {
1414                announcer.upsert_interface(iface);
1415            } else if let Some(identity) = self.transport_identity.as_ref() {
1416                self.interface_announcer = Some(crate::discovery::InterfaceAnnouncer::new(
1417                    *identity.hash(),
1418                    vec![iface],
1419                ));
1420            }
1421        } else if let Some(announcer) = self.interface_announcer.as_mut() {
1422            announcer.remove_interface(interface_name);
1423            if announcer.is_empty() {
1424                self.interface_announcer = None;
1425            }
1426        }
1427
1428        Ok(())
1429    }
1430
1431    #[cfg(feature = "iface-tcp")]
1432    fn make_tcp_server_discoverable_interface(
1433        runtime: &TcpServerDiscoveryRuntimeHandle,
1434    ) -> crate::discovery::DiscoverableInterface {
1435        crate::discovery::DiscoverableInterface {
1436            interface_name: runtime.interface_name.clone(),
1437            config: runtime.current.config.clone(),
1438            transport_enabled: runtime.current.transport_enabled,
1439            ifac_netname: runtime.current.ifac_netname.clone(),
1440            ifac_netkey: runtime.current.ifac_netkey.clone(),
1441        }
1442    }
1443
1444    #[cfg(feature = "iface-tcp")]
1445    fn sync_tcp_server_discovery_runtime(
1446        &mut self,
1447        interface_name: &str,
1448    ) -> Result<(), RuntimeConfigError> {
1449        let handle = self
1450            .tcp_server_discovery_runtime
1451            .get(interface_name)
1452            .ok_or(RuntimeConfigError {
1453                code: RuntimeConfigErrorCode::NotFound,
1454                message: format!("tcp server interface '{}' not found", interface_name),
1455            })?
1456            .clone();
1457
1458        if handle.current.discoverable {
1459            let iface = Self::make_tcp_server_discoverable_interface(&handle);
1460            if let Some(announcer) = self.interface_announcer.as_mut() {
1461                announcer.upsert_interface(iface);
1462            } else if let Some(identity) = self.transport_identity.as_ref() {
1463                self.interface_announcer = Some(crate::discovery::InterfaceAnnouncer::new(
1464                    *identity.hash(),
1465                    vec![iface],
1466                ));
1467            }
1468        } else if let Some(announcer) = self.interface_announcer.as_mut() {
1469            announcer.remove_interface(interface_name);
1470            if announcer.is_empty() {
1471                self.interface_announcer = None;
1472            }
1473        }
1474
1475        Ok(())
1476    }
1477
1478    #[cfg(feature = "hooks")]
1479    fn update_hook_program<F>(
1480        &mut self,
1481        name: &str,
1482        attach_point: &str,
1483        mut update: F,
1484    ) -> Result<(), String>
1485    where
1486        F: FnMut(&mut rns_hooks::LoadedProgram),
1487    {
1488        let point_idx = crate::config::parse_hook_point(attach_point)
1489            .ok_or_else(|| format!("unknown hook point '{}'", attach_point))?;
1490        let program = self.hook_slots[point_idx]
1491            .programs
1492            .iter_mut()
1493            .find(|program| program.name == name)
1494            .ok_or_else(|| format!("hook '{}' not found at point '{}'", name, attach_point))?;
1495        update(program);
1496        Ok(())
1497    }
1498
1499    pub(crate) fn set_tick_interval_handle(&mut self, tick_interval_ms: Arc<AtomicU64>) {
1500        self.tick_interval_ms = tick_interval_ms;
1501    }
1502
1503    /// Share the node's stamper, so announces and path requests wait in the
1504    /// same priority queues as upstream's single stamper thread.
1505    pub(crate) fn set_stamper(&mut self, stamper: Arc<crate::stamper::Stamper>) {
1506        self.stamper = stamper;
1507    }
1508
1509    /// Apply the `[stamps]` config section and `[reticulum] hop_limiter`,
1510    /// as `Reticulum.__apply_config` sets the `Transport` attributes before
1511    /// transport starts (Reticulum.py, d7749b0b). The configured flags also
1512    /// become the defaults the `global.stamp_*` runtime keys reset to.
1513    ///
1514    /// Call after [`Self::set_stamper`] and
1515    /// [`Self::set_stamp_destinations_flag`], which this updates in place.
1516    pub(crate) fn apply_stamp_config(
1517        &mut self,
1518        stamps: &crate::config::StampsSection,
1519        hop_limiter: u8,
1520    ) {
1521        self.engine.set_stamp_requirements(stamps.requirements());
1522        self.engine.set_stamp_prs(stamps.stamp_prs);
1523        self.stamp_destinations
1524            .store(stamps.stamp_destinations, Ordering::Relaxed);
1525        self.stamper
1526            .set_targets(crate::stamper::StampTargets::from(stamps));
1527        self.stamper
1528            .set_path_request_timeout(stamps.path_request_stamp_timeout());
1529        self.runtime_config_defaults.stamp_destinations = stamps.stamp_destinations;
1530        self.runtime_config_defaults.stamp_path_requests = stamps.stamp_prs;
1531        self.runtime_config_defaults.stamp_link_requests = stamps.stamp_lrs;
1532        self.runtime_config_defaults.always_stamp_link_requests = stamps.always_stamp_lrs;
1533        self.runtime_config_defaults.stamps_enforced = stamps.stamps_enforced;
1534        self.runtime_config_defaults.lr_stamps_enforced = stamps.lr_stamps_enforced;
1535        // The per-destination cost table shared with the node API and the
1536        // announce builder: `lr_cost` is `Transport.LR_COST`, the cost of
1537        // destinations that call no `set_link_cost`.
1538        let link_request_costs = self.link_manager.link_request_costs();
1539        link_request_costs.set_default_cost(stamps.lr_cost);
1540        link_request_costs.set_enforced(stamps.lr_stamps_enforced);
1541        self.stamp_settings = stamps.clone();
1542        self.hop_limiter = hop_limiter;
1543        // Transport.start (Transport.py:521).
1544        if hop_limiter != rns_core::constants::PATHFINDER_M {
1545            log::debug!("Transport hop limit set to {hop_limiter}");
1546        }
1547    }
1548
1549    /// Switch announce and path request stamp enforcement
1550    /// (`Transport.STAMPS_ENFORCED`), keeping the required costs.
1551    pub(crate) fn set_stamps_enforced(&mut self, enforced: bool) {
1552        let mut requirements = self.engine.stamp_requirements();
1553        requirements.enforced = enforced;
1554        self.engine.set_stamp_requirements(requirements);
1555    }
1556
1557    /// Share the node handle's `STAMP_DESTINATIONS` flag so runtime config
1558    /// changes made here are seen by the announce API.
1559    pub(crate) fn set_stamp_destinations_flag(&mut self, flag: Arc<AtomicBool>) {
1560        self.stamp_destinations = flag;
1561    }
1562
1563    /// The flag the node handle reads to refuse new work once draining starts.
1564    pub(crate) fn accepting_work_handle(&self) -> Arc<AtomicBool> {
1565        Arc::clone(&self.accepting_work)
1566    }
1567
1568    #[allow(dead_code)]
1569    pub(crate) fn set_packet_hashlist_max_entries(&mut self, max_entries: usize) {
1570        self.engine.set_packet_hashlist_max_entries(max_entries);
1571    }
1572
1573    /// Turn a packet hook chain result into the action the hook point takes.
1574    ///
1575    /// `Drop` stops the packet. `Modify` with replacement bytes is validated
1576    /// against `max_len` and the packet decoder; a replacement that fails
1577    /// validation is treated as `Drop` and counted in
1578    /// `hook_rejected_modifications`. `Modify` without bytes, `Continue` and
1579    /// `Halt` keep the current bytes.
1580    #[cfg(feature = "hooks")]
1581    pub(crate) fn packet_hook_outcome(
1582        &mut self,
1583        point: HookPoint,
1584        exec: &rns_hooks::ExecuteResult,
1585        max_len: usize,
1586    ) -> PacketHookOutcome {
1587        let Some(result) = exec.hook_result.as_ref() else {
1588            return PacketHookOutcome::Keep;
1589        };
1590        if result.is_drop() {
1591            return PacketHookOutcome::Drop;
1592        }
1593        if rns_hooks::Verdict::from_u32(result.verdict) != Some(rns_hooks::Verdict::Modify) {
1594            return PacketHookOutcome::Keep;
1595        }
1596        let Some(data) = exec.modified_data.as_deref() else {
1597            return PacketHookOutcome::Keep;
1598        };
1599        match validate_hook_replacement(data, max_len) {
1600            Ok(packet) => PacketHookOutcome::Replace(packet),
1601            Err(reason) => {
1602                let rejected = &mut self.hook_rejected_modifications[point as usize];
1603                *rejected = rejected.saturating_add(1);
1604                log::debug!("{:?} hook: {}; dropping packet", point, reason);
1605                PacketHookOutcome::Drop
1606            }
1607        }
1608    }
1609
1610    /// Largest packet a hook may substitute for one handled on `interface`.
1611    #[cfg(feature = "hooks")]
1612    pub(crate) fn hook_packet_limit(&self, interface: InterfaceId) -> usize {
1613        self.interfaces
1614            .get(&interface)
1615            .map(|entry| entry.info.mtu as usize)
1616            .filter(|mtu| *mtu > 0)
1617            .unwrap_or(rns_core::constants::MTU)
1618    }
1619
1620    /// Point a pending confirmed link send at the replacement bytes a hook
1621    /// produced for the packet it was tracking.
1622    #[cfg(feature = "hooks")]
1623    pub(crate) fn retarget_tracked_link_send(&mut self, original: &[u8], replacement: &RawPacket) {
1624        let Some((hash, _)) = self.tracked_link_send.as_mut() else {
1625            return;
1626        };
1627        if RawPacket::unpack(original).is_ok_and(|packet| packet.packet_hash == *hash) {
1628            *hash = replacement.packet_hash;
1629        }
1630    }
1631
1632    #[cfg(feature = "hooks")]
1633    fn forward_hook_side_effects(&mut self, attach_point: &str, exec: &rns_hooks::ExecuteResult) {
1634        if !exec.injected_actions.is_empty() {
1635            self.dispatch_all(convert_injected_actions(exec.injected_actions.clone()));
1636        }
1637        if let Some(ref bridge) = self.provider_bridge {
1638            for event in &exec.provider_events {
1639                bridge.emit_event(
1640                    attach_point,
1641                    event.hook_name.clone(),
1642                    event.payload_type.clone(),
1643                    event.payload.clone(),
1644                );
1645            }
1646        }
1647    }
1648
1649    #[cfg(feature = "hooks")]
1650    fn collect_hook_side_effects(
1651        &mut self,
1652        attach_point: &str,
1653        exec: &rns_hooks::ExecuteResult,
1654        out: &mut Vec<TransportAction>,
1655    ) {
1656        if !exec.injected_actions.is_empty() {
1657            out.extend(convert_injected_actions(exec.injected_actions.clone()));
1658        }
1659        if let Some(ref bridge) = self.provider_bridge {
1660            for event in &exec.provider_events {
1661                bridge.emit_event(
1662                    attach_point,
1663                    event.hook_name.clone(),
1664                    event.payload_type.clone(),
1665                    event.payload.clone(),
1666                );
1667            }
1668        }
1669    }
1670
1671    /// Set the probe addresses, protocol, and optional device for hole punching.
1672    pub fn set_probe_config(
1673        &mut self,
1674        addrs: Vec<std::net::SocketAddr>,
1675        protocol: rns_core::holepunch::ProbeProtocol,
1676        device: Option<String>,
1677    ) {
1678        let policy = self.holepunch_manager.policy();
1679        let allow_private = self.holepunch_manager.allow_private_endpoints();
1680        self.holepunch_manager = HolePunchManager::new(addrs, protocol, device);
1681        self.holepunch_manager
1682            .set_event_sender(self.event_tx.clone());
1683        self.holepunch_manager.set_underlay_mark(self.underlay_mark);
1684        self.holepunch_manager.set_policy(policy);
1685        self.holepunch_manager
1686            .set_allow_private_endpoints(allow_private);
1687    }
1688}
1689
1690pub(crate) struct PendingLinkFrame {
1691    interface: InterfaceId,
1692    data: Vec<u8>,
1693    is_announce: bool,
1694    completion: Option<crate::link_send::Completion>,
1695}
1696
1697/// Wakes a worker thread when there is work for it, or when it should
1698/// stop, so it sleeps until then instead of polling.
1699#[derive(Default)]
1700pub(crate) struct WorkSignal {
1701    /// Work is pending; the worker is stopped.
1702    state: Mutex<(bool, bool)>,
1703    condvar: std::sync::Condvar,
1704}
1705
1706impl WorkSignal {
1707    fn lock(&self) -> std::sync::MutexGuard<'_, (bool, bool)> {
1708        self.state
1709            .lock()
1710            .unwrap_or_else(|poisoned| poisoned.into_inner())
1711    }
1712
1713    /// There is work.
1714    pub(crate) fn notify(&self) {
1715        self.lock().0 = true;
1716        self.condvar.notify_one();
1717    }
1718
1719    /// The worker should stop.
1720    pub(crate) fn stop(&self) {
1721        self.lock().1 = true;
1722        self.condvar.notify_all();
1723    }
1724
1725    pub(crate) fn stopped(&self) -> bool {
1726        self.lock().1
1727    }
1728
1729    /// Wait until there is work or the worker should stop, consuming the
1730    /// notification. Returns `false` once stopped.
1731    pub(crate) fn wait(&self) -> bool {
1732        let mut state = self.lock();
1733        while !state.0 && !state.1 {
1734            state = self
1735                .condvar
1736                .wait(state)
1737                .unwrap_or_else(|poisoned| poisoned.into_inner());
1738        }
1739        state.0 = false;
1740        !state.1
1741    }
1742}