Skip to main content

confium_net_quic/
listener.rs

1//! Listening endpoint for `quic://`, `quic4://`, `quic6://` URLs.
2//!
3//! [`QuicListener`] wraps a Quinn [`Endpoint`] configured as a server
4//! and implements [`confium_net::Listener`]. Each
5//! [`accept`](confium_net::Listener::accept) drives the Quinn
6//! `accept` future on the listener's owned runtime, then wraps the
7//! accepted [`Connection`](quinn::Connection) in a
8//! [`crate::QuicTransport`], so accepted peers speak the same
9//! length-prefixed framing as dial-out peers.
10
11use std::net::SocketAddr;
12use std::sync::Arc;
13use std::time::Duration;
14
15use quinn::Endpoint;
16use quinn::IdleTimeout;
17use quinn::TransportConfig;
18
19use confium_net::Listener;
20use confium_net::Result;
21use confium_net::Transport;
22use confium_net::error::ClosedSnafu;
23
24use crate::QuicTransport;
25use crate::runtime::Handle;
26use crate::tls;
27use crate::transport::quinn_io_to_closed;
28use crate::transport::resolve_addr;
29
30/// Listening endpoint for inbound QUIC connections.
31///
32/// Holding this alive keeps the bound UDP socket open; dropping it
33/// closes the endpoint (the OS releases the port).
34pub struct QuicListener {
35    rt: Handle,
36    endpoint: Option<Endpoint>,
37}
38
39impl QuicListener {
40    /// Bind a new listener at `host:port`, honoring the address-family
41    /// hint `quic4` / `quic6` / `quic` encoded in `scheme`. Passing
42    /// port `0` requests an ephemeral port from the OS; the caller can
43    /// read the assigned port back via [`local_addr`](Self::local_addr).
44    pub fn bind(scheme: &str, host: &str, port: u16) -> std::io::Result<Self> {
45        let addr = resolve_addr(scheme, host, port)?;
46        let rt = Handle::new()?;
47
48        let mut server_cfg =
49            tls::server_config().map_err(|e| io_error_str("server TLS config", &e))?;
50
51        // Generous idle timeout so long-lived TC sessions (minutes
52        // between rounds) are not prematurely torn down.
53        let mut tc = TransportConfig::default();
54        tc.max_idle_timeout(Some(
55            IdleTimeout::try_from(Duration::from_secs(300)).expect("300s is a valid idle timeout"),
56        ));
57        server_cfg.transport_config(Arc::new(tc));
58
59        let endpoint = rt
60            .block_on(async { Endpoint::server(server_cfg, addr) })
61            .map_err(|e| io_error("bind server endpoint", e))?;
62
63        Ok(Self {
64            rt,
65            endpoint: Some(endpoint),
66        })
67    }
68
69    /// The locally-bound socket address. Useful for reading back the
70    /// OS-assigned ephemeral port after binding with port `0`.
71    pub fn local_addr(&self) -> std::io::Result<SocketAddr> {
72        let endpoint = self
73            .endpoint
74            .as_ref()
75            .ok_or_else(|| std::io::Error::from(std::io::ErrorKind::NotConnected))?;
76        endpoint.local_addr().map_err(|e| io_error("local_addr", e))
77    }
78}
79
80impl Listener for QuicListener {
81    fn accept(&mut self) -> Result<Box<dyn Transport>> {
82        let endpoint = match &self.endpoint {
83            Some(e) => e,
84            None => return ClosedSnafu.fail(),
85        };
86        let rt = self.rt.clone();
87        let incoming = match rt.block_on(async { endpoint.accept().await }) {
88            Some(i) => i,
89            None => return ClosedSnafu.fail(),
90        };
91        let conn: quinn::Connection = rt
92            .block_on(async { incoming.await })
93            .map_err(|e| io_error("accept handshake", e))
94            .map_err(quinn_io_to_closed)?;
95        let transport = QuicTransport::from_connection(rt, conn).map_err(quinn_io_to_closed)?;
96        Ok(Box::new(transport))
97    }
98}
99
100impl Drop for QuicListener {
101    fn drop(&mut self) {
102        if let Some(ep) = self.endpoint.take() {
103            self.rt.block_on(async {
104                ep.close(0u32.into(), &[]);
105            });
106        }
107    }
108}
109
110fn io_error<E: std::fmt::Display>(ctx: &str, e: E) -> std::io::Error {
111    std::io::Error::other(format!("{ctx}: {e}"))
112}
113
114fn io_error_str(ctx: &str, e: &str) -> std::io::Error {
115    std::io::Error::other(format!("{ctx}: {e}"))
116}