tokio_quiche/lib.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
27#![allow(clippy::collapsible_match)]
28
29//! Bridging the gap between [quiche] and [tokio].
30//!
31//! tokio-quiche connects [quiche::Connection]s and [quiche::h3::Connection]s to
32//! tokio's event loop. Users have the choice between implementing their own,
33//! custom [`ApplicationOverQuic`] or using the ready-made
34//! [H3Driver](crate::http3::driver::H3Driver) for HTTP/3 clients and servers.
35//!
36//! # Starting an HTTP/3 Server
37//!
38//! A server [`listen`]s on a UDP socket for QUIC connections and spawns a new
39//! tokio task to handle each individual connection.
40//!
41//! ```
42//! use futures::stream::StreamExt;
43//! use tokio_quiche::http3::settings::Http3Settings;
44//! use tokio_quiche::listen;
45//! use tokio_quiche::metrics::DefaultMetrics;
46//! use tokio_quiche::quic::SimpleConnectionIdGenerator;
47//! use tokio_quiche::ConnectionParams;
48//! use tokio_quiche::ServerH3Driver;
49//!
50//! # async fn example() -> tokio_quiche::QuicResult<()> {
51//! let socket = tokio::net::UdpSocket::bind("0.0.0.0:443").await?;
52//! let mut listeners =
53//! listen([socket], ConnectionParams::default(), DefaultMetrics)?;
54//! let mut accept_stream = &mut listeners[0];
55//!
56//! while let Some(conn) = accept_stream.next().await {
57//! let (driver, mut controller) =
58//! ServerH3Driver::new(Http3Settings::default());
59//! conn?.start(driver);
60//!
61//! tokio::spawn(async move {
62//! // `controller` is the handle to our established HTTP/3 connection.
63//! // For example, inbound requests are available as H3Events via:
64//! let event = controller.event_receiver_mut().recv().await;
65//! });
66//! }
67//! # Ok(())
68//! # }
69//! ```
70//!
71//! For client-side use cases, check out our [`connect`](crate::quic::connect)
72//! API.
73//!
74//! # Feature Flags
75//!
76//! tokio-quiche supports a number of feature flags to enable experimental
77//! features, performance enhancements, and additional telemetry.
78//!
79//! Enabled by default:
80//!
81//! - `qlog-gzip`: Forwards to the `qlog` crate's `gzip` feature so QLOG output
82//! can be emitted as `.sqlog.gz` via `flate2`.
83//! - `qlog-zstd`: Forwards to the `qlog` crate's `zstd` feature so QLOG output
84//! can be emitted as `.sqlog.zst`. Pulls in the `zstd` crate (C dependency
85//! via `zstd-sys`).
86//!
87//! Both compression features may be enabled together; the algorithm
88//! is selected per connection at runtime via
89//! [`settings::QuicSettings::qlog_compression`]. Disable both with
90//! `default-features = false` to opt out of the extra dependencies;
91//! the default `QlogCompression::None` keeps writing raw `.sqlog`
92//! files in that configuration.
93//!
94//! Off by default:
95//!
96//! - `congestion_window_unchecked_available`: Expose quiche's congestion
97//! control algorithm that provides no congestion-window protection.
98//! - `rpk`: Support for raw public keys (RPK) in QUIC handshakes (via
99//! [boring]).
100//! - `gcongestion`: Replace quiche's original congestion control implementation
101//! with one adapted from google/quiche.
102//! - `zero-copy`: Deprecated. Zero-copy sends are now always enabled. This
103//! feature is kept for backwards compatibility and only enables
104//! `gcongestion`.
105//! - `perf-quic-listener-metrics`: Extra telemetry for QUIC handshake
106//! durations, including protocol overhead and network delays.
107//! - `tokio-task-metrics`: Scheduling & poll duration histograms for tokio
108//! tasks.
109//!
110//! Other parts of the crate are enabled by separate build flags instead, to be
111//! controlled by the final binary:
112//!
113//! - `--cfg capture_keylogs`: Optional `SSLKEYLOGFILE` capturing for QUIC
114//! connections.
115
116pub extern crate quiche;
117
118pub mod buf_factory;
119pub mod http3;
120pub mod metrics;
121pub mod quic;
122mod result;
123pub mod settings;
124pub mod socket;
125
126pub use datagram_socket;
127
128use foundations::telemetry::settings::LogVerbosity;
129use std::io;
130use std::sync::Arc;
131use std::sync::Once;
132use tokio::net::UdpSocket;
133use tokio_stream::wrappers::ReceiverStream;
134
135use crate::metrics::Metrics;
136use crate::socket::QuicListener;
137
138pub use crate::http3::driver::ClientH3Controller;
139pub use crate::http3::driver::ClientH3Driver;
140pub use crate::http3::driver::ServerH3Controller;
141pub use crate::http3::driver::ServerH3Driver;
142pub use crate::http3::ClientH3Connection;
143pub use crate::http3::ServerH3Connection;
144pub use crate::quic::connection::ApplicationOverQuic;
145pub use crate::quic::connection::ConnectionIdGenerator;
146pub use crate::quic::connection::InitialQuicConnection;
147pub use crate::quic::connection::QuicConnection;
148pub use crate::result::BoxError;
149pub use crate::result::QuicResult;
150pub use crate::settings::ConnectionParams;
151
152#[doc(hidden)]
153pub use crate::result::QuicResultExt;
154
155/// A stream of accepted [`InitialQuicConnection`]s from a [`listen`] call.
156///
157/// Errors from processing the client's QUIC initials can also be emitted on
158/// this stream. These do not indicate that the listener itself has failed.
159pub type QuicConnectionStream<M> =
160 ReceiverStream<io::Result<InitialQuicConnection<UdpSocket, M>>>;
161
162/// Starts listening for inbound QUIC connections on the given
163/// [`QuicListener`]s.
164///
165/// Each socket starts a separate tokio task to process and route inbound
166/// packets. This task emits connections on the respective
167/// [`QuicConnectionStream`] after receiving the client's QUIC initial and
168/// (optionally) validating its IP address.
169///
170/// The task shuts down when the returned stream is closed (or dropped) and all
171/// previously-yielded connections are closed.
172pub fn listen_with_capabilities<M>(
173 sockets: impl IntoIterator<Item = QuicListener>, params: ConnectionParams,
174 metrics: M,
175) -> io::Result<Vec<QuicConnectionStream<M>>>
176where
177 M: Metrics,
178{
179 if params.settings.capture_quiche_logs {
180 capture_quiche_logs();
181 }
182
183 sockets
184 .into_iter()
185 .map(|s| crate::quic::start_listener(s, ¶ms, metrics.clone()))
186 .collect()
187}
188
189/// Starts listening for inbound QUIC connections on the given `sockets`.
190///
191/// Each socket is converted into a [`QuicListener`] with defaulted socket
192/// parameters. The listeners are then passed to [`listen_with_capabilities`].
193pub fn listen<S, M>(
194 sockets: impl IntoIterator<Item = S>, params: ConnectionParams, metrics: M,
195) -> io::Result<Vec<QuicConnectionStream<M>>>
196where
197 S: TryInto<QuicListener, Error = io::Error>,
198 M: Metrics,
199{
200 let quic_sockets: Vec<QuicListener> = sockets
201 .into_iter()
202 .map(|s| {
203 #[cfg_attr(not(target_os = "linux"), expect(unused_mut))]
204 let mut socket = s.try_into()?;
205 #[cfg(target_os = "linux")]
206 socket.apply_max_capabilities();
207 Ok(socket)
208 })
209 .collect::<io::Result<_>>()?;
210
211 listen_with_capabilities(quic_sockets, params, metrics)
212}
213
214static GLOBAL_LOGGER_ONCE: Once = Once::new();
215
216/// Forward Quiche logs into the slog::Drain currently used by Foundations
217///
218/// # Warning
219///
220/// This should **only be used for local debugging**. Quiche can potentially
221/// emit lots (and lots, and lots) of logs (the TRACE level emits a log record
222/// on every packet and frame) and you can very easily overwhelm your logging
223/// pipeline.
224///
225/// # Note
226///
227/// Quiche uses the `env_logger` crate, which uses `log` under the hood. `log`
228/// requires that you only set the global logger once. That means that we have
229/// to register the logger at `listen()` time for servers - for clients, we
230/// should register loggers when the `quiche::Connection` is established.
231pub(crate) fn capture_quiche_logs() {
232 GLOBAL_LOGGER_ONCE.call_once(|| {
233 use foundations::telemetry::log as foundations_log;
234 use log::Level as std_level;
235
236 let curr_logger =
237 Arc::clone(&foundations_log::slog_logger()).read().clone();
238 let scope_guard = slog_scope::set_global_logger(curr_logger);
239
240 // Convert slog::Level from Foundations settings to log::Level
241 let normalized_level = match foundations_log::verbosity() {
242 LogVerbosity::Critical | LogVerbosity::Error => std_level::Error,
243 LogVerbosity::Warning => std_level::Warn,
244 LogVerbosity::Info => std_level::Info,
245 LogVerbosity::Debug => std_level::Debug,
246 LogVerbosity::Trace => std_level::Trace,
247 };
248
249 slog_stdlog::init_with_level(normalized_level).unwrap();
250
251 // Dropping the scope guard replaces the slog Drain with
252 // `slog::Discard`. The `log` crate cannot reset the global
253 // logger, so retain the guard for the process lifetime.
254 let _scope_guard = std::mem::ManuallyDrop::new(scope_guard);
255 });
256}