// Copyright 2015-2017 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. //! Configuration for a resolver #![allow(clippy::use_self)] use std::collections::HashSet; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; #[cfg(all( feature = "recursor", feature = "toml", feature = "serde", any(feature = "__tls", feature = "__quic") ))] use std::{fs, io}; use ipnet::IpNet; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use tracing::warn; #[cfg(all( feature = "recursor", feature = "toml", feature = "serde", any(feature = "__tls", feature = "__quic") ))] use tracing::{debug, info}; #[cfg(all( feature = "recursor", feature = "toml", feature = "serde", any(feature = "__tls", feature = "__quic") ))] use crate::name_server_pool::NameServerTransportState; #[cfg(any(feature = "__https", feature = "__h3"))] use crate::net::http::DEFAULT_DNS_QUERY_PATH; use crate::net::xfer::Protocol; use crate::proto::access_control::{AccessControlSet, AccessControlSetBuilder}; use crate::proto::op::DEFAULT_MAX_PAYLOAD_LEN; use crate::proto::rr::Name; /// Configuration for the upstream nameservers to use for resolution. /// /// The `Default` implementation of this struct will be removed in a future version. Use /// [`Self::from_name_servers()`] instead. Note that a `ResolverConfig` with no name servers will /// produce a nonfunctional resolver. #[non_exhaustive] #[derive(Clone, Debug, Default)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct ResolverConfig { /// Base search domain #[cfg_attr(feature = "serde", serde(default))] pub domain: Option, /// Search domains #[cfg_attr(feature = "serde", serde(default))] pub search: Vec, /// Name servers to use for resolution pub name_servers: Vec, } impl ResolverConfig { /// Create a new `ResolverConfig` from [`ServerGroup`] configuration. /// /// Connects via UDP and TCP. pub fn udp_and_tcp(config: &ServerGroup<'_>) -> Self { Self { // TODO: this should get the hostname and use the basename as the default domain: None, search: vec![], name_servers: config.udp_and_tcp().collect(), } } /// Create a new `ResolverConfig` from [`ServerGroup`] configuration. /// /// Only connects via TLS. #[cfg(feature = "__tls")] pub fn tls(config: &ServerGroup<'_>) -> Self { Self { // TODO: this should get the hostname and use the basename as the default domain: None, search: vec![], name_servers: config.tls().collect(), } } /// Create a new `ResolverConfig` from [`ServerGroup`] configuration. /// /// Only connects via HTTPS (HTTP/2). #[cfg(feature = "__https")] pub fn https(config: &ServerGroup<'_>) -> Self { Self { // TODO: this should get the hostname and use the basename as the default domain: None, search: vec![], name_servers: config.https().collect(), } } /// Create a new `ResolverConfig` from [`ServerGroup`] configuration. /// /// Only connects via QUIC. #[cfg(feature = "__quic")] pub fn quic(config: &ServerGroup<'_>) -> Self { Self { // TODO: this should get the hostname and use the basename as the default domain: None, search: vec![], name_servers: config.quic().collect(), } } /// Create a new `ResolverConfig` from [`ServerGroup`] configuration. /// /// Only connects via HTTP/3. #[cfg(feature = "__h3")] pub fn h3(config: &ServerGroup<'_>) -> Self { Self { // TODO: this should get the hostname and use the basename as the default domain: None, search: vec![], name_servers: config.h3().collect(), } } /// Create a ResolverConfig with all parts specified /// /// # Arguments /// /// * `domain` - domain of the entity querying results. If the `Name` being looked up is not an FQDN, then this is the first part appended to attempt a lookup. `ndots` in the `ResolverOption` does take precedence over this. /// * `search` - additional search domains that are attempted if the `Name` is not found in `domain`, defaults to `vec![]` /// * `name_servers` - set of name servers to use for lookups pub fn from_parts( domain: Option, search: Vec, name_servers: Vec, ) -> Self { Self { domain, search, name_servers, } } /// Create a ResolverConfig from a list of name server configurations. /// /// No base domain for relative names will be set, and no additional search domains will be set. pub fn from_name_servers(name_servers: Vec) -> Self { Self::from_parts(None, vec![], name_servers) } /// Take the `domain`, `search`, and `name_servers` from the config. pub fn into_parts(self) -> (Option, Vec, Vec) { (self.domain, self.search, self.name_servers) } /// Returns the local domain /// /// By default any names will be appended to all non-fully-qualified-domain names, and searched for after any ndots rules pub fn domain(&self) -> Option<&Name> { self.domain.as_ref() } /// Set the domain of the entity querying results. pub fn set_domain(&mut self, domain: Name) { self.domain = Some(domain.clone()); self.search = vec![domain]; } /// Returns the search domains /// /// These will be queried after any local domain and then in the order of the set of search domains pub fn search(&self) -> &[Name] { &self.search } /// Add a search domain pub fn add_search(&mut self, search: Name) { self.search.push(search) } // TODO: consider allowing options per NameServer... like different timeouts? /// Add the configuration for a name server pub fn add_name_server(&mut self, name_server: NameServerConfig) { self.name_servers.push(name_server); } /// Returns a reference to the name servers pub fn name_servers(&self) -> &[NameServerConfig] { &self.name_servers } } /// Configuration for the NameServer #[derive(Clone, Debug)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(deny_unknown_fields) )] #[non_exhaustive] pub struct NameServerConfig { /// The address which the DNS NameServer is registered at. pub ip: IpAddr, /// Whether to trust `NXDOMAIN` responses from upstream nameservers. /// /// When this is `true`, and an empty `NXDOMAIN` response with an empty answers set is /// received, the query will not be retried against other configured name servers. /// /// (On a response with any other error response code, the query will still be retried /// regardless of this configuration setting.) /// /// Defaults to `true`. #[cfg_attr(feature = "serde", serde(default = "default_trust_negative_responses"))] pub trust_negative_responses: bool, /// Connection protocols configured for this server. pub connections: Vec, } impl NameServerConfig { /// Constructs a nameserver configuration with a UDP and TCP connections pub fn udp_and_tcp(ip: IpAddr) -> Self { Self { ip, trust_negative_responses: true, connections: vec![ConnectionConfig::udp(), ConnectionConfig::tcp()], } } /// Constructs a nameserver configuration with a single UDP connection pub fn udp(ip: IpAddr) -> Self { Self { ip, trust_negative_responses: true, connections: vec![ConnectionConfig::udp()], } } /// Constructs a nameserver configuration with a single TCP connection pub fn tcp(ip: IpAddr) -> Self { Self { ip, trust_negative_responses: true, connections: vec![ConnectionConfig::tcp()], } } /// Constructs a nameserver configuration with a single TLS connection #[cfg(feature = "__tls")] pub fn tls(ip: IpAddr, server_name: Arc) -> Self { Self { ip, trust_negative_responses: true, connections: vec![ConnectionConfig::tls(server_name)], } } /// Constructs a nameserver configuration with a single HTTP/2 connection #[cfg(feature = "__https")] pub fn https(ip: IpAddr, server_name: Arc, path: Option>) -> Self { Self { ip, trust_negative_responses: true, connections: vec![ConnectionConfig::https(server_name, path)], } } /// Constructs a nameserver configuration with a single QUIC connection #[cfg(feature = "__quic")] pub fn quic(ip: IpAddr, server_name: Arc) -> Self { Self { ip, trust_negative_responses: true, connections: vec![ConnectionConfig::quic(server_name)], } } /// Constructs a nameserver configuration with a single HTTP/3 connection #[cfg(feature = "__h3")] pub fn h3(ip: IpAddr, server_name: Arc, path: Option>) -> Self { Self { ip, trust_negative_responses: true, connections: vec![ConnectionConfig::h3(server_name, path)], } } /// Constructs a nameserver configuration for opportunistic encryption. /// /// This will include configurations for plaintext UDP/TCP as well as DNS-over-TLS and/or /// DNS-over-QUIC depending on feature flag support. /// /// Notably, the TLS and QUIC configurations will **not** verify peer certificates, in /// keeping with RFC 9539's requirement. See [RFC 9539 §4.6.3.4] for more information. /// /// [RFC 9539 §4.6.3.4]: https://www.rfc-editor.org/rfc/rfc9539.html#section-4.6.3.4 #[cfg(any(feature = "__tls", feature = "__quic"))] pub fn opportunistic_encryption(ip: IpAddr) -> Self { Self { ip, trust_negative_responses: true, connections: vec![ ConnectionConfig::udp(), ConnectionConfig::tcp(), #[cfg(feature = "__tls")] ConnectionConfig::tls(Arc::from(ip.to_string())), #[cfg(feature = "__quic")] ConnectionConfig::quic(Arc::from(ip.to_string())), ], } } /// Create a new [`NameServerConfig`] from its constituent parts. pub fn new( ip: IpAddr, trust_negative_responses: bool, connections: Vec, ) -> Self { Self { ip, trust_negative_responses, connections, } } } #[cfg(feature = "serde")] fn default_trust_negative_responses() -> bool { true } /// Configuration for a connection to a nameserver #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(Serialize))] #[non_exhaustive] pub struct ConnectionConfig { /// The remote port to connect to pub port: u16, /// The protocol to use for the connection pub protocol: ProtocolConfig, /// The client address (IP and port) to use for connecting to the server pub bind_addr: Option, } impl ConnectionConfig { /// Constructs a new ConnectionConfig for UDP pub fn udp() -> Self { Self::new(ProtocolConfig::Udp) } /// Constructs a new ConnectionConfig for TCP pub fn tcp() -> Self { Self::new(ProtocolConfig::Tcp) } /// Constructs a new ConnectionConfig for TLS #[cfg(feature = "__tls")] pub fn tls(server_name: Arc) -> Self { Self::new(ProtocolConfig::Tls { server_name }) } /// Constructs a new ConnectionConfig for HTTPS (HTTP/2) #[cfg(feature = "__https")] pub fn https(server_name: Arc, path: Option>) -> Self { Self::new(ProtocolConfig::Https { server_name, path: path.unwrap_or_else(|| Arc::from(DEFAULT_DNS_QUERY_PATH)), }) } /// Constructs a new ConnectionConfig for QUIC #[cfg(feature = "__quic")] pub fn quic(server_name: Arc) -> Self { Self::new(ProtocolConfig::Quic { server_name }) } /// Constructs a new ConnectionConfig for HTTP/3 #[cfg(feature = "__h3")] pub fn h3(server_name: Arc, path: Option>) -> Self { Self::new(ProtocolConfig::H3 { server_name, path: path.unwrap_or_else(|| Arc::from(DEFAULT_DNS_QUERY_PATH)), disable_grease: false, }) } /// Constructs a new ConnectionConfig with the specified [`ProtocolConfig`]. pub fn new(protocol: ProtocolConfig) -> Self { Self { port: protocol.default_port(), protocol, bind_addr: None, } } } #[cfg(feature = "serde")] impl<'de> Deserialize<'de> for ConnectionConfig { fn deserialize>(deserializer: D) -> Result { #[derive(Deserialize)] #[serde(deny_unknown_fields)] struct OptionalParts { #[serde(default)] port: Option, protocol: ProtocolConfig, #[serde(default)] bind_addr: Option, } let parts = OptionalParts::deserialize(deserializer)?; Ok(Self { port: parts.port.unwrap_or_else(|| parts.protocol.default_port()), protocol: parts.protocol, bind_addr: parts.bind_addr, }) } } /// Protocol configuration #[allow(missing_docs)] #[derive(Clone, Debug, Default, PartialEq)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(deny_unknown_fields, rename_all = "snake_case", tag = "type") )] pub enum ProtocolConfig { #[default] Udp, Tcp, #[cfg(feature = "__tls")] Tls { /// The server name to use in the TLS handshake. server_name: Arc, }, #[cfg(feature = "__https")] Https { /// The server name to use in the TLS handshake. server_name: Arc, /// The path (or endpoint) to use for the DNS query. path: Arc, }, #[cfg(feature = "__quic")] Quic { /// The server name to use in the TLS handshake. server_name: Arc, }, #[cfg(feature = "__h3")] H3 { /// The server name to use in the TLS handshake. server_name: Arc, /// The path (or endpoint) to use for the DNS query. path: Arc, /// Whether to disable sending "grease" #[cfg_attr(feature = "serde", serde(default))] disable_grease: bool, }, } impl ProtocolConfig { /// Get the [`Protocol`] for this [`ProtocolConfig`]. pub fn to_protocol(&self) -> Protocol { match self { ProtocolConfig::Udp => Protocol::Udp, ProtocolConfig::Tcp => Protocol::Tcp, #[cfg(feature = "__tls")] ProtocolConfig::Tls { .. } => Protocol::Tls, #[cfg(feature = "__https")] ProtocolConfig::Https { .. } => Protocol::Https, #[cfg(feature = "__quic")] ProtocolConfig::Quic { .. } => Protocol::Quic, #[cfg(feature = "__h3")] ProtocolConfig::H3 { .. } => Protocol::H3, } } /// Returns the TLS server name, if the protocol uses TLS. pub fn server_name(&self) -> Option<&Arc> { match self { ProtocolConfig::Udp | ProtocolConfig::Tcp => None, #[cfg(feature = "__tls")] ProtocolConfig::Tls { server_name } => Some(server_name), #[cfg(feature = "__https")] ProtocolConfig::Https { server_name, .. } => Some(server_name), #[cfg(feature = "__quic")] ProtocolConfig::Quic { server_name } => Some(server_name), #[cfg(feature = "__h3")] ProtocolConfig::H3 { server_name, .. } => Some(server_name), } } /// Default port for the protocol. pub fn default_port(&self) -> u16 { match self { ProtocolConfig::Udp => 53, ProtocolConfig::Tcp => 53, #[cfg(feature = "__tls")] ProtocolConfig::Tls { .. } => 853, #[cfg(feature = "__https")] ProtocolConfig::Https { .. } => 443, #[cfg(feature = "__quic")] ProtocolConfig::Quic { .. } => 853, #[cfg(feature = "__h3")] ProtocolConfig::H3 { .. } => 443, } } } /// Configuration for the Resolver #[derive(Debug, Clone)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(default, deny_unknown_fields) )] #[non_exhaustive] pub struct ResolverOpts { /// Sets the number of dots that must appear (unless it's a final dot representing the root) /// before a query is assumed to include the TLD. The default is one, which means that `www` /// would never be assumed to be a TLD, and would always be appended to either the search #[cfg_attr(feature = "serde", serde(default = "default_ndots"))] pub ndots: usize, /// Specify the timeout for a request. Defaults to 5 seconds #[cfg_attr( feature = "serde", serde(default = "default_timeout", with = "duration") )] pub timeout: Duration, /// Number of retries after lookup failure before giving up. Defaults to 2 #[cfg_attr(feature = "serde", serde(default = "default_attempts"))] pub attempts: usize, /// Enable edns, for larger records pub edns0: bool, /// Use DNSSEC to validate the request #[cfg(feature = "__dnssec")] pub validate: bool, /// The strategy for the Resolver to use when looking up host IP addresses pub ip_strategy: LookupIpStrategy, /// Cache size is in number of responses (some responses can be large) #[cfg_attr(feature = "serde", serde(default = "default_cache_size"))] pub cache_size: u64, /// Check /etc/hosts file before dns requery (only works for unix like OS) pub use_hosts_file: ResolveHosts, /// Optional minimum TTL for positive responses. /// /// If this is set, any positive responses with a TTL lower than this value will have a TTL of /// `positive_min_ttl` instead. Otherwise, this will default to 0 seconds. #[cfg_attr(feature = "serde", serde(with = "duration_opt"))] pub positive_min_ttl: Option, /// Optional minimum TTL for negative (`NXDOMAIN`) responses. /// /// If this is set, any negative responses with a TTL lower than this value will have a TTL of /// `negative_min_ttl` instead. Otherwise, this will default to 0 seconds. #[cfg_attr(feature = "serde", serde(with = "duration_opt"))] pub negative_min_ttl: Option, /// Optional maximum TTL for positive responses. /// /// If this is set, any positive responses with a TTL higher than this value will have a TTL of /// `positive_max_ttl` instead. Otherwise, this will default to [`MAX_TTL`](crate::MAX_TTL) seconds. #[cfg_attr(feature = "serde", serde(with = "duration_opt"))] pub positive_max_ttl: Option, /// Optional maximum TTL for negative (`NXDOMAIN`) responses. /// /// If this is set, any negative responses with a TTL higher than this value will have a TTL of /// `negative_max_ttl` instead. Otherwise, this will default to [`MAX_TTL`](crate::MAX_TTL) seconds. #[cfg_attr(feature = "serde", serde(with = "duration_opt"))] pub negative_max_ttl: Option, /// Number of concurrent requests per query /// /// Where more than one nameserver is configured, this configures the resolver to send queries /// to a number of servers in parallel. Defaults to 2; 0 or 1 will execute requests serially. #[cfg_attr(feature = "serde", serde(default = "default_num_concurrent_reqs"))] pub num_concurrent_reqs: usize, /// Maximum number of active (in-flight) requests per multiplexed connection. /// /// This limits how many DNS queries can be simultaneously pending on a single /// connection to an upstream nameserver. When the limit is reached, new requests /// will return a busy error. /// /// Defaults to 32. Higher values allow more parallelism but consume more memory. #[cfg_attr(feature = "serde", serde(default = "default_max_active_requests"))] pub max_active_requests: usize, /// Preserve all intermediate records in the lookup response, such as CNAME records #[cfg_attr(feature = "serde", serde(default = "default_preserve_intermediates"))] pub preserve_intermediates: bool, /// Try queries over TCP if they fail over UDP. pub try_tcp_on_error: bool, /// The server ordering strategy that the resolver should use. pub server_ordering_strategy: ServerOrderingStrategy, /// Request upstream recursive resolvers to not perform any recursion. /// /// This is true by default, disabling this is useful for requesting single records, but may prevent successful resolution. #[cfg_attr(feature = "serde", serde(default = "default_recursion_desired"))] pub recursion_desired: bool, /// Local UDP ports to avoid when making outgoing queries pub avoid_local_udp_ports: Arc>, /// Request UDP bind ephemeral ports directly from the OS /// /// Boolean parameter to specify whether to use the operating system's standard UDP port /// selection logic instead of Hickory's logic to securely select a random source port. We do /// not recommend using this option unless absolutely necessary, as the operating system may /// select ephemeral ports from a smaller range than Hickory, which can make response poisoning /// attacks easier to conduct. Some operating systems (notably, Windows) might display a /// user-prompt to allow a Hickory-specified port to be used, and setting this option will /// prevent those prompts from being displayed. If os_port_selection is true, avoid_local_udp_ports /// will be ignored. pub os_port_selection: bool, /// Enable case randomization. /// /// Randomize the case of letters in query names, and require that responses preserve the case /// of the query name, in order to mitigate spoofing attacks. This is only applied over UDP. /// /// This implements the mechanism described in /// [draft-vixie-dnsext-dns0x20-00](https://datatracker.ietf.org/doc/html/draft-vixie-dnsext-dns0x20-00). pub case_randomization: bool, /// Path to a DNSSEC trust anchor file. /// /// If this is provided, `validate` will automatically be set to `true`, enabling DNSSEC validation. pub trust_anchor: Option, /// Exceptions to `deny_answer_addresses`. Networks listed here will be allowed, even if the IP address /// matches a network in `deny_answer_addresses`. pub allow_answers: Vec, /// Networks listed here will be removed from any answers returned by an upstream server. pub deny_answers: Vec, /// Configure the EDNS UDP payload size used in queries. /// /// See [DnsRequestOptions::edns_payload_len][crate::proto::op::DnsRequestOptions::edns_payload_len]. #[cfg_attr(feature = "serde", serde(default = "default_edns_payload_len"))] pub edns_payload_len: u16, /// Report the IP of the name server for query result metrics. /// /// When `false`, metrics are aggregated under a static "aggregate" address label to avoid /// cardinality blowup. For this reason, you might want to leave this disabled when querying a /// large or unbounded set of DNS servers. #[cfg_attr(feature = "serde", serde(default))] #[cfg(feature = "metrics")] pub enable_per_name_server_metrics: bool, } impl ResolverOpts { pub(crate) fn answer_address_filter(&self) -> AccessControlSet { let name = "resolver_answer_filter"; AccessControlSetBuilder::new(name) .allow(self.allow_answers.iter()) .deny(self.deny_answers.iter()) .build() .inspect_err(|err| warn!("{err}")) .unwrap_or_else(|_| AccessControlSet::empty(name)) } } impl Default for ResolverOpts { /// Default values for the Resolver configuration. /// /// This follows the resolv.conf defaults as defined in the [Linux man pages](https://man7.org/linux/man-pages/man5/resolv.conf.5.html) fn default() -> Self { Self { ndots: default_ndots(), timeout: default_timeout(), attempts: default_attempts(), edns0: true, #[cfg(feature = "__dnssec")] validate: false, ip_strategy: LookupIpStrategy::default(), cache_size: default_cache_size(), use_hosts_file: ResolveHosts::default(), positive_min_ttl: None, negative_min_ttl: None, positive_max_ttl: None, negative_max_ttl: None, num_concurrent_reqs: default_num_concurrent_reqs(), max_active_requests: default_max_active_requests(), // Defaults to `true` to match the behavior of dig and nslookup. preserve_intermediates: default_preserve_intermediates(), try_tcp_on_error: false, server_ordering_strategy: ServerOrderingStrategy::default(), recursion_desired: default_recursion_desired(), avoid_local_udp_ports: Arc::default(), os_port_selection: false, case_randomization: false, trust_anchor: None, allow_answers: vec![], deny_answers: vec![], edns_payload_len: default_edns_payload_len(), #[cfg(feature = "metrics")] enable_per_name_server_metrics: false, } } } fn default_ndots() -> usize { 1 } fn default_timeout() -> Duration { Duration::from_secs(5) } fn default_attempts() -> usize { 2 } fn default_cache_size() -> u64 { 8_192 } fn default_num_concurrent_reqs() -> usize { 2 } fn default_max_active_requests() -> usize { 32 } fn default_preserve_intermediates() -> bool { true } fn default_recursion_desired() -> bool { true } fn default_edns_payload_len() -> u16 { DEFAULT_MAX_PAYLOAD_LEN } /// The lookup ip strategy #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum LookupIpStrategy { /// Only query for A (Ipv4) records Ipv4Only, /// Only query for AAAA (Ipv6) records Ipv6Only, /// Query for A and AAAA in parallel, ordering A before AAAA Ipv4AndIpv6, /// Query for AAAA and A in parallel, ordering AAAA before A #[default] Ipv6AndIpv4, /// Query for Ipv6 if that fails, query for Ipv4 Ipv6thenIpv4, /// Query for Ipv4 if that fails, query for Ipv6 (default) Ipv4thenIpv6, } /// The strategy for establishing the query order of name servers in a pool. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[non_exhaustive] pub enum ServerOrderingStrategy { /// Servers are ordered based on collected query statistics. The ordering /// may vary over time. #[default] QueryStatistics, /// The order provided to the resolver is used. The ordering does not vary /// over time. UserProvidedOrder, /// The order of servers is rotated in a round-robin fashion. This is useful for /// load balancing and ensuring that all servers are used evenly. RoundRobin, } /// Whether the system hosts file should be respected by the resolver. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum ResolveHosts { /// Always attempt to look up IP addresses from the system hosts file. /// If the hostname cannot be found, query the DNS. Always, /// The DNS will always be queried. Never, /// Use local resolver configurations only when this resolver is not used in /// a DNS forwarder. This is the default. #[default] Auto, } /// Configuration for enabling RFC 9539 opportunistic encryption. /// /// Controls how a recursive resolver probes name servers to discover if they support /// encrypted transports. #[derive(Debug, Clone, Default, Eq, PartialEq)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "snake_case") )] #[non_exhaustive] pub enum OpportunisticEncryption { /// Opportunistic encryption will not be performed. #[default] Disabled, /// Opportunistic encryption will be performed. #[cfg(any(feature = "__tls", feature = "__quic"))] Enabled { /// Configuration parameters for opportunistic encryption. #[cfg_attr(feature = "serde", serde(flatten))] config: OpportunisticEncryptionConfig, }, } impl OpportunisticEncryption { #[cfg(all( feature = "recursor", feature = "toml", feature = "serde", any(feature = "__tls", feature = "__quic") ))] pub(super) fn persisted_state(&self) -> Result, String> { let OpportunisticEncryption::Enabled { config: OpportunisticEncryptionConfig { persistence: Some(OpportunisticEncryptionPersistence { path, .. }), .. }, } = self else { return Ok(None); }; let state = match fs::read_to_string(path) { Ok(toml_content) => toml::from_str(&toml_content).map_err(|e| { format!( "failed to parse opportunistic encryption state TOML file: {file_path}: {e}", file_path = path.display() ) })?, Err(e) if e.kind() == io::ErrorKind::NotFound => { info!( state_file = %path.display(), "no pre-existing opportunistic encryption state TOML file, starting with default state", ); NameServerTransportState::default() } Err(e) => { return Err(format!( "failed to read opportunistic encryption state TOML file: {file_path}: {e}", file_path = path.display() )); } }; debug!( path = %path.display(), nameserver_count = state.nameserver_count(), "loaded opportunistic encryption state" ); Ok(Some(state)) } /// Returns true if opportunistic encryption is enabled. pub fn is_enabled(&self) -> bool { match self { Self::Disabled => false, #[cfg(any(feature = "__tls", feature = "__quic"))] Self::Enabled { .. } => true, } } /// Returns the maximum number of concurrent probes if opportunistic encrypt is enabled. pub fn max_concurrent_probes(&self) -> Option { match self { Self::Disabled => None, #[cfg(any(feature = "__tls", feature = "__quic"))] Self::Enabled { config, .. } => Some(config.max_concurrent_probes), } } } /// Configuration parameters for opportunistic encryption. #[derive(Debug, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", serde(default, deny_unknown_fields))] pub struct OpportunisticEncryptionConfig { /// How long the recursive resolver remembers a successful encrypted transport connection. #[cfg_attr( feature = "serde", serde(default = "default_persistence_period", with = "duration") )] pub persistence_period: Duration, /// How long the recursive resolver remembers a failed encrypted transport connection. #[cfg_attr( feature = "serde", serde(default = "default_damping_period", with = "duration") )] pub damping_period: Duration, /// Maximum number of concurrent opportunistic encryption probes. #[cfg_attr(feature = "serde", serde(default = "default_max_concurrent_probes"))] pub max_concurrent_probes: u8, /// Optional configuration for persistence of opportunistic encryption probe state. pub persistence: Option, } impl Default for OpportunisticEncryptionConfig { fn default() -> Self { Self { persistence_period: default_persistence_period(), damping_period: default_damping_period(), max_concurrent_probes: default_max_concurrent_probes(), persistence: None, } } } /// The RFC 9539 suggested default for the resolver persistence period. fn default_persistence_period() -> Duration { Duration::from_secs(60 * 60 * 24 * 3) // 3 days } /// The RFC 9539 suggested default for the resolver damping period. fn default_damping_period() -> Duration { Duration::from_secs(24 * 60 * 60) // 1 day } /// A conservative default for the maximum number of in-flight opportunistic probe requests. fn default_max_concurrent_probes() -> u8 { 10 } #[derive(Debug, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] /// Configuration for persistence of opportunistic encryption probe state. pub struct OpportunisticEncryptionPersistence { /// Path to a TOML state file that may be used for saving/loading opportunistic encryption state. pub path: PathBuf, /// Interval after which opportunistic encryption state is periodically saved to `path`. #[cfg_attr( feature = "serde", serde(default = "default_save_interval", with = "duration") )] pub save_interval: Duration, } #[cfg(feature = "serde")] fn default_save_interval() -> Duration { Duration::from_secs(60 * 10) // 10 minutes } /// Google Public DNS configuration. /// /// Please see Google's [privacy statement](https://developers.google.com/speed/public-dns/privacy) /// for important information about what they track, many ISP's track similar information in DNS. /// To use the system configuration see: `Resolver::from_system_conf`. pub const GOOGLE: ServerGroup<'static> = ServerGroup { ips: &[ IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)), IpAddr::V4(Ipv4Addr::new(8, 8, 4, 4)), IpAddr::V6(Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888)), IpAddr::V6(Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8844)), ], server_name: "dns.google", path: "/dns-query", }; /// Cloudflare's 1.1.1.1 DNS service configuration. /// /// See for more information. pub const CLOUDFLARE: ServerGroup<'static> = ServerGroup { ips: &[ IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)), IpAddr::V4(Ipv4Addr::new(1, 0, 0, 1)), IpAddr::V6(Ipv6Addr::new(0x2606, 0x4700, 0x4700, 0, 0, 0, 0, 0x1111)), IpAddr::V6(Ipv6Addr::new(0x2606, 0x4700, 0x4700, 0, 0, 0, 0, 0x1001)), ], server_name: "cloudflare-dns.com", path: "/dns-query", }; /// The Quad9 DNS service configuration. /// /// See for more information. pub const QUAD9: ServerGroup<'static> = ServerGroup { ips: &[ IpAddr::V4(Ipv4Addr::new(9, 9, 9, 9)), IpAddr::V4(Ipv4Addr::new(149, 112, 112, 112)), IpAddr::V6(Ipv6Addr::new(0x2620, 0x00fe, 0, 0, 0, 0, 0, 0x00fe)), IpAddr::V6(Ipv6Addr::new(0x2620, 0x00fe, 0, 0, 0, 0, 0, 0x0009)), ], server_name: "dns.quad9.net", path: "/dns-query", }; /// A group of DNS servers. #[derive(Clone, Copy, Debug)] pub struct ServerGroup<'a> { /// IP addresses of the DNS servers in this group. pub ips: &'a [IpAddr], /// The TLS server name to use for servers. pub server_name: &'a str, /// The query path to use for HTTP queries. pub path: &'a str, } impl<'a> ServerGroup<'a> { /// Create an iterator with `NameServerConfig` for each IP address in the group. pub fn udp_and_tcp(&self) -> impl Iterator + 'a { self.ips.iter().map(|&ip| { NameServerConfig::new( ip, true, vec![ConnectionConfig::udp(), ConnectionConfig::tcp()], ) }) } /// Create an iterator with `NameServerConfig` for each IP address in the group. pub fn udp(&self) -> impl Iterator + 'a { self.ips .iter() .map(|&ip| NameServerConfig::new(ip, true, vec![ConnectionConfig::udp()])) } /// Create an iterator with `NameServerConfig` for each IP address in the group. pub fn tcp(&self) -> impl Iterator + 'a { self.ips .iter() .map(|&ip| NameServerConfig::new(ip, true, vec![ConnectionConfig::tcp()])) } /// Create an iterator with `NameServerConfig` for each IP address in the group. #[cfg(feature = "__tls")] pub fn tls(&self) -> impl Iterator + 'a { let this = *self; self.ips.iter().map(move |&ip| { NameServerConfig::new( ip, true, vec![ConnectionConfig::tls(Arc::from(this.server_name))], ) }) } /// Create an iterator with `NameServerConfig` for each IP address in the group. #[cfg(feature = "__https")] pub fn https(&self) -> impl Iterator + 'a { let this = *self; self.ips.iter().map(move |&ip| { NameServerConfig::new( ip, true, vec![ConnectionConfig::https( Arc::from(this.server_name), Some(Arc::from(this.path)), )], ) }) } /// Create an iterator with `NameServerConfig` for each IP address in the group. #[cfg(feature = "__quic")] pub fn quic(&self) -> impl Iterator + 'a { let this = *self; self.ips.iter().map(move |&ip| { NameServerConfig::new( ip, true, vec![ConnectionConfig::quic(Arc::from(this.server_name))], ) }) } /// Create an iterator with `NameServerConfig` for each IP address in the group. #[cfg(feature = "__h3")] pub fn h3(&self) -> impl Iterator + 'a { let this = *self; self.ips.iter().map(move |&ip| { NameServerConfig::new( ip, true, vec![ConnectionConfig::h3( Arc::from(this.server_name), Some(Arc::from(this.path)), )], ) }) } } #[cfg(feature = "serde")] pub(crate) mod duration { use std::time::Duration; use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// This is an alternate serialization function for a [`Duration`] that emits a single number, /// representing the number of seconds, instead of a struct with `secs` and `nanos` fields. pub(super) fn serialize( duration: &Duration, serializer: S, ) -> Result { duration.as_secs().serialize(serializer) } /// This is an alternate deserialization function for a [`Duration`] that expects a single number, /// representing the number of seconds, instead of a struct with `secs` and `nanos` fields. pub(crate) fn deserialize<'de, D: Deserializer<'de>>( deserializer: D, ) -> Result { Ok(Duration::from_secs(u64::deserialize(deserializer)?)) } } #[cfg(feature = "serde")] pub(crate) mod duration_opt { use std::time::Duration; use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// This is an alternate serialization function for an optional [`Duration`] that emits a single /// number, representing the number of seconds, instead of a struct with `secs` and `nanos` fields. pub(super) fn serialize( duration: &Option, serializer: S, ) -> Result { struct Wrapper<'a>(&'a Duration); impl Serialize for Wrapper<'_> { fn serialize(&self, serializer: S) -> Result { super::duration::serialize(self.0, serializer) } } match duration { Some(duration) => serializer.serialize_some(&Wrapper(duration)), None => serializer.serialize_none(), } } /// This is an alternate deserialization function for an optional [`Duration`] that expects a single /// number, representing the number of seconds, instead of a struct with `secs` and `nanos` fields. pub(crate) fn deserialize<'de, D: Deserializer<'de>>( deserializer: D, ) -> Result, D::Error> { Ok(Option::::deserialize(deserializer)?.map(Duration::from_secs)) } } #[cfg(all(test, feature = "serde"))] mod tests { use super::*; #[cfg(feature = "serde")] #[test] fn default_opts() { let code = ResolverOpts::default(); let json = serde_json::from_str::("{}").unwrap(); assert_eq!(code.ndots, json.ndots); assert_eq!(code.timeout, json.timeout); assert_eq!(code.attempts, json.attempts); assert_eq!(code.edns0, json.edns0); #[cfg(feature = "__dnssec")] assert_eq!(code.validate, json.validate); assert_eq!(code.ip_strategy, json.ip_strategy); assert_eq!(code.cache_size, json.cache_size); assert_eq!(code.use_hosts_file, json.use_hosts_file); assert_eq!(code.positive_min_ttl, json.positive_min_ttl); assert_eq!(code.negative_min_ttl, json.negative_min_ttl); assert_eq!(code.positive_max_ttl, json.positive_max_ttl); assert_eq!(code.negative_max_ttl, json.negative_max_ttl); assert_eq!(code.num_concurrent_reqs, json.num_concurrent_reqs); assert_eq!(code.preserve_intermediates, json.preserve_intermediates); assert_eq!(code.try_tcp_on_error, json.try_tcp_on_error); assert_eq!(code.recursion_desired, json.recursion_desired); assert_eq!(code.server_ordering_strategy, json.server_ordering_strategy); assert_eq!(code.avoid_local_udp_ports, json.avoid_local_udp_ports); assert_eq!(code.os_port_selection, json.os_port_selection); assert_eq!(code.case_randomization, json.case_randomization); assert_eq!(code.trust_anchor, json.trust_anchor); #[cfg(feature = "metrics")] assert_eq!( code.enable_per_name_server_metrics, json.enable_per_name_server_metrics ); } }