use std::fmt::{Display, Formatter};
use std::net::IpAddr;
use thiserror::Error;
pub trait Resolver {
fn lookup(&self, hostname: impl AsRef<str>) -> Result<ResolvedIpAddrs>;
#[must_use]
fn reverse_lookup(&self, addr: impl Into<IpAddr>) -> DnsEntry;
#[must_use]
fn reverse_lookup_with_asinfo(&self, addr: impl Into<IpAddr>) -> DnsEntry;
#[must_use]
fn lazy_reverse_lookup(&self, addr: impl Into<IpAddr>) -> DnsEntry;
#[must_use]
fn lazy_reverse_lookup_with_asinfo(&self, addr: impl Into<IpAddr>) -> DnsEntry;
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug)]
pub enum Error {
#[error("DNS lookup failed")]
LookupFailed(Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("ASN origin query failed")]
QueryAsnOriginFailed,
#[error("ASN query failed")]
QueryAsnFailed,
#[error("origin query txt parse failed: {0}")]
ParseOriginQueryFailed(String),
#[error("asn query txt parse failed: {0}")]
ParseAsnQueryFailed(String),
}
#[derive(Debug, Clone)]
pub struct ResolvedIpAddrs(pub(super) Vec<IpAddr>);
impl ResolvedIpAddrs {
pub fn iter(&self) -> impl Iterator<Item = &'_ IpAddr> {
self.0.iter()
}
}
impl IntoIterator for ResolvedIpAddrs {
type Item = IpAddr;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
#[derive(Debug, Clone)]
pub enum DnsEntry {
Pending(IpAddr),
Resolved(Resolved),
NotFound(Unresolved),
Failed(IpAddr),
Timeout(IpAddr),
}
#[derive(Debug, Clone)]
pub struct ResolvedHostnames<'a>(pub(super) &'a [String]);
impl<'a> Iterator for ResolvedHostnames<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
self.0.iter().next().map(String::as_str)
}
}
impl DnsEntry {
#[must_use]
pub fn hostnames(&self) -> ResolvedHostnames<'_> {
match self {
Self::Resolved(Resolved::WithAsInfo(_, hosts, _) | Resolved::Normal(_, hosts)) => {
ResolvedHostnames(hosts)
}
Self::Pending(_) | Self::Timeout(_) | Self::NotFound(_) | Self::Failed(_) => {
ResolvedHostnames(&[])
}
}
}
}
#[derive(Debug, Clone)]
pub enum Resolved {
Normal(IpAddr, Vec<String>),
WithAsInfo(IpAddr, Vec<String>, AsInfo),
}
#[derive(Debug, Clone)]
pub enum Unresolved {
Normal(IpAddr),
WithAsInfo(IpAddr, AsInfo),
}
#[derive(Debug, Clone, Default)]
pub struct AsInfo {
pub asn: String,
pub prefix: String,
pub cc: String,
pub registry: String,
pub allocated: String,
pub name: String,
}
impl Display for DnsEntry {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
#[allow(clippy::match_same_arms)]
match self {
Self::Resolved(Resolved::Normal(_, hosts)) => write!(f, "{}", hosts.join(" ")),
Self::Resolved(Resolved::WithAsInfo(_, hosts, asinfo)) => {
write!(f, "AS{} {}", asinfo.asn, hosts.join(" "))
}
Self::Pending(ip) => write!(f, "{ip}"),
Self::Timeout(ip) => write!(f, "Timeout: {ip}"),
Self::NotFound(Unresolved::Normal(ip)) => write!(f, "{ip}"),
Self::NotFound(Unresolved::WithAsInfo(ip, asinfo)) => {
write!(f, "AS{} {}", asinfo.asn, ip)
}
Self::Failed(ip) => write!(f, "Failed: {ip}"),
}
}
}