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 dynamic_template: Option<super::DynamicInterfaceTemplate>,
591}
592
593pub fn start(
599 config: AutoConfig,
600 tx: EventSender,
601 next_dynamic_id: Arc<AtomicU64>,
602) -> io::Result<()> {
603 start_with_template(config, tx, next_dynamic_id, None)
604}
605
606fn start_with_template(
607 config: AutoConfig,
608 tx: EventSender,
609 next_dynamic_id: Arc<AtomicU64>,
610 dynamic_template: Option<super::DynamicInterfaceTemplate>,
611) -> io::Result<()> {
612 let group_id = config.group_id.clone();
613 let mcast_addr_str = derive_multicast_address(
614 &group_id,
615 &config.multicast_address_type,
616 &config.discovery_scope,
617 );
618
619 let mcast_ip = match parse_multicast_addr(&mcast_addr_str) {
620 Some(ip) => ip,
621 None => {
622 return Err(io::Error::new(
623 io::ErrorKind::InvalidData,
624 format!("Invalid multicast address: {}", mcast_addr_str),
625 ));
626 }
627 };
628
629 {
630 let startup = AutoRuntime::from_config(&config);
631 *lock_or_recover(&config.runtime, "auto runtime") = startup;
632 }
633 let runtime = Arc::clone(&config.runtime);
634 let shared = Arc::new(Mutex::new(SharedState::new(next_dynamic_id)));
635 let running = Arc::new(AtomicBool::new(true));
636
637 let worker_context = AutoWorkerContext {
638 group_id,
639 mcast_ip,
640 discovery_port: config.discovery_port,
641 unicast_discovery_port: config.discovery_port + 1,
642 data_port: config.data_port,
643 name: config.name.clone(),
644 configured_bitrate: config.configured_bitrate,
645 ingress_control: config.ingress_control,
646 runtime: runtime.clone(),
647 shared: shared.clone(),
648 tx: tx.clone(),
649 dynamic_template,
650 };
651
652 {
653 let allowed = config.allowed_interfaces.clone();
654 let ignored = config.ignored_interfaces.clone();
655 let running = running.clone();
656 let name = config.name.clone();
657 thread::Builder::new()
658 .name(format!("auto-supervisor-{}", name))
659 .spawn(move || {
660 auto_supervisor_loop(allowed, ignored, worker_context, running, &name);
661 })?;
662 }
663
664 {
665 let shared = shared.clone();
666 let tx = tx.clone();
667 let running = running.clone();
668 let name = config.name.clone();
669 let runtime = runtime.clone();
670
671 thread::Builder::new()
672 .name(format!("auto-peer-jobs-{}", name))
673 .spawn(move || {
674 peer_jobs_loop(shared, tx, runtime, &running, &name);
675 })?;
676 }
677
678 let announce_interval = lock_or_recover(&runtime, "auto runtime").announce_interval_secs;
679 let peering_wait = Duration::from_secs_f64(announce_interval * 1.2);
680 thread::sleep(peering_wait);
681
682 {
683 let mut state = lock_or_recover(&shared, "auto shared state");
684 state.online = true;
685 }
686
687 log::info!("[{}] AutoInterface online", config.name);
688
689 Ok(())
690}
691
692fn auto_supervisor_loop(
693 allowed_interfaces: Vec<String>,
694 ignored_interfaces: Vec<String>,
695 context: AutoWorkerContext,
696 running: Arc<AtomicBool>,
697 name: &str,
698) {
699 let mut workers: HashMap<AutoWorkerKey, AutoWorkerEntry> = HashMap::new();
700
701 while running.load(Ordering::Relaxed) {
702 let interfaces = enumerate_interfaces(&allowed_interfaces, &ignored_interfaces);
703 let desired = interfaces
704 .iter()
705 .map(AutoWorkerKey::from_local_interface)
706 .collect::<Vec<_>>();
707
708 {
709 let mut state = lock_or_recover(&context.shared, "auto shared state");
710 state.link_local_addresses = desired
711 .iter()
712 .map(|key| PeerKey {
713 link_local_addr: key.link_local_addr.clone(),
714 if_index: key.if_index,
715 })
716 .collect();
717 }
718
719 let removed_workers = reconcile_worker_keys(workers.keys().cloned(), desired.clone())
720 .into_iter()
721 .filter_map(|action| match action {
722 WorkerReconcileAction::Remove(key) => Some(key),
723 WorkerReconcileAction::Add(_) => None,
724 })
725 .collect::<Vec<_>>();
726 for key in &removed_workers {
727 remove_peers_for_worker(&context.shared, &context.tx, key);
728 }
729
730 if desired.is_empty() && workers.is_empty() {
731 log::warn!("[{}] No suitable IPv6 link-local interfaces found", name);
732 }
733
734 reconcile_auto_workers(&mut workers, desired, |key| {
735 start_auto_worker(key, &context)
736 });
737
738 thread::sleep(Duration::from_secs_f64(AUTO_RESCAN_INTERVAL));
739 }
740
741 for (key, worker) in workers {
742 remove_peers_for_worker(&context.shared, &context.tx, &key);
743 worker.stop();
744 }
745}
746
747fn start_auto_worker(
748 key: &AutoWorkerKey,
749 context: &AutoWorkerContext,
750) -> io::Result<RunningAutoWorker> {
751 let mcast_socket =
752 create_multicast_recv_socket(&context.mcast_ip, context.discovery_port, key.if_index)?;
753 let unicast_socket = create_unicast_recv_socket(
754 &key.link_local_addr,
755 context.unicast_discovery_port,
756 key.if_index,
757 )?;
758 let data_socket =
759 create_data_recv_socket(&key.link_local_addr, context.data_port, key.if_index)?;
760
761 let worker_running = Arc::new(AtomicBool::new(true));
762
763 {
764 let group_id = context.group_id.clone();
765 let link_local = key.link_local_addr.clone();
766 let running = worker_running.clone();
767 let name = context.name.clone();
768 let runtime = context.runtime.clone();
769 let mcast_ip = context.mcast_ip;
770 let discovery_port = context.discovery_port;
771 let if_index = key.if_index;
772 thread::Builder::new()
773 .name(format!("auto-disc-tx-{}", key.ifname))
774 .spawn(move || {
775 discovery_sender_loop(
776 &group_id,
777 &link_local,
778 &mcast_ip,
779 discovery_port,
780 if_index,
781 runtime,
782 &running,
783 &name,
784 );
785 })?;
786 }
787
788 {
789 let group_id = context.group_id.clone();
790 let shared = context.shared.clone();
791 let tx = context.tx.clone();
792 let running = worker_running.clone();
793 let name = context.name.clone();
794 let ifname = key.ifname.clone();
795 let if_index = key.if_index;
796 let runtime = context.runtime.clone();
797 let data_port = context.data_port;
798 let configured_bitrate = context.configured_bitrate;
799 let ingress_control = context.ingress_control;
800 let dynamic_template = context.dynamic_template.clone();
801 thread::Builder::new()
802 .name(format!("auto-disc-rx-{}", key.ifname))
803 .spawn(move || {
804 discovery_receiver_loop(
805 mcast_socket,
806 &group_id,
807 shared,
808 tx,
809 &running,
810 &name,
811 &ifname,
812 if_index,
813 data_port,
814 configured_bitrate,
815 ingress_control,
816 runtime,
817 dynamic_template,
818 );
819 })?;
820 }
821
822 {
823 let group_id = context.group_id.clone();
824 let shared = context.shared.clone();
825 let tx = context.tx.clone();
826 let running = worker_running.clone();
827 let name = context.name.clone();
828 let ifname = key.ifname.clone();
829 let if_index = key.if_index;
830 let runtime = context.runtime.clone();
831 let data_port = context.data_port;
832 let configured_bitrate = context.configured_bitrate;
833 let ingress_control = context.ingress_control;
834 let dynamic_template = context.dynamic_template.clone();
835 thread::Builder::new()
836 .name(format!("auto-udisc-rx-{}", key.ifname))
837 .spawn(move || {
838 discovery_receiver_loop(
839 unicast_socket,
840 &group_id,
841 shared,
842 tx,
843 &running,
844 &name,
845 &ifname,
846 if_index,
847 data_port,
848 configured_bitrate,
849 ingress_control,
850 runtime,
851 dynamic_template,
852 );
853 })?;
854 }
855
856 {
857 let shared = context.shared.clone();
858 let tx = context.tx.clone();
859 let running = worker_running.clone();
860 let name = context.name.clone();
861 let if_index = key.if_index;
862 thread::Builder::new()
863 .name(format!("auto-data-rx-{}", key.ifname))
864 .spawn(move || {
865 data_receiver_loop(data_socket, shared, tx, &running, &name, if_index);
866 })?;
867 }
868
869 log::info!(
870 "[{}] AutoInterface worker online on {} {}%{}",
871 context.name,
872 key.ifname,
873 key.link_local_addr,
874 key.if_index
875 );
876
877 Ok(RunningAutoWorker {
878 running: worker_running,
879 })
880}
881
882fn create_multicast_recv_socket(
885 mcast_ip: &Ipv6Addr,
886 port: u16,
887 if_index: u32,
888) -> io::Result<UdpSocket> {
889 let socket = socket2::Socket::new(
890 socket2::Domain::IPV6,
891 socket2::Type::DGRAM,
892 Some(socket2::Protocol::UDP),
893 )?;
894
895 socket.set_reuse_address(true)?;
896 #[cfg(not(target_os = "windows"))]
897 socket.set_reuse_port(true)?;
898
899 let bind_addr = SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, port, 0, if_index);
901 socket.bind(&bind_addr.into())?;
902
903 socket.join_multicast_v6(mcast_ip, if_index)?;
905
906 socket.set_nonblocking(false)?;
907 let std_socket: UdpSocket = socket.into();
908 std_socket.set_read_timeout(Some(Duration::from_secs(2)))?;
909 Ok(std_socket)
910}
911
912fn create_unicast_recv_socket(link_local: &str, port: u16, if_index: u32) -> io::Result<UdpSocket> {
913 let ip: Ipv6Addr = link_local
914 .parse()
915 .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, format!("bad IPv6: {}", e)))?;
916
917 let socket = socket2::Socket::new(
918 socket2::Domain::IPV6,
919 socket2::Type::DGRAM,
920 Some(socket2::Protocol::UDP),
921 )?;
922
923 socket.set_reuse_address(true)?;
924 #[cfg(not(target_os = "windows"))]
925 socket.set_reuse_port(true)?;
926
927 let bind_addr = SocketAddrV6::new(ip, port, 0, if_index);
928 socket.bind(&bind_addr.into())?;
929
930 socket.set_nonblocking(false)?;
931 let std_socket: UdpSocket = socket.into();
932 std_socket.set_read_timeout(Some(Duration::from_secs(2)))?;
933 Ok(std_socket)
934}
935
936fn create_data_recv_socket(link_local: &str, port: u16, if_index: u32) -> io::Result<UdpSocket> {
937 let ip: Ipv6Addr = link_local
938 .parse()
939 .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, format!("bad IPv6: {}", e)))?;
940
941 let socket = socket2::Socket::new(
942 socket2::Domain::IPV6,
943 socket2::Type::DGRAM,
944 Some(socket2::Protocol::UDP),
945 )?;
946
947 socket.set_reuse_address(true)?;
948 #[cfg(not(target_os = "windows"))]
949 socket.set_reuse_port(true)?;
950
951 let bind_addr = SocketAddrV6::new(ip, port, 0, if_index);
952 socket.bind(&bind_addr.into())?;
953
954 socket.set_nonblocking(false)?;
955 let std_socket: UdpSocket = socket.into();
956 std_socket.set_read_timeout(Some(Duration::from_secs(2)))?;
957 Ok(std_socket)
958}
959
960fn discovery_sender_loop(
964 group_id: &[u8],
965 link_local_addr: &str,
966 mcast_ip: &Ipv6Addr,
967 discovery_port: u16,
968 if_index: u32,
969 runtime: Arc<Mutex<AutoRuntime>>,
970 running: &AtomicBool,
971 name: &str,
972) {
973 let token = compute_discovery_token(group_id, link_local_addr);
974
975 while running.load(Ordering::Relaxed) {
976 if let Ok(socket) = UdpSocket::bind("[::]:0") {
978 let if_bytes = if_index.to_ne_bytes();
980 unsafe {
981 libc::setsockopt(
982 socket_fd(&socket),
983 libc::IPPROTO_IPV6,
984 libc::IPV6_MULTICAST_IF,
985 if_bytes.as_ptr() as *const libc::c_void,
986 4,
987 );
988 }
989
990 let target = SocketAddrV6::new(*mcast_ip, discovery_port, 0, 0);
991 if let Err(e) = socket.send_to(&token, target) {
992 log::debug!("[{}] multicast send error: {}", name, e);
993 }
994 }
995
996 let sleep_dur = Duration::from_secs_f64(
997 lock_or_recover(&runtime, "auto runtime")
998 .announce_interval_secs
999 .max(0.1),
1000 );
1001 thread::sleep(sleep_dur);
1002 }
1003}
1004
1005fn discovery_receiver_loop(
1007 socket: UdpSocket,
1008 group_id: &[u8],
1009 shared: Arc<Mutex<SharedState>>,
1010 tx: EventSender,
1011 running: &AtomicBool,
1012 name: &str,
1013 ifname: &str,
1014 if_index: u32,
1015 data_port: u16,
1016 configured_bitrate: u64,
1017 ingress_control: rns_core::transport::types::IngressControlConfig,
1018 runtime: Arc<Mutex<AutoRuntime>>,
1019 dynamic_template: Option<super::DynamicInterfaceTemplate>,
1020) {
1021 let mut buf = [0u8; 1024];
1022
1023 while running.load(Ordering::Relaxed) {
1024 match socket.recv_from(&mut buf) {
1025 Ok((n, src)) => {
1026 if n < 32 {
1027 continue;
1028 }
1029
1030 let src_addr = match src {
1032 std::net::SocketAddr::V6(v6) => v6,
1033 _ => continue,
1034 };
1035 let peer_key = peer_key_from_src(&src_addr, if_index);
1036 let src_ip = peer_key.link_local_addr.clone();
1037
1038 let peering_hash = &buf[..32];
1039 let expected = compute_discovery_token(group_id, &src_ip);
1040
1041 if peering_hash != expected {
1042 log::debug!("[{}] invalid peering hash from {}", name, src_ip);
1043 continue;
1044 }
1045
1046 let state = lock_or_recover(&shared, "auto shared state");
1048 if !state.online {
1049 }
1052
1053 if state.link_local_addresses.contains(&peer_key) {
1055 drop(state);
1057 continue;
1058 }
1059
1060 if state.peers.contains_key(&peer_key) {
1062 let now = crate::time::now();
1063 drop(state);
1064 let mut state = lock_or_recover(&shared, "auto shared state");
1065 state.refresh_peer(&peer_key, now);
1066 continue;
1067 }
1068 drop(state);
1069
1070 add_peer(
1072 &shared,
1073 &tx,
1074 &peer_key,
1075 data_port,
1076 name,
1077 ifname,
1078 configured_bitrate,
1079 ingress_control,
1080 &runtime,
1081 dynamic_template.as_ref(),
1082 );
1083 }
1084 Err(ref e)
1085 if e.kind() == io::ErrorKind::WouldBlock || e.kind() == io::ErrorKind::TimedOut =>
1086 {
1087 continue;
1089 }
1090 Err(e) => {
1091 log::warn!("[{}] discovery recv error: {}", name, e);
1092 if !running.load(Ordering::Relaxed) {
1093 return;
1094 }
1095 thread::sleep(Duration::from_millis(100));
1096 }
1097 }
1098 }
1099}
1100
1101fn add_peer(
1103 shared: &Arc<Mutex<SharedState>>,
1104 tx: &EventSender,
1105 peer_key: &PeerKey,
1106 data_port: u16,
1107 name: &str,
1108 ifname: &str,
1109 configured_bitrate: u64,
1110 ingress_control: rns_core::transport::types::IngressControlConfig,
1111 _runtime: &Arc<Mutex<AutoRuntime>>,
1112 dynamic_template: Option<&super::DynamicInterfaceTemplate>,
1113) {
1114 let send_socket = match UdpSocket::bind("[::]:0") {
1116 Ok(s) => s,
1117 Err(e) => {
1118 log::warn!(
1119 "[{}] failed to create writer for peer {}: {}",
1120 name,
1121 peer_key.link_local_addr,
1122 e
1123 );
1124 return;
1125 }
1126 };
1127
1128 let target = match peer_target_addr(peer_key, data_port) {
1129 Ok(target) => target,
1130 Err(e) => {
1131 log::warn!(
1132 "[{}] failed to create target for peer {}%{}: {}",
1133 name,
1134 peer_key.link_local_addr,
1135 peer_key.if_index,
1136 e
1137 );
1138 return;
1139 }
1140 };
1141
1142 let mut state = lock_or_recover(shared, "auto shared state");
1143
1144 if state.peers.contains_key(peer_key) {
1146 state.refresh_peer(peer_key, crate::time::now());
1147 return;
1148 }
1149
1150 let peer_id = InterfaceId(state.next_id.fetch_add(1, Ordering::Relaxed));
1151
1152 let driver_writer: Box<dyn Writer> = Box::new(UdpWriter {
1154 socket: send_socket,
1155 target,
1156 });
1157
1158 let peer_info = rns_core::transport::types::InterfaceInfo {
1159 id: peer_id,
1160 name: format!(
1161 "{}:{}%{}",
1162 name, peer_key.link_local_addr, peer_key.if_index
1163 ),
1164 mode: rns_core::constants::MODE_FULL,
1165 gravity: 0,
1166 recursive_prs: false,
1167 announces_from_internal: true,
1168 announces_to_internal: None,
1169 out_capable: true,
1170 in_capable: true,
1171 bitrate: Some(configured_bitrate),
1172 airtime_profile: None,
1173 announce_rate_target: None,
1174 announce_rate_grace: 0,
1175 announce_rate_penalty: 0.0,
1176 announce_cap: rns_core::constants::ANNOUNCE_CAP,
1177 is_local_client: false,
1178 wants_tunnel: false,
1179 tunnel_id: None,
1180 mtu: 1400,
1181 ia_freq: 0.0,
1182 ip_freq: 0.0,
1183 op_freq: 0.0,
1184 op_samples: 0,
1185 started: 0.0,
1186 ingress_control,
1187 };
1188
1189 let now = crate::time::now();
1190 state.peers.insert(
1191 peer_key.clone(),
1192 AutoPeer {
1193 interface_id: peer_id,
1194 link_local_addr: peer_key.link_local_addr.clone(),
1195 ifname: ifname.to_string(),
1196 if_index: peer_key.if_index,
1197 last_heard: now,
1198 },
1199 );
1200
1201 log::info!(
1202 "[{}] Peer discovered: {} (id={})",
1203 name,
1204 peer_key.link_local_addr,
1205 peer_id.0
1206 );
1207
1208 let event = if let Some(template) = dynamic_template {
1210 Event::DynamicInterfaceUp {
1211 id: peer_id,
1212 writer: driver_writer,
1213 registration: template.registration(peer_info),
1214 }
1215 } else {
1216 Event::InterfaceUp(peer_id, Some(driver_writer), Some(peer_info))
1217 };
1218 let _ = tx.send(event);
1219}
1220
1221fn data_receiver_loop(
1223 socket: UdpSocket,
1224 shared: Arc<Mutex<SharedState>>,
1225 tx: EventSender,
1226 running: &AtomicBool,
1227 name: &str,
1228 if_index: u32,
1229) {
1230 let mut buf = [0u8; HW_MTU + 64]; while running.load(Ordering::Relaxed) {
1233 match socket.recv_from(&mut buf) {
1234 Ok((n, src)) => {
1235 if n == 0 {
1236 continue;
1237 }
1238
1239 let src_addr = match src {
1240 std::net::SocketAddr::V6(v6) => v6,
1241 _ => continue,
1242 };
1243 let peer_key = peer_key_from_src(&src_addr, if_index);
1244 let data = &buf[..n];
1245
1246 let now = crate::time::now();
1247 let data_hash = rns_crypto::sha256::sha256(data);
1248
1249 let mut state = lock_or_recover(&shared, "auto shared state");
1250
1251 if !state.online {
1252 continue;
1253 }
1254
1255 if state.is_duplicate(&data_hash, now) {
1257 continue;
1258 }
1259 state.add_dedup(data_hash, now);
1260
1261 state.refresh_peer(&peer_key, now);
1263
1264 let iface_id = match state.peers.get(&peer_key) {
1266 Some(peer) => peer.interface_id,
1267 None => {
1268 continue;
1270 }
1271 };
1272
1273 drop(state);
1274
1275 if tx
1276 .send(Event::Frame {
1277 interface_id: iface_id,
1278 data: data.to_vec(),
1279 rssi: None,
1280 snr: None,
1281 })
1282 .is_err()
1283 {
1284 return;
1285 }
1286 }
1287 Err(ref e)
1288 if e.kind() == io::ErrorKind::WouldBlock || e.kind() == io::ErrorKind::TimedOut =>
1289 {
1290 continue;
1291 }
1292 Err(e) => {
1293 log::warn!("[{}] data recv error: {}", name, e);
1294 if !running.load(Ordering::Relaxed) {
1295 return;
1296 }
1297 thread::sleep(Duration::from_millis(100));
1298 }
1299 }
1300 }
1301}
1302
1303fn peer_jobs_loop(
1305 shared: Arc<Mutex<SharedState>>,
1306 tx: EventSender,
1307 runtime: Arc<Mutex<AutoRuntime>>,
1308 running: &AtomicBool,
1309 name: &str,
1310) {
1311 while running.load(Ordering::Relaxed) {
1312 let interval = Duration::from_secs_f64(
1313 lock_or_recover(&runtime, "auto runtime")
1314 .peer_job_interval_secs
1315 .max(0.1),
1316 );
1317 thread::sleep(interval);
1318
1319 let now = crate::time::now();
1320 let mut timed_out = Vec::new();
1321 let peer_timeout_secs = lock_or_recover(&runtime, "auto runtime").peer_timeout_secs;
1322
1323 {
1324 let state = lock_or_recover(&shared, "auto shared state");
1325 for (peer_key, peer) in &state.peers {
1326 if now > peer.last_heard + peer_timeout_secs {
1327 timed_out.push((peer_key.clone(), peer.interface_id));
1328 }
1329 }
1330 }
1331
1332 for (peer_key, iface_id) in &timed_out {
1333 log::info!(
1334 "[{}] Peer timed out: {}%{}",
1335 name,
1336 peer_key.link_local_addr,
1337 peer_key.if_index
1338 );
1339 let mut state = lock_or_recover(&shared, "auto shared state");
1340 state.peers.remove(peer_key);
1341 let _ = tx.send(Event::InterfaceDown(*iface_id));
1342 }
1343 }
1344}
1345
1346#[cfg(unix)]
1350fn socket_fd(socket: &UdpSocket) -> i32 {
1351 use std::os::unix::io::AsRawFd;
1352 socket.as_raw_fd()
1353}
1354
1355#[cfg(not(unix))]
1356fn socket_fd(_socket: &UdpSocket) -> i32 {
1357 0
1358}
1359
1360use super::{InterfaceConfigData, InterfaceFactory, StartContext, StartResult};
1363
1364pub struct AutoFactory;
1366
1367impl InterfaceFactory for AutoFactory {
1368 fn type_name(&self) -> &str {
1369 "AutoInterface"
1370 }
1371
1372 fn parse_config(
1373 &self,
1374 name: &str,
1375 id: InterfaceId,
1376 params: &HashMap<String, String>,
1377 ) -> Result<Box<dyn InterfaceConfigData>, String> {
1378 let group_id = params
1379 .get("group_id")
1380 .map(|v| v.as_bytes().to_vec())
1381 .unwrap_or_else(|| DEFAULT_GROUP_ID.to_vec());
1382
1383 let discovery_scope = params
1384 .get("discovery_scope")
1385 .map(|v| match v.to_lowercase().as_str() {
1386 "link" => SCOPE_LINK.to_string(),
1387 "admin" => SCOPE_ADMIN.to_string(),
1388 "site" => SCOPE_SITE.to_string(),
1389 "organisation" | "organization" => SCOPE_ORGANISATION.to_string(),
1390 "global" => SCOPE_GLOBAL.to_string(),
1391 _ => v.clone(),
1392 })
1393 .unwrap_or_else(|| SCOPE_LINK.to_string());
1394
1395 let discovery_port = params
1396 .get("discovery_port")
1397 .and_then(|v| v.parse().ok())
1398 .unwrap_or(DEFAULT_DISCOVERY_PORT);
1399
1400 let data_port = params
1401 .get("data_port")
1402 .and_then(|v| v.parse().ok())
1403 .unwrap_or(DEFAULT_DATA_PORT);
1404
1405 let multicast_address_type = params
1406 .get("multicast_address_type")
1407 .map(|v| match v.to_lowercase().as_str() {
1408 "permanent" => MULTICAST_PERMANENT_ADDRESS_TYPE.to_string(),
1409 "temporary" => MULTICAST_TEMPORARY_ADDRESS_TYPE.to_string(),
1410 _ => v.clone(),
1411 })
1412 .unwrap_or_else(|| MULTICAST_TEMPORARY_ADDRESS_TYPE.to_string());
1413
1414 let configured_bitrate = params
1415 .get("configured_bitrate")
1416 .or_else(|| params.get("bitrate"))
1417 .and_then(|v| v.parse().ok())
1418 .unwrap_or(BITRATE_GUESS);
1419
1420 let allowed_interfaces = params
1421 .get("devices")
1422 .or_else(|| params.get("allowed_interfaces"))
1423 .map(|v| {
1424 v.split(',')
1425 .map(|s| s.trim().to_string())
1426 .filter(|s| !s.is_empty())
1427 .collect()
1428 })
1429 .unwrap_or_default();
1430
1431 let ignored_interfaces = params
1432 .get("ignored_devices")
1433 .or_else(|| params.get("ignored_interfaces"))
1434 .map(|v| {
1435 v.split(',')
1436 .map(|s| s.trim().to_string())
1437 .filter(|s| !s.is_empty())
1438 .collect()
1439 })
1440 .unwrap_or_default();
1441
1442 Ok(Box::new(AutoConfig {
1443 name: name.to_string(),
1444 group_id,
1445 discovery_scope,
1446 discovery_port,
1447 data_port,
1448 multicast_address_type,
1449 allowed_interfaces,
1450 ignored_interfaces,
1451 configured_bitrate,
1452 interface_id: id,
1453 ingress_control: rns_core::transport::types::IngressControlConfig::enabled(),
1454 runtime: Arc::new(Mutex::new(AutoRuntime {
1455 announce_interval_secs: ANNOUNCE_INTERVAL,
1456 peer_timeout_secs: PEERING_TIMEOUT,
1457 peer_job_interval_secs: PEER_JOB_INTERVAL,
1458 })),
1459 }))
1460 }
1461
1462 fn start(
1463 &self,
1464 config: Box<dyn InterfaceConfigData>,
1465 ctx: StartContext,
1466 ) -> std::io::Result<StartResult> {
1467 let mut auto_config = *config.into_any().downcast::<AutoConfig>().map_err(|_| {
1468 std::io::Error::new(std::io::ErrorKind::InvalidData, "wrong config type")
1469 })?;
1470
1471 auto_config.ingress_control = ctx.ingress_control;
1472 let parent_id = auto_config.interface_id;
1473 start_with_template(
1474 auto_config,
1475 ctx.tx,
1476 ctx.next_dynamic_id,
1477 Some(super::DynamicInterfaceTemplate {
1478 parent_id,
1479 interface_type: "AutoInterfacePeer".into(),
1480 ifac: ctx.ifac,
1481 mode: ctx.mode,
1482 gravity: ctx.gravity,
1483 recursive_prs: ctx.recursive_prs,
1484 announces_from_internal: ctx.announces_from_internal,
1485 announces_to_internal: ctx.announces_to_internal,
1486 }),
1487 )?;
1488 Ok(StartResult::Listener { control: None })
1489 }
1490}
1491
1492pub(crate) fn auto_runtime_handle_from_config(config: &AutoConfig) -> AutoRuntimeConfigHandle {
1493 AutoRuntimeConfigHandle {
1494 interface_name: config.name.clone(),
1495 runtime: Arc::clone(&config.runtime),
1496 startup: AutoRuntime::from_config(config),
1497 }
1498}
1499
1500#[cfg(test)]
1503mod tests {
1504 use super::*;
1505
1506 #[test]
1509 fn multicast_address_default_group() {
1510 let addr = derive_multicast_address(
1512 DEFAULT_GROUP_ID,
1513 MULTICAST_TEMPORARY_ADDRESS_TYPE,
1514 SCOPE_LINK,
1515 );
1516 assert_eq!(addr, "ff12:0:d70b:fb1c:16e4:5e39:485e:31e1");
1517 }
1518
1519 #[test]
1520 fn multicast_address_custom_group() {
1521 let addr =
1522 derive_multicast_address(b"testgroup", MULTICAST_TEMPORARY_ADDRESS_TYPE, SCOPE_LINK);
1523 assert!(addr.starts_with("ff12:0:"));
1525 assert_ne!(addr, "ff12:0:d70b:fb1c:16e4:5e39:485e:31e1");
1527 }
1528
1529 #[test]
1530 fn multicast_address_scope_admin() {
1531 let addr = derive_multicast_address(
1532 DEFAULT_GROUP_ID,
1533 MULTICAST_TEMPORARY_ADDRESS_TYPE,
1534 SCOPE_ADMIN,
1535 );
1536 assert!(addr.starts_with("ff14:0:"));
1537 }
1538
1539 #[test]
1540 fn multicast_address_permanent_type() {
1541 let addr = derive_multicast_address(
1542 DEFAULT_GROUP_ID,
1543 MULTICAST_PERMANENT_ADDRESS_TYPE,
1544 SCOPE_LINK,
1545 );
1546 assert!(addr.starts_with("ff02:0:"));
1547 }
1548
1549 #[test]
1550 fn multicast_address_parseable() {
1551 let addr = derive_multicast_address(
1552 DEFAULT_GROUP_ID,
1553 MULTICAST_TEMPORARY_ADDRESS_TYPE,
1554 SCOPE_LINK,
1555 );
1556 let ip = parse_multicast_addr(&addr);
1557 assert!(ip.is_some());
1558 assert!(ip.unwrap().is_multicast());
1559 }
1560
1561 #[test]
1564 fn discovery_token_interop() {
1565 let token = compute_discovery_token(DEFAULT_GROUP_ID, "fe80::1");
1567 let expected = "97b25576749ea936b0d8a8536ffaf442d157cf47d460dcf13c48b7bd18b6c163";
1568 let got = token
1569 .iter()
1570 .map(|b| format!("{:02x}", b))
1571 .collect::<String>();
1572 assert_eq!(got, expected);
1573 }
1574
1575 #[test]
1576 fn discovery_token_interop_2() {
1577 let token = compute_discovery_token(DEFAULT_GROUP_ID, "fe80::dead:beef:1234:5678");
1579 let expected = "46b6ec7595504b6a35f06bd4bfff71567fb82fcf2706cd361bab20409c42d072";
1580 let got = token
1581 .iter()
1582 .map(|b| format!("{:02x}", b))
1583 .collect::<String>();
1584 assert_eq!(got, expected);
1585 }
1586
1587 #[test]
1588 fn discovery_token_different_groups() {
1589 let t1 = compute_discovery_token(b"reticulum", "fe80::1");
1590 let t2 = compute_discovery_token(b"othergroup", "fe80::1");
1591 assert_ne!(t1, t2);
1592 }
1593
1594 #[test]
1595 fn discovery_token_different_addrs() {
1596 let t1 = compute_discovery_token(DEFAULT_GROUP_ID, "fe80::1");
1597 let t2 = compute_discovery_token(DEFAULT_GROUP_ID, "fe80::2");
1598 assert_ne!(t1, t2);
1599 }
1600
1601 #[test]
1604 fn dedup_basic() {
1605 let next_id = Arc::new(AtomicU64::new(1));
1606 let mut state = SharedState::new(next_id);
1607
1608 let hash = [0xAA; 32];
1609 let now = 1000.0;
1610
1611 assert!(!state.is_duplicate(&hash, now));
1612 state.add_dedup(hash, now);
1613 assert!(state.is_duplicate(&hash, now));
1614 }
1615
1616 #[test]
1617 fn dedup_expired() {
1618 let next_id = Arc::new(AtomicU64::new(1));
1619 let mut state = SharedState::new(next_id);
1620
1621 let hash = [0xBB; 32];
1622 state.add_dedup(hash, 1000.0);
1623
1624 assert!(state.is_duplicate(&hash, 1000.5));
1626 assert!(!state.is_duplicate(&hash, 1001.0));
1628 }
1629
1630 #[test]
1631 fn dedup_max_length() {
1632 let next_id = Arc::new(AtomicU64::new(1));
1633 let mut state = SharedState::new(next_id);
1634
1635 for i in 0..MULTI_IF_DEQUE_LEN + 10 {
1637 let mut hash = [0u8; 32];
1638 hash[0] = (i & 0xFF) as u8;
1639 hash[1] = ((i >> 8) & 0xFF) as u8;
1640 state.add_dedup(hash, 1000.0);
1641 }
1642
1643 assert_eq!(state.dedup_deque.len(), MULTI_IF_DEQUE_LEN);
1644 }
1645
1646 #[test]
1649 fn peer_refresh() {
1650 let next_id = Arc::new(AtomicU64::new(100));
1651 let mut state = SharedState::new(next_id);
1652 let key = peer_key("fe80::1", 2);
1653
1654 state.peers.insert(
1655 key.clone(),
1656 AutoPeer {
1657 interface_id: InterfaceId(100),
1658 link_local_addr: key.link_local_addr.clone(),
1659 ifname: "eth0".to_string(),
1660 if_index: key.if_index,
1661 last_heard: 1000.0,
1662 },
1663 );
1664
1665 state.refresh_peer(&key, 2000.0);
1666 assert_eq!(state.peers[&key].last_heard, 2000.0);
1667 }
1668
1669 #[test]
1670 fn peer_not_found_refresh() {
1671 let next_id = Arc::new(AtomicU64::new(100));
1672 let mut state = SharedState::new(next_id);
1673 state.refresh_peer(&peer_key("fe80::999", 4), 1000.0);
1675 }
1676
1677 fn peer_key(addr: &str, if_index: u32) -> PeerKey {
1678 PeerKey {
1679 link_local_addr: addr.to_string(),
1680 if_index,
1681 }
1682 }
1683
1684 #[test]
1685 fn peer_key_distinguishes_same_link_local_on_different_interfaces() {
1686 let next_id = Arc::new(AtomicU64::new(100));
1687 let mut state = SharedState::new(next_id);
1688 let eth = peer_key("fe80::1", 2);
1689 let wlan = peer_key("fe80::1", 4);
1690
1691 state.peers.insert(
1692 eth.clone(),
1693 AutoPeer {
1694 interface_id: InterfaceId(100),
1695 link_local_addr: eth.link_local_addr.clone(),
1696 ifname: "eth0".to_string(),
1697 if_index: eth.if_index,
1698 last_heard: 1000.0,
1699 },
1700 );
1701 state.peers.insert(
1702 wlan.clone(),
1703 AutoPeer {
1704 interface_id: InterfaceId(101),
1705 link_local_addr: wlan.link_local_addr.clone(),
1706 ifname: "wlan0".to_string(),
1707 if_index: wlan.if_index,
1708 last_heard: 1000.0,
1709 },
1710 );
1711
1712 assert_eq!(state.peers.len(), 2);
1713 assert_eq!(state.peers[ð].interface_id, InterfaceId(100));
1714 assert_eq!(state.peers[&wlan].interface_id, InterfaceId(101));
1715 }
1716
1717 #[test]
1718 fn peer_target_addr_uses_scope_id() {
1719 let target = peer_target_addr(&peer_key("fe80::1", 7), 42671).unwrap();
1720
1721 assert_eq!(target.port(), 42671);
1722 assert_eq!(target.scope_id(), 7);
1723 }
1724
1725 #[test]
1726 fn remove_peers_for_worker_emits_interface_down() {
1727 let next_id = Arc::new(AtomicU64::new(100));
1728 let shared = Arc::new(Mutex::new(SharedState::new(next_id)));
1729 let worker = worker_key("wlan0", 4, "fe80::abcd");
1730 let removed = peer_key("fe80::1", 4);
1731 let kept = peer_key("fe80::1", 5);
1732 let (tx, rx) = crate::event::channel_with_capacity(4);
1733
1734 {
1735 let mut state = lock_or_recover(&shared, "test auto shared state");
1736 state.peers.insert(
1737 removed.clone(),
1738 AutoPeer {
1739 interface_id: InterfaceId(100),
1740 link_local_addr: removed.link_local_addr.clone(),
1741 ifname: "wlan0".to_string(),
1742 if_index: removed.if_index,
1743 last_heard: 1000.0,
1744 },
1745 );
1746 state.peers.insert(
1747 kept.clone(),
1748 AutoPeer {
1749 interface_id: InterfaceId(101),
1750 link_local_addr: kept.link_local_addr.clone(),
1751 ifname: "wlan1".to_string(),
1752 if_index: kept.if_index,
1753 last_heard: 1000.0,
1754 },
1755 );
1756 }
1757
1758 remove_peers_for_worker(&shared, &tx, &worker);
1759
1760 let state = lock_or_recover(&shared, "test auto shared state");
1761 assert!(!state.peers.contains_key(&removed));
1762 assert!(state.peers.contains_key(&kept));
1763 drop(state);
1764
1765 assert!(matches!(
1766 rx.recv_timeout(std::time::Duration::from_secs(1)).unwrap(),
1767 Event::InterfaceDown(InterfaceId(100))
1768 ));
1769 assert!(rx.try_recv().is_err());
1770 }
1771
1772 #[test]
1775 fn enumerate_returns_vec() {
1776 let interfaces = enumerate_interfaces(&[], &[]);
1779 for iface in &interfaces {
1781 assert!(!iface.name.is_empty());
1782 assert!(iface.link_local_addr.starts_with("fe80"));
1783 assert!(iface.index > 0);
1784 }
1785 }
1786
1787 #[test]
1788 fn enumerate_with_ignored() {
1789 let interfaces = enumerate_interfaces(
1791 &[],
1792 &[
1793 "lo".to_string(),
1794 "eth0".to_string(),
1795 "wlan0".to_string(),
1796 "enp0s3".to_string(),
1797 "docker0".to_string(),
1798 ],
1799 );
1800 for iface in &interfaces {
1802 assert_ne!(iface.name, "lo");
1803 assert_ne!(iface.name, "eth0");
1804 assert_ne!(iface.name, "wlan0");
1805 }
1806 }
1807
1808 #[test]
1809 fn enumerate_with_allowed_nonexistent() {
1810 let interfaces = enumerate_interfaces(&["nonexistent_if_12345".to_string()], &[]);
1812 assert!(interfaces.is_empty());
1813 }
1814
1815 fn worker_key(name: &str, index: u32, addr: &str) -> AutoWorkerKey {
1816 AutoWorkerKey {
1817 ifname: name.to_string(),
1818 if_index: index,
1819 link_local_addr: addr.to_string(),
1820 }
1821 }
1822
1823 #[test]
1824 fn reconcile_worker_keys_adds_new_interface() {
1825 let desired = vec![worker_key("wlan0", 4, "fe80::1")];
1826
1827 assert_eq!(
1828 reconcile_worker_keys(Vec::new(), desired.clone()),
1829 vec![WorkerReconcileAction::Add(desired[0].clone())]
1830 );
1831 }
1832
1833 #[test]
1834 fn reconcile_worker_keys_keeps_unchanged_interface() {
1835 let key = worker_key("wlan0", 4, "fe80::1");
1836
1837 assert!(reconcile_worker_keys(vec![key.clone()], vec![key]).is_empty());
1838 }
1839
1840 #[test]
1841 fn reconcile_worker_keys_removes_missing_interface() {
1842 let active = vec![worker_key("wlan0", 4, "fe80::1")];
1843
1844 assert_eq!(
1845 reconcile_worker_keys(active.clone(), Vec::new()),
1846 vec![WorkerReconcileAction::Remove(active[0].clone())]
1847 );
1848 }
1849
1850 #[test]
1851 fn reconcile_worker_keys_replaces_changed_interface_identity() {
1852 let old = worker_key("wlan0", 4, "fe80::1");
1853 let new = worker_key("wlan0", 9, "fe80::2");
1854 let actions = reconcile_worker_keys(vec![old.clone()], vec![new.clone()]);
1855
1856 assert!(actions.contains(&WorkerReconcileAction::Remove(old)));
1857 assert!(actions.contains(&WorkerReconcileAction::Add(new)));
1858 assert_eq!(actions.len(), 2);
1859 }
1860
1861 #[test]
1862 fn reconcile_auto_workers_starts_new_worker() {
1863 let key = worker_key("wlan0", 4, "fe80::1");
1864 let mut workers = HashMap::new();
1865 let mut starts = Vec::new();
1866
1867 reconcile_auto_workers(&mut workers, vec![key.clone()], |key| {
1868 starts.push(key.clone());
1869 Ok(RunningAutoWorker {
1870 running: Arc::new(AtomicBool::new(true)),
1871 })
1872 });
1873
1874 assert_eq!(starts, vec![key.clone()]);
1875 assert!(matches!(
1876 workers.get(&key),
1877 Some(AutoWorkerEntry::Running(_))
1878 ));
1879 }
1880
1881 #[test]
1882 fn reconcile_auto_workers_stops_removed_worker() {
1883 let key = worker_key("wlan0", 4, "fe80::1");
1884 let running = Arc::new(AtomicBool::new(true));
1885 let mut workers = HashMap::from([(
1886 key.clone(),
1887 AutoWorkerEntry::Running(RunningAutoWorker {
1888 running: running.clone(),
1889 }),
1890 )]);
1891
1892 reconcile_auto_workers(&mut workers, Vec::new(), |_| unreachable!());
1893
1894 assert!(!running.load(Ordering::Relaxed));
1895 assert!(!workers.contains_key(&key));
1896 }
1897
1898 #[test]
1899 fn reconcile_auto_workers_leaves_unchanged_worker_running() {
1900 let key = worker_key("wlan0", 4, "fe80::1");
1901 let running = Arc::new(AtomicBool::new(true));
1902 let mut workers = HashMap::from([(
1903 key.clone(),
1904 AutoWorkerEntry::Running(RunningAutoWorker {
1905 running: running.clone(),
1906 }),
1907 )]);
1908 let mut starts = 0;
1909
1910 reconcile_auto_workers(&mut workers, vec![key.clone()], |_| {
1911 starts += 1;
1912 unreachable!()
1913 });
1914
1915 assert_eq!(starts, 0);
1916 assert!(running.load(Ordering::Relaxed));
1917 assert!(workers.contains_key(&key));
1918 }
1919
1920 #[test]
1921 fn reconcile_auto_workers_records_pending_start_failure() {
1922 let key = worker_key("wlan0", 4, "fe80::1");
1923 let mut workers = HashMap::new();
1924
1925 reconcile_auto_workers(&mut workers, vec![key.clone()], |_| {
1926 Err(io::Error::new(io::ErrorKind::AddrInUse, "busy"))
1927 });
1928
1929 assert!(matches!(workers.get(&key), Some(AutoWorkerEntry::Pending)));
1930 }
1931
1932 #[test]
1933 fn reconcile_auto_workers_retries_pending_worker() {
1934 let key = worker_key("wlan0", 4, "fe80::1");
1935 let running = Arc::new(AtomicBool::new(true));
1936 let mut workers = HashMap::from([(key.clone(), AutoWorkerEntry::Pending)]);
1937 let mut starts = 0;
1938
1939 reconcile_auto_workers(&mut workers, vec![key.clone()], |_| {
1940 starts += 1;
1941 Ok(RunningAutoWorker {
1942 running: running.clone(),
1943 })
1944 });
1945
1946 assert_eq!(starts, 1);
1947 assert!(matches!(
1948 workers.get(&key),
1949 Some(AutoWorkerEntry::Running(_))
1950 ));
1951 }
1952
1953 #[test]
1954 fn reconcile_auto_workers_keeps_pending_worker_on_retry_failure() {
1955 let key = worker_key("wlan0", 4, "fe80::1");
1956 let mut workers = HashMap::from([(key.clone(), AutoWorkerEntry::Pending)]);
1957 let mut starts = 0;
1958
1959 reconcile_auto_workers(&mut workers, vec![key.clone()], |_| {
1960 starts += 1;
1961 Err(io::Error::new(io::ErrorKind::AddrInUse, "busy"))
1962 });
1963
1964 assert_eq!(starts, 1);
1965 assert!(matches!(workers.get(&key), Some(AutoWorkerEntry::Pending)));
1966 }
1967
1968 #[test]
1969 fn reconcile_auto_workers_removes_pending_worker() {
1970 let key = worker_key("wlan0", 4, "fe80::1");
1971 let mut workers = HashMap::from([(key.clone(), AutoWorkerEntry::Pending)]);
1972
1973 reconcile_auto_workers(&mut workers, Vec::new(), |_| unreachable!());
1974
1975 assert!(!workers.contains_key(&key));
1976 }
1977
1978 #[test]
1979 fn filter_skips_android_system_interfaces() {
1980 let allowed = Vec::new();
1981 let ignored = Vec::new();
1982
1983 for name in ["dummy0", "lo", "tun0", "rmnet0", "rmnet7"] {
1984 assert!(
1985 !should_adopt_interface_name(name, &allowed, &ignored, ANDROID_IGNORE_IFS),
1986 "{name} should be skipped by Android AutoInterface defaults"
1987 );
1988 }
1989 }
1990
1991 #[test]
1992 fn filter_does_not_skip_rmnet8_by_android_defaults() {
1993 assert!(should_adopt_interface_name(
1994 "rmnet8",
1995 &[],
1996 &[],
1997 ANDROID_IGNORE_IFS
1998 ));
1999 }
2000
2001 #[test]
2002 fn filter_allowed_overrides_system_ignored_interface() {
2003 assert!(should_adopt_interface_name(
2004 "rmnet0",
2005 &["rmnet0".to_string()],
2006 &[],
2007 ANDROID_IGNORE_IFS
2008 ));
2009
2010 assert!(should_adopt_interface_name(
2011 "lo0",
2012 &["lo0".to_string()],
2013 &[],
2014 &[]
2015 ));
2016 }
2017
2018 #[test]
2019 fn filter_ignored_wins_over_allowed_interface() {
2020 assert!(!should_adopt_interface_name(
2021 "rmnet0",
2022 &["rmnet0".to_string()],
2023 &["rmnet0".to_string()],
2024 ANDROID_IGNORE_IFS
2025 ));
2026 }
2027
2028 #[test]
2029 fn filter_allowed_list_excludes_unlisted_interfaces() {
2030 assert!(!should_adopt_interface_name(
2031 "wlan0",
2032 &["eth0".to_string()],
2033 &[],
2034 ANDROID_IGNORE_IFS
2035 ));
2036
2037 assert!(should_adopt_interface_name(
2038 "wlan0",
2039 &["wlan0".to_string()],
2040 &[],
2041 ANDROID_IGNORE_IFS
2042 ));
2043 }
2044
2045 #[test]
2048 fn config_defaults() {
2049 let config = AutoConfig::default();
2050 assert_eq!(config.group_id, DEFAULT_GROUP_ID);
2051 assert_eq!(config.discovery_scope, SCOPE_LINK);
2052 assert_eq!(config.discovery_port, DEFAULT_DISCOVERY_PORT);
2053 assert_eq!(config.data_port, DEFAULT_DATA_PORT);
2054 assert_eq!(
2055 config.multicast_address_type,
2056 MULTICAST_TEMPORARY_ADDRESS_TYPE
2057 );
2058 assert_eq!(config.configured_bitrate, BITRATE_GUESS);
2059 assert!(config.allowed_interfaces.is_empty());
2060 assert!(config.ignored_interfaces.is_empty());
2061 }
2062
2063 #[test]
2066 fn constants_match_python() {
2067 assert_eq!(DEFAULT_DISCOVERY_PORT, 29716);
2068 assert_eq!(DEFAULT_DATA_PORT, 42671);
2069 assert_eq!(HW_MTU, 1196);
2070 assert_eq!(MULTI_IF_DEQUE_LEN, 48);
2071 assert!((MULTI_IF_DEQUE_TTL - 0.75).abs() < f64::EPSILON);
2072 assert!((PEERING_TIMEOUT - 22.0).abs() < f64::EPSILON);
2073 assert!((ANNOUNCE_INTERVAL - 1.6).abs() < f64::EPSILON);
2074 assert!((PEER_JOB_INTERVAL - 4.0).abs() < f64::EPSILON);
2075 assert!((MCAST_ECHO_TIMEOUT - 6.5).abs() < f64::EPSILON);
2076 assert_eq!(BITRATE_GUESS, 10_000_000);
2077 }
2078
2079 #[test]
2080 fn unicast_discovery_port() {
2081 let unicast_port = DEFAULT_DISCOVERY_PORT + 1;
2083 assert_eq!(unicast_port, 29717);
2084 }
2085
2086 #[test]
2087 fn reverse_peering_interval() {
2088 let interval = ANNOUNCE_INTERVAL * REVERSE_PEERING_MULTIPLIER;
2089 assert!((interval - 5.2).abs() < 0.01);
2090 }
2091}