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;