use std::collections::HashMap;
use std::net::IpAddr;
use std::sync::Mutex;
use hickory_resolver::TokioResolver;
use hickory_resolver::proto::rr::{Name, RData};
#[derive(Debug)]
pub struct ReverseResolver {
resolver: Option<TokioResolver>,
cache: Mutex<HashMap<IpAddr, Option<String>>>,
}
impl ReverseResolver {
pub fn from_system() -> Self {
let resolver = TokioResolver::builder_tokio()
.and_then(|builder| builder.build())
.ok();
Self {
resolver,
cache: Mutex::new(HashMap::new()),
}
}
pub fn disabled() -> Self {
Self {
resolver: None,
cache: Mutex::new(HashMap::new()),
}
}
pub async fn hostname(&self, ip: IpAddr) -> Option<String> {
if let Some(hit) = self
.cache
.lock()
.expect("dns cache mutex poisoned")
.get(&ip)
.cloned()
{
return hit;
}
let resolved = match &self.resolver {
Some(resolver) => resolver
.reverse_lookup(Name::from(ip))
.await
.ok()
.and_then(|lookup| {
lookup
.answers()
.iter()
.find_map(|record| match &record.data {
RData::PTR(ptr) => {
Some(ptr.0.to_string().trim_end_matches('.').to_ascii_lowercase())
}
_ => None,
})
})
.filter(|name| !name.is_empty()),
None => None,
};
self.cache
.lock()
.expect("dns cache mutex poisoned")
.insert(ip, resolved.clone());
resolved
}
}
impl Default for ReverseResolver {
fn default() -> Self {
Self::from_system()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn disabled_resolver_returns_none() {
let resolver = ReverseResolver::disabled();
assert_eq!(resolver.hostname("127.0.0.1".parse().unwrap()).await, None);
}
}