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