1use std::collections::HashSet;
6use std::io;
7use std::path::Path;
8use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
9use std::sync::Arc;
10use std::thread::{self, JoinHandle};
11use std::time::Duration;
12
13use rns_core::transport::announce_verify_queue::OverflowPolicy as AnnounceQueueOverflowPolicy;
14use rns_core::transport::types::TransportConfig;
15use rns_crypto::identity::Identity;
16use rns_crypto::{OsRng, Rng};
17
18use crate::config;
19use crate::driver::{AnnounceRateDefaults, Callbacks, Driver};
20#[cfg(feature = "iface-backbone")]
21use crate::driver::{BackbonePeerPoolCandidateConfig, BackbonePeerPoolSettings};
22use crate::event::{self, Event, EventSender};
23use crate::ifac;
24#[cfg(feature = "iface-auto")]
25use crate::interface::auto::{auto_runtime_handle_from_config, AutoConfig};
26#[cfg(feature = "iface-backbone")]
27use crate::interface::backbone::{
28 client_config_from_mode, client_priority_from_mode, client_runtime_handle_from_mode,
29 peer_state_handle_from_mode, runtime_handle_from_mode, BackboneMode,
30};
31#[cfg(feature = "iface-i2p")]
32use crate::interface::i2p::{i2p_runtime_handle_from_config, I2pConfig};
33#[cfg(feature = "iface-local")]
34use crate::interface::local::LocalServerConfig;
35#[cfg(feature = "iface-pipe")]
36use crate::interface::pipe::{pipe_runtime_handle_from_config, PipeConfig};
37#[cfg(feature = "iface-rnode")]
38use crate::interface::rnode::{rnode_runtime_handle_from_config, RNodeConfig};
39#[cfg(feature = "iface-tcp")]
40use crate::interface::tcp::{tcp_client_runtime_handle_from_config, TcpClientConfig};
41#[cfg(feature = "iface-tcp")]
42use crate::interface::tcp_server::{
43 runtime_handle_from_config as tcp_runtime_handle_from_config, TcpServerConfig,
44};
45#[cfg(feature = "iface-udp")]
46use crate::interface::udp::{udp_runtime_handle_from_config, UdpConfig};
47use crate::interface::{InterfaceEntry, InterfaceStats};
48use crate::storage;
49use crate::time;
50
51#[cfg(test)]
52const DEFAULT_KNOWN_DESTINATIONS_TTL: Duration = Duration::from_secs(48 * 60 * 60);
53const DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES: usize = 8192;
54
55fn parse_interface_mode(mode: &str) -> u8 {
58 match mode.to_lowercase().as_str() {
59 "full" => rns_core::constants::MODE_FULL,
60 "access_point" | "accesspoint" | "ap" => rns_core::constants::MODE_ACCESS_POINT,
61 "pointtopoint" | "ptp" => rns_core::constants::MODE_POINT_TO_POINT,
62 "roaming" => rns_core::constants::MODE_ROAMING,
63 "boundary" => rns_core::constants::MODE_BOUNDARY,
64 "gateway" | "gw" => rns_core::constants::MODE_GATEWAY,
65 "internal" => rns_core::constants::MODE_INTERNAL,
66 _ => rns_core::constants::MODE_FULL,
67 }
68}
69
70fn parse_optional_interface_mode(mode: &str) -> Option<u8> {
71 match mode.to_lowercase().as_str() {
72 "full" => Some(rns_core::constants::MODE_FULL),
73 "access_point" | "accesspoint" | "ap" => Some(rns_core::constants::MODE_ACCESS_POINT),
74 "pointtopoint" | "ptp" => Some(rns_core::constants::MODE_POINT_TO_POINT),
75 "roaming" => Some(rns_core::constants::MODE_ROAMING),
76 "boundary" => Some(rns_core::constants::MODE_BOUNDARY),
77 "gateway" | "gw" => Some(rns_core::constants::MODE_GATEWAY),
78 "internal" => Some(rns_core::constants::MODE_INTERNAL),
79 _ => None,
80 }
81}
82
83fn parse_interface_gravity(value: Option<&str>, default_gravity: i64) -> i64 {
84 value
85 .and_then(|value| value.parse().ok())
86 .unwrap_or(default_gravity)
87}
88
89fn normalize_discovery_mode(
91 interface_type: &str,
92 configured_mode: u8,
93 discoverable: bool,
94 ignore_config_warnings: bool,
95) -> u8 {
96 if !discoverable
97 || ignore_config_warnings
98 || matches!(
99 configured_mode,
100 rns_core::constants::MODE_ACCESS_POINT
101 | rns_core::constants::MODE_GATEWAY
102 | rns_core::constants::MODE_INTERNAL
103 )
104 {
105 return configured_mode;
106 }
107
108 if matches!(interface_type, "RNodeInterface" | "RNodeMultiInterface") {
109 rns_core::constants::MODE_ACCESS_POINT
110 } else {
111 rns_core::constants::MODE_GATEWAY
112 }
113}
114
115fn default_ingress_control_for_type(
116 iface_type: &str,
117) -> rns_core::transport::types::IngressControlConfig {
118 match iface_type {
119 "AutoInterface" | "BackboneInterface" | "TCPClientInterface" | "TCPServerInterface"
120 | "UDPInterface" | "I2PInterface" => {
121 rns_core::transport::types::IngressControlConfig::enabled()
122 }
123 _ => rns_core::transport::types::IngressControlConfig::disabled(),
124 }
125}
126
127fn parse_ingress_control_config(
128 iface_type: &str,
129 params: &std::collections::HashMap<String, String>,
130) -> Result<rns_core::transport::types::IngressControlConfig, String> {
131 let mut config = default_ingress_control_for_type(iface_type);
132
133 if let Some(v) = params.get("ingress_control") {
134 config.enabled = config::parse_bool_pub(v)
135 .ok_or_else(|| format!("ingress_control must be a boolean, got '{}'", v))?;
136 }
137 if let Some(v) = params.get("ic_max_held_announces") {
138 config.max_held_announces = v
139 .parse::<usize>()
140 .map_err(|_| format!("ic_max_held_announces must be an integer, got '{}'", v))?;
141 }
142 if let Some(v) = params.get("ic_burst_hold") {
143 config.burst_hold = parse_nonnegative_f64("ic_burst_hold", v)?;
144 }
145 if let Some(v) = params.get("ic_burst_freq_new") {
146 config.burst_freq_new = parse_nonnegative_f64("ic_burst_freq_new", v)?;
147 }
148 if let Some(v) = params.get("ic_burst_freq") {
149 config.burst_freq = parse_nonnegative_f64("ic_burst_freq", v)?;
150 }
151 if let Some(v) = params.get("ic_pr_burst_freq_new") {
152 config.pr_burst_freq_new = parse_nonnegative_f64("ic_pr_burst_freq_new", v)?;
153 }
154 if let Some(v) = params.get("ic_pr_burst_freq") {
155 config.pr_burst_freq = parse_nonnegative_f64("ic_pr_burst_freq", v)?;
156 }
157 if let Some(v) = params.get("egress_control") {
158 config.egress_enabled = config::parse_bool_pub(v)
159 .ok_or_else(|| format!("egress_control must be a boolean, got '{}'", v))?;
160 }
161 if let Some(v) = params.get("ec_pr_freq") {
162 config.egress_pr_freq = parse_nonnegative_f64("ec_pr_freq", v)?;
163 }
164 if let Some(v) = params.get("ic_new_time") {
165 config.new_time = parse_nonnegative_f64("ic_new_time", v)?;
166 }
167 if let Some(v) = params.get("ic_burst_penalty") {
168 config.burst_penalty = parse_nonnegative_f64("ic_burst_penalty", v)?;
169 }
170 if let Some(v) = params.get("ic_held_release_interval") {
171 config.held_release_interval = parse_nonnegative_f64("ic_held_release_interval", v)?;
172 }
173
174 Ok(config)
175}
176
177fn parse_nonnegative_f64(key: &str, value: &str) -> Result<f64, String> {
178 let parsed = value
179 .parse::<f64>()
180 .map_err(|_| format!("{} must be numeric, got '{}'", key, value))?;
181 if parsed < 0.0 {
182 return Err(format!("{} must be >= 0, got '{}'", key, value));
183 }
184 Ok(parsed)
185}
186
187fn extract_ifac_config(
190 params: &std::collections::HashMap<String, String>,
191 default_size: usize,
192) -> Option<IfacConfig> {
193 let netname = params
194 .get("networkname")
195 .or_else(|| params.get("network_name"))
196 .cloned();
197 let netkey = params
198 .get("passphrase")
199 .or_else(|| params.get("pass_phrase"))
200 .cloned();
201
202 if netname.is_none() && netkey.is_none() {
203 return None;
204 }
205
206 let size = params
208 .get("ifac_size")
209 .and_then(|v| v.parse::<usize>().ok())
210 .map(|bits| (bits / 8).max(1))
211 .unwrap_or(default_size);
212
213 Some(IfacConfig {
214 netname,
215 netkey,
216 size,
217 })
218}
219
220fn extract_discovery_config(
222 iface_name: &str,
223 iface_type: &str,
224 params: &std::collections::HashMap<String, String>,
225) -> Option<crate::discovery::DiscoveryConfig> {
226 let discoverable = params
227 .get("discoverable")
228 .and_then(|v| config::parse_bool_pub(v))
229 .unwrap_or(false);
230 if !discoverable {
231 return None;
232 }
233
234 if iface_type == "TCPClientInterface" {
235 log::error!(
236 "Invalid interface discovery configuration for {}, aborting discovery announce",
237 iface_name
238 );
239 return None;
240 }
241
242 let discovery_name = params
243 .get("discovery_name")
244 .cloned()
245 .unwrap_or_else(|| iface_name.to_string());
246
247 let announce_interval = params
249 .get("announce_interval")
250 .and_then(|v| v.parse::<u64>().ok())
251 .map(|secs| secs.max(300))
252 .unwrap_or(21600);
253
254 let stamp_value = params
255 .get("discovery_stamp_value")
256 .and_then(|v| v.parse::<u8>().ok())
257 .unwrap_or(crate::discovery::DEFAULT_STAMP_VALUE);
258
259 let reachable_on = params.get("reachable_on").cloned();
260
261 let listen_port = params
262 .get("listen_port")
263 .or_else(|| params.get("port"))
264 .and_then(|v| v.parse().ok());
265
266 let location_cmd = params.get("location_cmd").cloned();
267
268 let latitude = params
269 .get("latitude")
270 .or_else(|| params.get("lat"))
271 .and_then(|v| v.parse().ok());
272 let longitude = params
273 .get("longitude")
274 .or_else(|| params.get("lon"))
275 .and_then(|v| v.parse().ok());
276 let height = params.get("height").and_then(|v| v.parse().ok());
277
278 Some(crate::discovery::DiscoveryConfig {
279 discovery_name,
280 announce_interval,
281 stamp_value,
282 reachable_on,
283 interface_type: iface_type.to_string(),
284 listen_port,
285 location_cmd,
286 latitude,
287 longitude,
288 height,
289 })
290}
291
292fn default_discovery_runtime_config(
293 interface_name: &str,
294 interface_type: &str,
295 listen_port: Option<u16>,
296) -> crate::discovery::DiscoveryConfig {
297 crate::discovery::DiscoveryConfig {
298 discovery_name: interface_name.to_string(),
299 announce_interval: 21600,
300 stamp_value: crate::discovery::DEFAULT_STAMP_VALUE,
301 reachable_on: None,
302 interface_type: interface_type.to_string(),
303 listen_port,
304 location_cmd: None,
305 latitude: None,
306 longitude: None,
307 height: None,
308 }
309}
310
311fn discovery_runtime_ifac_fields(ifac: Option<&IfacConfig>) -> (Option<String>, Option<String>) {
312 (
313 ifac.and_then(|cfg| cfg.netname.clone()),
314 ifac.and_then(|cfg| cfg.netkey.clone()),
315 )
316}
317
318#[cfg(feature = "iface-backbone")]
319fn backbone_discovery_runtime_from_interface(
320 interface_name: &str,
321 mode: &BackboneMode,
322 discovery: Option<&crate::discovery::DiscoveryConfig>,
323 transport_enabled: bool,
324 ifac: Option<&IfacConfig>,
325) -> Option<crate::driver::BackboneDiscoveryRuntimeHandle> {
326 let config = match mode {
327 BackboneMode::Server(config) => config,
328 BackboneMode::Client(_, _) => return None,
329 };
330
331 let startup_config = discovery.cloned().unwrap_or_else(|| {
332 default_discovery_runtime_config(
333 interface_name,
334 "BackboneInterface",
335 Some(config.listen_port),
336 )
337 });
338 let (ifac_netname, ifac_netkey) = discovery_runtime_ifac_fields(ifac);
339
340 Some(crate::driver::BackboneDiscoveryRuntimeHandle::from_parts(
341 config.name.clone(),
342 startup_config,
343 transport_enabled,
344 ifac_netname,
345 ifac_netkey,
346 discovery.is_some(),
347 ))
348}
349
350#[cfg(feature = "iface-tcp")]
351fn tcp_server_discovery_runtime_from_interface(
352 interface_name: &str,
353 config: &crate::interface::tcp_server::TcpServerConfig,
354 discovery: Option<&crate::discovery::DiscoveryConfig>,
355 transport_enabled: bool,
356 ifac: Option<&IfacConfig>,
357) -> crate::driver::TcpServerDiscoveryRuntimeHandle {
358 let startup_config = discovery.cloned().unwrap_or_else(|| {
359 default_discovery_runtime_config(
360 interface_name,
361 "TCPServerInterface",
362 Some(config.listen_port),
363 )
364 });
365 let (ifac_netname, ifac_netkey) = discovery_runtime_ifac_fields(ifac);
366
367 crate::driver::TcpServerDiscoveryRuntimeHandle::from_parts(
368 config.name.clone(),
369 startup_config,
370 transport_enabled,
371 ifac_netname,
372 ifac_netkey,
373 discovery.is_some(),
374 )
375}
376
377fn ifac_runtime_from_config(
378 ifac: Option<&IfacConfig>,
379 default_size: usize,
380) -> crate::driver::IfacRuntimeConfig {
381 crate::driver::IfacRuntimeConfig::from_parts(
382 ifac.and_then(|cfg| cfg.netname.clone()),
383 ifac.and_then(|cfg| cfg.netkey.clone()),
384 ifac.map(|cfg| cfg.size).unwrap_or(default_size),
385 )
386}
387
388fn discoverable_interface_from_config(
389 interface_name: &str,
390 discovery: &crate::discovery::DiscoveryConfig,
391 transport_enabled: bool,
392 ifac: Option<&IfacConfig>,
393) -> crate::discovery::DiscoverableInterface {
394 crate::discovery::DiscoverableInterface {
395 interface_name: interface_name.to_string(),
396 config: discovery.clone(),
397 transport_enabled,
398 ifac_netname: ifac.and_then(|cfg| cfg.netname.clone()),
399 ifac_netkey: ifac.and_then(|cfg| cfg.netkey.clone()),
400 }
401}
402
403fn derive_ifac_state(
404 ifac: Option<&IfacConfig>,
405 interface_name: &str,
406) -> io::Result<Option<crate::ifac::IfacState>> {
407 let Some(ifac) = ifac else {
408 return Ok(None);
409 };
410 if ifac.netname.is_none() && ifac.netkey.is_none() {
411 return Ok(None);
412 }
413
414 ifac::derive_ifac(ifac.netname.as_deref(), ifac.netkey.as_deref(), ifac.size)
415 .map(Some)
416 .map_err(|err| {
417 io::Error::other(format!(
418 "failed to derive IFAC for {}: {}",
419 interface_name, err
420 ))
421 })
422}
423
424fn register_started_interface(
425 driver: &mut Driver,
426 tx: &EventSender,
427 queue_capacity: usize,
428 id: rns_core::transport::types::InterfaceId,
429 mut info: rns_core::transport::types::InterfaceInfo,
430 writer: Box<dyn crate::interface::Writer>,
431 interface_type_name: String,
432 ifac_state: Option<crate::ifac::IfacState>,
433 ifac_runtime: &crate::driver::IfacRuntimeConfig,
434) {
435 driver.apply_announce_rate_defaults(&mut info);
436 driver.apply_ingress_control_defaults(&mut info);
437 let (writer, async_writer_metrics) =
438 crate::interface::wrap_async_writer(writer, id, &info.name, tx.clone(), queue_capacity);
439 driver.register_interface_runtime_defaults(&info);
440 driver.register_interface_ifac_runtime(&info.name, ifac_runtime.clone());
441 driver.engine.register_interface(info.clone());
442 driver.interfaces.insert(
443 id,
444 InterfaceEntry {
445 id,
446 info,
447 writer,
448 async_writer_metrics: Some(async_writer_metrics),
449 enabled: true,
450 online: false,
451 dynamic: false,
452 ifac: ifac_state,
453 stats: InterfaceStats {
454 started: time::now(),
455 ..Default::default()
456 },
457 interface_type: interface_type_name,
458 send_retry_at: None,
459 send_retry_backoff: Duration::ZERO,
460 },
461 );
462}
463
464pub struct NodeConfig {
466 pub transport_enabled: bool,
467 pub static_transport_identity: bool,
468 pub local_hops_delta: bool,
469 pub identity: Option<Identity>,
470 pub interfaces: Vec<InterfaceConfig>,
472 pub share_instance: bool,
474 pub instance_name: String,
476 pub shared_instance_port: u16,
478 pub rpc_port: u16,
480 pub cache_dir: Option<std::path::PathBuf>,
482 pub ratchet_store: Option<Arc<dyn storage::RatchetStore>>,
484 pub ratchet_expiry: Duration,
486 pub management: crate::management::ManagementConfig,
488 pub probe_port: Option<u16>,
490 pub probe_addrs: Vec<std::net::SocketAddr>,
492 pub probe_protocol: rns_core::holepunch::ProbeProtocol,
494 pub device: Option<String>,
496 pub hooks: Vec<config::ParsedHook>,
498 pub discover_interfaces: bool,
500 pub autoconnect_interface_mode: Option<u8>,
502 pub autoconnect_interface_gravity: i64,
504 pub autoconnect_announces_to_internal: bool,
506 pub discovery_required_value: Option<u8>,
508 pub respond_to_probes: bool,
510 pub prefer_shorter_path: bool,
514 pub max_paths_per_destination: usize,
517 pub packet_hashlist_max_entries: usize,
519 pub max_discovery_pr_tags: usize,
521 pub max_path_destinations: usize,
523 pub max_tunnel_destinations_total: usize,
525 pub known_destinations_ttl: Duration,
527 pub known_destinations_max_entries: usize,
529 pub announce_table_ttl: Duration,
531 pub announce_table_max_bytes: usize,
533 pub driver_event_queue_capacity: usize,
535 pub interface_writer_queue_capacity: usize,
537 pub announce_rate_defaults: AnnounceRateDefaults,
539 pub ingress_control_defaults: rns_core::transport::types::IngressControlConfig,
541 #[cfg(feature = "iface-backbone")]
543 pub backbone_peer_pool: Option<BackbonePeerPoolSettings>,
544 pub announce_sig_cache_enabled: bool,
546 pub announce_sig_cache_max_entries: usize,
548 pub announce_sig_cache_ttl: Duration,
550 pub registry: Option<crate::interface::registry::InterfaceRegistry>,
552 pub panic_on_interface_error: bool,
555 #[cfg(feature = "hooks")]
557 pub provider_bridge: Option<crate::provider_bridge::ProviderBridgeConfig>,
558}
559
560impl Default for NodeConfig {
561 fn default() -> Self {
562 Self {
563 transport_enabled: false,
564 static_transport_identity: false,
565 local_hops_delta: false,
566 identity: None,
567 interfaces: Vec::new(),
568 share_instance: false,
569 instance_name: "default".into(),
570 shared_instance_port: 37428,
571 rpc_port: 0,
572 cache_dir: None,
573 ratchet_store: None,
574 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
575 management: Default::default(),
576 probe_port: None,
577 probe_addrs: vec![],
578 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
579 device: None,
580 hooks: Vec::new(),
581 discover_interfaces: false,
582 autoconnect_interface_mode: None,
583 autoconnect_interface_gravity: 0,
584 autoconnect_announces_to_internal: false,
585 discovery_required_value: None,
586 respond_to_probes: false,
587 prefer_shorter_path: false,
588 max_paths_per_destination: 1,
589 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
590 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
591 max_path_destinations: rns_core::transport::types::DEFAULT_MAX_PATH_DESTINATIONS,
592 max_tunnel_destinations_total: usize::MAX,
593 known_destinations_ttl: Duration::from_secs(48 * 60 * 60),
594 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
595 announce_table_ttl: Duration::from_secs(rns_core::constants::ANNOUNCE_TABLE_TTL as u64),
596 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
597 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
598 interface_writer_queue_capacity: crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
599 announce_rate_defaults: AnnounceRateDefaults::default(),
600 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(),
601 #[cfg(feature = "iface-backbone")]
602 backbone_peer_pool: None,
603 announce_sig_cache_enabled: true,
604 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
605 announce_sig_cache_ttl: Duration::from_secs(
606 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
607 ),
608 registry: None,
609 panic_on_interface_error: false,
610 #[cfg(feature = "hooks")]
611 provider_bridge: None,
612 }
613 }
614}
615
616pub struct IfacConfig {
618 pub netname: Option<String>,
619 pub netkey: Option<String>,
620 pub size: usize,
621}
622
623pub struct InterfaceConfig {
625 pub name: String,
626 pub type_name: String,
627 pub config_data: Box<dyn crate::interface::InterfaceConfigData>,
628 pub mode: u8,
629 pub gravity: i64,
630 pub recursive_prs: bool,
631 pub announces_from_internal: bool,
632 pub announces_to_internal: Option<bool>,
633 pub ingress_control: rns_core::transport::types::IngressControlConfig,
634 pub ifac: Option<IfacConfig>,
635 pub discovery: Option<crate::discovery::DiscoveryConfig>,
636}
637
638use crate::event::{QueryRequest, QueryResponse};
639
640#[derive(Debug)]
642pub struct SendError;
643
644impl std::fmt::Display for SendError {
645 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
646 write!(f, "driver shut down")
647 }
648}
649
650impl std::error::Error for SendError {}
651
652pub struct RnsNode {
654 tx: EventSender,
655 driver_handle: Option<JoinHandle<()>>,
656 verify_handle: Option<JoinHandle<()>>,
657 verify_shutdown: Arc<AtomicBool>,
658 persist_handle: Option<JoinHandle<()>>,
659 persist_stop: Option<std::sync::mpsc::Sender<()>>,
660 rpc_server: Option<crate::rpc::RpcServer>,
661 tick_interval_ms: Arc<AtomicU64>,
662 #[allow(dead_code)]
663 probe_server: Option<crate::holepunch::probe::ProbeServerHandle>,
664 known_destinations_path: Option<std::path::PathBuf>,
665 transport_state_dir: Option<std::path::PathBuf>,
666 ratchet_store: Option<Arc<dyn storage::RatchetStore>>,
667 ratchet_expiry: Duration,
668}
669
670impl RnsNode {
671 pub fn from_config(
674 config_path: Option<&Path>,
675 callbacks: Box<dyn Callbacks>,
676 ) -> io::Result<Self> {
677 let config_dir = storage::resolve_config_dir(config_path);
678 let paths = storage::ensure_storage_dirs(&config_dir)?;
679 let known_destinations_path = paths.storage.join("known_destinations");
680 let ratchet_store: Arc<dyn storage::RatchetStore> =
681 Arc::new(storage::FsRatchetStore::new(paths.ratchets.clone()));
682
683 let config_file = config_dir.join("config");
685 let rns_config = if config_file.exists() {
686 config::parse_file(&config_file)
687 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("{}", e)))?
688 } else {
689 config::parse("")
691 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("{}", e)))?
692 };
693
694 let identity = if let Some(ref id_path_str) = rns_config.reticulum.network_identity {
696 let id_path = std::path::PathBuf::from(id_path_str);
697 if id_path.exists() {
698 storage::load_identity(&id_path)?
699 } else {
700 let id = Identity::new(&mut OsRng);
701 storage::save_identity(&id, &id_path)?;
702 id
703 }
704 } else {
705 storage::load_or_create_identity(&paths.identities)?
706 };
707
708 let registry = crate::interface::registry::InterfaceRegistry::with_builtins();
710 let mut interface_configs = Vec::new();
711 let mut next_id_val = 1u64;
712
713 for iface in &rns_config.interfaces {
714 if !iface.enabled {
715 continue;
716 }
717
718 let iface_id = rns_core::transport::types::InterfaceId(next_id_val);
719 next_id_val += 1;
720
721 let factory = match registry.get(&iface.interface_type) {
722 Some(f) => f,
723 None => {
724 log::warn!(
725 "Unsupported interface type '{}' for '{}'",
726 iface.interface_type,
727 iface.name,
728 );
729 continue;
730 }
731 };
732
733 let mut iface_mode = parse_interface_mode(&iface.mode);
734
735 let discoverable = iface
736 .params
737 .get("discoverable")
738 .and_then(|value| config::parse_bool_pub(value))
739 .unwrap_or(false);
740 let ignore_config_warnings = iface
741 .params
742 .get("ignore_config_warnings")
743 .and_then(|value| config::parse_bool_pub(value))
744 .unwrap_or(false);
745 let normalized_mode = normalize_discovery_mode(
746 &iface.interface_type,
747 iface_mode,
748 discoverable,
749 ignore_config_warnings,
750 );
751 if normalized_mode != iface_mode {
752 log::info!(
753 "Interface '{}' has discovery enabled, auto-configuring mode to {}",
754 iface.name,
755 if normalized_mode == rns_core::constants::MODE_ACCESS_POINT {
756 "ACCESS_POINT"
757 } else {
758 "GATEWAY"
759 }
760 );
761 iface_mode = normalized_mode;
762 }
763
764 let default_ifac_size = factory.default_ifac_size();
765 let ifac_config = extract_ifac_config(&iface.params, default_ifac_size);
766 let discovery_config =
767 extract_discovery_config(&iface.name, &iface.interface_type, &iface.params);
768 let ingress_control =
769 match parse_ingress_control_config(&iface.interface_type, &iface.params) {
770 Ok(config) => config,
771 Err(e) => {
772 log::warn!(
773 "Failed to parse ingress control config for '{}': {}",
774 iface.name,
775 e
776 );
777 continue;
778 }
779 };
780 let recursive_prs = iface
781 .params
782 .get("recursive_prs")
783 .and_then(|v| config::parse_bool_pub(v))
784 .unwrap_or(false);
785 let announces_from_internal = iface
786 .params
787 .get("announces_from_internal")
788 .and_then(|v| config::parse_bool_pub(v))
789 .unwrap_or(true);
790 let gravity = parse_interface_gravity(
791 iface.params.get("gravity").map(String::as_str),
792 rns_config.reticulum.default_gravity,
793 );
794 let announces_to_internal = iface
795 .params
796 .get("announces_to_internal")
797 .and_then(|v| config::parse_bool_pub(v));
798
799 let mut params = iface.params.clone();
801 if !params.contains_key("storage_dir") {
802 params.insert(
803 "storage_dir".to_string(),
804 paths.storage.to_string_lossy().to_string(),
805 );
806 }
807 if let Some(ref device) = rns_config.reticulum.device {
809 if !params.contains_key("device") {
810 params.insert("device".to_string(), device.clone());
811 }
812 }
813
814 let config_data = match factory.parse_config_section(
815 &iface.name,
816 iface_id,
817 crate::interface::ConfigSection {
818 params: ¶ms,
819 children: &iface.subinterfaces,
820 },
821 ) {
822 Ok(data) => data,
823 Err(e) => {
824 log::warn!("Failed to parse config for '{}': {}", iface.name, e);
825 continue;
826 }
827 };
828
829 interface_configs.push(InterfaceConfig {
830 name: iface.name.clone(),
831 type_name: iface.interface_type.clone(),
832 config_data,
833 mode: iface_mode,
834 gravity,
835 recursive_prs,
836 announces_from_internal,
837 announces_to_internal,
838 ingress_control,
839 ifac: ifac_config,
840 discovery: discovery_config,
841 });
842 }
843
844 let mut mgmt_allowed = Vec::new();
846 for hex_hash in &rns_config.reticulum.remote_management_allowed {
847 if hex_hash.len() == 32 {
848 if let Ok(bytes) = (0..hex_hash.len())
849 .step_by(2)
850 .map(|i| u8::from_str_radix(&hex_hash[i..i + 2], 16))
851 .collect::<Result<Vec<u8>, _>>()
852 {
853 if bytes.len() == 16 {
854 let mut h = [0u8; 16];
855 h.copy_from_slice(&bytes);
856 mgmt_allowed.push(h);
857 }
858 } else {
859 log::warn!("Invalid hex in remote_management_allowed: {}", hex_hash);
860 }
861 } else {
862 log::warn!(
863 "Invalid entry in remote_management_allowed (expected 32 hex chars, got {}): {}",
864 hex_hash.len(), hex_hash,
865 );
866 }
867 }
868
869 let probe_addrs: Vec<std::net::SocketAddr> = rns_config
871 .reticulum
872 .probe_addr
873 .as_ref()
874 .map(|s| {
875 s.split(',')
876 .filter_map(|entry| {
877 let trimmed = entry.trim();
878 if trimmed.is_empty() {
879 return None;
880 }
881 trimmed
882 .parse::<std::net::SocketAddr>()
883 .map_err(|e| {
884 log::warn!("Invalid probe_addr entry '{}': {}", trimmed, e);
885 e
886 })
887 .ok()
888 })
889 .collect()
890 })
891 .unwrap_or_default();
892
893 let probe_protocol = match rns_config
895 .reticulum
896 .probe_protocol
897 .as_deref()
898 .map(|s| s.to_lowercase())
899 {
900 Some(ref s) if s == "stun" => rns_core::holepunch::ProbeProtocol::Stun,
901 _ => rns_core::holepunch::ProbeProtocol::Rnsp,
902 };
903
904 let known_destinations = match storage::load_known_destinations(&known_destinations_path) {
905 Ok(destinations) => destinations,
906 Err(err) if err.kind() == io::ErrorKind::NotFound => Default::default(),
907 Err(err) => {
908 log::warn!("failed to load known destinations: {}", err);
909 Default::default()
910 }
911 };
912 let known_destination_hashes: HashSet<[u8; 16]> =
913 known_destinations.keys().copied().collect();
914 match ratchet_store.cleanup(
915 &known_destination_hashes,
916 time::now(),
917 rns_config.reticulum.ratchet_expiry as f64,
918 ) {
919 Ok(stats) if stats.processed > 0 => log::debug!(
920 "Processed {} ratchets, not in use {}, removed {}",
921 stats.processed,
922 stats.not_known,
923 stats.removed
924 ),
925 Ok(_) => {}
926 Err(err) => log::warn!("failed to clean ratchets: {}", err),
927 }
928
929 let node_config = NodeConfig {
930 transport_enabled: rns_config.reticulum.enable_transport,
931 static_transport_identity: rns_config.reticulum.static_transport_identity,
932 local_hops_delta: rns_config.reticulum.local_hops_delta,
933 identity: Some(identity),
934 share_instance: rns_config.reticulum.share_instance,
935 instance_name: rns_config.reticulum.instance_name.clone(),
936 shared_instance_port: rns_config.reticulum.shared_instance_port,
937 rpc_port: rns_config.reticulum.instance_control_port,
938 cache_dir: Some(paths.cache),
939 ratchet_store: Some(Arc::clone(&ratchet_store)),
940 ratchet_expiry: Duration::from_secs(rns_config.reticulum.ratchet_expiry),
941 management: crate::management::ManagementConfig {
942 enable_remote_management: rns_config.reticulum.enable_remote_management,
943 remote_management_allowed: mgmt_allowed,
944 publish_blackhole: rns_config.reticulum.publish_blackhole,
945 },
946 probe_port: rns_config.reticulum.probe_port,
947 probe_addrs,
948 probe_protocol,
949 device: rns_config.reticulum.device.clone(),
950 hooks: rns_config.hooks.clone(),
951 discover_interfaces: rns_config.reticulum.discover_interfaces,
952 autoconnect_interface_mode: rns_config
953 .reticulum
954 .autoconnect_interface_mode
955 .as_deref()
956 .and_then(parse_optional_interface_mode),
957 autoconnect_interface_gravity: rns_config.reticulum.autoconnect_interface_gravity,
958 autoconnect_announces_to_internal: rns_config
959 .reticulum
960 .autoconnect_announces_to_internal,
961 discovery_required_value: rns_config.reticulum.required_discovery_value,
962 respond_to_probes: rns_config.reticulum.respond_to_probes,
963 prefer_shorter_path: rns_config.reticulum.prefer_shorter_path,
964 max_paths_per_destination: rns_config.reticulum.max_paths_per_destination,
965 packet_hashlist_max_entries: rns_config.reticulum.packet_hashlist_max_entries,
966 max_discovery_pr_tags: rns_config.reticulum.max_discovery_pr_tags,
967 max_path_destinations: rns_config.reticulum.max_path_destinations,
968 max_tunnel_destinations_total: rns_config.reticulum.max_tunnel_destinations_total,
969 known_destinations_ttl: Duration::from_secs(
970 rns_config.reticulum.known_destinations_ttl,
971 ),
972 known_destinations_max_entries: rns_config.reticulum.known_destinations_max_entries,
973 announce_table_ttl: Duration::from_secs(rns_config.reticulum.announce_table_ttl),
974 announce_table_max_bytes: rns_config.reticulum.announce_table_max_bytes,
975 driver_event_queue_capacity: rns_config.reticulum.driver_event_queue_capacity,
976 interface_writer_queue_capacity: rns_config.reticulum.interface_writer_queue_capacity,
977 announce_rate_defaults: AnnounceRateDefaults {
978 target: rns_config.reticulum.default_ar_target,
979 penalty: rns_config.reticulum.default_ar_penalty,
980 grace: rns_config.reticulum.default_ar_grace,
981 },
982 ingress_control_defaults: rns_core::transport::types::IngressControlConfig {
983 enabled: true,
984 egress_enabled: rns_config.reticulum.default_egress_control,
985 max_held_announces: rns_config.reticulum.default_ic_max_held_announces,
986 burst_freq_new: rns_config.reticulum.default_ic_burst_freq_new,
987 burst_freq: rns_config.reticulum.default_ic_burst_freq,
988 pr_burst_freq_new: rns_config.reticulum.default_ic_pr_burst_freq_new,
989 pr_burst_freq: rns_config.reticulum.default_ic_pr_burst_freq,
990 egress_pr_freq: rns_config.reticulum.default_ec_pr_freq,
991 new_time: rns_config.reticulum.default_ic_new_time,
992 burst_hold: rns_config.reticulum.default_ic_burst_hold,
993 burst_penalty: rns_config.reticulum.default_ic_burst_penalty,
994 held_release_interval: rns_config.reticulum.default_ic_held_release_interval,
995 },
996 #[cfg(feature = "iface-backbone")]
997 backbone_peer_pool: if rns_config.reticulum.backbone_peer_pool_max_connected > 0 {
998 Some(BackbonePeerPoolSettings {
999 max_connected: rns_config.reticulum.backbone_peer_pool_max_connected,
1000 failure_threshold: rns_config.reticulum.backbone_peer_pool_failure_threshold,
1001 failure_window: Duration::from_secs(
1002 rns_config.reticulum.backbone_peer_pool_failure_window,
1003 ),
1004 cooldown: Duration::from_secs(rns_config.reticulum.backbone_peer_pool_cooldown),
1005 })
1006 } else {
1007 None
1008 },
1009 announce_sig_cache_enabled: rns_config.reticulum.announce_sig_cache_enabled,
1010 announce_sig_cache_max_entries: rns_config.reticulum.announce_sig_cache_max_entries,
1011 announce_sig_cache_ttl: Duration::from_secs(
1012 rns_config.reticulum.announce_sig_cache_ttl,
1013 ),
1014 interfaces: interface_configs,
1015 registry: None,
1016 panic_on_interface_error: rns_config.reticulum.panic_on_interface_error,
1017 #[cfg(feature = "hooks")]
1018 provider_bridge: if rns_config.reticulum.provider_bridge {
1019 Some(crate::provider_bridge::ProviderBridgeConfig {
1020 enabled: true,
1021 socket_path: rns_config
1022 .reticulum
1023 .provider_socket_path
1024 .as_ref()
1025 .map(std::path::PathBuf::from)
1026 .unwrap_or_else(|| config_dir.join("provider.sock")),
1027 queue_max_events: rns_config.reticulum.provider_queue_max_events,
1028 queue_max_bytes: rns_config.reticulum.provider_queue_max_bytes,
1029 overflow_policy: match rns_config.reticulum.provider_overflow_policy.as_str() {
1030 "drop_oldest" => crate::provider_bridge::OverflowPolicy::DropOldest,
1031 _ => crate::provider_bridge::OverflowPolicy::DropNewest,
1032 },
1033 node_instance: rns_config.reticulum.instance_name.clone(),
1034 })
1035 } else {
1036 None
1037 },
1038 };
1039
1040 let mut node = Self::start_with_announce_queue_max_entries(
1041 node_config,
1042 callbacks,
1043 rns_config.reticulum.announce_queue_max_entries,
1044 rns_config.reticulum.announce_queue_max_interfaces,
1045 rns_config.reticulum.announce_queue_max_bytes,
1046 rns_config.reticulum.announce_queue_ttl as f64,
1047 match rns_config.reticulum.announce_queue_overflow_policy.as_str() {
1048 "drop_newest" => AnnounceQueueOverflowPolicy::DropNewest,
1049 "drop_oldest" => AnnounceQueueOverflowPolicy::DropOldest,
1050 _ => AnnounceQueueOverflowPolicy::DropWorst,
1051 },
1052 )?;
1053
1054 node.known_destinations_path = Some(known_destinations_path.clone());
1055 for (dest_hash, known) in known_destinations {
1056 let _ = node.query(QueryRequest::RestoreKnownDestination(
1057 crate::event::KnownDestinationEntry {
1058 dest_hash,
1059 identity_hash: known.identity_hash,
1060 public_key: known.public_key,
1061 app_data: known.app_data,
1062 hops: known.hops,
1063 received_at: known.received_at,
1064 receiving_interface: rns_core::transport::types::InterfaceId(
1065 known.receiving_interface,
1066 ),
1067 was_used: known.was_used,
1068 last_used_at: known.last_used_at,
1069 retained: known.retained,
1070 },
1071 ));
1072 }
1073
1074 Ok(node)
1075 }
1076
1077 pub fn start(config: NodeConfig, callbacks: Box<dyn Callbacks>) -> io::Result<Self> {
1079 Self::start_with_announce_queue_max_entries(
1080 config,
1081 callbacks,
1082 256,
1083 1024,
1084 256 * 1024,
1085 30.0,
1086 AnnounceQueueOverflowPolicy::DropWorst,
1087 )
1088 }
1089
1090 fn start_with_announce_queue_max_entries(
1091 config: NodeConfig,
1092 callbacks: Box<dyn Callbacks>,
1093 announce_queue_max_entries: usize,
1094 announce_queue_max_interfaces: usize,
1095 announce_queue_max_bytes: usize,
1096 announce_queue_ttl_secs: f64,
1097 announce_queue_overflow_policy: AnnounceQueueOverflowPolicy,
1098 ) -> io::Result<Self> {
1099 let transport_state_dir = if config.transport_enabled {
1100 config.cache_dir.as_ref().map(|cache_dir| {
1101 cache_dir
1102 .parent()
1103 .unwrap_or_else(|| std::path::Path::new("."))
1104 .join("storage")
1105 })
1106 } else {
1107 None
1108 };
1109 let identity = config.identity.unwrap_or_else(|| Identity::new(&mut OsRng));
1110 let transport_identity = if config.transport_enabled || config.static_transport_identity {
1111 Identity::from_private_key(
1112 &identity
1113 .get_private_key()
1114 .expect("node identity must expose a private key"),
1115 )
1116 } else {
1117 Identity::new(&mut OsRng)
1118 };
1119
1120 let local_hops_delta = if config.local_hops_delta {
1121 let mut byte = [0u8; 1];
1122 OsRng.fill_bytes(&mut byte);
1123 byte[0] % 6 + 2
1124 } else {
1125 0
1126 };
1127
1128 let transport_config = TransportConfig {
1129 transport_enabled: config.transport_enabled,
1130 identity_hash: Some(*transport_identity.hash()),
1131 local_hops_delta,
1132 prefer_shorter_path: config.prefer_shorter_path,
1133 max_paths_per_destination: config.max_paths_per_destination,
1134 packet_hashlist_max_entries: config.packet_hashlist_max_entries,
1135 max_discovery_pr_tags: config.max_discovery_pr_tags,
1136 max_path_destinations: config.max_path_destinations,
1137 max_tunnel_destinations_total: config.max_tunnel_destinations_total,
1138 destination_timeout_secs: config.known_destinations_ttl.as_secs_f64(),
1139 announce_table_ttl_secs: config.announce_table_ttl.as_secs_f64(),
1140 announce_table_max_bytes: config.announce_table_max_bytes,
1141 announce_sig_cache_enabled: config.announce_sig_cache_enabled,
1142 announce_sig_cache_max_entries: config.announce_sig_cache_max_entries,
1143 announce_sig_cache_ttl_secs: config.announce_sig_cache_ttl.as_secs_f64(),
1144 announce_queue_max_entries,
1145 announce_queue_max_interfaces,
1146 };
1147
1148 let (tx, rx) = event::channel_with_capacity(config.driver_event_queue_capacity);
1149 let tick_interval_ms = Arc::new(AtomicU64::new(1000));
1150 let mut driver = Driver::new(transport_config, rx, tx.clone(), callbacks);
1151 driver.set_announce_verify_queue_config(
1152 announce_queue_max_entries,
1153 announce_queue_max_bytes,
1154 announce_queue_ttl_secs,
1155 announce_queue_overflow_policy,
1156 );
1157 driver.async_announce_verification = true;
1158 driver.set_tick_interval_handle(Arc::clone(&tick_interval_ms));
1159 driver.set_packet_hashlist_max_entries(config.packet_hashlist_max_entries);
1160 driver.known_destinations_ttl = config.known_destinations_ttl.as_secs_f64();
1161 driver.known_destinations_max_entries = config.known_destinations_max_entries;
1162 driver.ratchet_store = config.ratchet_store.clone();
1163 driver.ratchet_expiry_secs = config.ratchet_expiry.as_secs_f64();
1164 driver.interface_writer_queue_capacity = config.interface_writer_queue_capacity;
1165 driver.set_announce_rate_defaults(config.announce_rate_defaults);
1166 driver.set_ingress_control_defaults(config.ingress_control_defaults);
1167 driver.runtime_config_defaults.known_destinations_ttl =
1168 config.known_destinations_ttl.as_secs_f64();
1169 #[cfg(feature = "hooks")]
1170 if let Some(provider_config) = config.provider_bridge.clone() {
1171 driver.runtime_config_defaults.provider_queue_max_events =
1172 provider_config.queue_max_events;
1173 driver.runtime_config_defaults.provider_queue_max_bytes =
1174 provider_config.queue_max_bytes;
1175 if provider_config.enabled {
1176 match crate::provider_bridge::ProviderBridge::start(provider_config) {
1177 Ok(bridge) => driver.provider_bridge = Some(bridge),
1178 Err(err) => log::warn!("failed to start provider bridge: {}", err),
1179 }
1180 }
1181 }
1182
1183 if let Some(ref cache_dir) = config.cache_dir {
1185 let announces_dir = cache_dir.join("announces");
1186 let _ = std::fs::create_dir_all(&announces_dir);
1187 driver.announce_cache = Some(crate::announce_cache::AnnounceCache::new(announces_dir));
1188 }
1189
1190 if !config.probe_addrs.is_empty() || config.device.is_some() {
1192 driver.set_probe_config(
1193 config.probe_addrs.clone(),
1194 config.probe_protocol,
1195 config.device.clone(),
1196 );
1197 }
1198
1199 let probe_server = if let Some(port) = config.probe_port {
1201 let listen_addr: std::net::SocketAddr = ([0, 0, 0, 0], port).into();
1202 match crate::holepunch::probe::start_probe_server(listen_addr) {
1203 Ok(handle) => {
1204 log::info!("Probe server started on 0.0.0.0:{}", port);
1205 Some(handle)
1206 }
1207 Err(e) => {
1208 log::error!("Failed to start probe server on port {}: {}", port, e);
1209 None
1210 }
1211 }
1212 } else {
1213 None
1214 };
1215
1216 driver.management_config = config.management.clone();
1218
1219 driver.transport_identity = Some(transport_identity);
1221
1222 #[cfg(feature = "hooks")]
1224 {
1225 for hook_cfg in &config.hooks {
1226 if !hook_cfg.enabled {
1227 continue;
1228 }
1229 let point_idx = match config::parse_hook_point(&hook_cfg.attach_point) {
1230 Some(idx) => idx,
1231 None => {
1232 log::warn!(
1233 "Unknown hook point '{}' for hook '{}'",
1234 hook_cfg.attach_point,
1235 hook_cfg.name,
1236 );
1237 continue;
1238 }
1239 };
1240 let mgr = match driver.hook_manager.as_ref() {
1241 Some(m) => m,
1242 None => {
1243 log::warn!(
1244 "Hook manager not available, skipping hook '{}'",
1245 hook_cfg.name
1246 );
1247 continue;
1248 }
1249 };
1250 let hook_backend = match config::parse_hook_backend(&hook_cfg.hook_type) {
1251 Ok(backend) => backend,
1252 Err(e) => {
1253 log::warn!(
1254 "Invalid hook type '{}' for hook '{}': {}",
1255 hook_cfg.hook_type,
1256 hook_cfg.name,
1257 e,
1258 );
1259 continue;
1260 }
1261 };
1262 let load_result = if hook_backend == rns_hooks::HookBackend::Builtin {
1263 let builtin_id = hook_cfg
1264 .builtin_id
1265 .as_deref()
1266 .filter(|id| !id.is_empty())
1267 .or_else(|| (!hook_cfg.path.is_empty()).then_some(hook_cfg.path.as_str()));
1268 match builtin_id {
1269 Some(id) => mgr.load_builtin(hook_cfg.name.clone(), id, hook_cfg.priority),
1270 None => Err(rns_hooks::HookError::CompileError(
1271 "built-in hook requires builtin/id or path".to_string(),
1272 )),
1273 }
1274 } else {
1275 mgr.load_file_backend(
1276 hook_cfg.name.clone(),
1277 std::path::Path::new(&hook_cfg.path),
1278 hook_cfg.priority,
1279 hook_backend,
1280 )
1281 };
1282 match load_result {
1283 Ok(program) => {
1284 driver.hook_slots[point_idx].attach(program);
1285 log::info!(
1286 "Loaded hook '{}' at point {} (priority {})",
1287 hook_cfg.name,
1288 hook_cfg.attach_point,
1289 hook_cfg.priority,
1290 );
1291 }
1292 Err(e) => {
1293 log::error!(
1294 "Failed to load hook '{}' from '{}': {}",
1295 hook_cfg.name,
1296 hook_cfg.path,
1297 e,
1298 );
1299 }
1300 }
1301 }
1302 }
1303
1304 driver.discover_interfaces = config.discover_interfaces;
1306 driver.autoconnect_interface_mode = config.autoconnect_interface_mode;
1307 driver.autoconnect_interface_gravity = config.autoconnect_interface_gravity;
1308 driver.autoconnect_announces_to_internal =
1309 config.autoconnect_announces_to_internal.then_some(true);
1310 if let Some(val) = config.discovery_required_value {
1311 driver.discovery_required_value = val;
1312 }
1313
1314 let next_dynamic_id = Arc::new(AtomicU64::new(10000));
1316 #[cfg(feature = "iface-backbone")]
1317 {
1318 driver.next_dynamic_interface_id = Arc::clone(&next_dynamic_id);
1319 }
1320
1321 let mut discoverable_interfaces = Vec::new();
1323 #[cfg(feature = "iface-backbone")]
1324 let mut backbone_peer_pool_candidates = Vec::new();
1325
1326 let registry = config
1328 .registry
1329 .unwrap_or_else(crate::interface::registry::InterfaceRegistry::with_builtins);
1330 for iface_config in config.interfaces {
1331 #[cfg(feature = "iface-backbone")]
1332 if iface_config.type_name == "BackboneInterface" {
1333 if let Some(mode) = iface_config
1334 .config_data
1335 .as_any()
1336 .downcast_ref::<BackboneMode>()
1337 {
1338 if let Some(handle) = runtime_handle_from_mode(mode) {
1339 driver.register_backbone_runtime(handle);
1340 }
1341 if let Some(handle) = peer_state_handle_from_mode(mode) {
1342 driver.register_backbone_peer_state(handle);
1343 }
1344 if let Some(handle) = client_runtime_handle_from_mode(mode) {
1345 driver.register_backbone_client_runtime(handle);
1346 }
1347 if let Some(handle) = backbone_discovery_runtime_from_interface(
1348 &iface_config.name,
1349 mode,
1350 iface_config.discovery.as_ref(),
1351 config.transport_enabled,
1352 iface_config.ifac.as_ref(),
1353 ) {
1354 driver.register_backbone_discovery_runtime(handle);
1355 }
1356 }
1357 }
1358 #[cfg(feature = "iface-tcp")]
1359 if iface_config.type_name == "TCPClientInterface" {
1360 if let Some(tcp_config) = iface_config
1361 .config_data
1362 .as_any()
1363 .downcast_ref::<TcpClientConfig>()
1364 {
1365 driver.register_tcp_client_runtime(tcp_client_runtime_handle_from_config(
1366 tcp_config,
1367 ));
1368 }
1369 }
1370 #[cfg(feature = "iface-tcp")]
1371 if iface_config.type_name == "TCPServerInterface" {
1372 if let Some(tcp_config) = iface_config
1373 .config_data
1374 .as_any()
1375 .downcast_ref::<TcpServerConfig>()
1376 {
1377 driver.register_tcp_server_runtime(tcp_runtime_handle_from_config(tcp_config));
1378 driver.register_tcp_server_discovery_runtime(
1379 tcp_server_discovery_runtime_from_interface(
1380 &iface_config.name,
1381 tcp_config,
1382 iface_config.discovery.as_ref(),
1383 config.transport_enabled,
1384 iface_config.ifac.as_ref(),
1385 ),
1386 );
1387 }
1388 }
1389 #[cfg(feature = "iface-udp")]
1390 if iface_config.type_name == "UDPInterface" {
1391 if let Some(udp_config) = iface_config
1392 .config_data
1393 .as_any()
1394 .downcast_ref::<UdpConfig>()
1395 {
1396 driver.register_udp_runtime(udp_runtime_handle_from_config(udp_config));
1397 }
1398 }
1399 #[cfg(feature = "iface-auto")]
1400 if iface_config.type_name == "AutoInterface" {
1401 if let Some(auto_config) = iface_config
1402 .config_data
1403 .as_any()
1404 .downcast_ref::<AutoConfig>()
1405 {
1406 driver.register_auto_runtime(auto_runtime_handle_from_config(auto_config));
1407 }
1408 }
1409 #[cfg(feature = "iface-i2p")]
1410 if iface_config.type_name == "I2PInterface" {
1411 if let Some(i2p_config) = iface_config
1412 .config_data
1413 .as_any()
1414 .downcast_ref::<I2pConfig>()
1415 {
1416 driver.register_i2p_runtime(i2p_runtime_handle_from_config(i2p_config));
1417 }
1418 }
1419 #[cfg(feature = "iface-pipe")]
1420 if iface_config.type_name == "PipeInterface" {
1421 if let Some(pipe_config) = iface_config
1422 .config_data
1423 .as_any()
1424 .downcast_ref::<PipeConfig>()
1425 {
1426 driver.register_pipe_runtime(pipe_runtime_handle_from_config(pipe_config));
1427 }
1428 }
1429 #[cfg(feature = "iface-rnode")]
1430 if iface_config.type_name == "RNodeInterface" {
1431 if let Some(rnode_config) = iface_config
1432 .config_data
1433 .as_any()
1434 .downcast_ref::<RNodeConfig>()
1435 {
1436 driver.register_rnode_runtime(rnode_runtime_handle_from_config(rnode_config));
1437 }
1438 }
1439
1440 let factory = match registry.get(&iface_config.type_name) {
1441 Some(f) => f,
1442 None => {
1443 log::warn!(
1444 "No factory registered for interface type '{}'",
1445 iface_config.type_name
1446 );
1447 continue;
1448 }
1449 };
1450
1451 let mut ifac_state = derive_ifac_state(iface_config.ifac.as_ref(), &iface_config.name)?;
1452 let ifac_runtime =
1453 ifac_runtime_from_config(iface_config.ifac.as_ref(), factory.default_ifac_size());
1454
1455 #[cfg(feature = "iface-backbone")]
1456 if config.backbone_peer_pool.is_some() && iface_config.type_name == "BackboneInterface"
1457 {
1458 if let Some(mode) = iface_config
1459 .config_data
1460 .as_any()
1461 .downcast_ref::<BackboneMode>()
1462 {
1463 if let Some(client) = client_config_from_mode(mode) {
1464 let priority = client_priority_from_mode(mode).unwrap_or(
1465 crate::driver::BACKBONE_PEER_POOL_CONFIGURED_DEFAULT_PRIORITY,
1466 );
1467 backbone_peer_pool_candidates.push(BackbonePeerPoolCandidateConfig {
1468 client,
1469 mode: iface_config.mode,
1470 gravity: iface_config.gravity,
1471 recursive_prs: iface_config.recursive_prs,
1472 announces_from_internal: iface_config.announces_from_internal,
1473 announces_to_internal: iface_config.announces_to_internal,
1474 ingress_control: iface_config.ingress_control,
1475 ifac_runtime: ifac_runtime.clone(),
1476 ifac_enabled: ifac_state.is_some(),
1477 interface_type_name: iface_config.type_name.clone(),
1478 source: crate::driver::BackbonePeerPoolCandidateSource::Configured,
1479 priority,
1480 discovery: None,
1481 });
1482 if let Some(ref disc) = iface_config.discovery {
1483 discoverable_interfaces.push(discoverable_interface_from_config(
1484 &iface_config.name,
1485 disc,
1486 config.transport_enabled,
1487 iface_config.ifac.as_ref(),
1488 ));
1489 }
1490 continue;
1491 }
1492 }
1493 }
1494
1495 let ctx = crate::interface::StartContext {
1496 tx: tx.clone(),
1497 next_dynamic_id: next_dynamic_id.clone(),
1498 mode: iface_config.mode,
1499 gravity: iface_config.gravity,
1500 recursive_prs: iface_config.recursive_prs,
1501 announces_from_internal: iface_config.announces_from_internal,
1502 announces_to_internal: iface_config.announces_to_internal,
1503 ingress_control: iface_config.ingress_control,
1504 ifac: ifac_state.clone(),
1505 };
1506
1507 let result = match factory.start(iface_config.config_data, ctx) {
1508 Ok(r) => r,
1509 Err(e) => {
1510 if config.panic_on_interface_error {
1511 return Err(e);
1512 }
1513 log::error!(
1514 "Interface '{}' ({}) failed to start: {}",
1515 iface_config.name,
1516 iface_config.type_name,
1517 e
1518 );
1519 continue;
1520 }
1521 };
1522
1523 if let Some(ref disc) = iface_config.discovery {
1524 discoverable_interfaces.push(discoverable_interface_from_config(
1525 &iface_config.name,
1526 disc,
1527 config.transport_enabled,
1528 iface_config.ifac.as_ref(),
1529 ));
1530 }
1531
1532 match result {
1533 crate::interface::StartResult::Simple {
1534 id,
1535 info,
1536 writer,
1537 interface_type_name,
1538 } => {
1539 register_started_interface(
1540 &mut driver,
1541 &tx,
1542 config.interface_writer_queue_capacity,
1543 id,
1544 info,
1545 writer,
1546 interface_type_name,
1547 ifac_state,
1548 &ifac_runtime,
1549 );
1550 }
1551 crate::interface::StartResult::Listener { control } => {
1552 if let Some(control) = control {
1555 driver.register_listener_control(control);
1556 }
1557 }
1558 crate::interface::StartResult::Multi(subs) => {
1559 let ifac_cfg = &iface_config.ifac;
1560 let mut first = true;
1561 for sub in subs {
1562 let sub_ifac = if first {
1563 first = false;
1564 ifac_state.take()
1565 } else {
1566 derive_ifac_state(ifac_cfg.as_ref(), &sub.info.name)?
1567 };
1568 register_started_interface(
1569 &mut driver,
1570 &tx,
1571 config.interface_writer_queue_capacity,
1572 sub.id,
1573 sub.info,
1574 sub.writer,
1575 sub.interface_type_name,
1576 sub_ifac,
1577 &ifac_runtime,
1578 );
1579 }
1580 }
1581 }
1582 }
1583
1584 if !discoverable_interfaces.is_empty() {
1586 let transport_id = *identity.hash();
1587 let announcer =
1588 crate::discovery::InterfaceAnnouncer::new(transport_id, discoverable_interfaces);
1589 log::info!("Interface discovery announcer initialized");
1590 driver.interface_announcer = Some(announcer);
1591 }
1592
1593 if let Some(ref cache_dir) = config.cache_dir {
1595 let disc_path = std::path::PathBuf::from(cache_dir)
1596 .parent()
1597 .unwrap_or(std::path::Path::new("."))
1598 .join("storage")
1599 .join("discovery")
1600 .join("interfaces");
1601 let _ = std::fs::create_dir_all(&disc_path);
1602 driver.discovered_interfaces =
1603 crate::discovery::DiscoveredInterfaceStorage::new(disc_path);
1604 }
1605
1606 if let Some(ref storage_dir) = transport_state_dir {
1609 match storage::load_transport_state(storage_dir) {
1610 Ok(snapshot) => {
1611 let announce_cache = driver.announce_cache.as_ref();
1612 let stats = driver.engine.restore_persistence_snapshot(
1613 snapshot,
1614 time::now(),
1615 |packet_hash| {
1616 announce_cache
1617 .and_then(|cache| cache.get(packet_hash).ok().flatten())
1618 .map(|(raw, _)| raw)
1619 },
1620 );
1621 log::debug!(
1622 "restored transport state: {} packet hashes, {} paths, {} tunnels ({} paths skipped)",
1623 stats.packet_hashes,
1624 stats.paths,
1625 stats.tunnels,
1626 stats.skipped_paths,
1627 );
1628 }
1629 Err(err) => log::warn!("failed to restore transport state: {}", err),
1630 }
1631 }
1632
1633 #[cfg(feature = "iface-backbone")]
1634 if let Some(settings) = config.backbone_peer_pool.clone() {
1635 driver.configure_backbone_peer_pool(settings, backbone_peer_pool_candidates);
1636 driver.seed_backbone_peer_pool_from_discovery_cache();
1637 }
1638
1639 if config.management.enable_remote_management {
1641 if let Some(prv_key) = identity.get_private_key() {
1642 let identity_hash = *identity.hash();
1643 let mgmt_dest = crate::management::management_dest_hash(&identity_hash);
1644
1645 let sig_prv = rns_crypto::ed25519::Ed25519PrivateKey::from_bytes(
1647 &prv_key[32..64].try_into().unwrap(),
1648 );
1649 let sig_pub_bytes: [u8; 32] = identity.get_public_key().unwrap()[32..64]
1650 .try_into()
1651 .unwrap();
1652
1653 driver
1655 .engine
1656 .register_destination(mgmt_dest, rns_core::constants::DESTINATION_SINGLE);
1657 driver
1658 .local_destinations
1659 .insert(mgmt_dest, rns_core::constants::DESTINATION_SINGLE);
1660
1661 driver.link_manager.register_link_destination(
1663 mgmt_dest,
1664 sig_prv,
1665 sig_pub_bytes,
1666 crate::link_manager::ResourceStrategy::AcceptNone,
1667 );
1668
1669 driver
1671 .link_manager
1672 .register_management_path(crate::management::status_path_hash());
1673 driver
1674 .link_manager
1675 .register_management_path(crate::management::path_path_hash());
1676
1677 log::info!("Remote management enabled on {:02x?}", &mgmt_dest[..4],);
1678
1679 if !config.management.remote_management_allowed.is_empty() {
1681 log::info!(
1682 "Remote management allowed for {} identities",
1683 config.management.remote_management_allowed.len(),
1684 );
1685 }
1686 }
1687 }
1688
1689 if config.management.publish_blackhole {
1690 if let Some(prv_key) = identity.get_private_key() {
1691 let identity_hash = *identity.hash();
1692 let bh_dest = crate::management::blackhole_dest_hash(&identity_hash);
1693
1694 let sig_prv = rns_crypto::ed25519::Ed25519PrivateKey::from_bytes(
1695 &prv_key[32..64].try_into().unwrap(),
1696 );
1697 let sig_pub_bytes: [u8; 32] = identity.get_public_key().unwrap()[32..64]
1698 .try_into()
1699 .unwrap();
1700
1701 driver
1702 .engine
1703 .register_destination(bh_dest, rns_core::constants::DESTINATION_SINGLE);
1704 driver.link_manager.register_link_destination(
1705 bh_dest,
1706 sig_prv,
1707 sig_pub_bytes,
1708 crate::link_manager::ResourceStrategy::AcceptNone,
1709 );
1710 driver
1711 .link_manager
1712 .register_management_path(crate::management::list_path_hash());
1713
1714 log::info!(
1715 "Blackhole list publishing enabled on {:02x?}",
1716 &bh_dest[..4],
1717 );
1718 }
1719 }
1720
1721 if config.respond_to_probes && config.transport_enabled {
1723 let identity_hash = *identity.hash();
1724 let probe_dest = crate::management::probe_dest_hash(&identity_hash);
1725
1726 driver
1728 .engine
1729 .register_destination(probe_dest, rns_core::constants::DESTINATION_SINGLE);
1730 driver
1731 .local_destinations
1732 .insert(probe_dest, rns_core::constants::DESTINATION_SINGLE);
1733
1734 let probe_identity = rns_crypto::identity::Identity::from_private_key(
1736 &identity.get_private_key().unwrap(),
1737 );
1738 driver.proof_strategies.insert(
1739 probe_dest,
1740 (
1741 rns_core::types::ProofStrategy::ProveAll,
1742 Some(probe_identity),
1743 ),
1744 );
1745
1746 driver.probe_responder_hash = Some(probe_dest);
1747
1748 log::info!("Probe responder enabled on {:02x?}", &probe_dest[..4],);
1749 }
1750
1751 let timer_tx = tx.clone();
1753 let timer_interval = Arc::clone(&tick_interval_ms);
1754 thread::Builder::new()
1755 .name("rns-timer".into())
1756 .spawn(move || {
1757 loop {
1758 let ms = timer_interval.load(Ordering::Relaxed);
1759 thread::sleep(Duration::from_millis(ms));
1760 if timer_tx.send(Event::Tick).is_err() {
1761 break; }
1763 }
1764 })?;
1765
1766 #[cfg(feature = "iface-local")]
1768 if config.share_instance {
1769 let local_server_config = LocalServerConfig {
1770 instance_name: config.instance_name.clone(),
1771 port: config.shared_instance_port,
1772 interface_id: rns_core::transport::types::InterfaceId(0), };
1774 match crate::interface::local::start_server(
1775 local_server_config,
1776 tx.clone(),
1777 next_dynamic_id.clone(),
1778 ) {
1779 Ok(control) => {
1780 driver.register_listener_control(control);
1781 log::info!(
1782 "Local shared instance server started (instance={}, port={})",
1783 config.instance_name,
1784 config.shared_instance_port
1785 );
1786 }
1787 Err(e) => {
1788 log::error!("Failed to start local shared instance server: {}", e);
1789 }
1790 }
1791 }
1792
1793 let rpc_server = if config.share_instance {
1795 let auth_key =
1796 crate::rpc::derive_auth_key(&identity.get_private_key().unwrap_or([0u8; 64]));
1797 let rpc_addr = crate::rpc::RpcAddr::Tcp("127.0.0.1".into(), config.rpc_port);
1798 match crate::rpc::RpcServer::start(&rpc_addr, auth_key, tx.clone()) {
1799 Ok(server) => {
1800 log::info!("RPC server started on 127.0.0.1:{}", config.rpc_port);
1801 Some(server)
1802 }
1803 Err(e) => {
1804 log::error!("Failed to start RPC server: {}", e);
1805 None
1806 }
1807 }
1808 } else {
1809 None
1810 };
1811
1812 let announce_verify_queue = Arc::clone(&driver.announce_verify_queue);
1813 let verify_shutdown = Arc::new(AtomicBool::new(false));
1814 let verify_shutdown_thread = Arc::clone(&verify_shutdown);
1815 let verify_tx = tx.clone();
1816 let verify_handle = thread::Builder::new()
1817 .name("rns-verify".into())
1818 .spawn(move || {
1819 #[cfg(target_family = "unix")]
1820 {
1821 unsafe {
1822 libc::nice(5);
1823 }
1824 }
1825
1826 while !verify_shutdown_thread.load(Ordering::Relaxed) {
1827 let batch = {
1828 let mut queue = announce_verify_queue
1829 .lock()
1830 .unwrap_or_else(|poisoned| poisoned.into_inner());
1831 queue.take_pending(time::now())
1832 };
1833
1834 if batch.is_empty() {
1835 thread::sleep(Duration::from_millis(50));
1836 continue;
1837 }
1838
1839 for (key, pending) in batch {
1840 if verify_shutdown_thread.load(Ordering::Relaxed) {
1841 break;
1842 }
1843 let has_ratchet =
1844 pending.packet.flags.context_flag == rns_core::constants::FLAG_SET;
1845 let announce = match rns_core::announce::AnnounceData::unpack(
1846 &pending.packet.data,
1847 has_ratchet,
1848 ) {
1849 Ok(announce) => announce,
1850 Err(_) => {
1851 let signature = [0u8; 64];
1852 let sig_cache_key = {
1853 let mut material = [0u8; 80];
1854 material[..16]
1855 .copy_from_slice(&pending.packet.destination_hash);
1856 material[16..].copy_from_slice(&signature);
1857 rns_core::hash::full_hash(&material)
1858 };
1859 if verify_tx
1860 .send(Event::AnnounceVerifyFailed { key, sig_cache_key })
1861 .is_err()
1862 {
1863 return;
1864 }
1865 continue;
1866 }
1867 };
1868 let mut material = [0u8; 80];
1869 material[..16].copy_from_slice(&pending.packet.destination_hash);
1870 material[16..].copy_from_slice(&announce.signature);
1871 let sig_cache_key = rns_core::hash::full_hash(&material);
1872 match announce.validate(&pending.packet.destination_hash) {
1873 Ok(validated) => {
1874 if verify_tx
1875 .send(Event::AnnounceVerified {
1876 key,
1877 validated,
1878 sig_cache_key,
1879 })
1880 .is_err()
1881 {
1882 return;
1883 }
1884 }
1885 Err(_) => {
1886 if verify_tx
1887 .send(Event::AnnounceVerifyFailed { key, sig_cache_key })
1888 .is_err()
1889 {
1890 return;
1891 }
1892 }
1893 }
1894 }
1895 }
1896 })?;
1897
1898 let driver_handle = thread::Builder::new()
1902 .name("rns-driver".into())
1903 .spawn(move || {
1904 driver.run();
1905 })?;
1906
1907 let (persist_handle, persist_stop) = if let Some(storage_dir) = transport_state_dir.clone()
1911 {
1912 let persist_tx = tx.clone();
1913 let (stop_tx, stop_rx) = std::sync::mpsc::channel();
1914 let handle = thread::Builder::new()
1915 .name("rns-persist".into())
1916 .spawn(move || {
1917 #[cfg(target_family = "unix")]
1918 unsafe {
1919 libc::nice(5);
1920 }
1921
1922 loop {
1923 match stop_rx.recv_timeout(Duration::from_secs(12 * 60 * 60)) {
1924 Ok(()) | Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => break,
1925 Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {}
1926 }
1927 let (response_tx, response_rx) = std::sync::mpsc::channel();
1928 if persist_tx
1929 .send(Event::Query(
1930 QueryRequest::TransportStateSnapshot,
1931 response_tx,
1932 ))
1933 .is_err()
1934 {
1935 break;
1936 }
1937 let Ok(QueryResponse::TransportStateSnapshot(snapshot)) =
1938 response_rx.recv()
1939 else {
1940 continue;
1941 };
1942 if let Err(err) =
1943 storage::save_transport_state(&snapshot, &storage_dir, true)
1944 {
1945 log::warn!("failed to persist transport state: {}", err);
1946 }
1947 }
1948 })?;
1949 (Some(handle), Some(stop_tx))
1950 } else {
1951 (None, None)
1952 };
1953
1954 Ok(RnsNode {
1955 tx,
1956 driver_handle: Some(driver_handle),
1957 verify_handle: Some(verify_handle),
1958 verify_shutdown,
1959 persist_handle,
1960 persist_stop,
1961 rpc_server,
1962 tick_interval_ms,
1963 probe_server,
1964 known_destinations_path: None,
1965 transport_state_dir,
1966 ratchet_store: config.ratchet_store,
1967 ratchet_expiry: config.ratchet_expiry,
1968 })
1969 }
1970
1971 pub fn query(&self, request: QueryRequest) -> Result<QueryResponse, SendError> {
1973 let (resp_tx, resp_rx) = std::sync::mpsc::channel();
1974 self.tx
1975 .send(Event::Query(request, resp_tx))
1976 .map_err(|_| SendError)?;
1977 resp_rx.recv().map_err(|_| SendError)
1978 }
1979
1980 pub fn begin_drain(&self, timeout: Duration) -> Result<(), SendError> {
1982 self.tx
1983 .send(Event::BeginDrain { timeout })
1984 .map_err(|_| SendError)
1985 }
1986
1987 pub fn drain_status(&self) -> Result<crate::event::DrainStatus, SendError> {
1989 match self.query(QueryRequest::DrainStatus)? {
1990 QueryResponse::DrainStatus(status) => Ok(status),
1991 _ => Err(SendError),
1992 }
1993 }
1994
1995 fn reject_new_work_if_draining(&self) -> Result<(), SendError> {
1996 let status = self.drain_status()?;
1997 if matches!(status.state, crate::event::LifecycleState::Active) {
1998 Ok(())
1999 } else {
2000 Err(SendError)
2001 }
2002 }
2003
2004 pub fn send_raw(
2006 &self,
2007 raw: Vec<u8>,
2008 dest_type: u8,
2009 attached_interface: Option<rns_core::transport::types::InterfaceId>,
2010 ) -> Result<(), SendError> {
2011 self.tx
2012 .send(Event::SendOutbound {
2013 raw,
2014 dest_type,
2015 attached_interface,
2016 })
2017 .map_err(|_| SendError)
2018 }
2019
2020 pub fn register_destination(
2022 &self,
2023 dest_hash: [u8; 16],
2024 dest_type: u8,
2025 ) -> Result<(), SendError> {
2026 self.tx
2027 .send(Event::RegisterDestination {
2028 dest_hash,
2029 dest_type,
2030 })
2031 .map_err(|_| SendError)
2032 }
2033
2034 pub fn deregister_destination(&self, dest_hash: [u8; 16]) -> Result<(), SendError> {
2036 self.tx
2037 .send(Event::DeregisterDestination { dest_hash })
2038 .map_err(|_| SendError)
2039 }
2040
2041 pub fn deregister_link_destination(&self, dest_hash: [u8; 16]) -> Result<(), SendError> {
2043 self.tx
2044 .send(Event::DeregisterLinkDestination { dest_hash })
2045 .map_err(|_| SendError)
2046 }
2047
2048 pub fn register_link_destination(
2054 &self,
2055 dest_hash: [u8; 16],
2056 sig_prv_bytes: [u8; 32],
2057 sig_pub_bytes: [u8; 32],
2058 resource_strategy: u8,
2059 ) -> Result<(), SendError> {
2060 self.register_link_destination_with_max_request_size(
2061 dest_hash,
2062 sig_prv_bytes,
2063 sig_pub_bytes,
2064 resource_strategy,
2065 None,
2066 )
2067 }
2068
2069 pub fn register_link_destination_with_max_request_size(
2071 &self,
2072 dest_hash: [u8; 16],
2073 sig_prv_bytes: [u8; 32],
2074 sig_pub_bytes: [u8; 32],
2075 resource_strategy: u8,
2076 max_request_size: Option<usize>,
2077 ) -> Result<(), SendError> {
2078 self.tx
2079 .send(Event::RegisterLinkDestination {
2080 dest_hash,
2081 sig_prv_bytes,
2082 sig_pub_bytes,
2083 resource_strategy,
2084 max_request_size,
2085 })
2086 .map_err(|_| SendError)
2087 }
2088
2089 pub fn register_request_handler<F>(
2091 &self,
2092 path: &str,
2093 allowed_list: Option<Vec<[u8; 16]>>,
2094 handler: F,
2095 ) -> Result<(), SendError>
2096 where
2097 F: Fn([u8; 16], &str, &[u8], Option<&([u8; 16], [u8; 64])>) -> Option<Vec<u8>>
2098 + Send
2099 + 'static,
2100 {
2101 self.tx
2102 .send(Event::RegisterRequestHandler {
2103 path: path.to_string(),
2104 allowed_list,
2105 handler: Box::new(handler),
2106 })
2107 .map_err(|_| SendError)
2108 }
2109
2110 pub fn register_request_handler_response<F>(
2112 &self,
2113 path: &str,
2114 allowed_list: Option<Vec<[u8; 16]>>,
2115 handler: F,
2116 ) -> Result<(), SendError>
2117 where
2118 F: Fn(
2119 [u8; 16],
2120 &str,
2121 &[u8],
2122 Option<&([u8; 16], [u8; 64])>,
2123 ) -> Option<crate::link_manager::RequestResponse>
2124 + Send
2125 + 'static,
2126 {
2127 self.tx
2128 .send(Event::RegisterRequestHandlerResponse {
2129 path: path.to_string(),
2130 allowed_list,
2131 handler: Box::new(handler),
2132 })
2133 .map_err(|_| SendError)
2134 }
2135
2136 pub fn create_link(
2140 &self,
2141 dest_hash: [u8; 16],
2142 dest_sig_pub_bytes: [u8; 32],
2143 ) -> Result<[u8; 16], SendError> {
2144 self.reject_new_work_if_draining()?;
2145 let (response_tx, response_rx) = std::sync::mpsc::channel();
2146 self.tx
2147 .send(Event::CreateLink {
2148 dest_hash,
2149 dest_sig_pub_bytes,
2150 response_tx,
2151 })
2152 .map_err(|_| SendError)?;
2153 let link_id = response_rx.recv().map_err(|_| SendError)?;
2154 if link_id == [0u8; 16] {
2155 Err(SendError)
2156 } else {
2157 Ok(link_id)
2158 }
2159 }
2160
2161 pub fn send_request(
2163 &self,
2164 link_id: [u8; 16],
2165 path: &str,
2166 data: &[u8],
2167 ) -> Result<(), SendError> {
2168 self.send_request_with_max_response_size(link_id, path, data, None)
2169 }
2170
2171 pub fn send_request_with_max_response_size(
2173 &self,
2174 link_id: [u8; 16],
2175 path: &str,
2176 data: &[u8],
2177 max_response_size: Option<usize>,
2178 ) -> Result<(), SendError> {
2179 self.reject_new_work_if_draining()?;
2180 self.tx
2181 .send(Event::SendRequest {
2182 link_id,
2183 path: path.to_string(),
2184 data: data.to_vec(),
2185 max_response_size,
2186 })
2187 .map_err(|_| SendError)
2188 }
2189
2190 pub fn identify_on_link(
2192 &self,
2193 link_id: [u8; 16],
2194 identity_prv_key: [u8; 64],
2195 ) -> Result<(), SendError> {
2196 self.reject_new_work_if_draining()?;
2197 self.tx
2198 .send(Event::IdentifyOnLink {
2199 link_id,
2200 identity_prv_key,
2201 })
2202 .map_err(|_| SendError)
2203 }
2204
2205 pub fn teardown_link(&self, link_id: [u8; 16]) -> Result<(), SendError> {
2207 self.tx
2208 .send(Event::TeardownLink { link_id })
2209 .map_err(|_| SendError)
2210 }
2211
2212 pub fn send_resource(
2214 &self,
2215 link_id: [u8; 16],
2216 data: Vec<u8>,
2217 metadata: Option<Vec<u8>>,
2218 ) -> Result<(), SendError> {
2219 self.send_resource_with_auto_compress(link_id, data, metadata, true)
2220 }
2221
2222 pub fn send_resource_with_auto_compress(
2224 &self,
2225 link_id: [u8; 16],
2226 data: Vec<u8>,
2227 metadata: Option<Vec<u8>>,
2228 auto_compress: bool,
2229 ) -> Result<(), SendError> {
2230 self.reject_new_work_if_draining()?;
2231 self.tx
2232 .send(Event::SendResource {
2233 link_id,
2234 data,
2235 metadata,
2236 auto_compress,
2237 })
2238 .map_err(|_| SendError)
2239 }
2240
2241 pub fn set_resource_strategy(&self, link_id: [u8; 16], strategy: u8) -> Result<(), SendError> {
2245 self.tx
2246 .send(Event::SetResourceStrategy { link_id, strategy })
2247 .map_err(|_| SendError)
2248 }
2249
2250 pub fn accept_resource(
2252 &self,
2253 link_id: [u8; 16],
2254 resource_hash: Vec<u8>,
2255 accept: bool,
2256 ) -> Result<(), SendError> {
2257 if accept {
2258 self.reject_new_work_if_draining()?;
2259 }
2260 self.tx
2261 .send(Event::AcceptResource {
2262 link_id,
2263 resource_hash,
2264 accept,
2265 })
2266 .map_err(|_| SendError)
2267 }
2268
2269 pub fn send_channel_message(
2271 &self,
2272 link_id: [u8; 16],
2273 msgtype: u16,
2274 payload: Vec<u8>,
2275 ) -> Result<(), SendError> {
2276 self.reject_new_work_if_draining()?;
2277 let (response_tx, response_rx) = std::sync::mpsc::channel();
2278 self.tx
2279 .send(Event::SendChannelMessage {
2280 link_id,
2281 msgtype,
2282 payload,
2283 response_tx,
2284 })
2285 .map_err(|_| SendError)?;
2286 response_rx
2287 .recv()
2288 .map_err(|_| SendError)?
2289 .map_err(|_| SendError)
2290 }
2291
2292 pub fn propose_direct_connect(&self, link_id: [u8; 16]) -> Result<(), SendError> {
2297 self.reject_new_work_if_draining()?;
2298 self.tx
2299 .send(Event::ProposeDirectConnect { link_id })
2300 .map_err(|_| SendError)
2301 }
2302
2303 pub fn set_direct_connect_policy(
2305 &self,
2306 policy: crate::holepunch::orchestrator::HolePunchPolicy,
2307 ) -> Result<(), SendError> {
2308 self.tx
2309 .send(Event::SetDirectConnectPolicy { policy })
2310 .map_err(|_| SendError)
2311 }
2312
2313 pub fn send_on_link(
2315 &self,
2316 link_id: [u8; 16],
2317 data: Vec<u8>,
2318 context: u8,
2319 ) -> Result<(), SendError> {
2320 self.reject_new_work_if_draining()?;
2321 self.tx
2322 .send(Event::SendOnLink {
2323 link_id,
2324 data,
2325 context,
2326 })
2327 .map_err(|_| SendError)
2328 }
2329
2330 pub fn announce(
2335 &self,
2336 dest: &crate::destination::Destination,
2337 identity: &Identity,
2338 app_data: Option<&[u8]>,
2339 ) -> Result<(), SendError> {
2340 self.reject_new_work_if_draining()?;
2341 let name_hash = rns_core::destination::name_hash(
2342 &dest.app_name,
2343 &dest.aspects.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
2344 );
2345
2346 let mut random_hash = [0u8; 10];
2347 OsRng.fill_bytes(&mut random_hash[..5]);
2348 let now_secs = std::time::SystemTime::now()
2352 .duration_since(std::time::UNIX_EPOCH)
2353 .unwrap_or_default()
2354 .as_secs();
2355 random_hash[5..10].copy_from_slice(&now_secs.to_be_bytes()[3..8]);
2356
2357 let (announce_data, _has_ratchet) = rns_core::announce::AnnounceData::pack(
2358 identity,
2359 &dest.hash.0,
2360 &name_hash,
2361 &random_hash,
2362 None, app_data,
2364 )
2365 .map_err(|_| SendError)?;
2366
2367 let context_flag = rns_core::constants::FLAG_UNSET;
2368
2369 let flags = rns_core::packet::PacketFlags {
2370 header_type: rns_core::constants::HEADER_1,
2371 context_flag,
2372 transport_type: rns_core::constants::TRANSPORT_BROADCAST,
2373 destination_type: rns_core::constants::DESTINATION_SINGLE,
2374 packet_type: rns_core::constants::PACKET_TYPE_ANNOUNCE,
2375 };
2376
2377 let packet = rns_core::packet::RawPacket::pack(
2378 flags,
2379 0,
2380 &dest.hash.0,
2381 None,
2382 rns_core::constants::CONTEXT_NONE,
2383 &announce_data,
2384 )
2385 .map_err(|_| SendError)?;
2386
2387 if dest.dest_type == rns_core::types::DestinationType::Single {
2388 if let Some(identity_prv_key) = identity.get_private_key() {
2389 self.tx
2390 .send(Event::StoreSharedAnnounce {
2391 dest_hash: dest.hash.0,
2392 name_hash,
2393 identity_prv_key,
2394 app_data: app_data.map(|d| d.to_vec()),
2395 })
2396 .map_err(|_| SendError)?;
2397 }
2398 }
2399
2400 self.send_raw(packet.raw, dest.dest_type.to_wire_constant(), None)
2401 }
2402
2403 pub fn send_packet(
2408 &self,
2409 dest: &crate::destination::Destination,
2410 data: &[u8],
2411 ) -> Result<rns_core::types::PacketHash, SendError> {
2412 self.reject_new_work_if_draining()?;
2413 use rns_core::types::DestinationType;
2414
2415 let payload = match dest.dest_type {
2416 DestinationType::Single => self.encrypt_single_payload(dest, data)?,
2417 DestinationType::Plain => data.to_vec(),
2418 DestinationType::Group => dest.encrypt(data).map_err(|_| SendError)?,
2419 };
2420
2421 let flags = rns_core::packet::PacketFlags {
2422 header_type: rns_core::constants::HEADER_1,
2423 context_flag: rns_core::constants::FLAG_UNSET,
2424 transport_type: rns_core::constants::TRANSPORT_BROADCAST,
2425 destination_type: dest.dest_type.to_wire_constant(),
2426 packet_type: rns_core::constants::PACKET_TYPE_DATA,
2427 };
2428
2429 let packet = rns_core::packet::RawPacket::pack(
2430 flags,
2431 0,
2432 &dest.hash.0,
2433 None,
2434 rns_core::constants::CONTEXT_NONE,
2435 &payload,
2436 )
2437 .map_err(|_| SendError)?;
2438
2439 let packet_hash = rns_core::types::PacketHash(packet.packet_hash);
2440
2441 self.tx
2442 .send(Event::SendOutbound {
2443 raw: packet.raw,
2444 dest_type: dest.dest_type.to_wire_constant(),
2445 attached_interface: None,
2446 })
2447 .map_err(|_| SendError)?;
2448
2449 Ok(packet_hash)
2450 }
2451
2452 fn encrypt_single_payload(
2453 &self,
2454 dest: &crate::destination::Destination,
2455 data: &[u8],
2456 ) -> Result<Vec<u8>, SendError> {
2457 let pub_key = dest.public_key.ok_or(SendError)?;
2458 let remote_id = rns_crypto::identity::Identity::from_public_key(&pub_key);
2459 let ratchet = self.ratchet_store.as_ref().and_then(|store| {
2460 match store.current(&dest.hash.0, time::now(), self.ratchet_expiry.as_secs_f64()) {
2461 Ok(entry) => entry.map(|entry| entry.ratchet),
2462 Err(err) => {
2463 log::warn!(
2464 "failed to load ratchet for {:02x}{:02x}{:02x}{:02x}..: {}",
2465 dest.hash.0[0],
2466 dest.hash.0[1],
2467 dest.hash.0[2],
2468 dest.hash.0[3],
2469 err
2470 );
2471 None
2472 }
2473 }
2474 });
2475 remote_id
2476 .encrypt_with_ratchet(data, ratchet.as_ref(), &mut OsRng)
2477 .map_err(|_| SendError)
2478 }
2479
2480 pub fn register_destination_with_proof(
2485 &self,
2486 dest: &crate::destination::Destination,
2487 signing_key: Option<[u8; 64]>,
2488 ) -> Result<(), SendError> {
2489 self.register_destination(dest.hash.0, dest.dest_type.to_wire_constant())?;
2491
2492 if dest.proof_strategy != rns_core::types::ProofStrategy::ProveNone {
2494 self.tx
2495 .send(Event::RegisterProofStrategy {
2496 dest_hash: dest.hash.0,
2497 strategy: dest.proof_strategy,
2498 signing_key,
2499 })
2500 .map_err(|_| SendError)?;
2501 }
2502
2503 Ok(())
2504 }
2505
2506 pub fn request_path(&self, dest_hash: &rns_core::types::DestHash) -> Result<(), SendError> {
2508 self.reject_new_work_if_draining()?;
2509 self.tx
2510 .send(Event::RequestPath {
2511 dest_hash: dest_hash.0,
2512 })
2513 .map_err(|_| SendError)
2514 }
2515
2516 pub fn has_path(&self, dest_hash: &rns_core::types::DestHash) -> Result<bool, SendError> {
2518 match self.query(QueryRequest::HasPath {
2519 dest_hash: dest_hash.0,
2520 })? {
2521 QueryResponse::HasPath(v) => Ok(v),
2522 _ => Ok(false),
2523 }
2524 }
2525
2526 pub fn hops_to(&self, dest_hash: &rns_core::types::DestHash) -> Result<Option<u8>, SendError> {
2528 match self.query(QueryRequest::HopsTo {
2529 dest_hash: dest_hash.0,
2530 })? {
2531 QueryResponse::HopsTo(v) => Ok(v),
2532 _ => Ok(None),
2533 }
2534 }
2535
2536 pub fn recall_identity(
2538 &self,
2539 dest_hash: &rns_core::types::DestHash,
2540 ) -> Result<Option<crate::destination::AnnouncedIdentity>, SendError> {
2541 match self.query(QueryRequest::RecallIdentity {
2542 dest_hash: dest_hash.0,
2543 })? {
2544 QueryResponse::RecallIdentity(v) => Ok(v),
2545 _ => Ok(None),
2546 }
2547 }
2548
2549 pub fn known_destinations(
2551 &self,
2552 ) -> Result<Vec<crate::event::KnownDestinationEntry>, SendError> {
2553 match self.query(QueryRequest::KnownDestinations)? {
2554 QueryResponse::KnownDestinations(entries) => Ok(entries),
2555 _ => Ok(Vec::new()),
2556 }
2557 }
2558
2559 pub fn retain_known_destination(
2561 &self,
2562 dest_hash: &rns_core::types::DestHash,
2563 ) -> Result<bool, SendError> {
2564 match self.query(QueryRequest::RetainKnownDestination {
2565 dest_hash: dest_hash.0,
2566 })? {
2567 QueryResponse::RetainKnownDestination(ok) => Ok(ok),
2568 _ => Ok(false),
2569 }
2570 }
2571
2572 pub fn retain_identity(
2574 &self,
2575 identity_hash: &rns_core::types::IdentityHash,
2576 ) -> Result<bool, SendError> {
2577 match self.query(QueryRequest::RetainIdentity {
2578 identity_hash: identity_hash.0,
2579 })? {
2580 QueryResponse::RetainIdentity(ok) => Ok(ok),
2581 _ => Ok(false),
2582 }
2583 }
2584
2585 pub fn unretain_known_destination(
2587 &self,
2588 dest_hash: &rns_core::types::DestHash,
2589 ) -> Result<bool, SendError> {
2590 match self.query(QueryRequest::UnretainKnownDestination {
2591 dest_hash: dest_hash.0,
2592 })? {
2593 QueryResponse::UnretainKnownDestination(ok) => Ok(ok),
2594 _ => Ok(false),
2595 }
2596 }
2597
2598 pub fn mark_known_destination_used(
2600 &self,
2601 dest_hash: &rns_core::types::DestHash,
2602 ) -> Result<bool, SendError> {
2603 match self.query(QueryRequest::MarkKnownDestinationUsed {
2604 dest_hash: dest_hash.0,
2605 })? {
2606 QueryResponse::MarkKnownDestinationUsed(ok) => Ok(ok),
2607 _ => Ok(false),
2608 }
2609 }
2610
2611 fn persist_known_destinations(&self) {
2612 let Some(path) = self.known_destinations_path.as_ref() else {
2613 return;
2614 };
2615
2616 let Ok(entries) = self.known_destinations() else {
2617 return;
2618 };
2619
2620 let destinations: std::collections::HashMap<[u8; 16], storage::KnownDestination> = entries
2621 .into_iter()
2622 .map(|entry| {
2623 (
2624 entry.dest_hash,
2625 storage::KnownDestination {
2626 identity_hash: entry.identity_hash,
2627 public_key: entry.public_key,
2628 app_data: entry.app_data,
2629 hops: entry.hops,
2630 received_at: entry.received_at,
2631 receiving_interface: entry.receiving_interface.0,
2632 was_used: entry.was_used,
2633 last_used_at: entry.last_used_at,
2634 retained: entry.retained,
2635 },
2636 )
2637 })
2638 .collect();
2639
2640 if let Err(err) = storage::save_known_destinations(&destinations, path) {
2641 log::warn!("failed to persist known destinations: {}", err);
2642 }
2643 }
2644
2645 fn persist_transport_state(&self) {
2646 let Some(storage_dir) = self.transport_state_dir.as_ref() else {
2647 return;
2648 };
2649 let Ok(QueryResponse::TransportStateSnapshot(snapshot)) =
2650 self.query(QueryRequest::TransportStateSnapshot)
2651 else {
2652 return;
2653 };
2654 if let Err(err) = storage::save_transport_state(&snapshot, storage_dir, true) {
2655 log::warn!("failed to persist transport state: {}", err);
2656 }
2657 }
2658
2659 pub fn load_hook(
2661 &self,
2662 name: String,
2663 wasm_bytes: Vec<u8>,
2664 attach_point: String,
2665 priority: i32,
2666 ) -> Result<Result<(), String>, SendError> {
2667 let (response_tx, response_rx) = std::sync::mpsc::channel();
2668 self.tx
2669 .send(Event::LoadHook {
2670 name,
2671 wasm_bytes,
2672 attach_point,
2673 priority,
2674 response_tx,
2675 })
2676 .map_err(|_| SendError)?;
2677 response_rx.recv().map_err(|_| SendError)
2678 }
2679
2680 pub fn load_hook_file(
2682 &self,
2683 name: String,
2684 path: String,
2685 hook_type: String,
2686 attach_point: String,
2687 priority: i32,
2688 ) -> Result<Result<(), String>, SendError> {
2689 let (response_tx, response_rx) = std::sync::mpsc::channel();
2690 self.tx
2691 .send(Event::LoadHookFile {
2692 name,
2693 path,
2694 hook_type,
2695 attach_point,
2696 priority,
2697 response_tx,
2698 })
2699 .map_err(|_| SendError)?;
2700 response_rx.recv().map_err(|_| SendError)
2701 }
2702
2703 pub fn load_builtin_hook(
2705 &self,
2706 name: String,
2707 builtin_id: String,
2708 attach_point: String,
2709 priority: i32,
2710 ) -> Result<Result<(), String>, SendError> {
2711 let (response_tx, response_rx) = std::sync::mpsc::channel();
2712 self.tx
2713 .send(Event::LoadBuiltinHook {
2714 name,
2715 builtin_id,
2716 attach_point,
2717 priority,
2718 response_tx,
2719 })
2720 .map_err(|_| SendError)?;
2721 response_rx.recv().map_err(|_| SendError)
2722 }
2723
2724 pub fn unload_hook(
2726 &self,
2727 name: String,
2728 attach_point: String,
2729 ) -> Result<Result<(), String>, SendError> {
2730 let (response_tx, response_rx) = std::sync::mpsc::channel();
2731 self.tx
2732 .send(Event::UnloadHook {
2733 name,
2734 attach_point,
2735 response_tx,
2736 })
2737 .map_err(|_| SendError)?;
2738 response_rx.recv().map_err(|_| SendError)
2739 }
2740
2741 pub fn reload_hook(
2743 &self,
2744 name: String,
2745 attach_point: String,
2746 wasm_bytes: Vec<u8>,
2747 ) -> Result<Result<(), String>, SendError> {
2748 let (response_tx, response_rx) = std::sync::mpsc::channel();
2749 self.tx
2750 .send(Event::ReloadHook {
2751 name,
2752 attach_point,
2753 wasm_bytes,
2754 response_tx,
2755 })
2756 .map_err(|_| SendError)?;
2757 response_rx.recv().map_err(|_| SendError)
2758 }
2759
2760 pub fn reload_hook_file(
2762 &self,
2763 name: String,
2764 attach_point: String,
2765 path: String,
2766 hook_type: String,
2767 ) -> Result<Result<(), String>, SendError> {
2768 let (response_tx, response_rx) = std::sync::mpsc::channel();
2769 self.tx
2770 .send(Event::ReloadHookFile {
2771 name,
2772 attach_point,
2773 path,
2774 hook_type,
2775 response_tx,
2776 })
2777 .map_err(|_| SendError)?;
2778 response_rx.recv().map_err(|_| SendError)
2779 }
2780
2781 pub fn reload_builtin_hook(
2783 &self,
2784 name: String,
2785 attach_point: String,
2786 builtin_id: String,
2787 ) -> Result<Result<(), String>, SendError> {
2788 let (response_tx, response_rx) = std::sync::mpsc::channel();
2789 self.tx
2790 .send(Event::ReloadBuiltinHook {
2791 name,
2792 attach_point,
2793 builtin_id,
2794 response_tx,
2795 })
2796 .map_err(|_| SendError)?;
2797 response_rx.recv().map_err(|_| SendError)
2798 }
2799
2800 pub fn set_hook_enabled(
2802 &self,
2803 name: String,
2804 attach_point: String,
2805 enabled: bool,
2806 ) -> Result<Result<(), String>, SendError> {
2807 let (response_tx, response_rx) = std::sync::mpsc::channel();
2808 self.tx
2809 .send(Event::SetHookEnabled {
2810 name,
2811 attach_point,
2812 enabled,
2813 response_tx,
2814 })
2815 .map_err(|_| SendError)?;
2816 response_rx.recv().map_err(|_| SendError)
2817 }
2818
2819 pub fn set_hook_priority(
2821 &self,
2822 name: String,
2823 attach_point: String,
2824 priority: i32,
2825 ) -> Result<Result<(), String>, SendError> {
2826 let (response_tx, response_rx) = std::sync::mpsc::channel();
2827 self.tx
2828 .send(Event::SetHookPriority {
2829 name,
2830 attach_point,
2831 priority,
2832 response_tx,
2833 })
2834 .map_err(|_| SendError)?;
2835 response_rx.recv().map_err(|_| SendError)
2836 }
2837
2838 pub fn list_hooks(&self) -> Result<Vec<crate::event::HookInfo>, SendError> {
2840 let (response_tx, response_rx) = std::sync::mpsc::channel();
2841 self.tx
2842 .send(Event::ListHooks { response_tx })
2843 .map_err(|_| SendError)?;
2844 response_rx.recv().map_err(|_| SendError)
2845 }
2846
2847 pub(crate) fn from_parts(
2850 tx: EventSender,
2851 driver_handle: thread::JoinHandle<()>,
2852 rpc_server: Option<crate::rpc::RpcServer>,
2853 tick_interval_ms: Arc<AtomicU64>,
2854 ) -> Self {
2855 RnsNode {
2856 tx,
2857 driver_handle: Some(driver_handle),
2858 verify_handle: None,
2859 verify_shutdown: Arc::new(AtomicBool::new(false)),
2860 persist_handle: None,
2861 persist_stop: None,
2862 rpc_server,
2863 tick_interval_ms,
2864 probe_server: None,
2865 known_destinations_path: None,
2866 transport_state_dir: None,
2867 ratchet_store: None,
2868 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
2869 }
2870 }
2871
2872 pub fn event_sender(&self) -> &EventSender {
2874 &self.tx
2875 }
2876
2877 pub fn set_tick_interval(&self, ms: u64) -> u64 {
2882 let clamped = ms.clamp(100, 10_000);
2883 if clamped != ms {
2884 log::warn!(
2885 "tick interval {}ms out of range, clamped to {}ms",
2886 ms,
2887 clamped
2888 );
2889 }
2890 self.tick_interval_ms.store(clamped, Ordering::Relaxed);
2891 clamped
2892 }
2893
2894 pub fn tick_interval(&self) -> u64 {
2896 self.tick_interval_ms.load(Ordering::Relaxed)
2897 }
2898
2899 pub fn shutdown(mut self) {
2901 if let Some(mut rpc) = self.rpc_server.take() {
2903 rpc.stop();
2904 }
2905 self.persist_known_destinations();
2906 self.persist_transport_state();
2907 if let Some(stop) = self.persist_stop.take() {
2908 let _ = stop.send(());
2909 }
2910 if let Some(handle) = self.persist_handle.take() {
2911 let _ = handle.join();
2912 }
2913 self.verify_shutdown.store(true, Ordering::Relaxed);
2914 let _ = self.tx.send(Event::Shutdown);
2915 if let Some(handle) = self.driver_handle.take() {
2916 let _ = handle.join();
2917 }
2918 if let Some(handle) = self.verify_handle.take() {
2919 let _ = handle.join();
2920 }
2921 }
2922}
2923
2924#[cfg(test)]
2925mod tests {
2926 use super::*;
2927 use crate::driver::IfacRuntimeConfig;
2928 use crate::storage::RatchetStore;
2929 use std::fs;
2930 use tempfile::tempdir;
2931
2932 struct NoopCallbacks;
2933
2934 impl Callbacks for NoopCallbacks {
2935 fn on_announce(&mut self, _: crate::destination::AnnouncedIdentity) {}
2936 fn on_path_updated(&mut self, _: rns_core::types::DestHash, _: u8) {}
2937 fn on_local_delivery(
2938 &mut self,
2939 _: rns_core::types::DestHash,
2940 _: Vec<u8>,
2941 _: rns_core::types::PacketHash,
2942 ) {
2943 }
2944 }
2945
2946 struct TestWriter;
2947
2948 impl crate::interface::Writer for TestWriter {
2949 fn send_frame(&mut self, _data: &[u8]) -> io::Result<()> {
2950 Ok(())
2951 }
2952 }
2953
2954 fn test_transport_config(transport_enabled: bool) -> TransportConfig {
2955 TransportConfig {
2956 transport_enabled,
2957 identity_hash: None,
2958 local_hops_delta: 0,
2959 prefer_shorter_path: false,
2960 max_paths_per_destination: 1,
2961 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
2962 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
2963 max_path_destinations: usize::MAX,
2964 max_tunnel_destinations_total: usize::MAX,
2965 destination_timeout_secs: rns_core::constants::DESTINATION_TIMEOUT,
2966 announce_table_ttl_secs: rns_core::constants::ANNOUNCE_TABLE_TTL,
2967 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
2968 announce_sig_cache_enabled: true,
2969 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
2970 announce_sig_cache_ttl_secs: rns_core::constants::ANNOUNCE_SIG_CACHE_TTL,
2971 announce_queue_max_entries: 256,
2972 announce_queue_max_interfaces: 1024,
2973 }
2974 }
2975
2976 fn test_interface_info(id: u64) -> rns_core::transport::types::InterfaceInfo {
2977 rns_core::transport::types::InterfaceInfo {
2978 id: rns_core::transport::types::InterfaceId(id),
2979 name: format!("test-{id}"),
2980 mode: rns_core::constants::MODE_FULL,
2981 gravity: 0,
2982 recursive_prs: false,
2983 announces_from_internal: true,
2984 announces_to_internal: None,
2985 out_capable: true,
2986 in_capable: true,
2987 bitrate: None,
2988 airtime_profile: None,
2989 announce_rate_target: None,
2990 announce_rate_grace: 0,
2991 announce_rate_penalty: 0.0,
2992 announce_cap: rns_core::constants::ANNOUNCE_CAP,
2993 is_local_client: false,
2994 wants_tunnel: false,
2995 tunnel_id: None,
2996 mtu: rns_core::constants::MTU as u32,
2997 ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
2998 ia_freq: 0.0,
2999 ip_freq: 0.0,
3000 op_freq: 0.0,
3001 op_samples: 0,
3002 started: 0.0,
3003 }
3004 }
3005
3006 #[test]
3007 fn static_interface_registration_applies_transport_announce_rate_defaults() {
3008 let (tx, rx) = crate::event::channel();
3009 let mut driver = Driver::new(
3010 test_transport_config(true),
3011 rx,
3012 tx.clone(),
3013 Box::new(NoopCallbacks),
3014 );
3015 driver.set_announce_rate_defaults(AnnounceRateDefaults {
3016 target: Some(7200.0),
3017 penalty: 15.0,
3018 grace: 7,
3019 });
3020
3021 let id = rns_core::transport::types::InterfaceId(7);
3022 register_started_interface(
3023 &mut driver,
3024 &tx,
3025 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
3026 id,
3027 test_interface_info(id.0),
3028 Box::new(TestWriter),
3029 "TestInterface".to_string(),
3030 None,
3031 &IfacRuntimeConfig {
3032 netname: None,
3033 netkey: None,
3034 size: 16,
3035 },
3036 );
3037
3038 let info = &driver.interfaces[&id].info;
3039 assert_eq!(info.announce_rate_target, Some(7200.0));
3040 assert_eq!(info.announce_rate_penalty, 15.0);
3041 assert_eq!(info.announce_rate_grace, 7);
3042 }
3043
3044 #[test]
3045 fn static_interface_registration_applies_transport_ingress_control_defaults() {
3046 let (tx, rx) = crate::event::channel();
3047 let mut driver = Driver::new(
3048 test_transport_config(true),
3049 rx,
3050 tx.clone(),
3051 Box::new(NoopCallbacks),
3052 );
3053 let mut defaults = rns_core::transport::types::IngressControlConfig::enabled();
3054 defaults.burst_hold = 1.5;
3055 defaults.pr_burst_freq = 5.5;
3056 defaults.egress_enabled = true;
3057 defaults.egress_pr_freq = 9.5;
3058 driver.set_ingress_control_defaults(defaults);
3059
3060 let id = rns_core::transport::types::InterfaceId(8);
3061 register_started_interface(
3062 &mut driver,
3063 &tx,
3064 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
3065 id,
3066 test_interface_info(id.0),
3067 Box::new(TestWriter),
3068 "TestInterface".to_string(),
3069 None,
3070 &IfacRuntimeConfig {
3071 netname: None,
3072 netkey: None,
3073 size: 16,
3074 },
3075 );
3076
3077 let ic = driver.interfaces[&id].info.ingress_control;
3078 assert_eq!(ic.burst_hold, 1.5);
3079 assert_eq!(ic.pr_burst_freq, 5.5);
3080 assert!(ic.egress_enabled);
3081 assert_eq!(ic.egress_pr_freq, 9.5);
3082 }
3083
3084 struct TestNodeRatchetStore {
3085 entry: storage::RatchetEntry,
3086 current_calls: std::sync::Mutex<Vec<[u8; 16]>>,
3087 }
3088
3089 impl storage::RatchetStore for TestNodeRatchetStore {
3090 fn remember(&self, _dest_hash: [u8; 16], _entry: storage::RatchetEntry) -> io::Result<()> {
3091 Ok(())
3092 }
3093
3094 fn current(
3095 &self,
3096 dest_hash: &[u8; 16],
3097 _now: f64,
3098 _expiry_secs: f64,
3099 ) -> io::Result<Option<storage::RatchetEntry>> {
3100 self.current_calls.lock().unwrap().push(*dest_hash);
3101 Ok(Some(self.entry))
3102 }
3103
3104 fn cleanup(
3105 &self,
3106 _known_destinations: &HashSet<[u8; 16]>,
3107 _now: f64,
3108 _expiry_secs: f64,
3109 ) -> io::Result<storage::RatchetCleanupStats> {
3110 Ok(Default::default())
3111 }
3112 }
3113
3114 #[test]
3115 fn send_packet_checks_ratchet_store_for_single_destinations() {
3116 let store = Arc::new(TestNodeRatchetStore {
3117 entry: storage::RatchetEntry {
3118 ratchet: [0x55; 32],
3119 received_at: time::now(),
3120 },
3121 current_calls: std::sync::Mutex::new(Vec::new()),
3122 });
3123 let ratchet_store: Arc<dyn storage::RatchetStore> = store.clone();
3124 let node = RnsNode::start(
3125 NodeConfig {
3126 ratchet_store: Some(ratchet_store),
3127 ..Default::default()
3128 },
3129 Box::new(NoopCallbacks),
3130 )
3131 .unwrap();
3132
3133 let mut rng = OsRng;
3134 let remote_identity = Identity::new(&mut rng);
3135 let public_key = remote_identity.get_public_key().unwrap();
3136 let announced = crate::destination::AnnouncedIdentity {
3137 dest_hash: rns_core::types::DestHash([0x22; 16]),
3138 identity_hash: rns_core::types::IdentityHash(*remote_identity.hash()),
3139 public_key,
3140 app_data: None,
3141 hops: 1,
3142 received_at: time::now(),
3143 receiving_interface: rns_core::transport::types::InterfaceId(0),
3144 rssi: Some(-100),
3145 snr: Some(10.5),
3146 };
3147 let dest = crate::destination::Destination::single_out("test", &["ratchet"], &announced);
3148
3149 node.send_packet(&dest, b"hello").unwrap();
3150 assert_eq!(
3151 store.current_calls.lock().unwrap().as_slice(),
3152 &[dest.hash.0]
3153 );
3154
3155 node.shutdown();
3156 }
3157
3158 #[test]
3159 fn single_payload_uses_stored_ratchet_key_material() {
3160 let ratchet_prv = rns_crypto::x25519::X25519PrivateKey::from_bytes(&[0x42; 32]);
3161 let ratchet_pub = ratchet_prv.public_key().public_bytes();
3162 let store = Arc::new(TestNodeRatchetStore {
3163 entry: storage::RatchetEntry {
3164 ratchet: ratchet_pub,
3165 received_at: time::now(),
3166 },
3167 current_calls: std::sync::Mutex::new(Vec::new()),
3168 });
3169 let ratchet_store: Arc<dyn storage::RatchetStore> = store.clone();
3170 let (tx, _rx) = crate::event::channel();
3171 let node = RnsNode {
3172 tx,
3173 driver_handle: None,
3174 verify_handle: None,
3175 verify_shutdown: Arc::new(AtomicBool::new(false)),
3176 persist_handle: None,
3177 persist_stop: None,
3178 rpc_server: None,
3179 tick_interval_ms: Arc::new(AtomicU64::new(1000)),
3180 probe_server: None,
3181 known_destinations_path: None,
3182 transport_state_dir: None,
3183 ratchet_store: Some(ratchet_store),
3184 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
3185 };
3186
3187 let mut rng = OsRng;
3188 let remote_identity = Identity::new(&mut rng);
3189 let public_key = remote_identity.get_public_key().unwrap();
3190 let announced = crate::destination::AnnouncedIdentity {
3191 dest_hash: rns_core::types::DestHash([0x33; 16]),
3192 identity_hash: rns_core::types::IdentityHash(*remote_identity.hash()),
3193 public_key,
3194 app_data: None,
3195 hops: 1,
3196 received_at: time::now(),
3197 receiving_interface: rns_core::transport::types::InterfaceId(0),
3198 rssi: Some(-100),
3199 snr: Some(10.5),
3200 };
3201 let dest = crate::destination::Destination::single_out("test", &["ratchet"], &announced);
3202 let payload = node
3203 .encrypt_single_payload(&dest, b"ratchet message")
3204 .unwrap();
3205
3206 assert_eq!(
3207 store.current_calls.lock().unwrap().as_slice(),
3208 &[dest.hash.0]
3209 );
3210 assert!(remote_identity.decrypt(&payload).is_err());
3211
3212 let peer_pub_bytes: [u8; 32] = payload[..32].try_into().unwrap();
3213 let peer_pub = rns_crypto::x25519::X25519PublicKey::from_bytes(&peer_pub_bytes);
3214 let shared_key = ratchet_prv.exchange(&peer_pub);
3215 let derived_key = rns_crypto::hkdf::hkdf(
3216 rns_crypto::identity::DERIVED_KEY_LENGTH,
3217 &shared_key,
3218 Some(remote_identity.hash()),
3219 None,
3220 )
3221 .unwrap();
3222 let token = rns_crypto::token::Token::new(&derived_key).unwrap();
3223 let plaintext = token.decrypt(&payload[32..]).unwrap();
3224 assert_eq!(plaintext, b"ratchet message");
3225 }
3226
3227 #[test]
3228 fn tcp_client_interface_is_not_discoverable_without_kiss_framing() {
3229 let mut params = std::collections::HashMap::new();
3230 params.insert("discoverable".to_string(), "yes".to_string());
3231 params.insert(
3232 "discovery_name".to_string(),
3233 "invalid-tcp-client".to_string(),
3234 );
3235 params.insert("reachable_on".to_string(), "example.com".to_string());
3236 params.insert("target_port".to_string(), "4242".to_string());
3237
3238 let discovery =
3239 super::extract_discovery_config("tcp-client", "TCPClientInterface", ¶ms);
3240
3241 assert!(
3242 discovery.is_none(),
3243 "TCPClientInterface discovery must be rejected unless KISS framing is supported"
3244 );
3245 }
3246
3247 #[test]
3248 fn discovery_config_parses_location_command() {
3249 let mut params = std::collections::HashMap::new();
3250 params.insert("discoverable".into(), "yes".into());
3251 params.insert("location_cmd".into(), "~/bin/reticulum-location".into());
3252
3253 let discovery =
3254 super::extract_discovery_config("public", "BackboneInterface", ¶ms).unwrap();
3255 assert_eq!(
3256 discovery.location_cmd.as_deref(),
3257 Some("~/bin/reticulum-location")
3258 );
3259 }
3260
3261 #[test]
3262 fn ingress_control_config_defaults_by_interface_type() {
3263 let params = std::collections::HashMap::new();
3264
3265 let tcp = super::parse_ingress_control_config("TCPServerInterface", ¶ms).unwrap();
3266 assert!(tcp.enabled);
3267 assert_eq!(
3268 tcp.max_held_announces,
3269 rns_core::constants::IC_MAX_HELD_ANNOUNCES
3270 );
3271 assert_eq!(tcp.burst_hold, rns_core::constants::IC_BURST_HOLD);
3272
3273 let pipe = super::parse_ingress_control_config("PipeInterface", ¶ms).unwrap();
3274 assert!(!pipe.enabled);
3275 assert_eq!(
3276 pipe.held_release_interval,
3277 rns_core::constants::IC_HELD_RELEASE_INTERVAL
3278 );
3279 }
3280
3281 #[test]
3282 fn ingress_control_config_parses_python_ic_keys() {
3283 let mut params = std::collections::HashMap::new();
3284 params.insert("ingress_control".to_string(), "No".to_string());
3285 params.insert("ic_max_held_announces".to_string(), "17".to_string());
3286 params.insert("ic_burst_hold".to_string(), "1.5".to_string());
3287 params.insert("ic_burst_freq_new".to_string(), "2.5".to_string());
3288 params.insert("ic_burst_freq".to_string(), "3.5".to_string());
3289 params.insert("ic_pr_burst_freq_new".to_string(), "3.25".to_string());
3290 params.insert("ic_pr_burst_freq".to_string(), "8.25".to_string());
3291 params.insert("egress_control".to_string(), "Yes".to_string());
3292 params.insert("ec_pr_freq".to_string(), "5.25".to_string());
3293 params.insert("ic_new_time".to_string(), "4.5".to_string());
3294 params.insert("ic_burst_penalty".to_string(), "5.5".to_string());
3295 params.insert("ic_held_release_interval".to_string(), "6.5".to_string());
3296
3297 let config = super::parse_ingress_control_config("TCPServerInterface", ¶ms).unwrap();
3298
3299 assert!(!config.enabled);
3300 assert_eq!(config.max_held_announces, 17);
3301 assert_eq!(config.burst_hold, 1.5);
3302 assert_eq!(config.burst_freq_new, 2.5);
3303 assert_eq!(config.burst_freq, 3.5);
3304 assert_eq!(config.pr_burst_freq_new, 3.25);
3305 assert_eq!(config.pr_burst_freq, 8.25);
3306 assert!(config.egress_enabled);
3307 assert_eq!(config.egress_pr_freq, 5.25);
3308 assert_eq!(config.new_time, 4.5);
3309 assert_eq!(config.burst_penalty, 5.5);
3310 assert_eq!(config.held_release_interval, 6.5);
3311 }
3312
3313 #[test]
3314 fn ingress_control_config_rejects_invalid_values() {
3315 let mut params = std::collections::HashMap::new();
3316 params.insert("ic_burst_hold".to_string(), "-1".to_string());
3317
3318 let err = super::parse_ingress_control_config("TCPServerInterface", ¶ms).unwrap_err();
3319
3320 assert!(err.contains("ic_burst_hold"));
3321 }
3322
3323 #[test]
3324 fn discovery_stamp_default_is_16_but_explicit_override_is_preserved() {
3325 let mut params = std::collections::HashMap::new();
3326 params.insert("discoverable".to_string(), "yes".to_string());
3327 let defaults =
3328 super::extract_discovery_config("Discovery Test", "BackboneInterface", ¶ms)
3329 .unwrap();
3330 assert_eq!(defaults.stamp_value, 16);
3331
3332 params.insert("discovery_stamp_value".to_string(), "14".to_string());
3333 let overridden =
3334 super::extract_discovery_config("Discovery Test", "BackboneInterface", ¶ms)
3335 .unwrap();
3336 assert_eq!(overridden.stamp_value, 14);
3337 }
3338
3339 #[test]
3340 fn start_and_shutdown() {
3341 let node = RnsNode::start(
3342 NodeConfig {
3343 panic_on_interface_error: false,
3344 transport_enabled: false,
3345 static_transport_identity: false,
3346 local_hops_delta: false,
3347 identity: None,
3348 interfaces: vec![],
3349 share_instance: false,
3350 instance_name: "default".into(),
3351 shared_instance_port: 37428,
3352 rpc_port: 0,
3353 cache_dir: None,
3354 ratchet_store: None,
3355 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
3356 management: Default::default(),
3357 probe_port: None,
3358 probe_addrs: vec![],
3359 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
3360 device: None,
3361 hooks: Vec::new(),
3362 discover_interfaces: false,
3363 autoconnect_interface_mode: None,
3364 autoconnect_interface_gravity: 0,
3365 autoconnect_announces_to_internal: false,
3366 discovery_required_value: None,
3367 respond_to_probes: false,
3368 prefer_shorter_path: false,
3369 max_paths_per_destination: 1,
3370 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
3371 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
3372 max_path_destinations: usize::MAX,
3373 max_tunnel_destinations_total: usize::MAX,
3374 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
3375 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
3376 announce_table_ttl: Duration::from_secs(
3377 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
3378 ),
3379 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
3380 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
3381 interface_writer_queue_capacity:
3382 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
3383 announce_rate_defaults: AnnounceRateDefaults::default(),
3384 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
3385 ),
3386 #[cfg(feature = "iface-backbone")]
3387 backbone_peer_pool: None,
3388 announce_sig_cache_enabled: true,
3389 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
3390 announce_sig_cache_ttl: Duration::from_secs(
3391 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
3392 ),
3393 registry: None,
3394 #[cfg(feature = "hooks")]
3395 provider_bridge: None,
3396 },
3397 Box::new(NoopCallbacks),
3398 )
3399 .unwrap();
3400 node.shutdown();
3401 }
3402
3403 #[test]
3404 fn known_destinations_persist_across_restart() {
3405 let dir = tempdir().unwrap();
3406 let dest_hash = [0x91; 16];
3407 let identity = Identity::new(&mut OsRng);
3408 let last_used_at = 77.0;
3409 let receiving_interface = rns_core::transport::types::InterfaceId(42);
3410
3411 let node = RnsNode::from_config(Some(dir.path()), Box::new(NoopCallbacks)).unwrap();
3412 let (response_tx, response_rx) = std::sync::mpsc::channel();
3413 node.event_sender()
3414 .send(crate::event::Event::Query(
3415 QueryRequest::RestoreKnownDestination(crate::event::KnownDestinationEntry {
3416 dest_hash,
3417 identity_hash: *identity.hash(),
3418 public_key: identity.get_public_key().unwrap(),
3419 app_data: Some(b"persisted".to_vec()),
3420 hops: 2,
3421 received_at: 55.0,
3422 receiving_interface,
3423 was_used: true,
3424 last_used_at: Some(last_used_at),
3425 retained: true,
3426 }),
3427 response_tx,
3428 ))
3429 .unwrap();
3430 assert!(matches!(
3431 response_rx.recv().unwrap(),
3432 QueryResponse::RestoreKnownDestination(true)
3433 ));
3434 node.shutdown();
3435
3436 let restarted = RnsNode::from_config(Some(dir.path()), Box::new(NoopCallbacks)).unwrap();
3437 let entries = restarted.known_destinations().unwrap();
3438 let entry = entries
3439 .iter()
3440 .find(|entry| entry.dest_hash == dest_hash)
3441 .expect("reloaded destination should appear in lifecycle listing");
3442 assert!(entry.retained);
3443 assert!(entry.was_used);
3444 assert_eq!(entry.hops, 2);
3445 assert_eq!(entry.receiving_interface, receiving_interface);
3446 assert_eq!(entry.last_used_at, Some(last_used_at));
3447
3448 let recalled = restarted
3449 .recall_identity(&rns_core::types::DestHash(dest_hash))
3450 .unwrap()
3451 .expect("known destination should reload from storage");
3452 assert_eq!(recalled.identity_hash.0, *identity.hash());
3453 assert_eq!(recalled.app_data, Some(b"persisted".to_vec()));
3454 restarted.shutdown();
3455 }
3456
3457 #[test]
3458 fn transport_paths_hashes_and_detached_tunnels_restore_from_upstream_files() {
3459 use rns_core::packet::{PacketFlags, RawPacket};
3460 use rns_core::transport::persistence::{
3461 PersistedPath, PersistedTunnel, TransportStateSnapshot,
3462 };
3463
3464 let dir = tempdir().unwrap();
3465 fs::write(
3466 dir.path().join("config"),
3467 r#"[reticulum]
3468enable_transport = True
3469share_instance = False
3470
3471[interfaces]
3472 [[Persistence Test]]
3473 type = UDPInterface
3474 forward_ip = 127.0.0.1
3475 forward_port = 9
3476 interface_mode = full
3477"#,
3478 )
3479 .unwrap();
3480 let paths = storage::ensure_storage_dirs(dir.path()).unwrap();
3481 let destination_hash = [0x31; 16];
3482 let packet = RawPacket::pack(
3483 PacketFlags {
3484 header_type: rns_core::constants::HEADER_1,
3485 context_flag: rns_core::constants::FLAG_UNSET,
3486 transport_type: rns_core::constants::TRANSPORT_BROADCAST,
3487 destination_type: rns_core::constants::DESTINATION_SINGLE,
3488 packet_type: rns_core::constants::PACKET_TYPE_ANNOUNCE,
3489 },
3490 0,
3491 &destination_hash,
3492 None,
3493 rns_core::constants::CONTEXT_NONE,
3494 &[0xA5; 32],
3495 )
3496 .unwrap();
3497 crate::announce_cache::AnnounceCache::new(paths.cache.join("announces"))
3498 .store(&packet.packet_hash, &packet.raw, None)
3499 .unwrap();
3500 let now = time::now();
3501 let route = PersistedPath {
3502 destination_hash,
3503 timestamp: now,
3504 received_from: [0x41; 16],
3505 hops: 2,
3506 expires: now + 600.0,
3507 random_blobs: vec![[0x51; 10]],
3508 interface_hash: Some(rns_core::hash::full_hash(b"Persistence Test")),
3509 packet_hash: packet.packet_hash,
3510 };
3511 storage::save_transport_state(
3512 &TransportStateSnapshot {
3513 packet_hashes: vec![[0x61; 32], [0x62; 32]],
3514 paths: vec![route.clone()],
3515 tunnels: vec![PersistedTunnel {
3516 tunnel_id: [0x71; 32],
3517 interface_hash: None,
3518 paths: vec![PersistedPath {
3519 interface_hash: None,
3520 ..route
3521 }],
3522 expires: now + 1_200.0,
3523 }],
3524 },
3525 &paths.storage,
3526 false,
3527 )
3528 .unwrap();
3529
3530 let node = RnsNode::from_config(Some(dir.path()), Box::new(NoopCallbacks)).unwrap();
3531 let QueryResponse::TransportStateSnapshot(restored) =
3532 node.query(QueryRequest::TransportStateSnapshot).unwrap()
3533 else {
3534 panic!("unexpected transport snapshot response");
3535 };
3536 assert_eq!(restored.packet_hashes, vec![[0x61; 32], [0x62; 32]]);
3537 assert_eq!(restored.paths.len(), 1);
3538 assert_eq!(restored.paths[0].destination_hash, destination_hash);
3539 assert_eq!(restored.paths[0].received_from, [0x41; 16]);
3540 assert_eq!(restored.tunnels.len(), 1);
3541 assert_eq!(restored.tunnels[0].tunnel_id, [0x71; 32]);
3542 assert_eq!(restored.tunnels[0].interface_hash, None);
3543 node.shutdown();
3544 }
3545
3546 #[test]
3547 fn from_config_cleans_persistent_ratchets_after_loading_known_destinations() {
3548 fn hex(bytes: &[u8]) -> String {
3549 bytes.iter().map(|byte| format!("{:02x}", byte)).collect()
3550 }
3551
3552 let dir = tempdir().unwrap();
3553 fs::write(
3554 dir.path().join("config"),
3555 "[reticulum]\nenable_transport = False\nshare_instance = False\nratchet_expiry = 300\n",
3556 )
3557 .unwrap();
3558 let paths = storage::ensure_storage_dirs(dir.path()).unwrap();
3559 let known_live = [0x01; 16];
3560 let known_expired = [0x02; 16];
3561 let unknown = [0x03; 16];
3562 let known_corrupt = [0x04; 16];
3563 let now = time::now();
3564
3565 let identity = Identity::new(&mut OsRng);
3566 let known = std::collections::HashMap::from([
3567 (
3568 known_live,
3569 storage::KnownDestination {
3570 identity_hash: *identity.hash(),
3571 public_key: identity.get_public_key().unwrap(),
3572 app_data: None,
3573 hops: 1,
3574 received_at: now,
3575 receiving_interface: 0,
3576 was_used: false,
3577 last_used_at: None,
3578 retained: false,
3579 },
3580 ),
3581 (
3582 known_expired,
3583 storage::KnownDestination {
3584 identity_hash: *identity.hash(),
3585 public_key: identity.get_public_key().unwrap(),
3586 app_data: None,
3587 hops: 1,
3588 received_at: now,
3589 receiving_interface: 0,
3590 was_used: false,
3591 last_used_at: None,
3592 retained: false,
3593 },
3594 ),
3595 (
3596 known_corrupt,
3597 storage::KnownDestination {
3598 identity_hash: *identity.hash(),
3599 public_key: identity.get_public_key().unwrap(),
3600 app_data: None,
3601 hops: 1,
3602 received_at: now,
3603 receiving_interface: 0,
3604 was_used: false,
3605 last_used_at: None,
3606 retained: false,
3607 },
3608 ),
3609 ]);
3610 storage::save_known_destinations(&known, &paths.storage.join("known_destinations"))
3611 .unwrap();
3612
3613 let store = storage::FsRatchetStore::new(paths.ratchets.clone());
3614 store
3615 .remember(
3616 known_live,
3617 storage::RatchetEntry {
3618 ratchet: [0x11; 32],
3619 received_at: now,
3620 },
3621 )
3622 .unwrap();
3623 store
3624 .remember(
3625 known_expired,
3626 storage::RatchetEntry {
3627 ratchet: [0x22; 32],
3628 received_at: now - 1000.0,
3629 },
3630 )
3631 .unwrap();
3632 store
3633 .remember(
3634 unknown,
3635 storage::RatchetEntry {
3636 ratchet: [0x33; 32],
3637 received_at: now,
3638 },
3639 )
3640 .unwrap();
3641 fs::write(paths.ratchets.join(hex(&known_corrupt)), b"not msgpack").unwrap();
3642 fs::write(paths.ratchets.join("0102.out"), b"temp").unwrap();
3643
3644 let node = RnsNode::from_config(Some(dir.path()), Box::new(NoopCallbacks)).unwrap();
3645
3646 assert!(paths.ratchets.join(hex(&known_live)).exists());
3647 assert!(!paths.ratchets.join(hex(&known_expired)).exists());
3648 assert!(!paths.ratchets.join(hex(&unknown)).exists());
3649 assert!(!paths.ratchets.join(hex(&known_corrupt)).exists());
3650 assert!(!paths.ratchets.join("0102.out").exists());
3651
3652 node.shutdown();
3653 }
3654
3655 #[test]
3656 fn start_with_identity() {
3657 let identity = Identity::new(&mut OsRng);
3658 let hash = *identity.hash();
3659 let node = RnsNode::start(
3660 NodeConfig {
3661 panic_on_interface_error: false,
3662 transport_enabled: true,
3663 static_transport_identity: false,
3664 local_hops_delta: false,
3665 identity: Some(identity),
3666 interfaces: vec![],
3667 share_instance: false,
3668 instance_name: "default".into(),
3669 shared_instance_port: 37428,
3670 rpc_port: 0,
3671 cache_dir: None,
3672 ratchet_store: None,
3673 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
3674 management: Default::default(),
3675 probe_port: None,
3676 probe_addrs: vec![],
3677 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
3678 device: None,
3679 hooks: Vec::new(),
3680 discover_interfaces: false,
3681 autoconnect_interface_mode: None,
3682 autoconnect_interface_gravity: 0,
3683 autoconnect_announces_to_internal: false,
3684 discovery_required_value: None,
3685 respond_to_probes: false,
3686 prefer_shorter_path: false,
3687 max_paths_per_destination: 1,
3688 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
3689 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
3690 max_path_destinations: usize::MAX,
3691 max_tunnel_destinations_total: usize::MAX,
3692 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
3693 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
3694 announce_table_ttl: Duration::from_secs(
3695 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
3696 ),
3697 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
3698 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
3699 interface_writer_queue_capacity:
3700 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
3701 announce_rate_defaults: AnnounceRateDefaults::default(),
3702 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
3703 ),
3704 #[cfg(feature = "iface-backbone")]
3705 backbone_peer_pool: None,
3706 announce_sig_cache_enabled: true,
3707 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
3708 announce_sig_cache_ttl: Duration::from_secs(
3709 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
3710 ),
3711 registry: None,
3712 #[cfg(feature = "hooks")]
3713 provider_bridge: None,
3714 },
3715 Box::new(NoopCallbacks),
3716 )
3717 .unwrap();
3718 let _ = hash;
3720 node.shutdown();
3721 }
3722
3723 #[test]
3724 fn start_generates_identity() {
3725 let node = RnsNode::start(
3726 NodeConfig {
3727 panic_on_interface_error: false,
3728 transport_enabled: false,
3729 static_transport_identity: false,
3730 local_hops_delta: false,
3731 identity: None,
3732 interfaces: vec![],
3733 share_instance: false,
3734 instance_name: "default".into(),
3735 shared_instance_port: 37428,
3736 rpc_port: 0,
3737 cache_dir: None,
3738 ratchet_store: None,
3739 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
3740 management: Default::default(),
3741 probe_port: None,
3742 probe_addrs: vec![],
3743 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
3744 device: None,
3745 hooks: Vec::new(),
3746 discover_interfaces: false,
3747 autoconnect_interface_mode: None,
3748 autoconnect_interface_gravity: 0,
3749 autoconnect_announces_to_internal: false,
3750 discovery_required_value: None,
3751 respond_to_probes: false,
3752 prefer_shorter_path: false,
3753 max_paths_per_destination: 1,
3754 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
3755 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
3756 max_path_destinations: usize::MAX,
3757 max_tunnel_destinations_total: usize::MAX,
3758 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
3759 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
3760 announce_table_ttl: Duration::from_secs(
3761 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
3762 ),
3763 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
3764 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
3765 interface_writer_queue_capacity:
3766 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
3767 announce_rate_defaults: AnnounceRateDefaults::default(),
3768 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
3769 ),
3770 #[cfg(feature = "iface-backbone")]
3771 backbone_peer_pool: None,
3772 announce_sig_cache_enabled: true,
3773 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
3774 announce_sig_cache_ttl: Duration::from_secs(
3775 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
3776 ),
3777 registry: None,
3778 #[cfg(feature = "hooks")]
3779 provider_bridge: None,
3780 },
3781 Box::new(NoopCallbacks),
3782 )
3783 .unwrap();
3784 node.shutdown();
3786 }
3787
3788 #[test]
3789 fn from_config_creates_identity() {
3790 let dir = std::env::temp_dir().join(format!("rns-test-fc-{}", std::process::id()));
3791 let _ = fs::remove_dir_all(&dir);
3792 fs::create_dir_all(&dir).unwrap();
3793
3794 fs::write(
3796 dir.join("config"),
3797 "[reticulum]\nenable_transport = False\n",
3798 )
3799 .unwrap();
3800
3801 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
3802
3803 assert!(dir.join("storage/identities/identity").exists());
3805
3806 node.shutdown();
3807 let _ = fs::remove_dir_all(&dir);
3808 }
3809
3810 #[test]
3811 fn from_config_loads_identity() {
3812 let dir = std::env::temp_dir().join(format!("rns-test-fl-{}", std::process::id()));
3813 let _ = fs::remove_dir_all(&dir);
3814 fs::create_dir_all(dir.join("storage/identities")).unwrap();
3815
3816 let identity = Identity::new(&mut OsRng);
3818 let hash = *identity.hash();
3819 storage::save_identity(&identity, &dir.join("storage/identities/identity")).unwrap();
3820
3821 fs::write(
3822 dir.join("config"),
3823 "[reticulum]\nenable_transport = False\n",
3824 )
3825 .unwrap();
3826
3827 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
3828
3829 let loaded = storage::load_identity(&dir.join("storage/identities/identity")).unwrap();
3831 assert_eq!(*loaded.hash(), hash);
3832
3833 node.shutdown();
3834 let _ = fs::remove_dir_all(&dir);
3835 }
3836
3837 #[test]
3838 fn from_config_tcp_server() {
3839 let dir = std::env::temp_dir().join(format!("rns-test-fts-{}", std::process::id()));
3840 let _ = fs::remove_dir_all(&dir);
3841 fs::create_dir_all(&dir).unwrap();
3842
3843 let port = std::net::TcpListener::bind("127.0.0.1:0")
3845 .unwrap()
3846 .local_addr()
3847 .unwrap()
3848 .port();
3849
3850 let config = format!(
3851 r#"
3852[reticulum]
3853enable_transport = False
3854
3855[interfaces]
3856 [[Test TCP Server]]
3857 type = TCPServerInterface
3858 listen_ip = 127.0.0.1
3859 listen_port = {}
3860"#,
3861 port
3862 );
3863
3864 fs::write(dir.join("config"), config).unwrap();
3865
3866 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
3867
3868 thread::sleep(Duration::from_millis(100));
3870
3871 let _client = std::net::TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3873
3874 node.shutdown();
3875 let _ = fs::remove_dir_all(&dir);
3876 }
3877
3878 #[test]
3879 fn from_config_starts_rpc_when_share_instance_enabled() {
3880 let dir = std::env::temp_dir().join(format!("rns-test-rpc-{}", std::process::id()));
3881 let _ = fs::remove_dir_all(&dir);
3882 fs::create_dir_all(&dir).unwrap();
3883
3884 let rpc_port = std::net::TcpListener::bind("127.0.0.1:0")
3885 .unwrap()
3886 .local_addr()
3887 .unwrap()
3888 .port();
3889
3890 let config = format!(
3891 r#"
3892[reticulum]
3893enable_transport = False
3894share_instance = Yes
3895instance_control_port = {}
3896
3897[interfaces]
3898"#,
3899 rpc_port
3900 );
3901
3902 fs::write(dir.join("config"), config).unwrap();
3903
3904 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
3905
3906 thread::sleep(Duration::from_millis(100));
3907
3908 let _client = std::net::TcpStream::connect(format!("127.0.0.1:{}", rpc_port)).unwrap();
3909
3910 node.shutdown();
3911 let _ = fs::remove_dir_all(&dir);
3912 }
3913
3914 #[test]
3915 fn from_config_starts_rpc_when_transport_enabled() {
3916 let dir =
3917 std::env::temp_dir().join(format!("rns-test-rpc-transport-{}", std::process::id()));
3918 let _ = fs::remove_dir_all(&dir);
3919 fs::create_dir_all(&dir).unwrap();
3920
3921 let rpc_port = std::net::TcpListener::bind("127.0.0.1:0")
3922 .unwrap()
3923 .local_addr()
3924 .unwrap()
3925 .port();
3926
3927 let config = format!(
3928 r#"
3929[reticulum]
3930enable_transport = True
3931share_instance = Yes
3932instance_control_port = {}
3933
3934[interfaces]
3935"#,
3936 rpc_port
3937 );
3938
3939 fs::write(dir.join("config"), config).unwrap();
3940
3941 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
3942
3943 thread::sleep(Duration::from_millis(100));
3944
3945 let _client = std::net::TcpStream::connect(format!("127.0.0.1:{}", rpc_port)).unwrap();
3946
3947 node.shutdown();
3948 let _ = fs::remove_dir_all(&dir);
3949 }
3950
3951 #[test]
3952 fn from_config_starts_rpc_when_tcp_client_is_unreachable() {
3953 let dir =
3954 std::env::temp_dir().join(format!("rns-test-rpc-unreachable-{}", std::process::id()));
3955 let _ = fs::remove_dir_all(&dir);
3956 fs::create_dir_all(&dir).unwrap();
3957
3958 let rpc_port = std::net::TcpListener::bind("127.0.0.1:0")
3959 .unwrap()
3960 .local_addr()
3961 .unwrap()
3962 .port();
3963 let unreachable_port = std::net::TcpListener::bind("127.0.0.1:0")
3964 .unwrap()
3965 .local_addr()
3966 .unwrap()
3967 .port();
3968
3969 let config = format!(
3970 r#"
3971[reticulum]
3972enable_transport = True
3973share_instance = Yes
3974instance_control_port = {}
3975
3976[interfaces]
3977 [[Unreachable Upstream]]
3978 type = TCPClientInterface
3979 target_host = 127.0.0.1
3980 target_port = {}
3981"#,
3982 rpc_port, unreachable_port
3983 );
3984
3985 fs::write(dir.join("config"), config).unwrap();
3986
3987 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
3988
3989 thread::sleep(Duration::from_millis(100));
3990
3991 let _client = std::net::TcpStream::connect(format!("127.0.0.1:{}", rpc_port)).unwrap();
3992
3993 node.shutdown();
3994 let _ = fs::remove_dir_all(&dir);
3995 }
3996
3997 #[test]
3998 fn test_parse_interface_mode() {
3999 use rns_core::constants::*;
4000
4001 assert_eq!(parse_interface_mode("full"), MODE_FULL);
4002 assert_eq!(parse_interface_mode("Full"), MODE_FULL);
4003 assert_eq!(parse_interface_mode("access_point"), MODE_ACCESS_POINT);
4004 assert_eq!(parse_interface_mode("accesspoint"), MODE_ACCESS_POINT);
4005 assert_eq!(parse_interface_mode("ap"), MODE_ACCESS_POINT);
4006 assert_eq!(parse_interface_mode("AP"), MODE_ACCESS_POINT);
4007 assert_eq!(parse_interface_mode("pointtopoint"), MODE_POINT_TO_POINT);
4008 assert_eq!(parse_interface_mode("ptp"), MODE_POINT_TO_POINT);
4009 assert_eq!(parse_interface_mode("roaming"), MODE_ROAMING);
4010 assert_eq!(parse_interface_mode("boundary"), MODE_BOUNDARY);
4011 assert_eq!(parse_interface_mode("gateway"), MODE_GATEWAY);
4012 assert_eq!(parse_interface_mode("gw"), MODE_GATEWAY);
4013 assert_eq!(parse_interface_mode("internal"), MODE_INTERNAL);
4014 assert_eq!(parse_interface_mode("Internal"), MODE_INTERNAL);
4015 assert_eq!(parse_interface_mode("invalid"), MODE_FULL);
4017 }
4018
4019 #[test]
4020 fn discovery_mode_normalization_matrix() {
4021 use rns_core::constants::*;
4022
4023 let interface_types = [
4024 "RNodeInterface",
4025 "RNodeMultiInterface",
4026 "BackboneInterface",
4027 "TCPServerInterface",
4028 "AutoInterface",
4029 ];
4030 let modes = [
4031 MODE_FULL,
4032 MODE_ACCESS_POINT,
4033 MODE_POINT_TO_POINT,
4034 MODE_ROAMING,
4035 MODE_BOUNDARY,
4036 MODE_GATEWAY,
4037 MODE_INTERNAL,
4038 ];
4039
4040 for interface_type in interface_types {
4041 for configured_mode in modes {
4042 for discoverable in [false, true] {
4043 for ignore_warnings in [false, true] {
4044 let expected = if !discoverable
4045 || ignore_warnings
4046 || matches!(
4047 configured_mode,
4048 MODE_ACCESS_POINT | MODE_GATEWAY | MODE_INTERNAL
4049 ) {
4050 configured_mode
4051 } else if matches!(interface_type, "RNodeInterface" | "RNodeMultiInterface")
4052 {
4053 MODE_ACCESS_POINT
4054 } else {
4055 MODE_GATEWAY
4056 };
4057 assert_eq!(
4058 normalize_discovery_mode(
4059 interface_type,
4060 configured_mode,
4061 discoverable,
4062 ignore_warnings,
4063 ),
4064 expected,
4065 "type={interface_type}, mode={configured_mode}, discoverable={discoverable}, ignore={ignore_warnings}",
4066 );
4067 }
4068 }
4069 }
4070 }
4071 }
4072
4073 #[test]
4074 fn to_node_config_serial() {
4075 let dir = std::env::temp_dir().join(format!("rns-test-serial-{}", std::process::id()));
4079 let _ = fs::remove_dir_all(&dir);
4080 fs::create_dir_all(&dir).unwrap();
4081
4082 let config = r#"
4083[reticulum]
4084enable_transport = False
4085
4086[interfaces]
4087 [[Test Serial Port]]
4088 type = SerialInterface
4089 port = /dev/nonexistent_rns_test_serial
4090 speed = 115200
4091 databits = 8
4092 parity = E
4093 stopbits = 1
4094 interface_mode = ptp
4095 networkname = testnet
4096"#;
4097 fs::write(dir.join("config"), config).unwrap();
4098
4099 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks))
4101 .expect("Config should parse; interface failure is non-fatal");
4102 node.shutdown();
4103
4104 let _ = fs::remove_dir_all(&dir);
4105 }
4106
4107 #[test]
4108 fn to_node_config_kiss() {
4109 let dir = std::env::temp_dir().join(format!("rns-test-kiss-{}", std::process::id()));
4111 let _ = fs::remove_dir_all(&dir);
4112 fs::create_dir_all(&dir).unwrap();
4113
4114 let config = r#"
4115[reticulum]
4116enable_transport = False
4117
4118[interfaces]
4119 [[Test KISS TNC]]
4120 type = KISSInterface
4121 port = /dev/nonexistent_rns_test_kiss
4122 speed = 9600
4123 preamble = 500
4124 txtail = 30
4125 persistence = 128
4126 slottime = 40
4127 flow_control = True
4128 id_interval = 600
4129 id_callsign = TEST0
4130 interface_mode = full
4131 passphrase = secretkey
4132"#;
4133 fs::write(dir.join("config"), config).unwrap();
4134
4135 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks))
4137 .expect("Config should parse; interface failure is non-fatal");
4138 node.shutdown();
4139
4140 let _ = fs::remove_dir_all(&dir);
4141 }
4142
4143 #[test]
4144 fn config_parser_accepts_recursive_prs_interface_option() {
4145 let config = r#"
4146[reticulum]
4147enable_transport = True
4148
4149[interfaces]
4150 [[Recursive Client]]
4151 type = TCPClientInterface
4152 target_host = 127.0.0.1
4153 target_port = 4242
4154 interface_mode = full
4155 recursive_prs = yes
4156
4157 [[Default Client]]
4158 type = TCPClientInterface
4159 target_host = 127.0.0.1
4160 target_port = 4243
4161 interface_mode = gateway
4162"#;
4163
4164 let parsed = config::parse(config).unwrap();
4165
4166 assert_eq!(parsed.interfaces.len(), 2);
4167 assert_eq!(parsed.interfaces[0].name, "Recursive Client");
4168 assert_eq!(parsed.interfaces[0].mode, "full");
4169 assert_eq!(
4170 parsed.interfaces[0]
4171 .params
4172 .get("recursive_prs")
4173 .map(String::as_str),
4174 Some("yes")
4175 );
4176 assert_eq!(parsed.interfaces[1].name, "Default Client");
4177 assert_eq!(parsed.interfaces[1].mode, "gateway");
4178 assert!(!parsed.interfaces[1].params.contains_key("recursive_prs"));
4179 }
4180
4181 #[test]
4182 fn config_parser_accepts_announces_from_internal_interface_option() {
4183 let config = r#"
4184[reticulum]
4185enable_transport = True
4186
4187[interfaces]
4188 [[Filtered Client]]
4189 type = TCPClientInterface
4190 target_host = 127.0.0.1
4191 target_port = 4242
4192 interface_mode = gateway
4193 announces_from_internal = no
4194
4195 [[Default Client]]
4196 type = TCPClientInterface
4197 target_host = 127.0.0.1
4198 target_port = 4243
4199 interface_mode = full
4200"#;
4201
4202 let parsed = config::parse(config).unwrap();
4203
4204 assert_eq!(parsed.interfaces.len(), 2);
4205 assert_eq!(parsed.interfaces[0].name, "Filtered Client");
4206 assert_eq!(parsed.interfaces[0].mode, "gateway");
4207 assert_eq!(
4208 parsed.interfaces[0]
4209 .params
4210 .get("announces_from_internal")
4211 .map(String::as_str),
4212 Some("no")
4213 );
4214 assert_eq!(parsed.interfaces[1].name, "Default Client");
4215 assert_eq!(parsed.interfaces[1].mode, "full");
4216 assert!(!parsed.interfaces[1]
4217 .params
4218 .contains_key("announces_from_internal"));
4219 }
4220
4221 #[test]
4222 fn config_parser_preserves_announces_to_internal_tristate() {
4223 let config = r#"
4224[interfaces]
4225 [[Allowed Boundary]]
4226 type = TCPClientInterface
4227 target_host = 127.0.0.1
4228 target_port = 4242
4229 announces_to_internal = yes
4230
4231 [[Blocked Boundary]]
4232 type = TCPClientInterface
4233 target_host = 127.0.0.1
4234 target_port = 4243
4235 announces_to_internal = no
4236
4237 [[Default Boundary]]
4238 type = TCPClientInterface
4239 target_host = 127.0.0.1
4240 target_port = 4244
4241"#;
4242
4243 let parsed = config::parse(config).unwrap();
4244 let parsed_value = |index: usize| {
4245 parsed.interfaces[index]
4246 .params
4247 .get("announces_to_internal")
4248 .and_then(|value| config::parse_bool_pub(value))
4249 };
4250
4251 assert_eq!(parsed_value(0), Some(true));
4252 assert_eq!(parsed_value(1), Some(false));
4253 assert_eq!(parsed_value(2), None);
4254 }
4255
4256 #[test]
4257 fn interface_gravity_parser_accepts_signed_values_and_uses_global_default() {
4258 assert_eq!(parse_interface_gravity(Some("7"), -2), 7);
4259 assert_eq!(parse_interface_gravity(Some("-4"), 2), -4);
4260 assert_eq!(parse_interface_gravity(None, -6), -6);
4261 }
4262
4263 #[test]
4264 fn config_parser_accepts_local_hops_delta_option() {
4265 let cfg = r#"
4266[reticulum]
4267local_hops_delta = yes
4268"#;
4269 let parsed = config::parse(cfg).unwrap();
4270 assert!(parsed.reticulum.local_hops_delta);
4271 }
4272
4273 #[test]
4274 fn config_parser_accepts_static_transport_identity_option() {
4275 let config = r#"
4276[reticulum]
4277enable_transport = false
4278static_transport_identity = yes
4279"#;
4280
4281 let parsed = config::parse(config).unwrap();
4282
4283 assert!(!parsed.reticulum.enable_transport);
4284 assert!(parsed.reticulum.static_transport_identity);
4285 }
4286
4287 #[test]
4288 fn non_transport_node_uses_ephemeral_transport_identity_by_default() {
4289 let identity = Identity::new(&mut rns_crypto::FixedRng::new(&[0x41; 32]));
4290 let stored_hash = *identity.hash();
4291 let node = RnsNode::start(
4292 NodeConfig {
4293 transport_enabled: false,
4294 static_transport_identity: false,
4295 local_hops_delta: false,
4296 identity: Some(identity),
4297 share_instance: false,
4298 ..Default::default()
4299 },
4300 Box::new(NoopCallbacks),
4301 )
4302 .unwrap();
4303
4304 let response = node.query(QueryRequest::TransportIdentity).unwrap();
4305 let QueryResponse::TransportIdentity(Some(hash)) = response else {
4306 panic!("transport identity query returned {response:?}");
4307 };
4308 assert_ne!(hash, stored_hash);
4309
4310 node.shutdown();
4311 }
4312
4313 #[test]
4314 fn non_transport_node_can_use_static_transport_identity() {
4315 let identity = Identity::new(&mut rns_crypto::FixedRng::new(&[0x42; 32]));
4316 let stored_hash = *identity.hash();
4317 let node = RnsNode::start(
4318 NodeConfig {
4319 transport_enabled: false,
4320 static_transport_identity: true,
4321 local_hops_delta: false,
4322 identity: Some(identity),
4323 share_instance: false,
4324 ..Default::default()
4325 },
4326 Box::new(NoopCallbacks),
4327 )
4328 .unwrap();
4329
4330 let response = node.query(QueryRequest::TransportIdentity).unwrap();
4331 let QueryResponse::TransportIdentity(Some(hash)) = response else {
4332 panic!("transport identity query returned {response:?}");
4333 };
4334 assert_eq!(hash, stored_hash);
4335
4336 node.shutdown();
4337 }
4338
4339 #[test]
4340 fn test_extract_ifac_config() {
4341 use std::collections::HashMap;
4342
4343 let params: HashMap<String, String> = HashMap::new();
4345 assert!(extract_ifac_config(¶ms, 16).is_none());
4346
4347 let mut params = HashMap::new();
4349 params.insert("networkname".into(), "testnet".into());
4350 let ifac = extract_ifac_config(¶ms, 16).unwrap();
4351 assert_eq!(ifac.netname.as_deref(), Some("testnet"));
4352 assert!(ifac.netkey.is_none());
4353 assert_eq!(ifac.size, 16);
4354
4355 let mut params = HashMap::new();
4357 params.insert("passphrase".into(), "secret".into());
4358 params.insert("ifac_size".into(), "64".into()); let ifac = extract_ifac_config(¶ms, 16).unwrap();
4360 assert!(ifac.netname.is_none());
4361 assert_eq!(ifac.netkey.as_deref(), Some("secret"));
4362 assert_eq!(ifac.size, 8);
4363
4364 let mut params = HashMap::new();
4366 params.insert("network_name".into(), "mynet".into());
4367 params.insert("pass_phrase".into(), "mykey".into());
4368 let ifac = extract_ifac_config(¶ms, 8).unwrap();
4369 assert_eq!(ifac.netname.as_deref(), Some("mynet"));
4370 assert_eq!(ifac.netkey.as_deref(), Some("mykey"));
4371 assert_eq!(ifac.size, 8);
4372 }
4373
4374 #[test]
4375 fn to_node_config_rnode() {
4376 let dir = std::env::temp_dir().join(format!("rns-test-rnode-{}", std::process::id()));
4379 let _ = fs::remove_dir_all(&dir);
4380 fs::create_dir_all(&dir).unwrap();
4381
4382 let config = r#"
4383[reticulum]
4384enable_transport = False
4385
4386[interfaces]
4387 [[Test RNode]]
4388 type = RNodeInterface
4389 port = /dev/nonexistent_rns_test_rnode
4390 frequency = 867200000
4391 bandwidth = 125000
4392 txpower = 7
4393 spreadingfactor = 8
4394 codingrate = 5
4395 flow_control = True
4396 st_alock = 5.0
4397 lt_alock = 2.5
4398 interface_mode = full
4399 networkname = testnet
4400"#;
4401 fs::write(dir.join("config"), config).unwrap();
4402
4403 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks))
4405 .expect("Config should parse; interface failure is non-fatal");
4406 node.shutdown();
4407
4408 let _ = fs::remove_dir_all(&dir);
4409 }
4410
4411 #[test]
4412 fn to_node_config_pipe() {
4413 let dir = std::env::temp_dir().join(format!("rns-test-pipe-{}", std::process::id()));
4416 let _ = fs::remove_dir_all(&dir);
4417 fs::create_dir_all(&dir).unwrap();
4418
4419 let config = r#"
4420[reticulum]
4421enable_transport = False
4422
4423[interfaces]
4424 [[Test Pipe]]
4425 type = PipeInterface
4426 command = cat
4427 respawn_delay = 5000
4428 interface_mode = full
4429"#;
4430 fs::write(dir.join("config"), config).unwrap();
4431
4432 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
4433 node.shutdown();
4435
4436 let _ = fs::remove_dir_all(&dir);
4437 }
4438
4439 #[test]
4440 fn to_node_config_backbone() {
4441 let dir = std::env::temp_dir().join(format!("rns-test-backbone-{}", std::process::id()));
4443 let _ = fs::remove_dir_all(&dir);
4444 fs::create_dir_all(&dir).unwrap();
4445
4446 let port = std::net::TcpListener::bind("127.0.0.1:0")
4447 .unwrap()
4448 .local_addr()
4449 .unwrap()
4450 .port();
4451
4452 let config = format!(
4453 r#"
4454[reticulum]
4455enable_transport = False
4456
4457[interfaces]
4458 [[Test Backbone]]
4459 type = BackboneInterface
4460 listen_ip = 127.0.0.1
4461 listen_port = {}
4462 interface_mode = full
4463"#,
4464 port
4465 );
4466
4467 fs::write(dir.join("config"), config).unwrap();
4468
4469 let node = RnsNode::from_config(Some(&dir), Box::new(NoopCallbacks)).unwrap();
4470
4471 thread::sleep(Duration::from_millis(100));
4473
4474 {
4476 let _client = std::net::TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
4477 }
4479
4480 thread::sleep(Duration::from_millis(50));
4482
4483 node.shutdown();
4484 let _ = fs::remove_dir_all(&dir);
4485 }
4486
4487 #[test]
4488 fn rnode_config_defaults() {
4489 use crate::interface::rnode::{RNodeConfig, RNodeSubConfig};
4490
4491 let config = RNodeConfig::default();
4492 assert_eq!(config.speed, 115200);
4493 assert!(config.subinterfaces.is_empty());
4494 assert!(config.id_interval.is_none());
4495 assert!(config.id_callsign.is_none());
4496
4497 let sub = RNodeSubConfig {
4498 name: "test".into(),
4499 vport: 0,
4500 outgoing: true,
4501 frequency: 868_000_000,
4502 bandwidth: 125_000,
4503 txpower: 7,
4504 spreading_factor: 8,
4505 coding_rate: 5,
4506 flow_control: false,
4507 st_alock: None,
4508 lt_alock: None,
4509 };
4510 assert_eq!(sub.frequency, 868_000_000);
4511 assert_eq!(sub.bandwidth, 125_000);
4512 assert!(!sub.flow_control);
4513 }
4514
4515 #[test]
4520 fn announce_builds_valid_packet() {
4521 let identity = Identity::new(&mut OsRng);
4522 let identity_hash = rns_core::types::IdentityHash(*identity.hash());
4523
4524 let node = RnsNode::start(
4525 NodeConfig {
4526 panic_on_interface_error: false,
4527 transport_enabled: false,
4528 static_transport_identity: false,
4529 local_hops_delta: false,
4530 identity: None,
4531 interfaces: vec![],
4532 share_instance: false,
4533 instance_name: "default".into(),
4534 shared_instance_port: 37428,
4535 rpc_port: 0,
4536 cache_dir: None,
4537 ratchet_store: None,
4538 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
4539 management: Default::default(),
4540 probe_port: None,
4541 probe_addrs: vec![],
4542 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
4543 device: None,
4544 hooks: Vec::new(),
4545 discover_interfaces: false,
4546 autoconnect_interface_mode: None,
4547 autoconnect_interface_gravity: 0,
4548 autoconnect_announces_to_internal: false,
4549 discovery_required_value: None,
4550 respond_to_probes: false,
4551 prefer_shorter_path: false,
4552 max_paths_per_destination: 1,
4553 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
4554 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
4555 max_path_destinations: usize::MAX,
4556 max_tunnel_destinations_total: usize::MAX,
4557 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
4558 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
4559 announce_table_ttl: Duration::from_secs(
4560 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
4561 ),
4562 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
4563 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
4564 interface_writer_queue_capacity:
4565 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
4566 announce_rate_defaults: AnnounceRateDefaults::default(),
4567 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
4568 ),
4569 #[cfg(feature = "iface-backbone")]
4570 backbone_peer_pool: None,
4571 announce_sig_cache_enabled: true,
4572 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
4573 announce_sig_cache_ttl: Duration::from_secs(
4574 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
4575 ),
4576 registry: None,
4577 #[cfg(feature = "hooks")]
4578 provider_bridge: None,
4579 },
4580 Box::new(NoopCallbacks),
4581 )
4582 .unwrap();
4583
4584 let dest = crate::destination::Destination::single_in("test", &["echo"], identity_hash);
4585
4586 node.register_destination(dest.hash.0, dest.dest_type.to_wire_constant())
4588 .unwrap();
4589
4590 let result = node.announce(&dest, &identity, Some(b"hello"));
4592 assert!(result.is_ok());
4593
4594 node.shutdown();
4595 }
4596
4597 #[test]
4598 fn has_path_and_hops_to() {
4599 let node = RnsNode::start(
4600 NodeConfig {
4601 panic_on_interface_error: false,
4602 transport_enabled: false,
4603 static_transport_identity: false,
4604 local_hops_delta: false,
4605 identity: None,
4606 interfaces: vec![],
4607 share_instance: false,
4608 instance_name: "default".into(),
4609 shared_instance_port: 37428,
4610 rpc_port: 0,
4611 cache_dir: None,
4612 ratchet_store: None,
4613 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
4614 management: Default::default(),
4615 probe_port: None,
4616 probe_addrs: vec![],
4617 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
4618 device: None,
4619 hooks: Vec::new(),
4620 discover_interfaces: false,
4621 autoconnect_interface_mode: None,
4622 autoconnect_interface_gravity: 0,
4623 autoconnect_announces_to_internal: false,
4624 discovery_required_value: None,
4625 respond_to_probes: false,
4626 prefer_shorter_path: false,
4627 max_paths_per_destination: 1,
4628 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
4629 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
4630 max_path_destinations: usize::MAX,
4631 max_tunnel_destinations_total: usize::MAX,
4632 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
4633 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
4634 announce_table_ttl: Duration::from_secs(
4635 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
4636 ),
4637 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
4638 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
4639 interface_writer_queue_capacity:
4640 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
4641 announce_rate_defaults: AnnounceRateDefaults::default(),
4642 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
4643 ),
4644 #[cfg(feature = "iface-backbone")]
4645 backbone_peer_pool: None,
4646 announce_sig_cache_enabled: true,
4647 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
4648 announce_sig_cache_ttl: Duration::from_secs(
4649 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
4650 ),
4651 registry: None,
4652 #[cfg(feature = "hooks")]
4653 provider_bridge: None,
4654 },
4655 Box::new(NoopCallbacks),
4656 )
4657 .unwrap();
4658
4659 let dh = rns_core::types::DestHash([0xAA; 16]);
4660
4661 assert_eq!(node.has_path(&dh).unwrap(), false);
4663 assert_eq!(node.hops_to(&dh).unwrap(), None);
4664
4665 node.shutdown();
4666 }
4667
4668 #[test]
4669 fn recall_identity_none_when_unknown() {
4670 let node = RnsNode::start(
4671 NodeConfig {
4672 panic_on_interface_error: false,
4673 transport_enabled: false,
4674 static_transport_identity: false,
4675 local_hops_delta: false,
4676 identity: None,
4677 interfaces: vec![],
4678 share_instance: false,
4679 instance_name: "default".into(),
4680 shared_instance_port: 37428,
4681 rpc_port: 0,
4682 cache_dir: None,
4683 ratchet_store: None,
4684 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
4685 management: Default::default(),
4686 probe_port: None,
4687 probe_addrs: vec![],
4688 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
4689 device: None,
4690 hooks: Vec::new(),
4691 discover_interfaces: false,
4692 autoconnect_interface_mode: None,
4693 autoconnect_interface_gravity: 0,
4694 autoconnect_announces_to_internal: false,
4695 discovery_required_value: None,
4696 respond_to_probes: false,
4697 prefer_shorter_path: false,
4698 max_paths_per_destination: 1,
4699 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
4700 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
4701 max_path_destinations: usize::MAX,
4702 max_tunnel_destinations_total: usize::MAX,
4703 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
4704 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
4705 announce_table_ttl: Duration::from_secs(
4706 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
4707 ),
4708 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
4709 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
4710 interface_writer_queue_capacity:
4711 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
4712 announce_rate_defaults: AnnounceRateDefaults::default(),
4713 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
4714 ),
4715 #[cfg(feature = "iface-backbone")]
4716 backbone_peer_pool: None,
4717 announce_sig_cache_enabled: true,
4718 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
4719 announce_sig_cache_ttl: Duration::from_secs(
4720 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
4721 ),
4722 registry: None,
4723 #[cfg(feature = "hooks")]
4724 provider_bridge: None,
4725 },
4726 Box::new(NoopCallbacks),
4727 )
4728 .unwrap();
4729
4730 let dh = rns_core::types::DestHash([0xBB; 16]);
4731 assert!(node.recall_identity(&dh).unwrap().is_none());
4732
4733 node.shutdown();
4734 }
4735
4736 #[test]
4737 fn request_path_does_not_crash() {
4738 let node = RnsNode::start(
4739 NodeConfig {
4740 panic_on_interface_error: false,
4741 transport_enabled: false,
4742 static_transport_identity: false,
4743 local_hops_delta: false,
4744 identity: None,
4745 interfaces: vec![],
4746 share_instance: false,
4747 instance_name: "default".into(),
4748 shared_instance_port: 37428,
4749 rpc_port: 0,
4750 cache_dir: None,
4751 ratchet_store: None,
4752 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
4753 management: Default::default(),
4754 probe_port: None,
4755 probe_addrs: vec![],
4756 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
4757 device: None,
4758 hooks: Vec::new(),
4759 discover_interfaces: false,
4760 autoconnect_interface_mode: None,
4761 autoconnect_interface_gravity: 0,
4762 autoconnect_announces_to_internal: false,
4763 discovery_required_value: None,
4764 respond_to_probes: false,
4765 prefer_shorter_path: false,
4766 max_paths_per_destination: 1,
4767 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
4768 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
4769 max_path_destinations: usize::MAX,
4770 max_tunnel_destinations_total: usize::MAX,
4771 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
4772 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
4773 announce_table_ttl: Duration::from_secs(
4774 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
4775 ),
4776 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
4777 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
4778 interface_writer_queue_capacity:
4779 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
4780 announce_rate_defaults: AnnounceRateDefaults::default(),
4781 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
4782 ),
4783 #[cfg(feature = "iface-backbone")]
4784 backbone_peer_pool: None,
4785 announce_sig_cache_enabled: true,
4786 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
4787 announce_sig_cache_ttl: Duration::from_secs(
4788 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
4789 ),
4790 registry: None,
4791 #[cfg(feature = "hooks")]
4792 provider_bridge: None,
4793 },
4794 Box::new(NoopCallbacks),
4795 )
4796 .unwrap();
4797
4798 let dh = rns_core::types::DestHash([0xCC; 16]);
4799 assert!(node.request_path(&dh).is_ok());
4800
4801 thread::sleep(Duration::from_millis(50));
4803
4804 node.shutdown();
4805 }
4806
4807 #[test]
4808 fn create_link_returns_error_while_draining() {
4809 let node = RnsNode::start(NodeConfig::default(), Box::new(NoopCallbacks)).unwrap();
4810
4811 node.begin_drain(Duration::from_secs(1)).unwrap();
4812 assert!(node.create_link([0xAB; 16], [0xCD; 32]).is_err());
4813
4814 node.shutdown();
4815 }
4816
4817 #[test]
4818 fn request_path_returns_error_while_draining() {
4819 let node = RnsNode::start(NodeConfig::default(), Box::new(NoopCallbacks)).unwrap();
4820
4821 node.begin_drain(Duration::from_secs(1)).unwrap();
4822 assert!(node
4823 .request_path(&rns_core::types::DestHash([0xAB; 16]))
4824 .is_err());
4825
4826 node.shutdown();
4827 }
4828
4829 #[test]
4834 fn send_packet_returns_error_while_draining() {
4835 let node = RnsNode::start(NodeConfig::default(), Box::new(NoopCallbacks)).unwrap();
4836 let dest = crate::destination::Destination::plain("drain-test", &["send"]);
4837
4838 node.begin_drain(Duration::from_secs(1)).unwrap();
4839 assert!(node.send_packet(&dest, b"hello").is_err());
4840
4841 node.shutdown();
4842 }
4843
4844 #[test]
4845 fn send_packet_plain() {
4846 let node = RnsNode::start(
4847 NodeConfig {
4848 panic_on_interface_error: false,
4849 transport_enabled: false,
4850 static_transport_identity: false,
4851 local_hops_delta: false,
4852 identity: None,
4853 interfaces: vec![],
4854 share_instance: false,
4855 instance_name: "default".into(),
4856 shared_instance_port: 37428,
4857 rpc_port: 0,
4858 cache_dir: None,
4859 ratchet_store: None,
4860 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
4861 management: Default::default(),
4862 probe_port: None,
4863 probe_addrs: vec![],
4864 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
4865 device: None,
4866 hooks: Vec::new(),
4867 discover_interfaces: false,
4868 autoconnect_interface_mode: None,
4869 autoconnect_interface_gravity: 0,
4870 autoconnect_announces_to_internal: false,
4871 discovery_required_value: None,
4872 respond_to_probes: false,
4873 prefer_shorter_path: false,
4874 max_paths_per_destination: 1,
4875 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
4876 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
4877 max_path_destinations: usize::MAX,
4878 max_tunnel_destinations_total: usize::MAX,
4879 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
4880 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
4881 announce_table_ttl: Duration::from_secs(
4882 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
4883 ),
4884 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
4885 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
4886 interface_writer_queue_capacity:
4887 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
4888 announce_rate_defaults: AnnounceRateDefaults::default(),
4889 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
4890 ),
4891 #[cfg(feature = "iface-backbone")]
4892 backbone_peer_pool: None,
4893 announce_sig_cache_enabled: true,
4894 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
4895 announce_sig_cache_ttl: Duration::from_secs(
4896 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
4897 ),
4898 registry: None,
4899 #[cfg(feature = "hooks")]
4900 provider_bridge: None,
4901 },
4902 Box::new(NoopCallbacks),
4903 )
4904 .unwrap();
4905
4906 let dest = crate::destination::Destination::plain("test", &["echo"]);
4907 let result = node.send_packet(&dest, b"hello world");
4908 assert!(result.is_ok());
4909
4910 let packet_hash = result.unwrap();
4911 assert_ne!(packet_hash.0, [0u8; 32]);
4913
4914 thread::sleep(Duration::from_millis(50));
4916
4917 node.shutdown();
4918 }
4919
4920 #[test]
4921 fn send_packet_single_requires_public_key() {
4922 let node = RnsNode::start(
4923 NodeConfig {
4924 panic_on_interface_error: false,
4925 transport_enabled: false,
4926 static_transport_identity: false,
4927 local_hops_delta: false,
4928 identity: None,
4929 interfaces: vec![],
4930 share_instance: false,
4931 instance_name: "default".into(),
4932 shared_instance_port: 37428,
4933 rpc_port: 0,
4934 cache_dir: None,
4935 ratchet_store: None,
4936 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
4937 management: Default::default(),
4938 probe_port: None,
4939 probe_addrs: vec![],
4940 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
4941 device: None,
4942 hooks: Vec::new(),
4943 discover_interfaces: false,
4944 autoconnect_interface_mode: None,
4945 autoconnect_interface_gravity: 0,
4946 autoconnect_announces_to_internal: false,
4947 discovery_required_value: None,
4948 respond_to_probes: false,
4949 prefer_shorter_path: false,
4950 max_paths_per_destination: 1,
4951 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
4952 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
4953 max_path_destinations: usize::MAX,
4954 max_tunnel_destinations_total: usize::MAX,
4955 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
4956 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
4957 announce_table_ttl: Duration::from_secs(
4958 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
4959 ),
4960 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
4961 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
4962 interface_writer_queue_capacity:
4963 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
4964 announce_rate_defaults: AnnounceRateDefaults::default(),
4965 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
4966 ),
4967 #[cfg(feature = "iface-backbone")]
4968 backbone_peer_pool: None,
4969 announce_sig_cache_enabled: true,
4970 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
4971 announce_sig_cache_ttl: Duration::from_secs(
4972 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
4973 ),
4974 registry: None,
4975 #[cfg(feature = "hooks")]
4976 provider_bridge: None,
4977 },
4978 Box::new(NoopCallbacks),
4979 )
4980 .unwrap();
4981
4982 let dest = crate::destination::Destination::single_in(
4984 "test",
4985 &["echo"],
4986 rns_core::types::IdentityHash([0x42; 16]),
4987 );
4988 let result = node.send_packet(&dest, b"hello");
4989 assert!(result.is_err(), "single_in has no public_key, should fail");
4990
4991 node.shutdown();
4992 }
4993
4994 #[test]
4995 fn send_packet_single_encrypts() {
4996 let node = RnsNode::start(
4997 NodeConfig {
4998 panic_on_interface_error: false,
4999 transport_enabled: false,
5000 static_transport_identity: false,
5001 local_hops_delta: false,
5002 identity: None,
5003 interfaces: vec![],
5004 share_instance: false,
5005 instance_name: "default".into(),
5006 shared_instance_port: 37428,
5007 rpc_port: 0,
5008 cache_dir: None,
5009 ratchet_store: None,
5010 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
5011 management: Default::default(),
5012 probe_port: None,
5013 probe_addrs: vec![],
5014 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
5015 device: None,
5016 hooks: Vec::new(),
5017 discover_interfaces: false,
5018 autoconnect_interface_mode: None,
5019 autoconnect_interface_gravity: 0,
5020 autoconnect_announces_to_internal: false,
5021 discovery_required_value: None,
5022 respond_to_probes: false,
5023 prefer_shorter_path: false,
5024 max_paths_per_destination: 1,
5025 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
5026 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
5027 max_path_destinations: usize::MAX,
5028 max_tunnel_destinations_total: usize::MAX,
5029 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
5030 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
5031 announce_table_ttl: Duration::from_secs(
5032 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
5033 ),
5034 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
5035 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
5036 interface_writer_queue_capacity:
5037 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
5038 announce_rate_defaults: AnnounceRateDefaults::default(),
5039 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
5040 ),
5041 #[cfg(feature = "iface-backbone")]
5042 backbone_peer_pool: None,
5043 announce_sig_cache_enabled: true,
5044 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
5045 announce_sig_cache_ttl: Duration::from_secs(
5046 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
5047 ),
5048 registry: None,
5049 #[cfg(feature = "hooks")]
5050 provider_bridge: None,
5051 },
5052 Box::new(NoopCallbacks),
5053 )
5054 .unwrap();
5055
5056 let remote_identity = Identity::new(&mut OsRng);
5058 let recalled = crate::destination::AnnouncedIdentity {
5059 dest_hash: rns_core::types::DestHash([0xAA; 16]),
5060 identity_hash: rns_core::types::IdentityHash(*remote_identity.hash()),
5061 public_key: remote_identity.get_public_key().unwrap(),
5062 app_data: None,
5063 hops: 1,
5064 received_at: 0.0,
5065 receiving_interface: rns_core::transport::types::InterfaceId(0),
5066 rssi: Some(-100),
5067 snr: Some(10.5),
5068 };
5069 let dest = crate::destination::Destination::single_out("test", &["echo"], &recalled);
5070
5071 let result = node.send_packet(&dest, b"secret message");
5072 assert!(result.is_ok());
5073
5074 let packet_hash = result.unwrap();
5075 assert_ne!(packet_hash.0, [0u8; 32]);
5076
5077 thread::sleep(Duration::from_millis(50));
5078 node.shutdown();
5079 }
5080
5081 #[test]
5082 fn register_destination_with_proof_prove_all() {
5083 let node = RnsNode::start(
5084 NodeConfig {
5085 panic_on_interface_error: false,
5086 transport_enabled: false,
5087 static_transport_identity: false,
5088 local_hops_delta: false,
5089 identity: None,
5090 interfaces: vec![],
5091 share_instance: false,
5092 instance_name: "default".into(),
5093 shared_instance_port: 37428,
5094 rpc_port: 0,
5095 cache_dir: None,
5096 ratchet_store: None,
5097 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
5098 management: Default::default(),
5099 probe_port: None,
5100 probe_addrs: vec![],
5101 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
5102 device: None,
5103 hooks: Vec::new(),
5104 discover_interfaces: false,
5105 autoconnect_interface_mode: None,
5106 autoconnect_interface_gravity: 0,
5107 autoconnect_announces_to_internal: false,
5108 discovery_required_value: None,
5109 respond_to_probes: false,
5110 prefer_shorter_path: false,
5111 max_paths_per_destination: 1,
5112 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
5113 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
5114 max_path_destinations: usize::MAX,
5115 max_tunnel_destinations_total: usize::MAX,
5116 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
5117 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
5118 announce_table_ttl: Duration::from_secs(
5119 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
5120 ),
5121 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
5122 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
5123 interface_writer_queue_capacity:
5124 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
5125 announce_rate_defaults: AnnounceRateDefaults::default(),
5126 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
5127 ),
5128 #[cfg(feature = "iface-backbone")]
5129 backbone_peer_pool: None,
5130 announce_sig_cache_enabled: true,
5131 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
5132 announce_sig_cache_ttl: Duration::from_secs(
5133 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
5134 ),
5135 registry: None,
5136 #[cfg(feature = "hooks")]
5137 provider_bridge: None,
5138 },
5139 Box::new(NoopCallbacks),
5140 )
5141 .unwrap();
5142
5143 let identity = Identity::new(&mut OsRng);
5144 let ih = rns_core::types::IdentityHash(*identity.hash());
5145 let dest = crate::destination::Destination::single_in("echo", &["request"], ih)
5146 .set_proof_strategy(rns_core::types::ProofStrategy::ProveAll);
5147 let prv_key = identity.get_private_key().unwrap();
5148
5149 let result = node.register_destination_with_proof(&dest, Some(prv_key));
5150 assert!(result.is_ok());
5151
5152 thread::sleep(Duration::from_millis(50));
5154
5155 node.shutdown();
5156 }
5157
5158 #[test]
5159 fn register_destination_with_proof_prove_none() {
5160 let node = RnsNode::start(
5161 NodeConfig {
5162 panic_on_interface_error: false,
5163 transport_enabled: false,
5164 static_transport_identity: false,
5165 local_hops_delta: false,
5166 identity: None,
5167 interfaces: vec![],
5168 share_instance: false,
5169 instance_name: "default".into(),
5170 shared_instance_port: 37428,
5171 rpc_port: 0,
5172 cache_dir: None,
5173 ratchet_store: None,
5174 ratchet_expiry: Duration::from_secs(rns_core::constants::RATCHET_EXPIRY),
5175 management: Default::default(),
5176 probe_port: None,
5177 probe_addrs: vec![],
5178 probe_protocol: rns_core::holepunch::ProbeProtocol::Rnsp,
5179 device: None,
5180 hooks: Vec::new(),
5181 discover_interfaces: false,
5182 autoconnect_interface_mode: None,
5183 autoconnect_interface_gravity: 0,
5184 autoconnect_announces_to_internal: false,
5185 discovery_required_value: None,
5186 respond_to_probes: false,
5187 prefer_shorter_path: false,
5188 max_paths_per_destination: 1,
5189 packet_hashlist_max_entries: rns_core::constants::HASHLIST_MAXSIZE,
5190 max_discovery_pr_tags: rns_core::constants::MAX_PR_TAGS,
5191 max_path_destinations: usize::MAX,
5192 max_tunnel_destinations_total: usize::MAX,
5193 known_destinations_ttl: DEFAULT_KNOWN_DESTINATIONS_TTL,
5194 known_destinations_max_entries: DEFAULT_KNOWN_DESTINATIONS_MAX_ENTRIES,
5195 announce_table_ttl: Duration::from_secs(
5196 rns_core::constants::ANNOUNCE_TABLE_TTL as u64,
5197 ),
5198 announce_table_max_bytes: rns_core::constants::ANNOUNCE_TABLE_MAX_BYTES,
5199 driver_event_queue_capacity: crate::event::DEFAULT_EVENT_QUEUE_CAPACITY,
5200 interface_writer_queue_capacity:
5201 crate::interface::DEFAULT_ASYNC_WRITER_QUEUE_CAPACITY,
5202 announce_rate_defaults: AnnounceRateDefaults::default(),
5203 ingress_control_defaults: rns_core::transport::types::IngressControlConfig::enabled(
5204 ),
5205 #[cfg(feature = "iface-backbone")]
5206 backbone_peer_pool: None,
5207 announce_sig_cache_enabled: true,
5208 announce_sig_cache_max_entries: rns_core::constants::ANNOUNCE_SIG_CACHE_MAXSIZE,
5209 announce_sig_cache_ttl: Duration::from_secs(
5210 rns_core::constants::ANNOUNCE_SIG_CACHE_TTL as u64,
5211 ),
5212 registry: None,
5213 #[cfg(feature = "hooks")]
5214 provider_bridge: None,
5215 },
5216 Box::new(NoopCallbacks),
5217 )
5218 .unwrap();
5219
5220 let dest = crate::destination::Destination::plain("test", &["data"])
5222 .set_proof_strategy(rns_core::types::ProofStrategy::ProveNone);
5223
5224 let result = node.register_destination_with_proof(&dest, None);
5225 assert!(result.is_ok());
5226
5227 thread::sleep(Duration::from_millis(50));
5228 node.shutdown();
5229 }
5230}