Skip to main content

tokio_quiche/settings/
quic.rs

1// Copyright (C) 2025, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//
11//     * Redistributions in binary form must reproduce the above copyright
12//       notice, this list of conditions and the following disclaimer in the
13//       documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27use foundations::settings::settings;
28use serde::Deserialize;
29use serde::Deserializer;
30use serde_with::serde_as;
31use serde_with::DurationMilliSeconds;
32use std::time::Duration;
33
34pub use qlog::writer::QlogCompression;
35
36fn deserialize_optional_nonzero_usize<'de, D>(
37    deserializer: D,
38) -> Result<Option<usize>, D::Error>
39where
40    D: Deserializer<'de>,
41{
42    Ok(Option::<usize>::deserialize(deserializer)?.filter(|value| *value != 0))
43}
44
45/// QUIC configuration parameters.
46#[serde_as]
47#[settings]
48#[non_exhaustive]
49pub struct QuicSettings {
50    /// Configures the list of supported application protocols.
51    ///
52    /// Defaults to `[b"h3"]`.
53    #[serde(skip, default = "QuicSettings::default_alpn")]
54    pub alpn: Vec<Vec<u8>>,
55
56    /// Configures whether to enable DATAGRAM frame support. H3 connections
57    /// copy this setting from the underlying QUIC connection.
58    ///
59    /// Defaults to `true`.
60    #[serde(default = "QuicSettings::default_enable_dgram")]
61    pub enable_dgram: bool,
62
63    /// Max queue length for received DATAGRAM frames.
64    ///
65    /// Defaults to `2^16`.
66    #[serde(default = "QuicSettings::default_dgram_max_queue_len")]
67    pub dgram_recv_max_queue_len: usize,
68
69    /// Max queue length for sending DATAGRAM frames.
70    ///
71    /// Defaults to `2^16`.
72    #[serde(default = "QuicSettings::default_dgram_max_queue_len")]
73    pub dgram_send_max_queue_len: usize,
74
75    /// Configures whether to enable early data (0-RTT) support. Currently only
76    /// supported for servers.
77    ///
78    /// Defaults to `false`.
79    pub enable_early_data: bool,
80
81    /// Sets the `initial_max_data` transport parameter.
82    ///
83    /// Defaults to 10 MB.
84    #[serde(default = "QuicSettings::default_initial_max_data")]
85    pub initial_max_data: u64,
86
87    /// Sets the `initial_max_stream_data_bidi_local` transport parameter.
88    ///
89    /// Defaults to 1 MB.
90    #[serde(default = "QuicSettings::default_initial_max_stream_data")]
91    pub initial_max_stream_data_bidi_local: u64,
92
93    /// Sets the `initial_max_stream_data_bidi_remote` transport parameter.
94    ///
95    /// Defaults to 1 MB.
96    #[serde(default = "QuicSettings::default_initial_max_stream_data")]
97    pub initial_max_stream_data_bidi_remote: u64,
98
99    /// Sets the `initial_max_stream_data_uni` transport parameter.
100    ///
101    /// Defaults to 1 MB.
102    #[serde(default = "QuicSettings::default_initial_max_stream_data")]
103    pub initial_max_stream_data_uni: u64,
104
105    /// Sets the `initial_max_streams_bidi` transport parameter.
106    ///
107    /// Defaults to `100`.
108    #[serde(default = "QuicSettings::default_initial_max_streams")]
109    pub initial_max_streams_bidi: u64,
110
111    /// Sets the `initial_max_streams_uni` transport parameter.
112    ///
113    /// Defaults to `100`.
114    #[serde(default = "QuicSettings::default_initial_max_streams")]
115    pub initial_max_streams_uni: u64,
116
117    /// Configures the max idle timeout of the connection in milliseconds. The
118    /// real idle timeout is the minimum of this and the peer's
119    /// `max_idle_timeout`.
120    ///
121    /// Defaults to 56 seconds.
122    #[serde(
123        rename = "max_idle_timeout_ms",
124        default = "QuicSettings::default_max_idle_timeout"
125    )]
126    #[serde_as(as = "Option<DurationMilliSeconds>")]
127    pub max_idle_timeout: Option<Duration>,
128
129    /// Configures whether the local endpoint supports active connection
130    /// migration.
131    ///
132    /// Defaults to `true` (meaning disabled).
133    #[serde(default = "QuicSettings::default_disable_active_migration")]
134    pub disable_active_migration: bool,
135
136    /// Sets the `active_connection_id_limit` transport parameter.
137    ///
138    /// Defaults to 2. Note that values less than 2 will be ignored.
139    #[serde(default = "QuicSettings::default_active_connection_id_limit")]
140    pub active_connection_id_limit: u64,
141
142    /// Sets the maximum incoming UDP payload size.
143    ///
144    /// Defaults to 1350 bytes.
145    #[serde(default = "QuicSettings::default_max_recv_udp_payload_size")]
146    pub max_recv_udp_payload_size: usize,
147
148    /// Sets the maximum outgoing UDP payload size.
149    ///
150    /// Defaults to 1350 bytes.
151    #[serde(default = "QuicSettings::default_max_send_udp_payload_size")]
152    pub max_send_udp_payload_size: usize,
153
154    /// Whether to validate client IPs in QUIC initials.
155    ///
156    /// If set to `true`, any received QUIC initial will immediately spawn a
157    /// connection and start crypto operations for the handshake. Otherwise,
158    /// the client is asked to execute a stateless retry first (the default).
159    pub disable_client_ip_validation: bool,
160
161    /// Path to a file in which TLS secrets will be logged in
162    /// [SSLKEYLOGFILE format](https://tlswg.org/sslkeylogfile/draft-ietf-tls-keylogfile.html).
163    pub keylog_file: Option<String>,
164
165    /// Path to a directory where QLOG files will be saved.
166    pub qlog_dir: Option<String>,
167
168    /// Compression applied to QLOG output files.
169    ///
170    /// Defaults to [`QlogCompression::None`], preserving the historical
171    /// behavior of emitting raw `.sqlog` files. The `Gzip` and `Zstd`
172    /// variants require the `qlog-gzip` and `qlog-zstd` Cargo features
173    /// (both enabled by default); builds that disable those features
174    /// cannot reference the corresponding variant.
175    #[serde(default)]
176    pub qlog_compression: QlogCompression,
177
178    /// Congestion control algorithm to use.
179    ///
180    /// For available values, see
181    /// [`CongestionControlAlgorithm`](quiche::CongestionControlAlgorithm).
182    ///
183    /// Defaults to `cubic`.
184    #[serde(default = "QuicSettings::default_cc_algorithm")]
185    pub cc_algorithm: String,
186
187    /// The default initial congestion window size in terms of packet count.
188    ///
189    /// Defaults to 10.
190    #[serde(default = "QuicSettings::default_initial_congestion_window_packets")]
191    pub initial_congestion_window_packets: usize,
192
193    /// Fixed congestion window in bytes for `congestion_window_unchecked`.
194    ///
195    /// Defaults to [`usize::MAX`]. Requires selecting
196    /// `cc_algorithm = "congestion_window_unchecked"` and enabling the
197    /// `congestion_window_unchecked_available` Cargo feature. Ignored for other
198    /// algorithms or when the feature is disabled.
199    ///
200    /// See [`quiche::Config::set_unchecked_congestion_window`] for risks and
201    /// restrictions.
202    #[serde(default = "QuicSettings::default_unchecked_congestion_window")]
203    pub unchecked_congestion_window: usize,
204
205    /// Configures whether to enable relaxed loss detection on spurious loss.
206    ///
207    /// Defaults to `false`.
208    pub enable_relaxed_loss_threshold: bool,
209
210    /// Configures whether to do path MTU discovery.
211    ///
212    /// Defaults to `false`.
213    pub discover_path_mtu: bool,
214
215    /// Configures the maximum number of PMTUD probe attempts before treating
216    /// a size as failed.
217    ///
218    /// Defaults to 3 per [RFC 8899 Section 5.1.2](https://datatracker.ietf.org/doc/html/rfc8899#section-5.1.2).
219    /// If 0 is passed, the default value is used.
220    #[serde(default = "QuicSettings::default_pmtud_max_probes")]
221    pub pmtud_max_probes: u8,
222
223    /// Whether to use HyStart++ (only with `cubic` and `reno` CC).
224    ///
225    /// Defaults to `true`.
226    #[serde(default = "QuicSettings::default_enable_hystart")]
227    pub enable_hystart: bool,
228
229    /// Optionally enables pacing for outgoing packets.
230    ///
231    /// Note: this also requires pacing-compatible
232    /// [`SocketCapabilities`](crate::socket::SocketCapabilities).
233    pub enable_pacing: bool,
234
235    /// Sets the max value for pacing rate.
236    ///
237    /// By default, there is no limit.
238    pub max_pacing_rate: Option<u64>,
239
240    /// Optionally enables expensive versions of the
241    /// `accepted_initial_quic_packet_count`
242    /// and `rejected_initial_quic_packet_count` metrics.
243    ///
244    /// The expensive versions add a label for the peer IP subnet (`/24` for
245    /// IPv4, `/32` for IPv6). They thus generate many more time series if
246    /// peers are arbitrary eyeballs from the global Internet.
247    pub enable_expensive_packet_count_metrics: bool,
248
249    /// Forwards [`quiche`] logs into the logging system currently used by
250    /// [`foundations`].
251    ///
252    /// Defaults to `false`.
253    ///
254    /// # Warning
255    /// This should **only be used for local debugging**. `quiche` can emit lots
256    /// (and lots, and lots) of logs (the TRACE level emits a log record for
257    /// every packet and frame) and you can very easily overwhelm your
258    /// logging pipeline.
259    pub capture_quiche_logs: bool,
260
261    /// A timeout for the QUIC handshake, in milliseconds.
262    ///
263    /// For connections that accept 0-RTT early data, the application is
264    /// started before the handshake completes, but the timeout still applies
265    /// until the handshake completes.
266    ///
267    /// Disabled by default.
268    #[serde(rename = "handshake_timeout_ms")]
269    #[serde_as(as = "Option<DurationMilliSeconds>")]
270    pub handshake_timeout: Option<Duration>,
271
272    /// The maximum number of newly-created connections that will be queued for
273    /// the application to receive. Not applicable to client-side usage.
274    ///
275    /// Defaults to 1024 connections.
276    #[serde(default = "QuicSettings::default_listen_backlog")]
277    pub listen_backlog: usize,
278
279    /// Whether or not to verify the peer's certificate.
280    ///
281    /// Defaults to `false`, meaning no peer verification is performed. Note
282    /// that clients should usually set this value to `true` - see
283    /// [`verify_peer()`] for more.
284    ///
285    /// [`verify_peer()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.verify_peer
286    pub verify_peer: bool,
287
288    /// The maximum size of the receiver connection flow control window.
289    ///
290    /// Defaults to 24MB.
291    #[serde(default = "QuicSettings::default_max_connection_window")]
292    pub max_connection_window: u64,
293
294    /// The maximum size of the receiveer stream flow control window.
295    ///
296    /// Defaults to 16MB.
297    #[serde(default = "QuicSettings::default_max_stream_window")]
298    pub max_stream_window: u64,
299
300    /// Deprecated: this is now always enabled and this setting is
301    /// ignored.
302    ///
303    /// Previously controlled whether to use the `initial_max_data`
304    /// transport parameter as the initial connection and stream flow
305    /// control window.
306    pub use_initial_max_data_as_fc_window: bool,
307
308    /// If true, send an advisory STREAMS_BLOCKED frame when the
309    /// application's local stream creation attempts fail due to the
310    /// peer advertised MAX_STREAMS limit.
311    ///
312    /// Defaults to false.
313    pub enable_send_streams_blocked: bool,
314
315    /// Configures whether to send GREASE values.
316    ///
317    /// Defaults to true.
318    #[serde(default = "QuicSettings::default_grease")]
319    pub grease: bool,
320
321    /// Sets the anti-amplification limit factor.
322    ///
323    /// Defaults to 3.
324    #[serde(default = "QuicSettings::default_amplification_factor")]
325    pub max_amplification_factor: usize,
326
327    /// Sets the number of sent packets that can be tracked for loss detection
328    /// before the connection is closed. The limit should be larger than the
329    /// expected maximum congestion window, in packets. `None` or `0` disables
330    /// this limit, so the memory used to track sent packets is no longer
331    /// bounded.
332    ///
333    /// Defaults to 22,000. See
334    /// [`quiche::Config::set_max_outstanding_sent_packets()`] for more.
335    #[serde(
336        default = "QuicSettings::default_max_outstanding_sent_packets",
337        deserialize_with = "deserialize_optional_nonzero_usize"
338    )]
339    pub max_outstanding_sent_packets: Option<usize>,
340
341    /// Sets the send capacity factor.
342    ///
343    /// A factor greater than 1 allows the connection to buffer more outbound
344    /// data than can be sent at this moment. This can improve throughput by
345    /// reducing time spent waiting for new data.
346    ///
347    /// Defaults to 1.
348    #[serde(default = "QuicSettings::default_send_capacity_factor")]
349    pub send_capacity_factor: f64,
350
351    /// Sets the `ack_delay_exponent` transport parameter.
352    ///
353    /// Defaults to 3.
354    #[serde(default = "QuicSettings::default_ack_delay_exponent")]
355    pub ack_delay_exponent: u64,
356
357    /// Sets the `max_ack_delay` transport parameter.
358    ///
359    /// Defaults to 25.
360    #[serde(default = "QuicSettings::default_max_ack_delay")]
361    pub max_ack_delay: u64,
362
363    /// Configures the max number of queued received PATH_CHALLENGE frames.
364    ///
365    /// Defaults to 3.
366    #[serde(default = "QuicSettings::default_max_path_challenge_recv_queue_len")]
367    pub max_path_challenge_recv_queue_len: usize,
368
369    /// Sets the initial stateless reset token.
370    ///
371    /// Note that this applies only to server-side connections - on client-side
372    /// connections, this is a no-op.
373    ///
374    /// Defaults to `None`.
375    pub stateless_reset_token: Option<u128>,
376
377    /// Sets whether the QUIC connection should avoid reusing DCIDs over
378    /// different paths.
379    ///
380    /// Defaults to `false`. See [`set_disable_dcid_reuse()`] for more.
381    ///
382    /// [`set_disable_dcid_reuse()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.disable_dcid_reuse
383    pub disable_dcid_reuse: bool,
384
385    /// Specifies the number of bytes used to track unknown transport
386    /// parameters.
387    ///
388    /// Defaults to `None`, e.g., unknown transport parameters will not be
389    /// tracked. See [`enable_track_unknown_transport_parameters()`] for
390    /// more.
391    ///
392    /// [`enable_track_unknown_transport_parameters()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.enable_track_unknown_transport_parameters
393    pub track_unknown_transport_parameters: Option<usize>,
394
395    /// Configures whether the IO worker borrows its egress scratch buffer from
396    /// a per-worker-thread pool that is shared across connections, instead of
397    /// holding a persistent buffer for each connection's entire lifetime.
398    ///
399    /// Pooling stops idle connections from pinning a large egress buffer, which
400    /// reduces steady-state heap usage when many connections are idle. Setting
401    /// this to `false` restores the previous behavior of a persistent
402    /// per-connection buffer (owned by the IO worker), which is useful as a
403    /// runtime fallback.
404    ///
405    /// Defaults to `true`.
406    #[serde(default = "QuicSettings::default_pool_send_buffer")]
407    pub pool_send_buffer: bool,
408}
409
410impl QuicSettings {
411    #[inline]
412    fn default_alpn() -> Vec<Vec<u8>> {
413        quiche::h3::APPLICATION_PROTOCOL
414            .iter()
415            .map(|v| v.to_vec())
416            .collect()
417    }
418
419    #[inline]
420    fn default_enable_dgram() -> bool {
421        true
422    }
423
424    #[inline]
425    fn default_pool_send_buffer() -> bool {
426        true
427    }
428
429    #[inline]
430    fn default_dgram_max_queue_len() -> usize {
431        65536
432    }
433
434    #[inline]
435    fn default_initial_max_data() -> u64 {
436        10_000_000
437    }
438
439    #[inline]
440    fn default_initial_max_stream_data() -> u64 {
441        1_000_000
442    }
443
444    #[inline]
445    fn default_initial_max_streams() -> u64 {
446        100
447    }
448
449    #[inline]
450    fn default_max_idle_timeout() -> Option<Duration> {
451        Some(Duration::from_secs(56))
452    }
453
454    #[inline]
455    fn default_max_recv_udp_payload_size() -> usize {
456        1350
457    }
458
459    #[inline]
460    fn default_max_send_udp_payload_size() -> usize {
461        1350
462    }
463
464    #[inline]
465    fn default_disable_active_migration() -> bool {
466        true
467    }
468
469    #[inline]
470    fn default_cc_algorithm() -> String {
471        "cubic".to_string()
472    }
473
474    #[inline]
475    fn default_initial_congestion_window_packets() -> usize {
476        10
477    }
478
479    #[inline]
480    fn default_unchecked_congestion_window() -> usize {
481        usize::MAX
482    }
483
484    #[inline]
485    fn default_enable_hystart() -> bool {
486        true
487    }
488
489    #[inline]
490    fn default_listen_backlog() -> usize {
491        // With a worst-case one-minute timeout and 4096 concurrent handshakes,
492        // at least 70 connections are dequeued per second. A backlog of 1024
493        // therefore limits queueing latency to about 15 seconds.
494        1024
495    }
496
497    #[inline]
498    fn default_max_connection_window() -> u64 {
499        24 * 1024 * 1024
500    }
501
502    #[inline]
503    fn default_max_stream_window() -> u64 {
504        16 * 1024 * 1024
505    }
506
507    #[inline]
508    fn default_grease() -> bool {
509        true
510    }
511
512    #[inline]
513    fn default_amplification_factor() -> usize {
514        3
515    }
516
517    #[inline]
518    fn default_max_outstanding_sent_packets() -> Option<usize> {
519        Some(quiche::DEFAULT_MAX_OUTSTANDING_SENT_PACKETS.get())
520    }
521
522    #[inline]
523    fn default_send_capacity_factor() -> f64 {
524        1.0
525    }
526
527    #[inline]
528    fn default_ack_delay_exponent() -> u64 {
529        3
530    }
531
532    #[inline]
533    fn default_max_ack_delay() -> u64 {
534        25
535    }
536
537    #[inline]
538    fn default_active_connection_id_limit() -> u64 {
539        2
540    }
541
542    #[inline]
543    fn default_max_path_challenge_recv_queue_len() -> usize {
544        3
545    }
546
547    #[inline]
548    fn default_pmtud_max_probes() -> u8 {
549        3
550    }
551}
552
553#[cfg(test)]
554mod test {
555    use super::QuicSettings;
556    use std::time::Duration;
557
558    #[test]
559    fn timeouts_parse_as_milliseconds() {
560        let quic = serde_json::from_str::<QuicSettings>(
561            r#"{ "handshake_timeout_ms": 5000, "max_idle_timeout_ms": 7000 }"#,
562        )
563        .unwrap();
564
565        assert_eq!(quic.handshake_timeout.unwrap(), Duration::from_secs(5));
566        assert_eq!(quic.max_idle_timeout.unwrap(), Duration::from_secs(7));
567    }
568
569    #[test]
570    fn unchecked_congestion_window_defaults_and_round_trips() {
571        let defaults = QuicSettings::default();
572        assert_eq!(defaults.unchecked_congestion_window, usize::MAX);
573
574        for (input, expected_window) in [
575            (serde_json::json!({}), usize::MAX),
576            (serde_json::json!({ "unchecked_congestion_window": 0 }), 0),
577            (
578                serde_json::json!({ "unchecked_congestion_window": 4096 }),
579                4096,
580            ),
581            (
582                serde_json::json!({ "unchecked_congestion_window": usize::MAX }),
583                usize::MAX,
584            ),
585        ] {
586            let settings: QuicSettings =
587                serde_json::from_value(input).expect("deserialize QUIC settings");
588            assert_eq!(settings.unchecked_congestion_window, expected_window);
589
590            let serialized =
591                serde_json::to_value(&settings).expect("serialize QUIC settings");
592            assert_eq!(
593                serialized["unchecked_congestion_window"],
594                serde_json::json!(expected_window),
595            );
596            let restored: QuicSettings = serde_json::from_value(serialized)
597                .expect("round-trip QUIC settings");
598            assert_eq!(restored.unchecked_congestion_window, expected_window);
599            assert_eq!(restored.cc_algorithm, defaults.cc_algorithm);
600            assert_eq!(
601                restored.initial_congestion_window_packets,
602                defaults.initial_congestion_window_packets,
603            );
604        }
605    }
606
607    #[test]
608    fn max_outstanding_sent_packets() {
609        let quic = serde_json::from_str::<QuicSettings>("{}").unwrap();
610
611        assert_eq!(quic.max_outstanding_sent_packets, Some(22_000));
612
613        let quic = serde_json::from_str::<QuicSettings>(
614            r#"{ "max_outstanding_sent_packets": 1000 }"#,
615        )
616        .unwrap();
617
618        assert_eq!(quic.max_outstanding_sent_packets, Some(1000));
619
620        let quic = serde_json::from_str::<QuicSettings>(
621            r#"{ "max_outstanding_sent_packets": null }"#,
622        )
623        .unwrap();
624
625        assert_eq!(quic.max_outstanding_sent_packets, None);
626
627        let quic = serde_json::from_str::<QuicSettings>(
628            r#"{ "max_outstanding_sent_packets": 0 }"#,
629        )
630        .unwrap();
631
632        assert_eq!(quic.max_outstanding_sent_packets, None);
633    }
634}