1use std::collections::{HashMap, HashSet, VecDeque};
15use std::io;
16use std::net::{Ipv6Addr, SocketAddrV6, UdpSocket};
17use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
18use std::sync::{Arc, Mutex};
19use std::thread;
20use std::time::Duration;
21
22use rns_core::transport::types::InterfaceId;
23
24use crate::event::{Event, EventSender};
25use crate::interface::{lock_or_recover, Writer};
26
27pub const DEFAULT_DISCOVERY_PORT: u16 = 29716;
31
32pub const DEFAULT_DATA_PORT: u16 = 42671;
34
35pub const DEFAULT_GROUP_ID: &[u8] = b"reticulum";
37
38pub const DEFAULT_IFAC_SIZE: usize = 16;
40
41pub const HW_MTU: usize = 1196;
43
44pub const SCOPE_LINK: &str = "2";
46pub const SCOPE_ADMIN: &str = "4";
48pub const SCOPE_SITE: &str = "5";
50pub const SCOPE_ORGANISATION: &str = "8";
52pub const SCOPE_GLOBAL: &str = "e";
54
55pub const MULTICAST_PERMANENT_ADDRESS_TYPE: &str = "0";
57pub const MULTICAST_TEMPORARY_ADDRESS_TYPE: &str = "1";
59
60pub const PEERING_TIMEOUT: f64 = 22.0;
62
63pub const ANNOUNCE_INTERVAL: f64 = 1.6;
65
66pub const PEER_JOB_INTERVAL: f64 = 4.0;
68
69pub const MCAST_ECHO_TIMEOUT: f64 = 6.5;
71
72pub const BITRATE_GUESS: u64 = 10_000_000;
74
75pub const MULTI_IF_DEQUE_LEN: usize = 48;
77
78pub const MULTI_IF_DEQUE_TTL: f64 = 0.75;
80
81pub const REVERSE_PEERING_MULTIPLIER: f64 = 3.25;
83
84pub const AUTO_RESCAN_INTERVAL: f64 = 1.25;
86
87pub const ALL_IGNORE_IFS: &[&str] = &["lo0"];
89
90#[cfg_attr(not(any(target_os = "android", test)), allow(dead_code))]
91const ANDROID_IGNORE_IFS: &[&str] = &[
92 "dummy0", "lo", "tun0", "rmnet0", "rmnet1", "rmnet2", "rmnet3", "rmnet4", "rmnet5", "rmnet6",
93 "rmnet7",
94];
95
96#[derive(Debug, Clone)]
100pub struct AutoConfig {
101 pub name: String,
102 pub group_id: Vec<u8>,
103 pub discovery_scope: String,
104 pub discovery_port: u16,
105 pub data_port: u16,
106 pub multicast_address_type: String,
107 pub allowed_interfaces: Vec<String>,
108 pub ignored_interfaces: Vec<String>,
109 pub configured_bitrate: u64,
110 pub interface_id: InterfaceId,
112 pub ingress_control: rns_core::transport::types::IngressControlConfig,
113 pub runtime: Arc<Mutex<AutoRuntime>>,
114}
115
116#[derive(Debug, Clone)]
117pub struct AutoRuntime {
118 pub announce_interval_secs: f64,
119 pub peer_timeout_secs: f64,
120 pub peer_job_interval_secs: f64,
121}
122
123impl AutoRuntime {
124 pub fn from_config(_config: &AutoConfig) -> Self {
125 Self {
126 announce_interval_secs: ANNOUNCE_INTERVAL,
127 peer_timeout_secs: PEERING_TIMEOUT,
128 peer_job_interval_secs: PEER_JOB_INTERVAL,
129 }
130 }
131}
132
133#[derive(Debug, Clone)]
134pub struct AutoRuntimeConfigHandle {
135 pub interface_name: String,
136 pub runtime: Arc<Mutex<AutoRuntime>>,
137 pub startup: AutoRuntime,
138}
139
140impl Default for AutoConfig {
141 fn default() -> Self {
142 let mut config = AutoConfig {
143 name: String::new(),
144 group_id: DEFAULT_GROUP_ID.to_vec(),
145 discovery_scope: SCOPE_LINK.to_string(),
146 discovery_port: DEFAULT_DISCOVERY_PORT,
147 data_port: DEFAULT_DATA_PORT,
148 multicast_address_type: MULTICAST_TEMPORARY_ADDRESS_TYPE.to_string(),
149 allowed_interfaces: Vec::new(),
150 ignored_interfaces: Vec::new(),
151 configured_bitrate: BITRATE_GUESS,
152 interface_id: InterfaceId(0),
153 ingress_control: rns_core::transport::types::IngressControlConfig::enabled(),
154 runtime: Arc::new(Mutex::new(AutoRuntime {
155 announce_interval_secs: ANNOUNCE_INTERVAL,
156 peer_timeout_secs: PEERING_TIMEOUT,
157 peer_job_interval_secs: PEER_JOB_INTERVAL,
158 })),
159 };
160 let startup = AutoRuntime::from_config(&config);
161 config.runtime = Arc::new(Mutex::new(startup));
162 config
163 }
164}
165
166pub fn derive_multicast_address(group_id: &[u8], address_type: &str, scope: &str) -> String {
176 let group_hash = rns_crypto::sha256::sha256(group_id);
177 let g = &group_hash;
178
179 let w1 = (g[2] as u16) << 8 | g[3] as u16;
181 let w2 = (g[4] as u16) << 8 | g[5] as u16;
182 let w3 = (g[6] as u16) << 8 | g[7] as u16;
183 let w4 = (g[8] as u16) << 8 | g[9] as u16;
184 let w5 = (g[10] as u16) << 8 | g[11] as u16;
185 let w6 = (g[12] as u16) << 8 | g[13] as u16;
186
187 format!(
188 "ff{}{}:0:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
189 address_type, scope, w1, w2, w3, w4, w5, w6
190 )
191}
192
193pub fn parse_multicast_addr(addr: &str) -> Option<Ipv6Addr> {
195 addr.parse::<Ipv6Addr>().ok()
196}
197
198pub fn compute_discovery_token(group_id: &[u8], link_local_addr: &str) -> [u8; 32] {
202 let mut input = group_id.to_vec();
203 input.extend_from_slice(link_local_addr.as_bytes());
204 rns_crypto::sha256::sha256(&input)
205}
206
207#[derive(Debug, Clone)]
211pub struct LocalInterface {
212 pub name: String,
213 pub link_local_addr: String,
214 pub index: u32,
215}
216
217#[cfg(target_os = "android")]
218fn platform_ignored_interfaces() -> &'static [&'static str] {
219 ANDROID_IGNORE_IFS
220}
221
222#[cfg(not(target_os = "android"))]
223fn platform_ignored_interfaces() -> &'static [&'static str] {
224 &[]
225}
226
227fn should_adopt_interface_name(
228 name: &str,
229 allowed: &[String],
230 ignored: &[String],
231 platform_ignored: &[&str],
232) -> bool {
233 let is_allowed = allowed.iter().any(|a| a == name);
234 let is_system_ignored = ALL_IGNORE_IFS.iter().any(|&ig| ig == name)
235 || platform_ignored.iter().any(|&ig| ig == name);
236
237 if is_system_ignored && !is_allowed {
238 return false;
239 }
240
241 if ignored.iter().any(|ig| ig == name) {
242 return false;
243 }
244
245 if !allowed.is_empty() && !is_allowed {
246 return false;
247 }
248
249 true
250}
251
252pub fn enumerate_interfaces(allowed: &[String], ignored: &[String]) -> Vec<LocalInterface> {
256 let mut result = Vec::new();
257 let platform_ignored = platform_ignored_interfaces();
258
259 unsafe {
260 let mut ifaddrs: *mut libc::ifaddrs = std::ptr::null_mut();
261 if libc::getifaddrs(&mut ifaddrs) != 0 {
262 return result;
263 }
264
265 let mut current = ifaddrs;
266 while !current.is_null() {
267 let ifa = &*current;
268 current = ifa.ifa_next;
269
270 if ifa.ifa_addr.is_null() {
272 continue;
273 }
274
275 if (*ifa.ifa_addr).sa_family as i32 != libc::AF_INET6 {
277 continue;
278 }
279
280 let name = match std::ffi::CStr::from_ptr(ifa.ifa_name).to_str() {
282 Ok(s) => s.to_string(),
283 Err(_) => continue,
284 };
285
286 if !should_adopt_interface_name(&name, allowed, ignored, platform_ignored) {
287 continue;
288 }
289
290 let sa6 = ifa.ifa_addr as *const libc::sockaddr_in6;
292 let addr_bytes = (*sa6).sin6_addr.s6_addr;
293 let ipv6 = Ipv6Addr::from(addr_bytes);
294
295 let octets = ipv6.octets();
297 if octets[0] != 0xfe || (octets[1] & 0xc0) != 0x80 {
298 continue;
299 }
300
301 let addr_str = format!("{}", ipv6);
303
304 let index = libc::if_nametoindex(ifa.ifa_name);
306 if index == 0 {
307 continue;
308 }
309
310 if result.iter().any(|li: &LocalInterface| li.name == name) {
312 continue;
313 }
314
315 result.push(LocalInterface {
316 name,
317 link_local_addr: addr_str,
318 index,
319 });
320 }
321
322 libc::freeifaddrs(ifaddrs);
323 }
324
325 result
326}
327
328#[cfg_attr(not(test), allow(dead_code))]
331#[derive(Debug, Clone, PartialEq, Eq, Hash)]
332struct AutoWorkerKey {
333 ifname: String,
334 if_index: u32,
335 link_local_addr: String,
336}
337
338#[allow(dead_code)]
339impl AutoWorkerKey {
340 fn from_local_interface(local: &LocalInterface) -> Self {
341 Self {
342 ifname: local.name.clone(),
343 if_index: local.index,
344 link_local_addr: local.link_local_addr.clone(),
345 }
346 }
347}
348
349#[cfg_attr(not(test), allow(dead_code))]
350#[derive(Debug, Clone, PartialEq, Eq)]
351enum WorkerReconcileAction {
352 Add(AutoWorkerKey),
353 Remove(AutoWorkerKey),
354}
355
356#[cfg_attr(not(test), allow(dead_code))]
357fn reconcile_worker_keys(
358 active: impl IntoIterator<Item = AutoWorkerKey>,
359 desired: impl IntoIterator<Item = AutoWorkerKey>,
360) -> Vec<WorkerReconcileAction> {
361 let active = active.into_iter().collect::<HashSet<_>>();
362 let desired = desired.into_iter().collect::<HashSet<_>>();
363 let mut actions = Vec::new();
364
365 for key in active.difference(&desired) {
366 actions.push(WorkerReconcileAction::Remove(key.clone()));
367 }
368 for key in desired.difference(&active) {
369 actions.push(WorkerReconcileAction::Add(key.clone()));
370 }
371
372 actions.sort_by(|a, b| format!("{a:?}").cmp(&format!("{b:?}")));
373 actions
374}
375
376struct RunningAutoWorker {
377 running: Arc<AtomicBool>,
378}
379
380impl RunningAutoWorker {
381 fn stop(&self) {
382 self.running.store(false, Ordering::Relaxed);
383 }
384}
385
386enum AutoWorkerEntry {
387 Running(RunningAutoWorker),
388 Pending,
389}
390
391impl AutoWorkerEntry {
392 fn stop(&self) {
393 if let AutoWorkerEntry::Running(worker) = self {
394 worker.stop();
395 }
396 }
397}
398
399fn reconcile_auto_workers<F>(
400 workers: &mut HashMap<AutoWorkerKey, AutoWorkerEntry>,
401 desired: Vec<AutoWorkerKey>,
402 mut start_worker: F,
403) where
404 F: FnMut(&AutoWorkerKey) -> io::Result<RunningAutoWorker>,
405{
406 let mut desired_keys = desired.into_iter().collect::<HashSet<_>>();
407 let active_keys = workers.keys().cloned().collect::<Vec<_>>();
408
409 for key in active_keys {
410 if !desired_keys.contains(&key) {
411 if let Some(worker) = workers.remove(&key) {
412 worker.stop();
413 }
414 }
415 }
416
417 let mut desired = desired_keys.drain().collect::<Vec<_>>();
418 desired.sort_by(|a, b| a.ifname.cmp(&b.ifname).then(a.if_index.cmp(&b.if_index)));
419
420 for key in desired {
421 if matches!(workers.get(&key), Some(AutoWorkerEntry::Running(_))) {
422 continue;
423 }
424
425 let entry = match start_worker(&key) {
426 Ok(worker) => AutoWorkerEntry::Running(worker),
427 Err(e) => {
428 log::warn!(
429 "AutoInterface worker {} failed to start on {}%{}: {}",
430 key.ifname,
431 key.link_local_addr,
432 key.if_index,
433 e
434 );
435 AutoWorkerEntry::Pending
436 }
437 };
438 workers.insert(key, entry);
439 }
440}
441
442#[derive(Debug, Clone, PartialEq, Eq, Hash)]
443struct PeerKey {
444 link_local_addr: String,
445 if_index: u32,
446}
447
448struct AutoPeer {
450 interface_id: InterfaceId,
451 #[allow(dead_code)]
452 link_local_addr: String,
453 #[allow(dead_code)]
454 ifname: String,
455 #[allow(dead_code)]
456 if_index: u32,
457 last_heard: f64,
458}
459
460struct UdpWriter {
462 socket: UdpSocket,
463 target: SocketAddrV6,
464}
465
466impl Writer for UdpWriter {
467 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
468 self.socket.send_to(data, self.target)?;
469 Ok(())
470 }
471}
472
473struct SharedState {
475 peers: HashMap<PeerKey, AutoPeer>,
477 link_local_addresses: HashSet<PeerKey>,
479 dedup_deque: VecDeque<([u8; 32], f64)>,
481 online: bool,
483 next_id: Arc<AtomicU64>,
485}
486
487impl SharedState {
488 fn new(next_id: Arc<AtomicU64>) -> Self {
489 SharedState {
490 peers: HashMap::new(),
491 link_local_addresses: HashSet::new(),
492 dedup_deque: VecDeque::new(),
493 online: false,
494 next_id,
495 }
496 }
497
498 fn is_duplicate(&self, hash: &[u8; 32], now: f64) -> bool {
500 for (h, ts) in &self.dedup_deque {
501 if h == hash && now < *ts + MULTI_IF_DEQUE_TTL {
502 return true;
503 }
504 }
505 false
506 }
507
508 fn add_dedup(&mut self, hash: [u8; 32], now: f64) {
510 self.dedup_deque.push_back((hash, now));
511 while self.dedup_deque.len() > MULTI_IF_DEQUE_LEN {
512 self.dedup_deque.pop_front();
513 }
514 }
515
516 fn refresh_peer(&mut self, key: &PeerKey, now: f64) {
518 if let Some(peer) = self.peers.get_mut(key) {
519 peer.last_heard = now;
520 }
521 }
522}
523
524fn peer_key_from_src(src_addr: &SocketAddrV6, fallback_if_index: u32) -> PeerKey {
525 let if_index = match src_addr.scope_id() {
526 0 => fallback_if_index,
527 scope_id => scope_id,
528 };
529
530 PeerKey {
531 link_local_addr: src_addr.ip().to_string(),
532 if_index,
533 }
534}
535
536fn peer_target_addr(peer_key: &PeerKey, data_port: u16) -> io::Result<SocketAddrV6> {
537 let peer_ip = peer_key.link_local_addr.parse::<Ipv6Addr>().map_err(|e| {
538 io::Error::new(io::ErrorKind::InvalidInput, format!("bad peer IPv6: {}", e))
539 })?;
540
541 Ok(SocketAddrV6::new(peer_ip, data_port, 0, peer_key.if_index))
542}
543
544fn remove_peers_for_worker(
545 shared: &Arc<Mutex<SharedState>>,
546 tx: &EventSender,
547 key: &AutoWorkerKey,
548) {
549 let removed = {
550 let mut state = lock_or_recover(shared, "auto shared state");
551 let removed = state
552 .peers
553 .iter()
554 .filter_map(|(peer_key, peer)| {
555 if peer.if_index == key.if_index && peer.ifname == key.ifname {
556 Some((peer_key.clone(), peer.interface_id))
557 } else {
558 None
559 }
560 })
561 .collect::<Vec<_>>();
562
563 for (peer_key, _) in &removed {
564 state.peers.remove(peer_key);
565 }
566
567 removed
568 };
569
570 for (_, iface_id) in removed {
571 let _ = tx.send(Event::InterfaceDown(iface_id));
572 }
573}
574
575#[derive(Clone)]
578struct AutoWorkerContext {
579 group_id: Vec<u8>,
580 mcast_ip: Ipv6Addr,
581 discovery_port: u16,
582 unicast_discovery_port: u16,
583 data_port: u16,
584 name: String,
585 configured_bitrate: u64,
586 ingress_control: rns_core::transport::types::IngressControlConfig,
587 runtime: Arc<Mutex<AutoRuntime>>,
588 shared: Arc<Mutex<SharedState>>,
589 tx: EventSender,
590}
591
592pub fn start(
598 config: AutoConfig,
599 tx: EventSender,
600 next_dynamic_id: Arc<AtomicU64>,
601) -> io::Result<()> {
602 let group_id = config.group_id.clone();
603 let mcast_addr_str = derive_multicast_address(
604 &group_id,
605 &config.multicast_address_type,
606 &config.discovery_scope,
607 );
608
609 let mcast_ip = match parse_multicast_addr(&mcast_addr_str) {
610 Some(ip) => ip,
611 None => {
612 return Err(io::Error::new(
613 io::ErrorKind::InvalidData,
614 format!("Invalid multicast address: {}", mcast_addr_str),
615 ));
616 }
617 };
618
619 {
620 let startup = AutoRuntime::from_config(&config);
621 *lock_or_recover(&config.runtime, "auto runtime") = startup;
622 }
623 let runtime = Arc::clone(&config.runtime);
624 let shared = Arc::new(Mutex::new(SharedState::new(next_dynamic_id)));
625 let running = Arc::new(AtomicBool::new(true));
626
627 let worker_context = AutoWorkerContext {
628 group_id,
629 mcast_ip,
630 discovery_port: config.discovery_port,
631 unicast_discovery_port: config.discovery_port + 1,
632 data_port: config.data_port,
633 name: config.name.clone(),
634 configured_bitrate: config.configured_bitrate,
635 ingress_control: config.ingress_control,
636 runtime: runtime.clone(),
637 shared: shared.clone(),
638 tx: tx.clone(),
639 };
640
641 {
642 let allowed = config.allowed_interfaces.clone();
643 let ignored = config.ignored_interfaces.clone();
644 let running = running.clone();
645 let name = config.name.clone();
646 thread::Builder::new()
647 .name(format!("auto-supervisor-{}", name))
648 .spawn(move || {
649 auto_supervisor_loop(allowed, ignored, worker_context, running, &name);
650 })?;
651 }
652
653 {
654 let shared = shared.clone();
655 let tx = tx.clone();
656 let running = running.clone();
657 let name = config.name.clone();
658 let runtime = runtime.clone();
659
660 thread::Builder::new()
661 .name(format!("auto-peer-jobs-{}", name))
662 .spawn(move || {
663 peer_jobs_loop(shared, tx, runtime, &running, &name);
664 })?;
665 }
666
667 let announce_interval = lock_or_recover(&runtime, "auto runtime").announce_interval_secs;
668 let peering_wait = Duration::from_secs_f64(announce_interval * 1.2);
669 thread::sleep(peering_wait);
670
671 {
672 let mut state = lock_or_recover(&shared, "auto shared state");
673 state.online = true;
674 }
675
676 log::info!("[{}] AutoInterface online", config.name);
677
678 Ok(())
679}
680
681fn auto_supervisor_loop(
682 allowed_interfaces: Vec<String>,
683 ignored_interfaces: Vec<String>,
684 context: AutoWorkerContext,
685 running: Arc<AtomicBool>,
686 name: &str,
687) {
688 let mut workers: HashMap<AutoWorkerKey, AutoWorkerEntry> = HashMap::new();
689
690 while running.load(Ordering::Relaxed) {
691 let interfaces = enumerate_interfaces(&allowed_interfaces, &ignored_interfaces);
692 let desired = interfaces
693 .iter()
694 .map(AutoWorkerKey::from_local_interface)
695 .collect::<Vec<_>>();
696
697 {
698 let mut state = lock_or_recover(&context.shared, "auto shared state");
699 state.link_local_addresses = desired
700 .iter()
701 .map(|key| PeerKey {
702 link_local_addr: key.link_local_addr.clone(),
703 if_index: key.if_index,
704 })
705 .collect();
706 }
707
708 let removed_workers = reconcile_worker_keys(workers.keys().cloned(), desired.clone())
709 .into_iter()
710 .filter_map(|action| match action {
711 WorkerReconcileAction::Remove(key) => Some(key),
712 WorkerReconcileAction::Add(_) => None,
713 })
714 .collect::<Vec<_>>();
715 for key in &removed_workers {
716 remove_peers_for_worker(&context.shared, &context.tx, key);
717 }
718
719 if desired.is_empty() && workers.is_empty() {
720 log::warn!("[{}] No suitable IPv6 link-local interfaces found", name);
721 }
722
723 reconcile_auto_workers(&mut workers, desired, |key| {
724 start_auto_worker(key, &context)
725 });
726
727 thread::sleep(Duration::from_secs_f64(AUTO_RESCAN_INTERVAL));
728 }
729
730 for (key, worker) in workers {
731 remove_peers_for_worker(&context.shared, &context.tx, &key);
732 worker.stop();
733 }
734}
735
736fn start_auto_worker(
737 key: &AutoWorkerKey,
738 context: &AutoWorkerContext,
739) -> io::Result<RunningAutoWorker> {
740 let mcast_socket =
741 create_multicast_recv_socket(&context.mcast_ip, context.discovery_port, key.if_index)?;
742 let unicast_socket = create_unicast_recv_socket(
743 &key.link_local_addr,
744 context.unicast_discovery_port,
745 key.if_index,
746 )?;
747 let data_socket =
748 create_data_recv_socket(&key.link_local_addr, context.data_port, key.if_index)?;
749
750 let worker_running = Arc::new(AtomicBool::new(true));
751
752 {
753 let group_id = context.group_id.clone();
754 let link_local = key.link_local_addr.clone();
755 let running = worker_running.clone();
756 let name = context.name.clone();
757 let runtime = context.runtime.clone();
758 let mcast_ip = context.mcast_ip;
759 let discovery_port = context.discovery_port;
760 let if_index = key.if_index;
761 thread::Builder::new()
762 .name(format!("auto-disc-tx-{}", key.ifname))
763 .spawn(move || {
764 discovery_sender_loop(
765 &group_id,
766 &link_local,
767 &mcast_ip,
768 discovery_port,
769 if_index,
770 runtime,
771 &running,
772 &name,
773 );
774 })?;
775 }
776
777 {
778 let group_id = context.group_id.clone();
779 let shared = context.shared.clone();
780 let tx = context.tx.clone();
781 let running = worker_running.clone();
782 let name = context.name.clone();
783 let ifname = key.ifname.clone();
784 let if_index = key.if_index;
785 let runtime = context.runtime.clone();
786 let data_port = context.data_port;
787 let configured_bitrate = context.configured_bitrate;
788 let ingress_control = context.ingress_control;
789 thread::Builder::new()
790 .name(format!("auto-disc-rx-{}", key.ifname))
791 .spawn(move || {
792 discovery_receiver_loop(
793 mcast_socket,
794 &group_id,
795 shared,
796 tx,
797 &running,
798 &name,
799 &ifname,
800 if_index,
801 data_port,
802 configured_bitrate,
803 ingress_control,
804 runtime,
805 );
806 })?;
807 }
808
809 {
810 let group_id = context.group_id.clone();
811 let shared = context.shared.clone();
812 let tx = context.tx.clone();
813 let running = worker_running.clone();
814 let name = context.name.clone();
815 let ifname = key.ifname.clone();
816 let if_index = key.if_index;
817 let runtime = context.runtime.clone();
818 let data_port = context.data_port;
819 let configured_bitrate = context.configured_bitrate;
820 let ingress_control = context.ingress_control;
821 thread::Builder::new()
822 .name(format!("auto-udisc-rx-{}", key.ifname))
823 .spawn(move || {
824 discovery_receiver_loop(
825 unicast_socket,
826 &group_id,
827 shared,
828 tx,
829 &running,
830 &name,
831 &ifname,
832 if_index,
833 data_port,
834 configured_bitrate,
835 ingress_control,
836 runtime,
837 );
838 })?;
839 }
840
841 {
842 let shared = context.shared.clone();
843 let tx = context.tx.clone();
844 let running = worker_running.clone();
845 let name = context.name.clone();
846 let if_index = key.if_index;
847 thread::Builder::new()
848 .name(format!("auto-data-rx-{}", key.ifname))
849 .spawn(move || {
850 data_receiver_loop(data_socket, shared, tx, &running, &name, if_index);
851 })?;
852 }
853
854 log::info!(
855 "[{}] AutoInterface worker online on {} {}%{}",
856 context.name,
857 key.ifname,
858 key.link_local_addr,
859 key.if_index
860 );
861
862 Ok(RunningAutoWorker {
863 running: worker_running,
864 })
865}
866
867fn create_multicast_recv_socket(
870 mcast_ip: &Ipv6Addr,
871 port: u16,
872 if_index: u32,
873) -> io::Result<UdpSocket> {
874 let socket = socket2::Socket::new(
875 socket2::Domain::IPV6,
876 socket2::Type::DGRAM,
877 Some(socket2::Protocol::UDP),
878 )?;
879
880 socket.set_reuse_address(true)?;
881 #[cfg(not(target_os = "windows"))]
882 socket.set_reuse_port(true)?;
883
884 let bind_addr = SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, port, 0, if_index);
886 socket.bind(&bind_addr.into())?;
887
888 socket.join_multicast_v6(mcast_ip, if_index)?;
890
891 socket.set_nonblocking(false)?;
892 let std_socket: UdpSocket = socket.into();
893 std_socket.set_read_timeout(Some(Duration::from_secs(2)))?;
894 Ok(std_socket)
895}
896
897fn create_unicast_recv_socket(link_local: &str, port: u16, if_index: u32) -> io::Result<UdpSocket> {
898 let ip: Ipv6Addr = link_local
899 .parse()
900 .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, format!("bad IPv6: {}", e)))?;
901
902 let socket = socket2::Socket::new(
903 socket2::Domain::IPV6,
904 socket2::Type::DGRAM,
905 Some(socket2::Protocol::UDP),
906 )?;
907
908 socket.set_reuse_address(true)?;
909 #[cfg(not(target_os = "windows"))]
910 socket.set_reuse_port(true)?;
911
912 let bind_addr = SocketAddrV6::new(ip, port, 0, if_index);
913 socket.bind(&bind_addr.into())?;
914
915 socket.set_nonblocking(false)?;
916 let std_socket: UdpSocket = socket.into();
917 std_socket.set_read_timeout(Some(Duration::from_secs(2)))?;
918 Ok(std_socket)
919}
920
921fn create_data_recv_socket(link_local: &str, port: u16, if_index: u32) -> io::Result<UdpSocket> {
922 let ip: Ipv6Addr = link_local
923 .parse()
924 .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, format!("bad IPv6: {}", e)))?;
925
926 let socket = socket2::Socket::new(
927 socket2::Domain::IPV6,
928 socket2::Type::DGRAM,
929 Some(socket2::Protocol::UDP),
930 )?;
931
932 socket.set_reuse_address(true)?;
933 #[cfg(not(target_os = "windows"))]
934 socket.set_reuse_port(true)?;
935
936 let bind_addr = SocketAddrV6::new(ip, port, 0, if_index);
937 socket.bind(&bind_addr.into())?;
938
939 socket.set_nonblocking(false)?;
940 let std_socket: UdpSocket = socket.into();
941 std_socket.set_read_timeout(Some(Duration::from_secs(2)))?;
942 Ok(std_socket)
943}
944
945fn discovery_sender_loop(
949 group_id: &[u8],
950 link_local_addr: &str,
951 mcast_ip: &Ipv6Addr,
952 discovery_port: u16,
953 if_index: u32,
954 runtime: Arc<Mutex<AutoRuntime>>,
955 running: &AtomicBool,
956 name: &str,
957) {
958 let token = compute_discovery_token(group_id, link_local_addr);
959
960 while running.load(Ordering::Relaxed) {
961 if let Ok(socket) = UdpSocket::bind("[::]:0") {
963 let if_bytes = if_index.to_ne_bytes();
965 unsafe {
966 libc::setsockopt(
967 socket_fd(&socket),
968 libc::IPPROTO_IPV6,
969 libc::IPV6_MULTICAST_IF,
970 if_bytes.as_ptr() as *const libc::c_void,
971 4,
972 );
973 }
974
975 let target = SocketAddrV6::new(*mcast_ip, discovery_port, 0, 0);
976 if let Err(e) = socket.send_to(&token, target) {
977 log::debug!("[{}] multicast send error: {}", name, e);
978 }
979 }
980
981 let sleep_dur = Duration::from_secs_f64(
982 lock_or_recover(&runtime, "auto runtime")
983 .announce_interval_secs
984 .max(0.1),
985 );
986 thread::sleep(sleep_dur);
987 }
988}
989
990fn discovery_receiver_loop(
992 socket: UdpSocket,
993 group_id: &[u8],
994 shared: Arc<Mutex<SharedState>>,
995 tx: EventSender,
996 running: &AtomicBool,
997 name: &str,
998 ifname: &str,
999 if_index: u32,
1000 data_port: u16,
1001 configured_bitrate: u64,
1002 ingress_control: rns_core::transport::types::IngressControlConfig,
1003 runtime: Arc<Mutex<AutoRuntime>>,
1004) {
1005 let mut buf = [0u8; 1024];
1006
1007 while running.load(Ordering::Relaxed) {
1008 match socket.recv_from(&mut buf) {
1009 Ok((n, src)) => {
1010 if n < 32 {
1011 continue;
1012 }
1013
1014 let src_addr = match src {
1016 std::net::SocketAddr::V6(v6) => v6,
1017 _ => continue,
1018 };
1019 let peer_key = peer_key_from_src(&src_addr, if_index);
1020 let src_ip = peer_key.link_local_addr.clone();
1021
1022 let peering_hash = &buf[..32];
1023 let expected = compute_discovery_token(group_id, &src_ip);
1024
1025 if peering_hash != expected {
1026 log::debug!("[{}] invalid peering hash from {}", name, src_ip);
1027 continue;
1028 }
1029
1030 let state = lock_or_recover(&shared, "auto shared state");
1032 if !state.online {
1033 }
1036
1037 if state.link_local_addresses.contains(&peer_key) {
1039 drop(state);
1041 continue;
1042 }
1043
1044 if state.peers.contains_key(&peer_key) {
1046 let now = crate::time::now();
1047 drop(state);
1048 let mut state = lock_or_recover(&shared, "auto shared state");
1049 state.refresh_peer(&peer_key, now);
1050 continue;
1051 }
1052 drop(state);
1053
1054 add_peer(
1056 &shared,
1057 &tx,
1058 &peer_key,
1059 data_port,
1060 name,
1061 ifname,
1062 configured_bitrate,
1063 ingress_control,
1064 &runtime,
1065 );
1066 }
1067 Err(ref e)
1068 if e.kind() == io::ErrorKind::WouldBlock || e.kind() == io::ErrorKind::TimedOut =>
1069 {
1070 continue;
1072 }
1073 Err(e) => {
1074 log::warn!("[{}] discovery recv error: {}", name, e);
1075 if !running.load(Ordering::Relaxed) {
1076 return;
1077 }
1078 thread::sleep(Duration::from_millis(100));
1079 }
1080 }
1081 }
1082}
1083
1084fn add_peer(
1086 shared: &Arc<Mutex<SharedState>>,
1087 tx: &EventSender,
1088 peer_key: &PeerKey,
1089 data_port: u16,
1090 name: &str,
1091 ifname: &str,
1092 configured_bitrate: u64,
1093 ingress_control: rns_core::transport::types::IngressControlConfig,
1094 _runtime: &Arc<Mutex<AutoRuntime>>,
1095) {
1096 let send_socket = match UdpSocket::bind("[::]:0") {
1098 Ok(s) => s,
1099 Err(e) => {
1100 log::warn!(
1101 "[{}] failed to create writer for peer {}: {}",
1102 name,
1103 peer_key.link_local_addr,
1104 e
1105 );
1106 return;
1107 }
1108 };
1109
1110 let target = match peer_target_addr(peer_key, data_port) {
1111 Ok(target) => target,
1112 Err(e) => {
1113 log::warn!(
1114 "[{}] failed to create target for peer {}%{}: {}",
1115 name,
1116 peer_key.link_local_addr,
1117 peer_key.if_index,
1118 e
1119 );
1120 return;
1121 }
1122 };
1123
1124 let mut state = lock_or_recover(shared, "auto shared state");
1125
1126 if state.peers.contains_key(peer_key) {
1128 state.refresh_peer(peer_key, crate::time::now());
1129 return;
1130 }
1131
1132 let peer_id = InterfaceId(state.next_id.fetch_add(1, Ordering::Relaxed));
1133
1134 let driver_writer: Box<dyn Writer> = Box::new(UdpWriter {
1136 socket: send_socket,
1137 target,
1138 });
1139
1140 let peer_info = rns_core::transport::types::InterfaceInfo {
1141 id: peer_id,
1142 name: format!(
1143 "{}:{}%{}",
1144 name, peer_key.link_local_addr, peer_key.if_index
1145 ),
1146 mode: rns_core::constants::MODE_FULL,
1147 recursive_prs: false,
1148 announces_from_internal: true,
1149 out_capable: true,
1150 in_capable: true,
1151 bitrate: Some(configured_bitrate),
1152 airtime_profile: None,
1153 announce_rate_target: None,
1154 announce_rate_grace: 0,
1155 announce_rate_penalty: 0.0,
1156 announce_cap: rns_core::constants::ANNOUNCE_CAP,
1157 is_local_client: false,
1158 wants_tunnel: false,
1159 tunnel_id: None,
1160 mtu: 1400,
1161 ia_freq: 0.0,
1162 ip_freq: 0.0,
1163 op_freq: 0.0,
1164 op_samples: 0,
1165 started: 0.0,
1166 ingress_control,
1167 };
1168
1169 let now = crate::time::now();
1170 state.peers.insert(
1171 peer_key.clone(),
1172 AutoPeer {
1173 interface_id: peer_id,
1174 link_local_addr: peer_key.link_local_addr.clone(),
1175 ifname: ifname.to_string(),
1176 if_index: peer_key.if_index,
1177 last_heard: now,
1178 },
1179 );
1180
1181 log::info!(
1182 "[{}] Peer discovered: {} (id={})",
1183 name,
1184 peer_key.link_local_addr,
1185 peer_id.0
1186 );
1187
1188 let _ = tx.send(Event::InterfaceUp(
1190 peer_id,
1191 Some(driver_writer),
1192 Some(peer_info),
1193 ));
1194}
1195
1196fn data_receiver_loop(
1198 socket: UdpSocket,
1199 shared: Arc<Mutex<SharedState>>,
1200 tx: EventSender,
1201 running: &AtomicBool,
1202 name: &str,
1203 if_index: u32,
1204) {
1205 let mut buf = [0u8; HW_MTU + 64]; while running.load(Ordering::Relaxed) {
1208 match socket.recv_from(&mut buf) {
1209 Ok((n, src)) => {
1210 if n == 0 {
1211 continue;
1212 }
1213
1214 let src_addr = match src {
1215 std::net::SocketAddr::V6(v6) => v6,
1216 _ => continue,
1217 };
1218 let peer_key = peer_key_from_src(&src_addr, if_index);
1219 let data = &buf[..n];
1220
1221 let now = crate::time::now();
1222 let data_hash = rns_crypto::sha256::sha256(data);
1223
1224 let mut state = lock_or_recover(&shared, "auto shared state");
1225
1226 if !state.online {
1227 continue;
1228 }
1229
1230 if state.is_duplicate(&data_hash, now) {
1232 continue;
1233 }
1234 state.add_dedup(data_hash, now);
1235
1236 state.refresh_peer(&peer_key, now);
1238
1239 let iface_id = match state.peers.get(&peer_key) {
1241 Some(peer) => peer.interface_id,
1242 None => {
1243 continue;
1245 }
1246 };
1247
1248 drop(state);
1249
1250 if tx
1251 .send(Event::Frame {
1252 interface_id: iface_id,
1253 data: data.to_vec(),
1254 rssi: None,
1255 snr: None,
1256 })
1257 .is_err()
1258 {
1259 return;
1260 }
1261 }
1262 Err(ref e)
1263 if e.kind() == io::ErrorKind::WouldBlock || e.kind() == io::ErrorKind::TimedOut =>
1264 {
1265 continue;
1266 }
1267 Err(e) => {
1268 log::warn!("[{}] data recv error: {}", name, e);
1269 if !running.load(Ordering::Relaxed) {
1270 return;
1271 }
1272 thread::sleep(Duration::from_millis(100));
1273 }
1274 }
1275 }
1276}
1277
1278fn peer_jobs_loop(
1280 shared: Arc<Mutex<SharedState>>,
1281 tx: EventSender,
1282 runtime: Arc<Mutex<AutoRuntime>>,
1283 running: &AtomicBool,
1284 name: &str,
1285) {
1286 while running.load(Ordering::Relaxed) {
1287 let interval = Duration::from_secs_f64(
1288 lock_or_recover(&runtime, "auto runtime")
1289 .peer_job_interval_secs
1290 .max(0.1),
1291 );
1292 thread::sleep(interval);
1293
1294 let now = crate::time::now();
1295 let mut timed_out = Vec::new();
1296 let peer_timeout_secs = lock_or_recover(&runtime, "auto runtime").peer_timeout_secs;
1297
1298 {
1299 let state = lock_or_recover(&shared, "auto shared state");
1300 for (peer_key, peer) in &state.peers {
1301 if now > peer.last_heard + peer_timeout_secs {
1302 timed_out.push((peer_key.clone(), peer.interface_id));
1303 }
1304 }
1305 }
1306
1307 for (peer_key, iface_id) in &timed_out {
1308 log::info!(
1309 "[{}] Peer timed out: {}%{}",
1310 name,
1311 peer_key.link_local_addr,
1312 peer_key.if_index
1313 );
1314 let mut state = lock_or_recover(&shared, "auto shared state");
1315 state.peers.remove(peer_key);
1316 let _ = tx.send(Event::InterfaceDown(*iface_id));
1317 }
1318 }
1319}
1320
1321#[cfg(unix)]
1325fn socket_fd(socket: &UdpSocket) -> i32 {
1326 use std::os::unix::io::AsRawFd;
1327 socket.as_raw_fd()
1328}
1329
1330#[cfg(not(unix))]
1331fn socket_fd(_socket: &UdpSocket) -> i32 {
1332 0
1333}
1334
1335use super::{InterfaceConfigData, InterfaceFactory, StartContext, StartResult};
1338
1339pub struct AutoFactory;
1341
1342impl InterfaceFactory for AutoFactory {
1343 fn type_name(&self) -> &str {
1344 "AutoInterface"
1345 }
1346
1347 fn parse_config(
1348 &self,
1349 name: &str,
1350 id: InterfaceId,
1351 params: &HashMap<String, String>,
1352 ) -> Result<Box<dyn InterfaceConfigData>, String> {
1353 let group_id = params
1354 .get("group_id")
1355 .map(|v| v.as_bytes().to_vec())
1356 .unwrap_or_else(|| DEFAULT_GROUP_ID.to_vec());
1357
1358 let discovery_scope = params
1359 .get("discovery_scope")
1360 .map(|v| match v.to_lowercase().as_str() {
1361 "link" => SCOPE_LINK.to_string(),
1362 "admin" => SCOPE_ADMIN.to_string(),
1363 "site" => SCOPE_SITE.to_string(),
1364 "organisation" | "organization" => SCOPE_ORGANISATION.to_string(),
1365 "global" => SCOPE_GLOBAL.to_string(),
1366 _ => v.clone(),
1367 })
1368 .unwrap_or_else(|| SCOPE_LINK.to_string());
1369
1370 let discovery_port = params
1371 .get("discovery_port")
1372 .and_then(|v| v.parse().ok())
1373 .unwrap_or(DEFAULT_DISCOVERY_PORT);
1374
1375 let data_port = params
1376 .get("data_port")
1377 .and_then(|v| v.parse().ok())
1378 .unwrap_or(DEFAULT_DATA_PORT);
1379
1380 let multicast_address_type = params
1381 .get("multicast_address_type")
1382 .map(|v| match v.to_lowercase().as_str() {
1383 "permanent" => MULTICAST_PERMANENT_ADDRESS_TYPE.to_string(),
1384 "temporary" => MULTICAST_TEMPORARY_ADDRESS_TYPE.to_string(),
1385 _ => v.clone(),
1386 })
1387 .unwrap_or_else(|| MULTICAST_TEMPORARY_ADDRESS_TYPE.to_string());
1388
1389 let configured_bitrate = params
1390 .get("configured_bitrate")
1391 .or_else(|| params.get("bitrate"))
1392 .and_then(|v| v.parse().ok())
1393 .unwrap_or(BITRATE_GUESS);
1394
1395 let allowed_interfaces = params
1396 .get("devices")
1397 .or_else(|| params.get("allowed_interfaces"))
1398 .map(|v| {
1399 v.split(',')
1400 .map(|s| s.trim().to_string())
1401 .filter(|s| !s.is_empty())
1402 .collect()
1403 })
1404 .unwrap_or_default();
1405
1406 let ignored_interfaces = params
1407 .get("ignored_devices")
1408 .or_else(|| params.get("ignored_interfaces"))
1409 .map(|v| {
1410 v.split(',')
1411 .map(|s| s.trim().to_string())
1412 .filter(|s| !s.is_empty())
1413 .collect()
1414 })
1415 .unwrap_or_default();
1416
1417 Ok(Box::new(AutoConfig {
1418 name: name.to_string(),
1419 group_id,
1420 discovery_scope,
1421 discovery_port,
1422 data_port,
1423 multicast_address_type,
1424 allowed_interfaces,
1425 ignored_interfaces,
1426 configured_bitrate,
1427 interface_id: id,
1428 ingress_control: rns_core::transport::types::IngressControlConfig::enabled(),
1429 runtime: Arc::new(Mutex::new(AutoRuntime {
1430 announce_interval_secs: ANNOUNCE_INTERVAL,
1431 peer_timeout_secs: PEERING_TIMEOUT,
1432 peer_job_interval_secs: PEER_JOB_INTERVAL,
1433 })),
1434 }))
1435 }
1436
1437 fn start(
1438 &self,
1439 config: Box<dyn InterfaceConfigData>,
1440 ctx: StartContext,
1441 ) -> std::io::Result<StartResult> {
1442 let mut auto_config = *config.into_any().downcast::<AutoConfig>().map_err(|_| {
1443 std::io::Error::new(std::io::ErrorKind::InvalidData, "wrong config type")
1444 })?;
1445
1446 auto_config.ingress_control = ctx.ingress_control;
1447 start(auto_config, ctx.tx, ctx.next_dynamic_id)?;
1448 Ok(StartResult::Listener { control: None })
1449 }
1450}
1451
1452pub(crate) fn auto_runtime_handle_from_config(config: &AutoConfig) -> AutoRuntimeConfigHandle {
1453 AutoRuntimeConfigHandle {
1454 interface_name: config.name.clone(),
1455 runtime: Arc::clone(&config.runtime),
1456 startup: AutoRuntime::from_config(config),
1457 }
1458}
1459
1460#[cfg(test)]
1463mod tests {
1464 use super::*;
1465
1466 #[test]
1469 fn multicast_address_default_group() {
1470 let addr = derive_multicast_address(
1472 DEFAULT_GROUP_ID,
1473 MULTICAST_TEMPORARY_ADDRESS_TYPE,
1474 SCOPE_LINK,
1475 );
1476 assert_eq!(addr, "ff12:0:d70b:fb1c:16e4:5e39:485e:31e1");
1477 }
1478
1479 #[test]
1480 fn multicast_address_custom_group() {
1481 let addr =
1482 derive_multicast_address(b"testgroup", MULTICAST_TEMPORARY_ADDRESS_TYPE, SCOPE_LINK);
1483 assert!(addr.starts_with("ff12:0:"));
1485 assert_ne!(addr, "ff12:0:d70b:fb1c:16e4:5e39:485e:31e1");
1487 }
1488
1489 #[test]
1490 fn multicast_address_scope_admin() {
1491 let addr = derive_multicast_address(
1492 DEFAULT_GROUP_ID,
1493 MULTICAST_TEMPORARY_ADDRESS_TYPE,
1494 SCOPE_ADMIN,
1495 );
1496 assert!(addr.starts_with("ff14:0:"));
1497 }
1498
1499 #[test]
1500 fn multicast_address_permanent_type() {
1501 let addr = derive_multicast_address(
1502 DEFAULT_GROUP_ID,
1503 MULTICAST_PERMANENT_ADDRESS_TYPE,
1504 SCOPE_LINK,
1505 );
1506 assert!(addr.starts_with("ff02:0:"));
1507 }
1508
1509 #[test]
1510 fn multicast_address_parseable() {
1511 let addr = derive_multicast_address(
1512 DEFAULT_GROUP_ID,
1513 MULTICAST_TEMPORARY_ADDRESS_TYPE,
1514 SCOPE_LINK,
1515 );
1516 let ip = parse_multicast_addr(&addr);
1517 assert!(ip.is_some());
1518 assert!(ip.unwrap().is_multicast());
1519 }
1520
1521 #[test]
1524 fn discovery_token_interop() {
1525 let token = compute_discovery_token(DEFAULT_GROUP_ID, "fe80::1");
1527 let expected = "97b25576749ea936b0d8a8536ffaf442d157cf47d460dcf13c48b7bd18b6c163";
1528 let got = token
1529 .iter()
1530 .map(|b| format!("{:02x}", b))
1531 .collect::<String>();
1532 assert_eq!(got, expected);
1533 }
1534
1535 #[test]
1536 fn discovery_token_interop_2() {
1537 let token = compute_discovery_token(DEFAULT_GROUP_ID, "fe80::dead:beef:1234:5678");
1539 let expected = "46b6ec7595504b6a35f06bd4bfff71567fb82fcf2706cd361bab20409c42d072";
1540 let got = token
1541 .iter()
1542 .map(|b| format!("{:02x}", b))
1543 .collect::<String>();
1544 assert_eq!(got, expected);
1545 }
1546
1547 #[test]
1548 fn discovery_token_different_groups() {
1549 let t1 = compute_discovery_token(b"reticulum", "fe80::1");
1550 let t2 = compute_discovery_token(b"othergroup", "fe80::1");
1551 assert_ne!(t1, t2);
1552 }
1553
1554 #[test]
1555 fn discovery_token_different_addrs() {
1556 let t1 = compute_discovery_token(DEFAULT_GROUP_ID, "fe80::1");
1557 let t2 = compute_discovery_token(DEFAULT_GROUP_ID, "fe80::2");
1558 assert_ne!(t1, t2);
1559 }
1560
1561 #[test]
1564 fn dedup_basic() {
1565 let next_id = Arc::new(AtomicU64::new(1));
1566 let mut state = SharedState::new(next_id);
1567
1568 let hash = [0xAA; 32];
1569 let now = 1000.0;
1570
1571 assert!(!state.is_duplicate(&hash, now));
1572 state.add_dedup(hash, now);
1573 assert!(state.is_duplicate(&hash, now));
1574 }
1575
1576 #[test]
1577 fn dedup_expired() {
1578 let next_id = Arc::new(AtomicU64::new(1));
1579 let mut state = SharedState::new(next_id);
1580
1581 let hash = [0xBB; 32];
1582 state.add_dedup(hash, 1000.0);
1583
1584 assert!(state.is_duplicate(&hash, 1000.5));
1586 assert!(!state.is_duplicate(&hash, 1001.0));
1588 }
1589
1590 #[test]
1591 fn dedup_max_length() {
1592 let next_id = Arc::new(AtomicU64::new(1));
1593 let mut state = SharedState::new(next_id);
1594
1595 for i in 0..MULTI_IF_DEQUE_LEN + 10 {
1597 let mut hash = [0u8; 32];
1598 hash[0] = (i & 0xFF) as u8;
1599 hash[1] = ((i >> 8) & 0xFF) as u8;
1600 state.add_dedup(hash, 1000.0);
1601 }
1602
1603 assert_eq!(state.dedup_deque.len(), MULTI_IF_DEQUE_LEN);
1604 }
1605
1606 #[test]
1609 fn peer_refresh() {
1610 let next_id = Arc::new(AtomicU64::new(100));
1611 let mut state = SharedState::new(next_id);
1612 let key = peer_key("fe80::1", 2);
1613
1614 state.peers.insert(
1615 key.clone(),
1616 AutoPeer {
1617 interface_id: InterfaceId(100),
1618 link_local_addr: key.link_local_addr.clone(),
1619 ifname: "eth0".to_string(),
1620 if_index: key.if_index,
1621 last_heard: 1000.0,
1622 },
1623 );
1624
1625 state.refresh_peer(&key, 2000.0);
1626 assert_eq!(state.peers[&key].last_heard, 2000.0);
1627 }
1628
1629 #[test]
1630 fn peer_not_found_refresh() {
1631 let next_id = Arc::new(AtomicU64::new(100));
1632 let mut state = SharedState::new(next_id);
1633 state.refresh_peer(&peer_key("fe80::999", 4), 1000.0);
1635 }
1636
1637 fn peer_key(addr: &str, if_index: u32) -> PeerKey {
1638 PeerKey {
1639 link_local_addr: addr.to_string(),
1640 if_index,
1641 }
1642 }
1643
1644 #[test]
1645 fn peer_key_distinguishes_same_link_local_on_different_interfaces() {
1646 let next_id = Arc::new(AtomicU64::new(100));
1647 let mut state = SharedState::new(next_id);
1648 let eth = peer_key("fe80::1", 2);
1649 let wlan = peer_key("fe80::1", 4);
1650
1651 state.peers.insert(
1652 eth.clone(),
1653 AutoPeer {
1654 interface_id: InterfaceId(100),
1655 link_local_addr: eth.link_local_addr.clone(),
1656 ifname: "eth0".to_string(),
1657 if_index: eth.if_index,
1658 last_heard: 1000.0,
1659 },
1660 );
1661 state.peers.insert(
1662 wlan.clone(),
1663 AutoPeer {
1664 interface_id: InterfaceId(101),
1665 link_local_addr: wlan.link_local_addr.clone(),
1666 ifname: "wlan0".to_string(),
1667 if_index: wlan.if_index,
1668 last_heard: 1000.0,
1669 },
1670 );
1671
1672 assert_eq!(state.peers.len(), 2);
1673 assert_eq!(state.peers[ð].interface_id, InterfaceId(100));
1674 assert_eq!(state.peers[&wlan].interface_id, InterfaceId(101));
1675 }
1676
1677 #[test]
1678 fn peer_target_addr_uses_scope_id() {
1679 let target = peer_target_addr(&peer_key("fe80::1", 7), 42671).unwrap();
1680
1681 assert_eq!(target.port(), 42671);
1682 assert_eq!(target.scope_id(), 7);
1683 }
1684
1685 #[test]
1686 fn remove_peers_for_worker_emits_interface_down() {
1687 let next_id = Arc::new(AtomicU64::new(100));
1688 let shared = Arc::new(Mutex::new(SharedState::new(next_id)));
1689 let worker = worker_key("wlan0", 4, "fe80::abcd");
1690 let removed = peer_key("fe80::1", 4);
1691 let kept = peer_key("fe80::1", 5);
1692 let (tx, rx) = crate::event::channel_with_capacity(4);
1693
1694 {
1695 let mut state = lock_or_recover(&shared, "test auto shared state");
1696 state.peers.insert(
1697 removed.clone(),
1698 AutoPeer {
1699 interface_id: InterfaceId(100),
1700 link_local_addr: removed.link_local_addr.clone(),
1701 ifname: "wlan0".to_string(),
1702 if_index: removed.if_index,
1703 last_heard: 1000.0,
1704 },
1705 );
1706 state.peers.insert(
1707 kept.clone(),
1708 AutoPeer {
1709 interface_id: InterfaceId(101),
1710 link_local_addr: kept.link_local_addr.clone(),
1711 ifname: "wlan1".to_string(),
1712 if_index: kept.if_index,
1713 last_heard: 1000.0,
1714 },
1715 );
1716 }
1717
1718 remove_peers_for_worker(&shared, &tx, &worker);
1719
1720 let state = lock_or_recover(&shared, "test auto shared state");
1721 assert!(!state.peers.contains_key(&removed));
1722 assert!(state.peers.contains_key(&kept));
1723 drop(state);
1724
1725 assert!(matches!(
1726 rx.recv_timeout(std::time::Duration::from_secs(1)).unwrap(),
1727 Event::InterfaceDown(InterfaceId(100))
1728 ));
1729 assert!(rx.try_recv().is_err());
1730 }
1731
1732 #[test]
1735 fn enumerate_returns_vec() {
1736 let interfaces = enumerate_interfaces(&[], &[]);
1739 for iface in &interfaces {
1741 assert!(!iface.name.is_empty());
1742 assert!(iface.link_local_addr.starts_with("fe80"));
1743 assert!(iface.index > 0);
1744 }
1745 }
1746
1747 #[test]
1748 fn enumerate_with_ignored() {
1749 let interfaces = enumerate_interfaces(
1751 &[],
1752 &[
1753 "lo".to_string(),
1754 "eth0".to_string(),
1755 "wlan0".to_string(),
1756 "enp0s3".to_string(),
1757 "docker0".to_string(),
1758 ],
1759 );
1760 for iface in &interfaces {
1762 assert_ne!(iface.name, "lo");
1763 assert_ne!(iface.name, "eth0");
1764 assert_ne!(iface.name, "wlan0");
1765 }
1766 }
1767
1768 #[test]
1769 fn enumerate_with_allowed_nonexistent() {
1770 let interfaces = enumerate_interfaces(&["nonexistent_if_12345".to_string()], &[]);
1772 assert!(interfaces.is_empty());
1773 }
1774
1775 fn worker_key(name: &str, index: u32, addr: &str) -> AutoWorkerKey {
1776 AutoWorkerKey {
1777 ifname: name.to_string(),
1778 if_index: index,
1779 link_local_addr: addr.to_string(),
1780 }
1781 }
1782
1783 #[test]
1784 fn reconcile_worker_keys_adds_new_interface() {
1785 let desired = vec![worker_key("wlan0", 4, "fe80::1")];
1786
1787 assert_eq!(
1788 reconcile_worker_keys(Vec::new(), desired.clone()),
1789 vec![WorkerReconcileAction::Add(desired[0].clone())]
1790 );
1791 }
1792
1793 #[test]
1794 fn reconcile_worker_keys_keeps_unchanged_interface() {
1795 let key = worker_key("wlan0", 4, "fe80::1");
1796
1797 assert!(reconcile_worker_keys(vec![key.clone()], vec![key]).is_empty());
1798 }
1799
1800 #[test]
1801 fn reconcile_worker_keys_removes_missing_interface() {
1802 let active = vec![worker_key("wlan0", 4, "fe80::1")];
1803
1804 assert_eq!(
1805 reconcile_worker_keys(active.clone(), Vec::new()),
1806 vec![WorkerReconcileAction::Remove(active[0].clone())]
1807 );
1808 }
1809
1810 #[test]
1811 fn reconcile_worker_keys_replaces_changed_interface_identity() {
1812 let old = worker_key("wlan0", 4, "fe80::1");
1813 let new = worker_key("wlan0", 9, "fe80::2");
1814 let actions = reconcile_worker_keys(vec![old.clone()], vec![new.clone()]);
1815
1816 assert!(actions.contains(&WorkerReconcileAction::Remove(old)));
1817 assert!(actions.contains(&WorkerReconcileAction::Add(new)));
1818 assert_eq!(actions.len(), 2);
1819 }
1820
1821 #[test]
1822 fn reconcile_auto_workers_starts_new_worker() {
1823 let key = worker_key("wlan0", 4, "fe80::1");
1824 let mut workers = HashMap::new();
1825 let mut starts = Vec::new();
1826
1827 reconcile_auto_workers(&mut workers, vec![key.clone()], |key| {
1828 starts.push(key.clone());
1829 Ok(RunningAutoWorker {
1830 running: Arc::new(AtomicBool::new(true)),
1831 })
1832 });
1833
1834 assert_eq!(starts, vec![key.clone()]);
1835 assert!(matches!(
1836 workers.get(&key),
1837 Some(AutoWorkerEntry::Running(_))
1838 ));
1839 }
1840
1841 #[test]
1842 fn reconcile_auto_workers_stops_removed_worker() {
1843 let key = worker_key("wlan0", 4, "fe80::1");
1844 let running = Arc::new(AtomicBool::new(true));
1845 let mut workers = HashMap::from([(
1846 key.clone(),
1847 AutoWorkerEntry::Running(RunningAutoWorker {
1848 running: running.clone(),
1849 }),
1850 )]);
1851
1852 reconcile_auto_workers(&mut workers, Vec::new(), |_| unreachable!());
1853
1854 assert!(!running.load(Ordering::Relaxed));
1855 assert!(!workers.contains_key(&key));
1856 }
1857
1858 #[test]
1859 fn reconcile_auto_workers_leaves_unchanged_worker_running() {
1860 let key = worker_key("wlan0", 4, "fe80::1");
1861 let running = Arc::new(AtomicBool::new(true));
1862 let mut workers = HashMap::from([(
1863 key.clone(),
1864 AutoWorkerEntry::Running(RunningAutoWorker {
1865 running: running.clone(),
1866 }),
1867 )]);
1868 let mut starts = 0;
1869
1870 reconcile_auto_workers(&mut workers, vec![key.clone()], |_| {
1871 starts += 1;
1872 unreachable!()
1873 });
1874
1875 assert_eq!(starts, 0);
1876 assert!(running.load(Ordering::Relaxed));
1877 assert!(workers.contains_key(&key));
1878 }
1879
1880 #[test]
1881 fn reconcile_auto_workers_records_pending_start_failure() {
1882 let key = worker_key("wlan0", 4, "fe80::1");
1883 let mut workers = HashMap::new();
1884
1885 reconcile_auto_workers(&mut workers, vec![key.clone()], |_| {
1886 Err(io::Error::new(io::ErrorKind::AddrInUse, "busy"))
1887 });
1888
1889 assert!(matches!(workers.get(&key), Some(AutoWorkerEntry::Pending)));
1890 }
1891
1892 #[test]
1893 fn reconcile_auto_workers_retries_pending_worker() {
1894 let key = worker_key("wlan0", 4, "fe80::1");
1895 let running = Arc::new(AtomicBool::new(true));
1896 let mut workers = HashMap::from([(key.clone(), AutoWorkerEntry::Pending)]);
1897 let mut starts = 0;
1898
1899 reconcile_auto_workers(&mut workers, vec![key.clone()], |_| {
1900 starts += 1;
1901 Ok(RunningAutoWorker {
1902 running: running.clone(),
1903 })
1904 });
1905
1906 assert_eq!(starts, 1);
1907 assert!(matches!(
1908 workers.get(&key),
1909 Some(AutoWorkerEntry::Running(_))
1910 ));
1911 }
1912
1913 #[test]
1914 fn reconcile_auto_workers_keeps_pending_worker_on_retry_failure() {
1915 let key = worker_key("wlan0", 4, "fe80::1");
1916 let mut workers = HashMap::from([(key.clone(), AutoWorkerEntry::Pending)]);
1917 let mut starts = 0;
1918
1919 reconcile_auto_workers(&mut workers, vec![key.clone()], |_| {
1920 starts += 1;
1921 Err(io::Error::new(io::ErrorKind::AddrInUse, "busy"))
1922 });
1923
1924 assert_eq!(starts, 1);
1925 assert!(matches!(workers.get(&key), Some(AutoWorkerEntry::Pending)));
1926 }
1927
1928 #[test]
1929 fn reconcile_auto_workers_removes_pending_worker() {
1930 let key = worker_key("wlan0", 4, "fe80::1");
1931 let mut workers = HashMap::from([(key.clone(), AutoWorkerEntry::Pending)]);
1932
1933 reconcile_auto_workers(&mut workers, Vec::new(), |_| unreachable!());
1934
1935 assert!(!workers.contains_key(&key));
1936 }
1937
1938 #[test]
1939 fn filter_skips_android_system_interfaces() {
1940 let allowed = Vec::new();
1941 let ignored = Vec::new();
1942
1943 for name in ["dummy0", "lo", "tun0", "rmnet0", "rmnet7"] {
1944 assert!(
1945 !should_adopt_interface_name(name, &allowed, &ignored, ANDROID_IGNORE_IFS),
1946 "{name} should be skipped by Android AutoInterface defaults"
1947 );
1948 }
1949 }
1950
1951 #[test]
1952 fn filter_does_not_skip_rmnet8_by_android_defaults() {
1953 assert!(should_adopt_interface_name(
1954 "rmnet8",
1955 &[],
1956 &[],
1957 ANDROID_IGNORE_IFS
1958 ));
1959 }
1960
1961 #[test]
1962 fn filter_allowed_overrides_system_ignored_interface() {
1963 assert!(should_adopt_interface_name(
1964 "rmnet0",
1965 &["rmnet0".to_string()],
1966 &[],
1967 ANDROID_IGNORE_IFS
1968 ));
1969
1970 assert!(should_adopt_interface_name(
1971 "lo0",
1972 &["lo0".to_string()],
1973 &[],
1974 &[]
1975 ));
1976 }
1977
1978 #[test]
1979 fn filter_ignored_wins_over_allowed_interface() {
1980 assert!(!should_adopt_interface_name(
1981 "rmnet0",
1982 &["rmnet0".to_string()],
1983 &["rmnet0".to_string()],
1984 ANDROID_IGNORE_IFS
1985 ));
1986 }
1987
1988 #[test]
1989 fn filter_allowed_list_excludes_unlisted_interfaces() {
1990 assert!(!should_adopt_interface_name(
1991 "wlan0",
1992 &["eth0".to_string()],
1993 &[],
1994 ANDROID_IGNORE_IFS
1995 ));
1996
1997 assert!(should_adopt_interface_name(
1998 "wlan0",
1999 &["wlan0".to_string()],
2000 &[],
2001 ANDROID_IGNORE_IFS
2002 ));
2003 }
2004
2005 #[test]
2008 fn config_defaults() {
2009 let config = AutoConfig::default();
2010 assert_eq!(config.group_id, DEFAULT_GROUP_ID);
2011 assert_eq!(config.discovery_scope, SCOPE_LINK);
2012 assert_eq!(config.discovery_port, DEFAULT_DISCOVERY_PORT);
2013 assert_eq!(config.data_port, DEFAULT_DATA_PORT);
2014 assert_eq!(
2015 config.multicast_address_type,
2016 MULTICAST_TEMPORARY_ADDRESS_TYPE
2017 );
2018 assert_eq!(config.configured_bitrate, BITRATE_GUESS);
2019 assert!(config.allowed_interfaces.is_empty());
2020 assert!(config.ignored_interfaces.is_empty());
2021 }
2022
2023 #[test]
2026 fn constants_match_python() {
2027 assert_eq!(DEFAULT_DISCOVERY_PORT, 29716);
2028 assert_eq!(DEFAULT_DATA_PORT, 42671);
2029 assert_eq!(HW_MTU, 1196);
2030 assert_eq!(MULTI_IF_DEQUE_LEN, 48);
2031 assert!((MULTI_IF_DEQUE_TTL - 0.75).abs() < f64::EPSILON);
2032 assert!((PEERING_TIMEOUT - 22.0).abs() < f64::EPSILON);
2033 assert!((ANNOUNCE_INTERVAL - 1.6).abs() < f64::EPSILON);
2034 assert!((PEER_JOB_INTERVAL - 4.0).abs() < f64::EPSILON);
2035 assert!((MCAST_ECHO_TIMEOUT - 6.5).abs() < f64::EPSILON);
2036 assert_eq!(BITRATE_GUESS, 10_000_000);
2037 }
2038
2039 #[test]
2040 fn unicast_discovery_port() {
2041 let unicast_port = DEFAULT_DISCOVERY_PORT + 1;
2043 assert_eq!(unicast_port, 29717);
2044 }
2045
2046 #[test]
2047 fn reverse_peering_interval() {
2048 let interval = ANNOUNCE_INTERVAL * REVERSE_PEERING_MULTIPLIER;
2049 assert!((interval - 5.2).abs() < 0.01);
2050 }
2051}