tokio_quiche/quic/raw.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//! Helper to wrap existing [quiche::Connection]s.
28//!
29//! This is a low-level interface for users who either need to heavily customize
30//! the [`quiche::Connection`] beyond what is possible via the crate's
31//! [`settings`](crate::settings), or need more control over how to pass data
32//! into the connection.
33//!
34//! Most use cases are much better served by our
35//! [`connect`](crate::quic::connect) (for clients) or [`listen`](crate::listen)
36//! (for servers) API.
37
38use datagram_socket::DatagramSocketSend;
39use std::sync::Arc;
40use std::task::ready;
41use std::task::Context;
42use std::task::Poll;
43use std::time::Instant;
44use tokio::sync::mpsc;
45
46use super::connection::InitialQuicConnection;
47use super::connection::QuicConnectionParams;
48use super::io::worker::WriterConfig;
49use super::router::ConnectionMapCommand;
50use crate::metrics::Metrics;
51use crate::quic::HandshakeInfo;
52use crate::quic::Incoming;
53use crate::quic::QuicheConnection;
54use crate::socket::Socket;
55
56/// Result of manually wrapping a [`quiche::Connection`] in an
57/// [`InitialQuicConnection`].
58///
59/// This struct bundles the interfaces which interact with the connection.
60pub struct ConnWrapperResult<Tx, M>
61where
62 Tx: DatagramSocketSend + Send + 'static + ?Sized,
63 M: Metrics,
64{
65 /// The connection wrapper.
66 pub conn: InitialQuicConnection<Tx, M>,
67 /// Sender for inbound packets on the connection.
68 pub incoming_tx: mpsc::Sender<Incoming>,
69 /// Receiver for `connection closed` notifications. This fires
70 /// after a `CONNECTION_CLOSE` frame has been sent on the connection,
71 /// but before `worker_shutdown_rx`.
72 pub conn_close_rx: ConnCloseReceiver,
73 /// Receiver which fires only when its associated sender is dropped.
74 /// This happens when the connection's IO task exits.
75 pub worker_shutdown_rx: mpsc::Receiver<()>,
76}
77
78/// Wraps an existing [`quiche::Connection`] in an [`InitialQuicConnection`],
79/// bypassing the regular packet router workflow.
80///
81/// Connections wrapped in this way require the user to manually pass inbound
82/// packets via the channel returned in [`ConnWrapperResult`]. The passed
83/// `tx_socket` is only used to _send_ outbound packets and to extract the
84/// endpoint's addresses.
85///
86/// # Note
87/// This function does not attempt any I/O when wrapping the
88/// [`quiche::Connection`]. To start handshaking and consuming packets from the
89/// returned channel, use the methods on [`InitialQuicConnection`].
90pub fn wrap_quiche_conn<Tx, R, M>(
91 quiche_conn: QuicheConnection, tx_socket: Socket<Arc<Tx>, R>, metrics: M,
92) -> ConnWrapperResult<Tx, M>
93where
94 Tx: DatagramSocketSend + Send + 'static + ?Sized,
95 M: Metrics,
96{
97 let Socket {
98 send: socket,
99 local_addr,
100 peer_addr,
101 ..
102 } = tx_socket;
103 let (shutdown_tx, worker_shutdown_rx) = mpsc::channel(1);
104 let (conn_map_cmd_tx, conn_map_rx) = mpsc::unbounded_channel();
105
106 let scid = quiche_conn.source_id().into_owned();
107
108 let writer_cfg = WriterConfig {
109 pending_cid: None, // only used for unmapping in IPR
110 peer_addr,
111 local_addr,
112 // TODO: try to read Tx' SocketCaps. false is always a safe default.
113 with_gso: false,
114 pacing_offload: false,
115 with_pktinfo: false,
116 };
117
118 let conn_params = QuicConnectionParams {
119 writer_cfg,
120 initial_pkt: None,
121 shutdown_tx,
122 conn_map_cmd_tx,
123 scid,
124 cid_generator: None,
125 metrics,
126 #[cfg(feature = "perf-quic-listener-metrics")]
127 init_rx_time: None,
128 handshake_info: HandshakeInfo::new(Instant::now(), None),
129 quiche_conn,
130 socket,
131 local_addr,
132 peer_addr,
133 };
134
135 let conn = InitialQuicConnection::new(conn_params);
136 let incoming_tx = conn.incoming_ev_sender.clone();
137
138 ConnWrapperResult {
139 conn,
140 incoming_tx,
141 conn_close_rx: ConnCloseReceiver(conn_map_rx),
142 worker_shutdown_rx,
143 }
144}
145
146/// Pollable receiver for `connection closed` notifications from a QUIC
147/// connection.
148///
149/// This receiver also fires if the corresponding sender has been dropped
150/// without a `CONNECTION_CLOSE` frame on the connection.
151pub struct ConnCloseReceiver(mpsc::UnboundedReceiver<ConnectionMapCommand>);
152
153impl ConnCloseReceiver {
154 /// Polls to receive a `connection closed` notification.
155 pub fn poll_recv(&mut self, cx: &mut Context) -> Poll<()> {
156 loop {
157 let cmd = ready!(self.0.poll_recv(cx));
158 if matches!(cmd, None | Some(ConnectionMapCommand::UnmapCid(_))) {
159 // Raw connections have neither a `pending_cid` nor a
160 // `cid_generator`. The only time they unmap a CID is when the
161 // connection is closed.
162 return Poll::Ready(());
163 }
164 }
165 }
166
167 /// Waits for a `connection closed` notification.
168 pub async fn recv(&mut self) {
169 std::future::poll_fn(|cx| self.poll_recv(cx)).await
170 }
171}