diff --git a/quinn-proto/src/config/transport.rs b/quinn-proto/src/config/transport.rs index f66aa459c..4f1c8d5ca 100644 --- a/quinn-proto/src/config/transport.rs +++ b/quinn-proto/src/config/transport.rs @@ -8,7 +8,7 @@ use std::{ }; use crate::{ - ConnectionId, Duration, INITIAL_MTU, Instant, MAX_UDP_PAYLOAD, Side, VarInt, + ConnectionId, Duration, FourTuple, INITIAL_MTU, Instant, MAX_UDP_PAYLOAD, Side, VarInt, VarIntBoundsExceeded, address_discovery, congestion, connection::qlog::QlogSink, }; #[cfg(feature = "qlog")] @@ -83,6 +83,8 @@ pub struct TransportConfig { pub(crate) max_remote_nat_traversal_addresses: Option, + pub(crate) rtt_hint: Arc Option + Send + Sync>, + #[cfg(feature = "qlog")] pub(crate) qlog_factory: Option>, } @@ -467,6 +469,27 @@ impl TransportConfig { self } + /// Sets the rtt hint function. + /// + /// This function is consulted any time a new path is opened on a 4-tuple that we don't + /// yet have a good rtt estimate for yet. + /// + /// If the function returns some RTT, then this RTT is used as the initial RTT in the path's + /// rtt estimator. + /// If the function returns None, then we use the default rtt estimator's initial RTT value + /// (see also [`Self::initial_rtt`]). + /// + /// Be sure to always use conservative estimates of RTT, as the initial RTT value is used + /// for setting the path's timeout. If the RTT estimate is about 9x lower than the actual + /// RTT, then the path will be abandoned before a response could even be processed. + pub fn set_rtt_hint( + &mut self, + rtt_hint: impl Fn(FourTuple) -> Option + Send + Sync + 'static, + ) -> &mut Self { + self.rtt_hint = Arc::new(rtt_hint); + self + } + /// Configures qlog capturing by setting a [`QlogFactory`]. /// /// This assigns a [`QlogFactory`] that produces qlog capture configurations for @@ -582,6 +605,8 @@ impl Default for TransportConfig { // nat traversal disabled by default max_remote_nat_traversal_addresses: None, + rtt_hint: Arc::new(|_| None), + #[cfg(feature = "qlog")] qlog_factory: None, } @@ -621,6 +646,7 @@ impl fmt::Debug for TransportConfig { default_path_max_idle_timeout, default_path_keep_alive_interval, max_remote_nat_traversal_addresses, + rtt_hint: _, #[cfg(feature = "qlog")] qlog_factory, } = self; diff --git a/quinn-proto/src/connection/mod.rs b/quinn-proto/src/connection/mod.rs index 6528de6e7..1b57efb7b 100644 --- a/quinn-proto/src/connection/mod.rs +++ b/quinn-proto/src/connection/mod.rs @@ -818,15 +818,6 @@ impl Connection { &mut self.paths.get_mut(&path_id).expect("known path").data } - /// Check if the 4-tuple path (as in RFC9000 Path, not multipath path) had already been validated. - fn is_path_validated(&self, addresses: FourTuple) -> bool { - self.paths - .values() - .any(|path_state| path_state.data.validated && path_state.data.addresses == addresses) - // TODO(@divma): we might want to ensure the path has been recently active to consider the - // address validated - } - fn ensure_path( &mut self, path_id: PathId, @@ -834,7 +825,17 @@ impl Connection { now: Instant, pn: Option, ) -> &mut PathData { - let validated = self.is_path_validated(addresses); + let path_on_same_address = self + .paths + .iter() + .find(|(path_id, state)| { + addresses.is_probably_same_path(&state.data.addresses) + && state.data.validated + && !self.abandoned_paths.contains(path_id) + }) + .clone(); + let validated = path_on_same_address.is_some(); + let initial_rtt = path_on_same_address.map(|(_, state)| state.data.rtt.conservative()); let vacant_entry = match self.paths.entry(path_id) { btree_map::Entry::Vacant(vacant_entry) => vacant_entry, btree_map::Entry::Occupied(occupied_entry) => { @@ -856,6 +857,9 @@ impl Connection { ); data.validated = validated; + if let Some(rtt_hint) = initial_rtt.or_else(|| (self.config.rtt_hint)(addresses)) { + data.rtt.reset_initial_rtt(rtt_hint); + } let pto = self.ack_frequency.max_ack_delay_for_pto() + data.rtt.pto_base(); self.timers.set(