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