diff --git a/quinn-proto/src/connection/mod.rs b/quinn-proto/src/connection/mod.rs index daad63690..228ac8399 100644 --- a/quinn-proto/src/connection/mod.rs +++ b/quinn-proto/src/connection/mod.rs @@ -4279,44 +4279,21 @@ impl Connection { .get_mut(&path_id) .expect("payload is processed only after the path becomes known"); - match path.data.challenges_sent.get(&response.0) { - // Response to an on-path PathChallenge - Some(info) if info.remote == remote && path.data.remote == remote => { - let sent_instant = info.sent_instant; + use paths::OnPathResponseReceived::*; + match path.data.on_path_response_received(now, response.0, remote) { + OnPath { was_open } => { // TODO(@divma): reset timers using the remaining off-path challenges - self.timers.stop( - Timer::PerPath(path_id, PathTimer::PathValidation), - self.qlog.with_time(now), - ); - self.timers.stop( - Timer::PerPath(path_id, PathTimer::PathChallengeLost), - self.qlog.with_time(now), - ); - if !path.data.validated { - trace!("new path validated"); - } - self.timers.stop( - Timer::PerPath(path_id, PathTimer::PathOpen), - self.qlog.with_time(now), - ); - // Clear any other on-path sent challenge. - path.data - .challenges_sent - .retain(|_token, info| info.remote != remote); - path.data.send_new_challenge = false; - path.data.validated = true; - - // This RTT can only be used for the initial RTT, not as a normal - // sample: https://www.rfc-editor.org/rfc/rfc9002#section-6.2.2-2. - let rtt = now.saturating_duration_since(sent_instant); - path.data.rtt.reset_initial_rtt(rtt); - - self.events - .push_back(Event::Path(PathEvent::Opened { id: path_id })); - // mark the path as open from the application perspective now that Opened - // event has been queued - if !std::mem::replace(&mut path.data.open, true) { - trace!("path opened"); + use PathTimer::*; + let qlog = self.qlog.with_time(now); + + self.timers + .stop(Timer::PerPath(path_id, PathValidation), qlog.clone()); + self.timers + .stop(Timer::PerPath(path_id, PathChallengeLost), qlog.clone()); + self.timers.stop(Timer::PerPath(path_id, PathOpen), qlog); + if !was_open { + self.events + .push_back(Event::Path(PathEvent::Opened { id: path_id })); if let Some(observed) = path.data.last_observed_addr_report.as_ref() { self.events.push_back(Event::Path(PathEvent::ObservedAddr { @@ -4330,19 +4307,13 @@ impl Connection { prev.send_new_challenge = false; } } - // Response to an off-path PathChallenge - Some(info) if info.remote == remote => { + OffPath => { debug!("Response to off-path PathChallenge!"); - path.data - .challenges_sent - .retain(|_token, info| info.remote != remote); } - // Response to a PathChallenge we recognize, but from an invalid remote - Some(info) => { - debug!(%response, from=%remote, expected=%info.remote, "ignoring invalid PATH_RESPONSE") + Invalid { expected } => { + debug!(%response, from=%remote, %expected, "ignoring invalid PATH_RESPONSE") } - // Response to an unknown PathChallenge - None => debug!(%response, "ignoring invalid PATH_RESPONSE"), + Unknown => debug!(%response, "ignoring invalid PATH_RESPONSE"), } } Frame::MaxData(bytes) => { diff --git a/quinn-proto/src/connection/paths.rs b/quinn-proto/src/connection/paths.rs index 1a2db60c1..e787f9fef 100644 --- a/quinn-proto/src/connection/paths.rs +++ b/quinn-proto/src/connection/paths.rs @@ -385,6 +385,49 @@ impl PathData { } } + /// Handle receiving a PATH_RESPONSE. + pub(super) fn on_path_response_received( + &mut self, + now: Instant, + token: u64, + remote: SocketAddr, + ) -> OnPathResponseReceived { + match self.challenges_sent.get(&token) { + // Response to an on-path PathChallenge + Some(info) if info.remote == remote && self.remote == remote => { + let sent_instant = info.sent_instant; + if !std::mem::replace(&mut self.validated, true) { + trace!("new path validated"); + } + // Clear any other on-path sent challenge. + self.challenges_sent + .retain(|_token, info| info.remote != remote); + + self.send_new_challenge = false; + + // This RTT can only be used for the initial RTT, not as a normal + // sample: https://www.rfc-editor.org/rfc/rfc9002#section-6.2.2-2. + let rtt = now.saturating_duration_since(sent_instant); + self.rtt.reset_initial_rtt(rtt); + + let was_open = std::mem::replace(&mut self.open, true); + OnPathResponseReceived::OnPath { was_open } + } + // Response to an off-path PathChallenge + Some(info) if info.remote == remote => { + self.challenges_sent + .retain(|_token, info| info.remote != remote); + OnPathResponseReceived::OffPath + } + // Response to a PathChallenge we recognize, but from an invalid remote + Some(info) => OnPathResponseReceived::Invalid { + expected: info.remote, + }, + // Response to an unknown PathChallenge + None => OnPathResponseReceived::Unknown, + } + } + #[cfg(feature = "qlog")] pub(super) fn qlog_recovery_metrics( &mut self, @@ -469,6 +512,20 @@ impl PathData { } } +pub(super) enum OnPathResponseReceived { + /// This response validates the path on its current remote address. + OnPath { was_open: bool }, + /// This response is valid, but it's for a remote other than the path's current remote address. + OffPath, + /// The received token is unknown. + Unknown, + /// The response is invalid. + Invalid { + /// The remote that was expected for this token. + expected: SocketAddr, + }, +} + /// Congestion metrics as described in [`recovery_metrics_updated`]. /// /// [`recovery_metrics_updated`]: https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-quic-events.html#name-recovery_metrics_updated diff --git a/quinn-proto/src/connection/qlog.rs b/quinn-proto/src/connection/qlog.rs index bf6b58274..5215fc1be 100644 --- a/quinn-proto/src/connection/qlog.rs +++ b/quinn-proto/src/connection/qlog.rs @@ -368,6 +368,7 @@ impl QlogSink { } /// A [`QlogSink`] with a `now` timestamp. +#[derive(Clone)] pub(super) struct QlogSinkWithTime<'a> { #[cfg(feature = "qlog")] sink: &'a QlogSink,