diff --git a/Cargo.lock b/Cargo.lock index 9bf1cb5c85..c57fe9f62b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2521,6 +2521,7 @@ dependencies = [ "iroh-net", "iroh-test", "lru", + "mainline", "parking_lot", "pkarr", "rcgen 0.12.1", diff --git a/iroh-dns-server/Cargo.toml b/iroh-dns-server/Cargo.toml index 0fb9b2a1d5..4a14e7095d 100644 --- a/iroh-dns-server/Cargo.toml +++ b/iroh-dns-server/Cargo.toml @@ -54,3 +54,4 @@ z32 = "1.1.1" hickory-resolver = "0.24.0" iroh-net = { version = "0.14.0", path = "../iroh-net" } iroh-test = { path = "../iroh-test" } +mainline = "<1.5.0" diff --git a/iroh-dns-server/src/config.rs b/iroh-dns-server/src/config.rs index 1041f62a72..89222f9daf 100644 --- a/iroh-dns-server/src/config.rs +++ b/iroh-dns-server/src/config.rs @@ -69,12 +69,31 @@ impl MetricsConfig { pub struct MainlineConfig { /// Set to true to enable the mainline lookup. pub enabled: bool, + /// Set custom bootstrap nodes. + /// + /// Addresses can either be `domain:port` or `ipv4:port`. + /// + /// If empty this will use the default bittorrent mainline bootstrap nodes as defined by pkarr. + pub bootstrap: Option>, +} + +/// Configure the bootstrap servers for mainline DHT resolution. +#[derive(Debug, Serialize, Deserialize, Default)] +pub enum BootstrapOption { + /// Use the default bootstrap servers. + #[default] + Default, + /// Use custom bootstrap servers. + Custom(Vec), } #[allow(clippy::derivable_impls)] impl Default for MainlineConfig { fn default() -> Self { - Self { enabled: false } + Self { + enabled: false, + bootstrap: None, + } } } @@ -121,11 +140,18 @@ impl Config { } } - pub(crate) fn mainline_enabled(&self) -> bool { - self.mainline - .as_ref() - .map(|x| x.enabled) - .unwrap_or_default() + pub(crate) fn mainline_enabled(&self) -> Option { + match self.mainline.as_ref() { + None => None, + Some(MainlineConfig { enabled: false, .. }) => None, + Some(MainlineConfig { + bootstrap: Some(bootstrap), + .. + }) => Some(BootstrapOption::Custom(bootstrap.clone())), + Some(MainlineConfig { + bootstrap: None, .. + }) => Some(BootstrapOption::Default), + } } } diff --git a/iroh-dns-server/src/lib.rs b/iroh-dns-server/src/lib.rs index bb62a969e9..95e09bfc1e 100644 --- a/iroh-dns-server/src/lib.rs +++ b/iroh-dns-server/src/lib.rs @@ -28,10 +28,10 @@ mod tests { }, key::SecretKey, }; - use pkarr::SignedPacket; + use pkarr::{PkarrClient, SignedPacket}; use url::Url; - use crate::server::Server; + use crate::{config::BootstrapOption, server::Server}; #[tokio::test] async fn pkarr_publish_dns_resolve() -> Result<()> { @@ -177,6 +177,45 @@ mod tests { Ok(()) } + #[tokio::test] + async fn integration_mainline() -> Result<()> { + iroh_test::logging::setup_multithreaded(); + + // run a mainline testnet + let testnet = mainline::dht::Testnet::new(5); + let bootstrap = testnet.bootstrap.clone(); + + // spawn our server with mainline support + let (server, nameserver, _http_url) = + Server::spawn_for_tests_with_mainline(Some(BootstrapOption::Custom(bootstrap))).await?; + + let origin = "irohdns.example."; + + // create a signed packet + let secret_key = SecretKey::generate(); + let node_id = secret_key.public(); + let relay_url: Url = "https://relay.example.".parse()?; + let node_info = NodeInfo::new(node_id, Some(relay_url.clone()), Default::default()); + let signed_packet = node_info.to_pkarr_signed_packet(&secret_key, 30)?; + + // publish the signed packet to our DHT + let pkarr = PkarrClient::builder().bootstrap(&testnet.bootstrap).build(); + pkarr.publish(&signed_packet).await?; + + // resolve via DNS from our server, which will lookup from our DHT + let resolver = test_resolver(nameserver); + let res = lookup_by_id(&resolver, &node_id, origin).await?; + + assert_eq!(res.node_id, node_id); + assert_eq!(res.info.relay_url.map(Url::from), Some(relay_url)); + + server.shutdown().await?; + for node in testnet.nodes { + node.shutdown(); + } + Ok(()) + } + fn test_resolver(nameserver: SocketAddr) -> DnsResolver { let mut config = ResolverConfig::new(); let nameserver_config = NameServerConfig::new(nameserver, Protocol::Udp); diff --git a/iroh-dns-server/src/server.rs b/iroh-dns-server/src/server.rs index 0669cb6f48..c9580fa121 100644 --- a/iroh-dns-server/src/server.rs +++ b/iroh-dns-server/src/server.rs @@ -1,4 +1,5 @@ //! The main server which combines the DNS and HTTP(S) servers. + use anyhow::Result; use iroh_metrics::metrics::start_metrics_server; use tracing::info; @@ -14,9 +15,9 @@ use crate::{ /// Spawn the server and run until the `Ctrl-C` signal is received, then shutdown. pub async fn run_with_config_until_ctrl_c(config: Config) -> Result<()> { let mut store = ZoneStore::persistent(Config::signed_packet_store_path()?)?; - if config.mainline_enabled() { + if let Some(bootstrap) = config.mainline_enabled() { info!("mainline fallback enabled"); - store = store.with_pkarr(Default::default()); + store = store.with_mainline_fallback(bootstrap); }; let server = Server::spawn(config, store).await?; tokio::signal::ctrl_c().await?; @@ -89,6 +90,15 @@ impl Server { /// HTTP server. #[cfg(test)] pub async fn spawn_for_tests() -> Result<(Self, std::net::SocketAddr, url::Url)> { + Self::spawn_for_tests_with_mainline(None).await + } + + /// Spawn a server suitable for testing, while optionally enabling mainline with custom + /// bootstrap addresses. + #[cfg(test)] + pub async fn spawn_for_tests_with_mainline( + mainline: Option, + ) -> Result<(Self, std::net::SocketAddr, url::Url)> { use crate::config::MetricsConfig; use std::net::{IpAddr, Ipv4Addr}; @@ -100,7 +110,11 @@ impl Server { config.https = None; config.metrics = Some(MetricsConfig::disabled()); - let store = ZoneStore::in_memory()?; + let mut store = ZoneStore::in_memory()?; + if let Some(bootstrap) = mainline { + info!("mainline fallback enabled"); + store = store.with_mainline_fallback(bootstrap); + } let server = Self::spawn(config, store).await?; let dns_addr = server.dns_server.local_addr(); let http_addr = server.http_server.http_addr().expect("http is set"); diff --git a/iroh-dns-server/src/store.rs b/iroh-dns-server/src/store.rs index 7c7ede9f7e..dd8d911911 100644 --- a/iroh-dns-server/src/store.rs +++ b/iroh-dns-server/src/store.rs @@ -12,6 +12,7 @@ use tracing::{debug, trace}; use ttl_cache::TtlCache; use crate::{ + config::BootstrapOption, metrics::Metrics, util::{signed_packet_to_hickory_records_without_origin, PublicKeyBytes}, }; @@ -55,12 +56,23 @@ impl ZoneStore { Ok(Self::new(packet_store)) } - /// Configure a pkarr client for resolution of packets from the bittorrent - /// mainline DHT. + /// Configure a pkarr client for resolution of packets from the bittorent mainline DHT. /// /// This will be used only as a fallback if there is no local info available. - pub fn with_pkarr(self, pkarr: Option>) -> Self { - Self { pkarr, ..self } + /// + /// Optionally set custom bootstrap nodes. If `bootstrap` is empty it will use the default + /// mainline bootstrap nodes. + pub fn with_mainline_fallback(self, bootstrap: BootstrapOption) -> Self { + let pkarr_client = match bootstrap { + BootstrapOption::Default => PkarrClient::default(), + BootstrapOption::Custom(bootstrap) => { + PkarrClient::builder().bootstrap(&bootstrap).build() + } + }; + Self { + pkarr: Some(Arc::new(pkarr_client)), + ..self + } } /// Create a new zone store.