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