1use super::*;
2
3impl Node {
4 pub(crate) fn is_tree_peer(&self, peer_addr: &NodeAddr) -> bool {
11 if !self.tree_state.is_root() && self.tree_state.my_declaration().parent_id() == peer_addr {
13 return true;
14 }
15 if let Some(decl) = self.tree_state.peer_declaration(peer_addr)
17 && decl.parent_id() == self.node_addr()
18 {
19 return true;
20 }
21 false
22 }
23
24 pub fn find_next_hop(&mut self, dest_node_addr: &NodeAddr) -> Option<&ActivePeer> {
51 if dest_node_addr == self.node_addr() {
53 return None;
54 }
55 let now_ms = Self::now_ms();
56 let direct_session_degraded =
57 self.session_direct_path_blocks_direct_payload(dest_node_addr, now_ms);
58 let direct_session_untrusted = !direct_session_degraded
59 && self.session_direct_path_exclusive_trust_expired(dest_node_addr, now_ms);
60 let stale_traversal_direct_route = self
61 .peers
62 .get(dest_node_addr)
63 .filter(|peer| !peer.is_healthy() && peer.can_send())
64 .and_then(|_| {
65 self.configured_peer(dest_node_addr)
66 .and_then(|peer_config| {
67 (peer_config.is_auto_connect()
68 && self.active_peer_uses_traversal_path(dest_node_addr, peer_config))
69 .then_some(*dest_node_addr)
70 })
71 });
72
73 let healthy_direct_route = self
74 .peers
75 .get(dest_node_addr)
76 .filter(|peer| peer.is_healthy() && !direct_session_degraded)
77 .map(|_| *dest_node_addr);
78 if let Some(direct_addr) = healthy_direct_route
79 && !direct_session_untrusted
80 && self
81 .peers
82 .get(&direct_addr)
83 .is_some_and(|peer| peer.link_cost() <= 1.0 + ROUTING_FALLBACK_MIN_COST_ADVANTAGE)
84 {
85 return self.peers.get(&direct_addr);
86 }
87 let direct_payload_eligible = healthy_direct_route.is_some();
88 let payload_candidate_can_send = |addr: &NodeAddr, peer: &ActivePeer| {
89 if addr == dest_node_addr {
90 direct_payload_eligible
91 } else {
92 peer.is_healthy()
93 }
94 };
95
96 let fallback_beats_direct = |node: &Self, fallback_addr: NodeAddr| {
101 if direct_session_untrusted {
102 return healthy_direct_route != Some(fallback_addr)
103 && node
104 .peers
105 .get(&fallback_addr)
106 .is_some_and(|peer| peer.is_healthy());
107 }
108 node.route_candidate_beats_direct(healthy_direct_route, fallback_addr)
109 };
110
111 let sendable_learned_peers = if self.config.node.routing.mode == RoutingMode::ReplyLearned {
112 Some(
113 self.peers
114 .iter()
115 .filter(|(addr, peer)| payload_candidate_can_send(addr, peer))
116 .map(|(addr, _)| *addr)
117 .collect::<HashSet<_>>(),
118 )
119 } else {
120 None
121 };
122
123 let explore_fallback = sendable_learned_peers.as_ref().is_some_and(|sendable| {
130 self.learned_routes.should_explore_fallback(
131 dest_node_addr,
132 now_ms,
133 self.config.node.routing.learned_fallback_explore_interval,
134 |addr| sendable.contains(addr),
135 )
136 });
137 if let Some(sendable) = &sendable_learned_peers
138 && !explore_fallback
139 {
140 let eligible = sendable
141 .iter()
142 .copied()
143 .filter(|addr| fallback_beats_direct(self, *addr))
144 .collect::<HashSet<_>>();
145 if !eligible.is_empty()
146 && let Some(next_hop_addr) =
147 self.learned_routes
148 .select_next_hop(dest_node_addr, now_ms, |addr| eligible.contains(addr))
149 {
150 return self.peers.get(&next_hop_addr);
151 }
152 }
153
154 let Some(dest_coords) = self
156 .coord_cache
157 .get_and_touch(dest_node_addr, now_ms)
158 .cloned()
159 else {
160 if (healthy_direct_route.is_none() || explore_fallback)
161 && let Some(sendable) = &sendable_learned_peers
162 && let Some(next_hop_addr) =
163 self.learned_routes
164 .select_next_hop(dest_node_addr, now_ms, |addr| sendable.contains(addr))
165 {
166 return self.peers.get(&next_hop_addr);
167 }
168 if let Some(direct_addr) = healthy_direct_route {
169 return self.peers.get(&direct_addr);
170 }
171 if let Some(direct_addr) = stale_traversal_direct_route {
172 return self.peers.get(&direct_addr);
173 }
174 return None;
175 };
176
177 let coordinate_route_addr = {
180 let candidates: Vec<&ActivePeer> = self
181 .peers
182 .iter()
183 .filter(|(addr, peer)| {
184 payload_candidate_can_send(addr, peer) && peer.may_reach(dest_node_addr)
185 })
186 .map(|(_, peer)| peer)
187 .collect();
188 if !candidates.is_empty() {
189 self.select_best_candidate(&candidates, &dest_coords)
190 .map(|peer| *peer.node_addr())
191 } else {
192 None
193 }
194 };
195 if let Some(next_hop_addr) = coordinate_route_addr
196 && fallback_beats_direct(self, next_hop_addr)
197 {
198 return self.peers.get(&next_hop_addr);
199 }
200
201 let tree_route_addr = self.select_tree_payload_candidate(
203 &dest_coords,
204 dest_node_addr,
205 direct_payload_eligible,
206 );
207 if let Some(next_hop_addr) = tree_route_addr
208 && fallback_beats_direct(self, next_hop_addr)
209 {
210 return self.peers.get(&next_hop_addr);
211 }
212
213 if explore_fallback
214 && let Some(peer) = sendable_learned_peers.as_ref().and_then(|sendable| {
215 self.learned_routes
216 .select_next_hop(dest_node_addr, now_ms, |addr| sendable.contains(addr))
217 .and_then(|next_hop_addr| self.peers.get(&next_hop_addr))
218 })
219 {
220 return Some(peer);
221 }
222
223 if let Some(direct_addr) = healthy_direct_route {
224 return self.peers.get(&direct_addr);
225 }
226
227 if let Some(sendable) = &sendable_learned_peers
228 && let Some(next_hop_addr) =
229 self.learned_routes
230 .select_next_hop(dest_node_addr, now_ms, |addr| sendable.contains(addr))
231 {
232 return self.peers.get(&next_hop_addr);
233 }
234
235 if let Some(direct_addr) = stale_traversal_direct_route {
236 return self.peers.get(&direct_addr);
237 }
238
239 None
240 }
241
242 pub(in crate::node) fn find_transit_next_hop(
243 &mut self,
244 dest_node_addr: &NodeAddr,
245 previous_hop: &NodeAddr,
246 ) -> Option<NodeAddr> {
247 if dest_node_addr == self.node_addr() {
248 return None;
249 }
250
251 if dest_node_addr != previous_hop
252 && self
253 .peers
254 .get(dest_node_addr)
255 .is_some_and(|peer| peer.is_healthy())
256 {
257 return Some(*dest_node_addr);
258 }
259
260 let next_hop_addr = *self.find_next_hop(dest_node_addr)?.node_addr();
261 if &next_hop_addr == previous_hop {
262 self.record_route_failure(*dest_node_addr, next_hop_addr);
263 return None;
264 }
265 Some(next_hop_addr)
266 }
267
268 pub(super) fn route_candidate_beats_direct(
269 &self,
270 healthy_direct_route: Option<NodeAddr>,
271 candidate_addr: NodeAddr,
272 ) -> bool {
273 let Some(direct_addr) = healthy_direct_route else {
274 return true;
275 };
276 if candidate_addr == direct_addr {
277 return false;
278 }
279
280 let Some(direct) = self.peers.get(&direct_addr) else {
281 return true;
282 };
283 if self.active_peer_uses_configured_static_udp_path(&direct_addr) {
284 return false;
285 }
286 let Some(candidate) = self.peers.get(&candidate_addr) else {
287 return false;
288 };
289 if !candidate.is_healthy() {
290 return false;
291 }
292
293 let direct_cost = direct.link_cost();
294 let candidate_cost = candidate.link_cost();
295 candidate_cost + ROUTING_FALLBACK_MIN_COST_ADVANTAGE < direct_cost
296 }
297
298 pub(super) fn select_tree_payload_candidate(
299 &self,
300 dest_coords: &crate::tree::TreeCoordinate,
301 direct_dest: &NodeAddr,
302 direct_payload_eligible: bool,
303 ) -> Option<NodeAddr> {
304 if self.tree_state.my_coords().root_id() != dest_coords.root_id() {
305 return None;
306 }
307
308 let my_distance = self.tree_state.my_coords().distance_to(dest_coords);
309 let mut best: Option<(NodeAddr, usize)> = None;
310
311 for (peer_addr, peer) in &self.peers {
312 if peer_addr == direct_dest {
313 if !direct_payload_eligible {
314 continue;
315 }
316 } else if !peer.is_healthy() {
317 continue;
318 }
319
320 let Some(peer_coords) = self.tree_state.peer_coords(peer_addr) else {
321 continue;
322 };
323 let distance = peer_coords.distance_to(dest_coords);
324 if distance >= my_distance {
325 continue;
326 }
327
328 let dominated = match &best {
329 None => true,
330 Some((best_id, best_dist)) => {
331 distance < *best_dist || (distance == *best_dist && peer_addr < best_id)
332 }
333 };
334 if dominated {
335 best = Some((*peer_addr, distance));
336 }
337 }
338
339 best.map(|(peer_addr, _)| peer_addr)
340 }
341
342 pub(in crate::node) fn session_direct_path_is_degraded(
343 &mut self,
344 dest: &NodeAddr,
345 now_ms: u64,
346 ) -> bool {
347 self.session_direct_degradation.is_degraded(dest, now_ms)
348 }
349
350 pub(in crate::node) fn session_direct_path_blocks_direct_payload(
351 &mut self,
352 dest: &NodeAddr,
353 now_ms: u64,
354 ) -> bool {
355 self.session_direct_path_is_degraded(dest, now_ms)
356 && !self.active_peer_uses_configured_static_udp_path(dest)
357 }
358
359 pub(in crate::node) fn session_direct_path_exclusive_trust_timeout_ms(&self) -> u64 {
360 self.config
361 .node
362 .heartbeat_interval_secs
363 .saturating_mul(1000)
364 .saturating_add(1_500)
365 .max(SESSION_DIRECT_MIN_EXCLUSIVE_TRUST_MS)
366 }
367
368 pub(in crate::node) fn session_direct_path_exclusive_trust_expired(
369 &self,
370 dest: &NodeAddr,
371 now_ms: u64,
372 ) -> bool {
373 if !self
374 .peers
375 .get(dest)
376 .is_some_and(|peer| peer.is_healthy() && peer.can_send())
377 {
378 return false;
379 }
380 let Some(session) = self.sessions.get(dest) else {
381 return false;
382 };
383 if !session.is_established() {
384 return false;
385 }
386 session.has_recent_outbound_without_inbound(
387 now_ms,
388 self.session_direct_path_exclusive_trust_timeout_ms(),
389 )
390 }
391
392 pub(in crate::node) fn mark_session_direct_path_degraded(
393 &mut self,
394 dest: NodeAddr,
395 now_ms: u64,
396 ) -> bool {
397 self.session_direct_degradation
398 .mark_degraded(dest, now_ms, SESSION_DIRECT_DEGRADED_HOLD_MS)
399 }
400
401 pub(in crate::node) fn clear_session_direct_path_degraded(&mut self, dest: &NodeAddr) -> bool {
402 self.session_direct_degradation.clear(dest)
403 }
404
405 pub(in crate::node) fn clear_session_direct_path_degraded_after_promotion(
406 &mut self,
407 dest: &NodeAddr,
408 now_ms: u64,
409 ) {
410 let keep_degraded = self.session_direct_path_blocks_direct_payload(dest, now_ms);
411 if keep_degraded {
412 debug!(
413 peer = %self.peer_display_name(dest),
414 "Keeping direct payload degraded after direct-path promotion"
415 );
416 } else {
417 self.clear_session_direct_path_degraded(dest);
418 }
419 }
420
421 pub(in crate::node) fn learn_reverse_route(
422 &mut self,
423 destination: NodeAddr,
424 next_hop: NodeAddr,
425 ) {
426 if self.config.node.routing.mode != RoutingMode::ReplyLearned
427 || destination == *self.node_addr()
428 {
429 return;
430 }
431 let now_ms = Self::now_ms();
432 self.learned_routes.learn(
433 destination,
434 next_hop,
435 now_ms,
436 self.config.node.routing.learned_ttl_secs,
437 self.config.node.routing.max_learned_routes_per_dest,
438 );
439 }
440
441 pub(in crate::node) fn record_route_failure(
442 &mut self,
443 destination: NodeAddr,
444 next_hop: NodeAddr,
445 ) {
446 if self.config.node.routing.mode != RoutingMode::ReplyLearned {
447 return;
448 }
449 self.learned_routes.record_failure(&destination, &next_hop);
450 }
451
452 pub(crate) fn learned_route_table_snapshot(&self, now_ms: u64) -> LearnedRouteTableSnapshot {
453 self.learned_routes.snapshot(now_ms)
454 }
455
456 pub(in crate::node) fn purge_learned_routes(&mut self, now_ms: u64) {
457 self.learned_routes.purge_expired(now_ms);
458 }
459
460 pub(super) fn select_best_candidate<'a>(
469 &'a self,
470 candidates: &[&'a ActivePeer],
471 dest_coords: &crate::tree::TreeCoordinate,
472 ) -> Option<&'a ActivePeer> {
473 let my_distance = self.tree_state.my_coords().distance_to(dest_coords);
474
475 let mut best: Option<(&ActivePeer, f64, usize)> = None;
476
477 for &candidate in candidates {
478 if !candidate.can_send() {
479 continue;
480 }
481
482 let cost = candidate.link_cost();
483
484 let dist = self
485 .tree_state
486 .peer_coords(candidate.node_addr())
487 .map(|pc| pc.distance_to(dest_coords))
488 .unwrap_or(usize::MAX);
489
490 if dist >= my_distance {
493 continue;
494 }
495
496 let dominated = match &best {
497 None => true,
498 Some((_, best_cost, best_dist)) => {
499 cost < *best_cost
500 || (cost == *best_cost && dist < *best_dist)
501 || (cost == *best_cost
502 && dist == *best_dist
503 && candidate.node_addr() < best.as_ref().unwrap().0.node_addr())
504 }
505 };
506
507 if dominated {
508 best = Some((candidate, cost, dist));
509 }
510 }
511
512 best.map(|(peer, _, _)| peer)
513 }
514
515 pub fn destination_in_filters(&self, dest: &NodeAddr) -> Vec<&ActivePeer> {
517 self.peers.values().filter(|p| p.may_reach(dest)).collect()
518 }
519}