1use super::*;
2
3mod direct_path;
4
5pub(in crate::node) enum TransitNextHopPlan {
6 Route(NodeAddr),
7 Loop(NodeAddr),
8 NoRoute,
9}
10
11impl Node {
12 pub(in crate::node) fn cache_current_root_coords(
15 &mut self,
16 node_addr: NodeAddr,
17 coords: crate::tree::TreeCoordinate,
18 now_ms: u64,
19 ) -> bool {
20 if coords.node_addr() != &node_addr
21 || coords.root_id() != self.tree_state.my_coords().root_id()
22 {
23 return false;
24 }
25 self.coord_cache.insert(node_addr, coords, now_ms);
26 true
27 }
28
29 pub(crate) fn is_tree_peer(&self, peer_addr: &NodeAddr) -> bool {
34 if !self.tree_state.is_root() && self.tree_state.my_declaration().parent_id() == peer_addr {
36 return true;
37 }
38 if let Some(decl) = self.tree_state.peer_declaration(peer_addr)
40 && decl.parent_id() == self.node_addr()
41 {
42 return true;
43 }
44 false
45 }
46
47 pub fn find_next_hop(&mut self, dest_node_addr: &NodeAddr) -> Option<&ActivePeer> {
74 if dest_node_addr == self.node_addr() {
76 return None;
77 }
78 let now_ms = Self::now_ms();
79 let failed_learned_routes = self.learned_routes.failed_next_hops(dest_node_addr, now_ms);
80 let direct_path_hard_degraded =
81 self.session_direct_path_is_degraded(dest_node_addr, now_ms);
82 let direct_path_soft_degraded = !direct_path_hard_degraded
83 && self.session_direct_discovered_endpoint_trust_expired(dest_node_addr, now_ms);
84 let fallback_peer_available = self.has_sendable_fallback_lookup_peer(dest_node_addr);
85 let direct_session_degraded =
86 fallback_peer_available && (direct_path_hard_degraded || direct_path_soft_degraded);
87 let direct_session_untrusted = !direct_session_degraded
88 && self.session_direct_path_exclusive_trust_expired(dest_node_addr, now_ms);
89 let stale_traversal_direct_route = self
90 .peers
91 .get(dest_node_addr)
92 .filter(|peer| {
93 !direct_path_hard_degraded
94 && !direct_session_degraded
95 && !peer.is_healthy()
96 && peer.can_send()
97 })
98 .and_then(|_| {
99 self.configured_peer(dest_node_addr)
100 .and_then(|peer_config| {
101 (peer_config.is_auto_connect()
102 && self.active_peer_uses_traversal_path(dest_node_addr, peer_config))
103 .then_some(*dest_node_addr)
104 })
105 });
106
107 let healthy_direct_route = self
108 .peers
109 .get(dest_node_addr)
110 .filter(|peer| peer.is_healthy() && !direct_session_degraded)
111 .map(|_| *dest_node_addr);
112 let direct_payload_untried = self.sessions.get(dest_node_addr).is_some()
113 && self
114 .dataplane
115 .fsp_owner_activity(dest_node_addr)
116 .is_none_or(|activity| activity.last_outbound_next_hop().is_none());
117
118 if let Some(direct_addr) = healthy_direct_route
127 && direct_payload_untried
128 && !direct_session_untrusted
129 {
130 return self.peers.get(&direct_addr);
131 }
132 let authenticated_direct_handshake = self.config.node.routing.mode
133 == RoutingMode::ReplyLearned
134 && self
135 .learned_routes
136 .active_handshake_route(dest_node_addr, now_ms)
137 == Some(*dest_node_addr);
138
139 if let Some(direct_addr) = healthy_direct_route
146 && authenticated_direct_handshake
147 && !direct_session_untrusted
148 {
149 return self.peers.get(&direct_addr);
150 }
151 let active_fallback_affinity = (self.config.node.routing.mode == RoutingMode::ReplyLearned)
152 .then(|| {
153 let activity = self.dataplane.fsp_owner_activity(dest_node_addr)?;
154 let next_hop = activity.last_outbound_next_hop()?;
155 (next_hop != *dest_node_addr
156 && activity.has_recent_outbound_activity(
157 now_ms,
158 self.session_direct_path_exclusive_trust_timeout_ms(),
159 )
160 && !failed_learned_routes.contains(&next_hop)
161 && self
162 .peers
163 .get(&next_hop)
164 .is_some_and(|peer| peer.is_healthy() && peer.can_send()))
165 .then_some(next_hop)
166 })
167 .flatten();
168 if let Some(next_hop_addr) = active_fallback_affinity {
169 self.learned_routes
170 .record_selected(dest_node_addr, &next_hop_addr, now_ms);
171 return self.peers.get(&next_hop_addr);
172 }
173 let direct_session_has_recent_data_return =
174 self.session_direct_path_has_recent_data_return(dest_node_addr, now_ms);
175 if let Some(direct_addr) = healthy_direct_route
176 && direct_session_has_recent_data_return
177 {
178 return self.peers.get(&direct_addr);
179 }
180 if let Some(direct_addr) = healthy_direct_route
181 && !direct_session_untrusted
182 && self.dataplane_fmp_link_cost(&direct_addr)
183 <= 1.0 + ROUTING_FALLBACK_MIN_COST_ADVANTAGE
184 {
185 return self.peers.get(&direct_addr);
186 }
187 let direct_payload_eligible = healthy_direct_route.is_some();
188 let payload_candidate_can_send = |addr: &NodeAddr, peer: &ActivePeer| {
189 if addr == dest_node_addr {
190 direct_payload_eligible
191 } else {
192 peer.is_healthy() && !failed_learned_routes.contains(addr)
193 }
194 };
195
196 let fallback_beats_direct = |node: &Self, fallback_addr: NodeAddr| {
201 if direct_session_untrusted {
202 return healthy_direct_route != Some(fallback_addr)
203 && node
204 .peers
205 .get(&fallback_addr)
206 .is_some_and(|peer| peer.is_healthy());
207 }
208 node.route_candidate_beats_direct(healthy_direct_route, fallback_addr)
209 };
210
211 let sendable_learned_peers = if self.config.node.routing.mode == RoutingMode::ReplyLearned {
212 Some(
213 self.peers
214 .iter()
215 .filter(|(addr, peer)| payload_candidate_can_send(addr, peer))
216 .map(|(addr, _)| *addr)
217 .collect::<HashSet<_>>(),
218 )
219 } else {
220 None
221 };
222
223 let explore_fallback = sendable_learned_peers.as_ref().is_some_and(|sendable| {
224 self.learned_routes.should_explore_fallback(
225 dest_node_addr,
226 now_ms,
227 self.config.node.routing.learned_fallback_explore_interval,
228 |addr| sendable.contains(addr),
229 )
230 });
231 if let Some(sendable) = &sendable_learned_peers
238 && !explore_fallback
239 {
240 let eligible = sendable
241 .iter()
242 .copied()
243 .filter(|addr| fallback_beats_direct(self, *addr))
244 .collect::<HashSet<_>>();
245 if !eligible.is_empty()
246 && let Some(next_hop_addr) =
247 self.learned_routes
248 .select_next_hop(dest_node_addr, now_ms, |addr| eligible.contains(addr))
249 {
250 return self.peers.get(&next_hop_addr);
251 }
252 }
253
254 let Some(dest_coords) = self
256 .coord_cache
257 .get_and_touch(dest_node_addr, now_ms)
258 .cloned()
259 else {
260 if (healthy_direct_route.is_none() || explore_fallback)
261 && let Some(sendable) = &sendable_learned_peers
262 && let Some(next_hop_addr) =
263 self.learned_routes
264 .select_next_hop(dest_node_addr, now_ms, |addr| sendable.contains(addr))
265 {
266 return self.peers.get(&next_hop_addr);
267 }
268 if let Some(direct_addr) = healthy_direct_route {
269 return self.peers.get(&direct_addr);
270 }
271 if let Some(direct_addr) = stale_traversal_direct_route {
272 return self.peers.get(&direct_addr);
273 }
274 return None;
275 };
276
277 let coordinate_route_addr = {
280 let candidates: Vec<&ActivePeer> = self
281 .peers
282 .iter()
283 .filter(|(addr, peer)| {
284 payload_candidate_can_send(addr, peer) && peer.may_reach(dest_node_addr)
285 })
286 .map(|(_, peer)| peer)
287 .collect();
288 if !candidates.is_empty() {
289 self.select_best_candidate(&candidates, &dest_coords)
290 .map(|peer| *peer.node_addr())
291 } else {
292 None
293 }
294 };
295 if let Some(next_hop_addr) = coordinate_route_addr
296 && fallback_beats_direct(self, next_hop_addr)
297 {
298 return self.peers.get(&next_hop_addr);
299 }
300
301 let tree_route_addr = self.select_tree_payload_candidate(
303 &dest_coords,
304 dest_node_addr,
305 direct_payload_eligible,
306 );
307 if let Some(next_hop_addr) = tree_route_addr
308 && fallback_beats_direct(self, next_hop_addr)
309 {
310 return self.peers.get(&next_hop_addr);
311 }
312
313 if explore_fallback
314 && let Some(peer) = sendable_learned_peers.as_ref().and_then(|sendable| {
315 self.learned_routes
316 .select_next_hop(dest_node_addr, now_ms, |addr| sendable.contains(addr))
317 .and_then(|next_hop_addr| self.peers.get(&next_hop_addr))
318 })
319 {
320 return Some(peer);
321 }
322
323 if let Some(direct_addr) = healthy_direct_route {
324 return self.peers.get(&direct_addr);
325 }
326
327 if let Some(sendable) = &sendable_learned_peers
328 && let Some(next_hop_addr) =
329 self.learned_routes
330 .select_next_hop(dest_node_addr, now_ms, |addr| sendable.contains(addr))
331 {
332 return self.peers.get(&next_hop_addr);
333 }
334
335 if let Some(direct_addr) = stale_traversal_direct_route {
336 return self.peers.get(&direct_addr);
337 }
338
339 None
340 }
341
342 pub(in crate::node) fn plan_transit_next_hop(
343 &mut self,
344 dest_node_addr: &NodeAddr,
345 previous_hop: &NodeAddr,
346 ) -> TransitNextHopPlan {
347 if dest_node_addr == self.node_addr() {
348 return TransitNextHopPlan::NoRoute;
349 }
350
351 if dest_node_addr != previous_hop
352 && self
353 .peers
354 .get(dest_node_addr)
355 .is_some_and(|peer| peer.is_healthy())
356 {
357 return TransitNextHopPlan::Route(*dest_node_addr);
358 }
359
360 if self.config.node.routing.mode == RoutingMode::ReplyLearned {
366 let sendable = self
367 .peers
368 .iter()
369 .filter(|(addr, peer)| *addr != previous_hop && peer.is_healthy())
370 .map(|(addr, _)| *addr)
371 .collect::<HashSet<_>>();
372 if let Some(next_hop_addr) =
373 self.learned_routes
374 .select_handshake_route(dest_node_addr, Self::now_ms(), |addr| {
375 sendable.contains(addr)
376 })
377 {
378 return TransitNextHopPlan::Route(next_hop_addr);
379 }
380 }
381
382 let Some(next_hop_addr) = self
383 .find_next_hop(dest_node_addr)
384 .map(|peer| *peer.node_addr())
385 else {
386 return TransitNextHopPlan::NoRoute;
387 };
388 if next_hop_addr == *dest_node_addr && &next_hop_addr != previous_hop {
389 return TransitNextHopPlan::Route(next_hop_addr);
390 }
391
392 let now_ms = Self::now_ms();
393 let dest_coords = self
394 .coord_cache
395 .get_and_touch(dest_node_addr, now_ms)
396 .cloned();
397 let selected_strictly_progresses = dest_coords.as_ref().is_some_and(|dest_coords| {
398 self.tree_state.my_coords().root_id() == dest_coords.root_id()
399 && self
400 .tree_state
401 .peer_coords(&next_hop_addr)
402 .is_some_and(|peer_coords| {
403 peer_coords.distance_to(dest_coords)
404 < self.tree_state.my_coords().distance_to(dest_coords)
405 })
406 });
407
408 if &next_hop_addr != previous_hop && (dest_coords.is_none() || selected_strictly_progresses)
409 {
410 return TransitNextHopPlan::Route(next_hop_addr);
411 }
412
413 let coordinate_fallback = dest_coords.and_then(|dest_coords| {
414 self.select_tree_payload_candidate_avoiding(
415 &dest_coords,
416 dest_node_addr,
417 false,
418 Some(previous_hop),
419 )
420 });
421 if let Some(next_hop_addr) = coordinate_fallback {
422 return TransitNextHopPlan::Route(next_hop_addr);
423 }
424 TransitNextHopPlan::Loop(next_hop_addr)
425 }
426
427 #[cfg(test)]
428 pub(in crate::node) fn find_transit_next_hop(
429 &mut self,
430 dest_node_addr: &NodeAddr,
431 previous_hop: &NodeAddr,
432 ) -> Option<NodeAddr> {
433 match self.plan_transit_next_hop(dest_node_addr, previous_hop) {
434 TransitNextHopPlan::Route(next_hop_addr) => Some(next_hop_addr),
435 TransitNextHopPlan::Loop(next_hop_addr) => {
436 self.record_route_failure(*dest_node_addr, next_hop_addr);
437 None
438 }
439 TransitNextHopPlan::NoRoute => None,
440 }
441 }
442
443 pub(super) fn route_candidate_beats_direct(
444 &self,
445 healthy_direct_route: Option<NodeAddr>,
446 candidate_addr: NodeAddr,
447 ) -> bool {
448 let Some(direct_addr) = healthy_direct_route else {
449 return true;
450 };
451 if candidate_addr == direct_addr {
452 return false;
453 }
454
455 if !self.peers.contains_key(&direct_addr) {
456 return true;
457 }
458 if self.active_peer_uses_configured_static_udp_path(&direct_addr) {
459 return false;
460 }
461 let Some(candidate) = self.peers.get(&candidate_addr) else {
462 return false;
463 };
464 if !candidate.is_healthy() {
465 return false;
466 }
467
468 let direct_cost = self.dataplane_fmp_link_cost(&direct_addr);
469 let candidate_cost = self.dataplane_fmp_link_cost(&candidate_addr);
470 candidate_cost + ROUTING_FALLBACK_MIN_COST_ADVANTAGE < direct_cost
471 }
472
473 pub(super) fn select_tree_payload_candidate(
474 &self,
475 dest_coords: &crate::tree::TreeCoordinate,
476 direct_dest: &NodeAddr,
477 direct_payload_eligible: bool,
478 ) -> Option<NodeAddr> {
479 self.select_tree_payload_candidate_avoiding(
480 dest_coords,
481 direct_dest,
482 direct_payload_eligible,
483 None,
484 )
485 }
486
487 fn select_tree_payload_candidate_avoiding(
488 &self,
489 dest_coords: &crate::tree::TreeCoordinate,
490 direct_dest: &NodeAddr,
491 direct_payload_eligible: bool,
492 excluded: Option<&NodeAddr>,
493 ) -> Option<NodeAddr> {
494 if self.tree_state.my_coords().root_id() != dest_coords.root_id() {
495 return None;
496 }
497
498 let my_distance = self.tree_state.my_coords().distance_to(dest_coords);
499 let mut best: Option<(NodeAddr, usize)> = None;
500
501 for (peer_addr, peer) in &self.peers {
502 if excluded == Some(peer_addr) {
503 continue;
504 }
505 if peer_addr == direct_dest {
506 if !direct_payload_eligible {
507 continue;
508 }
509 } else if !peer.is_healthy() {
510 continue;
511 }
512
513 let Some(peer_coords) = self.tree_state.peer_coords(peer_addr) else {
514 continue;
515 };
516 let distance = peer_coords.distance_to(dest_coords);
517 if distance >= my_distance {
518 continue;
519 }
520
521 let dominated = match &best {
522 None => true,
523 Some((best_id, best_dist)) => {
524 distance < *best_dist || (distance == *best_dist && peer_addr < best_id)
525 }
526 };
527 if dominated {
528 best = Some((*peer_addr, distance));
529 }
530 }
531
532 best.map(|(peer_addr, _)| peer_addr)
533 }
534
535 pub(in crate::node) fn learn_reverse_route(
536 &mut self,
537 destination: NodeAddr,
538 next_hop: NodeAddr,
539 ) {
540 if self.config.node.routing.mode != RoutingMode::ReplyLearned
541 || destination == *self.node_addr()
542 {
543 return;
544 }
545 let now_ms = Self::now_ms();
546 self.learned_routes.learn(
547 destination,
548 next_hop,
549 now_ms,
550 self.config.node.routing.learned_ttl_secs,
551 self.config.node.routing.max_learned_routes_per_dest,
552 );
553 let _ = self.refresh_dataplane_fsp_owner_routes_retaining_current(&destination);
558 }
559
560 pub(in crate::node) fn pin_handshake_reverse_route(
561 &mut self,
562 destination: NodeAddr,
563 next_hop: NodeAddr,
564 ) {
565 if self.config.node.routing.mode != RoutingMode::ReplyLearned
566 || destination == *self.node_addr()
567 {
568 return;
569 }
570 self.learned_routes.pin_handshake_route(
571 destination,
572 next_hop,
573 Self::now_ms(),
574 self.config.node.routing.learned_ttl_secs,
575 self.config.node.routing.max_learned_routes_per_dest,
576 );
577 }
578
579 pub(in crate::node) fn pin_duplicate_handshake_reverse_route(
580 &mut self,
581 destination: NodeAddr,
582 next_hop: NodeAddr,
583 ) {
584 if self.config.node.routing.mode != RoutingMode::ReplyLearned
585 || destination == *self.node_addr()
586 {
587 return;
588 }
589 let now_ms = Self::now_ms();
590 if self
591 .learned_routes
592 .active_handshake_route(&destination, now_ms)
593 .is_some_and(|pinned_hop| pinned_hop != next_hop)
594 {
595 return;
596 }
597 self.learned_routes.pin_handshake_route(
598 destination,
599 next_hop,
600 now_ms,
601 self.config.node.routing.learned_ttl_secs,
602 self.config.node.routing.max_learned_routes_per_dest,
603 );
604 }
605
606 pub(in crate::node) fn routing_error_matches_active_path(
607 &mut self,
608 destination: &NodeAddr,
609 previous_hop: &NodeAddr,
610 ) -> bool {
611 if self.config.node.routing.mode != RoutingMode::ReplyLearned {
612 return true;
613 }
614
615 if let Some(last_outbound_next_hop) = self
621 .dataplane
622 .fsp_owner_activity(destination)
623 .and_then(|activity| activity.last_outbound_next_hop())
624 {
625 return last_outbound_next_hop == *previous_hop;
626 }
627
628 if let Some(pinned_hop) = self
629 .learned_routes
630 .active_handshake_route(destination, Self::now_ms())
631 {
632 return pinned_hop == *previous_hop;
636 }
637
638 self.dataplane
647 .fsp_owner_next_hop(destination)
648 .is_none_or(|next_hop| next_hop == *previous_hop)
649 }
650
651 pub(in crate::node) fn record_route_failure(
652 &mut self,
653 destination: NodeAddr,
654 next_hop: NodeAddr,
655 ) {
656 self.record_route_failure_inner(destination, next_hop, false);
657 }
658
659 pub(in crate::node) fn record_active_route_failure(
660 &mut self,
661 destination: NodeAddr,
662 next_hop: NodeAddr,
663 ) {
664 self.record_route_failure_inner(destination, next_hop, true);
665 }
666
667 pub(in crate::node) fn has_proven_alternate_session_route(
668 &self,
669 destination: &NodeAddr,
670 current_next_hop: &NodeAddr,
671 now_ms: u64,
672 ) -> bool {
673 if destination != current_next_hop
674 && self
675 .peers
676 .get(destination)
677 .is_some_and(|peer| peer.is_healthy() && peer.can_send())
678 && !self.session_direct_path_degradation_active(destination, now_ms)
679 && self.session_direct_path_has_recent_data_return(destination, now_ms)
680 {
681 return true;
682 }
683
684 self.learned_routes.has_sendable_alternate(
685 destination,
686 current_next_hop,
687 now_ms,
688 |next_hop| {
689 self.peers
690 .get(next_hop)
691 .is_some_and(|peer| peer.is_healthy() && peer.can_send())
692 },
693 )
694 }
695
696 fn record_route_failure_inner(
697 &mut self,
698 destination: NodeAddr,
699 next_hop: NodeAddr,
700 clear_failed_output: bool,
701 ) {
702 if self.config.node.routing.mode != RoutingMode::ReplyLearned {
703 return;
704 }
705 let current_next_hop = self.dataplane.fsp_owner_next_hop(&destination);
706 let _ = self.dataplane.forget_fsp_data_route(destination, next_hop);
707 self.learned_routes.quarantine_failed_next_hop(
708 destination,
709 next_hop,
710 Self::now_ms(),
711 self.config.node.routing.learned_ttl_secs,
712 self.config.node.routing.max_learned_routes_per_dest,
713 );
714
715 if !clear_failed_output || current_next_hop != Some(next_hop) {
716 return;
717 }
718
719 let replacement = self
720 .find_next_hop(&destination)
721 .map(|peer| *peer.node_addr())
722 .filter(|candidate| *candidate != next_hop);
723 let _ = self.refresh_dataplane_fsp_owner_routes_via(&destination, replacement);
724 if self.dataplane.fsp_owner_next_hop(&destination) == Some(next_hop) {
725 let _ = self.dataplane.clear_fsp_output_route(destination);
726 }
727 }
728
729 pub(crate) fn learned_route_table_snapshot(&self, now_ms: u64) -> LearnedRouteTableSnapshot {
730 self.learned_routes.snapshot(now_ms)
731 }
732
733 pub(in crate::node) fn purge_learned_routes(&mut self, now_ms: u64) {
734 self.learned_routes.purge_expired(now_ms);
735 }
736
737 pub(super) fn select_best_candidate<'a>(
746 &'a self,
747 candidates: &[&'a ActivePeer],
748 dest_coords: &crate::tree::TreeCoordinate,
749 ) -> Option<&'a ActivePeer> {
750 let my_distance = self.tree_state.my_coords().distance_to(dest_coords);
751
752 let mut best: Option<(&ActivePeer, f64, usize)> = None;
753
754 for &candidate in candidates {
755 if !candidate.can_send() {
756 continue;
757 }
758
759 let cost = self.dataplane_fmp_link_cost(candidate.node_addr());
760
761 let dist = self
762 .tree_state
763 .peer_coords(candidate.node_addr())
764 .map(|pc| pc.distance_to(dest_coords))
765 .unwrap_or(usize::MAX);
766
767 if dist >= my_distance {
770 continue;
771 }
772
773 let dominated = match &best {
774 None => true,
775 Some((_, best_cost, best_dist)) => {
776 cost < *best_cost
777 || (cost == *best_cost && dist < *best_dist)
778 || (cost == *best_cost
779 && dist == *best_dist
780 && candidate.node_addr() < best.as_ref().unwrap().0.node_addr())
781 }
782 };
783
784 if dominated {
785 best = Some((candidate, cost, dist));
786 }
787 }
788
789 best.map(|(peer, _, _)| peer)
790 }
791
792 pub fn destination_in_filters(&self, dest: &NodeAddr) -> Vec<&ActivePeer> {
794 self.peers.values().filter(|p| p.may_reach(dest)).collect()
795 }
796}