use std::net::SocketAddr;
use std::sync::Arc;
use moqtap_codec::version::DraftVersion;
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use crate::error::ProxyError;
const H3_ALPN: &[u8] = b"h3";
pub struct ListenerConfig {
pub bind_addr: SocketAddr,
pub cert_chain: Vec<CertificateDer<'static>>,
pub key_der: PrivateKeyDer<'static>,
}
pub enum AcceptedConn {
Quic {
conn: quinn::Connection,
alpn: Vec<u8>,
},
#[cfg(feature = "webtransport")]
WebTransport(wtransport::Connection),
}
fn advertised_alpns() -> Vec<Vec<u8>> {
let mut out: Vec<Vec<u8>> = Vec::new();
for d in [
DraftVersion::Draft07,
DraftVersion::Draft08,
DraftVersion::Draft09,
DraftVersion::Draft10,
DraftVersion::Draft11,
DraftVersion::Draft12,
DraftVersion::Draft13,
DraftVersion::Draft14,
DraftVersion::Draft15,
DraftVersion::Draft16,
DraftVersion::Draft17,
DraftVersion::Draft18,
DraftVersion::Draft19,
] {
let alpn = d.quic_alpn().to_vec();
if !out.iter().any(|existing| existing == &alpn) {
out.push(alpn);
}
}
#[cfg(feature = "webtransport")]
out.push(H3_ALPN.to_vec());
out
}
pub struct Listener {
endpoint: quinn::Endpoint,
}
impl Listener {
pub fn bind(config: ListenerConfig) -> Result<Self, ProxyError> {
let mut server_tls = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(config.cert_chain, config.key_der)
.map_err(|e| ProxyError::TlsConfig(format!("server cert config: {e}")))?;
server_tls.alpn_protocols = advertised_alpns();
server_tls.max_early_data_size = u32::MAX;
let quic_server_config: quinn::crypto::rustls::QuicServerConfig =
server_tls.try_into().map_err(|e| ProxyError::TlsConfig(format!("{e}")))?;
let server_config = quinn::ServerConfig::with_crypto(Arc::new(quic_server_config));
let endpoint = quinn::Endpoint::server(server_config, config.bind_addr)
.map_err(|e| ProxyError::Listener(e.to_string()))?;
Ok(Self { endpoint })
}
pub async fn accept(&self) -> Result<AcceptedConn, ProxyError> {
let incoming = self
.endpoint
.accept()
.await
.ok_or_else(|| ProxyError::Listener("endpoint closed".to_string()))?;
let mut connecting = incoming.accept().map_err(|e| ProxyError::Listener(e.to_string()))?;
let hs_data = connecting
.handshake_data()
.await
.map_err(|e| ProxyError::Listener(format!("handshake data: {e}")))?;
let alpn = hs_data
.downcast::<quinn::crypto::rustls::HandshakeData>()
.ok()
.and_then(|hd| hd.protocol)
.map(|p| p.to_vec())
.unwrap_or_default();
if alpn == H3_ALPN {
#[cfg(feature = "webtransport")]
{
let session_fut =
wtransport::endpoint::IncomingSessionFuture::with_quic_connecting(connecting);
let session_request = session_fut
.await
.map_err(|e| ProxyError::Listener(format!("webtransport handshake: {e}")))?;
let conn = session_request
.accept()
.await
.map_err(|e| ProxyError::Listener(format!("webtransport accept: {e}")))?;
Ok(AcceptedConn::WebTransport(conn))
}
#[cfg(not(feature = "webtransport"))]
{
drop(connecting);
Err(ProxyError::Listener(
"client negotiated h3 but webtransport feature is not enabled".to_string(),
))
}
} else {
let conn = connecting.await.map_err(|e| ProxyError::Listener(e.to_string()))?;
Ok(AcceptedConn::Quic { conn, alpn })
}
}
pub fn local_addr(&self) -> Result<SocketAddr, ProxyError> {
self.endpoint.local_addr().map_err(|e| ProxyError::Listener(e.to_string()))
}
pub fn close(&self) {
self.endpoint.close(0u32.into(), b"proxy shutting down");
}
}