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_with::serde_as;
29use serde_with::DurationMilliSeconds;
30use std::time::Duration;
31
32pub use qlog::writer::QlogCompression;
33
34/// QUIC configuration parameters.
35#[serde_as]
36#[settings]
37#[non_exhaustive]
38pub struct QuicSettings {
39    /// Configures the list of supported application protocols.
40    ///
41    /// Defaults to `[b"h3"]`.
42    #[serde(skip, default = "QuicSettings::default_alpn")]
43    pub alpn: Vec<Vec<u8>>,
44
45    /// Configures whether to enable DATAGRAM frame support. H3 connections
46    /// copy this setting from the underlying QUIC connection.
47    ///
48    /// Defaults to `true`.
49    #[serde(default = "QuicSettings::default_enable_dgram")]
50    pub enable_dgram: bool,
51
52    /// Max queue length for received DATAGRAM frames.
53    ///
54    /// Defaults to `2^16`.
55    #[serde(default = "QuicSettings::default_dgram_max_queue_len")]
56    pub dgram_recv_max_queue_len: usize,
57
58    /// Max queue length for sending DATAGRAM frames.
59    ///
60    /// Defaults to `2^16`.
61    #[serde(default = "QuicSettings::default_dgram_max_queue_len")]
62    pub dgram_send_max_queue_len: usize,
63
64    /// Configures whether to enable early data (0-RTT) support. Currently only
65    /// supported for servers.
66    ///
67    /// Defaults to `false`.
68    pub enable_early_data: bool,
69
70    /// Sets the `initial_max_data` transport parameter.
71    ///
72    /// Defaults to 10 MB.
73    #[serde(default = "QuicSettings::default_initial_max_data")]
74    pub initial_max_data: u64,
75
76    /// Sets the `initial_max_stream_data_bidi_local` transport parameter.
77    ///
78    /// Defaults to 1 MB.
79    #[serde(default = "QuicSettings::default_initial_max_stream_data")]
80    pub initial_max_stream_data_bidi_local: u64,
81
82    /// Sets the `initial_max_stream_data_bidi_remote` transport parameter.
83    ///
84    /// Defaults to 1 MB.
85    #[serde(default = "QuicSettings::default_initial_max_stream_data")]
86    pub initial_max_stream_data_bidi_remote: u64,
87
88    /// Sets the `initial_max_stream_data_uni` transport parameter.
89    ///
90    /// Defaults to 1 MB.
91    #[serde(default = "QuicSettings::default_initial_max_stream_data")]
92    pub initial_max_stream_data_uni: u64,
93
94    /// Sets the `initial_max_streams_bidi` transport parameter.
95    ///
96    /// Defaults to `100`.
97    #[serde(default = "QuicSettings::default_initial_max_streams")]
98    pub initial_max_streams_bidi: u64,
99
100    /// Sets the `initial_max_streams_uni` transport parameter.
101    ///
102    /// Defaults to `100`.
103    #[serde(default = "QuicSettings::default_initial_max_streams")]
104    pub initial_max_streams_uni: u64,
105
106    /// Configures the max idle timeout of the connection in milliseconds. The
107    /// real idle timeout is the minimum of this and the peer's
108    /// `max_idle_timeout`.
109    ///
110    /// Defaults to 56 seconds.
111    #[serde(
112        rename = "max_idle_timeout_ms",
113        default = "QuicSettings::default_max_idle_timeout"
114    )]
115    #[serde_as(as = "Option<DurationMilliSeconds>")]
116    pub max_idle_timeout: Option<Duration>,
117
118    /// Configures whether the local endpoint supports active connection
119    /// migration.
120    ///
121    /// Defaults to `true` (meaning disabled).
122    #[serde(default = "QuicSettings::default_disable_active_migration")]
123    pub disable_active_migration: bool,
124
125    /// Sets the `active_connection_id_limit` transport parameter.
126    ///
127    /// Defaults to 2. Note that values less than 2 will be ignored.
128    #[serde(default = "QuicSettings::default_active_connection_id_limit")]
129    pub active_connection_id_limit: u64,
130
131    /// Sets the maximum incoming UDP payload size.
132    ///
133    /// Defaults to 1350 bytes.
134    #[serde(default = "QuicSettings::default_max_recv_udp_payload_size")]
135    pub max_recv_udp_payload_size: usize,
136
137    /// Sets the maximum outgoing UDP payload size.
138    ///
139    /// Defaults to 1350 bytes.
140    #[serde(default = "QuicSettings::default_max_send_udp_payload_size")]
141    pub max_send_udp_payload_size: usize,
142
143    /// Whether to validate client IPs in QUIC initials.
144    ///
145    /// If set to `true`, any received QUIC initial will immediately spawn a
146    /// connection and start crypto operations for the handshake. Otherwise,
147    /// the client is asked to execute a stateless retry first (the default).
148    pub disable_client_ip_validation: bool,
149
150    /// Path to a file in which TLS secrets will be logged in
151    /// [SSLKEYLOGFILE format](https://tlswg.org/sslkeylogfile/draft-ietf-tls-keylogfile.html).
152    pub keylog_file: Option<String>,
153
154    /// Path to a directory where QLOG files will be saved.
155    pub qlog_dir: Option<String>,
156
157    /// Compression applied to QLOG output files.
158    ///
159    /// Defaults to [`QlogCompression::None`], preserving the historical
160    /// behavior of emitting raw `.sqlog` files. The `Gzip` and `Zstd`
161    /// variants require the `qlog-gzip` and `qlog-zstd` Cargo features
162    /// (both enabled by default); builds that disable those features
163    /// cannot reference the corresponding variant.
164    #[serde(default)]
165    pub qlog_compression: QlogCompression,
166
167    /// Congestion control algorithm to use.
168    ///
169    /// For available values, see
170    /// [`CongestionControlAlgorithm`](quiche::CongestionControlAlgorithm).
171    ///
172    /// Defaults to `cubic`.
173    #[serde(default = "QuicSettings::default_cc_algorithm")]
174    pub cc_algorithm: String,
175
176    /// The default initial congestion window size in terms of packet count.
177    ///
178    /// Defaults to 10.
179    #[serde(default = "QuicSettings::default_initial_congestion_window_packets")]
180    pub initial_congestion_window_packets: usize,
181
182    /// Configures whether to enable relaxed loss detection on spurious loss.
183    ///
184    /// Defaults to `false`.
185    pub enable_relaxed_loss_threshold: bool,
186
187    /// Configures whether to do path MTU discovery.
188    ///
189    /// Defaults to `false`.
190    pub discover_path_mtu: bool,
191
192    /// Configures the maximum number of PMTUD probe attempts before treating
193    /// a size as failed.
194    ///
195    /// Defaults to 3 per [RFC 8899 Section 5.1.2](https://datatracker.ietf.org/doc/html/rfc8899#section-5.1.2).
196    /// If 0 is passed, the default value is used.
197    #[serde(default = "QuicSettings::default_pmtud_max_probes")]
198    pub pmtud_max_probes: u8,
199
200    /// Whether to use HyStart++ (only with `cubic` and `reno` CC).
201    ///
202    /// Defaults to `true`.
203    #[serde(default = "QuicSettings::default_enable_hystart")]
204    pub enable_hystart: bool,
205
206    /// Optionally enables pacing for outgoing packets.
207    ///
208    /// Note: this also requires pacing-compatible
209    /// [`SocketCapabilities`](crate::socket::SocketCapabilities).
210    pub enable_pacing: bool,
211
212    /// Sets the max value for pacing rate.
213    ///
214    /// By default, there is no limit.
215    pub max_pacing_rate: Option<u64>,
216
217    /// Optionally enables expensive versions of the
218    /// `accepted_initial_quic_packet_count`
219    /// and `rejected_initial_quic_packet_count` metrics.
220    ///
221    /// The expensive versions add a label for the peer IP subnet (`/24` for
222    /// IPv4, `/32` for IPv6). They thus generate many more time series if
223    /// peers are arbitrary eyeballs from the global Internet.
224    pub enable_expensive_packet_count_metrics: bool,
225
226    /// Forwards [`quiche`] logs into the logging system currently used by
227    /// [`foundations`].
228    ///
229    /// Defaults to `false`.
230    ///
231    /// # Warning
232    /// This should **only be used for local debugging**. `quiche` can emit lots
233    /// (and lots, and lots) of logs (the TRACE level emits a log record for
234    /// every packet and frame) and you can very easily overwhelm your
235    /// logging pipeline.
236    pub capture_quiche_logs: bool,
237
238    /// A timeout for the QUIC handshake, in milliseconds.
239    ///
240    /// Disabled by default.
241    #[serde(rename = "handshake_timeout_ms")]
242    #[serde_as(as = "Option<DurationMilliSeconds>")]
243    pub handshake_timeout: Option<Duration>,
244
245    /// The maximum number of newly-created connections that will be queued for
246    /// the application to receive. Not applicable to client-side usage.
247    ///
248    /// Defaults to 1024 connections.
249    #[serde(default = "QuicSettings::default_listen_backlog")]
250    pub listen_backlog: usize,
251
252    /// Whether or not to verify the peer's certificate.
253    ///
254    /// Defaults to `false`, meaning no peer verification is performed. Note
255    /// that clients should usually set this value to `true` - see
256    /// [`verify_peer()`] for more.
257    ///
258    /// [`verify_peer()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.verify_peer
259    pub verify_peer: bool,
260
261    /// The maximum size of the receiver connection flow control window.
262    ///
263    /// Defaults to 24MB.
264    #[serde(default = "QuicSettings::default_max_connection_window")]
265    pub max_connection_window: u64,
266
267    /// The maximum size of the receiveer stream flow control window.
268    ///
269    /// Defaults to 16MB.
270    #[serde(default = "QuicSettings::default_max_stream_window")]
271    pub max_stream_window: u64,
272
273    /// Deprecated: this is now always enabled and this setting is
274    /// ignored.
275    ///
276    /// Previously controlled whether to use the `initial_max_data`
277    /// transport parameter as the initial connection and stream flow
278    /// control window.
279    pub use_initial_max_data_as_fc_window: bool,
280
281    /// If true, send an advisory STREAMS_BLOCKED frame when the
282    /// application's local stream creation attempts fail due to the
283    /// peer advertised MAX_STREAMS limit.
284    ///
285    /// Defaults to false.
286    pub enable_send_streams_blocked: bool,
287
288    /// Configures whether to send GREASE values.
289    ///
290    /// Defaults to true.
291    #[serde(default = "QuicSettings::default_grease")]
292    pub grease: bool,
293
294    /// Sets the anti-amplification limit factor.
295    ///
296    /// Defaults to 3.
297    #[serde(default = "QuicSettings::default_amplification_factor")]
298    pub max_amplification_factor: usize,
299
300    /// Sets the send capacity factor.
301    ///
302    /// A factor greater than 1 allows the connection to buffer more outbound
303    /// data than can be sent at this moment. This can improve throughput by
304    /// reducing time spent waiting for new data.
305    ///
306    /// Defaults to 1.
307    #[serde(default = "QuicSettings::default_send_capacity_factor")]
308    pub send_capacity_factor: f64,
309
310    /// Sets the `ack_delay_exponent` transport parameter.
311    ///
312    /// Defaults to 3.
313    #[serde(default = "QuicSettings::default_ack_delay_exponent")]
314    pub ack_delay_exponent: u64,
315
316    /// Sets the `max_ack_delay` transport parameter.
317    ///
318    /// Defaults to 25.
319    #[serde(default = "QuicSettings::default_max_ack_delay")]
320    pub max_ack_delay: u64,
321
322    /// Configures the max number of queued received PATH_CHALLENGE frames.
323    ///
324    /// Defaults to 3.
325    #[serde(default = "QuicSettings::default_max_path_challenge_recv_queue_len")]
326    pub max_path_challenge_recv_queue_len: usize,
327
328    /// Sets the initial stateless reset token.
329    ///
330    /// Note that this applies only to server-side connections - on client-side
331    /// connections, this is a no-op.
332    ///
333    /// Defaults to `None`.
334    pub stateless_reset_token: Option<u128>,
335
336    /// Sets whether the QUIC connection should avoid reusing DCIDs over
337    /// different paths.
338    ///
339    /// Defaults to `false`. See [`set_disable_dcid_reuse()`] for more.
340    ///
341    /// [`set_disable_dcid_reuse()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.disable_dcid_reuse
342    pub disable_dcid_reuse: bool,
343
344    /// Specifies the number of bytes used to track unknown transport
345    /// parameters.
346    ///
347    /// Defaults to `None`, e.g., unknown transport parameters will not be
348    /// tracked. See [`enable_track_unknown_transport_parameters()`] for
349    /// more.
350    ///
351    /// [`enable_track_unknown_transport_parameters()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.enable_track_unknown_transport_parameters
352    pub track_unknown_transport_parameters: Option<usize>,
353
354    /// Configures whether the IO worker borrows its egress scratch buffer from
355    /// a per-worker-thread pool that is shared across connections, instead of
356    /// holding a persistent buffer for each connection's entire lifetime.
357    ///
358    /// Pooling stops idle connections from pinning a large egress buffer, which
359    /// reduces steady-state heap usage when many connections are idle. Setting
360    /// this to `false` restores the previous behavior of a persistent
361    /// per-connection buffer (owned by the IO worker), which is useful as a
362    /// runtime fallback.
363    ///
364    /// Defaults to `true`.
365    #[serde(default = "QuicSettings::default_pool_send_buffer")]
366    pub pool_send_buffer: bool,
367}
368
369impl QuicSettings {
370    #[inline]
371    fn default_alpn() -> Vec<Vec<u8>> {
372        quiche::h3::APPLICATION_PROTOCOL
373            .iter()
374            .map(|v| v.to_vec())
375            .collect()
376    }
377
378    #[inline]
379    fn default_enable_dgram() -> bool {
380        true
381    }
382
383    #[inline]
384    fn default_pool_send_buffer() -> bool {
385        true
386    }
387
388    #[inline]
389    fn default_dgram_max_queue_len() -> usize {
390        65536
391    }
392
393    #[inline]
394    fn default_initial_max_data() -> u64 {
395        10_000_000
396    }
397
398    #[inline]
399    fn default_initial_max_stream_data() -> u64 {
400        1_000_000
401    }
402
403    #[inline]
404    fn default_initial_max_streams() -> u64 {
405        100
406    }
407
408    #[inline]
409    fn default_max_idle_timeout() -> Option<Duration> {
410        Some(Duration::from_secs(56))
411    }
412
413    #[inline]
414    fn default_max_recv_udp_payload_size() -> usize {
415        1350
416    }
417
418    #[inline]
419    fn default_max_send_udp_payload_size() -> usize {
420        1350
421    }
422
423    #[inline]
424    fn default_disable_active_migration() -> bool {
425        true
426    }
427
428    #[inline]
429    fn default_cc_algorithm() -> String {
430        "cubic".to_string()
431    }
432
433    #[inline]
434    fn default_initial_congestion_window_packets() -> usize {
435        10
436    }
437
438    #[inline]
439    fn default_enable_hystart() -> bool {
440        true
441    }
442
443    #[inline]
444    fn default_listen_backlog() -> usize {
445        // Given a worst-case 1 minute handshake timeout and up to 4096 concurrent
446        // handshakes, we will dequeue at least 70 connections per second.
447        // This means this backlog size limits the queueing latency to
448        // ~15s.
449        1024
450    }
451
452    #[inline]
453    fn default_max_connection_window() -> u64 {
454        24 * 1024 * 1024
455    }
456
457    #[inline]
458    fn default_max_stream_window() -> u64 {
459        16 * 1024 * 1024
460    }
461
462    #[inline]
463    fn default_grease() -> bool {
464        true
465    }
466
467    #[inline]
468    fn default_amplification_factor() -> usize {
469        3
470    }
471
472    #[inline]
473    fn default_send_capacity_factor() -> f64 {
474        1.0
475    }
476
477    #[inline]
478    fn default_ack_delay_exponent() -> u64 {
479        3
480    }
481
482    #[inline]
483    fn default_max_ack_delay() -> u64 {
484        25
485    }
486
487    #[inline]
488    fn default_active_connection_id_limit() -> u64 {
489        2
490    }
491
492    #[inline]
493    fn default_max_path_challenge_recv_queue_len() -> usize {
494        3
495    }
496
497    #[inline]
498    fn default_pmtud_max_probes() -> u8 {
499        3
500    }
501}
502
503#[cfg(test)]
504mod test {
505    use super::QuicSettings;
506    use std::time::Duration;
507
508    #[test]
509    fn timeouts_parse_as_milliseconds() {
510        let quic = serde_json::from_str::<QuicSettings>(
511            r#"{ "handshake_timeout_ms": 5000, "max_idle_timeout_ms": 7000 }"#,
512        )
513        .unwrap();
514
515        assert_eq!(quic.handshake_timeout.unwrap(), Duration::from_secs(5));
516        assert_eq!(quic.max_idle_timeout.unwrap(), Duration::from_secs(7));
517    }
518}