trippy_dns/
resolver.rs

1use std::fmt::{Display, Formatter};
2use std::net::IpAddr;
3use thiserror::Error;
4
5/// A DNS resolver.
6pub trait Resolver {
7    /// Perform a blocking DNS hostname lookup and return the resolved IPv4 or IPv6 addresses.
8    fn lookup(&self, hostname: impl AsRef<str>) -> Result<ResolvedIpAddrs>;
9
10    /// Perform a blocking reverse DNS lookup of `IpAddr` and return a `DnsEntry`.
11    ///
12    /// As this method is blocking it will never return a `DnsEntry::Pending`.
13    #[must_use]
14    fn reverse_lookup(&self, addr: impl Into<IpAddr>) -> DnsEntry;
15
16    /// Perform a blocking reverse DNS lookup of `IpAddr` and return a `DnsEntry` with `AS`
17    /// information.
18    ///
19    /// See [`Resolver::reverse_lookup`]
20    #[must_use]
21    fn reverse_lookup_with_asinfo(&self, addr: impl Into<IpAddr>) -> DnsEntry;
22
23    /// Perform a lazy reverse DNS lookup of `IpAddr` and return a `DnsEntry`.
24    ///
25    /// If the `IpAddr` has already been resolved then `DnsEntry::Resolved` is returned immediately.
26    ///
27    /// Otherwise, the `IpAddr` is enqueued to be resolved in the background and a
28    /// `DnsEntry::Pending` is returned.
29    ///
30    /// If the entry exists but is `DnsEntry::Timeout` then it is changed to be `DnsEntry::Pending`
31    /// and enqueued.
32    ///
33    /// If enqueuing times out then the entry is changed to be `DnsEntry::Timeout` and returned.
34    #[must_use]
35    fn lazy_reverse_lookup(&self, addr: impl Into<IpAddr>) -> DnsEntry;
36
37    /// Perform a lazy reverse DNS lookup of `IpAddr` and return a `DnsEntry` with `AS` information.
38    ///
39    /// See [`Resolver::lazy_reverse_lookup`]
40    #[must_use]
41    fn lazy_reverse_lookup_with_asinfo(&self, addr: impl Into<IpAddr>) -> DnsEntry;
42}
43
44/// A DNS resolver error result.
45pub type Result<T> = std::result::Result<T, Error>;
46
47/// A DNS resolver error.
48#[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/// The output of a successful DNS lookup.
63#[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/// The state of reverse DNS resolution.
82#[derive(Debug, Clone)]
83pub enum DnsEntry {
84    /// The reverse DNS resolution of `IpAddr` is pending.
85    Pending(IpAddr),
86    /// The reverse DNS resolution of `IpAddr` has resolved.
87    Resolved(Resolved),
88    /// The `IpAddr` could not be resolved.
89    NotFound(Unresolved),
90    /// The reverse DNS resolution of `IpAddr` failed.
91    Failed(IpAddr),
92    /// The reverse DNS resolution of `IpAddr` timed out.
93    Timeout(IpAddr),
94}
95
96/// The resolved hostnames of a `DnsEntry`.
97#[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    /// The resolved hostnames.
110    #[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/// Information about a resolved `IpAddr`.
124#[derive(Debug, Clone)]
125pub enum Resolved {
126    /// Resolved without `AsInfo`.
127    Normal(IpAddr, Vec<String>),
128    /// Resolved with `AsInfo`.
129    WithAsInfo(IpAddr, Vec<String>, AsInfo),
130}
131
132/// Information about an unresolved `IpAddr`.
133#[derive(Debug, Clone)]
134pub enum Unresolved {
135    /// Unresolved without `AsInfo`.
136    Normal(IpAddr),
137    /// Unresolved with `AsInfo`.
138    WithAsInfo(IpAddr, AsInfo),
139}
140
141/// Information about an autonomous System (AS).
142#[derive(Debug, Clone, Default)]
143pub struct AsInfo {
144    /// The autonomous system Number.
145    ///
146    /// This is returned without the AS prefix i.e. `12301`.
147    pub asn: String,
148    /// The AS prefix.
149    ///
150    /// Given in CIDR notation i.e. `81.0.100.0/22`.
151    pub prefix: String,
152    /// The country code.
153    ///
154    /// Given as a ISO format i.e. `HU`.
155    pub cc: String,
156    /// AS registry name.
157    ///
158    /// Given as a string i.e. `ripencc`.
159    pub registry: String,
160    /// Allocation date.
161    ///
162    /// Given as an ISO date i.e. `1999-02-25`.
163    pub allocated: String,
164    /// The autonomous system (AS) Name.
165    ///
166    /// Given as a string i.e. `INVITECH, HU`.
167    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}