1use super::*;
2
3impl TransportEngine {
4 pub fn new(config: TransportConfig) -> Self {
5 let packet_hashlist_max_entries = config.packet_hashlist_max_entries;
6 let packet_hashlist_allocation = config.packet_hashlist_allocation;
7 let sig_cache_max = if config.announce_sig_cache_enabled {
8 config.announce_sig_cache_max_entries
9 } else {
10 0
11 };
12 let sig_cache_ttl = config.announce_sig_cache_ttl_secs;
13 let announce_queue_max_interfaces = config.announce_queue_max_interfaces;
14 TransportEngine {
15 config,
16 path_table: BTreeMap::new(),
17 announce_table: BTreeMap::new(),
18 reverse_table: tables::ReverseTable::default(),
19 link_table: BTreeMap::new(),
20 held_announces: BTreeMap::new(),
21 packet_hashlist: PacketHashlist::with_allocation(
22 packet_hashlist_max_entries,
23 packet_hashlist_allocation,
24 ),
25 announce_sig_cache: AnnounceSignatureCache::new(sig_cache_max, sig_cache_ttl),
26 rate_limiter: AnnounceRateLimiter::new(),
27 path_states: BTreeMap::new(),
28 interfaces: BTreeMap::new(),
29 interface_hashes: BTreeMap::new(),
30 local_destinations: BTreeMap::new(),
31 blackholed_identities: BTreeMap::new(),
32 announce_queues: AnnounceQueues::new(announce_queue_max_interfaces),
33 ingress_control: IngressControl::new(),
34 tunnel_table: TunnelTable::new(),
35 discovery_pr_tags: VecDeque::new(),
36 discovery_pr_tag_set: BTreeSet::new(),
37 path_requests: BTreeMap::new(),
38 discovery_path_requests: BTreeMap::new(),
39 discovery_path_request_deadlines: BTreeMap::new(),
40 path_destination_cap_evict_count: 0,
41 announces_last_checked: 0.0,
42 tables_last_culled: 0.0,
43 }
44 }
45
46 pub fn register_interface(&mut self, info: InterfaceInfo) {
51 self.interface_hashes
52 .insert(info.id, hash::full_hash(info.name.as_bytes()));
53 self.interfaces.insert(info.id, info);
54 }
55
56 pub fn deregister_interface(&mut self, id: InterfaceId) {
57 self.interfaces.remove(&id);
58 self.interface_hashes.remove(&id);
59 self.drop_paths_for_interface(id);
60 self.drop_reverse_for_interface(id);
61 self.drop_links_for_interface(id);
62 self.announce_queues.remove_interface(id);
63 self.ingress_control.remove_interface(&id);
64 }
65
66 pub fn register_destination(&mut self, dest_hash: [u8; 16], dest_type: u8) {
71 self.local_destinations.insert(dest_hash, dest_type);
72 }
73
74 pub fn deregister_destination(&mut self, dest_hash: &[u8; 16]) {
75 self.local_destinations.remove(dest_hash);
76 }
77
78 pub fn has_path(&self, dest_hash: &[u8; 16]) -> bool {
83 self.path_table
84 .get(dest_hash)
85 .is_some_and(|ps| !ps.is_empty())
86 }
87
88 pub fn hops_to(&self, dest_hash: &[u8; 16]) -> Option<u8> {
89 self.path_table
90 .get(dest_hash)
91 .and_then(|ps| ps.primary())
92 .map(|e| e.hops)
93 }
94
95 pub fn next_hop(&self, dest_hash: &[u8; 16]) -> Option<[u8; 16]> {
96 self.path_table
97 .get(dest_hash)
98 .and_then(|ps| ps.primary())
99 .map(|e| e.next_hop)
100 }
101
102 pub fn next_hop_interface(&self, dest_hash: &[u8; 16]) -> Option<InterfaceId> {
103 self.path_table
104 .get(dest_hash)
105 .and_then(|ps| ps.primary())
106 .map(|e| e.receiving_interface)
107 }
108
109 pub fn mark_path_unresponsive(
119 &mut self,
120 dest_hash: &[u8; 16],
121 receiving_interface: Option<InterfaceId>,
122 ) {
123 if let Some(iface_id) = receiving_interface {
124 if let Some(info) = self.interfaces.get(&iface_id) {
125 if info.mode == constants::MODE_BOUNDARY {
126 return;
127 }
128 }
129 }
130
131 if let Some(ps) = self.path_table.get_mut(dest_hash) {
133 if ps.len() > 1 {
134 ps.failover(false); self.path_states.remove(dest_hash);
137 return;
138 }
139 }
140
141 self.path_states
142 .insert(*dest_hash, constants::STATE_UNRESPONSIVE);
143 }
144
145 pub fn mark_path_responsive(&mut self, dest_hash: &[u8; 16]) {
146 self.path_states
147 .insert(*dest_hash, constants::STATE_RESPONSIVE);
148 }
149
150 pub fn path_is_unresponsive(&self, dest_hash: &[u8; 16]) -> bool {
151 self.path_states.get(dest_hash) == Some(&constants::STATE_UNRESPONSIVE)
152 }
153
154 pub fn expire_path(&mut self, dest_hash: &[u8; 16]) {
155 if let Some(ps) = self.path_table.get_mut(dest_hash) {
156 ps.expire_all();
157 }
158 }
159
160 pub fn register_link(&mut self, link_id: [u8; 16], entry: LinkEntry) {
165 self.link_table.insert(link_id, entry);
166 }
167
168 pub fn validate_link(&mut self, link_id: &[u8; 16]) {
169 if let Some(entry) = self.link_table.get_mut(link_id) {
170 entry.validated = true;
171 }
172 }
173
174 pub fn remove_link(&mut self, link_id: &[u8; 16]) {
175 self.link_table.remove(link_id);
176 }
177
178 pub fn link_rebalance_destination(
181 &self,
182 link_id: &[u8; 16],
183 packet_hops: u8,
184 receiving_interface: InterfaceId,
185 ) -> Option<[u8; 16]> {
186 let entry = self.link_table.get(link_id)?;
187 (self.config.transport_enabled
188 && !entry.validated
189 && packet_hops != entry.remaining_hops
190 && receiving_interface == entry.next_hop_interface)
191 .then_some(entry.destination_hash)
192 }
193
194 pub fn inbound_lrproof_rebalance_candidate(
198 &self,
199 raw: &[u8],
200 receiving_interface: InterfaceId,
201 ) -> Option<super::LrproofRebalanceCandidate> {
202 let decoded = DecodedPacket::unpack(raw).ok()?;
203 self.decoded_lrproof_rebalance_candidate(&decoded, receiving_interface)
204 }
205
206 pub fn decoded_lrproof_rebalance_candidate(
207 &self,
208 decoded: &DecodedPacket,
209 receiving_interface: InterfaceId,
210 ) -> Option<super::LrproofRebalanceCandidate> {
211 let packet = &decoded.packet;
212 let (hops, _) = self.filtered_inbound_hops(packet, receiving_interface)?;
213 if packet.flags.packet_type != constants::PACKET_TYPE_PROOF
214 || packet.context != constants::CONTEXT_LRPROOF
215 {
216 return None;
217 }
218 let link_id = packet.destination_hash;
219 let destination_hash =
220 self.link_rebalance_destination(&link_id, hops, receiving_interface)?;
221 Some((link_id, destination_hash, hops, packet.data().to_vec()))
222 }
223
224 pub fn rebalance_link_path_from_lrproof(
232 &mut self,
233 link_id: &[u8; 16],
234 packet_hops: u8,
235 receiving_interface: InterfaceId,
236 proof_data: &[u8],
237 destination_sig_pub_bytes: &[u8; 32],
238 ) -> bool {
239 if self
240 .link_rebalance_destination(link_id, packet_hops, receiving_interface)
241 .is_none()
242 {
243 return false;
244 }
245
246 let Ok(destination_sig_pub) =
247 rns_crypto::ed25519::Ed25519PublicKey::from_bytes(destination_sig_pub_bytes)
248 else {
249 return false;
250 };
251 if crate::link::handshake::validate_lrproof(
252 proof_data,
253 link_id,
254 &destination_sig_pub,
255 destination_sig_pub_bytes,
256 )
257 .is_err()
258 {
259 return false;
260 }
261
262 let destination_hash = match self.link_table.get_mut(link_id) {
263 Some(entry) if !entry.validated => {
264 entry.remaining_hops = packet_hops;
265 entry.destination_hash
266 }
267 _ => return false,
268 };
269 if let Some(paths) = self.path_table.get_mut(&destination_hash) {
270 paths.update_primary_hops(packet_hops);
271 }
272 true
273 }
274
275 pub fn rebalance_destination_path_hops(
277 &mut self,
278 destination_hash: &[u8; 16],
279 packet_hops: u8,
280 ) -> bool {
281 self.path_table
282 .get_mut(destination_hash)
283 .is_some_and(|paths| paths.update_primary_hops(packet_hops))
284 }
285
286 pub fn blackhole_identity(
297 &mut self,
298 identity_hash: [u8; 16],
299 now: f64,
300 duration_hours: Option<f64>,
301 reason: Option<String>,
302 ) {
303 let expires = match duration_hours {
304 Some(h) if h > 0.0 => now + h * 3600.0,
305 _ => 0.0, };
307 self.blackholed_identities.insert(
308 identity_hash,
309 BlackholeEntry {
310 created: now,
311 expires,
312 reason,
313 },
314 );
315 }
316
317 pub fn unblackhole_identity(&mut self, identity_hash: &[u8; 16]) -> bool {
322 self.blackholed_identities.remove(identity_hash).is_some()
323 }
324
325 pub fn is_blackholed(&self, identity_hash: &[u8; 16], now: f64) -> bool {
327 if let Some(entry) = self.blackholed_identities.get(identity_hash) {
328 if entry.expires == 0.0 || entry.expires > now {
329 return true;
330 }
331 }
332 false
333 }
334
335 pub fn blackholed_entries(&self) -> impl Iterator<Item = (&[u8; 16], &BlackholeEntry)> {
337 self.blackholed_identities.iter()
338 }
339
340 pub(super) fn cull_blackholed(&mut self, now: f64) {
342 self.blackholed_identities
343 .retain(|_, entry| entry.expires == 0.0 || entry.expires > now);
344 }
345
346 pub fn handle_tunnel(
354 &mut self,
355 tunnel_id: [u8; 32],
356 interface: InterfaceId,
357 now: f64,
358 ) -> Vec<TransportAction> {
359 let mut actions = Vec::new();
360 let reattaching = self.tunnel_table.get(&tunnel_id).is_some();
361 if reattaching {
362 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
363 "Tunnel endpoint {:02x?} reappeared on interface {}; restoring paths",
364 &tunnel_id[..4],
365 interface.0,
366 );
367 } else {
368 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
369 "Tunnel endpoint {:02x?} established on interface {}",
370 &tunnel_id[..4],
371 interface.0,
372 );
373 }
374
375 if let Some(info) = self.interfaces.get_mut(&interface) {
377 info.tunnel_id = Some(tunnel_id);
378 }
379
380 let restored_paths = self.tunnel_table.handle_tunnel(
381 tunnel_id,
382 interface,
383 now,
384 self.config.destination_timeout_secs,
385 );
386
387 for (dest_hash, tunnel_path) in &restored_paths {
389 let should_restore = match self.path_table.get(dest_hash).and_then(|ps| ps.primary()) {
390 Some(existing) => {
391 if tunnel_path.hops <= existing.hops || existing.expires < now {
394 let existing_timebase = timebase_from_random_blobs(&existing.random_blobs);
395 let tunnel_timebase = timebase_from_random_blobs(&tunnel_path.random_blobs);
396 tunnel_timebase >= existing_timebase
397 } else {
398 false
399 }
400 }
401 None => now < tunnel_path.expires,
402 };
403
404 if should_restore {
405 let entry = PathEntry {
406 timestamp: tunnel_path.timestamp,
407 next_hop: tunnel_path.received_from,
408 hops: tunnel_path.hops,
409 expires: tunnel_path.expires,
410 random_blobs: tunnel_path.random_blobs.clone(),
411 receiving_interface: interface,
412 packet_hash: tunnel_path.packet_hash,
413 announce_raw: None,
414 };
415 self.upsert_path_destination(*dest_hash, entry, now);
416 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
417 "Restored tunnel path to {:02x?}: hops={} via={:02x?} interface={}",
418 &dest_hash[..4],
419 tunnel_path.hops,
420 &tunnel_path.received_from[..4],
421 interface.0,
422 );
423 } else {
424 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
425 "Did not restore tunnel path to {:02x?}: existing path is preferred or tunnel path expired",
426 &dest_hash[..4],
427 );
428 }
429 }
430
431 actions.push(TransportAction::TunnelEstablished {
432 tunnel_id,
433 interface,
434 });
435
436 actions
437 }
438
439 pub fn synthesize_tunnel(
447 &self,
448 identity: &rns_crypto::identity::Identity,
449 interface_id: InterfaceId,
450 rng: &mut dyn Rng,
451 ) -> Vec<TransportAction> {
452 let mut actions = Vec::new();
453
454 let interface_hash = if let Some(interface_hash) = self.interface_hashes.get(&interface_id)
455 {
456 *interface_hash
457 } else {
458 log::warn!(
459 "Cannot synthesize tunnel on {:?}: unknown interface or missing cached hash",
460 interface_id
461 );
462 return actions;
463 };
464
465 match tunnel::build_tunnel_synthesize_data(identity, &interface_hash, rng) {
466 Ok((data, _tunnel_id)) => {
467 let dest_hash = crate::destination::destination_hash(
468 "rnstransport",
469 &["tunnel", "synthesize"],
470 None,
471 );
472 actions.push(TransportAction::TunnelSynthesize {
473 interface: interface_id,
474 data,
475 dest_hash,
476 });
477 }
478 Err(e) => {
479 log::warn!("Cannot synthesize tunnel on {:?}: {}", interface_id, e);
480 }
481 }
482
483 actions
484 }
485
486 pub fn void_tunnel_interface(&mut self, tunnel_id: &[u8; 32]) {
488 self.tunnel_table.void_tunnel_interface(tunnel_id);
489 }
490
491 pub fn tunnel_table(&self) -> &TunnelTable {
493 &self.tunnel_table
494 }
495
496 pub(super) fn has_local_clients(&self) -> bool {
502 self.interfaces.values().any(|i| i.is_local_client)
503 }
504
505 pub(super) fn interface_is_local_client(&self, iface: InterfaceId) -> bool {
506 self.interfaces
507 .get(&iface)
508 .map(|i| i.is_local_client)
509 .unwrap_or(false)
510 }
511
512 #[cfg(test)]
516 pub(super) fn packet_filter(&self, packet: &RawPacket) -> bool {
517 self.packet_filter_at_hops(packet, packet.hops)
518 }
519
520 pub(super) fn packet_filter_at_hops(&self, packet: &RawPacket, hops: u8) -> bool {
521 if packet.transport_id.is_some()
523 && packet.flags.packet_type != constants::PACKET_TYPE_ANNOUNCE
524 {
525 if let Some(ref identity_hash) = self.config.identity_hash {
526 if packet.transport_id.as_ref() != Some(identity_hash) {
527 return false;
528 }
529 }
530 }
531
532 match packet.context {
534 constants::CONTEXT_KEEPALIVE
535 | constants::CONTEXT_RESOURCE_REQ
536 | constants::CONTEXT_RESOURCE_PRF
537 | constants::CONTEXT_RESOURCE
538 | constants::CONTEXT_CACHE_REQUEST
539 | constants::CONTEXT_CHANNEL => return true,
540 _ => {}
541 }
542
543 if packet.flags.destination_type == constants::DESTINATION_PLAIN
545 || packet.flags.destination_type == constants::DESTINATION_GROUP
546 {
547 if packet.flags.packet_type != constants::PACKET_TYPE_ANNOUNCE {
548 return hops <= 1;
549 } else {
550 return false;
552 }
553 }
554
555 if !self.packet_hashlist.is_duplicate(&packet.packet_hash) {
557 return true;
558 }
559
560 if packet.flags.packet_type == constants::PACKET_TYPE_ANNOUNCE
562 && packet.flags.destination_type == constants::DESTINATION_SINGLE
563 {
564 return true;
565 }
566
567 false
568 }
569}