From 385ec6ad2dab67ed9d059bf82e4c080483e80eba Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 29 Apr 2026 07:06:00 +0000 Subject: [PATCH] deploy: 408a65414df81a9c995f1098b0de585f3dbdaad1 --- pr/616/docs/help.html | 2 +- pr/616/docs/noq/enum.ClosePathError.html | 6 +- pr/616/docs/noq/enum.ConnectionError.html | 6 +- pr/616/docs/noq/enum.PathError.html | 6 +- pr/616/docs/noq/enum.PathEvent.html | 6 +- pr/616/docs/noq/enum.PathStatus.html | 6 +- pr/616/docs/noq/enum.SetPathStatusError.html | 6 +- pr/616/docs/noq/struct.ClosedPath.html | 6 +- pr/616/docs/noq/trait.NetworkChangeHint.html | 4 +- .../docs/noq_proto/enum.ClosePathError.html | 6 +- .../docs/noq_proto/enum.ConnectionError.html | 6 +- pr/616/docs/noq_proto/enum.Event.html | 4 +- .../noq_proto/enum.PathAbandonReason.html | 6 +- pr/616/docs/noq_proto/enum.PathError.html | 6 +- pr/616/docs/noq_proto/enum.PathEvent.html | 6 +- pr/616/docs/noq_proto/enum.PathStatus.html | 6 +- .../noq_proto/enum.SetPathStatusError.html | 6 +- .../n0_nat_traversal/enum.Error.html | 4 +- .../n0_nat_traversal/enum.Event.html | 4 +- .../noq_proto/n0_nat_traversal/index.html | 2 +- pr/616/docs/noq_proto/struct.ClosedPath.html | 6 +- pr/616/docs/noq_proto/struct.Connection.html | 70 +- .../struct.MultipathNotNegotiated.html | 4 +- .../docs/noq_proto/struct.RttEstimator.html | 10 +- .../docs/noq_proto/struct.TransportError.html | 2 +- .../noq_proto/trait.NetworkChangeHint.html | 4 +- pr/616/docs/settings.html | 2 +- .../docs/src/noq_proto/connection/mod.rs.html | 11306 ++++++++-------- .../src/noq_proto/connection/paths.rs.html | 1217 +- .../src/noq_proto/connection/spaces.rs.html | 3105 ++--- pr/616/docs/src/noq_proto/frame.rs.html | 993 +- .../src/noq_proto/n0_nat_traversal.rs.html | 1499 +- 32 files changed, 9183 insertions(+), 9139 deletions(-) diff --git a/pr/616/docs/help.html b/pr/616/docs/help.html index cda0c7f93..09d0ca8e7 100644 --- a/pr/616/docs/help.html +++ b/pr/616/docs/help.html @@ -1 +1 @@ -Help

All

Rustdoc help

Back
\ No newline at end of file +Help

All

Rustdoc help

Back
\ No newline at end of file diff --git a/pr/616/docs/noq/enum.ClosePathError.html b/pr/616/docs/noq/enum.ClosePathError.html index e4a4a3fd5..5ea7eee51 100644 --- a/pr/616/docs/noq/enum.ClosePathError.html +++ b/pr/616/docs/noq/enum.ClosePathError.html @@ -1,4 +1,4 @@ -ClosePathError in noq - Rust

ClosePathError

Enum ClosePathError 

Source
pub enum ClosePathError {
+ClosePathError in noq - Rust

ClosePathError

Enum ClosePathError 

Source
pub enum ClosePathError {
     MultipathNotNegotiated,
     ClosedPath,
     LastOpenPath,
@@ -7,8 +7,8 @@
 
§

ClosedPath

The path is already closed or was never opened

§

LastOpenPath

Cannot close the last remaining open path via the local API.

Use Connection::close to end the connection instead.

-

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<(), Error>

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

impl Display for ClosePathError

Source§

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

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<(), Error>

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

impl Display for ClosePathError

Source§

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

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/616/docs/noq/enum.ConnectionError.html b/pr/616/docs/noq/enum.ConnectionError.html index 20de8e1ae..9789e7ada 100644 --- a/pr/616/docs/noq/enum.ConnectionError.html +++ b/pr/616/docs/noq/enum.ConnectionError.html @@ -1,4 +1,4 @@ -ConnectionError in noq - Rust

ConnectionError

Enum ConnectionError 

Source
pub enum ConnectionError {
+ConnectionError in noq - 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/616/docs/noq/enum.PathError.html b/pr/616/docs/noq/enum.PathError.html index b48c22d4a..db54abc82 100644 --- a/pr/616/docs/noq/enum.PathError.html +++ b/pr/616/docs/noq/enum.PathError.html @@ -1,4 +1,4 @@ -PathError in noq - Rust

PathError

Enum PathError 

Source
pub enum PathError {
+PathError in noq - 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<(), Error>

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

impl Display for PathError

Source§

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

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<(), Error>

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

impl Display for PathError

Source§

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

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/616/docs/noq/enum.PathEvent.html b/pr/616/docs/noq/enum.PathEvent.html index da4b6dad6..d3226b00c 100644 --- a/pr/616/docs/noq/enum.PathEvent.html +++ b/pr/616/docs/noq/enum.PathEvent.html @@ -1,4 +1,4 @@ -PathEvent in noq - Rust

PathEvent

Enum PathEvent 

Source
pub enum PathEvent {
+PathEvent in noq - Rust

PathEvent

Enum PathEvent 

Source
pub enum PathEvent {
     Opened {
         id: PathId,
     },
@@ -39,8 +39,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<(), Error>

Formats the value using the given formatter. Read more
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<(), Error>

Formats the value using the given formatter. Read more
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/616/docs/noq/enum.PathStatus.html b/pr/616/docs/noq/enum.PathStatus.html index 4422c371b..f6c4f6825 100644 --- a/pr/616/docs/noq/enum.PathStatus.html +++ b/pr/616/docs/noq/enum.PathStatus.html @@ -1,4 +1,4 @@ -PathStatus in noq - Rust

PathStatus

Enum PathStatus 

Source
pub enum PathStatus {
+PathStatus in noq - 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<(), Error>

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<(), Error>

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/616/docs/noq/enum.SetPathStatusError.html b/pr/616/docs/noq/enum.SetPathStatusError.html index 2a9557440..4d1b5d584 100644 --- a/pr/616/docs/noq/enum.SetPathStatusError.html +++ b/pr/616/docs/noq/enum.SetPathStatusError.html @@ -1,11 +1,11 @@ -SetPathStatusError in noq - Rust

SetPathStatusError

Enum SetPathStatusError 

Source
pub enum SetPathStatusError {
+SetPathStatusError in noq - 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<(), Error>

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

impl Display for SetPathStatusError

Source§

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

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<(), Error>

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

impl Display for SetPathStatusError

Source§

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

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/616/docs/noq/struct.ClosedPath.html b/pr/616/docs/noq/struct.ClosedPath.html index 38e0c7a7a..245f45d51 100644 --- a/pr/616/docs/noq/struct.ClosedPath.html +++ b/pr/616/docs/noq/struct.ClosedPath.html @@ -1,6 +1,6 @@ -ClosedPath in noq - 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<(), Error>

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<(), Error>

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 noq - 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<(), Error>

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<(), Error>

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/616/docs/noq/trait.NetworkChangeHint.html b/pr/616/docs/noq/trait.NetworkChangeHint.html index 50fe8c0f7..d88774e37 100644 --- a/pr/616/docs/noq/trait.NetworkChangeHint.html +++ b/pr/616/docs/noq/trait.NetworkChangeHint.html @@ -1,4 +1,4 @@ -NetworkChangeHint in noq - Rust

NetworkChangeHint

Trait NetworkChangeHint 

Source
pub trait NetworkChangeHint: Debug + 'static {
+NetworkChangeHint in noq - Rust

NetworkChangeHint

Trait NetworkChangeHint 

Source
pub trait NetworkChangeHint: Debug + 'static {
     // Required method
     fn is_path_recoverable(
         &self,
@@ -6,7 +6,7 @@
         network_path: FourTuple,
     ) -> bool;
 }
Expand description

Hints when the caller identifies a network change.

-

Required Methods§

Source

fn is_path_recoverable(&self, path_id: PathId, network_path: FourTuple) -> bool

Inform the connection if a path may recover after a network change.

+

Required Methods§

Source

fn is_path_recoverable(&self, path_id: PathId, network_path: FourTuple) -> bool

Inform the connection if a path may recover after a network change.

After network changes, paths may not be recoverable. In this case, waiting for the path to become idle may take longer than what is desirable. If Self::is_path_recoverable returns false, a multipath-enabled, client-side connection will establish a new path to diff --git a/pr/616/docs/noq_proto/enum.ClosePathError.html b/pr/616/docs/noq_proto/enum.ClosePathError.html index b74b76bce..f1519c06b 100644 --- a/pr/616/docs/noq_proto/enum.ClosePathError.html +++ b/pr/616/docs/noq_proto/enum.ClosePathError.html @@ -1,4 +1,4 @@ -ClosePathError in noq_proto - Rust

ClosePathError

Enum ClosePathError 

Source
pub enum ClosePathError {
+ClosePathError in noq_proto - Rust

ClosePathError

Enum ClosePathError 

Source
pub enum ClosePathError {
     MultipathNotNegotiated,
     ClosedPath,
     LastOpenPath,
@@ -7,8 +7,8 @@
 
§

ClosedPath

The path is already closed or was never opened

§

LastOpenPath

Cannot close the last remaining open path via the local API.

Use Connection::close to end the connection instead.

-

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/616/docs/noq_proto/enum.ConnectionError.html b/pr/616/docs/noq_proto/enum.ConnectionError.html index eef962f41..9186e9aaa 100644 --- a/pr/616/docs/noq_proto/enum.ConnectionError.html +++ b/pr/616/docs/noq_proto/enum.ConnectionError.html @@ -1,4 +1,4 @@ -ConnectionError in noq_proto - Rust

ConnectionError

Enum ConnectionError 

Source
pub enum ConnectionError {
+ConnectionError in noq_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/616/docs/noq_proto/enum.Event.html b/pr/616/docs/noq_proto/enum.Event.html index e386f61fe..c952732cb 100644 --- a/pr/616/docs/noq_proto/enum.Event.html +++ b/pr/616/docs/noq_proto/enum.Event.html @@ -1,4 +1,4 @@ -Event in noq_proto - Rust

Event

Enum Event 

Source
pub enum Event {
+Event in noq_proto - Rust

Event

Enum Event 

Source
pub enum Event {
     HandshakeDataReady,
     Connected,
     HandshakeConfirmed,
@@ -25,7 +25,7 @@ fail with §

DatagramsUnblocked

One or more application datagrams have been sent after blocking

§

Path(PathEvent)

(Multi)Path events

§

NatTraversal(Event)

n0’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/616/docs/noq_proto/enum.PathAbandonReason.html b/pr/616/docs/noq_proto/enum.PathAbandonReason.html index e54f8f350..be6112030 100644 --- a/pr/616/docs/noq_proto/enum.PathAbandonReason.html +++ b/pr/616/docs/noq_proto/enum.PathAbandonReason.html @@ -1,4 +1,4 @@ -PathAbandonReason in noq_proto - Rust

PathAbandonReason

Enum PathAbandonReason 

Source
pub enum PathAbandonReason {
+PathAbandonReason in noq_proto - Rust

PathAbandonReason

Enum PathAbandonReason 

Source
pub enum PathAbandonReason {
     ApplicationClosed {
         error_code: VarInt,
     },
@@ -18,8 +18,8 @@
 
§

NatTraversalRoundEnded

The path was opened in a NAT traversal round which was terminated.

§

RemoteAbandoned

The remote closed the path.

Fields

§error_code: VarInt

The error that was sent with the abandon frame.

-

Trait Implementations§

Source§

impl Clone for PathAbandonReason

Source§

fn clone(&self) -> PathAbandonReason

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 PathAbandonReason

Source§

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

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

impl PartialEq for PathAbandonReason

Source§

fn eq(&self, other: &PathAbandonReason) -> 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 PathAbandonReason

Source§

impl StructuralPartialEq for PathAbandonReason

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for PathAbandonReason

Source§

fn clone(&self) -> PathAbandonReason

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 PathAbandonReason

Source§

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

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

impl PartialEq for PathAbandonReason

Source§

fn eq(&self, other: &PathAbandonReason) -> 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 PathAbandonReason

Source§

impl StructuralPartialEq for PathAbandonReason

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/616/docs/noq_proto/enum.PathError.html b/pr/616/docs/noq_proto/enum.PathError.html index e2925ec10..f852d35f3 100644 --- a/pr/616/docs/noq_proto/enum.PathError.html +++ b/pr/616/docs/noq_proto/enum.PathError.html @@ -1,4 +1,4 @@ -PathError in noq_proto - Rust

PathError

Enum PathError 

Source
pub enum PathError {
+PathError in noq_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/616/docs/noq_proto/enum.PathEvent.html b/pr/616/docs/noq_proto/enum.PathEvent.html index 5f84d36cc..87927cc8a 100644 --- a/pr/616/docs/noq_proto/enum.PathEvent.html +++ b/pr/616/docs/noq_proto/enum.PathEvent.html @@ -1,4 +1,4 @@ -PathEvent in noq_proto - Rust

PathEvent

Enum PathEvent 

Source
pub enum PathEvent {
+PathEvent in noq_proto - Rust

PathEvent

Enum PathEvent 

Source
pub enum PathEvent {
     Opened {
         id: PathId,
     },
@@ -39,8 +39,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/616/docs/noq_proto/enum.PathStatus.html b/pr/616/docs/noq_proto/enum.PathStatus.html index 7c6f1e7a2..2fcac19f9 100644 --- a/pr/616/docs/noq_proto/enum.PathStatus.html +++ b/pr/616/docs/noq_proto/enum.PathStatus.html @@ -1,4 +1,4 @@ -PathStatus in noq_proto - Rust

PathStatus

Enum PathStatus 

Source
pub enum PathStatus {
+PathStatus in noq_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/616/docs/noq_proto/enum.SetPathStatusError.html b/pr/616/docs/noq_proto/enum.SetPathStatusError.html index 47648ab96..c96ec8096 100644 --- a/pr/616/docs/noq_proto/enum.SetPathStatusError.html +++ b/pr/616/docs/noq_proto/enum.SetPathStatusError.html @@ -1,11 +1,11 @@ -SetPathStatusError in noq_proto - Rust

SetPathStatusError

Enum SetPathStatusError 

Source
pub enum SetPathStatusError {
+SetPathStatusError in noq_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/616/docs/noq_proto/n0_nat_traversal/enum.Error.html b/pr/616/docs/noq_proto/n0_nat_traversal/enum.Error.html index 8b860033b..3a4a18d65 100644 --- a/pr/616/docs/noq_proto/n0_nat_traversal/enum.Error.html +++ b/pr/616/docs/noq_proto/n0_nat_traversal/enum.Error.html @@ -1,4 +1,4 @@ -Error in noq_proto::n0_nat_traversal - Rust

Error

Enum Error 

Source
pub enum Error {
+Error in noq_proto::n0_nat_traversal - Rust

Error

Enum Error 

Source
pub enum Error {
     TooManyAddresses,
     WrongConnectionSide,
     ExtensionNotNegotiated,
@@ -12,7 +12,7 @@
 
§

NotEnoughAddresses

Not enough addresses to complete the operation

§

Multipath(PathError)

Nat traversal attempt failed due to a multipath error

§

Closed

Attempted to initiate NAT traversal on a closed, or closing connection.

-

Trait Implementations§

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, __formatter: &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

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 +

Trait Implementations§

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, __formatter: &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

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 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/616/docs/noq_proto/n0_nat_traversal/enum.Event.html b/pr/616/docs/noq_proto/n0_nat_traversal/enum.Event.html index ba59da0d3..816cbd846 100644 --- a/pr/616/docs/noq_proto/n0_nat_traversal/enum.Event.html +++ b/pr/616/docs/noq_proto/n0_nat_traversal/enum.Event.html @@ -1,10 +1,10 @@ -Event in noq_proto::n0_nat_traversal - Rust

Event

Enum Event 

Source
pub enum Event {
+Event in noq_proto::n0_nat_traversal - Rust

Event

Enum Event 

Source
pub enum Event {
     AddressAdded(SocketAddr),
     AddressRemoved(SocketAddr),
 }
Expand description

Event emitted when the client receives ADD_ADDRESS or REMOVE_ADDRESS frames.

Variants§

§

AddressAdded(SocketAddr)

An ADD_ADDRESS frame was received.

§

AddressRemoved(SocketAddr)

A REMOVE_ADDRESS frame was received.

-

Trait Implementations§

Source§

impl Clone for Event

Source§

fn clone(&self) -> Event

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 Event

Source§

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

Formats the value using the given formatter. Read more

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 Clone for Event

Source§

fn clone(&self) -> Event

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 Event

Source§

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

Formats the value using the given formatter. Read more

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> CloneToUninit for T
where diff --git a/pr/616/docs/noq_proto/n0_nat_traversal/index.html b/pr/616/docs/noq_proto/n0_nat_traversal/index.html index 4b718cd34..0cfd28f3d 100644 --- a/pr/616/docs/noq_proto/n0_nat_traversal/index.html +++ b/pr/616/docs/noq_proto/n0_nat_traversal/index.html @@ -1,2 +1,2 @@ -noq_proto::n0_nat_traversal - Rust

Module n0_nat_traversal

Module n0_nat_traversal 

Source
Expand description

n0’s (https://n0.computer) NAT Traversal protocol implementation.

+noq_proto::n0_nat_traversal - Rust

Module n0_nat_traversal

Module n0_nat_traversal 

Source
Expand description

n0’s (https://n0.computer) NAT Traversal protocol implementation.

Enums§

Error
Errors that the nat traversal state might encounter.
Event
Event emitted when the client receives ADD_ADDRESS or REMOVE_ADDRESS frames.
\ No newline at end of file diff --git a/pr/616/docs/noq_proto/struct.ClosedPath.html b/pr/616/docs/noq_proto/struct.ClosedPath.html index 15d3ebfbd..22054481b 100644 --- a/pr/616/docs/noq_proto/struct.ClosedPath.html +++ b/pr/616/docs/noq_proto/struct.ClosedPath.html @@ -1,6 +1,6 @@ -ClosedPath in noq_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 noq_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/616/docs/noq_proto/struct.Connection.html b/pr/616/docs/noq_proto/struct.Connection.html index 8a5961490..acf0bad12 100644 --- a/pr/616/docs/noq_proto/struct.Connection.html +++ b/pr/616/docs/noq_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
  • @@ -124,42 +124,42 @@ idle time. Setting None disables the timeout and stops the timer.

    max_datagrams specifies how many datagrams can be returned inside a single Transmit using GSO. This must be at least 1.

    -
Source

pub fn handle_event(&mut self, event: ConnectionEvent)

Process ConnectionEvents generated by the associated Endpoint

+
Source

pub fn handle_event(&mut self, event: ConnectionEvent)

Process ConnectionEvents generated by the associated Endpoint

Will execute protocol logic upon receipt of a connection event, in turn preparing signals (including application Events, EndpointEvents and outgoing datagrams) that should be extracted through the relevant methods.

-
Source

pub fn handle_timeout(&mut self, now: Instant)

Process timer expirations

+
Source

pub fn handle_timeout(&mut self, now: Instant)

Process timer expirations

Executes protocol logic, potentially preparing signals (including application Events, EndpointEvents and outgoing datagrams) that should be extracted through the relevant methods.

It is most efficient to call this immediately after the system clock reaches the latest Instant that was output by poll_timeout; however spurious extra calls will simply no-op and therefore are safe.

-
Source

pub fn close(&mut self, now: Instant, error_code: VarInt, reason: Bytes)

Close a connection immediately

+
Source

pub fn close(&mut self, now: Instant, error_code: VarInt, reason: Bytes)

Close a connection immediately

This does not ensure delivery of outstanding data. It is the application’s responsibility to call this only when all important communications have been completed, e.g. by calling SendStream::finish on outstanding streams and waiting for the corresponding StreamEvent::Finished event.

If Streams::send_streams returns 0, all outstanding stream data has been delivered. There may still be data from the peer that has not been received.

-
Source

pub fn datagrams(&mut self) -> Datagrams<'_>

Control datagrams

-
Source

pub fn stats(&mut self) -> ConnectionStats

Returns connection statistics

-
Source

pub fn path_stats(&mut self, path_id: PathId) -> Option<PathStats>

Returns path statistics

-
Source

pub fn ping(&mut self)

Ping the remote endpoint

+
Source

pub fn datagrams(&mut self) -> Datagrams<'_>

Control datagrams

+
Source

pub fn stats(&mut self) -> ConnectionStats

Returns connection statistics

+
Source

pub fn path_stats(&mut self, path_id: PathId) -> Option<PathStats>

Returns path statistics

+
Source

pub fn ping(&mut self)

Ping the remote endpoint

Causes an ACK-eliciting packet to be transmitted on the connection.

-
Source

pub fn ping_path(&mut self, path: PathId) -> Result<(), ClosedPath>

Ping the remote endpoint over a specific path

+
Source

pub fn ping_path(&mut self, path: PathId) -> Result<(), ClosedPath>

Ping the remote endpoint over a specific path

Causes an ACK-eliciting packet to be transmitted on the path.

-
Source

pub fn force_key_update(&mut self)

Update traffic keys spontaneously

+
Source

pub fn force_key_update(&mut self)

Update traffic keys spontaneously

This can be useful for testing key updates, as they otherwise only happen infrequently.

-
Source

pub fn crypto_session(&self) -> &dyn Session

Get a session reference

-
Source

pub fn is_handshaking(&self) -> bool

Whether the connection is in the process of being established

+
Source

pub fn crypto_session(&self) -> &dyn Session

Get a session reference

+
Source

pub fn is_handshaking(&self) -> bool

Whether the connection is in the process of being established

If this returns false, the connection may be either established or closed, signaled by the emission of a Connected or ConnectionLost event respectively. Note that locally-initiated closes via close() do not emit a ConnectionLost event.

For an established connection this essentially means the handshake is completed, but not necessarily yet confirmed.

-
Source

pub fn is_closed(&self) -> bool

Whether the connection is closed

+
Source

pub fn is_closed(&self) -> bool

Whether the connection is closed

Closed connections cannot transport any further data. A connection becomes closed when either peer application intentionally closes it, or when either transport layer detects an error such as a time-out or certificate validation failure.

@@ -167,24 +167,24 @@ error such as a time-out or certificate validation failure.

connection is closed by the peer or due to an error. When the local application closes the connection via close(), no ConnectionLost event is emitted; instead, pending operations fail with ConnectionError::LocallyClosed.

-
Source

pub fn is_drained(&self) -> bool

Whether there is no longer any need to keep the connection around

+
Source

pub fn is_drained(&self) -> bool

Whether there is no longer any need to keep the connection around

Closed connections become drained after a brief timeout to absorb any remaining in-flight packets from the peer. All drained connections have been closed.

-
Source

pub fn accepted_0rtt(&self) -> bool

For clients, if the peer accepted the 0-RTT data packets

+
Source

pub fn accepted_0rtt(&self) -> bool

For clients, if the peer accepted the 0-RTT data packets

The value is meaningless until after the handshake completes.

-
Source

pub fn has_0rtt(&self) -> bool

Whether 0-RTT is/was possible during the handshake

-
Source

pub fn has_pending_retransmits(&self) -> bool

Whether there are any pending retransmits

-
Source

pub fn side(&self) -> Side

Look up whether we’re the client or server of this Connection

-
Source

pub fn path_observed_address( +

Source

pub fn has_0rtt(&self) -> bool

Whether 0-RTT is/was possible during the handshake

+
Source

pub fn has_pending_retransmits(&self) -> bool

Whether there are any pending retransmits

+
Source

pub fn side(&self) -> Side

Look up whether we’re the client or server of this Connection

+
Source

pub fn path_observed_address( &self, path_id: PathId, ) -> Result<Option<SocketAddr>, ClosedPath>

Get the address observed by the remote over the given path

-
Source

pub fn rtt(&self, path_id: PathId) -> Option<Duration>

Current best estimate of this connection’s latency (round-trip-time)

-
Source

pub fn congestion_state(&self, path_id: PathId) -> Option<&dyn Controller>

Current state of this connection’s congestion controller, for debugging purposes

-
Source

pub fn set_max_concurrent_streams(&mut self, dir: Dir, count: VarInt)

Modify the number of remotely initiated streams that may be concurrently open

+
Source

pub fn rtt(&self, path_id: PathId) -> Option<Duration>

Current best estimate of this connection’s latency (round-trip-time)

+
Source

pub fn congestion_state(&self, path_id: PathId) -> Option<&dyn Controller>

Current state of this connection’s congestion controller, for debugging purposes

+
Source

pub fn set_max_concurrent_streams(&mut self, dir: Dir, count: VarInt)

Modify the number of remotely initiated streams that may be concurrently open

No streams may be opened by the peer unless fewer than count are already open. Large counts increase both minimum and worst-case memory consumption.

-
Source

pub fn set_max_concurrent_paths( +

Source

pub fn set_max_concurrent_paths( &mut self, now: Instant, count: NonZeroU32, @@ -195,16 +195,16 @@ actively reduce paths they must be closed using TransportConfig) this can not enable multipath and will fail.

-

Source

pub fn max_concurrent_streams(&self, dir: Dir) -> u64

Current number of remotely initiated streams that may be concurrently open

+
Source

pub fn max_concurrent_streams(&self, dir: Dir) -> u64

Current number of remotely initiated streams that may be concurrently open

If the target for this limit is reduced using set_max_concurrent_streams, it will not change immediately, even if fewer streams are open. Instead, it will decrement by one for each time a remotely initiated stream of matching directionality is closed.

-
Source

pub fn set_send_window(&mut self, send_window: u64)

Source

pub fn set_receive_window(&mut self, receive_window: VarInt)

Source

pub fn is_multipath_negotiated(&self) -> bool

Whether the Multipath for QUIC extension is enabled.

+
Source

pub fn set_send_window(&mut self, send_window: u64)

Source

pub fn set_receive_window(&mut self, receive_window: VarInt)

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 handle_network_change( +

Source

pub fn handle_network_change( &mut self, hint: Option<&dyn NetworkChangeHint>, now: Instant, @@ -219,26 +219,26 @@ paths as non recoverable when necessary accelerates connectivity re-establishmen allow it altogether.

The optional hint allows callers to indicate when paths are non-recoverable and should be migrated to new a PathId.

-

Source

pub fn current_mtu(&self) -> u16

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

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

@@ -249,7 +249,7 @@ round is initiated, the previous one is cancelled.

4-tuple.

Returns the server addresses that are now being probed. If addresses fail due to spurious errors, these might succeed later and not be returned in this set.

-

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/616/docs/noq_proto/struct.MultipathNotNegotiated.html b/pr/616/docs/noq_proto/struct.MultipathNotNegotiated.html index 4ed1904f2..d3f05c79e 100644 --- a/pr/616/docs/noq_proto/struct.MultipathNotNegotiated.html +++ b/pr/616/docs/noq_proto/struct.MultipathNotNegotiated.html @@ -1,5 +1,5 @@ -MultipathNotNegotiated in noq_proto - Rust

MultipathNotNegotiated

Struct MultipathNotNegotiated 

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

Error when the multipath extension was not negotiated, but attempted to be used.

-

Trait Implementations§

Source§

impl Clone for MultipathNotNegotiated

Source§

fn clone(&self) -> MultipathNotNegotiated

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 MultipathNotNegotiated

Source§

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

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

impl Display for MultipathNotNegotiated

Source§

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

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

impl Error for MultipathNotNegotiated

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 Copy for MultipathNotNegotiated

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +MultipathNotNegotiated in noq_proto - Rust

MultipathNotNegotiated

Struct MultipathNotNegotiated 

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

Error when the multipath extension was not negotiated, but attempted to be used.

+

Trait Implementations§

Source§

impl Clone for MultipathNotNegotiated

Source§

fn clone(&self) -> MultipathNotNegotiated

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 MultipathNotNegotiated

Source§

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

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

impl Display for MultipathNotNegotiated

Source§

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

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

impl Error for MultipathNotNegotiated

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 Copy for MultipathNotNegotiated

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/616/docs/noq_proto/struct.RttEstimator.html b/pr/616/docs/noq_proto/struct.RttEstimator.html index d6c03ab12..50807c4f0 100644 --- a/pr/616/docs/noq_proto/struct.RttEstimator.html +++ b/pr/616/docs/noq_proto/struct.RttEstimator.html @@ -1,10 +1,10 @@ -RttEstimator in noq_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 noq_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/616/docs/noq_proto/struct.TransportError.html b/pr/616/docs/noq_proto/struct.TransportError.html index 243d857c4..9eabadc9d 100644 --- a/pr/616/docs/noq_proto/struct.TransportError.html +++ b/pr/616/docs/noq_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/616/docs/noq_proto/trait.NetworkChangeHint.html b/pr/616/docs/noq_proto/trait.NetworkChangeHint.html index 25d57b29d..0f19ec7e4 100644 --- a/pr/616/docs/noq_proto/trait.NetworkChangeHint.html +++ b/pr/616/docs/noq_proto/trait.NetworkChangeHint.html @@ -1,4 +1,4 @@ -NetworkChangeHint in noq_proto - Rust

NetworkChangeHint

Trait NetworkChangeHint 

Source
pub trait NetworkChangeHint: Debug + 'static {
+NetworkChangeHint in noq_proto - Rust

NetworkChangeHint

Trait NetworkChangeHint 

Source
pub trait NetworkChangeHint: Debug + 'static {
     // Required method
     fn is_path_recoverable(
         &self,
@@ -6,7 +6,7 @@
         network_path: FourTuple,
     ) -> bool;
 }
Expand description

Hints when the caller identifies a network change.

-

Required Methods§

Source

fn is_path_recoverable(&self, path_id: PathId, network_path: FourTuple) -> bool

Inform the connection if a path may recover after a network change.

+

Required Methods§

Source

fn is_path_recoverable(&self, path_id: PathId, network_path: FourTuple) -> bool

Inform the connection if a path may recover after a network change.

After network changes, paths may not be recoverable. In this case, waiting for the path to become idle may take longer than what is desirable. If Self::is_path_recoverable returns false, a multipath-enabled, client-side connection will establish a new path to diff --git a/pr/616/docs/settings.html b/pr/616/docs/settings.html index 1adf3f67d..fc24471ee 100644 --- a/pr/616/docs/settings.html +++ b/pr/616/docs/settings.html @@ -1 +1 @@ -Settings

All

Rustdoc settings

Back
\ No newline at end of file +Settings

All

Rustdoc settings

Back
\ No newline at end of file diff --git a/pr/616/docs/src/noq_proto/connection/mod.rs.html b/pr/616/docs/src/noq_proto/connection/mod.rs.html index 98a167cff..a719d88f7 100644 --- a/pr/616/docs/src/noq_proto/connection/mod.rs.html +++ b/pr/616/docs/src/noq_proto/connection/mod.rs.html @@ -3,7 +3,7 @@ 3 collections::{BTreeMap, VecDeque, btree_map}, 4 convert::TryFrom, 5 fmt, io, mem, -6 net::{IpAddr, SocketAddr}, +6 net::SocketAddr, 7 num::{NonZeroU32, NonZeroUsize}, 8 sync::Arc, 9}; @@ -81,7 +81,7 @@ 81pub(crate) mod qlog; 82pub(crate) mod send_buffer; 83 -84mod spaces; +84pub(crate) mod spaces; 85#[cfg(fuzzing)] 86pub use spaces::Retransmits; 87#[cfg(not(fuzzing))] @@ -1991,5802 +1991,5676 @@ 1991 .udp_tx 1992 .on_sent(1, buf.len()); 1993 -1994 Some(Transmit { -1995 destination: network_path.remote, -1996 size: buf.len(), -1997 ecn: None, -1998 segment_size: None, -1999 src_ip: network_path.local_ip, -2000 }) -2001 } -2002 -2003 fn send_off_path_path_response( -2004 &mut self, -2005 now: Instant, -2006 buf: &mut Vec<u8>, -2007 path_id: PathId, -2008 ) -> Option<Transmit> { -2009 let path = self.paths.get_mut(&path_id).map(|state| &mut state.data)?; -2010 let cid_queue = self.remote_cids.get_mut(&path_id)?; -2011 let (token, network_path) = path.path_responses.pop_off_path(path.network_path)?; -2012 -2013 let cid = cid_queue -2014 .next_reserved() -2015 .unwrap_or_else(|| cid_queue.active()); -2016 // TODO(@divma): we should take a different approach when there is no fresh CID to use. -2017 // https://github.com/quinn-rs/quinn/issues/2184 +1994 trace!( +1995 dst = ?network_path.remote, +1996 src = ?network_path.local_ip, +1997 len = buf.len(), +1998 "sending prev_path off-path challenge", +1999 ); +2000 Some(Transmit { +2001 destination: network_path.remote, +2002 size: buf.len(), +2003 ecn: None, +2004 segment_size: None, +2005 src_ip: network_path.local_ip, +2006 }) +2007 } +2008 +2009 fn send_off_path_path_response( +2010 &mut self, +2011 now: Instant, +2012 buf: &mut Vec<u8>, +2013 path_id: PathId, +2014 ) -> Option<Transmit> { +2015 let path = self.paths.get_mut(&path_id).map(|state| &mut state.data)?; +2016 let cid_queue = self.remote_cids.get_mut(&path_id)?; +2017 let (token, network_path) = path.path_responses.pop_off_path(path.network_path)?; 2018 -2019 let frame = frame::PathResponse(token); -2020 -2021 let buf = &mut TransmitBuf::new(buf, NonZeroUsize::MIN, MIN_INITIAL_SIZE.into()); -2022 buf.start_new_datagram(); -2023 -2024 let mut builder = PacketBuilder::new(now, SpaceId::Data, path_id, cid, buf, self)?; -2025 let stats = &mut self.path_stats.for_path(path_id).frame_tx; -2026 builder.write_frame_with_log_msg(frame, stats, Some("(off-path)")); -2027 // Off-path: not tracked in congestion control. The packet is sent to a -2028 // different destination than path_id's network path. -2029 builder.pad_to(MIN_INITIAL_SIZE); -2030 builder.finish(self, now); -2031 -2032 let size = buf.len(); -2033 -2034 self.path_stats.for_path(path_id).udp_tx.on_sent(1, size); -2035 Some(Transmit { -2036 destination: network_path.remote, -2037 size, -2038 ecn: None, -2039 segment_size: None, -2040 src_ip: network_path.local_ip, -2041 }) -2042 } -2043 -2044 /// Send a nat traversal challenge (off-path) on this path if possible. -2045 fn send_nat_traversal_path_challenge( -2046 &mut self, -2047 now: Instant, -2048 buf: &mut Vec<u8>, -2049 path_id: PathId, -2050 ) -> Option<Transmit> { -2051 let remote = self -2052 .n0_nat_traversal -2053 .server_side_mut() -2054 .ok()? -2055 .next_probe_addr()?; -2056 -2057 if !self.paths.get(&path_id)?.data.validated { -2058 // Path is not usable for probing -2059 return None; -2060 } -2061 -2062 // TODO: Using the active CID here makes the paths linkable. This is a violation of -2063 // RFC9000 but something we want to accept in the short term. Eventually we aim -2064 // to fix up the supply of CIDs sufficiently so that we can keep paths unlinkable -2065 // again. -2066 let Some(cid) = self -2067 .remote_cids -2068 .get(&path_id) -2069 .map(|cid_queue| cid_queue.active()) -2070 else { -2071 trace!(%path_id, "Not sending NAT traversal probe for path with no CIDs"); -2072 return None; -2073 }; -2074 let token = self.rng.random(); -2075 -2076 let frame = frame::PathChallenge(token); -2077 -2078 let mut buf = TransmitBuf::new(buf, NonZeroUsize::MIN, MIN_INITIAL_SIZE.into()); -2079 buf.start_new_datagram(); -2080 -2081 let mut builder = PacketBuilder::new(now, SpaceId::Data, path_id, cid, &mut buf, self)?; -2082 let stats = &mut self.path_stats.for_path(path_id).frame_tx; -2083 builder.write_frame_with_log_msg(frame, stats, Some("(nat-traversal)")); -2084 // Off-path: not tracked in congestion control. The packet is sent to a -2085 // different destination than path_id's network path. -2086 builder.finish(self, now); -2087 -2088 // Mark as sent after packet build succeeds. -2089 if let Ok(server_state) = self.n0_nat_traversal.server_side_mut() { -2090 server_state.mark_probe_sent((remote.ip(), remote.port()), token); -2091 } -2092 -2093 let size = buf.len(); -2094 -2095 self.path_stats.for_path(path_id).udp_tx.on_sent(1, size); -2096 -2097 Some(Transmit { -2098 destination: remote, -2099 size, -2100 ecn: None, -2101 segment_size: None, -2102 src_ip: None, -2103 }) -2104 } -2105 -2106 /// Indicate what types of frames are ready to send for the given space. -2107 /// -2108 /// Only for on-path data. -2109 /// -2110 /// *packet_size* is the number of bytes available to build the next packet. -2111 /// *connection_close_pending* indicates whether a CONNECTION_CLOSE frame needs to be -2112 /// sent. -2113 fn space_can_send( -2114 &mut self, -2115 space_id: SpaceId, -2116 path_id: PathId, -2117 packet_size: usize, -2118 connection_close_pending: bool, -2119 ) -> SendableFrames { -2120 let space = &mut self.spaces[space_id]; -2121 let space_has_crypto = self.crypto_state.has_keys(space_id.encryption_level()); -2122 -2123 if !space_has_crypto -2124 && (space_id != SpaceId::Data -2125 || !self.crypto_state.has_keys(EncryptionLevel::ZeroRtt) -2126 || self.side.is_server()) -2127 { -2128 // Nothing to send in this space -2129 return SendableFrames::empty(); -2130 } -2131 -2132 let mut can_send = space.can_send(path_id, &self.streams); -2133 -2134 // Check for 1RTT space. -2135 if space_id == SpaceId::Data { -2136 let pn = space.for_path(path_id).peek_tx_number(); -2137 // Number of bytes available for frames if this is a 1-RTT packet. We're -2138 // guaranteed to be able to send an individual frame at least this large in the -2139 // next 1-RTT packet. This could be generalized to support every space, but it's -2140 // only needed to handle large fixed-size frames, which only exist in 1-RTT -2141 // (application datagrams). -2142 let frame_space_1rtt = -2143 packet_size.saturating_sub(self.predict_1rtt_overhead(pn, path_id)); -2144 can_send |= self.can_send_1rtt(path_id, frame_space_1rtt); -2145 } -2146 -2147 can_send.close = connection_close_pending && space_has_crypto; -2148 -2149 can_send -2150 } -2151 -2152 /// Process `ConnectionEvent`s generated by the associated `Endpoint` -2153 /// -2154 /// Will execute protocol logic upon receipt of a connection event, in turn preparing signals -2155 /// (including application `Event`s, `EndpointEvent`s and outgoing datagrams) that should be -2156 /// extracted through the relevant methods. -2157 pub fn handle_event(&mut self, event: ConnectionEvent) { -2158 use ConnectionEventInner::*; -2159 match event.0 { -2160 Datagram(DatagramConnectionEvent { -2161 now, -2162 network_path, -2163 path_id, -2164 ecn, -2165 first_decode, -2166 remaining, -2167 }) => { -2168 let span = trace_span!("pkt", %path_id); -2169 let _guard = span.enter(); -2170 -2171 if self.early_discard_packet(network_path, path_id) { -2172 // A return value of true indicates we should discard this packet. -2173 return; -2174 } -2175 -2176 let was_anti_amplification_blocked = self -2177 .path(path_id) -2178 .map(|path| path.anti_amplification_blocked(1)) -2179 // We never tried to send on an non-existing (new) path so have not been -2180 // anti-amplification blocked for it previously. -2181 .unwrap_or(false); +2019 let cid = cid_queue +2020 .next_reserved() +2021 .unwrap_or_else(|| cid_queue.active()); +2022 // TODO(@divma): we should take a different approach when there is no fresh CID to use. +2023 // https://github.com/quinn-rs/quinn/issues/2184 +2024 +2025 let frame = frame::PathResponse(token); +2026 +2027 let buf = &mut TransmitBuf::new(buf, NonZeroUsize::MIN, MIN_INITIAL_SIZE.into()); +2028 buf.start_new_datagram(); +2029 +2030 let mut builder = PacketBuilder::new(now, SpaceId::Data, path_id, cid, buf, self)?; +2031 let stats = &mut self.path_stats.for_path(path_id).frame_tx; +2032 builder.write_frame_with_log_msg(frame, stats, Some("(off-path)")); +2033 // Off-path: not tracked in congestion control. The packet is sent to a +2034 // different destination than path_id's network path. +2035 builder.pad_to(MIN_INITIAL_SIZE); +2036 builder.finish(self, now); +2037 +2038 let size = buf.len(); +2039 self.path_stats.for_path(path_id).udp_tx.on_sent(1, size); +2040 +2041 trace!( +2042 dst = ?network_path.remote, +2043 src = ?network_path.local_ip, +2044 len = buf.len(), +2045 "sending off-path PATH_RESPONSE", +2046 ); +2047 Some(Transmit { +2048 destination: network_path.remote, +2049 size, +2050 ecn: None, +2051 segment_size: None, +2052 src_ip: network_path.local_ip, +2053 }) +2054 } +2055 +2056 /// Send a nat traversal challenge (off-path) on this path if possible. +2057 fn send_nat_traversal_path_challenge( +2058 &mut self, +2059 now: Instant, +2060 buf: &mut Vec<u8>, +2061 path_id: PathId, +2062 ) -> Option<Transmit> { +2063 let remote = self.n0_nat_traversal.next_probe_addr()?; +2064 +2065 if !self.paths.get(&path_id)?.data.validated { +2066 // Path is not usable for probing +2067 return None; +2068 } +2069 +2070 // TODO: Using the active CID here makes the paths linkable. This is a violation of +2071 // RFC9000 but something we want to accept in the short term. Eventually we aim +2072 // to fix up the supply of CIDs sufficiently so that we can keep paths unlinkable +2073 // again. +2074 let Some(cid) = self +2075 .remote_cids +2076 .get(&path_id) +2077 .map(|cid_queue| cid_queue.active()) +2078 else { +2079 trace!(%path_id, "Not sending NAT traversal probe for path with no CIDs"); +2080 return None; +2081 }; +2082 let token = self.rng.random(); +2083 +2084 let frame = frame::PathChallenge(token); +2085 +2086 let mut buf = TransmitBuf::new(buf, NonZeroUsize::MIN, MIN_INITIAL_SIZE.into()); +2087 buf.start_new_datagram(); +2088 +2089 let mut builder = PacketBuilder::new(now, SpaceId::Data, path_id, cid, &mut buf, self)?; +2090 let stats = &mut self.path_stats.for_path(path_id).frame_tx; +2091 builder.write_frame_with_log_msg(frame, stats, Some("(nat-traversal)")); +2092 // Off-path: not tracked in congestion control. The packet is sent to a +2093 // different destination than path_id's network path. +2094 builder.finish(self, now); +2095 +2096 // Mark as sent after packet build succeeds. +2097 self.n0_nat_traversal +2098 .mark_probe_sent((remote.ip(), remote.port()), token); +2099 +2100 let size = buf.len(); +2101 self.path_stats.for_path(path_id).udp_tx.on_sent(1, size); +2102 +2103 trace!(dst = ?remote, len = buf.len(), "sending off-path NAT probe"); +2104 Some(Transmit { +2105 destination: remote, +2106 size, +2107 ecn: None, +2108 segment_size: None, +2109 src_ip: None, +2110 }) +2111 } +2112 +2113 /// Indicate what types of frames are ready to send for the given space. +2114 /// +2115 /// Only for on-path data. +2116 /// +2117 /// *packet_size* is the number of bytes available to build the next packet. +2118 /// *connection_close_pending* indicates whether a CONNECTION_CLOSE frame needs to be +2119 /// sent. +2120 fn space_can_send( +2121 &mut self, +2122 space_id: SpaceId, +2123 path_id: PathId, +2124 packet_size: usize, +2125 connection_close_pending: bool, +2126 ) -> SendableFrames { +2127 let space = &mut self.spaces[space_id]; +2128 let space_has_crypto = self.crypto_state.has_keys(space_id.encryption_level()); +2129 +2130 if !space_has_crypto +2131 && (space_id != SpaceId::Data +2132 || !self.crypto_state.has_keys(EncryptionLevel::ZeroRtt) +2133 || self.side.is_server()) +2134 { +2135 // Nothing to send in this space +2136 return SendableFrames::empty(); +2137 } +2138 +2139 let mut can_send = space.can_send(path_id, &self.streams); +2140 +2141 // Check for 1RTT space. +2142 if space_id == SpaceId::Data { +2143 let pn = space.for_path(path_id).peek_tx_number(); +2144 // Number of bytes available for frames if this is a 1-RTT packet. We're +2145 // guaranteed to be able to send an individual frame at least this large in the +2146 // next 1-RTT packet. This could be generalized to support every space, but it's +2147 // only needed to handle large fixed-size frames, which only exist in 1-RTT +2148 // (application datagrams). +2149 let frame_space_1rtt = +2150 packet_size.saturating_sub(self.predict_1rtt_overhead(pn, path_id)); +2151 can_send |= self.can_send_1rtt(path_id, frame_space_1rtt); +2152 } +2153 +2154 can_send.close = connection_close_pending && space_has_crypto; +2155 +2156 can_send +2157 } +2158 +2159 /// Process `ConnectionEvent`s generated by the associated `Endpoint` +2160 /// +2161 /// Will execute protocol logic upon receipt of a connection event, in turn preparing signals +2162 /// (including application `Event`s, `EndpointEvent`s and outgoing datagrams) that should be +2163 /// extracted through the relevant methods. +2164 pub fn handle_event(&mut self, event: ConnectionEvent) { +2165 use ConnectionEventInner::*; +2166 match event.0 { +2167 Datagram(DatagramConnectionEvent { +2168 now, +2169 network_path, +2170 path_id, +2171 ecn, +2172 first_decode, +2173 remaining, +2174 }) => { +2175 let span = trace_span!("pkt", %path_id); +2176 let _guard = span.enter(); +2177 +2178 if self.early_discard_packet(network_path, path_id) { +2179 // A return value of true indicates we should discard this packet. +2180 return; +2181 } 2182 -2183 let rx = &mut self.path_stats.for_path(path_id).udp_rx; -2184 rx.datagrams += 1; -2185 rx.bytes += first_decode.len() as u64; -2186 let data_len = first_decode.len(); -2187 -2188 self.handle_decode(now, network_path, path_id, ecn, first_decode); -2189 // The current `path` might have changed inside `handle_decode` since the packet -2190 // could have triggered a migration. The packet might also belong to an unknown -2191 // path and have been rejected. Make sure the data received is accounted for the -2192 // most recent path by accessing `path` after `handle_decode`. -2193 if let Some(path) = self.path_mut(path_id) { -2194 path.inc_total_recvd(data_len as u64); -2195 } -2196 -2197 if let Some(data) = remaining { -2198 self.path_stats.for_path(path_id).udp_rx.bytes += data.len() as u64; -2199 self.handle_coalesced(now, network_path, path_id, ecn, data); -2200 } -2201 -2202 if let Some(path) = self.paths.get_mut(&path_id) { -2203 self.qlog -2204 .emit_recovery_metrics(path_id, &mut path.data, now); -2205 } -2206 -2207 if was_anti_amplification_blocked { -2208 // A prior attempt to set the loss detection timer may have failed due to -2209 // anti-amplification, so ensure it's set now. Prevents a handshake deadlock if -2210 // the server's first flight is lost. -2211 self.set_loss_detection_timer(now, path_id); +2183 let was_anti_amplification_blocked = self +2184 .path(path_id) +2185 .map(|path| path.anti_amplification_blocked(1)) +2186 // We never tried to send on an non-existing (new) path so have not been +2187 // anti-amplification blocked for it previously. +2188 .unwrap_or(false); +2189 +2190 let rx = &mut self.path_stats.for_path(path_id).udp_rx; +2191 rx.datagrams += 1; +2192 rx.bytes += first_decode.len() as u64; +2193 let data_len = first_decode.len(); +2194 +2195 self.handle_decode(now, network_path, path_id, ecn, first_decode); +2196 // The current `path` might have changed inside `handle_decode` since the packet +2197 // could have triggered a migration. The packet might also belong to an unknown +2198 // path and have been rejected. Make sure the data received is accounted for the +2199 // most recent path by accessing `path` after `handle_decode`. +2200 if let Some(path) = self.path_mut(path_id) { +2201 path.inc_total_recvd(data_len as u64); +2202 } +2203 +2204 if let Some(data) = remaining { +2205 self.path_stats.for_path(path_id).udp_rx.bytes += data.len() as u64; +2206 self.handle_coalesced(now, network_path, path_id, ecn, data); +2207 } +2208 +2209 if let Some(path) = self.paths.get_mut(&path_id) { +2210 self.qlog +2211 .emit_recovery_metrics(path_id, &mut path.data, now); 2212 } -2213 } -2214 NewIdentifiers(ids, now, cid_len, cid_lifetime) => { -2215 let path_id = ids.first().map(|issued| issued.path_id).unwrap_or_default(); -2216 debug_assert!(ids.iter().all(|issued| issued.path_id == path_id)); -2217 let cid_state = self -2218 .local_cid_state -2219 .entry(path_id) -2220 .or_insert_with(|| CidState::new(cid_len, cid_lifetime, now, 0)); -2221 cid_state.new_cids(&ids, now); -2222 -2223 ids.into_iter().rev().for_each(|frame| { -2224 self.spaces[SpaceId::Data].pending.new_cids.push(frame); -2225 }); -2226 // Always update Timer::PushNewCid -2227 self.reset_cid_retirement(now); -2228 } -2229 } -2230 } -2231 -2232 /// Returns whether a packet can be discarded early. -2233 /// -2234 /// Packets sent on the wrong network path can be entirely ignored, saving further -2235 /// processing. -2236 /// -2237 /// Returns true if a packet coming in for this `path_id` over given `network_path` -2238 /// should be discarded. -2239 fn early_discard_packet(&mut self, network_path: FourTuple, path_id: PathId) -> bool { -2240 if self.is_handshaking() && path_id != PathId::ZERO { -2241 debug!(%network_path, %path_id, "discarding multipath packet during handshake"); -2242 return true; -2243 } -2244 -2245 // TODO(flub): In RFC9000 the server is allowed to send off-path probing packets -2246 // once the client has been probing such a 4-tuple. These probes are currently -2247 // not yet recognised and discarded here. -2248 // See https://github.com/n0-computer/noq/issues/607. -2249 let remote_may_migrate = self.remote_may_migrate(); -2250 -2251 let local_ip_may_migrate = self.local_ip_may_migrate(); -2252 -2253 // If this packet could initiate a migration and we're a client or a server that -2254 // forbids migration, drop the datagram. This could be relaxed to heuristically -2255 // permit NAT-rebinding-like migration. -2256 if let Some(known_path) = self.path_mut(path_id) { -2257 if network_path.remote != known_path.network_path.remote && !remote_may_migrate { -2258 trace!( -2259 %path_id, -2260 %network_path, -2261 %known_path.network_path, -2262 "discarding packet from unrecognized peer" -2263 ); -2264 return true; -2265 } -2266 -2267 if known_path.network_path.local_ip.is_some() -2268 && network_path.local_ip.is_some() -2269 && known_path.network_path.local_ip != network_path.local_ip -2270 && !local_ip_may_migrate -2271 { -2272 trace!( -2273 %path_id, -2274 %network_path, -2275 %known_path.network_path, -2276 "discarding packet sent to incorrect interface" -2277 ); -2278 return true; -2279 } -2280 } -2281 false -2282 } -2283 -2284 /// Whether a remote is allowed to migrate. -2285 /// -2286 /// QUIC relies on stable endpoints during the handshake. So other than the server's -2287 /// preferred_address transport parameter no side may migrate before the handshake is -2288 /// completed. -2289 /// -2290 /// In RFC9000 only the client may migrate. If QNT is negotiated the server may migrate -2291 /// as well. +2213 +2214 if was_anti_amplification_blocked { +2215 // A prior attempt to set the loss detection timer may have failed due to +2216 // anti-amplification, so ensure it's set now. Prevents a handshake deadlock if +2217 // the server's first flight is lost. +2218 self.set_loss_detection_timer(now, path_id); +2219 } +2220 } +2221 NewIdentifiers(ids, now, cid_len, cid_lifetime) => { +2222 let path_id = ids.first().map(|issued| issued.path_id).unwrap_or_default(); +2223 debug_assert!(ids.iter().all(|issued| issued.path_id == path_id)); +2224 let cid_state = self +2225 .local_cid_state +2226 .entry(path_id) +2227 .or_insert_with(|| CidState::new(cid_len, cid_lifetime, now, 0)); +2228 cid_state.new_cids(&ids, now); +2229 +2230 ids.into_iter().rev().for_each(|frame| { +2231 self.spaces[SpaceId::Data].pending.new_cids.push(frame); +2232 }); +2233 // Always update Timer::PushNewCid +2234 self.reset_cid_retirement(now); +2235 } +2236 } +2237 } +2238 +2239 /// Returns whether a packet can be discarded early. +2240 /// +2241 /// Packets sent on the wrong network path can be entirely ignored, saving further +2242 /// processing. +2243 /// +2244 /// Returns true if a packet coming in for this `path_id` over given `network_path` +2245 /// should be discarded. +2246 fn early_discard_packet(&mut self, network_path: FourTuple, path_id: PathId) -> bool { +2247 if self.is_handshaking() && path_id != PathId::ZERO { +2248 debug!(%network_path, %path_id, "discarding multipath packet during handshake"); +2249 return true; +2250 } +2251 +2252 // TODO(flub): In RFC9000 the server is allowed to send off-path probing packets +2253 // once the client has been probing such a 4-tuple. These probes are currently +2254 // not yet recognised and discarded here. +2255 // See https://github.com/n0-computer/noq/issues/607. +2256 let remote_may_migrate = self.remote_may_migrate(); +2257 +2258 let local_ip_may_migrate = self.local_ip_may_migrate(); +2259 +2260 // If this packet could initiate a migration and we're a client or a server that +2261 // forbids migration, drop the datagram. This could be relaxed to heuristically +2262 // permit NAT-rebinding-like migration. +2263 if let Some(known_path) = self.path_mut(path_id) { +2264 if network_path.remote != known_path.network_path.remote && !remote_may_migrate { +2265 trace!( +2266 %path_id, +2267 %network_path, +2268 %known_path.network_path, +2269 "discarding packet from unrecognized peer" +2270 ); +2271 return true; +2272 } +2273 +2274 if known_path.network_path.local_ip.is_some() +2275 && network_path.local_ip.is_some() +2276 && known_path.network_path.local_ip != network_path.local_ip +2277 && !local_ip_may_migrate +2278 { +2279 trace!( +2280 %path_id, +2281 %network_path, +2282 %known_path.network_path, +2283 "discarding packet sent to incorrect interface" +2284 ); +2285 return true; +2286 } +2287 } +2288 false +2289 } +2290 +2291 /// Whether a remote is allowed to migrate. 2292 /// -2293 /// Additionally for iroh we allow the server to migrate once during the handshake as -2294 /// long as the client has not received an authenticated Handshake packet. This allows -2295 /// us to duplicate client Initial packets to multiple destinations. See -2296 /// [`state::Handshake::allow_server_migration`]. -2297 fn remote_may_migrate(&self) -> bool { -2298 match &self.side { -2299 ConnectionSide::Server { server_config } => { -2300 server_config.migration && self.is_handshake_confirmed() -2301 } -2302 ConnectionSide::Client { .. } => { -2303 if let Some(hs) = self.state.as_handshake() { -2304 hs.allow_server_migration -2305 } else { -2306 self.n0_nat_traversal.is_negotiated() && self.is_handshake_confirmed() -2307 } +2293 /// QUIC relies on stable endpoints during the handshake. So other than the server's +2294 /// preferred_address transport parameter no side may migrate before the handshake is +2295 /// completed. +2296 /// +2297 /// In RFC9000 only the client may migrate. If QNT is negotiated the server may migrate +2298 /// as well. +2299 /// +2300 /// Additionally for iroh we allow the server to migrate once during the handshake as +2301 /// long as the client has not received an authenticated Handshake packet. This allows +2302 /// us to duplicate client Initial packets to multiple destinations. See +2303 /// [`state::Handshake::allow_server_migration`]. +2304 fn remote_may_migrate(&self) -> bool { +2305 match &self.side { +2306 ConnectionSide::Server { server_config } => { +2307 server_config.migration && self.is_handshake_confirmed() 2308 } -2309 } -2310 } -2311 -2312 /// Whether our local IP address is allowed to change with new incoming packets. -2313 /// -2314 /// Incoming packets show us the local IP address we received a packet on, which could -2315 /// be different from what we thought due to e.g. NAT rebinding or moving from mobile -2316 /// data to WiFi without being notified of the network change. -2317 /// -2318 /// This is only allowed to happen after the handshake is confirmed and when we are the -2319 /// client. Unless QNT is negotiated in which case the server is also allowed to -2320 /// migrate. -2321 /// -2322 /// Be aware that probing packets, which do not exist in Multipath without QNT, are -2323 /// exempt from this. -2324 fn local_ip_may_migrate(&self) -> bool { -2325 (self.side.is_client() || self.n0_nat_traversal.is_negotiated()) -2326 && self.is_handshake_confirmed() -2327 } -2328 /// Process timer expirations -2329 /// -2330 /// Executes protocol logic, potentially preparing signals (including application `Event`s, -2331 /// `EndpointEvent`s and outgoing datagrams) that should be extracted through the relevant -2332 /// methods. -2333 /// -2334 /// It is most efficient to call this immediately after the system clock reaches the latest -2335 /// `Instant` that was output by `poll_timeout`; however spurious extra calls will simply -2336 /// no-op and therefore are safe. -2337 pub fn handle_timeout(&mut self, now: Instant) { -2338 while let Some((timer, _time)) = self.timers.expire_before(now, &self.qlog) { -2339 let span = match timer { -2340 Timer::Conn(timer) => trace_span!("timeout", scope = "conn", ?timer), -2341 Timer::PerPath(path_id, timer) => { -2342 trace_span!("timer_fired", scope="path", %path_id, ?timer) -2343 } -2344 }; -2345 let _guard = span.enter(); -2346 trace!("timeout"); -2347 match timer { -2348 Timer::Conn(timer) => match timer { -2349 ConnTimer::Close => { -2350 self.state.move_to_drained(None); -2351 // move_to_drained checks that we weren't in drained before. -2352 // Adding events to endpoint_events is only legal if `Drained` was never queued before. -2353 self.endpoint_events.push_back(EndpointEventInner::Drained); -2354 } -2355 ConnTimer::Idle => { -2356 self.kill(ConnectionError::TimedOut); -2357 } -2358 ConnTimer::KeepAlive => { -2359 self.ping(); -2360 } -2361 ConnTimer::KeyDiscard => { -2362 self.crypto_state.discard_temporary_keys(); -2363 } -2364 ConnTimer::PushNewCid => { -2365 while let Some((path_id, when)) = self.next_cid_retirement() { -2366 if when > now { -2367 break; -2368 } -2369 match self.local_cid_state.get_mut(&path_id) { -2370 None => error!(%path_id, "No local CID state for path"), -2371 Some(cid_state) => { -2372 // Update `retire_prior_to` field in NEW_CONNECTION_ID frame -2373 let num_new_cid = cid_state.on_cid_timeout().into(); -2374 if !self.state.is_closed() { -2375 trace!( -2376 "push a new CID to peer RETIRE_PRIOR_TO field {}", -2377 cid_state.retire_prior_to() -2378 ); -2379 self.endpoint_events.push_back( -2380 EndpointEventInner::NeedIdentifiers( -2381 path_id, -2382 now, -2383 num_new_cid, -2384 ), +2309 ConnectionSide::Client { .. } => { +2310 if let Some(hs) = self.state.as_handshake() { +2311 hs.allow_server_migration +2312 } else { +2313 self.n0_nat_traversal.is_negotiated() && self.is_handshake_confirmed() +2314 } +2315 } +2316 } +2317 } +2318 +2319 /// Whether our local IP address is allowed to change with new incoming packets. +2320 /// +2321 /// Incoming packets show us the local IP address we received a packet on, which could +2322 /// be different from what we thought due to e.g. NAT rebinding or moving from mobile +2323 /// data to WiFi without being notified of the network change. +2324 /// +2325 /// This is only allowed to happen after the handshake is confirmed and when we are the +2326 /// client. Unless QNT is negotiated in which case the server is also allowed to +2327 /// migrate. +2328 /// +2329 /// Be aware that probing packets, which do not exist in Multipath without QNT, are +2330 /// exempt from this. +2331 fn local_ip_may_migrate(&self) -> bool { +2332 (self.side.is_client() || self.n0_nat_traversal.is_negotiated()) +2333 && self.is_handshake_confirmed() +2334 } +2335 /// Process timer expirations +2336 /// +2337 /// Executes protocol logic, potentially preparing signals (including application `Event`s, +2338 /// `EndpointEvent`s and outgoing datagrams) that should be extracted through the relevant +2339 /// methods. +2340 /// +2341 /// It is most efficient to call this immediately after the system clock reaches the latest +2342 /// `Instant` that was output by `poll_timeout`; however spurious extra calls will simply +2343 /// no-op and therefore are safe. +2344 pub fn handle_timeout(&mut self, now: Instant) { +2345 while let Some((timer, _time)) = self.timers.expire_before(now, &self.qlog) { +2346 let span = match timer { +2347 Timer::Conn(timer) => trace_span!("timeout", scope = "conn", ?timer), +2348 Timer::PerPath(path_id, timer) => { +2349 trace_span!("timer_fired", scope="path", %path_id, ?timer) +2350 } +2351 }; +2352 let _guard = span.enter(); +2353 trace!("timeout"); +2354 match timer { +2355 Timer::Conn(timer) => match timer { +2356 ConnTimer::Close => { +2357 self.state.move_to_drained(None); +2358 // move_to_drained checks that we weren't in drained before. +2359 // Adding events to endpoint_events is only legal if `Drained` was never queued before. +2360 self.endpoint_events.push_back(EndpointEventInner::Drained); +2361 } +2362 ConnTimer::Idle => { +2363 self.kill(ConnectionError::TimedOut); +2364 } +2365 ConnTimer::KeepAlive => { +2366 self.ping(); +2367 } +2368 ConnTimer::KeyDiscard => { +2369 self.crypto_state.discard_temporary_keys(); +2370 } +2371 ConnTimer::PushNewCid => { +2372 while let Some((path_id, when)) = self.next_cid_retirement() { +2373 if when > now { +2374 break; +2375 } +2376 match self.local_cid_state.get_mut(&path_id) { +2377 None => error!(%path_id, "No local CID state for path"), +2378 Some(cid_state) => { +2379 // Update `retire_prior_to` field in NEW_CONNECTION_ID frame +2380 let num_new_cid = cid_state.on_cid_timeout().into(); +2381 if !self.state.is_closed() { +2382 trace!( +2383 "push a new CID to peer RETIRE_PRIOR_TO field {}", +2384 cid_state.retire_prior_to() 2385 ); -2386 } -2387 } -2388 } -2389 } -2390 } -2391 ConnTimer::NoAvailablePath => { -2392 // Grace period expired: all paths were abandoned and no new path -2393 // was opened. Close the connection. There are no paths left to -2394 // send CONNECTION_CLOSE on, so this is a silent close. -2395 // https://www.ietf.org/archive/id/draft-ietf-quic-multipath-21.html#section-3.4-8 -2396 if self.state.is_closed() || self.state.is_drained() { -2397 // Connection already closing/drained (e.g. application called -2398 // close() before the grace timer fired). Nothing to do. -2399 error!("no viable path timer fired, but connection already closing"); -2400 } else { -2401 trace!("no viable path grace period expired, closing connection"); -2402 let err = TransportError::NO_VIABLE_PATH( -2403 "last path abandoned, no new path opened", -2404 ); -2405 self.close_common(); -2406 self.set_close_timer(now); -2407 self.connection_close_pending = true; -2408 self.state.move_to_closed(err); -2409 } -2410 } -2411 ConnTimer::NatTraversalProbeRetry => { -2412 if let Ok(server_state) = self.n0_nat_traversal.server_side_mut() -2413 && server_state.queue_retries() -2414 { -2415 let delay = -2416 RttEstimator::new(self.config.initial_rtt).pto_base() * 2 / 3; -2417 self.timers.set( -2418 Timer::Conn(ConnTimer::NatTraversalProbeRetry), -2419 now + delay, -2420 self.qlog.with_time(now), -2421 ); -2422 trace!("off-path probe retry timer fired, re-queued probes"); -2423 } -2424 } -2425 }, -2426 Timer::PerPath(path_id, timer) => { -2427 match timer { -2428 PathTimer::PathIdle => { -2429 if let Err(err) = -2430 self.close_path_inner(now, path_id, PathAbandonReason::TimedOut) -2431 { -2432 warn!(?err, "failed closing path"); -2433 } -2434 } -2435 -2436 PathTimer::PathKeepAlive => { -2437 self.ping_path(path_id).ok(); -2438 } -2439 PathTimer::LossDetection => { -2440 self.on_loss_detection_timeout(now, path_id); -2441 self.qlog.emit_recovery_metrics( -2442 path_id, -2443 &mut self -2444 .paths -2445 .get_mut(&path_id) -2446 .expect("loss-detection timer fires only on live paths") -2447 .data, -2448 now, -2449 ); -2450 } -2451 PathTimer::PathValidationFailed => { -2452 let Some(path) = self.paths.get_mut(&path_id) else { -2453 continue; -2454 }; -2455 self.timers.stop( -2456 Timer::PerPath(path_id, PathTimer::PathChallengeLost), -2457 self.qlog.with_time(now), -2458 ); -2459 debug!("path migration validation failed"); -2460 if let Some((_, prev)) = path.prev.take() { -2461 path.data = prev; -2462 } -2463 path.data.reset_on_path_challenges(); -2464 } -2465 PathTimer::PathChallengeLost => { -2466 let Some(path) = self.paths.get_mut(&path_id) else { -2467 continue; -2468 }; -2469 trace!("path challenge deemed lost"); -2470 path.data.pending_on_path_challenge = true; +2386 self.endpoint_events.push_back( +2387 EndpointEventInner::NeedIdentifiers( +2388 path_id, +2389 now, +2390 num_new_cid, +2391 ), +2392 ); +2393 } +2394 } +2395 } +2396 } +2397 } +2398 ConnTimer::NoAvailablePath => { +2399 // Grace period expired: all paths were abandoned and no new path +2400 // was opened. Close the connection. There are no paths left to +2401 // send CONNECTION_CLOSE on, so this is a silent close. +2402 // https://www.ietf.org/archive/id/draft-ietf-quic-multipath-21.html#section-3.4-8 +2403 if self.state.is_closed() || self.state.is_drained() { +2404 // Connection already closing/drained (e.g. application called +2405 // close() before the grace timer fired). Nothing to do. +2406 error!("no viable path timer fired, but connection already closing"); +2407 } else { +2408 trace!("no viable path grace period expired, closing connection"); +2409 let err = TransportError::NO_VIABLE_PATH( +2410 "last path abandoned, no new path opened", +2411 ); +2412 self.close_common(); +2413 self.set_close_timer(now); +2414 self.connection_close_pending = true; +2415 self.state.move_to_closed(err); +2416 } +2417 } +2418 ConnTimer::NatTraversalProbeRetry => { +2419 if self.n0_nat_traversal.queue_retries(self.is_ipv6()) { +2420 let delay = +2421 RttEstimator::new(self.config.initial_rtt).pto_base() * 2 / 3; +2422 self.timers.set( +2423 Timer::Conn(ConnTimer::NatTraversalProbeRetry), +2424 now + delay, +2425 self.qlog.with_time(now), +2426 ); +2427 trace!("re-queued NAT probes"); +2428 } else { +2429 trace!("no more NAT probes remaining"); +2430 } +2431 } +2432 }, +2433 Timer::PerPath(path_id, timer) => { +2434 match timer { +2435 PathTimer::PathIdle => { +2436 if let Err(err) = +2437 self.close_path_inner(now, path_id, PathAbandonReason::TimedOut) +2438 { +2439 warn!(?err, "failed closing path"); +2440 } +2441 } +2442 +2443 PathTimer::PathKeepAlive => { +2444 self.ping_path(path_id).ok(); +2445 } +2446 PathTimer::LossDetection => { +2447 self.on_loss_detection_timeout(now, path_id); +2448 self.qlog.emit_recovery_metrics( +2449 path_id, +2450 &mut self +2451 .paths +2452 .get_mut(&path_id) +2453 .expect("loss-detection timer fires only on live paths") +2454 .data, +2455 now, +2456 ); +2457 } +2458 PathTimer::PathValidationFailed => { +2459 let Some(path) = self.paths.get_mut(&path_id) else { +2460 continue; +2461 }; +2462 self.timers.stop( +2463 Timer::PerPath(path_id, PathTimer::PathChallengeLost), +2464 self.qlog.with_time(now), +2465 ); +2466 debug!("path migration validation failed"); +2467 if let Some((_, prev)) = path.prev.take() { +2468 path.data = prev; +2469 } +2470 path.data.reset_on_path_challenges(); 2471 } -2472 PathTimer::AbandonFromValidation => { +2472 PathTimer::PathChallengeLost => { 2473 let Some(path) = self.paths.get_mut(&path_id) else { 2474 continue; 2475 }; -2476 path.data.reset_on_path_challenges(); -2477 self.timers.stop( -2478 Timer::PerPath(path_id, PathTimer::PathChallengeLost), -2479 self.qlog.with_time(now), -2480 ); -2481 debug!("new path validation failed"); -2482 if let Err(err) = self.close_path_inner( -2483 now, -2484 path_id, -2485 PathAbandonReason::ValidationFailed, -2486 ) { -2487 warn!(?err, "failed closing path"); -2488 } -2489 } -2490 PathTimer::Pacing => {} -2491 PathTimer::MaxAckDelay => { -2492 // This timer is only armed in the Data space -2493 self.spaces[SpaceId::Data] -2494 .for_path(path_id) -2495 .pending_acks -2496 .on_max_ack_delay_timeout() -2497 } -2498 PathTimer::PathDrained => { -2499 // The path was abandoned and 3*PTO has expired since. Clean up all -2500 // remaining state and install stateless reset token. -2501 self.timers.stop_per_path(path_id, self.qlog.with_time(now)); -2502 if let Some(local_cid_state) = self.local_cid_state.remove(&path_id) { -2503 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events. All timers should be cleared in drained connections. -2504 let (min_seq, max_seq) = local_cid_state.active_seq(); -2505 for seq in min_seq..=max_seq { -2506 self.endpoint_events.push_back( -2507 EndpointEventInner::RetireConnectionId( -2508 now, path_id, seq, false, -2509 ), -2510 ); -2511 } -2512 } -2513 self.discard_path(path_id, now); -2514 } -2515 } -2516 } -2517 } -2518 } -2519 } -2520 -2521 /// Close a connection immediately -2522 /// -2523 /// This does not ensure delivery of outstanding data. It is the application's responsibility to -2524 /// call this only when all important communications have been completed, e.g. by calling -2525 /// [`SendStream::finish`] on outstanding streams and waiting for the corresponding -2526 /// [`StreamEvent::Finished`] event. -2527 /// -2528 /// If [`Streams::send_streams`] returns 0, all outstanding stream data has been -2529 /// delivered. There may still be data from the peer that has not been received. -2530 /// -2531 /// [`StreamEvent::Finished`]: crate::StreamEvent::Finished -2532 pub fn close(&mut self, now: Instant, error_code: VarInt, reason: Bytes) { -2533 self.close_inner( -2534 now, -2535 Close::Application(frame::ApplicationClose { error_code, reason }), -2536 ) -2537 } -2538 -2539 /// Close the connection immediately, initiated by an API call. -2540 /// -2541 /// This will not produce a [`ConnectionLost`] event propagated by the -2542 /// [`Connection::poll`] call, because the API call already propagated the error to the -2543 /// user. -2544 /// -2545 /// Not to be used when entering immediate close due to an internal state change based -2546 /// on an event. See [`State::move_to_closed_local`] for details. +2476 trace!("path challenge deemed lost"); +2477 path.data.pending_on_path_challenge = true; +2478 } +2479 PathTimer::AbandonFromValidation => { +2480 let Some(path) = self.paths.get_mut(&path_id) else { +2481 continue; +2482 }; +2483 path.data.reset_on_path_challenges(); +2484 self.timers.stop( +2485 Timer::PerPath(path_id, PathTimer::PathChallengeLost), +2486 self.qlog.with_time(now), +2487 ); +2488 debug!("new path validation failed"); +2489 if let Err(err) = self.close_path_inner( +2490 now, +2491 path_id, +2492 PathAbandonReason::ValidationFailed, +2493 ) { +2494 warn!(?err, "failed closing path"); +2495 } +2496 } +2497 PathTimer::Pacing => {} +2498 PathTimer::MaxAckDelay => { +2499 // This timer is only armed in the Data space +2500 self.spaces[SpaceId::Data] +2501 .for_path(path_id) +2502 .pending_acks +2503 .on_max_ack_delay_timeout() +2504 } +2505 PathTimer::PathDrained => { +2506 // The path was abandoned and 3*PTO has expired since. Clean up all +2507 // remaining state and install stateless reset token. +2508 self.timers.stop_per_path(path_id, self.qlog.with_time(now)); +2509 if let Some(local_cid_state) = self.local_cid_state.remove(&path_id) { +2510 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events. All timers should be cleared in drained connections. +2511 let (min_seq, max_seq) = local_cid_state.active_seq(); +2512 for seq in min_seq..=max_seq { +2513 self.endpoint_events.push_back( +2514 EndpointEventInner::RetireConnectionId( +2515 now, path_id, seq, false, +2516 ), +2517 ); +2518 } +2519 } +2520 self.discard_path(path_id, now); +2521 } +2522 } +2523 } +2524 } +2525 } +2526 } +2527 +2528 /// Close a connection immediately +2529 /// +2530 /// This does not ensure delivery of outstanding data. It is the application's responsibility to +2531 /// call this only when all important communications have been completed, e.g. by calling +2532 /// [`SendStream::finish`] on outstanding streams and waiting for the corresponding +2533 /// [`StreamEvent::Finished`] event. +2534 /// +2535 /// If [`Streams::send_streams`] returns 0, all outstanding stream data has been +2536 /// delivered. There may still be data from the peer that has not been received. +2537 /// +2538 /// [`StreamEvent::Finished`]: crate::StreamEvent::Finished +2539 pub fn close(&mut self, now: Instant, error_code: VarInt, reason: Bytes) { +2540 self.close_inner( +2541 now, +2542 Close::Application(frame::ApplicationClose { error_code, reason }), +2543 ) +2544 } +2545 +2546 /// Close the connection immediately, initiated by an API call. 2547 /// -2548 /// This initiates immediate close from -2549 /// <https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2>, moving to the closed -2550 /// state. +2548 /// This will not produce a [`ConnectionLost`] event propagated by the +2549 /// [`Connection::poll`] call, because the API call already propagated the error to the +2550 /// user. 2551 /// -2552 /// [`ConnectionLost`]: crate::Event::ConnectionLost -2553 /// [`Connection::poll`]: super::Connection::poll -2554 fn close_inner(&mut self, now: Instant, reason: Close) { -2555 let was_closed = self.state.is_closed(); -2556 if !was_closed { -2557 self.close_common(); -2558 self.set_close_timer(now); -2559 self.connection_close_pending = true; -2560 self.state.move_to_closed_local(reason); -2561 } -2562 } -2563 -2564 /// Control datagrams -2565 pub fn datagrams(&mut self) -> Datagrams<'_> { -2566 Datagrams { conn: self } -2567 } -2568 -2569 /// Returns connection statistics -2570 pub fn stats(&mut self) -> ConnectionStats { -2571 let mut stats = self.partial_stats.clone(); -2572 -2573 for path_stats in self.path_stats.iter_stats() { -2574 // Self::path_stats() computes the path rtt, cwnd and current_mtu on access -2575 // because they are not simple counters. When computing the connection stats we -2576 // can skip that effort since those fields are not used in the `impl -2577 // Add<PathStats> for ConnectionStats`. -2578 stats += *path_stats; -2579 } -2580 -2581 stats -2582 } -2583 -2584 /// Returns path statistics -2585 pub fn path_stats(&mut self, path_id: PathId) -> Option<PathStats> { -2586 let path = self.paths.get(&path_id)?; -2587 let stats = self.path_stats.for_path(path_id); -2588 stats.rtt = path.data.rtt.get(); -2589 stats.cwnd = path.data.congestion.window(); -2590 stats.current_mtu = path.data.mtud.current_mtu(); -2591 Some(*stats) -2592 } -2593 -2594 /// Ping the remote endpoint -2595 /// -2596 /// Causes an ACK-eliciting packet to be transmitted on the connection. -2597 pub fn ping(&mut self) { -2598 // TODO(flub): This is very brute-force: it pings *all* the paths. Instead it would -2599 // be nice if we could only send a single packet for this. -2600 for path_data in self.spaces[self.highest_space].number_spaces.values_mut() { -2601 path_data.ping_pending = true; -2602 } -2603 } -2604 -2605 /// Ping the remote endpoint over a specific path -2606 /// -2607 /// Causes an ACK-eliciting packet to be transmitted on the path. -2608 pub fn ping_path(&mut self, path: PathId) -> Result<(), ClosedPath> { -2609 let path_data = self.spaces[self.highest_space] -2610 .number_spaces -2611 .get_mut(&path) -2612 .ok_or(ClosedPath { _private: () })?; -2613 path_data.ping_pending = true; -2614 Ok(()) -2615 } -2616 -2617 /// Update traffic keys spontaneously -2618 /// -2619 /// This can be useful for testing key updates, as they otherwise only happen infrequently. -2620 pub fn force_key_update(&mut self) { -2621 if !self.state.is_established() { -2622 debug!("ignoring forced key update in illegal state"); -2623 return; -2624 } -2625 if self.crypto_state.prev_crypto.is_some() { -2626 // We already just updated, or are currently updating, the keys. Concurrent key updates -2627 // are illegal. -2628 debug!("ignoring redundant forced key update"); -2629 return; -2630 } -2631 self.crypto_state.update_keys(None, false); -2632 } -2633 -2634 /// Get a session reference -2635 pub fn crypto_session(&self) -> &dyn crypto::Session { -2636 self.crypto_state.session.as_ref() -2637 } -2638 -2639 /// Whether the connection is in the process of being established -2640 /// -2641 /// If this returns `false`, the connection may be either established or closed, signaled by the -2642 /// emission of a [`Connected`](Event::Connected) or [`ConnectionLost`](Event::ConnectionLost) -2643 /// event respectively. Note that locally-initiated closes via [`close()`](Self::close) do not -2644 /// emit a `ConnectionLost` event. -2645 /// -2646 /// For an established connection this essentially means the handshake is **completed**, -2647 /// but not necessarily yet confirmed. -2648 pub fn is_handshaking(&self) -> bool { -2649 self.state.is_handshake() -2650 } -2651 -2652 /// Whether the connection is closed -2653 /// -2654 /// Closed connections cannot transport any further data. A connection becomes closed when -2655 /// either peer application intentionally closes it, or when either transport layer detects an -2656 /// error such as a time-out or certificate validation failure. -2657 /// -2658 /// A [`ConnectionLost`](Event::ConnectionLost) event is emitted with details when the -2659 /// connection is closed by the peer or due to an error. When the local application closes -2660 /// the connection via [`close()`](Self::close), no `ConnectionLost` event is emitted; -2661 /// instead, pending operations fail with [`ConnectionError::LocallyClosed`]. -2662 pub fn is_closed(&self) -> bool { -2663 self.state.is_closed() -2664 } -2665 -2666 /// Whether there is no longer any need to keep the connection around -2667 /// -2668 /// Closed connections become drained after a brief timeout to absorb any remaining in-flight -2669 /// packets from the peer. All drained connections have been closed. -2670 pub fn is_drained(&self) -> bool { -2671 self.state.is_drained() -2672 } -2673 -2674 /// For clients, if the peer accepted the 0-RTT data packets -2675 /// -2676 /// The value is meaningless until after the handshake completes. -2677 pub fn accepted_0rtt(&self) -> bool { -2678 self.crypto_state.accepted_0rtt +2552 /// Not to be used when entering immediate close due to an internal state change based +2553 /// on an event. See [`State::move_to_closed_local`] for details. +2554 /// +2555 /// This initiates immediate close from +2556 /// <https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2>, moving to the closed +2557 /// state. +2558 /// +2559 /// [`ConnectionLost`]: crate::Event::ConnectionLost +2560 /// [`Connection::poll`]: super::Connection::poll +2561 fn close_inner(&mut self, now: Instant, reason: Close) { +2562 let was_closed = self.state.is_closed(); +2563 if !was_closed { +2564 self.close_common(); +2565 self.set_close_timer(now); +2566 self.connection_close_pending = true; +2567 self.state.move_to_closed_local(reason); +2568 } +2569 } +2570 +2571 /// Control datagrams +2572 pub fn datagrams(&mut self) -> Datagrams<'_> { +2573 Datagrams { conn: self } +2574 } +2575 +2576 /// Returns connection statistics +2577 pub fn stats(&mut self) -> ConnectionStats { +2578 let mut stats = self.partial_stats.clone(); +2579 +2580 for path_stats in self.path_stats.iter_stats() { +2581 // Self::path_stats() computes the path rtt, cwnd and current_mtu on access +2582 // because they are not simple counters. When computing the connection stats we +2583 // can skip that effort since those fields are not used in the `impl +2584 // Add<PathStats> for ConnectionStats`. +2585 stats += *path_stats; +2586 } +2587 +2588 stats +2589 } +2590 +2591 /// Returns path statistics +2592 pub fn path_stats(&mut self, path_id: PathId) -> Option<PathStats> { +2593 let path = self.paths.get(&path_id)?; +2594 let stats = self.path_stats.for_path(path_id); +2595 stats.rtt = path.data.rtt.get(); +2596 stats.cwnd = path.data.congestion.window(); +2597 stats.current_mtu = path.data.mtud.current_mtu(); +2598 Some(*stats) +2599 } +2600 +2601 /// Ping the remote endpoint +2602 /// +2603 /// Causes an ACK-eliciting packet to be transmitted on the connection. +2604 pub fn ping(&mut self) { +2605 // TODO(flub): This is very brute-force: it pings *all* the paths. Instead it would +2606 // be nice if we could only send a single packet for this. +2607 for path_data in self.spaces[self.highest_space].number_spaces.values_mut() { +2608 path_data.ping_pending = true; +2609 } +2610 } +2611 +2612 /// Ping the remote endpoint over a specific path +2613 /// +2614 /// Causes an ACK-eliciting packet to be transmitted on the path. +2615 pub fn ping_path(&mut self, path: PathId) -> Result<(), ClosedPath> { +2616 let path_data = self.spaces[self.highest_space] +2617 .number_spaces +2618 .get_mut(&path) +2619 .ok_or(ClosedPath { _private: () })?; +2620 path_data.ping_pending = true; +2621 Ok(()) +2622 } +2623 +2624 /// Update traffic keys spontaneously +2625 /// +2626 /// This can be useful for testing key updates, as they otherwise only happen infrequently. +2627 pub fn force_key_update(&mut self) { +2628 if !self.state.is_established() { +2629 debug!("ignoring forced key update in illegal state"); +2630 return; +2631 } +2632 if self.crypto_state.prev_crypto.is_some() { +2633 // We already just updated, or are currently updating, the keys. Concurrent key updates +2634 // are illegal. +2635 debug!("ignoring redundant forced key update"); +2636 return; +2637 } +2638 self.crypto_state.update_keys(None, false); +2639 } +2640 +2641 /// Get a session reference +2642 pub fn crypto_session(&self) -> &dyn crypto::Session { +2643 self.crypto_state.session.as_ref() +2644 } +2645 +2646 /// Whether the connection is in the process of being established +2647 /// +2648 /// If this returns `false`, the connection may be either established or closed, signaled by the +2649 /// emission of a [`Connected`](Event::Connected) or [`ConnectionLost`](Event::ConnectionLost) +2650 /// event respectively. Note that locally-initiated closes via [`close()`](Self::close) do not +2651 /// emit a `ConnectionLost` event. +2652 /// +2653 /// For an established connection this essentially means the handshake is **completed**, +2654 /// but not necessarily yet confirmed. +2655 pub fn is_handshaking(&self) -> bool { +2656 self.state.is_handshake() +2657 } +2658 +2659 /// Whether the connection is closed +2660 /// +2661 /// Closed connections cannot transport any further data. A connection becomes closed when +2662 /// either peer application intentionally closes it, or when either transport layer detects an +2663 /// error such as a time-out or certificate validation failure. +2664 /// +2665 /// A [`ConnectionLost`](Event::ConnectionLost) event is emitted with details when the +2666 /// connection is closed by the peer or due to an error. When the local application closes +2667 /// the connection via [`close()`](Self::close), no `ConnectionLost` event is emitted; +2668 /// instead, pending operations fail with [`ConnectionError::LocallyClosed`]. +2669 pub fn is_closed(&self) -> bool { +2670 self.state.is_closed() +2671 } +2672 +2673 /// Whether there is no longer any need to keep the connection around +2674 /// +2675 /// Closed connections become drained after a brief timeout to absorb any remaining in-flight +2676 /// packets from the peer. All drained connections have been closed. +2677 pub fn is_drained(&self) -> bool { +2678 self.state.is_drained() 2679 } 2680 -2681 /// Whether 0-RTT is/was possible during the handshake -2682 pub fn has_0rtt(&self) -> bool { -2683 self.crypto_state.zero_rtt_enabled -2684 } -2685 -2686 /// Whether there are any pending retransmits -2687 pub fn has_pending_retransmits(&self) -> bool { -2688 !self.spaces[SpaceId::Data].pending.is_empty(&self.streams) -2689 } -2690 -2691 /// Look up whether we're the client or server of this Connection -2692 pub fn side(&self) -> Side { -2693 self.side.side() -2694 } -2695 -2696 /// Get the address observed by the remote over the given path -2697 pub fn path_observed_address(&self, path_id: PathId) -> Result<Option<SocketAddr>, ClosedPath> { -2698 self.path(path_id) -2699 .map(|path_data| { -2700 path_data -2701 .last_observed_addr_report -2702 .as_ref() -2703 .map(|observed| observed.socket_addr()) -2704 }) -2705 .ok_or(ClosedPath { _private: () }) -2706 } -2707 -2708 /// Current best estimate of this connection's latency (round-trip-time) -2709 pub fn rtt(&self, path_id: PathId) -> Option<Duration> { -2710 self.path(path_id).map(|d| d.rtt.get()) -2711 } -2712 -2713 /// Current state of this connection's congestion controller, for debugging purposes -2714 pub fn congestion_state(&self, path_id: PathId) -> Option<&dyn Controller> { -2715 self.path(path_id).map(|d| d.congestion.as_ref()) -2716 } -2717 -2718 /// Modify the number of remotely initiated streams that may be concurrently open -2719 /// -2720 /// No streams may be opened by the peer unless fewer than `count` are already open. Large -2721 /// `count`s increase both minimum and worst-case memory consumption. -2722 pub fn set_max_concurrent_streams(&mut self, dir: Dir, count: VarInt) { -2723 self.streams.set_max_concurrent(dir, count); -2724 // If the limit was reduced, then a flow control update previously deemed insignificant may -2725 // now be significant. -2726 let pending = &mut self.spaces[SpaceId::Data].pending; -2727 self.streams.queue_max_stream_id(pending); -2728 } -2729 -2730 /// Modify the number of open paths allowed when multipath is enabled -2731 /// -2732 /// When reducing the number of concurrent paths this will only affect delaying sending -2733 /// new MAX_PATH_ID frames until fewer than this number of paths are possible. To -2734 /// actively reduce paths they must be closed using [`Connection::close_path`], which -2735 /// can also be used to close not-yet-opened paths. -2736 /// -2737 /// If multipath is not negotiated (see the [`TransportConfig`]) this can not enable -2738 /// multipath and will fail. -2739 pub fn set_max_concurrent_paths( -2740 &mut self, -2741 now: Instant, -2742 count: NonZeroU32, -2743 ) -> Result<(), MultipathNotNegotiated> { -2744 if !self.is_multipath_negotiated() { -2745 return Err(MultipathNotNegotiated { _private: () }); -2746 } -2747 self.max_concurrent_paths = count; -2748 -2749 let in_use_count = self -2750 .local_max_path_id -2751 .next() -2752 .saturating_sub(self.abandoned_paths.len() as u32) -2753 .as_u32(); -2754 let extra_needed = count.get().saturating_sub(in_use_count); -2755 let new_max_path_id = self.local_max_path_id.saturating_add(extra_needed); -2756 -2757 self.set_max_path_id(now, new_max_path_id); -2758 -2759 Ok(()) -2760 } -2761 -2762 /// If needed, issues a new MAX_PATH_ID frame and new CIDs for any newly allowed paths -2763 fn set_max_path_id(&mut self, now: Instant, max_path_id: PathId) { -2764 if max_path_id <= self.local_max_path_id { -2765 return; -2766 } -2767 -2768 self.local_max_path_id = max_path_id; -2769 self.spaces[SpaceId::Data].pending.max_path_id = true; -2770 -2771 self.issue_first_path_cids(now); -2772 } -2773 -2774 /// Current number of remotely initiated streams that may be concurrently open -2775 /// -2776 /// If the target for this limit is reduced using [`set_max_concurrent_streams`](Self::set_max_concurrent_streams), -2777 /// it will not change immediately, even if fewer streams are open. Instead, it will -2778 /// decrement by one for each time a remotely initiated stream of matching directionality is closed. -2779 pub fn max_concurrent_streams(&self, dir: Dir) -> u64 { -2780 self.streams.max_concurrent(dir) -2781 } -2782 -2783 /// See [`TransportConfig::send_window()`] -2784 pub fn set_send_window(&mut self, send_window: u64) { -2785 self.streams.set_send_window(send_window); -2786 } -2787 -2788 /// See [`TransportConfig::receive_window()`] -2789 pub fn set_receive_window(&mut self, receive_window: VarInt) { -2790 if self.streams.set_receive_window(receive_window) { -2791 self.spaces[SpaceId::Data].pending.max_data = true; -2792 } +2681 /// For clients, if the peer accepted the 0-RTT data packets +2682 /// +2683 /// The value is meaningless until after the handshake completes. +2684 pub fn accepted_0rtt(&self) -> bool { +2685 self.crypto_state.accepted_0rtt +2686 } +2687 +2688 /// Whether 0-RTT is/was possible during the handshake +2689 pub fn has_0rtt(&self) -> bool { +2690 self.crypto_state.zero_rtt_enabled +2691 } +2692 +2693 /// Whether there are any pending retransmits +2694 pub fn has_pending_retransmits(&self) -> bool { +2695 !self.spaces[SpaceId::Data].pending.is_empty(&self.streams) +2696 } +2697 +2698 /// Look up whether we're the client or server of this Connection +2699 pub fn side(&self) -> Side { +2700 self.side.side() +2701 } +2702 +2703 /// Get the address observed by the remote over the given path +2704 pub fn path_observed_address(&self, path_id: PathId) -> Result<Option<SocketAddr>, ClosedPath> { +2705 self.path(path_id) +2706 .map(|path_data| { +2707 path_data +2708 .last_observed_addr_report +2709 .as_ref() +2710 .map(|observed| observed.socket_addr()) +2711 }) +2712 .ok_or(ClosedPath { _private: () }) +2713 } +2714 +2715 /// Current best estimate of this connection's latency (round-trip-time) +2716 pub fn rtt(&self, path_id: PathId) -> Option<Duration> { +2717 self.path(path_id).map(|d| d.rtt.get()) +2718 } +2719 +2720 /// Current state of this connection's congestion controller, for debugging purposes +2721 pub fn congestion_state(&self, path_id: PathId) -> Option<&dyn Controller> { +2722 self.path(path_id).map(|d| d.congestion.as_ref()) +2723 } +2724 +2725 /// Modify the number of remotely initiated streams that may be concurrently open +2726 /// +2727 /// No streams may be opened by the peer unless fewer than `count` are already open. Large +2728 /// `count`s increase both minimum and worst-case memory consumption. +2729 pub fn set_max_concurrent_streams(&mut self, dir: Dir, count: VarInt) { +2730 self.streams.set_max_concurrent(dir, count); +2731 // If the limit was reduced, then a flow control update previously deemed insignificant may +2732 // now be significant. +2733 let pending = &mut self.spaces[SpaceId::Data].pending; +2734 self.streams.queue_max_stream_id(pending); +2735 } +2736 +2737 /// Modify the number of open paths allowed when multipath is enabled +2738 /// +2739 /// When reducing the number of concurrent paths this will only affect delaying sending +2740 /// new MAX_PATH_ID frames until fewer than this number of paths are possible. To +2741 /// actively reduce paths they must be closed using [`Connection::close_path`], which +2742 /// can also be used to close not-yet-opened paths. +2743 /// +2744 /// If multipath is not negotiated (see the [`TransportConfig`]) this can not enable +2745 /// multipath and will fail. +2746 pub fn set_max_concurrent_paths( +2747 &mut self, +2748 now: Instant, +2749 count: NonZeroU32, +2750 ) -> Result<(), MultipathNotNegotiated> { +2751 if !self.is_multipath_negotiated() { +2752 return Err(MultipathNotNegotiated { _private: () }); +2753 } +2754 self.max_concurrent_paths = count; +2755 +2756 let in_use_count = self +2757 .local_max_path_id +2758 .next() +2759 .saturating_sub(self.abandoned_paths.len() as u32) +2760 .as_u32(); +2761 let extra_needed = count.get().saturating_sub(in_use_count); +2762 let new_max_path_id = self.local_max_path_id.saturating_add(extra_needed); +2763 +2764 self.set_max_path_id(now, new_max_path_id); +2765 +2766 Ok(()) +2767 } +2768 +2769 /// If needed, issues a new MAX_PATH_ID frame and new CIDs for any newly allowed paths +2770 fn set_max_path_id(&mut self, now: Instant, max_path_id: PathId) { +2771 if max_path_id <= self.local_max_path_id { +2772 return; +2773 } +2774 +2775 self.local_max_path_id = max_path_id; +2776 self.spaces[SpaceId::Data].pending.max_path_id = true; +2777 +2778 self.issue_first_path_cids(now); +2779 } +2780 +2781 /// Current number of remotely initiated streams that may be concurrently open +2782 /// +2783 /// If the target for this limit is reduced using [`set_max_concurrent_streams`](Self::set_max_concurrent_streams), +2784 /// it will not change immediately, even if fewer streams are open. Instead, it will +2785 /// decrement by one for each time a remotely initiated stream of matching directionality is closed. +2786 pub fn max_concurrent_streams(&self, dir: Dir) -> u64 { +2787 self.streams.max_concurrent(dir) +2788 } +2789 +2790 /// See [`TransportConfig::send_window()`] +2791 pub fn set_send_window(&mut self, send_window: u64) { +2792 self.streams.set_send_window(send_window); 2793 } 2794 -2795 /// Whether the Multipath for QUIC extension is enabled. -2796 /// -2797 /// Multipath is only enabled after the handshake is completed and if it was enabled by both -2798 /// peers. -2799 pub fn is_multipath_negotiated(&self) -> bool { -2800 !self.is_handshaking() -2801 && self.config.max_concurrent_multipath_paths.is_some() -2802 && self.peer_params.initial_max_path_id.is_some() -2803 } -2804 -2805 fn on_ack_received( -2806 &mut self, -2807 now: Instant, -2808 space: SpaceId, -2809 ack: frame::Ack, -2810 ) -> Result<(), TransportError> { -2811 // All ACKs are referencing path 0 -2812 let path = PathId::ZERO; -2813 self.inner_on_ack_received(now, space, path, ack) -2814 } -2815 -2816 fn on_path_ack_received( -2817 &mut self, -2818 now: Instant, -2819 space: SpaceId, -2820 path_ack: frame::PathAck, -2821 ) -> Result<(), TransportError> { -2822 let (ack, path) = path_ack.into_ack(); -2823 self.inner_on_ack_received(now, space, path, ack) -2824 } -2825 -2826 /// Handles an ACK frame acknowledging packets sent on *path*. -2827 fn inner_on_ack_received( -2828 &mut self, -2829 now: Instant, -2830 space: SpaceId, -2831 path: PathId, -2832 ack: frame::Ack, -2833 ) -> Result<(), TransportError> { -2834 if !self.spaces[space].number_spaces.contains_key(&path) { -2835 if self.abandoned_paths.contains(&path) { -2836 // See also -2837 // https://www.ietf.org/archive/id/draft-ietf-quic-multipath-21.html#section-3.4.3-3 -2838 // > When an endpoint finally deletes all state associated with the path [...] -2839 // > PATH_ACK frames received with an abandoned path ID are silently ignored, -2840 // > as specified in Section 4. -2841 trace!("silently ignoring PATH_ACK on discarded path"); -2842 return Ok(()); -2843 } else { -2844 return Err(TransportError::PROTOCOL_VIOLATION( -2845 "received PATH_ACK with path ID never used", -2846 )); -2847 } -2848 } -2849 if ack.largest >= self.spaces[space].for_path(path).next_packet_number { -2850 return Err(TransportError::PROTOCOL_VIOLATION("unsent packet acked")); -2851 } -2852 // `Some(pn)` if this ACK raised `largest_acked_packet_pn`. -2853 let new_largest_pn = { -2854 let space = &mut self.spaces[space].for_path(path); -2855 if space -2856 .largest_acked_packet_pn -2857 .is_none_or(|pn| ack.largest > pn) -2858 { -2859 space.largest_acked_packet_pn = Some(ack.largest); -2860 if let Some(info) = space.sent_packets.get(ack.largest) { -2861 // This should always succeed, but a misbehaving peer might ACK a packet we -2862 // haven't sent. At worst, that will result in us spuriously reducing the -2863 // congestion window. -2864 space.largest_acked_packet_send_time = info.time_sent; -2865 } -2866 Some(ack.largest) -2867 } else { -2868 None -2869 } -2870 }; -2871 -2872 if self.detect_spurious_loss(&ack, space, path) { -2873 self.path_stats.for_path(path).spurious_congestion_events += 1; -2874 self.path_data_mut(path) -2875 .congestion -2876 .on_spurious_congestion_event(); -2877 } +2795 /// See [`TransportConfig::receive_window()`] +2796 pub fn set_receive_window(&mut self, receive_window: VarInt) { +2797 if self.streams.set_receive_window(receive_window) { +2798 self.spaces[SpaceId::Data].pending.max_data = true; +2799 } +2800 } +2801 +2802 /// Whether the Multipath for QUIC extension is enabled. +2803 /// +2804 /// Multipath is only enabled after the handshake is completed and if it was enabled by both +2805 /// peers. +2806 pub fn is_multipath_negotiated(&self) -> bool { +2807 !self.is_handshaking() +2808 && self.config.max_concurrent_multipath_paths.is_some() +2809 && self.peer_params.initial_max_path_id.is_some() +2810 } +2811 +2812 fn on_ack_received( +2813 &mut self, +2814 now: Instant, +2815 space: SpaceId, +2816 ack: frame::Ack, +2817 ) -> Result<(), TransportError> { +2818 // All ACKs are referencing path 0 +2819 let path = PathId::ZERO; +2820 self.inner_on_ack_received(now, space, path, ack) +2821 } +2822 +2823 fn on_path_ack_received( +2824 &mut self, +2825 now: Instant, +2826 space: SpaceId, +2827 path_ack: frame::PathAck, +2828 ) -> Result<(), TransportError> { +2829 let (ack, path) = path_ack.into_ack(); +2830 self.inner_on_ack_received(now, space, path, ack) +2831 } +2832 +2833 /// Handles an ACK frame acknowledging packets sent on *path*. +2834 fn inner_on_ack_received( +2835 &mut self, +2836 now: Instant, +2837 space: SpaceId, +2838 path: PathId, +2839 ack: frame::Ack, +2840 ) -> Result<(), TransportError> { +2841 if !self.spaces[space].number_spaces.contains_key(&path) { +2842 if self.abandoned_paths.contains(&path) { +2843 // See also +2844 // https://www.ietf.org/archive/id/draft-ietf-quic-multipath-21.html#section-3.4.3-3 +2845 // > When an endpoint finally deletes all state associated with the path [...] +2846 // > PATH_ACK frames received with an abandoned path ID are silently ignored, +2847 // > as specified in Section 4. +2848 trace!("silently ignoring PATH_ACK on discarded path"); +2849 return Ok(()); +2850 } else { +2851 return Err(TransportError::PROTOCOL_VIOLATION( +2852 "received PATH_ACK with path ID never used", +2853 )); +2854 } +2855 } +2856 if ack.largest >= self.spaces[space].for_path(path).next_packet_number { +2857 return Err(TransportError::PROTOCOL_VIOLATION("unsent packet acked")); +2858 } +2859 // `Some(pn)` if this ACK raised `largest_acked_packet_pn`. +2860 let new_largest_pn = { +2861 let space = &mut self.spaces[space].for_path(path); +2862 if space +2863 .largest_acked_packet_pn +2864 .is_none_or(|pn| ack.largest > pn) +2865 { +2866 space.largest_acked_packet_pn = Some(ack.largest); +2867 if let Some(info) = space.sent_packets.get(ack.largest) { +2868 // This should always succeed, but a misbehaving peer might ACK a packet we +2869 // haven't sent. At worst, that will result in us spuriously reducing the +2870 // congestion window. +2871 space.largest_acked_packet_send_time = info.time_sent; +2872 } +2873 Some(ack.largest) +2874 } else { +2875 None +2876 } +2877 }; 2878 -2879 // Avoid DoS from unreasonably huge ack ranges by filtering out just the new acks. -2880 let mut newly_acked = ArrayRangeSet::new(); -2881 for range in ack.iter() { -2882 self.spaces[space].for_path(path).check_ack(range.clone())?; -2883 for (pn, _) in self.spaces[space] -2884 .for_path(path) -2885 .sent_packets -2886 .iter_range(range) -2887 { -2888 newly_acked.insert_one(pn); -2889 } -2890 } -2891 -2892 if newly_acked.is_empty() { -2893 return Ok(()); -2894 } -2895 -2896 let mut ack_eliciting_acked = false; -2897 for packet in newly_acked.elts() { -2898 if let Some(info) = self.spaces[space].for_path(path).take(packet) { -2899 for (acked_path_id, acked_pn) in info.largest_acked.iter() { -2900 // Assume ACKs for all packets below the largest acknowledged in -2901 // `packet` have been received. This can cause the peer to spuriously -2902 // retransmit if some of our earlier ACKs were lost, but allows for -2903 // simpler state tracking. See discussion at -2904 // https://www.rfc-editor.org/rfc/rfc9000.html#name-limiting-ranges-by-tracking -2905 if let Some(pns) = self.spaces[space].path_space_mut(*acked_path_id) { -2906 pns.pending_acks.subtract_below(*acked_pn); -2907 } -2908 } -2909 ack_eliciting_acked |= info.ack_eliciting; -2910 -2911 // Notify MTU discovery that a packet was acked, because it might be an MTU probe -2912 let path_data = self.path_data_mut(path); -2913 let mtu_updated = path_data.mtud.on_acked(space.kind(), packet, info.size); -2914 if mtu_updated { -2915 path_data -2916 .congestion -2917 .on_mtu_update(path_data.mtud.current_mtu()); -2918 } -2919 -2920 // Notify ack frequency that a packet was acked, because it might contain an ACK_FREQUENCY frame -2921 self.ack_frequency.on_acked(path, packet); -2922 -2923 self.on_packet_acked(now, path, packet, info); -2924 } -2925 } +2879 if self.detect_spurious_loss(&ack, space, path) { +2880 self.path_stats.for_path(path).spurious_congestion_events += 1; +2881 self.path_data_mut(path) +2882 .congestion +2883 .on_spurious_congestion_event(); +2884 } +2885 +2886 // Avoid DoS from unreasonably huge ack ranges by filtering out just the new acks. +2887 let mut newly_acked = ArrayRangeSet::new(); +2888 for range in ack.iter() { +2889 self.spaces[space].for_path(path).check_ack(range.clone())?; +2890 for (pn, _) in self.spaces[space] +2891 .for_path(path) +2892 .sent_packets +2893 .iter_range(range) +2894 { +2895 newly_acked.insert_one(pn); +2896 } +2897 } +2898 +2899 if newly_acked.is_empty() { +2900 return Ok(()); +2901 } +2902 +2903 let mut ack_eliciting_acked = false; +2904 for packet in newly_acked.elts() { +2905 if let Some(info) = self.spaces[space].for_path(path).take(packet) { +2906 for (acked_path_id, acked_pn) in info.largest_acked.iter() { +2907 // Assume ACKs for all packets below the largest acknowledged in +2908 // `packet` have been received. This can cause the peer to spuriously +2909 // retransmit if some of our earlier ACKs were lost, but allows for +2910 // simpler state tracking. See discussion at +2911 // https://www.rfc-editor.org/rfc/rfc9000.html#name-limiting-ranges-by-tracking +2912 if let Some(pns) = self.spaces[space].path_space_mut(*acked_path_id) { +2913 pns.pending_acks.subtract_below(*acked_pn); +2914 } +2915 } +2916 ack_eliciting_acked |= info.ack_eliciting; +2917 +2918 // Notify MTU discovery that a packet was acked, because it might be an MTU probe +2919 let path_data = self.path_data_mut(path); +2920 let mtu_updated = path_data.mtud.on_acked(space.kind(), packet, info.size); +2921 if mtu_updated { +2922 path_data +2923 .congestion +2924 .on_mtu_update(path_data.mtud.current_mtu()); +2925 } 2926 -2927 let largest_ackd = self.spaces[space].for_path(path).largest_acked_packet_pn; -2928 let path_data = self.path_data_mut(path); -2929 let app_limited = path_data.app_limited; -2930 let in_flight = path_data.in_flight.bytes; -2931 -2932 path_data -2933 .congestion -2934 .on_end_acks(now, in_flight, app_limited, largest_ackd); -2935 -2936 if new_largest_pn.is_some() && ack_eliciting_acked { -2937 let ack_delay = if space != SpaceId::Data { -2938 Duration::from_micros(0) -2939 } else { -2940 cmp::min( -2941 self.ack_frequency.peer_max_ack_delay, -2942 Duration::from_micros(ack.delay << self.peer_params.ack_delay_exponent.0), -2943 ) -2944 }; -2945 let rtt = now.saturating_duration_since( -2946 self.spaces[space] -2947 .for_path(path) -2948 .largest_acked_packet_send_time, -2949 ); -2950 -2951 let next_pn = self.spaces[space].for_path(path).next_packet_number; -2952 let path_data = self.path_data_mut(path); -2953 // TODO(@divma): should be a method of path, should be contained in a single place -2954 path_data.rtt.update(ack_delay, rtt); -2955 if path_data.first_packet_after_rtt_sample.is_none() { -2956 path_data.first_packet_after_rtt_sample = Some((space.kind(), next_pn)); -2957 } -2958 } -2959 -2960 // Must be called before crypto/pto_count are clobbered -2961 self.detect_lost_packets(now, space, path, true); -2962 -2963 // If the peer did not complete the handshake address validation the ACK could be -2964 // spoofed, e.g. in the Initial space. Setting the pto_count back to 0 removes the -2965 // exponential backoff from the PTO timer and would result in too many tail-loss -2966 // probes being sent. -2967 if self.peer_completed_handshake_address_validation() { -2968 self.path_data_mut(path).pto_count = 0; -2969 } -2970 -2971 // Explicit congestion notification -2972 // TODO(@divma): this code is a good example of logic that should be contained in a single -2973 // place but it's split between the path data and the packet number space data, we should -2974 // find a way to make this work without two lookups -2975 if self.path_data(path).sending_ecn { -2976 if let Some(ecn) = ack.ecn { -2977 // We only examine ECN counters from ACKs that we are certain we received in transmit -2978 // order, allowing us to compute an increase in ECN counts to compare against the number -2979 // of newly acked packets that remains well-defined in the presence of arbitrary packet -2980 // reordering. -2981 if let Some(largest_sent_pn) = new_largest_pn { -2982 let sent = self.spaces[space] -2983 .for_path(path) -2984 .largest_acked_packet_send_time; -2985 self.process_ecn( -2986 now, -2987 space, -2988 path, -2989 newly_acked.len() as u64, -2990 ecn, -2991 sent, -2992 largest_sent_pn, -2993 ); -2994 } -2995 } else { -2996 // We always start out sending ECN, so any ack that doesn't acknowledge it disables it. -2997 debug!("ECN not acknowledged by peer"); -2998 self.path_data_mut(path).sending_ecn = false; -2999 } -3000 } -3001 -3002 self.set_loss_detection_timer(now, path); -3003 Ok(()) -3004 } -3005 -3006 fn detect_spurious_loss(&mut self, ack: &frame::Ack, space: SpaceId, path: PathId) -> bool { -3007 let lost_packets = &mut self.spaces[space].for_path(path).lost_packets; +2927 // Notify ack frequency that a packet was acked, because it might contain an ACK_FREQUENCY frame +2928 self.ack_frequency.on_acked(path, packet); +2929 +2930 self.on_packet_acked(now, path, packet, info); +2931 } +2932 } +2933 +2934 let largest_ackd = self.spaces[space].for_path(path).largest_acked_packet_pn; +2935 let path_data = self.path_data_mut(path); +2936 let app_limited = path_data.app_limited; +2937 let in_flight = path_data.in_flight.bytes; +2938 +2939 path_data +2940 .congestion +2941 .on_end_acks(now, in_flight, app_limited, largest_ackd); +2942 +2943 if new_largest_pn.is_some() && ack_eliciting_acked { +2944 let ack_delay = if space != SpaceId::Data { +2945 Duration::from_micros(0) +2946 } else { +2947 cmp::min( +2948 self.ack_frequency.peer_max_ack_delay, +2949 Duration::from_micros(ack.delay << self.peer_params.ack_delay_exponent.0), +2950 ) +2951 }; +2952 let rtt = now.saturating_duration_since( +2953 self.spaces[space] +2954 .for_path(path) +2955 .largest_acked_packet_send_time, +2956 ); +2957 +2958 let next_pn = self.spaces[space].for_path(path).next_packet_number; +2959 let path_data = self.path_data_mut(path); +2960 // TODO(@divma): should be a method of path, should be contained in a single place +2961 path_data.rtt.update(ack_delay, rtt); +2962 if path_data.first_packet_after_rtt_sample.is_none() { +2963 path_data.first_packet_after_rtt_sample = Some((space.kind(), next_pn)); +2964 } +2965 } +2966 +2967 // Must be called before crypto/pto_count are clobbered +2968 self.detect_lost_packets(now, space, path, true); +2969 +2970 // If the peer did not complete the handshake address validation the ACK could be +2971 // spoofed, e.g. in the Initial space. Setting the pto_count back to 0 removes the +2972 // exponential backoff from the PTO timer and would result in too many tail-loss +2973 // probes being sent. +2974 if self.peer_completed_handshake_address_validation() { +2975 self.path_data_mut(path).pto_count = 0; +2976 } +2977 +2978 // Explicit congestion notification +2979 // TODO(@divma): this code is a good example of logic that should be contained in a single +2980 // place but it's split between the path data and the packet number space data, we should +2981 // find a way to make this work without two lookups +2982 if self.path_data(path).sending_ecn { +2983 if let Some(ecn) = ack.ecn { +2984 // We only examine ECN counters from ACKs that we are certain we received in transmit +2985 // order, allowing us to compute an increase in ECN counts to compare against the number +2986 // of newly acked packets that remains well-defined in the presence of arbitrary packet +2987 // reordering. +2988 if let Some(largest_sent_pn) = new_largest_pn { +2989 let sent = self.spaces[space] +2990 .for_path(path) +2991 .largest_acked_packet_send_time; +2992 self.process_ecn( +2993 now, +2994 space, +2995 path, +2996 newly_acked.len() as u64, +2997 ecn, +2998 sent, +2999 largest_sent_pn, +3000 ); +3001 } +3002 } else { +3003 // We always start out sending ECN, so any ack that doesn't acknowledge it disables it. +3004 debug!("ECN not acknowledged by peer"); +3005 self.path_data_mut(path).sending_ecn = false; +3006 } +3007 } 3008 -3009 if lost_packets.is_empty() { -3010 return false; -3011 } +3009 self.set_loss_detection_timer(now, path); +3010 Ok(()) +3011 } 3012 -3013 for range in ack.iter() { -3014 let spurious_losses: Vec<u64> = lost_packets -3015 .iter_range(range.clone()) -3016 .map(|(pn, _info)| pn) -3017 .collect(); -3018 -3019 for pn in spurious_losses { -3020 lost_packets.remove(pn); -3021 } -3022 } -3023 -3024 // If this ACK frame acknowledged all deemed lost packets, -3025 // then we have raised a spurious congestion event in the past. -3026 // We cannot conclude when there are remaining packets, -3027 // but future ACK frames might indicate a spurious loss detection. -3028 lost_packets.is_empty() -3029 } +3013 fn detect_spurious_loss(&mut self, ack: &frame::Ack, space: SpaceId, path: PathId) -> bool { +3014 let lost_packets = &mut self.spaces[space].for_path(path).lost_packets; +3015 +3016 if lost_packets.is_empty() { +3017 return false; +3018 } +3019 +3020 for range in ack.iter() { +3021 let spurious_losses: Vec<u64> = lost_packets +3022 .iter_range(range.clone()) +3023 .map(|(pn, _info)| pn) +3024 .collect(); +3025 +3026 for pn in spurious_losses { +3027 lost_packets.remove(pn); +3028 } +3029 } 3030 -3031 /// Drain lost packets that we reasonably think will never arrive -3032 /// -3033 /// The current criterion is copied from `msquic`: -3034 /// discard packets that were sent earlier than 2 probe timeouts ago. -3035 fn drain_lost_packets(&mut self, now: Instant, space: SpaceId, path: PathId) { -3036 let two_pto = 2 * self.path_data(path).rtt.pto_base(); +3031 // If this ACK frame acknowledged all deemed lost packets, +3032 // then we have raised a spurious congestion event in the past. +3033 // We cannot conclude when there are remaining packets, +3034 // but future ACK frames might indicate a spurious loss detection. +3035 lost_packets.is_empty() +3036 } 3037 -3038 let lost_packets = &mut self.spaces[space].for_path(path).lost_packets; -3039 lost_packets.retain(|_pn, info| now.saturating_duration_since(info.time_sent) <= two_pto); -3040 } -3041 -3042 /// Process a new ECN block from an in-order ACK -3043 fn process_ecn( -3044 &mut self, -3045 now: Instant, -3046 space: SpaceId, -3047 path: PathId, -3048 newly_acked_pn: u64, -3049 ecn: frame::EcnCounts, -3050 largest_sent_time: Instant, -3051 largest_sent_pn: u64, -3052 ) { -3053 match self.spaces[space] -3054 .for_path(path) -3055 .detect_ecn(newly_acked_pn, ecn) -3056 { -3057 Err(e) => { -3058 debug!("halting ECN due to verification failure: {}", e); -3059 -3060 self.path_data_mut(path).sending_ecn = false; -3061 // Wipe out the existing value because it might be garbage and could interfere with -3062 // future attempts to use ECN on new paths. -3063 self.spaces[space].for_path(path).ecn_feedback = frame::EcnCounts::ZERO; -3064 } -3065 Ok(false) => {} -3066 Ok(true) => { -3067 self.path_stats.for_path(path).congestion_events += 1; -3068 self.path_data_mut(path).congestion.on_congestion_event( -3069 now, -3070 largest_sent_time, -3071 false, -3072 true, -3073 0, -3074 largest_sent_pn, -3075 ); -3076 } -3077 } -3078 } -3079 -3080 // Not timing-aware, so it's safe to call this for inferred acks, such as arise from -3081 // high-latency handshakes -3082 fn on_packet_acked(&mut self, now: Instant, path_id: PathId, pn: u64, info: SentPacket) { -3083 let path = self.path_data_mut(path_id); -3084 let app_limited = path.app_limited; -3085 path.remove_in_flight(&info); -3086 if info.ack_eliciting && info.path_generation == path.generation() { -3087 // Only pass ACKs to the congestion controller if it belongs to this exact -3088 // generation of the path. Otherwise we might be feeding ACKs from the previous -3089 // 4-tuple into our congestion controller. -3090 let rtt = path.rtt; -3091 path.congestion -3092 .on_ack(now, info.time_sent, info.size.into(), pn, app_limited, &rtt); -3093 } -3094 -3095 // Update state for confirmed delivery of frames -3096 if let Some(retransmits) = info.retransmits.get() { -3097 for (id, _) in retransmits.reset_stream.iter() { -3098 self.streams.reset_acked(*id); -3099 } +3038 /// Drain lost packets that we reasonably think will never arrive +3039 /// +3040 /// The current criterion is copied from `msquic`: +3041 /// discard packets that were sent earlier than 2 probe timeouts ago. +3042 fn drain_lost_packets(&mut self, now: Instant, space: SpaceId, path: PathId) { +3043 let two_pto = 2 * self.path_data(path).rtt.pto_base(); +3044 +3045 let lost_packets = &mut self.spaces[space].for_path(path).lost_packets; +3046 lost_packets.retain(|_pn, info| now.saturating_duration_since(info.time_sent) <= two_pto); +3047 } +3048 +3049 /// Process a new ECN block from an in-order ACK +3050 fn process_ecn( +3051 &mut self, +3052 now: Instant, +3053 space: SpaceId, +3054 path: PathId, +3055 newly_acked_pn: u64, +3056 ecn: frame::EcnCounts, +3057 largest_sent_time: Instant, +3058 largest_sent_pn: u64, +3059 ) { +3060 match self.spaces[space] +3061 .for_path(path) +3062 .detect_ecn(newly_acked_pn, ecn) +3063 { +3064 Err(e) => { +3065 debug!("halting ECN due to verification failure: {}", e); +3066 +3067 self.path_data_mut(path).sending_ecn = false; +3068 // Wipe out the existing value because it might be garbage and could interfere with +3069 // future attempts to use ECN on new paths. +3070 self.spaces[space].for_path(path).ecn_feedback = frame::EcnCounts::ZERO; +3071 } +3072 Ok(false) => {} +3073 Ok(true) => { +3074 self.path_stats.for_path(path).congestion_events += 1; +3075 self.path_data_mut(path).congestion.on_congestion_event( +3076 now, +3077 largest_sent_time, +3078 false, +3079 true, +3080 0, +3081 largest_sent_pn, +3082 ); +3083 } +3084 } +3085 } +3086 +3087 // Not timing-aware, so it's safe to call this for inferred acks, such as arise from +3088 // high-latency handshakes +3089 fn on_packet_acked(&mut self, now: Instant, path_id: PathId, pn: u64, info: SentPacket) { +3090 let path = self.path_data_mut(path_id); +3091 let app_limited = path.app_limited; +3092 path.remove_in_flight(&info); +3093 if info.ack_eliciting && info.path_generation == path.generation() { +3094 // Only pass ACKs to the congestion controller if it belongs to this exact +3095 // generation of the path. Otherwise we might be feeding ACKs from the previous +3096 // 4-tuple into our congestion controller. +3097 let rtt = path.rtt; +3098 path.congestion +3099 .on_ack(now, info.time_sent, info.size.into(), pn, app_limited, &rtt); 3100 } 3101 -3102 for frame in info.stream_frames { -3103 self.streams.received_ack_of(frame); -3104 } -3105 } -3106 -3107 fn set_key_discard_timer(&mut self, now: Instant, space: SpaceKind) { -3108 let start = if self.crypto_state.has_keys(EncryptionLevel::ZeroRtt) { -3109 now -3110 } else { -3111 self.crypto_state -3112 .prev_crypto -3113 .as_ref() -3114 .expect("no previous keys") -3115 .end_packet -3116 .as_ref() -3117 .expect("update not acknowledged yet") -3118 .1 -3119 }; -3120 -3121 // QUIC-MULTIPATH § 2.5 Key Phase Update Process: use largest PTO of all paths. -3122 self.timers.set( -3123 Timer::Conn(ConnTimer::KeyDiscard), -3124 start + self.max_pto_for_space(space) * 3, -3125 self.qlog.with_time(now), -3126 ); -3127 } -3128 -3129 /// Handle a [`PathTimer::LossDetection`] timeout. -3130 /// -3131 /// This timer expires for two reasons: -3132 /// - An ACK-eliciting packet we sent should be considered lost. -3133 /// - The PTO may have expired and a tail-loss probe needs to be scheduled. -3134 /// -3135 /// The former needs us to schedule re-transmission of the lost data. -3136 /// -3137 /// The latter means we have not received an ACK for an ack-eliciting packet we sent -3138 /// within the PTO time-window. We need to schedule a tail-loss probe, an ack-eliciting -3139 /// packet, to try and elicit new acknowledgements. These new acknowledgements will -3140 /// indicate whether the previously sent packets were lost or not. -3141 fn on_loss_detection_timeout(&mut self, now: Instant, path_id: PathId) { -3142 if let Some((_, pn_space)) = self.loss_time_and_space(path_id) { -3143 // Time threshold loss Detection -3144 self.detect_lost_packets(now, pn_space, path_id, false); -3145 self.set_loss_detection_timer(now, path_id); -3146 return; -3147 } -3148 -3149 let Some((_, space)) = self.pto_time_and_space(now, path_id) else { -3150 error!(%path_id, "PTO expired while unset"); -3151 return; -3152 }; -3153 trace!( -3154 in_flight = self.path_data(path_id).in_flight.bytes, -3155 count = self.path_data(path_id).pto_count, -3156 ?space, -3157 %path_id, -3158 "PTO fired" -3159 ); -3160 -3161 let count = match self.path_data(path_id).in_flight.ack_eliciting { -3162 // A PTO when we're not expecting any ACKs must be due to handshake -3163 // anti-amplification deadlock prevention. -3164 0 => { -3165 debug_assert!(!self.peer_completed_handshake_address_validation()); -3166 1 -3167 } -3168 // Conventional loss probe -3169 _ => 2, -3170 }; -3171 let pns = self.spaces[space].for_path(path_id); -3172 pns.loss_probes = pns.loss_probes.saturating_add(count); -3173 let path_data = self.path_data_mut(path_id); -3174 path_data.pto_count = path_data.pto_count.saturating_add(1); -3175 self.set_loss_detection_timer(now, path_id); -3176 } -3177 -3178 /// Detect any lost packets -3179 /// -3180 /// There are two cases in which we detects lost packets: -3181 /// -3182 /// - We received an ACK packet. -3183 /// - The [`PathTimer::LossDetection`] timer expired. So there is an un-acknowledged packet -3184 /// that was followed by an acknowledged packet. The loss timer for this -3185 /// un-acknowledged packet expired and we need to detect that packet as lost. +3102 // Update state for confirmed delivery of frames +3103 if let Some(retransmits) = info.retransmits.get() { +3104 for (id, _) in retransmits.reset_stream.iter() { +3105 self.streams.reset_acked(*id); +3106 } +3107 } +3108 +3109 for frame in info.stream_frames { +3110 self.streams.received_ack_of(frame); +3111 } +3112 } +3113 +3114 fn set_key_discard_timer(&mut self, now: Instant, space: SpaceKind) { +3115 let start = if self.crypto_state.has_keys(EncryptionLevel::ZeroRtt) { +3116 now +3117 } else { +3118 self.crypto_state +3119 .prev_crypto +3120 .as_ref() +3121 .expect("no previous keys") +3122 .end_packet +3123 .as_ref() +3124 .expect("update not acknowledged yet") +3125 .1 +3126 }; +3127 +3128 // QUIC-MULTIPATH § 2.5 Key Phase Update Process: use largest PTO of all paths. +3129 self.timers.set( +3130 Timer::Conn(ConnTimer::KeyDiscard), +3131 start + self.max_pto_for_space(space) * 3, +3132 self.qlog.with_time(now), +3133 ); +3134 } +3135 +3136 /// Handle a [`PathTimer::LossDetection`] timeout. +3137 /// +3138 /// This timer expires for two reasons: +3139 /// - An ACK-eliciting packet we sent should be considered lost. +3140 /// - The PTO may have expired and a tail-loss probe needs to be scheduled. +3141 /// +3142 /// The former needs us to schedule re-transmission of the lost data. +3143 /// +3144 /// The latter means we have not received an ACK for an ack-eliciting packet we sent +3145 /// within the PTO time-window. We need to schedule a tail-loss probe, an ack-eliciting +3146 /// packet, to try and elicit new acknowledgements. These new acknowledgements will +3147 /// indicate whether the previously sent packets were lost or not. +3148 fn on_loss_detection_timeout(&mut self, now: Instant, path_id: PathId) { +3149 if let Some((_, pn_space)) = self.loss_time_and_space(path_id) { +3150 // Time threshold loss Detection +3151 self.detect_lost_packets(now, pn_space, path_id, false); +3152 self.set_loss_detection_timer(now, path_id); +3153 return; +3154 } +3155 +3156 let Some((_, space)) = self.pto_time_and_space(now, path_id) else { +3157 error!(%path_id, "PTO expired while unset"); +3158 return; +3159 }; +3160 trace!( +3161 in_flight = self.path_data(path_id).in_flight.bytes, +3162 count = self.path_data(path_id).pto_count, +3163 ?space, +3164 %path_id, +3165 "PTO fired" +3166 ); +3167 +3168 let count = match self.path_data(path_id).in_flight.ack_eliciting { +3169 // A PTO when we're not expecting any ACKs must be due to handshake +3170 // anti-amplification deadlock prevention. +3171 0 => { +3172 debug_assert!(!self.peer_completed_handshake_address_validation()); +3173 1 +3174 } +3175 // Conventional loss probe +3176 _ => 2, +3177 }; +3178 let pns = self.spaces[space].for_path(path_id); +3179 pns.loss_probes = pns.loss_probes.saturating_add(count); +3180 let path_data = self.path_data_mut(path_id); +3181 path_data.pto_count = path_data.pto_count.saturating_add(1); +3182 self.set_loss_detection_timer(now, path_id); +3183 } +3184 +3185 /// Detect any lost packets 3186 /// -3187 /// Packets are lost if they are both (See RFC9002 §6.1): +3187 /// There are two cases in which we detects lost packets: 3188 /// -3189 /// - Unacknowledged, in flight and sent prior to an acknowledged packet. -3190 /// - Old enough by either: -3191 /// - Having a packet number [`TransportConfig::packet_threshold`] lower then the last -3192 /// acknowledged packet. -3193 /// - Being sent [`TransportConfig::time_threshold`] * RTT in the past. -3194 fn detect_lost_packets( -3195 &mut self, -3196 now: Instant, -3197 pn_space: SpaceId, -3198 path_id: PathId, -3199 due_to_ack: bool, -3200 ) { -3201 let mut lost_packets = Vec::<u64>::new(); -3202 let mut lost_mtu_probe = None; -3203 let mut in_persistent_congestion = false; -3204 let mut size_of_lost_packets = 0u64; -3205 self.spaces[pn_space].for_path(path_id).loss_time = None; -3206 -3207 // Find all the lost packets, populating all variables initialised above. -3208 -3209 let path = self.path_data(path_id); -3210 let in_flight_mtu_probe = path.mtud.in_flight_mtu_probe(); -3211 let loss_delay = path -3212 .rtt -3213 .conservative() -3214 .mul_f32(self.config.time_threshold) -3215 .max(TIMER_GRANULARITY); -3216 let first_packet_after_rtt_sample = path.first_packet_after_rtt_sample; -3217 -3218 let largest_acked_packet_pn = self.spaces[pn_space] -3219 .for_path(path_id) -3220 .largest_acked_packet_pn -3221 .expect("detect_lost_packets only to be called if path received at least one ACK"); -3222 let packet_threshold = self.config.packet_threshold as u64; -3223 -3224 // InPersistentCongestion: Determine if all packets in the time period before the newest -3225 // lost packet, including the edges, are marked lost. PTO computation must always -3226 // include max ACK delay, i.e. operate as if in Data space (see RFC9001 §7.6.1). -3227 let congestion_period = self -3228 .pto(SpaceKind::Data, path_id) -3229 .saturating_mul(self.config.persistent_congestion_threshold); -3230 let mut persistent_congestion_start: Option<Instant> = None; -3231 let mut prev_packet = None; -3232 let space = self.spaces[pn_space].for_path(path_id); -3233 -3234 for (packet, info) in space.sent_packets.iter_range(0..largest_acked_packet_pn) { -3235 if prev_packet != Some(packet.wrapping_sub(1)) { -3236 // An intervening packet was acknowledged -3237 persistent_congestion_start = None; -3238 } -3239 -3240 // Packets sent before now - loss_delay are deemed lost. -3241 // However, we avoid subtraction as it can panic and there's no -3242 // saturating equivalent of this subtraction operation with a Duration. -3243 let packet_too_old = now.saturating_duration_since(info.time_sent) >= loss_delay; -3244 if packet_too_old || largest_acked_packet_pn >= packet + packet_threshold { -3245 // The packet should be declared lost. -3246 if Some(packet) == in_flight_mtu_probe { -3247 // Lost MTU probes are not included in `lost_packets`, because they -3248 // should not trigger a congestion control response -3249 lost_mtu_probe = in_flight_mtu_probe; -3250 } else { -3251 lost_packets.push(packet); -3252 size_of_lost_packets += info.size as u64; -3253 if info.ack_eliciting && due_to_ack { -3254 match persistent_congestion_start { -3255 // Two ACK-eliciting packets lost more than -3256 // congestion_period apart, with no ACKed packets in between -3257 Some(start) if info.time_sent - start > congestion_period => { -3258 in_persistent_congestion = true; -3259 } -3260 // Persistent congestion must start after the first RTT sample -3261 None if first_packet_after_rtt_sample -3262 .is_some_and(|x| x < (pn_space.kind(), packet)) => -3263 { -3264 persistent_congestion_start = Some(info.time_sent); -3265 } -3266 _ => {} -3267 } -3268 } -3269 } -3270 } else { -3271 // The packet should not yet be declared lost. -3272 if space.loss_time.is_none() { -3273 // Since we iterate in order the lowest packet number's loss time will -3274 // always be the earliest. -3275 space.loss_time = Some(info.time_sent + loss_delay); +3189 /// - We received an ACK packet. +3190 /// - The [`PathTimer::LossDetection`] timer expired. So there is an un-acknowledged packet +3191 /// that was followed by an acknowledged packet. The loss timer for this +3192 /// un-acknowledged packet expired and we need to detect that packet as lost. +3193 /// +3194 /// Packets are lost if they are both (See RFC9002 §6.1): +3195 /// +3196 /// - Unacknowledged, in flight and sent prior to an acknowledged packet. +3197 /// - Old enough by either: +3198 /// - Having a packet number [`TransportConfig::packet_threshold`] lower then the last +3199 /// acknowledged packet. +3200 /// - Being sent [`TransportConfig::time_threshold`] * RTT in the past. +3201 fn detect_lost_packets( +3202 &mut self, +3203 now: Instant, +3204 pn_space: SpaceId, +3205 path_id: PathId, +3206 due_to_ack: bool, +3207 ) { +3208 let mut lost_packets = Vec::<u64>::new(); +3209 let mut lost_mtu_probe = None; +3210 let mut in_persistent_congestion = false; +3211 let mut size_of_lost_packets = 0u64; +3212 self.spaces[pn_space].for_path(path_id).loss_time = None; +3213 +3214 // Find all the lost packets, populating all variables initialised above. +3215 +3216 let path = self.path_data(path_id); +3217 let in_flight_mtu_probe = path.mtud.in_flight_mtu_probe(); +3218 let loss_delay = path +3219 .rtt +3220 .conservative() +3221 .mul_f32(self.config.time_threshold) +3222 .max(TIMER_GRANULARITY); +3223 let first_packet_after_rtt_sample = path.first_packet_after_rtt_sample; +3224 +3225 let largest_acked_packet_pn = self.spaces[pn_space] +3226 .for_path(path_id) +3227 .largest_acked_packet_pn +3228 .expect("detect_lost_packets only to be called if path received at least one ACK"); +3229 let packet_threshold = self.config.packet_threshold as u64; +3230 +3231 // InPersistentCongestion: Determine if all packets in the time period before the newest +3232 // lost packet, including the edges, are marked lost. PTO computation must always +3233 // include max ACK delay, i.e. operate as if in Data space (see RFC9001 §7.6.1). +3234 let congestion_period = self +3235 .pto(SpaceKind::Data, path_id) +3236 .saturating_mul(self.config.persistent_congestion_threshold); +3237 let mut persistent_congestion_start: Option<Instant> = None; +3238 let mut prev_packet = None; +3239 let space = self.spaces[pn_space].for_path(path_id); +3240 +3241 for (packet, info) in space.sent_packets.iter_range(0..largest_acked_packet_pn) { +3242 if prev_packet != Some(packet.wrapping_sub(1)) { +3243 // An intervening packet was acknowledged +3244 persistent_congestion_start = None; +3245 } +3246 +3247 // Packets sent before now - loss_delay are deemed lost. +3248 // However, we avoid subtraction as it can panic and there's no +3249 // saturating equivalent of this subtraction operation with a Duration. +3250 let packet_too_old = now.saturating_duration_since(info.time_sent) >= loss_delay; +3251 if packet_too_old || largest_acked_packet_pn >= packet + packet_threshold { +3252 // The packet should be declared lost. +3253 if Some(packet) == in_flight_mtu_probe { +3254 // Lost MTU probes are not included in `lost_packets`, because they +3255 // should not trigger a congestion control response +3256 lost_mtu_probe = in_flight_mtu_probe; +3257 } else { +3258 lost_packets.push(packet); +3259 size_of_lost_packets += info.size as u64; +3260 if info.ack_eliciting && due_to_ack { +3261 match persistent_congestion_start { +3262 // Two ACK-eliciting packets lost more than +3263 // congestion_period apart, with no ACKed packets in between +3264 Some(start) if info.time_sent - start > congestion_period => { +3265 in_persistent_congestion = true; +3266 } +3267 // Persistent congestion must start after the first RTT sample +3268 None if first_packet_after_rtt_sample +3269 .is_some_and(|x| x < (pn_space.kind(), packet)) => +3270 { +3271 persistent_congestion_start = Some(info.time_sent); +3272 } +3273 _ => {} +3274 } +3275 } 3276 } -3277 persistent_congestion_start = None; -3278 } -3279 -3280 prev_packet = Some(packet); -3281 } -3282 -3283 self.handle_lost_packets( -3284 pn_space, -3285 path_id, -3286 now, -3287 lost_packets, -3288 lost_mtu_probe, -3289 loss_delay, -3290 in_persistent_congestion, -3291 size_of_lost_packets, -3292 ); -3293 } -3294 -3295 /// Drops the path state, declaring any remaining in-flight packets as lost -3296 fn discard_path(&mut self, path_id: PathId, now: Instant) { -3297 trace!(%path_id, "dropping path state"); -3298 let path = self.path_data(path_id); -3299 let in_flight_mtu_probe = path.mtud.in_flight_mtu_probe(); -3300 -3301 let mut size_of_lost_packets = 0u64; // add to path_stats.lost_bytes; -3302 let lost_pns: Vec<_> = self.spaces[SpaceId::Data] -3303 .for_path(path_id) -3304 .sent_packets -3305 .iter() -3306 .filter(|(pn, _info)| Some(*pn) != in_flight_mtu_probe) -3307 .map(|(pn, info)| { -3308 size_of_lost_packets += info.size as u64; -3309 pn -3310 }) -3311 .collect(); -3312 -3313 if !lost_pns.is_empty() { -3314 trace!( -3315 %path_id, -3316 count = lost_pns.len(), -3317 lost_bytes = size_of_lost_packets, -3318 "packets lost on path abandon" -3319 ); -3320 self.handle_lost_packets( -3321 SpaceId::Data, -3322 path_id, -3323 now, -3324 lost_pns, -3325 in_flight_mtu_probe, -3326 Duration::ZERO, -3327 false, -3328 size_of_lost_packets, -3329 ); -3330 } -3331 // Before removing the path, we fetch the final path stats via `Self::path_stats`. -3332 // This updates some values for the last time. -3333 let path_stats = self.path_stats.discard(&path_id); -3334 self.partial_stats += path_stats; -3335 self.paths.remove(&path_id); -3336 self.spaces[SpaceId::Data].number_spaces.remove(&path_id); -3337 -3338 self.events.push_back( -3339 PathEvent::Discarded { -3340 id: path_id, -3341 path_stats: Box::new(path_stats), -3342 } -3343 .into(), -3344 ); -3345 } -3346 -3347 fn handle_lost_packets( -3348 &mut self, -3349 pn_space: SpaceId, -3350 path_id: PathId, -3351 now: Instant, -3352 lost_packets: Vec<u64>, -3353 lost_mtu_probe: Option<u64>, -3354 loss_delay: Duration, -3355 in_persistent_congestion: bool, -3356 size_of_lost_packets: u64, -3357 ) { -3358 debug_assert!(lost_packets.is_sorted(), "lost_packets must be sorted"); -3359 -3360 self.drain_lost_packets(now, pn_space, path_id); -3361 -3362 // OnPacketsLost -3363 if let Some(largest_lost) = lost_packets.last().cloned() { -3364 let old_bytes_in_flight = self.path_data_mut(path_id).in_flight.bytes; -3365 let largest_lost_sent = self.spaces[pn_space] -3366 .for_path(path_id) -3367 .sent_packets -3368 .get(largest_lost) -3369 .unwrap() -3370 .time_sent; -3371 let path_stats = self.path_stats.for_path(path_id); -3372 path_stats.lost_packets += lost_packets.len() as u64; -3373 path_stats.lost_bytes += size_of_lost_packets; -3374 trace!( -3375 %path_id, -3376 count = lost_packets.len(), -3377 lost_bytes = size_of_lost_packets, -3378 "packets lost", -3379 ); -3380 -3381 for &packet in &lost_packets { -3382 let Some(info) = self.spaces[pn_space].for_path(path_id).take(packet) else { -3383 continue; -3384 }; -3385 self.qlog -3386 .emit_packet_lost(packet, &info, loss_delay, pn_space.kind(), now); -3387 self.paths -3388 .get_mut(&path_id) -3389 .unwrap() -3390 .remove_in_flight(&info); -3391 -3392 for frame in info.stream_frames { -3393 self.streams.retransmit(frame); -3394 } -3395 self.spaces[pn_space].pending |= info.retransmits; -3396 let path = self.path_data_mut(path_id); -3397 path.mtud.on_non_probe_lost(packet, info.size); -3398 path.congestion.on_packet_lost(info.size, packet, now); -3399 -3400 self.spaces[pn_space].for_path(path_id).lost_packets.insert( -3401 packet, -3402 LostPacket { -3403 time_sent: info.time_sent, -3404 }, -3405 ); -3406 } -3407 -3408 let path = self.path_data_mut(path_id); -3409 if path.mtud.black_hole_detected(now) { -3410 path.congestion.on_mtu_update(path.mtud.current_mtu()); -3411 if let Some(max_datagram_size) = self.datagrams().max_size() -3412 && self.datagrams.drop_oversized(max_datagram_size) -3413 && self.datagrams.send_blocked -3414 { -3415 self.datagrams.send_blocked = false; -3416 self.events.push_back(Event::DatagramsUnblocked); -3417 } -3418 self.path_stats.for_path(path_id).black_holes_detected += 1; -3419 } -3420 -3421 // Don't apply congestion penalty for lost ack-only packets -3422 let lost_ack_eliciting = -3423 old_bytes_in_flight != self.path_data_mut(path_id).in_flight.bytes; -3424 -3425 if lost_ack_eliciting { -3426 self.path_stats.for_path(path_id).congestion_events += 1; -3427 self.path_data_mut(path_id).congestion.on_congestion_event( -3428 now, -3429 largest_lost_sent, -3430 in_persistent_congestion, -3431 false, -3432 size_of_lost_packets, -3433 largest_lost, -3434 ); -3435 } -3436 } -3437 -3438 // Handle a lost MTU probe -3439 if let Some(packet) = lost_mtu_probe { -3440 let info = self.spaces[SpaceId::Data] -3441 .for_path(path_id) -3442 .take(packet) -3443 .unwrap(); // safe: lost_mtu_probe is omitted from lost_packets, and -3444 // therefore must not have been removed yet -3445 self.paths -3446 .get_mut(&path_id) -3447 .unwrap() -3448 .remove_in_flight(&info); -3449 self.path_data_mut(path_id).mtud.on_probe_lost(); -3450 self.path_stats.for_path(path_id).lost_plpmtud_probes += 1; -3451 } -3452 } -3453 -3454 /// Returns the earliest time packets should be declared lost for all spaces on a path. -3455 /// -3456 /// If a path has an acknowledged packet with any prior un-acknowledged packets, the -3457 /// earliest un-acknowledged packet can be declared lost after a timeout has elapsed. -3458 /// The time returned is when this packet should be declared lost. -3459 fn loss_time_and_space(&self, path_id: PathId) -> Option<(Instant, SpaceId)> { -3460 SpaceId::iter() -3461 .filter_map(|id| { -3462 self.spaces[id] -3463 .number_spaces -3464 .get(&path_id) -3465 .and_then(|pns| pns.loss_time) -3466 .map(|time| (time, id)) -3467 }) -3468 .min_by_key(|&(time, _)| time) -3469 } -3470 -3471 /// Returns the earliest next PTO should fire for all spaces on a path. -3472 /// -3473 /// This needs to be fully deterministic because it is also used to determine the PTO -3474 /// that fired, not just to set the next timer. So if it fired in the past it needs to -3475 /// return the time from the past at which it fired. -3476 /// -3477 /// This is the next time a tail-loss probe should be sent. -3478 fn pto_time_and_space(&mut self, now: Instant, path_id: PathId) -> Option<(Instant, SpaceId)> { -3479 let path = self.path(path_id)?; -3480 let pto_count = path.pto_count; -3481 -3482 // Cap the maximum interval between two tail-loss probes. -3483 let max_interval = if path.rtt.get() > SLOW_RTT_THRESHOLD { -3484 // For slow links we want to increase the interval beyond 2s. -3485 (path.rtt.get() * 3) / 2 -3486 } else if let Some(idle) = path.idle_timeout.or(self.idle_timeout) -3487 && idle <= MIN_IDLE_FOR_FAST_PTO -3488 { -3489 // If the idle timeout is relatively low, cap at 1s so we get plenty of retries -3490 // before the idle timeout fires. -3491 MAX_PTO_FAST_INTERVAL -3492 } else { -3493 // Otherwise cap to 2s. -3494 MAX_PTO_INTERVAL -3495 }; -3496 -3497 if path_id == PathId::ZERO -3498 && path.in_flight.ack_eliciting == 0 -3499 && !self.peer_completed_handshake_address_validation() -3500 { -3501 // Address Validation during Connection Establishment: -3502 // https://www.rfc-editor.org/rfc/rfc9000.html#section-8.1. To prevent a -3503 // deadlock if an Initial or Handshake packet from the server is lost and the -3504 // server can not send more due to its anti-amplification limit the client must -3505 // send another packet on PTO. -3506 let space = match self.highest_space { -3507 SpaceKind::Handshake => SpaceId::Handshake, -3508 _ => SpaceId::Initial, -3509 }; -3510 -3511 let backoff = 2u32.pow(path.pto_count.min(MAX_BACKOFF_EXPONENT)); -3512 let duration = path.rtt.pto_base() * backoff; -3513 let duration = duration.min(max_interval); -3514 return Some((now + duration, space)); -3515 } -3516 -3517 let mut result = None; -3518 for space in SpaceId::iter() { -3519 let Some(pns) = self.spaces[space].number_spaces.get(&path_id) else { -3520 continue; -3521 }; -3522 -3523 if space == SpaceId::Data && !self.is_handshake_confirmed() { -3524 // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.1-7: -3525 // An endpoint MUST NOT set its PTO timer for the Application Data packet -3526 // number space until the handshake is confirmed. -3527 continue; -3528 } +3277 } else { +3278 // The packet should not yet be declared lost. +3279 if space.loss_time.is_none() { +3280 // Since we iterate in order the lowest packet number's loss time will +3281 // always be the earliest. +3282 space.loss_time = Some(info.time_sent + loss_delay); +3283 } +3284 persistent_congestion_start = None; +3285 } +3286 +3287 prev_packet = Some(packet); +3288 } +3289 +3290 self.handle_lost_packets( +3291 pn_space, +3292 path_id, +3293 now, +3294 lost_packets, +3295 lost_mtu_probe, +3296 loss_delay, +3297 in_persistent_congestion, +3298 size_of_lost_packets, +3299 ); +3300 } +3301 +3302 /// Drops the path state, declaring any remaining in-flight packets as lost +3303 fn discard_path(&mut self, path_id: PathId, now: Instant) { +3304 trace!(%path_id, "dropping path state"); +3305 let path = self.path_data(path_id); +3306 let in_flight_mtu_probe = path.mtud.in_flight_mtu_probe(); +3307 +3308 let mut size_of_lost_packets = 0u64; // add to path_stats.lost_bytes; +3309 let lost_pns: Vec<_> = self.spaces[SpaceId::Data] +3310 .for_path(path_id) +3311 .sent_packets +3312 .iter() +3313 .filter(|(pn, _info)| Some(*pn) != in_flight_mtu_probe) +3314 .map(|(pn, info)| { +3315 size_of_lost_packets += info.size as u64; +3316 pn +3317 }) +3318 .collect(); +3319 +3320 if !lost_pns.is_empty() { +3321 trace!( +3322 %path_id, +3323 count = lost_pns.len(), +3324 lost_bytes = size_of_lost_packets, +3325 "packets lost on path abandon" +3326 ); +3327 self.handle_lost_packets( +3328 SpaceId::Data, +3329 path_id, +3330 now, +3331 lost_pns, +3332 in_flight_mtu_probe, +3333 Duration::ZERO, +3334 false, +3335 size_of_lost_packets, +3336 ); +3337 } +3338 // Before removing the path, we fetch the final path stats via `Self::path_stats`. +3339 // This updates some values for the last time. +3340 let path_stats = self.path_stats.discard(&path_id); +3341 self.partial_stats += path_stats; +3342 self.paths.remove(&path_id); +3343 self.spaces[SpaceId::Data].number_spaces.remove(&path_id); +3344 +3345 self.events.push_back( +3346 PathEvent::Discarded { +3347 id: path_id, +3348 path_stats: Box::new(path_stats), +3349 } +3350 .into(), +3351 ); +3352 } +3353 +3354 fn handle_lost_packets( +3355 &mut self, +3356 pn_space: SpaceId, +3357 path_id: PathId, +3358 now: Instant, +3359 lost_packets: Vec<u64>, +3360 lost_mtu_probe: Option<u64>, +3361 loss_delay: Duration, +3362 in_persistent_congestion: bool, +3363 size_of_lost_packets: u64, +3364 ) { +3365 debug_assert!(lost_packets.is_sorted(), "lost_packets must be sorted"); +3366 +3367 self.drain_lost_packets(now, pn_space, path_id); +3368 +3369 // OnPacketsLost +3370 if let Some(largest_lost) = lost_packets.last().cloned() { +3371 let old_bytes_in_flight = self.path_data_mut(path_id).in_flight.bytes; +3372 let largest_lost_sent = self.spaces[pn_space] +3373 .for_path(path_id) +3374 .sent_packets +3375 .get(largest_lost) +3376 .unwrap() +3377 .time_sent; +3378 let path_stats = self.path_stats.for_path(path_id); +3379 path_stats.lost_packets += lost_packets.len() as u64; +3380 path_stats.lost_bytes += size_of_lost_packets; +3381 trace!( +3382 %path_id, +3383 count = lost_packets.len(), +3384 lost_bytes = size_of_lost_packets, +3385 "packets lost", +3386 ); +3387 +3388 for &packet in &lost_packets { +3389 let Some(info) = self.spaces[pn_space].for_path(path_id).take(packet) else { +3390 continue; +3391 }; +3392 self.qlog +3393 .emit_packet_lost(packet, &info, loss_delay, pn_space.kind(), now); +3394 self.paths +3395 .get_mut(&path_id) +3396 .unwrap() +3397 .remove_in_flight(&info); +3398 +3399 for frame in info.stream_frames { +3400 self.streams.retransmit(frame); +3401 } +3402 self.spaces[pn_space].pending |= info.retransmits; +3403 let path = self.path_data_mut(path_id); +3404 path.mtud.on_non_probe_lost(packet, info.size); +3405 path.congestion.on_packet_lost(info.size, packet, now); +3406 +3407 self.spaces[pn_space].for_path(path_id).lost_packets.insert( +3408 packet, +3409 LostPacket { +3410 time_sent: info.time_sent, +3411 }, +3412 ); +3413 } +3414 +3415 let path = self.path_data_mut(path_id); +3416 if path.mtud.black_hole_detected(now) { +3417 path.congestion.on_mtu_update(path.mtud.current_mtu()); +3418 if let Some(max_datagram_size) = self.datagrams().max_size() +3419 && self.datagrams.drop_oversized(max_datagram_size) +3420 && self.datagrams.send_blocked +3421 { +3422 self.datagrams.send_blocked = false; +3423 self.events.push_back(Event::DatagramsUnblocked); +3424 } +3425 self.path_stats.for_path(path_id).black_holes_detected += 1; +3426 } +3427 +3428 // Don't apply congestion penalty for lost ack-only packets +3429 let lost_ack_eliciting = +3430 old_bytes_in_flight != self.path_data_mut(path_id).in_flight.bytes; +3431 +3432 if lost_ack_eliciting { +3433 self.path_stats.for_path(path_id).congestion_events += 1; +3434 self.path_data_mut(path_id).congestion.on_congestion_event( +3435 now, +3436 largest_lost_sent, +3437 in_persistent_congestion, +3438 false, +3439 size_of_lost_packets, +3440 largest_lost, +3441 ); +3442 } +3443 } +3444 +3445 // Handle a lost MTU probe +3446 if let Some(packet) = lost_mtu_probe { +3447 let info = self.spaces[SpaceId::Data] +3448 .for_path(path_id) +3449 .take(packet) +3450 .unwrap(); // safe: lost_mtu_probe is omitted from lost_packets, and +3451 // therefore must not have been removed yet +3452 self.paths +3453 .get_mut(&path_id) +3454 .unwrap() +3455 .remove_in_flight(&info); +3456 self.path_data_mut(path_id).mtud.on_probe_lost(); +3457 self.path_stats.for_path(path_id).lost_plpmtud_probes += 1; +3458 } +3459 } +3460 +3461 /// Returns the earliest time packets should be declared lost for all spaces on a path. +3462 /// +3463 /// If a path has an acknowledged packet with any prior un-acknowledged packets, the +3464 /// earliest un-acknowledged packet can be declared lost after a timeout has elapsed. +3465 /// The time returned is when this packet should be declared lost. +3466 fn loss_time_and_space(&self, path_id: PathId) -> Option<(Instant, SpaceId)> { +3467 SpaceId::iter() +3468 .filter_map(|id| { +3469 self.spaces[id] +3470 .number_spaces +3471 .get(&path_id) +3472 .and_then(|pns| pns.loss_time) +3473 .map(|time| (time, id)) +3474 }) +3475 .min_by_key(|&(time, _)| time) +3476 } +3477 +3478 /// Returns the earliest next PTO should fire for all spaces on a path. +3479 /// +3480 /// This needs to be fully deterministic because it is also used to determine the PTO +3481 /// that fired, not just to set the next timer. So if it fired in the past it needs to +3482 /// return the time from the past at which it fired. +3483 /// +3484 /// This is the next time a tail-loss probe should be sent. +3485 fn pto_time_and_space(&mut self, now: Instant, path_id: PathId) -> Option<(Instant, SpaceId)> { +3486 let path = self.path(path_id)?; +3487 let pto_count = path.pto_count; +3488 +3489 // Cap the maximum interval between two tail-loss probes. +3490 let max_interval = if path.rtt.get() > SLOW_RTT_THRESHOLD { +3491 // For slow links we want to increase the interval beyond 2s. +3492 (path.rtt.get() * 3) / 2 +3493 } else if let Some(idle) = path.idle_timeout.or(self.idle_timeout) +3494 && idle <= MIN_IDLE_FOR_FAST_PTO +3495 { +3496 // If the idle timeout is relatively low, cap at 1s so we get plenty of retries +3497 // before the idle timeout fires. +3498 MAX_PTO_FAST_INTERVAL +3499 } else { +3500 // Otherwise cap to 2s. +3501 MAX_PTO_INTERVAL +3502 }; +3503 +3504 if path_id == PathId::ZERO +3505 && path.in_flight.ack_eliciting == 0 +3506 && !self.peer_completed_handshake_address_validation() +3507 { +3508 // Address Validation during Connection Establishment: +3509 // https://www.rfc-editor.org/rfc/rfc9000.html#section-8.1. To prevent a +3510 // deadlock if an Initial or Handshake packet from the server is lost and the +3511 // server can not send more due to its anti-amplification limit the client must +3512 // send another packet on PTO. +3513 let space = match self.highest_space { +3514 SpaceKind::Handshake => SpaceId::Handshake, +3515 _ => SpaceId::Initial, +3516 }; +3517 +3518 let backoff = 2u32.pow(path.pto_count.min(MAX_BACKOFF_EXPONENT)); +3519 let duration = path.rtt.pto_base() * backoff; +3520 let duration = duration.min(max_interval); +3521 return Some((now + duration, space)); +3522 } +3523 +3524 let mut result = None; +3525 for space in SpaceId::iter() { +3526 let Some(pns) = self.spaces[space].number_spaces.get(&path_id) else { +3527 continue; +3528 }; 3529 -3530 if !pns.has_in_flight() { -3531 continue; -3532 } -3533 -3534 // Compute the PTO duration for this space, we want to cap the maximum interval -3535 // between two tail-loss probes so to not do a simple exponential backoff but -3536 // rather iterate through the probes to compute the capped increment for an -3537 // exponential backoff at each step. -3538 let duration = { -3539 let max_ack_delay = if space == SpaceId::Data { -3540 self.ack_frequency.max_ack_delay_for_pto() -3541 } else { -3542 Duration::ZERO -3543 }; -3544 let pto_base = path.rtt.pto_base() + max_ack_delay; -3545 let mut duration = pto_base; -3546 for i in 1..=pto_count { -3547 let exponential_duration = pto_base * 2u32.pow(i.min(MAX_BACKOFF_EXPONENT)); -3548 let max_duration = duration + max_interval; -3549 duration = exponential_duration.min(max_duration); -3550 } -3551 duration -3552 }; -3553 -3554 let Some(last_ack_eliciting) = pns.time_of_last_ack_eliciting_packet else { -3555 continue; -3556 }; -3557 // Base the deadline on when the last probe was sent, so the PTO -3558 // doesn't fire before the response has had time to arrive. -3559 let pto = last_ack_eliciting + duration; -3560 if result.is_none_or(|(earliest_pto, _)| pto < earliest_pto) { -3561 if path.anti_amplification_blocked(1) { -3562 // Nothing would be able to be sent. -3563 continue; -3564 } -3565 if path.in_flight.ack_eliciting == 0 { -3566 // Nothing ack-eliciting, no PTO to arm/fire. -3567 continue; -3568 } -3569 result = Some((pto, space)); -3570 } -3571 } -3572 result -3573 } -3574 -3575 /// Whether the peer validated our address in the connection handshake. -3576 fn peer_completed_handshake_address_validation(&self) -> bool { -3577 if self.side.is_server() || self.state.is_closed() { -3578 return true; -3579 } -3580 // The server is guaranteed to have validated our address if any of our handshake or -3581 // 1-RTT packets are acknowledged or we've seen HANDSHAKE_DONE and discarded -3582 // handshake keys. -3583 self.spaces[SpaceId::Handshake] -3584 .path_space(PathId::ZERO) -3585 .and_then(|pns| pns.largest_acked_packet_pn) -3586 .is_some() -3587 || self.spaces[SpaceId::Data] -3588 .path_space(PathId::ZERO) -3589 .and_then(|pns| pns.largest_acked_packet_pn) -3590 .is_some() -3591 || (self.crypto_state.has_keys(EncryptionLevel::OneRtt) -3592 && !self.crypto_state.has_keys(EncryptionLevel::Handshake)) -3593 } -3594 -3595 /// Resets the the [`PathTimer::LossDetection`] timer to the next instant it may be needed -3596 /// -3597 /// The timer must fire if either: -3598 /// - An ack-eliciting packet we sent needs to be declared lost. -3599 /// - A tail-loss probe needs to be sent. -3600 /// -3601 /// See [`Connection::on_loss_detection_timeout`] for details. -3602 fn set_loss_detection_timer(&mut self, now: Instant, path_id: PathId) { -3603 if self.state.is_closed() { -3604 // No loss detection takes place on closed connections, and `close_common` already -3605 // stopped time timer. Ensure we don't restart it inadvertently, e.g. in response to a -3606 // reordered packet being handled by state-insensitive code. -3607 return; -3608 } -3609 -3610 if let Some((loss_time, _)) = self.loss_time_and_space(path_id) { -3611 // Time threshold loss detection. -3612 self.timers.set( -3613 Timer::PerPath(path_id, PathTimer::LossDetection), -3614 loss_time, -3615 self.qlog.with_time(now), -3616 ); -3617 return; -3618 } -3619 -3620 // Determine which PN space to arm PTO for. -3621 // We can only send tail-loss probes on paths that aren't abandoned yet. -3622 if !self.abandoned_paths.contains(&path_id) -3623 && let Some((timeout, _)) = self.pto_time_and_space(now, path_id) -3624 { -3625 self.timers.set( -3626 Timer::PerPath(path_id, PathTimer::LossDetection), -3627 timeout, -3628 self.qlog.with_time(now), -3629 ); -3630 } else { -3631 self.timers.stop( -3632 Timer::PerPath(path_id, PathTimer::LossDetection), -3633 self.qlog.with_time(now), -3634 ); -3635 } -3636 } -3637 -3638 /// The maximum probe timeout across all paths -3639 /// -3640 /// See [`Connection::pto`] -3641 fn max_pto_for_space(&self, space: SpaceKind) -> Duration { -3642 self.paths -3643 .keys() -3644 .map(|path_id| self.pto(space, *path_id)) -3645 .max() -3646 .unwrap_or_else(|| { -3647 // No paths remain (e.g. last path was abandoned and the NoAvailablePath grace timer -3648 // fired before any new path was opened). Fall back to a PTO derived from the -3649 // configured initial RTT, matching RFC 9002 §6.2.2 initial values. -3650 let rtt = self.config.initial_rtt; -3651 let max_ack_delay = match space { -3652 SpaceKind::Initial | SpaceKind::Handshake => Duration::ZERO, -3653 SpaceKind::Data => self.ack_frequency.max_ack_delay_for_pto(), -3654 }; -3655 rtt + cmp::max(4 * (rtt / 2), TIMER_GRANULARITY) + max_ack_delay -3656 }) -3657 } -3658 -3659 /// Probe Timeout -3660 /// -3661 /// The PTO is logically the time in which you'd expect to receive an acknowledgement -3662 /// for a packet. So approximately RTT + max_ack_delay. -3663 fn pto(&self, space: SpaceKind, path_id: PathId) -> Duration { -3664 let max_ack_delay = match space { -3665 SpaceKind::Initial | SpaceKind::Handshake => Duration::ZERO, -3666 SpaceKind::Data => self.ack_frequency.max_ack_delay_for_pto(), -3667 }; -3668 self.path_data(path_id).rtt.pto_base() + max_ack_delay -3669 } -3670 -3671 fn on_packet_authenticated( -3672 &mut self, -3673 now: Instant, -3674 space_id: SpaceKind, -3675 path_id: PathId, -3676 ecn: Option<EcnCodepoint>, -3677 packet_number: Option<u64>, -3678 spin: bool, -3679 is_1rtt: bool, -3680 remote: &FourTuple, -3681 ) { -3682 // During the handshake we already have discarded packets that do not match the path -3683 // remote. So any off-path packet here is either a probing packet or a -3684 // migration. Handling probing packets here means that the path's idle timeout will -3685 // be reset and will delay detecting the path as idle. However tail-loss probes -3686 // would still not get acknowledged if the path was broken so eventually the path -3687 // would still become idle. -3688 let is_on_path = *remote == self.path_data(path_id).network_path; -3689 -3690 self.total_authed_packets += 1; -3691 self.reset_keep_alive(path_id, now); -3692 self.reset_idle_timeout(now, space_id, path_id); -3693 self.path_data_mut(path_id).permit_idle_reset = true; -3694 -3695 // Do not process ECN for off-path packets. If this is a migration we'll get ECN -3696 // back once we've migrated. -3697 if is_on_path { -3698 self.receiving_ecn |= ecn.is_some(); -3699 if let Some(x) = ecn { -3700 let space = &mut self.spaces[space_id]; -3701 space.for_path(path_id).ecn_counters += x; -3702 -3703 if x.is_ce() { -3704 space -3705 .for_path(path_id) -3706 .pending_acks -3707 .set_immediate_ack_required(); -3708 } -3709 } -3710 } -3711 -3712 let Some(packet_number) = packet_number else { -3713 return; -3714 }; -3715 match &self.side { -3716 ConnectionSide::Client { .. } => { -3717 // If we received a handshake packet that authenticated, then we're talking to -3718 // the real server. From now on we should no longer allow the server to migrate -3719 // its address. -3720 if space_id == SpaceKind::Handshake -3721 && let Some(hs) = self.state.as_handshake_mut() -3722 { -3723 hs.allow_server_migration = false; -3724 } -3725 } -3726 ConnectionSide::Server { .. } => { -3727 if self.crypto_state.has_keys(EncryptionLevel::Initial) -3728 && space_id == SpaceKind::Handshake +3530 if space == SpaceId::Data && !self.is_handshake_confirmed() { +3531 // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.1-7: +3532 // An endpoint MUST NOT set its PTO timer for the Application Data packet +3533 // number space until the handshake is confirmed. +3534 continue; +3535 } +3536 +3537 if !pns.has_in_flight() { +3538 continue; +3539 } +3540 +3541 // Compute the PTO duration for this space, we want to cap the maximum interval +3542 // between two tail-loss probes so to not do a simple exponential backoff but +3543 // rather iterate through the probes to compute the capped increment for an +3544 // exponential backoff at each step. +3545 let duration = { +3546 let max_ack_delay = if space == SpaceId::Data { +3547 self.ack_frequency.max_ack_delay_for_pto() +3548 } else { +3549 Duration::ZERO +3550 }; +3551 let pto_base = path.rtt.pto_base() + max_ack_delay; +3552 let mut duration = pto_base; +3553 for i in 1..=pto_count { +3554 let exponential_duration = pto_base * 2u32.pow(i.min(MAX_BACKOFF_EXPONENT)); +3555 let max_duration = duration + max_interval; +3556 duration = exponential_duration.min(max_duration); +3557 } +3558 duration +3559 }; +3560 +3561 let Some(last_ack_eliciting) = pns.time_of_last_ack_eliciting_packet else { +3562 continue; +3563 }; +3564 // Base the deadline on when the last probe was sent, so the PTO +3565 // doesn't fire before the response has had time to arrive. +3566 let pto = last_ack_eliciting + duration; +3567 if result.is_none_or(|(earliest_pto, _)| pto < earliest_pto) { +3568 if path.anti_amplification_blocked(1) { +3569 // Nothing would be able to be sent. +3570 continue; +3571 } +3572 if path.in_flight.ack_eliciting == 0 { +3573 // Nothing ack-eliciting, no PTO to arm/fire. +3574 continue; +3575 } +3576 result = Some((pto, space)); +3577 } +3578 } +3579 result +3580 } +3581 +3582 /// Whether the peer validated our address in the connection handshake. +3583 fn peer_completed_handshake_address_validation(&self) -> bool { +3584 if self.side.is_server() || self.state.is_closed() { +3585 return true; +3586 } +3587 // The server is guaranteed to have validated our address if any of our handshake or +3588 // 1-RTT packets are acknowledged or we've seen HANDSHAKE_DONE and discarded +3589 // handshake keys. +3590 self.spaces[SpaceId::Handshake] +3591 .path_space(PathId::ZERO) +3592 .and_then(|pns| pns.largest_acked_packet_pn) +3593 .is_some() +3594 || self.spaces[SpaceId::Data] +3595 .path_space(PathId::ZERO) +3596 .and_then(|pns| pns.largest_acked_packet_pn) +3597 .is_some() +3598 || (self.crypto_state.has_keys(EncryptionLevel::OneRtt) +3599 && !self.crypto_state.has_keys(EncryptionLevel::Handshake)) +3600 } +3601 +3602 /// Resets the the [`PathTimer::LossDetection`] timer to the next instant it may be needed +3603 /// +3604 /// The timer must fire if either: +3605 /// - An ack-eliciting packet we sent needs to be declared lost. +3606 /// - A tail-loss probe needs to be sent. +3607 /// +3608 /// See [`Connection::on_loss_detection_timeout`] for details. +3609 fn set_loss_detection_timer(&mut self, now: Instant, path_id: PathId) { +3610 if self.state.is_closed() { +3611 // No loss detection takes place on closed connections, and `close_common` already +3612 // stopped time timer. Ensure we don't restart it inadvertently, e.g. in response to a +3613 // reordered packet being handled by state-insensitive code. +3614 return; +3615 } +3616 +3617 if let Some((loss_time, _)) = self.loss_time_and_space(path_id) { +3618 // Time threshold loss detection. +3619 self.timers.set( +3620 Timer::PerPath(path_id, PathTimer::LossDetection), +3621 loss_time, +3622 self.qlog.with_time(now), +3623 ); +3624 return; +3625 } +3626 +3627 // Determine which PN space to arm PTO for. +3628 // We can only send tail-loss probes on paths that aren't abandoned yet. +3629 if !self.abandoned_paths.contains(&path_id) +3630 && let Some((timeout, _)) = self.pto_time_and_space(now, path_id) +3631 { +3632 self.timers.set( +3633 Timer::PerPath(path_id, PathTimer::LossDetection), +3634 timeout, +3635 self.qlog.with_time(now), +3636 ); +3637 } else { +3638 self.timers.stop( +3639 Timer::PerPath(path_id, PathTimer::LossDetection), +3640 self.qlog.with_time(now), +3641 ); +3642 } +3643 } +3644 +3645 /// The maximum probe timeout across all paths +3646 /// +3647 /// See [`Connection::pto`] +3648 fn max_pto_for_space(&self, space: SpaceKind) -> Duration { +3649 self.paths +3650 .keys() +3651 .map(|path_id| self.pto(space, *path_id)) +3652 .max() +3653 .unwrap_or_else(|| { +3654 // No paths remain (e.g. last path was abandoned and the NoAvailablePath grace timer +3655 // fired before any new path was opened). Fall back to a PTO derived from the +3656 // configured initial RTT, matching RFC 9002 §6.2.2 initial values. +3657 let rtt = self.config.initial_rtt; +3658 let max_ack_delay = match space { +3659 SpaceKind::Initial | SpaceKind::Handshake => Duration::ZERO, +3660 SpaceKind::Data => self.ack_frequency.max_ack_delay_for_pto(), +3661 }; +3662 rtt + cmp::max(4 * (rtt / 2), TIMER_GRANULARITY) + max_ack_delay +3663 }) +3664 } +3665 +3666 /// Probe Timeout +3667 /// +3668 /// The PTO is logically the time in which you'd expect to receive an acknowledgement +3669 /// for a packet. So approximately RTT + max_ack_delay. +3670 fn pto(&self, space: SpaceKind, path_id: PathId) -> Duration { +3671 let max_ack_delay = match space { +3672 SpaceKind::Initial | SpaceKind::Handshake => Duration::ZERO, +3673 SpaceKind::Data => self.ack_frequency.max_ack_delay_for_pto(), +3674 }; +3675 self.path_data(path_id).rtt.pto_base() + max_ack_delay +3676 } +3677 +3678 fn on_packet_authenticated( +3679 &mut self, +3680 now: Instant, +3681 space_id: SpaceKind, +3682 path_id: PathId, +3683 ecn: Option<EcnCodepoint>, +3684 packet_number: Option<u64>, +3685 spin: bool, +3686 is_1rtt: bool, +3687 remote: &FourTuple, +3688 ) { +3689 // During the handshake we already have discarded packets that do not match the path +3690 // remote. So any off-path packet here is either a probing packet or a +3691 // migration. Handling probing packets here means that the path's idle timeout will +3692 // be reset and will delay detecting the path as idle. However tail-loss probes +3693 // would still not get acknowledged if the path was broken so eventually the path +3694 // would still become idle. +3695 let is_on_path = *remote == self.path_data(path_id).network_path; +3696 +3697 self.total_authed_packets += 1; +3698 self.reset_keep_alive(path_id, now); +3699 self.reset_idle_timeout(now, space_id, path_id); +3700 self.path_data_mut(path_id).permit_idle_reset = true; +3701 +3702 // Do not process ECN for off-path packets. If this is a migration we'll get ECN +3703 // back once we've migrated. +3704 if is_on_path { +3705 self.receiving_ecn |= ecn.is_some(); +3706 if let Some(x) = ecn { +3707 let space = &mut self.spaces[space_id]; +3708 space.for_path(path_id).ecn_counters += x; +3709 +3710 if x.is_ce() { +3711 space +3712 .for_path(path_id) +3713 .pending_acks +3714 .set_immediate_ack_required(); +3715 } +3716 } +3717 } +3718 +3719 let Some(packet_number) = packet_number else { +3720 return; +3721 }; +3722 match &self.side { +3723 ConnectionSide::Client { .. } => { +3724 // If we received a handshake packet that authenticated, then we're talking to +3725 // the real server. From now on we should no longer allow the server to migrate +3726 // its address. +3727 if space_id == SpaceKind::Handshake +3728 && let Some(hs) = self.state.as_handshake_mut() 3729 { -3730 // A server stops sending and processing Initial packets when it receives its first Handshake packet. -3731 self.discard_space(now, SpaceKind::Initial); -3732 } -3733 if self.crypto_state.has_keys(EncryptionLevel::ZeroRtt) && is_1rtt { -3734 // Discard 0-RTT keys soon after receiving a 1-RTT packet -3735 self.set_key_discard_timer(now, space_id) -3736 } -3737 } -3738 } -3739 let space = self.spaces[space_id].for_path(path_id); -3740 -3741 space.pending_acks.insert_one(packet_number, now); -3742 if packet_number >= space.largest_received_packet_number.unwrap_or_default() { -3743 space.largest_received_packet_number = Some(packet_number); -3744 -3745 // Update outgoing spin bit for on-path packets, inverting iff we're the client -3746 if is_on_path { -3747 self.spin = self.side.is_client() ^ spin; -3748 } -3749 } -3750 } +3730 hs.allow_server_migration = false; +3731 } +3732 } +3733 ConnectionSide::Server { .. } => { +3734 if self.crypto_state.has_keys(EncryptionLevel::Initial) +3735 && space_id == SpaceKind::Handshake +3736 { +3737 // A server stops sending and processing Initial packets when it receives its first Handshake packet. +3738 self.discard_space(now, SpaceKind::Initial); +3739 } +3740 if self.crypto_state.has_keys(EncryptionLevel::ZeroRtt) && is_1rtt { +3741 // Discard 0-RTT keys soon after receiving a 1-RTT packet +3742 self.set_key_discard_timer(now, space_id) +3743 } +3744 } +3745 } +3746 let space = self.spaces[space_id].for_path(path_id); +3747 +3748 space.pending_acks.insert_one(packet_number, now); +3749 if packet_number >= space.largest_received_packet_number.unwrap_or_default() { +3750 space.largest_received_packet_number = Some(packet_number); 3751 -3752 /// Resets the idle timeout timers -3753 /// -3754 /// Without multipath there is only the connection-wide idle timeout. When multipath is -3755 /// enabled there is an additional per-path idle timeout. -3756 fn reset_idle_timeout(&mut self, now: Instant, space: SpaceKind, path_id: PathId) { -3757 // First reset the global idle timeout. -3758 if let Some(timeout) = self.idle_timeout { -3759 if self.state.is_closed() { -3760 self.timers -3761 .stop(Timer::Conn(ConnTimer::Idle), self.qlog.with_time(now)); -3762 } else { -3763 let dt = cmp::max(timeout, 3 * self.max_pto_for_space(space)); -3764 self.timers.set( -3765 Timer::Conn(ConnTimer::Idle), -3766 now + dt, -3767 self.qlog.with_time(now), -3768 ); -3769 } -3770 } -3771 -3772 // Now handle the per-path state -3773 if let Some(timeout) = self.path_data(path_id).idle_timeout { -3774 if self.state.is_closed() { -3775 self.timers.stop( -3776 Timer::PerPath(path_id, PathTimer::PathIdle), -3777 self.qlog.with_time(now), -3778 ); -3779 } else { -3780 let dt = cmp::max(timeout, 3 * self.pto(space, path_id)); -3781 self.timers.set( -3782 Timer::PerPath(path_id, PathTimer::PathIdle), -3783 now + dt, +3752 // Update outgoing spin bit for on-path packets, inverting iff we're the client +3753 if is_on_path { +3754 self.spin = self.side.is_client() ^ spin; +3755 } +3756 } +3757 } +3758 +3759 /// Resets the idle timeout timers +3760 /// +3761 /// Without multipath there is only the connection-wide idle timeout. When multipath is +3762 /// enabled there is an additional per-path idle timeout. +3763 fn reset_idle_timeout(&mut self, now: Instant, space: SpaceKind, path_id: PathId) { +3764 // First reset the global idle timeout. +3765 if let Some(timeout) = self.idle_timeout { +3766 if self.state.is_closed() { +3767 self.timers +3768 .stop(Timer::Conn(ConnTimer::Idle), self.qlog.with_time(now)); +3769 } else { +3770 let dt = cmp::max(timeout, 3 * self.max_pto_for_space(space)); +3771 self.timers.set( +3772 Timer::Conn(ConnTimer::Idle), +3773 now + dt, +3774 self.qlog.with_time(now), +3775 ); +3776 } +3777 } +3778 +3779 // Now handle the per-path state +3780 if let Some(timeout) = self.path_data(path_id).idle_timeout { +3781 if self.state.is_closed() { +3782 self.timers.stop( +3783 Timer::PerPath(path_id, PathTimer::PathIdle), 3784 self.qlog.with_time(now), 3785 ); -3786 } -3787 } -3788 } -3789 -3790 /// Resets both the [`ConnTimer::KeepAlive`] and [`PathTimer::PathKeepAlive`] timers -3791 fn reset_keep_alive(&mut self, path_id: PathId, now: Instant) { -3792 if !self.state.is_established() { -3793 return; +3786 } else { +3787 let dt = cmp::max(timeout, 3 * self.pto(space, path_id)); +3788 self.timers.set( +3789 Timer::PerPath(path_id, PathTimer::PathIdle), +3790 now + dt, +3791 self.qlog.with_time(now), +3792 ); +3793 } 3794 } -3795 -3796 if let Some(interval) = self.config.keep_alive_interval { -3797 self.timers.set( -3798 Timer::Conn(ConnTimer::KeepAlive), -3799 now + interval, -3800 self.qlog.with_time(now), -3801 ); -3802 } -3803 -3804 if let Some(interval) = self.path_data(path_id).keep_alive { -3805 self.timers.set( -3806 Timer::PerPath(path_id, PathTimer::PathKeepAlive), -3807 now + interval, -3808 self.qlog.with_time(now), -3809 ); -3810 } -3811 } -3812 -3813 /// Sets the timer for when a previously issued CID should be retired next -3814 fn reset_cid_retirement(&mut self, now: Instant) { -3815 if let Some((_path, t)) = self.next_cid_retirement() { -3816 self.timers.set( -3817 Timer::Conn(ConnTimer::PushNewCid), -3818 t, -3819 self.qlog.with_time(now), -3820 ); -3821 } -3822 } -3823 -3824 /// The next time when a previously issued CID should be retired -3825 fn next_cid_retirement(&self) -> Option<(PathId, Instant)> { -3826 self.local_cid_state -3827 .iter() -3828 .filter_map(|(path_id, cid_state)| cid_state.next_timeout().map(|t| (*path_id, t))) -3829 .min_by_key(|(_path_id, timeout)| *timeout) -3830 } -3831 -3832 /// Handle the already-decrypted first packet from the client -3833 /// -3834 /// Decrypting the first packet in the `Endpoint` allows stateless packet handling to be more -3835 /// efficient. -3836 pub(crate) fn handle_first_packet( -3837 &mut self, -3838 now: Instant, -3839 network_path: FourTuple, -3840 ecn: Option<EcnCodepoint>, -3841 packet_number: u64, -3842 packet: InitialPacket, -3843 remaining: Option<BytesMut>, -3844 ) -> Result<(), ConnectionError> { -3845 let span = trace_span!("first recv"); -3846 let _guard = span.enter(); -3847 debug_assert!(self.side.is_server()); -3848 let len = packet.header_data.len() + packet.payload.len(); -3849 let path_id = PathId::ZERO; -3850 self.path_data_mut(path_id).total_recvd = len as u64; -3851 -3852 if let Some(hs) = self.state.as_handshake_mut() { -3853 hs.expected_token = packet.header.token.clone(); -3854 } else { -3855 unreachable!("first packet must be delivered in Handshake state"); -3856 } -3857 -3858 // The first packet is always on PathId::ZERO -3859 self.on_packet_authenticated( -3860 now, -3861 SpaceKind::Initial, -3862 path_id, -3863 ecn, -3864 Some(packet_number), -3865 false, -3866 false, -3867 &network_path, -3868 ); -3869 -3870 let packet: Packet = packet.into(); -3871 -3872 let mut qlog = QlogRecvPacket::new(len); -3873 qlog.header(&packet.header, Some(packet_number), path_id); -3874 -3875 self.process_decrypted_packet( -3876 now, -3877 network_path, -3878 path_id, -3879 Some(packet_number), -3880 packet, -3881 &mut qlog, -3882 )?; -3883 self.qlog.emit_packet_received(qlog, now); -3884 if let Some(data) = remaining { -3885 self.handle_coalesced(now, network_path, path_id, ecn, data); -3886 } -3887 -3888 self.qlog.emit_recovery_metrics( -3889 path_id, -3890 &mut self -3891 .paths -3892 .get_mut(&path_id) -3893 .expect("path_id was supplied by the caller for an active path") -3894 .data, -3895 now, -3896 ); -3897 -3898 Ok(()) -3899 } -3900 -3901 fn init_0rtt(&mut self, now: Instant) { -3902 let Some((header, packet)) = self.crypto_state.session.early_crypto() else { -3903 return; -3904 }; -3905 if self.side.is_client() { -3906 match self.crypto_state.session.transport_parameters() { -3907 Ok(params) => { -3908 let params = params -3909 .expect("crypto layer didn't supply transport parameters with ticket"); -3910 // Certain values must not be cached -3911 let params = TransportParameters { -3912 initial_src_cid: None, -3913 original_dst_cid: None, -3914 preferred_address: None, -3915 retry_src_cid: None, -3916 stateless_reset_token: None, -3917 min_ack_delay: None, -3918 ack_delay_exponent: TransportParameters::default().ack_delay_exponent, -3919 max_ack_delay: TransportParameters::default().max_ack_delay, -3920 initial_max_path_id: None, -3921 ..params -3922 }; -3923 self.set_peer_params(params); -3924 self.qlog.emit_peer_transport_params_restored(self, now); -3925 } -3926 Err(e) => { -3927 error!("session ticket has malformed transport parameters: {}", e); -3928 return; -3929 } -3930 } -3931 } -3932 trace!("0-RTT enabled"); -3933 self.crypto_state.enable_zero_rtt(header, packet); -3934 } -3935 -3936 fn read_crypto( -3937 &mut self, -3938 space: SpaceId, -3939 crypto: &frame::Crypto, -3940 payload_len: usize, -3941 ) -> Result<(), TransportError> { -3942 let expected = if !self.state.is_handshake() { -3943 SpaceId::Data -3944 } else if self.highest_space == SpaceKind::Initial { -3945 SpaceId::Initial -3946 } else { -3947 // On the server, self.highest_space can be Data after receiving the client's first -3948 // flight, but we expect Handshake CRYPTO until the handshake is complete. -3949 SpaceId::Handshake -3950 }; -3951 // We can't decrypt Handshake packets when highest_space is Initial, CRYPTO frames in 0-RTT -3952 // packets are illegal, and we don't process 1-RTT packets until the handshake is -3953 // complete. Therefore, we will never see CRYPTO data from a later-than-expected space. -3954 debug_assert!(space <= expected, "received out-of-order CRYPTO data"); -3955 -3956 let end = crypto.offset + crypto.data.len() as u64; -3957 if space < expected -3958 && end -3959 > self.crypto_state.spaces[space.kind()] -3960 .crypto_stream -3961 .bytes_read() -3962 { -3963 warn!( -3964 "received new {:?} CRYPTO data when expecting {:?}", -3965 space, expected -3966 ); -3967 return Err(TransportError::PROTOCOL_VIOLATION( -3968 "new data at unexpected encryption level", -3969 )); -3970 } -3971 -3972 let crypto_space = &mut self.crypto_state.spaces[space.kind()]; -3973 let max = end.saturating_sub(crypto_space.crypto_stream.bytes_read()); -3974 if max > self.config.crypto_buffer_size as u64 { -3975 return Err(TransportError::CRYPTO_BUFFER_EXCEEDED("")); -3976 } -3977 -3978 crypto_space -3979 .crypto_stream -3980 .insert(crypto.offset, crypto.data.clone(), payload_len); -3981 while let Some(chunk) = crypto_space.crypto_stream.read(usize::MAX, true) { -3982 trace!("consumed {} CRYPTO bytes", chunk.bytes.len()); -3983 if self.crypto_state.session.read_handshake(&chunk.bytes)? { -3984 self.events.push_back(Event::HandshakeDataReady); -3985 } -3986 } -3987 -3988 Ok(()) -3989 } -3990 -3991 fn write_crypto(&mut self) { -3992 loop { -3993 let space = self.highest_space; -3994 let mut outgoing = Vec::new(); -3995 if let Some(crypto) = self.crypto_state.session.write_handshake(&mut outgoing) { -3996 match space { -3997 SpaceKind::Initial => { -3998 self.upgrade_crypto(SpaceKind::Handshake, crypto); -3999 } -4000 SpaceKind::Handshake => { -4001 self.upgrade_crypto(SpaceKind::Data, crypto); -4002 } -4003 SpaceKind::Data => unreachable!("got updated secrets during 1-RTT"), -4004 } -4005 } -4006 if outgoing.is_empty() { -4007 if space == self.highest_space { -4008 break; -4009 } else { -4010 // Keys updated, check for more data to send -4011 continue; -4012 } -4013 } -4014 let offset = self.crypto_state.spaces[space].crypto_offset; -4015 let outgoing = Bytes::from(outgoing); -4016 if let Some(hs) = self.state.as_handshake_mut() -4017 && space == SpaceKind::Initial -4018 && offset == 0 -4019 && self.side.is_client() -4020 { -4021 hs.client_hello = Some(outgoing.clone()); -4022 } -4023 self.crypto_state.spaces[space].crypto_offset += outgoing.len() as u64; -4024 trace!("wrote {} {:?} CRYPTO bytes", outgoing.len(), space); -4025 self.spaces[space].pending.crypto.push_back(frame::Crypto { -4026 offset, -4027 data: outgoing, -4028 }); -4029 } -4030 } -4031 -4032 /// Switch to stronger cryptography during handshake -4033 fn upgrade_crypto(&mut self, space: SpaceKind, crypto: Keys) { -4034 debug_assert!( -4035 !self.crypto_state.has_keys(space.encryption_level()), -4036 "already reached packet space {space:?}" -4037 ); -4038 trace!("{:?} keys ready", space); -4039 if space == SpaceKind::Data { -4040 // Precompute the first key update -4041 self.crypto_state.next_crypto = Some( -4042 self.crypto_state -4043 .session -4044 .next_1rtt_keys() -4045 .expect("handshake should be complete"), -4046 ); -4047 } -4048 -4049 self.crypto_state.spaces[space].keys = Some(crypto); -4050 debug_assert!(space > self.highest_space); -4051 self.highest_space = space; -4052 if space == SpaceKind::Data && self.side.is_client() { -4053 // Discard 0-RTT keys because 1-RTT keys are available. -4054 self.crypto_state.discard_zero_rtt(); -4055 } -4056 } -4057 -4058 fn discard_space(&mut self, now: Instant, space: SpaceKind) { -4059 debug_assert!(space != SpaceKind::Data); -4060 trace!("discarding {:?} keys", space); -4061 if space == SpaceKind::Initial { -4062 // No longer needed -4063 if let ConnectionSide::Client { token, .. } = &mut self.side { -4064 *token = Bytes::new(); -4065 } -4066 } -4067 self.crypto_state.spaces[space].keys = None; -4068 let space = &mut self.spaces[space]; -4069 let pns = space.for_path(PathId::ZERO); -4070 pns.time_of_last_ack_eliciting_packet = None; -4071 pns.loss_time = None; -4072 pns.loss_probes = 0; -4073 let sent_packets = mem::take(&mut pns.sent_packets); -4074 let path = self -4075 .paths -4076 .get_mut(&PathId::ZERO) -4077 .expect("PathId::ZERO is alive while Initial/Handshake spaces exist"); -4078 for (_, packet) in sent_packets.into_iter() { -4079 path.data.remove_in_flight(&packet); -4080 } -4081 -4082 self.set_loss_detection_timer(now, PathId::ZERO) -4083 } -4084 -4085 fn handle_coalesced( -4086 &mut self, -4087 now: Instant, -4088 network_path: FourTuple, -4089 path_id: PathId, -4090 ecn: Option<EcnCodepoint>, -4091 data: BytesMut, -4092 ) { -4093 self.path_data_mut(path_id) -4094 .inc_total_recvd(data.len() as u64); -4095 let mut remaining = Some(data); -4096 let cid_len = self -4097 .local_cid_state -4098 .values() -4099 .map(|cid_state| cid_state.cid_len()) -4100 .next() -4101 .expect("one cid_state must exist"); -4102 while let Some(data) = remaining { -4103 match PartialDecode::new( -4104 data, -4105 &FixedLengthConnectionIdParser::new(cid_len), -4106 &[self.version], -4107 self.endpoint_config.grease_quic_bit, -4108 ) { -4109 Ok((partial_decode, rest)) => { -4110 remaining = rest; -4111 self.handle_decode(now, network_path, path_id, ecn, partial_decode); -4112 } -4113 Err(e) => { -4114 trace!("malformed header: {}", e); -4115 return; -4116 } -4117 } -4118 } -4119 } -4120 -4121 /// Decrypts the packet and processes the payload. -4122 /// -4123 /// Processes the entire packet, starting with removing header protection, then handling -4124 /// a stateless reset if needed, and decrypting and processing the frames in the payload -4125 /// if not a stateless reset. -4126 fn handle_decode( -4127 &mut self, -4128 now: Instant, -4129 network_path: FourTuple, -4130 path_id: PathId, -4131 ecn: Option<EcnCodepoint>, -4132 partial_decode: PartialDecode, -4133 ) { -4134 let qlog = QlogRecvPacket::new(partial_decode.len()); -4135 if let Some(decoded) = self -4136 .crypto_state -4137 .unprotect_header(partial_decode, self.peer_params.stateless_reset_token) -4138 { -4139 self.handle_packet( -4140 now, -4141 network_path, -4142 path_id, -4143 ecn, -4144 decoded.packet, -4145 decoded.stateless_reset, -4146 qlog, -4147 ); -4148 } -4149 } -4150 -4151 /// Handles a packet with header protection removed. -4152 /// -4153 /// The packet body is still encrypted at this point. -4154 /// -4155 /// If the datagram was a stateless reset we may have failed to remove header protection -4156 /// and thus `packet` may be `None`. -4157 fn handle_packet( -4158 &mut self, -4159 now: Instant, -4160 network_path: FourTuple, -4161 path_id: PathId, -4162 ecn: Option<EcnCodepoint>, -4163 packet: Option<Packet>, -4164 stateless_reset: bool, -4165 mut qlog: QlogRecvPacket, -4166 ) { -4167 self.path_stats.for_path(path_id).udp_rx.ios += 1; -4168 -4169 if let Some(ref packet) = packet { -4170 trace!( -4171 "got {:?} packet ({} bytes) from {} using id {}", -4172 packet.header.space(), -4173 packet.payload.len() + packet.header_data.len(), -4174 network_path, -4175 packet.header.dst_cid(), -4176 ); -4177 } -4178 -4179 let was_closed = self.state.is_closed(); -4180 let was_drained = self.state.is_drained(); -4181 -4182 // Now decrypt the packet payload in-place. -4183 let decrypted = match packet { -4184 None => Err(None), -4185 Some(mut packet) => self -4186 .decrypt_packet(now, path_id, &mut packet) -4187 .map(move |number| (packet, number)), -4188 }; -4189 let result = match decrypted { -4190 _ if stateless_reset => { -4191 debug!("got stateless reset"); -4192 Err(ConnectionError::Reset) -4193 } -4194 Err(Some(e)) => { -4195 warn!("illegal packet: {}", e); -4196 Err(e.into()) -4197 } -4198 Err(None) => { -4199 debug!("failed to authenticate packet"); -4200 self.authentication_failures += 1; -4201 let integrity_limit = self -4202 .crypto_state -4203 .integrity_limit(self.highest_space) -4204 .unwrap(); -4205 if self.authentication_failures > integrity_limit { -4206 Err(TransportError::AEAD_LIMIT_REACHED("integrity limit violated").into()) -4207 } else { -4208 return; -4209 } -4210 } -4211 Ok((packet, number)) => { -4212 // We received an authenticated packet and decrypted it. -4213 qlog.header(&packet.header, number, path_id); -4214 let span = match number { -4215 Some(pn) => trace_span!("recv", space = ?packet.header.space(), pn), -4216 None => trace_span!("recv", space = ?packet.header.space()), -4217 }; -4218 let _guard = span.enter(); -4219 -4220 // Now the packet is authenticated we do the migration during the -4221 // handshake. See Handshake::allow_server_migration for details. -4222 if self.is_handshaking() && network_path != self.path_data_mut(path_id).network_path -4223 { -4224 if let Some(hs) = self.state.as_handshake() -4225 && hs.allow_server_migration -4226 { -4227 trace!( -4228 %network_path, -4229 prev = %self.path_data(path_id).network_path, -4230 "server migrated to new remote", -4231 ); -4232 self.path_data_mut(path_id).network_path = network_path; -4233 self.qlog.emit_tuple_assigned(path_id, network_path, now); -4234 } else { -4235 debug!("discarding packet with unexpected remote during handshake"); -4236 return; -4237 } -4238 } -4239 -4240 let dedup = self.spaces[packet.header.space()] -4241 .path_space_mut(path_id) -4242 .map(|pns| &mut pns.dedup); -4243 if number.zip(dedup).is_some_and(|(n, d)| d.insert(n)) { -4244 debug!("discarding possible duplicate packet"); -4245 self.qlog.emit_packet_received(qlog, now); -4246 return; -4247 } else if self.state.is_handshake() && packet.header.is_short() { -4248 // TODO: SHOULD buffer these to improve reordering tolerance. -4249 trace!("dropping short packet during handshake"); -4250 self.qlog.emit_packet_received(qlog, now); -4251 return; -4252 } else { -4253 if let Header::Initial(InitialHeader { ref token, .. }) = packet.header -4254 && let Some(hs) = self.state.as_handshake() -4255 && self.side.is_server() -4256 && token != &hs.expected_token -4257 { -4258 // Clients must send the same retry token in every Initial. Initial -4259 // packets can be spoofed, so we discard rather than killing the -4260 // connection. -4261 warn!("discarding Initial with invalid retry token"); -4262 self.qlog.emit_packet_received(qlog, now); -4263 return; -4264 } -4265 -4266 if !self.state.is_closed() { -4267 let spin = match packet.header { -4268 Header::Short { spin, .. } => spin, -4269 _ => false, -4270 }; -4271 -4272 if self.side().is_server() && !self.abandoned_paths.contains(&path_id) { -4273 // Only the client is allowed to open paths -4274 self.ensure_path(path_id, network_path, now, number); -4275 } -4276 if self.paths.contains_key(&path_id) { -4277 self.on_packet_authenticated( -4278 now, -4279 packet.header.space(), -4280 path_id, -4281 ecn, -4282 number, -4283 spin, -4284 packet.header.is_1rtt(), -4285 &network_path, -4286 ); -4287 } -4288 } -4289 -4290 let res = self.process_decrypted_packet( -4291 now, -4292 network_path, -4293 path_id, -4294 number, -4295 packet, -4296 &mut qlog, -4297 ); -4298 -4299 self.qlog.emit_packet_received(qlog, now); -4300 res -4301 } -4302 } -4303 }; -4304 -4305 // State transitions for error cases -4306 if let Err(conn_err) = result { -4307 match conn_err { -4308 ConnectionError::ApplicationClosed(reason) => self.state.move_to_closed(reason), -4309 ConnectionError::ConnectionClosed(reason) => self.state.move_to_closed(reason), -4310 ConnectionError::Reset -4311 | ConnectionError::TransportError(TransportError { -4312 code: TransportErrorCode::AEAD_LIMIT_REACHED, -4313 .. -4314 }) => { -4315 self.state.move_to_drained(Some(conn_err)); -4316 } -4317 ConnectionError::TimedOut => { -4318 unreachable!("timeouts aren't generated by packet processing"); -4319 } -4320 ConnectionError::TransportError(err) => { -4321 debug!("closing connection due to transport error: {}", err); -4322 self.state.move_to_closed(err); +3795 } +3796 +3797 /// Resets both the [`ConnTimer::KeepAlive`] and [`PathTimer::PathKeepAlive`] timers +3798 fn reset_keep_alive(&mut self, path_id: PathId, now: Instant) { +3799 if !self.state.is_established() { +3800 return; +3801 } +3802 +3803 if let Some(interval) = self.config.keep_alive_interval { +3804 self.timers.set( +3805 Timer::Conn(ConnTimer::KeepAlive), +3806 now + interval, +3807 self.qlog.with_time(now), +3808 ); +3809 } +3810 +3811 if let Some(interval) = self.path_data(path_id).keep_alive { +3812 self.timers.set( +3813 Timer::PerPath(path_id, PathTimer::PathKeepAlive), +3814 now + interval, +3815 self.qlog.with_time(now), +3816 ); +3817 } +3818 } +3819 +3820 /// Sets the timer for when a previously issued CID should be retired next +3821 fn reset_cid_retirement(&mut self, now: Instant) { +3822 if let Some((_path, t)) = self.next_cid_retirement() { +3823 self.timers.set( +3824 Timer::Conn(ConnTimer::PushNewCid), +3825 t, +3826 self.qlog.with_time(now), +3827 ); +3828 } +3829 } +3830 +3831 /// The next time when a previously issued CID should be retired +3832 fn next_cid_retirement(&self) -> Option<(PathId, Instant)> { +3833 self.local_cid_state +3834 .iter() +3835 .filter_map(|(path_id, cid_state)| cid_state.next_timeout().map(|t| (*path_id, t))) +3836 .min_by_key(|(_path_id, timeout)| *timeout) +3837 } +3838 +3839 /// Handle the already-decrypted first packet from the client +3840 /// +3841 /// Decrypting the first packet in the `Endpoint` allows stateless packet handling to be more +3842 /// efficient. +3843 pub(crate) fn handle_first_packet( +3844 &mut self, +3845 now: Instant, +3846 network_path: FourTuple, +3847 ecn: Option<EcnCodepoint>, +3848 packet_number: u64, +3849 packet: InitialPacket, +3850 remaining: Option<BytesMut>, +3851 ) -> Result<(), ConnectionError> { +3852 let span = trace_span!("first recv"); +3853 let _guard = span.enter(); +3854 debug_assert!(self.side.is_server()); +3855 let len = packet.header_data.len() + packet.payload.len(); +3856 let path_id = PathId::ZERO; +3857 self.path_data_mut(path_id).total_recvd = len as u64; +3858 +3859 if let Some(hs) = self.state.as_handshake_mut() { +3860 hs.expected_token = packet.header.token.clone(); +3861 } else { +3862 unreachable!("first packet must be delivered in Handshake state"); +3863 } +3864 +3865 // The first packet is always on PathId::ZERO +3866 self.on_packet_authenticated( +3867 now, +3868 SpaceKind::Initial, +3869 path_id, +3870 ecn, +3871 Some(packet_number), +3872 false, +3873 false, +3874 &network_path, +3875 ); +3876 +3877 let packet: Packet = packet.into(); +3878 +3879 let mut qlog = QlogRecvPacket::new(len); +3880 qlog.header(&packet.header, Some(packet_number), path_id); +3881 +3882 self.process_decrypted_packet( +3883 now, +3884 network_path, +3885 path_id, +3886 Some(packet_number), +3887 packet, +3888 &mut qlog, +3889 )?; +3890 self.qlog.emit_packet_received(qlog, now); +3891 if let Some(data) = remaining { +3892 self.handle_coalesced(now, network_path, path_id, ecn, data); +3893 } +3894 +3895 self.qlog.emit_recovery_metrics( +3896 path_id, +3897 &mut self +3898 .paths +3899 .get_mut(&path_id) +3900 .expect("path_id was supplied by the caller for an active path") +3901 .data, +3902 now, +3903 ); +3904 +3905 Ok(()) +3906 } +3907 +3908 fn init_0rtt(&mut self, now: Instant) { +3909 let Some((header, packet)) = self.crypto_state.session.early_crypto() else { +3910 return; +3911 }; +3912 if self.side.is_client() { +3913 match self.crypto_state.session.transport_parameters() { +3914 Ok(params) => { +3915 let params = params +3916 .expect("crypto layer didn't supply transport parameters with ticket"); +3917 // Certain values must not be cached +3918 let params = TransportParameters { +3919 initial_src_cid: None, +3920 original_dst_cid: None, +3921 preferred_address: None, +3922 retry_src_cid: None, +3923 stateless_reset_token: None, +3924 min_ack_delay: None, +3925 ack_delay_exponent: TransportParameters::default().ack_delay_exponent, +3926 max_ack_delay: TransportParameters::default().max_ack_delay, +3927 initial_max_path_id: None, +3928 ..params +3929 }; +3930 self.set_peer_params(params); +3931 self.qlog.emit_peer_transport_params_restored(self, now); +3932 } +3933 Err(e) => { +3934 error!("session ticket has malformed transport parameters: {}", e); +3935 return; +3936 } +3937 } +3938 } +3939 trace!("0-RTT enabled"); +3940 self.crypto_state.enable_zero_rtt(header, packet); +3941 } +3942 +3943 fn read_crypto( +3944 &mut self, +3945 space: SpaceId, +3946 crypto: &frame::Crypto, +3947 payload_len: usize, +3948 ) -> Result<(), TransportError> { +3949 let expected = if !self.state.is_handshake() { +3950 SpaceId::Data +3951 } else if self.highest_space == SpaceKind::Initial { +3952 SpaceId::Initial +3953 } else { +3954 // On the server, self.highest_space can be Data after receiving the client's first +3955 // flight, but we expect Handshake CRYPTO until the handshake is complete. +3956 SpaceId::Handshake +3957 }; +3958 // We can't decrypt Handshake packets when highest_space is Initial, CRYPTO frames in 0-RTT +3959 // packets are illegal, and we don't process 1-RTT packets until the handshake is +3960 // complete. Therefore, we will never see CRYPTO data from a later-than-expected space. +3961 debug_assert!(space <= expected, "received out-of-order CRYPTO data"); +3962 +3963 let end = crypto.offset + crypto.data.len() as u64; +3964 if space < expected +3965 && end +3966 > self.crypto_state.spaces[space.kind()] +3967 .crypto_stream +3968 .bytes_read() +3969 { +3970 warn!( +3971 "received new {:?} CRYPTO data when expecting {:?}", +3972 space, expected +3973 ); +3974 return Err(TransportError::PROTOCOL_VIOLATION( +3975 "new data at unexpected encryption level", +3976 )); +3977 } +3978 +3979 let crypto_space = &mut self.crypto_state.spaces[space.kind()]; +3980 let max = end.saturating_sub(crypto_space.crypto_stream.bytes_read()); +3981 if max > self.config.crypto_buffer_size as u64 { +3982 return Err(TransportError::CRYPTO_BUFFER_EXCEEDED("")); +3983 } +3984 +3985 crypto_space +3986 .crypto_stream +3987 .insert(crypto.offset, crypto.data.clone(), payload_len); +3988 while let Some(chunk) = crypto_space.crypto_stream.read(usize::MAX, true) { +3989 trace!("consumed {} CRYPTO bytes", chunk.bytes.len()); +3990 if self.crypto_state.session.read_handshake(&chunk.bytes)? { +3991 self.events.push_back(Event::HandshakeDataReady); +3992 } +3993 } +3994 +3995 Ok(()) +3996 } +3997 +3998 fn write_crypto(&mut self) { +3999 loop { +4000 let space = self.highest_space; +4001 let mut outgoing = Vec::new(); +4002 if let Some(crypto) = self.crypto_state.session.write_handshake(&mut outgoing) { +4003 match space { +4004 SpaceKind::Initial => { +4005 self.upgrade_crypto(SpaceKind::Handshake, crypto); +4006 } +4007 SpaceKind::Handshake => { +4008 self.upgrade_crypto(SpaceKind::Data, crypto); +4009 } +4010 SpaceKind::Data => unreachable!("got updated secrets during 1-RTT"), +4011 } +4012 } +4013 if outgoing.is_empty() { +4014 if space == self.highest_space { +4015 break; +4016 } else { +4017 // Keys updated, check for more data to send +4018 continue; +4019 } +4020 } +4021 let offset = self.crypto_state.spaces[space].crypto_offset; +4022 let outgoing = Bytes::from(outgoing); +4023 if let Some(hs) = self.state.as_handshake_mut() +4024 && space == SpaceKind::Initial +4025 && offset == 0 +4026 && self.side.is_client() +4027 { +4028 hs.client_hello = Some(outgoing.clone()); +4029 } +4030 self.crypto_state.spaces[space].crypto_offset += outgoing.len() as u64; +4031 trace!("wrote {} {:?} CRYPTO bytes", outgoing.len(), space); +4032 self.spaces[space].pending.crypto.push_back(frame::Crypto { +4033 offset, +4034 data: outgoing, +4035 }); +4036 } +4037 } +4038 +4039 /// Switch to stronger cryptography during handshake +4040 fn upgrade_crypto(&mut self, space: SpaceKind, crypto: Keys) { +4041 debug_assert!( +4042 !self.crypto_state.has_keys(space.encryption_level()), +4043 "already reached packet space {space:?}" +4044 ); +4045 trace!("{:?} keys ready", space); +4046 if space == SpaceKind::Data { +4047 // Precompute the first key update +4048 self.crypto_state.next_crypto = Some( +4049 self.crypto_state +4050 .session +4051 .next_1rtt_keys() +4052 .expect("handshake should be complete"), +4053 ); +4054 } +4055 +4056 self.crypto_state.spaces[space].keys = Some(crypto); +4057 debug_assert!(space > self.highest_space); +4058 self.highest_space = space; +4059 if space == SpaceKind::Data && self.side.is_client() { +4060 // Discard 0-RTT keys because 1-RTT keys are available. +4061 self.crypto_state.discard_zero_rtt(); +4062 } +4063 } +4064 +4065 fn discard_space(&mut self, now: Instant, space: SpaceKind) { +4066 debug_assert!(space != SpaceKind::Data); +4067 trace!("discarding {:?} keys", space); +4068 if space == SpaceKind::Initial { +4069 // No longer needed +4070 if let ConnectionSide::Client { token, .. } = &mut self.side { +4071 *token = Bytes::new(); +4072 } +4073 } +4074 self.crypto_state.spaces[space].keys = None; +4075 let space = &mut self.spaces[space]; +4076 let pns = space.for_path(PathId::ZERO); +4077 pns.time_of_last_ack_eliciting_packet = None; +4078 pns.loss_time = None; +4079 pns.loss_probes = 0; +4080 let sent_packets = mem::take(&mut pns.sent_packets); +4081 let path = self +4082 .paths +4083 .get_mut(&PathId::ZERO) +4084 .expect("PathId::ZERO is alive while Initial/Handshake spaces exist"); +4085 for (_, packet) in sent_packets.into_iter() { +4086 path.data.remove_in_flight(&packet); +4087 } +4088 +4089 self.set_loss_detection_timer(now, PathId::ZERO) +4090 } +4091 +4092 fn handle_coalesced( +4093 &mut self, +4094 now: Instant, +4095 network_path: FourTuple, +4096 path_id: PathId, +4097 ecn: Option<EcnCodepoint>, +4098 data: BytesMut, +4099 ) { +4100 self.path_data_mut(path_id) +4101 .inc_total_recvd(data.len() as u64); +4102 let mut remaining = Some(data); +4103 let cid_len = self +4104 .local_cid_state +4105 .values() +4106 .map(|cid_state| cid_state.cid_len()) +4107 .next() +4108 .expect("one cid_state must exist"); +4109 while let Some(data) = remaining { +4110 match PartialDecode::new( +4111 data, +4112 &FixedLengthConnectionIdParser::new(cid_len), +4113 &[self.version], +4114 self.endpoint_config.grease_quic_bit, +4115 ) { +4116 Ok((partial_decode, rest)) => { +4117 remaining = rest; +4118 self.handle_decode(now, network_path, path_id, ecn, partial_decode); +4119 } +4120 Err(e) => { +4121 trace!("malformed header: {}", e); +4122 return; +4123 } +4124 } +4125 } +4126 } +4127 +4128 /// Decrypts the packet and processes the payload. +4129 /// +4130 /// Processes the entire packet, starting with removing header protection, then handling +4131 /// a stateless reset if needed, and decrypting and processing the frames in the payload +4132 /// if not a stateless reset. +4133 fn handle_decode( +4134 &mut self, +4135 now: Instant, +4136 network_path: FourTuple, +4137 path_id: PathId, +4138 ecn: Option<EcnCodepoint>, +4139 partial_decode: PartialDecode, +4140 ) { +4141 let qlog = QlogRecvPacket::new(partial_decode.len()); +4142 if let Some(decoded) = self +4143 .crypto_state +4144 .unprotect_header(partial_decode, self.peer_params.stateless_reset_token) +4145 { +4146 self.handle_packet( +4147 now, +4148 network_path, +4149 path_id, +4150 ecn, +4151 decoded.packet, +4152 decoded.stateless_reset, +4153 qlog, +4154 ); +4155 } +4156 } +4157 +4158 /// Handles a packet with header protection removed. +4159 /// +4160 /// The packet body is still encrypted at this point. +4161 /// +4162 /// If the datagram was a stateless reset we may have failed to remove header protection +4163 /// and thus `packet` may be `None`. +4164 fn handle_packet( +4165 &mut self, +4166 now: Instant, +4167 network_path: FourTuple, +4168 path_id: PathId, +4169 ecn: Option<EcnCodepoint>, +4170 packet: Option<Packet>, +4171 stateless_reset: bool, +4172 mut qlog: QlogRecvPacket, +4173 ) { +4174 self.path_stats.for_path(path_id).udp_rx.ios += 1; +4175 +4176 if let Some(ref packet) = packet { +4177 trace!( +4178 "got {:?} packet ({} bytes) from {} using id {}", +4179 packet.header.space(), +4180 packet.payload.len() + packet.header_data.len(), +4181 network_path, +4182 packet.header.dst_cid(), +4183 ); +4184 } +4185 +4186 let was_closed = self.state.is_closed(); +4187 let was_drained = self.state.is_drained(); +4188 +4189 // Now decrypt the packet payload in-place. +4190 let decrypted = match packet { +4191 None => Err(None), +4192 Some(mut packet) => self +4193 .decrypt_packet(now, path_id, &mut packet) +4194 .map(move |number| (packet, number)), +4195 }; +4196 let result = match decrypted { +4197 _ if stateless_reset => { +4198 debug!("got stateless reset"); +4199 Err(ConnectionError::Reset) +4200 } +4201 Err(Some(e)) => { +4202 warn!("illegal packet: {}", e); +4203 Err(e.into()) +4204 } +4205 Err(None) => { +4206 debug!("failed to authenticate packet"); +4207 self.authentication_failures += 1; +4208 let integrity_limit = self +4209 .crypto_state +4210 .integrity_limit(self.highest_space) +4211 .unwrap(); +4212 if self.authentication_failures > integrity_limit { +4213 Err(TransportError::AEAD_LIMIT_REACHED("integrity limit violated").into()) +4214 } else { +4215 return; +4216 } +4217 } +4218 Ok((packet, number)) => { +4219 // We received an authenticated packet and decrypted it. +4220 qlog.header(&packet.header, number, path_id); +4221 let span = match number { +4222 Some(pn) => trace_span!("recv", space = ?packet.header.space(), pn), +4223 None => trace_span!("recv", space = ?packet.header.space()), +4224 }; +4225 let _guard = span.enter(); +4226 +4227 // Now the packet is authenticated we do the migration during the +4228 // handshake. See Handshake::allow_server_migration for details. +4229 if self.is_handshaking() && network_path != self.path_data_mut(path_id).network_path +4230 { +4231 if let Some(hs) = self.state.as_handshake() +4232 && hs.allow_server_migration +4233 { +4234 trace!( +4235 %network_path, +4236 prev = %self.path_data(path_id).network_path, +4237 "server migrated to new remote", +4238 ); +4239 self.path_data_mut(path_id).network_path = network_path; +4240 self.qlog.emit_tuple_assigned(path_id, network_path, now); +4241 } else { +4242 debug!("discarding packet with unexpected remote during handshake"); +4243 return; +4244 } +4245 } +4246 +4247 let dedup = self.spaces[packet.header.space()] +4248 .path_space_mut(path_id) +4249 .map(|pns| &mut pns.dedup); +4250 if number.zip(dedup).is_some_and(|(n, d)| d.insert(n)) { +4251 debug!("discarding possible duplicate packet"); +4252 self.qlog.emit_packet_received(qlog, now); +4253 return; +4254 } else if self.state.is_handshake() && packet.header.is_short() { +4255 // TODO: SHOULD buffer these to improve reordering tolerance. +4256 trace!("dropping short packet during handshake"); +4257 self.qlog.emit_packet_received(qlog, now); +4258 return; +4259 } else { +4260 if let Header::Initial(InitialHeader { ref token, .. }) = packet.header +4261 && let Some(hs) = self.state.as_handshake() +4262 && self.side.is_server() +4263 && token != &hs.expected_token +4264 { +4265 // Clients must send the same retry token in every Initial. Initial +4266 // packets can be spoofed, so we discard rather than killing the +4267 // connection. +4268 warn!("discarding Initial with invalid retry token"); +4269 self.qlog.emit_packet_received(qlog, now); +4270 return; +4271 } +4272 +4273 if !self.state.is_closed() { +4274 let spin = match packet.header { +4275 Header::Short { spin, .. } => spin, +4276 _ => false, +4277 }; +4278 +4279 if self.side().is_server() && !self.abandoned_paths.contains(&path_id) { +4280 // Only the client is allowed to open paths +4281 self.ensure_path(path_id, network_path, now, number); +4282 } +4283 if self.paths.contains_key(&path_id) { +4284 self.on_packet_authenticated( +4285 now, +4286 packet.header.space(), +4287 path_id, +4288 ecn, +4289 number, +4290 spin, +4291 packet.header.is_1rtt(), +4292 &network_path, +4293 ); +4294 } +4295 } +4296 +4297 let res = self.process_decrypted_packet( +4298 now, +4299 network_path, +4300 path_id, +4301 number, +4302 packet, +4303 &mut qlog, +4304 ); +4305 +4306 self.qlog.emit_packet_received(qlog, now); +4307 res +4308 } +4309 } +4310 }; +4311 +4312 // State transitions for error cases +4313 if let Err(conn_err) = result { +4314 match conn_err { +4315 ConnectionError::ApplicationClosed(reason) => self.state.move_to_closed(reason), +4316 ConnectionError::ConnectionClosed(reason) => self.state.move_to_closed(reason), +4317 ConnectionError::Reset +4318 | ConnectionError::TransportError(TransportError { +4319 code: TransportErrorCode::AEAD_LIMIT_REACHED, +4320 .. +4321 }) => { +4322 self.state.move_to_drained(Some(conn_err)); 4323 } -4324 ConnectionError::VersionMismatch => { -4325 self.state.move_to_draining(Some(conn_err)); +4324 ConnectionError::TimedOut => { +4325 unreachable!("timeouts aren't generated by packet processing"); 4326 } -4327 ConnectionError::LocallyClosed => { -4328 unreachable!("LocallyClosed isn't generated by packet processing"); -4329 } -4330 ConnectionError::CidsExhausted => { -4331 unreachable!("CidsExhausted isn't generated by packet processing"); -4332 } -4333 }; -4334 } -4335 -4336 if !was_closed && self.state.is_closed() { -4337 self.close_common(); -4338 if !self.state.is_drained() { -4339 self.set_close_timer(now); -4340 } +4327 ConnectionError::TransportError(err) => { +4328 debug!("closing connection due to transport error: {}", err); +4329 self.state.move_to_closed(err); +4330 } +4331 ConnectionError::VersionMismatch => { +4332 self.state.move_to_draining(Some(conn_err)); +4333 } +4334 ConnectionError::LocallyClosed => { +4335 unreachable!("LocallyClosed isn't generated by packet processing"); +4336 } +4337 ConnectionError::CidsExhausted => { +4338 unreachable!("CidsExhausted isn't generated by packet processing"); +4339 } +4340 }; 4341 } -4342 if !was_drained && self.state.is_drained() { -4343 self.endpoint_events.push_back(EndpointEventInner::Drained); -4344 // Close timer may have been started previously, e.g. if we sent a close and got a -4345 // stateless reset in response -4346 self.timers -4347 .stop(Timer::Conn(ConnTimer::Close), self.qlog.with_time(now)); +4342 +4343 if !was_closed && self.state.is_closed() { +4344 self.close_common(); +4345 if !self.state.is_drained() { +4346 self.set_close_timer(now); +4347 } 4348 } -4349 -4350 // Transmit CONNECTION_CLOSE if necessary. -4351 // -4352 // If we received a valid packet and we are in the closed state we should respond -4353 // with a CONNECTION_CLOSE frame. -4354 // TODO: This SHOULD be rate-limited according to §10.2.1 of QUIC-TRANSPORT, but -4355 // that does not yet happen. This is triggered by each received packet. -4356 if matches!(self.state.as_type(), StateType::Closed) { -4357 // From https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.1-7 -4358 // -4359 // While in the closing state we must either: -4360 // - discard packets coming from an un-validated remote OR -4361 // - ensure we do not send more than 3 times the received data -4362 // -4363 // Doing the 2nd would mean we would be able to send CONNECTION_CLOSE to a peer -4364 // who was (involuntary) migrated just at the time we initiated immediate -4365 // close. It is a lot more work though. So while we would like to do this for -4366 // now we only do 1. -4367 // -4368 // Another shortcoming of the current implementation is that when we have a -4369 // previous PathData which is validated and the remote matches that path, we -4370 // should schedule CONNECTION_CLOSE on that path. However currently we can not -4371 // schedule such a packet. We should also fix this some day. This makes us -4372 // vulnerable to an attacker faking a migration at the right time and then we'd -4373 // be unable to send the CONNECTION_CLOSE to the real remote. -4374 if self -4375 .paths -4376 .get(&path_id) -4377 .map(|p| p.data.validated && p.data.network_path == network_path) -4378 .unwrap_or(false) -4379 { -4380 self.connection_close_pending = true; -4381 } -4382 } -4383 } -4384 -4385 fn process_decrypted_packet( -4386 &mut self, -4387 now: Instant, -4388 network_path: FourTuple, -4389 path_id: PathId, -4390 number: Option<u64>, -4391 packet: Packet, -4392 qlog: &mut QlogRecvPacket, -4393 ) -> Result<(), ConnectionError> { -4394 if !self.paths.contains_key(&path_id) { -4395 // There is a chance this is a server side, first (for this path) packet, which would -4396 // be a protocol violation. It's more likely, however, that this is a packet of a -4397 // pruned path -4398 trace!(%path_id, ?number, "discarding packet for unknown path"); -4399 return Ok(()); -4400 } -4401 let state = match self.state.as_type() { -4402 StateType::Established => { -4403 match packet.header.space() { -4404 SpaceKind::Data => self.process_payload( -4405 now, -4406 network_path, -4407 path_id, -4408 number.unwrap(), -4409 packet, -4410 qlog, -4411 )?, -4412 _ if packet.header.has_frames() => { -4413 self.process_early_payload(now, path_id, packet, qlog)? -4414 } -4415 _ => { -4416 trace!("discarding unexpected pre-handshake packet"); -4417 } -4418 } -4419 return Ok(()); -4420 } -4421 StateType::Closed => { -4422 for result in frame::Iter::new(packet.payload.freeze())? { -4423 let frame = match result { -4424 Ok(frame) => frame, -4425 Err(err) => { -4426 debug!("frame decoding error: {err:?}"); -4427 continue; -4428 } -4429 }; -4430 qlog.frame(&frame); -4431 -4432 if let Frame::Padding = frame { -4433 continue; -4434 }; -4435 -4436 self.path_stats -4437 .for_path(path_id) -4438 .frame_rx -4439 .record(frame.ty()); -4440 -4441 if let Frame::Close(_error) = frame { -4442 self.state.move_to_draining(None); -4443 break; -4444 } -4445 } -4446 return Ok(()); -4447 } -4448 StateType::Draining | StateType::Drained => return Ok(()), -4449 StateType::Handshake => self.state.as_handshake_mut().expect("checked"), -4450 }; -4451 -4452 match packet.header { -4453 Header::Retry { -4454 src_cid: remote_cid, -4455 .. -4456 } => { -4457 debug_assert_eq!(path_id, PathId::ZERO); -4458 if self.side.is_server() { -4459 return Err(TransportError::PROTOCOL_VIOLATION("client sent Retry").into()); -4460 } -4461 -4462 let is_valid_retry = self -4463 .remote_cids -4464 .get(&path_id) -4465 .map(|cids| cids.active()) -4466 .map(|orig_dst_cid| { -4467 self.crypto_state.session.is_valid_retry( -4468 orig_dst_cid, -4469 &packet.header_data, -4470 &packet.payload, -4471 ) -4472 }) -4473 .unwrap_or_default(); -4474 if self.total_authed_packets > 1 -4475 || packet.payload.len() <= 16 // token + 16 byte tag -4476 || !is_valid_retry -4477 { -4478 trace!("discarding invalid Retry"); -4479 // - After the client has received and processed an Initial or Retry -4480 // packet from the server, it MUST discard any subsequent Retry -4481 // packets that it receives. -4482 // - A client MUST discard a Retry packet with a zero-length Retry Token -4483 // field. -4484 // - Clients MUST discard Retry packets that have a Retry Integrity Tag -4485 // that cannot be validated -4486 return Ok(()); -4487 } -4488 -4489 trace!("retrying with CID {}", remote_cid); -4490 let client_hello = state.client_hello.take().unwrap(); -4491 self.retry_src_cid = Some(remote_cid); -4492 self.remote_cids -4493 .get_mut(&path_id) -4494 .expect("PathId::ZERO not yet abandoned, is_valid_retry would have been false") -4495 .update_initial_cid(remote_cid); -4496 self.remote_handshake_cid = remote_cid; -4497 -4498 let space = &mut self.spaces[SpaceId::Initial]; -4499 if let Some(info) = space.for_path(PathId::ZERO).take(0) { -4500 self.on_packet_acked(now, PathId::ZERO, 0, info); -4501 }; -4502 -4503 self.discard_space(now, SpaceKind::Initial); // Make sure we clean up after -4504 // any retransmitted Initials -4505 let crypto_space = &mut self.crypto_state.spaces[SpaceKind::Initial]; -4506 crypto_space.keys = Some( -4507 self.crypto_state -4508 .session -4509 .initial_keys(remote_cid, self.side.side()), -4510 ); -4511 crypto_space.crypto_offset = client_hello.len() as u64; -4512 -4513 let next_pn = self.spaces[SpaceId::Initial] -4514 .for_path(path_id) -4515 .next_packet_number; -4516 self.spaces[SpaceId::Initial] = { -4517 let mut space = PacketSpace::new(now, SpaceId::Initial, &mut self.rng); -4518 space.for_path(path_id).next_packet_number = next_pn; -4519 space.pending.crypto.push_back(frame::Crypto { -4520 offset: 0, -4521 data: client_hello, -4522 }); -4523 space -4524 }; -4525 -4526 // Retransmit all 0-RTT data -4527 let zero_rtt = mem::take( -4528 &mut self.spaces[SpaceId::Data] -4529 .for_path(PathId::ZERO) -4530 .sent_packets, -4531 ); -4532 for (_, info) in zero_rtt.into_iter() { -4533 self.paths -4534 .get_mut(&PathId::ZERO) -4535 .unwrap() -4536 .remove_in_flight(&info); -4537 self.spaces[SpaceId::Data].pending |= info.retransmits; -4538 } -4539 self.streams.retransmit_all_for_0rtt(); -4540 -4541 let token_len = packet.payload.len() - 16; -4542 let ConnectionSide::Client { ref mut token, .. } = self.side else { -4543 unreachable!("we already short-circuited if we're server"); -4544 }; -4545 *token = packet.payload.freeze().split_to(token_len); -4546 -4547 self.state = State::handshake(state::Handshake { -4548 expected_token: Bytes::new(), -4549 remote_cid_set: false, -4550 client_hello: None, -4551 allow_server_migration: true, -4552 }); -4553 Ok(()) -4554 } -4555 Header::Long { -4556 ty: LongType::Handshake, -4557 src_cid: remote_cid, -4558 dst_cid: local_cid, -4559 .. -4560 } => { -4561 debug_assert_eq!(path_id, PathId::ZERO); -4562 if remote_cid != self.remote_handshake_cid { -4563 debug!( -4564 "discarding packet with mismatched remote CID: {} != {}", -4565 self.remote_handshake_cid, remote_cid -4566 ); -4567 return Ok(()); -4568 } -4569 self.on_path_validated(path_id); -4570 -4571 self.process_early_payload(now, path_id, packet, qlog)?; -4572 if self.state.is_closed() { -4573 return Ok(()); -4574 } -4575 -4576 if self.crypto_state.session.is_handshaking() { -4577 trace!("handshake ongoing"); -4578 return Ok(()); -4579 } -4580 -4581 if self.side.is_client() { -4582 // Client-only because server params were set from the client's Initial -4583 let params = self -4584 .crypto_state -4585 .session -4586 .transport_parameters()? -4587 .ok_or_else(|| { -4588 TransportError::new( -4589 TransportErrorCode::crypto(0x6d), -4590 "transport parameters missing".to_owned(), -4591 ) -4592 })?; -4593 -4594 if self.has_0rtt() { -4595 if !self.crypto_state.session.early_data_accepted().unwrap() { -4596 debug_assert!(self.side.is_client()); -4597 debug!("0-RTT rejected"); -4598 self.crypto_state.accepted_0rtt = false; -4599 self.streams.zero_rtt_rejected(); +4349 if !was_drained && self.state.is_drained() { +4350 self.endpoint_events.push_back(EndpointEventInner::Drained); +4351 // Close timer may have been started previously, e.g. if we sent a close and got a +4352 // stateless reset in response +4353 self.timers +4354 .stop(Timer::Conn(ConnTimer::Close), self.qlog.with_time(now)); +4355 } +4356 +4357 // Transmit CONNECTION_CLOSE if necessary. +4358 // +4359 // If we received a valid packet and we are in the closed state we should respond +4360 // with a CONNECTION_CLOSE frame. +4361 // TODO: This SHOULD be rate-limited according to §10.2.1 of QUIC-TRANSPORT, but +4362 // that does not yet happen. This is triggered by each received packet. +4363 if matches!(self.state.as_type(), StateType::Closed) { +4364 // From https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.1-7 +4365 // +4366 // While in the closing state we must either: +4367 // - discard packets coming from an un-validated remote OR +4368 // - ensure we do not send more than 3 times the received data +4369 // +4370 // Doing the 2nd would mean we would be able to send CONNECTION_CLOSE to a peer +4371 // who was (involuntary) migrated just at the time we initiated immediate +4372 // close. It is a lot more work though. So while we would like to do this for +4373 // now we only do 1. +4374 // +4375 // Another shortcoming of the current implementation is that when we have a +4376 // previous PathData which is validated and the remote matches that path, we +4377 // should schedule CONNECTION_CLOSE on that path. However currently we can not +4378 // schedule such a packet. We should also fix this some day. This makes us +4379 // vulnerable to an attacker faking a migration at the right time and then we'd +4380 // be unable to send the CONNECTION_CLOSE to the real remote. +4381 if self +4382 .paths +4383 .get(&path_id) +4384 .map(|p| p.data.validated && p.data.network_path == network_path) +4385 .unwrap_or(false) +4386 { +4387 self.connection_close_pending = true; +4388 } +4389 } +4390 } +4391 +4392 fn process_decrypted_packet( +4393 &mut self, +4394 now: Instant, +4395 network_path: FourTuple, +4396 path_id: PathId, +4397 number: Option<u64>, +4398 packet: Packet, +4399 qlog: &mut QlogRecvPacket, +4400 ) -> Result<(), ConnectionError> { +4401 if !self.paths.contains_key(&path_id) { +4402 // There is a chance this is a server side, first (for this path) packet, which would +4403 // be a protocol violation. It's more likely, however, that this is a packet of a +4404 // pruned path +4405 trace!(%path_id, ?number, "discarding packet for unknown path"); +4406 return Ok(()); +4407 } +4408 let state = match self.state.as_type() { +4409 StateType::Established => { +4410 match packet.header.space() { +4411 SpaceKind::Data => self.process_payload( +4412 now, +4413 network_path, +4414 path_id, +4415 number.unwrap(), +4416 packet, +4417 qlog, +4418 )?, +4419 _ if packet.header.has_frames() => { +4420 self.process_early_payload(now, path_id, packet, qlog)? +4421 } +4422 _ => { +4423 trace!("discarding unexpected pre-handshake packet"); +4424 } +4425 } +4426 return Ok(()); +4427 } +4428 StateType::Closed => { +4429 for result in frame::Iter::new(packet.payload.freeze())? { +4430 let frame = match result { +4431 Ok(frame) => frame, +4432 Err(err) => { +4433 debug!("frame decoding error: {err:?}"); +4434 continue; +4435 } +4436 }; +4437 qlog.frame(&frame); +4438 +4439 if let Frame::Padding = frame { +4440 continue; +4441 }; +4442 +4443 self.path_stats +4444 .for_path(path_id) +4445 .frame_rx +4446 .record(frame.ty()); +4447 +4448 if let Frame::Close(_error) = frame { +4449 self.state.move_to_draining(None); +4450 break; +4451 } +4452 } +4453 return Ok(()); +4454 } +4455 StateType::Draining | StateType::Drained => return Ok(()), +4456 StateType::Handshake => self.state.as_handshake_mut().expect("checked"), +4457 }; +4458 +4459 match packet.header { +4460 Header::Retry { +4461 src_cid: remote_cid, +4462 .. +4463 } => { +4464 debug_assert_eq!(path_id, PathId::ZERO); +4465 if self.side.is_server() { +4466 return Err(TransportError::PROTOCOL_VIOLATION("client sent Retry").into()); +4467 } +4468 +4469 let is_valid_retry = self +4470 .remote_cids +4471 .get(&path_id) +4472 .map(|cids| cids.active()) +4473 .map(|orig_dst_cid| { +4474 self.crypto_state.session.is_valid_retry( +4475 orig_dst_cid, +4476 &packet.header_data, +4477 &packet.payload, +4478 ) +4479 }) +4480 .unwrap_or_default(); +4481 if self.total_authed_packets > 1 +4482 || packet.payload.len() <= 16 // token + 16 byte tag +4483 || !is_valid_retry +4484 { +4485 trace!("discarding invalid Retry"); +4486 // - After the client has received and processed an Initial or Retry +4487 // packet from the server, it MUST discard any subsequent Retry +4488 // packets that it receives. +4489 // - A client MUST discard a Retry packet with a zero-length Retry Token +4490 // field. +4491 // - Clients MUST discard Retry packets that have a Retry Integrity Tag +4492 // that cannot be validated +4493 return Ok(()); +4494 } +4495 +4496 trace!("retrying with CID {}", remote_cid); +4497 let client_hello = state.client_hello.take().unwrap(); +4498 self.retry_src_cid = Some(remote_cid); +4499 self.remote_cids +4500 .get_mut(&path_id) +4501 .expect("PathId::ZERO not yet abandoned, is_valid_retry would have been false") +4502 .update_initial_cid(remote_cid); +4503 self.remote_handshake_cid = remote_cid; +4504 +4505 let space = &mut self.spaces[SpaceId::Initial]; +4506 if let Some(info) = space.for_path(PathId::ZERO).take(0) { +4507 self.on_packet_acked(now, PathId::ZERO, 0, info); +4508 }; +4509 +4510 self.discard_space(now, SpaceKind::Initial); // Make sure we clean up after +4511 // any retransmitted Initials +4512 let crypto_space = &mut self.crypto_state.spaces[SpaceKind::Initial]; +4513 crypto_space.keys = Some( +4514 self.crypto_state +4515 .session +4516 .initial_keys(remote_cid, self.side.side()), +4517 ); +4518 crypto_space.crypto_offset = client_hello.len() as u64; +4519 +4520 let next_pn = self.spaces[SpaceId::Initial] +4521 .for_path(path_id) +4522 .next_packet_number; +4523 self.spaces[SpaceId::Initial] = { +4524 let mut space = PacketSpace::new(now, SpaceId::Initial, &mut self.rng); +4525 space.for_path(path_id).next_packet_number = next_pn; +4526 space.pending.crypto.push_back(frame::Crypto { +4527 offset: 0, +4528 data: client_hello, +4529 }); +4530 space +4531 }; +4532 +4533 // Retransmit all 0-RTT data +4534 let zero_rtt = mem::take( +4535 &mut self.spaces[SpaceId::Data] +4536 .for_path(PathId::ZERO) +4537 .sent_packets, +4538 ); +4539 for (_, info) in zero_rtt.into_iter() { +4540 self.paths +4541 .get_mut(&PathId::ZERO) +4542 .unwrap() +4543 .remove_in_flight(&info); +4544 self.spaces[SpaceId::Data].pending |= info.retransmits; +4545 } +4546 self.streams.retransmit_all_for_0rtt(); +4547 +4548 let token_len = packet.payload.len() - 16; +4549 let ConnectionSide::Client { ref mut token, .. } = self.side else { +4550 unreachable!("we already short-circuited if we're server"); +4551 }; +4552 *token = packet.payload.freeze().split_to(token_len); +4553 +4554 self.state = State::handshake(state::Handshake { +4555 expected_token: Bytes::new(), +4556 remote_cid_set: false, +4557 client_hello: None, +4558 allow_server_migration: true, +4559 }); +4560 Ok(()) +4561 } +4562 Header::Long { +4563 ty: LongType::Handshake, +4564 src_cid: remote_cid, +4565 dst_cid: local_cid, +4566 .. +4567 } => { +4568 debug_assert_eq!(path_id, PathId::ZERO); +4569 if remote_cid != self.remote_handshake_cid { +4570 debug!( +4571 "discarding packet with mismatched remote CID: {} != {}", +4572 self.remote_handshake_cid, remote_cid +4573 ); +4574 return Ok(()); +4575 } +4576 self.on_path_validated(path_id); +4577 +4578 self.process_early_payload(now, path_id, packet, qlog)?; +4579 if self.state.is_closed() { +4580 return Ok(()); +4581 } +4582 +4583 if self.crypto_state.session.is_handshaking() { +4584 trace!("handshake ongoing"); +4585 return Ok(()); +4586 } +4587 +4588 if self.side.is_client() { +4589 // Client-only because server params were set from the client's Initial +4590 let params = self +4591 .crypto_state +4592 .session +4593 .transport_parameters()? +4594 .ok_or_else(|| { +4595 TransportError::new( +4596 TransportErrorCode::crypto(0x6d), +4597 "transport parameters missing".to_owned(), +4598 ) +4599 })?; 4600 -4601 // Discard already-queued frames -4602 self.spaces[SpaceId::Data].pending = Retransmits::default(); -4603 -4604 // Discard 0-RTT packets -4605 let sent_packets = mem::take( -4606 &mut self.spaces[SpaceId::Data].for_path(path_id).sent_packets, -4607 ); -4608 for (_, packet) in sent_packets.into_iter() { -4609 self.paths -4610 .get_mut(&path_id) -4611 .unwrap() -4612 .remove_in_flight(&packet); -4613 } -4614 } else { -4615 self.crypto_state.accepted_0rtt = true; -4616 params.validate_resumption_from(&self.peer_params)?; -4617 } -4618 } -4619 if let Some(token) = params.stateless_reset_token { -4620 let remote = self.path_data(path_id).network_path.remote; -4621 debug_assert!(!self.state.is_drained()); // requirement for endpoint events, checked above -4622 self.endpoint_events -4623 .push_back(EndpointEventInner::ResetToken(path_id, remote, token)); -4624 } -4625 self.handle_peer_params(params, local_cid, remote_cid, now)?; -4626 self.issue_first_cids(now); -4627 } else { -4628 // Server-only -4629 self.spaces[SpaceId::Data].pending.handshake_done = true; -4630 self.discard_space(now, SpaceKind::Handshake); -4631 self.events.push_back(Event::HandshakeConfirmed); -4632 trace!("handshake confirmed"); -4633 } -4634 -4635 self.events.push_back(Event::Connected); -4636 self.state.move_to_established(); -4637 trace!("established"); -4638 -4639 // Multipath can only be enabled after the state has reached Established. -4640 // So this can not happen any earlier. -4641 self.issue_first_path_cids(now); -4642 Ok(()) -4643 } -4644 Header::Initial(InitialHeader { -4645 src_cid: remote_cid, -4646 dst_cid: local_cid, -4647 .. -4648 }) => { -4649 debug_assert_eq!(path_id, PathId::ZERO); -4650 if !state.remote_cid_set { -4651 trace!("switching remote CID to {}", remote_cid); -4652 let mut state = state.clone(); -4653 self.remote_cids -4654 .get_mut(&path_id) -4655 .expect("PathId::ZERO not yet abandoned") -4656 .update_initial_cid(remote_cid); -4657 self.remote_handshake_cid = remote_cid; -4658 self.original_remote_cid = remote_cid; -4659 state.remote_cid_set = true; -4660 self.state.move_to_handshake(state); -4661 } else if remote_cid != self.remote_handshake_cid { -4662 debug!( -4663 "discarding packet with mismatched remote CID: {} != {}", -4664 self.remote_handshake_cid, remote_cid -4665 ); -4666 return Ok(()); -4667 } -4668 -4669 let starting_space = self.highest_space; -4670 self.process_early_payload(now, path_id, packet, qlog)?; -4671 -4672 if self.side.is_server() -4673 && starting_space == SpaceKind::Initial -4674 && self.highest_space != SpaceKind::Initial -4675 { -4676 let params = self -4677 .crypto_state -4678 .session -4679 .transport_parameters()? -4680 .ok_or_else(|| { -4681 TransportError::new( -4682 TransportErrorCode::crypto(0x6d), -4683 "transport parameters missing".to_owned(), -4684 ) -4685 })?; -4686 self.handle_peer_params(params, local_cid, remote_cid, now)?; -4687 self.issue_first_cids(now); -4688 self.init_0rtt(now); -4689 } -4690 Ok(()) -4691 } -4692 Header::Long { -4693 ty: LongType::ZeroRtt, -4694 .. -4695 } => { -4696 self.process_payload(now, network_path, path_id, number.unwrap(), packet, qlog)?; +4601 if self.has_0rtt() { +4602 if !self.crypto_state.session.early_data_accepted().unwrap() { +4603 debug_assert!(self.side.is_client()); +4604 debug!("0-RTT rejected"); +4605 self.crypto_state.accepted_0rtt = false; +4606 self.streams.zero_rtt_rejected(); +4607 +4608 // Discard already-queued frames +4609 self.spaces[SpaceId::Data].pending = Retransmits::default(); +4610 +4611 // Discard 0-RTT packets +4612 let sent_packets = mem::take( +4613 &mut self.spaces[SpaceId::Data].for_path(path_id).sent_packets, +4614 ); +4615 for (_, packet) in sent_packets.into_iter() { +4616 self.paths +4617 .get_mut(&path_id) +4618 .unwrap() +4619 .remove_in_flight(&packet); +4620 } +4621 } else { +4622 self.crypto_state.accepted_0rtt = true; +4623 params.validate_resumption_from(&self.peer_params)?; +4624 } +4625 } +4626 if let Some(token) = params.stateless_reset_token { +4627 let remote = self.path_data(path_id).network_path.remote; +4628 debug_assert!(!self.state.is_drained()); // requirement for endpoint events, checked above +4629 self.endpoint_events +4630 .push_back(EndpointEventInner::ResetToken(path_id, remote, token)); +4631 } +4632 self.handle_peer_params(params, local_cid, remote_cid, now)?; +4633 self.issue_first_cids(now); +4634 } else { +4635 // Server-only +4636 self.spaces[SpaceId::Data].pending.handshake_done = true; +4637 self.discard_space(now, SpaceKind::Handshake); +4638 self.events.push_back(Event::HandshakeConfirmed); +4639 trace!("handshake confirmed"); +4640 } +4641 +4642 self.events.push_back(Event::Connected); +4643 self.state.move_to_established(); +4644 trace!("established"); +4645 +4646 // Multipath can only be enabled after the state has reached Established. +4647 // So this can not happen any earlier. +4648 self.issue_first_path_cids(now); +4649 Ok(()) +4650 } +4651 Header::Initial(InitialHeader { +4652 src_cid: remote_cid, +4653 dst_cid: local_cid, +4654 .. +4655 }) => { +4656 debug_assert_eq!(path_id, PathId::ZERO); +4657 if !state.remote_cid_set { +4658 trace!("switching remote CID to {}", remote_cid); +4659 let mut state = state.clone(); +4660 self.remote_cids +4661 .get_mut(&path_id) +4662 .expect("PathId::ZERO not yet abandoned") +4663 .update_initial_cid(remote_cid); +4664 self.remote_handshake_cid = remote_cid; +4665 self.original_remote_cid = remote_cid; +4666 state.remote_cid_set = true; +4667 self.state.move_to_handshake(state); +4668 } else if remote_cid != self.remote_handshake_cid { +4669 debug!( +4670 "discarding packet with mismatched remote CID: {} != {}", +4671 self.remote_handshake_cid, remote_cid +4672 ); +4673 return Ok(()); +4674 } +4675 +4676 let starting_space = self.highest_space; +4677 self.process_early_payload(now, path_id, packet, qlog)?; +4678 +4679 if self.side.is_server() +4680 && starting_space == SpaceKind::Initial +4681 && self.highest_space != SpaceKind::Initial +4682 { +4683 let params = self +4684 .crypto_state +4685 .session +4686 .transport_parameters()? +4687 .ok_or_else(|| { +4688 TransportError::new( +4689 TransportErrorCode::crypto(0x6d), +4690 "transport parameters missing".to_owned(), +4691 ) +4692 })?; +4693 self.handle_peer_params(params, local_cid, remote_cid, now)?; +4694 self.issue_first_cids(now); +4695 self.init_0rtt(now); +4696 } 4697 Ok(()) 4698 } -4699 Header::VersionNegotiate { .. } => { -4700 if self.total_authed_packets > 1 { -4701 return Ok(()); -4702 } -4703 let supported = packet -4704 .payload -4705 .chunks(4) -4706 .any(|x| match <[u8; 4]>::try_from(x) { -4707 Ok(version) => self.version == u32::from_be_bytes(version), -4708 Err(_) => false, -4709 }); -4710 if supported { -4711 return Ok(()); -4712 } -4713 debug!("remote doesn't support our version"); -4714 Err(ConnectionError::VersionMismatch) -4715 } -4716 Header::Short { .. } => unreachable!( -4717 "short packets received during handshake are discarded in handle_packet" -4718 ), -4719 } -4720 } -4721 -4722 /// Process an Initial or Handshake packet payload -4723 fn process_early_payload( -4724 &mut self, -4725 now: Instant, -4726 path_id: PathId, -4727 packet: Packet, -4728 #[allow(unused)] qlog: &mut QlogRecvPacket, -4729 ) -> Result<(), TransportError> { -4730 debug_assert_ne!(packet.header.space(), SpaceKind::Data); -4731 debug_assert_eq!(path_id, PathId::ZERO); -4732 let payload_len = packet.payload.len(); -4733 let mut ack_eliciting = false; -4734 for result in frame::Iter::new(packet.payload.freeze())? { -4735 let frame = result?; -4736 qlog.frame(&frame); -4737 let span = match frame { -4738 Frame::Padding => continue, -4739 _ => Some(trace_span!("frame", ty = %frame.ty(), path = tracing::field::Empty)), -4740 }; -4741 -4742 self.path_stats -4743 .for_path(path_id) -4744 .frame_rx -4745 .record(frame.ty()); -4746 -4747 let _guard = span.as_ref().map(|x| x.enter()); -4748 ack_eliciting |= frame.is_ack_eliciting(); -4749 -4750 // Process frames -4751 if frame.is_1rtt() && packet.header.space() != SpaceKind::Data { -4752 return Err(TransportError::PROTOCOL_VIOLATION( -4753 "illegal frame type in handshake", -4754 )); -4755 } +4699 Header::Long { +4700 ty: LongType::ZeroRtt, +4701 .. +4702 } => { +4703 self.process_payload(now, network_path, path_id, number.unwrap(), packet, qlog)?; +4704 Ok(()) +4705 } +4706 Header::VersionNegotiate { .. } => { +4707 if self.total_authed_packets > 1 { +4708 return Ok(()); +4709 } +4710 let supported = packet +4711 .payload +4712 .chunks(4) +4713 .any(|x| match <[u8; 4]>::try_from(x) { +4714 Ok(version) => self.version == u32::from_be_bytes(version), +4715 Err(_) => false, +4716 }); +4717 if supported { +4718 return Ok(()); +4719 } +4720 debug!("remote doesn't support our version"); +4721 Err(ConnectionError::VersionMismatch) +4722 } +4723 Header::Short { .. } => unreachable!( +4724 "short packets received during handshake are discarded in handle_packet" +4725 ), +4726 } +4727 } +4728 +4729 /// Process an Initial or Handshake packet payload +4730 fn process_early_payload( +4731 &mut self, +4732 now: Instant, +4733 path_id: PathId, +4734 packet: Packet, +4735 #[allow(unused)] qlog: &mut QlogRecvPacket, +4736 ) -> Result<(), TransportError> { +4737 debug_assert_ne!(packet.header.space(), SpaceKind::Data); +4738 debug_assert_eq!(path_id, PathId::ZERO); +4739 let payload_len = packet.payload.len(); +4740 let mut ack_eliciting = false; +4741 for result in frame::Iter::new(packet.payload.freeze())? { +4742 let frame = result?; +4743 qlog.frame(&frame); +4744 let span = match frame { +4745 Frame::Padding => continue, +4746 _ => Some(trace_span!("frame", ty = %frame.ty(), path = tracing::field::Empty)), +4747 }; +4748 +4749 self.path_stats +4750 .for_path(path_id) +4751 .frame_rx +4752 .record(frame.ty()); +4753 +4754 let _guard = span.as_ref().map(|x| x.enter()); +4755 ack_eliciting |= frame.is_ack_eliciting(); 4756 -4757 match frame { -4758 Frame::Padding | Frame::Ping => {} -4759 Frame::Crypto(frame) => { -4760 self.read_crypto(packet.header.space().into(), &frame, payload_len)?; -4761 } -4762 Frame::Ack(ack) => { -4763 self.on_ack_received(now, packet.header.space().into(), ack)?; -4764 } -4765 Frame::PathAck(ack) => { -4766 span.as_ref() -4767 .map(|span| span.record("path", tracing::field::display(&ack.path_id))); -4768 self.on_path_ack_received(now, packet.header.space().into(), ack)?; -4769 } -4770 Frame::Close(reason) => { -4771 self.state.move_to_draining(Some(reason.into())); -4772 return Ok(()); -4773 } -4774 _ => { -4775 let mut err = -4776 TransportError::PROTOCOL_VIOLATION("illegal frame type in handshake"); -4777 err.frame = frame::MaybeFrame::Known(frame.ty()); -4778 return Err(err); -4779 } -4780 } -4781 } -4782 -4783 if ack_eliciting { -4784 // In the initial and handshake spaces, ACKs must be sent immediately -4785 self.spaces[packet.header.space()] -4786 .for_path(path_id) -4787 .pending_acks -4788 .set_immediate_ack_required(); -4789 } -4790 -4791 self.write_crypto(); -4792 Ok(()) -4793 } -4794 -4795 /// Processes the decrypted packet payload, always in the data space. -4796 fn process_payload( -4797 &mut self, -4798 now: Instant, -4799 network_path: FourTuple, -4800 path_id: PathId, -4801 number: u64, -4802 packet: Packet, -4803 #[allow(unused)] qlog: &mut QlogRecvPacket, -4804 ) -> Result<(), TransportError> { -4805 let is_multipath_negotiated = self.is_multipath_negotiated(); -4806 let payload = packet.payload.freeze(); -4807 let mut is_probing_packet = true; -4808 let mut close = None; -4809 let payload_len = payload.len(); -4810 let mut ack_eliciting = false; -4811 // if this packet triggers a path migration and includes a observed address frame, it's -4812 // stored here -4813 let mut migration_observed_addr = None; -4814 for result in frame::Iter::new(payload)? { -4815 let frame = result?; -4816 qlog.frame(&frame); -4817 let span = match frame { -4818 Frame::Padding => continue, -4819 _ => trace_span!("frame", ty = %frame.ty(), path = tracing::field::Empty), -4820 }; -4821 -4822 self.path_stats -4823 .for_path(path_id) -4824 .frame_rx -4825 .record(frame.ty()); -4826 // Crypto, Stream and Datagram frames are special cased in order no pollute -4827 // the log with payload data -4828 match &frame { -4829 Frame::Crypto(f) => { -4830 trace!(offset = f.offset, len = f.data.len(), "got frame CRYPTO"); -4831 } -4832 Frame::Stream(f) => { -4833 trace!(id = %f.id, offset = f.offset, len = f.data.len(), fin = f.fin, "got frame STREAM"); -4834 } -4835 Frame::Datagram(f) => { -4836 trace!(len = f.data.len(), "got frame DATAGRAM"); -4837 } -4838 f => { -4839 trace!("got frame {f}"); -4840 } -4841 } -4842 -4843 let _guard = span.enter(); -4844 if packet.header.is_0rtt() { -4845 match frame { -4846 Frame::Crypto(_) | Frame::Close(Close::Application(_)) => { -4847 return Err(TransportError::PROTOCOL_VIOLATION( -4848 "illegal frame type in 0-RTT", -4849 )); -4850 } -4851 _ => { -4852 if frame.is_1rtt() { -4853 return Err(TransportError::PROTOCOL_VIOLATION( -4854 "illegal frame type in 0-RTT", -4855 )); -4856 } +4757 // Process frames +4758 if frame.is_1rtt() && packet.header.space() != SpaceKind::Data { +4759 return Err(TransportError::PROTOCOL_VIOLATION( +4760 "illegal frame type in handshake", +4761 )); +4762 } +4763 +4764 match frame { +4765 Frame::Padding | Frame::Ping => {} +4766 Frame::Crypto(frame) => { +4767 self.read_crypto(packet.header.space().into(), &frame, payload_len)?; +4768 } +4769 Frame::Ack(ack) => { +4770 self.on_ack_received(now, packet.header.space().into(), ack)?; +4771 } +4772 Frame::PathAck(ack) => { +4773 span.as_ref() +4774 .map(|span| span.record("path", tracing::field::display(&ack.path_id))); +4775 self.on_path_ack_received(now, packet.header.space().into(), ack)?; +4776 } +4777 Frame::Close(reason) => { +4778 self.state.move_to_draining(Some(reason.into())); +4779 return Ok(()); +4780 } +4781 _ => { +4782 let mut err = +4783 TransportError::PROTOCOL_VIOLATION("illegal frame type in handshake"); +4784 err.frame = frame::MaybeFrame::Known(frame.ty()); +4785 return Err(err); +4786 } +4787 } +4788 } +4789 +4790 if ack_eliciting { +4791 // In the initial and handshake spaces, ACKs must be sent immediately +4792 self.spaces[packet.header.space()] +4793 .for_path(path_id) +4794 .pending_acks +4795 .set_immediate_ack_required(); +4796 } +4797 +4798 self.write_crypto(); +4799 Ok(()) +4800 } +4801 +4802 /// Processes the decrypted packet payload, always in the data space. +4803 fn process_payload( +4804 &mut self, +4805 now: Instant, +4806 network_path: FourTuple, +4807 path_id: PathId, +4808 number: u64, +4809 packet: Packet, +4810 #[allow(unused)] qlog: &mut QlogRecvPacket, +4811 ) -> Result<(), TransportError> { +4812 let is_multipath_negotiated = self.is_multipath_negotiated(); +4813 let payload = packet.payload.freeze(); +4814 let mut is_probing_packet = true; +4815 let mut close = None; +4816 let payload_len = payload.len(); +4817 let mut ack_eliciting = false; +4818 // if this packet triggers a path migration and includes a observed address frame, it's +4819 // stored here +4820 let mut migration_observed_addr = None; +4821 for result in frame::Iter::new(payload)? { +4822 let frame = result?; +4823 qlog.frame(&frame); +4824 let span = match frame { +4825 Frame::Padding => continue, +4826 _ => trace_span!("frame", ty = %frame.ty(), path = tracing::field::Empty), +4827 }; +4828 +4829 self.path_stats +4830 .for_path(path_id) +4831 .frame_rx +4832 .record(frame.ty()); +4833 // Crypto, Stream and Datagram frames are special cased in order no pollute +4834 // the log with payload data +4835 match &frame { +4836 Frame::Crypto(f) => { +4837 trace!(offset = f.offset, len = f.data.len(), "got frame CRYPTO"); +4838 } +4839 Frame::Stream(f) => { +4840 trace!(id = %f.id, offset = f.offset, len = f.data.len(), fin = f.fin, "got frame STREAM"); +4841 } +4842 Frame::Datagram(f) => { +4843 trace!(len = f.data.len(), "got frame DATAGRAM"); +4844 } +4845 f => { +4846 trace!("got frame {f}"); +4847 } +4848 } +4849 +4850 let _guard = span.enter(); +4851 if packet.header.is_0rtt() { +4852 match frame { +4853 Frame::Crypto(_) | Frame::Close(Close::Application(_)) => { +4854 return Err(TransportError::PROTOCOL_VIOLATION( +4855 "illegal frame type in 0-RTT", +4856 )); 4857 } -4858 } -4859 } -4860 ack_eliciting |= frame.is_ack_eliciting(); -4861 -4862 // Check whether this could be a probing packet -4863 match frame { -4864 Frame::Padding -4865 | Frame::PathChallenge(_) -4866 | Frame::PathResponse(_) -4867 | Frame::NewConnectionId(_) -4868 | Frame::ObservedAddr(_) => {} -4869 _ => { -4870 is_probing_packet = false; -4871 } -4872 } -4873 -4874 match frame { -4875 Frame::Crypto(frame) => { -4876 self.read_crypto(SpaceId::Data, &frame, payload_len)?; -4877 } -4878 Frame::Stream(frame) => { -4879 if self.streams.received(frame, payload_len)?.should_transmit() { -4880 self.spaces[SpaceId::Data].pending.max_data = true; -4881 } -4882 } -4883 Frame::Ack(ack) => { -4884 self.on_ack_received(now, SpaceId::Data, ack)?; -4885 } -4886 Frame::PathAck(ack) => { -4887 if !self.is_multipath_negotiated() { -4888 return Err(TransportError::PROTOCOL_VIOLATION( -4889 "received PATH_ACK frame when multipath was not negotiated", -4890 )); -4891 } -4892 span.record("path", tracing::field::display(&ack.path_id)); -4893 self.on_path_ack_received(now, SpaceId::Data, ack)?; -4894 } -4895 Frame::Padding | Frame::Ping => {} -4896 Frame::Close(reason) => { -4897 close = Some(reason); -4898 } -4899 Frame::PathChallenge(challenge) => { -4900 let path = &mut self -4901 .path_mut(path_id) -4902 .expect("payload is processed only after the path becomes known"); -4903 path.path_responses.push(number, challenge.0, network_path); -4904 // If we were passively migrated (e.g. NAT rebinding), our local_ip will -4905 // not match. Once we processed a non-probing packet the local_ip will -4906 // finally be updated. -4907 if network_path.remote == path.network_path.remote { -4908 // PATH_CHALLENGE on active path, possible off-path packet -4909 // forwarding attack. Send a non-probing packet to recover the -4910 // active path. See -4911 // https://www.rfc-editor.org/rfc/rfc9000.html#section-9.3.3-3. In -4912 // rare cases NAT probes might also appear on-path and would also -4913 // get a non-probing packet as response. There is little harm in -4914 // this. -4915 match self.peer_supports_ack_frequency() { -4916 true => self.immediate_ack(path_id), -4917 false => { -4918 self.ping_path(path_id).ok(); -4919 } -4920 } -4921 } -4922 } -4923 Frame::PathResponse(response) => { -4924 // First try to see if this is a NAT probe response. -4925 if let Ok(nat_state) = self.n0_nat_traversal.server_side_mut() -4926 && nat_state.handle_path_response(network_path, response.0) -4927 { -4928 trace!( -4929 src = ?network_path, -4930 challenge = response.0, -4931 "Received valid NAT traversal probe response" -4932 ); -4933 // Server-side: nothing else to do. -4934 } else { -4935 // Try to see if this is a response to an on-path PATH_CHALLENGE. -4936 -4937 let path = self -4938 .paths -4939 .get_mut(&path_id) -4940 .expect("payload is processed only after the path becomes known"); -4941 -4942 use PathTimer::*; -4943 use paths::OnPathResponseReceived::*; -4944 match path -4945 .data -4946 .on_path_response_received(now, response.0, network_path) -4947 { -4948 OnPath { was_open } => { -4949 let qlog = self.qlog.with_time(now); -4950 -4951 self.timers.stop( -4952 Timer::PerPath(path_id, PathValidationFailed), -4953 qlog.clone(), -4954 ); -4955 self.timers.stop( -4956 Timer::PerPath(path_id, AbandonFromValidation), -4957 qlog.clone(), -4958 ); -4959 -4960 let next_challenge = path -4961 .data -4962 .earliest_on_path_expiring_challenge() -4963 .map(|time| time + self.ack_frequency.max_ack_delay_for_pto()); -4964 self.timers.set_or_stop( -4965 Timer::PerPath(path_id, PathChallengeLost), -4966 next_challenge, -4967 qlog, -4968 ); -4969 -4970 if !was_open { -4971 if is_multipath_negotiated { -4972 self.events.push_back(Event::Path(PathEvent::Opened { -4973 id: path_id, -4974 })); -4975 } -4976 if let Some(observed) = -4977 path.data.last_observed_addr_report.as_ref() -4978 { -4979 self.events.push_back(Event::Path( -4980 PathEvent::ObservedAddr { -4981 id: path_id, -4982 addr: observed.socket_addr(), -4983 }, -4984 )); -4985 } -4986 } -4987 if let Some((_, ref mut prev)) = path.prev { -4988 // If an on-path response was received while there is a -4989 // previous path from a migration, then the new path is -4990 // validated and we can stop sending challenges that try to -4991 // re-validate the previous path. -4992 prev.reset_on_path_challenges(); -4993 } -4994 } -4995 Ignored { -4996 sent_on, -4997 current_path, -4998 } => { -4999 debug!(%sent_on, %current_path, %response, "ignoring valid PATH_RESPONSE") -5000 } -5001 Unknown => debug!(%response, "ignoring invalid PATH_RESPONSE"), -5002 } -5003 } -5004 } -5005 Frame::MaxData(frame::MaxData(bytes)) => { -5006 self.streams.received_max_data(bytes); +4858 _ => { +4859 if frame.is_1rtt() { +4860 return Err(TransportError::PROTOCOL_VIOLATION( +4861 "illegal frame type in 0-RTT", +4862 )); +4863 } +4864 } +4865 } +4866 } +4867 ack_eliciting |= frame.is_ack_eliciting(); +4868 +4869 // Check whether this could be a probing packet +4870 match frame { +4871 Frame::Padding +4872 | Frame::PathChallenge(_) +4873 | Frame::PathResponse(_) +4874 | Frame::NewConnectionId(_) +4875 | Frame::ObservedAddr(_) => {} +4876 _ => { +4877 is_probing_packet = false; +4878 } +4879 } +4880 +4881 match frame { +4882 Frame::Crypto(frame) => { +4883 self.read_crypto(SpaceId::Data, &frame, payload_len)?; +4884 } +4885 Frame::Stream(frame) => { +4886 if self.streams.received(frame, payload_len)?.should_transmit() { +4887 self.spaces[SpaceId::Data].pending.max_data = true; +4888 } +4889 } +4890 Frame::Ack(ack) => { +4891 self.on_ack_received(now, SpaceId::Data, ack)?; +4892 } +4893 Frame::PathAck(ack) => { +4894 if !self.is_multipath_negotiated() { +4895 return Err(TransportError::PROTOCOL_VIOLATION( +4896 "received PATH_ACK frame when multipath was not negotiated", +4897 )); +4898 } +4899 span.record("path", tracing::field::display(&ack.path_id)); +4900 self.on_path_ack_received(now, SpaceId::Data, ack)?; +4901 } +4902 Frame::Padding | Frame::Ping => {} +4903 Frame::Close(reason) => { +4904 close = Some(reason); +4905 } +4906 Frame::PathChallenge(challenge) => { +4907 let path = &mut self +4908 .path_mut(path_id) +4909 .expect("payload is processed only after the path becomes known"); +4910 path.path_responses.push(number, challenge.0, network_path); +4911 // If we were passively migrated (e.g. NAT rebinding), our local_ip will +4912 // not match. Once we processed a non-probing packet the local_ip will +4913 // finally be updated. +4914 if network_path.remote == path.network_path.remote { +4915 // PATH_CHALLENGE on active path, possible off-path packet +4916 // forwarding attack. Send a non-probing packet to recover the +4917 // active path. See +4918 // https://www.rfc-editor.org/rfc/rfc9000.html#section-9.3.3-3. In +4919 // rare cases NAT probes might also appear on-path and would also +4920 // get a non-probing packet as response. There is little harm in +4921 // this. +4922 match self.peer_supports_ack_frequency() { +4923 true => self.immediate_ack(path_id), +4924 false => { +4925 self.ping_path(path_id).ok(); +4926 } +4927 } +4928 } +4929 } +4930 Frame::PathResponse(response) => { +4931 // First try to see if this is a NAT probe response. +4932 if self +4933 .n0_nat_traversal +4934 .handle_path_response(network_path, response.0) +4935 { +4936 self.open_nat_traversed_paths(now); +4937 } else { +4938 // Try to see if this is a response to an on-path PATH_CHALLENGE. +4939 +4940 let path = self +4941 .paths +4942 .get_mut(&path_id) +4943 .expect("payload is processed only after the path becomes known"); +4944 +4945 use PathTimer::*; +4946 use paths::OnPathResponseReceived::*; +4947 match path +4948 .data +4949 .on_path_response_received(now, response.0, network_path) +4950 { +4951 OnPath { was_open } => { +4952 let qlog = self.qlog.with_time(now); +4953 +4954 self.timers.stop( +4955 Timer::PerPath(path_id, PathValidationFailed), +4956 qlog.clone(), +4957 ); +4958 self.timers.stop( +4959 Timer::PerPath(path_id, AbandonFromValidation), +4960 qlog.clone(), +4961 ); +4962 +4963 let next_challenge = path +4964 .data +4965 .earliest_on_path_expiring_challenge() +4966 .map(|time| time + self.ack_frequency.max_ack_delay_for_pto()); +4967 self.timers.set_or_stop( +4968 Timer::PerPath(path_id, PathChallengeLost), +4969 next_challenge, +4970 qlog, +4971 ); +4972 +4973 if !was_open { +4974 if is_multipath_negotiated { +4975 self.events.push_back(Event::Path(PathEvent::Opened { +4976 id: path_id, +4977 })); +4978 } +4979 if let Some(observed) = +4980 path.data.last_observed_addr_report.as_ref() +4981 { +4982 self.events.push_back(Event::Path( +4983 PathEvent::ObservedAddr { +4984 id: path_id, +4985 addr: observed.socket_addr(), +4986 }, +4987 )); +4988 } +4989 } +4990 if let Some((_, ref mut prev)) = path.prev { +4991 // If an on-path response was received while there is a +4992 // previous path from a migration, then the new path is +4993 // validated and we can stop sending challenges that try to +4994 // re-validate the previous path. +4995 prev.reset_on_path_challenges(); +4996 } +4997 } +4998 Ignored { +4999 sent_on, +5000 current_path, +5001 } => { +5002 debug!(%sent_on, %current_path, %response, "ignoring valid PATH_RESPONSE") +5003 } +5004 Unknown => debug!(%response, "ignoring invalid PATH_RESPONSE"), +5005 } +5006 } 5007 } -5008 Frame::MaxStreamData(frame::MaxStreamData { id, offset }) => { -5009 self.streams.received_max_stream_data(id, offset)?; +5008 Frame::MaxData(frame::MaxData(bytes)) => { +5009 self.streams.received_max_data(bytes); 5010 } -5011 Frame::MaxStreams(frame::MaxStreams { dir, count }) => { -5012 self.streams.received_max_streams(dir, count)?; +5011 Frame::MaxStreamData(frame::MaxStreamData { id, offset }) => { +5012 self.streams.received_max_stream_data(id, offset)?; 5013 } -5014 Frame::ResetStream(frame) => { -5015 if self.streams.received_reset(frame)?.should_transmit() { -5016 self.spaces[SpaceId::Data].pending.max_data = true; -5017 } -5018 } -5019 Frame::DataBlocked(DataBlocked(offset)) => { -5020 debug!(offset, "peer claims to be blocked at connection level"); +5014 Frame::MaxStreams(frame::MaxStreams { dir, count }) => { +5015 self.streams.received_max_streams(dir, count)?; +5016 } +5017 Frame::ResetStream(frame) => { +5018 if self.streams.received_reset(frame)?.should_transmit() { +5019 self.spaces[SpaceId::Data].pending.max_data = true; +5020 } 5021 } -5022 Frame::StreamDataBlocked(StreamDataBlocked { id, offset }) => { -5023 if id.initiator() == self.side.side() && id.dir() == Dir::Uni { -5024 debug!("got STREAM_DATA_BLOCKED on send-only {}", id); -5025 return Err(TransportError::STREAM_STATE_ERROR( -5026 "STREAM_DATA_BLOCKED on send-only stream", -5027 )); -5028 } -5029 debug!( -5030 stream = %id, -5031 offset, "peer claims to be blocked at stream level" -5032 ); -5033 } -5034 Frame::StreamsBlocked(StreamsBlocked { dir, limit }) => { -5035 if limit > MAX_STREAM_COUNT { -5036 return Err(TransportError::FRAME_ENCODING_ERROR( -5037 "unrepresentable stream limit", -5038 )); -5039 } -5040 debug!( -5041 "peer claims to be blocked opening more than {} {} streams", -5042 limit, dir -5043 ); -5044 } -5045 Frame::StopSending(frame::StopSending { id, error_code }) => { -5046 if id.initiator() != self.side.side() { -5047 if id.dir() == Dir::Uni { -5048 debug!("got STOP_SENDING on recv-only {}", id); -5049 return Err(TransportError::STREAM_STATE_ERROR( -5050 "STOP_SENDING on recv-only stream", -5051 )); -5052 } -5053 } else if self.streams.is_local_unopened(id) { -5054 return Err(TransportError::STREAM_STATE_ERROR( -5055 "STOP_SENDING on unopened stream", -5056 )); -5057 } -5058 self.streams.received_stop_sending(id, error_code); -5059 } -5060 Frame::RetireConnectionId(frame::RetireConnectionId { path_id, sequence }) => { -5061 if let Some(ref path_id) = path_id { -5062 span.record("path", tracing::field::display(&path_id)); -5063 } -5064 let path_id = path_id.unwrap_or_default(); -5065 match self.local_cid_state.get_mut(&path_id) { -5066 None => error!(?path_id, "RETIRE_CONNECTION_ID for unknown path"), -5067 Some(cid_state) => { -5068 let allow_more_cids = cid_state -5069 .on_cid_retirement(sequence, self.peer_params.issue_cids_limit())?; -5070 -5071 // If the path has closed, we do not issue more CIDs for this path -5072 // For details see https://www.ietf.org/archive/id/draft-ietf-quic-multipath-17.html#section-3.2.2 -5073 // > an endpoint SHOULD provide new connection IDs for that path, if still open, using PATH_NEW_CONNECTION_ID frames. -5074 let has_path = !self.abandoned_paths.contains(&path_id); -5075 let allow_more_cids = allow_more_cids && has_path; -5076 -5077 debug_assert!(!self.state.is_drained()); // required for adding endpoint events, process_payload is never called for drained connections -5078 self.endpoint_events -5079 .push_back(EndpointEventInner::RetireConnectionId( -5080 now, -5081 path_id, -5082 sequence, -5083 allow_more_cids, -5084 )); -5085 } -5086 } -5087 } -5088 Frame::NewConnectionId(frame) => { -5089 let path_id = if let Some(path_id) = frame.path_id { -5090 if !self.is_multipath_negotiated() { -5091 return Err(TransportError::PROTOCOL_VIOLATION( -5092 "received PATH_NEW_CONNECTION_ID frame when multipath was not negotiated", -5093 )); -5094 } -5095 if path_id > self.local_max_path_id { -5096 return Err(TransportError::PROTOCOL_VIOLATION( -5097 "PATH_NEW_CONNECTION_ID contains path_id exceeding current max", -5098 )); -5099 } -5100 path_id -5101 } else { -5102 PathId::ZERO -5103 }; -5104 -5105 if let Some(ref path_id) = frame.path_id { -5106 span.record("path", tracing::field::display(&path_id)); -5107 } -5108 -5109 if self.abandoned_paths.contains(&path_id) { -5110 trace!("ignoring issued CID for abandoned path"); -5111 continue; -5112 } -5113 let remote_cids = self -5114 .remote_cids -5115 .entry(path_id) -5116 .or_insert_with(|| CidQueue::new(frame.id)); -5117 if remote_cids.active().is_empty() { -5118 return Err(TransportError::PROTOCOL_VIOLATION( -5119 "NEW_CONNECTION_ID when CIDs aren't in use", -5120 )); -5121 } -5122 if frame.retire_prior_to > frame.sequence { -5123 return Err(TransportError::PROTOCOL_VIOLATION( -5124 "NEW_CONNECTION_ID retiring unissued CIDs", -5125 )); -5126 } -5127 -5128 use crate::cid_queue::InsertError; -5129 match remote_cids.insert(frame) { -5130 Ok(None) if self.path(path_id).is_none() => { -5131 // TODO(flub): Once the client does off-path NAT probes as well -5132 // we should remove this. -5133 // If this gives us CIDs to open a new path and a nat traversal attempt -5134 // is underway we could try to probe a pending remote -5135 self.continue_nat_traversal_round(now); -5136 } -5137 Ok(None) => {} -5138 Ok(Some((retired, reset_token))) => { -5139 let pending_retired = -5140 &mut self.spaces[SpaceId::Data].pending.retire_cids; -5141 /// Ensure `pending_retired` cannot grow without bound. Limit is -5142 /// somewhat arbitrary but very permissive. -5143 const MAX_PENDING_RETIRED_CIDS: u64 = CidQueue::LEN as u64 * 10; -5144 // We don't bother counting in-flight frames because those are bounded -5145 // by congestion control. -5146 if (pending_retired.len() as u64) -5147 .saturating_add(retired.end.saturating_sub(retired.start)) -5148 > MAX_PENDING_RETIRED_CIDS -5149 { -5150 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR( -5151 "queued too many retired CIDs", -5152 )); -5153 } -5154 pending_retired.extend(retired.map(|seq| (path_id, seq))); -5155 self.set_reset_token(path_id, network_path.remote, reset_token); -5156 } -5157 Err(InsertError::ExceedsLimit) => { -5158 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR("")); -5159 } -5160 Err(InsertError::Retired) => { -5161 trace!("discarding already-retired"); -5162 // RETIRE_CONNECTION_ID might not have been previously sent if e.g. a -5163 // range of connection IDs larger than the active connection ID limit -5164 // was retired all at once via retire_prior_to. -5165 self.spaces[SpaceId::Data] -5166 .pending -5167 .retire_cids -5168 .push((path_id, frame.sequence)); -5169 continue; -5170 } -5171 }; -5172 -5173 if self.side.is_server() -5174 && path_id == PathId::ZERO -5175 && self -5176 .remote_cids -5177 .get(&PathId::ZERO) -5178 .map(|cids| cids.active_seq() == 0) -5179 .unwrap_or_default() -5180 { -5181 // We're a server still using the initial remote CID for the client, so -5182 // let's switch immediately to enable clientside stateless resets. -5183 self.update_remote_cid(PathId::ZERO); -5184 } -5185 } -5186 Frame::NewToken(NewToken { token }) => { -5187 let ConnectionSide::Client { -5188 token_store, -5189 server_name, -5190 .. -5191 } = &self.side -5192 else { -5193 return Err(TransportError::PROTOCOL_VIOLATION("client sent NEW_TOKEN")); -5194 }; -5195 if token.is_empty() { -5196 return Err(TransportError::FRAME_ENCODING_ERROR("empty token")); -5197 } -5198 trace!("got new token"); -5199 token_store.insert(server_name, token); -5200 } -5201 Frame::Datagram(datagram) => { -5202 if self -5203 .datagrams -5204 .received(datagram, &self.config.datagram_receive_buffer_size)? -5205 { -5206 self.events.push_back(Event::DatagramReceived); -5207 } -5208 } -5209 Frame::AckFrequency(ack_frequency) => { -5210 // This frame can only be sent in the Data space -5211 -5212 if !self.ack_frequency.ack_frequency_received(&ack_frequency)? { -5213 // The AckFrequency frame is stale (we have already received a more -5214 // recent one) -5215 continue; -5216 } -5217 -5218 // Update the params for all of our paths -5219 for (path_id, space) in self.spaces[SpaceId::Data].number_spaces.iter_mut() { -5220 space.pending_acks.set_ack_frequency_params(&ack_frequency); -5221 -5222 // Our `max_ack_delay` has been updated, so we may need to adjust -5223 // its associated timeout. -5224 // Packets received on abandoned paths are always acknowledged immediately. -5225 if !self.abandoned_paths.contains(path_id) -5226 && let Some(timeout) = space -5227 .pending_acks -5228 .max_ack_delay_timeout(self.ack_frequency.max_ack_delay) -5229 { -5230 self.timers.set( -5231 Timer::PerPath(*path_id, PathTimer::MaxAckDelay), -5232 timeout, -5233 self.qlog.with_time(now), -5234 ); -5235 } -5236 } -5237 } -5238 Frame::ImmediateAck => { -5239 // This frame can only be sent in the Data space -5240 for pns in self.spaces[SpaceId::Data].iter_paths_mut() { -5241 pns.pending_acks.set_immediate_ack_required(); -5242 } -5243 } -5244 Frame::HandshakeDone => { -5245 if self.side.is_server() { -5246 return Err(TransportError::PROTOCOL_VIOLATION( -5247 "client sent HANDSHAKE_DONE", -5248 )); -5249 } -5250 if self.crypto_state.has_keys(EncryptionLevel::Handshake) { -5251 self.discard_space(now, SpaceKind::Handshake); -5252 self.events.push_back(Event::HandshakeConfirmed); -5253 trace!("handshake confirmed"); -5254 } -5255 } -5256 Frame::ObservedAddr(observed) => { -5257 // check if params allows the peer to send report and this node to receive it -5258 trace!(seq_no = %observed.seq_no, ip = %observed.ip, port = observed.port); -5259 if !self -5260 .peer_params -5261 .address_discovery_role -5262 .should_report(&self.config.address_discovery_role) -5263 { -5264 return Err(TransportError::PROTOCOL_VIOLATION( -5265 "received OBSERVED_ADDRESS frame when not negotiated", -5266 )); -5267 } -5268 // must only be sent in data space -5269 if packet.header.space() != SpaceKind::Data { -5270 return Err(TransportError::PROTOCOL_VIOLATION( -5271 "OBSERVED_ADDRESS frame outside data space", -5272 )); -5273 } -5274 -5275 let path = self.path_data_mut(path_id); -5276 if network_path == path.network_path { -5277 if let Some(updated) = path.update_observed_addr_report(observed) -5278 && path.open_status == paths::OpenStatus::Informed -5279 { -5280 self.events.push_back(Event::Path(PathEvent::ObservedAddr { -5281 id: path_id, -5282 addr: updated, -5283 })); -5284 // otherwise the event is reported when the path is deemed open -5285 } -5286 } else { -5287 // include in migration -5288 migration_observed_addr = Some(observed) -5289 } -5290 } -5291 Frame::PathAbandon(frame::PathAbandon { -5292 path_id, -5293 error_code, -5294 }) => { -5295 span.record("path", tracing::field::display(&path_id)); -5296 match self.close_path_inner( -5297 now, -5298 path_id, -5299 PathAbandonReason::RemoteAbandoned { -5300 error_code: error_code.into(), -5301 }, -5302 ) { -5303 Ok(()) => { -5304 trace!("peer abandoned path"); -5305 } -5306 Err(ClosePathError::ClosedPath) => { -5307 trace!("peer abandoned already closed path"); -5308 } -5309 Err(ClosePathError::MultipathNotNegotiated) => { -5310 return Err(TransportError::PROTOCOL_VIOLATION( -5311 "received PATH_ABANDON frame when multipath was not negotiated", -5312 )); -5313 } -5314 Err(ClosePathError::LastOpenPath) => { -5315 // Not reachable: close_path_inner allows remote abandons -5316 // for the last path. But handle gracefully just in case. -5317 error!( -5318 "peer abandoned last path but close_path_inner returned LastOpenPath" -5319 ); -5320 } -5321 }; -5322 -5323 // Start draining the path if it still exists and hasn't started draining yet. -5324 if let Some(path) = self.paths.get_mut(&path_id) -5325 && !mem::replace(&mut path.data.draining, true) -5326 { -5327 let ack_delay = self.ack_frequency.max_ack_delay_for_pto(); -5328 let pto = path.data.rtt.pto_base() + ack_delay; -5329 self.timers.set( -5330 Timer::PerPath(path_id, PathTimer::PathDrained), -5331 now + 3 * pto, -5332 self.qlog.with_time(now), -5333 ); -5334 -5335 self.set_max_path_id(now, self.local_max_path_id.saturating_add(1u8)); -5336 } -5337 } -5338 Frame::PathStatusAvailable(info) => { -5339 span.record("path", tracing::field::display(&info.path_id)); -5340 if self.is_multipath_negotiated() { -5341 self.on_path_status( -5342 info.path_id, -5343 PathStatus::Available, -5344 info.status_seq_no, -5345 ); -5346 } else { -5347 return Err(TransportError::PROTOCOL_VIOLATION( -5348 "received PATH_STATUS_AVAILABLE frame when multipath was not negotiated", -5349 )); -5350 } -5351 } -5352 Frame::PathStatusBackup(info) => { -5353 span.record("path", tracing::field::display(&info.path_id)); -5354 if self.is_multipath_negotiated() { -5355 self.on_path_status(info.path_id, PathStatus::Backup, info.status_seq_no); -5356 } else { -5357 return Err(TransportError::PROTOCOL_VIOLATION( -5358 "received PATH_STATUS_BACKUP frame when multipath was not negotiated", -5359 )); -5360 } -5361 } -5362 Frame::MaxPathId(frame::MaxPathId(path_id)) => { -5363 span.record("path", tracing::field::display(&path_id)); -5364 if !self.is_multipath_negotiated() { -5365 return Err(TransportError::PROTOCOL_VIOLATION( -5366 "received MAX_PATH_ID frame when multipath was not negotiated", -5367 )); -5368 } -5369 // frames that do not increase the path id are ignored -5370 if path_id > self.remote_max_path_id { -5371 self.remote_max_path_id = path_id; -5372 self.issue_first_path_cids(now); -5373 // TODO(flub): Once the client sends off-path NAT probes this is no -5374 // longer needed. But new paths that need to be opened may need -5375 // to be notified. -5376 while let Some(true) = self.continue_nat_traversal_round(now) {} -5377 } -5378 } -5379 Frame::PathsBlocked(frame::PathsBlocked(max_path_id)) => { -5380 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST -5381 // be treated as a connection error of type PROTOCOL_VIOLATION. -5382 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> -5383 if self.is_multipath_negotiated() { -5384 if max_path_id > self.local_max_path_id { -5385 return Err(TransportError::PROTOCOL_VIOLATION( -5386 "PATHS_BLOCKED maximum path identifier was larger than local maximum", -5387 )); -5388 } -5389 debug!("received PATHS_BLOCKED({:?})", max_path_id); -5390 // TODO(@divma): ensure max concurrent paths -5391 } else { -5392 return Err(TransportError::PROTOCOL_VIOLATION( -5393 "received PATHS_BLOCKED frame when not multipath was not negotiated", -5394 )); -5395 } -5396 } -5397 Frame::PathCidsBlocked(frame::PathCidsBlocked { path_id, next_seq }) => { -5398 // Nothing to do. This is recorded in the frame stats, but otherwise we -5399 // always issue all CIDs we're allowed to issue, so either this is an -5400 // impatient peer or a bug on our side. -5401 -5402 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST -5403 // be treated as a connection error of type PROTOCOL_VIOLATION. -5404 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> -5405 if self.is_multipath_negotiated() { -5406 if path_id > self.local_max_path_id { -5407 return Err(TransportError::PROTOCOL_VIOLATION( -5408 "PATH_CIDS_BLOCKED path identifier was larger than local maximum", -5409 )); -5410 } -5411 if next_seq.0 -5412 > self -5413 .local_cid_state -5414 .get(&path_id) -5415 .map(|cid_state| cid_state.active_seq().1 + 1) -5416 .unwrap_or_default() -5417 { -5418 return Err(TransportError::PROTOCOL_VIOLATION( -5419 "PATH_CIDS_BLOCKED next sequence number larger than in local state", -5420 )); -5421 } -5422 debug!(%path_id, %next_seq, "received PATH_CIDS_BLOCKED"); -5423 } else { -5424 return Err(TransportError::PROTOCOL_VIOLATION( -5425 "received PATH_CIDS_BLOCKED frame when not multipath was not negotiated", -5426 )); -5427 } -5428 } -5429 Frame::AddAddress(addr) => { -5430 let client_state = match self.n0_nat_traversal.client_side_mut() { -5431 Ok(state) => state, -5432 Err(err) => { -5433 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5434 "Nat traversal(ADD_ADDRESS): {err}" -5435 ))); -5436 } -5437 }; -5438 -5439 if !client_state.check_remote_address(&addr) { -5440 // if the address is not valid we flag it, but update anyway -5441 warn!(?addr, "server sent illegal ADD_ADDRESS frame"); -5442 } -5443 -5444 match client_state.add_remote_address(addr) { -5445 Ok(maybe_added) => { -5446 if let Some(added) = maybe_added { -5447 self.events.push_back(Event::NatTraversal( -5448 n0_nat_traversal::Event::AddressAdded(added), -5449 )); -5450 } -5451 } -5452 Err(e) => { -5453 warn!(%e, "failed to add remote address") -5454 } -5455 } -5456 } -5457 Frame::RemoveAddress(addr) => { -5458 let client_state = match self.n0_nat_traversal.client_side_mut() { -5459 Ok(state) => state, -5460 Err(err) => { -5461 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5462 "Nat traversal(REMOVE_ADDRESS): {err}" -5463 ))); -5464 } -5465 }; -5466 if let Some(removed_addr) = client_state.remove_remote_address(addr) { -5467 self.events.push_back(Event::NatTraversal( -5468 n0_nat_traversal::Event::AddressRemoved(removed_addr), -5469 )); -5470 } -5471 } -5472 Frame::ReachOut(reach_out) => { -5473 let ipv6 = self.is_ipv6(); -5474 let server_state = match self.n0_nat_traversal.server_side_mut() { -5475 Ok(state) => state, -5476 Err(err) => { -5477 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5478 "Nat traversal(REACH_OUT): {err}" -5479 ))); -5480 } -5481 }; -5482 -5483 let round_before = server_state.current_round(); -5484 -5485 if let Err(err) = server_state.handle_reach_out(reach_out, ipv6) { -5486 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5487 "Nat traversal(REACH_OUT): {err}" -5488 ))); -5489 } -5490 -5491 if server_state.current_round() > round_before { -5492 // A new round was started, reset the NAT probe retry timer. -5493 let delay = RttEstimator::new(self.config.initial_rtt).pto_base() * 2 / 3; -5494 self.timers.set( -5495 Timer::Conn(ConnTimer::NatTraversalProbeRetry), -5496 now + delay, -5497 self.qlog.with_time(now), -5498 ); -5499 } -5500 } -5501 } -5502 } -5503 -5504 let space = self.spaces[SpaceId::Data].for_path(path_id); -5505 if space -5506 .pending_acks -5507 .packet_received(now, number, ack_eliciting, &space.dedup) -5508 { -5509 if self.abandoned_paths.contains(&path_id) { -5510 // § 3.4.3 QUIC-MULTIPATH: promptly send ACKs for packets received from -5511 // abandoned paths. -5512 space.pending_acks.set_immediate_ack_required(); -5513 } else { -5514 self.timers.set( -5515 Timer::PerPath(path_id, PathTimer::MaxAckDelay), -5516 now + self.ack_frequency.max_ack_delay, -5517 self.qlog.with_time(now), -5518 ); -5519 } -5520 } -5521 -5522 // Issue stream ID credit due to ACKs of outgoing finish/resets and incoming finish/resets -5523 // on stopped streams. Incoming finishes/resets on open streams are not handled here as they -5524 // are only freed, and hence only issue credit, once the application has been notified -5525 // during a read on the stream. -5526 let pending = &mut self.spaces[SpaceId::Data].pending; -5527 self.streams.queue_max_stream_id(pending); -5528 -5529 if let Some(reason) = close { -5530 self.state.move_to_draining(Some(reason.into())); -5531 self.connection_close_pending = true; -5532 } -5533 -5534 // For Multipath any packet triggers migration. For RFC9000 or QNT (+ Multipath) -5535 // only non-probing packets trigger migration. -5536 let migrate_on_any_packet = -5537 self.is_multipath_negotiated() && !self.n0_nat_traversal.is_negotiated(); -5538 -5539 // Only migrate if this is the largest packet number seen. -5540 let is_largest_received_pn = Some(number) -5541 == self.spaces[SpaceId::Data] -5542 .for_path(path_id) -5543 .largest_received_packet_number; -5544 -5545 // If we receive a non-probing packet on a new local IP that means we had a NAT -5546 // rebinding-like migration. We update our local address but do not otherwise -5547 // validate the new path, we only need to validate the path if the peer migrates per -5548 // RFC9000 §9: https://www.rfc-editor.org/rfc/rfc9000.html#section-9-4 -5549 if (migrate_on_any_packet || !is_probing_packet) -5550 && is_largest_received_pn -5551 && self.local_ip_may_migrate() -5552 && let Some(new_local_ip) = network_path.local_ip -5553 { -5554 let path_data = self.path_data_mut(path_id); -5555 if path_data -5556 .network_path -5557 .local_ip -5558 .is_some_and(|ip| ip != new_local_ip) -5559 { -5560 debug!( -5561 %path_id, -5562 new_4tuple = %network_path, -5563 prev_4tuple = %path_data.network_path, -5564 "local address passive migration" -5565 ); -5566 } -5567 path_data.network_path.local_ip = Some(new_local_ip) -5568 } -5569 -5570 // If the peer migrated to a new address, trigger migration. -5571 if (migrate_on_any_packet || !is_probing_packet) -5572 && is_largest_received_pn -5573 && network_path.remote != self.path_data(path_id).network_path.remote -5574 && self.remote_may_migrate() -5575 { -5576 self.migrate(path_id, now, network_path, migration_observed_addr); -5577 // Break linkability, if possible -5578 self.update_remote_cid(path_id); -5579 self.spin = false; -5580 } -5581 -5582 Ok(()) -5583 } -5584 -5585 /// Migrates the 4-tuple of the path. -5586 /// -5587 /// This creates a new [`PathData`] for the migrated path and stores the previous -5588 /// [`PathData`] in [`PathState::prev`]. -5589 fn migrate( -5590 &mut self, -5591 path_id: PathId, -5592 now: Instant, -5593 network_path: FourTuple, -5594 observed_addr: Option<ObservedAddr>, -5595 ) { -5596 trace!( -5597 new_4tuple = %network_path, -5598 prev_4tuple = %self.path_data(path_id).network_path, -5599 %path_id, -5600 "migration initiated", -5601 ); -5602 self.path_generation_counter = self.path_generation_counter.wrapping_add(1); -5603 // TODO(@divma): conditions for path migration in multipath are very specific, check them -5604 // again to prevent path migrations that should actually create a new path -5605 -5606 // Reset rtt/congestion state for new path unless it looks like a NAT rebinding. -5607 // Note that the congestion window will not grow until validation terminates. Helps mitigate -5608 // amplification attacks performed by spoofing source addresses. -5609 let prev_pto = self.pto(SpaceKind::Data, path_id); -5610 let path = self.paths.get_mut(&path_id).expect("known path"); -5611 let mut new_path_data = if network_path.remote.is_ipv4() -5612 && network_path.remote.ip() == path.data.network_path.remote.ip() -5613 { -5614 PathData::from_previous(network_path, &path.data, self.path_generation_counter, now) -5615 } else { -5616 let peer_max_udp_payload_size = -5617 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()) -5618 .unwrap_or(u16::MAX); -5619 PathData::new( -5620 network_path, -5621 self.allow_mtud, -5622 Some(peer_max_udp_payload_size), -5623 self.path_generation_counter, -5624 now, -5625 &self.config, -5626 ) -5627 }; -5628 new_path_data.last_observed_addr_report = path.data.last_observed_addr_report.clone(); -5629 if let Some(report) = observed_addr -5630 && let Some(updated) = new_path_data.update_observed_addr_report(report) -5631 { -5632 tracing::info!("adding observed addr event from migration"); -5633 self.events.push_back(Event::Path(PathEvent::ObservedAddr { -5634 id: path_id, -5635 addr: updated, -5636 })); -5637 } -5638 new_path_data.pending_on_path_challenge = true; -5639 -5640 let mut prev_path_data = mem::replace(&mut path.data, new_path_data); -5641 -5642 // Only store this as previous path if it was validated. For all we know there could -5643 // already be a previous path stored which might have been validated in the past, -5644 // which is more valuable than one that's not yet validated. -5645 // -5646 // With multipath it is possible that there are no remote CIDs for the path ID -5647 // yet. In this case we would never have sent on this path yet and would not be able -5648 // to send a PATH_CHALLENGE either, which is currently a fire-and-forget affair -5649 // anyway. So don't store such a path either. -5650 if !prev_path_data.validated -5651 && let Some(cid) = self.remote_cids.get(&path_id).map(CidQueue::active) -5652 { -5653 prev_path_data.pending_on_path_challenge = true; -5654 // We haven't updated the remote CID yet, this captures the remote CID we were using on -5655 // the previous path. -5656 path.prev = Some((cid, prev_path_data)); -5657 } -5658 -5659 // We need to re-assign the correct remote to this path in qlog -5660 self.qlog.emit_tuple_assigned(path_id, network_path, now); -5661 -5662 self.timers.set( -5663 Timer::PerPath(path_id, PathTimer::PathValidationFailed), -5664 now + 3 * cmp::max(self.pto(SpaceKind::Data, path_id), prev_pto), -5665 self.qlog.with_time(now), -5666 ); -5667 } -5668 -5669 /// Handle a change in the local address, i.e. an active migration -5670 /// -5671 /// In the general (non-multipath) case, paths will perform a RFC9000 migration and be pinged -5672 /// for a liveness check. This is the behaviour of a path assumed to be recoverable, even if -5673 /// this is not the case. -5674 /// -5675 /// Clients in a connection in which multipath has been negotiated should migrate paths to new -5676 /// [`PathId`]s. For paths that are known to be non-recoverable can be migrated to a new -5677 /// [`PathId`] by closing the current path, and opening a new one to the same remote. Treating -5678 /// paths as non recoverable when necessary accelerates connectivity re-establishment, or might -5679 /// allow it altogether. -5680 /// -5681 /// The optional `hint` allows callers to indicate when paths are non-recoverable and should be -5682 /// migrated to new a [`PathId`]. -5683 // NOTE: only clients are allowed to migrate, but generally dealing with RFC9000 migrations is -5684 // lacking <https://github.com/n0-computer/noq/issues/364> -5685 pub fn handle_network_change(&mut self, hint: Option<&dyn NetworkChangeHint>, now: Instant) { -5686 debug!("network changed"); -5687 if self.state.is_drained() { -5688 return; -5689 } -5690 if self.highest_space < SpaceKind::Data { -5691 for path in self.paths.values_mut() { -5692 // Clear the local address for it to be obtained from the socket again. -5693 path.data.network_path.local_ip = None; -5694 } -5695 -5696 self.update_remote_cid(PathId::ZERO); -5697 self.ping(); -5698 -5699 return; -5700 } -5701 -5702 // Paths that can't recover so a new path should be open instead. If multipath is not -5703 // negotiated, this will be empty. -5704 let mut non_recoverable_paths = Vec::default(); -5705 let mut recoverable_paths = Vec::default(); -5706 let mut open_paths = 0; -5707 -5708 let is_multipath_negotiated = self.is_multipath_negotiated(); -5709 let is_client = self.side().is_client(); -5710 let immediate_ack_allowed = self.peer_supports_ack_frequency(); -5711 -5712 for (path_id, path) in self.paths.iter_mut() { -5713 if self.abandoned_paths.contains(path_id) { -5714 continue; -5715 } -5716 open_paths += 1; -5717 -5718 // Read the network path BEFORE clearing local_ip, so the hint can -5719 // check which interface the path was using. -5720 let network_path = path.data.network_path; -5721 -5722 // Clear the local address for it to be obtained from the socket again. This applies to -5723 // all paths, regardless of being considered recoverable or not -5724 path.data.network_path.local_ip = None; -5725 let remote = network_path.remote; -5726 -5727 // Without multipath, the connection tries to recover the single path, whereas with -5728 // multipath, even in a single-path scenario, we attempt to migrate the path to a new -5729 // PathId. -5730 let attempt_to_recover = if is_multipath_negotiated { -5731 // Use the hint to determine if the path can recover. When no hint is -5732 // provided, clients default to non-recoverable (abandon and re-open) -5733 // while servers default to recoverable (attempt in-place recovery). -5734 hint.map(|h| h.is_path_recoverable(*path_id, network_path)) -5735 .unwrap_or(!is_client) -5736 } else { -5737 // In the non multipath case, we try to recover the single active path -5738 true -5739 }; +5022 Frame::DataBlocked(DataBlocked(offset)) => { +5023 debug!(offset, "peer claims to be blocked at connection level"); +5024 } +5025 Frame::StreamDataBlocked(StreamDataBlocked { id, offset }) => { +5026 if id.initiator() == self.side.side() && id.dir() == Dir::Uni { +5027 debug!("got STREAM_DATA_BLOCKED on send-only {}", id); +5028 return Err(TransportError::STREAM_STATE_ERROR( +5029 "STREAM_DATA_BLOCKED on send-only stream", +5030 )); +5031 } +5032 debug!( +5033 stream = %id, +5034 offset, "peer claims to be blocked at stream level" +5035 ); +5036 } +5037 Frame::StreamsBlocked(StreamsBlocked { dir, limit }) => { +5038 if limit > MAX_STREAM_COUNT { +5039 return Err(TransportError::FRAME_ENCODING_ERROR( +5040 "unrepresentable stream limit", +5041 )); +5042 } +5043 debug!( +5044 "peer claims to be blocked opening more than {} {} streams", +5045 limit, dir +5046 ); +5047 } +5048 Frame::StopSending(frame::StopSending { id, error_code }) => { +5049 if id.initiator() != self.side.side() { +5050 if id.dir() == Dir::Uni { +5051 debug!("got STOP_SENDING on recv-only {}", id); +5052 return Err(TransportError::STREAM_STATE_ERROR( +5053 "STOP_SENDING on recv-only stream", +5054 )); +5055 } +5056 } else if self.streams.is_local_unopened(id) { +5057 return Err(TransportError::STREAM_STATE_ERROR( +5058 "STOP_SENDING on unopened stream", +5059 )); +5060 } +5061 self.streams.received_stop_sending(id, error_code); +5062 } +5063 Frame::RetireConnectionId(frame::RetireConnectionId { path_id, sequence }) => { +5064 if let Some(ref path_id) = path_id { +5065 span.record("path", tracing::field::display(&path_id)); +5066 } +5067 let path_id = path_id.unwrap_or_default(); +5068 match self.local_cid_state.get_mut(&path_id) { +5069 None => error!(?path_id, "RETIRE_CONNECTION_ID for unknown path"), +5070 Some(cid_state) => { +5071 let allow_more_cids = cid_state +5072 .on_cid_retirement(sequence, self.peer_params.issue_cids_limit())?; +5073 +5074 // If the path has closed, we do not issue more CIDs for this path +5075 // For details see https://www.ietf.org/archive/id/draft-ietf-quic-multipath-17.html#section-3.2.2 +5076 // > an endpoint SHOULD provide new connection IDs for that path, if still open, using PATH_NEW_CONNECTION_ID frames. +5077 let has_path = !self.abandoned_paths.contains(&path_id); +5078 let allow_more_cids = allow_more_cids && has_path; +5079 +5080 debug_assert!(!self.state.is_drained()); // required for adding endpoint events, process_payload is never called for drained connections +5081 self.endpoint_events +5082 .push_back(EndpointEventInner::RetireConnectionId( +5083 now, +5084 path_id, +5085 sequence, +5086 allow_more_cids, +5087 )); +5088 } +5089 } +5090 } +5091 Frame::NewConnectionId(frame) => { +5092 let path_id = if let Some(path_id) = frame.path_id { +5093 if !self.is_multipath_negotiated() { +5094 return Err(TransportError::PROTOCOL_VIOLATION( +5095 "received PATH_NEW_CONNECTION_ID frame when multipath was not negotiated", +5096 )); +5097 } +5098 if path_id > self.local_max_path_id { +5099 return Err(TransportError::PROTOCOL_VIOLATION( +5100 "PATH_NEW_CONNECTION_ID contains path_id exceeding current max", +5101 )); +5102 } +5103 path_id +5104 } else { +5105 PathId::ZERO +5106 }; +5107 +5108 if let Some(ref path_id) = frame.path_id { +5109 span.record("path", tracing::field::display(&path_id)); +5110 } +5111 +5112 if self.abandoned_paths.contains(&path_id) { +5113 trace!("ignoring issued CID for abandoned path"); +5114 continue; +5115 } +5116 let remote_cids = self +5117 .remote_cids +5118 .entry(path_id) +5119 .or_insert_with(|| CidQueue::new(frame.id)); +5120 if remote_cids.active().is_empty() { +5121 return Err(TransportError::PROTOCOL_VIOLATION( +5122 "NEW_CONNECTION_ID when CIDs aren't in use", +5123 )); +5124 } +5125 if frame.retire_prior_to > frame.sequence { +5126 return Err(TransportError::PROTOCOL_VIOLATION( +5127 "NEW_CONNECTION_ID retiring unissued CIDs", +5128 )); +5129 } +5130 +5131 use crate::cid_queue::InsertError; +5132 match remote_cids.insert(frame) { +5133 Ok(None) => { +5134 self.open_nat_traversed_paths(now); +5135 } +5136 Ok(Some((retired, reset_token))) => { +5137 let pending_retired = +5138 &mut self.spaces[SpaceId::Data].pending.retire_cids; +5139 /// Ensure `pending_retired` cannot grow without bound. Limit is +5140 /// somewhat arbitrary but very permissive. +5141 const MAX_PENDING_RETIRED_CIDS: u64 = CidQueue::LEN as u64 * 10; +5142 // We don't bother counting in-flight frames because those are bounded +5143 // by congestion control. +5144 if (pending_retired.len() as u64) +5145 .saturating_add(retired.end.saturating_sub(retired.start)) +5146 > MAX_PENDING_RETIRED_CIDS +5147 { +5148 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR( +5149 "queued too many retired CIDs", +5150 )); +5151 } +5152 pending_retired.extend(retired.map(|seq| (path_id, seq))); +5153 self.set_reset_token(path_id, network_path.remote, reset_token); +5154 self.open_nat_traversed_paths(now); +5155 } +5156 Err(InsertError::ExceedsLimit) => { +5157 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR("")); +5158 } +5159 Err(InsertError::Retired) => { +5160 trace!("discarding already-retired"); +5161 // RETIRE_CONNECTION_ID might not have been previously sent if e.g. a +5162 // range of connection IDs larger than the active connection ID limit +5163 // was retired all at once via retire_prior_to. +5164 self.spaces[SpaceId::Data] +5165 .pending +5166 .retire_cids +5167 .push((path_id, frame.sequence)); +5168 continue; +5169 } +5170 }; +5171 +5172 if self.side.is_server() +5173 && path_id == PathId::ZERO +5174 && self +5175 .remote_cids +5176 .get(&PathId::ZERO) +5177 .map(|cids| cids.active_seq() == 0) +5178 .unwrap_or_default() +5179 { +5180 // We're a server still using the initial remote CID for the client, so +5181 // let's switch immediately to enable clientside stateless resets. +5182 self.update_remote_cid(PathId::ZERO); +5183 } +5184 } +5185 Frame::NewToken(NewToken { token }) => { +5186 let ConnectionSide::Client { +5187 token_store, +5188 server_name, +5189 .. +5190 } = &self.side +5191 else { +5192 return Err(TransportError::PROTOCOL_VIOLATION("client sent NEW_TOKEN")); +5193 }; +5194 if token.is_empty() { +5195 return Err(TransportError::FRAME_ENCODING_ERROR("empty token")); +5196 } +5197 trace!("got new token"); +5198 token_store.insert(server_name, token); +5199 } +5200 Frame::Datagram(datagram) => { +5201 if self +5202 .datagrams +5203 .received(datagram, &self.config.datagram_receive_buffer_size)? +5204 { +5205 self.events.push_back(Event::DatagramReceived); +5206 } +5207 } +5208 Frame::AckFrequency(ack_frequency) => { +5209 // This frame can only be sent in the Data space +5210 +5211 if !self.ack_frequency.ack_frequency_received(&ack_frequency)? { +5212 // The AckFrequency frame is stale (we have already received a more +5213 // recent one) +5214 continue; +5215 } +5216 +5217 // Update the params for all of our paths +5218 for (path_id, space) in self.spaces[SpaceId::Data].number_spaces.iter_mut() { +5219 space.pending_acks.set_ack_frequency_params(&ack_frequency); +5220 +5221 // Our `max_ack_delay` has been updated, so we may need to adjust +5222 // its associated timeout. +5223 // Packets received on abandoned paths are always acknowledged immediately. +5224 if !self.abandoned_paths.contains(path_id) +5225 && let Some(timeout) = space +5226 .pending_acks +5227 .max_ack_delay_timeout(self.ack_frequency.max_ack_delay) +5228 { +5229 self.timers.set( +5230 Timer::PerPath(*path_id, PathTimer::MaxAckDelay), +5231 timeout, +5232 self.qlog.with_time(now), +5233 ); +5234 } +5235 } +5236 } +5237 Frame::ImmediateAck => { +5238 // This frame can only be sent in the Data space +5239 for pns in self.spaces[SpaceId::Data].iter_paths_mut() { +5240 pns.pending_acks.set_immediate_ack_required(); +5241 } +5242 } +5243 Frame::HandshakeDone => { +5244 if self.side.is_server() { +5245 return Err(TransportError::PROTOCOL_VIOLATION( +5246 "client sent HANDSHAKE_DONE", +5247 )); +5248 } +5249 if self.crypto_state.has_keys(EncryptionLevel::Handshake) { +5250 self.discard_space(now, SpaceKind::Handshake); +5251 self.events.push_back(Event::HandshakeConfirmed); +5252 trace!("handshake confirmed"); +5253 } +5254 } +5255 Frame::ObservedAddr(observed) => { +5256 // check if params allows the peer to send report and this node to receive it +5257 trace!(seq_no = %observed.seq_no, ip = %observed.ip, port = observed.port); +5258 if !self +5259 .peer_params +5260 .address_discovery_role +5261 .should_report(&self.config.address_discovery_role) +5262 { +5263 return Err(TransportError::PROTOCOL_VIOLATION( +5264 "received OBSERVED_ADDRESS frame when not negotiated", +5265 )); +5266 } +5267 // must only be sent in data space +5268 if packet.header.space() != SpaceKind::Data { +5269 return Err(TransportError::PROTOCOL_VIOLATION( +5270 "OBSERVED_ADDRESS frame outside data space", +5271 )); +5272 } +5273 +5274 let path = self.path_data_mut(path_id); +5275 if network_path == path.network_path { +5276 if let Some(updated) = path.update_observed_addr_report(observed) +5277 && path.open_status == paths::OpenStatus::Informed +5278 { +5279 self.events.push_back(Event::Path(PathEvent::ObservedAddr { +5280 id: path_id, +5281 addr: updated, +5282 })); +5283 // otherwise the event is reported when the path is deemed open +5284 } +5285 } else { +5286 // include in migration +5287 migration_observed_addr = Some(observed) +5288 } +5289 } +5290 Frame::PathAbandon(frame::PathAbandon { +5291 path_id, +5292 error_code, +5293 }) => { +5294 span.record("path", tracing::field::display(&path_id)); +5295 match self.close_path_inner( +5296 now, +5297 path_id, +5298 PathAbandonReason::RemoteAbandoned { +5299 error_code: error_code.into(), +5300 }, +5301 ) { +5302 Ok(()) => { +5303 trace!("peer abandoned path"); +5304 } +5305 Err(ClosePathError::ClosedPath) => { +5306 trace!("peer abandoned already closed path"); +5307 } +5308 Err(ClosePathError::MultipathNotNegotiated) => { +5309 return Err(TransportError::PROTOCOL_VIOLATION( +5310 "received PATH_ABANDON frame when multipath was not negotiated", +5311 )); +5312 } +5313 Err(ClosePathError::LastOpenPath) => { +5314 // Not reachable: close_path_inner allows remote abandons +5315 // for the last path. But handle gracefully just in case. +5316 error!( +5317 "peer abandoned last path but close_path_inner returned LastOpenPath" +5318 ); +5319 } +5320 }; +5321 +5322 // Start draining the path if it still exists and hasn't started draining yet. +5323 if let Some(path) = self.paths.get_mut(&path_id) +5324 && !mem::replace(&mut path.data.draining, true) +5325 { +5326 let ack_delay = self.ack_frequency.max_ack_delay_for_pto(); +5327 let pto = path.data.rtt.pto_base() + ack_delay; +5328 self.timers.set( +5329 Timer::PerPath(path_id, PathTimer::PathDrained), +5330 now + 3 * pto, +5331 self.qlog.with_time(now), +5332 ); +5333 +5334 self.set_max_path_id(now, self.local_max_path_id.saturating_add(1u8)); +5335 } +5336 } +5337 Frame::PathStatusAvailable(info) => { +5338 span.record("path", tracing::field::display(&info.path_id)); +5339 if self.is_multipath_negotiated() { +5340 self.on_path_status( +5341 info.path_id, +5342 PathStatus::Available, +5343 info.status_seq_no, +5344 ); +5345 } else { +5346 return Err(TransportError::PROTOCOL_VIOLATION( +5347 "received PATH_STATUS_AVAILABLE frame when multipath was not negotiated", +5348 )); +5349 } +5350 } +5351 Frame::PathStatusBackup(info) => { +5352 span.record("path", tracing::field::display(&info.path_id)); +5353 if self.is_multipath_negotiated() { +5354 self.on_path_status(info.path_id, PathStatus::Backup, info.status_seq_no); +5355 } else { +5356 return Err(TransportError::PROTOCOL_VIOLATION( +5357 "received PATH_STATUS_BACKUP frame when multipath was not negotiated", +5358 )); +5359 } +5360 } +5361 Frame::MaxPathId(frame::MaxPathId(path_id)) => { +5362 span.record("path", tracing::field::display(&path_id)); +5363 if !self.is_multipath_negotiated() { +5364 return Err(TransportError::PROTOCOL_VIOLATION( +5365 "received MAX_PATH_ID frame when multipath was not negotiated", +5366 )); +5367 } +5368 // frames that do not increase the path id are ignored +5369 if path_id > self.remote_max_path_id { +5370 self.remote_max_path_id = path_id; +5371 self.issue_first_path_cids(now); +5372 self.open_nat_traversed_paths(now); +5373 } +5374 } +5375 Frame::PathsBlocked(frame::PathsBlocked(max_path_id)) => { +5376 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST +5377 // be treated as a connection error of type PROTOCOL_VIOLATION. +5378 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> +5379 if self.is_multipath_negotiated() { +5380 if max_path_id > self.local_max_path_id { +5381 return Err(TransportError::PROTOCOL_VIOLATION( +5382 "PATHS_BLOCKED maximum path identifier was larger than local maximum", +5383 )); +5384 } +5385 debug!("received PATHS_BLOCKED({:?})", max_path_id); +5386 // TODO(@divma): ensure max concurrent paths +5387 } else { +5388 return Err(TransportError::PROTOCOL_VIOLATION( +5389 "received PATHS_BLOCKED frame when not multipath was not negotiated", +5390 )); +5391 } +5392 } +5393 Frame::PathCidsBlocked(frame::PathCidsBlocked { path_id, next_seq }) => { +5394 // Nothing to do. This is recorded in the frame stats, but otherwise we +5395 // always issue all CIDs we're allowed to issue, so either this is an +5396 // impatient peer or a bug on our side. +5397 +5398 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST +5399 // be treated as a connection error of type PROTOCOL_VIOLATION. +5400 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> +5401 if self.is_multipath_negotiated() { +5402 if path_id > self.local_max_path_id { +5403 return Err(TransportError::PROTOCOL_VIOLATION( +5404 "PATH_CIDS_BLOCKED path identifier was larger than local maximum", +5405 )); +5406 } +5407 if next_seq.0 +5408 > self +5409 .local_cid_state +5410 .get(&path_id) +5411 .map(|cid_state| cid_state.active_seq().1 + 1) +5412 .unwrap_or_default() +5413 { +5414 return Err(TransportError::PROTOCOL_VIOLATION( +5415 "PATH_CIDS_BLOCKED next sequence number larger than in local state", +5416 )); +5417 } +5418 debug!(%path_id, %next_seq, "received PATH_CIDS_BLOCKED"); +5419 } else { +5420 return Err(TransportError::PROTOCOL_VIOLATION( +5421 "received PATH_CIDS_BLOCKED frame when not multipath was not negotiated", +5422 )); +5423 } +5424 } +5425 Frame::AddAddress(addr) => { +5426 let client_state = match self.n0_nat_traversal.client_side_mut() { +5427 Ok(state) => state, +5428 Err(err) => { +5429 return Err(TransportError::PROTOCOL_VIOLATION(format!( +5430 "Nat traversal(ADD_ADDRESS): {err}" +5431 ))); +5432 } +5433 }; +5434 +5435 if !client_state.check_remote_address(&addr) { +5436 // if the address is not valid we flag it, but update anyway +5437 warn!(?addr, "server sent illegal ADD_ADDRESS frame"); +5438 } +5439 +5440 match client_state.add_remote_address(addr) { +5441 Ok(maybe_added) => { +5442 if let Some(added) = maybe_added { +5443 self.events.push_back(Event::NatTraversal( +5444 n0_nat_traversal::Event::AddressAdded(added), +5445 )); +5446 } +5447 } +5448 Err(e) => { +5449 warn!(%e, "failed to add remote address") +5450 } +5451 } +5452 } +5453 Frame::RemoveAddress(addr) => { +5454 let client_state = match self.n0_nat_traversal.client_side_mut() { +5455 Ok(state) => state, +5456 Err(err) => { +5457 return Err(TransportError::PROTOCOL_VIOLATION(format!( +5458 "Nat traversal(REMOVE_ADDRESS): {err}" +5459 ))); +5460 } +5461 }; +5462 if let Some(removed_addr) = client_state.remove_remote_address(addr) { +5463 self.events.push_back(Event::NatTraversal( +5464 n0_nat_traversal::Event::AddressRemoved(removed_addr), +5465 )); +5466 } +5467 } +5468 Frame::ReachOut(reach_out) => { +5469 let ipv6 = self.is_ipv6(); +5470 let server_state = match self.n0_nat_traversal.server_side_mut() { +5471 Ok(state) => state, +5472 Err(err) => { +5473 return Err(TransportError::PROTOCOL_VIOLATION(format!( +5474 "Nat traversal(REACH_OUT): {err}" +5475 ))); +5476 } +5477 }; +5478 +5479 let round_before = server_state.current_round(); +5480 +5481 if let Err(err) = server_state.handle_reach_out(reach_out, ipv6) { +5482 return Err(TransportError::PROTOCOL_VIOLATION(format!( +5483 "Nat traversal(REACH_OUT): {err}" +5484 ))); +5485 } +5486 +5487 if server_state.current_round() > round_before { +5488 // A new round was started, reset the NAT probe retry timer. +5489 let delay = RttEstimator::new(self.config.initial_rtt).pto_base() * 2 / 3; +5490 self.timers.set( +5491 Timer::Conn(ConnTimer::NatTraversalProbeRetry), +5492 now + delay, +5493 self.qlog.with_time(now), +5494 ); +5495 } +5496 } +5497 } +5498 } +5499 +5500 let space = self.spaces[SpaceId::Data].for_path(path_id); +5501 if space +5502 .pending_acks +5503 .packet_received(now, number, ack_eliciting, &space.dedup) +5504 { +5505 if self.abandoned_paths.contains(&path_id) { +5506 // § 3.4.3 QUIC-MULTIPATH: promptly send ACKs for packets received from +5507 // abandoned paths. +5508 space.pending_acks.set_immediate_ack_required(); +5509 } else { +5510 self.timers.set( +5511 Timer::PerPath(path_id, PathTimer::MaxAckDelay), +5512 now + self.ack_frequency.max_ack_delay, +5513 self.qlog.with_time(now), +5514 ); +5515 } +5516 } +5517 +5518 // Issue stream ID credit due to ACKs of outgoing finish/resets and incoming finish/resets +5519 // on stopped streams. Incoming finishes/resets on open streams are not handled here as they +5520 // are only freed, and hence only issue credit, once the application has been notified +5521 // during a read on the stream. +5522 let pending = &mut self.spaces[SpaceId::Data].pending; +5523 self.streams.queue_max_stream_id(pending); +5524 +5525 if let Some(reason) = close { +5526 self.state.move_to_draining(Some(reason.into())); +5527 self.connection_close_pending = true; +5528 } +5529 +5530 // For Multipath any packet triggers migration. For RFC9000 or QNT (+ Multipath) +5531 // only non-probing packets trigger migration. +5532 let migrate_on_any_packet = +5533 self.is_multipath_negotiated() && !self.n0_nat_traversal.is_negotiated(); +5534 +5535 // Only migrate if this is the largest packet number seen. +5536 let is_largest_received_pn = Some(number) +5537 == self.spaces[SpaceId::Data] +5538 .for_path(path_id) +5539 .largest_received_packet_number; +5540 +5541 // If we receive a non-probing packet on a new local IP that means we had a NAT +5542 // rebinding-like migration. We update our local address but do not otherwise +5543 // validate the new path, we only need to validate the path if the peer migrates per +5544 // RFC9000 §9: https://www.rfc-editor.org/rfc/rfc9000.html#section-9-4 +5545 if (migrate_on_any_packet || !is_probing_packet) +5546 && is_largest_received_pn +5547 && self.local_ip_may_migrate() +5548 && let Some(new_local_ip) = network_path.local_ip +5549 { +5550 let path_data = self.path_data_mut(path_id); +5551 if path_data +5552 .network_path +5553 .local_ip +5554 .is_some_and(|ip| ip != new_local_ip) +5555 { +5556 debug!( +5557 %path_id, +5558 new_4tuple = %network_path, +5559 prev_4tuple = %path_data.network_path, +5560 "local address passive migration" +5561 ); +5562 } +5563 path_data.network_path.local_ip = Some(new_local_ip) +5564 } +5565 +5566 // If the peer migrated to a new address, trigger migration. +5567 if (migrate_on_any_packet || !is_probing_packet) +5568 && is_largest_received_pn +5569 && network_path.remote != self.path_data(path_id).network_path.remote +5570 && self.remote_may_migrate() +5571 { +5572 self.migrate(path_id, now, network_path, migration_observed_addr); +5573 // Break linkability, if possible +5574 self.update_remote_cid(path_id); +5575 self.spin = false; +5576 } +5577 +5578 Ok(()) +5579 } +5580 +5581 /// Opens any paths that have been successfully NAT traversed. +5582 fn open_nat_traversed_paths(&mut self, now: Instant) { +5583 while let Some(network_path) = self +5584 .n0_nat_traversal +5585 .client_side_mut() +5586 .ok() +5587 .and_then(|s| s.pop_pending_path_open()) +5588 { +5589 match self.open_path_ensure(network_path, PathStatus::Backup, now) { +5590 Ok((path_id, already_existed)) => { +5591 debug!( +5592 %path_id, +5593 ?network_path, +5594 new_path = !already_existed, +5595 "Opened NAT traversal path", +5596 ); +5597 } +5598 Err(err) => match err { +5599 PathError::MultipathNotNegotiated +5600 | PathError::ServerSideNotAllowed +5601 | PathError::ValidationFailed +5602 | PathError::InvalidRemoteAddress(_) => { +5603 error!( +5604 ?err, +5605 ?network_path, +5606 "Failed to open path for successful NAT traversal" +5607 ); +5608 } +5609 PathError::MaxPathIdReached | PathError::RemoteCidsExhausted => { +5610 // Temporary error, put back. +5611 self.n0_nat_traversal +5612 .client_side_mut() +5613 .map(|s| s.push_pending_path_open(network_path)) +5614 .ok(); +5615 debug!( +5616 ?err, +5617 ?network_path, +5618 "Blocked opening NAT traversal path, enqueued" +5619 ); +5620 return; +5621 } +5622 }, +5623 } +5624 } +5625 } +5626 +5627 /// Migrates the 4-tuple of the path. +5628 /// +5629 /// This creates a new [`PathData`] for the migrated path and stores the previous +5630 /// [`PathData`] in [`PathState::prev`]. +5631 fn migrate( +5632 &mut self, +5633 path_id: PathId, +5634 now: Instant, +5635 network_path: FourTuple, +5636 observed_addr: Option<ObservedAddr>, +5637 ) { +5638 trace!( +5639 new_4tuple = %network_path, +5640 prev_4tuple = %self.path_data(path_id).network_path, +5641 %path_id, +5642 "migration initiated", +5643 ); +5644 self.path_generation_counter = self.path_generation_counter.wrapping_add(1); +5645 // TODO(@divma): conditions for path migration in multipath are very specific, check them +5646 // again to prevent path migrations that should actually create a new path +5647 +5648 // Reset rtt/congestion state for new path unless it looks like a NAT rebinding. +5649 // Note that the congestion window will not grow until validation terminates. Helps mitigate +5650 // amplification attacks performed by spoofing source addresses. +5651 let prev_pto = self.pto(SpaceKind::Data, path_id); +5652 let path = self.paths.get_mut(&path_id).expect("known path"); +5653 let mut new_path_data = if network_path.remote.is_ipv4() +5654 && network_path.remote.ip() == path.data.network_path.remote.ip() +5655 { +5656 PathData::from_previous(network_path, &path.data, self.path_generation_counter, now) +5657 } else { +5658 let peer_max_udp_payload_size = +5659 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()) +5660 .unwrap_or(u16::MAX); +5661 PathData::new( +5662 network_path, +5663 self.allow_mtud, +5664 Some(peer_max_udp_payload_size), +5665 self.path_generation_counter, +5666 now, +5667 &self.config, +5668 ) +5669 }; +5670 new_path_data.last_observed_addr_report = path.data.last_observed_addr_report.clone(); +5671 if let Some(report) = observed_addr +5672 && let Some(updated) = new_path_data.update_observed_addr_report(report) +5673 { +5674 tracing::info!("adding observed addr event from migration"); +5675 self.events.push_back(Event::Path(PathEvent::ObservedAddr { +5676 id: path_id, +5677 addr: updated, +5678 })); +5679 } +5680 new_path_data.pending_on_path_challenge = true; +5681 +5682 let mut prev_path_data = mem::replace(&mut path.data, new_path_data); +5683 +5684 // Only store this as previous path if it was validated. For all we know there could +5685 // already be a previous path stored which might have been validated in the past, +5686 // which is more valuable than one that's not yet validated. +5687 // +5688 // With multipath it is possible that there are no remote CIDs for the path ID +5689 // yet. In this case we would never have sent on this path yet and would not be able +5690 // to send a PATH_CHALLENGE either, which is currently a fire-and-forget affair +5691 // anyway. So don't store such a path either. +5692 if !prev_path_data.validated +5693 && let Some(cid) = self.remote_cids.get(&path_id).map(CidQueue::active) +5694 { +5695 prev_path_data.pending_on_path_challenge = true; +5696 // We haven't updated the remote CID yet, this captures the remote CID we were using on +5697 // the previous path. +5698 path.prev = Some((cid, prev_path_data)); +5699 } +5700 +5701 // We need to re-assign the correct remote to this path in qlog +5702 self.qlog.emit_tuple_assigned(path_id, network_path, now); +5703 +5704 self.timers.set( +5705 Timer::PerPath(path_id, PathTimer::PathValidationFailed), +5706 now + 3 * cmp::max(self.pto(SpaceKind::Data, path_id), prev_pto), +5707 self.qlog.with_time(now), +5708 ); +5709 } +5710 +5711 /// Handle a change in the local address, i.e. an active migration +5712 /// +5713 /// In the general (non-multipath) case, paths will perform a RFC9000 migration and be pinged +5714 /// for a liveness check. This is the behaviour of a path assumed to be recoverable, even if +5715 /// this is not the case. +5716 /// +5717 /// Clients in a connection in which multipath has been negotiated should migrate paths to new +5718 /// [`PathId`]s. For paths that are known to be non-recoverable can be migrated to a new +5719 /// [`PathId`] by closing the current path, and opening a new one to the same remote. Treating +5720 /// paths as non recoverable when necessary accelerates connectivity re-establishment, or might +5721 /// allow it altogether. +5722 /// +5723 /// The optional `hint` allows callers to indicate when paths are non-recoverable and should be +5724 /// migrated to new a [`PathId`]. +5725 // NOTE: only clients are allowed to migrate, but generally dealing with RFC9000 migrations is +5726 // lacking <https://github.com/n0-computer/noq/issues/364> +5727 pub fn handle_network_change(&mut self, hint: Option<&dyn NetworkChangeHint>, now: Instant) { +5728 debug!("network changed"); +5729 if self.state.is_drained() { +5730 return; +5731 } +5732 if self.highest_space < SpaceKind::Data { +5733 for path in self.paths.values_mut() { +5734 // Clear the local address for it to be obtained from the socket again. +5735 path.data.network_path.local_ip = None; +5736 } +5737 +5738 self.update_remote_cid(PathId::ZERO); +5739 self.ping(); 5740 -5741 if attempt_to_recover { -5742 recoverable_paths.push((*path_id, remote)); -5743 } else { -5744 non_recoverable_paths.push((*path_id, remote, path.data.local_status())) -5745 } -5746 } -5747 -5748 /* NON RECOVERABLE PATHS */ -5749 // This are handled first, so that in case the treatment intended for these fails, we can -5750 // go the recoverable route instead. -5751 -5752 // Decide if we need to close first or open first in the multipath case. -5753 // - Opening first has a higher risk of getting limited by the negotiated MAX_PATH_ID. -5754 // - Closing first risks this being the only open path. -5755 // We prefer closing paths first unless we identify this is the last open path. -5756 let open_first = open_paths == non_recoverable_paths.len(); -5757 -5758 for (path_id, remote, status) in non_recoverable_paths.into_iter() { -5759 let network_path = FourTuple { -5760 remote, -5761 local_ip: None, /* allow the local ip to be discovered */ -5762 }; +5741 return; +5742 } +5743 +5744 // Paths that can't recover so a new path should be open instead. If multipath is not +5745 // negotiated, this will be empty. +5746 let mut non_recoverable_paths = Vec::default(); +5747 let mut recoverable_paths = Vec::default(); +5748 let mut open_paths = 0; +5749 +5750 let is_multipath_negotiated = self.is_multipath_negotiated(); +5751 let is_client = self.side().is_client(); +5752 let immediate_ack_allowed = self.peer_supports_ack_frequency(); +5753 +5754 for (path_id, path) in self.paths.iter_mut() { +5755 if self.abandoned_paths.contains(path_id) { +5756 continue; +5757 } +5758 open_paths += 1; +5759 +5760 // Read the network path BEFORE clearing local_ip, so the hint can +5761 // check which interface the path was using. +5762 let network_path = path.data.network_path; 5763 -5764 if open_first && let Err(e) = self.open_path(network_path, status, now) { -5765 if self.side().is_client() { -5766 debug!(%e, "Failed to open new path for network change"); -5767 } -5768 // if this fails, let the path try to recover itself -5769 recoverable_paths.push((path_id, remote)); -5770 continue; -5771 } -5772 -5773 if let Err(e) = -5774 self.close_path_inner(now, path_id, PathAbandonReason::UnusableAfterNetworkChange) -5775 { -5776 debug!(%e,"Failed to close unrecoverable path after network change"); -5777 recoverable_paths.push((path_id, remote)); -5778 continue; -5779 } -5780 -5781 if !open_first && let Err(e) = self.open_path(network_path, status, now) { -5782 // Path has already been closed if we got here. Since the path was not recoverable, -5783 // this might be desirable in any case, because other paths exist (!open_first) and -5784 // this was is considered non recoverable -5785 debug!(%e,"Failed to open new path for network change"); -5786 } -5787 } -5788 -5789 /* RECOVERABLE PATHS */ -5790 -5791 for (path_id, remote) in recoverable_paths.into_iter() { -5792 // Schedule a Ping for a liveness check. -5793 if let Some(path_space) = self.spaces[SpaceId::Data].number_spaces.get_mut(&path_id) { -5794 path_space.ping_pending = true; -5795 -5796 if immediate_ack_allowed { -5797 path_space.immediate_ack_pending = true; -5798 } -5799 } -5800 -5801 // Reset PTO backoff so retransmits resume promptly. Congestion controller and -5802 // RTT are intentionally preserved for recoverable paths. We explicitly allow -5803 // this reset also during the handshake, so do not check -5804 // Self::peer_competed_handshake_address_validation. -5805 if let Some(path) = self.paths.get_mut(&path_id) { -5806 path.data.pto_count = 0; -5807 } -5808 self.set_loss_detection_timer(now, path_id); -5809 -5810 let Some((reset_token, retired)) = -5811 self.remote_cids.get_mut(&path_id).and_then(CidQueue::next) -5812 else { -5813 continue; -5814 }; -5815 -5816 // Retire the current remote CID and any CIDs we had to skip. -5817 self.spaces[SpaceId::Data] -5818 .pending -5819 .retire_cids -5820 .extend(retired.map(|seq| (path_id, seq))); -5821 -5822 debug_assert!(!self.state.is_drained()); // required for endpoint_events, checked above -5823 self.endpoint_events -5824 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); -5825 } -5826 } -5827 -5828 /// Switch to a previously unused remote connection ID, if possible -5829 fn update_remote_cid(&mut self, path_id: PathId) { -5830 let Some((reset_token, retired)) = self -5831 .remote_cids -5832 .get_mut(&path_id) -5833 .and_then(|cids| cids.next()) -5834 else { -5835 return; -5836 }; +5764 // Clear the local address for it to be obtained from the socket again. This applies to +5765 // all paths, regardless of being considered recoverable or not +5766 path.data.network_path.local_ip = None; +5767 let remote = network_path.remote; +5768 +5769 // Without multipath, the connection tries to recover the single path, whereas with +5770 // multipath, even in a single-path scenario, we attempt to migrate the path to a new +5771 // PathId. +5772 let attempt_to_recover = if is_multipath_negotiated { +5773 // Use the hint to determine if the path can recover. When no hint is +5774 // provided, clients default to non-recoverable (abandon and re-open) +5775 // while servers default to recoverable (attempt in-place recovery). +5776 hint.map(|h| h.is_path_recoverable(*path_id, network_path)) +5777 .unwrap_or(!is_client) +5778 } else { +5779 // In the non multipath case, we try to recover the single active path +5780 true +5781 }; +5782 +5783 if attempt_to_recover { +5784 recoverable_paths.push((*path_id, remote)); +5785 } else { +5786 non_recoverable_paths.push((*path_id, remote, path.data.local_status())) +5787 } +5788 } +5789 +5790 /* NON RECOVERABLE PATHS */ +5791 // This are handled first, so that in case the treatment intended for these fails, we can +5792 // go the recoverable route instead. +5793 +5794 // Decide if we need to close first or open first in the multipath case. +5795 // - Opening first has a higher risk of getting limited by the negotiated MAX_PATH_ID. +5796 // - Closing first risks this being the only open path. +5797 // We prefer closing paths first unless we identify this is the last open path. +5798 let open_first = open_paths == non_recoverable_paths.len(); +5799 +5800 for (path_id, remote, status) in non_recoverable_paths.into_iter() { +5801 let network_path = FourTuple { +5802 remote, +5803 local_ip: None, /* allow the local ip to be discovered */ +5804 }; +5805 +5806 if open_first && let Err(e) = self.open_path(network_path, status, now) { +5807 if self.side().is_client() { +5808 debug!(%e, "Failed to open new path for network change"); +5809 } +5810 // if this fails, let the path try to recover itself +5811 recoverable_paths.push((path_id, remote)); +5812 continue; +5813 } +5814 +5815 if let Err(e) = +5816 self.close_path_inner(now, path_id, PathAbandonReason::UnusableAfterNetworkChange) +5817 { +5818 debug!(%e,"Failed to close unrecoverable path after network change"); +5819 recoverable_paths.push((path_id, remote)); +5820 continue; +5821 } +5822 +5823 if !open_first && let Err(e) = self.open_path(network_path, status, now) { +5824 // Path has already been closed if we got here. Since the path was not recoverable, +5825 // this might be desirable in any case, because other paths exist (!open_first) and +5826 // this was is considered non recoverable +5827 debug!(%e,"Failed to open new path for network change"); +5828 } +5829 } +5830 +5831 /* RECOVERABLE PATHS */ +5832 +5833 for (path_id, remote) in recoverable_paths.into_iter() { +5834 // Schedule a Ping for a liveness check. +5835 if let Some(path_space) = self.spaces[SpaceId::Data].number_spaces.get_mut(&path_id) { +5836 path_space.ping_pending = true; 5837 -5838 // Retire the current remote CID and any CIDs we had to skip. -5839 self.spaces[SpaceId::Data] -5840 .pending -5841 .retire_cids -5842 .extend(retired.map(|seq| (path_id, seq))); -5843 let remote = self.path_data(path_id).network_path.remote; -5844 self.set_reset_token(path_id, remote, reset_token); -5845 } -5846 -5847 /// Sends this reset token to the endpoint -5848 /// -5849 /// The endpoint needs to know the reset tokens issued by the peer, so that if the peer -5850 /// sends a reset token it knows to route it to this connection. See RFC 9000 section -5851 /// 10.3. Stateless Reset. -5852 /// -5853 /// Reset tokens are different for each path, the endpoint identifies paths by peer -5854 /// socket address however, not by path ID. -5855 fn set_reset_token(&mut self, path_id: PathId, remote: SocketAddr, reset_token: ResetToken) { -5856 debug_assert!(!self.state.is_drained()); // required for endpoint events, set_reset_token is never called for drained connections -5857 self.endpoint_events -5858 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); -5859 -5860 // During the handshake the server sends a reset token in the transport -5861 // parameters. When we are the client and we receive the reset token during the -5862 // handshake we want this to affect our peer transport parameters. -5863 // TODO(flub): Pretty sure this is pointless, the entire params is overwritten -5864 // shortly after this was called. And then the params don't have this anymore. -5865 if path_id == PathId::ZERO { -5866 self.peer_params.stateless_reset_token = Some(reset_token); +5838 if immediate_ack_allowed { +5839 path_space.immediate_ack_pending = true; +5840 } +5841 } +5842 +5843 // Reset PTO backoff so retransmits resume promptly. Congestion controller and +5844 // RTT are intentionally preserved for recoverable paths. We explicitly allow +5845 // this reset also during the handshake, so do not check +5846 // Self::peer_competed_handshake_address_validation. +5847 if let Some(path) = self.paths.get_mut(&path_id) { +5848 path.data.pto_count = 0; +5849 } +5850 self.set_loss_detection_timer(now, path_id); +5851 +5852 let Some((reset_token, retired)) = +5853 self.remote_cids.get_mut(&path_id).and_then(CidQueue::next) +5854 else { +5855 continue; +5856 }; +5857 +5858 // Retire the current remote CID and any CIDs we had to skip. +5859 self.spaces[SpaceId::Data] +5860 .pending +5861 .retire_cids +5862 .extend(retired.map(|seq| (path_id, seq))); +5863 +5864 debug_assert!(!self.state.is_drained()); // required for endpoint_events, checked above +5865 self.endpoint_events +5866 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); 5867 } 5868 } 5869 -5870 /// Issue an initial set of connection IDs to the peer upon connection -5871 fn issue_first_cids(&mut self, now: Instant) { -5872 if self -5873 .local_cid_state -5874 .get(&PathId::ZERO) -5875 .expect("PathId::ZERO exists when the connection is created") -5876 .cid_len() -5877 == 0 -5878 { -5879 return; -5880 } -5881 -5882 // Subtract 1 to account for the CID we supplied while handshaking -5883 let mut n = self.peer_params.issue_cids_limit() - 1; -5884 if let ConnectionSide::Server { server_config } = &self.side -5885 && server_config.has_preferred_address() -5886 { -5887 // We also sent a CID in the transport parameters -5888 n -= 1; -5889 } -5890 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events -5891 self.endpoint_events -5892 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); -5893 } -5894 -5895 /// Issues an initial set of CIDs for paths that have not yet had any CIDs issued -5896 /// -5897 /// Later CIDs are issued when CIDs expire or are retired by the peer. -5898 fn issue_first_path_cids(&mut self, now: Instant) { -5899 if let Some(max_path_id) = self.max_path_id() { -5900 let mut path_id = self.max_path_id_with_cids.next(); -5901 while path_id <= max_path_id { -5902 self.endpoint_events -5903 .push_back(EndpointEventInner::NeedIdentifiers( -5904 path_id, -5905 now, -5906 self.peer_params.issue_cids_limit(), -5907 )); -5908 path_id = path_id.next(); -5909 } -5910 self.max_path_id_with_cids = max_path_id; -5911 } -5912 } -5913 -5914 /// Populates a packet with frames -5915 /// -5916 /// This tries to fit as many frames as possible into the packet. -5917 /// -5918 /// *path_exclusive_only* means to only build frames which can only be sent on this -5919 /// *path. This is used in multipath for backup paths while there is still an active -5920 /// *path. -5921 fn populate_packet<'a, 'b>( -5922 &mut self, -5923 now: Instant, -5924 space_id: SpaceId, -5925 path_id: PathId, -5926 scheduling_info: &PathSchedulingInfo, -5927 builder: &mut PacketBuilder<'a, 'b>, -5928 ) { -5929 let is_multipath_negotiated = self.is_multipath_negotiated(); -5930 let space_has_keys = self.crypto_state.has_keys(space_id.encryption_level()); -5931 let is_0rtt = space_id == SpaceId::Data && !space_has_keys; -5932 let stats = &mut self.path_stats.for_path(path_id).frame_tx; -5933 let space = &mut self.spaces[space_id]; -5934 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; -5935 space -5936 .for_path(path_id) -5937 .pending_acks -5938 .maybe_ack_non_eliciting(); -5939 -5940 // HANDSHAKE_DONE -5941 if !is_0rtt -5942 && !scheduling_info.is_abandoned -5943 && scheduling_info.may_send_data -5944 && mem::replace(&mut space.pending.handshake_done, false) -5945 { -5946 builder.write_frame(frame::HandshakeDone, stats); -5947 } -5948 -5949 // PING -5950 if !scheduling_info.is_abandoned -5951 && mem::replace(&mut space.for_path(path_id).ping_pending, false) -5952 { -5953 builder.write_frame(frame::Ping, stats); -5954 } +5870 /// Switch to a previously unused remote connection ID, if possible +5871 fn update_remote_cid(&mut self, path_id: PathId) { +5872 let Some((reset_token, retired)) = self +5873 .remote_cids +5874 .get_mut(&path_id) +5875 .and_then(|cids| cids.next()) +5876 else { +5877 return; +5878 }; +5879 +5880 // Retire the current remote CID and any CIDs we had to skip. +5881 self.spaces[SpaceId::Data] +5882 .pending +5883 .retire_cids +5884 .extend(retired.map(|seq| (path_id, seq))); +5885 let remote = self.path_data(path_id).network_path.remote; +5886 self.set_reset_token(path_id, remote, reset_token); +5887 } +5888 +5889 /// Sends this reset token to the endpoint +5890 /// +5891 /// The endpoint needs to know the reset tokens issued by the peer, so that if the peer +5892 /// sends a reset token it knows to route it to this connection. See RFC 9000 section +5893 /// 10.3. Stateless Reset. +5894 /// +5895 /// Reset tokens are different for each path, the endpoint identifies paths by peer +5896 /// socket address however, not by path ID. +5897 fn set_reset_token(&mut self, path_id: PathId, remote: SocketAddr, reset_token: ResetToken) { +5898 debug_assert!(!self.state.is_drained()); // required for endpoint events, set_reset_token is never called for drained connections +5899 self.endpoint_events +5900 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); +5901 +5902 // During the handshake the server sends a reset token in the transport +5903 // parameters. When we are the client and we receive the reset token during the +5904 // handshake we want this to affect our peer transport parameters. +5905 // TODO(flub): Pretty sure this is pointless, the entire params is overwritten +5906 // shortly after this was called. And then the params don't have this anymore. +5907 if path_id == PathId::ZERO { +5908 self.peer_params.stateless_reset_token = Some(reset_token); +5909 } +5910 } +5911 +5912 /// Issue an initial set of connection IDs to the peer upon connection +5913 fn issue_first_cids(&mut self, now: Instant) { +5914 if self +5915 .local_cid_state +5916 .get(&PathId::ZERO) +5917 .expect("PathId::ZERO exists when the connection is created") +5918 .cid_len() +5919 == 0 +5920 { +5921 return; +5922 } +5923 +5924 // Subtract 1 to account for the CID we supplied while handshaking +5925 let mut n = self.peer_params.issue_cids_limit() - 1; +5926 if let ConnectionSide::Server { server_config } = &self.side +5927 && server_config.has_preferred_address() +5928 { +5929 // We also sent a CID in the transport parameters +5930 n -= 1; +5931 } +5932 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events +5933 self.endpoint_events +5934 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); +5935 } +5936 +5937 /// Issues an initial set of CIDs for paths that have not yet had any CIDs issued +5938 /// +5939 /// Later CIDs are issued when CIDs expire or are retired by the peer. +5940 fn issue_first_path_cids(&mut self, now: Instant) { +5941 if let Some(max_path_id) = self.max_path_id() { +5942 let mut path_id = self.max_path_id_with_cids.next(); +5943 while path_id <= max_path_id { +5944 self.endpoint_events +5945 .push_back(EndpointEventInner::NeedIdentifiers( +5946 path_id, +5947 now, +5948 self.peer_params.issue_cids_limit(), +5949 )); +5950 path_id = path_id.next(); +5951 } +5952 self.max_path_id_with_cids = max_path_id; +5953 } +5954 } 5955 -5956 // IMMEDIATE_ACK -5957 if !scheduling_info.is_abandoned -5958 && mem::replace(&mut space.for_path(path_id).immediate_ack_pending, false) -5959 { -5960 debug_assert_eq!( -5961 space_id, -5962 SpaceId::Data, -5963 "immediate acks must be sent in the data space" -5964 ); -5965 builder.write_frame(frame::ImmediateAck, stats); -5966 } -5967 -5968 // ACK -5969 if !scheduling_info.is_abandoned && scheduling_info.may_send_data { -5970 for path_id in space -5971 .number_spaces -5972 .iter_mut() -5973 .filter(|(_, pns)| pns.pending_acks.can_send()) -5974 .map(|(&path_id, _)| path_id) -5975 .collect::<Vec<_>>() -5976 { -5977 Self::populate_acks( -5978 now, -5979 self.receiving_ecn, -5980 path_id, -5981 space_id, -5982 space, -5983 is_multipath_negotiated, -5984 builder, -5985 stats, -5986 space_has_keys, -5987 ); -5988 } +5956 /// Populates a packet with frames +5957 /// +5958 /// This tries to fit as many frames as possible into the packet. +5959 /// +5960 /// *path_exclusive_only* means to only build frames which can only be sent on this +5961 /// *path. This is used in multipath for backup paths while there is still an active +5962 /// *path. +5963 fn populate_packet<'a, 'b>( +5964 &mut self, +5965 now: Instant, +5966 space_id: SpaceId, +5967 path_id: PathId, +5968 scheduling_info: &PathSchedulingInfo, +5969 builder: &mut PacketBuilder<'a, 'b>, +5970 ) { +5971 let is_multipath_negotiated = self.is_multipath_negotiated(); +5972 let space_has_keys = self.crypto_state.has_keys(space_id.encryption_level()); +5973 let is_0rtt = space_id == SpaceId::Data && !space_has_keys; +5974 let stats = &mut self.path_stats.for_path(path_id).frame_tx; +5975 let space = &mut self.spaces[space_id]; +5976 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; +5977 space +5978 .for_path(path_id) +5979 .pending_acks +5980 .maybe_ack_non_eliciting(); +5981 +5982 // HANDSHAKE_DONE +5983 if !is_0rtt +5984 && !scheduling_info.is_abandoned +5985 && scheduling_info.may_send_data +5986 && mem::replace(&mut space.pending.handshake_done, false) +5987 { +5988 builder.write_frame(frame::HandshakeDone, stats); 5989 } 5990 -5991 // ACK_FREQUENCY +5991 // PING 5992 if !scheduling_info.is_abandoned -5993 && scheduling_info.may_send_data -5994 && mem::replace(&mut space.pending.ack_frequency, false) -5995 { -5996 let sequence_number = self.ack_frequency.next_sequence_number(); +5993 && mem::replace(&mut space.for_path(path_id).ping_pending, false) +5994 { +5995 builder.write_frame(frame::Ping, stats); +5996 } 5997 -5998 // Safe to unwrap because this is always provided when ACK frequency is enabled -5999 let config = self.config.ack_frequency_config.as_ref().unwrap(); -6000 -6001 // Ensure the delay is within bounds to avoid a PROTOCOL_VIOLATION error -6002 let max_ack_delay = self.ack_frequency.candidate_max_ack_delay( -6003 path.rtt.get(), -6004 config, -6005 &self.peer_params, -6006 ); -6007 -6008 let frame = frame::AckFrequency { -6009 sequence: sequence_number, -6010 ack_eliciting_threshold: config.ack_eliciting_threshold, -6011 request_max_ack_delay: max_ack_delay.as_micros().try_into().unwrap_or(VarInt::MAX), -6012 reordering_threshold: config.reordering_threshold, -6013 }; -6014 builder.write_frame(frame, stats); -6015 -6016 self.ack_frequency -6017 .ack_frequency_sent(path_id, builder.packet_number, max_ack_delay); -6018 } -6019 -6020 // PATH_CHALLENGE -6021 if !scheduling_info.is_abandoned -6022 && space_id == SpaceId::Data -6023 && path.pending_on_path_challenge -6024 && !self.state.is_closed() -6025 && builder.frame_space_remaining() > frame::PathChallenge::SIZE_BOUND -6026 // we don't want to send new challenges if we are already closing -6027 { -6028 path.pending_on_path_challenge = false; -6029 -6030 let token = self.rng.random(); -6031 path.record_path_challenge_sent(now, token, path.network_path); -6032 // Generate a new challenge every time we send a new PATH_CHALLENGE -6033 let challenge = frame::PathChallenge(token); -6034 builder.write_frame(challenge, stats); -6035 builder.require_padding(); -6036 let pto = self.ack_frequency.max_ack_delay_for_pto() + path.rtt.pto_base(); -6037 match path.open_status { -6038 paths::OpenStatus::Sent | paths::OpenStatus::Informed => {} -6039 paths::OpenStatus::Pending => { -6040 path.open_status = paths::OpenStatus::Sent; -6041 self.timers.set( -6042 Timer::PerPath(path_id, PathTimer::AbandonFromValidation), -6043 now + 3 * pto, -6044 self.qlog.with_time(now), -6045 ); -6046 } -6047 // The path open status was informed before, we just want to revalidate again. -6048 // For that, we want to make sure we set the PathOpenFailed timer again. -6049 paths::OpenStatus::Revalidating => { -6050 path.open_status = paths::OpenStatus::Informed; -6051 self.timers.set( -6052 Timer::PerPath(path_id, PathTimer::AbandonFromValidation), -6053 now + 3 * pto, -6054 self.qlog.with_time(now), -6055 ); -6056 } -6057 } -6058 -6059 self.timers.set( -6060 Timer::PerPath(path_id, PathTimer::PathChallengeLost), -6061 now + pto, -6062 self.qlog.with_time(now), -6063 ); -6064 -6065 if is_multipath_negotiated && !path.validated && path.pending_on_path_challenge { -6066 // queue informing the path status along with the challenge -6067 space.pending.path_status.insert(path_id); -6068 } -6069 -6070 // Always include an OBSERVED_ADDR frame with a PATH_CHALLENGE, regardless -6071 // of whether one has already been sent on this path. -6072 if space_id == SpaceId::Data -6073 && self -6074 .config -6075 .address_discovery_role -6076 .should_report(&self.peer_params.address_discovery_role) -6077 { -6078 let frame = frame::ObservedAddr::new( -6079 path.network_path.remote, -6080 self.next_observed_addr_seq_no, -6081 ); -6082 if builder.frame_space_remaining() > frame.size() { -6083 builder.write_frame(frame, stats); -6084 -6085 self.next_observed_addr_seq_no = -6086 self.next_observed_addr_seq_no.saturating_add(1u8); -6087 path.observed_addr_sent = true; -6088 -6089 space.pending.observed_addr = false; -6090 } -6091 } -6092 } -6093 -6094 // PATH_RESPONSE -6095 if !scheduling_info.is_abandoned -6096 && space_id == SpaceId::Data -6097 && builder.frame_space_remaining() > frame::PathResponse::SIZE_BOUND -6098 && let Some(token) = path.path_responses.pop_on_path(path.network_path) -6099 { -6100 let response = frame::PathResponse(token); -6101 builder.write_frame(response, stats); -6102 builder.require_padding(); -6103 -6104 // NOTE: this is technically not required but might be useful to ride the -6105 // request/response nature of path challenges to refresh an observation -6106 // Since PATH_RESPONSE is a probing frame, this is allowed by the spec. -6107 if space_id == SpaceId::Data -6108 && self -6109 .config -6110 .address_discovery_role -6111 .should_report(&self.peer_params.address_discovery_role) -6112 { -6113 let frame = frame::ObservedAddr::new( -6114 path.network_path.remote, -6115 self.next_observed_addr_seq_no, -6116 ); -6117 if builder.frame_space_remaining() > frame.size() { -6118 builder.write_frame(frame, stats); -6119 -6120 self.next_observed_addr_seq_no = -6121 self.next_observed_addr_seq_no.saturating_add(1u8); -6122 path.observed_addr_sent = true; -6123 -6124 space.pending.observed_addr = false; -6125 } -6126 } -6127 } -6128 -6129 // REACH_OUT -6130 if !scheduling_info.is_abandoned -6131 && scheduling_info.may_send_data -6132 && let Some((round, addresses)) = space.pending.reach_out.as_mut() -6133 { -6134 while let Some(local_addr) = addresses.iter().next().copied() { -6135 let local_addr = addresses.take(&local_addr).expect("found from iter"); -6136 let reach_out = frame::ReachOut::new(*round, local_addr); -6137 if builder.frame_space_remaining() > reach_out.size() { -6138 builder.write_frame(reach_out, stats); -6139 } else { -6140 addresses.insert(local_addr); -6141 break; -6142 } -6143 } -6144 if addresses.is_empty() { -6145 space.pending.reach_out = None; -6146 } -6147 } -6148 -6149 // PATH_ABANDON -6150 if space_id == SpaceId::Data -6151 && scheduling_info.is_abandoned -6152 && scheduling_info.may_self_abandon -6153 && frame::PathAbandon::SIZE_BOUND <= builder.frame_space_remaining() -6154 && let Some(error_code) = space.pending.path_abandon.remove(&path_id) -6155 { -6156 let frame = frame::PathAbandon { -6157 path_id, -6158 error_code, -6159 }; -6160 builder.write_frame(frame, stats); -6161 -6162 // Consider remotely issued CIDs as retired now that we have sent this frame at -6163 // least once. -6164 self.remote_cids.remove(&path_id); -6165 } -6166 while space_id == SpaceId::Data -6167 && scheduling_info.may_send_data -6168 && frame::PathAbandon::SIZE_BOUND <= builder.frame_space_remaining() -6169 && let Some((abandoned_path_id, error_code)) = space.pending.path_abandon.pop_first() -6170 { -6171 let frame = frame::PathAbandon { -6172 path_id: abandoned_path_id, -6173 error_code, -6174 }; -6175 builder.write_frame(frame, stats); -6176 -6177 // Consider remotely issued CIDs as retired now that we have sent this frame at -6178 // least once. -6179 self.remote_cids.remove(&abandoned_path_id); -6180 } -6181 -6182 // OBSERVED_ADDR -6183 if !scheduling_info.is_abandoned -6184 && scheduling_info.may_send_data -6185 && space_id == SpaceId::Data -6186 && self -6187 .config -6188 .address_discovery_role -6189 .should_report(&self.peer_params.address_discovery_role) -6190 && (!path.observed_addr_sent || space.pending.observed_addr) -6191 { -6192 let frame = -6193 frame::ObservedAddr::new(path.network_path.remote, self.next_observed_addr_seq_no); -6194 if builder.frame_space_remaining() > frame.size() { -6195 builder.write_frame(frame, stats); -6196 -6197 self.next_observed_addr_seq_no = self.next_observed_addr_seq_no.saturating_add(1u8); -6198 path.observed_addr_sent = true; +5998 // IMMEDIATE_ACK +5999 if !scheduling_info.is_abandoned +6000 && mem::replace(&mut space.for_path(path_id).immediate_ack_pending, false) +6001 { +6002 debug_assert_eq!( +6003 space_id, +6004 SpaceId::Data, +6005 "immediate acks must be sent in the data space" +6006 ); +6007 builder.write_frame(frame::ImmediateAck, stats); +6008 } +6009 +6010 // ACK +6011 if !scheduling_info.is_abandoned && scheduling_info.may_send_data { +6012 for path_id in space +6013 .number_spaces +6014 .iter_mut() +6015 .filter(|(_, pns)| pns.pending_acks.can_send()) +6016 .map(|(&path_id, _)| path_id) +6017 .collect::<Vec<_>>() +6018 { +6019 Self::populate_acks( +6020 now, +6021 self.receiving_ecn, +6022 path_id, +6023 space_id, +6024 space, +6025 is_multipath_negotiated, +6026 builder, +6027 stats, +6028 space_has_keys, +6029 ); +6030 } +6031 } +6032 +6033 // ACK_FREQUENCY +6034 if !scheduling_info.is_abandoned +6035 && scheduling_info.may_send_data +6036 && mem::replace(&mut space.pending.ack_frequency, false) +6037 { +6038 let sequence_number = self.ack_frequency.next_sequence_number(); +6039 +6040 // Safe to unwrap because this is always provided when ACK frequency is enabled +6041 let config = self.config.ack_frequency_config.as_ref().unwrap(); +6042 +6043 // Ensure the delay is within bounds to avoid a PROTOCOL_VIOLATION error +6044 let max_ack_delay = self.ack_frequency.candidate_max_ack_delay( +6045 path.rtt.get(), +6046 config, +6047 &self.peer_params, +6048 ); +6049 +6050 let frame = frame::AckFrequency { +6051 sequence: sequence_number, +6052 ack_eliciting_threshold: config.ack_eliciting_threshold, +6053 request_max_ack_delay: max_ack_delay.as_micros().try_into().unwrap_or(VarInt::MAX), +6054 reordering_threshold: config.reordering_threshold, +6055 }; +6056 builder.write_frame(frame, stats); +6057 +6058 self.ack_frequency +6059 .ack_frequency_sent(path_id, builder.packet_number, max_ack_delay); +6060 } +6061 +6062 // PATH_CHALLENGE +6063 if !scheduling_info.is_abandoned +6064 && space_id == SpaceId::Data +6065 && path.pending_on_path_challenge +6066 && !self.state.is_closed() +6067 && builder.frame_space_remaining() > frame::PathChallenge::SIZE_BOUND +6068 // we don't want to send new challenges if we are already closing +6069 { +6070 path.pending_on_path_challenge = false; +6071 +6072 let token = self.rng.random(); +6073 path.record_path_challenge_sent(now, token, path.network_path); +6074 // Generate a new challenge every time we send a new PATH_CHALLENGE +6075 let challenge = frame::PathChallenge(token); +6076 builder.write_frame(challenge, stats); +6077 builder.require_padding(); +6078 let pto = self.ack_frequency.max_ack_delay_for_pto() + path.rtt.pto_base(); +6079 match path.open_status { +6080 paths::OpenStatus::Sent | paths::OpenStatus::Informed => {} +6081 paths::OpenStatus::Pending => { +6082 path.open_status = paths::OpenStatus::Sent; +6083 self.timers.set( +6084 Timer::PerPath(path_id, PathTimer::AbandonFromValidation), +6085 now + 3 * pto, +6086 self.qlog.with_time(now), +6087 ); +6088 } +6089 } +6090 +6091 self.timers.set( +6092 Timer::PerPath(path_id, PathTimer::PathChallengeLost), +6093 now + pto, +6094 self.qlog.with_time(now), +6095 ); +6096 +6097 if is_multipath_negotiated && !path.validated && path.pending_on_path_challenge { +6098 // queue informing the path status along with the challenge +6099 space.pending.path_status.insert(path_id); +6100 } +6101 +6102 // Always include an OBSERVED_ADDR frame with a PATH_CHALLENGE, regardless +6103 // of whether one has already been sent on this path. +6104 if space_id == SpaceId::Data +6105 && self +6106 .config +6107 .address_discovery_role +6108 .should_report(&self.peer_params.address_discovery_role) +6109 { +6110 let frame = frame::ObservedAddr::new( +6111 path.network_path.remote, +6112 self.next_observed_addr_seq_no, +6113 ); +6114 if builder.frame_space_remaining() > frame.size() { +6115 builder.write_frame(frame, stats); +6116 +6117 self.next_observed_addr_seq_no = +6118 self.next_observed_addr_seq_no.saturating_add(1u8); +6119 path.observed_addr_sent = true; +6120 +6121 space.pending.observed_addr = false; +6122 } +6123 } +6124 } +6125 +6126 // PATH_RESPONSE +6127 if !scheduling_info.is_abandoned +6128 && space_id == SpaceId::Data +6129 && builder.frame_space_remaining() > frame::PathResponse::SIZE_BOUND +6130 && let Some(token) = path.path_responses.pop_on_path(path.network_path) +6131 { +6132 let response = frame::PathResponse(token); +6133 builder.write_frame(response, stats); +6134 builder.require_padding(); +6135 +6136 // NOTE: this is technically not required but might be useful to ride the +6137 // request/response nature of path challenges to refresh an observation +6138 // Since PATH_RESPONSE is a probing frame, this is allowed by the spec. +6139 if space_id == SpaceId::Data +6140 && self +6141 .config +6142 .address_discovery_role +6143 .should_report(&self.peer_params.address_discovery_role) +6144 { +6145 let frame = frame::ObservedAddr::new( +6146 path.network_path.remote, +6147 self.next_observed_addr_seq_no, +6148 ); +6149 if builder.frame_space_remaining() > frame.size() { +6150 builder.write_frame(frame, stats); +6151 +6152 self.next_observed_addr_seq_no = +6153 self.next_observed_addr_seq_no.saturating_add(1u8); +6154 path.observed_addr_sent = true; +6155 +6156 space.pending.observed_addr = false; +6157 } +6158 } +6159 } +6160 +6161 // REACH_OUT +6162 while !scheduling_info.is_abandoned +6163 && scheduling_info.may_send_data +6164 && let Some(reach_out) = space +6165 .pending +6166 .reach_out +6167 .pop_if(|frame| builder.frame_space_remaining() >= frame.size()) +6168 { +6169 builder.write_frame(reach_out, stats); +6170 } +6171 +6172 // PATH_ABANDON +6173 if space_id == SpaceId::Data +6174 && scheduling_info.is_abandoned +6175 && scheduling_info.may_self_abandon +6176 && frame::PathAbandon::SIZE_BOUND <= builder.frame_space_remaining() +6177 && let Some(error_code) = space.pending.path_abandon.remove(&path_id) +6178 { +6179 let frame = frame::PathAbandon { +6180 path_id, +6181 error_code, +6182 }; +6183 builder.write_frame(frame, stats); +6184 +6185 // Consider remotely issued CIDs as retired now that we have sent this frame at +6186 // least once. +6187 self.remote_cids.remove(&path_id); +6188 } +6189 while space_id == SpaceId::Data +6190 && scheduling_info.may_send_data +6191 && frame::PathAbandon::SIZE_BOUND <= builder.frame_space_remaining() +6192 && let Some((abandoned_path_id, error_code)) = space.pending.path_abandon.pop_first() +6193 { +6194 let frame = frame::PathAbandon { +6195 path_id: abandoned_path_id, +6196 error_code, +6197 }; +6198 builder.write_frame(frame, stats); 6199 -6200 space.pending.observed_addr = false; -6201 } -6202 } -6203 -6204 // CRYPTO -6205 while !is_0rtt -6206 && !scheduling_info.is_abandoned +6200 // Consider remotely issued CIDs as retired now that we have sent this frame at +6201 // least once. +6202 self.remote_cids.remove(&abandoned_path_id); +6203 } +6204 +6205 // OBSERVED_ADDR +6206 if !scheduling_info.is_abandoned 6207 && scheduling_info.may_send_data -6208 && builder.frame_space_remaining() > frame::Crypto::SIZE_BOUND -6209 { -6210 let Some(mut frame) = space.pending.crypto.pop_front() else { -6211 break; -6212 }; -6213 -6214 // Calculate the maximum amount of crypto data we can store in the buffer. -6215 // Since the offset is known, we can reserve the exact size required to encode it. -6216 // For length we reserve 2bytes which allows to encode up to 2^14, -6217 // which is more than what fits into normally sized QUIC frames. -6218 let max_crypto_data_size = builder.frame_space_remaining() -6219 - 1 // Frame Type -6220 - VarInt::size(unsafe { VarInt::from_u64_unchecked(frame.offset) }) -6221 - 2; // Maximum encoded length for frame size, given we send less than 2^14 bytes +6208 && space_id == SpaceId::Data +6209 && self +6210 .config +6211 .address_discovery_role +6212 .should_report(&self.peer_params.address_discovery_role) +6213 && (!path.observed_addr_sent || space.pending.observed_addr) +6214 { +6215 let frame = +6216 frame::ObservedAddr::new(path.network_path.remote, self.next_observed_addr_seq_no); +6217 if builder.frame_space_remaining() > frame.size() { +6218 builder.write_frame(frame, stats); +6219 +6220 self.next_observed_addr_seq_no = self.next_observed_addr_seq_no.saturating_add(1u8); +6221 path.observed_addr_sent = true; 6222 -6223 let len = frame -6224 .data -6225 .len() -6226 .min(2usize.pow(14) - 1) -6227 .min(max_crypto_data_size); -6228 -6229 let data = frame.data.split_to(len); -6230 let offset = frame.offset; -6231 let truncated = frame::Crypto { offset, data }; -6232 builder.write_frame(truncated, stats); -6233 -6234 if !frame.data.is_empty() { -6235 frame.offset += len as u64; -6236 space.pending.crypto.push_front(frame); -6237 } -6238 } -6239 -6240 // PATH_STATUS_AVAILABLE & PATH_STATUS_BACKUP -6241 while space_id == SpaceId::Data -6242 && !scheduling_info.is_abandoned -6243 && scheduling_info.may_send_data -6244 && frame::PathStatusAvailable::SIZE_BOUND <= builder.frame_space_remaining() -6245 { -6246 let Some(path_id) = space.pending.path_status.pop_first() else { -6247 break; -6248 }; -6249 let Some(path) = self.paths.get(&path_id).map(|path_state| &path_state.data) else { -6250 trace!(%path_id, "discarding queued path status for unknown path"); -6251 continue; -6252 }; -6253 -6254 let seq = path.status.seq(); -6255 match path.local_status() { -6256 PathStatus::Available => { -6257 let frame = frame::PathStatusAvailable { -6258 path_id, -6259 status_seq_no: seq, -6260 }; -6261 builder.write_frame(frame, stats); -6262 } -6263 PathStatus::Backup => { -6264 let frame = frame::PathStatusBackup { -6265 path_id, -6266 status_seq_no: seq, -6267 }; -6268 builder.write_frame(frame, stats); -6269 } -6270 } -6271 } -6272 -6273 // MAX_PATH_ID -6274 if space_id == SpaceId::Data -6275 && !scheduling_info.is_abandoned -6276 && scheduling_info.may_send_data -6277 && space.pending.max_path_id -6278 && frame::MaxPathId::SIZE_BOUND <= builder.frame_space_remaining() -6279 { -6280 let frame = frame::MaxPathId(self.local_max_path_id); -6281 builder.write_frame(frame, stats); -6282 space.pending.max_path_id = false; -6283 } -6284 -6285 // PATHS_BLOCKED -6286 if space_id == SpaceId::Data -6287 && !scheduling_info.is_abandoned -6288 && scheduling_info.may_send_data -6289 && space.pending.paths_blocked -6290 && frame::PathsBlocked::SIZE_BOUND <= builder.frame_space_remaining() -6291 { -6292 let frame = frame::PathsBlocked(self.remote_max_path_id); -6293 builder.write_frame(frame, stats); -6294 space.pending.paths_blocked = false; -6295 } -6296 -6297 // PATH_CIDS_BLOCKED -6298 while space_id == SpaceId::Data -6299 && !scheduling_info.is_abandoned -6300 && scheduling_info.may_send_data -6301 && frame::PathCidsBlocked::SIZE_BOUND <= builder.frame_space_remaining() +6223 space.pending.observed_addr = false; +6224 } +6225 } +6226 +6227 // CRYPTO +6228 while !is_0rtt +6229 && !scheduling_info.is_abandoned +6230 && scheduling_info.may_send_data +6231 && builder.frame_space_remaining() > frame::Crypto::SIZE_BOUND +6232 { +6233 let Some(mut frame) = space.pending.crypto.pop_front() else { +6234 break; +6235 }; +6236 +6237 // Calculate the maximum amount of crypto data we can store in the buffer. +6238 // Since the offset is known, we can reserve the exact size required to encode it. +6239 // For length we reserve 2bytes which allows to encode up to 2^14, +6240 // which is more than what fits into normally sized QUIC frames. +6241 let max_crypto_data_size = builder.frame_space_remaining() +6242 - 1 // Frame Type +6243 - VarInt::size(unsafe { VarInt::from_u64_unchecked(frame.offset) }) +6244 - 2; // Maximum encoded length for frame size, given we send less than 2^14 bytes +6245 +6246 let len = frame +6247 .data +6248 .len() +6249 .min(2usize.pow(14) - 1) +6250 .min(max_crypto_data_size); +6251 +6252 let data = frame.data.split_to(len); +6253 let offset = frame.offset; +6254 let truncated = frame::Crypto { offset, data }; +6255 builder.write_frame(truncated, stats); +6256 +6257 if !frame.data.is_empty() { +6258 frame.offset += len as u64; +6259 space.pending.crypto.push_front(frame); +6260 } +6261 } +6262 +6263 // PATH_STATUS_AVAILABLE & PATH_STATUS_BACKUP +6264 while space_id == SpaceId::Data +6265 && !scheduling_info.is_abandoned +6266 && scheduling_info.may_send_data +6267 && frame::PathStatusAvailable::SIZE_BOUND <= builder.frame_space_remaining() +6268 { +6269 let Some(path_id) = space.pending.path_status.pop_first() else { +6270 break; +6271 }; +6272 let Some(path) = self.paths.get(&path_id).map(|path_state| &path_state.data) else { +6273 trace!(%path_id, "discarding queued path status for unknown path"); +6274 continue; +6275 }; +6276 +6277 let seq = path.status.seq(); +6278 match path.local_status() { +6279 PathStatus::Available => { +6280 let frame = frame::PathStatusAvailable { +6281 path_id, +6282 status_seq_no: seq, +6283 }; +6284 builder.write_frame(frame, stats); +6285 } +6286 PathStatus::Backup => { +6287 let frame = frame::PathStatusBackup { +6288 path_id, +6289 status_seq_no: seq, +6290 }; +6291 builder.write_frame(frame, stats); +6292 } +6293 } +6294 } +6295 +6296 // MAX_PATH_ID +6297 if space_id == SpaceId::Data +6298 && !scheduling_info.is_abandoned +6299 && scheduling_info.may_send_data +6300 && space.pending.max_path_id +6301 && frame::MaxPathId::SIZE_BOUND <= builder.frame_space_remaining() 6302 { -6303 let Some(path_id) = space.pending.path_cids_blocked.pop_first() else { -6304 break; -6305 }; -6306 let next_seq = match self.remote_cids.get(&path_id) { -6307 Some(cid_queue) => VarInt(cid_queue.active_seq() + 1), -6308 None => VarInt(0), -6309 }; -6310 let frame = frame::PathCidsBlocked { path_id, next_seq }; -6311 builder.write_frame(frame, stats); -6312 } -6313 -6314 // RESET_STREAM, STOP_SENDING, MAX_DATA, MAX_STREAM_DATA, MAX_STREAMS -6315 if space_id == SpaceId::Data -6316 && !scheduling_info.is_abandoned -6317 && scheduling_info.may_send_data -6318 { -6319 self.streams -6320 .write_control_frames(builder, &mut space.pending, stats); -6321 } -6322 -6323 // NEW_CONNECTION_ID -6324 let cid_len = self -6325 .local_cid_state -6326 .values() -6327 .map(|cid_state| cid_state.cid_len()) -6328 .max() -6329 .expect("some local CID state must exist"); -6330 let new_cid_size_bound = -6331 frame::NewConnectionId::size_bound(is_multipath_negotiated, cid_len); -6332 while !scheduling_info.is_abandoned -6333 && scheduling_info.may_send_data -6334 && builder.frame_space_remaining() > new_cid_size_bound -6335 { -6336 let Some(issued) = space.pending.new_cids.pop() else { -6337 break; -6338 }; -6339 let retire_prior_to = self -6340 .local_cid_state -6341 .get(&issued.path_id) -6342 .map(|cid_state| cid_state.retire_prior_to()) -6343 .unwrap_or_else(|| panic!("missing local CID state for path={}", issued.path_id)); -6344 -6345 let cid_path_id = match is_multipath_negotiated { -6346 true => Some(issued.path_id), -6347 false => { -6348 debug_assert_eq!(issued.path_id, PathId::ZERO); -6349 None -6350 } -6351 }; -6352 let frame = frame::NewConnectionId { -6353 path_id: cid_path_id, -6354 sequence: issued.sequence, -6355 retire_prior_to, -6356 id: issued.id, -6357 reset_token: issued.reset_token, -6358 }; -6359 builder.write_frame(frame, stats); -6360 } -6361 -6362 // RETIRE_CONNECTION_ID -6363 let retire_cid_bound = frame::RetireConnectionId::size_bound(is_multipath_negotiated); -6364 while !scheduling_info.is_abandoned -6365 && scheduling_info.may_send_data -6366 && builder.frame_space_remaining() > retire_cid_bound -6367 { -6368 let (path_id, sequence) = match space.pending.retire_cids.pop() { -6369 Some((PathId::ZERO, seq)) if !is_multipath_negotiated => (None, seq), -6370 Some((path_id, seq)) => (Some(path_id), seq), -6371 None => break, -6372 }; -6373 let frame = frame::RetireConnectionId { path_id, sequence }; -6374 builder.write_frame(frame, stats); -6375 } -6376 -6377 // DATAGRAM -6378 let mut sent_datagrams = false; -6379 while !scheduling_info.is_abandoned -6380 && scheduling_info.may_send_data -6381 && builder.frame_space_remaining() > Datagram::SIZE_BOUND -6382 && space_id == SpaceId::Data -6383 { -6384 match self.datagrams.write(builder, stats) { -6385 true => { -6386 sent_datagrams = true; -6387 } -6388 false => break, -6389 } -6390 } -6391 if self.datagrams.send_blocked && sent_datagrams { -6392 self.events.push_back(Event::DatagramsUnblocked); -6393 self.datagrams.send_blocked = false; -6394 } -6395 -6396 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; -6397 -6398 // NEW_TOKEN -6399 if !scheduling_info.is_abandoned && scheduling_info.may_send_data { -6400 while let Some(network_path) = space.pending.new_tokens.pop() { -6401 debug_assert_eq!(space_id, SpaceId::Data); -6402 let ConnectionSide::Server { server_config } = &self.side else { -6403 panic!("NEW_TOKEN frames should not be enqueued by clients"); -6404 }; -6405 -6406 if !network_path.is_probably_same_path(&path.network_path) { -6407 // NEW_TOKEN frames contain tokens bound to a client's IP address, and are only -6408 // useful if used from the same IP address. Thus, we abandon enqueued NEW_TOKEN -6409 // frames upon an path change. Instead, when the new path becomes validated, -6410 // NEW_TOKEN frames may be enqueued for the new path instead. -6411 continue; -6412 } -6413 -6414 let token = Token::new( -6415 TokenPayload::Validation { -6416 ip: network_path.remote.ip(), -6417 issued: server_config.time_source.now(), -6418 }, -6419 &mut self.rng, -6420 ); -6421 let new_token = NewToken { -6422 token: token.encode(&*server_config.token_key).into(), -6423 }; -6424 -6425 if builder.frame_space_remaining() < new_token.size() { -6426 space.pending.new_tokens.push(network_path); -6427 break; -6428 } -6429 -6430 builder.write_frame(new_token, stats); -6431 builder.retransmits_mut().new_tokens.push(network_path); -6432 } -6433 } -6434 -6435 // ADD_ADDRESS -6436 while space_id == SpaceId::Data -6437 && !scheduling_info.is_abandoned -6438 && scheduling_info.may_send_data -6439 && frame::AddAddress::SIZE_BOUND <= builder.frame_space_remaining() -6440 { -6441 if let Some(added_address) = space.pending.add_address.pop_last() { -6442 builder.write_frame(added_address, stats); -6443 } else { -6444 break; -6445 } -6446 } +6303 let frame = frame::MaxPathId(self.local_max_path_id); +6304 builder.write_frame(frame, stats); +6305 space.pending.max_path_id = false; +6306 } +6307 +6308 // PATHS_BLOCKED +6309 if space_id == SpaceId::Data +6310 && !scheduling_info.is_abandoned +6311 && scheduling_info.may_send_data +6312 && space.pending.paths_blocked +6313 && frame::PathsBlocked::SIZE_BOUND <= builder.frame_space_remaining() +6314 { +6315 let frame = frame::PathsBlocked(self.remote_max_path_id); +6316 builder.write_frame(frame, stats); +6317 space.pending.paths_blocked = false; +6318 } +6319 +6320 // PATH_CIDS_BLOCKED +6321 while space_id == SpaceId::Data +6322 && !scheduling_info.is_abandoned +6323 && scheduling_info.may_send_data +6324 && frame::PathCidsBlocked::SIZE_BOUND <= builder.frame_space_remaining() +6325 { +6326 let Some(path_id) = space.pending.path_cids_blocked.pop_first() else { +6327 break; +6328 }; +6329 let next_seq = match self.remote_cids.get(&path_id) { +6330 Some(cid_queue) => VarInt(cid_queue.active_seq() + 1), +6331 None => VarInt(0), +6332 }; +6333 let frame = frame::PathCidsBlocked { path_id, next_seq }; +6334 builder.write_frame(frame, stats); +6335 } +6336 +6337 // RESET_STREAM, STOP_SENDING, MAX_DATA, MAX_STREAM_DATA, MAX_STREAMS +6338 if space_id == SpaceId::Data +6339 && !scheduling_info.is_abandoned +6340 && scheduling_info.may_send_data +6341 { +6342 self.streams +6343 .write_control_frames(builder, &mut space.pending, stats); +6344 } +6345 +6346 // NEW_CONNECTION_ID +6347 let cid_len = self +6348 .local_cid_state +6349 .values() +6350 .map(|cid_state| cid_state.cid_len()) +6351 .max() +6352 .expect("some local CID state must exist"); +6353 let new_cid_size_bound = +6354 frame::NewConnectionId::size_bound(is_multipath_negotiated, cid_len); +6355 while !scheduling_info.is_abandoned +6356 && scheduling_info.may_send_data +6357 && builder.frame_space_remaining() > new_cid_size_bound +6358 { +6359 let Some(issued) = space.pending.new_cids.pop() else { +6360 break; +6361 }; +6362 let retire_prior_to = self +6363 .local_cid_state +6364 .get(&issued.path_id) +6365 .map(|cid_state| cid_state.retire_prior_to()) +6366 .unwrap_or_else(|| panic!("missing local CID state for path={}", issued.path_id)); +6367 +6368 let cid_path_id = match is_multipath_negotiated { +6369 true => Some(issued.path_id), +6370 false => { +6371 debug_assert_eq!(issued.path_id, PathId::ZERO); +6372 None +6373 } +6374 }; +6375 let frame = frame::NewConnectionId { +6376 path_id: cid_path_id, +6377 sequence: issued.sequence, +6378 retire_prior_to, +6379 id: issued.id, +6380 reset_token: issued.reset_token, +6381 }; +6382 builder.write_frame(frame, stats); +6383 } +6384 +6385 // RETIRE_CONNECTION_ID +6386 let retire_cid_bound = frame::RetireConnectionId::size_bound(is_multipath_negotiated); +6387 while !scheduling_info.is_abandoned +6388 && scheduling_info.may_send_data +6389 && builder.frame_space_remaining() > retire_cid_bound +6390 { +6391 let (path_id, sequence) = match space.pending.retire_cids.pop() { +6392 Some((PathId::ZERO, seq)) if !is_multipath_negotiated => (None, seq), +6393 Some((path_id, seq)) => (Some(path_id), seq), +6394 None => break, +6395 }; +6396 let frame = frame::RetireConnectionId { path_id, sequence }; +6397 builder.write_frame(frame, stats); +6398 } +6399 +6400 // DATAGRAM +6401 let mut sent_datagrams = false; +6402 while !scheduling_info.is_abandoned +6403 && scheduling_info.may_send_data +6404 && builder.frame_space_remaining() > Datagram::SIZE_BOUND +6405 && space_id == SpaceId::Data +6406 { +6407 match self.datagrams.write(builder, stats) { +6408 true => { +6409 sent_datagrams = true; +6410 } +6411 false => break, +6412 } +6413 } +6414 if self.datagrams.send_blocked && sent_datagrams { +6415 self.events.push_back(Event::DatagramsUnblocked); +6416 self.datagrams.send_blocked = false; +6417 } +6418 +6419 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; +6420 +6421 // NEW_TOKEN +6422 if !scheduling_info.is_abandoned && scheduling_info.may_send_data { +6423 while let Some(network_path) = space.pending.new_tokens.pop() { +6424 debug_assert_eq!(space_id, SpaceId::Data); +6425 let ConnectionSide::Server { server_config } = &self.side else { +6426 panic!("NEW_TOKEN frames should not be enqueued by clients"); +6427 }; +6428 +6429 if !network_path.is_probably_same_path(&path.network_path) { +6430 // NEW_TOKEN frames contain tokens bound to a client's IP address, and are only +6431 // useful if used from the same IP address. Thus, we abandon enqueued NEW_TOKEN +6432 // frames upon an path change. Instead, when the new path becomes validated, +6433 // NEW_TOKEN frames may be enqueued for the new path instead. +6434 continue; +6435 } +6436 +6437 let token = Token::new( +6438 TokenPayload::Validation { +6439 ip: network_path.remote.ip(), +6440 issued: server_config.time_source.now(), +6441 }, +6442 &mut self.rng, +6443 ); +6444 let new_token = NewToken { +6445 token: token.encode(&*server_config.token_key).into(), +6446 }; 6447 -6448 // REMOVE_ADDRESS -6449 while space_id == SpaceId::Data -6450 && !scheduling_info.is_abandoned -6451 && scheduling_info.may_send_data -6452 && frame::RemoveAddress::SIZE_BOUND <= builder.frame_space_remaining() -6453 { -6454 if let Some(removed_address) = space.pending.remove_address.pop_last() { -6455 builder.write_frame(removed_address, stats); -6456 } else { -6457 break; -6458 } -6459 } -6460 -6461 // STREAM -6462 if !scheduling_info.is_abandoned -6463 && scheduling_info.may_send_data -6464 && space_id == SpaceId::Data -6465 { -6466 self.streams -6467 .write_stream_frames(builder, self.config.send_fairness, stats); -6468 } -6469 } +6448 if builder.frame_space_remaining() < new_token.size() { +6449 space.pending.new_tokens.push(network_path); +6450 break; +6451 } +6452 +6453 builder.write_frame(new_token, stats); +6454 builder.retransmits_mut().new_tokens.push(network_path); +6455 } +6456 } +6457 +6458 // ADD_ADDRESS +6459 while space_id == SpaceId::Data +6460 && !scheduling_info.is_abandoned +6461 && scheduling_info.may_send_data +6462 && frame::AddAddress::SIZE_BOUND <= builder.frame_space_remaining() +6463 { +6464 if let Some(added_address) = space.pending.add_address.pop_last() { +6465 builder.write_frame(added_address, stats); +6466 } else { +6467 break; +6468 } +6469 } 6470 -6471 /// Write pending ACKs into a buffer -6472 fn populate_acks<'a, 'b>( -6473 now: Instant, -6474 receiving_ecn: bool, -6475 path_id: PathId, -6476 space_id: SpaceId, -6477 space: &mut PacketSpace, -6478 is_multipath_negotiated: bool, -6479 builder: &mut PacketBuilder<'a, 'b>, -6480 stats: &mut FrameStats, -6481 space_has_keys: bool, -6482 ) { -6483 // 0-RTT packets must never carry acks (which would have to be of handshake packets) -6484 debug_assert!(space_has_keys, "tried to send ACK in 0-RTT"); -6485 -6486 debug_assert!( -6487 is_multipath_negotiated || path_id == PathId::ZERO, -6488 "Only PathId::ZERO allowed without multipath (have {path_id:?})" -6489 ); -6490 if is_multipath_negotiated { -6491 debug_assert!( -6492 space_id == SpaceId::Data || path_id == PathId::ZERO, -6493 "path acks must be sent in 1RTT space (have {space_id:?})" -6494 ); -6495 } -6496 -6497 let pns = space.for_path(path_id); -6498 let ranges = pns.pending_acks.ranges(); -6499 debug_assert!(!ranges.is_empty(), "can not send empty ACK range"); -6500 let ecn = if receiving_ecn { -6501 Some(&pns.ecn_counters) -6502 } else { -6503 None -6504 }; -6505 -6506 let delay_micros = pns.pending_acks.ack_delay(now).as_micros() as u64; -6507 // TODO: This should come from `TransportConfig` if that gets configurable. -6508 let ack_delay_exp = TransportParameters::default().ack_delay_exponent; -6509 let delay = delay_micros >> ack_delay_exp.into_inner(); -6510 -6511 if is_multipath_negotiated && space_id == SpaceId::Data { -6512 if !ranges.is_empty() { -6513 let frame = frame::PathAck::encoder(path_id, delay, ranges, ecn); -6514 builder.write_frame(frame, stats); -6515 } -6516 } else { -6517 builder.write_frame(frame::Ack::encoder(delay, ranges, ecn), stats); +6471 // REMOVE_ADDRESS +6472 while space_id == SpaceId::Data +6473 && !scheduling_info.is_abandoned +6474 && scheduling_info.may_send_data +6475 && frame::RemoveAddress::SIZE_BOUND <= builder.frame_space_remaining() +6476 { +6477 if let Some(removed_address) = space.pending.remove_address.pop_last() { +6478 builder.write_frame(removed_address, stats); +6479 } else { +6480 break; +6481 } +6482 } +6483 +6484 // STREAM +6485 if !scheduling_info.is_abandoned +6486 && scheduling_info.may_send_data +6487 && space_id == SpaceId::Data +6488 { +6489 self.streams +6490 .write_stream_frames(builder, self.config.send_fairness, stats); +6491 } +6492 } +6493 +6494 /// Write pending ACKs into a buffer +6495 fn populate_acks<'a, 'b>( +6496 now: Instant, +6497 receiving_ecn: bool, +6498 path_id: PathId, +6499 space_id: SpaceId, +6500 space: &mut PacketSpace, +6501 is_multipath_negotiated: bool, +6502 builder: &mut PacketBuilder<'a, 'b>, +6503 stats: &mut FrameStats, +6504 space_has_keys: bool, +6505 ) { +6506 // 0-RTT packets must never carry acks (which would have to be of handshake packets) +6507 debug_assert!(space_has_keys, "tried to send ACK in 0-RTT"); +6508 +6509 debug_assert!( +6510 is_multipath_negotiated || path_id == PathId::ZERO, +6511 "Only PathId::ZERO allowed without multipath (have {path_id:?})" +6512 ); +6513 if is_multipath_negotiated { +6514 debug_assert!( +6515 space_id == SpaceId::Data || path_id == PathId::ZERO, +6516 "path acks must be sent in 1RTT space (have {space_id:?})" +6517 ); 6518 } -6519 } -6520 -6521 fn close_common(&mut self) { -6522 trace!("connection closed"); -6523 self.timers.reset(); -6524 } -6525 -6526 fn set_close_timer(&mut self, now: Instant) { -6527 // QUIC-MULTIPATH § 2.6 Connection Closure: draining for 3*PTO using the max PTO of -6528 // all paths. -6529 let pto_max = self.max_pto_for_space(self.highest_space); -6530 self.timers.set( -6531 Timer::Conn(ConnTimer::Close), -6532 now + 3 * pto_max, -6533 self.qlog.with_time(now), -6534 ); -6535 } -6536 -6537 /// Handle transport parameters received from the peer -6538 /// -6539 /// *remote_cid* and *local_cid* are the source and destination CIDs respectively of the -6540 /// *packet into which the transport parameters arrived. -6541 fn handle_peer_params( -6542 &mut self, -6543 params: TransportParameters, -6544 local_cid: ConnectionId, -6545 remote_cid: ConnectionId, -6546 now: Instant, -6547 ) -> Result<(), TransportError> { -6548 if Some(self.original_remote_cid) != params.initial_src_cid -6549 || (self.side.is_client() -6550 && (Some(self.initial_dst_cid) != params.original_dst_cid -6551 || self.retry_src_cid != params.retry_src_cid)) -6552 { -6553 return Err(TransportError::TRANSPORT_PARAMETER_ERROR( -6554 "CID authentication failure", -6555 )); -6556 } -6557 if params.initial_max_path_id.is_some() && (local_cid.is_empty() || remote_cid.is_empty()) { -6558 return Err(TransportError::PROTOCOL_VIOLATION( -6559 "multipath must not use zero-length CIDs", -6560 )); -6561 } -6562 -6563 self.set_peer_params(params); -6564 self.qlog.emit_peer_transport_params_received(self, now); -6565 -6566 Ok(()) -6567 } -6568 -6569 fn set_peer_params(&mut self, params: TransportParameters) { -6570 self.streams.set_params(&params); -6571 self.idle_timeout = -6572 negotiate_max_idle_timeout(self.config.max_idle_timeout, Some(params.max_idle_timeout)); -6573 trace!("negotiated max idle timeout {:?}", self.idle_timeout); -6574 -6575 if let Some(ref info) = params.preferred_address { -6576 // During the handshake PathId::ZERO exists. -6577 self.remote_cids.get_mut(&PathId::ZERO).expect("not yet abandoned").insert(frame::NewConnectionId { -6578 path_id: None, -6579 sequence: 1, -6580 id: info.connection_id, -6581 reset_token: info.stateless_reset_token, -6582 retire_prior_to: 0, -6583 }) -6584 .expect( -6585 "preferred address CID is the first received, and hence is guaranteed to be legal", -6586 ); -6587 let remote = self.path_data(PathId::ZERO).network_path.remote; -6588 self.set_reset_token(PathId::ZERO, remote, info.stateless_reset_token); -6589 } -6590 self.ack_frequency.peer_max_ack_delay = get_max_ack_delay(&params); +6519 +6520 let pns = space.for_path(path_id); +6521 let ranges = pns.pending_acks.ranges(); +6522 debug_assert!(!ranges.is_empty(), "can not send empty ACK range"); +6523 let ecn = if receiving_ecn { +6524 Some(&pns.ecn_counters) +6525 } else { +6526 None +6527 }; +6528 +6529 let delay_micros = pns.pending_acks.ack_delay(now).as_micros() as u64; +6530 // TODO: This should come from `TransportConfig` if that gets configurable. +6531 let ack_delay_exp = TransportParameters::default().ack_delay_exponent; +6532 let delay = delay_micros >> ack_delay_exp.into_inner(); +6533 +6534 if is_multipath_negotiated && space_id == SpaceId::Data { +6535 if !ranges.is_empty() { +6536 let frame = frame::PathAck::encoder(path_id, delay, ranges, ecn); +6537 builder.write_frame(frame, stats); +6538 } +6539 } else { +6540 builder.write_frame(frame::Ack::encoder(delay, ranges, ecn), stats); +6541 } +6542 } +6543 +6544 fn close_common(&mut self) { +6545 trace!("connection closed"); +6546 self.timers.reset(); +6547 } +6548 +6549 fn set_close_timer(&mut self, now: Instant) { +6550 // QUIC-MULTIPATH § 2.6 Connection Closure: draining for 3*PTO using the max PTO of +6551 // all paths. +6552 let pto_max = self.max_pto_for_space(self.highest_space); +6553 self.timers.set( +6554 Timer::Conn(ConnTimer::Close), +6555 now + 3 * pto_max, +6556 self.qlog.with_time(now), +6557 ); +6558 } +6559 +6560 /// Handle transport parameters received from the peer +6561 /// +6562 /// *remote_cid* and *local_cid* are the source and destination CIDs respectively of the +6563 /// *packet into which the transport parameters arrived. +6564 fn handle_peer_params( +6565 &mut self, +6566 params: TransportParameters, +6567 local_cid: ConnectionId, +6568 remote_cid: ConnectionId, +6569 now: Instant, +6570 ) -> Result<(), TransportError> { +6571 if Some(self.original_remote_cid) != params.initial_src_cid +6572 || (self.side.is_client() +6573 && (Some(self.initial_dst_cid) != params.original_dst_cid +6574 || self.retry_src_cid != params.retry_src_cid)) +6575 { +6576 return Err(TransportError::TRANSPORT_PARAMETER_ERROR( +6577 "CID authentication failure", +6578 )); +6579 } +6580 if params.initial_max_path_id.is_some() && (local_cid.is_empty() || remote_cid.is_empty()) { +6581 return Err(TransportError::PROTOCOL_VIOLATION( +6582 "multipath must not use zero-length CIDs", +6583 )); +6584 } +6585 +6586 self.set_peer_params(params); +6587 self.qlog.emit_peer_transport_params_received(self, now); +6588 +6589 Ok(()) +6590 } 6591 -6592 let mut multipath_enabled = None; -6593 if let (Some(local_max_path_id), Some(remote_max_path_id)) = ( -6594 self.config.get_initial_max_path_id(), -6595 params.initial_max_path_id, -6596 ) { -6597 // multipath is enabled, register the local and remote maximums -6598 self.local_max_path_id = local_max_path_id; -6599 self.remote_max_path_id = remote_max_path_id; -6600 let initial_max_path_id = local_max_path_id.min(remote_max_path_id); -6601 debug!(%initial_max_path_id, "multipath negotiated"); -6602 multipath_enabled = Some(initial_max_path_id); -6603 } -6604 -6605 if let Some((max_locally_allowed_remote_addresses, max_remotely_allowed_remote_addresses)) = -6606 self.config -6607 .max_remote_nat_traversal_addresses -6608 .zip(params.max_remote_nat_traversal_addresses) -6609 { -6610 if let Some(max_initial_paths) = -6611 multipath_enabled.map(|path_id| path_id.saturating_add(1u8)) -6612 { -6613 let max_local_addresses = max_remotely_allowed_remote_addresses.get(); -6614 let max_remote_addresses = max_locally_allowed_remote_addresses.get(); -6615 self.n0_nat_traversal = n0_nat_traversal::State::new( -6616 max_remote_addresses, -6617 max_local_addresses, -6618 self.side(), -6619 ); -6620 debug!( -6621 %max_remote_addresses, %max_local_addresses, -6622 "n0's nat traversal negotiated" -6623 ); -6624 -6625 match self.side() { -6626 Side::Client => { -6627 if max_initial_paths.as_u32() < max_remote_addresses as u32 + 1 { -6628 // in this case the client might try to open `max_remote_addresses` new -6629 // paths, but the current multipath configuration will not allow it -6630 debug!(%max_initial_paths, %max_remote_addresses, "local client configuration might cause nat traversal issues") -6631 } else if max_local_addresses as u64 -6632 > params.active_connection_id_limit.into_inner() -6633 { -6634 // the server allows us to send at most `params.active_connection_id_limit` -6635 // but they might need at least `max_local_addresses` to effectively send -6636 // `PATH_CHALLENGE` frames to each advertised local address -6637 debug!(%max_local_addresses, remote_cid_limit=%params.active_connection_id_limit.into_inner(), "remote server configuration might cause nat traversal issues") -6638 } -6639 } -6640 Side::Server => { -6641 if (max_initial_paths.as_u32() as u64) < crate::LOCAL_CID_COUNT { -6642 debug!(%max_initial_paths, local_cid_limit=%crate::LOCAL_CID_COUNT, "local server configuration might cause nat traversal issues") -6643 } -6644 } -6645 } -6646 } else { -6647 debug!("n0 nat traversal enabled for both endpoints, but multipath is missing") -6648 } -6649 } -6650 -6651 self.peer_params = params; -6652 let peer_max_udp_payload_size = -6653 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()).unwrap_or(u16::MAX); -6654 self.path_data_mut(PathId::ZERO) -6655 .mtud -6656 .on_peer_max_udp_payload_size_received(peer_max_udp_payload_size); -6657 } -6658 -6659 /// Decrypts a packet, returning the packet number on success -6660 fn decrypt_packet( -6661 &mut self, -6662 now: Instant, -6663 path_id: PathId, -6664 packet: &mut Packet, -6665 ) -> Result<Option<u64>, Option<TransportError>> { -6666 let result = self -6667 .crypto_state -6668 .decrypt_packet_body(packet, path_id, &self.spaces)?; -6669 -6670 let Some(result) = result else { -6671 return Ok(None); -6672 }; +6592 fn set_peer_params(&mut self, params: TransportParameters) { +6593 self.streams.set_params(&params); +6594 self.idle_timeout = +6595 negotiate_max_idle_timeout(self.config.max_idle_timeout, Some(params.max_idle_timeout)); +6596 trace!("negotiated max idle timeout {:?}", self.idle_timeout); +6597 +6598 if let Some(ref info) = params.preferred_address { +6599 // During the handshake PathId::ZERO exists. +6600 self.remote_cids.get_mut(&PathId::ZERO).expect("not yet abandoned").insert(frame::NewConnectionId { +6601 path_id: None, +6602 sequence: 1, +6603 id: info.connection_id, +6604 reset_token: info.stateless_reset_token, +6605 retire_prior_to: 0, +6606 }) +6607 .expect( +6608 "preferred address CID is the first received, and hence is guaranteed to be legal", +6609 ); +6610 let remote = self.path_data(PathId::ZERO).network_path.remote; +6611 self.set_reset_token(PathId::ZERO, remote, info.stateless_reset_token); +6612 } +6613 self.ack_frequency.peer_max_ack_delay = get_max_ack_delay(&params); +6614 +6615 let mut multipath_enabled = None; +6616 if let (Some(local_max_path_id), Some(remote_max_path_id)) = ( +6617 self.config.get_initial_max_path_id(), +6618 params.initial_max_path_id, +6619 ) { +6620 // multipath is enabled, register the local and remote maximums +6621 self.local_max_path_id = local_max_path_id; +6622 self.remote_max_path_id = remote_max_path_id; +6623 let initial_max_path_id = local_max_path_id.min(remote_max_path_id); +6624 debug!(%initial_max_path_id, "multipath negotiated"); +6625 multipath_enabled = Some(initial_max_path_id); +6626 } +6627 +6628 if let Some((max_locally_allowed_remote_addresses, max_remotely_allowed_remote_addresses)) = +6629 self.config +6630 .max_remote_nat_traversal_addresses +6631 .zip(params.max_remote_nat_traversal_addresses) +6632 { +6633 if let Some(max_initial_paths) = +6634 multipath_enabled.map(|path_id| path_id.saturating_add(1u8)) +6635 { +6636 let max_local_addresses = max_remotely_allowed_remote_addresses.get(); +6637 let max_remote_addresses = max_locally_allowed_remote_addresses.get(); +6638 self.n0_nat_traversal = n0_nat_traversal::State::new( +6639 max_remote_addresses, +6640 max_local_addresses, +6641 self.side(), +6642 ); +6643 debug!( +6644 %max_remote_addresses, %max_local_addresses, +6645 "n0's nat traversal negotiated" +6646 ); +6647 +6648 match self.side() { +6649 Side::Client => { +6650 if max_initial_paths.as_u32() < max_remote_addresses as u32 + 1 { +6651 // in this case the client might try to open `max_remote_addresses` new +6652 // paths, but the current multipath configuration will not allow it +6653 debug!(%max_initial_paths, %max_remote_addresses, "local client configuration might cause nat traversal issues") +6654 } else if max_local_addresses as u64 +6655 > params.active_connection_id_limit.into_inner() +6656 { +6657 // the server allows us to send at most `params.active_connection_id_limit` +6658 // but they might need at least `max_local_addresses` to effectively send +6659 // `PATH_CHALLENGE` frames to each advertised local address +6660 debug!(%max_local_addresses, remote_cid_limit=%params.active_connection_id_limit.into_inner(), "remote server configuration might cause nat traversal issues") +6661 } +6662 } +6663 Side::Server => { +6664 if (max_initial_paths.as_u32() as u64) < crate::LOCAL_CID_COUNT { +6665 debug!(%max_initial_paths, local_cid_limit=%crate::LOCAL_CID_COUNT, "local server configuration might cause nat traversal issues") +6666 } +6667 } +6668 } +6669 } else { +6670 debug!("n0 nat traversal enabled for both endpoints, but multipath is missing") +6671 } +6672 } 6673 -6674 if result.outgoing_key_update_acked -6675 && let Some(prev) = self.crypto_state.prev_crypto.as_mut() -6676 { -6677 prev.end_packet = Some((result.packet_number, now)); -6678 self.set_key_discard_timer(now, packet.header.space()); -6679 } -6680 -6681 if result.incoming_key_update { -6682 trace!("key update authenticated"); -6683 self.crypto_state -6684 .update_keys(Some((result.packet_number, now)), true); -6685 self.set_key_discard_timer(now, packet.header.space()); -6686 } -6687 -6688 Ok(Some(result.packet_number)) -6689 } -6690 -6691 fn peer_supports_ack_frequency(&self) -> bool { -6692 self.peer_params.min_ack_delay.is_some() -6693 } -6694 -6695 /// Send an IMMEDIATE_ACK frame to the remote endpoint -6696 /// -6697 /// According to the spec, this will result in an error if the remote endpoint does not support -6698 /// the Acknowledgement Frequency extension -6699 pub(crate) fn immediate_ack(&mut self, path_id: PathId) { -6700 debug_assert_eq!( -6701 self.highest_space, -6702 SpaceKind::Data, -6703 "immediate ack must be written in the data space" -6704 ); -6705 self.spaces[SpaceId::Data] -6706 .for_path(path_id) -6707 .immediate_ack_pending = true; -6708 } -6709 -6710 /// Decodes a packet, returning its decrypted payload, so it can be inspected in tests -6711 #[cfg(test)] -6712 pub(crate) fn decode_packet(&self, event: &ConnectionEvent) -> Option<Vec<u8>> { -6713 let ConnectionEventInner::Datagram(DatagramConnectionEvent { -6714 path_id, -6715 first_decode, -6716 remaining, -6717 .. -6718 }) = &event.0 -6719 else { -6720 return None; -6721 }; -6722 -6723 if remaining.is_some() { -6724 panic!("Packets should never be coalesced in tests"); -6725 } -6726 -6727 let decrypted_header = self -6728 .crypto_state -6729 .unprotect_header(first_decode.clone(), self.peer_params.stateless_reset_token)?; -6730 -6731 let mut packet = decrypted_header.packet?; -6732 self.crypto_state -6733 .decrypt_packet_body(&mut packet, *path_id, &self.spaces) -6734 .ok()?; -6735 -6736 Some(packet.payload.to_vec()) -6737 } -6738 -6739 /// The number of bytes of packets containing retransmittable frames that have not been -6740 /// acknowledged or declared lost. -6741 #[cfg(test)] -6742 pub(crate) fn bytes_in_flight(&self) -> u64 { -6743 // TODO(@divma): consider including for multipath? -6744 self.path_data(PathId::ZERO).in_flight.bytes -6745 } -6746 -6747 /// Number of bytes worth of non-ack-only packets that may be sent -6748 #[cfg(test)] -6749 pub(crate) fn congestion_window(&self) -> u64 { -6750 let path = self.path_data(PathId::ZERO); -6751 path.congestion -6752 .window() -6753 .saturating_sub(path.in_flight.bytes) -6754 } -6755 -6756 /// Whether no timers but keepalive, idle, rtt, pushnewcid, and key discard are running -6757 #[cfg(test)] -6758 pub(crate) fn is_idle(&self) -> bool { -6759 let current_timers = self.timers.values(); -6760 current_timers -6761 .into_iter() -6762 .filter(|(timer, _)| { -6763 !matches!( -6764 timer, -6765 Timer::Conn(ConnTimer::KeepAlive) -6766 | Timer::PerPath(_, PathTimer::PathKeepAlive) -6767 | Timer::Conn(ConnTimer::PushNewCid) -6768 | Timer::Conn(ConnTimer::KeyDiscard) -6769 ) -6770 }) -6771 .min_by_key(|(_, time)| *time) -6772 .is_none_or(|(timer, _)| { -6773 matches!( -6774 timer, -6775 Timer::Conn(ConnTimer::Idle) | Timer::PerPath(_, PathTimer::PathIdle) -6776 ) -6777 }) -6778 } -6779 -6780 /// Whether explicit congestion notification is in use on outgoing packets. -6781 #[cfg(test)] -6782 pub(crate) fn using_ecn(&self) -> bool { -6783 self.path_data(PathId::ZERO).sending_ecn -6784 } -6785 -6786 /// The number of received bytes in the current path -6787 #[cfg(test)] -6788 pub(crate) fn total_recvd(&self) -> u64 { -6789 self.path_data(PathId::ZERO).total_recvd -6790 } -6791 -6792 #[cfg(test)] -6793 pub(crate) fn active_local_cid_seq(&self) -> (u64, u64) { -6794 self.local_cid_state -6795 .get(&PathId::ZERO) -6796 .unwrap() -6797 .active_seq() -6798 } -6799 -6800 #[cfg(test)] -6801 #[track_caller] -6802 pub(crate) fn active_local_path_cid_seq(&self, path_id: u32) -> (u64, u64) { -6803 self.local_cid_state -6804 .get(&PathId(path_id)) -6805 .unwrap() -6806 .active_seq() +6674 self.peer_params = params; +6675 let peer_max_udp_payload_size = +6676 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()).unwrap_or(u16::MAX); +6677 self.path_data_mut(PathId::ZERO) +6678 .mtud +6679 .on_peer_max_udp_payload_size_received(peer_max_udp_payload_size); +6680 } +6681 +6682 /// Decrypts a packet, returning the packet number on success +6683 fn decrypt_packet( +6684 &mut self, +6685 now: Instant, +6686 path_id: PathId, +6687 packet: &mut Packet, +6688 ) -> Result<Option<u64>, Option<TransportError>> { +6689 let result = self +6690 .crypto_state +6691 .decrypt_packet_body(packet, path_id, &self.spaces)?; +6692 +6693 let Some(result) = result else { +6694 return Ok(None); +6695 }; +6696 +6697 if result.outgoing_key_update_acked +6698 && let Some(prev) = self.crypto_state.prev_crypto.as_mut() +6699 { +6700 prev.end_packet = Some((result.packet_number, now)); +6701 self.set_key_discard_timer(now, packet.header.space()); +6702 } +6703 +6704 if result.incoming_key_update { +6705 trace!("key update authenticated"); +6706 self.crypto_state +6707 .update_keys(Some((result.packet_number, now)), true); +6708 self.set_key_discard_timer(now, packet.header.space()); +6709 } +6710 +6711 Ok(Some(result.packet_number)) +6712 } +6713 +6714 fn peer_supports_ack_frequency(&self) -> bool { +6715 self.peer_params.min_ack_delay.is_some() +6716 } +6717 +6718 /// Send an IMMEDIATE_ACK frame to the remote endpoint +6719 /// +6720 /// According to the spec, this will result in an error if the remote endpoint does not support +6721 /// the Acknowledgement Frequency extension +6722 pub(crate) fn immediate_ack(&mut self, path_id: PathId) { +6723 debug_assert_eq!( +6724 self.highest_space, +6725 SpaceKind::Data, +6726 "immediate ack must be written in the data space" +6727 ); +6728 self.spaces[SpaceId::Data] +6729 .for_path(path_id) +6730 .immediate_ack_pending = true; +6731 } +6732 +6733 /// Decodes a packet, returning its decrypted payload, so it can be inspected in tests +6734 #[cfg(test)] +6735 pub(crate) fn decode_packet(&self, event: &ConnectionEvent) -> Option<Vec<u8>> { +6736 let ConnectionEventInner::Datagram(DatagramConnectionEvent { +6737 path_id, +6738 first_decode, +6739 remaining, +6740 .. +6741 }) = &event.0 +6742 else { +6743 return None; +6744 }; +6745 +6746 if remaining.is_some() { +6747 panic!("Packets should never be coalesced in tests"); +6748 } +6749 +6750 let decrypted_header = self +6751 .crypto_state +6752 .unprotect_header(first_decode.clone(), self.peer_params.stateless_reset_token)?; +6753 +6754 let mut packet = decrypted_header.packet?; +6755 self.crypto_state +6756 .decrypt_packet_body(&mut packet, *path_id, &self.spaces) +6757 .ok()?; +6758 +6759 Some(packet.payload.to_vec()) +6760 } +6761 +6762 /// The number of bytes of packets containing retransmittable frames that have not been +6763 /// acknowledged or declared lost. +6764 #[cfg(test)] +6765 pub(crate) fn bytes_in_flight(&self) -> u64 { +6766 // TODO(@divma): consider including for multipath? +6767 self.path_data(PathId::ZERO).in_flight.bytes +6768 } +6769 +6770 /// Number of bytes worth of non-ack-only packets that may be sent +6771 #[cfg(test)] +6772 pub(crate) fn congestion_window(&self) -> u64 { +6773 let path = self.path_data(PathId::ZERO); +6774 path.congestion +6775 .window() +6776 .saturating_sub(path.in_flight.bytes) +6777 } +6778 +6779 /// Whether no timers but keepalive, idle, rtt, pushnewcid, and key discard are running +6780 #[cfg(test)] +6781 pub(crate) fn is_idle(&self) -> bool { +6782 let current_timers = self.timers.values(); +6783 current_timers +6784 .into_iter() +6785 .filter(|(timer, _)| { +6786 !matches!( +6787 timer, +6788 Timer::Conn(ConnTimer::KeepAlive) +6789 | Timer::PerPath(_, PathTimer::PathKeepAlive) +6790 | Timer::Conn(ConnTimer::PushNewCid) +6791 | Timer::Conn(ConnTimer::KeyDiscard) +6792 ) +6793 }) +6794 .min_by_key(|(_, time)| *time) +6795 .is_none_or(|(timer, _)| { +6796 matches!( +6797 timer, +6798 Timer::Conn(ConnTimer::Idle) | Timer::PerPath(_, PathTimer::PathIdle) +6799 ) +6800 }) +6801 } +6802 +6803 /// Whether explicit congestion notification is in use on outgoing packets. +6804 #[cfg(test)] +6805 pub(crate) fn using_ecn(&self) -> bool { +6806 self.path_data(PathId::ZERO).sending_ecn 6807 } 6808 -6809 /// Instruct the peer to replace previously issued CIDs by sending a NEW_CONNECTION_ID frame -6810 /// with updated `retire_prior_to` field set to `v` -6811 #[cfg(test)] -6812 pub(crate) fn rotate_local_cid(&mut self, v: u64, now: Instant) { -6813 let n = self -6814 .local_cid_state -6815 .get_mut(&PathId::ZERO) -6816 .unwrap() -6817 .assign_retire_seq(v); -6818 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events -6819 self.endpoint_events -6820 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); +6809 /// The number of received bytes in the current path +6810 #[cfg(test)] +6811 pub(crate) fn total_recvd(&self) -> u64 { +6812 self.path_data(PathId::ZERO).total_recvd +6813 } +6814 +6815 #[cfg(test)] +6816 pub(crate) fn active_local_cid_seq(&self) -> (u64, u64) { +6817 self.local_cid_state +6818 .get(&PathId::ZERO) +6819 .unwrap() +6820 .active_seq() 6821 } 6822 -6823 /// Check the current active remote CID sequence for `PathId::ZERO` -6824 #[cfg(test)] -6825 pub(crate) fn active_remote_cid_seq(&self) -> u64 { -6826 self.remote_cids.get(&PathId::ZERO).unwrap().active_seq() -6827 } -6828 -6829 /// Returns the detected maximum udp payload size for the current path -6830 #[cfg(test)] -6831 pub(crate) fn path_mtu(&self, path_id: PathId) -> u16 { -6832 self.path_data(path_id).current_mtu() -6833 } -6834 -6835 /// Triggers path validation on all paths -6836 #[cfg(test)] -6837 pub(crate) fn trigger_path_validation(&mut self) { -6838 for path in self.paths.values_mut() { -6839 path.data.pending_on_path_challenge = true; -6840 } -6841 } -6842 -6843 /// Simulates a protocol violation error for test purposes. -6844 #[cfg(test)] -6845 pub fn simulate_protocol_violation(&mut self, now: Instant) { -6846 if !self.state.is_closed() { -6847 self.state -6848 .move_to_closed(TransportError::PROTOCOL_VIOLATION("simulated violation")); -6849 self.close_common(); -6850 if !self.state.is_drained() { -6851 self.set_close_timer(now); -6852 } -6853 self.connection_close_pending = true; -6854 } -6855 } -6856 -6857 /// Whether we have on-path 1-RTT data to send. -6858 /// -6859 /// This checks for frames that can only be sent in the data space (1-RTT): -6860 /// - Pending PATH_CHALLENGE frames on the active and previous path if just migrated. -6861 /// - Pending PATH_RESPONSE frames. -6862 /// - Pending data to send in STREAM frames. -6863 /// - Pending DATAGRAM frames to send. -6864 /// -6865 /// See also [`PacketSpace::can_send`] which keeps track of all other frame types that -6866 /// may need to be sent. -6867 fn can_send_1rtt(&self, path_id: PathId, max_size: usize) -> SendableFrames { -6868 let space_specific = self.paths.get(&path_id).is_some_and(|path| { -6869 path.data.pending_on_path_challenge || !path.data.path_responses.is_empty() -6870 }); -6871 -6872 // Stream control frames are checked in PacketSpace::can_send, only check data here. -6873 let other = self.streams.can_send_stream_data() -6874 || self -6875 .datagrams -6876 .outgoing -6877 .front() -6878 .is_some_and(|x| x.size(true) <= max_size); +6823 #[cfg(test)] +6824 #[track_caller] +6825 pub(crate) fn active_local_path_cid_seq(&self, path_id: u32) -> (u64, u64) { +6826 self.local_cid_state +6827 .get(&PathId(path_id)) +6828 .unwrap() +6829 .active_seq() +6830 } +6831 +6832 /// Instruct the peer to replace previously issued CIDs by sending a NEW_CONNECTION_ID frame +6833 /// with updated `retire_prior_to` field set to `v` +6834 #[cfg(test)] +6835 pub(crate) fn rotate_local_cid(&mut self, v: u64, now: Instant) { +6836 let n = self +6837 .local_cid_state +6838 .get_mut(&PathId::ZERO) +6839 .unwrap() +6840 .assign_retire_seq(v); +6841 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events +6842 self.endpoint_events +6843 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); +6844 } +6845 +6846 /// Check the current active remote CID sequence for `PathId::ZERO` +6847 #[cfg(test)] +6848 pub(crate) fn active_remote_cid_seq(&self) -> u64 { +6849 self.remote_cids.get(&PathId::ZERO).unwrap().active_seq() +6850 } +6851 +6852 /// Returns the detected maximum udp payload size for the current path +6853 #[cfg(test)] +6854 pub(crate) fn path_mtu(&self, path_id: PathId) -> u16 { +6855 self.path_data(path_id).current_mtu() +6856 } +6857 +6858 /// Triggers path validation on all paths +6859 #[cfg(test)] +6860 pub(crate) fn trigger_path_validation(&mut self) { +6861 for path in self.paths.values_mut() { +6862 path.data.pending_on_path_challenge = true; +6863 } +6864 } +6865 +6866 /// Simulates a protocol violation error for test purposes. +6867 #[cfg(test)] +6868 pub fn simulate_protocol_violation(&mut self, now: Instant) { +6869 if !self.state.is_closed() { +6870 self.state +6871 .move_to_closed(TransportError::PROTOCOL_VIOLATION("simulated violation")); +6872 self.close_common(); +6873 if !self.state.is_drained() { +6874 self.set_close_timer(now); +6875 } +6876 self.connection_close_pending = true; +6877 } +6878 } 6879 -6880 // All `false` fields are set in PacketSpace::can_send. -6881 SendableFrames { -6882 acks: false, -6883 close: false, -6884 space_specific, -6885 other, -6886 } -6887 } -6888 -6889 /// Terminate the connection instantly, without sending a close packet -6890 fn kill(&mut self, reason: ConnectionError) { -6891 self.close_common(); -6892 self.state.move_to_drained(Some(reason)); -6893 // move_to_drained checks that we were never in drained before, so we -6894 // never sent a `Drained` event before (it's illegal to send more events after drained). -6895 self.endpoint_events.push_back(EndpointEventInner::Drained); -6896 } -6897 -6898 /// Storage size required for the largest packet that can be transmitted on all currently -6899 /// available paths -6900 /// -6901 /// Buffers passed to [`Connection::poll_transmit`] should be at least this large. -6902 /// -6903 /// When multipath is enabled, this value is the minimum MTU across all available paths. -6904 pub fn current_mtu(&self) -> u16 { -6905 self.paths -6906 .iter() -6907 .filter(|&(path_id, _path_state)| !self.abandoned_paths.contains(path_id)) -6908 .map(|(_path_id, path_state)| path_state.data.current_mtu()) -6909 .min() -6910 .unwrap_or(INITIAL_MTU) -6911 } -6912 -6913 /// Size of non-frame data for a 1-RTT packet -6914 /// -6915 /// Quantifies space consumed by the QUIC header and AEAD tag. All other bytes in a packet are -6916 /// frames. Changes if the length of the remote connection ID changes, which is expected to be -6917 /// rare. If `pn` is specified, may additionally change unpredictably due to variations in -6918 /// latency and packet loss. -6919 fn predict_1rtt_overhead(&mut self, pn: u64, path: PathId) -> usize { -6920 let pn_len = PacketNumber::new( -6921 pn, -6922 self.spaces[SpaceId::Data] -6923 .for_path(path) -6924 .largest_acked_packet_pn -6925 .unwrap_or(0), -6926 ) -6927 .len(); -6928 -6929 // 1 byte for flags -6930 1 + self -6931 .remote_cids -6932 .get(&path) -6933 .map(|cids| cids.active().len()) -6934 .unwrap_or(20) // Max CID len in QUIC v1 -6935 + pn_len -6936 + self.tag_len_1rtt() -6937 } -6938 -6939 fn predict_1rtt_overhead_no_pn(&self) -> usize { -6940 let pn_len = 4; -6941 -6942 let cid_len = self -6943 .remote_cids -6944 .values() -6945 .map(|cids| cids.active().len()) -6946 .max() -6947 .unwrap_or(20); // Max CID len in QUIC v1 -6948 -6949 // 1 byte for flags -6950 1 + cid_len + pn_len + self.tag_len_1rtt() -6951 } -6952 -6953 fn tag_len_1rtt(&self) -> usize { -6954 // encryption_keys for Data space returns 1-RTT keys if available, otherwise 0-RTT keys -6955 let packet_crypto = self -6956 .crypto_state -6957 .encryption_keys(SpaceKind::Data, self.side.side()) -6958 .map(|(_header, packet, _level)| packet); -6959 // If neither Data nor 0-RTT keys are available, make a reasonable tag length guess. As of -6960 // this writing, all QUIC cipher suites use 16-byte tags. We could return `None` instead, -6961 // but that would needlessly prevent sending datagrams during 0-RTT. -6962 packet_crypto.map_or(16, |x| x.tag_len()) -6963 } +6880 /// Whether we have on-path 1-RTT data to send. +6881 /// +6882 /// This checks for frames that can only be sent in the data space (1-RTT): +6883 /// - Pending PATH_CHALLENGE frames on the active and previous path if just migrated. +6884 /// - Pending PATH_RESPONSE frames. +6885 /// - Pending data to send in STREAM frames. +6886 /// - Pending DATAGRAM frames to send. +6887 /// +6888 /// See also [`PacketSpace::can_send`] which keeps track of all other frame types that +6889 /// may need to be sent. +6890 fn can_send_1rtt(&self, path_id: PathId, max_size: usize) -> SendableFrames { +6891 let space_specific = self.paths.get(&path_id).is_some_and(|path| { +6892 path.data.pending_on_path_challenge || !path.data.path_responses.is_empty() +6893 }); +6894 +6895 // Stream control frames are checked in PacketSpace::can_send, only check data here. +6896 let other = self.streams.can_send_stream_data() +6897 || self +6898 .datagrams +6899 .outgoing +6900 .front() +6901 .is_some_and(|x| x.size(true) <= max_size); +6902 +6903 // All `false` fields are set in PacketSpace::can_send. +6904 SendableFrames { +6905 acks: false, +6906 close: false, +6907 space_specific, +6908 other, +6909 } +6910 } +6911 +6912 /// Terminate the connection instantly, without sending a close packet +6913 fn kill(&mut self, reason: ConnectionError) { +6914 self.close_common(); +6915 self.state.move_to_drained(Some(reason)); +6916 // move_to_drained checks that we were never in drained before, so we +6917 // never sent a `Drained` event before (it's illegal to send more events after drained). +6918 self.endpoint_events.push_back(EndpointEventInner::Drained); +6919 } +6920 +6921 /// Storage size required for the largest packet that can be transmitted on all currently +6922 /// available paths +6923 /// +6924 /// Buffers passed to [`Connection::poll_transmit`] should be at least this large. +6925 /// +6926 /// When multipath is enabled, this value is the minimum MTU across all available paths. +6927 pub fn current_mtu(&self) -> u16 { +6928 self.paths +6929 .iter() +6930 .filter(|&(path_id, _path_state)| !self.abandoned_paths.contains(path_id)) +6931 .map(|(_path_id, path_state)| path_state.data.current_mtu()) +6932 .min() +6933 .unwrap_or(INITIAL_MTU) +6934 } +6935 +6936 /// Size of non-frame data for a 1-RTT packet +6937 /// +6938 /// Quantifies space consumed by the QUIC header and AEAD tag. All other bytes in a packet are +6939 /// frames. Changes if the length of the remote connection ID changes, which is expected to be +6940 /// rare. If `pn` is specified, may additionally change unpredictably due to variations in +6941 /// latency and packet loss. +6942 fn predict_1rtt_overhead(&mut self, pn: u64, path: PathId) -> usize { +6943 let pn_len = PacketNumber::new( +6944 pn, +6945 self.spaces[SpaceId::Data] +6946 .for_path(path) +6947 .largest_acked_packet_pn +6948 .unwrap_or(0), +6949 ) +6950 .len(); +6951 +6952 // 1 byte for flags +6953 1 + self +6954 .remote_cids +6955 .get(&path) +6956 .map(|cids| cids.active().len()) +6957 .unwrap_or(20) // Max CID len in QUIC v1 +6958 + pn_len +6959 + self.tag_len_1rtt() +6960 } +6961 +6962 fn predict_1rtt_overhead_no_pn(&self) -> usize { +6963 let pn_len = 4; 6964 -6965 /// Mark the path as validated, and enqueue NEW_TOKEN frames to be sent as appropriate -6966 fn on_path_validated(&mut self, path_id: PathId) { -6967 self.path_data_mut(path_id).validated = true; -6968 let ConnectionSide::Server { server_config } = &self.side else { -6969 return; -6970 }; -6971 let network_path = self.path_data(path_id).network_path; -6972 let new_tokens = &mut self.spaces[SpaceId::Data as usize].pending.new_tokens; -6973 new_tokens.clear(); -6974 for _ in 0..server_config.validation_token.sent { -6975 new_tokens.push(network_path); -6976 } -6977 } -6978 -6979 /// Handle new path status information: PATH_STATUS_AVAILABLE, PATH_STATUS_BACKUP -6980 fn on_path_status(&mut self, path_id: PathId, status: PathStatus, status_seq_no: VarInt) { -6981 if let Some(path) = self.paths.get_mut(&path_id) { -6982 path.data.status.remote_update(status, status_seq_no); -6983 } else { -6984 debug!("PATH_STATUS_AVAILABLE received unknown path {:?}", path_id); -6985 } -6986 self.events.push_back( -6987 PathEvent::RemoteStatus { -6988 id: path_id, -6989 status, -6990 } -6991 .into(), -6992 ); -6993 } -6994 -6995 /// Returns the maximum [`PathId`] to be used for sending in this connection. -6996 /// -6997 /// This is calculated as minimum between the local and remote's maximums when multipath is -6998 /// enabled, or `None` when disabled. -6999 /// -7000 /// For data that's received, we should use [`Self::local_max_path_id`] instead. -7001 /// The reasoning is that the remote might already have updated to its own newer -7002 /// [`Self::max_path_id`] after sending out a `MAX_PATH_ID` frame, but it got re-ordered. -7003 fn max_path_id(&self) -> Option<PathId> { -7004 if self.is_multipath_negotiated() { -7005 Some(self.remote_max_path_id.min(self.local_max_path_id)) +6965 let cid_len = self +6966 .remote_cids +6967 .values() +6968 .map(|cids| cids.active().len()) +6969 .max() +6970 .unwrap_or(20); // Max CID len in QUIC v1 +6971 +6972 // 1 byte for flags +6973 1 + cid_len + pn_len + self.tag_len_1rtt() +6974 } +6975 +6976 fn tag_len_1rtt(&self) -> usize { +6977 // encryption_keys for Data space returns 1-RTT keys if available, otherwise 0-RTT keys +6978 let packet_crypto = self +6979 .crypto_state +6980 .encryption_keys(SpaceKind::Data, self.side.side()) +6981 .map(|(_header, packet, _level)| packet); +6982 // If neither Data nor 0-RTT keys are available, make a reasonable tag length guess. As of +6983 // this writing, all QUIC cipher suites use 16-byte tags. We could return `None` instead, +6984 // but that would needlessly prevent sending datagrams during 0-RTT. +6985 packet_crypto.map_or(16, |x| x.tag_len()) +6986 } +6987 +6988 /// Mark the path as validated, and enqueue NEW_TOKEN frames to be sent as appropriate +6989 fn on_path_validated(&mut self, path_id: PathId) { +6990 self.path_data_mut(path_id).validated = true; +6991 let ConnectionSide::Server { server_config } = &self.side else { +6992 return; +6993 }; +6994 let network_path = self.path_data(path_id).network_path; +6995 let new_tokens = &mut self.spaces[SpaceId::Data as usize].pending.new_tokens; +6996 new_tokens.clear(); +6997 for _ in 0..server_config.validation_token.sent { +6998 new_tokens.push(network_path); +6999 } +7000 } +7001 +7002 /// Handle new path status information: PATH_STATUS_AVAILABLE, PATH_STATUS_BACKUP +7003 fn on_path_status(&mut self, path_id: PathId, status: PathStatus, status_seq_no: VarInt) { +7004 if let Some(path) = self.paths.get_mut(&path_id) { +7005 path.data.status.remote_update(status, status_seq_no); 7006 } else { -7007 None -7008 } -7009 } -7010 -7011 /// Returns whether this connection has a socket that supports IPv6. -7012 /// -7013 /// TODO(matheus23): This is related to noq endpoint state's `ipv6` bool. We should move that info -7014 /// here instead of trying to hack around not knowing it exactly. -7015 fn is_ipv6(&self) -> bool { -7016 self.paths -7017 .values() -7018 .any(|p| p.data.network_path.remote.is_ipv6()) -7019 } -7020 -7021 /// Add addresses the local endpoint considers are reachable for nat traversal. -7022 pub fn add_nat_traversal_address( -7023 &mut self, -7024 address: SocketAddr, -7025 ) -> Result<(), n0_nat_traversal::Error> { -7026 if let Some(added) = self.n0_nat_traversal.add_local_address(address)? { -7027 self.spaces[SpaceId::Data].pending.add_address.insert(added); -7028 }; -7029 Ok(()) -7030 } -7031 -7032 /// Removes an address the endpoing no longer considers reachable for nat traversal -7033 /// -7034 /// Addresses not present in the set will be silently ignored. -7035 pub fn remove_nat_traversal_address( -7036 &mut self, -7037 address: SocketAddr, -7038 ) -> Result<(), n0_nat_traversal::Error> { -7039 if let Some(removed) = self.n0_nat_traversal.remove_local_address(address)? { -7040 self.spaces[SpaceId::Data] -7041 .pending -7042 .remove_address -7043 .insert(removed); -7044 } -7045 Ok(()) -7046 } -7047 -7048 /// Get the current local nat traversal addresses -7049 pub fn get_local_nat_traversal_addresses( -7050 &self, -7051 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -7052 self.n0_nat_traversal.get_local_nat_traversal_addresses() +7007 debug!("PATH_STATUS_AVAILABLE received unknown path {:?}", path_id); +7008 } +7009 self.events.push_back( +7010 PathEvent::RemoteStatus { +7011 id: path_id, +7012 status, +7013 } +7014 .into(), +7015 ); +7016 } +7017 +7018 /// Returns the maximum [`PathId`] to be used for sending in this connection. +7019 /// +7020 /// This is calculated as minimum between the local and remote's maximums when multipath is +7021 /// enabled, or `None` when disabled. +7022 /// +7023 /// For data that's received, we should use [`Self::local_max_path_id`] instead. +7024 /// The reasoning is that the remote might already have updated to its own newer +7025 /// [`Self::max_path_id`] after sending out a `MAX_PATH_ID` frame, but it got re-ordered. +7026 fn max_path_id(&self) -> Option<PathId> { +7027 if self.is_multipath_negotiated() { +7028 Some(self.remote_max_path_id.min(self.local_max_path_id)) +7029 } else { +7030 None +7031 } +7032 } +7033 +7034 /// Returns whether this connection has a socket that supports IPv6. +7035 /// +7036 /// TODO(matheus23): This is related to noq endpoint state's `ipv6` bool. We should move that info +7037 /// here instead of trying to hack around not knowing it exactly. +7038 fn is_ipv6(&self) -> bool { +7039 self.paths +7040 .values() +7041 .any(|p| p.data.network_path.remote.is_ipv6()) +7042 } +7043 +7044 /// Add addresses the local endpoint considers are reachable for nat traversal. +7045 pub fn add_nat_traversal_address( +7046 &mut self, +7047 address: SocketAddr, +7048 ) -> Result<(), n0_nat_traversal::Error> { +7049 if let Some(added) = self.n0_nat_traversal.add_local_address(address)? { +7050 self.spaces[SpaceId::Data].pending.add_address.insert(added); +7051 }; +7052 Ok(()) 7053 } 7054 -7055 /// Get the currently advertised nat traversal addresses by the server -7056 pub fn get_remote_nat_traversal_addresses( -7057 &self, -7058 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -7059 Ok(self -7060 .n0_nat_traversal -7061 .client_side()? -7062 .get_remote_nat_traversal_addresses()) -7063 } -7064 -7065 /// Attempts to open a path for nat traversal. -7066 /// -7067 /// On success returns the [`PathId`] and remote address of the path. -7068 fn open_nat_traversal_path( -7069 &mut self, -7070 now: Instant, -7071 ip_port: (IpAddr, u16), -7072 ) -> Result<Option<(PathId, SocketAddr)>, PathError> { -7073 let remote = ip_port.into(); -7074 // TODO(matheus23): Probe the correct 4-tuple, instead of only a remote address? -7075 // By specifying None for `local_ip`, we do two things: 1. open_path_ensure won't -7076 // generate two paths to the same remote and 2. we let the OS choose which -7077 // interface to use for sending on that path. -7078 let network_path = FourTuple { -7079 remote, -7080 local_ip: None, -7081 }; -7082 match self.open_path_ensure(network_path, PathStatus::Backup, now) { -7083 Ok((path_id, path_was_known)) => { -7084 if path_was_known { -7085 trace!(%path_id, %remote, "nat traversal: path existed for remote, revalidating"); -7086 if let Some(path) = self.paths.get_mut(&path_id) { -7087 use paths::OpenStatus::*; -7088 -7089 path.data.pending_on_path_challenge = true; -7090 path.data.open_status = match path.data.open_status { -7091 // If we just opened the path and have never sent a `PATH_CHALLENGE` yet, -7092 // then we need to keep it at pending, to ensure that -7093 // 1. The PathOpenFailed timer for stopping the PathChallengeLost retries will be set. -7094 // 2. When validation eventually succeeds, then we inform the application layer about this path opening. -7095 Pending => Pending, -7096 // If we had already sent a path challenge in the past, but it hasn't been validated yet (and also not -7097 // failed via the PathOpenFailed timer yet), then we need to go back to pending, to ensure we properly -7098 // re-arm the `PathOpenFailed` timer again. -7099 Sent => Pending, -7100 // If we're already revalidating this path, but haven't sent a `PATH_CHALLENGE` yet, then we just keep -7101 // that state. -7102 Revalidating => Revalidating, -7103 // If we've informed the application layer about the path opening in the past, but we now re-send -7104 // PATH_CHALLENGEs for validation, then using this we ensure: -7105 // 1. The PathOpenFailed timer for stopping the PathChallengeLost retries will be set. -7106 // 2. When validation eventually succeeds, we *don't* inform the application layer about the path -7107 // opening again. -7108 Informed => Revalidating, -7109 } -7110 } -7111 } -7112 Ok(Some((path_id, remote))) -7113 } -7114 Err(e) => { -7115 debug!(%remote, %e, "nat traversal: failed to probe remote"); -7116 Err(e) -7117 } +7055 /// Removes an address the endpoing no longer considers reachable for nat traversal +7056 /// +7057 /// Addresses not present in the set will be silently ignored. +7058 pub fn remove_nat_traversal_address( +7059 &mut self, +7060 address: SocketAddr, +7061 ) -> Result<(), n0_nat_traversal::Error> { +7062 if let Some(removed) = self.n0_nat_traversal.remove_local_address(address)? { +7063 self.spaces[SpaceId::Data] +7064 .pending +7065 .remove_address +7066 .insert(removed); +7067 } +7068 Ok(()) +7069 } +7070 +7071 /// Get the current local nat traversal addresses +7072 pub fn get_local_nat_traversal_addresses( +7073 &self, +7074 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { +7075 self.n0_nat_traversal.get_local_nat_traversal_addresses() +7076 } +7077 +7078 /// Get the currently advertised nat traversal addresses by the server +7079 pub fn get_remote_nat_traversal_addresses( +7080 &self, +7081 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { +7082 Ok(self +7083 .n0_nat_traversal +7084 .client_side()? +7085 .get_remote_nat_traversal_addresses()) +7086 } +7087 +7088 /// Initiates a new nat traversal round +7089 /// +7090 /// A nat traversal round involves advertising the client's local addresses in +7091 /// `REACH_OUT` frames, and initiating probing of the known remote addresses. When a new +7092 /// round is initiated, the previous one is cancelled. +7093 /// +7094 /// For all probes that succeed, if any, a new path will be opened on the successful +7095 /// 4-tuple. +7096 /// +7097 /// Returns the server addresses that are now being probed. If addresses fail due to +7098 /// spurious errors, these might succeed later and not be returned in this set. +7099 pub fn initiate_nat_traversal_round( +7100 &mut self, +7101 now: Instant, +7102 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { +7103 if self.state.is_closed() { +7104 return Err(n0_nat_traversal::Error::Closed); +7105 } +7106 +7107 let ipv6 = self.is_ipv6(); +7108 let client_state = self.n0_nat_traversal.client_side_mut()?; +7109 let (mut reach_out_frames, probed_addrs) = +7110 client_state.initiate_nat_traversal_round(ipv6)?; +7111 if !probed_addrs.is_empty() { +7112 let delay = RttEstimator::new(self.config.initial_rtt).pto_base() * 2 / 3; +7113 self.timers.set( +7114 Timer::Conn(ConnTimer::NatTraversalProbeRetry), +7115 now + delay, +7116 self.qlog.with_time(now), +7117 ); 7118 } -7119 } -7120 -7121 /// Initiates a new nat traversal round -7122 /// -7123 /// A nat traversal round involves advertising the client's local addresses in -7124 /// `REACH_OUT` frames, and initiating probing of the known remote addresses. When a new -7125 /// round is initiated, the previous one is cancelled. -7126 /// -7127 /// For all probes that succeed, if any, a new path will be opened on the successful -7128 /// 4-tuple. +7119 +7120 self.spaces[SpaceId::Data] +7121 .pending +7122 .reach_out +7123 .append(&mut reach_out_frames); +7124 +7125 Ok(probed_addrs) +7126 } +7127 +7128 /// Whether the handshake is considered **confirmed**. 7129 /// -7130 /// Returns the server addresses that are now being probed. If addresses fail due to -7131 /// spurious errors, these might succeed later and not be returned in this set. -7132 pub fn initiate_nat_traversal_round( -7133 &mut self, -7134 now: Instant, -7135 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -7136 if self.state.is_closed() { -7137 return Err(n0_nat_traversal::Error::Closed); -7138 } -7139 -7140 let ipv6 = self.is_ipv6(); -7141 let client_state = self.n0_nat_traversal.client_side_mut()?; -7142 let n0_nat_traversal::NatTraversalRound { -7143 new_round, -7144 reach_out_at, -7145 addresses_to_probe, -7146 prev_round_path_ids, -7147 } = client_state.initiate_nat_traversal_round(ipv6)?; +7130 /// <https://www.rfc-editor.org/rfc/rfc9001#section-4.1.2> defines a handshake to be +7131 /// confirmed when you know the peer successfully received and successfully processed +7132 /// your TLS Finished message. +7133 /// +7134 /// Implementation-wise this is the point at which the handshake crypto keys are +7135 /// discarded. So we can use this to know if the handshake is confirmed. +7136 fn is_handshake_confirmed(&self) -> bool { +7137 !self.is_handshaking() && !self.crypto_state.has_keys(EncryptionLevel::Handshake) +7138 } +7139} +7140 +7141impl fmt::Debug for Connection { +7142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +7143 f.debug_struct("Connection") +7144 .field("handshake_cid", &self.handshake_cid) +7145 .finish() +7146 } +7147} 7148 -7149 trace!(%new_round, reach_out=reach_out_at.len(), to_probe=addresses_to_probe.len(), -7150 "initiating nat traversal round"); -7151 -7152 self.spaces[SpaceId::Data].pending.reach_out = Some((new_round, reach_out_at)); -7153 -7154 for path_id in prev_round_path_ids { -7155 let Some(path) = self.path(path_id) else { -7156 continue; -7157 }; -7158 let ip = path.network_path.remote.ip(); -7159 let port = path.network_path.remote.port(); -7160 -7161 // We only close paths that aren't validated (thus are working) that we opened -7162 // in a previous round. -7163 // And we only close paths that we don't want to probe anyways. -7164 if !addresses_to_probe -7165 .iter() -7166 .any(|(_, probe)| *probe == (ip, port)) -7167 && !path.validated -7168 && !self.abandoned_paths.contains(&path_id) -7169 { -7170 trace!(%path_id, "closing path from previous round"); -7171 let _ = -7172 self.close_path_inner(now, path_id, PathAbandonReason::NatTraversalRoundEnded); -7173 } -7174 } -7175 -7176 let mut err = None; -7177 -7178 let mut path_ids = Vec::with_capacity(addresses_to_probe.len()); -7179 let mut probed_addresses = Vec::with_capacity(addresses_to_probe.len()); -7180 -7181 for (id, address) in addresses_to_probe { -7182 match self.open_nat_traversal_path(now, address) { -7183 Ok(None) => {} -7184 Ok(Some((path_id, remote))) => { -7185 path_ids.push(path_id); -7186 probed_addresses.push(remote); -7187 } -7188 Err(e) => { -7189 self.n0_nat_traversal -7190 .client_side_mut() -7191 .expect("validated") -7192 .report_in_continuation(id, e); -7193 err.get_or_insert(e); -7194 } -7195 } -7196 } -7197 -7198 if let Some(err) = err { -7199 // We failed to probe any addresses, bail out -7200 if probed_addresses.is_empty() { -7201 return Err(n0_nat_traversal::Error::Multipath(err)); -7202 } -7203 } -7204 -7205 self.n0_nat_traversal -7206 .client_side_mut() -7207 .expect("connection side validated") -7208 .set_round_path_ids(path_ids); -7209 -7210 Ok(probed_addresses) -7211 } -7212 -7213 /// Attempts to continue a nat traversal round by trying to open paths for pending client probes. -7214 /// -7215 /// If there was nothing to do, it returns `None`. Otherwise it returns whether the path was -7216 /// successfully open. -7217 fn continue_nat_traversal_round(&mut self, now: Instant) -> Option<bool> { -7218 let ipv6 = self.is_ipv6(); -7219 let client_state = self.n0_nat_traversal.client_side_mut().ok()?; -7220 let (id, address) = client_state.continue_nat_traversal_round(ipv6)?; -7221 let open_result = self.open_nat_traversal_path(now, address); -7222 let client_state = self.n0_nat_traversal.client_side_mut().expect("validated"); -7223 match open_result { -7224 Ok(None) => Some(true), -7225 Ok(Some((path_id, _remote))) => { -7226 client_state.add_round_path_id(path_id); -7227 Some(true) -7228 } -7229 Err(e) => { -7230 client_state.report_in_continuation(id, e); -7231 Some(false) -7232 } -7233 } -7234 } -7235 -7236 /// Whether the handshake is considered **confirmed**. -7237 /// -7238 /// <https://www.rfc-editor.org/rfc/rfc9001#section-4.1.2> defines a handshake to be -7239 /// confirmed when you know the peer successfully received and successfully processed -7240 /// your TLS Finished message. -7241 /// -7242 /// Implementation-wise this is the point at which the handshake crypto keys are -7243 /// discarded. So we can use this to know if the handshake is confirmed. -7244 fn is_handshake_confirmed(&self) -> bool { -7245 !self.is_handshaking() && !self.crypto_state.has_keys(EncryptionLevel::Handshake) -7246 } +7149/// Hints when the caller identifies a network change. +7150pub trait NetworkChangeHint: std::fmt::Debug + 'static { +7151 /// Inform the connection if a path may recover after a network change. +7152 /// +7153 /// After network changes, paths may not be recoverable. In this case, waiting for the path to +7154 /// become idle may take longer than what is desirable. If [`Self::is_path_recoverable`] +7155 /// returns `false`, a multipath-enabled, client-side connection will establish a new path to +7156 /// the same remote, closing the current one, instead of migrating the path. +7157 /// +7158 /// Paths that are deemed recoverable will simply be sent a PING for a liveness check. +7159 fn is_path_recoverable(&self, path_id: PathId, network_path: FourTuple) -> bool; +7160} +7161 +7162/// Return value for [`Connection::poll_transmit_path_space`]. +7163#[derive(Debug)] +7164enum PollPathSpaceStatus { +7165 /// Nothing to send in the space, nothing was written into the [`TransmitBuf`]. +7166 NothingToSend { +7167 /// If true there was data to send but congestion control did not allow so. +7168 congestion_blocked: bool, +7169 }, +7170 /// One or more packets have been written into the [`TransmitBuf`]. +7171 WrotePacket { +7172 /// The highest packet number. +7173 last_packet_number: u64, +7174 /// Whether to pad an already started datagram in the next packet. +7175 /// +7176 /// When packets in Initial, 0-RTT or Handshake packet do not fill the entire +7177 /// datagram they may decide to coalesce with the next packet from a higher +7178 /// encryption level on the same path. But the earlier packet may require specific +7179 /// size requirements for the datagram they are sent in. +7180 /// +7181 /// If a space did not complete the datagram, they use this to request the correct +7182 /// padding in the final packet of the datagram so that the final datagram will have +7183 /// the correct size. +7184 /// +7185 /// If a space did fill an entire datagram, it leaves this to the default of +7186 /// [`PadDatagram::No`]. +7187 pad_datagram: PadDatagram, +7188 }, +7189 /// Send the contents of the transmit immediately. +7190 /// +7191 /// Packets were written and the GSO batch must end now, regardless from whether higher +7192 /// spaces still have frames to write. This is used when the last datagram written would +7193 /// require too much padding to continue a GSO batch, which would waste space on the +7194 /// wire. +7195 Send { +7196 /// The highest packet number written into the transmit. +7197 last_packet_number: u64, +7198 }, +7199} +7200 +7201/// Information used to decide what frames to schedule into which packets. +7202/// +7203/// Primarily used by [`Connection::poll_transmit_on_path`] and the functions that help +7204/// building packets for it: [`Connection::poll_transmit_path_space`] and +7205/// [`Connection::populate_packet`]. +7206#[derive(Debug, Copy, Clone)] +7207struct PathSchedulingInfo { +7208 /// Whether the path is abandoned. +7209 /// +7210 /// Note that a path that is abandoned but still has CIDs can still send a packet. After +7211 /// sending that packet the CIDs issued by the remote have to be considered retired as +7212 /// well. +7213 is_abandoned: bool, +7214 /// Whether the path may send [`SpaceKind::Data`] frames. +7215 /// +7216 /// Some paths should only send frames from [`SendableFrames::space_specific`]. All other +7217 /// frames are essentially frames that can be sent on any [`SpaceKind::Data`] space. For +7218 /// those we want to respect packet scheduling rules however. +7219 /// +7220 /// Roughly speaking data frames are only sent on spaces that have CIDs, are not +7221 /// abandoned and have no *better* spaces. However see to comments where this is +7222 /// populated for the exact packet scheduling implementation. +7223 /// +7224 /// This essentially marks this paths as the best validated space ID. Except during +7225 /// the handshake in which case it does not need to be validated. Several paths could be +7226 /// equally good and all have this set to `true`, in that case packet scheduling can +7227 /// choose which path to use. Currently it chooses the lowest path that is not +7228 /// congestion blocked. +7229 /// +7230 /// Note that once in the closed or draining states this will never be true. +7231 may_send_data: bool, +7232 /// Whether the path may send a CONNECTION_CLOSE frame. +7233 /// +7234 /// This essentially marks this path as the best validated space ID with a fallback +7235 /// to unvalidated spaces if there are no validated spaces. Like for +7236 /// [`Self::may_send_data`] other paths could be equally good. +7237 may_send_close: bool, +7238 may_self_abandon: bool, +7239} +7240 +7241#[derive(Debug, Copy, Clone, PartialEq, Eq)] +7242enum PathBlocked { +7243 No, +7244 AntiAmplification, +7245 Congestion, +7246 Pacing, 7247} 7248 -7249impl fmt::Debug for Connection { -7250 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -7251 f.debug_struct("Connection") -7252 .field("handshake_cid", &self.handshake_cid) -7253 .finish() -7254 } -7255} -7256 -7257/// Hints when the caller identifies a network change. -7258pub trait NetworkChangeHint: std::fmt::Debug + 'static { -7259 /// Inform the connection if a path may recover after a network change. -7260 /// -7261 /// After network changes, paths may not be recoverable. In this case, waiting for the path to -7262 /// become idle may take longer than what is desirable. If [`Self::is_path_recoverable`] -7263 /// returns `false`, a multipath-enabled, client-side connection will establish a new path to -7264 /// the same remote, closing the current one, instead of migrating the path. -7265 /// -7266 /// Paths that are deemed recoverable will simply be sent a PING for a liveness check. -7267 fn is_path_recoverable(&self, path_id: PathId, network_path: FourTuple) -> bool; -7268} -7269 -7270/// Return value for [`Connection::poll_transmit_path_space`]. -7271#[derive(Debug)] -7272enum PollPathSpaceStatus { -7273 /// Nothing to send in the space, nothing was written into the [`TransmitBuf`]. -7274 NothingToSend { -7275 /// If true there was data to send but congestion control did not allow so. -7276 congestion_blocked: bool, -7277 }, -7278 /// One or more packets have been written into the [`TransmitBuf`]. -7279 WrotePacket { -7280 /// The highest packet number. -7281 last_packet_number: u64, -7282 /// Whether to pad an already started datagram in the next packet. -7283 /// -7284 /// When packets in Initial, 0-RTT or Handshake packet do not fill the entire -7285 /// datagram they may decide to coalesce with the next packet from a higher -7286 /// encryption level on the same path. But the earlier packet may require specific -7287 /// size requirements for the datagram they are sent in. -7288 /// -7289 /// If a space did not complete the datagram, they use this to request the correct -7290 /// padding in the final packet of the datagram so that the final datagram will have -7291 /// the correct size. -7292 /// -7293 /// If a space did fill an entire datagram, it leaves this to the default of -7294 /// [`PadDatagram::No`]. -7295 pad_datagram: PadDatagram, -7296 }, -7297 /// Send the contents of the transmit immediately. -7298 /// -7299 /// Packets were written and the GSO batch must end now, regardless from whether higher -7300 /// spaces still have frames to write. This is used when the last datagram written would -7301 /// require too much padding to continue a GSO batch, which would waste space on the -7302 /// wire. -7303 Send { -7304 /// The highest packet number written into the transmit. -7305 last_packet_number: u64, -7306 }, -7307} -7308 -7309/// Information used to decide what frames to schedule into which packets. -7310/// -7311/// Primarily used by [`Connection::poll_transmit_on_path`] and the functions that help -7312/// building packets for it: [`Connection::poll_transmit_path_space`] and -7313/// [`Connection::populate_packet`]. -7314#[derive(Debug, Copy, Clone)] -7315struct PathSchedulingInfo { -7316 /// Whether the path is abandoned. -7317 /// -7318 /// Note that a path that is abandoned but still has CIDs can still send a packet. After -7319 /// sending that packet the CIDs issued by the remote have to be considered retired as -7320 /// well. -7321 is_abandoned: bool, -7322 /// Whether the path may send [`SpaceKind::Data`] frames. -7323 /// -7324 /// Some paths should only send frames from [`SendableFrames::space_specific`]. All other -7325 /// frames are essentially frames that can be sent on any [`SpaceKind::Data`] space. For -7326 /// those we want to respect packet scheduling rules however. -7327 /// -7328 /// Roughly speaking data frames are only sent on spaces that have CIDs, are not -7329 /// abandoned and have no *better* spaces. However see to comments where this is -7330 /// populated for the exact packet scheduling implementation. -7331 /// -7332 /// This essentially marks this paths as the best validated space ID. Except during -7333 /// the handshake in which case it does not need to be validated. Several paths could be -7334 /// equally good and all have this set to `true`, in that case packet scheduling can -7335 /// choose which path to use. Currently it chooses the lowest path that is not -7336 /// congestion blocked. -7337 /// -7338 /// Note that once in the closed or draining states this will never be true. -7339 may_send_data: bool, -7340 /// Whether the path may send a CONNECTION_CLOSE frame. -7341 /// -7342 /// This essentially marks this path as the best validated space ID with a fallback -7343 /// to unvalidated spaces if there are no validated spaces. Like for -7344 /// [`Self::may_send_data`] other paths could be equally good. -7345 may_send_close: bool, -7346 may_self_abandon: bool, -7347} -7348 -7349#[derive(Debug, Copy, Clone, PartialEq, Eq)] -7350enum PathBlocked { -7351 No, -7352 AntiAmplification, -7353 Congestion, -7354 Pacing, -7355} -7356 -7357/// Fields of `Connection` specific to it being client-side or server-side -7358enum ConnectionSide { -7359 Client { -7360 /// Sent in every outgoing Initial packet. Always empty after Initial keys are discarded -7361 token: Bytes, -7362 token_store: Arc<dyn TokenStore>, -7363 server_name: String, -7364 }, -7365 Server { -7366 server_config: Arc<ServerConfig>, -7367 }, +7249/// Fields of `Connection` specific to it being client-side or server-side +7250enum ConnectionSide { +7251 Client { +7252 /// Sent in every outgoing Initial packet. Always empty after Initial keys are discarded +7253 token: Bytes, +7254 token_store: Arc<dyn TokenStore>, +7255 server_name: String, +7256 }, +7257 Server { +7258 server_config: Arc<ServerConfig>, +7259 }, +7260} +7261 +7262impl ConnectionSide { +7263 fn is_client(&self) -> bool { +7264 self.side().is_client() +7265 } +7266 +7267 fn is_server(&self) -> bool { +7268 self.side().is_server() +7269 } +7270 +7271 fn side(&self) -> Side { +7272 match *self { +7273 Self::Client { .. } => Side::Client, +7274 Self::Server { .. } => Side::Server, +7275 } +7276 } +7277} +7278 +7279impl From<SideArgs> for ConnectionSide { +7280 fn from(side: SideArgs) -> Self { +7281 match side { +7282 SideArgs::Client { +7283 token_store, +7284 server_name, +7285 } => Self::Client { +7286 token: token_store.take(&server_name).unwrap_or_default(), +7287 token_store, +7288 server_name, +7289 }, +7290 SideArgs::Server { +7291 server_config, +7292 pref_addr_cid: _, +7293 path_validated: _, +7294 } => Self::Server { server_config }, +7295 } +7296 } +7297} +7298 +7299/// Parameters to `Connection::new` specific to it being client-side or server-side +7300pub(crate) enum SideArgs { +7301 Client { +7302 token_store: Arc<dyn TokenStore>, +7303 server_name: String, +7304 }, +7305 Server { +7306 server_config: Arc<ServerConfig>, +7307 pref_addr_cid: Option<ConnectionId>, +7308 path_validated: bool, +7309 }, +7310} +7311 +7312impl SideArgs { +7313 pub(crate) fn pref_addr_cid(&self) -> Option<ConnectionId> { +7314 match *self { +7315 Self::Client { .. } => None, +7316 Self::Server { pref_addr_cid, .. } => pref_addr_cid, +7317 } +7318 } +7319 +7320 pub(crate) fn path_validated(&self) -> bool { +7321 match *self { +7322 Self::Client { .. } => true, +7323 Self::Server { path_validated, .. } => path_validated, +7324 } +7325 } +7326 +7327 pub(crate) fn side(&self) -> Side { +7328 match *self { +7329 Self::Client { .. } => Side::Client, +7330 Self::Server { .. } => Side::Server, +7331 } +7332 } +7333} +7334 +7335/// Reasons why a connection might be lost +7336#[derive(Debug, Error, Clone, PartialEq, Eq)] +7337pub enum ConnectionError { +7338 /// The peer doesn't implement any supported version +7339 #[error("peer doesn't implement any supported version")] +7340 VersionMismatch, +7341 /// The peer violated the QUIC specification as understood by this implementation +7342 #[error(transparent)] +7343 TransportError(#[from] TransportError), +7344 /// The peer's QUIC stack aborted the connection automatically +7345 #[error("aborted by peer: {0}")] +7346 ConnectionClosed(frame::ConnectionClose), +7347 /// The peer closed the connection +7348 #[error("closed by peer: {0}")] +7349 ApplicationClosed(frame::ApplicationClose), +7350 /// The peer is unable to continue processing this connection, usually due to having restarted +7351 #[error("reset by peer")] +7352 Reset, +7353 /// Communication with the peer has lapsed for longer than the negotiated idle timeout +7354 /// +7355 /// If neither side is sending keep-alives, a connection will time out after a long enough idle +7356 /// period even if the peer is still reachable. See also [`TransportConfig::max_idle_timeout()`] +7357 /// and [`TransportConfig::keep_alive_interval()`]. +7358 #[error("timed out")] +7359 TimedOut, +7360 /// The local application closed the connection +7361 #[error("closed")] +7362 LocallyClosed, +7363 /// The connection could not be created because not enough of the CID space is available +7364 /// +7365 /// Try using longer connection IDs. +7366 #[error("CIDs exhausted")] +7367 CidsExhausted, 7368} 7369 -7370impl ConnectionSide { -7371 fn is_client(&self) -> bool { -7372 self.side().is_client() -7373 } -7374 -7375 fn is_server(&self) -> bool { -7376 self.side().is_server() -7377 } +7370impl From<Close> for ConnectionError { +7371 fn from(x: Close) -> Self { +7372 match x { +7373 Close::Connection(reason) => Self::ConnectionClosed(reason), +7374 Close::Application(reason) => Self::ApplicationClosed(reason), +7375 } +7376 } +7377} 7378 -7379 fn side(&self) -> Side { -7380 match *self { -7381 Self::Client { .. } => Side::Client, -7382 Self::Server { .. } => Side::Server, -7383 } -7384 } -7385} -7386 -7387impl From<SideArgs> for ConnectionSide { -7388 fn from(side: SideArgs) -> Self { -7389 match side { -7390 SideArgs::Client { -7391 token_store, -7392 server_name, -7393 } => Self::Client { -7394 token: token_store.take(&server_name).unwrap_or_default(), -7395 token_store, -7396 server_name, -7397 }, -7398 SideArgs::Server { -7399 server_config, -7400 pref_addr_cid: _, -7401 path_validated: _, -7402 } => Self::Server { server_config }, -7403 } -7404 } -7405} -7406 -7407/// Parameters to `Connection::new` specific to it being client-side or server-side -7408pub(crate) enum SideArgs { -7409 Client { -7410 token_store: Arc<dyn TokenStore>, -7411 server_name: String, -7412 }, -7413 Server { -7414 server_config: Arc<ServerConfig>, -7415 pref_addr_cid: Option<ConnectionId>, -7416 path_validated: bool, -7417 }, -7418} -7419 -7420impl SideArgs { -7421 pub(crate) fn pref_addr_cid(&self) -> Option<ConnectionId> { -7422 match *self { -7423 Self::Client { .. } => None, -7424 Self::Server { pref_addr_cid, .. } => pref_addr_cid, -7425 } -7426 } -7427 -7428 pub(crate) fn path_validated(&self) -> bool { -7429 match *self { -7430 Self::Client { .. } => true, -7431 Self::Server { path_validated, .. } => path_validated, -7432 } -7433 } +7379// For compatibility with API consumers +7380impl From<ConnectionError> for io::Error { +7381 fn from(x: ConnectionError) -> Self { +7382 use ConnectionError::*; +7383 let kind = match x { +7384 TimedOut => io::ErrorKind::TimedOut, +7385 Reset => io::ErrorKind::ConnectionReset, +7386 ApplicationClosed(_) | ConnectionClosed(_) => io::ErrorKind::ConnectionAborted, +7387 TransportError(_) | VersionMismatch | LocallyClosed | CidsExhausted => { +7388 io::ErrorKind::Other +7389 } +7390 }; +7391 Self::new(kind, x) +7392 } +7393} +7394 +7395/// Errors that might trigger a path being closed +7396// TODO(@divma): maybe needs to be reworked based on what we want to do with the public API +7397#[derive(Debug, Error, PartialEq, Eq, Clone, Copy)] +7398pub enum PathError { +7399 /// The extension was not negotiated with the peer +7400 #[error("multipath extension not negotiated")] +7401 MultipathNotNegotiated, +7402 /// Paths can only be opened client-side +7403 #[error("the server side may not open a path")] +7404 ServerSideNotAllowed, +7405 /// Current limits do not allow us to open more paths +7406 #[error("maximum number of concurrent paths reached")] +7407 MaxPathIdReached, +7408 /// No remote CIDs available to open a new path +7409 #[error("remoted CIDs exhausted")] +7410 RemoteCidsExhausted, +7411 /// Path could not be validated and will be abandoned +7412 #[error("path validation failed")] +7413 ValidationFailed, +7414 /// The remote address for the path is not supported by the endpoint +7415 #[error("invalid remote address")] +7416 InvalidRemoteAddress(SocketAddr), +7417} +7418 +7419/// Errors triggered when abandoning a path +7420#[derive(Debug, Error, Clone, Eq, PartialEq)] +7421pub enum ClosePathError { +7422 /// Multipath is not negotiated +7423 #[error("Multipath extension not negotiated")] +7424 MultipathNotNegotiated, +7425 /// The path is already closed or was never opened +7426 #[error("closed path")] +7427 ClosedPath, +7428 /// Cannot close the last remaining open path via the local API. +7429 /// +7430 /// Use [`Connection::close`] to end the connection instead. +7431 #[error("last open path")] +7432 LastOpenPath, +7433} 7434 -7435 pub(crate) fn side(&self) -> Side { -7436 match *self { -7437 Self::Client { .. } => Side::Client, -7438 Self::Server { .. } => Side::Server, -7439 } -7440 } -7441} -7442 -7443/// Reasons why a connection might be lost -7444#[derive(Debug, Error, Clone, PartialEq, Eq)] -7445pub enum ConnectionError { -7446 /// The peer doesn't implement any supported version -7447 #[error("peer doesn't implement any supported version")] -7448 VersionMismatch, -7449 /// The peer violated the QUIC specification as understood by this implementation -7450 #[error(transparent)] -7451 TransportError(#[from] TransportError), -7452 /// The peer's QUIC stack aborted the connection automatically -7453 #[error("aborted by peer: {0}")] -7454 ConnectionClosed(frame::ConnectionClose), -7455 /// The peer closed the connection -7456 #[error("closed by peer: {0}")] -7457 ApplicationClosed(frame::ApplicationClose), -7458 /// The peer is unable to continue processing this connection, usually due to having restarted -7459 #[error("reset by peer")] -7460 Reset, -7461 /// Communication with the peer has lapsed for longer than the negotiated idle timeout -7462 /// -7463 /// If neither side is sending keep-alives, a connection will time out after a long enough idle -7464 /// period even if the peer is still reachable. See also [`TransportConfig::max_idle_timeout()`] -7465 /// and [`TransportConfig::keep_alive_interval()`]. -7466 #[error("timed out")] -7467 TimedOut, -7468 /// The local application closed the connection -7469 #[error("closed")] -7470 LocallyClosed, -7471 /// The connection could not be created because not enough of the CID space is available -7472 /// -7473 /// Try using longer connection IDs. -7474 #[error("CIDs exhausted")] -7475 CidsExhausted, -7476} -7477 -7478impl From<Close> for ConnectionError { -7479 fn from(x: Close) -> Self { -7480 match x { -7481 Close::Connection(reason) => Self::ConnectionClosed(reason), -7482 Close::Application(reason) => Self::ApplicationClosed(reason), -7483 } -7484 } -7485} -7486 -7487// For compatibility with API consumers -7488impl From<ConnectionError> for io::Error { -7489 fn from(x: ConnectionError) -> Self { -7490 use ConnectionError::*; -7491 let kind = match x { -7492 TimedOut => io::ErrorKind::TimedOut, -7493 Reset => io::ErrorKind::ConnectionReset, -7494 ApplicationClosed(_) | ConnectionClosed(_) => io::ErrorKind::ConnectionAborted, -7495 TransportError(_) | VersionMismatch | LocallyClosed | CidsExhausted => { -7496 io::ErrorKind::Other -7497 } -7498 }; -7499 Self::new(kind, x) -7500 } -7501} -7502 -7503/// Errors that might trigger a path being closed -7504// TODO(@divma): maybe needs to be reworked based on what we want to do with the public API -7505#[derive(Debug, Error, PartialEq, Eq, Clone, Copy)] -7506pub enum PathError { -7507 /// The extension was not negotiated with the peer -7508 #[error("multipath extension not negotiated")] -7509 MultipathNotNegotiated, -7510 /// Paths can only be opened client-side -7511 #[error("the server side may not open a path")] -7512 ServerSideNotAllowed, -7513 /// Current limits do not allow us to open more paths -7514 #[error("maximum number of concurrent paths reached")] -7515 MaxPathIdReached, -7516 /// No remote CIDs available to open a new path -7517 #[error("remoted CIDs exhausted")] -7518 RemoteCidsExhausted, -7519 /// Path could not be validated and will be abandoned -7520 #[error("path validation failed")] -7521 ValidationFailed, -7522 /// The remote address for the path is not supported by the endpoint -7523 #[error("invalid remote address")] -7524 InvalidRemoteAddress(SocketAddr), -7525} -7526 -7527/// Errors triggered when abandoning a path -7528#[derive(Debug, Error, Clone, Eq, PartialEq)] -7529pub enum ClosePathError { -7530 /// Multipath is not negotiated -7531 #[error("Multipath extension not negotiated")] -7532 MultipathNotNegotiated, -7533 /// The path is already closed or was never opened -7534 #[error("closed path")] -7535 ClosedPath, -7536 /// Cannot close the last remaining open path via the local API. -7537 /// -7538 /// Use [`Connection::close`] to end the connection instead. -7539 #[error("last open path")] -7540 LastOpenPath, -7541} -7542 -7543/// Error when the multipath extension was not negotiated, but attempted to be used. -7544#[derive(Debug, Error, Clone, Copy)] -7545#[error("Multipath extension not negotiated")] -7546pub struct MultipathNotNegotiated { -7547 _private: (), -7548} -7549 -7550/// Events of interest to the application -7551#[derive(Debug)] -7552pub enum Event { -7553 /// The connection's handshake data is ready -7554 HandshakeDataReady, -7555 /// The connection was successfully established -7556 Connected, -7557 /// The TLS handshake was confirmed -7558 HandshakeConfirmed, -7559 /// The connection was lost -7560 /// -7561 /// Emitted when the connection is closed due to an error, a timeout, or the peer closing it. -7562 /// This is **not** emitted when the local application closes the connection via -7563 /// [`Connection::close()`](crate::Connection::close). In that case, pending operations will -7564 /// fail with [`ConnectionError::LocallyClosed`]. -7565 ConnectionLost { -7566 /// Reason that the connection was closed -7567 reason: ConnectionError, -7568 }, -7569 /// Stream events -7570 Stream(StreamEvent), -7571 /// One or more application datagrams have been received -7572 DatagramReceived, -7573 /// One or more application datagrams have been sent after blocking -7574 DatagramsUnblocked, -7575 /// (Multi)Path events -7576 Path(PathEvent), -7577 /// n0's nat traversal events -7578 NatTraversal(n0_nat_traversal::Event), -7579} -7580 -7581impl From<PathEvent> for Event { -7582 fn from(source: PathEvent) -> Self { -7583 Self::Path(source) -7584 } -7585} -7586 -7587fn get_max_ack_delay(params: &TransportParameters) -> Duration { -7588 Duration::from_micros(params.max_ack_delay.0 * 1000) -7589} -7590 -7591/// Prevents overflow and improves behavior in extreme circumstances. -7592const MAX_BACKOFF_EXPONENT: u32 = 16; -7593 -7594/// The max interval between successive tail-loss probes. -7595/// -7596/// This is the "normal" value we use. -7597const MAX_PTO_INTERVAL: Duration = Duration::from_secs(2); -7598 -7599/// The idle time, below which we use the shorter [`MAX_PTO_FAST_INTERVAL`]. -7600const MIN_IDLE_FOR_FAST_PTO: Duration = Duration::from_secs(25); -7601 -7602/// The max interval between successive tail-loss probes with short idle times. -7603/// -7604/// If the path or connection idle time is less than [`MIN_IDLE_FOR_FAST_PTO`] then we use -7605/// this value to ensure we have plenty of retransmits before we reach the idle time. -7606const MAX_PTO_FAST_INTERVAL: Duration = Duration::from_secs(1); -7607 -7608/// The RTT threshold above which we cap the PTO interval to 1.5 * smoothed_rtt -7609/// -7610/// This is RTT time above which 1.5 * RTT > [`MAX_PTO_INTERVAL`], for these links we want -7611/// to extend the interval between tail-loss probes to not fill the entire pipe with them. -7612const SLOW_RTT_THRESHOLD: Duration = -7613 Duration::from_millis((MAX_PTO_INTERVAL.as_millis() as u64 * 2) / 3); -7614 -7615/// Minimal remaining size to allow packet coalescing, excluding cryptographic tag -7616/// -7617/// This must be at least as large as the header for a well-formed empty packet to be coalesced, -7618/// plus some space for frames. We only care about handshake headers because short header packets -7619/// necessarily have smaller headers, and initial packets are only ever the first packet in a -7620/// datagram (because we coalesce in ascending packet space order and the only reason to split a -7621/// packet is when packet space changes). -7622const MIN_PACKET_SPACE: usize = MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE + 32; -7623 -7624/// Largest amount of space that could be occupied by a Handshake or 0-RTT packet's header +7435/// Error when the multipath extension was not negotiated, but attempted to be used. +7436#[derive(Debug, Error, Clone, Copy)] +7437#[error("Multipath extension not negotiated")] +7438pub struct MultipathNotNegotiated { +7439 _private: (), +7440} +7441 +7442/// Events of interest to the application +7443#[derive(Debug)] +7444pub enum Event { +7445 /// The connection's handshake data is ready +7446 HandshakeDataReady, +7447 /// The connection was successfully established +7448 Connected, +7449 /// The TLS handshake was confirmed +7450 HandshakeConfirmed, +7451 /// The connection was lost +7452 /// +7453 /// Emitted when the connection is closed due to an error, a timeout, or the peer closing it. +7454 /// This is **not** emitted when the local application closes the connection via +7455 /// [`Connection::close()`](crate::Connection::close). In that case, pending operations will +7456 /// fail with [`ConnectionError::LocallyClosed`]. +7457 ConnectionLost { +7458 /// Reason that the connection was closed +7459 reason: ConnectionError, +7460 }, +7461 /// Stream events +7462 Stream(StreamEvent), +7463 /// One or more application datagrams have been received +7464 DatagramReceived, +7465 /// One or more application datagrams have been sent after blocking +7466 DatagramsUnblocked, +7467 /// (Multi)Path events +7468 Path(PathEvent), +7469 /// n0's nat traversal events +7470 NatTraversal(n0_nat_traversal::Event), +7471} +7472 +7473impl From<PathEvent> for Event { +7474 fn from(source: PathEvent) -> Self { +7475 Self::Path(source) +7476 } +7477} +7478 +7479fn get_max_ack_delay(params: &TransportParameters) -> Duration { +7480 Duration::from_micros(params.max_ack_delay.0 * 1000) +7481} +7482 +7483/// Prevents overflow and improves behavior in extreme circumstances. +7484const MAX_BACKOFF_EXPONENT: u32 = 16; +7485 +7486/// The max interval between successive tail-loss probes. +7487/// +7488/// This is the "normal" value we use. +7489const MAX_PTO_INTERVAL: Duration = Duration::from_secs(2); +7490 +7491/// The idle time, below which we use the shorter [`MAX_PTO_FAST_INTERVAL`]. +7492const MIN_IDLE_FOR_FAST_PTO: Duration = Duration::from_secs(25); +7493 +7494/// The max interval between successive tail-loss probes with short idle times. +7495/// +7496/// If the path or connection idle time is less than [`MIN_IDLE_FOR_FAST_PTO`] then we use +7497/// this value to ensure we have plenty of retransmits before we reach the idle time. +7498const MAX_PTO_FAST_INTERVAL: Duration = Duration::from_secs(1); +7499 +7500/// The RTT threshold above which we cap the PTO interval to 1.5 * smoothed_rtt +7501/// +7502/// This is RTT time above which 1.5 * RTT > [`MAX_PTO_INTERVAL`], for these links we want +7503/// to extend the interval between tail-loss probes to not fill the entire pipe with them. +7504const SLOW_RTT_THRESHOLD: Duration = +7505 Duration::from_millis((MAX_PTO_INTERVAL.as_millis() as u64 * 2) / 3); +7506 +7507/// Minimal remaining size to allow packet coalescing, excluding cryptographic tag +7508/// +7509/// This must be at least as large as the header for a well-formed empty packet to be coalesced, +7510/// plus some space for frames. We only care about handshake headers because short header packets +7511/// necessarily have smaller headers, and initial packets are only ever the first packet in a +7512/// datagram (because we coalesce in ascending packet space order and the only reason to split a +7513/// packet is when packet space changes). +7514const MIN_PACKET_SPACE: usize = MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE + 32; +7515 +7516/// Largest amount of space that could be occupied by a Handshake or 0-RTT packet's header +7517/// +7518/// Excludes packet-type-specific fields such as packet number or Initial token +7519// https://www.rfc-editor.org/rfc/rfc9000.html#name-0-rtt: flags + version + dcid len + dcid + +7520// scid len + scid + length + pn +7521const MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE: usize = +7522 1 + 4 + 1 + MAX_CID_SIZE + 1 + MAX_CID_SIZE + VarInt::from_u32(u16::MAX as u32).size() + 4; +7523 +7524#[derive(Default)] +7525struct SentFrames { +7526 retransmits: ThinRetransmits, +7527 /// The packet number of the largest acknowledged packet for each path +7528 largest_acked: FxHashMap<PathId, u64>, +7529 stream_frames: StreamMetaVec, +7530 /// Whether the packet contains non-retransmittable frames (like datagrams) +7531 non_retransmits: bool, +7532 /// If the datagram containing these frames should be padded to the min MTU +7533 requires_padding: bool, +7534} +7535 +7536impl SentFrames { +7537 /// Returns whether the packet contains only ACKs +7538 fn is_ack_only(&self, streams: &StreamsState) -> bool { +7539 !self.largest_acked.is_empty() +7540 && !self.non_retransmits +7541 && self.stream_frames.is_empty() +7542 && self.retransmits.is_empty(streams) +7543 } +7544 +7545 fn retransmits_mut(&mut self) -> &mut Retransmits { +7546 self.retransmits.get_or_create() +7547 } +7548 +7549 fn record_sent_frame(&mut self, frame: frame::EncodableFrame<'_>) { +7550 use frame::EncodableFrame::*; +7551 match frame { +7552 PathAck(path_ack_encoder) => { +7553 if let Some(max) = path_ack_encoder.ranges.max() { +7554 self.largest_acked.insert(path_ack_encoder.path_id, max); +7555 } +7556 } +7557 Ack(ack_encoder) => { +7558 if let Some(max) = ack_encoder.ranges.max() { +7559 self.largest_acked.insert(PathId::ZERO, max); +7560 } +7561 } +7562 Close(_) => { /* non retransmittable, but after this we don't really care */ } +7563 PathResponse(_) => self.non_retransmits = true, +7564 HandshakeDone(_) => self.retransmits_mut().handshake_done = true, +7565 ReachOut(frame) => self.retransmits_mut().reach_out.push(frame), +7566 ObservedAddr(_) => self.retransmits_mut().observed_addr = true, +7567 Ping(_) => self.non_retransmits = true, +7568 ImmediateAck(_) => self.non_retransmits = true, +7569 AckFrequency(_) => self.retransmits_mut().ack_frequency = true, +7570 PathChallenge(_) => self.non_retransmits = true, +7571 Crypto(crypto) => self.retransmits_mut().crypto.push_back(crypto), +7572 PathAbandon(path_abandon) => { +7573 self.retransmits_mut() +7574 .path_abandon +7575 .entry(path_abandon.path_id) +7576 .or_insert(path_abandon.error_code); +7577 } +7578 PathStatusAvailable(frame::PathStatusAvailable { path_id, .. }) +7579 | PathStatusBackup(frame::PathStatusBackup { path_id, .. }) => { +7580 self.retransmits_mut().path_status.insert(path_id); +7581 } +7582 MaxPathId(_) => self.retransmits_mut().max_path_id = true, +7583 PathsBlocked(_) => self.retransmits_mut().paths_blocked = true, +7584 PathCidsBlocked(path_cids_blocked) => { +7585 self.retransmits_mut() +7586 .path_cids_blocked +7587 .insert(path_cids_blocked.path_id); +7588 } +7589 ResetStream(reset) => self +7590 .retransmits_mut() +7591 .reset_stream +7592 .push((reset.id, reset.error_code)), +7593 StopSending(stop_sending) => self.retransmits_mut().stop_sending.push(stop_sending), +7594 NewConnectionId(new_cid) => self.retransmits_mut().new_cids.push(new_cid.issued()), +7595 RetireConnectionId(retire_cid) => self +7596 .retransmits_mut() +7597 .retire_cids +7598 .push((retire_cid.path_id.unwrap_or_default(), retire_cid.sequence)), +7599 Datagram(_) => self.non_retransmits = true, +7600 NewToken(_) => {} +7601 AddAddress(add_address) => { +7602 self.retransmits_mut().add_address.insert(add_address); +7603 } +7604 RemoveAddress(remove_address) => { +7605 self.retransmits_mut().remove_address.insert(remove_address); +7606 } +7607 StreamMeta(stream_meta_encoder) => self.stream_frames.push(stream_meta_encoder.meta), +7608 MaxData(_) => self.retransmits_mut().max_data = true, +7609 MaxStreamData(max) => { +7610 self.retransmits_mut().max_stream_data.insert(max.id); +7611 } +7612 MaxStreams(max_streams) => { +7613 self.retransmits_mut().max_stream_id[max_streams.dir as usize] = true +7614 } +7615 StreamsBlocked(streams_blocked) => { +7616 self.retransmits_mut().streams_blocked[streams_blocked.dir as usize] = true +7617 } +7618 } +7619 } +7620} +7621 +7622/// Compute the negotiated idle timeout based on local and remote max_idle_timeout transport parameters. +7623/// +7624/// 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.> 7625/// -7626/// Excludes packet-type-specific fields such as packet number or Initial token -7627// https://www.rfc-editor.org/rfc/rfc9000.html#name-0-rtt: flags + version + dcid len + dcid + -7628// scid len + scid + length + pn -7629const MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE: usize = -7630 1 + 4 + 1 + MAX_CID_SIZE + 1 + MAX_CID_SIZE + VarInt::from_u32(u16::MAX as u32).size() + 4; -7631 -7632#[derive(Default)] -7633struct SentFrames { -7634 retransmits: ThinRetransmits, -7635 /// The packet number of the largest acknowledged packet for each path -7636 largest_acked: FxHashMap<PathId, u64>, -7637 stream_frames: StreamMetaVec, -7638 /// Whether the packet contains non-retransmittable frames (like datagrams) -7639 non_retransmits: bool, -7640 /// If the datagram containing these frames should be padded to the min MTU -7641 requires_padding: bool, -7642} -7643 -7644impl SentFrames { -7645 /// Returns whether the packet contains only ACKs -7646 fn is_ack_only(&self, streams: &StreamsState) -> bool { -7647 !self.largest_acked.is_empty() -7648 && !self.non_retransmits -7649 && self.stream_frames.is_empty() -7650 && self.retransmits.is_empty(streams) -7651 } -7652 -7653 fn retransmits_mut(&mut self) -> &mut Retransmits { -7654 self.retransmits.get_or_create() -7655 } -7656 -7657 fn record_sent_frame(&mut self, frame: frame::EncodableFrame<'_>) { -7658 use frame::EncodableFrame::*; -7659 match frame { -7660 PathAck(path_ack_encoder) => { -7661 if let Some(max) = path_ack_encoder.ranges.max() { -7662 self.largest_acked.insert(path_ack_encoder.path_id, max); -7663 } -7664 } -7665 Ack(ack_encoder) => { -7666 if let Some(max) = ack_encoder.ranges.max() { -7667 self.largest_acked.insert(PathId::ZERO, max); -7668 } -7669 } -7670 Close(_) => { /* non retransmittable, but after this we don't really care */ } -7671 PathResponse(_) => self.non_retransmits = true, -7672 HandshakeDone(_) => self.retransmits_mut().handshake_done = true, -7673 ReachOut(frame::ReachOut { round, ip, port }) => { -7674 let (recorded_round, reach_outs) = self -7675 .retransmits_mut() -7676 .reach_out -7677 .get_or_insert_with(|| (round, FxHashSet::default())); -7678 // Only record reach outs for the current round or a newer than the recorded one. -7679 if *recorded_round == round { -7680 // Same round, simply append. -7681 reach_outs.insert((ip, port)); -7682 } else if *recorded_round < round { -7683 // New round. -7684 *recorded_round = round; -7685 reach_outs.drain(); -7686 reach_outs.insert((ip, port)); -7687 } else { -7688 // ignore old reach out that was sent -7689 } -7690 } -7691 -7692 ObservedAddr(_) => self.retransmits_mut().observed_addr = true, -7693 Ping(_) => self.non_retransmits = true, -7694 ImmediateAck(_) => self.non_retransmits = true, -7695 AckFrequency(_) => self.retransmits_mut().ack_frequency = true, -7696 PathChallenge(_) => self.non_retransmits = true, -7697 Crypto(crypto) => self.retransmits_mut().crypto.push_back(crypto), -7698 PathAbandon(path_abandon) => { -7699 self.retransmits_mut() -7700 .path_abandon -7701 .entry(path_abandon.path_id) -7702 .or_insert(path_abandon.error_code); -7703 } -7704 PathStatusAvailable(frame::PathStatusAvailable { path_id, .. }) -7705 | PathStatusBackup(frame::PathStatusBackup { path_id, .. }) => { -7706 self.retransmits_mut().path_status.insert(path_id); -7707 } -7708 MaxPathId(_) => self.retransmits_mut().max_path_id = true, -7709 PathsBlocked(_) => self.retransmits_mut().paths_blocked = true, -7710 PathCidsBlocked(path_cids_blocked) => { -7711 self.retransmits_mut() -7712 .path_cids_blocked -7713 .insert(path_cids_blocked.path_id); -7714 } -7715 ResetStream(reset) => self -7716 .retransmits_mut() -7717 .reset_stream -7718 .push((reset.id, reset.error_code)), -7719 StopSending(stop_sending) => self.retransmits_mut().stop_sending.push(stop_sending), -7720 NewConnectionId(new_cid) => self.retransmits_mut().new_cids.push(new_cid.issued()), -7721 RetireConnectionId(retire_cid) => self -7722 .retransmits_mut() -7723 .retire_cids -7724 .push((retire_cid.path_id.unwrap_or_default(), retire_cid.sequence)), -7725 Datagram(_) => self.non_retransmits = true, -7726 NewToken(_) => {} -7727 AddAddress(add_address) => { -7728 self.retransmits_mut().add_address.insert(add_address); -7729 } -7730 RemoveAddress(remove_address) => { -7731 self.retransmits_mut().remove_address.insert(remove_address); -7732 } -7733 StreamMeta(stream_meta_encoder) => self.stream_frames.push(stream_meta_encoder.meta), -7734 MaxData(_) => self.retransmits_mut().max_data = true, -7735 MaxStreamData(max) => { -7736 self.retransmits_mut().max_stream_data.insert(max.id); -7737 } -7738 MaxStreams(max_streams) => { -7739 self.retransmits_mut().max_stream_id[max_streams.dir as usize] = true -7740 } -7741 StreamsBlocked(streams_blocked) => { -7742 self.retransmits_mut().streams_blocked[streams_blocked.dir as usize] = true -7743 } -7744 } -7745 } -7746} -7747 -7748/// Compute the negotiated idle timeout based on local and remote max_idle_timeout transport parameters. -7749/// -7750/// 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.> -7751/// -7752/// 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.> -7753/// -7754/// Returns the negotiated idle timeout as a `Duration`, or `None` when both endpoints have opted out of idle timeout. -7755fn negotiate_max_idle_timeout(x: Option<VarInt>, y: Option<VarInt>) -> Option<Duration> { -7756 match (x, y) { -7757 (Some(VarInt(0)) | None, Some(VarInt(0)) | None) => None, -7758 (Some(VarInt(0)) | None, Some(y)) => Some(Duration::from_millis(y.0)), -7759 (Some(x), Some(VarInt(0)) | None) => Some(Duration::from_millis(x.0)), -7760 (Some(x), Some(y)) => Some(Duration::from_millis(cmp::min(x, y).0)), -7761 } -7762} -7763 -7764#[cfg(test)] -7765mod tests { -7766 use super::*; -7767 -7768 #[test] -7769 fn negotiate_max_idle_timeout_commutative() { -7770 let test_params = [ -7771 (None, None, None), -7772 (None, Some(VarInt(0)), None), -7773 (None, Some(VarInt(2)), Some(Duration::from_millis(2))), -7774 (Some(VarInt(0)), Some(VarInt(0)), None), -7775 ( -7776 Some(VarInt(2)), -7777 Some(VarInt(0)), -7778 Some(Duration::from_millis(2)), -7779 ), -7780 ( -7781 Some(VarInt(1)), -7782 Some(VarInt(4)), -7783 Some(Duration::from_millis(1)), -7784 ), -7785 ]; -7786 -7787 for (left, right, result) in test_params { -7788 assert_eq!(negotiate_max_idle_timeout(left, right), result); -7789 assert_eq!(negotiate_max_idle_timeout(right, left), result); -7790 } -7791 } -7792}

\ No newline at end of file +7626/// 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.> +7627/// +7628/// Returns the negotiated idle timeout as a `Duration`, or `None` when both endpoints have opted out of idle timeout. +7629fn negotiate_max_idle_timeout(x: Option<VarInt>, y: Option<VarInt>) -> Option<Duration> { +7630 match (x, y) { +7631 (Some(VarInt(0)) | None, Some(VarInt(0)) | None) => None, +7632 (Some(VarInt(0)) | None, Some(y)) => Some(Duration::from_millis(y.0)), +7633 (Some(x), Some(VarInt(0)) | None) => Some(Duration::from_millis(x.0)), +7634 (Some(x), Some(y)) => Some(Duration::from_millis(cmp::min(x, y).0)), +7635 } +7636} +7637 +7638#[cfg(test)] +7639mod tests { +7640 use super::*; +7641 +7642 #[test] +7643 fn negotiate_max_idle_timeout_commutative() { +7644 let test_params = [ +7645 (None, None, None), +7646 (None, Some(VarInt(0)), None), +7647 (None, Some(VarInt(2)), Some(Duration::from_millis(2))), +7648 (Some(VarInt(0)), Some(VarInt(0)), None), +7649 ( +7650 Some(VarInt(2)), +7651 Some(VarInt(0)), +7652 Some(Duration::from_millis(2)), +7653 ), +7654 ( +7655 Some(VarInt(1)), +7656 Some(VarInt(4)), +7657 Some(Duration::from_millis(1)), +7658 ), +7659 ]; +7660 +7661 for (left, right, result) in test_params { +7662 assert_eq!(negotiate_max_idle_timeout(left, right), result); +7663 assert_eq!(negotiate_max_idle_timeout(right, left), result); +7664 } +7665 } +7666}

\ No newline at end of file diff --git a/pr/616/docs/src/noq_proto/connection/paths.rs.html b/pr/616/docs/src/noq_proto/connection/paths.rs.html index 2886ad45e..666df7311 100644 --- a/pr/616/docs/src/noq_proto/connection/paths.rs.html +++ b/pr/616/docs/src/noq_proto/connection/paths.rs.html @@ -535,622 +535,613 @@ 535 536 let prev_status = std::mem::replace(&mut self.open_status, OpenStatus::Informed); 537 OnPathResponseReceived::OnPath { -538 was_open: matches!( -539 prev_status, -540 OpenStatus::Informed | OpenStatus::Revalidating -541 ), -542 } -543 } -544 // Response to an on-path PathChallenge that does not validate this path. -545 Some(info) => { -546 // This is a valid path response, but this validates a 4-tuple we no longer -547 // have in use. Keep only sent challenges for the current path. -548 self.on_path_challenges_unconfirmed -549 .retain(|_token, i| i.network_path == self.network_path); -550 -551 // If there are no challenges for the current path, schedule one -552 if !self.on_path_challenges_unconfirmed.is_empty() { -553 self.pending_on_path_challenge = true; -554 } -555 OnPathResponseReceived::Ignored { -556 sent_on: info.network_path, -557 current_path: self.network_path, -558 } -559 } -560 None => { -561 // Response to an unknown PathChallenge. Does not indicate failure. -562 OnPathResponseReceived::Unknown -563 } -564 } -565 } -566 -567 /// Removes all on-path challenges we remember and cancels sending new on-path challenges. -568 pub(super) fn reset_on_path_challenges(&mut self) { -569 self.on_path_challenges_unconfirmed.clear(); -570 self.pending_on_path_challenge = false; -571 } -572 -573 #[cfg(feature = "qlog")] -574 pub(super) fn qlog_recovery_metrics( -575 &mut self, -576 path_id: PathId, -577 ) -> Option<RecoveryMetricsUpdated> { -578 let controller_metrics = self.congestion.metrics(); -579 -580 let metrics = RecoveryMetrics { -581 min_rtt: Some(self.rtt.min), -582 smoothed_rtt: Some(self.rtt.get()), -583 latest_rtt: Some(self.rtt.latest), -584 rtt_variance: Some(self.rtt.var), -585 pto_count: Some(self.pto_count), -586 bytes_in_flight: Some(self.in_flight.bytes), -587 packets_in_flight: Some(self.in_flight.ack_eliciting), -588 -589 congestion_window: Some(controller_metrics.congestion_window), -590 ssthresh: controller_metrics.ssthresh, -591 pacing_rate: controller_metrics.pacing_rate, -592 }; -593 -594 let event = metrics.to_qlog_event(path_id, &self.recovery_metrics); -595 self.recovery_metrics = metrics; -596 event -597 } -598 -599 /// Return how long we need to wait before sending `bytes_to_send` -600 /// -601 /// See [`Pacer::delay`]. -602 pub(super) fn pacing_delay(&mut self, bytes_to_send: u64, now: Instant) -> Option<Duration> { -603 let smoothed_rtt = self.rtt.get(); -604 let metrics = self.congestion.metrics(); -605 self.pacing.delay( -606 smoothed_rtt, -607 bytes_to_send, -608 self.current_mtu(), -609 metrics.congestion_window, -610 now, -611 metrics.send_quantum, -612 metrics.pacing_rate, -613 ) -614 } -615 -616 /// Updates the last observed address report received on this path. -617 /// -618 /// If the address was updated, it's returned to be informed to the application. -619 #[must_use = "updated observed address must be reported to the application"] -620 pub(super) fn update_observed_addr_report( -621 &mut self, -622 observed: ObservedAddr, -623 ) -> Option<SocketAddr> { -624 match self.last_observed_addr_report.as_mut() { -625 Some(prev) => { -626 if prev.seq_no >= observed.seq_no { -627 // frames that do not increase the sequence number on this path are ignored -628 None -629 } else if prev.ip == observed.ip && prev.port == observed.port { -630 // keep track of the last seq_no but do not report the address as updated -631 prev.seq_no = observed.seq_no; -632 None -633 } else { -634 let addr = observed.socket_addr(); -635 self.last_observed_addr_report = Some(observed); -636 Some(addr) -637 } -638 } -639 None => { -640 let addr = observed.socket_addr(); -641 self.last_observed_addr_report = Some(observed); -642 Some(addr) -643 } -644 } -645 } -646 -647 pub(crate) fn remote_status(&self) -> Option<PathStatus> { -648 self.status.remote_status.map(|(_seq, status)| status) -649 } -650 -651 pub(crate) fn local_status(&self) -> PathStatus { -652 self.status.local_status -653 } -654 -655 /// Tag uniquely identifying a path in a connection. -656 /// -657 /// When a migration happens on the same [`PathId`] we still detect a change in the -658 /// 4-tuple and generate a new [`PathData`] for it. Each such generation has a unique -659 /// value to keep track of which 4-tuple a packet belonged to. -660 pub(super) fn generation(&self) -> u64 { -661 self.generation -662 } -663} -664 -665pub(super) enum OnPathResponseReceived { -666 /// This response validates the path on its current remote address. -667 OnPath { was_open: bool }, -668 /// The received token is unknown. -669 Unknown, -670 /// The response is valid but it's not usable for path validation. -671 Ignored { -672 sent_on: FourTuple, -673 current_path: FourTuple, -674 }, -675} -676 -677#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] -678pub(super) enum OpenStatus { -679 /// A first packet has not been sent using this [`PathId`]. -680 #[default] -681 Pending, -682 /// The first packet has been sent using this [`PathId`]. However, it is not yet deemed good -683 /// enough to be reported to the application. -684 Sent, -685 /// The application has been informed of this path. -686 Informed, -687 /// The path was [`Self::Informed`] before, but we want to trigger path validation again. -688 /// -689 /// This is used to ensure we properly stop trying to re-send path challenges eventually, without -690 /// having to switch to [`Self::Pending`] when re-validating, as that would trigger another -691 /// application-level event about the path opening once validation succeeds. -692 Revalidating, -693} -694 -695/// Congestion metrics as described in [`recovery_metrics_updated`]. -696/// -697/// [`recovery_metrics_updated`]: https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-quic-events.html#name-recovery_metrics_updated -698#[cfg(feature = "qlog")] -699#[derive(Default, Clone, PartialEq, Debug)] -700#[non_exhaustive] -701struct RecoveryMetrics { -702 pub min_rtt: Option<Duration>, -703 pub smoothed_rtt: Option<Duration>, -704 pub latest_rtt: Option<Duration>, -705 pub rtt_variance: Option<Duration>, -706 pub pto_count: Option<u32>, -707 pub bytes_in_flight: Option<u64>, -708 pub packets_in_flight: Option<u64>, -709 pub congestion_window: Option<u64>, -710 pub ssthresh: Option<u64>, -711 pub pacing_rate: Option<u64>, -712} -713 -714#[cfg(feature = "qlog")] -715impl RecoveryMetrics { -716 /// Retain only values that have been updated since the last snapshot. -717 fn retain_updated(&self, previous: &Self) -> Self { -718 macro_rules! keep_if_changed { -719 ($name:ident) => { -720 if previous.$name == self.$name { -721 None -722 } else { -723 self.$name -724 } -725 }; -726 } -727 -728 Self { -729 min_rtt: keep_if_changed!(min_rtt), -730 smoothed_rtt: keep_if_changed!(smoothed_rtt), -731 latest_rtt: keep_if_changed!(latest_rtt), -732 rtt_variance: keep_if_changed!(rtt_variance), -733 pto_count: keep_if_changed!(pto_count), -734 bytes_in_flight: keep_if_changed!(bytes_in_flight), -735 packets_in_flight: keep_if_changed!(packets_in_flight), -736 congestion_window: keep_if_changed!(congestion_window), -737 ssthresh: keep_if_changed!(ssthresh), -738 pacing_rate: keep_if_changed!(pacing_rate), +538 was_open: matches!(prev_status, OpenStatus::Informed), +539 } +540 } +541 // Response to an on-path PathChallenge that does not validate this path. +542 Some(info) => { +543 // This is a valid path response, but this validates a 4-tuple we no longer +544 // have in use. Keep only sent challenges for the current path. +545 self.on_path_challenges_unconfirmed +546 .retain(|_token, i| i.network_path == self.network_path); +547 +548 // If there are no challenges for the current path, schedule one +549 if !self.on_path_challenges_unconfirmed.is_empty() { +550 self.pending_on_path_challenge = true; +551 } +552 OnPathResponseReceived::Ignored { +553 sent_on: info.network_path, +554 current_path: self.network_path, +555 } +556 } +557 None => { +558 // Response to an unknown PathChallenge. Does not indicate failure. +559 OnPathResponseReceived::Unknown +560 } +561 } +562 } +563 +564 /// Removes all on-path challenges we remember and cancels sending new on-path challenges. +565 pub(super) fn reset_on_path_challenges(&mut self) { +566 self.on_path_challenges_unconfirmed.clear(); +567 self.pending_on_path_challenge = false; +568 } +569 +570 #[cfg(feature = "qlog")] +571 pub(super) fn qlog_recovery_metrics( +572 &mut self, +573 path_id: PathId, +574 ) -> Option<RecoveryMetricsUpdated> { +575 let controller_metrics = self.congestion.metrics(); +576 +577 let metrics = RecoveryMetrics { +578 min_rtt: Some(self.rtt.min), +579 smoothed_rtt: Some(self.rtt.get()), +580 latest_rtt: Some(self.rtt.latest), +581 rtt_variance: Some(self.rtt.var), +582 pto_count: Some(self.pto_count), +583 bytes_in_flight: Some(self.in_flight.bytes), +584 packets_in_flight: Some(self.in_flight.ack_eliciting), +585 +586 congestion_window: Some(controller_metrics.congestion_window), +587 ssthresh: controller_metrics.ssthresh, +588 pacing_rate: controller_metrics.pacing_rate, +589 }; +590 +591 let event = metrics.to_qlog_event(path_id, &self.recovery_metrics); +592 self.recovery_metrics = metrics; +593 event +594 } +595 +596 /// Return how long we need to wait before sending `bytes_to_send` +597 /// +598 /// See [`Pacer::delay`]. +599 pub(super) fn pacing_delay(&mut self, bytes_to_send: u64, now: Instant) -> Option<Duration> { +600 let smoothed_rtt = self.rtt.get(); +601 let metrics = self.congestion.metrics(); +602 self.pacing.delay( +603 smoothed_rtt, +604 bytes_to_send, +605 self.current_mtu(), +606 metrics.congestion_window, +607 now, +608 metrics.send_quantum, +609 metrics.pacing_rate, +610 ) +611 } +612 +613 /// Updates the last observed address report received on this path. +614 /// +615 /// If the address was updated, it's returned to be informed to the application. +616 #[must_use = "updated observed address must be reported to the application"] +617 pub(super) fn update_observed_addr_report( +618 &mut self, +619 observed: ObservedAddr, +620 ) -> Option<SocketAddr> { +621 match self.last_observed_addr_report.as_mut() { +622 Some(prev) => { +623 if prev.seq_no >= observed.seq_no { +624 // frames that do not increase the sequence number on this path are ignored +625 None +626 } else if prev.ip == observed.ip && prev.port == observed.port { +627 // keep track of the last seq_no but do not report the address as updated +628 prev.seq_no = observed.seq_no; +629 None +630 } else { +631 let addr = observed.socket_addr(); +632 self.last_observed_addr_report = Some(observed); +633 Some(addr) +634 } +635 } +636 None => { +637 let addr = observed.socket_addr(); +638 self.last_observed_addr_report = Some(observed); +639 Some(addr) +640 } +641 } +642 } +643 +644 pub(crate) fn remote_status(&self) -> Option<PathStatus> { +645 self.status.remote_status.map(|(_seq, status)| status) +646 } +647 +648 pub(crate) fn local_status(&self) -> PathStatus { +649 self.status.local_status +650 } +651 +652 /// Tag uniquely identifying a path in a connection. +653 /// +654 /// When a migration happens on the same [`PathId`] we still detect a change in the +655 /// 4-tuple and generate a new [`PathData`] for it. Each such generation has a unique +656 /// value to keep track of which 4-tuple a packet belonged to. +657 pub(super) fn generation(&self) -> u64 { +658 self.generation +659 } +660} +661 +662pub(super) enum OnPathResponseReceived { +663 /// This response validates the path on its current remote address. +664 OnPath { was_open: bool }, +665 /// The received token is unknown. +666 Unknown, +667 /// The response is valid but it's not usable for path validation. +668 Ignored { +669 sent_on: FourTuple, +670 current_path: FourTuple, +671 }, +672} +673 +674#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +675pub(super) enum OpenStatus { +676 /// A first packet has not been sent using this [`PathId`]. +677 #[default] +678 Pending, +679 /// The first packet has been sent using this [`PathId`]. However, it is not yet deemed good +680 /// enough to be reported to the application. +681 Sent, +682 /// The application has been informed of this path. +683 Informed, +684} +685 +686/// Congestion metrics as described in [`recovery_metrics_updated`]. +687/// +688/// [`recovery_metrics_updated`]: https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-quic-events.html#name-recovery_metrics_updated +689#[cfg(feature = "qlog")] +690#[derive(Default, Clone, PartialEq, Debug)] +691#[non_exhaustive] +692struct RecoveryMetrics { +693 pub min_rtt: Option<Duration>, +694 pub smoothed_rtt: Option<Duration>, +695 pub latest_rtt: Option<Duration>, +696 pub rtt_variance: Option<Duration>, +697 pub pto_count: Option<u32>, +698 pub bytes_in_flight: Option<u64>, +699 pub packets_in_flight: Option<u64>, +700 pub congestion_window: Option<u64>, +701 pub ssthresh: Option<u64>, +702 pub pacing_rate: Option<u64>, +703} +704 +705#[cfg(feature = "qlog")] +706impl RecoveryMetrics { +707 /// Retain only values that have been updated since the last snapshot. +708 fn retain_updated(&self, previous: &Self) -> Self { +709 macro_rules! keep_if_changed { +710 ($name:ident) => { +711 if previous.$name == self.$name { +712 None +713 } else { +714 self.$name +715 } +716 }; +717 } +718 +719 Self { +720 min_rtt: keep_if_changed!(min_rtt), +721 smoothed_rtt: keep_if_changed!(smoothed_rtt), +722 latest_rtt: keep_if_changed!(latest_rtt), +723 rtt_variance: keep_if_changed!(rtt_variance), +724 pto_count: keep_if_changed!(pto_count), +725 bytes_in_flight: keep_if_changed!(bytes_in_flight), +726 packets_in_flight: keep_if_changed!(packets_in_flight), +727 congestion_window: keep_if_changed!(congestion_window), +728 ssthresh: keep_if_changed!(ssthresh), +729 pacing_rate: keep_if_changed!(pacing_rate), +730 } +731 } +732 +733 /// Emit a `MetricsUpdated` event containing only updated values +734 fn to_qlog_event(&self, path_id: PathId, previous: &Self) -> Option<RecoveryMetricsUpdated> { +735 let updated = self.retain_updated(previous); +736 +737 if updated == Self::default() { +738 return None; 739 } -740 } -741 -742 /// Emit a `MetricsUpdated` event containing only updated values -743 fn to_qlog_event(&self, path_id: PathId, previous: &Self) -> Option<RecoveryMetricsUpdated> { -744 let updated = self.retain_updated(previous); -745 -746 if updated == Self::default() { -747 return None; -748 } -749 -750 Some(RecoveryMetricsUpdated { -751 min_rtt: updated.min_rtt.map(|rtt| rtt.as_secs_f32()), -752 smoothed_rtt: updated.smoothed_rtt.map(|rtt| rtt.as_secs_f32()), -753 latest_rtt: updated.latest_rtt.map(|rtt| rtt.as_secs_f32()), -754 rtt_variance: updated.rtt_variance.map(|rtt| rtt.as_secs_f32()), -755 pto_count: updated -756 .pto_count -757 .map(|count| count.try_into().unwrap_or(u16::MAX)), -758 bytes_in_flight: updated.bytes_in_flight, -759 packets_in_flight: updated.packets_in_flight, -760 congestion_window: updated.congestion_window, -761 ssthresh: updated.ssthresh, -762 pacing_rate: updated.pacing_rate, -763 path_id: Some(path_id.as_u32() as u64), -764 }) -765 } -766} -767 -768/// RTT estimation for a particular network path -769#[derive(Copy, Clone, Debug)] -770pub struct RttEstimator { -771 /// The most recent RTT measurement made when receiving an ack for a previously unacked packet -772 latest: Duration, -773 /// The smoothed RTT of the connection, computed as described in RFC6298 -774 smoothed: Option<Duration>, -775 /// The RTT variance, computed as described in RFC6298 -776 var: Duration, -777 /// The minimum RTT seen in the connection, ignoring ack delay. -778 min: Duration, -779} -780 -781impl RttEstimator { -782 pub(super) fn new(initial_rtt: Duration) -> Self { -783 Self { -784 latest: initial_rtt, -785 smoothed: None, -786 var: initial_rtt / 2, -787 min: initial_rtt, -788 } -789 } -790 -791 /// Resets the estimator using a new initial_rtt value. -792 /// -793 /// This only resets the initial_rtt **if** no samples have been recorded yet. If there -794 /// are any recorded samples the initial estimate can not be adjusted after the fact. -795 /// -796 /// This is useful when you receive a PATH_RESPONSE in the first packet received on a -797 /// new path. In this case you can use the delay of the PATH_CHALLENGE-PATH_RESPONSE as -798 /// the initial RTT to get a better expected estimation. -799 /// -800 /// A PATH_CHALLENGE-PATH_RESPONSE pair later in the connection should not be used -801 /// explicitly as an estimation since PATH_CHALLENGE is an ACK-eliciting packet itself -802 /// already. -803 pub(crate) fn reset_initial_rtt(&mut self, initial_rtt: Duration) { -804 if self.smoothed.is_none() { -805 self.latest = initial_rtt; -806 self.var = initial_rtt / 2; -807 self.min = initial_rtt; -808 } -809 } -810 -811 /// The current best RTT estimation. -812 pub fn get(&self) -> Duration { -813 self.smoothed.unwrap_or(self.latest) -814 } -815 -816 /// Conservative estimate of RTT -817 /// -818 /// Takes the maximum of smoothed and latest RTT, as recommended -819 /// in 6.1.2 of the recovery spec (draft 29). -820 pub fn conservative(&self) -> Duration { -821 self.get().max(self.latest) -822 } -823 -824 /// Minimum RTT registered so far for this estimator. -825 pub fn min(&self) -> Duration { -826 self.min -827 } -828 -829 /// PTO computed as described in RFC9002#6.2.1. -830 pub(crate) fn pto_base(&self) -> Duration { -831 self.get() + cmp::max(4 * self.var, TIMER_GRANULARITY) -832 } -833 -834 /// Records an RTT sample. -835 pub(crate) fn update(&mut self, ack_delay: Duration, rtt: Duration) { -836 self.latest = rtt; -837 // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.2-3: -838 // min_rtt does not adjust for ack_delay to avoid underestimating. -839 self.min = cmp::min(self.min, self.latest); -840 // Based on RFC6298. -841 if let Some(smoothed) = self.smoothed { -842 let adjusted_rtt = if self.min + ack_delay <= self.latest { -843 self.latest - ack_delay -844 } else { -845 self.latest -846 }; -847 let var_sample = smoothed.abs_diff(adjusted_rtt); -848 self.var = (3 * self.var + var_sample) / 4; -849 self.smoothed = Some((7 * smoothed + adjusted_rtt) / 8); -850 } else { -851 self.smoothed = Some(self.latest); -852 self.var = self.latest / 2; -853 self.min = self.latest; -854 } -855 } -856} -857 -858#[derive(Default, Debug)] -859pub(crate) struct PathResponses { -860 pending: Vec<PathResponse>, -861} -862 -863impl PathResponses { -864 pub(crate) fn push(&mut self, packet: u64, token: u64, network_path: FourTuple) { -865 /// Arbitrary permissive limit to prevent abuse -866 const MAX_PATH_RESPONSES: usize = 16; -867 let response = PathResponse { -868 packet, -869 token, -870 network_path, -871 }; -872 let existing = self -873 .pending -874 .iter_mut() -875 .find(|x| x.network_path.remote == network_path.remote); -876 if let Some(existing) = existing { -877 // Update a queued response -878 if existing.packet <= packet { -879 *existing = response; -880 } -881 return; -882 } -883 if self.pending.len() < MAX_PATH_RESPONSES { -884 self.pending.push(response); -885 } else { -886 // We don't expect to ever hit this with well-behaved peers, so we don't bother dropping -887 // older challenges. -888 trace!("ignoring excessive PATH_CHALLENGE"); -889 } -890 } -891 -892 pub(crate) fn pop_off_path(&mut self, network_path: FourTuple) -> Option<(u64, FourTuple)> { -893 let response = *self.pending.last()?; -894 // We use an exact comparison here, because once we've received for the first time, -895 // we really should either already have a local_ip, or we will never get one -896 // (because our OS doesn't support it). -897 if response.network_path == network_path { -898 // We don't bother searching further because we expect that the on-path response will -899 // get drained in the immediate future by a call to `pop_on_path` -900 return None; -901 } -902 self.pending.pop(); -903 Some((response.token, response.network_path)) -904 } -905 -906 pub(crate) fn pop_on_path(&mut self, network_path: FourTuple) -> Option<u64> { -907 let response = *self.pending.last()?; -908 // Using an exact comparison. See explanation in `pop_off_path`. -909 if response.network_path != network_path { -910 // We don't bother searching further because we expect that the off-path response will -911 // get drained in the immediate future by a call to `pop_off_path` -912 return None; -913 } -914 self.pending.pop(); -915 Some(response.token) -916 } -917 -918 pub(crate) fn is_empty(&self) -> bool { -919 self.pending.is_empty() -920 } -921} -922 -923#[derive(Copy, Clone, Debug)] -924struct PathResponse { -925 /// The packet number the corresponding PATH_CHALLENGE was received in -926 packet: u64, -927 /// The token of the PATH_CHALLENGE -928 token: u64, -929 /// The path the corresponding PATH_CHALLENGE was received from -930 network_path: FourTuple, -931} -932 -933/// Summary statistics of packets that have been sent on a particular path, but which have not yet -934/// been acked or deemed lost -935#[derive(Debug)] -936pub(super) struct InFlight { -937 /// Sum of the sizes of all sent packets considered "in flight" by congestion control -938 /// -939 /// The size does not include IP or UDP overhead. Packets only containing ACK frames do not -940 /// count towards this to ensure congestion control does not impede congestion feedback. -941 pub(super) bytes: u64, -942 /// Number of packets in flight containing frames other than ACK and PADDING -943 /// -944 /// This can be 0 even when bytes is not 0 because PADDING frames cause a packet to be -945 /// considered "in flight" by congestion control. However, if this is nonzero, bytes will always -946 /// also be nonzero. -947 pub(super) ack_eliciting: u64, -948} -949 -950impl InFlight { -951 fn new() -> Self { -952 Self { -953 bytes: 0, -954 ack_eliciting: 0, -955 } -956 } -957 -958 fn insert(&mut self, packet: &SentPacket) { -959 self.bytes += u64::from(packet.size); -960 self.ack_eliciting += u64::from(packet.ack_eliciting); -961 } -962 -963 /// Update counters to account for a packet becoming acknowledged, lost, or abandoned -964 fn remove(&mut self, packet: &SentPacket) { -965 self.bytes -= u64::from(packet.size); -966 self.ack_eliciting -= u64::from(packet.ack_eliciting); -967 } -968} -969 -970/// State for QUIC-MULTIPATH PATH_STATUS_AVAILABLE and PATH_STATUS_BACKUP frames -971#[derive(Debug, Clone, Default)] -972pub(super) struct PathStatusState { -973 /// The local status -974 local_status: PathStatus, -975 /// Local sequence number, for both PATH_STATUS_AVAILABLE and PATH_STATUS_BACKUP -976 /// -977 /// This is the number of the *next* path status frame to be sent. -978 local_seq: VarInt, -979 /// The status set by the remote -980 remote_status: Option<(VarInt, PathStatus)>, -981} -982 -983impl PathStatusState { -984 /// To be called on received PATH_STATUS_AVAILABLE/PATH_STATUS_BACKUP frames -985 pub(super) fn remote_update(&mut self, status: PathStatus, seq: VarInt) { -986 if self.remote_status.is_some_and(|(curr, _)| curr >= seq) { -987 return trace!(%seq, "ignoring path status update"); -988 } -989 -990 let prev = self.remote_status.replace((seq, status)).map(|(_, s)| s); -991 if prev != Some(status) { -992 debug!(?status, ?seq, "remote changed path status"); +740 +741 Some(RecoveryMetricsUpdated { +742 min_rtt: updated.min_rtt.map(|rtt| rtt.as_secs_f32()), +743 smoothed_rtt: updated.smoothed_rtt.map(|rtt| rtt.as_secs_f32()), +744 latest_rtt: updated.latest_rtt.map(|rtt| rtt.as_secs_f32()), +745 rtt_variance: updated.rtt_variance.map(|rtt| rtt.as_secs_f32()), +746 pto_count: updated +747 .pto_count +748 .map(|count| count.try_into().unwrap_or(u16::MAX)), +749 bytes_in_flight: updated.bytes_in_flight, +750 packets_in_flight: updated.packets_in_flight, +751 congestion_window: updated.congestion_window, +752 ssthresh: updated.ssthresh, +753 pacing_rate: updated.pacing_rate, +754 path_id: Some(path_id.as_u32() as u64), +755 }) +756 } +757} +758 +759/// RTT estimation for a particular network path +760#[derive(Copy, Clone, Debug)] +761pub struct RttEstimator { +762 /// The most recent RTT measurement made when receiving an ack for a previously unacked packet +763 latest: Duration, +764 /// The smoothed RTT of the connection, computed as described in RFC6298 +765 smoothed: Option<Duration>, +766 /// The RTT variance, computed as described in RFC6298 +767 var: Duration, +768 /// The minimum RTT seen in the connection, ignoring ack delay. +769 min: Duration, +770} +771 +772impl RttEstimator { +773 pub(super) fn new(initial_rtt: Duration) -> Self { +774 Self { +775 latest: initial_rtt, +776 smoothed: None, +777 var: initial_rtt / 2, +778 min: initial_rtt, +779 } +780 } +781 +782 /// Resets the estimator using a new initial_rtt value. +783 /// +784 /// This only resets the initial_rtt **if** no samples have been recorded yet. If there +785 /// are any recorded samples the initial estimate can not be adjusted after the fact. +786 /// +787 /// This is useful when you receive a PATH_RESPONSE in the first packet received on a +788 /// new path. In this case you can use the delay of the PATH_CHALLENGE-PATH_RESPONSE as +789 /// the initial RTT to get a better expected estimation. +790 /// +791 /// A PATH_CHALLENGE-PATH_RESPONSE pair later in the connection should not be used +792 /// explicitly as an estimation since PATH_CHALLENGE is an ACK-eliciting packet itself +793 /// already. +794 pub(crate) fn reset_initial_rtt(&mut self, initial_rtt: Duration) { +795 if self.smoothed.is_none() { +796 self.latest = initial_rtt; +797 self.var = initial_rtt / 2; +798 self.min = initial_rtt; +799 } +800 } +801 +802 /// The current best RTT estimation. +803 pub fn get(&self) -> Duration { +804 self.smoothed.unwrap_or(self.latest) +805 } +806 +807 /// Conservative estimate of RTT +808 /// +809 /// Takes the maximum of smoothed and latest RTT, as recommended +810 /// in 6.1.2 of the recovery spec (draft 29). +811 pub fn conservative(&self) -> Duration { +812 self.get().max(self.latest) +813 } +814 +815 /// Minimum RTT registered so far for this estimator. +816 pub fn min(&self) -> Duration { +817 self.min +818 } +819 +820 /// PTO computed as described in RFC9002#6.2.1. +821 pub(crate) fn pto_base(&self) -> Duration { +822 self.get() + cmp::max(4 * self.var, TIMER_GRANULARITY) +823 } +824 +825 /// Records an RTT sample. +826 pub(crate) fn update(&mut self, ack_delay: Duration, rtt: Duration) { +827 self.latest = rtt; +828 // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.2-3: +829 // min_rtt does not adjust for ack_delay to avoid underestimating. +830 self.min = cmp::min(self.min, self.latest); +831 // Based on RFC6298. +832 if let Some(smoothed) = self.smoothed { +833 let adjusted_rtt = if self.min + ack_delay <= self.latest { +834 self.latest - ack_delay +835 } else { +836 self.latest +837 }; +838 let var_sample = smoothed.abs_diff(adjusted_rtt); +839 self.var = (3 * self.var + var_sample) / 4; +840 self.smoothed = Some((7 * smoothed + adjusted_rtt) / 8); +841 } else { +842 self.smoothed = Some(self.latest); +843 self.var = self.latest / 2; +844 self.min = self.latest; +845 } +846 } +847} +848 +849#[derive(Default, Debug)] +850pub(crate) struct PathResponses { +851 pending: Vec<PathResponse>, +852} +853 +854impl PathResponses { +855 pub(crate) fn push(&mut self, packet: u64, token: u64, network_path: FourTuple) { +856 /// Arbitrary permissive limit to prevent abuse +857 const MAX_PATH_RESPONSES: usize = 16; +858 let response = PathResponse { +859 packet, +860 token, +861 network_path, +862 }; +863 let existing = self +864 .pending +865 .iter_mut() +866 .find(|x| x.network_path.remote == network_path.remote); +867 if let Some(existing) = existing { +868 // Update a queued response +869 if existing.packet <= packet { +870 *existing = response; +871 } +872 return; +873 } +874 if self.pending.len() < MAX_PATH_RESPONSES { +875 self.pending.push(response); +876 } else { +877 // We don't expect to ever hit this with well-behaved peers, so we don't bother dropping +878 // older challenges. +879 trace!("ignoring excessive PATH_CHALLENGE"); +880 } +881 } +882 +883 pub(crate) fn pop_off_path(&mut self, network_path: FourTuple) -> Option<(u64, FourTuple)> { +884 let response = *self.pending.last()?; +885 // We use an exact comparison here, because once we've received for the first time, +886 // we really should either already have a local_ip, or we will never get one +887 // (because our OS doesn't support it). +888 if response.network_path == network_path { +889 // We don't bother searching further because we expect that the on-path response will +890 // get drained in the immediate future by a call to `pop_on_path` +891 return None; +892 } +893 self.pending.pop(); +894 Some((response.token, response.network_path)) +895 } +896 +897 pub(crate) fn pop_on_path(&mut self, network_path: FourTuple) -> Option<u64> { +898 let response = *self.pending.last()?; +899 // Using an exact comparison. See explanation in `pop_off_path`. +900 if response.network_path != network_path { +901 // We don't bother searching further because we expect that the off-path response will +902 // get drained in the immediate future by a call to `pop_off_path` +903 return None; +904 } +905 self.pending.pop(); +906 Some(response.token) +907 } +908 +909 pub(crate) fn is_empty(&self) -> bool { +910 self.pending.is_empty() +911 } +912} +913 +914#[derive(Copy, Clone, Debug)] +915struct PathResponse { +916 /// The packet number the corresponding PATH_CHALLENGE was received in +917 packet: u64, +918 /// The token of the PATH_CHALLENGE +919 token: u64, +920 /// The path the corresponding PATH_CHALLENGE was received from +921 network_path: FourTuple, +922} +923 +924/// Summary statistics of packets that have been sent on a particular path, but which have not yet +925/// been acked or deemed lost +926#[derive(Debug)] +927pub(super) struct InFlight { +928 /// Sum of the sizes of all sent packets considered "in flight" by congestion control +929 /// +930 /// The size does not include IP or UDP overhead. Packets only containing ACK frames do not +931 /// count towards this to ensure congestion control does not impede congestion feedback. +932 pub(super) bytes: u64, +933 /// Number of packets in flight containing frames other than ACK and PADDING +934 /// +935 /// This can be 0 even when bytes is not 0 because PADDING frames cause a packet to be +936 /// considered "in flight" by congestion control. However, if this is nonzero, bytes will always +937 /// also be nonzero. +938 pub(super) ack_eliciting: u64, +939} +940 +941impl InFlight { +942 fn new() -> Self { +943 Self { +944 bytes: 0, +945 ack_eliciting: 0, +946 } +947 } +948 +949 fn insert(&mut self, packet: &SentPacket) { +950 self.bytes += u64::from(packet.size); +951 self.ack_eliciting += u64::from(packet.ack_eliciting); +952 } +953 +954 /// Update counters to account for a packet becoming acknowledged, lost, or abandoned +955 fn remove(&mut self, packet: &SentPacket) { +956 self.bytes -= u64::from(packet.size); +957 self.ack_eliciting -= u64::from(packet.ack_eliciting); +958 } +959} +960 +961/// State for QUIC-MULTIPATH PATH_STATUS_AVAILABLE and PATH_STATUS_BACKUP frames +962#[derive(Debug, Clone, Default)] +963pub(super) struct PathStatusState { +964 /// The local status +965 local_status: PathStatus, +966 /// Local sequence number, for both PATH_STATUS_AVAILABLE and PATH_STATUS_BACKUP +967 /// +968 /// This is the number of the *next* path status frame to be sent. +969 local_seq: VarInt, +970 /// The status set by the remote +971 remote_status: Option<(VarInt, PathStatus)>, +972} +973 +974impl PathStatusState { +975 /// To be called on received PATH_STATUS_AVAILABLE/PATH_STATUS_BACKUP frames +976 pub(super) fn remote_update(&mut self, status: PathStatus, seq: VarInt) { +977 if self.remote_status.is_some_and(|(curr, _)| curr >= seq) { +978 return trace!(%seq, "ignoring path status update"); +979 } +980 +981 let prev = self.remote_status.replace((seq, status)).map(|(_, s)| s); +982 if prev != Some(status) { +983 debug!(?status, ?seq, "remote changed path status"); +984 } +985 } +986 +987 /// Updates the local status +988 /// +989 /// If the local status changed, the previous value is returned +990 pub(super) fn local_update(&mut self, status: PathStatus) -> Option<PathStatus> { +991 if self.local_status == status { +992 return None; 993 } -994 } -995 -996 /// Updates the local status -997 /// -998 /// If the local status changed, the previous value is returned -999 pub(super) fn local_update(&mut self, status: PathStatus) -> Option<PathStatus> { -1000 if self.local_status == status { -1001 return None; -1002 } +994 +995 self.local_seq = self.local_seq.saturating_add(1u8); +996 Some(std::mem::replace(&mut self.local_status, status)) +997 } +998 +999 pub(crate) fn seq(&self) -> VarInt { +1000 self.local_seq +1001 } +1002} 1003 -1004 self.local_seq = self.local_seq.saturating_add(1u8); -1005 Some(std::mem::replace(&mut self.local_status, status)) -1006 } -1007 -1008 pub(crate) fn seq(&self) -> VarInt { -1009 self.local_seq -1010 } -1011} -1012 -1013/// The QUIC-MULTIPATH path status -1014/// -1015/// See section "3.3 Path Status Management": -1016/// <https://quicwg.org/multipath/draft-ietf-quic-multipath.html#name-path-status-management> -1017#[cfg_attr(test, derive(test_strategy::Arbitrary))] -1018#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] -1019pub enum PathStatus { -1020 /// Paths marked with as available will be used when scheduling packets -1021 /// -1022 /// If multiple paths are available, packets will be scheduled on whichever has -1023 /// capacity. -1024 #[default] -1025 Available, -1026 /// Paths marked as backup will only be used if there are no available paths -1027 /// -1028 /// If the max_idle_timeout is specified the path will be kept alive so that it does not -1029 /// expire. -1030 Backup, -1031} -1032 -1033/// Application events about paths -1034#[derive(Debug, Clone, PartialEq, Eq)] -1035pub enum PathEvent { -1036 /// A new path has been opened -1037 Opened { -1038 /// Which path is now open -1039 id: PathId, +1004/// The QUIC-MULTIPATH path status +1005/// +1006/// See section "3.3 Path Status Management": +1007/// <https://quicwg.org/multipath/draft-ietf-quic-multipath.html#name-path-status-management> +1008#[cfg_attr(test, derive(test_strategy::Arbitrary))] +1009#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] +1010pub enum PathStatus { +1011 /// Paths marked with as available will be used when scheduling packets +1012 /// +1013 /// If multiple paths are available, packets will be scheduled on whichever has +1014 /// capacity. +1015 #[default] +1016 Available, +1017 /// Paths marked as backup will only be used if there are no available paths +1018 /// +1019 /// If the max_idle_timeout is specified the path will be kept alive so that it does not +1020 /// expire. +1021 Backup, +1022} +1023 +1024/// Application events about paths +1025#[derive(Debug, Clone, PartialEq, Eq)] +1026pub enum PathEvent { +1027 /// A new path has been opened +1028 Opened { +1029 /// Which path is now open +1030 id: PathId, +1031 }, +1032 /// A path was abandoned and is no longer usable. +1033 /// +1034 /// This event will always be followed by [`Self::Discarded`] after some time. +1035 Abandoned { +1036 /// With path was abandoned. +1037 id: PathId, +1038 /// Reason why this path was abandoned. +1039 reason: PathAbandonReason, 1040 }, -1041 /// A path was abandoned and is no longer usable. +1041 /// A path was discarded and all remaining state for it has been removed. 1042 /// -1043 /// This event will always be followed by [`Self::Discarded`] after some time. -1044 Abandoned { -1045 /// With path was abandoned. +1043 /// This event is the last event for a path, and is always emitted after [`Self::Abandoned`]. +1044 Discarded { +1045 /// Which path had its state dropped 1046 id: PathId, -1047 /// Reason why this path was abandoned. -1048 reason: PathAbandonReason, -1049 }, -1050 /// A path was discarded and all remaining state for it has been removed. -1051 /// -1052 /// This event is the last event for a path, and is always emitted after [`Self::Abandoned`]. -1053 Discarded { -1054 /// Which path had its state dropped -1055 id: PathId, -1056 /// The final path stats, they are no longer available via [`Connection::stats`] -1057 /// -1058 /// [`Connection::stats`]: super::Connection::stats -1059 path_stats: Box<PathStats>, -1060 }, -1061 /// The remote changed the status of the path -1062 /// -1063 /// The local status is not changed because of this event. It is up to the application -1064 /// to update the local status, which is used for packet scheduling, when the remote -1065 /// changes the status. -1066 RemoteStatus { -1067 /// Path which has changed status -1068 id: PathId, -1069 /// The new status set by the remote -1070 status: PathStatus, -1071 }, -1072 /// Received an observation of our external address from the peer. -1073 ObservedAddr { -1074 /// Path over which the observed address was reported, [`PathId::ZERO`] when multipath is -1075 /// not negotiated -1076 id: PathId, -1077 /// The address observed by the remote over this path -1078 addr: SocketAddr, -1079 }, -1080} -1081 -1082/// Reason for why a path was abandoned. -1083#[derive(Debug, Clone, Eq, PartialEq)] -1084pub enum PathAbandonReason { -1085 /// The path was closed locally by the application. -1086 ApplicationClosed { -1087 /// The error code to be sent with the abandon frame. -1088 error_code: VarInt, -1089 }, -1090 /// We didn't receive a path response in time after opening this path. -1091 ValidationFailed, -1092 /// We didn't receive any data from the remote within the path's idle timeout. -1093 TimedOut, -1094 /// The path became unusable after a local network change. -1095 UnusableAfterNetworkChange, -1096 /// The path was opened in a NAT traversal round which was terminated. -1097 NatTraversalRoundEnded, -1098 /// The remote closed the path. -1099 RemoteAbandoned { -1100 /// The error that was sent with the abandon frame. -1101 error_code: VarInt, -1102 }, -1103} -1104 -1105impl PathAbandonReason { -1106 /// Whether this abandon was initiated by the remote peer. -1107 pub(crate) fn is_remote(&self) -> bool { -1108 matches!(self, Self::RemoteAbandoned { .. }) -1109 } -1110 -1111 /// Returns the error code to send with a PATH_ABANDON frame. -1112 pub(crate) fn error_code(&self) -> TransportErrorCode { -1113 match self { -1114 Self::ApplicationClosed { error_code } => (*error_code).into(), -1115 Self::NatTraversalRoundEnded => TransportErrorCode::APPLICATION_ABANDON_PATH, -1116 Self::ValidationFailed | Self::TimedOut | Self::UnusableAfterNetworkChange => { -1117 TransportErrorCode::PATH_UNSTABLE_OR_POOR -1118 } -1119 Self::RemoteAbandoned { error_code } => (*error_code).into(), -1120 } -1121 } -1122} -1123 -1124/// Error from setting path status -1125#[derive(Debug, Error, Clone, PartialEq, Eq)] -1126pub enum SetPathStatusError { -1127 /// Error indicating that a path has not been opened or has already been abandoned -1128 #[error("closed path")] -1129 ClosedPath, -1130 /// Error indicating that this operation requires multipath to be negotiated whereas it hasn't been -1131 #[error("multipath not negotiated")] -1132 MultipathNotNegotiated, -1133} -1134 -1135/// Error indicating that a path has not been opened or has already been abandoned -1136#[derive(Debug, Default, Error, Clone, PartialEq, Eq)] -1137#[error("closed path")] -1138pub struct ClosedPath { -1139 pub(super) _private: (), -1140} -1141 -1142#[cfg(test)] -1143mod tests { -1144 use super::*; -1145 -1146 #[test] -1147 fn test_path_id_saturating_add() { -1148 // add within range behaves normally -1149 let large: PathId = u16::MAX.into(); -1150 let next = u32::from(u16::MAX) + 1; -1151 assert_eq!(large.saturating_add(1u8), PathId::from(next)); -1152 -1153 // outside range saturates -1154 assert_eq!(PathId::MAX.saturating_add(1u8), PathId::MAX) -1155 } -1156}

\ No newline at end of file +1047 /// The final path stats, they are no longer available via [`Connection::stats`] +1048 /// +1049 /// [`Connection::stats`]: super::Connection::stats +1050 path_stats: Box<PathStats>, +1051 }, +1052 /// The remote changed the status of the path +1053 /// +1054 /// The local status is not changed because of this event. It is up to the application +1055 /// to update the local status, which is used for packet scheduling, when the remote +1056 /// changes the status. +1057 RemoteStatus { +1058 /// Path which has changed status +1059 id: PathId, +1060 /// The new status set by the remote +1061 status: PathStatus, +1062 }, +1063 /// Received an observation of our external address from the peer. +1064 ObservedAddr { +1065 /// Path over which the observed address was reported, [`PathId::ZERO`] when multipath is +1066 /// not negotiated +1067 id: PathId, +1068 /// The address observed by the remote over this path +1069 addr: SocketAddr, +1070 }, +1071} +1072 +1073/// Reason for why a path was abandoned. +1074#[derive(Debug, Clone, Eq, PartialEq)] +1075pub enum PathAbandonReason { +1076 /// The path was closed locally by the application. +1077 ApplicationClosed { +1078 /// The error code to be sent with the abandon frame. +1079 error_code: VarInt, +1080 }, +1081 /// We didn't receive a path response in time after opening this path. +1082 ValidationFailed, +1083 /// We didn't receive any data from the remote within the path's idle timeout. +1084 TimedOut, +1085 /// The path became unusable after a local network change. +1086 UnusableAfterNetworkChange, +1087 /// The path was opened in a NAT traversal round which was terminated. +1088 NatTraversalRoundEnded, +1089 /// The remote closed the path. +1090 RemoteAbandoned { +1091 /// The error that was sent with the abandon frame. +1092 error_code: VarInt, +1093 }, +1094} +1095 +1096impl PathAbandonReason { +1097 /// Whether this abandon was initiated by the remote peer. +1098 pub(crate) fn is_remote(&self) -> bool { +1099 matches!(self, Self::RemoteAbandoned { .. }) +1100 } +1101 +1102 /// Returns the error code to send with a PATH_ABANDON frame. +1103 pub(crate) fn error_code(&self) -> TransportErrorCode { +1104 match self { +1105 Self::ApplicationClosed { error_code } => (*error_code).into(), +1106 Self::NatTraversalRoundEnded => TransportErrorCode::APPLICATION_ABANDON_PATH, +1107 Self::ValidationFailed | Self::TimedOut | Self::UnusableAfterNetworkChange => { +1108 TransportErrorCode::PATH_UNSTABLE_OR_POOR +1109 } +1110 Self::RemoteAbandoned { error_code } => (*error_code).into(), +1111 } +1112 } +1113} +1114 +1115/// Error from setting path status +1116#[derive(Debug, Error, Clone, PartialEq, Eq)] +1117pub enum SetPathStatusError { +1118 /// Error indicating that a path has not been opened or has already been abandoned +1119 #[error("closed path")] +1120 ClosedPath, +1121 /// Error indicating that this operation requires multipath to be negotiated whereas it hasn't been +1122 #[error("multipath not negotiated")] +1123 MultipathNotNegotiated, +1124} +1125 +1126/// Error indicating that a path has not been opened or has already been abandoned +1127#[derive(Debug, Default, Error, Clone, PartialEq, Eq)] +1128#[error("closed path")] +1129pub struct ClosedPath { +1130 pub(super) _private: (), +1131} +1132 +1133#[cfg(test)] +1134mod tests { +1135 use super::*; +1136 +1137 #[test] +1138 fn test_path_id_saturating_add() { +1139 // add within range behaves normally +1140 let large: PathId = u16::MAX.into(); +1141 let next = u32::from(u16::MAX) + 1; +1142 assert_eq!(large.saturating_add(1u8), PathId::from(next)); +1143 +1144 // outside range saturates +1145 assert_eq!(PathId::MAX.saturating_add(1u8), PathId::MAX) +1146 } +1147}

\ No newline at end of file diff --git a/pr/616/docs/src/noq_proto/connection/spaces.rs.html b/pr/616/docs/src/noq_proto/connection/spaces.rs.html index 5d65aedc1..3f3a3fa55 100644 --- a/pr/616/docs/src/noq_proto/connection/spaces.rs.html +++ b/pr/616/docs/src/noq_proto/connection/spaces.rs.html @@ -2,1543 +2,1594 @@ 2 cmp, 3 collections::{BTreeMap, BTreeSet, VecDeque}, 4 mem, -5 net::IpAddr, -6 ops::{Bound, Index, IndexMut}, -7}; -8 -9use rand::{CryptoRng, RngExt}; -10use rustc_hash::{FxHashMap, FxHashSet}; -11use sorted_index_buffer::SortedIndexBuffer; -12use tracing::trace; -13 -14use super::PathId; -15use crate::{ -16 Dir, Duration, FourTuple, Instant, StreamId, TransportError, TransportErrorCode, VarInt, -17 connection::StreamsState, -18 frame::{self, AddAddress, RemoveAddress}, -19 packet::SpaceId, -20 range_set::ArrayRangeSet, -21 shared::IssuedCid, -22}; -23 -24pub(super) struct PacketSpace { -25 /// Data to send -26 pub(super) pending: Retransmits, -27 -28 /// Multipath packet number spaces -29 /// -30 /// Each [`PathId`] has it's own [`PacketNumberSpace`]. Only the [`SpaceId::Data`] can -31 /// have multiple packet number spaces, the other spaces only have a number space for -32 /// `PathId::ZERO`, which is populated at creation. -33 pub(super) number_spaces: BTreeMap<PathId, PacketNumberSpace>, -34} -35 -36impl PacketSpace { -37 pub(super) fn new(now: Instant, space: SpaceId, rng: &mut (impl CryptoRng + ?Sized)) -> Self { -38 let number_space_0 = PacketNumberSpace::new(now, space, rng); -39 Self { -40 pending: Retransmits::default(), -41 number_spaces: BTreeMap::from([(PathId::ZERO, number_space_0)]), -42 } -43 } -44 -45 #[cfg(test)] -46 pub(super) fn new_deterministic(now: Instant, space: SpaceId) -> Self { -47 let number_space_0 = PacketNumberSpace::new_deterministic(now, space); -48 Self { -49 pending: Retransmits::default(), -50 number_spaces: BTreeMap::from([(PathId::ZERO, number_space_0)]), -51 } -52 } -53 -54 /// Returns the [`PacketNumberSpace`] for a path -55 /// -56 /// When multipath is disabled use [`PathId::ZERO`]. -57 pub(super) fn path_space(&self, path_id: PathId) -> Option<&PacketNumberSpace> { -58 self.number_spaces.get(&path_id) -59 } -60 -61 /// Returns a mutable reference to the [`PacketNumberSpace`] for a path -62 /// -63 /// When multipath is disabled use [`PathId::ZERO`]. -64 pub(super) fn path_space_mut(&mut self, path_id: PathId) -> Option<&mut PacketNumberSpace> { -65 self.number_spaces.get_mut(&path_id) -66 } -67 -68 /// Returns the [`PacketNumberSpace`] for a path -69 /// -70 /// When multipath is disabled use `PathId::ZERO`. -71 // TODO(flub): Note that this only exists as `&mut self` because it creates a new -72 // [`PacketNumberSpace`] if one is not yet available for a path. This forces a few -73 // more `&mut` references to users than strictly needed. An alternative would be to -74 // return an Option but that would need to be handled for all callers. This could be -75 // worth exploring once we have all the main multipath bits fitted. -76 pub(super) fn for_path(&mut self, path: PathId) -> &mut PacketNumberSpace { -77 self.number_spaces -78 .get_mut(&path) -79 .unwrap_or_else(|| panic!("PacketNumberSpace missing for {path}")) -80 } -81 -82 pub(super) fn iter_paths_mut(&mut self) -> impl Iterator<Item = &mut PacketNumberSpace> { -83 self.number_spaces.values_mut() -84 } -85 -86 /// Queue data for a tail loss probe (or anti-amplification deadlock prevention) packet -87 /// -88 /// Probes are sent similarly to normal packets when an expected ACK has not arrived. We never -89 /// deem a packet lost until we receive an ACK that should have included it, but if a trailing -90 /// run of packets (or their ACKs) are lost, this might not happen in a timely fashion. We send -91 /// probe packets to force an ACK, and exempt them from congestion control to prevent a deadlock -92 /// when the congestion window is filled with lost tail packets. -93 /// -94 /// We prefer to send new data, to make the most efficient use of bandwidth. If there's no data -95 /// waiting to be sent, then we retransmit in-flight data to reduce odds of loss. If there's no -96 /// in-flight data either, we're probably a client guarding against a handshake -97 /// anti-amplification deadlock and we just make something up. -98 pub(super) fn queue_tail_loss_probe( -99 &mut self, -100 path_id: PathId, -101 request_immediate_ack: bool, -102 streams: &StreamsState, -103 ) { -104 if request_immediate_ack { -105 // The probe should be ACKed without delay (should only be used in the Data space and -106 // when the peer supports the acknowledgement frequency extension) -107 self.for_path(path_id).immediate_ack_pending = true; -108 } -109 -110 // We prefer to send new data to make most efficient use of bandwidth. -111 if !self.pending.is_empty(streams) { -112 // There's real data to send here, no need to make something up -113 return; -114 } -115 -116 // Retransmit data from the oldest in-flight data from any path -117 for packet in self -118 .number_spaces -119 .values_mut() -120 .flat_map(|s| s.sent_packets.values_mut()) -121 { -122 if !packet.retransmits.is_empty(streams) { -123 // Remove retransmitted data from the old packet so we don't end up retransmitting -124 // it *again* even if the copy we're sending now gets acknowledged. -125 self.pending |= mem::take(&mut packet.retransmits); -126 return; -127 } -128 } -129 -130 // Nothing new to send and nothing to retransmit, so fall back on a ping. This should only -131 // happen in rare cases during the handshake when the server becomes blocked by -132 // anti-amplification. -133 if !self.for_path(path_id).immediate_ack_pending { -134 self.for_path(path_id).ping_pending = true; -135 } -136 } -137 -138 /// Whether there is anything to send in this space -139 /// -140 /// For the data space [`Connection::can_send_1rtt`] also needs to be consulted. Prefer -141 /// to use [`Connection::space_can_send`] which handles this. -142 /// -143 /// [`Connection::can_send_1rtt`]: super::Connection::can_send_1rtt -144 /// [`Connection::space_can_send`]: super::Connection::space_can_send -145 pub(super) fn can_send(&self, path_id: PathId, streams: &StreamsState) -> SendableFrames { -146 let acks = self -147 .number_spaces -148 .values() -149 .any(|pns| pns.pending_acks.can_send()); -150 let space_specific = self -151 .number_spaces -152 .get(&path_id) -153 .is_some_and(|s| s.ping_pending || s.immediate_ack_pending); -154 let other = !self.pending.is_empty(streams); -155 SendableFrames { -156 acks, -157 close: false, -158 space_specific, -159 other, -160 } -161 } -162} -163 -164impl Index<SpaceId> for [PacketSpace; 3] { -165 type Output = PacketSpace; -166 fn index(&self, space: SpaceId) -> &PacketSpace { -167 &self.as_ref()[space as usize] -168 } -169} -170 -171impl IndexMut<SpaceId> for [PacketSpace; 3] { -172 fn index_mut(&mut self, space: SpaceId) -> &mut PacketSpace { -173 &mut self.as_mut()[space as usize] -174 } -175} -176 -177/// The three QUIC packet number space kinds -178/// -179/// Unlike [`SpaceId`], this always has exactly three variants — it represents the -180/// encryption level / space kind, not a specific packet number space identity. -181#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -182pub(crate) enum SpaceKind { -183 /// Initial packets (client and server). -184 Initial = 0, -185 /// Handshake packets. -186 Handshake = 1, -187 /// Data (1-RTT and 0-RTT) -188 Data = 2, -189} -190 -191impl SpaceKind { -192 /// Returns the encryption level for this space kind. -193 pub(crate) fn encryption_level(self) -> super::EncryptionLevel { -194 match self { -195 Self::Initial => super::EncryptionLevel::Initial, -196 Self::Handshake => super::EncryptionLevel::Handshake, -197 Self::Data => super::EncryptionLevel::OneRtt, -198 } -199 } -200} -201 -202impl Index<SpaceKind> for [PacketSpace; 3] { -203 type Output = PacketSpace; -204 fn index(&self, space: SpaceKind) -> &PacketSpace { -205 &self.as_ref()[space as usize] -206 } -207} -208 -209impl IndexMut<SpaceKind> for [PacketSpace; 3] { -210 fn index_mut(&mut self, space: SpaceKind) -> &mut PacketSpace { -211 &mut self.as_mut()[space as usize] -212 } -213} -214 -215/// The state of a single packet number space. -216/// -217/// In RFC9000 there are 3 packet number spaces: Initial, Handshake and Data. In QUIC -218/// Multipath there are multiple packet number spaces for Data, each identified by a -219/// [`PathId`]. -220/// -221/// This contains the state for a packet number space which is not specific to the 4-tuple -222/// this space is currently using. The 4-tuple specific state, like congestion controller, -223/// pacing, ECN, MTU etc, is stored in [`PathData`]. -224/// -225/// Note that the `Initial`, `Handshake` and `Data(PathId(0))` space all share the same -226/// [`PathData`]. -227/// -228/// You should access this via [`PacketSpace::for_path`]. -229/// -230/// [`PathData`]: super::paths::PathData -231pub(super) struct PacketNumberSpace { -232 /// Highest received packet number, if any -233 pub(super) largest_received_packet_number: Option<u64>, -234 /// The packet number of the next packet that will be sent, if any. In the Data space, the -235 /// packet number stored here is sometimes skipped by [`PacketNumberFilter`] logic. -236 pub(super) next_packet_number: u64, -237 /// The largest packet number the remote peer acknowledged in an ACK frame. -238 pub(super) largest_acked_packet_pn: Option<u64>, -239 pub(super) largest_acked_packet_send_time: Instant, -240 /// The highest-numbered ACK-eliciting packet we've sent -241 pub(super) largest_ack_eliciting_sent: u64, -242 /// Number of packets in `sent_packets` with numbers above `largest_ack_eliciting_sent` -243 pub(super) unacked_non_ack_eliciting_tail: u64, -244 /// Transmitted but not acked -245 // We use a BTreeMap here so we can efficiently query by range on ACK and for loss detection -246 pub(super) sent_packets: SortedIndexBuffer<SentPacket>, -247 /// Packets that were deemed lost -248 // Older packets are regularly removed in `Connection::drain_lost_packets`. -249 pub(super) lost_packets: SortedIndexBuffer<LostPacket>, -250 /// Number of explicit congestion notification codepoints seen on incoming packets -251 pub(super) ecn_counters: frame::EcnCounts, -252 /// Recent ECN counters sent by the peer in ACK frames -253 /// -254 /// Updated (and inspected) whenever we receive an ACK with a new highest acked packet -255 /// number. Stored per-space to simplify verification, which would otherwise have difficulty -256 /// distinguishing between ECN bleaching and counts having been updated by a near-simultaneous -257 /// ACK already processed in another space. -258 pub(super) ecn_feedback: frame::EcnCounts, -259 /// A PING frame needs to be sent on this path -260 pub(super) ping_pending: bool, -261 /// An IMMEDIATE_ACK (draft-ietf-quic-ack-frequency) frame needs to be sent on this path -262 pub(super) immediate_ack_pending: bool, -263 /// Packet deduplicator -264 pub(super) dedup: Dedup, -265 /// Packet numbers to acknowledge -266 pub(super) pending_acks: PendingAcks, -267 -268 // -269 // Loss Detection -270 // -271 /// The time the most recently sent retransmittable packet was sent. -272 pub(super) time_of_last_ack_eliciting_packet: Option<Instant>, -273 /// Earliest time when we might declare a packet lost. -274 /// -275 /// The time at which the earliest sent packet in this space will be considered lost -276 /// based on exceeding the reordering window in time. Only set for packets numbered -277 /// prior to a packet that has been acknowledged. -278 pub(super) loss_time: Option<Instant>, -279 /// Number of tail loss probes to send -280 pub(super) loss_probes: u32, -281 -282 /// Packet numbers to skip, only used in the data package space. -283 pn_filter: Option<PacketNumberFilter>, -284} -285 -286impl PacketNumberSpace { -287 pub(super) fn new(now: Instant, space: SpaceId, rng: &mut (impl CryptoRng + ?Sized)) -> Self { -288 let pn_filter = match space { -289 SpaceId::Initial | SpaceId::Handshake => None, -290 SpaceId::Data => Some(PacketNumberFilter::new(rng)), -291 }; -292 Self { -293 largest_received_packet_number: None, -294 next_packet_number: 0, -295 largest_acked_packet_pn: None, -296 largest_acked_packet_send_time: now, -297 largest_ack_eliciting_sent: 0, -298 unacked_non_ack_eliciting_tail: 0, -299 sent_packets: SortedIndexBuffer::new(), -300 lost_packets: SortedIndexBuffer::new(), -301 ecn_counters: frame::EcnCounts::ZERO, -302 ecn_feedback: frame::EcnCounts::ZERO, -303 ping_pending: false, -304 immediate_ack_pending: false, -305 dedup: Default::default(), -306 pending_acks: PendingAcks::new(), -307 time_of_last_ack_eliciting_packet: None, -308 loss_time: None, -309 loss_probes: 0, -310 pn_filter, -311 } -312 } -313 -314 #[cfg(test)] -315 fn new_deterministic(now: Instant, space: SpaceId) -> Self { -316 let pn_filter = match space { -317 SpaceId::Initial | SpaceId::Handshake => None, -318 SpaceId::Data => Some(PacketNumberFilter::disabled()), -319 }; -320 Self { -321 largest_received_packet_number: None, -322 next_packet_number: 0, -323 largest_acked_packet_pn: None, -324 largest_acked_packet_send_time: now, -325 largest_ack_eliciting_sent: 0, -326 unacked_non_ack_eliciting_tail: 0, -327 sent_packets: SortedIndexBuffer::new(), -328 lost_packets: SortedIndexBuffer::new(), -329 ecn_counters: frame::EcnCounts::ZERO, -330 ecn_feedback: frame::EcnCounts::ZERO, -331 ping_pending: false, -332 immediate_ack_pending: false, -333 dedup: Default::default(), -334 pending_acks: PendingAcks::new(), -335 time_of_last_ack_eliciting_packet: None, -336 loss_time: None, -337 loss_probes: 0, -338 pn_filter, -339 } -340 } -341 -342 /// Get the next outgoing packet number in this space -343 /// -344 /// In the Data space, the connection's [`PacketNumberFilter`] must be used rather than calling -345 /// this directly. -346 pub(super) fn get_tx_number(&mut self, rng: &mut (impl CryptoRng + ?Sized)) -> u64 { -347 // TODO: Handle packet number overflow gracefully -348 assert!(self.next_packet_number < 2u64.pow(62)); -349 let mut pn = self.next_packet_number; -350 self.next_packet_number += 1; -351 -352 // Skip this number if the filter says so, only enabled in the data space -353 if let Some(ref mut filter) = self.pn_filter -354 && filter.skip_pn(pn, rng) -355 { -356 pn = self.next_packet_number; -357 self.next_packet_number += 1; -358 } -359 pn -360 } -361 -362 pub(super) fn peek_tx_number(&mut self) -> u64 { -363 let pn = self.next_packet_number; -364 if let Some(ref filter) = self.pn_filter -365 && pn == filter.next_skipped_packet_number -366 { -367 return pn + 1; -368 } -369 pn -370 } -371 -372 /// Checks whether a skipped packet number was ACKed. -373 pub(super) fn check_ack(&self, range: std::ops::Range<u64>) -> Result<(), TransportError> { -374 if let Some(ref filter) = self.pn_filter -375 && filter -376 .prev_skipped_packet_number -377 .is_some_and(|pn| range.contains(&pn)) -378 { -379 return Err(TransportError::PROTOCOL_VIOLATION("unsent packet acked")); -380 } -381 Ok(()) -382 } -383 -384 /// Verifies sanity of an ECN block and returns whether congestion was encountered. -385 pub(super) fn detect_ecn( -386 &mut self, -387 newly_acked: u64, -388 ecn: frame::EcnCounts, -389 ) -> Result<bool, &'static str> { -390 let ect0_increase = ecn -391 .ect0 -392 .checked_sub(self.ecn_feedback.ect0) -393 .ok_or("peer ECT(0) count regression")?; -394 let ect1_increase = ecn -395 .ect1 -396 .checked_sub(self.ecn_feedback.ect1) -397 .ok_or("peer ECT(1) count regression")?; -398 let ce_increase = ecn -399 .ce -400 .checked_sub(self.ecn_feedback.ce) -401 .ok_or("peer CE count regression")?; -402 let total_increase = ect0_increase + ect1_increase + ce_increase; -403 if total_increase < newly_acked { -404 return Err("ECN bleaching"); -405 } -406 if (ect0_increase + ce_increase) < newly_acked || ect1_increase != 0 { -407 return Err("ECN corruption"); -408 } -409 // If total_increase > newly_acked (which happens when ACKs are lost), this is required by -410 // the draft so that long-term drift does not occur. If =, then the only question is whether -411 // to count CE packets as CE or ECT0. Recording them as CE is more consistent and keeps the -412 // congestion check obvious. -413 self.ecn_feedback = ecn; -414 Ok(ce_increase != 0) -415 } -416 -417 /// Stop tracking sent packet `number`, and return what we knew about it -418 pub(super) fn take(&mut self, number: u64) -> Option<SentPacket> { -419 let packet = self.sent_packets.remove(number)?; -420 if !packet.ack_eliciting && number > self.largest_ack_eliciting_sent { -421 self.unacked_non_ack_eliciting_tail = -422 self.unacked_non_ack_eliciting_tail.checked_sub(1).unwrap(); -423 } -424 Some(packet) -425 } -426 -427 /// May return a packet that should be forgotten -428 pub(super) fn sent(&mut self, number: u64, packet: SentPacket) -> Option<SentPacket> { -429 // Retain state for at most this many non-ACK-eliciting packets sent after the most recently -430 // sent ACK-eliciting packet. We're never guaranteed to receive an ACK for those, and we -431 // can't judge them as lost without an ACK, so to limit memory in applications which receive -432 // packets but don't send ACK-eliciting data for long periods use we must eventually start -433 // forgetting about them, although it might also be reasonable to just kill the connection -434 // due to weird peer behavior. -435 const MAX_UNACKED_NON_ACK_ELICTING_TAIL: u64 = 1_000; -436 -437 let mut forgotten = None; -438 if packet.ack_eliciting { -439 self.unacked_non_ack_eliciting_tail = 0; -440 self.largest_ack_eliciting_sent = number; -441 } else if self.unacked_non_ack_eliciting_tail > MAX_UNACKED_NON_ACK_ELICTING_TAIL { -442 let oldest_after_ack_eliciting = self -443 .sent_packets -444 .keys_range(( -445 Bound::Excluded(self.largest_ack_eliciting_sent), -446 Bound::Unbounded, -447 )) -448 .next() -449 .unwrap(); -450 // Per https://www.rfc-editor.org/rfc/rfc9000.html#name-frames-and-frame-types, -451 // non-ACK-eliciting packets must only contain PADDING, ACK, and CONNECTION_CLOSE -452 // frames, which require no special handling on ACK or loss beyond removal from -453 // in-flight counters if padded. -454 let packet = self -455 .sent_packets -456 .remove(oldest_after_ack_eliciting) -457 .unwrap(); -458 debug_assert!(!packet.ack_eliciting); -459 forgotten = Some(packet); -460 } else { -461 self.unacked_non_ack_eliciting_tail += 1; -462 } -463 -464 self.sent_packets.insert(number, packet); -465 forgotten -466 } -467 -468 /// Whether any congestion-controlled packets in this space are not yet acknowledged or lost -469 pub(super) fn has_in_flight(&self) -> bool { -470 // The number of non-congestion-controlled (i.e. size == 0) packets in flight at a time -471 // should be small, since otherwise congestion control wouldn't be effective. Therefore, -472 // this shouldn't need to visit many packets before finishing one way or another. -473 self.sent_packets.values().any(|x| x.size != 0) -474 } -475} -476 -477/// Represents one or more packets subject to retransmission -478#[derive(Debug, Clone)] -479pub(super) struct SentPacket { -480 /// [`PathData::generation`](super::PathData::generation) of the path on which this packet was sent -481 pub(super) path_generation: u64, -482 /// The time the packet was sent. -483 pub(super) time_sent: Instant, -484 /// The number of bytes sent in the packet, not including UDP or IP overhead, but including QUIC -485 /// framing overhead. Zero if this packet is not counted towards congestion control, i.e. not an -486 /// "in flight" packet. -487 pub(super) size: u16, -488 /// Whether an acknowledgement is expected directly in response to this packet. -489 pub(super) ack_eliciting: bool, -490 /// The largest packet number acknowledged by this packet -491 pub(super) largest_acked: FxHashMap<PathId, u64>, -492 /// Data which needs to be retransmitted in case the packet is lost. -493 /// The data is boxed to minimize `SentPacket` size for the typical case of -494 /// packets only containing ACKs and STREAM frames. -495 pub(super) retransmits: ThinRetransmits, -496 /// Metadata for stream frames in a packet -497 /// -498 /// The actual application data is stored with the stream state. -499 pub(super) stream_frames: frame::StreamMetaVec, -500} -501 -502/// Represents one or more packets that are deemed lost. -503#[derive(Debug)] -504pub(super) struct LostPacket { -505 /// The time the packet was sent. -506 pub(super) time_sent: Instant, -507} -508 -509/// Retransmittable data queue -510#[allow(unreachable_pub)] // fuzzing only -511#[derive(Debug, Default, Clone)] -512pub struct Retransmits { -513 pub(super) max_data: bool, -514 pub(super) max_stream_id: [bool; 2], -515 pub(super) streams_blocked: [bool; 2], -516 pub(super) reset_stream: Vec<(StreamId, VarInt)>, -517 pub(super) stop_sending: Vec<frame::StopSending>, -518 pub(super) max_stream_data: FxHashSet<StreamId>, -519 pub(super) crypto: VecDeque<frame::Crypto>, -520 pub(super) new_cids: PendingNewCids, -521 pub(super) retire_cids: Vec<(PathId, u64)>, -522 pub(super) ack_frequency: bool, -523 pub(super) handshake_done: bool, -524 pub(super) observed_addr: bool, -525 /// Whether we should inform the peer we will allow higher [`PathId`]s. -526 pub(super) max_path_id: bool, -527 /// Whether we should inform the peer that their max [`PathId`] is blocking our attempt to open -528 /// new paths. -529 // TODO(@divma): we need logic to prevent sending this more than once after being ack-d once -530 pub(super) paths_blocked: bool, -531 /// For each enqueued NEW_TOKEN frame, a copy of the path's remote address -532 /// -533 /// There are 2 reasons this is unusual: -534 /// -535 /// - If the path changes, NEW_TOKEN frames bound for the old path are not retransmitted on the -536 /// new path. That is why this field stores the remote address: so that ones for old paths -537 /// can be filtered out. -538 /// - If a token is lost, a new randomly generated token is re-transmitted, rather than the -539 /// original. This is so that if both transmissions are received, the client won't risk -540 /// sending the same token twice. That is why this field does _not_ store any actual token. -541 /// -542 /// It is true that a QUIC endpoint will only want to effectively have NEW_TOKEN frames -543 /// enqueued for its current path at a given point in time. Based on that, we could conceivably -544 /// change this from a vector to an `Option<(FourTuple, usize)>` or just a `usize` or -545 /// something. However, due to the architecture of noq, it is considerably simpler to not do -546 /// that; consider what such a change would mean for implementing `BitOrAssign` on Self. -547 pub(super) new_tokens: Vec<FourTuple>, -548 /// Paths which need to be abandoned -549 pub(super) path_abandon: BTreeMap<PathId, TransportErrorCode>, -550 /// If a [`frame::PathStatusAvailable`] and [`frame::PathStatusBackup`] need to be sent for a path -551 pub(super) path_status: BTreeSet<PathId>, -552 /// If a PATH_CIDS_BLOCKED frame needs to be sent for a path -553 pub(super) path_cids_blocked: BTreeSet<PathId>, -554 -555 // Nat traversal data -556 /// Addresses to report in `ADD_ADDRESS` frames -557 pub(super) add_address: BTreeSet<AddAddress>, -558 /// Address IDs to remove in `REMOVE_ADDRESS` frames -559 pub(super) remove_address: BTreeSet<RemoveAddress>, -560 /// Round and local addresses to advertise in `REACH_OUT` frames -561 pub(super) reach_out: Option<(VarInt, FxHashSet<(IpAddr, u16)>)>, -562} -563 -564impl Retransmits { -565 pub(super) fn is_empty(&self, streams: &StreamsState) -> bool { -566 let Self { -567 max_data, -568 max_stream_id, -569 streams_blocked, -570 reset_stream, -571 stop_sending, -572 max_stream_data, -573 crypto, -574 new_cids, -575 retire_cids, -576 ack_frequency, -577 handshake_done, -578 observed_addr, -579 max_path_id, -580 paths_blocked, -581 new_tokens, -582 path_abandon, -583 path_status, -584 path_cids_blocked, -585 add_address, -586 remove_address, -587 reach_out, -588 } = &self; -589 !max_data -590 && !max_stream_id.iter().any(|x| *x) -591 && !streams_blocked.iter().any(|x| *x) -592 && reset_stream.is_empty() -593 && stop_sending.is_empty() -594 && max_stream_data -595 .iter() -596 .all(|&id| !streams.can_send_flow_control(id)) -597 && crypto.is_empty() -598 && new_cids.is_empty() -599 && retire_cids.is_empty() -600 && !ack_frequency -601 && !handshake_done -602 && !observed_addr -603 && !max_path_id -604 && !paths_blocked -605 && new_tokens.is_empty() -606 && path_abandon.is_empty() -607 && path_status.is_empty() -608 && path_cids_blocked.is_empty() -609 && add_address.is_empty() -610 && remove_address.is_empty() -611 && reach_out.is_none() -612 } -613} -614 -615impl ::std::ops::BitOrAssign for Retransmits { -616 fn bitor_assign(&mut self, rhs: Self) { -617 let Self { -618 max_data, -619 max_stream_id, -620 streams_blocked, -621 reset_stream, -622 stop_sending, -623 max_stream_data, -624 crypto, -625 new_cids, -626 retire_cids, -627 ack_frequency, -628 handshake_done, -629 observed_addr, -630 max_path_id, -631 paths_blocked, -632 new_tokens, -633 mut path_abandon, -634 mut path_status, -635 mut path_cids_blocked, -636 add_address, -637 remove_address, -638 reach_out, -639 } = rhs; -640 -641 // We reduce in-stream head-of-line blocking by queueing retransmits before other data for -642 // STREAM and CRYPTO frames. -643 self.max_data |= max_data; -644 for dir in Dir::iter() { -645 self.max_stream_id[dir as usize] |= max_stream_id[dir as usize]; -646 self.streams_blocked[dir as usize] |= streams_blocked[dir as usize]; -647 } -648 self.reset_stream.extend_from_slice(&reset_stream); -649 self.stop_sending.extend_from_slice(&stop_sending); -650 self.max_stream_data.extend(&max_stream_data); -651 for crypto in crypto.into_iter().rev() { -652 self.crypto.push_front(crypto); -653 } -654 self.new_cids.extend(&new_cids); -655 self.retire_cids.extend(retire_cids); -656 self.ack_frequency |= ack_frequency; -657 self.handshake_done |= handshake_done; -658 self.observed_addr |= observed_addr; -659 self.max_path_id |= max_path_id; -660 self.paths_blocked |= paths_blocked; -661 self.new_tokens.extend_from_slice(&new_tokens); -662 self.path_abandon.append(&mut path_abandon); -663 self.path_status.append(&mut path_status); -664 self.path_cids_blocked.append(&mut path_cids_blocked); -665 self.add_address.extend(add_address.iter().copied()); -666 self.remove_address.extend(remove_address.iter().copied()); -667 if let Some((rhs_round, rhs_addrs)) = reach_out { -668 match self.reach_out.as_mut() { -669 // Use RHS if there is no recorded round. -670 None => self.reach_out = Some((rhs_round, rhs_addrs)), -671 // Use RHS if newer. -672 Some((lhs_round, _lhs_addrs)) if rhs_round > *lhs_round => { -673 self.reach_out = Some((rhs_round, rhs_addrs)); -674 } -675 // If both rounds are the same, merge them. -676 Some((lhs_round, lhs_addrs)) if rhs_round == *lhs_round => { -677 lhs_addrs.extend(rhs_addrs); -678 } -679 // LHS round is newer, ignore RHS -680 Some(_) => {} -681 } -682 } -683 } -684} -685 -686impl ::std::ops::BitOrAssign<ThinRetransmits> for Retransmits { -687 fn bitor_assign(&mut self, rhs: ThinRetransmits) { -688 let ThinRetransmits { retransmits } = rhs; -689 if let Some(retransmits) = retransmits { -690 self.bitor_assign(*retransmits) -691 } -692 } -693} -694 -695impl ::std::iter::FromIterator<Self> for Retransmits { -696 fn from_iter<T>(iter: T) -> Self -697 where -698 T: IntoIterator<Item = Self>, -699 { -700 let mut result = Self::default(); -701 for packet in iter { -702 result |= packet; -703 } -704 result -705 } -706} -707 -708/// The queue of new CIDs to be transmitted to the peer. -709/// -710/// This queue is always sorted, so that popping off the last item is always the lowest -711/// sequence number of the lowest path ID. Which is the CID you want to be issued next. -712/// -713/// This is but a newtype over a `Vec` to enforce the sorted invariant. -714#[derive(Clone, Debug, Default)] -715pub(super) struct PendingNewCids { -716 /// The CIDs themselves. -717 cids: Vec<IssuedCid>, -718 /// Whether [`Self::cids`] is sorted or not. -719 sorted: bool, -720} +5 ops::{Bound, Index, IndexMut}, +6}; +7 +8use rand::{CryptoRng, RngExt}; +9use rustc_hash::{FxHashMap, FxHashSet}; +10use sorted_index_buffer::SortedIndexBuffer; +11use tracing::trace; +12 +13use super::PathId; +14use crate::{ +15 Dir, Duration, FourTuple, Instant, StreamId, TransportError, TransportErrorCode, VarInt, +16 connection::StreamsState, +17 frame::{self, AddAddress, RemoveAddress}, +18 packet::SpaceId, +19 range_set::ArrayRangeSet, +20 shared::IssuedCid, +21}; +22 +23pub(super) struct PacketSpace { +24 /// Data to send +25 pub(super) pending: Retransmits, +26 +27 /// Multipath packet number spaces +28 /// +29 /// Each [`PathId`] has it's own [`PacketNumberSpace`]. Only the [`SpaceId::Data`] can +30 /// have multiple packet number spaces, the other spaces only have a number space for +31 /// `PathId::ZERO`, which is populated at creation. +32 pub(super) number_spaces: BTreeMap<PathId, PacketNumberSpace>, +33} +34 +35impl PacketSpace { +36 pub(super) fn new(now: Instant, space: SpaceId, rng: &mut (impl CryptoRng + ?Sized)) -> Self { +37 let number_space_0 = PacketNumberSpace::new(now, space, rng); +38 Self { +39 pending: Retransmits::default(), +40 number_spaces: BTreeMap::from([(PathId::ZERO, number_space_0)]), +41 } +42 } +43 +44 #[cfg(test)] +45 pub(super) fn new_deterministic(now: Instant, space: SpaceId) -> Self { +46 let number_space_0 = PacketNumberSpace::new_deterministic(now, space); +47 Self { +48 pending: Retransmits::default(), +49 number_spaces: BTreeMap::from([(PathId::ZERO, number_space_0)]), +50 } +51 } +52 +53 /// Returns the [`PacketNumberSpace`] for a path +54 /// +55 /// When multipath is disabled use [`PathId::ZERO`]. +56 pub(super) fn path_space(&self, path_id: PathId) -> Option<&PacketNumberSpace> { +57 self.number_spaces.get(&path_id) +58 } +59 +60 /// Returns a mutable reference to the [`PacketNumberSpace`] for a path +61 /// +62 /// When multipath is disabled use [`PathId::ZERO`]. +63 pub(super) fn path_space_mut(&mut self, path_id: PathId) -> Option<&mut PacketNumberSpace> { +64 self.number_spaces.get_mut(&path_id) +65 } +66 +67 /// Returns the [`PacketNumberSpace`] for a path +68 /// +69 /// When multipath is disabled use `PathId::ZERO`. +70 // TODO(flub): Note that this only exists as `&mut self` because it creates a new +71 // [`PacketNumberSpace`] if one is not yet available for a path. This forces a few +72 // more `&mut` references to users than strictly needed. An alternative would be to +73 // return an Option but that would need to be handled for all callers. This could be +74 // worth exploring once we have all the main multipath bits fitted. +75 pub(super) fn for_path(&mut self, path: PathId) -> &mut PacketNumberSpace { +76 self.number_spaces +77 .get_mut(&path) +78 .unwrap_or_else(|| panic!("PacketNumberSpace missing for {path}")) +79 } +80 +81 pub(super) fn iter_paths_mut(&mut self) -> impl Iterator<Item = &mut PacketNumberSpace> { +82 self.number_spaces.values_mut() +83 } +84 +85 /// Queue data for a tail loss probe (or anti-amplification deadlock prevention) packet +86 /// +87 /// Probes are sent similarly to normal packets when an expected ACK has not arrived. We never +88 /// deem a packet lost until we receive an ACK that should have included it, but if a trailing +89 /// run of packets (or their ACKs) are lost, this might not happen in a timely fashion. We send +90 /// probe packets to force an ACK, and exempt them from congestion control to prevent a deadlock +91 /// when the congestion window is filled with lost tail packets. +92 /// +93 /// We prefer to send new data, to make the most efficient use of bandwidth. If there's no data +94 /// waiting to be sent, then we retransmit in-flight data to reduce odds of loss. If there's no +95 /// in-flight data either, we're probably a client guarding against a handshake +96 /// anti-amplification deadlock and we just make something up. +97 pub(super) fn queue_tail_loss_probe( +98 &mut self, +99 path_id: PathId, +100 request_immediate_ack: bool, +101 streams: &StreamsState, +102 ) { +103 if request_immediate_ack { +104 // The probe should be ACKed without delay (should only be used in the Data space and +105 // when the peer supports the acknowledgement frequency extension) +106 self.for_path(path_id).immediate_ack_pending = true; +107 } +108 +109 // We prefer to send new data to make most efficient use of bandwidth. +110 if !self.pending.is_empty(streams) { +111 // There's real data to send here, no need to make something up +112 return; +113 } +114 +115 // Retransmit data from the oldest in-flight data from any path +116 for packet in self +117 .number_spaces +118 .values_mut() +119 .flat_map(|s| s.sent_packets.values_mut()) +120 { +121 if !packet.retransmits.is_empty(streams) { +122 // Remove retransmitted data from the old packet so we don't end up retransmitting +123 // it *again* even if the copy we're sending now gets acknowledged. +124 self.pending |= mem::take(&mut packet.retransmits); +125 return; +126 } +127 } +128 +129 // Nothing new to send and nothing to retransmit, so fall back on a ping. This should only +130 // happen in rare cases during the handshake when the server becomes blocked by +131 // anti-amplification. +132 if !self.for_path(path_id).immediate_ack_pending { +133 self.for_path(path_id).ping_pending = true; +134 } +135 } +136 +137 /// Whether there is anything to send in this space +138 /// +139 /// For the data space [`Connection::can_send_1rtt`] also needs to be consulted. Prefer +140 /// to use [`Connection::space_can_send`] which handles this. +141 /// +142 /// [`Connection::can_send_1rtt`]: super::Connection::can_send_1rtt +143 /// [`Connection::space_can_send`]: super::Connection::space_can_send +144 pub(super) fn can_send(&self, path_id: PathId, streams: &StreamsState) -> SendableFrames { +145 let acks = self +146 .number_spaces +147 .values() +148 .any(|pns| pns.pending_acks.can_send()); +149 let space_specific = self +150 .number_spaces +151 .get(&path_id) +152 .is_some_and(|s| s.ping_pending || s.immediate_ack_pending); +153 let other = !self.pending.is_empty(streams); +154 SendableFrames { +155 acks, +156 close: false, +157 space_specific, +158 other, +159 } +160 } +161} +162 +163impl Index<SpaceId> for [PacketSpace; 3] { +164 type Output = PacketSpace; +165 fn index(&self, space: SpaceId) -> &PacketSpace { +166 &self.as_ref()[space as usize] +167 } +168} +169 +170impl IndexMut<SpaceId> for [PacketSpace; 3] { +171 fn index_mut(&mut self, space: SpaceId) -> &mut PacketSpace { +172 &mut self.as_mut()[space as usize] +173 } +174} +175 +176/// The three QUIC packet number space kinds +177/// +178/// Unlike [`SpaceId`], this always has exactly three variants — it represents the +179/// encryption level / space kind, not a specific packet number space identity. +180#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +181pub(crate) enum SpaceKind { +182 /// Initial packets (client and server). +183 Initial = 0, +184 /// Handshake packets. +185 Handshake = 1, +186 /// Data (1-RTT and 0-RTT) +187 Data = 2, +188} +189 +190impl SpaceKind { +191 /// Returns the encryption level for this space kind. +192 pub(crate) fn encryption_level(self) -> super::EncryptionLevel { +193 match self { +194 Self::Initial => super::EncryptionLevel::Initial, +195 Self::Handshake => super::EncryptionLevel::Handshake, +196 Self::Data => super::EncryptionLevel::OneRtt, +197 } +198 } +199} +200 +201impl Index<SpaceKind> for [PacketSpace; 3] { +202 type Output = PacketSpace; +203 fn index(&self, space: SpaceKind) -> &PacketSpace { +204 &self.as_ref()[space as usize] +205 } +206} +207 +208impl IndexMut<SpaceKind> for [PacketSpace; 3] { +209 fn index_mut(&mut self, space: SpaceKind) -> &mut PacketSpace { +210 &mut self.as_mut()[space as usize] +211 } +212} +213 +214/// The state of a single packet number space. +215/// +216/// In RFC9000 there are 3 packet number spaces: Initial, Handshake and Data. In QUIC +217/// Multipath there are multiple packet number spaces for Data, each identified by a +218/// [`PathId`]. +219/// +220/// This contains the state for a packet number space which is not specific to the 4-tuple +221/// this space is currently using. The 4-tuple specific state, like congestion controller, +222/// pacing, ECN, MTU etc, is stored in [`PathData`]. +223/// +224/// Note that the `Initial`, `Handshake` and `Data(PathId(0))` space all share the same +225/// [`PathData`]. +226/// +227/// You should access this via [`PacketSpace::for_path`]. +228/// +229/// [`PathData`]: super::paths::PathData +230pub(super) struct PacketNumberSpace { +231 /// Highest received packet number, if any +232 pub(super) largest_received_packet_number: Option<u64>, +233 /// The packet number of the next packet that will be sent, if any. In the Data space, the +234 /// packet number stored here is sometimes skipped by [`PacketNumberFilter`] logic. +235 pub(super) next_packet_number: u64, +236 /// The largest packet number the remote peer acknowledged in an ACK frame. +237 pub(super) largest_acked_packet_pn: Option<u64>, +238 pub(super) largest_acked_packet_send_time: Instant, +239 /// The highest-numbered ACK-eliciting packet we've sent +240 pub(super) largest_ack_eliciting_sent: u64, +241 /// Number of packets in `sent_packets` with numbers above `largest_ack_eliciting_sent` +242 pub(super) unacked_non_ack_eliciting_tail: u64, +243 /// Transmitted but not acked +244 // We use a BTreeMap here so we can efficiently query by range on ACK and for loss detection +245 pub(super) sent_packets: SortedIndexBuffer<SentPacket>, +246 /// Packets that were deemed lost +247 // Older packets are regularly removed in `Connection::drain_lost_packets`. +248 pub(super) lost_packets: SortedIndexBuffer<LostPacket>, +249 /// Number of explicit congestion notification codepoints seen on incoming packets +250 pub(super) ecn_counters: frame::EcnCounts, +251 /// Recent ECN counters sent by the peer in ACK frames +252 /// +253 /// Updated (and inspected) whenever we receive an ACK with a new highest acked packet +254 /// number. Stored per-space to simplify verification, which would otherwise have difficulty +255 /// distinguishing between ECN bleaching and counts having been updated by a near-simultaneous +256 /// ACK already processed in another space. +257 pub(super) ecn_feedback: frame::EcnCounts, +258 /// A PING frame needs to be sent on this path +259 pub(super) ping_pending: bool, +260 /// An IMMEDIATE_ACK (draft-ietf-quic-ack-frequency) frame needs to be sent on this path +261 pub(super) immediate_ack_pending: bool, +262 /// Packet deduplicator +263 pub(super) dedup: Dedup, +264 /// Packet numbers to acknowledge +265 pub(super) pending_acks: PendingAcks, +266 +267 // +268 // Loss Detection +269 // +270 /// The time the most recently sent retransmittable packet was sent. +271 pub(super) time_of_last_ack_eliciting_packet: Option<Instant>, +272 /// Earliest time when we might declare a packet lost. +273 /// +274 /// The time at which the earliest sent packet in this space will be considered lost +275 /// based on exceeding the reordering window in time. Only set for packets numbered +276 /// prior to a packet that has been acknowledged. +277 pub(super) loss_time: Option<Instant>, +278 /// Number of tail loss probes to send +279 pub(super) loss_probes: u32, +280 +281 /// Packet numbers to skip, only used in the data package space. +282 pn_filter: Option<PacketNumberFilter>, +283} +284 +285impl PacketNumberSpace { +286 pub(super) fn new(now: Instant, space: SpaceId, rng: &mut (impl CryptoRng + ?Sized)) -> Self { +287 let pn_filter = match space { +288 SpaceId::Initial | SpaceId::Handshake => None, +289 SpaceId::Data => Some(PacketNumberFilter::new(rng)), +290 }; +291 Self { +292 largest_received_packet_number: None, +293 next_packet_number: 0, +294 largest_acked_packet_pn: None, +295 largest_acked_packet_send_time: now, +296 largest_ack_eliciting_sent: 0, +297 unacked_non_ack_eliciting_tail: 0, +298 sent_packets: SortedIndexBuffer::new(), +299 lost_packets: SortedIndexBuffer::new(), +300 ecn_counters: frame::EcnCounts::ZERO, +301 ecn_feedback: frame::EcnCounts::ZERO, +302 ping_pending: false, +303 immediate_ack_pending: false, +304 dedup: Default::default(), +305 pending_acks: PendingAcks::new(), +306 time_of_last_ack_eliciting_packet: None, +307 loss_time: None, +308 loss_probes: 0, +309 pn_filter, +310 } +311 } +312 +313 #[cfg(test)] +314 fn new_deterministic(now: Instant, space: SpaceId) -> Self { +315 let pn_filter = match space { +316 SpaceId::Initial | SpaceId::Handshake => None, +317 SpaceId::Data => Some(PacketNumberFilter::disabled()), +318 }; +319 Self { +320 largest_received_packet_number: None, +321 next_packet_number: 0, +322 largest_acked_packet_pn: None, +323 largest_acked_packet_send_time: now, +324 largest_ack_eliciting_sent: 0, +325 unacked_non_ack_eliciting_tail: 0, +326 sent_packets: SortedIndexBuffer::new(), +327 lost_packets: SortedIndexBuffer::new(), +328 ecn_counters: frame::EcnCounts::ZERO, +329 ecn_feedback: frame::EcnCounts::ZERO, +330 ping_pending: false, +331 immediate_ack_pending: false, +332 dedup: Default::default(), +333 pending_acks: PendingAcks::new(), +334 time_of_last_ack_eliciting_packet: None, +335 loss_time: None, +336 loss_probes: 0, +337 pn_filter, +338 } +339 } +340 +341 /// Get the next outgoing packet number in this space +342 /// +343 /// In the Data space, the connection's [`PacketNumberFilter`] must be used rather than calling +344 /// this directly. +345 pub(super) fn get_tx_number(&mut self, rng: &mut (impl CryptoRng + ?Sized)) -> u64 { +346 // TODO: Handle packet number overflow gracefully +347 assert!(self.next_packet_number < 2u64.pow(62)); +348 let mut pn = self.next_packet_number; +349 self.next_packet_number += 1; +350 +351 // Skip this number if the filter says so, only enabled in the data space +352 if let Some(ref mut filter) = self.pn_filter +353 && filter.skip_pn(pn, rng) +354 { +355 pn = self.next_packet_number; +356 self.next_packet_number += 1; +357 } +358 pn +359 } +360 +361 pub(super) fn peek_tx_number(&mut self) -> u64 { +362 let pn = self.next_packet_number; +363 if let Some(ref filter) = self.pn_filter +364 && pn == filter.next_skipped_packet_number +365 { +366 return pn + 1; +367 } +368 pn +369 } +370 +371 /// Checks whether a skipped packet number was ACKed. +372 pub(super) fn check_ack(&self, range: std::ops::Range<u64>) -> Result<(), TransportError> { +373 if let Some(ref filter) = self.pn_filter +374 && filter +375 .prev_skipped_packet_number +376 .is_some_and(|pn| range.contains(&pn)) +377 { +378 return Err(TransportError::PROTOCOL_VIOLATION("unsent packet acked")); +379 } +380 Ok(()) +381 } +382 +383 /// Verifies sanity of an ECN block and returns whether congestion was encountered. +384 pub(super) fn detect_ecn( +385 &mut self, +386 newly_acked: u64, +387 ecn: frame::EcnCounts, +388 ) -> Result<bool, &'static str> { +389 let ect0_increase = ecn +390 .ect0 +391 .checked_sub(self.ecn_feedback.ect0) +392 .ok_or("peer ECT(0) count regression")?; +393 let ect1_increase = ecn +394 .ect1 +395 .checked_sub(self.ecn_feedback.ect1) +396 .ok_or("peer ECT(1) count regression")?; +397 let ce_increase = ecn +398 .ce +399 .checked_sub(self.ecn_feedback.ce) +400 .ok_or("peer CE count regression")?; +401 let total_increase = ect0_increase + ect1_increase + ce_increase; +402 if total_increase < newly_acked { +403 return Err("ECN bleaching"); +404 } +405 if (ect0_increase + ce_increase) < newly_acked || ect1_increase != 0 { +406 return Err("ECN corruption"); +407 } +408 // If total_increase > newly_acked (which happens when ACKs are lost), this is required by +409 // the draft so that long-term drift does not occur. If =, then the only question is whether +410 // to count CE packets as CE or ECT0. Recording them as CE is more consistent and keeps the +411 // congestion check obvious. +412 self.ecn_feedback = ecn; +413 Ok(ce_increase != 0) +414 } +415 +416 /// Stop tracking sent packet `number`, and return what we knew about it +417 pub(super) fn take(&mut self, number: u64) -> Option<SentPacket> { +418 let packet = self.sent_packets.remove(number)?; +419 if !packet.ack_eliciting && number > self.largest_ack_eliciting_sent { +420 self.unacked_non_ack_eliciting_tail = +421 self.unacked_non_ack_eliciting_tail.checked_sub(1).unwrap(); +422 } +423 Some(packet) +424 } +425 +426 /// May return a packet that should be forgotten +427 pub(super) fn sent(&mut self, number: u64, packet: SentPacket) -> Option<SentPacket> { +428 // Retain state for at most this many non-ACK-eliciting packets sent after the most recently +429 // sent ACK-eliciting packet. We're never guaranteed to receive an ACK for those, and we +430 // can't judge them as lost without an ACK, so to limit memory in applications which receive +431 // packets but don't send ACK-eliciting data for long periods use we must eventually start +432 // forgetting about them, although it might also be reasonable to just kill the connection +433 // due to weird peer behavior. +434 const MAX_UNACKED_NON_ACK_ELICTING_TAIL: u64 = 1_000; +435 +436 let mut forgotten = None; +437 if packet.ack_eliciting { +438 self.unacked_non_ack_eliciting_tail = 0; +439 self.largest_ack_eliciting_sent = number; +440 } else if self.unacked_non_ack_eliciting_tail > MAX_UNACKED_NON_ACK_ELICTING_TAIL { +441 let oldest_after_ack_eliciting = self +442 .sent_packets +443 .keys_range(( +444 Bound::Excluded(self.largest_ack_eliciting_sent), +445 Bound::Unbounded, +446 )) +447 .next() +448 .unwrap(); +449 // Per https://www.rfc-editor.org/rfc/rfc9000.html#name-frames-and-frame-types, +450 // non-ACK-eliciting packets must only contain PADDING, ACK, and CONNECTION_CLOSE +451 // frames, which require no special handling on ACK or loss beyond removal from +452 // in-flight counters if padded. +453 let packet = self +454 .sent_packets +455 .remove(oldest_after_ack_eliciting) +456 .unwrap(); +457 debug_assert!(!packet.ack_eliciting); +458 forgotten = Some(packet); +459 } else { +460 self.unacked_non_ack_eliciting_tail += 1; +461 } +462 +463 self.sent_packets.insert(number, packet); +464 forgotten +465 } +466 +467 /// Whether any congestion-controlled packets in this space are not yet acknowledged or lost +468 pub(super) fn has_in_flight(&self) -> bool { +469 // The number of non-congestion-controlled (i.e. size == 0) packets in flight at a time +470 // should be small, since otherwise congestion control wouldn't be effective. Therefore, +471 // this shouldn't need to visit many packets before finishing one way or another. +472 self.sent_packets.values().any(|x| x.size != 0) +473 } +474} +475 +476/// Represents one or more packets subject to retransmission +477#[derive(Debug, Clone)] +478pub(super) struct SentPacket { +479 /// [`PathData::generation`](super::PathData::generation) of the path on which this packet was sent +480 pub(super) path_generation: u64, +481 /// The time the packet was sent. +482 pub(super) time_sent: Instant, +483 /// The number of bytes sent in the packet, not including UDP or IP overhead, but including QUIC +484 /// framing overhead. Zero if this packet is not counted towards congestion control, i.e. not an +485 /// "in flight" packet. +486 pub(super) size: u16, +487 /// Whether an acknowledgement is expected directly in response to this packet. +488 pub(super) ack_eliciting: bool, +489 /// The largest packet number acknowledged by this packet +490 pub(super) largest_acked: FxHashMap<PathId, u64>, +491 /// Data which needs to be retransmitted in case the packet is lost. +492 /// The data is boxed to minimize `SentPacket` size for the typical case of +493 /// packets only containing ACKs and STREAM frames. +494 pub(super) retransmits: ThinRetransmits, +495 /// Metadata for stream frames in a packet +496 /// +497 /// The actual application data is stored with the stream state. +498 pub(super) stream_frames: frame::StreamMetaVec, +499} +500 +501/// Represents one or more packets that are deemed lost. +502#[derive(Debug)] +503pub(super) struct LostPacket { +504 /// The time the packet was sent. +505 pub(super) time_sent: Instant, +506} +507 +508/// Retransmittable data queue +509#[allow(unreachable_pub)] // fuzzing only +510#[derive(Debug, Default, Clone)] +511pub struct Retransmits { +512 pub(super) max_data: bool, +513 pub(super) max_stream_id: [bool; 2], +514 pub(super) streams_blocked: [bool; 2], +515 pub(super) reset_stream: Vec<(StreamId, VarInt)>, +516 pub(super) stop_sending: Vec<frame::StopSending>, +517 pub(super) max_stream_data: FxHashSet<StreamId>, +518 pub(super) crypto: VecDeque<frame::Crypto>, +519 pub(super) new_cids: PendingNewCids, +520 pub(super) retire_cids: Vec<(PathId, u64)>, +521 pub(super) ack_frequency: bool, +522 pub(super) handshake_done: bool, +523 pub(super) observed_addr: bool, +524 /// Whether we should inform the peer we will allow higher [`PathId`]s. +525 pub(super) max_path_id: bool, +526 /// Whether we should inform the peer that their max [`PathId`] is blocking our attempt to open +527 /// new paths. +528 // TODO(@divma): we need logic to prevent sending this more than once after being ack-d once +529 pub(super) paths_blocked: bool, +530 /// For each enqueued NEW_TOKEN frame, a copy of the path's remote address +531 /// +532 /// There are 2 reasons this is unusual: +533 /// +534 /// - If the path changes, NEW_TOKEN frames bound for the old path are not retransmitted on the +535 /// new path. That is why this field stores the remote address: so that ones for old paths +536 /// can be filtered out. +537 /// - If a token is lost, a new randomly generated token is re-transmitted, rather than the +538 /// original. This is so that if both transmissions are received, the client won't risk +539 /// sending the same token twice. That is why this field does _not_ store any actual token. +540 /// +541 /// It is true that a QUIC endpoint will only want to effectively have NEW_TOKEN frames +542 /// enqueued for its current path at a given point in time. Based on that, we could conceivably +543 /// change this from a vector to an `Option<(FourTuple, usize)>` or just a `usize` or +544 /// something. However, due to the architecture of noq, it is considerably simpler to not do +545 /// that; consider what such a change would mean for implementing `BitOrAssign` on Self. +546 pub(super) new_tokens: Vec<FourTuple>, +547 /// Paths which need to be abandoned +548 pub(super) path_abandon: BTreeMap<PathId, TransportErrorCode>, +549 /// If a [`frame::PathStatusAvailable`] and [`frame::PathStatusBackup`] need to be sent for a path +550 pub(super) path_status: BTreeSet<PathId>, +551 /// If a PATH_CIDS_BLOCKED frame needs to be sent for a path +552 pub(super) path_cids_blocked: BTreeSet<PathId>, +553 +554 // Nat traversal data +555 /// Addresses to report in `ADD_ADDRESS` frames +556 pub(super) add_address: BTreeSet<AddAddress>, +557 /// Address IDs to remove in `REMOVE_ADDRESS` frames +558 pub(super) remove_address: BTreeSet<RemoveAddress>, +559 /// Round and local addresses to advertise in `REACH_OUT` frames +560 pub(super) reach_out: PendingReachOutFrames, +561} +562 +563impl Retransmits { +564 pub(super) fn is_empty(&self, streams: &StreamsState) -> bool { +565 let Self { +566 max_data, +567 max_stream_id, +568 streams_blocked, +569 reset_stream, +570 stop_sending, +571 max_stream_data, +572 crypto, +573 new_cids, +574 retire_cids, +575 ack_frequency, +576 handshake_done, +577 observed_addr, +578 max_path_id, +579 paths_blocked, +580 new_tokens, +581 path_abandon, +582 path_status, +583 path_cids_blocked, +584 add_address, +585 remove_address, +586 reach_out, +587 } = &self; +588 !max_data +589 && !max_stream_id.iter().any(|x| *x) +590 && !streams_blocked.iter().any(|x| *x) +591 && reset_stream.is_empty() +592 && stop_sending.is_empty() +593 && max_stream_data +594 .iter() +595 .all(|&id| !streams.can_send_flow_control(id)) +596 && crypto.is_empty() +597 && new_cids.is_empty() +598 && retire_cids.is_empty() +599 && !ack_frequency +600 && !handshake_done +601 && !observed_addr +602 && !max_path_id +603 && !paths_blocked +604 && new_tokens.is_empty() +605 && path_abandon.is_empty() +606 && path_status.is_empty() +607 && path_cids_blocked.is_empty() +608 && add_address.is_empty() +609 && remove_address.is_empty() +610 && reach_out.is_empty() +611 } +612} +613 +614impl ::std::ops::BitOrAssign for Retransmits { +615 fn bitor_assign(&mut self, rhs: Self) { +616 let Self { +617 max_data, +618 max_stream_id, +619 streams_blocked, +620 reset_stream, +621 stop_sending, +622 max_stream_data, +623 crypto, +624 new_cids, +625 retire_cids, +626 ack_frequency, +627 handshake_done, +628 observed_addr, +629 max_path_id, +630 paths_blocked, +631 new_tokens, +632 mut path_abandon, +633 mut path_status, +634 mut path_cids_blocked, +635 add_address, +636 remove_address, +637 mut reach_out, +638 } = rhs; +639 +640 // We reduce in-stream head-of-line blocking by queueing retransmits before other data for +641 // STREAM and CRYPTO frames. +642 self.max_data |= max_data; +643 for dir in Dir::iter() { +644 self.max_stream_id[dir as usize] |= max_stream_id[dir as usize]; +645 self.streams_blocked[dir as usize] |= streams_blocked[dir as usize]; +646 } +647 self.reset_stream.extend_from_slice(&reset_stream); +648 self.stop_sending.extend_from_slice(&stop_sending); +649 self.max_stream_data.extend(&max_stream_data); +650 for crypto in crypto.into_iter().rev() { +651 self.crypto.push_front(crypto); +652 } +653 self.new_cids.extend(&new_cids); +654 self.retire_cids.extend(retire_cids); +655 self.ack_frequency |= ack_frequency; +656 self.handshake_done |= handshake_done; +657 self.observed_addr |= observed_addr; +658 self.max_path_id |= max_path_id; +659 self.paths_blocked |= paths_blocked; +660 self.new_tokens.extend_from_slice(&new_tokens); +661 self.path_abandon.append(&mut path_abandon); +662 self.path_status.append(&mut path_status); +663 self.path_cids_blocked.append(&mut path_cids_blocked); +664 self.add_address.extend(add_address.iter().copied()); +665 self.remove_address.extend(remove_address.iter().copied()); +666 self.reach_out.append(&mut reach_out); +667 } +668} +669 +670impl ::std::ops::BitOrAssign<ThinRetransmits> for Retransmits { +671 fn bitor_assign(&mut self, rhs: ThinRetransmits) { +672 let ThinRetransmits { retransmits } = rhs; +673 if let Some(retransmits) = retransmits { +674 self.bitor_assign(*retransmits) +675 } +676 } +677} +678 +679impl ::std::iter::FromIterator<Self> for Retransmits { +680 fn from_iter<T>(iter: T) -> Self +681 where +682 T: IntoIterator<Item = Self>, +683 { +684 let mut result = Self::default(); +685 for packet in iter { +686 result |= packet; +687 } +688 result +689 } +690} +691 +692/// The queue of new CIDs to be transmitted to the peer. +693/// +694/// This queue is always sorted, so that popping off the last item is always the lowest +695/// sequence number of the lowest path ID. Which is the CID you want to be issued next. +696/// +697/// This is but a newtype over a `Vec` to enforce the sorted invariant. +698#[derive(Clone, Debug, Default)] +699pub(super) struct PendingNewCids { +700 /// The CIDs themselves. +701 cids: Vec<IssuedCid>, +702 /// Whether [`Self::cids`] is sorted or not. +703 sorted: bool, +704} +705 +706impl PendingNewCids { +707 /// Inserts an issued CID into the queue. +708 pub(super) fn push(&mut self, cid: IssuedCid) { +709 self.cids.push(cid); +710 self.sorted = false; +711 } +712 +713 /// Pops the next issued CID to transmit from the queue. +714 pub(super) fn pop(&mut self) -> Option<IssuedCid> { +715 if !std::mem::replace(&mut self.sorted, true) { +716 self.cids +717 .sort_by_key(|cid| cmp::Reverse((cid.path_id, cid.sequence))); +718 } +719 self.cids.pop() +720 } 721 -722impl PendingNewCids { -723 /// Inserts an issued CID into the queue. -724 pub(super) fn push(&mut self, cid: IssuedCid) { -725 self.cids.push(cid); -726 self.sorted = false; -727 } -728 -729 /// Pops the next issued CID to transmit from the queue. -730 pub(super) fn pop(&mut self) -> Option<IssuedCid> { -731 if !std::mem::replace(&mut self.sorted, true) { -732 self.cids -733 .sort_by_key(|cid| cmp::Reverse((cid.path_id, cid.sequence))); -734 } -735 self.cids.pop() +722 pub(super) fn is_empty(&self) -> bool { +723 self.cids.is_empty() +724 } +725 +726 pub(super) fn extend(&mut self, other: &Self) { +727 self.cids.extend(&other.cids); +728 self.sorted = false; +729 } +730 +731 pub(super) fn retain<F>(&mut self, f: F) +732 where +733 F: FnMut(&IssuedCid) -> bool, +734 { +735 self.cids.retain(f); 736 } -737 -738 pub(super) fn is_empty(&self) -> bool { -739 self.cids.is_empty() -740 } -741 -742 pub(super) fn extend(&mut self, other: &Self) { -743 self.cids.extend(&other.cids); -744 self.sorted = false; -745 } -746 -747 pub(super) fn retain<F>(&mut self, f: F) -748 where -749 F: FnMut(&IssuedCid) -> bool, -750 { -751 self.cids.retain(f); -752 } -753} -754 -755/// A variant of `Retransmits` which only allocates storage when required -756#[derive(Debug, Default, Clone)] -757pub(super) struct ThinRetransmits { -758 retransmits: Option<Box<Retransmits>>, -759} -760 -761impl ThinRetransmits { -762 /// Returns `true` if no retransmits are necessary -763 pub(super) fn is_empty(&self, streams: &StreamsState) -> bool { -764 match &self.retransmits { -765 Some(retransmits) => retransmits.is_empty(streams), -766 None => true, -767 } -768 } -769 -770 /// Returns a reference to the retransmits stored in this box -771 pub(super) fn get(&self) -> Option<&Retransmits> { -772 self.retransmits.as_deref() -773 } -774 -775 /// Returns a mutable reference to the retransmits stored in this box -776 pub(super) fn get_mut(&mut self) -> Option<&mut Retransmits> { -777 self.retransmits.as_deref_mut() -778 } -779 -780 /// Returns a mutable reference to the stored retransmits -781 /// -782 /// This function will allocate a backing storage if required. -783 pub(super) fn get_or_create(&mut self) -> &mut Retransmits { -784 if self.retransmits.is_none() { -785 self.retransmits = Some(Box::default()); -786 } -787 self.retransmits.as_deref_mut().unwrap() +737} +738 +739/// Logically a Vec of REACH_OUT frames queued for transmit. +740/// +741/// This keeps track of the highest round ID and automatically drops frames with a lower +742/// round ID. +743/// +744/// The API is directly modelled on [`Vec`]. +745#[derive(Debug, Default, Clone)] +746pub(crate) struct PendingReachOutFrames { +747 /// The round ID of the REACH_OUT frames currently pending. +748 round: VarInt, +749 /// The REACH_OUT frames, always all having the same round ID. +750 frames: Vec<frame::ReachOut>, +751} +752 +753impl PendingReachOutFrames { +754 pub(crate) fn len(&self) -> usize { +755 self.frames.len() +756 } +757 +758 pub(crate) fn is_empty(&self) -> bool { +759 self.frames.is_empty() +760 } +761 +762 pub(crate) fn push(&mut self, frame: frame::ReachOut) { +763 if frame.round < self.round { +764 return; +765 } else if frame.round > self.round { +766 self.round = frame.round; +767 self.frames.clear(); +768 } +769 self.frames.push(frame); +770 } +771 +772 pub(crate) fn append(&mut self, other: &mut Self) { +773 if other.round < self.round { +774 other.frames.clear(); +775 return; +776 } else if other.round > self.round { +777 self.round = other.round; +778 self.frames.clear(); +779 } +780 self.frames.append(&mut other.frames); +781 } +782 +783 pub(crate) fn pop_if( +784 &mut self, +785 predicate: impl FnOnce(&mut frame::ReachOut) -> bool, +786 ) -> Option<frame::ReachOut> { +787 self.frames.pop_if(predicate) 788 } 789} 790 -791/// RFC4303-style sliding window packet number deduplicator. -792/// -793/// A contiguous bitfield, where each bit corresponds to a packet number and the rightmost bit is -794/// always set. A set bit represents a packet that has been successfully authenticated. Bits left of -795/// the window are assumed to be set. -796/// -797/// ```text -798/// ...xxxxxxxxx 1 0 -799/// ^ ^ ^ -800/// window highest next -801/// ``` -802#[derive(Debug, Default)] -803pub(super) struct Dedup { -804 window: Window, -805 /// Lowest packet number higher than all yet authenticated. -806 next: u64, -807} -808 -809/// Inner bitfield type. -810/// -811/// Because QUIC never reuses packet numbers, this only needs to be large enough to deal with -812/// packets that are reordered but still delivered in a timely manner. -813type Window = u128; -814 -815/// Number of packets tracked by `Dedup`. -816const WINDOW_SIZE: u64 = 1 + mem::size_of::<Window>() as u64 * 8; -817 -818impl Dedup { -819 /// Construct an empty window positioned at the start. -820 #[cfg(test)] -821 pub(super) fn new() -> Self { -822 Self { window: 0, next: 0 } -823 } -824 -825 /// Highest packet number authenticated. -826 fn highest(&self) -> u64 { -827 self.next - 1 -828 } -829 -830 /// Record a newly authenticated packet number. -831 /// -832 /// Returns whether the packet might be a duplicate. -833 pub(super) fn insert(&mut self, packet: u64) -> bool { -834 if let Some(diff) = packet.checked_sub(self.next) { -835 // Right of window -836 self.window = ((self.window << 1) | 1) -837 .checked_shl(cmp::min(diff, u64::from(u32::MAX)) as u32) -838 .unwrap_or(0); -839 self.next = packet + 1; -840 false -841 } else if self.highest() - packet < WINDOW_SIZE { -842 // Within window -843 if let Some(bit) = (self.highest() - packet).checked_sub(1) { -844 // < highest -845 let mask = 1 << bit; -846 let duplicate = self.window & mask != 0; -847 self.window |= mask; -848 duplicate -849 } else { -850 // == highest -851 true -852 } -853 } else { -854 // Left of window -855 true -856 } -857 } -858 -859 /// Returns the packet number of the smallest packet missing between the provided interval -860 /// -861 /// If there are no missing packets, returns `None` -862 fn smallest_missing_in_interval(&self, lower_bound: u64, upper_bound: u64) -> Option<u64> { -863 debug_assert!(lower_bound <= upper_bound); -864 debug_assert!(upper_bound <= self.highest()); -865 const BITFIELD_SIZE: u64 = (mem::size_of::<Window>() * 8) as u64; -866 -867 // Since we already know the packets at the boundaries have been received, we only need to -868 // check those in between them (this removes the necessity of extra logic to deal with the -869 // highest packet, which is stored outside the bitfield) -870 let lower_bound = lower_bound + 1; -871 let upper_bound = upper_bound.saturating_sub(1); -872 -873 // Note: the offsets are counted from the right -874 // The highest packet is not included in the bitfield, so we subtract 1 to account for that -875 let start_offset = (self.highest() - upper_bound).max(1) - 1; -876 if start_offset >= BITFIELD_SIZE { -877 // The start offset is outside of the window. All packets outside of the window are -878 // considered to be received. -879 return None; -880 } -881 -882 let end_offset_exclusive = self.highest().saturating_sub(lower_bound); -883 -884 // The range is clamped at the edge of the window, because any earlier packets are -885 // considered to be received -886 let range_len = end_offset_exclusive -887 .saturating_sub(start_offset) -888 .min(BITFIELD_SIZE); -889 if range_len == 0 { -890 return None; -891 } -892 -893 // Ensure the shift is within bounds (we already know start_offset < BITFIELD_SIZE, -894 // because of the early return) -895 let mask = if range_len == BITFIELD_SIZE { -896 u128::MAX -897 } else { -898 ((1u128 << range_len) - 1) << start_offset -899 }; -900 let gaps = !self.window & mask; -901 -902 let smallest_missing_offset = 128 - gaps.leading_zeros() as u64; -903 let smallest_missing_packet = self.highest() - smallest_missing_offset; -904 -905 if smallest_missing_packet <= upper_bound { -906 Some(smallest_missing_packet) -907 } else { -908 None -909 } -910 } -911 -912 /// Returns true if there are any missing packets between the provided interval -913 /// -914 /// The provided packet numbers must have been received before calling this function -915 fn missing_in_interval(&self, lower_bound: u64, upper_bound: u64) -> bool { -916 self.smallest_missing_in_interval(lower_bound, upper_bound) -917 .is_some() -918 } -919} -920 -921/// Indicates which data is available for sending. -922/// -923/// This applies to a particular space ID that was queried and all refers to on-path data. -924#[derive(Clone, Copy, PartialEq, Eq, Debug)] -925pub(super) struct SendableFrames { -926 /// Whether there are ACK frames to send, these are not ack-eliciting. -927 pub(super) acks: bool, -928 /// Whether there is a CONNECTION_CLOSE to send, this is not ack-eliciting. -929 pub(super) close: bool, -930 /// Whether there are any frames that must be sent on this specific space. -931 /// -932 /// A space here in the sense of a QUIC Multipath packet number space: `Initial`, -933 /// `Handshake` and all `Data(PathId)` spaces. -934 /// -935 /// These are ack-eliciting. Some frames are scheduled per path, e.g. PING, -936 /// IMMEDIATE_ACK, PATH_CHALLENGE or PATH_RESPONSE. -937 pub(super) space_specific: bool, -938 /// Whether there are any other frames to send, these are ack-eliciting. -939 pub(super) other: bool, -940} -941 -942impl SendableFrames { -943 /// Returns that no data is available for sending -944 pub(super) fn empty() -> Self { -945 Self { -946 acks: false, -947 close: false, -948 space_specific: false, -949 other: false, -950 } -951 } +791impl FromIterator<frame::ReachOut> for PendingReachOutFrames { +792 fn from_iter<T: IntoIterator<Item = frame::ReachOut>>(iter: T) -> Self { +793 let iter = iter.into_iter(); +794 let size_hint = iter.size_hint(); +795 let mut this = Self { +796 round: Default::default(), +797 frames: Vec::with_capacity(size_hint.1.unwrap_or(size_hint.0)), +798 }; +799 for frame in iter { +800 this.push(frame); +801 } +802 this +803 } +804} +805 +806/// A variant of `Retransmits` which only allocates storage when required +807#[derive(Debug, Default, Clone)] +808pub(super) struct ThinRetransmits { +809 retransmits: Option<Box<Retransmits>>, +810} +811 +812impl ThinRetransmits { +813 /// Returns `true` if no retransmits are necessary +814 pub(super) fn is_empty(&self, streams: &StreamsState) -> bool { +815 match &self.retransmits { +816 Some(retransmits) => retransmits.is_empty(streams), +817 None => true, +818 } +819 } +820 +821 /// Returns a reference to the retransmits stored in this box +822 pub(super) fn get(&self) -> Option<&Retransmits> { +823 self.retransmits.as_deref() +824 } +825 +826 /// Returns a mutable reference to the retransmits stored in this box +827 pub(super) fn get_mut(&mut self) -> Option<&mut Retransmits> { +828 self.retransmits.as_deref_mut() +829 } +830 +831 /// Returns a mutable reference to the stored retransmits +832 /// +833 /// This function will allocate a backing storage if required. +834 pub(super) fn get_or_create(&mut self) -> &mut Retransmits { +835 if self.retransmits.is_none() { +836 self.retransmits = Some(Box::default()); +837 } +838 self.retransmits.as_deref_mut().unwrap() +839 } +840} +841 +842/// RFC4303-style sliding window packet number deduplicator. +843/// +844/// A contiguous bitfield, where each bit corresponds to a packet number and the rightmost bit is +845/// always set. A set bit represents a packet that has been successfully authenticated. Bits left of +846/// the window are assumed to be set. +847/// +848/// ```text +849/// ...xxxxxxxxx 1 0 +850/// ^ ^ ^ +851/// window highest next +852/// ``` +853#[derive(Debug, Default)] +854pub(super) struct Dedup { +855 window: Window, +856 /// Lowest packet number higher than all yet authenticated. +857 next: u64, +858} +859 +860/// Inner bitfield type. +861/// +862/// Because QUIC never reuses packet numbers, this only needs to be large enough to deal with +863/// packets that are reordered but still delivered in a timely manner. +864type Window = u128; +865 +866/// Number of packets tracked by `Dedup`. +867const WINDOW_SIZE: u64 = 1 + mem::size_of::<Window>() as u64 * 8; +868 +869impl Dedup { +870 /// Construct an empty window positioned at the start. +871 #[cfg(test)] +872 pub(super) fn new() -> Self { +873 Self { window: 0, next: 0 } +874 } +875 +876 /// Highest packet number authenticated. +877 fn highest(&self) -> u64 { +878 self.next - 1 +879 } +880 +881 /// Record a newly authenticated packet number. +882 /// +883 /// Returns whether the packet might be a duplicate. +884 pub(super) fn insert(&mut self, packet: u64) -> bool { +885 if let Some(diff) = packet.checked_sub(self.next) { +886 // Right of window +887 self.window = ((self.window << 1) | 1) +888 .checked_shl(cmp::min(diff, u64::from(u32::MAX)) as u32) +889 .unwrap_or(0); +890 self.next = packet + 1; +891 false +892 } else if self.highest() - packet < WINDOW_SIZE { +893 // Within window +894 if let Some(bit) = (self.highest() - packet).checked_sub(1) { +895 // < highest +896 let mask = 1 << bit; +897 let duplicate = self.window & mask != 0; +898 self.window |= mask; +899 duplicate +900 } else { +901 // == highest +902 true +903 } +904 } else { +905 // Left of window +906 true +907 } +908 } +909 +910 /// Returns the packet number of the smallest packet missing between the provided interval +911 /// +912 /// If there are no missing packets, returns `None` +913 fn smallest_missing_in_interval(&self, lower_bound: u64, upper_bound: u64) -> Option<u64> { +914 debug_assert!(lower_bound <= upper_bound); +915 debug_assert!(upper_bound <= self.highest()); +916 const BITFIELD_SIZE: u64 = (mem::size_of::<Window>() * 8) as u64; +917 +918 // Since we already know the packets at the boundaries have been received, we only need to +919 // check those in between them (this removes the necessity of extra logic to deal with the +920 // highest packet, which is stored outside the bitfield) +921 let lower_bound = lower_bound + 1; +922 let upper_bound = upper_bound.saturating_sub(1); +923 +924 // Note: the offsets are counted from the right +925 // The highest packet is not included in the bitfield, so we subtract 1 to account for that +926 let start_offset = (self.highest() - upper_bound).max(1) - 1; +927 if start_offset >= BITFIELD_SIZE { +928 // The start offset is outside of the window. All packets outside of the window are +929 // considered to be received. +930 return None; +931 } +932 +933 let end_offset_exclusive = self.highest().saturating_sub(lower_bound); +934 +935 // The range is clamped at the edge of the window, because any earlier packets are +936 // considered to be received +937 let range_len = end_offset_exclusive +938 .saturating_sub(start_offset) +939 .min(BITFIELD_SIZE); +940 if range_len == 0 { +941 return None; +942 } +943 +944 // Ensure the shift is within bounds (we already know start_offset < BITFIELD_SIZE, +945 // because of the early return) +946 let mask = if range_len == BITFIELD_SIZE { +947 u128::MAX +948 } else { +949 ((1u128 << range_len) - 1) << start_offset +950 }; +951 let gaps = !self.window & mask; 952 -953 /// Whether an ack-eliciting packet will be sent. -954 pub(super) fn is_ack_eliciting(&self) -> bool { -955 let Self { -956 acks: _, -957 close, -958 space_specific, -959 other, -960 } = *self; -961 if close { -962 // No ack-eliciting frames are included with a CONNECTION_CLOSE, only acks. -963 return false; -964 } -965 space_specific || other -966 } -967 -968 /// Whether no data is sendable. -969 pub(super) fn is_empty(&self) -> bool { -970 let Self { -971 acks, -972 close, -973 space_specific, -974 other, -975 } = *self; -976 !acks && !close && !space_specific && !other -977 } -978} -979 -980impl ::std::ops::BitOrAssign for SendableFrames { -981 fn bitor_assign(&mut self, rhs: Self) { -982 let Self { -983 acks, -984 close, -985 space_specific, -986 other, -987 } = rhs; -988 -989 self.acks |= acks; -990 self.close |= close; -991 self.space_specific |= space_specific; -992 self.other |= other; -993 } -994} -995 -996#[derive(Debug)] -997pub(super) struct PendingAcks { -998 /// Whether we should send an ACK immediately, even if that means sending an ACK-only packet -999 /// -1000 /// When `immediate_ack_required` is false, the normal behavior is to send ACK frames only when -1001 /// there is other data to send, or when the `MaxAckDelay` timer expires. -1002 immediate_ack_required: bool, -1003 /// The number of ack-eliciting packets received since the last ACK frame was sent -1004 /// -1005 /// Once the count _exceeds_ `ack_eliciting_threshold`, an immediate ACK is required -1006 ack_eliciting_since_last_ack_sent: u64, -1007 non_ack_eliciting_since_last_ack_sent: u64, -1008 ack_eliciting_threshold: u64, -1009 /// The reordering threshold, controlling how we respond to out-of-order ack-eliciting packets -1010 /// -1011 /// Different values enable different behavior: -1012 /// -1013 /// * `0`: no special action is taken -1014 /// * `1`: an ACK is immediately sent if it is out-of-order according to RFC 9000 -1015 /// * `>1`: an ACK is immediately sent if it is out-of-order according to the ACK frequency draft -1016 reordering_threshold: u64, -1017 /// The earliest ack-eliciting packet since the last ACK was sent, used to calculate the moment -1018 /// upon which `max_ack_delay` elapses -1019 earliest_ack_eliciting_since_last_ack_sent: Option<Instant>, -1020 /// Packet number ranges for which to still send acknowledgements. -1021 /// -1022 /// These are packet number ranges of ack-eliciting packets the peer has sent and which -1023 /// need to be acknowledged. Packet numbers are only removed from here once the peer has -1024 /// acknowledged the ACKs for them. -1025 ranges: ArrayRangeSet, -1026 /// The largest packet number received and the time it was received -1027 /// -1028 /// Used to calculate ACK delay in [`PendingAcks::ack_delay`]. -1029 largest_packet: Option<(u64, Instant)>, -1030 /// The ack-eliciting packet we have received with the largest packet number -1031 largest_ack_eliciting_packet: Option<u64>, -1032 /// The largest acknowledged packet number sent in an ACK frame -1033 largest_acked: Option<u64>, -1034} -1035 -1036impl PendingAcks { -1037 fn new() -> Self { -1038 Self { -1039 immediate_ack_required: false, -1040 ack_eliciting_since_last_ack_sent: 0, -1041 non_ack_eliciting_since_last_ack_sent: 0, -1042 ack_eliciting_threshold: 1, -1043 reordering_threshold: 1, -1044 earliest_ack_eliciting_since_last_ack_sent: None, -1045 ranges: Default::default(), -1046 largest_packet: Default::default(), -1047 largest_ack_eliciting_packet: Default::default(), -1048 largest_acked: Default::default(), -1049 } -1050 } -1051 -1052 pub(super) fn set_ack_frequency_params(&mut self, frame: &frame::AckFrequency) { -1053 self.ack_eliciting_threshold = frame.ack_eliciting_threshold.into_inner(); -1054 self.reordering_threshold = frame.reordering_threshold.into_inner(); -1055 } -1056 -1057 pub(super) fn set_immediate_ack_required(&mut self) { -1058 self.immediate_ack_required = true; -1059 } -1060 -1061 pub(super) fn on_max_ack_delay_timeout(&mut self) { -1062 self.immediate_ack_required = self.ack_eliciting_since_last_ack_sent > 0; -1063 } -1064 -1065 pub(super) fn max_ack_delay_timeout(&self, max_ack_delay: Duration) -> Option<Instant> { -1066 self.earliest_ack_eliciting_since_last_ack_sent -1067 .map(|earliest_unacked| earliest_unacked + max_ack_delay) -1068 } -1069 -1070 /// Whether any ACK frames SHOULD be sent -1071 /// -1072 /// This is used in the top-level [`Connection::space_can_send`], so determines if a -1073 /// packet will be built. It is often possible to construct new ACK ranges to send -1074 /// before this returns `true`. This results in more ACK frames being sent, and -1075 /// processing those at the receiver costs CPU for very little improvements. -1076 /// -1077 /// [`Connection::space_can_send`]: super::Connection::space_can_send -1078 pub(super) fn can_send(&self) -> bool { -1079 self.immediate_ack_required && !self.ranges.is_empty() -1080 } -1081 -1082 /// Returns the delay since the packet with the largest packet number was received -1083 pub(super) fn ack_delay(&self, now: Instant) -> Duration { -1084 self.largest_packet -1085 .map_or_else(Duration::default, |(_, received)| now - received) -1086 } -1087 -1088 /// Handle receipt of a new packet -1089 /// -1090 /// Returns true if the max ack delay timer should be armed -1091 pub(super) fn packet_received( -1092 &mut self, -1093 now: Instant, -1094 packet_number: u64, -1095 ack_eliciting: bool, -1096 dedup: &Dedup, -1097 ) -> bool { -1098 if !ack_eliciting { -1099 self.non_ack_eliciting_since_last_ack_sent += 1; -1100 return false; -1101 } +953 let smallest_missing_offset = 128 - gaps.leading_zeros() as u64; +954 let smallest_missing_packet = self.highest() - smallest_missing_offset; +955 +956 if smallest_missing_packet <= upper_bound { +957 Some(smallest_missing_packet) +958 } else { +959 None +960 } +961 } +962 +963 /// Returns true if there are any missing packets between the provided interval +964 /// +965 /// The provided packet numbers must have been received before calling this function +966 fn missing_in_interval(&self, lower_bound: u64, upper_bound: u64) -> bool { +967 self.smallest_missing_in_interval(lower_bound, upper_bound) +968 .is_some() +969 } +970} +971 +972/// Indicates which data is available for sending. +973/// +974/// This applies to a particular space ID that was queried and all refers to on-path data. +975#[derive(Clone, Copy, PartialEq, Eq, Debug)] +976pub(super) struct SendableFrames { +977 /// Whether there are ACK frames to send, these are not ack-eliciting. +978 pub(super) acks: bool, +979 /// Whether there is a CONNECTION_CLOSE to send, this is not ack-eliciting. +980 pub(super) close: bool, +981 /// Whether there are any frames that must be sent on this specific space. +982 /// +983 /// A space here in the sense of a QUIC Multipath packet number space: `Initial`, +984 /// `Handshake` and all `Data(PathId)` spaces. +985 /// +986 /// These are ack-eliciting. Some frames are scheduled per path, e.g. PING, +987 /// IMMEDIATE_ACK, PATH_CHALLENGE or PATH_RESPONSE. +988 pub(super) space_specific: bool, +989 /// Whether there are any other frames to send, these are ack-eliciting. +990 pub(super) other: bool, +991} +992 +993impl SendableFrames { +994 /// Returns that no data is available for sending +995 pub(super) fn empty() -> Self { +996 Self { +997 acks: false, +998 close: false, +999 space_specific: false, +1000 other: false, +1001 } +1002 } +1003 +1004 /// Whether an ack-eliciting packet will be sent. +1005 pub(super) fn is_ack_eliciting(&self) -> bool { +1006 let Self { +1007 acks: _, +1008 close, +1009 space_specific, +1010 other, +1011 } = *self; +1012 if close { +1013 // No ack-eliciting frames are included with a CONNECTION_CLOSE, only acks. +1014 return false; +1015 } +1016 space_specific || other +1017 } +1018 +1019 /// Whether no data is sendable. +1020 pub(super) fn is_empty(&self) -> bool { +1021 let Self { +1022 acks, +1023 close, +1024 space_specific, +1025 other, +1026 } = *self; +1027 !acks && !close && !space_specific && !other +1028 } +1029} +1030 +1031impl ::std::ops::BitOrAssign for SendableFrames { +1032 fn bitor_assign(&mut self, rhs: Self) { +1033 let Self { +1034 acks, +1035 close, +1036 space_specific, +1037 other, +1038 } = rhs; +1039 +1040 self.acks |= acks; +1041 self.close |= close; +1042 self.space_specific |= space_specific; +1043 self.other |= other; +1044 } +1045} +1046 +1047#[derive(Debug)] +1048pub(super) struct PendingAcks { +1049 /// Whether we should send an ACK immediately, even if that means sending an ACK-only packet +1050 /// +1051 /// When `immediate_ack_required` is false, the normal behavior is to send ACK frames only when +1052 /// there is other data to send, or when the `MaxAckDelay` timer expires. +1053 immediate_ack_required: bool, +1054 /// The number of ack-eliciting packets received since the last ACK frame was sent +1055 /// +1056 /// Once the count _exceeds_ `ack_eliciting_threshold`, an immediate ACK is required +1057 ack_eliciting_since_last_ack_sent: u64, +1058 non_ack_eliciting_since_last_ack_sent: u64, +1059 ack_eliciting_threshold: u64, +1060 /// The reordering threshold, controlling how we respond to out-of-order ack-eliciting packets +1061 /// +1062 /// Different values enable different behavior: +1063 /// +1064 /// * `0`: no special action is taken +1065 /// * `1`: an ACK is immediately sent if it is out-of-order according to RFC 9000 +1066 /// * `>1`: an ACK is immediately sent if it is out-of-order according to the ACK frequency draft +1067 reordering_threshold: u64, +1068 /// The earliest ack-eliciting packet since the last ACK was sent, used to calculate the moment +1069 /// upon which `max_ack_delay` elapses +1070 earliest_ack_eliciting_since_last_ack_sent: Option<Instant>, +1071 /// Packet number ranges for which to still send acknowledgements. +1072 /// +1073 /// These are packet number ranges of ack-eliciting packets the peer has sent and which +1074 /// need to be acknowledged. Packet numbers are only removed from here once the peer has +1075 /// acknowledged the ACKs for them. +1076 ranges: ArrayRangeSet, +1077 /// The largest packet number received and the time it was received +1078 /// +1079 /// Used to calculate ACK delay in [`PendingAcks::ack_delay`]. +1080 largest_packet: Option<(u64, Instant)>, +1081 /// The ack-eliciting packet we have received with the largest packet number +1082 largest_ack_eliciting_packet: Option<u64>, +1083 /// The largest acknowledged packet number sent in an ACK frame +1084 largest_acked: Option<u64>, +1085} +1086 +1087impl PendingAcks { +1088 fn new() -> Self { +1089 Self { +1090 immediate_ack_required: false, +1091 ack_eliciting_since_last_ack_sent: 0, +1092 non_ack_eliciting_since_last_ack_sent: 0, +1093 ack_eliciting_threshold: 1, +1094 reordering_threshold: 1, +1095 earliest_ack_eliciting_since_last_ack_sent: None, +1096 ranges: Default::default(), +1097 largest_packet: Default::default(), +1098 largest_ack_eliciting_packet: Default::default(), +1099 largest_acked: Default::default(), +1100 } +1101 } 1102 -1103 let prev_largest_ack_eliciting = self.largest_ack_eliciting_packet.unwrap_or(0); -1104 -1105 // Track largest ack-eliciting packet -1106 self.largest_ack_eliciting_packet = self -1107 .largest_ack_eliciting_packet -1108 .map(|pn| pn.max(packet_number)) -1109 .or(Some(packet_number)); -1110 -1111 // Handle ack_eliciting_threshold -1112 self.ack_eliciting_since_last_ack_sent += 1; -1113 self.immediate_ack_required |= -1114 self.ack_eliciting_since_last_ack_sent > self.ack_eliciting_threshold; +1103 pub(super) fn set_ack_frequency_params(&mut self, frame: &frame::AckFrequency) { +1104 self.ack_eliciting_threshold = frame.ack_eliciting_threshold.into_inner(); +1105 self.reordering_threshold = frame.reordering_threshold.into_inner(); +1106 } +1107 +1108 pub(super) fn set_immediate_ack_required(&mut self) { +1109 self.immediate_ack_required = true; +1110 } +1111 +1112 pub(super) fn on_max_ack_delay_timeout(&mut self) { +1113 self.immediate_ack_required = self.ack_eliciting_since_last_ack_sent > 0; +1114 } 1115 -1116 // Handle out-of-order packets -1117 self.immediate_ack_required |= -1118 self.is_out_of_order(packet_number, prev_largest_ack_eliciting, dedup); -1119 -1120 // Arm max_ack_delay timer if necessary -1121 if self.earliest_ack_eliciting_since_last_ack_sent.is_none() && !self.can_send() { -1122 self.earliest_ack_eliciting_since_last_ack_sent = Some(now); -1123 return true; -1124 } -1125 -1126 false -1127 } -1128 -1129 fn is_out_of_order( -1130 &self, -1131 packet_number: u64, -1132 prev_largest_ack_eliciting: u64, -1133 dedup: &Dedup, -1134 ) -> bool { -1135 match self.reordering_threshold { -1136 0 => false, -1137 1 => { -1138 // From https://www.rfc-editor.org/rfc/rfc9000#section-13.2.1-7 -1139 packet_number < prev_largest_ack_eliciting -1140 || dedup.missing_in_interval(prev_largest_ack_eliciting, packet_number) -1141 } -1142 _ => { -1143 // From acknowledgement frequency draft, section 6.1: send an ACK immediately if -1144 // doing so would cause the sender to detect a new packet loss -1145 let Some((largest_acked, largest_unacked)) = -1146 self.largest_acked.zip(self.largest_ack_eliciting_packet) -1147 else { -1148 return false; -1149 }; -1150 if self.reordering_threshold > largest_acked { -1151 return false; -1152 } -1153 // The largest packet number that could be declared lost without a new ACK being -1154 // sent -1155 let largest_reported = largest_acked - self.reordering_threshold + 1; -1156 let Some(smallest_missing_unreported) = -1157 dedup.smallest_missing_in_interval(largest_reported, largest_unacked) -1158 else { -1159 return false; -1160 }; -1161 largest_unacked - smallest_missing_unreported >= self.reordering_threshold -1162 } -1163 } -1164 } -1165 -1166 /// Should be called whenever ACKs have been sent -1167 /// -1168 /// This will suppress sending further ACKs until additional ACK eliciting frames arrive -1169 pub(super) fn acks_sent(&mut self) { -1170 // It is possible (though unlikely) that the ACKs we just sent do not cover all the -1171 // ACK-eliciting packets we have received (e.g. if there is not enough room in the packet to -1172 // fit all the ranges). To keep things simple, however, we assume they do. If there are -1173 // indeed some ACKs that weren't covered, the packets might be ACKed later anyway, because -1174 // they are still contained in `self.ranges`. If we somehow fail to send the ACKs at a later -1175 // moment, the peer will assume the packets got lost and will retransmit their frames in a -1176 // new packet, which is suboptimal, because we already received them. Our assumption here is -1177 // that simplicity results in code that is more performant, even in the presence of -1178 // occasional redundant retransmits. -1179 self.immediate_ack_required = false; -1180 self.ack_eliciting_since_last_ack_sent = 0; -1181 self.non_ack_eliciting_since_last_ack_sent = 0; -1182 self.earliest_ack_eliciting_since_last_ack_sent = None; -1183 self.largest_acked = self.largest_ack_eliciting_packet; -1184 } -1185 -1186 /// Insert one packet that needs to be acknowledged -1187 pub(super) fn insert_one(&mut self, packet: u64, now: Instant) { -1188 self.ranges.insert_one(packet); -1189 -1190 if self.largest_packet.is_none_or(|(pn, _)| packet > pn) { -1191 self.largest_packet = Some((packet, now)); -1192 } -1193 -1194 if self.ranges.len() > MAX_ACK_BLOCKS { -1195 self.ranges.pop_min(); -1196 } -1197 } -1198 -1199 /// Remove ACKs of packets numbered at or below `max` from the set of pending ACKs -1200 pub(super) fn subtract_below(&mut self, max: u64) { -1201 self.ranges.remove(0..(max + 1)); -1202 } -1203 -1204 /// Returns the set of currently pending ACK ranges -1205 pub(super) fn ranges(&self) -> &ArrayRangeSet { -1206 &self.ranges -1207 } -1208 -1209 /// Queue an ACK if a significant number of non-ACK-eliciting packets have not yet been -1210 /// acknowledged -1211 /// -1212 /// Should be called immediately before a non-probing packet is composed, when we've already -1213 /// committed to sending a packet regardless. -1214 pub(super) fn maybe_ack_non_eliciting(&mut self) { -1215 // If we're going to send a packet anyway, and we've received a significant number of -1216 // non-ACK-eliciting packets, then include an ACK to help the peer perform timely loss -1217 // detection even if they're not sending any ACK-eliciting packets themselves. Exact -1218 // threshold chosen somewhat arbitrarily. -1219 const LAZY_ACK_THRESHOLD: u64 = 10; -1220 if self.non_ack_eliciting_since_last_ack_sent > LAZY_ACK_THRESHOLD { -1221 self.immediate_ack_required = true; -1222 } -1223 } -1224} -1225 -1226/// Helper for mitigating [optimistic ACK attacks] -1227/// -1228/// A malicious peer could prompt the local application to begin a large data transfer, and then -1229/// send ACKs without first waiting for data to be received. This could defeat congestion control, -1230/// allowing the connection to consume disproportionate resources. We therefore occasionally skip -1231/// packet numbers, and classify any ACK referencing a skipped packet number as a transport error. -1232/// -1233/// Skipped packet numbers occur only in the application data space (where costly transfers might -1234/// take place) and are distributed exponentially to reflect the reduced likelihood and impact of -1235/// bad behavior from a peer that has been well-behaved for an extended period. -1236/// -1237/// ACKs for packet numbers that have not yet been allocated are also a transport error, but an -1238/// attacker with knowledge of the congestion control algorithm in use could time falsified ACKs to -1239/// arrive after the packets they reference are sent. -1240/// -1241/// [optimistic ACK attacks]: https://www.rfc-editor.org/rfc/rfc9000.html#name-optimistic-ack-attack -1242pub(super) struct PacketNumberFilter { -1243 /// Next outgoing packet number to skip -1244 next_skipped_packet_number: u64, -1245 /// Most recently skipped packet number -1246 prev_skipped_packet_number: Option<u64>, -1247 /// Next packet number to skip is randomly selected from 2^n..2^n+1 -1248 exponent: u32, -1249} -1250 -1251impl PacketNumberFilter { -1252 pub(super) fn new(rng: &mut (impl CryptoRng + ?Sized)) -> Self { -1253 // First skipped PN is in 0..64 -1254 let exponent = 6; -1255 Self { -1256 next_skipped_packet_number: rng.random_range(0..2u64.saturating_pow(exponent)), -1257 prev_skipped_packet_number: None, -1258 exponent, -1259 } -1260 } -1261 -1262 #[cfg(test)] -1263 pub(super) fn disabled() -> Self { -1264 Self { -1265 next_skipped_packet_number: u64::MAX, -1266 prev_skipped_packet_number: None, -1267 exponent: u32::MAX, -1268 } -1269 } -1270 -1271 /// Whether to use the provided packet number (false) or to skip it (true) -1272 pub(super) fn skip_pn(&mut self, n: u64, rng: &mut (impl CryptoRng + ?Sized)) -> bool { -1273 if n != self.next_skipped_packet_number { -1274 return false; -1275 } +1116 pub(super) fn max_ack_delay_timeout(&self, max_ack_delay: Duration) -> Option<Instant> { +1117 self.earliest_ack_eliciting_since_last_ack_sent +1118 .map(|earliest_unacked| earliest_unacked + max_ack_delay) +1119 } +1120 +1121 /// Whether any ACK frames SHOULD be sent +1122 /// +1123 /// This is used in the top-level [`Connection::space_can_send`], so determines if a +1124 /// packet will be built. It is often possible to construct new ACK ranges to send +1125 /// before this returns `true`. This results in more ACK frames being sent, and +1126 /// processing those at the receiver costs CPU for very little improvements. +1127 /// +1128 /// [`Connection::space_can_send`]: super::Connection::space_can_send +1129 pub(super) fn can_send(&self) -> bool { +1130 self.immediate_ack_required && !self.ranges.is_empty() +1131 } +1132 +1133 /// Returns the delay since the packet with the largest packet number was received +1134 pub(super) fn ack_delay(&self, now: Instant) -> Duration { +1135 self.largest_packet +1136 .map_or_else(Duration::default, |(_, received)| now - received) +1137 } +1138 +1139 /// Handle receipt of a new packet +1140 /// +1141 /// Returns true if the max ack delay timer should be armed +1142 pub(super) fn packet_received( +1143 &mut self, +1144 now: Instant, +1145 packet_number: u64, +1146 ack_eliciting: bool, +1147 dedup: &Dedup, +1148 ) -> bool { +1149 if !ack_eliciting { +1150 self.non_ack_eliciting_since_last_ack_sent += 1; +1151 return false; +1152 } +1153 +1154 let prev_largest_ack_eliciting = self.largest_ack_eliciting_packet.unwrap_or(0); +1155 +1156 // Track largest ack-eliciting packet +1157 self.largest_ack_eliciting_packet = self +1158 .largest_ack_eliciting_packet +1159 .map(|pn| pn.max(packet_number)) +1160 .or(Some(packet_number)); +1161 +1162 // Handle ack_eliciting_threshold +1163 self.ack_eliciting_since_last_ack_sent += 1; +1164 self.immediate_ack_required |= +1165 self.ack_eliciting_since_last_ack_sent > self.ack_eliciting_threshold; +1166 +1167 // Handle out-of-order packets +1168 self.immediate_ack_required |= +1169 self.is_out_of_order(packet_number, prev_largest_ack_eliciting, dedup); +1170 +1171 // Arm max_ack_delay timer if necessary +1172 if self.earliest_ack_eliciting_since_last_ack_sent.is_none() && !self.can_send() { +1173 self.earliest_ack_eliciting_since_last_ack_sent = Some(now); +1174 return true; +1175 } +1176 +1177 false +1178 } +1179 +1180 fn is_out_of_order( +1181 &self, +1182 packet_number: u64, +1183 prev_largest_ack_eliciting: u64, +1184 dedup: &Dedup, +1185 ) -> bool { +1186 match self.reordering_threshold { +1187 0 => false, +1188 1 => { +1189 // From https://www.rfc-editor.org/rfc/rfc9000#section-13.2.1-7 +1190 packet_number < prev_largest_ack_eliciting +1191 || dedup.missing_in_interval(prev_largest_ack_eliciting, packet_number) +1192 } +1193 _ => { +1194 // From acknowledgement frequency draft, section 6.1: send an ACK immediately if +1195 // doing so would cause the sender to detect a new packet loss +1196 let Some((largest_acked, largest_unacked)) = +1197 self.largest_acked.zip(self.largest_ack_eliciting_packet) +1198 else { +1199 return false; +1200 }; +1201 if self.reordering_threshold > largest_acked { +1202 return false; +1203 } +1204 // The largest packet number that could be declared lost without a new ACK being +1205 // sent +1206 let largest_reported = largest_acked - self.reordering_threshold + 1; +1207 let Some(smallest_missing_unreported) = +1208 dedup.smallest_missing_in_interval(largest_reported, largest_unacked) +1209 else { +1210 return false; +1211 }; +1212 largest_unacked - smallest_missing_unreported >= self.reordering_threshold +1213 } +1214 } +1215 } +1216 +1217 /// Should be called whenever ACKs have been sent +1218 /// +1219 /// This will suppress sending further ACKs until additional ACK eliciting frames arrive +1220 pub(super) fn acks_sent(&mut self) { +1221 // It is possible (though unlikely) that the ACKs we just sent do not cover all the +1222 // ACK-eliciting packets we have received (e.g. if there is not enough room in the packet to +1223 // fit all the ranges). To keep things simple, however, we assume they do. If there are +1224 // indeed some ACKs that weren't covered, the packets might be ACKed later anyway, because +1225 // they are still contained in `self.ranges`. If we somehow fail to send the ACKs at a later +1226 // moment, the peer will assume the packets got lost and will retransmit their frames in a +1227 // new packet, which is suboptimal, because we already received them. Our assumption here is +1228 // that simplicity results in code that is more performant, even in the presence of +1229 // occasional redundant retransmits. +1230 self.immediate_ack_required = false; +1231 self.ack_eliciting_since_last_ack_sent = 0; +1232 self.non_ack_eliciting_since_last_ack_sent = 0; +1233 self.earliest_ack_eliciting_since_last_ack_sent = None; +1234 self.largest_acked = self.largest_ack_eliciting_packet; +1235 } +1236 +1237 /// Insert one packet that needs to be acknowledged +1238 pub(super) fn insert_one(&mut self, packet: u64, now: Instant) { +1239 self.ranges.insert_one(packet); +1240 +1241 if self.largest_packet.is_none_or(|(pn, _)| packet > pn) { +1242 self.largest_packet = Some((packet, now)); +1243 } +1244 +1245 if self.ranges.len() > MAX_ACK_BLOCKS { +1246 self.ranges.pop_min(); +1247 } +1248 } +1249 +1250 /// Remove ACKs of packets numbered at or below `max` from the set of pending ACKs +1251 pub(super) fn subtract_below(&mut self, max: u64) { +1252 self.ranges.remove(0..(max + 1)); +1253 } +1254 +1255 /// Returns the set of currently pending ACK ranges +1256 pub(super) fn ranges(&self) -> &ArrayRangeSet { +1257 &self.ranges +1258 } +1259 +1260 /// Queue an ACK if a significant number of non-ACK-eliciting packets have not yet been +1261 /// acknowledged +1262 /// +1263 /// Should be called immediately before a non-probing packet is composed, when we've already +1264 /// committed to sending a packet regardless. +1265 pub(super) fn maybe_ack_non_eliciting(&mut self) { +1266 // If we're going to send a packet anyway, and we've received a significant number of +1267 // non-ACK-eliciting packets, then include an ACK to help the peer perform timely loss +1268 // detection even if they're not sending any ACK-eliciting packets themselves. Exact +1269 // threshold chosen somewhat arbitrarily. +1270 const LAZY_ACK_THRESHOLD: u64 = 10; +1271 if self.non_ack_eliciting_since_last_ack_sent > LAZY_ACK_THRESHOLD { +1272 self.immediate_ack_required = true; +1273 } +1274 } +1275} 1276 -1277 trace!("skipping pn {n}"); -1278 // Skip this packet number, and choose the next one to skip -1279 self.prev_skipped_packet_number = Some(self.next_skipped_packet_number); -1280 let next_exponent = self.exponent.saturating_add(1); -1281 self.next_skipped_packet_number = rng -1282 .random_range(2u64.saturating_pow(self.exponent)..2u64.saturating_pow(next_exponent)); -1283 self.exponent = next_exponent; -1284 true -1285 } -1286} -1287 -1288/// Ensures we can always fit all our ACKs in a single minimum-MTU packet with room to spare -1289const MAX_ACK_BLOCKS: usize = 64; -1290 -1291#[cfg(test)] -1292mod test { -1293 use rand::Rng; -1294 use rand::seq::SliceRandom; -1295 -1296 use crate::token::ResetToken; -1297 use crate::{ConnectionIdGenerator, RandomConnectionIdGenerator}; -1298 -1299 use super::*; -1300 -1301 #[test] -1302 fn sanity() { -1303 let mut dedup = Dedup::new(); -1304 assert!(!dedup.insert(0)); -1305 assert_eq!(dedup.next, 1); -1306 assert_eq!(dedup.window, 0b1); -1307 assert!(dedup.insert(0)); -1308 assert_eq!(dedup.next, 1); -1309 assert_eq!(dedup.window, 0b1); -1310 assert!(!dedup.insert(1)); -1311 assert_eq!(dedup.next, 2); -1312 assert_eq!(dedup.window, 0b11); -1313 assert!(!dedup.insert(2)); -1314 assert_eq!(dedup.next, 3); -1315 assert_eq!(dedup.window, 0b111); -1316 assert!(!dedup.insert(4)); -1317 assert_eq!(dedup.next, 5); -1318 assert_eq!(dedup.window, 0b11110); -1319 assert!(!dedup.insert(7)); -1320 assert_eq!(dedup.next, 8); -1321 assert_eq!(dedup.window, 0b1111_0100); -1322 assert!(dedup.insert(4)); -1323 assert!(!dedup.insert(3)); -1324 assert_eq!(dedup.next, 8); -1325 assert_eq!(dedup.window, 0b1111_1100); -1326 assert!(!dedup.insert(6)); -1327 assert_eq!(dedup.next, 8); -1328 assert_eq!(dedup.window, 0b1111_1101); -1329 assert!(!dedup.insert(5)); -1330 assert_eq!(dedup.next, 8); -1331 assert_eq!(dedup.window, 0b1111_1111); -1332 } -1333 -1334 #[test] -1335 fn happypath() { -1336 let mut dedup = Dedup::new(); -1337 for i in 0..(2 * WINDOW_SIZE) { -1338 assert!(!dedup.insert(i)); -1339 for j in 0..=i { -1340 assert!(dedup.insert(j)); -1341 } -1342 } -1343 } -1344 -1345 #[test] -1346 fn jump() { -1347 let mut dedup = Dedup::new(); -1348 dedup.insert(2 * WINDOW_SIZE); -1349 assert!(dedup.insert(WINDOW_SIZE)); -1350 assert_eq!(dedup.next, 2 * WINDOW_SIZE + 1); -1351 assert_eq!(dedup.window, 0); -1352 assert!(!dedup.insert(WINDOW_SIZE + 1)); -1353 assert_eq!(dedup.next, 2 * WINDOW_SIZE + 1); -1354 assert_eq!(dedup.window, 1 << (WINDOW_SIZE - 2)); -1355 } -1356 -1357 #[test] -1358 fn dedup_has_missing() { -1359 let mut dedup = Dedup::new(); -1360 -1361 dedup.insert(0); -1362 assert!(!dedup.missing_in_interval(0, 0)); -1363 -1364 dedup.insert(1); -1365 assert!(!dedup.missing_in_interval(0, 1)); -1366 -1367 dedup.insert(3); -1368 assert!(dedup.missing_in_interval(1, 3)); -1369 -1370 dedup.insert(4); -1371 assert!(!dedup.missing_in_interval(3, 4)); -1372 assert!(dedup.missing_in_interval(0, 4)); -1373 -1374 dedup.insert(2); -1375 assert!(!dedup.missing_in_interval(0, 4)); -1376 } -1377 -1378 #[test] -1379 fn dedup_outside_of_window_has_missing() { -1380 let mut dedup = Dedup::new(); -1381 -1382 for i in 0..140 { -1383 dedup.insert(i); -1384 } -1385 -1386 // 0 and 4 are outside of the window -1387 assert!(!dedup.missing_in_interval(0, 4)); -1388 dedup.insert(160); -1389 assert!(!dedup.missing_in_interval(0, 4)); -1390 assert!(!dedup.missing_in_interval(0, 140)); -1391 assert!(dedup.missing_in_interval(0, 160)); -1392 } -1393 -1394 #[test] -1395 fn dedup_smallest_missing() { -1396 let mut dedup = Dedup::new(); -1397 -1398 dedup.insert(0); -1399 assert_eq!(dedup.smallest_missing_in_interval(0, 0), None); -1400 -1401 dedup.insert(1); -1402 assert_eq!(dedup.smallest_missing_in_interval(0, 1), None); -1403 -1404 dedup.insert(5); -1405 dedup.insert(7); -1406 assert_eq!(dedup.smallest_missing_in_interval(0, 7), Some(2)); -1407 assert_eq!(dedup.smallest_missing_in_interval(5, 7), Some(6)); -1408 -1409 dedup.insert(2); -1410 assert_eq!(dedup.smallest_missing_in_interval(1, 7), Some(3)); +1277/// Helper for mitigating [optimistic ACK attacks] +1278/// +1279/// A malicious peer could prompt the local application to begin a large data transfer, and then +1280/// send ACKs without first waiting for data to be received. This could defeat congestion control, +1281/// allowing the connection to consume disproportionate resources. We therefore occasionally skip +1282/// packet numbers, and classify any ACK referencing a skipped packet number as a transport error. +1283/// +1284/// Skipped packet numbers occur only in the application data space (where costly transfers might +1285/// take place) and are distributed exponentially to reflect the reduced likelihood and impact of +1286/// bad behavior from a peer that has been well-behaved for an extended period. +1287/// +1288/// ACKs for packet numbers that have not yet been allocated are also a transport error, but an +1289/// attacker with knowledge of the congestion control algorithm in use could time falsified ACKs to +1290/// arrive after the packets they reference are sent. +1291/// +1292/// [optimistic ACK attacks]: https://www.rfc-editor.org/rfc/rfc9000.html#name-optimistic-ack-attack +1293pub(super) struct PacketNumberFilter { +1294 /// Next outgoing packet number to skip +1295 next_skipped_packet_number: u64, +1296 /// Most recently skipped packet number +1297 prev_skipped_packet_number: Option<u64>, +1298 /// Next packet number to skip is randomly selected from 2^n..2^n+1 +1299 exponent: u32, +1300} +1301 +1302impl PacketNumberFilter { +1303 pub(super) fn new(rng: &mut (impl CryptoRng + ?Sized)) -> Self { +1304 // First skipped PN is in 0..64 +1305 let exponent = 6; +1306 Self { +1307 next_skipped_packet_number: rng.random_range(0..2u64.saturating_pow(exponent)), +1308 prev_skipped_packet_number: None, +1309 exponent, +1310 } +1311 } +1312 +1313 #[cfg(test)] +1314 pub(super) fn disabled() -> Self { +1315 Self { +1316 next_skipped_packet_number: u64::MAX, +1317 prev_skipped_packet_number: None, +1318 exponent: u32::MAX, +1319 } +1320 } +1321 +1322 /// Whether to use the provided packet number (false) or to skip it (true) +1323 pub(super) fn skip_pn(&mut self, n: u64, rng: &mut (impl CryptoRng + ?Sized)) -> bool { +1324 if n != self.next_skipped_packet_number { +1325 return false; +1326 } +1327 +1328 trace!("skipping pn {n}"); +1329 // Skip this packet number, and choose the next one to skip +1330 self.prev_skipped_packet_number = Some(self.next_skipped_packet_number); +1331 let next_exponent = self.exponent.saturating_add(1); +1332 self.next_skipped_packet_number = rng +1333 .random_range(2u64.saturating_pow(self.exponent)..2u64.saturating_pow(next_exponent)); +1334 self.exponent = next_exponent; +1335 true +1336 } +1337} +1338 +1339/// Ensures we can always fit all our ACKs in a single minimum-MTU packet with room to spare +1340const MAX_ACK_BLOCKS: usize = 64; +1341 +1342#[cfg(test)] +1343mod test { +1344 use rand::Rng; +1345 use rand::seq::SliceRandom; +1346 +1347 use crate::token::ResetToken; +1348 use crate::{ConnectionIdGenerator, RandomConnectionIdGenerator}; +1349 +1350 use super::*; +1351 +1352 #[test] +1353 fn sanity() { +1354 let mut dedup = Dedup::new(); +1355 assert!(!dedup.insert(0)); +1356 assert_eq!(dedup.next, 1); +1357 assert_eq!(dedup.window, 0b1); +1358 assert!(dedup.insert(0)); +1359 assert_eq!(dedup.next, 1); +1360 assert_eq!(dedup.window, 0b1); +1361 assert!(!dedup.insert(1)); +1362 assert_eq!(dedup.next, 2); +1363 assert_eq!(dedup.window, 0b11); +1364 assert!(!dedup.insert(2)); +1365 assert_eq!(dedup.next, 3); +1366 assert_eq!(dedup.window, 0b111); +1367 assert!(!dedup.insert(4)); +1368 assert_eq!(dedup.next, 5); +1369 assert_eq!(dedup.window, 0b11110); +1370 assert!(!dedup.insert(7)); +1371 assert_eq!(dedup.next, 8); +1372 assert_eq!(dedup.window, 0b1111_0100); +1373 assert!(dedup.insert(4)); +1374 assert!(!dedup.insert(3)); +1375 assert_eq!(dedup.next, 8); +1376 assert_eq!(dedup.window, 0b1111_1100); +1377 assert!(!dedup.insert(6)); +1378 assert_eq!(dedup.next, 8); +1379 assert_eq!(dedup.window, 0b1111_1101); +1380 assert!(!dedup.insert(5)); +1381 assert_eq!(dedup.next, 8); +1382 assert_eq!(dedup.window, 0b1111_1111); +1383 } +1384 +1385 #[test] +1386 fn happypath() { +1387 let mut dedup = Dedup::new(); +1388 for i in 0..(2 * WINDOW_SIZE) { +1389 assert!(!dedup.insert(i)); +1390 for j in 0..=i { +1391 assert!(dedup.insert(j)); +1392 } +1393 } +1394 } +1395 +1396 #[test] +1397 fn jump() { +1398 let mut dedup = Dedup::new(); +1399 dedup.insert(2 * WINDOW_SIZE); +1400 assert!(dedup.insert(WINDOW_SIZE)); +1401 assert_eq!(dedup.next, 2 * WINDOW_SIZE + 1); +1402 assert_eq!(dedup.window, 0); +1403 assert!(!dedup.insert(WINDOW_SIZE + 1)); +1404 assert_eq!(dedup.next, 2 * WINDOW_SIZE + 1); +1405 assert_eq!(dedup.window, 1 << (WINDOW_SIZE - 2)); +1406 } +1407 +1408 #[test] +1409 fn dedup_has_missing() { +1410 let mut dedup = Dedup::new(); 1411 -1412 dedup.insert(170); -1413 dedup.insert(172); -1414 dedup.insert(300); -1415 assert_eq!(dedup.smallest_missing_in_interval(170, 172), None); -1416 -1417 dedup.insert(500); -1418 assert_eq!(dedup.smallest_missing_in_interval(0, 500), Some(372)); -1419 assert_eq!(dedup.smallest_missing_in_interval(0, 373), Some(372)); -1420 assert_eq!(dedup.smallest_missing_in_interval(0, 372), None); -1421 } -1422 -1423 #[test] -1424 fn pending_acks_first_packet_is_not_considered_reordered() { -1425 let mut acks = PendingAcks::new(); -1426 let mut dedup = Dedup::new(); -1427 dedup.insert(0); -1428 acks.packet_received(Instant::now(), 0, true, &dedup); -1429 assert!(!acks.immediate_ack_required); -1430 } -1431 -1432 #[test] -1433 fn pending_acks_after_immediate_ack_set() { -1434 let mut acks = PendingAcks::new(); -1435 let mut dedup = Dedup::new(); +1412 dedup.insert(0); +1413 assert!(!dedup.missing_in_interval(0, 0)); +1414 +1415 dedup.insert(1); +1416 assert!(!dedup.missing_in_interval(0, 1)); +1417 +1418 dedup.insert(3); +1419 assert!(dedup.missing_in_interval(1, 3)); +1420 +1421 dedup.insert(4); +1422 assert!(!dedup.missing_in_interval(3, 4)); +1423 assert!(dedup.missing_in_interval(0, 4)); +1424 +1425 dedup.insert(2); +1426 assert!(!dedup.missing_in_interval(0, 4)); +1427 } +1428 +1429 #[test] +1430 fn dedup_outside_of_window_has_missing() { +1431 let mut dedup = Dedup::new(); +1432 +1433 for i in 0..140 { +1434 dedup.insert(i); +1435 } 1436 -1437 // Receive ack-eliciting packet -1438 dedup.insert(0); -1439 let now = Instant::now(); -1440 acks.insert_one(0, now); -1441 acks.packet_received(now, 0, true, &dedup); -1442 -1443 // Sanity check -1444 assert!(!acks.ranges.is_empty()); -1445 assert!(!acks.can_send()); -1446 -1447 // Can send ACK after max_ack_delay exceeded -1448 acks.set_immediate_ack_required(); -1449 assert!(acks.can_send()); -1450 } +1437 // 0 and 4 are outside of the window +1438 assert!(!dedup.missing_in_interval(0, 4)); +1439 dedup.insert(160); +1440 assert!(!dedup.missing_in_interval(0, 4)); +1441 assert!(!dedup.missing_in_interval(0, 140)); +1442 assert!(dedup.missing_in_interval(0, 160)); +1443 } +1444 +1445 #[test] +1446 fn dedup_smallest_missing() { +1447 let mut dedup = Dedup::new(); +1448 +1449 dedup.insert(0); +1450 assert_eq!(dedup.smallest_missing_in_interval(0, 0), None); 1451 -1452 #[test] -1453 fn pending_acks_ack_delay() { -1454 let mut acks = PendingAcks::new(); -1455 let mut dedup = Dedup::new(); -1456 -1457 let t1 = Instant::now(); -1458 let t2 = t1 + Duration::from_millis(2); -1459 let t3 = t2 + Duration::from_millis(5); -1460 assert_eq!(acks.ack_delay(t1), Duration::from_millis(0)); -1461 assert_eq!(acks.ack_delay(t2), Duration::from_millis(0)); -1462 assert_eq!(acks.ack_delay(t3), Duration::from_millis(0)); -1463 -1464 // In-order packet -1465 dedup.insert(0); -1466 acks.insert_one(0, t1); -1467 acks.packet_received(t1, 0, true, &dedup); -1468 assert_eq!(acks.ack_delay(t1), Duration::from_millis(0)); -1469 assert_eq!(acks.ack_delay(t2), Duration::from_millis(2)); -1470 assert_eq!(acks.ack_delay(t3), Duration::from_millis(7)); -1471 -1472 // Out of order (higher than expected) -1473 dedup.insert(3); -1474 acks.insert_one(3, t2); -1475 acks.packet_received(t2, 3, true, &dedup); -1476 assert_eq!(acks.ack_delay(t2), Duration::from_millis(0)); -1477 assert_eq!(acks.ack_delay(t3), Duration::from_millis(5)); -1478 -1479 // Out of order (lower than expected, so previous instant is kept) -1480 dedup.insert(2); -1481 acks.insert_one(2, t3); -1482 acks.packet_received(t3, 2, true, &dedup); -1483 assert_eq!(acks.ack_delay(t3), Duration::from_millis(5)); -1484 } -1485 -1486 #[test] -1487 fn sent_packet_size() { -1488 // The tracking state of sent packets should be minimal, and not grow -1489 // over time. -1490 assert!(std::mem::size_of::<SentPacket>() <= 128); -1491 } -1492 -1493 #[test] -1494 fn pending_new_cids() { -1495 #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] -1496 use aws_lc_rs::hmac; -1497 #[cfg(feature = "ring")] -1498 use ring::hmac; -1499 -1500 let mut cid_generator = RandomConnectionIdGenerator::new(8); -1501 let mut reset_key = [0; 64]; -1502 rand::rng().fill_bytes(&mut reset_key); -1503 let hmac = hmac::Key::new(hmac::HMAC_SHA256, &reset_key); -1504 -1505 let cid_a = cid_generator.generate_cid(); -1506 let a = IssuedCid { -1507 path_id: PathId::ZERO, -1508 sequence: 1, -1509 id: cid_a, -1510 reset_token: ResetToken::new(&hmac, cid_a), -1511 }; -1512 let cid_b = cid_generator.generate_cid(); -1513 let b = IssuedCid { -1514 path_id: PathId::ZERO, -1515 sequence: 2, -1516 id: cid_b, -1517 reset_token: ResetToken::new(&hmac, cid_b), -1518 }; -1519 let cid_c = cid_generator.generate_cid(); -1520 let c = IssuedCid { -1521 path_id: PathId(1), -1522 sequence: 1, -1523 id: cid_c, -1524 reset_token: ResetToken::new(&hmac, cid_c), -1525 }; -1526 -1527 let mut pending_cids = PendingNewCids::default(); -1528 -1529 for _ in 0..9 { -1530 // Push CIDs in a random order -1531 let mut input = vec![a, b, c]; -1532 input.shuffle(&mut rand::rng()); -1533 for cid in input { -1534 pending_cids.push(cid); -1535 } +1452 dedup.insert(1); +1453 assert_eq!(dedup.smallest_missing_in_interval(0, 1), None); +1454 +1455 dedup.insert(5); +1456 dedup.insert(7); +1457 assert_eq!(dedup.smallest_missing_in_interval(0, 7), Some(2)); +1458 assert_eq!(dedup.smallest_missing_in_interval(5, 7), Some(6)); +1459 +1460 dedup.insert(2); +1461 assert_eq!(dedup.smallest_missing_in_interval(1, 7), Some(3)); +1462 +1463 dedup.insert(170); +1464 dedup.insert(172); +1465 dedup.insert(300); +1466 assert_eq!(dedup.smallest_missing_in_interval(170, 172), None); +1467 +1468 dedup.insert(500); +1469 assert_eq!(dedup.smallest_missing_in_interval(0, 500), Some(372)); +1470 assert_eq!(dedup.smallest_missing_in_interval(0, 373), Some(372)); +1471 assert_eq!(dedup.smallest_missing_in_interval(0, 372), None); +1472 } +1473 +1474 #[test] +1475 fn pending_acks_first_packet_is_not_considered_reordered() { +1476 let mut acks = PendingAcks::new(); +1477 let mut dedup = Dedup::new(); +1478 dedup.insert(0); +1479 acks.packet_received(Instant::now(), 0, true, &dedup); +1480 assert!(!acks.immediate_ack_required); +1481 } +1482 +1483 #[test] +1484 fn pending_acks_after_immediate_ack_set() { +1485 let mut acks = PendingAcks::new(); +1486 let mut dedup = Dedup::new(); +1487 +1488 // Receive ack-eliciting packet +1489 dedup.insert(0); +1490 let now = Instant::now(); +1491 acks.insert_one(0, now); +1492 acks.packet_received(now, 0, true, &dedup); +1493 +1494 // Sanity check +1495 assert!(!acks.ranges.is_empty()); +1496 assert!(!acks.can_send()); +1497 +1498 // Can send ACK after max_ack_delay exceeded +1499 acks.set_immediate_ack_required(); +1500 assert!(acks.can_send()); +1501 } +1502 +1503 #[test] +1504 fn pending_acks_ack_delay() { +1505 let mut acks = PendingAcks::new(); +1506 let mut dedup = Dedup::new(); +1507 +1508 let t1 = Instant::now(); +1509 let t2 = t1 + Duration::from_millis(2); +1510 let t3 = t2 + Duration::from_millis(5); +1511 assert_eq!(acks.ack_delay(t1), Duration::from_millis(0)); +1512 assert_eq!(acks.ack_delay(t2), Duration::from_millis(0)); +1513 assert_eq!(acks.ack_delay(t3), Duration::from_millis(0)); +1514 +1515 // In-order packet +1516 dedup.insert(0); +1517 acks.insert_one(0, t1); +1518 acks.packet_received(t1, 0, true, &dedup); +1519 assert_eq!(acks.ack_delay(t1), Duration::from_millis(0)); +1520 assert_eq!(acks.ack_delay(t2), Duration::from_millis(2)); +1521 assert_eq!(acks.ack_delay(t3), Duration::from_millis(7)); +1522 +1523 // Out of order (higher than expected) +1524 dedup.insert(3); +1525 acks.insert_one(3, t2); +1526 acks.packet_received(t2, 3, true, &dedup); +1527 assert_eq!(acks.ack_delay(t2), Duration::from_millis(0)); +1528 assert_eq!(acks.ack_delay(t3), Duration::from_millis(5)); +1529 +1530 // Out of order (lower than expected, so previous instant is kept) +1531 dedup.insert(2); +1532 acks.insert_one(2, t3); +1533 acks.packet_received(t3, 2, true, &dedup); +1534 assert_eq!(acks.ack_delay(t3), Duration::from_millis(5)); +1535 } 1536 -1537 // Pop order is always the same -1538 assert_eq!(pending_cids.pop().map(|i| i.id), Some(a.id)); -1539 assert_eq!(pending_cids.pop().map(|i| i.id), Some(b.id)); -1540 assert_eq!(pending_cids.pop().map(|i| i.id), Some(c.id)); -1541 assert!(pending_cids.pop().is_none()); -1542 } -1543 } -1544}

\ No newline at end of file +1537 #[test] +1538 fn sent_packet_size() { +1539 // The tracking state of sent packets should be minimal, and not grow +1540 // over time. +1541 assert!(std::mem::size_of::<SentPacket>() <= 128); +1542 } +1543 +1544 #[test] +1545 fn pending_new_cids() { +1546 #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] +1547 use aws_lc_rs::hmac; +1548 #[cfg(feature = "ring")] +1549 use ring::hmac; +1550 +1551 let mut cid_generator = RandomConnectionIdGenerator::new(8); +1552 let mut reset_key = [0; 64]; +1553 rand::rng().fill_bytes(&mut reset_key); +1554 let hmac = hmac::Key::new(hmac::HMAC_SHA256, &reset_key); +1555 +1556 let cid_a = cid_generator.generate_cid(); +1557 let a = IssuedCid { +1558 path_id: PathId::ZERO, +1559 sequence: 1, +1560 id: cid_a, +1561 reset_token: ResetToken::new(&hmac, cid_a), +1562 }; +1563 let cid_b = cid_generator.generate_cid(); +1564 let b = IssuedCid { +1565 path_id: PathId::ZERO, +1566 sequence: 2, +1567 id: cid_b, +1568 reset_token: ResetToken::new(&hmac, cid_b), +1569 }; +1570 let cid_c = cid_generator.generate_cid(); +1571 let c = IssuedCid { +1572 path_id: PathId(1), +1573 sequence: 1, +1574 id: cid_c, +1575 reset_token: ResetToken::new(&hmac, cid_c), +1576 }; +1577 +1578 let mut pending_cids = PendingNewCids::default(); +1579 +1580 for _ in 0..9 { +1581 // Push CIDs in a random order +1582 let mut input = vec![a, b, c]; +1583 input.shuffle(&mut rand::rng()); +1584 for cid in input { +1585 pending_cids.push(cid); +1586 } +1587 +1588 // Pop order is always the same +1589 assert_eq!(pending_cids.pop().map(|i| i.id), Some(a.id)); +1590 assert_eq!(pending_cids.pop().map(|i| i.id), Some(b.id)); +1591 assert_eq!(pending_cids.pop().map(|i| i.id), Some(c.id)); +1592 assert!(pending_cids.pop().is_none()); +1593 } +1594 } +1595}

\ No newline at end of file diff --git a/pr/616/docs/src/noq_proto/frame.rs.html b/pr/616/docs/src/noq_proto/frame.rs.html index c8853decc..3a12df1b4 100644 --- a/pr/616/docs/src/noq_proto/frame.rs.html +++ b/pr/616/docs/src/noq_proto/frame.rs.html @@ -2370,511 +2370,498 @@ 2370 } 2371} 2372 -2373/// Conjunction of the information contained in the reach out frames -2374/// ([`FrameType::ReachOutAtIpv4`], [`FrameType::ReachOutAtIpv6`]) -2375#[derive(Debug, PartialEq, Eq, Clone, derive_more::Display)] -2376#[display("REACH_OUT round: {round} local_addr: {}", self.socket_addr())] -2377#[cfg_attr(test, derive(Arbitrary))] -2378pub(crate) struct ReachOut { -2379 /// The sequence number of the NAT Traversal attempts -2380 pub(crate) round: VarInt, -2381 /// Address to use -2382 pub(crate) ip: IpAddr, -2383 /// Port to use with this address -2384 pub(crate) port: u16, -2385} -2386 -2387// TODO(@divma): remove -2388#[allow(dead_code)] -2389impl ReachOut { -2390 /// Smallest number of bytes this type of frame is guaranteed to fit within -2391 pub(crate) const SIZE_BOUND: usize = Self { -2392 round: VarInt::MAX, -2393 ip: IpAddr::V6(std::net::Ipv6Addr::LOCALHOST), -2394 port: u16::MAX, -2395 } -2396 .size(); +2373/// Conjunction of the information contained in the reach out frames. +2374/// +2375/// ([`FrameType::ReachOutAtIpv4`], [`FrameType::ReachOutAtIpv6`]) +2376#[derive(Debug, PartialEq, Eq, Clone, derive_more::Display)] +2377#[display("REACH_OUT round: {round} local_addr: {}", self.socket_addr())] +2378#[cfg_attr(test, derive(Arbitrary))] +2379pub(crate) struct ReachOut { +2380 /// The sequence number of the NAT Traversal attempts +2381 pub(crate) round: VarInt, +2382 /// Address to use +2383 pub(crate) ip: IpAddr, +2384 /// Port to use with this address +2385 pub(crate) port: u16, +2386} +2387 +2388impl ReachOut { +2389 /// Get the [`FrameType`] for this frame +2390 pub(crate) const fn get_type(&self) -> FrameType { +2391 if self.ip.is_ipv6() { +2392 FrameType::ReachOutAtIpv6 +2393 } else { +2394 FrameType::ReachOutAtIpv4 +2395 } +2396 } 2397 -2398 pub(crate) const fn new(round: VarInt, (ip, port): (IpAddr, u16)) -> Self { -2399 Self { round, ip, port } -2400 } -2401 -2402 /// Get the [`FrameType`] for this frame -2403 pub(crate) const fn get_type(&self) -> FrameType { -2404 if self.ip.is_ipv6() { -2405 FrameType::ReachOutAtIpv6 -2406 } else { -2407 FrameType::ReachOutAtIpv4 -2408 } -2409 } -2410 -2411 /// Compute the number of bytes needed to encode the frame -2412 pub(crate) const fn size(&self) -> usize { -2413 let type_size = self.get_type().size(); -2414 let round_bytes = self.round.size(); -2415 let ip_bytes = if self.ip.is_ipv6() { 16 } else { 4 }; -2416 let port_bytes = 2; -2417 type_size + round_bytes + ip_bytes + port_bytes -2418 } -2419 -2420 /// Read the frame contents from the buffer -2421 /// -2422 /// Should only be called when the frame type has been identified as -2423 /// [`FrameType::ReachOutAtIpv4`] or [`FrameType::ReachOutAtIpv6`]. -2424 pub(crate) fn read<R: Buf>(bytes: &mut R, is_ipv6: bool) -> coding::Result<Self> { -2425 let round = bytes.get()?; -2426 let ip = if is_ipv6 { -2427 IpAddr::V6(bytes.get()?) -2428 } else { -2429 IpAddr::V4(bytes.get()?) -2430 }; -2431 let port = bytes.get()?; -2432 Ok(Self { round, ip, port }) -2433 } -2434 -2435 /// Give the [`SocketAddr`] encoded in the frame -2436 pub(crate) fn socket_addr(&self) -> SocketAddr { -2437 (self.ip, self.port).into() -2438 } -2439} -2440 -2441impl Encodable for ReachOut { -2442 fn encode<W: BufMut>(&self, buf: &mut W) { -2443 buf.write(self.get_type()); -2444 buf.write(self.round); -2445 match self.ip { -2446 IpAddr::V4(ipv4_addr) => { -2447 buf.write(ipv4_addr); -2448 } -2449 IpAddr::V6(ipv6_addr) => { -2450 buf.write(ipv6_addr); -2451 } -2452 } -2453 buf.write::<u16>(self.port); -2454 } -2455} -2456 -2457/// Frame signaling an address is no longer being advertised -2458#[derive(Debug, PartialEq, Eq, Copy, Clone, PartialOrd, Ord, derive_more::Display)] -2459#[cfg_attr(test, derive(Arbitrary))] -2460#[display("REMOVE_ADDRESS seq_no: {seq_no}")] -2461pub(crate) struct RemoveAddress { -2462 /// The sequence number of the address advertisement to be removed -2463 pub(crate) seq_no: VarInt, -2464} +2398 /// Compute the number of bytes needed to encode the frame +2399 pub(crate) const fn size(&self) -> usize { +2400 let type_size = self.get_type().size(); +2401 let round_bytes = self.round.size(); +2402 let ip_bytes = if self.ip.is_ipv6() { 16 } else { 4 }; +2403 let port_bytes = 2; +2404 type_size + round_bytes + ip_bytes + port_bytes +2405 } +2406 +2407 /// Read the frame contents from the buffer +2408 /// +2409 /// Should only be called when the frame type has been identified as +2410 /// [`FrameType::ReachOutAtIpv4`] or [`FrameType::ReachOutAtIpv6`]. +2411 pub(crate) fn read<R: Buf>(bytes: &mut R, is_ipv6: bool) -> coding::Result<Self> { +2412 let round = bytes.get()?; +2413 let ip = if is_ipv6 { +2414 IpAddr::V6(bytes.get()?) +2415 } else { +2416 IpAddr::V4(bytes.get()?) +2417 }; +2418 let port = bytes.get()?; +2419 Ok(Self { round, ip, port }) +2420 } +2421 +2422 /// Give the [`SocketAddr`] encoded in the frame +2423 pub(crate) fn socket_addr(&self) -> SocketAddr { +2424 (self.ip, self.port).into() +2425 } +2426} +2427 +2428impl Encodable for ReachOut { +2429 fn encode<W: BufMut>(&self, buf: &mut W) { +2430 buf.write(self.get_type()); +2431 buf.write(self.round); +2432 match self.ip { +2433 IpAddr::V4(ipv4_addr) => { +2434 buf.write(ipv4_addr); +2435 } +2436 IpAddr::V6(ipv6_addr) => { +2437 buf.write(ipv6_addr); +2438 } +2439 } +2440 buf.write::<u16>(self.port); +2441 } +2442} +2443 +2444/// Frame signaling an address is no longer being advertised +2445#[derive(Debug, PartialEq, Eq, Copy, Clone, PartialOrd, Ord, derive_more::Display)] +2446#[cfg_attr(test, derive(Arbitrary))] +2447#[display("REMOVE_ADDRESS seq_no: {seq_no}")] +2448pub(crate) struct RemoveAddress { +2449 /// The sequence number of the address advertisement to be removed +2450 pub(crate) seq_no: VarInt, +2451} +2452 +2453// TODO(@divma): remove +2454#[allow(dead_code)] +2455impl RemoveAddress { +2456 /// [`FrameType`] of this frame +2457 pub(crate) const TYPE: FrameType = FrameType::RemoveAddress; +2458 +2459 /// Smallest number of bytes this type of frame is guaranteed to fit within +2460 pub(crate) const SIZE_BOUND: usize = Self::new(VarInt::MAX).size(); +2461 +2462 pub(crate) const fn new(seq_no: VarInt) -> Self { +2463 Self { seq_no } +2464 } 2465 -2466// TODO(@divma): remove -2467#[allow(dead_code)] -2468impl RemoveAddress { -2469 /// [`FrameType`] of this frame -2470 pub(crate) const TYPE: FrameType = FrameType::RemoveAddress; -2471 -2472 /// Smallest number of bytes this type of frame is guaranteed to fit within -2473 pub(crate) const SIZE_BOUND: usize = Self::new(VarInt::MAX).size(); -2474 -2475 pub(crate) const fn new(seq_no: VarInt) -> Self { -2476 Self { seq_no } -2477 } -2478 -2479 /// Compute the number of bytes needed to encode the frame -2480 pub(crate) const fn size(&self) -> usize { -2481 let type_size = Self::TYPE.size(); -2482 let seq_no_bytes = self.seq_no.size(); -2483 type_size + seq_no_bytes -2484 } -2485 -2486 /// Read the frame contents from the buffer -2487 /// -2488 /// Should only be called when the frame type has been identified as -2489 /// [`FrameType::RemoveAddress`]. -2490 pub(crate) fn read<R: Buf>(bytes: &mut R) -> coding::Result<Self> { -2491 Ok(Self { -2492 seq_no: bytes.get()?, -2493 }) -2494 } -2495 -2496 const fn get_type(&self) -> FrameType { -2497 FrameType::RemoveAddress -2498 } -2499} -2500 -2501impl Encodable for RemoveAddress { -2502 fn encode<W: BufMut>(&self, buf: &mut W) { -2503 buf.write(Self::TYPE); -2504 buf.write(self.seq_no); -2505 } -2506} -2507 -2508/// Helper struct for display implementations. -2509// NOTE: Due to lifetimes in fmt::Arguments it's not possible to make this a simple function that -2510// avoids allocations. -2511struct DisplayOption<T: Display> { -2512 field_name: &'static str, -2513 op: Option<T>, -2514} -2515 -2516impl<T: Display> DisplayOption<T> { -2517 fn new(field_name: &'static str, op: Option<T>) -> Self { -2518 Self { field_name, op } -2519 } -2520} -2521 -2522impl<T: Display> Display for DisplayOption<T> { -2523 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -2524 if let Some(x) = self.op.as_ref() { -2525 write!(f, "{}: {x}", self.field_name) -2526 } else { -2527 fmt::Result::Ok(()) -2528 } -2529 } -2530} +2466 /// Compute the number of bytes needed to encode the frame +2467 pub(crate) const fn size(&self) -> usize { +2468 let type_size = Self::TYPE.size(); +2469 let seq_no_bytes = self.seq_no.size(); +2470 type_size + seq_no_bytes +2471 } +2472 +2473 /// Read the frame contents from the buffer +2474 /// +2475 /// Should only be called when the frame type has been identified as +2476 /// [`FrameType::RemoveAddress`]. +2477 pub(crate) fn read<R: Buf>(bytes: &mut R) -> coding::Result<Self> { +2478 Ok(Self { +2479 seq_no: bytes.get()?, +2480 }) +2481 } +2482 +2483 const fn get_type(&self) -> FrameType { +2484 FrameType::RemoveAddress +2485 } +2486} +2487 +2488impl Encodable for RemoveAddress { +2489 fn encode<W: BufMut>(&self, buf: &mut W) { +2490 buf.write(Self::TYPE); +2491 buf.write(self.seq_no); +2492 } +2493} +2494 +2495/// Helper struct for display implementations. +2496// NOTE: Due to lifetimes in fmt::Arguments it's not possible to make this a simple function that +2497// avoids allocations. +2498struct DisplayOption<T: Display> { +2499 field_name: &'static str, +2500 op: Option<T>, +2501} +2502 +2503impl<T: Display> DisplayOption<T> { +2504 fn new(field_name: &'static str, op: Option<T>) -> Self { +2505 Self { field_name, op } +2506 } +2507} +2508 +2509impl<T: Display> Display for DisplayOption<T> { +2510 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +2511 if let Some(x) = self.op.as_ref() { +2512 write!(f, "{}: {x}", self.field_name) +2513 } else { +2514 fmt::Result::Ok(()) +2515 } +2516 } +2517} +2518 +2519#[cfg(test)] +2520mod test { +2521 use super::*; +2522 use crate::coding::Encodable; +2523 use assert_matches::assert_matches; +2524 +2525 #[test] +2526 fn frame_type() { +2527 assert_eq!( +2528 FrameType::try_from(FrameType::Padding.to_u64()), +2529 Ok(FrameType::Padding), +2530 ); 2531 -2532#[cfg(test)] -2533mod test { -2534 use super::*; -2535 use crate::coding::Encodable; -2536 use assert_matches::assert_matches; -2537 -2538 #[test] -2539 fn frame_type() { -2540 assert_eq!( -2541 FrameType::try_from(FrameType::Padding.to_u64()), -2542 Ok(FrameType::Padding), -2543 ); -2544 -2545 assert_eq!( -2546 FrameType::try_from(FrameType::Datagram(DatagramInfo(0x30)).to_u64()), -2547 Ok(FrameType::Datagram(DatagramInfo(0x30))), -2548 ); -2549 -2550 assert_eq!( -2551 FrameType::try_from(FrameType::Stream(StreamInfo(0x08)).to_u64()), -2552 Ok(FrameType::Stream(StreamInfo(0x08))), -2553 ); -2554 } -2555 -2556 #[track_caller] -2557 fn frames(buf: Vec<u8>) -> Vec<Frame> { -2558 Iter::new(Bytes::from(buf)) -2559 .unwrap() -2560 .collect::<Result<Vec<_>, _>>() -2561 .unwrap() -2562 } -2563 -2564 #[test] -2565 fn ack_coding() { -2566 const PACKETS: &[u64] = &[1, 2, 3, 5, 10, 11, 14]; -2567 let mut ranges = ArrayRangeSet::new(); -2568 for &packet in PACKETS { -2569 ranges.insert(packet..packet + 1); -2570 } -2571 let mut buf = Vec::new(); -2572 const ECN: EcnCounts = EcnCounts { -2573 ect0: 42, -2574 ect1: 24, -2575 ce: 12, -2576 }; -2577 Ack::encoder(42, &ranges, Some(&ECN)).encode(&mut buf); -2578 let frames = frames(buf); -2579 assert_eq!(frames.len(), 1); -2580 match frames[0] { -2581 Frame::Ack(ref ack) => { -2582 let mut packets = ack.iter().flatten().collect::<Vec<_>>(); -2583 packets.sort_unstable(); -2584 assert_eq!(&packets[..], PACKETS); -2585 assert_eq!(ack.ecn, Some(ECN)); -2586 } -2587 ref x => panic!("incorrect frame {x:?}"), -2588 } -2589 } -2590 -2591 #[test] -2592 #[allow(clippy::range_plus_one)] -2593 fn path_ack_coding_with_ecn() { -2594 const PACKETS: &[u64] = &[1, 2, 3, 5, 10, 11, 14]; -2595 let mut ranges = ArrayRangeSet::new(); -2596 for &packet in PACKETS { -2597 ranges.insert(packet..packet + 1); -2598 } -2599 let mut buf = Vec::new(); -2600 const ECN: EcnCounts = EcnCounts { -2601 ect0: 42, -2602 ect1: 24, -2603 ce: 12, -2604 }; -2605 const PATH_ID: PathId = PathId::MAX; -2606 PathAck::encoder(PATH_ID, 42, &ranges, Some(&ECN)).encode(&mut buf); -2607 let frames = frames(buf); -2608 assert_eq!(frames.len(), 1); -2609 match frames[0] { -2610 Frame::PathAck(ref ack) => { -2611 assert_eq!(ack.path_id, PATH_ID); -2612 let mut packets = ack.ranges.iter().flatten().collect::<Vec<_>>(); -2613 packets.sort_unstable(); -2614 assert_eq!(&packets[..], PACKETS); -2615 assert_eq!(ack.ecn, Some(ECN)); -2616 } -2617 ref x => panic!("incorrect frame {x:?}"), -2618 } -2619 } -2620 -2621 #[test] -2622 #[allow(clippy::range_plus_one)] -2623 fn path_ack_coding_no_ecn() { -2624 const PACKETS: &[u64] = &[1, 2, 3, 5, 10, 11, 14]; -2625 let mut ranges = ArrayRangeSet::new(); -2626 for &packet in PACKETS { -2627 ranges.insert(packet..packet + 1); -2628 } -2629 let mut buf = Vec::new(); -2630 const PATH_ID: PathId = PathId::MAX; -2631 PathAck::encoder(PATH_ID, 42, &ranges, None).encode(&mut buf); -2632 let frames = frames(buf); -2633 assert_eq!(frames.len(), 1); -2634 match frames[0] { -2635 Frame::PathAck(ref ack) => { -2636 assert_eq!(ack.path_id, PATH_ID); -2637 let mut packets = ack.ranges.iter().flatten().collect::<Vec<_>>(); -2638 packets.sort_unstable(); -2639 assert_eq!(&packets[..], PACKETS); -2640 assert_eq!(ack.ecn, None); -2641 } -2642 ref x => panic!("incorrect frame {x:?}"), -2643 } -2644 } -2645 -2646 #[test] -2647 fn ack_frequency_coding() { -2648 let mut buf = Vec::new(); -2649 let original = AckFrequency { -2650 sequence: VarInt(42), -2651 ack_eliciting_threshold: VarInt(20), -2652 request_max_ack_delay: VarInt(50_000), -2653 reordering_threshold: VarInt(1), -2654 }; -2655 original.encode(&mut buf); -2656 let frames = frames(buf); -2657 assert_eq!(frames.len(), 1); -2658 match &frames[0] { -2659 Frame::AckFrequency(decoded) => assert_eq!(decoded, &original), -2660 x => panic!("incorrect frame {x:?}"), -2661 } -2662 } -2663 -2664 #[test] -2665 fn immediate_ack_coding() { -2666 let mut buf = Vec::new(); -2667 FrameType::ImmediateAck.encode(&mut buf); -2668 let frames = frames(buf); -2669 assert_eq!(frames.len(), 1); -2670 assert_matches!(&frames[0], Frame::ImmediateAck); -2671 } -2672 -2673 /// Test that encoding and decoding [`ObservedAddr`] produces the same result. -2674 #[test] -2675 fn test_observed_addr_roundrip() { -2676 let observed_addr = ObservedAddr { -2677 seq_no: VarInt(42), -2678 ip: std::net::Ipv4Addr::LOCALHOST.into(), -2679 port: 4242, -2680 }; -2681 let mut buf = Vec::with_capacity(observed_addr.size()); -2682 observed_addr.encode(&mut buf); -2683 -2684 assert_eq!( -2685 observed_addr.size(), -2686 buf.len(), -2687 "expected written bytes and actual size differ" -2688 ); -2689 -2690 let mut decoded = frames(buf); -2691 assert_eq!(decoded.len(), 1); -2692 match decoded.pop().expect("non empty") { -2693 Frame::ObservedAddr(decoded) => assert_eq!(decoded, observed_addr), -2694 x => panic!("incorrect frame {x:?}"), -2695 } -2696 } -2697 -2698 #[test] -2699 fn test_path_abandon_roundtrip() { -2700 let abandon = PathAbandon { -2701 path_id: PathId(42), -2702 error_code: TransportErrorCode::NO_ERROR, -2703 }; -2704 let mut buf = Vec::new(); -2705 abandon.encode(&mut buf); -2706 -2707 let mut decoded = frames(buf); -2708 assert_eq!(decoded.len(), 1); -2709 match decoded.pop().expect("non empty") { -2710 Frame::PathAbandon(decoded) => assert_eq!(decoded, abandon), -2711 x => panic!("incorrect frame {x:?}"), -2712 } -2713 } -2714 -2715 #[test] -2716 fn test_path_status_available_roundtrip() { -2717 let path_status_available = PathStatusAvailable { -2718 path_id: PathId(42), -2719 status_seq_no: VarInt(73), -2720 }; -2721 let mut buf = Vec::new(); -2722 path_status_available.encode(&mut buf); -2723 -2724 let mut decoded = frames(buf); -2725 assert_eq!(decoded.len(), 1); -2726 match decoded.pop().expect("non empty") { -2727 Frame::PathStatusAvailable(decoded) => assert_eq!(decoded, path_status_available), -2728 x => panic!("incorrect frame {x:?}"), -2729 } -2730 } -2731 -2732 #[test] -2733 fn test_path_status_backup_roundtrip() { -2734 let path_status_backup = PathStatusBackup { -2735 path_id: PathId(42), -2736 status_seq_no: VarInt(73), -2737 }; -2738 let mut buf = Vec::new(); -2739 path_status_backup.encode(&mut buf); -2740 -2741 let mut decoded = frames(buf); -2742 assert_eq!(decoded.len(), 1); -2743 match decoded.pop().expect("non empty") { -2744 Frame::PathStatusBackup(decoded) => assert_eq!(decoded, path_status_backup), -2745 x => panic!("incorrect frame {x:?}"), -2746 } -2747 } -2748 -2749 #[test] -2750 fn test_path_new_connection_id_roundtrip() { -2751 let cid = NewConnectionId { -2752 path_id: Some(PathId(22)), -2753 sequence: 31, -2754 retire_prior_to: 13, -2755 id: ConnectionId::new(&[0xAB; 8]), -2756 reset_token: ResetToken::from([0xCD; crate::RESET_TOKEN_SIZE]), -2757 }; -2758 let mut buf = Vec::new(); -2759 cid.encode(&mut buf); -2760 -2761 let mut decoded = frames(buf); -2762 assert_eq!(decoded.len(), 1); -2763 match decoded.pop().expect("non empty") { -2764 Frame::NewConnectionId(decoded) => assert_eq!(decoded, cid), -2765 x => panic!("incorrect frame {x:?}"), -2766 } -2767 } -2768 -2769 #[test] -2770 fn test_path_retire_connection_id_roundtrip() { -2771 let retire_cid = RetireConnectionId { -2772 path_id: Some(PathId(22)), -2773 sequence: 31, -2774 }; +2532 assert_eq!( +2533 FrameType::try_from(FrameType::Datagram(DatagramInfo(0x30)).to_u64()), +2534 Ok(FrameType::Datagram(DatagramInfo(0x30))), +2535 ); +2536 +2537 assert_eq!( +2538 FrameType::try_from(FrameType::Stream(StreamInfo(0x08)).to_u64()), +2539 Ok(FrameType::Stream(StreamInfo(0x08))), +2540 ); +2541 } +2542 +2543 #[track_caller] +2544 fn frames(buf: Vec<u8>) -> Vec<Frame> { +2545 Iter::new(Bytes::from(buf)) +2546 .unwrap() +2547 .collect::<Result<Vec<_>, _>>() +2548 .unwrap() +2549 } +2550 +2551 #[test] +2552 fn ack_coding() { +2553 const PACKETS: &[u64] = &[1, 2, 3, 5, 10, 11, 14]; +2554 let mut ranges = ArrayRangeSet::new(); +2555 for &packet in PACKETS { +2556 ranges.insert(packet..packet + 1); +2557 } +2558 let mut buf = Vec::new(); +2559 const ECN: EcnCounts = EcnCounts { +2560 ect0: 42, +2561 ect1: 24, +2562 ce: 12, +2563 }; +2564 Ack::encoder(42, &ranges, Some(&ECN)).encode(&mut buf); +2565 let frames = frames(buf); +2566 assert_eq!(frames.len(), 1); +2567 match frames[0] { +2568 Frame::Ack(ref ack) => { +2569 let mut packets = ack.iter().flatten().collect::<Vec<_>>(); +2570 packets.sort_unstable(); +2571 assert_eq!(&packets[..], PACKETS); +2572 assert_eq!(ack.ecn, Some(ECN)); +2573 } +2574 ref x => panic!("incorrect frame {x:?}"), +2575 } +2576 } +2577 +2578 #[test] +2579 #[allow(clippy::range_plus_one)] +2580 fn path_ack_coding_with_ecn() { +2581 const PACKETS: &[u64] = &[1, 2, 3, 5, 10, 11, 14]; +2582 let mut ranges = ArrayRangeSet::new(); +2583 for &packet in PACKETS { +2584 ranges.insert(packet..packet + 1); +2585 } +2586 let mut buf = Vec::new(); +2587 const ECN: EcnCounts = EcnCounts { +2588 ect0: 42, +2589 ect1: 24, +2590 ce: 12, +2591 }; +2592 const PATH_ID: PathId = PathId::MAX; +2593 PathAck::encoder(PATH_ID, 42, &ranges, Some(&ECN)).encode(&mut buf); +2594 let frames = frames(buf); +2595 assert_eq!(frames.len(), 1); +2596 match frames[0] { +2597 Frame::PathAck(ref ack) => { +2598 assert_eq!(ack.path_id, PATH_ID); +2599 let mut packets = ack.ranges.iter().flatten().collect::<Vec<_>>(); +2600 packets.sort_unstable(); +2601 assert_eq!(&packets[..], PACKETS); +2602 assert_eq!(ack.ecn, Some(ECN)); +2603 } +2604 ref x => panic!("incorrect frame {x:?}"), +2605 } +2606 } +2607 +2608 #[test] +2609 #[allow(clippy::range_plus_one)] +2610 fn path_ack_coding_no_ecn() { +2611 const PACKETS: &[u64] = &[1, 2, 3, 5, 10, 11, 14]; +2612 let mut ranges = ArrayRangeSet::new(); +2613 for &packet in PACKETS { +2614 ranges.insert(packet..packet + 1); +2615 } +2616 let mut buf = Vec::new(); +2617 const PATH_ID: PathId = PathId::MAX; +2618 PathAck::encoder(PATH_ID, 42, &ranges, None).encode(&mut buf); +2619 let frames = frames(buf); +2620 assert_eq!(frames.len(), 1); +2621 match frames[0] { +2622 Frame::PathAck(ref ack) => { +2623 assert_eq!(ack.path_id, PATH_ID); +2624 let mut packets = ack.ranges.iter().flatten().collect::<Vec<_>>(); +2625 packets.sort_unstable(); +2626 assert_eq!(&packets[..], PACKETS); +2627 assert_eq!(ack.ecn, None); +2628 } +2629 ref x => panic!("incorrect frame {x:?}"), +2630 } +2631 } +2632 +2633 #[test] +2634 fn ack_frequency_coding() { +2635 let mut buf = Vec::new(); +2636 let original = AckFrequency { +2637 sequence: VarInt(42), +2638 ack_eliciting_threshold: VarInt(20), +2639 request_max_ack_delay: VarInt(50_000), +2640 reordering_threshold: VarInt(1), +2641 }; +2642 original.encode(&mut buf); +2643 let frames = frames(buf); +2644 assert_eq!(frames.len(), 1); +2645 match &frames[0] { +2646 Frame::AckFrequency(decoded) => assert_eq!(decoded, &original), +2647 x => panic!("incorrect frame {x:?}"), +2648 } +2649 } +2650 +2651 #[test] +2652 fn immediate_ack_coding() { +2653 let mut buf = Vec::new(); +2654 FrameType::ImmediateAck.encode(&mut buf); +2655 let frames = frames(buf); +2656 assert_eq!(frames.len(), 1); +2657 assert_matches!(&frames[0], Frame::ImmediateAck); +2658 } +2659 +2660 /// Test that encoding and decoding [`ObservedAddr`] produces the same result. +2661 #[test] +2662 fn test_observed_addr_roundrip() { +2663 let observed_addr = ObservedAddr { +2664 seq_no: VarInt(42), +2665 ip: std::net::Ipv4Addr::LOCALHOST.into(), +2666 port: 4242, +2667 }; +2668 let mut buf = Vec::with_capacity(observed_addr.size()); +2669 observed_addr.encode(&mut buf); +2670 +2671 assert_eq!( +2672 observed_addr.size(), +2673 buf.len(), +2674 "expected written bytes and actual size differ" +2675 ); +2676 +2677 let mut decoded = frames(buf); +2678 assert_eq!(decoded.len(), 1); +2679 match decoded.pop().expect("non empty") { +2680 Frame::ObservedAddr(decoded) => assert_eq!(decoded, observed_addr), +2681 x => panic!("incorrect frame {x:?}"), +2682 } +2683 } +2684 +2685 #[test] +2686 fn test_path_abandon_roundtrip() { +2687 let abandon = PathAbandon { +2688 path_id: PathId(42), +2689 error_code: TransportErrorCode::NO_ERROR, +2690 }; +2691 let mut buf = Vec::new(); +2692 abandon.encode(&mut buf); +2693 +2694 let mut decoded = frames(buf); +2695 assert_eq!(decoded.len(), 1); +2696 match decoded.pop().expect("non empty") { +2697 Frame::PathAbandon(decoded) => assert_eq!(decoded, abandon), +2698 x => panic!("incorrect frame {x:?}"), +2699 } +2700 } +2701 +2702 #[test] +2703 fn test_path_status_available_roundtrip() { +2704 let path_status_available = PathStatusAvailable { +2705 path_id: PathId(42), +2706 status_seq_no: VarInt(73), +2707 }; +2708 let mut buf = Vec::new(); +2709 path_status_available.encode(&mut buf); +2710 +2711 let mut decoded = frames(buf); +2712 assert_eq!(decoded.len(), 1); +2713 match decoded.pop().expect("non empty") { +2714 Frame::PathStatusAvailable(decoded) => assert_eq!(decoded, path_status_available), +2715 x => panic!("incorrect frame {x:?}"), +2716 } +2717 } +2718 +2719 #[test] +2720 fn test_path_status_backup_roundtrip() { +2721 let path_status_backup = PathStatusBackup { +2722 path_id: PathId(42), +2723 status_seq_no: VarInt(73), +2724 }; +2725 let mut buf = Vec::new(); +2726 path_status_backup.encode(&mut buf); +2727 +2728 let mut decoded = frames(buf); +2729 assert_eq!(decoded.len(), 1); +2730 match decoded.pop().expect("non empty") { +2731 Frame::PathStatusBackup(decoded) => assert_eq!(decoded, path_status_backup), +2732 x => panic!("incorrect frame {x:?}"), +2733 } +2734 } +2735 +2736 #[test] +2737 fn test_path_new_connection_id_roundtrip() { +2738 let cid = NewConnectionId { +2739 path_id: Some(PathId(22)), +2740 sequence: 31, +2741 retire_prior_to: 13, +2742 id: ConnectionId::new(&[0xAB; 8]), +2743 reset_token: ResetToken::from([0xCD; crate::RESET_TOKEN_SIZE]), +2744 }; +2745 let mut buf = Vec::new(); +2746 cid.encode(&mut buf); +2747 +2748 let mut decoded = frames(buf); +2749 assert_eq!(decoded.len(), 1); +2750 match decoded.pop().expect("non empty") { +2751 Frame::NewConnectionId(decoded) => assert_eq!(decoded, cid), +2752 x => panic!("incorrect frame {x:?}"), +2753 } +2754 } +2755 +2756 #[test] +2757 fn test_path_retire_connection_id_roundtrip() { +2758 let retire_cid = RetireConnectionId { +2759 path_id: Some(PathId(22)), +2760 sequence: 31, +2761 }; +2762 let mut buf = Vec::new(); +2763 retire_cid.encode(&mut buf); +2764 +2765 let mut decoded = frames(buf); +2766 assert_eq!(decoded.len(), 1); +2767 match decoded.pop().expect("non empty") { +2768 Frame::RetireConnectionId(decoded) => assert_eq!(decoded, retire_cid), +2769 x => panic!("incorrect frame {x:?}"), +2770 } +2771 } +2772 +2773 #[test] +2774 fn test_paths_blocked_path_cids_blocked_roundtrip() { 2775 let mut buf = Vec::new(); -2776 retire_cid.encode(&mut buf); -2777 -2778 let mut decoded = frames(buf); -2779 assert_eq!(decoded.len(), 1); -2780 match decoded.pop().expect("non empty") { -2781 Frame::RetireConnectionId(decoded) => assert_eq!(decoded, retire_cid), -2782 x => panic!("incorrect frame {x:?}"), -2783 } -2784 } -2785 -2786 #[test] -2787 fn test_paths_blocked_path_cids_blocked_roundtrip() { -2788 let mut buf = Vec::new(); -2789 -2790 let frame0 = PathsBlocked(PathId(22)); -2791 frame0.encode(&mut buf); -2792 let frame1 = PathCidsBlocked { -2793 path_id: PathId(23), -2794 next_seq: VarInt(32), -2795 }; -2796 frame1.encode(&mut buf); -2797 -2798 let mut decoded = frames(buf); -2799 assert_eq!(decoded.len(), 2); -2800 match decoded.pop().expect("non empty") { -2801 Frame::PathCidsBlocked(decoded) => assert_eq!(decoded, frame1), -2802 x => panic!("incorrect frame {x:?}"), -2803 } -2804 match decoded.pop().expect("non empty") { -2805 Frame::PathsBlocked(decoded) => assert_eq!(decoded, frame0), -2806 x => panic!("incorrect frame {x:?}"), -2807 } -2808 } -2809 -2810 /// Test that encoding and decoding [`AddAddress`] produces the same result -2811 #[test] -2812 fn test_add_address_roundrip() { -2813 let add_address = AddAddress { -2814 seq_no: VarInt(42), -2815 ip: std::net::Ipv4Addr::LOCALHOST.into(), -2816 port: 4242, -2817 }; -2818 let mut buf = Vec::with_capacity(add_address.size()); -2819 add_address.encode(&mut buf); -2820 -2821 assert_eq!( -2822 add_address.size(), -2823 buf.len(), -2824 "expected written bytes and actual size differ" -2825 ); -2826 -2827 let mut decoded = frames(buf); -2828 assert_eq!(decoded.len(), 1); -2829 match decoded.pop().expect("non empty") { -2830 Frame::AddAddress(decoded) => assert_eq!(decoded, add_address), -2831 x => panic!("incorrect frame {x:?}"), -2832 } -2833 } -2834 -2835 /// Test that encoding and decoding [`AddAddress`] produces the same result -2836 #[test] -2837 fn test_reach_out_roundrip() { -2838 let reach_out = ReachOut { -2839 round: VarInt(42), -2840 ip: std::net::Ipv6Addr::LOCALHOST.into(), -2841 port: 4242, -2842 }; -2843 let mut buf = Vec::with_capacity(reach_out.size()); -2844 reach_out.encode(&mut buf); -2845 -2846 assert_eq!( -2847 reach_out.size(), -2848 buf.len(), -2849 "expected written bytes and actual size differ" -2850 ); -2851 -2852 let mut decoded = frames(buf); -2853 assert_eq!(decoded.len(), 1); -2854 match decoded.pop().expect("non empty") { -2855 Frame::ReachOut(decoded) => assert_eq!(decoded, reach_out), -2856 x => panic!("incorrect frame {x:?}"), -2857 } -2858 } +2776 +2777 let frame0 = PathsBlocked(PathId(22)); +2778 frame0.encode(&mut buf); +2779 let frame1 = PathCidsBlocked { +2780 path_id: PathId(23), +2781 next_seq: VarInt(32), +2782 }; +2783 frame1.encode(&mut buf); +2784 +2785 let mut decoded = frames(buf); +2786 assert_eq!(decoded.len(), 2); +2787 match decoded.pop().expect("non empty") { +2788 Frame::PathCidsBlocked(decoded) => assert_eq!(decoded, frame1), +2789 x => panic!("incorrect frame {x:?}"), +2790 } +2791 match decoded.pop().expect("non empty") { +2792 Frame::PathsBlocked(decoded) => assert_eq!(decoded, frame0), +2793 x => panic!("incorrect frame {x:?}"), +2794 } +2795 } +2796 +2797 /// Test that encoding and decoding [`AddAddress`] produces the same result +2798 #[test] +2799 fn test_add_address_roundrip() { +2800 let add_address = AddAddress { +2801 seq_no: VarInt(42), +2802 ip: std::net::Ipv4Addr::LOCALHOST.into(), +2803 port: 4242, +2804 }; +2805 let mut buf = Vec::with_capacity(add_address.size()); +2806 add_address.encode(&mut buf); +2807 +2808 assert_eq!( +2809 add_address.size(), +2810 buf.len(), +2811 "expected written bytes and actual size differ" +2812 ); +2813 +2814 let mut decoded = frames(buf); +2815 assert_eq!(decoded.len(), 1); +2816 match decoded.pop().expect("non empty") { +2817 Frame::AddAddress(decoded) => assert_eq!(decoded, add_address), +2818 x => panic!("incorrect frame {x:?}"), +2819 } +2820 } +2821 +2822 /// Test that encoding and decoding [`AddAddress`] produces the same result +2823 #[test] +2824 fn test_reach_out_roundrip() { +2825 let reach_out = ReachOut { +2826 round: VarInt(42), +2827 ip: std::net::Ipv6Addr::LOCALHOST.into(), +2828 port: 4242, +2829 }; +2830 let mut buf = Vec::with_capacity(reach_out.size()); +2831 reach_out.encode(&mut buf); +2832 +2833 assert_eq!( +2834 reach_out.size(), +2835 buf.len(), +2836 "expected written bytes and actual size differ" +2837 ); +2838 +2839 let mut decoded = frames(buf); +2840 assert_eq!(decoded.len(), 1); +2841 match decoded.pop().expect("non empty") { +2842 Frame::ReachOut(decoded) => assert_eq!(decoded, reach_out), +2843 x => panic!("incorrect frame {x:?}"), +2844 } +2845 } +2846 +2847 /// Test that encoding and decoding [`RemoveAddress`] produces the same result +2848 #[test] +2849 fn test_remove_address_roundrip() { +2850 let remove_addr = RemoveAddress::new(VarInt(10)); +2851 let mut buf = Vec::with_capacity(remove_addr.size()); +2852 remove_addr.encode(&mut buf); +2853 +2854 assert_eq!( +2855 remove_addr.size(), +2856 buf.len(), +2857 "expected written bytes and actual size differ" +2858 ); 2859 -2860 /// Test that encoding and decoding [`RemoveAddress`] produces the same result -2861 #[test] -2862 fn test_remove_address_roundrip() { -2863 let remove_addr = RemoveAddress::new(VarInt(10)); -2864 let mut buf = Vec::with_capacity(remove_addr.size()); -2865 remove_addr.encode(&mut buf); -2866 -2867 assert_eq!( -2868 remove_addr.size(), -2869 buf.len(), -2870 "expected written bytes and actual size differ" -2871 ); -2872 -2873 let mut decoded = frames(buf); -2874 assert_eq!(decoded.len(), 1); -2875 match decoded.pop().expect("non empty") { -2876 Frame::RemoveAddress(decoded) => assert_eq!(decoded, remove_addr), -2877 x => panic!("incorrect frame {x:?}"), -2878 } -2879 } -2880}

\ No newline at end of file +2860 let mut decoded = frames(buf); +2861 assert_eq!(decoded.len(), 1); +2862 match decoded.pop().expect("non empty") { +2863 Frame::RemoveAddress(decoded) => assert_eq!(decoded, remove_addr), +2864 x => panic!("incorrect frame {x:?}"), +2865 } +2866 } +2867} \ No newline at end of file diff --git a/pr/616/docs/src/noq_proto/n0_nat_traversal.rs.html b/pr/616/docs/src/noq_proto/n0_nat_traversal.rs.html index 6d8bc22fd..37b1e10c4 100644 --- a/pr/616/docs/src/noq_proto/n0_nat_traversal.rs.html +++ b/pr/616/docs/src/noq_proto/n0_nat_traversal.rs.html @@ -9,690 +9,831 @@ 9use tracing::{debug, trace}; 10 11use crate::{ -12 FourTuple, PathId, Side, VarInt, -13 frame::{AddAddress, ReachOut, RemoveAddress}, -14}; -15 -16type IpPort = (IpAddr, u16); -17 -18/// Errors that the nat traversal state might encounter. -19#[derive(Debug, thiserror::Error)] -20pub enum Error { -21 /// An endpoint (local or remote) tried to add too many addresses to their advertised set -22 #[error("Tried to add too many addresses to their advertised set")] -23 TooManyAddresses, -24 /// The operation is not allowed for this endpoint's connection side -25 #[error("Not allowed for this endpoint's connection side")] -26 WrongConnectionSide, -27 /// The extension was not negotiated -28 #[error("n0's nat traversal was not negotiated")] -29 ExtensionNotNegotiated, -30 /// Not enough addresses to complete the operation -31 #[error("Not enough addresses")] -32 NotEnoughAddresses, -33 /// Nat traversal attempt failed due to a multipath error -34 #[error("Failed to establish paths {0}")] -35 Multipath(super::PathError), -36 /// Attempted to initiate NAT traversal on a closed, or closing connection. -37 #[error("The connection is already closed")] -38 Closed, -39} -40 -41pub(crate) struct NatTraversalRound { -42 /// Sequence number to use for the new reach out frames. -43 pub(crate) new_round: VarInt, -44 /// Addresses to use to send reach out frames. -45 pub(crate) reach_out_at: FxHashSet<IpPort>, -46 /// Remotes to probe by attempting to open new paths. -47 /// -48 /// The addresses include their Id, so that it can be used to signal these should be returned -49 /// in a nat traversal continuation by calling [`ClientState::report_in_continuation`]. -50 /// -51 /// These are filtered and mapped to the IP family the local socket supports. -52 pub(crate) addresses_to_probe: Vec<(VarInt, IpPort)>, -53 /// [`PathId`]s of the cancelled round. -54 pub(crate) prev_round_path_ids: Vec<PathId>, -55} -56 -57/// Event emitted when the client receives ADD_ADDRESS or REMOVE_ADDRESS frames. -58#[derive(Debug, Clone)] -59pub enum Event { -60 /// An ADD_ADDRESS frame was received. -61 AddressAdded(SocketAddr), -62 /// A REMOVE_ADDRESS frame was received. -63 AddressRemoved(SocketAddr), -64} -65 -66/// State kept for n0's nat traversal -67#[derive(Debug, Default)] -68pub(crate) enum State { -69 #[default] -70 NotNegotiated, -71 ClientSide(ClientState), -72 ServerSide(ServerState), -73} -74 -75impl State { -76 pub(crate) fn new(max_remote_addresses: u8, max_local_addresses: u8, side: Side) -> Self { -77 match side { -78 Side::Client => Self::ClientSide(ClientState::new( -79 max_remote_addresses.into(), -80 max_local_addresses.into(), -81 )), -82 Side::Server => Self::ServerSide(ServerState::new( -83 max_remote_addresses.into(), -84 max_local_addresses.into(), -85 )), +12 FourTuple, Side, VarInt, +13 connection::spaces::PendingReachOutFrames, +14 frame::{AddAddress, ReachOut, RemoveAddress}, +15}; +16 +17type IpPort = (IpAddr, u16); +18 +19/// Errors that the nat traversal state might encounter. +20#[derive(Debug, thiserror::Error)] +21pub enum Error { +22 /// An endpoint (local or remote) tried to add too many addresses to their advertised set +23 #[error("Tried to add too many addresses to their advertised set")] +24 TooManyAddresses, +25 /// The operation is not allowed for this endpoint's connection side +26 #[error("Not allowed for this endpoint's connection side")] +27 WrongConnectionSide, +28 /// The extension was not negotiated +29 #[error("n0's nat traversal was not negotiated")] +30 ExtensionNotNegotiated, +31 /// Not enough addresses to complete the operation +32 #[error("Not enough addresses")] +33 NotEnoughAddresses, +34 /// Nat traversal attempt failed due to a multipath error +35 #[error("Failed to establish paths {0}")] +36 Multipath(super::PathError), +37 /// Attempted to initiate NAT traversal on a closed, or closing connection. +38 #[error("The connection is already closed")] +39 Closed, +40} +41 +42/// Event emitted when the client receives ADD_ADDRESS or REMOVE_ADDRESS frames. +43#[derive(Debug, Clone)] +44pub enum Event { +45 /// An ADD_ADDRESS frame was received. +46 AddressAdded(SocketAddr), +47 /// A REMOVE_ADDRESS frame was received. +48 AddressRemoved(SocketAddr), +49} +50 +51/// State kept for n0's nat traversal +52#[derive(Debug, Default)] +53pub(crate) enum State { +54 #[default] +55 NotNegotiated, +56 ClientSide(ClientState), +57 ServerSide(ServerState), +58} +59 +60impl State { +61 pub(crate) fn new(max_remote_addresses: u8, max_local_addresses: u8, side: Side) -> Self { +62 match side { +63 Side::Client => Self::ClientSide(ClientState::new( +64 max_remote_addresses.into(), +65 max_local_addresses.into(), +66 )), +67 Side::Server => Self::ServerSide(ServerState::new( +68 max_remote_addresses.into(), +69 max_local_addresses.into(), +70 )), +71 } +72 } +73 +74 pub(crate) fn is_negotiated(&self) -> bool { +75 match self { +76 Self::NotNegotiated => false, +77 Self::ClientSide(_) | Self::ServerSide(_) => true, +78 } +79 } +80 +81 pub(crate) fn client_side(&self) -> Result<&ClientState, Error> { +82 match self { +83 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), +84 Self::ClientSide(client_side) => Ok(client_side), +85 Self::ServerSide(_) => Err(Error::WrongConnectionSide), 86 } 87 } 88 -89 pub(crate) fn is_negotiated(&self) -> bool { +89 pub(crate) fn client_side_mut(&mut self) -> Result<&mut ClientState, Error> { 90 match self { -91 Self::NotNegotiated => false, -92 Self::ClientSide(_) | Self::ServerSide(_) => true, -93 } -94 } -95 -96 pub(crate) fn client_side(&self) -> Result<&ClientState, Error> { -97 match self { -98 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), -99 Self::ClientSide(client_side) => Ok(client_side), -100 Self::ServerSide(_) => Err(Error::WrongConnectionSide), -101 } -102 } -103 -104 pub(crate) fn client_side_mut(&mut self) -> Result<&mut ClientState, Error> { -105 match self { -106 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), -107 Self::ClientSide(client_side) => Ok(client_side), -108 Self::ServerSide(_) => Err(Error::WrongConnectionSide), -109 } -110 } -111 -112 pub(crate) fn server_side_mut(&mut self) -> Result<&mut ServerState, Error> { -113 match self { -114 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), -115 Self::ClientSide(_) => Err(Error::WrongConnectionSide), -116 Self::ServerSide(server_side) => Ok(server_side), -117 } -118 } -119 -120 /// Adds a local address to use for nat traversal. -121 /// -122 /// When this endpoint is the server within the connection, these addresses will be sent to the -123 /// client in add address frames. For clients, these addresses will be sent in reach out frames -124 /// when nat traversal attempts are initiated. -125 /// -126 /// If a frame should be sent, it is returned. -127 pub(crate) fn add_local_address( -128 &mut self, -129 address: SocketAddr, -130 ) -> Result<Option<AddAddress>, Error> { -131 let ip_port = IpPort::from((address.ip(), address.port())); -132 match self { -133 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), -134 Self::ClientSide(client_state) => { -135 client_state.add_local_address(ip_port)?; -136 Ok(None) -137 } -138 Self::ServerSide(server_state) => server_state.add_local_address(ip_port), -139 } -140 } -141 -142 /// Removes a local address from the advertised set for nat traversal. -143 /// -144 /// When this endpoint is the server, removed addresses must be reported with remove address -145 /// frames. Clients will simply stop reporting these addresses in reach out frames. -146 /// -147 /// If a frame should be sent, it is returned. -148 pub(crate) fn remove_local_address( -149 &mut self, -150 address: SocketAddr, -151 ) -> Result<Option<RemoveAddress>, Error> { -152 let address = IpPort::from((address.ip(), address.port())); -153 match self { -154 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), -155 Self::ClientSide(client_state) => { -156 client_state.remove_local_address(&address); -157 Ok(None) -158 } -159 Self::ServerSide(server_state) => Ok(server_state.remove_local_address(&address)), -160 } -161 } -162 -163 pub(crate) fn get_local_nat_traversal_addresses(&self) -> Result<Vec<SocketAddr>, Error> { -164 match self { -165 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), -166 Self::ClientSide(client_state) => Ok(client_state -167 .local_addresses -168 .iter() -169 .copied() -170 .map(Into::into) -171 .collect()), -172 Self::ServerSide(server_state) => Ok(server_state -173 .local_addresses -174 .keys() -175 .copied() -176 .map(Into::into) -177 .collect()), -178 } -179 } -180} -181 -182#[derive(Debug)] -183pub(crate) struct ClientState { -184 /// Max number of remote addresses we allow -185 /// -186 /// This is set by the local endpoint. -187 max_remote_addresses: usize, -188 /// Max number of local addresses allowed -189 /// -190 /// This is set by the remote endpoint. -191 max_local_addresses: usize, -192 /// Candidate addresses the remote server reports as potentially reachable, to use for nat -193 /// traversal attempts. -194 /// -195 /// These are indexed by their advertised Id. For each address, whether the address should be -196 /// reported in nat traversal continuations is kept. -197 remote_addresses: FxHashMap<VarInt, (IpPort, bool)>, -198 /// Candidate addresses the local client reports as potentially reachable, to use for nat -199 /// traversal attempts. -200 local_addresses: FxHashSet<IpPort>, -201 /// Current nat traversal round. -202 round: VarInt, -203 /// [`PathId`]s used to probe remotes assigned to this round. -204 round_path_ids: Vec<PathId>, -205} -206 -207impl ClientState { -208 fn new(max_remote_addresses: usize, max_local_addresses: usize) -> Self { -209 Self { -210 max_remote_addresses, -211 max_local_addresses, -212 remote_addresses: Default::default(), -213 local_addresses: Default::default(), -214 round: Default::default(), -215 round_path_ids: Default::default(), -216 } -217 } -218 -219 fn add_local_address(&mut self, address: IpPort) -> Result<(), Error> { -220 if self.local_addresses.len() < self.max_local_addresses { -221 self.local_addresses.insert(address); -222 Ok(()) -223 } else if self.local_addresses.contains(&address) { -224 // at capacity, but the address is known, no issues here -225 Ok(()) -226 } else { -227 // at capacity and the address is new -228 Err(Error::TooManyAddresses) -229 } -230 } -231 -232 fn remove_local_address(&mut self, address: &IpPort) { -233 self.local_addresses.remove(address); -234 } -235 -236 /// Initiates a new nat traversal round. -237 /// -238 /// A nat traversal round involves advertising the client's local addresses in `REACH_OUT` -239 /// frames, and initiating probing of the known remote addresses. When a new round is -240 /// initiated, the previous one is cancelled, and paths that have not been opened should be -241 /// closed. -242 /// -243 /// `ipv6` indicates if the connection runs on a socket that supports IPv6. If so, then all -244 /// addresses returned in [`NatTraversalRound`] will be IPv6 addresses (and IPv4-mapped IPv6 -245 /// addresses if necessary). Otherwise they're all IPv4 addresses. -246 /// See also [`map_to_local_socket_family`]. -247 pub(crate) fn initiate_nat_traversal_round( -248 &mut self, -249 ipv6: bool, -250 ) -> Result<NatTraversalRound, Error> { -251 if self.local_addresses.is_empty() { -252 return Err(Error::NotEnoughAddresses); -253 } -254 -255 let prev_round_path_ids = std::mem::take(&mut self.round_path_ids); -256 self.round = self.round.saturating_add(1u8); -257 let mut addresses_to_probe = Vec::with_capacity(self.remote_addresses.len()); -258 for (id, ((ip, port), report_in_continuation)) in self.remote_addresses.iter_mut() { -259 *report_in_continuation = false; -260 -261 if let Some(ip) = map_to_local_socket_family(*ip, ipv6) { -262 addresses_to_probe.push((*id, (ip, *port))); -263 } else { -264 trace!(?ip, "not using IPv6 nat candidate for IPv4 socket"); -265 } -266 } -267 -268 Ok(NatTraversalRound { -269 new_round: self.round, -270 reach_out_at: self.local_addresses.iter().copied().collect(), -271 addresses_to_probe, -272 prev_round_path_ids, -273 }) -274 } -275 -276 /// Mark a remote address to be reported back in a nat traversal continuation if the error is -277 /// considered spurious from a nat traversal point of view. -278 /// -279 /// Ids not present are silently ignored. -280 pub(crate) fn report_in_continuation(&mut self, id: VarInt, e: crate::PathError) { -281 match e { -282 crate::PathError::MaxPathIdReached | crate::PathError::RemoteCidsExhausted => { -283 if let Some((_address, report_in_continuation)) = self.remote_addresses.get_mut(&id) -284 { -285 *report_in_continuation = true; -286 } -287 } -288 _ => {} -289 } -290 } -291 -292 /// Returns an address that needs to be probed, if any. -293 /// -294 /// The address will not be returned twice unless marked as such again with -295 /// [`Self::report_in_continuation`]. -296 /// -297 /// `ipv6` indicates if the connection runs on a socket that supports IPv6. If so, then all -298 /// addresses returned in [`NatTraversalRound`] will be IPv6 addresses (and IPv4-mapped IPv6 -299 /// addresses if necessary). Otherwise they're all IPv4 addresses. -300 /// See also [`map_to_local_socket_family`]. -301 pub(crate) fn continue_nat_traversal_round(&mut self, ipv6: bool) -> Option<(VarInt, IpPort)> { -302 // this being random depends on iteration not returning always on the same order -303 let (id, (address, report_in_continuation)) = self -304 .remote_addresses -305 .iter_mut() -306 .filter(|(_id, (_addr, report))| *report) -307 .filter_map(|(id, ((ip, port), report))| { -308 // only continue with addresses we can send on our local socket -309 let Some(ip) = map_to_local_socket_family(*ip, ipv6) else { -310 trace!(?ip, "not using IPv6 nat candidate for IPv4 socket"); -311 return None; -312 }; -313 Some((*id, ((ip, *port), report))) -314 }) -315 .next()?; -316 *report_in_continuation = false; -317 Some((id, address)) -318 } -319 -320 /// Add a [`PathId`] as part of the current attempts to create paths based on the server's -321 /// advertised addresses. -322 pub(crate) fn set_round_path_ids(&mut self, path_ids: Vec<PathId>) { -323 self.round_path_ids = path_ids; -324 } -325 -326 /// Add a [`PathId`] as part of the current attempts to create paths based on the server's -327 /// advertised addresses. -328 pub(crate) fn add_round_path_id(&mut self, path_id: PathId) { -329 self.round_path_ids.push(path_id); -330 } -331 -332 /// Adds an address to the remote set -333 /// -334 /// On success returns the address if it was new to the set. It will error when the set has no -335 /// capacity for the address. -336 pub(crate) fn add_remote_address( -337 &mut self, -338 add_addr: AddAddress, -339 ) -> Result<Option<SocketAddr>, Error> { -340 let AddAddress { seq_no, ip, port } = add_addr; -341 let address = (ip, port); -342 let allow_new = self.remote_addresses.len() < self.max_remote_addresses; -343 match self.remote_addresses.entry(seq_no) { -344 Entry::Occupied(mut occupied_entry) => { -345 let is_update = occupied_entry.get().0 != address; -346 if is_update { -347 occupied_entry.insert((address, false)); -348 } -349 // The value might be different. This should not happen, but we assume that the new -350 // address is more recent than the previous, and thus worth updating -351 Ok(is_update.then_some(address.into())) -352 } -353 Entry::Vacant(vacant_entry) if allow_new => { -354 vacant_entry.insert((address, false)); -355 Ok(Some(address.into())) -356 } -357 _ => Err(Error::TooManyAddresses), -358 } -359 } -360 -361 /// Removes an address from the remote set -362 /// -363 /// Returns whether the address was present. -364 pub(crate) fn remove_remote_address( -365 &mut self, -366 remove_addr: RemoveAddress, -367 ) -> Option<SocketAddr> { -368 self.remote_addresses -369 .remove(&remove_addr.seq_no) -370 .map(|(address, _report_in_continuation)| address.into()) -371 } -372 -373 /// Checks that a received remote address is valid -374 /// -375 /// An address is valid as long as it does not change the value of a known address id. -376 pub(crate) fn check_remote_address(&self, add_addr: &AddAddress) -> bool { -377 match self.remote_addresses.get(&add_addr.seq_no) { -378 None => true, -379 Some((existing, _)) => existing == &add_addr.ip_port(), -380 } -381 } -382 -383 pub(crate) fn get_remote_nat_traversal_addresses(&self) -> Vec<SocketAddr> { -384 self.remote_addresses -385 .values() -386 .map(|(address, _report_in_continuation)| (*address).into()) -387 .collect() +91 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), +92 Self::ClientSide(client_side) => Ok(client_side), +93 Self::ServerSide(_) => Err(Error::WrongConnectionSide), +94 } +95 } +96 +97 pub(crate) fn server_side_mut(&mut self) -> Result<&mut ServerState, Error> { +98 match self { +99 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), +100 Self::ClientSide(_) => Err(Error::WrongConnectionSide), +101 Self::ServerSide(server_side) => Ok(server_side), +102 } +103 } +104 +105 /// Adds a local address to use for nat traversal. +106 /// +107 /// When this endpoint is the server within the connection, these addresses will be sent to the +108 /// client in add address frames. For clients, these addresses will be sent in reach out frames +109 /// when nat traversal attempts are initiated. +110 /// +111 /// If a frame should be sent, it is returned. +112 pub(crate) fn add_local_address( +113 &mut self, +114 address: SocketAddr, +115 ) -> Result<Option<AddAddress>, Error> { +116 match self { +117 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), +118 Self::ClientSide(client_state) => { +119 client_state.add_local_address(address)?; +120 Ok(None) +121 } +122 Self::ServerSide(server_state) => server_state.add_local_address(address), +123 } +124 } +125 +126 /// Removes a local address from the advertised set for nat traversal. +127 /// +128 /// When this endpoint is the server, removed addresses must be reported with remove address +129 /// frames. Clients will simply stop reporting these addresses in reach out frames. +130 /// +131 /// If a frame should be sent, it is returned. +132 pub(crate) fn remove_local_address( +133 &mut self, +134 address: SocketAddr, +135 ) -> Result<Option<RemoveAddress>, Error> { +136 let address = IpPort::from((address.ip(), address.port())); +137 match self { +138 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), +139 Self::ClientSide(client_state) => { +140 client_state.remove_local_address(&address); +141 Ok(None) +142 } +143 Self::ServerSide(server_state) => Ok(server_state.remove_local_address(&address)), +144 } +145 } +146 +147 pub(crate) fn get_local_nat_traversal_addresses(&self) -> Result<Vec<SocketAddr>, Error> { +148 match self { +149 Self::NotNegotiated => Err(Error::ExtensionNotNegotiated), +150 Self::ClientSide(client_state) => Ok(client_state +151 .local_addresses +152 .iter() +153 .copied() +154 .map(Into::into) +155 .collect()), +156 Self::ServerSide(server_state) => Ok(server_state +157 .local_addresses +158 .keys() +159 .copied() +160 .map(Into::into) +161 .collect()), +162 } +163 } +164 +165 /// Returns the next ready probe's address. +166 /// +167 /// If this is actually sent you must call [`Self::mark_probe_sent`]. +168 pub(crate) fn next_probe_addr(&self) -> Option<SocketAddr> { +169 match self { +170 Self::NotNegotiated => None, +171 Self::ClientSide(state) => state.next_probe_addr(), +172 Self::ServerSide(state) => state.next_probe_addr(), +173 } +174 } +175 +176 /// Marks a probe as sent to the address with the challenge. +177 pub(crate) fn mark_probe_sent(&mut self, remote: IpPort, challenge: u64) { +178 match self { +179 Self::NotNegotiated => (), +180 Self::ClientSide(state) => state.mark_probe_sent(remote, challenge), +181 Self::ServerSide(state) => state.mark_probe_sent(remote, challenge), +182 } +183 } +184 +185 /// Re-queues probes that have not yet succeeded or reached 0 remaining retries. +186 /// +187 /// Returns whether any probes are now queued to send. In this case the +188 /// `NatTraversalProbeRetry` timer needs to be reset. +189 pub(crate) fn queue_retries(&mut self, ipv6: bool) -> bool { +190 match self { +191 Self::NotNegotiated => false, +192 Self::ClientSide(state) => state.queue_retries(ipv6), +193 Self::ServerSide(state) => state.queue_retries(), +194 } +195 } +196 +197 /// Marks a remote as successful if the response matches a sent probe. +198 /// +199 /// Returns the open network path if it was a response to one of the NAT traversal +200 /// probes. Note that the NAT probes are not padded to 1200 bytes so only the address is +201 /// validated, but not the entire path. +202 pub(crate) fn handle_path_response(&mut self, src: FourTuple, challenge: u64) -> bool { +203 match self { +204 Self::NotNegotiated => false, +205 Self::ClientSide(state) => state.handle_path_response(src, challenge), +206 Self::ServerSide(state) => state.handle_path_response(src, challenge), +207 } +208 } +209} +210 +211#[derive(Debug)] +212pub(crate) struct ClientState { +213 /// Max number of remote addresses we allow +214 /// +215 /// This is set by the local endpoint. +216 max_remote_addresses: usize, +217 /// Max number of local addresses allowed +218 /// +219 /// This is set by the remote endpoint. +220 max_local_addresses: usize, +221 /// Candidate addresses the remote endpoint advertises. +222 /// +223 /// These are addresses on which the server is potentially reachable, to use for NAT +224 /// traversal attempts. +225 /// +226 /// They are indexed by their ADD_ADDRESS sequence id and stored in **canonical +227 /// form**. Not in the socket-native form as usual. This because we need to store them +228 /// so we have the correct sequence IDs. +229 remote_addresses: FxHashMap<VarInt, (IpPort, ProbeState)>, +230 /// Candidate addresses for the local endpoint. +231 /// +232 /// These are addresses on which we are potentially reachable, to use for NAT traversal +233 /// attempts. +234 /// +235 /// They are stored in **canonical form**, not in socket-native form as usual. We may +236 /// nave a reflexive address that is IPv6 even if our local socket can only handle IPv4. +237 local_addresses: FxHashSet<IpPort>, +238 /// Current nat traversal round. +239 round: VarInt, +240 /// The data of PATH_CHALLENGE frames sent in probes. +241 /// +242 /// These are cleared when a new round starts, so any late-arriving PATH_RESPONSEs will +243 /// have no effect. +244 /// +245 /// They are stored in the usual socket-native form. +246 sent_challenges: FxHashMap<u64, IpPort>, +247 /// Queued probes to be sent in the next [`poll_transmit`] call. +248 /// +249 /// [`poll_transmit`]: crate::connection::Connection::poll_transmit +250 /// +251 /// They are stored in the usual socket-native form. Probes to address families not +252 /// addressable by the family are never inserted. +253 pending_probes: FxHashSet<IpPort>, +254 /// Network paths that were successfully probed but not yet opened. +255 /// +256 /// When we do not have enough CIDs or free path IDs we might not have been able to open +257 /// a new path. This allows us to try re-open the path when we get new CIDs or a new +258 /// MAX_PATH_ID. +259 // TODO(flub): perhaps there should be a time-limit on these? +260 paths_to_be_opened: Vec<FourTuple>, +261} +262 +263impl ClientState { +264 fn new(max_remote_addresses: usize, max_local_addresses: usize) -> Self { +265 Self { +266 max_remote_addresses, +267 max_local_addresses, +268 remote_addresses: Default::default(), +269 local_addresses: Default::default(), +270 round: Default::default(), +271 sent_challenges: Default::default(), +272 pending_probes: Default::default(), +273 paths_to_be_opened: Default::default(), +274 } +275 } +276 +277 fn add_local_address(&mut self, address: SocketAddr) -> Result<(), Error> { +278 let address = (address.ip().to_canonical(), address.port()); +279 if self.local_addresses.len() < self.max_local_addresses { +280 self.local_addresses.insert(address); +281 Ok(()) +282 } else if self.local_addresses.contains(&address) { +283 // at capacity, but the address is known, no issues here +284 Ok(()) +285 } else { +286 // at capacity and the address is new +287 Err(Error::TooManyAddresses) +288 } +289 } +290 +291 fn remove_local_address(&mut self, address: &IpPort) { +292 let address = (address.0.to_canonical(), address.1); +293 self.local_addresses.remove(&address); +294 } +295 +296 /// Initiates a new nat traversal round. +297 /// +298 /// A nat traversal round involves advertising the client's local addresses in +299 /// `REACH_OUT` frames, and initiating probing of the known remote addresses. When a new +300 /// round is initiated, the previous one is cancelled. +301 /// +302 /// `ipv6` indicates if the connection runs on a socket that supports IPv6. If so, then +303 /// all addresses returned [`PendingReachOutFrames`] will be IPv6 addresses (and +304 /// IPv4-mapped IPv6 addresses if necessary). Otherwise they're all IPv4 addresses. See +305 /// also [`map_to_local_socket_family`]. +306 /// +307 /// # Returns +308 /// +309 /// The REACH_OUT frames that need to be sent to the peer and the probed addresses. The +310 /// probed addresses are only informational, the pending probes are stored in +311 /// [`Self::pending_probes`]. +312 /// +313 /// If the probed addresses are non-empty the `NatTraversalProbeRetry` timer needs to be +314 /// set. +315 pub(crate) fn initiate_nat_traversal_round( +316 &mut self, +317 ipv6: bool, +318 ) -> Result<(PendingReachOutFrames, Vec<SocketAddr>), Error> { +319 if self.local_addresses.is_empty() { +320 return Err(Error::NotEnoughAddresses); +321 } +322 +323 self.round = self.round.saturating_add(1u8); +324 self.sent_challenges.clear(); +325 self.pending_probes.clear(); +326 +327 // Enqueue the NAT probes to known remote addresses. +328 self.remote_addresses +329 .values_mut() +330 .for_each(|((ip, port), state)| { +331 if let Some(ip) = map_to_local_socket_family(*ip, ipv6) { +332 self.pending_probes.insert((ip, *port)); +333 *state = ProbeState::Active(MAX_NAT_PROBE_ATTEMPTS - 1); +334 } else { +335 trace!(?ip, "not using IPv6 NAT candidate for IPv4 socket"); +336 *state = ProbeState::Active(0); +337 } +338 }); +339 let probed_addrs: Vec<SocketAddr> = self +340 .pending_probes +341 .iter() +342 .copied() +343 .map(Into::into) +344 .collect(); +345 +346 // Build the REACH_OUT frames. +347 let reach_out_frames: PendingReachOutFrames = self +348 .local_addresses +349 .iter() +350 .map(|&(ip, port)| ReachOut { +351 round: self.round, +352 ip, +353 port, +354 }) +355 .collect(); +356 +357 trace!( +358 round = %self.round, +359 reach_out = %reach_out_frames.len(), +360 to_probe = %self.pending_probes.len(), +361 "initiating NAT traversal round", +362 ); +363 Ok((reach_out_frames, probed_addrs)) +364 } +365 +366 /// Re-queues probes that have not yet succeeded or reached 0 remaining retries. +367 /// +368 /// Returns whether any probes are now queued to send. In this case the +369 /// `NatTraversalProbeRetry` timer needs to be reset. +370 /// +371 /// `ipv6` as for [`Self::initiate_nat_traversal_round`]. +372 pub(crate) fn queue_retries(&mut self, ipv6: bool) -> bool { +373 self.remote_addresses +374 .values_mut() +375 .for_each(|(addr, state)| match state { +376 ProbeState::Active(remaining) if *remaining > 0 => { +377 *remaining -= 1; +378 if let Some(ip) = map_to_local_socket_family(addr.0, ipv6) { +379 self.pending_probes.insert((ip, addr.1)); +380 } else { +381 trace!(?addr, "skipping IPv6 NAT candidate for IPv4 socket"); +382 *remaining = 0; +383 } +384 } +385 ProbeState::Active(_) | ProbeState::Succeeded => {} +386 }); +387 !self.pending_probes.is_empty() 388 } -389} -390 -391/// Maximum number of times we send a NAT probe to the same remote address in a round. -392/// -393/// This is a trade-off between several factors: -394/// - Probe packets could be lost. This allows recovery. -395/// - We may need two probes to reach the NAT firewall to get through. -396/// - We may be sending probes to innocent bystanders on the internet. -397/// - A round never "finishes": probing of remotes only stops when: -398/// 1. A new round is started. -399/// 2. A probe was successful. -400/// 3. This number of attempts is exhausted. -401/// -402/// Currently probes are retried after 2/3rd of the configured initial RTT. At a -403/// fixed interval, so without exponential backoff. For the default initial RTT of 333ms -404/// this is 222ms. So 10 attempts covers 2220ms. -405// TODO(flub): I would like to improve this sometime so that we cover about 2s but with only -406// about 5-6 probes. The three initial probes should be faster, later probes should start -407// to slow down. Unfortunately we only have one timer for the entire round currently, we -408// would need to have a timer per remote. Because REACH_OUT frames can appear in the -409// middle of a round. -410pub(crate) const MAX_NAT_PROBE_ATTEMPTS: u8 = 10; -411 -412/// State of an off-path NAT traversal probe to a remote address. -413#[derive(Debug)] -414enum ProbeState { -415 /// The remote still needs to be probed in this round. -416 /// -417 /// The remaining number of retries are stored in the `u8`. -418 Active(u8), -419 /// We received a probe response for this remote. -420 Succeeded, -421} -422 -423#[derive(Debug)] -424pub(crate) struct ServerState { -425 /// Max number of remote addresses we allow. -426 /// -427 /// This is set by the local endpoint. -428 max_remote_addresses: usize, -429 /// Max number of local addresses allowed. -430 /// -431 /// This is set by the remote endpoint. -432 max_local_addresses: usize, -433 /// Candidate addresses the server reports as potentially reachable, to use for nat -434 /// traversal attempts. -435 local_addresses: FxHashMap<IpPort, VarInt>, -436 /// The next id to use for local addresses sent to the client. -437 next_local_addr_id: VarInt, -438 /// Current nat traversal round -439 /// -440 /// Servers keep track of the client's most recent round and cancel probing related to previous -441 /// rounds. -442 round: VarInt, -443 /// The remote addresses participating in this round. -444 /// -445 /// The set is cleared when a new round starts. -446 remotes: FxHashMap<IpPort, ProbeState>, -447 /// The data of PATH_CHALLENGE frames sent in probes. -448 /// -449 /// These are cleared when a new round starts, so any late-arriving PATH_RESPONSEs will -450 /// have no effect. -451 sent_challenges: FxHashMap<u64, IpPort>, -452 /// Queued probes to be sent in the next [`poll_transmit`] call. -453 /// -454 /// At the beginning of a round this is populated from REACH_OUT frames and at every -455 /// retry this is populated from [`Self::remotes`]. -456 /// -457 /// [`poll_transmit`]: crate::connection::Connection::poll_transmit -458 pending_probes: FxHashSet<IpPort>, -459} +389 +390 /// Returns the next ready probe's address. +391 /// +392 /// If this is actually sent you must call [`Self::mark_probe_sent`]. +393 fn next_probe_addr(&self) -> Option<SocketAddr> { +394 self.pending_probes.iter().next().map(|addr| (*addr).into()) +395 } +396 +397 /// Marks a probe as sent to the address with the challenge. +398 fn mark_probe_sent(&mut self, remote: IpPort, challenge: u64) { +399 self.pending_probes.remove(&remote); +400 self.sent_challenges.insert(challenge, remote); +401 } +402 +403 /// Adds an address to the remote set +404 /// +405 /// On success returns the address if it was new to the set. It will error when the set +406 /// has no capacity for the address. +407 /// +408 /// If this is called while a round is in progress this will effectively add the address +409 /// to the current round. There is no guarantee however that the current round is still +410 /// in progress however, if the last [`Self::queue_retries`] call returned `false` the +411 /// round has stopped. +412 // TODO(flub): probably should add an event to signal that the round is finished, so +413 // that the application knows to start a new round. +414 pub(crate) fn add_remote_address( +415 &mut self, +416 add_addr: AddAddress, +417 ) -> Result<Option<SocketAddr>, Error> { +418 let AddAddress { seq_no, ip, port } = add_addr; +419 let address = (ip.to_canonical(), port); +420 let allow_new = self.remote_addresses.len() < self.max_remote_addresses; +421 match self.remote_addresses.entry(seq_no) { +422 Entry::Occupied(mut occupied_entry) => { +423 let is_update = occupied_entry.get().0 != address; +424 if is_update { +425 occupied_entry.insert((address, ProbeState::Active(MAX_NAT_PROBE_ATTEMPTS))); +426 } +427 // The value might be different. This should not happen, but we assume that the new +428 // address is more recent than the previous, and thus worth updating +429 Ok(is_update.then_some(address.into())) +430 } +431 Entry::Vacant(vacant_entry) if allow_new => { +432 vacant_entry.insert((address, ProbeState::Active(MAX_NAT_PROBE_ATTEMPTS))); +433 Ok(Some(address.into())) +434 } +435 _ => Err(Error::TooManyAddresses), +436 } +437 } +438 +439 /// Removes an address from the remote set. +440 /// +441 /// Returns whether the address was present. +442 pub(crate) fn remove_remote_address( +443 &mut self, +444 remove_addr: RemoveAddress, +445 ) -> Option<SocketAddr> { +446 self.remote_addresses +447 .remove(&remove_addr.seq_no) +448 .map(|(address, _)| address.into()) +449 } +450 +451 /// Checks that a received remote address is valid. +452 /// +453 /// An address is valid as long as it does not change the value of a known address id. +454 pub(crate) fn check_remote_address(&self, add_addr: &AddAddress) -> bool { +455 match self.remote_addresses.get(&add_addr.seq_no) { +456 None => true, +457 Some((existing, _)) => existing == &add_addr.ip_port(), +458 } +459 } 460 -461impl ServerState { -462 fn new(max_remote_addresses: usize, max_local_addresses: usize) -> Self { -463 Self { -464 max_remote_addresses, -465 max_local_addresses, -466 local_addresses: Default::default(), -467 next_local_addr_id: Default::default(), -468 round: Default::default(), -469 remotes: Default::default(), -470 sent_challenges: Default::default(), -471 pending_probes: Default::default(), -472 } -473 } -474 -475 fn add_local_address(&mut self, address: IpPort) -> Result<Option<AddAddress>, Error> { -476 let allow_new = self.local_addresses.len() < self.max_local_addresses; -477 match self.local_addresses.entry(address) { -478 Entry::Occupied(_) => Ok(None), -479 Entry::Vacant(vacant_entry) if allow_new => { -480 let id = self.next_local_addr_id; -481 self.next_local_addr_id = self.next_local_addr_id.saturating_add(1u8); -482 vacant_entry.insert(id); -483 Ok(Some(AddAddress::new(address, id))) -484 } -485 _ => Err(Error::TooManyAddresses), -486 } -487 } -488 -489 fn remove_local_address(&mut self, address: &IpPort) -> Option<RemoveAddress> { -490 self.local_addresses.remove(address).map(RemoveAddress::new) -491 } -492 -493 /// Returns the current NAT traversal round number. -494 pub(crate) fn current_round(&self) -> VarInt { -495 self.round -496 } -497 -498 /// Handles a received REACH_OUT frame. -499 /// -500 /// This might ignore the reach out frame if it belongs to an older round or if the -501 /// frame contains an IPv6 address while the local socket is IPv4-only. -502 /// -503 /// If a new round was started, the `NatTraversalProbeRetry` timer needs to be reset. -504 pub(crate) fn handle_reach_out( -505 &mut self, -506 reach_out: ReachOut, -507 ipv6: bool, -508 ) -> Result<(), Error> { -509 let ReachOut { round, ip, port } = reach_out; -510 -511 if round < self.round { -512 trace!(current_round=%self.round, "ignoring REACH_OUT for previous round"); -513 return Ok(()); -514 } -515 let Some(ip) = map_to_local_socket_family(ip, ipv6) else { -516 trace!("Ignoring IPv6 REACH_OUT frame due to not supporting IPv6 locally"); -517 return Ok(()); -518 }; +461 pub(crate) fn get_remote_nat_traversal_addresses(&self) -> Vec<SocketAddr> { +462 self.remote_addresses +463 .values() +464 .map(|(address, _)| (*address).into()) +465 .collect() +466 } +467 +468 /// Marks a remote as successful if the response matches a sent probe. +469 /// +470 /// Returns `true` if it was a response to one of the NAT traversal probes. In that case +471 /// [`Self::pop_pending_path_open`] should be called to open the next path. +472 fn handle_path_response(&mut self, network_path: FourTuple, challenge: u64) -> bool { +473 if let Entry::Occupied(entry) = self.sent_challenges.entry(challenge) { +474 let remote = (network_path.remote().ip(), network_path.remote().port()); +475 if *entry.get() == remote { +476 entry.remove(); +477 +478 // self.remote_addresses is stored in canonical form. +479 let remote = (remote.0.to_canonical(), remote.1); +480 // TODO: linear search is sad. +481 if let Some(seq) = self +482 .remote_addresses +483 .iter() +484 .filter_map( +485 |(seq, (addr, _))| { +486 if *addr == remote { Some(*seq) } else { None } +487 }, +488 ) +489 .next() +490 { +491 trace!( +492 ?network_path, +493 challenge = %display(format_args!("0x{challenge:x}")), +494 "Received valid NAT traversal probe response", +495 ); +496 self.remote_addresses +497 .insert(seq, (remote, ProbeState::Succeeded)); +498 self.paths_to_be_opened.push(network_path); +499 return true; +500 } else { +501 debug!("inconsistent remote addrs and seq"); +502 } +503 } else { +504 debug!( +505 ?network_path.remote, +506 expected_remote = ?entry.get(), +507 challenge = %display(format_args!("0x{challenge:x}")), +508 "PATH_RESPONSE matched a NAT traversal probe but mismatching addr XXXX", +509 ) +510 } +511 } +512 false +513 } +514 +515 /// Returns a path that was NAT traversed and needs to be opened. +516 pub(crate) fn pop_pending_path_open(&mut self) -> Option<FourTuple> { +517 self.paths_to_be_opened.pop() +518 } 519 -520 if round > self.round { -521 self.round = round; -522 self.remotes.clear(); -523 self.sent_challenges.clear(); -524 self.pending_probes.clear(); -525 } else if self.remotes.contains_key(&(ip, port)) { -526 // Retransmitted frame. -527 return Ok(()); -528 } else if self.remotes.len() >= self.max_remote_addresses { -529 return Err(Error::TooManyAddresses); -530 } -531 self.remotes -532 .entry((ip, port)) -533 .or_insert(ProbeState::Active(MAX_NAT_PROBE_ATTEMPTS - 1)); -534 self.pending_probes.insert((ip, port)); -535 Ok(()) -536 } -537 -538 /// Re-queues probes that have not yet succeeded or reached [`MAX_NAT_PROBE_ATTEMPTS`]. -539 /// -540 /// Returns whether any probes are now queued to send. In this case the -541 /// `NatTraversalProbeRetry` timer needs to be reset. -542 pub(crate) fn queue_retries(&mut self) -> bool { -543 self.remotes -544 .iter_mut() -545 .for_each(|(remote, state)| match state { -546 ProbeState::Active(remaining) if *remaining > 0 => { -547 *remaining -= 1; -548 self.pending_probes.insert(*remote); -549 } -550 ProbeState::Active(_) | ProbeState::Succeeded => (), -551 }); -552 !self.pending_probes.is_empty() -553 } -554 -555 /// Returns the next ready probe's address. -556 /// -557 /// If this is actually sent you must call [`Self::mark_probe_sent`]. -558 pub(crate) fn next_probe_addr(&self) -> Option<SocketAddr> { -559 self.pending_probes.iter().next().map(|addr| (*addr).into()) -560 } -561 -562 /// Marks a probe as sent to the address with the challenge. -563 pub(crate) fn mark_probe_sent(&mut self, remote: IpPort, challenge: u64) { -564 self.pending_probes.remove(&remote); -565 self.sent_challenges.insert(challenge, remote); -566 } -567 -568 /// Marks a remote as successful if the response matches a sent probe. -569 /// -570 /// Returns `true` if it was a response to one of the NAT traversal probes. -571 pub(crate) fn handle_path_response(&mut self, src: FourTuple, challenge: u64) -> bool { -572 if let Entry::Occupied(entry) = self.sent_challenges.entry(challenge) { -573 let remote = (src.remote().ip(), src.remote().port()); -574 if *entry.get() == remote { -575 entry.remove(); -576 self.remotes.insert(remote, ProbeState::Succeeded); -577 return true; -578 } else { -579 debug!( -580 ?challenge, -581 ?src.remote, -582 "PATH_RESPONSE matched a NAT traversal probe but mismatching addr", -583 ) -584 } -585 } -586 false -587 } -588} -589 -590/// Returns the given address as canonicalized IP address. -591/// -592/// This checks that the address family is supported by our local socket. -593/// If it is supported, then the address is mapped to the respective IP address. -594/// If the given address is an IPv6 address, but our local socket doesn't support -595/// IPv6, then this returns `None`. -596pub(crate) fn map_to_local_socket_family(address: IpAddr, ipv6: bool) -> Option<IpAddr> { -597 let ip = match address { -598 IpAddr::V4(addr) if ipv6 => IpAddr::V6(addr.to_ipv6_mapped()), -599 IpAddr::V4(_) => address, -600 IpAddr::V6(_) if ipv6 => address, -601 IpAddr::V6(addr) => IpAddr::V4(addr.to_ipv4_mapped()?), -602 }; -603 Some(ip) -604} -605 -606#[cfg(test)] -607mod tests { -608 use super::*; -609 -610 #[test] -611 fn test_basic_server_state() { -612 let mut state = ServerState::new(2, 2); -613 -614 state -615 .handle_reach_out( -616 ReachOut { -617 round: 1u32.into(), -618 ip: std::net::Ipv4Addr::LOCALHOST.into(), -619 port: 1, -620 }, -621 true, -622 ) -623 .unwrap(); -624 -625 state -626 .handle_reach_out( -627 ReachOut { -628 round: 1u32.into(), -629 ip: "1.1.1.1".parse().unwrap(), //std::net::Ipv4Addr::LOCALHOST.into(), -630 port: 2, -631 }, -632 true, -633 ) -634 .unwrap(); -635 -636 dbg!(&state); -637 assert_eq!(state.pending_probes.len(), 2); +520 /// Put back a path that needs to be opened, e.g. for a temporary failure. +521 pub(crate) fn push_pending_path_open(&mut self, network_path: FourTuple) { +522 self.paths_to_be_opened.push(network_path) +523 } +524} +525 +526/// Maximum number of times we send a NAT probe to the same remote address in a round. +527/// +528/// This is a trade-off between several factors: +529/// - Probe packets could be lost. This allows recovery. +530/// - We may need two probes to reach the NAT firewall to get through. +531/// - We may be sending probes to innocent bystanders on the internet. +532/// - A round never "finishes": probing of remotes only stops when: +533/// 1. A new round is started. +534/// 2. A probe was successful. +535/// 3. This number of attempts is exhausted. +536/// +537/// Currently probes are retried after 2/3rd of the configured initial RTT. At a +538/// fixed interval, so without exponential backoff. For the default initial RTT of 333ms +539/// this is 222ms. So 10 attempts covers 2220ms. +540// TODO(flub): I would like to improve this sometime so that we cover about 2s but with only +541// about 5-6 probes. The three initial probes should be faster, later probes should start +542// to slow down. Unfortunately we only have one timer for the entire round currently, we +543// would need to have a timer per remote. Because REACH_OUT frames can appear in the +544// middle of a round. +545pub(crate) const MAX_NAT_PROBE_ATTEMPTS: u8 = 10; +546 +547/// State of an off-path NAT traversal probe to a remote address. +548#[derive(Debug)] +549enum ProbeState { +550 /// The remote still needs to be probed in this round. +551 /// +552 /// The remaining number of retries are stored in the `u8`. +553 Active(u8), +554 /// We received a probe response for this remote. +555 Succeeded, +556} +557 +558#[derive(Debug)] +559pub(crate) struct ServerState { +560 /// Max number of remote addresses we allow. +561 /// +562 /// This is set by the local endpoint. +563 max_remote_addresses: usize, +564 /// Max number of local addresses allowed. +565 /// +566 /// This is set by the remote endpoint. +567 max_local_addresses: usize, +568 /// Candidate addresses the server reports as potentially reachable, to use for nat +569 /// traversal attempts. +570 /// +571 /// They are stored in **canonical form**, not in socket-native form as usual. We may +572 /// nave a reflexive address that is IPv6 even if our local socket can only handle IPv4. +573 local_addresses: FxHashMap<IpPort, VarInt>, +574 /// The next id to use for local addresses sent to the client. +575 next_local_addr_id: VarInt, +576 /// Current nat traversal round +577 /// +578 /// Servers keep track of the client's most recent round and cancel probing related to previous +579 /// rounds. +580 round: VarInt, +581 /// The remote addresses participating in this round. +582 /// +583 /// The set is cleared when a new round starts. +584 /// +585 /// These are stored in the usual local-socket native form. +586 remotes: FxHashMap<IpPort, ProbeState>, +587 /// The data of PATH_CHALLENGE frames sent in probes. +588 /// +589 /// These are cleared when a new round starts, so any late-arriving PATH_RESPONSEs will +590 /// have no effect. +591 sent_challenges: FxHashMap<u64, IpPort>, +592 /// Queued probes to be sent in the next [`poll_transmit`] call. +593 /// +594 /// At the beginning of a round this is populated from REACH_OUT frames and at every +595 /// retry this is populated from [`Self::remotes`]. +596 /// +597 /// [`poll_transmit`]: crate::connection::Connection::poll_transmit +598 pending_probes: FxHashSet<IpPort>, +599} +600 +601impl ServerState { +602 fn new(max_remote_addresses: usize, max_local_addresses: usize) -> Self { +603 Self { +604 max_remote_addresses, +605 max_local_addresses, +606 local_addresses: Default::default(), +607 next_local_addr_id: Default::default(), +608 round: Default::default(), +609 remotes: Default::default(), +610 sent_challenges: Default::default(), +611 pending_probes: Default::default(), +612 } +613 } +614 +615 fn add_local_address(&mut self, address: SocketAddr) -> Result<Option<AddAddress>, Error> { +616 let address = (address.ip().to_canonical(), address.port()); +617 let allow_new = self.local_addresses.len() < self.max_local_addresses; +618 match self.local_addresses.entry(address) { +619 Entry::Occupied(_) => Ok(None), +620 Entry::Vacant(vacant_entry) if allow_new => { +621 let id = self.next_local_addr_id; +622 self.next_local_addr_id = self.next_local_addr_id.saturating_add(1u8); +623 vacant_entry.insert(id); +624 Ok(Some(AddAddress::new(address, id))) +625 } +626 _ => Err(Error::TooManyAddresses), +627 } +628 } +629 +630 fn remove_local_address(&mut self, address: &IpPort) -> Option<RemoveAddress> { +631 self.local_addresses.remove(address).map(RemoveAddress::new) +632 } +633 +634 /// Returns the current NAT traversal round number. +635 pub(crate) fn current_round(&self) -> VarInt { +636 self.round +637 } 638 -639 // Helper: send next ready probe -640 let mut challenge = 0; -641 let mut send_probe = |state: &mut ServerState| { -642 let remote = state.next_probe_addr().unwrap(); -643 challenge += 1; -644 state.mark_probe_sent((remote.ip(), remote.port()), challenge); -645 }; -646 -647 send_probe(&mut state); -648 send_probe(&mut state); -649 -650 // After sending both probes, no ready probes remain but they're still tracked. -651 assert!(state.next_probe_addr().is_none()); -652 -653 // After queuing retries, probes become available again -654 assert!(state.queue_retries()); -655 send_probe(&mut state); -656 send_probe(&mut state); -657 -658 // After 2 attempts each, retries still available (max is 10) -659 assert!(state.queue_retries()); -660 send_probe(&mut state); -661 send_probe(&mut state); -662 -663 // Exhaust remaining attempts -664 for _ in 3..MAX_NAT_PROBE_ATTEMPTS { -665 assert!(state.queue_retries()); -666 send_probe(&mut state); -667 send_probe(&mut state); -668 } -669 -670 // After max attempts, probes are removed -671 assert!(!state.queue_retries()); -672 assert!(state.next_probe_addr().is_none()); -673 } -674 -675 #[test] -676 fn test_map_to_local_socket() { -677 assert_eq!( -678 map_to_local_socket_family("1.1.1.1".parse().unwrap(), false), -679 Some("1.1.1.1".parse().unwrap()) -680 ); -681 assert_eq!( -682 map_to_local_socket_family("1.1.1.1".parse().unwrap(), true), -683 Some("::ffff:1.1.1.1".parse().unwrap()) -684 ); -685 assert_eq!( -686 map_to_local_socket_family("::1".parse().unwrap(), true), -687 Some("::1".parse().unwrap()) -688 ); -689 assert_eq!( -690 map_to_local_socket_family("::1".parse().unwrap(), false), -691 None -692 ); -693 assert_eq!( -694 map_to_local_socket_family("::ffff:1.1.1.1".parse().unwrap(), false), -695 Some("1.1.1.1".parse().unwrap()) -696 ) -697 } -698} \ No newline at end of file +639 /// Handles a received REACH_OUT frame. +640 /// +641 /// This might ignore the reach out frame if it belongs to an older round or if the +642 /// frame contains an IPv6 address while the local socket is IPv4-only. +643 /// +644 /// If a new round was started, the `NatTraversalProbeRetry` timer needs to be reset. +645 pub(crate) fn handle_reach_out( +646 &mut self, +647 reach_out: ReachOut, +648 ipv6: bool, +649 ) -> Result<(), Error> { +650 let ReachOut { round, ip, port } = reach_out; +651 +652 if round < self.round { +653 trace!(current_round=%self.round, "ignoring REACH_OUT for previous round"); +654 return Ok(()); +655 } +656 let Some(ip) = map_to_local_socket_family(ip, ipv6) else { +657 trace!("Ignoring IPv6 REACH_OUT frame due to not supporting IPv6 locally"); +658 return Ok(()); +659 }; +660 +661 if round > self.round { +662 self.round = round; +663 self.remotes.clear(); +664 self.sent_challenges.clear(); +665 self.pending_probes.clear(); +666 } else if self.remotes.contains_key(&(ip, port)) { +667 // Retransmitted frame. +668 return Ok(()); +669 } else if self.remotes.len() >= self.max_remote_addresses { +670 return Err(Error::TooManyAddresses); +671 } +672 self.remotes +673 .entry((ip, port)) +674 .or_insert(ProbeState::Active(MAX_NAT_PROBE_ATTEMPTS - 1)); +675 self.pending_probes.insert((ip, port)); +676 Ok(()) +677 } +678 +679 /// Re-queues probes that have not yet succeeded or reached [`MAX_NAT_PROBE_ATTEMPTS`]. +680 /// +681 /// Returns whether any probes are now queued to send. In this case the +682 /// `NatTraversalProbeRetry` timer needs to be reset. +683 pub(crate) fn queue_retries(&mut self) -> bool { +684 self.remotes +685 .iter_mut() +686 .for_each(|(remote, state)| match state { +687 ProbeState::Active(remaining) if *remaining > 0 => { +688 *remaining -= 1; +689 self.pending_probes.insert(*remote); +690 } +691 ProbeState::Active(_) | ProbeState::Succeeded => (), +692 }); +693 !self.pending_probes.is_empty() +694 } +695 +696 /// Returns the next ready probe's address. +697 /// +698 /// If this is actually sent you must call [`Self::mark_probe_sent`]. +699 fn next_probe_addr(&self) -> Option<SocketAddr> { +700 self.pending_probes.iter().next().map(|addr| (*addr).into()) +701 } +702 +703 /// Marks a probe as sent to the address with the challenge. +704 fn mark_probe_sent(&mut self, remote: IpPort, challenge: u64) { +705 self.pending_probes.remove(&remote); +706 self.sent_challenges.insert(challenge, remote); +707 } +708 +709 /// Marks a remote as successful if the response matches a sent probe. +710 /// +711 /// Returns `true` if it was a response to one of the NAT traversal probes. +712 fn handle_path_response(&mut self, src: FourTuple, challenge: u64) -> bool { +713 if let Entry::Occupied(entry) = self.sent_challenges.entry(challenge) { +714 let remote = (src.remote().ip(), src.remote().port()); +715 if *entry.get() == remote { +716 entry.remove(); +717 self.remotes.insert(remote, ProbeState::Succeeded); +718 return true; +719 } else { +720 debug!( +721 ?challenge, +722 ?src.remote, +723 "PATH_RESPONSE matched a NAT traversal probe but mismatching addr", +724 ) +725 } +726 } +727 false +728 } +729} +730 +731/// Returns the given address as canonicalized IP address. +732/// +733/// This checks that the address family is supported by our local socket. +734/// If it is supported, then the address is mapped to the respective IP address. +735/// If the given address is an IPv6 address, but our local socket doesn't support +736/// IPv6, then this returns `None`. +737pub(crate) fn map_to_local_socket_family(address: IpAddr, ipv6: bool) -> Option<IpAddr> { +738 let ip = match address { +739 IpAddr::V4(addr) if ipv6 => IpAddr::V6(addr.to_ipv6_mapped()), +740 IpAddr::V4(_) => address, +741 IpAddr::V6(_) if ipv6 => address, +742 IpAddr::V6(addr) => IpAddr::V4(addr.to_ipv4_mapped()?), +743 }; +744 Some(ip) +745} +746 +747#[cfg(test)] +748mod tests { +749 use super::*; +750 +751 #[test] +752 fn test_basic_server_state() { +753 let mut state = ServerState::new(2, 2); +754 +755 state +756 .handle_reach_out( +757 ReachOut { +758 round: 1u32.into(), +759 ip: std::net::Ipv4Addr::LOCALHOST.into(), +760 port: 1, +761 }, +762 true, +763 ) +764 .unwrap(); +765 +766 state +767 .handle_reach_out( +768 ReachOut { +769 round: 1u32.into(), +770 ip: "1.1.1.1".parse().unwrap(), //std::net::Ipv4Addr::LOCALHOST.into(), +771 port: 2, +772 }, +773 true, +774 ) +775 .unwrap(); +776 +777 dbg!(&state); +778 assert_eq!(state.pending_probes.len(), 2); +779 +780 // Helper: send next ready probe +781 let mut challenge = 0; +782 let mut send_probe = |state: &mut ServerState| { +783 let remote = state.next_probe_addr().unwrap(); +784 challenge += 1; +785 state.mark_probe_sent((remote.ip(), remote.port()), challenge); +786 }; +787 +788 send_probe(&mut state); +789 send_probe(&mut state); +790 +791 // After sending both probes, no ready probes remain but they're still tracked. +792 assert!(state.next_probe_addr().is_none()); +793 +794 // After queuing retries, probes become available again +795 assert!(state.queue_retries()); +796 send_probe(&mut state); +797 send_probe(&mut state); +798 +799 // After 2 attempts each, retries still available (max is 10) +800 assert!(state.queue_retries()); +801 send_probe(&mut state); +802 send_probe(&mut state); +803 +804 // Exhaust remaining attempts +805 for _ in 3..MAX_NAT_PROBE_ATTEMPTS { +806 assert!(state.queue_retries()); +807 send_probe(&mut state); +808 send_probe(&mut state); +809 } +810 +811 // After max attempts, probes are removed +812 assert!(!state.queue_retries()); +813 assert!(state.next_probe_addr().is_none()); +814 } +815 +816 #[test] +817 fn test_map_to_local_socket() { +818 assert_eq!( +819 map_to_local_socket_family("1.1.1.1".parse().unwrap(), false), +820 Some("1.1.1.1".parse().unwrap()) +821 ); +822 assert_eq!( +823 map_to_local_socket_family("1.1.1.1".parse().unwrap(), true), +824 Some("::ffff:1.1.1.1".parse().unwrap()) +825 ); +826 assert_eq!( +827 map_to_local_socket_family("::1".parse().unwrap(), true), +828 Some("::1".parse().unwrap()) +829 ); +830 assert_eq!( +831 map_to_local_socket_family("::1".parse().unwrap(), false), +832 None +833 ); +834 assert_eq!( +835 map_to_local_socket_family("::ffff:1.1.1.1".parse().unwrap(), false), +836 Some("1.1.1.1".parse().unwrap()) +837 ) +838 } +839} \ No newline at end of file