Skip to main content

rns_core/transport/
mod.rs

1pub mod announce_proc;
2pub mod announce_queue;
3pub mod announce_verify_queue;
4pub mod dedup;
5pub mod destination_filter;
6pub mod inbound;
7pub mod ingress_control;
8pub mod jobs;
9pub mod outbound;
10pub mod path_requests;
11pub mod pathfinder;
12pub mod persistence;
13pub mod queries;
14pub mod rate_limit;
15pub mod retention;
16pub mod tables;
17pub mod tunnel;
18pub mod types;
19
20use alloc::collections::{BTreeMap, BTreeSet, VecDeque};
21use alloc::string::String;
22use alloc::vec::Vec;
23use core::mem::size_of;
24
25use rns_crypto::Rng;
26
27use crate::announce::AnnounceData;
28use crate::constants;
29use crate::hash;
30use crate::packet::RawPacket;
31
32use self::announce_proc::compute_path_expires;
33use self::announce_queue::AnnounceQueues;
34use self::announce_verify_queue::{AnnounceVerifyKey, AnnounceVerifyQueue, PendingAnnounce};
35use self::dedup::{AnnounceSignatureCache, PacketHashlist};
36use self::inbound::{
37    clamp_forwarded_link_request_mtu, create_link_entry, create_reverse_entry,
38    forward_transport_packet, route_proof_via_reverse, route_via_link_table, LocalHopRewrite,
39};
40use self::ingress_control::IngressControl;
41use self::outbound::{route_outbound_into, should_transmit_announce, OutboundRouteOptions};
42use self::pathfinder::{
43    extract_random_blob, timebase_from_random_blob, trusted_timebase_from_random_blobs,
44    MultiPathDecision,
45};
46use self::rate_limit::AnnounceRateLimiter;
47use self::tables::{AnnounceEntry, DiscoveryPathRequest, LinkEntry, PathEntry, PathSet};
48use self::tunnel::TunnelTable;
49use self::types::{
50    BlackholeEntry, InterfaceId, InterfaceInfo, PacketBytes, StampRequirements, TransportAction,
51    TransportConfig,
52};
53
54pub type PathTableRow = ([u8; 16], f64, [u8; 16], u8, f64, f64, String);
55pub type RateTableRow = ([u8; 16], f64, u32, f64, Vec<f64>);
56/// Parsed LRPROOF data used to rebalance an existing link route.
57pub type LrproofRebalanceCandidate = ([u8; 16], [u8; 16], u8, Vec<u8>);
58
59fn lrproof_hop_mismatch_diagnostic(packet_hops: u8, entry: &LinkEntry) -> String {
60    alloc::format!(
61        "Received link request proof with hop mismatch ({}/{}:{}->{}), not transporting it",
62        packet_hops,
63        entry.remaining_hops,
64        entry.next_hop_interface.0,
65        entry.received_interface.0,
66    )
67}
68
69fn link_route_hops_match(
70    packet_hops: u8,
71    entry: &LinkEntry,
72    receiving_interface: InterfaceId,
73) -> bool {
74    if entry.next_hop_interface == entry.received_interface {
75        packet_hops == entry.remaining_hops || packet_hops == entry.taken_hops
76    } else if receiving_interface == entry.next_hop_interface {
77        packet_hops == entry.remaining_hops
78    } else if receiving_interface == entry.received_interface {
79        packet_hops == entry.taken_hops
80    } else {
81        // The routing failure is the interface, not a hop mismatch.
82        true
83    }
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Default)]
87pub struct RxMetadata {
88    pub rssi: Option<i16>,
89    pub snr: Option<f32>,
90}
91
92#[derive(Debug, Clone, Copy, PartialEq)]
93pub struct InboundFrame<'a> {
94    pub raw: &'a [u8],
95    pub iface: InterfaceId,
96    pub now: f64,
97    pub rx: RxMetadata,
98}
99
100impl<'a> InboundFrame<'a> {
101    pub fn new(raw: &'a [u8], iface: InterfaceId, now: f64) -> Self {
102        Self {
103            raw,
104            iface,
105            now,
106            rx: RxMetadata::default(),
107        }
108    }
109
110    pub fn with_rx(mut self, rx: RxMetadata) -> Self {
111        self.rx = rx;
112        self
113    }
114}
115
116/// A syntactically decoded packet. Routing/admission is rechecked on every use.
117/// Fields are read-only so cached hashes cannot diverge from the parsed bytes.
118pub struct DecodedPacket {
119    packet: RawPacket<PacketBytes>,
120    /// Set by [`TransportEngine::decode_inbound`] for link traffic whose hash
121    /// it leaves unread; `packet.packet_hash` is then zeroed until the engine
122    /// needs it.
123    hash_deferred: bool,
124}
125
126impl DecodedPacket {
127    pub fn unpack(raw: &[u8]) -> Result<Self, crate::packet::PacketError> {
128        Self::unpack_shared(PacketBytes::from(raw))
129    }
130
131    /// Decode wire bytes the caller owns (copied once into shared bytes).
132    pub fn unpack_owned(raw: Vec<u8>) -> Result<Self, crate::packet::PacketError> {
133        Self::unpack_shared(PacketBytes::from(raw))
134    }
135
136    /// Decode shared wire bytes without copying them.
137    pub fn unpack_shared(raw: PacketBytes) -> Result<Self, crate::packet::PacketError> {
138        RawPacket::unpack_shared(raw).map(Self::hashed)
139    }
140
141    fn hashed(packet: RawPacket<PacketBytes>) -> Self {
142        Self {
143            packet,
144            hash_deferred: false,
145        }
146    }
147
148    /// The decoded fields. Its `packet_hash` is zeroed when hashing was
149    /// deferred; read the hash through [`DecodedPacket::packet_hash`].
150    pub fn packet(&self) -> &RawPacket<PacketBytes> {
151        &self.packet
152    }
153
154    /// The packet hash, unless [`TransportEngine::decode_inbound`] deferred it.
155    pub fn packet_hash(&self) -> Option<&[u8; 32]> {
156        (!self.hash_deferred).then_some(&self.packet.packet_hash)
157    }
158}
159
160/// Decoded bytes plus the current receive context; no admission decision cached.
161pub struct DecodedInboundFrame {
162    pub decoded: DecodedPacket,
163    pub iface: InterfaceId,
164    pub now: f64,
165    pub rx: RxMetadata,
166}
167
168struct InboundPacketCtx {
169    packet: RawPacket<PacketBytes>,
170    /// `packet.packet_hash` is zeroed: the packet is link traffic that is
171    /// only forwarded, or a local link resource part, and nothing reads it.
172    hash_deferred: bool,
173    original_raw: Option<PacketBytes>,
174    iface: InterfaceId,
175    now: f64,
176    from_local_client: bool,
177}
178
179struct VerifiedAnnounceCtx<'a> {
180    packet: &'a RawPacket<PacketBytes>,
181    original_raw: &'a [u8],
182    iface: InterfaceId,
183    now: f64,
184    validated: crate::announce::ValidatedAnnounce,
185    received_from: [u8; 16],
186    random_blob: [u8; 10],
187    announce_emitted: u64,
188}
189
190struct TickCtx<'a> {
191    now: f64,
192    rng: &'a mut dyn Rng,
193    actions: Vec<TransportAction>,
194}
195
196/// Validated, uniquely tagged path request awaiting ingress accounting.
197///
198/// This two-phase token is intended for the network driver; applications
199/// should use [`TransportEngine::handle_path_request`] instead.
200#[doc(hidden)]
201pub struct AcceptedPathRequest {
202    tag: [u8; 16],
203    tag_len: usize,
204    interface_id: InterfaceId,
205    now: f64,
206    destination_hash: [u8; 16],
207    already_in_flight: bool,
208    /// Stamp extension payload carried by a 64-byte path request, preserved
209    /// verbatim when the request is relayed.
210    ext_payload: Option<[u8; constants::PR_EXT_PAYLOAD_LENGTH]>,
211    /// Value of the carried stamp over `destination_hash || tag`, when the
212    /// extension carries the stamp marker.
213    stamp_value: Option<crate::stamp::StampValue>,
214    /// Whether the carried stamp meets `PATH_REQUEST_STAMP_COST`.
215    stamped: bool,
216}
217
218impl AcceptedPathRequest {
219    /// Value of the stamp this request carried, if any.
220    pub fn stamp_value(&self) -> Option<crate::stamp::StampValue> {
221        self.stamp_value
222    }
223
224    /// Whether this request carried a stamp meeting the required cost.
225    pub fn stamped(&self) -> bool {
226        self.stamped
227    }
228}
229
230/// The core transport/routing engine.
231///
232/// Maintains routing tables and processes packets without performing any I/O.
233/// Returns `Vec<TransportAction>` that the caller must execute.
234pub struct TransportEngine {
235    config: TransportConfig,
236    path_table: BTreeMap<[u8; 16], PathSet>,
237    announce_table: BTreeMap<[u8; 16], AnnounceEntry>,
238    reverse_table: tables::ReverseTable,
239    link_table: BTreeMap<[u8; 16], LinkEntry>,
240    held_announces: BTreeMap<[u8; 16], AnnounceEntry>,
241    packet_hashlist: PacketHashlist,
242    announce_sig_cache: AnnounceSignatureCache,
243    rate_limiter: AnnounceRateLimiter,
244    path_states: BTreeMap<[u8; 16], u8>,
245    interfaces: BTreeMap<InterfaceId, InterfaceInfo>,
246    interface_hashes: BTreeMap<InterfaceId, [u8; 32]>,
247    local_destinations: BTreeMap<[u8; 16], u8>,
248    blackholed_identities: BTreeMap<[u8; 16], BlackholeEntry>,
249    announce_queues: AnnounceQueues,
250    ingress_control: IngressControl,
251    tunnel_table: TunnelTable,
252    discovery_pr_tags: VecDeque<[u8; 32]>,
253    discovery_pr_tag_set: BTreeSet<[u8; 32]>,
254    path_requests: BTreeMap<[u8; 16], f64>,
255    /// Whether path requests emitted or forwarded for local clients by this
256    /// node are stamped (`Transport.STAMP_PRS`). Defaults to the compile-time
257    /// constant; settable so the stamping path can be exercised.
258    stamp_prs: bool,
259    /// Announce and path request stamp requirements (`STAMPS_ENFORCED`,
260    /// `DESTINATION_COST`, `PR_COST`), configurable through `[stamps]`.
261    stamp_requirements: StampRequirements,
262    /// Announce filtering by destination type (`[filter]`, upstream
263    /// 825c2e19).
264    destination_filter: destination_filter::DestinationTypeFilter,
265    /// `Transport.control_hashes`: the path request and tunnel synthesis
266    /// destinations, which are handled by the engine and never bridged as
267    /// plain broadcasts.
268    control_destinations: [[u8; 16]; 2],
269    discovery_path_requests: BTreeMap<[u8; 16], DiscoveryPathRequest>,
270    discovery_path_request_deadlines: BTreeMap<[u8; 16], f64>,
271    /// Path requests for destinations behind a local client, by destination:
272    /// the requesting interface and when it asked (Transport.py
273    /// `pending_local_path_requests`).
274    pending_local_path_requests: BTreeMap<[u8; 16], (InterfaceId, f64)>,
275    /// Destinations whose path is being rediscovered, with the interface to
276    /// skip, sent one every `DISCOVERY_PR_TX_THROTTLE` seconds
277    /// (`Transport.pending_discovery_prs`).
278    pending_discovery_prs: VecDeque<([u8; 16], Option<InterfaceId>)>,
279    /// When the next queued rediscovery request may be sent.
280    discovery_pr_next_tx: f64,
281    path_destination_cap_evict_count: usize,
282    // Job timing
283    announces_last_checked: f64,
284    tables_last_culled: f64,
285}
286
287mod engine_state;
288mod inbound_engine;
289mod maintenance;
290mod outbound_engine;
291
292#[cfg(test)]
293mod tests;