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::time::Duration;
31
32use crate::result::QuicResult;
33use crate::settings::CertificateKind;
34use crate::settings::ConnectionParams;
35use crate::settings::TlsCertificatePaths;
36use crate::socket::SocketCapabilities;
37
38/// Whether `--cfg capture_keylogs` was set at build time. We keep supporting
39/// the `capture_keylogs` feature for backward compatibility.
40const KEYLOGFILE_ENABLED: bool =
41    cfg!(capture_keylogs) || cfg!(feature = "capture_keylogs");
42
43/// Internal representation of the combined configuration for a QUIC connection.
44pub(crate) struct Config {
45    pub quiche_config: quiche::Config,
46    pub disable_client_ip_validation: bool,
47    pub qlog_dir: Option<String>,
48    pub has_gso: bool,
49    pub pacing_offload: bool,
50    pub enable_expensive_packet_count_metrics: bool,
51    pub keylog_file: Option<File>,
52    pub listen_backlog: usize,
53    pub handshake_timeout: Option<Duration>,
54    pub has_ippktinfo: bool,
55    pub has_ipv6pktinfo: bool,
56}
57
58impl AsMut<quiche::Config> for Config {
59    fn as_mut(&mut self) -> &mut quiche::Config {
60        &mut self.quiche_config
61    }
62}
63
64impl Config {
65    pub(crate) fn new(
66        params: &ConnectionParams, socket_capabilities: SocketCapabilities,
67    ) -> QuicResult<Self> {
68        let quic_settings = &params.settings;
69        let keylog_path = match &quic_settings.keylog_file {
70            Some(f) => Some(Cow::Borrowed(f.as_ref())),
71            None => std::env::var_os("SSLKEYLOGFILE").map(Cow::from),
72        };
73        let keylog_file = keylog_path.and_then(|path| if KEYLOGFILE_ENABLED {
74                File::options().create(true).append(true).open(path)
75                    .inspect_err(|e| log::warn!("failed to open SSLKEYLOGFILE"; "error" => e))
76                    .ok()
77            } else {
78                log::warn!("SSLKEYLOGFILE is set, but `--cfg capture_keylogs` was not enabled. No keys will be logged.");
79                None
80            });
81
82        let SocketCapabilities {
83            has_gso,
84            has_txtime: pacing_offload,
85            has_ippktinfo,
86            has_ipv6pktinfo,
87            ..
88        } = socket_capabilities;
89
90        Ok(Config {
91            quiche_config: make_quiche_config(params, keylog_file.is_some())?,
92            disable_client_ip_validation: quic_settings
93                .disable_client_ip_validation,
94            qlog_dir: quic_settings.qlog_dir.clone(),
95            has_gso,
96            // Only enable pacing if it is explicitly enabled in the configuration
97            // and offload is supported.
98            pacing_offload: quic_settings.enable_pacing && pacing_offload,
99            enable_expensive_packet_count_metrics: quic_settings
100                .enable_expensive_packet_count_metrics,
101            keylog_file,
102            listen_backlog: quic_settings.listen_backlog,
103            handshake_timeout: quic_settings.handshake_timeout,
104            has_ippktinfo,
105            has_ipv6pktinfo,
106        })
107    }
108}
109
110fn make_quiche_config(
111    params: &ConnectionParams, should_log_keys: bool,
112) -> QuicResult<quiche::Config> {
113    let ssl_ctx_builder = params
114        .hooks
115        .connection_hook
116        .as_ref()
117        .zip(params.tls_cert)
118        .and_then(|(hook, tls)| hook.create_custom_ssl_context_builder(tls));
119
120    let mut config = if let Some(builder) = ssl_ctx_builder {
121        quiche::Config::with_boring_ssl_ctx_builder(
122            quiche::PROTOCOL_VERSION,
123            builder,
124        )?
125    } else {
126        quiche_config_with_tls(params.tls_cert)?
127    };
128
129    let quic_settings = &params.settings;
130    let alpns: Vec<&[u8]> =
131        quic_settings.alpn.iter().map(Vec::as_slice).collect();
132    config.set_application_protos(&alpns).unwrap();
133
134    if let Some(timeout) = quic_settings.max_idle_timeout {
135        let ms = timeout
136            .as_millis()
137            .try_into()
138            .map_err(|_| "QuicSettings::max_idle_timeout exceeds u64")?;
139        config.set_max_idle_timeout(ms);
140    }
141
142    config.enable_dgram(
143        quic_settings.enable_dgram,
144        quic_settings.dgram_recv_max_queue_len,
145        quic_settings.dgram_send_max_queue_len,
146    );
147
148    config.set_max_recv_udp_payload_size(quic_settings.max_recv_udp_payload_size);
149    config.set_max_send_udp_payload_size(quic_settings.max_send_udp_payload_size);
150    config.set_initial_max_data(quic_settings.initial_max_data);
151    config.set_initial_max_stream_data_bidi_local(
152        quic_settings.initial_max_stream_data_bidi_local,
153    );
154    config.set_initial_max_stream_data_bidi_remote(
155        quic_settings.initial_max_stream_data_bidi_remote,
156    );
157    config.set_initial_max_stream_data_uni(
158        quic_settings.initial_max_stream_data_uni,
159    );
160    config.set_initial_max_streams_bidi(quic_settings.initial_max_streams_bidi);
161    config.set_initial_max_streams_uni(quic_settings.initial_max_streams_uni);
162    config.set_disable_active_migration(quic_settings.disable_active_migration);
163    config.set_cc_algorithm_name(quic_settings.cc_algorithm.as_str())?;
164    config.enable_hystart(quic_settings.enable_hystart);
165    config.enable_pacing(quic_settings.enable_pacing);
166
167    if should_log_keys {
168        config.log_keys();
169    }
170
171    Ok(config)
172}
173
174fn quiche_config_with_tls(
175    tls_cert: Option<TlsCertificatePaths>,
176) -> QuicResult<quiche::Config> {
177    let Some(tls) = tls_cert else {
178        return Ok(quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap());
179    };
180
181    match tls.kind {
182        #[cfg(not(feature = "rpk"))]
183        CertificateKind::RawPublicKey => {
184            // TODO: don't compile this enum variant unless rpk feature is enabled
185            panic!("Can't use RPK when compiled without rpk feature");
186        },
187        #[cfg(feature = "rpk")]
188        CertificateKind::RawPublicKey => {
189            let mut ssl_ctx_builder = boring::ssl::SslContextBuilder::new_rpk()?;
190            let raw_public_key = std::fs::read(tls.cert)?;
191            ssl_ctx_builder.set_rpk_certificate(&raw_public_key)?;
192
193            let raw_private_key = std::fs::read(tls.private_key)?;
194            let pkey =
195                boring::pkey::PKey::private_key_from_pem(&raw_private_key)?;
196            ssl_ctx_builder.set_null_chain_private_key(&pkey)?;
197
198            Ok(quiche::Config::with_boring_ssl_ctx_builder(
199                quiche::PROTOCOL_VERSION,
200                ssl_ctx_builder,
201            )?)
202        },
203        CertificateKind::X509 => {
204            let mut config =
205                quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();
206            config.load_cert_chain_from_pem_file(tls.cert)?;
207            config.load_priv_key_from_pem_file(tls.private_key)?;
208            Ok(config)
209        },
210    }
211}