use super::*;
use hickory_resolver::TokioResolver;
use itertools::Itertools;
use serial_test::serial;
use std::{
collections::HashMap,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
};
const GUARANTEED_BROKEN_IPS_1: &[IpAddr] = &[
IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1)),
IpAddr::V4(Ipv4Addr::new(198, 51, 100, 1)),
IpAddr::V6(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x1111)),
IpAddr::V6(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x1001)),
];
#[tokio::test]
async fn reqwest_with_custom_dns() {
let var_name = HickoryDnsResolver::new();
let resolver = var_name;
let client = reqwest::ClientBuilder::new()
.dns_resolver(resolver)
.build()
.unwrap();
let resp = client
.get("http://ifconfig.me:80")
.send()
.await
.unwrap()
.bytes()
.await
.unwrap();
assert!(!resp.is_empty());
}
#[tokio::test]
async fn dns_lookup() -> Result<(), ResolveError> {
let resolver = HickoryDnsResolver::new();
let domain = "ifconfig.me";
let addrs = resolver.resolve_str(domain).await?;
assert!(addrs.into_iter().next().is_some());
Ok(())
}
#[tokio::test]
async fn static_resolver_as_fallback() -> Result<(), ResolveError> {
let example_domain = "non-existent.nymvpn.com";
let mut resolver: HickoryDnsResolver = HickoryDnsResolver {
use_shared: false,
..Default::default()
};
let result = resolver.resolve_str(example_domain).await;
assert!(result.is_err());
resolver.static_base = Some(Default::default());
let mut addr_map = HashMap::new();
let example_ip4: IpAddr = "10.10.10.10".parse().unwrap();
let example_ip6: IpAddr = "dead::beef".parse().unwrap();
addr_map.insert(example_domain.to_string(), vec![example_ip4, example_ip6]);
resolver.set_fallback_addrs(addr_map);
let addrs = resolver.resolve_str(example_domain).await?.collect_vec();
assert!(addrs.contains(&example_ip4));
assert!(addrs.contains(&example_ip6));
Ok(())
}
#[tokio::test]
async fn set_name_servers_rebuilds_independent_resolver() -> Result<(), ResolveError> {
let resolver: HickoryDnsResolver = HickoryDnsResolver {
use_shared: false,
overall_dns_timeout: Duration::from_secs(5),
..Default::default()
};
assert_eq!(
resolver
.get_name_servers()
.iter()
.map(|ns| ns.ip)
.collect::<Vec<_>>(),
default_nameserver_group_ipv4_only()
.iter()
.map(|ns| ns.ip)
.collect::<Vec<_>>()
);
assert!(resolver.resolve_str("ifconfig.me").await?.next().is_some());
let broken_domain = Arc::<str>::from("cloudflare-dns.com");
let broken_ns = GUARANTEED_BROKEN_IPS_1
.iter()
.map(|ip| NameServerConfig::tls(*ip, broken_domain.clone()))
.collect::<Vec<_>>();
resolver.set_name_servers(broken_ns.clone());
assert_eq!(
resolver
.get_name_servers()
.iter()
.map(|ns| ns.ip)
.collect::<Vec<_>>(),
broken_ns.iter().map(|ns| ns.ip).collect::<Vec<_>>()
);
let result = resolver.resolve_str("non-existent.nymtech.net").await;
assert!(result.is_err_and(|e| e.is_timeout()));
Ok(())
}
#[tokio::test]
#[serial]
async fn set_name_servers_on_shared_resolver() -> Result<(), ResolveError> {
let default_ns = default_nameserver_group_ipv4_only();
let warm_sibling: HickoryDnsResolver = HickoryDnsResolver {
overall_dns_timeout: Duration::from_secs(5),
..Default::default()
};
assert!(
warm_sibling
.resolve_str("ifconfig.me")
.await?
.next()
.is_some()
);
let resolver1: HickoryDnsResolver = HickoryDnsResolver {
overall_dns_timeout: Duration::from_secs(5),
..Default::default()
};
let broken_domain = Arc::<str>::from("cloudflare-dns.com");
let broken_ns = GUARANTEED_BROKEN_IPS_1
.iter()
.map(|ip| NameServerConfig::tls(*ip, broken_domain.clone()))
.collect::<Vec<_>>();
resolver1.set_name_servers(broken_ns);
let resolver2: HickoryDnsResolver = HickoryDnsResolver {
overall_dns_timeout: Duration::from_secs(5),
..Default::default()
};
let result2 = resolver2.resolve_str("non-existent.nymtech.net").await;
let warm_result = warm_sibling.resolve_str("non-existent.nymtech.net").await;
resolver1.set_name_servers(default_ns);
assert!(warm_result.is_err_and(|e| e.is_timeout()));
assert!(result2.is_err_and(|e| e.is_timeout()));
Ok(())
}
#[test]
fn edge1_streaming_gateway_com_is_pinned_to_live_ipv4() {
let addrs = constants::default_static_addrs();
let pinned = addrs
.get(constants::NYM_VPN_API_EDGE1_STREAMING_GATEWAY_COM)
.expect("edge1.streaming-gateway.com must be statically pinned after smoke");
assert_eq!(
pinned,
&constants::NYM_VPN_API_EDGE1_STREAMING_GATEWAY_COM_IPS.to_vec()
);
assert_eq!(pinned, &vec![IpAddr::V4(Ipv4Addr::new(139, 162, 57, 231))]);
}
#[tokio::test]
async fn trial_nameservers() {
let good_cf_ip = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
let mut ns_ips = GUARANTEED_BROKEN_IPS_1.to_vec();
ns_ips.push(good_cf_ip);
let domain = Arc::<str>::from("cloudflare-dns.com");
let path = Arc::<str>::from("/dns-query");
let broken_ns_https = GUARANTEED_BROKEN_IPS_1
.iter()
.chain([&good_cf_ip])
.map(|ip| NameServerConfig::https(*ip, domain.clone(), Some(path.clone())))
.collect::<Vec<_>>();
for (ns, result) in crate::dns::trial::trial_nameservers_inner(&broken_ns_https).await {
if ns.ip == good_cf_ip {
assert!(result.is_ok())
} else {
assert!(result.is_err())
}
}
}
mod failure_test {
use super::*;
fn build_broken_resolver() -> Result<TokioResolver, ResolveError> {
info!("building new faulty resolver");
let domain = Arc::<str>::from("cloudflare-dns.com");
let path = Arc::<str>::from("/dns-query");
let broken_ns_group = GUARANTEED_BROKEN_IPS_1
.iter()
.map(|ip| NameServerConfig::tls(*ip, domain.clone()))
.chain(
GUARANTEED_BROKEN_IPS_1
.iter()
.map(|ip| NameServerConfig::https(*ip, domain.clone(), Some(path.clone())))
.collect::<Vec<_>>(),
)
.collect::<Vec<_>>();
configure_and_build_resolver(broken_ns_group)
}
#[tokio::test]
async fn dns_lookup_failures() -> Result<(), ResolveError> {
let time_start = std::time::Instant::now();
let r = OnceCell::new();
r.set(build_broken_resolver().expect("failed to build resolver"))
.expect("broken resolver init error");
let resolver = HickoryDnsResolver {
use_shared: false,
state: Arc::new(ArcSwap::from_pointee(r)),
overall_dns_timeout: Duration::from_secs(5),
..Default::default()
};
build_broken_resolver()?;
let domain = "ifconfig.me";
let result = resolver.resolve_str(domain).await;
assert!(result.is_err_and(|e| e.is_timeout()));
let duration = time_start.elapsed();
assert!(duration < resolver.overall_dns_timeout + Duration::from_secs(1));
Ok(())
}
#[tokio::test]
async fn fallback_to_static() -> Result<(), ResolveError> {
let r = OnceCell::new();
r.set(build_broken_resolver().expect("failed to build resolver"))
.expect("broken resolver init error");
let resolver = HickoryDnsResolver {
use_shared: false,
state: Arc::new(ArcSwap::from_pointee(r)),
static_base: Some(Default::default()),
overall_dns_timeout: Duration::from_secs(5),
..Default::default()
};
build_broken_resolver()?;
let domain = "nymvpn.com";
let _ = resolver
.resolve_str(domain)
.await
.expect("failed to resolve address in static lookup");
let domain = "non-existent.nymtech.net";
let result = resolver.resolve_str(domain).await;
assert!(result.is_err_and(|e| e.is_timeout()));
Ok(())
}
#[tokio::test]
#[serial]
async fn setting_dns_fallbacks_with_shared_resolver() -> Result<(), ResolveError> {
let resolver1 = HickoryDnsResolver::new();
let mut resolver = HickoryDnsResolver::new();
struct ClearPreresolveOnDrop(HickoryDnsResolver);
impl Drop for ClearPreresolveOnDrop {
fn drop(&mut self) {
self.0.clear_preresolve();
}
}
let _clear_preresolve = ClearPreresolveOnDrop(resolver1.clone());
let example_domains = [
String::from("static1.nymvpn.com"),
String::from("static2.nymvpn.com"),
];
let mut addr_map1 = HashMap::new();
addr_map1.insert(
example_domains[0].clone(),
vec![Ipv4Addr::new(10, 10, 10, 10).into()],
);
addr_map1.insert(
example_domains[1].clone(),
vec![Ipv4Addr::new(1, 1, 1, 1).into()],
);
resolver.set_static_preresolve(addr_map1);
let addrs = resolver1
.resolve_str(&example_domains[0])
.await
.expect("domain expected to be in pre-resolve")
.collect_vec();
assert_eq!(addrs, vec![IpAddr::V4(Ipv4Addr::new(10, 10, 10, 10))]);
resolver.clear_preresolve();
let prereslve_lookup = resolver1
.static_base
.as_ref()
.unwrap()
.get()
.unwrap()
.pre_resolve(&example_domains[0]);
assert!(prereslve_lookup.is_none());
Ok(())
}
#[tokio::test]
#[cfg(any())] async fn dns_lookup_failure_on_shared() -> Result<(), ResolveError> {
let resolver1 = HickoryDnsResolver::shared();
let time_start = std::time::Instant::now();
let resolver = HickoryDnsResolver::shared();
let domain = "rpc.nymtech.net";
let _ = resolver
.resolve_str(domain)
.await
.expect("failed to resolve address in static lookup");
let lookup_dur = Instant::now() - time_start;
assert!(
lookup_dur > resolver.overall_dns_timeout,
"expected lookup timeout - took {}ms",
(lookup_dur).as_millis()
);
let time_start = std::time::Instant::now();
let domain = "rpc.nymtech.net";
let _ = resolver1
.resolve_str(domain)
.await
.expect("domain expected to be in pre-resolve");
let lookup_dur = std::time::Instant::now() - time_start;
assert!(
lookup_dur < Duration::from_millis(10),
"expected instant - took {}ms",
(lookup_dur).as_millis()
);
let domain = "non-existent.nymtech.net";
let result = resolver.resolve_str(domain).await;
assert!(result.is_err());
Ok(())
}
}