// Copyright 2015-2019 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use std::future::Future; use std::marker::Unpin; use std::net::{IpAddr, SocketAddr}; #[cfg(feature = "__quic")] use std::net::{Ipv4Addr, Ipv6Addr}; use std::pin::Pin; #[cfg(any(feature = "__tls", feature = "__https"))] use std::sync::Arc; #[cfg(feature = "__https")] use hickory_net::h2::HttpsClientStream; #[cfg(feature = "__tls")] use rustls::DigitallySignedStruct; #[cfg(feature = "__tls")] use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; #[cfg(feature = "__tls")] use rustls::crypto::{CryptoProvider, verify_tls12_signature, verify_tls13_signature}; #[cfg(feature = "__tls")] use rustls::pki_types::{CertificateDer, ServerName, UnixTime}; #[cfg(not(feature = "__tls"))] use tracing::warn; #[cfg(feature = "__h3")] use crate::net::h3::H3ClientStream; #[cfg(feature = "__quic")] use crate::net::quic::QuicClientStream; #[cfg(feature = "__tls")] use crate::net::tls::{client_config, default_provider, tls_exchange_with_bind_addr}; use crate::{ config::{ConnectionConfig, ProtocolConfig}, name_server_pool::PoolContext, net::{ NetError, runtime::RuntimeProvider, tcp::TcpClientStream, udp::UdpClientStream, xfer::{DnsExchange, DnsHandle}, }, }; /// Create `DnsHandle` with the help of `RuntimeProvider`. /// This trait is designed for customization. pub trait ConnectionProvider: 'static + Clone + Send + Sync + Unpin { /// The handle to the connection for sending DNS requests. type Conn: DnsHandle + Clone + Send + Sync + 'static; /// Ths future is responsible for spawning any background tasks as necessary. type FutureConn: Future> + Send + 'static; /// Provider that handles the underlying I/O and timing. type RuntimeProvider: RuntimeProvider; /// Create a new connection. fn new_connection( &self, ip: IpAddr, config: &ConnectionConfig, cx: &PoolContext, ) -> Result; /// Get a reference to a [`RuntimeProvider`]. fn runtime_provider(&self) -> &Self::RuntimeProvider; } impl ConnectionProvider for P { type Conn = DnsExchange

; type FutureConn = Pin> + Send + 'static>>; type RuntimeProvider = P; fn new_connection( &self, ip: IpAddr, config: &ConnectionConfig, cx: &PoolContext, ) -> Result { let remote_addr = SocketAddr::new(ip, config.port); match (&config.protocol, self.quic_binder()) { (ProtocolConfig::Udp, _) => { let (timeout, os_port_selection, avoid_local_udp_ports, bind_addr, provider) = ( cx.options.timeout, cx.options.os_port_selection, cx.options.avoid_local_udp_ports.clone(), config.bind_addr, self.clone(), ); Ok(Box::pin(async move { Ok(UdpClientStream::builder(remote_addr, provider) .with_timeout(Some(timeout)) .with_os_port_selection(os_port_selection) .avoid_local_ports(avoid_local_udp_ports) .with_bind_addr(bind_addr) .exchange()) })) } (ProtocolConfig::Tcp, _) => Ok(Box::pin(TcpClientStream::exchange( remote_addr, config.bind_addr, cx.options.timeout, Some(cx.options.max_active_requests), self.clone(), ))), #[cfg(feature = "__tls")] (ProtocolConfig::Tls { server_name }, _) => { let Ok(server_name) = ServerName::try_from(&**server_name) else { return Err(NetError::from(format!( "invalid server name: {server_name}" ))); }; let server_name = server_name.to_owned(); Ok(Box::pin(tls_exchange_with_bind_addr( remote_addr, config.bind_addr, server_name, cx.tls.clone(), cx.options.timeout, Some(cx.options.max_active_requests), self.clone(), ))) } #[cfg(feature = "__https")] (ProtocolConfig::Https { server_name, path }, _) => { let mut builder = HttpsClientStream::builder(Arc::new(cx.tls.clone()), self.clone()); builder.request_timeout(cx.options.timeout); if let Some(bind_addr) = config.bind_addr { builder.bind_addr(bind_addr); } Ok(Box::pin(builder.exchange( remote_addr, server_name.clone(), path.clone(), ))) } #[cfg(feature = "__quic")] (ProtocolConfig::Quic { server_name }, Some(binder)) => { let bind_addr = config.bind_addr.unwrap_or(match remote_addr { SocketAddr::V4(_) => SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0), SocketAddr::V6(_) => SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0), }); Ok(Box::pin( QuicClientStream::builder() .crypto_config(cx.tls.clone()) .request_timeout(cx.options.timeout) .exchange( binder.bind_quic(bind_addr, remote_addr)?, remote_addr, server_name.clone(), self.clone(), ), )) } #[cfg(feature = "__h3")] ( ProtocolConfig::H3 { server_name, path, disable_grease, }, Some(binder), ) => { let bind_addr = config.bind_addr.unwrap_or(match remote_addr { SocketAddr::V4(_) => SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0), SocketAddr::V6(_) => SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0), }); Ok(Box::pin( H3ClientStream::builder() .crypto_config(cx.tls.clone()) .disable_grease(*disable_grease) .request_timeout(cx.options.timeout) .exchange( binder.bind_quic(bind_addr, remote_addr)?, remote_addr, server_name.clone(), path.clone(), self.clone(), ), )) } #[cfg(feature = "__quic")] (ProtocolConfig::Quic { .. }, None) => { Err(NetError::from("runtime provider does not support QUIC")) } #[cfg(feature = "__h3")] (ProtocolConfig::H3 { .. }, None) => { Err(NetError::from("runtime provider does not support QUIC")) } } } fn runtime_provider(&self) -> &Self::RuntimeProvider { self } } /// TLS configuration for the connection provider. pub struct TlsConfig { /// The TLS configuration to use for secure connections. #[cfg(feature = "__tls")] pub config: rustls::ClientConfig, } impl TlsConfig { /// Create a new `TlsConfig` with default settings. pub fn new() -> Result { Ok(Self { #[cfg(feature = "__tls")] config: client_config()?, }) } /// Disable certificate verification. /// /// This is typically unsafe and insecure, except in the context of RFC 9539 opportunistic /// encryption which requires the peer certificate not be verified. #[cfg(feature = "__tls")] pub fn insecure_skip_verify(&mut self) { self.config .dangerous() .set_certificate_verifier(Arc::new(NoCertificateVerification::default())) } /// Disable certificate verification. /// /// This is typically unsafe and insecure, except in the context of RFC 9539 opportunistic /// encryption which requires the peer certificate not be verified. #[cfg(not(feature = "__tls"))] pub fn insecure_skip_verify(&mut self) { warn!("asked to skip TLS verification without TLS support") } } /// A rustls ServerCertVerifier that performs **no** certificate verification. /// /// This should only be used with great care, as skipping certificate verification is insecure /// and could allow person-in-the-middle attacks. #[cfg(feature = "__tls")] #[derive(Debug)] struct NoCertificateVerification(CryptoProvider); #[cfg(feature = "__tls")] impl Default for NoCertificateVerification { fn default() -> Self { Self(default_provider()) } } #[cfg(feature = "__tls")] impl ServerCertVerifier for NoCertificateVerification { fn verify_server_cert( &self, _end_entity: &CertificateDer<'_>, _intermediates: &[CertificateDer<'_>], _server_name: &ServerName<'_>, _ocsp: &[u8], _now: UnixTime, ) -> Result { Ok(ServerCertVerified::assertion()) } fn verify_tls12_signature( &self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct, ) -> Result { verify_tls12_signature( message, cert, dss, &self.0.signature_verification_algorithms, ) } fn verify_tls13_signature( &self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct, ) -> Result { verify_tls13_signature( message, cert, dss, &self.0.signature_verification_algorithms, ) } fn supported_verify_schemes(&self) -> Vec { self.0.signature_verification_algorithms.supported_schemes() } } #[cfg(all( test, feature = "tokio", any(feature = "webpki-roots", feature = "rustls-platform-verifier"), any( feature = "__tls", feature = "__https", feature = "__quic", feature = "__h3" ) ))] mod tests { #[cfg(feature = "__quic")] use std::net::IpAddr; use test_support::subscribe; use crate::TokioResolver; #[cfg(any(feature = "__tls", feature = "__https"))] use crate::config::CLOUDFLARE; #[cfg(any( feature = "__tls", feature = "__https", feature = "__quic", feature = "__h3" ))] use crate::config::GOOGLE; use crate::config::ResolverConfig; #[cfg(feature = "__quic")] use crate::config::ServerGroup; #[cfg(feature = "__quic")] use crate::config::ServerOrderingStrategy; use crate::net::runtime::TokioRuntimeProvider; #[cfg(feature = "__quic")] use crate::net::tls::client_config; #[cfg(feature = "__h3")] #[tokio::test] async fn test_google_h3() { subscribe(); h3_test(ResolverConfig::h3(&GOOGLE)).await } #[cfg(feature = "__h3")] async fn h3_test(config: ResolverConfig) { let mut builder = TokioResolver::builder_with_config(config, TokioRuntimeProvider::default()); // Prefer IPv4 addresses for this test. builder.options_mut().server_ordering_strategy = ServerOrderingStrategy::UserProvidedOrder; let resolver = builder.build().unwrap(); let response = resolver .lookup_ip("www.example.com.") .await .expect("failed to run lookup"); assert_ne!(response.iter().count(), 0); // check if there is another connection created let response = resolver .lookup_ip("www.example.com.") .await .expect("failed to run lookup"); assert_ne!(response.iter().count(), 0); } #[cfg(feature = "__quic")] #[tokio::test] async fn test_adguard_quic() { subscribe(); // AdGuard requires SNI. let config = client_config().unwrap(); let group = ServerGroup { ips: &[ IpAddr::from([94, 140, 14, 140]), IpAddr::from([94, 140, 14, 141]), IpAddr::from([0x2a10, 0x50c0, 0, 0, 0, 0, 0x1, 0xff]), IpAddr::from([0x2a10, 0x50c0, 0, 0, 0, 0, 0x2, 0xff]), ], server_name: "unfiltered.adguard-dns.com", path: "/dns-query", }; quic_test(ResolverConfig::quic(&group), config).await } #[cfg(feature = "__quic")] async fn quic_test(config: ResolverConfig, tls_config: rustls::ClientConfig) { let mut resolver_builder = TokioResolver::builder_with_config(config, TokioRuntimeProvider::default()); resolver_builder.options_mut().try_tcp_on_error = true; // Prefer IPv4 addresses for this test. resolver_builder.options_mut().server_ordering_strategy = ServerOrderingStrategy::UserProvidedOrder; resolver_builder = resolver_builder.with_tls_config(tls_config); let resolver = resolver_builder.build().unwrap(); let response = resolver .lookup_ip("www.example.com.") .await .expect("failed to run lookup"); assert_ne!(response.iter().count(), 0); // check if there is another connection created let response = resolver .lookup_ip("www.example.com.") .await .expect("failed to run lookup"); assert_ne!(response.iter().count(), 0); } #[cfg(feature = "__https")] #[tokio::test] async fn test_google_https() { subscribe(); https_test(ResolverConfig::https(&GOOGLE)).await } #[cfg(feature = "__https")] #[tokio::test] async fn test_cloudflare_https() { subscribe(); https_test(ResolverConfig::https(&CLOUDFLARE)).await } #[cfg(feature = "__https")] async fn https_test(config: ResolverConfig) { let mut resolver_builder = TokioResolver::builder_with_config(config, TokioRuntimeProvider::default()); resolver_builder.options_mut().try_tcp_on_error = true; let resolver = resolver_builder.build().unwrap(); let response = resolver .lookup_ip("www.example.com.") .await .expect("failed to run lookup"); assert_ne!(response.iter().count(), 0); // check if there is another connection created let response = resolver .lookup_ip("www.example.com.") .await .expect("failed to run lookup"); assert_ne!(response.iter().count(), 0); } #[cfg(feature = "__tls")] #[tokio::test] async fn test_google_tls() { subscribe(); tls_test(ResolverConfig::tls(&GOOGLE)).await } #[cfg(feature = "__tls")] #[tokio::test] async fn test_cloudflare_tls() { subscribe(); tls_test(ResolverConfig::tls(&CLOUDFLARE)).await } #[cfg(feature = "__tls")] async fn tls_test(config: ResolverConfig) { let mut resolver_builder = TokioResolver::builder_with_config(config, TokioRuntimeProvider::default()); resolver_builder.options_mut().try_tcp_on_error = true; let resolver = resolver_builder.build().unwrap(); let response = resolver .lookup_ip("www.example.com.") .await .expect("failed to run lookup"); assert_ne!(response.iter().count(), 0); } }