1use std::fmt::{Display, Formatter};
2use std::net::IpAddr;
3use thiserror::Error;
4
5pub trait Resolver {
7 fn lookup(&self, hostname: impl AsRef<str>) -> Result<ResolvedIpAddrs>;
9
10 #[must_use]
14 fn reverse_lookup(&self, addr: impl Into<IpAddr>) -> DnsEntry;
15
16 #[must_use]
21 fn reverse_lookup_with_asinfo(&self, addr: impl Into<IpAddr>) -> DnsEntry;
22
23 #[must_use]
35 fn lazy_reverse_lookup(&self, addr: impl Into<IpAddr>) -> DnsEntry;
36
37 #[must_use]
41 fn lazy_reverse_lookup_with_asinfo(&self, addr: impl Into<IpAddr>) -> DnsEntry;
42}
43
44pub type Result<T> = std::result::Result<T, Error>;
46
47#[derive(Error, Debug)]
49pub enum Error {
50 #[error("DNS lookup failed")]
51 LookupFailed(Box<dyn std::error::Error + Send + Sync + 'static>),
52 #[error("ASN origin query failed")]
53 QueryAsnOriginFailed,
54 #[error("ASN query failed")]
55 QueryAsnFailed,
56 #[error("origin query txt parse failed: {0}")]
57 ParseOriginQueryFailed(String),
58 #[error("asn query txt parse failed: {0}")]
59 ParseAsnQueryFailed(String),
60}
61
62#[derive(Debug, Clone)]
64pub struct ResolvedIpAddrs(pub(super) Vec<IpAddr>);
65
66impl ResolvedIpAddrs {
67 pub fn iter(&self) -> impl Iterator<Item = &'_ IpAddr> {
68 self.0.iter()
69 }
70}
71
72impl IntoIterator for ResolvedIpAddrs {
73 type Item = IpAddr;
74 type IntoIter = std::vec::IntoIter<Self::Item>;
75
76 fn into_iter(self) -> Self::IntoIter {
77 self.0.into_iter()
78 }
79}
80
81#[derive(Debug, Clone)]
83pub enum DnsEntry {
84 Pending(IpAddr),
86 Resolved(Resolved),
88 NotFound(Unresolved),
90 Failed(IpAddr),
92 Timeout(IpAddr),
94}
95
96#[derive(Debug, Clone)]
98pub struct ResolvedHostnames<'a>(pub(super) &'a [String]);
99
100impl<'a> Iterator for ResolvedHostnames<'a> {
101 type Item = &'a str;
102
103 fn next(&mut self) -> Option<Self::Item> {
104 self.0.iter().next().map(String::as_str)
105 }
106}
107
108impl DnsEntry {
109 #[must_use]
111 pub fn hostnames(&self) -> ResolvedHostnames<'_> {
112 match self {
113 Self::Resolved(Resolved::WithAsInfo(_, hosts, _) | Resolved::Normal(_, hosts)) => {
114 ResolvedHostnames(hosts)
115 }
116 Self::Pending(_) | Self::Timeout(_) | Self::NotFound(_) | Self::Failed(_) => {
117 ResolvedHostnames(&[])
118 }
119 }
120 }
121}
122
123#[derive(Debug, Clone)]
125pub enum Resolved {
126 Normal(IpAddr, Vec<String>),
128 WithAsInfo(IpAddr, Vec<String>, AsInfo),
130}
131
132#[derive(Debug, Clone)]
134pub enum Unresolved {
135 Normal(IpAddr),
137 WithAsInfo(IpAddr, AsInfo),
139}
140
141#[derive(Debug, Clone, Default)]
143pub struct AsInfo {
144 pub asn: String,
148 pub prefix: String,
152 pub cc: String,
156 pub registry: String,
160 pub allocated: String,
164 pub name: String,
168}
169
170impl Display for DnsEntry {
171 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
172 #[allow(clippy::match_same_arms)]
173 match self {
174 Self::Resolved(Resolved::Normal(_, hosts)) => write!(f, "{}", hosts.join(" ")),
175 Self::Resolved(Resolved::WithAsInfo(_, hosts, asinfo)) => {
176 write!(f, "AS{} {}", asinfo.asn, hosts.join(" "))
177 }
178 Self::Pending(ip) => write!(f, "{ip}"),
179 Self::Timeout(ip) => write!(f, "Timeout: {ip}"),
180 Self::NotFound(Unresolved::Normal(ip)) => write!(f, "{ip}"),
181 Self::NotFound(Unresolved::WithAsInfo(ip, asinfo)) => {
182 write!(f, "AS{} {}", asinfo.asn, ip)
183 }
184 Self::Failed(ip) => write!(f, "Failed: {ip}"),
185 }
186 }
187}