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: BTreeMap::new(),
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 ctx = self.prepare_inbound_packet(InboundFrame {
203 raw,
204 iface: receiving_interface,
205 now: 0.0,
206 rx: RxMetadata::default(),
207 })?;
208 if ctx.packet.flags.packet_type != constants::PACKET_TYPE_PROOF
209 || ctx.packet.context != constants::CONTEXT_LRPROOF
210 {
211 return None;
212 }
213 let link_id = ctx.packet.destination_hash;
214 let destination_hash =
215 self.link_rebalance_destination(&link_id, ctx.packet.hops, receiving_interface)?;
216 Some((link_id, destination_hash, ctx.packet.hops, ctx.packet.data))
217 }
218
219 pub fn rebalance_link_path_from_lrproof(
227 &mut self,
228 link_id: &[u8; 16],
229 packet_hops: u8,
230 receiving_interface: InterfaceId,
231 proof_data: &[u8],
232 destination_sig_pub_bytes: &[u8; 32],
233 ) -> bool {
234 if self
235 .link_rebalance_destination(link_id, packet_hops, receiving_interface)
236 .is_none()
237 {
238 return false;
239 }
240
241 let destination_sig_pub =
242 rns_crypto::ed25519::Ed25519PublicKey::from_bytes(destination_sig_pub_bytes);
243 if crate::link::handshake::validate_lrproof(
244 proof_data,
245 link_id,
246 &destination_sig_pub,
247 destination_sig_pub_bytes,
248 )
249 .is_err()
250 {
251 return false;
252 }
253
254 let destination_hash = match self.link_table.get_mut(link_id) {
255 Some(entry) if !entry.validated => {
256 entry.remaining_hops = packet_hops;
257 entry.destination_hash
258 }
259 _ => return false,
260 };
261 if let Some(paths) = self.path_table.get_mut(&destination_hash) {
262 paths.update_primary_hops(packet_hops);
263 }
264 true
265 }
266
267 pub fn rebalance_destination_path_hops(
269 &mut self,
270 destination_hash: &[u8; 16],
271 packet_hops: u8,
272 ) -> bool {
273 self.path_table
274 .get_mut(destination_hash)
275 .is_some_and(|paths| paths.update_primary_hops(packet_hops))
276 }
277
278 pub fn blackhole_identity(
289 &mut self,
290 identity_hash: [u8; 16],
291 now: f64,
292 duration_hours: Option<f64>,
293 reason: Option<String>,
294 ) {
295 let expires = match duration_hours {
296 Some(h) if h > 0.0 => now + h * 3600.0,
297 _ => 0.0, };
299 self.blackholed_identities.insert(
300 identity_hash,
301 BlackholeEntry {
302 created: now,
303 expires,
304 reason,
305 },
306 );
307 }
308
309 pub fn unblackhole_identity(&mut self, identity_hash: &[u8; 16]) -> bool {
314 self.blackholed_identities.remove(identity_hash).is_some()
315 }
316
317 pub fn is_blackholed(&self, identity_hash: &[u8; 16], now: f64) -> bool {
319 if let Some(entry) = self.blackholed_identities.get(identity_hash) {
320 if entry.expires == 0.0 || entry.expires > now {
321 return true;
322 }
323 }
324 false
325 }
326
327 pub fn blackholed_entries(&self) -> impl Iterator<Item = (&[u8; 16], &BlackholeEntry)> {
329 self.blackholed_identities.iter()
330 }
331
332 pub(super) fn cull_blackholed(&mut self, now: f64) {
334 self.blackholed_identities
335 .retain(|_, entry| entry.expires == 0.0 || entry.expires > now);
336 }
337
338 pub fn handle_tunnel(
346 &mut self,
347 tunnel_id: [u8; 32],
348 interface: InterfaceId,
349 now: f64,
350 ) -> Vec<TransportAction> {
351 let mut actions = Vec::new();
352 let reattaching = self.tunnel_table.get(&tunnel_id).is_some();
353 if reattaching {
354 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
355 "Tunnel endpoint {:02x?} reappeared on interface {}; restoring paths",
356 &tunnel_id[..4],
357 interface.0,
358 );
359 } else {
360 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
361 "Tunnel endpoint {:02x?} established on interface {}",
362 &tunnel_id[..4],
363 interface.0,
364 );
365 }
366
367 if let Some(info) = self.interfaces.get_mut(&interface) {
369 info.tunnel_id = Some(tunnel_id);
370 }
371
372 let restored_paths = self.tunnel_table.handle_tunnel(
373 tunnel_id,
374 interface,
375 now,
376 self.config.destination_timeout_secs,
377 );
378
379 for (dest_hash, tunnel_path) in &restored_paths {
381 let should_restore = match self.path_table.get(dest_hash).and_then(|ps| ps.primary()) {
382 Some(existing) => {
383 if tunnel_path.hops <= existing.hops || existing.expires < now {
386 let existing_timebase = timebase_from_random_blobs(&existing.random_blobs);
387 let tunnel_timebase = timebase_from_random_blobs(&tunnel_path.random_blobs);
388 tunnel_timebase >= existing_timebase
389 } else {
390 false
391 }
392 }
393 None => now < tunnel_path.expires,
394 };
395
396 if should_restore {
397 let entry = PathEntry {
398 timestamp: tunnel_path.timestamp,
399 next_hop: tunnel_path.received_from,
400 hops: tunnel_path.hops,
401 expires: tunnel_path.expires,
402 random_blobs: tunnel_path.random_blobs.clone(),
403 receiving_interface: interface,
404 packet_hash: tunnel_path.packet_hash,
405 announce_raw: None,
406 };
407 self.upsert_path_destination(*dest_hash, entry, now);
408 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
409 "Restored tunnel path to {:02x?}: hops={} via={:02x?} interface={}",
410 &dest_hash[..4],
411 tunnel_path.hops,
412 &tunnel_path.received_from[..4],
413 interface.0,
414 );
415 } else {
416 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
417 "Did not restore tunnel path to {:02x?}: existing path is preferred or tunnel path expired",
418 &dest_hash[..4],
419 );
420 }
421 }
422
423 actions.push(TransportAction::TunnelEstablished {
424 tunnel_id,
425 interface,
426 });
427
428 actions
429 }
430
431 pub fn synthesize_tunnel(
439 &self,
440 identity: &rns_crypto::identity::Identity,
441 interface_id: InterfaceId,
442 rng: &mut dyn Rng,
443 ) -> Vec<TransportAction> {
444 let mut actions = Vec::new();
445
446 let interface_hash = if let Some(interface_hash) = self.interface_hashes.get(&interface_id)
447 {
448 *interface_hash
449 } else {
450 log::warn!(
451 "Cannot synthesize tunnel on {:?}: unknown interface or missing cached hash",
452 interface_id
453 );
454 return actions;
455 };
456
457 match tunnel::build_tunnel_synthesize_data(identity, &interface_hash, rng) {
458 Ok((data, _tunnel_id)) => {
459 let dest_hash = crate::destination::destination_hash(
460 "rnstransport",
461 &["tunnel", "synthesize"],
462 None,
463 );
464 actions.push(TransportAction::TunnelSynthesize {
465 interface: interface_id,
466 data,
467 dest_hash,
468 });
469 }
470 Err(e) => {
471 log::warn!("Cannot synthesize tunnel on {:?}: {}", interface_id, e);
472 }
473 }
474
475 actions
476 }
477
478 pub fn void_tunnel_interface(&mut self, tunnel_id: &[u8; 32]) {
480 self.tunnel_table.void_tunnel_interface(tunnel_id);
481 }
482
483 pub fn tunnel_table(&self) -> &TunnelTable {
485 &self.tunnel_table
486 }
487
488 pub(super) fn has_local_clients(&self) -> bool {
494 self.interfaces.values().any(|i| i.is_local_client)
495 }
496
497 pub(super) fn interface_is_local_client(&self, iface: InterfaceId) -> bool {
498 self.interfaces
499 .get(&iface)
500 .map(|i| i.is_local_client)
501 .unwrap_or(false)
502 }
503
504 pub(super) fn packet_filter(&self, packet: &RawPacket) -> bool {
508 if packet.transport_id.is_some()
510 && packet.flags.packet_type != constants::PACKET_TYPE_ANNOUNCE
511 {
512 if let Some(ref identity_hash) = self.config.identity_hash {
513 if packet.transport_id.as_ref() != Some(identity_hash) {
514 return false;
515 }
516 }
517 }
518
519 match packet.context {
521 constants::CONTEXT_KEEPALIVE
522 | constants::CONTEXT_RESOURCE_REQ
523 | constants::CONTEXT_RESOURCE_PRF
524 | constants::CONTEXT_RESOURCE
525 | constants::CONTEXT_CACHE_REQUEST
526 | constants::CONTEXT_CHANNEL => return true,
527 _ => {}
528 }
529
530 if packet.flags.destination_type == constants::DESTINATION_PLAIN
532 || packet.flags.destination_type == constants::DESTINATION_GROUP
533 {
534 if packet.flags.packet_type != constants::PACKET_TYPE_ANNOUNCE {
535 return packet.hops <= 1;
536 } else {
537 return false;
539 }
540 }
541
542 if !self.packet_hashlist.is_duplicate(&packet.packet_hash) {
544 return true;
545 }
546
547 if packet.flags.packet_type == constants::PACKET_TYPE_ANNOUNCE
549 && packet.flags.destination_type == constants::DESTINATION_SINGLE
550 {
551 return true;
552 }
553
554 false
555 }
556}