use std::io::{self, Read, Write};
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use quinn::crypto::rustls::{QuicClientConfig, QuicServerConfig};
use quinn::{
ClientConfig, Connection, Endpoint, EndpointConfig, IdleTimeout, RecvStream, SendStream,
ServerConfig, TokioRuntime, TransportConfig,
};
use super::{Identity, PinPolicy};
pub use quinn::Endpoint as QuicEndpoint;
const IDLE_TIMEOUT: Duration = Duration::from_secs(300);
static RUNTIME: std::sync::OnceLock<tokio::runtime::Runtime> = std::sync::OnceLock::new();
fn runtime() -> &'static tokio::runtime::Runtime {
RUNTIME.get_or_init(|| {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.enable_all()
.build()
.expect("tokio runtime for quinn (D3: transport edges only)")
})
}
fn block_on<F: std::future::Future>(f: F) -> F::Output {
runtime().block_on(f)
}
pub struct QuicStream {
recv: RecvStream,
send: SendStream,
}
impl Read for QuicStream {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match block_on(self.recv.read(buf)) {
Ok(Some(n)) => Ok(n),
Ok(None) => Ok(0),
Err(quinn::ReadError::ConnectionLost(e)) => Err(quic_conn_err(e)),
Err(e) => Err(io::Error::other(format!("QUIC read failed: {e}"))),
}
}
}
impl Write for QuicStream {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match block_on(self.send.write(buf)) {
Ok(n) => Ok(n),
Err(quinn::WriteError::ConnectionLost(e)) => Err(quic_conn_err(e)),
Err(e) => Err(io::Error::other(format!("QUIC write failed: {e}"))),
}
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
fn quic_conn_err(e: quinn::ConnectionError) -> io::Error {
io::Error::other(format!("QUIC connection failed: {e}"))
}
fn quic_connect_err(e: quinn::ConnectError) -> io::Error {
io::Error::other(format!("QUIC connect failed: {e}"))
}
fn transport_config() -> TransportConfig {
let mut t = TransportConfig::default();
t.max_idle_timeout(Some(
IdleTimeout::try_from(IDLE_TIMEOUT).expect("idle timeout"),
));
t
}
pub struct QuicSession {
connection: Connection,
}
impl Clone for QuicSession {
fn clone(&self) -> Self {
Self {
connection: self.connection.clone(),
}
}
}
impl QuicSession {
pub fn remote_address(&self) -> SocketAddr {
self.connection.remote_address()
}
pub fn open_stream(&self) -> io::Result<QuicStream> {
let (send, recv) = block_on(self.connection.open_bi())
.map_err(|e| io::Error::other(format!("QUIC stream open failed: {e}")))?;
Ok(QuicStream { recv, send })
}
pub fn accept_stream(&self) -> io::Result<QuicStream> {
let (send, recv) = block_on(self.connection.accept_bi())
.map_err(|e| io::Error::other(format!("QUIC stream accept failed: {e}")))?;
Ok(QuicStream { recv, send })
}
pub fn wait_for_peer_close(&self, bound: Duration) {
let _ = block_on(async { tokio::time::timeout(bound, self.connection.closed()).await });
}
}
fn client_endpoint() -> io::Result<Endpoint> {
block_on(async {
let socket = std::net::UdpSocket::bind("0.0.0.0:0")?;
Endpoint::new(
EndpointConfig::default(),
None,
socket,
Arc::new(TokioRuntime),
)
})
}
fn server_endpoint(
bind: SocketAddr,
identity: &Identity,
pins: &PinPolicy,
) -> io::Result<Endpoint> {
block_on(async {
let socket = std::net::UdpSocket::bind(bind)?;
let tls = identity.quinn_server_config(pins)?;
let mut server_config = ServerConfig::with_crypto(Arc::new(tls));
server_config.transport_config(Arc::new(transport_config()));
Endpoint::new(
EndpointConfig::default(),
Some(server_config),
socket,
Arc::new(TokioRuntime),
)
})
}
const CONNECT_TIMEOUT: Duration = Duration::from_secs(30);
pub fn client_session(
identity: &Identity,
pins: &PinPolicy,
server_addr: SocketAddr,
server_name: rustls::pki_types::ServerName<'static>,
) -> io::Result<(QuicSession, QuicStream, Vec<u8>)> {
let mut endpoint = client_endpoint()?;
let tls = identity.quinn_client_config(pins)?;
let mut client_config = ClientConfig::new(Arc::new(
QuicClientConfig::try_from(tls)
.map_err(|e| io::Error::other(format!("QUIC client config: {e}")))?,
));
client_config.transport_config(Arc::new(transport_config()));
endpoint.set_default_client_config(client_config);
let name = server_name.to_str().into_owned();
let connecting = block_on(async {
endpoint
.connect(server_addr, name.as_str())
.map_err(quic_connect_err)
})?;
let connection = block_on(async { tokio::time::timeout(CONNECT_TIMEOUT, connecting).await })
.map_err(|_| {
io::Error::other(format!(
"QUIC handshake timeout after {} s (is the receiver running, and is UDP \
open? QUIC needs UDP; try TCP (default) if the path blocks UDP)",
CONNECT_TIMEOUT.as_secs()
))
})?
.map_err(quic_conn_err)?;
let presented = peer_fingerprint(&connection)?;
let (send, recv) = block_on(connection.open_bi())
.map_err(|e| io::Error::other(format!("QUIC stream open failed: {e}")))?;
Ok((
QuicSession { connection },
QuicStream { recv, send },
presented,
))
}
pub fn server_session(endpoint: Endpoint) -> io::Result<(QuicSession, QuicStream, Vec<u8>)> {
let incoming = block_on(async {
endpoint
.accept()
.await
.ok_or_else(|| io::Error::other("QUIC endpoint closed"))
})?;
let connecting = block_on(async { incoming.accept() }).map_err(quic_conn_err)?;
let connection = block_on(connecting).map_err(quic_conn_err)?;
drop(endpoint);
let presented = peer_fingerprint(&connection)?;
let (send, recv) = block_on(connection.accept_bi())
.map_err(|e| io::Error::other(format!("QUIC stream accept failed: {e}")))?;
Ok((
QuicSession { connection },
QuicStream { recv, send },
presented,
))
}
pub fn listen(bind: SocketAddr, identity: &Identity, pins: &PinPolicy) -> io::Result<Endpoint> {
server_endpoint(bind, identity, pins)
}
fn peer_fingerprint(connection: &Connection) -> io::Result<Vec<u8>> {
let identity = connection
.peer_identity()
.ok_or_else(|| io::Error::other("peer presented no certificate"))?;
let certs = identity
.downcast::<Vec<rustls::pki_types::CertificateDer<'static>>>()
.map_err(|_| io::Error::other("unexpected peer identity type"))?;
certs
.first()
.map(|c| super::sha256(c.as_ref()))
.ok_or_else(|| io::Error::other("peer presented an empty certificate chain"))
}
impl Identity {
pub fn quinn_server_config(&self, pins: &PinPolicy) -> io::Result<QuicServerConfig> {
let tls = self.server_config(pins)?;
QuicServerConfig::try_from(tls)
.map_err(|e| io::Error::other(format!("QUIC server config: {e}")))
}
pub fn quinn_client_config(&self, pins: &PinPolicy) -> io::Result<rustls::ClientConfig> {
self.client_config(pins)
}
}