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