s2n_quic_transport/path/
manager.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! This module contains the Manager implementation
5
6use crate::{
7    connection::PeerIdRegistry,
8    endpoint, path,
9    path::{challenge, Path},
10    transmission,
11};
12use s2n_quic_core::{
13    ack,
14    connection::{self, Limits, PeerId},
15    event::{
16        self,
17        builder::{DatagramDropReason, MtuUpdatedCause},
18        IntoEvent,
19    },
20    frame,
21    frame::path_validation,
22    inet::DatagramInfo,
23    packet::number::PacketNumberSpace,
24    path::{
25        migration::{self, Validator as _},
26        mtu, Handle as _, Id,
27    },
28    random,
29    recovery::congestion_controller::{self, Endpoint as _},
30    stateless_reset,
31    time::{timer, Timestamp},
32    transport,
33};
34use smallvec::SmallVec;
35
36/// The amount of Paths that can be maintained without using the heap.
37/// This value is also used to limit the number of connection migrations.
38const MAX_ALLOWED_PATHS: usize = 5;
39
40/// The PathManager handles paths for a specific connection.
41/// It will handle path validation operations, and track the active path for a connection.
42#[derive(Debug)]
43pub struct Manager<Config: endpoint::Config> {
44    /// Path array
45    paths: SmallVec<[Path<Config>; MAX_ALLOWED_PATHS]>,
46
47    /// Registry of `connection::PeerId`s
48    pub(crate) peer_id_registry: PeerIdRegistry,
49
50    /// Index to the active path
51    active: u8,
52
53    /// Index of last known active and validated path.
54    ///
55    /// The Path must be validated and also active at some some point to
56    /// be set as the last_known_active_validated_path.
57    last_known_active_validated_path: Option<u8>,
58
59    /// The current index of a path that is pending packet protection authentication
60    ///
61    /// This field is used to annotate a new path that is pending packet authentication.
62    /// If packet authentication fails then this path index will get reused instead of
63    /// appending another to the list. This is used to prevent an off-path attacker from
64    /// creating new paths with garbage data and preventing the peer to migrate paths.
65    ///
66    /// Note that it doesn't prevent an on-path attacker from observing/forwarding
67    /// authenticated packets from bogus addresses. Because of the current hard limit
68    /// of `MAX_ALLOWED_PATHS`, this will prevent the peer from migrating, if it needs to.
69    /// The `paths` data structure will need to be enhanced to include garbage collection
70    /// of old paths to overcome this limitation.
71    pending_packet_authentication: Option<u8>,
72}
73
74impl<Config: endpoint::Config> Manager<Config> {
75    pub fn new(initial_path: Path<Config>, peer_id_registry: PeerIdRegistry) -> Self {
76        let mut manager = Manager {
77            paths: SmallVec::from_elem(initial_path, 1),
78            peer_id_registry,
79            active: 0,
80            last_known_active_validated_path: None,
81            pending_packet_authentication: None,
82        };
83        manager.paths[0].activated = true;
84        manager.paths[0].is_active = true;
85        manager
86    }
87
88    /// Update the active path
89    fn update_active_path<Pub: event::ConnectionPublisher>(
90        &mut self,
91        new_path_id: Id,
92        random_generator: &mut dyn random::Generator,
93        publisher: &mut Pub,
94    ) -> Result<AmplificationOutcome, transport::Error> {
95        debug_assert!(new_path_id != path_id(self.active));
96
97        let prev_path_id = self.active_path_id();
98
99        let mut peer_connection_id = self[new_path_id].peer_connection_id;
100
101        // The path's connection id might have retired since we last used it. Check if it is still
102        // active, otherwise try and consume a new connection id.
103        if !self.peer_id_registry.is_active(&peer_connection_id) {
104            // If there are no new connection ids the peer is responsible for
105            // providing additional connection ids to continue.
106            //
107            // Insufficient connection ids should not cause the connection to close.
108            // Investigate api after this is used.
109            peer_connection_id = self
110                .peer_id_registry
111                .consume_new_id_for_existing_path(new_path_id, peer_connection_id, publisher)
112                .ok_or(
113                    // TODO: add an event if active path update fails due to insufficient ids
114                    transport::Error::INTERNAL_ERROR,
115                )?;
116        };
117        self[new_path_id].peer_connection_id = peer_connection_id;
118
119        if self.active_path().is_validated() {
120            self.last_known_active_validated_path = Some(self.active);
121        }
122
123        //= https://www.rfc-editor.org/rfc/rfc9000#section-9.3.3
124        //# In response to an apparent migration, endpoints MUST validate the
125        //# previously active path using a PATH_CHALLENGE frame.
126        //
127        // TODO: https://github.com/aws/s2n-quic/issues/711
128        // The usage of 'apparent' is vague and its not clear if the previous path should
129        // always be validated or only if the new active path is not validated.
130        if !self.active_path().is_challenge_pending() {
131            self.set_challenge(self.active_path_id(), random_generator);
132        }
133
134        let amplification_outcome = self.activate_path(publisher, prev_path_id, new_path_id);
135
136        // Restart ECN validation to check that the path still supports ECN
137        let path = self.active_path_mut();
138        path.ecn_controller
139            .restart(path_event!(path, new_path_id), publisher);
140        Ok(amplification_outcome)
141    }
142
143    /// Return the active path
144    #[inline]
145    pub fn active_path(&self) -> &Path<Config> {
146        &self.paths[self.active as usize]
147    }
148
149    /// Return a mutable reference to the active path
150    #[inline]
151    pub fn active_path_mut(&mut self) -> &mut Path<Config> {
152        &mut self.paths[self.active as usize]
153    }
154
155    /// Return the Id of the active path
156    #[inline]
157    pub fn active_path_id(&self) -> Id {
158        path_id(self.active)
159    }
160
161    pub fn check_active_path_is_synced(&self) {
162        if cfg!(debug_assertions) {
163            for (idx, path) in self.paths.iter().enumerate() {
164                assert_eq!(path.is_active, (self.active == idx as u8));
165            }
166        }
167    }
168
169    fn activate_path<Pub: event::ConnectionPublisher>(
170        &mut self,
171        publisher: &mut Pub,
172        prev_path_id: Id,
173        new_path_id: Id,
174    ) -> AmplificationOutcome {
175        self.check_active_path_is_synced();
176        self.active = new_path_id.as_u8();
177        self[prev_path_id].is_active = false;
178        self[new_path_id].is_active = true;
179        self[new_path_id].on_activated();
180        let amplification_outcome = if self[prev_path_id].at_amplification_limit()
181            && !self[new_path_id].at_amplification_limit()
182        {
183            AmplificationOutcome::ActivePathUnblocked
184        } else {
185            AmplificationOutcome::Unchanged
186        };
187        self.check_active_path_is_synced();
188
189        let prev_path = &self[prev_path_id];
190        let new_path = &self[new_path_id];
191        publisher.on_active_path_updated(event::builder::ActivePathUpdated {
192            previous: path_event!(prev_path, prev_path_id),
193            active: path_event!(new_path, new_path_id),
194        });
195
196        amplification_outcome
197    }
198
199    //= https://www.rfc-editor.org/rfc/rfc9000#section-9.3
200    //= type=TODO
201    //= tracking-issue=714
202    //# An endpoint MAY skip validation of a peer address if
203    //# that address has been seen recently.
204    /// Returns the Path for the provided address if the PathManager knows about it
205    #[inline]
206    pub fn path(&self, handle: &Config::PathHandle) -> Option<(Id, &Path<Config>)> {
207        self.paths
208            .iter()
209            .enumerate()
210            .find(|(_id, path)| Path::eq_by_handle(path, handle))
211            .map(|(id, path)| (path_id(id as u8), path))
212    }
213
214    /// Returns the Path for the provided address if the PathManager knows about it
215    #[inline]
216    pub fn path_mut(&mut self, handle: &Config::PathHandle) -> Option<(Id, &mut Path<Config>)> {
217        self.paths
218            .iter_mut()
219            .enumerate()
220            .find(|(_id, path)| Path::eq_by_handle(path, handle))
221            .map(|(id, path)| (path_id(id as u8), path))
222    }
223
224    /// Returns an iterator over all paths pending path_challenge or path_response
225    /// transmission.
226    pub fn paths_pending_validation(&mut self) -> PathsPendingValidation<'_, Config> {
227        PathsPendingValidation::new(self)
228    }
229
230    /// Called when a datagram is received on a connection
231    /// Upon success, returns a `(Id, AmplificationOutcome)` containing the path ID and an
232    /// `AmplificationOutcome` value that indicates if the path had been amplification limited
233    /// prior to receiving the datagram and is now no longer amplification limited.
234    ///
235    /// This function is called prior to packet authentication. If possible add business
236    /// logic to [`Self::on_processed_packet`], which is called after the packet has been
237    /// authenticated.
238    pub fn on_datagram_received<Pub: event::ConnectionPublisher>(
239        &mut self,
240        path_handle: &Config::PathHandle,
241        datagram: &DatagramInfo,
242        handshake_confirmed: bool,
243        congestion_controller_endpoint: &mut Config::CongestionControllerEndpoint,
244        migration_validator: &mut Config::PathMigrationValidator,
245        mtu: &mut mtu::Manager<Config::Mtu>,
246        limits: &Limits,
247        publisher: &mut Pub,
248    ) -> Result<(Id, AmplificationOutcome), DatagramDropReason> {
249        let valid_initial_received = self.valid_initial_received();
250
251        let matched_path = if handshake_confirmed {
252            self.path_mut(path_handle)
253        } else {
254            //= https://www.rfc-editor.org/rfc/rfc9000#section-9
255            //# The design of QUIC relies on endpoints retaining a stable address
256            //# for the duration of the handshake.  An endpoint MUST NOT initiate
257            //# connection migration before the handshake is confirmed, as defined
258            //# in section 4.1.2 of [QUIC-TLS].
259
260            // NOTE: while we must not _initiate_ a migration before the handshake is done,
261            // it doesn't mean we can't handle the packet. So instead we pick the default path.
262            let path_id = self.active_path_id();
263            let path = self.active_path_mut();
264
265            // check if the remote addr changed
266            if !path
267                .handle
268                .remote_address()
269                .unmapped_eq(&path_handle.remote_address())
270            {
271                publisher.on_handshake_remote_address_change_observed(
272                    event::builder::HandshakeRemoteAddressChangeObserved {
273                        local_addr: path.handle.local_address().into_event(),
274                        initial_remote_addr: path.handle.remote_address().into_event(),
275                        remote_addr: path_handle.remote_address().into_event(),
276                    },
277                );
278
279                //= https://www.rfc-editor.org/rfc/rfc9000#section-9
280                //# If a client receives packets from an unknown server address,
281                //# the client MUST discard these packets.
282                if Config::ENDPOINT_TYPE.is_client() {
283                    return Err(DatagramDropReason::UnknownServerAddress);
284                }
285            }
286
287            Some((path_id, path))
288        };
289
290        if let Some((id, path)) = matched_path {
291            let amplification_outcome =
292                path.on_datagram_received(path_handle, datagram, valid_initial_received)?;
293            return Ok((id, amplification_outcome));
294        }
295
296        //= https://www.rfc-editor.org/rfc/rfc9000#section-9
297        //# If a client receives packets from an unknown server address,
298        //# the client MUST discard these packets.
299        if Config::ENDPOINT_TYPE.is_client() {
300            return Err(DatagramDropReason::UnknownServerAddress);
301        }
302
303        //= https://www.rfc-editor.org/rfc/rfc9000#section-9
304        //# If the peer
305        //# violates this requirement, the endpoint MUST either drop the incoming
306        //# packets on that path without generating a Stateless Reset or proceed
307        //# with path validation and allow the peer to migrate.  Generating a
308        //# Stateless Reset or closing the connection would allow third parties
309        //# in the network to cause connections to close by spoofing or otherwise
310        //# manipulating observed traffic.
311
312        self.handle_connection_migration(
313            path_handle,
314            datagram,
315            congestion_controller_endpoint,
316            migration_validator,
317            mtu,
318            limits,
319            publisher,
320        )
321    }
322
323    fn handle_connection_migration<Pub: event::ConnectionPublisher>(
324        &mut self,
325        path_handle: &Config::PathHandle,
326        datagram: &DatagramInfo,
327        congestion_controller_endpoint: &mut Config::CongestionControllerEndpoint,
328        migration_validator: &mut Config::PathMigrationValidator,
329        mtu: &mut mtu::Manager<Config::Mtu>,
330        limits: &Limits,
331        publisher: &mut Pub,
332    ) -> Result<(Id, AmplificationOutcome), DatagramDropReason> {
333        //= https://www.rfc-editor.org/rfc/rfc9000#section-9
334        //# Clients are responsible for initiating all migrations.
335        debug_assert!(Config::ENDPOINT_TYPE.is_server());
336
337        let remote_address = path_handle.remote_address();
338        let local_address = path_handle.local_address();
339        let active_local_addr = self.active_path().local_address();
340        let active_remote_addr = self.active_path().remote_address();
341
342        // TODO set alpn if available
343        let attempt: migration::Attempt = migration::AttemptBuilder {
344            active_path: event::builder::Path {
345                local_addr: active_local_addr.into_event(),
346                local_cid: self.active_path().local_connection_id.into_event(),
347                remote_addr: active_remote_addr.into_event(),
348                remote_cid: self.active_path().peer_connection_id.into_event(),
349                id: self.active_path_id().into_event(),
350                is_active: true,
351            }
352            .into_event(),
353            packet: migration::PacketInfoBuilder {
354                remote_address: &remote_address,
355                local_address: &local_address,
356            }
357            .into(),
358        }
359        .into();
360
361        match migration_validator.on_migration_attempt(&attempt) {
362            migration::Outcome::Allow => {
363                // no-op: allow the migration to continue
364            }
365            migration::Outcome::Deny(reason) => {
366                publisher.on_connection_migration_denied(reason.into_event());
367                return Err(DatagramDropReason::RejectedConnectionMigration {
368                    reason: reason.into_event().reason,
369                });
370            }
371            _ => {
372                unimplemented!("unimplemented migration outcome");
373            }
374        }
375
376        // Determine which index will be used for the newly created path
377        //
378        // If a previously allocated path failed to contain an authenticated packet, we
379        // use that index instead of pushing on to the end.
380        let new_path_idx = if let Some(idx) = self.pending_packet_authentication {
381            idx as _
382        } else {
383            let idx = self.paths.len();
384            self.pending_packet_authentication = Some(idx as _);
385            idx
386        };
387
388        // TODO: Support deletion of old paths: https://github.com/aws/s2n-quic/issues/741
389        // The current path manager implementation does not delete or reuse indices
390        // in the path array. This can result in an unbounded number of paths. To prevent
391        // this we limit the max number of paths per connection.
392        if new_path_idx >= MAX_ALLOWED_PATHS {
393            return Err(DatagramDropReason::PathLimitExceeded);
394        }
395        let new_path_id = path_id(new_path_idx as u8);
396
397        //= https://www.rfc-editor.org/rfc/rfc9000#section-9.4
398        //= type=TODO
399        //# Because port-only changes are commonly the
400        //# result of NAT rebinding or other middlebox activity, the endpoint MAY
401        //# instead retain its congestion control state and round-trip estimate
402        //# in those cases instead of reverting to initial values.
403
404        //= https://www.rfc-editor.org/rfc/rfc9000#section-9.4
405        //# On confirming a peer's ownership of its new address, an endpoint MUST
406        //# immediately reset the congestion controller and round-trip time
407        //# estimator for the new path to initial values (see Appendices A.3 and
408        //# B.3 of [QUIC-RECOVERY]) unless the only change in the peer's address
409        //# is its port number.
410        // Since we maintain a separate congestion controller and round-trip time
411        // estimator for the new path, and they are initialized with initial values,
412        // we do not need to reset congestion controller and round-trip time estimator
413        // again on confirming the peer's ownership of its new address.
414        let rtt = self
415            .active_path()
416            .rtt_estimator
417            .for_new_path(limits.initial_round_trip_time());
418
419        let mtu_config = mtu.config(&remote_address).map_err(|_err| {
420            event::builder::DatagramDropReason::InvalidMtuConfiguration {
421                endpoint_mtu_config: mtu.endpoint_config().into_event(),
422            }
423        })?;
424
425        let path_info = congestion_controller::PathInfo::new(&mtu_config, &remote_address);
426        let cc = congestion_controller_endpoint.new_congestion_controller(path_info);
427
428        let peer_connection_id = {
429            if self.active_path().local_connection_id != datagram.destination_connection_id {
430                //= https://www.rfc-editor.org/rfc/rfc9000#section-9.5
431                //# Similarly, an endpoint MUST NOT reuse a connection ID when sending to
432                //# more than one destination address.
433
434                // The peer changed destination CIDs, so we will attempt to switch to a new
435                // destination CID as well. This could still just be a NAT rebind though, so
436                // we continue with the existing destination CID if there isn't a new one
437                // available.
438                self.peer_id_registry
439                    .consume_new_id_for_new_path()
440                    .unwrap_or(self.active_path().peer_connection_id)
441            } else {
442                //= https://www.rfc-editor.org/rfc/rfc9000#section-9.5
443                //# Due to network changes outside
444                //# the control of its peer, an endpoint might receive packets from a new
445                //# source address with the same Destination Connection ID field value,
446                //# in which case it MAY continue to use the current connection ID with
447                //# the new remote address while still sending from the same local
448                //# address.
449                self.active_path().peer_connection_id
450            }
451        };
452
453        //= https://www.rfc-editor.org/rfc/rfc9000#section-9.3.1
454        //# Until a peer's address is deemed valid, an endpoint limits
455        //# the amount of data it sends to that address; see Section 8.
456        //
457        //= https://www.rfc-editor.org/rfc/rfc9000#section-9.3
458        //# An endpoint MAY send data to an unvalidated peer address, but it MUST
459        //# protect against potential attacks as described in Sections 9.3.1 and
460        //# 9.3.2.
461        //
462        // New paths for a Server endpoint start in AmplificationLimited state until they are validated.
463        let mut path = Path::new(
464            *path_handle,
465            peer_connection_id,
466            datagram.destination_connection_id,
467            rtt,
468            cc,
469            true,
470            mtu_config,
471            limits.anti_amplification_multiplier(),
472            limits.pto_jitter_percentage(),
473        );
474
475        let amplification_outcome = path.on_bytes_received(datagram.payload_len);
476
477        let active_path = self.active_path();
478        let active_path_id = self.active_path_id();
479        publisher.on_path_created(event::builder::PathCreated {
480            active: path_event!(active_path, active_path_id),
481            new: path_event!(path, new_path_id),
482        });
483
484        publisher.on_mtu_updated(event::builder::MtuUpdated {
485            path_id: new_path_id.into_event(),
486            mtu: path.mtu_controller.max_datagram_size() as u16,
487            cause: MtuUpdatedCause::NewPath,
488            search_complete: path.mtu_controller.is_search_completed(),
489        });
490
491        // create a new path
492        if new_path_idx < self.paths.len() {
493            self.paths[new_path_idx] = path;
494        } else {
495            self.paths.push(path);
496        }
497
498        Ok((new_path_id, amplification_outcome))
499    }
500
501    fn set_challenge(&mut self, path_id: Id, random_generator: &mut dyn random::Generator) {
502        //= https://www.rfc-editor.org/rfc/rfc9000#section-8.2.1
503        //# The endpoint MUST use unpredictable data in every PATH_CHALLENGE
504        //# frame so that it can associate the peer's response with the
505        //# corresponding PATH_CHALLENGE.
506        let mut data: challenge::Data = [0; 8];
507        random_generator.public_random_fill(&mut data);
508
509        //= https://www.rfc-editor.org/rfc/rfc9000#section-8.2.4
510        //# Endpoints SHOULD abandon path validation based on a timer.
511        //
512        //= https://www.rfc-editor.org/rfc/rfc9000#section-8.2.4
513        //# When
514        //# setting this timer, implementations are cautioned that the new path
515        //# could have a longer round-trip time than the original.  A value of
516        //# three times the larger of the current PTO or the PTO for the new path
517        //# (using kInitialRtt, as defined in [QUIC-RECOVERY]) is RECOMMENDED.
518        let abandon_duration = self[path_id].pto_period(PacketNumberSpace::ApplicationData);
519        let abandon_duration = 3 * abandon_duration.max(
520            self.active_path()
521                .pto_period(PacketNumberSpace::ApplicationData),
522        );
523
524        //= https://www.rfc-editor.org/rfc/rfc9000#section-9
525        //# An endpoint MUST
526        //# perform path validation (Section 8.2) if it detects any change to a
527        //# peer's address, unless it has previously validated that address.
528
529        //= https://www.rfc-editor.org/rfc/rfc9000#section-9.6.3
530        //# Servers SHOULD initiate path validation to the client's new address
531        //# upon receiving a probe packet from a different address.
532        let challenge = challenge::Challenge::new(abandon_duration, data);
533        self[path_id].set_challenge(challenge);
534    }
535
536    /// Returns true if a valid initial packet has been received
537    pub fn valid_initial_received(&self) -> bool {
538        if Config::ENDPOINT_TYPE.is_server() {
539            // Since the path manager is owned by a connection, and a connection can only exist
540            // on the server if a valid initial has been received, we immediately return true
541            true
542        } else {
543            // A QUIC client uses a randomly generated value as the Initial Connection Id
544            // until it receives a packet from the Server. Upon receiving a Server packet,
545            // the Client switches to using the new Destination Connection Id. The
546            // PeerIdRegistry is expected to be empty until the first Server initial packet.
547            !self.peer_id_registry.is_empty()
548        }
549    }
550
551    /// Writes any frames the path manager wishes to transmit to the given context
552    #[inline]
553    pub fn on_transmit<W: transmission::WriteContext>(&mut self, context: &mut W) {
554        self.peer_id_registry.on_transmit(context)
555
556        // TODO Add in per-path constraints based on whether a Challenge needs to be
557        // transmitted.
558    }
559
560    /// Called when packets are acknowledged
561    #[inline]
562    pub fn on_packet_ack<A: ack::Set>(&mut self, ack_set: &A) {
563        self.peer_id_registry.on_packet_ack(ack_set);
564    }
565
566    /// Called when packets are lost
567    #[inline]
568    pub fn on_packet_loss<A: ack::Set>(&mut self, ack_set: &A) {
569        self.peer_id_registry.on_packet_loss(ack_set);
570    }
571
572    #[inline]
573    pub fn on_path_challenge(
574        &mut self,
575        path_id: Id,
576        challenge: &frame::path_challenge::PathChallenge,
577    ) {
578        self[path_id].on_path_challenge(challenge.data);
579    }
580
581    //= https://www.rfc-editor.org/rfc/rfc9000#section-8.2.3
582    //# Path validation succeeds when a PATH_RESPONSE frame is received that
583    //# contains the data that was sent in a previous PATH_CHALLENGE frame.
584    //# A PATH_RESPONSE frame received on any network path validates the path
585    //# on which the PATH_CHALLENGE was sent.
586    #[inline]
587    pub fn on_path_response<Pub: event::ConnectionPublisher>(
588        &mut self,
589        response: &frame::PathResponse,
590        publisher: &mut Pub,
591    ) -> AmplificationOutcome {
592        //= https://www.rfc-editor.org/rfc/rfc9000#section-8.2.2
593        //# A PATH_RESPONSE frame MUST be sent on the network path where the
594        //# PATH_CHALLENGE frame was received.
595
596        //= https://www.rfc-editor.org/rfc/rfc9000#section-8.2.2
597        //# This requirement MUST NOT be enforced by the endpoint that initiates
598        //# path validation, as that would enable an attack on migration; see
599        //# Section 9.3.3.
600        //
601        // The 'attack on migration' refers to the following scenario:
602        // If the packet forwarded by the off-attacker is received before the
603        // genuine packet, the genuine packet will be discarded as a duplicate
604        // and path validation will fail.
605
606        //= https://www.rfc-editor.org/rfc/rfc9000#section-8.2.3
607        //# A PATH_RESPONSE frame received on any network path validates the path
608        //# on which the PATH_CHALLENGE was sent.
609
610        for (id, path) in self.paths.iter_mut().enumerate() {
611            let was_amplification_limited = path.at_amplification_limit();
612            if path.on_path_response(response.data) {
613                let id = id as u64;
614                publisher.on_path_challenge_updated(event::builder::PathChallengeUpdated {
615                    path_challenge_status: event::builder::PathChallengeStatus::Validated,
616                    path: path_event!(path, id),
617                    challenge_data: path.challenge.challenge_data().into_event(),
618                });
619                // A path was validated so check if it becomes the new
620                // last_known_active_validated_path
621                if path.is_activated() {
622                    self.last_known_active_validated_path = Some(id as u8);
623                }
624                // The path is now validated, so it is unblocked if it was
625                // previously amplification limited
626                debug_assert!(!path.at_amplification_limit());
627                return match (was_amplification_limited, path.is_active()) {
628                    (true, true) => AmplificationOutcome::ActivePathUnblocked,
629                    (true, false) => AmplificationOutcome::InactivePathUnblocked,
630                    _ => AmplificationOutcome::Unchanged,
631                };
632            }
633        }
634        AmplificationOutcome::Unchanged
635    }
636
637    /// Process a packet and update internal state.
638    ///
639    /// Check if the packet is a non-probing (path validation) packet and attempt to
640    /// update the active path for the connection.
641    ///
642    /// Returns `Ok(true)` if the packet caused the active path to change from a path
643    /// blocked by amplification limits to a path not blocked by amplification limits.
644    pub fn on_processed_packet<Pub: event::ConnectionPublisher>(
645        &mut self,
646        path_id: Id,
647        source_connection_id: Option<PeerId>,
648        path_validation_probing: path_validation::Probe,
649        random_generator: &mut dyn random::Generator,
650        publisher: &mut Pub,
651    ) -> Result<AmplificationOutcome, transport::Error> {
652        //= https://www.rfc-editor.org/rfc/rfc9000#section-7.2
653        //# A client MUST change the Destination Connection ID it uses for
654        //# sending packets in response to only the first received Initial or
655        //# Retry packet.
656        if !self.valid_initial_received() {
657            //= https://www.rfc-editor.org/rfc/rfc9000#section-7.2
658            //# Until a packet is received from the server, the client MUST
659            //# use the same Destination Connection ID value on all packets in this
660            //# connection.
661            //
662            // This is the first Server packet so start using the newly provided
663            // connection id form the Server.
664            assert!(Config::ENDPOINT_TYPE.is_client());
665            if let Some(source_connection_id) = source_connection_id {
666                self[path_id].peer_connection_id = source_connection_id;
667                self.peer_id_registry
668                    .register_initial_connection_id(source_connection_id);
669            }
670        }
671
672        // Remove the temporary status after successfully processing a packet
673        if self.pending_packet_authentication == Some(path_id.as_u8()) {
674            self.pending_packet_authentication = None;
675
676            // We can finally arm the challenge after authenticating the packet
677            self.set_challenge(path_id, random_generator);
678        }
679
680        let mut amplification_outcome = AmplificationOutcome::Unchanged;
681
682        //= https://www.rfc-editor.org/rfc/rfc9000#section-9.2
683        //# An endpoint can migrate a connection to a new local address by
684        //# sending packets containing non-probing frames from that address.
685        if !path_validation_probing.is_probing() && self.active_path_id() != path_id {
686            amplification_outcome =
687                self.update_active_path(path_id, random_generator, publisher)?;
688            //= https://www.rfc-editor.org/rfc/rfc9000#section-9.3
689            //# After changing the address to which it sends non-probing packets, an
690            //# endpoint can abandon any path validation for other addresses.
691            //
692            // Abandon other path validations only if the active path is validated since an
693            // attacker could block all path validation attempts simply by forwarding packets.
694            if self.active_path().is_validated() {
695                self.abandon_all_path_challenges(publisher);
696            } else if !self.active_path().is_challenge_pending() {
697                //= https://www.rfc-editor.org/rfc/rfc9000#section-9.3
698                //# If the recipient permits the migration, it MUST send subsequent
699                //# packets to the new peer address and MUST initiate path validation
700                //# (Section 8.2) to verify the peer's ownership of the address if
701                //# validation is not already underway.
702                self.set_challenge(self.active_path_id(), random_generator);
703            }
704        }
705        Ok(amplification_outcome)
706    }
707
708    #[inline]
709    fn abandon_all_path_challenges<Pub: event::ConnectionPublisher>(
710        &mut self,
711        publisher: &mut Pub,
712    ) {
713        for (idx, path) in self.paths.iter_mut().enumerate() {
714            let path_id = idx as u64;
715            path.abandon_challenge(publisher, path_id);
716        }
717    }
718
719    //= https://www.rfc-editor.org/rfc/rfc9000#section-10.3
720    //# Tokens are
721    //# invalidated when their associated connection ID is retired via a
722    //# RETIRE_CONNECTION_ID frame (Section 19.16).
723    pub fn on_connection_id_retire(&self, _connection_id: &connection::LocalId) {
724        // TODO invalidate any tokens issued under this connection id
725    }
726
727    /// Called when a NEW_CONNECTION_ID frame is received from the peer
728    pub fn on_new_connection_id<Pub: event::ConnectionPublisher>(
729        &mut self,
730        connection_id: &connection::PeerId,
731        sequence_number: u32,
732        retire_prior_to: u32,
733        stateless_reset_token: &stateless_reset::Token,
734        publisher: &mut Pub,
735    ) -> Result<(), transport::Error> {
736        // Retire and register connection ID
737        self.peer_id_registry.on_new_connection_id(
738            connection_id,
739            sequence_number,
740            retire_prior_to,
741            stateless_reset_token,
742        )?;
743
744        //= https://www.rfc-editor.org/rfc/rfc9000#section-5.1.2
745        //# Upon receipt of an increased Retire Prior To field, the peer MUST
746        //# stop using the corresponding connection IDs and retire them with
747        //# RETIRE_CONNECTION_ID frames before adding the newly provided
748        //# connection ID to the set of active connection IDs.
749        let active_path_connection_id = self.active_path().peer_connection_id;
750
751        if !self.peer_id_registry.is_active(&active_path_connection_id) {
752            self.active_path_mut().peer_connection_id = self
753                .peer_id_registry
754                .consume_new_id_for_existing_path(
755                    self.active_path_id(),
756                    active_path_connection_id,
757                    publisher,
758                )
759                .ok_or(transport::Error::PROTOCOL_VIOLATION.with_reason(
760                    "A NEW_CONNECTION_ID frame was received that retired the active path's \
761                    connection ID and no unused connection IDs remain to replace it",
762                ))?
763        }
764
765        Ok(())
766    }
767
768    /// Called when the connection timer expired
769    ///
770    /// Returns `Ok(true)` if the timeout caused the active path to change from a path
771    /// blocked by amplification limits to a path not blocked by amplification limits.
772    /// This can occur if the active path was amplification limited and failed path validation.
773    pub fn on_timeout<Pub: event::ConnectionPublisher>(
774        &mut self,
775        timestamp: Timestamp,
776        random_generator: &mut dyn random::Generator,
777        publisher: &mut Pub,
778    ) -> Result<AmplificationOutcome, connection::Error> {
779        for (id, path) in self.paths.iter_mut().enumerate() {
780            path.on_timeout(timestamp, path_id(id as u8), random_generator, publisher);
781        }
782
783        let mut amplification_outcome = AmplificationOutcome::Unchanged;
784
785        if self.active_path().failed_validation() {
786            match self.last_known_active_validated_path {
787                Some(last_known_active_validated_path) => {
788                    //= https://www.rfc-editor.org/rfc/rfc9000#section-9.3.2
789                    //# To protect the connection from failing due to such a spurious
790                    //# migration, an endpoint MUST revert to using the last validated peer
791                    //# address when validation of a new peer address fails.
792                    let prev_path_id = path_id(self.active);
793                    let new_path_id = path_id(last_known_active_validated_path);
794                    amplification_outcome =
795                        self.activate_path(publisher, prev_path_id, new_path_id);
796                    self.last_known_active_validated_path = None;
797                }
798                None => {
799                    //= https://www.rfc-editor.org/rfc/rfc9000#section-9
800                    //# When an endpoint has no validated path on which to send packets, it
801                    //# MAY discard connection state.
802
803                    //= https://www.rfc-editor.org/rfc/rfc9000#section-9
804                    //= type=TODO
805                    //= tracking-issue=713
806                    //# An endpoint capable of connection
807                    //# migration MAY wait for a new path to become available before
808                    //# discarding connection state.
809
810                    //= https://www.rfc-editor.org/rfc/rfc9000#section-9.3.2
811                    //# If an endpoint has no state about the last validated peer address, it
812                    //# MUST close the connection silently by discarding all connection
813                    //# state.
814
815                    //= https://www.rfc-editor.org/rfc/rfc9000#section-10
816                    //# An endpoint MAY discard connection state if it does not have a
817                    //# validated path on which it can send packets; see Section 8.2
818                    return Err(connection::Error::no_valid_path());
819                }
820            }
821        }
822
823        Ok(amplification_outcome)
824    }
825
826    /// true if ALL paths are amplification_limited
827    #[inline]
828    pub fn is_amplification_limited(&self) -> bool {
829        self.paths
830            .iter()
831            .all(|path| path.transmission_constraint().is_amplification_limited())
832    }
833
834    /// true if ANY of the paths can transmit
835    #[inline]
836    pub fn can_transmit(&self, interest: transmission::Interest) -> bool {
837        self.paths.iter().any(|path| {
838            let constraint = path.transmission_constraint();
839            interest.can_transmit(constraint)
840        })
841    }
842
843    #[inline]
844    pub fn transmission_constraint(&self) -> transmission::Constraint {
845        // Return the lowest constraint which will ensure we don't get blocked on transmission by a single path
846        self.paths
847            .iter()
848            .map(|path| path.transmission_constraint())
849            .min()
850            .unwrap_or(transmission::Constraint::None)
851    }
852
853    #[cfg(test)]
854    pub(crate) fn activate_path_for_test(&mut self, path_id: Id) {
855        self[path_id].activated = true;
856        self[path_id].is_active = true;
857        self.active = path_id.as_u8();
858    }
859}
860
861#[inline]
862fn path_id(id: u8) -> path::Id {
863    // Safety: The path::Manager is responsible for managing path ID and is thus
864    //         responsible for using them safely
865    unsafe { path::Id::new(id) }
866}
867
868impl<Config: endpoint::Config> timer::Provider for Manager<Config> {
869    #[inline]
870    fn timers<Q: timer::Query>(&self, query: &mut Q) -> timer::Result {
871        for path in self.paths.iter() {
872            path.timers(query)?;
873        }
874
875        Ok(())
876    }
877}
878
879/// Iterate over all paths that have an interest in sending PATH_CHALLENGE
880/// or PATH_RESPONSE frames.
881///
882/// This abstraction allows for iterating over pending paths while also
883/// having mutable access to the Manager.
884pub struct PathsPendingValidation<'a, Config: endpoint::Config> {
885    index: u8,
886    path_manager: &'a mut Manager<Config>,
887}
888
889impl<'a, Config: endpoint::Config> PathsPendingValidation<'a, Config> {
890    pub fn new(path_manager: &'a mut Manager<Config>) -> Self {
891        Self {
892            index: 0,
893            path_manager,
894        }
895    }
896
897    #[inline]
898    pub fn next_path(&mut self) -> Option<(Id, &mut Manager<Config>)> {
899        loop {
900            let path = self.path_manager.paths.get(self.index as usize)?;
901
902            // Advance the index otherwise this will continue to process the
903            // same path.
904            self.index += 1;
905
906            if path.is_challenge_pending() || path.is_response_pending() {
907                return Some((path_id(self.index - 1), self.path_manager));
908            }
909        }
910    }
911}
912
913impl<Config: endpoint::Config> transmission::interest::Provider for Manager<Config> {
914    #[inline]
915    fn transmission_interest<Q: transmission::interest::Query>(
916        &self,
917        query: &mut Q,
918    ) -> transmission::interest::Result {
919        self.peer_id_registry.transmission_interest(query)?;
920
921        for path in self.paths.iter() {
922            // query PATH_CHALLENGE and PATH_RESPONSE interest for each path
923            path.transmission_interest(query)?;
924        }
925
926        Ok(())
927    }
928}
929
930impl<Config: endpoint::Config> core::ops::Index<Id> for Manager<Config> {
931    type Output = Path<Config>;
932
933    #[inline]
934    fn index(&self, id: Id) -> &Self::Output {
935        &self.paths[id.as_u8() as usize]
936    }
937}
938
939impl<Config: endpoint::Config> core::ops::IndexMut<Id> for Manager<Config> {
940    #[inline]
941    fn index_mut(&mut self, id: Id) -> &mut Self::Output {
942        &mut self.paths[id.as_u8() as usize]
943    }
944}
945
946#[derive(Debug, Eq, PartialEq)]
947#[must_use]
948pub enum AmplificationOutcome {
949    /// The active path was amplification limited and is now not amplification limited
950    ActivePathUnblocked,
951    /// A path other than the active path was amplification limited and is now not amplification limited
952    InactivePathUnblocked,
953    /// The path has remained amplification limited or unblocked
954    Unchanged,
955}
956
957impl AmplificationOutcome {
958    /// The active path was amplification limited and is now not amplification limited
959    pub fn is_active_path_unblocked(&self) -> bool {
960        matches!(self, AmplificationOutcome::ActivePathUnblocked)
961    }
962
963    /// A path other than the active path was amplification limited and is now not amplification limited
964    pub fn is_inactivate_path_unblocked(&self) -> bool {
965        matches!(self, AmplificationOutcome::InactivePathUnblocked)
966    }
967
968    /// The path has remained amplification limited or unblocked
969    pub fn is_unchanged(&self) -> bool {
970        matches!(self, AmplificationOutcome::Unchanged)
971    }
972}
973
974macro_rules! path_event {
975    ($path:ident, $path_id:ident) => {{
976        event::builder::Path {
977            local_addr: $path.local_address().into_event(),
978            local_cid: $path.local_connection_id.into_event(),
979            remote_addr: $path.remote_address().into_event(),
980            remote_cid: $path.peer_connection_id.into_event(),
981            id: $path_id.into_event(),
982            is_active: $path.is_active(),
983        }
984    }};
985}
986
987pub(crate) use path_event;
988
989#[cfg(test)]
990mod tests;
991
992#[cfg(test)]
993mod fuzz_target;