diff --git a/Cargo.lock b/Cargo.lock index 509e2cd7788..f79b1cde266 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -580,9 +580,9 @@ dependencies = [ [[package]] name = "console" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b430743a6eb14e9764d4260d4c0d8123087d504eeb9c48f2b2a5e810dd369df4" +checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4" dependencies = [ "encode_unicode", "libc", @@ -1241,9 +1241,9 @@ dependencies = [ [[package]] name = "fs-err" -version = "3.2.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62d91fd049c123429b018c47887d3f75a265540dd3c30ba9cb7bae9197edb03a" +checksum = "824f08d01d0f496b3eca4f001a13cf17690a6ee930043d20817f547455fd98f8" dependencies = [ "autocfg", "tokio", @@ -1591,9 +1591,9 @@ dependencies = [ [[package]] name = "governor" -version = "0.10.2" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e23d5986fd4364c2fb7498523540618b4b8d92eec6c36a02e565f66748e2f79" +checksum = "9efcab3c1958580ff1f25a2a41be1668f7603d849bb63af523b208a3cc1223b8" dependencies = [ "cfg-if", "dashmap", @@ -2541,9 +2541,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" dependencies = [ "bitflags", "libc", @@ -3537,9 +3537,9 @@ dependencies = [ [[package]] name = "rcgen" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fae430c6b28f1ad601274e78b7dffa0546de0b73b4cd32f46723c0c2a16f7a5" +checksum = "3ec0a99f2de91c3cddc84b37e7db80e4d96b743e05607f647eb236fc0455907f" dependencies = [ "pem", "ring", @@ -3626,9 +3626,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.25" +version = "0.12.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6eff9328d40131d43bd911d42d79eb6a47312002a4daefc9e37f17e74a7701a" +checksum = "3b4c14b2d9afca6a60277086b0cc6a6ae0b568f6f7916c943a8cdc79f8be240f" dependencies = [ "base64", "bytes", @@ -4216,9 +4216,9 @@ dependencies = [ [[package]] name = "sorted-index-buffer" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d96a9d278ef78c991bf2faabd578b41199037846ff96fbb093f84bb50adaa00" +checksum = "ea06cc588e43c632923a55450401b8f25e628131571d4e1baea1bdfdb2b5ed06" [[package]] name = "spez" diff --git a/iroh/bench/src/iroh.rs b/iroh/bench/src/iroh.rs index 35ffb10d556..6f0a0f027ea 100644 --- a/iroh/bench/src/iroh.rs +++ b/iroh/bench/src/iroh.rs @@ -125,18 +125,20 @@ 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 = QuicTransportConfig::builder() + .max_concurrent_uni_streams(max_streams.try_into().unwrap()) + .initial_mtu(initial_mtu); let mut acks = quinn::AckFrequencyConfig::default(); acks.ack_eliciting_threshold(10u32.into()); - config.ack_frequency_config(Some(acks)); + builder = builder.ack_frequency_config(Some(acks)); #[cfg(feature = "qlog")] - config.qlog_from_env("bench-iroh"); + { + builder = builder.qlog_from_env("bench-iroh"); + } - config + builder.build() } async fn drain_stream( 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/examples/transfer.rs b/iroh/examples/transfer.rs index dcb336db5c9..6f6bb62470d 100644 --- a/iroh/examples/transfer.rs +++ b/iroh/examples/transfer.rs @@ -268,9 +268,10 @@ 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 cfg = iroh::endpoint::QuicTransportConfig::builder() + .qlog_from_env("transfer") + .build(); + builder = builder.transport_config(cfg) } let endpoint = builder.alpns(vec![TRANSFER_ALPN.to_vec()]).bind().await?; diff --git a/iroh/src/discovery.rs b/iroh/src/discovery.rs index aab0b663088..1a66b1b0e46 100644 --- a/iroh/src/discovery.rs +++ b/iroh/src/discovery.rs @@ -727,10 +727,11 @@ 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 cfg = QuicTransportConfig::builder() + .keep_alive_interval(Duration::from_secs(1)) + .max_idle_timeout(Some(IdleTimeout::try_from(Duration::from_secs(3)).unwrap())) + .build(); + let opts = ConnectOptions::new().with_transport_config(cfg); let res = ep2 .connect_with_opts(ep1.id(), TEST_ALPN, opts) diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index 1aa3882bc13..afcd7f97ee1 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,17 +55,20 @@ 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, - ConnectionClose, ConnectionError, ConnectionStats, Controller, ControllerFactory, - CryptoError, CryptoServerConfig, ExportKeyingMaterialError, FrameStats, HandshakeTokenKey, - IdleTimeout, MtuDiscoveryConfig, 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, + Codec, ConnectionClose, ConnectionError, ConnectionStats, Controller, ControllerFactory, + ControllerMetrics, CryptoError, Dir, ExportKeyingMaterialError, FrameStats, FrameType, + HandshakeTokenKey, HeaderKey, IdleTimeout, Keys, MtuDiscoveryConfig, OpenBi, OpenUni, + PacketKey, PathId, PathStats, QuicConnectError, QuicTransportConfig, + QuicTransportConfigBuilder, ReadDatagram, ReadError, ReadExactError, ReadToEndError, + RecvStream, ResetError, RttEstimator, SendDatagram, SendDatagramError, SendStream, + ServerConfig, ServerConfigBuilder, Side, StoppedError, StreamId, TimeSource, TokenLog, + TokenReuseError, TransportError, TransportErrorCode, TransportParameters, UdpStats, + UnorderedRecvStream, UnsupportedVersion, ValidationTokenConfig, VarInt, + VarIntBoundsExceeded, WriteError, Written, }, }; pub use crate::magicsock::transports::TransportConfig; @@ -468,15 +471,14 @@ struct StaticConfig { } impl StaticConfig { - /// Create a [`quinn::ServerConfig`] with the specified ALPN protocols. - fn create_server_config(&self, alpn_protocols: Vec>) -> ServerConfig { + /// Create a [`ServerConfig`] with the specified ALPN protocols. + 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 server_config = ServerConfig::with_crypto(Arc::new(quic_server_config)); - server_config.transport_config(self.transport_config.to_arc()); - - server_config + let mut inner = quinn::ServerConfig::with_crypto(Arc::new(quic_server_config)); + inner.transport_config(self.transport_config.to_inner_arc()); + inner } } @@ -525,7 +527,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 { @@ -715,8 +717,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. @@ -1144,6 +1146,15 @@ impl Endpoint { self.msock.is_closed() } + /// 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 #[cfg(test)] @@ -1313,7 +1324,6 @@ mod tests { use n0_future::{BufferedStreamExt, StreamExt, stream, time}; use n0_tracing_test::traced_test; use n0_watcher::Watcher; - use quinn::ConnectionError; use rand::SeedableRng; use tokio::sync::oneshot; use tracing::{Instrument, debug_span, info, info_span, instrument}; @@ -1322,7 +1332,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}, }; @@ -1380,13 +1390,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"); @@ -1417,11 +1427,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"); @@ -1631,7 +1640,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()?; @@ -1660,7 +1669,7 @@ mod tests { let conn_closed = dbg!(ep2_connect.await.anyerr()??); assert!(matches!( conn_closed, - ConnectionError::ApplicationClosed(quinn::ApplicationClose { .. }) + ConnectionError::ApplicationClosed(ApplicationClose { .. }) )); Ok(()) @@ -1680,7 +1689,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() @@ -1756,7 +1765,7 @@ mod tests { let conn_closed = dbg!(client_task.await.anyerr()??); assert!(matches!( conn_closed, - ConnectionError::ApplicationClosed(quinn::ApplicationClose { .. }) + ConnectionError::ApplicationClosed(ApplicationClose { .. }) )); Ok(()) @@ -1774,7 +1783,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() @@ -1853,7 +1862,7 @@ mod tests { let conn_closed = dbg!(client_task.await.anyerr()??); assert!(matches!( conn_closed, - ConnectionError::ApplicationClosed(quinn::ApplicationClose { .. }) + ConnectionError::ApplicationClosed(ApplicationClose { .. }) )); Ok(()) @@ -1920,7 +1929,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) @@ -1968,7 +1977,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 f27e3d8a0c3..67d7ca26290 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_proto::PathId; +use quinn::WeakConnectionHandle; use tracing::warn; use crate::{ Endpoint, - endpoint::AfterHandshakeOutcome, + endpoint::{ + AfterHandshakeOutcome, + quic::{ + AcceptBi, AcceptUni, ConnectionError, ConnectionStats, Controller, + ExportKeyingMaterialError, OpenBi, OpenUni, PathId, ReadDatagram, SendDatagram, + SendDatagramError, ServerConfig, Side, VarInt, + }, + }, magicsock::{ RemoteStateActorStoppedError, remote_map::{PathInfoList, PathsWatcher}, @@ -100,15 +103,21 @@ impl Incoming { /// Accepts this incoming connection using a custom configuration. /// + /// 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_builder`]: crate::Endpoint::create_server_config_builder + /// [`ServerConfigBuilder`]: crate::endpoint::ServerConfigBuilder + /// [`ServerConfig`]: crate::endpoint::ServerConfig pub fn accept_with( self, server_config: Arc, ) -> Result { self.inner - .accept_with(server_config) + .accept_with(server_config.to_inner_arc()) .map(|conn| Accepting::new(conn, self.ep)) } @@ -124,7 +133,9 @@ impl Incoming { /// Errors if `remote_address_validated()` is true. #[allow(clippy::result_large_err)] pub fn retry(self) -> Result<(), RetryError> { - self.inner.retry() + self.inner + .retry() + .map_err(|err| e!(RetryError { err, ep: self.ep })) } /// Ignores this incoming connection attempt, not sending any packet in response. @@ -165,6 +176,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")] @@ -522,7 +551,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 @@ -726,8 +755,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() @@ -747,8 +776,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() @@ -822,20 +851,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`]. /// @@ -879,10 +906,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) } @@ -934,7 +958,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 cde0e5ae98a..12b0fcaa588 100644 --- a/iroh/src/endpoint/quic.rs +++ b/iroh/src/endpoint/quic.rs @@ -10,28 +10,85 @@ use std::path::Path; use std::{sync::Arc, time::Duration}; -// Missing still: SendDatagram and ConnectionClose::frame_type's Type. +/// `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, 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, + 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 + UnorderedRecvStream, // quinn::RecvStream + VarInt, // various + VarIntBoundsExceeded, // quinn::VarInt, quinn::IdleTimeout + 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::{ - FrameStats, IdleTimeout, TransportError, TransportErrorCode, UdpStats, Written, - congestion::{Controller, ControllerFactory}, + ApplicationClose, // quinn::ConnectionError + Chunk, // quinn::RecvStream + ConnectError as QuicConnectError, // iroh::endpoint::ConnectWithOptsError + ConnectionClose, // quinn::ConnectionError + 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 + UnsupportedVersion, // quinn_proto::ConnectError }, + transport_parameters::TransportParameters, // quinn_proto::crypot::ServerConfig }; use tracing::warn; use crate::magicsock::{HEARTBEAT_INTERVAL, MAX_MULTIPATH_PATHS, PATH_MAX_IDLE_TIMEOUT}; +/// Builder for a [`QuicTransportConfig`]. +#[derive(Debug, Clone)] +pub struct QuicTransportConfigBuilder(quinn::TransportConfig); + /// Parameters governing the core QUIC state machine /// /// Default values should be suitable for most internet applications. Applications protocols which @@ -45,19 +102,53 @@ use crate::magicsock::{HEARTBEAT_INTERVAL, MAX_MULTIPATH_PATHS, PATH_MAX_IDLE_TI /// performance at lower bandwidths and latencies. The default configuration is tuned for a 100Mbps /// link with a 100ms round trip time. /// +/// Use the [`QuicTransportConfigBuilder`] to customize these tunable fields. +/// /// In iroh, the config has some specific default values that make iroh's holepunching work /// 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`] +/// +/// # Examples +/// ``` +/// use std::time::Duration; +/// +/// use iroh::endpoint::QuicTransportConfig; +/// +/// let _cfg = QuicTransportConfig::builder() +/// .send_observed_address_reports(true) +/// .build(); +/// ``` #[derive(Debug, Clone)] -pub struct QuicTransportConfig(pub(crate) quinn::TransportConfig); +pub struct QuicTransportConfig(Arc); + +impl QuicTransportConfig { + /// Returns a default [`QuicTransportConfigBuilder`] that allows customizing + /// a [`QuicTransportConfig`]. + pub fn builder() -> QuicTransportConfigBuilder { + QuicTransportConfigBuilder::new() + } +} impl Default for QuicTransportConfig { fn default() -> Self { + QuicTransportConfigBuilder::new().build() + } +} + +impl QuicTransportConfig { + pub(crate) fn to_inner_arc(&self) -> Arc { + self.0.clone() + } +} + +impl QuicTransportConfigBuilder { + /// Create a default [`QuicTransportConfigBuilder`]. + fn new() -> Self { let mut cfg = quinn::TransportConfig::default(); // Override some transport config settings. cfg.keep_alive_interval(Some(HEARTBEAT_INTERVAL)); @@ -67,27 +158,25 @@ impl Default for QuicTransportConfig { cfg.set_max_remote_nat_traversal_addresses(MAX_MULTIPATH_PATHS as u8); Self(cfg) } -} -impl QuicTransportConfig { - /// Return an `Arc`-d [`quinn::TransportConfig`] - pub(super) fn to_arc(&self) -> Arc { - Arc::new(self.0.clone()) + /// Build a [`QuicTransportConfig`] from the builder. + pub fn build(self) -> QuicTransportConfig { + QuicTransportConfig(Arc::new(self.0)) } - /// 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. /// /// Worst-case memory use is directly proportional to `max_concurrent_bidi_streams * /// stream_receive_window`, with an upper bound proportional to `receive_window`. - pub fn max_concurrent_bidi_streams(&mut self, value: VarInt) -> &mut Self { + pub fn max_concurrent_bidi_streams(mut self, value: VarInt) -> Self { self.0.max_concurrent_bidi_streams(value); self } - /// Variant of `max_concurrent_bidi_streams` affecting unidirectional streams - pub fn max_concurrent_uni_streams(&mut self, value: VarInt) -> &mut Self { + /// Variant of `max_concurrent_bidi_streams` affecting unidirectional streams. + pub fn max_concurrent_uni_streams(mut self, value: VarInt) -> Self { self.0.max_concurrent_uni_streams(value); self } @@ -104,17 +193,19 @@ impl QuicTransportConfig { /// # use std::{convert::TryInto, time::Duration}; /// # use iroh::endpoint::{QuicTransportConfig, VarInt, VarIntBoundsExceeded}; /// # fn main() -> Result<(), VarIntBoundsExceeded> { - /// let mut config = QuicTransportConfig::default(); - /// - /// // Set the idle timeout as `VarInt`-encoded milliseconds - /// config.max_idle_timeout(Some(VarInt::from_u32(10_000).into())); + /// let mut builder = QuicTransportConfig::builder() + /// // Set the idle timeout as `VarInt`-encoded milliseconds + /// .max_idle_timeout(Some(VarInt::from_u32(10_000).into())); /// /// // Set the idle timeout as a `Duration` - /// config.max_idle_timeout(Some(Duration::from_secs(10).try_into()?)); + /// builder = builder.max_idle_timeout(Some(Duration::from_secs(10).try_into()?)); + /// + /// let _cfg = builder.build(); + /// /// # Ok(()) /// # } /// ``` - pub fn max_idle_timeout(&mut self, value: Option) -> &mut Self { + pub fn max_idle_timeout(mut self, value: Option) -> Self { self.0.max_idle_timeout(value); self } @@ -127,7 +218,7 @@ impl QuicTransportConfig { /// stream doesn't monopolize receive buffers, which may otherwise occur if the application /// chooses not to read from a large stream for a time while still requiring data on other /// streams. - pub fn stream_receive_window(&mut self, value: VarInt) -> &mut Self { + pub fn stream_receive_window(mut self, value: VarInt) -> Self { self.0.stream_receive_window(value); self } @@ -138,18 +229,18 @@ impl QuicTransportConfig { /// This should be set to at least the expected connection latency multiplied by the maximum /// desired throughput. Larger values can be useful to allow maximum throughput within a /// stream while another is blocked. - pub fn receive_window(&mut self, value: VarInt) -> &mut Self { + pub fn receive_window(mut self, value: VarInt) -> Self { self.0.receive_window(value); 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 /// should take care to set this low enough to guarantee memory exhaustion does not occur if /// every connection uses the entire window. - pub fn send_window(&mut self, value: u64) -> &mut Self { + pub fn send_window(mut self, value: u64) -> Self { self.0.send_window(value); self } @@ -164,40 +255,40 @@ impl QuicTransportConfig { /// /// Disabling fairness can reduce fragmentation and protocol overhead for workloads that use /// many small streams. - pub fn send_fairness(&mut self, value: bool) -> &mut Self { + pub fn send_fairness(mut self, value: bool) -> Self { self.0.send_fairness(value); self } /// Maximum reordering in packet number space before FACK style loss detection considers a /// packet lost. Should not be less than 3, per RFC5681. - pub fn packet_threshold(&mut self, value: u32) -> &mut Self { + pub fn packet_threshold(mut self, value: u32) -> Self { self.0.packet_threshold(value); self } /// Maximum reordering in time space before time based loss detection considers a packet lost, - /// as a factor of RTT - pub fn time_threshold(&mut self, value: f32) -> &mut Self { + /// as a factor of RTT. + pub fn time_threshold(mut self, value: f32) -> Self { self.0.time_threshold(value); self } - /// The RTT used before an RTT sample is taken - pub fn initial_rtt(&mut self, value: Duration) -> &mut Self { + /// The RTT used before an RTT sample is taken. + pub fn initial_rtt(mut self, value: Duration) -> Self { self.0.initial_rtt(value); self } /// 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`]. - pub fn initial_mtu(&mut self, value: u16) -> &mut Self { + /// it down to [`QuicTransportConfigBuilder::min_mtu`]. + pub fn initial_mtu(mut self, value: u16) -> Self { self.0.initial_mtu(value); self } @@ -205,17 +296,17 @@ 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 { + pub fn min_mtu(mut self, value: u16) -> Self { self.0.min_mtu(value); self } @@ -223,12 +314,12 @@ impl QuicTransportConfig { /// Specifies the MTU discovery config (see [`MtuDiscoveryConfig`] for details). /// /// Enabled by default. - pub fn mtu_discovery_config(&mut self, value: Option) -> &mut Self { + pub fn mtu_discovery_config(mut self, value: Option) -> Self { self.0.mtu_discovery_config(value); 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. /// @@ -237,12 +328,12 @@ impl QuicTransportConfig { /// well as the total size of stream write bursts can be inferred by observers under certain /// conditions. This analysis requires either an uncongested connection or application datagrams /// too large to be coalesced. - pub fn pad_to_mtu(&mut self, value: bool) -> &mut Self { + pub fn pad_to_mtu(mut self, value: bool) -> Self { self.0.pad_to_mtu(value); 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. @@ -250,67 +341,67 @@ impl QuicTransportConfig { /// Defaults to `None`, which disables controlling the peer's acknowledgement frequency. Even /// if set to `None`, the local side still supports the acknowledgement frequency QUIC /// extension and may use it in other ways. - pub fn ack_frequency_config(&mut self, value: Option) -> &mut Self { + pub fn ack_frequency_config(mut self, value: Option) -> Self { self.0.ack_frequency_config(value); self } /// Number of consecutive PTOs after which network is considered to be experiencing persistent congestion. - pub fn persistent_congestion_threshold(&mut self, value: u32) -> &mut Self { + pub fn persistent_congestion_threshold(mut self, value: u32) -> Self { self.0.persistent_congestion_threshold(value); 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. /// /// `None` to disable, which is the default. Only one side of any given connection needs keep-alive /// enabled for the connection to be preserved. Must be set lower than the idle_timeout of both /// peers to be effective. - pub fn keep_alive_interval(&mut self, value: Duration) -> &mut Self { + pub fn keep_alive_interval(mut self, value: Duration) -> Self { self.0.keep_alive_interval(Some(value)); self } - /// Maximum quantity of out-of-order crypto layer data to buffer - pub fn crypto_buffer_size(&mut self, value: usize) -> &mut Self { + /// Maximum quantity of out-of-order crypto layer data to buffer. + pub fn crypto_buffer_size(mut self, value: usize) -> 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. - pub fn allow_spin(&mut self, value: bool) -> &mut Self { + pub fn allow_spin(mut self, value: bool) -> Self { self.0.allow_spin(value); self } /// 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 /// exceeds this value, old datagrams are dropped until it is no longer exceeded. - pub fn datagram_receive_buffer_size(&mut self, value: Option) -> &mut Self { + pub fn datagram_receive_buffer_size(mut self, value: Option) -> Self { self.0.datagram_receive_buffer_size(value); 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 /// memory that may be consumed in that case. When the send buffer is full and a new datagram is /// sent, older datagrams are dropped until sufficient space is available. - pub fn datagram_send_buffer_size(&mut self, value: usize) -> &mut Self { + pub fn datagram_send_buffer_size(mut self, value: usize) -> Self { self.0.datagram_send_buffer_size(value); 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`. @@ -318,19 +409,20 @@ impl QuicTransportConfig { /// # Example /// ``` /// # use iroh::endpoint::QuicTransportConfig; use quinn_proto::congestion; use std::sync::Arc; - /// let mut config = QuicTransportConfig::default(); - /// config.congestion_controller_factory(Arc::new(congestion::NewRenoConfig::default())); + /// let config = QuicTransportConfig::builder() + /// .congestion_controller_factory(Arc::new(congestion::NewRenoConfig::default())) + /// .build(); /// ``` pub fn congestion_controller_factory( - &mut self, + mut self, factory: Arc, - ) -> &mut Self { + ) -> Self { self.0.congestion_controller_factory(factory); self } /// Whether to use "Generic Segmentation Offload" to accelerate transmits, when supported by the - /// environment + /// environment. /// /// Defaults to `true`. /// @@ -339,7 +431,7 @@ impl QuicTransportConfig { /// by all network interface drivers or packet inspection tools. `quinn-udp` will attempt to /// disable GSO automatically when unavailable, but this can lead to spurious packet loss at /// startup, temporarily degrading performance. - pub fn enable_segmentation_offload(&mut self, enabled: bool) -> &mut Self { + pub fn enable_segmentation_offload(mut self, enabled: bool) -> Self { self.0.enable_segmentation_offload(enabled); self } @@ -348,7 +440,7 @@ impl QuicTransportConfig { /// /// This will aid peers in inferring their reachable address, which in most NATd networks /// will not be easily available to them. - pub fn send_observed_address_reports(&mut self, enabled: bool) -> &mut Self { + pub fn send_observed_address_reports(mut self, enabled: bool) -> Self { self.0.send_observed_address_reports(enabled); self } @@ -359,7 +451,7 @@ impl QuicTransportConfig { /// address reports will do so if this transport parameter is set. In general, observed address /// reports cannot be trusted. This, however, can aid the current endpoint in inferring its /// reachable address, which in most NATd networks will not be easily available. - pub fn receive_observed_address_reports(&mut self, enabled: bool) -> &mut Self { + pub fn receive_observed_address_reports(mut self, enabled: bool) -> Self { self.0.receive_observed_address_reports(enabled); self } @@ -374,7 +466,7 @@ impl QuicTransportConfig { /// enable multipath as well. /// /// Note: this method will ignore values less than the recommended 13 and will log a warning. - pub fn max_concurrent_multipath_paths(&mut self, max_concurrent: u32) -> &mut Self { + pub fn max_concurrent_multipath_paths(mut self, max_concurrent: u32) -> Self { if max_concurrent < MAX_MULTIPATH_PATHS + 1 { warn!( "QuicTransportConfig::max_concurrent_multipath_paths must be at minimum {}, ignoring user supplied value", @@ -386,14 +478,14 @@ 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 + /// 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. - pub fn default_path_max_idle_timeout(&mut self, timeout: Duration) -> &mut Self { + pub fn default_path_max_idle_timeout(mut self, timeout: Duration) -> Self { if timeout > PATH_MAX_IDLE_TIMEOUT { warn!( "QuicTransportConfig::default_path_max_idle must be at most {:?}, ignoring user supplied value", @@ -405,15 +497,15 @@ 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, - /// [`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. - pub fn default_path_keep_alive_interval(&mut self, interval: Duration) -> &mut Self { + pub fn default_path_keep_alive_interval(mut self, interval: Duration) -> Self { if interval > HEARTBEAT_INTERVAL { warn!( "QuicTransportConfig::default_path_keep_alive must be at most {:?}, ignoring user supplied value", @@ -426,7 +518,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 @@ -437,7 +529,7 @@ impl QuicTransportConfig { /// 12 will be used. /// /// Note: this method will ignore values less than the recommended 12 and will log a warning. - pub fn set_max_remote_nat_traversal_addresses(&mut self, max_addresses: u8) -> &mut Self { + pub fn set_max_remote_nat_traversal_addresses(mut self, max_addresses: u8) -> Self { if max_addresses < MAX_MULTIPATH_PATHS as u8 { warn!( "QuicTransportConfig::max_remote_nat_traversal_addresses must be at least {}, ignoring user supplied value", @@ -454,7 +546,7 @@ impl QuicTransportConfig { /// This assigns a [`QlogFactory`] that produces qlog capture configurations for /// individual connections. #[cfg(feature = "qlog")] - pub fn qlog_factory(&mut self, factory: Arc) -> &mut Self { + pub fn qlog_factory(mut self, factory: Arc) -> Self { self.0.qlog_factory(factory); self } @@ -469,7 +561,7 @@ impl QuicTransportConfig { /// /// The files will be prefixed with `prefix`. #[cfg(feature = "qlog")] - pub fn qlog_from_env(&mut self, prefix: &str) -> &mut Self { + pub fn qlog_from_env(mut self, prefix: &str) -> Self { self.0.qlog_from_env(prefix); self } @@ -479,8 +571,141 @@ impl QuicTransportConfig { /// This uses [`QlogFileFactory`] to create a factory to write qlog traces into /// the specified directory. The files will be prefixed with `prefix`. #[cfg(feature = "qlog")] - pub fn qlog_from_path(&mut self, path: impl AsRef, prefix: &str) -> &mut Self { + pub fn qlog_from_path(mut self, path: impl AsRef, prefix: &str) -> Self { self.0.qlog_from_path(path, prefix); self } } + +/// A builder for a [`ServerConfig`]. +#[derive(Debug, Clone)] +pub struct ServerConfigBuilder { + inner: quinn::ServerConfig, + transport: QuicTransportConfig, +} + +/// Parameters governing incoming connections +/// +/// Default values should be suitable for most internet applications. +/// +/// Use a [`ServerConfigBuilder`] to adjust the default values. +/// +/// To create a [`ServerConfig`] compatible with your [`Endpoint`] identity, use the [`Endpoint::create_server_config_builder`] method. +/// +/// [`Endpoint`]: crate::Endpoint +/// [`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(Arc); + +impl ServerConfig { + pub(crate) fn to_inner_arc(&self) -> Arc { + self.0.clone() + } +} + +impl ServerConfigBuilder { + /// Build a [`ServerConfig`] from a [`ServerConfigBuilder`]. + pub fn build(self) -> ServerConfig { + ServerConfig(Arc::new(self.inner)) + } + + pub(crate) fn new(inner: quinn::ServerConfig, transport: QuicTransportConfig) -> Self { + Self { inner, transport } + } + + /// Sets a custom [`QuicTransportConfig`]. + pub fn set_transport_config(mut self, transport: QuicTransportConfig) -> Self { + self.inner.transport_config(transport.to_inner_arc()); + self.transport = transport; + self + } + + /// Sets a custom [`ValidationTokenConfig`]. + pub fn set_validation_token_config(mut self, validation_token: ValidationTokenConfig) -> Self { + self.inner.validation_token_config(validation_token); + self + } + + /// Private key used to authenticate data included in handshake tokens + pub fn set_token_key(mut self, value: Arc) -> 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 set_retry_token_lifetime(mut self, value: Duration) -> Self { + self.inner.retry_token_lifetime(value); + self + } + + /// 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 + /// 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. + /// + /// [`Incoming`]: crate::endpoint::Incoming + pub fn set_max_incoming(mut self, max_incoming: usize) -> Self { + self.inner.max_incoming(max_incoming); + self + } + + /// 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 + /// 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`]. + /// + /// [`Incoming`]: crate::endpoint::Incoming + pub fn set_incoming_buffer_size(mut self, incoming_buffer_size: u64) -> Self { + self.inner.incoming_buffer_size(incoming_buffer_size); + self + } + + /// Maximum number of received bytes to buffer for all [`Incoming`] + /// 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 + /// 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. + /// + /// [`Incoming`]: crate::endpoint::Incoming + pub fn set_incoming_buffer_size_total(mut self, incoming_buffer_size_total: u64) -> 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 [`quinn::StdSystemTime`], which simply calls [`SystemTime::now()`](std::time::SystemTime::now). + /// + /// [`SystemTime`]: std::time::SystemTime + pub fn set_time_source(mut self, time_source: Arc) -> Self { + self.inner.time_source(time_source); + self + } +} diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index 913a5ea29fb..abb61bd949c 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; @@ -130,7 +130,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_proto::ServerConfig, /// Skip verification of SSL certificates from relay servers /// @@ -1542,7 +1542,6 @@ mod tests { use n0_future::{MergeBounded, StreamExt, time}; use n0_tracing_test::traced_test; 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}; @@ -1552,6 +1551,7 @@ mod tests { Endpoint, RelayMode, SecretKey, discovery::static_provider::StaticProvider, dns::DnsResolver, + endpoint::QuicTransportConfig, magicsock::{ Handle, MagicSock, TransportConfig, mapped_addrs::{EndpointIdMappedAddr, MappedAddr}, @@ -1583,12 +1583,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_inner_arc()); server_config } @@ -1953,7 +1954,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 b8d8548cac4..a1adc3aaec4 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; @@ -33,7 +33,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 8dcd5688bad..10e52a5f6e6 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) } } @@ -607,14 +607,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, }, }; diff --git a/iroh/src/test_utils/qlog.rs b/iroh/src/test_utils/qlog.rs index 17b733271e0..321b0be333d 100644 --- a/iroh/src/test_utils/qlog.rs +++ b/iroh/src/test_utils/qlog.rs @@ -72,16 +72,16 @@ impl QlogFileGroup { } #[cfg(feature = "qlog")] { - let mut config = QuicTransportConfig::default(); + let mut builder = QuicTransportConfig::builder(); 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 = builder.qlog_factory(Arc::new(factory)); } - Ok(config) + Ok(builder.build()) } } }