From 39238fc03ca764e52e44ee194ce73cb3d73e6a4e Mon Sep 17 00:00:00 2001 From: divagant-martian Date: Fri, 20 Feb 2026 20:50:41 +0000 Subject: [PATCH] deploy: 8618bff6498900627d07852d361f8d85b18c978c --- pr/443/docs/bench/fn.connect_client.html | 2 +- pr/443/docs/bench/fn.drain_stream.html | 2 +- pr/443/docs/bench/fn.send_data_on_stream.html | 2 +- pr/443/docs/bench/fn.server_endpoint.html | 2 +- .../docs/iroh_quinn/enum.ConnectionError.html | 6 +- .../iroh_quinn/trait.NetworkChangeHint.html | 4 +- .../iroh_quinn_proto/enum.ClosePathError.html | 6 +- .../enum.ConnectionError.html | 6 +- pr/443/docs/iroh_quinn_proto/enum.Event.html | 4 +- .../enum.PathAbandonReason.html | 6 +- .../docs/iroh_quinn_proto/enum.PathError.html | 6 +- .../docs/iroh_quinn_proto/enum.PathEvent.html | 6 +- .../iroh_quinn_proto/enum.PathStatus.html | 6 +- .../enum.SetPathStatusError.html | 6 +- .../iroh_quinn_proto/struct.ClosedPath.html | 6 +- .../iroh_quinn_proto/struct.Connection.html | 18 +- .../struct.MultipathNotNegotiated.html | 4 +- .../iroh_quinn_proto/struct.RttEstimator.html | 10 +- .../struct.TransportError.html | 2 +- .../trait.NetworkChangeHint.html | 4 +- .../iroh_quinn_proto/connection/mod.rs.html | 4903 +++++++++-------- .../iroh_quinn_proto/connection/paths.rs.html | 1162 ++-- 22 files changed, 3092 insertions(+), 3081 deletions(-) diff --git a/pr/443/docs/bench/fn.connect_client.html b/pr/443/docs/bench/fn.connect_client.html index 1e6015731..1953d8b68 100644 --- a/pr/443/docs/bench/fn.connect_client.html +++ b/pr/443/docs/bench/fn.connect_client.html @@ -2,5 +2,5 @@ server_addr: SocketAddr, server_cert: CertificateDer<'_>, opt: Opt, -) -> Result<(Endpoint, Connection)>
Expand description

Create a client endpoint and client connection

+) -> Result<(Endpoint, Connection)>
Expand description

Create a client endpoint and client connection

\ No newline at end of file diff --git a/pr/443/docs/bench/fn.drain_stream.html b/pr/443/docs/bench/fn.drain_stream.html index 65117a5c3..5cd0ee437 100644 --- a/pr/443/docs/bench/fn.drain_stream.html +++ b/pr/443/docs/bench/fn.drain_stream.html @@ -1,4 +1,4 @@ drain_stream in bench - Rust

drain_stream

Function drain_stream 

Source
pub async fn drain_stream(
-    stream: RecvStream,
+    stream: RecvStream,
     read_unordered: bool,
 ) -> Result<usize>
\ No newline at end of file diff --git a/pr/443/docs/bench/fn.send_data_on_stream.html b/pr/443/docs/bench/fn.send_data_on_stream.html index 6dfa3ddf9..e61f81d0b 100644 --- a/pr/443/docs/bench/fn.send_data_on_stream.html +++ b/pr/443/docs/bench/fn.send_data_on_stream.html @@ -1,4 +1,4 @@ send_data_on_stream in bench - Rust

send_data_on_stream

Function send_data_on_stream 

Source
pub async fn send_data_on_stream(
-    stream: &mut SendStream,
+    stream: &mut SendStream,
     stream_size: u64,
 ) -> Result<()>
\ No newline at end of file diff --git a/pr/443/docs/bench/fn.server_endpoint.html b/pr/443/docs/bench/fn.server_endpoint.html index 771d00034..605fd3cf2 100644 --- a/pr/443/docs/bench/fn.server_endpoint.html +++ b/pr/443/docs/bench/fn.server_endpoint.html @@ -3,5 +3,5 @@ cert: CertificateDer<'static>, key: PrivateKeyDer<'static>, opt: &Opt, -) -> (SocketAddr, Endpoint)
Expand description

Creates a server endpoint which runs on the given runtime

+) -> (SocketAddr, Endpoint)
Expand description

Creates a server endpoint which runs on the given runtime

\ No newline at end of file diff --git a/pr/443/docs/iroh_quinn/enum.ConnectionError.html b/pr/443/docs/iroh_quinn/enum.ConnectionError.html index 00f8bdf7d..1aabc0d87 100644 --- a/pr/443/docs/iroh_quinn/enum.ConnectionError.html +++ b/pr/443/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/443/docs/iroh_quinn/trait.NetworkChangeHint.html b/pr/443/docs/iroh_quinn/trait.NetworkChangeHint.html index 37ace2652..56f7c104d 100644 --- a/pr/443/docs/iroh_quinn/trait.NetworkChangeHint.html +++ b/pr/443/docs/iroh_quinn/trait.NetworkChangeHint.html @@ -1,4 +1,4 @@ -NetworkChangeHint in iroh_quinn - Rust

NetworkChangeHint

Trait NetworkChangeHint 

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

NetworkChangeHint

Trait NetworkChangeHint 

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

Hints when the caller identifies a network change.

-

Required Methods§

Source

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

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

+

Required Methods§

Source

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

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

After network changes, paths may not be recoverable. In this case, waiting for the path to become idle may take longer than what is desirable. If Self::is_path_recoverable returns false, a multipath-enabled, client-side connection will establish a new path to diff --git a/pr/443/docs/iroh_quinn_proto/enum.ClosePathError.html b/pr/443/docs/iroh_quinn_proto/enum.ClosePathError.html index 5a259a69b..3364bd904 100644 --- a/pr/443/docs/iroh_quinn_proto/enum.ClosePathError.html +++ b/pr/443/docs/iroh_quinn_proto/enum.ClosePathError.html @@ -1,4 +1,4 @@ -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 {
     MultipathNotNegotiated,
     ClosedPath,
     LastOpenPath,
@@ -6,8 +6,8 @@
 

Variants§

§

MultipathNotNegotiated

Multipath is not negotiated

§

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/443/docs/iroh_quinn_proto/enum.ConnectionError.html b/pr/443/docs/iroh_quinn_proto/enum.ConnectionError.html index 2d3d5bb6b..33c8a460c 100644 --- a/pr/443/docs/iroh_quinn_proto/enum.ConnectionError.html +++ b/pr/443/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/443/docs/iroh_quinn_proto/enum.Event.html b/pr/443/docs/iroh_quinn_proto/enum.Event.html index 585aac3f6..bdbca4c61 100644 --- a/pr/443/docs/iroh_quinn_proto/enum.Event.html +++ b/pr/443/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/443/docs/iroh_quinn_proto/enum.PathAbandonReason.html b/pr/443/docs/iroh_quinn_proto/enum.PathAbandonReason.html index 5f5e42baa..3a851acbd 100644 --- a/pr/443/docs/iroh_quinn_proto/enum.PathAbandonReason.html +++ b/pr/443/docs/iroh_quinn_proto/enum.PathAbandonReason.html @@ -1,4 +1,4 @@ -PathAbandonReason in iroh_quinn_proto - Rust

PathAbandonReason

Enum PathAbandonReason 

Source
pub enum PathAbandonReason {
+PathAbandonReason in iroh_quinn_proto - Rust

PathAbandonReason

Enum PathAbandonReason 

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

NatTraversalRoundEnded

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

§

RemoteAbandoned

The remote closed the path.

Fields

§error_code: VarInt

The error that was sent with the abandon frame.

-

Trait Implementations§

Source§

impl Clone for PathAbandonReason

Source§

fn clone(&self) -> PathAbandonReason

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for PathAbandonReason

Source§

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

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

impl PartialEq for PathAbandonReason

Source§

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

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

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

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

impl Eq for PathAbandonReason

Source§

impl StructuralPartialEq for PathAbandonReason

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for PathAbandonReason

Source§

fn clone(&self) -> PathAbandonReason

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for PathAbandonReason

Source§

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

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

impl PartialEq for PathAbandonReason

Source§

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

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

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

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

impl Eq for PathAbandonReason

Source§

impl StructuralPartialEq for PathAbandonReason

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/443/docs/iroh_quinn_proto/enum.PathError.html b/pr/443/docs/iroh_quinn_proto/enum.PathError.html index 6111c6966..794c31357 100644 --- a/pr/443/docs/iroh_quinn_proto/enum.PathError.html +++ b/pr/443/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/443/docs/iroh_quinn_proto/enum.PathEvent.html b/pr/443/docs/iroh_quinn_proto/enum.PathEvent.html index 0dffe2446..74f25571d 100644 --- a/pr/443/docs/iroh_quinn_proto/enum.PathEvent.html +++ b/pr/443/docs/iroh_quinn_proto/enum.PathEvent.html @@ -1,4 +1,4 @@ -PathEvent in iroh_quinn_proto - Rust

PathEvent

Enum PathEvent 

Source
pub enum PathEvent {
+PathEvent in iroh_quinn_proto - Rust

PathEvent

Enum PathEvent 

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

Fields

§id: PathId

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

§addr: SocketAddr

The address observed by the remote over this path

-

Trait Implementations§

Source§

impl Clone for PathEvent

Source§

fn clone(&self) -> PathEvent

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for PathEvent

Source§

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

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

impl From<PathEvent> for Event

Source§

fn from(source: PathEvent) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for PathEvent

Source§

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

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

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

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

impl Eq for PathEvent

Source§

impl StructuralPartialEq for PathEvent

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for PathEvent

Source§

fn clone(&self) -> PathEvent

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for PathEvent

Source§

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

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

impl From<PathEvent> for Event

Source§

fn from(source: PathEvent) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for PathEvent

Source§

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

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

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

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

impl Eq for PathEvent

Source§

impl StructuralPartialEq for PathEvent

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/443/docs/iroh_quinn_proto/enum.PathStatus.html b/pr/443/docs/iroh_quinn_proto/enum.PathStatus.html index 6b182a25d..789e4628c 100644 --- a/pr/443/docs/iroh_quinn_proto/enum.PathStatus.html +++ b/pr/443/docs/iroh_quinn_proto/enum.PathStatus.html @@ -1,4 +1,4 @@ -PathStatus in iroh_quinn_proto - Rust

PathStatus

Enum PathStatus 

Source
pub enum PathStatus {
+PathStatus in iroh_quinn_proto - Rust

PathStatus

Enum PathStatus 

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

The QUIC-MULTIPATH path status

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

§

Backup

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

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

-

Trait Implementations§

Source§

impl Clone for PathStatus

Source§

fn clone(&self) -> PathStatus

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for PathStatus

Source§

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

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

impl Default for PathStatus

Source§

fn default() -> PathStatus

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

impl PartialEq for PathStatus

Source§

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

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

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

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

impl Copy for PathStatus

Source§

impl Eq for PathStatus

Source§

impl StructuralPartialEq for PathStatus

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for PathStatus

Source§

fn clone(&self) -> PathStatus

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for PathStatus

Source§

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

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

impl Default for PathStatus

Source§

fn default() -> PathStatus

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

impl PartialEq for PathStatus

Source§

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

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

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

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

impl Copy for PathStatus

Source§

impl Eq for PathStatus

Source§

impl StructuralPartialEq for PathStatus

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

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

SetPathStatusError

Enum SetPathStatusError 

Source
pub enum SetPathStatusError {
+SetPathStatusError in iroh_quinn_proto - Rust

SetPathStatusError

Enum SetPathStatusError 

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

Error from setting path status

Variants§

§

ClosedPath

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

§

MultipathNotNegotiated

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

-

Trait Implementations§

Source§

impl Clone for SetPathStatusError

Source§

fn clone(&self) -> SetPathStatusError

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for SetPathStatusError

Source§

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

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

impl Display for SetPathStatusError

Source§

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

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

impl Error for SetPathStatusError

1.30.0 · Source§

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

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

fn description(&self) -> &str

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

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

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

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

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

impl PartialEq for SetPathStatusError

Source§

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

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

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

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

impl Eq for SetPathStatusError

Source§

impl StructuralPartialEq for SetPathStatusError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for SetPathStatusError

Source§

fn clone(&self) -> SetPathStatusError

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for SetPathStatusError

Source§

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

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

impl Display for SetPathStatusError

Source§

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

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

impl Error for SetPathStatusError

1.30.0 · Source§

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

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

fn description(&self) -> &str

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

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

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

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

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

impl PartialEq for SetPathStatusError

Source§

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

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

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

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

impl Eq for SetPathStatusError

Source§

impl StructuralPartialEq for SetPathStatusError

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/443/docs/iroh_quinn_proto/struct.ClosedPath.html b/pr/443/docs/iroh_quinn_proto/struct.ClosedPath.html index 49a97d544..cccfaaa60 100644 --- a/pr/443/docs/iroh_quinn_proto/struct.ClosedPath.html +++ b/pr/443/docs/iroh_quinn_proto/struct.ClosedPath.html @@ -1,6 +1,6 @@ -ClosedPath in iroh_quinn_proto - Rust

ClosedPath

Struct ClosedPath 

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

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

-

Trait Implementations§

Source§

impl Clone for ClosedPath

Source§

fn clone(&self) -> ClosedPath

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosedPath

Source§

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

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

impl Default for ClosedPath

Source§

fn default() -> ClosedPath

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

impl Display for ClosedPath

Source§

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

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

impl Error for ClosedPath

1.30.0 · Source§

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

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

fn description(&self) -> &str

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

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

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

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

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

impl PartialEq for ClosedPath

Source§

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

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

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

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

impl Eq for ClosedPath

Source§

impl StructuralPartialEq for ClosedPath

Auto Trait Implementations§

Blanket Implementations§

Source§

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

ClosedPath

Struct ClosedPath 

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

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

+

Trait Implementations§

Source§

impl Clone for ClosedPath

Source§

fn clone(&self) -> ClosedPath

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosedPath

Source§

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

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

impl Default for ClosedPath

Source§

fn default() -> ClosedPath

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

impl Display for ClosedPath

Source§

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

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

impl Error for ClosedPath

1.30.0 · Source§

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

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

fn description(&self) -> &str

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

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

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

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

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

impl PartialEq for ClosedPath

Source§

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

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

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

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

impl Eq for ClosedPath

Source§

impl StructuralPartialEq for ClosedPath

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/443/docs/iroh_quinn_proto/struct.Connection.html b/pr/443/docs/iroh_quinn_proto/struct.Connection.html index 6bada2b8f..6c3e9feb0 100644 --- a/pr/443/docs/iroh_quinn_proto/struct.Connection.html +++ b/pr/443/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
  • @@ -194,7 +194,7 @@ decrement by one for each time a remotely initiated stream of matching direction
Source

pub fn is_multipath_negotiated(&self) -> bool

Whether the Multipath for QUIC extension is enabled.

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

-
Source

pub fn handle_network_change( +

Source

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

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

-

Source

pub fn current_mtu(&self) -> u16

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

Source

pub fn current_mtu(&self) -> u16

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

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

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

-
Source

pub fn add_nat_traversal_address( +

Source

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

Add addresses the local endpoint considers are reachable for nat traversal.

-
Source

pub fn remove_nat_traversal_address( +

Source

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

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

Addresses not present in the set will be silently ignored.

-
Source

pub fn get_local_nat_traversal_addresses( +

Source

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

Get the current local nat traversal addresses

-
Source

pub fn get_remote_nat_traversal_addresses( +

Source

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

Get the currently advertised nat traversal addresses by the server

-
Source

pub fn initiate_nat_traversal_round( +

Source

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

Initiates a new nat traversal round

@@ -238,7 +238,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§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Debug for Connection

Source§

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

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

diff --git a/pr/443/docs/iroh_quinn_proto/struct.MultipathNotNegotiated.html b/pr/443/docs/iroh_quinn_proto/struct.MultipathNotNegotiated.html index fc41e3a3f..e10fb5370 100644 --- a/pr/443/docs/iroh_quinn_proto/struct.MultipathNotNegotiated.html +++ b/pr/443/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/443/docs/iroh_quinn_proto/struct.RttEstimator.html b/pr/443/docs/iroh_quinn_proto/struct.RttEstimator.html index 4a6245107..80e023c54 100644 --- a/pr/443/docs/iroh_quinn_proto/struct.RttEstimator.html +++ b/pr/443/docs/iroh_quinn_proto/struct.RttEstimator.html @@ -1,10 +1,10 @@ -RttEstimator in iroh_quinn_proto - Rust

RttEstimator

Struct RttEstimator 

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

RTT estimation for a particular network path

-

Implementations§

Source§

impl RttEstimator

Source

pub fn get(&self) -> Duration

The current best RTT estimation.

-
Source

pub fn conservative(&self) -> Duration

Conservative estimate of RTT

+RttEstimator in iroh_quinn_proto - Rust

RttEstimator

Struct RttEstimator 

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

RTT estimation for a particular network path

+

Implementations§

Source§

impl RttEstimator

Source

pub fn get(&self) -> Duration

The current best RTT estimation.

+
Source

pub fn conservative(&self) -> Duration

Conservative estimate of RTT

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

-
Source

pub fn min(&self) -> Duration

Minimum RTT registered so far for this estimator.

-

Trait Implementations§

Source§

impl Clone for RttEstimator

Source§

fn clone(&self) -> RttEstimator

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for RttEstimator

Source§

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

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

impl Copy for RttEstimator

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Source

pub fn min(&self) -> Duration

Minimum RTT registered so far for this estimator.

+

Trait Implementations§

Source§

impl Clone for RttEstimator

Source§

fn clone(&self) -> RttEstimator

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

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

Performs copy-assignment from source. Read more
Source§

impl Debug for RttEstimator

Source§

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

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

impl Copy for RttEstimator

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/443/docs/iroh_quinn_proto/struct.TransportError.html b/pr/443/docs/iroh_quinn_proto/struct.TransportError.html index bffaaebaa..7e91dbe6f 100644 --- a/pr/443/docs/iroh_quinn_proto/struct.TransportError.html +++ b/pr/443/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/443/docs/iroh_quinn_proto/trait.NetworkChangeHint.html b/pr/443/docs/iroh_quinn_proto/trait.NetworkChangeHint.html index f615b7fa2..1e75b09e0 100644 --- a/pr/443/docs/iroh_quinn_proto/trait.NetworkChangeHint.html +++ b/pr/443/docs/iroh_quinn_proto/trait.NetworkChangeHint.html @@ -1,4 +1,4 @@ -NetworkChangeHint in iroh_quinn_proto - Rust

NetworkChangeHint

Trait NetworkChangeHint 

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

NetworkChangeHint

Trait NetworkChangeHint 

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

Hints when the caller identifies a network change.

-

Required Methods§

Source

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

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

+

Required Methods§

Source

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

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

After network changes, paths may not be recoverable. In this case, waiting for the path to become idle may take longer than what is desirable. If Self::is_path_recoverable returns false, a multipath-enabled, client-side connection will establish a new path to diff --git a/pr/443/docs/src/iroh_quinn_proto/connection/mod.rs.html b/pr/443/docs/src/iroh_quinn_proto/connection/mod.rs.html index f9d2f2438..cabb01d38 100644 --- a/pr/443/docs/src/iroh_quinn_proto/connection/mod.rs.html +++ b/pr/443/docs/src/iroh_quinn_proto/connection/mod.rs.html @@ -4696,2495 +4696,2500 @@ 4696 self.qlog.with_time(now), 4697 ); 4698 } -4699 Ignored => debug!(%response, "ignoring valid PATH_RESPONSE"), -4700 Unknown => debug!(%response, "ignoring invalid PATH_RESPONSE"), -4701 } -4702 } -4703 Frame::MaxData(frame::MaxData(bytes)) => { -4704 self.streams.received_max_data(bytes); -4705 } -4706 Frame::MaxStreamData(frame::MaxStreamData { id, offset }) => { -4707 self.streams.received_max_stream_data(id, offset)?; -4708 } -4709 Frame::MaxStreams(frame::MaxStreams { dir, count }) => { -4710 self.streams.received_max_streams(dir, count)?; -4711 } -4712 Frame::ResetStream(frame) => { -4713 if self.streams.received_reset(frame)?.should_transmit() { -4714 self.spaces[SpaceId::Data].pending.max_data = true; -4715 } +4699 Ignored { +4700 sent_on, +4701 current_path, +4702 } => { +4703 debug!(%sent_on, %current_path, %response, "ignoring valid PATH_RESPONSE") +4704 } +4705 Unknown => debug!(%response, "ignoring invalid PATH_RESPONSE"), +4706 } +4707 } +4708 Frame::MaxData(frame::MaxData(bytes)) => { +4709 self.streams.received_max_data(bytes); +4710 } +4711 Frame::MaxStreamData(frame::MaxStreamData { id, offset }) => { +4712 self.streams.received_max_stream_data(id, offset)?; +4713 } +4714 Frame::MaxStreams(frame::MaxStreams { dir, count }) => { +4715 self.streams.received_max_streams(dir, count)?; 4716 } -4717 Frame::DataBlocked(DataBlocked(offset)) => { -4718 debug!(offset, "peer claims to be blocked at connection level"); -4719 } -4720 Frame::StreamDataBlocked(StreamDataBlocked { id, offset }) => { -4721 if id.initiator() == self.side.side() && id.dir() == Dir::Uni { -4722 debug!("got STREAM_DATA_BLOCKED on send-only {}", id); -4723 return Err(TransportError::STREAM_STATE_ERROR( -4724 "STREAM_DATA_BLOCKED on send-only stream", -4725 )); -4726 } -4727 debug!( -4728 stream = %id, -4729 offset, "peer claims to be blocked at stream level" -4730 ); -4731 } -4732 Frame::StreamsBlocked(StreamsBlocked { dir, limit }) => { -4733 if limit > MAX_STREAM_COUNT { -4734 return Err(TransportError::FRAME_ENCODING_ERROR( -4735 "unrepresentable stream limit", -4736 )); -4737 } -4738 debug!( -4739 "peer claims to be blocked opening more than {} {} streams", -4740 limit, dir -4741 ); -4742 } -4743 Frame::StopSending(frame::StopSending { id, error_code }) => { -4744 if id.initiator() != self.side.side() { -4745 if id.dir() == Dir::Uni { -4746 debug!("got STOP_SENDING on recv-only {}", id); -4747 return Err(TransportError::STREAM_STATE_ERROR( -4748 "STOP_SENDING on recv-only stream", -4749 )); -4750 } -4751 } else if self.streams.is_local_unopened(id) { -4752 return Err(TransportError::STREAM_STATE_ERROR( -4753 "STOP_SENDING on unopened stream", -4754 )); -4755 } -4756 self.streams.received_stop_sending(id, error_code); -4757 } -4758 Frame::RetireConnectionId(frame::RetireConnectionId { path_id, sequence }) => { -4759 if let Some(ref path_id) = path_id { -4760 span.record("path", tracing::field::display(&path_id)); -4761 } -4762 let path_id = path_id.unwrap_or_default(); -4763 match self.local_cid_state.get_mut(&path_id) { -4764 None => error!(?path_id, "RETIRE_CONNECTION_ID for unknown path"), -4765 Some(cid_state) => { -4766 let allow_more_cids = cid_state -4767 .on_cid_retirement(sequence, self.peer_params.issue_cids_limit())?; -4768 -4769 // If the path has closed, we do not issue more CIDs for this path -4770 // For details see https://www.ietf.org/archive/id/draft-ietf-quic-multipath-17.html#section-3.2.2 -4771 // > an endpoint SHOULD provide new connection IDs for that path, if still open, using PATH_NEW_CONNECTION_ID frames. -4772 let has_path = !self.abandoned_paths.contains(&path_id); -4773 let allow_more_cids = allow_more_cids && has_path; -4774 -4775 self.endpoint_events -4776 .push_back(EndpointEventInner::RetireConnectionId( -4777 now, -4778 path_id, -4779 sequence, -4780 allow_more_cids, -4781 )); -4782 } -4783 } -4784 } -4785 Frame::NewConnectionId(frame) => { -4786 let path_id = if let Some(path_id) = frame.path_id { -4787 if !self.is_multipath_negotiated() { -4788 return Err(TransportError::PROTOCOL_VIOLATION( -4789 "received PATH_NEW_CONNECTION_ID frame when multipath was not negotiated", -4790 )); -4791 } -4792 if path_id > self.local_max_path_id { +4717 Frame::ResetStream(frame) => { +4718 if self.streams.received_reset(frame)?.should_transmit() { +4719 self.spaces[SpaceId::Data].pending.max_data = true; +4720 } +4721 } +4722 Frame::DataBlocked(DataBlocked(offset)) => { +4723 debug!(offset, "peer claims to be blocked at connection level"); +4724 } +4725 Frame::StreamDataBlocked(StreamDataBlocked { id, offset }) => { +4726 if id.initiator() == self.side.side() && id.dir() == Dir::Uni { +4727 debug!("got STREAM_DATA_BLOCKED on send-only {}", id); +4728 return Err(TransportError::STREAM_STATE_ERROR( +4729 "STREAM_DATA_BLOCKED on send-only stream", +4730 )); +4731 } +4732 debug!( +4733 stream = %id, +4734 offset, "peer claims to be blocked at stream level" +4735 ); +4736 } +4737 Frame::StreamsBlocked(StreamsBlocked { dir, limit }) => { +4738 if limit > MAX_STREAM_COUNT { +4739 return Err(TransportError::FRAME_ENCODING_ERROR( +4740 "unrepresentable stream limit", +4741 )); +4742 } +4743 debug!( +4744 "peer claims to be blocked opening more than {} {} streams", +4745 limit, dir +4746 ); +4747 } +4748 Frame::StopSending(frame::StopSending { id, error_code }) => { +4749 if id.initiator() != self.side.side() { +4750 if id.dir() == Dir::Uni { +4751 debug!("got STOP_SENDING on recv-only {}", id); +4752 return Err(TransportError::STREAM_STATE_ERROR( +4753 "STOP_SENDING on recv-only stream", +4754 )); +4755 } +4756 } else if self.streams.is_local_unopened(id) { +4757 return Err(TransportError::STREAM_STATE_ERROR( +4758 "STOP_SENDING on unopened stream", +4759 )); +4760 } +4761 self.streams.received_stop_sending(id, error_code); +4762 } +4763 Frame::RetireConnectionId(frame::RetireConnectionId { path_id, sequence }) => { +4764 if let Some(ref path_id) = path_id { +4765 span.record("path", tracing::field::display(&path_id)); +4766 } +4767 let path_id = path_id.unwrap_or_default(); +4768 match self.local_cid_state.get_mut(&path_id) { +4769 None => error!(?path_id, "RETIRE_CONNECTION_ID for unknown path"), +4770 Some(cid_state) => { +4771 let allow_more_cids = cid_state +4772 .on_cid_retirement(sequence, self.peer_params.issue_cids_limit())?; +4773 +4774 // If the path has closed, we do not issue more CIDs for this path +4775 // For details see https://www.ietf.org/archive/id/draft-ietf-quic-multipath-17.html#section-3.2.2 +4776 // > an endpoint SHOULD provide new connection IDs for that path, if still open, using PATH_NEW_CONNECTION_ID frames. +4777 let has_path = !self.abandoned_paths.contains(&path_id); +4778 let allow_more_cids = allow_more_cids && has_path; +4779 +4780 self.endpoint_events +4781 .push_back(EndpointEventInner::RetireConnectionId( +4782 now, +4783 path_id, +4784 sequence, +4785 allow_more_cids, +4786 )); +4787 } +4788 } +4789 } +4790 Frame::NewConnectionId(frame) => { +4791 let path_id = if let Some(path_id) = frame.path_id { +4792 if !self.is_multipath_negotiated() { 4793 return Err(TransportError::PROTOCOL_VIOLATION( -4794 "PATH_NEW_CONNECTION_ID contains path_id exceeding current max", +4794 "received PATH_NEW_CONNECTION_ID frame when multipath was not negotiated", 4795 )); 4796 } -4797 path_id -4798 } else { -4799 PathId::ZERO -4800 }; -4801 -4802 if let Some(ref path_id) = frame.path_id { -4803 span.record("path", tracing::field::display(&path_id)); -4804 } -4805 -4806 if self.abandoned_paths.contains(&path_id) { -4807 trace!("ignoring issued CID for abandoned path"); -4808 continue; +4797 if path_id > self.local_max_path_id { +4798 return Err(TransportError::PROTOCOL_VIOLATION( +4799 "PATH_NEW_CONNECTION_ID contains path_id exceeding current max", +4800 )); +4801 } +4802 path_id +4803 } else { +4804 PathId::ZERO +4805 }; +4806 +4807 if let Some(ref path_id) = frame.path_id { +4808 span.record("path", tracing::field::display(&path_id)); 4809 } -4810 let remote_cids = self -4811 .remote_cids -4812 .entry(path_id) -4813 .or_insert_with(|| CidQueue::new(frame.id)); -4814 if remote_cids.active().is_empty() { -4815 return Err(TransportError::PROTOCOL_VIOLATION( -4816 "NEW_CONNECTION_ID when CIDs aren't in use", -4817 )); -4818 } -4819 if frame.retire_prior_to > frame.sequence { +4810 +4811 if self.abandoned_paths.contains(&path_id) { +4812 trace!("ignoring issued CID for abandoned path"); +4813 continue; +4814 } +4815 let remote_cids = self +4816 .remote_cids +4817 .entry(path_id) +4818 .or_insert_with(|| CidQueue::new(frame.id)); +4819 if remote_cids.active().is_empty() { 4820 return Err(TransportError::PROTOCOL_VIOLATION( -4821 "NEW_CONNECTION_ID retiring unissued CIDs", +4821 "NEW_CONNECTION_ID when CIDs aren't in use", 4822 )); 4823 } -4824 -4825 use crate::cid_queue::InsertError; -4826 match remote_cids.insert(frame) { -4827 Ok(None) if self.path(path_id).is_none() => { -4828 // if this gives us CIDs to open a new path and a nat traversal attempt -4829 // is underway we could try to probe a pending remote -4830 self.continue_nat_traversal_round(now); -4831 } -4832 Ok(None) => {} -4833 Ok(Some((retired, reset_token))) => { -4834 let pending_retired = -4835 &mut self.spaces[SpaceId::Data].pending.retire_cids; -4836 /// Ensure `pending_retired` cannot grow without bound. Limit is -4837 /// somewhat arbitrary but very permissive. -4838 const MAX_PENDING_RETIRED_CIDS: u64 = CidQueue::LEN as u64 * 10; -4839 // We don't bother counting in-flight frames because those are bounded -4840 // by congestion control. -4841 if (pending_retired.len() as u64) -4842 .saturating_add(retired.end.saturating_sub(retired.start)) -4843 > MAX_PENDING_RETIRED_CIDS -4844 { -4845 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR( -4846 "queued too many retired CIDs", -4847 )); -4848 } -4849 pending_retired.extend(retired.map(|seq| (path_id, seq))); -4850 // TODO(matheus23): Reset token for a remote or a full 4-tuple? -4851 self.set_reset_token(path_id, network_path.remote, reset_token); -4852 } -4853 Err(InsertError::ExceedsLimit) => { -4854 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR("")); -4855 } -4856 Err(InsertError::Retired) => { -4857 trace!("discarding already-retired"); -4858 // RETIRE_CONNECTION_ID might not have been previously sent if e.g. a -4859 // range of connection IDs larger than the active connection ID limit -4860 // was retired all at once via retire_prior_to. -4861 self.spaces[SpaceId::Data] -4862 .pending -4863 .retire_cids -4864 .push((path_id, frame.sequence)); -4865 continue; -4866 } -4867 }; -4868 -4869 if self.side.is_server() -4870 && path_id == PathId::ZERO -4871 && self -4872 .remote_cids -4873 .get(&PathId::ZERO) -4874 .map(|cids| cids.active_seq() == 0) -4875 .unwrap_or_default() -4876 { -4877 // We're a server still using the initial remote CID for the client, so -4878 // let's switch immediately to enable clientside stateless resets. -4879 self.update_remote_cid(PathId::ZERO); -4880 } -4881 } -4882 Frame::NewToken(NewToken { token }) => { -4883 let ConnectionSide::Client { -4884 token_store, -4885 server_name, -4886 .. -4887 } = &self.side -4888 else { -4889 return Err(TransportError::PROTOCOL_VIOLATION("client sent NEW_TOKEN")); -4890 }; -4891 if token.is_empty() { -4892 return Err(TransportError::FRAME_ENCODING_ERROR("empty token")); -4893 } -4894 trace!("got new token"); -4895 token_store.insert(server_name, token); -4896 } -4897 Frame::Datagram(datagram) => { -4898 if self -4899 .datagrams -4900 .received(datagram, &self.config.datagram_receive_buffer_size)? -4901 { -4902 self.events.push_back(Event::DatagramReceived); -4903 } -4904 } -4905 Frame::AckFrequency(ack_frequency) => { -4906 // This frame can only be sent in the Data space -4907 -4908 if !self.ack_frequency.ack_frequency_received(&ack_frequency)? { -4909 // The AckFrequency frame is stale (we have already received a more -4910 // recent one) -4911 continue; -4912 } -4913 -4914 // Update the params for all of our paths -4915 for (path_id, space) in self.spaces[SpaceId::Data].number_spaces.iter_mut() { -4916 space.pending_acks.set_ack_frequency_params(&ack_frequency); -4917 -4918 // Our `max_ack_delay` has been updated, so we may need to adjust -4919 // its associated timeout -4920 if let Some(timeout) = space -4921 .pending_acks -4922 .max_ack_delay_timeout(self.ack_frequency.max_ack_delay) -4923 { -4924 self.timers.set( -4925 Timer::PerPath(*path_id, PathTimer::MaxAckDelay), -4926 timeout, -4927 self.qlog.with_time(now), -4928 ); -4929 } -4930 } -4931 } -4932 Frame::ImmediateAck => { -4933 // This frame can only be sent in the Data space -4934 for pns in self.spaces[SpaceId::Data].iter_paths_mut() { -4935 pns.pending_acks.set_immediate_ack_required(); -4936 } -4937 } -4938 Frame::HandshakeDone => { -4939 if self.side.is_server() { -4940 return Err(TransportError::PROTOCOL_VIOLATION( -4941 "client sent HANDSHAKE_DONE", -4942 )); -4943 } -4944 if self.crypto_state.has_keys(EncryptionLevel::Handshake) { -4945 self.discard_space(now, SpaceKind::Handshake); -4946 } -4947 self.events.push_back(Event::HandshakeConfirmed); -4948 trace!("handshake confirmed"); -4949 } -4950 Frame::ObservedAddr(observed) => { -4951 // check if params allows the peer to send report and this node to receive it -4952 trace!(seq_no = %observed.seq_no, ip = %observed.ip, port = observed.port); -4953 if !self -4954 .peer_params -4955 .address_discovery_role -4956 .should_report(&self.config.address_discovery_role) -4957 { -4958 return Err(TransportError::PROTOCOL_VIOLATION( -4959 "received OBSERVED_ADDRESS frame when not negotiated", -4960 )); -4961 } -4962 // must only be sent in data space -4963 if packet.header.space() != SpaceKind::Data { -4964 return Err(TransportError::PROTOCOL_VIOLATION( -4965 "OBSERVED_ADDRESS frame outside data space", -4966 )); -4967 } -4968 -4969 let path = self.path_data_mut(path_id); -4970 if network_path == path.network_path { -4971 if let Some(updated) = path.update_observed_addr_report(observed) -4972 && path.open -4973 { -4974 self.events.push_back(Event::Path(PathEvent::ObservedAddr { -4975 id: path_id, -4976 addr: updated, -4977 })); -4978 // otherwise the event is reported when the path is deemed open -4979 } -4980 } else { -4981 // include in migration -4982 migration_observed_addr = Some(observed) -4983 } -4984 } -4985 Frame::PathAbandon(frame::PathAbandon { -4986 path_id, -4987 error_code, -4988 }) => { -4989 span.record("path", tracing::field::display(&path_id)); -4990 match self.close_path_inner( -4991 now, -4992 path_id, -4993 PathAbandonReason::RemoteAbandoned { -4994 error_code: error_code.into(), -4995 }, -4996 ) { -4997 Ok(()) => { -4998 trace!("peer abandoned path"); -4999 } -5000 Err(ClosePathError::LastOpenPath) => { -5001 trace!("peer abandoned last path, closing connection"); -5002 return Err(TransportError::NO_VIABLE_PATH( -5003 "last path abandoned by peer", -5004 )); -5005 } -5006 Err(ClosePathError::ClosedPath) => { -5007 trace!("peer abandoned already closed path"); -5008 } -5009 Err(ClosePathError::MultipathNotNegotiated) => { -5010 return Err(TransportError::PROTOCOL_VIOLATION( -5011 "received PATH_ABANDON frame when multipath was not negotiated", -5012 )); +4824 if frame.retire_prior_to > frame.sequence { +4825 return Err(TransportError::PROTOCOL_VIOLATION( +4826 "NEW_CONNECTION_ID retiring unissued CIDs", +4827 )); +4828 } +4829 +4830 use crate::cid_queue::InsertError; +4831 match remote_cids.insert(frame) { +4832 Ok(None) if self.path(path_id).is_none() => { +4833 // if this gives us CIDs to open a new path and a nat traversal attempt +4834 // is underway we could try to probe a pending remote +4835 self.continue_nat_traversal_round(now); +4836 } +4837 Ok(None) => {} +4838 Ok(Some((retired, reset_token))) => { +4839 let pending_retired = +4840 &mut self.spaces[SpaceId::Data].pending.retire_cids; +4841 /// Ensure `pending_retired` cannot grow without bound. Limit is +4842 /// somewhat arbitrary but very permissive. +4843 const MAX_PENDING_RETIRED_CIDS: u64 = CidQueue::LEN as u64 * 10; +4844 // We don't bother counting in-flight frames because those are bounded +4845 // by congestion control. +4846 if (pending_retired.len() as u64) +4847 .saturating_add(retired.end.saturating_sub(retired.start)) +4848 > MAX_PENDING_RETIRED_CIDS +4849 { +4850 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR( +4851 "queued too many retired CIDs", +4852 )); +4853 } +4854 pending_retired.extend(retired.map(|seq| (path_id, seq))); +4855 // TODO(matheus23): Reset token for a remote or a full 4-tuple? +4856 self.set_reset_token(path_id, network_path.remote, reset_token); +4857 } +4858 Err(InsertError::ExceedsLimit) => { +4859 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR("")); +4860 } +4861 Err(InsertError::Retired) => { +4862 trace!("discarding already-retired"); +4863 // RETIRE_CONNECTION_ID might not have been previously sent if e.g. a +4864 // range of connection IDs larger than the active connection ID limit +4865 // was retired all at once via retire_prior_to. +4866 self.spaces[SpaceId::Data] +4867 .pending +4868 .retire_cids +4869 .push((path_id, frame.sequence)); +4870 continue; +4871 } +4872 }; +4873 +4874 if self.side.is_server() +4875 && path_id == PathId::ZERO +4876 && self +4877 .remote_cids +4878 .get(&PathId::ZERO) +4879 .map(|cids| cids.active_seq() == 0) +4880 .unwrap_or_default() +4881 { +4882 // We're a server still using the initial remote CID for the client, so +4883 // let's switch immediately to enable clientside stateless resets. +4884 self.update_remote_cid(PathId::ZERO); +4885 } +4886 } +4887 Frame::NewToken(NewToken { token }) => { +4888 let ConnectionSide::Client { +4889 token_store, +4890 server_name, +4891 .. +4892 } = &self.side +4893 else { +4894 return Err(TransportError::PROTOCOL_VIOLATION("client sent NEW_TOKEN")); +4895 }; +4896 if token.is_empty() { +4897 return Err(TransportError::FRAME_ENCODING_ERROR("empty token")); +4898 } +4899 trace!("got new token"); +4900 token_store.insert(server_name, token); +4901 } +4902 Frame::Datagram(datagram) => { +4903 if self +4904 .datagrams +4905 .received(datagram, &self.config.datagram_receive_buffer_size)? +4906 { +4907 self.events.push_back(Event::DatagramReceived); +4908 } +4909 } +4910 Frame::AckFrequency(ack_frequency) => { +4911 // This frame can only be sent in the Data space +4912 +4913 if !self.ack_frequency.ack_frequency_received(&ack_frequency)? { +4914 // The AckFrequency frame is stale (we have already received a more +4915 // recent one) +4916 continue; +4917 } +4918 +4919 // Update the params for all of our paths +4920 for (path_id, space) in self.spaces[SpaceId::Data].number_spaces.iter_mut() { +4921 space.pending_acks.set_ack_frequency_params(&ack_frequency); +4922 +4923 // Our `max_ack_delay` has been updated, so we may need to adjust +4924 // its associated timeout +4925 if let Some(timeout) = space +4926 .pending_acks +4927 .max_ack_delay_timeout(self.ack_frequency.max_ack_delay) +4928 { +4929 self.timers.set( +4930 Timer::PerPath(*path_id, PathTimer::MaxAckDelay), +4931 timeout, +4932 self.qlog.with_time(now), +4933 ); +4934 } +4935 } +4936 } +4937 Frame::ImmediateAck => { +4938 // This frame can only be sent in the Data space +4939 for pns in self.spaces[SpaceId::Data].iter_paths_mut() { +4940 pns.pending_acks.set_immediate_ack_required(); +4941 } +4942 } +4943 Frame::HandshakeDone => { +4944 if self.side.is_server() { +4945 return Err(TransportError::PROTOCOL_VIOLATION( +4946 "client sent HANDSHAKE_DONE", +4947 )); +4948 } +4949 if self.crypto_state.has_keys(EncryptionLevel::Handshake) { +4950 self.discard_space(now, SpaceKind::Handshake); +4951 } +4952 self.events.push_back(Event::HandshakeConfirmed); +4953 trace!("handshake confirmed"); +4954 } +4955 Frame::ObservedAddr(observed) => { +4956 // check if params allows the peer to send report and this node to receive it +4957 trace!(seq_no = %observed.seq_no, ip = %observed.ip, port = observed.port); +4958 if !self +4959 .peer_params +4960 .address_discovery_role +4961 .should_report(&self.config.address_discovery_role) +4962 { +4963 return Err(TransportError::PROTOCOL_VIOLATION( +4964 "received OBSERVED_ADDRESS frame when not negotiated", +4965 )); +4966 } +4967 // must only be sent in data space +4968 if packet.header.space() != SpaceKind::Data { +4969 return Err(TransportError::PROTOCOL_VIOLATION( +4970 "OBSERVED_ADDRESS frame outside data space", +4971 )); +4972 } +4973 +4974 let path = self.path_data_mut(path_id); +4975 if network_path == path.network_path { +4976 if let Some(updated) = path.update_observed_addr_report(observed) +4977 && path.open +4978 { +4979 self.events.push_back(Event::Path(PathEvent::ObservedAddr { +4980 id: path_id, +4981 addr: updated, +4982 })); +4983 // otherwise the event is reported when the path is deemed open +4984 } +4985 } else { +4986 // include in migration +4987 migration_observed_addr = Some(observed) +4988 } +4989 } +4990 Frame::PathAbandon(frame::PathAbandon { +4991 path_id, +4992 error_code, +4993 }) => { +4994 span.record("path", tracing::field::display(&path_id)); +4995 match self.close_path_inner( +4996 now, +4997 path_id, +4998 PathAbandonReason::RemoteAbandoned { +4999 error_code: error_code.into(), +5000 }, +5001 ) { +5002 Ok(()) => { +5003 trace!("peer abandoned path"); +5004 } +5005 Err(ClosePathError::LastOpenPath) => { +5006 trace!("peer abandoned last path, closing connection"); +5007 return Err(TransportError::NO_VIABLE_PATH( +5008 "last path abandoned by peer", +5009 )); +5010 } +5011 Err(ClosePathError::ClosedPath) => { +5012 trace!("peer abandoned already closed path"); 5013 } -5014 }; -5015 -5016 // Start draining the path if it still exists and hasn't started draining yet. -5017 if let Some(path) = self.paths.get_mut(&path_id) -5018 && !mem::replace(&mut path.data.draining, true) -5019 { -5020 let ack_delay = self.ack_frequency.max_ack_delay_for_pto(); -5021 let pto = path.data.rtt.pto_base() + ack_delay; -5022 self.timers.set( -5023 Timer::PerPath(path_id, PathTimer::DiscardPath), -5024 now + 3 * pto, -5025 self.qlog.with_time(now), -5026 ); -5027 -5028 self.set_max_path_id(now, self.local_max_path_id.saturating_add(1u8)); -5029 } -5030 } -5031 Frame::PathStatusAvailable(info) => { -5032 span.record("path", tracing::field::display(&info.path_id)); -5033 if self.is_multipath_negotiated() { -5034 self.on_path_status( -5035 info.path_id, -5036 PathStatus::Available, -5037 info.status_seq_no, -5038 ); -5039 } else { -5040 return Err(TransportError::PROTOCOL_VIOLATION( -5041 "received PATH_STATUS_AVAILABLE frame when multipath was not negotiated", -5042 )); -5043 } -5044 } -5045 Frame::PathStatusBackup(info) => { -5046 span.record("path", tracing::field::display(&info.path_id)); -5047 if self.is_multipath_negotiated() { -5048 self.on_path_status(info.path_id, PathStatus::Backup, info.status_seq_no); -5049 } else { -5050 return Err(TransportError::PROTOCOL_VIOLATION( -5051 "received PATH_STATUS_BACKUP frame when multipath was not negotiated", -5052 )); -5053 } -5054 } -5055 Frame::MaxPathId(frame::MaxPathId(path_id)) => { -5056 span.record("path", tracing::field::display(&path_id)); -5057 if !self.is_multipath_negotiated() { -5058 return Err(TransportError::PROTOCOL_VIOLATION( -5059 "received MAX_PATH_ID frame when multipath was not negotiated", -5060 )); -5061 } -5062 // frames that do not increase the path id are ignored -5063 if path_id > self.remote_max_path_id { -5064 self.remote_max_path_id = path_id; -5065 self.issue_first_path_cids(now); -5066 while let Some(true) = self.continue_nat_traversal_round(now) {} -5067 } -5068 } -5069 Frame::PathsBlocked(frame::PathsBlocked(max_path_id)) => { -5070 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST -5071 // be treated as a connection error of type PROTOCOL_VIOLATION. -5072 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> -5073 if self.is_multipath_negotiated() { -5074 if max_path_id > self.local_max_path_id { -5075 return Err(TransportError::PROTOCOL_VIOLATION( -5076 "PATHS_BLOCKED maximum path identifier was larger than local maximum", -5077 )); -5078 } -5079 debug!("received PATHS_BLOCKED({:?})", max_path_id); -5080 // TODO(@divma): ensure max concurrent paths -5081 } else { -5082 return Err(TransportError::PROTOCOL_VIOLATION( -5083 "received PATHS_BLOCKED frame when not multipath was not negotiated", -5084 )); -5085 } -5086 } -5087 Frame::PathCidsBlocked(frame::PathCidsBlocked { path_id, next_seq }) => { -5088 // Nothing to do. This is recorded in the frame stats, but otherwise we -5089 // always issue all CIDs we're allowed to issue, so either this is an -5090 // impatient peer or a bug on our side. -5091 -5092 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST -5093 // be treated as a connection error of type PROTOCOL_VIOLATION. -5094 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> -5095 if self.is_multipath_negotiated() { -5096 if path_id > self.local_max_path_id { -5097 return Err(TransportError::PROTOCOL_VIOLATION( -5098 "PATH_CIDS_BLOCKED path identifier was larger than local maximum", -5099 )); -5100 } -5101 if next_seq.0 -5102 > self -5103 .local_cid_state -5104 .get(&path_id) -5105 .map(|cid_state| cid_state.active_seq().1 + 1) -5106 .unwrap_or_default() -5107 { -5108 return Err(TransportError::PROTOCOL_VIOLATION( -5109 "PATH_CIDS_BLOCKED next sequence number larger than in local state", -5110 )); -5111 } -5112 debug!(%path_id, %next_seq, "received PATH_CIDS_BLOCKED"); -5113 } else { -5114 return Err(TransportError::PROTOCOL_VIOLATION( -5115 "received PATH_CIDS_BLOCKED frame when not multipath was not negotiated", -5116 )); -5117 } -5118 } -5119 Frame::AddAddress(addr) => { -5120 let client_state = match self.iroh_hp.client_side_mut() { -5121 Ok(state) => state, -5122 Err(err) => { -5123 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5124 "Nat traversal(ADD_ADDRESS): {err}" -5125 ))); -5126 } -5127 }; -5128 -5129 if !client_state.check_remote_address(&addr) { -5130 // if the address is not valid we flag it, but update anyway -5131 warn!(?addr, "server sent illegal ADD_ADDRESS frame"); -5132 } +5014 Err(ClosePathError::MultipathNotNegotiated) => { +5015 return Err(TransportError::PROTOCOL_VIOLATION( +5016 "received PATH_ABANDON frame when multipath was not negotiated", +5017 )); +5018 } +5019 }; +5020 +5021 // Start draining the path if it still exists and hasn't started draining yet. +5022 if let Some(path) = self.paths.get_mut(&path_id) +5023 && !mem::replace(&mut path.data.draining, true) +5024 { +5025 let ack_delay = self.ack_frequency.max_ack_delay_for_pto(); +5026 let pto = path.data.rtt.pto_base() + ack_delay; +5027 self.timers.set( +5028 Timer::PerPath(path_id, PathTimer::DiscardPath), +5029 now + 3 * pto, +5030 self.qlog.with_time(now), +5031 ); +5032 +5033 self.set_max_path_id(now, self.local_max_path_id.saturating_add(1u8)); +5034 } +5035 } +5036 Frame::PathStatusAvailable(info) => { +5037 span.record("path", tracing::field::display(&info.path_id)); +5038 if self.is_multipath_negotiated() { +5039 self.on_path_status( +5040 info.path_id, +5041 PathStatus::Available, +5042 info.status_seq_no, +5043 ); +5044 } else { +5045 return Err(TransportError::PROTOCOL_VIOLATION( +5046 "received PATH_STATUS_AVAILABLE frame when multipath was not negotiated", +5047 )); +5048 } +5049 } +5050 Frame::PathStatusBackup(info) => { +5051 span.record("path", tracing::field::display(&info.path_id)); +5052 if self.is_multipath_negotiated() { +5053 self.on_path_status(info.path_id, PathStatus::Backup, info.status_seq_no); +5054 } else { +5055 return Err(TransportError::PROTOCOL_VIOLATION( +5056 "received PATH_STATUS_BACKUP frame when multipath was not negotiated", +5057 )); +5058 } +5059 } +5060 Frame::MaxPathId(frame::MaxPathId(path_id)) => { +5061 span.record("path", tracing::field::display(&path_id)); +5062 if !self.is_multipath_negotiated() { +5063 return Err(TransportError::PROTOCOL_VIOLATION( +5064 "received MAX_PATH_ID frame when multipath was not negotiated", +5065 )); +5066 } +5067 // frames that do not increase the path id are ignored +5068 if path_id > self.remote_max_path_id { +5069 self.remote_max_path_id = path_id; +5070 self.issue_first_path_cids(now); +5071 while let Some(true) = self.continue_nat_traversal_round(now) {} +5072 } +5073 } +5074 Frame::PathsBlocked(frame::PathsBlocked(max_path_id)) => { +5075 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST +5076 // be treated as a connection error of type PROTOCOL_VIOLATION. +5077 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> +5078 if self.is_multipath_negotiated() { +5079 if max_path_id > self.local_max_path_id { +5080 return Err(TransportError::PROTOCOL_VIOLATION( +5081 "PATHS_BLOCKED maximum path identifier was larger than local maximum", +5082 )); +5083 } +5084 debug!("received PATHS_BLOCKED({:?})", max_path_id); +5085 // TODO(@divma): ensure max concurrent paths +5086 } else { +5087 return Err(TransportError::PROTOCOL_VIOLATION( +5088 "received PATHS_BLOCKED frame when not multipath was not negotiated", +5089 )); +5090 } +5091 } +5092 Frame::PathCidsBlocked(frame::PathCidsBlocked { path_id, next_seq }) => { +5093 // Nothing to do. This is recorded in the frame stats, but otherwise we +5094 // always issue all CIDs we're allowed to issue, so either this is an +5095 // impatient peer or a bug on our side. +5096 +5097 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST +5098 // be treated as a connection error of type PROTOCOL_VIOLATION. +5099 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> +5100 if self.is_multipath_negotiated() { +5101 if path_id > self.local_max_path_id { +5102 return Err(TransportError::PROTOCOL_VIOLATION( +5103 "PATH_CIDS_BLOCKED path identifier was larger than local maximum", +5104 )); +5105 } +5106 if next_seq.0 +5107 > self +5108 .local_cid_state +5109 .get(&path_id) +5110 .map(|cid_state| cid_state.active_seq().1 + 1) +5111 .unwrap_or_default() +5112 { +5113 return Err(TransportError::PROTOCOL_VIOLATION( +5114 "PATH_CIDS_BLOCKED next sequence number larger than in local state", +5115 )); +5116 } +5117 debug!(%path_id, %next_seq, "received PATH_CIDS_BLOCKED"); +5118 } else { +5119 return Err(TransportError::PROTOCOL_VIOLATION( +5120 "received PATH_CIDS_BLOCKED frame when not multipath was not negotiated", +5121 )); +5122 } +5123 } +5124 Frame::AddAddress(addr) => { +5125 let client_state = match self.iroh_hp.client_side_mut() { +5126 Ok(state) => state, +5127 Err(err) => { +5128 return Err(TransportError::PROTOCOL_VIOLATION(format!( +5129 "Nat traversal(ADD_ADDRESS): {err}" +5130 ))); +5131 } +5132 }; 5133 -5134 match client_state.add_remote_address(addr) { -5135 Ok(maybe_added) => { -5136 if let Some(added) = maybe_added { -5137 self.events.push_back(Event::NatTraversal( -5138 iroh_hp::Event::AddressAdded(added), -5139 )); -5140 } -5141 } -5142 Err(e) => { -5143 warn!(%e, "failed to add remote address") -5144 } -5145 } -5146 } -5147 Frame::RemoveAddress(addr) => { -5148 let client_state = match self.iroh_hp.client_side_mut() { -5149 Ok(state) => state, -5150 Err(err) => { -5151 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5152 "Nat traversal(REMOVE_ADDRESS): {err}" -5153 ))); -5154 } -5155 }; -5156 if let Some(removed_addr) = client_state.remove_remote_address(addr) { -5157 self.events -5158 .push_back(Event::NatTraversal(iroh_hp::Event::AddressRemoved( -5159 removed_addr, -5160 ))); -5161 } -5162 } -5163 Frame::ReachOut(reach_out) => { -5164 let ipv6 = self.is_ipv6(); -5165 let server_state = match self.iroh_hp.server_side_mut() { -5166 Ok(state) => state, -5167 Err(err) => { -5168 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5169 "Nat traversal(REACH_OUT): {err}" -5170 ))); -5171 } -5172 }; -5173 -5174 if let Err(err) = server_state.handle_reach_out(reach_out, ipv6) { -5175 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5176 "Nat traversal(REACH_OUT): {err}" -5177 ))); -5178 } -5179 } -5180 } -5181 } -5182 -5183 let space = self.spaces[SpaceId::Data].for_path(path_id); -5184 if space -5185 .pending_acks -5186 .packet_received(now, number, ack_eliciting, &space.dedup) -5187 { -5188 if self.abandoned_paths.contains(&path_id) { -5189 // § 3.4.3 QUIC-MULTIPATH: promptly send ACKs for packets received from -5190 // abandoned paths. -5191 space.pending_acks.set_immediate_ack_required(); -5192 } else { -5193 self.timers.set( -5194 Timer::PerPath(path_id, PathTimer::MaxAckDelay), -5195 now + self.ack_frequency.max_ack_delay, -5196 self.qlog.with_time(now), -5197 ); -5198 } -5199 } -5200 -5201 // Issue stream ID credit due to ACKs of outgoing finish/resets and incoming finish/resets -5202 // on stopped streams. Incoming finishes/resets on open streams are not handled here as they -5203 // are only freed, and hence only issue credit, once the application has been notified -5204 // during a read on the stream. -5205 let pending = &mut self.spaces[SpaceId::Data].pending; -5206 self.streams.queue_max_stream_id(pending); -5207 -5208 if let Some(reason) = close { -5209 self.state.move_to_draining(Some(reason.into())); -5210 self.connection_close_pending = true; -5211 } +5134 if !client_state.check_remote_address(&addr) { +5135 // if the address is not valid we flag it, but update anyway +5136 warn!(?addr, "server sent illegal ADD_ADDRESS frame"); +5137 } +5138 +5139 match client_state.add_remote_address(addr) { +5140 Ok(maybe_added) => { +5141 if let Some(added) = maybe_added { +5142 self.events.push_back(Event::NatTraversal( +5143 iroh_hp::Event::AddressAdded(added), +5144 )); +5145 } +5146 } +5147 Err(e) => { +5148 warn!(%e, "failed to add remote address") +5149 } +5150 } +5151 } +5152 Frame::RemoveAddress(addr) => { +5153 let client_state = match self.iroh_hp.client_side_mut() { +5154 Ok(state) => state, +5155 Err(err) => { +5156 return Err(TransportError::PROTOCOL_VIOLATION(format!( +5157 "Nat traversal(REMOVE_ADDRESS): {err}" +5158 ))); +5159 } +5160 }; +5161 if let Some(removed_addr) = client_state.remove_remote_address(addr) { +5162 self.events +5163 .push_back(Event::NatTraversal(iroh_hp::Event::AddressRemoved( +5164 removed_addr, +5165 ))); +5166 } +5167 } +5168 Frame::ReachOut(reach_out) => { +5169 let ipv6 = self.is_ipv6(); +5170 let server_state = match self.iroh_hp.server_side_mut() { +5171 Ok(state) => state, +5172 Err(err) => { +5173 return Err(TransportError::PROTOCOL_VIOLATION(format!( +5174 "Nat traversal(REACH_OUT): {err}" +5175 ))); +5176 } +5177 }; +5178 +5179 if let Err(err) = server_state.handle_reach_out(reach_out, ipv6) { +5180 return Err(TransportError::PROTOCOL_VIOLATION(format!( +5181 "Nat traversal(REACH_OUT): {err}" +5182 ))); +5183 } +5184 } +5185 } +5186 } +5187 +5188 let space = self.spaces[SpaceId::Data].for_path(path_id); +5189 if space +5190 .pending_acks +5191 .packet_received(now, number, ack_eliciting, &space.dedup) +5192 { +5193 if self.abandoned_paths.contains(&path_id) { +5194 // § 3.4.3 QUIC-MULTIPATH: promptly send ACKs for packets received from +5195 // abandoned paths. +5196 space.pending_acks.set_immediate_ack_required(); +5197 } else { +5198 self.timers.set( +5199 Timer::PerPath(path_id, PathTimer::MaxAckDelay), +5200 now + self.ack_frequency.max_ack_delay, +5201 self.qlog.with_time(now), +5202 ); +5203 } +5204 } +5205 +5206 // Issue stream ID credit due to ACKs of outgoing finish/resets and incoming finish/resets +5207 // on stopped streams. Incoming finishes/resets on open streams are not handled here as they +5208 // are only freed, and hence only issue credit, once the application has been notified +5209 // during a read on the stream. +5210 let pending = &mut self.spaces[SpaceId::Data].pending; +5211 self.streams.queue_max_stream_id(pending); 5212 -5213 if Some(number) == self.spaces[SpaceId::Data].for_path(path_id).rx_packet -5214 && !is_probing_packet -5215 && network_path != self.path_data(path_id).network_path -5216 { -5217 let ConnectionSide::Server { ref server_config } = self.side else { -5218 panic!("packets from unknown remote should be dropped by clients"); -5219 }; -5220 debug_assert!( -5221 server_config.migration, -5222 "migration-initiating packets should have been dropped immediately" -5223 ); -5224 self.migrate(path_id, now, network_path, migration_observed_addr); -5225 // Break linkability, if possible -5226 self.update_remote_cid(path_id); -5227 self.spin = false; -5228 } -5229 -5230 Ok(()) -5231 } -5232 -5233 fn migrate( -5234 &mut self, -5235 path_id: PathId, -5236 now: Instant, -5237 network_path: FourTuple, -5238 observed_addr: Option<ObservedAddr>, -5239 ) { -5240 trace!(%network_path, %path_id, "migration initiated"); -5241 self.path_generation_counter = self.path_generation_counter.wrapping_add(1); -5242 // TODO(@divma): conditions for path migration in multipath are very specific, check them -5243 // again to prevent path migrations that should actually create a new path -5244 -5245 // Reset rtt/congestion state for new path unless it looks like a NAT rebinding. -5246 // Note that the congestion window will not grow until validation terminates. Helps mitigate -5247 // amplification attacks performed by spoofing source addresses. -5248 let prev_pto = self.pto(SpaceKind::Data, path_id); -5249 let path = self.paths.get_mut(&path_id).expect("known path"); -5250 let mut new_path_data = if network_path.remote.is_ipv4() -5251 && network_path.remote.ip() == path.data.network_path.remote.ip() -5252 { -5253 PathData::from_previous(network_path, &path.data, self.path_generation_counter, now) -5254 } else { -5255 let peer_max_udp_payload_size = -5256 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()) -5257 .unwrap_or(u16::MAX); -5258 PathData::new( -5259 network_path, -5260 self.allow_mtud, -5261 Some(peer_max_udp_payload_size), -5262 self.path_generation_counter, -5263 now, -5264 &self.config, -5265 ) -5266 }; -5267 new_path_data.last_observed_addr_report = path.data.last_observed_addr_report.clone(); -5268 if let Some(report) = observed_addr -5269 && let Some(updated) = new_path_data.update_observed_addr_report(report) -5270 { -5271 tracing::info!("adding observed addr event from migration"); -5272 self.events.push_back(Event::Path(PathEvent::ObservedAddr { -5273 id: path_id, -5274 addr: updated, -5275 })); -5276 } -5277 new_path_data.send_new_challenge = true; -5278 -5279 let mut prev_path_data = mem::replace(&mut path.data, new_path_data); -5280 -5281 // Only store this as previous path if it was validated. For all we know there could -5282 // already be a previous path stored which might have been validated in the past, -5283 // which is more valuable than one that's not yet validated. -5284 // -5285 // With multipath it is possible that there are no remote CIDs for the path ID -5286 // yet. In this case we would never have sent on this path yet and would not be able -5287 // to send a PATH_CHALLENGE either, which is currently a fire-and-forget affair -5288 // anyway. So don't store such a path either. -5289 if !prev_path_data.validated -5290 && let Some(cid) = self.remote_cids.get(&path_id).map(CidQueue::active) -5291 { -5292 prev_path_data.send_new_challenge = true; -5293 // We haven't updated the remote CID yet, this captures the remote CID we were using on -5294 // the previous path. -5295 path.prev = Some((cid, prev_path_data)); -5296 } -5297 -5298 // We need to re-assign the correct remote to this path in qlog -5299 self.qlog.emit_tuple_assigned(path_id, network_path, now); -5300 -5301 self.timers.set( -5302 Timer::PerPath(path_id, PathTimer::PathValidation), -5303 now + 3 * cmp::max(self.pto(SpaceKind::Data, path_id), prev_pto), -5304 self.qlog.with_time(now), -5305 ); -5306 } -5307 -5308 /// Handle a change in the local address, i.e. an active migration -5309 /// -5310 /// In the general (non-multipath) case, paths will perform a RFC9000 migration and be pinged -5311 /// for a liveness check. This is the behaviour of a path assumed to be recoverable, even if -5312 /// this is not the case. -5313 /// -5314 /// Clients in a connection in which multipath has been negotiated should migrate paths to new -5315 /// [`PathId`]s. For paths that are known to be non-recoverable can be migrated to a new -5316 /// [`PathId`] by closing the current path, and opening a new one to the same remote. Treating -5317 /// paths as non recoverable when necessary accelerates connectivity re-establishment, or might -5318 /// allow it altogether. -5319 /// -5320 /// The optional `hint` allows callers to indicate when paths are non-recoverable and should be -5321 /// migrated to new a [`PathId`]. -5322 // NOTE: only clients are allowed to migrate, but generally dealing with RFC9000 migrations is -5323 // lacking <https://github.com/n0-computer/quinn/issues/364> -5324 pub fn handle_network_change(&mut self, hint: Option<&dyn NetworkChangeHint>, now: Instant) { -5325 debug!("network changed"); -5326 if self.highest_space < SpaceKind::Data { -5327 for path in self.paths.values_mut() { -5328 // Clear the local address for it to be obtained from the socket again. -5329 path.data.network_path.local_ip = None; -5330 } -5331 -5332 self.update_remote_cid(PathId::ZERO); -5333 self.ping(); -5334 -5335 return; -5336 } -5337 -5338 // Paths that can't recover so a new path should be open instead. If multipath is not -5339 // negotiated, this will be empty. -5340 let mut non_recoverable_paths = Vec::default(); -5341 let mut recoverable_paths = Vec::default(); -5342 let mut open_paths = 0; -5343 -5344 let is_multipath_negotiated = self.is_multipath_negotiated(); -5345 let is_client = self.side().is_client(); -5346 let immediate_ack_allowed = self.peer_supports_ack_frequency(); -5347 -5348 for (path_id, path) in self.paths.iter_mut() { -5349 if self.abandoned_paths.contains(path_id) { -5350 continue; -5351 } -5352 open_paths += 1; -5353 -5354 // Clear the local address for it to be obtained from the socket again. This applies to -5355 // all paths, regardless of being considered recoverable or not -5356 path.data.network_path.local_ip = None; -5357 -5358 let network_path = path.data.network_path; -5359 let remote = network_path.remote; -5360 -5361 // Without multipath, the connection tries to recover the single path, whereas with -5362 // multipath, even in a single-path scenario, we attempt to migrate the path to a new -5363 // PathId. -5364 let attempt_to_recover = if is_multipath_negotiated { -5365 if is_client { -5366 hint.map(|h| h.is_path_recoverable(*path_id, network_path)) -5367 .unwrap_or(false) -5368 } else { -5369 // Servers should have stable addresses so this scenario is generally discouraged. -5370 // There is no way to prevent this, so the best hope is to attempt to recover the -5371 // path -5372 true -5373 } -5374 } else { -5375 // In the non multipath case, we try to recover the single active path -5376 true -5377 }; -5378 -5379 if attempt_to_recover { -5380 recoverable_paths.push((*path_id, remote)); -5381 } else { -5382 non_recoverable_paths.push((*path_id, remote, path.data.local_status())) -5383 } -5384 } -5385 -5386 /* NON RECOVERABLE PATHS */ -5387 // This are handled first, so that in case the treatment intended for these fails, we can -5388 // go the recoverable route instead. -5389 -5390 // Decide if we need to close first or open first in the multipath case. -5391 // - Opening first has a higher risk of getting limited by the negotiated MAX_PATH_ID. -5392 // - Closing first risks this being the only open path. -5393 // We prefer closing paths first unless we identify this is the last open path. -5394 let open_first = open_paths == non_recoverable_paths.len(); -5395 -5396 for (path_id, remote, status) in non_recoverable_paths.into_iter() { -5397 let network_path = FourTuple { -5398 remote, -5399 local_ip: None, /* allow the local ip to be discovered */ -5400 }; -5401 -5402 if open_first && let Err(e) = self.open_path(network_path, status, now) { -5403 debug!(%e,"Failed to open new path for network change"); -5404 // if this fails, let the path try to recover itself -5405 recoverable_paths.push((path_id, remote)); -5406 continue; -5407 } -5408 -5409 if let Err(e) = -5410 self.close_path_inner(now, path_id, PathAbandonReason::UnusableAfterNetworkChange) -5411 { -5412 debug!(%e,"Failed to close unrecoverable path after network change"); -5413 recoverable_paths.push((path_id, remote)); -5414 continue; -5415 } -5416 -5417 if !open_first && let Err(e) = self.open_path(network_path, status, now) { -5418 // Path has already been closed if we got here. Since the path was not recoverable, -5419 // this might be desirable in any case, because other paths exist (!open_first) and -5420 // this was is considered non recoverable -5421 debug!(%e,"Failed to open new path for network change"); -5422 } -5423 } -5424 -5425 /* RECOVERABLE PATHS */ -5426 -5427 for (path_id, remote) in recoverable_paths.into_iter() { -5428 let space = &mut self.spaces[SpaceId::Data]; +5213 if let Some(reason) = close { +5214 self.state.move_to_draining(Some(reason.into())); +5215 self.connection_close_pending = true; +5216 } +5217 +5218 if Some(number) == self.spaces[SpaceId::Data].for_path(path_id).rx_packet +5219 && !is_probing_packet +5220 && network_path != self.path_data(path_id).network_path +5221 { +5222 let ConnectionSide::Server { ref server_config } = self.side else { +5223 panic!("packets from unknown remote should be dropped by clients"); +5224 }; +5225 debug_assert!( +5226 server_config.migration, +5227 "migration-initiating packets should have been dropped immediately" +5228 ); +5229 self.migrate(path_id, now, network_path, migration_observed_addr); +5230 // Break linkability, if possible +5231 self.update_remote_cid(path_id); +5232 self.spin = false; +5233 } +5234 +5235 Ok(()) +5236 } +5237 +5238 fn migrate( +5239 &mut self, +5240 path_id: PathId, +5241 now: Instant, +5242 network_path: FourTuple, +5243 observed_addr: Option<ObservedAddr>, +5244 ) { +5245 trace!(%network_path, %path_id, "migration initiated"); +5246 self.path_generation_counter = self.path_generation_counter.wrapping_add(1); +5247 // TODO(@divma): conditions for path migration in multipath are very specific, check them +5248 // again to prevent path migrations that should actually create a new path +5249 +5250 // Reset rtt/congestion state for new path unless it looks like a NAT rebinding. +5251 // Note that the congestion window will not grow until validation terminates. Helps mitigate +5252 // amplification attacks performed by spoofing source addresses. +5253 let prev_pto = self.pto(SpaceKind::Data, path_id); +5254 let path = self.paths.get_mut(&path_id).expect("known path"); +5255 let mut new_path_data = if network_path.remote.is_ipv4() +5256 && network_path.remote.ip() == path.data.network_path.remote.ip() +5257 { +5258 PathData::from_previous(network_path, &path.data, self.path_generation_counter, now) +5259 } else { +5260 let peer_max_udp_payload_size = +5261 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()) +5262 .unwrap_or(u16::MAX); +5263 PathData::new( +5264 network_path, +5265 self.allow_mtud, +5266 Some(peer_max_udp_payload_size), +5267 self.path_generation_counter, +5268 now, +5269 &self.config, +5270 ) +5271 }; +5272 new_path_data.last_observed_addr_report = path.data.last_observed_addr_report.clone(); +5273 if let Some(report) = observed_addr +5274 && let Some(updated) = new_path_data.update_observed_addr_report(report) +5275 { +5276 tracing::info!("adding observed addr event from migration"); +5277 self.events.push_back(Event::Path(PathEvent::ObservedAddr { +5278 id: path_id, +5279 addr: updated, +5280 })); +5281 } +5282 new_path_data.send_new_challenge = true; +5283 +5284 let mut prev_path_data = mem::replace(&mut path.data, new_path_data); +5285 +5286 // Only store this as previous path if it was validated. For all we know there could +5287 // already be a previous path stored which might have been validated in the past, +5288 // which is more valuable than one that's not yet validated. +5289 // +5290 // With multipath it is possible that there are no remote CIDs for the path ID +5291 // yet. In this case we would never have sent on this path yet and would not be able +5292 // to send a PATH_CHALLENGE either, which is currently a fire-and-forget affair +5293 // anyway. So don't store such a path either. +5294 if !prev_path_data.validated +5295 && let Some(cid) = self.remote_cids.get(&path_id).map(CidQueue::active) +5296 { +5297 prev_path_data.send_new_challenge = true; +5298 // We haven't updated the remote CID yet, this captures the remote CID we were using on +5299 // the previous path. +5300 path.prev = Some((cid, prev_path_data)); +5301 } +5302 +5303 // We need to re-assign the correct remote to this path in qlog +5304 self.qlog.emit_tuple_assigned(path_id, network_path, now); +5305 +5306 self.timers.set( +5307 Timer::PerPath(path_id, PathTimer::PathValidation), +5308 now + 3 * cmp::max(self.pto(SpaceKind::Data, path_id), prev_pto), +5309 self.qlog.with_time(now), +5310 ); +5311 } +5312 +5313 /// Handle a change in the local address, i.e. an active migration +5314 /// +5315 /// In the general (non-multipath) case, paths will perform a RFC9000 migration and be pinged +5316 /// for a liveness check. This is the behaviour of a path assumed to be recoverable, even if +5317 /// this is not the case. +5318 /// +5319 /// Clients in a connection in which multipath has been negotiated should migrate paths to new +5320 /// [`PathId`]s. For paths that are known to be non-recoverable can be migrated to a new +5321 /// [`PathId`] by closing the current path, and opening a new one to the same remote. Treating +5322 /// paths as non recoverable when necessary accelerates connectivity re-establishment, or might +5323 /// allow it altogether. +5324 /// +5325 /// The optional `hint` allows callers to indicate when paths are non-recoverable and should be +5326 /// migrated to new a [`PathId`]. +5327 // NOTE: only clients are allowed to migrate, but generally dealing with RFC9000 migrations is +5328 // lacking <https://github.com/n0-computer/quinn/issues/364> +5329 pub fn handle_network_change(&mut self, hint: Option<&dyn NetworkChangeHint>, now: Instant) { +5330 debug!("network changed"); +5331 if self.highest_space < SpaceKind::Data { +5332 for path in self.paths.values_mut() { +5333 // Clear the local address for it to be obtained from the socket again. +5334 path.data.network_path.local_ip = None; +5335 } +5336 +5337 self.update_remote_cid(PathId::ZERO); +5338 self.ping(); +5339 +5340 return; +5341 } +5342 +5343 // Paths that can't recover so a new path should be open instead. If multipath is not +5344 // negotiated, this will be empty. +5345 let mut non_recoverable_paths = Vec::default(); +5346 let mut recoverable_paths = Vec::default(); +5347 let mut open_paths = 0; +5348 +5349 let is_multipath_negotiated = self.is_multipath_negotiated(); +5350 let is_client = self.side().is_client(); +5351 let immediate_ack_allowed = self.peer_supports_ack_frequency(); +5352 +5353 for (path_id, path) in self.paths.iter_mut() { +5354 if self.abandoned_paths.contains(path_id) { +5355 continue; +5356 } +5357 open_paths += 1; +5358 +5359 // Clear the local address for it to be obtained from the socket again. This applies to +5360 // all paths, regardless of being considered recoverable or not +5361 path.data.network_path.local_ip = None; +5362 +5363 let network_path = path.data.network_path; +5364 let remote = network_path.remote; +5365 +5366 // Without multipath, the connection tries to recover the single path, whereas with +5367 // multipath, even in a single-path scenario, we attempt to migrate the path to a new +5368 // PathId. +5369 let attempt_to_recover = if is_multipath_negotiated { +5370 if is_client { +5371 hint.map(|h| h.is_path_recoverable(*path_id, network_path)) +5372 .unwrap_or(false) +5373 } else { +5374 // Servers should have stable addresses so this scenario is generally discouraged. +5375 // There is no way to prevent this, so the best hope is to attempt to recover the +5376 // path +5377 true +5378 } +5379 } else { +5380 // In the non multipath case, we try to recover the single active path +5381 true +5382 }; +5383 +5384 if attempt_to_recover { +5385 recoverable_paths.push((*path_id, remote)); +5386 } else { +5387 non_recoverable_paths.push((*path_id, remote, path.data.local_status())) +5388 } +5389 } +5390 +5391 /* NON RECOVERABLE PATHS */ +5392 // This are handled first, so that in case the treatment intended for these fails, we can +5393 // go the recoverable route instead. +5394 +5395 // Decide if we need to close first or open first in the multipath case. +5396 // - Opening first has a higher risk of getting limited by the negotiated MAX_PATH_ID. +5397 // - Closing first risks this being the only open path. +5398 // We prefer closing paths first unless we identify this is the last open path. +5399 let open_first = open_paths == non_recoverable_paths.len(); +5400 +5401 for (path_id, remote, status) in non_recoverable_paths.into_iter() { +5402 let network_path = FourTuple { +5403 remote, +5404 local_ip: None, /* allow the local ip to be discovered */ +5405 }; +5406 +5407 if open_first && let Err(e) = self.open_path(network_path, status, now) { +5408 debug!(%e,"Failed to open new path for network change"); +5409 // if this fails, let the path try to recover itself +5410 recoverable_paths.push((path_id, remote)); +5411 continue; +5412 } +5413 +5414 if let Err(e) = +5415 self.close_path_inner(now, path_id, PathAbandonReason::UnusableAfterNetworkChange) +5416 { +5417 debug!(%e,"Failed to close unrecoverable path after network change"); +5418 recoverable_paths.push((path_id, remote)); +5419 continue; +5420 } +5421 +5422 if !open_first && let Err(e) = self.open_path(network_path, status, now) { +5423 // Path has already been closed if we got here. Since the path was not recoverable, +5424 // this might be desirable in any case, because other paths exist (!open_first) and +5425 // this was is considered non recoverable +5426 debug!(%e,"Failed to open new path for network change"); +5427 } +5428 } 5429 -5430 // Schedule a Ping for a liveness check. -5431 if let Some(path_space) = space.number_spaces.get_mut(&path_id) { -5432 path_space.ping_pending = true; -5433 -5434 if immediate_ack_allowed { -5435 path_space.immediate_ack_pending = true; -5436 } -5437 } +5430 /* RECOVERABLE PATHS */ +5431 +5432 for (path_id, remote) in recoverable_paths.into_iter() { +5433 let space = &mut self.spaces[SpaceId::Data]; +5434 +5435 // Schedule a Ping for a liveness check. +5436 if let Some(path_space) = space.number_spaces.get_mut(&path_id) { +5437 path_space.ping_pending = true; 5438 -5439 let Some((reset_token, retired)) = -5440 self.remote_cids.get_mut(&path_id).and_then(CidQueue::next) -5441 else { -5442 continue; -5443 }; -5444 -5445 // Retire the current remote CID and any CIDs we had to skip. -5446 space -5447 .pending -5448 .retire_cids -5449 .extend(retired.map(|seq| (path_id, seq))); -5450 -5451 self.endpoint_events -5452 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); -5453 } -5454 } +5439 if immediate_ack_allowed { +5440 path_space.immediate_ack_pending = true; +5441 } +5442 } +5443 +5444 let Some((reset_token, retired)) = +5445 self.remote_cids.get_mut(&path_id).and_then(CidQueue::next) +5446 else { +5447 continue; +5448 }; +5449 +5450 // Retire the current remote CID and any CIDs we had to skip. +5451 space +5452 .pending +5453 .retire_cids +5454 .extend(retired.map(|seq| (path_id, seq))); 5455 -5456 /// Switch to a previously unused remote connection ID, if possible -5457 fn update_remote_cid(&mut self, path_id: PathId) { -5458 let Some((reset_token, retired)) = self -5459 .remote_cids -5460 .get_mut(&path_id) -5461 .and_then(|cids| cids.next()) -5462 else { -5463 return; -5464 }; -5465 -5466 // Retire the current remote CID and any CIDs we had to skip. -5467 self.spaces[SpaceId::Data] -5468 .pending -5469 .retire_cids -5470 .extend(retired.map(|seq| (path_id, seq))); -5471 let remote = self.path_data(path_id).network_path.remote; -5472 self.set_reset_token(path_id, remote, reset_token); -5473 } -5474 -5475 /// Sends this reset token to the endpoint -5476 /// -5477 /// The endpoint needs to know the reset tokens issued by the peer, so that if the peer -5478 /// sends a reset token it knows to route it to this connection. See RFC 9000 section -5479 /// 10.3. Stateless Reset. -5480 /// -5481 /// Reset tokens are different for each path, the endpoint identifies paths by peer -5482 /// socket address however, not by path ID. -5483 fn set_reset_token(&mut self, path_id: PathId, remote: SocketAddr, reset_token: ResetToken) { -5484 self.endpoint_events -5485 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); -5486 -5487 // During the handshake the server sends a reset token in the transport -5488 // parameters. When we are the client and we receive the reset token during the -5489 // handshake we want this to affect our peer transport parameters. -5490 // TODO(flub): Pretty sure this is pointless, the entire params is overwritten -5491 // shortly after this was called. And then the params don't have this anymore. -5492 if path_id == PathId::ZERO { -5493 self.peer_params.stateless_reset_token = Some(reset_token); -5494 } -5495 } -5496 -5497 /// Issue an initial set of connection IDs to the peer upon connection -5498 fn issue_first_cids(&mut self, now: Instant) { -5499 if self -5500 .local_cid_state -5501 .get(&PathId::ZERO) -5502 .expect("PathId::ZERO exists when the connection is created") -5503 .cid_len() -5504 == 0 -5505 { -5506 return; -5507 } -5508 -5509 // Subtract 1 to account for the CID we supplied while handshaking -5510 let mut n = self.peer_params.issue_cids_limit() - 1; -5511 if let ConnectionSide::Server { server_config } = &self.side -5512 && server_config.has_preferred_address() -5513 { -5514 // We also sent a CID in the transport parameters -5515 n -= 1; -5516 } -5517 self.endpoint_events -5518 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); -5519 } -5520 -5521 /// Issues an initial set of CIDs for paths that have not yet had any CIDs issued -5522 /// -5523 /// Later CIDs are issued when CIDs expire or are retired by the peer. -5524 fn issue_first_path_cids(&mut self, now: Instant) { -5525 if let Some(max_path_id) = self.max_path_id() { -5526 let mut path_id = self.max_path_id_with_cids.next(); -5527 while path_id <= max_path_id { -5528 self.endpoint_events -5529 .push_back(EndpointEventInner::NeedIdentifiers( -5530 path_id, -5531 now, -5532 self.peer_params.issue_cids_limit(), -5533 )); -5534 path_id = path_id.next(); -5535 } -5536 self.max_path_id_with_cids = max_path_id; -5537 } -5538 } -5539 -5540 /// Populates a packet with frames -5541 /// -5542 /// This tries to fit as many frames as possible into the packet. -5543 /// -5544 /// *path_exclusive_only* means to only build frames which can only be sent on this -5545 /// *path. This is used in multipath for backup paths while there is still an active -5546 /// *path. -5547 fn populate_packet<'a, 'b>( -5548 &mut self, -5549 now: Instant, -5550 space_id: SpaceId, -5551 path_id: PathId, -5552 path_exclusive_only: bool, -5553 builder: &mut PacketBuilder<'a, 'b>, -5554 ) { -5555 let pn = builder.packet_number; -5556 let is_multipath_negotiated = self.is_multipath_negotiated(); -5557 let space_has_keys = self.crypto_state.has_keys(space_id.encryption_level()); -5558 let is_0rtt = space_id == SpaceId::Data && !space_has_keys; -5559 let stats = &mut self.stats.frame_tx; -5560 let space = &mut self.spaces[space_id]; -5561 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; -5562 space -5563 .for_path(path_id) -5564 .pending_acks -5565 .maybe_ack_non_eliciting(); -5566 -5567 // HANDSHAKE_DONE -5568 if !is_0rtt -5569 && !path_exclusive_only -5570 && mem::replace(&mut space.pending.handshake_done, false) -5571 { -5572 builder.write_frame(frame::HandshakeDone, stats); -5573 } -5574 -5575 // REACH_OUT -5576 if let Some((round, addresses)) = space.pending.reach_out.as_mut() -5577 && !path_exclusive_only -5578 { -5579 while let Some(local_addr) = addresses.iter().next().copied() { -5580 let local_addr = addresses.take(&local_addr).expect("found from iter"); -5581 let reach_out = frame::ReachOut::new(*round, local_addr); -5582 if builder.frame_space_remaining() > reach_out.size() { -5583 builder.write_frame(reach_out, stats); -5584 } else { -5585 addresses.insert(local_addr); -5586 break; -5587 } -5588 } -5589 if addresses.is_empty() { -5590 space.pending.reach_out = None; -5591 } -5592 } -5593 -5594 // OBSERVED_ADDR -5595 if !path_exclusive_only -5596 && space_id == SpaceId::Data -5597 && self -5598 .config -5599 .address_discovery_role -5600 .should_report(&self.peer_params.address_discovery_role) -5601 && (!path.observed_addr_sent || space.pending.observed_addr) -5602 { -5603 let frame = -5604 frame::ObservedAddr::new(path.network_path.remote, self.next_observed_addr_seq_no); -5605 if builder.frame_space_remaining() > frame.size() { -5606 builder.write_frame(frame, stats); -5607 -5608 self.next_observed_addr_seq_no = self.next_observed_addr_seq_no.saturating_add(1u8); -5609 path.observed_addr_sent = true; -5610 -5611 space.pending.observed_addr = false; -5612 } -5613 } -5614 -5615 // PING -5616 if mem::replace(&mut space.for_path(path_id).ping_pending, false) { -5617 builder.write_frame(frame::Ping, stats); +5456 self.endpoint_events +5457 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); +5458 } +5459 } +5460 +5461 /// Switch to a previously unused remote connection ID, if possible +5462 fn update_remote_cid(&mut self, path_id: PathId) { +5463 let Some((reset_token, retired)) = self +5464 .remote_cids +5465 .get_mut(&path_id) +5466 .and_then(|cids| cids.next()) +5467 else { +5468 return; +5469 }; +5470 +5471 // Retire the current remote CID and any CIDs we had to skip. +5472 self.spaces[SpaceId::Data] +5473 .pending +5474 .retire_cids +5475 .extend(retired.map(|seq| (path_id, seq))); +5476 let remote = self.path_data(path_id).network_path.remote; +5477 self.set_reset_token(path_id, remote, reset_token); +5478 } +5479 +5480 /// Sends this reset token to the endpoint +5481 /// +5482 /// The endpoint needs to know the reset tokens issued by the peer, so that if the peer +5483 /// sends a reset token it knows to route it to this connection. See RFC 9000 section +5484 /// 10.3. Stateless Reset. +5485 /// +5486 /// Reset tokens are different for each path, the endpoint identifies paths by peer +5487 /// socket address however, not by path ID. +5488 fn set_reset_token(&mut self, path_id: PathId, remote: SocketAddr, reset_token: ResetToken) { +5489 self.endpoint_events +5490 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); +5491 +5492 // During the handshake the server sends a reset token in the transport +5493 // parameters. When we are the client and we receive the reset token during the +5494 // handshake we want this to affect our peer transport parameters. +5495 // TODO(flub): Pretty sure this is pointless, the entire params is overwritten +5496 // shortly after this was called. And then the params don't have this anymore. +5497 if path_id == PathId::ZERO { +5498 self.peer_params.stateless_reset_token = Some(reset_token); +5499 } +5500 } +5501 +5502 /// Issue an initial set of connection IDs to the peer upon connection +5503 fn issue_first_cids(&mut self, now: Instant) { +5504 if self +5505 .local_cid_state +5506 .get(&PathId::ZERO) +5507 .expect("PathId::ZERO exists when the connection is created") +5508 .cid_len() +5509 == 0 +5510 { +5511 return; +5512 } +5513 +5514 // Subtract 1 to account for the CID we supplied while handshaking +5515 let mut n = self.peer_params.issue_cids_limit() - 1; +5516 if let ConnectionSide::Server { server_config } = &self.side +5517 && server_config.has_preferred_address() +5518 { +5519 // We also sent a CID in the transport parameters +5520 n -= 1; +5521 } +5522 self.endpoint_events +5523 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); +5524 } +5525 +5526 /// Issues an initial set of CIDs for paths that have not yet had any CIDs issued +5527 /// +5528 /// Later CIDs are issued when CIDs expire or are retired by the peer. +5529 fn issue_first_path_cids(&mut self, now: Instant) { +5530 if let Some(max_path_id) = self.max_path_id() { +5531 let mut path_id = self.max_path_id_with_cids.next(); +5532 while path_id <= max_path_id { +5533 self.endpoint_events +5534 .push_back(EndpointEventInner::NeedIdentifiers( +5535 path_id, +5536 now, +5537 self.peer_params.issue_cids_limit(), +5538 )); +5539 path_id = path_id.next(); +5540 } +5541 self.max_path_id_with_cids = max_path_id; +5542 } +5543 } +5544 +5545 /// Populates a packet with frames +5546 /// +5547 /// This tries to fit as many frames as possible into the packet. +5548 /// +5549 /// *path_exclusive_only* means to only build frames which can only be sent on this +5550 /// *path. This is used in multipath for backup paths while there is still an active +5551 /// *path. +5552 fn populate_packet<'a, 'b>( +5553 &mut self, +5554 now: Instant, +5555 space_id: SpaceId, +5556 path_id: PathId, +5557 path_exclusive_only: bool, +5558 builder: &mut PacketBuilder<'a, 'b>, +5559 ) { +5560 let pn = builder.packet_number; +5561 let is_multipath_negotiated = self.is_multipath_negotiated(); +5562 let space_has_keys = self.crypto_state.has_keys(space_id.encryption_level()); +5563 let is_0rtt = space_id == SpaceId::Data && !space_has_keys; +5564 let stats = &mut self.stats.frame_tx; +5565 let space = &mut self.spaces[space_id]; +5566 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; +5567 space +5568 .for_path(path_id) +5569 .pending_acks +5570 .maybe_ack_non_eliciting(); +5571 +5572 // HANDSHAKE_DONE +5573 if !is_0rtt +5574 && !path_exclusive_only +5575 && mem::replace(&mut space.pending.handshake_done, false) +5576 { +5577 builder.write_frame(frame::HandshakeDone, stats); +5578 } +5579 +5580 // REACH_OUT +5581 if let Some((round, addresses)) = space.pending.reach_out.as_mut() +5582 && !path_exclusive_only +5583 { +5584 while let Some(local_addr) = addresses.iter().next().copied() { +5585 let local_addr = addresses.take(&local_addr).expect("found from iter"); +5586 let reach_out = frame::ReachOut::new(*round, local_addr); +5587 if builder.frame_space_remaining() > reach_out.size() { +5588 builder.write_frame(reach_out, stats); +5589 } else { +5590 addresses.insert(local_addr); +5591 break; +5592 } +5593 } +5594 if addresses.is_empty() { +5595 space.pending.reach_out = None; +5596 } +5597 } +5598 +5599 // OBSERVED_ADDR +5600 if !path_exclusive_only +5601 && space_id == SpaceId::Data +5602 && self +5603 .config +5604 .address_discovery_role +5605 .should_report(&self.peer_params.address_discovery_role) +5606 && (!path.observed_addr_sent || space.pending.observed_addr) +5607 { +5608 let frame = +5609 frame::ObservedAddr::new(path.network_path.remote, self.next_observed_addr_seq_no); +5610 if builder.frame_space_remaining() > frame.size() { +5611 builder.write_frame(frame, stats); +5612 +5613 self.next_observed_addr_seq_no = self.next_observed_addr_seq_no.saturating_add(1u8); +5614 path.observed_addr_sent = true; +5615 +5616 space.pending.observed_addr = false; +5617 } 5618 } 5619 -5620 // IMMEDIATE_ACK -5621 if mem::replace(&mut space.for_path(path_id).immediate_ack_pending, false) { -5622 debug_assert_eq!( -5623 space_id, -5624 SpaceId::Data, -5625 "immediate acks must be sent in the data space" -5626 ); -5627 builder.write_frame(frame::ImmediateAck, stats); -5628 } -5629 -5630 // ACK -5631 // TODO(flub): Should this send acks for this path anyway? -5632 -5633 if !path_exclusive_only { -5634 for path_id in space -5635 .number_spaces -5636 .iter_mut() -5637 .filter(|(_, pns)| pns.pending_acks.can_send()) -5638 .map(|(&path_id, _)| path_id) -5639 .collect::<Vec<_>>() -5640 { -5641 Self::populate_acks( -5642 now, -5643 self.receiving_ecn, -5644 path_id, -5645 space_id, -5646 space, -5647 is_multipath_negotiated, -5648 builder, -5649 stats, -5650 space_has_keys, -5651 ); -5652 } -5653 } -5654 -5655 // ACK_FREQUENCY -5656 if !path_exclusive_only && mem::replace(&mut space.pending.ack_frequency, false) { -5657 let sequence_number = self.ack_frequency.next_sequence_number(); -5658 -5659 // Safe to unwrap because this is always provided when ACK frequency is enabled -5660 let config = self.config.ack_frequency_config.as_ref().unwrap(); -5661 -5662 // Ensure the delay is within bounds to avoid a PROTOCOL_VIOLATION error -5663 let max_ack_delay = self.ack_frequency.candidate_max_ack_delay( -5664 path.rtt.get(), -5665 config, -5666 &self.peer_params, -5667 ); -5668 -5669 let frame = frame::AckFrequency { -5670 sequence: sequence_number, -5671 ack_eliciting_threshold: config.ack_eliciting_threshold, -5672 request_max_ack_delay: max_ack_delay.as_micros().try_into().unwrap_or(VarInt::MAX), -5673 reordering_threshold: config.reordering_threshold, -5674 }; -5675 builder.write_frame(frame, stats); -5676 -5677 self.ack_frequency -5678 .ack_frequency_sent(path_id, pn, max_ack_delay); -5679 } -5680 -5681 // PATH_CHALLENGE -5682 if builder.frame_space_remaining() > frame::PathChallenge::SIZE_BOUND -5683 && space_id == SpaceId::Data -5684 && path.send_new_challenge -5685 && !self.state.is_closed() -5686 // we don't want to send new challenges if we are already closing -5687 { -5688 path.send_new_challenge = false; -5689 -5690 let token = self.rng.random(); -5691 path.new_path_challenge(now, token, path.network_path); -5692 // Generate a new challenge every time we send a new PATH_CHALLENGE -5693 let challenge = frame::PathChallenge(token); -5694 builder.write_frame(challenge, stats); -5695 builder.require_padding(); -5696 let pto = self.ack_frequency.max_ack_delay_for_pto() + path.rtt.pto_base(); -5697 self.timers.set( -5698 Timer::PerPath(path_id, PathTimer::PathChallengeLost), -5699 now + pto, -5700 self.qlog.with_time(now), -5701 ); -5702 -5703 if is_multipath_negotiated && !path.validated && path.send_new_challenge { -5704 // queue informing the path status along with the challenge -5705 space.pending.path_status.insert(path_id); -5706 } +5620 // PING +5621 if mem::replace(&mut space.for_path(path_id).ping_pending, false) { +5622 builder.write_frame(frame::Ping, stats); +5623 } +5624 +5625 // IMMEDIATE_ACK +5626 if mem::replace(&mut space.for_path(path_id).immediate_ack_pending, false) { +5627 debug_assert_eq!( +5628 space_id, +5629 SpaceId::Data, +5630 "immediate acks must be sent in the data space" +5631 ); +5632 builder.write_frame(frame::ImmediateAck, stats); +5633 } +5634 +5635 // ACK +5636 // TODO(flub): Should this send acks for this path anyway? +5637 +5638 if !path_exclusive_only { +5639 for path_id in space +5640 .number_spaces +5641 .iter_mut() +5642 .filter(|(_, pns)| pns.pending_acks.can_send()) +5643 .map(|(&path_id, _)| path_id) +5644 .collect::<Vec<_>>() +5645 { +5646 Self::populate_acks( +5647 now, +5648 self.receiving_ecn, +5649 path_id, +5650 space_id, +5651 space, +5652 is_multipath_negotiated, +5653 builder, +5654 stats, +5655 space_has_keys, +5656 ); +5657 } +5658 } +5659 +5660 // ACK_FREQUENCY +5661 if !path_exclusive_only && mem::replace(&mut space.pending.ack_frequency, false) { +5662 let sequence_number = self.ack_frequency.next_sequence_number(); +5663 +5664 // Safe to unwrap because this is always provided when ACK frequency is enabled +5665 let config = self.config.ack_frequency_config.as_ref().unwrap(); +5666 +5667 // Ensure the delay is within bounds to avoid a PROTOCOL_VIOLATION error +5668 let max_ack_delay = self.ack_frequency.candidate_max_ack_delay( +5669 path.rtt.get(), +5670 config, +5671 &self.peer_params, +5672 ); +5673 +5674 let frame = frame::AckFrequency { +5675 sequence: sequence_number, +5676 ack_eliciting_threshold: config.ack_eliciting_threshold, +5677 request_max_ack_delay: max_ack_delay.as_micros().try_into().unwrap_or(VarInt::MAX), +5678 reordering_threshold: config.reordering_threshold, +5679 }; +5680 builder.write_frame(frame, stats); +5681 +5682 self.ack_frequency +5683 .ack_frequency_sent(path_id, pn, max_ack_delay); +5684 } +5685 +5686 // PATH_CHALLENGE +5687 if builder.frame_space_remaining() > frame::PathChallenge::SIZE_BOUND +5688 && space_id == SpaceId::Data +5689 && path.send_new_challenge +5690 && !self.state.is_closed() +5691 // we don't want to send new challenges if we are already closing +5692 { +5693 path.send_new_challenge = false; +5694 +5695 let token = self.rng.random(); +5696 path.new_path_challenge(now, token, path.network_path); +5697 // Generate a new challenge every time we send a new PATH_CHALLENGE +5698 let challenge = frame::PathChallenge(token); +5699 builder.write_frame(challenge, stats); +5700 builder.require_padding(); +5701 let pto = self.ack_frequency.max_ack_delay_for_pto() + path.rtt.pto_base(); +5702 self.timers.set( +5703 Timer::PerPath(path_id, PathTimer::PathChallengeLost), +5704 now + pto, +5705 self.qlog.with_time(now), +5706 ); 5707 -5708 // Always include an OBSERVED_ADDR frame with a PATH_CHALLENGE, regardless -5709 // of whether one has already been sent on this path. -5710 if space_id == SpaceId::Data -5711 && self -5712 .config -5713 .address_discovery_role -5714 .should_report(&self.peer_params.address_discovery_role) -5715 { -5716 let frame = frame::ObservedAddr::new( -5717 path.network_path.remote, -5718 self.next_observed_addr_seq_no, -5719 ); -5720 if builder.frame_space_remaining() > frame.size() { -5721 builder.write_frame(frame, stats); -5722 -5723 self.next_observed_addr_seq_no = -5724 self.next_observed_addr_seq_no.saturating_add(1u8); -5725 path.observed_addr_sent = true; -5726 -5727 space.pending.observed_addr = false; -5728 } -5729 } -5730 } +5708 if is_multipath_negotiated && !path.validated && path.send_new_challenge { +5709 // queue informing the path status along with the challenge +5710 space.pending.path_status.insert(path_id); +5711 } +5712 +5713 // Always include an OBSERVED_ADDR frame with a PATH_CHALLENGE, regardless +5714 // of whether one has already been sent on this path. +5715 if space_id == SpaceId::Data +5716 && self +5717 .config +5718 .address_discovery_role +5719 .should_report(&self.peer_params.address_discovery_role) +5720 { +5721 let frame = frame::ObservedAddr::new( +5722 path.network_path.remote, +5723 self.next_observed_addr_seq_no, +5724 ); +5725 if builder.frame_space_remaining() > frame.size() { +5726 builder.write_frame(frame, stats); +5727 +5728 self.next_observed_addr_seq_no = +5729 self.next_observed_addr_seq_no.saturating_add(1u8); +5730 path.observed_addr_sent = true; 5731 -5732 // PATH_RESPONSE -5733 if builder.frame_space_remaining() > frame::PathResponse::SIZE_BOUND -5734 && space_id == SpaceId::Data -5735 && let Some(token) = path.path_responses.pop_on_path(path.network_path) -5736 { -5737 let response = frame::PathResponse(token); -5738 trace!(frame = %response); -5739 builder.write_frame(response, stats); -5740 builder.require_padding(); -5741 -5742 // NOTE: this is technically not required but might be useful to ride the -5743 // request/response nature of path challenges to refresh an observation -5744 // Since PATH_RESPONSE is a probing frame, this is allowed by the spec. -5745 if space_id == SpaceId::Data -5746 && self -5747 .config -5748 .address_discovery_role -5749 .should_report(&self.peer_params.address_discovery_role) -5750 { -5751 let frame = frame::ObservedAddr::new( -5752 path.network_path.remote, -5753 self.next_observed_addr_seq_no, -5754 ); -5755 if builder.frame_space_remaining() > frame.size() { -5756 builder.write_frame(frame, stats); -5757 -5758 self.next_observed_addr_seq_no = -5759 self.next_observed_addr_seq_no.saturating_add(1u8); -5760 path.observed_addr_sent = true; -5761 -5762 space.pending.observed_addr = false; -5763 } -5764 } -5765 } +5732 space.pending.observed_addr = false; +5733 } +5734 } +5735 } +5736 +5737 // PATH_RESPONSE +5738 if builder.frame_space_remaining() > frame::PathResponse::SIZE_BOUND +5739 && space_id == SpaceId::Data +5740 && let Some(token) = path.path_responses.pop_on_path(path.network_path) +5741 { +5742 let response = frame::PathResponse(token); +5743 trace!(frame = %response); +5744 builder.write_frame(response, stats); +5745 builder.require_padding(); +5746 +5747 // NOTE: this is technically not required but might be useful to ride the +5748 // request/response nature of path challenges to refresh an observation +5749 // Since PATH_RESPONSE is a probing frame, this is allowed by the spec. +5750 if space_id == SpaceId::Data +5751 && self +5752 .config +5753 .address_discovery_role +5754 .should_report(&self.peer_params.address_discovery_role) +5755 { +5756 let frame = frame::ObservedAddr::new( +5757 path.network_path.remote, +5758 self.next_observed_addr_seq_no, +5759 ); +5760 if builder.frame_space_remaining() > frame.size() { +5761 builder.write_frame(frame, stats); +5762 +5763 self.next_observed_addr_seq_no = +5764 self.next_observed_addr_seq_no.saturating_add(1u8); +5765 path.observed_addr_sent = true; 5766 -5767 // CRYPTO -5768 while !path_exclusive_only -5769 && builder.frame_space_remaining() > frame::Crypto::SIZE_BOUND -5770 && !is_0rtt -5771 { -5772 let mut frame = match space.pending.crypto.pop_front() { -5773 Some(x) => x, -5774 None => break, -5775 }; -5776 -5777 // Calculate the maximum amount of crypto data we can store in the buffer. -5778 // Since the offset is known, we can reserve the exact size required to encode it. -5779 // For length we reserve 2bytes which allows to encode up to 2^14, -5780 // which is more than what fits into normally sized QUIC frames. -5781 let max_crypto_data_size = builder.frame_space_remaining() -5782 - 1 // Frame Type -5783 - VarInt::size(unsafe { VarInt::from_u64_unchecked(frame.offset) }) -5784 - 2; // Maximum encoded length for frame size, given we send less than 2^14 bytes -5785 -5786 let len = frame -5787 .data -5788 .len() -5789 .min(2usize.pow(14) - 1) -5790 .min(max_crypto_data_size); -5791 -5792 let data = frame.data.split_to(len); -5793 let offset = frame.offset; -5794 let truncated = frame::Crypto { offset, data }; -5795 builder.write_frame(truncated, stats); +5767 space.pending.observed_addr = false; +5768 } +5769 } +5770 } +5771 +5772 // CRYPTO +5773 while !path_exclusive_only +5774 && builder.frame_space_remaining() > frame::Crypto::SIZE_BOUND +5775 && !is_0rtt +5776 { +5777 let mut frame = match space.pending.crypto.pop_front() { +5778 Some(x) => x, +5779 None => break, +5780 }; +5781 +5782 // Calculate the maximum amount of crypto data we can store in the buffer. +5783 // Since the offset is known, we can reserve the exact size required to encode it. +5784 // For length we reserve 2bytes which allows to encode up to 2^14, +5785 // which is more than what fits into normally sized QUIC frames. +5786 let max_crypto_data_size = builder.frame_space_remaining() +5787 - 1 // Frame Type +5788 - VarInt::size(unsafe { VarInt::from_u64_unchecked(frame.offset) }) +5789 - 2; // Maximum encoded length for frame size, given we send less than 2^14 bytes +5790 +5791 let len = frame +5792 .data +5793 .len() +5794 .min(2usize.pow(14) - 1) +5795 .min(max_crypto_data_size); 5796 -5797 if !frame.data.is_empty() { -5798 frame.offset += len as u64; -5799 space.pending.crypto.push_front(frame); -5800 } -5801 } -5802 -5803 // TODO(flub): maybe this is much higher priority? -5804 // PATH_ABANDON -5805 while !path_exclusive_only -5806 && space_id == SpaceId::Data -5807 && frame::PathAbandon::SIZE_BOUND <= builder.frame_space_remaining() -5808 { -5809 let Some((abandoned_path_id, error_code)) = space.pending.path_abandon.pop_first() -5810 else { -5811 break; -5812 }; -5813 let frame = frame::PathAbandon { -5814 path_id: abandoned_path_id, -5815 error_code, -5816 }; -5817 builder.write_frame(frame, stats); -5818 } -5819 -5820 // PATH_STATUS_AVAILABLE & PATH_STATUS_BACKUP -5821 while !path_exclusive_only -5822 && space_id == SpaceId::Data -5823 && frame::PathStatusAvailable::SIZE_BOUND <= builder.frame_space_remaining() -5824 { -5825 let Some(path_id) = space.pending.path_status.pop_first() else { -5826 break; -5827 }; -5828 let Some(path) = self.paths.get(&path_id).map(|path_state| &path_state.data) else { -5829 trace!(%path_id, "discarding queued path status for unknown path"); -5830 continue; -5831 }; -5832 -5833 let seq = path.status.seq(); -5834 match path.local_status() { -5835 PathStatus::Available => { -5836 let frame = frame::PathStatusAvailable { -5837 path_id, -5838 status_seq_no: seq, -5839 }; -5840 builder.write_frame(frame, stats); -5841 } -5842 PathStatus::Backup => { -5843 let frame = frame::PathStatusBackup { -5844 path_id, -5845 status_seq_no: seq, -5846 }; -5847 builder.write_frame(frame, stats); -5848 } -5849 } -5850 } -5851 -5852 // MAX_PATH_ID -5853 if space_id == SpaceId::Data -5854 && !path_exclusive_only -5855 && space.pending.max_path_id -5856 && frame::MaxPathId::SIZE_BOUND <= builder.frame_space_remaining() -5857 { -5858 let frame = frame::MaxPathId(self.local_max_path_id); -5859 builder.write_frame(frame, stats); -5860 space.pending.max_path_id = false; -5861 } -5862 -5863 // PATHS_BLOCKED -5864 if space_id == SpaceId::Data -5865 && !path_exclusive_only -5866 && space.pending.paths_blocked -5867 && frame::PathsBlocked::SIZE_BOUND <= builder.frame_space_remaining() -5868 { -5869 let frame = frame::PathsBlocked(self.remote_max_path_id); -5870 builder.write_frame(frame, stats); -5871 space.pending.paths_blocked = false; -5872 } -5873 -5874 // PATH_CIDS_BLOCKED -5875 while space_id == SpaceId::Data -5876 && !path_exclusive_only -5877 && frame::PathCidsBlocked::SIZE_BOUND <= builder.frame_space_remaining() -5878 { -5879 let Some(path_id) = space.pending.path_cids_blocked.pop_first() else { -5880 break; -5881 }; -5882 let next_seq = match self.remote_cids.get(&path_id) { -5883 Some(cid_queue) => VarInt(cid_queue.active_seq() + 1), -5884 None => VarInt(0), -5885 }; -5886 let frame = frame::PathCidsBlocked { path_id, next_seq }; -5887 builder.write_frame(frame, stats); -5888 } -5889 -5890 // RESET_STREAM, STOP_SENDING, MAX_DATA, MAX_STREAM_DATA, MAX_STREAMS -5891 if space_id == SpaceId::Data && !path_exclusive_only { -5892 self.streams -5893 .write_control_frames(builder, &mut space.pending, stats); -5894 } -5895 -5896 // NEW_CONNECTION_ID -5897 let cid_len = self -5898 .local_cid_state -5899 .values() -5900 .map(|cid_state| cid_state.cid_len()) -5901 .max() -5902 .expect("some local CID state must exist"); -5903 let new_cid_size_bound = -5904 frame::NewConnectionId::size_bound(is_multipath_negotiated, cid_len); -5905 while !path_exclusive_only && builder.frame_space_remaining() > new_cid_size_bound { -5906 let issued = match space.pending.new_cids.pop() { -5907 Some(x) => x, -5908 None => break, -5909 }; -5910 let retire_prior_to = self -5911 .local_cid_state -5912 .get(&issued.path_id) -5913 .map(|cid_state| cid_state.retire_prior_to()) -5914 .unwrap_or_else(|| panic!("missing local CID state for path={}", issued.path_id)); -5915 -5916 let cid_path_id = match is_multipath_negotiated { -5917 true => Some(issued.path_id), -5918 false => { -5919 debug_assert_eq!(issued.path_id, PathId::ZERO); -5920 None -5921 } -5922 }; -5923 let frame = frame::NewConnectionId { -5924 path_id: cid_path_id, -5925 sequence: issued.sequence, -5926 retire_prior_to, -5927 id: issued.id, -5928 reset_token: issued.reset_token, -5929 }; -5930 builder.write_frame(frame, stats); -5931 } -5932 -5933 // RETIRE_CONNECTION_ID -5934 let retire_cid_bound = frame::RetireConnectionId::size_bound(is_multipath_negotiated); -5935 while !path_exclusive_only && builder.frame_space_remaining() > retire_cid_bound { -5936 let (path_id, sequence) = match space.pending.retire_cids.pop() { -5937 Some((PathId::ZERO, seq)) if !is_multipath_negotiated => (None, seq), -5938 Some((path_id, seq)) => (Some(path_id), seq), -5939 None => break, -5940 }; -5941 let frame = frame::RetireConnectionId { path_id, sequence }; -5942 builder.write_frame(frame, stats); -5943 } -5944 -5945 // DATAGRAM -5946 let mut sent_datagrams = false; -5947 while !path_exclusive_only -5948 && builder.frame_space_remaining() > Datagram::SIZE_BOUND -5949 && space_id == SpaceId::Data -5950 { -5951 match self.datagrams.write(builder, stats) { -5952 true => { -5953 sent_datagrams = true; -5954 } -5955 false => break, -5956 } -5957 } -5958 if self.datagrams.send_blocked && sent_datagrams { -5959 self.events.push_back(Event::DatagramsUnblocked); -5960 self.datagrams.send_blocked = false; -5961 } -5962 -5963 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; -5964 -5965 // NEW_TOKEN -5966 if !path_exclusive_only { -5967 while let Some(network_path) = space.pending.new_tokens.pop() { -5968 debug_assert_eq!(space_id, SpaceId::Data); -5969 let ConnectionSide::Server { server_config } = &self.side else { -5970 panic!("NEW_TOKEN frames should not be enqueued by clients"); -5971 }; -5972 -5973 if !network_path.is_probably_same_path(&path.network_path) { -5974 // NEW_TOKEN frames contain tokens bound to a client's IP address, and are only -5975 // useful if used from the same IP address. Thus, we abandon enqueued NEW_TOKEN -5976 // frames upon an path change. Instead, when the new path becomes validated, -5977 // NEW_TOKEN frames may be enqueued for the new path instead. -5978 continue; -5979 } -5980 -5981 let token = Token::new( -5982 TokenPayload::Validation { -5983 ip: network_path.remote.ip(), -5984 issued: server_config.time_source.now(), -5985 }, -5986 &mut self.rng, -5987 ); -5988 let new_token = NewToken { -5989 token: token.encode(&*server_config.token_key).into(), -5990 }; -5991 -5992 if builder.frame_space_remaining() < new_token.size() { -5993 space.pending.new_tokens.push(network_path); -5994 break; -5995 } +5797 let data = frame.data.split_to(len); +5798 let offset = frame.offset; +5799 let truncated = frame::Crypto { offset, data }; +5800 builder.write_frame(truncated, stats); +5801 +5802 if !frame.data.is_empty() { +5803 frame.offset += len as u64; +5804 space.pending.crypto.push_front(frame); +5805 } +5806 } +5807 +5808 // TODO(flub): maybe this is much higher priority? +5809 // PATH_ABANDON +5810 while !path_exclusive_only +5811 && space_id == SpaceId::Data +5812 && frame::PathAbandon::SIZE_BOUND <= builder.frame_space_remaining() +5813 { +5814 let Some((abandoned_path_id, error_code)) = space.pending.path_abandon.pop_first() +5815 else { +5816 break; +5817 }; +5818 let frame = frame::PathAbandon { +5819 path_id: abandoned_path_id, +5820 error_code, +5821 }; +5822 builder.write_frame(frame, stats); +5823 } +5824 +5825 // PATH_STATUS_AVAILABLE & PATH_STATUS_BACKUP +5826 while !path_exclusive_only +5827 && space_id == SpaceId::Data +5828 && frame::PathStatusAvailable::SIZE_BOUND <= builder.frame_space_remaining() +5829 { +5830 let Some(path_id) = space.pending.path_status.pop_first() else { +5831 break; +5832 }; +5833 let Some(path) = self.paths.get(&path_id).map(|path_state| &path_state.data) else { +5834 trace!(%path_id, "discarding queued path status for unknown path"); +5835 continue; +5836 }; +5837 +5838 let seq = path.status.seq(); +5839 match path.local_status() { +5840 PathStatus::Available => { +5841 let frame = frame::PathStatusAvailable { +5842 path_id, +5843 status_seq_no: seq, +5844 }; +5845 builder.write_frame(frame, stats); +5846 } +5847 PathStatus::Backup => { +5848 let frame = frame::PathStatusBackup { +5849 path_id, +5850 status_seq_no: seq, +5851 }; +5852 builder.write_frame(frame, stats); +5853 } +5854 } +5855 } +5856 +5857 // MAX_PATH_ID +5858 if space_id == SpaceId::Data +5859 && !path_exclusive_only +5860 && space.pending.max_path_id +5861 && frame::MaxPathId::SIZE_BOUND <= builder.frame_space_remaining() +5862 { +5863 let frame = frame::MaxPathId(self.local_max_path_id); +5864 builder.write_frame(frame, stats); +5865 space.pending.max_path_id = false; +5866 } +5867 +5868 // PATHS_BLOCKED +5869 if space_id == SpaceId::Data +5870 && !path_exclusive_only +5871 && space.pending.paths_blocked +5872 && frame::PathsBlocked::SIZE_BOUND <= builder.frame_space_remaining() +5873 { +5874 let frame = frame::PathsBlocked(self.remote_max_path_id); +5875 builder.write_frame(frame, stats); +5876 space.pending.paths_blocked = false; +5877 } +5878 +5879 // PATH_CIDS_BLOCKED +5880 while space_id == SpaceId::Data +5881 && !path_exclusive_only +5882 && frame::PathCidsBlocked::SIZE_BOUND <= builder.frame_space_remaining() +5883 { +5884 let Some(path_id) = space.pending.path_cids_blocked.pop_first() else { +5885 break; +5886 }; +5887 let next_seq = match self.remote_cids.get(&path_id) { +5888 Some(cid_queue) => VarInt(cid_queue.active_seq() + 1), +5889 None => VarInt(0), +5890 }; +5891 let frame = frame::PathCidsBlocked { path_id, next_seq }; +5892 builder.write_frame(frame, stats); +5893 } +5894 +5895 // RESET_STREAM, STOP_SENDING, MAX_DATA, MAX_STREAM_DATA, MAX_STREAMS +5896 if space_id == SpaceId::Data && !path_exclusive_only { +5897 self.streams +5898 .write_control_frames(builder, &mut space.pending, stats); +5899 } +5900 +5901 // NEW_CONNECTION_ID +5902 let cid_len = self +5903 .local_cid_state +5904 .values() +5905 .map(|cid_state| cid_state.cid_len()) +5906 .max() +5907 .expect("some local CID state must exist"); +5908 let new_cid_size_bound = +5909 frame::NewConnectionId::size_bound(is_multipath_negotiated, cid_len); +5910 while !path_exclusive_only && builder.frame_space_remaining() > new_cid_size_bound { +5911 let issued = match space.pending.new_cids.pop() { +5912 Some(x) => x, +5913 None => break, +5914 }; +5915 let retire_prior_to = self +5916 .local_cid_state +5917 .get(&issued.path_id) +5918 .map(|cid_state| cid_state.retire_prior_to()) +5919 .unwrap_or_else(|| panic!("missing local CID state for path={}", issued.path_id)); +5920 +5921 let cid_path_id = match is_multipath_negotiated { +5922 true => Some(issued.path_id), +5923 false => { +5924 debug_assert_eq!(issued.path_id, PathId::ZERO); +5925 None +5926 } +5927 }; +5928 let frame = frame::NewConnectionId { +5929 path_id: cid_path_id, +5930 sequence: issued.sequence, +5931 retire_prior_to, +5932 id: issued.id, +5933 reset_token: issued.reset_token, +5934 }; +5935 builder.write_frame(frame, stats); +5936 } +5937 +5938 // RETIRE_CONNECTION_ID +5939 let retire_cid_bound = frame::RetireConnectionId::size_bound(is_multipath_negotiated); +5940 while !path_exclusive_only && builder.frame_space_remaining() > retire_cid_bound { +5941 let (path_id, sequence) = match space.pending.retire_cids.pop() { +5942 Some((PathId::ZERO, seq)) if !is_multipath_negotiated => (None, seq), +5943 Some((path_id, seq)) => (Some(path_id), seq), +5944 None => break, +5945 }; +5946 let frame = frame::RetireConnectionId { path_id, sequence }; +5947 builder.write_frame(frame, stats); +5948 } +5949 +5950 // DATAGRAM +5951 let mut sent_datagrams = false; +5952 while !path_exclusive_only +5953 && builder.frame_space_remaining() > Datagram::SIZE_BOUND +5954 && space_id == SpaceId::Data +5955 { +5956 match self.datagrams.write(builder, stats) { +5957 true => { +5958 sent_datagrams = true; +5959 } +5960 false => break, +5961 } +5962 } +5963 if self.datagrams.send_blocked && sent_datagrams { +5964 self.events.push_back(Event::DatagramsUnblocked); +5965 self.datagrams.send_blocked = false; +5966 } +5967 +5968 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; +5969 +5970 // NEW_TOKEN +5971 if !path_exclusive_only { +5972 while let Some(network_path) = space.pending.new_tokens.pop() { +5973 debug_assert_eq!(space_id, SpaceId::Data); +5974 let ConnectionSide::Server { server_config } = &self.side else { +5975 panic!("NEW_TOKEN frames should not be enqueued by clients"); +5976 }; +5977 +5978 if !network_path.is_probably_same_path(&path.network_path) { +5979 // NEW_TOKEN frames contain tokens bound to a client's IP address, and are only +5980 // useful if used from the same IP address. Thus, we abandon enqueued NEW_TOKEN +5981 // frames upon an path change. Instead, when the new path becomes validated, +5982 // NEW_TOKEN frames may be enqueued for the new path instead. +5983 continue; +5984 } +5985 +5986 let token = Token::new( +5987 TokenPayload::Validation { +5988 ip: network_path.remote.ip(), +5989 issued: server_config.time_source.now(), +5990 }, +5991 &mut self.rng, +5992 ); +5993 let new_token = NewToken { +5994 token: token.encode(&*server_config.token_key).into(), +5995 }; 5996 -5997 builder.write_frame(new_token, stats); -5998 builder.retransmits_mut().new_tokens.push(network_path); -5999 } -6000 } +5997 if builder.frame_space_remaining() < new_token.size() { +5998 space.pending.new_tokens.push(network_path); +5999 break; +6000 } 6001 -6002 // STREAM -6003 if !path_exclusive_only && space_id == SpaceId::Data { -6004 self.streams -6005 .write_stream_frames(builder, self.config.send_fairness, stats); -6006 } -6007 -6008 // ADD_ADDRESS -6009 while space_id == SpaceId::Data -6010 && !path_exclusive_only -6011 && frame::AddAddress::SIZE_BOUND <= builder.frame_space_remaining() -6012 { -6013 if let Some(added_address) = space.pending.add_address.pop_last() { -6014 builder.write_frame(added_address, stats); -6015 } else { -6016 break; -6017 } -6018 } -6019 -6020 // REMOVE_ADDRESS -6021 while space_id == SpaceId::Data -6022 && !path_exclusive_only -6023 && frame::RemoveAddress::SIZE_BOUND <= builder.frame_space_remaining() -6024 { -6025 if let Some(removed_address) = space.pending.remove_address.pop_last() { -6026 builder.write_frame(removed_address, stats); -6027 } else { -6028 break; -6029 } -6030 } -6031 } -6032 -6033 /// Write pending ACKs into a buffer -6034 fn populate_acks<'a, 'b>( -6035 now: Instant, -6036 receiving_ecn: bool, -6037 path_id: PathId, -6038 space_id: SpaceId, -6039 space: &mut PacketSpace, -6040 is_multipath_negotiated: bool, -6041 builder: &mut PacketBuilder<'a, 'b>, -6042 stats: &mut FrameStats, -6043 space_has_keys: bool, -6044 ) { -6045 // 0-RTT packets must never carry acks (which would have to be of handshake packets) -6046 debug_assert!(space_has_keys, "tried to send ACK in 0-RTT"); -6047 -6048 debug_assert!( -6049 is_multipath_negotiated || path_id == PathId::ZERO, -6050 "Only PathId::ZERO allowed without multipath (have {path_id:?})" -6051 ); -6052 if is_multipath_negotiated { -6053 debug_assert!( -6054 space_id == SpaceId::Data || path_id == PathId::ZERO, -6055 "path acks must be sent in 1RTT space (have {space_id:?})" -6056 ); -6057 } -6058 -6059 let pns = space.for_path(path_id); -6060 let ranges = pns.pending_acks.ranges(); -6061 debug_assert!(!ranges.is_empty(), "can not send empty ACK range"); -6062 let ecn = if receiving_ecn { -6063 Some(&pns.ecn_counters) -6064 } else { -6065 None -6066 }; -6067 -6068 let delay_micros = pns.pending_acks.ack_delay(now).as_micros() as u64; -6069 // TODO: This should come from `TransportConfig` if that gets configurable. -6070 let ack_delay_exp = TransportParameters::default().ack_delay_exponent; -6071 let delay = delay_micros >> ack_delay_exp.into_inner(); +6002 builder.write_frame(new_token, stats); +6003 builder.retransmits_mut().new_tokens.push(network_path); +6004 } +6005 } +6006 +6007 // STREAM +6008 if !path_exclusive_only && space_id == SpaceId::Data { +6009 self.streams +6010 .write_stream_frames(builder, self.config.send_fairness, stats); +6011 } +6012 +6013 // ADD_ADDRESS +6014 while space_id == SpaceId::Data +6015 && !path_exclusive_only +6016 && frame::AddAddress::SIZE_BOUND <= builder.frame_space_remaining() +6017 { +6018 if let Some(added_address) = space.pending.add_address.pop_last() { +6019 builder.write_frame(added_address, stats); +6020 } else { +6021 break; +6022 } +6023 } +6024 +6025 // REMOVE_ADDRESS +6026 while space_id == SpaceId::Data +6027 && !path_exclusive_only +6028 && frame::RemoveAddress::SIZE_BOUND <= builder.frame_space_remaining() +6029 { +6030 if let Some(removed_address) = space.pending.remove_address.pop_last() { +6031 builder.write_frame(removed_address, stats); +6032 } else { +6033 break; +6034 } +6035 } +6036 } +6037 +6038 /// Write pending ACKs into a buffer +6039 fn populate_acks<'a, 'b>( +6040 now: Instant, +6041 receiving_ecn: bool, +6042 path_id: PathId, +6043 space_id: SpaceId, +6044 space: &mut PacketSpace, +6045 is_multipath_negotiated: bool, +6046 builder: &mut PacketBuilder<'a, 'b>, +6047 stats: &mut FrameStats, +6048 space_has_keys: bool, +6049 ) { +6050 // 0-RTT packets must never carry acks (which would have to be of handshake packets) +6051 debug_assert!(space_has_keys, "tried to send ACK in 0-RTT"); +6052 +6053 debug_assert!( +6054 is_multipath_negotiated || path_id == PathId::ZERO, +6055 "Only PathId::ZERO allowed without multipath (have {path_id:?})" +6056 ); +6057 if is_multipath_negotiated { +6058 debug_assert!( +6059 space_id == SpaceId::Data || path_id == PathId::ZERO, +6060 "path acks must be sent in 1RTT space (have {space_id:?})" +6061 ); +6062 } +6063 +6064 let pns = space.for_path(path_id); +6065 let ranges = pns.pending_acks.ranges(); +6066 debug_assert!(!ranges.is_empty(), "can not send empty ACK range"); +6067 let ecn = if receiving_ecn { +6068 Some(&pns.ecn_counters) +6069 } else { +6070 None +6071 }; 6072 -6073 if is_multipath_negotiated && space_id == SpaceId::Data { -6074 if !ranges.is_empty() { -6075 let frame = frame::PathAck::encoder(path_id, delay, ranges, ecn); -6076 builder.write_frame(frame, stats); -6077 } -6078 } else { -6079 builder.write_frame(frame::Ack::encoder(delay, ranges, ecn), stats); -6080 } -6081 } -6082 -6083 fn close_common(&mut self) { -6084 trace!("connection closed"); -6085 self.timers.reset(); +6073 let delay_micros = pns.pending_acks.ack_delay(now).as_micros() as u64; +6074 // TODO: This should come from `TransportConfig` if that gets configurable. +6075 let ack_delay_exp = TransportParameters::default().ack_delay_exponent; +6076 let delay = delay_micros >> ack_delay_exp.into_inner(); +6077 +6078 if is_multipath_negotiated && space_id == SpaceId::Data { +6079 if !ranges.is_empty() { +6080 let frame = frame::PathAck::encoder(path_id, delay, ranges, ecn); +6081 builder.write_frame(frame, stats); +6082 } +6083 } else { +6084 builder.write_frame(frame::Ack::encoder(delay, ranges, ecn), stats); +6085 } 6086 } 6087 -6088 fn set_close_timer(&mut self, now: Instant) { -6089 // QUIC-MULTIPATH § 2.6 Connection Closure: draining for 3*PTO using the max PTO of -6090 // all paths. -6091 let pto_max = self.max_pto_for_space(self.highest_space); -6092 self.timers.set( -6093 Timer::Conn(ConnTimer::Close), -6094 now + 3 * pto_max, -6095 self.qlog.with_time(now), -6096 ); -6097 } -6098 -6099 /// Handle transport parameters received from the peer -6100 /// -6101 /// *remote_cid* and *local_cid* are the source and destination CIDs respectively of the -6102 /// *packet into which the transport parameters arrived. -6103 fn handle_peer_params( -6104 &mut self, -6105 params: TransportParameters, -6106 local_cid: ConnectionId, -6107 remote_cid: ConnectionId, -6108 now: Instant, -6109 ) -> Result<(), TransportError> { -6110 if Some(self.original_remote_cid) != params.initial_src_cid -6111 || (self.side.is_client() -6112 && (Some(self.initial_dst_cid) != params.original_dst_cid -6113 || self.retry_src_cid != params.retry_src_cid)) -6114 { -6115 return Err(TransportError::TRANSPORT_PARAMETER_ERROR( -6116 "CID authentication failure", -6117 )); -6118 } -6119 if params.initial_max_path_id.is_some() && (local_cid.is_empty() || remote_cid.is_empty()) { -6120 return Err(TransportError::PROTOCOL_VIOLATION( -6121 "multipath must not use zero-length CIDs", +6088 fn close_common(&mut self) { +6089 trace!("connection closed"); +6090 self.timers.reset(); +6091 } +6092 +6093 fn set_close_timer(&mut self, now: Instant) { +6094 // QUIC-MULTIPATH § 2.6 Connection Closure: draining for 3*PTO using the max PTO of +6095 // all paths. +6096 let pto_max = self.max_pto_for_space(self.highest_space); +6097 self.timers.set( +6098 Timer::Conn(ConnTimer::Close), +6099 now + 3 * pto_max, +6100 self.qlog.with_time(now), +6101 ); +6102 } +6103 +6104 /// Handle transport parameters received from the peer +6105 /// +6106 /// *remote_cid* and *local_cid* are the source and destination CIDs respectively of the +6107 /// *packet into which the transport parameters arrived. +6108 fn handle_peer_params( +6109 &mut self, +6110 params: TransportParameters, +6111 local_cid: ConnectionId, +6112 remote_cid: ConnectionId, +6113 now: Instant, +6114 ) -> Result<(), TransportError> { +6115 if Some(self.original_remote_cid) != params.initial_src_cid +6116 || (self.side.is_client() +6117 && (Some(self.initial_dst_cid) != params.original_dst_cid +6118 || self.retry_src_cid != params.retry_src_cid)) +6119 { +6120 return Err(TransportError::TRANSPORT_PARAMETER_ERROR( +6121 "CID authentication failure", 6122 )); 6123 } -6124 -6125 self.set_peer_params(params); -6126 self.qlog.emit_peer_transport_params_received(self, now); -6127 -6128 Ok(()) -6129 } -6130 -6131 fn set_peer_params(&mut self, params: TransportParameters) { -6132 self.streams.set_params(&params); -6133 self.idle_timeout = -6134 negotiate_max_idle_timeout(self.config.max_idle_timeout, Some(params.max_idle_timeout)); -6135 trace!("negotiated max idle timeout {:?}", self.idle_timeout); -6136 -6137 if let Some(ref info) = params.preferred_address { -6138 // During the handshake PathId::ZERO exists. -6139 self.remote_cids.get_mut(&PathId::ZERO).expect("not yet abandoned").insert(frame::NewConnectionId { -6140 path_id: None, -6141 sequence: 1, -6142 id: info.connection_id, -6143 reset_token: info.stateless_reset_token, -6144 retire_prior_to: 0, -6145 }) -6146 .expect( -6147 "preferred address CID is the first received, and hence is guaranteed to be legal", -6148 ); -6149 let remote = self.path_data(PathId::ZERO).network_path.remote; -6150 self.set_reset_token(PathId::ZERO, remote, info.stateless_reset_token); -6151 } -6152 self.ack_frequency.peer_max_ack_delay = get_max_ack_delay(&params); -6153 -6154 let mut multipath_enabled = None; -6155 if let (Some(local_max_path_id), Some(remote_max_path_id)) = ( -6156 self.config.get_initial_max_path_id(), -6157 params.initial_max_path_id, -6158 ) { -6159 // multipath is enabled, register the local and remote maximums -6160 self.local_max_path_id = local_max_path_id; -6161 self.remote_max_path_id = remote_max_path_id; -6162 let initial_max_path_id = local_max_path_id.min(remote_max_path_id); -6163 debug!(%initial_max_path_id, "multipath negotiated"); -6164 multipath_enabled = Some(initial_max_path_id); -6165 } -6166 -6167 if let Some((max_locally_allowed_remote_addresses, max_remotely_allowed_remote_addresses)) = -6168 self.config -6169 .max_remote_nat_traversal_addresses -6170 .zip(params.max_remote_nat_traversal_addresses) -6171 { -6172 if let Some(max_initial_paths) = -6173 multipath_enabled.map(|path_id| path_id.saturating_add(1u8)) -6174 { -6175 let max_local_addresses = max_remotely_allowed_remote_addresses.get(); -6176 let max_remote_addresses = max_locally_allowed_remote_addresses.get(); -6177 self.iroh_hp = -6178 iroh_hp::State::new(max_remote_addresses, max_local_addresses, self.side()); -6179 debug!( -6180 %max_remote_addresses, %max_local_addresses, -6181 "iroh hole punching negotiated" -6182 ); -6183 -6184 match self.side() { -6185 Side::Client => { -6186 if max_initial_paths.as_u32() < max_remote_addresses as u32 + 1 { -6187 // in this case the client might try to open `max_remote_addresses` new -6188 // paths, but the current multipath configuration will not allow it -6189 warn!(%max_initial_paths, %max_remote_addresses, "local client configuration might cause nat traversal issues") -6190 } else if max_local_addresses as u64 -6191 > params.active_connection_id_limit.into_inner() -6192 { -6193 // the server allows us to send at most `params.active_connection_id_limit` -6194 // but they might need at least `max_local_addresses` to effectively send -6195 // `PATH_CHALLENGE` frames to each advertised local address -6196 warn!(%max_local_addresses, remote_cid_limit=%params.active_connection_id_limit.into_inner(), "remote server configuration might cause nat traversal issues") -6197 } -6198 } -6199 Side::Server => { -6200 if (max_initial_paths.as_u32() as u64) < crate::LOCAL_CID_COUNT { -6201 warn!(%max_initial_paths, local_cid_limit=%crate::LOCAL_CID_COUNT, "local server configuration might cause nat traversal issues") +6124 if params.initial_max_path_id.is_some() && (local_cid.is_empty() || remote_cid.is_empty()) { +6125 return Err(TransportError::PROTOCOL_VIOLATION( +6126 "multipath must not use zero-length CIDs", +6127 )); +6128 } +6129 +6130 self.set_peer_params(params); +6131 self.qlog.emit_peer_transport_params_received(self, now); +6132 +6133 Ok(()) +6134 } +6135 +6136 fn set_peer_params(&mut self, params: TransportParameters) { +6137 self.streams.set_params(&params); +6138 self.idle_timeout = +6139 negotiate_max_idle_timeout(self.config.max_idle_timeout, Some(params.max_idle_timeout)); +6140 trace!("negotiated max idle timeout {:?}", self.idle_timeout); +6141 +6142 if let Some(ref info) = params.preferred_address { +6143 // During the handshake PathId::ZERO exists. +6144 self.remote_cids.get_mut(&PathId::ZERO).expect("not yet abandoned").insert(frame::NewConnectionId { +6145 path_id: None, +6146 sequence: 1, +6147 id: info.connection_id, +6148 reset_token: info.stateless_reset_token, +6149 retire_prior_to: 0, +6150 }) +6151 .expect( +6152 "preferred address CID is the first received, and hence is guaranteed to be legal", +6153 ); +6154 let remote = self.path_data(PathId::ZERO).network_path.remote; +6155 self.set_reset_token(PathId::ZERO, remote, info.stateless_reset_token); +6156 } +6157 self.ack_frequency.peer_max_ack_delay = get_max_ack_delay(&params); +6158 +6159 let mut multipath_enabled = None; +6160 if let (Some(local_max_path_id), Some(remote_max_path_id)) = ( +6161 self.config.get_initial_max_path_id(), +6162 params.initial_max_path_id, +6163 ) { +6164 // multipath is enabled, register the local and remote maximums +6165 self.local_max_path_id = local_max_path_id; +6166 self.remote_max_path_id = remote_max_path_id; +6167 let initial_max_path_id = local_max_path_id.min(remote_max_path_id); +6168 debug!(%initial_max_path_id, "multipath negotiated"); +6169 multipath_enabled = Some(initial_max_path_id); +6170 } +6171 +6172 if let Some((max_locally_allowed_remote_addresses, max_remotely_allowed_remote_addresses)) = +6173 self.config +6174 .max_remote_nat_traversal_addresses +6175 .zip(params.max_remote_nat_traversal_addresses) +6176 { +6177 if let Some(max_initial_paths) = +6178 multipath_enabled.map(|path_id| path_id.saturating_add(1u8)) +6179 { +6180 let max_local_addresses = max_remotely_allowed_remote_addresses.get(); +6181 let max_remote_addresses = max_locally_allowed_remote_addresses.get(); +6182 self.iroh_hp = +6183 iroh_hp::State::new(max_remote_addresses, max_local_addresses, self.side()); +6184 debug!( +6185 %max_remote_addresses, %max_local_addresses, +6186 "iroh hole punching negotiated" +6187 ); +6188 +6189 match self.side() { +6190 Side::Client => { +6191 if max_initial_paths.as_u32() < max_remote_addresses as u32 + 1 { +6192 // in this case the client might try to open `max_remote_addresses` new +6193 // paths, but the current multipath configuration will not allow it +6194 warn!(%max_initial_paths, %max_remote_addresses, "local client configuration might cause nat traversal issues") +6195 } else if max_local_addresses as u64 +6196 > params.active_connection_id_limit.into_inner() +6197 { +6198 // the server allows us to send at most `params.active_connection_id_limit` +6199 // but they might need at least `max_local_addresses` to effectively send +6200 // `PATH_CHALLENGE` frames to each advertised local address +6201 warn!(%max_local_addresses, remote_cid_limit=%params.active_connection_id_limit.into_inner(), "remote server configuration might cause nat traversal issues") 6202 } 6203 } -6204 } -6205 } else { -6206 debug!("iroh nat traversal enabled for both endpoints, but multipath is missing") -6207 } -6208 } -6209 -6210 self.peer_params = params; -6211 let peer_max_udp_payload_size = -6212 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()).unwrap_or(u16::MAX); -6213 self.path_data_mut(PathId::ZERO) -6214 .mtud -6215 .on_peer_max_udp_payload_size_received(peer_max_udp_payload_size); -6216 } -6217 -6218 /// Decrypts a packet, returning the packet number on success -6219 fn decrypt_packet( -6220 &mut self, -6221 now: Instant, -6222 path_id: PathId, -6223 packet: &mut Packet, -6224 ) -> Result<Option<u64>, Option<TransportError>> { -6225 let result = self -6226 .crypto_state -6227 .decrypt_packet_body(packet, path_id, &self.spaces)?; -6228 -6229 let result = match result { -6230 Some(r) => r, -6231 None => return Ok(None), -6232 }; +6204 Side::Server => { +6205 if (max_initial_paths.as_u32() as u64) < crate::LOCAL_CID_COUNT { +6206 warn!(%max_initial_paths, local_cid_limit=%crate::LOCAL_CID_COUNT, "local server configuration might cause nat traversal issues") +6207 } +6208 } +6209 } +6210 } else { +6211 debug!("iroh nat traversal enabled for both endpoints, but multipath is missing") +6212 } +6213 } +6214 +6215 self.peer_params = params; +6216 let peer_max_udp_payload_size = +6217 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()).unwrap_or(u16::MAX); +6218 self.path_data_mut(PathId::ZERO) +6219 .mtud +6220 .on_peer_max_udp_payload_size_received(peer_max_udp_payload_size); +6221 } +6222 +6223 /// Decrypts a packet, returning the packet number on success +6224 fn decrypt_packet( +6225 &mut self, +6226 now: Instant, +6227 path_id: PathId, +6228 packet: &mut Packet, +6229 ) -> Result<Option<u64>, Option<TransportError>> { +6230 let result = self +6231 .crypto_state +6232 .decrypt_packet_body(packet, path_id, &self.spaces)?; 6233 -6234 if result.outgoing_key_update_acked -6235 && let Some(prev) = self.crypto_state.prev_crypto.as_mut() -6236 { -6237 prev.end_packet = Some((result.number, now)); -6238 self.set_key_discard_timer(now, packet.header.space()); -6239 } -6240 -6241 if result.incoming_key_update { -6242 trace!("key update authenticated"); -6243 self.crypto_state -6244 .update_keys(Some((result.number, now)), true); -6245 self.set_key_discard_timer(now, packet.header.space()); -6246 } -6247 -6248 Ok(Some(result.number)) -6249 } -6250 -6251 fn peer_supports_ack_frequency(&self) -> bool { -6252 self.peer_params.min_ack_delay.is_some() -6253 } -6254 -6255 /// Send an IMMEDIATE_ACK frame to the remote endpoint -6256 /// -6257 /// According to the spec, this will result in an error if the remote endpoint does not support -6258 /// the Acknowledgement Frequency extension -6259 pub(crate) fn immediate_ack(&mut self, path_id: PathId) { -6260 debug_assert_eq!( -6261 self.highest_space, -6262 SpaceKind::Data, -6263 "immediate ack must be written in the data space" -6264 ); -6265 self.spaces[SpaceId::Data] -6266 .for_path(path_id) -6267 .immediate_ack_pending = true; -6268 } -6269 -6270 /// Decodes a packet, returning its decrypted payload, so it can be inspected in tests -6271 #[cfg(test)] -6272 pub(crate) fn decode_packet(&self, event: &ConnectionEvent) -> Option<Vec<u8>> { -6273 let (path_id, first_decode, remaining) = match &event.0 { -6274 ConnectionEventInner::Datagram(DatagramConnectionEvent { -6275 path_id, -6276 first_decode, -6277 remaining, -6278 .. -6279 }) => (path_id, first_decode, remaining), -6280 _ => return None, -6281 }; -6282 -6283 if remaining.is_some() { -6284 panic!("Packets should never be coalesced in tests"); -6285 } -6286 -6287 let decrypted_header = self -6288 .crypto_state -6289 .unprotect_header(first_decode.clone(), self.peer_params.stateless_reset_token)?; -6290 -6291 let mut packet = decrypted_header.packet?; -6292 self.crypto_state -6293 .decrypt_packet_body(&mut packet, *path_id, &self.spaces) -6294 .ok()?; +6234 let result = match result { +6235 Some(r) => r, +6236 None => return Ok(None), +6237 }; +6238 +6239 if result.outgoing_key_update_acked +6240 && let Some(prev) = self.crypto_state.prev_crypto.as_mut() +6241 { +6242 prev.end_packet = Some((result.number, now)); +6243 self.set_key_discard_timer(now, packet.header.space()); +6244 } +6245 +6246 if result.incoming_key_update { +6247 trace!("key update authenticated"); +6248 self.crypto_state +6249 .update_keys(Some((result.number, now)), true); +6250 self.set_key_discard_timer(now, packet.header.space()); +6251 } +6252 +6253 Ok(Some(result.number)) +6254 } +6255 +6256 fn peer_supports_ack_frequency(&self) -> bool { +6257 self.peer_params.min_ack_delay.is_some() +6258 } +6259 +6260 /// Send an IMMEDIATE_ACK frame to the remote endpoint +6261 /// +6262 /// According to the spec, this will result in an error if the remote endpoint does not support +6263 /// the Acknowledgement Frequency extension +6264 pub(crate) fn immediate_ack(&mut self, path_id: PathId) { +6265 debug_assert_eq!( +6266 self.highest_space, +6267 SpaceKind::Data, +6268 "immediate ack must be written in the data space" +6269 ); +6270 self.spaces[SpaceId::Data] +6271 .for_path(path_id) +6272 .immediate_ack_pending = true; +6273 } +6274 +6275 /// Decodes a packet, returning its decrypted payload, so it can be inspected in tests +6276 #[cfg(test)] +6277 pub(crate) fn decode_packet(&self, event: &ConnectionEvent) -> Option<Vec<u8>> { +6278 let (path_id, first_decode, remaining) = match &event.0 { +6279 ConnectionEventInner::Datagram(DatagramConnectionEvent { +6280 path_id, +6281 first_decode, +6282 remaining, +6283 .. +6284 }) => (path_id, first_decode, remaining), +6285 _ => return None, +6286 }; +6287 +6288 if remaining.is_some() { +6289 panic!("Packets should never be coalesced in tests"); +6290 } +6291 +6292 let decrypted_header = self +6293 .crypto_state +6294 .unprotect_header(first_decode.clone(), self.peer_params.stateless_reset_token)?; 6295 -6296 Some(packet.payload.to_vec()) -6297 } -6298 -6299 /// The number of bytes of packets containing retransmittable frames that have not been -6300 /// acknowledged or declared lost. -6301 #[cfg(test)] -6302 pub(crate) fn bytes_in_flight(&self) -> u64 { -6303 // TODO(@divma): consider including for multipath? -6304 self.path_data(PathId::ZERO).in_flight.bytes -6305 } -6306 -6307 /// Number of bytes worth of non-ack-only packets that may be sent -6308 #[cfg(test)] -6309 pub(crate) fn congestion_window(&self) -> u64 { -6310 let path = self.path_data(PathId::ZERO); -6311 path.congestion -6312 .window() -6313 .saturating_sub(path.in_flight.bytes) -6314 } -6315 -6316 /// Whether no timers but keepalive, idle, rtt, pushnewcid, and key discard are running -6317 #[cfg(test)] -6318 pub(crate) fn is_idle(&self) -> bool { -6319 let current_timers = self.timers.values(); -6320 current_timers -6321 .into_iter() -6322 .filter(|(timer, _)| { -6323 !matches!( -6324 timer, -6325 Timer::Conn(ConnTimer::KeepAlive) -6326 | Timer::PerPath(_, PathTimer::PathKeepAlive) -6327 | Timer::Conn(ConnTimer::PushNewCid) -6328 | Timer::Conn(ConnTimer::KeyDiscard) -6329 ) -6330 }) -6331 .min_by_key(|(_, time)| *time) -6332 .is_none_or(|(timer, _)| timer == Timer::Conn(ConnTimer::Idle)) -6333 } -6334 -6335 /// Whether explicit congestion notification is in use on outgoing packets. -6336 #[cfg(test)] -6337 pub(crate) fn using_ecn(&self) -> bool { -6338 self.path_data(PathId::ZERO).sending_ecn -6339 } -6340 -6341 /// The number of received bytes in the current path -6342 #[cfg(test)] -6343 pub(crate) fn total_recvd(&self) -> u64 { -6344 self.path_data(PathId::ZERO).total_recvd -6345 } -6346 -6347 #[cfg(test)] -6348 pub(crate) fn active_local_cid_seq(&self) -> (u64, u64) { -6349 self.local_cid_state -6350 .get(&PathId::ZERO) -6351 .unwrap() -6352 .active_seq() -6353 } -6354 -6355 #[cfg(test)] -6356 #[track_caller] -6357 pub(crate) fn active_local_path_cid_seq(&self, path_id: u32) -> (u64, u64) { -6358 self.local_cid_state -6359 .get(&PathId(path_id)) -6360 .unwrap() -6361 .active_seq() -6362 } -6363 -6364 /// Instruct the peer to replace previously issued CIDs by sending a NEW_CONNECTION_ID frame -6365 /// with updated `retire_prior_to` field set to `v` -6366 #[cfg(test)] -6367 pub(crate) fn rotate_local_cid(&mut self, v: u64, now: Instant) { -6368 let n = self -6369 .local_cid_state -6370 .get_mut(&PathId::ZERO) -6371 .unwrap() -6372 .assign_retire_seq(v); -6373 self.endpoint_events -6374 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); -6375 } -6376 -6377 /// Check the current active remote CID sequence for `PathId::ZERO` -6378 #[cfg(test)] -6379 pub(crate) fn active_remote_cid_seq(&self) -> u64 { -6380 self.remote_cids.get(&PathId::ZERO).unwrap().active_seq() -6381 } -6382 -6383 /// Returns the detected maximum udp payload size for the current path -6384 #[cfg(test)] -6385 pub(crate) fn path_mtu(&self, path_id: PathId) -> u16 { -6386 self.path_data(path_id).current_mtu() -6387 } -6388 -6389 /// Triggers path validation on all paths -6390 #[cfg(test)] -6391 pub(crate) fn trigger_path_validation(&mut self) { -6392 for path in self.paths.values_mut() { -6393 path.data.send_new_challenge = true; -6394 } -6395 } -6396 -6397 /// Whether we have 1-RTT data to send -6398 /// -6399 /// This checks for frames that can only be sent in the data space (1-RTT): -6400 /// - Pending PATH_CHALLENGE frames on the active and previous path if just migrated. -6401 /// - Pending PATH_RESPONSE frames. -6402 /// - Pending data to send in STREAM frames. -6403 /// - Pending DATAGRAM frames to send. -6404 /// -6405 /// See also [`PacketSpace::can_send`] which keeps track of all other frame types that -6406 /// may need to be sent. -6407 fn can_send_1rtt(&self, path_id: PathId, max_size: usize) -> SendableFrames { -6408 let path_exclusive = self.paths.get(&path_id).is_some_and(|path| { -6409 path.data.send_new_challenge -6410 || path -6411 .prev -6412 .as_ref() -6413 .is_some_and(|(_, path)| path.send_new_challenge) -6414 || !path.data.path_responses.is_empty() -6415 }); -6416 let other = self.streams.can_send_stream_data() -6417 || self -6418 .datagrams -6419 .outgoing -6420 .front() -6421 .is_some_and(|x| x.size(true) <= max_size); -6422 SendableFrames { -6423 acks: false, -6424 other, -6425 close: false, -6426 path_exclusive, -6427 } -6428 } -6429 -6430 /// Terminate the connection instantly, without sending a close packet -6431 fn kill(&mut self, reason: ConnectionError) { -6432 self.close_common(); -6433 self.state.move_to_drained(Some(reason)); -6434 self.endpoint_events.push_back(EndpointEventInner::Drained); -6435 } -6436 -6437 /// Storage size required for the largest packet that can be transmitted on all currently -6438 /// available paths -6439 /// -6440 /// Buffers passed to [`Connection::poll_transmit`] should be at least this large. -6441 /// -6442 /// When multipath is enabled, this value is the minimum MTU across all available paths. -6443 pub fn current_mtu(&self) -> u16 { -6444 self.paths -6445 .iter() -6446 .filter(|&(path_id, _path_state)| !self.abandoned_paths.contains(path_id)) -6447 .map(|(_path_id, path_state)| path_state.data.current_mtu()) -6448 .min() -6449 .expect("There is always at least one available path") -6450 } -6451 -6452 /// Size of non-frame data for a 1-RTT packet -6453 /// -6454 /// Quantifies space consumed by the QUIC header and AEAD tag. All other bytes in a packet are -6455 /// frames. Changes if the length of the remote connection ID changes, which is expected to be -6456 /// rare. If `pn` is specified, may additionally change unpredictably due to variations in -6457 /// latency and packet loss. -6458 fn predict_1rtt_overhead(&mut self, pn: u64, path: PathId) -> usize { -6459 let pn_len = PacketNumber::new( -6460 pn, -6461 self.spaces[SpaceId::Data] -6462 .for_path(path) -6463 .largest_acked_packet -6464 .unwrap_or(0), -6465 ) -6466 .len(); -6467 -6468 // 1 byte for flags -6469 1 + self -6470 .remote_cids -6471 .get(&path) -6472 .map(|cids| cids.active().len()) -6473 .unwrap_or(20) // Max CID len in QUIC v1 -6474 + pn_len -6475 + self.tag_len_1rtt() -6476 } -6477 -6478 fn predict_1rtt_overhead_no_pn(&self) -> usize { -6479 let pn_len = 4; -6480 -6481 let cid_len = self -6482 .remote_cids -6483 .values() -6484 .map(|cids| cids.active().len()) -6485 .max() -6486 .unwrap_or(20); // Max CID len in QUIC v1 -6487 -6488 // 1 byte for flags -6489 1 + cid_len + pn_len + self.tag_len_1rtt() -6490 } -6491 -6492 fn tag_len_1rtt(&self) -> usize { -6493 // encryption_keys for Data space returns 1-RTT keys if available, otherwise 0-RTT keys -6494 let packet_crypto = self -6495 .crypto_state -6496 .encryption_keys(SpaceKind::Data, self.side.side()) -6497 .map(|(_header, packet, _level)| packet); -6498 // If neither Data nor 0-RTT keys are available, make a reasonable tag length guess. As of -6499 // this writing, all QUIC cipher suites use 16-byte tags. We could return `None` instead, -6500 // but that would needlessly prevent sending datagrams during 0-RTT. -6501 packet_crypto.map_or(16, |x| x.tag_len()) -6502 } -6503 -6504 /// Mark the path as validated, and enqueue NEW_TOKEN frames to be sent as appropriate -6505 fn on_path_validated(&mut self, path_id: PathId) { -6506 self.path_data_mut(path_id).validated = true; -6507 let ConnectionSide::Server { server_config } = &self.side else { -6508 return; -6509 }; -6510 let network_path = self.path_data(path_id).network_path; -6511 let new_tokens = &mut self.spaces[SpaceId::Data as usize].pending.new_tokens; -6512 new_tokens.clear(); -6513 for _ in 0..server_config.validation_token.sent { -6514 new_tokens.push(network_path); -6515 } -6516 } -6517 -6518 /// Handle new path status information: PATH_STATUS_AVAILABLE, PATH_STATUS_BACKUP -6519 fn on_path_status(&mut self, path_id: PathId, status: PathStatus, status_seq_no: VarInt) { -6520 if let Some(path) = self.paths.get_mut(&path_id) { -6521 path.data.status.remote_update(status, status_seq_no); -6522 } else { -6523 debug!("PATH_STATUS_AVAILABLE received unknown path {:?}", path_id); -6524 } -6525 self.events.push_back( -6526 PathEvent::RemoteStatus { -6527 id: path_id, -6528 status, -6529 } -6530 .into(), -6531 ); -6532 } -6533 -6534 /// Returns the maximum [`PathId`] to be used for sending in this connection. -6535 /// -6536 /// This is calculated as minimum between the local and remote's maximums when multipath is -6537 /// enabled, or `None` when disabled. -6538 /// -6539 /// For data that's received, we should use [`Self::local_max_path_id`] instead. -6540 /// The reasoning is that the remote might already have updated to its own newer -6541 /// [`Self::max_path_id`] after sending out a `MAX_PATH_ID` frame, but it got re-ordered. -6542 fn max_path_id(&self) -> Option<PathId> { -6543 if self.is_multipath_negotiated() { -6544 Some(self.remote_max_path_id.min(self.local_max_path_id)) -6545 } else { -6546 None -6547 } -6548 } -6549 -6550 /// Returns whether this connection has a socket that supports IPv6. -6551 /// -6552 /// TODO(matheus23): This is related to quinn endpoint state's `ipv6` bool. We should move that info -6553 /// here instead of trying to hack around not knowing it exactly. -6554 fn is_ipv6(&self) -> bool { -6555 self.paths -6556 .values() -6557 .any(|p| p.data.network_path.remote.is_ipv6()) -6558 } -6559 -6560 /// Add addresses the local endpoint considers are reachable for nat traversal. -6561 pub fn add_nat_traversal_address(&mut self, address: SocketAddr) -> Result<(), iroh_hp::Error> { -6562 if let Some(added) = self.iroh_hp.add_local_address(address)? { -6563 self.spaces[SpaceId::Data].pending.add_address.insert(added); -6564 }; -6565 Ok(()) -6566 } -6567 -6568 /// Removes an address the endpoing no longer considers reachable for nat traversal -6569 /// -6570 /// Addresses not present in the set will be silently ignored. -6571 pub fn remove_nat_traversal_address( -6572 &mut self, -6573 address: SocketAddr, -6574 ) -> Result<(), iroh_hp::Error> { -6575 if let Some(removed) = self.iroh_hp.remove_local_address(address)? { -6576 self.spaces[SpaceId::Data] -6577 .pending -6578 .remove_address -6579 .insert(removed); -6580 } -6581 Ok(()) -6582 } -6583 -6584 /// Get the current local nat traversal addresses -6585 pub fn get_local_nat_traversal_addresses(&self) -> Result<Vec<SocketAddr>, iroh_hp::Error> { -6586 self.iroh_hp.get_local_nat_traversal_addresses() +6296 let mut packet = decrypted_header.packet?; +6297 self.crypto_state +6298 .decrypt_packet_body(&mut packet, *path_id, &self.spaces) +6299 .ok()?; +6300 +6301 Some(packet.payload.to_vec()) +6302 } +6303 +6304 /// The number of bytes of packets containing retransmittable frames that have not been +6305 /// acknowledged or declared lost. +6306 #[cfg(test)] +6307 pub(crate) fn bytes_in_flight(&self) -> u64 { +6308 // TODO(@divma): consider including for multipath? +6309 self.path_data(PathId::ZERO).in_flight.bytes +6310 } +6311 +6312 /// Number of bytes worth of non-ack-only packets that may be sent +6313 #[cfg(test)] +6314 pub(crate) fn congestion_window(&self) -> u64 { +6315 let path = self.path_data(PathId::ZERO); +6316 path.congestion +6317 .window() +6318 .saturating_sub(path.in_flight.bytes) +6319 } +6320 +6321 /// Whether no timers but keepalive, idle, rtt, pushnewcid, and key discard are running +6322 #[cfg(test)] +6323 pub(crate) fn is_idle(&self) -> bool { +6324 let current_timers = self.timers.values(); +6325 current_timers +6326 .into_iter() +6327 .filter(|(timer, _)| { +6328 !matches!( +6329 timer, +6330 Timer::Conn(ConnTimer::KeepAlive) +6331 | Timer::PerPath(_, PathTimer::PathKeepAlive) +6332 | Timer::Conn(ConnTimer::PushNewCid) +6333 | Timer::Conn(ConnTimer::KeyDiscard) +6334 ) +6335 }) +6336 .min_by_key(|(_, time)| *time) +6337 .is_none_or(|(timer, _)| timer == Timer::Conn(ConnTimer::Idle)) +6338 } +6339 +6340 /// Whether explicit congestion notification is in use on outgoing packets. +6341 #[cfg(test)] +6342 pub(crate) fn using_ecn(&self) -> bool { +6343 self.path_data(PathId::ZERO).sending_ecn +6344 } +6345 +6346 /// The number of received bytes in the current path +6347 #[cfg(test)] +6348 pub(crate) fn total_recvd(&self) -> u64 { +6349 self.path_data(PathId::ZERO).total_recvd +6350 } +6351 +6352 #[cfg(test)] +6353 pub(crate) fn active_local_cid_seq(&self) -> (u64, u64) { +6354 self.local_cid_state +6355 .get(&PathId::ZERO) +6356 .unwrap() +6357 .active_seq() +6358 } +6359 +6360 #[cfg(test)] +6361 #[track_caller] +6362 pub(crate) fn active_local_path_cid_seq(&self, path_id: u32) -> (u64, u64) { +6363 self.local_cid_state +6364 .get(&PathId(path_id)) +6365 .unwrap() +6366 .active_seq() +6367 } +6368 +6369 /// Instruct the peer to replace previously issued CIDs by sending a NEW_CONNECTION_ID frame +6370 /// with updated `retire_prior_to` field set to `v` +6371 #[cfg(test)] +6372 pub(crate) fn rotate_local_cid(&mut self, v: u64, now: Instant) { +6373 let n = self +6374 .local_cid_state +6375 .get_mut(&PathId::ZERO) +6376 .unwrap() +6377 .assign_retire_seq(v); +6378 self.endpoint_events +6379 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); +6380 } +6381 +6382 /// Check the current active remote CID sequence for `PathId::ZERO` +6383 #[cfg(test)] +6384 pub(crate) fn active_remote_cid_seq(&self) -> u64 { +6385 self.remote_cids.get(&PathId::ZERO).unwrap().active_seq() +6386 } +6387 +6388 /// Returns the detected maximum udp payload size for the current path +6389 #[cfg(test)] +6390 pub(crate) fn path_mtu(&self, path_id: PathId) -> u16 { +6391 self.path_data(path_id).current_mtu() +6392 } +6393 +6394 /// Triggers path validation on all paths +6395 #[cfg(test)] +6396 pub(crate) fn trigger_path_validation(&mut self) { +6397 for path in self.paths.values_mut() { +6398 path.data.send_new_challenge = true; +6399 } +6400 } +6401 +6402 /// Whether we have 1-RTT data to send +6403 /// +6404 /// This checks for frames that can only be sent in the data space (1-RTT): +6405 /// - Pending PATH_CHALLENGE frames on the active and previous path if just migrated. +6406 /// - Pending PATH_RESPONSE frames. +6407 /// - Pending data to send in STREAM frames. +6408 /// - Pending DATAGRAM frames to send. +6409 /// +6410 /// See also [`PacketSpace::can_send`] which keeps track of all other frame types that +6411 /// may need to be sent. +6412 fn can_send_1rtt(&self, path_id: PathId, max_size: usize) -> SendableFrames { +6413 let path_exclusive = self.paths.get(&path_id).is_some_and(|path| { +6414 path.data.send_new_challenge +6415 || path +6416 .prev +6417 .as_ref() +6418 .is_some_and(|(_, path)| path.send_new_challenge) +6419 || !path.data.path_responses.is_empty() +6420 }); +6421 let other = self.streams.can_send_stream_data() +6422 || self +6423 .datagrams +6424 .outgoing +6425 .front() +6426 .is_some_and(|x| x.size(true) <= max_size); +6427 SendableFrames { +6428 acks: false, +6429 other, +6430 close: false, +6431 path_exclusive, +6432 } +6433 } +6434 +6435 /// Terminate the connection instantly, without sending a close packet +6436 fn kill(&mut self, reason: ConnectionError) { +6437 self.close_common(); +6438 self.state.move_to_drained(Some(reason)); +6439 self.endpoint_events.push_back(EndpointEventInner::Drained); +6440 } +6441 +6442 /// Storage size required for the largest packet that can be transmitted on all currently +6443 /// available paths +6444 /// +6445 /// Buffers passed to [`Connection::poll_transmit`] should be at least this large. +6446 /// +6447 /// When multipath is enabled, this value is the minimum MTU across all available paths. +6448 pub fn current_mtu(&self) -> u16 { +6449 self.paths +6450 .iter() +6451 .filter(|&(path_id, _path_state)| !self.abandoned_paths.contains(path_id)) +6452 .map(|(_path_id, path_state)| path_state.data.current_mtu()) +6453 .min() +6454 .expect("There is always at least one available path") +6455 } +6456 +6457 /// Size of non-frame data for a 1-RTT packet +6458 /// +6459 /// Quantifies space consumed by the QUIC header and AEAD tag. All other bytes in a packet are +6460 /// frames. Changes if the length of the remote connection ID changes, which is expected to be +6461 /// rare. If `pn` is specified, may additionally change unpredictably due to variations in +6462 /// latency and packet loss. +6463 fn predict_1rtt_overhead(&mut self, pn: u64, path: PathId) -> usize { +6464 let pn_len = PacketNumber::new( +6465 pn, +6466 self.spaces[SpaceId::Data] +6467 .for_path(path) +6468 .largest_acked_packet +6469 .unwrap_or(0), +6470 ) +6471 .len(); +6472 +6473 // 1 byte for flags +6474 1 + self +6475 .remote_cids +6476 .get(&path) +6477 .map(|cids| cids.active().len()) +6478 .unwrap_or(20) // Max CID len in QUIC v1 +6479 + pn_len +6480 + self.tag_len_1rtt() +6481 } +6482 +6483 fn predict_1rtt_overhead_no_pn(&self) -> usize { +6484 let pn_len = 4; +6485 +6486 let cid_len = self +6487 .remote_cids +6488 .values() +6489 .map(|cids| cids.active().len()) +6490 .max() +6491 .unwrap_or(20); // Max CID len in QUIC v1 +6492 +6493 // 1 byte for flags +6494 1 + cid_len + pn_len + self.tag_len_1rtt() +6495 } +6496 +6497 fn tag_len_1rtt(&self) -> usize { +6498 // encryption_keys for Data space returns 1-RTT keys if available, otherwise 0-RTT keys +6499 let packet_crypto = self +6500 .crypto_state +6501 .encryption_keys(SpaceKind::Data, self.side.side()) +6502 .map(|(_header, packet, _level)| packet); +6503 // If neither Data nor 0-RTT keys are available, make a reasonable tag length guess. As of +6504 // this writing, all QUIC cipher suites use 16-byte tags. We could return `None` instead, +6505 // but that would needlessly prevent sending datagrams during 0-RTT. +6506 packet_crypto.map_or(16, |x| x.tag_len()) +6507 } +6508 +6509 /// Mark the path as validated, and enqueue NEW_TOKEN frames to be sent as appropriate +6510 fn on_path_validated(&mut self, path_id: PathId) { +6511 self.path_data_mut(path_id).validated = true; +6512 let ConnectionSide::Server { server_config } = &self.side else { +6513 return; +6514 }; +6515 let network_path = self.path_data(path_id).network_path; +6516 let new_tokens = &mut self.spaces[SpaceId::Data as usize].pending.new_tokens; +6517 new_tokens.clear(); +6518 for _ in 0..server_config.validation_token.sent { +6519 new_tokens.push(network_path); +6520 } +6521 } +6522 +6523 /// Handle new path status information: PATH_STATUS_AVAILABLE, PATH_STATUS_BACKUP +6524 fn on_path_status(&mut self, path_id: PathId, status: PathStatus, status_seq_no: VarInt) { +6525 if let Some(path) = self.paths.get_mut(&path_id) { +6526 path.data.status.remote_update(status, status_seq_no); +6527 } else { +6528 debug!("PATH_STATUS_AVAILABLE received unknown path {:?}", path_id); +6529 } +6530 self.events.push_back( +6531 PathEvent::RemoteStatus { +6532 id: path_id, +6533 status, +6534 } +6535 .into(), +6536 ); +6537 } +6538 +6539 /// Returns the maximum [`PathId`] to be used for sending in this connection. +6540 /// +6541 /// This is calculated as minimum between the local and remote's maximums when multipath is +6542 /// enabled, or `None` when disabled. +6543 /// +6544 /// For data that's received, we should use [`Self::local_max_path_id`] instead. +6545 /// The reasoning is that the remote might already have updated to its own newer +6546 /// [`Self::max_path_id`] after sending out a `MAX_PATH_ID` frame, but it got re-ordered. +6547 fn max_path_id(&self) -> Option<PathId> { +6548 if self.is_multipath_negotiated() { +6549 Some(self.remote_max_path_id.min(self.local_max_path_id)) +6550 } else { +6551 None +6552 } +6553 } +6554 +6555 /// Returns whether this connection has a socket that supports IPv6. +6556 /// +6557 /// TODO(matheus23): This is related to quinn endpoint state's `ipv6` bool. We should move that info +6558 /// here instead of trying to hack around not knowing it exactly. +6559 fn is_ipv6(&self) -> bool { +6560 self.paths +6561 .values() +6562 .any(|p| p.data.network_path.remote.is_ipv6()) +6563 } +6564 +6565 /// Add addresses the local endpoint considers are reachable for nat traversal. +6566 pub fn add_nat_traversal_address(&mut self, address: SocketAddr) -> Result<(), iroh_hp::Error> { +6567 if let Some(added) = self.iroh_hp.add_local_address(address)? { +6568 self.spaces[SpaceId::Data].pending.add_address.insert(added); +6569 }; +6570 Ok(()) +6571 } +6572 +6573 /// Removes an address the endpoing no longer considers reachable for nat traversal +6574 /// +6575 /// Addresses not present in the set will be silently ignored. +6576 pub fn remove_nat_traversal_address( +6577 &mut self, +6578 address: SocketAddr, +6579 ) -> Result<(), iroh_hp::Error> { +6580 if let Some(removed) = self.iroh_hp.remove_local_address(address)? { +6581 self.spaces[SpaceId::Data] +6582 .pending +6583 .remove_address +6584 .insert(removed); +6585 } +6586 Ok(()) 6587 } 6588 -6589 /// Get the currently advertised nat traversal addresses by the server -6590 pub fn get_remote_nat_traversal_addresses(&self) -> Result<Vec<SocketAddr>, iroh_hp::Error> { -6591 Ok(self -6592 .iroh_hp -6593 .client_side()? -6594 .get_remote_nat_traversal_addresses()) -6595 } -6596 -6597 /// Attempts to open a path for nat traversal. -6598 /// -6599 /// On success returns the [`PathId`] and remote address of the path. -6600 fn open_nat_traversal_path( -6601 &mut self, -6602 now: Instant, -6603 ip_port: (IpAddr, u16), -6604 ) -> Result<Option<(PathId, SocketAddr)>, PathError> { -6605 let remote = ip_port.into(); -6606 // TODO(matheus23): Probe the correct 4-tuple, instead of only a remote address? -6607 // By specifying None for `local_ip`, we do two things: 1. open_path_ensure won't -6608 // generate two paths to the same remote and 2. we let the OS choose which -6609 // interface to use for sending on that path. -6610 let network_path = FourTuple { -6611 remote, -6612 local_ip: None, -6613 }; -6614 match self.open_path_ensure(network_path, PathStatus::Backup, now) { -6615 Ok((path_id, path_was_known)) => { -6616 if path_was_known { -6617 trace!(%path_id, %remote, "nat traversal: path existed for remote"); -6618 } -6619 Ok(Some((path_id, remote))) -6620 } -6621 Err(e) => { -6622 debug!(%remote, %e, "nat traversal: failed to probe remote"); -6623 Err(e) -6624 } -6625 } -6626 } -6627 -6628 /// Initiates a new nat traversal round -6629 /// -6630 /// A nat traversal round involves advertising the client's local addresses in `REACH_OUT` -6631 /// frames, and initiating probing of the known remote addresses. When a new round is -6632 /// initiated, the previous one is cancelled, and paths that have not been opened are closed. -6633 /// -6634 /// Returns the server addresses that are now being probed. -6635 /// If addresses fail due to spurious errors, these might succeed later and not be returned in -6636 /// this set. -6637 pub fn initiate_nat_traversal_round( -6638 &mut self, -6639 now: Instant, -6640 ) -> Result<Vec<SocketAddr>, iroh_hp::Error> { -6641 if self.state.is_closed() { -6642 return Err(iroh_hp::Error::Closed); -6643 } -6644 -6645 let ipv6 = self.is_ipv6(); -6646 let client_state = self.iroh_hp.client_side_mut()?; -6647 let iroh_hp::NatTraversalRound { -6648 new_round, -6649 reach_out_at, -6650 addresses_to_probe, -6651 prev_round_path_ids, -6652 } = client_state.initiate_nat_traversal_round(ipv6)?; -6653 -6654 trace!(%new_round, reach_out=reach_out_at.len(), to_probe=addresses_to_probe.len(), -6655 "initiating nat traversal round"); -6656 -6657 self.spaces[SpaceId::Data].pending.reach_out = Some((new_round, reach_out_at)); +6589 /// Get the current local nat traversal addresses +6590 pub fn get_local_nat_traversal_addresses(&self) -> Result<Vec<SocketAddr>, iroh_hp::Error> { +6591 self.iroh_hp.get_local_nat_traversal_addresses() +6592 } +6593 +6594 /// Get the currently advertised nat traversal addresses by the server +6595 pub fn get_remote_nat_traversal_addresses(&self) -> Result<Vec<SocketAddr>, iroh_hp::Error> { +6596 Ok(self +6597 .iroh_hp +6598 .client_side()? +6599 .get_remote_nat_traversal_addresses()) +6600 } +6601 +6602 /// Attempts to open a path for nat traversal. +6603 /// +6604 /// On success returns the [`PathId`] and remote address of the path. +6605 fn open_nat_traversal_path( +6606 &mut self, +6607 now: Instant, +6608 ip_port: (IpAddr, u16), +6609 ) -> Result<Option<(PathId, SocketAddr)>, PathError> { +6610 let remote = ip_port.into(); +6611 // TODO(matheus23): Probe the correct 4-tuple, instead of only a remote address? +6612 // By specifying None for `local_ip`, we do two things: 1. open_path_ensure won't +6613 // generate two paths to the same remote and 2. we let the OS choose which +6614 // interface to use for sending on that path. +6615 let network_path = FourTuple { +6616 remote, +6617 local_ip: None, +6618 }; +6619 match self.open_path_ensure(network_path, PathStatus::Backup, now) { +6620 Ok((path_id, path_was_known)) => { +6621 if path_was_known { +6622 trace!(%path_id, %remote, "nat traversal: path existed for remote"); +6623 } +6624 Ok(Some((path_id, remote))) +6625 } +6626 Err(e) => { +6627 debug!(%remote, %e, "nat traversal: failed to probe remote"); +6628 Err(e) +6629 } +6630 } +6631 } +6632 +6633 /// Initiates a new nat traversal round +6634 /// +6635 /// A nat traversal round involves advertising the client's local addresses in `REACH_OUT` +6636 /// frames, and initiating probing of the known remote addresses. When a new round is +6637 /// initiated, the previous one is cancelled, and paths that have not been opened are closed. +6638 /// +6639 /// Returns the server addresses that are now being probed. +6640 /// If addresses fail due to spurious errors, these might succeed later and not be returned in +6641 /// this set. +6642 pub fn initiate_nat_traversal_round( +6643 &mut self, +6644 now: Instant, +6645 ) -> Result<Vec<SocketAddr>, iroh_hp::Error> { +6646 if self.state.is_closed() { +6647 return Err(iroh_hp::Error::Closed); +6648 } +6649 +6650 let ipv6 = self.is_ipv6(); +6651 let client_state = self.iroh_hp.client_side_mut()?; +6652 let iroh_hp::NatTraversalRound { +6653 new_round, +6654 reach_out_at, +6655 addresses_to_probe, +6656 prev_round_path_ids, +6657 } = client_state.initiate_nat_traversal_round(ipv6)?; 6658 -6659 for path_id in prev_round_path_ids { -6660 let Some(path) = self.path(path_id) else { -6661 continue; -6662 }; -6663 let ip = path.network_path.remote.ip(); -6664 let port = path.network_path.remote.port(); -6665 -6666 // We only close paths that aren't validated (thus are working) that we opened -6667 // in a previous round. -6668 // And we only close paths that we don't want to probe anyways. -6669 if !addresses_to_probe -6670 .iter() -6671 .any(|(_, probe)| *probe == (ip, port)) -6672 && !path.validated -6673 && !self.abandoned_paths.contains(&path_id) -6674 { -6675 trace!(%path_id, "closing path from previous round"); -6676 let _ = -6677 self.close_path_inner(now, path_id, PathAbandonReason::NatTraversalRoundEnded); -6678 } -6679 } -6680 -6681 let mut err = None; -6682 -6683 let mut path_ids = Vec::with_capacity(addresses_to_probe.len()); -6684 let mut probed_addresses = Vec::with_capacity(addresses_to_probe.len()); +6659 trace!(%new_round, reach_out=reach_out_at.len(), to_probe=addresses_to_probe.len(), +6660 "initiating nat traversal round"); +6661 +6662 self.spaces[SpaceId::Data].pending.reach_out = Some((new_round, reach_out_at)); +6663 +6664 for path_id in prev_round_path_ids { +6665 let Some(path) = self.path(path_id) else { +6666 continue; +6667 }; +6668 let ip = path.network_path.remote.ip(); +6669 let port = path.network_path.remote.port(); +6670 +6671 // We only close paths that aren't validated (thus are working) that we opened +6672 // in a previous round. +6673 // And we only close paths that we don't want to probe anyways. +6674 if !addresses_to_probe +6675 .iter() +6676 .any(|(_, probe)| *probe == (ip, port)) +6677 && !path.validated +6678 && !self.abandoned_paths.contains(&path_id) +6679 { +6680 trace!(%path_id, "closing path from previous round"); +6681 let _ = +6682 self.close_path_inner(now, path_id, PathAbandonReason::NatTraversalRoundEnded); +6683 } +6684 } 6685 -6686 for (id, address) in addresses_to_probe { -6687 match self.open_nat_traversal_path(now, address) { -6688 Ok(None) => {} -6689 Ok(Some((path_id, remote))) => { -6690 path_ids.push(path_id); -6691 probed_addresses.push(remote); -6692 } -6693 Err(e) => { -6694 self.iroh_hp -6695 .client_side_mut() -6696 .expect("validated") -6697 .report_in_continuation(id, e); -6698 err.get_or_insert(e); -6699 } -6700 } -6701 } -6702 -6703 if let Some(err) = err { -6704 // We failed to probe any addresses, bail out -6705 if probed_addresses.is_empty() { -6706 return Err(iroh_hp::Error::Multipath(err)); -6707 } -6708 } -6709 -6710 self.iroh_hp -6711 .client_side_mut() -6712 .expect("connection side validated") -6713 .set_round_path_ids(path_ids); +6686 let mut err = None; +6687 +6688 let mut path_ids = Vec::with_capacity(addresses_to_probe.len()); +6689 let mut probed_addresses = Vec::with_capacity(addresses_to_probe.len()); +6690 +6691 for (id, address) in addresses_to_probe { +6692 match self.open_nat_traversal_path(now, address) { +6693 Ok(None) => {} +6694 Ok(Some((path_id, remote))) => { +6695 path_ids.push(path_id); +6696 probed_addresses.push(remote); +6697 } +6698 Err(e) => { +6699 self.iroh_hp +6700 .client_side_mut() +6701 .expect("validated") +6702 .report_in_continuation(id, e); +6703 err.get_or_insert(e); +6704 } +6705 } +6706 } +6707 +6708 if let Some(err) = err { +6709 // We failed to probe any addresses, bail out +6710 if probed_addresses.is_empty() { +6711 return Err(iroh_hp::Error::Multipath(err)); +6712 } +6713 } 6714 -6715 Ok(probed_addresses) -6716 } -6717 -6718 /// Attempts to continue a nat traversal round by trying to open paths for pending client probes. -6719 /// -6720 /// If there was nothing to do, it returns `None`. Otherwise it returns whether the path was -6721 /// successfully open. -6722 fn continue_nat_traversal_round(&mut self, now: Instant) -> Option<bool> { -6723 let ipv6 = self.is_ipv6(); -6724 let client_state = self.iroh_hp.client_side_mut().ok()?; -6725 let (id, address) = client_state.continue_nat_traversal_round(ipv6)?; -6726 let open_result = self.open_nat_traversal_path(now, address); -6727 let client_state = self.iroh_hp.client_side_mut().expect("validated"); -6728 match open_result { -6729 Ok(None) => Some(true), -6730 Ok(Some((path_id, _remote))) => { -6731 client_state.add_round_path_id(path_id); -6732 Some(true) -6733 } -6734 Err(e) => { -6735 client_state.report_in_continuation(id, e); -6736 Some(false) -6737 } -6738 } -6739 } -6740} -6741 -6742/// Hints when the caller identifies a network change. -6743pub trait NetworkChangeHint: std::fmt::Debug + 'static { -6744 /// Inform the connection if a path may recover after a network change. -6745 /// -6746 /// After network changes, paths may not be recoverable. In this case, waiting for the path to -6747 /// become idle may take longer than what is desirable. If [`Self::is_path_recoverable`] -6748 /// returns `false`, a multipath-enabled, client-side connection will establish a new path to -6749 /// the same remote, closing the current one, instead of migrating the path. +6715 self.iroh_hp +6716 .client_side_mut() +6717 .expect("connection side validated") +6718 .set_round_path_ids(path_ids); +6719 +6720 Ok(probed_addresses) +6721 } +6722 +6723 /// Attempts to continue a nat traversal round by trying to open paths for pending client probes. +6724 /// +6725 /// If there was nothing to do, it returns `None`. Otherwise it returns whether the path was +6726 /// successfully open. +6727 fn continue_nat_traversal_round(&mut self, now: Instant) -> Option<bool> { +6728 let ipv6 = self.is_ipv6(); +6729 let client_state = self.iroh_hp.client_side_mut().ok()?; +6730 let (id, address) = client_state.continue_nat_traversal_round(ipv6)?; +6731 let open_result = self.open_nat_traversal_path(now, address); +6732 let client_state = self.iroh_hp.client_side_mut().expect("validated"); +6733 match open_result { +6734 Ok(None) => Some(true), +6735 Ok(Some((path_id, _remote))) => { +6736 client_state.add_round_path_id(path_id); +6737 Some(true) +6738 } +6739 Err(e) => { +6740 client_state.report_in_continuation(id, e); +6741 Some(false) +6742 } +6743 } +6744 } +6745} +6746 +6747/// Hints when the caller identifies a network change. +6748pub trait NetworkChangeHint: std::fmt::Debug + 'static { +6749 /// Inform the connection if a path may recover after a network change. 6750 /// -6751 /// Paths that are deemed recoverable will simply be sent a PING for a liveness check. -6752 fn is_path_recoverable(&self, path_id: PathId, network_path: FourTuple) -> bool; -6753} -6754 -6755impl fmt::Debug for Connection { -6756 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -6757 f.debug_struct("Connection") -6758 .field("handshake_cid", &self.handshake_cid) -6759 .finish() -6760 } -6761} -6762 -6763#[derive(Debug, Copy, Clone, PartialEq, Eq)] -6764enum PathBlocked { -6765 No, -6766 AntiAmplification, -6767 Congestion, -6768 Pacing, -6769} -6770 -6771/// Fields of `Connection` specific to it being client-side or server-side -6772enum ConnectionSide { -6773 Client { -6774 /// Sent in every outgoing Initial packet. Always empty after Initial keys are discarded -6775 token: Bytes, -6776 token_store: Arc<dyn TokenStore>, -6777 server_name: String, -6778 }, -6779 Server { -6780 server_config: Arc<ServerConfig>, -6781 }, -6782} -6783 -6784impl ConnectionSide { -6785 fn remote_may_migrate(&self, state: &State) -> bool { -6786 match self { -6787 Self::Server { server_config } => server_config.migration, -6788 Self::Client { .. } => { -6789 if let Some(hs) = state.as_handshake() { -6790 hs.allow_server_migration -6791 } else { -6792 false -6793 } -6794 } -6795 } -6796 } -6797 -6798 fn is_client(&self) -> bool { -6799 self.side().is_client() -6800 } -6801 -6802 fn is_server(&self) -> bool { -6803 self.side().is_server() -6804 } -6805 -6806 fn side(&self) -> Side { -6807 match *self { -6808 Self::Client { .. } => Side::Client, -6809 Self::Server { .. } => Side::Server, -6810 } -6811 } -6812} -6813 -6814impl From<SideArgs> for ConnectionSide { -6815 fn from(side: SideArgs) -> Self { -6816 match side { -6817 SideArgs::Client { -6818 token_store, -6819 server_name, -6820 } => Self::Client { -6821 token: token_store.take(&server_name).unwrap_or_default(), -6822 token_store, -6823 server_name, -6824 }, -6825 SideArgs::Server { -6826 server_config, -6827 pref_addr_cid: _, -6828 path_validated: _, -6829 } => Self::Server { server_config }, -6830 } -6831 } -6832} -6833 -6834/// Parameters to `Connection::new` specific to it being client-side or server-side -6835pub(crate) enum SideArgs { -6836 Client { -6837 token_store: Arc<dyn TokenStore>, -6838 server_name: String, -6839 }, -6840 Server { -6841 server_config: Arc<ServerConfig>, -6842 pref_addr_cid: Option<ConnectionId>, -6843 path_validated: bool, +6751 /// After network changes, paths may not be recoverable. In this case, waiting for the path to +6752 /// become idle may take longer than what is desirable. If [`Self::is_path_recoverable`] +6753 /// returns `false`, a multipath-enabled, client-side connection will establish a new path to +6754 /// the same remote, closing the current one, instead of migrating the path. +6755 /// +6756 /// Paths that are deemed recoverable will simply be sent a PING for a liveness check. +6757 fn is_path_recoverable(&self, path_id: PathId, network_path: FourTuple) -> bool; +6758} +6759 +6760impl fmt::Debug for Connection { +6761 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +6762 f.debug_struct("Connection") +6763 .field("handshake_cid", &self.handshake_cid) +6764 .finish() +6765 } +6766} +6767 +6768#[derive(Debug, Copy, Clone, PartialEq, Eq)] +6769enum PathBlocked { +6770 No, +6771 AntiAmplification, +6772 Congestion, +6773 Pacing, +6774} +6775 +6776/// Fields of `Connection` specific to it being client-side or server-side +6777enum ConnectionSide { +6778 Client { +6779 /// Sent in every outgoing Initial packet. Always empty after Initial keys are discarded +6780 token: Bytes, +6781 token_store: Arc<dyn TokenStore>, +6782 server_name: String, +6783 }, +6784 Server { +6785 server_config: Arc<ServerConfig>, +6786 }, +6787} +6788 +6789impl ConnectionSide { +6790 fn remote_may_migrate(&self, state: &State) -> bool { +6791 match self { +6792 Self::Server { server_config } => server_config.migration, +6793 Self::Client { .. } => { +6794 if let Some(hs) = state.as_handshake() { +6795 hs.allow_server_migration +6796 } else { +6797 false +6798 } +6799 } +6800 } +6801 } +6802 +6803 fn is_client(&self) -> bool { +6804 self.side().is_client() +6805 } +6806 +6807 fn is_server(&self) -> bool { +6808 self.side().is_server() +6809 } +6810 +6811 fn side(&self) -> Side { +6812 match *self { +6813 Self::Client { .. } => Side::Client, +6814 Self::Server { .. } => Side::Server, +6815 } +6816 } +6817} +6818 +6819impl From<SideArgs> for ConnectionSide { +6820 fn from(side: SideArgs) -> Self { +6821 match side { +6822 SideArgs::Client { +6823 token_store, +6824 server_name, +6825 } => Self::Client { +6826 token: token_store.take(&server_name).unwrap_or_default(), +6827 token_store, +6828 server_name, +6829 }, +6830 SideArgs::Server { +6831 server_config, +6832 pref_addr_cid: _, +6833 path_validated: _, +6834 } => Self::Server { server_config }, +6835 } +6836 } +6837} +6838 +6839/// Parameters to `Connection::new` specific to it being client-side or server-side +6840pub(crate) enum SideArgs { +6841 Client { +6842 token_store: Arc<dyn TokenStore>, +6843 server_name: String, 6844 }, -6845} -6846 -6847impl SideArgs { -6848 pub(crate) fn pref_addr_cid(&self) -> Option<ConnectionId> { -6849 match *self { -6850 Self::Client { .. } => None, -6851 Self::Server { pref_addr_cid, .. } => pref_addr_cid, -6852 } -6853 } -6854 -6855 pub(crate) fn path_validated(&self) -> bool { -6856 match *self { -6857 Self::Client { .. } => true, -6858 Self::Server { path_validated, .. } => path_validated, -6859 } -6860 } -6861 -6862 pub(crate) fn side(&self) -> Side { -6863 match *self { -6864 Self::Client { .. } => Side::Client, -6865 Self::Server { .. } => Side::Server, -6866 } -6867 } -6868} -6869 -6870/// Reasons why a connection might be lost -6871#[derive(Debug, Error, Clone, PartialEq, Eq)] -6872pub enum ConnectionError { -6873 /// The peer doesn't implement any supported version -6874 #[error("peer doesn't implement any supported version")] -6875 VersionMismatch, -6876 /// The peer violated the QUIC specification as understood by this implementation -6877 #[error(transparent)] -6878 TransportError(#[from] TransportError), -6879 /// The peer's QUIC stack aborted the connection automatically -6880 #[error("aborted by peer: {0}")] -6881 ConnectionClosed(frame::ConnectionClose), -6882 /// The peer closed the connection -6883 #[error("closed by peer: {0}")] -6884 ApplicationClosed(frame::ApplicationClose), -6885 /// The peer is unable to continue processing this connection, usually due to having restarted -6886 #[error("reset by peer")] -6887 Reset, -6888 /// Communication with the peer has lapsed for longer than the negotiated idle timeout -6889 /// -6890 /// If neither side is sending keep-alives, a connection will time out after a long enough idle -6891 /// period even if the peer is still reachable. See also [`TransportConfig::max_idle_timeout()`] -6892 /// and [`TransportConfig::keep_alive_interval()`]. -6893 #[error("timed out")] -6894 TimedOut, -6895 /// The local application closed the connection -6896 #[error("closed")] -6897 LocallyClosed, -6898 /// The connection could not be created because not enough of the CID space is available -6899 /// -6900 /// Try using longer connection IDs. -6901 #[error("CIDs exhausted")] -6902 CidsExhausted, -6903} -6904 -6905impl From<Close> for ConnectionError { -6906 fn from(x: Close) -> Self { -6907 match x { -6908 Close::Connection(reason) => Self::ConnectionClosed(reason), -6909 Close::Application(reason) => Self::ApplicationClosed(reason), -6910 } -6911 } -6912} -6913 -6914// For compatibility with API consumers -6915impl From<ConnectionError> for io::Error { -6916 fn from(x: ConnectionError) -> Self { -6917 use ConnectionError::*; -6918 let kind = match x { -6919 TimedOut => io::ErrorKind::TimedOut, -6920 Reset => io::ErrorKind::ConnectionReset, -6921 ApplicationClosed(_) | ConnectionClosed(_) => io::ErrorKind::ConnectionAborted, -6922 TransportError(_) | VersionMismatch | LocallyClosed | CidsExhausted => { -6923 io::ErrorKind::Other -6924 } -6925 }; -6926 Self::new(kind, x) -6927 } -6928} -6929 -6930/// Errors that might trigger a path being closed -6931// TODO(@divma): maybe needs to be reworked based on what we want to do with the public API -6932#[derive(Debug, Error, PartialEq, Eq, Clone, Copy)] -6933pub enum PathError { -6934 /// The extension was not negotiated with the peer -6935 #[error("multipath extension not negotiated")] -6936 MultipathNotNegotiated, -6937 /// Paths can only be opened client-side -6938 #[error("the server side may not open a path")] -6939 ServerSideNotAllowed, -6940 /// Current limits do not allow us to open more paths -6941 #[error("maximum number of concurrent paths reached")] -6942 MaxPathIdReached, -6943 /// No remote CIDs available to open a new path -6944 #[error("remoted CIDs exhausted")] -6945 RemoteCidsExhausted, -6946 /// Path could not be validated and will be abandoned -6947 #[error("path validation failed")] -6948 ValidationFailed, -6949 /// The remote address for the path is not supported by the endpoint -6950 #[error("invalid remote address")] -6951 InvalidRemoteAddress(SocketAddr), -6952} -6953 -6954/// Errors triggered when abandoning a path -6955#[derive(Debug, Error, Clone, Eq, PartialEq)] -6956pub enum ClosePathError { -6957 /// Multipath is not negotiated -6958 #[error("Multipath extension not negotiated")] -6959 MultipathNotNegotiated, -6960 /// The path is already closed or was never opened -6961 #[error("closed path")] -6962 ClosedPath, -6963 /// This is the last path, which can not be abandoned -6964 #[error("last open path")] -6965 LastOpenPath, -6966} -6967 -6968/// Error when the multipath extension was not negotiated, but attempted to be used. -6969#[derive(Debug, Error, Clone, Copy)] -6970#[error("Multipath extension not negotiated")] -6971pub struct MultipathNotNegotiated { -6972 _private: (), -6973} -6974 -6975/// Events of interest to the application -6976#[derive(Debug)] -6977pub enum Event { -6978 /// The connection's handshake data is ready -6979 HandshakeDataReady, -6980 /// The connection was successfully established -6981 Connected, -6982 /// The TLS handshake was confirmed -6983 HandshakeConfirmed, -6984 /// The connection was lost -6985 /// -6986 /// Emitted if the peer closes the connection or an error is encountered. -6987 ConnectionLost { -6988 /// Reason that the connection was closed -6989 reason: ConnectionError, -6990 }, -6991 /// Stream events -6992 Stream(StreamEvent), -6993 /// One or more application datagrams have been received -6994 DatagramReceived, -6995 /// One or more application datagrams have been sent after blocking -6996 DatagramsUnblocked, -6997 /// (Multi)Path events -6998 Path(PathEvent), -6999 /// Iroh's nat traversal events -7000 NatTraversal(iroh_hp::Event), -7001} -7002 -7003impl From<PathEvent> for Event { -7004 fn from(source: PathEvent) -> Self { -7005 Self::Path(source) -7006 } -7007} -7008 -7009fn get_max_ack_delay(params: &TransportParameters) -> Duration { -7010 Duration::from_micros(params.max_ack_delay.0 * 1000) -7011} -7012 -7013// Prevents overflow and improves behavior in extreme circumstances -7014const MAX_BACKOFF_EXPONENT: u32 = 16; -7015 -7016/// Minimal remaining size to allow packet coalescing, excluding cryptographic tag -7017/// -7018/// This must be at least as large as the header for a well-formed empty packet to be coalesced, -7019/// plus some space for frames. We only care about handshake headers because short header packets -7020/// necessarily have smaller headers, and initial packets are only ever the first packet in a -7021/// datagram (because we coalesce in ascending packet space order and the only reason to split a -7022/// packet is when packet space changes). -7023const MIN_PACKET_SPACE: usize = MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE + 32; -7024 -7025/// Largest amount of space that could be occupied by a Handshake or 0-RTT packet's header -7026/// -7027/// Excludes packet-type-specific fields such as packet number or Initial token -7028// https://www.rfc-editor.org/rfc/rfc9000.html#name-0-rtt: flags + version + dcid len + dcid + -7029// scid len + scid + length + pn -7030const MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE: usize = -7031 1 + 4 + 1 + MAX_CID_SIZE + 1 + MAX_CID_SIZE + VarInt::from_u32(u16::MAX as u32).size() + 4; -7032 -7033#[derive(Default)] -7034struct SentFrames { -7035 retransmits: ThinRetransmits, -7036 /// The packet number of the largest acknowledged packet for each path -7037 largest_acked: FxHashMap<PathId, u64>, -7038 stream_frames: StreamMetaVec, -7039 /// Whether the packet contains non-retransmittable frames (like datagrams) -7040 non_retransmits: bool, -7041 /// If the datagram containing these frames should be padded to the min MTU -7042 requires_padding: bool, -7043} -7044 -7045impl SentFrames { -7046 /// Returns whether the packet contains only ACKs -7047 fn is_ack_only(&self, streams: &StreamsState) -> bool { -7048 !self.largest_acked.is_empty() -7049 && !self.non_retransmits -7050 && self.stream_frames.is_empty() -7051 && self.retransmits.is_empty(streams) -7052 } -7053 -7054 fn retransmits_mut(&mut self) -> &mut Retransmits { -7055 self.retransmits.get_or_create() -7056 } -7057 -7058 fn record_sent_frame(&mut self, frame: frame::EncodableFrame<'_>) { -7059 use frame::EncodableFrame::*; -7060 match frame { -7061 PathAck(path_ack_encoder) => { -7062 if let Some(max) = path_ack_encoder.ranges.max() { -7063 self.largest_acked.insert(path_ack_encoder.path_id, max); -7064 } -7065 } -7066 Ack(ack_encoder) => { -7067 if let Some(max) = ack_encoder.ranges.max() { -7068 self.largest_acked.insert(PathId::ZERO, max); +6845 Server { +6846 server_config: Arc<ServerConfig>, +6847 pref_addr_cid: Option<ConnectionId>, +6848 path_validated: bool, +6849 }, +6850} +6851 +6852impl SideArgs { +6853 pub(crate) fn pref_addr_cid(&self) -> Option<ConnectionId> { +6854 match *self { +6855 Self::Client { .. } => None, +6856 Self::Server { pref_addr_cid, .. } => pref_addr_cid, +6857 } +6858 } +6859 +6860 pub(crate) fn path_validated(&self) -> bool { +6861 match *self { +6862 Self::Client { .. } => true, +6863 Self::Server { path_validated, .. } => path_validated, +6864 } +6865 } +6866 +6867 pub(crate) fn side(&self) -> Side { +6868 match *self { +6869 Self::Client { .. } => Side::Client, +6870 Self::Server { .. } => Side::Server, +6871 } +6872 } +6873} +6874 +6875/// Reasons why a connection might be lost +6876#[derive(Debug, Error, Clone, PartialEq, Eq)] +6877pub enum ConnectionError { +6878 /// The peer doesn't implement any supported version +6879 #[error("peer doesn't implement any supported version")] +6880 VersionMismatch, +6881 /// The peer violated the QUIC specification as understood by this implementation +6882 #[error(transparent)] +6883 TransportError(#[from] TransportError), +6884 /// The peer's QUIC stack aborted the connection automatically +6885 #[error("aborted by peer: {0}")] +6886 ConnectionClosed(frame::ConnectionClose), +6887 /// The peer closed the connection +6888 #[error("closed by peer: {0}")] +6889 ApplicationClosed(frame::ApplicationClose), +6890 /// The peer is unable to continue processing this connection, usually due to having restarted +6891 #[error("reset by peer")] +6892 Reset, +6893 /// Communication with the peer has lapsed for longer than the negotiated idle timeout +6894 /// +6895 /// If neither side is sending keep-alives, a connection will time out after a long enough idle +6896 /// period even if the peer is still reachable. See also [`TransportConfig::max_idle_timeout()`] +6897 /// and [`TransportConfig::keep_alive_interval()`]. +6898 #[error("timed out")] +6899 TimedOut, +6900 /// The local application closed the connection +6901 #[error("closed")] +6902 LocallyClosed, +6903 /// The connection could not be created because not enough of the CID space is available +6904 /// +6905 /// Try using longer connection IDs. +6906 #[error("CIDs exhausted")] +6907 CidsExhausted, +6908} +6909 +6910impl From<Close> for ConnectionError { +6911 fn from(x: Close) -> Self { +6912 match x { +6913 Close::Connection(reason) => Self::ConnectionClosed(reason), +6914 Close::Application(reason) => Self::ApplicationClosed(reason), +6915 } +6916 } +6917} +6918 +6919// For compatibility with API consumers +6920impl From<ConnectionError> for io::Error { +6921 fn from(x: ConnectionError) -> Self { +6922 use ConnectionError::*; +6923 let kind = match x { +6924 TimedOut => io::ErrorKind::TimedOut, +6925 Reset => io::ErrorKind::ConnectionReset, +6926 ApplicationClosed(_) | ConnectionClosed(_) => io::ErrorKind::ConnectionAborted, +6927 TransportError(_) | VersionMismatch | LocallyClosed | CidsExhausted => { +6928 io::ErrorKind::Other +6929 } +6930 }; +6931 Self::new(kind, x) +6932 } +6933} +6934 +6935/// Errors that might trigger a path being closed +6936// TODO(@divma): maybe needs to be reworked based on what we want to do with the public API +6937#[derive(Debug, Error, PartialEq, Eq, Clone, Copy)] +6938pub enum PathError { +6939 /// The extension was not negotiated with the peer +6940 #[error("multipath extension not negotiated")] +6941 MultipathNotNegotiated, +6942 /// Paths can only be opened client-side +6943 #[error("the server side may not open a path")] +6944 ServerSideNotAllowed, +6945 /// Current limits do not allow us to open more paths +6946 #[error("maximum number of concurrent paths reached")] +6947 MaxPathIdReached, +6948 /// No remote CIDs available to open a new path +6949 #[error("remoted CIDs exhausted")] +6950 RemoteCidsExhausted, +6951 /// Path could not be validated and will be abandoned +6952 #[error("path validation failed")] +6953 ValidationFailed, +6954 /// The remote address for the path is not supported by the endpoint +6955 #[error("invalid remote address")] +6956 InvalidRemoteAddress(SocketAddr), +6957} +6958 +6959/// Errors triggered when abandoning a path +6960#[derive(Debug, Error, Clone, Eq, PartialEq)] +6961pub enum ClosePathError { +6962 /// Multipath is not negotiated +6963 #[error("Multipath extension not negotiated")] +6964 MultipathNotNegotiated, +6965 /// The path is already closed or was never opened +6966 #[error("closed path")] +6967 ClosedPath, +6968 /// This is the last path, which can not be abandoned +6969 #[error("last open path")] +6970 LastOpenPath, +6971} +6972 +6973/// Error when the multipath extension was not negotiated, but attempted to be used. +6974#[derive(Debug, Error, Clone, Copy)] +6975#[error("Multipath extension not negotiated")] +6976pub struct MultipathNotNegotiated { +6977 _private: (), +6978} +6979 +6980/// Events of interest to the application +6981#[derive(Debug)] +6982pub enum Event { +6983 /// The connection's handshake data is ready +6984 HandshakeDataReady, +6985 /// The connection was successfully established +6986 Connected, +6987 /// The TLS handshake was confirmed +6988 HandshakeConfirmed, +6989 /// The connection was lost +6990 /// +6991 /// Emitted if the peer closes the connection or an error is encountered. +6992 ConnectionLost { +6993 /// Reason that the connection was closed +6994 reason: ConnectionError, +6995 }, +6996 /// Stream events +6997 Stream(StreamEvent), +6998 /// One or more application datagrams have been received +6999 DatagramReceived, +7000 /// One or more application datagrams have been sent after blocking +7001 DatagramsUnblocked, +7002 /// (Multi)Path events +7003 Path(PathEvent), +7004 /// Iroh's nat traversal events +7005 NatTraversal(iroh_hp::Event), +7006} +7007 +7008impl From<PathEvent> for Event { +7009 fn from(source: PathEvent) -> Self { +7010 Self::Path(source) +7011 } +7012} +7013 +7014fn get_max_ack_delay(params: &TransportParameters) -> Duration { +7015 Duration::from_micros(params.max_ack_delay.0 * 1000) +7016} +7017 +7018// Prevents overflow and improves behavior in extreme circumstances +7019const MAX_BACKOFF_EXPONENT: u32 = 16; +7020 +7021/// Minimal remaining size to allow packet coalescing, excluding cryptographic tag +7022/// +7023/// This must be at least as large as the header for a well-formed empty packet to be coalesced, +7024/// plus some space for frames. We only care about handshake headers because short header packets +7025/// necessarily have smaller headers, and initial packets are only ever the first packet in a +7026/// datagram (because we coalesce in ascending packet space order and the only reason to split a +7027/// packet is when packet space changes). +7028const MIN_PACKET_SPACE: usize = MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE + 32; +7029 +7030/// Largest amount of space that could be occupied by a Handshake or 0-RTT packet's header +7031/// +7032/// Excludes packet-type-specific fields such as packet number or Initial token +7033// https://www.rfc-editor.org/rfc/rfc9000.html#name-0-rtt: flags + version + dcid len + dcid + +7034// scid len + scid + length + pn +7035const MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE: usize = +7036 1 + 4 + 1 + MAX_CID_SIZE + 1 + MAX_CID_SIZE + VarInt::from_u32(u16::MAX as u32).size() + 4; +7037 +7038#[derive(Default)] +7039struct SentFrames { +7040 retransmits: ThinRetransmits, +7041 /// The packet number of the largest acknowledged packet for each path +7042 largest_acked: FxHashMap<PathId, u64>, +7043 stream_frames: StreamMetaVec, +7044 /// Whether the packet contains non-retransmittable frames (like datagrams) +7045 non_retransmits: bool, +7046 /// If the datagram containing these frames should be padded to the min MTU +7047 requires_padding: bool, +7048} +7049 +7050impl SentFrames { +7051 /// Returns whether the packet contains only ACKs +7052 fn is_ack_only(&self, streams: &StreamsState) -> bool { +7053 !self.largest_acked.is_empty() +7054 && !self.non_retransmits +7055 && self.stream_frames.is_empty() +7056 && self.retransmits.is_empty(streams) +7057 } +7058 +7059 fn retransmits_mut(&mut self) -> &mut Retransmits { +7060 self.retransmits.get_or_create() +7061 } +7062 +7063 fn record_sent_frame(&mut self, frame: frame::EncodableFrame<'_>) { +7064 use frame::EncodableFrame::*; +7065 match frame { +7066 PathAck(path_ack_encoder) => { +7067 if let Some(max) = path_ack_encoder.ranges.max() { +7068 self.largest_acked.insert(path_ack_encoder.path_id, max); 7069 } 7070 } -7071 Close(_) => { /* non retransmittable, but after this we don't really care */ } -7072 PathResponse(_) => self.non_retransmits = true, -7073 HandshakeDone(_) => self.retransmits_mut().handshake_done = true, -7074 ReachOut(frame::ReachOut { round, ip, port }) => { -7075 let (recorded_round, reach_outs) = self -7076 .retransmits_mut() -7077 .reach_out -7078 .get_or_insert_with(|| (round, FxHashSet::default())); -7079 // Only record reach outs for the current round or a newer than the recorded one. -7080 if *recorded_round == round { -7081 // Same round, simply append. -7082 reach_outs.insert((ip, port)); -7083 } else if *recorded_round < round { -7084 // New round. -7085 *recorded_round = round; -7086 reach_outs.drain(); -7087 reach_outs.insert((ip, port)); -7088 } else { -7089 // ignore old reach out that was sent -7090 } -7091 } -7092 -7093 ObservedAddr(_) => self.retransmits_mut().observed_addr = true, -7094 Ping(_) => self.non_retransmits = true, -7095 ImmediateAck(_) => self.non_retransmits = true, -7096 AckFrequency(_) => self.retransmits_mut().ack_frequency = true, -7097 PathChallenge(_) => self.non_retransmits = true, -7098 Crypto(crypto) => self.retransmits_mut().crypto.push_back(crypto), -7099 PathAbandon(path_abandon) => { -7100 self.retransmits_mut() -7101 .path_abandon -7102 .entry(path_abandon.path_id) -7103 .or_insert(path_abandon.error_code); -7104 } -7105 PathStatusAvailable(frame::PathStatusAvailable { path_id, .. }) -7106 | PathStatusBackup(frame::PathStatusBackup { path_id, .. }) => { -7107 self.retransmits_mut().path_status.insert(path_id); -7108 } -7109 MaxPathId(_) => self.retransmits_mut().max_path_id = true, -7110 PathsBlocked(_) => self.retransmits_mut().paths_blocked = true, -7111 PathCidsBlocked(path_cids_blocked) => { -7112 self.retransmits_mut() -7113 .path_cids_blocked -7114 .insert(path_cids_blocked.path_id); -7115 } -7116 ResetStream(reset) => self -7117 .retransmits_mut() -7118 .reset_stream -7119 .push((reset.id, reset.error_code)), -7120 StopSending(stop_sending) => self.retransmits_mut().stop_sending.push(stop_sending), -7121 NewConnectionId(new_cid) => self.retransmits_mut().new_cids.push(new_cid.issued()), -7122 RetireConnectionId(retire_cid) => self -7123 .retransmits_mut() -7124 .retire_cids -7125 .push((retire_cid.path_id.unwrap_or_default(), retire_cid.sequence)), -7126 Datagram(_) => self.non_retransmits = true, -7127 NewToken(_) => {} -7128 AddAddress(add_address) => { -7129 self.retransmits_mut().add_address.insert(add_address); -7130 } -7131 RemoveAddress(remove_address) => { -7132 self.retransmits_mut().remove_address.insert(remove_address); -7133 } -7134 StreamMeta(stream_meta_encoder) => self.stream_frames.push(stream_meta_encoder.meta), -7135 MaxData(_) => self.retransmits_mut().max_data = true, -7136 MaxStreamData(max) => { -7137 self.retransmits_mut().max_stream_data.insert(max.id); +7071 Ack(ack_encoder) => { +7072 if let Some(max) = ack_encoder.ranges.max() { +7073 self.largest_acked.insert(PathId::ZERO, max); +7074 } +7075 } +7076 Close(_) => { /* non retransmittable, but after this we don't really care */ } +7077 PathResponse(_) => self.non_retransmits = true, +7078 HandshakeDone(_) => self.retransmits_mut().handshake_done = true, +7079 ReachOut(frame::ReachOut { round, ip, port }) => { +7080 let (recorded_round, reach_outs) = self +7081 .retransmits_mut() +7082 .reach_out +7083 .get_or_insert_with(|| (round, FxHashSet::default())); +7084 // Only record reach outs for the current round or a newer than the recorded one. +7085 if *recorded_round == round { +7086 // Same round, simply append. +7087 reach_outs.insert((ip, port)); +7088 } else if *recorded_round < round { +7089 // New round. +7090 *recorded_round = round; +7091 reach_outs.drain(); +7092 reach_outs.insert((ip, port)); +7093 } else { +7094 // ignore old reach out that was sent +7095 } +7096 } +7097 +7098 ObservedAddr(_) => self.retransmits_mut().observed_addr = true, +7099 Ping(_) => self.non_retransmits = true, +7100 ImmediateAck(_) => self.non_retransmits = true, +7101 AckFrequency(_) => self.retransmits_mut().ack_frequency = true, +7102 PathChallenge(_) => self.non_retransmits = true, +7103 Crypto(crypto) => self.retransmits_mut().crypto.push_back(crypto), +7104 PathAbandon(path_abandon) => { +7105 self.retransmits_mut() +7106 .path_abandon +7107 .entry(path_abandon.path_id) +7108 .or_insert(path_abandon.error_code); +7109 } +7110 PathStatusAvailable(frame::PathStatusAvailable { path_id, .. }) +7111 | PathStatusBackup(frame::PathStatusBackup { path_id, .. }) => { +7112 self.retransmits_mut().path_status.insert(path_id); +7113 } +7114 MaxPathId(_) => self.retransmits_mut().max_path_id = true, +7115 PathsBlocked(_) => self.retransmits_mut().paths_blocked = true, +7116 PathCidsBlocked(path_cids_blocked) => { +7117 self.retransmits_mut() +7118 .path_cids_blocked +7119 .insert(path_cids_blocked.path_id); +7120 } +7121 ResetStream(reset) => self +7122 .retransmits_mut() +7123 .reset_stream +7124 .push((reset.id, reset.error_code)), +7125 StopSending(stop_sending) => self.retransmits_mut().stop_sending.push(stop_sending), +7126 NewConnectionId(new_cid) => self.retransmits_mut().new_cids.push(new_cid.issued()), +7127 RetireConnectionId(retire_cid) => self +7128 .retransmits_mut() +7129 .retire_cids +7130 .push((retire_cid.path_id.unwrap_or_default(), retire_cid.sequence)), +7131 Datagram(_) => self.non_retransmits = true, +7132 NewToken(_) => {} +7133 AddAddress(add_address) => { +7134 self.retransmits_mut().add_address.insert(add_address); +7135 } +7136 RemoveAddress(remove_address) => { +7137 self.retransmits_mut().remove_address.insert(remove_address); 7138 } -7139 MaxStreams(max_streams) => { -7140 self.retransmits_mut().max_stream_id[max_streams.dir as usize] = true -7141 } -7142 } -7143 } -7144} -7145 -7146/// Compute the negotiated idle timeout based on local and remote max_idle_timeout transport parameters. -7147/// -7148/// 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.> -7149/// -7150/// 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.> -7151/// -7152/// Returns the negotiated idle timeout as a `Duration`, or `None` when both endpoints have opted out of idle timeout. -7153fn negotiate_max_idle_timeout(x: Option<VarInt>, y: Option<VarInt>) -> Option<Duration> { -7154 match (x, y) { -7155 (Some(VarInt(0)) | None, Some(VarInt(0)) | None) => None, -7156 (Some(VarInt(0)) | None, Some(y)) => Some(Duration::from_millis(y.0)), -7157 (Some(x), Some(VarInt(0)) | None) => Some(Duration::from_millis(x.0)), -7158 (Some(x), Some(y)) => Some(Duration::from_millis(cmp::min(x, y).0)), -7159 } -7160} -7161 -7162#[cfg(test)] -7163mod tests { -7164 use super::*; -7165 -7166 #[test] -7167 fn negotiate_max_idle_timeout_commutative() { -7168 let test_params = [ -7169 (None, None, None), -7170 (None, Some(VarInt(0)), None), -7171 (None, Some(VarInt(2)), Some(Duration::from_millis(2))), -7172 (Some(VarInt(0)), Some(VarInt(0)), None), -7173 ( -7174 Some(VarInt(2)), -7175 Some(VarInt(0)), -7176 Some(Duration::from_millis(2)), -7177 ), +7139 StreamMeta(stream_meta_encoder) => self.stream_frames.push(stream_meta_encoder.meta), +7140 MaxData(_) => self.retransmits_mut().max_data = true, +7141 MaxStreamData(max) => { +7142 self.retransmits_mut().max_stream_data.insert(max.id); +7143 } +7144 MaxStreams(max_streams) => { +7145 self.retransmits_mut().max_stream_id[max_streams.dir as usize] = true +7146 } +7147 } +7148 } +7149} +7150 +7151/// Compute the negotiated idle timeout based on local and remote max_idle_timeout transport parameters. +7152/// +7153/// 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.> +7154/// +7155/// 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.> +7156/// +7157/// Returns the negotiated idle timeout as a `Duration`, or `None` when both endpoints have opted out of idle timeout. +7158fn negotiate_max_idle_timeout(x: Option<VarInt>, y: Option<VarInt>) -> Option<Duration> { +7159 match (x, y) { +7160 (Some(VarInt(0)) | None, Some(VarInt(0)) | None) => None, +7161 (Some(VarInt(0)) | None, Some(y)) => Some(Duration::from_millis(y.0)), +7162 (Some(x), Some(VarInt(0)) | None) => Some(Duration::from_millis(x.0)), +7163 (Some(x), Some(y)) => Some(Duration::from_millis(cmp::min(x, y).0)), +7164 } +7165} +7166 +7167#[cfg(test)] +7168mod tests { +7169 use super::*; +7170 +7171 #[test] +7172 fn negotiate_max_idle_timeout_commutative() { +7173 let test_params = [ +7174 (None, None, None), +7175 (None, Some(VarInt(0)), None), +7176 (None, Some(VarInt(2)), Some(Duration::from_millis(2))), +7177 (Some(VarInt(0)), Some(VarInt(0)), None), 7178 ( -7179 Some(VarInt(1)), -7180 Some(VarInt(4)), -7181 Some(Duration::from_millis(1)), +7179 Some(VarInt(2)), +7180 Some(VarInt(0)), +7181 Some(Duration::from_millis(2)), 7182 ), -7183 ]; -7184 -7185 for (left, right, result) in test_params { -7186 assert_eq!(negotiate_max_idle_timeout(left, right), result); -7187 assert_eq!(negotiate_max_idle_timeout(right, left), result); -7188 } -7189 } -7190}

\ No newline at end of file +7183 ( +7184 Some(VarInt(1)), +7185 Some(VarInt(4)), +7186 Some(Duration::from_millis(1)), +7187 ), +7188 ]; +7189 +7190 for (left, right, result) in test_params { +7191 assert_eq!(negotiate_max_idle_timeout(left, right), result); +7192 assert_eq!(negotiate_max_idle_timeout(right, left), result); +7193 } +7194 } +7195}

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

\ No newline at end of file +1020impl PathAbandonReason { +1021 /// Returns `true` if the closing of this path was initiated locally. +1022 pub(crate) fn is_locally_initiated(&self) -> bool { +1023 !matches!(self, Self::RemoteAbandoned { .. }) +1024 } +1025 +1026 /// Returns the error code to send with a PATH_ABANDON frame. +1027 pub(crate) fn error_code(&self) -> TransportErrorCode { +1028 match self { +1029 Self::ApplicationClosed { error_code } => (*error_code).into(), +1030 Self::NatTraversalRoundEnded => TransportErrorCode::APPLICATION_ABANDON_PATH, +1031 Self::ValidationFailed | Self::TimedOut | Self::UnusableAfterNetworkChange => { +1032 TransportErrorCode::PATH_UNSTABLE_OR_POOR +1033 } +1034 Self::RemoteAbandoned { error_code } => (*error_code).into(), +1035 } +1036 } +1037} +1038 +1039/// Error from setting path status +1040#[derive(Debug, Error, Clone, PartialEq, Eq)] +1041pub enum SetPathStatusError { +1042 /// Error indicating that a path has not been opened or has already been abandoned +1043 #[error("closed path")] +1044 ClosedPath, +1045 /// Error indicating that this operation requires multipath to be negotiated whereas it hasn't been +1046 #[error("multipath not negotiated")] +1047 MultipathNotNegotiated, +1048} +1049 +1050/// Error indicating that a path has not been opened or has already been abandoned +1051#[derive(Debug, Default, Error, Clone, PartialEq, Eq)] +1052#[error("closed path")] +1053pub struct ClosedPath { +1054 pub(super) _private: (), +1055} +1056 +1057#[cfg(test)] +1058mod tests { +1059 use super::*; +1060 +1061 #[test] +1062 fn test_path_id_saturating_add() { +1063 // add within range behaves normally +1064 let large: PathId = u16::MAX.into(); +1065 let next = u32::from(u16::MAX) + 1; +1066 assert_eq!(large.saturating_add(1u8), PathId::from(next)); +1067 +1068 // outside range saturates +1069 assert_eq!(PathId::MAX.saturating_add(1u8), PathId::MAX) +1070 } +1071}
\ No newline at end of file