A long-running rc.33 daemon reached OOM with roughly 22 GiB resident and 24 GiB swapped. Live inspection found 40 GiB and 10/20 GiB anonymous container mappings; the active worker stack and tracing metadata identified iroh's scheduled path-open retry loop. In iroh 1.2.0 every failed connection requeues the same path, while each retry visits every connection again. Separate control and data connections multiply the queue geometrically. Vendor the published iroh 1.2.0 source and select it with a workspace Cargo patch. Deduplicate retry admission and cap distinct pending paths at 64, evicting the oldest before inserting a new candidate. Keep the current and next retry batches bounded without combining control/data connections or changing wire protocols, identity derivation, authentication or state. Document source provenance, the live evidence and removal conditions. Upstream PR 4551 merged on October 6, but inspection of the official 1.3.0 crate still shows the unconditional enqueue, so that upgrade alone is not a fix. No version bump, release tag or installed-service change. The default workspace suite includes the exact vendored admission module. Three regressions failed with the original enqueue and pass with the fix: repeated failures across connections, duplicate admission at capacity, and 10,000 distinct candidates with bounded length/capacity and FIFO eviction. Transport integration tests continue using real endpoints and encryption. Checks on Linux in nix develop: - cargo fmt --all -- --check - cargo clippy --locked --workspace --all-targets -- -D warnings - cargo test --locked --workspace --all-targets, with only the inherited TSUNAGI_CONTROL_SOCKET removed from the test environment - git diff --cached --check Initial full-suite runs failed six DNS readiness checks because the inherited system socket path overrode their temporary state directories. Clearing that environment variable resolves those failures without changing application code. Root/network-dependent ignored tests and other operating systems were not run.
717 lines
31 KiB
Rust
717 lines
31 KiB
Rust
//! Exporting and encapsulating structs from noq
|
|
//!
|
|
//! Co-locates all iroh-noq exports.
|
|
//!
|
|
//! There are some structs that we use in particular ways, where we would like
|
|
//! to limit or expand how those structs are used in iroh. By encapsulating them
|
|
//! we can ensure the functionality needed to make iroh work.
|
|
|
|
#[cfg(feature = "qlog")]
|
|
use std::path::Path;
|
|
use std::{sync::Arc, time::Duration};
|
|
|
|
/// `noq` types that are used in the public iroh API.
|
|
// Each type is notated with the iroh type or noq type that uses it.
|
|
pub use noq::{
|
|
AcceptBi, // iroh::endpoint::Connection
|
|
AcceptUni, // iroh::endpoint::Connection
|
|
AckFrequencyConfig, // iroh::endpoint::quic::QuicTransportConfig
|
|
Closed, // iroh::endpoint::WeakConnectionHandle
|
|
ClosedStream, // iroh::protocol::AcceptError, noq::RecvStream, noq::SendStream
|
|
ConnectionError, // iroh::endpoint::ConnectError
|
|
ConnectionStats, // iroh::endpoint::Connection
|
|
Dir, // noq::StreamId
|
|
IdleTimeout, // iroh::endpoint::quic::QuicTransportConfig
|
|
MtuDiscoveryConfig, // iroh::endpoint::quic::QuicTransportConfig
|
|
OpenBi, // iroh::endpoint::Connection
|
|
OpenUni, // iroh::endpoint::Connection
|
|
PathStats, // iroh::socket::remote_map::remote_state::PathInfo
|
|
ReadDatagram, // iroh::endpoint::Connection
|
|
ReadError, // noq::RecvStream
|
|
ReadExactError, // noq::RecvStream
|
|
ReadToEndError, // noq::RecvStream
|
|
RecvStream, // noq::AcceptBi, noq::AcceptUni, noq::OpenBi, noq::OpenUni
|
|
ResetError, // noq::RecvStream
|
|
SendDatagram, // iroh::endpoint::Connection
|
|
SendDatagramError, // iroh::endpoint::Connection
|
|
SendStream, // noq::AcceptBi, noq::OpenUni
|
|
Side, // iroh::endpoint::Connection, noq::StreamId,
|
|
StoppedError, // noq::SendStream
|
|
StreamId, // noq::RecvStream
|
|
UnorderedRecvStream, // noq::RecvStream
|
|
VarInt, // various
|
|
VarIntBoundsExceeded, // noq::VarInt, noq::IdleTimeout
|
|
WriteError, // noq::SendStream
|
|
};
|
|
#[cfg(feature = "qlog")]
|
|
pub use noq::{QlogConfig, QlogFactory, QlogFileFactory};
|
|
/// `noq_proto` types that are used in the public iroh API.
|
|
// Each type is notated with the iroh type or noq type that uses it.
|
|
pub use noq_proto::{
|
|
ApplicationClose, // noq::ConnectionError
|
|
Chunk, // noq::RecvStream
|
|
ConnectError as QuicConnectError, // iroh::endpoint::ConnectWithOptsError
|
|
ConnectionClose, // noq::ConnectionError
|
|
DecryptedInitial, // iroh::endpoint::connection::Incoming
|
|
FrameStats, // noq::ConnectionStats
|
|
FrameType, // noq_proto::TransportError
|
|
IncomingAlpns, // iroh::endpoint::DecryptedInitial
|
|
PathId, // noq_proto::crypto::PacketKey
|
|
RttEstimator, // noq_proto::congestion::Controller
|
|
TimeSource, // iroh::endpoint::quic::ServerConfig
|
|
TokenLog, // noq::ValidationTokenConfig
|
|
TokenReuseError, // noq::TokenLog
|
|
TransportError, // noq::ConnectionError
|
|
TransportErrorCode, // noq_proto::TransportError
|
|
UdpStats, // noq::ConnectionStats
|
|
ValidationTokenConfig, // iroh::endpoint::quic::::ServerConfig
|
|
congestion::{
|
|
Controller, // iroh::endpoint::Connection
|
|
ControllerFactory, // iroh::endpoint::quic::QuicTransportConfig
|
|
ControllerMetrics, // noq_proto::congestion::Controller
|
|
},
|
|
crypto::{
|
|
CryptoError, // noq_proto::crypto::CryptoError, noq_proto::crypto::PacketKey
|
|
ExportKeyingMaterialError, // iroh::endpoint::Connection
|
|
HandshakeTokenKey, // iroh::endpoint::quic::ServerConfig
|
|
HeaderKey, // noq_proto::crypto::Keys
|
|
Keys, // noq_proto::crypto::Session
|
|
PacketKey, // noq_proto::crypto::Keys
|
|
UnsupportedVersion, // noq_proto::ConnectError
|
|
},
|
|
transport_parameters::TransportParameters, // noq_proto::crypot::ServerConfig
|
|
};
|
|
use tracing::warn;
|
|
|
|
use crate::socket::{
|
|
HEARTBEAT_INTERVAL, MAX_MULTIPATH_PATHS, MAX_QNT_ADDRESSES, PATH_MAX_IDLE_TIMEOUT,
|
|
};
|
|
|
|
/// Builder for a [`QuicTransportConfig`].
|
|
#[derive(Debug, Clone)]
|
|
pub struct QuicTransportConfigBuilder(noq::TransportConfig);
|
|
|
|
/// Parameters governing the core QUIC state machine
|
|
///
|
|
/// Default values should be suitable for most internet applications. Applications protocols which
|
|
/// forbid remotely-initiated streams should set `max_concurrent_bidi_streams` and
|
|
/// `max_concurrent_uni_streams` to zero.
|
|
///
|
|
/// In some cases, performance or resource requirements can be improved by tuning these values to
|
|
/// suit a particular application and/or network connection. In particular, data window sizes can be
|
|
/// tuned for a particular expected round trip time, link capacity, and memory availability. Tuning
|
|
/// for higher bandwidths and latencies increases worst-case memory consumption, but does not impair
|
|
/// 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:
|
|
/// - [`QuicTransportConfigBuilder::default_path_keep_alive_interval`]
|
|
/// - [`QuicTransportConfigBuilder::default_path_max_idle_timeout`]
|
|
/// - [`QuicTransportConfigBuilder::max_concurrent_multipath_paths`]
|
|
/// - [`QuicTransportConfigBuilder::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(Arc<noq::TransportConfig>);
|
|
|
|
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<noq::TransportConfig> {
|
|
self.0.clone()
|
|
}
|
|
}
|
|
|
|
impl QuicTransportConfigBuilder {
|
|
/// Creates a default [`QuicTransportConfigBuilder`].
|
|
fn new() -> Self {
|
|
let mut cfg = noq::TransportConfig::default();
|
|
// Override some transport config settings.
|
|
cfg.keep_alive_interval(Some(HEARTBEAT_INTERVAL));
|
|
cfg.default_path_keep_alive_interval(Some(HEARTBEAT_INTERVAL));
|
|
cfg.default_path_max_idle_timeout(Some(PATH_MAX_IDLE_TIMEOUT));
|
|
cfg.max_concurrent_multipath_paths(MAX_MULTIPATH_PATHS);
|
|
cfg.max_remote_nat_traversal_addresses(MAX_QNT_ADDRESSES);
|
|
cfg.server_handshake_migration(true);
|
|
Self(cfg)
|
|
}
|
|
|
|
/// Builds 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.
|
|
///
|
|
/// 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) -> 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) -> Self {
|
|
self.0.max_concurrent_uni_streams(value);
|
|
self
|
|
}
|
|
|
|
/// Maximum duration of inactivity to accept before timing out the connection.
|
|
///
|
|
/// The true idle timeout is the minimum of this and the peer's own max idle timeout. `None`
|
|
/// represents an infinite timeout. Defaults to 30 seconds.
|
|
///
|
|
/// **WARNING**: If a peer or its network path malfunctions or acts maliciously, an infinite
|
|
/// idle timeout can result in permanently hung futures!
|
|
///
|
|
/// ```
|
|
/// # use std::{convert::TryInto, time::Duration};
|
|
/// # use iroh::endpoint::{QuicTransportConfig, VarInt, VarIntBoundsExceeded};
|
|
/// # fn main() -> Result<(), VarIntBoundsExceeded> {
|
|
/// 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`
|
|
/// 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<IdleTimeout>) -> Self {
|
|
self.0.max_idle_timeout(value);
|
|
self
|
|
}
|
|
|
|
/// Maximum number of bytes the peer may transmit without acknowledgement on any one stream
|
|
/// before becoming blocked.
|
|
///
|
|
/// This should be set to at least the expected connection latency multiplied by the maximum
|
|
/// desired throughput. Setting this smaller than `receive_window` helps ensure that a single
|
|
/// 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) -> Self {
|
|
self.0.stream_receive_window(value);
|
|
self
|
|
}
|
|
|
|
/// Maximum number of bytes the peer may transmit across all streams of a connection before
|
|
/// becoming blocked.
|
|
///
|
|
/// 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) -> Self {
|
|
self.0.receive_window(value);
|
|
self
|
|
}
|
|
|
|
/// 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) -> Self {
|
|
self.0.send_window(value);
|
|
self
|
|
}
|
|
|
|
/// Whether to implement fair queuing for send streams having the same priority.
|
|
///
|
|
/// When enabled, connections schedule data from outgoing streams having the same priority in a
|
|
/// round-robin fashion. When disabled, streams are scheduled in the order they are written to.
|
|
///
|
|
/// Note that this only affects streams with the same priority. Higher priority streams always
|
|
/// take precedence over lower priority streams.
|
|
///
|
|
/// Disabling fairness can reduce fragmentation and protocol overhead for workloads that use
|
|
/// many small streams.
|
|
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) -> 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) -> Self {
|
|
self.0.time_threshold(value);
|
|
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 [`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 [`QuicTransportConfigBuilder::min_mtu`].
|
|
pub fn initial_mtu(mut self, value: u16) -> Self {
|
|
self.0.initial_mtu(value);
|
|
self
|
|
}
|
|
|
|
/// 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
|
|
/// [`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
|
|
/// [`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) -> Self {
|
|
self.0.min_mtu(value);
|
|
self
|
|
}
|
|
|
|
/// Specifies the MTU discovery config (see [`MtuDiscoveryConfig`] for details).
|
|
///
|
|
/// Enabled by default.
|
|
pub fn mtu_discovery_config(mut self, value: Option<MtuDiscoveryConfig>) -> Self {
|
|
self.0.mtu_discovery_config(value);
|
|
self
|
|
}
|
|
|
|
/// Pad UDP datagrams carrying application data to current maximum UDP payload size.
|
|
///
|
|
/// Disabled by default. UDP datagrams containing loss probes are exempt from padding.
|
|
///
|
|
/// Enabling this helps mitigate traffic analysis by network observers, but it increases
|
|
/// bandwidth usage. Without this mitigation precise plain text size of application datagrams as
|
|
/// 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) -> Self {
|
|
self.0.pad_to_mtu(value);
|
|
self
|
|
}
|
|
|
|
/// 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.
|
|
///
|
|
/// 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<AckFrequencyConfig>) -> 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) -> Self {
|
|
self.0.persistent_congestion_threshold(value);
|
|
self
|
|
}
|
|
|
|
/// Period of inactivity before sending a keep-alive packet.
|
|
///
|
|
/// Keep-alive packets prevent an inactive but otherwise healthy connection from timing
|
|
/// out. They are important to keep NAT bindings alive and firewalls open.
|
|
///
|
|
/// The default is 5s, please be careful when modifying this as it may affect connection
|
|
/// stability. Must be set lower than the idle_timeout of both peers to be effective.
|
|
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) -> Self {
|
|
self.0.crypto_buffer_size(value);
|
|
self
|
|
}
|
|
|
|
/// 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) -> Self {
|
|
self.0.allow_spin(value);
|
|
self
|
|
}
|
|
|
|
/// Maximum number of incoming application datagram bytes to buffer, or None to disable
|
|
/// 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<usize>) -> Self {
|
|
self.0.datagram_receive_buffer_size(value);
|
|
self
|
|
}
|
|
|
|
/// 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) -> Self {
|
|
self.0.datagram_send_buffer_size(value);
|
|
self
|
|
}
|
|
|
|
/// How to construct new `congestion::Controller`s.
|
|
///
|
|
/// Typically the refcounted configuration of a `congestion::Controller`,
|
|
/// e.g. a `congestion::NewRenoConfig`.
|
|
///
|
|
/// # Example
|
|
/// ```
|
|
/// # use iroh::endpoint::QuicTransportConfig; use noq_proto::congestion; use std::sync::Arc;
|
|
/// let config = QuicTransportConfig::builder()
|
|
/// .congestion_controller_factory(Arc::new(congestion::NewRenoConfig::default()))
|
|
/// .build();
|
|
/// ```
|
|
pub fn congestion_controller_factory(
|
|
mut self,
|
|
factory: Arc<dyn ControllerFactory + Send + Sync + 'static>,
|
|
) -> Self {
|
|
self.0.congestion_controller_factory(factory);
|
|
self
|
|
}
|
|
|
|
/// Whether to use "Generic Segmentation Offload" to accelerate transmits, when supported by the
|
|
/// environment.
|
|
///
|
|
/// Defaults to `true`.
|
|
///
|
|
/// GSO dramatically reduces CPU consumption when sending large numbers of packets with the same
|
|
/// headers, such as when transmitting bulk data on a connection. However, it is not supported
|
|
/// by all network interface drivers or packet inspection tools. `noq-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) -> Self {
|
|
self.0.enable_segmentation_offload(enabled);
|
|
self
|
|
}
|
|
|
|
/// Whether to send observed address reports to peers.
|
|
///
|
|
/// 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) -> Self {
|
|
self.0.send_observed_address_reports(enabled);
|
|
self
|
|
}
|
|
|
|
/// Whether to receive observed address reports from other peers.
|
|
///
|
|
/// Peers with the address discovery extension enabled that are willing to provide observed
|
|
/// 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) -> Self {
|
|
self.0.receive_observed_address_reports(enabled);
|
|
self
|
|
}
|
|
|
|
/// Enables the Multipath Extension for QUIC.
|
|
///
|
|
/// Setting this to any nonzero value will enable the Multipath Extension for QUIC,
|
|
/// <https://datatracker.ietf.org/doc/draft-ietf-quic-multipath/>.
|
|
///
|
|
/// The value provided specifies the number maximum number of paths this endpoint may open
|
|
/// concurrently when multipath is negotiated. For any path to be opened, the remote must
|
|
/// 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) -> Self {
|
|
if max_concurrent < MAX_MULTIPATH_PATHS + 1 {
|
|
warn!(
|
|
"QuicTransportConfig::max_concurrent_multipath_paths must be at minimum {}, ignoring user supplied value",
|
|
MAX_MULTIPATH_PATHS + 1
|
|
);
|
|
return self;
|
|
}
|
|
self.0.max_concurrent_multipath_paths(max_concurrent);
|
|
self
|
|
}
|
|
|
|
/// 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 [`QuicTransportConfigBuilder::max_idle_timeout`], if the last path is
|
|
/// abandoned the entire connection will be closed.
|
|
///
|
|
/// Note: values higher than `PATH_MAX_IDLE_TIMEOUT` (15 seconds) are clamped and a warning is logged.
|
|
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 {:?}, clamping",
|
|
PATH_MAX_IDLE_TIMEOUT
|
|
);
|
|
self.0
|
|
.default_path_max_idle_timeout(Some(PATH_MAX_IDLE_TIMEOUT));
|
|
return self;
|
|
}
|
|
self.0.default_path_max_idle_timeout(Some(timeout));
|
|
self
|
|
}
|
|
|
|
/// Sets a default per-path keep alive interval.
|
|
///
|
|
/// Note that this does not interact with the connection-wide
|
|
/// [`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) -> Self {
|
|
if interval > HEARTBEAT_INTERVAL {
|
|
warn!(
|
|
"QuicTransportConfig::default_path_keep_alive must be at most {:?}, ignoring user supplied value",
|
|
HEARTBEAT_INTERVAL
|
|
);
|
|
return self;
|
|
}
|
|
self.0.default_path_keep_alive_interval(Some(interval));
|
|
self
|
|
}
|
|
|
|
/// Sets the maximum number of nat traversal addresses this endpoint allows the remote to
|
|
/// advertise.
|
|
///
|
|
/// Setting this to any nonzero value will enable Iroh's holepunching, loosely based in the Nat
|
|
/// Traversal Extension for QUIC, see
|
|
/// <https://www.ietf.org/archive/id/draft-seemann-quic-nat-traversal-02.html>
|
|
///
|
|
/// This implementation expects the multipath extension to be enabled as well. If not yet
|
|
/// enabled via [`Self::max_concurrent_multipath_paths`], a default value of
|
|
/// 8 will be used.
|
|
///
|
|
/// Note: this method will ignore values less than the recommended 8 and will log a warning.
|
|
pub fn 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",
|
|
MAX_MULTIPATH_PATHS
|
|
);
|
|
return self;
|
|
}
|
|
self.0.max_remote_nat_traversal_addresses(max_addresses);
|
|
self
|
|
}
|
|
|
|
/// Configures qlog capturing by setting a [`QlogFactory`].
|
|
///
|
|
/// This assigns a [`QlogFactory`] that produces qlog capture configurations for
|
|
/// individual connections.
|
|
#[cfg(feature = "qlog")]
|
|
pub fn qlog_factory(mut self, factory: Arc<dyn QlogFactory>) -> Self {
|
|
self.0.qlog_factory(factory);
|
|
self
|
|
}
|
|
|
|
/// Configures qlog capturing through the `QLOGDIR` environment variable.
|
|
///
|
|
/// This uses [`QlogFileFactory::from_env`] to create a factory to write qlog traces
|
|
/// into the directory set through the `QLOGDIR` environment variable.
|
|
///
|
|
/// If `QLOGDIR` is not set, no traces will be written. If `QLOGDIR` is set to a path
|
|
/// that does not exist, it will be created.
|
|
///
|
|
/// The files will be prefixed with `prefix`.
|
|
#[cfg(feature = "qlog")]
|
|
pub fn qlog_from_env(mut self, prefix: &str) -> Self {
|
|
self.0.qlog_from_env(prefix);
|
|
self
|
|
}
|
|
|
|
/// Configures qlog capturing into a directory.
|
|
///
|
|
/// 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<Path>, prefix: &str) -> Self {
|
|
self.0.qlog_from_path(path, prefix);
|
|
self
|
|
}
|
|
}
|
|
|
|
/// A builder for a [`ServerConfig`].
|
|
#[derive(Debug, Clone)]
|
|
pub struct ServerConfigBuilder {
|
|
inner: noq::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 `noq::ServerConfig` takes a `TransportConfig`, which we new-type as a `QuicTransportConfig`
|
|
#[derive(Debug, Clone)]
|
|
pub struct ServerConfig(Arc<noq::ServerConfig>);
|
|
|
|
impl ServerConfig {
|
|
pub(crate) fn to_inner_arc(&self) -> Arc<noq::ServerConfig> {
|
|
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: noq::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<dyn HandshakeTokenKey>) -> 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 [`noq::StdSystemTime`], which simply calls [`SystemTime::now()`](std::time::SystemTime::now).
|
|
///
|
|
/// [`SystemTime`]: std::time::SystemTime
|
|
pub fn set_time_source(mut self, time_source: Arc<dyn TimeSource>) -> Self {
|
|
self.inner.time_source(time_source);
|
|
self
|
|
}
|
|
}
|