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 From<$name> for core::net::SocketAddr {
149            #[inline]
150            fn from(value: $name) -> Self {
151                value.0.into()
152            }
153        }
154
155        impl core::ops::Deref for $name {
156            type Target = SocketAddress;
157
158            fn deref(&self) -> &Self::Target {
159                &self.0
160            }
161        }
162
163        impl core::ops::DerefMut for $name {
164            fn deref_mut(&mut self) -> &mut Self::Target {
165                &mut self.0
166            }
167        }
168    };
169}
170
171impl_addr!(LocalAddress);
172
173impl LocalAddress {
174    #[inline]
175    pub fn maybe_update(&mut self, other: &Self) {
176        // only update the local address if it's specified
177        ensure!(!other.is_unspecified());
178
179        *self = *other;
180    }
181}
182
183impl_addr!(RemoteAddress);
184
185impl Handle for RemoteAddress {
186    #[inline]
187    fn from_remote_address(remote_address: RemoteAddress) -> Self {
188        remote_address
189    }
190
191    #[inline]
192    fn remote_address(&self) -> RemoteAddress {
193        *self
194    }
195
196    #[inline]
197    fn set_remote_address(&mut self, addr: RemoteAddress) {
198        *self = addr;
199    }
200
201    #[inline]
202    fn local_address(&self) -> LocalAddress {
203        SocketAddressV4::UNSPECIFIED.into()
204    }
205
206    #[inline]
207    fn set_local_address(&mut self, _addr: LocalAddress) {
208        // nothing to update
209    }
210
211    #[inline]
212    fn unmapped_eq(&self, other: &Self) -> bool {
213        Self::unmapped_eq(self, other)
214    }
215
216    #[inline]
217    fn strict_eq(&self, other: &Self) -> bool {
218        PartialEq::eq(self, other)
219    }
220
221    #[inline]
222    fn maybe_update(&mut self, _other: &Self) {
223        // nothing to update
224    }
225}
226
227#[derive(Clone, Copy, Debug, Eq)]
228#[cfg_attr(any(test, feature = "generator"), derive(TypeGenerator))]
229pub struct Tuple {
230    pub remote_address: RemoteAddress,
231    pub local_address: LocalAddress,
232}
233
234impl PartialEq for Tuple {
235    #[inline]
236    fn eq(&self, other: &Self) -> bool {
237        PartialEq::eq(&self.remote_address, &other.remote_address)
238            && PartialEq::eq(&self.local_address, &other.local_address)
239    }
240}
241
242impl Handle for Tuple {
243    #[inline]
244    fn from_remote_address(remote_address: RemoteAddress) -> Self {
245        let local_address = SocketAddressV4::UNSPECIFIED.into();
246        Self {
247            remote_address,
248            local_address,
249        }
250    }
251
252    #[inline]
253    fn remote_address(&self) -> RemoteAddress {
254        self.remote_address
255    }
256
257    #[inline]
258    fn set_remote_address(&mut self, addr: RemoteAddress) {
259        self.remote_address = addr;
260    }
261
262    #[inline]
263    fn local_address(&self) -> LocalAddress {
264        self.local_address
265    }
266
267    #[inline]
268    fn set_local_address(&mut self, addr: LocalAddress) {
269        self.local_address = addr;
270    }
271
272    #[inline]
273    fn unmapped_eq(&self, other: &Self) -> bool {
274        self.local_address.unmapped_eq(&other.local_address)
275            && self.remote_address.unmapped_eq(&other.remote_address)
276    }
277
278    #[inline]
279    fn strict_eq(&self, other: &Self) -> bool {
280        PartialEq::eq(self, other)
281    }
282
283    #[inline]
284    fn maybe_update(&mut self, other: &Self) {
285        self.local_address.maybe_update(&other.local_address);
286    }
287}
288
289//= https://www.rfc-editor.org/rfc/rfc9308#section-8.1
290//# Some UDP protocols are vulnerable to reflection attacks, where an
291//# attacker is able to direct traffic to a third party as a denial of
292//# service.  For example, these source ports are associated with
293//# applications known to be vulnerable to reflection attacks, often due
294//# to server misconfiguration:
295//#
296//# *  port 53 - DNS [RFC1034]
297//#
298//# *  port 123 - NTP [RFC5905]
299//#
300//# *  port 1900 - SSDP [SSDP]
301//#
302//# *  port 5353 - mDNS [RFC6762]
303//#
304//# *  port 11211 - memcache
305
306/// List of ports to refuse connections from. This list must be sorted.
307///
308/// Based on https://quiche.googlesource.com/quiche/+/bac04054bccb2a249d4705ecc94a646404d41c1b/quiche/quic/core/quic_dispatcher.cc#498
309const BLOCKED_PORTS: [u16; 11] = [
310    0,   // We cannot send to port 0 so drop that source port.
311    17,  // Quote of the Day, can loop with QUIC.
312    19,  // Chargen, can loop with QUIC.
313    53,  // DNS, vulnerable to reflection attacks.
314    111, // Portmap.
315    123, // NTP, vulnerable to reflection attacks.
316    137, // NETBIOS Name Service,
317    138, // NETBIOS Datagram Service
318    161, // SNMP.
319    389, // CLDAP.
320    500, // IKE, can loop with QUIC.
321];
322const MAX_BLOCKED_PORT: u16 = BLOCKED_PORTS[BLOCKED_PORTS.len() - 1];
323
324#[inline]
325pub fn remote_port_blocked(port: u16) -> bool {
326    if port > MAX_BLOCKED_PORT {
327        // Early return to avoid iteration if possible
328        return false;
329    }
330
331    for blocked in BLOCKED_PORTS {
332        if port == blocked {
333            return true;
334        }
335    }
336
337    false
338}
339
340// The below ports are also vulnerable to reflection attacks, but are within
341// the original ephemeral port range of 1024–65535, so there is a chance
342// clients may be randomly assigned them. To address this, we can throttle
343// connections using these ports instead of fully blocking them.
344
345/// List of ports to throttle connections from. This list must be sorted.
346///
347/// Based on https://quiche.googlesource.com/quiche/+/bac04054bccb2a249d4705ecc94a646404d41c1b/quiche/quic/core/quic_dispatcher.cc#498
348const THROTTLED_PORTS: [u16; 5] = [
349    1900,  // SSDP, vulnerable to reflection attacks.
350    3702,  // WS-Discovery, vulnerable to reflection attacks.
351    5353,  // mDNS, vulnerable to reflection attacks.
352    5355,  // LLMNR, vulnerable to reflection attacks.
353    11211, // memcache, vulnerable to reflection attacks.
354];
355const MAX_THROTTLED_PORT: u16 = THROTTLED_PORTS[THROTTLED_PORTS.len() - 1];
356pub const THROTTLED_PORTS_LEN: usize = THROTTLED_PORTS.len();
357
358#[inline]
359pub fn remote_port_throttled_index(port: u16) -> Option<usize> {
360    for (idx, throttled_port) in THROTTLED_PORTS.iter().enumerate() {
361        if *throttled_port == port {
362            return Some(idx);
363        }
364    }
365    None
366}
367
368#[inline]
369pub fn remote_port_throttled(port: u16) -> bool {
370    if port > MAX_THROTTLED_PORT {
371        // Early return to avoid iteration if possible
372        return false;
373    }
374
375    for throttled in THROTTLED_PORTS {
376        if port == throttled {
377            return true;
378        }
379    }
380
381    false
382}
383
384#[cfg(test)]
385mod tests {
386    use crate::path::{
387        remote_port_blocked, remote_port_throttled, BLOCKED_PORTS, MAX_BLOCKED_PORT,
388        MAX_THROTTLED_PORT, THROTTLED_PORTS,
389    };
390
391    #[test]
392    fn blocked_ports_is_sorted() {
393        assert_eq!(Some(MAX_BLOCKED_PORT), BLOCKED_PORTS.iter().max().copied());
394
395        let mut sorted = BLOCKED_PORTS.to_vec();
396        sorted.sort_unstable();
397
398        for i in 0..BLOCKED_PORTS.len() {
399            assert_eq!(sorted[i], BLOCKED_PORTS[i]);
400        }
401    }
402
403    #[test]
404    #[cfg_attr(miri, ignore)]
405    fn blocked_port() {
406        for port in 0..u16::MAX {
407            let blocked_expected = BLOCKED_PORTS.iter().copied().any(|blocked| blocked == port);
408            assert_eq!(blocked_expected, remote_port_blocked(port));
409        }
410    }
411
412    #[test]
413    fn throttled_ports_is_sorted() {
414        assert_eq!(
415            Some(MAX_THROTTLED_PORT),
416            THROTTLED_PORTS.iter().max().copied()
417        );
418
419        let mut sorted = THROTTLED_PORTS.to_vec();
420        sorted.sort_unstable();
421
422        for i in 0..THROTTLED_PORTS.len() {
423            assert_eq!(sorted[i], THROTTLED_PORTS[i]);
424        }
425    }
426
427    #[test]
428    #[cfg_attr(miri, ignore)]
429    fn throttled_port() {
430        for port in 0..u16::MAX {
431            let throttled_expected = THROTTLED_PORTS
432                .iter()
433                .copied()
434                .any(|throttled| throttled == port);
435            assert_eq!(throttled_expected, remote_port_throttled(port));
436        }
437    }
438}
439
440#[cfg(any(test, feature = "testing"))]
441pub mod testing {
442    use crate::{
443        connection, event,
444        event::{builder::SocketAddress, IntoEvent},
445    };
446
447    impl event::builder::Path<'_> {
448        pub fn test() -> Self {
449            Self {
450                local_addr: SocketAddress::IpV4 {
451                    ip: &[127, 0, 0, 1],
452                    port: 0,
453                },
454                local_cid: connection::LocalId::TEST_ID.into_event(),
455                remote_addr: SocketAddress::IpV4 {
456                    ip: &[127, 0, 0, 1],
457                    port: 0,
458                },
459                remote_cid: connection::PeerId::TEST_ID.into_event(),
460                id: 0,
461                is_active: false,
462            }
463        }
464    }
465}