s2n_quic_core/path/migration.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    event::{
7        api::{Path, SocketAddress},
8        IntoEvent,
9    },
10    inet,
11};
12
13#[derive(Debug)]
14#[non_exhaustive]
15pub struct Attempt<'a> {
16    /// The path that the connection is currently actively using
17    pub active_path: Path<'a>,
18    /// Information about the packet triggering the migration attempt
19    pub packet: PacketInfo<'a>,
20}
21
22#[derive(Debug)]
23pub struct AttemptBuilder<'a> {
24    /// The path that the connection is currently actively using
25    pub active_path: Path<'a>,
26    /// Information about the packet triggering the migration attempt
27    pub packet: PacketInfo<'a>,
28}
29
30impl<'a> From<AttemptBuilder<'a>> for Attempt<'a> {
31    #[inline]
32    fn from(builder: AttemptBuilder<'a>) -> Self {
33        Self {
34            active_path: builder.active_path,
35            packet: builder.packet,
36        }
37    }
38}
39
40#[derive(Debug)]
41#[non_exhaustive]
42pub struct PacketInfo<'a> {
43    pub remote_address: SocketAddress<'a>,
44    pub local_address: SocketAddress<'a>,
45}
46
47#[derive(Debug)]
48pub struct PacketInfoBuilder<'a> {
49    pub remote_address: &'a inet::SocketAddress,
50    pub local_address: &'a inet::SocketAddress,
51}
52
53impl<'a> From<PacketInfoBuilder<'a>> for PacketInfo<'a> {
54    #[inline]
55    fn from(builder: PacketInfoBuilder<'a>) -> Self {
56        Self {
57            remote_address: builder.remote_address.into_event(),
58            local_address: builder.local_address.into_event(),
59        }
60    }
61}
62
63// TODO: Add an outcome that allows the connection to be closed/stateless reset https://github.com/aws/s2n-quic/issues/317
64
65#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66#[non_exhaustive]
67pub enum Outcome {
68    /// Allows the path migration attempt to continue
69    ///
70    /// Note that path validation will still be attempted as described in
71    /// [Section 8.2](https://datatracker.ietf.org/doc/html/rfc9000#section-8.2).
72    Allow,
73
74    /// Rejects a path migration attempt
75    ///
76    /// The connection will drop the packet that attempted to migrate and not reserve any state
77    /// for the new path.
78    Deny(DenyReason),
79    // Additional outcomes must be handled in the path::Manager
80}
81
82#[derive(Clone, Copy, Debug, PartialEq, Eq)]
83pub enum DenyReason {
84    // The new address uses a port that is blocked
85    BlockedPort,
86    // The new address uses a port in a different scope
87    PortScopeChanged,
88    // The new address uses an IP in a different scope
89    IpScopeChanged,
90    // All connection migrations are disabled
91    ConnectionMigrationDisabled,
92}
93
94impl IntoEvent<event::builder::ConnectionMigrationDenied> for DenyReason {
95    #[inline]
96    fn into_event(self) -> event::builder::ConnectionMigrationDenied {
97        let reason = match self {
98            DenyReason::BlockedPort => event::builder::MigrationDenyReason::BlockedPort,
99            DenyReason::PortScopeChanged => event::builder::MigrationDenyReason::PortScopeChanged,
100            DenyReason::IpScopeChanged => event::builder::MigrationDenyReason::IpScopeChange,
101            DenyReason::ConnectionMigrationDisabled => {
102                event::builder::MigrationDenyReason::ConnectionMigrationDisabled
103            }
104        };
105        event::builder::ConnectionMigrationDenied { reason }
106    }
107}
108
109/// Validates a path migration attempt from an active path to another
110pub trait Validator: 'static + Send {
111    /// Called on each connection migration attempt for a connection
112    fn on_migration_attempt(&mut self, attempt: &Attempt) -> Outcome;
113}
114
115pub mod default {
116    use super::*;
117    use crate::path::remote_port_blocked;
118
119    #[derive(Debug, Default)]
120    pub struct Validator;
121
122    impl super::Validator for Validator {
123        #[inline]
124        fn on_migration_attempt(&mut self, attempt: &Attempt) -> Outcome {
125            let active_addr = to_addr(&attempt.active_path.remote_addr);
126            let packet_addr = to_addr(&attempt.packet.remote_address);
127
128            // Block migrations to a port that is blocked
129            if remote_port_blocked(packet_addr.port()) {
130                return Outcome::Deny(DenyReason::BlockedPort);
131            }
132
133            //= https://www.rfc-editor.org/rfc/rfc9000#section-21.5.6
134            //# it might be possible over time to identify
135            //# specific UDP ports that are common targets of attacks or particular
136            //# patterns in datagrams that are used for attacks.  Endpoints MAY
137            //# choose to avoid sending datagrams to these ports or not send
138            //# datagrams that match these patterns prior to validating the
139            //# destination address.
140
141            // NOTE: this may cause reachability issues if a peer or NAT use different
142            //       port scopes for the same connection. Additional research may
143            //       be required to determine if this countermeasure needs to be relaxed.
144            if PortScope::new(active_addr.port()) != PortScope::new(packet_addr.port()) {
145                return Outcome::Deny(DenyReason::PortScopeChanged);
146            }
147
148            //= https://www.rfc-editor.org/rfc/rfc9000#section-21.5.6
149            //# Endpoints MAY prevent connection attempts or
150            //# migration to a loopback address.  Endpoints SHOULD NOT allow
151            //# connections or migration to a loopback address if the same service
152            //# was previously available at a different interface or if the address
153            //# was provided by a service at a non-loopback address.
154
155            //= https://www.rfc-editor.org/rfc/rfc9000#section-21.5.6
156            //# Similarly, endpoints could regard a change in address to a link-local
157            //# address [RFC4291] or an address in a private-use range [RFC1918] from
158            //# a global, unique-local [RFC4193], or non-private address as a
159            //# potential attempt at request forgery.
160
161            // Here, we ensure the ip scope match so peers are unable to change after
162            // establishing a connection
163            match (active_addr.unicast_scope(), packet_addr.unicast_scope()) {
164                (Some(a), Some(b)) if a == b => Outcome::Allow,
165                _ => Outcome::Deny(DenyReason::IpScopeChanged),
166            }
167        }
168    }
169
170    #[derive(Debug, PartialEq, Eq)]
171    enum PortScope {
172        System,
173        User,
174        Dynamic,
175    }
176
177    impl PortScope {
178        #[inline]
179        pub const fn new(value: u16) -> Self {
180            //= https://www.rfc-editor.org/rfc/rfc6335#section-6
181            //# o  the System Ports, also known as the Well Known Ports, from 0-1023
182            //#    (assigned by IANA)
183            //#
184            //# o  the User Ports, also known as the Registered Ports, from 1024-
185            //#    49151 (assigned by IANA)
186            //#
187            //# o  the Dynamic Ports, also known as the Private or Ephemeral Ports,
188            //#    from 49152-65535 (never assigned)
189            match value {
190                0..=1023 => Self::System,
191                1024..=49151 => Self::User,
192                49152..=65535 => Self::Dynamic,
193            }
194        }
195    }
196
197    fn to_addr(addr: &SocketAddress) -> crate::inet::SocketAddress {
198        match addr {
199            SocketAddress::IpV4 { ip, port, .. } => {
200                crate::inet::SocketAddressV4::new(**ip, *port).into()
201            }
202            SocketAddress::IpV6 { ip, port, .. } => {
203                crate::inet::SocketAddressV6::new(**ip, *port).into()
204            }
205        }
206    }
207}
208
209pub mod disabled {
210    use super::*;
211
212    #[derive(Debug, Default)]
213    pub struct Validator;
214
215    impl super::Validator for Validator {
216        fn on_migration_attempt(&mut self, _attempt: &Attempt) -> Outcome {
217            // deny all migration attempts
218            Outcome::Deny(DenyReason::ConnectionMigrationDisabled)
219        }
220    }
221}
222
223#[cfg(any(test, feature = "testing"))]
224pub mod allow_all {
225    use super::*;
226
227    #[derive(Debug, Default)]
228    pub struct Validator;
229
230    impl super::Validator for Validator {
231        fn on_migration_attempt(&mut self, _attempt: &Attempt) -> Outcome {
232            // allow all migration attempts
233            Outcome::Allow
234        }
235    }
236}