This builds, I guess.

This commit is contained in:
2025-01-12 14:10:23 -08:00
parent b30823a502
commit 268ca2d1a5
25 changed files with 1750 additions and 735 deletions

View File

@@ -1,35 +1,28 @@
use error_stack::report;
use hickory_proto::error::ProtoError;
use hickory_resolver::config::{NameServerConfig, Protocol, ResolverConfig, ResolverOpts};
use hickory_resolver::{Name, TokioAsyncResolver};
use proptest::arbitrary::Arbitrary;
use proptest::strategy::{BoxedStrategy, Just, Strategy};
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
use thiserror::Error;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct DnsConfig {
built_in: Option<BuiltinDnsOption>,
local_domain: Option<String>,
pub local_domain: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
search_domains: Vec<String>,
pub search_domains: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
name_servers: Vec<ServerConfig>,
pub name_servers: Vec<NameServerConfig>,
#[serde(default)]
timeout_in_seconds: Option<u16>,
pub retry_attempts: Option<u16>,
#[serde(default)]
retry_attempts: Option<u16>,
pub cache_size: Option<u32>,
#[serde(default)]
cache_size: Option<u32>,
pub max_concurrent_requests_for_query: Option<u16>,
#[serde(default)]
use_hosts_file: Option<bool>,
pub preserve_intermediates: Option<bool>,
#[serde(default)]
max_concurrent_requests_for_query: Option<u16>,
pub shuffle_dns_servers: Option<bool>,
#[serde(default)]
preserve_intermediates: Option<bool>,
#[serde(default)]
shuffle_dns_servers: Option<bool>,
pub allow_mdns: Option<bool>,
}
impl Default for DnsConfig {
@@ -39,13 +32,12 @@ impl Default for DnsConfig {
local_domain: None,
search_domains: vec![],
name_servers: vec![],
timeout_in_seconds: None,
retry_attempts: None,
cache_size: None,
use_hosts_file: None,
max_concurrent_requests_for_query: None,
preserve_intermediates: None,
shuffle_dns_servers: None,
allow_mdns: None,
}
}
}
@@ -62,13 +54,12 @@ impl Arbitrary for DnsConfig {
local_domain: None,
search_domains: vec![],
name_servers: vec![],
timeout_in_seconds: None,
retry_attempts: None,
cache_size: None,
use_hosts_file: None,
max_concurrent_requests_for_query: None,
preserve_intermediates: None,
shuffle_dns_servers: None,
allow_mdns: None,
})
.boxed()
} else {
@@ -79,51 +70,47 @@ impl Arbitrary for DnsConfig {
let search_domains = proptest::collection::vec(domain_name_strat(), 0..10);
let min_servers = if built_in.is_some() { 0 } else { 1 };
let name_servers =
proptest::collection::vec(ServerConfig::arbitrary(), min_servers..6);
let timeout_in_seconds = proptest::option::of(u16::arbitrary());
proptest::collection::vec(NameServerConfig::arbitrary(), min_servers..6);
let retry_attempts = proptest::option::of(u16::arbitrary());
let cache_size = proptest::option::of(u32::arbitrary());
let use_hosts_file = proptest::option::of(bool::arbitrary());
let max_concurrent_requests_for_query = proptest::option::of(u16::arbitrary());
let preserve_intermediates = proptest::option::of(bool::arbitrary());
let shuffle_dns_servers = proptest::option::of(bool::arbitrary());
let allow_mdns = proptest::option::of(bool::arbitrary());
(
local_domain,
search_domains,
name_servers,
timeout_in_seconds,
retry_attempts,
cache_size,
use_hosts_file,
max_concurrent_requests_for_query,
preserve_intermediates,
shuffle_dns_servers,
allow_mdns,
)
.prop_map(
move |(
local_domain,
search_domains,
name_servers,
timeout_in_seconds,
retry_attempts,
cache_size,
use_hosts_file,
max_concurrent_requests_for_query,
preserve_intermediates,
shuffle_dns_servers,
allow_mdns,
)| DnsConfig {
built_in,
local_domain,
search_domains,
name_servers,
timeout_in_seconds,
retry_attempts,
cache_size,
use_hosts_file,
max_concurrent_requests_for_query,
preserve_intermediates,
shuffle_dns_servers,
allow_mdns,
},
)
})
@@ -172,34 +159,34 @@ impl Arbitrary for BuiltinDnsOption {
}
}
#[derive(Debug, PartialEq, Deserialize, Serialize)]
struct ServerConfig {
address: SocketAddr,
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct NameServerConfig {
pub address: SocketAddr,
#[serde(default)]
trust_negatives: bool,
pub timeout_in_seconds: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
bind_address: Option<SocketAddr>,
pub bind_address: Option<SocketAddr>,
}
impl Arbitrary for ServerConfig {
impl Arbitrary for NameServerConfig {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
(
SocketAddr::arbitrary(),
bool::arbitrary(),
proptest::option::of(u64::arbitrary()),
proptest::option::of(SocketAddr::arbitrary()),
)
.prop_map(|(mut address, trust_negatives, mut bind_address)| {
.prop_map(|(mut address, timeout_in_seconds, mut bind_address)| {
clear_flow_and_scope_info(&mut address);
if let Some(bind_address) = bind_address.as_mut() {
clear_flow_and_scope_info(bind_address);
}
ServerConfig {
NameServerConfig {
address,
trust_negatives,
timeout_in_seconds,
bind_address,
}
})
@@ -214,86 +201,6 @@ fn clear_flow_and_scope_info(address: &mut SocketAddr) {
}
}
#[derive(Debug, Error)]
pub enum ResolverConfigError {
#[error("Bad local domain name '{name}' provided: {error}")]
BadDomainName { name: String, error: ProtoError },
#[error("Bad domain name for search '{name}' provided: {error}")]
BadSearchName { name: String, error: ProtoError },
#[error("No DNS hosts found to search")]
NoHosts,
}
impl DnsConfig {
/// Convert this resolver configuration into an actual ResolverConfig, or say
/// why it's bad.
pub fn resolver(&self) -> error_stack::Result<TokioAsyncResolver, ResolverConfigError> {
let mut config = match &self.built_in {
None => ResolverConfig::new(),
Some(BuiltinDnsOption::Cloudflare) => ResolverConfig::cloudflare(),
Some(BuiltinDnsOption::Google) => ResolverConfig::google(),
Some(BuiltinDnsOption::Quad9) => ResolverConfig::quad9(),
};
if let Some(local_domain) = &self.local_domain {
let name = Name::from_utf8(local_domain).map_err(|error| {
report!(ResolverConfigError::BadDomainName {
name: local_domain.clone(),
error,
})
})?;
config.set_domain(name);
}
for name in self.search_domains.iter() {
let name = Name::from_utf8(name).map_err(|error| {
report!(ResolverConfigError::BadSearchName {
name: name.clone(),
error,
})
})?;
config.add_search(name);
}
for ns in self.name_servers.iter() {
let mut nsconfig = NameServerConfig::new(ns.address, Protocol::Udp);
nsconfig.trust_negative_responses = ns.trust_negatives;
nsconfig.bind_addr = ns.bind_address;
config.add_name_server(nsconfig);
}
if config.name_servers().is_empty() {
return Err(report!(ResolverConfigError::NoHosts));
}
let mut options = ResolverOpts::default();
if let Some(seconds) = self.timeout_in_seconds {
options.timeout = tokio::time::Duration::from_secs(seconds as u64);
}
if let Some(retries) = self.retry_attempts {
options.attempts = retries as usize;
}
if let Some(cache_size) = self.cache_size {
options.cache_size = cache_size as usize;
}
options.use_hosts_file = self.use_hosts_file.unwrap_or(true);
if let Some(max) = self.max_concurrent_requests_for_query {
options.num_concurrent_reqs = max as usize;
}
if let Some(preserve) = self.preserve_intermediates {
options.preserve_intermediates = preserve;
}
if let Some(shuffle) = self.shuffle_dns_servers {
options.shuffle_dns_servers = shuffle;
}
Ok(TokioAsyncResolver::tokio(config, options))
}
}
proptest::proptest! {
#[test]
fn valid_configs_parse(config in DnsConfig::arbitrary_with(false)) {
@@ -302,3 +209,98 @@ proptest::proptest! {
assert_eq!(config, reversed);
}
}
impl DnsConfig {
/// Return the configurations for all of the name servers that the user
/// has selected.
pub fn name_servers(&self) -> Vec<NameServerConfig> {
let mut results = self.name_servers.clone();
match self.built_in {
None => {}
Some(BuiltinDnsOption::Cloudflare) => {
results.push(NameServerConfig {
address: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(1, 1, 1, 1), 53)),
timeout_in_seconds: None,
bind_address: None,
});
results.push(NameServerConfig {
address: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(1, 0, 0, 1), 53)),
timeout_in_seconds: None,
bind_address: None,
});
results.push(NameServerConfig {
address: SocketAddr::V6(SocketAddrV6::new(
Ipv6Addr::new(0x2606, 0x4700, 0x4700, 0, 0, 0, 0, 0x1111),
53,
0,
0,
)),
timeout_in_seconds: None,
bind_address: None,
});
results.push(NameServerConfig {
address: SocketAddr::V6(SocketAddrV6::new(
Ipv6Addr::new(0x2606, 0x4700, 0x4700, 0, 0, 0, 0, 0x1001),
53,
0,
0,
)),
timeout_in_seconds: None,
bind_address: None,
});
}
Some(BuiltinDnsOption::Google) => {
results.push(NameServerConfig {
address: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(8, 8, 8, 8), 53)),
timeout_in_seconds: None,
bind_address: None,
});
results.push(NameServerConfig {
address: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(8, 8, 4, 4), 53)),
timeout_in_seconds: None,
bind_address: None,
});
results.push(NameServerConfig {
address: SocketAddr::V6(SocketAddrV6::new(
Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888),
53,
0,
0,
)),
timeout_in_seconds: None,
bind_address: None,
});
results.push(NameServerConfig {
address: SocketAddr::V6(SocketAddrV6::new(
Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8844),
53,
0,
0,
)),
timeout_in_seconds: None,
bind_address: None,
});
}
Some(BuiltinDnsOption::Quad9) => {
results.push(NameServerConfig {
address: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(9, 9, 9, 9), 53)),
timeout_in_seconds: None,
bind_address: None,
});
results.push(NameServerConfig {
address: SocketAddr::V6(SocketAddrV6::new(
Ipv6Addr::new(0x2620, 0, 0, 0, 0, 0, 0xfe, 0xfe),
53,
0,
0,
)),
timeout_in_seconds: None,
bind_address: None,
});
}
}
results
}
}