1use std::net::SocketAddr;
10use std::time::Duration;
11
12pub const MAX_CANDIDATES_PER_PEER: usize = 30;
14
15pub const MAX_INACTIVE_CANDIDATES: usize = 10;
17
18pub const RTT_SWITCHING_MIN: Duration = Duration::from_millis(5);
20
21pub const IPV6_RTT_ADVANTAGE: Duration = Duration::from_millis(3);
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum PathType {
27 Direct,
29 Relay,
31}
32
33#[derive(Debug, Clone)]
35pub struct PathCandidate {
36 pub addr: SocketAddr,
38 pub rtt: Duration,
40 pub path_type: PathType,
42}
43
44impl PathCandidate {
45 pub fn new(addr: SocketAddr, rtt: Duration) -> Self {
47 Self {
48 addr,
49 rtt,
50 path_type: PathType::Direct,
51 }
52 }
53
54 pub fn direct(addr: SocketAddr, rtt: Duration) -> Self {
56 Self {
57 addr,
58 rtt,
59 path_type: PathType::Direct,
60 }
61 }
62
63 pub fn relay(addr: SocketAddr, rtt: Duration) -> Self {
65 Self {
66 addr,
67 rtt,
68 path_type: PathType::Relay,
69 }
70 }
71
72 pub fn is_direct(&self) -> bool {
74 self.path_type == PathType::Direct
75 }
76
77 pub fn is_relay(&self) -> bool {
79 self.path_type == PathType::Relay
80 }
81
82 pub fn effective_rtt(&self) -> Duration {
84 if self.addr.is_ipv6() {
85 self.rtt.saturating_sub(IPV6_RTT_ADVANTAGE)
86 } else {
87 self.rtt
88 }
89 }
90}
91
92pub fn select_best_path(
100 paths: &[PathCandidate],
101 current: Option<&PathCandidate>,
102) -> Option<PathCandidate> {
103 if paths.is_empty() {
104 return None;
105 }
106
107 let direct_paths: Vec<_> = paths.iter().filter(|p| p.is_direct()).collect();
109 let relay_paths: Vec<_> = paths.iter().filter(|p| p.is_relay()).collect();
110
111 let best_direct = find_best_by_rtt(&direct_paths);
113
114 let best_relay = find_best_by_rtt(&relay_paths);
116
117 let best_new = match (best_direct, best_relay) {
119 (Some(direct), _) => Some(direct),
120 (None, Some(relay)) => Some(relay),
121 (None, None) => None,
122 };
123
124 match (current, best_new) {
126 (None, best) => best.cloned(),
127 (Some(current), None) => Some(current.clone()),
128 (Some(current), Some(new)) => {
129 if current.is_direct() && new.is_relay() {
131 return Some(current.clone());
132 }
133
134 let current_eff = current.effective_rtt();
136 let new_eff = new.effective_rtt();
137
138 if current_eff > new_eff + RTT_SWITCHING_MIN {
139 Some(new.clone())
141 } else {
142 Some(current.clone())
144 }
145 }
146 }
147}
148
149fn find_best_by_rtt<'a>(paths: &[&'a PathCandidate]) -> Option<&'a PathCandidate> {
151 paths.iter().min_by_key(|p| p.effective_rtt()).copied()
152}
153
154pub fn select_v4_v6(
156 v4_addr: SocketAddr,
157 v4_rtt: Duration,
158 v6_addr: SocketAddr,
159 v6_rtt: Duration,
160) -> (SocketAddr, Duration) {
161 let v6_effective = v6_rtt.saturating_sub(IPV6_RTT_ADVANTAGE);
163
164 if v6_effective <= v4_rtt {
165 (v6_addr, v6_rtt)
166 } else {
167 (v4_addr, v4_rtt)
168 }
169}
170
171use std::collections::HashMap;
176
177pub const MIN_DIRECT_PATHS: usize = 2;
179
180#[derive(Debug, Clone)]
182pub struct PathInfo {
183 pub addr: SocketAddr,
185 pub path_type: PathType,
187 pub rtt: Option<Duration>,
189 pub is_open: bool,
191}
192
193impl PathInfo {
194 pub fn direct(addr: SocketAddr) -> Self {
196 Self {
197 addr,
198 path_type: PathType::Direct,
199 rtt: None,
200 is_open: true,
201 }
202 }
203
204 pub fn relay(addr: SocketAddr) -> Self {
206 Self {
207 addr,
208 path_type: PathType::Relay,
209 rtt: None,
210 is_open: true,
211 }
212 }
213
214 pub fn with_rtt(mut self, rtt: Duration) -> Self {
216 self.rtt = Some(rtt);
217 self
218 }
219}
220
221#[derive(Debug, Default)]
229pub struct PathManager {
230 paths: HashMap<SocketAddr, PathInfo>,
232 selected_path: Option<SocketAddr>,
234 min_direct_paths: usize,
236}
237
238impl PathManager {
239 pub fn new() -> Self {
241 Self {
242 paths: HashMap::new(),
243 selected_path: None,
244 min_direct_paths: MIN_DIRECT_PATHS,
245 }
246 }
247
248 pub fn with_min_direct_paths(min_direct_paths: usize) -> Self {
250 Self {
251 paths: HashMap::new(),
252 selected_path: None,
253 min_direct_paths,
254 }
255 }
256
257 pub fn add_path(&mut self, info: PathInfo) {
259 self.paths.insert(info.addr, info);
260 }
261
262 pub fn remove_path(&mut self, addr: &SocketAddr) {
264 self.paths.remove(addr);
265 if self.selected_path.as_ref() == Some(addr) {
266 self.selected_path = None;
267 }
268 }
269
270 pub fn set_selected_path(&mut self, addr: SocketAddr) {
272 self.selected_path = Some(addr);
273 }
274
275 pub fn selected_path(&self) -> Option<SocketAddr> {
277 self.selected_path
278 }
279
280 pub fn contains(&self, addr: &SocketAddr) -> bool {
282 self.paths.contains_key(addr)
283 }
284
285 pub fn is_relay_path(&self, addr: &SocketAddr) -> bool {
287 self.paths
288 .get(addr)
289 .map(|p| p.path_type == PathType::Relay)
290 .unwrap_or(false)
291 }
292
293 pub fn direct_path_count(&self) -> usize {
295 self.paths
296 .values()
297 .filter(|p| p.path_type == PathType::Direct && p.is_open)
298 .count()
299 }
300
301 pub fn relay_path_count(&self) -> usize {
303 self.paths
304 .values()
305 .filter(|p| p.path_type == PathType::Relay && p.is_open)
306 .count()
307 }
308
309 pub fn open_paths(&self) -> Vec<&PathInfo> {
311 self.paths.values().filter(|p| p.is_open).collect()
312 }
313
314 pub fn close_redundant_paths(&mut self) -> Vec<SocketAddr> {
321 let Some(selected) = self.selected_path else {
322 return Vec::new();
323 };
324
325 let open_direct: Vec<_> = self
327 .paths
328 .iter()
329 .filter(|(_, p)| p.path_type == PathType::Direct && p.is_open)
330 .map(|(addr, _)| *addr)
331 .collect();
332
333 if open_direct.len() <= self.min_direct_paths {
335 return Vec::new();
336 }
337
338 let excess = open_direct.len() - self.min_direct_paths;
340
341 let mut to_close = Vec::new();
343 for addr in open_direct {
344 if to_close.len() >= excess {
345 break;
346 }
347 if addr != selected {
348 to_close.push(addr);
349 }
350 }
351
352 for addr in &to_close {
354 if let Some(path) = self.paths.get_mut(addr) {
355 path.is_open = false;
356 }
357 }
358
359 tracing::debug!(
360 closed = to_close.len(),
361 remaining = self.direct_path_count(),
362 "Closed redundant paths"
363 );
364
365 to_close
366 }
367
368 pub fn update_rtt(&mut self, addr: &SocketAddr, rtt: Duration) {
370 if let Some(path) = self.paths.get_mut(addr) {
371 path.rtt = Some(rtt);
372 }
373 }
374
375 pub fn mark_open(&mut self, addr: &SocketAddr) {
377 if let Some(path) = self.paths.get_mut(addr) {
378 path.is_open = true;
379 }
380 }
381
382 pub fn mark_closed(&mut self, addr: &SocketAddr) {
384 if let Some(path) = self.paths.get_mut(addr) {
385 path.is_open = false;
386 }
387 }
388}
389
390#[cfg(test)]
391mod tests {
392 use super::*;
393 use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6};
394
395 fn v4_addr(port: u16) -> SocketAddr {
396 SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 168, 1, 1), port))
397 }
398
399 fn v6_addr(port: u16) -> SocketAddr {
400 SocketAddr::V6(SocketAddrV6::new(
401 Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1),
402 port,
403 0,
404 0,
405 ))
406 }
407
408 #[test]
409 fn test_selects_lower_rtt_path() {
410 let paths = vec![
411 PathCandidate::new(v4_addr(5000), Duration::from_millis(50)),
412 PathCandidate::new(v4_addr(5001), Duration::from_millis(20)),
413 PathCandidate::new(v4_addr(5002), Duration::from_millis(100)),
414 ];
415
416 let selected = select_best_path(&paths, None);
417
418 assert_eq!(selected.as_ref().map(|p| p.addr.port()), Some(5001));
419 }
420
421 #[test]
422 fn test_hysteresis_prevents_flapping() {
423 let current = PathCandidate::new(v4_addr(5000), Duration::from_millis(50));
424
425 let paths = vec![
426 current.clone(),
427 PathCandidate::new(v4_addr(5001), Duration::from_millis(48)),
429 ];
430
431 let selected = select_best_path(&paths, Some(¤t));
432
433 assert_eq!(selected.as_ref().map(|p| p.addr.port()), Some(5000));
435 }
436
437 #[test]
438 fn test_switches_when_significantly_better() {
439 let current = PathCandidate::new(v4_addr(5000), Duration::from_millis(50));
440
441 let paths = vec![
442 current.clone(),
443 PathCandidate::new(v4_addr(5001), Duration::from_millis(40)),
445 ];
446
447 let selected = select_best_path(&paths, Some(¤t));
448
449 assert_eq!(selected.as_ref().map(|p| p.addr.port()), Some(5001));
450 }
451
452 #[test]
453 fn test_ipv6_preference() {
454 let paths = vec![
455 PathCandidate::new(v4_addr(5000), Duration::from_millis(50)),
456 PathCandidate::new(v6_addr(5001), Duration::from_millis(50)),
458 ];
459
460 let selected = select_best_path(&paths, None);
461
462 assert!(selected.as_ref().map(|p| p.addr.is_ipv6()).unwrap_or(false));
463 }
464
465 #[test]
466 fn test_ipv6_advantage_applied_correctly() {
467 let paths = vec![
468 PathCandidate::new(v4_addr(5000), Duration::from_millis(48)),
470 PathCandidate::new(v6_addr(5001), Duration::from_millis(50)),
471 ];
472
473 let selected = select_best_path(&paths, None);
474
475 assert!(selected.as_ref().map(|p| p.addr.is_ipv6()).unwrap_or(false));
477 }
478
479 #[test]
480 fn test_direct_preferred_over_relay() {
481 let paths = vec![
482 PathCandidate::direct(v4_addr(5000), Duration::from_millis(100)),
483 PathCandidate::relay(v4_addr(5001), Duration::from_millis(50)),
485 ];
486
487 let selected = select_best_path(&paths, None);
488
489 assert!(selected.as_ref().map(|p| p.is_direct()).unwrap_or(false));
490 }
491
492 #[test]
493 fn test_falls_back_to_relay_when_no_direct() {
494 let paths = vec![
495 PathCandidate::relay(v4_addr(5000), Duration::from_millis(100)),
496 PathCandidate::relay(v4_addr(5001), Duration::from_millis(50)),
497 ];
498
499 let selected = select_best_path(&paths, None);
500
501 assert_eq!(selected.as_ref().map(|p| p.addr.port()), Some(5001));
503 }
504
505 #[test]
506 fn test_never_switches_from_direct_to_relay() {
507 let current = PathCandidate::direct(v4_addr(5000), Duration::from_millis(100));
508
509 let paths = vec![
510 current.clone(),
511 PathCandidate::relay(v4_addr(5001), Duration::from_millis(10)),
513 ];
514
515 let selected = select_best_path(&paths, Some(¤t));
516
517 assert!(selected.as_ref().map(|p| p.is_direct()).unwrap_or(false));
518 }
519
520 #[test]
521 fn test_empty_paths_returns_none() {
522 let paths: Vec<PathCandidate> = vec![];
523 let selected = select_best_path(&paths, None);
524 assert!(selected.is_none());
525 }
526
527 #[test]
528 fn test_all_paths_same_rtt() {
529 let paths = vec![
530 PathCandidate::new(v4_addr(5000), Duration::from_millis(50)),
531 PathCandidate::new(v4_addr(5001), Duration::from_millis(50)),
532 PathCandidate::new(v4_addr(5002), Duration::from_millis(50)),
533 ];
534
535 let selected = select_best_path(&paths, None);
537 assert!(selected.is_some());
538 }
539
540 #[test]
541 fn test_select_v4_v6_prefers_faster() {
542 let (addr, rtt) = select_v4_v6(
543 v4_addr(5000),
544 Duration::from_millis(100),
545 v6_addr(5001),
546 Duration::from_millis(50),
547 );
548
549 assert!(addr.is_ipv6());
551 assert_eq!(rtt, Duration::from_millis(50));
552 }
553
554 #[test]
555 fn test_select_v4_v6_applies_ipv6_advantage() {
556 let (addr, _) = select_v4_v6(
557 v4_addr(5000),
558 Duration::from_millis(48),
559 v6_addr(5001),
560 Duration::from_millis(50),
561 );
562
563 assert!(addr.is_ipv6());
565 }
566
567 #[test]
570 fn test_path_manager_closes_redundant_direct_paths() {
571 let mut manager = PathManager::with_min_direct_paths(2);
572
573 for port in 5000..5005 {
575 manager.add_path(PathInfo::direct(v4_addr(port)));
576 }
577
578 manager.set_selected_path(v4_addr(5000));
580
581 let closed = manager.close_redundant_paths();
583
584 assert_eq!(closed.len(), 3);
586
587 assert!(!closed.contains(&v4_addr(5000)));
589
590 assert_eq!(manager.direct_path_count(), 2);
592 }
593
594 #[test]
595 fn test_path_manager_keeps_minimum_direct_paths() {
596 let mut manager = PathManager::with_min_direct_paths(2);
597
598 manager.add_path(PathInfo::direct(v4_addr(5000)));
600 manager.add_path(PathInfo::direct(v4_addr(5001)));
601
602 manager.set_selected_path(v4_addr(5000));
603
604 let closed = manager.close_redundant_paths();
606 assert!(closed.is_empty());
607 assert_eq!(manager.direct_path_count(), 2);
608 }
609
610 #[test]
611 fn test_path_manager_never_closes_relay_paths() {
612 let mut manager = PathManager::with_min_direct_paths(1);
613
614 manager.add_path(PathInfo::direct(v4_addr(5000)));
616 manager.add_path(PathInfo::direct(v4_addr(5001)));
617 manager.add_path(PathInfo::direct(v4_addr(5002)));
618 manager.add_path(PathInfo::relay(v4_addr(6000)));
619 manager.add_path(PathInfo::relay(v4_addr(6001)));
620
621 manager.set_selected_path(v4_addr(5000));
622
623 let closed = manager.close_redundant_paths();
625
626 for addr in &closed {
628 assert!(!manager.is_relay_path(addr), "Closed a relay path!");
629 }
630
631 assert_eq!(manager.relay_path_count(), 2);
633 }
634
635 #[test]
636 fn test_path_manager_does_not_close_selected_path() {
637 let mut manager = PathManager::with_min_direct_paths(1);
638
639 manager.add_path(PathInfo::direct(v4_addr(5000)));
641 manager.add_path(PathInfo::direct(v4_addr(5001)));
642 manager.add_path(PathInfo::direct(v4_addr(5002)));
643
644 manager.set_selected_path(v4_addr(5000));
646
647 let closed = manager.close_redundant_paths();
648
649 assert_eq!(closed.len(), 2);
651
652 assert!(!closed.contains(&v4_addr(5000)));
654
655 assert!(manager.contains(&v4_addr(5000)));
657 }
658
659 #[test]
660 fn test_path_manager_no_close_without_selected() {
661 let mut manager = PathManager::with_min_direct_paths(1);
662
663 manager.add_path(PathInfo::direct(v4_addr(5000)));
665 manager.add_path(PathInfo::direct(v4_addr(5001)));
666 manager.add_path(PathInfo::direct(v4_addr(5002)));
667
668 let closed = manager.close_redundant_paths();
670 assert!(closed.is_empty());
671 }
672
673 #[test]
674 fn test_path_manager_add_and_remove() {
675 let mut manager = PathManager::new();
676
677 let addr = v4_addr(5000);
678 manager.add_path(PathInfo::direct(addr));
679 assert!(manager.contains(&addr));
680
681 manager.remove_path(&addr);
682 assert!(!manager.contains(&addr));
683 }
684
685 #[test]
686 fn test_path_manager_update_rtt() {
687 let mut manager = PathManager::new();
688
689 let addr = v4_addr(5000);
690 manager.add_path(PathInfo::direct(addr));
691
692 manager.update_rtt(&addr, Duration::from_millis(50));
693
694 let paths = manager.open_paths();
695 assert_eq!(paths.len(), 1);
696 assert_eq!(paths[0].rtt, Some(Duration::from_millis(50)));
697 }
698
699 #[test]
700 fn test_path_manager_mark_open_closed() {
701 let mut manager = PathManager::new();
702
703 let addr = v4_addr(5000);
704 manager.add_path(PathInfo::direct(addr));
705
706 assert_eq!(manager.direct_path_count(), 1);
707
708 manager.mark_closed(&addr);
709 assert_eq!(manager.direct_path_count(), 0);
710
711 manager.mark_open(&addr);
712 assert_eq!(manager.direct_path_count(), 1);
713 }
714
715 #[test]
716 fn test_path_manager_selected_path_cleared_on_remove() {
717 let mut manager = PathManager::new();
718
719 let addr = v4_addr(5000);
720 manager.add_path(PathInfo::direct(addr));
721 manager.set_selected_path(addr);
722
723 assert_eq!(manager.selected_path(), Some(addr));
724
725 manager.remove_path(&addr);
726 assert_eq!(manager.selected_path(), None);
727 }
728}