blob: ea5965989eb9451da3c439222c25b22b826312d8 [file] [log] [blame]
// Copyright (C) 2022, Cloudflare, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use std::time;
use std::collections::BTreeMap;
use std::collections::VecDeque;
use std::net::SocketAddr;
use slab::Slab;
use crate::Error;
use crate::Result;
use crate::recovery;
use crate::recovery::HandshakeStatus;
/// The different states of the path validation.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum PathState {
/// The path failed its validation.
Failed,
/// The path exists, but no path validation has been performed.
Unknown,
/// The path is under validation.
Validating,
/// The remote address has been validated, but not the path MTU.
ValidatingMTU,
/// The path has been validated.
Validated,
}
impl PathState {
#[cfg(feature = "ffi")]
pub fn to_c(self) -> libc::ssize_t {
match self {
PathState::Failed => -1,
PathState::Unknown => 0,
PathState::Validating => 1,
PathState::ValidatingMTU => 2,
PathState::Validated => 3,
}
}
}
/// A path-specific event.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PathEvent {
/// A new network path (local address, peer address) has been seen on a
/// received packet. Note that this event is only triggered for servers, as
/// the client is responsible from initiating new paths. The application may
/// then probe this new path, if desired.
New(SocketAddr, SocketAddr),
/// The related network path between local `SocketAddr` and peer
/// `SocketAddr` has been validated.
Validated(SocketAddr, SocketAddr),
/// The related network path between local `SocketAddr` and peer
/// `SocketAddr` failed to be validated. This network path will not be used
/// anymore, unless the application requests probing this path again.
FailedValidation(SocketAddr, SocketAddr),
/// The related network path between local `SocketAddr` and peer
/// `SocketAddr` has been closed and is now unusable on this connection.
Closed(SocketAddr, SocketAddr),
/// The stack observes that the Source Connection ID with the given sequence
/// number, initially used by the peer over the first pair of `SocketAddr`s,
/// is now reused over the second pair of `SocketAddr`s.
ReusedSourceConnectionId(
u64,
(SocketAddr, SocketAddr),
(SocketAddr, SocketAddr),
),
/// The connection observed that the peer migrated over the network path
/// denoted by the pair of `SocketAddr`, i.e., non-probing packets have been
/// received on this network path. This is a server side only event.
///
/// Note that this event is only raised if the path has been validated.
PeerMigrated(SocketAddr, SocketAddr),
}
/// A network path on which QUIC packets can be sent.
#[derive(Debug)]
pub struct Path {
/// The local address.
local_addr: SocketAddr,
/// The remote address.
peer_addr: SocketAddr,
/// Source CID sequence number used over that path.
pub active_scid_seq: Option<u64>,
/// Destination CID sequence number used over that path.
pub active_dcid_seq: Option<u64>,
/// The current validation state of the path.
state: PathState,
/// Is this path used to send non-probing packets.
active: bool,
/// Loss recovery and congestion control state.
pub recovery: recovery::Recovery,
/// Pending challenge data with the size of the packet containing them and
/// when they were sent.
in_flight_challenges: VecDeque<([u8; 8], usize, time::Instant)>,
/// The maximum challenge size that got acknowledged.
max_challenge_size: usize,
/// Number of consecutive (spaced by at least 1 RTT) probing packets lost.
probing_lost: usize,
/// Last instant when a probing packet got lost.
last_probe_lost_time: Option<time::Instant>,
/// Received challenge data.
received_challenges: VecDeque<[u8; 8]>,
/// Number of packets sent on this path.
pub sent_count: usize,
/// Number of packets received on this path.
pub recv_count: usize,
/// Total number of packets sent with data retransmitted from this path.
pub retrans_count: usize,
/// Total number of sent bytes over this path.
pub sent_bytes: u64,
/// Total number of bytes received over this path.
pub recv_bytes: u64,
/// Total number of bytes retransmitted from this path.
/// This counts only STREAM and CRYPTO data.
pub stream_retrans_bytes: u64,
/// Total number of bytes the server can send before the peer's address
/// is verified.
pub max_send_bytes: usize,
/// Whether the peer's address has been verified.
pub verified_peer_address: bool,
/// Whether the peer has verified our address.
pub peer_verified_local_address: bool,
/// Does it requires sending PATH_CHALLENGE?
challenge_requested: bool,
/// Whether the failure of this path was notified.
failure_notified: bool,
/// Whether the connection tries to migrate to this path, but it still needs
/// to be validated.
migrating: bool,
/// Whether or not we should force eliciting of an ACK (e.g. via PING frame)
pub needs_ack_eliciting: bool,
}
impl Path {
/// Create a new Path instance with the provided addresses, the remaining of
/// the fields being set to their default value.
pub fn new(
local_addr: SocketAddr, peer_addr: SocketAddr,
recovery_config: &recovery::RecoveryConfig, is_initial: bool,
) -> Self {
let (state, active_scid_seq, active_dcid_seq) = if is_initial {
(PathState::Validated, Some(0), Some(0))
} else {
(PathState::Unknown, None, None)
};
Self {
local_addr,
peer_addr,
active_scid_seq,
active_dcid_seq,
state,
active: false,
recovery: recovery::Recovery::new_with_config(recovery_config),
in_flight_challenges: VecDeque::new(),
max_challenge_size: 0,
probing_lost: 0,
last_probe_lost_time: None,
received_challenges: VecDeque::new(),
sent_count: 0,
recv_count: 0,
retrans_count: 0,
sent_bytes: 0,
recv_bytes: 0,
stream_retrans_bytes: 0,
max_send_bytes: 0,
verified_peer_address: false,
peer_verified_local_address: false,
challenge_requested: false,
failure_notified: false,
migrating: false,
needs_ack_eliciting: false,
}
}
/// Returns the local address on which this path operates.
#[inline]
pub fn local_addr(&self) -> SocketAddr {
self.local_addr
}
/// Returns the peer address on which this path operates.
#[inline]
pub fn peer_addr(&self) -> SocketAddr {
self.peer_addr
}
/// Returns whether the path is working (i.e., not failed).
#[inline]
fn working(&self) -> bool {
self.state > PathState::Failed
}
/// Returns whether the path is active.
#[inline]
pub fn active(&self) -> bool {
self.active && self.working() && self.active_dcid_seq.is_some()
}
/// Returns whether the path can be used to send non-probing packets.
#[inline]
pub fn usable(&self) -> bool {
self.active() ||
(self.state == PathState::Validated &&
self.active_dcid_seq.is_some())
}
/// Returns whether the path is unused.
#[inline]
fn unused(&self) -> bool {
// FIXME: we should check that there is nothing in the sent queue.
!self.active() && self.active_dcid_seq.is_none()
}
/// Returns whether the path requires sending a probing packet.
#[inline]
pub fn probing_required(&self) -> bool {
!self.received_challenges.is_empty() || self.validation_requested()
}
/// Promotes the path to the provided state only if the new state is greater
/// than the current one.
fn promote_to(&mut self, state: PathState) {
if self.state < state {
self.state = state;
}
}
/// Returns whether the path is validated.
#[inline]
pub fn validated(&self) -> bool {
self.state == PathState::Validated
}
/// Returns whether this path failed its validation.
#[inline]
fn validation_failed(&self) -> bool {
self.state == PathState::Failed
}
// Returns whether this path is under path validation process.
#[inline]
pub fn under_validation(&self) -> bool {
matches!(self.state, PathState::Validating | PathState::ValidatingMTU)
}
/// Requests path validation.
#[inline]
pub fn request_validation(&mut self) {
self.challenge_requested = true;
}
/// Returns whether a validation is requested.
#[inline]
pub fn validation_requested(&self) -> bool {
self.challenge_requested
}
pub fn on_challenge_sent(&mut self) {
self.promote_to(PathState::Validating);
self.challenge_requested = false;
}
/// Handles the sending of PATH_CHALLENGE.
pub fn add_challenge_sent(
&mut self, data: [u8; 8], pkt_size: usize, sent_time: time::Instant,
) {
self.on_challenge_sent();
self.in_flight_challenges
.push_back((data, pkt_size, sent_time));
}
pub fn on_challenge_received(&mut self, data: [u8; 8]) {
self.received_challenges.push_back(data);
self.peer_verified_local_address = true;
}
pub fn has_pending_challenge(&self, data: [u8; 8]) -> bool {
self.in_flight_challenges.iter().any(|(d, ..)| *d == data)
}
/// Returns whether the path is now validated.
pub fn on_response_received(&mut self, data: [u8; 8]) -> bool {
self.verified_peer_address = true;
self.probing_lost = 0;
let mut challenge_size = 0;
self.in_flight_challenges.retain(|(d, s, _)| {
if *d == data {
challenge_size = *s;
false
} else {
true
}
});
// The 4-tuple is reachable, but we didn't check Path MTU yet.
self.promote_to(PathState::ValidatingMTU);
self.max_challenge_size =
std::cmp::max(self.max_challenge_size, challenge_size);
if self.state == PathState::ValidatingMTU {
if self.max_challenge_size >= crate::MIN_CLIENT_INITIAL_LEN {
// Path MTU is sufficient for QUIC traffic.
self.promote_to(PathState::Validated);
return true;
}
// If the MTU was not validated, probe again.
self.request_validation();
}
false
}
fn on_failed_validation(&mut self) {
self.state = PathState::Failed;
self.active = false;
}
#[inline]
pub fn pop_received_challenge(&mut self) -> Option<[u8; 8]> {
self.received_challenges.pop_front()
}
pub fn on_loss_detection_timeout(
&mut self, handshake_status: HandshakeStatus, now: time::Instant,
is_server: bool, trace_id: &str,
) -> (usize, usize) {
let (lost_packets, lost_bytes) = self.recovery.on_loss_detection_timeout(
handshake_status,
now,
trace_id,
);
let mut lost_probe_time = None;
self.in_flight_challenges.retain(|(_, _, sent_time)| {
if *sent_time <= now {
if lost_probe_time.is_none() {
lost_probe_time = Some(*sent_time);
}
false
} else {
true
}
});
// If we lost probing packets, check if the path failed
// validation.
if let Some(lost_probe_time) = lost_probe_time {
self.last_probe_lost_time = match self.last_probe_lost_time {
Some(last) => {
// Count a loss if at least 1-RTT happened.
if lost_probe_time - last >= self.recovery.rtt() {
self.probing_lost += 1;
Some(lost_probe_time)
} else {
Some(last)
}
},
None => {
self.probing_lost += 1;
Some(lost_probe_time)
},
};
// As a server, if requesting a challenge is not
// possible due to the amplification attack, declare the
// validation as failed.
if self.probing_lost >= crate::MAX_PROBING_TIMEOUTS ||
(is_server && self.max_send_bytes < crate::MIN_PROBING_SIZE)
{
self.on_failed_validation();
} else {
self.request_validation();
}
}
(lost_packets, lost_bytes)
}
pub fn stats(&self) -> PathStats {
PathStats {
local_addr: self.local_addr,
peer_addr: self.peer_addr,
validation_state: self.state,
active: self.active,
recv: self.recv_count,
sent: self.sent_count,
lost: self.recovery.lost_count,
retrans: self.retrans_count,
rtt: self.recovery.rtt(),
min_rtt: self.recovery.min_rtt(),
rttvar: self.recovery.rttvar(),
cwnd: self.recovery.cwnd(),
sent_bytes: self.sent_bytes,
recv_bytes: self.recv_bytes,
lost_bytes: self.recovery.bytes_lost,
stream_retrans_bytes: self.stream_retrans_bytes,
pmtu: self.recovery.max_datagram_size(),
delivery_rate: self.recovery.delivery_rate(),
}
}
}
/// An iterator over SocketAddr.
#[derive(Default)]
pub struct SocketAddrIter {
pub(crate) sockaddrs: Vec<SocketAddr>,
}
impl Iterator for SocketAddrIter {
type Item = SocketAddr;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.sockaddrs.pop()
}
}
impl ExactSizeIterator for SocketAddrIter {
#[inline]
fn len(&self) -> usize {
self.sockaddrs.len()
}
}
/// All path-related information.
pub struct PathMap {
/// The paths of the connection. Each of them has an internal identifier
/// that is used by `addrs_to_paths` and `ConnectionEntry`.
paths: Slab<Path>,
/// The maximum number of concurrent paths allowed.
max_concurrent_paths: usize,
/// The mapping from the (local `SocketAddr`, peer `SocketAddr`) to the
/// `Path` structure identifier.
addrs_to_paths: BTreeMap<(SocketAddr, SocketAddr), usize>,
/// Path-specific events to be notified to the application.
events: VecDeque<PathEvent>,
/// Whether this manager serves a connection as a server.
is_server: bool,
}
impl PathMap {
/// Creates a new `PathMap` with the initial provided `path` and a
/// capacity limit.
pub fn new(
mut initial_path: Path, max_concurrent_paths: usize, is_server: bool,
) -> Self {
let mut paths = Slab::with_capacity(1); // most connections only have one path
let mut addrs_to_paths = BTreeMap::new();
let local_addr = initial_path.local_addr;
let peer_addr = initial_path.peer_addr;
// As it is the first path, it is active by default.
initial_path.active = true;
let active_path_id = paths.insert(initial_path);
addrs_to_paths.insert((local_addr, peer_addr), active_path_id);
Self {
paths,
max_concurrent_paths,
addrs_to_paths,
events: VecDeque::new(),
is_server,
}
}
/// Gets an immutable reference to the path identified by `path_id`. If the
/// provided `path_id` does not identify any current `Path`, returns an
/// [`InvalidState`].
///
/// [`InvalidState`]: enum.Error.html#variant.InvalidState
#[inline]
pub fn get(&self, path_id: usize) -> Result<&Path> {
self.paths.get(path_id).ok_or(Error::InvalidState)
}
/// Gets a mutable reference to the path identified by `path_id`. If the
/// provided `path_id` does not identify any current `Path`, returns an
/// [`InvalidState`].
///
/// [`InvalidState`]: enum.Error.html#variant.InvalidState
#[inline]
pub fn get_mut(&mut self, path_id: usize) -> Result<&mut Path> {
self.paths.get_mut(path_id).ok_or(Error::InvalidState)
}
#[inline]
/// Gets an immutable reference to the active path with the value of the
/// lowest identifier. If there is no active path, returns `None`.
pub fn get_active_with_pid(&self) -> Option<(usize, &Path)> {
self.paths.iter().find(|(_, p)| p.active())
}
/// Gets an immutable reference to the active path with the lowest
/// identifier. If there is no active path, returns an [`InvalidState`].
///
/// [`InvalidState`]: enum.Error.html#variant.InvalidState
#[inline]
pub fn get_active(&self) -> Result<&Path> {
self.get_active_with_pid()
.map(|(_, p)| p)
.ok_or(Error::InvalidState)
}
/// Gets the lowest active path identifier. If there is no active path,
/// returns an [`InvalidState`].
///
/// [`InvalidState`]: enum.Error.html#variant.InvalidState
#[inline]
pub fn get_active_path_id(&self) -> Result<usize> {
self.get_active_with_pid()
.map(|(pid, _)| pid)
.ok_or(Error::InvalidState)
}
/// Gets an mutable reference to the active path with the lowest identifier.
/// If there is no active path, returns an [`InvalidState`].
///
/// [`InvalidState`]: enum.Error.html#variant.InvalidState
#[inline]
pub fn get_active_mut(&mut self) -> Result<&mut Path> {
self.paths
.iter_mut()
.map(|(_, p)| p)
.find(|p| p.active())
.ok_or(Error::InvalidState)
}
/// Returns an iterator over all existing paths.
#[inline]
pub fn iter(&self) -> slab::Iter<Path> {
self.paths.iter()
}
/// Returns a mutable iterator over all existing paths.
#[inline]
pub fn iter_mut(&mut self) -> slab::IterMut<Path> {
self.paths.iter_mut()
}
/// Returns the number of existing paths.
#[inline]
pub fn len(&self) -> usize {
self.paths.len()
}
/// Returns the `Path` identifier related to the provided `addrs`.
#[inline]
pub fn path_id_from_addrs(
&self, addrs: &(SocketAddr, SocketAddr),
) -> Option<usize> {
self.addrs_to_paths.get(addrs).copied()
}
/// Checks if creating a new path will not exceed the current `self.paths`
/// capacity. If yes, this method tries to remove one unused path. If it
/// fails to do so, returns [`Done`].
///
/// [`Done`]: enum.Error.html#variant.Done
fn make_room_for_new_path(&mut self) -> Result<()> {
if self.paths.len() < self.max_concurrent_paths {
return Ok(());
}
let (pid_to_remove, _) = self
.paths
.iter()
.find(|(_, p)| p.unused())
.ok_or(Error::Done)?;
let path = self.paths.remove(pid_to_remove);
self.addrs_to_paths
.remove(&(path.local_addr, path.peer_addr));
self.notify_event(PathEvent::Closed(path.local_addr, path.peer_addr));
Ok(())
}
/// Records the provided `Path` and returns its assigned identifier.
///
/// On success, this method takes care of creating a notification to the
/// serving application, if it serves a server-side connection.
///
/// If there are already `max_concurrent_paths` currently recorded, this
/// method tries to remove an unused `Path` first. If it fails to do so,
/// it returns [`Done`].
///
/// [`Done`]: enum.Error.html#variant.Done
pub fn insert_path(&mut self, path: Path, is_server: bool) -> Result<usize> {
self.make_room_for_new_path()?;
let local_addr = path.local_addr;
let peer_addr = path.peer_addr;
let pid = self.paths.insert(path);
self.addrs_to_paths.insert((local_addr, peer_addr), pid);
// Notifies the application if we are in server mode.
if is_server {
self.notify_event(PathEvent::New(local_addr, peer_addr));
}
Ok(pid)
}
/// Notifies a path event to the application served by the connection.
pub fn notify_event(&mut self, ev: PathEvent) {
self.events.push_back(ev);
}
/// Gets the first path event to be notified to the application.
pub fn pop_event(&mut self) -> Option<PathEvent> {
self.events.pop_front()
}
/// Notifies all failed validations to the application.
pub fn notify_failed_validations(&mut self) {
let validation_failed = self
.paths
.iter_mut()
.filter(|(_, p)| p.validation_failed() && !p.failure_notified);
for (_, p) in validation_failed {
self.events.push_back(PathEvent::FailedValidation(
p.local_addr,
p.peer_addr,
));
p.failure_notified = true;
}
}
/// Finds a path candidate to be active and returns its identifier.
pub fn find_candidate_path(&self) -> Option<usize> {
// TODO: also consider unvalidated paths if there are no more validated.
self.paths
.iter()
.find(|(_, p)| p.usable())
.map(|(pid, _)| pid)
}
/// Handles incoming PATH_RESPONSE data.
pub fn on_response_received(&mut self, data: [u8; 8]) -> Result<()> {
let active_pid = self.get_active_path_id()?;
let challenge_pending =
self.iter_mut().find(|(_, p)| p.has_pending_challenge(data));
if let Some((pid, p)) = challenge_pending {
if p.on_response_received(data) {
let local_addr = p.local_addr;
let peer_addr = p.peer_addr;
let was_migrating = p.migrating;
p.migrating = false;
// Notifies the application.
self.notify_event(PathEvent::Validated(local_addr, peer_addr));
// If this path was the candidate for migration, notifies the
// application.
if pid == active_pid && was_migrating {
self.notify_event(PathEvent::PeerMigrated(
local_addr, peer_addr,
));
}
}
}
Ok(())
}
/// Sets the path with identifier 'path_id' to be active.
///
/// There can be exactly one active path on which non-probing packets can be
/// sent. If another path is marked as active, it will be superseded by the
/// one having `path_id` as identifier.
///
/// A server should always ensure that the active path is validated. If it
/// is already the case, it notifies the application that the connection
/// migrated. Otherwise, it triggers a path validation and defers the
/// notification once it is actually validated.
pub fn set_active_path(&mut self, path_id: usize) -> Result<()> {
let is_server = self.is_server;
if let Ok(old_active_path) = self.get_active_mut() {
old_active_path.active = false;
}
let new_active_path = self.get_mut(path_id)?;
new_active_path.active = true;
if is_server {
if new_active_path.validated() {
let local_addr = new_active_path.local_addr();
let peer_addr = new_active_path.peer_addr();
self.notify_event(PathEvent::PeerMigrated(local_addr, peer_addr));
} else {
new_active_path.migrating = true;
// Requests path validation if needed.
if !new_active_path.under_validation() {
new_active_path.request_validation();
}
}
}
Ok(())
}
/// Handles potential connection migration.
pub fn on_peer_migrated(
&mut self, new_pid: usize, disable_dcid_reuse: bool,
) -> Result<()> {
let active_path_id = self.get_active_path_id()?;
if active_path_id == new_pid {
return Ok(());
}
self.set_active_path(new_pid)?;
let no_spare_dcid = self.get_mut(new_pid)?.active_dcid_seq.is_none();
if no_spare_dcid && !disable_dcid_reuse {
self.get_mut(new_pid)?.active_dcid_seq =
self.get_mut(active_path_id)?.active_dcid_seq;
}
Ok(())
}
}
/// Statistics about the path of a connection.
///
/// It is part of the `Stats` structure returned by the [`stats()`] method.
///
/// [`stats()`]: struct.Connection.html#method.stats
#[derive(Clone)]
pub struct PathStats {
/// The local address of the path.
pub local_addr: SocketAddr,
/// The peer address of the path.
pub peer_addr: SocketAddr,
/// The path validation state.
pub validation_state: PathState,
/// Whether the path is marked as active.
pub active: bool,
/// The number of QUIC packets received.
pub recv: usize,
/// The number of QUIC packets sent.
pub sent: usize,
/// The number of QUIC packets that were lost.
pub lost: usize,
/// The number of sent QUIC packets with retransmitted data.
pub retrans: usize,
/// The estimated round-trip time of the connection.
pub rtt: time::Duration,
/// The minimum round-trip time observed.
pub min_rtt: Option<time::Duration>,
/// The estimated round-trip time variation in samples using a mean
/// variation.
pub rttvar: time::Duration,
/// The size of the connection's congestion window in bytes.
pub cwnd: usize,
/// The number of sent bytes.
pub sent_bytes: u64,
/// The number of received bytes.
pub recv_bytes: u64,
/// The number of bytes lost.
pub lost_bytes: u64,
/// The number of stream bytes retransmitted.
pub stream_retrans_bytes: u64,
/// The current PMTU for the connection.
pub pmtu: usize,
/// The most recent data delivery rate estimate in bytes/s.
///
/// Note that this value could be inaccurate if the application does not
/// respect pacing hints (see [`SendInfo.at`] and [Pacing] for more
/// details).
///
/// [`SendInfo.at`]: struct.SendInfo.html#structfield.at
/// [Pacing]: index.html#pacing
pub delivery_rate: u64,
}
impl std::fmt::Debug for PathStats {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"local_addr={:?} peer_addr={:?} ",
self.local_addr, self.peer_addr,
)?;
write!(
f,
"validation_state={:?} active={} ",
self.validation_state, self.active,
)?;
write!(
f,
"recv={} sent={} lost={} retrans={} rtt={:?} min_rtt={:?} rttvar={:?} cwnd={}",
self.recv, self.sent, self.lost, self.retrans, self.rtt, self.min_rtt, self.rttvar, self.cwnd,
)?;
write!(
f,
" sent_bytes={} recv_bytes={} lost_bytes={}",
self.sent_bytes, self.recv_bytes, self.lost_bytes,
)?;
write!(
f,
" stream_retrans_bytes={} pmtu={} delivery_rate={}",
self.stream_retrans_bytes, self.pmtu, self.delivery_rate,
)
}
}
#[cfg(test)]
mod tests {
use crate::rand;
use crate::MIN_CLIENT_INITIAL_LEN;
use crate::recovery::RecoveryConfig;
use crate::Config;
use super::*;
#[test]
fn path_validation_limited_mtu() {
let client_addr = "127.0.0.1:1234".parse().unwrap();
let client_addr_2 = "127.0.0.1:5678".parse().unwrap();
let server_addr = "127.0.0.1:4321".parse().unwrap();
let config = Config::new(crate::PROTOCOL_VERSION).unwrap();
let recovery_config = RecoveryConfig::from_config(&config);
let path = Path::new(client_addr, server_addr, &recovery_config, true);
let mut path_mgr = PathMap::new(path, 2, false);
let probed_path =
Path::new(client_addr_2, server_addr, &recovery_config, false);
path_mgr.insert_path(probed_path, false).unwrap();
let pid = path_mgr
.path_id_from_addrs(&(client_addr_2, server_addr))
.unwrap();
path_mgr.get_mut(pid).unwrap().request_validation();
assert_eq!(path_mgr.get_mut(pid).unwrap().validation_requested(), true);
assert_eq!(path_mgr.get_mut(pid).unwrap().probing_required(), true);
// Fake sending of PathChallenge in a packet of MIN_CLIENT_INITIAL_LEN - 1
// bytes.
let data = rand::rand_u64().to_be_bytes();
path_mgr.get_mut(pid).unwrap().add_challenge_sent(
data,
MIN_CLIENT_INITIAL_LEN - 1,
time::Instant::now(),
);
assert_eq!(path_mgr.get_mut(pid).unwrap().validation_requested(), false);
assert_eq!(path_mgr.get_mut(pid).unwrap().probing_required(), false);
assert_eq!(path_mgr.get_mut(pid).unwrap().under_validation(), true);
assert_eq!(path_mgr.get_mut(pid).unwrap().validated(), false);
assert_eq!(path_mgr.get_mut(pid).unwrap().state, PathState::Validating);
assert_eq!(path_mgr.pop_event(), None);
// Receives the response. The path is reachable, but the MTU is not
// validated yet.
path_mgr.on_response_received(data).unwrap();
assert_eq!(path_mgr.get_mut(pid).unwrap().validation_requested(), true);
assert_eq!(path_mgr.get_mut(pid).unwrap().probing_required(), true);
assert_eq!(path_mgr.get_mut(pid).unwrap().under_validation(), true);
assert_eq!(path_mgr.get_mut(pid).unwrap().validated(), false);
assert_eq!(
path_mgr.get_mut(pid).unwrap().state,
PathState::ValidatingMTU
);
assert_eq!(path_mgr.pop_event(), None);
// Fake sending of PathChallenge in a packet of MIN_CLIENT_INITIAL_LEN
// bytes.
let data = rand::rand_u64().to_be_bytes();
path_mgr.get_mut(pid).unwrap().add_challenge_sent(
data,
MIN_CLIENT_INITIAL_LEN,
time::Instant::now(),
);
path_mgr.on_response_received(data).unwrap();
assert_eq!(path_mgr.get_mut(pid).unwrap().validation_requested(), false);
assert_eq!(path_mgr.get_mut(pid).unwrap().probing_required(), false);
assert_eq!(path_mgr.get_mut(pid).unwrap().under_validation(), false);
assert_eq!(path_mgr.get_mut(pid).unwrap().validated(), true);
assert_eq!(path_mgr.get_mut(pid).unwrap().state, PathState::Validated);
assert_eq!(
path_mgr.pop_event(),
Some(PathEvent::Validated(client_addr_2, server_addr))
);
}
#[test]
fn multiple_probes() {
let client_addr = "127.0.0.1:1234".parse().unwrap();
let server_addr = "127.0.0.1:4321".parse().unwrap();
let config = Config::new(crate::PROTOCOL_VERSION).unwrap();
let recovery_config = RecoveryConfig::from_config(&config);
let path = Path::new(client_addr, server_addr, &recovery_config, true);
let mut client_path_mgr = PathMap::new(path, 2, false);
let mut server_path =
Path::new(server_addr, client_addr, &recovery_config, false);
let client_pid = client_path_mgr
.path_id_from_addrs(&(client_addr, server_addr))
.unwrap();
// First probe.
let data = rand::rand_u64().to_be_bytes();
client_path_mgr
.get_mut(client_pid)
.unwrap()
.add_challenge_sent(
data,
MIN_CLIENT_INITIAL_LEN,
time::Instant::now(),
);
// Second probe.
let data_2 = rand::rand_u64().to_be_bytes();
client_path_mgr
.get_mut(client_pid)
.unwrap()
.add_challenge_sent(
data_2,
MIN_CLIENT_INITIAL_LEN,
time::Instant::now(),
);
assert_eq!(
client_path_mgr
.get(client_pid)
.unwrap()
.in_flight_challenges
.len(),
2
);
// If we receive multiple challenges, we can store them.
server_path.on_challenge_received(data);
assert_eq!(server_path.received_challenges.len(), 1);
server_path.on_challenge_received(data_2);
assert_eq!(server_path.received_challenges.len(), 2);
// Response for first probe.
client_path_mgr.on_response_received(data).unwrap();
assert_eq!(
client_path_mgr
.get(client_pid)
.unwrap()
.in_flight_challenges
.len(),
1
);
// Response for second probe.
client_path_mgr.on_response_received(data_2).unwrap();
assert_eq!(
client_path_mgr
.get(client_pid)
.unwrap()
.in_flight_challenges
.len(),
0
);
}
}