From c5586779c245aff796d71508142025d039adb80d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cramfox=E2=80=9D?= <“kasey@n0.computer”> Date: Fri, 5 Dec 2025 16:47:01 -0500 Subject: [PATCH 1/5] add and re-export any quinn types that we use in the public API --- iroh/src/endpoint.rs | 16 ++++++++------ iroh/src/endpoint/connection.rs | 39 +++++++++++++++++---------------- iroh/src/endpoint/quic.rs | 9 ++++---- 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index 54b622b694e..ac9fd06ea07 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -58,14 +58,16 @@ pub use self::{ RemoteEndpointIdError, ZeroRttStatus, }, quic::{ - AcceptBi, AcceptUni, AckFrequencyConfig, AeadKey, ApplicationClose, Chunk, ClosedStream, - ConnectionClose, ConnectionError, ConnectionStats, Controller, ControllerFactory, - CryptoError, CryptoServerConfig, ExportKeyingMaterialError, FrameStats, HandshakeTokenKey, - IdleTimeout, MtuDiscoveryConfig, OpenBi, OpenUni, PathStats, QuicTransportConfig, + AcceptBi, AcceptUni, AckFrequencyConfig, AeadKey, ApplicationClose, BloomTokenLog, Chunk, + ClosedStream, ConnectionClose, ConnectionError, ConnectionStats, Controller, + ControllerFactory, ControllerMetrics, CryptoError, CryptoServerConfig, Dir, + ExportKeyingMaterialError, FrameStats, FrameType, HandshakeTokenKey, IdleTimeout, + MtuDiscoveryConfig, NoneTokenLog, OpenBi, OpenUni, PathStats, QuicTransportConfig, ReadDatagram, ReadError, ReadExactError, ReadToEndError, RecvStream, ResetError, - RetryError, SendDatagramError, SendStream, ServerConfig, Side, StoppedError, StreamId, - TransportError, TransportErrorCode, UdpStats, UnsupportedVersion, VarInt, - VarIntBoundsExceeded, WeakConnectionHandle, WriteError, Written, + RetryError, RttEstimator, SendDatagramError, SendStream, ServerConfig, Side, StoppedError, + StreamId, TimeSource, TokenLog, TransportError, TransportErrorCode, UdpStats, + UnsupportedVersion, ValidationTokenConfig, VarInt, VarIntBoundsExceeded, WriteError, + Written, }, }; pub use crate::magicsock::transports::TransportConfig; diff --git a/iroh/src/endpoint/connection.rs b/iroh/src/endpoint/connection.rs index 38ad9d76d50..dfe7f1dead3 100644 --- a/iroh/src/endpoint/connection.rs +++ b/iroh/src/endpoint/connection.rs @@ -32,16 +32,19 @@ use n0_error::{e, stack_error}; use n0_future::{TryFutureExt, future::Boxed as BoxFuture, time::Duration}; use n0_watcher::Watcher; use pin_project::pin_project; -use quinn::{ - AcceptBi, AcceptUni, ConnectionError, ConnectionStats, OpenBi, OpenUni, ReadDatagram, - RetryError, SendDatagramError, ServerConfig, Side, VarInt, WeakConnectionHandle, -}; +use quinn::WeakConnectionHandle; use quinn_proto::PathId; use tracing::warn; use crate::{ Endpoint, - endpoint::AfterHandshakeOutcome, + endpoint::{ + AfterHandshakeOutcome, + quic::{ + AcceptBi, AcceptUni, ConnectionError, ConnectionStats, OpenBi, OpenUni, ReadDatagram, + RetryError, SendDatagram, SendDatagramError, ServerConfig, Side, VarInt, + }, + }, magicsock::{ RemoteStateActorStoppedError, remote_map::{PathInfoList, PathsWatcher}, @@ -822,20 +825,18 @@ impl Connection { self.inner.send_datagram(data) } - // TODO: It seems `SendDatagram` is not yet exposed by quinn. This has been fixed - // upstream and will be in the next release. - // /// Transmits `data` as an unreliable, unordered application datagram - // /// - // /// Unlike [`send_datagram()`], this method will wait for buffer space during congestion - // /// conditions, which effectively prioritizes old datagrams over new datagrams. - // /// - // /// See [`send_datagram()`] for details. - // /// - // /// [`send_datagram()`]: Connection::send_datagram - // #[inline] - // pub fn send_datagram_wait(&self, data: bytes::Bytes) -> SendDatagram<'_> { - // self.inner.send_datagram_wait(data) - // } + /// Transmits `data` as an unreliable, unordered application datagram + /// + /// Unlike [`send_datagram()`], this method will wait for buffer space during congestion + /// conditions, which effectively prioritizes old datagrams over new datagrams. + /// + /// See [`send_datagram()`] for details. + /// + /// [`send_datagram()`]: Connection::send_datagram + #[inline] + pub fn send_datagram_wait(&self, data: bytes::Bytes) -> SendDatagram<'_> { + self.inner.send_datagram_wait(data) + } /// Computes the maximum size of datagrams that may be passed to [`send_datagram`]. /// diff --git a/iroh/src/endpoint/quic.rs b/iroh/src/endpoint/quic.rs index cde0e5ae98a..1614c2163a3 100644 --- a/iroh/src/endpoint/quic.rs +++ b/iroh/src/endpoint/quic.rs @@ -15,14 +15,15 @@ pub use quinn::{ AcceptBi, AcceptUni, AckFrequencyConfig, ApplicationClose, Chunk, ClosedStream, ConnectionClose, ConnectionError, ConnectionStats, MtuDiscoveryConfig, OpenBi, OpenUni, PathStats, ReadDatagram, ReadError, ReadExactError, ReadToEndError, RecvStream, ResetError, - RetryError, SendDatagramError, SendStream, ServerConfig, Side, StoppedError, StreamId, VarInt, - VarIntBoundsExceeded, WeakConnectionHandle, WriteError, + RetryError, SendDatagram, SendDatagramError, SendStream, ServerConfig, Side, StoppedError, + StreamId, VarInt, VarIntBoundsExceeded, WriteError, }; #[cfg(feature = "qlog")] pub use quinn::{QlogConfig, QlogFactory, QlogFileFactory}; pub use quinn_proto::{ - FrameStats, IdleTimeout, TransportError, TransportErrorCode, UdpStats, Written, - congestion::{Controller, ControllerFactory}, + BloomTokenLog, Dir, FrameStats, FrameType, IdleTimeout, NoneTokenLog, RttEstimator, TimeSource, + TokenLog, TransportError, TransportErrorCode, UdpStats, ValidationTokenConfig, Written, + congestion::{Controller, ControllerFactory, ControllerMetrics}, crypto::{ AeadKey, CryptoError, ExportKeyingMaterialError, HandshakeTokenKey, ServerConfig as CryptoServerConfig, UnsupportedVersion, From 4f9b9d9635fc9278a43fcd6f944f2b1436903b39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cramfox=E2=80=9D?= <“kasey@n0.computer”> Date: Tue, 9 Dec 2025 23:16:49 -0500 Subject: [PATCH 2/5] feat: newtype `quinn::ServerConfig` `ServerConfig` is used in the public API in `Incoming::accept_with`. `quinn::ServerConfig` takes a `TransportConfig`. However, we want to maintain control over the `TransportConfig` fields, so we new typed it to `QuicTransportConfig`. So, we needed to new type `quinn::ServerConfig`, which ensures setting the `QuicTransportConfig` correctly. This also organizes the quinn imports and labels where each type comes from. --- iroh/src/endpoint.rs | 22 +-- iroh/src/endpoint/connection.rs | 12 +- iroh/src/endpoint/quic.rs | 254 ++++++++++++++++++++++++++++++-- 3 files changed, 255 insertions(+), 33 deletions(-) diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index ac9fd06ea07..11af55fe02e 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -58,16 +58,16 @@ pub use self::{ RemoteEndpointIdError, ZeroRttStatus, }, quic::{ - AcceptBi, AcceptUni, AckFrequencyConfig, AeadKey, ApplicationClose, BloomTokenLog, Chunk, - ClosedStream, ConnectionClose, ConnectionError, ConnectionStats, Controller, + AcceptBi, AcceptUni, AckFrequencyConfig, AeadKey, ApplicationClose, Chunk, ClosedStream, + Codec, ConnectionClose, ConnectionError, ConnectionId, ConnectionStats, Controller, ControllerFactory, ControllerMetrics, CryptoError, CryptoServerConfig, Dir, - ExportKeyingMaterialError, FrameStats, FrameType, HandshakeTokenKey, IdleTimeout, - MtuDiscoveryConfig, NoneTokenLog, OpenBi, OpenUni, PathStats, QuicTransportConfig, - ReadDatagram, ReadError, ReadExactError, ReadToEndError, RecvStream, ResetError, - RetryError, RttEstimator, SendDatagramError, SendStream, ServerConfig, Side, StoppedError, - StreamId, TimeSource, TokenLog, TransportError, TransportErrorCode, UdpStats, - UnsupportedVersion, ValidationTokenConfig, VarInt, VarIntBoundsExceeded, WriteError, - Written, + ExportKeyingMaterialError, FrameStats, FrameType, HandshakeTokenKey, HeaderKey, + IdleTimeout, Keys, MtuDiscoveryConfig, OpenBi, OpenUni, PacketKey, PathId, PathStats, + QuicConnectError, QuicTransportConfig, ReadDatagram, ReadError, ReadExactError, + ReadToEndError, RecvStream, ResetError, RttEstimator, SendDatagramError, SendStream, + ServerConfig, Session, Side, StoppedError, StreamId, TimeSource, TokenLog, TokenReuseError, + TransportError, TransportErrorCode, TransportParameters, UdpStats, UnsupportedVersion, + ValidationTokenConfig, VarInt, VarIntBoundsExceeded, WriteError, Written, }, }; pub use crate::magicsock::transports::TransportConfig; @@ -471,11 +471,11 @@ struct StaticConfig { impl StaticConfig { /// Create a [`quinn::ServerConfig`] with the specified ALPN protocols. - fn create_server_config(&self, alpn_protocols: Vec>) -> ServerConfig { + fn create_server_config(&self, alpn_protocols: Vec>) -> quinn::ServerConfig { let quic_server_config = self .tls_config .make_server_config(alpn_protocols, self.keylog); - let mut server_config = ServerConfig::with_crypto(Arc::new(quic_server_config)); + let mut server_config = quinn::ServerConfig::with_crypto(Arc::new(quic_server_config)); server_config.transport_config(self.transport_config.to_arc()); server_config diff --git a/iroh/src/endpoint/connection.rs b/iroh/src/endpoint/connection.rs index dfe7f1dead3..c4619e20aba 100644 --- a/iroh/src/endpoint/connection.rs +++ b/iroh/src/endpoint/connection.rs @@ -21,7 +21,6 @@ use std::{ future::{Future, IntoFuture}, net::{IpAddr, SocketAddr}, pin::Pin, - sync::Arc, task::Poll, }; @@ -42,7 +41,7 @@ use crate::{ AfterHandshakeOutcome, quic::{ AcceptBi, AcceptUni, ConnectionError, ConnectionStats, OpenBi, OpenUni, ReadDatagram, - RetryError, SendDatagram, SendDatagramError, ServerConfig, Side, VarInt, + SendDatagram, SendDatagramError, ServerConfig, Side, VarInt, }, }, magicsock::{ @@ -106,12 +105,9 @@ impl Incoming { /// See [`accept()`] for more details. /// /// [`accept()`]: Incoming::accept - pub fn accept_with( - self, - server_config: Arc, - ) -> Result { + pub fn accept_with(self, server_config: ServerConfig) -> Result { self.inner - .accept_with(server_config) + .accept_with(server_config.to_arc()) .map(|conn| Accepting::new(conn, self.ep)) } @@ -126,7 +122,7 @@ impl Incoming { /// /// Errors if `remote_address_validated()` is true. #[allow(clippy::result_large_err)] - pub fn retry(self) -> Result<(), RetryError> { + pub fn retry(self) -> Result<(), quinn::RetryError> { self.inner.retry() } diff --git a/iroh/src/endpoint/quic.rs b/iroh/src/endpoint/quic.rs index 1614c2163a3..514e199b265 100644 --- a/iroh/src/endpoint/quic.rs +++ b/iroh/src/endpoint/quic.rs @@ -8,26 +8,86 @@ #[cfg(feature = "qlog")] use std::path::Path; -use std::{sync::Arc, time::Duration}; -// Missing still: SendDatagram and ConnectionClose::frame_type's Type. -pub use quinn::{ - AcceptBi, AcceptUni, AckFrequencyConfig, ApplicationClose, Chunk, ClosedStream, - ConnectionClose, ConnectionError, ConnectionStats, MtuDiscoveryConfig, OpenBi, OpenUni, - PathStats, ReadDatagram, ReadError, ReadExactError, ReadToEndError, RecvStream, ResetError, - RetryError, SendDatagram, SendDatagramError, SendStream, ServerConfig, Side, StoppedError, - StreamId, VarInt, VarIntBoundsExceeded, WriteError, +use std::{ + net::{SocketAddrV4, SocketAddrV6}, + sync::Arc, + time::Duration, }; + #[cfg(feature = "qlog")] pub use quinn::{QlogConfig, QlogFactory, QlogFileFactory}; + +/// `quinn` types that are used in the public iroh API. +// Each type is notated with the iroh type or quinn type that uses it. +pub use quinn::{ + AcceptBi, // iroh::endpoint::Connection + AcceptUni, // iroh::endpoint::Connection + AckFrequencyConfig, // iroh::endpoint::quic::QuicTransportConfig + ClosedStream, // iroh::protocol::AcceptError, quinn::RecvStream, quinn::SendStream + ConnectionError, // iroh::endpoint::ConnectError + ConnectionStats, // iroh::endpoint::Connection + Dir, // quinn::StreamId + IdleTimeout, // iroh::endpoint::quic::QuicTransportConfig + MtuDiscoveryConfig, // iroh::endpoint::quic::QuicTransportConfig + OpenBi, // iroh::endpoint::Connection + OpenUni, // iroh::endpoint::Connection + PathStats, // iroh::magicsock::remote_map::remote_state::PathInfo + ReadDatagram, // iroh::endpoint::Connection + ReadError, // quinn::RecvStream + ReadExactError, // quinn::RecvStream + ReadToEndError, // quinn::RecvStream + RecvStream, // quinn::AcceptBi, quinn::AcceptUni, quinn::OpenBi, quinn::OpenUni + ResetError, // quinn::RecvStream + SendDatagram, // iroh::endpoint::Connection + SendDatagramError, // iroh::endpoint::Connection + SendStream, // quinn::AcceptBi, quinn::OpenUni + Side, // iroh::endpoint::Connection, quinn::StreamId, + StoppedError, // quinn::SendStream + StreamId, // quinn::RecvStream + VarInt, // various + VarIntBoundsExceeded, // quinn::VarInt, quinn::IdleTimeout + WriteError, // quinn::SendStream + Written, // quinn::SendStream +}; +/// `quinn_proto` types that are used in the public iroh API. +// Each type is notated with the iroh type or quinn type that uses it. pub use quinn_proto::{ - BloomTokenLog, Dir, FrameStats, FrameType, IdleTimeout, NoneTokenLog, RttEstimator, TimeSource, - TokenLog, TransportError, TransportErrorCode, UdpStats, ValidationTokenConfig, Written, - congestion::{Controller, ControllerFactory, ControllerMetrics}, + ApplicationClose, // quinn::ConnectionError + Chunk, // quinn::RecvStream + ConnectError as QuicConnectError, // iroh::endpoint::ConnectWithOptsError + ConnectionClose, // quinn::ConnectionError + ConnectionId, // quinn_proto::crypto::ServerConfig + FrameStats, // quinn::ConnectionStats + FrameType, // quinn_proto::TransportError + PathId, // quinn_proto::crypto::PacketKey + RttEstimator, // quinn_proto::congestion::Controller + TimeSource, // iroh::endpoint::quic::ServerConfig + TokenLog, // quinn::ValidationTokenConfig + TokenReuseError, // quinn::TokenLog + TransportError, // quinn::ConnectionError + TransportErrorCode, // quinn_proto::TransportError + UdpStats, // quinn::ConnectionStats + ValidationTokenConfig, // iroh::endpoint::quic::::ServerConfig + coding::Codec, // quinn_proto::TransportErrorCode, quinn::StreamId + congestion::{ + Controller, // iroh::endpoint::Connection + ControllerFactory, // iroh::endpoint::quic::QuicTransportConfig + ControllerMetrics, // quinn_proto::congestion::Controller + }, crypto::{ - AeadKey, CryptoError, ExportKeyingMaterialError, HandshakeTokenKey, - ServerConfig as CryptoServerConfig, UnsupportedVersion, + AeadKey, // quinn::HandshakeTokenKey + CryptoError, // quinn_proto::crypto::CryptoError, quinn_proto::crypto::PacketKey + ExportKeyingMaterialError, // iroh::endpoint::Connection + HandshakeTokenKey, // iroh::endpoint::quic::ServerConfig + HeaderKey, // quinn_proto::crypto::Keys + Keys, // quinn_proto::crypto::Session + PacketKey, // quinn_proto::crypto::Keys + ServerConfig as CryptoServerConfig, // iroh::endpoint::quic::ServerConfig + Session, // quinn_proto::crypto::ServerConfig + UnsupportedVersion, // quinn_proto::ConnectError }, + transport_parameters::TransportParameters, // quinn_proto::crypot::ServerConfig }; use tracing::warn; @@ -72,7 +132,7 @@ impl Default for QuicTransportConfig { impl QuicTransportConfig { /// Return an `Arc`-d [`quinn::TransportConfig`] - pub(super) fn to_arc(&self) -> Arc { + pub(crate) fn to_arc(&self) -> Arc { Arc::new(self.0.clone()) } @@ -485,3 +545,169 @@ impl QuicTransportConfig { self } } + +/// Parameters governing incoming connections +/// +/// Default values should be suitable for most internet applications. +// Note: used in `iroh::endpoint::connection::Incoming::accept_with` +// This is new-typed since `quinn::ServerConfig` takes a `TransportConfig`, which we new-type as a `QuicTransportConfig` +#[derive(Debug, Clone)] +pub struct ServerConfig { + inner: quinn::ServerConfig, + transport: QuicTransportConfig, +} + +impl ServerConfig { + /// Transport configuration to use for incoming connections + pub fn transport(&self) -> QuicTransportConfig { + self.transport.clone() + } + + /// TLS configuration used for incoming connections + /// + /// Must be set to use TLS 1.3 only. + pub fn crypto(&self) -> Arc { + self.inner.crypto.clone() + } + + /// Configuration for sending and handling validation tokens + pub fn validation_token(&self) -> ValidationTokenConfig { + self.inner.validation_token.clone() + } + + pub(crate) fn to_arc(&self) -> Arc { + Arc::new(self.inner.clone()) + } + + /// Create a default config with a particular handshake token key + pub fn new(crypto: Arc, token_key: Arc) -> Self { + let mut inner = quinn::ServerConfig::new(crypto, token_key); + let transport = QuicTransportConfig::default(); + inner.transport_config(transport.to_arc()); + Self { inner, transport } + } + + /// Set a custom [`TransportConfig`] + pub fn transport_config(&mut self, transport: QuicTransportConfig) -> &mut Self { + self.inner.transport_config(transport.to_arc()); + self.transport = transport; + self + } + + /// Set a custom [`ValidationTokenConfig`] + pub fn validation_token_config( + &mut self, + validation_token: ValidationTokenConfig, + ) -> &mut Self { + self.inner.validation_token_config(validation_token); + self + } + + /// Private key used to authenticate data included in handshake tokens + pub fn token_key(&mut self, value: Arc) -> &mut Self { + self.inner.token_key(value); + self + } + + /// Duration after a retry token was issued for which it's considered valid + /// + /// Defaults to 15 seconds. + pub fn retry_token_lifetime(&mut self, value: Duration) -> &mut Self { + self.inner.retry_token_lifetime(value); + self + } + + /// Whether to allow clients to migrate to new addresses + /// + /// Improves behavior for clients that move between different internet connections or suffer NAT + /// rebinding. Enabled by default. + pub fn migration(&mut self, value: bool) -> &mut Self { + self.inner.migration(value); + self + } + + /// The preferred IPv4 address that will be communicated to clients during handshaking + /// + /// If the client is able to reach this address, it will switch to it. + pub fn preferred_address_v4(&mut self, address: Option) -> &mut Self { + self.inner.preferred_address_v4(address); + self + } + + /// The preferred IPv6 address that will be communicated to clients during handshaking + /// + /// If the client is able to reach this address, it will switch to it. + pub fn preferred_address_v6(&mut self, address: Option) -> &mut Self { + self.inner.preferred_address_v6(address); + self + } + + /// Maximum number of [`Incoming`][crate::Incoming] to allow to exist at a time + /// + /// An [`Incoming`][crate::Incoming] comes into existence when an incoming connection attempt + /// is received and stops existing when the application either accepts it or otherwise disposes + /// of it. While this limit is reached, new incoming connection attempts are immediately + /// refused. Larger values have greater worst-case memory consumption, but accommodate greater + /// application latency in handling incoming connection attempts. + /// + /// The default value is set to 65536. With a typical Ethernet MTU of 1500 bytes, this limits + /// memory consumption from this to under 100 MiB--a generous amount that still prevents memory + /// exhaustion in most contexts. + pub fn max_incoming(&mut self, max_incoming: usize) -> &mut Self { + self.inner.max_incoming(max_incoming); + self + } + + /// Maximum number of received bytes to buffer for each [`Incoming`][crate::Incoming] + /// + /// An [`Incoming`][crate::Incoming] comes into existence when an incoming connection attempt + /// is received and stops existing when the application either accepts it or otherwise disposes + /// of it. This limit governs only packets received within that period, and does not include + /// the first packet. Packets received in excess of this limit are dropped, which may cause + /// 0-RTT or handshake data to have to be retransmitted. + /// + /// The default value is set to 10 MiB--an amount such that in most situations a client would + /// not transmit that much 0-RTT data faster than the server handles the corresponding + /// [`Incoming`][crate::Incoming]. + pub fn incoming_buffer_size(&mut self, incoming_buffer_size: u64) -> &mut Self { + self.inner.incoming_buffer_size(incoming_buffer_size); + self + } + + /// Maximum number of received bytes to buffer for all [`Incoming`][crate::Incoming] + /// collectively + /// + /// An [`Incoming`][crate::Incoming] comes into existence when an incoming connection attempt + /// is received and stops existing when the application either accepts it or otherwise disposes + /// of it. This limit governs only packets received within that period, and does not include + /// the first packet. Packets received in excess of this limit are dropped, which may cause + /// 0-RTT or handshake data to have to be retransmitted. + /// + /// The default value is set to 100 MiB--a generous amount that still prevents memory + /// exhaustion in most contexts. + pub fn incoming_buffer_size_total(&mut self, incoming_buffer_size_total: u64) -> &mut Self { + self.inner + .incoming_buffer_size_total(incoming_buffer_size_total); + self + } + + /// Object to get current [`SystemTime`] + /// + /// This exists to allow system time to be mocked in tests, or wherever else desired. + /// + /// Defaults to [`StdSystemTime`], which simply calls [`SystemTime::now()`](SystemTime::now). + pub fn time_source(&mut self, time_source: Arc) -> &mut Self { + self.inner.time_source(time_source); + self + } + + /// Create a server config with the given [`crypto::ServerConfig`] + /// + /// Uses a randomized handshake token key. + pub fn with_crypto(crypto: Arc) -> Self { + let mut inner = quinn::ServerConfig::with_crypto(crypto); + let transport = QuicTransportConfig::default(); + inner.transport_config(transport.to_arc()); + Self { inner, transport } + } +} From c144afe64b7aab3f13519f21ebf2e99cff69137d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cramfox=E2=80=9D?= <“kasey@n0.computer”> Date: Tue, 9 Dec 2025 23:20:03 -0500 Subject: [PATCH 3/5] adjust `use quinn` imports to point to `iroh::endpoint::quic`, for types that are re-exported by iroh --- iroh/examples/0rtt.rs | 7 ++- iroh/examples/auth-hook.rs | 5 +- iroh/src/endpoint.rs | 47 +++++++++---------- iroh/src/endpoint/connection.rs | 47 +++++++++++++------ iroh/src/endpoint/hooks.rs | 3 +- iroh/src/endpoint/quic.rs | 36 ++++++++------ iroh/src/magicsock.rs | 15 +++--- iroh/src/magicsock/remote_map/remote_state.rs | 4 +- iroh/src/protocol.rs | 11 ++--- 9 files changed, 100 insertions(+), 75 deletions(-) diff --git a/iroh/examples/0rtt.rs b/iroh/examples/0rtt.rs index eb3fd564b64..eb81df39e8b 100644 --- a/iroh/examples/0rtt.rs +++ b/iroh/examples/0rtt.rs @@ -2,10 +2,13 @@ use std::{env, str::FromStr, time::Instant}; use clap::Parser; use data_encoding::HEXLOWER; -use iroh::{EndpointId, SecretKey, discovery::Discovery, endpoint::ZeroRttStatus}; +use iroh::{ + EndpointId, SecretKey, + discovery::Discovery, + endpoint::{RecvStream, SendStream, ZeroRttStatus}, +}; use n0_error::{Result, StackResultExt, StdResultExt}; use n0_future::StreamExt; -use quinn::{RecvStream, SendStream}; use tracing::{info, trace}; const PINGPONG_ALPN: &[u8] = b"0rtt-pingpong"; diff --git a/iroh/examples/auth-hook.rs b/iroh/examples/auth-hook.rs index 3201f03d53d..3e0d30908ed 100644 --- a/iroh/examples/auth-hook.rs +++ b/iroh/examples/auth-hook.rs @@ -113,12 +113,13 @@ mod auth { use iroh::{ Endpoint, EndpointAddr, EndpointId, - endpoint::{AfterHandshakeOutcome, BeforeConnectOutcome, Connection, EndpointHooks}, + endpoint::{ + AfterHandshakeOutcome, BeforeConnectOutcome, Connection, ConnectionError, EndpointHooks, + }, protocol::{AcceptError, ProtocolHandler}, }; use n0_error::{AnyError, Result, StackResultExt, StdResultExt, anyerr}; use n0_future::task::AbortOnDropHandle; - use quinn::ConnectionError; use tokio::{ sync::{mpsc, oneshot}, task::JoinSet, diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index 11af55fe02e..3f2c6b522f7 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -44,7 +44,7 @@ use crate::{ mod connection; pub(crate) mod hooks; pub mod presets; -mod quic; +pub(crate) mod quic; pub use hooks::{AfterHandshakeOutcome, BeforeConnectOutcome, EndpointHooks}; @@ -55,7 +55,7 @@ pub use self::{ Accept, Accepting, AlpnError, AuthenticationError, Connecting, ConnectingError, Connection, ConnectionInfo, ConnectionState, HandshakeCompleted, Incoming, IncomingZeroRtt, IncomingZeroRttConnection, OutgoingZeroRtt, OutgoingZeroRttConnection, - RemoteEndpointIdError, ZeroRttStatus, + RemoteEndpointIdError, RetryError, ZeroRttStatus, }, quic::{ AcceptBi, AcceptUni, AckFrequencyConfig, AeadKey, ApplicationClose, Chunk, ClosedStream, @@ -64,10 +64,11 @@ pub use self::{ ExportKeyingMaterialError, FrameStats, FrameType, HandshakeTokenKey, HeaderKey, IdleTimeout, Keys, MtuDiscoveryConfig, OpenBi, OpenUni, PacketKey, PathId, PathStats, QuicConnectError, QuicTransportConfig, ReadDatagram, ReadError, ReadExactError, - ReadToEndError, RecvStream, ResetError, RttEstimator, SendDatagramError, SendStream, - ServerConfig, Session, Side, StoppedError, StreamId, TimeSource, TokenLog, TokenReuseError, - TransportError, TransportErrorCode, TransportParameters, UdpStats, UnsupportedVersion, - ValidationTokenConfig, VarInt, VarIntBoundsExceeded, WriteError, Written, + ReadToEndError, RecvStream, ResetError, RttEstimator, SendDatagram, SendDatagramError, + SendStream, ServerConfig, Session, Side, StoppedError, StreamId, TimeSource, TokenLog, + TokenReuseError, TransportError, TransportErrorCode, TransportParameters, UdpStats, + UnsupportedVersion, ValidationTokenConfig, VarInt, VarIntBoundsExceeded, WriteError, + Written, }, }; pub use crate::magicsock::transports::TransportConfig; @@ -527,7 +528,7 @@ pub enum ConnectWithOptsError { #[error("Unable to connect to remote")] Quinn { #[error(std_err)] - source: quinn_proto::ConnectError, + source: QuicConnectError, }, #[error("Internal consistency error")] InternalConsistencyError { @@ -1307,7 +1308,6 @@ mod tests { use n0_error::{AnyError as Error, Result, StdResultExt}; use n0_future::{BufferedStreamExt, StreamExt, stream, time}; use n0_watcher::Watcher; - use quinn::ConnectionError; use rand::SeedableRng; use tokio::sync::oneshot; use tracing::{Instrument, error_span, info, info_span, instrument}; @@ -1317,7 +1317,7 @@ mod tests { use crate::{ RelayMap, RelayMode, discovery::static_provider::StaticProvider, - endpoint::{ConnectOptions, Connection}, + endpoint::{ApplicationClose, ConnectOptions, Connection, ConnectionError}, protocol::{AcceptError, ProtocolHandler, Router}, test_utils::{QlogFileGroup, run_relay_server, run_relay_server_with}, }; @@ -1375,13 +1375,13 @@ mod tests { conn.close(7u8.into(), b"bye"); let res = conn.accept_uni().await; - assert_eq!(res.unwrap_err(), quinn::ConnectionError::LocallyClosed); + assert_eq!(res.unwrap_err(), ConnectionError::LocallyClosed); let res = stream.read_to_end(10).await; assert_eq!( res.unwrap_err(), quinn::ReadToEndError::Read(quinn::ReadError::ConnectionLost( - quinn::ConnectionError::LocallyClosed + ConnectionError::LocallyClosed )) ); info!("server test completed"); @@ -1412,11 +1412,10 @@ mod tests { info!("waiting for closed"); // Remote now closes the connection, we should see an error sometime soon. let err = conn.closed().await; - let expected_err = - quinn::ConnectionError::ApplicationClosed(quinn::ApplicationClose { - error_code: 7u8.into(), - reason: b"bye".to_vec().into(), - }); + let expected_err = ConnectionError::ApplicationClosed(ApplicationClose { + error_code: 7u8.into(), + reason: b"bye".to_vec().into(), + }); assert_eq!(err, expected_err); info!("opening new - expect it to fail"); @@ -1621,7 +1620,7 @@ mod tests { let ep1_nodeaddr = ep1.addr(); #[instrument(name = "client", skip_all)] - async fn connect(ep: Endpoint, dst: EndpointAddr) -> Result { + async fn connect(ep: Endpoint, dst: EndpointAddr) -> Result { info!(me = %ep.id().fmt_short(), "client starting"); let conn = ep.connect(dst, TEST_ALPN).await?; let mut send = conn.open_uni().await.anyerr()?; @@ -1650,7 +1649,7 @@ mod tests { let conn_closed = dbg!(ep2_connect.await.anyerr()??); assert!(matches!( conn_closed, - ConnectionError::ApplicationClosed(quinn::ApplicationClose { .. }) + ConnectionError::ApplicationClosed(ApplicationClose { .. }) )); Ok(()) @@ -1670,7 +1669,7 @@ mod tests { relay_map: RelayMap, node_addr_rx: oneshot::Receiver, qlog: Arc, - ) -> Result { + ) -> Result { let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64); let secret = SecretKey::generate(&mut rng); let ep = Endpoint::builder() @@ -1746,7 +1745,7 @@ mod tests { let conn_closed = dbg!(client_task.await.anyerr()??); assert!(matches!( conn_closed, - ConnectionError::ApplicationClosed(quinn::ApplicationClose { .. }) + ConnectionError::ApplicationClosed(ApplicationClose { .. }) )); Ok(()) @@ -1764,7 +1763,7 @@ mod tests { async fn connect( relay_map: RelayMap, node_addr_rx: oneshot::Receiver, - ) -> Result { + ) -> Result { let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64); let secret = SecretKey::generate(&mut rng); let ep = Endpoint::builder() @@ -1843,7 +1842,7 @@ mod tests { let conn_closed = dbg!(client_task.await.anyerr()??); assert!(matches!( conn_closed, - ConnectionError::ApplicationClosed(quinn::ApplicationClose { .. }) + ConnectionError::ApplicationClosed(ApplicationClose { .. }) )); Ok(()) @@ -1910,7 +1909,7 @@ mod tests { async fn accept( relay_map: RelayMap, node_addr_tx: oneshot::Sender, - ) -> Result { + ) -> Result { let secret = SecretKey::from([1u8; 32]); let ep = Endpoint::builder() .secret_key(secret) @@ -1958,7 +1957,7 @@ mod tests { let conn_closed = dbg!(server_task.await.anyerr()??); assert!(matches!( conn_closed, - ConnectionError::ApplicationClosed(quinn::ApplicationClose { .. }) + ConnectionError::ApplicationClosed(ApplicationClose { .. }) )); Ok(()) diff --git a/iroh/src/endpoint/connection.rs b/iroh/src/endpoint/connection.rs index c4619e20aba..26f60482898 100644 --- a/iroh/src/endpoint/connection.rs +++ b/iroh/src/endpoint/connection.rs @@ -32,7 +32,6 @@ use n0_future::{TryFutureExt, future::Boxed as BoxFuture, time::Duration}; use n0_watcher::Watcher; use pin_project::pin_project; use quinn::WeakConnectionHandle; -use quinn_proto::PathId; use tracing::warn; use crate::{ @@ -40,8 +39,9 @@ use crate::{ endpoint::{ AfterHandshakeOutcome, quic::{ - AcceptBi, AcceptUni, ConnectionError, ConnectionStats, OpenBi, OpenUni, ReadDatagram, - SendDatagram, SendDatagramError, ServerConfig, Side, VarInt, + AcceptBi, AcceptUni, ConnectionError, ConnectionStats, Controller, + ExportKeyingMaterialError, OpenBi, OpenUni, PathId, ReadDatagram, SendDatagram, + SendDatagramError, ServerConfig, Side, VarInt, }, }, magicsock::{ @@ -122,8 +122,10 @@ impl Incoming { /// /// Errors if `remote_address_validated()` is true. #[allow(clippy::result_large_err)] - pub fn retry(self) -> Result<(), quinn::RetryError> { - self.inner.retry() + pub fn retry(self) -> Result<(), RetryError> { + self.inner + .retry() + .map_err(|err| e!(RetryError { err, ep: self.ep })) } /// Ignores this incoming connection attempt, not sending any packet in response. @@ -164,6 +166,24 @@ impl IntoFuture for Incoming { } } +/// Error for attempting to retry an [`Incoming`] which already bears a token from a previous retry +#[stack_error(derive, add_meta, from_sources)] +#[error("retry() with validated Incoming")] +pub struct RetryError { + err: quinn::RetryError, + ep: Endpoint, +} + +impl RetryError { + /// Get the [`Incoming`] + pub fn into_incoming(self) -> Incoming { + Incoming { + inner: self.err.into_incoming(), + ep: self.ep, + } + } +} + /// Adaptor to let [`Incoming`] be `await`ed like a [`Connecting`]. #[derive(derive_more::Debug)] #[debug("IncomingFuture")] @@ -521,7 +541,7 @@ impl Accepting { /// /// See also documentation for [`Connecting::into_0rtt`]. /// - /// [`RecvStream::is_0rtt`]: quinn::RecvStream::is_0rtt + /// [`RecvStream::is_0rtt`]: crate::endpoint::RecvStream::is_0rtt pub fn into_0rtt(self) -> IncomingZeroRttConnection { let (quinn_conn, zrtt_accepted) = self .inner @@ -725,8 +745,8 @@ impl Connection { /// without writing anything to [`SendStream`] will never succeed. /// /// [`open_bi`]: Connection::open_bi - /// [`SendStream`]: quinn::SendStream - /// [`RecvStream`]: quinn::RecvStream + /// [`SendStream`]: crate::endpoint::SendStream + /// [`RecvStream`]: crate::endpoint::RecvStream #[inline] pub fn open_bi(&self) -> OpenBi<'_> { self.inner.open_bi() @@ -746,8 +766,8 @@ impl Connection { /// writing anything to the connected [`SendStream`] will never succeed. /// /// [`open_bi`]: Connection::open_bi - /// [`SendStream`]: quinn::SendStream - /// [`RecvStream`]: quinn::RecvStream + /// [`SendStream`]: crate::endpoint::SendStream + /// [`RecvStream`]: crate::endpoint::RecvStream #[inline] pub fn accept_bi(&self) -> AcceptBi<'_> { self.inner.accept_bi() @@ -876,10 +896,7 @@ impl Connection { /// Current state of the congestion control algorithm, for debugging purposes. #[inline] - pub fn congestion_state( - &self, - path_id: PathId, - ) -> Option> { + pub fn congestion_state(&self, path_id: PathId) -> Option> { self.inner.congestion_state(path_id) } @@ -931,7 +948,7 @@ impl Connection { output: &mut [u8], label: &[u8], context: &[u8], - ) -> Result<(), quinn_proto::crypto::ExportKeyingMaterialError> { + ) -> Result<(), ExportKeyingMaterialError> { self.inner.export_keying_material(output, label, context) } diff --git a/iroh/src/endpoint/hooks.rs b/iroh/src/endpoint/hooks.rs index a60dc5860c5..f0beed79443 100644 --- a/iroh/src/endpoint/hooks.rs +++ b/iroh/src/endpoint/hooks.rs @@ -1,9 +1,8 @@ use std::pin::Pin; use iroh_base::EndpointAddr; -use quinn::VarInt; -use crate::endpoint::connection::ConnectionInfo; +use crate::endpoint::{connection::ConnectionInfo, quic::VarInt}; type BoxFuture<'a, T> = Pin + Send + 'a>>; diff --git a/iroh/src/endpoint/quic.rs b/iroh/src/endpoint/quic.rs index 514e199b265..15f08cfcd36 100644 --- a/iroh/src/endpoint/quic.rs +++ b/iroh/src/endpoint/quic.rs @@ -8,16 +8,12 @@ #[cfg(feature = "qlog")] use std::path::Path; - use std::{ net::{SocketAddrV4, SocketAddrV6}, sync::Arc, time::Duration, }; -#[cfg(feature = "qlog")] -pub use quinn::{QlogConfig, QlogFactory, QlogFileFactory}; - /// `quinn` types that are used in the public iroh API. // Each type is notated with the iroh type or quinn type that uses it. pub use quinn::{ @@ -50,6 +46,8 @@ pub use quinn::{ WriteError, // quinn::SendStream Written, // quinn::SendStream }; +#[cfg(feature = "qlog")] +pub use quinn::{QlogConfig, QlogFactory, QlogFileFactory}; /// `quinn_proto` types that are used in the public iroh API. // Each type is notated with the iroh type or quinn type that uses it. pub use quinn_proto::{ @@ -587,7 +585,7 @@ impl ServerConfig { Self { inner, transport } } - /// Set a custom [`TransportConfig`] + /// Set a custom [`QuicTransportConfig`] pub fn transport_config(&mut self, transport: QuicTransportConfig) -> &mut Self { self.inner.transport_config(transport.to_arc()); self.transport = transport; @@ -642,9 +640,9 @@ impl ServerConfig { self } - /// Maximum number of [`Incoming`][crate::Incoming] to allow to exist at a time + /// Maximum number of [`Incoming`] to allow to exist at a time /// - /// An [`Incoming`][crate::Incoming] comes into existence when an incoming connection attempt + /// An [`Incoming`] comes into existence when an incoming connection attempt /// is received and stops existing when the application either accepts it or otherwise disposes /// of it. While this limit is reached, new incoming connection attempts are immediately /// refused. Larger values have greater worst-case memory consumption, but accommodate greater @@ -653,14 +651,16 @@ impl ServerConfig { /// The default value is set to 65536. With a typical Ethernet MTU of 1500 bytes, this limits /// memory consumption from this to under 100 MiB--a generous amount that still prevents memory /// exhaustion in most contexts. + /// + /// [`Incoming`]: crate::endpoint::Incoming pub fn max_incoming(&mut self, max_incoming: usize) -> &mut Self { self.inner.max_incoming(max_incoming); self } - /// Maximum number of received bytes to buffer for each [`Incoming`][crate::Incoming] + /// Maximum number of received bytes to buffer for each [`Incoming`] /// - /// An [`Incoming`][crate::Incoming] comes into existence when an incoming connection attempt + /// An [`Incoming`] comes into existence when an incoming connection attempt /// is received and stops existing when the application either accepts it or otherwise disposes /// of it. This limit governs only packets received within that period, and does not include /// the first packet. Packets received in excess of this limit are dropped, which may cause @@ -668,16 +668,18 @@ impl ServerConfig { /// /// The default value is set to 10 MiB--an amount such that in most situations a client would /// not transmit that much 0-RTT data faster than the server handles the corresponding - /// [`Incoming`][crate::Incoming]. + /// [`Incoming`]. + /// + /// [`Incoming`]: crate::endpoint::Incoming pub fn incoming_buffer_size(&mut self, incoming_buffer_size: u64) -> &mut Self { self.inner.incoming_buffer_size(incoming_buffer_size); self } - /// Maximum number of received bytes to buffer for all [`Incoming`][crate::Incoming] + /// Maximum number of received bytes to buffer for all [`Incoming`] /// collectively /// - /// An [`Incoming`][crate::Incoming] comes into existence when an incoming connection attempt + /// An [`Incoming`] comes into existence when an incoming connection attempt /// is received and stops existing when the application either accepts it or otherwise disposes /// of it. This limit governs only packets received within that period, and does not include /// the first packet. Packets received in excess of this limit are dropped, which may cause @@ -685,6 +687,8 @@ impl ServerConfig { /// /// The default value is set to 100 MiB--a generous amount that still prevents memory /// exhaustion in most contexts. + /// + /// [`Incoming`]: crate::endpoint::Incoming pub fn incoming_buffer_size_total(&mut self, incoming_buffer_size_total: u64) -> &mut Self { self.inner .incoming_buffer_size_total(incoming_buffer_size_total); @@ -695,16 +699,18 @@ impl ServerConfig { /// /// This exists to allow system time to be mocked in tests, or wherever else desired. /// - /// Defaults to [`StdSystemTime`], which simply calls [`SystemTime::now()`](SystemTime::now). + /// Defaults to [`quinn::StdSystemTime`], which simply calls [`SystemTime::now()`](std::time::SystemTime::now). + /// + /// [`SystemTime`]: std::time::SystemTime pub fn time_source(&mut self, time_source: Arc) -> &mut Self { self.inner.time_source(time_source); self } - /// Create a server config with the given [`crypto::ServerConfig`] + /// Create a server config with the given [`CryptoServerConfig`] /// /// Uses a randomized handshake token key. - pub fn with_crypto(crypto: Arc) -> Self { + pub fn with_crypto(crypto: Arc) -> Self { let mut inner = quinn::ServerConfig::with_crypto(crypto); let transport = QuicTransportConfig::default(); inner.transport_config(transport.to_arc()); diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index e4d3ad11a09..7c40513f3fb 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -37,7 +37,7 @@ use n0_watcher::{self, Watchable, Watcher}; #[cfg(not(wasm_browser))] use netwatch::ip::LocalAddresses; use netwatch::netmon; -use quinn::{ServerConfig, WeakConnectionHandle}; +use quinn::WeakConnectionHandle; use rand::Rng; use tokio::sync::{Mutex as AsyncMutex, mpsc, oneshot}; use tokio_util::sync::CancellationToken; @@ -133,7 +133,7 @@ pub(crate) struct Options { pub(crate) proxy_url: Option, /// ServerConfig for the internal QUIC endpoint - pub(crate) server_config: ServerConfig, + pub(crate) server_config: quinn::ServerConfig, /// Skip verification of SSL certificates from relay servers /// @@ -1529,7 +1529,6 @@ mod tests { use n0_error::{Result, StackResultExt, StdResultExt}; use n0_future::{MergeBounded, StreamExt, time}; use n0_watcher::Watcher; - use quinn::ServerConfig; use rand::{CryptoRng, Rng, RngCore, SeedableRng}; use tokio_util::task::AbortOnDropHandle; use tracing::{Instrument, error, info, info_span, instrument}; @@ -1540,6 +1539,7 @@ mod tests { Endpoint, RelayMode, SecretKey, discovery::static_provider::StaticProvider, dns::DnsResolver, + endpoint::QuicTransportConfig, magicsock::{ Handle, MagicSock, TransportConfig, mapped_addrs::{EndpointIdMappedAddr, MappedAddr}, @@ -1571,12 +1571,13 @@ mod tests { } /// Generate a server config with no ALPNS and a default transport configuration - fn make_default_server_config(secret_key: &SecretKey) -> ServerConfig { + fn make_default_server_config(secret_key: &SecretKey) -> quinn::ServerConfig { let quic_server_config = crate::tls::TlsConfig::new(secret_key.clone(), DEFAULT_MAX_TLS_TICKETS) .make_server_config(vec![], false); - let mut server_config = ServerConfig::with_crypto(Arc::new(quic_server_config)); - server_config.transport_config(Arc::new(quinn::TransportConfig::default())); + let mut server_config = quinn::ServerConfig::with_crypto(Arc::new(quic_server_config)); + let transport = QuicTransportConfig::default(); + server_config.transport_config(transport.to_arc()); server_config } @@ -1983,7 +1984,7 @@ mod tests { async fn magicsock_ep(secret_key: SecretKey) -> Result { let quic_server_config = tls::TlsConfig::new(secret_key.clone(), DEFAULT_MAX_TLS_TICKETS) .make_server_config(vec![ALPN.to_vec()], true); - let mut server_config = ServerConfig::with_crypto(Arc::new(quic_server_config)); + let mut server_config = quinn::ServerConfig::with_crypto(Arc::new(quic_server_config)); server_config.transport_config(Arc::new(quinn::TransportConfig::default())); let dns_resolver = DnsResolver::new(); diff --git a/iroh/src/magicsock/remote_map/remote_state.rs b/iroh/src/magicsock/remote_map/remote_state.rs index d34c3455c85..04a3728947a 100644 --- a/iroh/src/magicsock/remote_map/remote_state.rs +++ b/iroh/src/magicsock/remote_map/remote_state.rs @@ -15,7 +15,7 @@ use n0_future::{ time::{self, Duration, Instant}, }; use n0_watcher::{Watchable, Watcher}; -use quinn::{PathStats, WeakConnectionHandle}; +use quinn::WeakConnectionHandle; use quinn_proto::{PathError, PathEvent, PathId, PathStatus, iroh_hp}; use rustc_hash::FxHashMap; use smallvec::SmallVec; @@ -31,7 +31,7 @@ use self::{ use super::Source; use crate::{ discovery::{ConcurrentDiscovery, Discovery, DiscoveryError, DiscoveryItem}, - endpoint::DirectAddr, + endpoint::{DirectAddr, quic::PathStats}, magicsock::{ MagicsockMetrics, mapped_addrs::{AddrMap, MappedAddr, RelayMappedAddr}, diff --git a/iroh/src/protocol.rs b/iroh/src/protocol.rs index 70f9fdc8afc..e38d1429be2 100644 --- a/iroh/src/protocol.rs +++ b/iroh/src/protocol.rs @@ -53,7 +53,7 @@ use tracing::{Instrument, error, field::Empty, info_span, trace, warn}; use crate::{ Endpoint, - endpoint::{Accepting, Connection, RemoteEndpointIdError}, + endpoint::{Accepting, Connection, RemoteEndpointIdError, quic}, }; /// The built router. @@ -146,8 +146,8 @@ impl From for AcceptError { } } -impl From for AcceptError { - fn from(err: quinn::ClosedStream) -> Self { +impl From for AcceptError { + fn from(err: quic::ClosedStream) -> Self { Self::from_err(err) } } @@ -606,14 +606,13 @@ mod tests { use std::{sync::Mutex, time::Duration}; use n0_error::{Result, StdResultExt}; - use quinn::ApplicationClose; use super::*; use crate::{ RelayMode, endpoint::{ - BeforeConnectOutcome, ConnectError, ConnectWithOptsError, ConnectionError, - EndpointHooks, + ApplicationClose, BeforeConnectOutcome, ConnectError, ConnectWithOptsError, + ConnectionError, EndpointHooks, }, }; From 22b50dd7c6ec5f39ede239a6c5eb2af6a1d1273e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cramfox=E2=80=9D?= <“kasey@n0.computer”> Date: Thu, 11 Dec 2025 22:24:13 -0500 Subject: [PATCH 4/5] feat: allow creating a new `ServerConfig` from the internal crypto of the `Endpoint` --- iroh/src/endpoint.rs | 27 ++++-- iroh/src/endpoint/connection.rs | 13 ++- iroh/src/endpoint/quic.rs | 145 +++++++++++++------------------- iroh/src/magicsock.rs | 2 +- 4 files changed, 89 insertions(+), 98 deletions(-) diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index 3f2c6b522f7..492031a2358 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -172,7 +172,9 @@ impl Builder { tls_config: tls::TlsConfig::new(secret_key.clone(), self.max_tls_tickets), keylog: self.keylog, }; - let server_config = static_config.create_server_config(self.alpn_protocols); + let server_config = static_config + .create_server_config(self.alpn_protocols) + .into_inner(); #[cfg(not(wasm_browser))] let dns_resolver = self.dns_resolver.unwrap_or_default(); @@ -471,15 +473,15 @@ struct StaticConfig { } impl StaticConfig { - /// Create a [`quinn::ServerConfig`] with the specified ALPN protocols. - fn create_server_config(&self, alpn_protocols: Vec>) -> quinn::ServerConfig { + /// Create a [`ServerConfig`] with the specified ALPN protocols. + fn create_server_config(&self, alpn_protocols: Vec>) -> ServerConfig { let quic_server_config = self .tls_config .make_server_config(alpn_protocols, self.keylog); - let mut server_config = quinn::ServerConfig::with_crypto(Arc::new(quic_server_config)); - server_config.transport_config(self.transport_config.to_arc()); + let mut inner = quinn::ServerConfig::with_crypto(Arc::new(quic_server_config)); + inner.transport_config(self.transport_config.to_inner_arc()); - server_config + ServerConfig::new(inner, self.transport_config.clone()) } } @@ -602,7 +604,9 @@ impl Endpoint { /// Note that this *overrides* the current list of ALPNs. pub fn set_alpns(&self, alpns: Vec>) { let server_config = self.static_config.create_server_config(alpns); - self.msock.endpoint().set_server_config(Some(server_config)); + self.msock + .endpoint() + .set_server_config(Some(server_config.into_inner())); } /// Adds the provided configuration to the [`RelayMap`]. @@ -718,8 +722,8 @@ impl Endpoint { let transport_config = options .transport_config - .map(|cfg| cfg.to_arc()) - .unwrap_or(self.static_config.transport_config.to_arc()); + .map(|cfg| cfg.to_inner_arc()) + .unwrap_or(self.static_config.transport_config.to_inner_arc()); // Start connecting via quinn. This will time out after 10 seconds if no reachable // address is available. @@ -1140,6 +1144,11 @@ impl Endpoint { self.msock.is_closed() } + /// Create a [`ServerConfig`] for this endpoint that includes the given alpns. + pub fn create_server_config(&self, alpns: Vec>) -> ServerConfig { + self.static_config.create_server_config(alpns) + } + // # Remaining private methods #[cfg(test)] diff --git a/iroh/src/endpoint/connection.rs b/iroh/src/endpoint/connection.rs index 26f60482898..dcadb7ea81b 100644 --- a/iroh/src/endpoint/connection.rs +++ b/iroh/src/endpoint/connection.rs @@ -21,6 +21,7 @@ use std::{ future::{Future, IntoFuture}, net::{IpAddr, SocketAddr}, pin::Pin, + sync::Arc, task::Poll, }; @@ -102,12 +103,20 @@ impl Incoming { /// Accepts this incoming connection using a custom configuration. /// + /// Use the [`Endpoint::create_server_config`] method to create a [`ServerConfig`] + /// that can be customized. + /// /// See [`accept()`] for more details. /// /// [`accept()`]: Incoming::accept - pub fn accept_with(self, server_config: ServerConfig) -> Result { + /// [`Endpoint::create_server_config`]: crate::Endpoint::create_server_config + /// [`ServerConfig`]: crate::endpoint::ServerConfig + pub fn accept_with( + self, + server_config: Arc, + ) -> Result { self.inner - .accept_with(server_config.to_arc()) + .accept_with(server_config.to_inner_arc()) .map(|conn| Accepting::new(conn, self.ep)) } diff --git a/iroh/src/endpoint/quic.rs b/iroh/src/endpoint/quic.rs index 15f08cfcd36..99122b4bdc3 100644 --- a/iroh/src/endpoint/quic.rs +++ b/iroh/src/endpoint/quic.rs @@ -8,11 +8,7 @@ #[cfg(feature = "qlog")] use std::path::Path; -use std::{ - net::{SocketAddrV4, SocketAddrV6}, - sync::Arc, - time::Duration, -}; +use std::{sync::Arc, time::Duration}; /// `quinn` types that are used in the public iroh API. // Each type is notated with the iroh type or quinn type that uses it. @@ -129,12 +125,12 @@ impl Default for QuicTransportConfig { } impl QuicTransportConfig { - /// Return an `Arc`-d [`quinn::TransportConfig`] - pub(crate) fn to_arc(&self) -> Arc { + /// Return an `Arc`-d [`quinn::TransportConfig`]. + pub(crate) fn to_inner_arc(&self) -> Arc { Arc::new(self.0.clone()) } - /// Maximum number of incoming bidirectional streams that may be open concurrently + /// Maximum number of incoming bidirectional streams that may be open concurrently. /// /// Must be nonzero for the peer to open any bidirectional streams. /// @@ -145,7 +141,7 @@ impl QuicTransportConfig { self } - /// Variant of `max_concurrent_bidi_streams` affecting unidirectional streams + /// Variant of `max_concurrent_bidi_streams` affecting unidirectional streams. pub fn max_concurrent_uni_streams(&mut self, value: VarInt) -> &mut Self { self.0.max_concurrent_uni_streams(value); self @@ -202,7 +198,7 @@ impl QuicTransportConfig { self } - /// Maximum number of bytes to transmit to a peer without acknowledgment + /// Maximum number of bytes to transmit to a peer without acknowledgment. /// /// Provides an upper bound on memory when communicating with peers that issue large amounts of /// flow control credit. Endpoints that wish to handle large numbers of connections robustly @@ -236,13 +232,13 @@ impl QuicTransportConfig { } /// Maximum reordering in time space before time based loss detection considers a packet lost, - /// as a factor of RTT + /// as a factor of RTT. pub fn time_threshold(&mut self, value: f32) -> &mut Self { self.0.time_threshold(value); self } - /// The RTT used before an RTT sample is taken + /// The RTT used before an RTT sample is taken. pub fn initial_rtt(&mut self, value: Duration) -> &mut Self { self.0.initial_rtt(value); self @@ -287,7 +283,7 @@ impl QuicTransportConfig { self } - /// Pad UDP datagrams carrying application data to current maximum UDP payload size + /// Pad UDP datagrams carrying application data to current maximum UDP payload size. /// /// Disabled by default. UDP datagrams containing loss probes are exempt from padding. /// @@ -301,7 +297,7 @@ impl QuicTransportConfig { self } - /// Specifies the ACK frequency config (see [`AckFrequencyConfig`] for details) + /// Specifies the ACK frequency config (see [`AckFrequencyConfig`] for details). /// /// The provided configuration will be ignored if the peer does not support the acknowledgement /// frequency QUIC extension. @@ -320,7 +316,7 @@ impl QuicTransportConfig { self } - /// Period of inactivity before sending a keep-alive packet + /// Period of inactivity before sending a keep-alive packet. /// /// Keep-alive packets prevent an inactive but otherwise healthy connection from timing out. /// @@ -332,13 +328,13 @@ impl QuicTransportConfig { self } - /// Maximum quantity of out-of-order crypto layer data to buffer + /// Maximum quantity of out-of-order crypto layer data to buffer. pub fn crypto_buffer_size(&mut self, value: usize) -> &mut Self { self.0.crypto_buffer_size(value); self } - /// Whether the implementation is permitted to set the spin bit on this connection + /// Whether the implementation is permitted to set the spin bit on this connection. /// /// This allows passive observers to easily judge the round trip time of a connection, which can /// be useful for network administration but sacrifices a small amount of privacy. @@ -348,7 +344,7 @@ impl QuicTransportConfig { } /// Maximum number of incoming application datagram bytes to buffer, or None to disable - /// incoming datagrams + /// incoming datagrams. /// /// The peer is forbidden to send single datagrams larger than this size. If the aggregate size /// of all datagrams that have been received from the peer but not consumed by the application @@ -358,7 +354,7 @@ impl QuicTransportConfig { self } - /// Maximum number of outgoing application datagram bytes to buffer + /// Maximum number of outgoing application datagram bytes to buffer. /// /// While datagrams are sent ASAP, it is possible for an application to generate data faster /// than the link, or even the underlying hardware, can transmit them. This limits the amount of @@ -369,7 +365,7 @@ impl QuicTransportConfig { self } - /// How to construct new `congestion::Controller`s + /// How to construct new `congestion::Controller`s. /// /// Typically the refcounted configuration of a `congestion::Controller`, /// e.g. a `congestion::NewRenoConfig`. @@ -389,7 +385,7 @@ impl QuicTransportConfig { } /// Whether to use "Generic Segmentation Offload" to accelerate transmits, when supported by the - /// environment + /// environment. /// /// Defaults to `true`. /// @@ -445,7 +441,7 @@ impl QuicTransportConfig { self } - /// Sets a default per-path maximum idle timeout + /// Sets a default per-path maximum idle timeout. /// /// If the path is idle for this long the path will be abandoned. Bear in mind this will /// interact with the [`QuicTransportConfig::max_idle_timeout`], if the last path is @@ -464,7 +460,7 @@ impl QuicTransportConfig { self } - /// Sets a default per-path keep alive interval + /// Sets a default per-path keep alive interval. /// /// Note that this does not interact with the connection-wide /// [`QuicTransportConfig::keep_alive_interval`]. This setting will keep this path active, @@ -485,7 +481,7 @@ impl QuicTransportConfig { } /// Sets the maximum number of nat traversal addresses this endpoint allows the remote to - /// advertise + /// advertise. /// /// Setting this to any nonzero value will enable Iroh's holepunching, loosely based in the Nat /// Traversal Extension for QUIC, see @@ -544,9 +540,14 @@ impl QuicTransportConfig { } } -/// Parameters governing incoming connections +/// Parameters governing incoming connections. /// /// Default values should be suitable for most internet applications. +/// +/// To create a [`ServerConfig`] compatible with your [`Endpoint`] identity, use the [`Endpoint::create_server_config`] method. +/// +/// [`Endpoint`]: crate::Endpoint +/// [`Endpoint::create_server_config`]: crate::Endpoint::create_server_config // Note: used in `iroh::endpoint::connection::Incoming::accept_with` // This is new-typed since `quinn::ServerConfig` takes a `TransportConfig`, which we new-type as a `QuicTransportConfig` #[derive(Debug, Clone)] @@ -556,44 +557,42 @@ pub struct ServerConfig { } impl ServerConfig { - /// Transport configuration to use for incoming connections - pub fn transport(&self) -> QuicTransportConfig { + pub(crate) fn new(inner: quinn::ServerConfig, transport: QuicTransportConfig) -> Self { + Self { inner, transport } + } + + pub(crate) fn to_inner_arc(&self) -> Arc { + Arc::new(self.inner.clone()) + } + + pub(crate) fn into_inner(self) -> quinn::ServerConfig { + self.inner + } + + /// Transport configuration used for incoming connections. + pub fn transport_config(&self) -> QuicTransportConfig { self.transport.clone() } - /// TLS configuration used for incoming connections - /// - /// Must be set to use TLS 1.3 only. + /// TLS configuration used for incoming connections. pub fn crypto(&self) -> Arc { self.inner.crypto.clone() } - /// Configuration for sending and handling validation tokens + /// Configuration for sending and handling validation tokens. pub fn validation_token(&self) -> ValidationTokenConfig { self.inner.validation_token.clone() } - pub(crate) fn to_arc(&self) -> Arc { - Arc::new(self.inner.clone()) - } - - /// Create a default config with a particular handshake token key - pub fn new(crypto: Arc, token_key: Arc) -> Self { - let mut inner = quinn::ServerConfig::new(crypto, token_key); - let transport = QuicTransportConfig::default(); - inner.transport_config(transport.to_arc()); - Self { inner, transport } - } - - /// Set a custom [`QuicTransportConfig`] - pub fn transport_config(&mut self, transport: QuicTransportConfig) -> &mut Self { - self.inner.transport_config(transport.to_arc()); + /// Sets a custom [`QuicTransportConfig`]. + pub fn set_transport_config(&mut self, transport: QuicTransportConfig) -> &mut Self { + self.inner.transport_config(transport.to_inner_arc()); self.transport = transport; self } - /// Set a custom [`ValidationTokenConfig`] - pub fn validation_token_config( + /// Sets a custom [`ValidationTokenConfig`]. + pub fn set_validation_token_config( &mut self, validation_token: ValidationTokenConfig, ) -> &mut Self { @@ -602,7 +601,7 @@ impl ServerConfig { } /// Private key used to authenticate data included in handshake tokens - pub fn token_key(&mut self, value: Arc) -> &mut Self { + pub fn set_token_key(&mut self, value: Arc) -> &mut Self { self.inner.token_key(value); self } @@ -610,37 +609,21 @@ impl ServerConfig { /// Duration after a retry token was issued for which it's considered valid /// /// Defaults to 15 seconds. - pub fn retry_token_lifetime(&mut self, value: Duration) -> &mut Self { + pub fn set_retry_token_lifetime(&mut self, value: Duration) -> &mut Self { self.inner.retry_token_lifetime(value); self } - /// Whether to allow clients to migrate to new addresses + /// Whether to allow clients to migrate to new addresses. /// /// Improves behavior for clients that move between different internet connections or suffer NAT /// rebinding. Enabled by default. - pub fn migration(&mut self, value: bool) -> &mut Self { + pub fn set_migration(&mut self, value: bool) -> &mut Self { self.inner.migration(value); self } - /// The preferred IPv4 address that will be communicated to clients during handshaking - /// - /// If the client is able to reach this address, it will switch to it. - pub fn preferred_address_v4(&mut self, address: Option) -> &mut Self { - self.inner.preferred_address_v4(address); - self - } - - /// The preferred IPv6 address that will be communicated to clients during handshaking - /// - /// If the client is able to reach this address, it will switch to it. - pub fn preferred_address_v6(&mut self, address: Option) -> &mut Self { - self.inner.preferred_address_v6(address); - self - } - - /// Maximum number of [`Incoming`] to allow to exist at a time + /// Maximum number of [`Incoming`] to allow to exist at a time. /// /// An [`Incoming`] comes into existence when an incoming connection attempt /// is received and stops existing when the application either accepts it or otherwise disposes @@ -653,12 +636,12 @@ impl ServerConfig { /// exhaustion in most contexts. /// /// [`Incoming`]: crate::endpoint::Incoming - pub fn max_incoming(&mut self, max_incoming: usize) -> &mut Self { + pub fn set_max_incoming(&mut self, max_incoming: usize) -> &mut Self { self.inner.max_incoming(max_incoming); self } - /// Maximum number of received bytes to buffer for each [`Incoming`] + /// Maximum number of received bytes to buffer for each [`Incoming`]. /// /// An [`Incoming`] comes into existence when an incoming connection attempt /// is received and stops existing when the application either accepts it or otherwise disposes @@ -671,13 +654,13 @@ impl ServerConfig { /// [`Incoming`]. /// /// [`Incoming`]: crate::endpoint::Incoming - pub fn incoming_buffer_size(&mut self, incoming_buffer_size: u64) -> &mut Self { + pub fn set_incoming_buffer_size(&mut self, incoming_buffer_size: u64) -> &mut Self { self.inner.incoming_buffer_size(incoming_buffer_size); self } /// Maximum number of received bytes to buffer for all [`Incoming`] - /// collectively + /// collectively. /// /// An [`Incoming`] comes into existence when an incoming connection attempt /// is received and stops existing when the application either accepts it or otherwise disposes @@ -689,31 +672,21 @@ impl ServerConfig { /// exhaustion in most contexts. /// /// [`Incoming`]: crate::endpoint::Incoming - pub fn incoming_buffer_size_total(&mut self, incoming_buffer_size_total: u64) -> &mut Self { + pub fn set_incoming_buffer_size_total(&mut self, incoming_buffer_size_total: u64) -> &mut Self { self.inner .incoming_buffer_size_total(incoming_buffer_size_total); self } - /// Object to get current [`SystemTime`] + /// Object to get current [`SystemTime`]. /// /// This exists to allow system time to be mocked in tests, or wherever else desired. /// /// Defaults to [`quinn::StdSystemTime`], which simply calls [`SystemTime::now()`](std::time::SystemTime::now). /// /// [`SystemTime`]: std::time::SystemTime - pub fn time_source(&mut self, time_source: Arc) -> &mut Self { + pub fn set_time_source(&mut self, time_source: Arc) -> &mut Self { self.inner.time_source(time_source); self } - - /// Create a server config with the given [`CryptoServerConfig`] - /// - /// Uses a randomized handshake token key. - pub fn with_crypto(crypto: Arc) -> Self { - let mut inner = quinn::ServerConfig::with_crypto(crypto); - let transport = QuicTransportConfig::default(); - inner.transport_config(transport.to_arc()); - Self { inner, transport } - } } diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index 7c40513f3fb..9585d35a9c9 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -1577,7 +1577,7 @@ mod tests { .make_server_config(vec![], false); let mut server_config = quinn::ServerConfig::with_crypto(Arc::new(quic_server_config)); let transport = QuicTransportConfig::default(); - server_config.transport_config(transport.to_arc()); + server_config.transport_config(transport.to_inner_arc()); server_config } From 476f40ca79b07216e5fc20287c7ce7581a7ae812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cramfox=E2=80=9D?= <“kasey@n0.computer”> Date: Fri, 12 Dec 2025 17:30:54 -0500 Subject: [PATCH 5/5] refactor: create builder structs for `ServerConfig` and `QuicTransportConfig`, to enable arc-ing the inner quinn structs --- iroh/bench/src/iroh.rs | 17 +++--- iroh/examples/transfer.rs | 6 +- iroh/src/discovery.rs | 10 ++-- iroh/src/endpoint.rs | 35 ++++++------ iroh/src/endpoint/connection.rs | 7 ++- iroh/src/endpoint/quic.rs | 97 +++++++++++++++++++++------------ iroh/src/magicsock.rs | 2 +- iroh/src/test_utils/qlog.rs | 10 ++-- 8 files changed, 108 insertions(+), 76 deletions(-) diff --git a/iroh/bench/src/iroh.rs b/iroh/bench/src/iroh.rs index 9151a5aa36f..cfbf9a3c4db 100644 --- a/iroh/bench/src/iroh.rs +++ b/iroh/bench/src/iroh.rs @@ -6,7 +6,10 @@ use std::{ use bytes::Bytes; use iroh::{ Endpoint, EndpointAddr, RelayMode, RelayUrl, - endpoint::{Connection, ConnectionError, QuicTransportConfig, RecvStream, SendStream}, + endpoint::{ + Connection, ConnectionError, QuicTransportConfig, QuicTransportConfigBuilder, RecvStream, + SendStream, + }, }; use n0_error::{Result, StackResultExt, StdResultExt}; use tracing::{trace, warn}; @@ -125,18 +128,18 @@ pub async fn connect_client( pub fn transport_config(max_streams: usize, initial_mtu: u16) -> QuicTransportConfig { // High stream windows are chosen because the amount of concurrent streams // is configurable as a parameter. - let mut config = QuicTransportConfig::default(); - config.max_concurrent_uni_streams(max_streams.try_into().unwrap()); - config.initial_mtu(initial_mtu); + let mut builder = QuicTransportConfigBuilder::default(); + builder.max_concurrent_uni_streams(max_streams.try_into().unwrap()); + builder.initial_mtu(initial_mtu); let mut acks = quinn::AckFrequencyConfig::default(); acks.ack_eliciting_threshold(10u32.into()); - config.ack_frequency_config(Some(acks)); + builder.ack_frequency_config(Some(acks)); #[cfg(feature = "qlog")] - config.qlog_from_env("bench-iroh"); + builder.qlog_from_env("bench-iroh"); - config + builder.build() } async fn drain_stream( diff --git a/iroh/examples/transfer.rs b/iroh/examples/transfer.rs index ea14a19598e..576387b4ce0 100644 --- a/iroh/examples/transfer.rs +++ b/iroh/examples/transfer.rs @@ -265,9 +265,9 @@ impl EndpointArgs { #[cfg(feature = "qlog")] { - let mut transport_config = iroh::endpoint::QuicTransportConfig::default(); - transport_config.qlog_from_env("transfer"); - builder = builder.transport_config(transport_config) + let mut cfg_builder = iroh::endpoint::QuicTransportConfigBuilder::default(); + cfg_builder.qlog_from_env("transfer"); + builder = builder.transport_config(cfg_builder.build()) } let endpoint = builder.alpns(vec![TRANSFER_ALPN.to_vec()]).bind().await?; diff --git a/iroh/src/discovery.rs b/iroh/src/discovery.rs index cae18ba2d03..9eb1108a42e 100644 --- a/iroh/src/discovery.rs +++ b/iroh/src/discovery.rs @@ -509,7 +509,7 @@ mod tests { use super::*; use crate::{ Endpoint, RelayMode, - endpoint::{ConnectOptions, IdleTimeout, QuicTransportConfig}, + endpoint::{ConnectOptions, IdleTimeout, QuicTransportConfigBuilder}, }; type InfoStore = HashMap; @@ -727,10 +727,10 @@ mod tests { .await; // 10x faster test via a 3s idle timeout instead of the 30s default - let mut config = QuicTransportConfig::default(); - config.keep_alive_interval(Duration::from_secs(1)); - config.max_idle_timeout(Some(IdleTimeout::try_from(Duration::from_secs(3)).unwrap())); - let opts = ConnectOptions::new().with_transport_config(config); + let mut builder = QuicTransportConfigBuilder::default(); + builder.keep_alive_interval(Duration::from_secs(1)); + builder.max_idle_timeout(Some(IdleTimeout::try_from(Duration::from_secs(3)).unwrap())); + let opts = ConnectOptions::new().with_transport_config(builder.build()); let res = ep2 .connect_with_opts(ep1.id(), TEST_ALPN, opts) diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index 492031a2358..b7c96edc472 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -63,12 +63,12 @@ pub use self::{ ControllerFactory, ControllerMetrics, CryptoError, CryptoServerConfig, Dir, ExportKeyingMaterialError, FrameStats, FrameType, HandshakeTokenKey, HeaderKey, IdleTimeout, Keys, MtuDiscoveryConfig, OpenBi, OpenUni, PacketKey, PathId, PathStats, - QuicConnectError, QuicTransportConfig, ReadDatagram, ReadError, ReadExactError, - ReadToEndError, RecvStream, ResetError, RttEstimator, SendDatagram, SendDatagramError, - SendStream, ServerConfig, Session, Side, StoppedError, StreamId, TimeSource, TokenLog, - TokenReuseError, TransportError, TransportErrorCode, TransportParameters, UdpStats, - UnsupportedVersion, ValidationTokenConfig, VarInt, VarIntBoundsExceeded, WriteError, - Written, + QuicConnectError, QuicTransportConfig, QuicTransportConfigBuilder, ReadDatagram, ReadError, + ReadExactError, ReadToEndError, RecvStream, ResetError, RttEstimator, SendDatagram, + SendDatagramError, SendStream, ServerConfig, ServerConfigBuilder, Session, Side, + StoppedError, StreamId, TimeSource, TokenLog, TokenReuseError, TransportError, + TransportErrorCode, TransportParameters, UdpStats, UnsupportedVersion, + ValidationTokenConfig, VarInt, VarIntBoundsExceeded, WriteError, Written, }, }; pub use crate::magicsock::transports::TransportConfig; @@ -172,9 +172,7 @@ impl Builder { tls_config: tls::TlsConfig::new(secret_key.clone(), self.max_tls_tickets), keylog: self.keylog, }; - let server_config = static_config - .create_server_config(self.alpn_protocols) - .into_inner(); + let server_config = static_config.create_server_config(self.alpn_protocols); #[cfg(not(wasm_browser))] let dns_resolver = self.dns_resolver.unwrap_or_default(); @@ -474,14 +472,13 @@ struct StaticConfig { impl StaticConfig { /// Create a [`ServerConfig`] with the specified ALPN protocols. - fn create_server_config(&self, alpn_protocols: Vec>) -> ServerConfig { + fn create_server_config(&self, alpn_protocols: Vec>) -> quinn_proto::ServerConfig { let quic_server_config = self .tls_config .make_server_config(alpn_protocols, self.keylog); let mut inner = quinn::ServerConfig::with_crypto(Arc::new(quic_server_config)); inner.transport_config(self.transport_config.to_inner_arc()); - - ServerConfig::new(inner, self.transport_config.clone()) + inner } } @@ -604,9 +601,7 @@ impl Endpoint { /// Note that this *overrides* the current list of ALPNs. pub fn set_alpns(&self, alpns: Vec>) { let server_config = self.static_config.create_server_config(alpns); - self.msock - .endpoint() - .set_server_config(Some(server_config.into_inner())); + self.msock.endpoint().set_server_config(Some(server_config)); } /// Adds the provided configuration to the [`RelayMap`]. @@ -1144,9 +1139,13 @@ impl Endpoint { self.msock.is_closed() } - /// Create a [`ServerConfig`] for this endpoint that includes the given alpns. - pub fn create_server_config(&self, alpns: Vec>) -> ServerConfig { - self.static_config.create_server_config(alpns) + /// Create a [`ServerConfigBuilder`] for this endpoint that includes the given alpns. + /// + /// Use the [`ServerConfigBuilder`] to customize the [`ServerConfig`] connection configuration + /// for a connection accepted using the [`Incoming::accept_with`] method. + pub fn create_server_config_builder(&self, alpns: Vec>) -> ServerConfigBuilder { + let inner = self.static_config.create_server_config(alpns); + ServerConfigBuilder::new(inner, self.static_config.transport_config.clone()) } // # Remaining private methods diff --git a/iroh/src/endpoint/connection.rs b/iroh/src/endpoint/connection.rs index dcadb7ea81b..bd06f4e029b 100644 --- a/iroh/src/endpoint/connection.rs +++ b/iroh/src/endpoint/connection.rs @@ -103,13 +103,14 @@ impl Incoming { /// Accepts this incoming connection using a custom configuration. /// - /// Use the [`Endpoint::create_server_config`] method to create a [`ServerConfig`] - /// that can be customized. + /// Use the [`Endpoint::create_server_config_builder`] method to create a [`ServerConfigBuilder`] + /// to customize a [`ServerConfig`]. /// /// See [`accept()`] for more details. /// /// [`accept()`]: Incoming::accept - /// [`Endpoint::create_server_config`]: crate::Endpoint::create_server_config + /// [`Endpoint::create_server_config_builder`]: crate::Endpoint::create_server_config_builder + /// [`ServerConfigBuilder`]: crate::endpoint::ServerConfigBuilder /// [`ServerConfig`]: crate::endpoint::ServerConfig pub fn accept_with( self, diff --git a/iroh/src/endpoint/quic.rs b/iroh/src/endpoint/quic.rs index 99122b4bdc3..2ccc7f84892 100644 --- a/iroh/src/endpoint/quic.rs +++ b/iroh/src/endpoint/quic.rs @@ -104,14 +104,14 @@ use crate::magicsock::{HEARTBEAT_INTERVAL, MAX_MULTIPATH_PATHS, PATH_MAX_IDLE_TI /// well with QUIC multipath. Adjusting those settings may cause suboptimal usage. /// /// Look at the following methods for more details: -/// - [`QuicTransportConfig::default_path_keep_alive_interval`] -/// - [`QuicTransportConfig::default_path_max_idle_timeout`] -/// - [`QuicTransportConfig::max_concurrent_multipath_paths`] -/// - [`QuicTransportConfig::set_max_remote_nat_traversal_addresses`] +/// - [`QuicTransportConfigBuilder::default_path_keep_alive_interval`] +/// - [`QuicTransportConfigBuilder::default_path_max_idle_timeout`] +/// - [`QuicTransportConfigBuilder::max_concurrent_multipath_paths`] +/// - [`QuicTransportConfigBuilder::set_max_remote_nat_traversal_addresses`] #[derive(Debug, Clone)] -pub struct QuicTransportConfig(pub(crate) quinn::TransportConfig); +pub struct QuicTransportConfigBuilder(quinn::TransportConfig); -impl Default for QuicTransportConfig { +impl Default for QuicTransportConfigBuilder { fn default() -> Self { let mut cfg = quinn::TransportConfig::default(); // Override some transport config settings. @@ -124,10 +124,26 @@ impl Default for QuicTransportConfig { } } +/// todo: docs +#[derive(Debug, Clone)] +pub struct QuicTransportConfig(Arc); + +impl Default for QuicTransportConfig { + fn default() -> Self { + QuicTransportConfigBuilder::default().build() + } +} + impl QuicTransportConfig { - /// Return an `Arc`-d [`quinn::TransportConfig`]. pub(crate) fn to_inner_arc(&self) -> Arc { - Arc::new(self.0.clone()) + self.0.clone() + } +} + +impl QuicTransportConfigBuilder { + /// Todo: docs + pub fn build(self) -> QuicTransportConfig { + QuicTransportConfig(Arc::new(self.0)) } /// Maximum number of incoming bidirectional streams that may be open concurrently. @@ -245,13 +261,13 @@ impl QuicTransportConfig { } /// The initial value to be used as the maximum UDP payload size before running MTU discovery - /// (see [`QuicTransportConfig::mtu_discovery_config`]). + /// (see [`QuicTransportConfigBuilder::mtu_discovery_config`]). /// /// Must be at least 1200, which is the default, and known to be safe for typical internet /// applications. Larger values are more efficient, but increase the risk of packet loss due to /// exceeding the network path's IP MTU. If the provided value is higher than what the network /// path actually supports, packet loss will eventually trigger black hole detection and bring - /// it down to [`QuicTransportConfig::min_mtu`]. + /// it down to [`QuicTransportConfigBuilder::min_mtu`]. pub fn initial_mtu(&mut self, value: u16) -> &mut Self { self.0.initial_mtu(value); self @@ -260,15 +276,15 @@ impl QuicTransportConfig { /// The maximum UDP payload size guaranteed to be supported by the network. /// /// Must be at least 1200, which is the default, and lower than or equal to - /// [`QuicTransportConfig::initial_mtu`]. + /// [`QuicTransportConfigBuilder::initial_mtu`]. /// /// Real-world MTUs can vary according to ISP, VPN, and properties of intermediate network links /// outside of either endpoint's control. Extreme care should be used when raising this value /// outside of private networks where these factors are fully controlled. If the provided value /// is higher than what the network path actually supports, the result will be unpredictable and /// catastrophic packet loss, without a possibility of repair. Prefer - /// [`QuicTransportConfig::initial_mtu`] together with - /// [`QuicTransportConfig::mtu_discovery_config`] to set a maximum UDP payload size that robustly + /// [`QuicTransportConfigBuilder::initial_mtu`] together with + /// [`QuicTransportConfigBuilder::mtu_discovery_config`] to set a maximum UDP payload size that robustly /// adapts to the network. pub fn min_mtu(&mut self, value: u16) -> &mut Self { self.0.min_mtu(value); @@ -444,7 +460,7 @@ impl QuicTransportConfig { /// Sets a default per-path maximum idle timeout. /// /// If the path is idle for this long the path will be abandoned. Bear in mind this will - /// interact with the [`QuicTransportConfig::max_idle_timeout`], if the last path is + /// interact with the [`QuicTransportConfigBuilder::max_idle_timeout`], if the last path is /// abandoned the entire connection will be closed. /// /// Note: this method will ignore values higher than the recommended 6500 ms and will log a warning. @@ -463,8 +479,8 @@ impl QuicTransportConfig { /// Sets a default per-path keep alive interval. /// /// Note that this does not interact with the connection-wide - /// [`QuicTransportConfig::keep_alive_interval`]. This setting will keep this path active, - /// [`QuicTransportConfig::keep_alive_interval`] will keep the connection active, with no + /// [`QuicTransportConfigBuilder::keep_alive_interval`]. This setting will keep this path active, + /// [`QuicTransportConfigBuilder::keep_alive_interval`] will keep the connection active, with no /// control over which path is used for this. /// /// Note: this method will ignore values higher than the recommended 5 seconds and will log a warning. @@ -544,29 +560,51 @@ impl QuicTransportConfig { /// /// Default values should be suitable for most internet applications. /// -/// To create a [`ServerConfig`] compatible with your [`Endpoint`] identity, use the [`Endpoint::create_server_config`] method. +/// To create a [`ServerConfig`] compatible with your [`Endpoint`] identity, use the [`Endpoint::create_server_config_builder`] method. /// /// [`Endpoint`]: crate::Endpoint -/// [`Endpoint::create_server_config`]: crate::Endpoint::create_server_config +/// [`Endpoint::create_server_config_builder`]: crate::Endpoint::create_server_config_builder // Note: used in `iroh::endpoint::connection::Incoming::accept_with` // This is new-typed since `quinn::ServerConfig` takes a `TransportConfig`, which we new-type as a `QuicTransportConfig` #[derive(Debug, Clone)] -pub struct ServerConfig { +pub struct ServerConfigBuilder { inner: quinn::ServerConfig, transport: QuicTransportConfig, } +/// todo: docs +#[derive(Debug, Clone)] +pub struct ServerConfig(Arc); + impl ServerConfig { - pub(crate) fn new(inner: quinn::ServerConfig, transport: QuicTransportConfig) -> Self { - Self { inner, transport } + pub(crate) fn to_inner_arc(&self) -> Arc { + self.0.clone() } - pub(crate) fn to_inner_arc(&self) -> Arc { - Arc::new(self.inner.clone()) + /// Transport configuration used for incoming connections. + pub fn transport_config(&self) -> QuicTransportConfig { + QuicTransportConfig(self.0.transport.clone()) } - pub(crate) fn into_inner(self) -> quinn::ServerConfig { - self.inner + /// TLS configuration used for incoming connections. + pub fn crypto(&self) -> Arc { + self.0.crypto.clone() + } + + /// Configuration for sending and handling validation tokens. + pub fn validation_token(&self) -> ValidationTokenConfig { + self.0.validation_token.clone() + } +} + +impl ServerConfigBuilder { + /// todo: docs + pub fn build(self) -> ServerConfig { + ServerConfig(Arc::new(self.inner)) + } + + pub(crate) fn new(inner: quinn::ServerConfig, transport: QuicTransportConfig) -> Self { + Self { inner, transport } } /// Transport configuration used for incoming connections. @@ -614,15 +652,6 @@ impl ServerConfig { self } - /// Whether to allow clients to migrate to new addresses. - /// - /// Improves behavior for clients that move between different internet connections or suffer NAT - /// rebinding. Enabled by default. - pub fn set_migration(&mut self, value: bool) -> &mut Self { - self.inner.migration(value); - self - } - /// Maximum number of [`Incoming`] to allow to exist at a time. /// /// An [`Incoming`] comes into existence when an incoming connection attempt diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index 9585d35a9c9..16e3b9b78c9 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -133,7 +133,7 @@ pub(crate) struct Options { pub(crate) proxy_url: Option, /// ServerConfig for the internal QUIC endpoint - pub(crate) server_config: quinn::ServerConfig, + pub(crate) server_config: quinn_proto::ServerConfig, /// Skip verification of SSL certificates from relay servers /// diff --git a/iroh/src/test_utils/qlog.rs b/iroh/src/test_utils/qlog.rs index 17b733271e0..a477cbea323 100644 --- a/iroh/src/test_utils/qlog.rs +++ b/iroh/src/test_utils/qlog.rs @@ -8,9 +8,9 @@ use n0_error::Result; #[cfg(feature = "qlog")] use n0_future::time::Instant; -#[cfg(feature = "qlog")] -use crate::endpoint::QlogFileFactory; use crate::endpoint::QuicTransportConfig; +#[cfg(feature = "qlog")] +use crate::endpoint::{QlogFileFactory, QuicTransportConfigBuilder}; /// Builder to create one or more related qlog configs. /// @@ -72,16 +72,16 @@ impl QlogFileGroup { } #[cfg(feature = "qlog")] { - let mut config = QuicTransportConfig::default(); + let mut builder = QuicTransportConfigBuilder::default(); if std::env::var("IROH_TEST_QLOG").is_ok() { let prefix = format!("{}.{}", self.title, name.to_string()); let factory = QlogFileFactory::new(self.directory.clone()) .with_prefix(prefix) .with_start_instant(self.start.into()); - config.qlog_factory(Arc::new(factory)); + builder.qlog_factory(Arc::new(factory)); } - Ok(config) + Ok(builder.build()) } } }