s2n_quic_core/path/
mod.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{
5    event,
6    inet::{
7        IpV4Address, IpV6Address, SocketAddress, SocketAddressV4, SocketAddressV6, Unspecified as _,
8    },
9};
10use core::fmt;
11
12#[cfg(any(test, feature = "generator"))]
13use bolero_generator::prelude::*;
14
15pub mod ecn;
16pub mod migration;
17pub mod mtu;
18
19pub use mtu::{BaseMtu, Config, Endpoint, InitialMtu, MaxMtu, MtuError, MINIMUM_MAX_DATAGRAM_SIZE};
20
21// Initial PTO backoff multiplier is 1 indicating no additional increase to the backoff.
22pub const INITIAL_PTO_BACKOFF: u32 = 1;
23
24/// Internal Id of a path in the manager
25#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
26pub struct Id(u8);
27
28impl Id {
29    /// Create a new path::Id
30    ///
31    /// # Safety
32    /// This should only be used by the path::Manager
33    pub unsafe fn new(id: u8) -> Self {
34        Self(id)
35    }
36
37    pub fn as_u8(&self) -> u8 {
38        self.0
39    }
40}
41
42impl event::IntoEvent<u64> for Id {
43    #[inline]
44    fn into_event(self) -> u64 {
45        self.0 as u64
46    }
47}
48
49#[cfg(any(test, feature = "testing"))]
50impl Id {
51    pub fn test_id() -> Self {
52        unsafe { Id::new(0) }
53    }
54}
55
56/// An interface for an object that represents a unique path between two endpoints
57pub trait Handle: 'static + Copy + Send + fmt::Debug {
58    /// Creates a Handle from a RemoteAddress
59    fn from_remote_address(remote_addr: RemoteAddress) -> Self;
60
61    /// Returns the remote address for the given handle
62    fn remote_address(&self) -> RemoteAddress;
63
64    /// Updates the remote address to the given value
65    fn set_remote_address(&mut self, addr: RemoteAddress);
66
67    /// Returns the local address for the given handle
68    fn local_address(&self) -> LocalAddress;
69
70    /// Updates the local address to the given value
71    fn set_local_address(&mut self, addr: LocalAddress);
72
73    /// Returns `true` if the two handles are equal from a network perspective
74    ///
75    /// This function is used to determine if a connection has migrated to another
76    /// path.
77    fn unmapped_eq(&self, other: &Self) -> bool;
78
79    /// Returns `true` if the two handles are strictly equal to each other, i.e.
80    /// byte-for-byte.
81    fn strict_eq(&self, other: &Self) -> bool;
82
83    /// Depending on the current value of `self`, fields from `other` may be copied to increase the
84    /// fidelity of the value.
85    ///
86    /// This is especially useful for clients that initiate a connection only based on the remote
87    /// IP and port. They likely wouldn't know the IP address of the local socket. Once a response
88    /// is received from the server, the IP information will be known at this point and the handle
89    /// can be updated with the new information.
90    ///
91    /// Implementations should try to limit the cost of updating by checking the current value to
92    /// see if it needs updating.
93    fn maybe_update(&mut self, other: &Self);
94}
95
96macro_rules! impl_addr {
97    ($name:ident) => {
98        #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
99        #[cfg_attr(any(test, feature = "generator"), derive(TypeGenerator))]
100        #[cfg_attr(kani, derive(kani::Arbitrary))]
101        pub struct $name(pub SocketAddress);
102
103        impl $name {
104            /// Returns `true` if the two addresses are equal from a network perspective.
105            ///
106            /// This will unmap IPv4-mapped addresses to IpV4 tagged enum values
107            #[inline]
108            pub fn unmapped_eq(&self, other: &Self) -> bool {
109                self.0.unmapped_eq(&other.0)
110            }
111        }
112
113        impl From<event::api::SocketAddress<'_>> for $name {
114            #[inline]
115            fn from(value: event::api::SocketAddress<'_>) -> Self {
116                match value {
117                    event::api::SocketAddress::IpV4 { ip, port } => {
118                        $name(IpV4Address::new(*ip).with_port(port).into())
119                    }
120                    event::api::SocketAddress::IpV6 { ip, port } => {
121                        $name(IpV6Address::new(*ip).with_port(port).into())
122                    }
123                }
124            }
125        }
126
127        impl From<SocketAddress> for $name {
128            #[inline]
129            fn from(value: SocketAddress) -> Self {
130                Self(value)
131            }
132        }
133
134        impl From<SocketAddressV4> for $name {
135            #[inline]
136            fn from(value: SocketAddressV4) -> Self {
137                Self(value.into())
138            }
139        }
140
141        impl From<SocketAddressV6> for $name {
142            #[inline]
143            fn from(value: SocketAddressV6) -> Self {
144                Self(value.into())
145            }
146        }
147
148        impl core::ops::Deref for $name {
149            type Target = SocketAddress;
150
151            fn deref(&self) -> &Self::Target {
152                &self.0
153            }
154        }
155
156        impl core::ops::DerefMut for $name {
157            fn deref_mut(&mut self) -> &mut Self::Target {
158                &mut self.0
159            }
160        }
161    };
162}
163
164impl_addr!(LocalAddress);
165
166impl LocalAddress {
167    #[inline]
168    pub fn maybe_update(&mut self, other: &Self) {
169        // only update the local address if it's specified
170        ensure!(!other.is_unspecified());
171
172        *self = *other;
173    }
174}
175
176impl_addr!(RemoteAddress);
177
178impl Handle for RemoteAddress {
179    #[inline]
180    fn from_remote_address(remote_address: RemoteAddress) -> Self {
181        remote_address
182    }
183
184    #[inline]
185    fn remote_address(&self) -> RemoteAddress {
186        *self
187    }
188
189    #[inline]
190    fn set_remote_address(&mut self, addr: RemoteAddress) {
191        *self = addr;
192    }
193
194    #[inline]
195    fn local_address(&self) -> LocalAddress {
196        SocketAddressV4::UNSPECIFIED.into()
197    }
198
199    #[inline]
200    fn set_local_address(&mut self, _addr: LocalAddress) {
201        // nothing to update
202    }
203
204    #[inline]
205    fn unmapped_eq(&self, other: &Self) -> bool {
206        Self::unmapped_eq(self, other)
207    }
208
209    #[inline]
210    fn strict_eq(&self, other: &Self) -> bool {
211        PartialEq::eq(self, other)
212    }
213
214    #[inline]
215    fn maybe_update(&mut self, _other: &Self) {
216        // nothing to update
217    }
218}
219
220#[derive(Clone, Copy, Debug, Eq)]
221#[cfg_attr(any(test, feature = "generator"), derive(TypeGenerator))]
222pub struct Tuple {
223    pub remote_address: RemoteAddress,
224    pub local_address: LocalAddress,
225}
226
227impl PartialEq for Tuple {
228    #[inline]
229    fn eq(&self, other: &Self) -> bool {
230        PartialEq::eq(&self.remote_address, &other.remote_address)
231            && PartialEq::eq(&self.local_address, &other.local_address)
232    }
233}
234
235impl Handle for Tuple {
236    #[inline]
237    fn from_remote_address(remote_address: RemoteAddress) -> Self {
238        let local_address = SocketAddressV4::UNSPECIFIED.into();
239        Self {
240            remote_address,
241            local_address,
242        }
243    }
244
245    #[inline]
246    fn remote_address(&self) -> RemoteAddress {
247        self.remote_address
248    }
249
250    #[inline]
251    fn set_remote_address(&mut self, addr: RemoteAddress) {
252        self.remote_address = addr;
253    }
254
255    #[inline]
256    fn local_address(&self) -> LocalAddress {
257        self.local_address
258    }
259
260    #[inline]
261    fn set_local_address(&mut self, addr: LocalAddress) {
262        self.local_address = addr;
263    }
264
265    #[inline]
266    fn unmapped_eq(&self, other: &Self) -> bool {
267        self.local_address.unmapped_eq(&other.local_address)
268            && self.remote_address.unmapped_eq(&other.remote_address)
269    }
270
271    #[inline]
272    fn strict_eq(&self, other: &Self) -> bool {
273        PartialEq::eq(self, other)
274    }
275
276    #[inline]
277    fn maybe_update(&mut self, other: &Self) {
278        self.local_address.maybe_update(&other.local_address);
279    }
280}
281
282//= https://www.rfc-editor.org/rfc/rfc9308#section-8.1
283//# Some UDP protocols are vulnerable to reflection attacks, where an
284//# attacker is able to direct traffic to a third party as a denial of
285//# service.  For example, these source ports are associated with
286//# applications known to be vulnerable to reflection attacks, often due
287//# to server misconfiguration:
288//#
289//# *  port 53 - DNS [RFC1034]
290//#
291//# *  port 123 - NTP [RFC5905]
292//#
293//# *  port 1900 - SSDP [SSDP]
294//#
295//# *  port 5353 - mDNS [RFC6762]
296//#
297//# *  port 11211 - memcache
298
299/// List of ports to refuse connections from. This list must be sorted.
300///
301/// Based on https://quiche.googlesource.com/quiche/+/bac04054bccb2a249d4705ecc94a646404d41c1b/quiche/quic/core/quic_dispatcher.cc#498
302const BLOCKED_PORTS: [u16; 11] = [
303    0,   // We cannot send to port 0 so drop that source port.
304    17,  // Quote of the Day, can loop with QUIC.
305    19,  // Chargen, can loop with QUIC.
306    53,  // DNS, vulnerable to reflection attacks.
307    111, // Portmap.
308    123, // NTP, vulnerable to reflection attacks.
309    137, // NETBIOS Name Service,
310    138, // NETBIOS Datagram Service
311    161, // SNMP.
312    389, // CLDAP.
313    500, // IKE, can loop with QUIC.
314];
315const MAX_BLOCKED_PORT: u16 = BLOCKED_PORTS[BLOCKED_PORTS.len() - 1];
316
317#[inline]
318pub fn remote_port_blocked(port: u16) -> bool {
319    if port > MAX_BLOCKED_PORT {
320        // Early return to avoid iteration if possible
321        return false;
322    }
323
324    for blocked in BLOCKED_PORTS {
325        if port == blocked {
326            return true;
327        }
328    }
329
330    false
331}
332
333// The below ports are also vulnerable to reflection attacks, but are within
334// the original ephemeral port range of 1024–65535, so there is a chance
335// clients may be randomly assigned them. To address this, we can throttle
336// connections using these ports instead of fully blocking them.
337
338/// List of ports to throttle connections from. This list must be sorted.
339///
340/// Based on https://quiche.googlesource.com/quiche/+/bac04054bccb2a249d4705ecc94a646404d41c1b/quiche/quic/core/quic_dispatcher.cc#498
341const THROTTLED_PORTS: [u16; 5] = [
342    1900,  // SSDP, vulnerable to reflection attacks.
343    3702,  // WS-Discovery, vulnerable to reflection attacks.
344    5353,  // mDNS, vulnerable to reflection attacks.
345    5355,  // LLMNR, vulnerable to reflection attacks.
346    11211, // memcache, vulnerable to reflection attacks.
347];
348const MAX_THROTTLED_PORT: u16 = THROTTLED_PORTS[THROTTLED_PORTS.len() - 1];
349pub const THROTTLED_PORTS_LEN: usize = THROTTLED_PORTS.len();
350
351#[inline]
352pub fn remote_port_throttled_index(port: u16) -> Option<usize> {
353    for (idx, throttled_port) in THROTTLED_PORTS.iter().enumerate() {
354        if *throttled_port == port {
355            return Some(idx);
356        }
357    }
358    None
359}
360
361#[inline]
362pub fn remote_port_throttled(port: u16) -> bool {
363    if port > MAX_THROTTLED_PORT {
364        // Early return to avoid iteration if possible
365        return false;
366    }
367
368    for throttled in THROTTLED_PORTS {
369        if port == throttled {
370            return true;
371        }
372    }
373
374    false
375}
376
377#[cfg(test)]
378mod tests {
379    use crate::path::{
380        remote_port_blocked, remote_port_throttled, BLOCKED_PORTS, MAX_BLOCKED_PORT,
381        MAX_THROTTLED_PORT, THROTTLED_PORTS,
382    };
383
384    #[test]
385    fn blocked_ports_is_sorted() {
386        assert_eq!(Some(MAX_BLOCKED_PORT), BLOCKED_PORTS.iter().max().copied());
387
388        let mut sorted = BLOCKED_PORTS.to_vec();
389        sorted.sort_unstable();
390
391        for i in 0..BLOCKED_PORTS.len() {
392            assert_eq!(sorted[i], BLOCKED_PORTS[i]);
393        }
394    }
395
396    #[test]
397    #[cfg_attr(miri, ignore)]
398    fn blocked_port() {
399        for port in 0..u16::MAX {
400            let blocked_expected = BLOCKED_PORTS.iter().copied().any(|blocked| blocked == port);
401            assert_eq!(blocked_expected, remote_port_blocked(port));
402        }
403    }
404
405    #[test]
406    fn throttled_ports_is_sorted() {
407        assert_eq!(
408            Some(MAX_THROTTLED_PORT),
409            THROTTLED_PORTS.iter().max().copied()
410        );
411
412        let mut sorted = THROTTLED_PORTS.to_vec();
413        sorted.sort_unstable();
414
415        for i in 0..THROTTLED_PORTS.len() {
416            assert_eq!(sorted[i], THROTTLED_PORTS[i]);
417        }
418    }
419
420    #[test]
421    #[cfg_attr(miri, ignore)]
422    fn throttled_port() {
423        for port in 0..u16::MAX {
424            let throttled_expected = THROTTLED_PORTS
425                .iter()
426                .copied()
427                .any(|throttled| throttled == port);
428            assert_eq!(throttled_expected, remote_port_throttled(port));
429        }
430    }
431}
432
433#[cfg(any(test, feature = "testing"))]
434pub mod testing {
435    use crate::{
436        connection, event,
437        event::{builder::SocketAddress, IntoEvent},
438    };
439
440    impl event::builder::Path<'_> {
441        pub fn test() -> Self {
442            Self {
443                local_addr: SocketAddress::IpV4 {
444                    ip: &[127, 0, 0, 1],
445                    port: 0,
446                },
447                local_cid: connection::LocalId::TEST_ID.into_event(),
448                remote_addr: SocketAddress::IpV4 {
449                    ip: &[127, 0, 0, 1],
450                    port: 0,
451                },
452                remote_cid: connection::PeerId::TEST_ID.into_event(),
453                id: 0,
454                is_active: false,
455            }
456        }
457    }
458}