Skip to content

Commit c36a81c

Browse files
feat(s2n-quic): allow configuring initial RTT (#2047)
* feat(s2n-quic): allow configuring initial RTT * refactor * add integration test * fix tests * fix path manager test * fix more tests * fix more tests * PR feedback * Use ensure macro * update rust nightly toolchain
1 parent c17d4f2 commit c36a81c

File tree

11 files changed

+175
-41
lines changed

11 files changed

+175
-41
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ env:
1515
RUST_BACKTRACE: 1
1616
# Pin the nightly toolchain to prevent breakage.
1717
# This should be occasionally updated.
18-
RUST_NIGHTLY_TOOLCHAIN: nightly-2023-06-12
18+
RUST_NIGHTLY_TOOLCHAIN: nightly-2023-11-27
1919
CDN: https://dnglbrstg7yg.cloudfront.net
2020
# enable unstable features for testing
2121
S2N_UNSTABLE_CRYPTO_OPT_TX: 100

quic/s2n-quic-core/src/connection/limits.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
use crate::{
55
ack,
66
event::{api::SocketAddress, IntoEvent},
7-
inet, stream,
7+
inet, recovery, stream,
88
transport::parameters::{
99
AckDelayExponent, ActiveConnectionIdLimit, InitialFlowControlLimits, InitialMaxData,
1010
InitialMaxStreamDataBidiLocal, InitialMaxStreamDataBidiRemote, InitialMaxStreamDataUni,
@@ -65,6 +65,7 @@ pub struct Limits {
6565
pub(crate) max_handshake_duration: Duration,
6666
pub(crate) max_keep_alive_period: Duration,
6767
pub(crate) max_datagram_frame_size: MaxDatagramFrameSize,
68+
pub(crate) initial_round_trip_time: Duration,
6869
}
6970

7071
impl Default for Limits {
@@ -107,6 +108,7 @@ impl Limits {
107108
max_handshake_duration: MAX_HANDSHAKE_DURATION_DEFAULT,
108109
max_keep_alive_period: MAX_KEEP_ALIVE_PERIOD_DEFAULT,
109110
max_datagram_frame_size: MaxDatagramFrameSize::DEFAULT,
111+
initial_round_trip_time: recovery::DEFAULT_INITIAL_RTT,
110112
}
111113
}
112114

@@ -219,6 +221,26 @@ impl Limits {
219221
);
220222
setter!(with_max_keep_alive_period, max_keep_alive_period, Duration);
221223

224+
/// Sets the initial round trip time (RTT) for use in recovery mechanisms prior to
225+
/// measuring an actual RTT sample.
226+
///
227+
/// This is useful for environments where RTTs are mostly predictable (e.g. data centers)
228+
/// and are much lower than the default 333 milliseconds.
229+
pub fn with_initial_round_trip_time(
230+
mut self,
231+
value: Duration,
232+
) -> Result<Self, ValidationError> {
233+
ensure!(
234+
value >= recovery::MIN_RTT,
235+
Err(ValidationError(
236+
"provided value must be at least 1 microsecond",
237+
))
238+
);
239+
240+
self.initial_round_trip_time = value;
241+
Ok(self)
242+
}
243+
222244
// internal APIs
223245

224246
#[doc(hidden)]
@@ -291,6 +313,12 @@ impl Limits {
291313
pub fn max_keep_alive_period(&self) -> Duration {
292314
self.max_keep_alive_period
293315
}
316+
317+
#[doc(hidden)]
318+
#[inline]
319+
pub fn initial_round_trip_time(&self) -> Duration {
320+
self.initial_round_trip_time
321+
}
294322
}
295323

296324
/// Creates limits for a given connection

quic/s2n-quic-core/src/recovery/rtt_estimator.rs

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -50,21 +50,25 @@ pub struct RttEstimator {
5050
}
5151

5252
impl Default for RttEstimator {
53+
/// Creates a new RTT Estimator with default initial values
5354
fn default() -> Self {
54-
RttEstimator::new(Duration::ZERO)
55+
RttEstimator::new(DEFAULT_INITIAL_RTT)
5556
}
5657
}
5758

5859
impl RttEstimator {
59-
/// Creates a new RTT Estimator with default initial values using the given `max_ack_delay`.
60+
/// Creates a new RTT Estimator with the given `initial_rtt`
61+
///
62+
/// `on_max_ack_delay` must be called when the `max_ack_delay` transport
63+
/// parameter is received to initialize the `max_ack_delay` value.
6064
#[inline]
61-
pub fn new(max_ack_delay: Duration) -> Self {
62-
Self::new_with_initial(max_ack_delay, DEFAULT_INITIAL_RTT)
65+
pub fn new(initial_rtt: Duration) -> Self {
66+
Self::new_with_max_ack_delay(Duration::ZERO, initial_rtt)
6367
}
6468

6569
/// Creates a new RTT Estimator with the provided initial values using the given `max_ack_delay`.
6670
#[inline]
67-
pub fn new_with_initial(max_ack_delay: Duration, initial_rtt: Duration) -> Self {
71+
fn new_with_max_ack_delay(max_ack_delay: Duration, initial_rtt: Duration) -> Self {
6872
debug_assert!(initial_rtt >= MIN_RTT);
6973
let initial_rtt = initial_rtt.max(MIN_RTT);
7074

@@ -91,6 +95,11 @@ impl RttEstimator {
9195
}
9296
}
9397

98+
/// Creates a new RTT Estimator with the `max_ack_delay` from the current instance
99+
pub fn for_new_path(&self, initial_rtt: Duration) -> Self {
100+
Self::new_with_max_ack_delay(self.max_ack_delay, initial_rtt)
101+
}
102+
94103
/// Gets the latest round trip time sample
95104
#[inline]
96105
pub fn latest_rtt(&self) -> Duration {
@@ -385,7 +394,8 @@ mod test {
385394
/// Test the initial values before any RTT samples
386395
#[test]
387396
fn initial_rtt_across_spaces() {
388-
let rtt_estimator = RttEstimator::new(Duration::from_millis(10));
397+
let rtt_estimator =
398+
RttEstimator::new_with_max_ack_delay(Duration::from_millis(10), DEFAULT_INITIAL_RTT);
389399
assert_eq!(rtt_estimator.min_rtt, DEFAULT_INITIAL_RTT);
390400
assert_eq!(rtt_estimator.latest_rtt(), DEFAULT_INITIAL_RTT);
391401
assert_eq!(rtt_estimator.smoothed_rtt(), DEFAULT_INITIAL_RTT);
@@ -407,7 +417,7 @@ mod test {
407417
/// Test a zero RTT value is treated as 1 µs
408418
#[test]
409419
fn zero_rtt_sample() {
410-
let mut rtt_estimator = RttEstimator::new(Duration::from_millis(10));
420+
let mut rtt_estimator = RttEstimator::new(DEFAULT_INITIAL_RTT);
411421
let now = NoopClock.get_time();
412422
rtt_estimator.update_rtt(
413423
Duration::from_millis(10),
@@ -425,17 +435,26 @@ mod test {
425435
);
426436
}
427437

438+
#[test]
439+
fn for_new_path() {
440+
let mut rtt_estimator = RttEstimator::default();
441+
let max_ack_delay = Duration::from_millis(10);
442+
rtt_estimator.on_max_ack_delay(max_ack_delay.try_into().unwrap());
443+
let new_path_rtt_estimator = rtt_estimator.for_new_path(DEFAULT_INITIAL_RTT);
444+
assert_eq!(max_ack_delay, new_path_rtt_estimator.max_ack_delay)
445+
}
446+
428447
//= https://www.rfc-editor.org/rfc/rfc9002#section-5.3
429448
//= type=test
430449
//# * MUST use the lesser of the acknowledgement delay and the peer's
431450
//# max_ack_delay after the handshake is confirmed;
432451
#[test]
433452
fn max_ack_delay() {
434453
let mut rtt_estimator = RttEstimator::default();
435-
assert_eq!(Duration::ZERO, rtt_estimator.max_ack_delay());
454+
assert_eq!(Duration::ZERO, rtt_estimator.max_ack_delay);
436455

437456
rtt_estimator.on_max_ack_delay(MaxAckDelay::new(VarInt::from_u8(10)).unwrap());
438-
assert_eq!(Duration::from_millis(10), rtt_estimator.max_ack_delay());
457+
assert_eq!(Duration::from_millis(10), rtt_estimator.max_ack_delay);
439458

440459
let now = NoopClock.get_time();
441460
rtt_estimator.update_rtt(
@@ -495,7 +514,8 @@ mod test {
495514
/// Test several rounds of RTT updates
496515
#[test]
497516
fn update_rtt() {
498-
let mut rtt_estimator = RttEstimator::new(Duration::from_millis(10));
517+
let mut rtt_estimator =
518+
RttEstimator::new_with_max_ack_delay(Duration::from_millis(10), DEFAULT_INITIAL_RTT);
499519
let now = NoopClock.get_time();
500520
let rtt_sample = Duration::from_millis(500);
501521
assert!(rtt_estimator.first_rtt_sample.is_none());
@@ -574,7 +594,8 @@ mod test {
574594
//# the resulting value is smaller than the min_rtt.
575595
#[test]
576596
fn must_not_subtract_acknowledgement_delay_if_result_smaller_than_min_rtt() {
577-
let mut rtt_estimator = RttEstimator::new(Duration::from_millis(200));
597+
let mut rtt_estimator =
598+
RttEstimator::new_with_max_ack_delay(Duration::from_millis(200), DEFAULT_INITIAL_RTT);
578599
let now = NoopClock.get_time();
579600

580601
rtt_estimator.min_rtt = Duration::from_millis(500);
@@ -606,7 +627,8 @@ mod test {
606627
//# the min_rtt.
607628
#[test]
608629
fn prior_to_handshake_ignore_if_less_than_min_rtt() {
609-
let mut rtt_estimator = RttEstimator::new(Duration::from_millis(200));
630+
let mut rtt_estimator =
631+
RttEstimator::new_with_max_ack_delay(Duration::from_millis(200), DEFAULT_INITIAL_RTT);
610632
let now = NoopClock.get_time();
611633
let smoothed_rtt = Duration::from_millis(700);
612634

@@ -634,7 +656,8 @@ mod test {
634656
// of [QUIC-TRANSPORT]);
635657
#[test]
636658
fn initial_space() {
637-
let mut rtt_estimator = RttEstimator::new(Duration::from_millis(10));
659+
let mut rtt_estimator =
660+
RttEstimator::new_with_max_ack_delay(Duration::from_millis(10), DEFAULT_INITIAL_RTT);
638661
let now = NoopClock.get_time();
639662
let rtt_sample = Duration::from_millis(500);
640663
rtt_estimator.update_rtt(
@@ -671,7 +694,8 @@ mod test {
671694
#[test]
672695
fn persistent_congestion_duration() {
673696
let max_ack_delay = Duration::from_millis(10);
674-
let mut rtt_estimator = RttEstimator::new(max_ack_delay);
697+
let mut rtt_estimator =
698+
RttEstimator::new_with_max_ack_delay(max_ack_delay, DEFAULT_INITIAL_RTT);
675699

676700
rtt_estimator.smoothed_rtt = Duration::from_millis(100);
677701
rtt_estimator.rttvar = Duration::from_millis(50);
@@ -703,7 +727,8 @@ mod test {
703727

704728
#[test]
705729
fn set_min_rtt_to_latest_sample_after_persistent_congestion() {
706-
let mut rtt_estimator = RttEstimator::new(Duration::from_millis(10));
730+
let mut rtt_estimator =
731+
RttEstimator::new_with_max_ack_delay(Duration::from_millis(10), DEFAULT_INITIAL_RTT);
707732
let now = NoopClock.get_time();
708733
let mut rtt_sample = Duration::from_millis(500);
709734
rtt_estimator.update_rtt(
@@ -743,9 +768,8 @@ mod test {
743768
#[test]
744769
fn pto_must_be_at_least_k_granularity() {
745770
let space = PacketNumberSpace::Handshake;
746-
let max_ack_delay = Duration::from_millis(0);
747771
let now = NoopClock.get_time();
748-
let mut rtt_estimator = RttEstimator::new(max_ack_delay);
772+
let mut rtt_estimator = RttEstimator::new(DEFAULT_INITIAL_RTT);
749773

750774
// Update RTT with the smallest possible sample
751775
rtt_estimator.update_rtt(
@@ -795,7 +819,8 @@ mod test {
795819
//# RTT multiplier, is 9/8.
796820
#[test]
797821
fn time_threshold_multiplier_equals_nine_eighths() {
798-
let mut rtt_estimator = RttEstimator::new(Duration::from_millis(10));
822+
let mut rtt_estimator =
823+
RttEstimator::new_with_max_ack_delay(Duration::from_millis(10), DEFAULT_INITIAL_RTT);
799824
rtt_estimator.update_rtt(
800825
Duration::from_millis(10),
801826
Duration::from_secs(1),

quic/s2n-quic-core/src/transport/parameters/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ where
215215
}
216216

217217
#[derive(Copy, Clone, Debug, PartialEq)]
218-
pub struct ValidationError(&'static str);
218+
pub struct ValidationError(pub(crate) &'static str);
219219

220220
const MAX_ENCODABLE_VALUE: ValidationError =
221221
ValidationError("provided value exceeds maximum encodable value");

quic/s2n-quic-transport/src/connection/connection_impl.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -582,12 +582,11 @@ impl<Config: endpoint::Config> connection::Trait for ConnectionImpl<Config> {
582582
quic_version: parameters.quic_version,
583583
};
584584

585-
// The path manager always starts with a single path containing the known peer and local
586-
// connection ids.
587-
let rtt_estimator = RttEstimator::default();
585+
let rtt_estimator = RttEstimator::new(parameters.limits.initial_round_trip_time());
588586
// Assume clients validate the server's address implicitly.
589587
let peer_validated = Self::Config::ENDPOINT_TYPE.is_server();
590-
588+
// The path manager always starts with a single path containing the known peer and local
589+
// connection ids.
591590
let initial_path = path::Path::new(
592591
parameters.path_handle,
593592
parameters.peer_connection_id,
@@ -1152,6 +1151,7 @@ impl<Config: endpoint::Config> connection::Trait for ConnectionImpl<Config> {
11521151
congestion_controller_endpoint,
11531152
path_migration,
11541153
max_mtu,
1154+
self.limits.initial_round_trip_time(),
11551155
&mut publisher,
11561156
)?;
11571157

quic/s2n-quic-transport/src/path/manager.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::{
99
path::{challenge, Path},
1010
transmission,
1111
};
12+
use core::time::Duration;
1213
use s2n_quic_core::{
1314
ack,
1415
connection::{self, PeerId},
@@ -22,10 +23,7 @@ use s2n_quic_core::{
2223
Handle as _, Id, MaxMtu,
2324
},
2425
random,
25-
recovery::{
26-
congestion_controller::{self, Endpoint as _},
27-
RttEstimator,
28-
},
26+
recovery::congestion_controller::{self, Endpoint as _},
2927
stateless_reset,
3028
time::{timer, Timestamp},
3129
transport,
@@ -243,6 +241,7 @@ impl<Config: endpoint::Config> Manager<Config> {
243241
congestion_controller_endpoint: &mut Config::CongestionControllerEndpoint,
244242
migration_validator: &mut Config::PathMigrationValidator,
245243
max_mtu: MaxMtu,
244+
initial_rtt: Duration,
246245
publisher: &mut Pub,
247246
) -> Result<(Id, AmplificationOutcome), DatagramDropReason> {
248247
let valid_initial_received = self.valid_initial_received();
@@ -305,6 +304,7 @@ impl<Config: endpoint::Config> Manager<Config> {
305304
congestion_controller_endpoint,
306305
migration_validator,
307306
max_mtu,
307+
initial_rtt,
308308
publisher,
309309
)
310310
}
@@ -317,6 +317,7 @@ impl<Config: endpoint::Config> Manager<Config> {
317317
congestion_controller_endpoint: &mut Config::CongestionControllerEndpoint,
318318
migration_validator: &mut Config::PathMigrationValidator,
319319
max_mtu: MaxMtu,
320+
initial_rtt: Duration,
320321
publisher: &mut Pub,
321322
) -> Result<(Id, AmplificationOutcome), DatagramDropReason> {
322323
//= https://www.rfc-editor.org/rfc/rfc9000#section-9
@@ -398,7 +399,7 @@ impl<Config: endpoint::Config> Manager<Config> {
398399
// estimator for the new path, and they are initialized with initial values,
399400
// we do not need to reset congestion controller and round-trip time estimator
400401
// again on confirming the peer's ownership of its new address.
401-
let rtt = RttEstimator::new(self.active_path().rtt_estimator.max_ack_delay());
402+
let rtt = self.active_path().rtt_estimator.for_new_path(initial_rtt);
402403
let path_info = congestion_controller::PathInfo::new(&remote_address);
403404
let cc = congestion_controller_endpoint.new_congestion_controller(path_info);
404405

quic/s2n-quic-transport/src/path/manager/fuzz_target.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use s2n_quic_core::{
1515
inet::{DatagramInfo, ExplicitCongestionNotification},
1616
random,
1717
random::testing::Generator,
18+
recovery::DEFAULT_INITIAL_RTT,
1819
time::{testing::Clock, Clock as _},
1920
transport,
2021
};
@@ -176,6 +177,7 @@ impl Model {
176177
&mut Default::default(),
177178
&mut migration_validator,
178179
MaxMtu::default(),
180+
DEFAULT_INITIAL_RTT,
179181
&mut publisher,
180182
) {
181183
Ok(_) => {

0 commit comments

Comments
 (0)