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