#![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 = "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 = "toml",
feature = "serde",
any(feature = "__tls", feature = "__quic")
))]
use tracing::{debug, info};
#[cfg(all(
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;
#[non_exhaustive]
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ResolverConfig {
#[cfg_attr(feature = "serde", serde(default))]
pub domain: Option<Name>,
#[cfg_attr(feature = "serde", serde(default))]
pub search: Vec<Name>,
pub name_servers: Vec<NameServerConfig>,
}
impl ResolverConfig {
pub fn udp_and_tcp(config: &ServerGroup<'_>) -> Self {
Self {
domain: None,
search: vec![],
name_servers: config.udp_and_tcp().collect(),
}
}
#[cfg(feature = "__tls")]
pub fn tls(config: &ServerGroup<'_>) -> Self {
Self {
domain: None,
search: vec![],
name_servers: config.tls().collect(),
}
}
#[cfg(feature = "__https")]
pub fn https(config: &ServerGroup<'_>) -> Self {
Self {
domain: None,
search: vec![],
name_servers: config.https().collect(),
}
}
#[cfg(feature = "__quic")]
pub fn quic(config: &ServerGroup<'_>) -> Self {
Self {
domain: None,
search: vec![],
name_servers: config.quic().collect(),
}
}
#[cfg(feature = "__h3")]
pub fn h3(config: &ServerGroup<'_>) -> Self {
Self {
domain: None,
search: vec![],
name_servers: config.h3().collect(),
}
}
pub fn from_parts(
domain: Option<Name>,
search: Vec<Name>,
name_servers: Vec<NameServerConfig>,
) -> Self {
Self {
domain,
search,
name_servers,
}
}
pub fn into_parts(self) -> (Option<Name>, Vec<Name>, Vec<NameServerConfig>) {
(self.domain, self.search, self.name_servers)
}
pub fn domain(&self) -> Option<&Name> {
self.domain.as_ref()
}
pub fn set_domain(&mut self, domain: Name) {
self.domain = Some(domain.clone());
self.search = vec![domain];
}
pub fn search(&self) -> &[Name] {
&self.search
}
pub fn add_search(&mut self, search: Name) {
self.search.push(search)
}
pub fn add_name_server(&mut self, name_server: NameServerConfig) {
self.name_servers.push(name_server);
}
pub fn name_servers(&self) -> &[NameServerConfig] {
&self.name_servers
}
}
#[derive(Clone, Debug)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(deny_unknown_fields)
)]
#[non_exhaustive]
pub struct NameServerConfig {
pub ip: IpAddr,
#[cfg_attr(feature = "serde", serde(default = "default_trust_negative_responses"))]
pub trust_negative_responses: bool,
pub connections: Vec<ConnectionConfig>,
}
impl NameServerConfig {
pub fn udp_and_tcp(ip: IpAddr) -> Self {
Self {
ip,
trust_negative_responses: true,
connections: vec![ConnectionConfig::udp(), ConnectionConfig::tcp()],
}
}
pub fn udp(ip: IpAddr) -> Self {
Self {
ip,
trust_negative_responses: true,
connections: vec![ConnectionConfig::udp()],
}
}
pub fn tcp(ip: IpAddr) -> Self {
Self {
ip,
trust_negative_responses: true,
connections: vec![ConnectionConfig::tcp()],
}
}
#[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)],
}
}
#[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)],
}
}
#[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)],
}
}
#[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)],
}
}
#[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())),
],
}
}
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
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[non_exhaustive]
pub struct ConnectionConfig {
pub port: u16,
pub protocol: ProtocolConfig,
pub bind_addr: Option<SocketAddr>,
}
impl ConnectionConfig {
pub fn udp() -> Self {
Self::new(ProtocolConfig::Udp)
}
pub fn tcp() -> Self {
Self::new(ProtocolConfig::Tcp)
}
#[cfg(feature = "__tls")]
pub fn tls(server_name: Arc<str>) -> Self {
Self::new(ProtocolConfig::Tls { server_name })
}
#[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)),
})
}
#[cfg(feature = "__quic")]
pub fn quic(server_name: Arc<str>) -> Self {
Self::new(ProtocolConfig::Quic { server_name })
}
#[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,
})
}
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,
})
}
}
#[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 {
server_name: Arc<str>,
},
#[cfg(feature = "__https")]
Https {
server_name: Arc<str>,
path: Arc<str>,
},
#[cfg(feature = "__quic")]
Quic {
server_name: Arc<str>,
},
#[cfg(feature = "__h3")]
H3 {
server_name: Arc<str>,
path: Arc<str>,
#[cfg_attr(feature = "serde", serde(default))]
disable_grease: bool,
},
}
impl 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,
}
}
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,
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(default, deny_unknown_fields)
)]
#[non_exhaustive]
pub struct ResolverOpts {
#[cfg_attr(feature = "serde", serde(default = "default_ndots"))]
pub ndots: usize,
#[cfg_attr(
feature = "serde",
serde(default = "default_timeout", with = "duration")
)]
pub timeout: Duration,
#[cfg_attr(feature = "serde", serde(default = "default_attempts"))]
pub attempts: usize,
pub edns0: bool,
#[cfg(feature = "__dnssec")]
pub validate: bool,
pub ip_strategy: LookupIpStrategy,
#[cfg_attr(feature = "serde", serde(default = "default_cache_size"))]
pub cache_size: u64,
pub use_hosts_file: ResolveHosts,
#[cfg_attr(feature = "serde", serde(with = "duration_opt"))]
pub positive_min_ttl: Option<Duration>,
#[cfg_attr(feature = "serde", serde(with = "duration_opt"))]
pub negative_min_ttl: Option<Duration>,
#[cfg_attr(feature = "serde", serde(with = "duration_opt"))]
pub positive_max_ttl: Option<Duration>,
#[cfg_attr(feature = "serde", serde(with = "duration_opt"))]
pub negative_max_ttl: Option<Duration>,
#[cfg_attr(feature = "serde", serde(default = "default_num_concurrent_reqs"))]
pub num_concurrent_reqs: usize,
#[cfg_attr(feature = "serde", serde(default = "default_preserve_intermediates"))]
pub preserve_intermediates: bool,
pub try_tcp_on_error: bool,
pub server_ordering_strategy: ServerOrderingStrategy,
#[cfg_attr(feature = "serde", serde(default = "default_recursion_desired"))]
pub recursion_desired: bool,
pub avoid_local_udp_ports: Arc<HashSet<u16>>,
pub os_port_selection: bool,
pub case_randomization: bool,
pub trust_anchor: Option<PathBuf>,
pub allow_answers: Vec<IpNet>,
pub deny_answers: Vec<IpNet>,
#[cfg_attr(feature = "serde", serde(default = "default_edns_payload_len"))]
pub edns_payload_len: u16,
}
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 {
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(),
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(),
}
}
}
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_preserve_intermediates() -> bool {
true
}
fn default_recursion_desired() -> bool {
true
}
fn default_edns_payload_len() -> u16 {
DEFAULT_MAX_PAYLOAD_LEN
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum LookupIpStrategy {
Ipv4Only,
Ipv6Only,
Ipv4AndIpv6,
#[default]
Ipv6AndIpv4,
Ipv6thenIpv4,
Ipv4thenIpv6,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum ServerOrderingStrategy {
#[default]
QueryStatistics,
UserProvidedOrder,
RoundRobin,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ResolveHosts {
Always,
Never,
#[default]
Auto,
}
#[derive(Debug, Clone, Default, Eq, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(rename_all = "snake_case")
)]
#[non_exhaustive]
pub enum OpportunisticEncryption {
#[default]
Disabled,
#[cfg(any(feature = "__tls", feature = "__quic"))]
Enabled {
#[cfg_attr(feature = "serde", serde(flatten))]
config: OpportunisticEncryptionConfig,
},
}
impl OpportunisticEncryption {
#[cfg(all(
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))
}
pub fn is_enabled(&self) -> bool {
match self {
Self::Disabled => false,
#[cfg(any(feature = "__tls", feature = "__quic"))]
Self::Enabled { .. } => true,
}
}
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),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(default, deny_unknown_fields))]
pub struct OpportunisticEncryptionConfig {
#[cfg_attr(
feature = "serde",
serde(default = "default_persistence_period", with = "duration")
)]
pub persistence_period: Duration,
#[cfg_attr(
feature = "serde",
serde(default = "default_damping_period", with = "duration")
)]
pub damping_period: Duration,
#[cfg_attr(feature = "serde", serde(default = "default_max_concurrent_probes"))]
pub max_concurrent_probes: u8,
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,
}
}
}
fn default_persistence_period() -> Duration {
Duration::from_secs(60 * 60 * 24 * 3) }
fn default_damping_period() -> Duration {
Duration::from_secs(24 * 60 * 60) }
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))]
pub struct OpportunisticEncryptionPersistence {
pub path: PathBuf,
#[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) }
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",
};
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",
};
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",
};
#[derive(Clone, Copy, Debug)]
pub struct ServerGroup<'a> {
pub ips: &'a [IpAddr],
pub server_name: &'a str,
pub path: &'a str,
}
impl<'a> ServerGroup<'a> {
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()],
)
})
}
pub fn udp(&self) -> impl Iterator<Item = NameServerConfig> + 'a {
self.ips
.iter()
.map(|&ip| NameServerConfig::new(ip, true, vec![ConnectionConfig::udp()]))
}
pub fn tcp(&self) -> impl Iterator<Item = NameServerConfig> + 'a {
self.ips
.iter()
.map(|&ip| NameServerConfig::new(ip, true, vec![ConnectionConfig::tcp()]))
}
#[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))],
)
})
}
#[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)),
)],
)
})
}
#[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))],
)
})
}
#[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};
pub(super) fn serialize<S: Serializer>(
duration: &Duration,
serializer: S,
) -> Result<S::Ok, S::Error> {
duration.as_secs().serialize(serializer)
}
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};
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(),
}
}
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);
}
}