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