diff --git a/pr/338/docs/iroh_quinn/enum.ConnectionError.html b/pr/338/docs/iroh_quinn/enum.ConnectionError.html index 9a3673ced..4c8cdc7b3 100644 --- a/pr/338/docs/iroh_quinn/enum.ConnectionError.html +++ b/pr/338/docs/iroh_quinn/enum.ConnectionError.html @@ -1,4 +1,4 @@ -ConnectionError in iroh_quinn - Rust

ConnectionError

Enum ConnectionError 

Source
pub enum ConnectionError {
+ConnectionError in iroh_quinn - Rust

ConnectionError

Enum ConnectionError 

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

LocallyClosed

The local application closed the connection

§

CidsExhausted

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

Try using longer connection IDs.

-

Trait Implementations§

Source§

impl Clone for ConnectionError

Source§

fn clone(&self) -> ConnectionError

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectionError

Source§

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

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

impl Display for ConnectionError

Source§

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

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

impl Error for ConnectionError

Source§

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

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

fn description(&self) -> &str

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

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

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

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

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

impl From<Close> for ConnectionError

Source§

fn from(x: Close) -> ConnectionError

Converts to this type from the input type.
Source§

impl From<CloseReason> for ConnectionError

Source§

fn from(value: CloseReason) -> ConnectionError

Converts to this type from the input type.
Source§

impl From<ConnectionError> for ReadError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for ResetError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for SendDatagramError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for StoppedError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for WriteError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for ConnectionError

Source§

fn from(source: Error) -> ConnectionError

Converts to this type from the input type.
Source§

impl PartialEq for ConnectionError

Source§

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

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

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

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

impl Eq for ConnectionError

Source§

impl StructuralPartialEq for ConnectionError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for ConnectionError

Source§

fn clone(&self) -> ConnectionError

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectionError

Source§

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

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

impl Display for ConnectionError

Source§

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

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

impl Error for ConnectionError

Source§

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

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

fn description(&self) -> &str

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

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

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

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

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

impl From<Close> for ConnectionError

Source§

fn from(x: Close) -> ConnectionError

Converts to this type from the input type.
Source§

impl From<CloseReason> for ConnectionError

Source§

fn from(value: CloseReason) -> ConnectionError

Converts to this type from the input type.
Source§

impl From<ConnectionError> for ReadError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for ResetError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for SendDatagramError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for StoppedError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for WriteError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for ConnectionError

Source§

fn from(source: Error) -> ConnectionError

Converts to this type from the input type.
Source§

impl PartialEq for ConnectionError

Source§

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

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

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

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

impl Eq for ConnectionError

Source§

impl StructuralPartialEq for ConnectionError

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/338/docs/iroh_quinn_proto/enum.ClosePathError.html b/pr/338/docs/iroh_quinn_proto/enum.ClosePathError.html index 5efaf976f..06a717c2b 100644 --- a/pr/338/docs/iroh_quinn_proto/enum.ClosePathError.html +++ b/pr/338/docs/iroh_quinn_proto/enum.ClosePathError.html @@ -1,11 +1,11 @@ -ClosePathError in iroh_quinn_proto - Rust

ClosePathError

Enum ClosePathError 

Source
pub enum ClosePathError {
+ClosePathError in iroh_quinn_proto - Rust

ClosePathError

Enum ClosePathError 

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

Errors triggered when abandoning a path

Variants§

§

ClosedPath

The path is already closed or was never opened

§

LastOpenPath

This is the last path, which can not be abandoned

-

Trait Implementations§

Source§

impl Clone for ClosePathError

Source§

fn clone(&self) -> ClosePathError

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosePathError

Source§

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

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

impl Display for ClosePathError

Source§

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

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

impl Error for ClosePathError

1.30.0 · Source§

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

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

fn description(&self) -> &str

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

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

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

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

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

impl PartialEq for ClosePathError

Source§

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

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

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

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

impl Eq for ClosePathError

Source§

impl StructuralPartialEq for ClosePathError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for ClosePathError

Source§

fn clone(&self) -> ClosePathError

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosePathError

Source§

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

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

impl Display for ClosePathError

Source§

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

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

impl Error for ClosePathError

1.30.0 · Source§

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

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

fn description(&self) -> &str

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

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

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

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

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

impl PartialEq for ClosePathError

Source§

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

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

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

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

impl Eq for ClosePathError

Source§

impl StructuralPartialEq for ClosePathError

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/338/docs/iroh_quinn_proto/enum.ConnectionError.html b/pr/338/docs/iroh_quinn_proto/enum.ConnectionError.html index 192ef0967..6d3e0cda7 100644 --- a/pr/338/docs/iroh_quinn_proto/enum.ConnectionError.html +++ b/pr/338/docs/iroh_quinn_proto/enum.ConnectionError.html @@ -1,4 +1,4 @@ -ConnectionError in iroh_quinn_proto - Rust

ConnectionError

Enum ConnectionError 

Source
pub enum ConnectionError {
+ConnectionError in iroh_quinn_proto - Rust

ConnectionError

Enum ConnectionError 

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

LocallyClosed

The local application closed the connection

§

CidsExhausted

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

Try using longer connection IDs.

-

Trait Implementations§

Source§

impl Clone for ConnectionError

Source§

fn clone(&self) -> ConnectionError

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectionError

Source§

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

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

impl Display for ConnectionError

Source§

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

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

impl Error for ConnectionError

Source§

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

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

fn description(&self) -> &str

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

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

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

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

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

impl From<ConnectionError> for Error

Source§

fn from(x: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for ConnectionError

Source§

fn from(source: TransportError) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ConnectionError

Source§

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

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

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

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

impl Eq for ConnectionError

Source§

impl StructuralPartialEq for ConnectionError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for ConnectionError

Source§

fn clone(&self) -> ConnectionError

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectionError

Source§

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

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

impl Display for ConnectionError

Source§

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

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

impl Error for ConnectionError

Source§

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

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

fn description(&self) -> &str

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

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

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

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

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

impl From<ConnectionError> for Error

Source§

fn from(x: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for ConnectionError

Source§

fn from(source: TransportError) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ConnectionError

Source§

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

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

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

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

impl Eq for ConnectionError

Source§

impl StructuralPartialEq for ConnectionError

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/338/docs/iroh_quinn_proto/enum.Event.html b/pr/338/docs/iroh_quinn_proto/enum.Event.html index 476af3700..637cebb46 100644 --- a/pr/338/docs/iroh_quinn_proto/enum.Event.html +++ b/pr/338/docs/iroh_quinn_proto/enum.Event.html @@ -1,4 +1,4 @@ -Event in iroh_quinn_proto - Rust

Event

Enum Event 

Source
pub enum Event {
+Event in iroh_quinn_proto - Rust

Event

Enum Event 

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

DatagramsUnblocked

One or more application datagrams have been sent after blocking

§

Path(PathEvent)

(Multi)Path events

§

NatTraversal(Event)

Iroh’s nat traversal events

-

Trait Implementations§

Source§

impl Debug for Event

Source§

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

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

impl From<PathEvent> for Event

Source§

fn from(source: PathEvent) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

§

impl !Freeze for Event

§

impl !RefUnwindSafe for Event

§

impl Send for Event

§

impl Sync for Event

§

impl Unpin for Event

§

impl !UnwindSafe for Event

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Debug for Event

Source§

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

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

impl From<PathEvent> for Event

Source§

fn from(source: PathEvent) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

§

impl !Freeze for Event

§

impl !RefUnwindSafe for Event

§

impl Send for Event

§

impl Sync for Event

§

impl Unpin for Event

§

impl !UnwindSafe for Event

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

diff --git a/pr/338/docs/iroh_quinn_proto/enum.PathError.html b/pr/338/docs/iroh_quinn_proto/enum.PathError.html index 4e9d3c936..bc942ea95 100644 --- a/pr/338/docs/iroh_quinn_proto/enum.PathError.html +++ b/pr/338/docs/iroh_quinn_proto/enum.PathError.html @@ -1,4 +1,4 @@ -PathError in iroh_quinn_proto - Rust

PathError

Enum PathError 

Source
pub enum PathError {
+PathError in iroh_quinn_proto - Rust

PathError

Enum PathError 

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

RemoteCidsExhausted

No remote CIDs available to open a new path

§

ValidationFailed

Path could not be validated and will be abandoned

§

InvalidRemoteAddress(SocketAddr)

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

-

Trait Implementations§

Source§

impl Clone for PathError

Source§

fn clone(&self) -> PathError

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for PathError

Source§

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

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

impl Display for PathError

Source§

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

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

impl Error for PathError

1.30.0 · Source§

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

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

fn description(&self) -> &str

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

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

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

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

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

impl PartialEq for PathError

Source§

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

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

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

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

impl Copy for PathError

Source§

impl Eq for PathError

Source§

impl StructuralPartialEq for PathError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for PathError

Source§

fn clone(&self) -> PathError

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for PathError

Source§

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

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

impl Display for PathError

Source§

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

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

impl Error for PathError

1.30.0 · Source§

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

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

fn description(&self) -> &str

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

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

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

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

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

impl PartialEq for PathError

Source§

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

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

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

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

impl Copy for PathError

Source§

impl Eq for PathError

Source§

impl StructuralPartialEq for PathError

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/338/docs/iroh_quinn_proto/enum.PathEvent.html b/pr/338/docs/iroh_quinn_proto/enum.PathEvent.html index 1e7647eb2..8d5247158 100644 --- a/pr/338/docs/iroh_quinn_proto/enum.PathEvent.html +++ b/pr/338/docs/iroh_quinn_proto/enum.PathEvent.html @@ -47,7 +47,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/338/docs/iroh_quinn_proto/struct.Connection.html b/pr/338/docs/iroh_quinn_proto/struct.Connection.html index d217825b5..7420d6917 100644 --- a/pr/338/docs/iroh_quinn_proto/struct.Connection.html +++ b/pr/338/docs/iroh_quinn_proto/struct.Connection.html @@ -33,7 +33,7 @@ increasing time. Specifically, calling Instant may be interleaved in any order with a call to handle_event at that same instant; however events or timeouts with different instants must not be interleaved.

-

Implementations§

Source§

impl Connection

Source

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

Returns the next time at which handle_timeout should be called

+

Implementations§

Source§

impl Connection

Source

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

Returns the next time at which handle_timeout should be called

The value returned may change after:

  • the application performed some I/O on the connection
  • @@ -107,7 +107,7 @@ for a path that was never opened locally.

    ) -> Result<Option<Duration>, ClosedPath>

Sets the keep_alive_interval for a specific path

See TransportConfig::default_path_keep_alive_interval for details.

Returns the previous value of the setting.

-
Source

pub fn poll_transmit( +

Source

pub fn poll_transmit( &mut self, now: Instant, max_datagrams: NonZeroUsize, @@ -121,60 +121,60 @@ for a path that was never opened locally.

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

-

Source

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

Process ConnectionEvents generated by the associated Endpoint

+
Source

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

Process ConnectionEvents generated by the associated Endpoint

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

-
Source

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

Process timer expirations

+
Source

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

Process timer expirations

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

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

-
Source

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

Close a connection immediately

+
Source

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

Close a connection immediately

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

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

-
Source

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

Control datagrams

-
Source

pub fn stats(&mut self) -> ConnectionStats

Returns connection statistics

-
Source

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

Returns path statistics

-
Source

pub fn ping(&mut self)

Ping the remote endpoint

+
Source

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

Control datagrams

+
Source

pub fn stats(&mut self) -> ConnectionStats

Returns connection statistics

+
Source

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

Returns path statistics

+
Source

pub fn ping(&mut self)

Ping the remote endpoint

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

-
Source

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

Ping the remote endpoint over a specific path

+
Source

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

Ping the remote endpoint over a specific path

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

-
Source

pub fn force_key_update(&mut self)

Update traffic keys spontaneously

+
Source

pub fn force_key_update(&mut self)

Update traffic keys spontaneously

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

-
Source

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

Get a session reference

-
Source

pub fn is_handshaking(&self) -> bool

Whether the connection is in the process of being established

+
Source

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

Get a session reference

+
Source

pub fn is_handshaking(&self) -> bool

Whether the connection is in the process of being established

If this returns false, the connection may be either established or closed, signaled by the emission of a Connected or ConnectionLost message respectively.

-
Source

pub fn is_closed(&self) -> bool

Whether the connection is closed

+
Source

pub fn is_closed(&self) -> bool

Whether the connection is closed

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

A ConnectionLost event is emitted with details when the connection becomes closed.

-
Source

pub fn is_drained(&self) -> bool

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

+
Source

pub fn is_drained(&self) -> bool

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

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

-
Source

pub fn accepted_0rtt(&self) -> bool

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

+
Source

pub fn accepted_0rtt(&self) -> bool

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

The value is meaningless until after the handshake completes.

-
Source

pub fn has_0rtt(&self) -> bool

Whether 0-RTT is/was possible during the handshake

-
Source

pub fn has_pending_retransmits(&self) -> bool

Whether there are any pending retransmits

-
Source

pub fn side(&self) -> Side

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

-
Source

pub fn path_observed_address( +

Source

pub fn has_0rtt(&self) -> bool

Whether 0-RTT is/was possible during the handshake

+
Source

pub fn has_pending_retransmits(&self) -> bool

Whether there are any pending retransmits

+
Source

pub fn side(&self) -> Side

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

+
Source

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

Get the address observed by the remote over the given path

-
Source

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

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

-
Source

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

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

-
Source

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

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

+
Source

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

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

+
Source

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

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

+
Source

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

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

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

-
Source

pub fn set_max_concurrent_paths( +

Source

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

-

Source

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

Current number of remotely initiated streams that may be concurrently open

+
Source

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

Current number of remotely initiated streams that may be concurrently open

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

-
Source

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

Source

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

Source

pub fn is_multipath_negotiated(&self) -> bool

Whether the Multipath for QUIC extension is enabled.

+
Source

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

Source

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

Source

pub fn is_multipath_negotiated(&self) -> bool

Whether the Multipath for QUIC extension is enabled.

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

-
Source

pub fn local_address_changed(&mut self)

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

-
Source

pub fn current_mtu(&self) -> u16

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

Source

pub fn local_address_changed(&mut self)

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

+
Source

pub fn current_mtu(&self) -> u16

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

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

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

-
Source

pub fn add_nat_traversal_address( +

Source

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

Add addresses the local endpoint considers are reachable for nat traversal

-
Source

pub fn remove_nat_traversal_address( +

Source

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

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

Addresses not present in the set will be silently ignored.

-
Source

pub fn get_local_nat_traversal_addresses( +

Source

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

Get the current local nat traversal addresses

-
Source

pub fn get_remote_nat_traversal_addresses( +

Source

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

Get the currently advertised nat traversal addresses by the server

-
Source

pub fn initiate_nat_traversal_round( +

Source

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

Initiates a new nat traversal round

@@ -224,7 +224,7 @@ initiated, the previous one is cancelled, and paths that have not been opened ar

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§

§

impl !Freeze for Connection

§

impl !RefUnwindSafe for Connection

§

impl Send for Connection

§

impl Sync for Connection

§

impl Unpin for Connection

§

impl !UnwindSafe for Connection

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§

§

impl !Freeze for Connection

§

impl !RefUnwindSafe for Connection

§

impl Send for Connection

§

impl Sync for Connection

§

impl Unpin for Connection

§

impl !UnwindSafe for Connection

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/338/docs/iroh_quinn_proto/struct.MultipathNotNegotiated.html b/pr/338/docs/iroh_quinn_proto/struct.MultipathNotNegotiated.html index 863218d1a..dfa197efb 100644 --- a/pr/338/docs/iroh_quinn_proto/struct.MultipathNotNegotiated.html +++ b/pr/338/docs/iroh_quinn_proto/struct.MultipathNotNegotiated.html @@ -1,5 +1,5 @@ -MultipathNotNegotiated in iroh_quinn_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 iroh_quinn_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/338/docs/iroh_quinn_proto/struct.TransportError.html b/pr/338/docs/iroh_quinn_proto/struct.TransportError.html index a1880bbb0..03015f025 100644 --- a/pr/338/docs/iroh_quinn_proto/struct.TransportError.html +++ b/pr/338/docs/iroh_quinn_proto/struct.TransportError.html @@ -11,7 +11,7 @@
§reason: String

Human-readable explanation of the reason

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

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

Implementations§

Source§

impl Error

Source

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

Construct an error with a code and a reason

-

Trait Implementations§

Source§

impl Clone for Error

Source§

fn clone(&self) -> Error

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for Error

Source§

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

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

impl Display for Error

Source§

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

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

impl Error for Error

1.30.0 · Source§

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

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

fn description(&self) -> &str

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

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

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

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

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

impl From<Error> for ConnectionClose

Source§

fn from(x: TransportError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for ConnectionError

Source§

fn from(source: TransportError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for TransportError

Source§

fn from(e: Error) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for Error

Source§

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

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

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

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

Trait Implementations§

Source§

impl Clone for Error

Source§

fn clone(&self) -> Error

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for Error

Source§

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

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

impl Display for Error

Source§

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

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

impl Error for Error

1.30.0 · Source§

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

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

fn description(&self) -> &str

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

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

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

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

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

impl From<Error> for ConnectionClose

Source§

fn from(x: TransportError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for ConnectionError

Source§

fn from(source: TransportError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for TransportError

Source§

fn from(e: Error) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for Error

Source§

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

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

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

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

impl Eq for Error

Auto Trait Implementations§

§

impl Freeze for Error

§

impl !RefUnwindSafe for Error

§

impl Send for Error

§

impl Sync for Error

§

impl Unpin for Error

§

impl !UnwindSafe for Error

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/338/docs/src/iroh_quinn_proto/connection/mod.rs.html b/pr/338/docs/src/iroh_quinn_proto/connection/mod.rs.html index fa4768138..b504b140a 100644 --- a/pr/338/docs/src/iroh_quinn_proto/connection/mod.rs.html +++ b/pr/338/docs/src/iroh_quinn_proto/connection/mod.rs.html @@ -333,8 +333,8 @@ 333 /// If true there was data to send but congestion control did not allow so. 334 congestion_blocked: bool, 335 }, -336 /// One or more packets have been written into the [`TransmitBuf`] and should be sent. -337 Send, +336 /// The transmit is ready to be sent. +337 Send(Transmit), 338} 339 340/// Return value for [`Connection::poll_transmit_path_space`]. @@ -895,6131 +895,6140 @@ 895 // matheus23: Perhaps looking at !self.abandoned_paths.contains(path_id) is enough, given keep-alives? 896 } 897 -898 fn ensure_path( -899 &mut self, -900 path_id: PathId, -901 network_path: FourTuple, -902 now: Instant, -903 pn: Option<u64>, -904 ) -> &mut PathData { -905 let valid_path = self.find_validated_path_on_network_path(network_path); -906 let validated = valid_path.is_some(); -907 let initial_rtt = valid_path.map(|(_, path)| path.data.rtt.conservative()); -908 let vacant_entry = match self.paths.entry(path_id) { -909 btree_map::Entry::Vacant(vacant_entry) => vacant_entry, -910 btree_map::Entry::Occupied(occupied_entry) => { -911 return &mut occupied_entry.into_mut().data; -912 } -913 }; -914 -915 debug!(%validated, %path_id, %network_path, "path added"); -916 let peer_max_udp_payload_size = -917 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()).unwrap_or(u16::MAX); -918 self.path_generation_counter = self.path_generation_counter.wrapping_add(1); -919 let mut data = PathData::new( -920 network_path, -921 self.allow_mtud, -922 Some(peer_max_udp_payload_size), -923 self.path_generation_counter, -924 now, -925 &self.config, -926 ); -927 -928 data.validated = validated; -929 if let Some(initial_rtt) = initial_rtt { -930 data.rtt.reset_initial_rtt(initial_rtt); -931 } -932 -933 let pto = self.ack_frequency.max_ack_delay_for_pto() + data.rtt.pto_base(); -934 self.timers.set( -935 Timer::PerPath(path_id, PathTimer::PathOpen), -936 now + 3 * pto, -937 self.qlog.with_time(now), -938 ); -939 -940 // for the path to be opened we need to send a packet on the path. Sending a challenge -941 // guarantees this -942 data.send_new_challenge = true; -943 -944 let path = vacant_entry.insert(PathState { data, prev: None }); -945 -946 let mut pn_space = spaces::PacketNumberSpace::new(now, SpaceId::Data, &mut self.rng); -947 if let Some(pn) = pn { -948 pn_space.dedup.insert(pn); -949 } -950 self.spaces[SpaceId::Data] -951 .number_spaces -952 .insert(path_id, pn_space); -953 self.qlog.emit_tuple_assigned(path_id, network_path, now); -954 &mut path.data -955 } -956 -957 /// Returns packets to transmit -958 /// -959 /// Connections should be polled for transmit after: -960 /// - the application performed some I/O on the connection -961 /// - a call was made to `handle_event` -962 /// - a call was made to `handle_timeout` -963 /// -964 /// `max_datagrams` specifies how many datagrams can be returned inside a -965 /// single Transmit using GSO. This must be at least 1. -966 #[must_use] -967 pub fn poll_transmit( -968 &mut self, -969 now: Instant, -970 max_datagrams: NonZeroUsize, -971 buf: &mut Vec<u8>, -972 ) -> Option<Transmit> { -973 if let Some(probing) = self -974 .iroh_hp -975 .server_side_mut() -976 .ok() -977 .and_then(iroh_hp::ServerState::next_probe) -978 { -979 let destination = probing.remote(); -980 trace!(%destination, "RAND_DATA packet"); -981 let token: u64 = self.rng.random(); -982 buf.put_u64(token); -983 probing.finish(token); -984 return Some(Transmit { -985 destination, -986 ecn: None, -987 size: 8, -988 segment_size: None, -989 src_ip: None, -990 }); -991 } -992 -993 let max_datagrams = match self.config.enable_segmentation_offload { -994 false => NonZeroUsize::MIN, -995 true => max_datagrams, -996 }; -997 -998 // Each call to poll_transmit can only send datagrams to one destination, because -999 // all datagrams in a GSO batch are for the same destination. Therefore only -1000 // datagrams for one destination address are produced for each poll_transmit call. -1001 -1002 // Check whether we need to send a close message -1003 let close = match self.state.as_type() { -1004 StateType::Drained => { -1005 self.app_limited = true; -1006 return None; -1007 } -1008 StateType::Draining | StateType::Closed => { -1009 // self.close is only reset once the associated packet had been -1010 // encoded successfully -1011 if !self.connection_close_pending { -1012 self.app_limited = true; -1013 return None; -1014 } -1015 true -1016 } -1017 _ => false, -1018 }; -1019 -1020 // Schedule an ACK_FREQUENCY frame if a new one needs to be sent. -1021 if let Some(config) = &self.config.ack_frequency_config { -1022 let rtt = self -1023 .paths -1024 .values() -1025 .map(|p| p.data.rtt.get()) -1026 .min() -1027 .expect("one path exists"); -1028 self.spaces[SpaceId::Data].pending.ack_frequency = self -1029 .ack_frequency -1030 .should_send_ack_frequency(rtt, config, &self.peer_params) -1031 && self.highest_space == SpaceId::Data -1032 && self.peer_supports_ack_frequency(); -1033 } -1034 -1035 // TODO(flub): path scheduling logic might be buggy if there are only un-validated -1036 // paths and PATH_STATUS_BACKUP paths. -1037 -1038 // Path scheduling logic is currently as such: -1039 // -1040 // - For any un-validated paths we only send frames that *must* be sent on that -1041 // path. E.g. PATH_CHALLENGE, PATH_RESPONSE. -1042 // -1043 // - If there are any validated paths with CIDs and PathStatus::Available: -1044 // - Frames that can be sent on any path, e.g. STREAM, DATAGRAM, are only sent on -1045 // these available paths. -1046 // - All other paths only send frames that *must* be sent on those paths, -1047 // e.g. PATH_CHALLENGE, PATH_RESPONSE, tail-loss probes, keep alive PING. -1048 // -1049 // - If there are no validated paths with CIDs and PathStatus::Available all frames -1050 // are sent on the earlierst possible path. -1051 // -1052 // For all this we use the *path_exclusive_only* boolean: If set to true, only -1053 // frames that must be sent on the path will be built into the packet. +898 /// Creates the [`PathData`] for a new [`PathId`]. +899 /// +900 /// Called for incoming packets as well as when opening a new path locally. +901 fn ensure_path( +902 &mut self, +903 path_id: PathId, +904 network_path: FourTuple, +905 now: Instant, +906 pn: Option<u64>, +907 ) -> &mut PathData { +908 let valid_path = self.find_validated_path_on_network_path(network_path); +909 let validated = valid_path.is_some(); +910 let initial_rtt = valid_path.map(|(_, path)| path.data.rtt.conservative()); +911 let vacant_entry = match self.paths.entry(path_id) { +912 btree_map::Entry::Vacant(vacant_entry) => vacant_entry, +913 btree_map::Entry::Occupied(occupied_entry) => { +914 return &mut occupied_entry.into_mut().data; +915 } +916 }; +917 +918 debug!(%validated, %path_id, %network_path, "path added"); +919 let peer_max_udp_payload_size = +920 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()).unwrap_or(u16::MAX); +921 self.path_generation_counter = self.path_generation_counter.wrapping_add(1); +922 let mut data = PathData::new( +923 network_path, +924 self.allow_mtud, +925 Some(peer_max_udp_payload_size), +926 self.path_generation_counter, +927 now, +928 &self.config, +929 ); +930 +931 data.validated = validated; +932 if let Some(initial_rtt) = initial_rtt { +933 data.rtt.reset_initial_rtt(initial_rtt); +934 } +935 +936 let pto = self.ack_frequency.max_ack_delay_for_pto() + data.rtt.pto_base(); +937 self.timers.set( +938 Timer::PerPath(path_id, PathTimer::PathOpen), +939 now + 3 * pto, +940 self.qlog.with_time(now), +941 ); +942 +943 // To open a path locally we need to send a packet on the path. Sending a challenge +944 // guarantees this. +945 data.send_new_challenge = true; +946 +947 let path = vacant_entry.insert(PathState { data, prev: None }); +948 +949 let mut pn_space = spaces::PacketNumberSpace::new(now, SpaceId::Data, &mut self.rng); +950 if let Some(pn) = pn { +951 pn_space.dedup.insert(pn); +952 } +953 self.spaces[SpaceId::Data] +954 .number_spaces +955 .insert(path_id, pn_space); +956 self.qlog.emit_tuple_assigned(path_id, network_path, now); +957 +958 // If the remote opened this path we may not have CIDs for it. For locally opened +959 // paths the caller should have already made sure we have CIDs and refused to open +960 // it if there were none. +961 if !self.remote_cids.contains_key(&path_id) { +962 debug!("Remote opened path without issuing CIDs"); +963 self.spaces[SpaceId::Data] +964 .pending +965 .path_cids_blocked +966 .insert(path_id); +967 // Do not abandon this path right away. CIDs might be in-flight still and arrive +968 // soon. It is up to the remote to handle this situation. +969 } +970 +971 &mut path.data +972 } +973 +974 /// Returns packets to transmit +975 /// +976 /// Connections should be polled for transmit after: +977 /// - the application performed some I/O on the connection +978 /// - a call was made to `handle_event` +979 /// - a call was made to `handle_timeout` +980 /// +981 /// `max_datagrams` specifies how many datagrams can be returned inside a +982 /// single Transmit using GSO. This must be at least 1. +983 #[must_use] +984 pub fn poll_transmit( +985 &mut self, +986 now: Instant, +987 max_datagrams: NonZeroUsize, +988 buf: &mut Vec<u8>, +989 ) -> Option<Transmit> { +990 if let Some(probing) = self +991 .iroh_hp +992 .server_side_mut() +993 .ok() +994 .and_then(iroh_hp::ServerState::next_probe) +995 { +996 let destination = probing.remote(); +997 trace!(%destination, "RAND_DATA packet"); +998 let token: u64 = self.rng.random(); +999 buf.put_u64(token); +1000 probing.finish(token); +1001 return Some(Transmit { +1002 destination, +1003 ecn: None, +1004 size: 8, +1005 segment_size: None, +1006 src_ip: None, +1007 }); +1008 } +1009 +1010 let max_datagrams = match self.config.enable_segmentation_offload { +1011 false => NonZeroUsize::MIN, +1012 true => max_datagrams, +1013 }; +1014 +1015 // Each call to poll_transmit can only send datagrams to one destination, because +1016 // all datagrams in a GSO batch are for the same destination. Therefore only +1017 // datagrams for one destination address are produced for each poll_transmit call. +1018 +1019 // Check whether we need to send a close message +1020 let close = match self.state.as_type() { +1021 StateType::Drained => { +1022 self.app_limited = true; +1023 return None; +1024 } +1025 StateType::Draining | StateType::Closed => { +1026 // self.close is only reset once the associated packet had been +1027 // encoded successfully +1028 if !self.connection_close_pending { +1029 self.app_limited = true; +1030 return None; +1031 } +1032 true +1033 } +1034 _ => false, +1035 }; +1036 +1037 // Schedule an ACK_FREQUENCY frame if a new one needs to be sent. +1038 if let Some(config) = &self.config.ack_frequency_config { +1039 let rtt = self +1040 .paths +1041 .values() +1042 .map(|p| p.data.rtt.get()) +1043 .min() +1044 .expect("one path exists"); +1045 self.spaces[SpaceId::Data].pending.ack_frequency = self +1046 .ack_frequency +1047 .should_send_ack_frequency(rtt, config, &self.peer_params) +1048 && self.highest_space == SpaceId::Data +1049 && self.peer_supports_ack_frequency(); +1050 } +1051 +1052 // TODO(flub): path scheduling logic might be buggy if there are only un-validated +1053 // paths and PATH_STATUS_BACKUP paths. 1054 -1055 // Is there any open, validated and status available path with dst CIDs? If so we'll -1056 // want to set path_exclusive_only for any other paths. -1057 let have_available_path = self.paths.iter().any(|(id, path)| { -1058 path.data.validated -1059 && path.data.local_status() == PathStatus::Available -1060 && self.remote_cids.contains_key(id) -1061 }); -1062 -1063 // TODO: how to avoid the allocation? Cannot use a for loop because of -1064 // borrowing. Maybe SmallVec or similar. -1065 let path_ids: Vec<_> = self.paths.keys().copied().collect(); -1066 -1067 // If we end up not sending anything, we need to know if that was because there was -1068 // nothing to send or because we were congestion blocked. -1069 let mut congestion_blocked = false; -1070 -1071 for &path_id in &path_ids { -1072 if let Some(transmit) = self.poll_transmit_off_path(now, buf, path_id) { -1073 return Some(transmit); -1074 } -1075 -1076 // Set the segment size to this path's MTU for on-path data. -1077 let pmtu = self.path_data(path_id).current_mtu().into(); -1078 let mut transmit = TransmitBuf::new(buf, max_datagrams, pmtu); +1055 // Path scheduling logic is currently as such: +1056 // +1057 // - For any un-validated paths we only send frames that *must* be sent on that +1058 // path. E.g. PATH_CHALLENGE, PATH_RESPONSE. +1059 // +1060 // - If there are any validated paths with CIDs and PathStatus::Available: +1061 // - Frames that can be sent on any path, e.g. STREAM, DATAGRAM, are only sent on +1062 // these available paths. +1063 // - All other paths only send frames that *must* be sent on those paths, +1064 // e.g. PATH_CHALLENGE, PATH_RESPONSE, tail-loss probes, keep alive PING. +1065 // +1066 // - If there are no validated paths with CIDs and PathStatus::Available all frames +1067 // are sent on the earlierst possible path. +1068 // +1069 // For all this we use the *path_exclusive_only* boolean: If set to true, only +1070 // frames that must be sent on the path will be built into the packet. +1071 +1072 // Is there any open, validated and status available path with dst CIDs? If so we'll +1073 // want to set path_exclusive_only for any other paths. +1074 let have_available_path = self.paths.iter().any(|(id, path)| { +1075 path.data.validated +1076 && path.data.local_status() == PathStatus::Available +1077 && self.remote_cids.contains_key(id) +1078 }); 1079 -1080 // Poll for on-path transmits. -1081 match self.poll_transmit_on_path( -1082 now, -1083 &mut transmit, -1084 path_id, -1085 have_available_path, -1086 close, -1087 ) { -1088 PollPathStatus::Send => { -1089 let transmit = self.build_transmit(path_id, transmit); -1090 return Some(transmit); -1091 } -1092 PollPathStatus::NothingToSend { -1093 congestion_blocked: cb, -1094 } => { -1095 congestion_blocked |= cb; -1096 // Continue checking other paths, tail-loss probes may need to be sent -1097 // in all spaces. -1098 debug_assert!( -1099 transmit.is_empty(), -1100 "nothing to send on path but transmit not empty" -1101 ); -1102 } -1103 } -1104 } -1105 -1106 // We didn't produce any application data packet -1107 debug_assert!( -1108 buf.is_empty(), -1109 "there was data in the buffer, but it was not sent" -1110 ); -1111 -1112 self.app_limited = !congestion_blocked; -1113 -1114 if self.state.is_established() { -1115 // Try MTU probing now -1116 for path_id in path_ids { -1117 // The MTU parameter here is really a dummy one, poll_transmit_mut_probe -1118 // explicitly sets the size. -1119 let mut transmit = TransmitBuf::new(buf, max_datagrams, MIN_INITIAL_SIZE.into()); -1120 self.poll_transmit_mtu_probe(now, &mut transmit, path_id); -1121 if !transmit.is_empty() { -1122 let transmit = self.build_transmit(path_id, transmit); -1123 return Some(transmit); -1124 } -1125 } -1126 } -1127 -1128 None -1129 } -1130 -1131 fn build_transmit(&mut self, path_id: PathId, transmit: TransmitBuf<'_>) -> Transmit { -1132 debug_assert!( -1133 !transmit.is_empty(), -1134 "must not be called with an empty transmit buffer" -1135 ); -1136 -1137 let network_path = self.path_data(path_id).network_path; -1138 trace!( -1139 segment_size = transmit.segment_size(), -1140 last_datagram_len = transmit.len() % transmit.segment_size(), -1141 %network_path, -1142 "sending {} bytes in {} datagrams", -1143 transmit.len(), -1144 transmit.num_datagrams() -1145 ); -1146 self.path_data_mut(path_id) -1147 .inc_total_sent(transmit.len() as u64); -1148 -1149 self.stats -1150 .udp_tx -1151 .on_sent(transmit.num_datagrams() as u64, transmit.len()); -1152 self.path_stats -1153 .entry(path_id) -1154 .or_default() -1155 .udp_tx -1156 .on_sent(transmit.num_datagrams() as u64, transmit.len()); -1157 -1158 Transmit { -1159 destination: network_path.remote, -1160 size: transmit.len(), -1161 ecn: if self.path_data(path_id).sending_ecn { -1162 Some(EcnCodepoint::Ect0) -1163 } else { -1164 None -1165 }, -1166 segment_size: match transmit.num_datagrams() { -1167 1 => None, -1168 _ => Some(transmit.segment_size()), -1169 }, -1170 src_ip: network_path.local_ip, -1171 } -1172 } -1173 -1174 /// poll_transmit logic for off-path data. -1175 fn poll_transmit_off_path( -1176 &mut self, -1177 now: Instant, -1178 buf: &mut Vec<u8>, -1179 path_id: PathId, -1180 ) -> Option<Transmit> { -1181 if let Some(challenge) = self.send_prev_path_challenge(now, buf, path_id) { -1182 return Some(challenge); -1183 } -1184 if let Some(response) = self.send_off_path_path_response(now, buf, path_id) { -1185 return Some(response); -1186 } -1187 None -1188 } -1189 -1190 /// poll_transmit logic for on-path data. -1191 /// -1192 /// This is not quite the same as for a multipath packet space, since [`PathId::ZERO`] -1193 /// has 3 packet spaces, which this handles. -1194 /// -1195 /// See [`Self::poll_transmit_off_path`] for off-path data. -1196 #[must_use] -1197 fn poll_transmit_on_path( -1198 &mut self, -1199 now: Instant, -1200 transmit: &mut TransmitBuf<'_>, -1201 path_id: PathId, -1202 have_available_path: bool, -1203 close: bool, -1204 ) -> PollPathStatus { -1205 // Check if there is at least one active CID to use for sending -1206 let Some(remote_cid) = self.remote_cids.get(&path_id).map(CidQueue::active) else { -1207 if self.abandoned_paths.contains(&path_id) { -1208 trace!(%path_id, "remote CIDs retired for abandoned path"); -1209 } else { -1210 debug!(%path_id, "no remote CIDs for path"); -1211 } -1212 return PollPathStatus::NothingToSend { -1213 congestion_blocked: false, -1214 }; -1215 }; -1216 -1217 // Whether the last packet in the datagram must be padded so the datagram takes up -1218 // an exact size. An earlier space can decide to not fill an entire datagram and -1219 // require the next space to fill it further. But may need a specific size of the -1220 // datagram containing the packet. The final packet built in the datagram must pad -1221 // to this size. -1222 let mut pad_datagram = PadDatagram::No; +1080 // TODO: how to avoid the allocation? Cannot use a for loop because of +1081 // borrowing. Maybe SmallVec or similar. +1082 let path_ids: Vec<_> = self.paths.keys().copied().collect(); +1083 +1084 // If we end up not sending anything, we need to know if that was because there was +1085 // nothing to send or because we were congestion blocked. +1086 let mut congestion_blocked = false; +1087 +1088 for &path_id in &path_ids { +1089 if let Some(transmit) = self.poll_transmit_off_path(now, buf, path_id) { +1090 return Some(transmit); +1091 } +1092 +1093 // Poll for on-path transmits. +1094 match self.poll_transmit_on_path( +1095 now, +1096 buf, +1097 path_id, +1098 max_datagrams, +1099 have_available_path, +1100 close, +1101 ) { +1102 PollPathStatus::Send(transmit) => { +1103 return Some(transmit); +1104 } +1105 PollPathStatus::NothingToSend { +1106 congestion_blocked: cb, +1107 } => { +1108 congestion_blocked |= cb; +1109 // Continue checking other paths, tail-loss probes may need to be sent +1110 // in all spaces. +1111 debug_assert!( +1112 buf.is_empty(), +1113 "nothing to send on path but buffer not empty" +1114 ); +1115 } +1116 } +1117 } +1118 +1119 // We didn't produce any application data packet +1120 debug_assert!( +1121 buf.is_empty(), +1122 "there was data in the buffer, but it was not sent" +1123 ); +1124 +1125 self.app_limited = !congestion_blocked; +1126 +1127 if self.state.is_established() { +1128 // Try MTU probing now +1129 for path_id in path_ids { +1130 if let Some(transmit) = self.poll_transmit_mtu_probe(now, buf, path_id) { +1131 return Some(transmit); +1132 } +1133 } +1134 } +1135 +1136 None +1137 } +1138 +1139 fn build_transmit(&mut self, path_id: PathId, transmit: TransmitBuf<'_>) -> Transmit { +1140 debug_assert!( +1141 !transmit.is_empty(), +1142 "must not be called with an empty transmit buffer" +1143 ); +1144 +1145 let network_path = self.path_data(path_id).network_path; +1146 trace!( +1147 segment_size = transmit.segment_size(), +1148 last_datagram_len = transmit.len() % transmit.segment_size(), +1149 %network_path, +1150 "sending {} bytes in {} datagrams", +1151 transmit.len(), +1152 transmit.num_datagrams() +1153 ); +1154 self.path_data_mut(path_id) +1155 .inc_total_sent(transmit.len() as u64); +1156 +1157 self.stats +1158 .udp_tx +1159 .on_sent(transmit.num_datagrams() as u64, transmit.len()); +1160 self.path_stats +1161 .entry(path_id) +1162 .or_default() +1163 .udp_tx +1164 .on_sent(transmit.num_datagrams() as u64, transmit.len()); +1165 +1166 Transmit { +1167 destination: network_path.remote, +1168 size: transmit.len(), +1169 ecn: if self.path_data(path_id).sending_ecn { +1170 Some(EcnCodepoint::Ect0) +1171 } else { +1172 None +1173 }, +1174 segment_size: match transmit.num_datagrams() { +1175 1 => None, +1176 _ => Some(transmit.segment_size()), +1177 }, +1178 src_ip: network_path.local_ip, +1179 } +1180 } +1181 +1182 /// poll_transmit logic for off-path data. +1183 fn poll_transmit_off_path( +1184 &mut self, +1185 now: Instant, +1186 buf: &mut Vec<u8>, +1187 path_id: PathId, +1188 ) -> Option<Transmit> { +1189 if let Some(challenge) = self.send_prev_path_challenge(now, buf, path_id) { +1190 return Some(challenge); +1191 } +1192 if let Some(response) = self.send_off_path_path_response(now, buf, path_id) { +1193 return Some(response); +1194 } +1195 None +1196 } +1197 +1198 /// poll_transmit logic for on-path data. +1199 /// +1200 /// This is not quite the same as for a multipath packet space, since [`PathId::ZERO`] +1201 /// has 3 packet spaces, which this handles. +1202 /// +1203 /// See [`Self::poll_transmit_off_path`] for off-path data. +1204 #[must_use] +1205 fn poll_transmit_on_path( +1206 &mut self, +1207 now: Instant, +1208 buf: &mut Vec<u8>, +1209 path_id: PathId, +1210 max_datagrams: NonZeroUsize, +1211 have_available_path: bool, +1212 close: bool, +1213 ) -> PollPathStatus { +1214 // Check if there is at least one active CID to use for sending +1215 let Some(remote_cid) = self.remote_cids.get(&path_id).map(CidQueue::active) else { +1216 if !self.abandoned_paths.contains(&path_id) { +1217 debug!(%path_id, "no remote CIDs for path"); +1218 } +1219 return PollPathStatus::NothingToSend { +1220 congestion_blocked: false, +1221 }; +1222 }; 1223 -1224 // The packet number of the last built packet. This is kept kept across spaces. -1225 // QUIC is supposed to have a single congestion controller for the Initial, -1226 // Handshake and Data(PathId::ZERO) spaces. -1227 let mut last_packet_number = None; -1228 -1229 // If we end up not sending anything, we need to know if that was because there was -1230 // nothing to send or because we were congestion blocked. -1231 let mut congestion_blocked = false; -1232 -1233 // Iterate over the available spaces. -1234 for space_id in SpaceId::iter() { -1235 // Only PathId::ZERO uses non Data space ids. -1236 if path_id != PathId::ZERO && space_id != SpaceId::Data { -1237 continue; -1238 } -1239 match self.poll_transmit_path_space( -1240 now, -1241 transmit, -1242 path_id, -1243 space_id, -1244 remote_cid, -1245 have_available_path, -1246 close, -1247 pad_datagram, -1248 ) { -1249 PollPathSpaceStatus::NothingToSend { -1250 congestion_blocked: cb, -1251 } => { -1252 congestion_blocked |= cb; -1253 // Continue checking other spaces, tail-loss probes may need to be sent -1254 // in all spaces. -1255 } -1256 PollPathSpaceStatus::WrotePacket { -1257 last_packet_number: pn, -1258 pad_datagram: pad, -1259 } => { -1260 debug_assert!(!transmit.is_empty(), "transmit must contain packets"); -1261 last_packet_number = Some(pn); -1262 pad_datagram = pad; -1263 // Always check higher spaces. If the transmit is full or they have -1264 // nothing to send they will not write packets. But if they can, they -1265 // must always be allowed to add to this transmit because coalescing may -1266 // be required. -1267 continue; -1268 } -1269 PollPathSpaceStatus::Send { -1270 last_packet_number: pn, -1271 } => { -1272 debug_assert!(!transmit.is_empty(), "transmit must contain packets"); -1273 last_packet_number = Some(pn); -1274 break; -1275 } -1276 } -1277 } -1278 -1279 if last_packet_number.is_some() || congestion_blocked { -1280 self.qlog.emit_recovery_metrics( -1281 path_id, -1282 &mut self.paths.get_mut(&path_id).unwrap().data, -1283 now, -1284 ); -1285 } -1286 -1287 match last_packet_number { -1288 Some(last_packet_number) => { -1289 // Note that when sending in multiple spaces the last packet number will be -1290 // the one from the highest space. -1291 self.path_data_mut(path_id).congestion.on_sent( -1292 now, -1293 transmit.len() as u64, -1294 last_packet_number, -1295 ); -1296 -1297 PollPathStatus::Send -1298 } -1299 None => PollPathStatus::NothingToSend { congestion_blocked }, -1300 } -1301 } -1302 -1303 /// poll_transmit logic for a QUIC-MULTIPATH packet number space (PathID + SpaceId). -1304 #[must_use] -1305 fn poll_transmit_path_space( -1306 &mut self, -1307 now: Instant, -1308 transmit: &mut TransmitBuf<'_>, -1309 path_id: PathId, -1310 space_id: SpaceId, -1311 remote_cid: ConnectionId, -1312 // If any other packet space has a usable path with PathStatus::Available. -1313 have_available_path: bool, -1314 // If we need to send a CONNECTION_CLOSE frame. -1315 connection_close_pending: bool, -1316 // Whether the current datagram needs to be padded to a certain size. -1317 mut pad_datagram: PadDatagram, -1318 ) -> PollPathSpaceStatus { -1319 // Keep track of the last packet number we wrote. If None we did not write any -1320 // packets. -1321 let mut last_packet_number = None; -1322 -1323 // Each loop of this may build one packet. It works logically as follows: -1324 // -1325 // - Check if something *needs* to be sent in this space and *can* be sent. -1326 // - If not, return to the caller who will call us again for the next space. -1327 // - Start a new datagram. -1328 // - Unless coalescing the packet into an existing datagram. -1329 // - Write the packet header and payload. -1330 // - Check if coalescing a next packet into the datagram is possible. -1331 // - If coalescing, finish packet without padding to leave space in the datagram. -1332 // - If not coalescing, complete the datagram: -1333 // - Finish packet with padding. -1334 // - Set the transmit segment size if this is the first datagram. -1335 // - Loop: next iteration will exit the loop if nothing more to send in this -1336 // space. The TransmitBuf will contain a started datagram with space if -1337 // coalescing, or completely filled datagram if not coalescing. -1338 loop { -1339 // Determine if anything can be sent in this packet number space (SpaceId + PathId). -1340 let max_packet_size = if transmit.datagram_remaining_mut() > 0 { -1341 // A datagram is started already, we are coalescing another packet into it. -1342 transmit.datagram_remaining_mut() -1343 } else { -1344 // A new datagram needs to be started. -1345 transmit.segment_size() -1346 }; -1347 let can_send = -1348 self.space_can_send(space_id, path_id, max_packet_size, connection_close_pending); -1349 -1350 // Whether we would like to send any frames on this packet space. See the packet -1351 // scheduling described in poll_transmit. -1352 let space_should_send = { -1353 let path_exclusive_only = space_id == SpaceId::Data -1354 && have_available_path -1355 && self.path_data(path_id).local_status() == PathStatus::Backup; -1356 let path_should_send = if path_exclusive_only { -1357 can_send.path_exclusive -1358 } else { -1359 !can_send.is_empty() -1360 }; -1361 let needs_loss_probe = self.spaces[space_id].for_path(path_id).loss_probes > 0; -1362 path_should_send || needs_loss_probe -1363 }; -1364 -1365 if !space_should_send { -1366 // Nothing more to send. Previous iterations of this loop may have built -1367 // packets already. -1368 return match last_packet_number { -1369 Some(pn) => PollPathSpaceStatus::WrotePacket { -1370 last_packet_number: pn, -1371 pad_datagram, -1372 }, -1373 None => { -1374 // Only log for spaces which have crypto. -1375 if self.spaces[space_id].crypto.is_some() -1376 || (space_id == SpaceId::Data && self.zero_rtt_crypto.is_some()) -1377 { -1378 trace!(?space_id, %path_id, "nothing to send in space"); -1379 } -1380 return PollPathSpaceStatus::NothingToSend { -1381 congestion_blocked: false, -1382 }; -1383 } -1384 }; -1385 } -1386 -1387 // We want to send on this space, check congestion control if we can. But only -1388 // if we will need to start a new datagram. If we are coalescing into an already -1389 // started datagram we do not need to check congestion control again. -1390 if transmit.datagram_remaining_mut() == 0 { -1391 let congestion_blocked = -1392 self.path_congestion_check(space_id, path_id, transmit, &can_send, now); -1393 if congestion_blocked != PathBlocked::No { -1394 // Previous iterations of this loop may have built packets already. -1395 return match last_packet_number { -1396 Some(pn) => PollPathSpaceStatus::WrotePacket { -1397 last_packet_number: pn, -1398 pad_datagram, -1399 }, -1400 None => { -1401 return PollPathSpaceStatus::NothingToSend { -1402 congestion_blocked: true, -1403 }; -1404 } -1405 }; -1406 } -1407 } -1408 -1409 // If the datagram is full (or there never was one started), we need to start a -1410 // new one. -1411 if transmit.datagram_remaining_mut() == 0 { -1412 if transmit.num_datagrams() >= transmit.max_datagrams().get() { -1413 // No more datagrams allowed. -1414 // Previous iterations of this loop may have built packets already. -1415 return match last_packet_number { -1416 Some(pn) => PollPathSpaceStatus::WrotePacket { -1417 last_packet_number: pn, -1418 pad_datagram, -1419 }, -1420 None => { -1421 return PollPathSpaceStatus::NothingToSend { -1422 congestion_blocked: false, -1423 }; -1424 } -1425 }; -1426 } -1427 -1428 match self.spaces[space_id].for_path(path_id).loss_probes { -1429 0 => transmit.start_new_datagram(), -1430 _ => { -1431 // We need something to send for a tail-loss probe. -1432 let request_immediate_ack = -1433 space_id == SpaceId::Data && self.peer_supports_ack_frequency(); -1434 self.spaces[space_id].maybe_queue_probe( -1435 path_id, -1436 request_immediate_ack, -1437 &self.streams, -1438 ); -1439 -1440 self.spaces[space_id].for_path(path_id).loss_probes -= 1; -1441 -1442 // Clamp the datagram to at most the minimum MTU to ensure that loss -1443 // probes can get through and enable recovery even if the path MTU -1444 // has shrank unexpectedly. -1445 transmit.start_new_datagram_with_size(std::cmp::min( -1446 usize::from(INITIAL_MTU), -1447 transmit.segment_size(), -1448 )); -1449 } -1450 } -1451 trace!(count = transmit.num_datagrams(), "new datagram started"); -1452 -1453 // We started a new datagram, we decide later if it needs padding. -1454 pad_datagram = PadDatagram::No; -1455 } -1456 -1457 // If coalescing another packet into the existing datagram, there should -1458 // still be enough space for a whole packet. -1459 if transmit.datagram_start_offset() < transmit.len() { -1460 debug_assert!(transmit.datagram_remaining_mut() >= MIN_PACKET_SPACE); -1461 } +1224 // Whether the last packet in the datagram must be padded so the datagram takes up +1225 // an exact size. An earlier space can decide to not fill an entire datagram and +1226 // require the next space to fill it further. But may need a specific size of the +1227 // datagram containing the packet. The final packet built in the datagram must pad +1228 // to this size. +1229 let mut pad_datagram = PadDatagram::No; +1230 +1231 // The packet number of the last built packet. This is kept kept across spaces. +1232 // QUIC is supposed to have a single congestion controller for the Initial, +1233 // Handshake and Data(PathId::ZERO) spaces. +1234 let mut last_packet_number = None; +1235 +1236 // If we end up not sending anything, we need to know if that was because there was +1237 // nothing to send or because we were congestion blocked. +1238 let mut congestion_blocked = false; +1239 +1240 // Set the segment size to this path's MTU for on-path data. +1241 let pmtu = self.path_data(path_id).current_mtu().into(); +1242 let mut transmit = TransmitBuf::new(buf, max_datagrams, pmtu); +1243 +1244 // Iterate over the available spaces. +1245 for space_id in SpaceId::iter() { +1246 // Only PathId::ZERO uses non Data space ids. +1247 if path_id != PathId::ZERO && space_id != SpaceId::Data { +1248 continue; +1249 } +1250 match self.poll_transmit_path_space( +1251 now, +1252 &mut transmit, +1253 path_id, +1254 space_id, +1255 remote_cid, +1256 have_available_path, +1257 close, +1258 pad_datagram, +1259 ) { +1260 PollPathSpaceStatus::NothingToSend { +1261 congestion_blocked: cb, +1262 } => { +1263 congestion_blocked |= cb; +1264 // Continue checking other spaces, tail-loss probes may need to be sent +1265 // in all spaces. +1266 } +1267 PollPathSpaceStatus::WrotePacket { +1268 last_packet_number: pn, +1269 pad_datagram: pad, +1270 } => { +1271 debug_assert!(!transmit.is_empty(), "transmit must contain packets"); +1272 last_packet_number = Some(pn); +1273 pad_datagram = pad; +1274 // Always check higher spaces. If the transmit is full or they have +1275 // nothing to send they will not write packets. But if they can, they +1276 // must always be allowed to add to this transmit because coalescing may +1277 // be required. +1278 continue; +1279 } +1280 PollPathSpaceStatus::Send { +1281 last_packet_number: pn, +1282 } => { +1283 debug_assert!(!transmit.is_empty(), "transmit must contain packets"); +1284 last_packet_number = Some(pn); +1285 break; +1286 } +1287 } +1288 } +1289 +1290 if last_packet_number.is_some() || congestion_blocked { +1291 self.qlog.emit_recovery_metrics( +1292 path_id, +1293 &mut self.paths.get_mut(&path_id).unwrap().data, +1294 now, +1295 ); +1296 } +1297 +1298 match last_packet_number { +1299 Some(last_packet_number) => { +1300 // Note that when sending in multiple spaces the last packet number will be +1301 // the one from the highest space. +1302 self.path_data_mut(path_id).congestion.on_sent( +1303 now, +1304 transmit.len() as u64, +1305 last_packet_number, +1306 ); +1307 PollPathStatus::Send(self.build_transmit(path_id, transmit)) +1308 } +1309 None => PollPathStatus::NothingToSend { congestion_blocked }, +1310 } +1311 } +1312 +1313 /// poll_transmit logic for a QUIC-MULTIPATH packet number space (PathID + SpaceId). +1314 #[must_use] +1315 fn poll_transmit_path_space( +1316 &mut self, +1317 now: Instant, +1318 transmit: &mut TransmitBuf<'_>, +1319 path_id: PathId, +1320 space_id: SpaceId, +1321 remote_cid: ConnectionId, +1322 // If any other packet space has a usable path with PathStatus::Available. +1323 have_available_path: bool, +1324 // If we need to send a CONNECTION_CLOSE frame. +1325 connection_close_pending: bool, +1326 // Whether the current datagram needs to be padded to a certain size. +1327 mut pad_datagram: PadDatagram, +1328 ) -> PollPathSpaceStatus { +1329 // Keep track of the last packet number we wrote. If None we did not write any +1330 // packets. +1331 let mut last_packet_number = None; +1332 +1333 // Each loop of this may build one packet. It works logically as follows: +1334 // +1335 // - Check if something *needs* to be sent in this space and *can* be sent. +1336 // - If not, return to the caller who will call us again for the next space. +1337 // - Start a new datagram. +1338 // - Unless coalescing the packet into an existing datagram. +1339 // - Write the packet header and payload. +1340 // - Check if coalescing a next packet into the datagram is possible. +1341 // - If coalescing, finish packet without padding to leave space in the datagram. +1342 // - If not coalescing, complete the datagram: +1343 // - Finish packet with padding. +1344 // - Set the transmit segment size if this is the first datagram. +1345 // - Loop: next iteration will exit the loop if nothing more to send in this +1346 // space. The TransmitBuf will contain a started datagram with space if +1347 // coalescing, or completely filled datagram if not coalescing. +1348 loop { +1349 // Determine if anything can be sent in this packet number space (SpaceId + PathId). +1350 let max_packet_size = if transmit.datagram_remaining_mut() > 0 { +1351 // A datagram is started already, we are coalescing another packet into it. +1352 transmit.datagram_remaining_mut() +1353 } else { +1354 // A new datagram needs to be started. +1355 transmit.segment_size() +1356 }; +1357 let can_send = +1358 self.space_can_send(space_id, path_id, max_packet_size, connection_close_pending); +1359 +1360 // Whether we would like to send any frames on this packet space. See the packet +1361 // scheduling described in poll_transmit. +1362 let space_should_send = { +1363 let path_exclusive_only = space_id == SpaceId::Data +1364 && have_available_path +1365 && self.path_data(path_id).local_status() == PathStatus::Backup; +1366 let path_should_send = if path_exclusive_only { +1367 can_send.path_exclusive +1368 } else { +1369 !can_send.is_empty() +1370 }; +1371 let needs_loss_probe = self.spaces[space_id].for_path(path_id).loss_probes > 0; +1372 path_should_send || needs_loss_probe +1373 }; +1374 +1375 if !space_should_send { +1376 // Nothing more to send. Previous iterations of this loop may have built +1377 // packets already. +1378 return match last_packet_number { +1379 Some(pn) => PollPathSpaceStatus::WrotePacket { +1380 last_packet_number: pn, +1381 pad_datagram, +1382 }, +1383 None => { +1384 // Only log for spaces which have crypto. +1385 if self.spaces[space_id].crypto.is_some() +1386 || (space_id == SpaceId::Data && self.zero_rtt_crypto.is_some()) +1387 { +1388 trace!(?space_id, %path_id, "nothing to send in space"); +1389 } +1390 return PollPathSpaceStatus::NothingToSend { +1391 congestion_blocked: false, +1392 }; +1393 } +1394 }; +1395 } +1396 +1397 // We want to send on this space, check congestion control if we can. But only +1398 // if we will need to start a new datagram. If we are coalescing into an already +1399 // started datagram we do not need to check congestion control again. +1400 if transmit.datagram_remaining_mut() == 0 { +1401 let congestion_blocked = +1402 self.path_congestion_check(space_id, path_id, transmit, &can_send, now); +1403 if congestion_blocked != PathBlocked::No { +1404 // Previous iterations of this loop may have built packets already. +1405 return match last_packet_number { +1406 Some(pn) => PollPathSpaceStatus::WrotePacket { +1407 last_packet_number: pn, +1408 pad_datagram, +1409 }, +1410 None => { +1411 return PollPathSpaceStatus::NothingToSend { +1412 congestion_blocked: true, +1413 }; +1414 } +1415 }; +1416 } +1417 } +1418 +1419 // If the datagram is full (or there never was one started), we need to start a +1420 // new one. +1421 if transmit.datagram_remaining_mut() == 0 { +1422 if transmit.num_datagrams() >= transmit.max_datagrams().get() { +1423 // No more datagrams allowed. +1424 // Previous iterations of this loop may have built packets already. +1425 return match last_packet_number { +1426 Some(pn) => PollPathSpaceStatus::WrotePacket { +1427 last_packet_number: pn, +1428 pad_datagram, +1429 }, +1430 None => { +1431 return PollPathSpaceStatus::NothingToSend { +1432 congestion_blocked: false, +1433 }; +1434 } +1435 }; +1436 } +1437 +1438 match self.spaces[space_id].for_path(path_id).loss_probes { +1439 0 => transmit.start_new_datagram(), +1440 _ => { +1441 // We need something to send for a tail-loss probe. +1442 let request_immediate_ack = +1443 space_id == SpaceId::Data && self.peer_supports_ack_frequency(); +1444 self.spaces[space_id].maybe_queue_probe( +1445 path_id, +1446 request_immediate_ack, +1447 &self.streams, +1448 ); +1449 +1450 self.spaces[space_id].for_path(path_id).loss_probes -= 1; +1451 +1452 // Clamp the datagram to at most the minimum MTU to ensure that loss +1453 // probes can get through and enable recovery even if the path MTU +1454 // has shrank unexpectedly. +1455 transmit.start_new_datagram_with_size(std::cmp::min( +1456 usize::from(INITIAL_MTU), +1457 transmit.segment_size(), +1458 )); +1459 } +1460 } +1461 trace!(count = transmit.num_datagrams(), "new datagram started"); 1462 -1463 // -1464 // From here on, we've determined that a packet will definitely be sent. -1465 // +1463 // We started a new datagram, we decide later if it needs padding. +1464 pad_datagram = PadDatagram::No; +1465 } 1466 -1467 if self.spaces[SpaceId::Initial].crypto.is_some() -1468 && space_id == SpaceId::Handshake -1469 && self.side.is_client() -1470 { -1471 // A client stops both sending and processing Initial packets when it -1472 // sends its first Handshake packet. -1473 self.discard_space(now, SpaceId::Initial); -1474 } -1475 if let Some(ref mut prev) = self.prev_crypto { -1476 prev.update_unacked = false; -1477 } -1478 -1479 let Some(mut builder) = PacketBuilder::new( -1480 now, -1481 space_id, -1482 path_id, -1483 remote_cid, -1484 transmit, -1485 can_send.other, -1486 self, -1487 ) else { -1488 // Confidentiality limit is exceeded and the connection has been killed. We -1489 // should not send any other packets. This works in a roundabout way: We -1490 // have started a datagram but not written anything into it. So even if we -1491 // get called again for another space we will see an already started -1492 // datagram and try and start another packet here. Then be stopped by the -1493 // same confidentiality limit. -1494 return PollPathSpaceStatus::NothingToSend { -1495 congestion_blocked: false, -1496 }; -1497 }; -1498 last_packet_number = Some(builder.packet_number); -1499 -1500 if space_id == SpaceId::Initial && (self.side.is_client() || can_send.other) { -1501 // https://www.rfc-editor.org/rfc/rfc9000.html#section-14.1 -1502 pad_datagram |= PadDatagram::ToMinMtu; -1503 } -1504 if space_id == SpaceId::Data && self.config.pad_to_mtu { -1505 pad_datagram |= PadDatagram::ToSegmentSize; -1506 } -1507 -1508 if can_send.close { -1509 trace!("sending CONNECTION_CLOSE"); -1510 // Encode ACKs before the ConnectionClose message, to give the receiver -1511 // a better approximate on what data has been processed. This is -1512 // especially important with ack delay, since the peer might not -1513 // have gotten any other ACK for the data earlier on. -1514 let is_multipath_negotiated = self.is_multipath_negotiated(); -1515 for path_id in self.spaces[space_id] -1516 .number_spaces -1517 .iter() -1518 .filter(|(_, pns)| !pns.pending_acks.ranges().is_empty()) -1519 .map(|(&path_id, _)| path_id) -1520 .collect::<Vec<_>>() -1521 { -1522 Self::populate_acks( -1523 now, -1524 self.receiving_ecn, -1525 path_id, -1526 space_id, -1527 &mut self.spaces[space_id], -1528 is_multipath_negotiated, -1529 &mut builder, -1530 &mut self.stats.frame_tx, -1531 ); -1532 } -1533 -1534 // Since there only 64 ACK frames there will always be enough space -1535 // to encode the ConnectionClose frame too. However we still have the -1536 // check here to prevent crashes if something changes. -1537 // TODO(flub): This needs fixing for multipath. -1538 debug_assert!( -1539 builder.frame_space_remaining() > frame::ConnectionClose::SIZE_BOUND, -1540 "ACKs should leave space for ConnectionClose" -1541 ); -1542 let stats = &mut self.stats.frame_tx; -1543 if frame::ConnectionClose::SIZE_BOUND < builder.frame_space_remaining() { -1544 let max_frame_size = builder.frame_space_remaining(); -1545 let close: Close = match self.state.as_type() { -1546 StateType::Closed => { -1547 let reason: Close = -1548 self.state.as_closed().expect("checked").clone().into(); -1549 if space_id == SpaceId::Data || reason.is_transport_layer() { -1550 reason -1551 } else { -1552 TransportError::APPLICATION_ERROR("").into() -1553 } -1554 } -1555 StateType::Draining => TransportError::NO_ERROR("").into(), -1556 _ => unreachable!( -1557 "tried to make a close packet when the connection wasn't closed" -1558 ), -1559 }; -1560 builder.write_frame(close.encoder(max_frame_size), stats); -1561 } -1562 let last_pn = builder.packet_number; -1563 builder.finish_and_track(now, self, path_id, pad_datagram); -1564 if space_id == self.highest_space { -1565 // Don't send another close packet. Even with multipath we only send -1566 // CONNECTION_CLOSE on a single path since we expect our paths to work. -1567 self.connection_close_pending = false; -1568 } -1569 // Send a close frame in every possible space for robustness, per -1570 // RFC9000 "Immediate Close during the Handshake". Don't bother trying -1571 // to send anything else. -1572 // TODO(flub): This breaks during the handshake if we can not coalesce -1573 // packets due to space reasons: the next space would either fail a -1574 // debug_assert checking for enough packet space or produce an invalid -1575 // packet. We need to keep track of per-space pending CONNECTION_CLOSE to -1576 // be able to send these across multiple calls to poll_transmit. Then -1577 // check for coalescing space here because initial packets need to be in -1578 // padded datagrams. And also add space checks for CONNECTION_CLOSE in -1579 // space_can_send so it would stop a GSO batch if the datagram is too -1580 // small for another CONNECTION_CLOSE packet. -1581 return PollPathSpaceStatus::WrotePacket { -1582 last_packet_number: last_pn, -1583 pad_datagram, -1584 }; -1585 } -1586 -1587 // If this boolean is true we only want to send frames which can not be sent on -1588 // any other path. See the path scheduling notes in Self::poll_transmit. -1589 let path_exclusive_only = -1590 have_available_path && self.path_data(path_id).local_status() == PathStatus::Backup; -1591 -1592 self.populate_packet(now, space_id, path_id, path_exclusive_only, &mut builder); -1593 -1594 // ACK-only packets should only be sent when explicitly allowed. If we write them due to -1595 // any other reason, there is a bug which leads to one component announcing write -1596 // readiness while not writing any data. This degrades performance. The condition is -1597 // only checked if the full MTU is available and when potentially large fixed-size -1598 // frames aren't queued, so that lack of space in the datagram isn't the reason for just -1599 // writing ACKs. -1600 debug_assert!( -1601 !(builder.sent_frames().is_ack_only(&self.streams) -1602 && !can_send.acks -1603 && can_send.other -1604 && builder.buf.segment_size() -1605 == self.path_data(path_id).current_mtu() as usize -1606 && self.datagrams.outgoing.is_empty()), -1607 "SendableFrames was {can_send:?}, but only ACKs have been written" -1608 ); -1609 if builder.sent_frames().requires_padding { -1610 pad_datagram |= PadDatagram::ToMinMtu; -1611 } -1612 -1613 for (path_id, _pn) in builder.sent_frames().largest_acked.iter() { -1614 self.spaces[space_id] -1615 .for_path(*path_id) -1616 .pending_acks -1617 .acks_sent(); -1618 self.timers.stop( -1619 Timer::PerPath(*path_id, PathTimer::MaxAckDelay), -1620 self.qlog.with_time(now), -1621 ); -1622 } -1623 -1624 // Now we need to finish the packet. Before we do so we need to know if we will -1625 // be coalescing the next packet into this one, or will be ending the datagram -1626 // as well. Because if this is the last packet in the datagram more padding -1627 // might be needed because of the packet type, or to fill the GSO segment size. -1628 -1629 // Are we allowed to coalesce AND is there enough space for another *packet* in -1630 // this datagram AND will we definitely send another packet? -1631 if builder.can_coalesce && path_id == PathId::ZERO && { -1632 let max_packet_size = builder -1633 .buf -1634 .datagram_remaining_mut() -1635 .saturating_sub(builder.predict_packet_end()); -1636 max_packet_size > MIN_PACKET_SPACE -1637 && self.has_pending_packet(space_id, max_packet_size, connection_close_pending) -1638 } { -1639 // We can append/coalesce the next packet into the current -1640 // datagram. Finish the current packet without adding extra padding. -1641 trace!("will coalesce with next packet"); -1642 builder.finish_and_track(now, self, path_id, PadDatagram::No); -1643 } else { -1644 // We need a new datagram for the next packet. Finish the current -1645 // packet with padding. -1646 // TODO(flub): if there isn't any more data to be sent, this will still pad -1647 // to the segment size and only discover there is nothing to send before -1648 // starting the next packet. That is wasting up to 32 bytes. -1649 if builder.buf.num_datagrams() > 1 && matches!(pad_datagram, PadDatagram::No) { -1650 // If too many padding bytes would be required to continue the -1651 // GSO batch after this packet, end the GSO batch here. Ensures -1652 // that fixed-size frames with heterogeneous sizes -1653 // (e.g. application datagrams) won't inadvertently waste large -1654 // amounts of bandwidth. The exact threshold is a bit arbitrary -1655 // and might benefit from further tuning, though there's no -1656 // universally optimal value. -1657 const MAX_PADDING: usize = 32; -1658 if builder.buf.datagram_remaining_mut() -1659 > builder.predict_packet_end() + MAX_PADDING -1660 { -1661 trace!( -1662 "GSO truncated by demand for {} padding bytes", -1663 builder.buf.datagram_remaining_mut() - builder.predict_packet_end() -1664 ); -1665 let last_pn = builder.packet_number; -1666 builder.finish_and_track(now, self, path_id, PadDatagram::No); -1667 return PollPathSpaceStatus::Send { -1668 last_packet_number: last_pn, -1669 }; -1670 } -1671 -1672 // Pad the current datagram to GSO segment size so it can be -1673 // included in the GSO batch. -1674 builder.finish_and_track(now, self, path_id, PadDatagram::ToSegmentSize); -1675 } else { -1676 builder.finish_and_track(now, self, path_id, pad_datagram); -1677 } -1678 -1679 // If this is the first datagram we set the segment size to the size of the -1680 // first datagram. -1681 if transmit.num_datagrams() == 1 { -1682 transmit.clip_segment_size(); -1683 } -1684 } -1685 } -1686 } -1687 -1688 fn poll_transmit_mtu_probe( -1689 &mut self, -1690 now: Instant, -1691 transmit: &mut TransmitBuf<'_>, -1692 path_id: PathId, -1693 ) { -1694 let Some((active_cid, probe_size)) = self.get_mtu_probe_data(now, path_id) else { -1695 return; -1696 }; +1467 // If coalescing another packet into the existing datagram, there should +1468 // still be enough space for a whole packet. +1469 if transmit.datagram_start_offset() < transmit.len() { +1470 debug_assert!(transmit.datagram_remaining_mut() >= MIN_PACKET_SPACE); +1471 } +1472 +1473 // +1474 // From here on, we've determined that a packet will definitely be sent. +1475 // +1476 +1477 if self.spaces[SpaceId::Initial].crypto.is_some() +1478 && space_id == SpaceId::Handshake +1479 && self.side.is_client() +1480 { +1481 // A client stops both sending and processing Initial packets when it +1482 // sends its first Handshake packet. +1483 self.discard_space(now, SpaceId::Initial); +1484 } +1485 if let Some(ref mut prev) = self.prev_crypto { +1486 prev.update_unacked = false; +1487 } +1488 +1489 let Some(mut builder) = PacketBuilder::new( +1490 now, +1491 space_id, +1492 path_id, +1493 remote_cid, +1494 transmit, +1495 can_send.other, +1496 self, +1497 ) else { +1498 // Confidentiality limit is exceeded and the connection has been killed. We +1499 // should not send any other packets. This works in a roundabout way: We +1500 // have started a datagram but not written anything into it. So even if we +1501 // get called again for another space we will see an already started +1502 // datagram and try and start another packet here. Then be stopped by the +1503 // same confidentiality limit. +1504 return PollPathSpaceStatus::NothingToSend { +1505 congestion_blocked: false, +1506 }; +1507 }; +1508 last_packet_number = Some(builder.packet_number); +1509 +1510 if space_id == SpaceId::Initial && (self.side.is_client() || can_send.other) { +1511 // https://www.rfc-editor.org/rfc/rfc9000.html#section-14.1 +1512 pad_datagram |= PadDatagram::ToMinMtu; +1513 } +1514 if space_id == SpaceId::Data && self.config.pad_to_mtu { +1515 pad_datagram |= PadDatagram::ToSegmentSize; +1516 } +1517 +1518 if can_send.close { +1519 trace!("sending CONNECTION_CLOSE"); +1520 // Encode ACKs before the ConnectionClose message, to give the receiver +1521 // a better approximate on what data has been processed. This is +1522 // especially important with ack delay, since the peer might not +1523 // have gotten any other ACK for the data earlier on. +1524 let is_multipath_negotiated = self.is_multipath_negotiated(); +1525 for path_id in self.spaces[space_id] +1526 .number_spaces +1527 .iter() +1528 .filter(|(_, pns)| !pns.pending_acks.ranges().is_empty()) +1529 .map(|(&path_id, _)| path_id) +1530 .collect::<Vec<_>>() +1531 { +1532 Self::populate_acks( +1533 now, +1534 self.receiving_ecn, +1535 path_id, +1536 space_id, +1537 &mut self.spaces[space_id], +1538 is_multipath_negotiated, +1539 &mut builder, +1540 &mut self.stats.frame_tx, +1541 ); +1542 } +1543 +1544 // Since there only 64 ACK frames there will always be enough space +1545 // to encode the ConnectionClose frame too. However we still have the +1546 // check here to prevent crashes if something changes. +1547 // TODO(flub): This needs fixing for multipath. +1548 debug_assert!( +1549 builder.frame_space_remaining() > frame::ConnectionClose::SIZE_BOUND, +1550 "ACKs should leave space for ConnectionClose" +1551 ); +1552 let stats = &mut self.stats.frame_tx; +1553 if frame::ConnectionClose::SIZE_BOUND < builder.frame_space_remaining() { +1554 let max_frame_size = builder.frame_space_remaining(); +1555 let close: Close = match self.state.as_type() { +1556 StateType::Closed => { +1557 let reason: Close = +1558 self.state.as_closed().expect("checked").clone().into(); +1559 if space_id == SpaceId::Data || reason.is_transport_layer() { +1560 reason +1561 } else { +1562 TransportError::APPLICATION_ERROR("").into() +1563 } +1564 } +1565 StateType::Draining => TransportError::NO_ERROR("").into(), +1566 _ => unreachable!( +1567 "tried to make a close packet when the connection wasn't closed" +1568 ), +1569 }; +1570 builder.write_frame(close.encoder(max_frame_size), stats); +1571 } +1572 let last_pn = builder.packet_number; +1573 builder.finish_and_track(now, self, path_id, pad_datagram); +1574 if space_id == self.highest_space { +1575 // Don't send another close packet. Even with multipath we only send +1576 // CONNECTION_CLOSE on a single path since we expect our paths to work. +1577 self.connection_close_pending = false; +1578 } +1579 // Send a close frame in every possible space for robustness, per +1580 // RFC9000 "Immediate Close during the Handshake". Don't bother trying +1581 // to send anything else. +1582 // TODO(flub): This breaks during the handshake if we can not coalesce +1583 // packets due to space reasons: the next space would either fail a +1584 // debug_assert checking for enough packet space or produce an invalid +1585 // packet. We need to keep track of per-space pending CONNECTION_CLOSE to +1586 // be able to send these across multiple calls to poll_transmit. Then +1587 // check for coalescing space here because initial packets need to be in +1588 // padded datagrams. And also add space checks for CONNECTION_CLOSE in +1589 // space_can_send so it would stop a GSO batch if the datagram is too +1590 // small for another CONNECTION_CLOSE packet. +1591 return PollPathSpaceStatus::WrotePacket { +1592 last_packet_number: last_pn, +1593 pad_datagram, +1594 }; +1595 } +1596 +1597 // If this boolean is true we only want to send frames which can not be sent on +1598 // any other path. See the path scheduling notes in Self::poll_transmit. +1599 let path_exclusive_only = +1600 have_available_path && self.path_data(path_id).local_status() == PathStatus::Backup; +1601 +1602 self.populate_packet(now, space_id, path_id, path_exclusive_only, &mut builder); +1603 +1604 // ACK-only packets should only be sent when explicitly allowed. If we write them due to +1605 // any other reason, there is a bug which leads to one component announcing write +1606 // readiness while not writing any data. This degrades performance. The condition is +1607 // only checked if the full MTU is available and when potentially large fixed-size +1608 // frames aren't queued, so that lack of space in the datagram isn't the reason for just +1609 // writing ACKs. +1610 debug_assert!( +1611 !(builder.sent_frames().is_ack_only(&self.streams) +1612 && !can_send.acks +1613 && can_send.other +1614 && builder.buf.segment_size() +1615 == self.path_data(path_id).current_mtu() as usize +1616 && self.datagrams.outgoing.is_empty()), +1617 "SendableFrames was {can_send:?}, but only ACKs have been written" +1618 ); +1619 if builder.sent_frames().requires_padding { +1620 pad_datagram |= PadDatagram::ToMinMtu; +1621 } +1622 +1623 for (path_id, _pn) in builder.sent_frames().largest_acked.iter() { +1624 self.spaces[space_id] +1625 .for_path(*path_id) +1626 .pending_acks +1627 .acks_sent(); +1628 self.timers.stop( +1629 Timer::PerPath(*path_id, PathTimer::MaxAckDelay), +1630 self.qlog.with_time(now), +1631 ); +1632 } +1633 +1634 // Now we need to finish the packet. Before we do so we need to know if we will +1635 // be coalescing the next packet into this one, or will be ending the datagram +1636 // as well. Because if this is the last packet in the datagram more padding +1637 // might be needed because of the packet type, or to fill the GSO segment size. +1638 +1639 // Are we allowed to coalesce AND is there enough space for another *packet* in +1640 // this datagram AND will we definitely send another packet? +1641 if builder.can_coalesce && path_id == PathId::ZERO && { +1642 let max_packet_size = builder +1643 .buf +1644 .datagram_remaining_mut() +1645 .saturating_sub(builder.predict_packet_end()); +1646 max_packet_size > MIN_PACKET_SPACE +1647 && self.has_pending_packet(space_id, max_packet_size, connection_close_pending) +1648 } { +1649 // We can append/coalesce the next packet into the current +1650 // datagram. Finish the current packet without adding extra padding. +1651 trace!("will coalesce with next packet"); +1652 builder.finish_and_track(now, self, path_id, PadDatagram::No); +1653 } else { +1654 // We need a new datagram for the next packet. Finish the current +1655 // packet with padding. +1656 // TODO(flub): if there isn't any more data to be sent, this will still pad +1657 // to the segment size and only discover there is nothing to send before +1658 // starting the next packet. That is wasting up to 32 bytes. +1659 if builder.buf.num_datagrams() > 1 && matches!(pad_datagram, PadDatagram::No) { +1660 // If too many padding bytes would be required to continue the +1661 // GSO batch after this packet, end the GSO batch here. Ensures +1662 // that fixed-size frames with heterogeneous sizes +1663 // (e.g. application datagrams) won't inadvertently waste large +1664 // amounts of bandwidth. The exact threshold is a bit arbitrary +1665 // and might benefit from further tuning, though there's no +1666 // universally optimal value. +1667 const MAX_PADDING: usize = 32; +1668 if builder.buf.datagram_remaining_mut() +1669 > builder.predict_packet_end() + MAX_PADDING +1670 { +1671 trace!( +1672 "GSO truncated by demand for {} padding bytes", +1673 builder.buf.datagram_remaining_mut() - builder.predict_packet_end() +1674 ); +1675 let last_pn = builder.packet_number; +1676 builder.finish_and_track(now, self, path_id, PadDatagram::No); +1677 return PollPathSpaceStatus::Send { +1678 last_packet_number: last_pn, +1679 }; +1680 } +1681 +1682 // Pad the current datagram to GSO segment size so it can be +1683 // included in the GSO batch. +1684 builder.finish_and_track(now, self, path_id, PadDatagram::ToSegmentSize); +1685 } else { +1686 builder.finish_and_track(now, self, path_id, pad_datagram); +1687 } +1688 +1689 // If this is the first datagram we set the segment size to the size of the +1690 // first datagram. +1691 if transmit.num_datagrams() == 1 { +1692 transmit.clip_segment_size(); +1693 } +1694 } +1695 } +1696 } 1697 -1698 // We are definitely sending a DPLPMTUD probe. -1699 debug_assert_eq!(transmit.num_datagrams(), 0); -1700 transmit.start_new_datagram_with_size(probe_size as usize); -1701 -1702 let Some(mut builder) = PacketBuilder::new( -1703 now, -1704 SpaceId::Data, -1705 path_id, -1706 active_cid, -1707 transmit, -1708 true, -1709 self, -1710 ) else { -1711 return; -1712 }; -1713 -1714 // We implement MTU probes as ping packets padded up to the probe size -1715 trace!(?probe_size, "writing MTUD probe"); -1716 builder.write_frame(frame::Ping, &mut self.stats.frame_tx); -1717 -1718 // If supported by the peer, we want no delays to the probe's ACK -1719 if self.peer_supports_ack_frequency() { -1720 builder.write_frame(frame::ImmediateAck, &mut self.stats.frame_tx); -1721 } -1722 -1723 builder.finish_and_track(now, self, path_id, PadDatagram::ToSize(probe_size)); -1724 -1725 self.path_stats -1726 .entry(path_id) -1727 .or_default() -1728 .sent_plpmtud_probes += 1; -1729 } +1698 fn poll_transmit_mtu_probe( +1699 &mut self, +1700 now: Instant, +1701 buf: &mut Vec<u8>, +1702 path_id: PathId, +1703 ) -> Option<Transmit> { +1704 let (active_cid, probe_size) = self.get_mtu_probe_data(now, path_id)?; +1705 +1706 // We are definitely sending a DPLPMTUD probe. +1707 let mut transmit = TransmitBuf::new(buf, NonZeroUsize::MIN, probe_size as usize); +1708 transmit.start_new_datagram_with_size(probe_size as usize); +1709 +1710 let mut builder = PacketBuilder::new( +1711 now, +1712 SpaceId::Data, +1713 path_id, +1714 active_cid, +1715 &mut transmit, +1716 true, +1717 self, +1718 )?; +1719 +1720 // We implement MTU probes as ping packets padded up to the probe size +1721 trace!(?probe_size, "writing MTUD probe"); +1722 builder.write_frame(frame::Ping, &mut self.stats.frame_tx); +1723 +1724 // If supported by the peer, we want no delays to the probe's ACK +1725 if self.peer_supports_ack_frequency() { +1726 builder.write_frame(frame::ImmediateAck, &mut self.stats.frame_tx); +1727 } +1728 +1729 builder.finish_and_track(now, self, path_id, PadDatagram::ToSize(probe_size)); 1730 -1731 /// Returns the CID and probe size if a DPLPMTUD probe is needed. -1732 /// -1733 /// We MTU probe all paths for which all of the following is true: -1734 /// - We have an active destination CID for the path. -1735 /// - The remote address *and* path are validated. -1736 /// - The path is not abandoned. -1737 /// - The MTU Discovery subsystem wants to probe the path. -1738 fn get_mtu_probe_data(&mut self, now: Instant, path_id: PathId) -> Option<(ConnectionId, u16)> { -1739 let active_cid = self.remote_cids.get(&path_id).map(CidQueue::active)?; -1740 let is_eligible = self.path_data(path_id).validated -1741 && !self.path_data(path_id).is_validating_path() -1742 && !self.abandoned_paths.contains(&path_id); -1743 -1744 if !is_eligible { -1745 return None; -1746 } -1747 let next_pn = self.spaces[SpaceId::Data] -1748 .for_path(path_id) -1749 .peek_tx_number(); -1750 let probe_size = self -1751 .path_data_mut(path_id) -1752 .mtud -1753 .poll_transmit(now, next_pn)?; -1754 -1755 Some((active_cid, probe_size)) -1756 } -1757 -1758 /// Returns true if there is a further packet to send on [`PathId::ZERO`]. -1759 /// -1760 /// In other words this is predicting whether the next call to -1761 /// [`Connection::space_can_send`] issued will return some frames to be sent. Including -1762 /// having to predict which packet number space it will be invoked with. This depends on -1763 /// how both [`Connection::poll_transmit_path`] and -1764 /// [`Connection::poll_transmit_path_space`] behave. -1765 /// -1766 /// This is needed to determine if packet coalescing can happen. Because the last packet -1767 /// in a datagram may need to be padded and thus we must know if another packet will -1768 /// follow or not. -1769 /// -1770 /// The next packet can be either in the same space, or in one of the following spaces -1771 /// on the same path. Because a 0-RTT packet can be coalesced with a 1-RTT packet and -1772 /// both are in the Data(PathId::ZERO) space. Previous spaces are not checked, because -1773 /// packets are built from Initial to Handshake to Data spaces. -1774 fn has_pending_packet( -1775 &mut self, -1776 current_space_id: SpaceId, -1777 max_packet_size: usize, -1778 connection_close_pending: bool, -1779 ) -> bool { -1780 let mut space_id = current_space_id; -1781 loop { -1782 let can_send = self.space_can_send( -1783 space_id, -1784 PathId::ZERO, -1785 max_packet_size, -1786 connection_close_pending, -1787 ); -1788 if !can_send.is_empty() { -1789 return true; -1790 } -1791 match space_id.next() { -1792 Some(next_space_id) => space_id = next_space_id, -1793 None => break, -1794 } -1795 } -1796 false -1797 } -1798 -1799 /// Checks if creating a new datagram would be blocked by congestion control -1800 fn path_congestion_check( -1801 &mut self, -1802 space_id: SpaceId, -1803 path_id: PathId, -1804 transmit: &TransmitBuf<'_>, -1805 can_send: &SendableFrames, -1806 now: Instant, -1807 ) -> PathBlocked { -1808 // Anti-amplification is only based on `total_sent`, which gets updated after -1809 // the transmit is sent. Therefore we pass the amount of bytes for datagrams -1810 // that are already created, as well as 1 byte for starting another datagram. If -1811 // there is any anti-amplification budget left, we always allow a full MTU to be -1812 // sent (see https://github.com/quinn-rs/quinn/issues/1082). -1813 if self.side().is_server() -1814 && self -1815 .path_data(path_id) -1816 .anti_amplification_blocked(transmit.len() as u64 + 1) -1817 { -1818 trace!(?space_id, %path_id, "blocked by anti-amplification"); -1819 return PathBlocked::AntiAmplification; -1820 } -1821 -1822 // Congestion control check. -1823 // Tail loss probes must not be blocked by congestion, or a deadlock could arise. -1824 let bytes_to_send = transmit.segment_size() as u64; -1825 let need_loss_probe = self.spaces[space_id].for_path(path_id).loss_probes > 0; -1826 -1827 if can_send.other && !need_loss_probe && !can_send.close { -1828 let path = self.path_data(path_id); -1829 if path.in_flight.bytes + bytes_to_send >= path.congestion.window() { -1830 trace!(?space_id, %path_id, "blocked by congestion control"); -1831 return PathBlocked::Congestion; -1832 } -1833 } +1731 self.path_stats +1732 .entry(path_id) +1733 .or_default() +1734 .sent_plpmtud_probes += 1; +1735 +1736 Some(self.build_transmit(path_id, transmit)) +1737 } +1738 +1739 /// Returns the CID and probe size if a DPLPMTUD probe is needed. +1740 /// +1741 /// We MTU probe all paths for which all of the following is true: +1742 /// - We have an active destination CID for the path. +1743 /// - The remote address *and* path are validated. +1744 /// - The path is not abandoned. +1745 /// - The MTU Discovery subsystem wants to probe the path. +1746 fn get_mtu_probe_data(&mut self, now: Instant, path_id: PathId) -> Option<(ConnectionId, u16)> { +1747 let active_cid = self.remote_cids.get(&path_id).map(CidQueue::active)?; +1748 let is_eligible = self.path_data(path_id).validated +1749 && !self.path_data(path_id).is_validating_path() +1750 && !self.abandoned_paths.contains(&path_id); +1751 +1752 if !is_eligible { +1753 return None; +1754 } +1755 let next_pn = self.spaces[SpaceId::Data] +1756 .for_path(path_id) +1757 .peek_tx_number(); +1758 let probe_size = self +1759 .path_data_mut(path_id) +1760 .mtud +1761 .poll_transmit(now, next_pn)?; +1762 +1763 Some((active_cid, probe_size)) +1764 } +1765 +1766 /// Returns true if there is a further packet to send on [`PathId::ZERO`]. +1767 /// +1768 /// In other words this is predicting whether the next call to +1769 /// [`Connection::space_can_send`] issued will return some frames to be sent. Including +1770 /// having to predict which packet number space it will be invoked with. This depends on +1771 /// how both [`Connection::poll_transmit_path`] and +1772 /// [`Connection::poll_transmit_path_space`] behave. +1773 /// +1774 /// This is needed to determine if packet coalescing can happen. Because the last packet +1775 /// in a datagram may need to be padded and thus we must know if another packet will +1776 /// follow or not. +1777 /// +1778 /// The next packet can be either in the same space, or in one of the following spaces +1779 /// on the same path. Because a 0-RTT packet can be coalesced with a 1-RTT packet and +1780 /// both are in the Data(PathId::ZERO) space. Previous spaces are not checked, because +1781 /// packets are built from Initial to Handshake to Data spaces. +1782 fn has_pending_packet( +1783 &mut self, +1784 current_space_id: SpaceId, +1785 max_packet_size: usize, +1786 connection_close_pending: bool, +1787 ) -> bool { +1788 let mut space_id = current_space_id; +1789 loop { +1790 let can_send = self.space_can_send( +1791 space_id, +1792 PathId::ZERO, +1793 max_packet_size, +1794 connection_close_pending, +1795 ); +1796 if !can_send.is_empty() { +1797 return true; +1798 } +1799 match space_id.next() { +1800 Some(next_space_id) => space_id = next_space_id, +1801 None => break, +1802 } +1803 } +1804 false +1805 } +1806 +1807 /// Checks if creating a new datagram would be blocked by congestion control +1808 fn path_congestion_check( +1809 &mut self, +1810 space_id: SpaceId, +1811 path_id: PathId, +1812 transmit: &TransmitBuf<'_>, +1813 can_send: &SendableFrames, +1814 now: Instant, +1815 ) -> PathBlocked { +1816 // Anti-amplification is only based on `total_sent`, which gets updated after +1817 // the transmit is sent. Therefore we pass the amount of bytes for datagrams +1818 // that are already created, as well as 1 byte for starting another datagram. If +1819 // there is any anti-amplification budget left, we always allow a full MTU to be +1820 // sent (see https://github.com/quinn-rs/quinn/issues/1082). +1821 if self.side().is_server() +1822 && self +1823 .path_data(path_id) +1824 .anti_amplification_blocked(transmit.len() as u64 + 1) +1825 { +1826 trace!(?space_id, %path_id, "blocked by anti-amplification"); +1827 return PathBlocked::AntiAmplification; +1828 } +1829 +1830 // Congestion control check. +1831 // Tail loss probes must not be blocked by congestion, or a deadlock could arise. +1832 let bytes_to_send = transmit.segment_size() as u64; +1833 let need_loss_probe = self.spaces[space_id].for_path(path_id).loss_probes > 0; 1834 -1835 // Pacing check. -1836 if let Some(delay) = self.path_data_mut(path_id).pacing_delay(bytes_to_send, now) { -1837 self.timers.set( -1838 Timer::PerPath(path_id, PathTimer::Pacing), -1839 delay, -1840 self.qlog.with_time(now), -1841 ); -1842 // Loss probes and CONNECTION_CLOSE should be subject to pacing, even though -1843 // they are not congestion controlled. -1844 trace!(?space_id, %path_id, "blocked by pacing"); -1845 return PathBlocked::Pacing; -1846 } -1847 -1848 PathBlocked::No -1849 } -1850 -1851 /// Send PATH_CHALLENGE for a previous path if necessary -1852 /// -1853 /// QUIC-TRANSPORT section 9.3.3 -1854 /// <https://www.rfc-editor.org/rfc/rfc9000.html#name-off-path-packet-forwarding> -1855 fn send_prev_path_challenge( -1856 &mut self, -1857 now: Instant, -1858 buf: &mut Vec<u8>, -1859 path_id: PathId, -1860 ) -> Option<Transmit> { -1861 let (prev_cid, prev_path) = self.paths.get_mut(&path_id)?.prev.as_mut()?; -1862 // TODO (matheus23): We could use !prev_path.is_validating() here instead to -1863 // (possibly) also re-send challenges when they get lost. -1864 if !prev_path.send_new_challenge { -1865 return None; -1866 }; -1867 prev_path.send_new_challenge = false; -1868 let network_path = prev_path.network_path; -1869 let token = self.rng.random(); -1870 let info = paths::SentChallengeInfo { -1871 sent_instant: now, -1872 network_path, -1873 }; -1874 prev_path.challenges_sent.insert(token, info); -1875 debug_assert_eq!( -1876 self.highest_space, -1877 SpaceId::Data, -1878 "PATH_CHALLENGE queued without 1-RTT keys" -1879 ); -1880 let buf = &mut TransmitBuf::new(buf, NonZeroUsize::MIN, MIN_INITIAL_SIZE.into()); -1881 buf.start_new_datagram(); -1882 -1883 // Use the previous CID to avoid linking the new path with the previous path. We -1884 // don't bother accounting for possible retirement of that prev_cid because this is -1885 // sent once, immediately after migration, when the CID is known to be valid. Even -1886 // if a post-migration packet caused the CID to be retired, it's fair to pretend -1887 // this is sent first. -1888 let mut builder = -1889 PacketBuilder::new(now, SpaceId::Data, path_id, *prev_cid, buf, false, self)?; -1890 let challenge = frame::PathChallenge(token); -1891 let stats = &mut self.stats.frame_tx; -1892 builder.write_frame_with_log_msg(challenge, stats, Some("validating previous path")); -1893 -1894 // An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame -1895 // to at least the smallest allowed maximum datagram size of 1200 bytes, -1896 // unless the anti-amplification limit for the path does not permit -1897 // sending a datagram of this size -1898 builder.pad_to(MIN_INITIAL_SIZE); -1899 -1900 builder.finish(self, now); -1901 self.stats.udp_tx.on_sent(1, buf.len()); -1902 self.path_stats -1903 .entry(path_id) -1904 .or_default() -1905 .udp_tx -1906 .on_sent(1, buf.len()); +1835 if can_send.other && !need_loss_probe && !can_send.close { +1836 let path = self.path_data(path_id); +1837 if path.in_flight.bytes + bytes_to_send >= path.congestion.window() { +1838 trace!(?space_id, %path_id, "blocked by congestion control"); +1839 return PathBlocked::Congestion; +1840 } +1841 } +1842 +1843 // Pacing check. +1844 if let Some(delay) = self.path_data_mut(path_id).pacing_delay(bytes_to_send, now) { +1845 self.timers.set( +1846 Timer::PerPath(path_id, PathTimer::Pacing), +1847 delay, +1848 self.qlog.with_time(now), +1849 ); +1850 // Loss probes and CONNECTION_CLOSE should be subject to pacing, even though +1851 // they are not congestion controlled. +1852 trace!(?space_id, %path_id, "blocked by pacing"); +1853 return PathBlocked::Pacing; +1854 } +1855 +1856 PathBlocked::No +1857 } +1858 +1859 /// Send PATH_CHALLENGE for a previous path if necessary +1860 /// +1861 /// QUIC-TRANSPORT section 9.3.3 +1862 /// <https://www.rfc-editor.org/rfc/rfc9000.html#name-off-path-packet-forwarding> +1863 fn send_prev_path_challenge( +1864 &mut self, +1865 now: Instant, +1866 buf: &mut Vec<u8>, +1867 path_id: PathId, +1868 ) -> Option<Transmit> { +1869 let (prev_cid, prev_path) = self.paths.get_mut(&path_id)?.prev.as_mut()?; +1870 // TODO (matheus23): We could use !prev_path.is_validating() here instead to +1871 // (possibly) also re-send challenges when they get lost. +1872 if !prev_path.send_new_challenge { +1873 return None; +1874 }; +1875 prev_path.send_new_challenge = false; +1876 let network_path = prev_path.network_path; +1877 let token = self.rng.random(); +1878 let info = paths::SentChallengeInfo { +1879 sent_instant: now, +1880 network_path, +1881 }; +1882 prev_path.challenges_sent.insert(token, info); +1883 debug_assert_eq!( +1884 self.highest_space, +1885 SpaceId::Data, +1886 "PATH_CHALLENGE queued without 1-RTT keys" +1887 ); +1888 let buf = &mut TransmitBuf::new(buf, NonZeroUsize::MIN, MIN_INITIAL_SIZE.into()); +1889 buf.start_new_datagram(); +1890 +1891 // Use the previous CID to avoid linking the new path with the previous path. We +1892 // don't bother accounting for possible retirement of that prev_cid because this is +1893 // sent once, immediately after migration, when the CID is known to be valid. Even +1894 // if a post-migration packet caused the CID to be retired, it's fair to pretend +1895 // this is sent first. +1896 let mut builder = +1897 PacketBuilder::new(now, SpaceId::Data, path_id, *prev_cid, buf, false, self)?; +1898 let challenge = frame::PathChallenge(token); +1899 let stats = &mut self.stats.frame_tx; +1900 builder.write_frame_with_log_msg(challenge, stats, Some("validating previous path")); +1901 +1902 // An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame +1903 // to at least the smallest allowed maximum datagram size of 1200 bytes, +1904 // unless the anti-amplification limit for the path does not permit +1905 // sending a datagram of this size +1906 builder.pad_to(MIN_INITIAL_SIZE); 1907 -1908 Some(Transmit { -1909 destination: network_path.remote, -1910 size: buf.len(), -1911 ecn: None, -1912 segment_size: None, -1913 src_ip: network_path.local_ip, -1914 }) -1915 } -1916 -1917 fn send_off_path_path_response( -1918 &mut self, -1919 now: Instant, -1920 buf: &mut Vec<u8>, -1921 path_id: PathId, -1922 ) -> Option<Transmit> { -1923 let path = self.paths.get_mut(&path_id).map(|state| &mut state.data)?; -1924 let cid_queue = self.remote_cids.get_mut(&path_id)?; -1925 let (token, network_path) = path.path_responses.pop_off_path(path.network_path)?; -1926 -1927 let cid = cid_queue -1928 .next_reserved() -1929 .unwrap_or_else(|| cid_queue.active()); -1930 // TODO(@divma): we should take a different approach when there is no fresh CID to use. -1931 // https://github.com/quinn-rs/quinn/issues/2184 -1932 -1933 let frame = frame::PathResponse(token); +1908 builder.finish(self, now); +1909 self.stats.udp_tx.on_sent(1, buf.len()); +1910 self.path_stats +1911 .entry(path_id) +1912 .or_default() +1913 .udp_tx +1914 .on_sent(1, buf.len()); +1915 +1916 Some(Transmit { +1917 destination: network_path.remote, +1918 size: buf.len(), +1919 ecn: None, +1920 segment_size: None, +1921 src_ip: network_path.local_ip, +1922 }) +1923 } +1924 +1925 fn send_off_path_path_response( +1926 &mut self, +1927 now: Instant, +1928 buf: &mut Vec<u8>, +1929 path_id: PathId, +1930 ) -> Option<Transmit> { +1931 let path = self.paths.get_mut(&path_id).map(|state| &mut state.data)?; +1932 let cid_queue = self.remote_cids.get_mut(&path_id)?; +1933 let (token, network_path) = path.path_responses.pop_off_path(path.network_path)?; 1934 -1935 let buf = &mut TransmitBuf::new(buf, NonZeroUsize::MIN, MIN_INITIAL_SIZE.into()); -1936 buf.start_new_datagram(); -1937 -1938 let mut builder = PacketBuilder::new(now, SpaceId::Data, path_id, cid, buf, false, self)?; -1939 let stats = &mut self.stats.frame_tx; -1940 builder.write_frame_with_log_msg(frame, stats, Some("(off-path)")); -1941 builder.finish_and_track(now, self, path_id, PadDatagram::ToMinMtu); +1935 let cid = cid_queue +1936 .next_reserved() +1937 .unwrap_or_else(|| cid_queue.active()); +1938 // TODO(@divma): we should take a different approach when there is no fresh CID to use. +1939 // https://github.com/quinn-rs/quinn/issues/2184 +1940 +1941 let frame = frame::PathResponse(token); 1942 -1943 let size = buf.len(); -1944 -1945 self.stats.udp_tx.on_sent(1, size); -1946 self.path_stats -1947 .entry(path_id) -1948 .or_default() -1949 .udp_tx -1950 .on_sent(1, size); -1951 Some(Transmit { -1952 destination: network_path.remote, -1953 size, -1954 ecn: None, -1955 segment_size: None, -1956 src_ip: network_path.local_ip, -1957 }) -1958 } -1959 -1960 /// Indicate what types of frames are ready to send for the given space -1961 /// -1962 /// *packet_size* is the number of bytes available to build the next packet. -1963 /// *connection_close_pending* indicates whether a CONNECTION_CLOSE frame needs to be -1964 /// sent. -1965 fn space_can_send( -1966 &mut self, -1967 space_id: SpaceId, -1968 path_id: PathId, -1969 packet_size: usize, -1970 connection_close_pending: bool, -1971 ) -> SendableFrames { -1972 let space = &mut self.spaces[space_id]; -1973 let space_has_crypto = space.crypto.is_some(); -1974 -1975 if !space_has_crypto -1976 && (space_id != SpaceId::Data -1977 || self.zero_rtt_crypto.is_none() -1978 || self.side.is_server()) -1979 { -1980 // Nothing to send in this space -1981 return SendableFrames::empty(); -1982 } -1983 -1984 let mut can_send = space.can_send(path_id, &self.streams); -1985 -1986 // Check for 1RTT space. -1987 if space_id == SpaceId::Data { -1988 let pn = space.for_path(path_id).peek_tx_number(); -1989 // Number of bytes available for frames if this is a 1-RTT packet. We're -1990 // guaranteed to be able to send an individual frame at least this large in the -1991 // next 1-RTT packet. This could be generalized to support every space, but it's -1992 // only needed to handle large fixed-size frames, which only exist in 1-RTT -1993 // (application datagrams). -1994 let frame_space_1rtt = -1995 packet_size.saturating_sub(self.predict_1rtt_overhead(pn, path_id)); -1996 can_send |= self.can_send_1rtt(path_id, frame_space_1rtt); -1997 } -1998 -1999 can_send.close = connection_close_pending && space_has_crypto; -2000 -2001 can_send -2002 } -2003 -2004 /// Process `ConnectionEvent`s generated by the associated `Endpoint` -2005 /// -2006 /// Will execute protocol logic upon receipt of a connection event, in turn preparing signals -2007 /// (including application `Event`s, `EndpointEvent`s and outgoing datagrams) that should be -2008 /// extracted through the relevant methods. -2009 pub fn handle_event(&mut self, event: ConnectionEvent) { -2010 use ConnectionEventInner::*; -2011 match event.0 { -2012 Datagram(DatagramConnectionEvent { -2013 now, -2014 network_path, -2015 path_id, -2016 ecn, -2017 first_decode, -2018 remaining, -2019 }) => { -2020 let span = trace_span!("pkt", %path_id); -2021 let _guard = span.enter(); -2022 -2023 if self.update_network_path_or_discard(network_path, path_id) { -2024 // A return value of true indicates we should discard this packet. -2025 return; -2026 } -2027 -2028 let was_anti_amplification_blocked = self -2029 .path(path_id) -2030 .map(|path| path.anti_amplification_blocked(1)) -2031 .unwrap_or(true); // if we don't know about this path it's eagerly considered as unvalidated -2032 // TODO(@divma): revisit this -2033 -2034 self.stats.udp_rx.datagrams += 1; -2035 self.stats.udp_rx.bytes += first_decode.len() as u64; -2036 let rx = &mut self.path_stats.entry(path_id).or_default().udp_rx; -2037 rx.datagrams += 1; -2038 rx.bytes += first_decode.len() as u64; -2039 let data_len = first_decode.len(); -2040 -2041 self.handle_decode(now, network_path, path_id, ecn, first_decode); -2042 // The current `path` might have changed inside `handle_decode` since the packet -2043 // could have triggered a migration. The packet might also belong to an unknown -2044 // path and have been rejected. Make sure the data received is accounted for the -2045 // most recent path by accessing `path` after `handle_decode`. -2046 if let Some(path) = self.path_mut(path_id) { -2047 path.inc_total_recvd(data_len as u64); -2048 } +1943 let buf = &mut TransmitBuf::new(buf, NonZeroUsize::MIN, MIN_INITIAL_SIZE.into()); +1944 buf.start_new_datagram(); +1945 +1946 let mut builder = PacketBuilder::new(now, SpaceId::Data, path_id, cid, buf, false, self)?; +1947 let stats = &mut self.stats.frame_tx; +1948 builder.write_frame_with_log_msg(frame, stats, Some("(off-path)")); +1949 builder.finish_and_track(now, self, path_id, PadDatagram::ToMinMtu); +1950 +1951 let size = buf.len(); +1952 +1953 self.stats.udp_tx.on_sent(1, size); +1954 self.path_stats +1955 .entry(path_id) +1956 .or_default() +1957 .udp_tx +1958 .on_sent(1, size); +1959 Some(Transmit { +1960 destination: network_path.remote, +1961 size, +1962 ecn: None, +1963 segment_size: None, +1964 src_ip: network_path.local_ip, +1965 }) +1966 } +1967 +1968 /// Indicate what types of frames are ready to send for the given space +1969 /// +1970 /// *packet_size* is the number of bytes available to build the next packet. +1971 /// *connection_close_pending* indicates whether a CONNECTION_CLOSE frame needs to be +1972 /// sent. +1973 fn space_can_send( +1974 &mut self, +1975 space_id: SpaceId, +1976 path_id: PathId, +1977 packet_size: usize, +1978 connection_close_pending: bool, +1979 ) -> SendableFrames { +1980 let space = &mut self.spaces[space_id]; +1981 let space_has_crypto = space.crypto.is_some(); +1982 +1983 if !space_has_crypto +1984 && (space_id != SpaceId::Data +1985 || self.zero_rtt_crypto.is_none() +1986 || self.side.is_server()) +1987 { +1988 // Nothing to send in this space +1989 return SendableFrames::empty(); +1990 } +1991 +1992 let mut can_send = space.can_send(path_id, &self.streams); +1993 +1994 // Check for 1RTT space. +1995 if space_id == SpaceId::Data { +1996 let pn = space.for_path(path_id).peek_tx_number(); +1997 // Number of bytes available for frames if this is a 1-RTT packet. We're +1998 // guaranteed to be able to send an individual frame at least this large in the +1999 // next 1-RTT packet. This could be generalized to support every space, but it's +2000 // only needed to handle large fixed-size frames, which only exist in 1-RTT +2001 // (application datagrams). +2002 let frame_space_1rtt = +2003 packet_size.saturating_sub(self.predict_1rtt_overhead(pn, path_id)); +2004 can_send |= self.can_send_1rtt(path_id, frame_space_1rtt); +2005 } +2006 +2007 can_send.close = connection_close_pending && space_has_crypto; +2008 +2009 can_send +2010 } +2011 +2012 /// Process `ConnectionEvent`s generated by the associated `Endpoint` +2013 /// +2014 /// Will execute protocol logic upon receipt of a connection event, in turn preparing signals +2015 /// (including application `Event`s, `EndpointEvent`s and outgoing datagrams) that should be +2016 /// extracted through the relevant methods. +2017 pub fn handle_event(&mut self, event: ConnectionEvent) { +2018 use ConnectionEventInner::*; +2019 match event.0 { +2020 Datagram(DatagramConnectionEvent { +2021 now, +2022 network_path, +2023 path_id, +2024 ecn, +2025 first_decode, +2026 remaining, +2027 }) => { +2028 let span = trace_span!("pkt", %path_id); +2029 let _guard = span.enter(); +2030 +2031 if self.update_network_path_or_discard(network_path, path_id) { +2032 // A return value of true indicates we should discard this packet. +2033 return; +2034 } +2035 +2036 let was_anti_amplification_blocked = self +2037 .path(path_id) +2038 .map(|path| path.anti_amplification_blocked(1)) +2039 // We never tried to send on an non-existing (new) path so have not been +2040 // anti-amplification blocked for it previously. +2041 .unwrap_or(false); +2042 +2043 self.stats.udp_rx.datagrams += 1; +2044 self.stats.udp_rx.bytes += first_decode.len() as u64; +2045 let rx = &mut self.path_stats.entry(path_id).or_default().udp_rx; +2046 rx.datagrams += 1; +2047 rx.bytes += first_decode.len() as u64; +2048 let data_len = first_decode.len(); 2049 -2050 if let Some(data) = remaining { -2051 self.stats.udp_rx.bytes += data.len() as u64; -2052 self.path_stats.entry(path_id).or_default().udp_rx.bytes += data.len() as u64; -2053 self.handle_coalesced(now, network_path, path_id, ecn, data); -2054 } -2055 -2056 if let Some(path) = self.paths.get_mut(&path_id) { -2057 self.qlog -2058 .emit_recovery_metrics(path_id, &mut path.data, now); -2059 } -2060 -2061 if was_anti_amplification_blocked { -2062 // A prior attempt to set the loss detection timer may have failed due to -2063 // anti-amplification, so ensure it's set now. Prevents a handshake deadlock if -2064 // the server's first flight is lost. -2065 self.set_loss_detection_timer(now, path_id); -2066 } -2067 } -2068 NewIdentifiers(ids, now, cid_len, cid_lifetime) => { -2069 let path_id = ids.first().map(|issued| issued.path_id).unwrap_or_default(); -2070 debug_assert!(ids.iter().all(|issued| issued.path_id == path_id)); -2071 let cid_state = self -2072 .local_cid_state -2073 .entry(path_id) -2074 .or_insert_with(|| CidState::new(cid_len, cid_lifetime, now, 0)); -2075 cid_state.new_cids(&ids, now); -2076 -2077 ids.into_iter().rev().for_each(|frame| { -2078 self.spaces[SpaceId::Data].pending.new_cids.push(frame); -2079 }); -2080 // Always update Timer::PushNewCid -2081 self.reset_cid_retirement(now); -2082 } -2083 } -2084 } +2050 self.handle_decode(now, network_path, path_id, ecn, first_decode); +2051 // The current `path` might have changed inside `handle_decode` since the packet +2052 // could have triggered a migration. The packet might also belong to an unknown +2053 // path and have been rejected. Make sure the data received is accounted for the +2054 // most recent path by accessing `path` after `handle_decode`. +2055 if let Some(path) = self.path_mut(path_id) { +2056 path.inc_total_recvd(data_len as u64); +2057 } +2058 +2059 if let Some(data) = remaining { +2060 self.stats.udp_rx.bytes += data.len() as u64; +2061 self.path_stats.entry(path_id).or_default().udp_rx.bytes += data.len() as u64; +2062 self.handle_coalesced(now, network_path, path_id, ecn, data); +2063 } +2064 +2065 if let Some(path) = self.paths.get_mut(&path_id) { +2066 self.qlog +2067 .emit_recovery_metrics(path_id, &mut path.data, now); +2068 } +2069 +2070 if was_anti_amplification_blocked { +2071 // A prior attempt to set the loss detection timer may have failed due to +2072 // anti-amplification, so ensure it's set now. Prevents a handshake deadlock if +2073 // the server's first flight is lost. +2074 self.set_loss_detection_timer(now, path_id); +2075 } +2076 } +2077 NewIdentifiers(ids, now, cid_len, cid_lifetime) => { +2078 let path_id = ids.first().map(|issued| issued.path_id).unwrap_or_default(); +2079 debug_assert!(ids.iter().all(|issued| issued.path_id == path_id)); +2080 let cid_state = self +2081 .local_cid_state +2082 .entry(path_id) +2083 .or_insert_with(|| CidState::new(cid_len, cid_lifetime, now, 0)); +2084 cid_state.new_cids(&ids, now); 2085 -2086 /// Updates the network path for `path_id`. -2087 /// -2088 /// Returns true if a packet coming in for this `path_id` over given `network_path` should be discarded. -2089 /// Returns false if the path was updated and the packet doesn't need to be discarded. -2090 fn update_network_path_or_discard(&mut self, network_path: FourTuple, path_id: PathId) -> bool { -2091 let remote_may_migrate = self.side.remote_may_migrate(&self.state); -2092 let local_ip_may_migrate = self.side.is_client(); -2093 // If this packet could initiate a migration and we're a client or a server that -2094 // forbids migration, drop the datagram. This could be relaxed to heuristically -2095 // permit NAT-rebinding-like migration. -2096 if let Some(known_path) = self.path_mut(path_id) { -2097 if network_path.remote != known_path.network_path.remote && !remote_may_migrate { -2098 trace!( -2099 %path_id, -2100 %network_path, -2101 %known_path.network_path, -2102 "discarding packet from unrecognized peer" -2103 ); -2104 return true; -2105 } -2106 -2107 if known_path.network_path.local_ip.is_some() -2108 && network_path.local_ip.is_some() -2109 && known_path.network_path.local_ip != network_path.local_ip -2110 && !local_ip_may_migrate -2111 { -2112 trace!( -2113 %path_id, -2114 %network_path, -2115 %known_path.network_path, -2116 "discarding packet sent to incorrect interface" -2117 ); -2118 return true; -2119 } -2120 // If the datagram indicates that we've changed our local IP, we update it. -2121 // This is alluded to in Section 5.2 of the Multipath RFC draft 18: -2122 // https://www.ietf.org/archive/id/draft-ietf-quic-multipath-18.html#name-using-multiple-paths-on-the -2123 // > Client receives the packet, recognizes a path migration, updates the source address of path 2 to 192.0.2.1. -2124 if let Some(local_ip) = network_path.local_ip { -2125 if known_path -2126 .network_path -2127 .local_ip -2128 .is_some_and(|ip| ip != local_ip) -2129 { -2130 debug!( -2131 %path_id, -2132 %network_path, -2133 %known_path.network_path, -2134 "path's local address seemingly migrated" -2135 ); -2136 } -2137 // We update the address without path validation on the client side. -2138 // https://www.ietf.org/archive/id/draft-ietf-quic-multipath-18.html#section-5.1 -2139 // > Servers observing a 4-tuple change will perform path validation (see Section 9 of [QUIC-TRANSPORT]). -2140 // This sounds like it's *only* the server endpoints that do this. -2141 // TODO(matheus23): We should still consider doing a proper migration on the client side in the future. -2142 // For now, this preserves the behavior of this code pre 4-tuple tracking. -2143 known_path.network_path.local_ip = Some(local_ip); -2144 } -2145 } -2146 false -2147 } -2148 -2149 /// Process timer expirations -2150 /// -2151 /// Executes protocol logic, potentially preparing signals (including application `Event`s, -2152 /// `EndpointEvent`s and outgoing datagrams) that should be extracted through the relevant -2153 /// methods. -2154 /// -2155 /// It is most efficient to call this immediately after the system clock reaches the latest -2156 /// `Instant` that was output by `poll_timeout`; however spurious extra calls will simply -2157 /// no-op and therefore are safe. -2158 pub fn handle_timeout(&mut self, now: Instant) { -2159 while let Some((timer, _time)) = self.timers.expire_before(now, &self.qlog) { -2160 // TODO(@divma): remove `at` when the unicorn is born -2161 trace!(?timer, at=?now, "timeout"); -2162 match timer { -2163 Timer::Conn(timer) => match timer { -2164 ConnTimer::Close => { -2165 self.state.move_to_drained(None); -2166 self.endpoint_events.push_back(EndpointEventInner::Drained); -2167 } -2168 ConnTimer::Idle => { -2169 self.kill(ConnectionError::TimedOut); -2170 } -2171 ConnTimer::KeepAlive => { -2172 trace!("sending keep-alive"); -2173 self.ping(); -2174 } -2175 ConnTimer::KeyDiscard => { -2176 self.zero_rtt_crypto = None; -2177 self.prev_crypto = None; -2178 } -2179 ConnTimer::PushNewCid => { -2180 while let Some((path_id, when)) = self.next_cid_retirement() { -2181 if when > now { -2182 break; -2183 } -2184 match self.local_cid_state.get_mut(&path_id) { -2185 None => error!(%path_id, "No local CID state for path"), -2186 Some(cid_state) => { -2187 // Update `retire_prior_to` field in NEW_CONNECTION_ID frame -2188 let num_new_cid = cid_state.on_cid_timeout().into(); -2189 if !self.state.is_closed() { -2190 trace!( -2191 "push a new CID to peer RETIRE_PRIOR_TO field {}", -2192 cid_state.retire_prior_to() -2193 ); -2194 self.endpoint_events.push_back( -2195 EndpointEventInner::NeedIdentifiers( -2196 path_id, -2197 now, -2198 num_new_cid, -2199 ), -2200 ); -2201 } -2202 } -2203 } -2204 } -2205 } -2206 }, -2207 // TODO: add path_id as span somehow -2208 Timer::PerPath(path_id, timer) => { -2209 let span = trace_span!("per-path timer fired", %path_id, ?timer); -2210 let _guard = span.enter(); -2211 match timer { -2212 PathTimer::PathIdle => { -2213 if let Err(err) = self.close_path( -2214 now, -2215 path_id, -2216 TransportErrorCode::PATH_UNSTABLE_OR_POOR.into(), -2217 ) { -2218 warn!(?err, "failed closing path"); -2219 } -2220 } -2221 -2222 PathTimer::PathKeepAlive => { -2223 trace!("sending keep-alive on path"); -2224 self.ping_path(path_id).ok(); -2225 } -2226 PathTimer::LossDetection => { -2227 self.on_loss_detection_timeout(now, path_id); -2228 self.qlog.emit_recovery_metrics( -2229 path_id, -2230 &mut self.paths.get_mut(&path_id).unwrap().data, -2231 now, -2232 ); -2233 } -2234 PathTimer::PathValidation => { -2235 let Some(path) = self.paths.get_mut(&path_id) else { -2236 continue; -2237 }; -2238 self.timers.stop( -2239 Timer::PerPath(path_id, PathTimer::PathChallengeLost), -2240 self.qlog.with_time(now), +2086 ids.into_iter().rev().for_each(|frame| { +2087 self.spaces[SpaceId::Data].pending.new_cids.push(frame); +2088 }); +2089 // Always update Timer::PushNewCid +2090 self.reset_cid_retirement(now); +2091 } +2092 } +2093 } +2094 +2095 /// Updates the network path for `path_id`. +2096 /// +2097 /// Returns true if a packet coming in for this `path_id` over given `network_path` should be discarded. +2098 /// Returns false if the path was updated and the packet doesn't need to be discarded. +2099 fn update_network_path_or_discard(&mut self, network_path: FourTuple, path_id: PathId) -> bool { +2100 let remote_may_migrate = self.side.remote_may_migrate(&self.state); +2101 let local_ip_may_migrate = self.side.is_client(); +2102 // If this packet could initiate a migration and we're a client or a server that +2103 // forbids migration, drop the datagram. This could be relaxed to heuristically +2104 // permit NAT-rebinding-like migration. +2105 if let Some(known_path) = self.path_mut(path_id) { +2106 if network_path.remote != known_path.network_path.remote && !remote_may_migrate { +2107 trace!( +2108 %path_id, +2109 %network_path, +2110 %known_path.network_path, +2111 "discarding packet from unrecognized peer" +2112 ); +2113 return true; +2114 } +2115 +2116 if known_path.network_path.local_ip.is_some() +2117 && network_path.local_ip.is_some() +2118 && known_path.network_path.local_ip != network_path.local_ip +2119 && !local_ip_may_migrate +2120 { +2121 trace!( +2122 %path_id, +2123 %network_path, +2124 %known_path.network_path, +2125 "discarding packet sent to incorrect interface" +2126 ); +2127 return true; +2128 } +2129 // If the datagram indicates that we've changed our local IP, we update it. +2130 // This is alluded to in Section 5.2 of the Multipath RFC draft 18: +2131 // https://www.ietf.org/archive/id/draft-ietf-quic-multipath-18.html#name-using-multiple-paths-on-the +2132 // > Client receives the packet, recognizes a path migration, updates the source address of path 2 to 192.0.2.1. +2133 if let Some(local_ip) = network_path.local_ip { +2134 if known_path +2135 .network_path +2136 .local_ip +2137 .is_some_and(|ip| ip != local_ip) +2138 { +2139 debug!( +2140 %path_id, +2141 %network_path, +2142 %known_path.network_path, +2143 "path's local address seemingly migrated" +2144 ); +2145 } +2146 // We update the address without path validation on the client side. +2147 // https://www.ietf.org/archive/id/draft-ietf-quic-multipath-18.html#section-5.1 +2148 // > Servers observing a 4-tuple change will perform path validation (see Section 9 of [QUIC-TRANSPORT]). +2149 // This sounds like it's *only* the server endpoints that do this. +2150 // TODO(matheus23): We should still consider doing a proper migration on the client side in the future. +2151 // For now, this preserves the behavior of this code pre 4-tuple tracking. +2152 known_path.network_path.local_ip = Some(local_ip); +2153 } +2154 } +2155 false +2156 } +2157 +2158 /// Process timer expirations +2159 /// +2160 /// Executes protocol logic, potentially preparing signals (including application `Event`s, +2161 /// `EndpointEvent`s and outgoing datagrams) that should be extracted through the relevant +2162 /// methods. +2163 /// +2164 /// It is most efficient to call this immediately after the system clock reaches the latest +2165 /// `Instant` that was output by `poll_timeout`; however spurious extra calls will simply +2166 /// no-op and therefore are safe. +2167 pub fn handle_timeout(&mut self, now: Instant) { +2168 while let Some((timer, _time)) = self.timers.expire_before(now, &self.qlog) { +2169 // TODO(@divma): remove `at` when the unicorn is born +2170 trace!(?timer, at=?now, "timeout"); +2171 match timer { +2172 Timer::Conn(timer) => match timer { +2173 ConnTimer::Close => { +2174 self.state.move_to_drained(None); +2175 self.endpoint_events.push_back(EndpointEventInner::Drained); +2176 } +2177 ConnTimer::Idle => { +2178 self.kill(ConnectionError::TimedOut); +2179 } +2180 ConnTimer::KeepAlive => { +2181 trace!("sending keep-alive"); +2182 self.ping(); +2183 } +2184 ConnTimer::KeyDiscard => { +2185 self.zero_rtt_crypto = None; +2186 self.prev_crypto = None; +2187 } +2188 ConnTimer::PushNewCid => { +2189 while let Some((path_id, when)) = self.next_cid_retirement() { +2190 if when > now { +2191 break; +2192 } +2193 match self.local_cid_state.get_mut(&path_id) { +2194 None => error!(%path_id, "No local CID state for path"), +2195 Some(cid_state) => { +2196 // Update `retire_prior_to` field in NEW_CONNECTION_ID frame +2197 let num_new_cid = cid_state.on_cid_timeout().into(); +2198 if !self.state.is_closed() { +2199 trace!( +2200 "push a new CID to peer RETIRE_PRIOR_TO field {}", +2201 cid_state.retire_prior_to() +2202 ); +2203 self.endpoint_events.push_back( +2204 EndpointEventInner::NeedIdentifiers( +2205 path_id, +2206 now, +2207 num_new_cid, +2208 ), +2209 ); +2210 } +2211 } +2212 } +2213 } +2214 } +2215 }, +2216 // TODO: add path_id as span somehow +2217 Timer::PerPath(path_id, timer) => { +2218 let span = trace_span!("per-path timer fired", %path_id, ?timer); +2219 let _guard = span.enter(); +2220 match timer { +2221 PathTimer::PathIdle => { +2222 if let Err(err) = self.close_path( +2223 now, +2224 path_id, +2225 TransportErrorCode::PATH_UNSTABLE_OR_POOR.into(), +2226 ) { +2227 warn!(?err, "failed closing path"); +2228 } +2229 } +2230 +2231 PathTimer::PathKeepAlive => { +2232 trace!("sending keep-alive on path"); +2233 self.ping_path(path_id).ok(); +2234 } +2235 PathTimer::LossDetection => { +2236 self.on_loss_detection_timeout(now, path_id); +2237 self.qlog.emit_recovery_metrics( +2238 path_id, +2239 &mut self.paths.get_mut(&path_id).unwrap().data, +2240 now, 2241 ); -2242 debug!("path validation failed"); -2243 if let Some((_, prev)) = path.prev.take() { -2244 path.data = prev; -2245 } -2246 path.data.challenges_sent.clear(); -2247 path.data.send_new_challenge = false; -2248 } -2249 PathTimer::PathChallengeLost => { -2250 let Some(path) = self.paths.get_mut(&path_id) else { -2251 continue; -2252 }; -2253 trace!("path challenge deemed lost"); -2254 path.data.send_new_challenge = true; -2255 } -2256 PathTimer::PathOpen => { -2257 let Some(path) = self.paths.get_mut(&path_id) else { -2258 continue; -2259 }; -2260 path.data.challenges_sent.clear(); -2261 path.data.send_new_challenge = false; -2262 self.timers.stop( -2263 Timer::PerPath(path_id, PathTimer::PathChallengeLost), -2264 self.qlog.with_time(now), -2265 ); -2266 debug!("new path validation failed"); -2267 if let Err(err) = self.close_path( -2268 now, -2269 path_id, -2270 TransportErrorCode::PATH_UNSTABLE_OR_POOR.into(), -2271 ) { -2272 warn!(?err, "failed closing path"); -2273 } -2274 -2275 self.events.push_back(Event::Path(PathEvent::LocallyClosed { -2276 id: path_id, -2277 error: PathError::ValidationFailed, -2278 })); -2279 } -2280 PathTimer::Pacing => trace!("pacing timer expired"), -2281 PathTimer::MaxAckDelay => { -2282 trace!("max ack delay reached"); -2283 // This timer is only armed in the Data space -2284 self.spaces[SpaceId::Data] -2285 .for_path(path_id) -2286 .pending_acks -2287 .on_max_ack_delay_timeout() +2242 } +2243 PathTimer::PathValidation => { +2244 let Some(path) = self.paths.get_mut(&path_id) else { +2245 continue; +2246 }; +2247 self.timers.stop( +2248 Timer::PerPath(path_id, PathTimer::PathChallengeLost), +2249 self.qlog.with_time(now), +2250 ); +2251 debug!("path validation failed"); +2252 if let Some((_, prev)) = path.prev.take() { +2253 path.data = prev; +2254 } +2255 path.data.challenges_sent.clear(); +2256 path.data.send_new_challenge = false; +2257 } +2258 PathTimer::PathChallengeLost => { +2259 let Some(path) = self.paths.get_mut(&path_id) else { +2260 continue; +2261 }; +2262 trace!("path challenge deemed lost"); +2263 path.data.send_new_challenge = true; +2264 } +2265 PathTimer::PathOpen => { +2266 let Some(path) = self.paths.get_mut(&path_id) else { +2267 continue; +2268 }; +2269 path.data.challenges_sent.clear(); +2270 path.data.send_new_challenge = false; +2271 self.timers.stop( +2272 Timer::PerPath(path_id, PathTimer::PathChallengeLost), +2273 self.qlog.with_time(now), +2274 ); +2275 debug!("new path validation failed"); +2276 if let Err(err) = self.close_path( +2277 now, +2278 path_id, +2279 TransportErrorCode::PATH_UNSTABLE_OR_POOR.into(), +2280 ) { +2281 warn!(?err, "failed closing path"); +2282 } +2283 +2284 self.events.push_back(Event::Path(PathEvent::LocallyClosed { +2285 id: path_id, +2286 error: PathError::ValidationFailed, +2287 })); 2288 } -2289 PathTimer::DiscardPath => { -2290 // The path was abandoned and 3*PTO has expired since. Clean up all -2291 // remaining state and install stateless reset token. -2292 self.timers.stop_per_path(path_id, self.qlog.with_time(now)); -2293 if let Some(local_cid_state) = self.local_cid_state.remove(&path_id) { -2294 let (min_seq, max_seq) = local_cid_state.active_seq(); -2295 for seq in min_seq..=max_seq { -2296 self.endpoint_events.push_back( -2297 EndpointEventInner::RetireConnectionId( -2298 now, path_id, seq, false, -2299 ), -2300 ); -2301 } -2302 } -2303 self.discard_path(path_id, now); -2304 } -2305 } -2306 } -2307 } -2308 } -2309 } -2310 -2311 /// Close a connection immediately -2312 /// -2313 /// This does not ensure delivery of outstanding data. It is the application's responsibility to -2314 /// call this only when all important communications have been completed, e.g. by calling -2315 /// [`SendStream::finish`] on outstanding streams and waiting for the corresponding -2316 /// [`StreamEvent::Finished`] event. -2317 /// -2318 /// If [`Streams::send_streams`] returns 0, all outstanding stream data has been -2319 /// delivered. There may still be data from the peer that has not been received. -2320 /// -2321 /// [`StreamEvent::Finished`]: crate::StreamEvent::Finished -2322 pub fn close(&mut self, now: Instant, error_code: VarInt, reason: Bytes) { -2323 self.close_inner( -2324 now, -2325 Close::Application(frame::ApplicationClose { error_code, reason }), -2326 ) -2327 } -2328 -2329 fn close_inner(&mut self, now: Instant, reason: Close) { -2330 let was_closed = self.state.is_closed(); -2331 if !was_closed { -2332 self.close_common(); -2333 self.set_close_timer(now); -2334 self.connection_close_pending = true; -2335 self.state.move_to_closed_local(reason); -2336 } -2337 } -2338 -2339 /// Control datagrams -2340 pub fn datagrams(&mut self) -> Datagrams<'_> { -2341 Datagrams { conn: self } -2342 } -2343 -2344 /// Returns connection statistics -2345 pub fn stats(&mut self) -> ConnectionStats { -2346 self.stats.clone() -2347 } -2348 -2349 /// Returns path statistics -2350 pub fn path_stats(&mut self, path_id: PathId) -> Option<PathStats> { -2351 let path = self.paths.get(&path_id)?; -2352 let stats = self.path_stats.entry(path_id).or_default(); -2353 stats.rtt = path.data.rtt.get(); -2354 stats.cwnd = path.data.congestion.window(); -2355 stats.current_mtu = path.data.mtud.current_mtu(); -2356 Some(*stats) -2357 } -2358 -2359 /// Ping the remote endpoint -2360 /// -2361 /// Causes an ACK-eliciting packet to be transmitted on the connection. -2362 pub fn ping(&mut self) { -2363 // TODO(flub): This is very brute-force: it pings *all* the paths. Instead it would -2364 // be nice if we could only send a single packet for this. -2365 for path_data in self.spaces[self.highest_space].number_spaces.values_mut() { -2366 path_data.ping_pending = true; -2367 } -2368 } -2369 -2370 /// Ping the remote endpoint over a specific path -2371 /// -2372 /// Causes an ACK-eliciting packet to be transmitted on the path. -2373 pub fn ping_path(&mut self, path: PathId) -> Result<(), ClosedPath> { -2374 let path_data = self.spaces[self.highest_space] -2375 .number_spaces -2376 .get_mut(&path) -2377 .ok_or(ClosedPath { _private: () })?; -2378 path_data.ping_pending = true; -2379 Ok(()) -2380 } -2381 -2382 /// Update traffic keys spontaneously -2383 /// -2384 /// This can be useful for testing key updates, as they otherwise only happen infrequently. -2385 pub fn force_key_update(&mut self) { -2386 if !self.state.is_established() { -2387 debug!("ignoring forced key update in illegal state"); -2388 return; -2389 } -2390 if self.prev_crypto.is_some() { -2391 // We already just updated, or are currently updating, the keys. Concurrent key updates -2392 // are illegal. -2393 debug!("ignoring redundant forced key update"); -2394 return; -2395 } -2396 self.update_keys(None, false); -2397 } -2398 -2399 /// Get a session reference -2400 pub fn crypto_session(&self) -> &dyn crypto::Session { -2401 &*self.crypto -2402 } -2403 -2404 /// Whether the connection is in the process of being established -2405 /// -2406 /// If this returns `false`, the connection may be either established or closed, signaled by the -2407 /// emission of a `Connected` or `ConnectionLost` message respectively. -2408 pub fn is_handshaking(&self) -> bool { -2409 self.state.is_handshake() -2410 } -2411 -2412 /// Whether the connection is closed -2413 /// -2414 /// Closed connections cannot transport any further data. A connection becomes closed when -2415 /// either peer application intentionally closes it, or when either transport layer detects an -2416 /// error such as a time-out or certificate validation failure. -2417 /// -2418 /// A `ConnectionLost` event is emitted with details when the connection becomes closed. -2419 pub fn is_closed(&self) -> bool { -2420 self.state.is_closed() -2421 } -2422 -2423 /// Whether there is no longer any need to keep the connection around -2424 /// -2425 /// Closed connections become drained after a brief timeout to absorb any remaining in-flight -2426 /// packets from the peer. All drained connections have been closed. -2427 pub fn is_drained(&self) -> bool { -2428 self.state.is_drained() -2429 } -2430 -2431 /// For clients, if the peer accepted the 0-RTT data packets -2432 /// -2433 /// The value is meaningless until after the handshake completes. -2434 pub fn accepted_0rtt(&self) -> bool { -2435 self.accepted_0rtt -2436 } -2437 -2438 /// Whether 0-RTT is/was possible during the handshake -2439 pub fn has_0rtt(&self) -> bool { -2440 self.zero_rtt_enabled -2441 } -2442 -2443 /// Whether there are any pending retransmits -2444 pub fn has_pending_retransmits(&self) -> bool { -2445 !self.spaces[SpaceId::Data].pending.is_empty(&self.streams) -2446 } -2447 -2448 /// Look up whether we're the client or server of this Connection -2449 pub fn side(&self) -> Side { -2450 self.side.side() -2451 } -2452 -2453 /// Get the address observed by the remote over the given path -2454 pub fn path_observed_address(&self, path_id: PathId) -> Result<Option<SocketAddr>, ClosedPath> { -2455 self.path(path_id) -2456 .map(|path_data| { -2457 path_data -2458 .last_observed_addr_report -2459 .as_ref() -2460 .map(|observed| observed.socket_addr()) -2461 }) -2462 .ok_or(ClosedPath { _private: () }) -2463 } -2464 -2465 /// Current best estimate of this connection's latency (round-trip-time) -2466 pub fn rtt(&self, path_id: PathId) -> Option<Duration> { -2467 self.path(path_id).map(|d| d.rtt.get()) -2468 } -2469 -2470 /// Current state of this connection's congestion controller, for debugging purposes -2471 pub fn congestion_state(&self, path_id: PathId) -> Option<&dyn Controller> { -2472 self.path(path_id).map(|d| d.congestion.as_ref()) -2473 } -2474 -2475 /// Modify the number of remotely initiated streams that may be concurrently open -2476 /// -2477 /// No streams may be opened by the peer unless fewer than `count` are already open. Large -2478 /// `count`s increase both minimum and worst-case memory consumption. -2479 pub fn set_max_concurrent_streams(&mut self, dir: Dir, count: VarInt) { -2480 self.streams.set_max_concurrent(dir, count); -2481 // If the limit was reduced, then a flow control update previously deemed insignificant may -2482 // now be significant. -2483 let pending = &mut self.spaces[SpaceId::Data].pending; -2484 self.streams.queue_max_stream_id(pending); -2485 } -2486 -2487 /// Modify the number of open paths allowed when multipath is enabled -2488 /// -2489 /// When reducing the number of concurrent paths this will only affect delaying sending -2490 /// new MAX_PATH_ID frames until fewer than this number of paths are possible. To -2491 /// actively reduce paths they must be closed using [`Connection::close_path`], which -2492 /// can also be used to close not-yet-opened paths. -2493 /// -2494 /// If multipath is not negotiated (see the [`TransportConfig`]) this can not enable -2495 /// multipath and will fail. -2496 pub fn set_max_concurrent_paths( -2497 &mut self, -2498 now: Instant, -2499 count: NonZeroU32, -2500 ) -> Result<(), MultipathNotNegotiated> { -2501 if !self.is_multipath_negotiated() { -2502 return Err(MultipathNotNegotiated { _private: () }); -2503 } -2504 self.max_concurrent_paths = count; -2505 -2506 let in_use_count = self -2507 .local_max_path_id -2508 .next() -2509 .saturating_sub(self.abandoned_paths.len() as u32) -2510 .as_u32(); -2511 let extra_needed = count.get().saturating_sub(in_use_count); -2512 let new_max_path_id = self.local_max_path_id.saturating_add(extra_needed); -2513 -2514 self.set_max_path_id(now, new_max_path_id); -2515 -2516 Ok(()) -2517 } -2518 -2519 /// If needed, issues a new MAX_PATH_ID frame and new CIDs for any newly allowed paths -2520 fn set_max_path_id(&mut self, now: Instant, max_path_id: PathId) { -2521 if max_path_id <= self.local_max_path_id { -2522 return; -2523 } +2289 PathTimer::Pacing => trace!("pacing timer expired"), +2290 PathTimer::MaxAckDelay => { +2291 trace!("max ack delay reached"); +2292 // This timer is only armed in the Data space +2293 self.spaces[SpaceId::Data] +2294 .for_path(path_id) +2295 .pending_acks +2296 .on_max_ack_delay_timeout() +2297 } +2298 PathTimer::DiscardPath => { +2299 // The path was abandoned and 3*PTO has expired since. Clean up all +2300 // remaining state and install stateless reset token. +2301 self.timers.stop_per_path(path_id, self.qlog.with_time(now)); +2302 if let Some(local_cid_state) = self.local_cid_state.remove(&path_id) { +2303 let (min_seq, max_seq) = local_cid_state.active_seq(); +2304 for seq in min_seq..=max_seq { +2305 self.endpoint_events.push_back( +2306 EndpointEventInner::RetireConnectionId( +2307 now, path_id, seq, false, +2308 ), +2309 ); +2310 } +2311 } +2312 self.discard_path(path_id, now); +2313 } +2314 } +2315 } +2316 } +2317 } +2318 } +2319 +2320 /// Close a connection immediately +2321 /// +2322 /// This does not ensure delivery of outstanding data. It is the application's responsibility to +2323 /// call this only when all important communications have been completed, e.g. by calling +2324 /// [`SendStream::finish`] on outstanding streams and waiting for the corresponding +2325 /// [`StreamEvent::Finished`] event. +2326 /// +2327 /// If [`Streams::send_streams`] returns 0, all outstanding stream data has been +2328 /// delivered. There may still be data from the peer that has not been received. +2329 /// +2330 /// [`StreamEvent::Finished`]: crate::StreamEvent::Finished +2331 pub fn close(&mut self, now: Instant, error_code: VarInt, reason: Bytes) { +2332 self.close_inner( +2333 now, +2334 Close::Application(frame::ApplicationClose { error_code, reason }), +2335 ) +2336 } +2337 +2338 fn close_inner(&mut self, now: Instant, reason: Close) { +2339 let was_closed = self.state.is_closed(); +2340 if !was_closed { +2341 self.close_common(); +2342 self.set_close_timer(now); +2343 self.connection_close_pending = true; +2344 self.state.move_to_closed_local(reason); +2345 } +2346 } +2347 +2348 /// Control datagrams +2349 pub fn datagrams(&mut self) -> Datagrams<'_> { +2350 Datagrams { conn: self } +2351 } +2352 +2353 /// Returns connection statistics +2354 pub fn stats(&mut self) -> ConnectionStats { +2355 self.stats.clone() +2356 } +2357 +2358 /// Returns path statistics +2359 pub fn path_stats(&mut self, path_id: PathId) -> Option<PathStats> { +2360 let path = self.paths.get(&path_id)?; +2361 let stats = self.path_stats.entry(path_id).or_default(); +2362 stats.rtt = path.data.rtt.get(); +2363 stats.cwnd = path.data.congestion.window(); +2364 stats.current_mtu = path.data.mtud.current_mtu(); +2365 Some(*stats) +2366 } +2367 +2368 /// Ping the remote endpoint +2369 /// +2370 /// Causes an ACK-eliciting packet to be transmitted on the connection. +2371 pub fn ping(&mut self) { +2372 // TODO(flub): This is very brute-force: it pings *all* the paths. Instead it would +2373 // be nice if we could only send a single packet for this. +2374 for path_data in self.spaces[self.highest_space].number_spaces.values_mut() { +2375 path_data.ping_pending = true; +2376 } +2377 } +2378 +2379 /// Ping the remote endpoint over a specific path +2380 /// +2381 /// Causes an ACK-eliciting packet to be transmitted on the path. +2382 pub fn ping_path(&mut self, path: PathId) -> Result<(), ClosedPath> { +2383 let path_data = self.spaces[self.highest_space] +2384 .number_spaces +2385 .get_mut(&path) +2386 .ok_or(ClosedPath { _private: () })?; +2387 path_data.ping_pending = true; +2388 Ok(()) +2389 } +2390 +2391 /// Update traffic keys spontaneously +2392 /// +2393 /// This can be useful for testing key updates, as they otherwise only happen infrequently. +2394 pub fn force_key_update(&mut self) { +2395 if !self.state.is_established() { +2396 debug!("ignoring forced key update in illegal state"); +2397 return; +2398 } +2399 if self.prev_crypto.is_some() { +2400 // We already just updated, or are currently updating, the keys. Concurrent key updates +2401 // are illegal. +2402 debug!("ignoring redundant forced key update"); +2403 return; +2404 } +2405 self.update_keys(None, false); +2406 } +2407 +2408 /// Get a session reference +2409 pub fn crypto_session(&self) -> &dyn crypto::Session { +2410 &*self.crypto +2411 } +2412 +2413 /// Whether the connection is in the process of being established +2414 /// +2415 /// If this returns `false`, the connection may be either established or closed, signaled by the +2416 /// emission of a `Connected` or `ConnectionLost` message respectively. +2417 pub fn is_handshaking(&self) -> bool { +2418 self.state.is_handshake() +2419 } +2420 +2421 /// Whether the connection is closed +2422 /// +2423 /// Closed connections cannot transport any further data. A connection becomes closed when +2424 /// either peer application intentionally closes it, or when either transport layer detects an +2425 /// error such as a time-out or certificate validation failure. +2426 /// +2427 /// A `ConnectionLost` event is emitted with details when the connection becomes closed. +2428 pub fn is_closed(&self) -> bool { +2429 self.state.is_closed() +2430 } +2431 +2432 /// Whether there is no longer any need to keep the connection around +2433 /// +2434 /// Closed connections become drained after a brief timeout to absorb any remaining in-flight +2435 /// packets from the peer. All drained connections have been closed. +2436 pub fn is_drained(&self) -> bool { +2437 self.state.is_drained() +2438 } +2439 +2440 /// For clients, if the peer accepted the 0-RTT data packets +2441 /// +2442 /// The value is meaningless until after the handshake completes. +2443 pub fn accepted_0rtt(&self) -> bool { +2444 self.accepted_0rtt +2445 } +2446 +2447 /// Whether 0-RTT is/was possible during the handshake +2448 pub fn has_0rtt(&self) -> bool { +2449 self.zero_rtt_enabled +2450 } +2451 +2452 /// Whether there are any pending retransmits +2453 pub fn has_pending_retransmits(&self) -> bool { +2454 !self.spaces[SpaceId::Data].pending.is_empty(&self.streams) +2455 } +2456 +2457 /// Look up whether we're the client or server of this Connection +2458 pub fn side(&self) -> Side { +2459 self.side.side() +2460 } +2461 +2462 /// Get the address observed by the remote over the given path +2463 pub fn path_observed_address(&self, path_id: PathId) -> Result<Option<SocketAddr>, ClosedPath> { +2464 self.path(path_id) +2465 .map(|path_data| { +2466 path_data +2467 .last_observed_addr_report +2468 .as_ref() +2469 .map(|observed| observed.socket_addr()) +2470 }) +2471 .ok_or(ClosedPath { _private: () }) +2472 } +2473 +2474 /// Current best estimate of this connection's latency (round-trip-time) +2475 pub fn rtt(&self, path_id: PathId) -> Option<Duration> { +2476 self.path(path_id).map(|d| d.rtt.get()) +2477 } +2478 +2479 /// Current state of this connection's congestion controller, for debugging purposes +2480 pub fn congestion_state(&self, path_id: PathId) -> Option<&dyn Controller> { +2481 self.path(path_id).map(|d| d.congestion.as_ref()) +2482 } +2483 +2484 /// Modify the number of remotely initiated streams that may be concurrently open +2485 /// +2486 /// No streams may be opened by the peer unless fewer than `count` are already open. Large +2487 /// `count`s increase both minimum and worst-case memory consumption. +2488 pub fn set_max_concurrent_streams(&mut self, dir: Dir, count: VarInt) { +2489 self.streams.set_max_concurrent(dir, count); +2490 // If the limit was reduced, then a flow control update previously deemed insignificant may +2491 // now be significant. +2492 let pending = &mut self.spaces[SpaceId::Data].pending; +2493 self.streams.queue_max_stream_id(pending); +2494 } +2495 +2496 /// Modify the number of open paths allowed when multipath is enabled +2497 /// +2498 /// When reducing the number of concurrent paths this will only affect delaying sending +2499 /// new MAX_PATH_ID frames until fewer than this number of paths are possible. To +2500 /// actively reduce paths they must be closed using [`Connection::close_path`], which +2501 /// can also be used to close not-yet-opened paths. +2502 /// +2503 /// If multipath is not negotiated (see the [`TransportConfig`]) this can not enable +2504 /// multipath and will fail. +2505 pub fn set_max_concurrent_paths( +2506 &mut self, +2507 now: Instant, +2508 count: NonZeroU32, +2509 ) -> Result<(), MultipathNotNegotiated> { +2510 if !self.is_multipath_negotiated() { +2511 return Err(MultipathNotNegotiated { _private: () }); +2512 } +2513 self.max_concurrent_paths = count; +2514 +2515 let in_use_count = self +2516 .local_max_path_id +2517 .next() +2518 .saturating_sub(self.abandoned_paths.len() as u32) +2519 .as_u32(); +2520 let extra_needed = count.get().saturating_sub(in_use_count); +2521 let new_max_path_id = self.local_max_path_id.saturating_add(extra_needed); +2522 +2523 self.set_max_path_id(now, new_max_path_id); 2524 -2525 self.local_max_path_id = max_path_id; -2526 self.spaces[SpaceId::Data].pending.max_path_id = true; +2525 Ok(()) +2526 } 2527 -2528 self.issue_first_path_cids(now); -2529 } -2530 -2531 /// Current number of remotely initiated streams that may be concurrently open -2532 /// -2533 /// If the target for this limit is reduced using [`set_max_concurrent_streams`](Self::set_max_concurrent_streams), -2534 /// it will not change immediately, even if fewer streams are open. Instead, it will -2535 /// decrement by one for each time a remotely initiated stream of matching directionality is closed. -2536 pub fn max_concurrent_streams(&self, dir: Dir) -> u64 { -2537 self.streams.max_concurrent(dir) +2528 /// If needed, issues a new MAX_PATH_ID frame and new CIDs for any newly allowed paths +2529 fn set_max_path_id(&mut self, now: Instant, max_path_id: PathId) { +2530 if max_path_id <= self.local_max_path_id { +2531 return; +2532 } +2533 +2534 self.local_max_path_id = max_path_id; +2535 self.spaces[SpaceId::Data].pending.max_path_id = true; +2536 +2537 self.issue_first_path_cids(now); 2538 } 2539 -2540 /// See [`TransportConfig::send_window()`] -2541 pub fn set_send_window(&mut self, send_window: u64) { -2542 self.streams.set_send_window(send_window); -2543 } -2544 -2545 /// See [`TransportConfig::receive_window()`] -2546 pub fn set_receive_window(&mut self, receive_window: VarInt) { -2547 if self.streams.set_receive_window(receive_window) { -2548 self.spaces[SpaceId::Data].pending.max_data = true; -2549 } -2550 } -2551 -2552 /// Whether the Multipath for QUIC extension is enabled. -2553 /// -2554 /// Multipath is only enabled after the handshake is completed and if it was enabled by both -2555 /// peers. -2556 pub fn is_multipath_negotiated(&self) -> bool { -2557 !self.is_handshaking() -2558 && self.config.max_concurrent_multipath_paths.is_some() -2559 && self.peer_params.initial_max_path_id.is_some() -2560 } -2561 -2562 fn on_ack_received( -2563 &mut self, -2564 now: Instant, -2565 space: SpaceId, -2566 ack: frame::Ack, -2567 ) -> Result<(), TransportError> { -2568 // All ACKs are referencing path 0 -2569 let path = PathId::ZERO; -2570 self.inner_on_ack_received(now, space, path, ack) -2571 } -2572 -2573 fn on_path_ack_received( -2574 &mut self, -2575 now: Instant, -2576 space: SpaceId, -2577 path_ack: frame::PathAck, -2578 ) -> Result<(), TransportError> { -2579 let (ack, path) = path_ack.into_ack(); -2580 self.inner_on_ack_received(now, space, path, ack) -2581 } -2582 -2583 /// Handles an ACK frame acknowledging packets sent on *path*. -2584 fn inner_on_ack_received( -2585 &mut self, -2586 now: Instant, -2587 space: SpaceId, -2588 path: PathId, -2589 ack: frame::Ack, -2590 ) -> Result<(), TransportError> { -2591 if self.abandoned_paths.contains(&path) { -2592 // See also https://www.ietf.org/archive/id/draft-ietf-quic-multipath-17.html#section-3.4.3-3 -2593 // > PATH_ACK frames received with an abandoned path ID are silently ignored, as specified in Section 4. -2594 trace!("silently ignoring PATH_ACK on abandoned path"); -2595 return Ok(()); -2596 } -2597 if ack.largest >= self.spaces[space].for_path(path).next_packet_number { -2598 return Err(TransportError::PROTOCOL_VIOLATION("unsent packet acked")); -2599 } -2600 let new_largest = { -2601 let space = &mut self.spaces[space].for_path(path); -2602 if space.largest_acked_packet.is_none_or(|pn| ack.largest > pn) { -2603 space.largest_acked_packet = Some(ack.largest); -2604 if let Some(info) = space.sent_packets.get(ack.largest) { -2605 // This should always succeed, but a misbehaving peer might ACK a packet we -2606 // haven't sent. At worst, that will result in us spuriously reducing the -2607 // congestion window. -2608 space.largest_acked_packet_sent = info.time_sent; -2609 } -2610 true -2611 } else { -2612 false -2613 } -2614 }; -2615 -2616 if self.detect_spurious_loss(&ack, space, path) { -2617 self.path_data_mut(path) -2618 .congestion -2619 .on_spurious_congestion_event(); -2620 } -2621 -2622 // Avoid DoS from unreasonably huge ack ranges by filtering out just the new acks. -2623 let mut newly_acked = ArrayRangeSet::new(); -2624 for range in ack.iter() { -2625 self.spaces[space].for_path(path).check_ack(range.clone())?; -2626 for (pn, _) in self.spaces[space] -2627 .for_path(path) -2628 .sent_packets -2629 .iter_range(range) -2630 { -2631 newly_acked.insert_one(pn); -2632 } -2633 } -2634 -2635 if newly_acked.is_empty() { -2636 return Ok(()); -2637 } -2638 -2639 let mut ack_eliciting_acked = false; -2640 for packet in newly_acked.elts() { -2641 if let Some(info) = self.spaces[space].for_path(path).take(packet) { -2642 for (acked_path_id, acked_pn) in info.largest_acked.iter() { -2643 // Assume ACKs for all packets below the largest acknowledged in -2644 // `packet` have been received. This can cause the peer to spuriously -2645 // retransmit if some of our earlier ACKs were lost, but allows for -2646 // simpler state tracking. See discussion at -2647 // https://www.rfc-editor.org/rfc/rfc9000.html#name-limiting-ranges-by-tracking -2648 if let Some(pns) = self.spaces[space].path_space_mut(*acked_path_id) { -2649 pns.pending_acks.subtract_below(*acked_pn); -2650 } -2651 } -2652 ack_eliciting_acked |= info.ack_eliciting; -2653 -2654 // Notify MTU discovery that a packet was acked, because it might be an MTU probe -2655 let path_data = self.path_data_mut(path); -2656 let mtu_updated = path_data.mtud.on_acked(space, packet, info.size); -2657 if mtu_updated { -2658 path_data -2659 .congestion -2660 .on_mtu_update(path_data.mtud.current_mtu()); -2661 } +2540 /// Current number of remotely initiated streams that may be concurrently open +2541 /// +2542 /// If the target for this limit is reduced using [`set_max_concurrent_streams`](Self::set_max_concurrent_streams), +2543 /// it will not change immediately, even if fewer streams are open. Instead, it will +2544 /// decrement by one for each time a remotely initiated stream of matching directionality is closed. +2545 pub fn max_concurrent_streams(&self, dir: Dir) -> u64 { +2546 self.streams.max_concurrent(dir) +2547 } +2548 +2549 /// See [`TransportConfig::send_window()`] +2550 pub fn set_send_window(&mut self, send_window: u64) { +2551 self.streams.set_send_window(send_window); +2552 } +2553 +2554 /// See [`TransportConfig::receive_window()`] +2555 pub fn set_receive_window(&mut self, receive_window: VarInt) { +2556 if self.streams.set_receive_window(receive_window) { +2557 self.spaces[SpaceId::Data].pending.max_data = true; +2558 } +2559 } +2560 +2561 /// Whether the Multipath for QUIC extension is enabled. +2562 /// +2563 /// Multipath is only enabled after the handshake is completed and if it was enabled by both +2564 /// peers. +2565 pub fn is_multipath_negotiated(&self) -> bool { +2566 !self.is_handshaking() +2567 && self.config.max_concurrent_multipath_paths.is_some() +2568 && self.peer_params.initial_max_path_id.is_some() +2569 } +2570 +2571 fn on_ack_received( +2572 &mut self, +2573 now: Instant, +2574 space: SpaceId, +2575 ack: frame::Ack, +2576 ) -> Result<(), TransportError> { +2577 // All ACKs are referencing path 0 +2578 let path = PathId::ZERO; +2579 self.inner_on_ack_received(now, space, path, ack) +2580 } +2581 +2582 fn on_path_ack_received( +2583 &mut self, +2584 now: Instant, +2585 space: SpaceId, +2586 path_ack: frame::PathAck, +2587 ) -> Result<(), TransportError> { +2588 let (ack, path) = path_ack.into_ack(); +2589 self.inner_on_ack_received(now, space, path, ack) +2590 } +2591 +2592 /// Handles an ACK frame acknowledging packets sent on *path*. +2593 fn inner_on_ack_received( +2594 &mut self, +2595 now: Instant, +2596 space: SpaceId, +2597 path: PathId, +2598 ack: frame::Ack, +2599 ) -> Result<(), TransportError> { +2600 if self.abandoned_paths.contains(&path) { +2601 // See also https://www.ietf.org/archive/id/draft-ietf-quic-multipath-17.html#section-3.4.3-3 +2602 // > PATH_ACK frames received with an abandoned path ID are silently ignored, as specified in Section 4. +2603 trace!("silently ignoring PATH_ACK on abandoned path"); +2604 return Ok(()); +2605 } +2606 if ack.largest >= self.spaces[space].for_path(path).next_packet_number { +2607 return Err(TransportError::PROTOCOL_VIOLATION("unsent packet acked")); +2608 } +2609 let new_largest = { +2610 let space = &mut self.spaces[space].for_path(path); +2611 if space.largest_acked_packet.is_none_or(|pn| ack.largest > pn) { +2612 space.largest_acked_packet = Some(ack.largest); +2613 if let Some(info) = space.sent_packets.get(ack.largest) { +2614 // This should always succeed, but a misbehaving peer might ACK a packet we +2615 // haven't sent. At worst, that will result in us spuriously reducing the +2616 // congestion window. +2617 space.largest_acked_packet_sent = info.time_sent; +2618 } +2619 true +2620 } else { +2621 false +2622 } +2623 }; +2624 +2625 if self.detect_spurious_loss(&ack, space, path) { +2626 self.path_data_mut(path) +2627 .congestion +2628 .on_spurious_congestion_event(); +2629 } +2630 +2631 // Avoid DoS from unreasonably huge ack ranges by filtering out just the new acks. +2632 let mut newly_acked = ArrayRangeSet::new(); +2633 for range in ack.iter() { +2634 self.spaces[space].for_path(path).check_ack(range.clone())?; +2635 for (pn, _) in self.spaces[space] +2636 .for_path(path) +2637 .sent_packets +2638 .iter_range(range) +2639 { +2640 newly_acked.insert_one(pn); +2641 } +2642 } +2643 +2644 if newly_acked.is_empty() { +2645 return Ok(()); +2646 } +2647 +2648 let mut ack_eliciting_acked = false; +2649 for packet in newly_acked.elts() { +2650 if let Some(info) = self.spaces[space].for_path(path).take(packet) { +2651 for (acked_path_id, acked_pn) in info.largest_acked.iter() { +2652 // Assume ACKs for all packets below the largest acknowledged in +2653 // `packet` have been received. This can cause the peer to spuriously +2654 // retransmit if some of our earlier ACKs were lost, but allows for +2655 // simpler state tracking. See discussion at +2656 // https://www.rfc-editor.org/rfc/rfc9000.html#name-limiting-ranges-by-tracking +2657 if let Some(pns) = self.spaces[space].path_space_mut(*acked_path_id) { +2658 pns.pending_acks.subtract_below(*acked_pn); +2659 } +2660 } +2661 ack_eliciting_acked |= info.ack_eliciting; 2662 -2663 // Notify ack frequency that a packet was acked, because it might contain an ACK_FREQUENCY frame -2664 self.ack_frequency.on_acked(path, packet); -2665 -2666 self.on_packet_acked(now, path, info); -2667 } -2668 } -2669 -2670 let largest_ackd = self.spaces[space].for_path(path).largest_acked_packet; -2671 let app_limited = self.app_limited; -2672 let path_data = self.path_data_mut(path); -2673 let in_flight = path_data.in_flight.bytes; +2663 // Notify MTU discovery that a packet was acked, because it might be an MTU probe +2664 let path_data = self.path_data_mut(path); +2665 let mtu_updated = path_data.mtud.on_acked(space, packet, info.size); +2666 if mtu_updated { +2667 path_data +2668 .congestion +2669 .on_mtu_update(path_data.mtud.current_mtu()); +2670 } +2671 +2672 // Notify ack frequency that a packet was acked, because it might contain an ACK_FREQUENCY frame +2673 self.ack_frequency.on_acked(path, packet); 2674 -2675 path_data -2676 .congestion -2677 .on_end_acks(now, in_flight, app_limited, largest_ackd); +2675 self.on_packet_acked(now, path, info); +2676 } +2677 } 2678 -2679 if new_largest && ack_eliciting_acked { -2680 let ack_delay = if space != SpaceId::Data { -2681 Duration::from_micros(0) -2682 } else { -2683 cmp::min( -2684 self.ack_frequency.peer_max_ack_delay, -2685 Duration::from_micros(ack.delay << self.peer_params.ack_delay_exponent.0), -2686 ) -2687 }; -2688 let rtt = now.saturating_duration_since( -2689 self.spaces[space].for_path(path).largest_acked_packet_sent, -2690 ); -2691 -2692 let next_pn = self.spaces[space].for_path(path).next_packet_number; -2693 let path_data = self.path_data_mut(path); -2694 // TODO(@divma): should be a method of path, should be contained in a single place -2695 path_data.rtt.update(ack_delay, rtt); -2696 if path_data.first_packet_after_rtt_sample.is_none() { -2697 path_data.first_packet_after_rtt_sample = Some((space, next_pn)); -2698 } -2699 } +2679 let largest_ackd = self.spaces[space].for_path(path).largest_acked_packet; +2680 let app_limited = self.app_limited; +2681 let path_data = self.path_data_mut(path); +2682 let in_flight = path_data.in_flight.bytes; +2683 +2684 path_data +2685 .congestion +2686 .on_end_acks(now, in_flight, app_limited, largest_ackd); +2687 +2688 if new_largest && ack_eliciting_acked { +2689 let ack_delay = if space != SpaceId::Data { +2690 Duration::from_micros(0) +2691 } else { +2692 cmp::min( +2693 self.ack_frequency.peer_max_ack_delay, +2694 Duration::from_micros(ack.delay << self.peer_params.ack_delay_exponent.0), +2695 ) +2696 }; +2697 let rtt = now.saturating_duration_since( +2698 self.spaces[space].for_path(path).largest_acked_packet_sent, +2699 ); 2700 -2701 // Must be called before crypto/pto_count are clobbered -2702 self.detect_lost_packets(now, space, path, true); -2703 -2704 if self.peer_completed_address_validation(path) { -2705 self.path_data_mut(path).pto_count = 0; -2706 } -2707 -2708 // Explicit congestion notification -2709 // TODO(@divma): this code is a good example of logic that should be contained in a single -2710 // place but it's split between the path data and the packet number space data, we should -2711 // find a way to make this work without two lookups -2712 if self.path_data(path).sending_ecn { -2713 if let Some(ecn) = ack.ecn { -2714 // We only examine ECN counters from ACKs that we are certain we received in transmit -2715 // order, allowing us to compute an increase in ECN counts to compare against the number -2716 // of newly acked packets that remains well-defined in the presence of arbitrary packet -2717 // reordering. -2718 if new_largest { -2719 let sent = self.spaces[space].for_path(path).largest_acked_packet_sent; -2720 self.process_ecn(now, space, path, newly_acked.len() as u64, ecn, sent); -2721 } -2722 } else { -2723 // We always start out sending ECN, so any ack that doesn't acknowledge it disables it. -2724 debug!("ECN not acknowledged by peer"); -2725 self.path_data_mut(path).sending_ecn = false; -2726 } -2727 } -2728 -2729 self.set_loss_detection_timer(now, path); -2730 Ok(()) -2731 } -2732 -2733 fn detect_spurious_loss(&mut self, ack: &frame::Ack, space: SpaceId, path: PathId) -> bool { -2734 let lost_packets = &mut self.spaces[space].for_path(path).lost_packets; -2735 -2736 if lost_packets.is_empty() { -2737 return false; -2738 } -2739 -2740 for range in ack.iter() { -2741 let spurious_losses: Vec<u64> = lost_packets -2742 .iter_range(range.clone()) -2743 .map(|(pn, _info)| pn) -2744 .collect(); -2745 -2746 for pn in spurious_losses { -2747 lost_packets.remove(pn); -2748 } -2749 } -2750 -2751 // If this ACK frame acknowledged all deemed lost packets, -2752 // then we have raised a spurious congestion event in the past. -2753 // We cannot conclude when there are remaining packets, -2754 // but future ACK frames might indicate a spurious loss detection. -2755 lost_packets.is_empty() -2756 } -2757 -2758 /// Drain lost packets that we reasonably think will never arrive -2759 /// -2760 /// The current criterion is copied from `msquic`: -2761 /// discard packets that were sent earlier than 2 probe timeouts ago. -2762 fn drain_lost_packets(&mut self, now: Instant, space: SpaceId, path: PathId) { -2763 let two_pto = 2 * self.path_data(path).rtt.pto_base(); -2764 -2765 let lost_packets = &mut self.spaces[space].for_path(path).lost_packets; -2766 lost_packets.retain(|_pn, info| now.saturating_duration_since(info.time_sent) <= two_pto); -2767 } -2768 -2769 /// Process a new ECN block from an in-order ACK -2770 fn process_ecn( -2771 &mut self, -2772 now: Instant, -2773 space: SpaceId, -2774 path: PathId, -2775 newly_acked: u64, -2776 ecn: frame::EcnCounts, -2777 largest_sent_time: Instant, -2778 ) { -2779 match self.spaces[space] -2780 .for_path(path) -2781 .detect_ecn(newly_acked, ecn) -2782 { -2783 Err(e) => { -2784 debug!("halting ECN due to verification failure: {}", e); -2785 -2786 self.path_data_mut(path).sending_ecn = false; -2787 // Wipe out the existing value because it might be garbage and could interfere with -2788 // future attempts to use ECN on new paths. -2789 self.spaces[space].for_path(path).ecn_feedback = frame::EcnCounts::ZERO; -2790 } -2791 Ok(false) => {} -2792 Ok(true) => { -2793 self.path_stats.entry(path).or_default().congestion_events += 1; -2794 self.path_data_mut(path).congestion.on_congestion_event( -2795 now, -2796 largest_sent_time, -2797 false, -2798 true, -2799 0, -2800 ); -2801 } -2802 } -2803 } -2804 -2805 // Not timing-aware, so it's safe to call this for inferred acks, such as arise from -2806 // high-latency handshakes -2807 fn on_packet_acked(&mut self, now: Instant, path_id: PathId, info: SentPacket) { -2808 self.paths -2809 .get_mut(&path_id) -2810 .expect("known path") -2811 .remove_in_flight(&info); -2812 let app_limited = self.app_limited; -2813 let path = self.path_data_mut(path_id); -2814 if info.ack_eliciting && !path.is_validating_path() { -2815 // Only pass ACKs to the congestion controller if we are not validating the current -2816 // path, so as to ignore any ACKs from older paths still coming in. -2817 let rtt = path.rtt; -2818 path.congestion -2819 .on_ack(now, info.time_sent, info.size.into(), app_limited, &rtt); -2820 } -2821 -2822 // Update state for confirmed delivery of frames -2823 if let Some(retransmits) = info.retransmits.get() { -2824 for (id, _) in retransmits.reset_stream.iter() { -2825 self.streams.reset_acked(*id); -2826 } -2827 } -2828 -2829 for frame in info.stream_frames { -2830 self.streams.received_ack_of(frame); -2831 } -2832 } -2833 -2834 fn set_key_discard_timer(&mut self, now: Instant, space: SpaceId) { -2835 let start = if self.zero_rtt_crypto.is_some() { -2836 now -2837 } else { -2838 self.prev_crypto -2839 .as_ref() -2840 .expect("no previous keys") -2841 .end_packet -2842 .as_ref() -2843 .expect("update not acknowledged yet") -2844 .1 -2845 }; -2846 -2847 // QUIC-MULTIPATH § 2.5 Key Phase Update Process: use largest PTO off all paths. -2848 self.timers.set( -2849 Timer::Conn(ConnTimer::KeyDiscard), -2850 start + self.pto_max_path(space, false) * 3, -2851 self.qlog.with_time(now), -2852 ); -2853 } -2854 -2855 /// Handle a [`PathTimer::LossDetection`] timeout. -2856 /// -2857 /// This timer expires for two reasons: -2858 /// - An ACK-eliciting packet we sent should be considered lost. -2859 /// - The PTO may have expired and a tail-loss probe needs to be scheduled. -2860 /// -2861 /// The former needs us to schedule re-transmission of the lost data. -2862 /// -2863 /// The latter means we have not received an ACK for an ack-eliciting packet we sent -2864 /// within the PTO time-window. We need to schedule a tail-loss probe, an ack-eliciting -2865 /// packet, to try and elicit new acknowledgements. These new acknowledgements will -2866 /// indicate whether the previously sent packets were lost or not. -2867 fn on_loss_detection_timeout(&mut self, now: Instant, path_id: PathId) { -2868 if let Some((_, pn_space)) = self.loss_time_and_space(path_id) { -2869 // Time threshold loss Detection -2870 self.detect_lost_packets(now, pn_space, path_id, false); -2871 self.set_loss_detection_timer(now, path_id); -2872 return; -2873 } -2874 -2875 let (_, space) = match self.pto_time_and_space(now, path_id) { -2876 Some(x) => x, -2877 None => { -2878 error!(%path_id, "PTO expired while unset"); -2879 return; -2880 } -2881 }; -2882 trace!( -2883 in_flight = self.path_data(path_id).in_flight.bytes, -2884 count = self.path_data(path_id).pto_count, -2885 ?space, -2886 %path_id, -2887 "PTO fired" -2888 ); -2889 -2890 let count = match self.path_data(path_id).in_flight.ack_eliciting { -2891 // A PTO when we're not expecting any ACKs must be due to handshake anti-amplification -2892 // deadlock preventions -2893 0 => { -2894 debug_assert!(!self.peer_completed_address_validation(path_id)); -2895 1 -2896 } -2897 // Conventional loss probe -2898 _ => 2, -2899 }; -2900 let pns = self.spaces[space].for_path(path_id); -2901 pns.loss_probes = pns.loss_probes.saturating_add(count); -2902 let path_data = self.path_data_mut(path_id); -2903 path_data.pto_count = path_data.pto_count.saturating_add(1); -2904 self.set_loss_detection_timer(now, path_id); -2905 } -2906 -2907 /// Detect any lost packets -2908 /// -2909 /// There are two cases in which we detects lost packets: -2910 /// -2911 /// - We received an ACK packet. -2912 /// - The [`PathTimer::LossDetection`] timer expired. So there is an un-acknowledged packet -2913 /// that was followed by an acknowledged packet. The loss timer for this -2914 /// un-acknowledged packet expired and we need to detect that packet as lost. -2915 /// -2916 /// Packets are lost if they are both (See RFC9002 §6.1): +2701 let next_pn = self.spaces[space].for_path(path).next_packet_number; +2702 let path_data = self.path_data_mut(path); +2703 // TODO(@divma): should be a method of path, should be contained in a single place +2704 path_data.rtt.update(ack_delay, rtt); +2705 if path_data.first_packet_after_rtt_sample.is_none() { +2706 path_data.first_packet_after_rtt_sample = Some((space, next_pn)); +2707 } +2708 } +2709 +2710 // Must be called before crypto/pto_count are clobbered +2711 self.detect_lost_packets(now, space, path, true); +2712 +2713 if self.peer_completed_address_validation(path) { +2714 self.path_data_mut(path).pto_count = 0; +2715 } +2716 +2717 // Explicit congestion notification +2718 // TODO(@divma): this code is a good example of logic that should be contained in a single +2719 // place but it's split between the path data and the packet number space data, we should +2720 // find a way to make this work without two lookups +2721 if self.path_data(path).sending_ecn { +2722 if let Some(ecn) = ack.ecn { +2723 // We only examine ECN counters from ACKs that we are certain we received in transmit +2724 // order, allowing us to compute an increase in ECN counts to compare against the number +2725 // of newly acked packets that remains well-defined in the presence of arbitrary packet +2726 // reordering. +2727 if new_largest { +2728 let sent = self.spaces[space].for_path(path).largest_acked_packet_sent; +2729 self.process_ecn(now, space, path, newly_acked.len() as u64, ecn, sent); +2730 } +2731 } else { +2732 // We always start out sending ECN, so any ack that doesn't acknowledge it disables it. +2733 debug!("ECN not acknowledged by peer"); +2734 self.path_data_mut(path).sending_ecn = false; +2735 } +2736 } +2737 +2738 self.set_loss_detection_timer(now, path); +2739 Ok(()) +2740 } +2741 +2742 fn detect_spurious_loss(&mut self, ack: &frame::Ack, space: SpaceId, path: PathId) -> bool { +2743 let lost_packets = &mut self.spaces[space].for_path(path).lost_packets; +2744 +2745 if lost_packets.is_empty() { +2746 return false; +2747 } +2748 +2749 for range in ack.iter() { +2750 let spurious_losses: Vec<u64> = lost_packets +2751 .iter_range(range.clone()) +2752 .map(|(pn, _info)| pn) +2753 .collect(); +2754 +2755 for pn in spurious_losses { +2756 lost_packets.remove(pn); +2757 } +2758 } +2759 +2760 // If this ACK frame acknowledged all deemed lost packets, +2761 // then we have raised a spurious congestion event in the past. +2762 // We cannot conclude when there are remaining packets, +2763 // but future ACK frames might indicate a spurious loss detection. +2764 lost_packets.is_empty() +2765 } +2766 +2767 /// Drain lost packets that we reasonably think will never arrive +2768 /// +2769 /// The current criterion is copied from `msquic`: +2770 /// discard packets that were sent earlier than 2 probe timeouts ago. +2771 fn drain_lost_packets(&mut self, now: Instant, space: SpaceId, path: PathId) { +2772 let two_pto = 2 * self.path_data(path).rtt.pto_base(); +2773 +2774 let lost_packets = &mut self.spaces[space].for_path(path).lost_packets; +2775 lost_packets.retain(|_pn, info| now.saturating_duration_since(info.time_sent) <= two_pto); +2776 } +2777 +2778 /// Process a new ECN block from an in-order ACK +2779 fn process_ecn( +2780 &mut self, +2781 now: Instant, +2782 space: SpaceId, +2783 path: PathId, +2784 newly_acked: u64, +2785 ecn: frame::EcnCounts, +2786 largest_sent_time: Instant, +2787 ) { +2788 match self.spaces[space] +2789 .for_path(path) +2790 .detect_ecn(newly_acked, ecn) +2791 { +2792 Err(e) => { +2793 debug!("halting ECN due to verification failure: {}", e); +2794 +2795 self.path_data_mut(path).sending_ecn = false; +2796 // Wipe out the existing value because it might be garbage and could interfere with +2797 // future attempts to use ECN on new paths. +2798 self.spaces[space].for_path(path).ecn_feedback = frame::EcnCounts::ZERO; +2799 } +2800 Ok(false) => {} +2801 Ok(true) => { +2802 self.path_stats.entry(path).or_default().congestion_events += 1; +2803 self.path_data_mut(path).congestion.on_congestion_event( +2804 now, +2805 largest_sent_time, +2806 false, +2807 true, +2808 0, +2809 ); +2810 } +2811 } +2812 } +2813 +2814 // Not timing-aware, so it's safe to call this for inferred acks, such as arise from +2815 // high-latency handshakes +2816 fn on_packet_acked(&mut self, now: Instant, path_id: PathId, info: SentPacket) { +2817 self.paths +2818 .get_mut(&path_id) +2819 .expect("known path") +2820 .remove_in_flight(&info); +2821 let app_limited = self.app_limited; +2822 let path = self.path_data_mut(path_id); +2823 if info.ack_eliciting && !path.is_validating_path() { +2824 // Only pass ACKs to the congestion controller if we are not validating the current +2825 // path, so as to ignore any ACKs from older paths still coming in. +2826 let rtt = path.rtt; +2827 path.congestion +2828 .on_ack(now, info.time_sent, info.size.into(), app_limited, &rtt); +2829 } +2830 +2831 // Update state for confirmed delivery of frames +2832 if let Some(retransmits) = info.retransmits.get() { +2833 for (id, _) in retransmits.reset_stream.iter() { +2834 self.streams.reset_acked(*id); +2835 } +2836 } +2837 +2838 for frame in info.stream_frames { +2839 self.streams.received_ack_of(frame); +2840 } +2841 } +2842 +2843 fn set_key_discard_timer(&mut self, now: Instant, space: SpaceId) { +2844 let start = if self.zero_rtt_crypto.is_some() { +2845 now +2846 } else { +2847 self.prev_crypto +2848 .as_ref() +2849 .expect("no previous keys") +2850 .end_packet +2851 .as_ref() +2852 .expect("update not acknowledged yet") +2853 .1 +2854 }; +2855 +2856 // QUIC-MULTIPATH § 2.5 Key Phase Update Process: use largest PTO off all paths. +2857 self.timers.set( +2858 Timer::Conn(ConnTimer::KeyDiscard), +2859 start + self.pto_max_path(space, false) * 3, +2860 self.qlog.with_time(now), +2861 ); +2862 } +2863 +2864 /// Handle a [`PathTimer::LossDetection`] timeout. +2865 /// +2866 /// This timer expires for two reasons: +2867 /// - An ACK-eliciting packet we sent should be considered lost. +2868 /// - The PTO may have expired and a tail-loss probe needs to be scheduled. +2869 /// +2870 /// The former needs us to schedule re-transmission of the lost data. +2871 /// +2872 /// The latter means we have not received an ACK for an ack-eliciting packet we sent +2873 /// within the PTO time-window. We need to schedule a tail-loss probe, an ack-eliciting +2874 /// packet, to try and elicit new acknowledgements. These new acknowledgements will +2875 /// indicate whether the previously sent packets were lost or not. +2876 fn on_loss_detection_timeout(&mut self, now: Instant, path_id: PathId) { +2877 if let Some((_, pn_space)) = self.loss_time_and_space(path_id) { +2878 // Time threshold loss Detection +2879 self.detect_lost_packets(now, pn_space, path_id, false); +2880 self.set_loss_detection_timer(now, path_id); +2881 return; +2882 } +2883 +2884 let (_, space) = match self.pto_time_and_space(now, path_id) { +2885 Some(x) => x, +2886 None => { +2887 error!(%path_id, "PTO expired while unset"); +2888 return; +2889 } +2890 }; +2891 trace!( +2892 in_flight = self.path_data(path_id).in_flight.bytes, +2893 count = self.path_data(path_id).pto_count, +2894 ?space, +2895 %path_id, +2896 "PTO fired" +2897 ); +2898 +2899 let count = match self.path_data(path_id).in_flight.ack_eliciting { +2900 // A PTO when we're not expecting any ACKs must be due to handshake anti-amplification +2901 // deadlock preventions +2902 0 => { +2903 debug_assert!(!self.peer_completed_address_validation(path_id)); +2904 1 +2905 } +2906 // Conventional loss probe +2907 _ => 2, +2908 }; +2909 let pns = self.spaces[space].for_path(path_id); +2910 pns.loss_probes = pns.loss_probes.saturating_add(count); +2911 let path_data = self.path_data_mut(path_id); +2912 path_data.pto_count = path_data.pto_count.saturating_add(1); +2913 self.set_loss_detection_timer(now, path_id); +2914 } +2915 +2916 /// Detect any lost packets 2917 /// -2918 /// - Unacknowledged, in flight and sent prior to an acknowledged packet. -2919 /// - Old enough by either: -2920 /// - Having a packet number [`TransportConfig::packet_threshold`] lower then the last -2921 /// acknowledged packet. -2922 /// - Being sent [`TransportConfig::time_threshold`] * RTT in the past. -2923 fn detect_lost_packets( -2924 &mut self, -2925 now: Instant, -2926 pn_space: SpaceId, -2927 path_id: PathId, -2928 due_to_ack: bool, -2929 ) { -2930 let mut lost_packets = Vec::<u64>::new(); -2931 let mut lost_mtu_probe = None; -2932 let mut in_persistent_congestion = false; -2933 let mut size_of_lost_packets = 0u64; -2934 self.spaces[pn_space].for_path(path_id).loss_time = None; -2935 -2936 // Find all the lost packets, populating all variables initialised above. -2937 -2938 let path = self.path_data(path_id); -2939 let in_flight_mtu_probe = path.mtud.in_flight_mtu_probe(); -2940 let loss_delay = path -2941 .rtt -2942 .conservative() -2943 .mul_f32(self.config.time_threshold) -2944 .max(TIMER_GRANULARITY); -2945 let first_packet_after_rtt_sample = path.first_packet_after_rtt_sample; +2918 /// There are two cases in which we detects lost packets: +2919 /// +2920 /// - We received an ACK packet. +2921 /// - The [`PathTimer::LossDetection`] timer expired. So there is an un-acknowledged packet +2922 /// that was followed by an acknowledged packet. The loss timer for this +2923 /// un-acknowledged packet expired and we need to detect that packet as lost. +2924 /// +2925 /// Packets are lost if they are both (See RFC9002 §6.1): +2926 /// +2927 /// - Unacknowledged, in flight and sent prior to an acknowledged packet. +2928 /// - Old enough by either: +2929 /// - Having a packet number [`TransportConfig::packet_threshold`] lower then the last +2930 /// acknowledged packet. +2931 /// - Being sent [`TransportConfig::time_threshold`] * RTT in the past. +2932 fn detect_lost_packets( +2933 &mut self, +2934 now: Instant, +2935 pn_space: SpaceId, +2936 path_id: PathId, +2937 due_to_ack: bool, +2938 ) { +2939 let mut lost_packets = Vec::<u64>::new(); +2940 let mut lost_mtu_probe = None; +2941 let mut in_persistent_congestion = false; +2942 let mut size_of_lost_packets = 0u64; +2943 self.spaces[pn_space].for_path(path_id).loss_time = None; +2944 +2945 // Find all the lost packets, populating all variables initialised above. 2946 -2947 let largest_acked_packet = self.spaces[pn_space] -2948 .for_path(path_id) -2949 .largest_acked_packet -2950 .expect("detect_lost_packets only to be called if path received at least one ACK"); -2951 let packet_threshold = self.config.packet_threshold as u64; -2952 -2953 // InPersistentCongestion: Determine if all packets in the time period before the newest -2954 // lost packet, including the edges, are marked lost. PTO computation must always -2955 // include max ACK delay, i.e. operate as if in Data space (see RFC9001 §7.6.1). -2956 let congestion_period = self -2957 .pto(SpaceId::Data, path_id) -2958 .saturating_mul(self.config.persistent_congestion_threshold); -2959 let mut persistent_congestion_start: Option<Instant> = None; -2960 let mut prev_packet = None; -2961 let space = self.spaces[pn_space].for_path(path_id); -2962 -2963 for (packet, info) in space.sent_packets.iter_range(0..largest_acked_packet) { -2964 if prev_packet != Some(packet.wrapping_sub(1)) { -2965 // An intervening packet was acknowledged -2966 persistent_congestion_start = None; -2967 } -2968 -2969 // Packets sent before now - loss_delay are deemed lost. -2970 // However, we avoid subtraction as it can panic and there's no -2971 // saturating equivalent of this subtraction operation with a Duration. -2972 let packet_too_old = now.saturating_duration_since(info.time_sent) >= loss_delay; -2973 if packet_too_old || largest_acked_packet >= packet + packet_threshold { -2974 // The packet should be declared lost. -2975 if Some(packet) == in_flight_mtu_probe { -2976 // Lost MTU probes are not included in `lost_packets`, because they -2977 // should not trigger a congestion control response -2978 lost_mtu_probe = in_flight_mtu_probe; -2979 } else { -2980 lost_packets.push(packet); -2981 size_of_lost_packets += info.size as u64; -2982 if info.ack_eliciting && due_to_ack { -2983 match persistent_congestion_start { -2984 // Two ACK-eliciting packets lost more than -2985 // congestion_period apart, with no ACKed packets in between -2986 Some(start) if info.time_sent - start > congestion_period => { -2987 in_persistent_congestion = true; -2988 } -2989 // Persistent congestion must start after the first RTT sample -2990 None if first_packet_after_rtt_sample -2991 .is_some_and(|x| x < (pn_space, packet)) => -2992 { -2993 persistent_congestion_start = Some(info.time_sent); -2994 } -2995 _ => {} -2996 } -2997 } -2998 } -2999 } else { -3000 // The packet should not yet be declared lost. -3001 if space.loss_time.is_none() { -3002 // Since we iterate in order the lowest packet number's loss time will -3003 // always be the earliest. -3004 space.loss_time = Some(info.time_sent + loss_delay); -3005 } -3006 persistent_congestion_start = None; -3007 } -3008 -3009 prev_packet = Some(packet); -3010 } -3011 -3012 self.handle_lost_packets( -3013 pn_space, -3014 path_id, -3015 now, -3016 lost_packets, -3017 lost_mtu_probe, -3018 loss_delay, -3019 in_persistent_congestion, -3020 size_of_lost_packets, -3021 ); -3022 } -3023 -3024 /// Drops the path state, declaring any remaining in-flight packets as lost -3025 fn discard_path(&mut self, path_id: PathId, now: Instant) { -3026 trace!(%path_id, "dropping path state"); -3027 let path = self.path_data(path_id); -3028 let in_flight_mtu_probe = path.mtud.in_flight_mtu_probe(); -3029 -3030 let mut size_of_lost_packets = 0u64; // add to path_stats.lost_bytes; -3031 let lost_pns: Vec<_> = self.spaces[SpaceId::Data] -3032 .for_path(path_id) -3033 .sent_packets -3034 .iter() -3035 .filter(|(pn, _info)| Some(*pn) != in_flight_mtu_probe) -3036 .map(|(pn, info)| { -3037 size_of_lost_packets += info.size as u64; -3038 pn -3039 }) -3040 .collect(); -3041 -3042 if !lost_pns.is_empty() { -3043 trace!( -3044 %path_id, -3045 count = lost_pns.len(), -3046 lost_bytes = size_of_lost_packets, -3047 "packets lost on path abandon" -3048 ); -3049 self.handle_lost_packets( -3050 SpaceId::Data, -3051 path_id, -3052 now, -3053 lost_pns, -3054 in_flight_mtu_probe, -3055 Duration::ZERO, -3056 false, -3057 size_of_lost_packets, -3058 ); -3059 } -3060 self.paths.remove(&path_id); -3061 self.spaces[SpaceId::Data].number_spaces.remove(&path_id); -3062 -3063 let path_stats = self.path_stats.remove(&path_id).unwrap_or_default(); -3064 self.events.push_back( -3065 PathEvent::Abandoned { -3066 id: path_id, -3067 path_stats, -3068 } -3069 .into(), -3070 ); -3071 } -3072 -3073 fn handle_lost_packets( -3074 &mut self, -3075 pn_space: SpaceId, -3076 path_id: PathId, -3077 now: Instant, -3078 lost_packets: Vec<u64>, -3079 lost_mtu_probe: Option<u64>, -3080 loss_delay: Duration, -3081 in_persistent_congestion: bool, -3082 size_of_lost_packets: u64, -3083 ) { -3084 debug_assert!( -3085 { -3086 let mut sorted = lost_packets.clone(); -3087 sorted.sort(); -3088 sorted == lost_packets -3089 }, -3090 "lost_packets must be sorted" -3091 ); -3092 -3093 self.drain_lost_packets(now, pn_space, path_id); -3094 -3095 // OnPacketsLost -3096 if let Some(largest_lost) = lost_packets.last().cloned() { -3097 let old_bytes_in_flight = self.path_data_mut(path_id).in_flight.bytes; -3098 let largest_lost_sent = self.spaces[pn_space] -3099 .for_path(path_id) -3100 .sent_packets -3101 .get(largest_lost) -3102 .unwrap() -3103 .time_sent; -3104 let path_stats = self.path_stats.entry(path_id).or_default(); -3105 path_stats.lost_packets += lost_packets.len() as u64; -3106 path_stats.lost_bytes += size_of_lost_packets; -3107 trace!( -3108 %path_id, -3109 count = lost_packets.len(), -3110 lost_bytes = size_of_lost_packets, -3111 "packets lost", -3112 ); -3113 -3114 for &packet in &lost_packets { -3115 let Some(info) = self.spaces[pn_space].for_path(path_id).take(packet) else { -3116 continue; -3117 }; -3118 self.qlog -3119 .emit_packet_lost(packet, &info, loss_delay, pn_space, now); -3120 self.paths -3121 .get_mut(&path_id) -3122 .unwrap() -3123 .remove_in_flight(&info); -3124 -3125 for frame in info.stream_frames { -3126 self.streams.retransmit(frame); -3127 } -3128 self.spaces[pn_space].pending |= info.retransmits; -3129 self.path_data_mut(path_id) -3130 .mtud -3131 .on_non_probe_lost(packet, info.size); -3132 -3133 self.spaces[pn_space].for_path(path_id).lost_packets.insert( -3134 packet, -3135 LostPacket { -3136 time_sent: info.time_sent, -3137 }, -3138 ); -3139 } -3140 -3141 let path = self.path_data_mut(path_id); -3142 if path.mtud.black_hole_detected(now) { -3143 path.congestion.on_mtu_update(path.mtud.current_mtu()); -3144 if let Some(max_datagram_size) = self.datagrams().max_size() -3145 && self.datagrams.drop_oversized(max_datagram_size) -3146 && self.datagrams.send_blocked -3147 { -3148 self.datagrams.send_blocked = false; -3149 self.events.push_back(Event::DatagramsUnblocked); -3150 } -3151 self.path_stats -3152 .entry(path_id) -3153 .or_default() -3154 .black_holes_detected += 1; -3155 } -3156 -3157 // Don't apply congestion penalty for lost ack-only packets -3158 let lost_ack_eliciting = -3159 old_bytes_in_flight != self.path_data_mut(path_id).in_flight.bytes; -3160 -3161 if lost_ack_eliciting { -3162 self.path_stats -3163 .entry(path_id) -3164 .or_default() -3165 .congestion_events += 1; -3166 self.path_data_mut(path_id).congestion.on_congestion_event( -3167 now, -3168 largest_lost_sent, -3169 in_persistent_congestion, -3170 false, -3171 size_of_lost_packets, -3172 ); -3173 } -3174 } -3175 -3176 // Handle a lost MTU probe -3177 if let Some(packet) = lost_mtu_probe { -3178 let info = self.spaces[SpaceId::Data] -3179 .for_path(path_id) -3180 .take(packet) -3181 .unwrap(); // safe: lost_mtu_probe is omitted from lost_packets, and -3182 // therefore must not have been removed yet -3183 self.paths -3184 .get_mut(&path_id) -3185 .unwrap() -3186 .remove_in_flight(&info); -3187 self.path_data_mut(path_id).mtud.on_probe_lost(); -3188 self.path_stats -3189 .entry(path_id) -3190 .or_default() -3191 .lost_plpmtud_probes += 1; -3192 } -3193 } -3194 -3195 /// Returns the earliest time packets should be declared lost for all spaces on a path. -3196 /// -3197 /// If a path has an acknowledged packet with any prior un-acknowledged packets, the -3198 /// earliest un-acknowledged packet can be declared lost after a timeout has elapsed. -3199 /// The time returned is when this packet should be declared lost. -3200 fn loss_time_and_space(&self, path_id: PathId) -> Option<(Instant, SpaceId)> { -3201 SpaceId::iter() -3202 .filter_map(|id| { -3203 self.spaces[id] -3204 .number_spaces -3205 .get(&path_id) -3206 .and_then(|pns| pns.loss_time) -3207 .map(|time| (time, id)) -3208 }) -3209 .min_by_key(|&(time, _)| time) -3210 } -3211 -3212 /// Returns the earliest next PTO should fire for all spaces on a path. -3213 fn pto_time_and_space(&mut self, now: Instant, path_id: PathId) -> Option<(Instant, SpaceId)> { -3214 let path = self.path(path_id)?; -3215 let pto_count = path.pto_count; -3216 let backoff = 2u32.pow(pto_count.min(MAX_BACKOFF_EXPONENT)); -3217 let mut duration = path.rtt.pto_base() * backoff; -3218 -3219 if path_id == PathId::ZERO -3220 && path.in_flight.ack_eliciting == 0 -3221 && !self.peer_completed_address_validation(PathId::ZERO) -3222 { -3223 // Address Validation during Connection Establishment: -3224 // https://www.rfc-editor.org/rfc/rfc9000.html#section-8.1. To prevent a -3225 // deadlock if an Initial or Handshake packet from the server is lost and the -3226 // server can not send more due to its anti-amplification limit the client must -3227 // send another packet on PTO. -3228 let space = match self.highest_space { -3229 SpaceId::Handshake => SpaceId::Handshake, -3230 _ => SpaceId::Initial, -3231 }; -3232 -3233 return Some((now + duration, space)); -3234 } -3235 -3236 let mut result = None; -3237 for space in SpaceId::iter() { -3238 let Some(pns) = self.spaces[space].number_spaces.get(&path_id) else { -3239 continue; +2947 let path = self.path_data(path_id); +2948 let in_flight_mtu_probe = path.mtud.in_flight_mtu_probe(); +2949 let loss_delay = path +2950 .rtt +2951 .conservative() +2952 .mul_f32(self.config.time_threshold) +2953 .max(TIMER_GRANULARITY); +2954 let first_packet_after_rtt_sample = path.first_packet_after_rtt_sample; +2955 +2956 let largest_acked_packet = self.spaces[pn_space] +2957 .for_path(path_id) +2958 .largest_acked_packet +2959 .expect("detect_lost_packets only to be called if path received at least one ACK"); +2960 let packet_threshold = self.config.packet_threshold as u64; +2961 +2962 // InPersistentCongestion: Determine if all packets in the time period before the newest +2963 // lost packet, including the edges, are marked lost. PTO computation must always +2964 // include max ACK delay, i.e. operate as if in Data space (see RFC9001 §7.6.1). +2965 let congestion_period = self +2966 .pto(SpaceId::Data, path_id) +2967 .saturating_mul(self.config.persistent_congestion_threshold); +2968 let mut persistent_congestion_start: Option<Instant> = None; +2969 let mut prev_packet = None; +2970 let space = self.spaces[pn_space].for_path(path_id); +2971 +2972 for (packet, info) in space.sent_packets.iter_range(0..largest_acked_packet) { +2973 if prev_packet != Some(packet.wrapping_sub(1)) { +2974 // An intervening packet was acknowledged +2975 persistent_congestion_start = None; +2976 } +2977 +2978 // Packets sent before now - loss_delay are deemed lost. +2979 // However, we avoid subtraction as it can panic and there's no +2980 // saturating equivalent of this subtraction operation with a Duration. +2981 let packet_too_old = now.saturating_duration_since(info.time_sent) >= loss_delay; +2982 if packet_too_old || largest_acked_packet >= packet + packet_threshold { +2983 // The packet should be declared lost. +2984 if Some(packet) == in_flight_mtu_probe { +2985 // Lost MTU probes are not included in `lost_packets`, because they +2986 // should not trigger a congestion control response +2987 lost_mtu_probe = in_flight_mtu_probe; +2988 } else { +2989 lost_packets.push(packet); +2990 size_of_lost_packets += info.size as u64; +2991 if info.ack_eliciting && due_to_ack { +2992 match persistent_congestion_start { +2993 // Two ACK-eliciting packets lost more than +2994 // congestion_period apart, with no ACKed packets in between +2995 Some(start) if info.time_sent - start > congestion_period => { +2996 in_persistent_congestion = true; +2997 } +2998 // Persistent congestion must start after the first RTT sample +2999 None if first_packet_after_rtt_sample +3000 .is_some_and(|x| x < (pn_space, packet)) => +3001 { +3002 persistent_congestion_start = Some(info.time_sent); +3003 } +3004 _ => {} +3005 } +3006 } +3007 } +3008 } else { +3009 // The packet should not yet be declared lost. +3010 if space.loss_time.is_none() { +3011 // Since we iterate in order the lowest packet number's loss time will +3012 // always be the earliest. +3013 space.loss_time = Some(info.time_sent + loss_delay); +3014 } +3015 persistent_congestion_start = None; +3016 } +3017 +3018 prev_packet = Some(packet); +3019 } +3020 +3021 self.handle_lost_packets( +3022 pn_space, +3023 path_id, +3024 now, +3025 lost_packets, +3026 lost_mtu_probe, +3027 loss_delay, +3028 in_persistent_congestion, +3029 size_of_lost_packets, +3030 ); +3031 } +3032 +3033 /// Drops the path state, declaring any remaining in-flight packets as lost +3034 fn discard_path(&mut self, path_id: PathId, now: Instant) { +3035 trace!(%path_id, "dropping path state"); +3036 let path = self.path_data(path_id); +3037 let in_flight_mtu_probe = path.mtud.in_flight_mtu_probe(); +3038 +3039 let mut size_of_lost_packets = 0u64; // add to path_stats.lost_bytes; +3040 let lost_pns: Vec<_> = self.spaces[SpaceId::Data] +3041 .for_path(path_id) +3042 .sent_packets +3043 .iter() +3044 .filter(|(pn, _info)| Some(*pn) != in_flight_mtu_probe) +3045 .map(|(pn, info)| { +3046 size_of_lost_packets += info.size as u64; +3047 pn +3048 }) +3049 .collect(); +3050 +3051 if !lost_pns.is_empty() { +3052 trace!( +3053 %path_id, +3054 count = lost_pns.len(), +3055 lost_bytes = size_of_lost_packets, +3056 "packets lost on path abandon" +3057 ); +3058 self.handle_lost_packets( +3059 SpaceId::Data, +3060 path_id, +3061 now, +3062 lost_pns, +3063 in_flight_mtu_probe, +3064 Duration::ZERO, +3065 false, +3066 size_of_lost_packets, +3067 ); +3068 } +3069 self.paths.remove(&path_id); +3070 self.spaces[SpaceId::Data].number_spaces.remove(&path_id); +3071 +3072 let path_stats = self.path_stats.remove(&path_id).unwrap_or_default(); +3073 self.events.push_back( +3074 PathEvent::Abandoned { +3075 id: path_id, +3076 path_stats, +3077 } +3078 .into(), +3079 ); +3080 } +3081 +3082 fn handle_lost_packets( +3083 &mut self, +3084 pn_space: SpaceId, +3085 path_id: PathId, +3086 now: Instant, +3087 lost_packets: Vec<u64>, +3088 lost_mtu_probe: Option<u64>, +3089 loss_delay: Duration, +3090 in_persistent_congestion: bool, +3091 size_of_lost_packets: u64, +3092 ) { +3093 debug_assert!( +3094 { +3095 let mut sorted = lost_packets.clone(); +3096 sorted.sort(); +3097 sorted == lost_packets +3098 }, +3099 "lost_packets must be sorted" +3100 ); +3101 +3102 self.drain_lost_packets(now, pn_space, path_id); +3103 +3104 // OnPacketsLost +3105 if let Some(largest_lost) = lost_packets.last().cloned() { +3106 let old_bytes_in_flight = self.path_data_mut(path_id).in_flight.bytes; +3107 let largest_lost_sent = self.spaces[pn_space] +3108 .for_path(path_id) +3109 .sent_packets +3110 .get(largest_lost) +3111 .unwrap() +3112 .time_sent; +3113 let path_stats = self.path_stats.entry(path_id).or_default(); +3114 path_stats.lost_packets += lost_packets.len() as u64; +3115 path_stats.lost_bytes += size_of_lost_packets; +3116 trace!( +3117 %path_id, +3118 count = lost_packets.len(), +3119 lost_bytes = size_of_lost_packets, +3120 "packets lost", +3121 ); +3122 +3123 for &packet in &lost_packets { +3124 let Some(info) = self.spaces[pn_space].for_path(path_id).take(packet) else { +3125 continue; +3126 }; +3127 self.qlog +3128 .emit_packet_lost(packet, &info, loss_delay, pn_space, now); +3129 self.paths +3130 .get_mut(&path_id) +3131 .unwrap() +3132 .remove_in_flight(&info); +3133 +3134 for frame in info.stream_frames { +3135 self.streams.retransmit(frame); +3136 } +3137 self.spaces[pn_space].pending |= info.retransmits; +3138 self.path_data_mut(path_id) +3139 .mtud +3140 .on_non_probe_lost(packet, info.size); +3141 +3142 self.spaces[pn_space].for_path(path_id).lost_packets.insert( +3143 packet, +3144 LostPacket { +3145 time_sent: info.time_sent, +3146 }, +3147 ); +3148 } +3149 +3150 let path = self.path_data_mut(path_id); +3151 if path.mtud.black_hole_detected(now) { +3152 path.congestion.on_mtu_update(path.mtud.current_mtu()); +3153 if let Some(max_datagram_size) = self.datagrams().max_size() +3154 && self.datagrams.drop_oversized(max_datagram_size) +3155 && self.datagrams.send_blocked +3156 { +3157 self.datagrams.send_blocked = false; +3158 self.events.push_back(Event::DatagramsUnblocked); +3159 } +3160 self.path_stats +3161 .entry(path_id) +3162 .or_default() +3163 .black_holes_detected += 1; +3164 } +3165 +3166 // Don't apply congestion penalty for lost ack-only packets +3167 let lost_ack_eliciting = +3168 old_bytes_in_flight != self.path_data_mut(path_id).in_flight.bytes; +3169 +3170 if lost_ack_eliciting { +3171 self.path_stats +3172 .entry(path_id) +3173 .or_default() +3174 .congestion_events += 1; +3175 self.path_data_mut(path_id).congestion.on_congestion_event( +3176 now, +3177 largest_lost_sent, +3178 in_persistent_congestion, +3179 false, +3180 size_of_lost_packets, +3181 ); +3182 } +3183 } +3184 +3185 // Handle a lost MTU probe +3186 if let Some(packet) = lost_mtu_probe { +3187 let info = self.spaces[SpaceId::Data] +3188 .for_path(path_id) +3189 .take(packet) +3190 .unwrap(); // safe: lost_mtu_probe is omitted from lost_packets, and +3191 // therefore must not have been removed yet +3192 self.paths +3193 .get_mut(&path_id) +3194 .unwrap() +3195 .remove_in_flight(&info); +3196 self.path_data_mut(path_id).mtud.on_probe_lost(); +3197 self.path_stats +3198 .entry(path_id) +3199 .or_default() +3200 .lost_plpmtud_probes += 1; +3201 } +3202 } +3203 +3204 /// Returns the earliest time packets should be declared lost for all spaces on a path. +3205 /// +3206 /// If a path has an acknowledged packet with any prior un-acknowledged packets, the +3207 /// earliest un-acknowledged packet can be declared lost after a timeout has elapsed. +3208 /// The time returned is when this packet should be declared lost. +3209 fn loss_time_and_space(&self, path_id: PathId) -> Option<(Instant, SpaceId)> { +3210 SpaceId::iter() +3211 .filter_map(|id| { +3212 self.spaces[id] +3213 .number_spaces +3214 .get(&path_id) +3215 .and_then(|pns| pns.loss_time) +3216 .map(|time| (time, id)) +3217 }) +3218 .min_by_key(|&(time, _)| time) +3219 } +3220 +3221 /// Returns the earliest next PTO should fire for all spaces on a path. +3222 fn pto_time_and_space(&mut self, now: Instant, path_id: PathId) -> Option<(Instant, SpaceId)> { +3223 let path = self.path(path_id)?; +3224 let pto_count = path.pto_count; +3225 let backoff = 2u32.pow(pto_count.min(MAX_BACKOFF_EXPONENT)); +3226 let mut duration = path.rtt.pto_base() * backoff; +3227 +3228 if path_id == PathId::ZERO +3229 && path.in_flight.ack_eliciting == 0 +3230 && !self.peer_completed_address_validation(PathId::ZERO) +3231 { +3232 // Address Validation during Connection Establishment: +3233 // https://www.rfc-editor.org/rfc/rfc9000.html#section-8.1. To prevent a +3234 // deadlock if an Initial or Handshake packet from the server is lost and the +3235 // server can not send more due to its anti-amplification limit the client must +3236 // send another packet on PTO. +3237 let space = match self.highest_space { +3238 SpaceId::Handshake => SpaceId::Handshake, +3239 _ => SpaceId::Initial, 3240 }; 3241 -3242 if !pns.has_in_flight() { -3243 continue; -3244 } -3245 if space == SpaceId::Data { -3246 // Skip ApplicationData until handshake completes. -3247 if self.is_handshaking() { -3248 return result; -3249 } -3250 // Include max_ack_delay and backoff for ApplicationData. -3251 duration += self.ack_frequency.max_ack_delay_for_pto() * backoff; -3252 } -3253 let Some(last_ack_eliciting) = pns.time_of_last_ack_eliciting_packet else { -3254 continue; -3255 }; -3256 let pto = last_ack_eliciting + duration; -3257 if result.is_none_or(|(earliest_pto, _)| pto < earliest_pto) { -3258 if path.anti_amplification_blocked(1) { -3259 // Nothing would be able to be sent. -3260 continue; -3261 } -3262 if path.in_flight.ack_eliciting == 0 { -3263 // Nothing ack-eliciting, no PTO to arm/fire. -3264 continue; -3265 } -3266 result = Some((pto, space)); -3267 } -3268 } -3269 result -3270 } -3271 -3272 fn peer_completed_address_validation(&self, path: PathId) -> bool { -3273 // TODO(flub): This logic needs updating for multipath -3274 if self.side.is_server() || self.state.is_closed() { -3275 return true; -3276 } -3277 // The server is guaranteed to have validated our address if any of our handshake or 1-RTT -3278 // packets are acknowledged or we've seen HANDSHAKE_DONE and discarded handshake keys. -3279 self.spaces[SpaceId::Handshake] -3280 .path_space(PathId::ZERO) -3281 .and_then(|pns| pns.largest_acked_packet) -3282 .is_some() -3283 || self.spaces[SpaceId::Data] -3284 .path_space(path) -3285 .and_then(|pns| pns.largest_acked_packet) -3286 .is_some() -3287 || (self.spaces[SpaceId::Data].crypto.is_some() -3288 && self.spaces[SpaceId::Handshake].crypto.is_none()) -3289 } -3290 -3291 /// Resets the the [`PathTimer::LossDetection`] timer to the next instant it may be needed -3292 /// -3293 /// The timer must fire if either: -3294 /// - An ack-eliciting packet we sent needs to be declared lost. -3295 /// - A tail-loss probe needs to be sent. -3296 /// -3297 /// See [`Connection::on_loss_detection_timeout`] for details. -3298 fn set_loss_detection_timer(&mut self, now: Instant, path_id: PathId) { -3299 if self.state.is_closed() { -3300 // No loss detection takes place on closed connections, and `close_common` already -3301 // stopped time timer. Ensure we don't restart it inadvertently, e.g. in response to a -3302 // reordered packet being handled by state-insensitive code. -3303 return; -3304 } -3305 -3306 if let Some((loss_time, _)) = self.loss_time_and_space(path_id) { -3307 // Time threshold loss detection. -3308 self.timers.set( -3309 Timer::PerPath(path_id, PathTimer::LossDetection), -3310 loss_time, -3311 self.qlog.with_time(now), -3312 ); -3313 return; -3314 } -3315 -3316 // Determine which PN space to arm PTO for. -3317 // Calculate PTO duration -3318 if let Some((timeout, _)) = self.pto_time_and_space(now, path_id) { -3319 self.timers.set( -3320 Timer::PerPath(path_id, PathTimer::LossDetection), -3321 timeout, -3322 self.qlog.with_time(now), -3323 ); -3324 } else { -3325 self.timers.stop( -3326 Timer::PerPath(path_id, PathTimer::LossDetection), -3327 self.qlog.with_time(now), -3328 ); -3329 } -3330 } -3331 -3332 /// The maximum probe timeout across all paths -3333 /// -3334 /// If `is_closing` is set to `true` it will filter out paths that have not yet been used. -3335 /// -3336 /// See [`Connection::pto`] -3337 fn pto_max_path(&self, space: SpaceId, is_closing: bool) -> Duration { -3338 match space { -3339 SpaceId::Initial | SpaceId::Handshake => self.pto(space, PathId::ZERO), -3340 SpaceId::Data => self -3341 .paths -3342 .iter() -3343 .filter_map(|(path_id, state)| { -3344 if is_closing && state.data.total_sent == 0 && state.data.total_recvd == 0 { -3345 // If we are closing and haven't sent anything yet, do not include -3346 None -3347 } else { -3348 let pto = self.pto(space, *path_id); -3349 Some(pto) -3350 } -3351 }) -3352 .max() -3353 .expect("there should be at least one path"), -3354 } -3355 } -3356 -3357 /// Probe Timeout -3358 /// -3359 /// The PTO is logically the time in which you'd expect to receive an acknowledgement -3360 /// for a packet. So approximately RTT + max_ack_delay. -3361 fn pto(&self, space: SpaceId, path_id: PathId) -> Duration { -3362 let max_ack_delay = match space { -3363 SpaceId::Initial | SpaceId::Handshake => Duration::ZERO, -3364 SpaceId::Data => self.ack_frequency.max_ack_delay_for_pto(), -3365 }; -3366 self.path_data(path_id).rtt.pto_base() + max_ack_delay -3367 } -3368 -3369 fn on_packet_authenticated( -3370 &mut self, -3371 now: Instant, -3372 space_id: SpaceId, -3373 path_id: PathId, -3374 ecn: Option<EcnCodepoint>, -3375 packet: Option<u64>, -3376 spin: bool, -3377 is_1rtt: bool, -3378 ) { -3379 self.total_authed_packets += 1; -3380 if let Some(AbandonState::ExpectingPathAbandon { deadline }) = self -3381 .paths -3382 .get(&path_id) -3383 .map(|path| &path.data.abandon_state) -3384 && now > *deadline -3385 { -3386 warn!("received data on path which we abandoned more than 3 * PTO ago"); -3387 // The peer failed to respond with a PATH_ABANDON in time. -3388 if !self.state.is_closed() { -3389 self.state -3390 .move_to_closed(TransportError::PROTOCOL_VIOLATION( -3391 "peer failed to respond with PATH_ABANDON in time", -3392 )); -3393 self.close_common(); -3394 self.set_close_timer(now); -3395 self.connection_close_pending = true; -3396 } -3397 return; -3398 } -3399 -3400 self.reset_keep_alive(path_id, now); -3401 self.reset_idle_timeout(now, space_id, path_id); -3402 self.permit_idle_reset = true; -3403 self.receiving_ecn |= ecn.is_some(); -3404 if let Some(x) = ecn { -3405 let space = &mut self.spaces[space_id]; -3406 space.for_path(path_id).ecn_counters += x; -3407 -3408 if x.is_ce() { -3409 space -3410 .for_path(path_id) -3411 .pending_acks -3412 .set_immediate_ack_required(); -3413 } -3414 } -3415 -3416 let packet = match packet { -3417 Some(x) => x, -3418 None => return, -3419 }; -3420 match &self.side { -3421 ConnectionSide::Client { .. } => { -3422 // If we received a handshake packet that authenticated, then we're talking to -3423 // the real server. From now on we should no longer allow the server to migrate -3424 // its address. -3425 if space_id == SpaceId::Handshake -3426 && let Some(hs) = self.state.as_handshake_mut() -3427 { -3428 hs.allow_server_migration = false; -3429 } -3430 } -3431 ConnectionSide::Server { .. } => { -3432 if self.spaces[SpaceId::Initial].crypto.is_some() && space_id == SpaceId::Handshake -3433 { -3434 // A server stops sending and processing Initial packets when it receives its first Handshake packet. -3435 self.discard_space(now, SpaceId::Initial); -3436 } -3437 if self.zero_rtt_crypto.is_some() && is_1rtt { -3438 // Discard 0-RTT keys soon after receiving a 1-RTT packet -3439 self.set_key_discard_timer(now, space_id) -3440 } -3441 } -3442 } -3443 let space = self.spaces[space_id].for_path(path_id); -3444 space.pending_acks.insert_one(packet, now); -3445 if packet >= space.rx_packet.unwrap_or_default() { -3446 space.rx_packet = Some(packet); -3447 // Update outgoing spin bit, inverting iff we're the client -3448 self.spin = self.side.is_client() ^ spin; -3449 } -3450 } -3451 -3452 /// Resets the idle timeout timers -3453 /// -3454 /// Without multipath there is only the connection-wide idle timeout. When multipath is -3455 /// enabled there is an additional per-path idle timeout. -3456 fn reset_idle_timeout(&mut self, now: Instant, space: SpaceId, path_id: PathId) { -3457 // First reset the global idle timeout. -3458 if let Some(timeout) = self.idle_timeout { -3459 if self.state.is_closed() { -3460 self.timers -3461 .stop(Timer::Conn(ConnTimer::Idle), self.qlog.with_time(now)); -3462 } else { -3463 let dt = cmp::max(timeout, 3 * self.pto_max_path(space, false)); -3464 self.timers.set( -3465 Timer::Conn(ConnTimer::Idle), -3466 now + dt, -3467 self.qlog.with_time(now), -3468 ); -3469 } -3470 } -3471 -3472 // Now handle the per-path state -3473 if let Some(timeout) = self.path_data(path_id).idle_timeout { -3474 if self.state.is_closed() { -3475 self.timers.stop( -3476 Timer::PerPath(path_id, PathTimer::PathIdle), -3477 self.qlog.with_time(now), -3478 ); -3479 } else { -3480 let dt = cmp::max(timeout, 3 * self.pto(space, path_id)); -3481 self.timers.set( -3482 Timer::PerPath(path_id, PathTimer::PathIdle), -3483 now + dt, -3484 self.qlog.with_time(now), -3485 ); -3486 } -3487 } -3488 } -3489 -3490 /// Resets both the [`ConnTimer::KeepAlive`] and [`PathTimer::PathKeepAlive`] timers -3491 fn reset_keep_alive(&mut self, path_id: PathId, now: Instant) { -3492 if !self.state.is_established() { -3493 return; -3494 } -3495 -3496 if let Some(interval) = self.config.keep_alive_interval { -3497 self.timers.set( -3498 Timer::Conn(ConnTimer::KeepAlive), -3499 now + interval, -3500 self.qlog.with_time(now), -3501 ); -3502 } -3503 -3504 if let Some(interval) = self.path_data(path_id).keep_alive { -3505 self.timers.set( -3506 Timer::PerPath(path_id, PathTimer::PathKeepAlive), -3507 now + interval, -3508 self.qlog.with_time(now), -3509 ); -3510 } -3511 } +3242 return Some((now + duration, space)); +3243 } +3244 +3245 let mut result = None; +3246 for space in SpaceId::iter() { +3247 let Some(pns) = self.spaces[space].number_spaces.get(&path_id) else { +3248 continue; +3249 }; +3250 +3251 if !pns.has_in_flight() { +3252 continue; +3253 } +3254 if space == SpaceId::Data { +3255 // Skip ApplicationData until handshake completes. +3256 if self.is_handshaking() { +3257 return result; +3258 } +3259 // Include max_ack_delay and backoff for ApplicationData. +3260 duration += self.ack_frequency.max_ack_delay_for_pto() * backoff; +3261 } +3262 let Some(last_ack_eliciting) = pns.time_of_last_ack_eliciting_packet else { +3263 continue; +3264 }; +3265 let pto = last_ack_eliciting + duration; +3266 if result.is_none_or(|(earliest_pto, _)| pto < earliest_pto) { +3267 if path.anti_amplification_blocked(1) { +3268 // Nothing would be able to be sent. +3269 continue; +3270 } +3271 if path.in_flight.ack_eliciting == 0 { +3272 // Nothing ack-eliciting, no PTO to arm/fire. +3273 continue; +3274 } +3275 result = Some((pto, space)); +3276 } +3277 } +3278 result +3279 } +3280 +3281 fn peer_completed_address_validation(&self, path: PathId) -> bool { +3282 // TODO(flub): This logic needs updating for multipath +3283 if self.side.is_server() || self.state.is_closed() { +3284 return true; +3285 } +3286 // The server is guaranteed to have validated our address if any of our handshake or 1-RTT +3287 // packets are acknowledged or we've seen HANDSHAKE_DONE and discarded handshake keys. +3288 self.spaces[SpaceId::Handshake] +3289 .path_space(PathId::ZERO) +3290 .and_then(|pns| pns.largest_acked_packet) +3291 .is_some() +3292 || self.spaces[SpaceId::Data] +3293 .path_space(path) +3294 .and_then(|pns| pns.largest_acked_packet) +3295 .is_some() +3296 || (self.spaces[SpaceId::Data].crypto.is_some() +3297 && self.spaces[SpaceId::Handshake].crypto.is_none()) +3298 } +3299 +3300 /// Resets the the [`PathTimer::LossDetection`] timer to the next instant it may be needed +3301 /// +3302 /// The timer must fire if either: +3303 /// - An ack-eliciting packet we sent needs to be declared lost. +3304 /// - A tail-loss probe needs to be sent. +3305 /// +3306 /// See [`Connection::on_loss_detection_timeout`] for details. +3307 fn set_loss_detection_timer(&mut self, now: Instant, path_id: PathId) { +3308 if self.state.is_closed() { +3309 // No loss detection takes place on closed connections, and `close_common` already +3310 // stopped time timer. Ensure we don't restart it inadvertently, e.g. in response to a +3311 // reordered packet being handled by state-insensitive code. +3312 return; +3313 } +3314 +3315 if let Some((loss_time, _)) = self.loss_time_and_space(path_id) { +3316 // Time threshold loss detection. +3317 self.timers.set( +3318 Timer::PerPath(path_id, PathTimer::LossDetection), +3319 loss_time, +3320 self.qlog.with_time(now), +3321 ); +3322 return; +3323 } +3324 +3325 // Determine which PN space to arm PTO for. +3326 // Calculate PTO duration +3327 if let Some((timeout, _)) = self.pto_time_and_space(now, path_id) { +3328 self.timers.set( +3329 Timer::PerPath(path_id, PathTimer::LossDetection), +3330 timeout, +3331 self.qlog.with_time(now), +3332 ); +3333 } else { +3334 self.timers.stop( +3335 Timer::PerPath(path_id, PathTimer::LossDetection), +3336 self.qlog.with_time(now), +3337 ); +3338 } +3339 } +3340 +3341 /// The maximum probe timeout across all paths +3342 /// +3343 /// If `is_closing` is set to `true` it will filter out paths that have not yet been used. +3344 /// +3345 /// See [`Connection::pto`] +3346 fn pto_max_path(&self, space: SpaceId, is_closing: bool) -> Duration { +3347 match space { +3348 SpaceId::Initial | SpaceId::Handshake => self.pto(space, PathId::ZERO), +3349 SpaceId::Data => self +3350 .paths +3351 .iter() +3352 .filter_map(|(path_id, state)| { +3353 if is_closing && state.data.total_sent == 0 && state.data.total_recvd == 0 { +3354 // If we are closing and haven't sent anything yet, do not include +3355 None +3356 } else { +3357 let pto = self.pto(space, *path_id); +3358 Some(pto) +3359 } +3360 }) +3361 .max() +3362 .expect("there should be at least one path"), +3363 } +3364 } +3365 +3366 /// Probe Timeout +3367 /// +3368 /// The PTO is logically the time in which you'd expect to receive an acknowledgement +3369 /// for a packet. So approximately RTT + max_ack_delay. +3370 fn pto(&self, space: SpaceId, path_id: PathId) -> Duration { +3371 let max_ack_delay = match space { +3372 SpaceId::Initial | SpaceId::Handshake => Duration::ZERO, +3373 SpaceId::Data => self.ack_frequency.max_ack_delay_for_pto(), +3374 }; +3375 self.path_data(path_id).rtt.pto_base() + max_ack_delay +3376 } +3377 +3378 fn on_packet_authenticated( +3379 &mut self, +3380 now: Instant, +3381 space_id: SpaceId, +3382 path_id: PathId, +3383 ecn: Option<EcnCodepoint>, +3384 packet: Option<u64>, +3385 spin: bool, +3386 is_1rtt: bool, +3387 ) { +3388 self.total_authed_packets += 1; +3389 if let Some(AbandonState::ExpectingPathAbandon { deadline }) = self +3390 .paths +3391 .get(&path_id) +3392 .map(|path| &path.data.abandon_state) +3393 && now > *deadline +3394 { +3395 warn!("received data on path which we abandoned more than 3 * PTO ago"); +3396 // The peer failed to respond with a PATH_ABANDON in time. +3397 if !self.state.is_closed() { +3398 self.state +3399 .move_to_closed(TransportError::PROTOCOL_VIOLATION( +3400 "peer failed to respond with PATH_ABANDON in time", +3401 )); +3402 self.close_common(); +3403 self.set_close_timer(now); +3404 self.connection_close_pending = true; +3405 } +3406 return; +3407 } +3408 +3409 self.reset_keep_alive(path_id, now); +3410 self.reset_idle_timeout(now, space_id, path_id); +3411 self.permit_idle_reset = true; +3412 self.receiving_ecn |= ecn.is_some(); +3413 if let Some(x) = ecn { +3414 let space = &mut self.spaces[space_id]; +3415 space.for_path(path_id).ecn_counters += x; +3416 +3417 if x.is_ce() { +3418 space +3419 .for_path(path_id) +3420 .pending_acks +3421 .set_immediate_ack_required(); +3422 } +3423 } +3424 +3425 let packet = match packet { +3426 Some(x) => x, +3427 None => return, +3428 }; +3429 match &self.side { +3430 ConnectionSide::Client { .. } => { +3431 // If we received a handshake packet that authenticated, then we're talking to +3432 // the real server. From now on we should no longer allow the server to migrate +3433 // its address. +3434 if space_id == SpaceId::Handshake +3435 && let Some(hs) = self.state.as_handshake_mut() +3436 { +3437 hs.allow_server_migration = false; +3438 } +3439 } +3440 ConnectionSide::Server { .. } => { +3441 if self.spaces[SpaceId::Initial].crypto.is_some() && space_id == SpaceId::Handshake +3442 { +3443 // A server stops sending and processing Initial packets when it receives its first Handshake packet. +3444 self.discard_space(now, SpaceId::Initial); +3445 } +3446 if self.zero_rtt_crypto.is_some() && is_1rtt { +3447 // Discard 0-RTT keys soon after receiving a 1-RTT packet +3448 self.set_key_discard_timer(now, space_id) +3449 } +3450 } +3451 } +3452 let space = self.spaces[space_id].for_path(path_id); +3453 space.pending_acks.insert_one(packet, now); +3454 if packet >= space.rx_packet.unwrap_or_default() { +3455 space.rx_packet = Some(packet); +3456 // Update outgoing spin bit, inverting iff we're the client +3457 self.spin = self.side.is_client() ^ spin; +3458 } +3459 } +3460 +3461 /// Resets the idle timeout timers +3462 /// +3463 /// Without multipath there is only the connection-wide idle timeout. When multipath is +3464 /// enabled there is an additional per-path idle timeout. +3465 fn reset_idle_timeout(&mut self, now: Instant, space: SpaceId, path_id: PathId) { +3466 // First reset the global idle timeout. +3467 if let Some(timeout) = self.idle_timeout { +3468 if self.state.is_closed() { +3469 self.timers +3470 .stop(Timer::Conn(ConnTimer::Idle), self.qlog.with_time(now)); +3471 } else { +3472 let dt = cmp::max(timeout, 3 * self.pto_max_path(space, false)); +3473 self.timers.set( +3474 Timer::Conn(ConnTimer::Idle), +3475 now + dt, +3476 self.qlog.with_time(now), +3477 ); +3478 } +3479 } +3480 +3481 // Now handle the per-path state +3482 if let Some(timeout) = self.path_data(path_id).idle_timeout { +3483 if self.state.is_closed() { +3484 self.timers.stop( +3485 Timer::PerPath(path_id, PathTimer::PathIdle), +3486 self.qlog.with_time(now), +3487 ); +3488 } else { +3489 let dt = cmp::max(timeout, 3 * self.pto(space, path_id)); +3490 self.timers.set( +3491 Timer::PerPath(path_id, PathTimer::PathIdle), +3492 now + dt, +3493 self.qlog.with_time(now), +3494 ); +3495 } +3496 } +3497 } +3498 +3499 /// Resets both the [`ConnTimer::KeepAlive`] and [`PathTimer::PathKeepAlive`] timers +3500 fn reset_keep_alive(&mut self, path_id: PathId, now: Instant) { +3501 if !self.state.is_established() { +3502 return; +3503 } +3504 +3505 if let Some(interval) = self.config.keep_alive_interval { +3506 self.timers.set( +3507 Timer::Conn(ConnTimer::KeepAlive), +3508 now + interval, +3509 self.qlog.with_time(now), +3510 ); +3511 } 3512 -3513 /// Sets the timer for when a previously issued CID should be retired next -3514 fn reset_cid_retirement(&mut self, now: Instant) { -3515 if let Some((_path, t)) = self.next_cid_retirement() { -3516 self.timers.set( -3517 Timer::Conn(ConnTimer::PushNewCid), -3518 t, -3519 self.qlog.with_time(now), -3520 ); -3521 } -3522 } -3523 -3524 /// The next time when a previously issued CID should be retired -3525 fn next_cid_retirement(&self) -> Option<(PathId, Instant)> { -3526 self.local_cid_state -3527 .iter() -3528 .filter_map(|(path_id, cid_state)| cid_state.next_timeout().map(|t| (*path_id, t))) -3529 .min_by_key(|(_path_id, timeout)| *timeout) -3530 } -3531 -3532 /// Handle the already-decrypted first packet from the client -3533 /// -3534 /// Decrypting the first packet in the `Endpoint` allows stateless packet handling to be more -3535 /// efficient. -3536 pub(crate) fn handle_first_packet( -3537 &mut self, -3538 now: Instant, -3539 network_path: FourTuple, -3540 ecn: Option<EcnCodepoint>, -3541 packet_number: u64, -3542 packet: InitialPacket, -3543 remaining: Option<BytesMut>, -3544 ) -> Result<(), ConnectionError> { -3545 let span = trace_span!("first recv"); -3546 let _guard = span.enter(); -3547 debug_assert!(self.side.is_server()); -3548 let len = packet.header_data.len() + packet.payload.len(); -3549 let path_id = PathId::ZERO; -3550 self.path_data_mut(path_id).total_recvd = len as u64; -3551 -3552 if let Some(hs) = self.state.as_handshake_mut() { -3553 hs.expected_token = packet.header.token.clone(); -3554 } else { -3555 unreachable!("first packet must be delivered in Handshake state"); -3556 } -3557 -3558 // The first packet is always on PathId::ZERO -3559 self.on_packet_authenticated( -3560 now, -3561 SpaceId::Initial, -3562 path_id, -3563 ecn, -3564 Some(packet_number), -3565 false, -3566 false, -3567 ); -3568 -3569 let packet: Packet = packet.into(); -3570 -3571 let mut qlog = QlogRecvPacket::new(len); -3572 qlog.header(&packet.header, Some(packet_number), path_id); -3573 -3574 self.process_decrypted_packet( -3575 now, -3576 network_path, -3577 path_id, -3578 Some(packet_number), -3579 packet, -3580 &mut qlog, -3581 )?; -3582 self.qlog.emit_packet_received(qlog, now); -3583 if let Some(data) = remaining { -3584 self.handle_coalesced(now, network_path, path_id, ecn, data); -3585 } -3586 -3587 self.qlog.emit_recovery_metrics( -3588 path_id, -3589 &mut self.paths.get_mut(&path_id).unwrap().data, -3590 now, -3591 ); -3592 -3593 Ok(()) -3594 } +3513 if let Some(interval) = self.path_data(path_id).keep_alive { +3514 self.timers.set( +3515 Timer::PerPath(path_id, PathTimer::PathKeepAlive), +3516 now + interval, +3517 self.qlog.with_time(now), +3518 ); +3519 } +3520 } +3521 +3522 /// Sets the timer for when a previously issued CID should be retired next +3523 fn reset_cid_retirement(&mut self, now: Instant) { +3524 if let Some((_path, t)) = self.next_cid_retirement() { +3525 self.timers.set( +3526 Timer::Conn(ConnTimer::PushNewCid), +3527 t, +3528 self.qlog.with_time(now), +3529 ); +3530 } +3531 } +3532 +3533 /// The next time when a previously issued CID should be retired +3534 fn next_cid_retirement(&self) -> Option<(PathId, Instant)> { +3535 self.local_cid_state +3536 .iter() +3537 .filter_map(|(path_id, cid_state)| cid_state.next_timeout().map(|t| (*path_id, t))) +3538 .min_by_key(|(_path_id, timeout)| *timeout) +3539 } +3540 +3541 /// Handle the already-decrypted first packet from the client +3542 /// +3543 /// Decrypting the first packet in the `Endpoint` allows stateless packet handling to be more +3544 /// efficient. +3545 pub(crate) fn handle_first_packet( +3546 &mut self, +3547 now: Instant, +3548 network_path: FourTuple, +3549 ecn: Option<EcnCodepoint>, +3550 packet_number: u64, +3551 packet: InitialPacket, +3552 remaining: Option<BytesMut>, +3553 ) -> Result<(), ConnectionError> { +3554 let span = trace_span!("first recv"); +3555 let _guard = span.enter(); +3556 debug_assert!(self.side.is_server()); +3557 let len = packet.header_data.len() + packet.payload.len(); +3558 let path_id = PathId::ZERO; +3559 self.path_data_mut(path_id).total_recvd = len as u64; +3560 +3561 if let Some(hs) = self.state.as_handshake_mut() { +3562 hs.expected_token = packet.header.token.clone(); +3563 } else { +3564 unreachable!("first packet must be delivered in Handshake state"); +3565 } +3566 +3567 // The first packet is always on PathId::ZERO +3568 self.on_packet_authenticated( +3569 now, +3570 SpaceId::Initial, +3571 path_id, +3572 ecn, +3573 Some(packet_number), +3574 false, +3575 false, +3576 ); +3577 +3578 let packet: Packet = packet.into(); +3579 +3580 let mut qlog = QlogRecvPacket::new(len); +3581 qlog.header(&packet.header, Some(packet_number), path_id); +3582 +3583 self.process_decrypted_packet( +3584 now, +3585 network_path, +3586 path_id, +3587 Some(packet_number), +3588 packet, +3589 &mut qlog, +3590 )?; +3591 self.qlog.emit_packet_received(qlog, now); +3592 if let Some(data) = remaining { +3593 self.handle_coalesced(now, network_path, path_id, ecn, data); +3594 } 3595 -3596 fn init_0rtt(&mut self, now: Instant) { -3597 let (header, packet) = match self.crypto.early_crypto() { -3598 Some(x) => x, -3599 None => return, -3600 }; -3601 if self.side.is_client() { -3602 match self.crypto.transport_parameters() { -3603 Ok(params) => { -3604 let params = params -3605 .expect("crypto layer didn't supply transport parameters with ticket"); -3606 // Certain values must not be cached -3607 let params = TransportParameters { -3608 initial_src_cid: None, -3609 original_dst_cid: None, -3610 preferred_address: None, -3611 retry_src_cid: None, -3612 stateless_reset_token: None, -3613 min_ack_delay: None, -3614 ack_delay_exponent: TransportParameters::default().ack_delay_exponent, -3615 max_ack_delay: TransportParameters::default().max_ack_delay, -3616 initial_max_path_id: None, -3617 ..params -3618 }; -3619 self.set_peer_params(params); -3620 self.qlog.emit_peer_transport_params_restored(self, now); -3621 } -3622 Err(e) => { -3623 error!("session ticket has malformed transport parameters: {}", e); -3624 return; -3625 } -3626 } -3627 } -3628 trace!("0-RTT enabled"); -3629 self.zero_rtt_enabled = true; -3630 self.zero_rtt_crypto = Some(ZeroRttCrypto { header, packet }); -3631 } -3632 -3633 fn read_crypto( -3634 &mut self, -3635 space: SpaceId, -3636 crypto: &frame::Crypto, -3637 payload_len: usize, -3638 ) -> Result<(), TransportError> { -3639 let expected = if !self.state.is_handshake() { -3640 SpaceId::Data -3641 } else if self.highest_space == SpaceId::Initial { -3642 SpaceId::Initial -3643 } else { -3644 // On the server, self.highest_space can be Data after receiving the client's first -3645 // flight, but we expect Handshake CRYPTO until the handshake is complete. -3646 SpaceId::Handshake -3647 }; -3648 // We can't decrypt Handshake packets when highest_space is Initial, CRYPTO frames in 0-RTT -3649 // packets are illegal, and we don't process 1-RTT packets until the handshake is -3650 // complete. Therefore, we will never see CRYPTO data from a later-than-expected space. -3651 debug_assert!(space <= expected, "received out-of-order CRYPTO data"); -3652 -3653 let end = crypto.offset + crypto.data.len() as u64; -3654 if space < expected && end > self.spaces[space].crypto_stream.bytes_read() { -3655 warn!( -3656 "received new {:?} CRYPTO data when expecting {:?}", -3657 space, expected -3658 ); -3659 return Err(TransportError::PROTOCOL_VIOLATION( -3660 "new data at unexpected encryption level", -3661 )); -3662 } -3663 -3664 let space = &mut self.spaces[space]; -3665 let max = end.saturating_sub(space.crypto_stream.bytes_read()); -3666 if max > self.config.crypto_buffer_size as u64 { -3667 return Err(TransportError::CRYPTO_BUFFER_EXCEEDED("")); -3668 } -3669 -3670 space -3671 .crypto_stream -3672 .insert(crypto.offset, crypto.data.clone(), payload_len); -3673 while let Some(chunk) = space.crypto_stream.read(usize::MAX, true) { -3674 trace!("consumed {} CRYPTO bytes", chunk.bytes.len()); -3675 if self.crypto.read_handshake(&chunk.bytes)? { -3676 self.events.push_back(Event::HandshakeDataReady); -3677 } -3678 } -3679 -3680 Ok(()) -3681 } -3682 -3683 fn write_crypto(&mut self) { -3684 loop { -3685 let space = self.highest_space; -3686 let mut outgoing = Vec::new(); -3687 if let Some(crypto) = self.crypto.write_handshake(&mut outgoing) { -3688 match space { -3689 SpaceId::Initial => { -3690 self.upgrade_crypto(SpaceId::Handshake, crypto); -3691 } -3692 SpaceId::Handshake => { -3693 self.upgrade_crypto(SpaceId::Data, crypto); -3694 } -3695 _ => unreachable!("got updated secrets during 1-RTT"), -3696 } -3697 } -3698 if outgoing.is_empty() { -3699 if space == self.highest_space { -3700 break; -3701 } else { -3702 // Keys updated, check for more data to send -3703 continue; -3704 } -3705 } -3706 let offset = self.spaces[space].crypto_offset; -3707 let outgoing = Bytes::from(outgoing); -3708 if let Some(hs) = self.state.as_handshake_mut() -3709 && space == SpaceId::Initial -3710 && offset == 0 -3711 && self.side.is_client() -3712 { -3713 hs.client_hello = Some(outgoing.clone()); +3596 self.qlog.emit_recovery_metrics( +3597 path_id, +3598 &mut self.paths.get_mut(&path_id).unwrap().data, +3599 now, +3600 ); +3601 +3602 Ok(()) +3603 } +3604 +3605 fn init_0rtt(&mut self, now: Instant) { +3606 let (header, packet) = match self.crypto.early_crypto() { +3607 Some(x) => x, +3608 None => return, +3609 }; +3610 if self.side.is_client() { +3611 match self.crypto.transport_parameters() { +3612 Ok(params) => { +3613 let params = params +3614 .expect("crypto layer didn't supply transport parameters with ticket"); +3615 // Certain values must not be cached +3616 let params = TransportParameters { +3617 initial_src_cid: None, +3618 original_dst_cid: None, +3619 preferred_address: None, +3620 retry_src_cid: None, +3621 stateless_reset_token: None, +3622 min_ack_delay: None, +3623 ack_delay_exponent: TransportParameters::default().ack_delay_exponent, +3624 max_ack_delay: TransportParameters::default().max_ack_delay, +3625 initial_max_path_id: None, +3626 ..params +3627 }; +3628 self.set_peer_params(params); +3629 self.qlog.emit_peer_transport_params_restored(self, now); +3630 } +3631 Err(e) => { +3632 error!("session ticket has malformed transport parameters: {}", e); +3633 return; +3634 } +3635 } +3636 } +3637 trace!("0-RTT enabled"); +3638 self.zero_rtt_enabled = true; +3639 self.zero_rtt_crypto = Some(ZeroRttCrypto { header, packet }); +3640 } +3641 +3642 fn read_crypto( +3643 &mut self, +3644 space: SpaceId, +3645 crypto: &frame::Crypto, +3646 payload_len: usize, +3647 ) -> Result<(), TransportError> { +3648 let expected = if !self.state.is_handshake() { +3649 SpaceId::Data +3650 } else if self.highest_space == SpaceId::Initial { +3651 SpaceId::Initial +3652 } else { +3653 // On the server, self.highest_space can be Data after receiving the client's first +3654 // flight, but we expect Handshake CRYPTO until the handshake is complete. +3655 SpaceId::Handshake +3656 }; +3657 // We can't decrypt Handshake packets when highest_space is Initial, CRYPTO frames in 0-RTT +3658 // packets are illegal, and we don't process 1-RTT packets until the handshake is +3659 // complete. Therefore, we will never see CRYPTO data from a later-than-expected space. +3660 debug_assert!(space <= expected, "received out-of-order CRYPTO data"); +3661 +3662 let end = crypto.offset + crypto.data.len() as u64; +3663 if space < expected && end > self.spaces[space].crypto_stream.bytes_read() { +3664 warn!( +3665 "received new {:?} CRYPTO data when expecting {:?}", +3666 space, expected +3667 ); +3668 return Err(TransportError::PROTOCOL_VIOLATION( +3669 "new data at unexpected encryption level", +3670 )); +3671 } +3672 +3673 let space = &mut self.spaces[space]; +3674 let max = end.saturating_sub(space.crypto_stream.bytes_read()); +3675 if max > self.config.crypto_buffer_size as u64 { +3676 return Err(TransportError::CRYPTO_BUFFER_EXCEEDED("")); +3677 } +3678 +3679 space +3680 .crypto_stream +3681 .insert(crypto.offset, crypto.data.clone(), payload_len); +3682 while let Some(chunk) = space.crypto_stream.read(usize::MAX, true) { +3683 trace!("consumed {} CRYPTO bytes", chunk.bytes.len()); +3684 if self.crypto.read_handshake(&chunk.bytes)? { +3685 self.events.push_back(Event::HandshakeDataReady); +3686 } +3687 } +3688 +3689 Ok(()) +3690 } +3691 +3692 fn write_crypto(&mut self) { +3693 loop { +3694 let space = self.highest_space; +3695 let mut outgoing = Vec::new(); +3696 if let Some(crypto) = self.crypto.write_handshake(&mut outgoing) { +3697 match space { +3698 SpaceId::Initial => { +3699 self.upgrade_crypto(SpaceId::Handshake, crypto); +3700 } +3701 SpaceId::Handshake => { +3702 self.upgrade_crypto(SpaceId::Data, crypto); +3703 } +3704 _ => unreachable!("got updated secrets during 1-RTT"), +3705 } +3706 } +3707 if outgoing.is_empty() { +3708 if space == self.highest_space { +3709 break; +3710 } else { +3711 // Keys updated, check for more data to send +3712 continue; +3713 } 3714 } -3715 self.spaces[space].crypto_offset += outgoing.len() as u64; -3716 trace!("wrote {} {:?} CRYPTO bytes", outgoing.len(), space); -3717 self.spaces[space].pending.crypto.push_back(frame::Crypto { -3718 offset, -3719 data: outgoing, -3720 }); -3721 } -3722 } -3723 -3724 /// Switch to stronger cryptography during handshake -3725 fn upgrade_crypto(&mut self, space: SpaceId, crypto: Keys) { -3726 debug_assert!( -3727 self.spaces[space].crypto.is_none(), -3728 "already reached packet space {space:?}" -3729 ); -3730 trace!("{:?} keys ready", space); -3731 if space == SpaceId::Data { -3732 // Precompute the first key update -3733 self.next_crypto = Some( -3734 self.crypto -3735 .next_1rtt_keys() -3736 .expect("handshake should be complete"), -3737 ); -3738 } -3739 -3740 self.spaces[space].crypto = Some(crypto); -3741 debug_assert!(space as usize > self.highest_space as usize); -3742 self.highest_space = space; -3743 if space == SpaceId::Data && self.side.is_client() { -3744 // Discard 0-RTT keys because 1-RTT keys are available. -3745 self.zero_rtt_crypto = None; -3746 } -3747 } +3715 let offset = self.spaces[space].crypto_offset; +3716 let outgoing = Bytes::from(outgoing); +3717 if let Some(hs) = self.state.as_handshake_mut() +3718 && space == SpaceId::Initial +3719 && offset == 0 +3720 && self.side.is_client() +3721 { +3722 hs.client_hello = Some(outgoing.clone()); +3723 } +3724 self.spaces[space].crypto_offset += outgoing.len() as u64; +3725 trace!("wrote {} {:?} CRYPTO bytes", outgoing.len(), space); +3726 self.spaces[space].pending.crypto.push_back(frame::Crypto { +3727 offset, +3728 data: outgoing, +3729 }); +3730 } +3731 } +3732 +3733 /// Switch to stronger cryptography during handshake +3734 fn upgrade_crypto(&mut self, space: SpaceId, crypto: Keys) { +3735 debug_assert!( +3736 self.spaces[space].crypto.is_none(), +3737 "already reached packet space {space:?}" +3738 ); +3739 trace!("{:?} keys ready", space); +3740 if space == SpaceId::Data { +3741 // Precompute the first key update +3742 self.next_crypto = Some( +3743 self.crypto +3744 .next_1rtt_keys() +3745 .expect("handshake should be complete"), +3746 ); +3747 } 3748 -3749 fn discard_space(&mut self, now: Instant, space_id: SpaceId) { -3750 debug_assert!(space_id != SpaceId::Data); -3751 trace!("discarding {:?} keys", space_id); -3752 if space_id == SpaceId::Initial { -3753 // No longer needed -3754 if let ConnectionSide::Client { token, .. } = &mut self.side { -3755 *token = Bytes::new(); -3756 } -3757 } -3758 let space = &mut self.spaces[space_id]; -3759 space.crypto = None; -3760 let pns = space.for_path(PathId::ZERO); -3761 pns.time_of_last_ack_eliciting_packet = None; -3762 pns.loss_time = None; -3763 pns.loss_probes = 0; -3764 let sent_packets = mem::take(&mut pns.sent_packets); -3765 let path = self.paths.get_mut(&PathId::ZERO).unwrap(); -3766 for (_, packet) in sent_packets.into_iter() { -3767 path.data.remove_in_flight(&packet); -3768 } -3769 -3770 self.set_loss_detection_timer(now, PathId::ZERO) -3771 } -3772 -3773 fn handle_coalesced( -3774 &mut self, -3775 now: Instant, -3776 network_path: FourTuple, -3777 path_id: PathId, -3778 ecn: Option<EcnCodepoint>, -3779 data: BytesMut, -3780 ) { -3781 self.path_data_mut(path_id) -3782 .inc_total_recvd(data.len() as u64); -3783 let mut remaining = Some(data); -3784 let cid_len = self -3785 .local_cid_state -3786 .values() -3787 .map(|cid_state| cid_state.cid_len()) -3788 .next() -3789 .expect("one cid_state must exist"); -3790 while let Some(data) = remaining { -3791 match PartialDecode::new( -3792 data, -3793 &FixedLengthConnectionIdParser::new(cid_len), -3794 &[self.version], -3795 self.endpoint_config.grease_quic_bit, -3796 ) { -3797 Ok((partial_decode, rest)) => { -3798 remaining = rest; -3799 self.handle_decode(now, network_path, path_id, ecn, partial_decode); -3800 } -3801 Err(e) => { -3802 trace!("malformed header: {}", e); -3803 return; -3804 } -3805 } -3806 } -3807 } -3808 -3809 fn handle_decode( -3810 &mut self, -3811 now: Instant, -3812 network_path: FourTuple, -3813 path_id: PathId, -3814 ecn: Option<EcnCodepoint>, -3815 partial_decode: PartialDecode, -3816 ) { -3817 let qlog = QlogRecvPacket::new(partial_decode.len()); -3818 if let Some(decoded) = packet_crypto::unprotect_header( -3819 partial_decode, -3820 &self.spaces, -3821 self.zero_rtt_crypto.as_ref(), -3822 self.peer_params.stateless_reset_token, -3823 ) { -3824 self.handle_packet( -3825 now, -3826 network_path, -3827 path_id, -3828 ecn, -3829 decoded.packet, -3830 decoded.stateless_reset, -3831 qlog, -3832 ); -3833 } -3834 } -3835 -3836 fn handle_packet( -3837 &mut self, -3838 now: Instant, -3839 network_path: FourTuple, -3840 path_id: PathId, -3841 ecn: Option<EcnCodepoint>, -3842 packet: Option<Packet>, -3843 stateless_reset: bool, -3844 mut qlog: QlogRecvPacket, -3845 ) { -3846 self.stats.udp_rx.ios += 1; -3847 self.path_stats.entry(path_id).or_default().udp_rx.ios += 1; -3848 -3849 if let Some(ref packet) = packet { -3850 trace!( -3851 "got {:?} packet ({} bytes) from {} using id {}", -3852 packet.header.space(), -3853 packet.payload.len() + packet.header_data.len(), -3854 network_path, -3855 packet.header.dst_cid(), -3856 ); -3857 } -3858 -3859 if self.is_handshaking() { -3860 if path_id != PathId::ZERO { -3861 debug!(%network_path, %path_id, "discarding multipath packet during handshake"); -3862 return; -3863 } -3864 if network_path != self.path_data_mut(path_id).network_path { -3865 if let Some(hs) = self.state.as_handshake() { -3866 if hs.allow_server_migration { -3867 trace!(%network_path, prev = %self.path_data(path_id).network_path, "server migrated to new remote"); -3868 self.path_data_mut(path_id).network_path = network_path; -3869 self.qlog.emit_tuple_assigned(path_id, network_path, now); -3870 } else { -3871 debug!("discarding packet with unexpected remote during handshake"); -3872 return; -3873 } -3874 } else { -3875 debug!("discarding packet with unexpected remote during handshake"); -3876 return; -3877 } -3878 } -3879 } -3880 -3881 let was_closed = self.state.is_closed(); -3882 let was_drained = self.state.is_drained(); -3883 -3884 let decrypted = match packet { -3885 None => Err(None), -3886 Some(mut packet) => self -3887 .decrypt_packet(now, path_id, &mut packet) -3888 .map(move |number| (packet, number)), -3889 }; -3890 let result = match decrypted { -3891 _ if stateless_reset => { -3892 debug!("got stateless reset"); -3893 Err(ConnectionError::Reset) -3894 } -3895 Err(Some(e)) => { -3896 warn!("illegal packet: {}", e); -3897 Err(e.into()) -3898 } -3899 Err(None) => { -3900 debug!("failed to authenticate packet"); -3901 self.authentication_failures += 1; -3902 let integrity_limit = self.spaces[self.highest_space] -3903 .crypto -3904 .as_ref() -3905 .unwrap() -3906 .packet -3907 .local -3908 .integrity_limit(); -3909 if self.authentication_failures > integrity_limit { -3910 Err(TransportError::AEAD_LIMIT_REACHED("integrity limit violated").into()) -3911 } else { -3912 return; -3913 } -3914 } -3915 Ok((packet, number)) => { -3916 qlog.header(&packet.header, number, path_id); -3917 let span = match number { -3918 Some(pn) => trace_span!("recv", space = ?packet.header.space(), pn), -3919 None => trace_span!("recv", space = ?packet.header.space()), -3920 }; -3921 let _guard = span.enter(); -3922 -3923 let dedup = self.spaces[packet.header.space()] -3924 .path_space_mut(path_id) -3925 .map(|pns| &mut pns.dedup); -3926 if number.zip(dedup).is_some_and(|(n, d)| d.insert(n)) { -3927 debug!("discarding possible duplicate packet"); -3928 self.qlog.emit_packet_received(qlog, now); -3929 return; -3930 } else if self.state.is_handshake() && packet.header.is_short() { -3931 // TODO: SHOULD buffer these to improve reordering tolerance. -3932 trace!("dropping short packet during handshake"); -3933 self.qlog.emit_packet_received(qlog, now); -3934 return; -3935 } else { -3936 if let Header::Initial(InitialHeader { ref token, .. }) = packet.header -3937 && let Some(hs) = self.state.as_handshake() -3938 && self.side.is_server() -3939 && token != &hs.expected_token -3940 { -3941 // Clients must send the same retry token in every Initial. Initial -3942 // packets can be spoofed, so we discard rather than killing the -3943 // connection. -3944 warn!("discarding Initial with invalid retry token"); -3945 self.qlog.emit_packet_received(qlog, now); -3946 return; -3947 } -3948 -3949 if !self.state.is_closed() { -3950 let spin = match packet.header { -3951 Header::Short { spin, .. } => spin, -3952 _ => false, -3953 }; -3954 -3955 if self.side().is_server() && !self.abandoned_paths.contains(&path_id) { -3956 // Only the client is allowed to open paths -3957 self.ensure_path(path_id, network_path, now, number); -3958 } -3959 if self.paths.contains_key(&path_id) { -3960 self.on_packet_authenticated( -3961 now, -3962 packet.header.space(), -3963 path_id, -3964 ecn, -3965 number, -3966 spin, -3967 packet.header.is_1rtt(), -3968 ); -3969 } -3970 } -3971 -3972 let res = self.process_decrypted_packet( -3973 now, -3974 network_path, -3975 path_id, -3976 number, -3977 packet, -3978 &mut qlog, -3979 ); +3749 self.spaces[space].crypto = Some(crypto); +3750 debug_assert!(space as usize > self.highest_space as usize); +3751 self.highest_space = space; +3752 if space == SpaceId::Data && self.side.is_client() { +3753 // Discard 0-RTT keys because 1-RTT keys are available. +3754 self.zero_rtt_crypto = None; +3755 } +3756 } +3757 +3758 fn discard_space(&mut self, now: Instant, space_id: SpaceId) { +3759 debug_assert!(space_id != SpaceId::Data); +3760 trace!("discarding {:?} keys", space_id); +3761 if space_id == SpaceId::Initial { +3762 // No longer needed +3763 if let ConnectionSide::Client { token, .. } = &mut self.side { +3764 *token = Bytes::new(); +3765 } +3766 } +3767 let space = &mut self.spaces[space_id]; +3768 space.crypto = None; +3769 let pns = space.for_path(PathId::ZERO); +3770 pns.time_of_last_ack_eliciting_packet = None; +3771 pns.loss_time = None; +3772 pns.loss_probes = 0; +3773 let sent_packets = mem::take(&mut pns.sent_packets); +3774 let path = self.paths.get_mut(&PathId::ZERO).unwrap(); +3775 for (_, packet) in sent_packets.into_iter() { +3776 path.data.remove_in_flight(&packet); +3777 } +3778 +3779 self.set_loss_detection_timer(now, PathId::ZERO) +3780 } +3781 +3782 fn handle_coalesced( +3783 &mut self, +3784 now: Instant, +3785 network_path: FourTuple, +3786 path_id: PathId, +3787 ecn: Option<EcnCodepoint>, +3788 data: BytesMut, +3789 ) { +3790 self.path_data_mut(path_id) +3791 .inc_total_recvd(data.len() as u64); +3792 let mut remaining = Some(data); +3793 let cid_len = self +3794 .local_cid_state +3795 .values() +3796 .map(|cid_state| cid_state.cid_len()) +3797 .next() +3798 .expect("one cid_state must exist"); +3799 while let Some(data) = remaining { +3800 match PartialDecode::new( +3801 data, +3802 &FixedLengthConnectionIdParser::new(cid_len), +3803 &[self.version], +3804 self.endpoint_config.grease_quic_bit, +3805 ) { +3806 Ok((partial_decode, rest)) => { +3807 remaining = rest; +3808 self.handle_decode(now, network_path, path_id, ecn, partial_decode); +3809 } +3810 Err(e) => { +3811 trace!("malformed header: {}", e); +3812 return; +3813 } +3814 } +3815 } +3816 } +3817 +3818 fn handle_decode( +3819 &mut self, +3820 now: Instant, +3821 network_path: FourTuple, +3822 path_id: PathId, +3823 ecn: Option<EcnCodepoint>, +3824 partial_decode: PartialDecode, +3825 ) { +3826 let qlog = QlogRecvPacket::new(partial_decode.len()); +3827 if let Some(decoded) = packet_crypto::unprotect_header( +3828 partial_decode, +3829 &self.spaces, +3830 self.zero_rtt_crypto.as_ref(), +3831 self.peer_params.stateless_reset_token, +3832 ) { +3833 self.handle_packet( +3834 now, +3835 network_path, +3836 path_id, +3837 ecn, +3838 decoded.packet, +3839 decoded.stateless_reset, +3840 qlog, +3841 ); +3842 } +3843 } +3844 +3845 fn handle_packet( +3846 &mut self, +3847 now: Instant, +3848 network_path: FourTuple, +3849 path_id: PathId, +3850 ecn: Option<EcnCodepoint>, +3851 packet: Option<Packet>, +3852 stateless_reset: bool, +3853 mut qlog: QlogRecvPacket, +3854 ) { +3855 self.stats.udp_rx.ios += 1; +3856 self.path_stats.entry(path_id).or_default().udp_rx.ios += 1; +3857 +3858 if let Some(ref packet) = packet { +3859 trace!( +3860 "got {:?} packet ({} bytes) from {} using id {}", +3861 packet.header.space(), +3862 packet.payload.len() + packet.header_data.len(), +3863 network_path, +3864 packet.header.dst_cid(), +3865 ); +3866 } +3867 +3868 if self.is_handshaking() { +3869 if path_id != PathId::ZERO { +3870 debug!(%network_path, %path_id, "discarding multipath packet during handshake"); +3871 return; +3872 } +3873 if network_path != self.path_data_mut(path_id).network_path { +3874 if let Some(hs) = self.state.as_handshake() { +3875 if hs.allow_server_migration { +3876 trace!(%network_path, prev = %self.path_data(path_id).network_path, "server migrated to new remote"); +3877 self.path_data_mut(path_id).network_path = network_path; +3878 self.qlog.emit_tuple_assigned(path_id, network_path, now); +3879 } else { +3880 debug!("discarding packet with unexpected remote during handshake"); +3881 return; +3882 } +3883 } else { +3884 debug!("discarding packet with unexpected remote during handshake"); +3885 return; +3886 } +3887 } +3888 } +3889 +3890 let was_closed = self.state.is_closed(); +3891 let was_drained = self.state.is_drained(); +3892 +3893 let decrypted = match packet { +3894 None => Err(None), +3895 Some(mut packet) => self +3896 .decrypt_packet(now, path_id, &mut packet) +3897 .map(move |number| (packet, number)), +3898 }; +3899 let result = match decrypted { +3900 _ if stateless_reset => { +3901 debug!("got stateless reset"); +3902 Err(ConnectionError::Reset) +3903 } +3904 Err(Some(e)) => { +3905 warn!("illegal packet: {}", e); +3906 Err(e.into()) +3907 } +3908 Err(None) => { +3909 debug!("failed to authenticate packet"); +3910 self.authentication_failures += 1; +3911 let integrity_limit = self.spaces[self.highest_space] +3912 .crypto +3913 .as_ref() +3914 .unwrap() +3915 .packet +3916 .local +3917 .integrity_limit(); +3918 if self.authentication_failures > integrity_limit { +3919 Err(TransportError::AEAD_LIMIT_REACHED("integrity limit violated").into()) +3920 } else { +3921 return; +3922 } +3923 } +3924 Ok((packet, number)) => { +3925 qlog.header(&packet.header, number, path_id); +3926 let span = match number { +3927 Some(pn) => trace_span!("recv", space = ?packet.header.space(), pn), +3928 None => trace_span!("recv", space = ?packet.header.space()), +3929 }; +3930 let _guard = span.enter(); +3931 +3932 let dedup = self.spaces[packet.header.space()] +3933 .path_space_mut(path_id) +3934 .map(|pns| &mut pns.dedup); +3935 if number.zip(dedup).is_some_and(|(n, d)| d.insert(n)) { +3936 debug!("discarding possible duplicate packet"); +3937 self.qlog.emit_packet_received(qlog, now); +3938 return; +3939 } else if self.state.is_handshake() && packet.header.is_short() { +3940 // TODO: SHOULD buffer these to improve reordering tolerance. +3941 trace!("dropping short packet during handshake"); +3942 self.qlog.emit_packet_received(qlog, now); +3943 return; +3944 } else { +3945 if let Header::Initial(InitialHeader { ref token, .. }) = packet.header +3946 && let Some(hs) = self.state.as_handshake() +3947 && self.side.is_server() +3948 && token != &hs.expected_token +3949 { +3950 // Clients must send the same retry token in every Initial. Initial +3951 // packets can be spoofed, so we discard rather than killing the +3952 // connection. +3953 warn!("discarding Initial with invalid retry token"); +3954 self.qlog.emit_packet_received(qlog, now); +3955 return; +3956 } +3957 +3958 if !self.state.is_closed() { +3959 let spin = match packet.header { +3960 Header::Short { spin, .. } => spin, +3961 _ => false, +3962 }; +3963 +3964 if self.side().is_server() && !self.abandoned_paths.contains(&path_id) { +3965 // Only the client is allowed to open paths +3966 self.ensure_path(path_id, network_path, now, number); +3967 } +3968 if self.paths.contains_key(&path_id) { +3969 self.on_packet_authenticated( +3970 now, +3971 packet.header.space(), +3972 path_id, +3973 ecn, +3974 number, +3975 spin, +3976 packet.header.is_1rtt(), +3977 ); +3978 } +3979 } 3980 -3981 self.qlog.emit_packet_received(qlog, now); -3982 res -3983 } -3984 } -3985 }; -3986 -3987 // State transitions for error cases -3988 if let Err(conn_err) = result { -3989 match conn_err { -3990 ConnectionError::ApplicationClosed(reason) => self.state.move_to_closed(reason), -3991 ConnectionError::ConnectionClosed(reason) => self.state.move_to_closed(reason), -3992 ConnectionError::Reset -3993 | ConnectionError::TransportError(TransportError { -3994 code: TransportErrorCode::AEAD_LIMIT_REACHED, -3995 .. -3996 }) => { -3997 self.state.move_to_drained(Some(conn_err)); -3998 } -3999 ConnectionError::TimedOut => { -4000 unreachable!("timeouts aren't generated by packet processing"); -4001 } -4002 ConnectionError::TransportError(err) => { -4003 debug!("closing connection due to transport error: {}", err); -4004 self.state.move_to_closed(err); -4005 } -4006 ConnectionError::VersionMismatch => { -4007 self.state.move_to_draining(Some(conn_err)); -4008 } -4009 ConnectionError::LocallyClosed => { -4010 unreachable!("LocallyClosed isn't generated by packet processing"); -4011 } -4012 ConnectionError::CidsExhausted => { -4013 unreachable!("CidsExhausted isn't generated by packet processing"); +3981 let res = self.process_decrypted_packet( +3982 now, +3983 network_path, +3984 path_id, +3985 number, +3986 packet, +3987 &mut qlog, +3988 ); +3989 +3990 self.qlog.emit_packet_received(qlog, now); +3991 res +3992 } +3993 } +3994 }; +3995 +3996 // State transitions for error cases +3997 if let Err(conn_err) = result { +3998 match conn_err { +3999 ConnectionError::ApplicationClosed(reason) => self.state.move_to_closed(reason), +4000 ConnectionError::ConnectionClosed(reason) => self.state.move_to_closed(reason), +4001 ConnectionError::Reset +4002 | ConnectionError::TransportError(TransportError { +4003 code: TransportErrorCode::AEAD_LIMIT_REACHED, +4004 .. +4005 }) => { +4006 self.state.move_to_drained(Some(conn_err)); +4007 } +4008 ConnectionError::TimedOut => { +4009 unreachable!("timeouts aren't generated by packet processing"); +4010 } +4011 ConnectionError::TransportError(err) => { +4012 debug!("closing connection due to transport error: {}", err); +4013 self.state.move_to_closed(err); 4014 } -4015 }; -4016 } -4017 -4018 if !was_closed && self.state.is_closed() { -4019 self.close_common(); -4020 if !self.state.is_drained() { -4021 self.set_close_timer(now); -4022 } -4023 } -4024 if !was_drained && self.state.is_drained() { -4025 self.endpoint_events.push_back(EndpointEventInner::Drained); -4026 // Close timer may have been started previously, e.g. if we sent a close and got a -4027 // stateless reset in response -4028 self.timers -4029 .stop(Timer::Conn(ConnTimer::Close), self.qlog.with_time(now)); -4030 } -4031 -4032 // Transmit CONNECTION_CLOSE if necessary -4033 if matches!(self.state.as_type(), StateType::Closed) { -4034 // If there is no PathData for this PathId the packet was for a brand new -4035 // path. It was a valid packet however, so the remote is valid and we want to -4036 // send CONNECTION_CLOSE. -4037 let path_remote = self -4038 .paths -4039 .get(&path_id) -4040 .map(|p| p.data.network_path) -4041 .unwrap_or(network_path); -4042 self.connection_close_pending = network_path == path_remote; -4043 } -4044 } -4045 -4046 fn process_decrypted_packet( -4047 &mut self, -4048 now: Instant, -4049 network_path: FourTuple, -4050 path_id: PathId, -4051 number: Option<u64>, -4052 packet: Packet, -4053 qlog: &mut QlogRecvPacket, -4054 ) -> Result<(), ConnectionError> { -4055 if !self.paths.contains_key(&path_id) { -4056 // There is a chance this is a server side, first (for this path) packet, which would -4057 // be a protocol violation. It's more likely, however, that this is a packet of a -4058 // pruned path -4059 trace!(%path_id, ?number, "discarding packet for unknown path"); -4060 return Ok(()); -4061 } -4062 let state = match self.state.as_type() { -4063 StateType::Established => { -4064 match packet.header.space() { -4065 SpaceId::Data => self.process_payload( -4066 now, -4067 network_path, -4068 path_id, -4069 number.unwrap(), -4070 packet, -4071 qlog, -4072 )?, -4073 _ if packet.header.has_frames() => { -4074 self.process_early_payload(now, path_id, packet, qlog)? -4075 } -4076 _ => { -4077 trace!("discarding unexpected pre-handshake packet"); -4078 } -4079 } -4080 return Ok(()); -4081 } -4082 StateType::Closed => { -4083 for result in frame::Iter::new(packet.payload.freeze())? { -4084 let frame = match result { -4085 Ok(frame) => frame, -4086 Err(err) => { -4087 debug!("frame decoding error: {err:?}"); -4088 continue; -4089 } -4090 }; -4091 qlog.frame(&frame); -4092 -4093 if let Frame::Padding = frame { -4094 continue; -4095 }; -4096 -4097 self.stats.frame_rx.record(frame.ty()); -4098 -4099 if let Frame::Close(_error) = frame { -4100 self.state.move_to_draining(None); -4101 break; -4102 } -4103 } -4104 return Ok(()); -4105 } -4106 StateType::Draining | StateType::Drained => return Ok(()), -4107 StateType::Handshake => self.state.as_handshake_mut().expect("checked"), -4108 }; -4109 -4110 match packet.header { -4111 Header::Retry { -4112 src_cid: remote_cid, -4113 .. -4114 } => { -4115 debug_assert_eq!(path_id, PathId::ZERO); -4116 if self.side.is_server() { -4117 return Err(TransportError::PROTOCOL_VIOLATION("client sent Retry").into()); -4118 } -4119 -4120 let is_valid_retry = self -4121 .remote_cids -4122 .get(&path_id) -4123 .map(|cids| cids.active()) -4124 .map(|orig_dst_cid| { -4125 self.crypto.is_valid_retry( -4126 orig_dst_cid, -4127 &packet.header_data, -4128 &packet.payload, -4129 ) -4130 }) -4131 .unwrap_or_default(); -4132 if self.total_authed_packets > 1 -4133 || packet.payload.len() <= 16 // token + 16 byte tag -4134 || !is_valid_retry -4135 { -4136 trace!("discarding invalid Retry"); -4137 // - After the client has received and processed an Initial or Retry -4138 // packet from the server, it MUST discard any subsequent Retry -4139 // packets that it receives. -4140 // - A client MUST discard a Retry packet with a zero-length Retry Token -4141 // field. -4142 // - Clients MUST discard Retry packets that have a Retry Integrity Tag -4143 // that cannot be validated -4144 return Ok(()); -4145 } -4146 -4147 trace!("retrying with CID {}", remote_cid); -4148 let client_hello = state.client_hello.take().unwrap(); -4149 self.retry_src_cid = Some(remote_cid); -4150 self.remote_cids -4151 .get_mut(&path_id) -4152 .expect("PathId::ZERO not yet abandoned, is_valid_retry would have been false") -4153 .update_initial_cid(remote_cid); -4154 self.remote_handshake_cid = remote_cid; +4015 ConnectionError::VersionMismatch => { +4016 self.state.move_to_draining(Some(conn_err)); +4017 } +4018 ConnectionError::LocallyClosed => { +4019 unreachable!("LocallyClosed isn't generated by packet processing"); +4020 } +4021 ConnectionError::CidsExhausted => { +4022 unreachable!("CidsExhausted isn't generated by packet processing"); +4023 } +4024 }; +4025 } +4026 +4027 if !was_closed && self.state.is_closed() { +4028 self.close_common(); +4029 if !self.state.is_drained() { +4030 self.set_close_timer(now); +4031 } +4032 } +4033 if !was_drained && self.state.is_drained() { +4034 self.endpoint_events.push_back(EndpointEventInner::Drained); +4035 // Close timer may have been started previously, e.g. if we sent a close and got a +4036 // stateless reset in response +4037 self.timers +4038 .stop(Timer::Conn(ConnTimer::Close), self.qlog.with_time(now)); +4039 } +4040 +4041 // Transmit CONNECTION_CLOSE if necessary +4042 if matches!(self.state.as_type(), StateType::Closed) { +4043 // If there is no PathData for this PathId the packet was for a brand new +4044 // path. It was a valid packet however, so the remote is valid and we want to +4045 // send CONNECTION_CLOSE. +4046 let path_remote = self +4047 .paths +4048 .get(&path_id) +4049 .map(|p| p.data.network_path) +4050 .unwrap_or(network_path); +4051 self.connection_close_pending = network_path == path_remote; +4052 } +4053 } +4054 +4055 fn process_decrypted_packet( +4056 &mut self, +4057 now: Instant, +4058 network_path: FourTuple, +4059 path_id: PathId, +4060 number: Option<u64>, +4061 packet: Packet, +4062 qlog: &mut QlogRecvPacket, +4063 ) -> Result<(), ConnectionError> { +4064 if !self.paths.contains_key(&path_id) { +4065 // There is a chance this is a server side, first (for this path) packet, which would +4066 // be a protocol violation. It's more likely, however, that this is a packet of a +4067 // pruned path +4068 trace!(%path_id, ?number, "discarding packet for unknown path"); +4069 return Ok(()); +4070 } +4071 let state = match self.state.as_type() { +4072 StateType::Established => { +4073 match packet.header.space() { +4074 SpaceId::Data => self.process_payload( +4075 now, +4076 network_path, +4077 path_id, +4078 number.unwrap(), +4079 packet, +4080 qlog, +4081 )?, +4082 _ if packet.header.has_frames() => { +4083 self.process_early_payload(now, path_id, packet, qlog)? +4084 } +4085 _ => { +4086 trace!("discarding unexpected pre-handshake packet"); +4087 } +4088 } +4089 return Ok(()); +4090 } +4091 StateType::Closed => { +4092 for result in frame::Iter::new(packet.payload.freeze())? { +4093 let frame = match result { +4094 Ok(frame) => frame, +4095 Err(err) => { +4096 debug!("frame decoding error: {err:?}"); +4097 continue; +4098 } +4099 }; +4100 qlog.frame(&frame); +4101 +4102 if let Frame::Padding = frame { +4103 continue; +4104 }; +4105 +4106 self.stats.frame_rx.record(frame.ty()); +4107 +4108 if let Frame::Close(_error) = frame { +4109 self.state.move_to_draining(None); +4110 break; +4111 } +4112 } +4113 return Ok(()); +4114 } +4115 StateType::Draining | StateType::Drained => return Ok(()), +4116 StateType::Handshake => self.state.as_handshake_mut().expect("checked"), +4117 }; +4118 +4119 match packet.header { +4120 Header::Retry { +4121 src_cid: remote_cid, +4122 .. +4123 } => { +4124 debug_assert_eq!(path_id, PathId::ZERO); +4125 if self.side.is_server() { +4126 return Err(TransportError::PROTOCOL_VIOLATION("client sent Retry").into()); +4127 } +4128 +4129 let is_valid_retry = self +4130 .remote_cids +4131 .get(&path_id) +4132 .map(|cids| cids.active()) +4133 .map(|orig_dst_cid| { +4134 self.crypto.is_valid_retry( +4135 orig_dst_cid, +4136 &packet.header_data, +4137 &packet.payload, +4138 ) +4139 }) +4140 .unwrap_or_default(); +4141 if self.total_authed_packets > 1 +4142 || packet.payload.len() <= 16 // token + 16 byte tag +4143 || !is_valid_retry +4144 { +4145 trace!("discarding invalid Retry"); +4146 // - After the client has received and processed an Initial or Retry +4147 // packet from the server, it MUST discard any subsequent Retry +4148 // packets that it receives. +4149 // - A client MUST discard a Retry packet with a zero-length Retry Token +4150 // field. +4151 // - Clients MUST discard Retry packets that have a Retry Integrity Tag +4152 // that cannot be validated +4153 return Ok(()); +4154 } 4155 -4156 let space = &mut self.spaces[SpaceId::Initial]; -4157 if let Some(info) = space.for_path(PathId::ZERO).take(0) { -4158 self.on_packet_acked(now, PathId::ZERO, info); -4159 }; -4160 -4161 self.discard_space(now, SpaceId::Initial); // Make sure we clean up after -4162 // any retransmitted Initials -4163 self.spaces[SpaceId::Initial] = { -4164 let mut space = PacketSpace::new(now, SpaceId::Initial, &mut self.rng); -4165 space.crypto = Some(self.crypto.initial_keys(remote_cid, self.side.side())); -4166 space.crypto_offset = client_hello.len() as u64; -4167 space.for_path(path_id).next_packet_number = self.spaces[SpaceId::Initial] -4168 .for_path(path_id) -4169 .next_packet_number; -4170 space.pending.crypto.push_back(frame::Crypto { -4171 offset: 0, -4172 data: client_hello, -4173 }); -4174 space -4175 }; -4176 -4177 // Retransmit all 0-RTT data -4178 let zero_rtt = mem::take( -4179 &mut self.spaces[SpaceId::Data] -4180 .for_path(PathId::ZERO) -4181 .sent_packets, -4182 ); -4183 for (_, info) in zero_rtt.into_iter() { -4184 self.paths -4185 .get_mut(&PathId::ZERO) -4186 .unwrap() -4187 .remove_in_flight(&info); -4188 self.spaces[SpaceId::Data].pending |= info.retransmits; -4189 } -4190 self.streams.retransmit_all_for_0rtt(); -4191 -4192 let token_len = packet.payload.len() - 16; -4193 let ConnectionSide::Client { ref mut token, .. } = self.side else { -4194 unreachable!("we already short-circuited if we're server"); -4195 }; -4196 *token = packet.payload.freeze().split_to(token_len); -4197 -4198 self.state = State::handshake(state::Handshake { -4199 expected_token: Bytes::new(), -4200 remote_cid_set: false, -4201 client_hello: None, -4202 allow_server_migration: true, -4203 }); -4204 Ok(()) -4205 } -4206 Header::Long { -4207 ty: LongType::Handshake, -4208 src_cid: remote_cid, -4209 dst_cid: local_cid, -4210 .. -4211 } => { -4212 debug_assert_eq!(path_id, PathId::ZERO); -4213 if remote_cid != self.remote_handshake_cid { -4214 debug!( -4215 "discarding packet with mismatched remote CID: {} != {}", -4216 self.remote_handshake_cid, remote_cid -4217 ); -4218 return Ok(()); -4219 } -4220 self.on_path_validated(path_id); -4221 -4222 self.process_early_payload(now, path_id, packet, qlog)?; -4223 if self.state.is_closed() { -4224 return Ok(()); -4225 } -4226 -4227 if self.crypto.is_handshaking() { -4228 trace!("handshake ongoing"); -4229 return Ok(()); -4230 } -4231 -4232 if self.side.is_client() { -4233 // Client-only because server params were set from the client's Initial -4234 let params = self.crypto.transport_parameters()?.ok_or_else(|| { -4235 TransportError::new( -4236 TransportErrorCode::crypto(0x6d), -4237 "transport parameters missing".to_owned(), -4238 ) -4239 })?; +4156 trace!("retrying with CID {}", remote_cid); +4157 let client_hello = state.client_hello.take().unwrap(); +4158 self.retry_src_cid = Some(remote_cid); +4159 self.remote_cids +4160 .get_mut(&path_id) +4161 .expect("PathId::ZERO not yet abandoned, is_valid_retry would have been false") +4162 .update_initial_cid(remote_cid); +4163 self.remote_handshake_cid = remote_cid; +4164 +4165 let space = &mut self.spaces[SpaceId::Initial]; +4166 if let Some(info) = space.for_path(PathId::ZERO).take(0) { +4167 self.on_packet_acked(now, PathId::ZERO, info); +4168 }; +4169 +4170 self.discard_space(now, SpaceId::Initial); // Make sure we clean up after +4171 // any retransmitted Initials +4172 self.spaces[SpaceId::Initial] = { +4173 let mut space = PacketSpace::new(now, SpaceId::Initial, &mut self.rng); +4174 space.crypto = Some(self.crypto.initial_keys(remote_cid, self.side.side())); +4175 space.crypto_offset = client_hello.len() as u64; +4176 space.for_path(path_id).next_packet_number = self.spaces[SpaceId::Initial] +4177 .for_path(path_id) +4178 .next_packet_number; +4179 space.pending.crypto.push_back(frame::Crypto { +4180 offset: 0, +4181 data: client_hello, +4182 }); +4183 space +4184 }; +4185 +4186 // Retransmit all 0-RTT data +4187 let zero_rtt = mem::take( +4188 &mut self.spaces[SpaceId::Data] +4189 .for_path(PathId::ZERO) +4190 .sent_packets, +4191 ); +4192 for (_, info) in zero_rtt.into_iter() { +4193 self.paths +4194 .get_mut(&PathId::ZERO) +4195 .unwrap() +4196 .remove_in_flight(&info); +4197 self.spaces[SpaceId::Data].pending |= info.retransmits; +4198 } +4199 self.streams.retransmit_all_for_0rtt(); +4200 +4201 let token_len = packet.payload.len() - 16; +4202 let ConnectionSide::Client { ref mut token, .. } = self.side else { +4203 unreachable!("we already short-circuited if we're server"); +4204 }; +4205 *token = packet.payload.freeze().split_to(token_len); +4206 +4207 self.state = State::handshake(state::Handshake { +4208 expected_token: Bytes::new(), +4209 remote_cid_set: false, +4210 client_hello: None, +4211 allow_server_migration: true, +4212 }); +4213 Ok(()) +4214 } +4215 Header::Long { +4216 ty: LongType::Handshake, +4217 src_cid: remote_cid, +4218 dst_cid: local_cid, +4219 .. +4220 } => { +4221 debug_assert_eq!(path_id, PathId::ZERO); +4222 if remote_cid != self.remote_handshake_cid { +4223 debug!( +4224 "discarding packet with mismatched remote CID: {} != {}", +4225 self.remote_handshake_cid, remote_cid +4226 ); +4227 return Ok(()); +4228 } +4229 self.on_path_validated(path_id); +4230 +4231 self.process_early_payload(now, path_id, packet, qlog)?; +4232 if self.state.is_closed() { +4233 return Ok(()); +4234 } +4235 +4236 if self.crypto.is_handshaking() { +4237 trace!("handshake ongoing"); +4238 return Ok(()); +4239 } 4240 -4241 if self.has_0rtt() { -4242 if !self.crypto.early_data_accepted().unwrap() { -4243 debug_assert!(self.side.is_client()); -4244 debug!("0-RTT rejected"); -4245 self.accepted_0rtt = false; -4246 self.streams.zero_rtt_rejected(); -4247 -4248 // Discard already-queued frames -4249 self.spaces[SpaceId::Data].pending = Retransmits::default(); -4250 -4251 // Discard 0-RTT packets -4252 let sent_packets = mem::take( -4253 &mut self.spaces[SpaceId::Data].for_path(path_id).sent_packets, -4254 ); -4255 for (_, packet) in sent_packets.into_iter() { -4256 self.paths -4257 .get_mut(&path_id) -4258 .unwrap() -4259 .remove_in_flight(&packet); -4260 } -4261 } else { -4262 self.accepted_0rtt = true; -4263 params.validate_resumption_from(&self.peer_params)?; -4264 } -4265 } -4266 if let Some(token) = params.stateless_reset_token { -4267 // TODO(matheus23): Reset token for a remote, or for a 4-tuple? -4268 let remote = self.path_data(path_id).network_path.remote; -4269 self.endpoint_events -4270 .push_back(EndpointEventInner::ResetToken(path_id, remote, token)); -4271 } -4272 self.handle_peer_params(params, local_cid, remote_cid, now)?; -4273 self.issue_first_cids(now); -4274 } else { -4275 // Server-only -4276 self.spaces[SpaceId::Data].pending.handshake_done = true; -4277 self.discard_space(now, SpaceId::Handshake); -4278 self.events.push_back(Event::HandshakeConfirmed); -4279 trace!("handshake confirmed"); -4280 } -4281 -4282 self.events.push_back(Event::Connected); -4283 self.state.move_to_established(); -4284 trace!("established"); -4285 -4286 // Multipath can only be enabled after the state has reached Established. -4287 // So this can not happen any earlier. -4288 self.issue_first_path_cids(now); -4289 Ok(()) -4290 } -4291 Header::Initial(InitialHeader { -4292 src_cid: remote_cid, -4293 dst_cid: local_cid, -4294 .. -4295 }) => { -4296 debug_assert_eq!(path_id, PathId::ZERO); -4297 if !state.remote_cid_set { -4298 trace!("switching remote CID to {}", remote_cid); -4299 let mut state = state.clone(); -4300 self.remote_cids -4301 .get_mut(&path_id) -4302 .expect("PathId::ZERO not yet abandoned") -4303 .update_initial_cid(remote_cid); -4304 self.remote_handshake_cid = remote_cid; -4305 self.original_remote_cid = remote_cid; -4306 state.remote_cid_set = true; -4307 self.state.move_to_handshake(state); -4308 } else if remote_cid != self.remote_handshake_cid { -4309 debug!( -4310 "discarding packet with mismatched remote CID: {} != {}", -4311 self.remote_handshake_cid, remote_cid -4312 ); -4313 return Ok(()); -4314 } -4315 -4316 let starting_space = self.highest_space; -4317 self.process_early_payload(now, path_id, packet, qlog)?; -4318 -4319 if self.side.is_server() -4320 && starting_space == SpaceId::Initial -4321 && self.highest_space != SpaceId::Initial -4322 { -4323 let params = self.crypto.transport_parameters()?.ok_or_else(|| { -4324 TransportError::new( -4325 TransportErrorCode::crypto(0x6d), -4326 "transport parameters missing".to_owned(), -4327 ) -4328 })?; -4329 self.handle_peer_params(params, local_cid, remote_cid, now)?; -4330 self.issue_first_cids(now); -4331 self.init_0rtt(now); -4332 } -4333 Ok(()) -4334 } -4335 Header::Long { -4336 ty: LongType::ZeroRtt, -4337 .. -4338 } => { -4339 self.process_payload(now, network_path, path_id, number.unwrap(), packet, qlog)?; -4340 Ok(()) -4341 } -4342 Header::VersionNegotiate { .. } => { -4343 if self.total_authed_packets > 1 { -4344 return Ok(()); -4345 } -4346 let supported = packet -4347 .payload -4348 .chunks(4) -4349 .any(|x| match <[u8; 4]>::try_from(x) { -4350 Ok(version) => self.version == u32::from_be_bytes(version), -4351 Err(_) => false, -4352 }); -4353 if supported { -4354 return Ok(()); -4355 } -4356 debug!("remote doesn't support our version"); -4357 Err(ConnectionError::VersionMismatch) -4358 } -4359 Header::Short { .. } => unreachable!( -4360 "short packets received during handshake are discarded in handle_packet" -4361 ), -4362 } -4363 } -4364 -4365 /// Process an Initial or Handshake packet payload -4366 fn process_early_payload( -4367 &mut self, -4368 now: Instant, -4369 path_id: PathId, -4370 packet: Packet, -4371 #[allow(unused)] qlog: &mut QlogRecvPacket, -4372 ) -> Result<(), TransportError> { -4373 debug_assert_ne!(packet.header.space(), SpaceId::Data); -4374 debug_assert_eq!(path_id, PathId::ZERO); -4375 let payload_len = packet.payload.len(); -4376 let mut ack_eliciting = false; -4377 for result in frame::Iter::new(packet.payload.freeze())? { -4378 let frame = result?; -4379 qlog.frame(&frame); -4380 let span = match frame { -4381 Frame::Padding => continue, -4382 _ => Some(trace_span!("frame", ty = %frame.ty(), path = tracing::field::Empty)), -4383 }; -4384 -4385 self.stats.frame_rx.record(frame.ty()); -4386 -4387 let _guard = span.as_ref().map(|x| x.enter()); -4388 ack_eliciting |= frame.is_ack_eliciting(); -4389 -4390 // Process frames -4391 if frame.is_1rtt() && packet.header.space() != SpaceId::Data { -4392 return Err(TransportError::PROTOCOL_VIOLATION( -4393 "illegal frame type in handshake", -4394 )); -4395 } -4396 -4397 match frame { -4398 Frame::Padding | Frame::Ping => {} -4399 Frame::Crypto(frame) => { -4400 self.read_crypto(packet.header.space(), &frame, payload_len)?; -4401 } -4402 Frame::Ack(ack) => { -4403 self.on_ack_received(now, packet.header.space(), ack)?; -4404 } -4405 Frame::PathAck(ack) => { -4406 span.as_ref() -4407 .map(|span| span.record("path", tracing::field::debug(&ack.path_id))); -4408 self.on_path_ack_received(now, packet.header.space(), ack)?; -4409 } -4410 Frame::Close(reason) => { -4411 self.state.move_to_draining(Some(reason.into())); -4412 return Ok(()); +4241 if self.side.is_client() { +4242 // Client-only because server params were set from the client's Initial +4243 let params = self.crypto.transport_parameters()?.ok_or_else(|| { +4244 TransportError::new( +4245 TransportErrorCode::crypto(0x6d), +4246 "transport parameters missing".to_owned(), +4247 ) +4248 })?; +4249 +4250 if self.has_0rtt() { +4251 if !self.crypto.early_data_accepted().unwrap() { +4252 debug_assert!(self.side.is_client()); +4253 debug!("0-RTT rejected"); +4254 self.accepted_0rtt = false; +4255 self.streams.zero_rtt_rejected(); +4256 +4257 // Discard already-queued frames +4258 self.spaces[SpaceId::Data].pending = Retransmits::default(); +4259 +4260 // Discard 0-RTT packets +4261 let sent_packets = mem::take( +4262 &mut self.spaces[SpaceId::Data].for_path(path_id).sent_packets, +4263 ); +4264 for (_, packet) in sent_packets.into_iter() { +4265 self.paths +4266 .get_mut(&path_id) +4267 .unwrap() +4268 .remove_in_flight(&packet); +4269 } +4270 } else { +4271 self.accepted_0rtt = true; +4272 params.validate_resumption_from(&self.peer_params)?; +4273 } +4274 } +4275 if let Some(token) = params.stateless_reset_token { +4276 // TODO(matheus23): Reset token for a remote, or for a 4-tuple? +4277 let remote = self.path_data(path_id).network_path.remote; +4278 self.endpoint_events +4279 .push_back(EndpointEventInner::ResetToken(path_id, remote, token)); +4280 } +4281 self.handle_peer_params(params, local_cid, remote_cid, now)?; +4282 self.issue_first_cids(now); +4283 } else { +4284 // Server-only +4285 self.spaces[SpaceId::Data].pending.handshake_done = true; +4286 self.discard_space(now, SpaceId::Handshake); +4287 self.events.push_back(Event::HandshakeConfirmed); +4288 trace!("handshake confirmed"); +4289 } +4290 +4291 self.events.push_back(Event::Connected); +4292 self.state.move_to_established(); +4293 trace!("established"); +4294 +4295 // Multipath can only be enabled after the state has reached Established. +4296 // So this can not happen any earlier. +4297 self.issue_first_path_cids(now); +4298 Ok(()) +4299 } +4300 Header::Initial(InitialHeader { +4301 src_cid: remote_cid, +4302 dst_cid: local_cid, +4303 .. +4304 }) => { +4305 debug_assert_eq!(path_id, PathId::ZERO); +4306 if !state.remote_cid_set { +4307 trace!("switching remote CID to {}", remote_cid); +4308 let mut state = state.clone(); +4309 self.remote_cids +4310 .get_mut(&path_id) +4311 .expect("PathId::ZERO not yet abandoned") +4312 .update_initial_cid(remote_cid); +4313 self.remote_handshake_cid = remote_cid; +4314 self.original_remote_cid = remote_cid; +4315 state.remote_cid_set = true; +4316 self.state.move_to_handshake(state); +4317 } else if remote_cid != self.remote_handshake_cid { +4318 debug!( +4319 "discarding packet with mismatched remote CID: {} != {}", +4320 self.remote_handshake_cid, remote_cid +4321 ); +4322 return Ok(()); +4323 } +4324 +4325 let starting_space = self.highest_space; +4326 self.process_early_payload(now, path_id, packet, qlog)?; +4327 +4328 if self.side.is_server() +4329 && starting_space == SpaceId::Initial +4330 && self.highest_space != SpaceId::Initial +4331 { +4332 let params = self.crypto.transport_parameters()?.ok_or_else(|| { +4333 TransportError::new( +4334 TransportErrorCode::crypto(0x6d), +4335 "transport parameters missing".to_owned(), +4336 ) +4337 })?; +4338 self.handle_peer_params(params, local_cid, remote_cid, now)?; +4339 self.issue_first_cids(now); +4340 self.init_0rtt(now); +4341 } +4342 Ok(()) +4343 } +4344 Header::Long { +4345 ty: LongType::ZeroRtt, +4346 .. +4347 } => { +4348 self.process_payload(now, network_path, path_id, number.unwrap(), packet, qlog)?; +4349 Ok(()) +4350 } +4351 Header::VersionNegotiate { .. } => { +4352 if self.total_authed_packets > 1 { +4353 return Ok(()); +4354 } +4355 let supported = packet +4356 .payload +4357 .chunks(4) +4358 .any(|x| match <[u8; 4]>::try_from(x) { +4359 Ok(version) => self.version == u32::from_be_bytes(version), +4360 Err(_) => false, +4361 }); +4362 if supported { +4363 return Ok(()); +4364 } +4365 debug!("remote doesn't support our version"); +4366 Err(ConnectionError::VersionMismatch) +4367 } +4368 Header::Short { .. } => unreachable!( +4369 "short packets received during handshake are discarded in handle_packet" +4370 ), +4371 } +4372 } +4373 +4374 /// Process an Initial or Handshake packet payload +4375 fn process_early_payload( +4376 &mut self, +4377 now: Instant, +4378 path_id: PathId, +4379 packet: Packet, +4380 #[allow(unused)] qlog: &mut QlogRecvPacket, +4381 ) -> Result<(), TransportError> { +4382 debug_assert_ne!(packet.header.space(), SpaceId::Data); +4383 debug_assert_eq!(path_id, PathId::ZERO); +4384 let payload_len = packet.payload.len(); +4385 let mut ack_eliciting = false; +4386 for result in frame::Iter::new(packet.payload.freeze())? { +4387 let frame = result?; +4388 qlog.frame(&frame); +4389 let span = match frame { +4390 Frame::Padding => continue, +4391 _ => Some(trace_span!("frame", ty = %frame.ty(), path = tracing::field::Empty)), +4392 }; +4393 +4394 self.stats.frame_rx.record(frame.ty()); +4395 +4396 let _guard = span.as_ref().map(|x| x.enter()); +4397 ack_eliciting |= frame.is_ack_eliciting(); +4398 +4399 // Process frames +4400 if frame.is_1rtt() && packet.header.space() != SpaceId::Data { +4401 return Err(TransportError::PROTOCOL_VIOLATION( +4402 "illegal frame type in handshake", +4403 )); +4404 } +4405 +4406 match frame { +4407 Frame::Padding | Frame::Ping => {} +4408 Frame::Crypto(frame) => { +4409 self.read_crypto(packet.header.space(), &frame, payload_len)?; +4410 } +4411 Frame::Ack(ack) => { +4412 self.on_ack_received(now, packet.header.space(), ack)?; 4413 } -4414 _ => { -4415 let mut err = -4416 TransportError::PROTOCOL_VIOLATION("illegal frame type in handshake"); -4417 err.frame = frame::MaybeFrame::Known(frame.ty()); -4418 return Err(err); -4419 } -4420 } -4421 } -4422 -4423 if ack_eliciting { -4424 // In the initial and handshake spaces, ACKs must be sent immediately -4425 self.spaces[packet.header.space()] -4426 .for_path(path_id) -4427 .pending_acks -4428 .set_immediate_ack_required(); -4429 } -4430 -4431 self.write_crypto(); -4432 Ok(()) -4433 } -4434 -4435 /// Processes the packet payload, always in the data space. -4436 fn process_payload( -4437 &mut self, -4438 now: Instant, -4439 network_path: FourTuple, -4440 path_id: PathId, -4441 number: u64, -4442 packet: Packet, -4443 #[allow(unused)] qlog: &mut QlogRecvPacket, -4444 ) -> Result<(), TransportError> { -4445 let payload = packet.payload.freeze(); -4446 let mut is_probing_packet = true; -4447 let mut close = None; -4448 let payload_len = payload.len(); -4449 let mut ack_eliciting = false; -4450 // if this packet triggers a path migration and includes a observed address frame, it's -4451 // stored here -4452 let mut migration_observed_addr = None; -4453 for result in frame::Iter::new(payload)? { -4454 let frame = result?; -4455 qlog.frame(&frame); -4456 let span = match frame { -4457 Frame::Padding => continue, -4458 _ => trace_span!("frame", ty = %frame.ty(), path = tracing::field::Empty), -4459 }; -4460 -4461 self.stats.frame_rx.record(frame.ty()); -4462 // Crypto, Stream and Datagram frames are special cased in order no pollute -4463 // the log with payload data -4464 match &frame { -4465 Frame::Crypto(f) => { -4466 trace!(offset = f.offset, len = f.data.len(), "got frame CRYPTO"); -4467 } -4468 Frame::Stream(f) => { -4469 trace!(id = %f.id, offset = f.offset, len = f.data.len(), fin = f.fin, "got frame STREAM"); -4470 } -4471 Frame::Datagram(f) => { -4472 trace!(len = f.data.len(), "got frame DATAGRAM"); -4473 } -4474 f => { -4475 trace!("got frame {f}"); +4414 Frame::PathAck(ack) => { +4415 span.as_ref() +4416 .map(|span| span.record("path", tracing::field::debug(&ack.path_id))); +4417 self.on_path_ack_received(now, packet.header.space(), ack)?; +4418 } +4419 Frame::Close(reason) => { +4420 self.state.move_to_draining(Some(reason.into())); +4421 return Ok(()); +4422 } +4423 _ => { +4424 let mut err = +4425 TransportError::PROTOCOL_VIOLATION("illegal frame type in handshake"); +4426 err.frame = frame::MaybeFrame::Known(frame.ty()); +4427 return Err(err); +4428 } +4429 } +4430 } +4431 +4432 if ack_eliciting { +4433 // In the initial and handshake spaces, ACKs must be sent immediately +4434 self.spaces[packet.header.space()] +4435 .for_path(path_id) +4436 .pending_acks +4437 .set_immediate_ack_required(); +4438 } +4439 +4440 self.write_crypto(); +4441 Ok(()) +4442 } +4443 +4444 /// Processes the packet payload, always in the data space. +4445 fn process_payload( +4446 &mut self, +4447 now: Instant, +4448 network_path: FourTuple, +4449 path_id: PathId, +4450 number: u64, +4451 packet: Packet, +4452 #[allow(unused)] qlog: &mut QlogRecvPacket, +4453 ) -> Result<(), TransportError> { +4454 let payload = packet.payload.freeze(); +4455 let mut is_probing_packet = true; +4456 let mut close = None; +4457 let payload_len = payload.len(); +4458 let mut ack_eliciting = false; +4459 // if this packet triggers a path migration and includes a observed address frame, it's +4460 // stored here +4461 let mut migration_observed_addr = None; +4462 for result in frame::Iter::new(payload)? { +4463 let frame = result?; +4464 qlog.frame(&frame); +4465 let span = match frame { +4466 Frame::Padding => continue, +4467 _ => trace_span!("frame", ty = %frame.ty(), path = tracing::field::Empty), +4468 }; +4469 +4470 self.stats.frame_rx.record(frame.ty()); +4471 // Crypto, Stream and Datagram frames are special cased in order no pollute +4472 // the log with payload data +4473 match &frame { +4474 Frame::Crypto(f) => { +4475 trace!(offset = f.offset, len = f.data.len(), "got frame CRYPTO"); 4476 } -4477 } -4478 -4479 let _guard = span.enter(); -4480 if packet.header.is_0rtt() { -4481 match frame { -4482 Frame::Crypto(_) | Frame::Close(Close::Application(_)) => { -4483 return Err(TransportError::PROTOCOL_VIOLATION( -4484 "illegal frame type in 0-RTT", -4485 )); -4486 } -4487 _ => { -4488 if frame.is_1rtt() { -4489 return Err(TransportError::PROTOCOL_VIOLATION( -4490 "illegal frame type in 0-RTT", -4491 )); -4492 } -4493 } -4494 } -4495 } -4496 ack_eliciting |= frame.is_ack_eliciting(); -4497 -4498 // Check whether this could be a probing packet -4499 match frame { -4500 Frame::Padding -4501 | Frame::PathChallenge(_) -4502 | Frame::PathResponse(_) -4503 | Frame::NewConnectionId(_) -4504 | Frame::ObservedAddr(_) => {} -4505 _ => { -4506 is_probing_packet = false; -4507 } -4508 } -4509 -4510 match frame { -4511 Frame::Crypto(frame) => { -4512 self.read_crypto(SpaceId::Data, &frame, payload_len)?; -4513 } -4514 Frame::Stream(frame) => { -4515 if self.streams.received(frame, payload_len)?.should_transmit() { -4516 self.spaces[SpaceId::Data].pending.max_data = true; -4517 } -4518 } -4519 Frame::Ack(ack) => { -4520 self.on_ack_received(now, SpaceId::Data, ack)?; -4521 } -4522 Frame::PathAck(ack) => { -4523 span.record("path", tracing::field::debug(&ack.path_id)); -4524 self.on_path_ack_received(now, SpaceId::Data, ack)?; -4525 } -4526 Frame::Padding | Frame::Ping => {} -4527 Frame::Close(reason) => { -4528 close = Some(reason); -4529 } -4530 Frame::PathChallenge(challenge) => { -4531 let path = &mut self -4532 .path_mut(path_id) -4533 .expect("payload is processed only after the path becomes known"); -4534 path.path_responses.push(number, challenge.0, network_path); -4535 // At this point, update_network_path_or_discard was already called, so -4536 // we don't need to be lenient about `local_ip` possibly mis-matching. -4537 if network_path == path.network_path { -4538 // PATH_CHALLENGE on active path, possible off-path packet forwarding -4539 // attack. Send a non-probing packet to recover the active path. -4540 // TODO(flub): No longer true! We now path_challege also to validate -4541 // the path if the path is new, without an RFC9000-style -4542 // migration involved. This means we add in an extra -4543 // IMMEDIATE_ACK on some challenges. It isn't really wrong to do -4544 // so, but it still is something untidy. We should instead -4545 // suppress this when we know the remote is still validating the -4546 // path. -4547 match self.peer_supports_ack_frequency() { -4548 true => self.immediate_ack(path_id), -4549 false => { -4550 self.ping_path(path_id).ok(); -4551 } -4552 } -4553 } -4554 } -4555 Frame::PathResponse(response) => { -4556 let path = self -4557 .paths -4558 .get_mut(&path_id) -4559 .expect("payload is processed only after the path becomes known"); -4560 -4561 use PathTimer::*; -4562 use paths::OnPathResponseReceived::*; -4563 match path -4564 .data -4565 .on_path_response_received(now, response.0, network_path) -4566 { -4567 OnPath { was_open } => { -4568 let qlog = self.qlog.with_time(now); +4477 Frame::Stream(f) => { +4478 trace!(id = %f.id, offset = f.offset, len = f.data.len(), fin = f.fin, "got frame STREAM"); +4479 } +4480 Frame::Datagram(f) => { +4481 trace!(len = f.data.len(), "got frame DATAGRAM"); +4482 } +4483 f => { +4484 trace!("got frame {f}"); +4485 } +4486 } +4487 +4488 let _guard = span.enter(); +4489 if packet.header.is_0rtt() { +4490 match frame { +4491 Frame::Crypto(_) | Frame::Close(Close::Application(_)) => { +4492 return Err(TransportError::PROTOCOL_VIOLATION( +4493 "illegal frame type in 0-RTT", +4494 )); +4495 } +4496 _ => { +4497 if frame.is_1rtt() { +4498 return Err(TransportError::PROTOCOL_VIOLATION( +4499 "illegal frame type in 0-RTT", +4500 )); +4501 } +4502 } +4503 } +4504 } +4505 ack_eliciting |= frame.is_ack_eliciting(); +4506 +4507 // Check whether this could be a probing packet +4508 match frame { +4509 Frame::Padding +4510 | Frame::PathChallenge(_) +4511 | Frame::PathResponse(_) +4512 | Frame::NewConnectionId(_) +4513 | Frame::ObservedAddr(_) => {} +4514 _ => { +4515 is_probing_packet = false; +4516 } +4517 } +4518 +4519 match frame { +4520 Frame::Crypto(frame) => { +4521 self.read_crypto(SpaceId::Data, &frame, payload_len)?; +4522 } +4523 Frame::Stream(frame) => { +4524 if self.streams.received(frame, payload_len)?.should_transmit() { +4525 self.spaces[SpaceId::Data].pending.max_data = true; +4526 } +4527 } +4528 Frame::Ack(ack) => { +4529 self.on_ack_received(now, SpaceId::Data, ack)?; +4530 } +4531 Frame::PathAck(ack) => { +4532 span.record("path", tracing::field::debug(&ack.path_id)); +4533 self.on_path_ack_received(now, SpaceId::Data, ack)?; +4534 } +4535 Frame::Padding | Frame::Ping => {} +4536 Frame::Close(reason) => { +4537 close = Some(reason); +4538 } +4539 Frame::PathChallenge(challenge) => { +4540 let path = &mut self +4541 .path_mut(path_id) +4542 .expect("payload is processed only after the path becomes known"); +4543 path.path_responses.push(number, challenge.0, network_path); +4544 // At this point, update_network_path_or_discard was already called, so +4545 // we don't need to be lenient about `local_ip` possibly mis-matching. +4546 if network_path == path.network_path { +4547 // PATH_CHALLENGE on active path, possible off-path packet forwarding +4548 // attack. Send a non-probing packet to recover the active path. +4549 // TODO(flub): No longer true! We now path_challege also to validate +4550 // the path if the path is new, without an RFC9000-style +4551 // migration involved. This means we add in an extra +4552 // IMMEDIATE_ACK on some challenges. It isn't really wrong to do +4553 // so, but it still is something untidy. We should instead +4554 // suppress this when we know the remote is still validating the +4555 // path. +4556 match self.peer_supports_ack_frequency() { +4557 true => self.immediate_ack(path_id), +4558 false => { +4559 self.ping_path(path_id).ok(); +4560 } +4561 } +4562 } +4563 } +4564 Frame::PathResponse(response) => { +4565 let path = self +4566 .paths +4567 .get_mut(&path_id) +4568 .expect("payload is processed only after the path becomes known"); 4569 -4570 self.timers -4571 .stop(Timer::PerPath(path_id, PathValidation), qlog.clone()); -4572 self.timers -4573 .stop(Timer::PerPath(path_id, PathOpen), qlog.clone()); -4574 -4575 let next_challenge = path -4576 .data -4577 .earliest_expiring_challenge() -4578 .map(|time| time + self.ack_frequency.max_ack_delay_for_pto()); -4579 self.timers.set_or_stop( -4580 Timer::PerPath(path_id, PathChallengeLost), -4581 next_challenge, -4582 qlog, -4583 ); -4584 -4585 if !was_open { -4586 self.events -4587 .push_back(Event::Path(PathEvent::Opened { id: path_id })); -4588 if let Some(observed) = path.data.last_observed_addr_report.as_ref() -4589 { -4590 self.events.push_back(Event::Path(PathEvent::ObservedAddr { -4591 id: path_id, -4592 addr: observed.socket_addr(), -4593 })); -4594 } -4595 } -4596 if let Some((_, ref mut prev)) = path.prev { -4597 prev.challenges_sent.clear(); -4598 prev.send_new_challenge = false; -4599 } -4600 } -4601 OffPath => { -4602 debug!("Response to off-path PathChallenge!"); -4603 let next_challenge = path -4604 .data -4605 .earliest_expiring_challenge() -4606 .map(|time| time + self.ack_frequency.max_ack_delay_for_pto()); -4607 self.timers.set_or_stop( -4608 Timer::PerPath(path_id, PathChallengeLost), -4609 next_challenge, -4610 self.qlog.with_time(now), -4611 ); -4612 } -4613 Invalid { expected } => { -4614 debug!(%response, %network_path, %expected, "ignoring invalid PATH_RESPONSE") -4615 } -4616 Unknown => debug!(%response, "ignoring invalid PATH_RESPONSE"), -4617 } -4618 } -4619 Frame::MaxData(frame::MaxData(bytes)) => { -4620 self.streams.received_max_data(bytes); -4621 } -4622 Frame::MaxStreamData(frame::MaxStreamData { id, offset }) => { -4623 self.streams.received_max_stream_data(id, offset)?; -4624 } -4625 Frame::MaxStreams(frame::MaxStreams { dir, count }) => { -4626 self.streams.received_max_streams(dir, count)?; +4570 use PathTimer::*; +4571 use paths::OnPathResponseReceived::*; +4572 match path +4573 .data +4574 .on_path_response_received(now, response.0, network_path) +4575 { +4576 OnPath { was_open } => { +4577 let qlog = self.qlog.with_time(now); +4578 +4579 self.timers +4580 .stop(Timer::PerPath(path_id, PathValidation), qlog.clone()); +4581 self.timers +4582 .stop(Timer::PerPath(path_id, PathOpen), qlog.clone()); +4583 +4584 let next_challenge = path +4585 .data +4586 .earliest_expiring_challenge() +4587 .map(|time| time + self.ack_frequency.max_ack_delay_for_pto()); +4588 self.timers.set_or_stop( +4589 Timer::PerPath(path_id, PathChallengeLost), +4590 next_challenge, +4591 qlog, +4592 ); +4593 +4594 if !was_open { +4595 self.events +4596 .push_back(Event::Path(PathEvent::Opened { id: path_id })); +4597 if let Some(observed) = path.data.last_observed_addr_report.as_ref() +4598 { +4599 self.events.push_back(Event::Path(PathEvent::ObservedAddr { +4600 id: path_id, +4601 addr: observed.socket_addr(), +4602 })); +4603 } +4604 } +4605 if let Some((_, ref mut prev)) = path.prev { +4606 prev.challenges_sent.clear(); +4607 prev.send_new_challenge = false; +4608 } +4609 } +4610 OffPath => { +4611 debug!("Response to off-path PathChallenge!"); +4612 let next_challenge = path +4613 .data +4614 .earliest_expiring_challenge() +4615 .map(|time| time + self.ack_frequency.max_ack_delay_for_pto()); +4616 self.timers.set_or_stop( +4617 Timer::PerPath(path_id, PathChallengeLost), +4618 next_challenge, +4619 self.qlog.with_time(now), +4620 ); +4621 } +4622 Invalid { expected } => { +4623 debug!(%response, %network_path, %expected, "ignoring invalid PATH_RESPONSE") +4624 } +4625 Unknown => debug!(%response, "ignoring invalid PATH_RESPONSE"), +4626 } 4627 } -4628 Frame::ResetStream(frame) => { -4629 if self.streams.received_reset(frame)?.should_transmit() { -4630 self.spaces[SpaceId::Data].pending.max_data = true; -4631 } -4632 } -4633 Frame::DataBlocked(DataBlocked(offset)) => { -4634 debug!(offset, "peer claims to be blocked at connection level"); -4635 } -4636 Frame::StreamDataBlocked(StreamDataBlocked { id, offset }) => { -4637 if id.initiator() == self.side.side() && id.dir() == Dir::Uni { -4638 debug!("got STREAM_DATA_BLOCKED on send-only {}", id); -4639 return Err(TransportError::STREAM_STATE_ERROR( -4640 "STREAM_DATA_BLOCKED on send-only stream", -4641 )); -4642 } -4643 debug!( -4644 stream = %id, -4645 offset, "peer claims to be blocked at stream level" -4646 ); -4647 } -4648 Frame::StreamsBlocked(StreamsBlocked { dir, limit }) => { -4649 if limit > MAX_STREAM_COUNT { -4650 return Err(TransportError::FRAME_ENCODING_ERROR( -4651 "unrepresentable stream limit", -4652 )); -4653 } -4654 debug!( -4655 "peer claims to be blocked opening more than {} {} streams", -4656 limit, dir -4657 ); -4658 } -4659 Frame::StopSending(frame::StopSending { id, error_code }) => { -4660 if id.initiator() != self.side.side() { -4661 if id.dir() == Dir::Uni { -4662 debug!("got STOP_SENDING on recv-only {}", id); -4663 return Err(TransportError::STREAM_STATE_ERROR( -4664 "STOP_SENDING on recv-only stream", -4665 )); -4666 } -4667 } else if self.streams.is_local_unopened(id) { -4668 return Err(TransportError::STREAM_STATE_ERROR( -4669 "STOP_SENDING on unopened stream", -4670 )); -4671 } -4672 self.streams.received_stop_sending(id, error_code); -4673 } -4674 Frame::RetireConnectionId(frame::RetireConnectionId { path_id, sequence }) => { -4675 if let Some(ref path_id) = path_id { -4676 span.record("path", tracing::field::debug(&path_id)); -4677 } -4678 let path_id = path_id.unwrap_or_default(); -4679 match self.local_cid_state.get_mut(&path_id) { -4680 None => error!(?path_id, "RETIRE_CONNECTION_ID for unknown path"), -4681 Some(cid_state) => { -4682 let allow_more_cids = cid_state -4683 .on_cid_retirement(sequence, self.peer_params.issue_cids_limit())?; -4684 -4685 // If the path has closed, we do not issue more CIDs for this path -4686 // For details see https://www.ietf.org/archive/id/draft-ietf-quic-multipath-17.html#section-3.2.2 -4687 // > an endpoint SHOULD provide new connection IDs for that path, if still open, using PATH_NEW_CONNECTION_ID frames. -4688 let has_path = !self.abandoned_paths.contains(&path_id); -4689 let allow_more_cids = allow_more_cids && has_path; -4690 -4691 self.endpoint_events -4692 .push_back(EndpointEventInner::RetireConnectionId( -4693 now, -4694 path_id, -4695 sequence, -4696 allow_more_cids, -4697 )); -4698 } -4699 } -4700 } -4701 Frame::NewConnectionId(frame) => { -4702 let path_id = if let Some(path_id) = frame.path_id { -4703 if !self.is_multipath_negotiated() { -4704 return Err(TransportError::PROTOCOL_VIOLATION( -4705 "received PATH_NEW_CONNECTION_ID frame when multipath was not negotiated", -4706 )); +4628 Frame::MaxData(frame::MaxData(bytes)) => { +4629 self.streams.received_max_data(bytes); +4630 } +4631 Frame::MaxStreamData(frame::MaxStreamData { id, offset }) => { +4632 self.streams.received_max_stream_data(id, offset)?; +4633 } +4634 Frame::MaxStreams(frame::MaxStreams { dir, count }) => { +4635 self.streams.received_max_streams(dir, count)?; +4636 } +4637 Frame::ResetStream(frame) => { +4638 if self.streams.received_reset(frame)?.should_transmit() { +4639 self.spaces[SpaceId::Data].pending.max_data = true; +4640 } +4641 } +4642 Frame::DataBlocked(DataBlocked(offset)) => { +4643 debug!(offset, "peer claims to be blocked at connection level"); +4644 } +4645 Frame::StreamDataBlocked(StreamDataBlocked { id, offset }) => { +4646 if id.initiator() == self.side.side() && id.dir() == Dir::Uni { +4647 debug!("got STREAM_DATA_BLOCKED on send-only {}", id); +4648 return Err(TransportError::STREAM_STATE_ERROR( +4649 "STREAM_DATA_BLOCKED on send-only stream", +4650 )); +4651 } +4652 debug!( +4653 stream = %id, +4654 offset, "peer claims to be blocked at stream level" +4655 ); +4656 } +4657 Frame::StreamsBlocked(StreamsBlocked { dir, limit }) => { +4658 if limit > MAX_STREAM_COUNT { +4659 return Err(TransportError::FRAME_ENCODING_ERROR( +4660 "unrepresentable stream limit", +4661 )); +4662 } +4663 debug!( +4664 "peer claims to be blocked opening more than {} {} streams", +4665 limit, dir +4666 ); +4667 } +4668 Frame::StopSending(frame::StopSending { id, error_code }) => { +4669 if id.initiator() != self.side.side() { +4670 if id.dir() == Dir::Uni { +4671 debug!("got STOP_SENDING on recv-only {}", id); +4672 return Err(TransportError::STREAM_STATE_ERROR( +4673 "STOP_SENDING on recv-only stream", +4674 )); +4675 } +4676 } else if self.streams.is_local_unopened(id) { +4677 return Err(TransportError::STREAM_STATE_ERROR( +4678 "STOP_SENDING on unopened stream", +4679 )); +4680 } +4681 self.streams.received_stop_sending(id, error_code); +4682 } +4683 Frame::RetireConnectionId(frame::RetireConnectionId { path_id, sequence }) => { +4684 if let Some(ref path_id) = path_id { +4685 span.record("path", tracing::field::debug(&path_id)); +4686 } +4687 let path_id = path_id.unwrap_or_default(); +4688 match self.local_cid_state.get_mut(&path_id) { +4689 None => error!(?path_id, "RETIRE_CONNECTION_ID for unknown path"), +4690 Some(cid_state) => { +4691 let allow_more_cids = cid_state +4692 .on_cid_retirement(sequence, self.peer_params.issue_cids_limit())?; +4693 +4694 // If the path has closed, we do not issue more CIDs for this path +4695 // For details see https://www.ietf.org/archive/id/draft-ietf-quic-multipath-17.html#section-3.2.2 +4696 // > an endpoint SHOULD provide new connection IDs for that path, if still open, using PATH_NEW_CONNECTION_ID frames. +4697 let has_path = !self.abandoned_paths.contains(&path_id); +4698 let allow_more_cids = allow_more_cids && has_path; +4699 +4700 self.endpoint_events +4701 .push_back(EndpointEventInner::RetireConnectionId( +4702 now, +4703 path_id, +4704 sequence, +4705 allow_more_cids, +4706 )); 4707 } -4708 if path_id > self.local_max_path_id { -4709 return Err(TransportError::PROTOCOL_VIOLATION( -4710 "PATH_NEW_CONNECTION_ID contains path_id exceeding current max", -4711 )); -4712 } -4713 path_id -4714 } else { -4715 PathId::ZERO -4716 }; -4717 -4718 if self.abandoned_paths.contains(&path_id) { -4719 trace!("ignoring issued CID for abandoned path"); -4720 continue; -4721 } -4722 if let Some(ref path_id) = frame.path_id { -4723 span.record("path", tracing::field::debug(&path_id)); -4724 } -4725 let remote_cids = self -4726 .remote_cids -4727 .entry(path_id) -4728 .or_insert_with(|| CidQueue::new(frame.id)); -4729 if remote_cids.active().is_empty() { -4730 return Err(TransportError::PROTOCOL_VIOLATION( -4731 "NEW_CONNECTION_ID when CIDs aren't in use", -4732 )); +4708 } +4709 } +4710 Frame::NewConnectionId(frame) => { +4711 let path_id = if let Some(path_id) = frame.path_id { +4712 if !self.is_multipath_negotiated() { +4713 return Err(TransportError::PROTOCOL_VIOLATION( +4714 "received PATH_NEW_CONNECTION_ID frame when multipath was not negotiated", +4715 )); +4716 } +4717 if path_id > self.local_max_path_id { +4718 return Err(TransportError::PROTOCOL_VIOLATION( +4719 "PATH_NEW_CONNECTION_ID contains path_id exceeding current max", +4720 )); +4721 } +4722 path_id +4723 } else { +4724 PathId::ZERO +4725 }; +4726 +4727 if self.abandoned_paths.contains(&path_id) { +4728 trace!("ignoring issued CID for abandoned path"); +4729 continue; +4730 } +4731 if let Some(ref path_id) = frame.path_id { +4732 span.record("path", tracing::field::debug(&path_id)); 4733 } -4734 if frame.retire_prior_to > frame.sequence { -4735 return Err(TransportError::PROTOCOL_VIOLATION( -4736 "NEW_CONNECTION_ID retiring unissued CIDs", -4737 )); -4738 } -4739 -4740 use crate::cid_queue::InsertError; -4741 match remote_cids.insert(frame) { -4742 Ok(None) if self.path(path_id).is_none() => { -4743 // if this gives us CIDs to open a new path and a nat traversal attempt -4744 // is underway we could try to probe a pending remote -4745 self.continue_nat_traversal_round(now); -4746 } -4747 Ok(None) => {} -4748 Ok(Some((retired, reset_token))) => { -4749 let pending_retired = -4750 &mut self.spaces[SpaceId::Data].pending.retire_cids; -4751 /// Ensure `pending_retired` cannot grow without bound. Limit is -4752 /// somewhat arbitrary but very permissive. -4753 const MAX_PENDING_RETIRED_CIDS: u64 = CidQueue::LEN as u64 * 10; -4754 // We don't bother counting in-flight frames because those are bounded -4755 // by congestion control. -4756 if (pending_retired.len() as u64) -4757 .saturating_add(retired.end.saturating_sub(retired.start)) -4758 > MAX_PENDING_RETIRED_CIDS -4759 { -4760 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR( -4761 "queued too many retired CIDs", -4762 )); -4763 } -4764 pending_retired.extend(retired.map(|seq| (path_id, seq))); -4765 // TODO(matheus23): Reset token for a remote or a full 4-tuple? -4766 self.set_reset_token(path_id, network_path.remote, reset_token); -4767 } -4768 Err(InsertError::ExceedsLimit) => { -4769 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR("")); -4770 } -4771 Err(InsertError::Retired) => { -4772 trace!("discarding already-retired"); -4773 // RETIRE_CONNECTION_ID might not have been previously sent if e.g. a -4774 // range of connection IDs larger than the active connection ID limit -4775 // was retired all at once via retire_prior_to. -4776 self.spaces[SpaceId::Data] -4777 .pending -4778 .retire_cids -4779 .push((path_id, frame.sequence)); -4780 continue; -4781 } -4782 }; -4783 -4784 if self.side.is_server() -4785 && path_id == PathId::ZERO -4786 && self -4787 .remote_cids -4788 .get(&PathId::ZERO) -4789 .map(|cids| cids.active_seq() == 0) -4790 .unwrap_or_default() -4791 { -4792 // We're a server still using the initial remote CID for the client, so -4793 // let's switch immediately to enable clientside stateless resets. -4794 self.update_remote_cid(PathId::ZERO); -4795 } -4796 } -4797 Frame::NewToken(NewToken { token }) => { -4798 let ConnectionSide::Client { -4799 token_store, -4800 server_name, -4801 .. -4802 } = &self.side -4803 else { -4804 return Err(TransportError::PROTOCOL_VIOLATION("client sent NEW_TOKEN")); -4805 }; -4806 if token.is_empty() { -4807 return Err(TransportError::FRAME_ENCODING_ERROR("empty token")); -4808 } -4809 trace!("got new token"); -4810 token_store.insert(server_name, token); -4811 } -4812 Frame::Datagram(datagram) => { -4813 if self -4814 .datagrams -4815 .received(datagram, &self.config.datagram_receive_buffer_size)? -4816 { -4817 self.events.push_back(Event::DatagramReceived); -4818 } -4819 } -4820 Frame::AckFrequency(ack_frequency) => { -4821 // This frame can only be sent in the Data space -4822 -4823 if !self.ack_frequency.ack_frequency_received(&ack_frequency)? { -4824 // The AckFrequency frame is stale (we have already received a more -4825 // recent one) -4826 continue; +4734 let remote_cids = self +4735 .remote_cids +4736 .entry(path_id) +4737 .or_insert_with(|| CidQueue::new(frame.id)); +4738 if remote_cids.active().is_empty() { +4739 return Err(TransportError::PROTOCOL_VIOLATION( +4740 "NEW_CONNECTION_ID when CIDs aren't in use", +4741 )); +4742 } +4743 if frame.retire_prior_to > frame.sequence { +4744 return Err(TransportError::PROTOCOL_VIOLATION( +4745 "NEW_CONNECTION_ID retiring unissued CIDs", +4746 )); +4747 } +4748 +4749 use crate::cid_queue::InsertError; +4750 match remote_cids.insert(frame) { +4751 Ok(None) if self.path(path_id).is_none() => { +4752 // if this gives us CIDs to open a new path and a nat traversal attempt +4753 // is underway we could try to probe a pending remote +4754 self.continue_nat_traversal_round(now); +4755 } +4756 Ok(None) => {} +4757 Ok(Some((retired, reset_token))) => { +4758 let pending_retired = +4759 &mut self.spaces[SpaceId::Data].pending.retire_cids; +4760 /// Ensure `pending_retired` cannot grow without bound. Limit is +4761 /// somewhat arbitrary but very permissive. +4762 const MAX_PENDING_RETIRED_CIDS: u64 = CidQueue::LEN as u64 * 10; +4763 // We don't bother counting in-flight frames because those are bounded +4764 // by congestion control. +4765 if (pending_retired.len() as u64) +4766 .saturating_add(retired.end.saturating_sub(retired.start)) +4767 > MAX_PENDING_RETIRED_CIDS +4768 { +4769 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR( +4770 "queued too many retired CIDs", +4771 )); +4772 } +4773 pending_retired.extend(retired.map(|seq| (path_id, seq))); +4774 // TODO(matheus23): Reset token for a remote or a full 4-tuple? +4775 self.set_reset_token(path_id, network_path.remote, reset_token); +4776 } +4777 Err(InsertError::ExceedsLimit) => { +4778 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR("")); +4779 } +4780 Err(InsertError::Retired) => { +4781 trace!("discarding already-retired"); +4782 // RETIRE_CONNECTION_ID might not have been previously sent if e.g. a +4783 // range of connection IDs larger than the active connection ID limit +4784 // was retired all at once via retire_prior_to. +4785 self.spaces[SpaceId::Data] +4786 .pending +4787 .retire_cids +4788 .push((path_id, frame.sequence)); +4789 continue; +4790 } +4791 }; +4792 +4793 if self.side.is_server() +4794 && path_id == PathId::ZERO +4795 && self +4796 .remote_cids +4797 .get(&PathId::ZERO) +4798 .map(|cids| cids.active_seq() == 0) +4799 .unwrap_or_default() +4800 { +4801 // We're a server still using the initial remote CID for the client, so +4802 // let's switch immediately to enable clientside stateless resets. +4803 self.update_remote_cid(PathId::ZERO); +4804 } +4805 } +4806 Frame::NewToken(NewToken { token }) => { +4807 let ConnectionSide::Client { +4808 token_store, +4809 server_name, +4810 .. +4811 } = &self.side +4812 else { +4813 return Err(TransportError::PROTOCOL_VIOLATION("client sent NEW_TOKEN")); +4814 }; +4815 if token.is_empty() { +4816 return Err(TransportError::FRAME_ENCODING_ERROR("empty token")); +4817 } +4818 trace!("got new token"); +4819 token_store.insert(server_name, token); +4820 } +4821 Frame::Datagram(datagram) => { +4822 if self +4823 .datagrams +4824 .received(datagram, &self.config.datagram_receive_buffer_size)? +4825 { +4826 self.events.push_back(Event::DatagramReceived); 4827 } -4828 -4829 // Update the params for all of our paths -4830 for (path_id, space) in self.spaces[SpaceId::Data].number_spaces.iter_mut() { -4831 space.pending_acks.set_ack_frequency_params(&ack_frequency); -4832 -4833 // Our `max_ack_delay` has been updated, so we may need to adjust -4834 // its associated timeout -4835 if let Some(timeout) = space -4836 .pending_acks -4837 .max_ack_delay_timeout(self.ack_frequency.max_ack_delay) -4838 { -4839 self.timers.set( -4840 Timer::PerPath(*path_id, PathTimer::MaxAckDelay), -4841 timeout, -4842 self.qlog.with_time(now), -4843 ); -4844 } -4845 } -4846 } -4847 Frame::ImmediateAck => { -4848 // This frame can only be sent in the Data space -4849 for pns in self.spaces[SpaceId::Data].iter_paths_mut() { -4850 pns.pending_acks.set_immediate_ack_required(); -4851 } -4852 } -4853 Frame::HandshakeDone => { -4854 if self.side.is_server() { -4855 return Err(TransportError::PROTOCOL_VIOLATION( -4856 "client sent HANDSHAKE_DONE", -4857 )); -4858 } -4859 if self.spaces[SpaceId::Handshake].crypto.is_some() { -4860 self.discard_space(now, SpaceId::Handshake); -4861 } -4862 self.events.push_back(Event::HandshakeConfirmed); -4863 trace!("handshake confirmed"); -4864 } -4865 Frame::ObservedAddr(observed) => { -4866 // check if params allows the peer to send report and this node to receive it -4867 trace!(seq_no = %observed.seq_no, ip = %observed.ip, port = observed.port); -4868 if !self -4869 .peer_params -4870 .address_discovery_role -4871 .should_report(&self.config.address_discovery_role) -4872 { -4873 return Err(TransportError::PROTOCOL_VIOLATION( -4874 "received OBSERVED_ADDRESS frame when not negotiated", -4875 )); -4876 } -4877 // must only be sent in data space -4878 if packet.header.space() != SpaceId::Data { -4879 return Err(TransportError::PROTOCOL_VIOLATION( -4880 "OBSERVED_ADDRESS frame outside data space", -4881 )); -4882 } -4883 -4884 let path = self.path_data_mut(path_id); -4885 if network_path == path.network_path { -4886 if let Some(updated) = path.update_observed_addr_report(observed) -4887 && path.open -4888 { -4889 self.events.push_back(Event::Path(PathEvent::ObservedAddr { -4890 id: path_id, -4891 addr: updated, -4892 })); -4893 // otherwise the event is reported when the path is deemed open -4894 } -4895 } else { -4896 // include in migration -4897 migration_observed_addr = Some(observed) -4898 } -4899 } -4900 Frame::PathAbandon(frame::PathAbandon { -4901 path_id, -4902 error_code, -4903 }) => { -4904 span.record("path", tracing::field::debug(&path_id)); -4905 // TODO(flub): don't really know which error code to use here. -4906 match self.close_path(now, path_id, error_code.into()) { -4907 Ok(()) => { -4908 trace!("peer abandoned path"); -4909 } -4910 Err(ClosePathError::LastOpenPath) => { -4911 trace!("peer abandoned last path, closing connection"); -4912 return Err(TransportError::NO_VIABLE_PATH( -4913 "last path abandoned by peer", -4914 )); -4915 } -4916 Err(ClosePathError::ClosedPath) => { -4917 trace!("peer abandoned already closed path"); +4828 } +4829 Frame::AckFrequency(ack_frequency) => { +4830 // This frame can only be sent in the Data space +4831 +4832 if !self.ack_frequency.ack_frequency_received(&ack_frequency)? { +4833 // The AckFrequency frame is stale (we have already received a more +4834 // recent one) +4835 continue; +4836 } +4837 +4838 // Update the params for all of our paths +4839 for (path_id, space) in self.spaces[SpaceId::Data].number_spaces.iter_mut() { +4840 space.pending_acks.set_ack_frequency_params(&ack_frequency); +4841 +4842 // Our `max_ack_delay` has been updated, so we may need to adjust +4843 // its associated timeout +4844 if let Some(timeout) = space +4845 .pending_acks +4846 .max_ack_delay_timeout(self.ack_frequency.max_ack_delay) +4847 { +4848 self.timers.set( +4849 Timer::PerPath(*path_id, PathTimer::MaxAckDelay), +4850 timeout, +4851 self.qlog.with_time(now), +4852 ); +4853 } +4854 } +4855 } +4856 Frame::ImmediateAck => { +4857 // This frame can only be sent in the Data space +4858 for pns in self.spaces[SpaceId::Data].iter_paths_mut() { +4859 pns.pending_acks.set_immediate_ack_required(); +4860 } +4861 } +4862 Frame::HandshakeDone => { +4863 if self.side.is_server() { +4864 return Err(TransportError::PROTOCOL_VIOLATION( +4865 "client sent HANDSHAKE_DONE", +4866 )); +4867 } +4868 if self.spaces[SpaceId::Handshake].crypto.is_some() { +4869 self.discard_space(now, SpaceId::Handshake); +4870 } +4871 self.events.push_back(Event::HandshakeConfirmed); +4872 trace!("handshake confirmed"); +4873 } +4874 Frame::ObservedAddr(observed) => { +4875 // check if params allows the peer to send report and this node to receive it +4876 trace!(seq_no = %observed.seq_no, ip = %observed.ip, port = observed.port); +4877 if !self +4878 .peer_params +4879 .address_discovery_role +4880 .should_report(&self.config.address_discovery_role) +4881 { +4882 return Err(TransportError::PROTOCOL_VIOLATION( +4883 "received OBSERVED_ADDRESS frame when not negotiated", +4884 )); +4885 } +4886 // must only be sent in data space +4887 if packet.header.space() != SpaceId::Data { +4888 return Err(TransportError::PROTOCOL_VIOLATION( +4889 "OBSERVED_ADDRESS frame outside data space", +4890 )); +4891 } +4892 +4893 let path = self.path_data_mut(path_id); +4894 if network_path == path.network_path { +4895 if let Some(updated) = path.update_observed_addr_report(observed) +4896 && path.open +4897 { +4898 self.events.push_back(Event::Path(PathEvent::ObservedAddr { +4899 id: path_id, +4900 addr: updated, +4901 })); +4902 // otherwise the event is reported when the path is deemed open +4903 } +4904 } else { +4905 // include in migration +4906 migration_observed_addr = Some(observed) +4907 } +4908 } +4909 Frame::PathAbandon(frame::PathAbandon { +4910 path_id, +4911 error_code, +4912 }) => { +4913 span.record("path", tracing::field::debug(&path_id)); +4914 // TODO(flub): don't really know which error code to use here. +4915 match self.close_path(now, path_id, error_code.into()) { +4916 Ok(()) => { +4917 trace!("peer abandoned path"); 4918 } -4919 }; -4920 // If we receive a retransmit of PATH_ABANDON then we may already have -4921 // abandoned this path locally. In that case the DiscardPath timer -4922 // may already have fired and we no longer have any state for this path. -4923 // Only set this timer if we still have path state. -4924 if let Some(path) = self.paths.get_mut(&path_id) -4925 && !matches!(path.data.abandon_state, AbandonState::ReceivedPathAbandon) -4926 { -4927 let ack_delay = self.ack_frequency.max_ack_delay_for_pto(); -4928 let pto = path.data.rtt.pto_base() + ack_delay; -4929 self.timers.set( -4930 Timer::PerPath(path_id, PathTimer::DiscardPath), -4931 now + 3 * pto, -4932 self.qlog.with_time(now), -4933 ); -4934 // We received a PATH_ABANDON, we don't expect another one by a certain time. -4935 path.data.abandon_state = AbandonState::ReceivedPathAbandon; -4936 } -4937 } -4938 Frame::PathStatusAvailable(info) => { -4939 span.record("path", tracing::field::debug(&info.path_id)); -4940 if self.is_multipath_negotiated() { -4941 self.on_path_status( -4942 info.path_id, -4943 PathStatus::Available, -4944 info.status_seq_no, -4945 ); -4946 } else { -4947 return Err(TransportError::PROTOCOL_VIOLATION( -4948 "received PATH_STATUS_AVAILABLE frame when multipath was not negotiated", -4949 )); -4950 } -4951 } -4952 Frame::PathStatusBackup(info) => { -4953 span.record("path", tracing::field::debug(&info.path_id)); -4954 if self.is_multipath_negotiated() { -4955 self.on_path_status(info.path_id, PathStatus::Backup, info.status_seq_no); -4956 } else { -4957 return Err(TransportError::PROTOCOL_VIOLATION( -4958 "received PATH_STATUS_BACKUP frame when multipath was not negotiated", -4959 )); -4960 } -4961 } -4962 Frame::MaxPathId(frame::MaxPathId(path_id)) => { -4963 span.record("path", tracing::field::debug(&path_id)); -4964 if !self.is_multipath_negotiated() { -4965 return Err(TransportError::PROTOCOL_VIOLATION( -4966 "received MAX_PATH_ID frame when multipath was not negotiated", -4967 )); -4968 } -4969 // frames that do not increase the path id are ignored -4970 if path_id > self.remote_max_path_id { -4971 self.remote_max_path_id = path_id; -4972 self.issue_first_path_cids(now); -4973 while let Some(true) = self.continue_nat_traversal_round(now) {} -4974 } -4975 } -4976 Frame::PathsBlocked(frame::PathsBlocked(max_path_id)) => { -4977 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST -4978 // be treated as a connection error of type PROTOCOL_VIOLATION. -4979 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> -4980 if self.is_multipath_negotiated() { -4981 if max_path_id > self.local_max_path_id { -4982 return Err(TransportError::PROTOCOL_VIOLATION( -4983 "PATHS_BLOCKED maximum path identifier was larger than local maximum", -4984 )); -4985 } -4986 debug!("received PATHS_BLOCKED({:?})", max_path_id); -4987 // TODO(@divma): ensure max concurrent paths -4988 } else { -4989 return Err(TransportError::PROTOCOL_VIOLATION( -4990 "received PATHS_BLOCKED frame when not multipath was not negotiated", -4991 )); -4992 } -4993 } -4994 Frame::PathCidsBlocked(frame::PathCidsBlocked { path_id, next_seq }) => { -4995 // Nothing to do. This is recorded in the frame stats, but otherwise we -4996 // always issue all CIDs we're allowed to issue, so either this is an -4997 // impatient peer or a bug on our side. -4998 -4999 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST -5000 // be treated as a connection error of type PROTOCOL_VIOLATION. -5001 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> -5002 if self.is_multipath_negotiated() { -5003 if path_id > self.local_max_path_id { -5004 return Err(TransportError::PROTOCOL_VIOLATION( -5005 "PATH_CIDS_BLOCKED path identifier was larger than local maximum", -5006 )); -5007 } -5008 if next_seq.0 -5009 > self -5010 .local_cid_state -5011 .get(&path_id) -5012 .map(|cid_state| cid_state.active_seq().1 + 1) -5013 .unwrap_or_default() -5014 { -5015 return Err(TransportError::PROTOCOL_VIOLATION( -5016 "PATH_CIDS_BLOCKED next sequence number larger than in local state", -5017 )); -5018 } -5019 debug!(%path_id, %next_seq, "received PATH_CIDS_BLOCKED"); -5020 } else { -5021 return Err(TransportError::PROTOCOL_VIOLATION( -5022 "received PATH_CIDS_BLOCKED frame when not multipath was not negotiated", -5023 )); -5024 } -5025 } -5026 Frame::AddAddress(addr) => { -5027 let client_state = match self.iroh_hp.client_side_mut() { -5028 Ok(state) => state, -5029 Err(err) => { -5030 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5031 "Nat traversal(ADD_ADDRESS): {err}" -5032 ))); -5033 } -5034 }; -5035 -5036 if !client_state.check_remote_address(&addr) { -5037 // if the address is not valid we flag it, but update anyway -5038 warn!(?addr, "server sent illegal ADD_ADDRESS frame"); -5039 } -5040 -5041 match client_state.add_remote_address(addr) { -5042 Ok(maybe_added) => { -5043 if let Some(added) = maybe_added { -5044 self.events.push_back(Event::NatTraversal( -5045 iroh_hp::Event::AddressAdded(added), -5046 )); -5047 } -5048 } -5049 Err(e) => { -5050 warn!(%e, "failed to add remote address") -5051 } -5052 } -5053 } -5054 Frame::RemoveAddress(addr) => { -5055 let client_state = match self.iroh_hp.client_side_mut() { -5056 Ok(state) => state, -5057 Err(err) => { -5058 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5059 "Nat traversal(REMOVE_ADDRESS): {err}" -5060 ))); -5061 } -5062 }; -5063 if let Some(removed_addr) = client_state.remove_remote_address(addr) { -5064 self.events -5065 .push_back(Event::NatTraversal(iroh_hp::Event::AddressRemoved( -5066 removed_addr, -5067 ))); -5068 } -5069 } -5070 Frame::ReachOut(reach_out) => { -5071 let server_state = match self.iroh_hp.server_side_mut() { -5072 Ok(state) => state, -5073 Err(err) => { -5074 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5075 "Nat traversal(REACH_OUT): {err}" -5076 ))); -5077 } -5078 }; -5079 -5080 if let Err(err) = server_state.handle_reach_out(reach_out) { -5081 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5082 "Nat traversal(REACH_OUT): {err}" -5083 ))); -5084 } -5085 } -5086 } -5087 } +4919 Err(ClosePathError::LastOpenPath) => { +4920 trace!("peer abandoned last path, closing connection"); +4921 return Err(TransportError::NO_VIABLE_PATH( +4922 "last path abandoned by peer", +4923 )); +4924 } +4925 Err(ClosePathError::ClosedPath) => { +4926 trace!("peer abandoned already closed path"); +4927 } +4928 }; +4929 // If we receive a retransmit of PATH_ABANDON then we may already have +4930 // abandoned this path locally. In that case the DiscardPath timer +4931 // may already have fired and we no longer have any state for this path. +4932 // Only set this timer if we still have path state. +4933 if let Some(path) = self.paths.get_mut(&path_id) +4934 && !matches!(path.data.abandon_state, AbandonState::ReceivedPathAbandon) +4935 { +4936 let ack_delay = self.ack_frequency.max_ack_delay_for_pto(); +4937 let pto = path.data.rtt.pto_base() + ack_delay; +4938 self.timers.set( +4939 Timer::PerPath(path_id, PathTimer::DiscardPath), +4940 now + 3 * pto, +4941 self.qlog.with_time(now), +4942 ); +4943 // We received a PATH_ABANDON, we don't expect another one by a certain time. +4944 path.data.abandon_state = AbandonState::ReceivedPathAbandon; +4945 } +4946 } +4947 Frame::PathStatusAvailable(info) => { +4948 span.record("path", tracing::field::debug(&info.path_id)); +4949 if self.is_multipath_negotiated() { +4950 self.on_path_status( +4951 info.path_id, +4952 PathStatus::Available, +4953 info.status_seq_no, +4954 ); +4955 } else { +4956 return Err(TransportError::PROTOCOL_VIOLATION( +4957 "received PATH_STATUS_AVAILABLE frame when multipath was not negotiated", +4958 )); +4959 } +4960 } +4961 Frame::PathStatusBackup(info) => { +4962 span.record("path", tracing::field::debug(&info.path_id)); +4963 if self.is_multipath_negotiated() { +4964 self.on_path_status(info.path_id, PathStatus::Backup, info.status_seq_no); +4965 } else { +4966 return Err(TransportError::PROTOCOL_VIOLATION( +4967 "received PATH_STATUS_BACKUP frame when multipath was not negotiated", +4968 )); +4969 } +4970 } +4971 Frame::MaxPathId(frame::MaxPathId(path_id)) => { +4972 span.record("path", tracing::field::debug(&path_id)); +4973 if !self.is_multipath_negotiated() { +4974 return Err(TransportError::PROTOCOL_VIOLATION( +4975 "received MAX_PATH_ID frame when multipath was not negotiated", +4976 )); +4977 } +4978 // frames that do not increase the path id are ignored +4979 if path_id > self.remote_max_path_id { +4980 self.remote_max_path_id = path_id; +4981 self.issue_first_path_cids(now); +4982 while let Some(true) = self.continue_nat_traversal_round(now) {} +4983 } +4984 } +4985 Frame::PathsBlocked(frame::PathsBlocked(max_path_id)) => { +4986 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST +4987 // be treated as a connection error of type PROTOCOL_VIOLATION. +4988 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> +4989 if self.is_multipath_negotiated() { +4990 if max_path_id > self.local_max_path_id { +4991 return Err(TransportError::PROTOCOL_VIOLATION( +4992 "PATHS_BLOCKED maximum path identifier was larger than local maximum", +4993 )); +4994 } +4995 debug!("received PATHS_BLOCKED({:?})", max_path_id); +4996 // TODO(@divma): ensure max concurrent paths +4997 } else { +4998 return Err(TransportError::PROTOCOL_VIOLATION( +4999 "received PATHS_BLOCKED frame when not multipath was not negotiated", +5000 )); +5001 } +5002 } +5003 Frame::PathCidsBlocked(frame::PathCidsBlocked { path_id, next_seq }) => { +5004 // Nothing to do. This is recorded in the frame stats, but otherwise we +5005 // always issue all CIDs we're allowed to issue, so either this is an +5006 // impatient peer or a bug on our side. +5007 +5008 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST +5009 // be treated as a connection error of type PROTOCOL_VIOLATION. +5010 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> +5011 if self.is_multipath_negotiated() { +5012 if path_id > self.local_max_path_id { +5013 return Err(TransportError::PROTOCOL_VIOLATION( +5014 "PATH_CIDS_BLOCKED path identifier was larger than local maximum", +5015 )); +5016 } +5017 if next_seq.0 +5018 > self +5019 .local_cid_state +5020 .get(&path_id) +5021 .map(|cid_state| cid_state.active_seq().1 + 1) +5022 .unwrap_or_default() +5023 { +5024 return Err(TransportError::PROTOCOL_VIOLATION( +5025 "PATH_CIDS_BLOCKED next sequence number larger than in local state", +5026 )); +5027 } +5028 debug!(%path_id, %next_seq, "received PATH_CIDS_BLOCKED"); +5029 } else { +5030 return Err(TransportError::PROTOCOL_VIOLATION( +5031 "received PATH_CIDS_BLOCKED frame when not multipath was not negotiated", +5032 )); +5033 } +5034 } +5035 Frame::AddAddress(addr) => { +5036 let client_state = match self.iroh_hp.client_side_mut() { +5037 Ok(state) => state, +5038 Err(err) => { +5039 return Err(TransportError::PROTOCOL_VIOLATION(format!( +5040 "Nat traversal(ADD_ADDRESS): {err}" +5041 ))); +5042 } +5043 }; +5044 +5045 if !client_state.check_remote_address(&addr) { +5046 // if the address is not valid we flag it, but update anyway +5047 warn!(?addr, "server sent illegal ADD_ADDRESS frame"); +5048 } +5049 +5050 match client_state.add_remote_address(addr) { +5051 Ok(maybe_added) => { +5052 if let Some(added) = maybe_added { +5053 self.events.push_back(Event::NatTraversal( +5054 iroh_hp::Event::AddressAdded(added), +5055 )); +5056 } +5057 } +5058 Err(e) => { +5059 warn!(%e, "failed to add remote address") +5060 } +5061 } +5062 } +5063 Frame::RemoveAddress(addr) => { +5064 let client_state = match self.iroh_hp.client_side_mut() { +5065 Ok(state) => state, +5066 Err(err) => { +5067 return Err(TransportError::PROTOCOL_VIOLATION(format!( +5068 "Nat traversal(REMOVE_ADDRESS): {err}" +5069 ))); +5070 } +5071 }; +5072 if let Some(removed_addr) = client_state.remove_remote_address(addr) { +5073 self.events +5074 .push_back(Event::NatTraversal(iroh_hp::Event::AddressRemoved( +5075 removed_addr, +5076 ))); +5077 } +5078 } +5079 Frame::ReachOut(reach_out) => { +5080 let server_state = match self.iroh_hp.server_side_mut() { +5081 Ok(state) => state, +5082 Err(err) => { +5083 return Err(TransportError::PROTOCOL_VIOLATION(format!( +5084 "Nat traversal(REACH_OUT): {err}" +5085 ))); +5086 } +5087 }; 5088 -5089 let space = self.spaces[SpaceId::Data].for_path(path_id); -5090 if space -5091 .pending_acks -5092 .packet_received(now, number, ack_eliciting, &space.dedup) -5093 { -5094 if self.abandoned_paths.contains(&path_id) { -5095 // § 3.4.3 QUIC-MULTIPATH: promptly send ACKs for packets received from -5096 // abandoned paths. -5097 space.pending_acks.set_immediate_ack_required(); -5098 } else { -5099 self.timers.set( -5100 Timer::PerPath(path_id, PathTimer::MaxAckDelay), -5101 now + self.ack_frequency.max_ack_delay, -5102 self.qlog.with_time(now), -5103 ); -5104 } -5105 } -5106 -5107 // Issue stream ID credit due to ACKs of outgoing finish/resets and incoming finish/resets -5108 // on stopped streams. Incoming finishes/resets on open streams are not handled here as they -5109 // are only freed, and hence only issue credit, once the application has been notified -5110 // during a read on the stream. -5111 let pending = &mut self.spaces[SpaceId::Data].pending; -5112 self.streams.queue_max_stream_id(pending); -5113 -5114 if let Some(reason) = close { -5115 self.state.move_to_draining(Some(reason.into())); -5116 self.connection_close_pending = true; -5117 } -5118 -5119 if Some(number) == self.spaces[SpaceId::Data].for_path(path_id).rx_packet -5120 && !is_probing_packet -5121 && network_path != self.path_data(path_id).network_path -5122 { -5123 let ConnectionSide::Server { ref server_config } = self.side else { -5124 panic!("packets from unknown remote should be dropped by clients"); -5125 }; -5126 debug_assert!( -5127 server_config.migration, -5128 "migration-initiating packets should have been dropped immediately" -5129 ); -5130 self.migrate(path_id, now, network_path, migration_observed_addr); -5131 // Break linkability, if possible -5132 self.update_remote_cid(path_id); -5133 self.spin = false; -5134 } -5135 -5136 Ok(()) -5137 } -5138 -5139 fn migrate( -5140 &mut self, -5141 path_id: PathId, -5142 now: Instant, -5143 network_path: FourTuple, -5144 observed_addr: Option<ObservedAddr>, -5145 ) { -5146 trace!(%network_path, %path_id, "migration initiated"); -5147 self.path_generation_counter = self.path_generation_counter.wrapping_add(1); -5148 // TODO(@divma): conditions for path migration in multipath are very specific, check them -5149 // again to prevent path migrations that should actually create a new path -5150 -5151 // Reset rtt/congestion state for new path unless it looks like a NAT rebinding. -5152 // Note that the congestion window will not grow until validation terminates. Helps mitigate -5153 // amplification attacks performed by spoofing source addresses. -5154 let prev_pto = self.pto(SpaceId::Data, path_id); -5155 let known_path = self.paths.get_mut(&path_id).expect("known path"); -5156 let path = &mut known_path.data; -5157 let mut new_path = if network_path.remote.is_ipv4() -5158 && network_path.remote.ip() == path.network_path.remote.ip() -5159 { -5160 PathData::from_previous(network_path, path, self.path_generation_counter, now) -5161 } else { -5162 let peer_max_udp_payload_size = -5163 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()) -5164 .unwrap_or(u16::MAX); -5165 PathData::new( -5166 network_path, -5167 self.allow_mtud, -5168 Some(peer_max_udp_payload_size), -5169 self.path_generation_counter, -5170 now, -5171 &self.config, -5172 ) -5173 }; -5174 new_path.last_observed_addr_report = path.last_observed_addr_report.clone(); -5175 if let Some(report) = observed_addr -5176 && let Some(updated) = new_path.update_observed_addr_report(report) -5177 { -5178 tracing::info!("adding observed addr event from migration"); -5179 self.events.push_back(Event::Path(PathEvent::ObservedAddr { -5180 id: path_id, -5181 addr: updated, -5182 })); -5183 } -5184 new_path.send_new_challenge = true; -5185 -5186 let mut prev = mem::replace(path, new_path); -5187 // Don't clobber the original path if the previous one hasn't been validated yet -5188 if !prev.is_validating_path() { -5189 prev.send_new_challenge = true; -5190 // We haven't updated the remote CID yet, this captures the remote CID we were using on -5191 // the previous path. -5192 -5193 known_path.prev = Some((self.remote_cids.get(&path_id).unwrap().active(), prev)); -5194 } -5195 -5196 // We need to re-assign the correct remote to this path in qlog -5197 self.qlog.emit_tuple_assigned(path_id, network_path, now); -5198 -5199 self.timers.set( -5200 Timer::PerPath(path_id, PathTimer::PathValidation), -5201 now + 3 * cmp::max(self.pto(SpaceId::Data, path_id), prev_pto), -5202 self.qlog.with_time(now), -5203 ); -5204 } -5205 -5206 /// Handle a change in the local address, i.e. an active migration -5207 pub fn local_address_changed(&mut self) { -5208 // TODO(flub): if multipath is enabled this needs to create a new path entirely. -5209 self.update_remote_cid(PathId::ZERO); -5210 self.ping(); -5211 } -5212 -5213 /// Switch to a previously unused remote connection ID, if possible -5214 fn update_remote_cid(&mut self, path_id: PathId) { -5215 let Some((reset_token, retired)) = self -5216 .remote_cids -5217 .get_mut(&path_id) -5218 .and_then(|cids| cids.next()) -5219 else { -5220 return; -5221 }; -5222 -5223 // Retire the current remote CID and any CIDs we had to skip. -5224 self.spaces[SpaceId::Data] -5225 .pending -5226 .retire_cids -5227 .extend(retired.map(|seq| (path_id, seq))); -5228 let remote = self.path_data(path_id).network_path.remote; -5229 self.set_reset_token(path_id, remote, reset_token); -5230 } +5089 if let Err(err) = server_state.handle_reach_out(reach_out) { +5090 return Err(TransportError::PROTOCOL_VIOLATION(format!( +5091 "Nat traversal(REACH_OUT): {err}" +5092 ))); +5093 } +5094 } +5095 } +5096 } +5097 +5098 let space = self.spaces[SpaceId::Data].for_path(path_id); +5099 if space +5100 .pending_acks +5101 .packet_received(now, number, ack_eliciting, &space.dedup) +5102 { +5103 if self.abandoned_paths.contains(&path_id) { +5104 // § 3.4.3 QUIC-MULTIPATH: promptly send ACKs for packets received from +5105 // abandoned paths. +5106 space.pending_acks.set_immediate_ack_required(); +5107 } else { +5108 self.timers.set( +5109 Timer::PerPath(path_id, PathTimer::MaxAckDelay), +5110 now + self.ack_frequency.max_ack_delay, +5111 self.qlog.with_time(now), +5112 ); +5113 } +5114 } +5115 +5116 // Issue stream ID credit due to ACKs of outgoing finish/resets and incoming finish/resets +5117 // on stopped streams. Incoming finishes/resets on open streams are not handled here as they +5118 // are only freed, and hence only issue credit, once the application has been notified +5119 // during a read on the stream. +5120 let pending = &mut self.spaces[SpaceId::Data].pending; +5121 self.streams.queue_max_stream_id(pending); +5122 +5123 if let Some(reason) = close { +5124 self.state.move_to_draining(Some(reason.into())); +5125 self.connection_close_pending = true; +5126 } +5127 +5128 if Some(number) == self.spaces[SpaceId::Data].for_path(path_id).rx_packet +5129 && !is_probing_packet +5130 && network_path != self.path_data(path_id).network_path +5131 { +5132 let ConnectionSide::Server { ref server_config } = self.side else { +5133 panic!("packets from unknown remote should be dropped by clients"); +5134 }; +5135 debug_assert!( +5136 server_config.migration, +5137 "migration-initiating packets should have been dropped immediately" +5138 ); +5139 self.migrate(path_id, now, network_path, migration_observed_addr); +5140 // Break linkability, if possible +5141 self.update_remote_cid(path_id); +5142 self.spin = false; +5143 } +5144 +5145 Ok(()) +5146 } +5147 +5148 fn migrate( +5149 &mut self, +5150 path_id: PathId, +5151 now: Instant, +5152 network_path: FourTuple, +5153 observed_addr: Option<ObservedAddr>, +5154 ) { +5155 trace!(%network_path, %path_id, "migration initiated"); +5156 self.path_generation_counter = self.path_generation_counter.wrapping_add(1); +5157 // TODO(@divma): conditions for path migration in multipath are very specific, check them +5158 // again to prevent path migrations that should actually create a new path +5159 +5160 // Reset rtt/congestion state for new path unless it looks like a NAT rebinding. +5161 // Note that the congestion window will not grow until validation terminates. Helps mitigate +5162 // amplification attacks performed by spoofing source addresses. +5163 let prev_pto = self.pto(SpaceId::Data, path_id); +5164 let known_path = self.paths.get_mut(&path_id).expect("known path"); +5165 let path = &mut known_path.data; +5166 let mut new_path = if network_path.remote.is_ipv4() +5167 && network_path.remote.ip() == path.network_path.remote.ip() +5168 { +5169 PathData::from_previous(network_path, path, self.path_generation_counter, now) +5170 } else { +5171 let peer_max_udp_payload_size = +5172 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()) +5173 .unwrap_or(u16::MAX); +5174 PathData::new( +5175 network_path, +5176 self.allow_mtud, +5177 Some(peer_max_udp_payload_size), +5178 self.path_generation_counter, +5179 now, +5180 &self.config, +5181 ) +5182 }; +5183 new_path.last_observed_addr_report = path.last_observed_addr_report.clone(); +5184 if let Some(report) = observed_addr +5185 && let Some(updated) = new_path.update_observed_addr_report(report) +5186 { +5187 tracing::info!("adding observed addr event from migration"); +5188 self.events.push_back(Event::Path(PathEvent::ObservedAddr { +5189 id: path_id, +5190 addr: updated, +5191 })); +5192 } +5193 new_path.send_new_challenge = true; +5194 +5195 let mut prev = mem::replace(path, new_path); +5196 // Don't clobber the original path if the previous one hasn't been validated yet +5197 if !prev.is_validating_path() { +5198 prev.send_new_challenge = true; +5199 // We haven't updated the remote CID yet, this captures the remote CID we were using on +5200 // the previous path. +5201 +5202 known_path.prev = Some((self.remote_cids.get(&path_id).unwrap().active(), prev)); +5203 } +5204 +5205 // We need to re-assign the correct remote to this path in qlog +5206 self.qlog.emit_tuple_assigned(path_id, network_path, now); +5207 +5208 self.timers.set( +5209 Timer::PerPath(path_id, PathTimer::PathValidation), +5210 now + 3 * cmp::max(self.pto(SpaceId::Data, path_id), prev_pto), +5211 self.qlog.with_time(now), +5212 ); +5213 } +5214 +5215 /// Handle a change in the local address, i.e. an active migration +5216 pub fn local_address_changed(&mut self) { +5217 // TODO(flub): if multipath is enabled this needs to create a new path entirely. +5218 self.update_remote_cid(PathId::ZERO); +5219 self.ping(); +5220 } +5221 +5222 /// Switch to a previously unused remote connection ID, if possible +5223 fn update_remote_cid(&mut self, path_id: PathId) { +5224 let Some((reset_token, retired)) = self +5225 .remote_cids +5226 .get_mut(&path_id) +5227 .and_then(|cids| cids.next()) +5228 else { +5229 return; +5230 }; 5231 -5232 /// Sends this reset token to the endpoint -5233 /// -5234 /// The endpoint needs to know the reset tokens issued by the peer, so that if the peer -5235 /// sends a reset token it knows to route it to this connection. See RFC 9000 section -5236 /// 10.3. Stateless Reset. -5237 /// -5238 /// Reset tokens are different for each path, the endpoint identifies paths by peer -5239 /// socket address however, not by path ID. -5240 fn set_reset_token(&mut self, path_id: PathId, remote: SocketAddr, reset_token: ResetToken) { -5241 self.endpoint_events -5242 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); -5243 -5244 // During the handshake the server sends a reset token in the transport -5245 // parameters. When we are the client and we receive the reset token during the -5246 // handshake we want this to affect our peer transport parameters. -5247 // TODO(flub): Pretty sure this is pointless, the entire params is overwritten -5248 // shortly after this was called. And then the params don't have this anymore. -5249 if path_id == PathId::ZERO { -5250 self.peer_params.stateless_reset_token = Some(reset_token); -5251 } -5252 } -5253 -5254 /// Issue an initial set of connection IDs to the peer upon connection -5255 fn issue_first_cids(&mut self, now: Instant) { -5256 if self -5257 .local_cid_state -5258 .get(&PathId::ZERO) -5259 .expect("PathId::ZERO exists when the connection is created") -5260 .cid_len() -5261 == 0 -5262 { -5263 return; -5264 } -5265 -5266 // Subtract 1 to account for the CID we supplied while handshaking -5267 let mut n = self.peer_params.issue_cids_limit() - 1; -5268 if let ConnectionSide::Server { server_config } = &self.side -5269 && server_config.has_preferred_address() -5270 { -5271 // We also sent a CID in the transport parameters -5272 n -= 1; +5232 // Retire the current remote CID and any CIDs we had to skip. +5233 self.spaces[SpaceId::Data] +5234 .pending +5235 .retire_cids +5236 .extend(retired.map(|seq| (path_id, seq))); +5237 let remote = self.path_data(path_id).network_path.remote; +5238 self.set_reset_token(path_id, remote, reset_token); +5239 } +5240 +5241 /// Sends this reset token to the endpoint +5242 /// +5243 /// The endpoint needs to know the reset tokens issued by the peer, so that if the peer +5244 /// sends a reset token it knows to route it to this connection. See RFC 9000 section +5245 /// 10.3. Stateless Reset. +5246 /// +5247 /// Reset tokens are different for each path, the endpoint identifies paths by peer +5248 /// socket address however, not by path ID. +5249 fn set_reset_token(&mut self, path_id: PathId, remote: SocketAddr, reset_token: ResetToken) { +5250 self.endpoint_events +5251 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); +5252 +5253 // During the handshake the server sends a reset token in the transport +5254 // parameters. When we are the client and we receive the reset token during the +5255 // handshake we want this to affect our peer transport parameters. +5256 // TODO(flub): Pretty sure this is pointless, the entire params is overwritten +5257 // shortly after this was called. And then the params don't have this anymore. +5258 if path_id == PathId::ZERO { +5259 self.peer_params.stateless_reset_token = Some(reset_token); +5260 } +5261 } +5262 +5263 /// Issue an initial set of connection IDs to the peer upon connection +5264 fn issue_first_cids(&mut self, now: Instant) { +5265 if self +5266 .local_cid_state +5267 .get(&PathId::ZERO) +5268 .expect("PathId::ZERO exists when the connection is created") +5269 .cid_len() +5270 == 0 +5271 { +5272 return; 5273 } -5274 self.endpoint_events -5275 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); -5276 } -5277 -5278 /// Issues an initial set of CIDs for paths that have not yet had any CIDs issued -5279 /// -5280 /// Later CIDs are issued when CIDs expire or are retired by the peer. -5281 fn issue_first_path_cids(&mut self, now: Instant) { -5282 if let Some(max_path_id) = self.max_path_id() { -5283 let mut path_id = self.max_path_id_with_cids.next(); -5284 while path_id <= max_path_id { -5285 self.endpoint_events -5286 .push_back(EndpointEventInner::NeedIdentifiers( -5287 path_id, -5288 now, -5289 self.peer_params.issue_cids_limit(), -5290 )); -5291 path_id = path_id.next(); -5292 } -5293 self.max_path_id_with_cids = max_path_id; -5294 } -5295 } -5296 -5297 /// Populates a packet with frames -5298 /// -5299 /// This tries to fit as many frames as possible into the packet. -5300 /// -5301 /// *path_exclusive_only* means to only build frames which can only be sent on this -5302 /// *path. This is used in multipath for backup paths while there is still an active -5303 /// *path. -5304 fn populate_packet<'a, 'b>( -5305 &mut self, -5306 now: Instant, -5307 space_id: SpaceId, -5308 path_id: PathId, -5309 path_exclusive_only: bool, -5310 builder: &mut PacketBuilder<'a, 'b>, -5311 ) { -5312 let pn = builder.packet_number; -5313 let is_multipath_negotiated = self.is_multipath_negotiated(); -5314 let stats = &mut self.stats.frame_tx; -5315 let space = &mut self.spaces[space_id]; -5316 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; -5317 let is_0rtt = space_id == SpaceId::Data && space.crypto.is_none(); -5318 space -5319 .for_path(path_id) -5320 .pending_acks -5321 .maybe_ack_non_eliciting(); -5322 -5323 // HANDSHAKE_DONE -5324 if !is_0rtt -5325 && !path_exclusive_only -5326 && mem::replace(&mut space.pending.handshake_done, false) -5327 { -5328 builder.write_frame(frame::HandshakeDone, stats); -5329 } -5330 -5331 // REACH_OUT -5332 if let Some((round, addresses)) = space.pending.reach_out.as_mut() -5333 && !path_exclusive_only -5334 { -5335 while let Some(local_addr) = addresses.pop() { -5336 let reach_out = frame::ReachOut::new(*round, local_addr); -5337 if builder.frame_space_remaining() > reach_out.size() { -5338 builder.write_frame(reach_out, stats); -5339 } else { -5340 addresses.push(local_addr); -5341 break; -5342 } -5343 } -5344 if addresses.is_empty() { -5345 space.pending.reach_out = None; -5346 } -5347 } -5348 -5349 // OBSERVED_ADDR -5350 if !path_exclusive_only -5351 && space_id == SpaceId::Data -5352 && self -5353 .config -5354 .address_discovery_role -5355 .should_report(&self.peer_params.address_discovery_role) -5356 && (!path.observed_addr_sent || space.pending.observed_addr) -5357 { -5358 let frame = -5359 frame::ObservedAddr::new(path.network_path.remote, self.next_observed_addr_seq_no); -5360 if builder.frame_space_remaining() > frame.size() { -5361 builder.write_frame(frame, stats); -5362 -5363 self.next_observed_addr_seq_no = self.next_observed_addr_seq_no.saturating_add(1u8); -5364 path.observed_addr_sent = true; -5365 -5366 space.pending.observed_addr = false; -5367 } -5368 } -5369 -5370 // PING -5371 if mem::replace(&mut space.for_path(path_id).ping_pending, false) { -5372 builder.write_frame(frame::Ping, stats); -5373 } +5274 +5275 // Subtract 1 to account for the CID we supplied while handshaking +5276 let mut n = self.peer_params.issue_cids_limit() - 1; +5277 if let ConnectionSide::Server { server_config } = &self.side +5278 && server_config.has_preferred_address() +5279 { +5280 // We also sent a CID in the transport parameters +5281 n -= 1; +5282 } +5283 self.endpoint_events +5284 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); +5285 } +5286 +5287 /// Issues an initial set of CIDs for paths that have not yet had any CIDs issued +5288 /// +5289 /// Later CIDs are issued when CIDs expire or are retired by the peer. +5290 fn issue_first_path_cids(&mut self, now: Instant) { +5291 if let Some(max_path_id) = self.max_path_id() { +5292 let mut path_id = self.max_path_id_with_cids.next(); +5293 while path_id <= max_path_id { +5294 self.endpoint_events +5295 .push_back(EndpointEventInner::NeedIdentifiers( +5296 path_id, +5297 now, +5298 self.peer_params.issue_cids_limit(), +5299 )); +5300 path_id = path_id.next(); +5301 } +5302 self.max_path_id_with_cids = max_path_id; +5303 } +5304 } +5305 +5306 /// Populates a packet with frames +5307 /// +5308 /// This tries to fit as many frames as possible into the packet. +5309 /// +5310 /// *path_exclusive_only* means to only build frames which can only be sent on this +5311 /// *path. This is used in multipath for backup paths while there is still an active +5312 /// *path. +5313 fn populate_packet<'a, 'b>( +5314 &mut self, +5315 now: Instant, +5316 space_id: SpaceId, +5317 path_id: PathId, +5318 path_exclusive_only: bool, +5319 builder: &mut PacketBuilder<'a, 'b>, +5320 ) { +5321 let pn = builder.packet_number; +5322 let is_multipath_negotiated = self.is_multipath_negotiated(); +5323 let stats = &mut self.stats.frame_tx; +5324 let space = &mut self.spaces[space_id]; +5325 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; +5326 let is_0rtt = space_id == SpaceId::Data && space.crypto.is_none(); +5327 space +5328 .for_path(path_id) +5329 .pending_acks +5330 .maybe_ack_non_eliciting(); +5331 +5332 // HANDSHAKE_DONE +5333 if !is_0rtt +5334 && !path_exclusive_only +5335 && mem::replace(&mut space.pending.handshake_done, false) +5336 { +5337 builder.write_frame(frame::HandshakeDone, stats); +5338 } +5339 +5340 // REACH_OUT +5341 if let Some((round, addresses)) = space.pending.reach_out.as_mut() +5342 && !path_exclusive_only +5343 { +5344 while let Some(local_addr) = addresses.pop() { +5345 let reach_out = frame::ReachOut::new(*round, local_addr); +5346 if builder.frame_space_remaining() > reach_out.size() { +5347 builder.write_frame(reach_out, stats); +5348 } else { +5349 addresses.push(local_addr); +5350 break; +5351 } +5352 } +5353 if addresses.is_empty() { +5354 space.pending.reach_out = None; +5355 } +5356 } +5357 +5358 // OBSERVED_ADDR +5359 if !path_exclusive_only +5360 && space_id == SpaceId::Data +5361 && self +5362 .config +5363 .address_discovery_role +5364 .should_report(&self.peer_params.address_discovery_role) +5365 && (!path.observed_addr_sent || space.pending.observed_addr) +5366 { +5367 let frame = +5368 frame::ObservedAddr::new(path.network_path.remote, self.next_observed_addr_seq_no); +5369 if builder.frame_space_remaining() > frame.size() { +5370 builder.write_frame(frame, stats); +5371 +5372 self.next_observed_addr_seq_no = self.next_observed_addr_seq_no.saturating_add(1u8); +5373 path.observed_addr_sent = true; 5374 -5375 // IMMEDIATE_ACK -5376 if mem::replace(&mut space.for_path(path_id).immediate_ack_pending, false) { -5377 debug_assert_eq!( -5378 space_id, -5379 SpaceId::Data, -5380 "immediate acks must be sent in the data space" -5381 ); -5382 builder.write_frame(frame::ImmediateAck, stats); -5383 } -5384 -5385 // ACK -5386 // TODO(flub): Should this send acks for this path anyway? -5387 -5388 if !path_exclusive_only { -5389 for path_id in space -5390 .number_spaces -5391 .iter_mut() -5392 .filter(|(_, pns)| pns.pending_acks.can_send()) -5393 .map(|(&path_id, _)| path_id) -5394 .collect::<Vec<_>>() -5395 { -5396 Self::populate_acks( -5397 now, -5398 self.receiving_ecn, -5399 path_id, -5400 space_id, -5401 space, -5402 is_multipath_negotiated, -5403 builder, -5404 stats, -5405 ); -5406 } -5407 } -5408 -5409 // ACK_FREQUENCY -5410 if !path_exclusive_only && mem::replace(&mut space.pending.ack_frequency, false) { -5411 let sequence_number = self.ack_frequency.next_sequence_number(); -5412 -5413 // Safe to unwrap because this is always provided when ACK frequency is enabled -5414 let config = self.config.ack_frequency_config.as_ref().unwrap(); -5415 -5416 // Ensure the delay is within bounds to avoid a PROTOCOL_VIOLATION error -5417 let max_ack_delay = self.ack_frequency.candidate_max_ack_delay( -5418 path.rtt.get(), -5419 config, -5420 &self.peer_params, -5421 ); -5422 -5423 let frame = frame::AckFrequency { -5424 sequence: sequence_number, -5425 ack_eliciting_threshold: config.ack_eliciting_threshold, -5426 request_max_ack_delay: max_ack_delay.as_micros().try_into().unwrap_or(VarInt::MAX), -5427 reordering_threshold: config.reordering_threshold, -5428 }; -5429 builder.write_frame(frame, stats); -5430 -5431 self.ack_frequency -5432 .ack_frequency_sent(path_id, pn, max_ack_delay); -5433 } -5434 -5435 // PATH_CHALLENGE -5436 if builder.frame_space_remaining() > frame::PathChallenge::SIZE_BOUND -5437 && space_id == SpaceId::Data -5438 && path.send_new_challenge -5439 && !self.state.is_closed() -5440 // we don't want to send new challenges if we are already closing -5441 { -5442 path.send_new_challenge = false; +5375 space.pending.observed_addr = false; +5376 } +5377 } +5378 +5379 // PING +5380 if mem::replace(&mut space.for_path(path_id).ping_pending, false) { +5381 builder.write_frame(frame::Ping, stats); +5382 } +5383 +5384 // IMMEDIATE_ACK +5385 if mem::replace(&mut space.for_path(path_id).immediate_ack_pending, false) { +5386 debug_assert_eq!( +5387 space_id, +5388 SpaceId::Data, +5389 "immediate acks must be sent in the data space" +5390 ); +5391 builder.write_frame(frame::ImmediateAck, stats); +5392 } +5393 +5394 // ACK +5395 // TODO(flub): Should this send acks for this path anyway? +5396 +5397 if !path_exclusive_only { +5398 for path_id in space +5399 .number_spaces +5400 .iter_mut() +5401 .filter(|(_, pns)| pns.pending_acks.can_send()) +5402 .map(|(&path_id, _)| path_id) +5403 .collect::<Vec<_>>() +5404 { +5405 Self::populate_acks( +5406 now, +5407 self.receiving_ecn, +5408 path_id, +5409 space_id, +5410 space, +5411 is_multipath_negotiated, +5412 builder, +5413 stats, +5414 ); +5415 } +5416 } +5417 +5418 // ACK_FREQUENCY +5419 if !path_exclusive_only && mem::replace(&mut space.pending.ack_frequency, false) { +5420 let sequence_number = self.ack_frequency.next_sequence_number(); +5421 +5422 // Safe to unwrap because this is always provided when ACK frequency is enabled +5423 let config = self.config.ack_frequency_config.as_ref().unwrap(); +5424 +5425 // Ensure the delay is within bounds to avoid a PROTOCOL_VIOLATION error +5426 let max_ack_delay = self.ack_frequency.candidate_max_ack_delay( +5427 path.rtt.get(), +5428 config, +5429 &self.peer_params, +5430 ); +5431 +5432 let frame = frame::AckFrequency { +5433 sequence: sequence_number, +5434 ack_eliciting_threshold: config.ack_eliciting_threshold, +5435 request_max_ack_delay: max_ack_delay.as_micros().try_into().unwrap_or(VarInt::MAX), +5436 reordering_threshold: config.reordering_threshold, +5437 }; +5438 builder.write_frame(frame, stats); +5439 +5440 self.ack_frequency +5441 .ack_frequency_sent(path_id, pn, max_ack_delay); +5442 } 5443 -5444 // Generate a new challenge every time we send a new PATH_CHALLENGE -5445 let token = self.rng.random(); -5446 let info = paths::SentChallengeInfo { -5447 sent_instant: now, -5448 network_path: path.network_path, -5449 }; -5450 path.challenges_sent.insert(token, info); -5451 let challenge = frame::PathChallenge(token); -5452 trace!(frame = %challenge); -5453 builder.write_frame(challenge, stats); -5454 builder.require_padding(); -5455 let pto = self.ack_frequency.max_ack_delay_for_pto() + path.rtt.pto_base(); -5456 self.timers.set( -5457 Timer::PerPath(path_id, PathTimer::PathChallengeLost), -5458 now + pto, -5459 self.qlog.with_time(now), -5460 ); -5461 -5462 if is_multipath_negotiated && !path.validated && path.send_new_challenge { -5463 // queue informing the path status along with the challenge -5464 space.pending.path_status.insert(path_id); -5465 } -5466 -5467 // Always include an OBSERVED_ADDR frame with a PATH_CHALLENGE, regardless -5468 // of whether one has already been sent on this path. -5469 if space_id == SpaceId::Data -5470 && self -5471 .config -5472 .address_discovery_role -5473 .should_report(&self.peer_params.address_discovery_role) -5474 { -5475 let frame = frame::ObservedAddr::new( -5476 path.network_path.remote, -5477 self.next_observed_addr_seq_no, -5478 ); -5479 if builder.frame_space_remaining() > frame.size() { -5480 builder.write_frame(frame, stats); -5481 -5482 self.next_observed_addr_seq_no = -5483 self.next_observed_addr_seq_no.saturating_add(1u8); -5484 path.observed_addr_sent = true; -5485 -5486 space.pending.observed_addr = false; -5487 } -5488 } -5489 } +5444 // PATH_CHALLENGE +5445 if builder.frame_space_remaining() > frame::PathChallenge::SIZE_BOUND +5446 && space_id == SpaceId::Data +5447 && path.send_new_challenge +5448 && !self.state.is_closed() +5449 // we don't want to send new challenges if we are already closing +5450 { +5451 path.send_new_challenge = false; +5452 +5453 // Generate a new challenge every time we send a new PATH_CHALLENGE +5454 let token = self.rng.random(); +5455 let info = paths::SentChallengeInfo { +5456 sent_instant: now, +5457 network_path: path.network_path, +5458 }; +5459 path.challenges_sent.insert(token, info); +5460 let challenge = frame::PathChallenge(token); +5461 trace!(frame = %challenge); +5462 builder.write_frame(challenge, stats); +5463 builder.require_padding(); +5464 let pto = self.ack_frequency.max_ack_delay_for_pto() + path.rtt.pto_base(); +5465 self.timers.set( +5466 Timer::PerPath(path_id, PathTimer::PathChallengeLost), +5467 now + pto, +5468 self.qlog.with_time(now), +5469 ); +5470 +5471 if is_multipath_negotiated && !path.validated && path.send_new_challenge { +5472 // queue informing the path status along with the challenge +5473 space.pending.path_status.insert(path_id); +5474 } +5475 +5476 // Always include an OBSERVED_ADDR frame with a PATH_CHALLENGE, regardless +5477 // of whether one has already been sent on this path. +5478 if space_id == SpaceId::Data +5479 && self +5480 .config +5481 .address_discovery_role +5482 .should_report(&self.peer_params.address_discovery_role) +5483 { +5484 let frame = frame::ObservedAddr::new( +5485 path.network_path.remote, +5486 self.next_observed_addr_seq_no, +5487 ); +5488 if builder.frame_space_remaining() > frame.size() { +5489 builder.write_frame(frame, stats); 5490 -5491 // PATH_RESPONSE -5492 if builder.frame_space_remaining() > frame::PathResponse::SIZE_BOUND -5493 && space_id == SpaceId::Data -5494 && let Some(token) = path.path_responses.pop_on_path(path.network_path) -5495 { -5496 let response = frame::PathResponse(token); -5497 trace!(frame = %response); -5498 builder.write_frame(response, stats); -5499 builder.require_padding(); -5500 -5501 // NOTE: this is technically not required but might be useful to ride the -5502 // request/response nature of path challenges to refresh an observation -5503 // Since PATH_RESPONSE is a probing frame, this is allowed by the spec. -5504 if space_id == SpaceId::Data -5505 && self -5506 .config -5507 .address_discovery_role -5508 .should_report(&self.peer_params.address_discovery_role) -5509 { -5510 let frame = frame::ObservedAddr::new( -5511 path.network_path.remote, -5512 self.next_observed_addr_seq_no, -5513 ); -5514 if builder.frame_space_remaining() > frame.size() { -5515 builder.write_frame(frame, stats); -5516 -5517 self.next_observed_addr_seq_no = -5518 self.next_observed_addr_seq_no.saturating_add(1u8); -5519 path.observed_addr_sent = true; -5520 -5521 space.pending.observed_addr = false; -5522 } -5523 } -5524 } +5491 self.next_observed_addr_seq_no = +5492 self.next_observed_addr_seq_no.saturating_add(1u8); +5493 path.observed_addr_sent = true; +5494 +5495 space.pending.observed_addr = false; +5496 } +5497 } +5498 } +5499 +5500 // PATH_RESPONSE +5501 if builder.frame_space_remaining() > frame::PathResponse::SIZE_BOUND +5502 && space_id == SpaceId::Data +5503 && let Some(token) = path.path_responses.pop_on_path(path.network_path) +5504 { +5505 let response = frame::PathResponse(token); +5506 trace!(frame = %response); +5507 builder.write_frame(response, stats); +5508 builder.require_padding(); +5509 +5510 // NOTE: this is technically not required but might be useful to ride the +5511 // request/response nature of path challenges to refresh an observation +5512 // Since PATH_RESPONSE is a probing frame, this is allowed by the spec. +5513 if space_id == SpaceId::Data +5514 && self +5515 .config +5516 .address_discovery_role +5517 .should_report(&self.peer_params.address_discovery_role) +5518 { +5519 let frame = frame::ObservedAddr::new( +5520 path.network_path.remote, +5521 self.next_observed_addr_seq_no, +5522 ); +5523 if builder.frame_space_remaining() > frame.size() { +5524 builder.write_frame(frame, stats); 5525 -5526 // CRYPTO -5527 while !path_exclusive_only -5528 && builder.frame_space_remaining() > frame::Crypto::SIZE_BOUND -5529 && !is_0rtt -5530 { -5531 let mut frame = match space.pending.crypto.pop_front() { -5532 Some(x) => x, -5533 None => break, -5534 }; -5535 -5536 // Calculate the maximum amount of crypto data we can store in the buffer. -5537 // Since the offset is known, we can reserve the exact size required to encode it. -5538 // For length we reserve 2bytes which allows to encode up to 2^14, -5539 // which is more than what fits into normally sized QUIC frames. -5540 let max_crypto_data_size = builder.frame_space_remaining() -5541 - 1 // Frame Type -5542 - VarInt::size(unsafe { VarInt::from_u64_unchecked(frame.offset) }) -5543 - 2; // Maximum encoded length for frame size, given we send less than 2^14 bytes +5526 self.next_observed_addr_seq_no = +5527 self.next_observed_addr_seq_no.saturating_add(1u8); +5528 path.observed_addr_sent = true; +5529 +5530 space.pending.observed_addr = false; +5531 } +5532 } +5533 } +5534 +5535 // CRYPTO +5536 while !path_exclusive_only +5537 && builder.frame_space_remaining() > frame::Crypto::SIZE_BOUND +5538 && !is_0rtt +5539 { +5540 let mut frame = match space.pending.crypto.pop_front() { +5541 Some(x) => x, +5542 None => break, +5543 }; 5544 -5545 let len = frame -5546 .data -5547 .len() -5548 .min(2usize.pow(14) - 1) -5549 .min(max_crypto_data_size); -5550 -5551 let data = frame.data.split_to(len); -5552 let offset = frame.offset; -5553 let truncated = frame::Crypto { offset, data }; -5554 builder.write_frame(truncated, stats); -5555 -5556 if !frame.data.is_empty() { -5557 frame.offset += len as u64; -5558 space.pending.crypto.push_front(frame); -5559 } -5560 } -5561 -5562 // TODO(flub): maybe this is much higher priority? -5563 // PATH_ABANDON -5564 while !path_exclusive_only -5565 && space_id == SpaceId::Data -5566 && frame::PathAbandon::SIZE_BOUND <= builder.frame_space_remaining() -5567 { -5568 let Some((abandoned_path_id, error_code)) = space.pending.path_abandon.pop_first() -5569 else { -5570 break; -5571 }; -5572 let frame = frame::PathAbandon { -5573 path_id: abandoned_path_id, -5574 error_code, -5575 }; -5576 builder.write_frame(frame, stats); -5577 -5578 let ack_delay = self.ack_frequency.max_ack_delay_for_pto(); -5579 // We can't access path here anymore due to borrowing issues. -5580 let send_pto = self.paths.get(&path_id).unwrap().data.rtt.pto_base() + ack_delay; -5581 if let Some(abandoned_path) = self.paths.get_mut(&abandoned_path_id) { -5582 // We only want to set the deadline on the *first* PATH_ABANDON we send. -5583 // Retransmits shouldn't run this code again -5584 if matches!( -5585 abandoned_path.data.abandon_state, -5586 AbandonState::NotAbandoned -5587 ) { -5588 // The peer MUST respond with a corresponding PATH_ABANDON frame. -5589 // The other peer has 3 * PTO to do that. -5590 // This uses the PTO of the path we send on! -5591 // If the PATH_ABANDON comes in within the deadline we're giving here, then this -5592 // state will be set to `AbandonState::ReceivedPathAbandon`, essentially clearing -5593 // the deadline. If we receive a frame after the deadline, we error out with a -5594 // protocol violation. -5595 // Receiving other frames before the deadline is fine, as those might be packets -5596 // that were still in-flight. -5597 abandoned_path.data.abandon_state = AbandonState::ExpectingPathAbandon { -5598 deadline: now + 3 * send_pto, -5599 }; -5600 -5601 // At some point, we need to forget about the path. -5602 // If we do so too early, then we'll have discarded the CIDs of that path and won't -5603 // handle incoming packets on that path correctly. -5604 // To give this path enough time, we assume that the peer will have received our -5605 // PATH_ABANDON within 3 * PTO of the path we sent the abandon on, -5606 // and then we give the path 3 * PTO time to make it very unlikely that there will -5607 // still be packets incoming on the path at that point. -5608 // This timer will actually get reset to a value that's likely to be even earlier -5609 // once we actually receive the PATH_ABANDON frame itself. -5610 let abandoned_pto = -5611 self.paths.get(&path_id).unwrap().data.rtt.pto_base() + ack_delay; -5612 self.timers.set( -5613 Timer::PerPath(abandoned_path_id, PathTimer::DiscardPath), -5614 now + 3 * send_pto + 3 * abandoned_pto, -5615 self.qlog.with_time(now), -5616 ); -5617 } -5618 } else { -5619 warn!("sent PATH_ABANDON after path was already discarded"); -5620 } -5621 } -5622 -5623 // PATH_STATUS_AVAILABLE & PATH_STATUS_BACKUP -5624 while !path_exclusive_only -5625 && space_id == SpaceId::Data -5626 && frame::PathStatusAvailable::SIZE_BOUND <= builder.frame_space_remaining() -5627 { -5628 let Some(path_id) = space.pending.path_status.pop_first() else { -5629 break; -5630 }; -5631 let Some(path) = self.paths.get(&path_id).map(|path_state| &path_state.data) else { -5632 trace!(%path_id, "discarding queued path status for unknown path"); -5633 continue; -5634 }; -5635 -5636 let seq = path.status.seq(); -5637 match path.local_status() { -5638 PathStatus::Available => { -5639 let frame = frame::PathStatusAvailable { -5640 path_id, -5641 status_seq_no: seq, -5642 }; -5643 builder.write_frame(frame, stats); -5644 } -5645 PathStatus::Backup => { -5646 let frame = frame::PathStatusBackup { -5647 path_id, -5648 status_seq_no: seq, -5649 }; -5650 builder.write_frame(frame, stats); -5651 } -5652 } -5653 } -5654 -5655 // MAX_PATH_ID -5656 if space_id == SpaceId::Data -5657 && !path_exclusive_only -5658 && space.pending.max_path_id -5659 && frame::MaxPathId::SIZE_BOUND <= builder.frame_space_remaining() -5660 { -5661 let frame = frame::MaxPathId(self.local_max_path_id); -5662 builder.write_frame(frame, stats); -5663 space.pending.max_path_id = false; -5664 } -5665 -5666 // PATHS_BLOCKED -5667 if space_id == SpaceId::Data -5668 && !path_exclusive_only -5669 && space.pending.paths_blocked -5670 && frame::PathsBlocked::SIZE_BOUND <= builder.frame_space_remaining() -5671 { -5672 let frame = frame::PathsBlocked(self.remote_max_path_id); -5673 builder.write_frame(frame, stats); -5674 space.pending.paths_blocked = false; -5675 } -5676 -5677 // PATH_CIDS_BLOCKED -5678 while space_id == SpaceId::Data -5679 && !path_exclusive_only -5680 && frame::PathCidsBlocked::SIZE_BOUND <= builder.frame_space_remaining() -5681 { -5682 let Some(path_id) = space.pending.path_cids_blocked.pop_first() else { -5683 break; -5684 }; -5685 let next_seq = match self.remote_cids.get(&path_id) { -5686 Some(cid_queue) => VarInt(cid_queue.active_seq() + 1), -5687 None => VarInt(0), -5688 }; -5689 let frame = frame::PathCidsBlocked { path_id, next_seq }; -5690 builder.write_frame(frame, stats); -5691 } -5692 -5693 // RESET_STREAM, STOP_SENDING, MAX_DATA, MAX_STREAM_DATA, MAX_STREAMS -5694 if space_id == SpaceId::Data && !path_exclusive_only { -5695 self.streams -5696 .write_control_frames(builder, &mut space.pending, stats); -5697 } -5698 -5699 // NEW_CONNECTION_ID -5700 let cid_len = self -5701 .local_cid_state -5702 .values() -5703 .map(|cid_state| cid_state.cid_len()) -5704 .max() -5705 .expect("some local CID state must exist"); -5706 let new_cid_size_bound = -5707 frame::NewConnectionId::size_bound(is_multipath_negotiated, cid_len); -5708 while !path_exclusive_only && builder.frame_space_remaining() > new_cid_size_bound { -5709 let issued = match space.pending.new_cids.pop() { -5710 Some(x) => x, -5711 None => break, -5712 }; -5713 let retire_prior_to = self -5714 .local_cid_state -5715 .get(&issued.path_id) -5716 .map(|cid_state| cid_state.retire_prior_to()) -5717 .unwrap_or_else(|| panic!("missing local CID state for path={}", issued.path_id)); -5718 -5719 let cid_path_id = match is_multipath_negotiated { -5720 true => Some(issued.path_id), -5721 false => { -5722 debug_assert_eq!(issued.path_id, PathId::ZERO); -5723 None -5724 } -5725 }; -5726 let frame = frame::NewConnectionId { -5727 path_id: cid_path_id, -5728 sequence: issued.sequence, -5729 retire_prior_to, -5730 id: issued.id, -5731 reset_token: issued.reset_token, -5732 }; -5733 builder.write_frame(frame, stats); -5734 } -5735 -5736 // RETIRE_CONNECTION_ID -5737 let retire_cid_bound = frame::RetireConnectionId::size_bound(is_multipath_negotiated); -5738 while !path_exclusive_only && builder.frame_space_remaining() > retire_cid_bound { -5739 let (path_id, sequence) = match space.pending.retire_cids.pop() { -5740 Some((PathId::ZERO, seq)) if !is_multipath_negotiated => (None, seq), -5741 Some((path_id, seq)) => (Some(path_id), seq), -5742 None => break, -5743 }; -5744 let frame = frame::RetireConnectionId { path_id, sequence }; -5745 builder.write_frame(frame, stats); -5746 } -5747 -5748 // DATAGRAM -5749 let mut sent_datagrams = false; -5750 while !path_exclusive_only -5751 && builder.frame_space_remaining() > Datagram::SIZE_BOUND -5752 && space_id == SpaceId::Data -5753 { -5754 match self.datagrams.write(builder, stats) { -5755 true => { -5756 sent_datagrams = true; -5757 } -5758 false => break, -5759 } -5760 } -5761 if self.datagrams.send_blocked && sent_datagrams { -5762 self.events.push_back(Event::DatagramsUnblocked); -5763 self.datagrams.send_blocked = false; -5764 } -5765 -5766 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; -5767 -5768 // NEW_TOKEN -5769 if !path_exclusive_only { -5770 while let Some(network_path) = space.pending.new_tokens.pop() { -5771 debug_assert_eq!(space_id, SpaceId::Data); -5772 let ConnectionSide::Server { server_config } = &self.side else { -5773 panic!("NEW_TOKEN frames should not be enqueued by clients"); -5774 }; -5775 -5776 if !network_path.is_probably_same_path(&path.network_path) { -5777 // NEW_TOKEN frames contain tokens bound to a client's IP address, and are only -5778 // useful if used from the same IP address. Thus, we abandon enqueued NEW_TOKEN -5779 // frames upon an path change. Instead, when the new path becomes validated, -5780 // NEW_TOKEN frames may be enqueued for the new path instead. -5781 continue; -5782 } -5783 -5784 let token = Token::new( -5785 TokenPayload::Validation { -5786 ip: network_path.remote.ip(), -5787 issued: server_config.time_source.now(), -5788 }, -5789 &mut self.rng, -5790 ); -5791 let new_token = NewToken { -5792 token: token.encode(&*server_config.token_key).into(), -5793 }; -5794 -5795 if builder.frame_space_remaining() < new_token.size() { -5796 space.pending.new_tokens.push(network_path); -5797 break; -5798 } -5799 -5800 builder.write_frame(new_token, stats); -5801 builder.retransmits_mut().new_tokens.push(network_path); -5802 } -5803 } -5804 -5805 // STREAM -5806 if !path_exclusive_only && space_id == SpaceId::Data { -5807 self.streams -5808 .write_stream_frames(builder, self.config.send_fairness, stats); -5809 } -5810 -5811 // ADD_ADDRESS -5812 while space_id == SpaceId::Data -5813 && !path_exclusive_only -5814 && frame::AddAddress::SIZE_BOUND <= builder.frame_space_remaining() -5815 { -5816 if let Some(added_address) = space.pending.add_address.pop_last() { -5817 builder.write_frame(added_address, stats); -5818 } else { -5819 break; -5820 } -5821 } -5822 -5823 // REMOVE_ADDRESS -5824 while space_id == SpaceId::Data -5825 && !path_exclusive_only -5826 && frame::RemoveAddress::SIZE_BOUND <= builder.frame_space_remaining() -5827 { -5828 if let Some(removed_address) = space.pending.remove_address.pop_last() { -5829 builder.write_frame(removed_address, stats); -5830 } else { -5831 break; -5832 } -5833 } -5834 } -5835 -5836 /// Write pending ACKs into a buffer -5837 fn populate_acks<'a, 'b>( -5838 now: Instant, -5839 receiving_ecn: bool, -5840 path_id: PathId, -5841 space_id: SpaceId, -5842 space: &mut PacketSpace, -5843 is_multipath_negotiated: bool, -5844 builder: &mut PacketBuilder<'a, 'b>, -5845 stats: &mut FrameStats, -5846 ) { -5847 // 0-RTT packets must never carry acks (which would have to be of handshake packets) -5848 debug_assert!(space.crypto.is_some(), "tried to send ACK in 0-RTT"); -5849 -5850 debug_assert!( -5851 is_multipath_negotiated || path_id == PathId::ZERO, -5852 "Only PathId::ZERO allowed without multipath (have {path_id:?})" -5853 ); -5854 if is_multipath_negotiated { -5855 debug_assert!( -5856 space_id == SpaceId::Data || path_id == PathId::ZERO, -5857 "path acks must be sent in 1RTT space (have {space_id:?})" -5858 ); -5859 } -5860 -5861 let pns = space.for_path(path_id); -5862 let ranges = pns.pending_acks.ranges(); -5863 debug_assert!(!ranges.is_empty(), "can not send empty ACK range"); -5864 let ecn = if receiving_ecn { -5865 Some(&pns.ecn_counters) -5866 } else { -5867 None -5868 }; +5545 // Calculate the maximum amount of crypto data we can store in the buffer. +5546 // Since the offset is known, we can reserve the exact size required to encode it. +5547 // For length we reserve 2bytes which allows to encode up to 2^14, +5548 // which is more than what fits into normally sized QUIC frames. +5549 let max_crypto_data_size = builder.frame_space_remaining() +5550 - 1 // Frame Type +5551 - VarInt::size(unsafe { VarInt::from_u64_unchecked(frame.offset) }) +5552 - 2; // Maximum encoded length for frame size, given we send less than 2^14 bytes +5553 +5554 let len = frame +5555 .data +5556 .len() +5557 .min(2usize.pow(14) - 1) +5558 .min(max_crypto_data_size); +5559 +5560 let data = frame.data.split_to(len); +5561 let offset = frame.offset; +5562 let truncated = frame::Crypto { offset, data }; +5563 builder.write_frame(truncated, stats); +5564 +5565 if !frame.data.is_empty() { +5566 frame.offset += len as u64; +5567 space.pending.crypto.push_front(frame); +5568 } +5569 } +5570 +5571 // TODO(flub): maybe this is much higher priority? +5572 // PATH_ABANDON +5573 while !path_exclusive_only +5574 && space_id == SpaceId::Data +5575 && frame::PathAbandon::SIZE_BOUND <= builder.frame_space_remaining() +5576 { +5577 let Some((abandoned_path_id, error_code)) = space.pending.path_abandon.pop_first() +5578 else { +5579 break; +5580 }; +5581 let frame = frame::PathAbandon { +5582 path_id: abandoned_path_id, +5583 error_code, +5584 }; +5585 builder.write_frame(frame, stats); +5586 +5587 let ack_delay = self.ack_frequency.max_ack_delay_for_pto(); +5588 // We can't access path here anymore due to borrowing issues. +5589 let send_pto = self.paths.get(&path_id).unwrap().data.rtt.pto_base() + ack_delay; +5590 if let Some(abandoned_path) = self.paths.get_mut(&abandoned_path_id) { +5591 // We only want to set the deadline on the *first* PATH_ABANDON we send. +5592 // Retransmits shouldn't run this code again +5593 if matches!( +5594 abandoned_path.data.abandon_state, +5595 AbandonState::NotAbandoned +5596 ) { +5597 // The peer MUST respond with a corresponding PATH_ABANDON frame. +5598 // The other peer has 3 * PTO to do that. +5599 // This uses the PTO of the path we send on! +5600 // If the PATH_ABANDON comes in within the deadline we're giving here, then this +5601 // state will be set to `AbandonState::ReceivedPathAbandon`, essentially clearing +5602 // the deadline. If we receive a frame after the deadline, we error out with a +5603 // protocol violation. +5604 // Receiving other frames before the deadline is fine, as those might be packets +5605 // that were still in-flight. +5606 abandoned_path.data.abandon_state = AbandonState::ExpectingPathAbandon { +5607 deadline: now + 3 * send_pto, +5608 }; +5609 +5610 // At some point, we need to forget about the path. +5611 // If we do so too early, then we'll have discarded the CIDs of that path and won't +5612 // handle incoming packets on that path correctly. +5613 // To give this path enough time, we assume that the peer will have received our +5614 // PATH_ABANDON within 3 * PTO of the path we sent the abandon on, +5615 // and then we give the path 3 * PTO time to make it very unlikely that there will +5616 // still be packets incoming on the path at that point. +5617 // This timer will actually get reset to a value that's likely to be even earlier +5618 // once we actually receive the PATH_ABANDON frame itself. +5619 let abandoned_pto = +5620 self.paths.get(&path_id).unwrap().data.rtt.pto_base() + ack_delay; +5621 self.timers.set( +5622 Timer::PerPath(abandoned_path_id, PathTimer::DiscardPath), +5623 now + 3 * send_pto + 3 * abandoned_pto, +5624 self.qlog.with_time(now), +5625 ); +5626 } +5627 } else { +5628 warn!("sent PATH_ABANDON after path was already discarded"); +5629 } +5630 } +5631 +5632 // PATH_STATUS_AVAILABLE & PATH_STATUS_BACKUP +5633 while !path_exclusive_only +5634 && space_id == SpaceId::Data +5635 && frame::PathStatusAvailable::SIZE_BOUND <= builder.frame_space_remaining() +5636 { +5637 let Some(path_id) = space.pending.path_status.pop_first() else { +5638 break; +5639 }; +5640 let Some(path) = self.paths.get(&path_id).map(|path_state| &path_state.data) else { +5641 trace!(%path_id, "discarding queued path status for unknown path"); +5642 continue; +5643 }; +5644 +5645 let seq = path.status.seq(); +5646 match path.local_status() { +5647 PathStatus::Available => { +5648 let frame = frame::PathStatusAvailable { +5649 path_id, +5650 status_seq_no: seq, +5651 }; +5652 builder.write_frame(frame, stats); +5653 } +5654 PathStatus::Backup => { +5655 let frame = frame::PathStatusBackup { +5656 path_id, +5657 status_seq_no: seq, +5658 }; +5659 builder.write_frame(frame, stats); +5660 } +5661 } +5662 } +5663 +5664 // MAX_PATH_ID +5665 if space_id == SpaceId::Data +5666 && !path_exclusive_only +5667 && space.pending.max_path_id +5668 && frame::MaxPathId::SIZE_BOUND <= builder.frame_space_remaining() +5669 { +5670 let frame = frame::MaxPathId(self.local_max_path_id); +5671 builder.write_frame(frame, stats); +5672 space.pending.max_path_id = false; +5673 } +5674 +5675 // PATHS_BLOCKED +5676 if space_id == SpaceId::Data +5677 && !path_exclusive_only +5678 && space.pending.paths_blocked +5679 && frame::PathsBlocked::SIZE_BOUND <= builder.frame_space_remaining() +5680 { +5681 let frame = frame::PathsBlocked(self.remote_max_path_id); +5682 builder.write_frame(frame, stats); +5683 space.pending.paths_blocked = false; +5684 } +5685 +5686 // PATH_CIDS_BLOCKED +5687 while space_id == SpaceId::Data +5688 && !path_exclusive_only +5689 && frame::PathCidsBlocked::SIZE_BOUND <= builder.frame_space_remaining() +5690 { +5691 let Some(path_id) = space.pending.path_cids_blocked.pop_first() else { +5692 break; +5693 }; +5694 let next_seq = match self.remote_cids.get(&path_id) { +5695 Some(cid_queue) => VarInt(cid_queue.active_seq() + 1), +5696 None => VarInt(0), +5697 }; +5698 let frame = frame::PathCidsBlocked { path_id, next_seq }; +5699 builder.write_frame(frame, stats); +5700 } +5701 +5702 // RESET_STREAM, STOP_SENDING, MAX_DATA, MAX_STREAM_DATA, MAX_STREAMS +5703 if space_id == SpaceId::Data && !path_exclusive_only { +5704 self.streams +5705 .write_control_frames(builder, &mut space.pending, stats); +5706 } +5707 +5708 // NEW_CONNECTION_ID +5709 let cid_len = self +5710 .local_cid_state +5711 .values() +5712 .map(|cid_state| cid_state.cid_len()) +5713 .max() +5714 .expect("some local CID state must exist"); +5715 let new_cid_size_bound = +5716 frame::NewConnectionId::size_bound(is_multipath_negotiated, cid_len); +5717 while !path_exclusive_only && builder.frame_space_remaining() > new_cid_size_bound { +5718 let issued = match space.pending.new_cids.pop() { +5719 Some(x) => x, +5720 None => break, +5721 }; +5722 let retire_prior_to = self +5723 .local_cid_state +5724 .get(&issued.path_id) +5725 .map(|cid_state| cid_state.retire_prior_to()) +5726 .unwrap_or_else(|| panic!("missing local CID state for path={}", issued.path_id)); +5727 +5728 let cid_path_id = match is_multipath_negotiated { +5729 true => Some(issued.path_id), +5730 false => { +5731 debug_assert_eq!(issued.path_id, PathId::ZERO); +5732 None +5733 } +5734 }; +5735 let frame = frame::NewConnectionId { +5736 path_id: cid_path_id, +5737 sequence: issued.sequence, +5738 retire_prior_to, +5739 id: issued.id, +5740 reset_token: issued.reset_token, +5741 }; +5742 builder.write_frame(frame, stats); +5743 } +5744 +5745 // RETIRE_CONNECTION_ID +5746 let retire_cid_bound = frame::RetireConnectionId::size_bound(is_multipath_negotiated); +5747 while !path_exclusive_only && builder.frame_space_remaining() > retire_cid_bound { +5748 let (path_id, sequence) = match space.pending.retire_cids.pop() { +5749 Some((PathId::ZERO, seq)) if !is_multipath_negotiated => (None, seq), +5750 Some((path_id, seq)) => (Some(path_id), seq), +5751 None => break, +5752 }; +5753 let frame = frame::RetireConnectionId { path_id, sequence }; +5754 builder.write_frame(frame, stats); +5755 } +5756 +5757 // DATAGRAM +5758 let mut sent_datagrams = false; +5759 while !path_exclusive_only +5760 && builder.frame_space_remaining() > Datagram::SIZE_BOUND +5761 && space_id == SpaceId::Data +5762 { +5763 match self.datagrams.write(builder, stats) { +5764 true => { +5765 sent_datagrams = true; +5766 } +5767 false => break, +5768 } +5769 } +5770 if self.datagrams.send_blocked && sent_datagrams { +5771 self.events.push_back(Event::DatagramsUnblocked); +5772 self.datagrams.send_blocked = false; +5773 } +5774 +5775 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; +5776 +5777 // NEW_TOKEN +5778 if !path_exclusive_only { +5779 while let Some(network_path) = space.pending.new_tokens.pop() { +5780 debug_assert_eq!(space_id, SpaceId::Data); +5781 let ConnectionSide::Server { server_config } = &self.side else { +5782 panic!("NEW_TOKEN frames should not be enqueued by clients"); +5783 }; +5784 +5785 if !network_path.is_probably_same_path(&path.network_path) { +5786 // NEW_TOKEN frames contain tokens bound to a client's IP address, and are only +5787 // useful if used from the same IP address. Thus, we abandon enqueued NEW_TOKEN +5788 // frames upon an path change. Instead, when the new path becomes validated, +5789 // NEW_TOKEN frames may be enqueued for the new path instead. +5790 continue; +5791 } +5792 +5793 let token = Token::new( +5794 TokenPayload::Validation { +5795 ip: network_path.remote.ip(), +5796 issued: server_config.time_source.now(), +5797 }, +5798 &mut self.rng, +5799 ); +5800 let new_token = NewToken { +5801 token: token.encode(&*server_config.token_key).into(), +5802 }; +5803 +5804 if builder.frame_space_remaining() < new_token.size() { +5805 space.pending.new_tokens.push(network_path); +5806 break; +5807 } +5808 +5809 builder.write_frame(new_token, stats); +5810 builder.retransmits_mut().new_tokens.push(network_path); +5811 } +5812 } +5813 +5814 // STREAM +5815 if !path_exclusive_only && space_id == SpaceId::Data { +5816 self.streams +5817 .write_stream_frames(builder, self.config.send_fairness, stats); +5818 } +5819 +5820 // ADD_ADDRESS +5821 while space_id == SpaceId::Data +5822 && !path_exclusive_only +5823 && frame::AddAddress::SIZE_BOUND <= builder.frame_space_remaining() +5824 { +5825 if let Some(added_address) = space.pending.add_address.pop_last() { +5826 builder.write_frame(added_address, stats); +5827 } else { +5828 break; +5829 } +5830 } +5831 +5832 // REMOVE_ADDRESS +5833 while space_id == SpaceId::Data +5834 && !path_exclusive_only +5835 && frame::RemoveAddress::SIZE_BOUND <= builder.frame_space_remaining() +5836 { +5837 if let Some(removed_address) = space.pending.remove_address.pop_last() { +5838 builder.write_frame(removed_address, stats); +5839 } else { +5840 break; +5841 } +5842 } +5843 } +5844 +5845 /// Write pending ACKs into a buffer +5846 fn populate_acks<'a, 'b>( +5847 now: Instant, +5848 receiving_ecn: bool, +5849 path_id: PathId, +5850 space_id: SpaceId, +5851 space: &mut PacketSpace, +5852 is_multipath_negotiated: bool, +5853 builder: &mut PacketBuilder<'a, 'b>, +5854 stats: &mut FrameStats, +5855 ) { +5856 // 0-RTT packets must never carry acks (which would have to be of handshake packets) +5857 debug_assert!(space.crypto.is_some(), "tried to send ACK in 0-RTT"); +5858 +5859 debug_assert!( +5860 is_multipath_negotiated || path_id == PathId::ZERO, +5861 "Only PathId::ZERO allowed without multipath (have {path_id:?})" +5862 ); +5863 if is_multipath_negotiated { +5864 debug_assert!( +5865 space_id == SpaceId::Data || path_id == PathId::ZERO, +5866 "path acks must be sent in 1RTT space (have {space_id:?})" +5867 ); +5868 } 5869 -5870 let delay_micros = pns.pending_acks.ack_delay(now).as_micros() as u64; -5871 // TODO: This should come from `TransportConfig` if that gets configurable. -5872 let ack_delay_exp = TransportParameters::default().ack_delay_exponent; -5873 let delay = delay_micros >> ack_delay_exp.into_inner(); -5874 -5875 if is_multipath_negotiated && space_id == SpaceId::Data { -5876 if !ranges.is_empty() { -5877 let frame = frame::PathAck::encoder(path_id, delay, ranges, ecn); -5878 builder.write_frame(frame, stats); -5879 } -5880 } else { -5881 builder.write_frame(frame::Ack::encoder(delay, ranges, ecn), stats); -5882 } -5883 } -5884 -5885 fn close_common(&mut self) { -5886 trace!("connection closed"); -5887 self.timers.reset(); -5888 } -5889 -5890 fn set_close_timer(&mut self, now: Instant) { -5891 // QUIC-MULTIPATH § 2.6 Connection Closure: draining for 3*PTO with PTO the max of -5892 // the PTO for all paths. -5893 let pto_max = self.pto_max_path(self.highest_space, true); -5894 self.timers.set( -5895 Timer::Conn(ConnTimer::Close), -5896 now + 3 * pto_max, -5897 self.qlog.with_time(now), -5898 ); -5899 } -5900 -5901 /// Handle transport parameters received from the peer -5902 /// -5903 /// *remote_cid* and *local_cid* are the source and destination CIDs respectively of the -5904 /// *packet into which the transport parameters arrived. -5905 fn handle_peer_params( -5906 &mut self, -5907 params: TransportParameters, -5908 local_cid: ConnectionId, -5909 remote_cid: ConnectionId, -5910 now: Instant, -5911 ) -> Result<(), TransportError> { -5912 if Some(self.original_remote_cid) != params.initial_src_cid -5913 || (self.side.is_client() -5914 && (Some(self.initial_dst_cid) != params.original_dst_cid -5915 || self.retry_src_cid != params.retry_src_cid)) -5916 { -5917 return Err(TransportError::TRANSPORT_PARAMETER_ERROR( -5918 "CID authentication failure", -5919 )); -5920 } -5921 if params.initial_max_path_id.is_some() && (local_cid.is_empty() || remote_cid.is_empty()) { -5922 return Err(TransportError::PROTOCOL_VIOLATION( -5923 "multipath must not use zero-length CIDs", -5924 )); -5925 } -5926 -5927 self.set_peer_params(params); -5928 self.qlog.emit_peer_transport_params_received(self, now); -5929 -5930 Ok(()) -5931 } -5932 -5933 fn set_peer_params(&mut self, params: TransportParameters) { -5934 self.streams.set_params(&params); -5935 self.idle_timeout = -5936 negotiate_max_idle_timeout(self.config.max_idle_timeout, Some(params.max_idle_timeout)); -5937 trace!("negotiated max idle timeout {:?}", self.idle_timeout); +5870 let pns = space.for_path(path_id); +5871 let ranges = pns.pending_acks.ranges(); +5872 debug_assert!(!ranges.is_empty(), "can not send empty ACK range"); +5873 let ecn = if receiving_ecn { +5874 Some(&pns.ecn_counters) +5875 } else { +5876 None +5877 }; +5878 +5879 let delay_micros = pns.pending_acks.ack_delay(now).as_micros() as u64; +5880 // TODO: This should come from `TransportConfig` if that gets configurable. +5881 let ack_delay_exp = TransportParameters::default().ack_delay_exponent; +5882 let delay = delay_micros >> ack_delay_exp.into_inner(); +5883 +5884 if is_multipath_negotiated && space_id == SpaceId::Data { +5885 if !ranges.is_empty() { +5886 let frame = frame::PathAck::encoder(path_id, delay, ranges, ecn); +5887 builder.write_frame(frame, stats); +5888 } +5889 } else { +5890 builder.write_frame(frame::Ack::encoder(delay, ranges, ecn), stats); +5891 } +5892 } +5893 +5894 fn close_common(&mut self) { +5895 trace!("connection closed"); +5896 self.timers.reset(); +5897 } +5898 +5899 fn set_close_timer(&mut self, now: Instant) { +5900 // QUIC-MULTIPATH § 2.6 Connection Closure: draining for 3*PTO with PTO the max of +5901 // the PTO for all paths. +5902 let pto_max = self.pto_max_path(self.highest_space, true); +5903 self.timers.set( +5904 Timer::Conn(ConnTimer::Close), +5905 now + 3 * pto_max, +5906 self.qlog.with_time(now), +5907 ); +5908 } +5909 +5910 /// Handle transport parameters received from the peer +5911 /// +5912 /// *remote_cid* and *local_cid* are the source and destination CIDs respectively of the +5913 /// *packet into which the transport parameters arrived. +5914 fn handle_peer_params( +5915 &mut self, +5916 params: TransportParameters, +5917 local_cid: ConnectionId, +5918 remote_cid: ConnectionId, +5919 now: Instant, +5920 ) -> Result<(), TransportError> { +5921 if Some(self.original_remote_cid) != params.initial_src_cid +5922 || (self.side.is_client() +5923 && (Some(self.initial_dst_cid) != params.original_dst_cid +5924 || self.retry_src_cid != params.retry_src_cid)) +5925 { +5926 return Err(TransportError::TRANSPORT_PARAMETER_ERROR( +5927 "CID authentication failure", +5928 )); +5929 } +5930 if params.initial_max_path_id.is_some() && (local_cid.is_empty() || remote_cid.is_empty()) { +5931 return Err(TransportError::PROTOCOL_VIOLATION( +5932 "multipath must not use zero-length CIDs", +5933 )); +5934 } +5935 +5936 self.set_peer_params(params); +5937 self.qlog.emit_peer_transport_params_received(self, now); 5938 -5939 if let Some(ref info) = params.preferred_address { -5940 // During the handshake PathId::ZERO exists. -5941 self.remote_cids.get_mut(&PathId::ZERO).expect("not yet abandoned").insert(frame::NewConnectionId { -5942 path_id: None, -5943 sequence: 1, -5944 id: info.connection_id, -5945 reset_token: info.stateless_reset_token, -5946 retire_prior_to: 0, -5947 }) -5948 .expect( -5949 "preferred address CID is the first received, and hence is guaranteed to be legal", -5950 ); -5951 let remote = self.path_data(PathId::ZERO).network_path.remote; -5952 self.set_reset_token(PathId::ZERO, remote, info.stateless_reset_token); -5953 } -5954 self.ack_frequency.peer_max_ack_delay = get_max_ack_delay(&params); -5955 -5956 let mut multipath_enabled = None; -5957 if let (Some(local_max_path_id), Some(remote_max_path_id)) = ( -5958 self.config.get_initial_max_path_id(), -5959 params.initial_max_path_id, -5960 ) { -5961 // multipath is enabled, register the local and remote maximums -5962 self.local_max_path_id = local_max_path_id; -5963 self.remote_max_path_id = remote_max_path_id; -5964 let initial_max_path_id = local_max_path_id.min(remote_max_path_id); -5965 debug!(%initial_max_path_id, "multipath negotiated"); -5966 multipath_enabled = Some(initial_max_path_id); -5967 } -5968 -5969 if let Some((max_locally_allowed_remote_addresses, max_remotely_allowed_remote_addresses)) = -5970 self.config -5971 .max_remote_nat_traversal_addresses -5972 .zip(params.max_remote_nat_traversal_addresses) -5973 { -5974 if let Some(max_initial_paths) = -5975 multipath_enabled.map(|path_id| path_id.saturating_add(1u8)) -5976 { -5977 let max_local_addresses = max_remotely_allowed_remote_addresses.get(); -5978 let max_remote_addresses = max_locally_allowed_remote_addresses.get(); -5979 self.iroh_hp = -5980 iroh_hp::State::new(max_remote_addresses, max_local_addresses, self.side()); -5981 debug!( -5982 %max_remote_addresses, %max_local_addresses, -5983 "iroh hole punching negotiated" -5984 ); -5985 -5986 match self.side() { -5987 Side::Client => { -5988 if max_initial_paths.as_u32() < max_remote_addresses as u32 + 1 { -5989 // in this case the client might try to open `max_remote_addresses` new -5990 // paths, but the current multipath configuration will not allow it -5991 warn!(%max_initial_paths, %max_remote_addresses, "local client configuration might cause nat traversal issues") -5992 } else if max_local_addresses as u64 -5993 > params.active_connection_id_limit.into_inner() -5994 { -5995 // the server allows us to send at most `params.active_connection_id_limit` -5996 // but they might need at least `max_local_addresses` to effectively send -5997 // `PATH_CHALLENGE` frames to each advertised local address -5998 warn!(%max_local_addresses, remote_cid_limit=%params.active_connection_id_limit.into_inner(), "remote server configuration might cause nat traversal issues") -5999 } -6000 } -6001 Side::Server => { -6002 if (max_initial_paths.as_u32() as u64) < crate::LOCAL_CID_COUNT { -6003 warn!(%max_initial_paths, local_cid_limit=%crate::LOCAL_CID_COUNT, "local server configuration might cause nat traversal issues") -6004 } -6005 } -6006 } -6007 } else { -6008 debug!("iroh nat traversal enabled for both endpoints, but multipath is missing") -6009 } -6010 } -6011 -6012 self.peer_params = params; -6013 let peer_max_udp_payload_size = -6014 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()).unwrap_or(u16::MAX); -6015 self.path_data_mut(PathId::ZERO) -6016 .mtud -6017 .on_peer_max_udp_payload_size_received(peer_max_udp_payload_size); -6018 } -6019 -6020 /// Decrypts a packet, returning the packet number on success -6021 fn decrypt_packet( -6022 &mut self, -6023 now: Instant, -6024 path_id: PathId, -6025 packet: &mut Packet, -6026 ) -> Result<Option<u64>, Option<TransportError>> { -6027 let result = packet_crypto::decrypt_packet_body( -6028 packet, -6029 path_id, -6030 &self.spaces, -6031 self.zero_rtt_crypto.as_ref(), -6032 self.key_phase, -6033 self.prev_crypto.as_ref(), -6034 self.next_crypto.as_ref(), -6035 )?; -6036 -6037 let result = match result { -6038 Some(r) => r, -6039 None => return Ok(None), -6040 }; -6041 -6042 if result.outgoing_key_update_acked -6043 && let Some(prev) = self.prev_crypto.as_mut() -6044 { -6045 prev.end_packet = Some((result.number, now)); -6046 self.set_key_discard_timer(now, packet.header.space()); -6047 } -6048 -6049 if result.incoming_key_update { -6050 trace!("key update authenticated"); -6051 self.update_keys(Some((result.number, now)), true); -6052 self.set_key_discard_timer(now, packet.header.space()); -6053 } -6054 -6055 Ok(Some(result.number)) -6056 } +5939 Ok(()) +5940 } +5941 +5942 fn set_peer_params(&mut self, params: TransportParameters) { +5943 self.streams.set_params(&params); +5944 self.idle_timeout = +5945 negotiate_max_idle_timeout(self.config.max_idle_timeout, Some(params.max_idle_timeout)); +5946 trace!("negotiated max idle timeout {:?}", self.idle_timeout); +5947 +5948 if let Some(ref info) = params.preferred_address { +5949 // During the handshake PathId::ZERO exists. +5950 self.remote_cids.get_mut(&PathId::ZERO).expect("not yet abandoned").insert(frame::NewConnectionId { +5951 path_id: None, +5952 sequence: 1, +5953 id: info.connection_id, +5954 reset_token: info.stateless_reset_token, +5955 retire_prior_to: 0, +5956 }) +5957 .expect( +5958 "preferred address CID is the first received, and hence is guaranteed to be legal", +5959 ); +5960 let remote = self.path_data(PathId::ZERO).network_path.remote; +5961 self.set_reset_token(PathId::ZERO, remote, info.stateless_reset_token); +5962 } +5963 self.ack_frequency.peer_max_ack_delay = get_max_ack_delay(&params); +5964 +5965 let mut multipath_enabled = None; +5966 if let (Some(local_max_path_id), Some(remote_max_path_id)) = ( +5967 self.config.get_initial_max_path_id(), +5968 params.initial_max_path_id, +5969 ) { +5970 // multipath is enabled, register the local and remote maximums +5971 self.local_max_path_id = local_max_path_id; +5972 self.remote_max_path_id = remote_max_path_id; +5973 let initial_max_path_id = local_max_path_id.min(remote_max_path_id); +5974 debug!(%initial_max_path_id, "multipath negotiated"); +5975 multipath_enabled = Some(initial_max_path_id); +5976 } +5977 +5978 if let Some((max_locally_allowed_remote_addresses, max_remotely_allowed_remote_addresses)) = +5979 self.config +5980 .max_remote_nat_traversal_addresses +5981 .zip(params.max_remote_nat_traversal_addresses) +5982 { +5983 if let Some(max_initial_paths) = +5984 multipath_enabled.map(|path_id| path_id.saturating_add(1u8)) +5985 { +5986 let max_local_addresses = max_remotely_allowed_remote_addresses.get(); +5987 let max_remote_addresses = max_locally_allowed_remote_addresses.get(); +5988 self.iroh_hp = +5989 iroh_hp::State::new(max_remote_addresses, max_local_addresses, self.side()); +5990 debug!( +5991 %max_remote_addresses, %max_local_addresses, +5992 "iroh hole punching negotiated" +5993 ); +5994 +5995 match self.side() { +5996 Side::Client => { +5997 if max_initial_paths.as_u32() < max_remote_addresses as u32 + 1 { +5998 // in this case the client might try to open `max_remote_addresses` new +5999 // paths, but the current multipath configuration will not allow it +6000 warn!(%max_initial_paths, %max_remote_addresses, "local client configuration might cause nat traversal issues") +6001 } else if max_local_addresses as u64 +6002 > params.active_connection_id_limit.into_inner() +6003 { +6004 // the server allows us to send at most `params.active_connection_id_limit` +6005 // but they might need at least `max_local_addresses` to effectively send +6006 // `PATH_CHALLENGE` frames to each advertised local address +6007 warn!(%max_local_addresses, remote_cid_limit=%params.active_connection_id_limit.into_inner(), "remote server configuration might cause nat traversal issues") +6008 } +6009 } +6010 Side::Server => { +6011 if (max_initial_paths.as_u32() as u64) < crate::LOCAL_CID_COUNT { +6012 warn!(%max_initial_paths, local_cid_limit=%crate::LOCAL_CID_COUNT, "local server configuration might cause nat traversal issues") +6013 } +6014 } +6015 } +6016 } else { +6017 debug!("iroh nat traversal enabled for both endpoints, but multipath is missing") +6018 } +6019 } +6020 +6021 self.peer_params = params; +6022 let peer_max_udp_payload_size = +6023 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()).unwrap_or(u16::MAX); +6024 self.path_data_mut(PathId::ZERO) +6025 .mtud +6026 .on_peer_max_udp_payload_size_received(peer_max_udp_payload_size); +6027 } +6028 +6029 /// Decrypts a packet, returning the packet number on success +6030 fn decrypt_packet( +6031 &mut self, +6032 now: Instant, +6033 path_id: PathId, +6034 packet: &mut Packet, +6035 ) -> Result<Option<u64>, Option<TransportError>> { +6036 let result = packet_crypto::decrypt_packet_body( +6037 packet, +6038 path_id, +6039 &self.spaces, +6040 self.zero_rtt_crypto.as_ref(), +6041 self.key_phase, +6042 self.prev_crypto.as_ref(), +6043 self.next_crypto.as_ref(), +6044 )?; +6045 +6046 let result = match result { +6047 Some(r) => r, +6048 None => return Ok(None), +6049 }; +6050 +6051 if result.outgoing_key_update_acked +6052 && let Some(prev) = self.prev_crypto.as_mut() +6053 { +6054 prev.end_packet = Some((result.number, now)); +6055 self.set_key_discard_timer(now, packet.header.space()); +6056 } 6057 -6058 fn update_keys(&mut self, end_packet: Option<(u64, Instant)>, remote: bool) { -6059 trace!("executing key update"); -6060 // Generate keys for the key phase after the one we're switching to, store them in -6061 // `next_crypto`, make the contents of `next_crypto` current, and move the current keys into -6062 // `prev_crypto`. -6063 let new = self -6064 .crypto -6065 .next_1rtt_keys() -6066 .expect("only called for `Data` packets"); -6067 self.key_phase_size = new -6068 .local -6069 .confidentiality_limit() -6070 .saturating_sub(KEY_UPDATE_MARGIN); -6071 let old = mem::replace( -6072 &mut self.spaces[SpaceId::Data] -6073 .crypto -6074 .as_mut() -6075 .unwrap() // safe because update_keys() can only be triggered by short packets -6076 .packet, -6077 mem::replace(self.next_crypto.as_mut().unwrap(), new), -6078 ); -6079 self.spaces[SpaceId::Data] -6080 .iter_paths_mut() -6081 .for_each(|s| s.sent_with_keys = 0); -6082 self.prev_crypto = Some(PrevCrypto { -6083 crypto: old, -6084 end_packet, -6085 update_unacked: remote, -6086 }); -6087 self.key_phase = !self.key_phase; -6088 } -6089 -6090 fn peer_supports_ack_frequency(&self) -> bool { -6091 self.peer_params.min_ack_delay.is_some() -6092 } -6093 -6094 /// Send an IMMEDIATE_ACK frame to the remote endpoint -6095 /// -6096 /// According to the spec, this will result in an error if the remote endpoint does not support -6097 /// the Acknowledgement Frequency extension -6098 pub(crate) fn immediate_ack(&mut self, path_id: PathId) { -6099 debug_assert_eq!( -6100 self.highest_space, -6101 SpaceId::Data, -6102 "immediate ack must be written in the data space" -6103 ); -6104 self.spaces[self.highest_space] -6105 .for_path(path_id) -6106 .immediate_ack_pending = true; -6107 } -6108 -6109 /// Decodes a packet, returning its decrypted payload, so it can be inspected in tests -6110 #[cfg(test)] -6111 pub(crate) fn decode_packet(&self, event: &ConnectionEvent) -> Option<Vec<u8>> { -6112 let (path_id, first_decode, remaining) = match &event.0 { -6113 ConnectionEventInner::Datagram(DatagramConnectionEvent { -6114 path_id, -6115 first_decode, -6116 remaining, -6117 .. -6118 }) => (path_id, first_decode, remaining), -6119 _ => return None, -6120 }; -6121 -6122 if remaining.is_some() { -6123 panic!("Packets should never be coalesced in tests"); -6124 } -6125 -6126 let decrypted_header = packet_crypto::unprotect_header( -6127 first_decode.clone(), -6128 &self.spaces, -6129 self.zero_rtt_crypto.as_ref(), -6130 self.peer_params.stateless_reset_token, -6131 )?; -6132 -6133 let mut packet = decrypted_header.packet?; -6134 packet_crypto::decrypt_packet_body( -6135 &mut packet, -6136 *path_id, +6058 if result.incoming_key_update { +6059 trace!("key update authenticated"); +6060 self.update_keys(Some((result.number, now)), true); +6061 self.set_key_discard_timer(now, packet.header.space()); +6062 } +6063 +6064 Ok(Some(result.number)) +6065 } +6066 +6067 fn update_keys(&mut self, end_packet: Option<(u64, Instant)>, remote: bool) { +6068 trace!("executing key update"); +6069 // Generate keys for the key phase after the one we're switching to, store them in +6070 // `next_crypto`, make the contents of `next_crypto` current, and move the current keys into +6071 // `prev_crypto`. +6072 let new = self +6073 .crypto +6074 .next_1rtt_keys() +6075 .expect("only called for `Data` packets"); +6076 self.key_phase_size = new +6077 .local +6078 .confidentiality_limit() +6079 .saturating_sub(KEY_UPDATE_MARGIN); +6080 let old = mem::replace( +6081 &mut self.spaces[SpaceId::Data] +6082 .crypto +6083 .as_mut() +6084 .unwrap() // safe because update_keys() can only be triggered by short packets +6085 .packet, +6086 mem::replace(self.next_crypto.as_mut().unwrap(), new), +6087 ); +6088 self.spaces[SpaceId::Data] +6089 .iter_paths_mut() +6090 .for_each(|s| s.sent_with_keys = 0); +6091 self.prev_crypto = Some(PrevCrypto { +6092 crypto: old, +6093 end_packet, +6094 update_unacked: remote, +6095 }); +6096 self.key_phase = !self.key_phase; +6097 } +6098 +6099 fn peer_supports_ack_frequency(&self) -> bool { +6100 self.peer_params.min_ack_delay.is_some() +6101 } +6102 +6103 /// Send an IMMEDIATE_ACK frame to the remote endpoint +6104 /// +6105 /// According to the spec, this will result in an error if the remote endpoint does not support +6106 /// the Acknowledgement Frequency extension +6107 pub(crate) fn immediate_ack(&mut self, path_id: PathId) { +6108 debug_assert_eq!( +6109 self.highest_space, +6110 SpaceId::Data, +6111 "immediate ack must be written in the data space" +6112 ); +6113 self.spaces[self.highest_space] +6114 .for_path(path_id) +6115 .immediate_ack_pending = true; +6116 } +6117 +6118 /// Decodes a packet, returning its decrypted payload, so it can be inspected in tests +6119 #[cfg(test)] +6120 pub(crate) fn decode_packet(&self, event: &ConnectionEvent) -> Option<Vec<u8>> { +6121 let (path_id, first_decode, remaining) = match &event.0 { +6122 ConnectionEventInner::Datagram(DatagramConnectionEvent { +6123 path_id, +6124 first_decode, +6125 remaining, +6126 .. +6127 }) => (path_id, first_decode, remaining), +6128 _ => return None, +6129 }; +6130 +6131 if remaining.is_some() { +6132 panic!("Packets should never be coalesced in tests"); +6133 } +6134 +6135 let decrypted_header = packet_crypto::unprotect_header( +6136 first_decode.clone(), 6137 &self.spaces, 6138 self.zero_rtt_crypto.as_ref(), -6139 self.key_phase, -6140 self.prev_crypto.as_ref(), -6141 self.next_crypto.as_ref(), -6142 ) -6143 .ok()?; -6144 -6145 Some(packet.payload.to_vec()) -6146 } -6147 -6148 /// The number of bytes of packets containing retransmittable frames that have not been -6149 /// acknowledged or declared lost. -6150 #[cfg(test)] -6151 pub(crate) fn bytes_in_flight(&self) -> u64 { -6152 // TODO(@divma): consider including for multipath? -6153 self.path_data(PathId::ZERO).in_flight.bytes -6154 } -6155 -6156 /// Number of bytes worth of non-ack-only packets that may be sent -6157 #[cfg(test)] -6158 pub(crate) fn congestion_window(&self) -> u64 { -6159 let path = self.path_data(PathId::ZERO); -6160 path.congestion -6161 .window() -6162 .saturating_sub(path.in_flight.bytes) +6139 self.peer_params.stateless_reset_token, +6140 )?; +6141 +6142 let mut packet = decrypted_header.packet?; +6143 packet_crypto::decrypt_packet_body( +6144 &mut packet, +6145 *path_id, +6146 &self.spaces, +6147 self.zero_rtt_crypto.as_ref(), +6148 self.key_phase, +6149 self.prev_crypto.as_ref(), +6150 self.next_crypto.as_ref(), +6151 ) +6152 .ok()?; +6153 +6154 Some(packet.payload.to_vec()) +6155 } +6156 +6157 /// The number of bytes of packets containing retransmittable frames that have not been +6158 /// acknowledged or declared lost. +6159 #[cfg(test)] +6160 pub(crate) fn bytes_in_flight(&self) -> u64 { +6161 // TODO(@divma): consider including for multipath? +6162 self.path_data(PathId::ZERO).in_flight.bytes 6163 } 6164 -6165 /// Whether no timers but keepalive, idle, rtt, pushnewcid, and key discard are running +6165 /// Number of bytes worth of non-ack-only packets that may be sent 6166 #[cfg(test)] -6167 pub(crate) fn is_idle(&self) -> bool { -6168 let current_timers = self.timers.values(); -6169 current_timers -6170 .into_iter() -6171 .filter(|(timer, _)| { -6172 !matches!( -6173 timer, -6174 Timer::Conn(ConnTimer::KeepAlive) -6175 | Timer::PerPath(_, PathTimer::PathKeepAlive) -6176 | Timer::Conn(ConnTimer::PushNewCid) -6177 | Timer::Conn(ConnTimer::KeyDiscard) -6178 ) -6179 }) -6180 .min_by_key(|(_, time)| *time) -6181 .is_none_or(|(timer, _)| timer == Timer::Conn(ConnTimer::Idle)) -6182 } -6183 -6184 /// Whether explicit congestion notification is in use on outgoing packets. -6185 #[cfg(test)] -6186 pub(crate) fn using_ecn(&self) -> bool { -6187 self.path_data(PathId::ZERO).sending_ecn -6188 } -6189 -6190 /// The number of received bytes in the current path -6191 #[cfg(test)] -6192 pub(crate) fn total_recvd(&self) -> u64 { -6193 self.path_data(PathId::ZERO).total_recvd -6194 } -6195 -6196 #[cfg(test)] -6197 pub(crate) fn active_local_cid_seq(&self) -> (u64, u64) { -6198 self.local_cid_state -6199 .get(&PathId::ZERO) -6200 .unwrap() -6201 .active_seq() -6202 } -6203 -6204 #[cfg(test)] -6205 #[track_caller] -6206 pub(crate) fn active_local_path_cid_seq(&self, path_id: u32) -> (u64, u64) { +6167 pub(crate) fn congestion_window(&self) -> u64 { +6168 let path = self.path_data(PathId::ZERO); +6169 path.congestion +6170 .window() +6171 .saturating_sub(path.in_flight.bytes) +6172 } +6173 +6174 /// Whether no timers but keepalive, idle, rtt, pushnewcid, and key discard are running +6175 #[cfg(test)] +6176 pub(crate) fn is_idle(&self) -> bool { +6177 let current_timers = self.timers.values(); +6178 current_timers +6179 .into_iter() +6180 .filter(|(timer, _)| { +6181 !matches!( +6182 timer, +6183 Timer::Conn(ConnTimer::KeepAlive) +6184 | Timer::PerPath(_, PathTimer::PathKeepAlive) +6185 | Timer::Conn(ConnTimer::PushNewCid) +6186 | Timer::Conn(ConnTimer::KeyDiscard) +6187 ) +6188 }) +6189 .min_by_key(|(_, time)| *time) +6190 .is_none_or(|(timer, _)| timer == Timer::Conn(ConnTimer::Idle)) +6191 } +6192 +6193 /// Whether explicit congestion notification is in use on outgoing packets. +6194 #[cfg(test)] +6195 pub(crate) fn using_ecn(&self) -> bool { +6196 self.path_data(PathId::ZERO).sending_ecn +6197 } +6198 +6199 /// The number of received bytes in the current path +6200 #[cfg(test)] +6201 pub(crate) fn total_recvd(&self) -> u64 { +6202 self.path_data(PathId::ZERO).total_recvd +6203 } +6204 +6205 #[cfg(test)] +6206 pub(crate) fn active_local_cid_seq(&self) -> (u64, u64) { 6207 self.local_cid_state -6208 .get(&PathId(path_id)) +6208 .get(&PathId::ZERO) 6209 .unwrap() 6210 .active_seq() 6211 } 6212 -6213 /// Instruct the peer to replace previously issued CIDs by sending a NEW_CONNECTION_ID frame -6214 /// with updated `retire_prior_to` field set to `v` -6215 #[cfg(test)] -6216 pub(crate) fn rotate_local_cid(&mut self, v: u64, now: Instant) { -6217 let n = self -6218 .local_cid_state -6219 .get_mut(&PathId::ZERO) -6220 .unwrap() -6221 .assign_retire_seq(v); -6222 self.endpoint_events -6223 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); -6224 } -6225 -6226 /// Check the current active remote CID sequence for `PathId::ZERO` -6227 #[cfg(test)] -6228 pub(crate) fn active_remote_cid_seq(&self) -> u64 { -6229 self.remote_cids.get(&PathId::ZERO).unwrap().active_seq() -6230 } -6231 -6232 /// Returns the detected maximum udp payload size for the current path -6233 #[cfg(test)] -6234 pub(crate) fn path_mtu(&self, path_id: PathId) -> u16 { -6235 self.path_data(path_id).current_mtu() -6236 } -6237 -6238 /// Triggers path validation on all paths -6239 #[cfg(test)] -6240 pub(crate) fn trigger_path_validation(&mut self) { -6241 for path in self.paths.values_mut() { -6242 path.data.send_new_challenge = true; -6243 } -6244 } -6245 -6246 /// Whether we have 1-RTT data to send -6247 /// -6248 /// This checks for frames that can only be sent in the data space (1-RTT): -6249 /// - Pending PATH_CHALLENGE frames on the active and previous path if just migrated. -6250 /// - Pending PATH_RESPONSE frames. -6251 /// - Pending data to send in STREAM frames. -6252 /// - Pending DATAGRAM frames to send. -6253 /// -6254 /// See also [`PacketSpace::can_send`] which keeps track of all other frame types that -6255 /// may need to be sent. -6256 fn can_send_1rtt(&self, path_id: PathId, max_size: usize) -> SendableFrames { -6257 let path_exclusive = self.paths.get(&path_id).is_some_and(|path| { -6258 path.data.send_new_challenge -6259 || path -6260 .prev -6261 .as_ref() -6262 .is_some_and(|(_, path)| path.send_new_challenge) -6263 || !path.data.path_responses.is_empty() -6264 }); -6265 let other = self.streams.can_send_stream_data() -6266 || self -6267 .datagrams -6268 .outgoing -6269 .front() -6270 .is_some_and(|x| x.size(true) <= max_size); -6271 SendableFrames { -6272 acks: false, -6273 other, -6274 close: false, -6275 path_exclusive, -6276 } -6277 } -6278 -6279 /// Terminate the connection instantly, without sending a close packet -6280 fn kill(&mut self, reason: ConnectionError) { -6281 self.close_common(); -6282 self.state.move_to_drained(Some(reason)); -6283 self.endpoint_events.push_back(EndpointEventInner::Drained); -6284 } -6285 -6286 /// Storage size required for the largest packet that can be transmitted on all currently -6287 /// available paths -6288 /// -6289 /// Buffers passed to [`Connection::poll_transmit`] should be at least this large. -6290 /// -6291 /// When multipath is enabled, this value is the minimum MTU across all available paths. -6292 pub fn current_mtu(&self) -> u16 { -6293 self.paths -6294 .iter() -6295 .filter(|&(path_id, _path_state)| !self.abandoned_paths.contains(path_id)) -6296 .map(|(_path_id, path_state)| path_state.data.current_mtu()) -6297 .min() -6298 .expect("There is always at least one available path") -6299 } -6300 -6301 /// Size of non-frame data for a 1-RTT packet -6302 /// -6303 /// Quantifies space consumed by the QUIC header and AEAD tag. All other bytes in a packet are -6304 /// frames. Changes if the length of the remote connection ID changes, which is expected to be -6305 /// rare. If `pn` is specified, may additionally change unpredictably due to variations in -6306 /// latency and packet loss. -6307 fn predict_1rtt_overhead(&mut self, pn: u64, path: PathId) -> usize { -6308 let pn_len = PacketNumber::new( -6309 pn, -6310 self.spaces[SpaceId::Data] -6311 .for_path(path) -6312 .largest_acked_packet -6313 .unwrap_or(0), -6314 ) -6315 .len(); -6316 -6317 // 1 byte for flags -6318 1 + self -6319 .remote_cids -6320 .get(&path) -6321 .map(|cids| cids.active().len()) -6322 .unwrap_or(20) // Max CID len in QUIC v1 -6323 + pn_len -6324 + self.tag_len_1rtt() -6325 } -6326 -6327 fn predict_1rtt_overhead_no_pn(&self) -> usize { -6328 let pn_len = 4; -6329 -6330 let cid_len = self -6331 .remote_cids -6332 .values() -6333 .map(|cids| cids.active().len()) -6334 .max() -6335 .unwrap_or(20); // Max CID len in QUIC v1 -6336 -6337 // 1 byte for flags -6338 1 + cid_len + pn_len + self.tag_len_1rtt() -6339 } -6340 -6341 fn tag_len_1rtt(&self) -> usize { -6342 let key = match self.spaces[SpaceId::Data].crypto.as_ref() { -6343 Some(crypto) => Some(&*crypto.packet.local), -6344 None => self.zero_rtt_crypto.as_ref().map(|x| &*x.packet), -6345 }; -6346 // If neither Data nor 0-RTT keys are available, make a reasonable tag length guess. As of -6347 // this writing, all QUIC cipher suites use 16-byte tags. We could return `None` instead, -6348 // but that would needlessly prevent sending datagrams during 0-RTT. -6349 key.map_or(16, |x| x.tag_len()) -6350 } -6351 -6352 /// Mark the path as validated, and enqueue NEW_TOKEN frames to be sent as appropriate -6353 fn on_path_validated(&mut self, path_id: PathId) { -6354 self.path_data_mut(path_id).validated = true; -6355 let ConnectionSide::Server { server_config } = &self.side else { -6356 return; -6357 }; -6358 let network_path = self.path_data(path_id).network_path; -6359 let new_tokens = &mut self.spaces[SpaceId::Data as usize].pending.new_tokens; -6360 new_tokens.clear(); -6361 for _ in 0..server_config.validation_token.sent { -6362 new_tokens.push(network_path); -6363 } -6364 } -6365 -6366 /// Handle new path status information: PATH_STATUS_AVAILABLE, PATH_STATUS_BACKUP -6367 fn on_path_status(&mut self, path_id: PathId, status: PathStatus, status_seq_no: VarInt) { -6368 if let Some(path) = self.paths.get_mut(&path_id) { -6369 path.data.status.remote_update(status, status_seq_no); -6370 } else { -6371 debug!("PATH_STATUS_AVAILABLE received unknown path {:?}", path_id); +6213 #[cfg(test)] +6214 #[track_caller] +6215 pub(crate) fn active_local_path_cid_seq(&self, path_id: u32) -> (u64, u64) { +6216 self.local_cid_state +6217 .get(&PathId(path_id)) +6218 .unwrap() +6219 .active_seq() +6220 } +6221 +6222 /// Instruct the peer to replace previously issued CIDs by sending a NEW_CONNECTION_ID frame +6223 /// with updated `retire_prior_to` field set to `v` +6224 #[cfg(test)] +6225 pub(crate) fn rotate_local_cid(&mut self, v: u64, now: Instant) { +6226 let n = self +6227 .local_cid_state +6228 .get_mut(&PathId::ZERO) +6229 .unwrap() +6230 .assign_retire_seq(v); +6231 self.endpoint_events +6232 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); +6233 } +6234 +6235 /// Check the current active remote CID sequence for `PathId::ZERO` +6236 #[cfg(test)] +6237 pub(crate) fn active_remote_cid_seq(&self) -> u64 { +6238 self.remote_cids.get(&PathId::ZERO).unwrap().active_seq() +6239 } +6240 +6241 /// Returns the detected maximum udp payload size for the current path +6242 #[cfg(test)] +6243 pub(crate) fn path_mtu(&self, path_id: PathId) -> u16 { +6244 self.path_data(path_id).current_mtu() +6245 } +6246 +6247 /// Triggers path validation on all paths +6248 #[cfg(test)] +6249 pub(crate) fn trigger_path_validation(&mut self) { +6250 for path in self.paths.values_mut() { +6251 path.data.send_new_challenge = true; +6252 } +6253 } +6254 +6255 /// Whether we have 1-RTT data to send +6256 /// +6257 /// This checks for frames that can only be sent in the data space (1-RTT): +6258 /// - Pending PATH_CHALLENGE frames on the active and previous path if just migrated. +6259 /// - Pending PATH_RESPONSE frames. +6260 /// - Pending data to send in STREAM frames. +6261 /// - Pending DATAGRAM frames to send. +6262 /// +6263 /// See also [`PacketSpace::can_send`] which keeps track of all other frame types that +6264 /// may need to be sent. +6265 fn can_send_1rtt(&self, path_id: PathId, max_size: usize) -> SendableFrames { +6266 let path_exclusive = self.paths.get(&path_id).is_some_and(|path| { +6267 path.data.send_new_challenge +6268 || path +6269 .prev +6270 .as_ref() +6271 .is_some_and(|(_, path)| path.send_new_challenge) +6272 || !path.data.path_responses.is_empty() +6273 }); +6274 let other = self.streams.can_send_stream_data() +6275 || self +6276 .datagrams +6277 .outgoing +6278 .front() +6279 .is_some_and(|x| x.size(true) <= max_size); +6280 SendableFrames { +6281 acks: false, +6282 other, +6283 close: false, +6284 path_exclusive, +6285 } +6286 } +6287 +6288 /// Terminate the connection instantly, without sending a close packet +6289 fn kill(&mut self, reason: ConnectionError) { +6290 self.close_common(); +6291 self.state.move_to_drained(Some(reason)); +6292 self.endpoint_events.push_back(EndpointEventInner::Drained); +6293 } +6294 +6295 /// Storage size required for the largest packet that can be transmitted on all currently +6296 /// available paths +6297 /// +6298 /// Buffers passed to [`Connection::poll_transmit`] should be at least this large. +6299 /// +6300 /// When multipath is enabled, this value is the minimum MTU across all available paths. +6301 pub fn current_mtu(&self) -> u16 { +6302 self.paths +6303 .iter() +6304 .filter(|&(path_id, _path_state)| !self.abandoned_paths.contains(path_id)) +6305 .map(|(_path_id, path_state)| path_state.data.current_mtu()) +6306 .min() +6307 .expect("There is always at least one available path") +6308 } +6309 +6310 /// Size of non-frame data for a 1-RTT packet +6311 /// +6312 /// Quantifies space consumed by the QUIC header and AEAD tag. All other bytes in a packet are +6313 /// frames. Changes if the length of the remote connection ID changes, which is expected to be +6314 /// rare. If `pn` is specified, may additionally change unpredictably due to variations in +6315 /// latency and packet loss. +6316 fn predict_1rtt_overhead(&mut self, pn: u64, path: PathId) -> usize { +6317 let pn_len = PacketNumber::new( +6318 pn, +6319 self.spaces[SpaceId::Data] +6320 .for_path(path) +6321 .largest_acked_packet +6322 .unwrap_or(0), +6323 ) +6324 .len(); +6325 +6326 // 1 byte for flags +6327 1 + self +6328 .remote_cids +6329 .get(&path) +6330 .map(|cids| cids.active().len()) +6331 .unwrap_or(20) // Max CID len in QUIC v1 +6332 + pn_len +6333 + self.tag_len_1rtt() +6334 } +6335 +6336 fn predict_1rtt_overhead_no_pn(&self) -> usize { +6337 let pn_len = 4; +6338 +6339 let cid_len = self +6340 .remote_cids +6341 .values() +6342 .map(|cids| cids.active().len()) +6343 .max() +6344 .unwrap_or(20); // Max CID len in QUIC v1 +6345 +6346 // 1 byte for flags +6347 1 + cid_len + pn_len + self.tag_len_1rtt() +6348 } +6349 +6350 fn tag_len_1rtt(&self) -> usize { +6351 let key = match self.spaces[SpaceId::Data].crypto.as_ref() { +6352 Some(crypto) => Some(&*crypto.packet.local), +6353 None => self.zero_rtt_crypto.as_ref().map(|x| &*x.packet), +6354 }; +6355 // If neither Data nor 0-RTT keys are available, make a reasonable tag length guess. As of +6356 // this writing, all QUIC cipher suites use 16-byte tags. We could return `None` instead, +6357 // but that would needlessly prevent sending datagrams during 0-RTT. +6358 key.map_or(16, |x| x.tag_len()) +6359 } +6360 +6361 /// Mark the path as validated, and enqueue NEW_TOKEN frames to be sent as appropriate +6362 fn on_path_validated(&mut self, path_id: PathId) { +6363 self.path_data_mut(path_id).validated = true; +6364 let ConnectionSide::Server { server_config } = &self.side else { +6365 return; +6366 }; +6367 let network_path = self.path_data(path_id).network_path; +6368 let new_tokens = &mut self.spaces[SpaceId::Data as usize].pending.new_tokens; +6369 new_tokens.clear(); +6370 for _ in 0..server_config.validation_token.sent { +6371 new_tokens.push(network_path); 6372 } -6373 self.events.push_back( -6374 PathEvent::RemoteStatus { -6375 id: path_id, -6376 status, -6377 } -6378 .into(), -6379 ); -6380 } -6381 -6382 /// Returns the maximum [`PathId`] to be used for sending in this connection. -6383 /// -6384 /// This is calculated as minimum between the local and remote's maximums when multipath is -6385 /// enabled, or `None` when disabled. -6386 /// -6387 /// For data that's received, we should use [`Self::local_max_path_id`] instead. -6388 /// The reasoning is that the remote might already have updated to its own newer -6389 /// [`Self::max_path_id`] after sending out a `MAX_PATH_ID` frame, but it got re-ordered. -6390 fn max_path_id(&self) -> Option<PathId> { -6391 if self.is_multipath_negotiated() { -6392 Some(self.remote_max_path_id.min(self.local_max_path_id)) -6393 } else { -6394 None -6395 } -6396 } -6397 -6398 /// Add addresses the local endpoint considers are reachable for nat traversal -6399 pub fn add_nat_traversal_address(&mut self, address: SocketAddr) -> Result<(), iroh_hp::Error> { -6400 if let Some(added) = self.iroh_hp.add_local_address(address)? { -6401 self.spaces[SpaceId::Data].pending.add_address.insert(added); -6402 }; -6403 Ok(()) -6404 } -6405 -6406 /// Removes an address the endpoing no longer considers reachable for nat traversal -6407 /// -6408 /// Addresses not present in the set will be silently ignored. -6409 pub fn remove_nat_traversal_address( -6410 &mut self, -6411 address: SocketAddr, -6412 ) -> Result<(), iroh_hp::Error> { -6413 if let Some(removed) = self.iroh_hp.remove_local_address(address)? { -6414 self.spaces[SpaceId::Data] -6415 .pending -6416 .remove_address -6417 .insert(removed); -6418 } -6419 Ok(()) -6420 } -6421 -6422 /// Get the current local nat traversal addresses -6423 pub fn get_local_nat_traversal_addresses(&self) -> Result<Vec<SocketAddr>, iroh_hp::Error> { -6424 self.iroh_hp.get_local_nat_traversal_addresses() -6425 } -6426 -6427 /// Get the currently advertised nat traversal addresses by the server -6428 pub fn get_remote_nat_traversal_addresses(&self) -> Result<Vec<SocketAddr>, iroh_hp::Error> { -6429 Ok(self -6430 .iroh_hp -6431 .client_side()? -6432 .get_remote_nat_traversal_addresses()) -6433 } -6434 -6435 /// Attempts to open a path for nat traversal. -6436 /// -6437 /// `ipv6` indicates if the path should be opened using an IPV6 remote. If the address is -6438 /// ignored, it will return `None`. -6439 /// -6440 /// On success returns the [`PathId`] and remote address of the path. -6441 fn open_nat_traversal_path( -6442 &mut self, -6443 now: Instant, -6444 (ip, port): (IpAddr, u16), -6445 ipv6: bool, -6446 ) -> Result<Option<(PathId, SocketAddr)>, PathError> { -6447 // If this endpoint is an IPv6 endpoint we use IPv6 addresses for all remotes. -6448 let remote = match ip { -6449 IpAddr::V4(addr) if ipv6 => SocketAddr::new(addr.to_ipv6_mapped().into(), port), -6450 IpAddr::V4(addr) => SocketAddr::new(addr.into(), port), -6451 IpAddr::V6(_) if ipv6 => SocketAddr::new(ip, port), -6452 IpAddr::V6(_) => { -6453 trace!("not using IPv6 nat candidate for IPv4 socket"); -6454 return Ok(None); -6455 } -6456 }; -6457 // TODO(matheus23): Probe the correct 4-tuple, instead of only a remote address? -6458 // By specifying None, we do two things: 1. open_path_ensure won't generate two -6459 // paths to the same remote and 2. we let the OS choose which interface to use for -6460 // sending on that path. -6461 let network_path = FourTuple { -6462 remote, -6463 local_ip: None, -6464 }; -6465 match self.open_path_ensure(network_path, PathStatus::Backup, now) { -6466 Ok((path_id, path_was_known)) => { -6467 if path_was_known { -6468 trace!(%path_id, %remote, "nat traversal: path existed for remote"); -6469 } -6470 Ok(Some((path_id, remote))) -6471 } -6472 Err(e) => { -6473 debug!(%remote, %e, "nat traversal: failed to probe remote"); -6474 Err(e) -6475 } -6476 } -6477 } -6478 -6479 /// Initiates a new nat traversal round -6480 /// -6481 /// A nat traversal round involves advertising the client's local addresses in `REACH_OUT` -6482 /// frames, and initiating probing of the known remote addresses. When a new round is -6483 /// initiated, the previous one is cancelled, and paths that have not been opened are closed. -6484 /// -6485 /// Returns the server addresses that are now being probed. -6486 /// If addresses fail due to spurious errors, these might succeed later and not be returned in -6487 /// this set. -6488 pub fn initiate_nat_traversal_round( -6489 &mut self, -6490 now: Instant, -6491 ) -> Result<Vec<SocketAddr>, iroh_hp::Error> { -6492 if self.state.is_closed() { -6493 return Err(iroh_hp::Error::Closed); -6494 } -6495 -6496 let client_state = self.iroh_hp.client_side_mut()?; -6497 let iroh_hp::NatTraversalRound { -6498 new_round, -6499 reach_out_at, -6500 addresses_to_probe, -6501 prev_round_path_ids, -6502 } = client_state.initiate_nat_traversal_round()?; -6503 -6504 self.spaces[SpaceId::Data].pending.reach_out = Some((new_round, reach_out_at)); -6505 -6506 for path_id in prev_round_path_ids { -6507 let Some(path) = self.path(path_id) else { -6508 continue; -6509 }; -6510 let ip = path.network_path.remote.ip(); -6511 let port = path.network_path.remote.port(); +6373 } +6374 +6375 /// Handle new path status information: PATH_STATUS_AVAILABLE, PATH_STATUS_BACKUP +6376 fn on_path_status(&mut self, path_id: PathId, status: PathStatus, status_seq_no: VarInt) { +6377 if let Some(path) = self.paths.get_mut(&path_id) { +6378 path.data.status.remote_update(status, status_seq_no); +6379 } else { +6380 debug!("PATH_STATUS_AVAILABLE received unknown path {:?}", path_id); +6381 } +6382 self.events.push_back( +6383 PathEvent::RemoteStatus { +6384 id: path_id, +6385 status, +6386 } +6387 .into(), +6388 ); +6389 } +6390 +6391 /// Returns the maximum [`PathId`] to be used for sending in this connection. +6392 /// +6393 /// This is calculated as minimum between the local and remote's maximums when multipath is +6394 /// enabled, or `None` when disabled. +6395 /// +6396 /// For data that's received, we should use [`Self::local_max_path_id`] instead. +6397 /// The reasoning is that the remote might already have updated to its own newer +6398 /// [`Self::max_path_id`] after sending out a `MAX_PATH_ID` frame, but it got re-ordered. +6399 fn max_path_id(&self) -> Option<PathId> { +6400 if self.is_multipath_negotiated() { +6401 Some(self.remote_max_path_id.min(self.local_max_path_id)) +6402 } else { +6403 None +6404 } +6405 } +6406 +6407 /// Add addresses the local endpoint considers are reachable for nat traversal +6408 pub fn add_nat_traversal_address(&mut self, address: SocketAddr) -> Result<(), iroh_hp::Error> { +6409 if let Some(added) = self.iroh_hp.add_local_address(address)? { +6410 self.spaces[SpaceId::Data].pending.add_address.insert(added); +6411 }; +6412 Ok(()) +6413 } +6414 +6415 /// Removes an address the endpoing no longer considers reachable for nat traversal +6416 /// +6417 /// Addresses not present in the set will be silently ignored. +6418 pub fn remove_nat_traversal_address( +6419 &mut self, +6420 address: SocketAddr, +6421 ) -> Result<(), iroh_hp::Error> { +6422 if let Some(removed) = self.iroh_hp.remove_local_address(address)? { +6423 self.spaces[SpaceId::Data] +6424 .pending +6425 .remove_address +6426 .insert(removed); +6427 } +6428 Ok(()) +6429 } +6430 +6431 /// Get the current local nat traversal addresses +6432 pub fn get_local_nat_traversal_addresses(&self) -> Result<Vec<SocketAddr>, iroh_hp::Error> { +6433 self.iroh_hp.get_local_nat_traversal_addresses() +6434 } +6435 +6436 /// Get the currently advertised nat traversal addresses by the server +6437 pub fn get_remote_nat_traversal_addresses(&self) -> Result<Vec<SocketAddr>, iroh_hp::Error> { +6438 Ok(self +6439 .iroh_hp +6440 .client_side()? +6441 .get_remote_nat_traversal_addresses()) +6442 } +6443 +6444 /// Attempts to open a path for nat traversal. +6445 /// +6446 /// `ipv6` indicates if the path should be opened using an IPV6 remote. If the address is +6447 /// ignored, it will return `None`. +6448 /// +6449 /// On success returns the [`PathId`] and remote address of the path. +6450 fn open_nat_traversal_path( +6451 &mut self, +6452 now: Instant, +6453 (ip, port): (IpAddr, u16), +6454 ipv6: bool, +6455 ) -> Result<Option<(PathId, SocketAddr)>, PathError> { +6456 // If this endpoint is an IPv6 endpoint we use IPv6 addresses for all remotes. +6457 let remote = match ip { +6458 IpAddr::V4(addr) if ipv6 => SocketAddr::new(addr.to_ipv6_mapped().into(), port), +6459 IpAddr::V4(addr) => SocketAddr::new(addr.into(), port), +6460 IpAddr::V6(_) if ipv6 => SocketAddr::new(ip, port), +6461 IpAddr::V6(_) => { +6462 trace!("not using IPv6 nat candidate for IPv4 socket"); +6463 return Ok(None); +6464 } +6465 }; +6466 // TODO(matheus23): Probe the correct 4-tuple, instead of only a remote address? +6467 // By specifying None, we do two things: 1. open_path_ensure won't generate two +6468 // paths to the same remote and 2. we let the OS choose which interface to use for +6469 // sending on that path. +6470 let network_path = FourTuple { +6471 remote, +6472 local_ip: None, +6473 }; +6474 match self.open_path_ensure(network_path, PathStatus::Backup, now) { +6475 Ok((path_id, path_was_known)) => { +6476 if path_was_known { +6477 trace!(%path_id, %remote, "nat traversal: path existed for remote"); +6478 } +6479 Ok(Some((path_id, remote))) +6480 } +6481 Err(e) => { +6482 debug!(%remote, %e, "nat traversal: failed to probe remote"); +6483 Err(e) +6484 } +6485 } +6486 } +6487 +6488 /// Initiates a new nat traversal round +6489 /// +6490 /// A nat traversal round involves advertising the client's local addresses in `REACH_OUT` +6491 /// frames, and initiating probing of the known remote addresses. When a new round is +6492 /// initiated, the previous one is cancelled, and paths that have not been opened are closed. +6493 /// +6494 /// Returns the server addresses that are now being probed. +6495 /// If addresses fail due to spurious errors, these might succeed later and not be returned in +6496 /// this set. +6497 pub fn initiate_nat_traversal_round( +6498 &mut self, +6499 now: Instant, +6500 ) -> Result<Vec<SocketAddr>, iroh_hp::Error> { +6501 if self.state.is_closed() { +6502 return Err(iroh_hp::Error::Closed); +6503 } +6504 +6505 let client_state = self.iroh_hp.client_side_mut()?; +6506 let iroh_hp::NatTraversalRound { +6507 new_round, +6508 reach_out_at, +6509 addresses_to_probe, +6510 prev_round_path_ids, +6511 } = client_state.initiate_nat_traversal_round()?; 6512 -6513 // We only close paths that aren't validated (thus are working) that we opened -6514 // in a previous round. -6515 // And we only close paths that we don't want to probe anyways. -6516 if !addresses_to_probe -6517 .iter() -6518 .any(|(_, (probe_ip, probe_port))| { -6519 *probe_port == port && probe_ip.to_canonical() == ip.to_canonical() -6520 }) -6521 && !path.validated -6522 && !self.abandoned_paths.contains(&path_id) -6523 { -6524 trace!(%path_id, "closing path from previous round"); -6525 let _ = self.close_path( -6526 now, -6527 path_id, -6528 TransportErrorCode::APPLICATION_ABANDON_PATH.into(), -6529 ); -6530 } -6531 } -6532 -6533 let mut err = None; -6534 -6535 let mut path_ids = Vec::with_capacity(addresses_to_probe.len()); -6536 let mut probed_addresses = Vec::with_capacity(addresses_to_probe.len()); -6537 let ipv6 = self -6538 .paths -6539 .values() -6540 .any(|p| p.data.network_path.remote.is_ipv6()); +6513 self.spaces[SpaceId::Data].pending.reach_out = Some((new_round, reach_out_at)); +6514 +6515 for path_id in prev_round_path_ids { +6516 let Some(path) = self.path(path_id) else { +6517 continue; +6518 }; +6519 let ip = path.network_path.remote.ip(); +6520 let port = path.network_path.remote.port(); +6521 +6522 // We only close paths that aren't validated (thus are working) that we opened +6523 // in a previous round. +6524 // And we only close paths that we don't want to probe anyways. +6525 if !addresses_to_probe +6526 .iter() +6527 .any(|(_, (probe_ip, probe_port))| { +6528 *probe_port == port && probe_ip.to_canonical() == ip.to_canonical() +6529 }) +6530 && !path.validated +6531 && !self.abandoned_paths.contains(&path_id) +6532 { +6533 trace!(%path_id, "closing path from previous round"); +6534 let _ = self.close_path( +6535 now, +6536 path_id, +6537 TransportErrorCode::APPLICATION_ABANDON_PATH.into(), +6538 ); +6539 } +6540 } 6541 -6542 for (id, address) in addresses_to_probe { -6543 match self.open_nat_traversal_path(now, address, ipv6) { -6544 Ok(None) => {} -6545 Ok(Some((path_id, remote))) => { -6546 path_ids.push(path_id); -6547 probed_addresses.push(remote); -6548 } -6549 Err(e) => { -6550 self.iroh_hp -6551 .client_side_mut() -6552 .expect("validated") -6553 .report_in_continuation(id, e); -6554 err.get_or_insert(e); -6555 } -6556 } -6557 } -6558 -6559 if let Some(err) = err { -6560 // We failed to probe any addresses, bail out -6561 if probed_addresses.is_empty() { -6562 return Err(iroh_hp::Error::Multipath(err)); -6563 } -6564 } -6565 -6566 self.iroh_hp -6567 .client_side_mut() -6568 .expect("connection side validated") -6569 .set_round_path_ids(path_ids); -6570 -6571 Ok(probed_addresses) -6572 } -6573 -6574 /// Attempts to continue a nat traversal round by trying to open paths for pending client probes. -6575 /// -6576 /// If there was nothing to do, it returns `None`. Otherwise it returns whether the path was -6577 /// successfully open. -6578 fn continue_nat_traversal_round(&mut self, now: Instant) -> Option<bool> { -6579 let client_state = self.iroh_hp.client_side_mut().ok()?; -6580 let (id, address) = client_state.continue_nat_traversal_round()?; -6581 let ipv6 = self -6582 .paths -6583 .values() -6584 .any(|p| p.data.network_path.remote.is_ipv6()); -6585 let open_result = self.open_nat_traversal_path(now, address, ipv6); -6586 let client_state = self.iroh_hp.client_side_mut().expect("validated"); -6587 match open_result { -6588 Ok(None) => Some(true), -6589 Ok(Some((path_id, _remote))) => { -6590 client_state.add_round_path_id(path_id); -6591 Some(true) -6592 } -6593 Err(e) => { -6594 client_state.report_in_continuation(id, e); -6595 Some(false) -6596 } -6597 } -6598 } -6599} -6600 -6601impl fmt::Debug for Connection { -6602 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -6603 f.debug_struct("Connection") -6604 .field("handshake_cid", &self.handshake_cid) -6605 .finish() -6606 } -6607} -6608 -6609#[derive(Debug, Copy, Clone, PartialEq, Eq)] -6610enum PathBlocked { -6611 No, -6612 AntiAmplification, -6613 Congestion, -6614 Pacing, -6615} -6616 -6617/// Fields of `Connection` specific to it being client-side or server-side -6618enum ConnectionSide { -6619 Client { -6620 /// Sent in every outgoing Initial packet. Always empty after Initial keys are discarded -6621 token: Bytes, -6622 token_store: Arc<dyn TokenStore>, -6623 server_name: String, -6624 }, -6625 Server { -6626 server_config: Arc<ServerConfig>, -6627 }, -6628} -6629 -6630impl ConnectionSide { -6631 fn remote_may_migrate(&self, state: &State) -> bool { -6632 match self { -6633 Self::Server { server_config } => server_config.migration, -6634 Self::Client { .. } => { -6635 if let Some(hs) = state.as_handshake() { -6636 hs.allow_server_migration -6637 } else { -6638 false -6639 } -6640 } -6641 } -6642 } -6643 -6644 fn is_client(&self) -> bool { -6645 self.side().is_client() -6646 } -6647 -6648 fn is_server(&self) -> bool { -6649 self.side().is_server() -6650 } -6651 -6652 fn side(&self) -> Side { -6653 match *self { -6654 Self::Client { .. } => Side::Client, -6655 Self::Server { .. } => Side::Server, -6656 } -6657 } -6658} -6659 -6660impl From<SideArgs> for ConnectionSide { -6661 fn from(side: SideArgs) -> Self { -6662 match side { -6663 SideArgs::Client { -6664 token_store, -6665 server_name, -6666 } => Self::Client { -6667 token: token_store.take(&server_name).unwrap_or_default(), -6668 token_store, -6669 server_name, -6670 }, -6671 SideArgs::Server { -6672 server_config, -6673 pref_addr_cid: _, -6674 path_validated: _, -6675 } => Self::Server { server_config }, -6676 } -6677 } -6678} -6679 -6680/// Parameters to `Connection::new` specific to it being client-side or server-side -6681pub(crate) enum SideArgs { -6682 Client { -6683 token_store: Arc<dyn TokenStore>, -6684 server_name: String, -6685 }, -6686 Server { -6687 server_config: Arc<ServerConfig>, -6688 pref_addr_cid: Option<ConnectionId>, -6689 path_validated: bool, -6690 }, -6691} -6692 -6693impl SideArgs { -6694 pub(crate) fn pref_addr_cid(&self) -> Option<ConnectionId> { -6695 match *self { -6696 Self::Client { .. } => None, -6697 Self::Server { pref_addr_cid, .. } => pref_addr_cid, -6698 } -6699 } -6700 -6701 pub(crate) fn path_validated(&self) -> bool { -6702 match *self { -6703 Self::Client { .. } => true, -6704 Self::Server { path_validated, .. } => path_validated, -6705 } -6706 } -6707 -6708 pub(crate) fn side(&self) -> Side { -6709 match *self { -6710 Self::Client { .. } => Side::Client, -6711 Self::Server { .. } => Side::Server, -6712 } -6713 } -6714} -6715 -6716/// Reasons why a connection might be lost -6717#[derive(Debug, Error, Clone, PartialEq, Eq)] -6718pub enum ConnectionError { -6719 /// The peer doesn't implement any supported version -6720 #[error("peer doesn't implement any supported version")] -6721 VersionMismatch, -6722 /// The peer violated the QUIC specification as understood by this implementation -6723 #[error(transparent)] -6724 TransportError(#[from] TransportError), -6725 /// The peer's QUIC stack aborted the connection automatically -6726 #[error("aborted by peer: {0}")] -6727 ConnectionClosed(frame::ConnectionClose), -6728 /// The peer closed the connection -6729 #[error("closed by peer: {0}")] -6730 ApplicationClosed(frame::ApplicationClose), -6731 /// The peer is unable to continue processing this connection, usually due to having restarted -6732 #[error("reset by peer")] -6733 Reset, -6734 /// Communication with the peer has lapsed for longer than the negotiated idle timeout -6735 /// -6736 /// If neither side is sending keep-alives, a connection will time out after a long enough idle -6737 /// period even if the peer is still reachable. See also [`TransportConfig::max_idle_timeout()`] -6738 /// and [`TransportConfig::keep_alive_interval()`]. -6739 #[error("timed out")] -6740 TimedOut, -6741 /// The local application closed the connection -6742 #[error("closed")] -6743 LocallyClosed, -6744 /// The connection could not be created because not enough of the CID space is available -6745 /// -6746 /// Try using longer connection IDs. -6747 #[error("CIDs exhausted")] -6748 CidsExhausted, -6749} -6750 -6751impl From<Close> for ConnectionError { -6752 fn from(x: Close) -> Self { -6753 match x { -6754 Close::Connection(reason) => Self::ConnectionClosed(reason), -6755 Close::Application(reason) => Self::ApplicationClosed(reason), -6756 } -6757 } +6542 let mut err = None; +6543 +6544 let mut path_ids = Vec::with_capacity(addresses_to_probe.len()); +6545 let mut probed_addresses = Vec::with_capacity(addresses_to_probe.len()); +6546 let ipv6 = self +6547 .paths +6548 .values() +6549 .any(|p| p.data.network_path.remote.is_ipv6()); +6550 +6551 for (id, address) in addresses_to_probe { +6552 match self.open_nat_traversal_path(now, address, ipv6) { +6553 Ok(None) => {} +6554 Ok(Some((path_id, remote))) => { +6555 path_ids.push(path_id); +6556 probed_addresses.push(remote); +6557 } +6558 Err(e) => { +6559 self.iroh_hp +6560 .client_side_mut() +6561 .expect("validated") +6562 .report_in_continuation(id, e); +6563 err.get_or_insert(e); +6564 } +6565 } +6566 } +6567 +6568 if let Some(err) = err { +6569 // We failed to probe any addresses, bail out +6570 if probed_addresses.is_empty() { +6571 return Err(iroh_hp::Error::Multipath(err)); +6572 } +6573 } +6574 +6575 self.iroh_hp +6576 .client_side_mut() +6577 .expect("connection side validated") +6578 .set_round_path_ids(path_ids); +6579 +6580 Ok(probed_addresses) +6581 } +6582 +6583 /// Attempts to continue a nat traversal round by trying to open paths for pending client probes. +6584 /// +6585 /// If there was nothing to do, it returns `None`. Otherwise it returns whether the path was +6586 /// successfully open. +6587 fn continue_nat_traversal_round(&mut self, now: Instant) -> Option<bool> { +6588 let client_state = self.iroh_hp.client_side_mut().ok()?; +6589 let (id, address) = client_state.continue_nat_traversal_round()?; +6590 let ipv6 = self +6591 .paths +6592 .values() +6593 .any(|p| p.data.network_path.remote.is_ipv6()); +6594 let open_result = self.open_nat_traversal_path(now, address, ipv6); +6595 let client_state = self.iroh_hp.client_side_mut().expect("validated"); +6596 match open_result { +6597 Ok(None) => Some(true), +6598 Ok(Some((path_id, _remote))) => { +6599 client_state.add_round_path_id(path_id); +6600 Some(true) +6601 } +6602 Err(e) => { +6603 client_state.report_in_continuation(id, e); +6604 Some(false) +6605 } +6606 } +6607 } +6608} +6609 +6610impl fmt::Debug for Connection { +6611 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +6612 f.debug_struct("Connection") +6613 .field("handshake_cid", &self.handshake_cid) +6614 .finish() +6615 } +6616} +6617 +6618#[derive(Debug, Copy, Clone, PartialEq, Eq)] +6619enum PathBlocked { +6620 No, +6621 AntiAmplification, +6622 Congestion, +6623 Pacing, +6624} +6625 +6626/// Fields of `Connection` specific to it being client-side or server-side +6627enum ConnectionSide { +6628 Client { +6629 /// Sent in every outgoing Initial packet. Always empty after Initial keys are discarded +6630 token: Bytes, +6631 token_store: Arc<dyn TokenStore>, +6632 server_name: String, +6633 }, +6634 Server { +6635 server_config: Arc<ServerConfig>, +6636 }, +6637} +6638 +6639impl ConnectionSide { +6640 fn remote_may_migrate(&self, state: &State) -> bool { +6641 match self { +6642 Self::Server { server_config } => server_config.migration, +6643 Self::Client { .. } => { +6644 if let Some(hs) = state.as_handshake() { +6645 hs.allow_server_migration +6646 } else { +6647 false +6648 } +6649 } +6650 } +6651 } +6652 +6653 fn is_client(&self) -> bool { +6654 self.side().is_client() +6655 } +6656 +6657 fn is_server(&self) -> bool { +6658 self.side().is_server() +6659 } +6660 +6661 fn side(&self) -> Side { +6662 match *self { +6663 Self::Client { .. } => Side::Client, +6664 Self::Server { .. } => Side::Server, +6665 } +6666 } +6667} +6668 +6669impl From<SideArgs> for ConnectionSide { +6670 fn from(side: SideArgs) -> Self { +6671 match side { +6672 SideArgs::Client { +6673 token_store, +6674 server_name, +6675 } => Self::Client { +6676 token: token_store.take(&server_name).unwrap_or_default(), +6677 token_store, +6678 server_name, +6679 }, +6680 SideArgs::Server { +6681 server_config, +6682 pref_addr_cid: _, +6683 path_validated: _, +6684 } => Self::Server { server_config }, +6685 } +6686 } +6687} +6688 +6689/// Parameters to `Connection::new` specific to it being client-side or server-side +6690pub(crate) enum SideArgs { +6691 Client { +6692 token_store: Arc<dyn TokenStore>, +6693 server_name: String, +6694 }, +6695 Server { +6696 server_config: Arc<ServerConfig>, +6697 pref_addr_cid: Option<ConnectionId>, +6698 path_validated: bool, +6699 }, +6700} +6701 +6702impl SideArgs { +6703 pub(crate) fn pref_addr_cid(&self) -> Option<ConnectionId> { +6704 match *self { +6705 Self::Client { .. } => None, +6706 Self::Server { pref_addr_cid, .. } => pref_addr_cid, +6707 } +6708 } +6709 +6710 pub(crate) fn path_validated(&self) -> bool { +6711 match *self { +6712 Self::Client { .. } => true, +6713 Self::Server { path_validated, .. } => path_validated, +6714 } +6715 } +6716 +6717 pub(crate) fn side(&self) -> Side { +6718 match *self { +6719 Self::Client { .. } => Side::Client, +6720 Self::Server { .. } => Side::Server, +6721 } +6722 } +6723} +6724 +6725/// Reasons why a connection might be lost +6726#[derive(Debug, Error, Clone, PartialEq, Eq)] +6727pub enum ConnectionError { +6728 /// The peer doesn't implement any supported version +6729 #[error("peer doesn't implement any supported version")] +6730 VersionMismatch, +6731 /// The peer violated the QUIC specification as understood by this implementation +6732 #[error(transparent)] +6733 TransportError(#[from] TransportError), +6734 /// The peer's QUIC stack aborted the connection automatically +6735 #[error("aborted by peer: {0}")] +6736 ConnectionClosed(frame::ConnectionClose), +6737 /// The peer closed the connection +6738 #[error("closed by peer: {0}")] +6739 ApplicationClosed(frame::ApplicationClose), +6740 /// The peer is unable to continue processing this connection, usually due to having restarted +6741 #[error("reset by peer")] +6742 Reset, +6743 /// Communication with the peer has lapsed for longer than the negotiated idle timeout +6744 /// +6745 /// If neither side is sending keep-alives, a connection will time out after a long enough idle +6746 /// period even if the peer is still reachable. See also [`TransportConfig::max_idle_timeout()`] +6747 /// and [`TransportConfig::keep_alive_interval()`]. +6748 #[error("timed out")] +6749 TimedOut, +6750 /// The local application closed the connection +6751 #[error("closed")] +6752 LocallyClosed, +6753 /// The connection could not be created because not enough of the CID space is available +6754 /// +6755 /// Try using longer connection IDs. +6756 #[error("CIDs exhausted")] +6757 CidsExhausted, 6758} 6759 -6760// For compatibility with API consumers -6761impl From<ConnectionError> for io::Error { -6762 fn from(x: ConnectionError) -> Self { -6763 use ConnectionError::*; -6764 let kind = match x { -6765 TimedOut => io::ErrorKind::TimedOut, -6766 Reset => io::ErrorKind::ConnectionReset, -6767 ApplicationClosed(_) | ConnectionClosed(_) => io::ErrorKind::ConnectionAborted, -6768 TransportError(_) | VersionMismatch | LocallyClosed | CidsExhausted => { -6769 io::ErrorKind::Other -6770 } -6771 }; -6772 Self::new(kind, x) -6773 } -6774} -6775 -6776/// Errors that might trigger a path being closed -6777// TODO(@divma): maybe needs to be reworked based on what we want to do with the public API -6778#[derive(Debug, Error, PartialEq, Eq, Clone, Copy)] -6779pub enum PathError { -6780 /// The extension was not negotiated with the peer -6781 #[error("multipath extension not negotiated")] -6782 MultipathNotNegotiated, -6783 /// Paths can only be opened client-side -6784 #[error("the server side may not open a path")] -6785 ServerSideNotAllowed, -6786 /// Current limits do not allow us to open more paths -6787 #[error("maximum number of concurrent paths reached")] -6788 MaxPathIdReached, -6789 /// No remote CIDs available to open a new path -6790 #[error("remoted CIDs exhausted")] -6791 RemoteCidsExhausted, -6792 /// Path could not be validated and will be abandoned -6793 #[error("path validation failed")] -6794 ValidationFailed, -6795 /// The remote address for the path is not supported by the endpoint -6796 #[error("invalid remote address")] -6797 InvalidRemoteAddress(SocketAddr), -6798} -6799 -6800/// Errors triggered when abandoning a path -6801#[derive(Debug, Error, Clone, Eq, PartialEq)] -6802pub enum ClosePathError { -6803 /// The path is already closed or was never opened -6804 #[error("closed path")] -6805 ClosedPath, -6806 /// This is the last path, which can not be abandoned -6807 #[error("last open path")] -6808 LastOpenPath, -6809} -6810 -6811/// Error when the multipath extension was not negotiated, but attempted to be used. -6812#[derive(Debug, Error, Clone, Copy)] -6813#[error("Multipath extension not negotiated")] -6814pub struct MultipathNotNegotiated { -6815 _private: (), -6816} -6817 -6818/// Events of interest to the application -6819#[derive(Debug)] -6820pub enum Event { -6821 /// The connection's handshake data is ready -6822 HandshakeDataReady, -6823 /// The connection was successfully established -6824 Connected, -6825 /// The TLS handshake was confirmed -6826 HandshakeConfirmed, -6827 /// The connection was lost -6828 /// -6829 /// Emitted if the peer closes the connection or an error is encountered. -6830 ConnectionLost { -6831 /// Reason that the connection was closed -6832 reason: ConnectionError, -6833 }, -6834 /// Stream events -6835 Stream(StreamEvent), -6836 /// One or more application datagrams have been received -6837 DatagramReceived, -6838 /// One or more application datagrams have been sent after blocking -6839 DatagramsUnblocked, -6840 /// (Multi)Path events -6841 Path(PathEvent), -6842 /// Iroh's nat traversal events -6843 NatTraversal(iroh_hp::Event), -6844} -6845 -6846impl From<PathEvent> for Event { -6847 fn from(source: PathEvent) -> Self { -6848 Self::Path(source) -6849 } -6850} -6851 -6852fn get_max_ack_delay(params: &TransportParameters) -> Duration { -6853 Duration::from_micros(params.max_ack_delay.0 * 1000) -6854} -6855 -6856// Prevents overflow and improves behavior in extreme circumstances -6857const MAX_BACKOFF_EXPONENT: u32 = 16; -6858 -6859/// Minimal remaining size to allow packet coalescing, excluding cryptographic tag -6860/// -6861/// This must be at least as large as the header for a well-formed empty packet to be coalesced, -6862/// plus some space for frames. We only care about handshake headers because short header packets -6863/// necessarily have smaller headers, and initial packets are only ever the first packet in a -6864/// datagram (because we coalesce in ascending packet space order and the only reason to split a -6865/// packet is when packet space changes). -6866const MIN_PACKET_SPACE: usize = MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE + 32; +6760impl From<Close> for ConnectionError { +6761 fn from(x: Close) -> Self { +6762 match x { +6763 Close::Connection(reason) => Self::ConnectionClosed(reason), +6764 Close::Application(reason) => Self::ApplicationClosed(reason), +6765 } +6766 } +6767} +6768 +6769// For compatibility with API consumers +6770impl From<ConnectionError> for io::Error { +6771 fn from(x: ConnectionError) -> Self { +6772 use ConnectionError::*; +6773 let kind = match x { +6774 TimedOut => io::ErrorKind::TimedOut, +6775 Reset => io::ErrorKind::ConnectionReset, +6776 ApplicationClosed(_) | ConnectionClosed(_) => io::ErrorKind::ConnectionAborted, +6777 TransportError(_) | VersionMismatch | LocallyClosed | CidsExhausted => { +6778 io::ErrorKind::Other +6779 } +6780 }; +6781 Self::new(kind, x) +6782 } +6783} +6784 +6785/// Errors that might trigger a path being closed +6786// TODO(@divma): maybe needs to be reworked based on what we want to do with the public API +6787#[derive(Debug, Error, PartialEq, Eq, Clone, Copy)] +6788pub enum PathError { +6789 /// The extension was not negotiated with the peer +6790 #[error("multipath extension not negotiated")] +6791 MultipathNotNegotiated, +6792 /// Paths can only be opened client-side +6793 #[error("the server side may not open a path")] +6794 ServerSideNotAllowed, +6795 /// Current limits do not allow us to open more paths +6796 #[error("maximum number of concurrent paths reached")] +6797 MaxPathIdReached, +6798 /// No remote CIDs available to open a new path +6799 #[error("remoted CIDs exhausted")] +6800 RemoteCidsExhausted, +6801 /// Path could not be validated and will be abandoned +6802 #[error("path validation failed")] +6803 ValidationFailed, +6804 /// The remote address for the path is not supported by the endpoint +6805 #[error("invalid remote address")] +6806 InvalidRemoteAddress(SocketAddr), +6807} +6808 +6809/// Errors triggered when abandoning a path +6810#[derive(Debug, Error, Clone, Eq, PartialEq)] +6811pub enum ClosePathError { +6812 /// The path is already closed or was never opened +6813 #[error("closed path")] +6814 ClosedPath, +6815 /// This is the last path, which can not be abandoned +6816 #[error("last open path")] +6817 LastOpenPath, +6818} +6819 +6820/// Error when the multipath extension was not negotiated, but attempted to be used. +6821#[derive(Debug, Error, Clone, Copy)] +6822#[error("Multipath extension not negotiated")] +6823pub struct MultipathNotNegotiated { +6824 _private: (), +6825} +6826 +6827/// Events of interest to the application +6828#[derive(Debug)] +6829pub enum Event { +6830 /// The connection's handshake data is ready +6831 HandshakeDataReady, +6832 /// The connection was successfully established +6833 Connected, +6834 /// The TLS handshake was confirmed +6835 HandshakeConfirmed, +6836 /// The connection was lost +6837 /// +6838 /// Emitted if the peer closes the connection or an error is encountered. +6839 ConnectionLost { +6840 /// Reason that the connection was closed +6841 reason: ConnectionError, +6842 }, +6843 /// Stream events +6844 Stream(StreamEvent), +6845 /// One or more application datagrams have been received +6846 DatagramReceived, +6847 /// One or more application datagrams have been sent after blocking +6848 DatagramsUnblocked, +6849 /// (Multi)Path events +6850 Path(PathEvent), +6851 /// Iroh's nat traversal events +6852 NatTraversal(iroh_hp::Event), +6853} +6854 +6855impl From<PathEvent> for Event { +6856 fn from(source: PathEvent) -> Self { +6857 Self::Path(source) +6858 } +6859} +6860 +6861fn get_max_ack_delay(params: &TransportParameters) -> Duration { +6862 Duration::from_micros(params.max_ack_delay.0 * 1000) +6863} +6864 +6865// Prevents overflow and improves behavior in extreme circumstances +6866const MAX_BACKOFF_EXPONENT: u32 = 16; 6867 -6868/// Largest amount of space that could be occupied by a Handshake or 0-RTT packet's header +6868/// Minimal remaining size to allow packet coalescing, excluding cryptographic tag 6869/// -6870/// Excludes packet-type-specific fields such as packet number or Initial token -6871// https://www.rfc-editor.org/rfc/rfc9000.html#name-0-rtt: flags + version + dcid len + dcid + -6872// scid len + scid + length + pn -6873const MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE: usize = -6874 1 + 4 + 1 + MAX_CID_SIZE + 1 + MAX_CID_SIZE + VarInt::from_u32(u16::MAX as u32).size() + 4; -6875 -6876/// Perform key updates this many packets before the AEAD confidentiality limit. -6877/// -6878/// Chosen arbitrarily, intended to be large enough to prevent spurious connection loss. -6879const KEY_UPDATE_MARGIN: u64 = 10_000; -6880 -6881#[derive(Default)] -6882struct SentFrames { -6883 retransmits: ThinRetransmits, -6884 /// The packet number of the largest acknowledged packet for each path -6885 largest_acked: FxHashMap<PathId, u64>, -6886 stream_frames: StreamMetaVec, -6887 /// Whether the packet contains non-retransmittable frames (like datagrams) -6888 non_retransmits: bool, -6889 /// If the datagram containing these frames should be padded to the min MTU -6890 requires_padding: bool, -6891} -6892 -6893impl SentFrames { -6894 /// Returns whether the packet contains only ACKs -6895 fn is_ack_only(&self, streams: &StreamsState) -> bool { -6896 !self.largest_acked.is_empty() -6897 && !self.non_retransmits -6898 && self.stream_frames.is_empty() -6899 && self.retransmits.is_empty(streams) -6900 } +6870/// This must be at least as large as the header for a well-formed empty packet to be coalesced, +6871/// plus some space for frames. We only care about handshake headers because short header packets +6872/// necessarily have smaller headers, and initial packets are only ever the first packet in a +6873/// datagram (because we coalesce in ascending packet space order and the only reason to split a +6874/// packet is when packet space changes). +6875const MIN_PACKET_SPACE: usize = MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE + 32; +6876 +6877/// Largest amount of space that could be occupied by a Handshake or 0-RTT packet's header +6878/// +6879/// Excludes packet-type-specific fields such as packet number or Initial token +6880// https://www.rfc-editor.org/rfc/rfc9000.html#name-0-rtt: flags + version + dcid len + dcid + +6881// scid len + scid + length + pn +6882const MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE: usize = +6883 1 + 4 + 1 + MAX_CID_SIZE + 1 + MAX_CID_SIZE + VarInt::from_u32(u16::MAX as u32).size() + 4; +6884 +6885/// Perform key updates this many packets before the AEAD confidentiality limit. +6886/// +6887/// Chosen arbitrarily, intended to be large enough to prevent spurious connection loss. +6888const KEY_UPDATE_MARGIN: u64 = 10_000; +6889 +6890#[derive(Default)] +6891struct SentFrames { +6892 retransmits: ThinRetransmits, +6893 /// The packet number of the largest acknowledged packet for each path +6894 largest_acked: FxHashMap<PathId, u64>, +6895 stream_frames: StreamMetaVec, +6896 /// Whether the packet contains non-retransmittable frames (like datagrams) +6897 non_retransmits: bool, +6898 /// If the datagram containing these frames should be padded to the min MTU +6899 requires_padding: bool, +6900} 6901 -6902 fn retransmits_mut(&mut self) -> &mut Retransmits { -6903 self.retransmits.get_or_create() -6904 } -6905 -6906 fn record_sent_frame(&mut self, frame: frame::EncodableFrame<'_>) { -6907 use frame::EncodableFrame::*; -6908 match frame { -6909 PathAck(path_ack_encoder) => { -6910 if let Some(max) = path_ack_encoder.ranges.max() { -6911 self.largest_acked.insert(path_ack_encoder.path_id, max); -6912 } -6913 } -6914 Ack(ack_encoder) => { -6915 if let Some(max) = ack_encoder.ranges.max() { -6916 self.largest_acked.insert(PathId::ZERO, max); -6917 } -6918 } -6919 Close(_) => { /* non retransmittable, but after this we don't really care */ } -6920 PathResponse(_) => self.non_retransmits = true, -6921 HandshakeDone(_) => self.retransmits_mut().handshake_done = true, -6922 ReachOut(frame::ReachOut { round, ip, port }) => self -6923 .retransmits_mut() -6924 .reach_out -6925 .get_or_insert_with(|| (round, Vec::new())) -6926 .1 -6927 .push((ip, port)), -6928 ObservedAddr(_) => self.retransmits_mut().observed_addr = true, -6929 Ping(_) => self.non_retransmits = true, -6930 ImmediateAck(_) => self.non_retransmits = true, -6931 AckFrequency(_) => self.retransmits_mut().ack_frequency = true, -6932 PathChallenge(_) => self.non_retransmits = true, -6933 Crypto(crypto) => self.retransmits_mut().crypto.push_back(crypto), -6934 PathAbandon(path_abandon) => { -6935 self.retransmits_mut() -6936 .path_abandon -6937 .entry(path_abandon.path_id) -6938 .or_insert(path_abandon.error_code); -6939 } -6940 PathStatusAvailable(frame::PathStatusAvailable { path_id, .. }) -6941 | PathStatusBackup(frame::PathStatusBackup { path_id, .. }) => { -6942 self.retransmits_mut().path_status.insert(path_id); -6943 } -6944 MaxPathId(_) => self.retransmits_mut().max_path_id = true, -6945 PathsBlocked(_) => self.retransmits_mut().paths_blocked = true, -6946 PathCidsBlocked(path_cids_blocked) => { -6947 self.retransmits_mut() -6948 .path_cids_blocked -6949 .insert(path_cids_blocked.path_id); -6950 } -6951 ResetStream(reset) => self -6952 .retransmits_mut() -6953 .reset_stream -6954 .push((reset.id, reset.error_code)), -6955 StopSending(stop_sending) => self.retransmits_mut().stop_sending.push(stop_sending), -6956 NewConnectionId(new_cid) => self.retransmits_mut().new_cids.push(new_cid.issued()), -6957 RetireConnectionId(retire_cid) => self -6958 .retransmits_mut() -6959 .retire_cids -6960 .push((retire_cid.path_id.unwrap_or_default(), retire_cid.sequence)), -6961 Datagram(_) => self.non_retransmits = true, -6962 NewToken(_) => {} -6963 AddAddress(add_address) => { -6964 self.retransmits_mut().add_address.insert(add_address); -6965 } -6966 RemoveAddress(remove_address) => { -6967 self.retransmits_mut().remove_address.insert(remove_address); -6968 } -6969 StreamMeta(stream_meta_encoder) => self.stream_frames.push(stream_meta_encoder.meta), -6970 MaxData(_) => self.retransmits_mut().max_data = true, -6971 MaxStreamData(max) => { -6972 self.retransmits_mut().max_stream_data.insert(max.id); -6973 } -6974 MaxStreams(max_streams) => { -6975 self.retransmits_mut().max_stream_id[max_streams.dir as usize] = true -6976 } -6977 } -6978 } -6979} -6980 -6981/// Compute the negotiated idle timeout based on local and remote max_idle_timeout transport parameters. -6982/// -6983/// 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.> -6984/// -6985/// 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.> -6986/// -6987/// Returns the negotiated idle timeout as a `Duration`, or `None` when both endpoints have opted out of idle timeout. -6988fn negotiate_max_idle_timeout(x: Option<VarInt>, y: Option<VarInt>) -> Option<Duration> { -6989 match (x, y) { -6990 (Some(VarInt(0)) | None, Some(VarInt(0)) | None) => None, -6991 (Some(VarInt(0)) | None, Some(y)) => Some(Duration::from_millis(y.0)), -6992 (Some(x), Some(VarInt(0)) | None) => Some(Duration::from_millis(x.0)), -6993 (Some(x), Some(y)) => Some(Duration::from_millis(cmp::min(x, y).0)), -6994 } -6995} -6996 -6997#[cfg(test)] -6998mod tests { -6999 use super::*; -7000 -7001 #[test] -7002 fn negotiate_max_idle_timeout_commutative() { -7003 let test_params = [ -7004 (None, None, None), -7005 (None, Some(VarInt(0)), None), -7006 (None, Some(VarInt(2)), Some(Duration::from_millis(2))), -7007 (Some(VarInt(0)), Some(VarInt(0)), None), -7008 ( -7009 Some(VarInt(2)), -7010 Some(VarInt(0)), -7011 Some(Duration::from_millis(2)), -7012 ), -7013 ( -7014 Some(VarInt(1)), -7015 Some(VarInt(4)), -7016 Some(Duration::from_millis(1)), -7017 ), -7018 ]; -7019 -7020 for (left, right, result) in test_params { -7021 assert_eq!(negotiate_max_idle_timeout(left, right), result); -7022 assert_eq!(negotiate_max_idle_timeout(right, left), result); -7023 } -7024 } -7025}

\ No newline at end of file +6902impl SentFrames { +6903 /// Returns whether the packet contains only ACKs +6904 fn is_ack_only(&self, streams: &StreamsState) -> bool { +6905 !self.largest_acked.is_empty() +6906 && !self.non_retransmits +6907 && self.stream_frames.is_empty() +6908 && self.retransmits.is_empty(streams) +6909 } +6910 +6911 fn retransmits_mut(&mut self) -> &mut Retransmits { +6912 self.retransmits.get_or_create() +6913 } +6914 +6915 fn record_sent_frame(&mut self, frame: frame::EncodableFrame<'_>) { +6916 use frame::EncodableFrame::*; +6917 match frame { +6918 PathAck(path_ack_encoder) => { +6919 if let Some(max) = path_ack_encoder.ranges.max() { +6920 self.largest_acked.insert(path_ack_encoder.path_id, max); +6921 } +6922 } +6923 Ack(ack_encoder) => { +6924 if let Some(max) = ack_encoder.ranges.max() { +6925 self.largest_acked.insert(PathId::ZERO, max); +6926 } +6927 } +6928 Close(_) => { /* non retransmittable, but after this we don't really care */ } +6929 PathResponse(_) => self.non_retransmits = true, +6930 HandshakeDone(_) => self.retransmits_mut().handshake_done = true, +6931 ReachOut(frame::ReachOut { round, ip, port }) => self +6932 .retransmits_mut() +6933 .reach_out +6934 .get_or_insert_with(|| (round, Vec::new())) +6935 .1 +6936 .push((ip, port)), +6937 ObservedAddr(_) => self.retransmits_mut().observed_addr = true, +6938 Ping(_) => self.non_retransmits = true, +6939 ImmediateAck(_) => self.non_retransmits = true, +6940 AckFrequency(_) => self.retransmits_mut().ack_frequency = true, +6941 PathChallenge(_) => self.non_retransmits = true, +6942 Crypto(crypto) => self.retransmits_mut().crypto.push_back(crypto), +6943 PathAbandon(path_abandon) => { +6944 self.retransmits_mut() +6945 .path_abandon +6946 .entry(path_abandon.path_id) +6947 .or_insert(path_abandon.error_code); +6948 } +6949 PathStatusAvailable(frame::PathStatusAvailable { path_id, .. }) +6950 | PathStatusBackup(frame::PathStatusBackup { path_id, .. }) => { +6951 self.retransmits_mut().path_status.insert(path_id); +6952 } +6953 MaxPathId(_) => self.retransmits_mut().max_path_id = true, +6954 PathsBlocked(_) => self.retransmits_mut().paths_blocked = true, +6955 PathCidsBlocked(path_cids_blocked) => { +6956 self.retransmits_mut() +6957 .path_cids_blocked +6958 .insert(path_cids_blocked.path_id); +6959 } +6960 ResetStream(reset) => self +6961 .retransmits_mut() +6962 .reset_stream +6963 .push((reset.id, reset.error_code)), +6964 StopSending(stop_sending) => self.retransmits_mut().stop_sending.push(stop_sending), +6965 NewConnectionId(new_cid) => self.retransmits_mut().new_cids.push(new_cid.issued()), +6966 RetireConnectionId(retire_cid) => self +6967 .retransmits_mut() +6968 .retire_cids +6969 .push((retire_cid.path_id.unwrap_or_default(), retire_cid.sequence)), +6970 Datagram(_) => self.non_retransmits = true, +6971 NewToken(_) => {} +6972 AddAddress(add_address) => { +6973 self.retransmits_mut().add_address.insert(add_address); +6974 } +6975 RemoveAddress(remove_address) => { +6976 self.retransmits_mut().remove_address.insert(remove_address); +6977 } +6978 StreamMeta(stream_meta_encoder) => self.stream_frames.push(stream_meta_encoder.meta), +6979 MaxData(_) => self.retransmits_mut().max_data = true, +6980 MaxStreamData(max) => { +6981 self.retransmits_mut().max_stream_data.insert(max.id); +6982 } +6983 MaxStreams(max_streams) => { +6984 self.retransmits_mut().max_stream_id[max_streams.dir as usize] = true +6985 } +6986 } +6987 } +6988} +6989 +6990/// Compute the negotiated idle timeout based on local and remote max_idle_timeout transport parameters. +6991/// +6992/// 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.> +6993/// +6994/// 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.> +6995/// +6996/// Returns the negotiated idle timeout as a `Duration`, or `None` when both endpoints have opted out of idle timeout. +6997fn negotiate_max_idle_timeout(x: Option<VarInt>, y: Option<VarInt>) -> Option<Duration> { +6998 match (x, y) { +6999 (Some(VarInt(0)) | None, Some(VarInt(0)) | None) => None, +7000 (Some(VarInt(0)) | None, Some(y)) => Some(Duration::from_millis(y.0)), +7001 (Some(x), Some(VarInt(0)) | None) => Some(Duration::from_millis(x.0)), +7002 (Some(x), Some(y)) => Some(Duration::from_millis(cmp::min(x, y).0)), +7003 } +7004} +7005 +7006#[cfg(test)] +7007mod tests { +7008 use super::*; +7009 +7010 #[test] +7011 fn negotiate_max_idle_timeout_commutative() { +7012 let test_params = [ +7013 (None, None, None), +7014 (None, Some(VarInt(0)), None), +7015 (None, Some(VarInt(2)), Some(Duration::from_millis(2))), +7016 (Some(VarInt(0)), Some(VarInt(0)), None), +7017 ( +7018 Some(VarInt(2)), +7019 Some(VarInt(0)), +7020 Some(Duration::from_millis(2)), +7021 ), +7022 ( +7023 Some(VarInt(1)), +7024 Some(VarInt(4)), +7025 Some(Duration::from_millis(1)), +7026 ), +7027 ]; +7028 +7029 for (left, right, result) in test_params { +7030 assert_eq!(negotiate_max_idle_timeout(left, right), result); +7031 assert_eq!(negotiate_max_idle_timeout(right, left), result); +7032 } +7033 } +7034}

\ No newline at end of file