Files
hushd/src/config/resolver.rs
2025-01-12 14:10:23 -08:00

307 lines
11 KiB
Rust

use proptest::arbitrary::Arbitrary;
use proptest::strategy::{BoxedStrategy, Just, Strategy};
use serde::{Deserialize, Serialize};
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct DnsConfig {
built_in: Option<BuiltinDnsOption>,
pub local_domain: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub search_domains: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub name_servers: Vec<NameServerConfig>,
#[serde(default)]
pub retry_attempts: Option<u16>,
#[serde(default)]
pub cache_size: Option<u32>,
#[serde(default)]
pub max_concurrent_requests_for_query: Option<u16>,
#[serde(default)]
pub preserve_intermediates: Option<bool>,
#[serde(default)]
pub shuffle_dns_servers: Option<bool>,
#[serde(default)]
pub allow_mdns: Option<bool>,
}
impl Default for DnsConfig {
fn default() -> Self {
DnsConfig {
built_in: Some(BuiltinDnsOption::Cloudflare),
local_domain: None,
search_domains: vec![],
name_servers: vec![],
retry_attempts: None,
cache_size: None,
max_concurrent_requests_for_query: None,
preserve_intermediates: None,
shuffle_dns_servers: None,
allow_mdns: None,
}
}
}
impl Arbitrary for DnsConfig {
type Parameters = bool;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(always_use_builtin: Self::Parameters) -> Self::Strategy {
if always_use_builtin {
BuiltinDnsOption::arbitrary()
.prop_map(|x| DnsConfig {
built_in: Some(x),
local_domain: None,
search_domains: vec![],
name_servers: vec![],
retry_attempts: None,
cache_size: None,
max_concurrent_requests_for_query: None,
preserve_intermediates: None,
shuffle_dns_servers: None,
allow_mdns: None,
})
.boxed()
} else {
let built_in = proptest::option::of(BuiltinDnsOption::arbitrary());
built_in
.prop_flat_map(|built_in| {
let local_domain = proptest::option::of(domain_name_strat());
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(NameServerConfig::arbitrary(), min_servers..6);
let retry_attempts = proptest::option::of(u16::arbitrary());
let cache_size = proptest::option::of(u32::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,
retry_attempts,
cache_size,
max_concurrent_requests_for_query,
preserve_intermediates,
shuffle_dns_servers,
allow_mdns,
)
.prop_map(
move |(
local_domain,
search_domains,
name_servers,
retry_attempts,
cache_size,
max_concurrent_requests_for_query,
preserve_intermediates,
shuffle_dns_servers,
allow_mdns,
)| DnsConfig {
built_in,
local_domain,
search_domains,
name_servers,
retry_attempts,
cache_size,
max_concurrent_requests_for_query,
preserve_intermediates,
shuffle_dns_servers,
allow_mdns,
},
)
})
.boxed()
}
}
}
fn domain_name_strat() -> BoxedStrategy<String> {
let chunk = proptest::string::string_regex("[a-zA-Z0-9]{2,32}").unwrap();
let sets = proptest::collection::vec(chunk, 2..6);
sets.prop_map(|set| {
let mut output = String::new();
for x in set.into_iter() {
if !output.is_empty() {
output.push('.');
}
output.push_str(&x);
}
output
})
.boxed()
}
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
enum BuiltinDnsOption {
Google,
Cloudflare,
Quad9,
}
impl Arbitrary for BuiltinDnsOption {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
proptest::prop_oneof![
Just(BuiltinDnsOption::Google),
Just(BuiltinDnsOption::Cloudflare),
Just(BuiltinDnsOption::Quad9),
]
.boxed()
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct NameServerConfig {
pub address: SocketAddr,
#[serde(default)]
pub timeout_in_seconds: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bind_address: Option<SocketAddr>,
}
impl Arbitrary for NameServerConfig {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
(
SocketAddr::arbitrary(),
proptest::option::of(u64::arbitrary()),
proptest::option::of(SocketAddr::arbitrary()),
)
.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);
}
NameServerConfig {
address,
timeout_in_seconds,
bind_address,
}
})
.boxed()
}
}
fn clear_flow_and_scope_info(address: &mut SocketAddr) {
if let SocketAddr::V6(addr) = address {
addr.set_flowinfo(0);
addr.set_scope_id(0);
}
}
proptest::proptest! {
#[test]
fn valid_configs_parse(config in DnsConfig::arbitrary_with(false)) {
let toml = toml::to_string(&config).unwrap();
let reversed: DnsConfig = toml::from_str(&toml).unwrap();
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
}
}