iroh_net/discovery/dns.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
//! DNS node discovery for iroh-net
use anyhow::Result;
use futures_lite::stream::Boxed as BoxStream;
use crate::{
discovery::{Discovery, DiscoveryItem},
dns::ResolverExt,
relay::force_staging_infra,
Endpoint, NodeId,
};
/// The n0 testing DNS node origin, for production.
pub const N0_DNS_NODE_ORIGIN_PROD: &str = "dns.iroh.link";
/// The n0 testing DNS node origin, for testing.
pub const N0_DNS_NODE_ORIGIN_STAGING: &str = "staging-dns.iroh.link";
const DNS_STAGGERING_MS: &[u64] = &[200, 300];
/// DNS node discovery
///
/// When asked to resolve a [`NodeId`], this service performs a lookup in the Domain Name System (DNS).
///
/// It uses the [`Endpoint`]'s DNS resolver to query for `TXT` records under the domain
/// `_iroh.<z32-node-id>.<origin-domain>`:
///
/// * `_iroh`: is the record name
/// * `<z32-node-id>` is the [`NodeId`] encoded in [`z-base-32`] format
/// * `<origin-domain>` is the node origin domain as set in [`DnsDiscovery::new`].
///
/// Each TXT record returned from the query is expected to contain a string in the format `<name>=<value>`.
/// If a TXT record contains multiple character strings, they are concatenated first.
/// The supported attributes are:
/// * `relay=<url>`: The URL of the home relay server of the node
///
/// The DNS resolver defaults to using the nameservers configured on the host system, but can be changed
/// with [`crate::endpoint::Builder::dns_resolver`].
///
/// [z-base-32]: https://philzimmermann.com/docs/human-oriented-base-32-encoding.txt
#[derive(Debug)]
pub struct DnsDiscovery {
origin_domain: String,
}
impl DnsDiscovery {
/// Creates a new DNS discovery.
pub fn new(origin_domain: String) -> Self {
Self { origin_domain }
}
/// Creates a new DNS discovery using the `iroh.link` domain.
///
/// This uses the [`N0_DNS_NODE_ORIGIN_PROD`] domain.
///
/// # Usage during tests
///
/// For testing it is possible to use the [`N0_DNS_NODE_ORIGIN_STAGING`] domain
/// with [`DnsDiscovery::new`]. This would then use a hosted staging discovery
/// service for testing purposes.
pub fn n0_dns() -> Self {
if force_staging_infra() {
Self::new(N0_DNS_NODE_ORIGIN_STAGING.to_string())
} else {
Self::new(N0_DNS_NODE_ORIGIN_PROD.to_string())
}
}
}
impl Discovery for DnsDiscovery {
fn resolve(&self, ep: Endpoint, node_id: NodeId) -> Option<BoxStream<Result<DiscoveryItem>>> {
let resolver = ep.dns_resolver().clone();
let origin_domain = self.origin_domain.clone();
let fut = async move {
let node_addr = resolver
.lookup_by_id_staggered(&node_id, &origin_domain, DNS_STAGGERING_MS)
.await?;
Ok(DiscoveryItem {
node_id,
provenance: "dns",
last_updated: None,
addr_info: node_addr.info,
})
};
let stream = futures_lite::stream::once_future(fut);
Some(Box::pin(stream))
}
}