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