307 lines
11 KiB
Rust
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
|
|
}
|
|
}
|