mod fast;
use std::{
io,
net::SocketAddr,
sync::{Arc, OnceLock},
};
pub use hickory_resolver::config::{ResolverConfig, ResolverOpts};
use hickory_resolver::{
config::{LookupIpStrategy, NameServerConfigGroup},
lookup_ip::LookupIpIntoIter,
system_conf, TokioAsyncResolver,
};
use moka::future::Cache;
use rquest::dns::{Addrs, Name, Resolve, Resolving};
use tokio::sync::OnceCell;
static DNS_RESOLVER: OnceLock<Cache<u8, Arc<HickoryDnsResolver>>> = OnceLock::new();
fn from_strategy(strategy: LookupIpStrategy) -> u8 {
match strategy {
LookupIpStrategy::Ipv4Only => 0,
LookupIpStrategy::Ipv6Only => 1,
LookupIpStrategy::Ipv4AndIpv6 => 2,
LookupIpStrategy::Ipv6thenIpv4 => 3,
LookupIpStrategy::Ipv4thenIpv6 => 4,
}
}
pub async fn get_dns_resolver(ip_strategy: LookupIpStrategy) -> Arc<HickoryDnsResolver> {
let cache = DNS_RESOLVER.get_or_init(|| Cache::builder().max_capacity(5).build());
cache
.get_with(from_strategy(ip_strategy), async move {
Arc::new(HickoryDnsResolver::new(ip_strategy))
})
.await
}
#[derive(Debug, Clone)]
pub(crate) struct HickoryDnsResolver {
state: Arc<OnceCell<TokioAsyncResolver>>,
ip_strategy: LookupIpStrategy,
}
impl HickoryDnsResolver {
pub(crate) fn new(ip_strategy: LookupIpStrategy) -> Self {
Self {
state: Arc::new(OnceCell::new()),
ip_strategy,
}
}
}
struct SocketAddrs {
iter: LookupIpIntoIter,
}
impl Resolve for HickoryDnsResolver {
fn resolve(&self, name: Name) -> Resolving {
let resolver = self.clone();
Box::pin(async move {
let resolver = resolver
.state
.get_or_try_init(|| new_resolver(resolver.ip_strategy))
.await?;
let lookup = resolver.lookup_ip(name.as_str()).await?;
let addrs: Addrs = Box::new(SocketAddrs {
iter: lookup.into_iter(),
});
Ok(addrs)
})
}
}
impl Iterator for SocketAddrs {
type Item = SocketAddr;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|ip_addr| SocketAddr::new(ip_addr, 0))
}
}
async fn new_resolver(ip_strategy: LookupIpStrategy) -> io::Result<TokioAsyncResolver> {
let (default_config, mut opts) = match system_conf::read_system_conf() {
Ok((config, opts)) => (config, opts),
Err(err) => {
tracing::warn!("Error reading DNS system conf: {}", err);
let mut group = NameServerConfigGroup::new();
group.extend(NameServerConfigGroup::google().into_inner());
group.extend(NameServerConfigGroup::cloudflare().into_inner());
group.extend(NameServerConfigGroup::quad9().into_inner());
let config = ResolverConfig::from_parts(None, vec![], group);
(config, ResolverOpts::default())
}
};
opts.use_hosts_file = true;
opts.ip_strategy = ip_strategy;
let config = fast::FASTEST_DNS_CONFIG
.get_or_try_init(fast::load_fastest_dns)
.await
.cloned()
.unwrap_or(default_config);
Ok(TokioAsyncResolver::tokio(config, opts))
}