Skip to main content

tokio_quiche/settings/
config.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::telemetry::log;
28use std::borrow::Cow;
29use std::fs::File;
30use std::sync::Arc;
31use std::time::Duration;
32
33use qlog::writer::QlogCompression;
34
35use crate::quic::ConnectionHook;
36use crate::result::QuicResult;
37use crate::settings::CertificateKind;
38use crate::settings::ConnectionParams;
39use crate::settings::TlsCertificatePaths;
40use crate::socket::SocketCapabilities;
41
42/// Whether `--cfg capture_keylogs` was set at build time. We keep supporting
43/// the `capture_keylogs` feature for backward compatibility.
44const KEYLOGFILE_ENABLED: bool =
45    cfg!(capture_keylogs) || cfg!(feature = "capture_keylogs");
46
47/// Internal representation of the combined configuration for a QUIC connection.
48pub(crate) struct Config {
49    pub quiche_config: quiche::Config,
50    pub disable_client_ip_validation: bool,
51    pub qlog_dir: Option<String>,
52    pub qlog_compression: QlogCompression,
53    pub has_gso: bool,
54    pub pacing_offload: bool,
55    pub enable_expensive_packet_count_metrics: bool,
56    pub keylog_file: Option<File>,
57    pub listen_backlog: usize,
58    pub handshake_timeout: Option<Duration>,
59    pub has_ippktinfo: bool,
60    pub has_ipv6pktinfo: bool,
61    pub pool_send_buffer: bool,
62    pub connection_hook: Option<Arc<dyn ConnectionHook + Send + Sync + 'static>>,
63}
64
65impl AsMut<quiche::Config> for Config {
66    fn as_mut(&mut self) -> &mut quiche::Config {
67        &mut self.quiche_config
68    }
69}
70
71impl Config {
72    pub(crate) fn new(
73        params: &ConnectionParams, socket_capabilities: SocketCapabilities,
74    ) -> QuicResult<Self> {
75        let quic_settings = &params.settings;
76        let keylog_path = match &quic_settings.keylog_file {
77            Some(f) => Some(Cow::Borrowed(f.as_ref())),
78            None => std::env::var_os("SSLKEYLOGFILE").map(Cow::from),
79        };
80        let keylog_file = keylog_path.and_then(|path| if KEYLOGFILE_ENABLED {
81                File::options().create(true).append(true).open(path)
82                    .inspect_err(|e| {
83                        log::warn!("failed to open SSLKEYLOGFILE"; "error" => e);
84                    })
85                    .ok()
86            } else {
87                log::warn!("SSLKEYLOGFILE is set, but `--cfg capture_keylogs` was not enabled. No keys will be logged.");
88                None
89            });
90
91        let SocketCapabilities {
92            has_gso,
93            has_txtime: pacing_offload,
94            has_ippktinfo,
95            has_ipv6pktinfo,
96            ..
97        } = socket_capabilities;
98
99        #[cfg(feature = "gcongestion")]
100        let pacing_offload = quic_settings.enable_pacing && pacing_offload;
101
102        Ok(Config {
103            quiche_config: make_quiche_config(params, keylog_file.is_some())?,
104            disable_client_ip_validation: quic_settings
105                .disable_client_ip_validation,
106            qlog_dir: quic_settings.qlog_dir.clone(),
107            qlog_compression: quic_settings.qlog_compression,
108            has_gso,
109            pacing_offload,
110            enable_expensive_packet_count_metrics: quic_settings
111                .enable_expensive_packet_count_metrics,
112            keylog_file,
113            listen_backlog: quic_settings.listen_backlog,
114            handshake_timeout: quic_settings.handshake_timeout,
115            has_ippktinfo,
116            has_ipv6pktinfo,
117            pool_send_buffer: quic_settings.pool_send_buffer,
118            connection_hook: params.hooks.connection_hook.clone(),
119        })
120    }
121}
122
123fn make_quiche_config(
124    params: &ConnectionParams, should_log_keys: bool,
125) -> QuicResult<quiche::Config> {
126    let ssl_ctx_builder = params
127        .hooks
128        .connection_hook
129        .as_ref()
130        .zip(params.tls_cert)
131        .and_then(|(hook, tls)| hook.create_custom_ssl_context_builder(tls));
132
133    let mut config = if let Some(builder) = ssl_ctx_builder {
134        quiche::Config::with_boring_ssl_ctx_builder(
135            quiche::PROTOCOL_VERSION,
136            builder,
137        )?
138    } else {
139        quiche_config_with_tls(params.tls_cert)?
140    };
141
142    let quic_settings = &params.settings;
143
144    let alpns: Vec<&[u8]> =
145        quic_settings.alpn.iter().map(Vec::as_slice).collect();
146    config.set_application_protos(&alpns).unwrap();
147
148    if let Some(timeout) = quic_settings.max_idle_timeout {
149        let ms = timeout
150            .as_millis()
151            .try_into()
152            .map_err(|_| "QuicSettings::max_idle_timeout exceeds u64")?;
153        config.set_max_idle_timeout(ms);
154    }
155
156    config.enable_dgram(
157        quic_settings.enable_dgram,
158        quic_settings.dgram_recv_max_queue_len,
159        quic_settings.dgram_send_max_queue_len,
160    );
161
162    config.set_max_recv_udp_payload_size(quic_settings.max_recv_udp_payload_size);
163    config.set_max_send_udp_payload_size(quic_settings.max_send_udp_payload_size);
164    config.set_initial_max_data(quic_settings.initial_max_data);
165    config.set_initial_max_stream_data_bidi_local(
166        quic_settings.initial_max_stream_data_bidi_local,
167    );
168    config.set_initial_max_stream_data_bidi_remote(
169        quic_settings.initial_max_stream_data_bidi_remote,
170    );
171    config.set_initial_max_stream_data_uni(
172        quic_settings.initial_max_stream_data_uni,
173    );
174    config.set_initial_max_streams_bidi(quic_settings.initial_max_streams_bidi);
175    config.set_initial_max_streams_uni(quic_settings.initial_max_streams_uni);
176    config.set_disable_active_migration(quic_settings.disable_active_migration);
177    config
178        .set_active_connection_id_limit(quic_settings.active_connection_id_limit);
179    config.set_cc_algorithm_name(quic_settings.cc_algorithm.as_str())?;
180    config.set_initial_congestion_window_packets(
181        quic_settings.initial_congestion_window_packets,
182    );
183    config.set_enable_relaxed_loss_threshold(
184        quic_settings.enable_relaxed_loss_threshold,
185    );
186    config.discover_pmtu(quic_settings.discover_path_mtu);
187    config.set_pmtud_max_probes(quic_settings.pmtud_max_probes);
188    config.enable_hystart(quic_settings.enable_hystart);
189
190    config.enable_pacing(quic_settings.enable_pacing);
191    if let Some(max_pacing_rate) = quic_settings.max_pacing_rate {
192        config.set_max_pacing_rate(max_pacing_rate);
193    }
194
195    if quic_settings.verify_peer {
196        config.verify_peer(quic_settings.verify_peer);
197    }
198
199    config.set_max_connection_window(quic_settings.max_connection_window);
200    config.set_max_stream_window(quic_settings.max_stream_window);
201    config.set_enable_send_streams_blocked(
202        quic_settings.enable_send_streams_blocked,
203    );
204    config.grease(quic_settings.grease);
205    config.set_max_amplification_factor(quic_settings.max_amplification_factor);
206    config.set_send_capacity_factor(quic_settings.send_capacity_factor);
207    config.set_ack_delay_exponent(quic_settings.ack_delay_exponent);
208    config.set_max_ack_delay(quic_settings.max_ack_delay);
209    config.set_path_challenge_recv_max_queue_len(
210        quic_settings.max_path_challenge_recv_queue_len,
211    );
212    config.set_stateless_reset_token(quic_settings.stateless_reset_token);
213    config.set_disable_dcid_reuse(quic_settings.disable_dcid_reuse);
214
215    if let Some(track_unknown_transport_params) =
216        quic_settings.track_unknown_transport_parameters
217    {
218        config.enable_track_unknown_transport_parameters(
219            track_unknown_transport_params,
220        );
221    }
222    if params.settings.enable_early_data {
223        config.enable_early_data();
224    }
225
226    if should_log_keys {
227        config.log_keys();
228    }
229
230    Ok(config)
231}
232
233fn quiche_config_with_tls(
234    tls_cert: Option<TlsCertificatePaths>,
235) -> QuicResult<quiche::Config> {
236    let Some(tls) = tls_cert else {
237        return Ok(quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap());
238    };
239
240    match tls.kind {
241        #[cfg(not(feature = "rpk"))]
242        CertificateKind::RawPublicKey => {
243            // TODO: Gate this variant on the `rpk` feature.
244            panic!("Can't use RPK when compiled without rpk feature");
245        },
246        #[cfg(all(feature = "rpk", not(boring_v5)))]
247        CertificateKind::RawPublicKey => {
248            // boring 4.x (the default) exposes a dedicated
249            // `SslContextBuilder::new_rpk()` constructor plus
250            // `set_rpk_certificate` / `set_null_chain_private_key`.
251            let mut ssl_ctx_builder = boring::ssl::SslContextBuilder::new_rpk()?;
252            let raw_public_key = read_file(tls.cert)?;
253            ssl_ctx_builder.set_rpk_certificate(&raw_public_key)?;
254
255            let raw_private_key = read_file(tls.private_key)?;
256            let pkey =
257                boring::pkey::PKey::private_key_from_pem(&raw_private_key)?;
258            ssl_ctx_builder.set_null_chain_private_key(&pkey)?;
259
260            Ok(quiche::Config::with_boring_ssl_ctx_builder(
261                quiche::PROTOCOL_VERSION,
262                ssl_ctx_builder,
263            )?)
264        },
265        #[cfg(all(feature = "rpk", boring_v5))]
266        CertificateKind::RawPublicKey => {
267            // boring 5.x replaced the dedicated `SslContextBuilder::new_rpk()`
268            // entry point with a credential-based API: build an
269            // `SslCredential` configured for raw public keys and add it
270            // to a regular `SslContextBuilder` via `add_credential`.
271            let raw_public_key = read_file(tls.cert)?;
272            let raw_private_key = read_file(tls.private_key)?;
273            let pkey =
274                boring::pkey::PKey::private_key_from_pem(&raw_private_key)?;
275
276            let mut credential_builder =
277                boring::ssl::SslCredential::new_raw_public_key()?;
278            credential_builder.set_spki_bytes(Some(&raw_public_key))?;
279            credential_builder.set_private_key(&pkey)?;
280            let credential = credential_builder.build();
281
282            let mut ssl_ctx_builder = boring::ssl::SslContextBuilder::new(
283                boring::ssl::SslMethod::tls(),
284            )?;
285            ssl_ctx_builder.add_credential(&credential)?;
286
287            Ok(quiche::Config::with_boring_ssl_ctx_builder(
288                quiche::PROTOCOL_VERSION,
289                ssl_ctx_builder,
290            )?)
291        },
292        CertificateKind::X509 => {
293            let mut config =
294                quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();
295            config.load_cert_chain_from_pem_file(tls.cert)?;
296            config.load_priv_key_from_pem_file(tls.private_key)?;
297            Ok(config)
298        },
299    }
300}
301
302#[cfg(feature = "rpk")]
303fn read_file(path: &str) -> QuicResult<Vec<u8>> {
304    use anyhow::Context as _;
305    std::fs::read(path)
306        .with_context(|| format!("read {path}"))
307        .map_err(Into::into)
308}