tokio_quiche/quic/connection/mod.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
27mod error;
28mod id;
29mod map;
30
31pub use self::error::HandshakeError;
32pub use self::id::ConnectionIdGenerator;
33pub use self::id::SharedConnectionIdGenerator;
34pub use self::id::SimpleConnectionIdGenerator;
35pub(crate) use self::map::CidOwned;
36pub(crate) use self::map::ConnectionMap;
37
38use boring::ssl::SslRef;
39use datagram_socket::AsSocketStats;
40use datagram_socket::DatagramSocketSend;
41use datagram_socket::MaybeConnectedSocket;
42use datagram_socket::QuicAuditStats;
43use datagram_socket::ShutdownConnection;
44use datagram_socket::SocketStats;
45use foundations::telemetry::log;
46use foundations::telemetry::metrics::GaugeGuard;
47use futures::Future;
48use quiche::ConnectionId;
49use std::collections::HashSet;
50use std::fmt;
51use std::io;
52use std::net::SocketAddr;
53use std::sync::Arc;
54use std::sync::Mutex;
55use std::task::Poll;
56use std::time::Duration;
57use std::time::Instant;
58use std::time::SystemTime;
59use tokio::sync::mpsc;
60use tokio_util::task::AbortOnDropHandle;
61
62use self::error::make_handshake_result;
63use super::hooks::ConnectionHook;
64use super::io::connection_stage::Close;
65use super::io::connection_stage::ConnectionStageContext;
66use super::io::connection_stage::Handshake;
67use super::io::connection_stage::RunningApplication;
68use super::io::worker::Closing;
69use super::io::worker::IoWorkerParams;
70use super::io::worker::Running;
71use super::io::worker::RunningOrClosing;
72use super::io::worker::WriteState;
73use super::DscpHandle;
74use super::QuicheConnection;
75use crate::metrics::Metrics;
76use crate::quic::io::worker::IoWorker;
77use crate::quic::io::worker::WriterConfig;
78use crate::quic::io::worker::INCOMING_QUEUE_SIZE;
79use crate::quic::router::ConnectionMapCommand;
80use crate::QuicResult;
81
82/// Wrapper for connection statistics recorded by [quiche].
83#[derive(Debug)]
84pub struct QuicConnectionStats {
85 /// Aggregate connection statistics across all paths.
86 pub stats: quiche::Stats,
87 /// Specific statistics about the connection's active path.
88 pub path_stats: Option<quiche::PathStats>,
89}
90pub(crate) type QuicConnectionStatsShared = Arc<Mutex<QuicConnectionStats>>;
91
92impl QuicConnectionStats {
93 pub(crate) fn from_conn(qconn: &QuicheConnection) -> Self {
94 Self {
95 stats: qconn.stats(),
96 path_stats: qconn.path_stats().find(|stats| stats.active),
97 }
98 }
99
100 fn startup_exit_to_socket_stats(
101 value: quiche::StartupExit,
102 ) -> datagram_socket::StartupExit {
103 let reason = match value.reason {
104 quiche::StartupExitReason::Loss =>
105 datagram_socket::StartupExitReason::Loss,
106 quiche::StartupExitReason::BandwidthPlateau =>
107 datagram_socket::StartupExitReason::BandwidthPlateau,
108 quiche::StartupExitReason::PersistentQueue =>
109 datagram_socket::StartupExitReason::PersistentQueue,
110 quiche::StartupExitReason::ConservativeSlowStartRounds =>
111 datagram_socket::StartupExitReason::ConservativeSlowStartRounds,
112 };
113
114 datagram_socket::StartupExit {
115 cwnd: value.cwnd,
116 bandwidth: value.bandwidth,
117 reason,
118 }
119 }
120}
121
122impl AsSocketStats for QuicConnectionStats {
123 fn as_socket_stats(&self) -> SocketStats {
124 SocketStats {
125 pmtu: self
126 .path_stats
127 .as_ref()
128 .map(|p| p.pmtu as u16)
129 .unwrap_or_default(),
130 rtt_us: self
131 .path_stats
132 .as_ref()
133 .map(|p| p.rtt.as_micros() as i64)
134 .unwrap_or_default(),
135 min_rtt_us: self
136 .path_stats
137 .as_ref()
138 .and_then(|p| p.min_rtt.map(|x| x.as_micros() as i64))
139 .unwrap_or_default(),
140 max_rtt_us: self
141 .path_stats
142 .as_ref()
143 .and_then(|p| p.max_rtt.map(|x| x.as_micros() as i64))
144 .unwrap_or_default(),
145 rtt_var_us: self
146 .path_stats
147 .as_ref()
148 .map(|p| p.rttvar.as_micros() as i64)
149 .unwrap_or_default(),
150 cwnd: self
151 .path_stats
152 .as_ref()
153 .map(|p| p.cwnd as u64)
154 .unwrap_or_default(),
155 total_pto_count: self
156 .path_stats
157 .as_ref()
158 .map(|p| p.total_pto_count as u64)
159 .unwrap_or_default(),
160 packets_sent: self.stats.sent as u64,
161 packets_recvd: self.stats.recv as u64,
162 packets_lost: self.stats.lost as u64,
163 packets_lost_spurious: self.stats.spurious_lost as u64,
164 packets_retrans: self.stats.retrans as u64,
165 bytes_sent: self.stats.sent_bytes,
166 bytes_recvd: self.stats.recv_bytes,
167 bytes_lost: self.stats.lost_bytes,
168 bytes_retrans: self.stats.stream_retrans_bytes,
169 bytes_unsent: 0, /* not implemented yet, kept for compatibility
170 * with TCP */
171 delivery_rate: self
172 .path_stats
173 .as_ref()
174 .map(|p| p.delivery_rate)
175 .unwrap_or_default(),
176 max_bandwidth: self.path_stats.as_ref().and_then(|p| p.max_bandwidth),
177 startup_exit: self
178 .path_stats
179 .as_ref()
180 .and_then(|p| p.startup_exit)
181 .map(QuicConnectionStats::startup_exit_to_socket_stats),
182 bytes_in_flight_duration_us: self
183 .stats
184 .bytes_in_flight_duration
185 .as_micros() as u64,
186 }
187 }
188}
189
190/// A received network packet with additional metadata.
191#[derive(Debug)]
192pub struct Incoming {
193 /// The address that sent the inbound packet.
194 pub peer_addr: SocketAddr,
195 /// The address on which we received the inbound packet.
196 pub local_addr: SocketAddr,
197 /// The receive timestamp of the packet.
198 ///
199 /// Used for the `perf-quic-listener-metrics` feature.
200 pub rx_time: Option<SystemTime>,
201 /// The packet's contents.
202 pub buf: Vec<u8>,
203 /// If set, then `buf` is a GRO buffer containing multiple packets.
204 /// Each individual packet has a size of `gso` (except for the last one).
205 pub gro: Option<i32>,
206 /// [SO_MARK] control message value received from the socket.
207 ///
208 /// This will always be `None` after the connection has been spawned as
209 /// the message is `take()`d before spawning.
210 ///
211 /// [SO_MARK]: https://man7.org/linux/man-pages/man7/socket.7.html
212 #[cfg(target_os = "linux")]
213 pub so_mark_data: Option<[u8; 4]>,
214}
215
216pub(crate) struct IncomingPacketReceiver {
217 pub(crate) rx: mpsc::Receiver<Incoming>,
218 /// Channel to submit changes to the ConnectionMap
219 cmd_tx: mpsc::UnboundedSender<ConnectionMapCommand>,
220 /// Tracker for which CIDs we have to unmap still
221 registered_cids: HashSet<CidOwned>,
222}
223
224impl IncomingPacketReceiver {
225 pub(crate) fn new(
226 cid: &ConnectionId, cmd_tx: mpsc::UnboundedSender<ConnectionMapCommand>,
227 ) -> (mpsc::Sender<Incoming>, Self) {
228 // QUIC's `active_connection_id_limit` must be >= 2
229 let mut registered_cids = HashSet::with_capacity(2);
230 registered_cids.insert(cid.into());
231
232 let (tx, rx) = mpsc::channel(INCOMING_QUEUE_SIZE);
233 let incoming_pkt_receiver = Self {
234 rx,
235 cmd_tx,
236 registered_cids,
237 };
238 (tx, incoming_pkt_receiver)
239 }
240
241 pub(crate) fn prior_knowledge(&mut self, cid: &ConnectionId) {
242 self.registered_cids.insert(cid.into());
243 }
244
245 pub(crate) fn map_cid(&mut self, cid: &ConnectionId) -> bool {
246 let Some(existing) = self.registered_cids.iter().next() else {
247 // Can't map a CID if we don't have anything to map to
248 log::warn!(
249 "failed to map new CID";
250 "error" => "connection is not registered in InboundPacketRouter",
251 "connection_id" => ?cid,
252 );
253 return false;
254 };
255
256 let cid = CidOwned::from(cid);
257
258 let cmd = ConnectionMapCommand::MapCid {
259 existing_cid: existing.clone(),
260 new_cid: cid.clone(),
261 };
262 if self.cmd_tx.send(cmd).is_err() {
263 return false;
264 }
265
266 self.registered_cids.insert(cid);
267 true
268 }
269
270 pub(crate) fn unmap_cid(&mut self, cid: &ConnectionId) -> bool {
271 let cid_owned = CidOwned::from(cid);
272
273 let cmd = ConnectionMapCommand::UnmapCid(cid_owned.clone());
274 if self.cmd_tx.send(cmd).is_err() {
275 return false;
276 }
277
278 // We don't _need_ the CID to be registered with this receiver,
279 // but if it's not, something went wrong earlier. (For example,
280 // a missing call to `prior_knowledge`.)
281 if !self.registered_cids.remove(&cid_owned) {
282 log::debug!(
283 "unmapped a CID that was not registered with this IncomingPacketReceiver";
284 "connection_id" => ?cid,
285 );
286 }
287 true
288 }
289}
290
291impl Drop for IncomingPacketReceiver {
292 fn drop(&mut self) {
293 // Ensure all registered CIDs are released from the InboundPacketRouter
294 for cid in self.registered_cids.drain() {
295 let _ = self.cmd_tx.send(ConnectionMapCommand::UnmapCid(cid));
296 }
297 }
298}
299
300/// A QUIC connection that has not performed a handshake yet.
301///
302/// This type is currently only used for server-side connections. It is created
303/// and added to the listener's connection stream after an initial packet from
304/// a client has been received and (optionally) the client's IP address has been
305/// validated.
306///
307/// To turn the initial connection into a fully established one, a QUIC
308/// handshake must be performed. Users have multiple options to facilitate this:
309/// - `start` is a simple entrypoint which spawns a task to handle the entire
310/// lifetime of the QUIC connection. The caller can then only communicate with
311/// the connection via their [`ApplicationOverQuic`].
312/// - `handshake` spawns a task for the handshake and awaits its completion.
313/// Afterwards, it pauses the connection and allows the caller to resume it
314/// later via an opaque struct. We spawn a separate task to allow the tokio
315/// scheduler free choice in where to run the handshake.
316/// - `handshake_fut` returns a future to drive the handshake for maximum
317/// flexibility.
318#[must_use = "call InitialQuicConnection::start to establish the connection"]
319pub struct InitialQuicConnection<Tx, M>
320where
321 Tx: DatagramSocketSend + Send + 'static + ?Sized,
322 M: Metrics,
323{
324 params: QuicConnectionParams<Tx, M>,
325 pub(crate) audit_log_stats: Arc<QuicAuditStats>,
326 stats: QuicConnectionStatsShared,
327}
328
329impl<Tx, M> InitialQuicConnection<Tx, M>
330where
331 Tx: DatagramSocketSend + Send + 'static + ?Sized,
332 M: Metrics,
333{
334 #[inline]
335 pub(crate) fn new(params: QuicConnectionParams<Tx, M>) -> Self {
336 let scid = params.quiche_conn.source_id();
337 let audit_log_stats = Arc::new(QuicAuditStats::new(scid.into()));
338 let stats = Arc::new(Mutex::new(QuicConnectionStats::from_conn(
339 ¶ms.quiche_conn,
340 )));
341
342 Self {
343 params,
344 audit_log_stats,
345 stats,
346 }
347 }
348
349 /// The local address this connection listens on.
350 pub fn local_addr(&self) -> SocketAddr {
351 self.params.writer_cfg.local_addr
352 }
353
354 /// The remote address for this connection.
355 pub fn peer_addr(&self) -> SocketAddr {
356 self.params.writer_cfg.peer_addr
357 }
358
359 /// The QUIC source connection ID used by this connection.
360 pub fn scid(&self) -> ConnectionId<'_> {
361 self.params.quiche_conn.source_id()
362 }
363
364 /// [boring]'s SSL object for this connection.
365 #[doc(hidden)]
366 pub fn ssl_mut(&mut self) -> &mut SslRef {
367 // Deref to pick `Connection::as_mut` over `Box::as_mut`.
368 (*self.params.quiche_conn).as_mut()
369 }
370
371 /// Returns the outgoing DSCP handle when server marking is configured.
372 ///
373 /// Clone it before starting the handshake to update the worker's marking
374 /// from a certificate-selection callback. Until then the handle has no
375 /// per-packet override, so packets inherit the UDP socket's TOS/TClass.
376 pub fn dscp_handle(&self) -> Option<&DscpHandle> {
377 self.params.dscp_handle.as_ref()
378 }
379
380 /// A handle to the [`QuicAuditStats`] for this connection.
381 ///
382 /// # Note
383 /// These stats are updated during the lifetime of the connection.
384 /// The getter exists to grab a handle early on, which can then
385 /// be stowed away and read out after the connection has closed.
386 #[inline]
387 pub fn audit_log_stats(&self) -> Arc<QuicAuditStats> {
388 Arc::clone(&self.audit_log_stats)
389 }
390
391 /// A handle to the [`QuicConnectionStats`] for this connection.
392 ///
393 /// # Note
394 /// Initially, these stats represent the state when the [quiche::Connection]
395 /// was created. They are updated when the connection is closed, so this
396 /// getter exists primarily to grab a handle early on.
397 #[inline]
398 pub fn stats(&self) -> &QuicConnectionStatsShared {
399 &self.stats
400 }
401
402 /// Creates a future to drive the connection's handshake.
403 ///
404 /// This is a lower-level alternative to the `handshake` function which
405 /// gives the caller more control over execution of the future. See
406 /// `handshake` for details on the return values.
407 pub fn handshake_fut<A: ApplicationOverQuic>(
408 self, app: A,
409 ) -> (
410 QuicConnection,
411 impl Future<Output = io::Result<Running<Arc<Tx>, M, A>>> + Send + 'static,
412 ) {
413 let active_conn_guard =
414 GaugeGuard::new(self.params.metrics.connections_in_memory());
415 let scid = self.params.quiche_conn.source_id().into_owned();
416
417 let conn = QuicConnection {
418 local_addr: self.local_addr(),
419 peer_addr: self.peer_addr(),
420 audit_log_stats: Arc::clone(&self.audit_log_stats),
421 stats: Arc::clone(&self.stats),
422 scid,
423 };
424 let context = ConnectionStageContext {
425 in_pkt: self.params.initial_pkt,
426 incoming_pkt_receiver: self.params.incoming_pkt_receiver,
427 application: app,
428 stats: Arc::clone(&self.stats),
429 connection_hook: self.params.connection_hook,
430 };
431 let conn_stage = Handshake {
432 pending_cid: self.params.pending_cid,
433 };
434 let params = IoWorkerParams {
435 socket: MaybeConnectedSocket::new(self.params.socket),
436 shutdown_tx: self.params.shutdown_tx,
437 cfg: self.params.writer_cfg,
438 audit_log_stats: self.audit_log_stats,
439 write_state: WriteState::default(),
440 dscp_handle: self.params.dscp_handle,
441 cid_generator: self.params.cid_generator,
442 #[cfg(feature = "perf-quic-listener-metrics")]
443 init_rx_time: self.params.init_rx_time,
444 metrics: self.params.metrics.clone(),
445 handshake_info: self.params.handshake_info,
446 active_conn_guard,
447 };
448
449 let handshake_fut = async move {
450 let qconn = self.params.quiche_conn;
451 let handshake_done =
452 IoWorker::new(params, conn_stage).run(qconn, context).await;
453
454 match handshake_done {
455 RunningOrClosing::Running(r) => Ok(r),
456 RunningOrClosing::Closing(Closing {
457 params,
458 work_loop_result,
459 mut context,
460 mut qconn,
461 }) => {
462 let hs_result = make_handshake_result(&work_loop_result);
463 IoWorker::new(params, Close { work_loop_result })
464 .close(&mut qconn, &mut context)
465 .await;
466 hs_result
467 },
468 }
469 };
470
471 (conn, handshake_fut)
472 }
473
474 /// Performs the QUIC handshake in a separate tokio task and awaits its
475 /// completion.
476 ///
477 /// The returned [`QuicConnection`] holds metadata about the established
478 /// connection. The connection itself is paused after `handshake`
479 /// returns and must be resumed by passing the opaque `Running` value to
480 /// [`InitialQuicConnection::resume`]. This two-step process
481 /// allows callers to collect telemetry and run code before serving their
482 /// [`ApplicationOverQuic`].
483 pub async fn handshake<A: ApplicationOverQuic>(
484 self, app: A,
485 ) -> io::Result<(QuicConnection, Running<Arc<Tx>, M, A>)> {
486 let task_metrics = self.params.metrics.clone();
487 let (conn, handshake_fut) = Self::handshake_fut(self, app);
488
489 let handshake_handle = crate::metrics::tokio_task::spawn(
490 "quic_handshake_worker",
491 task_metrics,
492 handshake_fut,
493 );
494
495 // `AbortOnDropHandle` simulates task-killswitch behavior without
496 // needing to give up ownership of the `JoinHandle`.
497 let handshake_abort_handle = AbortOnDropHandle::new(handshake_handle);
498
499 let worker = handshake_abort_handle.await??;
500
501 Ok((conn, worker))
502 }
503
504 /// Resumes a QUIC connection which was paused after a successful handshake.
505 pub fn resume<A: ApplicationOverQuic>(pre_running: Running<Arc<Tx>, M, A>) {
506 let task_metrics = pre_running.params.metrics.clone();
507 let fut = async move {
508 let Running {
509 params,
510 context,
511 qconn,
512 } = pre_running;
513 let running_worker = IoWorker::new(params, RunningApplication);
514
515 let Closing {
516 params,
517 mut context,
518 work_loop_result,
519 mut qconn,
520 } = running_worker.run(qconn, context).await;
521
522 IoWorker::new(params, Close { work_loop_result })
523 .close(&mut qconn, &mut context)
524 .await;
525 };
526
527 crate::metrics::tokio_task::spawn_with_killswitch(
528 "quic_io_worker",
529 task_metrics,
530 fut,
531 );
532 }
533
534 /// Drives a QUIC connection from handshake to close in separate tokio
535 /// tasks.
536 ///
537 /// It combines [`InitialQuicConnection::handshake`] and
538 /// [`InitialQuicConnection::resume`] into a single call.
539 pub fn start<A: ApplicationOverQuic>(self, app: A) -> QuicConnection {
540 let task_metrics = self.params.metrics.clone();
541 let (conn, handshake_fut) = Self::handshake_fut(self, app);
542 // Pin to the heap so the spawned task only carries a pointer to it
543 // instead of inlining the full future state across the await.
544 let handshake_fut = Box::pin(handshake_fut);
545
546 let fut = async move {
547 match handshake_fut.await {
548 Ok(running) => Self::resume(running),
549 Err(e) => {
550 log::error!("QUIC handshake failed in IQC::start"; "error" => e);
551 },
552 }
553 };
554
555 crate::metrics::tokio_task::spawn_with_killswitch(
556 "quic_handshake_worker",
557 task_metrics,
558 fut,
559 );
560
561 conn
562 }
563}
564
565pub(crate) struct QuicConnectionParams<Tx, M>
566where
567 Tx: DatagramSocketSend + Send + 'static + ?Sized,
568 M: Metrics,
569{
570 pub writer_cfg: WriterConfig,
571 pub pending_cid: Option<ConnectionId<'static>>,
572 pub initial_pkt: Option<Incoming>,
573 pub dscp_handle: Option<DscpHandle>,
574 pub shutdown_tx: mpsc::Sender<()>,
575 pub incoming_pkt_receiver: IncomingPacketReceiver,
576 pub cid_generator: Option<SharedConnectionIdGenerator>,
577 pub metrics: M,
578 pub connection_hook: Option<Arc<dyn ConnectionHook + Send + Sync + 'static>>,
579 #[cfg(feature = "perf-quic-listener-metrics")]
580 pub init_rx_time: Option<SystemTime>,
581 pub handshake_info: HandshakeInfo,
582 /// Boxed because this value is moved by-value through several nested
583 /// async state machines. Inlining a [`QuicheConnection`] here would
584 /// duplicate its payload across the future state slots that hold it
585 /// across an `.await`.
586 pub quiche_conn: Box<QuicheConnection>,
587 pub socket: Arc<Tx>,
588}
589
590/// Metadata about an established QUIC connection.
591///
592/// While this struct allows access to some facets of a QUIC connection, it
593/// notably does not represent the [quiche::Connection] itself. The crate
594/// handles most interactions with [quiche] internally in a worker task. Users
595/// can only access the connection directly via their [`ApplicationOverQuic`]
596/// implementation.
597///
598/// See the [module-level docs](crate::quic) for an overview of how a QUIC
599/// connection is handled internally.
600pub struct QuicConnection {
601 local_addr: SocketAddr,
602 peer_addr: SocketAddr,
603 audit_log_stats: Arc<QuicAuditStats>,
604 stats: QuicConnectionStatsShared,
605 scid: ConnectionId<'static>,
606}
607
608impl QuicConnection {
609 /// The local address this connection listens on.
610 #[inline]
611 pub fn local_addr(&self) -> SocketAddr {
612 self.local_addr
613 }
614
615 /// The remote address for this connection.
616 #[inline]
617 pub fn peer_addr(&self) -> SocketAddr {
618 self.peer_addr
619 }
620
621 /// A handle to the [`QuicAuditStats`] for this connection.
622 ///
623 /// # Note
624 /// These stats are updated during the lifetime of the connection.
625 /// The getter exists to grab a handle early on, which can then
626 /// be stowed away and read out after the connection has closed.
627 #[inline]
628 pub fn audit_log_stats(&self) -> &Arc<QuicAuditStats> {
629 &self.audit_log_stats
630 }
631
632 /// A handle to the [`QuicConnectionStats`] for this connection.
633 ///
634 /// # Note
635 /// Initially, these stats represent the state when the [quiche::Connection]
636 /// was created. They are updated when the connection is closed, so this
637 /// getter exists primarily to grab a handle early on.
638 #[inline]
639 pub fn stats(&self) -> &QuicConnectionStatsShared {
640 &self.stats
641 }
642
643 /// The QUIC source connection ID used by this connection.
644 #[inline]
645 pub fn scid(&self) -> &ConnectionId<'static> {
646 &self.scid
647 }
648}
649
650impl AsSocketStats for QuicConnection {
651 #[inline]
652 fn as_socket_stats(&self) -> SocketStats {
653 // It is important to note that those stats are only updated when
654 // the connection stops, which is fine, since this is only used to
655 // log after the connection is finished.
656 self.stats.lock().unwrap().as_socket_stats()
657 }
658
659 #[inline]
660 fn as_quic_stats(&self) -> Option<&Arc<QuicAuditStats>> {
661 Some(&self.audit_log_stats)
662 }
663}
664
665impl<Tx, M> AsSocketStats for InitialQuicConnection<Tx, M>
666where
667 Tx: DatagramSocketSend + Send + 'static + ?Sized,
668 M: Metrics,
669{
670 #[inline]
671 fn as_socket_stats(&self) -> SocketStats {
672 // It is important to note that those stats are only updated when
673 // the connection stops, which is fine, since this is only used to
674 // log after the connection is finished.
675 self.stats.lock().unwrap().as_socket_stats()
676 }
677
678 #[inline]
679 fn as_quic_stats(&self) -> Option<&Arc<QuicAuditStats>> {
680 Some(&self.audit_log_stats)
681 }
682}
683
684impl<Tx, M> ShutdownConnection for InitialQuicConnection<Tx, M>
685where
686 Tx: DatagramSocketSend + Send + 'static + ?Sized,
687 M: Metrics,
688{
689 #[inline]
690 fn poll_shutdown(
691 &mut self, _cx: &mut std::task::Context,
692 ) -> std::task::Poll<io::Result<()>> {
693 // TODO: Does nothing at the moment. We always call Self::start
694 // anyway so it's not really important at this moment.
695 Poll::Ready(Ok(()))
696 }
697}
698
699impl ShutdownConnection for QuicConnection {
700 #[inline]
701 fn poll_shutdown(
702 &mut self, _cx: &mut std::task::Context,
703 ) -> std::task::Poll<io::Result<()>> {
704 // TODO: does nothing at the moment
705 Poll::Ready(Ok(()))
706 }
707}
708
709/// Details about a connection's QUIC handshake.
710#[derive(Debug, Clone)]
711pub struct HandshakeInfo {
712 /// The time at which the connection was created.
713 start_time: Instant,
714 /// The timeout before which the handshake must complete.
715 timeout: Option<Duration>,
716 /// The real duration that the handshake took to complete.
717 time_handshake: Option<Duration>,
718}
719
720impl HandshakeInfo {
721 pub(crate) fn new(start_time: Instant, timeout: Option<Duration>) -> Self {
722 Self {
723 start_time,
724 timeout,
725 time_handshake: None,
726 }
727 }
728
729 /// The time at which the connection was created.
730 #[inline]
731 pub fn start_time(&self) -> Instant {
732 self.start_time
733 }
734
735 /// How long the handshake took to complete.
736 #[inline]
737 pub fn elapsed(&self) -> Duration {
738 self.time_handshake.unwrap_or_default()
739 }
740
741 pub(crate) fn set_elapsed(&mut self) {
742 let elapsed = self.start_time.elapsed();
743 self.time_handshake = Some(elapsed)
744 }
745
746 pub(crate) fn deadline(&self) -> Option<Instant> {
747 self.timeout.map(|timeout| self.start_time + timeout)
748 }
749
750 pub(crate) fn is_expired(&self) -> bool {
751 self.timeout
752 .is_some_and(|timeout| self.start_time.elapsed() >= timeout)
753 }
754}
755
756/// A trait to implement an application served over QUIC.
757///
758/// The application is driven by an internal worker task, which also handles I/O
759/// for the connection. The worker feeds inbound packets into the
760/// [quiche::Connection], calls [`ApplicationOverQuic::process_reads`] followed
761/// by [`ApplicationOverQuic::process_writes`], and then flushes any pending
762/// outbound packets to the network. This repeats in a loop until either the
763/// connection is closed or the [`ApplicationOverQuic`] returns an error.
764///
765/// In between loop iterations, the worker yields until a new packet arrives, a
766/// timer expires, or [`ApplicationOverQuic::wait_for_data`] resolves.
767/// Implementors can interact with the underlying connection via the mutable
768/// reference passed to trait methods.
769#[allow(unused_variables)] // for default functions
770pub trait ApplicationOverQuic: Send + 'static {
771 /// Callback to customize the [`ApplicationOverQuic`] after the QUIC
772 /// handshake completed successfully.
773 ///
774 /// # Errors
775 /// Returning an error from this method immediately stops the worker loop
776 /// and transitions to the connection closing stage.
777 fn on_conn_established(
778 &mut self, qconn: &mut QuicheConnection, handshake_info: &HandshakeInfo,
779 ) -> QuicResult<()>;
780
781 /// Determines whether the application's methods will be called by the
782 /// worker.
783 ///
784 /// The function is checked in each iteration of the worker loop. Only
785 /// `on_conn_established()` bypasses this check.
786 fn should_act(&self) -> bool;
787
788 /// Waits for an event to trigger the next iteration of the worker loop.
789 ///
790 /// The returned future is awaited in parallel to inbound packets and the
791 /// connection's timers. Any one of those futures resolving triggers the
792 /// next loop iteration, so implementations should not rely on
793 /// `wait_for_data` for the bulk of their processing. Instead, after
794 /// `wait_for_data` resolves, `process_writes` should be used to pull all
795 /// available data out of the event source (for example, a channel).
796 ///
797 /// As for any future, it is **very important** that this method does not
798 /// block the runtime. If it does, the other concurrent futures will be
799 /// starved.
800 ///
801 /// # Cancel safety
802 /// This method MUST be cancel safe.
803 /// It gets called inside select! and could be (repeatedly) cancelled
804 ///
805 /// # Errors
806 /// Returning an error from this method immediately stops the worker loop
807 /// and transitions to the connection closing stage.
808 fn wait_for_data(
809 &mut self, qconn: &mut QuicheConnection,
810 ) -> impl Future<Output = QuicResult<()>> + Send;
811
812 /// Processes data received on the connection.
813 ///
814 /// This method is only called if `should_act()` returns `true` and any
815 /// packets were received since the last worker loop iteration. It
816 /// should be used to read from the connection's open streams.
817 ///
818 /// # Errors
819 /// Returning an error from this method immediately stops the worker loop
820 /// and transitions to the connection closing stage.
821 fn process_reads(&mut self, qconn: &mut QuicheConnection) -> QuicResult<()>;
822
823 /// Adds data to be sent on the connection.
824 ///
825 /// Unlike `process_reads`, this method is called on every iteration of the
826 /// worker loop (provided `should_act()` returns true). It is called
827 /// after `process_reads` and immediately before packets are pushed to
828 /// the socket. The main use case is providing already-buffered data to
829 /// the [quiche::Connection].
830 ///
831 /// # Errors
832 /// Returning an error from this method immediately stops the worker loop
833 /// and transitions to the connection closing stage.
834 fn process_writes(&mut self, qconn: &mut QuicheConnection) -> QuicResult<()>;
835
836 /// Callback to inspect the result of the worker task, before a final packet
837 /// with a `CONNECTION_CLOSE` frame is flushed to the network.
838 ///
839 /// `connection_result` is [`Ok`] only if the connection was closed without
840 /// any local error. Otherwise, the state of `qconn` depends on the
841 /// error type and application behavior.
842 fn on_conn_close<M: Metrics>(
843 &mut self, qconn: &mut QuicheConnection, metrics: &M,
844 connection_result: &QuicResult<()>,
845 ) {
846 }
847}
848
849/// A command to execute on a [quiche::Connection] in the context of an
850/// [`ApplicationOverQuic`].
851///
852/// We expect most [`ApplicationOverQuic`] implementations (such as
853/// [H3Driver](crate::http3::driver::H3Driver)) will provide some way to submit
854/// actions for them to take, for example via a channel. This enum may be
855/// accepted as part of those actions to inspect or alter the state of the
856/// underlying connection.
857pub enum QuicCommand {
858 /// Close the connection with the given parameters.
859 ///
860 /// Some packets may still be sent after this command has been executed, so
861 /// the worker task may continue running for a bit. See
862 /// [`quiche::Connection::close`] for details.
863 ConnectionClose(ConnectionShutdownBehaviour),
864 /// Execute a custom callback on the connection.
865 Custom(Box<dyn FnOnce(&mut QuicheConnection) + Send + 'static>),
866 /// Collect the current [`SocketStats`] from the connection.
867 ///
868 /// Unlike [`QuicConnection::stats()`], these statistics are not cached and
869 /// instead are retrieved right before the command is executed.
870 Stats(Box<dyn FnOnce(datagram_socket::SocketStats) + Send + 'static>),
871 /// Collect the current [`QuicConnectionStats`] from the connection.
872 ///
873 /// These statistics are not cached and instead are retrieved right before
874 /// the command is executed.
875 ConnectionStats(Box<dyn FnOnce(QuicConnectionStats) + Send + 'static>),
876}
877
878impl QuicCommand {
879 /// Consume the command and perform its operation on `qconn`.
880 ///
881 /// This method should be called by [`ApplicationOverQuic`] implementations
882 /// when they receive a [`QuicCommand`] to execute.
883 pub fn execute(self, qconn: &mut QuicheConnection) {
884 match self {
885 Self::ConnectionClose(behavior) => {
886 let ConnectionShutdownBehaviour {
887 send_application_close,
888 error_code,
889 reason,
890 } = behavior;
891
892 let _ = qconn.close(send_application_close, error_code, &reason);
893 },
894 Self::Custom(f) => {
895 (f)(qconn);
896 },
897 Self::Stats(callback) => {
898 let stats_pair = QuicConnectionStats::from_conn(qconn);
899 (callback)(stats_pair.as_socket_stats());
900 },
901 Self::ConnectionStats(callback) => {
902 let stats_pair = QuicConnectionStats::from_conn(qconn);
903 (callback)(stats_pair);
904 },
905 }
906 }
907}
908
909impl fmt::Debug for QuicCommand {
910 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
911 match self {
912 Self::ConnectionClose(b) =>
913 f.debug_tuple("ConnectionClose").field(b).finish(),
914 Self::Custom(_) => f.debug_tuple("Custom").finish_non_exhaustive(),
915 Self::Stats(_) => f.debug_tuple("Stats").finish_non_exhaustive(),
916 Self::ConnectionStats(_) =>
917 f.debug_tuple("ConnectionStats").finish_non_exhaustive(),
918 }
919 }
920}
921
922/// Parameters to close a [quiche::Connection].
923///
924/// The connection will use these parameters for the `CONNECTION_CLOSE` frame
925/// it sends to its peer.
926#[derive(Debug, Clone)]
927pub struct ConnectionShutdownBehaviour {
928 /// Whether to send an application close or a regular close to the peer.
929 ///
930 /// If this is true but the connection is not in a state where it is safe to
931 /// send an application error (not established nor in early data), in
932 /// accordance with [RFC 9000](https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.3-3), the
933 /// error code is changed to `APPLICATION_ERROR` and the reason phrase is
934 /// cleared.
935 pub send_application_close: bool,
936 /// The [QUIC][proto-err] or [application-level][app-err] error code to send
937 /// to the peer.
938 ///
939 /// [proto-err]: https://www.rfc-editor.org/rfc/rfc9000.html#section-20.1
940 /// [app-err]: https://www.rfc-editor.org/rfc/rfc9000.html#section-20.2
941 pub error_code: u64,
942 /// The reason phrase to send to the peer.
943 pub reason: Vec<u8>,
944}