diff --git a/pr/635/docs/help.html b/pr/635/docs/help.html index 09d0ca8e7..99d8cb979 100644 --- a/pr/635/docs/help.html +++ b/pr/635/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/635/docs/noq/enum.ClosePathError.html b/pr/635/docs/noq/enum.ClosePathError.html index 155214601..a29a71e69 100644 --- a/pr/635/docs/noq/enum.ClosePathError.html +++ b/pr/635/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§

§

impl Freeze for ClosePathError

§

impl RefUnwindSafe for ClosePathError

§

impl Send for ClosePathError

§

impl Sync for ClosePathError

§

impl Unpin for ClosePathError

§

impl UnwindSafe for ClosePathError

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§

§

impl Freeze for ClosePathError

§

impl RefUnwindSafe for ClosePathError

§

impl Send for ClosePathError

§

impl Sync for ClosePathError

§

impl Unpin for ClosePathError

§

impl UnwindSafe for ClosePathError

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/635/docs/noq/enum.ConnectionError.html b/pr/635/docs/noq/enum.ConnectionError.html index 781a261dd..598f26493 100644 --- a/pr/635/docs/noq/enum.ConnectionError.html +++ b/pr/635/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/635/docs/noq/enum.PathError.html b/pr/635/docs/noq/enum.PathError.html index ffeaaed77..74f05de06 100644 --- a/pr/635/docs/noq/enum.PathError.html +++ b/pr/635/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/635/docs/noq/trait.NetworkChangeHint.html b/pr/635/docs/noq/trait.NetworkChangeHint.html index f093c4cb4..866ee55e7 100644 --- a/pr/635/docs/noq/trait.NetworkChangeHint.html +++ b/pr/635/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/635/docs/noq_proto/enum.ClosePathError.html b/pr/635/docs/noq_proto/enum.ClosePathError.html index fa92ae887..ec7fd0f3d 100644 --- a/pr/635/docs/noq_proto/enum.ClosePathError.html +++ b/pr/635/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/635/docs/noq_proto/enum.ConnectionError.html b/pr/635/docs/noq_proto/enum.ConnectionError.html index 3cd2aa727..d5cd46285 100644 --- a/pr/635/docs/noq_proto/enum.ConnectionError.html +++ b/pr/635/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/635/docs/noq_proto/enum.Event.html b/pr/635/docs/noq_proto/enum.Event.html index 26424a364..6231078fe 100644 --- a/pr/635/docs/noq_proto/enum.Event.html +++ b/pr/635/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/635/docs/noq_proto/enum.PathError.html b/pr/635/docs/noq_proto/enum.PathError.html index b12cead5f..5cee5ea24 100644 --- a/pr/635/docs/noq_proto/enum.PathError.html +++ b/pr/635/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/635/docs/noq_proto/enum.PathEvent.html b/pr/635/docs/noq_proto/enum.PathEvent.html index f1d52b34a..80959a6f5 100644 --- a/pr/635/docs/noq_proto/enum.PathEvent.html +++ b/pr/635/docs/noq_proto/enum.PathEvent.html @@ -39,7 +39,7 @@ 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, +

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 diff --git a/pr/635/docs/noq_proto/struct.Connection.html b/pr/635/docs/noq_proto/struct.Connection.html index 1f9c3c56a..d43828e17 100644 --- a/pr/635/docs/noq_proto/struct.Connection.html +++ b/pr/635/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
  • @@ -204,7 +204,7 @@ decrement by one for each time a remotely initiated stream of matching direction
Source

pub fn is_multipath_negotiated(&self) -> bool

Whether the Multipath for QUIC extension is enabled.

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

-
Source

pub fn 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/635/docs/noq_proto/struct.MultipathNotNegotiated.html b/pr/635/docs/noq_proto/struct.MultipathNotNegotiated.html index 05707e928..3ada9ff08 100644 --- a/pr/635/docs/noq_proto/struct.MultipathNotNegotiated.html +++ b/pr/635/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/635/docs/noq_proto/struct.TransportError.html b/pr/635/docs/noq_proto/struct.TransportError.html index d70941856..917061fe9 100644 --- a/pr/635/docs/noq_proto/struct.TransportError.html +++ b/pr/635/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/635/docs/noq_proto/trait.NetworkChangeHint.html b/pr/635/docs/noq_proto/trait.NetworkChangeHint.html index 4469719fb..b35f99bdd 100644 --- a/pr/635/docs/noq_proto/trait.NetworkChangeHint.html +++ b/pr/635/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/635/docs/settings.html b/pr/635/docs/settings.html index fc24471ee..abf467830 100644 --- a/pr/635/docs/settings.html +++ b/pr/635/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/635/docs/src/noq_proto/connection/mod.rs.html b/pr/635/docs/src/noq_proto/connection/mod.rs.html index 3b653113f..f550ffaef 100644 --- a/pr/635/docs/src/noq_proto/connection/mod.rs.html +++ b/pr/635/docs/src/noq_proto/connection/mod.rs.html @@ -4259,3425 +4259,3423 @@ 4259 }) 4260 .unwrap_or(false) 4261 { -4262 // Accept the server migration, see Handshake::allow_server_migration -4263 // for details. -4264 if let Some(hs) = self.state.as_handshake() -4265 && hs.allow_server_migration -4266 { -4267 trace!( -4268 %network_path, -4269 prev = %self.path_data(path_id).network_path, -4270 "server migrated to new remote", -4271 ); -4272 self.path_data_mut(path_id).network_path = network_path; -4273 self.qlog.emit_tuple_assigned(path_id, network_path, now); -4274 } else { -4275 debug!( -4276 recv_path = %network_path, -4277 expected_path = %self.path_data_mut(path_id).network_path, -4278 "discarding packet with unexpected remote during handshake", -4279 ); -4280 return; -4281 } -4282 } -4283 -4284 let dedup = self.spaces[packet.header.space()] -4285 .path_space_mut(path_id) -4286 .map(|pns| &mut pns.dedup); -4287 if pn.zip(dedup).is_some_and(|(n, d)| d.insert(n)) { -4288 debug!("discarding possible duplicate packet"); -4289 self.qlog.emit_packet_received(qlog, now); -4290 return; -4291 } else if self.state.is_handshake() && packet.header.is_short() { -4292 // TODO: SHOULD buffer these to improve reordering tolerance. -4293 trace!("dropping short packet during handshake"); -4294 self.qlog.emit_packet_received(qlog, now); -4295 return; -4296 } else { -4297 if let Header::Initial(InitialHeader { ref token, .. }) = packet.header -4298 && let Some(hs) = self.state.as_handshake() -4299 && self.side.is_server() -4300 && token != &hs.expected_token -4301 { -4302 // Clients must send the same retry token in every Initial. Initial -4303 // packets can be spoofed, so we discard rather than killing the -4304 // connection. -4305 warn!("discarding Initial with invalid retry token"); -4306 self.qlog.emit_packet_received(qlog, now); -4307 return; -4308 } -4309 -4310 if !self.state.is_closed() { -4311 let spin = match packet.header { -4312 Header::Short { spin, .. } => spin, -4313 _ => false, -4314 }; -4315 -4316 if self.side().is_server() && !self.abandoned_paths.contains(&path_id) { -4317 // Only the client is allowed to open paths -4318 self.ensure_path(path_id, network_path, now, pn); -4319 } -4320 if self.paths.contains_key(&path_id) { -4321 self.on_packet_authenticated( -4322 now, -4323 packet.header.space(), -4324 path_id, -4325 ecn, -4326 pn, -4327 spin, -4328 packet.header.is_1rtt(), -4329 &network_path, -4330 ); -4331 } -4332 } -4333 -4334 let res = self.process_decrypted_packet( -4335 now, -4336 network_path, -4337 path_id, -4338 pn, -4339 packet, -4340 &mut qlog, -4341 ); -4342 -4343 self.qlog.emit_packet_received(qlog, now); -4344 res -4345 } -4346 } -4347 }; -4348 -4349 // State transitions for error cases -4350 if let Err(conn_err) = result { -4351 match conn_err { -4352 ConnectionError::ApplicationClosed(reason) => self.state.move_to_closed(reason), -4353 ConnectionError::ConnectionClosed(reason) => self.state.move_to_closed(reason), -4354 ConnectionError::Reset -4355 | ConnectionError::TransportError(TransportError { -4356 code: TransportErrorCode::AEAD_LIMIT_REACHED, -4357 .. -4358 }) => { -4359 self.state.move_to_drained(Some(conn_err)); -4360 } -4361 ConnectionError::TimedOut => { -4362 unreachable!("timeouts aren't generated by packet processing"); -4363 } -4364 ConnectionError::TransportError(err) => { -4365 debug!("closing connection due to transport error: {}", err); -4366 self.state.move_to_closed(err); -4367 } -4368 ConnectionError::VersionMismatch => { -4369 self.state.move_to_draining(Some(conn_err)); -4370 } -4371 ConnectionError::LocallyClosed => { -4372 unreachable!("LocallyClosed isn't generated by packet processing"); -4373 } -4374 ConnectionError::CidsExhausted => { -4375 unreachable!("CidsExhausted isn't generated by packet processing"); -4376 } -4377 }; -4378 } -4379 -4380 if !was_closed && self.state.is_closed() { -4381 self.close_common(); -4382 if !self.state.is_drained() { -4383 self.set_close_timer(now); -4384 } -4385 } -4386 if !was_drained && self.state.is_drained() { -4387 self.endpoint_events.push_back(EndpointEventInner::Drained); -4388 // Close timer may have been started previously, e.g. if we sent a close and got a -4389 // stateless reset in response -4390 self.timers -4391 .stop(Timer::Conn(ConnTimer::Close), self.qlog.with_time(now)); -4392 } -4393 -4394 // Transmit CONNECTION_CLOSE if necessary. -4395 // -4396 // If we received a valid packet and we are in the closed state we should respond -4397 // with a CONNECTION_CLOSE frame. -4398 // TODO: This SHOULD be rate-limited according to §10.2.1 of QUIC-TRANSPORT, but -4399 // that does not yet happen. This is triggered by each received packet. -4400 if matches!(self.state.as_type(), StateType::Closed) { -4401 // From https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.1-7 -4402 // -4403 // While in the closing state we must either: -4404 // - discard packets coming from an un-validated remote OR -4405 // - ensure we do not send more than 3 times the received data -4406 // -4407 // Doing the 2nd would mean we would be able to send CONNECTION_CLOSE to a peer -4408 // who was (involuntary) migrated just at the time we initiated immediate -4409 // close. It is a lot more work though. So while we would like to do this for -4410 // now we only do 1. -4411 // -4412 // Another shortcoming of the current implementation is that when we have a -4413 // previous PathData which is validated and the remote matches that path, we -4414 // should schedule CONNECTION_CLOSE on that path. However currently we can not -4415 // schedule such a packet. We should also fix this some day. This makes us -4416 // vulnerable to an attacker faking a migration at the right time and then we'd -4417 // be unable to send the CONNECTION_CLOSE to the real remote. -4418 if self -4419 .paths -4420 .get(&path_id) -4421 .map(|p| p.data.validated && p.data.network_path == network_path) -4422 .unwrap_or(false) -4423 { -4424 self.connection_close_pending = true; -4425 } -4426 } -4427 } -4428 -4429 fn process_decrypted_packet( -4430 &mut self, -4431 now: Instant, -4432 network_path: FourTuple, -4433 path_id: PathId, -4434 number: Option<u64>, -4435 packet: Packet, -4436 qlog: &mut QlogRecvPacket, -4437 ) -> Result<(), ConnectionError> { -4438 if !self.paths.contains_key(&path_id) { -4439 // There is a chance this is a server side, first (for this path) packet, which would -4440 // be a protocol violation. It's more likely, however, that this is a packet of a -4441 // pruned path -4442 trace!(%path_id, ?number, "discarding packet for unknown path"); -4443 return Ok(()); -4444 } -4445 let state = match self.state.as_type() { -4446 StateType::Established => { -4447 match packet.header.space() { -4448 SpaceKind::Data => self.process_payload( -4449 now, -4450 network_path, -4451 path_id, -4452 number.unwrap(), -4453 packet, -4454 qlog, -4455 )?, -4456 _ if packet.header.has_frames() => { -4457 self.process_early_payload(now, path_id, packet, qlog)? -4458 } -4459 _ => { -4460 trace!("discarding unexpected pre-handshake packet"); -4461 } -4462 } -4463 return Ok(()); -4464 } -4465 StateType::Closed => { -4466 for result in frame::Iter::new(packet.payload.freeze())? { -4467 let frame = match result { -4468 Ok(frame) => frame, -4469 Err(err) => { -4470 debug!("frame decoding error: {err:?}"); -4471 continue; -4472 } -4473 }; -4474 qlog.frame(&frame); -4475 -4476 if let Frame::Padding = frame { -4477 continue; -4478 }; -4479 -4480 self.path_stats -4481 .for_path(path_id) -4482 .frame_rx -4483 .record(frame.ty()); -4484 -4485 if let Frame::Close(_error) = frame { -4486 self.state.move_to_draining(None); -4487 break; -4488 } -4489 } -4490 return Ok(()); -4491 } -4492 StateType::Draining | StateType::Drained => return Ok(()), -4493 StateType::Handshake => self.state.as_handshake_mut().expect("checked"), -4494 }; -4495 -4496 match packet.header { -4497 Header::Retry { -4498 src_cid: remote_cid, -4499 .. -4500 } => { -4501 debug_assert_eq!(path_id, PathId::ZERO); -4502 if self.side.is_server() { -4503 return Err(TransportError::PROTOCOL_VIOLATION("client sent Retry").into()); -4504 } -4505 -4506 let is_valid_retry = self -4507 .remote_cids -4508 .get(&path_id) -4509 .map(|cids| cids.active()) -4510 .map(|orig_dst_cid| { -4511 self.crypto_state.session.is_valid_retry( -4512 orig_dst_cid, -4513 &packet.header_data, -4514 &packet.payload, -4515 ) -4516 }) -4517 .unwrap_or_default(); -4518 if self.total_authed_packets > 1 -4519 || packet.payload.len() <= 16 // token + 16 byte tag -4520 || !is_valid_retry -4521 { -4522 trace!("discarding invalid Retry"); -4523 // - After the client has received and processed an Initial or Retry -4524 // packet from the server, it MUST discard any subsequent Retry -4525 // packets that it receives. -4526 // - A client MUST discard a Retry packet with a zero-length Retry Token -4527 // field. -4528 // - Clients MUST discard Retry packets that have a Retry Integrity Tag -4529 // that cannot be validated -4530 return Ok(()); -4531 } -4532 -4533 trace!("retrying with CID {}", remote_cid); -4534 let client_hello = state.client_hello.take().unwrap(); -4535 self.retry_src_cid = Some(remote_cid); -4536 self.remote_cids -4537 .get_mut(&path_id) -4538 .expect("PathId::ZERO not yet abandoned, is_valid_retry would have been false") -4539 .update_initial_cid(remote_cid); -4540 self.remote_handshake_cid = remote_cid; -4541 -4542 let space = &mut self.spaces[SpaceId::Initial]; -4543 if let Some(info) = space.for_path(PathId::ZERO).take(0) { -4544 self.on_packet_acked(now, PathId::ZERO, 0, info); -4545 }; -4546 -4547 self.discard_space(now, SpaceKind::Initial); // Make sure we clean up after -4548 // any retransmitted Initials -4549 let crypto_space = &mut self.crypto_state.spaces[SpaceKind::Initial]; -4550 crypto_space.keys = Some( -4551 self.crypto_state -4552 .session -4553 .initial_keys(remote_cid, self.side.side()), -4554 ); -4555 crypto_space.crypto_offset = client_hello.len() as u64; -4556 -4557 let next_pn = self.spaces[SpaceId::Initial] -4558 .for_path(path_id) -4559 .next_packet_number; -4560 self.spaces[SpaceId::Initial] = { -4561 let mut space = PacketSpace::new(now, SpaceId::Initial, &mut self.rng); -4562 space.for_path(path_id).next_packet_number = next_pn; -4563 space.pending.crypto.push_back(frame::Crypto { -4564 offset: 0, -4565 data: client_hello, -4566 }); -4567 space -4568 }; -4569 -4570 // Retransmit all 0-RTT data -4571 let zero_rtt = mem::take( -4572 &mut self.spaces[SpaceId::Data] -4573 .for_path(PathId::ZERO) -4574 .sent_packets, -4575 ); -4576 for (_, info) in zero_rtt.into_iter() { -4577 self.paths -4578 .get_mut(&PathId::ZERO) -4579 .unwrap() -4580 .remove_in_flight(&info); -4581 self.spaces[SpaceId::Data].pending |= info.retransmits; -4582 } -4583 self.streams.retransmit_all_for_0rtt(); -4584 -4585 let token_len = packet.payload.len() - 16; -4586 let ConnectionSide::Client { ref mut token, .. } = self.side else { -4587 unreachable!("we already short-circuited if we're server"); -4588 }; -4589 *token = packet.payload.freeze().split_to(token_len); -4590 -4591 self.state = State::handshake(state::Handshake { -4592 expected_token: Bytes::new(), -4593 remote_cid_set: false, -4594 client_hello: None, -4595 allow_server_migration: true, -4596 }); -4597 Ok(()) -4598 } -4599 Header::Long { -4600 ty: LongType::Handshake, -4601 src_cid: remote_cid, -4602 dst_cid: local_cid, -4603 .. -4604 } => { -4605 debug_assert_eq!(path_id, PathId::ZERO); -4606 if remote_cid != self.remote_handshake_cid { -4607 debug!( -4608 "discarding packet with mismatched remote CID: {} != {}", -4609 self.remote_handshake_cid, remote_cid -4610 ); -4611 return Ok(()); -4612 } -4613 self.on_path_validated(path_id); -4614 -4615 self.process_early_payload(now, path_id, packet, qlog)?; -4616 if self.state.is_closed() { -4617 return Ok(()); -4618 } -4619 -4620 if self.crypto_state.session.is_handshaking() { -4621 trace!("handshake ongoing"); -4622 return Ok(()); -4623 } -4624 -4625 if self.side.is_client() { -4626 // Client-only because server params were set from the client's Initial -4627 let params = self -4628 .crypto_state -4629 .session -4630 .transport_parameters()? -4631 .ok_or_else(|| { -4632 TransportError::new( -4633 TransportErrorCode::crypto(0x6d), -4634 "transport parameters missing".to_owned(), -4635 ) -4636 })?; -4637 -4638 if self.has_0rtt() { -4639 if !self.crypto_state.session.early_data_accepted().unwrap() { -4640 debug_assert!(self.side.is_client()); -4641 debug!("0-RTT rejected"); -4642 self.crypto_state.accepted_0rtt = false; -4643 self.streams.zero_rtt_rejected(); -4644 -4645 // Discard already-queued frames -4646 self.spaces[SpaceId::Data].pending = Retransmits::default(); -4647 -4648 // Discard 0-RTT packets -4649 let sent_packets = mem::take( -4650 &mut self.spaces[SpaceId::Data].for_path(path_id).sent_packets, -4651 ); -4652 for (_, packet) in sent_packets.into_iter() { -4653 self.paths -4654 .get_mut(&path_id) -4655 .unwrap() -4656 .remove_in_flight(&packet); -4657 } -4658 } else { -4659 self.crypto_state.accepted_0rtt = true; -4660 params.validate_resumption_from(&self.peer_params)?; -4661 } -4662 } -4663 if let Some(token) = params.stateless_reset_token { -4664 let remote = self.path_data(path_id).network_path.remote; -4665 debug_assert!(!self.state.is_drained()); // requirement for endpoint events, checked above -4666 self.endpoint_events -4667 .push_back(EndpointEventInner::ResetToken(path_id, remote, token)); -4668 } -4669 self.handle_peer_params(params, local_cid, remote_cid, now)?; -4670 self.issue_first_cids(now); -4671 } else { -4672 // Server-only -4673 self.spaces[SpaceId::Data].pending.handshake_done = true; -4674 self.discard_space(now, SpaceKind::Handshake); -4675 self.events.push_back(Event::HandshakeConfirmed); -4676 trace!("handshake confirmed"); -4677 } -4678 -4679 self.events.push_back(Event::Connected); -4680 self.state.move_to_established(); -4681 trace!("established"); -4682 -4683 // Multipath can only be enabled after the state has reached Established. -4684 // So this can not happen any earlier. -4685 self.issue_first_path_cids(now); -4686 Ok(()) -4687 } -4688 Header::Initial(InitialHeader { -4689 src_cid: remote_cid, -4690 dst_cid: local_cid, -4691 .. -4692 }) => { -4693 debug_assert_eq!(path_id, PathId::ZERO); -4694 if !state.remote_cid_set { -4695 trace!("switching remote CID to {}", remote_cid); -4696 let mut state = state.clone(); -4697 self.remote_cids -4698 .get_mut(&path_id) -4699 .expect("PathId::ZERO not yet abandoned") -4700 .update_initial_cid(remote_cid); -4701 self.remote_handshake_cid = remote_cid; -4702 self.original_remote_cid = remote_cid; -4703 state.remote_cid_set = true; -4704 self.state.move_to_handshake(state); -4705 } else if remote_cid != self.remote_handshake_cid { -4706 debug!( -4707 "discarding packet with mismatched remote CID: {} != {}", -4708 self.remote_handshake_cid, remote_cid -4709 ); -4710 return Ok(()); -4711 } -4712 -4713 let starting_space = self.highest_space; -4714 self.process_early_payload(now, path_id, packet, qlog)?; -4715 -4716 if self.side.is_server() -4717 && starting_space == SpaceKind::Initial -4718 && self.highest_space != SpaceKind::Initial -4719 { -4720 let params = self -4721 .crypto_state -4722 .session -4723 .transport_parameters()? -4724 .ok_or_else(|| { -4725 TransportError::new( -4726 TransportErrorCode::crypto(0x6d), -4727 "transport parameters missing".to_owned(), -4728 ) -4729 })?; -4730 self.handle_peer_params(params, local_cid, remote_cid, now)?; -4731 self.issue_first_cids(now); -4732 self.init_0rtt(now); -4733 } -4734 Ok(()) -4735 } -4736 Header::Long { -4737 ty: LongType::ZeroRtt, -4738 .. -4739 } => { -4740 self.process_payload(now, network_path, path_id, number.unwrap(), packet, qlog)?; -4741 Ok(()) -4742 } -4743 Header::VersionNegotiate { .. } => { -4744 if self.total_authed_packets > 1 { -4745 return Ok(()); -4746 } -4747 let supported = packet -4748 .payload -4749 .chunks(4) -4750 .any(|x| match <[u8; 4]>::try_from(x) { -4751 Ok(version) => self.version == u32::from_be_bytes(version), -4752 Err(_) => false, -4753 }); -4754 if supported { -4755 return Ok(()); -4756 } -4757 debug!("remote doesn't support our version"); -4758 Err(ConnectionError::VersionMismatch) -4759 } -4760 Header::Short { .. } => unreachable!( -4761 "short packets received during handshake are discarded in handle_packet" -4762 ), -4763 } -4764 } -4765 -4766 /// Process an Initial or Handshake packet payload -4767 fn process_early_payload( -4768 &mut self, -4769 now: Instant, -4770 path_id: PathId, -4771 packet: Packet, -4772 #[allow(unused)] qlog: &mut QlogRecvPacket, -4773 ) -> Result<(), TransportError> { -4774 debug_assert_ne!(packet.header.space(), SpaceKind::Data); -4775 debug_assert_eq!(path_id, PathId::ZERO); -4776 let payload_len = packet.payload.len(); -4777 let mut ack_eliciting = false; -4778 for result in frame::Iter::new(packet.payload.freeze())? { -4779 let frame = result?; -4780 qlog.frame(&frame); -4781 let span = match frame { -4782 Frame::Padding => continue, -4783 _ => Some(trace_span!("frame", ty = %frame.ty(), path = tracing::field::Empty)), -4784 }; -4785 -4786 self.path_stats -4787 .for_path(path_id) -4788 .frame_rx -4789 .record(frame.ty()); -4790 -4791 let _guard = span.as_ref().map(|x| x.enter()); -4792 ack_eliciting |= frame.is_ack_eliciting(); -4793 -4794 // Process frames -4795 if frame.is_1rtt() && packet.header.space() != SpaceKind::Data { -4796 return Err(TransportError::PROTOCOL_VIOLATION( -4797 "illegal frame type in handshake", -4798 )); -4799 } -4800 -4801 match frame { -4802 Frame::Padding | Frame::Ping => {} -4803 Frame::Crypto(frame) => { -4804 self.read_crypto(packet.header.space().into(), &frame, payload_len)?; -4805 } -4806 Frame::Ack(ack) => { -4807 self.on_ack_received(now, packet.header.space().into(), ack)?; -4808 } -4809 Frame::PathAck(ack) => { -4810 span.as_ref() -4811 .map(|span| span.record("path", tracing::field::display(&ack.path_id))); -4812 self.on_path_ack_received(now, packet.header.space().into(), ack)?; -4813 } -4814 Frame::Close(reason) => { -4815 self.state.move_to_draining(Some(reason.into())); -4816 return Ok(()); -4817 } -4818 _ => { -4819 let mut err = -4820 TransportError::PROTOCOL_VIOLATION("illegal frame type in handshake"); -4821 err.frame = frame::MaybeFrame::Known(frame.ty()); -4822 return Err(err); -4823 } -4824 } -4825 } -4826 -4827 if ack_eliciting { -4828 // In the initial and handshake spaces, ACKs must be sent immediately -4829 self.spaces[packet.header.space()] -4830 .for_path(path_id) -4831 .pending_acks -4832 .set_immediate_ack_required(); -4833 } -4834 -4835 self.write_crypto(); -4836 Ok(()) -4837 } -4838 -4839 /// Processes the decrypted packet payload, always in the data space. -4840 fn process_payload( -4841 &mut self, -4842 now: Instant, -4843 network_path: FourTuple, -4844 path_id: PathId, -4845 number: u64, -4846 packet: Packet, -4847 #[allow(unused)] qlog: &mut QlogRecvPacket, -4848 ) -> Result<(), TransportError> { -4849 let is_multipath_negotiated = self.is_multipath_negotiated(); -4850 let payload = packet.payload.freeze(); -4851 let mut is_probing_packet = true; -4852 let mut close = None; -4853 let payload_len = payload.len(); -4854 let mut ack_eliciting = false; -4855 // if this packet triggers a path migration and includes a observed address frame, it's -4856 // stored here -4857 let mut migration_observed_addr = None; -4858 for result in frame::Iter::new(payload)? { -4859 let frame = result?; -4860 qlog.frame(&frame); -4861 let span = match frame { -4862 Frame::Padding => continue, -4863 _ => trace_span!("frame", ty = %frame.ty(), path = tracing::field::Empty), -4864 }; -4865 -4866 self.path_stats -4867 .for_path(path_id) -4868 .frame_rx -4869 .record(frame.ty()); -4870 // Crypto, Stream and Datagram frames are special cased in order no pollute -4871 // the log with payload data -4872 match &frame { -4873 Frame::Crypto(f) => { -4874 trace!(offset = f.offset, len = f.data.len(), "got frame CRYPTO"); -4875 } -4876 Frame::Stream(f) => { -4877 trace!(id = %f.id, offset = f.offset, len = f.data.len(), fin = f.fin, "got frame STREAM"); -4878 } -4879 Frame::Datagram(f) => { -4880 trace!(len = f.data.len(), "got frame DATAGRAM"); -4881 } -4882 f => { -4883 trace!("got frame {f}"); -4884 } -4885 } -4886 -4887 let _guard = span.enter(); -4888 if packet.header.is_0rtt() { -4889 match frame { -4890 Frame::Crypto(_) | Frame::Close(Close::Application(_)) => { -4891 return Err(TransportError::PROTOCOL_VIOLATION( -4892 "illegal frame type in 0-RTT", -4893 )); -4894 } -4895 _ => { -4896 if frame.is_1rtt() { -4897 return Err(TransportError::PROTOCOL_VIOLATION( -4898 "illegal frame type in 0-RTT", -4899 )); -4900 } -4901 } -4902 } -4903 } -4904 ack_eliciting |= frame.is_ack_eliciting(); -4905 -4906 // Check whether this could be a probing packet -4907 match frame { -4908 Frame::Padding -4909 | Frame::PathChallenge(_) -4910 | Frame::PathResponse(_) -4911 | Frame::NewConnectionId(_) -4912 | Frame::ObservedAddr(_) => {} -4913 _ => { -4914 is_probing_packet = false; -4915 } -4916 } -4917 -4918 match frame { -4919 Frame::Crypto(frame) => { -4920 self.read_crypto(SpaceId::Data, &frame, payload_len)?; -4921 } -4922 Frame::Stream(frame) => { -4923 if self.streams.received(frame, payload_len)?.should_transmit() { -4924 self.spaces[SpaceId::Data].pending.max_data = true; -4925 } -4926 } -4927 Frame::Ack(ack) => { -4928 self.on_ack_received(now, SpaceId::Data, ack)?; -4929 } -4930 Frame::PathAck(ack) => { -4931 if !self.is_multipath_negotiated() { -4932 return Err(TransportError::PROTOCOL_VIOLATION( -4933 "received PATH_ACK frame when multipath was not negotiated", -4934 )); -4935 } -4936 span.record("path", tracing::field::display(&ack.path_id)); -4937 self.on_path_ack_received(now, SpaceId::Data, ack)?; -4938 } -4939 Frame::Padding | Frame::Ping => {} -4940 Frame::Close(reason) => { -4941 close = Some(reason); -4942 } -4943 Frame::PathChallenge(challenge) => { -4944 let path = &mut self -4945 .path_mut(path_id) -4946 .expect("payload is processed only after the path becomes known"); -4947 path.path_responses.push(number, challenge.0, network_path); -4948 // If we were passively migrated (e.g. NAT rebinding), our local_ip will -4949 // not match. Once we processed a non-probing packet the local_ip will -4950 // finally be updated. -4951 if network_path.remote == path.network_path.remote { -4952 // PATH_CHALLENGE on active path, possible off-path packet -4953 // forwarding attack. Send a non-probing packet to recover the -4954 // active path. See -4955 // https://www.rfc-editor.org/rfc/rfc9000.html#section-9.3.3-3. In -4956 // rare cases NAT probes might also appear on-path and would also -4957 // get a non-probing packet as response. There is little harm in -4958 // this. -4959 match self.peer_supports_ack_frequency() { -4960 true => self.immediate_ack(path_id), -4961 false => { -4962 self.ping_path(path_id).ok(); -4963 } -4964 } -4965 } -4966 } -4967 Frame::PathResponse(response) => { -4968 // First try to see if this is a NAT probe response. -4969 if self -4970 .n0_nat_traversal -4971 .handle_path_response(network_path, response.0) -4972 { -4973 self.open_nat_traversed_paths(now); -4974 } else { -4975 // Try to see if this is a response to an on-path PATH_CHALLENGE. -4976 -4977 let path = self -4978 .paths -4979 .get_mut(&path_id) -4980 .expect("payload is processed only after the path becomes known"); -4981 -4982 use PathTimer::*; -4983 use paths::OnPathResponseReceived::*; -4984 match path -4985 .data -4986 .on_path_response_received(now, response.0, network_path) -4987 { -4988 OnPath { was_open } => { -4989 let qlog = self.qlog.with_time(now); -4990 -4991 self.timers.stop( -4992 Timer::PerPath(path_id, PathValidationFailed), -4993 qlog.clone(), -4994 ); -4995 self.timers.stop( -4996 Timer::PerPath(path_id, AbandonFromValidation), -4997 qlog.clone(), -4998 ); -4999 -5000 let next_challenge = path -5001 .data -5002 .earliest_on_path_expiring_challenge() -5003 .map(|time| time + self.ack_frequency.max_ack_delay_for_pto()); -5004 self.timers.set_or_stop( -5005 Timer::PerPath(path_id, PathChallengeLost), -5006 next_challenge, -5007 qlog, -5008 ); -5009 -5010 if !was_open { -5011 if is_multipath_negotiated { -5012 self.events.push_back(Event::Path(PathEvent::Opened { -5013 id: path_id, -5014 })); -5015 } -5016 if let Some(observed) = -5017 path.data.last_observed_addr_report.as_ref() -5018 { -5019 self.events.push_back(Event::Path( -5020 PathEvent::ObservedAddr { -5021 id: path_id, -5022 addr: observed.socket_addr(), -5023 }, -5024 )); -5025 } -5026 } -5027 if let Some((_, ref mut prev)) = path.prev { -5028 // If an on-path response was received while there is a -5029 // previous path from a migration, then the new path is -5030 // validated and we can stop sending challenges that try to -5031 // re-validate the previous path. -5032 prev.reset_on_path_challenges(); -5033 } -5034 } -5035 Ignored { -5036 sent_on, -5037 current_path, -5038 } => { -5039 debug!(%sent_on, %current_path, %response, "ignoring valid PATH_RESPONSE") -5040 } -5041 Unknown => debug!(%response, "ignoring invalid PATH_RESPONSE"), -5042 } -5043 } -5044 } -5045 Frame::MaxData(frame::MaxData(bytes)) => { -5046 self.streams.received_max_data(bytes); -5047 } -5048 Frame::MaxStreamData(frame::MaxStreamData { id, offset }) => { -5049 self.streams.received_max_stream_data(id, offset)?; -5050 } -5051 Frame::MaxStreams(frame::MaxStreams { dir, count }) => { -5052 self.streams.received_max_streams(dir, count)?; -5053 } -5054 Frame::ResetStream(frame) => { -5055 if self.streams.received_reset(frame)?.should_transmit() { -5056 self.spaces[SpaceId::Data].pending.max_data = true; -5057 } -5058 } -5059 Frame::DataBlocked(DataBlocked(offset)) => { -5060 debug!(offset, "peer claims to be blocked at connection level"); -5061 } -5062 Frame::StreamDataBlocked(StreamDataBlocked { id, offset }) => { -5063 if id.initiator() == self.side.side() && id.dir() == Dir::Uni { -5064 debug!("got STREAM_DATA_BLOCKED on send-only {}", id); -5065 return Err(TransportError::STREAM_STATE_ERROR( -5066 "STREAM_DATA_BLOCKED on send-only stream", -5067 )); -5068 } -5069 debug!( -5070 stream = %id, -5071 offset, "peer claims to be blocked at stream level" -5072 ); -5073 } -5074 Frame::StreamsBlocked(StreamsBlocked { dir, limit }) => { -5075 if limit > MAX_STREAM_COUNT { -5076 return Err(TransportError::FRAME_ENCODING_ERROR( -5077 "unrepresentable stream limit", -5078 )); -5079 } -5080 debug!( -5081 "peer claims to be blocked opening more than {} {} streams", -5082 limit, dir -5083 ); -5084 } -5085 Frame::StopSending(frame::StopSending { id, error_code }) => { -5086 if id.initiator() != self.side.side() { -5087 if id.dir() == Dir::Uni { -5088 debug!("got STOP_SENDING on recv-only {}", id); -5089 return Err(TransportError::STREAM_STATE_ERROR( -5090 "STOP_SENDING on recv-only stream", -5091 )); -5092 } -5093 } else if self.streams.is_local_unopened(id) { -5094 return Err(TransportError::STREAM_STATE_ERROR( -5095 "STOP_SENDING on unopened stream", -5096 )); -5097 } -5098 self.streams.received_stop_sending(id, error_code); -5099 } -5100 Frame::RetireConnectionId(frame::RetireConnectionId { path_id, sequence }) => { -5101 if let Some(ref path_id) = path_id { -5102 span.record("path", tracing::field::display(&path_id)); -5103 } -5104 let path_id = path_id.unwrap_or_default(); -5105 match self.local_cid_state.get_mut(&path_id) { -5106 None => debug!(?path_id, "RETIRE_CONNECTION_ID for unknown path"), -5107 Some(cid_state) => { -5108 let allow_more_cids = cid_state -5109 .on_cid_retirement(sequence, self.peer_params.issue_cids_limit())?; -5110 -5111 // If the path has closed, we do not issue more CIDs for this path -5112 // For details see https://www.ietf.org/archive/id/draft-ietf-quic-multipath-17.html#section-3.2.2 -5113 // > an endpoint SHOULD provide new connection IDs for that path, if still open, using PATH_NEW_CONNECTION_ID frames. -5114 let has_path = !self.abandoned_paths.contains(&path_id); -5115 let allow_more_cids = allow_more_cids && has_path; -5116 -5117 debug_assert!(!self.state.is_drained()); // required for adding endpoint events, process_payload is never called for drained connections -5118 self.endpoint_events -5119 .push_back(EndpointEventInner::RetireConnectionId( -5120 now, -5121 path_id, -5122 sequence, -5123 allow_more_cids, -5124 )); -5125 } -5126 } -5127 } -5128 Frame::NewConnectionId(frame) => { -5129 let path_id = if let Some(path_id) = frame.path_id { -5130 if !self.is_multipath_negotiated() { -5131 return Err(TransportError::PROTOCOL_VIOLATION( -5132 "received PATH_NEW_CONNECTION_ID frame when multipath was not negotiated", -5133 )); -5134 } -5135 if path_id > self.local_max_path_id { -5136 return Err(TransportError::PROTOCOL_VIOLATION( -5137 "PATH_NEW_CONNECTION_ID contains path_id exceeding current max", -5138 )); -5139 } -5140 path_id -5141 } else { -5142 PathId::ZERO -5143 }; -5144 -5145 if let Some(ref path_id) = frame.path_id { -5146 span.record("path", tracing::field::display(&path_id)); -5147 } -5148 -5149 if self.abandoned_paths.contains(&path_id) { -5150 trace!("ignoring issued CID for abandoned path"); -5151 continue; -5152 } -5153 let remote_cids = self -5154 .remote_cids -5155 .entry(path_id) -5156 .or_insert_with(|| CidQueue::new(frame.id)); -5157 if remote_cids.active().is_empty() { -5158 return Err(TransportError::PROTOCOL_VIOLATION( -5159 "NEW_CONNECTION_ID when CIDs aren't in use", -5160 )); -5161 } -5162 if frame.retire_prior_to > frame.sequence { -5163 return Err(TransportError::PROTOCOL_VIOLATION( -5164 "NEW_CONNECTION_ID retiring unissued CIDs", -5165 )); -5166 } -5167 -5168 use crate::cid_queue::InsertError; -5169 match remote_cids.insert(frame) { -5170 Ok(None) => { -5171 self.open_nat_traversed_paths(now); -5172 } -5173 Ok(Some((retired, reset_token))) => { -5174 let pending_retired = -5175 &mut self.spaces[SpaceId::Data].pending.retire_cids; -5176 /// Ensure `pending_retired` cannot grow without bound. Limit is -5177 /// somewhat arbitrary but very permissive. -5178 const MAX_PENDING_RETIRED_CIDS: u64 = CidQueue::LEN as u64 * 10; -5179 // We don't bother counting in-flight frames because those are bounded -5180 // by congestion control. -5181 if (pending_retired.len() as u64) -5182 .saturating_add(retired.end.saturating_sub(retired.start)) -5183 > MAX_PENDING_RETIRED_CIDS -5184 { -5185 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR( -5186 "queued too many retired CIDs", -5187 )); -5188 } -5189 pending_retired.extend(retired.map(|seq| (path_id, seq))); -5190 self.set_reset_token(path_id, network_path.remote, reset_token); -5191 self.open_nat_traversed_paths(now); -5192 } -5193 Err(InsertError::ExceedsLimit) => { -5194 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR("")); -5195 } -5196 Err(InsertError::Retired) => { -5197 trace!("discarding already-retired"); -5198 // RETIRE_CONNECTION_ID might not have been previously sent if e.g. a -5199 // range of connection IDs larger than the active connection ID limit -5200 // was retired all at once via retire_prior_to. -5201 self.spaces[SpaceId::Data] -5202 .pending -5203 .retire_cids -5204 .push((path_id, frame.sequence)); -5205 continue; -5206 } -5207 }; -5208 -5209 if self.side.is_server() -5210 && path_id == PathId::ZERO -5211 && self -5212 .remote_cids -5213 .get(&PathId::ZERO) -5214 .map(|cids| cids.active_seq() == 0) -5215 .unwrap_or_default() -5216 { -5217 // We're a server still using the initial remote CID for the client, so -5218 // let's switch immediately to enable clientside stateless resets. -5219 self.update_remote_cid(PathId::ZERO); -5220 } -5221 } -5222 Frame::NewToken(NewToken { token }) => { -5223 let ConnectionSide::Client { -5224 token_store, -5225 server_name, -5226 .. -5227 } = &self.side -5228 else { -5229 return Err(TransportError::PROTOCOL_VIOLATION("client sent NEW_TOKEN")); -5230 }; -5231 if token.is_empty() { -5232 return Err(TransportError::FRAME_ENCODING_ERROR("empty token")); -5233 } -5234 trace!("got new token"); -5235 token_store.insert(server_name, token); -5236 } -5237 Frame::Datagram(datagram) => { -5238 if self -5239 .datagrams -5240 .received(datagram, &self.config.datagram_receive_buffer_size)? -5241 { -5242 self.events.push_back(Event::DatagramReceived); -5243 } -5244 } -5245 Frame::AckFrequency(ack_frequency) => { -5246 // This frame can only be sent in the Data space -5247 -5248 if !self.ack_frequency.ack_frequency_received(&ack_frequency)? { -5249 // The AckFrequency frame is stale (we have already received a more -5250 // recent one) -5251 continue; -5252 } -5253 -5254 // Update the params for all of our paths -5255 for (path_id, space) in self.spaces[SpaceId::Data].number_spaces.iter_mut() { -5256 space.pending_acks.set_ack_frequency_params(&ack_frequency); -5257 -5258 // Our `max_ack_delay` has been updated, so we may need to adjust -5259 // its associated timeout. -5260 // Packets received on abandoned paths are always acknowledged immediately. -5261 if !self.abandoned_paths.contains(path_id) -5262 && let Some(timeout) = space -5263 .pending_acks -5264 .max_ack_delay_timeout(self.ack_frequency.max_ack_delay) -5265 { -5266 self.timers.set( -5267 Timer::PerPath(*path_id, PathTimer::MaxAckDelay), -5268 timeout, -5269 self.qlog.with_time(now), -5270 ); -5271 } -5272 } -5273 } -5274 Frame::ImmediateAck => { -5275 // This frame can only be sent in the Data space -5276 for pns in self.spaces[SpaceId::Data].iter_paths_mut() { -5277 pns.pending_acks.set_immediate_ack_required(); -5278 } -5279 } -5280 Frame::HandshakeDone => { -5281 if self.side.is_server() { -5282 return Err(TransportError::PROTOCOL_VIOLATION( -5283 "client sent HANDSHAKE_DONE", -5284 )); -5285 } -5286 if self.crypto_state.has_keys(EncryptionLevel::Handshake) { -5287 self.discard_space(now, SpaceKind::Handshake); -5288 self.events.push_back(Event::HandshakeConfirmed); -5289 trace!("handshake confirmed"); -5290 } -5291 } -5292 Frame::ObservedAddr(observed) => { -5293 // check if params allows the peer to send report and this node to receive it -5294 trace!(seq_no = %observed.seq_no, ip = %observed.ip, port = observed.port); -5295 if !self -5296 .peer_params -5297 .address_discovery_role -5298 .should_report(&self.config.address_discovery_role) -5299 { -5300 return Err(TransportError::PROTOCOL_VIOLATION( -5301 "received OBSERVED_ADDRESS frame when not negotiated", -5302 )); -5303 } -5304 // must only be sent in data space -5305 if packet.header.space() != SpaceKind::Data { -5306 return Err(TransportError::PROTOCOL_VIOLATION( -5307 "OBSERVED_ADDRESS frame outside data space", -5308 )); -5309 } -5310 -5311 let path = self.path_data_mut(path_id); -5312 if path.network_path.is_probably_same_path(&network_path) { -5313 if let Some(updated) = path.update_observed_addr_report(observed) -5314 && path.open_status == paths::OpenStatus::Informed -5315 { -5316 self.events.push_back(Event::Path(PathEvent::ObservedAddr { -5317 id: path_id, -5318 addr: updated, -5319 })); -5320 // otherwise the event is reported when the path is deemed open -5321 } -5322 } else { -5323 // include in migration -5324 migration_observed_addr = Some(observed) -5325 } -5326 } -5327 Frame::PathAbandon(frame::PathAbandon { -5328 path_id, -5329 error_code, -5330 }) => { -5331 span.record("path", tracing::field::display(&path_id)); -5332 match self.close_path_inner( -5333 now, -5334 path_id, -5335 PathAbandonReason::RemoteAbandoned { -5336 error_code: error_code.into(), -5337 }, -5338 ) { -5339 Ok(()) => { -5340 trace!("peer abandoned path"); -5341 } -5342 Err(ClosePathError::ClosedPath) => { -5343 trace!("peer abandoned already closed path"); -5344 } -5345 Err(ClosePathError::MultipathNotNegotiated) => { -5346 return Err(TransportError::PROTOCOL_VIOLATION( -5347 "received PATH_ABANDON frame when multipath was not negotiated", -5348 )); -5349 } -5350 Err(ClosePathError::LastOpenPath) => { -5351 // Not reachable: close_path_inner allows remote abandons -5352 // for the last path. But handle gracefully just in case. -5353 error!( -5354 "peer abandoned last path but close_path_inner returned LastOpenPath" -5355 ); -5356 } -5357 }; -5358 -5359 // Start draining the path if it still exists and hasn't started draining yet. -5360 if let Some(path) = self.paths.get_mut(&path_id) -5361 && !mem::replace(&mut path.data.draining, true) -5362 { -5363 let ack_delay = self.ack_frequency.max_ack_delay_for_pto(); -5364 let pto = path.data.rtt.pto_base() + ack_delay; -5365 self.timers.set( -5366 Timer::PerPath(path_id, PathTimer::PathDrained), -5367 now + 3 * pto, -5368 self.qlog.with_time(now), -5369 ); -5370 -5371 self.set_max_path_id(now, self.local_max_path_id.saturating_add(1u8)); -5372 } -5373 } -5374 Frame::PathStatusAvailable(info) => { -5375 span.record("path", tracing::field::display(&info.path_id)); -5376 if self.is_multipath_negotiated() { -5377 self.on_path_status( -5378 info.path_id, -5379 PathStatus::Available, -5380 info.status_seq_no, -5381 ); -5382 } else { -5383 return Err(TransportError::PROTOCOL_VIOLATION( -5384 "received PATH_STATUS_AVAILABLE frame when multipath was not negotiated", -5385 )); -5386 } -5387 } -5388 Frame::PathStatusBackup(info) => { -5389 span.record("path", tracing::field::display(&info.path_id)); -5390 if self.is_multipath_negotiated() { -5391 self.on_path_status(info.path_id, PathStatus::Backup, info.status_seq_no); -5392 } else { -5393 return Err(TransportError::PROTOCOL_VIOLATION( -5394 "received PATH_STATUS_BACKUP frame when multipath was not negotiated", -5395 )); -5396 } -5397 } -5398 Frame::MaxPathId(frame::MaxPathId(path_id)) => { -5399 span.record("path", tracing::field::display(&path_id)); -5400 if !self.is_multipath_negotiated() { -5401 return Err(TransportError::PROTOCOL_VIOLATION( -5402 "received MAX_PATH_ID frame when multipath was not negotiated", -5403 )); -5404 } -5405 // frames that do not increase the path id are ignored -5406 if path_id > self.remote_max_path_id { -5407 self.remote_max_path_id = path_id; -5408 self.issue_first_path_cids(now); -5409 self.open_nat_traversed_paths(now); -5410 } -5411 } -5412 Frame::PathsBlocked(frame::PathsBlocked(max_path_id)) => { -5413 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST -5414 // be treated as a connection error of type PROTOCOL_VIOLATION. -5415 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> -5416 if self.is_multipath_negotiated() { -5417 if max_path_id > self.local_max_path_id { -5418 return Err(TransportError::PROTOCOL_VIOLATION( -5419 "PATHS_BLOCKED maximum path identifier was larger than local maximum", -5420 )); -5421 } -5422 debug!("received PATHS_BLOCKED({:?})", max_path_id); -5423 // TODO(@divma): ensure max concurrent paths -5424 } else { -5425 return Err(TransportError::PROTOCOL_VIOLATION( -5426 "received PATHS_BLOCKED frame when not multipath was not negotiated", -5427 )); -5428 } -5429 } -5430 Frame::PathCidsBlocked(frame::PathCidsBlocked { path_id, next_seq }) => { -5431 // Nothing to do. This is recorded in the frame stats, but otherwise we -5432 // always issue all CIDs we're allowed to issue, so either this is an -5433 // impatient peer or a bug on our side. -5434 -5435 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST -5436 // be treated as a connection error of type PROTOCOL_VIOLATION. -5437 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> -5438 if self.is_multipath_negotiated() { -5439 if path_id > self.local_max_path_id { -5440 return Err(TransportError::PROTOCOL_VIOLATION( -5441 "PATH_CIDS_BLOCKED path identifier was larger than local maximum", -5442 )); -5443 } -5444 if next_seq.0 -5445 > self -5446 .local_cid_state -5447 .get(&path_id) -5448 .map(|cid_state| cid_state.active_seq().1 + 1) -5449 .unwrap_or_default() -5450 { -5451 return Err(TransportError::PROTOCOL_VIOLATION( -5452 "PATH_CIDS_BLOCKED next sequence number larger than in local state", -5453 )); -5454 } -5455 debug!(%path_id, %next_seq, "received PATH_CIDS_BLOCKED"); -5456 } else { -5457 return Err(TransportError::PROTOCOL_VIOLATION( -5458 "received PATH_CIDS_BLOCKED frame when not multipath was not negotiated", -5459 )); -5460 } -5461 } -5462 Frame::AddAddress(addr) => { -5463 let client_state = match self.n0_nat_traversal.client_side_mut() { -5464 Ok(state) => state, -5465 Err(err) => { -5466 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5467 "Nat traversal(ADD_ADDRESS): {err}" -5468 ))); -5469 } -5470 }; -5471 -5472 if !client_state.check_remote_address(&addr) { -5473 // if the address is not valid we flag it, but update anyway -5474 warn!(?addr, "server sent illegal ADD_ADDRESS frame"); -5475 } -5476 -5477 match client_state.add_remote_address(addr) { -5478 Ok(maybe_added) => { -5479 if let Some(added) = maybe_added { -5480 self.events.push_back(Event::NatTraversal( -5481 n0_nat_traversal::Event::AddressAdded(added), -5482 )); -5483 } -5484 } -5485 Err(e) => { -5486 warn!(%e, "failed to add remote address") -5487 } -5488 } -5489 } -5490 Frame::RemoveAddress(addr) => { -5491 let client_state = match self.n0_nat_traversal.client_side_mut() { -5492 Ok(state) => state, -5493 Err(err) => { -5494 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5495 "Nat traversal(REMOVE_ADDRESS): {err}" -5496 ))); -5497 } -5498 }; -5499 if let Some(removed_addr) = client_state.remove_remote_address(addr) { -5500 self.events.push_back(Event::NatTraversal( -5501 n0_nat_traversal::Event::AddressRemoved(removed_addr), -5502 )); -5503 } -5504 } -5505 Frame::ReachOut(reach_out) => { -5506 let ipv6 = self.is_ipv6(); -5507 let server_state = match self.n0_nat_traversal.server_side_mut() { -5508 Ok(state) => state, -5509 Err(err) => { -5510 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5511 "Nat traversal(REACH_OUT): {err}" -5512 ))); -5513 } -5514 }; +4262 if let Some(hs) = self.state.as_handshake() +4263 && hs.allow_server_migration +4264 { +4265 trace!( +4266 %network_path, +4267 prev = %self.path_data(path_id).network_path, +4268 "server migrated to new remote", +4269 ); +4270 self.path_data_mut(path_id).network_path = network_path; +4271 self.qlog.emit_tuple_assigned(path_id, network_path, now); +4272 } else { +4273 debug!( +4274 recv_path = %network_path, +4275 expected_path = %self.path_data_mut(path_id).network_path, +4276 "discarding packet with unexpected remote during handshake", +4277 ); +4278 return; +4279 } +4280 } +4281 +4282 let dedup = self.spaces[packet.header.space()] +4283 .path_space_mut(path_id) +4284 .map(|pns| &mut pns.dedup); +4285 if pn.zip(dedup).is_some_and(|(n, d)| d.insert(n)) { +4286 debug!("discarding possible duplicate packet"); +4287 self.qlog.emit_packet_received(qlog, now); +4288 return; +4289 } else if self.state.is_handshake() && packet.header.is_short() { +4290 // TODO: SHOULD buffer these to improve reordering tolerance. +4291 trace!("dropping short packet during handshake"); +4292 self.qlog.emit_packet_received(qlog, now); +4293 return; +4294 } else { +4295 if let Header::Initial(InitialHeader { ref token, .. }) = packet.header +4296 && let Some(hs) = self.state.as_handshake() +4297 && self.side.is_server() +4298 && token != &hs.expected_token +4299 { +4300 // Clients must send the same retry token in every Initial. Initial +4301 // packets can be spoofed, so we discard rather than killing the +4302 // connection. +4303 warn!("discarding Initial with invalid retry token"); +4304 self.qlog.emit_packet_received(qlog, now); +4305 return; +4306 } +4307 +4308 if !self.state.is_closed() { +4309 let spin = match packet.header { +4310 Header::Short { spin, .. } => spin, +4311 _ => false, +4312 }; +4313 +4314 if self.side().is_server() && !self.abandoned_paths.contains(&path_id) { +4315 // Only the client is allowed to open paths +4316 self.ensure_path(path_id, network_path, now, pn); +4317 } +4318 if self.paths.contains_key(&path_id) { +4319 self.on_packet_authenticated( +4320 now, +4321 packet.header.space(), +4322 path_id, +4323 ecn, +4324 pn, +4325 spin, +4326 packet.header.is_1rtt(), +4327 &network_path, +4328 ); +4329 } +4330 } +4331 +4332 let res = self.process_decrypted_packet( +4333 now, +4334 network_path, +4335 path_id, +4336 pn, +4337 packet, +4338 &mut qlog, +4339 ); +4340 +4341 self.qlog.emit_packet_received(qlog, now); +4342 res +4343 } +4344 } +4345 }; +4346 +4347 // State transitions for error cases +4348 if let Err(conn_err) = result { +4349 match conn_err { +4350 ConnectionError::ApplicationClosed(reason) => self.state.move_to_closed(reason), +4351 ConnectionError::ConnectionClosed(reason) => self.state.move_to_closed(reason), +4352 ConnectionError::Reset +4353 | ConnectionError::TransportError(TransportError { +4354 code: TransportErrorCode::AEAD_LIMIT_REACHED, +4355 .. +4356 }) => { +4357 self.state.move_to_drained(Some(conn_err)); +4358 } +4359 ConnectionError::TimedOut => { +4360 unreachable!("timeouts aren't generated by packet processing"); +4361 } +4362 ConnectionError::TransportError(err) => { +4363 debug!("closing connection due to transport error: {}", err); +4364 self.state.move_to_closed(err); +4365 } +4366 ConnectionError::VersionMismatch => { +4367 self.state.move_to_draining(Some(conn_err)); +4368 } +4369 ConnectionError::LocallyClosed => { +4370 unreachable!("LocallyClosed isn't generated by packet processing"); +4371 } +4372 ConnectionError::CidsExhausted => { +4373 unreachable!("CidsExhausted isn't generated by packet processing"); +4374 } +4375 }; +4376 } +4377 +4378 if !was_closed && self.state.is_closed() { +4379 self.close_common(); +4380 if !self.state.is_drained() { +4381 self.set_close_timer(now); +4382 } +4383 } +4384 if !was_drained && self.state.is_drained() { +4385 self.endpoint_events.push_back(EndpointEventInner::Drained); +4386 // Close timer may have been started previously, e.g. if we sent a close and got a +4387 // stateless reset in response +4388 self.timers +4389 .stop(Timer::Conn(ConnTimer::Close), self.qlog.with_time(now)); +4390 } +4391 +4392 // Transmit CONNECTION_CLOSE if necessary. +4393 // +4394 // If we received a valid packet and we are in the closed state we should respond +4395 // with a CONNECTION_CLOSE frame. +4396 // TODO: This SHOULD be rate-limited according to §10.2.1 of QUIC-TRANSPORT, but +4397 // that does not yet happen. This is triggered by each received packet. +4398 if matches!(self.state.as_type(), StateType::Closed) { +4399 // From https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.1-7 +4400 // +4401 // While in the closing state we must either: +4402 // - discard packets coming from an un-validated remote OR +4403 // - ensure we do not send more than 3 times the received data +4404 // +4405 // Doing the 2nd would mean we would be able to send CONNECTION_CLOSE to a peer +4406 // who was (involuntary) migrated just at the time we initiated immediate +4407 // close. It is a lot more work though. So while we would like to do this for +4408 // now we only do 1. +4409 // +4410 // Another shortcoming of the current implementation is that when we have a +4411 // previous PathData which is validated and the remote matches that path, we +4412 // should schedule CONNECTION_CLOSE on that path. However currently we can not +4413 // schedule such a packet. We should also fix this some day. This makes us +4414 // vulnerable to an attacker faking a migration at the right time and then we'd +4415 // be unable to send the CONNECTION_CLOSE to the real remote. +4416 if self +4417 .paths +4418 .get(&path_id) +4419 .map(|p| p.data.validated && p.data.network_path == network_path) +4420 .unwrap_or(false) +4421 { +4422 self.connection_close_pending = true; +4423 } +4424 } +4425 } +4426 +4427 fn process_decrypted_packet( +4428 &mut self, +4429 now: Instant, +4430 network_path: FourTuple, +4431 path_id: PathId, +4432 number: Option<u64>, +4433 packet: Packet, +4434 qlog: &mut QlogRecvPacket, +4435 ) -> Result<(), ConnectionError> { +4436 if !self.paths.contains_key(&path_id) { +4437 // There is a chance this is a server side, first (for this path) packet, which would +4438 // be a protocol violation. It's more likely, however, that this is a packet of a +4439 // pruned path +4440 trace!(%path_id, ?number, "discarding packet for unknown path"); +4441 return Ok(()); +4442 } +4443 let state = match self.state.as_type() { +4444 StateType::Established => { +4445 match packet.header.space() { +4446 SpaceKind::Data => self.process_payload( +4447 now, +4448 network_path, +4449 path_id, +4450 number.unwrap(), +4451 packet, +4452 qlog, +4453 )?, +4454 _ if packet.header.has_frames() => { +4455 self.process_early_payload(now, path_id, packet, qlog)? +4456 } +4457 _ => { +4458 trace!("discarding unexpected pre-handshake packet"); +4459 } +4460 } +4461 return Ok(()); +4462 } +4463 StateType::Closed => { +4464 for result in frame::Iter::new(packet.payload.freeze())? { +4465 let frame = match result { +4466 Ok(frame) => frame, +4467 Err(err) => { +4468 debug!("frame decoding error: {err:?}"); +4469 continue; +4470 } +4471 }; +4472 qlog.frame(&frame); +4473 +4474 if let Frame::Padding = frame { +4475 continue; +4476 }; +4477 +4478 self.path_stats +4479 .for_path(path_id) +4480 .frame_rx +4481 .record(frame.ty()); +4482 +4483 if let Frame::Close(_error) = frame { +4484 self.state.move_to_draining(None); +4485 break; +4486 } +4487 } +4488 return Ok(()); +4489 } +4490 StateType::Draining | StateType::Drained => return Ok(()), +4491 StateType::Handshake => self.state.as_handshake_mut().expect("checked"), +4492 }; +4493 +4494 match packet.header { +4495 Header::Retry { +4496 src_cid: remote_cid, +4497 .. +4498 } => { +4499 debug_assert_eq!(path_id, PathId::ZERO); +4500 if self.side.is_server() { +4501 return Err(TransportError::PROTOCOL_VIOLATION("client sent Retry").into()); +4502 } +4503 +4504 let is_valid_retry = self +4505 .remote_cids +4506 .get(&path_id) +4507 .map(|cids| cids.active()) +4508 .map(|orig_dst_cid| { +4509 self.crypto_state.session.is_valid_retry( +4510 orig_dst_cid, +4511 &packet.header_data, +4512 &packet.payload, +4513 ) +4514 }) +4515 .unwrap_or_default(); +4516 if self.total_authed_packets > 1 +4517 || packet.payload.len() <= 16 // token + 16 byte tag +4518 || !is_valid_retry +4519 { +4520 trace!("discarding invalid Retry"); +4521 // - After the client has received and processed an Initial or Retry +4522 // packet from the server, it MUST discard any subsequent Retry +4523 // packets that it receives. +4524 // - A client MUST discard a Retry packet with a zero-length Retry Token +4525 // field. +4526 // - Clients MUST discard Retry packets that have a Retry Integrity Tag +4527 // that cannot be validated +4528 return Ok(()); +4529 } +4530 +4531 trace!("retrying with CID {}", remote_cid); +4532 let client_hello = state.client_hello.take().unwrap(); +4533 self.retry_src_cid = Some(remote_cid); +4534 self.remote_cids +4535 .get_mut(&path_id) +4536 .expect("PathId::ZERO not yet abandoned, is_valid_retry would have been false") +4537 .update_initial_cid(remote_cid); +4538 self.remote_handshake_cid = remote_cid; +4539 +4540 let space = &mut self.spaces[SpaceId::Initial]; +4541 if let Some(info) = space.for_path(PathId::ZERO).take(0) { +4542 self.on_packet_acked(now, PathId::ZERO, 0, info); +4543 }; +4544 +4545 self.discard_space(now, SpaceKind::Initial); // Make sure we clean up after +4546 // any retransmitted Initials +4547 let crypto_space = &mut self.crypto_state.spaces[SpaceKind::Initial]; +4548 crypto_space.keys = Some( +4549 self.crypto_state +4550 .session +4551 .initial_keys(remote_cid, self.side.side()), +4552 ); +4553 crypto_space.crypto_offset = client_hello.len() as u64; +4554 +4555 let next_pn = self.spaces[SpaceId::Initial] +4556 .for_path(path_id) +4557 .next_packet_number; +4558 self.spaces[SpaceId::Initial] = { +4559 let mut space = PacketSpace::new(now, SpaceId::Initial, &mut self.rng); +4560 space.for_path(path_id).next_packet_number = next_pn; +4561 space.pending.crypto.push_back(frame::Crypto { +4562 offset: 0, +4563 data: client_hello, +4564 }); +4565 space +4566 }; +4567 +4568 // Retransmit all 0-RTT data +4569 let zero_rtt = mem::take( +4570 &mut self.spaces[SpaceId::Data] +4571 .for_path(PathId::ZERO) +4572 .sent_packets, +4573 ); +4574 for (_, info) in zero_rtt.into_iter() { +4575 self.paths +4576 .get_mut(&PathId::ZERO) +4577 .unwrap() +4578 .remove_in_flight(&info); +4579 self.spaces[SpaceId::Data].pending |= info.retransmits; +4580 } +4581 self.streams.retransmit_all_for_0rtt(); +4582 +4583 let token_len = packet.payload.len() - 16; +4584 let ConnectionSide::Client { ref mut token, .. } = self.side else { +4585 unreachable!("we already short-circuited if we're server"); +4586 }; +4587 *token = packet.payload.freeze().split_to(token_len); +4588 +4589 self.state = State::handshake(state::Handshake { +4590 expected_token: Bytes::new(), +4591 remote_cid_set: false, +4592 client_hello: None, +4593 allow_server_migration: true, +4594 }); +4595 Ok(()) +4596 } +4597 Header::Long { +4598 ty: LongType::Handshake, +4599 src_cid: remote_cid, +4600 dst_cid: local_cid, +4601 .. +4602 } => { +4603 debug_assert_eq!(path_id, PathId::ZERO); +4604 if remote_cid != self.remote_handshake_cid { +4605 debug!( +4606 "discarding packet with mismatched remote CID: {} != {}", +4607 self.remote_handshake_cid, remote_cid +4608 ); +4609 return Ok(()); +4610 } +4611 self.on_path_validated(path_id); +4612 +4613 self.process_early_payload(now, path_id, packet, qlog)?; +4614 if self.state.is_closed() { +4615 return Ok(()); +4616 } +4617 +4618 if self.crypto_state.session.is_handshaking() { +4619 trace!("handshake ongoing"); +4620 return Ok(()); +4621 } +4622 +4623 if self.side.is_client() { +4624 // Client-only because server params were set from the client's Initial +4625 let params = self +4626 .crypto_state +4627 .session +4628 .transport_parameters()? +4629 .ok_or_else(|| { +4630 TransportError::new( +4631 TransportErrorCode::crypto(0x6d), +4632 "transport parameters missing".to_owned(), +4633 ) +4634 })?; +4635 +4636 if self.has_0rtt() { +4637 if !self.crypto_state.session.early_data_accepted().unwrap() { +4638 debug_assert!(self.side.is_client()); +4639 debug!("0-RTT rejected"); +4640 self.crypto_state.accepted_0rtt = false; +4641 self.streams.zero_rtt_rejected(); +4642 +4643 // Discard already-queued frames +4644 self.spaces[SpaceId::Data].pending = Retransmits::default(); +4645 +4646 // Discard 0-RTT packets +4647 let sent_packets = mem::take( +4648 &mut self.spaces[SpaceId::Data].for_path(path_id).sent_packets, +4649 ); +4650 for (_, packet) in sent_packets.into_iter() { +4651 self.paths +4652 .get_mut(&path_id) +4653 .unwrap() +4654 .remove_in_flight(&packet); +4655 } +4656 } else { +4657 self.crypto_state.accepted_0rtt = true; +4658 params.validate_resumption_from(&self.peer_params)?; +4659 } +4660 } +4661 if let Some(token) = params.stateless_reset_token { +4662 let remote = self.path_data(path_id).network_path.remote; +4663 debug_assert!(!self.state.is_drained()); // requirement for endpoint events, checked above +4664 self.endpoint_events +4665 .push_back(EndpointEventInner::ResetToken(path_id, remote, token)); +4666 } +4667 self.handle_peer_params(params, local_cid, remote_cid, now)?; +4668 self.issue_first_cids(now); +4669 } else { +4670 // Server-only +4671 self.spaces[SpaceId::Data].pending.handshake_done = true; +4672 self.discard_space(now, SpaceKind::Handshake); +4673 self.events.push_back(Event::HandshakeConfirmed); +4674 trace!("handshake confirmed"); +4675 } +4676 +4677 self.events.push_back(Event::Connected); +4678 self.state.move_to_established(); +4679 trace!("established"); +4680 +4681 // Multipath can only be enabled after the state has reached Established. +4682 // So this can not happen any earlier. +4683 self.issue_first_path_cids(now); +4684 Ok(()) +4685 } +4686 Header::Initial(InitialHeader { +4687 src_cid: remote_cid, +4688 dst_cid: local_cid, +4689 .. +4690 }) => { +4691 debug_assert_eq!(path_id, PathId::ZERO); +4692 if !state.remote_cid_set { +4693 trace!("switching remote CID to {}", remote_cid); +4694 let mut state = state.clone(); +4695 self.remote_cids +4696 .get_mut(&path_id) +4697 .expect("PathId::ZERO not yet abandoned") +4698 .update_initial_cid(remote_cid); +4699 self.remote_handshake_cid = remote_cid; +4700 self.original_remote_cid = remote_cid; +4701 state.remote_cid_set = true; +4702 self.state.move_to_handshake(state); +4703 } else if remote_cid != self.remote_handshake_cid { +4704 debug!( +4705 "discarding packet with mismatched remote CID: {} != {}", +4706 self.remote_handshake_cid, remote_cid +4707 ); +4708 return Ok(()); +4709 } +4710 +4711 let starting_space = self.highest_space; +4712 self.process_early_payload(now, path_id, packet, qlog)?; +4713 +4714 if self.side.is_server() +4715 && starting_space == SpaceKind::Initial +4716 && self.highest_space != SpaceKind::Initial +4717 { +4718 let params = self +4719 .crypto_state +4720 .session +4721 .transport_parameters()? +4722 .ok_or_else(|| { +4723 TransportError::new( +4724 TransportErrorCode::crypto(0x6d), +4725 "transport parameters missing".to_owned(), +4726 ) +4727 })?; +4728 self.handle_peer_params(params, local_cid, remote_cid, now)?; +4729 self.issue_first_cids(now); +4730 self.init_0rtt(now); +4731 } +4732 Ok(()) +4733 } +4734 Header::Long { +4735 ty: LongType::ZeroRtt, +4736 .. +4737 } => { +4738 self.process_payload(now, network_path, path_id, number.unwrap(), packet, qlog)?; +4739 Ok(()) +4740 } +4741 Header::VersionNegotiate { .. } => { +4742 if self.total_authed_packets > 1 { +4743 return Ok(()); +4744 } +4745 let supported = packet +4746 .payload +4747 .chunks(4) +4748 .any(|x| match <[u8; 4]>::try_from(x) { +4749 Ok(version) => self.version == u32::from_be_bytes(version), +4750 Err(_) => false, +4751 }); +4752 if supported { +4753 return Ok(()); +4754 } +4755 debug!("remote doesn't support our version"); +4756 Err(ConnectionError::VersionMismatch) +4757 } +4758 Header::Short { .. } => unreachable!( +4759 "short packets received during handshake are discarded in handle_packet" +4760 ), +4761 } +4762 } +4763 +4764 /// Process an Initial or Handshake packet payload +4765 fn process_early_payload( +4766 &mut self, +4767 now: Instant, +4768 path_id: PathId, +4769 packet: Packet, +4770 #[allow(unused)] qlog: &mut QlogRecvPacket, +4771 ) -> Result<(), TransportError> { +4772 debug_assert_ne!(packet.header.space(), SpaceKind::Data); +4773 debug_assert_eq!(path_id, PathId::ZERO); +4774 let payload_len = packet.payload.len(); +4775 let mut ack_eliciting = false; +4776 for result in frame::Iter::new(packet.payload.freeze())? { +4777 let frame = result?; +4778 qlog.frame(&frame); +4779 let span = match frame { +4780 Frame::Padding => continue, +4781 _ => Some(trace_span!("frame", ty = %frame.ty(), path = tracing::field::Empty)), +4782 }; +4783 +4784 self.path_stats +4785 .for_path(path_id) +4786 .frame_rx +4787 .record(frame.ty()); +4788 +4789 let _guard = span.as_ref().map(|x| x.enter()); +4790 ack_eliciting |= frame.is_ack_eliciting(); +4791 +4792 // Process frames +4793 if frame.is_1rtt() && packet.header.space() != SpaceKind::Data { +4794 return Err(TransportError::PROTOCOL_VIOLATION( +4795 "illegal frame type in handshake", +4796 )); +4797 } +4798 +4799 match frame { +4800 Frame::Padding | Frame::Ping => {} +4801 Frame::Crypto(frame) => { +4802 self.read_crypto(packet.header.space().into(), &frame, payload_len)?; +4803 } +4804 Frame::Ack(ack) => { +4805 self.on_ack_received(now, packet.header.space().into(), ack)?; +4806 } +4807 Frame::PathAck(ack) => { +4808 span.as_ref() +4809 .map(|span| span.record("path", tracing::field::display(&ack.path_id))); +4810 self.on_path_ack_received(now, packet.header.space().into(), ack)?; +4811 } +4812 Frame::Close(reason) => { +4813 self.state.move_to_draining(Some(reason.into())); +4814 return Ok(()); +4815 } +4816 _ => { +4817 let mut err = +4818 TransportError::PROTOCOL_VIOLATION("illegal frame type in handshake"); +4819 err.frame = frame::MaybeFrame::Known(frame.ty()); +4820 return Err(err); +4821 } +4822 } +4823 } +4824 +4825 if ack_eliciting { +4826 // In the initial and handshake spaces, ACKs must be sent immediately +4827 self.spaces[packet.header.space()] +4828 .for_path(path_id) +4829 .pending_acks +4830 .set_immediate_ack_required(); +4831 } +4832 +4833 self.write_crypto(); +4834 Ok(()) +4835 } +4836 +4837 /// Processes the decrypted packet payload, always in the data space. +4838 fn process_payload( +4839 &mut self, +4840 now: Instant, +4841 network_path: FourTuple, +4842 path_id: PathId, +4843 number: u64, +4844 packet: Packet, +4845 #[allow(unused)] qlog: &mut QlogRecvPacket, +4846 ) -> Result<(), TransportError> { +4847 let is_multipath_negotiated = self.is_multipath_negotiated(); +4848 let payload = packet.payload.freeze(); +4849 let mut is_probing_packet = true; +4850 let mut close = None; +4851 let payload_len = payload.len(); +4852 let mut ack_eliciting = false; +4853 // if this packet triggers a path migration and includes a observed address frame, it's +4854 // stored here +4855 let mut migration_observed_addr = None; +4856 for result in frame::Iter::new(payload)? { +4857 let frame = result?; +4858 qlog.frame(&frame); +4859 let span = match frame { +4860 Frame::Padding => continue, +4861 _ => trace_span!("frame", ty = %frame.ty(), path = tracing::field::Empty), +4862 }; +4863 +4864 self.path_stats +4865 .for_path(path_id) +4866 .frame_rx +4867 .record(frame.ty()); +4868 // Crypto, Stream and Datagram frames are special cased in order no pollute +4869 // the log with payload data +4870 match &frame { +4871 Frame::Crypto(f) => { +4872 trace!(offset = f.offset, len = f.data.len(), "got frame CRYPTO"); +4873 } +4874 Frame::Stream(f) => { +4875 trace!(id = %f.id, offset = f.offset, len = f.data.len(), fin = f.fin, "got frame STREAM"); +4876 } +4877 Frame::Datagram(f) => { +4878 trace!(len = f.data.len(), "got frame DATAGRAM"); +4879 } +4880 f => { +4881 trace!("got frame {f}"); +4882 } +4883 } +4884 +4885 let _guard = span.enter(); +4886 if packet.header.is_0rtt() { +4887 match frame { +4888 Frame::Crypto(_) | Frame::Close(Close::Application(_)) => { +4889 return Err(TransportError::PROTOCOL_VIOLATION( +4890 "illegal frame type in 0-RTT", +4891 )); +4892 } +4893 _ => { +4894 if frame.is_1rtt() { +4895 return Err(TransportError::PROTOCOL_VIOLATION( +4896 "illegal frame type in 0-RTT", +4897 )); +4898 } +4899 } +4900 } +4901 } +4902 ack_eliciting |= frame.is_ack_eliciting(); +4903 +4904 // Check whether this could be a probing packet +4905 match frame { +4906 Frame::Padding +4907 | Frame::PathChallenge(_) +4908 | Frame::PathResponse(_) +4909 | Frame::NewConnectionId(_) +4910 | Frame::ObservedAddr(_) => {} +4911 _ => { +4912 is_probing_packet = false; +4913 } +4914 } +4915 +4916 match frame { +4917 Frame::Crypto(frame) => { +4918 self.read_crypto(SpaceId::Data, &frame, payload_len)?; +4919 } +4920 Frame::Stream(frame) => { +4921 if self.streams.received(frame, payload_len)?.should_transmit() { +4922 self.spaces[SpaceId::Data].pending.max_data = true; +4923 } +4924 } +4925 Frame::Ack(ack) => { +4926 self.on_ack_received(now, SpaceId::Data, ack)?; +4927 } +4928 Frame::PathAck(ack) => { +4929 if !self.is_multipath_negotiated() { +4930 return Err(TransportError::PROTOCOL_VIOLATION( +4931 "received PATH_ACK frame when multipath was not negotiated", +4932 )); +4933 } +4934 span.record("path", tracing::field::display(&ack.path_id)); +4935 self.on_path_ack_received(now, SpaceId::Data, ack)?; +4936 } +4937 Frame::Padding | Frame::Ping => {} +4938 Frame::Close(reason) => { +4939 close = Some(reason); +4940 } +4941 Frame::PathChallenge(challenge) => { +4942 let path = &mut self +4943 .path_mut(path_id) +4944 .expect("payload is processed only after the path becomes known"); +4945 path.path_responses.push(number, challenge.0, network_path); +4946 // If we were passively migrated (e.g. NAT rebinding), our local_ip will +4947 // not match. Once we processed a non-probing packet the local_ip will +4948 // finally be updated. +4949 if network_path.remote == path.network_path.remote { +4950 // PATH_CHALLENGE on active path, possible off-path packet +4951 // forwarding attack. Send a non-probing packet to recover the +4952 // active path. See +4953 // https://www.rfc-editor.org/rfc/rfc9000.html#section-9.3.3-3. In +4954 // rare cases NAT probes might also appear on-path and would also +4955 // get a non-probing packet as response. There is little harm in +4956 // this. +4957 match self.peer_supports_ack_frequency() { +4958 true => self.immediate_ack(path_id), +4959 false => { +4960 self.ping_path(path_id).ok(); +4961 } +4962 } +4963 } +4964 } +4965 Frame::PathResponse(response) => { +4966 // First try to see if this is a NAT probe response. +4967 if self +4968 .n0_nat_traversal +4969 .handle_path_response(network_path, response.0) +4970 { +4971 self.open_nat_traversed_paths(now); +4972 } else { +4973 // Try to see if this is a response to an on-path PATH_CHALLENGE. +4974 +4975 let path = self +4976 .paths +4977 .get_mut(&path_id) +4978 .expect("payload is processed only after the path becomes known"); +4979 +4980 use PathTimer::*; +4981 use paths::OnPathResponseReceived::*; +4982 match path +4983 .data +4984 .on_path_response_received(now, response.0, network_path) +4985 { +4986 OnPath { was_open } => { +4987 let qlog = self.qlog.with_time(now); +4988 +4989 self.timers.stop( +4990 Timer::PerPath(path_id, PathValidationFailed), +4991 qlog.clone(), +4992 ); +4993 self.timers.stop( +4994 Timer::PerPath(path_id, AbandonFromValidation), +4995 qlog.clone(), +4996 ); +4997 +4998 let next_challenge = path +4999 .data +5000 .earliest_on_path_expiring_challenge() +5001 .map(|time| time + self.ack_frequency.max_ack_delay_for_pto()); +5002 self.timers.set_or_stop( +5003 Timer::PerPath(path_id, PathChallengeLost), +5004 next_challenge, +5005 qlog, +5006 ); +5007 +5008 if !was_open { +5009 if is_multipath_negotiated { +5010 self.events.push_back(Event::Path(PathEvent::Opened { +5011 id: path_id, +5012 })); +5013 } +5014 if let Some(observed) = +5015 path.data.last_observed_addr_report.as_ref() +5016 { +5017 self.events.push_back(Event::Path( +5018 PathEvent::ObservedAddr { +5019 id: path_id, +5020 addr: observed.socket_addr(), +5021 }, +5022 )); +5023 } +5024 } +5025 if let Some((_, ref mut prev)) = path.prev { +5026 // If an on-path response was received while there is a +5027 // previous path from a migration, then the new path is +5028 // validated and we can stop sending challenges that try to +5029 // re-validate the previous path. +5030 prev.reset_on_path_challenges(); +5031 } +5032 } +5033 Ignored { +5034 sent_on, +5035 current_path, +5036 } => { +5037 debug!(%sent_on, %current_path, %response, "ignoring valid PATH_RESPONSE") +5038 } +5039 Unknown => debug!(%response, "ignoring invalid PATH_RESPONSE"), +5040 } +5041 } +5042 } +5043 Frame::MaxData(frame::MaxData(bytes)) => { +5044 self.streams.received_max_data(bytes); +5045 } +5046 Frame::MaxStreamData(frame::MaxStreamData { id, offset }) => { +5047 self.streams.received_max_stream_data(id, offset)?; +5048 } +5049 Frame::MaxStreams(frame::MaxStreams { dir, count }) => { +5050 self.streams.received_max_streams(dir, count)?; +5051 } +5052 Frame::ResetStream(frame) => { +5053 if self.streams.received_reset(frame)?.should_transmit() { +5054 self.spaces[SpaceId::Data].pending.max_data = true; +5055 } +5056 } +5057 Frame::DataBlocked(DataBlocked(offset)) => { +5058 debug!(offset, "peer claims to be blocked at connection level"); +5059 } +5060 Frame::StreamDataBlocked(StreamDataBlocked { id, offset }) => { +5061 if id.initiator() == self.side.side() && id.dir() == Dir::Uni { +5062 debug!("got STREAM_DATA_BLOCKED on send-only {}", id); +5063 return Err(TransportError::STREAM_STATE_ERROR( +5064 "STREAM_DATA_BLOCKED on send-only stream", +5065 )); +5066 } +5067 debug!( +5068 stream = %id, +5069 offset, "peer claims to be blocked at stream level" +5070 ); +5071 } +5072 Frame::StreamsBlocked(StreamsBlocked { dir, limit }) => { +5073 if limit > MAX_STREAM_COUNT { +5074 return Err(TransportError::FRAME_ENCODING_ERROR( +5075 "unrepresentable stream limit", +5076 )); +5077 } +5078 debug!( +5079 "peer claims to be blocked opening more than {} {} streams", +5080 limit, dir +5081 ); +5082 } +5083 Frame::StopSending(frame::StopSending { id, error_code }) => { +5084 if id.initiator() != self.side.side() { +5085 if id.dir() == Dir::Uni { +5086 debug!("got STOP_SENDING on recv-only {}", id); +5087 return Err(TransportError::STREAM_STATE_ERROR( +5088 "STOP_SENDING on recv-only stream", +5089 )); +5090 } +5091 } else if self.streams.is_local_unopened(id) { +5092 return Err(TransportError::STREAM_STATE_ERROR( +5093 "STOP_SENDING on unopened stream", +5094 )); +5095 } +5096 self.streams.received_stop_sending(id, error_code); +5097 } +5098 Frame::RetireConnectionId(frame::RetireConnectionId { path_id, sequence }) => { +5099 if let Some(ref path_id) = path_id { +5100 span.record("path", tracing::field::display(&path_id)); +5101 } +5102 let path_id = path_id.unwrap_or_default(); +5103 match self.local_cid_state.get_mut(&path_id) { +5104 None => debug!(?path_id, "RETIRE_CONNECTION_ID for unknown path"), +5105 Some(cid_state) => { +5106 let allow_more_cids = cid_state +5107 .on_cid_retirement(sequence, self.peer_params.issue_cids_limit())?; +5108 +5109 // If the path has closed, we do not issue more CIDs for this path +5110 // For details see https://www.ietf.org/archive/id/draft-ietf-quic-multipath-17.html#section-3.2.2 +5111 // > an endpoint SHOULD provide new connection IDs for that path, if still open, using PATH_NEW_CONNECTION_ID frames. +5112 let has_path = !self.abandoned_paths.contains(&path_id); +5113 let allow_more_cids = allow_more_cids && has_path; +5114 +5115 debug_assert!(!self.state.is_drained()); // required for adding endpoint events, process_payload is never called for drained connections +5116 self.endpoint_events +5117 .push_back(EndpointEventInner::RetireConnectionId( +5118 now, +5119 path_id, +5120 sequence, +5121 allow_more_cids, +5122 )); +5123 } +5124 } +5125 } +5126 Frame::NewConnectionId(frame) => { +5127 let path_id = if let Some(path_id) = frame.path_id { +5128 if !self.is_multipath_negotiated() { +5129 return Err(TransportError::PROTOCOL_VIOLATION( +5130 "received PATH_NEW_CONNECTION_ID frame when multipath was not negotiated", +5131 )); +5132 } +5133 if path_id > self.local_max_path_id { +5134 return Err(TransportError::PROTOCOL_VIOLATION( +5135 "PATH_NEW_CONNECTION_ID contains path_id exceeding current max", +5136 )); +5137 } +5138 path_id +5139 } else { +5140 PathId::ZERO +5141 }; +5142 +5143 if let Some(ref path_id) = frame.path_id { +5144 span.record("path", tracing::field::display(&path_id)); +5145 } +5146 +5147 if self.abandoned_paths.contains(&path_id) { +5148 trace!("ignoring issued CID for abandoned path"); +5149 continue; +5150 } +5151 let remote_cids = self +5152 .remote_cids +5153 .entry(path_id) +5154 .or_insert_with(|| CidQueue::new(frame.id)); +5155 if remote_cids.active().is_empty() { +5156 return Err(TransportError::PROTOCOL_VIOLATION( +5157 "NEW_CONNECTION_ID when CIDs aren't in use", +5158 )); +5159 } +5160 if frame.retire_prior_to > frame.sequence { +5161 return Err(TransportError::PROTOCOL_VIOLATION( +5162 "NEW_CONNECTION_ID retiring unissued CIDs", +5163 )); +5164 } +5165 +5166 use crate::cid_queue::InsertError; +5167 match remote_cids.insert(frame) { +5168 Ok(None) => { +5169 self.open_nat_traversed_paths(now); +5170 } +5171 Ok(Some((retired, reset_token))) => { +5172 let pending_retired = +5173 &mut self.spaces[SpaceId::Data].pending.retire_cids; +5174 /// Ensure `pending_retired` cannot grow without bound. Limit is +5175 /// somewhat arbitrary but very permissive. +5176 const MAX_PENDING_RETIRED_CIDS: u64 = CidQueue::LEN as u64 * 10; +5177 // We don't bother counting in-flight frames because those are bounded +5178 // by congestion control. +5179 if (pending_retired.len() as u64) +5180 .saturating_add(retired.end.saturating_sub(retired.start)) +5181 > MAX_PENDING_RETIRED_CIDS +5182 { +5183 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR( +5184 "queued too many retired CIDs", +5185 )); +5186 } +5187 pending_retired.extend(retired.map(|seq| (path_id, seq))); +5188 self.set_reset_token(path_id, network_path.remote, reset_token); +5189 self.open_nat_traversed_paths(now); +5190 } +5191 Err(InsertError::ExceedsLimit) => { +5192 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR("")); +5193 } +5194 Err(InsertError::Retired) => { +5195 trace!("discarding already-retired"); +5196 // RETIRE_CONNECTION_ID might not have been previously sent if e.g. a +5197 // range of connection IDs larger than the active connection ID limit +5198 // was retired all at once via retire_prior_to. +5199 self.spaces[SpaceId::Data] +5200 .pending +5201 .retire_cids +5202 .push((path_id, frame.sequence)); +5203 continue; +5204 } +5205 }; +5206 +5207 if self.side.is_server() +5208 && path_id == PathId::ZERO +5209 && self +5210 .remote_cids +5211 .get(&PathId::ZERO) +5212 .map(|cids| cids.active_seq() == 0) +5213 .unwrap_or_default() +5214 { +5215 // We're a server still using the initial remote CID for the client, so +5216 // let's switch immediately to enable clientside stateless resets. +5217 self.update_remote_cid(PathId::ZERO); +5218 } +5219 } +5220 Frame::NewToken(NewToken { token }) => { +5221 let ConnectionSide::Client { +5222 token_store, +5223 server_name, +5224 .. +5225 } = &self.side +5226 else { +5227 return Err(TransportError::PROTOCOL_VIOLATION("client sent NEW_TOKEN")); +5228 }; +5229 if token.is_empty() { +5230 return Err(TransportError::FRAME_ENCODING_ERROR("empty token")); +5231 } +5232 trace!("got new token"); +5233 token_store.insert(server_name, token); +5234 } +5235 Frame::Datagram(datagram) => { +5236 if self +5237 .datagrams +5238 .received(datagram, &self.config.datagram_receive_buffer_size)? +5239 { +5240 self.events.push_back(Event::DatagramReceived); +5241 } +5242 } +5243 Frame::AckFrequency(ack_frequency) => { +5244 // This frame can only be sent in the Data space +5245 +5246 if !self.ack_frequency.ack_frequency_received(&ack_frequency)? { +5247 // The AckFrequency frame is stale (we have already received a more +5248 // recent one) +5249 continue; +5250 } +5251 +5252 // Update the params for all of our paths +5253 for (path_id, space) in self.spaces[SpaceId::Data].number_spaces.iter_mut() { +5254 space.pending_acks.set_ack_frequency_params(&ack_frequency); +5255 +5256 // Our `max_ack_delay` has been updated, so we may need to adjust +5257 // its associated timeout. +5258 // Packets received on abandoned paths are always acknowledged immediately. +5259 if !self.abandoned_paths.contains(path_id) +5260 && let Some(timeout) = space +5261 .pending_acks +5262 .max_ack_delay_timeout(self.ack_frequency.max_ack_delay) +5263 { +5264 self.timers.set( +5265 Timer::PerPath(*path_id, PathTimer::MaxAckDelay), +5266 timeout, +5267 self.qlog.with_time(now), +5268 ); +5269 } +5270 } +5271 } +5272 Frame::ImmediateAck => { +5273 // This frame can only be sent in the Data space +5274 for pns in self.spaces[SpaceId::Data].iter_paths_mut() { +5275 pns.pending_acks.set_immediate_ack_required(); +5276 } +5277 } +5278 Frame::HandshakeDone => { +5279 if self.side.is_server() { +5280 return Err(TransportError::PROTOCOL_VIOLATION( +5281 "client sent HANDSHAKE_DONE", +5282 )); +5283 } +5284 if self.crypto_state.has_keys(EncryptionLevel::Handshake) { +5285 self.discard_space(now, SpaceKind::Handshake); +5286 self.events.push_back(Event::HandshakeConfirmed); +5287 trace!("handshake confirmed"); +5288 } +5289 } +5290 Frame::ObservedAddr(observed) => { +5291 // check if params allows the peer to send report and this node to receive it +5292 trace!(seq_no = %observed.seq_no, ip = %observed.ip, port = observed.port); +5293 if !self +5294 .peer_params +5295 .address_discovery_role +5296 .should_report(&self.config.address_discovery_role) +5297 { +5298 return Err(TransportError::PROTOCOL_VIOLATION( +5299 "received OBSERVED_ADDRESS frame when not negotiated", +5300 )); +5301 } +5302 // must only be sent in data space +5303 if packet.header.space() != SpaceKind::Data { +5304 return Err(TransportError::PROTOCOL_VIOLATION( +5305 "OBSERVED_ADDRESS frame outside data space", +5306 )); +5307 } +5308 +5309 let path = self.path_data_mut(path_id); +5310 if path.network_path.is_probably_same_path(&network_path) { +5311 if let Some(updated) = path.update_observed_addr_report(observed) +5312 && path.open_status == paths::OpenStatus::Informed +5313 { +5314 self.events.push_back(Event::Path(PathEvent::ObservedAddr { +5315 id: path_id, +5316 addr: updated, +5317 })); +5318 // otherwise the event is reported when the path is deemed open +5319 } +5320 } else { +5321 // include in migration +5322 migration_observed_addr = Some(observed) +5323 } +5324 } +5325 Frame::PathAbandon(frame::PathAbandon { +5326 path_id, +5327 error_code, +5328 }) => { +5329 span.record("path", tracing::field::display(&path_id)); +5330 match self.close_path_inner( +5331 now, +5332 path_id, +5333 PathAbandonReason::RemoteAbandoned { +5334 error_code: error_code.into(), +5335 }, +5336 ) { +5337 Ok(()) => { +5338 trace!("peer abandoned path"); +5339 } +5340 Err(ClosePathError::ClosedPath) => { +5341 trace!("peer abandoned already closed path"); +5342 } +5343 Err(ClosePathError::MultipathNotNegotiated) => { +5344 return Err(TransportError::PROTOCOL_VIOLATION( +5345 "received PATH_ABANDON frame when multipath was not negotiated", +5346 )); +5347 } +5348 Err(ClosePathError::LastOpenPath) => { +5349 // Not reachable: close_path_inner allows remote abandons +5350 // for the last path. But handle gracefully just in case. +5351 error!( +5352 "peer abandoned last path but close_path_inner returned LastOpenPath" +5353 ); +5354 } +5355 }; +5356 +5357 // Start draining the path if it still exists and hasn't started draining yet. +5358 if let Some(path) = self.paths.get_mut(&path_id) +5359 && !mem::replace(&mut path.data.draining, true) +5360 { +5361 let ack_delay = self.ack_frequency.max_ack_delay_for_pto(); +5362 let pto = path.data.rtt.pto_base() + ack_delay; +5363 self.timers.set( +5364 Timer::PerPath(path_id, PathTimer::PathDrained), +5365 now + 3 * pto, +5366 self.qlog.with_time(now), +5367 ); +5368 +5369 self.set_max_path_id(now, self.local_max_path_id.saturating_add(1u8)); +5370 } +5371 } +5372 Frame::PathStatusAvailable(info) => { +5373 span.record("path", tracing::field::display(&info.path_id)); +5374 if self.is_multipath_negotiated() { +5375 self.on_path_status( +5376 info.path_id, +5377 PathStatus::Available, +5378 info.status_seq_no, +5379 ); +5380 } else { +5381 return Err(TransportError::PROTOCOL_VIOLATION( +5382 "received PATH_STATUS_AVAILABLE frame when multipath was not negotiated", +5383 )); +5384 } +5385 } +5386 Frame::PathStatusBackup(info) => { +5387 span.record("path", tracing::field::display(&info.path_id)); +5388 if self.is_multipath_negotiated() { +5389 self.on_path_status(info.path_id, PathStatus::Backup, info.status_seq_no); +5390 } else { +5391 return Err(TransportError::PROTOCOL_VIOLATION( +5392 "received PATH_STATUS_BACKUP frame when multipath was not negotiated", +5393 )); +5394 } +5395 } +5396 Frame::MaxPathId(frame::MaxPathId(path_id)) => { +5397 span.record("path", tracing::field::display(&path_id)); +5398 if !self.is_multipath_negotiated() { +5399 return Err(TransportError::PROTOCOL_VIOLATION( +5400 "received MAX_PATH_ID frame when multipath was not negotiated", +5401 )); +5402 } +5403 // frames that do not increase the path id are ignored +5404 if path_id > self.remote_max_path_id { +5405 self.remote_max_path_id = path_id; +5406 self.issue_first_path_cids(now); +5407 self.open_nat_traversed_paths(now); +5408 } +5409 } +5410 Frame::PathsBlocked(frame::PathsBlocked(max_path_id)) => { +5411 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST +5412 // be treated as a connection error of type PROTOCOL_VIOLATION. +5413 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> +5414 if self.is_multipath_negotiated() { +5415 if max_path_id > self.local_max_path_id { +5416 return Err(TransportError::PROTOCOL_VIOLATION( +5417 "PATHS_BLOCKED maximum path identifier was larger than local maximum", +5418 )); +5419 } +5420 debug!("received PATHS_BLOCKED({:?})", max_path_id); +5421 // TODO(@divma): ensure max concurrent paths +5422 } else { +5423 return Err(TransportError::PROTOCOL_VIOLATION( +5424 "received PATHS_BLOCKED frame when not multipath was not negotiated", +5425 )); +5426 } +5427 } +5428 Frame::PathCidsBlocked(frame::PathCidsBlocked { path_id, next_seq }) => { +5429 // Nothing to do. This is recorded in the frame stats, but otherwise we +5430 // always issue all CIDs we're allowed to issue, so either this is an +5431 // impatient peer or a bug on our side. +5432 +5433 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST +5434 // be treated as a connection error of type PROTOCOL_VIOLATION. +5435 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> +5436 if self.is_multipath_negotiated() { +5437 if path_id > self.local_max_path_id { +5438 return Err(TransportError::PROTOCOL_VIOLATION( +5439 "PATH_CIDS_BLOCKED path identifier was larger than local maximum", +5440 )); +5441 } +5442 if next_seq.0 +5443 > self +5444 .local_cid_state +5445 .get(&path_id) +5446 .map(|cid_state| cid_state.active_seq().1 + 1) +5447 .unwrap_or_default() +5448 { +5449 return Err(TransportError::PROTOCOL_VIOLATION( +5450 "PATH_CIDS_BLOCKED next sequence number larger than in local state", +5451 )); +5452 } +5453 debug!(%path_id, %next_seq, "received PATH_CIDS_BLOCKED"); +5454 } else { +5455 return Err(TransportError::PROTOCOL_VIOLATION( +5456 "received PATH_CIDS_BLOCKED frame when not multipath was not negotiated", +5457 )); +5458 } +5459 } +5460 Frame::AddAddress(addr) => { +5461 let client_state = match self.n0_nat_traversal.client_side_mut() { +5462 Ok(state) => state, +5463 Err(err) => { +5464 return Err(TransportError::PROTOCOL_VIOLATION(format!( +5465 "Nat traversal(ADD_ADDRESS): {err}" +5466 ))); +5467 } +5468 }; +5469 +5470 if !client_state.check_remote_address(&addr) { +5471 // if the address is not valid we flag it, but update anyway +5472 warn!(?addr, "server sent illegal ADD_ADDRESS frame"); +5473 } +5474 +5475 match client_state.add_remote_address(addr) { +5476 Ok(maybe_added) => { +5477 if let Some(added) = maybe_added { +5478 self.events.push_back(Event::NatTraversal( +5479 n0_nat_traversal::Event::AddressAdded(added), +5480 )); +5481 } +5482 } +5483 Err(e) => { +5484 warn!(%e, "failed to add remote address") +5485 } +5486 } +5487 } +5488 Frame::RemoveAddress(addr) => { +5489 let client_state = match self.n0_nat_traversal.client_side_mut() { +5490 Ok(state) => state, +5491 Err(err) => { +5492 return Err(TransportError::PROTOCOL_VIOLATION(format!( +5493 "Nat traversal(REMOVE_ADDRESS): {err}" +5494 ))); +5495 } +5496 }; +5497 if let Some(removed_addr) = client_state.remove_remote_address(addr) { +5498 self.events.push_back(Event::NatTraversal( +5499 n0_nat_traversal::Event::AddressRemoved(removed_addr), +5500 )); +5501 } +5502 } +5503 Frame::ReachOut(reach_out) => { +5504 let ipv6 = self.is_ipv6(); +5505 let server_state = match self.n0_nat_traversal.server_side_mut() { +5506 Ok(state) => state, +5507 Err(err) => { +5508 return Err(TransportError::PROTOCOL_VIOLATION(format!( +5509 "Nat traversal(REACH_OUT): {err}" +5510 ))); +5511 } +5512 }; +5513 +5514 let round_before = server_state.current_round(); 5515 -5516 let round_before = server_state.current_round(); -5517 -5518 if let Err(err) = server_state.handle_reach_out(reach_out, ipv6) { -5519 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5520 "Nat traversal(REACH_OUT): {err}" -5521 ))); -5522 } -5523 -5524 if server_state.current_round() > round_before { -5525 // A new round was started, reset the NAT probe retry timer. -5526 let delay = RttEstimator::new(self.config.initial_rtt).pto_base() * 2 / 3; -5527 self.timers.set( -5528 Timer::Conn(ConnTimer::NatTraversalProbeRetry), -5529 now + delay, -5530 self.qlog.with_time(now), -5531 ); -5532 } -5533 } -5534 } -5535 } -5536 -5537 let space = self.spaces[SpaceId::Data].for_path(path_id); -5538 if space -5539 .pending_acks -5540 .packet_received(now, number, ack_eliciting, &space.dedup) -5541 { -5542 if self.abandoned_paths.contains(&path_id) { -5543 // § 3.4.3 QUIC-MULTIPATH: promptly send ACKs for packets received from -5544 // abandoned paths. -5545 space.pending_acks.set_immediate_ack_required(); -5546 } else { -5547 self.timers.set( -5548 Timer::PerPath(path_id, PathTimer::MaxAckDelay), -5549 now + self.ack_frequency.max_ack_delay, -5550 self.qlog.with_time(now), -5551 ); -5552 } -5553 } -5554 -5555 // Issue stream ID credit due to ACKs of outgoing finish/resets and incoming finish/resets -5556 // on stopped streams. Incoming finishes/resets on open streams are not handled here as they -5557 // are only freed, and hence only issue credit, once the application has been notified -5558 // during a read on the stream. -5559 let pending = &mut self.spaces[SpaceId::Data].pending; -5560 self.streams.queue_max_stream_id(pending); -5561 -5562 if let Some(reason) = close { -5563 self.state.move_to_draining(Some(reason.into())); -5564 self.connection_close_pending = true; -5565 } -5566 -5567 // For Multipath any packet triggers migration. For RFC9000 or QNT (+ Multipath) -5568 // only non-probing packets trigger migration. -5569 let migrate_on_any_packet = -5570 self.is_multipath_negotiated() && !self.n0_nat_traversal.is_negotiated(); -5571 -5572 // Only migrate if this is the largest packet number seen. -5573 let is_largest_received_pn = Some(number) -5574 == self.spaces[SpaceId::Data] -5575 .for_path(path_id) -5576 .largest_received_packet_number; -5577 -5578 // If we receive a non-probing packet on a new local IP that means we had a NAT -5579 // rebinding-like migration. We update our local address but do not otherwise -5580 // validate the new path, we only need to validate the path if the peer migrates per -5581 // RFC9000 §9: https://www.rfc-editor.org/rfc/rfc9000.html#section-9-4 -5582 if (migrate_on_any_packet || !is_probing_packet) -5583 && is_largest_received_pn -5584 && self.local_ip_may_migrate() -5585 && let Some(new_local_ip) = network_path.local_ip -5586 { -5587 let path_data = self.path_data_mut(path_id); -5588 if path_data -5589 .network_path -5590 .local_ip -5591 .is_some_and(|ip| ip != new_local_ip) -5592 { -5593 debug!( -5594 %path_id, -5595 new_4tuple = %network_path, -5596 prev_4tuple = %path_data.network_path, -5597 "local address passive migration" -5598 ); -5599 } -5600 path_data.network_path.local_ip = Some(new_local_ip) -5601 } -5602 -5603 // If the peer migrated to a new address, trigger migration. -5604 if (migrate_on_any_packet || !is_probing_packet) -5605 && is_largest_received_pn -5606 && network_path.remote != self.path_data(path_id).network_path.remote -5607 && self.remote_may_migrate() -5608 { -5609 self.migrate(path_id, now, network_path, migration_observed_addr); -5610 // Break linkability, if possible -5611 self.update_remote_cid(path_id); -5612 self.spin = false; -5613 } -5614 -5615 Ok(()) -5616 } -5617 -5618 /// Opens any paths that have been successfully NAT traversed. -5619 fn open_nat_traversed_paths(&mut self, now: Instant) { -5620 while let Some(network_path) = self -5621 .n0_nat_traversal -5622 .client_side_mut() -5623 .ok() -5624 .and_then(|s| s.pop_pending_path_open()) -5625 { -5626 match self.open_path_ensure(network_path, PathStatus::Backup, now) { -5627 Ok((path_id, already_existed)) => { -5628 debug!( -5629 %path_id, -5630 ?network_path, -5631 new_path = !already_existed, -5632 "Opened NAT traversal path", -5633 ); -5634 } -5635 Err(err) => match err { -5636 PathError::MultipathNotNegotiated -5637 | PathError::ServerSideNotAllowed -5638 | PathError::ValidationFailed -5639 | PathError::InvalidRemoteAddress(_) => { -5640 error!( -5641 ?err, -5642 ?network_path, -5643 "Failed to open path for successful NAT traversal" -5644 ); -5645 } -5646 PathError::MaxPathIdReached | PathError::RemoteCidsExhausted => { -5647 // Temporary error, put back. -5648 self.n0_nat_traversal -5649 .client_side_mut() -5650 .map(|s| s.push_pending_path_open(network_path)) -5651 .ok(); -5652 debug!( -5653 ?err, -5654 ?network_path, -5655 "Blocked opening NAT traversal path, enqueued" -5656 ); -5657 return; -5658 } -5659 }, -5660 } -5661 } -5662 } -5663 -5664 /// Migrates the 4-tuple of the path. -5665 /// -5666 /// This creates a new [`PathData`] for the migrated path and stores the previous -5667 /// [`PathData`] in [`PathState::prev`]. -5668 fn migrate( -5669 &mut self, -5670 path_id: PathId, -5671 now: Instant, -5672 network_path: FourTuple, -5673 observed_addr: Option<ObservedAddr>, -5674 ) { -5675 trace!( -5676 new_4tuple = %network_path, -5677 prev_4tuple = %self.path_data(path_id).network_path, -5678 %path_id, -5679 "migration initiated", -5680 ); -5681 self.path_generation_counter = self.path_generation_counter.wrapping_add(1); -5682 // TODO(@divma): conditions for path migration in multipath are very specific, check them -5683 // again to prevent path migrations that should actually create a new path -5684 -5685 // Reset rtt/congestion state for new path unless it looks like a NAT rebinding. -5686 // Note that the congestion window will not grow until validation terminates. Helps mitigate -5687 // amplification attacks performed by spoofing source addresses. -5688 let prev_pto = self.pto(SpaceKind::Data, path_id); -5689 let path = self.paths.get_mut(&path_id).expect("known path"); -5690 let mut new_path_data = if network_path.remote.is_ipv4() -5691 && network_path.remote.ip() == path.data.network_path.remote.ip() -5692 { -5693 PathData::from_previous(network_path, &path.data, self.path_generation_counter, now) -5694 } else { -5695 let peer_max_udp_payload_size = -5696 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()) -5697 .unwrap_or(u16::MAX); -5698 PathData::new( -5699 network_path, -5700 self.allow_mtud, -5701 Some(peer_max_udp_payload_size), -5702 self.path_generation_counter, -5703 now, -5704 &self.config, -5705 ) -5706 }; -5707 new_path_data.last_observed_addr_report = path.data.last_observed_addr_report.clone(); -5708 if let Some(report) = observed_addr -5709 && let Some(updated) = new_path_data.update_observed_addr_report(report) -5710 { -5711 tracing::info!("adding observed addr event from migration"); -5712 self.events.push_back(Event::Path(PathEvent::ObservedAddr { -5713 id: path_id, -5714 addr: updated, -5715 })); -5716 } -5717 new_path_data.pending_on_path_challenge = true; +5516 if let Err(err) = server_state.handle_reach_out(reach_out, ipv6) { +5517 return Err(TransportError::PROTOCOL_VIOLATION(format!( +5518 "Nat traversal(REACH_OUT): {err}" +5519 ))); +5520 } +5521 +5522 if server_state.current_round() > round_before { +5523 // A new round was started, reset the NAT probe retry timer. +5524 let delay = RttEstimator::new(self.config.initial_rtt).pto_base() * 2 / 3; +5525 self.timers.set( +5526 Timer::Conn(ConnTimer::NatTraversalProbeRetry), +5527 now + delay, +5528 self.qlog.with_time(now), +5529 ); +5530 } +5531 } +5532 } +5533 } +5534 +5535 let space = self.spaces[SpaceId::Data].for_path(path_id); +5536 if space +5537 .pending_acks +5538 .packet_received(now, number, ack_eliciting, &space.dedup) +5539 { +5540 if self.abandoned_paths.contains(&path_id) { +5541 // § 3.4.3 QUIC-MULTIPATH: promptly send ACKs for packets received from +5542 // abandoned paths. +5543 space.pending_acks.set_immediate_ack_required(); +5544 } else { +5545 self.timers.set( +5546 Timer::PerPath(path_id, PathTimer::MaxAckDelay), +5547 now + self.ack_frequency.max_ack_delay, +5548 self.qlog.with_time(now), +5549 ); +5550 } +5551 } +5552 +5553 // Issue stream ID credit due to ACKs of outgoing finish/resets and incoming finish/resets +5554 // on stopped streams. Incoming finishes/resets on open streams are not handled here as they +5555 // are only freed, and hence only issue credit, once the application has been notified +5556 // during a read on the stream. +5557 let pending = &mut self.spaces[SpaceId::Data].pending; +5558 self.streams.queue_max_stream_id(pending); +5559 +5560 if let Some(reason) = close { +5561 self.state.move_to_draining(Some(reason.into())); +5562 self.connection_close_pending = true; +5563 } +5564 +5565 // For Multipath any packet triggers migration. For RFC9000 or QNT (+ Multipath) +5566 // only non-probing packets trigger migration. +5567 let migrate_on_any_packet = +5568 self.is_multipath_negotiated() && !self.n0_nat_traversal.is_negotiated(); +5569 +5570 // Only migrate if this is the largest packet number seen. +5571 let is_largest_received_pn = Some(number) +5572 == self.spaces[SpaceId::Data] +5573 .for_path(path_id) +5574 .largest_received_packet_number; +5575 +5576 // If we receive a non-probing packet on a new local IP that means we had a NAT +5577 // rebinding-like migration. We update our local address but do not otherwise +5578 // validate the new path, we only need to validate the path if the peer migrates per +5579 // RFC9000 §9: https://www.rfc-editor.org/rfc/rfc9000.html#section-9-4 +5580 if (migrate_on_any_packet || !is_probing_packet) +5581 && is_largest_received_pn +5582 && self.local_ip_may_migrate() +5583 && let Some(new_local_ip) = network_path.local_ip +5584 { +5585 let path_data = self.path_data_mut(path_id); +5586 if path_data +5587 .network_path +5588 .local_ip +5589 .is_some_and(|ip| ip != new_local_ip) +5590 { +5591 debug!( +5592 %path_id, +5593 new_4tuple = %network_path, +5594 prev_4tuple = %path_data.network_path, +5595 "local address passive migration" +5596 ); +5597 } +5598 path_data.network_path.local_ip = Some(new_local_ip) +5599 } +5600 +5601 // If the peer migrated to a new address, trigger migration. +5602 if (migrate_on_any_packet || !is_probing_packet) +5603 && is_largest_received_pn +5604 && network_path.remote != self.path_data(path_id).network_path.remote +5605 && self.remote_may_migrate() +5606 { +5607 self.migrate(path_id, now, network_path, migration_observed_addr); +5608 // Break linkability, if possible +5609 self.update_remote_cid(path_id); +5610 self.spin = false; +5611 } +5612 +5613 Ok(()) +5614 } +5615 +5616 /// Opens any paths that have been successfully NAT traversed. +5617 fn open_nat_traversed_paths(&mut self, now: Instant) { +5618 while let Some(network_path) = self +5619 .n0_nat_traversal +5620 .client_side_mut() +5621 .ok() +5622 .and_then(|s| s.pop_pending_path_open()) +5623 { +5624 match self.open_path_ensure(network_path, PathStatus::Backup, now) { +5625 Ok((path_id, already_existed)) => { +5626 debug!( +5627 %path_id, +5628 ?network_path, +5629 new_path = !already_existed, +5630 "Opened NAT traversal path", +5631 ); +5632 } +5633 Err(err) => match err { +5634 PathError::MultipathNotNegotiated +5635 | PathError::ServerSideNotAllowed +5636 | PathError::ValidationFailed +5637 | PathError::InvalidRemoteAddress(_) => { +5638 error!( +5639 ?err, +5640 ?network_path, +5641 "Failed to open path for successful NAT traversal" +5642 ); +5643 } +5644 PathError::MaxPathIdReached | PathError::RemoteCidsExhausted => { +5645 // Temporary error, put back. +5646 self.n0_nat_traversal +5647 .client_side_mut() +5648 .map(|s| s.push_pending_path_open(network_path)) +5649 .ok(); +5650 debug!( +5651 ?err, +5652 ?network_path, +5653 "Blocked opening NAT traversal path, enqueued" +5654 ); +5655 return; +5656 } +5657 }, +5658 } +5659 } +5660 } +5661 +5662 /// Migrates the 4-tuple of the path. +5663 /// +5664 /// This creates a new [`PathData`] for the migrated path and stores the previous +5665 /// [`PathData`] in [`PathState::prev`]. +5666 fn migrate( +5667 &mut self, +5668 path_id: PathId, +5669 now: Instant, +5670 network_path: FourTuple, +5671 observed_addr: Option<ObservedAddr>, +5672 ) { +5673 trace!( +5674 new_4tuple = %network_path, +5675 prev_4tuple = %self.path_data(path_id).network_path, +5676 %path_id, +5677 "migration initiated", +5678 ); +5679 self.path_generation_counter = self.path_generation_counter.wrapping_add(1); +5680 // TODO(@divma): conditions for path migration in multipath are very specific, check them +5681 // again to prevent path migrations that should actually create a new path +5682 +5683 // Reset rtt/congestion state for new path unless it looks like a NAT rebinding. +5684 // Note that the congestion window will not grow until validation terminates. Helps mitigate +5685 // amplification attacks performed by spoofing source addresses. +5686 let prev_pto = self.pto(SpaceKind::Data, path_id); +5687 let path = self.paths.get_mut(&path_id).expect("known path"); +5688 let mut new_path_data = if network_path.remote.is_ipv4() +5689 && network_path.remote.ip() == path.data.network_path.remote.ip() +5690 { +5691 PathData::from_previous(network_path, &path.data, self.path_generation_counter, now) +5692 } else { +5693 let peer_max_udp_payload_size = +5694 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()) +5695 .unwrap_or(u16::MAX); +5696 PathData::new( +5697 network_path, +5698 self.allow_mtud, +5699 Some(peer_max_udp_payload_size), +5700 self.path_generation_counter, +5701 now, +5702 &self.config, +5703 ) +5704 }; +5705 new_path_data.last_observed_addr_report = path.data.last_observed_addr_report.clone(); +5706 if let Some(report) = observed_addr +5707 && let Some(updated) = new_path_data.update_observed_addr_report(report) +5708 { +5709 tracing::info!("adding observed addr event from migration"); +5710 self.events.push_back(Event::Path(PathEvent::ObservedAddr { +5711 id: path_id, +5712 addr: updated, +5713 })); +5714 } +5715 new_path_data.pending_on_path_challenge = true; +5716 +5717 let mut prev_path_data = mem::replace(&mut path.data, new_path_data); 5718 -5719 let mut prev_path_data = mem::replace(&mut path.data, new_path_data); -5720 -5721 // Only store this as previous path if it was validated. For all we know there could -5722 // already be a previous path stored which might have been validated in the past, -5723 // which is more valuable than one that's not yet validated. -5724 // -5725 // With multipath it is possible that there are no remote CIDs for the path ID -5726 // yet. In this case we would never have sent on this path yet and would not be able -5727 // to send a PATH_CHALLENGE either, which is currently a fire-and-forget affair -5728 // anyway. So don't store such a path either. -5729 if !prev_path_data.validated -5730 && let Some(cid) = self.remote_cids.get(&path_id).map(CidQueue::active) -5731 { -5732 prev_path_data.pending_on_path_challenge = true; -5733 // We haven't updated the remote CID yet, this captures the remote CID we were using on -5734 // the previous path. -5735 path.prev = Some((cid, prev_path_data)); -5736 } -5737 -5738 // We need to re-assign the correct remote to this path in qlog -5739 self.qlog.emit_tuple_assigned(path_id, network_path, now); -5740 -5741 self.timers.set( -5742 Timer::PerPath(path_id, PathTimer::PathValidationFailed), -5743 now + 3 * cmp::max(self.pto(SpaceKind::Data, path_id), prev_pto), -5744 self.qlog.with_time(now), -5745 ); -5746 } -5747 -5748 /// Handle a change in the local address, i.e. an active migration -5749 /// -5750 /// In the general (non-multipath) case, paths will perform a RFC9000 migration and be pinged -5751 /// for a liveness check. This is the behaviour of a path assumed to be recoverable, even if -5752 /// this is not the case. -5753 /// -5754 /// Clients in a connection in which multipath has been negotiated should migrate paths to new -5755 /// [`PathId`]s. For paths that are known to be non-recoverable can be migrated to a new -5756 /// [`PathId`] by closing the current path, and opening a new one to the same remote. Treating -5757 /// paths as non recoverable when necessary accelerates connectivity re-establishment, or might -5758 /// allow it altogether. -5759 /// -5760 /// The optional `hint` allows callers to indicate when paths are non-recoverable and should be -5761 /// migrated to new a [`PathId`]. -5762 // NOTE: only clients are allowed to migrate, but generally dealing with RFC9000 migrations is -5763 // lacking <https://github.com/n0-computer/noq/issues/364> -5764 pub fn handle_network_change(&mut self, hint: Option<&dyn NetworkChangeHint>, now: Instant) { -5765 debug!("network changed"); -5766 if self.state.is_drained() { -5767 return; -5768 } -5769 if self.highest_space < SpaceKind::Data { -5770 for path in self.paths.values_mut() { -5771 // Clear the local address for it to be obtained from the socket again. -5772 path.data.network_path.local_ip = None; -5773 } -5774 -5775 self.update_remote_cid(PathId::ZERO); -5776 self.ping(); -5777 -5778 return; -5779 } -5780 -5781 // Paths that can't recover so a new path should be open instead. If multipath is not -5782 // negotiated, this will be empty. -5783 let mut non_recoverable_paths = Vec::default(); -5784 let mut recoverable_paths = Vec::default(); -5785 let mut open_paths = 0; -5786 -5787 let is_multipath_negotiated = self.is_multipath_negotiated(); -5788 let is_client = self.side().is_client(); -5789 let immediate_ack_allowed = self.peer_supports_ack_frequency(); -5790 -5791 for (path_id, path) in self.paths.iter_mut() { -5792 if self.abandoned_paths.contains(path_id) { -5793 continue; -5794 } -5795 open_paths += 1; -5796 -5797 // Read the network path BEFORE clearing local_ip, so the hint can -5798 // check which interface the path was using. -5799 let network_path = path.data.network_path; -5800 -5801 // Clear the local address for it to be obtained from the socket again. This applies to -5802 // all paths, regardless of being considered recoverable or not -5803 path.data.network_path.local_ip = None; -5804 let remote = network_path.remote; -5805 -5806 // Without multipath, the connection tries to recover the single path, whereas with -5807 // multipath, even in a single-path scenario, we attempt to migrate the path to a new -5808 // PathId. -5809 let attempt_to_recover = if is_multipath_negotiated { -5810 // Use the hint to determine if the path can recover. When no hint is -5811 // provided, clients default to non-recoverable (abandon and re-open) -5812 // while servers default to recoverable (attempt in-place recovery). -5813 hint.map(|h| h.is_path_recoverable(*path_id, network_path)) -5814 .unwrap_or(!is_client) -5815 } else { -5816 // In the non multipath case, we try to recover the single active path -5817 true -5818 }; -5819 -5820 if attempt_to_recover { -5821 recoverable_paths.push((*path_id, remote)); -5822 } else { -5823 non_recoverable_paths.push((*path_id, remote, path.data.local_status())) -5824 } -5825 } -5826 -5827 /* NON RECOVERABLE PATHS */ -5828 // This are handled first, so that in case the treatment intended for these fails, we can -5829 // go the recoverable route instead. -5830 -5831 // Decide if we need to close first or open first in the multipath case. -5832 // - Opening first has a higher risk of getting limited by the negotiated MAX_PATH_ID. -5833 // - Closing first risks this being the only open path. -5834 // We prefer closing paths first unless we identify this is the last open path. -5835 let open_first = open_paths == non_recoverable_paths.len(); -5836 -5837 for (path_id, remote, status) in non_recoverable_paths.into_iter() { -5838 let network_path = FourTuple { -5839 remote, -5840 local_ip: None, /* allow the local ip to be discovered */ -5841 }; -5842 -5843 if open_first && let Err(e) = self.open_path(network_path, status, now) { -5844 if self.side().is_client() { -5845 debug!(%e, "Failed to open new path for network change"); -5846 } -5847 // if this fails, let the path try to recover itself -5848 recoverable_paths.push((path_id, remote)); -5849 continue; -5850 } -5851 -5852 if let Err(e) = -5853 self.close_path_inner(now, path_id, PathAbandonReason::UnusableAfterNetworkChange) -5854 { -5855 debug!(%e,"Failed to close unrecoverable path after network change"); -5856 recoverable_paths.push((path_id, remote)); -5857 continue; -5858 } -5859 -5860 if !open_first && let Err(e) = self.open_path(network_path, status, now) { -5861 // Path has already been closed if we got here. Since the path was not recoverable, -5862 // this might be desirable in any case, because other paths exist (!open_first) and -5863 // this was is considered non recoverable -5864 debug!(%e,"Failed to open new path for network change"); -5865 } -5866 } +5719 // Only store this as previous path if it was validated. For all we know there could +5720 // already be a previous path stored which might have been validated in the past, +5721 // which is more valuable than one that's not yet validated. +5722 // +5723 // With multipath it is possible that there are no remote CIDs for the path ID +5724 // yet. In this case we would never have sent on this path yet and would not be able +5725 // to send a PATH_CHALLENGE either, which is currently a fire-and-forget affair +5726 // anyway. So don't store such a path either. +5727 if !prev_path_data.validated +5728 && let Some(cid) = self.remote_cids.get(&path_id).map(CidQueue::active) +5729 { +5730 prev_path_data.pending_on_path_challenge = true; +5731 // We haven't updated the remote CID yet, this captures the remote CID we were using on +5732 // the previous path. +5733 path.prev = Some((cid, prev_path_data)); +5734 } +5735 +5736 // We need to re-assign the correct remote to this path in qlog +5737 self.qlog.emit_tuple_assigned(path_id, network_path, now); +5738 +5739 self.timers.set( +5740 Timer::PerPath(path_id, PathTimer::PathValidationFailed), +5741 now + 3 * cmp::max(self.pto(SpaceKind::Data, path_id), prev_pto), +5742 self.qlog.with_time(now), +5743 ); +5744 } +5745 +5746 /// Handle a change in the local address, i.e. an active migration +5747 /// +5748 /// In the general (non-multipath) case, paths will perform a RFC9000 migration and be pinged +5749 /// for a liveness check. This is the behaviour of a path assumed to be recoverable, even if +5750 /// this is not the case. +5751 /// +5752 /// Clients in a connection in which multipath has been negotiated should migrate paths to new +5753 /// [`PathId`]s. For paths that are known to be non-recoverable can be migrated to a new +5754 /// [`PathId`] by closing the current path, and opening a new one to the same remote. Treating +5755 /// paths as non recoverable when necessary accelerates connectivity re-establishment, or might +5756 /// allow it altogether. +5757 /// +5758 /// The optional `hint` allows callers to indicate when paths are non-recoverable and should be +5759 /// migrated to new a [`PathId`]. +5760 // NOTE: only clients are allowed to migrate, but generally dealing with RFC9000 migrations is +5761 // lacking <https://github.com/n0-computer/noq/issues/364> +5762 pub fn handle_network_change(&mut self, hint: Option<&dyn NetworkChangeHint>, now: Instant) { +5763 debug!("network changed"); +5764 if self.state.is_drained() { +5765 return; +5766 } +5767 if self.highest_space < SpaceKind::Data { +5768 for path in self.paths.values_mut() { +5769 // Clear the local address for it to be obtained from the socket again. +5770 path.data.network_path.local_ip = None; +5771 } +5772 +5773 self.update_remote_cid(PathId::ZERO); +5774 self.ping(); +5775 +5776 return; +5777 } +5778 +5779 // Paths that can't recover so a new path should be open instead. If multipath is not +5780 // negotiated, this will be empty. +5781 let mut non_recoverable_paths = Vec::default(); +5782 let mut recoverable_paths = Vec::default(); +5783 let mut open_paths = 0; +5784 +5785 let is_multipath_negotiated = self.is_multipath_negotiated(); +5786 let is_client = self.side().is_client(); +5787 let immediate_ack_allowed = self.peer_supports_ack_frequency(); +5788 +5789 for (path_id, path) in self.paths.iter_mut() { +5790 if self.abandoned_paths.contains(path_id) { +5791 continue; +5792 } +5793 open_paths += 1; +5794 +5795 // Read the network path BEFORE clearing local_ip, so the hint can +5796 // check which interface the path was using. +5797 let network_path = path.data.network_path; +5798 +5799 // Clear the local address for it to be obtained from the socket again. This applies to +5800 // all paths, regardless of being considered recoverable or not +5801 path.data.network_path.local_ip = None; +5802 let remote = network_path.remote; +5803 +5804 // Without multipath, the connection tries to recover the single path, whereas with +5805 // multipath, even in a single-path scenario, we attempt to migrate the path to a new +5806 // PathId. +5807 let attempt_to_recover = if is_multipath_negotiated { +5808 // Use the hint to determine if the path can recover. When no hint is +5809 // provided, clients default to non-recoverable (abandon and re-open) +5810 // while servers default to recoverable (attempt in-place recovery). +5811 hint.map(|h| h.is_path_recoverable(*path_id, network_path)) +5812 .unwrap_or(!is_client) +5813 } else { +5814 // In the non multipath case, we try to recover the single active path +5815 true +5816 }; +5817 +5818 if attempt_to_recover { +5819 recoverable_paths.push((*path_id, remote)); +5820 } else { +5821 non_recoverable_paths.push((*path_id, remote, path.data.local_status())) +5822 } +5823 } +5824 +5825 /* NON RECOVERABLE PATHS */ +5826 // This are handled first, so that in case the treatment intended for these fails, we can +5827 // go the recoverable route instead. +5828 +5829 // Decide if we need to close first or open first in the multipath case. +5830 // - Opening first has a higher risk of getting limited by the negotiated MAX_PATH_ID. +5831 // - Closing first risks this being the only open path. +5832 // We prefer closing paths first unless we identify this is the last open path. +5833 let open_first = open_paths == non_recoverable_paths.len(); +5834 +5835 for (path_id, remote, status) in non_recoverable_paths.into_iter() { +5836 let network_path = FourTuple { +5837 remote, +5838 local_ip: None, /* allow the local ip to be discovered */ +5839 }; +5840 +5841 if open_first && let Err(e) = self.open_path(network_path, status, now) { +5842 if self.side().is_client() { +5843 debug!(%e, "Failed to open new path for network change"); +5844 } +5845 // if this fails, let the path try to recover itself +5846 recoverable_paths.push((path_id, remote)); +5847 continue; +5848 } +5849 +5850 if let Err(e) = +5851 self.close_path_inner(now, path_id, PathAbandonReason::UnusableAfterNetworkChange) +5852 { +5853 debug!(%e,"Failed to close unrecoverable path after network change"); +5854 recoverable_paths.push((path_id, remote)); +5855 continue; +5856 } +5857 +5858 if !open_first && let Err(e) = self.open_path(network_path, status, now) { +5859 // Path has already been closed if we got here. Since the path was not recoverable, +5860 // this might be desirable in any case, because other paths exist (!open_first) and +5861 // this was is considered non recoverable +5862 debug!(%e,"Failed to open new path for network change"); +5863 } +5864 } +5865 +5866 /* RECOVERABLE PATHS */ 5867 -5868 /* RECOVERABLE PATHS */ -5869 -5870 for (path_id, remote) in recoverable_paths.into_iter() { -5871 // Schedule a Ping for a liveness check. -5872 if let Some(path_space) = self.spaces[SpaceId::Data].number_spaces.get_mut(&path_id) { -5873 path_space.ping_pending = true; -5874 -5875 if immediate_ack_allowed { -5876 path_space.immediate_ack_pending = true; -5877 } -5878 } -5879 -5880 // Reset PTO backoff so retransmits resume promptly. Congestion controller and -5881 // RTT are intentionally preserved for recoverable paths. We explicitly allow -5882 // this reset also during the handshake, so do not check -5883 // Self::peer_competed_handshake_address_validation. -5884 if let Some(path) = self.paths.get_mut(&path_id) { -5885 path.data.pto_count = 0; -5886 } -5887 self.set_loss_detection_timer(now, path_id); -5888 -5889 let Some((reset_token, retired)) = -5890 self.remote_cids.get_mut(&path_id).and_then(CidQueue::next) -5891 else { -5892 continue; -5893 }; -5894 -5895 // Retire the current remote CID and any CIDs we had to skip. -5896 self.spaces[SpaceId::Data] -5897 .pending -5898 .retire_cids -5899 .extend(retired.map(|seq| (path_id, seq))); -5900 -5901 debug_assert!(!self.state.is_drained()); // required for endpoint_events, checked above -5902 self.endpoint_events -5903 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); -5904 } -5905 } -5906 -5907 /// Switch to a previously unused remote connection ID, if possible -5908 fn update_remote_cid(&mut self, path_id: PathId) { -5909 let Some((reset_token, retired)) = self -5910 .remote_cids -5911 .get_mut(&path_id) -5912 .and_then(|cids| cids.next()) -5913 else { -5914 return; -5915 }; -5916 -5917 // Retire the current remote CID and any CIDs we had to skip. -5918 self.spaces[SpaceId::Data] -5919 .pending -5920 .retire_cids -5921 .extend(retired.map(|seq| (path_id, seq))); -5922 let remote = self.path_data(path_id).network_path.remote; -5923 self.set_reset_token(path_id, remote, reset_token); -5924 } -5925 -5926 /// Sends this reset token to the endpoint -5927 /// -5928 /// The endpoint needs to know the reset tokens issued by the peer, so that if the peer -5929 /// sends a reset token it knows to route it to this connection. See RFC 9000 section -5930 /// 10.3. Stateless Reset. -5931 /// -5932 /// Reset tokens are different for each path, the endpoint identifies paths by peer -5933 /// socket address however, not by path ID. -5934 fn set_reset_token(&mut self, path_id: PathId, remote: SocketAddr, reset_token: ResetToken) { -5935 debug_assert!(!self.state.is_drained()); // required for endpoint events, set_reset_token is never called for drained connections -5936 self.endpoint_events -5937 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); -5938 -5939 // During the handshake the server sends a reset token in the transport -5940 // parameters. When we are the client and we receive the reset token during the -5941 // handshake we want this to affect our peer transport parameters. -5942 // TODO(flub): Pretty sure this is pointless, the entire params is overwritten -5943 // shortly after this was called. And then the params don't have this anymore. -5944 if path_id == PathId::ZERO { -5945 self.peer_params.stateless_reset_token = Some(reset_token); -5946 } -5947 } -5948 -5949 /// Issue an initial set of connection IDs to the peer upon connection -5950 fn issue_first_cids(&mut self, now: Instant) { -5951 if self -5952 .local_cid_state -5953 .get(&PathId::ZERO) -5954 .expect("PathId::ZERO exists when the connection is created") -5955 .cid_len() -5956 == 0 -5957 { -5958 return; -5959 } -5960 -5961 // Subtract 1 to account for the CID we supplied while handshaking -5962 let mut n = self.peer_params.issue_cids_limit() - 1; -5963 if let ConnectionSide::Server { server_config } = &self.side -5964 && server_config.has_preferred_address() -5965 { -5966 // We also sent a CID in the transport parameters -5967 n -= 1; -5968 } -5969 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events -5970 self.endpoint_events -5971 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); -5972 } -5973 -5974 /// Issues an initial set of CIDs for paths that have not yet had any CIDs issued -5975 /// -5976 /// Later CIDs are issued when CIDs expire or are retired by the peer. -5977 fn issue_first_path_cids(&mut self, now: Instant) { -5978 if let Some(max_path_id) = self.max_path_id() { -5979 let mut path_id = self.max_path_id_with_cids.next(); -5980 while path_id <= max_path_id { -5981 self.endpoint_events -5982 .push_back(EndpointEventInner::NeedIdentifiers( -5983 path_id, -5984 now, -5985 self.peer_params.issue_cids_limit(), -5986 )); -5987 path_id = path_id.next(); -5988 } -5989 self.max_path_id_with_cids = max_path_id; -5990 } -5991 } -5992 -5993 /// Populates a packet with frames +5868 for (path_id, remote) in recoverable_paths.into_iter() { +5869 // Schedule a Ping for a liveness check. +5870 if let Some(path_space) = self.spaces[SpaceId::Data].number_spaces.get_mut(&path_id) { +5871 path_space.ping_pending = true; +5872 +5873 if immediate_ack_allowed { +5874 path_space.immediate_ack_pending = true; +5875 } +5876 } +5877 +5878 // Reset PTO backoff so retransmits resume promptly. Congestion controller and +5879 // RTT are intentionally preserved for recoverable paths. We explicitly allow +5880 // this reset also during the handshake, so do not check +5881 // Self::peer_competed_handshake_address_validation. +5882 if let Some(path) = self.paths.get_mut(&path_id) { +5883 path.data.pto_count = 0; +5884 } +5885 self.set_loss_detection_timer(now, path_id); +5886 +5887 let Some((reset_token, retired)) = +5888 self.remote_cids.get_mut(&path_id).and_then(CidQueue::next) +5889 else { +5890 continue; +5891 }; +5892 +5893 // Retire the current remote CID and any CIDs we had to skip. +5894 self.spaces[SpaceId::Data] +5895 .pending +5896 .retire_cids +5897 .extend(retired.map(|seq| (path_id, seq))); +5898 +5899 debug_assert!(!self.state.is_drained()); // required for endpoint_events, checked above +5900 self.endpoint_events +5901 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); +5902 } +5903 } +5904 +5905 /// Switch to a previously unused remote connection ID, if possible +5906 fn update_remote_cid(&mut self, path_id: PathId) { +5907 let Some((reset_token, retired)) = self +5908 .remote_cids +5909 .get_mut(&path_id) +5910 .and_then(|cids| cids.next()) +5911 else { +5912 return; +5913 }; +5914 +5915 // Retire the current remote CID and any CIDs we had to skip. +5916 self.spaces[SpaceId::Data] +5917 .pending +5918 .retire_cids +5919 .extend(retired.map(|seq| (path_id, seq))); +5920 let remote = self.path_data(path_id).network_path.remote; +5921 self.set_reset_token(path_id, remote, reset_token); +5922 } +5923 +5924 /// Sends this reset token to the endpoint +5925 /// +5926 /// The endpoint needs to know the reset tokens issued by the peer, so that if the peer +5927 /// sends a reset token it knows to route it to this connection. See RFC 9000 section +5928 /// 10.3. Stateless Reset. +5929 /// +5930 /// Reset tokens are different for each path, the endpoint identifies paths by peer +5931 /// socket address however, not by path ID. +5932 fn set_reset_token(&mut self, path_id: PathId, remote: SocketAddr, reset_token: ResetToken) { +5933 debug_assert!(!self.state.is_drained()); // required for endpoint events, set_reset_token is never called for drained connections +5934 self.endpoint_events +5935 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); +5936 +5937 // During the handshake the server sends a reset token in the transport +5938 // parameters. When we are the client and we receive the reset token during the +5939 // handshake we want this to affect our peer transport parameters. +5940 // TODO(flub): Pretty sure this is pointless, the entire params is overwritten +5941 // shortly after this was called. And then the params don't have this anymore. +5942 if path_id == PathId::ZERO { +5943 self.peer_params.stateless_reset_token = Some(reset_token); +5944 } +5945 } +5946 +5947 /// Issue an initial set of connection IDs to the peer upon connection +5948 fn issue_first_cids(&mut self, now: Instant) { +5949 if self +5950 .local_cid_state +5951 .get(&PathId::ZERO) +5952 .expect("PathId::ZERO exists when the connection is created") +5953 .cid_len() +5954 == 0 +5955 { +5956 return; +5957 } +5958 +5959 // Subtract 1 to account for the CID we supplied while handshaking +5960 let mut n = self.peer_params.issue_cids_limit() - 1; +5961 if let ConnectionSide::Server { server_config } = &self.side +5962 && server_config.has_preferred_address() +5963 { +5964 // We also sent a CID in the transport parameters +5965 n -= 1; +5966 } +5967 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events +5968 self.endpoint_events +5969 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); +5970 } +5971 +5972 /// Issues an initial set of CIDs for paths that have not yet had any CIDs issued +5973 /// +5974 /// Later CIDs are issued when CIDs expire or are retired by the peer. +5975 fn issue_first_path_cids(&mut self, now: Instant) { +5976 if let Some(max_path_id) = self.max_path_id() { +5977 let mut path_id = self.max_path_id_with_cids.next(); +5978 while path_id <= max_path_id { +5979 self.endpoint_events +5980 .push_back(EndpointEventInner::NeedIdentifiers( +5981 path_id, +5982 now, +5983 self.peer_params.issue_cids_limit(), +5984 )); +5985 path_id = path_id.next(); +5986 } +5987 self.max_path_id_with_cids = max_path_id; +5988 } +5989 } +5990 +5991 /// Populates a packet with frames +5992 /// +5993 /// This tries to fit as many frames as possible into the packet. 5994 /// -5995 /// This tries to fit as many frames as possible into the packet. -5996 /// -5997 /// *path_exclusive_only* means to only build frames which can only be sent on this -5998 /// *path. This is used in multipath for backup paths while there is still an active -5999 /// *path. -6000 fn populate_packet<'a, 'b>( -6001 &mut self, -6002 now: Instant, -6003 space_id: SpaceId, -6004 path_id: PathId, -6005 scheduling_info: &PathSchedulingInfo, -6006 builder: &mut PacketBuilder<'a, 'b>, -6007 ) { -6008 let is_multipath_negotiated = self.is_multipath_negotiated(); -6009 let space_has_keys = self.crypto_state.has_keys(space_id.encryption_level()); -6010 let is_0rtt = space_id == SpaceId::Data && !space_has_keys; -6011 let stats = &mut self.path_stats.for_path(path_id).frame_tx; -6012 let space = &mut self.spaces[space_id]; -6013 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; -6014 space -6015 .for_path(path_id) -6016 .pending_acks -6017 .maybe_ack_non_eliciting(); -6018 -6019 // HANDSHAKE_DONE -6020 if !is_0rtt -6021 && !scheduling_info.is_abandoned -6022 && scheduling_info.may_send_data -6023 && mem::replace(&mut space.pending.handshake_done, false) -6024 { -6025 builder.write_frame(frame::HandshakeDone, stats); -6026 } -6027 -6028 // PING -6029 if !scheduling_info.is_abandoned -6030 && mem::replace(&mut space.for_path(path_id).ping_pending, false) -6031 { -6032 builder.write_frame(frame::Ping, stats); -6033 } -6034 -6035 // IMMEDIATE_ACK -6036 if !scheduling_info.is_abandoned -6037 && mem::replace(&mut space.for_path(path_id).immediate_ack_pending, false) -6038 { -6039 debug_assert_eq!( -6040 space_id, -6041 SpaceId::Data, -6042 "immediate acks must be sent in the data space" -6043 ); -6044 builder.write_frame(frame::ImmediateAck, stats); -6045 } -6046 -6047 // ACK -6048 if !scheduling_info.is_abandoned && scheduling_info.may_send_data { -6049 for path_id in space -6050 .number_spaces -6051 .iter_mut() -6052 .filter(|(_, pns)| pns.pending_acks.can_send()) -6053 .map(|(&path_id, _)| path_id) -6054 .collect::<Vec<_>>() -6055 { -6056 Self::populate_acks( -6057 now, -6058 self.receiving_ecn, -6059 path_id, -6060 space_id, -6061 space, -6062 is_multipath_negotiated, -6063 builder, -6064 stats, -6065 space_has_keys, -6066 ); -6067 } -6068 } -6069 -6070 // ACK_FREQUENCY -6071 if !scheduling_info.is_abandoned -6072 && scheduling_info.may_send_data -6073 && mem::replace(&mut space.pending.ack_frequency, false) -6074 { -6075 let sequence_number = self.ack_frequency.next_sequence_number(); -6076 -6077 // Safe to unwrap because this is always provided when ACK frequency is enabled -6078 let config = self.config.ack_frequency_config.as_ref().unwrap(); -6079 -6080 // Ensure the delay is within bounds to avoid a PROTOCOL_VIOLATION error -6081 let max_ack_delay = self.ack_frequency.candidate_max_ack_delay( -6082 path.rtt.get(), -6083 config, -6084 &self.peer_params, -6085 ); -6086 -6087 let frame = frame::AckFrequency { -6088 sequence: sequence_number, -6089 ack_eliciting_threshold: config.ack_eliciting_threshold, -6090 request_max_ack_delay: max_ack_delay.as_micros().try_into().unwrap_or(VarInt::MAX), -6091 reordering_threshold: config.reordering_threshold, -6092 }; -6093 builder.write_frame(frame, stats); -6094 -6095 self.ack_frequency -6096 .ack_frequency_sent(path_id, builder.packet_number, max_ack_delay); -6097 } -6098 -6099 // PATH_CHALLENGE -6100 if !scheduling_info.is_abandoned -6101 && space_id == SpaceId::Data -6102 && path.pending_on_path_challenge -6103 && !self.state.is_closed() -6104 && builder.frame_space_remaining() > frame::PathChallenge::SIZE_BOUND -6105 // we don't want to send new challenges if we are already closing -6106 { -6107 path.pending_on_path_challenge = false; -6108 -6109 let token = self.rng.random(); -6110 path.record_path_challenge_sent(now, token, path.network_path); -6111 // Generate a new challenge every time we send a new PATH_CHALLENGE -6112 let challenge = frame::PathChallenge(token); -6113 builder.write_frame(challenge, stats); -6114 builder.require_padding(); -6115 let pto = self.ack_frequency.max_ack_delay_for_pto() + path.rtt.pto_base(); -6116 match path.open_status { -6117 paths::OpenStatus::Sent | paths::OpenStatus::Informed => {} -6118 paths::OpenStatus::Pending => { -6119 path.open_status = paths::OpenStatus::Sent; -6120 self.timers.set( -6121 Timer::PerPath(path_id, PathTimer::AbandonFromValidation), -6122 now + 3 * pto, -6123 self.qlog.with_time(now), -6124 ); -6125 } -6126 } -6127 -6128 self.timers.set( -6129 Timer::PerPath(path_id, PathTimer::PathChallengeLost), -6130 now + pto, -6131 self.qlog.with_time(now), -6132 ); -6133 -6134 if is_multipath_negotiated && !path.validated && path.pending_on_path_challenge { -6135 // queue informing the path status along with the challenge -6136 space.pending.path_status.insert(path_id); -6137 } -6138 -6139 // Always include an OBSERVED_ADDR frame with a PATH_CHALLENGE, regardless -6140 // of whether one has already been sent on this path. -6141 if space_id == SpaceId::Data -6142 && self -6143 .config -6144 .address_discovery_role -6145 .should_report(&self.peer_params.address_discovery_role) -6146 { -6147 let frame = frame::ObservedAddr::new( -6148 path.network_path.remote, -6149 self.next_observed_addr_seq_no, -6150 ); -6151 if builder.frame_space_remaining() > frame.size() { -6152 builder.write_frame(frame, stats); -6153 -6154 self.next_observed_addr_seq_no = -6155 self.next_observed_addr_seq_no.saturating_add(1u8); -6156 path.observed_addr_sent = true; -6157 -6158 space.pending.observed_addr = false; -6159 } -6160 } -6161 } -6162 -6163 // PATH_RESPONSE -6164 if !scheduling_info.is_abandoned -6165 && space_id == SpaceId::Data -6166 && builder.frame_space_remaining() > frame::PathResponse::SIZE_BOUND -6167 && let Some(token) = path.path_responses.pop_on_path(path.network_path) -6168 { -6169 let response = frame::PathResponse(token); -6170 builder.write_frame(response, stats); -6171 builder.require_padding(); -6172 -6173 // NOTE: this is technically not required but might be useful to ride the -6174 // request/response nature of path challenges to refresh an observation -6175 // Since PATH_RESPONSE is a probing frame, this is allowed by the spec. -6176 if space_id == SpaceId::Data -6177 && self -6178 .config -6179 .address_discovery_role -6180 .should_report(&self.peer_params.address_discovery_role) -6181 { -6182 let frame = frame::ObservedAddr::new( -6183 path.network_path.remote, -6184 self.next_observed_addr_seq_no, -6185 ); -6186 if builder.frame_space_remaining() > frame.size() { -6187 builder.write_frame(frame, stats); -6188 -6189 self.next_observed_addr_seq_no = -6190 self.next_observed_addr_seq_no.saturating_add(1u8); -6191 path.observed_addr_sent = true; -6192 -6193 space.pending.observed_addr = false; -6194 } -6195 } -6196 } -6197 -6198 // REACH_OUT -6199 while !scheduling_info.is_abandoned -6200 && scheduling_info.may_send_data -6201 && let Some(reach_out) = space -6202 .pending -6203 .reach_out -6204 .pop_if(|frame| builder.frame_space_remaining() >= frame.size()) -6205 { -6206 builder.write_frame(reach_out, stats); -6207 } -6208 -6209 // PATH_ABANDON -6210 if space_id == SpaceId::Data -6211 && scheduling_info.is_abandoned -6212 && scheduling_info.may_self_abandon -6213 && frame::PathAbandon::SIZE_BOUND <= builder.frame_space_remaining() -6214 && let Some(error_code) = space.pending.path_abandon.remove(&path_id) -6215 { -6216 let frame = frame::PathAbandon { -6217 path_id, -6218 error_code, -6219 }; -6220 builder.write_frame(frame, stats); -6221 -6222 // Consider remotely issued CIDs as retired now that we have sent this frame at -6223 // least once. -6224 self.remote_cids.remove(&path_id); -6225 } -6226 while space_id == SpaceId::Data -6227 && scheduling_info.may_send_data -6228 && frame::PathAbandon::SIZE_BOUND <= builder.frame_space_remaining() -6229 && let Some((abandoned_path_id, error_code)) = space.pending.path_abandon.pop_first() -6230 { -6231 let frame = frame::PathAbandon { -6232 path_id: abandoned_path_id, -6233 error_code, -6234 }; -6235 builder.write_frame(frame, stats); -6236 -6237 // Consider remotely issued CIDs as retired now that we have sent this frame at -6238 // least once. -6239 self.remote_cids.remove(&abandoned_path_id); -6240 } -6241 -6242 // OBSERVED_ADDR -6243 if !scheduling_info.is_abandoned -6244 && scheduling_info.may_send_data -6245 && space_id == SpaceId::Data -6246 && self -6247 .config -6248 .address_discovery_role -6249 .should_report(&self.peer_params.address_discovery_role) -6250 && (!path.observed_addr_sent || space.pending.observed_addr) -6251 { -6252 let frame = -6253 frame::ObservedAddr::new(path.network_path.remote, self.next_observed_addr_seq_no); -6254 if builder.frame_space_remaining() > frame.size() { -6255 builder.write_frame(frame, stats); -6256 -6257 self.next_observed_addr_seq_no = self.next_observed_addr_seq_no.saturating_add(1u8); -6258 path.observed_addr_sent = true; -6259 -6260 space.pending.observed_addr = false; -6261 } -6262 } -6263 -6264 // CRYPTO -6265 while !is_0rtt -6266 && !scheduling_info.is_abandoned -6267 && scheduling_info.may_send_data -6268 && builder.frame_space_remaining() > frame::Crypto::SIZE_BOUND -6269 { -6270 let Some(mut frame) = space.pending.crypto.pop_front() else { -6271 break; -6272 }; -6273 -6274 // Calculate the maximum amount of crypto data we can store in the buffer. -6275 // Since the offset is known, we can reserve the exact size required to encode it. -6276 // For length we reserve 2bytes which allows to encode up to 2^14, -6277 // which is more than what fits into normally sized QUIC frames. -6278 let max_crypto_data_size = builder.frame_space_remaining() -6279 - 1 // Frame Type -6280 - VarInt::size(unsafe { VarInt::from_u64_unchecked(frame.offset) }) -6281 - 2; // Maximum encoded length for frame size, given we send less than 2^14 bytes -6282 -6283 let len = frame -6284 .data -6285 .len() -6286 .min(2usize.pow(14) - 1) -6287 .min(max_crypto_data_size); -6288 -6289 let data = frame.data.split_to(len); -6290 let offset = frame.offset; -6291 let truncated = frame::Crypto { offset, data }; -6292 builder.write_frame(truncated, stats); -6293 -6294 if !frame.data.is_empty() { -6295 frame.offset += len as u64; -6296 space.pending.crypto.push_front(frame); -6297 } -6298 } -6299 -6300 // PATH_STATUS_AVAILABLE & PATH_STATUS_BACKUP -6301 while space_id == SpaceId::Data -6302 && !scheduling_info.is_abandoned -6303 && scheduling_info.may_send_data -6304 && frame::PathStatusAvailable::SIZE_BOUND <= builder.frame_space_remaining() -6305 { -6306 let Some(path_id) = space.pending.path_status.pop_first() else { -6307 break; -6308 }; -6309 let Some(path) = self.paths.get(&path_id).map(|path_state| &path_state.data) else { -6310 trace!(%path_id, "discarding queued path status for unknown path"); -6311 continue; -6312 }; -6313 -6314 let seq = path.status.seq(); -6315 match path.local_status() { -6316 PathStatus::Available => { -6317 let frame = frame::PathStatusAvailable { -6318 path_id, -6319 status_seq_no: seq, -6320 }; -6321 builder.write_frame(frame, stats); -6322 } -6323 PathStatus::Backup => { -6324 let frame = frame::PathStatusBackup { -6325 path_id, -6326 status_seq_no: seq, -6327 }; -6328 builder.write_frame(frame, stats); -6329 } -6330 } -6331 } -6332 -6333 // MAX_PATH_ID -6334 if space_id == SpaceId::Data -6335 && !scheduling_info.is_abandoned -6336 && scheduling_info.may_send_data -6337 && space.pending.max_path_id -6338 && frame::MaxPathId::SIZE_BOUND <= builder.frame_space_remaining() -6339 { -6340 let frame = frame::MaxPathId(self.local_max_path_id); -6341 builder.write_frame(frame, stats); -6342 space.pending.max_path_id = false; -6343 } -6344 -6345 // PATHS_BLOCKED -6346 if space_id == SpaceId::Data -6347 && !scheduling_info.is_abandoned -6348 && scheduling_info.may_send_data -6349 && space.pending.paths_blocked -6350 && frame::PathsBlocked::SIZE_BOUND <= builder.frame_space_remaining() -6351 { -6352 let frame = frame::PathsBlocked(self.remote_max_path_id); -6353 builder.write_frame(frame, stats); -6354 space.pending.paths_blocked = false; -6355 } -6356 -6357 // PATH_CIDS_BLOCKED -6358 while space_id == SpaceId::Data -6359 && !scheduling_info.is_abandoned -6360 && scheduling_info.may_send_data -6361 && frame::PathCidsBlocked::SIZE_BOUND <= builder.frame_space_remaining() -6362 { -6363 let Some(path_id) = space.pending.path_cids_blocked.pop_first() else { -6364 break; -6365 }; -6366 let next_seq = match self.remote_cids.get(&path_id) { -6367 Some(cid_queue) => VarInt(cid_queue.active_seq() + 1), -6368 None => VarInt(0), -6369 }; -6370 let frame = frame::PathCidsBlocked { path_id, next_seq }; -6371 builder.write_frame(frame, stats); -6372 } -6373 -6374 // RESET_STREAM, STOP_SENDING, MAX_DATA, MAX_STREAM_DATA, MAX_STREAMS -6375 if space_id == SpaceId::Data -6376 && !scheduling_info.is_abandoned -6377 && scheduling_info.may_send_data -6378 { -6379 self.streams -6380 .write_control_frames(builder, &mut space.pending, stats); -6381 } -6382 -6383 // NEW_CONNECTION_ID -6384 let cid_len = self -6385 .local_cid_state -6386 .values() -6387 .map(|cid_state| cid_state.cid_len()) -6388 .max() -6389 .expect("some local CID state must exist"); -6390 let new_cid_size_bound = -6391 frame::NewConnectionId::size_bound(is_multipath_negotiated, cid_len); -6392 while !scheduling_info.is_abandoned -6393 && scheduling_info.may_send_data -6394 && builder.frame_space_remaining() > new_cid_size_bound -6395 { -6396 let Some(issued) = space.pending.new_cids.pop() else { -6397 break; -6398 }; -6399 // Path was discarded after this CID was queued, drop. -6400 let Some(cid_state) = self.local_cid_state.get(&issued.path_id) else { -6401 debug!( -6402 path = %issued.path_id, seq = issued.sequence, -6403 "dropping queued NEW_CONNECTION_ID for discarded path", -6404 ); -6405 continue; -6406 }; -6407 let retire_prior_to = cid_state.retire_prior_to(); -6408 -6409 let cid_path_id = match is_multipath_negotiated { -6410 true => Some(issued.path_id), -6411 false => { -6412 debug_assert_eq!(issued.path_id, PathId::ZERO); -6413 None -6414 } -6415 }; -6416 let frame = frame::NewConnectionId { -6417 path_id: cid_path_id, -6418 sequence: issued.sequence, -6419 retire_prior_to, -6420 id: issued.id, -6421 reset_token: issued.reset_token, -6422 }; -6423 builder.write_frame(frame, stats); -6424 } -6425 -6426 // RETIRE_CONNECTION_ID -6427 let retire_cid_bound = frame::RetireConnectionId::size_bound(is_multipath_negotiated); -6428 while !scheduling_info.is_abandoned -6429 && scheduling_info.may_send_data -6430 && builder.frame_space_remaining() > retire_cid_bound -6431 { -6432 let (path_id, sequence) = match space.pending.retire_cids.pop() { -6433 Some((PathId::ZERO, seq)) if !is_multipath_negotiated => (None, seq), -6434 Some((path_id, seq)) => (Some(path_id), seq), -6435 None => break, -6436 }; -6437 let frame = frame::RetireConnectionId { path_id, sequence }; -6438 builder.write_frame(frame, stats); -6439 } -6440 -6441 // DATAGRAM -6442 let mut sent_datagrams = false; -6443 while !scheduling_info.is_abandoned -6444 && scheduling_info.may_send_data -6445 && builder.frame_space_remaining() > Datagram::SIZE_BOUND -6446 && space_id == SpaceId::Data -6447 { -6448 match self.datagrams.write(builder, stats) { -6449 true => { -6450 sent_datagrams = true; -6451 } -6452 false => break, -6453 } -6454 } -6455 if self.datagrams.send_blocked && sent_datagrams { -6456 self.events.push_back(Event::DatagramsUnblocked); -6457 self.datagrams.send_blocked = false; -6458 } +5995 /// *path_exclusive_only* means to only build frames which can only be sent on this +5996 /// *path. This is used in multipath for backup paths while there is still an active +5997 /// *path. +5998 fn populate_packet<'a, 'b>( +5999 &mut self, +6000 now: Instant, +6001 space_id: SpaceId, +6002 path_id: PathId, +6003 scheduling_info: &PathSchedulingInfo, +6004 builder: &mut PacketBuilder<'a, 'b>, +6005 ) { +6006 let is_multipath_negotiated = self.is_multipath_negotiated(); +6007 let space_has_keys = self.crypto_state.has_keys(space_id.encryption_level()); +6008 let is_0rtt = space_id == SpaceId::Data && !space_has_keys; +6009 let stats = &mut self.path_stats.for_path(path_id).frame_tx; +6010 let space = &mut self.spaces[space_id]; +6011 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; +6012 space +6013 .for_path(path_id) +6014 .pending_acks +6015 .maybe_ack_non_eliciting(); +6016 +6017 // HANDSHAKE_DONE +6018 if !is_0rtt +6019 && !scheduling_info.is_abandoned +6020 && scheduling_info.may_send_data +6021 && mem::replace(&mut space.pending.handshake_done, false) +6022 { +6023 builder.write_frame(frame::HandshakeDone, stats); +6024 } +6025 +6026 // PING +6027 if !scheduling_info.is_abandoned +6028 && mem::replace(&mut space.for_path(path_id).ping_pending, false) +6029 { +6030 builder.write_frame(frame::Ping, stats); +6031 } +6032 +6033 // IMMEDIATE_ACK +6034 if !scheduling_info.is_abandoned +6035 && mem::replace(&mut space.for_path(path_id).immediate_ack_pending, false) +6036 { +6037 debug_assert_eq!( +6038 space_id, +6039 SpaceId::Data, +6040 "immediate acks must be sent in the data space" +6041 ); +6042 builder.write_frame(frame::ImmediateAck, stats); +6043 } +6044 +6045 // ACK +6046 if !scheduling_info.is_abandoned && scheduling_info.may_send_data { +6047 for path_id in space +6048 .number_spaces +6049 .iter_mut() +6050 .filter(|(_, pns)| pns.pending_acks.can_send()) +6051 .map(|(&path_id, _)| path_id) +6052 .collect::<Vec<_>>() +6053 { +6054 Self::populate_acks( +6055 now, +6056 self.receiving_ecn, +6057 path_id, +6058 space_id, +6059 space, +6060 is_multipath_negotiated, +6061 builder, +6062 stats, +6063 space_has_keys, +6064 ); +6065 } +6066 } +6067 +6068 // ACK_FREQUENCY +6069 if !scheduling_info.is_abandoned +6070 && scheduling_info.may_send_data +6071 && mem::replace(&mut space.pending.ack_frequency, false) +6072 { +6073 let sequence_number = self.ack_frequency.next_sequence_number(); +6074 +6075 // Safe to unwrap because this is always provided when ACK frequency is enabled +6076 let config = self.config.ack_frequency_config.as_ref().unwrap(); +6077 +6078 // Ensure the delay is within bounds to avoid a PROTOCOL_VIOLATION error +6079 let max_ack_delay = self.ack_frequency.candidate_max_ack_delay( +6080 path.rtt.get(), +6081 config, +6082 &self.peer_params, +6083 ); +6084 +6085 let frame = frame::AckFrequency { +6086 sequence: sequence_number, +6087 ack_eliciting_threshold: config.ack_eliciting_threshold, +6088 request_max_ack_delay: max_ack_delay.as_micros().try_into().unwrap_or(VarInt::MAX), +6089 reordering_threshold: config.reordering_threshold, +6090 }; +6091 builder.write_frame(frame, stats); +6092 +6093 self.ack_frequency +6094 .ack_frequency_sent(path_id, builder.packet_number, max_ack_delay); +6095 } +6096 +6097 // PATH_CHALLENGE +6098 if !scheduling_info.is_abandoned +6099 && space_id == SpaceId::Data +6100 && path.pending_on_path_challenge +6101 && !self.state.is_closed() +6102 && builder.frame_space_remaining() > frame::PathChallenge::SIZE_BOUND +6103 // we don't want to send new challenges if we are already closing +6104 { +6105 path.pending_on_path_challenge = false; +6106 +6107 let token = self.rng.random(); +6108 path.record_path_challenge_sent(now, token, path.network_path); +6109 // Generate a new challenge every time we send a new PATH_CHALLENGE +6110 let challenge = frame::PathChallenge(token); +6111 builder.write_frame(challenge, stats); +6112 builder.require_padding(); +6113 let pto = self.ack_frequency.max_ack_delay_for_pto() + path.rtt.pto_base(); +6114 match path.open_status { +6115 paths::OpenStatus::Sent | paths::OpenStatus::Informed => {} +6116 paths::OpenStatus::Pending => { +6117 path.open_status = paths::OpenStatus::Sent; +6118 self.timers.set( +6119 Timer::PerPath(path_id, PathTimer::AbandonFromValidation), +6120 now + 3 * pto, +6121 self.qlog.with_time(now), +6122 ); +6123 } +6124 } +6125 +6126 self.timers.set( +6127 Timer::PerPath(path_id, PathTimer::PathChallengeLost), +6128 now + pto, +6129 self.qlog.with_time(now), +6130 ); +6131 +6132 if is_multipath_negotiated && !path.validated && path.pending_on_path_challenge { +6133 // queue informing the path status along with the challenge +6134 space.pending.path_status.insert(path_id); +6135 } +6136 +6137 // Always include an OBSERVED_ADDR frame with a PATH_CHALLENGE, regardless +6138 // of whether one has already been sent on this path. +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 // PATH_RESPONSE +6162 if !scheduling_info.is_abandoned +6163 && space_id == SpaceId::Data +6164 && builder.frame_space_remaining() > frame::PathResponse::SIZE_BOUND +6165 && let Some(token) = path.path_responses.pop_on_path(path.network_path) +6166 { +6167 let response = frame::PathResponse(token); +6168 builder.write_frame(response, stats); +6169 builder.require_padding(); +6170 +6171 // NOTE: this is technically not required but might be useful to ride the +6172 // request/response nature of path challenges to refresh an observation +6173 // Since PATH_RESPONSE is a probing frame, this is allowed by the spec. +6174 if space_id == SpaceId::Data +6175 && self +6176 .config +6177 .address_discovery_role +6178 .should_report(&self.peer_params.address_discovery_role) +6179 { +6180 let frame = frame::ObservedAddr::new( +6181 path.network_path.remote, +6182 self.next_observed_addr_seq_no, +6183 ); +6184 if builder.frame_space_remaining() > frame.size() { +6185 builder.write_frame(frame, stats); +6186 +6187 self.next_observed_addr_seq_no = +6188 self.next_observed_addr_seq_no.saturating_add(1u8); +6189 path.observed_addr_sent = true; +6190 +6191 space.pending.observed_addr = false; +6192 } +6193 } +6194 } +6195 +6196 // REACH_OUT +6197 while !scheduling_info.is_abandoned +6198 && scheduling_info.may_send_data +6199 && let Some(reach_out) = space +6200 .pending +6201 .reach_out +6202 .pop_if(|frame| builder.frame_space_remaining() >= frame.size()) +6203 { +6204 builder.write_frame(reach_out, stats); +6205 } +6206 +6207 // PATH_ABANDON +6208 if space_id == SpaceId::Data +6209 && scheduling_info.is_abandoned +6210 && scheduling_info.may_self_abandon +6211 && frame::PathAbandon::SIZE_BOUND <= builder.frame_space_remaining() +6212 && let Some(error_code) = space.pending.path_abandon.remove(&path_id) +6213 { +6214 let frame = frame::PathAbandon { +6215 path_id, +6216 error_code, +6217 }; +6218 builder.write_frame(frame, stats); +6219 +6220 // Consider remotely issued CIDs as retired now that we have sent this frame at +6221 // least once. +6222 self.remote_cids.remove(&path_id); +6223 } +6224 while space_id == SpaceId::Data +6225 && scheduling_info.may_send_data +6226 && frame::PathAbandon::SIZE_BOUND <= builder.frame_space_remaining() +6227 && let Some((abandoned_path_id, error_code)) = space.pending.path_abandon.pop_first() +6228 { +6229 let frame = frame::PathAbandon { +6230 path_id: abandoned_path_id, +6231 error_code, +6232 }; +6233 builder.write_frame(frame, stats); +6234 +6235 // Consider remotely issued CIDs as retired now that we have sent this frame at +6236 // least once. +6237 self.remote_cids.remove(&abandoned_path_id); +6238 } +6239 +6240 // OBSERVED_ADDR +6241 if !scheduling_info.is_abandoned +6242 && scheduling_info.may_send_data +6243 && space_id == SpaceId::Data +6244 && self +6245 .config +6246 .address_discovery_role +6247 .should_report(&self.peer_params.address_discovery_role) +6248 && (!path.observed_addr_sent || space.pending.observed_addr) +6249 { +6250 let frame = +6251 frame::ObservedAddr::new(path.network_path.remote, self.next_observed_addr_seq_no); +6252 if builder.frame_space_remaining() > frame.size() { +6253 builder.write_frame(frame, stats); +6254 +6255 self.next_observed_addr_seq_no = self.next_observed_addr_seq_no.saturating_add(1u8); +6256 path.observed_addr_sent = true; +6257 +6258 space.pending.observed_addr = false; +6259 } +6260 } +6261 +6262 // CRYPTO +6263 while !is_0rtt +6264 && !scheduling_info.is_abandoned +6265 && scheduling_info.may_send_data +6266 && builder.frame_space_remaining() > frame::Crypto::SIZE_BOUND +6267 { +6268 let Some(mut frame) = space.pending.crypto.pop_front() else { +6269 break; +6270 }; +6271 +6272 // Calculate the maximum amount of crypto data we can store in the buffer. +6273 // Since the offset is known, we can reserve the exact size required to encode it. +6274 // For length we reserve 2bytes which allows to encode up to 2^14, +6275 // which is more than what fits into normally sized QUIC frames. +6276 let max_crypto_data_size = builder.frame_space_remaining() +6277 - 1 // Frame Type +6278 - VarInt::size(unsafe { VarInt::from_u64_unchecked(frame.offset) }) +6279 - 2; // Maximum encoded length for frame size, given we send less than 2^14 bytes +6280 +6281 let len = frame +6282 .data +6283 .len() +6284 .min(2usize.pow(14) - 1) +6285 .min(max_crypto_data_size); +6286 +6287 let data = frame.data.split_to(len); +6288 let offset = frame.offset; +6289 let truncated = frame::Crypto { offset, data }; +6290 builder.write_frame(truncated, stats); +6291 +6292 if !frame.data.is_empty() { +6293 frame.offset += len as u64; +6294 space.pending.crypto.push_front(frame); +6295 } +6296 } +6297 +6298 // PATH_STATUS_AVAILABLE & PATH_STATUS_BACKUP +6299 while space_id == SpaceId::Data +6300 && !scheduling_info.is_abandoned +6301 && scheduling_info.may_send_data +6302 && frame::PathStatusAvailable::SIZE_BOUND <= builder.frame_space_remaining() +6303 { +6304 let Some(path_id) = space.pending.path_status.pop_first() else { +6305 break; +6306 }; +6307 let Some(path) = self.paths.get(&path_id).map(|path_state| &path_state.data) else { +6308 trace!(%path_id, "discarding queued path status for unknown path"); +6309 continue; +6310 }; +6311 +6312 let seq = path.status.seq(); +6313 match path.local_status() { +6314 PathStatus::Available => { +6315 let frame = frame::PathStatusAvailable { +6316 path_id, +6317 status_seq_no: seq, +6318 }; +6319 builder.write_frame(frame, stats); +6320 } +6321 PathStatus::Backup => { +6322 let frame = frame::PathStatusBackup { +6323 path_id, +6324 status_seq_no: seq, +6325 }; +6326 builder.write_frame(frame, stats); +6327 } +6328 } +6329 } +6330 +6331 // MAX_PATH_ID +6332 if space_id == SpaceId::Data +6333 && !scheduling_info.is_abandoned +6334 && scheduling_info.may_send_data +6335 && space.pending.max_path_id +6336 && frame::MaxPathId::SIZE_BOUND <= builder.frame_space_remaining() +6337 { +6338 let frame = frame::MaxPathId(self.local_max_path_id); +6339 builder.write_frame(frame, stats); +6340 space.pending.max_path_id = false; +6341 } +6342 +6343 // PATHS_BLOCKED +6344 if space_id == SpaceId::Data +6345 && !scheduling_info.is_abandoned +6346 && scheduling_info.may_send_data +6347 && space.pending.paths_blocked +6348 && frame::PathsBlocked::SIZE_BOUND <= builder.frame_space_remaining() +6349 { +6350 let frame = frame::PathsBlocked(self.remote_max_path_id); +6351 builder.write_frame(frame, stats); +6352 space.pending.paths_blocked = false; +6353 } +6354 +6355 // PATH_CIDS_BLOCKED +6356 while space_id == SpaceId::Data +6357 && !scheduling_info.is_abandoned +6358 && scheduling_info.may_send_data +6359 && frame::PathCidsBlocked::SIZE_BOUND <= builder.frame_space_remaining() +6360 { +6361 let Some(path_id) = space.pending.path_cids_blocked.pop_first() else { +6362 break; +6363 }; +6364 let next_seq = match self.remote_cids.get(&path_id) { +6365 Some(cid_queue) => VarInt(cid_queue.active_seq() + 1), +6366 None => VarInt(0), +6367 }; +6368 let frame = frame::PathCidsBlocked { path_id, next_seq }; +6369 builder.write_frame(frame, stats); +6370 } +6371 +6372 // RESET_STREAM, STOP_SENDING, MAX_DATA, MAX_STREAM_DATA, MAX_STREAMS +6373 if space_id == SpaceId::Data +6374 && !scheduling_info.is_abandoned +6375 && scheduling_info.may_send_data +6376 { +6377 self.streams +6378 .write_control_frames(builder, &mut space.pending, stats); +6379 } +6380 +6381 // NEW_CONNECTION_ID +6382 let cid_len = self +6383 .local_cid_state +6384 .values() +6385 .map(|cid_state| cid_state.cid_len()) +6386 .max() +6387 .expect("some local CID state must exist"); +6388 let new_cid_size_bound = +6389 frame::NewConnectionId::size_bound(is_multipath_negotiated, cid_len); +6390 while !scheduling_info.is_abandoned +6391 && scheduling_info.may_send_data +6392 && builder.frame_space_remaining() > new_cid_size_bound +6393 { +6394 let Some(issued) = space.pending.new_cids.pop() else { +6395 break; +6396 }; +6397 // Path was discarded after this CID was queued, drop. +6398 let Some(cid_state) = self.local_cid_state.get(&issued.path_id) else { +6399 debug!( +6400 path = %issued.path_id, seq = issued.sequence, +6401 "dropping queued NEW_CONNECTION_ID for discarded path", +6402 ); +6403 continue; +6404 }; +6405 let retire_prior_to = cid_state.retire_prior_to(); +6406 +6407 let cid_path_id = match is_multipath_negotiated { +6408 true => Some(issued.path_id), +6409 false => { +6410 debug_assert_eq!(issued.path_id, PathId::ZERO); +6411 None +6412 } +6413 }; +6414 let frame = frame::NewConnectionId { +6415 path_id: cid_path_id, +6416 sequence: issued.sequence, +6417 retire_prior_to, +6418 id: issued.id, +6419 reset_token: issued.reset_token, +6420 }; +6421 builder.write_frame(frame, stats); +6422 } +6423 +6424 // RETIRE_CONNECTION_ID +6425 let retire_cid_bound = frame::RetireConnectionId::size_bound(is_multipath_negotiated); +6426 while !scheduling_info.is_abandoned +6427 && scheduling_info.may_send_data +6428 && builder.frame_space_remaining() > retire_cid_bound +6429 { +6430 let (path_id, sequence) = match space.pending.retire_cids.pop() { +6431 Some((PathId::ZERO, seq)) if !is_multipath_negotiated => (None, seq), +6432 Some((path_id, seq)) => (Some(path_id), seq), +6433 None => break, +6434 }; +6435 let frame = frame::RetireConnectionId { path_id, sequence }; +6436 builder.write_frame(frame, stats); +6437 } +6438 +6439 // DATAGRAM +6440 let mut sent_datagrams = false; +6441 while !scheduling_info.is_abandoned +6442 && scheduling_info.may_send_data +6443 && builder.frame_space_remaining() > Datagram::SIZE_BOUND +6444 && space_id == SpaceId::Data +6445 { +6446 match self.datagrams.write(builder, stats) { +6447 true => { +6448 sent_datagrams = true; +6449 } +6450 false => break, +6451 } +6452 } +6453 if self.datagrams.send_blocked && sent_datagrams { +6454 self.events.push_back(Event::DatagramsUnblocked); +6455 self.datagrams.send_blocked = false; +6456 } +6457 +6458 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; 6459 -6460 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; -6461 -6462 // NEW_TOKEN -6463 if !scheduling_info.is_abandoned && scheduling_info.may_send_data { -6464 while let Some(network_path) = space.pending.new_tokens.pop() { -6465 debug_assert_eq!(space_id, SpaceId::Data); -6466 let ConnectionSide::Server { server_config } = &self.side else { -6467 panic!("NEW_TOKEN frames should not be enqueued by clients"); -6468 }; -6469 -6470 if !network_path.is_probably_same_path(&path.network_path) { -6471 // NEW_TOKEN frames contain tokens bound to a client's IP address, and are only -6472 // useful if used from the same IP address. Thus, we abandon enqueued NEW_TOKEN -6473 // frames upon an path change. Instead, when the new path becomes validated, -6474 // NEW_TOKEN frames may be enqueued for the new path instead. -6475 continue; -6476 } -6477 -6478 let token = Token::new( -6479 TokenPayload::Validation { -6480 ip: network_path.remote.ip(), -6481 issued: server_config.time_source.now(), -6482 }, -6483 &mut self.rng, -6484 ); -6485 let new_token = NewToken { -6486 token: token.encode(&*server_config.token_key).into(), -6487 }; -6488 -6489 if builder.frame_space_remaining() < new_token.size() { -6490 space.pending.new_tokens.push(network_path); -6491 break; -6492 } -6493 -6494 builder.write_frame(new_token, stats); -6495 builder.retransmits_mut().new_tokens.push(network_path); -6496 } -6497 } -6498 -6499 // ADD_ADDRESS -6500 while space_id == SpaceId::Data -6501 && !scheduling_info.is_abandoned -6502 && scheduling_info.may_send_data -6503 && frame::AddAddress::SIZE_BOUND <= builder.frame_space_remaining() -6504 { -6505 if let Some(added_address) = space.pending.add_address.pop_last() { -6506 builder.write_frame(added_address, stats); -6507 } else { -6508 break; -6509 } -6510 } -6511 -6512 // REMOVE_ADDRESS -6513 while space_id == SpaceId::Data -6514 && !scheduling_info.is_abandoned -6515 && scheduling_info.may_send_data -6516 && frame::RemoveAddress::SIZE_BOUND <= builder.frame_space_remaining() -6517 { -6518 if let Some(removed_address) = space.pending.remove_address.pop_last() { -6519 builder.write_frame(removed_address, stats); -6520 } else { -6521 break; -6522 } -6523 } -6524 -6525 // STREAM -6526 if !scheduling_info.is_abandoned -6527 && scheduling_info.may_send_data -6528 && space_id == SpaceId::Data -6529 { -6530 self.streams -6531 .write_stream_frames(builder, self.config.send_fairness, stats); -6532 } -6533 } -6534 -6535 /// Write pending ACKs into a buffer -6536 fn populate_acks<'a, 'b>( -6537 now: Instant, -6538 receiving_ecn: bool, -6539 path_id: PathId, -6540 space_id: SpaceId, -6541 space: &mut PacketSpace, -6542 is_multipath_negotiated: bool, -6543 builder: &mut PacketBuilder<'a, 'b>, -6544 stats: &mut FrameStats, -6545 space_has_keys: bool, -6546 ) { -6547 // 0-RTT packets must never carry acks (which would have to be of handshake packets) -6548 debug_assert!(space_has_keys, "tried to send ACK in 0-RTT"); -6549 -6550 debug_assert!( -6551 is_multipath_negotiated || path_id == PathId::ZERO, -6552 "Only PathId::ZERO allowed without multipath (have {path_id:?})" -6553 ); -6554 if is_multipath_negotiated { -6555 debug_assert!( -6556 space_id == SpaceId::Data || path_id == PathId::ZERO, -6557 "path acks must be sent in 1RTT space (have {space_id:?})" -6558 ); -6559 } -6560 -6561 let pns = space.for_path(path_id); -6562 let ranges = pns.pending_acks.ranges(); -6563 debug_assert!(!ranges.is_empty(), "can not send empty ACK range"); -6564 let ecn = if receiving_ecn { -6565 Some(&pns.ecn_counters) -6566 } else { -6567 None -6568 }; -6569 -6570 let delay_micros = pns.pending_acks.ack_delay(now).as_micros() as u64; -6571 // TODO: This should come from `TransportConfig` if that gets configurable. -6572 let ack_delay_exp = TransportParameters::default().ack_delay_exponent; -6573 let delay = delay_micros >> ack_delay_exp.into_inner(); -6574 -6575 if is_multipath_negotiated && space_id == SpaceId::Data { -6576 if !ranges.is_empty() { -6577 let frame = frame::PathAck::encoder(path_id, delay, ranges, ecn); -6578 builder.write_frame(frame, stats); -6579 } -6580 } else { -6581 builder.write_frame(frame::Ack::encoder(delay, ranges, ecn), stats); -6582 } -6583 } -6584 -6585 fn close_common(&mut self) { -6586 trace!("connection closed"); -6587 self.timers.reset(); -6588 } -6589 -6590 fn set_close_timer(&mut self, now: Instant) { -6591 // QUIC-MULTIPATH § 2.6 Connection Closure: draining for 3*PTO using the max PTO of -6592 // all paths. -6593 let pto_max = self.max_pto_for_space(self.highest_space); -6594 self.timers.set( -6595 Timer::Conn(ConnTimer::Close), -6596 now + 3 * pto_max, -6597 self.qlog.with_time(now), -6598 ); -6599 } -6600 -6601 /// Handle transport parameters received from the peer -6602 /// -6603 /// *remote_cid* and *local_cid* are the source and destination CIDs respectively of the -6604 /// *packet into which the transport parameters arrived. -6605 fn handle_peer_params( -6606 &mut self, -6607 params: TransportParameters, -6608 local_cid: ConnectionId, -6609 remote_cid: ConnectionId, -6610 now: Instant, -6611 ) -> Result<(), TransportError> { -6612 if Some(self.original_remote_cid) != params.initial_src_cid -6613 || (self.side.is_client() -6614 && (Some(self.initial_dst_cid) != params.original_dst_cid -6615 || self.retry_src_cid != params.retry_src_cid)) -6616 { -6617 return Err(TransportError::TRANSPORT_PARAMETER_ERROR( -6618 "CID authentication failure", -6619 )); -6620 } -6621 if params.initial_max_path_id.is_some() && (local_cid.is_empty() || remote_cid.is_empty()) { -6622 return Err(TransportError::PROTOCOL_VIOLATION( -6623 "multipath must not use zero-length CIDs", -6624 )); -6625 } -6626 -6627 self.set_peer_params(params); -6628 self.qlog.emit_peer_transport_params_received(self, now); -6629 -6630 Ok(()) -6631 } -6632 -6633 fn set_peer_params(&mut self, params: TransportParameters) { -6634 self.streams.set_params(&params); -6635 self.idle_timeout = -6636 negotiate_max_idle_timeout(self.config.max_idle_timeout, Some(params.max_idle_timeout)); -6637 trace!("negotiated max idle timeout {:?}", self.idle_timeout); -6638 -6639 if let Some(ref info) = params.preferred_address { -6640 // During the handshake PathId::ZERO exists. -6641 self.remote_cids.get_mut(&PathId::ZERO).expect("not yet abandoned").insert(frame::NewConnectionId { -6642 path_id: None, -6643 sequence: 1, -6644 id: info.connection_id, -6645 reset_token: info.stateless_reset_token, -6646 retire_prior_to: 0, -6647 }) -6648 .expect( -6649 "preferred address CID is the first received, and hence is guaranteed to be legal", -6650 ); -6651 let remote = self.path_data(PathId::ZERO).network_path.remote; -6652 self.set_reset_token(PathId::ZERO, remote, info.stateless_reset_token); -6653 } -6654 self.ack_frequency.peer_max_ack_delay = get_max_ack_delay(&params); -6655 -6656 let mut multipath_enabled = false; -6657 if let (Some(local_max_path_id), Some(remote_max_path_id)) = ( -6658 self.config.get_initial_max_path_id(), -6659 params.initial_max_path_id, -6660 ) { -6661 // multipath is enabled, register the local and remote maximums -6662 self.local_max_path_id = local_max_path_id; -6663 self.remote_max_path_id = remote_max_path_id; -6664 let initial_max_path_id = local_max_path_id.min(remote_max_path_id); -6665 debug!(%initial_max_path_id, "multipath negotiated"); -6666 multipath_enabled = true; -6667 } -6668 -6669 if let Some((max_locally_allowed_remote_addresses, max_remotely_allowed_remote_addresses)) = -6670 self.config -6671 .max_remote_nat_traversal_addresses -6672 .zip(params.max_remote_nat_traversal_addresses) -6673 { -6674 if multipath_enabled { -6675 let max_local_addresses = max_remotely_allowed_remote_addresses.get(); -6676 let max_remote_addresses = max_locally_allowed_remote_addresses.get(); -6677 self.n0_nat_traversal = n0_nat_traversal::State::new( -6678 max_remote_addresses, -6679 max_local_addresses, -6680 self.side(), -6681 ); -6682 debug!( -6683 %max_remote_addresses, %max_local_addresses, -6684 "n0's nat traversal negotiated" -6685 ); -6686 } else { -6687 debug!("n0 nat traversal enabled for both endpoints, but multipath is missing") -6688 } -6689 } -6690 -6691 self.peer_params = params; -6692 let peer_max_udp_payload_size = -6693 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()).unwrap_or(u16::MAX); -6694 self.path_data_mut(PathId::ZERO) -6695 .mtud -6696 .on_peer_max_udp_payload_size_received(peer_max_udp_payload_size); -6697 } -6698 -6699 /// Decrypts a packet, returning the packet number on success -6700 fn decrypt_packet( -6701 &mut self, -6702 now: Instant, -6703 path_id: PathId, -6704 packet: &mut Packet, -6705 ) -> Result<Option<u64>, Option<TransportError>> { -6706 let result = self -6707 .crypto_state -6708 .decrypt_packet_body(packet, path_id, &self.spaces)?; -6709 -6710 let Some(result) = result else { -6711 return Ok(None); -6712 }; -6713 -6714 if result.outgoing_key_update_acked -6715 && let Some(prev) = self.crypto_state.prev_crypto.as_mut() -6716 { -6717 prev.end_packet = Some((result.packet_number, now)); -6718 self.set_key_discard_timer(now, packet.header.space()); -6719 } -6720 -6721 if result.incoming_key_update { -6722 trace!("key update authenticated"); -6723 self.crypto_state -6724 .update_keys(Some((result.packet_number, now)), true); -6725 self.set_key_discard_timer(now, packet.header.space()); -6726 } -6727 -6728 Ok(Some(result.packet_number)) -6729 } -6730 -6731 fn peer_supports_ack_frequency(&self) -> bool { -6732 self.peer_params.min_ack_delay.is_some() -6733 } -6734 -6735 /// Send an IMMEDIATE_ACK frame to the remote endpoint -6736 /// -6737 /// According to the spec, this will result in an error if the remote endpoint does not support -6738 /// the Acknowledgement Frequency extension -6739 pub(crate) fn immediate_ack(&mut self, path_id: PathId) { -6740 debug_assert_eq!( -6741 self.highest_space, -6742 SpaceKind::Data, -6743 "immediate ack must be written in the data space" -6744 ); -6745 self.spaces[SpaceId::Data] -6746 .for_path(path_id) -6747 .immediate_ack_pending = true; -6748 } -6749 -6750 /// Decodes a packet, returning its decrypted payload, so it can be inspected in tests -6751 #[cfg(test)] -6752 pub(crate) fn decode_packet(&self, event: &ConnectionEvent) -> Option<Vec<u8>> { -6753 let ConnectionEventInner::Datagram(DatagramConnectionEvent { -6754 path_id, -6755 first_decode, -6756 remaining, -6757 .. -6758 }) = &event.0 -6759 else { -6760 return None; -6761 }; -6762 -6763 if remaining.is_some() { -6764 panic!("Packets should never be coalesced in tests"); -6765 } -6766 -6767 let decrypted_header = self -6768 .crypto_state -6769 .unprotect_header(first_decode.clone(), self.peer_params.stateless_reset_token)?; -6770 -6771 let mut packet = decrypted_header.packet?; -6772 self.crypto_state -6773 .decrypt_packet_body(&mut packet, *path_id, &self.spaces) -6774 .ok()?; -6775 -6776 Some(packet.payload.to_vec()) -6777 } -6778 -6779 /// The number of bytes of packets containing retransmittable frames that have not been -6780 /// acknowledged or declared lost. -6781 #[cfg(test)] -6782 pub(crate) fn bytes_in_flight(&self) -> u64 { -6783 // TODO(@divma): consider including for multipath? -6784 self.path_data(PathId::ZERO).in_flight.bytes -6785 } -6786 -6787 /// Number of bytes worth of non-ack-only packets that may be sent -6788 #[cfg(test)] -6789 pub(crate) fn congestion_window(&self) -> u64 { -6790 let path = self.path_data(PathId::ZERO); -6791 path.congestion -6792 .window() -6793 .saturating_sub(path.in_flight.bytes) -6794 } -6795 -6796 /// Whether no timers but keepalive, idle, rtt, pushnewcid, and key discard are running -6797 #[cfg(test)] -6798 pub(crate) fn is_idle(&self) -> bool { -6799 let current_timers = self.timers.values(); -6800 current_timers -6801 .into_iter() -6802 .filter(|(timer, _)| { -6803 !matches!( -6804 timer, -6805 Timer::Conn(ConnTimer::KeepAlive) -6806 | Timer::PerPath(_, PathTimer::PathKeepAlive) -6807 | Timer::Conn(ConnTimer::PushNewCid) -6808 | Timer::Conn(ConnTimer::KeyDiscard) -6809 ) -6810 }) -6811 .min_by_key(|(_, time)| *time) -6812 .is_none_or(|(timer, _)| { -6813 matches!( -6814 timer, -6815 Timer::Conn(ConnTimer::Idle) | Timer::PerPath(_, PathTimer::PathIdle) -6816 ) -6817 }) -6818 } -6819 -6820 /// Whether explicit congestion notification is in use on outgoing packets. -6821 #[cfg(test)] -6822 pub(crate) fn using_ecn(&self) -> bool { -6823 self.path_data(PathId::ZERO).sending_ecn -6824 } -6825 -6826 /// The number of received bytes in the current path -6827 #[cfg(test)] -6828 pub(crate) fn total_recvd(&self) -> u64 { -6829 self.path_data(PathId::ZERO).total_recvd -6830 } -6831 -6832 #[cfg(test)] -6833 pub(crate) fn active_local_cid_seq(&self) -> (u64, u64) { -6834 self.local_cid_state -6835 .get(&PathId::ZERO) -6836 .unwrap() -6837 .active_seq() -6838 } -6839 -6840 #[cfg(test)] -6841 #[track_caller] -6842 pub(crate) fn active_local_path_cid_seq(&self, path_id: u32) -> (u64, u64) { -6843 self.local_cid_state -6844 .get(&PathId(path_id)) -6845 .unwrap() -6846 .active_seq() -6847 } -6848 -6849 /// Instruct the peer to replace previously issued CIDs by sending a NEW_CONNECTION_ID frame -6850 /// with updated `retire_prior_to` field set to `v` -6851 #[cfg(test)] -6852 pub(crate) fn rotate_local_cid(&mut self, v: u64, now: Instant) { -6853 let n = self -6854 .local_cid_state -6855 .get_mut(&PathId::ZERO) -6856 .unwrap() -6857 .assign_retire_seq(v); -6858 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events -6859 self.endpoint_events -6860 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); -6861 } -6862 -6863 /// Check the current active remote CID sequence for `PathId::ZERO` -6864 #[cfg(test)] -6865 pub(crate) fn active_remote_cid_seq(&self) -> u64 { -6866 self.remote_cids.get(&PathId::ZERO).unwrap().active_seq() -6867 } -6868 -6869 /// Returns the detected maximum udp payload size for the current path -6870 #[cfg(test)] -6871 pub(crate) fn path_mtu(&self, path_id: PathId) -> u16 { -6872 self.path_data(path_id).current_mtu() -6873 } -6874 -6875 /// Triggers path validation on all paths -6876 #[cfg(test)] -6877 pub(crate) fn trigger_path_validation(&mut self) { -6878 for path in self.paths.values_mut() { -6879 path.data.pending_on_path_challenge = true; -6880 } -6881 } -6882 -6883 /// Simulates a protocol violation error for test purposes. -6884 #[cfg(test)] -6885 pub fn simulate_protocol_violation(&mut self, now: Instant) { -6886 if !self.state.is_closed() { -6887 self.state -6888 .move_to_closed(TransportError::PROTOCOL_VIOLATION("simulated violation")); -6889 self.close_common(); -6890 if !self.state.is_drained() { -6891 self.set_close_timer(now); -6892 } -6893 self.connection_close_pending = true; -6894 } -6895 } -6896 -6897 /// Whether we have on-path 1-RTT data to send. -6898 /// -6899 /// This checks for frames that can only be sent in the data space (1-RTT): -6900 /// - Pending PATH_CHALLENGE frames on the active and previous path if just migrated. -6901 /// - Pending PATH_RESPONSE frames. -6902 /// - Pending data to send in STREAM frames. -6903 /// - Pending DATAGRAM frames to send. -6904 /// -6905 /// See also [`PacketSpace::can_send`] which keeps track of all other frame types that -6906 /// may need to be sent. -6907 fn can_send_1rtt(&self, path_id: PathId, max_size: usize) -> SendableFrames { -6908 let space_specific = self.paths.get(&path_id).is_some_and(|path| { -6909 path.data.pending_on_path_challenge || !path.data.path_responses.is_empty() -6910 }); -6911 -6912 // Stream control frames are checked in PacketSpace::can_send, only check data here. -6913 let other = self.streams.can_send_stream_data() -6914 || self -6915 .datagrams -6916 .outgoing -6917 .front() -6918 .is_some_and(|x| x.size(true) <= max_size); -6919 -6920 // All `false` fields are set in PacketSpace::can_send. -6921 SendableFrames { -6922 acks: false, -6923 close: false, -6924 space_specific, -6925 other, -6926 } -6927 } -6928 -6929 /// Terminate the connection instantly, without sending a close packet -6930 fn kill(&mut self, reason: ConnectionError) { -6931 self.close_common(); -6932 self.state.move_to_drained(Some(reason)); -6933 // move_to_drained checks that we were never in drained before, so we -6934 // never sent a `Drained` event before (it's illegal to send more events after drained). -6935 self.endpoint_events.push_back(EndpointEventInner::Drained); -6936 } -6937 -6938 /// Storage size required for the largest packet that can be transmitted on all currently -6939 /// available paths +6460 // NEW_TOKEN +6461 if !scheduling_info.is_abandoned && scheduling_info.may_send_data { +6462 while let Some(network_path) = space.pending.new_tokens.pop() { +6463 debug_assert_eq!(space_id, SpaceId::Data); +6464 let ConnectionSide::Server { server_config } = &self.side else { +6465 panic!("NEW_TOKEN frames should not be enqueued by clients"); +6466 }; +6467 +6468 if !network_path.is_probably_same_path(&path.network_path) { +6469 // NEW_TOKEN frames contain tokens bound to a client's IP address, and are only +6470 // useful if used from the same IP address. Thus, we abandon enqueued NEW_TOKEN +6471 // frames upon an path change. Instead, when the new path becomes validated, +6472 // NEW_TOKEN frames may be enqueued for the new path instead. +6473 continue; +6474 } +6475 +6476 let token = Token::new( +6477 TokenPayload::Validation { +6478 ip: network_path.remote.ip(), +6479 issued: server_config.time_source.now(), +6480 }, +6481 &mut self.rng, +6482 ); +6483 let new_token = NewToken { +6484 token: token.encode(&*server_config.token_key).into(), +6485 }; +6486 +6487 if builder.frame_space_remaining() < new_token.size() { +6488 space.pending.new_tokens.push(network_path); +6489 break; +6490 } +6491 +6492 builder.write_frame(new_token, stats); +6493 builder.retransmits_mut().new_tokens.push(network_path); +6494 } +6495 } +6496 +6497 // ADD_ADDRESS +6498 while space_id == SpaceId::Data +6499 && !scheduling_info.is_abandoned +6500 && scheduling_info.may_send_data +6501 && frame::AddAddress::SIZE_BOUND <= builder.frame_space_remaining() +6502 { +6503 if let Some(added_address) = space.pending.add_address.pop_last() { +6504 builder.write_frame(added_address, stats); +6505 } else { +6506 break; +6507 } +6508 } +6509 +6510 // REMOVE_ADDRESS +6511 while space_id == SpaceId::Data +6512 && !scheduling_info.is_abandoned +6513 && scheduling_info.may_send_data +6514 && frame::RemoveAddress::SIZE_BOUND <= builder.frame_space_remaining() +6515 { +6516 if let Some(removed_address) = space.pending.remove_address.pop_last() { +6517 builder.write_frame(removed_address, stats); +6518 } else { +6519 break; +6520 } +6521 } +6522 +6523 // STREAM +6524 if !scheduling_info.is_abandoned +6525 && scheduling_info.may_send_data +6526 && space_id == SpaceId::Data +6527 { +6528 self.streams +6529 .write_stream_frames(builder, self.config.send_fairness, stats); +6530 } +6531 } +6532 +6533 /// Write pending ACKs into a buffer +6534 fn populate_acks<'a, 'b>( +6535 now: Instant, +6536 receiving_ecn: bool, +6537 path_id: PathId, +6538 space_id: SpaceId, +6539 space: &mut PacketSpace, +6540 is_multipath_negotiated: bool, +6541 builder: &mut PacketBuilder<'a, 'b>, +6542 stats: &mut FrameStats, +6543 space_has_keys: bool, +6544 ) { +6545 // 0-RTT packets must never carry acks (which would have to be of handshake packets) +6546 debug_assert!(space_has_keys, "tried to send ACK in 0-RTT"); +6547 +6548 debug_assert!( +6549 is_multipath_negotiated || path_id == PathId::ZERO, +6550 "Only PathId::ZERO allowed without multipath (have {path_id:?})" +6551 ); +6552 if is_multipath_negotiated { +6553 debug_assert!( +6554 space_id == SpaceId::Data || path_id == PathId::ZERO, +6555 "path acks must be sent in 1RTT space (have {space_id:?})" +6556 ); +6557 } +6558 +6559 let pns = space.for_path(path_id); +6560 let ranges = pns.pending_acks.ranges(); +6561 debug_assert!(!ranges.is_empty(), "can not send empty ACK range"); +6562 let ecn = if receiving_ecn { +6563 Some(&pns.ecn_counters) +6564 } else { +6565 None +6566 }; +6567 +6568 let delay_micros = pns.pending_acks.ack_delay(now).as_micros() as u64; +6569 // TODO: This should come from `TransportConfig` if that gets configurable. +6570 let ack_delay_exp = TransportParameters::default().ack_delay_exponent; +6571 let delay = delay_micros >> ack_delay_exp.into_inner(); +6572 +6573 if is_multipath_negotiated && space_id == SpaceId::Data { +6574 if !ranges.is_empty() { +6575 let frame = frame::PathAck::encoder(path_id, delay, ranges, ecn); +6576 builder.write_frame(frame, stats); +6577 } +6578 } else { +6579 builder.write_frame(frame::Ack::encoder(delay, ranges, ecn), stats); +6580 } +6581 } +6582 +6583 fn close_common(&mut self) { +6584 trace!("connection closed"); +6585 self.timers.reset(); +6586 } +6587 +6588 fn set_close_timer(&mut self, now: Instant) { +6589 // QUIC-MULTIPATH § 2.6 Connection Closure: draining for 3*PTO using the max PTO of +6590 // all paths. +6591 let pto_max = self.max_pto_for_space(self.highest_space); +6592 self.timers.set( +6593 Timer::Conn(ConnTimer::Close), +6594 now + 3 * pto_max, +6595 self.qlog.with_time(now), +6596 ); +6597 } +6598 +6599 /// Handle transport parameters received from the peer +6600 /// +6601 /// *remote_cid* and *local_cid* are the source and destination CIDs respectively of the +6602 /// *packet into which the transport parameters arrived. +6603 fn handle_peer_params( +6604 &mut self, +6605 params: TransportParameters, +6606 local_cid: ConnectionId, +6607 remote_cid: ConnectionId, +6608 now: Instant, +6609 ) -> Result<(), TransportError> { +6610 if Some(self.original_remote_cid) != params.initial_src_cid +6611 || (self.side.is_client() +6612 && (Some(self.initial_dst_cid) != params.original_dst_cid +6613 || self.retry_src_cid != params.retry_src_cid)) +6614 { +6615 return Err(TransportError::TRANSPORT_PARAMETER_ERROR( +6616 "CID authentication failure", +6617 )); +6618 } +6619 if params.initial_max_path_id.is_some() && (local_cid.is_empty() || remote_cid.is_empty()) { +6620 return Err(TransportError::PROTOCOL_VIOLATION( +6621 "multipath must not use zero-length CIDs", +6622 )); +6623 } +6624 +6625 self.set_peer_params(params); +6626 self.qlog.emit_peer_transport_params_received(self, now); +6627 +6628 Ok(()) +6629 } +6630 +6631 fn set_peer_params(&mut self, params: TransportParameters) { +6632 self.streams.set_params(&params); +6633 self.idle_timeout = +6634 negotiate_max_idle_timeout(self.config.max_idle_timeout, Some(params.max_idle_timeout)); +6635 trace!("negotiated max idle timeout {:?}", self.idle_timeout); +6636 +6637 if let Some(ref info) = params.preferred_address { +6638 // During the handshake PathId::ZERO exists. +6639 self.remote_cids.get_mut(&PathId::ZERO).expect("not yet abandoned").insert(frame::NewConnectionId { +6640 path_id: None, +6641 sequence: 1, +6642 id: info.connection_id, +6643 reset_token: info.stateless_reset_token, +6644 retire_prior_to: 0, +6645 }) +6646 .expect( +6647 "preferred address CID is the first received, and hence is guaranteed to be legal", +6648 ); +6649 let remote = self.path_data(PathId::ZERO).network_path.remote; +6650 self.set_reset_token(PathId::ZERO, remote, info.stateless_reset_token); +6651 } +6652 self.ack_frequency.peer_max_ack_delay = get_max_ack_delay(&params); +6653 +6654 let mut multipath_enabled = false; +6655 if let (Some(local_max_path_id), Some(remote_max_path_id)) = ( +6656 self.config.get_initial_max_path_id(), +6657 params.initial_max_path_id, +6658 ) { +6659 // multipath is enabled, register the local and remote maximums +6660 self.local_max_path_id = local_max_path_id; +6661 self.remote_max_path_id = remote_max_path_id; +6662 let initial_max_path_id = local_max_path_id.min(remote_max_path_id); +6663 debug!(%initial_max_path_id, "multipath negotiated"); +6664 multipath_enabled = true; +6665 } +6666 +6667 if let Some((max_locally_allowed_remote_addresses, max_remotely_allowed_remote_addresses)) = +6668 self.config +6669 .max_remote_nat_traversal_addresses +6670 .zip(params.max_remote_nat_traversal_addresses) +6671 { +6672 if multipath_enabled { +6673 let max_local_addresses = max_remotely_allowed_remote_addresses.get(); +6674 let max_remote_addresses = max_locally_allowed_remote_addresses.get(); +6675 self.n0_nat_traversal = n0_nat_traversal::State::new( +6676 max_remote_addresses, +6677 max_local_addresses, +6678 self.side(), +6679 ); +6680 debug!( +6681 %max_remote_addresses, %max_local_addresses, +6682 "n0's nat traversal negotiated" +6683 ); +6684 } else { +6685 debug!("n0 nat traversal enabled for both endpoints, but multipath is missing") +6686 } +6687 } +6688 +6689 self.peer_params = params; +6690 let peer_max_udp_payload_size = +6691 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()).unwrap_or(u16::MAX); +6692 self.path_data_mut(PathId::ZERO) +6693 .mtud +6694 .on_peer_max_udp_payload_size_received(peer_max_udp_payload_size); +6695 } +6696 +6697 /// Decrypts a packet, returning the packet number on success +6698 fn decrypt_packet( +6699 &mut self, +6700 now: Instant, +6701 path_id: PathId, +6702 packet: &mut Packet, +6703 ) -> Result<Option<u64>, Option<TransportError>> { +6704 let result = self +6705 .crypto_state +6706 .decrypt_packet_body(packet, path_id, &self.spaces)?; +6707 +6708 let Some(result) = result else { +6709 return Ok(None); +6710 }; +6711 +6712 if result.outgoing_key_update_acked +6713 && let Some(prev) = self.crypto_state.prev_crypto.as_mut() +6714 { +6715 prev.end_packet = Some((result.packet_number, now)); +6716 self.set_key_discard_timer(now, packet.header.space()); +6717 } +6718 +6719 if result.incoming_key_update { +6720 trace!("key update authenticated"); +6721 self.crypto_state +6722 .update_keys(Some((result.packet_number, now)), true); +6723 self.set_key_discard_timer(now, packet.header.space()); +6724 } +6725 +6726 Ok(Some(result.packet_number)) +6727 } +6728 +6729 fn peer_supports_ack_frequency(&self) -> bool { +6730 self.peer_params.min_ack_delay.is_some() +6731 } +6732 +6733 /// Send an IMMEDIATE_ACK frame to the remote endpoint +6734 /// +6735 /// According to the spec, this will result in an error if the remote endpoint does not support +6736 /// the Acknowledgement Frequency extension +6737 pub(crate) fn immediate_ack(&mut self, path_id: PathId) { +6738 debug_assert_eq!( +6739 self.highest_space, +6740 SpaceKind::Data, +6741 "immediate ack must be written in the data space" +6742 ); +6743 self.spaces[SpaceId::Data] +6744 .for_path(path_id) +6745 .immediate_ack_pending = true; +6746 } +6747 +6748 /// Decodes a packet, returning its decrypted payload, so it can be inspected in tests +6749 #[cfg(test)] +6750 pub(crate) fn decode_packet(&self, event: &ConnectionEvent) -> Option<Vec<u8>> { +6751 let ConnectionEventInner::Datagram(DatagramConnectionEvent { +6752 path_id, +6753 first_decode, +6754 remaining, +6755 .. +6756 }) = &event.0 +6757 else { +6758 return None; +6759 }; +6760 +6761 if remaining.is_some() { +6762 panic!("Packets should never be coalesced in tests"); +6763 } +6764 +6765 let decrypted_header = self +6766 .crypto_state +6767 .unprotect_header(first_decode.clone(), self.peer_params.stateless_reset_token)?; +6768 +6769 let mut packet = decrypted_header.packet?; +6770 self.crypto_state +6771 .decrypt_packet_body(&mut packet, *path_id, &self.spaces) +6772 .ok()?; +6773 +6774 Some(packet.payload.to_vec()) +6775 } +6776 +6777 /// The number of bytes of packets containing retransmittable frames that have not been +6778 /// acknowledged or declared lost. +6779 #[cfg(test)] +6780 pub(crate) fn bytes_in_flight(&self) -> u64 { +6781 // TODO(@divma): consider including for multipath? +6782 self.path_data(PathId::ZERO).in_flight.bytes +6783 } +6784 +6785 /// Number of bytes worth of non-ack-only packets that may be sent +6786 #[cfg(test)] +6787 pub(crate) fn congestion_window(&self) -> u64 { +6788 let path = self.path_data(PathId::ZERO); +6789 path.congestion +6790 .window() +6791 .saturating_sub(path.in_flight.bytes) +6792 } +6793 +6794 /// Whether no timers but keepalive, idle, rtt, pushnewcid, and key discard are running +6795 #[cfg(test)] +6796 pub(crate) fn is_idle(&self) -> bool { +6797 let current_timers = self.timers.values(); +6798 current_timers +6799 .into_iter() +6800 .filter(|(timer, _)| { +6801 !matches!( +6802 timer, +6803 Timer::Conn(ConnTimer::KeepAlive) +6804 | Timer::PerPath(_, PathTimer::PathKeepAlive) +6805 | Timer::Conn(ConnTimer::PushNewCid) +6806 | Timer::Conn(ConnTimer::KeyDiscard) +6807 ) +6808 }) +6809 .min_by_key(|(_, time)| *time) +6810 .is_none_or(|(timer, _)| { +6811 matches!( +6812 timer, +6813 Timer::Conn(ConnTimer::Idle) | Timer::PerPath(_, PathTimer::PathIdle) +6814 ) +6815 }) +6816 } +6817 +6818 /// Whether explicit congestion notification is in use on outgoing packets. +6819 #[cfg(test)] +6820 pub(crate) fn using_ecn(&self) -> bool { +6821 self.path_data(PathId::ZERO).sending_ecn +6822 } +6823 +6824 /// The number of received bytes in the current path +6825 #[cfg(test)] +6826 pub(crate) fn total_recvd(&self) -> u64 { +6827 self.path_data(PathId::ZERO).total_recvd +6828 } +6829 +6830 #[cfg(test)] +6831 pub(crate) fn active_local_cid_seq(&self) -> (u64, u64) { +6832 self.local_cid_state +6833 .get(&PathId::ZERO) +6834 .unwrap() +6835 .active_seq() +6836 } +6837 +6838 #[cfg(test)] +6839 #[track_caller] +6840 pub(crate) fn active_local_path_cid_seq(&self, path_id: u32) -> (u64, u64) { +6841 self.local_cid_state +6842 .get(&PathId(path_id)) +6843 .unwrap() +6844 .active_seq() +6845 } +6846 +6847 /// Instruct the peer to replace previously issued CIDs by sending a NEW_CONNECTION_ID frame +6848 /// with updated `retire_prior_to` field set to `v` +6849 #[cfg(test)] +6850 pub(crate) fn rotate_local_cid(&mut self, v: u64, now: Instant) { +6851 let n = self +6852 .local_cid_state +6853 .get_mut(&PathId::ZERO) +6854 .unwrap() +6855 .assign_retire_seq(v); +6856 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events +6857 self.endpoint_events +6858 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); +6859 } +6860 +6861 /// Check the current active remote CID sequence for `PathId::ZERO` +6862 #[cfg(test)] +6863 pub(crate) fn active_remote_cid_seq(&self) -> u64 { +6864 self.remote_cids.get(&PathId::ZERO).unwrap().active_seq() +6865 } +6866 +6867 /// Returns the detected maximum udp payload size for the current path +6868 #[cfg(test)] +6869 pub(crate) fn path_mtu(&self, path_id: PathId) -> u16 { +6870 self.path_data(path_id).current_mtu() +6871 } +6872 +6873 /// Triggers path validation on all paths +6874 #[cfg(test)] +6875 pub(crate) fn trigger_path_validation(&mut self) { +6876 for path in self.paths.values_mut() { +6877 path.data.pending_on_path_challenge = true; +6878 } +6879 } +6880 +6881 /// Simulates a protocol violation error for test purposes. +6882 #[cfg(test)] +6883 pub fn simulate_protocol_violation(&mut self, now: Instant) { +6884 if !self.state.is_closed() { +6885 self.state +6886 .move_to_closed(TransportError::PROTOCOL_VIOLATION("simulated violation")); +6887 self.close_common(); +6888 if !self.state.is_drained() { +6889 self.set_close_timer(now); +6890 } +6891 self.connection_close_pending = true; +6892 } +6893 } +6894 +6895 /// Whether we have on-path 1-RTT data to send. +6896 /// +6897 /// This checks for frames that can only be sent in the data space (1-RTT): +6898 /// - Pending PATH_CHALLENGE frames on the active and previous path if just migrated. +6899 /// - Pending PATH_RESPONSE frames. +6900 /// - Pending data to send in STREAM frames. +6901 /// - Pending DATAGRAM frames to send. +6902 /// +6903 /// See also [`PacketSpace::can_send`] which keeps track of all other frame types that +6904 /// may need to be sent. +6905 fn can_send_1rtt(&self, path_id: PathId, max_size: usize) -> SendableFrames { +6906 let space_specific = self.paths.get(&path_id).is_some_and(|path| { +6907 path.data.pending_on_path_challenge || !path.data.path_responses.is_empty() +6908 }); +6909 +6910 // Stream control frames are checked in PacketSpace::can_send, only check data here. +6911 let other = self.streams.can_send_stream_data() +6912 || self +6913 .datagrams +6914 .outgoing +6915 .front() +6916 .is_some_and(|x| x.size(true) <= max_size); +6917 +6918 // All `false` fields are set in PacketSpace::can_send. +6919 SendableFrames { +6920 acks: false, +6921 close: false, +6922 space_specific, +6923 other, +6924 } +6925 } +6926 +6927 /// Terminate the connection instantly, without sending a close packet +6928 fn kill(&mut self, reason: ConnectionError) { +6929 self.close_common(); +6930 self.state.move_to_drained(Some(reason)); +6931 // move_to_drained checks that we were never in drained before, so we +6932 // never sent a `Drained` event before (it's illegal to send more events after drained). +6933 self.endpoint_events.push_back(EndpointEventInner::Drained); +6934 } +6935 +6936 /// Storage size required for the largest packet that can be transmitted on all currently +6937 /// available paths +6938 /// +6939 /// Buffers passed to [`Connection::poll_transmit`] should be at least this large. 6940 /// -6941 /// Buffers passed to [`Connection::poll_transmit`] should be at least this large. -6942 /// -6943 /// When multipath is enabled, this value is the minimum MTU across all available paths. -6944 pub fn current_mtu(&self) -> u16 { -6945 self.paths -6946 .iter() -6947 .filter(|&(path_id, _path_state)| !self.abandoned_paths.contains(path_id)) -6948 .map(|(_path_id, path_state)| path_state.data.current_mtu()) -6949 .min() -6950 .unwrap_or(INITIAL_MTU) -6951 } -6952 -6953 /// Size of non-frame data for a 1-RTT packet -6954 /// -6955 /// Quantifies space consumed by the QUIC header and AEAD tag. All other bytes in a packet are -6956 /// frames. Changes if the length of the remote connection ID changes, which is expected to be -6957 /// rare. If `pn` is specified, may additionally change unpredictably due to variations in -6958 /// latency and packet loss. -6959 fn predict_1rtt_overhead(&mut self, pn: u64, path: PathId) -> usize { -6960 let pn_len = PacketNumber::new( -6961 pn, -6962 self.spaces[SpaceId::Data] -6963 .for_path(path) -6964 .largest_acked_packet_pn -6965 .unwrap_or(0), -6966 ) -6967 .len(); -6968 -6969 // 1 byte for flags -6970 1 + self -6971 .remote_cids -6972 .get(&path) -6973 .map(|cids| cids.active().len()) -6974 .unwrap_or(20) // Max CID len in QUIC v1 -6975 + pn_len -6976 + self.tag_len_1rtt() -6977 } -6978 -6979 fn predict_1rtt_overhead_no_pn(&self) -> usize { -6980 let pn_len = 4; -6981 -6982 let cid_len = self -6983 .remote_cids -6984 .values() -6985 .map(|cids| cids.active().len()) -6986 .max() -6987 .unwrap_or(20); // Max CID len in QUIC v1 -6988 -6989 // 1 byte for flags -6990 1 + cid_len + pn_len + self.tag_len_1rtt() -6991 } -6992 -6993 fn tag_len_1rtt(&self) -> usize { -6994 // encryption_keys for Data space returns 1-RTT keys if available, otherwise 0-RTT keys -6995 let packet_crypto = self -6996 .crypto_state -6997 .encryption_keys(SpaceKind::Data, self.side.side()) -6998 .map(|(_header, packet, _level)| packet); -6999 // If neither Data nor 0-RTT keys are available, make a reasonable tag length guess. As of -7000 // this writing, all QUIC cipher suites use 16-byte tags. We could return `None` instead, -7001 // but that would needlessly prevent sending datagrams during 0-RTT. -7002 packet_crypto.map_or(16, |x| x.tag_len()) -7003 } -7004 -7005 /// Mark the path as validated, and enqueue NEW_TOKEN frames to be sent as appropriate -7006 fn on_path_validated(&mut self, path_id: PathId) { -7007 self.path_data_mut(path_id).validated = true; -7008 let ConnectionSide::Server { server_config } = &self.side else { -7009 return; -7010 }; -7011 let network_path = self.path_data(path_id).network_path; -7012 let new_tokens = &mut self.spaces[SpaceId::Data as usize].pending.new_tokens; -7013 new_tokens.clear(); -7014 for _ in 0..server_config.validation_token.sent { -7015 new_tokens.push(network_path); -7016 } -7017 } -7018 -7019 /// Handle new path status information: PATH_STATUS_AVAILABLE, PATH_STATUS_BACKUP -7020 fn on_path_status(&mut self, path_id: PathId, status: PathStatus, status_seq_no: VarInt) { -7021 if let Some(path) = self.paths.get_mut(&path_id) { -7022 path.data.status.remote_update(status, status_seq_no); -7023 } else { -7024 debug!("PATH_STATUS_AVAILABLE received unknown path {:?}", path_id); -7025 } -7026 self.events.push_back( -7027 PathEvent::RemoteStatus { -7028 id: path_id, -7029 status, -7030 } -7031 .into(), -7032 ); -7033 } -7034 -7035 /// Returns the maximum [`PathId`] to be used for sending in this connection. -7036 /// -7037 /// This is calculated as minimum between the local and remote's maximums when multipath is -7038 /// enabled, or `None` when disabled. -7039 /// -7040 /// For data that's received, we should use [`Self::local_max_path_id`] instead. -7041 /// The reasoning is that the remote might already have updated to its own newer -7042 /// [`Self::max_path_id`] after sending out a `MAX_PATH_ID` frame, but it got re-ordered. -7043 fn max_path_id(&self) -> Option<PathId> { -7044 if self.is_multipath_negotiated() { -7045 Some(self.remote_max_path_id.min(self.local_max_path_id)) -7046 } else { -7047 None -7048 } -7049 } -7050 -7051 /// Returns whether this connection has a socket that supports IPv6. -7052 /// -7053 /// TODO(matheus23): This is related to noq endpoint state's `ipv6` bool. We should move that info -7054 /// here instead of trying to hack around not knowing it exactly. -7055 fn is_ipv6(&self) -> bool { -7056 self.paths -7057 .values() -7058 .any(|p| p.data.network_path.remote.is_ipv6()) -7059 } -7060 -7061 /// Add addresses the local endpoint considers are reachable for nat traversal. -7062 pub fn add_nat_traversal_address( -7063 &mut self, -7064 address: SocketAddr, -7065 ) -> Result<(), n0_nat_traversal::Error> { -7066 if let Some(added) = self.n0_nat_traversal.add_local_address(address)? { -7067 self.spaces[SpaceId::Data].pending.add_address.insert(added); -7068 }; -7069 Ok(()) -7070 } -7071 -7072 /// Removes an address the endpoing no longer considers reachable for nat traversal -7073 /// -7074 /// Addresses not present in the set will be silently ignored. -7075 pub fn remove_nat_traversal_address( -7076 &mut self, -7077 address: SocketAddr, -7078 ) -> Result<(), n0_nat_traversal::Error> { -7079 if let Some(removed) = self.n0_nat_traversal.remove_local_address(address)? { -7080 self.spaces[SpaceId::Data] -7081 .pending -7082 .remove_address -7083 .insert(removed); -7084 } -7085 Ok(()) -7086 } -7087 -7088 /// Get the current local nat traversal addresses -7089 pub fn get_local_nat_traversal_addresses( -7090 &self, -7091 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -7092 self.n0_nat_traversal.get_local_nat_traversal_addresses() -7093 } -7094 -7095 /// Get the currently advertised nat traversal addresses by the server -7096 pub fn get_remote_nat_traversal_addresses( -7097 &self, -7098 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -7099 Ok(self -7100 .n0_nat_traversal -7101 .client_side()? -7102 .get_remote_nat_traversal_addresses()) -7103 } -7104 -7105 /// Initiates a new nat traversal round -7106 /// -7107 /// A nat traversal round involves advertising the client's local addresses in -7108 /// `REACH_OUT` frames, and initiating probing of the known remote addresses. When a new -7109 /// round is initiated, the previous one is cancelled. -7110 /// -7111 /// For all probes that succeed, if any, a new path will be opened on the successful -7112 /// 4-tuple. -7113 /// -7114 /// Returns the server addresses that are now being probed. If addresses fail due to -7115 /// spurious errors, these might succeed later and not be returned in this set. -7116 pub fn initiate_nat_traversal_round( -7117 &mut self, -7118 now: Instant, -7119 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -7120 if self.state.is_closed() { -7121 return Err(n0_nat_traversal::Error::Closed); -7122 } -7123 -7124 let ipv6 = self.is_ipv6(); -7125 let client_state = self.n0_nat_traversal.client_side_mut()?; -7126 let (mut reach_out_frames, probed_addrs) = -7127 client_state.initiate_nat_traversal_round(ipv6)?; -7128 if !probed_addrs.is_empty() { -7129 let delay = RttEstimator::new(self.config.initial_rtt).pto_base() * 2 / 3; -7130 self.timers.set( -7131 Timer::Conn(ConnTimer::NatTraversalProbeRetry), -7132 now + delay, -7133 self.qlog.with_time(now), -7134 ); -7135 } -7136 -7137 self.spaces[SpaceId::Data] -7138 .pending -7139 .reach_out -7140 .append(&mut reach_out_frames); -7141 -7142 Ok(probed_addrs) -7143 } -7144 -7145 /// Whether the handshake is considered **confirmed**. -7146 /// -7147 /// <https://www.rfc-editor.org/rfc/rfc9001#section-4.1.2> defines a handshake to be -7148 /// confirmed when you know the peer successfully received and successfully processed -7149 /// your TLS Finished message. -7150 /// -7151 /// Implementation-wise this is the point at which the handshake crypto keys are -7152 /// discarded. So we can use this to know if the handshake is confirmed. -7153 fn is_handshake_confirmed(&self) -> bool { -7154 !self.is_handshaking() && !self.crypto_state.has_keys(EncryptionLevel::Handshake) -7155 } -7156} -7157 -7158impl fmt::Debug for Connection { -7159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -7160 f.debug_struct("Connection") -7161 .field("handshake_cid", &self.handshake_cid) -7162 .finish() -7163 } -7164} -7165 -7166/// Hints when the caller identifies a network change. -7167pub trait NetworkChangeHint: std::fmt::Debug + 'static { -7168 /// Inform the connection if a path may recover after a network change. -7169 /// -7170 /// After network changes, paths may not be recoverable. In this case, waiting for the path to -7171 /// become idle may take longer than what is desirable. If [`Self::is_path_recoverable`] -7172 /// returns `false`, a multipath-enabled, client-side connection will establish a new path to -7173 /// the same remote, closing the current one, instead of migrating the path. -7174 /// -7175 /// Paths that are deemed recoverable will simply be sent a PING for a liveness check. -7176 fn is_path_recoverable(&self, path_id: PathId, network_path: FourTuple) -> bool; -7177} -7178 -7179/// Return value for [`Connection::poll_transmit_path_space`]. -7180#[derive(Debug)] -7181enum PollPathSpaceStatus { -7182 /// Nothing to send in the space, nothing was written into the [`TransmitBuf`]. -7183 NothingToSend { -7184 /// If true there was data to send but congestion control did not allow so. -7185 congestion_blocked: bool, -7186 }, -7187 /// One or more packets have been written into the [`TransmitBuf`]. -7188 WrotePacket { -7189 /// The highest packet number. -7190 last_packet_number: u64, -7191 /// Whether to pad an already started datagram in the next packet. -7192 /// -7193 /// When packets in Initial, 0-RTT or Handshake packet do not fill the entire -7194 /// datagram they may decide to coalesce with the next packet from a higher -7195 /// encryption level on the same path. But the earlier packet may require specific -7196 /// size requirements for the datagram they are sent in. -7197 /// -7198 /// If a space did not complete the datagram, they use this to request the correct -7199 /// padding in the final packet of the datagram so that the final datagram will have -7200 /// the correct size. -7201 /// -7202 /// If a space did fill an entire datagram, it leaves this to the default of -7203 /// [`PadDatagram::No`]. -7204 pad_datagram: PadDatagram, -7205 }, -7206 /// Send the contents of the transmit immediately. -7207 /// -7208 /// Packets were written and the GSO batch must end now, regardless from whether higher -7209 /// spaces still have frames to write. This is used when the last datagram written would -7210 /// require too much padding to continue a GSO batch, which would waste space on the -7211 /// wire. -7212 Send { -7213 /// The highest packet number written into the transmit. -7214 last_packet_number: u64, -7215 }, -7216} -7217 -7218/// Information used to decide what frames to schedule into which packets. -7219/// -7220/// Primarily used by [`Connection::poll_transmit_on_path`] and the functions that help -7221/// building packets for it: [`Connection::poll_transmit_path_space`] and -7222/// [`Connection::populate_packet`]. -7223#[derive(Debug, Copy, Clone)] -7224struct PathSchedulingInfo { -7225 /// Whether the path is abandoned. -7226 /// -7227 /// Note that a path that is abandoned but still has CIDs can still send a packet. After -7228 /// sending that packet the CIDs issued by the remote have to be considered retired as -7229 /// well. -7230 is_abandoned: bool, -7231 /// Whether the path may send [`SpaceKind::Data`] frames. -7232 /// -7233 /// Some paths should only send frames from [`SendableFrames::space_specific`]. All other -7234 /// frames are essentially frames that can be sent on any [`SpaceKind::Data`] space. For -7235 /// those we want to respect packet scheduling rules however. -7236 /// -7237 /// Roughly speaking data frames are only sent on spaces that have CIDs, are not -7238 /// abandoned and have no *better* spaces. However see to comments where this is -7239 /// populated for the exact packet scheduling implementation. -7240 /// -7241 /// This essentially marks this paths as the best validated space ID. Except during -7242 /// the handshake in which case it does not need to be validated. Several paths could be -7243 /// equally good and all have this set to `true`, in that case packet scheduling can -7244 /// choose which path to use. Currently it chooses the lowest path that is not -7245 /// congestion blocked. -7246 /// -7247 /// Note that once in the closed or draining states this will never be true. -7248 may_send_data: bool, -7249 /// Whether the path may send a CONNECTION_CLOSE frame. -7250 /// -7251 /// This essentially marks this path as the best validated space ID with a fallback -7252 /// to unvalidated spaces if there are no validated spaces. Like for -7253 /// [`Self::may_send_data`] other paths could be equally good. -7254 may_send_close: bool, -7255 may_self_abandon: bool, -7256} -7257 -7258#[derive(Debug, Copy, Clone, PartialEq, Eq)] -7259enum PathBlocked { -7260 No, -7261 AntiAmplification, -7262 Congestion, -7263 Pacing, -7264} -7265 -7266/// Fields of `Connection` specific to it being client-side or server-side -7267enum ConnectionSide { -7268 Client { -7269 /// Sent in every outgoing Initial packet. Always empty after Initial keys are discarded -7270 token: Bytes, -7271 token_store: Arc<dyn TokenStore>, -7272 server_name: String, -7273 }, -7274 Server { -7275 server_config: Arc<ServerConfig>, -7276 }, -7277} -7278 -7279impl ConnectionSide { -7280 fn is_client(&self) -> bool { -7281 self.side().is_client() -7282 } -7283 -7284 fn is_server(&self) -> bool { -7285 self.side().is_server() -7286 } -7287 -7288 fn side(&self) -> Side { -7289 match *self { -7290 Self::Client { .. } => Side::Client, -7291 Self::Server { .. } => Side::Server, -7292 } -7293 } -7294} -7295 -7296impl From<SideArgs> for ConnectionSide { -7297 fn from(side: SideArgs) -> Self { -7298 match side { -7299 SideArgs::Client { -7300 token_store, -7301 server_name, -7302 } => Self::Client { -7303 token: token_store.take(&server_name).unwrap_or_default(), -7304 token_store, -7305 server_name, -7306 }, -7307 SideArgs::Server { -7308 server_config, -7309 pref_addr_cid: _, -7310 path_validated: _, -7311 } => Self::Server { server_config }, -7312 } -7313 } -7314} -7315 -7316/// Parameters to `Connection::new` specific to it being client-side or server-side -7317pub(crate) enum SideArgs { -7318 Client { -7319 token_store: Arc<dyn TokenStore>, -7320 server_name: String, -7321 }, -7322 Server { -7323 server_config: Arc<ServerConfig>, -7324 pref_addr_cid: Option<ConnectionId>, -7325 path_validated: bool, -7326 }, -7327} -7328 -7329impl SideArgs { -7330 pub(crate) fn pref_addr_cid(&self) -> Option<ConnectionId> { -7331 match *self { -7332 Self::Client { .. } => None, -7333 Self::Server { pref_addr_cid, .. } => pref_addr_cid, -7334 } -7335 } -7336 -7337 pub(crate) fn path_validated(&self) -> bool { -7338 match *self { -7339 Self::Client { .. } => true, -7340 Self::Server { path_validated, .. } => path_validated, -7341 } -7342 } -7343 -7344 pub(crate) fn side(&self) -> Side { -7345 match *self { -7346 Self::Client { .. } => Side::Client, -7347 Self::Server { .. } => Side::Server, -7348 } -7349 } -7350} -7351 -7352/// Reasons why a connection might be lost -7353#[derive(Debug, Error, Clone, PartialEq, Eq)] -7354pub enum ConnectionError { -7355 /// The peer doesn't implement any supported version -7356 #[error("peer doesn't implement any supported version")] -7357 VersionMismatch, -7358 /// The peer violated the QUIC specification as understood by this implementation -7359 #[error(transparent)] -7360 TransportError(#[from] TransportError), -7361 /// The peer's QUIC stack aborted the connection automatically -7362 #[error("aborted by peer: {0}")] -7363 ConnectionClosed(frame::ConnectionClose), -7364 /// The peer closed the connection -7365 #[error("closed by peer: {0}")] -7366 ApplicationClosed(frame::ApplicationClose), -7367 /// The peer is unable to continue processing this connection, usually due to having restarted -7368 #[error("reset by peer")] -7369 Reset, -7370 /// Communication with the peer has lapsed for longer than the negotiated idle timeout -7371 /// -7372 /// If neither side is sending keep-alives, a connection will time out after a long enough idle -7373 /// period even if the peer is still reachable. See also [`TransportConfig::max_idle_timeout()`] -7374 /// and [`TransportConfig::keep_alive_interval()`]. -7375 #[error("timed out")] -7376 TimedOut, -7377 /// The local application closed the connection -7378 #[error("closed")] -7379 LocallyClosed, -7380 /// The connection could not be created because not enough of the CID space is available -7381 /// -7382 /// Try using longer connection IDs. -7383 #[error("CIDs exhausted")] -7384 CidsExhausted, -7385} -7386 -7387impl From<Close> for ConnectionError { -7388 fn from(x: Close) -> Self { -7389 match x { -7390 Close::Connection(reason) => Self::ConnectionClosed(reason), -7391 Close::Application(reason) => Self::ApplicationClosed(reason), -7392 } -7393 } -7394} -7395 -7396// For compatibility with API consumers -7397impl From<ConnectionError> for io::Error { -7398 fn from(x: ConnectionError) -> Self { -7399 use ConnectionError::*; -7400 let kind = match x { -7401 TimedOut => io::ErrorKind::TimedOut, -7402 Reset => io::ErrorKind::ConnectionReset, -7403 ApplicationClosed(_) | ConnectionClosed(_) => io::ErrorKind::ConnectionAborted, -7404 TransportError(_) | VersionMismatch | LocallyClosed | CidsExhausted => { -7405 io::ErrorKind::Other -7406 } -7407 }; -7408 Self::new(kind, x) -7409 } -7410} -7411 -7412/// Errors that might trigger a path being closed -7413// TODO(@divma): maybe needs to be reworked based on what we want to do with the public API -7414#[derive(Debug, Error, PartialEq, Eq, Clone, Copy)] -7415pub enum PathError { -7416 /// The extension was not negotiated with the peer -7417 #[error("multipath extension not negotiated")] -7418 MultipathNotNegotiated, -7419 /// Paths can only be opened client-side -7420 #[error("the server side may not open a path")] -7421 ServerSideNotAllowed, -7422 /// Current limits do not allow us to open more paths -7423 #[error("maximum number of concurrent paths reached")] -7424 MaxPathIdReached, -7425 /// No remote CIDs available to open a new path -7426 #[error("remoted CIDs exhausted")] -7427 RemoteCidsExhausted, -7428 /// Path could not be validated and will be abandoned -7429 #[error("path validation failed")] -7430 ValidationFailed, -7431 /// The remote address for the path is not supported by the endpoint -7432 #[error("invalid remote address")] -7433 InvalidRemoteAddress(SocketAddr), -7434} -7435 -7436/// Errors triggered when abandoning a path -7437#[derive(Debug, Error, Clone, Eq, PartialEq)] -7438pub enum ClosePathError { -7439 /// Multipath is not negotiated -7440 #[error("Multipath extension not negotiated")] -7441 MultipathNotNegotiated, -7442 /// The path is already closed or was never opened -7443 #[error("closed path")] -7444 ClosedPath, -7445 /// Cannot close the last remaining open path via the local API. -7446 /// -7447 /// Use [`Connection::close`] to end the connection instead. -7448 #[error("last open path")] -7449 LastOpenPath, -7450} -7451 -7452/// Error when the multipath extension was not negotiated, but attempted to be used. -7453#[derive(Debug, Error, Clone, Copy)] -7454#[error("Multipath extension not negotiated")] -7455pub struct MultipathNotNegotiated { -7456 _private: (), -7457} -7458 -7459/// Events of interest to the application -7460#[derive(Debug)] -7461pub enum Event { -7462 /// The connection's handshake data is ready -7463 HandshakeDataReady, -7464 /// The connection was successfully established -7465 Connected, -7466 /// The TLS handshake was confirmed -7467 HandshakeConfirmed, -7468 /// The connection was lost -7469 /// -7470 /// Emitted when the connection is closed due to an error, a timeout, or the peer closing it. -7471 /// This is **not** emitted when the local application closes the connection via -7472 /// [`Connection::close()`](crate::Connection::close). In that case, pending operations will -7473 /// fail with [`ConnectionError::LocallyClosed`]. -7474 ConnectionLost { -7475 /// Reason that the connection was closed -7476 reason: ConnectionError, -7477 }, -7478 /// Stream events -7479 Stream(StreamEvent), -7480 /// One or more application datagrams have been received -7481 DatagramReceived, -7482 /// One or more application datagrams have been sent after blocking -7483 DatagramsUnblocked, -7484 /// (Multi)Path events -7485 Path(PathEvent), -7486 /// n0's nat traversal events -7487 NatTraversal(n0_nat_traversal::Event), -7488} -7489 -7490impl From<PathEvent> for Event { -7491 fn from(source: PathEvent) -> Self { -7492 Self::Path(source) -7493 } -7494} -7495 -7496fn get_max_ack_delay(params: &TransportParameters) -> Duration { -7497 Duration::from_micros(params.max_ack_delay.0 * 1000) -7498} -7499 -7500/// Prevents overflow and improves behavior in extreme circumstances. -7501const MAX_BACKOFF_EXPONENT: u32 = 16; -7502 -7503/// The max interval between successive tail-loss probes. -7504/// -7505/// This is the "normal" value we use. -7506const MAX_PTO_INTERVAL: Duration = Duration::from_secs(2); -7507 -7508/// The idle time, below which we use the shorter [`MAX_PTO_FAST_INTERVAL`]. -7509const MIN_IDLE_FOR_FAST_PTO: Duration = Duration::from_secs(25); -7510 -7511/// The max interval between successive tail-loss probes with short idle times. -7512/// -7513/// If the path or connection idle time is less than [`MIN_IDLE_FOR_FAST_PTO`] then we use -7514/// this value to ensure we have plenty of retransmits before we reach the idle time. -7515const MAX_PTO_FAST_INTERVAL: Duration = Duration::from_secs(1); -7516 -7517/// The RTT threshold above which we cap the PTO interval to 1.5 * smoothed_rtt -7518/// -7519/// This is RTT time above which 1.5 * RTT > [`MAX_PTO_INTERVAL`], for these links we want -7520/// to extend the interval between tail-loss probes to not fill the entire pipe with them. -7521const SLOW_RTT_THRESHOLD: Duration = -7522 Duration::from_millis((MAX_PTO_INTERVAL.as_millis() as u64 * 2) / 3); -7523 -7524/// Minimal remaining size to allow packet coalescing, excluding cryptographic tag -7525/// -7526/// This must be at least as large as the header for a well-formed empty packet to be coalesced, -7527/// plus some space for frames. We only care about handshake headers because short header packets -7528/// necessarily have smaller headers, and initial packets are only ever the first packet in a -7529/// datagram (because we coalesce in ascending packet space order and the only reason to split a -7530/// packet is when packet space changes). -7531const MIN_PACKET_SPACE: usize = MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE + 32; -7532 -7533/// Largest amount of space that could be occupied by a Handshake or 0-RTT packet's header -7534/// -7535/// Excludes packet-type-specific fields such as packet number or Initial token -7536// https://www.rfc-editor.org/rfc/rfc9000.html#name-0-rtt: flags + version + dcid len + dcid + -7537// scid len + scid + length + pn -7538const MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE: usize = -7539 1 + 4 + 1 + MAX_CID_SIZE + 1 + MAX_CID_SIZE + VarInt::from_u32(u16::MAX as u32).size() + 4; -7540 -7541#[derive(Default)] -7542struct SentFrames { -7543 retransmits: ThinRetransmits, -7544 /// The packet number of the largest acknowledged packet for each path -7545 largest_acked: FxHashMap<PathId, u64>, -7546 stream_frames: StreamMetaVec, -7547 /// Whether the packet contains non-retransmittable frames (like datagrams) -7548 non_retransmits: bool, -7549 /// If the datagram containing these frames should be padded to the min MTU -7550 requires_padding: bool, -7551} -7552 -7553impl SentFrames { -7554 /// Returns whether the packet contains only ACKs -7555 fn is_ack_only(&self, streams: &StreamsState) -> bool { -7556 !self.largest_acked.is_empty() -7557 && !self.non_retransmits -7558 && self.stream_frames.is_empty() -7559 && self.retransmits.is_empty(streams) -7560 } -7561 -7562 fn retransmits_mut(&mut self) -> &mut Retransmits { -7563 self.retransmits.get_or_create() -7564 } -7565 -7566 fn record_sent_frame(&mut self, frame: frame::EncodableFrame<'_>) { -7567 use frame::EncodableFrame::*; -7568 match frame { -7569 PathAck(path_ack_encoder) => { -7570 if let Some(max) = path_ack_encoder.ranges.max() { -7571 self.largest_acked.insert(path_ack_encoder.path_id, max); -7572 } -7573 } -7574 Ack(ack_encoder) => { -7575 if let Some(max) = ack_encoder.ranges.max() { -7576 self.largest_acked.insert(PathId::ZERO, max); -7577 } -7578 } -7579 Close(_) => { /* non retransmittable, but after this we don't really care */ } -7580 PathResponse(_) => self.non_retransmits = true, -7581 HandshakeDone(_) => self.retransmits_mut().handshake_done = true, -7582 ReachOut(frame) => self.retransmits_mut().reach_out.push(frame), -7583 ObservedAddr(_) => self.retransmits_mut().observed_addr = true, -7584 Ping(_) => self.non_retransmits = true, -7585 ImmediateAck(_) => self.non_retransmits = true, -7586 AckFrequency(_) => self.retransmits_mut().ack_frequency = true, -7587 PathChallenge(_) => self.non_retransmits = true, -7588 Crypto(crypto) => self.retransmits_mut().crypto.push_back(crypto), -7589 PathAbandon(path_abandon) => { -7590 self.retransmits_mut() -7591 .path_abandon -7592 .entry(path_abandon.path_id) -7593 .or_insert(path_abandon.error_code); -7594 } -7595 PathStatusAvailable(frame::PathStatusAvailable { path_id, .. }) -7596 | PathStatusBackup(frame::PathStatusBackup { path_id, .. }) => { -7597 self.retransmits_mut().path_status.insert(path_id); -7598 } -7599 MaxPathId(_) => self.retransmits_mut().max_path_id = true, -7600 PathsBlocked(_) => self.retransmits_mut().paths_blocked = true, -7601 PathCidsBlocked(path_cids_blocked) => { -7602 self.retransmits_mut() -7603 .path_cids_blocked -7604 .insert(path_cids_blocked.path_id); -7605 } -7606 ResetStream(reset) => self -7607 .retransmits_mut() -7608 .reset_stream -7609 .push((reset.id, reset.error_code)), -7610 StopSending(stop_sending) => self.retransmits_mut().stop_sending.push(stop_sending), -7611 NewConnectionId(new_cid) => self.retransmits_mut().new_cids.push(new_cid.issued()), -7612 RetireConnectionId(retire_cid) => self -7613 .retransmits_mut() -7614 .retire_cids -7615 .push((retire_cid.path_id.unwrap_or_default(), retire_cid.sequence)), -7616 Datagram(_) => self.non_retransmits = true, -7617 NewToken(_) => {} -7618 AddAddress(add_address) => { -7619 self.retransmits_mut().add_address.insert(add_address); -7620 } -7621 RemoveAddress(remove_address) => { -7622 self.retransmits_mut().remove_address.insert(remove_address); -7623 } -7624 StreamMeta(stream_meta_encoder) => self.stream_frames.push(stream_meta_encoder.meta), -7625 MaxData(_) => self.retransmits_mut().max_data = true, -7626 MaxStreamData(max) => { -7627 self.retransmits_mut().max_stream_data.insert(max.id); -7628 } -7629 MaxStreams(max_streams) => { -7630 self.retransmits_mut().max_stream_id[max_streams.dir as usize] = true -7631 } -7632 StreamsBlocked(streams_blocked) => { -7633 self.retransmits_mut().streams_blocked[streams_blocked.dir as usize] = true -7634 } -7635 } -7636 } -7637} -7638 -7639/// Compute the negotiated idle timeout based on local and remote max_idle_timeout transport parameters. +6941 /// When multipath is enabled, this value is the minimum MTU across all available paths. +6942 pub fn current_mtu(&self) -> u16 { +6943 self.paths +6944 .iter() +6945 .filter(|&(path_id, _path_state)| !self.abandoned_paths.contains(path_id)) +6946 .map(|(_path_id, path_state)| path_state.data.current_mtu()) +6947 .min() +6948 .unwrap_or(INITIAL_MTU) +6949 } +6950 +6951 /// Size of non-frame data for a 1-RTT packet +6952 /// +6953 /// Quantifies space consumed by the QUIC header and AEAD tag. All other bytes in a packet are +6954 /// frames. Changes if the length of the remote connection ID changes, which is expected to be +6955 /// rare. If `pn` is specified, may additionally change unpredictably due to variations in +6956 /// latency and packet loss. +6957 fn predict_1rtt_overhead(&mut self, pn: u64, path: PathId) -> usize { +6958 let pn_len = PacketNumber::new( +6959 pn, +6960 self.spaces[SpaceId::Data] +6961 .for_path(path) +6962 .largest_acked_packet_pn +6963 .unwrap_or(0), +6964 ) +6965 .len(); +6966 +6967 // 1 byte for flags +6968 1 + self +6969 .remote_cids +6970 .get(&path) +6971 .map(|cids| cids.active().len()) +6972 .unwrap_or(20) // Max CID len in QUIC v1 +6973 + pn_len +6974 + self.tag_len_1rtt() +6975 } +6976 +6977 fn predict_1rtt_overhead_no_pn(&self) -> usize { +6978 let pn_len = 4; +6979 +6980 let cid_len = self +6981 .remote_cids +6982 .values() +6983 .map(|cids| cids.active().len()) +6984 .max() +6985 .unwrap_or(20); // Max CID len in QUIC v1 +6986 +6987 // 1 byte for flags +6988 1 + cid_len + pn_len + self.tag_len_1rtt() +6989 } +6990 +6991 fn tag_len_1rtt(&self) -> usize { +6992 // encryption_keys for Data space returns 1-RTT keys if available, otherwise 0-RTT keys +6993 let packet_crypto = self +6994 .crypto_state +6995 .encryption_keys(SpaceKind::Data, self.side.side()) +6996 .map(|(_header, packet, _level)| packet); +6997 // If neither Data nor 0-RTT keys are available, make a reasonable tag length guess. As of +6998 // this writing, all QUIC cipher suites use 16-byte tags. We could return `None` instead, +6999 // but that would needlessly prevent sending datagrams during 0-RTT. +7000 packet_crypto.map_or(16, |x| x.tag_len()) +7001 } +7002 +7003 /// Mark the path as validated, and enqueue NEW_TOKEN frames to be sent as appropriate +7004 fn on_path_validated(&mut self, path_id: PathId) { +7005 self.path_data_mut(path_id).validated = true; +7006 let ConnectionSide::Server { server_config } = &self.side else { +7007 return; +7008 }; +7009 let network_path = self.path_data(path_id).network_path; +7010 let new_tokens = &mut self.spaces[SpaceId::Data as usize].pending.new_tokens; +7011 new_tokens.clear(); +7012 for _ in 0..server_config.validation_token.sent { +7013 new_tokens.push(network_path); +7014 } +7015 } +7016 +7017 /// Handle new path status information: PATH_STATUS_AVAILABLE, PATH_STATUS_BACKUP +7018 fn on_path_status(&mut self, path_id: PathId, status: PathStatus, status_seq_no: VarInt) { +7019 if let Some(path) = self.paths.get_mut(&path_id) { +7020 path.data.status.remote_update(status, status_seq_no); +7021 } else { +7022 debug!("PATH_STATUS_AVAILABLE received unknown path {:?}", path_id); +7023 } +7024 self.events.push_back( +7025 PathEvent::RemoteStatus { +7026 id: path_id, +7027 status, +7028 } +7029 .into(), +7030 ); +7031 } +7032 +7033 /// Returns the maximum [`PathId`] to be used for sending in this connection. +7034 /// +7035 /// This is calculated as minimum between the local and remote's maximums when multipath is +7036 /// enabled, or `None` when disabled. +7037 /// +7038 /// For data that's received, we should use [`Self::local_max_path_id`] instead. +7039 /// The reasoning is that the remote might already have updated to its own newer +7040 /// [`Self::max_path_id`] after sending out a `MAX_PATH_ID` frame, but it got re-ordered. +7041 fn max_path_id(&self) -> Option<PathId> { +7042 if self.is_multipath_negotiated() { +7043 Some(self.remote_max_path_id.min(self.local_max_path_id)) +7044 } else { +7045 None +7046 } +7047 } +7048 +7049 /// Returns whether this connection has a socket that supports IPv6. +7050 /// +7051 /// TODO(matheus23): This is related to noq endpoint state's `ipv6` bool. We should move that info +7052 /// here instead of trying to hack around not knowing it exactly. +7053 fn is_ipv6(&self) -> bool { +7054 self.paths +7055 .values() +7056 .any(|p| p.data.network_path.remote.is_ipv6()) +7057 } +7058 +7059 /// Add addresses the local endpoint considers are reachable for nat traversal. +7060 pub fn add_nat_traversal_address( +7061 &mut self, +7062 address: SocketAddr, +7063 ) -> Result<(), n0_nat_traversal::Error> { +7064 if let Some(added) = self.n0_nat_traversal.add_local_address(address)? { +7065 self.spaces[SpaceId::Data].pending.add_address.insert(added); +7066 }; +7067 Ok(()) +7068 } +7069 +7070 /// Removes an address the endpoing no longer considers reachable for nat traversal +7071 /// +7072 /// Addresses not present in the set will be silently ignored. +7073 pub fn remove_nat_traversal_address( +7074 &mut self, +7075 address: SocketAddr, +7076 ) -> Result<(), n0_nat_traversal::Error> { +7077 if let Some(removed) = self.n0_nat_traversal.remove_local_address(address)? { +7078 self.spaces[SpaceId::Data] +7079 .pending +7080 .remove_address +7081 .insert(removed); +7082 } +7083 Ok(()) +7084 } +7085 +7086 /// Get the current local nat traversal addresses +7087 pub fn get_local_nat_traversal_addresses( +7088 &self, +7089 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { +7090 self.n0_nat_traversal.get_local_nat_traversal_addresses() +7091 } +7092 +7093 /// Get the currently advertised nat traversal addresses by the server +7094 pub fn get_remote_nat_traversal_addresses( +7095 &self, +7096 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { +7097 Ok(self +7098 .n0_nat_traversal +7099 .client_side()? +7100 .get_remote_nat_traversal_addresses()) +7101 } +7102 +7103 /// Initiates a new nat traversal round +7104 /// +7105 /// A nat traversal round involves advertising the client's local addresses in +7106 /// `REACH_OUT` frames, and initiating probing of the known remote addresses. When a new +7107 /// round is initiated, the previous one is cancelled. +7108 /// +7109 /// For all probes that succeed, if any, a new path will be opened on the successful +7110 /// 4-tuple. +7111 /// +7112 /// Returns the server addresses that are now being probed. If addresses fail due to +7113 /// spurious errors, these might succeed later and not be returned in this set. +7114 pub fn initiate_nat_traversal_round( +7115 &mut self, +7116 now: Instant, +7117 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { +7118 if self.state.is_closed() { +7119 return Err(n0_nat_traversal::Error::Closed); +7120 } +7121 +7122 let ipv6 = self.is_ipv6(); +7123 let client_state = self.n0_nat_traversal.client_side_mut()?; +7124 let (mut reach_out_frames, probed_addrs) = +7125 client_state.initiate_nat_traversal_round(ipv6)?; +7126 if !probed_addrs.is_empty() { +7127 let delay = RttEstimator::new(self.config.initial_rtt).pto_base() * 2 / 3; +7128 self.timers.set( +7129 Timer::Conn(ConnTimer::NatTraversalProbeRetry), +7130 now + delay, +7131 self.qlog.with_time(now), +7132 ); +7133 } +7134 +7135 self.spaces[SpaceId::Data] +7136 .pending +7137 .reach_out +7138 .append(&mut reach_out_frames); +7139 +7140 Ok(probed_addrs) +7141 } +7142 +7143 /// Whether the handshake is considered **confirmed**. +7144 /// +7145 /// <https://www.rfc-editor.org/rfc/rfc9001#section-4.1.2> defines a handshake to be +7146 /// confirmed when you know the peer successfully received and successfully processed +7147 /// your TLS Finished message. +7148 /// +7149 /// Implementation-wise this is the point at which the handshake crypto keys are +7150 /// discarded. So we can use this to know if the handshake is confirmed. +7151 fn is_handshake_confirmed(&self) -> bool { +7152 !self.is_handshaking() && !self.crypto_state.has_keys(EncryptionLevel::Handshake) +7153 } +7154} +7155 +7156impl fmt::Debug for Connection { +7157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +7158 f.debug_struct("Connection") +7159 .field("handshake_cid", &self.handshake_cid) +7160 .finish() +7161 } +7162} +7163 +7164/// Hints when the caller identifies a network change. +7165pub trait NetworkChangeHint: std::fmt::Debug + 'static { +7166 /// Inform the connection if a path may recover after a network change. +7167 /// +7168 /// After network changes, paths may not be recoverable. In this case, waiting for the path to +7169 /// become idle may take longer than what is desirable. If [`Self::is_path_recoverable`] +7170 /// returns `false`, a multipath-enabled, client-side connection will establish a new path to +7171 /// the same remote, closing the current one, instead of migrating the path. +7172 /// +7173 /// Paths that are deemed recoverable will simply be sent a PING for a liveness check. +7174 fn is_path_recoverable(&self, path_id: PathId, network_path: FourTuple) -> bool; +7175} +7176 +7177/// Return value for [`Connection::poll_transmit_path_space`]. +7178#[derive(Debug)] +7179enum PollPathSpaceStatus { +7180 /// Nothing to send in the space, nothing was written into the [`TransmitBuf`]. +7181 NothingToSend { +7182 /// If true there was data to send but congestion control did not allow so. +7183 congestion_blocked: bool, +7184 }, +7185 /// One or more packets have been written into the [`TransmitBuf`]. +7186 WrotePacket { +7187 /// The highest packet number. +7188 last_packet_number: u64, +7189 /// Whether to pad an already started datagram in the next packet. +7190 /// +7191 /// When packets in Initial, 0-RTT or Handshake packet do not fill the entire +7192 /// datagram they may decide to coalesce with the next packet from a higher +7193 /// encryption level on the same path. But the earlier packet may require specific +7194 /// size requirements for the datagram they are sent in. +7195 /// +7196 /// If a space did not complete the datagram, they use this to request the correct +7197 /// padding in the final packet of the datagram so that the final datagram will have +7198 /// the correct size. +7199 /// +7200 /// If a space did fill an entire datagram, it leaves this to the default of +7201 /// [`PadDatagram::No`]. +7202 pad_datagram: PadDatagram, +7203 }, +7204 /// Send the contents of the transmit immediately. +7205 /// +7206 /// Packets were written and the GSO batch must end now, regardless from whether higher +7207 /// spaces still have frames to write. This is used when the last datagram written would +7208 /// require too much padding to continue a GSO batch, which would waste space on the +7209 /// wire. +7210 Send { +7211 /// The highest packet number written into the transmit. +7212 last_packet_number: u64, +7213 }, +7214} +7215 +7216/// Information used to decide what frames to schedule into which packets. +7217/// +7218/// Primarily used by [`Connection::poll_transmit_on_path`] and the functions that help +7219/// building packets for it: [`Connection::poll_transmit_path_space`] and +7220/// [`Connection::populate_packet`]. +7221#[derive(Debug, Copy, Clone)] +7222struct PathSchedulingInfo { +7223 /// Whether the path is abandoned. +7224 /// +7225 /// Note that a path that is abandoned but still has CIDs can still send a packet. After +7226 /// sending that packet the CIDs issued by the remote have to be considered retired as +7227 /// well. +7228 is_abandoned: bool, +7229 /// Whether the path may send [`SpaceKind::Data`] frames. +7230 /// +7231 /// Some paths should only send frames from [`SendableFrames::space_specific`]. All other +7232 /// frames are essentially frames that can be sent on any [`SpaceKind::Data`] space. For +7233 /// those we want to respect packet scheduling rules however. +7234 /// +7235 /// Roughly speaking data frames are only sent on spaces that have CIDs, are not +7236 /// abandoned and have no *better* spaces. However see to comments where this is +7237 /// populated for the exact packet scheduling implementation. +7238 /// +7239 /// This essentially marks this paths as the best validated space ID. Except during +7240 /// the handshake in which case it does not need to be validated. Several paths could be +7241 /// equally good and all have this set to `true`, in that case packet scheduling can +7242 /// choose which path to use. Currently it chooses the lowest path that is not +7243 /// congestion blocked. +7244 /// +7245 /// Note that once in the closed or draining states this will never be true. +7246 may_send_data: bool, +7247 /// Whether the path may send a CONNECTION_CLOSE frame. +7248 /// +7249 /// This essentially marks this path as the best validated space ID with a fallback +7250 /// to unvalidated spaces if there are no validated spaces. Like for +7251 /// [`Self::may_send_data`] other paths could be equally good. +7252 may_send_close: bool, +7253 may_self_abandon: bool, +7254} +7255 +7256#[derive(Debug, Copy, Clone, PartialEq, Eq)] +7257enum PathBlocked { +7258 No, +7259 AntiAmplification, +7260 Congestion, +7261 Pacing, +7262} +7263 +7264/// Fields of `Connection` specific to it being client-side or server-side +7265enum ConnectionSide { +7266 Client { +7267 /// Sent in every outgoing Initial packet. Always empty after Initial keys are discarded +7268 token: Bytes, +7269 token_store: Arc<dyn TokenStore>, +7270 server_name: String, +7271 }, +7272 Server { +7273 server_config: Arc<ServerConfig>, +7274 }, +7275} +7276 +7277impl ConnectionSide { +7278 fn is_client(&self) -> bool { +7279 self.side().is_client() +7280 } +7281 +7282 fn is_server(&self) -> bool { +7283 self.side().is_server() +7284 } +7285 +7286 fn side(&self) -> Side { +7287 match *self { +7288 Self::Client { .. } => Side::Client, +7289 Self::Server { .. } => Side::Server, +7290 } +7291 } +7292} +7293 +7294impl From<SideArgs> for ConnectionSide { +7295 fn from(side: SideArgs) -> Self { +7296 match side { +7297 SideArgs::Client { +7298 token_store, +7299 server_name, +7300 } => Self::Client { +7301 token: token_store.take(&server_name).unwrap_or_default(), +7302 token_store, +7303 server_name, +7304 }, +7305 SideArgs::Server { +7306 server_config, +7307 pref_addr_cid: _, +7308 path_validated: _, +7309 } => Self::Server { server_config }, +7310 } +7311 } +7312} +7313 +7314/// Parameters to `Connection::new` specific to it being client-side or server-side +7315pub(crate) enum SideArgs { +7316 Client { +7317 token_store: Arc<dyn TokenStore>, +7318 server_name: String, +7319 }, +7320 Server { +7321 server_config: Arc<ServerConfig>, +7322 pref_addr_cid: Option<ConnectionId>, +7323 path_validated: bool, +7324 }, +7325} +7326 +7327impl SideArgs { +7328 pub(crate) fn pref_addr_cid(&self) -> Option<ConnectionId> { +7329 match *self { +7330 Self::Client { .. } => None, +7331 Self::Server { pref_addr_cid, .. } => pref_addr_cid, +7332 } +7333 } +7334 +7335 pub(crate) fn path_validated(&self) -> bool { +7336 match *self { +7337 Self::Client { .. } => true, +7338 Self::Server { path_validated, .. } => path_validated, +7339 } +7340 } +7341 +7342 pub(crate) fn side(&self) -> Side { +7343 match *self { +7344 Self::Client { .. } => Side::Client, +7345 Self::Server { .. } => Side::Server, +7346 } +7347 } +7348} +7349 +7350/// Reasons why a connection might be lost +7351#[derive(Debug, Error, Clone, PartialEq, Eq)] +7352pub enum ConnectionError { +7353 /// The peer doesn't implement any supported version +7354 #[error("peer doesn't implement any supported version")] +7355 VersionMismatch, +7356 /// The peer violated the QUIC specification as understood by this implementation +7357 #[error(transparent)] +7358 TransportError(#[from] TransportError), +7359 /// The peer's QUIC stack aborted the connection automatically +7360 #[error("aborted by peer: {0}")] +7361 ConnectionClosed(frame::ConnectionClose), +7362 /// The peer closed the connection +7363 #[error("closed by peer: {0}")] +7364 ApplicationClosed(frame::ApplicationClose), +7365 /// The peer is unable to continue processing this connection, usually due to having restarted +7366 #[error("reset by peer")] +7367 Reset, +7368 /// Communication with the peer has lapsed for longer than the negotiated idle timeout +7369 /// +7370 /// If neither side is sending keep-alives, a connection will time out after a long enough idle +7371 /// period even if the peer is still reachable. See also [`TransportConfig::max_idle_timeout()`] +7372 /// and [`TransportConfig::keep_alive_interval()`]. +7373 #[error("timed out")] +7374 TimedOut, +7375 /// The local application closed the connection +7376 #[error("closed")] +7377 LocallyClosed, +7378 /// The connection could not be created because not enough of the CID space is available +7379 /// +7380 /// Try using longer connection IDs. +7381 #[error("CIDs exhausted")] +7382 CidsExhausted, +7383} +7384 +7385impl From<Close> for ConnectionError { +7386 fn from(x: Close) -> Self { +7387 match x { +7388 Close::Connection(reason) => Self::ConnectionClosed(reason), +7389 Close::Application(reason) => Self::ApplicationClosed(reason), +7390 } +7391 } +7392} +7393 +7394// For compatibility with API consumers +7395impl From<ConnectionError> for io::Error { +7396 fn from(x: ConnectionError) -> Self { +7397 use ConnectionError::*; +7398 let kind = match x { +7399 TimedOut => io::ErrorKind::TimedOut, +7400 Reset => io::ErrorKind::ConnectionReset, +7401 ApplicationClosed(_) | ConnectionClosed(_) => io::ErrorKind::ConnectionAborted, +7402 TransportError(_) | VersionMismatch | LocallyClosed | CidsExhausted => { +7403 io::ErrorKind::Other +7404 } +7405 }; +7406 Self::new(kind, x) +7407 } +7408} +7409 +7410/// Errors that might trigger a path being closed +7411// TODO(@divma): maybe needs to be reworked based on what we want to do with the public API +7412#[derive(Debug, Error, PartialEq, Eq, Clone, Copy)] +7413pub enum PathError { +7414 /// The extension was not negotiated with the peer +7415 #[error("multipath extension not negotiated")] +7416 MultipathNotNegotiated, +7417 /// Paths can only be opened client-side +7418 #[error("the server side may not open a path")] +7419 ServerSideNotAllowed, +7420 /// Current limits do not allow us to open more paths +7421 #[error("maximum number of concurrent paths reached")] +7422 MaxPathIdReached, +7423 /// No remote CIDs available to open a new path +7424 #[error("remoted CIDs exhausted")] +7425 RemoteCidsExhausted, +7426 /// Path could not be validated and will be abandoned +7427 #[error("path validation failed")] +7428 ValidationFailed, +7429 /// The remote address for the path is not supported by the endpoint +7430 #[error("invalid remote address")] +7431 InvalidRemoteAddress(SocketAddr), +7432} +7433 +7434/// Errors triggered when abandoning a path +7435#[derive(Debug, Error, Clone, Eq, PartialEq)] +7436pub enum ClosePathError { +7437 /// Multipath is not negotiated +7438 #[error("Multipath extension not negotiated")] +7439 MultipathNotNegotiated, +7440 /// The path is already closed or was never opened +7441 #[error("closed path")] +7442 ClosedPath, +7443 /// Cannot close the last remaining open path via the local API. +7444 /// +7445 /// Use [`Connection::close`] to end the connection instead. +7446 #[error("last open path")] +7447 LastOpenPath, +7448} +7449 +7450/// Error when the multipath extension was not negotiated, but attempted to be used. +7451#[derive(Debug, Error, Clone, Copy)] +7452#[error("Multipath extension not negotiated")] +7453pub struct MultipathNotNegotiated { +7454 _private: (), +7455} +7456 +7457/// Events of interest to the application +7458#[derive(Debug)] +7459pub enum Event { +7460 /// The connection's handshake data is ready +7461 HandshakeDataReady, +7462 /// The connection was successfully established +7463 Connected, +7464 /// The TLS handshake was confirmed +7465 HandshakeConfirmed, +7466 /// The connection was lost +7467 /// +7468 /// Emitted when the connection is closed due to an error, a timeout, or the peer closing it. +7469 /// This is **not** emitted when the local application closes the connection via +7470 /// [`Connection::close()`](crate::Connection::close). In that case, pending operations will +7471 /// fail with [`ConnectionError::LocallyClosed`]. +7472 ConnectionLost { +7473 /// Reason that the connection was closed +7474 reason: ConnectionError, +7475 }, +7476 /// Stream events +7477 Stream(StreamEvent), +7478 /// One or more application datagrams have been received +7479 DatagramReceived, +7480 /// One or more application datagrams have been sent after blocking +7481 DatagramsUnblocked, +7482 /// (Multi)Path events +7483 Path(PathEvent), +7484 /// n0's nat traversal events +7485 NatTraversal(n0_nat_traversal::Event), +7486} +7487 +7488impl From<PathEvent> for Event { +7489 fn from(source: PathEvent) -> Self { +7490 Self::Path(source) +7491 } +7492} +7493 +7494fn get_max_ack_delay(params: &TransportParameters) -> Duration { +7495 Duration::from_micros(params.max_ack_delay.0 * 1000) +7496} +7497 +7498/// Prevents overflow and improves behavior in extreme circumstances. +7499const MAX_BACKOFF_EXPONENT: u32 = 16; +7500 +7501/// The max interval between successive tail-loss probes. +7502/// +7503/// This is the "normal" value we use. +7504const MAX_PTO_INTERVAL: Duration = Duration::from_secs(2); +7505 +7506/// The idle time, below which we use the shorter [`MAX_PTO_FAST_INTERVAL`]. +7507const MIN_IDLE_FOR_FAST_PTO: Duration = Duration::from_secs(25); +7508 +7509/// The max interval between successive tail-loss probes with short idle times. +7510/// +7511/// If the path or connection idle time is less than [`MIN_IDLE_FOR_FAST_PTO`] then we use +7512/// this value to ensure we have plenty of retransmits before we reach the idle time. +7513const MAX_PTO_FAST_INTERVAL: Duration = Duration::from_secs(1); +7514 +7515/// The RTT threshold above which we cap the PTO interval to 1.5 * smoothed_rtt +7516/// +7517/// This is RTT time above which 1.5 * RTT > [`MAX_PTO_INTERVAL`], for these links we want +7518/// to extend the interval between tail-loss probes to not fill the entire pipe with them. +7519const SLOW_RTT_THRESHOLD: Duration = +7520 Duration::from_millis((MAX_PTO_INTERVAL.as_millis() as u64 * 2) / 3); +7521 +7522/// Minimal remaining size to allow packet coalescing, excluding cryptographic tag +7523/// +7524/// This must be at least as large as the header for a well-formed empty packet to be coalesced, +7525/// plus some space for frames. We only care about handshake headers because short header packets +7526/// necessarily have smaller headers, and initial packets are only ever the first packet in a +7527/// datagram (because we coalesce in ascending packet space order and the only reason to split a +7528/// packet is when packet space changes). +7529const MIN_PACKET_SPACE: usize = MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE + 32; +7530 +7531/// Largest amount of space that could be occupied by a Handshake or 0-RTT packet's header +7532/// +7533/// Excludes packet-type-specific fields such as packet number or Initial token +7534// https://www.rfc-editor.org/rfc/rfc9000.html#name-0-rtt: flags + version + dcid len + dcid + +7535// scid len + scid + length + pn +7536const MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE: usize = +7537 1 + 4 + 1 + MAX_CID_SIZE + 1 + MAX_CID_SIZE + VarInt::from_u32(u16::MAX as u32).size() + 4; +7538 +7539#[derive(Default)] +7540struct SentFrames { +7541 retransmits: ThinRetransmits, +7542 /// The packet number of the largest acknowledged packet for each path +7543 largest_acked: FxHashMap<PathId, u64>, +7544 stream_frames: StreamMetaVec, +7545 /// Whether the packet contains non-retransmittable frames (like datagrams) +7546 non_retransmits: bool, +7547 /// If the datagram containing these frames should be padded to the min MTU +7548 requires_padding: bool, +7549} +7550 +7551impl SentFrames { +7552 /// Returns whether the packet contains only ACKs +7553 fn is_ack_only(&self, streams: &StreamsState) -> bool { +7554 !self.largest_acked.is_empty() +7555 && !self.non_retransmits +7556 && self.stream_frames.is_empty() +7557 && self.retransmits.is_empty(streams) +7558 } +7559 +7560 fn retransmits_mut(&mut self) -> &mut Retransmits { +7561 self.retransmits.get_or_create() +7562 } +7563 +7564 fn record_sent_frame(&mut self, frame: frame::EncodableFrame<'_>) { +7565 use frame::EncodableFrame::*; +7566 match frame { +7567 PathAck(path_ack_encoder) => { +7568 if let Some(max) = path_ack_encoder.ranges.max() { +7569 self.largest_acked.insert(path_ack_encoder.path_id, max); +7570 } +7571 } +7572 Ack(ack_encoder) => { +7573 if let Some(max) = ack_encoder.ranges.max() { +7574 self.largest_acked.insert(PathId::ZERO, max); +7575 } +7576 } +7577 Close(_) => { /* non retransmittable, but after this we don't really care */ } +7578 PathResponse(_) => self.non_retransmits = true, +7579 HandshakeDone(_) => self.retransmits_mut().handshake_done = true, +7580 ReachOut(frame) => self.retransmits_mut().reach_out.push(frame), +7581 ObservedAddr(_) => self.retransmits_mut().observed_addr = true, +7582 Ping(_) => self.non_retransmits = true, +7583 ImmediateAck(_) => self.non_retransmits = true, +7584 AckFrequency(_) => self.retransmits_mut().ack_frequency = true, +7585 PathChallenge(_) => self.non_retransmits = true, +7586 Crypto(crypto) => self.retransmits_mut().crypto.push_back(crypto), +7587 PathAbandon(path_abandon) => { +7588 self.retransmits_mut() +7589 .path_abandon +7590 .entry(path_abandon.path_id) +7591 .or_insert(path_abandon.error_code); +7592 } +7593 PathStatusAvailable(frame::PathStatusAvailable { path_id, .. }) +7594 | PathStatusBackup(frame::PathStatusBackup { path_id, .. }) => { +7595 self.retransmits_mut().path_status.insert(path_id); +7596 } +7597 MaxPathId(_) => self.retransmits_mut().max_path_id = true, +7598 PathsBlocked(_) => self.retransmits_mut().paths_blocked = true, +7599 PathCidsBlocked(path_cids_blocked) => { +7600 self.retransmits_mut() +7601 .path_cids_blocked +7602 .insert(path_cids_blocked.path_id); +7603 } +7604 ResetStream(reset) => self +7605 .retransmits_mut() +7606 .reset_stream +7607 .push((reset.id, reset.error_code)), +7608 StopSending(stop_sending) => self.retransmits_mut().stop_sending.push(stop_sending), +7609 NewConnectionId(new_cid) => self.retransmits_mut().new_cids.push(new_cid.issued()), +7610 RetireConnectionId(retire_cid) => self +7611 .retransmits_mut() +7612 .retire_cids +7613 .push((retire_cid.path_id.unwrap_or_default(), retire_cid.sequence)), +7614 Datagram(_) => self.non_retransmits = true, +7615 NewToken(_) => {} +7616 AddAddress(add_address) => { +7617 self.retransmits_mut().add_address.insert(add_address); +7618 } +7619 RemoveAddress(remove_address) => { +7620 self.retransmits_mut().remove_address.insert(remove_address); +7621 } +7622 StreamMeta(stream_meta_encoder) => self.stream_frames.push(stream_meta_encoder.meta), +7623 MaxData(_) => self.retransmits_mut().max_data = true, +7624 MaxStreamData(max) => { +7625 self.retransmits_mut().max_stream_data.insert(max.id); +7626 } +7627 MaxStreams(max_streams) => { +7628 self.retransmits_mut().max_stream_id[max_streams.dir as usize] = true +7629 } +7630 StreamsBlocked(streams_blocked) => { +7631 self.retransmits_mut().streams_blocked[streams_blocked.dir as usize] = true +7632 } +7633 } +7634 } +7635} +7636 +7637/// Compute the negotiated idle timeout based on local and remote max_idle_timeout transport parameters. +7638/// +7639/// 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.> 7640/// -7641/// 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.> +7641/// 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.> 7642/// -7643/// 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.> -7644/// -7645/// Returns the negotiated idle timeout as a `Duration`, or `None` when both endpoints have opted out of idle timeout. -7646fn negotiate_max_idle_timeout(x: Option<VarInt>, y: Option<VarInt>) -> Option<Duration> { -7647 match (x, y) { -7648 (Some(VarInt(0)) | None, Some(VarInt(0)) | None) => None, -7649 (Some(VarInt(0)) | None, Some(y)) => Some(Duration::from_millis(y.0)), -7650 (Some(x), Some(VarInt(0)) | None) => Some(Duration::from_millis(x.0)), -7651 (Some(x), Some(y)) => Some(Duration::from_millis(cmp::min(x, y).0)), -7652 } -7653} -7654 -7655#[cfg(test)] -7656mod tests { -7657 use super::*; -7658 -7659 #[test] -7660 fn negotiate_max_idle_timeout_commutative() { -7661 let test_params = [ -7662 (None, None, None), -7663 (None, Some(VarInt(0)), None), -7664 (None, Some(VarInt(2)), Some(Duration::from_millis(2))), -7665 (Some(VarInt(0)), Some(VarInt(0)), None), -7666 ( -7667 Some(VarInt(2)), -7668 Some(VarInt(0)), -7669 Some(Duration::from_millis(2)), -7670 ), -7671 ( -7672 Some(VarInt(1)), -7673 Some(VarInt(4)), -7674 Some(Duration::from_millis(1)), -7675 ), -7676 ]; -7677 -7678 for (left, right, result) in test_params { -7679 assert_eq!(negotiate_max_idle_timeout(left, right), result); -7680 assert_eq!(negotiate_max_idle_timeout(right, left), result); -7681 } -7682 } -7683}

\ No newline at end of file +7643/// Returns the negotiated idle timeout as a `Duration`, or `None` when both endpoints have opted out of idle timeout. +7644fn negotiate_max_idle_timeout(x: Option<VarInt>, y: Option<VarInt>) -> Option<Duration> { +7645 match (x, y) { +7646 (Some(VarInt(0)) | None, Some(VarInt(0)) | None) => None, +7647 (Some(VarInt(0)) | None, Some(y)) => Some(Duration::from_millis(y.0)), +7648 (Some(x), Some(VarInt(0)) | None) => Some(Duration::from_millis(x.0)), +7649 (Some(x), Some(y)) => Some(Duration::from_millis(cmp::min(x, y).0)), +7650 } +7651} +7652 +7653#[cfg(test)] +7654mod tests { +7655 use super::*; +7656 +7657 #[test] +7658 fn negotiate_max_idle_timeout_commutative() { +7659 let test_params = [ +7660 (None, None, None), +7661 (None, Some(VarInt(0)), None), +7662 (None, Some(VarInt(2)), Some(Duration::from_millis(2))), +7663 (Some(VarInt(0)), Some(VarInt(0)), None), +7664 ( +7665 Some(VarInt(2)), +7666 Some(VarInt(0)), +7667 Some(Duration::from_millis(2)), +7668 ), +7669 ( +7670 Some(VarInt(1)), +7671 Some(VarInt(4)), +7672 Some(Duration::from_millis(1)), +7673 ), +7674 ]; +7675 +7676 for (left, right, result) in test_params { +7677 assert_eq!(negotiate_max_idle_timeout(left, right), result); +7678 assert_eq!(negotiate_max_idle_timeout(right, left), result); +7679 } +7680 } +7681}

\ No newline at end of file