1192 lines
43 KiB
Rust
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
|
|
);
|
|
}
|
|
}
|