use std::net::SocketAddr;
use std::num::NonZeroUsize;
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use hickory_resolver::{
config::{ResolverConfig, ResolverOpts},
net::runtime::TokioRuntimeProvider,
Resolver,
};
use lru::LruCache;
use tokio::sync::{OnceCell, RwLock};
use tokio::time::timeout;
use crate::error::McError;
use crate::models::DnsInfo;
const DNS_CACHE_TTL: Duration = Duration::from_secs(300);
const DEFAULT_CACHE_SIZE: usize = 1024;
static RESOLVER: OnceCell<Arc<Resolver<TokioRuntimeProvider>>> = OnceCell::const_new();
async fn global_resolver() -> Arc<Resolver<TokioRuntimeProvider>> {
RESOLVER
.get_or_init(|| async {
let mut opts = ResolverOpts::default();
opts.cache_size = 1000;
opts.positive_min_ttl = Some(Duration::from_secs(60));
opts.negative_min_ttl = Some(Duration::from_secs(10));
Arc::new(
Resolver::builder_with_config(
ResolverConfig::default(),
TokioRuntimeProvider::default(),
)
.with_options(opts)
.build()
.expect("failed to build DNS resolver"),
)
})
.await
.clone()
}
#[derive(Clone)]
pub struct DnsResolver {
dns_cache: Arc<RwLock<LruCache<String, (SocketAddr, SystemTime)>>>,
srv_cache: Arc<RwLock<LruCache<String, ((String, u16), SystemTime)>>>,
}
impl DnsResolver {
pub fn new() -> Self {
Self::with_cache_size(DEFAULT_CACHE_SIZE)
}
pub fn with_cache_size(size: usize) -> Self {
let cap = NonZeroUsize::new(size).expect("DNS cache size must be > 0");
Self {
dns_cache: Arc::new(RwLock::new(LruCache::new(cap))),
srv_cache: Arc::new(RwLock::new(LruCache::new(cap))),
}
}
pub async fn clear(&self) {
self.dns_cache.write().await.clear();
self.srv_cache.write().await.clear();
}
pub async fn dns_len(&self) -> usize { self.dns_cache.read().await.len() }
pub async fn srv_len(&self) -> usize { self.srv_cache.read().await.len() }
pub async fn lookup_srv(
&self,
host: &str,
tout: Duration,
) -> Option<(String, u16)> {
{
let cache = self.srv_cache.read().await;
if let Some(((h, p), ts)) = cache.peek(host)
&& ts.elapsed().unwrap_or(Duration::MAX) < DNS_CACHE_TTL {
return Some((h.clone(), *p));
}
}
let name = format!("_minecraft._tcp.{}", host);
let resolver = global_resolver().await;
let res = timeout(tout, resolver.srv_lookup(&name))
.await.ok()?.ok()?;
let record = res.answers().iter().find_map(|r| {
if let hickory_resolver::proto::rr::RData::SRV(s) = &r.data {
Some((
s.target.to_string().trim_end_matches('.').to_string(),
s.port,
))
} else {
None
}
})?;
let mut cache = self.srv_cache.write().await;
if !cache.contains(host) {
cache.put(host.to_string(), (record.clone(), SystemTime::now()));
}
Some(record)
}
pub async fn resolve(
&self,
host: &str,
port: u16,
) -> Result<(SocketAddr, DnsInfo), McError> {
let key = format!("{}:{}", host, port);
{
let cache = self.dns_cache.read().await;
if let Some((addr, ts)) = cache.peek(&key)
&& ts.elapsed().unwrap_or(Duration::MAX) < DNS_CACHE_TTL {
return Ok((*addr, make_dns_info(&[*addr])));
}
}
let host_port = format!("{}:{}", host, port);
let addrs: Vec<SocketAddr> = tokio::task::spawn_blocking(move || {
std::net::ToSocketAddrs::to_socket_addrs(&host_port).map(|i| i.collect())
})
.await
.map_err(|e| McError::dns(e.to_string()))?
.map_err(|e| McError::dns(e.to_string()))?;
let addr = addrs
.iter()
.find(|a| a.is_ipv4()) .or_else(|| addrs.first()) .copied()
.ok_or_else(|| McError::dns("no addresses resolved"))?;
let info = make_dns_info(&addrs);
let mut cache = self.dns_cache.write().await;
if !cache.contains(&key) {
cache.put(key, (addr, SystemTime::now()));
}
Ok((addr, info))
}
}
impl Default for DnsResolver {
fn default() -> Self { Self::new() }
}
fn make_dns_info(addrs: &[SocketAddr]) -> DnsInfo {
DnsInfo {
a_records: addrs.iter().map(|a| a.ip().to_string()).collect(),
cname: None,
ttl: DNS_CACHE_TTL.as_secs() as u32,
}
}