noq_proto/config/transport.rs
1#[cfg(feature = "qlog")]
2use std::path::Path;
3use std::{
4 fmt,
5 net::SocketAddr,
6 num::{NonZeroU8, NonZeroU32},
7 sync::Arc,
8};
9
10use crate::{
11 ConnectionId, Duration, INITIAL_MTU, Instant, MAX_UDP_PAYLOAD, Side, VarInt,
12 VarIntBoundsExceeded, address_discovery, congestion, connection::qlog::QlogSink,
13};
14#[cfg(feature = "qlog")]
15use crate::{QlogFactory, QlogFileFactory};
16
17/// Parameters governing the core QUIC state machine
18///
19/// Default values should be suitable for most internet applications. Applications protocols which
20/// forbid remotely-initiated streams should set `max_concurrent_bidi_streams` and
21/// `max_concurrent_uni_streams` to zero.
22///
23/// In some cases, performance or resource requirements can be improved by tuning these values to
24/// suit a particular application and/or network connection. In particular, data window sizes can be
25/// tuned for a particular expected round trip time, link capacity, and memory availability. Tuning
26/// for higher bandwidths and latencies increases worst-case memory consumption, but does not impair
27/// performance at lower bandwidths and latencies. The default configuration is tuned for a 100Mbps
28/// link with a 100ms round trip time.
29#[derive(Clone)]
30pub struct TransportConfig {
31 pub(crate) max_concurrent_bidi_streams: VarInt,
32 pub(crate) max_concurrent_uni_streams: VarInt,
33 pub(crate) max_idle_timeout: Option<VarInt>,
34 pub(crate) stream_receive_window: VarInt,
35 pub(crate) receive_window: VarInt,
36 pub(crate) send_window: u64,
37 pub(crate) send_fairness: bool,
38
39 pub(crate) packet_threshold: u32,
40 pub(crate) time_threshold: f32,
41 pub(crate) initial_rtt: Duration,
42 pub(crate) initial_mtu: u16,
43 pub(crate) min_mtu: u16,
44 pub(crate) mtu_discovery_config: Option<MtuDiscoveryConfig>,
45 pub(crate) pad_to_mtu: bool,
46 pub(crate) ack_frequency_config: Option<AckFrequencyConfig>,
47 pub(crate) max_outgoing_bytes_per_second: Option<u64>,
48
49 pub(crate) persistent_congestion_threshold: u32,
50 pub(crate) keep_alive_interval: Option<Duration>,
51 pub(crate) crypto_buffer_size: usize,
52 pub(crate) allow_spin: bool,
53 pub(crate) datagram_receive_buffer_size: Option<usize>,
54 pub(crate) datagram_send_buffer_size: usize,
55 #[cfg(test)]
56 pub(crate) deterministic_packet_numbers: bool,
57
58 pub(crate) congestion_controller_factory: Arc<dyn congestion::ControllerFactory + Send + Sync>,
59
60 pub(crate) enable_segmentation_offload: bool,
61
62 pub(crate) address_discovery_role: address_discovery::Role,
63
64 pub(crate) max_concurrent_multipath_paths: Option<NonZeroU32>,
65
66 pub(crate) default_path_max_idle_timeout: Option<Duration>,
67 pub(crate) default_path_keep_alive_interval: Option<Duration>,
68
69 pub(crate) max_remote_nat_traversal_addresses: Option<NonZeroU8>,
70 pub(crate) server_handshake_migration: bool,
71
72 #[cfg(feature = "qlog")]
73 pub(crate) qlog_factory: Option<Arc<dyn QlogFactory>>,
74}
75
76impl TransportConfig {
77 /// Maximum number of incoming bidirectional streams that may be open concurrently
78 ///
79 /// Must be nonzero for the peer to open any bidirectional streams.
80 ///
81 /// Worst-case memory use is directly proportional to `max_concurrent_bidi_streams *
82 /// stream_receive_window`, with an upper bound proportional to `receive_window`.
83 pub fn max_concurrent_bidi_streams(&mut self, value: VarInt) -> &mut Self {
84 self.max_concurrent_bidi_streams = value;
85 self
86 }
87
88 /// Variant of `max_concurrent_bidi_streams` affecting unidirectional streams
89 pub fn max_concurrent_uni_streams(&mut self, value: VarInt) -> &mut Self {
90 self.max_concurrent_uni_streams = value;
91 self
92 }
93
94 /// Maximum duration of inactivity to accept before timing out the connection.
95 ///
96 /// The true idle timeout is the minimum of this and the peer's own max idle timeout. `None`
97 /// represents an infinite timeout. Defaults to 30 seconds.
98 ///
99 /// **WARNING**: If a peer or its network path malfunctions or acts maliciously, an infinite
100 /// idle timeout can result in permanently hung futures!
101 ///
102 /// ```
103 /// # use std::{convert::TryInto, time::Duration};
104 /// # use noq_proto::{TransportConfig, VarInt, VarIntBoundsExceeded};
105 /// # fn main() -> Result<(), VarIntBoundsExceeded> {
106 /// let mut config = TransportConfig::default();
107 ///
108 /// // Set the idle timeout as `VarInt`-encoded milliseconds
109 /// config.max_idle_timeout(Some(VarInt::from_u32(10_000).into()));
110 ///
111 /// // Set the idle timeout as a `Duration`
112 /// config.max_idle_timeout(Some(Duration::from_secs(10).try_into()?));
113 /// # Ok(())
114 /// # }
115 /// ```
116 pub fn max_idle_timeout(&mut self, value: Option<IdleTimeout>) -> &mut Self {
117 self.max_idle_timeout = value.map(|t| t.0);
118 self
119 }
120
121 /// Maximum number of bytes the peer may transmit without acknowledgement on any one stream
122 /// before becoming blocked.
123 ///
124 /// This should be set to at least the expected connection latency multiplied by the maximum
125 /// desired throughput. Setting this smaller than `receive_window` helps ensure that a single
126 /// stream doesn't monopolize receive buffers, which may otherwise occur if the application
127 /// chooses not to read from a large stream for a time while still requiring data on other
128 /// streams.
129 pub fn stream_receive_window(&mut self, value: VarInt) -> &mut Self {
130 self.stream_receive_window = value;
131 self
132 }
133
134 /// Maximum number of bytes the peer may transmit across all streams of a connection before
135 /// becoming blocked.
136 ///
137 /// This should be set to at least the expected connection latency multiplied by the maximum
138 /// desired throughput. Larger values can be useful to allow maximum throughput within a
139 /// stream while another is blocked.
140 pub fn receive_window(&mut self, value: VarInt) -> &mut Self {
141 self.receive_window = value;
142 self
143 }
144
145 /// Maximum number of bytes to transmit to a peer without acknowledgment
146 ///
147 /// Provides an upper bound on memory when communicating with peers that issue large amounts of
148 /// flow control credit. Endpoints that wish to handle large numbers of connections robustly
149 /// should take care to set this low enough to guarantee memory exhaustion does not occur if
150 /// every connection uses the entire window.
151 pub fn send_window(&mut self, value: u64) -> &mut Self {
152 self.send_window = value;
153 self
154 }
155
156 /// Whether to implement fair queuing for send streams having the same priority.
157 ///
158 /// When enabled, connections schedule data from outgoing streams having the same priority in a
159 /// round-robin fashion. When disabled, streams are scheduled in the order they are written to.
160 ///
161 /// Note that this only affects streams with the same priority. Higher priority streams always
162 /// take precedence over lower priority streams.
163 ///
164 /// Disabling fairness can reduce fragmentation and protocol overhead for workloads that use
165 /// many small streams.
166 pub fn send_fairness(&mut self, value: bool) -> &mut Self {
167 self.send_fairness = value;
168 self
169 }
170
171 /// Maximum reordering in packet number space before FACK style loss detection considers a
172 /// packet lost. Should not be less than 3, per RFC5681.
173 pub fn packet_threshold(&mut self, value: u32) -> &mut Self {
174 self.packet_threshold = value;
175 self
176 }
177
178 /// Maximum reordering in time space before time based loss detection considers a packet lost,
179 /// as a factor of RTT
180 pub fn time_threshold(&mut self, value: f32) -> &mut Self {
181 self.time_threshold = value;
182 self
183 }
184
185 /// The RTT used before an RTT sample is taken
186 pub fn initial_rtt(&mut self, value: Duration) -> &mut Self {
187 self.initial_rtt = value;
188 self
189 }
190
191 /// The initial value to be used as the maximum UDP payload size before running MTU discovery
192 /// (see [`TransportConfig::mtu_discovery_config`]).
193 ///
194 /// Must be at least 1200, which is the default, and known to be safe for typical internet
195 /// applications. Larger values are more efficient, but increase the risk of packet loss due to
196 /// exceeding the network path's IP MTU. If the provided value is higher than what the network
197 /// path actually supports, packet loss will eventually trigger black hole detection and bring
198 /// it down to [`TransportConfig::min_mtu`].
199 pub fn initial_mtu(&mut self, value: u16) -> &mut Self {
200 self.initial_mtu = value.max(INITIAL_MTU);
201 self
202 }
203
204 pub(crate) fn get_initial_mtu(&self) -> u16 {
205 self.initial_mtu.max(self.min_mtu)
206 }
207
208 /// The maximum UDP payload size guaranteed to be supported by the network.
209 ///
210 /// Must be at least 1200, which is the default, and lower than or equal to
211 /// [`TransportConfig::initial_mtu`].
212 ///
213 /// Real-world MTUs can vary according to ISP, VPN, and properties of intermediate network links
214 /// outside of either endpoint's control. Extreme care should be used when raising this value
215 /// outside of private networks where these factors are fully controlled. If the provided value
216 /// is higher than what the network path actually supports, the result will be unpredictable and
217 /// catastrophic packet loss, without a possibility of repair. Prefer
218 /// [`TransportConfig::initial_mtu`] together with
219 /// [`TransportConfig::mtu_discovery_config`] to set a maximum UDP payload size that robustly
220 /// adapts to the network.
221 pub fn min_mtu(&mut self, value: u16) -> &mut Self {
222 self.min_mtu = value.max(INITIAL_MTU);
223 self
224 }
225
226 /// Specifies the MTU discovery config (see [`MtuDiscoveryConfig`] for details).
227 ///
228 /// Enabled by default.
229 pub fn mtu_discovery_config(&mut self, value: Option<MtuDiscoveryConfig>) -> &mut Self {
230 self.mtu_discovery_config = value;
231 self
232 }
233
234 /// Pad UDP datagrams carrying application data to current maximum UDP payload size
235 ///
236 /// Disabled by default. UDP datagrams containing loss probes are exempt from padding.
237 ///
238 /// Enabling this helps mitigate traffic analysis by network observers, but it increases
239 /// bandwidth usage. Without this mitigation precise plain text size of application datagrams as
240 /// well as the total size of stream write bursts can be inferred by observers under certain
241 /// conditions. This analysis requires either an uncongested connection or application datagrams
242 /// too large to be coalesced.
243 pub fn pad_to_mtu(&mut self, value: bool) -> &mut Self {
244 self.pad_to_mtu = value;
245 self
246 }
247
248 /// Specifies the ACK frequency config (see [`AckFrequencyConfig`] for details)
249 ///
250 /// The provided configuration will be ignored if the peer does not support the acknowledgement
251 /// frequency QUIC extension.
252 ///
253 /// Defaults to `None`, which disables controlling the peer's acknowledgement frequency. Even
254 /// if set to `None`, the local side still supports the acknowledgement frequency QUIC
255 /// extension and may use it in other ways.
256 pub fn ack_frequency_config(&mut self, value: Option<AckFrequencyConfig>) -> &mut Self {
257 self.ack_frequency_config = value;
258 self
259 }
260
261 /// Configures an outbound rate limit (in bytes per second) for each connection.
262 ///
263 /// Defaults to `None`, which disables rate limiting.
264 pub fn max_outgoing_bytes_per_second(&mut self, value: Option<u64>) -> &mut Self {
265 self.max_outgoing_bytes_per_second = value;
266 self
267 }
268
269 /// Number of consecutive PTOs after which network is considered to be experiencing persistent
270 /// congestion.
271 pub fn persistent_congestion_threshold(&mut self, value: u32) -> &mut Self {
272 self.persistent_congestion_threshold = value;
273 self
274 }
275
276 /// Period of inactivity before sending a keep-alive packet
277 ///
278 /// Keep-alive packets prevent an inactive but otherwise healthy connection from timing out.
279 ///
280 /// `None` to disable, which is the default. Only one side of any given connection needs
281 /// keep-alive enabled for the connection to be preserved. Must be set lower than the
282 /// idle_timeout of both peers to be effective.
283 pub fn keep_alive_interval(&mut self, value: Option<Duration>) -> &mut Self {
284 self.keep_alive_interval = value;
285 self
286 }
287
288 /// Maximum quantity of out-of-order crypto layer data to buffer
289 pub fn crypto_buffer_size(&mut self, value: usize) -> &mut Self {
290 self.crypto_buffer_size = value;
291 self
292 }
293
294 /// Whether the implementation is permitted to set the spin bit on this connection
295 ///
296 /// This allows passive observers to easily judge the round trip time of a connection, which can
297 /// be useful for network administration but sacrifices a small amount of privacy.
298 pub fn allow_spin(&mut self, value: bool) -> &mut Self {
299 self.allow_spin = value;
300 self
301 }
302
303 /// Maximum number of incoming application datagram bytes to buffer, or None to disable
304 /// incoming datagrams
305 ///
306 /// The peer is forbidden to send single datagrams larger than this size. If the aggregate size
307 /// of all datagrams that have been received from the peer but not consumed by the application
308 /// exceeds this value, old datagrams are dropped until it is no longer exceeded.
309 pub fn datagram_receive_buffer_size(&mut self, value: Option<usize>) -> &mut Self {
310 self.datagram_receive_buffer_size = value;
311 self
312 }
313
314 /// Maximum number of outgoing application datagram bytes to buffer
315 ///
316 /// While datagrams are sent ASAP, it is possible for an application to generate data faster
317 /// than the link, or even the underlying hardware, can transmit them. This limits the amount of
318 /// memory that may be consumed in that case. When the send buffer is full and a new datagram is
319 /// sent, older datagrams are dropped until sufficient space is available.
320 pub fn datagram_send_buffer_size(&mut self, value: usize) -> &mut Self {
321 self.datagram_send_buffer_size = value;
322 self
323 }
324
325 /// Whether to force every packet number to be used
326 ///
327 /// By default, packet numbers are occasionally skipped to ensure peers aren't ACKing packets
328 /// before they see them.
329 #[cfg(test)]
330 pub(crate) fn deterministic_packet_numbers(&mut self, enabled: bool) -> &mut Self {
331 self.deterministic_packet_numbers = enabled;
332 self
333 }
334
335 /// How to construct new `congestion::Controller`s
336 ///
337 /// Typically the refcounted configuration of a `congestion::Controller`,
338 /// e.g. a `congestion::NewRenoConfig`.
339 ///
340 /// # Example
341 /// ```
342 /// # use noq_proto::*; use std::sync::Arc;
343 /// let mut config = TransportConfig::default();
344 /// config.congestion_controller_factory(Arc::new(congestion::NewRenoConfig::default()));
345 /// ```
346 pub fn congestion_controller_factory(
347 &mut self,
348 factory: Arc<dyn congestion::ControllerFactory + Send + Sync + 'static>,
349 ) -> &mut Self {
350 self.congestion_controller_factory = factory;
351 self
352 }
353
354 /// Whether to use "Generic Segmentation Offload" to accelerate transmits, when supported by the
355 /// environment
356 ///
357 /// Defaults to `true`.
358 ///
359 /// GSO dramatically reduces CPU consumption when sending large numbers of packets with the same
360 /// headers, such as when transmitting bulk data on a connection. However, it is not supported
361 /// by all network interface drivers or packet inspection tools. `noq-udp` will attempt to
362 /// disable GSO automatically when unavailable, but this can lead to spurious packet loss at
363 /// startup, temporarily degrading performance.
364 pub fn enable_segmentation_offload(&mut self, enabled: bool) -> &mut Self {
365 self.enable_segmentation_offload = enabled;
366 self
367 }
368
369 /// Whether to send observed address reports to peers.
370 ///
371 /// This will aid peers in inferring their reachable address, which in most NATd networks
372 /// will not be easily available to them.
373 pub fn send_observed_address_reports(&mut self, enabled: bool) -> &mut Self {
374 self.address_discovery_role.send = enabled;
375 self
376 }
377
378 /// Whether to receive observed address reports from other peers.
379 ///
380 /// Peers with the address discovery extension enabled that are willing to provide observed
381 /// address reports will do so if this transport parameter is set. In general, observed address
382 /// reports cannot be trusted. This, however, can aid the current endpoint in inferring its
383 /// reachable address, which in most NATd networks will not be easily available.
384 pub fn receive_observed_address_reports(&mut self, enabled: bool) -> &mut Self {
385 self.address_discovery_role.receive = enabled;
386 self
387 }
388
389 /// Enables the Multipath Extension for QUIC.
390 ///
391 /// Setting this to any nonzero value will enable the Multipath Extension for QUIC,
392 /// <https://datatracker.ietf.org/doc/draft-ietf-quic-multipath/>.
393 ///
394 /// The value provided specifies the number maximum number of paths this endpoint may open
395 /// concurrently when multipath is negotiated. For any path to be opened, the remote must
396 /// enable multipath as well.
397 pub fn max_concurrent_multipath_paths(&mut self, max_concurrent: u32) -> &mut Self {
398 self.max_concurrent_multipath_paths = NonZeroU32::new(max_concurrent);
399 self
400 }
401
402 /// Sets a default per-path maximum idle timeout.
403 ///
404 /// If the path is idle for this long the path will be abandoned. Bear in mind this will
405 /// interact with the [`TransportConfig::max_idle_timeout`], if the last path is
406 /// abandoned the entire connection will be closed.
407 ///
408 /// You can also change this using [`Connection::set_path_max_idle_timeout`] for
409 /// existing paths.
410 ///
411 /// The idle timeout will only apply to paths of a multipath-negotiated connection. Before
412 /// multipath is negotiated, only the connection-wide max idle timeout is in effect.
413 ///
414 /// [`Connection::set_path_max_idle_timeout`]: crate::Connection::set_path_max_idle_timeout
415 pub fn default_path_max_idle_timeout(&mut self, timeout: Option<Duration>) -> &mut Self {
416 self.default_path_max_idle_timeout = timeout;
417 self
418 }
419
420 /// Sets a default per-path keep alive interval
421 ///
422 /// Note that this does not interact with the connection-wide
423 /// [`TransportConfig::keep_alive_interval`]. This setting will keep this path active,
424 /// [`TransportConfig::keep_alive_interval`] will keep the connection active, with no
425 /// control over which path is used for this.
426 ///
427 /// You can also change this using [`Connection::set_path_keep_alive_interval`] for
428 /// existing path.
429 ///
430 /// [`Connection::set_path_keep_alive_interval`]: crate::Connection::set_path_keep_alive_interval
431 pub fn default_path_keep_alive_interval(&mut self, interval: Option<Duration>) -> &mut Self {
432 self.default_path_keep_alive_interval = interval;
433 self
434 }
435
436 /// Get the initial max [`crate::PathId`] this endpoint allows.
437 ///
438 /// Returns `None` if multipath is disabled.
439 pub(crate) fn get_initial_max_path_id(&self) -> Option<crate::PathId> {
440 self.max_concurrent_multipath_paths
441 // a max_concurrent_multipath_paths value of 1 only allows the first path, which
442 // has id 0
443 .map(|nonzero_concurrent| nonzero_concurrent.get() - 1)
444 .map(Into::into)
445 }
446
447 /// Sets the maximum number of nat traversal addresses this endpoint allows the remote to
448 /// advertise
449 ///
450 /// Setting this to any nonzero value will enable n0's nat traversal protocol, loosely based in
451 /// the Nat Traversal Extension for QUIC, see
452 /// <https://www.ietf.org/archive/id/draft-seemann-quic-nat-traversal-02.html>
453 ///
454 /// This implementation expects the multipath extension to be enabled as well. if not yet
455 /// enabled via [`Self::max_concurrent_multipath_paths`], then that setting is set to 8.
456 pub fn max_remote_nat_traversal_addresses(&mut self, max_addresses: u8) -> &mut Self {
457 self.max_remote_nat_traversal_addresses = NonZeroU8::new(max_addresses);
458 if max_addresses != 0 && self.max_concurrent_multipath_paths.is_none() {
459 self.max_concurrent_multipath_paths(8);
460 }
461 self
462 }
463
464 /// Sets whether the server is allowed to migrate once during the handshake.
465 ///
466 /// **Enabling this is not RFC9000 compliant.**
467 ///
468 /// Defaults to `false`.
469 ///
470 /// Enabling this allows the server to migrate once during the handshake: it can send a
471 /// response from a different address than the client's initial packet was sent to. Once
472 /// an authenticated Handshake packet is received the server can no longer migrate
473 /// during the handshake (or after the handshake if not other extension enables this).
474 ///
475 /// This can be used to duplicate the client's initial packet to multiple addresses for
476 /// the server and accept the fastest response. The server will discard all but the
477 /// first such initial, considering any remaining as duplicates.
478 pub fn server_handshake_migration(&mut self, allow_migration: bool) -> &mut Self {
479 self.server_handshake_migration = allow_migration;
480 self
481 }
482
483 /// Configures qlog capturing by setting a [`QlogFactory`].
484 ///
485 /// This assigns a [`QlogFactory`] that produces qlog capture configurations for
486 /// individual connections.
487 #[cfg(feature = "qlog")]
488 pub fn qlog_factory(&mut self, factory: Arc<dyn QlogFactory>) -> &mut Self {
489 self.qlog_factory = Some(factory);
490 self
491 }
492
493 /// Configures qlog capturing through the `QLOGDIR` environment variable.
494 ///
495 /// This uses [`QlogFileFactory::from_env`] to create a factory to write qlog traces
496 /// into the directory set through the `QLOGDIR` environment variable.
497 ///
498 /// If `QLOGDIR` is not set, no traces will be written. If `QLOGDIR` is set to a path
499 /// that does not exist, it will be created.
500 ///
501 /// The files will be prefixed with `prefix`.
502 #[cfg(feature = "qlog")]
503 pub fn qlog_from_env(&mut self, prefix: &str) -> &mut Self {
504 self.qlog_factory(Arc::new(QlogFileFactory::from_env().with_prefix(prefix)))
505 }
506
507 /// Configures qlog capturing into a directory.
508 ///
509 /// This uses [`QlogFileFactory`] to create a factory to write qlog traces into
510 /// the specified directory. The files will be prefixed with `prefix`.
511 #[cfg(feature = "qlog")]
512 pub fn qlog_from_path(&mut self, path: impl AsRef<Path>, prefix: &str) -> &mut Self {
513 self.qlog_factory(Arc::new(
514 QlogFileFactory::new(path.as_ref().to_owned()).with_prefix(prefix),
515 ))
516 }
517
518 pub(crate) fn create_qlog_sink(
519 &self,
520 side: Side,
521 remote: SocketAddr,
522 initial_dst_cid: ConnectionId,
523 now: Instant,
524 ) -> QlogSink {
525 #[cfg(not(feature = "qlog"))]
526 let sink = {
527 let _ = (side, remote, initial_dst_cid, now);
528 QlogSink::default()
529 };
530
531 #[cfg(feature = "qlog")]
532 let sink = {
533 if let Some(config) = self
534 .qlog_factory
535 .as_ref()
536 .and_then(|factory| factory.for_connection(side, remote, initial_dst_cid, now))
537 {
538 QlogSink::new(config, initial_dst_cid, side, now)
539 } else {
540 QlogSink::default()
541 }
542 };
543
544 sink
545 }
546}
547
548impl Default for TransportConfig {
549 fn default() -> Self {
550 const EXPECTED_RTT: u32 = 100; // ms
551 const MAX_STREAM_BANDWIDTH: u32 = 12500 * 1000; // bytes/s
552 // Window size needed to avoid pipeline
553 // stalls
554 const STREAM_RWND: u32 = MAX_STREAM_BANDWIDTH / 1000 * EXPECTED_RTT;
555
556 Self {
557 max_concurrent_bidi_streams: 100u32.into(),
558 max_concurrent_uni_streams: 100u32.into(),
559 // 30 second default recommended by RFC 9308 ยง 3.2
560 max_idle_timeout: Some(VarInt(30_000)),
561 stream_receive_window: STREAM_RWND.into(),
562 receive_window: VarInt::MAX,
563 send_window: (8 * STREAM_RWND).into(),
564 send_fairness: true,
565
566 packet_threshold: 3,
567 time_threshold: 9.0 / 8.0,
568 initial_rtt: Duration::from_millis(333), /* per spec, intentionally distinct from
569 * EXPECTED_RTT */
570 initial_mtu: INITIAL_MTU,
571 min_mtu: INITIAL_MTU,
572 mtu_discovery_config: Some(MtuDiscoveryConfig::default()),
573 pad_to_mtu: false,
574 ack_frequency_config: None,
575 max_outgoing_bytes_per_second: None,
576
577 persistent_congestion_threshold: 3,
578 keep_alive_interval: None,
579 crypto_buffer_size: 16 * 1024,
580 allow_spin: true,
581 datagram_receive_buffer_size: Some(STREAM_RWND as usize),
582 datagram_send_buffer_size: 1024 * 1024,
583 #[cfg(test)]
584 deterministic_packet_numbers: false,
585
586 congestion_controller_factory: Arc::new(congestion::CubicConfig::default()),
587
588 enable_segmentation_offload: true,
589
590 address_discovery_role: address_discovery::Role::default(),
591
592 // disabled multipath by default
593 max_concurrent_multipath_paths: None,
594 default_path_max_idle_timeout: None,
595 default_path_keep_alive_interval: None,
596
597 // nat traversal disabled by default
598 max_remote_nat_traversal_addresses: None,
599 server_handshake_migration: false,
600
601 #[cfg(feature = "qlog")]
602 qlog_factory: None,
603 }
604 }
605}
606
607impl fmt::Debug for TransportConfig {
608 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
609 let Self {
610 max_concurrent_bidi_streams,
611 max_concurrent_uni_streams,
612 max_idle_timeout,
613 stream_receive_window,
614 receive_window,
615 send_window,
616 send_fairness,
617 packet_threshold,
618 time_threshold,
619 initial_rtt,
620 initial_mtu,
621 min_mtu,
622 mtu_discovery_config,
623 pad_to_mtu,
624 ack_frequency_config,
625 max_outgoing_bytes_per_second,
626 persistent_congestion_threshold,
627 keep_alive_interval,
628 crypto_buffer_size,
629 allow_spin,
630 datagram_receive_buffer_size,
631 datagram_send_buffer_size,
632 #[cfg(test)]
633 deterministic_packet_numbers: _,
634 congestion_controller_factory: _,
635 enable_segmentation_offload,
636 address_discovery_role,
637 max_concurrent_multipath_paths,
638 default_path_max_idle_timeout,
639 default_path_keep_alive_interval,
640 max_remote_nat_traversal_addresses,
641 server_handshake_migration,
642 #[cfg(feature = "qlog")]
643 qlog_factory,
644 } = self;
645 let mut s = fmt.debug_struct("TransportConfig");
646
647 s.field("max_concurrent_bidi_streams", max_concurrent_bidi_streams)
648 .field("max_concurrent_uni_streams", max_concurrent_uni_streams)
649 .field("max_idle_timeout", max_idle_timeout)
650 .field("stream_receive_window", stream_receive_window)
651 .field("receive_window", receive_window)
652 .field("send_window", send_window)
653 .field("send_fairness", send_fairness)
654 .field("packet_threshold", packet_threshold)
655 .field("time_threshold", time_threshold)
656 .field("initial_rtt", initial_rtt)
657 .field("initial_mtu", initial_mtu)
658 .field("min_mtu", min_mtu)
659 .field("mtu_discovery_config", mtu_discovery_config)
660 .field("pad_to_mtu", pad_to_mtu)
661 .field("ack_frequency_config", ack_frequency_config)
662 .field(
663 "max_outgoing_bytes_per_second",
664 max_outgoing_bytes_per_second,
665 )
666 .field(
667 "persistent_congestion_threshold",
668 persistent_congestion_threshold,
669 )
670 .field("keep_alive_interval", keep_alive_interval)
671 .field("crypto_buffer_size", crypto_buffer_size)
672 .field("allow_spin", allow_spin)
673 .field("datagram_receive_buffer_size", datagram_receive_buffer_size)
674 .field("datagram_send_buffer_size", datagram_send_buffer_size)
675 // congestion_controller_factory not debug
676 .field("enable_segmentation_offload", enable_segmentation_offload)
677 .field("address_discovery_role", address_discovery_role)
678 .field(
679 "max_concurrent_multipath_paths",
680 max_concurrent_multipath_paths,
681 )
682 .field(
683 "default_path_max_idle_timeout",
684 default_path_max_idle_timeout,
685 )
686 .field(
687 "default_path_keep_alive_interval",
688 default_path_keep_alive_interval,
689 )
690 .field(
691 "max_remote_nat_traversal_addresses",
692 max_remote_nat_traversal_addresses,
693 )
694 .field("server_handshake_migration", server_handshake_migration);
695 #[cfg(feature = "qlog")]
696 s.field("qlog_factory", &qlog_factory.is_some());
697
698 s.finish_non_exhaustive()
699 }
700}
701
702/// Parameters for controlling the peer's acknowledgement frequency
703///
704/// The parameters provided in this config will be sent to the peer at the beginning of the
705/// connection, so it can take them into account when sending acknowledgements (see each parameter's
706/// description for details on how it influences acknowledgement frequency).
707///
708/// noq's implementation follows the fourth draft of the
709/// [QUIC Acknowledgement Frequency extension](https://datatracker.ietf.org/doc/html/draft-ietf-quic-ack-frequency-04).
710/// The defaults produce behavior slightly different than the behavior without this extension,
711/// because they change the way reordered packets are handled (see
712/// [`AckFrequencyConfig::reordering_threshold`] for details).
713#[derive(Clone, Debug)]
714pub struct AckFrequencyConfig {
715 pub(crate) ack_eliciting_threshold: VarInt,
716 pub(crate) max_ack_delay: Option<Duration>,
717 pub(crate) reordering_threshold: VarInt,
718}
719
720impl AckFrequencyConfig {
721 /// The ack-eliciting threshold we will request the peer to use
722 ///
723 /// This threshold represents the number of ack-eliciting packets an endpoint may receive
724 /// without immediately sending an ACK.
725 ///
726 /// The remote peer should send at least one ACK frame when more than this number of
727 /// ack-eliciting packets have been received. A value of 0 results in a receiver immediately
728 /// acknowledging every ack-eliciting packet.
729 ///
730 /// Defaults to 1, which sends ACK frames for every other ack-eliciting packet.
731 pub fn ack_eliciting_threshold(&mut self, value: VarInt) -> &mut Self {
732 self.ack_eliciting_threshold = value;
733 self
734 }
735
736 /// The `max_ack_delay` we will request the peer to use
737 ///
738 /// This parameter represents the maximum amount of time that an endpoint waits before sending
739 /// an ACK when the ack-eliciting threshold hasn't been reached.
740 ///
741 /// The effective `max_ack_delay` will be clamped to be at least the peer's `min_ack_delay`
742 /// transport parameter, and at most the greater of the current path RTT or 25ms.
743 ///
744 /// Defaults to `None`, in which case the peer's original `max_ack_delay` will be used, as
745 /// obtained from its transport parameters.
746 pub fn max_ack_delay(&mut self, value: Option<Duration>) -> &mut Self {
747 self.max_ack_delay = value;
748 self
749 }
750
751 /// The reordering threshold we will request the peer to use
752 ///
753 /// This threshold represents the amount of out-of-order packets that will trigger an endpoint
754 /// to send an ACK, without waiting for `ack_eliciting_threshold` to be exceeded or for
755 /// `max_ack_delay` to be elapsed.
756 ///
757 /// A value of 0 indicates out-of-order packets do not elicit an immediate ACK. A value of 1
758 /// immediately acknowledges any packets that are received out of order (this is also the
759 /// behavior when the extension is disabled).
760 ///
761 /// It is recommended to set this value to [`TransportConfig::packet_threshold`] minus one.
762 /// Since the default value for [`TransportConfig::packet_threshold`] is 3, this value defaults
763 /// to 2.
764 pub fn reordering_threshold(&mut self, value: VarInt) -> &mut Self {
765 self.reordering_threshold = value;
766 self
767 }
768}
769
770impl Default for AckFrequencyConfig {
771 fn default() -> Self {
772 Self {
773 ack_eliciting_threshold: VarInt(1),
774 max_ack_delay: None,
775 reordering_threshold: VarInt(2),
776 }
777 }
778}
779
780/// Parameters governing MTU discovery.
781///
782/// # The why of MTU discovery
783///
784/// By design, QUIC ensures during the handshake that the network path between the client and the
785/// server is able to transmit unfragmented UDP packets with a body of 1200 bytes. In other words,
786/// once the connection is established, we know that the network path's maximum transmission unit
787/// (MTU) is of at least 1200 bytes (plus IP and UDP headers). Because of this, a QUIC endpoint can
788/// split outgoing data in packets of 1200 bytes, with confidence that the network will be able to
789/// deliver them (if the endpoint were to send bigger packets, they could prove too big and end up
790/// being dropped).
791///
792/// There is, however, a significant overhead associated to sending a packet. If the same
793/// information can be sent in fewer packets, that results in higher throughput. The amount of
794/// packets that need to be sent is inversely proportional to the MTU: the higher the MTU, the
795/// bigger the packets that can be sent, and the fewer packets that are needed to transmit a given
796/// amount of bytes.
797///
798/// Most networks have an MTU higher than 1200. Through MTU discovery, endpoints can detect the
799/// path's MTU and, if it turns out to be higher, start sending bigger packets.
800///
801/// # MTU discovery internals
802///
803/// noq implements MTU discovery through DPLPMTUD (Datagram Packetization Layer Path MTU
804/// Discovery), described in [section 14.3 of RFC
805/// 9000](https://www.rfc-editor.org/rfc/rfc9000.html#section-14.3). This method consists of sending
806/// QUIC packets padded to a particular size (called PMTU probes), and waiting to see if the remote
807/// peer responds with an ACK. If an ACK is received, that means the probe arrived at the remote
808/// peer, which in turn means that the network path's MTU is of at least the packet's size. If the
809/// probe is lost, it is sent another 2 times before concluding that the MTU is lower than the
810/// packet's size.
811///
812/// MTU discovery runs on a schedule (e.g. every 600 seconds) specified through
813/// [`MtuDiscoveryConfig::interval`]. The first run happens right after the handshake, and
814/// subsequent discoveries are scheduled to run when the interval has elapsed, starting from the
815/// last time when MTU discovery completed.
816///
817/// Since the search space for MTUs is quite big (the smallest possible MTU is 1200, and the highest
818/// is 65527), noq performs a binary search to keep the number of probes as low as possible. The
819/// lower bound of the search is equal to [`TransportConfig::initial_mtu`] in the
820/// initial MTU discovery run, and is equal to the currently discovered MTU in subsequent runs. The
821/// upper bound is determined by the minimum of [`MtuDiscoveryConfig::upper_bound`] and the
822/// `max_udp_payload_size` transport parameter received from the peer during the handshake.
823///
824/// # Black hole detection
825///
826/// If, at some point, the network path no longer accepts packets of the detected size, packet loss
827/// will eventually trigger black hole detection and reset the detected MTU to 1200. In that case,
828/// MTU discovery will be triggered after [`MtuDiscoveryConfig::black_hole_cooldown`] (ignoring the
829/// timer that was set based on [`MtuDiscoveryConfig::interval`]).
830///
831/// # Interaction between peers
832///
833/// There is no guarantee that the MTU on the path between A and B is the same as the MTU of the
834/// path between B and A. Therefore, each peer in the connection needs to run MTU discovery
835/// independently in order to discover the path's MTU.
836#[derive(Clone, Debug)]
837pub struct MtuDiscoveryConfig {
838 pub(crate) interval: Duration,
839 pub(crate) upper_bound: u16,
840 pub(crate) minimum_change: u16,
841 pub(crate) black_hole_cooldown: Duration,
842}
843
844impl MtuDiscoveryConfig {
845 /// Specifies the time to wait after completing MTU discovery before starting a new MTU
846 /// discovery run.
847 ///
848 /// Defaults to 600 seconds, as recommended by [RFC
849 /// 8899](https://www.rfc-editor.org/rfc/rfc8899).
850 pub fn interval(&mut self, value: Duration) -> &mut Self {
851 self.interval = value;
852 self
853 }
854
855 /// Specifies the upper bound to the max UDP payload size that MTU discovery will search for.
856 ///
857 /// Defaults to 1452, to stay within Ethernet's MTU when using IPv4 and IPv6. The highest
858 /// allowed value is 65527, which corresponds to the maximum permitted UDP payload on IPv6.
859 ///
860 /// It is safe to use an arbitrarily high upper bound, regardless of the network path's MTU. The
861 /// only drawback is that MTU discovery might take more time to finish.
862 pub fn upper_bound(&mut self, value: u16) -> &mut Self {
863 self.upper_bound = value.min(MAX_UDP_PAYLOAD);
864 self
865 }
866
867 /// Specifies the amount of time that MTU discovery should wait after a black hole was detected
868 /// before running again. Defaults to one minute.
869 ///
870 /// Black hole detection can be spuriously triggered in case of congestion, so it makes sense to
871 /// try MTU discovery again after a short period of time.
872 pub fn black_hole_cooldown(&mut self, value: Duration) -> &mut Self {
873 self.black_hole_cooldown = value;
874 self
875 }
876
877 /// Specifies the minimum MTU change to stop the MTU discovery phase.
878 /// Defaults to 20.
879 pub fn minimum_change(&mut self, value: u16) -> &mut Self {
880 self.minimum_change = value;
881 self
882 }
883}
884
885impl Default for MtuDiscoveryConfig {
886 fn default() -> Self {
887 Self {
888 interval: Duration::from_secs(600),
889 upper_bound: 1452,
890 black_hole_cooldown: Duration::from_secs(60),
891 minimum_change: 20,
892 }
893 }
894}
895
896/// Maximum duration of inactivity to accept before timing out the connection
897///
898/// This wraps an underlying [`VarInt`], representing the duration in milliseconds. Values can be
899/// constructed by converting directly from `VarInt`, or using `TryFrom<Duration>`.
900///
901/// ```
902/// # use std::{convert::TryFrom, time::Duration};
903/// # use noq_proto::{IdleTimeout, VarIntBoundsExceeded, VarInt};
904/// # fn main() -> Result<(), VarIntBoundsExceeded> {
905/// // A `VarInt`-encoded value in milliseconds
906/// let timeout = IdleTimeout::from(VarInt::from_u32(10_000));
907///
908/// // Try to convert a `Duration` into a `VarInt`-encoded timeout
909/// let timeout = IdleTimeout::try_from(Duration::from_secs(10))?;
910/// # Ok(())
911/// # }
912/// ```
913#[derive(Default, Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
914pub struct IdleTimeout(VarInt);
915
916impl From<VarInt> for IdleTimeout {
917 fn from(inner: VarInt) -> Self {
918 Self(inner)
919 }
920}
921
922impl TryFrom<Duration> for IdleTimeout {
923 type Error = VarIntBoundsExceeded;
924
925 fn try_from(timeout: Duration) -> Result<Self, Self::Error> {
926 let inner = VarInt::try_from(timeout.as_millis())?;
927 Ok(Self(inner))
928 }
929}
930
931impl fmt::Debug for IdleTimeout {
932 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
933 self.0.fmt(f)
934 }
935}