Files
2026-10-12 07:22:52 -07:00

1192 lines
43 KiB
Rust

// Copyright 2015-2017 Benjamin Fry <[email protected]>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// https://opensource.org/licenses/MIT>, 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<Name>,
/// Search domains
#[cfg_attr(feature = "serde", serde(default))]
pub search: Vec<Name>,
/// Name servers to use for resolution
pub name_servers: Vec<NameServerConfig>,
}
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<Name>,
search: Vec<Name>,
name_servers: Vec<NameServerConfig>,
) -> 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<NameServerConfig>) -> Self {
Self::from_parts(None, vec![], name_servers)
}
/// Take the `domain`, `search`, and `name_servers` from the config.
pub fn into_parts(self) -> (Option<Name>, Vec<Name>, Vec<NameServerConfig>) {
(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<ConnectionConfig>,
}
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<str>) -> 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<str>, path: Option<Arc<str>>) -> 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<str>) -> 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<str>, path: Option<Arc<str>>) -> 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<ConnectionConfig>,
) -> 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<SocketAddr>,
}
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<str>) -> Self {
Self::new(ProtocolConfig::Tls { server_name })
}
/// Constructs a new ConnectionConfig for HTTPS (HTTP/2)
#[cfg(feature = "__https")]
pub fn https(server_name: Arc<str>, path: Option<Arc<str>>) -> 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<str>) -> Self {
Self::new(ProtocolConfig::Quic { server_name })
}
/// Constructs a new ConnectionConfig for HTTP/3
#[cfg(feature = "__h3")]
pub fn h3(server_name: Arc<str>, path: Option<Arc<str>>) -> 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<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct OptionalParts {
#[serde(default)]
port: Option<u16>,
protocol: ProtocolConfig,
#[serde(default)]
bind_addr: Option<SocketAddr>,
}
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<str>,
},
#[cfg(feature = "__https")]
Https {
/// The server name to use in the TLS handshake.
server_name: Arc<str>,
/// The path (or endpoint) to use for the DNS query.
path: Arc<str>,
},
#[cfg(feature = "__quic")]
Quic {
/// The server name to use in the TLS handshake.
server_name: Arc<str>,
},
#[cfg(feature = "__h3")]
H3 {
/// The server name to use in the TLS handshake.
server_name: Arc<str>,
/// The path (or endpoint) to use for the DNS query.
path: Arc<str>,
/// 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<str>> {
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<Duration>,
/// 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<Duration>,
/// 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<Duration>,
/// 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<Duration>,
/// 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<HashSet<u16>>,
/// 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<PathBuf>,
/// 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<IpNet>,
/// Networks listed here will be removed from any answers returned by an upstream server.
pub deny_answers: Vec<IpNet>,
/// 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<Option<NameServerTransportState>, 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<u8> {
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<OpportunisticEncryptionPersistence>,
}
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 <https://www.cloudflare.com/dns/> 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 <https://www.quad9.net/faq/> 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<Item = NameServerConfig> + '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<Item = NameServerConfig> + '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<Item = NameServerConfig> + '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<Item = NameServerConfig> + '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<Item = NameServerConfig> + '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<Item = NameServerConfig> + '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<Item = NameServerConfig> + '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<S: Serializer>(
duration: &Duration,
serializer: S,
) -> Result<S::Ok, S::Error> {
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<Duration, D::Error> {
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<S: Serializer>(
duration: &Option<Duration>,
serializer: S,
) -> Result<S::Ok, S::Error> {
struct Wrapper<'a>(&'a Duration);
impl Serialize for Wrapper<'_> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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<Option<Duration>, D::Error> {
Ok(Option::<u64>::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::<ResolverOpts>("{}").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
);
}
}