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