diff --git a/quiche/src/lib.rs b/quiche/src/lib.rs index e9e1802192..a9edd88da9 100644 --- a/quiche/src/lib.rs +++ b/quiche/src/lib.rs @@ -720,6 +720,8 @@ pub struct Config { max_stream_window: u64, disable_dcid_reuse: bool, + + max_pto: time::Duration, } // See https://quicwg.org/base-drafts/rfc9000.html#section-15 @@ -779,6 +781,7 @@ impl Config { max_stream_window: stream::MAX_STREAM_WINDOW, disable_dcid_reuse: false, + max_pto: time::Duration::MAX, }) } @@ -1209,6 +1212,16 @@ impl Config { pub fn set_disable_dcid_reuse(&mut self, v: bool) { self.disable_dcid_reuse = v; } + + /// Sets the maximum Probe Timeout + /// + /// PTO is a function of RTT and can get large. Configure a max PTO to limit + /// how large it can get. + /// + /// The default value is [`Duration::MAX`] + pub fn set_max_pto(&mut self, v: time::Duration) { + self.max_pto = v; + } } /// A QUIC connection. diff --git a/quiche/src/recovery/mod.rs b/quiche/src/recovery/mod.rs index b262f9c15d..8c277d225c 100644 --- a/quiche/src/recovery/mod.rs +++ b/quiche/src/recovery/mod.rs @@ -80,6 +80,7 @@ pub struct Recovery { loss_detection_timer: Option, pto_count: u32, + max_pto: Duration, time_of_last_sent_ack_eliciting_pkt: [Option; packet::Epoch::count()], @@ -177,6 +178,7 @@ pub struct RecoveryConfig { hystart: bool, pacing: bool, max_pacing_rate: Option, + max_pto: Duration, } impl RecoveryConfig { @@ -188,6 +190,7 @@ impl RecoveryConfig { hystart: config.hystart, pacing: config.pacing, max_pacing_rate: config.max_pacing_rate, + max_pto: config.max_pto, } } } @@ -201,6 +204,7 @@ impl Recovery { loss_detection_timer: None, pto_count: 0, + max_pto: recovery_config.max_pto, time_of_last_sent_ack_eliciting_pkt: [None; packet::Epoch::count()], @@ -723,7 +727,10 @@ impl Recovery { } pub fn pto(&self) -> Duration { - self.rtt() + cmp::max(self.rttvar * 4, GRANULARITY) + cmp::min( + self.rtt() + cmp::max(self.rttvar * 4, GRANULARITY), + self.max_pto, + ) } pub fn delivery_rate(&self) -> u64 { @@ -2184,6 +2191,23 @@ mod tests { now + Duration::from_secs_f64(12000.0 / pacing_rate as f64) ); } + + #[test] + fn max_pto() { + let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap(); + cfg.set_max_pto(Duration::from_secs(10)); + + let mut r = Recovery::new(&cfg); + r.update_rtt(Duration::from_millis(100), Duration::ZERO, Instant::now()); + assert!( + r.pto() < Duration::from_secs(10), + "pto not unnecessarily limited" + ); + + r.update_rtt(Duration::from_secs(60), Duration::ZERO, Instant::now()); + + assert_eq!(r.pto(), Duration::from_secs(10), "pto limited"); + } } mod bbr;