Skip to main content

moqtap_proxy/
listener.rs

1//! Listeners for accepting incoming MoQT connections (QUIC and WebTransport).
2
3use std::net::SocketAddr;
4use std::sync::Arc;
5
6use rustls::pki_types::{CertificateDer, PrivateKeyDer};
7
8use crate::error::ProxyError;
9
10/// Configuration for the proxy's QUIC listener.
11pub struct ListenerConfig {
12    /// Address to bind to (e.g., `"0.0.0.0:4443"`).
13    pub bind_addr: SocketAddr,
14    /// TLS certificate chain (DER-encoded).
15    pub cert_chain: Vec<CertificateDer<'static>>,
16    /// TLS private key (DER-encoded).
17    pub key_der: PrivateKeyDer<'static>,
18    /// ALPN protocols to accept. Defaults to `[b"moq-00"]`.
19    pub alpn: Vec<Vec<u8>>,
20}
21
22/// A QUIC listener that accepts incoming connections and detects the
23/// negotiated ALPN protocol.
24pub struct Listener {
25    endpoint: quinn::Endpoint,
26}
27
28impl Listener {
29    /// Bind to the configured address and start listening.
30    pub fn bind(config: ListenerConfig) -> Result<Self, ProxyError> {
31        let mut server_tls = rustls::ServerConfig::builder()
32            .with_no_client_auth()
33            .with_single_cert(config.cert_chain, config.key_der)
34            .map_err(|e| ProxyError::TlsConfig(format!("server cert config: {e}")))?;
35
36        server_tls.alpn_protocols = config.alpn;
37        server_tls.max_early_data_size = u32::MAX;
38
39        let quic_server_config: quinn::crypto::rustls::QuicServerConfig =
40            server_tls.try_into().map_err(|e| ProxyError::TlsConfig(format!("{e}")))?;
41
42        let server_config = quinn::ServerConfig::with_crypto(Arc::new(quic_server_config));
43
44        let endpoint = quinn::Endpoint::server(server_config, config.bind_addr)
45            .map_err(|e| ProxyError::Listener(e.to_string()))?;
46
47        Ok(Self { endpoint })
48    }
49
50    /// Accept the next incoming QUIC connection.
51    ///
52    /// Returns the `quinn::Connection` and the negotiated ALPN protocol.
53    pub async fn accept(&self) -> Result<(quinn::Connection, Vec<u8>), ProxyError> {
54        let incoming = self
55            .endpoint
56            .accept()
57            .await
58            .ok_or_else(|| ProxyError::Listener("endpoint closed".to_string()))?;
59
60        let conn = incoming.await.map_err(|e| ProxyError::Listener(e.to_string()))?;
61
62        let alpn = conn
63            .handshake_data()
64            .and_then(|hd| hd.downcast::<quinn::crypto::rustls::HandshakeData>().ok())
65            .and_then(|hd| hd.protocol)
66            .map(|p| p.to_vec())
67            .unwrap_or_default();
68
69        Ok((conn, alpn))
70    }
71
72    /// Get the local address this listener is bound to.
73    pub fn local_addr(&self) -> Result<SocketAddr, ProxyError> {
74        self.endpoint.local_addr().map_err(|e| ProxyError::Listener(e.to_string()))
75    }
76
77    /// Stop accepting new connections.
78    pub fn close(&self) {
79        self.endpoint.close(0u32.into(), b"proxy shutting down");
80    }
81}
82
83// ── WebTransport listener ─────────────────────────────────────
84
85/// Configuration for the proxy's WebTransport listener.
86#[cfg(feature = "webtransport")]
87pub struct WtListenerConfig {
88    /// Address to bind to (e.g., `"0.0.0.0:4443"`).
89    pub bind_addr: SocketAddr,
90    /// TLS certificate chain (DER-encoded).
91    pub cert_chain: Vec<CertificateDer<'static>>,
92    /// TLS private key (DER-encoded).
93    pub key_der: PrivateKeyDer<'static>,
94}
95
96/// A WebTransport listener that accepts incoming sessions.
97#[cfg(feature = "webtransport")]
98pub struct WtListener {
99    endpoint: wtransport::Endpoint<wtransport::endpoint::endpoint_side::Server>,
100}
101
102#[cfg(feature = "webtransport")]
103impl WtListener {
104    /// Bind to the configured address and start listening.
105    pub fn bind(config: WtListenerConfig) -> Result<Self, ProxyError> {
106        let mut server_tls = rustls::ServerConfig::builder()
107            .with_no_client_auth()
108            .with_single_cert(config.cert_chain, config.key_der)
109            .map_err(|e| ProxyError::TlsConfig(format!("server cert config: {e}")))?;
110
111        // WebTransport uses h3 ALPN
112        server_tls.alpn_protocols = vec![b"h3".to_vec()];
113
114        let wt_config = wtransport::ServerConfig::builder()
115            .with_bind_address(config.bind_addr)
116            .with_custom_tls(server_tls)
117            .build();
118
119        let endpoint = wtransport::Endpoint::server(wt_config)
120            .map_err(|e| ProxyError::Listener(e.to_string()))?;
121
122        Ok(Self { endpoint })
123    }
124
125    /// Accept the next incoming WebTransport connection.
126    pub async fn accept(&self) -> Result<wtransport::Connection, ProxyError> {
127        let incoming = self.endpoint.accept().await;
128        let session_request = incoming.await.map_err(|e| ProxyError::Listener(e.to_string()))?;
129        let conn =
130            session_request.accept().await.map_err(|e| ProxyError::Listener(e.to_string()))?;
131        Ok(conn)
132    }
133
134    /// Get the local address this listener is bound to.
135    pub fn local_addr(&self) -> Result<SocketAddr, ProxyError> {
136        self.endpoint.local_addr().map_err(|e| ProxyError::Listener(e.to_string()))
137    }
138
139    /// Stop accepting new connections.
140    pub fn close(&self) {
141        self.endpoint.close(wtransport::VarInt::from_u32(0), b"proxy shutting down");
142    }
143}