Skip to content

Commit 71cee9e

Browse files
committed
feat: DNS resolver trait
1 parent 4886027 commit 71cee9e

File tree

10 files changed

+192
-167
lines changed

10 files changed

+192
-167
lines changed

iroh-dns-server/examples/resolve.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ async fn main() -> anyhow::Result<()> {
5454
DnsResolver::with_nameserver(addr)
5555
} else {
5656
match args.env {
57-
Env::Staging | Env::Prod => DnsResolver::new(),
57+
Env::Staging | Env::Prod => DnsResolver::default(),
5858
Env::Dev => {
5959
DnsResolver::with_nameserver(DEV_DNS_SERVER.parse().expect("valid address"))
6060
}

iroh-dns-server/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,25 +116,25 @@ mod tests {
116116
// resolve root record
117117
let name = Name::from_utf8(format!("{pubkey}."))?;
118118
let res = resolver.lookup_txt(name, DNS_TIMEOUT).await?;
119-
let records = res.into_iter().map(|t| t.to_string()).collect::<Vec<_>>();
119+
let records = res.map(|t| t.to_string()).collect::<Vec<_>>();
120120
assert_eq!(records, vec!["hi0".to_string()]);
121121

122122
// resolve level one record
123123
let name = Name::from_utf8(format!("_hello.{pubkey}."))?;
124124
let res = resolver.lookup_txt(name, DNS_TIMEOUT).await?;
125-
let records = res.into_iter().map(|t| t.to_string()).collect::<Vec<_>>();
125+
let records = res.map(|t| t.to_string()).collect::<Vec<_>>();
126126
assert_eq!(records, vec!["hi1".to_string()]);
127127

128128
// resolve level two record
129129
let name = Name::from_utf8(format!("_hello.world.{pubkey}."))?;
130130
let res = resolver.lookup_txt(name, DNS_TIMEOUT).await?;
131-
let records = res.into_iter().map(|t| t.to_string()).collect::<Vec<_>>();
131+
let records = res.map(|t| t.to_string()).collect::<Vec<_>>();
132132
assert_eq!(records, vec!["hi2".to_string()]);
133133

134134
// resolve multiple records for same name
135135
let name = Name::from_utf8(format!("multiple.{pubkey}."))?;
136136
let res = resolver.lookup_txt(name, DNS_TIMEOUT).await?;
137-
let records = res.into_iter().map(|t| t.to_string()).collect::<Vec<_>>();
137+
let records = res.map(|t| t.to_string()).collect::<Vec<_>>();
138138
assert_eq!(records, vec!["hi3".to_string(), "hi4".to_string()]);
139139

140140
// resolve A record

iroh-relay/src/dns.rs

Lines changed: 148 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,41 @@
33
use std::{
44
fmt::{self, Write},
55
future::Future,
6-
net::{IpAddr, Ipv6Addr, SocketAddr},
6+
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
7+
sync::Arc,
78
};
89

910
use anyhow::{bail, Context, Result};
1011
use hickory_resolver::{name_server::TokioConnectionProvider, TokioResolver};
1112
use iroh_base::NodeId;
1213
use n0_future::{
14+
boxed::BoxFuture,
1315
time::{self, Duration},
1416
StreamExt,
1517
};
1618
use url::Url;
1719

18-
use crate::node_info::NodeInfo;
20+
use crate::{
21+
defaults::timeouts::DNS_TIMEOUT,
22+
node_info::{self, NodeInfo},
23+
};
1924

2025
/// The n0 testing DNS node origin, for production.
2126
pub const N0_DNS_NODE_ORIGIN_PROD: &str = "dns.iroh.link";
2227
/// The n0 testing DNS node origin, for testing.
2328
pub const N0_DNS_NODE_ORIGIN_STAGING: &str = "staging-dns.iroh.link";
2429

25-
/// The DNS resolver used throughout `iroh`.
30+
/// The default DNS resolver used throughout `iroh`.
2631
#[derive(Debug, Clone)]
27-
pub struct DnsResolver(TokioResolver);
32+
struct HickoryResolver(TokioResolver);
2833

29-
impl DnsResolver {
34+
impl HickoryResolver {
3035
/// Create a new DNS resolver with sensible cross-platform defaults.
3136
///
3237
/// We first try to read the system's resolver from `/etc/resolv.conf`.
3338
/// This does not work at least on some Androids, therefore we fallback
3439
/// to the default `ResolverConfig` which uses eg. to google's `8.8.8.8` or `8.8.4.4`.
35-
pub fn new() -> Self {
40+
fn new() -> Self {
3641
let (system_config, mut options) =
3742
hickory_resolver::system_conf::read_system_conf().unwrap_or_default();
3843

@@ -57,11 +62,11 @@ impl DnsResolver {
5762
let mut builder =
5863
TokioResolver::builder_with_config(config, TokioConnectionProvider::default());
5964
*builder.options_mut() = options;
60-
DnsResolver(builder.build())
65+
HickoryResolver(builder.build())
6166
}
6267

6368
/// Create a new DNS resolver configured with a single UDP DNS nameserver.
64-
pub fn with_nameserver(nameserver: SocketAddr) -> Self {
69+
fn with_nameserver(nameserver: SocketAddr) -> Self {
6570
let mut config = hickory_resolver::config::ResolverConfig::new();
6671
let nameserver_config = hickory_resolver::config::NameServerConfig::new(
6772
nameserver,
@@ -71,19 +76,115 @@ impl DnsResolver {
7176

7277
let builder =
7378
TokioResolver::builder_with_config(config, TokioConnectionProvider::default());
74-
DnsResolver(builder.build())
79+
HickoryResolver(builder.build())
80+
}
81+
}
82+
83+
impl Default for HickoryResolver {
84+
fn default() -> Self {
85+
Self::new()
7586
}
87+
}
7688

77-
/// Removes all entries from the cache.
78-
pub fn clear_cache(&self) {
89+
/// Trait for DNS resolvers used in iroh.
90+
pub trait Resolver: std::fmt::Debug + Send + Sync + 'static {
91+
/// Lookup an IPv4 address.
92+
fn lookup_ipv4(&self, host: String) -> BoxFuture<Result<BoxIter<Ipv4Addr>>>;
93+
/// Lookup an IPv6 address.
94+
fn lookup_ipv6(&self, host: String) -> BoxFuture<Result<BoxIter<Ipv6Addr>>>;
95+
/// Lookup TXT records.
96+
fn lookup_txt(&self, host: String) -> BoxFuture<Result<BoxIter<TxtRecord>>>;
97+
/// Clear the internal cache.
98+
fn clear_cache(&self);
99+
}
100+
101+
/// Boxed iterator alias.
102+
pub type BoxIter<T> = Box<dyn Iterator<Item = T> + Send + 'static>;
103+
104+
impl Resolver for HickoryResolver {
105+
fn lookup_ipv4(&self, host: String) -> BoxFuture<Result<BoxIter<Ipv4Addr>>> {
106+
let this = self.0.clone();
107+
Box::pin(async move {
108+
let addrs = this.ipv4_lookup(host).await?;
109+
let iter: BoxIter<Ipv4Addr> = Box::new(addrs.into_iter().map(Ipv4Addr::from));
110+
Ok(iter)
111+
})
112+
}
113+
114+
fn lookup_ipv6(&self, host: String) -> BoxFuture<Result<BoxIter<Ipv6Addr>>> {
115+
let this = self.0.clone();
116+
Box::pin(async move {
117+
let addrs = this.ipv6_lookup(host).await?;
118+
let iter: BoxIter<Ipv6Addr> = Box::new(addrs.into_iter().map(Ipv6Addr::from));
119+
Ok(iter)
120+
})
121+
}
122+
123+
fn lookup_txt(&self, host: String) -> BoxFuture<Result<BoxIter<TxtRecord>>> {
124+
let this = self.0.clone();
125+
Box::pin(async move {
126+
let lookup = this.txt_lookup(host).await?;
127+
let iter: BoxIter<TxtRecord> = Box::new(
128+
lookup
129+
.into_iter()
130+
.map(|txt| TxtRecord::from_iter(txt.iter().cloned())),
131+
);
132+
Ok(iter)
133+
})
134+
}
135+
136+
fn clear_cache(&self) {
79137
self.0.clear_cache();
80138
}
139+
}
140+
141+
impl From<TokioResolver> for HickoryResolver {
142+
fn from(resolver: TokioResolver) -> Self {
143+
HickoryResolver(resolver)
144+
}
145+
}
146+
147+
///
148+
#[derive(Debug, Clone)]
149+
pub struct DnsResolver(Arc<dyn Resolver>);
150+
151+
impl std::ops::Deref for DnsResolver {
152+
type Target = dyn Resolver;
153+
fn deref(&self) -> &Self::Target {
154+
&*self.0
155+
}
156+
}
157+
158+
impl Default for DnsResolver {
159+
fn default() -> Self {
160+
Self::new_with_system_defaults()
161+
}
162+
}
163+
164+
impl DnsResolver {
165+
///
166+
pub fn new(resolver: impl Resolver) -> Self {
167+
Self(Arc::new(resolver))
168+
}
169+
170+
///
171+
pub fn new_with_system_defaults() -> Self {
172+
Self::new(HickoryResolver::new())
173+
}
174+
175+
///
176+
pub fn with_nameserver(nameserver: SocketAddr) -> Self {
177+
Self::new(HickoryResolver::with_nameserver(nameserver))
178+
}
81179

82180
/// Lookup a TXT record.
83-
pub async fn lookup_txt(&self, host: impl ToString, timeout: Duration) -> Result<TxtLookup> {
84-
let host = host.to_string();
85-
let res = time::timeout(timeout, self.0.txt_lookup(host)).await??;
86-
Ok(TxtLookup(res))
181+
pub async fn lookup_txt(
182+
&self,
183+
host: impl ToString,
184+
timeout: Duration,
185+
) -> Result<impl Iterator<Item = TxtRecord>> {
186+
let res = time::timeout(timeout, self.0.lookup_txt(host.to_string())).await??;
187+
Ok(res)
87188
}
88189

89190
/// Perform an ipv4 lookup with a timeout.
@@ -92,9 +193,8 @@ impl DnsResolver {
92193
host: impl ToString,
93194
timeout: Duration,
94195
) -> Result<impl Iterator<Item = IpAddr>> {
95-
let host = host.to_string();
96-
let addrs = time::timeout(timeout, self.0.ipv4_lookup(host)).await??;
97-
Ok(addrs.into_iter().map(|ip| IpAddr::V4(ip.0)))
196+
let addrs = time::timeout(timeout, self.0.lookup_ipv4(host.to_string())).await??;
197+
Ok(addrs.map(|ip| IpAddr::V4(ip)))
98198
}
99199

100200
/// Perform an ipv6 lookup with a timeout.
@@ -103,9 +203,8 @@ impl DnsResolver {
103203
host: impl ToString,
104204
timeout: Duration,
105205
) -> Result<impl Iterator<Item = IpAddr>> {
106-
let host = host.to_string();
107-
let addrs = time::timeout(timeout, self.0.ipv6_lookup(host)).await??;
108-
Ok(addrs.into_iter().map(|ip| IpAddr::V6(ip.0)))
206+
let addrs = time::timeout(timeout, self.0.lookup_ipv6(host.to_string())).await??;
207+
Ok(addrs.map(|ip| IpAddr::V6(ip)))
109208
}
110209

111210
/// Resolve IPv4 and IPv6 in parallel with a timeout.
@@ -221,21 +320,19 @@ impl DnsResolver {
221320
/// To lookup nodes that published their node info to the DNS servers run by n0,
222321
/// pass [`N0_DNS_NODE_ORIGIN_PROD`] as `origin`.
223322
pub async fn lookup_node_by_id(&self, node_id: &NodeId, origin: &str) -> Result<NodeInfo> {
224-
let attrs = crate::node_info::TxtAttrs::<crate::node_info::IrohAttr>::lookup_by_id(
225-
self, node_id, origin,
226-
)
227-
.await?;
228-
let info = attrs.into();
229-
Ok(info)
323+
let name = node_info::node_domain(node_id, origin);
324+
let name = node_info::ensure_iroh_txt_label(name);
325+
let lookup = self.lookup_txt(name.clone(), DNS_TIMEOUT).await?;
326+
let attrs = node_info::TxtAttrs::from_txt_lookup(name, lookup)?;
327+
Ok(attrs.into())
230328
}
231329

232330
/// Looks up node info by DNS name.
233331
pub async fn lookup_node_by_domain_name(&self, name: &str) -> Result<NodeInfo> {
234-
let attrs =
235-
crate::node_info::TxtAttrs::<crate::node_info::IrohAttr>::lookup_by_name(self, name)
236-
.await?;
237-
let info = attrs.into();
238-
Ok(info)
332+
let name = node_info::ensure_iroh_txt_label(name.to_string());
333+
let lookup = self.lookup_txt(name.clone(), DNS_TIMEOUT).await?;
334+
let attrs = node_info::TxtAttrs::from_txt_lookup(name, lookup)?;
335+
Ok(attrs.into())
239336
}
240337

241338
/// Looks up node info by DNS name in a staggered fashion.
@@ -270,52 +367,35 @@ impl DnsResolver {
270367
}
271368
}
272369

273-
impl Default for DnsResolver {
274-
fn default() -> Self {
275-
Self::new()
276-
}
277-
}
278-
279-
impl From<TokioResolver> for DnsResolver {
280-
fn from(resolver: TokioResolver) -> Self {
281-
DnsResolver(resolver)
282-
}
283-
}
284-
285-
/// TXT records returned from [`DnsResolver::lookup_txt`]
370+
/// Record data for a TXT record.
286371
#[derive(Debug, Clone)]
287-
pub struct TxtLookup(pub(crate) hickory_resolver::lookup::TxtLookup);
372+
pub struct TxtRecord(Vec<Box<[u8]>>);
288373

289-
impl From<hickory_resolver::lookup::TxtLookup> for TxtLookup {
290-
fn from(value: hickory_resolver::lookup::TxtLookup) -> Self {
291-
Self(value)
374+
impl TxtRecord {
375+
///
376+
pub fn iter(&self) -> impl Iterator<Item = &[u8]> {
377+
self.0.iter().map(|x| x.as_ref())
292378
}
293-
}
294-
295-
impl IntoIterator for TxtLookup {
296-
type Item = TXT;
297-
298-
type IntoIter = Box<dyn Iterator<Item = TXT>>;
299379

300-
fn into_iter(self) -> Self::IntoIter {
301-
Box::new(self.0.into_iter().map(TXT))
380+
///
381+
pub fn to_strings(&self) -> impl Iterator<Item = String> + '_ {
382+
self.iter()
383+
.map(|cstr| String::from_utf8_lossy(&cstr).to_string())
302384
}
303385
}
304386

305-
/// Record data for a TXT record
306-
#[derive(Debug, Clone)]
307-
pub struct TXT(hickory_resolver::proto::rr::rdata::TXT);
308-
309-
impl TXT {
310-
/// Returns the raw character strings of this TXT record.
311-
pub fn txt_data(&self) -> &[Box<[u8]>] {
312-
self.0.txt_data()
387+
impl std::fmt::Display for TxtRecord {
388+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
389+
for s in self.iter() {
390+
write!(f, "{}", &String::from_utf8_lossy(s))?
391+
}
392+
Ok(())
313393
}
314394
}
315395

316-
impl fmt::Display for TXT {
317-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318-
write!(f, "{}", self.0)
396+
impl FromIterator<Box<[u8]>> for TxtRecord {
397+
fn from_iter<T: IntoIterator<Item = Box<[u8]>>>(iter: T) -> Self {
398+
Self(iter.into_iter().collect())
319399
}
320400
}
321401

0 commit comments

Comments
 (0)