use anyhow::{Context, Error, Result};
use quinn::crypto::rustls::QuicServerConfig;
use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
use std::{fs, net::SocketAddr, path::PathBuf, sync::Arc, time::Instant};
use tracing::{debug, info, instrument, warn};
use crate::{
quic::{configure_transport_config, ALPN_QUIC_PORTREDIRECT},
};
#[derive(Debug)]
pub struct ServerConfig<AppDataType> {
pub cert_hostname: String,
pub cert_file: PathBuf,
pub key_file: PathBuf,
pub listen: SocketAddr,
pub stateless_retry: bool,
pub connection_limit: Option<usize>,
pub app_data: AppDataType,
}
impl<AppDataType> ServerConfig<AppDataType> {
pub fn create_default_config(
config_dir: PathBuf,
cert_alt_name: String,
bind_socket: SocketAddr,
connection_limit: Option<usize>,
app_data: AppDataType,
) -> Self {
ServerConfig {
cert_hostname: cert_alt_name,
cert_file: config_dir.join("cert.der"),
key_file: config_dir.join("key.der"),
listen: bind_socket,
stateless_retry: true, connection_limit,
app_data,
}
}
}
#[instrument()]
pub fn load_or_generate_quic_cert(
cert_alt_name: String,
key_path: PathBuf,
cert_path: PathBuf,
) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)> {
if key_path.exists() && cert_path.exists() {
load_quic_cert(key_path, cert_path)
} else {
generate_quic_cert(cert_alt_name, key_path, cert_path)
}
}
#[instrument()]
pub fn load_quic_cert(
key_path: PathBuf,
cert_path: PathBuf,
) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)> {
let key = fs::read(key_path.clone()).context("failed to read private key")?;
let key = if key_path.extension().is_some_and(|x| x == "der") {
PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(key))
} else {
rustls_pemfile::private_key(&mut &*key)
.context("malformed PKCS #1 private key")?
.ok_or_else(|| anyhow::Error::msg("no private keys found"))?
};
let cert_chain = fs::read(cert_path.clone()).context("failed to read certificate chain")?;
let cert_chain = if cert_path.extension().is_some_and(|x| x == "der") {
vec![CertificateDer::from(cert_chain)]
} else {
rustls_pemfile::certs(&mut &*cert_chain)
.collect::<Result<_, _>>()
.context("invalid PEM-encoded certificate")?
};
Ok((cert_chain, key))
}
#[instrument()]
pub fn generate_quic_cert(
cert_alt_name: String,
key_path: PathBuf,
cert_path: PathBuf,
) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)> {
info!("generating self-signed certificate");
let cert = rcgen::generate_simple_self_signed(vec![cert_alt_name]).unwrap();
let key = PrivatePkcs8KeyDer::from(cert.key_pair.serialize_der());
let cert = CertificateDer::from(cert.cert);
fs::write(&cert_path, &cert).context("failed to write certificate")?;
fs::write(&key_path, key.secret_pkcs8_der()).context("failed to write private key")?;
Ok((vec![cert], key.into()))
}
#[instrument(skip(config, handle_incoming_client))]
pub async fn run_quic_server<F, Fut, AppDataType>(
config: ServerConfig<AppDataType>,
handle_incoming_client: F,
) -> Result<()>
where
F: Fn(Arc<ServerConfig<AppDataType>>, quinn::Connection) -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = Result<(), Error>> + Send + 'static,
{
info!("Starting PR QUIC server setup");
let (cert_chain, key_der) = load_or_generate_quic_cert(
config.cert_hostname.clone(),
config.key_file.clone(),
config.cert_file.clone(),
)
.context("loading or generating cert")?;
info!(
"Configuring rustls server ({} certs, key: {:?})",
cert_chain.len(),
key_der
);
let mut server_crypto = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(cert_chain, key_der)
.context("rustls ServerConfig builder")?;
server_crypto.alpn_protocols = ALPN_QUIC_PORTREDIRECT.iter().map(|&x| x.into()).collect();
let mut server_config =
quinn::ServerConfig::with_crypto(Arc::new(QuicServerConfig::try_from(server_crypto)?));
let transport_config = Arc::get_mut(&mut server_config.transport).unwrap();
configure_transport_config(transport_config);
info!(listen_addr = %config.listen, "Binding QUIC endpoint");
let endpoint = quinn::Endpoint::server(server_config, config.listen)?;
let start = Instant::now();
let config = Arc::from(config);
info!("QUIC server is ready and accepting connections");
while let Some(conn) = endpoint.accept().await {
if config
.connection_limit
.is_some_and(|n| endpoint.open_connections() >= n)
{
warn!(
"Refusing connection: open connection limit ({}) reached",
config.connection_limit.unwrap()
);
conn.refuse();
} else if config.stateless_retry && !conn.remote_address_validated() {
info!(
"Requiring connection from {} to validate its address",
conn.remote_address()
);
conn.retry().unwrap();
} else {
let peer_info = format!(
"client: {} (validated: {})",
conn.remote_address(),
conn.remote_address_validated()
);
let connection = conn
.await
.context("accepting incoming quic client connection")?;
debug!(peer = %peer_info, "Accepting new QUIC client connection at {:?}", start.elapsed());
let fut = handle_incoming_client(Arc::clone(&config), connection);
tokio::spawn(async move {
if let Err(e) = fut.await {
warn!(
"Incoming connection dropped: {reason}",
reason = e.to_string()
)
}
});
}
}
info!("PR QUIC server terminated after {:?}.", start.elapsed());
Ok(())
}