Skip to main content

rns_core/transport/
queries.rs

1use super::*;
2
3impl TransportEngine {
4    /// Return the slowest positive bitrate among registered interfaces.
5    ///
6    /// Returns `None` when no registered interface advertises a usable
7    /// bitrate.
8    pub fn lowest_interface_bitrate(&self) -> Option<u64> {
9        super::path_requests::lowest_interface_bitrate(&self.interfaces)
10    }
11
12    /// Estimate a path-request round-trip timeout for the slowest medium.
13    ///
14    /// The estimate covers two MTU transmissions at the slowest registered
15    /// positive bitrate plus the per-hop link-establishment allowance. It is
16    /// zero when no usable interface bitrate is known.
17    pub fn medium_path_timeout(&self) -> f64 {
18        super::path_requests::medium_path_timeout(&self.interfaces)
19    }
20
21    pub fn path_table_entries(&self) -> impl Iterator<Item = (&[u8; 16], &PathEntry)> {
22        self.path_table
23            .iter()
24            .filter_map(|(k, ps)| ps.primary().map(|e| (k, e)))
25    }
26
27    pub fn path_table_sets(&self) -> impl Iterator<Item = (&[u8; 16], &PathSet)> {
28        self.path_table.iter()
29    }
30
31    pub fn interface_count(&self) -> usize {
32        self.interfaces.len()
33    }
34
35    pub fn link_table_count(&self) -> usize {
36        self.link_table.len()
37    }
38
39    pub fn active_link_count(&self) -> usize {
40        self.link_table
41            .values()
42            .filter(|entry| entry.validated)
43            .count()
44    }
45
46    pub fn is_unvalidated_link_packet<B: AsRef<[u8]>>(
47        &self,
48        packet: &crate::packet::RawPacket<B>,
49    ) -> bool {
50        packet.flags.packet_type != crate::constants::PACKET_TYPE_ANNOUNCE
51            && packet.flags.packet_type != crate::constants::PACKET_TYPE_LINKREQUEST
52            && packet.context != crate::constants::CONTEXT_LRPROOF
53            && self
54                .link_table
55                .get(&packet.destination_hash)
56                .is_some_and(|entry| !entry.validated)
57    }
58
59    /// Classify packet-filter rejections that are peer protocol violations.
60    pub fn is_packet_filter_protocol_violation<B: AsRef<[u8]>>(
61        &self,
62        packet: &crate::packet::RawPacket<B>,
63        interface: InterfaceId,
64    ) -> bool {
65        let mut hops = packet.hops.saturating_add(1);
66        if self.interface_is_local_client(interface) {
67            hops = hops.saturating_sub(1);
68        }
69        if packet.flags.packet_type == crate::constants::PACKET_TYPE_ANNOUNCE {
70            return packet.flags.destination_type != crate::constants::DESTINATION_SINGLE;
71        }
72        (packet.flags.destination_type == crate::constants::DESTINATION_PLAIN
73            || packet.flags.destination_type == crate::constants::DESTINATION_GROUP)
74            && hops > 1
75    }
76
77    pub fn path_table_count(&self) -> usize {
78        self.path_table.len()
79    }
80
81    pub fn announce_table_count(&self) -> usize {
82        self.announce_table.len()
83    }
84
85    pub fn reverse_table_count(&self) -> usize {
86        self.reverse_table.len()
87    }
88
89    pub fn held_announces_count(&self) -> usize {
90        self.held_announces.len()
91    }
92
93    pub fn packet_hashlist_len(&self) -> usize {
94        self.packet_hashlist.len()
95    }
96
97    /// Start loading the deduplication state for `packet_hash`, so the
98    /// inbound filter's lookup of a just-decoded packet overlaps that cache
99    /// miss with the driver's admission checks. It has no other effect.
100    pub fn prefetch_packet_hash(&self, packet_hash: &[u8; 32]) {
101        self.packet_hashlist.prefetch(packet_hash);
102    }
103
104    /// Remove a packet hash from deduplication after an interface failover race.
105    ///
106    /// Network drivers should call this when an active-link packet arrives on
107    /// the link's former interface, allowing the same packet to be accepted
108    /// later on its current route.
109    #[doc(hidden)]
110    pub fn forget_packet_hash(&mut self, packet_hash: &[u8; 32]) -> bool {
111        self.packet_hashlist.remove(packet_hash)
112    }
113
114    pub fn announce_sig_cache_len(&self) -> usize {
115        self.announce_sig_cache.len()
116    }
117
118    pub fn rate_limiter_count(&self) -> usize {
119        self.rate_limiter.len()
120    }
121
122    pub fn blackholed_count(&self) -> usize {
123        self.blackholed_identities.len()
124    }
125
126    pub fn tunnel_count(&self) -> usize {
127        self.tunnel_table.len()
128    }
129
130    pub fn discovery_pr_tags_count(&self) -> usize {
131        self.discovery_pr_tags.len()
132    }
133
134    #[cfg(test)]
135    pub(crate) fn has_discovery_pr_tag(&self, unique_tag: &[u8; 32]) -> bool {
136        self.discovery_pr_tag_set.contains(unique_tag)
137    }
138
139    pub fn discovery_path_requests_count(&self) -> usize {
140        self.discovery_path_requests.len()
141    }
142
143    pub fn announce_queue_count(&self) -> usize {
144        self.announce_queues.queue_count()
145    }
146
147    pub fn nonempty_announce_queue_count(&self) -> usize {
148        self.announce_queues.nonempty_queue_count()
149    }
150
151    pub fn queued_announce_count(&self) -> usize {
152        self.announce_queues.total_queued_announces()
153    }
154
155    pub fn queued_announce_bytes(&self) -> usize {
156        self.announce_queues.total_queued_bytes()
157    }
158
159    pub fn announce_queue_interface_cap_drop_count(&self) -> u64 {
160        self.announce_queues.interface_cap_drop_count()
161    }
162
163    pub fn local_destinations_count(&self) -> usize {
164        self.local_destinations.len()
165    }
166
167    pub fn rate_limiter(&self) -> &AnnounceRateLimiter {
168        &self.rate_limiter
169    }
170
171    pub fn interface_info(&self, id: &InterfaceId) -> Option<&InterfaceInfo> {
172        self.interfaces.get(id)
173    }
174
175    /// The registered interfaces that can send.
176    pub fn out_capable_interfaces(&self) -> impl Iterator<Item = InterfaceId> + '_ {
177        self.interfaces
178            .values()
179            .filter(|info| info.out_capable)
180            .map(|info| info.id)
181    }
182
183    pub fn redirect_path(&mut self, dest_hash: &[u8; 16], interface: InterfaceId, now: f64) {
184        if let Some(entry) = self
185            .path_table
186            .get_mut(dest_hash)
187            .and_then(|ps| ps.primary_mut())
188        {
189            entry.receiving_interface = interface;
190            // A redirected path is a true one-hop path to the destination.
191            // Retaining the previous transport as next_hop makes outbound
192            // routing inject a HEADER_2 transport header for that stale peer.
193            entry.next_hop = *dest_hash;
194            entry.hops = 1;
195            entry.timestamp = now;
196            entry.expires = now + 3600.0;
197        } else {
198            self.upsert_path_destination(
199                *dest_hash,
200                PathEntry {
201                    timestamp: now,
202                    next_hop: *dest_hash,
203                    hops: 1,
204                    expires: now + 3600.0,
205                    random_blobs: Vec::new(),
206                    receiving_interface: interface,
207                    packet_hash: [0u8; 32],
208                    used: 0.0,
209                    announce_raw: None,
210                },
211                now,
212            );
213        }
214    }
215
216    pub fn inject_path(&mut self, dest_hash: [u8; 16], entry: PathEntry) {
217        self.upsert_path_destination(dest_hash, entry.clone(), entry.timestamp);
218    }
219
220    pub fn drop_path(&mut self, dest_hash: &[u8; 16]) -> bool {
221        self.path_table.remove(dest_hash).is_some()
222    }
223
224    pub fn drop_all_via(&mut self, transport_hash: &[u8; 16]) -> usize {
225        let mut removed = 0usize;
226        for ps in self.path_table.values_mut() {
227            let before = ps.len();
228            ps.retain(|entry| &entry.next_hop != transport_hash);
229            removed += before - ps.len();
230        }
231        self.path_table.retain(|_, ps| !ps.is_empty());
232        removed
233    }
234
235    pub fn drop_paths_for_interface(&mut self, interface: InterfaceId) -> usize {
236        let mut removed = 0usize;
237        let mut cleared_destinations = Vec::new();
238        for (dest_hash, ps) in self.path_table.iter_mut() {
239            let before = ps.len();
240            ps.retain(|entry| entry.receiving_interface != interface);
241            if ps.is_empty() {
242                cleared_destinations.push(*dest_hash);
243            }
244            removed += before - ps.len();
245        }
246        self.path_table.retain(|_, ps| !ps.is_empty());
247        for dest_hash in cleared_destinations {
248            self.path_states.remove(&dest_hash);
249        }
250        removed
251    }
252
253    pub fn drop_reverse_for_interface(&mut self, interface: InterfaceId) -> usize {
254        let before = self.reverse_table.len();
255        self.reverse_table.retain(|_, entry| {
256            entry.receiving_interface != interface && entry.outbound_interface != interface
257        });
258        before - self.reverse_table.len()
259    }
260
261    pub fn drop_links_for_interface(&mut self, interface: InterfaceId) -> usize {
262        let before = self.link_table.len();
263        self.link_table.retain(|_, entry| {
264            entry.next_hop_interface != interface && entry.received_interface != interface
265        });
266        before - self.link_table.len()
267    }
268
269    pub fn drop_announce_queues(&mut self) {
270        self.announce_table.clear();
271        self.held_announces.clear();
272        self.announce_queues = AnnounceQueues::new(self.config.announce_queue_max_interfaces);
273        self.ingress_control.clear();
274    }
275
276    pub fn void_queues(&mut self) {
277        self.drop_announce_queues();
278        self.reverse_table.clear();
279    }
280
281    pub fn identity_hash(&self) -> Option<&[u8; 16]> {
282        self.config.identity_hash.as_ref()
283    }
284
285    pub fn transport_enabled(&self) -> bool {
286        self.config.transport_enabled
287    }
288
289    pub fn config(&self) -> &TransportConfig {
290        &self.config
291    }
292
293    pub fn set_packet_hashlist_max_entries(&mut self, max_entries: usize) {
294        self.config.packet_hashlist_max_entries = max_entries;
295        let seed = self.packet_hashlist.hashing_seed();
296        self.packet_hashlist =
297            PacketHashlist::with_allocation(max_entries, self.config.packet_hashlist_allocation);
298        if let Some(seed) = seed {
299            self.packet_hashlist.seed_hashing(seed);
300        }
301    }
302
303    pub fn get_path_table(&self, max_hops: Option<u8>) -> Vec<PathTableRow> {
304        let mut result = Vec::new();
305        for (dest_hash, ps) in self.path_table.iter() {
306            if let Some(entry) = ps.primary() {
307                if let Some(max) = max_hops {
308                    if entry.hops > max {
309                        continue;
310                    }
311                }
312                let iface_name = self
313                    .interfaces
314                    .get(&entry.receiving_interface)
315                    .map(|i| i.name.clone())
316                    .unwrap_or_else(|| {
317                        alloc::format!("Interface({})", entry.receiving_interface.0)
318                    });
319                result.push((
320                    *dest_hash,
321                    entry.timestamp,
322                    entry.next_hop,
323                    entry.hops,
324                    entry.expires,
325                    entry.used,
326                    iface_name,
327                ));
328            }
329        }
330        result
331    }
332
333    /// Return (total destinations, never-used destinations) in the path pool.
334    pub fn path_pool_counts(&self) -> (usize, usize) {
335        let total = self.path_table.len();
336        let unused = self
337            .path_table
338            .values()
339            .filter(|paths| paths.primary().is_some_and(|path| path.used == 0.0))
340            .count();
341        (total, unused)
342    }
343
344    /// Count active and validated links per destination hash.
345    pub fn link_distribution(&self) -> Vec<([u8; 16], usize, usize)> {
346        let mut distribution: alloc::collections::BTreeMap<[u8; 16], (usize, usize)> =
347            alloc::collections::BTreeMap::new();
348        for entry in self.link_table.values() {
349            let counts = distribution.entry(entry.destination_hash).or_default();
350            counts.0 += 1;
351            if entry.validated {
352                counts.1 += 1;
353            }
354        }
355        distribution
356            .into_iter()
357            .map(|(hash, (total, active))| (hash, total, active))
358            .collect()
359    }
360
361    pub fn get_rate_table(&self) -> Vec<RateTableRow> {
362        self.rate_limiter
363            .entries()
364            .map(|(hash, entry)| {
365                (
366                    *hash,
367                    entry.last,
368                    entry.rate_violations,
369                    entry.blocked_until,
370                    entry.timestamps.clone(),
371                )
372            })
373            .collect()
374    }
375
376    pub fn get_blackholed(&self) -> Vec<([u8; 16], f64, f64, Option<alloc::string::String>)> {
377        self.blackholed_entries()
378            .map(|(hash, entry)| (*hash, entry.created, entry.expires, entry.reason.clone()))
379            .collect()
380    }
381
382    pub fn active_destination_hashes(&self) -> alloc::collections::BTreeSet<[u8; 16]> {
383        self.path_table.keys().copied().collect()
384    }
385
386    pub fn path_destination_cap_evict_count(&self) -> usize {
387        self.path_destination_cap_evict_count
388    }
389
390    pub fn active_packet_hashes(&self) -> Vec<[u8; 32]> {
391        let mut hashes: Vec<[u8; 32]> = self
392            .path_table
393            .values()
394            .flat_map(|ps| ps.iter().map(|p| p.packet_hash))
395            .collect();
396
397        hashes.extend(
398            self.tunnel_table
399                .iter()
400                .flat_map(|(_, tunnel)| tunnel.paths.values().map(|p| p.packet_hash)),
401        );
402        hashes.sort_unstable();
403        hashes.dedup();
404        hashes
405    }
406
407    pub fn cull_rate_limiter(
408        &mut self,
409        active: &alloc::collections::BTreeSet<[u8; 16]>,
410        now: f64,
411        ttl_secs: f64,
412    ) -> usize {
413        self.rate_limiter.cull_stale(active, now, ttl_secs)
414    }
415
416    pub fn update_interface_freq(&mut self, id: InterfaceId, ia_freq: f64) {
417        if let Some(info) = self.interfaces.get_mut(&id) {
418            info.ia_freq = ia_freq;
419        }
420    }
421
422    pub fn update_interface_freqs(
423        &mut self,
424        id: InterfaceId,
425        ia_freq: f64,
426        ip_freq: f64,
427        op_freq: f64,
428        op_samples: usize,
429    ) {
430        if let Some(info) = self.interfaces.get_mut(&id) {
431            info.ia_freq = ia_freq;
432            info.ip_freq = ip_freq;
433            info.op_freq = op_freq;
434            info.op_samples = op_samples;
435        }
436    }
437
438    pub fn held_announce_count(&self, interface: &InterfaceId) -> usize {
439        self.ingress_control.held_count(interface)
440    }
441
442    pub fn burst_active(&self, interface: &InterfaceId) -> bool {
443        self.ingress_control.burst_active(interface)
444    }
445
446    pub fn burst_activated(&self, interface: &InterfaceId) -> f64 {
447        self.ingress_control.burst_activated(interface)
448    }
449
450    pub fn pr_burst_active(&self, interface: &InterfaceId) -> bool {
451        self.ingress_control.pr_burst_active(interface)
452    }
453
454    pub fn pr_burst_activated(&self, interface: &InterfaceId) -> f64 {
455        self.ingress_control.pr_burst_activated(interface)
456    }
457
458    #[cfg(test)]
459    #[allow(dead_code)]
460    pub(crate) fn path_table(&self) -> &BTreeMap<[u8; 16], PathSet> {
461        &self.path_table
462    }
463
464    #[cfg(test)]
465    #[allow(dead_code)]
466    pub(crate) fn announce_table(&self) -> &BTreeMap<[u8; 16], AnnounceEntry> {
467        &self.announce_table
468    }
469
470    #[cfg(test)]
471    #[allow(dead_code)]
472    pub(crate) fn held_announces(&self) -> &BTreeMap<[u8; 16], AnnounceEntry> {
473        &self.held_announces
474    }
475
476    #[cfg(test)]
477    #[allow(dead_code)]
478    pub(crate) fn announce_retained_bytes(&self) -> usize {
479        self.announce_retained_bytes_total()
480    }
481
482    /// Key the hashing of tables indexed by packet hashes, the reverse table
483    /// and the deduplication table, with seeds drawn from `rng`. Packet hashes are chosen by whoever crafts the packets,
484    /// so without a secret seed a sender could grind packets into one probe
485    /// sequence and make every lookup in that table linear. Call this once
486    /// with a cryptographic RNG after creating the engine; until then the
487    /// tables use a fixed public seed.
488    pub fn seed_table_hashing(&mut self, rng: &mut dyn Rng) {
489        let mut seeds = [0u8; 16];
490        rng.fill_bytes(&mut seeds);
491        let (reverse_seed, dedup_seed) = seeds.split_at(8);
492        let seed = |bytes: &[u8]| u64::from_le_bytes(bytes.try_into().expect("8 bytes"));
493        self.packet_hashlist.seed_hashing(seed(dedup_seed));
494        let hasher = foldhash::fast::FixedState::with_seed(seed(reverse_seed));
495        let mut table =
496            tables::ReverseTable::with_capacity_and_hasher(self.reverse_table.len(), hasher);
497        table.extend(self.reverse_table.drain());
498        self.reverse_table = table;
499    }
500
501    #[cfg(test)]
502    #[allow(dead_code)]
503    pub(crate) fn reverse_table(&self) -> &tables::ReverseTable {
504        &self.reverse_table
505    }
506}