use crate::Certificate;
use quinn::ClientConfig as QuicClientConfig;
use quinn::ServerConfig as QuicServerConfig;
use quinn::TransportConfig;
use rustls::ClientConfig as TlsClientConfig;
use rustls::RootCertStore;
use rustls::ServerConfig as TlsServerConfig;
use std::future::Future;
use std::net::IpAddr;
use std::net::Ipv4Addr;
use std::net::Ipv6Addr;
use std::net::SocketAddr;
use std::net::SocketAddrV6;
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
use wtransport_proto::WEBTRANSPORT_ALPN;
#[derive(Debug, Copy, Clone)]
pub enum IpBindConfig {
LocalV4,
LocalV6,
LocalDual,
InAddrAnyV4,
InAddrAnyV6,
InAddrAnyDual,
}
impl IpBindConfig {
fn into_ip(self) -> IpAddr {
match self {
IpBindConfig::LocalV4 => Ipv4Addr::LOCALHOST.into(),
IpBindConfig::LocalV6 => Ipv6Addr::LOCALHOST.into(),
IpBindConfig::LocalDual => Ipv6Addr::LOCALHOST.into(),
IpBindConfig::InAddrAnyV4 => Ipv4Addr::UNSPECIFIED.into(),
IpBindConfig::InAddrAnyV6 => Ipv6Addr::UNSPECIFIED.into(),
IpBindConfig::InAddrAnyDual => Ipv6Addr::UNSPECIFIED.into(),
}
}
fn into_dual_stack_config(self) -> Ipv6DualStackConfig {
match self {
IpBindConfig::LocalV4 | IpBindConfig::InAddrAnyV4 => Ipv6DualStackConfig::OsDefault,
IpBindConfig::LocalV6 | IpBindConfig::InAddrAnyV6 => Ipv6DualStackConfig::Deny,
IpBindConfig::LocalDual | IpBindConfig::InAddrAnyDual => Ipv6DualStackConfig::Allow,
}
}
}
#[derive(Debug, Copy, Clone)]
pub enum Ipv6DualStackConfig {
OsDefault,
Deny,
Allow,
}
#[derive(Debug)]
pub struct InvalidIdleTimeout;
pub struct ServerConfig {
pub(crate) bind_address: SocketAddr,
pub(crate) dual_stack_config: Ipv6DualStackConfig,
pub(crate) quic_config: QuicServerConfig,
}
impl ServerConfig {
pub fn builder() -> ServerConfigBuilder<WantsBindAddress> {
ServerConfigBuilder::default()
}
#[cfg(feature = "quinn")]
#[cfg_attr(docsrs, doc(cfg(feature = "quinn")))]
pub fn quic_config(&self) -> &quinn::ServerConfig {
&self.quic_config
}
#[cfg(feature = "quinn")]
#[cfg_attr(docsrs, doc(cfg(feature = "quinn")))]
pub fn quic_config_mut(&mut self) -> &mut quinn::ServerConfig {
&mut self.quic_config
}
}
#[must_use]
pub struct ServerConfigBuilder<State>(State);
impl ServerConfigBuilder<WantsBindAddress> {
pub fn with_bind_default(self, listening_port: u16) -> ServerConfigBuilder<WantsCertificate> {
self.with_bind_config(IpBindConfig::InAddrAnyDual, listening_port)
}
pub fn with_bind_config(
self,
ip_bind_config: IpBindConfig,
listening_port: u16,
) -> ServerConfigBuilder<WantsCertificate> {
let ip_address: IpAddr = ip_bind_config.into_ip();
match ip_address {
IpAddr::V4(ip) => self.with_bind_address(SocketAddr::new(ip.into(), listening_port)),
IpAddr::V6(ip) => self.with_bind_address_v6(
SocketAddrV6::new(ip, listening_port, 0, 0),
ip_bind_config.into_dual_stack_config(),
),
}
}
pub fn with_bind_address(self, address: SocketAddr) -> ServerConfigBuilder<WantsCertificate> {
ServerConfigBuilder(WantsCertificate {
bind_address: address,
dual_stack_config: Ipv6DualStackConfig::OsDefault,
})
}
pub fn with_bind_address_v6(
self,
address: SocketAddrV6,
dual_stack_config: Ipv6DualStackConfig,
) -> ServerConfigBuilder<WantsCertificate> {
ServerConfigBuilder(WantsCertificate {
bind_address: address.into(),
dual_stack_config,
})
}
}
impl ServerConfigBuilder<WantsCertificate> {
pub fn with_certificate(
self,
certificate: Certificate,
) -> ServerConfigBuilder<WantsTransportConfigServer> {
self.with_custom_tls(Self::build_tls_config(certificate))
}
pub fn with_custom_tls(
self,
tls_config: rustls::ServerConfig,
) -> ServerConfigBuilder<WantsTransportConfigServer> {
let transport_config = TransportConfig::default();
ServerConfigBuilder(WantsTransportConfigServer {
bind_address: self.0.bind_address,
dual_stack_config: self.0.dual_stack_config,
tls_config,
transport_config,
migration: true,
})
}
fn build_tls_config(certificate: Certificate) -> TlsServerConfig {
let mut tls_config = TlsServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_single_cert(certificate.certificates, certificate.key)
.expect("Certificate and private key should be already validated");
tls_config.alpn_protocols = [WEBTRANSPORT_ALPN.to_vec()].to_vec();
tls_config
}
}
impl ServerConfigBuilder<WantsTransportConfigServer> {
#[must_use]
pub fn build(self) -> ServerConfig {
let mut quic_config = QuicServerConfig::with_crypto(Arc::new(self.0.tls_config));
quic_config.transport_config(Arc::new(self.0.transport_config));
quic_config.migration(self.0.migration);
ServerConfig {
bind_address: self.0.bind_address,
dual_stack_config: self.0.dual_stack_config,
quic_config,
}
}
pub fn max_idle_timeout(
mut self,
idle_timeout: Option<Duration>,
) -> Result<Self, InvalidIdleTimeout> {
let idle_timeout = idle_timeout
.map(quinn::IdleTimeout::try_from)
.transpose()
.map_err(|_| InvalidIdleTimeout)?;
self.0.transport_config.max_idle_timeout(idle_timeout);
Ok(self)
}
pub fn keep_alive_interval(mut self, interval: Option<Duration>) -> Self {
self.0.transport_config.keep_alive_interval(interval);
self
}
pub fn allow_migration(mut self, value: bool) -> Self {
self.0.migration = value;
self
}
}
pub struct ClientConfig {
pub(crate) bind_address: SocketAddr,
pub(crate) dual_stack_config: Ipv6DualStackConfig,
pub(crate) quic_config: QuicClientConfig,
pub(crate) dns_resolver: Box<dyn DnsResolver + Send + Sync>,
}
impl ClientConfig {
pub fn builder() -> ClientConfigBuilder<WantsBindAddress> {
ClientConfigBuilder::default()
}
#[cfg(feature = "quinn")]
#[cfg_attr(docsrs, doc(cfg(feature = "quinn")))]
pub fn quic_config(&self) -> &quinn::ClientConfig {
&self.quic_config
}
#[cfg(feature = "quinn")]
#[cfg_attr(docsrs, doc(cfg(feature = "quinn")))]
pub fn quic_config_mut(&mut self) -> &mut quinn::ClientConfig {
&mut self.quic_config
}
}
impl Default for ClientConfig {
fn default() -> Self {
ClientConfig::builder()
.with_bind_default()
.with_native_certs()
.build()
}
}
#[must_use]
pub struct ClientConfigBuilder<State>(State);
impl ClientConfigBuilder<WantsBindAddress> {
pub fn with_bind_default(self) -> ClientConfigBuilder<WantsRootStore> {
self.with_bind_config(IpBindConfig::InAddrAnyDual)
}
pub fn with_bind_config(
self,
ip_bind_config: IpBindConfig,
) -> ClientConfigBuilder<WantsRootStore> {
let ip_address: IpAddr = ip_bind_config.into_ip();
match ip_address {
IpAddr::V4(ip) => self.with_bind_address(SocketAddr::new(ip.into(), 0)),
IpAddr::V6(ip) => self.with_bind_address_v6(
SocketAddrV6::new(ip, 0, 0, 0),
ip_bind_config.into_dual_stack_config(),
),
}
}
pub fn with_bind_address(self, address: SocketAddr) -> ClientConfigBuilder<WantsRootStore> {
ClientConfigBuilder(WantsRootStore {
bind_address: address,
dual_stack_config: Ipv6DualStackConfig::OsDefault,
})
}
pub fn with_bind_address_v6(
self,
address: SocketAddrV6,
dual_stack_config: Ipv6DualStackConfig,
) -> ClientConfigBuilder<WantsRootStore> {
ClientConfigBuilder(WantsRootStore {
bind_address: address.into(),
dual_stack_config,
})
}
}
impl ClientConfigBuilder<WantsRootStore> {
pub fn with_native_certs(self) -> ClientConfigBuilder<WantsTransportConfigClient> {
self.with_custom_tls(Self::build_tls_config(Self::native_cert_store()))
}
pub fn with_custom_tls(
self,
tls_config: rustls::ClientConfig,
) -> ClientConfigBuilder<WantsTransportConfigClient> {
let transport_config = TransportConfig::default();
ClientConfigBuilder(WantsTransportConfigClient {
bind_address: self.0.bind_address,
dual_stack_config: self.0.dual_stack_config,
tls_config,
transport_config,
dns_resolver: Box::<TokioDnsResolver>::default(),
})
}
#[cfg(feature = "dangerous-configuration")]
#[cfg_attr(docsrs, doc(cfg(feature = "dangerous-configuration")))]
pub fn with_no_cert_validation(self) -> ClientConfigBuilder<WantsTransportConfigClient> {
let mut tls_config = Self::build_tls_config(RootCertStore::empty());
tls_config
.dangerous()
.set_certificate_verifier(Arc::new(dangerous_configuration::NoServerVerification));
let transport_config = TransportConfig::default();
ClientConfigBuilder(WantsTransportConfigClient {
bind_address: self.0.bind_address,
dual_stack_config: self.0.dual_stack_config,
tls_config,
transport_config,
dns_resolver: Box::<TokioDnsResolver>::default(),
})
}
fn native_cert_store() -> RootCertStore {
let mut root_store = RootCertStore::empty();
let _var_restore_guard = utils::remove_var_tmp("SSL_CERT_FILE");
match rustls_native_certs::load_native_certs() {
Ok(certs) => {
for c in certs {
let _ = root_store.add(&rustls::Certificate(c.0));
}
}
Err(_error) => {}
}
root_store
}
fn build_tls_config(root_store: RootCertStore) -> TlsClientConfig {
let mut config = TlsClientConfig::builder()
.with_safe_default_cipher_suites()
.with_safe_default_kx_groups()
.with_safe_default_protocol_versions()
.expect("Safe protocols should not error")
.with_root_certificates(root_store)
.with_no_client_auth();
config.alpn_protocols = [WEBTRANSPORT_ALPN.to_vec()].to_vec();
config
}
}
impl ClientConfigBuilder<WantsTransportConfigClient> {
#[must_use]
pub fn build(self) -> ClientConfig {
let mut quic_config = QuicClientConfig::new(Arc::new(self.0.tls_config));
quic_config.transport_config(Arc::new(self.0.transport_config));
ClientConfig {
bind_address: self.0.bind_address,
dual_stack_config: self.0.dual_stack_config,
quic_config,
dns_resolver: self.0.dns_resolver,
}
}
pub fn max_idle_timeout(
mut self,
idle_timeout: Option<Duration>,
) -> Result<Self, InvalidIdleTimeout> {
let idle_timeout = idle_timeout
.map(quinn::IdleTimeout::try_from)
.transpose()
.map_err(|_| InvalidIdleTimeout)?;
self.0.transport_config.max_idle_timeout(idle_timeout);
Ok(self)
}
pub fn keep_alive_interval(mut self, interval: Option<Duration>) -> Self {
self.0.transport_config.keep_alive_interval(interval);
self
}
pub fn dns_resolver(mut self, dns_resolver: Box<dyn DnsResolver + Send + Sync>) -> Self {
self.0.dns_resolver = dns_resolver;
self
}
}
impl Default for ServerConfigBuilder<WantsBindAddress> {
fn default() -> Self {
Self(WantsBindAddress {})
}
}
impl Default for ClientConfigBuilder<WantsBindAddress> {
fn default() -> Self {
Self(WantsBindAddress {})
}
}
pub struct WantsBindAddress {}
pub struct WantsCertificate {
bind_address: SocketAddr,
dual_stack_config: Ipv6DualStackConfig,
}
pub struct WantsRootStore {
bind_address: SocketAddr,
dual_stack_config: Ipv6DualStackConfig,
}
pub struct WantsTransportConfigServer {
bind_address: SocketAddr,
dual_stack_config: Ipv6DualStackConfig,
tls_config: TlsServerConfig,
transport_config: quinn::TransportConfig,
migration: bool,
}
pub struct WantsTransportConfigClient {
bind_address: SocketAddr,
dual_stack_config: Ipv6DualStackConfig,
tls_config: TlsClientConfig,
transport_config: quinn::TransportConfig,
dns_resolver: Box<dyn DnsResolver + Send + Sync>,
}
#[cfg(feature = "dangerous-configuration")]
mod dangerous_configuration {
use rustls::client::ServerCertVerified;
use rustls::client::ServerCertVerifier;
pub(super) struct NoServerVerification;
impl ServerCertVerifier for NoServerVerification {
fn verify_server_cert(
&self,
_end_entity: &rustls::Certificate,
_intermediates: &[rustls::Certificate],
_server_name: &rustls::ServerName,
_scts: &mut dyn Iterator<Item = &[u8]>,
_ocsp_response: &[u8],
_now: std::time::SystemTime,
) -> Result<ServerCertVerified, rustls::Error> {
Ok(ServerCertVerified::assertion())
}
}
}
pub type DynFutureResolver = dyn Future<Output = std::io::Result<Option<SocketAddr>>> + Send;
pub trait DnsResolver {
fn resolve(&self, host: &str) -> Pin<Box<DynFutureResolver>>;
}
#[derive(Default)]
pub struct TokioDnsResolver;
impl DnsResolver for TokioDnsResolver {
fn resolve(&self, host: &str) -> Pin<Box<DynFutureResolver>> {
let host = host.to_string();
Box::pin(async move { Ok(tokio::net::lookup_host(host).await?.next()) })
}
}
mod utils {
use std::env;
use std::ffi::OsStr;
use std::ffi::OsString;
pub struct VarRestoreGuard {
key: OsString,
value: Option<OsString>,
}
impl Drop for VarRestoreGuard {
fn drop(&mut self) {
if let Some(value) = self.value.take() {
env::set_var(self.key.clone(), value);
}
}
}
pub fn remove_var_tmp<K: AsRef<OsStr>>(key: K) -> VarRestoreGuard {
let value = env::var_os(key.as_ref());
env::remove_var(key.as_ref());
VarRestoreGuard {
key: key.as_ref().to_os_string(),
value,
}
}
}