From 59f52ce72f8f2fbb1f95673e95fa4f30deb520ae Mon Sep 17 00:00:00 2001 From: divagant-martian Date: Mon, 15 Dec 2025 19:46:12 +0000 Subject: [PATCH] deploy: 142b50b4f6d78d99fcf262fb11852d86b0a60ecc --- .../docs/iroh_quinn/enum.ConnectionError.html | 6 +- .../iroh_quinn_proto/enum.ClosePathError.html | 6 +- .../enum.ConnectionError.html | 6 +- pr/259/docs/iroh_quinn_proto/enum.Event.html | 4 +- .../docs/iroh_quinn_proto/enum.PathError.html | 6 +- .../docs/iroh_quinn_proto/enum.PathEvent.html | 6 +- .../iroh_quinn_proto/enum.PathStatus.html | 6 +- .../enum.SetPathStatusError.html | 6 +- .../iroh_quinn_proto/struct.ClosedPath.html | 6 +- .../iroh_quinn_proto/struct.Connection.html | 18 +- .../iroh_quinn_proto/struct.RttEstimator.html | 10 +- .../struct.TransportError.html | 2 +- .../iroh_quinn_proto/connection/mod.rs.html | 4710 ++++++++--------- .../iroh_quinn_proto/connection/paths.rs.html | 1113 ++-- 14 files changed, 2932 insertions(+), 2973 deletions(-) diff --git a/pr/259/docs/iroh_quinn/enum.ConnectionError.html b/pr/259/docs/iroh_quinn/enum.ConnectionError.html index fa308b332..cde8d7320 100644 --- a/pr/259/docs/iroh_quinn/enum.ConnectionError.html +++ b/pr/259/docs/iroh_quinn/enum.ConnectionError.html @@ -1,4 +1,4 @@ -ConnectionError in iroh_quinn - Rust

ConnectionError

Enum ConnectionError 

Source
pub enum ConnectionError {
+ConnectionError in iroh_quinn - Rust

ConnectionError

Enum ConnectionError 

Source
pub enum ConnectionError {
     VersionMismatch,
     TransportError(Error),
     ConnectionClosed(ConnectionClose),
@@ -20,8 +20,8 @@ and §

LocallyClosed

The local application closed the connection

§

CidsExhausted

The connection could not be created because not enough of the CID space is available

Try using longer connection IDs.

-

Trait Implementations§

Source§

impl Clone for ConnectionError

Source§

fn clone(&self) -> ConnectionError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectionError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Display for ConnectionError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Error for ConnectionError

Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<Close> for ConnectionError

Source§

fn from(x: Close) -> ConnectionError

Converts to this type from the input type.
Source§

impl From<CloseReason> for ConnectionError

Source§

fn from(value: CloseReason) -> ConnectionError

Converts to this type from the input type.
Source§

impl From<ConnectionError> for ReadError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for ResetError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for SendDatagramError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for StoppedError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for WriteError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for ConnectionError

Source§

fn from(source: Error) -> ConnectionError

Converts to this type from the input type.
Source§

impl PartialEq for ConnectionError

Source§

fn eq(&self, other: &ConnectionError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for ConnectionError

Source§

impl StructuralPartialEq for ConnectionError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for ConnectionError

Source§

fn clone(&self) -> ConnectionError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectionError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Display for ConnectionError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Error for ConnectionError

Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<Close> for ConnectionError

Source§

fn from(x: Close) -> ConnectionError

Converts to this type from the input type.
Source§

impl From<CloseReason> for ConnectionError

Source§

fn from(value: CloseReason) -> ConnectionError

Converts to this type from the input type.
Source§

impl From<ConnectionError> for ReadError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for ResetError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for SendDatagramError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for StoppedError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for WriteError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for ConnectionError

Source§

fn from(source: Error) -> ConnectionError

Converts to this type from the input type.
Source§

impl PartialEq for ConnectionError

Source§

fn eq(&self, other: &ConnectionError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for ConnectionError

Source§

impl StructuralPartialEq for ConnectionError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/259/docs/iroh_quinn_proto/enum.ClosePathError.html b/pr/259/docs/iroh_quinn_proto/enum.ClosePathError.html index 8e9002d44..b1e45369e 100644 --- a/pr/259/docs/iroh_quinn_proto/enum.ClosePathError.html +++ b/pr/259/docs/iroh_quinn_proto/enum.ClosePathError.html @@ -1,11 +1,11 @@ -ClosePathError in iroh_quinn_proto - Rust

ClosePathError

Enum ClosePathError 

Source
pub enum ClosePathError {
+ClosePathError in iroh_quinn_proto - Rust

ClosePathError

Enum ClosePathError 

Source
pub enum ClosePathError {
     ClosedPath,
     LastOpenPath,
 }
Expand description

Errors triggered when abandoning a path

Variants§

§

ClosedPath

The path is already closed or was never opened

§

LastOpenPath

This is the last path, which can not be abandoned

-

Trait Implementations§

Source§

impl Clone for ClosePathError

Source§

fn clone(&self) -> ClosePathError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosePathError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for ClosePathError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for ClosePathError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for ClosePathError

Source§

fn eq(&self, other: &ClosePathError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for ClosePathError

Source§

impl StructuralPartialEq for ClosePathError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for ClosePathError

Source§

fn clone(&self) -> ClosePathError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosePathError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for ClosePathError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for ClosePathError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for ClosePathError

Source§

fn eq(&self, other: &ClosePathError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for ClosePathError

Source§

impl StructuralPartialEq for ClosePathError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/259/docs/iroh_quinn_proto/enum.ConnectionError.html b/pr/259/docs/iroh_quinn_proto/enum.ConnectionError.html index bd9417b2f..83ac5fec1 100644 --- a/pr/259/docs/iroh_quinn_proto/enum.ConnectionError.html +++ b/pr/259/docs/iroh_quinn_proto/enum.ConnectionError.html @@ -1,4 +1,4 @@ -ConnectionError in iroh_quinn_proto - Rust

ConnectionError

Enum ConnectionError 

Source
pub enum ConnectionError {
+ConnectionError in iroh_quinn_proto - Rust

ConnectionError

Enum ConnectionError 

Source
pub enum ConnectionError {
     VersionMismatch,
     TransportError(TransportError),
     ConnectionClosed(ConnectionClose),
@@ -20,8 +20,8 @@ and §

LocallyClosed

The local application closed the connection

§

CidsExhausted

The connection could not be created because not enough of the CID space is available

Try using longer connection IDs.

-

Trait Implementations§

Source§

impl Clone for ConnectionError

Source§

fn clone(&self) -> ConnectionError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectionError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for ConnectionError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for ConnectionError

Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<ConnectionError> for Error

Source§

fn from(x: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for ConnectionError

Source§

fn from(source: TransportError) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ConnectionError

Source§

fn eq(&self, other: &ConnectionError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for ConnectionError

Source§

impl StructuralPartialEq for ConnectionError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for ConnectionError

Source§

fn clone(&self) -> ConnectionError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectionError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for ConnectionError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for ConnectionError

Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<ConnectionError> for Error

Source§

fn from(x: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for ConnectionError

Source§

fn from(source: TransportError) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ConnectionError

Source§

fn eq(&self, other: &ConnectionError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for ConnectionError

Source§

impl StructuralPartialEq for ConnectionError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/259/docs/iroh_quinn_proto/enum.Event.html b/pr/259/docs/iroh_quinn_proto/enum.Event.html index 07d0a321b..36c4b629b 100644 --- a/pr/259/docs/iroh_quinn_proto/enum.Event.html +++ b/pr/259/docs/iroh_quinn_proto/enum.Event.html @@ -1,4 +1,4 @@ -Event in iroh_quinn_proto - Rust

Event

Enum Event 

Source
pub enum Event {
+Event in iroh_quinn_proto - Rust

Event

Enum Event 

Source
pub enum Event {
     HandshakeDataReady,
     Connected,
     HandshakeConfirmed,
@@ -22,7 +22,7 @@
 
§

DatagramsUnblocked

One or more application datagrams have been sent after blocking

§

Path(PathEvent)

(Multi)Path events

§

NatTraversal(Event)

Iroh’s nat traversal events

-

Trait Implementations§

Source§

impl Debug for Event

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl From<PathEvent> for Event

Source§

fn from(source: PathEvent) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

§

impl !Freeze for Event

§

impl !RefUnwindSafe for Event

§

impl Send for Event

§

impl Sync for Event

§

impl Unpin for Event

§

impl !UnwindSafe for Event

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Debug for Event

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl From<PathEvent> for Event

Source§

fn from(source: PathEvent) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

§

impl !Freeze for Event

§

impl !RefUnwindSafe for Event

§

impl Send for Event

§

impl Sync for Event

§

impl Unpin for Event

§

impl !UnwindSafe for Event

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

diff --git a/pr/259/docs/iroh_quinn_proto/enum.PathError.html b/pr/259/docs/iroh_quinn_proto/enum.PathError.html index ce284983a..9658b4929 100644 --- a/pr/259/docs/iroh_quinn_proto/enum.PathError.html +++ b/pr/259/docs/iroh_quinn_proto/enum.PathError.html @@ -1,4 +1,4 @@ -PathError in iroh_quinn_proto - Rust

PathError

Enum PathError 

Source
pub enum PathError {
+PathError in iroh_quinn_proto - Rust

PathError

Enum PathError 

Source
pub enum PathError {
     MultipathNotNegotiated,
     ServerSideNotAllowed,
     MaxPathIdReached,
@@ -12,8 +12,8 @@
 
§

RemoteCidsExhausted

No remote CIDs available to open a new path

§

ValidationFailed

Path could not be validated and will be abandoned

§

InvalidRemoteAddress(SocketAddr)

The remote address for the path is not supported by the endpoint

-

Trait Implementations§

Source§

impl Clone for PathError

Source§

fn clone(&self) -> PathError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for PathError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for PathError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for PathError

Source§

fn eq(&self, other: &PathError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Copy for PathError

Source§

impl Eq for PathError

Source§

impl StructuralPartialEq for PathError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for PathError

Source§

fn clone(&self) -> PathError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for PathError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for PathError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for PathError

Source§

fn eq(&self, other: &PathError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Copy for PathError

Source§

impl Eq for PathError

Source§

impl StructuralPartialEq for PathError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/259/docs/iroh_quinn_proto/enum.PathEvent.html b/pr/259/docs/iroh_quinn_proto/enum.PathEvent.html index b4cd722ef..55ac4627f 100644 --- a/pr/259/docs/iroh_quinn_proto/enum.PathEvent.html +++ b/pr/259/docs/iroh_quinn_proto/enum.PathEvent.html @@ -1,4 +1,4 @@ -PathEvent in iroh_quinn_proto - Rust

PathEvent

Enum PathEvent 

Source
pub enum PathEvent {
+PathEvent in iroh_quinn_proto - Rust

PathEvent

Enum PathEvent 

Source
pub enum PathEvent {
     Opened {
         id: PathId,
     },
@@ -47,8 +47,8 @@ changes the status.

Fields

§id: PathId

Path over which the observed address was reported, PathId::ZERO when multipath is not negotiated

§addr: SocketAddr

The address observed by the remote over this path

-

Trait Implementations§

Source§

impl Clone for PathEvent

Source§

fn clone(&self) -> PathEvent

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathEvent

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl From<PathEvent> for Event

Source§

fn from(source: PathEvent) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for PathEvent

Source§

fn eq(&self, other: &PathEvent) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for PathEvent

Source§

impl StructuralPartialEq for PathEvent

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for PathEvent

Source§

fn clone(&self) -> PathEvent

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathEvent

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl From<PathEvent> for Event

Source§

fn from(source: PathEvent) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for PathEvent

Source§

fn eq(&self, other: &PathEvent) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for PathEvent

Source§

impl StructuralPartialEq for PathEvent

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/259/docs/iroh_quinn_proto/enum.PathStatus.html b/pr/259/docs/iroh_quinn_proto/enum.PathStatus.html index ef9d90668..d44aea0d7 100644 --- a/pr/259/docs/iroh_quinn_proto/enum.PathStatus.html +++ b/pr/259/docs/iroh_quinn_proto/enum.PathStatus.html @@ -1,4 +1,4 @@ -PathStatus in iroh_quinn_proto - Rust

PathStatus

Enum PathStatus 

Source
pub enum PathStatus {
+PathStatus in iroh_quinn_proto - Rust

PathStatus

Enum PathStatus 

Source
pub enum PathStatus {
     Available,
     Backup,
 }
Expand description

The QUIC-MULTIPATH path status

@@ -10,8 +10,8 @@ capacity.

§

Backup

Paths marked as backup will only be used if there are no available paths

If the max_idle_timeout is specified the path will be kept alive so that it does not expire.

-

Trait Implementations§

Source§

impl Clone for PathStatus

Source§

fn clone(&self) -> PathStatus

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathStatus

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for PathStatus

Source§

fn default() -> PathStatus

Returns the “default value” for a type. Read more
Source§

impl PartialEq for PathStatus

Source§

fn eq(&self, other: &PathStatus) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Copy for PathStatus

Source§

impl Eq for PathStatus

Source§

impl StructuralPartialEq for PathStatus

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for PathStatus

Source§

fn clone(&self) -> PathStatus

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathStatus

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for PathStatus

Source§

fn default() -> PathStatus

Returns the “default value” for a type. Read more
Source§

impl PartialEq for PathStatus

Source§

fn eq(&self, other: &PathStatus) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Copy for PathStatus

Source§

impl Eq for PathStatus

Source§

impl StructuralPartialEq for PathStatus

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/259/docs/iroh_quinn_proto/enum.SetPathStatusError.html b/pr/259/docs/iroh_quinn_proto/enum.SetPathStatusError.html index 0f04cb3b3..ec171b932 100644 --- a/pr/259/docs/iroh_quinn_proto/enum.SetPathStatusError.html +++ b/pr/259/docs/iroh_quinn_proto/enum.SetPathStatusError.html @@ -1,11 +1,11 @@ -SetPathStatusError in iroh_quinn_proto - Rust

SetPathStatusError

Enum SetPathStatusError 

Source
pub enum SetPathStatusError {
+SetPathStatusError in iroh_quinn_proto - Rust

SetPathStatusError

Enum SetPathStatusError 

Source
pub enum SetPathStatusError {
     ClosedPath,
     MultipathNotNegotiated,
 }
Expand description

Error from setting path status

Variants§

§

ClosedPath

Error indicating that a path has not been opened or has already been abandoned

§

MultipathNotNegotiated

Error indicating that this operation requires multipath to be negotiated whereas it hasn’t been

-

Trait Implementations§

Source§

impl Clone for SetPathStatusError

Source§

fn clone(&self) -> SetPathStatusError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for SetPathStatusError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for SetPathStatusError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for SetPathStatusError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for SetPathStatusError

Source§

fn eq(&self, other: &SetPathStatusError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for SetPathStatusError

Source§

impl StructuralPartialEq for SetPathStatusError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for SetPathStatusError

Source§

fn clone(&self) -> SetPathStatusError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for SetPathStatusError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for SetPathStatusError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for SetPathStatusError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for SetPathStatusError

Source§

fn eq(&self, other: &SetPathStatusError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for SetPathStatusError

Source§

impl StructuralPartialEq for SetPathStatusError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/259/docs/iroh_quinn_proto/struct.ClosedPath.html b/pr/259/docs/iroh_quinn_proto/struct.ClosedPath.html index e0e8ee016..6243c5b06 100644 --- a/pr/259/docs/iroh_quinn_proto/struct.ClosedPath.html +++ b/pr/259/docs/iroh_quinn_proto/struct.ClosedPath.html @@ -1,6 +1,6 @@ -ClosedPath in iroh_quinn_proto - Rust

ClosedPath

Struct ClosedPath 

Source
pub struct ClosedPath { /* private fields */ }
Expand description

Error indicating that a path has not been opened or has already been abandoned

-

Trait Implementations§

Source§

impl Clone for ClosedPath

Source§

fn clone(&self) -> ClosedPath

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosedPath

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for ClosedPath

Source§

fn default() -> ClosedPath

Returns the “default value” for a type. Read more
Source§

impl Display for ClosedPath

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for ClosedPath

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for ClosedPath

Source§

fn eq(&self, other: &ClosedPath) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for ClosedPath

Source§

impl StructuralPartialEq for ClosedPath

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +ClosedPath in iroh_quinn_proto - Rust

ClosedPath

Struct ClosedPath 

Source
pub struct ClosedPath { /* private fields */ }
Expand description

Error indicating that a path has not been opened or has already been abandoned

+

Trait Implementations§

Source§

impl Clone for ClosedPath

Source§

fn clone(&self) -> ClosedPath

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosedPath

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for ClosedPath

Source§

fn default() -> ClosedPath

Returns the “default value” for a type. Read more
Source§

impl Display for ClosedPath

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for ClosedPath

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for ClosedPath

Source§

fn eq(&self, other: &ClosedPath) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for ClosedPath

Source§

impl StructuralPartialEq for ClosedPath

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/259/docs/iroh_quinn_proto/struct.Connection.html b/pr/259/docs/iroh_quinn_proto/struct.Connection.html index cad3a2479..41a649128 100644 --- a/pr/259/docs/iroh_quinn_proto/struct.Connection.html +++ b/pr/259/docs/iroh_quinn_proto/struct.Connection.html @@ -33,7 +33,7 @@ increasing time. Specifically, calling Instant may be interleaved in any order with a call to handle_event at that same instant; however events or timeouts with different instants must not be interleaved.

-

Implementations§

Source§

impl Connection

Source

pub fn poll_timeout(&mut self) -> Option<Instant>

Returns the next time at which handle_timeout should be called

+

Implementations§

Source§

impl Connection

Source

pub fn poll_timeout(&mut self) -> Option<Instant>

Returns the next time at which handle_timeout should be called

The value returned may change after:

  • the application performed some I/O on the connection
  • @@ -197,27 +197,27 @@ decrement by one for each time a remotely initiated stream of matching direction
Source

pub fn is_multipath_negotiated(&self) -> bool

Whether the Multipath for QUIC extension is enabled.

Multipath is only enabled after the handshake is completed and if it was enabled by both peers.

-
Source

pub fn local_address_changed(&mut self)

Handle a change in the local address, i.e. an active migration

-
Source

pub fn current_mtu(&self) -> u16

Storage size required for the largest packet that can be transmitted on all currently +

Source

pub fn local_address_changed(&mut self)

Handle a change in the local address, i.e. an active migration

+
Source

pub fn current_mtu(&self) -> u16

Storage size required for the largest packet that can be transmitted on all currently available paths

Buffers passed to Connection::poll_transmit should be at least this large.

When multipath is enabled, this value is the minimum MTU across all available paths.

-
Source

pub fn add_nat_traversal_address( +

Source

pub fn add_nat_traversal_address( &mut self, address: SocketAddr, ) -> Result<(), Error>

Add addresses the local endpoint considers are reachable for nat traversal

-
Source

pub fn remove_nat_traversal_address( +

Source

pub fn remove_nat_traversal_address( &mut self, address: SocketAddr, ) -> Result<(), Error>

Removes an address the endpoing no longer considers reachable for nat traversal

Addresses not present in the set will be silently ignored.

-
Source

pub fn get_local_nat_traversal_addresses( +

Source

pub fn get_local_nat_traversal_addresses( &self, ) -> Result<Vec<SocketAddr>, Error>

Get the current local nat traversal addresses

-
Source

pub fn get_remote_nat_traversal_addresses( +

Source

pub fn get_remote_nat_traversal_addresses( &self, ) -> Result<Vec<SocketAddr>, Error>

Get the currently advertised nat traversal addresses by the server

-
Source

pub fn initiate_nat_traversal_round( +

Source

pub fn initiate_nat_traversal_round( &mut self, now: Instant, ) -> Result<Vec<SocketAddr>, Error>

Initiates a new nat traversal round

@@ -225,7 +225,7 @@ available paths

frames, and initiating probing of the known remote addresses. When a new round is initiated, the previous one is cancelled, and paths that have not been opened are closed.

Returns the server addresses that are now being probed.

-

Trait Implementations§

Source§

impl Debug for Connection

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Debug for Connection

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

diff --git a/pr/259/docs/iroh_quinn_proto/struct.RttEstimator.html b/pr/259/docs/iroh_quinn_proto/struct.RttEstimator.html index 516d7d7a8..27f8d4164 100644 --- a/pr/259/docs/iroh_quinn_proto/struct.RttEstimator.html +++ b/pr/259/docs/iroh_quinn_proto/struct.RttEstimator.html @@ -1,10 +1,10 @@ -RttEstimator in iroh_quinn_proto - Rust

RttEstimator

Struct RttEstimator 

Source
pub struct RttEstimator { /* private fields */ }
Expand description

RTT estimation for a particular network path

-

Implementations§

Source§

impl RttEstimator

Source

pub fn get(&self) -> Duration

The current best RTT estimation.

-
Source

pub fn conservative(&self) -> Duration

Conservative estimate of RTT

+RttEstimator in iroh_quinn_proto - Rust

RttEstimator

Struct RttEstimator 

Source
pub struct RttEstimator { /* private fields */ }
Expand description

RTT estimation for a particular network path

+

Implementations§

Source§

impl RttEstimator

Source

pub fn get(&self) -> Duration

The current best RTT estimation.

+
Source

pub fn conservative(&self) -> Duration

Conservative estimate of RTT

Takes the maximum of smoothed and latest RTT, as recommended in 6.1.2 of the recovery spec (draft 29).

-
Source

pub fn min(&self) -> Duration

Minimum RTT registered so far for this estimator.

-

Trait Implementations§

Source§

impl Clone for RttEstimator

Source§

fn clone(&self) -> RttEstimator

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for RttEstimator

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Copy for RttEstimator

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Source

pub fn min(&self) -> Duration

Minimum RTT registered so far for this estimator.

+

Trait Implementations§

Source§

impl Clone for RttEstimator

Source§

fn clone(&self) -> RttEstimator

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for RttEstimator

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Copy for RttEstimator

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/259/docs/iroh_quinn_proto/struct.TransportError.html b/pr/259/docs/iroh_quinn_proto/struct.TransportError.html index 275605327..c19c6bef6 100644 --- a/pr/259/docs/iroh_quinn_proto/struct.TransportError.html +++ b/pr/259/docs/iroh_quinn_proto/struct.TransportError.html @@ -11,7 +11,7 @@
§reason: String

Human-readable explanation of the reason

§crypto: Option<Arc<dyn Error + Send + Sync>>

An underlying crypto (e.g. TLS) layer error

Implementations§

Source§

impl Error

Source

pub fn new(code: Code, reason: String) -> Self

Construct an error with a code and a reason

-

Trait Implementations§

Source§

impl Clone for Error

Source§

fn clone(&self) -> Error

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Error

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for Error

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for Error

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<Error> for ConnectionClose

Source§

fn from(x: TransportError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for ConnectionError

Source§

fn from(source: TransportError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for TransportError

Source§

fn from(e: Error) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for Error

Source§

fn eq(&self, other: &Self) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +

Trait Implementations§

Source§

impl Clone for Error

Source§

fn clone(&self) -> Error

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Error

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for Error

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for Error

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<Error> for ConnectionClose

Source§

fn from(x: TransportError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for ConnectionError

Source§

fn from(source: TransportError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for TransportError

Source§

fn from(e: Error) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for Error

Source§

fn eq(&self, other: &Self) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Eq for Error

Auto Trait Implementations§

§

impl Freeze for Error

§

impl !RefUnwindSafe for Error

§

impl Send for Error

§

impl Sync for Error

§

impl Unpin for Error

§

impl !UnwindSafe for Error

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/259/docs/src/iroh_quinn_proto/connection/mod.rs.html b/pr/259/docs/src/iroh_quinn_proto/connection/mod.rs.html index 06b644491..a20f621f2 100644 --- a/pr/259/docs/src/iroh_quinn_proto/connection/mod.rs.html +++ b/pr/259/docs/src/iroh_quinn_proto/connection/mod.rs.html @@ -4292,2405 +4292,2377 @@ 4292 use paths::OnPathResponseReceived::*; 4293 match path.data.on_path_response_received(now, response.0, remote) { 4294 OnPath { was_open } => { -4295 use PathTimer::*; -4296 let qlog = self.qlog.with_time(now); -4297 -4298 self.timers -4299 .stop(Timer::PerPath(path_id, PathValidation), qlog.clone()); -4300 self.timers -4301 .stop(Timer::PerPath(path_id, PathOpen), qlog.clone()); -4302 if !was_open { -4303 self.events -4304 .push_back(Event::Path(PathEvent::Opened { id: path_id })); -4305 if let Some(observed) = path.data.last_observed_addr_report.as_ref() -4306 { -4307 self.events.push_back(Event::Path(PathEvent::ObservedAddr { -4308 id: path_id, -4309 addr: observed.socket_addr(), -4310 })); -4311 } -4312 } -4313 if let Some((_, ref mut prev)) = path.prev { -4314 prev.challenges_sent.clear(); -4315 prev.send_new_challenge = false; -4316 } -4317 -4318 match path.data.earliest_expiring_challenge() { -4319 Some(new_expire_time) => { -4320 let expires = new_expire_time -4321 + self.ack_frequency.max_ack_delay_for_pto(); -4322 self.timers.set( -4323 Timer::PerPath(path_id, PathChallengeLost), -4324 expires, -4325 qlog, -4326 ) -4327 } -4328 None => self -4329 .timers -4330 .stop(Timer::PerPath(path_id, PathChallengeLost), qlog), -4331 } -4332 } -4333 OffPath => { -4334 debug!("Response to off-path PathChallenge!"); -4335 match path.data.earliest_expiring_challenge() { -4336 Some(new_expire_time) => { -4337 let expires = new_expire_time -4338 + self.ack_frequency.max_ack_delay_for_pto(); -4339 self.timers.set( -4340 Timer::PerPath(path_id, PathTimer::PathChallengeLost), -4341 expires, -4342 self.qlog.with_time(now), -4343 ) -4344 } -4345 None => self.timers.stop( -4346 Timer::PerPath(path_id, PathTimer::PathChallengeLost), -4347 self.qlog.with_time(now), -4348 ), -4349 } -4350 } -4351 Unknown => debug!(%response, "ignoring invalid PATH_RESPONSE"), -4352 Invalid { expected } => { -4353 debug!(%response, from=%remote, %expected, "ignoring invalid PATH_RESPONSE") -4354 } -4355 } -4356 } -4357 Frame::MaxData(bytes) => { -4358 self.streams.received_max_data(bytes); -4359 } -4360 Frame::MaxStreamData { id, offset } => { -4361 self.streams.received_max_stream_data(id, offset)?; -4362 } -4363 Frame::MaxStreams { dir, count } => { -4364 self.streams.received_max_streams(dir, count)?; -4365 } -4366 Frame::ResetStream(frame) => { -4367 if self.streams.received_reset(frame)?.should_transmit() { -4368 self.spaces[SpaceId::Data].pending.max_data = true; -4369 } -4370 } -4371 Frame::DataBlocked { offset } => { -4372 debug!(offset, "peer claims to be blocked at connection level"); -4373 } -4374 Frame::StreamDataBlocked { id, offset } => { -4375 if id.initiator() == self.side.side() && id.dir() == Dir::Uni { -4376 debug!("got STREAM_DATA_BLOCKED on send-only {}", id); -4377 return Err(TransportError::STREAM_STATE_ERROR( -4378 "STREAM_DATA_BLOCKED on send-only stream", -4379 )); -4380 } -4381 debug!( -4382 stream = %id, -4383 offset, "peer claims to be blocked at stream level" -4384 ); -4385 } -4386 Frame::StreamsBlocked { dir, limit } => { -4387 if limit > MAX_STREAM_COUNT { -4388 return Err(TransportError::FRAME_ENCODING_ERROR( -4389 "unrepresentable stream limit", -4390 )); -4391 } -4392 debug!( -4393 "peer claims to be blocked opening more than {} {} streams", -4394 limit, dir -4395 ); -4396 } -4397 Frame::StopSending(frame::StopSending { id, error_code }) => { -4398 if id.initiator() != self.side.side() { -4399 if id.dir() == Dir::Uni { -4400 debug!("got STOP_SENDING on recv-only {}", id); -4401 return Err(TransportError::STREAM_STATE_ERROR( -4402 "STOP_SENDING on recv-only stream", -4403 )); -4404 } -4405 } else if self.streams.is_local_unopened(id) { -4406 return Err(TransportError::STREAM_STATE_ERROR( -4407 "STOP_SENDING on unopened stream", -4408 )); +4295 // TODO(@divma): reset timers using the remaining off-path challenges +4296 use PathTimer::*; +4297 let qlog = self.qlog.with_time(now); +4298 +4299 self.timers +4300 .stop(Timer::PerPath(path_id, PathValidation), qlog.clone()); +4301 self.timers +4302 .stop(Timer::PerPath(path_id, PathChallengeLost), qlog.clone()); +4303 self.timers.stop(Timer::PerPath(path_id, PathOpen), qlog); +4304 if !was_open { +4305 self.events +4306 .push_back(Event::Path(PathEvent::Opened { id: path_id })); +4307 if let Some(observed) = path.data.last_observed_addr_report.as_ref() +4308 { +4309 self.events.push_back(Event::Path(PathEvent::ObservedAddr { +4310 id: path_id, +4311 addr: observed.socket_addr(), +4312 })); +4313 } +4314 } +4315 if let Some((_, ref mut prev)) = path.prev { +4316 prev.challenges_sent.clear(); +4317 prev.send_new_challenge = false; +4318 } +4319 } +4320 OffPath => { +4321 debug!("Response to off-path PathChallenge!"); +4322 } +4323 Unknown => debug!(%response, "ignoring invalid PATH_RESPONSE"), +4324 Invalid { expected } => { +4325 debug!(%response, from=%remote, %expected, "ignoring invalid PATH_RESPONSE") +4326 } +4327 } +4328 } +4329 Frame::MaxData(bytes) => { +4330 self.streams.received_max_data(bytes); +4331 } +4332 Frame::MaxStreamData { id, offset } => { +4333 self.streams.received_max_stream_data(id, offset)?; +4334 } +4335 Frame::MaxStreams { dir, count } => { +4336 self.streams.received_max_streams(dir, count)?; +4337 } +4338 Frame::ResetStream(frame) => { +4339 if self.streams.received_reset(frame)?.should_transmit() { +4340 self.spaces[SpaceId::Data].pending.max_data = true; +4341 } +4342 } +4343 Frame::DataBlocked { offset } => { +4344 debug!(offset, "peer claims to be blocked at connection level"); +4345 } +4346 Frame::StreamDataBlocked { id, offset } => { +4347 if id.initiator() == self.side.side() && id.dir() == Dir::Uni { +4348 debug!("got STREAM_DATA_BLOCKED on send-only {}", id); +4349 return Err(TransportError::STREAM_STATE_ERROR( +4350 "STREAM_DATA_BLOCKED on send-only stream", +4351 )); +4352 } +4353 debug!( +4354 stream = %id, +4355 offset, "peer claims to be blocked at stream level" +4356 ); +4357 } +4358 Frame::StreamsBlocked { dir, limit } => { +4359 if limit > MAX_STREAM_COUNT { +4360 return Err(TransportError::FRAME_ENCODING_ERROR( +4361 "unrepresentable stream limit", +4362 )); +4363 } +4364 debug!( +4365 "peer claims to be blocked opening more than {} {} streams", +4366 limit, dir +4367 ); +4368 } +4369 Frame::StopSending(frame::StopSending { id, error_code }) => { +4370 if id.initiator() != self.side.side() { +4371 if id.dir() == Dir::Uni { +4372 debug!("got STOP_SENDING on recv-only {}", id); +4373 return Err(TransportError::STREAM_STATE_ERROR( +4374 "STOP_SENDING on recv-only stream", +4375 )); +4376 } +4377 } else if self.streams.is_local_unopened(id) { +4378 return Err(TransportError::STREAM_STATE_ERROR( +4379 "STOP_SENDING on unopened stream", +4380 )); +4381 } +4382 self.streams.received_stop_sending(id, error_code); +4383 } +4384 Frame::RetireConnectionId(frame::RetireConnectionId { path_id, sequence }) => { +4385 if let Some(ref path_id) = path_id { +4386 span.record("path", tracing::field::debug(&path_id)); +4387 } +4388 let path_id = path_id.unwrap_or_default(); +4389 match self.local_cid_state.get_mut(&path_id) { +4390 None => error!(?path_id, "RETIRE_CONNECTION_ID for unknown path"), +4391 Some(cid_state) => { +4392 let allow_more_cids = cid_state +4393 .on_cid_retirement(sequence, self.peer_params.issue_cids_limit())?; +4394 +4395 // If the path has closed, we do not issue more CIDs for this path +4396 // For details see https://www.ietf.org/archive/id/draft-ietf-quic-multipath-17.html#section-3.2.2 +4397 // > an endpoint SHOULD provide new connection IDs for that path, if still open, using PATH_NEW_CONNECTION_ID frames. +4398 let has_path = !self.abandoned_paths.contains(&path_id); +4399 let allow_more_cids = allow_more_cids && has_path; +4400 +4401 self.endpoint_events +4402 .push_back(EndpointEventInner::RetireConnectionId( +4403 now, +4404 path_id, +4405 sequence, +4406 allow_more_cids, +4407 )); +4408 } 4409 } -4410 self.streams.received_stop_sending(id, error_code); -4411 } -4412 Frame::RetireConnectionId(frame::RetireConnectionId { path_id, sequence }) => { -4413 if let Some(ref path_id) = path_id { -4414 span.record("path", tracing::field::debug(&path_id)); -4415 } -4416 let path_id = path_id.unwrap_or_default(); -4417 match self.local_cid_state.get_mut(&path_id) { -4418 None => error!(?path_id, "RETIRE_CONNECTION_ID for unknown path"), -4419 Some(cid_state) => { -4420 let allow_more_cids = cid_state -4421 .on_cid_retirement(sequence, self.peer_params.issue_cids_limit())?; -4422 -4423 // If the path has closed, we do not issue more CIDs for this path -4424 // For details see https://www.ietf.org/archive/id/draft-ietf-quic-multipath-17.html#section-3.2.2 -4425 // > an endpoint SHOULD provide new connection IDs for that path, if still open, using PATH_NEW_CONNECTION_ID frames. -4426 let has_path = !self.abandoned_paths.contains(&path_id); -4427 let allow_more_cids = allow_more_cids && has_path; -4428 -4429 self.endpoint_events -4430 .push_back(EndpointEventInner::RetireConnectionId( -4431 now, -4432 path_id, -4433 sequence, -4434 allow_more_cids, -4435 )); -4436 } -4437 } -4438 } -4439 Frame::NewConnectionId(frame) => { -4440 let path_id = if let Some(path_id) = frame.path_id { -4441 if !self.is_multipath_negotiated() { -4442 return Err(TransportError::PROTOCOL_VIOLATION( -4443 "received PATH_NEW_CONNECTION_ID frame when multipath was not negotiated", -4444 )); -4445 } -4446 if path_id > self.local_max_path_id { -4447 return Err(TransportError::PROTOCOL_VIOLATION( -4448 "PATH_NEW_CONNECTION_ID contains path_id exceeding current max", -4449 )); -4450 } -4451 path_id -4452 } else { -4453 PathId::ZERO -4454 }; -4455 -4456 if self.abandoned_paths.contains(&path_id) { -4457 trace!("ignoring issued CID for abandoned path"); -4458 continue; -4459 } -4460 if let Some(ref path_id) = frame.path_id { -4461 span.record("path", tracing::field::debug(&path_id)); -4462 } -4463 let rem_cids = self -4464 .rem_cids -4465 .entry(path_id) -4466 .or_insert_with(|| CidQueue::new(frame.id)); -4467 if rem_cids.active().is_empty() { -4468 // TODO(@divma): is the entry removed later? (rem_cids.entry) -4469 return Err(TransportError::PROTOCOL_VIOLATION( -4470 "NEW_CONNECTION_ID when CIDs aren't in use", -4471 )); -4472 } -4473 if frame.retire_prior_to > frame.sequence { -4474 return Err(TransportError::PROTOCOL_VIOLATION( -4475 "NEW_CONNECTION_ID retiring unissued CIDs", -4476 )); -4477 } -4478 -4479 use crate::cid_queue::InsertError; -4480 match rem_cids.insert(frame) { -4481 Ok(None) => {} -4482 Ok(Some((retired, reset_token))) => { -4483 let pending_retired = -4484 &mut self.spaces[SpaceId::Data].pending.retire_cids; -4485 /// Ensure `pending_retired` cannot grow without bound. Limit is -4486 /// somewhat arbitrary but very permissive. -4487 const MAX_PENDING_RETIRED_CIDS: u64 = CidQueue::LEN as u64 * 10; -4488 // We don't bother counting in-flight frames because those are bounded -4489 // by congestion control. -4490 if (pending_retired.len() as u64) -4491 .saturating_add(retired.end.saturating_sub(retired.start)) -4492 > MAX_PENDING_RETIRED_CIDS -4493 { -4494 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR( -4495 "queued too many retired CIDs", -4496 )); -4497 } -4498 pending_retired.extend(retired.map(|seq| (path_id, seq))); -4499 self.set_reset_token(path_id, remote, reset_token); -4500 } -4501 Err(InsertError::ExceedsLimit) => { -4502 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR("")); -4503 } -4504 Err(InsertError::Retired) => { -4505 trace!("discarding already-retired"); -4506 // RETIRE_CONNECTION_ID might not have been previously sent if e.g. a -4507 // range of connection IDs larger than the active connection ID limit -4508 // was retired all at once via retire_prior_to. -4509 self.spaces[SpaceId::Data] -4510 .pending -4511 .retire_cids -4512 .push((path_id, frame.sequence)); -4513 continue; -4514 } -4515 }; -4516 -4517 if self.side.is_server() -4518 && path_id == PathId::ZERO -4519 && self -4520 .rem_cids -4521 .get(&PathId::ZERO) -4522 .map(|cids| cids.active_seq() == 0) -4523 .unwrap_or_default() -4524 { -4525 // We're a server still using the initial remote CID for the client, so -4526 // let's switch immediately to enable clientside stateless resets. -4527 self.update_rem_cid(PathId::ZERO); -4528 } -4529 } -4530 Frame::NewToken(NewToken { token }) => { -4531 let ConnectionSide::Client { -4532 token_store, -4533 server_name, -4534 .. -4535 } = &self.side -4536 else { -4537 return Err(TransportError::PROTOCOL_VIOLATION("client sent NEW_TOKEN")); -4538 }; -4539 if token.is_empty() { -4540 return Err(TransportError::FRAME_ENCODING_ERROR("empty token")); -4541 } -4542 trace!("got new token"); -4543 token_store.insert(server_name, token); -4544 } -4545 Frame::Datagram(datagram) => { -4546 if self -4547 .datagrams -4548 .received(datagram, &self.config.datagram_receive_buffer_size)? -4549 { -4550 self.events.push_back(Event::DatagramReceived); -4551 } -4552 } -4553 Frame::AckFrequency(ack_frequency) => { -4554 // This frame can only be sent in the Data space -4555 -4556 if !self.ack_frequency.ack_frequency_received(&ack_frequency)? { -4557 // The AckFrequency frame is stale (we have already received a more -4558 // recent one) -4559 continue; -4560 } -4561 -4562 // Update the params for all of our paths -4563 for (path_id, space) in self.spaces[SpaceId::Data].number_spaces.iter_mut() { -4564 space.pending_acks.set_ack_frequency_params(&ack_frequency); -4565 -4566 // Our `max_ack_delay` has been updated, so we may need to adjust -4567 // its associated timeout -4568 if let Some(timeout) = space -4569 .pending_acks -4570 .max_ack_delay_timeout(self.ack_frequency.max_ack_delay) -4571 { -4572 self.timers.set( -4573 Timer::PerPath(*path_id, PathTimer::MaxAckDelay), -4574 timeout, -4575 self.qlog.with_time(now), -4576 ); -4577 } -4578 } -4579 } -4580 Frame::ImmediateAck => { -4581 // This frame can only be sent in the Data space -4582 for pns in self.spaces[SpaceId::Data].iter_paths_mut() { -4583 pns.pending_acks.set_immediate_ack_required(); -4584 } -4585 } -4586 Frame::HandshakeDone => { -4587 if self.side.is_server() { -4588 return Err(TransportError::PROTOCOL_VIOLATION( -4589 "client sent HANDSHAKE_DONE", -4590 )); -4591 } -4592 if self.spaces[SpaceId::Handshake].crypto.is_some() { -4593 self.discard_space(now, SpaceId::Handshake); -4594 } -4595 self.events.push_back(Event::HandshakeConfirmed); -4596 trace!("handshake confirmed"); -4597 } -4598 Frame::ObservedAddr(observed) => { -4599 // check if params allows the peer to send report and this node to receive it -4600 trace!(seq_no = %observed.seq_no, ip = %observed.ip, port = observed.port); -4601 if !self -4602 .peer_params -4603 .address_discovery_role -4604 .should_report(&self.config.address_discovery_role) -4605 { -4606 return Err(TransportError::PROTOCOL_VIOLATION( -4607 "received OBSERVED_ADDRESS frame when not negotiated", -4608 )); -4609 } -4610 // must only be sent in data space -4611 if packet.header.space() != SpaceId::Data { -4612 return Err(TransportError::PROTOCOL_VIOLATION( -4613 "OBSERVED_ADDRESS frame outside data space", -4614 )); -4615 } -4616 -4617 let path = self.path_data_mut(path_id); -4618 if remote == path.remote { -4619 if let Some(updated) = path.update_observed_addr_report(observed) { -4620 if path.open { -4621 self.events.push_back(Event::Path(PathEvent::ObservedAddr { -4622 id: path_id, -4623 addr: updated, -4624 })); -4625 } -4626 // otherwise the event is reported when the path is deemed open -4627 } -4628 } else { -4629 // include in migration -4630 migration_observed_addr = Some(observed) -4631 } -4632 } -4633 Frame::PathAbandon(frame::PathAbandon { -4634 path_id, -4635 error_code, -4636 }) => { -4637 span.record("path", tracing::field::debug(&path_id)); -4638 // TODO(flub): don't really know which error code to use here. -4639 let already_abandoned = match self.close_path(now, path_id, error_code.into()) { -4640 Ok(()) => { -4641 trace!("peer abandoned path"); -4642 false -4643 } -4644 Err(ClosePathError::LastOpenPath) => { -4645 trace!("peer abandoned last path, closing connection"); -4646 // TODO(flub): which error code? -4647 return Err(TransportError::NO_ERROR("last path abandoned by peer")); -4648 } -4649 Err(ClosePathError::ClosedPath) => { -4650 trace!("peer abandoned already closed path"); -4651 true -4652 } -4653 }; -4654 // If we receive a retransmit of PATH_ABANDON then we may already have -4655 // abandoned this path locally. In that case the DiscardPath timer -4656 // may already have fired and we no longer have any state for this path. -4657 // Only set this timer if we still have path state. -4658 if self.path(path_id).is_some() && !already_abandoned { -4659 // TODO(flub): Checking is_some() here followed by a number of calls -4660 // that would panic if it was None is really ugly. If only we -4661 // could do something like PathData::pto(). One day we'll have -4662 // unified SpaceId and PathId and this will be possible. -4663 let delay = self.pto(SpaceId::Data, path_id) * 3; -4664 self.timers.set( -4665 Timer::PerPath(path_id, PathTimer::DiscardPath), -4666 now + delay, -4667 self.qlog.with_time(now), -4668 ); -4669 } -4670 } -4671 Frame::PathStatusAvailable(info) => { -4672 span.record("path", tracing::field::debug(&info.path_id)); -4673 if self.is_multipath_negotiated() { -4674 self.on_path_status( -4675 info.path_id, -4676 PathStatus::Available, -4677 info.status_seq_no, -4678 ); -4679 } else { -4680 return Err(TransportError::PROTOCOL_VIOLATION( -4681 "received PATH_STATUS_AVAILABLE frame when multipath was not negotiated", -4682 )); -4683 } -4684 } -4685 Frame::PathStatusBackup(info) => { -4686 span.record("path", tracing::field::debug(&info.path_id)); -4687 if self.is_multipath_negotiated() { -4688 self.on_path_status(info.path_id, PathStatus::Backup, info.status_seq_no); -4689 } else { -4690 return Err(TransportError::PROTOCOL_VIOLATION( -4691 "received PATH_STATUS_BACKUP frame when multipath was not negotiated", -4692 )); -4693 } -4694 } -4695 Frame::MaxPathId(frame::MaxPathId(path_id)) => { -4696 span.record("path", tracing::field::debug(&path_id)); -4697 if !self.is_multipath_negotiated() { -4698 return Err(TransportError::PROTOCOL_VIOLATION( -4699 "received MAX_PATH_ID frame when multipath was not negotiated", -4700 )); -4701 } -4702 // frames that do not increase the path id are ignored -4703 if path_id > self.remote_max_path_id { -4704 self.remote_max_path_id = path_id; -4705 self.issue_first_path_cids(now); -4706 } -4707 } -4708 Frame::PathsBlocked(frame::PathsBlocked(max_path_id)) => { -4709 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST -4710 // be treated as a connection error of type PROTOCOL_VIOLATION. -4711 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> -4712 if self.is_multipath_negotiated() { -4713 if self.local_max_path_id > max_path_id { -4714 return Err(TransportError::PROTOCOL_VIOLATION( -4715 "PATHS_BLOCKED maximum path identifier was larger than local maximum", -4716 )); -4717 } -4718 debug!("received PATHS_BLOCKED({:?})", max_path_id); -4719 // TODO(@divma): ensure max concurrent paths -4720 } else { -4721 return Err(TransportError::PROTOCOL_VIOLATION( -4722 "received PATHS_BLOCKED frame when not multipath was not negotiated", -4723 )); -4724 } -4725 } -4726 Frame::PathCidsBlocked(frame::PathCidsBlocked { path_id, next_seq }) => { -4727 // Nothing to do. This is recorded in the frame stats, but otherwise we -4728 // always issue all CIDs we're allowed to issue, so either this is an -4729 // impatient peer or a bug on our side. -4730 -4731 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST -4732 // be treated as a connection error of type PROTOCOL_VIOLATION. -4733 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> -4734 if self.is_multipath_negotiated() { -4735 if path_id > self.local_max_path_id { -4736 return Err(TransportError::PROTOCOL_VIOLATION( -4737 "PATH_CIDS_BLOCKED path identifier was larger than local maximum", -4738 )); -4739 } -4740 if next_seq.0 -4741 > self -4742 .local_cid_state -4743 .get(&path_id) -4744 .map(|cid_state| cid_state.active_seq().1 + 1) -4745 .unwrap_or_default() -4746 { -4747 return Err(TransportError::PROTOCOL_VIOLATION( -4748 "PATH_CIDS_BLOCKED next sequence number larger than in local state", -4749 )); -4750 } -4751 debug!(%path_id, %next_seq, "received PATH_CIDS_BLOCKED"); -4752 } else { -4753 return Err(TransportError::PROTOCOL_VIOLATION( -4754 "received PATH_CIDS_BLOCKED frame when not multipath was not negotiated", -4755 )); +4410 } +4411 Frame::NewConnectionId(frame) => { +4412 let path_id = if let Some(path_id) = frame.path_id { +4413 if !self.is_multipath_negotiated() { +4414 return Err(TransportError::PROTOCOL_VIOLATION( +4415 "received PATH_NEW_CONNECTION_ID frame when multipath was not negotiated", +4416 )); +4417 } +4418 if path_id > self.local_max_path_id { +4419 return Err(TransportError::PROTOCOL_VIOLATION( +4420 "PATH_NEW_CONNECTION_ID contains path_id exceeding current max", +4421 )); +4422 } +4423 path_id +4424 } else { +4425 PathId::ZERO +4426 }; +4427 +4428 if self.abandoned_paths.contains(&path_id) { +4429 trace!("ignoring issued CID for abandoned path"); +4430 continue; +4431 } +4432 if let Some(ref path_id) = frame.path_id { +4433 span.record("path", tracing::field::debug(&path_id)); +4434 } +4435 let rem_cids = self +4436 .rem_cids +4437 .entry(path_id) +4438 .or_insert_with(|| CidQueue::new(frame.id)); +4439 if rem_cids.active().is_empty() { +4440 // TODO(@divma): is the entry removed later? (rem_cids.entry) +4441 return Err(TransportError::PROTOCOL_VIOLATION( +4442 "NEW_CONNECTION_ID when CIDs aren't in use", +4443 )); +4444 } +4445 if frame.retire_prior_to > frame.sequence { +4446 return Err(TransportError::PROTOCOL_VIOLATION( +4447 "NEW_CONNECTION_ID retiring unissued CIDs", +4448 )); +4449 } +4450 +4451 use crate::cid_queue::InsertError; +4452 match rem_cids.insert(frame) { +4453 Ok(None) => {} +4454 Ok(Some((retired, reset_token))) => { +4455 let pending_retired = +4456 &mut self.spaces[SpaceId::Data].pending.retire_cids; +4457 /// Ensure `pending_retired` cannot grow without bound. Limit is +4458 /// somewhat arbitrary but very permissive. +4459 const MAX_PENDING_RETIRED_CIDS: u64 = CidQueue::LEN as u64 * 10; +4460 // We don't bother counting in-flight frames because those are bounded +4461 // by congestion control. +4462 if (pending_retired.len() as u64) +4463 .saturating_add(retired.end.saturating_sub(retired.start)) +4464 > MAX_PENDING_RETIRED_CIDS +4465 { +4466 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR( +4467 "queued too many retired CIDs", +4468 )); +4469 } +4470 pending_retired.extend(retired.map(|seq| (path_id, seq))); +4471 self.set_reset_token(path_id, remote, reset_token); +4472 } +4473 Err(InsertError::ExceedsLimit) => { +4474 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR("")); +4475 } +4476 Err(InsertError::Retired) => { +4477 trace!("discarding already-retired"); +4478 // RETIRE_CONNECTION_ID might not have been previously sent if e.g. a +4479 // range of connection IDs larger than the active connection ID limit +4480 // was retired all at once via retire_prior_to. +4481 self.spaces[SpaceId::Data] +4482 .pending +4483 .retire_cids +4484 .push((path_id, frame.sequence)); +4485 continue; +4486 } +4487 }; +4488 +4489 if self.side.is_server() +4490 && path_id == PathId::ZERO +4491 && self +4492 .rem_cids +4493 .get(&PathId::ZERO) +4494 .map(|cids| cids.active_seq() == 0) +4495 .unwrap_or_default() +4496 { +4497 // We're a server still using the initial remote CID for the client, so +4498 // let's switch immediately to enable clientside stateless resets. +4499 self.update_rem_cid(PathId::ZERO); +4500 } +4501 } +4502 Frame::NewToken(NewToken { token }) => { +4503 let ConnectionSide::Client { +4504 token_store, +4505 server_name, +4506 .. +4507 } = &self.side +4508 else { +4509 return Err(TransportError::PROTOCOL_VIOLATION("client sent NEW_TOKEN")); +4510 }; +4511 if token.is_empty() { +4512 return Err(TransportError::FRAME_ENCODING_ERROR("empty token")); +4513 } +4514 trace!("got new token"); +4515 token_store.insert(server_name, token); +4516 } +4517 Frame::Datagram(datagram) => { +4518 if self +4519 .datagrams +4520 .received(datagram, &self.config.datagram_receive_buffer_size)? +4521 { +4522 self.events.push_back(Event::DatagramReceived); +4523 } +4524 } +4525 Frame::AckFrequency(ack_frequency) => { +4526 // This frame can only be sent in the Data space +4527 +4528 if !self.ack_frequency.ack_frequency_received(&ack_frequency)? { +4529 // The AckFrequency frame is stale (we have already received a more +4530 // recent one) +4531 continue; +4532 } +4533 +4534 // Update the params for all of our paths +4535 for (path_id, space) in self.spaces[SpaceId::Data].number_spaces.iter_mut() { +4536 space.pending_acks.set_ack_frequency_params(&ack_frequency); +4537 +4538 // Our `max_ack_delay` has been updated, so we may need to adjust +4539 // its associated timeout +4540 if let Some(timeout) = space +4541 .pending_acks +4542 .max_ack_delay_timeout(self.ack_frequency.max_ack_delay) +4543 { +4544 self.timers.set( +4545 Timer::PerPath(*path_id, PathTimer::MaxAckDelay), +4546 timeout, +4547 self.qlog.with_time(now), +4548 ); +4549 } +4550 } +4551 } +4552 Frame::ImmediateAck => { +4553 // This frame can only be sent in the Data space +4554 for pns in self.spaces[SpaceId::Data].iter_paths_mut() { +4555 pns.pending_acks.set_immediate_ack_required(); +4556 } +4557 } +4558 Frame::HandshakeDone => { +4559 if self.side.is_server() { +4560 return Err(TransportError::PROTOCOL_VIOLATION( +4561 "client sent HANDSHAKE_DONE", +4562 )); +4563 } +4564 if self.spaces[SpaceId::Handshake].crypto.is_some() { +4565 self.discard_space(now, SpaceId::Handshake); +4566 } +4567 self.events.push_back(Event::HandshakeConfirmed); +4568 trace!("handshake confirmed"); +4569 } +4570 Frame::ObservedAddr(observed) => { +4571 // check if params allows the peer to send report and this node to receive it +4572 trace!(seq_no = %observed.seq_no, ip = %observed.ip, port = observed.port); +4573 if !self +4574 .peer_params +4575 .address_discovery_role +4576 .should_report(&self.config.address_discovery_role) +4577 { +4578 return Err(TransportError::PROTOCOL_VIOLATION( +4579 "received OBSERVED_ADDRESS frame when not negotiated", +4580 )); +4581 } +4582 // must only be sent in data space +4583 if packet.header.space() != SpaceId::Data { +4584 return Err(TransportError::PROTOCOL_VIOLATION( +4585 "OBSERVED_ADDRESS frame outside data space", +4586 )); +4587 } +4588 +4589 let path = self.path_data_mut(path_id); +4590 if remote == path.remote { +4591 if let Some(updated) = path.update_observed_addr_report(observed) { +4592 if path.open { +4593 self.events.push_back(Event::Path(PathEvent::ObservedAddr { +4594 id: path_id, +4595 addr: updated, +4596 })); +4597 } +4598 // otherwise the event is reported when the path is deemed open +4599 } +4600 } else { +4601 // include in migration +4602 migration_observed_addr = Some(observed) +4603 } +4604 } +4605 Frame::PathAbandon(frame::PathAbandon { +4606 path_id, +4607 error_code, +4608 }) => { +4609 span.record("path", tracing::field::debug(&path_id)); +4610 // TODO(flub): don't really know which error code to use here. +4611 let already_abandoned = match self.close_path(now, path_id, error_code.into()) { +4612 Ok(()) => { +4613 trace!("peer abandoned path"); +4614 false +4615 } +4616 Err(ClosePathError::LastOpenPath) => { +4617 trace!("peer abandoned last path, closing connection"); +4618 // TODO(flub): which error code? +4619 return Err(TransportError::NO_ERROR("last path abandoned by peer")); +4620 } +4621 Err(ClosePathError::ClosedPath) => { +4622 trace!("peer abandoned already closed path"); +4623 true +4624 } +4625 }; +4626 // If we receive a retransmit of PATH_ABANDON then we may already have +4627 // abandoned this path locally. In that case the DiscardPath timer +4628 // may already have fired and we no longer have any state for this path. +4629 // Only set this timer if we still have path state. +4630 if self.path(path_id).is_some() && !already_abandoned { +4631 // TODO(flub): Checking is_some() here followed by a number of calls +4632 // that would panic if it was None is really ugly. If only we +4633 // could do something like PathData::pto(). One day we'll have +4634 // unified SpaceId and PathId and this will be possible. +4635 let delay = self.pto(SpaceId::Data, path_id) * 3; +4636 self.timers.set( +4637 Timer::PerPath(path_id, PathTimer::DiscardPath), +4638 now + delay, +4639 self.qlog.with_time(now), +4640 ); +4641 } +4642 } +4643 Frame::PathStatusAvailable(info) => { +4644 span.record("path", tracing::field::debug(&info.path_id)); +4645 if self.is_multipath_negotiated() { +4646 self.on_path_status( +4647 info.path_id, +4648 PathStatus::Available, +4649 info.status_seq_no, +4650 ); +4651 } else { +4652 return Err(TransportError::PROTOCOL_VIOLATION( +4653 "received PATH_STATUS_AVAILABLE frame when multipath was not negotiated", +4654 )); +4655 } +4656 } +4657 Frame::PathStatusBackup(info) => { +4658 span.record("path", tracing::field::debug(&info.path_id)); +4659 if self.is_multipath_negotiated() { +4660 self.on_path_status(info.path_id, PathStatus::Backup, info.status_seq_no); +4661 } else { +4662 return Err(TransportError::PROTOCOL_VIOLATION( +4663 "received PATH_STATUS_BACKUP frame when multipath was not negotiated", +4664 )); +4665 } +4666 } +4667 Frame::MaxPathId(frame::MaxPathId(path_id)) => { +4668 span.record("path", tracing::field::debug(&path_id)); +4669 if !self.is_multipath_negotiated() { +4670 return Err(TransportError::PROTOCOL_VIOLATION( +4671 "received MAX_PATH_ID frame when multipath was not negotiated", +4672 )); +4673 } +4674 // frames that do not increase the path id are ignored +4675 if path_id > self.remote_max_path_id { +4676 self.remote_max_path_id = path_id; +4677 self.issue_first_path_cids(now); +4678 } +4679 } +4680 Frame::PathsBlocked(frame::PathsBlocked(max_path_id)) => { +4681 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST +4682 // be treated as a connection error of type PROTOCOL_VIOLATION. +4683 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> +4684 if self.is_multipath_negotiated() { +4685 if self.local_max_path_id > max_path_id { +4686 return Err(TransportError::PROTOCOL_VIOLATION( +4687 "PATHS_BLOCKED maximum path identifier was larger than local maximum", +4688 )); +4689 } +4690 debug!("received PATHS_BLOCKED({:?})", max_path_id); +4691 // TODO(@divma): ensure max concurrent paths +4692 } else { +4693 return Err(TransportError::PROTOCOL_VIOLATION( +4694 "received PATHS_BLOCKED frame when not multipath was not negotiated", +4695 )); +4696 } +4697 } +4698 Frame::PathCidsBlocked(frame::PathCidsBlocked { path_id, next_seq }) => { +4699 // Nothing to do. This is recorded in the frame stats, but otherwise we +4700 // always issue all CIDs we're allowed to issue, so either this is an +4701 // impatient peer or a bug on our side. +4702 +4703 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST +4704 // be treated as a connection error of type PROTOCOL_VIOLATION. +4705 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> +4706 if self.is_multipath_negotiated() { +4707 if path_id > self.local_max_path_id { +4708 return Err(TransportError::PROTOCOL_VIOLATION( +4709 "PATH_CIDS_BLOCKED path identifier was larger than local maximum", +4710 )); +4711 } +4712 if next_seq.0 +4713 > self +4714 .local_cid_state +4715 .get(&path_id) +4716 .map(|cid_state| cid_state.active_seq().1 + 1) +4717 .unwrap_or_default() +4718 { +4719 return Err(TransportError::PROTOCOL_VIOLATION( +4720 "PATH_CIDS_BLOCKED next sequence number larger than in local state", +4721 )); +4722 } +4723 debug!(%path_id, %next_seq, "received PATH_CIDS_BLOCKED"); +4724 } else { +4725 return Err(TransportError::PROTOCOL_VIOLATION( +4726 "received PATH_CIDS_BLOCKED frame when not multipath was not negotiated", +4727 )); +4728 } +4729 } +4730 Frame::AddAddress(addr) => { +4731 let client_state = match self.iroh_hp.client_side_mut() { +4732 Ok(state) => state, +4733 Err(err) => { +4734 return Err(TransportError::PROTOCOL_VIOLATION(format!( +4735 "Nat traversal(ADD_ADDRESS): {err}" +4736 ))); +4737 } +4738 }; +4739 +4740 if !client_state.check_remote_address(&addr) { +4741 // if the address is not valid we flag it, but update anyway +4742 warn!(?addr, "server sent illegal ADD_ADDRESS frame"); +4743 } +4744 +4745 match client_state.add_remote_address(addr) { +4746 Ok(maybe_added) => { +4747 if let Some(added) = maybe_added { +4748 self.events.push_back(Event::NatTraversal( +4749 iroh_hp::Event::AddressAdded(added), +4750 )); +4751 } +4752 } +4753 Err(e) => { +4754 warn!(%e, "failed to add remote address") +4755 } 4756 } 4757 } -4758 Frame::AddAddress(addr) => { +4758 Frame::RemoveAddress(addr) => { 4759 let client_state = match self.iroh_hp.client_side_mut() { 4760 Ok(state) => state, 4761 Err(err) => { 4762 return Err(TransportError::PROTOCOL_VIOLATION(format!( -4763 "Nat traversal(ADD_ADDRESS): {err}" +4763 "Nat traversal(REMOVE_ADDRESS): {err}" 4764 ))); 4765 } 4766 }; -4767 -4768 if !client_state.check_remote_address(&addr) { -4769 // if the address is not valid we flag it, but update anyway -4770 warn!(?addr, "server sent illegal ADD_ADDRESS frame"); -4771 } -4772 -4773 match client_state.add_remote_address(addr) { -4774 Ok(maybe_added) => { -4775 if let Some(added) = maybe_added { -4776 self.events.push_back(Event::NatTraversal( -4777 iroh_hp::Event::AddressAdded(added), -4778 )); -4779 } -4780 } -4781 Err(e) => { -4782 warn!(%e, "failed to add remote address") -4783 } -4784 } -4785 } -4786 Frame::RemoveAddress(addr) => { -4787 let client_state = match self.iroh_hp.client_side_mut() { -4788 Ok(state) => state, -4789 Err(err) => { -4790 return Err(TransportError::PROTOCOL_VIOLATION(format!( -4791 "Nat traversal(REMOVE_ADDRESS): {err}" -4792 ))); -4793 } -4794 }; -4795 if let Some(removed_addr) = client_state.remove_remote_address(addr) { -4796 self.events -4797 .push_back(Event::NatTraversal(iroh_hp::Event::AddressRemoved( -4798 removed_addr, -4799 ))); -4800 } -4801 } -4802 Frame::ReachOut(reach_out) => { -4803 let server_state = match self.iroh_hp.server_side_mut() { -4804 Ok(state) => state, -4805 Err(err) => { -4806 return Err(TransportError::PROTOCOL_VIOLATION(format!( -4807 "Nat traversal(REACH_OUT): {err}" -4808 ))); -4809 } -4810 }; -4811 -4812 if let Err(err) = server_state.handle_reach_out(reach_out) { -4813 return Err(TransportError::PROTOCOL_VIOLATION(format!( -4814 "Nat traversal(REACH_OUT): {err}" -4815 ))); -4816 } -4817 } -4818 } -4819 } -4820 -4821 let space = self.spaces[SpaceId::Data].for_path(path_id); -4822 if space -4823 .pending_acks -4824 .packet_received(now, number, ack_eliciting, &space.dedup) -4825 { -4826 if self.abandoned_paths.contains(&path_id) { -4827 // § 3.4.3 QUIC-MULTIPATH: promptly send ACKs for packets received from -4828 // abandoned paths. -4829 space.pending_acks.set_immediate_ack_required(); -4830 } else { -4831 self.timers.set( -4832 Timer::PerPath(path_id, PathTimer::MaxAckDelay), -4833 now + self.ack_frequency.max_ack_delay, -4834 self.qlog.with_time(now), -4835 ); -4836 } -4837 } -4838 -4839 // Issue stream ID credit due to ACKs of outgoing finish/resets and incoming finish/resets -4840 // on stopped streams. Incoming finishes/resets on open streams are not handled here as they -4841 // are only freed, and hence only issue credit, once the application has been notified -4842 // during a read on the stream. -4843 let pending = &mut self.spaces[SpaceId::Data].pending; -4844 self.streams.queue_max_stream_id(pending); -4845 -4846 if let Some(reason) = close { -4847 self.state.move_to_draining(Some(reason.into())); -4848 self.close = true; -4849 } -4850 -4851 if Some(number) == self.spaces[SpaceId::Data].for_path(path_id).rx_packet -4852 && !is_probing_packet -4853 && remote != self.path_data(path_id).remote -4854 { -4855 let ConnectionSide::Server { ref server_config } = self.side else { -4856 panic!("packets from unknown remote should be dropped by clients"); -4857 }; -4858 debug_assert!( -4859 server_config.migration, -4860 "migration-initiating packets should have been dropped immediately" -4861 ); -4862 self.migrate(path_id, now, remote, migration_observed_addr); -4863 // Break linkability, if possible -4864 self.update_rem_cid(path_id); -4865 self.spin = false; -4866 } -4867 -4868 Ok(()) -4869 } -4870 -4871 fn migrate( -4872 &mut self, -4873 path_id: PathId, -4874 now: Instant, -4875 remote: SocketAddr, -4876 observed_addr: Option<ObservedAddr>, -4877 ) { -4878 trace!(%remote, %path_id, "migration initiated"); -4879 self.path_counter = self.path_counter.wrapping_add(1); -4880 // TODO(@divma): conditions for path migration in multipath are very specific, check them -4881 // again to prevent path migrations that should actually create a new path -4882 -4883 // Reset rtt/congestion state for new path unless it looks like a NAT rebinding. -4884 // Note that the congestion window will not grow until validation terminates. Helps mitigate -4885 // amplification attacks performed by spoofing source addresses. -4886 let prev_pto = self.pto(SpaceId::Data, path_id); -4887 let known_path = self.paths.get_mut(&path_id).expect("known path"); -4888 let path = &mut known_path.data; -4889 let mut new_path = if remote.is_ipv4() && remote.ip() == path.remote.ip() { -4890 PathData::from_previous(remote, path, self.path_counter, now) -4891 } else { -4892 let peer_max_udp_payload_size = -4893 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()) -4894 .unwrap_or(u16::MAX); -4895 PathData::new( -4896 remote, -4897 self.allow_mtud, -4898 Some(peer_max_udp_payload_size), -4899 self.path_counter, -4900 now, -4901 &self.config, -4902 ) -4903 }; -4904 new_path.last_observed_addr_report = path.last_observed_addr_report.clone(); -4905 if let Some(report) = observed_addr { -4906 if let Some(updated) = new_path.update_observed_addr_report(report) { -4907 tracing::info!("adding observed addr event from migration"); -4908 self.events.push_back(Event::Path(PathEvent::ObservedAddr { -4909 id: path_id, -4910 addr: updated, -4911 })); -4912 } -4913 } -4914 new_path.send_new_challenge = true; -4915 -4916 let mut prev = mem::replace(path, new_path); -4917 // Don't clobber the original path if the previous one hasn't been validated yet -4918 if !prev.is_validating_path() { -4919 prev.send_new_challenge = true; -4920 // We haven't updated the remote CID yet, this captures the remote CID we were using on -4921 // the previous path. +4767 if let Some(removed_addr) = client_state.remove_remote_address(addr) { +4768 self.events +4769 .push_back(Event::NatTraversal(iroh_hp::Event::AddressRemoved( +4770 removed_addr, +4771 ))); +4772 } +4773 } +4774 Frame::ReachOut(reach_out) => { +4775 let server_state = match self.iroh_hp.server_side_mut() { +4776 Ok(state) => state, +4777 Err(err) => { +4778 return Err(TransportError::PROTOCOL_VIOLATION(format!( +4779 "Nat traversal(REACH_OUT): {err}" +4780 ))); +4781 } +4782 }; +4783 +4784 if let Err(err) = server_state.handle_reach_out(reach_out) { +4785 return Err(TransportError::PROTOCOL_VIOLATION(format!( +4786 "Nat traversal(REACH_OUT): {err}" +4787 ))); +4788 } +4789 } +4790 } +4791 } +4792 +4793 let space = self.spaces[SpaceId::Data].for_path(path_id); +4794 if space +4795 .pending_acks +4796 .packet_received(now, number, ack_eliciting, &space.dedup) +4797 { +4798 if self.abandoned_paths.contains(&path_id) { +4799 // § 3.4.3 QUIC-MULTIPATH: promptly send ACKs for packets received from +4800 // abandoned paths. +4801 space.pending_acks.set_immediate_ack_required(); +4802 } else { +4803 self.timers.set( +4804 Timer::PerPath(path_id, PathTimer::MaxAckDelay), +4805 now + self.ack_frequency.max_ack_delay, +4806 self.qlog.with_time(now), +4807 ); +4808 } +4809 } +4810 +4811 // Issue stream ID credit due to ACKs of outgoing finish/resets and incoming finish/resets +4812 // on stopped streams. Incoming finishes/resets on open streams are not handled here as they +4813 // are only freed, and hence only issue credit, once the application has been notified +4814 // during a read on the stream. +4815 let pending = &mut self.spaces[SpaceId::Data].pending; +4816 self.streams.queue_max_stream_id(pending); +4817 +4818 if let Some(reason) = close { +4819 self.state.move_to_draining(Some(reason.into())); +4820 self.close = true; +4821 } +4822 +4823 if Some(number) == self.spaces[SpaceId::Data].for_path(path_id).rx_packet +4824 && !is_probing_packet +4825 && remote != self.path_data(path_id).remote +4826 { +4827 let ConnectionSide::Server { ref server_config } = self.side else { +4828 panic!("packets from unknown remote should be dropped by clients"); +4829 }; +4830 debug_assert!( +4831 server_config.migration, +4832 "migration-initiating packets should have been dropped immediately" +4833 ); +4834 self.migrate(path_id, now, remote, migration_observed_addr); +4835 // Break linkability, if possible +4836 self.update_rem_cid(path_id); +4837 self.spin = false; +4838 } +4839 +4840 Ok(()) +4841 } +4842 +4843 fn migrate( +4844 &mut self, +4845 path_id: PathId, +4846 now: Instant, +4847 remote: SocketAddr, +4848 observed_addr: Option<ObservedAddr>, +4849 ) { +4850 trace!(%remote, %path_id, "migration initiated"); +4851 self.path_counter = self.path_counter.wrapping_add(1); +4852 // TODO(@divma): conditions for path migration in multipath are very specific, check them +4853 // again to prevent path migrations that should actually create a new path +4854 +4855 // Reset rtt/congestion state for new path unless it looks like a NAT rebinding. +4856 // Note that the congestion window will not grow until validation terminates. Helps mitigate +4857 // amplification attacks performed by spoofing source addresses. +4858 let prev_pto = self.pto(SpaceId::Data, path_id); +4859 let known_path = self.paths.get_mut(&path_id).expect("known path"); +4860 let path = &mut known_path.data; +4861 let mut new_path = if remote.is_ipv4() && remote.ip() == path.remote.ip() { +4862 PathData::from_previous(remote, path, self.path_counter, now) +4863 } else { +4864 let peer_max_udp_payload_size = +4865 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()) +4866 .unwrap_or(u16::MAX); +4867 PathData::new( +4868 remote, +4869 self.allow_mtud, +4870 Some(peer_max_udp_payload_size), +4871 self.path_counter, +4872 now, +4873 &self.config, +4874 ) +4875 }; +4876 new_path.last_observed_addr_report = path.last_observed_addr_report.clone(); +4877 if let Some(report) = observed_addr { +4878 if let Some(updated) = new_path.update_observed_addr_report(report) { +4879 tracing::info!("adding observed addr event from migration"); +4880 self.events.push_back(Event::Path(PathEvent::ObservedAddr { +4881 id: path_id, +4882 addr: updated, +4883 })); +4884 } +4885 } +4886 new_path.send_new_challenge = true; +4887 +4888 let mut prev = mem::replace(path, new_path); +4889 // Don't clobber the original path if the previous one hasn't been validated yet +4890 if !prev.is_validating_path() { +4891 prev.send_new_challenge = true; +4892 // We haven't updated the remote CID yet, this captures the remote CID we were using on +4893 // the previous path. +4894 +4895 known_path.prev = Some((self.rem_cids.get(&path_id).unwrap().active(), prev)); +4896 } +4897 +4898 // We need to re-assign the correct remote to this path in qlog +4899 self.qlog.emit_tuple_assigned(path_id, remote, now); +4900 +4901 self.timers.set( +4902 Timer::PerPath(path_id, PathTimer::PathValidation), +4903 now + 3 * cmp::max(self.pto(SpaceId::Data, path_id), prev_pto), +4904 self.qlog.with_time(now), +4905 ); +4906 } +4907 +4908 /// Handle a change in the local address, i.e. an active migration +4909 pub fn local_address_changed(&mut self) { +4910 // TODO(flub): if multipath is enabled this needs to create a new path entirely. +4911 self.update_rem_cid(PathId::ZERO); +4912 self.ping(); +4913 } +4914 +4915 /// Switch to a previously unused remote connection ID, if possible +4916 fn update_rem_cid(&mut self, path_id: PathId) { +4917 let Some((reset_token, retired)) = +4918 self.rem_cids.get_mut(&path_id).and_then(|cids| cids.next()) +4919 else { +4920 return; +4921 }; 4922 -4923 known_path.prev = Some((self.rem_cids.get(&path_id).unwrap().active(), prev)); -4924 } -4925 -4926 // We need to re-assign the correct remote to this path in qlog -4927 self.qlog.emit_tuple_assigned(path_id, remote, now); -4928 -4929 self.timers.set( -4930 Timer::PerPath(path_id, PathTimer::PathValidation), -4931 now + 3 * cmp::max(self.pto(SpaceId::Data, path_id), prev_pto), -4932 self.qlog.with_time(now), -4933 ); -4934 } -4935 -4936 /// Handle a change in the local address, i.e. an active migration -4937 pub fn local_address_changed(&mut self) { -4938 // TODO(flub): if multipath is enabled this needs to create a new path entirely. -4939 self.update_rem_cid(PathId::ZERO); -4940 self.ping(); -4941 } -4942 -4943 /// Switch to a previously unused remote connection ID, if possible -4944 fn update_rem_cid(&mut self, path_id: PathId) { -4945 let Some((reset_token, retired)) = -4946 self.rem_cids.get_mut(&path_id).and_then(|cids| cids.next()) -4947 else { -4948 return; -4949 }; -4950 -4951 // Retire the current remote CID and any CIDs we had to skip. -4952 self.spaces[SpaceId::Data] -4953 .pending -4954 .retire_cids -4955 .extend(retired.map(|seq| (path_id, seq))); -4956 let remote = self.path_data(path_id).remote; -4957 self.set_reset_token(path_id, remote, reset_token); -4958 } -4959 -4960 /// Sends this reset token to the endpoint -4961 /// -4962 /// The endpoint needs to know the reset tokens issued by the peer, so that if the peer -4963 /// sends a reset token it knows to route it to this connection. See RFC 9000 section -4964 /// 10.3. Stateless Reset. -4965 /// -4966 /// Reset tokens are different for each path, the endpoint identifies paths by peer -4967 /// socket address however, not by path ID. -4968 fn set_reset_token(&mut self, path_id: PathId, remote: SocketAddr, reset_token: ResetToken) { -4969 self.endpoint_events -4970 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); -4971 -4972 // During the handshake the server sends a reset token in the transport -4973 // parameters. When we are the client and we receive the reset token during the -4974 // handshake we want this to affect our peer transport parameters. -4975 // TODO(flub): Pretty sure this is pointless, the entire params is overwritten -4976 // shortly after this was called. And then the params don't have this anymore. -4977 if path_id == PathId::ZERO { -4978 self.peer_params.stateless_reset_token = Some(reset_token); -4979 } -4980 } -4981 -4982 /// Issue an initial set of connection IDs to the peer upon connection -4983 fn issue_first_cids(&mut self, now: Instant) { -4984 if self -4985 .local_cid_state -4986 .get(&PathId::ZERO) -4987 .expect("PathId::ZERO exists when the connection is created") -4988 .cid_len() -4989 == 0 -4990 { -4991 return; -4992 } -4993 -4994 // Subtract 1 to account for the CID we supplied while handshaking -4995 let mut n = self.peer_params.issue_cids_limit() - 1; -4996 if let ConnectionSide::Server { server_config } = &self.side { -4997 if server_config.has_preferred_address() { -4998 // We also sent a CID in the transport parameters -4999 n -= 1; -5000 } -5001 } -5002 self.endpoint_events -5003 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); -5004 } -5005 -5006 /// Issues an initial set of CIDs for paths that have not yet had any CIDs issued -5007 /// -5008 /// Later CIDs are issued when CIDs expire or are retired by the peer. -5009 fn issue_first_path_cids(&mut self, now: Instant) { -5010 if let Some(max_path_id) = self.max_path_id() { -5011 let mut path_id = self.max_path_id_with_cids.next(); -5012 while path_id <= max_path_id { -5013 self.endpoint_events -5014 .push_back(EndpointEventInner::NeedIdentifiers( -5015 path_id, -5016 now, -5017 self.peer_params.issue_cids_limit(), -5018 )); -5019 path_id = path_id.next(); -5020 } -5021 self.max_path_id_with_cids = max_path_id; -5022 } -5023 } -5024 -5025 /// Populates a packet with frames -5026 /// -5027 /// This tries to fit as many frames as possible into the packet. -5028 /// -5029 /// *path_exclusive_only* means to only build frames which can only be sent on this -5030 /// *path. This is used in multipath for backup paths while there is still an active -5031 /// *path. -5032 fn populate_packet( -5033 &mut self, -5034 now: Instant, -5035 space_id: SpaceId, -5036 path_id: PathId, -5037 path_exclusive_only: bool, -5038 buf: &mut impl BufMut, -5039 pn: u64, -5040 #[allow(unused)] qlog: &mut QlogSentPacket, -5041 ) -> SentFrames { -5042 let mut sent = SentFrames::default(); -5043 let is_multipath_negotiated = self.is_multipath_negotiated(); -5044 let space = &mut self.spaces[space_id]; -5045 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; -5046 let is_0rtt = space_id == SpaceId::Data && space.crypto.is_none(); -5047 space -5048 .for_path(path_id) -5049 .pending_acks -5050 .maybe_ack_non_eliciting(); -5051 -5052 // HANDSHAKE_DONE -5053 if !is_0rtt && mem::replace(&mut space.pending.handshake_done, false) { -5054 trace!("HANDSHAKE_DONE"); -5055 buf.write(frame::FrameType::HANDSHAKE_DONE); -5056 qlog.frame(&Frame::HandshakeDone); -5057 sent.retransmits.get_or_create().handshake_done = true; -5058 // This is just a u8 counter and the frame is typically just sent once -5059 self.stats.frame_tx.handshake_done = -5060 self.stats.frame_tx.handshake_done.saturating_add(1); -5061 } -5062 -5063 // REACH_OUT -5064 // TODO(@divma): path explusive considerations -5065 if let Some((round, addresses)) = space.pending.reach_out.as_mut() { -5066 while let Some(local_addr) = addresses.pop() { -5067 let reach_out = frame::ReachOut::new(*round, local_addr); -5068 if buf.remaining_mut() > reach_out.size() { -5069 trace!(%round, ?local_addr, "REACH_OUT"); -5070 reach_out.write(buf); -5071 let sent_reachouts = sent -5072 .retransmits -5073 .get_or_create() -5074 .reach_out -5075 .get_or_insert_with(|| (*round, Default::default())); -5076 sent_reachouts.1.push(local_addr); -5077 self.stats.frame_tx.reach_out = self.stats.frame_tx.reach_out.saturating_add(1); -5078 qlog.frame(&Frame::ReachOut(reach_out)); -5079 } else { -5080 addresses.push(local_addr); -5081 break; -5082 } -5083 } -5084 if addresses.is_empty() { -5085 space.pending.reach_out = None; -5086 } -5087 } -5088 -5089 // OBSERVED_ADDR -5090 if !path_exclusive_only -5091 && space_id == SpaceId::Data -5092 && self -5093 .config -5094 .address_discovery_role -5095 .should_report(&self.peer_params.address_discovery_role) -5096 && (!path.observed_addr_sent || space.pending.observed_addr) -5097 { -5098 let frame = frame::ObservedAddr::new(path.remote, self.next_observed_addr_seq_no); -5099 if buf.remaining_mut() > frame.size() { -5100 trace!(seq = %frame.seq_no, ip = %frame.ip, port = frame.port, "OBSERVED_ADDRESS"); -5101 frame.write(buf); -5102 -5103 self.next_observed_addr_seq_no = self.next_observed_addr_seq_no.saturating_add(1u8); -5104 path.observed_addr_sent = true; -5105 -5106 self.stats.frame_tx.observed_addr += 1; -5107 sent.retransmits.get_or_create().observed_addr = true; -5108 space.pending.observed_addr = false; -5109 qlog.frame(&Frame::ObservedAddr(frame)); -5110 } -5111 } -5112 -5113 // PING -5114 if mem::replace(&mut space.for_path(path_id).ping_pending, false) { -5115 trace!("PING"); -5116 buf.write(frame::FrameType::PING); -5117 sent.non_retransmits = true; -5118 self.stats.frame_tx.ping += 1; -5119 qlog.frame(&Frame::Ping); -5120 } -5121 -5122 // IMMEDIATE_ACK -5123 if mem::replace(&mut space.for_path(path_id).immediate_ack_pending, false) { -5124 debug_assert_eq!( -5125 space_id, -5126 SpaceId::Data, -5127 "immediate acks must be sent in the data space" -5128 ); -5129 trace!("IMMEDIATE_ACK"); -5130 buf.write(frame::FrameType::IMMEDIATE_ACK); -5131 sent.non_retransmits = true; -5132 self.stats.frame_tx.immediate_ack += 1; -5133 qlog.frame(&Frame::ImmediateAck); -5134 } -5135 -5136 // ACK -5137 // TODO(flub): Should this send acks for this path anyway? -5138 -5139 if !path_exclusive_only { -5140 for path_id in space -5141 .number_spaces -5142 .iter_mut() -5143 .filter(|(_, pns)| pns.pending_acks.can_send()) -5144 .map(|(&path_id, _)| path_id) -5145 .collect::<Vec<_>>() -5146 { -5147 Self::populate_acks( -5148 now, -5149 self.receiving_ecn, -5150 &mut sent, -5151 path_id, -5152 space_id, -5153 space, -5154 is_multipath_negotiated, -5155 buf, -5156 &mut self.stats, -5157 qlog, -5158 ); -5159 } -5160 } -5161 -5162 // ACK_FREQUENCY -5163 if !path_exclusive_only && mem::replace(&mut space.pending.ack_frequency, false) { -5164 let sequence_number = self.ack_frequency.next_sequence_number(); +4923 // Retire the current remote CID and any CIDs we had to skip. +4924 self.spaces[SpaceId::Data] +4925 .pending +4926 .retire_cids +4927 .extend(retired.map(|seq| (path_id, seq))); +4928 let remote = self.path_data(path_id).remote; +4929 self.set_reset_token(path_id, remote, reset_token); +4930 } +4931 +4932 /// Sends this reset token to the endpoint +4933 /// +4934 /// The endpoint needs to know the reset tokens issued by the peer, so that if the peer +4935 /// sends a reset token it knows to route it to this connection. See RFC 9000 section +4936 /// 10.3. Stateless Reset. +4937 /// +4938 /// Reset tokens are different for each path, the endpoint identifies paths by peer +4939 /// socket address however, not by path ID. +4940 fn set_reset_token(&mut self, path_id: PathId, remote: SocketAddr, reset_token: ResetToken) { +4941 self.endpoint_events +4942 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); +4943 +4944 // During the handshake the server sends a reset token in the transport +4945 // parameters. When we are the client and we receive the reset token during the +4946 // handshake we want this to affect our peer transport parameters. +4947 // TODO(flub): Pretty sure this is pointless, the entire params is overwritten +4948 // shortly after this was called. And then the params don't have this anymore. +4949 if path_id == PathId::ZERO { +4950 self.peer_params.stateless_reset_token = Some(reset_token); +4951 } +4952 } +4953 +4954 /// Issue an initial set of connection IDs to the peer upon connection +4955 fn issue_first_cids(&mut self, now: Instant) { +4956 if self +4957 .local_cid_state +4958 .get(&PathId::ZERO) +4959 .expect("PathId::ZERO exists when the connection is created") +4960 .cid_len() +4961 == 0 +4962 { +4963 return; +4964 } +4965 +4966 // Subtract 1 to account for the CID we supplied while handshaking +4967 let mut n = self.peer_params.issue_cids_limit() - 1; +4968 if let ConnectionSide::Server { server_config } = &self.side { +4969 if server_config.has_preferred_address() { +4970 // We also sent a CID in the transport parameters +4971 n -= 1; +4972 } +4973 } +4974 self.endpoint_events +4975 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); +4976 } +4977 +4978 /// Issues an initial set of CIDs for paths that have not yet had any CIDs issued +4979 /// +4980 /// Later CIDs are issued when CIDs expire or are retired by the peer. +4981 fn issue_first_path_cids(&mut self, now: Instant) { +4982 if let Some(max_path_id) = self.max_path_id() { +4983 let mut path_id = self.max_path_id_with_cids.next(); +4984 while path_id <= max_path_id { +4985 self.endpoint_events +4986 .push_back(EndpointEventInner::NeedIdentifiers( +4987 path_id, +4988 now, +4989 self.peer_params.issue_cids_limit(), +4990 )); +4991 path_id = path_id.next(); +4992 } +4993 self.max_path_id_with_cids = max_path_id; +4994 } +4995 } +4996 +4997 /// Populates a packet with frames +4998 /// +4999 /// This tries to fit as many frames as possible into the packet. +5000 /// +5001 /// *path_exclusive_only* means to only build frames which can only be sent on this +5002 /// *path. This is used in multipath for backup paths while there is still an active +5003 /// *path. +5004 fn populate_packet( +5005 &mut self, +5006 now: Instant, +5007 space_id: SpaceId, +5008 path_id: PathId, +5009 path_exclusive_only: bool, +5010 buf: &mut impl BufMut, +5011 pn: u64, +5012 #[allow(unused)] qlog: &mut QlogSentPacket, +5013 ) -> SentFrames { +5014 let mut sent = SentFrames::default(); +5015 let is_multipath_negotiated = self.is_multipath_negotiated(); +5016 let space = &mut self.spaces[space_id]; +5017 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; +5018 let is_0rtt = space_id == SpaceId::Data && space.crypto.is_none(); +5019 space +5020 .for_path(path_id) +5021 .pending_acks +5022 .maybe_ack_non_eliciting(); +5023 +5024 // HANDSHAKE_DONE +5025 if !is_0rtt && mem::replace(&mut space.pending.handshake_done, false) { +5026 trace!("HANDSHAKE_DONE"); +5027 buf.write(frame::FrameType::HANDSHAKE_DONE); +5028 qlog.frame(&Frame::HandshakeDone); +5029 sent.retransmits.get_or_create().handshake_done = true; +5030 // This is just a u8 counter and the frame is typically just sent once +5031 self.stats.frame_tx.handshake_done = +5032 self.stats.frame_tx.handshake_done.saturating_add(1); +5033 } +5034 +5035 // REACH_OUT +5036 // TODO(@divma): path explusive considerations +5037 if let Some((round, addresses)) = space.pending.reach_out.as_mut() { +5038 while let Some(local_addr) = addresses.pop() { +5039 let reach_out = frame::ReachOut::new(*round, local_addr); +5040 if buf.remaining_mut() > reach_out.size() { +5041 trace!(%round, ?local_addr, "REACH_OUT"); +5042 reach_out.write(buf); +5043 let sent_reachouts = sent +5044 .retransmits +5045 .get_or_create() +5046 .reach_out +5047 .get_or_insert_with(|| (*round, Default::default())); +5048 sent_reachouts.1.push(local_addr); +5049 self.stats.frame_tx.reach_out = self.stats.frame_tx.reach_out.saturating_add(1); +5050 qlog.frame(&Frame::ReachOut(reach_out)); +5051 } else { +5052 addresses.push(local_addr); +5053 break; +5054 } +5055 } +5056 if addresses.is_empty() { +5057 space.pending.reach_out = None; +5058 } +5059 } +5060 +5061 // OBSERVED_ADDR +5062 if !path_exclusive_only +5063 && space_id == SpaceId::Data +5064 && self +5065 .config +5066 .address_discovery_role +5067 .should_report(&self.peer_params.address_discovery_role) +5068 && (!path.observed_addr_sent || space.pending.observed_addr) +5069 { +5070 let frame = frame::ObservedAddr::new(path.remote, self.next_observed_addr_seq_no); +5071 if buf.remaining_mut() > frame.size() { +5072 trace!(seq = %frame.seq_no, ip = %frame.ip, port = frame.port, "OBSERVED_ADDRESS"); +5073 frame.write(buf); +5074 +5075 self.next_observed_addr_seq_no = self.next_observed_addr_seq_no.saturating_add(1u8); +5076 path.observed_addr_sent = true; +5077 +5078 self.stats.frame_tx.observed_addr += 1; +5079 sent.retransmits.get_or_create().observed_addr = true; +5080 space.pending.observed_addr = false; +5081 qlog.frame(&Frame::ObservedAddr(frame)); +5082 } +5083 } +5084 +5085 // PING +5086 if mem::replace(&mut space.for_path(path_id).ping_pending, false) { +5087 trace!("PING"); +5088 buf.write(frame::FrameType::PING); +5089 sent.non_retransmits = true; +5090 self.stats.frame_tx.ping += 1; +5091 qlog.frame(&Frame::Ping); +5092 } +5093 +5094 // IMMEDIATE_ACK +5095 if mem::replace(&mut space.for_path(path_id).immediate_ack_pending, false) { +5096 debug_assert_eq!( +5097 space_id, +5098 SpaceId::Data, +5099 "immediate acks must be sent in the data space" +5100 ); +5101 trace!("IMMEDIATE_ACK"); +5102 buf.write(frame::FrameType::IMMEDIATE_ACK); +5103 sent.non_retransmits = true; +5104 self.stats.frame_tx.immediate_ack += 1; +5105 qlog.frame(&Frame::ImmediateAck); +5106 } +5107 +5108 // ACK +5109 // TODO(flub): Should this send acks for this path anyway? +5110 +5111 if !path_exclusive_only { +5112 for path_id in space +5113 .number_spaces +5114 .iter_mut() +5115 .filter(|(_, pns)| pns.pending_acks.can_send()) +5116 .map(|(&path_id, _)| path_id) +5117 .collect::<Vec<_>>() +5118 { +5119 Self::populate_acks( +5120 now, +5121 self.receiving_ecn, +5122 &mut sent, +5123 path_id, +5124 space_id, +5125 space, +5126 is_multipath_negotiated, +5127 buf, +5128 &mut self.stats, +5129 qlog, +5130 ); +5131 } +5132 } +5133 +5134 // ACK_FREQUENCY +5135 if !path_exclusive_only && mem::replace(&mut space.pending.ack_frequency, false) { +5136 let sequence_number = self.ack_frequency.next_sequence_number(); +5137 +5138 // Safe to unwrap because this is always provided when ACK frequency is enabled +5139 let config = self.config.ack_frequency_config.as_ref().unwrap(); +5140 +5141 // Ensure the delay is within bounds to avoid a PROTOCOL_VIOLATION error +5142 let max_ack_delay = self.ack_frequency.candidate_max_ack_delay( +5143 path.rtt.get(), +5144 config, +5145 &self.peer_params, +5146 ); +5147 +5148 trace!(?max_ack_delay, "ACK_FREQUENCY"); +5149 +5150 let frame = frame::AckFrequency { +5151 sequence: sequence_number, +5152 ack_eliciting_threshold: config.ack_eliciting_threshold, +5153 request_max_ack_delay: max_ack_delay.as_micros().try_into().unwrap_or(VarInt::MAX), +5154 reordering_threshold: config.reordering_threshold, +5155 }; +5156 frame.encode(buf); +5157 qlog.frame(&Frame::AckFrequency(frame)); +5158 +5159 sent.retransmits.get_or_create().ack_frequency = true; +5160 +5161 self.ack_frequency +5162 .ack_frequency_sent(path_id, pn, max_ack_delay); +5163 self.stats.frame_tx.ack_frequency += 1; +5164 } 5165 -5166 // Safe to unwrap because this is always provided when ACK frequency is enabled -5167 let config = self.config.ack_frequency_config.as_ref().unwrap(); -5168 -5169 // Ensure the delay is within bounds to avoid a PROTOCOL_VIOLATION error -5170 let max_ack_delay = self.ack_frequency.candidate_max_ack_delay( -5171 path.rtt.get(), -5172 config, -5173 &self.peer_params, -5174 ); -5175 -5176 trace!(?max_ack_delay, "ACK_FREQUENCY"); -5177 -5178 let frame = frame::AckFrequency { -5179 sequence: sequence_number, -5180 ack_eliciting_threshold: config.ack_eliciting_threshold, -5181 request_max_ack_delay: max_ack_delay.as_micros().try_into().unwrap_or(VarInt::MAX), -5182 reordering_threshold: config.reordering_threshold, -5183 }; -5184 frame.encode(buf); -5185 qlog.frame(&Frame::AckFrequency(frame)); -5186 -5187 sent.retransmits.get_or_create().ack_frequency = true; -5188 -5189 self.ack_frequency -5190 .ack_frequency_sent(path_id, pn, max_ack_delay); -5191 self.stats.frame_tx.ack_frequency += 1; -5192 } +5166 // PATH_CHALLENGE +5167 if buf.remaining_mut() > frame::PathChallenge::SIZE_BOUND +5168 && space_id == SpaceId::Data +5169 && path.send_new_challenge +5170 { +5171 path.send_new_challenge = false; +5172 +5173 // Generate a new challenge every time we send a new PATH_CHALLENGE +5174 let token = self.rng.random(); +5175 let info = paths::SentChallengeInfo { +5176 sent_instant: now, +5177 remote: path.remote, +5178 }; +5179 path.challenges_sent.insert(token, info); +5180 sent.non_retransmits = true; +5181 sent.requires_padding = true; +5182 let challenge = frame::PathChallenge(token); +5183 trace!(%challenge, "sending new challenge"); +5184 buf.write(challenge); +5185 qlog.frame(&Frame::PathChallenge(challenge)); +5186 self.stats.frame_tx.path_challenge += 1; +5187 let pto = self.ack_frequency.max_ack_delay_for_pto() + path.rtt.pto_base(); +5188 self.timers.set( +5189 Timer::PerPath(path_id, PathTimer::PathChallengeLost), +5190 now + pto, +5191 self.qlog.with_time(now), +5192 ); 5193 -5194 // PATH_CHALLENGE -5195 if buf.remaining_mut() > frame::PathChallenge::SIZE_BOUND -5196 && space_id == SpaceId::Data -5197 && path.send_new_challenge -5198 { -5199 path.send_new_challenge = false; -5200 -5201 // Generate a new challenge every time we send a new PATH_CHALLENGE -5202 let token = self.rng.random(); -5203 let info = paths::SentChallengeInfo { -5204 sent_instant: now, -5205 remote: path.remote, -5206 }; -5207 path.challenges_sent.insert(token, info); -5208 sent.non_retransmits = true; -5209 sent.requires_padding = true; -5210 let challenge = frame::PathChallenge(token); -5211 trace!(%challenge, "sending new challenge"); -5212 buf.write(challenge); -5213 qlog.frame(&Frame::PathChallenge(challenge)); -5214 self.stats.frame_tx.path_challenge += 1; -5215 let pto = self.ack_frequency.max_ack_delay_for_pto() + path.rtt.pto_base(); -5216 self.timers.set( -5217 Timer::PerPath(path_id, PathTimer::PathChallengeLost), -5218 now + pto, -5219 self.qlog.with_time(now), -5220 ); -5221 -5222 if is_multipath_negotiated && !path.validated && path.send_new_challenge { -5223 // queue informing the path status along with the challenge -5224 space.pending.path_status.insert(path_id); -5225 } -5226 -5227 // Always include an OBSERVED_ADDR frame with a PATH_CHALLENGE, regardless -5228 // of whether one has already been sent on this path. -5229 if space_id == SpaceId::Data -5230 && self -5231 .config -5232 .address_discovery_role -5233 .should_report(&self.peer_params.address_discovery_role) -5234 { -5235 let frame = frame::ObservedAddr::new(path.remote, self.next_observed_addr_seq_no); -5236 if buf.remaining_mut() > frame.size() { -5237 frame.write(buf); -5238 qlog.frame(&Frame::ObservedAddr(frame)); -5239 -5240 self.next_observed_addr_seq_no = -5241 self.next_observed_addr_seq_no.saturating_add(1u8); -5242 path.observed_addr_sent = true; -5243 -5244 self.stats.frame_tx.observed_addr += 1; -5245 sent.retransmits.get_or_create().observed_addr = true; -5246 space.pending.observed_addr = false; -5247 } -5248 } -5249 } -5250 -5251 // PATH_RESPONSE -5252 if buf.remaining_mut() > frame::PathResponse::SIZE_BOUND && space_id == SpaceId::Data { -5253 if let Some(token) = path.path_responses.pop_on_path(path.remote) { -5254 sent.non_retransmits = true; -5255 sent.requires_padding = true; -5256 let response = frame::PathResponse(token); -5257 trace!(%response, "sending response"); -5258 buf.write(response); -5259 qlog.frame(&Frame::PathResponse(response)); -5260 self.stats.frame_tx.path_response += 1; -5261 -5262 // NOTE: this is technically not required but might be useful to ride the -5263 // request/response nature of path challenges to refresh an observation -5264 // Since PATH_RESPONSE is a probing frame, this is allowed by the spec. -5265 if space_id == SpaceId::Data -5266 && self -5267 .config -5268 .address_discovery_role -5269 .should_report(&self.peer_params.address_discovery_role) -5270 { -5271 let frame = -5272 frame::ObservedAddr::new(path.remote, self.next_observed_addr_seq_no); -5273 if buf.remaining_mut() > frame.size() { -5274 frame.write(buf); -5275 qlog.frame(&Frame::ObservedAddr(frame)); +5194 if is_multipath_negotiated && !path.validated && path.send_new_challenge { +5195 // queue informing the path status along with the challenge +5196 space.pending.path_status.insert(path_id); +5197 } +5198 +5199 // Always include an OBSERVED_ADDR frame with a PATH_CHALLENGE, regardless +5200 // of whether one has already been sent on this path. +5201 if space_id == SpaceId::Data +5202 && self +5203 .config +5204 .address_discovery_role +5205 .should_report(&self.peer_params.address_discovery_role) +5206 { +5207 let frame = frame::ObservedAddr::new(path.remote, self.next_observed_addr_seq_no); +5208 if buf.remaining_mut() > frame.size() { +5209 frame.write(buf); +5210 qlog.frame(&Frame::ObservedAddr(frame)); +5211 +5212 self.next_observed_addr_seq_no = +5213 self.next_observed_addr_seq_no.saturating_add(1u8); +5214 path.observed_addr_sent = true; +5215 +5216 self.stats.frame_tx.observed_addr += 1; +5217 sent.retransmits.get_or_create().observed_addr = true; +5218 space.pending.observed_addr = false; +5219 } +5220 } +5221 } +5222 +5223 // PATH_RESPONSE +5224 if buf.remaining_mut() > frame::PathResponse::SIZE_BOUND && space_id == SpaceId::Data { +5225 if let Some(token) = path.path_responses.pop_on_path(path.remote) { +5226 sent.non_retransmits = true; +5227 sent.requires_padding = true; +5228 let response = frame::PathResponse(token); +5229 trace!(%response, "sending response"); +5230 buf.write(response); +5231 qlog.frame(&Frame::PathResponse(response)); +5232 self.stats.frame_tx.path_response += 1; +5233 +5234 // NOTE: this is technically not required but might be useful to ride the +5235 // request/response nature of path challenges to refresh an observation +5236 // Since PATH_RESPONSE is a probing frame, this is allowed by the spec. +5237 if space_id == SpaceId::Data +5238 && self +5239 .config +5240 .address_discovery_role +5241 .should_report(&self.peer_params.address_discovery_role) +5242 { +5243 let frame = +5244 frame::ObservedAddr::new(path.remote, self.next_observed_addr_seq_no); +5245 if buf.remaining_mut() > frame.size() { +5246 frame.write(buf); +5247 qlog.frame(&Frame::ObservedAddr(frame)); +5248 +5249 self.next_observed_addr_seq_no = +5250 self.next_observed_addr_seq_no.saturating_add(1u8); +5251 path.observed_addr_sent = true; +5252 +5253 self.stats.frame_tx.observed_addr += 1; +5254 sent.retransmits.get_or_create().observed_addr = true; +5255 space.pending.observed_addr = false; +5256 } +5257 } +5258 } +5259 } +5260 +5261 // CRYPTO +5262 while !path_exclusive_only && buf.remaining_mut() > frame::Crypto::SIZE_BOUND && !is_0rtt { +5263 let mut frame = match space.pending.crypto.pop_front() { +5264 Some(x) => x, +5265 None => break, +5266 }; +5267 +5268 // Calculate the maximum amount of crypto data we can store in the buffer. +5269 // Since the offset is known, we can reserve the exact size required to encode it. +5270 // For length we reserve 2bytes which allows to encode up to 2^14, +5271 // which is more than what fits into normally sized QUIC frames. +5272 let max_crypto_data_size = buf.remaining_mut() +5273 - 1 // Frame Type +5274 - VarInt::size(unsafe { VarInt::from_u64_unchecked(frame.offset) }) +5275 - 2; // Maximum encoded length for frame size, given we send less than 2^14 bytes 5276 -5277 self.next_observed_addr_seq_no = -5278 self.next_observed_addr_seq_no.saturating_add(1u8); -5279 path.observed_addr_sent = true; -5280 -5281 self.stats.frame_tx.observed_addr += 1; -5282 sent.retransmits.get_or_create().observed_addr = true; -5283 space.pending.observed_addr = false; -5284 } -5285 } -5286 } -5287 } -5288 -5289 // CRYPTO -5290 while !path_exclusive_only && buf.remaining_mut() > frame::Crypto::SIZE_BOUND && !is_0rtt { -5291 let mut frame = match space.pending.crypto.pop_front() { -5292 Some(x) => x, -5293 None => break, -5294 }; +5277 let len = frame +5278 .data +5279 .len() +5280 .min(2usize.pow(14) - 1) +5281 .min(max_crypto_data_size); +5282 +5283 let data = frame.data.split_to(len); +5284 let truncated = frame::Crypto { +5285 offset: frame.offset, +5286 data, +5287 }; +5288 trace!( +5289 "CRYPTO: off {} len {}", +5290 truncated.offset, +5291 truncated.data.len() +5292 ); +5293 truncated.encode(buf); +5294 self.stats.frame_tx.crypto += 1; 5295 -5296 // Calculate the maximum amount of crypto data we can store in the buffer. -5297 // Since the offset is known, we can reserve the exact size required to encode it. -5298 // For length we reserve 2bytes which allows to encode up to 2^14, -5299 // which is more than what fits into normally sized QUIC frames. -5300 let max_crypto_data_size = buf.remaining_mut() -5301 - 1 // Frame Type -5302 - VarInt::size(unsafe { VarInt::from_u64_unchecked(frame.offset) }) -5303 - 2; // Maximum encoded length for frame size, given we send less than 2^14 bytes -5304 -5305 let len = frame -5306 .data -5307 .len() -5308 .min(2usize.pow(14) - 1) -5309 .min(max_crypto_data_size); -5310 -5311 let data = frame.data.split_to(len); -5312 let truncated = frame::Crypto { -5313 offset: frame.offset, -5314 data, -5315 }; -5316 trace!( -5317 "CRYPTO: off {} len {}", -5318 truncated.offset, -5319 truncated.data.len() -5320 ); -5321 truncated.encode(buf); -5322 self.stats.frame_tx.crypto += 1; -5323 -5324 // The clone is cheap but we still cfg it out if qlog is disabled. -5325 #[cfg(feature = "qlog")] -5326 qlog.frame(&Frame::Crypto(truncated.clone())); -5327 sent.retransmits.get_or_create().crypto.push_back(truncated); -5328 if !frame.data.is_empty() { -5329 frame.offset += len as u64; -5330 space.pending.crypto.push_front(frame); -5331 } -5332 } -5333 -5334 // TODO(flub): maybe this is much higher priority? -5335 // PATH_ABANDON -5336 while !path_exclusive_only -5337 && space_id == SpaceId::Data -5338 && frame::PathAbandon::SIZE_BOUND <= buf.remaining_mut() -5339 { -5340 let Some((path_id, error_code)) = space.pending.path_abandon.pop_first() else { -5341 break; -5342 }; -5343 let frame = frame::PathAbandon { -5344 path_id, -5345 error_code, -5346 }; -5347 frame.encode(buf); -5348 qlog.frame(&Frame::PathAbandon(frame)); -5349 self.stats.frame_tx.path_abandon += 1; -5350 trace!(%path_id, "PATH_ABANDON"); -5351 sent.retransmits -5352 .get_or_create() -5353 .path_abandon -5354 .entry(path_id) -5355 .or_insert(error_code); -5356 } -5357 -5358 // PATH_STATUS_AVAILABLE & PATH_STATUS_BACKUP -5359 while !path_exclusive_only -5360 && space_id == SpaceId::Data -5361 && frame::PathStatusAvailable::SIZE_BOUND <= buf.remaining_mut() -5362 { -5363 let Some(path_id) = space.pending.path_status.pop_first() else { -5364 break; -5365 }; -5366 let Some(path) = self.paths.get(&path_id).map(|path_state| &path_state.data) else { -5367 trace!(%path_id, "discarding queued path status for unknown path"); -5368 continue; -5369 }; -5370 -5371 let seq = path.status.seq(); -5372 sent.retransmits.get_or_create().path_status.insert(path_id); -5373 match path.local_status() { -5374 PathStatus::Available => { -5375 let frame = frame::PathStatusAvailable { -5376 path_id, -5377 status_seq_no: seq, -5378 }; -5379 frame.encode(buf); -5380 qlog.frame(&Frame::PathStatusAvailable(frame)); -5381 self.stats.frame_tx.path_status_available += 1; -5382 trace!(%path_id, %seq, "PATH_STATUS_AVAILABLE") -5383 } -5384 PathStatus::Backup => { -5385 let frame = frame::PathStatusBackup { -5386 path_id, -5387 status_seq_no: seq, -5388 }; -5389 frame.encode(buf); -5390 qlog.frame(&Frame::PathStatusBackup(frame)); -5391 self.stats.frame_tx.path_status_backup += 1; -5392 trace!(%path_id, %seq, "PATH_STATUS_BACKUP") -5393 } -5394 } +5296 // The clone is cheap but we still cfg it out if qlog is disabled. +5297 #[cfg(feature = "qlog")] +5298 qlog.frame(&Frame::Crypto(truncated.clone())); +5299 sent.retransmits.get_or_create().crypto.push_back(truncated); +5300 if !frame.data.is_empty() { +5301 frame.offset += len as u64; +5302 space.pending.crypto.push_front(frame); +5303 } +5304 } +5305 +5306 // TODO(flub): maybe this is much higher priority? +5307 // PATH_ABANDON +5308 while !path_exclusive_only +5309 && space_id == SpaceId::Data +5310 && frame::PathAbandon::SIZE_BOUND <= buf.remaining_mut() +5311 { +5312 let Some((path_id, error_code)) = space.pending.path_abandon.pop_first() else { +5313 break; +5314 }; +5315 let frame = frame::PathAbandon { +5316 path_id, +5317 error_code, +5318 }; +5319 frame.encode(buf); +5320 qlog.frame(&Frame::PathAbandon(frame)); +5321 self.stats.frame_tx.path_abandon += 1; +5322 trace!(%path_id, "PATH_ABANDON"); +5323 sent.retransmits +5324 .get_or_create() +5325 .path_abandon +5326 .entry(path_id) +5327 .or_insert(error_code); +5328 } +5329 +5330 // PATH_STATUS_AVAILABLE & PATH_STATUS_BACKUP +5331 while !path_exclusive_only +5332 && space_id == SpaceId::Data +5333 && frame::PathStatusAvailable::SIZE_BOUND <= buf.remaining_mut() +5334 { +5335 let Some(path_id) = space.pending.path_status.pop_first() else { +5336 break; +5337 }; +5338 let Some(path) = self.paths.get(&path_id).map(|path_state| &path_state.data) else { +5339 trace!(%path_id, "discarding queued path status for unknown path"); +5340 continue; +5341 }; +5342 +5343 let seq = path.status.seq(); +5344 sent.retransmits.get_or_create().path_status.insert(path_id); +5345 match path.local_status() { +5346 PathStatus::Available => { +5347 let frame = frame::PathStatusAvailable { +5348 path_id, +5349 status_seq_no: seq, +5350 }; +5351 frame.encode(buf); +5352 qlog.frame(&Frame::PathStatusAvailable(frame)); +5353 self.stats.frame_tx.path_status_available += 1; +5354 trace!(%path_id, %seq, "PATH_STATUS_AVAILABLE") +5355 } +5356 PathStatus::Backup => { +5357 let frame = frame::PathStatusBackup { +5358 path_id, +5359 status_seq_no: seq, +5360 }; +5361 frame.encode(buf); +5362 qlog.frame(&Frame::PathStatusBackup(frame)); +5363 self.stats.frame_tx.path_status_backup += 1; +5364 trace!(%path_id, %seq, "PATH_STATUS_BACKUP") +5365 } +5366 } +5367 } +5368 +5369 // MAX_PATH_ID +5370 if space_id == SpaceId::Data +5371 && space.pending.max_path_id +5372 && frame::MaxPathId::SIZE_BOUND <= buf.remaining_mut() +5373 { +5374 let frame = frame::MaxPathId(self.local_max_path_id); +5375 frame.encode(buf); +5376 qlog.frame(&Frame::MaxPathId(frame)); +5377 space.pending.max_path_id = false; +5378 sent.retransmits.get_or_create().max_path_id = true; +5379 trace!(val = %self.local_max_path_id, "MAX_PATH_ID"); +5380 self.stats.frame_tx.max_path_id += 1; +5381 } +5382 +5383 // PATHS_BLOCKED +5384 if space_id == SpaceId::Data +5385 && space.pending.paths_blocked +5386 && frame::PathsBlocked::SIZE_BOUND <= buf.remaining_mut() +5387 { +5388 let frame = frame::PathsBlocked(self.remote_max_path_id); +5389 frame.encode(buf); +5390 qlog.frame(&Frame::PathsBlocked(frame)); +5391 space.pending.paths_blocked = false; +5392 sent.retransmits.get_or_create().paths_blocked = true; +5393 trace!(max_path_id = ?self.remote_max_path_id, "PATHS_BLOCKED"); +5394 self.stats.frame_tx.paths_blocked += 1; 5395 } 5396 -5397 // MAX_PATH_ID -5398 if space_id == SpaceId::Data -5399 && space.pending.max_path_id -5400 && frame::MaxPathId::SIZE_BOUND <= buf.remaining_mut() -5401 { -5402 let frame = frame::MaxPathId(self.local_max_path_id); -5403 frame.encode(buf); -5404 qlog.frame(&Frame::MaxPathId(frame)); -5405 space.pending.max_path_id = false; -5406 sent.retransmits.get_or_create().max_path_id = true; -5407 trace!(val = %self.local_max_path_id, "MAX_PATH_ID"); -5408 self.stats.frame_tx.max_path_id += 1; -5409 } -5410 -5411 // PATHS_BLOCKED -5412 if space_id == SpaceId::Data -5413 && space.pending.paths_blocked -5414 && frame::PathsBlocked::SIZE_BOUND <= buf.remaining_mut() -5415 { -5416 let frame = frame::PathsBlocked(self.remote_max_path_id); -5417 frame.encode(buf); -5418 qlog.frame(&Frame::PathsBlocked(frame)); -5419 space.pending.paths_blocked = false; -5420 sent.retransmits.get_or_create().paths_blocked = true; -5421 trace!(max_path_id = ?self.remote_max_path_id, "PATHS_BLOCKED"); -5422 self.stats.frame_tx.paths_blocked += 1; -5423 } -5424 -5425 // PATH_CIDS_BLOCKED -5426 while space_id == SpaceId::Data && frame::PathCidsBlocked::SIZE_BOUND <= buf.remaining_mut() -5427 { -5428 let Some(path_id) = space.pending.path_cids_blocked.pop() else { -5429 break; -5430 }; -5431 let next_seq = match self.rem_cids.get(&path_id) { -5432 Some(cid_queue) => cid_queue.active_seq() + 1, -5433 None => 0, -5434 }; -5435 let frame = frame::PathCidsBlocked { -5436 path_id, -5437 next_seq: VarInt(next_seq), -5438 }; -5439 frame.encode(buf); -5440 qlog.frame(&Frame::PathCidsBlocked(frame)); -5441 sent.retransmits -5442 .get_or_create() -5443 .path_cids_blocked -5444 .push(path_id); -5445 trace!(%path_id, next_seq, "PATH_CIDS_BLOCKED"); -5446 self.stats.frame_tx.path_cids_blocked += 1; -5447 } -5448 -5449 // RESET_STREAM, STOP_SENDING, MAX_DATA, MAX_STREAM_DATA, MAX_STREAMS -5450 if space_id == SpaceId::Data { -5451 self.streams.write_control_frames( -5452 buf, -5453 &mut space.pending, -5454 &mut sent.retransmits, -5455 &mut self.stats.frame_tx, -5456 qlog, -5457 ); -5458 } -5459 -5460 // NEW_CONNECTION_ID -5461 let cid_len = self -5462 .local_cid_state -5463 .values() -5464 .map(|cid_state| cid_state.cid_len()) -5465 .max() -5466 .expect("some local CID state must exist"); -5467 let new_cid_size_bound = -5468 frame::NewConnectionId::size_bound(is_multipath_negotiated, cid_len); -5469 while !path_exclusive_only && buf.remaining_mut() > new_cid_size_bound { -5470 let issued = match space.pending.new_cids.pop() { -5471 Some(x) => x, -5472 None => break, +5397 // PATH_CIDS_BLOCKED +5398 while space_id == SpaceId::Data && frame::PathCidsBlocked::SIZE_BOUND <= buf.remaining_mut() +5399 { +5400 let Some(path_id) = space.pending.path_cids_blocked.pop() else { +5401 break; +5402 }; +5403 let next_seq = match self.rem_cids.get(&path_id) { +5404 Some(cid_queue) => cid_queue.active_seq() + 1, +5405 None => 0, +5406 }; +5407 let frame = frame::PathCidsBlocked { +5408 path_id, +5409 next_seq: VarInt(next_seq), +5410 }; +5411 frame.encode(buf); +5412 qlog.frame(&Frame::PathCidsBlocked(frame)); +5413 sent.retransmits +5414 .get_or_create() +5415 .path_cids_blocked +5416 .push(path_id); +5417 trace!(%path_id, next_seq, "PATH_CIDS_BLOCKED"); +5418 self.stats.frame_tx.path_cids_blocked += 1; +5419 } +5420 +5421 // RESET_STREAM, STOP_SENDING, MAX_DATA, MAX_STREAM_DATA, MAX_STREAMS +5422 if space_id == SpaceId::Data { +5423 self.streams.write_control_frames( +5424 buf, +5425 &mut space.pending, +5426 &mut sent.retransmits, +5427 &mut self.stats.frame_tx, +5428 qlog, +5429 ); +5430 } +5431 +5432 // NEW_CONNECTION_ID +5433 let cid_len = self +5434 .local_cid_state +5435 .values() +5436 .map(|cid_state| cid_state.cid_len()) +5437 .max() +5438 .expect("some local CID state must exist"); +5439 let new_cid_size_bound = +5440 frame::NewConnectionId::size_bound(is_multipath_negotiated, cid_len); +5441 while !path_exclusive_only && buf.remaining_mut() > new_cid_size_bound { +5442 let issued = match space.pending.new_cids.pop() { +5443 Some(x) => x, +5444 None => break, +5445 }; +5446 let retire_prior_to = self +5447 .local_cid_state +5448 .get(&issued.path_id) +5449 .map(|cid_state| cid_state.retire_prior_to()) +5450 .unwrap_or_else(|| panic!("missing local CID state for path={}", issued.path_id)); +5451 +5452 let cid_path_id = match is_multipath_negotiated { +5453 true => { +5454 trace!( +5455 path_id = ?issued.path_id, +5456 sequence = issued.sequence, +5457 id = %issued.id, +5458 "PATH_NEW_CONNECTION_ID", +5459 ); +5460 self.stats.frame_tx.path_new_connection_id += 1; +5461 Some(issued.path_id) +5462 } +5463 false => { +5464 trace!( +5465 sequence = issued.sequence, +5466 id = %issued.id, +5467 "NEW_CONNECTION_ID" +5468 ); +5469 debug_assert_eq!(issued.path_id, PathId::ZERO); +5470 self.stats.frame_tx.new_connection_id += 1; +5471 None +5472 } 5473 }; -5474 let retire_prior_to = self -5475 .local_cid_state -5476 .get(&issued.path_id) -5477 .map(|cid_state| cid_state.retire_prior_to()) -5478 .unwrap_or_else(|| panic!("missing local CID state for path={}", issued.path_id)); -5479 -5480 let cid_path_id = match is_multipath_negotiated { -5481 true => { -5482 trace!( -5483 path_id = ?issued.path_id, -5484 sequence = issued.sequence, -5485 id = %issued.id, -5486 "PATH_NEW_CONNECTION_ID", -5487 ); -5488 self.stats.frame_tx.path_new_connection_id += 1; -5489 Some(issued.path_id) -5490 } -5491 false => { -5492 trace!( -5493 sequence = issued.sequence, -5494 id = %issued.id, -5495 "NEW_CONNECTION_ID" -5496 ); -5497 debug_assert_eq!(issued.path_id, PathId::ZERO); -5498 self.stats.frame_tx.new_connection_id += 1; -5499 None -5500 } +5474 let frame = frame::NewConnectionId { +5475 path_id: cid_path_id, +5476 sequence: issued.sequence, +5477 retire_prior_to, +5478 id: issued.id, +5479 reset_token: issued.reset_token, +5480 }; +5481 frame.encode(buf); +5482 sent.retransmits.get_or_create().new_cids.push(issued); +5483 qlog.frame(&Frame::NewConnectionId(frame)); +5484 } +5485 +5486 // RETIRE_CONNECTION_ID +5487 let retire_cid_bound = frame::RetireConnectionId::size_bound(is_multipath_negotiated); +5488 while !path_exclusive_only && buf.remaining_mut() > retire_cid_bound { +5489 let (path_id, sequence) = match space.pending.retire_cids.pop() { +5490 Some((PathId::ZERO, seq)) if !is_multipath_negotiated => { +5491 trace!(sequence = seq, "RETIRE_CONNECTION_ID"); +5492 self.stats.frame_tx.retire_connection_id += 1; +5493 (None, seq) +5494 } +5495 Some((path_id, seq)) => { +5496 trace!(%path_id, sequence = seq, "PATH_RETIRE_CONNECTION_ID"); +5497 self.stats.frame_tx.path_retire_connection_id += 1; +5498 (Some(path_id), seq) +5499 } +5500 None => break, 5501 }; -5502 let frame = frame::NewConnectionId { -5503 path_id: cid_path_id, -5504 sequence: issued.sequence, -5505 retire_prior_to, -5506 id: issued.id, -5507 reset_token: issued.reset_token, -5508 }; -5509 frame.encode(buf); -5510 sent.retransmits.get_or_create().new_cids.push(issued); -5511 qlog.frame(&Frame::NewConnectionId(frame)); -5512 } -5513 -5514 // RETIRE_CONNECTION_ID -5515 let retire_cid_bound = frame::RetireConnectionId::size_bound(is_multipath_negotiated); -5516 while !path_exclusive_only && buf.remaining_mut() > retire_cid_bound { -5517 let (path_id, sequence) = match space.pending.retire_cids.pop() { -5518 Some((PathId::ZERO, seq)) if !is_multipath_negotiated => { -5519 trace!(sequence = seq, "RETIRE_CONNECTION_ID"); -5520 self.stats.frame_tx.retire_connection_id += 1; -5521 (None, seq) -5522 } -5523 Some((path_id, seq)) => { -5524 trace!(%path_id, sequence = seq, "PATH_RETIRE_CONNECTION_ID"); -5525 self.stats.frame_tx.path_retire_connection_id += 1; -5526 (Some(path_id), seq) -5527 } -5528 None => break, -5529 }; -5530 let frame = frame::RetireConnectionId { path_id, sequence }; -5531 frame.encode(buf); -5532 qlog.frame(&Frame::RetireConnectionId(frame)); -5533 sent.retransmits -5534 .get_or_create() -5535 .retire_cids -5536 .push((path_id.unwrap_or_default(), sequence)); -5537 } -5538 -5539 // DATAGRAM -5540 let mut sent_datagrams = false; -5541 while !path_exclusive_only -5542 && buf.remaining_mut() > Datagram::SIZE_BOUND -5543 && space_id == SpaceId::Data -5544 { -5545 let prev_remaining = buf.remaining_mut(); -5546 match self.datagrams.write(buf) { -5547 true => { -5548 sent_datagrams = true; -5549 sent.non_retransmits = true; -5550 self.stats.frame_tx.datagram += 1; -5551 qlog.frame_datagram((prev_remaining - buf.remaining_mut()) as u64); -5552 } -5553 false => break, -5554 } -5555 } -5556 if self.datagrams.send_blocked && sent_datagrams { -5557 self.events.push_back(Event::DatagramsUnblocked); -5558 self.datagrams.send_blocked = false; -5559 } -5560 -5561 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; -5562 -5563 // NEW_TOKEN -5564 while let Some(remote_addr) = space.pending.new_tokens.pop() { -5565 if path_exclusive_only { +5502 let frame = frame::RetireConnectionId { path_id, sequence }; +5503 frame.encode(buf); +5504 qlog.frame(&Frame::RetireConnectionId(frame)); +5505 sent.retransmits +5506 .get_or_create() +5507 .retire_cids +5508 .push((path_id.unwrap_or_default(), sequence)); +5509 } +5510 +5511 // DATAGRAM +5512 let mut sent_datagrams = false; +5513 while !path_exclusive_only +5514 && buf.remaining_mut() > Datagram::SIZE_BOUND +5515 && space_id == SpaceId::Data +5516 { +5517 let prev_remaining = buf.remaining_mut(); +5518 match self.datagrams.write(buf) { +5519 true => { +5520 sent_datagrams = true; +5521 sent.non_retransmits = true; +5522 self.stats.frame_tx.datagram += 1; +5523 qlog.frame_datagram((prev_remaining - buf.remaining_mut()) as u64); +5524 } +5525 false => break, +5526 } +5527 } +5528 if self.datagrams.send_blocked && sent_datagrams { +5529 self.events.push_back(Event::DatagramsUnblocked); +5530 self.datagrams.send_blocked = false; +5531 } +5532 +5533 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; +5534 +5535 // NEW_TOKEN +5536 while let Some(remote_addr) = space.pending.new_tokens.pop() { +5537 if path_exclusive_only { +5538 break; +5539 } +5540 debug_assert_eq!(space_id, SpaceId::Data); +5541 let ConnectionSide::Server { server_config } = &self.side else { +5542 panic!("NEW_TOKEN frames should not be enqueued by clients"); +5543 }; +5544 +5545 if remote_addr != path.remote { +5546 // NEW_TOKEN frames contain tokens bound to a client's IP address, and are only +5547 // useful if used from the same IP address. Thus, we abandon enqueued NEW_TOKEN +5548 // frames upon an path change. Instead, when the new path becomes validated, +5549 // NEW_TOKEN frames may be enqueued for the new path instead. +5550 continue; +5551 } +5552 +5553 let token = Token::new( +5554 TokenPayload::Validation { +5555 ip: remote_addr.ip(), +5556 issued: server_config.time_source.now(), +5557 }, +5558 &mut self.rng, +5559 ); +5560 let new_token = NewToken { +5561 token: token.encode(&*server_config.token_key).into(), +5562 }; +5563 +5564 if buf.remaining_mut() < new_token.size() { +5565 space.pending.new_tokens.push(remote_addr); 5566 break; 5567 } -5568 debug_assert_eq!(space_id, SpaceId::Data); -5569 let ConnectionSide::Server { server_config } = &self.side else { -5570 panic!("NEW_TOKEN frames should not be enqueued by clients"); -5571 }; -5572 -5573 if remote_addr != path.remote { -5574 // NEW_TOKEN frames contain tokens bound to a client's IP address, and are only -5575 // useful if used from the same IP address. Thus, we abandon enqueued NEW_TOKEN -5576 // frames upon an path change. Instead, when the new path becomes validated, -5577 // NEW_TOKEN frames may be enqueued for the new path instead. -5578 continue; -5579 } -5580 -5581 let token = Token::new( -5582 TokenPayload::Validation { -5583 ip: remote_addr.ip(), -5584 issued: server_config.time_source.now(), -5585 }, -5586 &mut self.rng, -5587 ); -5588 let new_token = NewToken { -5589 token: token.encode(&*server_config.token_key).into(), -5590 }; -5591 -5592 if buf.remaining_mut() < new_token.size() { -5593 space.pending.new_tokens.push(remote_addr); -5594 break; -5595 } -5596 -5597 trace!("NEW_TOKEN"); -5598 new_token.encode(buf); -5599 qlog.frame(&Frame::NewToken(new_token)); -5600 sent.retransmits -5601 .get_or_create() -5602 .new_tokens -5603 .push(remote_addr); -5604 self.stats.frame_tx.new_token += 1; -5605 } -5606 -5607 // STREAM -5608 if !path_exclusive_only && space_id == SpaceId::Data { -5609 sent.stream_frames = -5610 self.streams -5611 .write_stream_frames(buf, self.config.send_fairness, qlog); -5612 self.stats.frame_tx.stream += sent.stream_frames.len() as u64; -5613 } -5614 -5615 // ADD_ADDRESS -5616 // TODO(@divma): check if we need to do path exclusive filters -5617 while space_id == SpaceId::Data && frame::AddAddress::SIZE_BOUND <= buf.remaining_mut() { -5618 if let Some(added_address) = space.pending.add_address.pop_last() { -5619 trace!( -5620 seq = %added_address.seq_no, -5621 ip = ?added_address.ip, -5622 port = added_address.port, -5623 "ADD_ADDRESS", -5624 ); -5625 added_address.write(buf); -5626 sent.retransmits -5627 .get_or_create() -5628 .add_address -5629 .insert(added_address); -5630 self.stats.frame_tx.add_address = self.stats.frame_tx.add_address.saturating_add(1); -5631 qlog.frame(&Frame::AddAddress(added_address)); -5632 } else { -5633 break; -5634 } -5635 } -5636 -5637 // REMOVE_ADDRESS -5638 while space_id == SpaceId::Data && frame::RemoveAddress::SIZE_BOUND <= buf.remaining_mut() { -5639 if let Some(removed_address) = space.pending.remove_address.pop_last() { -5640 trace!(seq = %removed_address.seq_no, "REMOVE_ADDRESS"); -5641 removed_address.write(buf); -5642 sent.retransmits -5643 .get_or_create() -5644 .remove_address -5645 .insert(removed_address); -5646 self.stats.frame_tx.remove_address = -5647 self.stats.frame_tx.remove_address.saturating_add(1); -5648 qlog.frame(&Frame::RemoveAddress(removed_address)); -5649 } else { -5650 break; -5651 } -5652 } -5653 -5654 sent -5655 } -5656 -5657 /// Write pending ACKs into a buffer -5658 fn populate_acks( -5659 now: Instant, -5660 receiving_ecn: bool, -5661 sent: &mut SentFrames, -5662 path_id: PathId, -5663 space_id: SpaceId, -5664 space: &mut PacketSpace, -5665 is_multipath_negotiated: bool, -5666 buf: &mut impl BufMut, -5667 stats: &mut ConnectionStats, -5668 #[allow(unused)] qlog: &mut QlogSentPacket, -5669 ) { -5670 // 0-RTT packets must never carry acks (which would have to be of handshake packets) -5671 debug_assert!(space.crypto.is_some(), "tried to send ACK in 0-RTT"); +5568 +5569 trace!("NEW_TOKEN"); +5570 new_token.encode(buf); +5571 qlog.frame(&Frame::NewToken(new_token)); +5572 sent.retransmits +5573 .get_or_create() +5574 .new_tokens +5575 .push(remote_addr); +5576 self.stats.frame_tx.new_token += 1; +5577 } +5578 +5579 // STREAM +5580 if !path_exclusive_only && space_id == SpaceId::Data { +5581 sent.stream_frames = +5582 self.streams +5583 .write_stream_frames(buf, self.config.send_fairness, qlog); +5584 self.stats.frame_tx.stream += sent.stream_frames.len() as u64; +5585 } +5586 +5587 // ADD_ADDRESS +5588 // TODO(@divma): check if we need to do path exclusive filters +5589 while space_id == SpaceId::Data && frame::AddAddress::SIZE_BOUND <= buf.remaining_mut() { +5590 if let Some(added_address) = space.pending.add_address.pop_last() { +5591 trace!( +5592 seq = %added_address.seq_no, +5593 ip = ?added_address.ip, +5594 port = added_address.port, +5595 "ADD_ADDRESS", +5596 ); +5597 added_address.write(buf); +5598 sent.retransmits +5599 .get_or_create() +5600 .add_address +5601 .insert(added_address); +5602 self.stats.frame_tx.add_address = self.stats.frame_tx.add_address.saturating_add(1); +5603 qlog.frame(&Frame::AddAddress(added_address)); +5604 } else { +5605 break; +5606 } +5607 } +5608 +5609 // REMOVE_ADDRESS +5610 while space_id == SpaceId::Data && frame::RemoveAddress::SIZE_BOUND <= buf.remaining_mut() { +5611 if let Some(removed_address) = space.pending.remove_address.pop_last() { +5612 trace!(seq = %removed_address.seq_no, "REMOVE_ADDRESS"); +5613 removed_address.write(buf); +5614 sent.retransmits +5615 .get_or_create() +5616 .remove_address +5617 .insert(removed_address); +5618 self.stats.frame_tx.remove_address = +5619 self.stats.frame_tx.remove_address.saturating_add(1); +5620 qlog.frame(&Frame::RemoveAddress(removed_address)); +5621 } else { +5622 break; +5623 } +5624 } +5625 +5626 sent +5627 } +5628 +5629 /// Write pending ACKs into a buffer +5630 fn populate_acks( +5631 now: Instant, +5632 receiving_ecn: bool, +5633 sent: &mut SentFrames, +5634 path_id: PathId, +5635 space_id: SpaceId, +5636 space: &mut PacketSpace, +5637 is_multipath_negotiated: bool, +5638 buf: &mut impl BufMut, +5639 stats: &mut ConnectionStats, +5640 #[allow(unused)] qlog: &mut QlogSentPacket, +5641 ) { +5642 // 0-RTT packets must never carry acks (which would have to be of handshake packets) +5643 debug_assert!(space.crypto.is_some(), "tried to send ACK in 0-RTT"); +5644 +5645 debug_assert!( +5646 is_multipath_negotiated || path_id == PathId::ZERO, +5647 "Only PathId::ZERO allowed without multipath (have {path_id:?})" +5648 ); +5649 if is_multipath_negotiated { +5650 debug_assert!( +5651 space_id == SpaceId::Data || path_id == PathId::ZERO, +5652 "path acks must be sent in 1RTT space (have {space_id:?})" +5653 ); +5654 } +5655 +5656 let pns = space.for_path(path_id); +5657 let ranges = pns.pending_acks.ranges(); +5658 debug_assert!(!ranges.is_empty(), "can not send empty ACK range"); +5659 let ecn = if receiving_ecn { +5660 Some(&pns.ecn_counters) +5661 } else { +5662 None +5663 }; +5664 if let Some(max) = ranges.max() { +5665 sent.largest_acked.insert(path_id, max); +5666 } +5667 +5668 let delay_micros = pns.pending_acks.ack_delay(now).as_micros() as u64; +5669 // TODO: This should come from `TransportConfig` if that gets configurable. +5670 let ack_delay_exp = TransportParameters::default().ack_delay_exponent; +5671 let delay = delay_micros >> ack_delay_exp.into_inner(); 5672 -5673 debug_assert!( -5674 is_multipath_negotiated || path_id == PathId::ZERO, -5675 "Only PathId::ZERO allowed without multipath (have {path_id:?})" -5676 ); -5677 if is_multipath_negotiated { -5678 debug_assert!( -5679 space_id == SpaceId::Data || path_id == PathId::ZERO, -5680 "path acks must be sent in 1RTT space (have {space_id:?})" -5681 ); -5682 } -5683 -5684 let pns = space.for_path(path_id); -5685 let ranges = pns.pending_acks.ranges(); -5686 debug_assert!(!ranges.is_empty(), "can not send empty ACK range"); -5687 let ecn = if receiving_ecn { -5688 Some(&pns.ecn_counters) -5689 } else { -5690 None -5691 }; -5692 if let Some(max) = ranges.max() { -5693 sent.largest_acked.insert(path_id, max); -5694 } -5695 -5696 let delay_micros = pns.pending_acks.ack_delay(now).as_micros() as u64; -5697 // TODO: This should come from `TransportConfig` if that gets configurable. -5698 let ack_delay_exp = TransportParameters::default().ack_delay_exponent; -5699 let delay = delay_micros >> ack_delay_exp.into_inner(); -5700 -5701 if is_multipath_negotiated && space_id == SpaceId::Data { -5702 if !ranges.is_empty() { -5703 trace!("PATH_ACK {path_id:?} {ranges:?}, Delay = {delay_micros}us"); -5704 frame::PathAck::encode(path_id, delay as _, ranges, ecn, buf); -5705 qlog.frame_path_ack(path_id, delay as _, ranges, ecn); -5706 stats.frame_tx.path_acks += 1; -5707 } -5708 } else { -5709 trace!("ACK {ranges:?}, Delay = {delay_micros}us"); -5710 frame::Ack::encode(delay as _, ranges, ecn, buf); -5711 stats.frame_tx.acks += 1; -5712 qlog.frame_ack(delay, ranges, ecn); -5713 } -5714 } -5715 -5716 fn close_common(&mut self) { -5717 trace!("connection closed"); -5718 self.timers.reset(); -5719 } -5720 -5721 fn set_close_timer(&mut self, now: Instant) { -5722 // QUIC-MULTIPATH § 2.6 Connection Closure: draining for 3*PTO with PTO the max of -5723 // the PTO for all paths. -5724 self.timers.set( -5725 Timer::Conn(ConnTimer::Close), -5726 now + 3 * self.pto_max_path(self.highest_space), -5727 self.qlog.with_time(now), -5728 ); -5729 } -5730 -5731 /// Handle transport parameters received from the peer -5732 /// -5733 /// *rem_cid* and *loc_cid* are the source and destination CIDs respectively of the -5734 /// *packet into which the transport parameters arrived. -5735 fn handle_peer_params( -5736 &mut self, -5737 params: TransportParameters, -5738 loc_cid: ConnectionId, -5739 rem_cid: ConnectionId, -5740 now: Instant, -5741 ) -> Result<(), TransportError> { -5742 if Some(self.orig_rem_cid) != params.initial_src_cid -5743 || (self.side.is_client() -5744 && (Some(self.initial_dst_cid) != params.original_dst_cid -5745 || self.retry_src_cid != params.retry_src_cid)) -5746 { -5747 return Err(TransportError::TRANSPORT_PARAMETER_ERROR( -5748 "CID authentication failure", -5749 )); -5750 } -5751 if params.initial_max_path_id.is_some() && (loc_cid.is_empty() || rem_cid.is_empty()) { -5752 return Err(TransportError::PROTOCOL_VIOLATION( -5753 "multipath must not use zero-length CIDs", -5754 )); +5673 if is_multipath_negotiated && space_id == SpaceId::Data { +5674 if !ranges.is_empty() { +5675 trace!("PATH_ACK {path_id:?} {ranges:?}, Delay = {delay_micros}us"); +5676 frame::PathAck::encode(path_id, delay as _, ranges, ecn, buf); +5677 qlog.frame_path_ack(path_id, delay as _, ranges, ecn); +5678 stats.frame_tx.path_acks += 1; +5679 } +5680 } else { +5681 trace!("ACK {ranges:?}, Delay = {delay_micros}us"); +5682 frame::Ack::encode(delay as _, ranges, ecn, buf); +5683 stats.frame_tx.acks += 1; +5684 qlog.frame_ack(delay, ranges, ecn); +5685 } +5686 } +5687 +5688 fn close_common(&mut self) { +5689 trace!("connection closed"); +5690 self.timers.reset(); +5691 } +5692 +5693 fn set_close_timer(&mut self, now: Instant) { +5694 // QUIC-MULTIPATH § 2.6 Connection Closure: draining for 3*PTO with PTO the max of +5695 // the PTO for all paths. +5696 self.timers.set( +5697 Timer::Conn(ConnTimer::Close), +5698 now + 3 * self.pto_max_path(self.highest_space), +5699 self.qlog.with_time(now), +5700 ); +5701 } +5702 +5703 /// Handle transport parameters received from the peer +5704 /// +5705 /// *rem_cid* and *loc_cid* are the source and destination CIDs respectively of the +5706 /// *packet into which the transport parameters arrived. +5707 fn handle_peer_params( +5708 &mut self, +5709 params: TransportParameters, +5710 loc_cid: ConnectionId, +5711 rem_cid: ConnectionId, +5712 now: Instant, +5713 ) -> Result<(), TransportError> { +5714 if Some(self.orig_rem_cid) != params.initial_src_cid +5715 || (self.side.is_client() +5716 && (Some(self.initial_dst_cid) != params.original_dst_cid +5717 || self.retry_src_cid != params.retry_src_cid)) +5718 { +5719 return Err(TransportError::TRANSPORT_PARAMETER_ERROR( +5720 "CID authentication failure", +5721 )); +5722 } +5723 if params.initial_max_path_id.is_some() && (loc_cid.is_empty() || rem_cid.is_empty()) { +5724 return Err(TransportError::PROTOCOL_VIOLATION( +5725 "multipath must not use zero-length CIDs", +5726 )); +5727 } +5728 +5729 self.set_peer_params(params); +5730 self.qlog.emit_peer_transport_params_received(self, now); +5731 +5732 Ok(()) +5733 } +5734 +5735 fn set_peer_params(&mut self, params: TransportParameters) { +5736 self.streams.set_params(&params); +5737 self.idle_timeout = +5738 negotiate_max_idle_timeout(self.config.max_idle_timeout, Some(params.max_idle_timeout)); +5739 trace!("negotiated max idle timeout {:?}", self.idle_timeout); +5740 +5741 if let Some(ref info) = params.preferred_address { +5742 // During the handshake PathId::ZERO exists. +5743 self.rem_cids.get_mut(&PathId::ZERO).expect("not yet abandoned").insert(frame::NewConnectionId { +5744 path_id: None, +5745 sequence: 1, +5746 id: info.connection_id, +5747 reset_token: info.stateless_reset_token, +5748 retire_prior_to: 0, +5749 }) +5750 .expect( +5751 "preferred address CID is the first received, and hence is guaranteed to be legal", +5752 ); +5753 let remote = self.path_data(PathId::ZERO).remote; +5754 self.set_reset_token(PathId::ZERO, remote, info.stateless_reset_token); 5755 } -5756 -5757 self.set_peer_params(params); -5758 self.qlog.emit_peer_transport_params_received(self, now); -5759 -5760 Ok(()) -5761 } -5762 -5763 fn set_peer_params(&mut self, params: TransportParameters) { -5764 self.streams.set_params(&params); -5765 self.idle_timeout = -5766 negotiate_max_idle_timeout(self.config.max_idle_timeout, Some(params.max_idle_timeout)); -5767 trace!("negotiated max idle timeout {:?}", self.idle_timeout); -5768 -5769 if let Some(ref info) = params.preferred_address { -5770 // During the handshake PathId::ZERO exists. -5771 self.rem_cids.get_mut(&PathId::ZERO).expect("not yet abandoned").insert(frame::NewConnectionId { -5772 path_id: None, -5773 sequence: 1, -5774 id: info.connection_id, -5775 reset_token: info.stateless_reset_token, -5776 retire_prior_to: 0, -5777 }) -5778 .expect( -5779 "preferred address CID is the first received, and hence is guaranteed to be legal", -5780 ); -5781 let remote = self.path_data(PathId::ZERO).remote; -5782 self.set_reset_token(PathId::ZERO, remote, info.stateless_reset_token); -5783 } -5784 self.ack_frequency.peer_max_ack_delay = get_max_ack_delay(&params); -5785 -5786 let mut multipath_enabled = None; -5787 if let (Some(local_max_path_id), Some(remote_max_path_id)) = ( -5788 self.config.get_initial_max_path_id(), -5789 params.initial_max_path_id, -5790 ) { -5791 // multipath is enabled, register the local and remote maximums -5792 self.local_max_path_id = local_max_path_id; -5793 self.remote_max_path_id = remote_max_path_id; -5794 let initial_max_path_id = local_max_path_id.min(remote_max_path_id); -5795 debug!(%initial_max_path_id, "multipath negotiated"); -5796 multipath_enabled = Some(initial_max_path_id); -5797 } -5798 -5799 if let Some((max_locally_allowed_remote_addresses, max_remotely_allowed_remote_addresses)) = -5800 self.config -5801 .max_remote_nat_traversal_addresses -5802 .zip(params.max_remote_nat_traversal_addresses) -5803 { -5804 if let Some(max_initial_paths) = -5805 multipath_enabled.map(|path_id| path_id.saturating_add(1u8)) -5806 { -5807 let max_local_addresses = max_remotely_allowed_remote_addresses.get(); -5808 let max_remote_addresses = max_locally_allowed_remote_addresses.get(); -5809 self.iroh_hp = -5810 iroh_hp::State::new(max_remote_addresses, max_local_addresses, self.side()); -5811 debug!( -5812 %max_remote_addresses, %max_local_addresses, -5813 "iroh hole punching negotiated" -5814 ); -5815 -5816 match self.side() { -5817 Side::Client => { -5818 if max_initial_paths.as_u32() < max_remote_addresses as u32 + 1 { -5819 // in this case the client might try to open `max_remote_addresses` new -5820 // paths, but the current multipath configuration will not allow it -5821 warn!(%max_initial_paths, %max_remote_addresses, "local client configuration might cause nat traversal issues") -5822 } else if max_local_addresses as u64 -5823 > params.active_connection_id_limit.into_inner() -5824 { -5825 // the server allows us to send at most `params.active_connection_id_limit` -5826 // but they might need at least `max_local_addresses` to effectively send -5827 // `PATH_CHALLENGE` frames to each advertised local address -5828 warn!(%max_local_addresses, remote_cid_limit=%params.active_connection_id_limit.into_inner(), "remote server configuration might cause nat traversal issues") -5829 } -5830 } -5831 Side::Server => { -5832 if (max_initial_paths.as_u32() as u64) < crate::LOC_CID_COUNT { -5833 warn!(%max_initial_paths, local_cid_limit=%crate::LOC_CID_COUNT, "local server configuration might cause nat traversal issues") -5834 } -5835 } -5836 } -5837 } else { -5838 debug!("iroh nat traversal enabled for both endpoints, but multipath is missing") -5839 } -5840 } -5841 -5842 self.peer_params = params; -5843 let peer_max_udp_payload_size = -5844 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()).unwrap_or(u16::MAX); -5845 self.path_data_mut(PathId::ZERO) -5846 .mtud -5847 .on_peer_max_udp_payload_size_received(peer_max_udp_payload_size); -5848 } -5849 -5850 /// Decrypts a packet, returning the packet number on success -5851 fn decrypt_packet( -5852 &mut self, -5853 now: Instant, -5854 path_id: PathId, -5855 packet: &mut Packet, -5856 ) -> Result<Option<u64>, Option<TransportError>> { -5857 let result = packet_crypto::decrypt_packet_body( -5858 packet, -5859 path_id, -5860 &self.spaces, -5861 self.zero_rtt_crypto.as_ref(), -5862 self.key_phase, -5863 self.prev_crypto.as_ref(), -5864 self.next_crypto.as_ref(), -5865 )?; -5866 -5867 let result = match result { -5868 Some(r) => r, -5869 None => return Ok(None), -5870 }; -5871 -5872 if result.outgoing_key_update_acked { -5873 if let Some(prev) = self.prev_crypto.as_mut() { -5874 prev.end_packet = Some((result.number, now)); -5875 self.set_key_discard_timer(now, packet.header.space()); -5876 } -5877 } -5878 -5879 if result.incoming_key_update { -5880 trace!("key update authenticated"); -5881 self.update_keys(Some((result.number, now)), true); -5882 self.set_key_discard_timer(now, packet.header.space()); -5883 } -5884 -5885 Ok(Some(result.number)) -5886 } -5887 -5888 fn update_keys(&mut self, end_packet: Option<(u64, Instant)>, remote: bool) { -5889 trace!("executing key update"); -5890 // Generate keys for the key phase after the one we're switching to, store them in -5891 // `next_crypto`, make the contents of `next_crypto` current, and move the current keys into -5892 // `prev_crypto`. -5893 let new = self -5894 .crypto -5895 .next_1rtt_keys() -5896 .expect("only called for `Data` packets"); -5897 self.key_phase_size = new -5898 .local -5899 .confidentiality_limit() -5900 .saturating_sub(KEY_UPDATE_MARGIN); -5901 let old = mem::replace( -5902 &mut self.spaces[SpaceId::Data] -5903 .crypto -5904 .as_mut() -5905 .unwrap() // safe because update_keys() can only be triggered by short packets -5906 .packet, -5907 mem::replace(self.next_crypto.as_mut().unwrap(), new), -5908 ); -5909 self.spaces[SpaceId::Data] -5910 .iter_paths_mut() -5911 .for_each(|s| s.sent_with_keys = 0); -5912 self.prev_crypto = Some(PrevCrypto { -5913 crypto: old, -5914 end_packet, -5915 update_unacked: remote, -5916 }); -5917 self.key_phase = !self.key_phase; -5918 } -5919 -5920 fn peer_supports_ack_frequency(&self) -> bool { -5921 self.peer_params.min_ack_delay.is_some() -5922 } +5756 self.ack_frequency.peer_max_ack_delay = get_max_ack_delay(&params); +5757 +5758 let mut multipath_enabled = None; +5759 if let (Some(local_max_path_id), Some(remote_max_path_id)) = ( +5760 self.config.get_initial_max_path_id(), +5761 params.initial_max_path_id, +5762 ) { +5763 // multipath is enabled, register the local and remote maximums +5764 self.local_max_path_id = local_max_path_id; +5765 self.remote_max_path_id = remote_max_path_id; +5766 let initial_max_path_id = local_max_path_id.min(remote_max_path_id); +5767 debug!(%initial_max_path_id, "multipath negotiated"); +5768 multipath_enabled = Some(initial_max_path_id); +5769 } +5770 +5771 if let Some((max_locally_allowed_remote_addresses, max_remotely_allowed_remote_addresses)) = +5772 self.config +5773 .max_remote_nat_traversal_addresses +5774 .zip(params.max_remote_nat_traversal_addresses) +5775 { +5776 if let Some(max_initial_paths) = +5777 multipath_enabled.map(|path_id| path_id.saturating_add(1u8)) +5778 { +5779 let max_local_addresses = max_remotely_allowed_remote_addresses.get(); +5780 let max_remote_addresses = max_locally_allowed_remote_addresses.get(); +5781 self.iroh_hp = +5782 iroh_hp::State::new(max_remote_addresses, max_local_addresses, self.side()); +5783 debug!( +5784 %max_remote_addresses, %max_local_addresses, +5785 "iroh hole punching negotiated" +5786 ); +5787 +5788 match self.side() { +5789 Side::Client => { +5790 if max_initial_paths.as_u32() < max_remote_addresses as u32 + 1 { +5791 // in this case the client might try to open `max_remote_addresses` new +5792 // paths, but the current multipath configuration will not allow it +5793 warn!(%max_initial_paths, %max_remote_addresses, "local client configuration might cause nat traversal issues") +5794 } else if max_local_addresses as u64 +5795 > params.active_connection_id_limit.into_inner() +5796 { +5797 // the server allows us to send at most `params.active_connection_id_limit` +5798 // but they might need at least `max_local_addresses` to effectively send +5799 // `PATH_CHALLENGE` frames to each advertised local address +5800 warn!(%max_local_addresses, remote_cid_limit=%params.active_connection_id_limit.into_inner(), "remote server configuration might cause nat traversal issues") +5801 } +5802 } +5803 Side::Server => { +5804 if (max_initial_paths.as_u32() as u64) < crate::LOC_CID_COUNT { +5805 warn!(%max_initial_paths, local_cid_limit=%crate::LOC_CID_COUNT, "local server configuration might cause nat traversal issues") +5806 } +5807 } +5808 } +5809 } else { +5810 debug!("iroh nat traversal enabled for both endpoints, but multipath is missing") +5811 } +5812 } +5813 +5814 self.peer_params = params; +5815 let peer_max_udp_payload_size = +5816 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()).unwrap_or(u16::MAX); +5817 self.path_data_mut(PathId::ZERO) +5818 .mtud +5819 .on_peer_max_udp_payload_size_received(peer_max_udp_payload_size); +5820 } +5821 +5822 /// Decrypts a packet, returning the packet number on success +5823 fn decrypt_packet( +5824 &mut self, +5825 now: Instant, +5826 path_id: PathId, +5827 packet: &mut Packet, +5828 ) -> Result<Option<u64>, Option<TransportError>> { +5829 let result = packet_crypto::decrypt_packet_body( +5830 packet, +5831 path_id, +5832 &self.spaces, +5833 self.zero_rtt_crypto.as_ref(), +5834 self.key_phase, +5835 self.prev_crypto.as_ref(), +5836 self.next_crypto.as_ref(), +5837 )?; +5838 +5839 let result = match result { +5840 Some(r) => r, +5841 None => return Ok(None), +5842 }; +5843 +5844 if result.outgoing_key_update_acked { +5845 if let Some(prev) = self.prev_crypto.as_mut() { +5846 prev.end_packet = Some((result.number, now)); +5847 self.set_key_discard_timer(now, packet.header.space()); +5848 } +5849 } +5850 +5851 if result.incoming_key_update { +5852 trace!("key update authenticated"); +5853 self.update_keys(Some((result.number, now)), true); +5854 self.set_key_discard_timer(now, packet.header.space()); +5855 } +5856 +5857 Ok(Some(result.number)) +5858 } +5859 +5860 fn update_keys(&mut self, end_packet: Option<(u64, Instant)>, remote: bool) { +5861 trace!("executing key update"); +5862 // Generate keys for the key phase after the one we're switching to, store them in +5863 // `next_crypto`, make the contents of `next_crypto` current, and move the current keys into +5864 // `prev_crypto`. +5865 let new = self +5866 .crypto +5867 .next_1rtt_keys() +5868 .expect("only called for `Data` packets"); +5869 self.key_phase_size = new +5870 .local +5871 .confidentiality_limit() +5872 .saturating_sub(KEY_UPDATE_MARGIN); +5873 let old = mem::replace( +5874 &mut self.spaces[SpaceId::Data] +5875 .crypto +5876 .as_mut() +5877 .unwrap() // safe because update_keys() can only be triggered by short packets +5878 .packet, +5879 mem::replace(self.next_crypto.as_mut().unwrap(), new), +5880 ); +5881 self.spaces[SpaceId::Data] +5882 .iter_paths_mut() +5883 .for_each(|s| s.sent_with_keys = 0); +5884 self.prev_crypto = Some(PrevCrypto { +5885 crypto: old, +5886 end_packet, +5887 update_unacked: remote, +5888 }); +5889 self.key_phase = !self.key_phase; +5890 } +5891 +5892 fn peer_supports_ack_frequency(&self) -> bool { +5893 self.peer_params.min_ack_delay.is_some() +5894 } +5895 +5896 /// Send an IMMEDIATE_ACK frame to the remote endpoint +5897 /// +5898 /// According to the spec, this will result in an error if the remote endpoint does not support +5899 /// the Acknowledgement Frequency extension +5900 pub(crate) fn immediate_ack(&mut self, path_id: PathId) { +5901 debug_assert_eq!( +5902 self.highest_space, +5903 SpaceId::Data, +5904 "immediate ack must be written in the data space" +5905 ); +5906 self.spaces[self.highest_space] +5907 .for_path(path_id) +5908 .immediate_ack_pending = true; +5909 } +5910 +5911 /// Decodes a packet, returning its decrypted payload, so it can be inspected in tests +5912 #[cfg(test)] +5913 pub(crate) fn decode_packet(&self, event: &ConnectionEvent) -> Option<Vec<u8>> { +5914 let (path_id, first_decode, remaining) = match &event.0 { +5915 ConnectionEventInner::Datagram(DatagramConnectionEvent { +5916 path_id, +5917 first_decode, +5918 remaining, +5919 .. +5920 }) => (path_id, first_decode, remaining), +5921 _ => return None, +5922 }; 5923 -5924 /// Send an IMMEDIATE_ACK frame to the remote endpoint -5925 /// -5926 /// According to the spec, this will result in an error if the remote endpoint does not support -5927 /// the Acknowledgement Frequency extension -5928 pub(crate) fn immediate_ack(&mut self, path_id: PathId) { -5929 debug_assert_eq!( -5930 self.highest_space, -5931 SpaceId::Data, -5932 "immediate ack must be written in the data space" -5933 ); -5934 self.spaces[self.highest_space] -5935 .for_path(path_id) -5936 .immediate_ack_pending = true; -5937 } -5938 -5939 /// Decodes a packet, returning its decrypted payload, so it can be inspected in tests -5940 #[cfg(test)] -5941 pub(crate) fn decode_packet(&self, event: &ConnectionEvent) -> Option<Vec<u8>> { -5942 let (path_id, first_decode, remaining) = match &event.0 { -5943 ConnectionEventInner::Datagram(DatagramConnectionEvent { -5944 path_id, -5945 first_decode, -5946 remaining, -5947 .. -5948 }) => (path_id, first_decode, remaining), -5949 _ => return None, -5950 }; -5951 -5952 if remaining.is_some() { -5953 panic!("Packets should never be coalesced in tests"); -5954 } -5955 -5956 let decrypted_header = packet_crypto::unprotect_header( -5957 first_decode.clone(), -5958 &self.spaces, -5959 self.zero_rtt_crypto.as_ref(), -5960 self.peer_params.stateless_reset_token, -5961 )?; -5962 -5963 let mut packet = decrypted_header.packet?; -5964 packet_crypto::decrypt_packet_body( -5965 &mut packet, -5966 *path_id, -5967 &self.spaces, -5968 self.zero_rtt_crypto.as_ref(), -5969 self.key_phase, -5970 self.prev_crypto.as_ref(), -5971 self.next_crypto.as_ref(), -5972 ) -5973 .ok()?; -5974 -5975 Some(packet.payload.to_vec()) -5976 } -5977 -5978 /// The number of bytes of packets containing retransmittable frames that have not been -5979 /// acknowledged or declared lost. -5980 #[cfg(test)] -5981 pub(crate) fn bytes_in_flight(&self) -> u64 { -5982 // TODO(@divma): consider including for multipath? -5983 self.path_data(PathId::ZERO).in_flight.bytes +5924 if remaining.is_some() { +5925 panic!("Packets should never be coalesced in tests"); +5926 } +5927 +5928 let decrypted_header = packet_crypto::unprotect_header( +5929 first_decode.clone(), +5930 &self.spaces, +5931 self.zero_rtt_crypto.as_ref(), +5932 self.peer_params.stateless_reset_token, +5933 )?; +5934 +5935 let mut packet = decrypted_header.packet?; +5936 packet_crypto::decrypt_packet_body( +5937 &mut packet, +5938 *path_id, +5939 &self.spaces, +5940 self.zero_rtt_crypto.as_ref(), +5941 self.key_phase, +5942 self.prev_crypto.as_ref(), +5943 self.next_crypto.as_ref(), +5944 ) +5945 .ok()?; +5946 +5947 Some(packet.payload.to_vec()) +5948 } +5949 +5950 /// The number of bytes of packets containing retransmittable frames that have not been +5951 /// acknowledged or declared lost. +5952 #[cfg(test)] +5953 pub(crate) fn bytes_in_flight(&self) -> u64 { +5954 // TODO(@divma): consider including for multipath? +5955 self.path_data(PathId::ZERO).in_flight.bytes +5956 } +5957 +5958 /// Number of bytes worth of non-ack-only packets that may be sent +5959 #[cfg(test)] +5960 pub(crate) fn congestion_window(&self) -> u64 { +5961 let path = self.path_data(PathId::ZERO); +5962 path.congestion +5963 .window() +5964 .saturating_sub(path.in_flight.bytes) +5965 } +5966 +5967 /// Whether no timers but keepalive, idle, rtt, pushnewcid, and key discard are running +5968 #[cfg(test)] +5969 pub(crate) fn is_idle(&self) -> bool { +5970 let current_timers = self.timers.values(); +5971 current_timers +5972 .into_iter() +5973 .filter(|(timer, _)| { +5974 !matches!( +5975 timer, +5976 Timer::Conn(ConnTimer::KeepAlive) +5977 | Timer::PerPath(_, PathTimer::PathKeepAlive) +5978 | Timer::Conn(ConnTimer::PushNewCid) +5979 | Timer::Conn(ConnTimer::KeyDiscard) +5980 ) +5981 }) +5982 .min_by_key(|(_, time)| *time) +5983 .is_none_or(|(timer, _)| timer == Timer::Conn(ConnTimer::Idle)) 5984 } 5985 -5986 /// Number of bytes worth of non-ack-only packets that may be sent +5986 /// Whether explicit congestion notification is in use on outgoing packets. 5987 #[cfg(test)] -5988 pub(crate) fn congestion_window(&self) -> u64 { -5989 let path = self.path_data(PathId::ZERO); -5990 path.congestion -5991 .window() -5992 .saturating_sub(path.in_flight.bytes) -5993 } -5994 -5995 /// Whether no timers but keepalive, idle, rtt, pushnewcid, and key discard are running -5996 #[cfg(test)] -5997 pub(crate) fn is_idle(&self) -> bool { -5998 let current_timers = self.timers.values(); -5999 current_timers -6000 .into_iter() -6001 .filter(|(timer, _)| { -6002 !matches!( -6003 timer, -6004 Timer::Conn(ConnTimer::KeepAlive) -6005 | Timer::PerPath(_, PathTimer::PathKeepAlive) -6006 | Timer::Conn(ConnTimer::PushNewCid) -6007 | Timer::Conn(ConnTimer::KeyDiscard) -6008 ) -6009 }) -6010 .min_by_key(|(_, time)| *time) -6011 .is_none_or(|(timer, _)| timer == Timer::Conn(ConnTimer::Idle)) -6012 } -6013 -6014 /// Whether explicit congestion notification is in use on outgoing packets. -6015 #[cfg(test)] -6016 pub(crate) fn using_ecn(&self) -> bool { -6017 self.path_data(PathId::ZERO).sending_ecn -6018 } -6019 -6020 /// The number of received bytes in the current path -6021 #[cfg(test)] -6022 pub(crate) fn total_recvd(&self) -> u64 { -6023 self.path_data(PathId::ZERO).total_recvd -6024 } -6025 -6026 #[cfg(test)] -6027 pub(crate) fn active_local_cid_seq(&self) -> (u64, u64) { -6028 self.local_cid_state -6029 .get(&PathId::ZERO) -6030 .unwrap() -6031 .active_seq() +5988 pub(crate) fn using_ecn(&self) -> bool { +5989 self.path_data(PathId::ZERO).sending_ecn +5990 } +5991 +5992 /// The number of received bytes in the current path +5993 #[cfg(test)] +5994 pub(crate) fn total_recvd(&self) -> u64 { +5995 self.path_data(PathId::ZERO).total_recvd +5996 } +5997 +5998 #[cfg(test)] +5999 pub(crate) fn active_local_cid_seq(&self) -> (u64, u64) { +6000 self.local_cid_state +6001 .get(&PathId::ZERO) +6002 .unwrap() +6003 .active_seq() +6004 } +6005 +6006 #[cfg(test)] +6007 #[track_caller] +6008 pub(crate) fn active_local_path_cid_seq(&self, path_id: u32) -> (u64, u64) { +6009 self.local_cid_state +6010 .get(&PathId(path_id)) +6011 .unwrap() +6012 .active_seq() +6013 } +6014 +6015 /// Instruct the peer to replace previously issued CIDs by sending a NEW_CONNECTION_ID frame +6016 /// with updated `retire_prior_to` field set to `v` +6017 #[cfg(test)] +6018 pub(crate) fn rotate_local_cid(&mut self, v: u64, now: Instant) { +6019 let n = self +6020 .local_cid_state +6021 .get_mut(&PathId::ZERO) +6022 .unwrap() +6023 .assign_retire_seq(v); +6024 self.endpoint_events +6025 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); +6026 } +6027 +6028 /// Check the current active remote CID sequence for `PathId::ZERO` +6029 #[cfg(test)] +6030 pub(crate) fn active_rem_cid_seq(&self) -> u64 { +6031 self.rem_cids.get(&PathId::ZERO).unwrap().active_seq() 6032 } 6033 -6034 #[cfg(test)] -6035 #[track_caller] -6036 pub(crate) fn active_local_path_cid_seq(&self, path_id: u32) -> (u64, u64) { -6037 self.local_cid_state -6038 .get(&PathId(path_id)) -6039 .unwrap() -6040 .active_seq() -6041 } -6042 -6043 /// Instruct the peer to replace previously issued CIDs by sending a NEW_CONNECTION_ID frame -6044 /// with updated `retire_prior_to` field set to `v` -6045 #[cfg(test)] -6046 pub(crate) fn rotate_local_cid(&mut self, v: u64, now: Instant) { -6047 let n = self -6048 .local_cid_state -6049 .get_mut(&PathId::ZERO) -6050 .unwrap() -6051 .assign_retire_seq(v); -6052 self.endpoint_events -6053 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); -6054 } -6055 -6056 /// Check the current active remote CID sequence for `PathId::ZERO` -6057 #[cfg(test)] -6058 pub(crate) fn active_rem_cid_seq(&self) -> u64 { -6059 self.rem_cids.get(&PathId::ZERO).unwrap().active_seq() -6060 } -6061 -6062 /// Returns the detected maximum udp payload size for the current path -6063 #[cfg(test)] -6064 pub(crate) fn path_mtu(&self, path_id: PathId) -> u16 { -6065 self.path_data(path_id).current_mtu() -6066 } -6067 -6068 /// Triggers path validation on all paths -6069 #[cfg(test)] -6070 pub(crate) fn trigger_path_validation(&mut self) { -6071 for path in self.paths.values_mut() { -6072 path.data.send_new_challenge = true; -6073 } -6074 } -6075 -6076 /// Whether we have 1-RTT data to send -6077 /// -6078 /// This checks for frames that can only be sent in the data space (1-RTT): -6079 /// - Pending PATH_CHALLENGE frames on the active and previous path if just migrated. -6080 /// - Pending PATH_RESPONSE frames. -6081 /// - Pending data to send in STREAM frames. -6082 /// - Pending DATAGRAM frames to send. -6083 /// -6084 /// See also [`PacketSpace::can_send`] which keeps track of all other frame types that -6085 /// may need to be sent. -6086 fn can_send_1rtt(&self, path_id: PathId, max_size: usize) -> SendableFrames { -6087 let path_exclusive = self.paths.get(&path_id).is_some_and(|path| { -6088 path.data.send_new_challenge -6089 || path -6090 .prev -6091 .as_ref() -6092 .is_some_and(|(_, path)| path.send_new_challenge) -6093 || !path.data.path_responses.is_empty() -6094 }); -6095 let other = self.streams.can_send_stream_data() -6096 || self -6097 .datagrams -6098 .outgoing -6099 .front() -6100 .is_some_and(|x| x.size(true) <= max_size); -6101 SendableFrames { -6102 acks: false, -6103 other, -6104 close: false, -6105 path_exclusive, -6106 } -6107 } -6108 -6109 /// Terminate the connection instantly, without sending a close packet -6110 fn kill(&mut self, reason: ConnectionError) { -6111 self.close_common(); -6112 self.state.move_to_drained(Some(reason)); -6113 self.endpoint_events.push_back(EndpointEventInner::Drained); -6114 } -6115 -6116 /// Storage size required for the largest packet that can be transmitted on all currently -6117 /// available paths -6118 /// -6119 /// Buffers passed to [`Connection::poll_transmit`] should be at least this large. -6120 /// -6121 /// When multipath is enabled, this value is the minimum MTU across all available paths. -6122 pub fn current_mtu(&self) -> u16 { -6123 self.paths -6124 .iter() -6125 .filter(|&(path_id, _path_state)| !self.abandoned_paths.contains(path_id)) -6126 .map(|(_path_id, path_state)| path_state.data.current_mtu()) -6127 .min() -6128 .expect("There is always at least one available path") -6129 } -6130 -6131 /// Size of non-frame data for a 1-RTT packet -6132 /// -6133 /// Quantifies space consumed by the QUIC header and AEAD tag. All other bytes in a packet are -6134 /// frames. Changes if the length of the remote connection ID changes, which is expected to be -6135 /// rare. If `pn` is specified, may additionally change unpredictably due to variations in -6136 /// latency and packet loss. -6137 fn predict_1rtt_overhead(&mut self, pn: u64, path: PathId) -> usize { -6138 let pn_len = PacketNumber::new( -6139 pn, -6140 self.spaces[SpaceId::Data] -6141 .for_path(path) -6142 .largest_acked_packet -6143 .unwrap_or(0), -6144 ) -6145 .len(); -6146 -6147 // 1 byte for flags -6148 1 + self -6149 .rem_cids -6150 .get(&path) -6151 .map(|cids| cids.active().len()) -6152 .unwrap_or(20) // Max CID len in QUIC v1 -6153 + pn_len -6154 + self.tag_len_1rtt() -6155 } -6156 -6157 fn predict_1rtt_overhead_no_pn(&self) -> usize { -6158 let pn_len = 4; -6159 -6160 let cid_len = self -6161 .rem_cids -6162 .values() -6163 .map(|cids| cids.active().len()) -6164 .max() -6165 .unwrap_or(20); // Max CID len in QUIC v1 -6166 -6167 // 1 byte for flags -6168 1 + cid_len + pn_len + self.tag_len_1rtt() -6169 } -6170 -6171 fn tag_len_1rtt(&self) -> usize { -6172 let key = match self.spaces[SpaceId::Data].crypto.as_ref() { -6173 Some(crypto) => Some(&*crypto.packet.local), -6174 None => self.zero_rtt_crypto.as_ref().map(|x| &*x.packet), -6175 }; -6176 // If neither Data nor 0-RTT keys are available, make a reasonable tag length guess. As of -6177 // this writing, all QUIC cipher suites use 16-byte tags. We could return `None` instead, -6178 // but that would needlessly prevent sending datagrams during 0-RTT. -6179 key.map_or(16, |x| x.tag_len()) -6180 } -6181 -6182 /// Mark the path as validated, and enqueue NEW_TOKEN frames to be sent as appropriate -6183 fn on_path_validated(&mut self, path_id: PathId) { -6184 self.path_data_mut(path_id).validated = true; -6185 let ConnectionSide::Server { server_config } = &self.side else { -6186 return; -6187 }; -6188 let remote_addr = self.path_data(path_id).remote; -6189 let new_tokens = &mut self.spaces[SpaceId::Data as usize].pending.new_tokens; -6190 new_tokens.clear(); -6191 for _ in 0..server_config.validation_token.sent { -6192 new_tokens.push(remote_addr); -6193 } -6194 } -6195 -6196 /// Handle new path status information: PATH_STATUS_AVAILABLE, PATH_STATUS_BACKUP -6197 fn on_path_status(&mut self, path_id: PathId, status: PathStatus, status_seq_no: VarInt) { -6198 if let Some(path) = self.paths.get_mut(&path_id) { -6199 path.data.status.remote_update(status, status_seq_no); -6200 } else { -6201 debug!("PATH_STATUS_AVAILABLE received unknown path {:?}", path_id); -6202 } -6203 self.events.push_back( -6204 PathEvent::RemoteStatus { -6205 id: path_id, -6206 status, -6207 } -6208 .into(), -6209 ); -6210 } -6211 -6212 /// Returns the maximum [`PathId`] to be used for sending in this connection. -6213 /// -6214 /// This is calculated as minimum between the local and remote's maximums when multipath is -6215 /// enabled, or `None` when disabled. -6216 /// -6217 /// For data that's received, we should use [`Self::local_max_path_id`] instead. -6218 /// The reasoning is that the remote might already have updated to its own newer -6219 /// [`Self::max_path_id`] after sending out a `MAX_PATH_ID` frame, but it got re-ordered. -6220 fn max_path_id(&self) -> Option<PathId> { -6221 if self.is_multipath_negotiated() { -6222 Some(self.remote_max_path_id.min(self.local_max_path_id)) -6223 } else { -6224 None -6225 } -6226 } -6227 -6228 /// Add addresses the local endpoint considers are reachable for nat traversal -6229 pub fn add_nat_traversal_address(&mut self, address: SocketAddr) -> Result<(), iroh_hp::Error> { -6230 if let Some(added) = self.iroh_hp.add_local_address(address)? { -6231 self.spaces[SpaceId::Data].pending.add_address.insert(added); -6232 }; -6233 Ok(()) -6234 } -6235 -6236 /// Removes an address the endpoing no longer considers reachable for nat traversal -6237 /// -6238 /// Addresses not present in the set will be silently ignored. -6239 pub fn remove_nat_traversal_address( -6240 &mut self, -6241 address: SocketAddr, -6242 ) -> Result<(), iroh_hp::Error> { -6243 if let Some(removed) = self.iroh_hp.remove_local_address(address)? { -6244 self.spaces[SpaceId::Data] -6245 .pending -6246 .remove_address -6247 .insert(removed); -6248 } -6249 Ok(()) -6250 } -6251 -6252 /// Get the current local nat traversal addresses -6253 pub fn get_local_nat_traversal_addresses(&self) -> Result<Vec<SocketAddr>, iroh_hp::Error> { -6254 self.iroh_hp.get_local_nat_traversal_addresses() -6255 } -6256 -6257 /// Get the currently advertised nat traversal addresses by the server -6258 pub fn get_remote_nat_traversal_addresses(&self) -> Result<Vec<SocketAddr>, iroh_hp::Error> { -6259 Ok(self -6260 .iroh_hp -6261 .client_side()? -6262 .get_remote_nat_traversal_addresses()) -6263 } -6264 -6265 /// Initiates a new nat traversal round -6266 /// -6267 /// A nat traversal round involves advertising the client's local addresses in `REACH_OUT` -6268 /// frames, and initiating probing of the known remote addresses. When a new round is -6269 /// initiated, the previous one is cancelled, and paths that have not been opened are closed. -6270 /// -6271 /// Returns the server addresses that are now being probed. -6272 pub fn initiate_nat_traversal_round( -6273 &mut self, -6274 now: Instant, -6275 ) -> Result<Vec<SocketAddr>, iroh_hp::Error> { -6276 let client_state = self.iroh_hp.client_side_mut()?; -6277 let iroh_hp::NatTraversalRound { -6278 new_round, -6279 reach_out_at, -6280 addresses_to_probe, -6281 prev_round_path_ids, -6282 } = client_state.initiate_nat_traversal_round()?; -6283 -6284 self.spaces[SpaceId::Data].pending.reach_out = Some((new_round, reach_out_at)); -6285 -6286 for path_id in prev_round_path_ids { -6287 // TODO(@divma): this sounds reasonable but we need if this actually works for the -6288 // purposes of the protocol -6289 let validated = self -6290 .path(path_id) -6291 .map(|path| path.validated) -6292 .unwrap_or(false); -6293 -6294 if !validated { -6295 let _ = self.close_path( -6296 now, -6297 path_id, -6298 TransportErrorCode::APPLICATION_ABANDON_PATH.into(), -6299 ); -6300 } -6301 } -6302 -6303 let mut err = None; -6304 -6305 let mut path_ids = Vec::with_capacity(addresses_to_probe.len()); -6306 let mut probed_addresses = Vec::with_capacity(addresses_to_probe.len()); -6307 let ipv6 = self.paths.values().any(|p| p.data.remote.is_ipv6()); -6308 -6309 for (ip, port) in addresses_to_probe { -6310 // If this endpoint is an IPv6 endpoint we use IPv6 addresses for all remotes. -6311 let remote = match ip { -6312 IpAddr::V4(addr) if ipv6 => SocketAddr::new(addr.to_ipv6_mapped().into(), port), -6313 IpAddr::V4(addr) => SocketAddr::new(addr.into(), port), -6314 IpAddr::V6(_) if ipv6 => SocketAddr::new(ip, port), -6315 IpAddr::V6(_) => { -6316 trace!("not using IPv6 nat candidate for IPv4 socket"); -6317 continue; -6318 } -6319 }; -6320 match self.open_path_ensure(remote, PathStatus::Backup, now) { -6321 Ok((path_id, path_was_known)) if !path_was_known => { -6322 path_ids.push(path_id); -6323 probed_addresses.push(remote); -6324 } -6325 Ok((path_id, _)) => { -6326 trace!(%path_id, %remote,"nat traversal: path existed for remote") -6327 } -6328 Err(e) => { -6329 debug!(%remote, %e,"nat traversal: failed to probe remote"); -6330 err.get_or_insert(e); -6331 } -6332 } -6333 } -6334 -6335 if let Some(err) = err { -6336 // We failed to probe any addresses, bail out -6337 if probed_addresses.is_empty() { -6338 return Err(iroh_hp::Error::Multipath(err)); -6339 } -6340 } -6341 -6342 self.iroh_hp -6343 .client_side_mut() -6344 .expect("connection side validated") -6345 .set_round_path_ids(path_ids); -6346 -6347 Ok(probed_addresses) -6348 } -6349} -6350 -6351impl fmt::Debug for Connection { -6352 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { -6353 f.debug_struct("Connection") -6354 .field("handshake_cid", &self.handshake_cid) -6355 .finish() -6356 } -6357} -6358 -6359#[derive(Debug, Copy, Clone, PartialEq, Eq)] -6360enum PathBlocked { -6361 No, -6362 AntiAmplification, -6363 Congestion, -6364 Pacing, -6365} -6366 -6367/// Fields of `Connection` specific to it being client-side or server-side -6368enum ConnectionSide { -6369 Client { -6370 /// Sent in every outgoing Initial packet. Always empty after Initial keys are discarded -6371 token: Bytes, -6372 token_store: Arc<dyn TokenStore>, -6373 server_name: String, -6374 }, -6375 Server { -6376 server_config: Arc<ServerConfig>, -6377 }, -6378} -6379 -6380impl ConnectionSide { -6381 fn remote_may_migrate(&self, state: &State) -> bool { -6382 match self { -6383 Self::Server { server_config } => server_config.migration, -6384 Self::Client { .. } => { -6385 if let Some(hs) = state.as_handshake() { -6386 hs.allow_server_migration -6387 } else { -6388 false -6389 } -6390 } -6391 } -6392 } -6393 -6394 fn is_client(&self) -> bool { -6395 self.side().is_client() -6396 } -6397 -6398 fn is_server(&self) -> bool { -6399 self.side().is_server() -6400 } +6034 /// Returns the detected maximum udp payload size for the current path +6035 #[cfg(test)] +6036 pub(crate) fn path_mtu(&self, path_id: PathId) -> u16 { +6037 self.path_data(path_id).current_mtu() +6038 } +6039 +6040 /// Triggers path validation on all paths +6041 #[cfg(test)] +6042 pub(crate) fn trigger_path_validation(&mut self) { +6043 for path in self.paths.values_mut() { +6044 path.data.send_new_challenge = true; +6045 } +6046 } +6047 +6048 /// Whether we have 1-RTT data to send +6049 /// +6050 /// This checks for frames that can only be sent in the data space (1-RTT): +6051 /// - Pending PATH_CHALLENGE frames on the active and previous path if just migrated. +6052 /// - Pending PATH_RESPONSE frames. +6053 /// - Pending data to send in STREAM frames. +6054 /// - Pending DATAGRAM frames to send. +6055 /// +6056 /// See also [`PacketSpace::can_send`] which keeps track of all other frame types that +6057 /// may need to be sent. +6058 fn can_send_1rtt(&self, path_id: PathId, max_size: usize) -> SendableFrames { +6059 let path_exclusive = self.paths.get(&path_id).is_some_and(|path| { +6060 path.data.send_new_challenge +6061 || path +6062 .prev +6063 .as_ref() +6064 .is_some_and(|(_, path)| path.send_new_challenge) +6065 || !path.data.path_responses.is_empty() +6066 }); +6067 let other = self.streams.can_send_stream_data() +6068 || self +6069 .datagrams +6070 .outgoing +6071 .front() +6072 .is_some_and(|x| x.size(true) <= max_size); +6073 SendableFrames { +6074 acks: false, +6075 other, +6076 close: false, +6077 path_exclusive, +6078 } +6079 } +6080 +6081 /// Terminate the connection instantly, without sending a close packet +6082 fn kill(&mut self, reason: ConnectionError) { +6083 self.close_common(); +6084 self.state.move_to_drained(Some(reason)); +6085 self.endpoint_events.push_back(EndpointEventInner::Drained); +6086 } +6087 +6088 /// Storage size required for the largest packet that can be transmitted on all currently +6089 /// available paths +6090 /// +6091 /// Buffers passed to [`Connection::poll_transmit`] should be at least this large. +6092 /// +6093 /// When multipath is enabled, this value is the minimum MTU across all available paths. +6094 pub fn current_mtu(&self) -> u16 { +6095 self.paths +6096 .iter() +6097 .filter(|&(path_id, _path_state)| !self.abandoned_paths.contains(path_id)) +6098 .map(|(_path_id, path_state)| path_state.data.current_mtu()) +6099 .min() +6100 .expect("There is always at least one available path") +6101 } +6102 +6103 /// Size of non-frame data for a 1-RTT packet +6104 /// +6105 /// Quantifies space consumed by the QUIC header and AEAD tag. All other bytes in a packet are +6106 /// frames. Changes if the length of the remote connection ID changes, which is expected to be +6107 /// rare. If `pn` is specified, may additionally change unpredictably due to variations in +6108 /// latency and packet loss. +6109 fn predict_1rtt_overhead(&mut self, pn: u64, path: PathId) -> usize { +6110 let pn_len = PacketNumber::new( +6111 pn, +6112 self.spaces[SpaceId::Data] +6113 .for_path(path) +6114 .largest_acked_packet +6115 .unwrap_or(0), +6116 ) +6117 .len(); +6118 +6119 // 1 byte for flags +6120 1 + self +6121 .rem_cids +6122 .get(&path) +6123 .map(|cids| cids.active().len()) +6124 .unwrap_or(20) // Max CID len in QUIC v1 +6125 + pn_len +6126 + self.tag_len_1rtt() +6127 } +6128 +6129 fn predict_1rtt_overhead_no_pn(&self) -> usize { +6130 let pn_len = 4; +6131 +6132 let cid_len = self +6133 .rem_cids +6134 .values() +6135 .map(|cids| cids.active().len()) +6136 .max() +6137 .unwrap_or(20); // Max CID len in QUIC v1 +6138 +6139 // 1 byte for flags +6140 1 + cid_len + pn_len + self.tag_len_1rtt() +6141 } +6142 +6143 fn tag_len_1rtt(&self) -> usize { +6144 let key = match self.spaces[SpaceId::Data].crypto.as_ref() { +6145 Some(crypto) => Some(&*crypto.packet.local), +6146 None => self.zero_rtt_crypto.as_ref().map(|x| &*x.packet), +6147 }; +6148 // If neither Data nor 0-RTT keys are available, make a reasonable tag length guess. As of +6149 // this writing, all QUIC cipher suites use 16-byte tags. We could return `None` instead, +6150 // but that would needlessly prevent sending datagrams during 0-RTT. +6151 key.map_or(16, |x| x.tag_len()) +6152 } +6153 +6154 /// Mark the path as validated, and enqueue NEW_TOKEN frames to be sent as appropriate +6155 fn on_path_validated(&mut self, path_id: PathId) { +6156 self.path_data_mut(path_id).validated = true; +6157 let ConnectionSide::Server { server_config } = &self.side else { +6158 return; +6159 }; +6160 let remote_addr = self.path_data(path_id).remote; +6161 let new_tokens = &mut self.spaces[SpaceId::Data as usize].pending.new_tokens; +6162 new_tokens.clear(); +6163 for _ in 0..server_config.validation_token.sent { +6164 new_tokens.push(remote_addr); +6165 } +6166 } +6167 +6168 /// Handle new path status information: PATH_STATUS_AVAILABLE, PATH_STATUS_BACKUP +6169 fn on_path_status(&mut self, path_id: PathId, status: PathStatus, status_seq_no: VarInt) { +6170 if let Some(path) = self.paths.get_mut(&path_id) { +6171 path.data.status.remote_update(status, status_seq_no); +6172 } else { +6173 debug!("PATH_STATUS_AVAILABLE received unknown path {:?}", path_id); +6174 } +6175 self.events.push_back( +6176 PathEvent::RemoteStatus { +6177 id: path_id, +6178 status, +6179 } +6180 .into(), +6181 ); +6182 } +6183 +6184 /// Returns the maximum [`PathId`] to be used for sending in this connection. +6185 /// +6186 /// This is calculated as minimum between the local and remote's maximums when multipath is +6187 /// enabled, or `None` when disabled. +6188 /// +6189 /// For data that's received, we should use [`Self::local_max_path_id`] instead. +6190 /// The reasoning is that the remote might already have updated to its own newer +6191 /// [`Self::max_path_id`] after sending out a `MAX_PATH_ID` frame, but it got re-ordered. +6192 fn max_path_id(&self) -> Option<PathId> { +6193 if self.is_multipath_negotiated() { +6194 Some(self.remote_max_path_id.min(self.local_max_path_id)) +6195 } else { +6196 None +6197 } +6198 } +6199 +6200 /// Add addresses the local endpoint considers are reachable for nat traversal +6201 pub fn add_nat_traversal_address(&mut self, address: SocketAddr) -> Result<(), iroh_hp::Error> { +6202 if let Some(added) = self.iroh_hp.add_local_address(address)? { +6203 self.spaces[SpaceId::Data].pending.add_address.insert(added); +6204 }; +6205 Ok(()) +6206 } +6207 +6208 /// Removes an address the endpoing no longer considers reachable for nat traversal +6209 /// +6210 /// Addresses not present in the set will be silently ignored. +6211 pub fn remove_nat_traversal_address( +6212 &mut self, +6213 address: SocketAddr, +6214 ) -> Result<(), iroh_hp::Error> { +6215 if let Some(removed) = self.iroh_hp.remove_local_address(address)? { +6216 self.spaces[SpaceId::Data] +6217 .pending +6218 .remove_address +6219 .insert(removed); +6220 } +6221 Ok(()) +6222 } +6223 +6224 /// Get the current local nat traversal addresses +6225 pub fn get_local_nat_traversal_addresses(&self) -> Result<Vec<SocketAddr>, iroh_hp::Error> { +6226 self.iroh_hp.get_local_nat_traversal_addresses() +6227 } +6228 +6229 /// Get the currently advertised nat traversal addresses by the server +6230 pub fn get_remote_nat_traversal_addresses(&self) -> Result<Vec<SocketAddr>, iroh_hp::Error> { +6231 Ok(self +6232 .iroh_hp +6233 .client_side()? +6234 .get_remote_nat_traversal_addresses()) +6235 } +6236 +6237 /// Initiates a new nat traversal round +6238 /// +6239 /// A nat traversal round involves advertising the client's local addresses in `REACH_OUT` +6240 /// frames, and initiating probing of the known remote addresses. When a new round is +6241 /// initiated, the previous one is cancelled, and paths that have not been opened are closed. +6242 /// +6243 /// Returns the server addresses that are now being probed. +6244 pub fn initiate_nat_traversal_round( +6245 &mut self, +6246 now: Instant, +6247 ) -> Result<Vec<SocketAddr>, iroh_hp::Error> { +6248 let client_state = self.iroh_hp.client_side_mut()?; +6249 let iroh_hp::NatTraversalRound { +6250 new_round, +6251 reach_out_at, +6252 addresses_to_probe, +6253 prev_round_path_ids, +6254 } = client_state.initiate_nat_traversal_round()?; +6255 +6256 self.spaces[SpaceId::Data].pending.reach_out = Some((new_round, reach_out_at)); +6257 +6258 for path_id in prev_round_path_ids { +6259 // TODO(@divma): this sounds reasonable but we need if this actually works for the +6260 // purposes of the protocol +6261 let validated = self +6262 .path(path_id) +6263 .map(|path| path.validated) +6264 .unwrap_or(false); +6265 +6266 if !validated { +6267 let _ = self.close_path( +6268 now, +6269 path_id, +6270 TransportErrorCode::APPLICATION_ABANDON_PATH.into(), +6271 ); +6272 } +6273 } +6274 +6275 let mut err = None; +6276 +6277 let mut path_ids = Vec::with_capacity(addresses_to_probe.len()); +6278 let mut probed_addresses = Vec::with_capacity(addresses_to_probe.len()); +6279 let ipv6 = self.paths.values().any(|p| p.data.remote.is_ipv6()); +6280 +6281 for (ip, port) in addresses_to_probe { +6282 // If this endpoint is an IPv6 endpoint we use IPv6 addresses for all remotes. +6283 let remote = match ip { +6284 IpAddr::V4(addr) if ipv6 => SocketAddr::new(addr.to_ipv6_mapped().into(), port), +6285 IpAddr::V4(addr) => SocketAddr::new(addr.into(), port), +6286 IpAddr::V6(_) if ipv6 => SocketAddr::new(ip, port), +6287 IpAddr::V6(_) => { +6288 trace!("not using IPv6 nat candidate for IPv4 socket"); +6289 continue; +6290 } +6291 }; +6292 match self.open_path_ensure(remote, PathStatus::Backup, now) { +6293 Ok((path_id, path_was_known)) if !path_was_known => { +6294 path_ids.push(path_id); +6295 probed_addresses.push(remote); +6296 } +6297 Ok((path_id, _)) => { +6298 trace!(%path_id, %remote,"nat traversal: path existed for remote") +6299 } +6300 Err(e) => { +6301 debug!(%remote, %e,"nat traversal: failed to probe remote"); +6302 err.get_or_insert(e); +6303 } +6304 } +6305 } +6306 +6307 if let Some(err) = err { +6308 // We failed to probe any addresses, bail out +6309 if probed_addresses.is_empty() { +6310 return Err(iroh_hp::Error::Multipath(err)); +6311 } +6312 } +6313 +6314 self.iroh_hp +6315 .client_side_mut() +6316 .expect("connection side validated") +6317 .set_round_path_ids(path_ids); +6318 +6319 Ok(probed_addresses) +6320 } +6321} +6322 +6323impl fmt::Debug for Connection { +6324 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +6325 f.debug_struct("Connection") +6326 .field("handshake_cid", &self.handshake_cid) +6327 .finish() +6328 } +6329} +6330 +6331#[derive(Debug, Copy, Clone, PartialEq, Eq)] +6332enum PathBlocked { +6333 No, +6334 AntiAmplification, +6335 Congestion, +6336 Pacing, +6337} +6338 +6339/// Fields of `Connection` specific to it being client-side or server-side +6340enum ConnectionSide { +6341 Client { +6342 /// Sent in every outgoing Initial packet. Always empty after Initial keys are discarded +6343 token: Bytes, +6344 token_store: Arc<dyn TokenStore>, +6345 server_name: String, +6346 }, +6347 Server { +6348 server_config: Arc<ServerConfig>, +6349 }, +6350} +6351 +6352impl ConnectionSide { +6353 fn remote_may_migrate(&self, state: &State) -> bool { +6354 match self { +6355 Self::Server { server_config } => server_config.migration, +6356 Self::Client { .. } => { +6357 if let Some(hs) = state.as_handshake() { +6358 hs.allow_server_migration +6359 } else { +6360 false +6361 } +6362 } +6363 } +6364 } +6365 +6366 fn is_client(&self) -> bool { +6367 self.side().is_client() +6368 } +6369 +6370 fn is_server(&self) -> bool { +6371 self.side().is_server() +6372 } +6373 +6374 fn side(&self) -> Side { +6375 match *self { +6376 Self::Client { .. } => Side::Client, +6377 Self::Server { .. } => Side::Server, +6378 } +6379 } +6380} +6381 +6382impl From<SideArgs> for ConnectionSide { +6383 fn from(side: SideArgs) -> Self { +6384 match side { +6385 SideArgs::Client { +6386 token_store, +6387 server_name, +6388 } => Self::Client { +6389 token: token_store.take(&server_name).unwrap_or_default(), +6390 token_store, +6391 server_name, +6392 }, +6393 SideArgs::Server { +6394 server_config, +6395 pref_addr_cid: _, +6396 path_validated: _, +6397 } => Self::Server { server_config }, +6398 } +6399 } +6400} 6401 -6402 fn side(&self) -> Side { -6403 match *self { -6404 Self::Client { .. } => Side::Client, -6405 Self::Server { .. } => Side::Server, -6406 } -6407 } -6408} -6409 -6410impl From<SideArgs> for ConnectionSide { -6411 fn from(side: SideArgs) -> Self { -6412 match side { -6413 SideArgs::Client { -6414 token_store, -6415 server_name, -6416 } => Self::Client { -6417 token: token_store.take(&server_name).unwrap_or_default(), -6418 token_store, -6419 server_name, -6420 }, -6421 SideArgs::Server { -6422 server_config, -6423 pref_addr_cid: _, -6424 path_validated: _, -6425 } => Self::Server { server_config }, -6426 } -6427 } -6428} +6402/// Parameters to `Connection::new` specific to it being client-side or server-side +6403pub(crate) enum SideArgs { +6404 Client { +6405 token_store: Arc<dyn TokenStore>, +6406 server_name: String, +6407 }, +6408 Server { +6409 server_config: Arc<ServerConfig>, +6410 pref_addr_cid: Option<ConnectionId>, +6411 path_validated: bool, +6412 }, +6413} +6414 +6415impl SideArgs { +6416 pub(crate) fn pref_addr_cid(&self) -> Option<ConnectionId> { +6417 match *self { +6418 Self::Client { .. } => None, +6419 Self::Server { pref_addr_cid, .. } => pref_addr_cid, +6420 } +6421 } +6422 +6423 pub(crate) fn path_validated(&self) -> bool { +6424 match *self { +6425 Self::Client { .. } => true, +6426 Self::Server { path_validated, .. } => path_validated, +6427 } +6428 } 6429 -6430/// Parameters to `Connection::new` specific to it being client-side or server-side -6431pub(crate) enum SideArgs { -6432 Client { -6433 token_store: Arc<dyn TokenStore>, -6434 server_name: String, -6435 }, -6436 Server { -6437 server_config: Arc<ServerConfig>, -6438 pref_addr_cid: Option<ConnectionId>, -6439 path_validated: bool, -6440 }, -6441} -6442 -6443impl SideArgs { -6444 pub(crate) fn pref_addr_cid(&self) -> Option<ConnectionId> { -6445 match *self { -6446 Self::Client { .. } => None, -6447 Self::Server { pref_addr_cid, .. } => pref_addr_cid, -6448 } -6449 } -6450 -6451 pub(crate) fn path_validated(&self) -> bool { -6452 match *self { -6453 Self::Client { .. } => true, -6454 Self::Server { path_validated, .. } => path_validated, -6455 } -6456 } -6457 -6458 pub(crate) fn side(&self) -> Side { -6459 match *self { -6460 Self::Client { .. } => Side::Client, -6461 Self::Server { .. } => Side::Server, -6462 } -6463 } -6464} -6465 -6466/// Reasons why a connection might be lost -6467#[derive(Debug, Error, Clone, PartialEq, Eq)] -6468pub enum ConnectionError { -6469 /// The peer doesn't implement any supported version -6470 #[error("peer doesn't implement any supported version")] -6471 VersionMismatch, -6472 /// The peer violated the QUIC specification as understood by this implementation -6473 #[error(transparent)] -6474 TransportError(#[from] TransportError), -6475 /// The peer's QUIC stack aborted the connection automatically -6476 #[error("aborted by peer: {0}")] -6477 ConnectionClosed(frame::ConnectionClose), -6478 /// The peer closed the connection -6479 #[error("closed by peer: {0}")] -6480 ApplicationClosed(frame::ApplicationClose), -6481 /// The peer is unable to continue processing this connection, usually due to having restarted -6482 #[error("reset by peer")] -6483 Reset, -6484 /// Communication with the peer has lapsed for longer than the negotiated idle timeout -6485 /// -6486 /// If neither side is sending keep-alives, a connection will time out after a long enough idle -6487 /// period even if the peer is still reachable. See also [`TransportConfig::max_idle_timeout()`] -6488 /// and [`TransportConfig::keep_alive_interval()`]. -6489 #[error("timed out")] -6490 TimedOut, -6491 /// The local application closed the connection -6492 #[error("closed")] -6493 LocallyClosed, -6494 /// The connection could not be created because not enough of the CID space is available -6495 /// -6496 /// Try using longer connection IDs. -6497 #[error("CIDs exhausted")] -6498 CidsExhausted, -6499} -6500 -6501impl From<Close> for ConnectionError { -6502 fn from(x: Close) -> Self { -6503 match x { -6504 Close::Connection(reason) => Self::ConnectionClosed(reason), -6505 Close::Application(reason) => Self::ApplicationClosed(reason), -6506 } -6507 } -6508} -6509 -6510// For compatibility with API consumers -6511impl From<ConnectionError> for io::Error { -6512 fn from(x: ConnectionError) -> Self { -6513 use ConnectionError::*; -6514 let kind = match x { -6515 TimedOut => io::ErrorKind::TimedOut, -6516 Reset => io::ErrorKind::ConnectionReset, -6517 ApplicationClosed(_) | ConnectionClosed(_) => io::ErrorKind::ConnectionAborted, -6518 TransportError(_) | VersionMismatch | LocallyClosed | CidsExhausted => { -6519 io::ErrorKind::Other -6520 } -6521 }; -6522 Self::new(kind, x) -6523 } -6524} -6525 -6526/// Errors that might trigger a path being closed -6527// TODO(@divma): maybe needs to be reworked based on what we want to do with the public API -6528#[derive(Debug, Error, PartialEq, Eq, Clone, Copy)] -6529pub enum PathError { -6530 /// The extension was not negotiated with the peer -6531 #[error("multipath extension not negotiated")] -6532 MultipathNotNegotiated, -6533 /// Paths can only be opened client-side -6534 #[error("the server side may not open a path")] -6535 ServerSideNotAllowed, -6536 /// Current limits do not allow us to open more paths -6537 #[error("maximum number of concurrent paths reached")] -6538 MaxPathIdReached, -6539 /// No remote CIDs available to open a new path -6540 #[error("remoted CIDs exhausted")] -6541 RemoteCidsExhausted, -6542 /// Path could not be validated and will be abandoned -6543 #[error("path validation failed")] -6544 ValidationFailed, -6545 /// The remote address for the path is not supported by the endpoint -6546 #[error("invalid remote address")] -6547 InvalidRemoteAddress(SocketAddr), -6548} -6549 -6550/// Errors triggered when abandoning a path -6551#[derive(Debug, Error, Clone, Eq, PartialEq)] -6552pub enum ClosePathError { -6553 /// The path is already closed or was never opened -6554 #[error("closed path")] -6555 ClosedPath, -6556 /// This is the last path, which can not be abandoned -6557 #[error("last open path")] -6558 LastOpenPath, -6559} -6560 -6561#[derive(Debug, Error, Clone, Copy)] -6562#[error("Multipath extension not negotiated")] -6563pub struct MultipathNotNegotiated { -6564 _private: (), +6430 pub(crate) fn side(&self) -> Side { +6431 match *self { +6432 Self::Client { .. } => Side::Client, +6433 Self::Server { .. } => Side::Server, +6434 } +6435 } +6436} +6437 +6438/// Reasons why a connection might be lost +6439#[derive(Debug, Error, Clone, PartialEq, Eq)] +6440pub enum ConnectionError { +6441 /// The peer doesn't implement any supported version +6442 #[error("peer doesn't implement any supported version")] +6443 VersionMismatch, +6444 /// The peer violated the QUIC specification as understood by this implementation +6445 #[error(transparent)] +6446 TransportError(#[from] TransportError), +6447 /// The peer's QUIC stack aborted the connection automatically +6448 #[error("aborted by peer: {0}")] +6449 ConnectionClosed(frame::ConnectionClose), +6450 /// The peer closed the connection +6451 #[error("closed by peer: {0}")] +6452 ApplicationClosed(frame::ApplicationClose), +6453 /// The peer is unable to continue processing this connection, usually due to having restarted +6454 #[error("reset by peer")] +6455 Reset, +6456 /// Communication with the peer has lapsed for longer than the negotiated idle timeout +6457 /// +6458 /// If neither side is sending keep-alives, a connection will time out after a long enough idle +6459 /// period even if the peer is still reachable. See also [`TransportConfig::max_idle_timeout()`] +6460 /// and [`TransportConfig::keep_alive_interval()`]. +6461 #[error("timed out")] +6462 TimedOut, +6463 /// The local application closed the connection +6464 #[error("closed")] +6465 LocallyClosed, +6466 /// The connection could not be created because not enough of the CID space is available +6467 /// +6468 /// Try using longer connection IDs. +6469 #[error("CIDs exhausted")] +6470 CidsExhausted, +6471} +6472 +6473impl From<Close> for ConnectionError { +6474 fn from(x: Close) -> Self { +6475 match x { +6476 Close::Connection(reason) => Self::ConnectionClosed(reason), +6477 Close::Application(reason) => Self::ApplicationClosed(reason), +6478 } +6479 } +6480} +6481 +6482// For compatibility with API consumers +6483impl From<ConnectionError> for io::Error { +6484 fn from(x: ConnectionError) -> Self { +6485 use ConnectionError::*; +6486 let kind = match x { +6487 TimedOut => io::ErrorKind::TimedOut, +6488 Reset => io::ErrorKind::ConnectionReset, +6489 ApplicationClosed(_) | ConnectionClosed(_) => io::ErrorKind::ConnectionAborted, +6490 TransportError(_) | VersionMismatch | LocallyClosed | CidsExhausted => { +6491 io::ErrorKind::Other +6492 } +6493 }; +6494 Self::new(kind, x) +6495 } +6496} +6497 +6498/// Errors that might trigger a path being closed +6499// TODO(@divma): maybe needs to be reworked based on what we want to do with the public API +6500#[derive(Debug, Error, PartialEq, Eq, Clone, Copy)] +6501pub enum PathError { +6502 /// The extension was not negotiated with the peer +6503 #[error("multipath extension not negotiated")] +6504 MultipathNotNegotiated, +6505 /// Paths can only be opened client-side +6506 #[error("the server side may not open a path")] +6507 ServerSideNotAllowed, +6508 /// Current limits do not allow us to open more paths +6509 #[error("maximum number of concurrent paths reached")] +6510 MaxPathIdReached, +6511 /// No remote CIDs available to open a new path +6512 #[error("remoted CIDs exhausted")] +6513 RemoteCidsExhausted, +6514 /// Path could not be validated and will be abandoned +6515 #[error("path validation failed")] +6516 ValidationFailed, +6517 /// The remote address for the path is not supported by the endpoint +6518 #[error("invalid remote address")] +6519 InvalidRemoteAddress(SocketAddr), +6520} +6521 +6522/// Errors triggered when abandoning a path +6523#[derive(Debug, Error, Clone, Eq, PartialEq)] +6524pub enum ClosePathError { +6525 /// The path is already closed or was never opened +6526 #[error("closed path")] +6527 ClosedPath, +6528 /// This is the last path, which can not be abandoned +6529 #[error("last open path")] +6530 LastOpenPath, +6531} +6532 +6533#[derive(Debug, Error, Clone, Copy)] +6534#[error("Multipath extension not negotiated")] +6535pub struct MultipathNotNegotiated { +6536 _private: (), +6537} +6538 +6539/// Events of interest to the application +6540#[derive(Debug)] +6541pub enum Event { +6542 /// The connection's handshake data is ready +6543 HandshakeDataReady, +6544 /// The connection was successfully established +6545 Connected, +6546 /// The TLS handshake was confirmed +6547 HandshakeConfirmed, +6548 /// The connection was lost +6549 /// +6550 /// Emitted if the peer closes the connection or an error is encountered. +6551 ConnectionLost { +6552 /// Reason that the connection was closed +6553 reason: ConnectionError, +6554 }, +6555 /// Stream events +6556 Stream(StreamEvent), +6557 /// One or more application datagrams have been received +6558 DatagramReceived, +6559 /// One or more application datagrams have been sent after blocking +6560 DatagramsUnblocked, +6561 /// (Multi)Path events +6562 Path(PathEvent), +6563 /// Iroh's nat traversal events +6564 NatTraversal(iroh_hp::Event), 6565} 6566 -6567/// Events of interest to the application -6568#[derive(Debug)] -6569pub enum Event { -6570 /// The connection's handshake data is ready -6571 HandshakeDataReady, -6572 /// The connection was successfully established -6573 Connected, -6574 /// The TLS handshake was confirmed -6575 HandshakeConfirmed, -6576 /// The connection was lost -6577 /// -6578 /// Emitted if the peer closes the connection or an error is encountered. -6579 ConnectionLost { -6580 /// Reason that the connection was closed -6581 reason: ConnectionError, -6582 }, -6583 /// Stream events -6584 Stream(StreamEvent), -6585 /// One or more application datagrams have been received -6586 DatagramReceived, -6587 /// One or more application datagrams have been sent after blocking -6588 DatagramsUnblocked, -6589 /// (Multi)Path events -6590 Path(PathEvent), -6591 /// Iroh's nat traversal events -6592 NatTraversal(iroh_hp::Event), -6593} -6594 -6595impl From<PathEvent> for Event { -6596 fn from(source: PathEvent) -> Self { -6597 Self::Path(source) -6598 } -6599} -6600 -6601fn get_max_ack_delay(params: &TransportParameters) -> Duration { -6602 Duration::from_micros(params.max_ack_delay.0 * 1000) -6603} -6604 -6605// Prevents overflow and improves behavior in extreme circumstances -6606const MAX_BACKOFF_EXPONENT: u32 = 16; -6607 -6608/// Minimal remaining size to allow packet coalescing, excluding cryptographic tag -6609/// -6610/// This must be at least as large as the header for a well-formed empty packet to be coalesced, -6611/// plus some space for frames. We only care about handshake headers because short header packets -6612/// necessarily have smaller headers, and initial packets are only ever the first packet in a -6613/// datagram (because we coalesce in ascending packet space order and the only reason to split a -6614/// packet is when packet space changes). -6615const MIN_PACKET_SPACE: usize = MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE + 32; -6616 -6617/// Largest amount of space that could be occupied by a Handshake or 0-RTT packet's header -6618/// -6619/// Excludes packet-type-specific fields such as packet number or Initial token -6620// https://www.rfc-editor.org/rfc/rfc9000.html#name-0-rtt: flags + version + dcid len + dcid + -6621// scid len + scid + length + pn -6622const MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE: usize = -6623 1 + 4 + 1 + MAX_CID_SIZE + 1 + MAX_CID_SIZE + VarInt::from_u32(u16::MAX as u32).size() + 4; -6624 -6625/// Perform key updates this many packets before the AEAD confidentiality limit. -6626/// -6627/// Chosen arbitrarily, intended to be large enough to prevent spurious connection loss. -6628const KEY_UPDATE_MARGIN: u64 = 10_000; -6629 -6630#[derive(Default)] -6631struct SentFrames { -6632 retransmits: ThinRetransmits, -6633 /// The packet number of the largest acknowledged packet for each path -6634 largest_acked: FxHashMap<PathId, u64>, -6635 stream_frames: StreamMetaVec, -6636 /// Whether the packet contains non-retransmittable frames (like datagrams) -6637 non_retransmits: bool, -6638 /// If the datagram containing these frames should be padded to the min MTU -6639 requires_padding: bool, -6640} -6641 -6642impl SentFrames { -6643 /// Returns whether the packet contains only ACKs -6644 fn is_ack_only(&self, streams: &StreamsState) -> bool { -6645 !self.largest_acked.is_empty() -6646 && !self.non_retransmits -6647 && self.stream_frames.is_empty() -6648 && self.retransmits.is_empty(streams) -6649 } -6650} -6651 -6652/// Compute the negotiated idle timeout based on local and remote max_idle_timeout transport parameters. -6653/// -6654/// According to the definition of max_idle_timeout, a value of `0` means the timeout is disabled; see <https://www.rfc-editor.org/rfc/rfc9000#section-18.2-4.4.1.> -6655/// -6656/// According to the negotiation procedure, either the minimum of the timeouts or one specified is used as the negotiated value; see <https://www.rfc-editor.org/rfc/rfc9000#section-10.1-2.> -6657/// -6658/// Returns the negotiated idle timeout as a `Duration`, or `None` when both endpoints have opted out of idle timeout. -6659fn negotiate_max_idle_timeout(x: Option<VarInt>, y: Option<VarInt>) -> Option<Duration> { -6660 match (x, y) { -6661 (Some(VarInt(0)) | None, Some(VarInt(0)) | None) => None, -6662 (Some(VarInt(0)) | None, Some(y)) => Some(Duration::from_millis(y.0)), -6663 (Some(x), Some(VarInt(0)) | None) => Some(Duration::from_millis(x.0)), -6664 (Some(x), Some(y)) => Some(Duration::from_millis(cmp::min(x, y).0)), -6665 } -6666} -6667 -6668#[cfg(test)] -6669mod tests { -6670 use super::*; -6671 -6672 #[test] -6673 fn negotiate_max_idle_timeout_commutative() { -6674 let test_params = [ -6675 (None, None, None), -6676 (None, Some(VarInt(0)), None), -6677 (None, Some(VarInt(2)), Some(Duration::from_millis(2))), -6678 (Some(VarInt(0)), Some(VarInt(0)), None), -6679 ( -6680 Some(VarInt(2)), -6681 Some(VarInt(0)), -6682 Some(Duration::from_millis(2)), -6683 ), -6684 ( -6685 Some(VarInt(1)), -6686 Some(VarInt(4)), -6687 Some(Duration::from_millis(1)), -6688 ), -6689 ]; -6690 -6691 for (left, right, result) in test_params { -6692 assert_eq!(negotiate_max_idle_timeout(left, right), result); -6693 assert_eq!(negotiate_max_idle_timeout(right, left), result); -6694 } -6695 } -6696}

\ No newline at end of file +6567impl From<PathEvent> for Event { +6568 fn from(source: PathEvent) -> Self { +6569 Self::Path(source) +6570 } +6571} +6572 +6573fn get_max_ack_delay(params: &TransportParameters) -> Duration { +6574 Duration::from_micros(params.max_ack_delay.0 * 1000) +6575} +6576 +6577// Prevents overflow and improves behavior in extreme circumstances +6578const MAX_BACKOFF_EXPONENT: u32 = 16; +6579 +6580/// Minimal remaining size to allow packet coalescing, excluding cryptographic tag +6581/// +6582/// This must be at least as large as the header for a well-formed empty packet to be coalesced, +6583/// plus some space for frames. We only care about handshake headers because short header packets +6584/// necessarily have smaller headers, and initial packets are only ever the first packet in a +6585/// datagram (because we coalesce in ascending packet space order and the only reason to split a +6586/// packet is when packet space changes). +6587const MIN_PACKET_SPACE: usize = MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE + 32; +6588 +6589/// Largest amount of space that could be occupied by a Handshake or 0-RTT packet's header +6590/// +6591/// Excludes packet-type-specific fields such as packet number or Initial token +6592// https://www.rfc-editor.org/rfc/rfc9000.html#name-0-rtt: flags + version + dcid len + dcid + +6593// scid len + scid + length + pn +6594const MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE: usize = +6595 1 + 4 + 1 + MAX_CID_SIZE + 1 + MAX_CID_SIZE + VarInt::from_u32(u16::MAX as u32).size() + 4; +6596 +6597/// Perform key updates this many packets before the AEAD confidentiality limit. +6598/// +6599/// Chosen arbitrarily, intended to be large enough to prevent spurious connection loss. +6600const KEY_UPDATE_MARGIN: u64 = 10_000; +6601 +6602#[derive(Default)] +6603struct SentFrames { +6604 retransmits: ThinRetransmits, +6605 /// The packet number of the largest acknowledged packet for each path +6606 largest_acked: FxHashMap<PathId, u64>, +6607 stream_frames: StreamMetaVec, +6608 /// Whether the packet contains non-retransmittable frames (like datagrams) +6609 non_retransmits: bool, +6610 /// If the datagram containing these frames should be padded to the min MTU +6611 requires_padding: bool, +6612} +6613 +6614impl SentFrames { +6615 /// Returns whether the packet contains only ACKs +6616 fn is_ack_only(&self, streams: &StreamsState) -> bool { +6617 !self.largest_acked.is_empty() +6618 && !self.non_retransmits +6619 && self.stream_frames.is_empty() +6620 && self.retransmits.is_empty(streams) +6621 } +6622} +6623 +6624/// Compute the negotiated idle timeout based on local and remote max_idle_timeout transport parameters. +6625/// +6626/// According to the definition of max_idle_timeout, a value of `0` means the timeout is disabled; see <https://www.rfc-editor.org/rfc/rfc9000#section-18.2-4.4.1.> +6627/// +6628/// According to the negotiation procedure, either the minimum of the timeouts or one specified is used as the negotiated value; see <https://www.rfc-editor.org/rfc/rfc9000#section-10.1-2.> +6629/// +6630/// Returns the negotiated idle timeout as a `Duration`, or `None` when both endpoints have opted out of idle timeout. +6631fn negotiate_max_idle_timeout(x: Option<VarInt>, y: Option<VarInt>) -> Option<Duration> { +6632 match (x, y) { +6633 (Some(VarInt(0)) | None, Some(VarInt(0)) | None) => None, +6634 (Some(VarInt(0)) | None, Some(y)) => Some(Duration::from_millis(y.0)), +6635 (Some(x), Some(VarInt(0)) | None) => Some(Duration::from_millis(x.0)), +6636 (Some(x), Some(y)) => Some(Duration::from_millis(cmp::min(x, y).0)), +6637 } +6638} +6639 +6640#[cfg(test)] +6641mod tests { +6642 use super::*; +6643 +6644 #[test] +6645 fn negotiate_max_idle_timeout_commutative() { +6646 let test_params = [ +6647 (None, None, None), +6648 (None, Some(VarInt(0)), None), +6649 (None, Some(VarInt(2)), Some(Duration::from_millis(2))), +6650 (Some(VarInt(0)), Some(VarInt(0)), None), +6651 ( +6652 Some(VarInt(2)), +6653 Some(VarInt(0)), +6654 Some(Duration::from_millis(2)), +6655 ), +6656 ( +6657 Some(VarInt(1)), +6658 Some(VarInt(4)), +6659 Some(Duration::from_millis(1)), +6660 ), +6661 ]; +6662 +6663 for (left, right, result) in test_params { +6664 assert_eq!(negotiate_max_idle_timeout(left, right), result); +6665 assert_eq!(negotiate_max_idle_timeout(right, left), result); +6666 } +6667 } +6668}

\ No newline at end of file diff --git a/pr/259/docs/src/iroh_quinn_proto/connection/paths.rs.html b/pr/259/docs/src/iroh_quinn_proto/connection/paths.rs.html index 32903016e..92e594a40 100644 --- a/pr/259/docs/src/iroh_quinn_proto/connection/paths.rs.html +++ b/pr/259/docs/src/iroh_quinn_proto/connection/paths.rs.html @@ -385,578 +385,565 @@ 385 } 386 } 387 -388 /// The earliest time at which a sent challenge is considered lost. -389 pub(super) fn earliest_expiring_challenge(&self) -> Option<Instant> { -390 if self.challenges_sent.is_empty() { -391 return None; -392 } -393 let pto = self.rtt.pto_base(); -394 self.challenges_sent -395 .values() -396 .map(|info| info.sent_instant) -397 .min() -398 .map(|sent_instant| sent_instant + pto) -399 } -400 -401 /// Handle receiving a PATH_RESPONSE. -402 pub(super) fn on_path_response_received( -403 &mut self, -404 now: Instant, -405 token: u64, -406 remote: SocketAddr, -407 ) -> OnPathResponseReceived { -408 match self.challenges_sent.get(&token) { -409 // Response to an on-path PathChallenge -410 Some(info) if info.remote == remote && self.remote == remote => { -411 let sent_instant = info.sent_instant; -412 if !std::mem::replace(&mut self.validated, true) { -413 trace!("new path validated"); -414 } -415 // Clear any other on-path sent challenge. -416 self.challenges_sent -417 .retain(|_token, info| info.remote != remote); -418 -419 self.send_new_challenge = false; -420 -421 // This RTT can only be used for the initial RTT, not as a normal -422 // sample: https://www.rfc-editor.org/rfc/rfc9002#section-6.2.2-2. -423 let rtt = now.saturating_duration_since(sent_instant); -424 self.rtt.reset_initial_rtt(rtt); -425 -426 // mark the path as open from the application perspective now that Opened -427 // event has been queued -428 let was_open = std::mem::replace(&mut self.open, true); -429 OnPathResponseReceived::OnPath { was_open } -430 } -431 // Response to an off-path PathChallenge -432 Some(info) if info.remote == remote => { -433 self.challenges_sent -434 .retain(|_token, info| info.remote != remote); -435 OnPathResponseReceived::OffPath -436 } -437 // Response to a PathChallenge we recognize, but from an invalid remote -438 Some(info) => OnPathResponseReceived::Invalid { -439 expected: info.remote, -440 }, -441 // Response to an unknown PathChallenge -442 None => OnPathResponseReceived::Unknown, -443 } -444 } -445 -446 #[cfg(feature = "qlog")] -447 pub(super) fn qlog_recovery_metrics( -448 &mut self, -449 path_id: PathId, -450 ) -> Option<RecoveryMetricsUpdated> { -451 let controller_metrics = self.congestion.metrics(); -452 -453 let metrics = RecoveryMetrics { -454 min_rtt: Some(self.rtt.min), -455 smoothed_rtt: Some(self.rtt.get()), -456 latest_rtt: Some(self.rtt.latest), -457 rtt_variance: Some(self.rtt.var), -458 pto_count: Some(self.pto_count), -459 bytes_in_flight: Some(self.in_flight.bytes), -460 packets_in_flight: Some(self.in_flight.ack_eliciting), -461 -462 congestion_window: Some(controller_metrics.congestion_window), -463 ssthresh: controller_metrics.ssthresh, -464 pacing_rate: controller_metrics.pacing_rate, -465 }; -466 -467 let event = metrics.to_qlog_event(path_id, &self.recovery_metrics); -468 self.recovery_metrics = metrics; -469 event -470 } -471 -472 /// Return how long we need to wait before sending `bytes_to_send` -473 /// -474 /// See [`Pacer::delay`]. -475 pub(super) fn pacing_delay(&mut self, bytes_to_send: u64, now: Instant) -> Option<Instant> { -476 let smoothed_rtt = self.rtt.get(); -477 self.pacing.delay( -478 smoothed_rtt, -479 bytes_to_send, -480 self.current_mtu(), -481 self.congestion.window(), -482 now, -483 ) -484 } -485 -486 /// Updates the last observed address report received on this path. -487 /// -488 /// If the address was updated, it's returned to be informed to the application. -489 #[must_use = "updated observed address must be reported to the application"] -490 pub(super) fn update_observed_addr_report( -491 &mut self, -492 observed: ObservedAddr, -493 ) -> Option<SocketAddr> { -494 match self.last_observed_addr_report.as_mut() { -495 Some(prev) => { -496 if prev.seq_no >= observed.seq_no { -497 // frames that do not increase the sequence number on this path are ignored -498 None -499 } else if prev.ip == observed.ip && prev.port == observed.port { -500 // keep track of the last seq_no but do not report the address as updated -501 prev.seq_no = observed.seq_no; -502 None -503 } else { -504 let addr = observed.socket_addr(); -505 self.last_observed_addr_report = Some(observed); -506 Some(addr) -507 } -508 } -509 None => { -510 let addr = observed.socket_addr(); -511 self.last_observed_addr_report = Some(observed); -512 Some(addr) -513 } -514 } -515 } +388 /// Handle receiving a PATH_RESPONSE. +389 pub(super) fn on_path_response_received( +390 &mut self, +391 now: Instant, +392 token: u64, +393 remote: SocketAddr, +394 ) -> OnPathResponseReceived { +395 match self.challenges_sent.get(&token) { +396 // Response to an on-path PathChallenge +397 Some(info) if info.remote == remote && self.remote == remote => { +398 let sent_instant = info.sent_instant; +399 if !std::mem::replace(&mut self.validated, true) { +400 trace!("new path validated"); +401 } +402 // Clear any other on-path sent challenge. +403 self.challenges_sent +404 .retain(|_token, info| info.remote != remote); +405 +406 self.send_new_challenge = false; +407 +408 // This RTT can only be used for the initial RTT, not as a normal +409 // sample: https://www.rfc-editor.org/rfc/rfc9002#section-6.2.2-2. +410 let rtt = now.saturating_duration_since(sent_instant); +411 self.rtt.reset_initial_rtt(rtt); +412 +413 // mark the path as open from the application perspective now that Opened +414 // event has been queued +415 let was_open = std::mem::replace(&mut self.open, true); +416 OnPathResponseReceived::OnPath { was_open } +417 } +418 // Response to an off-path PathChallenge +419 Some(info) if info.remote == remote => { +420 self.challenges_sent +421 .retain(|_token, info| info.remote != remote); +422 OnPathResponseReceived::OffPath +423 } +424 // Response to a PathChallenge we recognize, but from an invalid remote +425 Some(info) => OnPathResponseReceived::Invalid { +426 expected: info.remote, +427 }, +428 // Response to an unknown PathChallenge +429 None => OnPathResponseReceived::Unknown, +430 } +431 } +432 +433 #[cfg(feature = "qlog")] +434 pub(super) fn qlog_recovery_metrics( +435 &mut self, +436 path_id: PathId, +437 ) -> Option<RecoveryMetricsUpdated> { +438 let controller_metrics = self.congestion.metrics(); +439 +440 let metrics = RecoveryMetrics { +441 min_rtt: Some(self.rtt.min), +442 smoothed_rtt: Some(self.rtt.get()), +443 latest_rtt: Some(self.rtt.latest), +444 rtt_variance: Some(self.rtt.var), +445 pto_count: Some(self.pto_count), +446 bytes_in_flight: Some(self.in_flight.bytes), +447 packets_in_flight: Some(self.in_flight.ack_eliciting), +448 +449 congestion_window: Some(controller_metrics.congestion_window), +450 ssthresh: controller_metrics.ssthresh, +451 pacing_rate: controller_metrics.pacing_rate, +452 }; +453 +454 let event = metrics.to_qlog_event(path_id, &self.recovery_metrics); +455 self.recovery_metrics = metrics; +456 event +457 } +458 +459 /// Return how long we need to wait before sending `bytes_to_send` +460 /// +461 /// See [`Pacer::delay`]. +462 pub(super) fn pacing_delay(&mut self, bytes_to_send: u64, now: Instant) -> Option<Instant> { +463 let smoothed_rtt = self.rtt.get(); +464 self.pacing.delay( +465 smoothed_rtt, +466 bytes_to_send, +467 self.current_mtu(), +468 self.congestion.window(), +469 now, +470 ) +471 } +472 +473 /// Updates the last observed address report received on this path. +474 /// +475 /// If the address was updated, it's returned to be informed to the application. +476 #[must_use = "updated observed address must be reported to the application"] +477 pub(super) fn update_observed_addr_report( +478 &mut self, +479 observed: ObservedAddr, +480 ) -> Option<SocketAddr> { +481 match self.last_observed_addr_report.as_mut() { +482 Some(prev) => { +483 if prev.seq_no >= observed.seq_no { +484 // frames that do not increase the sequence number on this path are ignored +485 None +486 } else if prev.ip == observed.ip && prev.port == observed.port { +487 // keep track of the last seq_no but do not report the address as updated +488 prev.seq_no = observed.seq_no; +489 None +490 } else { +491 let addr = observed.socket_addr(); +492 self.last_observed_addr_report = Some(observed); +493 Some(addr) +494 } +495 } +496 None => { +497 let addr = observed.socket_addr(); +498 self.last_observed_addr_report = Some(observed); +499 Some(addr) +500 } +501 } +502 } +503 +504 pub(crate) fn remote_status(&self) -> Option<PathStatus> { +505 self.status.remote_status.map(|(_seq, status)| status) +506 } +507 +508 pub(crate) fn local_status(&self) -> PathStatus { +509 self.status.local_status +510 } +511 +512 pub(super) fn generation(&self) -> u64 { +513 self.generation +514 } +515} 516 -517 pub(crate) fn remote_status(&self) -> Option<PathStatus> { -518 self.status.remote_status.map(|(_seq, status)| status) -519 } -520 -521 pub(crate) fn local_status(&self) -> PathStatus { -522 self.status.local_status -523 } -524 -525 pub(super) fn generation(&self) -> u64 { -526 self.generation -527 } -528} -529 -530pub(super) enum OnPathResponseReceived { -531 /// This response validates the path on it's current remote address. -532 OnPath { was_open: bool }, -533 /// This response is valid, but it's for a remote other than the path's current remote address. -534 OffPath, -535 /// The received token is unknown. -536 Unknown, -537 /// The response is invalid. -538 Invalid { -539 /// The remote that was expected for this token -540 expected: SocketAddr, -541 }, -542} -543 -544/// Congestion metrics as described in [`recovery_metrics_updated`]. -545/// -546/// [`recovery_metrics_updated`]: https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-quic-events.html#name-recovery_metrics_updated -547#[cfg(feature = "qlog")] -548#[derive(Default, Clone, PartialEq, Debug)] -549#[non_exhaustive] -550struct RecoveryMetrics { -551 pub min_rtt: Option<Duration>, -552 pub smoothed_rtt: Option<Duration>, -553 pub latest_rtt: Option<Duration>, -554 pub rtt_variance: Option<Duration>, -555 pub pto_count: Option<u32>, -556 pub bytes_in_flight: Option<u64>, -557 pub packets_in_flight: Option<u64>, -558 pub congestion_window: Option<u64>, -559 pub ssthresh: Option<u64>, -560 pub pacing_rate: Option<u64>, -561} -562 -563#[cfg(feature = "qlog")] -564impl RecoveryMetrics { -565 /// Retain only values that have been updated since the last snapshot. -566 fn retain_updated(&self, previous: &Self) -> Self { -567 macro_rules! keep_if_changed { -568 ($name:ident) => { -569 if previous.$name == self.$name { -570 None -571 } else { -572 self.$name -573 } -574 }; +517pub(super) enum OnPathResponseReceived { +518 /// This response validates the path on it's current remote address. +519 OnPath { was_open: bool }, +520 /// This response is valid, but it's for a remote other than the path's current remote address. +521 OffPath, +522 /// The received token is unknown. +523 Unknown, +524 /// The response is invalid. +525 Invalid { +526 /// The remote that was expected for this token +527 expected: SocketAddr, +528 }, +529} +530 +531/// Congestion metrics as described in [`recovery_metrics_updated`]. +532/// +533/// [`recovery_metrics_updated`]: https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-quic-events.html#name-recovery_metrics_updated +534#[cfg(feature = "qlog")] +535#[derive(Default, Clone, PartialEq, Debug)] +536#[non_exhaustive] +537struct RecoveryMetrics { +538 pub min_rtt: Option<Duration>, +539 pub smoothed_rtt: Option<Duration>, +540 pub latest_rtt: Option<Duration>, +541 pub rtt_variance: Option<Duration>, +542 pub pto_count: Option<u32>, +543 pub bytes_in_flight: Option<u64>, +544 pub packets_in_flight: Option<u64>, +545 pub congestion_window: Option<u64>, +546 pub ssthresh: Option<u64>, +547 pub pacing_rate: Option<u64>, +548} +549 +550#[cfg(feature = "qlog")] +551impl RecoveryMetrics { +552 /// Retain only values that have been updated since the last snapshot. +553 fn retain_updated(&self, previous: &Self) -> Self { +554 macro_rules! keep_if_changed { +555 ($name:ident) => { +556 if previous.$name == self.$name { +557 None +558 } else { +559 self.$name +560 } +561 }; +562 } +563 +564 Self { +565 min_rtt: keep_if_changed!(min_rtt), +566 smoothed_rtt: keep_if_changed!(smoothed_rtt), +567 latest_rtt: keep_if_changed!(latest_rtt), +568 rtt_variance: keep_if_changed!(rtt_variance), +569 pto_count: keep_if_changed!(pto_count), +570 bytes_in_flight: keep_if_changed!(bytes_in_flight), +571 packets_in_flight: keep_if_changed!(packets_in_flight), +572 congestion_window: keep_if_changed!(congestion_window), +573 ssthresh: keep_if_changed!(ssthresh), +574 pacing_rate: keep_if_changed!(pacing_rate), 575 } -576 -577 Self { -578 min_rtt: keep_if_changed!(min_rtt), -579 smoothed_rtt: keep_if_changed!(smoothed_rtt), -580 latest_rtt: keep_if_changed!(latest_rtt), -581 rtt_variance: keep_if_changed!(rtt_variance), -582 pto_count: keep_if_changed!(pto_count), -583 bytes_in_flight: keep_if_changed!(bytes_in_flight), -584 packets_in_flight: keep_if_changed!(packets_in_flight), -585 congestion_window: keep_if_changed!(congestion_window), -586 ssthresh: keep_if_changed!(ssthresh), -587 pacing_rate: keep_if_changed!(pacing_rate), -588 } -589 } -590 -591 /// Emit a `MetricsUpdated` event containing only updated values -592 fn to_qlog_event(&self, path_id: PathId, previous: &Self) -> Option<RecoveryMetricsUpdated> { -593 let updated = self.retain_updated(previous); -594 -595 if updated == Self::default() { -596 return None; -597 } -598 -599 Some(RecoveryMetricsUpdated { -600 min_rtt: updated.min_rtt.map(|rtt| rtt.as_secs_f32()), -601 smoothed_rtt: updated.smoothed_rtt.map(|rtt| rtt.as_secs_f32()), -602 latest_rtt: updated.latest_rtt.map(|rtt| rtt.as_secs_f32()), -603 rtt_variance: updated.rtt_variance.map(|rtt| rtt.as_secs_f32()), -604 pto_count: updated -605 .pto_count -606 .map(|count| count.try_into().unwrap_or(u16::MAX)), -607 bytes_in_flight: updated.bytes_in_flight, -608 packets_in_flight: updated.packets_in_flight, -609 congestion_window: updated.congestion_window, -610 ssthresh: updated.ssthresh, -611 pacing_rate: updated.pacing_rate, -612 path_id: Some(path_id.as_u32() as u64), -613 }) -614 } +576 } +577 +578 /// Emit a `MetricsUpdated` event containing only updated values +579 fn to_qlog_event(&self, path_id: PathId, previous: &Self) -> Option<RecoveryMetricsUpdated> { +580 let updated = self.retain_updated(previous); +581 +582 if updated == Self::default() { +583 return None; +584 } +585 +586 Some(RecoveryMetricsUpdated { +587 min_rtt: updated.min_rtt.map(|rtt| rtt.as_secs_f32()), +588 smoothed_rtt: updated.smoothed_rtt.map(|rtt| rtt.as_secs_f32()), +589 latest_rtt: updated.latest_rtt.map(|rtt| rtt.as_secs_f32()), +590 rtt_variance: updated.rtt_variance.map(|rtt| rtt.as_secs_f32()), +591 pto_count: updated +592 .pto_count +593 .map(|count| count.try_into().unwrap_or(u16::MAX)), +594 bytes_in_flight: updated.bytes_in_flight, +595 packets_in_flight: updated.packets_in_flight, +596 congestion_window: updated.congestion_window, +597 ssthresh: updated.ssthresh, +598 pacing_rate: updated.pacing_rate, +599 path_id: Some(path_id.as_u32() as u64), +600 }) +601 } +602} +603 +604/// RTT estimation for a particular network path +605#[derive(Copy, Clone, Debug)] +606pub struct RttEstimator { +607 /// The most recent RTT measurement made when receiving an ack for a previously unacked packet +608 latest: Duration, +609 /// The smoothed RTT of the connection, computed as described in RFC6298 +610 smoothed: Option<Duration>, +611 /// The RTT variance, computed as described in RFC6298 +612 var: Duration, +613 /// The minimum RTT seen in the connection, ignoring ack delay. +614 min: Duration, 615} 616 -617/// RTT estimation for a particular network path -618#[derive(Copy, Clone, Debug)] -619pub struct RttEstimator { -620 /// The most recent RTT measurement made when receiving an ack for a previously unacked packet -621 latest: Duration, -622 /// The smoothed RTT of the connection, computed as described in RFC6298 -623 smoothed: Option<Duration>, -624 /// The RTT variance, computed as described in RFC6298 -625 var: Duration, -626 /// The minimum RTT seen in the connection, ignoring ack delay. -627 min: Duration, -628} -629 -630impl RttEstimator { -631 pub(super) fn new(initial_rtt: Duration) -> Self { -632 Self { -633 latest: initial_rtt, -634 smoothed: None, -635 var: initial_rtt / 2, -636 min: initial_rtt, -637 } -638 } -639 -640 /// Resets the estimator using a new initial_rtt value. -641 /// -642 /// This only resets the initial_rtt **if** no samples have been recorded yet. If there -643 /// are any recorded samples the initial estimate can not be adjusted after the fact. -644 /// -645 /// This is useful when you receive a PATH_RESPONSE in the first packet received on a -646 /// new path. In this case you can use the delay of the PATH_CHALLENGE-PATH_RESPONSE as -647 /// the initial RTT to get a better expected estimation. -648 /// -649 /// A PATH_CHALLENGE-PATH_RESPONSE pair later in the connection should not be used -650 /// explicitly as an estimation since PATH_CHALLENGE is an ACK-eliciting packet itself -651 /// already. -652 pub(crate) fn reset_initial_rtt(&mut self, initial_rtt: Duration) { -653 if self.smoothed.is_none() { -654 self.latest = initial_rtt; -655 self.var = initial_rtt / 2; -656 self.min = initial_rtt; -657 } +617impl RttEstimator { +618 pub(super) fn new(initial_rtt: Duration) -> Self { +619 Self { +620 latest: initial_rtt, +621 smoothed: None, +622 var: initial_rtt / 2, +623 min: initial_rtt, +624 } +625 } +626 +627 /// Resets the estimator using a new initial_rtt value. +628 /// +629 /// This only resets the initial_rtt **if** no samples have been recorded yet. If there +630 /// are any recorded samples the initial estimate can not be adjusted after the fact. +631 /// +632 /// This is useful when you receive a PATH_RESPONSE in the first packet received on a +633 /// new path. In this case you can use the delay of the PATH_CHALLENGE-PATH_RESPONSE as +634 /// the initial RTT to get a better expected estimation. +635 /// +636 /// A PATH_CHALLENGE-PATH_RESPONSE pair later in the connection should not be used +637 /// explicitly as an estimation since PATH_CHALLENGE is an ACK-eliciting packet itself +638 /// already. +639 pub(crate) fn reset_initial_rtt(&mut self, initial_rtt: Duration) { +640 if self.smoothed.is_none() { +641 self.latest = initial_rtt; +642 self.var = initial_rtt / 2; +643 self.min = initial_rtt; +644 } +645 } +646 +647 /// The current best RTT estimation. +648 pub fn get(&self) -> Duration { +649 self.smoothed.unwrap_or(self.latest) +650 } +651 +652 /// Conservative estimate of RTT +653 /// +654 /// Takes the maximum of smoothed and latest RTT, as recommended +655 /// in 6.1.2 of the recovery spec (draft 29). +656 pub fn conservative(&self) -> Duration { +657 self.get().max(self.latest) 658 } 659 -660 /// The current best RTT estimation. -661 pub fn get(&self) -> Duration { -662 self.smoothed.unwrap_or(self.latest) +660 /// Minimum RTT registered so far for this estimator. +661 pub fn min(&self) -> Duration { +662 self.min 663 } 664 -665 /// Conservative estimate of RTT -666 /// -667 /// Takes the maximum of smoothed and latest RTT, as recommended -668 /// in 6.1.2 of the recovery spec (draft 29). -669 pub fn conservative(&self) -> Duration { -670 self.get().max(self.latest) -671 } -672 -673 /// Minimum RTT registered so far for this estimator. -674 pub fn min(&self) -> Duration { -675 self.min -676 } -677 -678 /// PTO computed as described in RFC9002#6.2.1. -679 pub(crate) fn pto_base(&self) -> Duration { -680 self.get() + cmp::max(4 * self.var, TIMER_GRANULARITY) -681 } -682 -683 /// Records an RTT sample. -684 pub(crate) fn update(&mut self, ack_delay: Duration, rtt: Duration) { -685 self.latest = rtt; -686 // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.2-3: -687 // min_rtt does not adjust for ack_delay to avoid underestimating. -688 self.min = cmp::min(self.min, self.latest); -689 // Based on RFC6298. -690 if let Some(smoothed) = self.smoothed { -691 let adjusted_rtt = if self.min + ack_delay <= self.latest { -692 self.latest - ack_delay -693 } else { -694 self.latest -695 }; -696 let var_sample = smoothed.abs_diff(adjusted_rtt); -697 self.var = (3 * self.var + var_sample) / 4; -698 self.smoothed = Some((7 * smoothed + adjusted_rtt) / 8); -699 } else { -700 self.smoothed = Some(self.latest); -701 self.var = self.latest / 2; -702 self.min = self.latest; -703 } -704 } -705} -706 -707#[derive(Default, Debug)] -708pub(crate) struct PathResponses { -709 pending: Vec<PathResponse>, -710} -711 -712impl PathResponses { -713 pub(crate) fn push(&mut self, packet: u64, token: u64, remote: SocketAddr) { -714 /// Arbitrary permissive limit to prevent abuse -715 const MAX_PATH_RESPONSES: usize = 16; -716 let response = PathResponse { -717 packet, -718 token, -719 remote, -720 }; -721 let existing = self.pending.iter_mut().find(|x| x.remote == remote); -722 if let Some(existing) = existing { -723 // Update a queued response -724 if existing.packet <= packet { -725 *existing = response; -726 } -727 return; -728 } -729 if self.pending.len() < MAX_PATH_RESPONSES { -730 self.pending.push(response); -731 } else { -732 // We don't expect to ever hit this with well-behaved peers, so we don't bother dropping -733 // older challenges. -734 trace!("ignoring excessive PATH_CHALLENGE"); -735 } -736 } -737 -738 pub(crate) fn pop_off_path(&mut self, remote: SocketAddr) -> Option<(u64, SocketAddr)> { -739 let response = *self.pending.last()?; -740 if response.remote == remote { -741 // We don't bother searching further because we expect that the on-path response will -742 // get drained in the immediate future by a call to `pop_on_path` -743 return None; -744 } -745 self.pending.pop(); -746 Some((response.token, response.remote)) -747 } -748 -749 pub(crate) fn pop_on_path(&mut self, remote: SocketAddr) -> Option<u64> { -750 let response = *self.pending.last()?; -751 if response.remote != remote { -752 // We don't bother searching further because we expect that the off-path response will -753 // get drained in the immediate future by a call to `pop_off_path` -754 return None; -755 } -756 self.pending.pop(); -757 Some(response.token) -758 } -759 -760 pub(crate) fn is_empty(&self) -> bool { -761 self.pending.is_empty() -762 } -763} -764 -765#[derive(Copy, Clone, Debug)] -766struct PathResponse { -767 /// The packet number the corresponding PATH_CHALLENGE was received in -768 packet: u64, -769 /// The token of the PATH_CHALLENGE -770 token: u64, -771 /// The address the corresponding PATH_CHALLENGE was received from -772 remote: SocketAddr, -773} -774 -775/// Summary statistics of packets that have been sent on a particular path, but which have not yet -776/// been acked or deemed lost -777#[derive(Debug)] -778pub(super) struct InFlight { -779 /// Sum of the sizes of all sent packets considered "in flight" by congestion control -780 /// -781 /// The size does not include IP or UDP overhead. Packets only containing ACK frames do not -782 /// count towards this to ensure congestion control does not impede congestion feedback. -783 pub(super) bytes: u64, -784 /// Number of packets in flight containing frames other than ACK and PADDING -785 /// -786 /// This can be 0 even when bytes is not 0 because PADDING frames cause a packet to be -787 /// considered "in flight" by congestion control. However, if this is nonzero, bytes will always -788 /// also be nonzero. -789 pub(super) ack_eliciting: u64, -790} +665 /// PTO computed as described in RFC9002#6.2.1. +666 pub(crate) fn pto_base(&self) -> Duration { +667 self.get() + cmp::max(4 * self.var, TIMER_GRANULARITY) +668 } +669 +670 /// Records an RTT sample. +671 pub(crate) fn update(&mut self, ack_delay: Duration, rtt: Duration) { +672 self.latest = rtt; +673 // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.2-3: +674 // min_rtt does not adjust for ack_delay to avoid underestimating. +675 self.min = cmp::min(self.min, self.latest); +676 // Based on RFC6298. +677 if let Some(smoothed) = self.smoothed { +678 let adjusted_rtt = if self.min + ack_delay <= self.latest { +679 self.latest - ack_delay +680 } else { +681 self.latest +682 }; +683 let var_sample = smoothed.abs_diff(adjusted_rtt); +684 self.var = (3 * self.var + var_sample) / 4; +685 self.smoothed = Some((7 * smoothed + adjusted_rtt) / 8); +686 } else { +687 self.smoothed = Some(self.latest); +688 self.var = self.latest / 2; +689 self.min = self.latest; +690 } +691 } +692} +693 +694#[derive(Default, Debug)] +695pub(crate) struct PathResponses { +696 pending: Vec<PathResponse>, +697} +698 +699impl PathResponses { +700 pub(crate) fn push(&mut self, packet: u64, token: u64, remote: SocketAddr) { +701 /// Arbitrary permissive limit to prevent abuse +702 const MAX_PATH_RESPONSES: usize = 16; +703 let response = PathResponse { +704 packet, +705 token, +706 remote, +707 }; +708 let existing = self.pending.iter_mut().find(|x| x.remote == remote); +709 if let Some(existing) = existing { +710 // Update a queued response +711 if existing.packet <= packet { +712 *existing = response; +713 } +714 return; +715 } +716 if self.pending.len() < MAX_PATH_RESPONSES { +717 self.pending.push(response); +718 } else { +719 // We don't expect to ever hit this with well-behaved peers, so we don't bother dropping +720 // older challenges. +721 trace!("ignoring excessive PATH_CHALLENGE"); +722 } +723 } +724 +725 pub(crate) fn pop_off_path(&mut self, remote: SocketAddr) -> Option<(u64, SocketAddr)> { +726 let response = *self.pending.last()?; +727 if response.remote == remote { +728 // We don't bother searching further because we expect that the on-path response will +729 // get drained in the immediate future by a call to `pop_on_path` +730 return None; +731 } +732 self.pending.pop(); +733 Some((response.token, response.remote)) +734 } +735 +736 pub(crate) fn pop_on_path(&mut self, remote: SocketAddr) -> Option<u64> { +737 let response = *self.pending.last()?; +738 if response.remote != remote { +739 // We don't bother searching further because we expect that the off-path response will +740 // get drained in the immediate future by a call to `pop_off_path` +741 return None; +742 } +743 self.pending.pop(); +744 Some(response.token) +745 } +746 +747 pub(crate) fn is_empty(&self) -> bool { +748 self.pending.is_empty() +749 } +750} +751 +752#[derive(Copy, Clone, Debug)] +753struct PathResponse { +754 /// The packet number the corresponding PATH_CHALLENGE was received in +755 packet: u64, +756 /// The token of the PATH_CHALLENGE +757 token: u64, +758 /// The address the corresponding PATH_CHALLENGE was received from +759 remote: SocketAddr, +760} +761 +762/// Summary statistics of packets that have been sent on a particular path, but which have not yet +763/// been acked or deemed lost +764#[derive(Debug)] +765pub(super) struct InFlight { +766 /// Sum of the sizes of all sent packets considered "in flight" by congestion control +767 /// +768 /// The size does not include IP or UDP overhead. Packets only containing ACK frames do not +769 /// count towards this to ensure congestion control does not impede congestion feedback. +770 pub(super) bytes: u64, +771 /// Number of packets in flight containing frames other than ACK and PADDING +772 /// +773 /// This can be 0 even when bytes is not 0 because PADDING frames cause a packet to be +774 /// considered "in flight" by congestion control. However, if this is nonzero, bytes will always +775 /// also be nonzero. +776 pub(super) ack_eliciting: u64, +777} +778 +779impl InFlight { +780 fn new() -> Self { +781 Self { +782 bytes: 0, +783 ack_eliciting: 0, +784 } +785 } +786 +787 fn insert(&mut self, packet: &SentPacket) { +788 self.bytes += u64::from(packet.size); +789 self.ack_eliciting += u64::from(packet.ack_eliciting); +790 } 791 -792impl InFlight { -793 fn new() -> Self { -794 Self { -795 bytes: 0, -796 ack_eliciting: 0, -797 } -798 } -799 -800 fn insert(&mut self, packet: &SentPacket) { -801 self.bytes += u64::from(packet.size); -802 self.ack_eliciting += u64::from(packet.ack_eliciting); -803 } -804 -805 /// Update counters to account for a packet becoming acknowledged, lost, or abandoned -806 fn remove(&mut self, packet: &SentPacket) { -807 self.bytes -= u64::from(packet.size); -808 self.ack_eliciting -= u64::from(packet.ack_eliciting); -809 } +792 /// Update counters to account for a packet becoming acknowledged, lost, or abandoned +793 fn remove(&mut self, packet: &SentPacket) { +794 self.bytes -= u64::from(packet.size); +795 self.ack_eliciting -= u64::from(packet.ack_eliciting); +796 } +797} +798 +799/// State for QUIC-MULTIPATH PATH_STATUS_AVAILABLE and PATH_STATUS_BACKUP frames +800#[derive(Debug, Clone, Default)] +801pub(super) struct PathStatusState { +802 /// The local status +803 local_status: PathStatus, +804 /// Local sequence number, for both PATH_STATUS_AVAILABLE and PATH_STATUS_BACKUP +805 /// +806 /// This is the number of the *next* path status frame to be sent. +807 local_seq: VarInt, +808 /// The status set by the remote +809 remote_status: Option<(VarInt, PathStatus)>, 810} 811 -812/// State for QUIC-MULTIPATH PATH_STATUS_AVAILABLE and PATH_STATUS_BACKUP frames -813#[derive(Debug, Clone, Default)] -814pub(super) struct PathStatusState { -815 /// The local status -816 local_status: PathStatus, -817 /// Local sequence number, for both PATH_STATUS_AVAILABLE and PATH_STATUS_BACKUP -818 /// -819 /// This is the number of the *next* path status frame to be sent. -820 local_seq: VarInt, -821 /// The status set by the remote -822 remote_status: Option<(VarInt, PathStatus)>, -823} +812impl PathStatusState { +813 /// To be called on received PATH_STATUS_AVAILABLE/PATH_STATUS_BACKUP frames +814 pub(super) fn remote_update(&mut self, status: PathStatus, seq: VarInt) { +815 if self.remote_status.is_some_and(|(curr, _)| curr >= seq) { +816 return trace!(%seq, "ignoring path status update"); +817 } +818 +819 let prev = self.remote_status.replace((seq, status)).map(|(_, s)| s); +820 if prev != Some(status) { +821 debug!(?status, ?seq, "remote changed path status"); +822 } +823 } 824 -825impl PathStatusState { -826 /// To be called on received PATH_STATUS_AVAILABLE/PATH_STATUS_BACKUP frames -827 pub(super) fn remote_update(&mut self, status: PathStatus, seq: VarInt) { -828 if self.remote_status.is_some_and(|(curr, _)| curr >= seq) { -829 return trace!(%seq, "ignoring path status update"); -830 } -831 -832 let prev = self.remote_status.replace((seq, status)).map(|(_, s)| s); -833 if prev != Some(status) { -834 debug!(?status, ?seq, "remote changed path status"); -835 } -836 } -837 -838 /// Updates the local status -839 /// -840 /// If the local status changed, the previous value is returned -841 pub(super) fn local_update(&mut self, status: PathStatus) -> Option<PathStatus> { -842 if self.local_status == status { -843 return None; -844 } -845 -846 self.local_seq = self.local_seq.saturating_add(1u8); -847 Some(std::mem::replace(&mut self.local_status, status)) -848 } -849 -850 pub(crate) fn seq(&self) -> VarInt { -851 self.local_seq -852 } -853} -854 -855/// The QUIC-MULTIPATH path status -856/// -857/// See section "3.3 Path Status Management": -858/// <https://quicwg.org/multipath/draft-ietf-quic-multipath.html#name-path-status-management> -859#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] -860pub enum PathStatus { -861 /// Paths marked with as available will be used when scheduling packets -862 /// -863 /// If multiple paths are available, packets will be scheduled on whichever has -864 /// capacity. -865 #[default] -866 Available, -867 /// Paths marked as backup will only be used if there are no available paths -868 /// -869 /// If the max_idle_timeout is specified the path will be kept alive so that it does not -870 /// expire. -871 Backup, -872} -873 -874/// Application events about paths -875#[derive(Debug, Clone, PartialEq, Eq)] -876pub enum PathEvent { -877 /// A new path has been opened -878 Opened { -879 /// Which path is now open -880 id: PathId, -881 }, -882 /// A path has been closed -883 Closed { -884 /// Which path has been closed -885 id: PathId, -886 /// Error code supplied by the peer -887 /// See <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-error-codes> -888 /// for a list of known errors. -889 error_code: VarInt, -890 }, -891 /// All remaining state for a path has been removed -892 /// -893 /// The [`PathEvent::Closed`] would have been emitted for this path earlier. -894 Abandoned { -895 /// Which path had its state dropped -896 id: PathId, -897 /// The final path stats, they are no longer available via [`Connection::stats`] -898 /// -899 /// [`Connection::stats`]: super::Connection::stats -900 path_stats: PathStats, -901 }, -902 /// Path was closed locally -903 LocallyClosed { -904 /// Path for which the error occurred -905 id: PathId, -906 /// The error that occurred -907 error: PathError, -908 }, -909 /// The remote changed the status of the path -910 /// -911 /// The local status is not changed because of this event. It is up to the application -912 /// to update the local status, which is used for packet scheduling, when the remote -913 /// changes the status. -914 RemoteStatus { -915 /// Path which has changed status -916 id: PathId, -917 /// The new status set by the remote -918 status: PathStatus, -919 }, -920 /// Received an observation of our external address from the peer. -921 ObservedAddr { -922 /// Path over which the observed address was reported, [`PathId::ZERO`] when multipath is -923 /// not negotiated -924 id: PathId, -925 /// The address observed by the remote over this path -926 addr: SocketAddr, -927 }, -928} -929 -930/// Error from setting path status -931#[derive(Debug, Error, Clone, PartialEq, Eq)] -932pub enum SetPathStatusError { -933 /// Error indicating that a path has not been opened or has already been abandoned -934 #[error("closed path")] -935 ClosedPath, -936 /// Error indicating that this operation requires multipath to be negotiated whereas it hasn't been -937 #[error("multipath not negotiated")] -938 MultipathNotNegotiated, -939} -940 -941/// Error indicating that a path has not been opened or has already been abandoned -942#[derive(Debug, Default, Error, Clone, PartialEq, Eq)] -943#[error("closed path")] -944pub struct ClosedPath { -945 pub(super) _private: (), -946} -947 -948#[cfg(test)] -949mod tests { -950 use super::*; -951 -952 #[test] -953 fn test_path_id_saturating_add() { -954 // add within range behaves normally -955 let large: PathId = u16::MAX.into(); -956 let next = u32::from(u16::MAX) + 1; -957 assert_eq!(large.saturating_add(1u8), PathId::from(next)); -958 -959 // outside range saturates -960 assert_eq!(PathId::MAX.saturating_add(1u8), PathId::MAX) -961 } -962}
\ No newline at end of file +825 /// Updates the local status +826 /// +827 /// If the local status changed, the previous value is returned +828 pub(super) fn local_update(&mut self, status: PathStatus) -> Option<PathStatus> { +829 if self.local_status == status { +830 return None; +831 } +832 +833 self.local_seq = self.local_seq.saturating_add(1u8); +834 Some(std::mem::replace(&mut self.local_status, status)) +835 } +836 +837 pub(crate) fn seq(&self) -> VarInt { +838 self.local_seq +839 } +840} +841 +842/// The QUIC-MULTIPATH path status +843/// +844/// See section "3.3 Path Status Management": +845/// <https://quicwg.org/multipath/draft-ietf-quic-multipath.html#name-path-status-management> +846#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] +847pub enum PathStatus { +848 /// Paths marked with as available will be used when scheduling packets +849 /// +850 /// If multiple paths are available, packets will be scheduled on whichever has +851 /// capacity. +852 #[default] +853 Available, +854 /// Paths marked as backup will only be used if there are no available paths +855 /// +856 /// If the max_idle_timeout is specified the path will be kept alive so that it does not +857 /// expire. +858 Backup, +859} +860 +861/// Application events about paths +862#[derive(Debug, Clone, PartialEq, Eq)] +863pub enum PathEvent { +864 /// A new path has been opened +865 Opened { +866 /// Which path is now open +867 id: PathId, +868 }, +869 /// A path has been closed +870 Closed { +871 /// Which path has been closed +872 id: PathId, +873 /// Error code supplied by the peer +874 /// See <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-error-codes> +875 /// for a list of known errors. +876 error_code: VarInt, +877 }, +878 /// All remaining state for a path has been removed +879 /// +880 /// The [`PathEvent::Closed`] would have been emitted for this path earlier. +881 Abandoned { +882 /// Which path had its state dropped +883 id: PathId, +884 /// The final path stats, they are no longer available via [`Connection::stats`] +885 /// +886 /// [`Connection::stats`]: super::Connection::stats +887 path_stats: PathStats, +888 }, +889 /// Path was closed locally +890 LocallyClosed { +891 /// Path for which the error occurred +892 id: PathId, +893 /// The error that occurred +894 error: PathError, +895 }, +896 /// The remote changed the status of the path +897 /// +898 /// The local status is not changed because of this event. It is up to the application +899 /// to update the local status, which is used for packet scheduling, when the remote +900 /// changes the status. +901 RemoteStatus { +902 /// Path which has changed status +903 id: PathId, +904 /// The new status set by the remote +905 status: PathStatus, +906 }, +907 /// Received an observation of our external address from the peer. +908 ObservedAddr { +909 /// Path over which the observed address was reported, [`PathId::ZERO`] when multipath is +910 /// not negotiated +911 id: PathId, +912 /// The address observed by the remote over this path +913 addr: SocketAddr, +914 }, +915} +916 +917/// Error from setting path status +918#[derive(Debug, Error, Clone, PartialEq, Eq)] +919pub enum SetPathStatusError { +920 /// Error indicating that a path has not been opened or has already been abandoned +921 #[error("closed path")] +922 ClosedPath, +923 /// Error indicating that this operation requires multipath to be negotiated whereas it hasn't been +924 #[error("multipath not negotiated")] +925 MultipathNotNegotiated, +926} +927 +928/// Error indicating that a path has not been opened or has already been abandoned +929#[derive(Debug, Default, Error, Clone, PartialEq, Eq)] +930#[error("closed path")] +931pub struct ClosedPath { +932 pub(super) _private: (), +933} +934 +935#[cfg(test)] +936mod tests { +937 use super::*; +938 +939 #[test] +940 fn test_path_id_saturating_add() { +941 // add within range behaves normally +942 let large: PathId = u16::MAX.into(); +943 let next = u32::from(u16::MAX) + 1; +944 assert_eq!(large.saturating_add(1u8), PathId::from(next)); +945 +946 // outside range saturates +947 assert_eq!(PathId::MAX.saturating_add(1u8), PathId::MAX) +948 } +949} \ No newline at end of file