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> {
50 if dest_node_addr == self.node_addr() {
52 return None;
53 }
54 let now_ms = Self::now_ms();
55 let direct_session_degraded =
56 self.session_direct_path_blocks_direct_payload(dest_node_addr, now_ms);
57
58 let healthy_direct_route = self
59 .peers
60 .get(dest_node_addr)
61 .filter(|peer| peer.is_healthy() && !direct_session_degraded)
62 .map(|_| *dest_node_addr);
63 if let Some(direct_addr) = healthy_direct_route
64 && self
65 .peers
66 .get(&direct_addr)
67 .is_some_and(|peer| peer.link_cost() <= 1.0 + ROUTING_FALLBACK_MIN_COST_ADVANTAGE)
68 {
69 return self.peers.get(&direct_addr);
70 }
71 let direct_payload_eligible = healthy_direct_route.is_some();
72 let payload_candidate_can_send = |addr: &NodeAddr, peer: &ActivePeer| {
73 if addr == dest_node_addr {
74 direct_payload_eligible
75 } else {
76 peer.is_healthy()
77 }
78 };
79
80 let fallback_beats_direct = |node: &Self, fallback_addr: NodeAddr| {
85 node.route_candidate_beats_direct(healthy_direct_route, fallback_addr)
86 };
87
88 let sendable_learned_peers = if self.config.node.routing.mode == RoutingMode::ReplyLearned {
89 Some(
90 self.peers
91 .iter()
92 .filter(|(addr, peer)| payload_candidate_can_send(addr, peer))
93 .map(|(addr, _)| *addr)
94 .collect::<HashSet<_>>(),
95 )
96 } else {
97 None
98 };
99
100 let explore_fallback = sendable_learned_peers.as_ref().is_some_and(|sendable| {
107 self.learned_routes.should_explore_fallback(
108 dest_node_addr,
109 now_ms,
110 self.config.node.routing.learned_fallback_explore_interval,
111 |addr| sendable.contains(addr),
112 )
113 });
114 if let Some(sendable) = &sendable_learned_peers
115 && !explore_fallback
116 {
117 let eligible = sendable
118 .iter()
119 .copied()
120 .filter(|addr| fallback_beats_direct(self, *addr))
121 .collect::<HashSet<_>>();
122 if !eligible.is_empty()
123 && let Some(next_hop_addr) =
124 self.learned_routes
125 .select_next_hop(dest_node_addr, now_ms, |addr| eligible.contains(addr))
126 {
127 return self.peers.get(&next_hop_addr);
128 }
129 }
130
131 let Some(dest_coords) = self
133 .coord_cache
134 .get_and_touch(dest_node_addr, now_ms)
135 .cloned()
136 else {
137 if (healthy_direct_route.is_none() || explore_fallback)
138 && let Some(sendable) = &sendable_learned_peers
139 && let Some(next_hop_addr) =
140 self.learned_routes
141 .select_next_hop(dest_node_addr, now_ms, |addr| sendable.contains(addr))
142 {
143 return self.peers.get(&next_hop_addr);
144 }
145 if let Some(direct_addr) = healthy_direct_route {
146 return self.peers.get(&direct_addr);
147 }
148 return None;
149 };
150
151 let coordinate_route_addr = {
154 let candidates: Vec<&ActivePeer> = self
155 .peers
156 .iter()
157 .filter(|(addr, peer)| {
158 payload_candidate_can_send(addr, peer) && peer.may_reach(dest_node_addr)
159 })
160 .map(|(_, peer)| peer)
161 .collect();
162 if !candidates.is_empty() {
163 self.select_best_candidate(&candidates, &dest_coords)
164 .map(|peer| *peer.node_addr())
165 } else {
166 None
167 }
168 };
169 if let Some(next_hop_addr) = coordinate_route_addr
170 && fallback_beats_direct(self, next_hop_addr)
171 {
172 return self.peers.get(&next_hop_addr);
173 }
174
175 let tree_route_addr = self.select_tree_payload_candidate(
177 &dest_coords,
178 dest_node_addr,
179 direct_payload_eligible,
180 );
181 if let Some(next_hop_addr) = tree_route_addr
182 && fallback_beats_direct(self, next_hop_addr)
183 {
184 return self.peers.get(&next_hop_addr);
185 }
186
187 if explore_fallback {
188 return sendable_learned_peers.as_ref().and_then(|sendable| {
189 self.learned_routes
190 .select_next_hop(dest_node_addr, now_ms, |addr| sendable.contains(addr))
191 .and_then(|next_hop_addr| self.peers.get(&next_hop_addr))
192 });
193 }
194
195 if let Some(direct_addr) = healthy_direct_route {
196 return self.peers.get(&direct_addr);
197 }
198
199 if let Some(sendable) = &sendable_learned_peers
200 && let Some(next_hop_addr) =
201 self.learned_routes
202 .select_next_hop(dest_node_addr, now_ms, |addr| sendable.contains(addr))
203 {
204 return self.peers.get(&next_hop_addr);
205 }
206
207 None
208 }
209
210 pub(in crate::node) fn find_transit_next_hop(
211 &mut self,
212 dest_node_addr: &NodeAddr,
213 previous_hop: &NodeAddr,
214 ) -> Option<NodeAddr> {
215 if dest_node_addr == self.node_addr() {
216 return None;
217 }
218
219 if dest_node_addr != previous_hop
220 && self
221 .peers
222 .get(dest_node_addr)
223 .is_some_and(|peer| peer.is_healthy())
224 {
225 return Some(*dest_node_addr);
226 }
227
228 let next_hop_addr = *self.find_next_hop(dest_node_addr)?.node_addr();
229 if &next_hop_addr == previous_hop {
230 self.record_route_failure(*dest_node_addr, next_hop_addr);
231 return None;
232 }
233 Some(next_hop_addr)
234 }
235
236 pub(super) fn route_candidate_beats_direct(
237 &self,
238 healthy_direct_route: Option<NodeAddr>,
239 candidate_addr: NodeAddr,
240 ) -> bool {
241 let Some(direct_addr) = healthy_direct_route else {
242 return true;
243 };
244 if candidate_addr == direct_addr {
245 return false;
246 }
247
248 let Some(direct) = self.peers.get(&direct_addr) else {
249 return true;
250 };
251 if self.active_peer_uses_configured_static_udp_path(&direct_addr) {
252 return false;
253 }
254 let Some(candidate) = self.peers.get(&candidate_addr) else {
255 return false;
256 };
257 if !candidate.is_healthy() {
258 return false;
259 }
260
261 let direct_cost = direct.link_cost();
262 let candidate_cost = candidate.link_cost();
263 candidate_cost + ROUTING_FALLBACK_MIN_COST_ADVANTAGE < direct_cost
264 }
265
266 pub(super) fn select_tree_payload_candidate(
267 &self,
268 dest_coords: &crate::tree::TreeCoordinate,
269 direct_dest: &NodeAddr,
270 direct_payload_eligible: bool,
271 ) -> Option<NodeAddr> {
272 if self.tree_state.my_coords().root_id() != dest_coords.root_id() {
273 return None;
274 }
275
276 let my_distance = self.tree_state.my_coords().distance_to(dest_coords);
277 let mut best: Option<(NodeAddr, usize)> = None;
278
279 for (peer_addr, peer) in &self.peers {
280 if peer_addr == direct_dest {
281 if !direct_payload_eligible {
282 continue;
283 }
284 } else if !peer.is_healthy() {
285 continue;
286 }
287
288 let Some(peer_coords) = self.tree_state.peer_coords(peer_addr) else {
289 continue;
290 };
291 let distance = peer_coords.distance_to(dest_coords);
292 if distance >= my_distance {
293 continue;
294 }
295
296 let dominated = match &best {
297 None => true,
298 Some((best_id, best_dist)) => {
299 distance < *best_dist || (distance == *best_dist && peer_addr < best_id)
300 }
301 };
302 if dominated {
303 best = Some((*peer_addr, distance));
304 }
305 }
306
307 best.map(|(peer_addr, _)| peer_addr)
308 }
309
310 pub(in crate::node) fn session_direct_path_is_degraded(
311 &mut self,
312 dest: &NodeAddr,
313 now_ms: u64,
314 ) -> bool {
315 self.session_direct_degradation.is_degraded(dest, now_ms)
316 }
317
318 pub(in crate::node) fn session_direct_path_blocks_direct_payload(
319 &mut self,
320 dest: &NodeAddr,
321 now_ms: u64,
322 ) -> bool {
323 self.session_direct_path_is_degraded(dest, now_ms)
324 && !self.active_peer_uses_configured_static_udp_path(dest)
325 }
326
327 pub(in crate::node) fn mark_session_direct_path_degraded(
328 &mut self,
329 dest: NodeAddr,
330 now_ms: u64,
331 ) -> bool {
332 self.session_direct_degradation
333 .mark_degraded(dest, now_ms, SESSION_DIRECT_DEGRADED_HOLD_MS)
334 }
335
336 pub(in crate::node) fn clear_session_direct_path_degraded(&mut self, dest: &NodeAddr) -> bool {
337 self.session_direct_degradation.clear(dest)
338 }
339
340 pub(in crate::node) fn learn_reverse_route(
341 &mut self,
342 destination: NodeAddr,
343 next_hop: NodeAddr,
344 ) {
345 if self.config.node.routing.mode != RoutingMode::ReplyLearned
346 || destination == *self.node_addr()
347 {
348 return;
349 }
350 let now_ms = Self::now_ms();
351 self.learned_routes.learn(
352 destination,
353 next_hop,
354 now_ms,
355 self.config.node.routing.learned_ttl_secs,
356 self.config.node.routing.max_learned_routes_per_dest,
357 );
358 }
359
360 pub(in crate::node) fn record_route_failure(
361 &mut self,
362 destination: NodeAddr,
363 next_hop: NodeAddr,
364 ) {
365 if self.config.node.routing.mode != RoutingMode::ReplyLearned {
366 return;
367 }
368 self.learned_routes.record_failure(&destination, &next_hop);
369 }
370
371 pub(crate) fn learned_route_table_snapshot(&self, now_ms: u64) -> LearnedRouteTableSnapshot {
372 self.learned_routes.snapshot(now_ms)
373 }
374
375 pub(in crate::node) fn purge_learned_routes(&mut self, now_ms: u64) {
376 self.learned_routes.purge_expired(now_ms);
377 }
378
379 pub(super) fn select_best_candidate<'a>(
388 &'a self,
389 candidates: &[&'a ActivePeer],
390 dest_coords: &crate::tree::TreeCoordinate,
391 ) -> Option<&'a ActivePeer> {
392 let my_distance = self.tree_state.my_coords().distance_to(dest_coords);
393
394 let mut best: Option<(&ActivePeer, f64, usize)> = None;
395
396 for &candidate in candidates {
397 if !candidate.can_send() {
398 continue;
399 }
400
401 let cost = candidate.link_cost();
402
403 let dist = self
404 .tree_state
405 .peer_coords(candidate.node_addr())
406 .map(|pc| pc.distance_to(dest_coords))
407 .unwrap_or(usize::MAX);
408
409 if dist >= my_distance {
412 continue;
413 }
414
415 let dominated = match &best {
416 None => true,
417 Some((_, best_cost, best_dist)) => {
418 cost < *best_cost
419 || (cost == *best_cost && dist < *best_dist)
420 || (cost == *best_cost
421 && dist == *best_dist
422 && candidate.node_addr() < best.as_ref().unwrap().0.node_addr())
423 }
424 };
425
426 if dominated {
427 best = Some((candidate, cost, dist));
428 }
429 }
430
431 best.map(|(peer, _, _)| peer)
432 }
433
434 pub fn destination_in_filters(&self, dest: &NodeAddr) -> Vec<&ActivePeer> {
436 self.peers.values().filter(|p| p.may_reach(dest)).collect()
437 }
438}