1use std::collections::{HashMap, HashSet, VecDeque};
15use std::io;
16use std::net::{Ipv6Addr, SocketAddrV6, UdpSocket};
17use std::os::unix::io::AsRawFd;
18use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
19use std::sync::{Arc, Mutex};
20use std::thread;
21use std::time::{Duration, Instant};
22
23use rns_core::transport::types::InterfaceId;
24
25use crate::event::{Event, EventSender};
26use crate::interface::{lock_or_recover, Writer};
27
28pub const DEFAULT_DISCOVERY_PORT: u16 = 29716;
32
33pub const DEFAULT_DATA_PORT: u16 = 42671;
35
36pub const DEFAULT_GROUP_ID: &[u8] = b"reticulum";
38
39pub const DEFAULT_IFAC_SIZE: usize = 16;
41
42pub const HW_MTU: usize = 1196;
44
45pub const SCOPE_LINK: &str = "2";
47pub const SCOPE_ADMIN: &str = "4";
49pub const SCOPE_SITE: &str = "5";
51pub const SCOPE_ORGANISATION: &str = "8";
53pub const SCOPE_GLOBAL: &str = "e";
55
56pub const MULTICAST_PERMANENT_ADDRESS_TYPE: &str = "0";
58pub const MULTICAST_TEMPORARY_ADDRESS_TYPE: &str = "1";
60
61pub const PEERING_TIMEOUT: f64 = 22.0;
63
64pub const ANNOUNCE_INTERVAL: f64 = 1.6;
66
67pub const PEER_JOB_INTERVAL: f64 = 4.0;
69
70pub const MCAST_ECHO_TIMEOUT: f64 = 6.5;
72
73pub const BITRATE_GUESS: u64 = 10_000_000;
75
76pub const MULTI_IF_DEQUE_LEN: usize = 48;
78
79pub const MULTI_IF_DEQUE_TTL: f64 = 0.75;
81
82pub const REVERSE_PEERING_MULTIPLIER: f64 = 3.25;
84
85pub const AUTO_RESCAN_INTERVAL: f64 = 1.25;
87
88pub const DEFAULT_MAX_PEERS: usize = 128;
92
93pub const MAX_NEW_PEERS_PER_WINDOW: usize = 32;
96
97pub const PEER_ADMISSION_WINDOW: Duration = Duration::from_secs(10);
99
100const PEER_REFUSAL_LOG_INTERVAL: Duration = Duration::from_secs(10);
102
103pub const ALL_IGNORE_IFS: &[&str] = &["lo0"];
105
106#[cfg_attr(not(any(target_os = "android", test)), allow(dead_code))]
107const ANDROID_IGNORE_IFS: &[&str] = &[
108 "dummy0", "lo", "tun0", "rmnet0", "rmnet1", "rmnet2", "rmnet3", "rmnet4", "rmnet5", "rmnet6",
109 "rmnet7",
110];
111
112#[derive(Debug, Clone)]
116pub struct AutoConfig {
117 pub name: String,
118 pub group_id: Vec<u8>,
119 pub discovery_scope: String,
120 pub discovery_port: u16,
121 pub data_port: u16,
122 pub multicast_address_type: String,
123 pub allowed_interfaces: Vec<String>,
124 pub ignored_interfaces: Vec<String>,
125 pub configured_bitrate: u64,
126 pub max_peers: usize,
130 pub interface_id: InterfaceId,
132 pub ingress_control: rns_core::transport::types::IngressControlConfig,
133 pub runtime: Arc<Mutex<AutoRuntime>>,
134}
135
136#[derive(Debug, Clone)]
137pub struct AutoRuntime {
138 pub announce_interval_secs: f64,
139 pub peer_timeout_secs: f64,
140 pub peer_job_interval_secs: f64,
141}
142
143impl AutoRuntime {
144 pub fn from_config(_config: &AutoConfig) -> Self {
145 Self {
146 announce_interval_secs: ANNOUNCE_INTERVAL,
147 peer_timeout_secs: PEERING_TIMEOUT,
148 peer_job_interval_secs: PEER_JOB_INTERVAL,
149 }
150 }
151}
152
153#[derive(Debug, Clone)]
154pub struct AutoRuntimeConfigHandle {
155 pub interface_name: String,
156 pub runtime: Arc<Mutex<AutoRuntime>>,
157 pub startup: AutoRuntime,
158}
159
160impl Default for AutoConfig {
161 fn default() -> Self {
162 let mut config = AutoConfig {
163 name: String::new(),
164 group_id: DEFAULT_GROUP_ID.to_vec(),
165 discovery_scope: SCOPE_LINK.to_string(),
166 discovery_port: DEFAULT_DISCOVERY_PORT,
167 data_port: DEFAULT_DATA_PORT,
168 multicast_address_type: MULTICAST_TEMPORARY_ADDRESS_TYPE.to_string(),
169 allowed_interfaces: Vec::new(),
170 ignored_interfaces: Vec::new(),
171 configured_bitrate: BITRATE_GUESS,
172 max_peers: DEFAULT_MAX_PEERS,
173 interface_id: InterfaceId(0),
174 ingress_control: rns_core::transport::types::IngressControlConfig::enabled(),
175 runtime: Arc::new(Mutex::new(AutoRuntime {
176 announce_interval_secs: ANNOUNCE_INTERVAL,
177 peer_timeout_secs: PEERING_TIMEOUT,
178 peer_job_interval_secs: PEER_JOB_INTERVAL,
179 })),
180 };
181 let startup = AutoRuntime::from_config(&config);
182 config.runtime = Arc::new(Mutex::new(startup));
183 config
184 }
185}
186
187pub fn derive_multicast_address(group_id: &[u8], address_type: &str, scope: &str) -> String {
197 let group_hash = rns_crypto::sha256::sha256(group_id);
198 let g = &group_hash;
199
200 let w1 = (g[2] as u16) << 8 | g[3] as u16;
202 let w2 = (g[4] as u16) << 8 | g[5] as u16;
203 let w3 = (g[6] as u16) << 8 | g[7] as u16;
204 let w4 = (g[8] as u16) << 8 | g[9] as u16;
205 let w5 = (g[10] as u16) << 8 | g[11] as u16;
206 let w6 = (g[12] as u16) << 8 | g[13] as u16;
207
208 format!(
209 "ff{}{}:0:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
210 address_type, scope, w1, w2, w3, w4, w5, w6
211 )
212}
213
214pub fn parse_multicast_addr(addr: &str) -> Option<Ipv6Addr> {
216 addr.parse::<Ipv6Addr>().ok()
217}
218
219pub fn compute_discovery_token(group_id: &[u8], link_local_addr: &str) -> [u8; 32] {
223 let mut input = group_id.to_vec();
224 input.extend_from_slice(link_local_addr.as_bytes());
225 rns_crypto::sha256::sha256(&input)
226}
227
228#[derive(Debug, Clone)]
232pub struct LocalInterface {
233 pub name: String,
234 pub link_local_addr: String,
235 pub index: u32,
236}
237
238#[cfg(target_os = "android")]
239fn platform_ignored_interfaces() -> &'static [&'static str] {
240 ANDROID_IGNORE_IFS
241}
242
243#[cfg(not(target_os = "android"))]
244fn platform_ignored_interfaces() -> &'static [&'static str] {
245 &[]
246}
247
248fn should_adopt_interface_name(
249 name: &str,
250 allowed: &[String],
251 ignored: &[String],
252 platform_ignored: &[&str],
253) -> bool {
254 let is_allowed = allowed.iter().any(|a| a == name);
255 let is_system_ignored = ALL_IGNORE_IFS.contains(&name) || platform_ignored.contains(&name);
256
257 if is_system_ignored && !is_allowed {
258 return false;
259 }
260
261 if ignored.iter().any(|ig| ig == name) {
262 return false;
263 }
264
265 if !allowed.is_empty() && !is_allowed {
266 return false;
267 }
268
269 true
270}
271
272pub fn enumerate_interfaces(allowed: &[String], ignored: &[String]) -> Vec<LocalInterface> {
276 let mut result = Vec::new();
277 let platform_ignored = platform_ignored_interfaces();
278
279 unsafe {
280 let mut ifaddrs: *mut libc::ifaddrs = std::ptr::null_mut();
281 if libc::getifaddrs(&mut ifaddrs) != 0 {
282 return result;
283 }
284
285 let mut current = ifaddrs;
286 while !current.is_null() {
287 let ifa = &*current;
288 current = ifa.ifa_next;
289
290 if ifa.ifa_addr.is_null() {
292 continue;
293 }
294
295 if (*ifa.ifa_addr).sa_family as i32 != libc::AF_INET6 {
297 continue;
298 }
299
300 let name = match std::ffi::CStr::from_ptr(ifa.ifa_name).to_str() {
302 Ok(s) => s.to_string(),
303 Err(_) => continue,
304 };
305
306 if !should_adopt_interface_name(&name, allowed, ignored, platform_ignored) {
307 continue;
308 }
309
310 let sa6 = ifa.ifa_addr as *const libc::sockaddr_in6;
312 let addr_bytes = (*sa6).sin6_addr.s6_addr;
313 let ipv6 = Ipv6Addr::from(addr_bytes);
314
315 let octets = ipv6.octets();
317 if octets[0] != 0xfe || (octets[1] & 0xc0) != 0x80 {
318 continue;
319 }
320
321 let addr_str = format!("{}", ipv6);
323
324 let index = libc::if_nametoindex(ifa.ifa_name);
326 if index == 0 {
327 continue;
328 }
329
330 if result.iter().any(|li: &LocalInterface| li.name == name) {
332 continue;
333 }
334
335 result.push(LocalInterface {
336 name,
337 link_local_addr: addr_str,
338 index,
339 });
340 }
341
342 libc::freeifaddrs(ifaddrs);
343 }
344
345 result
346}
347
348mod folded;
349
350#[cfg_attr(not(test), allow(dead_code))]
353#[derive(Debug, Clone, PartialEq, Eq, Hash)]
354struct AutoWorkerKey {
355 ifname: String,
356 if_index: u32,
357 link_local_addr: String,
358}
359
360#[allow(dead_code)]
361impl AutoWorkerKey {
362 fn from_local_interface(local: &LocalInterface) -> Self {
363 Self {
364 ifname: local.name.clone(),
365 if_index: local.index,
366 link_local_addr: local.link_local_addr.clone(),
367 }
368 }
369}
370
371#[cfg_attr(not(test), allow(dead_code))]
372#[derive(Debug, Clone, PartialEq, Eq)]
373enum WorkerReconcileAction {
374 Add(AutoWorkerKey),
375 Remove(AutoWorkerKey),
376}
377
378#[cfg_attr(not(test), allow(dead_code))]
379fn reconcile_worker_keys(
380 active: impl IntoIterator<Item = AutoWorkerKey>,
381 desired: impl IntoIterator<Item = AutoWorkerKey>,
382) -> Vec<WorkerReconcileAction> {
383 let active = active.into_iter().collect::<HashSet<_>>();
384 let desired = desired.into_iter().collect::<HashSet<_>>();
385 let mut actions = Vec::new();
386
387 for key in active.difference(&desired) {
388 actions.push(WorkerReconcileAction::Remove(key.clone()));
389 }
390 for key in desired.difference(&active) {
391 actions.push(WorkerReconcileAction::Add(key.clone()));
392 }
393
394 actions.sort_by(|a, b| format!("{a:?}").cmp(&format!("{b:?}")));
395 actions
396}
397
398struct RunningAutoWorker {
399 running: Arc<AtomicBool>,
400}
401
402impl RunningAutoWorker {
403 fn stop(&self) {
404 self.running.store(false, Ordering::Relaxed);
405 }
406}
407
408enum AutoWorkerEntry {
409 Running(RunningAutoWorker),
410 Pending,
411}
412
413impl AutoWorkerEntry {
414 fn stop(&self) {
415 if let AutoWorkerEntry::Running(worker) = self {
416 worker.stop();
417 }
418 }
419}
420
421fn reconcile_auto_workers<F>(
422 workers: &mut HashMap<AutoWorkerKey, AutoWorkerEntry>,
423 desired: Vec<AutoWorkerKey>,
424 mut start_worker: F,
425) where
426 F: FnMut(&AutoWorkerKey) -> io::Result<RunningAutoWorker>,
427{
428 let mut desired_keys = desired.into_iter().collect::<HashSet<_>>();
429 let active_keys = workers.keys().cloned().collect::<Vec<_>>();
430
431 for key in active_keys {
432 if !desired_keys.contains(&key) {
433 if let Some(worker) = workers.remove(&key) {
434 worker.stop();
435 }
436 }
437 }
438
439 let mut desired = desired_keys.drain().collect::<Vec<_>>();
440 desired.sort_by(|a, b| a.ifname.cmp(&b.ifname).then(a.if_index.cmp(&b.if_index)));
441
442 for key in desired {
443 if matches!(workers.get(&key), Some(AutoWorkerEntry::Running(_))) {
444 continue;
445 }
446
447 let entry = match start_worker(&key) {
448 Ok(worker) => AutoWorkerEntry::Running(worker),
449 Err(e) => {
450 log::warn!(
451 "AutoInterface worker {} failed to start on {}%{}: {}",
452 key.ifname,
453 key.link_local_addr,
454 key.if_index,
455 e
456 );
457 AutoWorkerEntry::Pending
458 }
459 };
460 workers.insert(key, entry);
461 }
462}
463
464#[derive(Debug, Clone, PartialEq, Eq, Hash)]
465struct PeerKey {
466 link_local_addr: String,
467 if_index: u32,
468}
469
470struct AutoPeer {
472 interface_id: InterfaceId,
473 #[allow(dead_code)]
474 link_local_addr: String,
475 #[allow(dead_code)]
476 ifname: String,
477 #[allow(dead_code)]
478 if_index: u32,
479 last_heard: f64,
480}
481
482#[derive(Debug, Clone, Copy, PartialEq, Eq)]
484enum PeerAdmissionDecision {
485 Admit,
486 AtCapacity,
488 RateLimited,
490}
491
492#[derive(Debug, Default)]
494struct PeerAdmission {
495 window_start: Option<Instant>,
496 admitted_in_window: usize,
497 last_refusal_log: Option<Instant>,
498 refusals_since_log: usize,
499}
500
501impl PeerAdmission {
502 fn decide(
505 &mut self,
506 active_peers: usize,
507 max_peers: usize,
508 now: Instant,
509 ) -> PeerAdmissionDecision {
510 if active_peers >= max_peers {
511 return PeerAdmissionDecision::AtCapacity;
512 }
513 let window_elapsed = self
514 .window_start
515 .is_none_or(|start| now.saturating_duration_since(start) >= PEER_ADMISSION_WINDOW);
516 if window_elapsed {
517 self.window_start = Some(now);
518 self.admitted_in_window = 0;
519 }
520 if self.admitted_in_window >= MAX_NEW_PEERS_PER_WINDOW {
521 return PeerAdmissionDecision::RateLimited;
522 }
523 self.admitted_in_window += 1;
524 PeerAdmissionDecision::Admit
525 }
526
527 fn note_refusal(&mut self, now: Instant) -> Option<usize> {
530 self.refusals_since_log = self.refusals_since_log.saturating_add(1);
531 let due = self
532 .last_refusal_log
533 .is_none_or(|last| now.saturating_duration_since(last) >= PEER_REFUSAL_LOG_INTERVAL);
534 if !due {
535 return None;
536 }
537 self.last_refusal_log = Some(now);
538 Some(std::mem::take(&mut self.refusals_since_log))
539 }
540}
541
542struct UdpWriter {
544 socket: UdpSocket,
545 target: SocketAddrV6,
546 inline: bool,
549}
550
551impl Writer for UdpWriter {
552 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
553 match self.socket.send_to(data, self.target) {
554 Err(e) if self.inline && e.kind() == io::ErrorKind::WouldBlock => Ok(()),
557 result => result.map(|_| ()),
558 }
559 }
560
561 fn send_frame_confirmed(&mut self, data: &[u8]) -> io::Result<()> {
562 self.socket.send_to(data, self.target).map(|_| ())
563 }
564
565 fn enqueue_confirmed(
566 &mut self,
567 data: &[u8],
568 completion: &mut Option<crate::link_send::Completion>,
569 ) -> io::Result<()> {
570 self.socket.send_to(data, self.target)?;
573 if let Some(completion) = completion.take() {
574 completion.finish(Ok(()));
575 }
576 Ok(())
577 }
578
579 fn sends_inline(&self) -> bool {
580 self.inline
581 }
582}
583
584struct SharedState {
586 peers: HashMap<PeerKey, AutoPeer>,
588 link_local_addresses: HashSet<PeerKey>,
590 dedup_deque: VecDeque<([u8; 32], f64)>,
592 online: bool,
594 next_id: Arc<AtomicU64>,
596 max_peers: usize,
598 peer_admission: HashMap<String, PeerAdmission>,
600}
601
602impl SharedState {
603 fn new(next_id: Arc<AtomicU64>) -> Self {
604 SharedState {
605 peers: HashMap::new(),
606 link_local_addresses: HashSet::new(),
607 dedup_deque: VecDeque::new(),
608 online: false,
609 next_id,
610 max_peers: DEFAULT_MAX_PEERS,
611 peer_admission: HashMap::new(),
612 }
613 }
614
615 fn active_peers_on(&self, ifname: &str) -> usize {
617 self.peers
618 .values()
619 .filter(|peer| peer.ifname == ifname)
620 .count()
621 }
622
623 fn admit_new_peer(&mut self, ifname: &str, now: Instant) -> PeerAdmissionDecision {
625 let active_peers = self.active_peers_on(ifname);
626 let max_peers = self.max_peers;
627 self.peer_admission
628 .entry(ifname.to_string())
629 .or_default()
630 .decide(active_peers, max_peers, now)
631 }
632
633 fn note_peer_refusal(&mut self, ifname: &str, now: Instant) -> Option<usize> {
635 self.peer_admission
636 .entry(ifname.to_string())
637 .or_default()
638 .note_refusal(now)
639 }
640
641 fn remove_timed_out_peers(
643 &mut self,
644 now: f64,
645 peer_timeout_secs: f64,
646 ) -> Vec<(PeerKey, InterfaceId)> {
647 let timed_out = self
648 .peers
649 .iter()
650 .filter(|(_, peer)| now > peer.last_heard + peer_timeout_secs)
651 .map(|(peer_key, peer)| (peer_key.clone(), peer.interface_id))
652 .collect::<Vec<_>>();
653 for (peer_key, _) in &timed_out {
654 self.peers.remove(peer_key);
655 }
656 timed_out
657 }
658
659 fn is_duplicate(&self, hash: &[u8; 32], now: f64) -> bool {
661 for (h, ts) in &self.dedup_deque {
662 if h == hash && now < *ts + MULTI_IF_DEQUE_TTL {
663 return true;
664 }
665 }
666 false
667 }
668
669 fn add_dedup(&mut self, hash: [u8; 32], now: f64) {
671 self.dedup_deque.push_back((hash, now));
672 while self.dedup_deque.len() > MULTI_IF_DEQUE_LEN {
673 self.dedup_deque.pop_front();
674 }
675 }
676
677 fn refresh_peer(&mut self, key: &PeerKey, now: f64) {
679 if let Some(peer) = self.peers.get_mut(key) {
680 peer.last_heard = now;
681 }
682 }
683}
684
685fn peer_key_from_src(src_addr: &SocketAddrV6, fallback_if_index: u32) -> PeerKey {
686 let if_index = match src_addr.scope_id() {
687 0 => fallback_if_index,
688 scope_id => scope_id,
689 };
690
691 PeerKey {
692 link_local_addr: src_addr.ip().to_string(),
693 if_index,
694 }
695}
696
697fn peer_target_addr(peer_key: &PeerKey, data_port: u16) -> io::Result<SocketAddrV6> {
698 let peer_ip = peer_key.link_local_addr.parse::<Ipv6Addr>().map_err(|e| {
699 io::Error::new(io::ErrorKind::InvalidInput, format!("bad peer IPv6: {}", e))
700 })?;
701
702 Ok(SocketAddrV6::new(peer_ip, data_port, 0, peer_key.if_index))
703}
704
705fn remove_peers_for_worker(
706 shared: &Arc<Mutex<SharedState>>,
707 tx: &EventSender,
708 key: &AutoWorkerKey,
709) {
710 for iface_id in take_peers_for_worker(shared, key) {
711 let _ = tx.send(Event::InterfaceDown(iface_id));
712 }
713}
714
715fn take_peers_for_worker(
718 shared: &Arc<Mutex<SharedState>>,
719 key: &AutoWorkerKey,
720) -> Vec<InterfaceId> {
721 let removed = {
722 let mut state = lock_or_recover(shared, "auto shared state");
723 let removed = state
724 .peers
725 .iter()
726 .filter_map(|(peer_key, peer)| {
727 if peer.if_index == key.if_index && peer.ifname == key.ifname {
728 Some((peer_key.clone(), peer.interface_id))
729 } else {
730 None
731 }
732 })
733 .collect::<Vec<_>>();
734
735 for (peer_key, _) in &removed {
736 state.peers.remove(peer_key);
737 }
738 state.peer_admission.remove(&key.ifname);
739
740 removed
741 };
742
743 removed.into_iter().map(|(_, iface_id)| iface_id).collect()
744}
745
746impl AutoWorkerContext {
749 fn discovery_handler(&self, key: &AutoWorkerKey) -> DiscoveryHandler {
750 DiscoveryHandler {
751 group_id: self.group_id.clone(),
752 shared: self.shared.clone(),
753 name: self.name.clone(),
754 ifname: key.ifname.clone(),
755 if_index: key.if_index,
756 data_port: self.data_port,
757 configured_bitrate: self.configured_bitrate,
758 ingress_control: self.ingress_control,
759 runtime: self.runtime.clone(),
760 dynamic_template: self.dynamic_template.clone(),
761 underlay_mark: self.underlay_mark,
762 inline: crate::engine::FOLD_IO,
763 }
764 }
765}
766
767#[derive(Clone)]
768struct AutoWorkerContext {
769 group_id: Vec<u8>,
770 mcast_ip: Ipv6Addr,
771 discovery_port: u16,
772 unicast_discovery_port: u16,
773 data_port: u16,
774 name: String,
775 configured_bitrate: u64,
776 ingress_control: rns_core::transport::types::IngressControlConfig,
777 runtime: Arc<Mutex<AutoRuntime>>,
778 shared: Arc<Mutex<SharedState>>,
779 tx: EventSender,
780 dynamic_template: Option<super::DynamicInterfaceTemplate>,
781 underlay_mark: Option<u32>,
782}
783
784pub fn start(
790 config: AutoConfig,
791 tx: EventSender,
792 next_dynamic_id: Arc<AtomicU64>,
793) -> io::Result<()> {
794 start_with_template(config, tx, next_dynamic_id, None, None).map(|_| ())
795}
796
797fn start_with_template(
798 config: AutoConfig,
799 tx: EventSender,
800 next_dynamic_id: Arc<AtomicU64>,
801 dynamic_template: Option<super::DynamicInterfaceTemplate>,
802 underlay_mark: Option<u32>,
803) -> io::Result<super::ListenerControl> {
804 let group_id = config.group_id.clone();
805 let mcast_addr_str = derive_multicast_address(
806 &group_id,
807 &config.multicast_address_type,
808 &config.discovery_scope,
809 );
810
811 let mcast_ip = match parse_multicast_addr(&mcast_addr_str) {
812 Some(ip) => ip,
813 None => {
814 return Err(io::Error::new(
815 io::ErrorKind::InvalidData,
816 format!("Invalid multicast address: {}", mcast_addr_str),
817 ));
818 }
819 };
820
821 {
822 let startup = AutoRuntime::from_config(&config);
823 *lock_or_recover(&config.runtime, "auto runtime") = startup;
824 }
825 let runtime = Arc::clone(&config.runtime);
826 let mut shared_state = SharedState::new(next_dynamic_id);
827 shared_state.max_peers = config.max_peers;
828 let shared = Arc::new(Mutex::new(shared_state));
829 let running = Arc::new(AtomicBool::new(true));
830
831 let worker_context = AutoWorkerContext {
832 group_id,
833 mcast_ip,
834 discovery_port: config.discovery_port,
835 unicast_discovery_port: config.discovery_port + 1,
836 data_port: config.data_port,
837 name: config.name.clone(),
838 configured_bitrate: config.configured_bitrate,
839 ingress_control: config.ingress_control,
840 runtime: runtime.clone(),
841 shared: shared.clone(),
842 tx: tx.clone(),
843 dynamic_template,
844 underlay_mark,
845 };
846
847 if crate::engine::FOLD_IO {
848 folded::start(
849 config.interface_id,
850 worker_context,
851 config.allowed_interfaces.clone(),
852 config.ignored_interfaces.clone(),
853 running.clone(),
854 )?;
855 } else {
856 start_threads(&config, worker_context, &shared, &runtime, &running)?;
857 }
858
859 let announce_interval = lock_or_recover(&runtime, "auto runtime").announce_interval_secs;
860 let peering_wait = Duration::from_secs_f64(announce_interval * 1.2);
861 thread::sleep(peering_wait);
862
863 {
864 let mut state = lock_or_recover(&shared, "auto shared state");
865 state.online = true;
866 }
867
868 log::info!("[{}] AutoInterface online", config.name);
869
870 Ok(super::ListenerControl::with_running_flag(running))
871}
872
873fn start_threads(
877 config: &AutoConfig,
878 worker_context: AutoWorkerContext,
879 shared: &Arc<Mutex<SharedState>>,
880 runtime: &Arc<Mutex<AutoRuntime>>,
881 running: &Arc<AtomicBool>,
882) -> io::Result<()> {
883 {
884 let shared = shared.clone();
885 let tx = worker_context.tx.clone();
886 let running = running.clone();
887 let name = config.name.clone();
888 let runtime = runtime.clone();
889
890 thread::Builder::new()
891 .name(format!("auto-peer-jobs-{}", name))
892 .spawn(move || {
893 peer_jobs_loop(shared, tx, runtime, &running, &name);
894 })?;
895 }
896
897 {
898 let allowed = config.allowed_interfaces.clone();
899 let ignored = config.ignored_interfaces.clone();
900 let running = running.clone();
901 let name = config.name.clone();
902 thread::Builder::new()
903 .name(format!("auto-supervisor-{}", name))
904 .spawn(move || {
905 auto_supervisor_loop(allowed, ignored, worker_context, running, &name);
906 })?;
907 }
908
909 Ok(())
910}
911
912fn auto_supervisor_loop(
913 allowed_interfaces: Vec<String>,
914 ignored_interfaces: Vec<String>,
915 context: AutoWorkerContext,
916 running: Arc<AtomicBool>,
917 name: &str,
918) {
919 let mut workers: HashMap<AutoWorkerKey, AutoWorkerEntry> = HashMap::new();
920
921 while running.load(Ordering::Relaxed) {
922 let interfaces = enumerate_interfaces(&allowed_interfaces, &ignored_interfaces);
923 let desired = interfaces
924 .iter()
925 .map(AutoWorkerKey::from_local_interface)
926 .collect::<Vec<_>>();
927
928 {
929 let mut state = lock_or_recover(&context.shared, "auto shared state");
930 state.link_local_addresses = desired
931 .iter()
932 .map(|key| PeerKey {
933 link_local_addr: key.link_local_addr.clone(),
934 if_index: key.if_index,
935 })
936 .collect();
937 }
938
939 let removed_workers = reconcile_worker_keys(workers.keys().cloned(), desired.clone())
940 .into_iter()
941 .filter_map(|action| match action {
942 WorkerReconcileAction::Remove(key) => Some(key),
943 WorkerReconcileAction::Add(_) => None,
944 })
945 .collect::<Vec<_>>();
946 for key in &removed_workers {
947 remove_peers_for_worker(&context.shared, &context.tx, key);
948 }
949
950 if desired.is_empty() && workers.is_empty() {
951 log::warn!("[{}] No suitable IPv6 link-local interfaces found", name);
952 }
953
954 reconcile_auto_workers(&mut workers, desired, |key| {
955 start_auto_worker(key, &context)
956 });
957
958 thread::sleep(Duration::from_secs_f64(AUTO_RESCAN_INTERVAL));
959 }
960
961 for (key, worker) in workers {
962 remove_peers_for_worker(&context.shared, &context.tx, &key);
963 worker.stop();
964 }
965}
966
967fn start_auto_worker(
968 key: &AutoWorkerKey,
969 context: &AutoWorkerContext,
970) -> io::Result<RunningAutoWorker> {
971 let underlay_mark = context.underlay_mark;
972 let mcast_socket = create_multicast_recv_socket(
973 &context.mcast_ip,
974 context.discovery_port,
975 key.if_index,
976 context.underlay_mark,
977 )?;
978 let unicast_socket = create_unicast_recv_socket(
979 &key.link_local_addr,
980 context.unicast_discovery_port,
981 key.if_index,
982 context.underlay_mark,
983 )?;
984 let data_socket = create_data_recv_socket(
985 &key.link_local_addr,
986 context.data_port,
987 key.if_index,
988 context.underlay_mark,
989 )?;
990
991 let worker_running = Arc::new(AtomicBool::new(true));
992
993 {
994 let group_id = context.group_id.clone();
995 let link_local = key.link_local_addr.clone();
996 let running = worker_running.clone();
997 let name = context.name.clone();
998 let runtime = context.runtime.clone();
999 let mcast_ip = context.mcast_ip;
1000 let discovery_port = context.discovery_port;
1001 let if_index = key.if_index;
1002 thread::Builder::new()
1003 .name(format!("auto-disc-tx-{}", key.ifname))
1004 .spawn(move || {
1005 discovery_sender_loop(DiscoverySenderContext {
1006 group_id,
1007 link_local_addr: link_local,
1008 mcast_ip,
1009 discovery_port,
1010 if_index,
1011 runtime,
1012 running,
1013 name,
1014 underlay_mark,
1015 });
1016 })?;
1017 }
1018
1019 let handler = context.discovery_handler(key);
1020 for (socket, thread_name) in [
1021 (mcast_socket, format!("auto-disc-rx-{}", key.ifname)),
1022 (unicast_socket, format!("auto-udisc-rx-{}", key.ifname)),
1023 ] {
1024 let handler = handler.clone();
1025 let tx = context.tx.clone();
1026 let running = worker_running.clone();
1027 thread::Builder::new()
1028 .name(thread_name)
1029 .spawn(move || discovery_receiver_loop(socket, handler, tx, running))?;
1030 }
1031
1032 {
1033 let shared = context.shared.clone();
1034 let tx = context.tx.clone();
1035 let running = worker_running.clone();
1036 let name = context.name.clone();
1037 let if_index = key.if_index;
1038 thread::Builder::new()
1039 .name(format!("auto-data-rx-{}", key.ifname))
1040 .spawn(move || {
1041 data_receiver_loop(data_socket, shared, tx, &running, &name, if_index);
1042 })?;
1043 }
1044
1045 log::info!(
1046 "[{}] AutoInterface worker online on {} {}%{}",
1047 context.name,
1048 key.ifname,
1049 key.link_local_addr,
1050 key.if_index
1051 );
1052
1053 Ok(RunningAutoWorker {
1054 running: worker_running,
1055 })
1056}
1057
1058fn create_multicast_recv_socket(
1061 mcast_ip: &Ipv6Addr,
1062 port: u16,
1063 if_index: u32,
1064 underlay_mark: Option<u32>,
1065) -> io::Result<UdpSocket> {
1066 let socket = socket2::Socket::new(
1067 socket2::Domain::IPV6,
1068 socket2::Type::DGRAM,
1069 Some(socket2::Protocol::UDP),
1070 )?;
1071 super::apply_underlay_mark(socket.as_raw_fd(), underlay_mark)?;
1072
1073 socket.set_reuse_address(true)?;
1074 #[cfg(not(target_os = "windows"))]
1075 socket.set_reuse_port(true)?;
1076
1077 let bind_addr = SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, port, 0, if_index);
1079 socket.bind(&bind_addr.into())?;
1080
1081 socket.join_multicast_v6(mcast_ip, if_index)?;
1083
1084 socket.set_nonblocking(false)?;
1085 let std_socket: UdpSocket = socket.into();
1086 std_socket.set_read_timeout(Some(Duration::from_secs(2)))?;
1087 Ok(std_socket)
1088}
1089
1090fn create_unicast_recv_socket(
1091 link_local: &str,
1092 port: u16,
1093 if_index: u32,
1094 underlay_mark: Option<u32>,
1095) -> io::Result<UdpSocket> {
1096 let ip: Ipv6Addr = link_local
1097 .parse()
1098 .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, format!("bad IPv6: {}", e)))?;
1099
1100 let socket = socket2::Socket::new(
1101 socket2::Domain::IPV6,
1102 socket2::Type::DGRAM,
1103 Some(socket2::Protocol::UDP),
1104 )?;
1105 super::apply_underlay_mark(socket.as_raw_fd(), underlay_mark)?;
1106
1107 socket.set_reuse_address(true)?;
1108 #[cfg(not(target_os = "windows"))]
1109 socket.set_reuse_port(true)?;
1110
1111 let bind_addr = SocketAddrV6::new(ip, port, 0, if_index);
1112 socket.bind(&bind_addr.into())?;
1113
1114 socket.set_nonblocking(false)?;
1115 let std_socket: UdpSocket = socket.into();
1116 std_socket.set_read_timeout(Some(Duration::from_secs(2)))?;
1117 Ok(std_socket)
1118}
1119
1120fn create_data_recv_socket(
1121 link_local: &str,
1122 port: u16,
1123 if_index: u32,
1124 underlay_mark: Option<u32>,
1125) -> io::Result<UdpSocket> {
1126 let ip: Ipv6Addr = link_local
1127 .parse()
1128 .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, format!("bad IPv6: {}", e)))?;
1129
1130 let socket = socket2::Socket::new(
1131 socket2::Domain::IPV6,
1132 socket2::Type::DGRAM,
1133 Some(socket2::Protocol::UDP),
1134 )?;
1135 super::apply_underlay_mark(socket.as_raw_fd(), underlay_mark)?;
1136
1137 socket.set_reuse_address(true)?;
1138 #[cfg(not(target_os = "windows"))]
1139 socket.set_reuse_port(true)?;
1140
1141 let bind_addr = SocketAddrV6::new(ip, port, 0, if_index);
1142 socket.bind(&bind_addr.into())?;
1143
1144 socket.set_nonblocking(false)?;
1145 let std_socket: UdpSocket = socket.into();
1146 std_socket.set_read_timeout(Some(Duration::from_secs(2)))?;
1147 Ok(std_socket)
1148}
1149
1150struct DiscoverySenderContext {
1154 group_id: Vec<u8>,
1155 link_local_addr: String,
1156 mcast_ip: Ipv6Addr,
1157 discovery_port: u16,
1158 if_index: u32,
1159 runtime: Arc<Mutex<AutoRuntime>>,
1160 running: Arc<AtomicBool>,
1161 name: String,
1162 underlay_mark: Option<u32>,
1163}
1164
1165fn discovery_sender_loop(context: DiscoverySenderContext) {
1166 let DiscoverySenderContext {
1167 group_id,
1168 link_local_addr,
1169 mcast_ip,
1170 discovery_port,
1171 if_index,
1172 runtime,
1173 running,
1174 name,
1175 underlay_mark,
1176 } = context;
1177 let token = compute_discovery_token(&group_id, &link_local_addr);
1178
1179 while running.load(Ordering::Relaxed) {
1180 if let Ok(socket) = UdpSocket::bind("[::]:0") {
1182 if let Err(error) = super::apply_underlay_mark(socket.as_raw_fd(), underlay_mark) {
1183 log::error!("[{}] failed to mark discovery socket: {}", name, error);
1184 return;
1185 }
1186 let if_bytes = if_index.to_ne_bytes();
1188 unsafe {
1189 libc::setsockopt(
1190 socket_fd(&socket),
1191 libc::IPPROTO_IPV6,
1192 libc::IPV6_MULTICAST_IF,
1193 if_bytes.as_ptr() as *const libc::c_void,
1194 4,
1195 );
1196 }
1197
1198 let target = SocketAddrV6::new(mcast_ip, discovery_port, 0, 0);
1199 if let Err(e) = socket.send_to(&token, target) {
1200 log::debug!("[{}] multicast send error: {}", name, e);
1201 }
1202 }
1203
1204 let sleep_dur = Duration::from_secs_f64(
1205 lock_or_recover(&runtime, "auto runtime")
1206 .announce_interval_secs
1207 .max(0.1),
1208 );
1209 thread::sleep(sleep_dur);
1210 }
1211}
1212
1213#[derive(Clone)]
1216struct DiscoveryHandler {
1217 group_id: Vec<u8>,
1218 shared: Arc<Mutex<SharedState>>,
1219 name: String,
1220 ifname: String,
1221 if_index: u32,
1222 data_port: u16,
1223 configured_bitrate: u64,
1224 ingress_control: rns_core::transport::types::IngressControlConfig,
1225 runtime: Arc<Mutex<AutoRuntime>>,
1226 dynamic_template: Option<super::DynamicInterfaceTemplate>,
1227 underlay_mark: Option<u32>,
1228 inline: bool,
1230}
1231
1232impl DiscoveryHandler {
1233 fn handle(&self, datagram: &[u8], src: std::net::SocketAddr) -> Option<Event> {
1236 if datagram.len() < 32 {
1237 return None;
1238 }
1239 let std::net::SocketAddr::V6(src_addr) = src else {
1241 return None;
1242 };
1243 let peer_key = peer_key_from_src(&src_addr, self.if_index);
1244 let peering_hash = &datagram[..32];
1245 let expected = compute_discovery_token(&self.group_id, &peer_key.link_local_addr);
1246 if peering_hash != expected {
1247 log::debug!(
1248 "[{}] invalid peering hash from {}",
1249 self.name,
1250 peer_key.link_local_addr
1251 );
1252 return None;
1253 }
1254
1255 {
1256 let mut state = lock_or_recover(&self.shared, "auto shared state");
1259 if state.link_local_addresses.contains(&peer_key) {
1261 return None;
1262 }
1263 if state.peers.contains_key(&peer_key) {
1265 state.refresh_peer(&peer_key, crate::time::now());
1266 return None;
1267 }
1268 }
1269
1270 add_peer(AddPeerContext {
1272 shared: &self.shared,
1273 peer_key: &peer_key,
1274 data_port: self.data_port,
1275 name: &self.name,
1276 ifname: &self.ifname,
1277 configured_bitrate: self.configured_bitrate,
1278 ingress_control: self.ingress_control,
1279 runtime: &self.runtime,
1280 dynamic_template: self.dynamic_template.as_ref(),
1281 underlay_mark: self.underlay_mark,
1282 inline: self.inline,
1283 })
1284 }
1285}
1286
1287fn discovery_receiver_loop(
1289 socket: UdpSocket,
1290 handler: DiscoveryHandler,
1291 tx: EventSender,
1292 running: Arc<AtomicBool>,
1293) {
1294 let mut buf = [0u8; 1024];
1295
1296 while running.load(Ordering::Relaxed) {
1297 match socket.recv_from(&mut buf) {
1298 Ok((n, src)) => {
1299 if let Some(event) = handler.handle(&buf[..n], src) {
1300 let _ = tx.send(event);
1301 }
1302 }
1303 Err(ref e)
1304 if e.kind() == io::ErrorKind::WouldBlock || e.kind() == io::ErrorKind::TimedOut =>
1305 {
1306 continue;
1308 }
1309 Err(e) => {
1310 log::warn!("[{}] discovery recv error: {}", handler.name, e);
1311 if !running.load(Ordering::Relaxed) {
1312 return;
1313 }
1314 thread::sleep(Duration::from_millis(100));
1315 }
1316 }
1317 }
1318}
1319
1320struct AddPeerContext<'a> {
1322 shared: &'a Arc<Mutex<SharedState>>,
1323 peer_key: &'a PeerKey,
1324 data_port: u16,
1325 name: &'a str,
1326 ifname: &'a str,
1327 configured_bitrate: u64,
1328 ingress_control: rns_core::transport::types::IngressControlConfig,
1329 runtime: &'a Arc<Mutex<AutoRuntime>>,
1330 dynamic_template: Option<&'a super::DynamicInterfaceTemplate>,
1331 underlay_mark: Option<u32>,
1332 inline: bool,
1333}
1334
1335fn add_peer(context: AddPeerContext<'_>) -> Option<Event> {
1337 let AddPeerContext {
1338 shared,
1339 peer_key,
1340 data_port,
1341 name,
1342 ifname,
1343 configured_bitrate,
1344 ingress_control,
1345 runtime: _runtime,
1346 dynamic_template,
1347 underlay_mark,
1348 inline,
1349 } = context;
1350
1351 {
1352 let mut state = lock_or_recover(shared, "auto shared state");
1353 if state.peers.contains_key(peer_key) {
1354 state.refresh_peer(peer_key, crate::time::now());
1355 return None;
1356 }
1357 let now = Instant::now();
1358 let decision = state.admit_new_peer(ifname, now);
1359 if decision != PeerAdmissionDecision::Admit {
1360 let report = state.note_peer_refusal(ifname, now);
1361 let max_peers = state.max_peers;
1362 drop(state);
1363 log_peer_refusal(name, ifname, peer_key, decision, max_peers, report);
1364 return None;
1365 }
1366 }
1367
1368 let send_socket = match UdpSocket::bind("[::]:0") {
1370 Ok(s) => s,
1371 Err(e) => {
1372 log::warn!(
1373 "[{}] failed to create writer for peer {}: {}",
1374 name,
1375 peer_key.link_local_addr,
1376 e
1377 );
1378 return None;
1379 }
1380 };
1381 if let Err(error) = super::apply_underlay_mark(send_socket.as_raw_fd(), underlay_mark) {
1382 log::error!("[{}] failed to mark peer socket: {}", name, error);
1383 return None;
1384 }
1385 if inline {
1386 if let Err(error) = send_socket.set_nonblocking(true) {
1387 log::warn!("[{}] failed to set up peer socket: {}", name, error);
1388 return None;
1389 }
1390 }
1391
1392 let target = match peer_target_addr(peer_key, data_port) {
1393 Ok(target) => target,
1394 Err(e) => {
1395 log::warn!(
1396 "[{}] failed to create target for peer {}%{}: {}",
1397 name,
1398 peer_key.link_local_addr,
1399 peer_key.if_index,
1400 e
1401 );
1402 return None;
1403 }
1404 };
1405
1406 let mut state = lock_or_recover(shared, "auto shared state");
1407
1408 if state.peers.contains_key(peer_key) {
1410 state.refresh_peer(peer_key, crate::time::now());
1411 return None;
1412 }
1413 if state.active_peers_on(ifname) >= state.max_peers {
1415 let report = state.note_peer_refusal(ifname, Instant::now());
1416 let max_peers = state.max_peers;
1417 drop(state);
1418 log_peer_refusal(
1419 name,
1420 ifname,
1421 peer_key,
1422 PeerAdmissionDecision::AtCapacity,
1423 max_peers,
1424 report,
1425 );
1426 return None;
1427 }
1428
1429 let peer_id = InterfaceId(state.next_id.fetch_add(1, Ordering::Relaxed));
1430
1431 let driver_writer: Box<dyn Writer> = Box::new(UdpWriter {
1433 socket: send_socket,
1434 target,
1435 inline,
1436 });
1437
1438 let peer_info = rns_core::transport::types::InterfaceInfo {
1439 stamp_costs: Default::default(),
1440 id: peer_id,
1441 name: format!(
1442 "{}:{}%{}",
1443 name, peer_key.link_local_addr, peer_key.if_index
1444 ),
1445 mode: rns_core::constants::MODE_FULL,
1446 gravity: 0,
1447 recursive_prs: false,
1448 announces_from_internal: true,
1449 announces_to_internal: None,
1450 out_capable: true,
1451 in_capable: true,
1452 bitrate: Some(configured_bitrate),
1453 airtime_profile: None,
1454 announce_rate_target: None,
1455 announce_rate_grace: 0,
1456 announce_rate_penalty: 0.0,
1457 announce_cap: rns_core::constants::ANNOUNCE_CAP,
1458 is_local_client: false,
1459 wants_tunnel: false,
1460 tunnel_id: None,
1461 mtu: HW_MTU as u32,
1462 ia_freq: 0.0,
1463 ip_freq: 0.0,
1464 op_freq: 0.0,
1465 op_samples: 0,
1466 started: 0.0,
1467 ingress_control,
1468 };
1469
1470 let now = crate::time::now();
1471 state.peers.insert(
1472 peer_key.clone(),
1473 AutoPeer {
1474 interface_id: peer_id,
1475 link_local_addr: peer_key.link_local_addr.clone(),
1476 ifname: ifname.to_string(),
1477 if_index: peer_key.if_index,
1478 last_heard: now,
1479 },
1480 );
1481
1482 log::info!(
1483 "[{}] Peer discovered: {} (id={})",
1484 name,
1485 peer_key.link_local_addr,
1486 peer_id.0
1487 );
1488
1489 let event = if let Some(template) = dynamic_template {
1491 Event::DynamicInterfaceUp {
1492 id: peer_id,
1493 writer: driver_writer,
1494 registration: Box::new(template.registration(peer_info)),
1495 }
1496 } else {
1497 Event::InterfaceUp(peer_id, Some(driver_writer), Some(Box::new(peer_info)))
1498 };
1499 Some(event)
1500}
1501
1502fn log_peer_refusal(
1504 name: &str,
1505 ifname: &str,
1506 peer_key: &PeerKey,
1507 decision: PeerAdmissionDecision,
1508 max_peers: usize,
1509 report: Option<usize>,
1510) {
1511 let Some(ignored) = report else {
1512 return;
1513 };
1514 let reason = match decision {
1515 PeerAdmissionDecision::AtCapacity => format!("{} active peers is the limit", max_peers),
1516 PeerAdmissionDecision::RateLimited => format!(
1517 "at most {} new peers per {}s",
1518 MAX_NEW_PEERS_PER_WINDOW,
1519 PEER_ADMISSION_WINDOW.as_secs()
1520 ),
1521 PeerAdmissionDecision::Admit => return,
1522 };
1523 log::warn!(
1524 "[{}] ignoring new peer {}%{} on {} ({}); {} new peer(s) ignored since the last warning",
1525 name,
1526 peer_key.link_local_addr,
1527 peer_key.if_index,
1528 ifname,
1529 reason,
1530 ignored
1531 );
1532}
1533
1534fn handle_data(
1537 shared: &Mutex<SharedState>,
1538 if_index: u32,
1539 data: &[u8],
1540 src: std::net::SocketAddr,
1541) -> Option<Event> {
1542 if data.is_empty() {
1543 return None;
1544 }
1545 let std::net::SocketAddr::V6(src_addr) = src else {
1546 return None;
1547 };
1548 let peer_key = peer_key_from_src(&src_addr, if_index);
1549
1550 let mut state = lock_or_recover(shared, "auto shared state");
1553 if !state.online || !state.peers.contains_key(&peer_key) {
1554 return None;
1555 }
1556 let now = crate::time::now();
1557 let data_hash = rns_crypto::sha256::sha256(data);
1558 if state.is_duplicate(&data_hash, now) {
1560 return None;
1561 }
1562 state.add_dedup(data_hash, now);
1563 state.refresh_peer(&peer_key, now);
1564 let iface_id = state.peers.get(&peer_key)?.interface_id;
1565 Some(Event::Frame {
1566 interface_id: iface_id,
1567 data: data.into(),
1568 rssi: None,
1569 snr: None,
1570 })
1571}
1572
1573fn data_receiver_loop(
1575 socket: UdpSocket,
1576 shared: Arc<Mutex<SharedState>>,
1577 tx: EventSender,
1578 running: &AtomicBool,
1579 name: &str,
1580 if_index: u32,
1581) {
1582 let mut buf = [0u8; HW_MTU + 64]; while running.load(Ordering::Relaxed) {
1585 match socket.recv_from(&mut buf) {
1586 Ok((n, src)) => {
1587 if let Some(frame) = handle_data(&shared, if_index, &buf[..n], src) {
1588 if tx.send(frame).is_err() {
1589 return;
1590 }
1591 }
1592 }
1593 Err(ref e)
1594 if e.kind() == io::ErrorKind::WouldBlock || e.kind() == io::ErrorKind::TimedOut =>
1595 {
1596 continue;
1597 }
1598 Err(e) => {
1599 log::warn!("[{}] data recv error: {}", name, e);
1600 if !running.load(Ordering::Relaxed) {
1601 return;
1602 }
1603 thread::sleep(Duration::from_millis(100));
1604 }
1605 }
1606 }
1607}
1608
1609fn peer_jobs_loop(
1611 shared: Arc<Mutex<SharedState>>,
1612 tx: EventSender,
1613 runtime: Arc<Mutex<AutoRuntime>>,
1614 running: &AtomicBool,
1615 name: &str,
1616) {
1617 while running.load(Ordering::Relaxed) {
1618 let interval = Duration::from_secs_f64(
1619 lock_or_recover(&runtime, "auto runtime")
1620 .peer_job_interval_secs
1621 .max(0.1),
1622 );
1623 thread::sleep(interval);
1624
1625 let now = crate::time::now();
1626 let peer_timeout_secs = lock_or_recover(&runtime, "auto runtime").peer_timeout_secs;
1627
1628 let timed_out = lock_or_recover(&shared, "auto shared state")
1629 .remove_timed_out_peers(now, peer_timeout_secs);
1630
1631 for (peer_key, iface_id) in &timed_out {
1632 log::info!(
1633 "[{}] Peer timed out: {}%{}",
1634 name,
1635 peer_key.link_local_addr,
1636 peer_key.if_index
1637 );
1638 let _ = tx.send(Event::InterfaceDown(*iface_id));
1639 }
1640 }
1641}
1642
1643#[cfg(unix)]
1647fn socket_fd(socket: &UdpSocket) -> i32 {
1648 use std::os::unix::io::AsRawFd;
1649 socket.as_raw_fd()
1650}
1651
1652#[cfg(not(unix))]
1653fn socket_fd(_socket: &UdpSocket) -> i32 {
1654 0
1655}
1656
1657use super::{InterfaceConfigData, InterfaceFactory, StartContext, StartResult};
1660
1661pub struct AutoFactory;
1663
1664impl InterfaceFactory for AutoFactory {
1665 fn type_name(&self) -> &str {
1666 "AutoInterface"
1667 }
1668
1669 fn parse_config(
1670 &self,
1671 name: &str,
1672 id: InterfaceId,
1673 params: &HashMap<String, String>,
1674 ) -> Result<Box<dyn InterfaceConfigData>, String> {
1675 let group_id = params
1676 .get("group_id")
1677 .map(|v| v.as_bytes().to_vec())
1678 .unwrap_or_else(|| DEFAULT_GROUP_ID.to_vec());
1679
1680 let discovery_scope = params
1681 .get("discovery_scope")
1682 .map(|v| match v.to_lowercase().as_str() {
1683 "link" => SCOPE_LINK.to_string(),
1684 "admin" => SCOPE_ADMIN.to_string(),
1685 "site" => SCOPE_SITE.to_string(),
1686 "organisation" | "organization" => SCOPE_ORGANISATION.to_string(),
1687 "global" => SCOPE_GLOBAL.to_string(),
1688 _ => v.clone(),
1689 })
1690 .unwrap_or_else(|| SCOPE_LINK.to_string());
1691
1692 let discovery_port = params
1693 .get("discovery_port")
1694 .and_then(|v| v.parse().ok())
1695 .unwrap_or(DEFAULT_DISCOVERY_PORT);
1696
1697 let data_port = params
1698 .get("data_port")
1699 .and_then(|v| v.parse().ok())
1700 .unwrap_or(DEFAULT_DATA_PORT);
1701
1702 let multicast_address_type = params
1703 .get("multicast_address_type")
1704 .map(|v| match v.to_lowercase().as_str() {
1705 "permanent" => MULTICAST_PERMANENT_ADDRESS_TYPE.to_string(),
1706 "temporary" => MULTICAST_TEMPORARY_ADDRESS_TYPE.to_string(),
1707 _ => v.clone(),
1708 })
1709 .unwrap_or_else(|| MULTICAST_TEMPORARY_ADDRESS_TYPE.to_string());
1710
1711 let configured_bitrate = params
1712 .get("configured_bitrate")
1713 .or_else(|| params.get("bitrate"))
1714 .and_then(|v| v.parse().ok())
1715 .unwrap_or(BITRATE_GUESS);
1716
1717 let allowed_interfaces = params
1718 .get("devices")
1719 .or_else(|| params.get("allowed_interfaces"))
1720 .map(|v| {
1721 v.split(',')
1722 .map(|s| s.trim().to_string())
1723 .filter(|s| !s.is_empty())
1724 .collect()
1725 })
1726 .unwrap_or_default();
1727
1728 let ignored_interfaces = params
1729 .get("ignored_devices")
1730 .or_else(|| params.get("ignored_interfaces"))
1731 .map(|v| {
1732 v.split(',')
1733 .map(|s| s.trim().to_string())
1734 .filter(|s| !s.is_empty())
1735 .collect()
1736 })
1737 .unwrap_or_default();
1738
1739 let max_peers = params
1740 .get("max_peers")
1741 .and_then(|v| v.parse::<usize>().ok())
1742 .filter(|max_peers| *max_peers > 0)
1743 .unwrap_or(DEFAULT_MAX_PEERS);
1744
1745 Ok(Box::new(AutoConfig {
1746 name: name.to_string(),
1747 group_id,
1748 discovery_scope,
1749 discovery_port,
1750 data_port,
1751 multicast_address_type,
1752 allowed_interfaces,
1753 ignored_interfaces,
1754 configured_bitrate,
1755 max_peers,
1756 interface_id: id,
1757 ingress_control: rns_core::transport::types::IngressControlConfig::enabled(),
1758 runtime: Arc::new(Mutex::new(AutoRuntime {
1759 announce_interval_secs: ANNOUNCE_INTERVAL,
1760 peer_timeout_secs: PEERING_TIMEOUT,
1761 peer_job_interval_secs: PEER_JOB_INTERVAL,
1762 })),
1763 }))
1764 }
1765
1766 fn start(
1767 &self,
1768 config: Box<dyn InterfaceConfigData>,
1769 ctx: StartContext,
1770 ) -> std::io::Result<StartResult> {
1771 let mut auto_config = *config.into_any().downcast::<AutoConfig>().map_err(|_| {
1772 std::io::Error::new(std::io::ErrorKind::InvalidData, "wrong config type")
1773 })?;
1774
1775 auto_config.ingress_control = ctx.ingress_control;
1776 let parent_id = auto_config.interface_id;
1777 let control = start_with_template(
1778 auto_config,
1779 ctx.tx,
1780 ctx.next_dynamic_id,
1781 Some(super::DynamicInterfaceTemplate {
1782 parent_id,
1783 interface_type: "AutoInterfacePeer".into(),
1784 ifac: ctx.ifac,
1785 mode: ctx.mode,
1786 gravity: ctx.gravity,
1787 recursive_prs: ctx.recursive_prs,
1788 announces_from_internal: ctx.announces_from_internal,
1789 announces_to_internal: ctx.announces_to_internal,
1790 stamp_costs: ctx.stamp_costs,
1791 }),
1792 ctx.underlay_mark,
1793 )?;
1794 Ok(StartResult::Listener {
1795 control: Some(control),
1796 })
1797 }
1798}
1799
1800pub(crate) fn auto_runtime_handle_from_config(config: &AutoConfig) -> AutoRuntimeConfigHandle {
1801 AutoRuntimeConfigHandle {
1802 interface_name: config.name.clone(),
1803 runtime: Arc::clone(&config.runtime),
1804 startup: AutoRuntime::from_config(config),
1805 }
1806}
1807
1808#[cfg(test)]
1811mod tests {
1812 use super::*;
1813
1814 #[test]
1815 fn listener_control_stops_auto_supervisor() {
1816 let running = Arc::new(AtomicBool::new(true));
1817 let control = super::super::ListenerControl::with_running_flag(Arc::clone(&running));
1818
1819 control.request_stop();
1820
1821 assert!(!running.load(Ordering::Relaxed));
1822 assert!(control.should_stop());
1823 }
1824
1825 #[test]
1828 fn multicast_address_default_group() {
1829 let addr = derive_multicast_address(
1831 DEFAULT_GROUP_ID,
1832 MULTICAST_TEMPORARY_ADDRESS_TYPE,
1833 SCOPE_LINK,
1834 );
1835 assert_eq!(addr, "ff12:0:d70b:fb1c:16e4:5e39:485e:31e1");
1836 }
1837
1838 #[test]
1839 fn multicast_address_custom_group() {
1840 let addr =
1841 derive_multicast_address(b"testgroup", MULTICAST_TEMPORARY_ADDRESS_TYPE, SCOPE_LINK);
1842 assert!(addr.starts_with("ff12:0:"));
1844 assert_ne!(addr, "ff12:0:d70b:fb1c:16e4:5e39:485e:31e1");
1846 }
1847
1848 #[test]
1849 fn multicast_address_scope_admin() {
1850 let addr = derive_multicast_address(
1851 DEFAULT_GROUP_ID,
1852 MULTICAST_TEMPORARY_ADDRESS_TYPE,
1853 SCOPE_ADMIN,
1854 );
1855 assert!(addr.starts_with("ff14:0:"));
1856 }
1857
1858 #[test]
1859 fn multicast_address_permanent_type() {
1860 let addr = derive_multicast_address(
1861 DEFAULT_GROUP_ID,
1862 MULTICAST_PERMANENT_ADDRESS_TYPE,
1863 SCOPE_LINK,
1864 );
1865 assert!(addr.starts_with("ff02:0:"));
1866 }
1867
1868 #[test]
1869 fn multicast_address_parseable() {
1870 let addr = derive_multicast_address(
1871 DEFAULT_GROUP_ID,
1872 MULTICAST_TEMPORARY_ADDRESS_TYPE,
1873 SCOPE_LINK,
1874 );
1875 let ip = parse_multicast_addr(&addr);
1876 assert!(ip.is_some());
1877 assert!(ip.unwrap().is_multicast());
1878 }
1879
1880 #[test]
1883 fn discovery_token_interop() {
1884 let token = compute_discovery_token(DEFAULT_GROUP_ID, "fe80::1");
1886 let expected = "97b25576749ea936b0d8a8536ffaf442d157cf47d460dcf13c48b7bd18b6c163";
1887 let got = token
1888 .iter()
1889 .map(|b| format!("{:02x}", b))
1890 .collect::<String>();
1891 assert_eq!(got, expected);
1892 }
1893
1894 #[test]
1895 fn discovery_token_interop_2() {
1896 let token = compute_discovery_token(DEFAULT_GROUP_ID, "fe80::dead:beef:1234:5678");
1898 let expected = "46b6ec7595504b6a35f06bd4bfff71567fb82fcf2706cd361bab20409c42d072";
1899 let got = token
1900 .iter()
1901 .map(|b| format!("{:02x}", b))
1902 .collect::<String>();
1903 assert_eq!(got, expected);
1904 }
1905
1906 #[test]
1907 fn discovery_token_different_groups() {
1908 let t1 = compute_discovery_token(b"reticulum", "fe80::1");
1909 let t2 = compute_discovery_token(b"othergroup", "fe80::1");
1910 assert_ne!(t1, t2);
1911 }
1912
1913 #[test]
1914 fn discovery_token_different_addrs() {
1915 let t1 = compute_discovery_token(DEFAULT_GROUP_ID, "fe80::1");
1916 let t2 = compute_discovery_token(DEFAULT_GROUP_ID, "fe80::2");
1917 assert_ne!(t1, t2);
1918 }
1919
1920 #[test]
1923 fn dedup_basic() {
1924 let next_id = Arc::new(AtomicU64::new(1));
1925 let mut state = SharedState::new(next_id);
1926
1927 let hash = [0xAA; 32];
1928 let now = 1000.0;
1929
1930 assert!(!state.is_duplicate(&hash, now));
1931 state.add_dedup(hash, now);
1932 assert!(state.is_duplicate(&hash, now));
1933 }
1934
1935 #[test]
1936 fn dedup_expired() {
1937 let next_id = Arc::new(AtomicU64::new(1));
1938 let mut state = SharedState::new(next_id);
1939
1940 let hash = [0xBB; 32];
1941 state.add_dedup(hash, 1000.0);
1942
1943 assert!(state.is_duplicate(&hash, 1000.5));
1945 assert!(!state.is_duplicate(&hash, 1001.0));
1947 }
1948
1949 #[test]
1950 fn dedup_max_length() {
1951 let next_id = Arc::new(AtomicU64::new(1));
1952 let mut state = SharedState::new(next_id);
1953
1954 for i in 0..MULTI_IF_DEQUE_LEN + 10 {
1956 let mut hash = [0u8; 32];
1957 hash[0] = (i & 0xFF) as u8;
1958 hash[1] = ((i >> 8) & 0xFF) as u8;
1959 state.add_dedup(hash, 1000.0);
1960 }
1961
1962 assert_eq!(state.dedup_deque.len(), MULTI_IF_DEQUE_LEN);
1963 }
1964
1965 #[test]
1968 fn peer_refresh() {
1969 let next_id = Arc::new(AtomicU64::new(100));
1970 let mut state = SharedState::new(next_id);
1971 let key = peer_key("fe80::1", 2);
1972
1973 state.peers.insert(
1974 key.clone(),
1975 AutoPeer {
1976 interface_id: InterfaceId(100),
1977 link_local_addr: key.link_local_addr.clone(),
1978 ifname: "eth0".to_string(),
1979 if_index: key.if_index,
1980 last_heard: 1000.0,
1981 },
1982 );
1983
1984 state.refresh_peer(&key, 2000.0);
1985 assert_eq!(state.peers[&key].last_heard, 2000.0);
1986 }
1987
1988 #[test]
1989 fn peer_not_found_refresh() {
1990 let next_id = Arc::new(AtomicU64::new(100));
1991 let mut state = SharedState::new(next_id);
1992 state.refresh_peer(&peer_key("fe80::999", 4), 1000.0);
1994 }
1995
1996 fn peer_key(addr: &str, if_index: u32) -> PeerKey {
1997 PeerKey {
1998 link_local_addr: addr.to_string(),
1999 if_index,
2000 }
2001 }
2002
2003 #[test]
2004 fn peer_key_distinguishes_same_link_local_on_different_interfaces() {
2005 let next_id = Arc::new(AtomicU64::new(100));
2006 let mut state = SharedState::new(next_id);
2007 let eth = peer_key("fe80::1", 2);
2008 let wlan = peer_key("fe80::1", 4);
2009
2010 state.peers.insert(
2011 eth.clone(),
2012 AutoPeer {
2013 interface_id: InterfaceId(100),
2014 link_local_addr: eth.link_local_addr.clone(),
2015 ifname: "eth0".to_string(),
2016 if_index: eth.if_index,
2017 last_heard: 1000.0,
2018 },
2019 );
2020 state.peers.insert(
2021 wlan.clone(),
2022 AutoPeer {
2023 interface_id: InterfaceId(101),
2024 link_local_addr: wlan.link_local_addr.clone(),
2025 ifname: "wlan0".to_string(),
2026 if_index: wlan.if_index,
2027 last_heard: 1000.0,
2028 },
2029 );
2030
2031 assert_eq!(state.peers.len(), 2);
2032 assert_eq!(state.peers[ð].interface_id, InterfaceId(100));
2033 assert_eq!(state.peers[&wlan].interface_id, InterfaceId(101));
2034 }
2035
2036 #[test]
2037 fn peer_target_addr_uses_scope_id() {
2038 let target = peer_target_addr(&peer_key("fe80::1", 7), 42671).unwrap();
2039
2040 assert_eq!(target.port(), 42671);
2041 assert_eq!(target.scope_id(), 7);
2042 }
2043
2044 #[test]
2045 fn remove_peers_for_worker_emits_interface_down() {
2046 let next_id = Arc::new(AtomicU64::new(100));
2047 let shared = Arc::new(Mutex::new(SharedState::new(next_id)));
2048 let worker = worker_key("wlan0", 4, "fe80::abcd");
2049 let removed = peer_key("fe80::1", 4);
2050 let kept = peer_key("fe80::1", 5);
2051 let (tx, rx) = crate::event::channel_with_capacity(4);
2052
2053 {
2054 let mut state = lock_or_recover(&shared, "test auto shared state");
2055 state.peers.insert(
2056 removed.clone(),
2057 AutoPeer {
2058 interface_id: InterfaceId(100),
2059 link_local_addr: removed.link_local_addr.clone(),
2060 ifname: "wlan0".to_string(),
2061 if_index: removed.if_index,
2062 last_heard: 1000.0,
2063 },
2064 );
2065 state.peers.insert(
2066 kept.clone(),
2067 AutoPeer {
2068 interface_id: InterfaceId(101),
2069 link_local_addr: kept.link_local_addr.clone(),
2070 ifname: "wlan1".to_string(),
2071 if_index: kept.if_index,
2072 last_heard: 1000.0,
2073 },
2074 );
2075 }
2076
2077 remove_peers_for_worker(&shared, &tx, &worker);
2078
2079 let state = lock_or_recover(&shared, "test auto shared state");
2080 assert!(!state.peers.contains_key(&removed));
2081 assert!(state.peers.contains_key(&kept));
2082 drop(state);
2083
2084 assert!(matches!(
2085 rx.recv_timeout(std::time::Duration::from_secs(1)).unwrap(),
2086 Event::InterfaceDown(InterfaceId(100))
2087 ));
2088 assert!(rx.try_recv().is_err());
2089 }
2090
2091 #[test]
2094 fn enumerate_returns_vec() {
2095 let interfaces = enumerate_interfaces(&[], &[]);
2098 for iface in &interfaces {
2100 assert!(!iface.name.is_empty());
2101 assert!(iface.link_local_addr.starts_with("fe80"));
2102 assert!(iface.index > 0);
2103 }
2104 }
2105
2106 #[test]
2107 fn enumerate_with_ignored() {
2108 let interfaces = enumerate_interfaces(
2110 &[],
2111 &[
2112 "lo".to_string(),
2113 "eth0".to_string(),
2114 "wlan0".to_string(),
2115 "enp0s3".to_string(),
2116 "docker0".to_string(),
2117 ],
2118 );
2119 for iface in &interfaces {
2121 assert_ne!(iface.name, "lo");
2122 assert_ne!(iface.name, "eth0");
2123 assert_ne!(iface.name, "wlan0");
2124 }
2125 }
2126
2127 #[test]
2128 fn enumerate_with_allowed_nonexistent() {
2129 let interfaces = enumerate_interfaces(&["nonexistent_if_12345".to_string()], &[]);
2131 assert!(interfaces.is_empty());
2132 }
2133
2134 fn worker_key(name: &str, index: u32, addr: &str) -> AutoWorkerKey {
2135 AutoWorkerKey {
2136 ifname: name.to_string(),
2137 if_index: index,
2138 link_local_addr: addr.to_string(),
2139 }
2140 }
2141
2142 #[test]
2143 fn reconcile_worker_keys_adds_new_interface() {
2144 let desired = vec![worker_key("wlan0", 4, "fe80::1")];
2145
2146 assert_eq!(
2147 reconcile_worker_keys(Vec::new(), desired.clone()),
2148 vec![WorkerReconcileAction::Add(desired[0].clone())]
2149 );
2150 }
2151
2152 #[test]
2153 fn reconcile_worker_keys_keeps_unchanged_interface() {
2154 let key = worker_key("wlan0", 4, "fe80::1");
2155
2156 assert!(reconcile_worker_keys(vec![key.clone()], vec![key]).is_empty());
2157 }
2158
2159 #[test]
2160 fn reconcile_worker_keys_removes_missing_interface() {
2161 let active = vec![worker_key("wlan0", 4, "fe80::1")];
2162
2163 assert_eq!(
2164 reconcile_worker_keys(active.clone(), Vec::new()),
2165 vec![WorkerReconcileAction::Remove(active[0].clone())]
2166 );
2167 }
2168
2169 #[test]
2170 fn reconcile_worker_keys_replaces_changed_interface_identity() {
2171 let old = worker_key("wlan0", 4, "fe80::1");
2172 let new = worker_key("wlan0", 9, "fe80::2");
2173 let actions = reconcile_worker_keys(vec![old.clone()], vec![new.clone()]);
2174
2175 assert!(actions.contains(&WorkerReconcileAction::Remove(old)));
2176 assert!(actions.contains(&WorkerReconcileAction::Add(new)));
2177 assert_eq!(actions.len(), 2);
2178 }
2179
2180 #[test]
2181 fn reconcile_auto_workers_starts_new_worker() {
2182 let key = worker_key("wlan0", 4, "fe80::1");
2183 let mut workers = HashMap::new();
2184 let mut starts = Vec::new();
2185
2186 reconcile_auto_workers(&mut workers, vec![key.clone()], |key| {
2187 starts.push(key.clone());
2188 Ok(RunningAutoWorker {
2189 running: Arc::new(AtomicBool::new(true)),
2190 })
2191 });
2192
2193 assert_eq!(starts, vec![key.clone()]);
2194 assert!(matches!(
2195 workers.get(&key),
2196 Some(AutoWorkerEntry::Running(_))
2197 ));
2198 }
2199
2200 #[test]
2201 fn reconcile_auto_workers_stops_removed_worker() {
2202 let key = worker_key("wlan0", 4, "fe80::1");
2203 let running = Arc::new(AtomicBool::new(true));
2204 let mut workers = HashMap::from([(
2205 key.clone(),
2206 AutoWorkerEntry::Running(RunningAutoWorker {
2207 running: running.clone(),
2208 }),
2209 )]);
2210
2211 reconcile_auto_workers(&mut workers, Vec::new(), |_| unreachable!());
2212
2213 assert!(!running.load(Ordering::Relaxed));
2214 assert!(!workers.contains_key(&key));
2215 }
2216
2217 #[test]
2218 fn reconcile_auto_workers_leaves_unchanged_worker_running() {
2219 let key = worker_key("wlan0", 4, "fe80::1");
2220 let running = Arc::new(AtomicBool::new(true));
2221 let mut workers = HashMap::from([(
2222 key.clone(),
2223 AutoWorkerEntry::Running(RunningAutoWorker {
2224 running: running.clone(),
2225 }),
2226 )]);
2227 let mut starts = 0;
2228
2229 reconcile_auto_workers(&mut workers, vec![key.clone()], |_| {
2230 starts += 1;
2231 unreachable!()
2232 });
2233
2234 assert_eq!(starts, 0);
2235 assert!(running.load(Ordering::Relaxed));
2236 assert!(workers.contains_key(&key));
2237 }
2238
2239 #[test]
2240 fn reconcile_auto_workers_records_pending_start_failure() {
2241 let key = worker_key("wlan0", 4, "fe80::1");
2242 let mut workers = HashMap::new();
2243
2244 reconcile_auto_workers(&mut workers, vec![key.clone()], |_| {
2245 Err(io::Error::new(io::ErrorKind::AddrInUse, "busy"))
2246 });
2247
2248 assert!(matches!(workers.get(&key), Some(AutoWorkerEntry::Pending)));
2249 }
2250
2251 #[test]
2252 fn reconcile_auto_workers_retries_pending_worker() {
2253 let key = worker_key("wlan0", 4, "fe80::1");
2254 let running = Arc::new(AtomicBool::new(true));
2255 let mut workers = HashMap::from([(key.clone(), AutoWorkerEntry::Pending)]);
2256 let mut starts = 0;
2257
2258 reconcile_auto_workers(&mut workers, vec![key.clone()], |_| {
2259 starts += 1;
2260 Ok(RunningAutoWorker {
2261 running: running.clone(),
2262 })
2263 });
2264
2265 assert_eq!(starts, 1);
2266 assert!(matches!(
2267 workers.get(&key),
2268 Some(AutoWorkerEntry::Running(_))
2269 ));
2270 }
2271
2272 #[test]
2273 fn reconcile_auto_workers_keeps_pending_worker_on_retry_failure() {
2274 let key = worker_key("wlan0", 4, "fe80::1");
2275 let mut workers = HashMap::from([(key.clone(), AutoWorkerEntry::Pending)]);
2276 let mut starts = 0;
2277
2278 reconcile_auto_workers(&mut workers, vec![key.clone()], |_| {
2279 starts += 1;
2280 Err(io::Error::new(io::ErrorKind::AddrInUse, "busy"))
2281 });
2282
2283 assert_eq!(starts, 1);
2284 assert!(matches!(workers.get(&key), Some(AutoWorkerEntry::Pending)));
2285 }
2286
2287 #[test]
2288 fn reconcile_auto_workers_removes_pending_worker() {
2289 let key = worker_key("wlan0", 4, "fe80::1");
2290 let mut workers = HashMap::from([(key.clone(), AutoWorkerEntry::Pending)]);
2291
2292 reconcile_auto_workers(&mut workers, Vec::new(), |_| unreachable!());
2293
2294 assert!(!workers.contains_key(&key));
2295 }
2296
2297 #[test]
2298 fn filter_skips_android_system_interfaces() {
2299 let allowed = Vec::new();
2300 let ignored = Vec::new();
2301
2302 for name in ["dummy0", "lo", "tun0", "rmnet0", "rmnet7"] {
2303 assert!(
2304 !should_adopt_interface_name(name, &allowed, &ignored, ANDROID_IGNORE_IFS),
2305 "{name} should be skipped by Android AutoInterface defaults"
2306 );
2307 }
2308 }
2309
2310 #[test]
2311 fn filter_does_not_skip_rmnet8_by_android_defaults() {
2312 assert!(should_adopt_interface_name(
2313 "rmnet8",
2314 &[],
2315 &[],
2316 ANDROID_IGNORE_IFS
2317 ));
2318 }
2319
2320 #[test]
2321 fn filter_allowed_overrides_system_ignored_interface() {
2322 assert!(should_adopt_interface_name(
2323 "rmnet0",
2324 &["rmnet0".to_string()],
2325 &[],
2326 ANDROID_IGNORE_IFS
2327 ));
2328
2329 assert!(should_adopt_interface_name(
2330 "lo0",
2331 &["lo0".to_string()],
2332 &[],
2333 &[]
2334 ));
2335 }
2336
2337 #[test]
2338 fn filter_ignored_wins_over_allowed_interface() {
2339 assert!(!should_adopt_interface_name(
2340 "rmnet0",
2341 &["rmnet0".to_string()],
2342 &["rmnet0".to_string()],
2343 ANDROID_IGNORE_IFS
2344 ));
2345 }
2346
2347 #[test]
2348 fn filter_allowed_list_excludes_unlisted_interfaces() {
2349 assert!(!should_adopt_interface_name(
2350 "wlan0",
2351 &["eth0".to_string()],
2352 &[],
2353 ANDROID_IGNORE_IFS
2354 ));
2355
2356 assert!(should_adopt_interface_name(
2357 "wlan0",
2358 &["wlan0".to_string()],
2359 &[],
2360 ANDROID_IGNORE_IFS
2361 ));
2362 }
2363
2364 #[test]
2367 fn config_defaults() {
2368 let config = AutoConfig::default();
2369 assert_eq!(config.group_id, DEFAULT_GROUP_ID);
2370 assert_eq!(config.discovery_scope, SCOPE_LINK);
2371 assert_eq!(config.discovery_port, DEFAULT_DISCOVERY_PORT);
2372 assert_eq!(config.data_port, DEFAULT_DATA_PORT);
2373 assert_eq!(
2374 config.multicast_address_type,
2375 MULTICAST_TEMPORARY_ADDRESS_TYPE
2376 );
2377 assert_eq!(config.configured_bitrate, BITRATE_GUESS);
2378 assert!(config.allowed_interfaces.is_empty());
2379 assert!(config.ignored_interfaces.is_empty());
2380 assert_eq!(config.max_peers, DEFAULT_MAX_PEERS);
2381 }
2382
2383 fn parse_auto_config(params: &[(&str, &str)]) -> AutoConfig {
2384 let params = params
2385 .iter()
2386 .map(|(k, v)| (k.to_string(), v.to_string()))
2387 .collect::<HashMap<_, _>>();
2388 let config = AutoFactory
2389 .parse_config("auto", InterfaceId(1), ¶ms)
2390 .unwrap();
2391 *config.into_any().downcast::<AutoConfig>().unwrap()
2392 }
2393
2394 #[test]
2395 fn parse_config_reads_max_peers() {
2396 assert_eq!(parse_auto_config(&[]).max_peers, DEFAULT_MAX_PEERS);
2397 assert_eq!(parse_auto_config(&[("max_peers", "16")]).max_peers, 16);
2398 assert_eq!(
2399 parse_auto_config(&[("max_peers", "0")]).max_peers,
2400 DEFAULT_MAX_PEERS
2401 );
2402 assert_eq!(
2403 parse_auto_config(&[("max_peers", "many")]).max_peers,
2404 DEFAULT_MAX_PEERS
2405 );
2406 }
2407
2408 fn insert_peer(state: &mut SharedState, addr: &str, ifname: &str, last_heard: f64) {
2411 let key = peer_key(addr, 2);
2412 let id = InterfaceId(state.next_id.fetch_add(1, Ordering::Relaxed));
2413 state.peers.insert(
2414 key.clone(),
2415 AutoPeer {
2416 interface_id: id,
2417 link_local_addr: key.link_local_addr.clone(),
2418 ifname: ifname.to_string(),
2419 if_index: key.if_index,
2420 last_heard,
2421 },
2422 );
2423 }
2424
2425 #[test]
2426 fn peer_cap_refuses_peer_beyond_limit_and_admits_after_timeout() {
2427 let mut state = SharedState::new(Arc::new(AtomicU64::new(100)));
2428 state.max_peers = 3;
2429 let now = Instant::now();
2430
2431 for (i, addr) in ["fe80::1", "fe80::2", "fe80::3"].iter().enumerate() {
2432 assert_eq!(
2433 state.admit_new_peer("eth0", now),
2434 PeerAdmissionDecision::Admit
2435 );
2436 insert_peer(&mut state, addr, "eth0", 1000.0 + i as f64);
2437 }
2438 assert_eq!(
2439 state.admit_new_peer("eth0", now),
2440 PeerAdmissionDecision::AtCapacity
2441 );
2442 assert_eq!(
2444 state.admit_new_peer("wlan0", now),
2445 PeerAdmissionDecision::Admit
2446 );
2447
2448 let removed = state.remove_timed_out_peers(1000.0 + PEERING_TIMEOUT + 0.5, PEERING_TIMEOUT);
2450 assert_eq!(removed.len(), 1);
2451 assert_eq!(removed[0].0, peer_key("fe80::1", 2));
2452 assert_eq!(state.active_peers_on("eth0"), 2);
2453
2454 assert_eq!(
2455 state.admit_new_peer("eth0", now),
2456 PeerAdmissionDecision::Admit
2457 );
2458 }
2459
2460 #[test]
2461 fn peer_admission_rate_limit_refuses_within_window_and_admits_after() {
2462 let mut admission = PeerAdmission::default();
2463 let start = Instant::now();
2464
2465 for _ in 0..MAX_NEW_PEERS_PER_WINDOW {
2466 assert_eq!(
2467 admission.decide(0, usize::MAX, start),
2468 PeerAdmissionDecision::Admit
2469 );
2470 }
2471 assert_eq!(
2472 admission.decide(0, usize::MAX, start),
2473 PeerAdmissionDecision::RateLimited
2474 );
2475 let almost = start + PEER_ADMISSION_WINDOW - Duration::from_millis(1);
2476 assert_eq!(
2477 admission.decide(0, usize::MAX, almost),
2478 PeerAdmissionDecision::RateLimited
2479 );
2480
2481 let next_window = start + PEER_ADMISSION_WINDOW;
2482 assert_eq!(
2483 admission.decide(0, usize::MAX, next_window),
2484 PeerAdmissionDecision::Admit
2485 );
2486 }
2487
2488 #[test]
2489 fn peer_admission_cap_takes_precedence_and_does_not_use_window() {
2490 let mut admission = PeerAdmission::default();
2491 let now = Instant::now();
2492 for _ in 0..MAX_NEW_PEERS_PER_WINDOW * 2 {
2493 assert_eq!(
2494 admission.decide(4, 4, now),
2495 PeerAdmissionDecision::AtCapacity
2496 );
2497 }
2498 assert_eq!(admission.decide(3, 4, now), PeerAdmissionDecision::Admit);
2499 }
2500
2501 #[test]
2502 fn peer_refusal_warnings_are_spaced_out() {
2503 let mut admission = PeerAdmission::default();
2504 let start = Instant::now();
2505 assert_eq!(admission.note_refusal(start), Some(1));
2506 assert_eq!(admission.note_refusal(start), None);
2507 assert_eq!(admission.note_refusal(start + Duration::from_secs(1)), None);
2508 assert_eq!(
2509 admission.note_refusal(start + PEER_REFUSAL_LOG_INTERVAL),
2510 Some(3)
2511 );
2512 }
2513
2514 #[test]
2515 fn peers_advertise_no_more_than_the_receive_buffer_takes() {
2516 let shared = Arc::new(Mutex::new(SharedState::new(Arc::new(AtomicU64::new(900)))));
2517 let runtime = Arc::new(Mutex::new(AutoRuntime::from_config(&AutoConfig::default())));
2518 let event = add_peer(AddPeerContext {
2519 shared: &shared,
2520 peer_key: &PeerKey {
2521 link_local_addr: "fe80::1".into(),
2522 if_index: 1,
2523 },
2524 data_port: DEFAULT_DATA_PORT,
2525 name: "auto-test",
2526 ifname: "test0",
2527 configured_bitrate: BITRATE_GUESS,
2528 ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
2529 runtime: &runtime,
2530 dynamic_template: None,
2531 underlay_mark: None,
2532 inline: false,
2533 });
2534 let Some(Event::InterfaceUp(_, Some(_), Some(info))) = event else {
2535 panic!("expected the new peer's InterfaceUp");
2536 };
2537 assert_eq!(info.mtu as usize, HW_MTU);
2540 }
2541
2542 #[test]
2543 fn remove_peers_for_worker_resets_peer_admission() {
2544 let next_id = Arc::new(AtomicU64::new(100));
2545 let shared = Arc::new(Mutex::new(SharedState::new(next_id)));
2546 let now = Instant::now();
2547 {
2548 let mut state = lock_or_recover(&shared, "auto shared state");
2549 for _ in 0..MAX_NEW_PEERS_PER_WINDOW {
2550 state.admit_new_peer("eth0", now);
2551 }
2552 assert_eq!(
2553 state.admit_new_peer("eth0", now),
2554 PeerAdmissionDecision::RateLimited
2555 );
2556 }
2557 let (tx, _rx) = crate::event::channel();
2558 let key = AutoWorkerKey {
2559 ifname: "eth0".to_string(),
2560 if_index: 2,
2561 link_local_addr: "fe80::10".to_string(),
2562 };
2563 remove_peers_for_worker(&shared, &tx, &key);
2564 assert_eq!(
2565 lock_or_recover(&shared, "auto shared state").admit_new_peer("eth0", now),
2566 PeerAdmissionDecision::Admit
2567 );
2568 }
2569
2570 #[test]
2573 fn constants_match_python() {
2574 assert_eq!(DEFAULT_DISCOVERY_PORT, 29716);
2575 assert_eq!(DEFAULT_DATA_PORT, 42671);
2576 assert_eq!(HW_MTU, 1196);
2577 assert_eq!(MULTI_IF_DEQUE_LEN, 48);
2578 assert!((MULTI_IF_DEQUE_TTL - 0.75).abs() < f64::EPSILON);
2579 assert!((PEERING_TIMEOUT - 22.0).abs() < f64::EPSILON);
2580 assert!((ANNOUNCE_INTERVAL - 1.6).abs() < f64::EPSILON);
2581 assert!((PEER_JOB_INTERVAL - 4.0).abs() < f64::EPSILON);
2582 assert!((MCAST_ECHO_TIMEOUT - 6.5).abs() < f64::EPSILON);
2583 assert_eq!(BITRATE_GUESS, 10_000_000);
2584 }
2585
2586 #[test]
2587 fn unicast_discovery_port() {
2588 let unicast_port = DEFAULT_DISCOVERY_PORT + 1;
2590 assert_eq!(unicast_port, 29717);
2591 }
2592
2593 #[test]
2594 fn reverse_peering_interval() {
2595 let interval = ANNOUNCE_INTERVAL * REVERSE_PEERING_MULTIPLIER;
2596 assert!((interval - 5.2).abs() < 0.01);
2597 }
2598}