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, pub local_domain: Option, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub search_domains: Vec, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub name_servers: Vec, #[serde(default)] pub retry_attempts: Option, #[serde(default)] pub cache_size: Option, #[serde(default)] pub max_concurrent_requests_for_query: Option, #[serde(default)] pub preserve_intermediates: Option, #[serde(default)] pub shuffle_dns_servers: Option, #[serde(default)] pub allow_mdns: Option, } 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; 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 { 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; 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, #[serde(default, skip_serializing_if = "Option::is_none")] pub bind_address: Option, } impl Arbitrary for NameServerConfig { type Parameters = (); type Strategy = BoxedStrategy; 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 { 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 } }