From d8d37a5c4e50b9fcb571317aa6f82b5075df1fbe Mon Sep 17 00:00:00 2001 From: Frando Date: Mon, 8 Dec 2025 11:14:35 +0100 Subject: [PATCH 1/7] tests: add DoH tests for iroh-dns-server --- Cargo.lock | 156 ++++++++++++++++++++- iroh-dns-server/Cargo.toml | 3 + iroh-dns-server/benches/write.rs | 2 +- iroh-dns-server/src/config.rs | 28 ++-- iroh-dns-server/src/http.rs | 232 ++++++++++++++++++++++++++++++- iroh-dns-server/src/http/doh.rs | 3 + iroh-dns-server/src/lib.rs | 27 ++-- iroh-dns-server/src/server.rs | 56 ++++++-- 8 files changed, 470 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 57fab39d6d..0b3ced3817 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1098,6 +1098,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "enum-as-inner" version = "0.6.1" @@ -1176,6 +1185,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1859,6 +1883,22 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.19" @@ -1878,9 +1918,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2 0.6.1", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -2246,11 +2288,14 @@ dependencies = [ "rcgen", "redb", "regex", + "reqwest", "rustls", "rustls-pemfile", "serde", + "serde_json", "struct_iterable", "strum", + "tempfile", "tokio", "tokio-rustls", "tokio-rustls-acme", @@ -2718,6 +2763,23 @@ dependencies = [ "n0-future", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework 2.11.1", + "security-framework-sys", + "tempfile", +] + [[package]] name = "netdev" version = "0.38.2" @@ -2960,12 +3022,50 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "parking" version = "2.2.1" @@ -3116,6 +3216,12 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "plotters" version = "0.3.7" @@ -3581,16 +3687,21 @@ checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "base64", "bytes", + "encoding_rs", "futures-core", "futures-util", + "h2", "http 1.4.0", "http-body", "http-body-util", "hyper", "hyper-rustls", + "hyper-tls", "hyper-util", "js-sys", "log", + "mime", + "native-tls", "percent-encoding", "pin-project-lite", "quinn", @@ -3601,6 +3712,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", + "tokio-native-tls", "tokio-rustls", "tokio-util", "tower", @@ -3730,7 +3842,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.5.1", ] [[package]] @@ -3767,7 +3879,7 @@ dependencies = [ "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki", - "security-framework", + "security-framework 3.5.1", "security-framework-sys", "webpki-root-certs 0.26.11", "windows-sys 0.59.0", @@ -3854,6 +3966,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + [[package]] name = "security-framework" version = "3.5.1" @@ -4476,6 +4601,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.4" @@ -4886,6 +5021,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -5267,6 +5408,17 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + [[package]] name = "windows-result" version = "0.3.4" diff --git a/iroh-dns-server/Cargo.toml b/iroh-dns-server/Cargo.toml index 951382138b..86cafcdeaf 100644 --- a/iroh-dns-server/Cargo.toml +++ b/iroh-dns-server/Cargo.toml @@ -65,6 +65,9 @@ hickory-resolver = "0.25.0" iroh = { path = "../iroh" } rand = "0.9.2" rand_chacha = "0.9" +reqwest = "0.12.24" +serde_json = "1.0.145" +tempfile = "3.23.0" tracing-test = "0.2.5" [[bench]] diff --git a/iroh-dns-server/benches/write.rs b/iroh-dns-server/benches/write.rs index 9373c1bfb9..47e6c47b42 100644 --- a/iroh-dns-server/benches/write.rs +++ b/iroh-dns-server/benches/write.rs @@ -12,7 +12,7 @@ const LOCALHOST_PKARR: &str = "http://localhost:8080/pkarr"; async fn start_dns_server(config: Config) -> Result { let metrics = Arc::new(Metrics::default()); let store = ZoneStore::persistent( - Config::signed_packet_store_path()?, + config.signed_packet_store_path()?, Default::default(), metrics.clone(), )?; diff --git a/iroh-dns-server/src/config.rs b/iroh-dns-server/src/config.rs index 9f60299009..ae0569b528 100644 --- a/iroh-dns-server/src/config.rs +++ b/iroh-dns-server/src/config.rs @@ -52,6 +52,12 @@ pub struct Config { /// Config for pkarr rate limit #[serde(default)] pub pkarr_put_rate_limit: RateLimitConfig, + + /// Location where all data of iroh-dns-server is stored. + /// + /// If unset, will use `IROH_DNS_DATA_DIR` environment variable if set, + /// and otherwise a `iroh-dns` directory in the host system's data directory. + pub data_dir: Option, } /// The config for the store. @@ -168,21 +174,26 @@ impl Config { } /// Get the data directory. - pub fn data_dir() -> Result { - let dir = if let Some(val) = env::var_os("IROH_DNS_DATA_DIR") { - PathBuf::from(val) + pub fn data_dir(&self) -> Result { + let dir = if let Some(dir) = &self.data_dir { + dir.clone() } else { - let path = dirs_next::data_dir() - .std_context("operating environment provides no directory for application data")?; + if let Some(val) = env::var_os("IROH_DNS_DATA_DIR") { + PathBuf::from(val) + } else { + let path = dirs_next::data_dir().std_context( + "operating environment provides no directory for application data", + )?; - path.join("iroh-dns") + path.join("iroh-dns") + } }; Ok(dir) } /// Get the path to the store database file. - pub fn signed_packet_store_path() -> Result { - Ok(Self::data_dir()?.join("signed-packets-1.db")) + pub fn signed_packet_store_path(&self) -> Result { + Ok(self.data_dir()?.join("signed-packets-1.db")) } /// Get the address where the metrics server should be bound, if set. @@ -243,6 +254,7 @@ impl Default for Config { metrics: None, mainline: None, pkarr_put_rate_limit: RateLimitConfig::default(), + data_dir: None, } } } diff --git a/iroh-dns-server/src/http.rs b/iroh-dns-server/src/http.rs index cc993ffcb2..bf1ada8097 100644 --- a/iroh-dns-server/src/http.rs +++ b/iroh-dns-server/src/http.rs @@ -2,6 +2,7 @@ use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, + path::PathBuf, time::Instant, }; @@ -30,7 +31,7 @@ mod rate_limiting; mod tls; pub use self::{rate_limiting::RateLimitConfig, tls::CertMode}; -use crate::{config::Config, state::AppState}; +use crate::state::AppState; /// Config for the HTTP server #[derive(Debug, Serialize, Deserialize, Clone)] @@ -72,6 +73,7 @@ impl HttpServer { https_config: Option, rate_limit_config: RateLimitConfig, state: AppState, + cert_cache_dir: PathBuf, ) -> Result { if http_config.is_none() && https_config.is_none() { bail_any!("Either http or https config is required"); @@ -110,19 +112,19 @@ impl HttpServer { config.port, ); let acceptor = { - let cache_path = Config::data_dir()? - .join("cert_cache") - .join(config.cert_mode.to_string()); - tokio::fs::create_dir_all(&cache_path) + tokio::fs::create_dir_all(&cert_cache_dir) .await .with_std_context(|_| { - format!("failed to create cert cache dir at {cache_path:?}") + format!( + "failed to create cert cache dir at {}", + cert_cache_dir.display() + ) })?; config .cert_mode .build( config.domains, - cache_path, + cert_cache_dir, config.letsencrypt_contact, config.letsencrypt_prod.unwrap_or(false), ) @@ -272,3 +274,219 @@ async fn metrics_middleware( } response } + +#[cfg(test)] +mod tests { + use hickory_resolver::{ + config::{NameServerConfig, ResolverConfig, ResolverOpts}, + name_server::TokioConnectionProvider, + }; + use hickory_server::proto::rr::RecordType; + use iroh::{ + RelayUrl, SecretKey, + discovery::{EndpointInfo, pkarr::PkarrRelayClient}, + endpoint_info::EndpointIdExt, + }; + use n0_error::StdResultExt; + use rand::SeedableRng; + + use crate::{http::HttpsConfig, server::Server}; + + #[tokio::test] + async fn test_doh() -> n0_error::Result { + tracing_subscriber::fmt::init(); + let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(0); + let dir = tempfile::tempdir()?; + let https_config = HttpsConfig { + port: 0, + bind_addr: None, + domains: vec!["localhost".to_string()], + cert_mode: crate::http::CertMode::SelfSigned, + letsencrypt_contact: None, + letsencrypt_prod: None, + }; + let server = + Server::spawn_for_tests_with_options(dir.path(), None, None, Some(https_config)) + .await?; + + let (name_z32, signed_packet) = { + let secret_key = SecretKey::generate(&mut rng); + let endpoint_id = secret_key.public(); + let relay_url: RelayUrl = "https://relay.example.".parse()?; + let endpoint_info = + EndpointInfo::new(endpoint_id).with_relay_url(Some(relay_url.clone())); + ( + secret_key.public().to_z32(), + endpoint_info.to_pkarr_signed_packet(&secret_key, 30)?, + ) + }; + + let http_url = server.http_url().expect("http is bound"); + let pkarr = PkarrRelayClient::new(format!("{http_url}pkarr").parse().anyerr()?); + pkarr.publish(&signed_packet).await?; + + // Create a reqwest client that does not verify certificates. + let client = reqwest::Client::builder() + .http2_prior_knowledge() + .use_preconfigured_tls(self::tls::insecure_tls_config()) + .build() + .anyerr()?; + + // Fetch as JSON via HTTP. + let url = format!( + "{http_url}dns-query?name={}&type=txt", + format_args!("_iroh.{name_z32}."), + ); + let res = client + .get(url) + .header("accept", "application/dns-json") + .send() + .await + .anyerr()? + .json::() + .await + .anyerr()?; + assert_eq!(res.answer.len(), 1); + assert_eq!(res.answer[0].name, format!("_iroh.{name_z32}.")); + assert_eq!(res.answer[0].data, "relay=https://relay.example./"); + + // Fetch as JSON via HTTPS. + let https_url = server.https_url().expect("https is bound"); + let url = format!( + "{https_url}dns-query?name={}&type=txt", + format_args!("_iroh.{name_z32}."), + ); + let res = client + .get(url) + .header("accept", "application/dns-json") + .send() + .await + .anyerr()? + .json::() + .await + .anyerr()?; + assert_eq!(res.answer.len(), 1); + assert_eq!(res.answer[0].name, format!("_iroh.{name_z32}.")); + assert_eq!(res.answer[0].data, "relay=https://relay.example./"); + + // Fetch over HTTPS via hickory-resolver + let client = { + let config = { + let mut config = ResolverConfig::new(); + let mut name_server = NameServerConfig::new( + server.https_addr().expect("https is bound"), + hickory_server::proto::xfer::Protocol::Https, + ); + name_server.tls_dns_name = Some("localhost".to_string()); + config.add_name_server(name_server); + config + }; + + let opts = { + let mut opts = ResolverOpts::default(); + opts.tls_config = self::tls::insecure_tls_config(); + opts + }; + + hickory_resolver::Resolver::builder_with_config( + config, + TokioConnectionProvider::default(), + ) + .with_options(opts) + .build() + }; + + let res = client + .txt_lookup(format!("_iroh.{name_z32}.")) + .await + .anyerr()?; + let records = res.as_lookup().records(); + assert_eq!(records.len(), 1); + assert_eq!(records[0].record_type(), RecordType::TXT); + let txt_data = records[0].data().as_txt().unwrap().txt_data(); + assert_eq!(&txt_data[0][..], b"relay=https://relay.example./"); + + server.shutdown().await?; + Ok(()) + } + + mod tls { + use std::sync::Arc; + + use rustls::{ + DigitallySignedStruct, RootCertStore, + client::{ + ClientConfig, + danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, + }, + crypto::{ + CryptoProvider, ring::default_provider, verify_tls12_signature, + verify_tls13_signature, + }, + pki_types::{CertificateDer, ServerName, UnixTime}, + }; + + #[derive(Debug)] + struct NoCertificateVerification(CryptoProvider); + + impl Default for NoCertificateVerification { + fn default() -> Self { + Self(default_provider()) + } + } + + impl ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp: &[u8], + _now: UnixTime, + ) -> Result { + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + verify_tls12_signature( + message, + cert, + dss, + &self.0.signature_verification_algorithms, + ) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + verify_tls13_signature( + message, + cert, + dss, + &self.0.signature_verification_algorithms, + ) + } + + fn supported_verify_schemes(&self) -> Vec { + self.0.signature_verification_algorithms.supported_schemes() + } + } + + pub(super) fn insecure_tls_config() -> ClientConfig { + let mut cfg = ClientConfig::builder() + .with_root_certificates(RootCertStore::empty()) + .with_no_client_auth(); + cfg.dangerous() + .set_certificate_verifier(Arc::new(NoCertificateVerification::default())); + cfg + } + } +} diff --git a/iroh-dns-server/src/http/doh.rs b/iroh-dns-server/src/http/doh.rs index 984d372853..aa5ff514f3 100644 --- a/iroh-dns-server/src/http/doh.rs +++ b/iroh-dns-server/src/http/doh.rs @@ -26,6 +26,9 @@ mod response; use self::extract::{DnsMimeType, DnsRequestBody, DnsRequestQuery}; +#[cfg(test)] +pub(crate) use self::response::DnsResponse; + /// GET handler for resolving DoH queries pub async fn get( State(state): State, diff --git a/iroh-dns-server/src/lib.rs b/iroh-dns-server/src/lib.rs index c3f7475a67..7d02b88f20 100644 --- a/iroh-dns-server/src/lib.rs +++ b/iroh-dns-server/src/lib.rs @@ -43,9 +43,10 @@ mod tests { #[tokio::test] #[traced_test] async fn pkarr_publish_dns_resolve() -> Result { - let (server, nameserver, http_url) = Server::spawn_for_tests().await?; + let dir = tempfile::tempdir()?; + let server = Server::spawn_for_tests(dir.path()).await?; let pkarr_relay_url = { - let mut url = http_url.clone(); + let mut url = server.http_url().expect("http is bound"); url.set_path("/pkarr"); url }; @@ -113,7 +114,7 @@ mod tests { use hickory_server::proto::rr::Name; let pubkey = signed_packet.public_key().to_z32(); - let resolver = test_resolver(nameserver); + let resolver = test_resolver(server.dns_addr()); // resolve root record let name = Name::from_utf8(format!("{pubkey}.")).anyerr()?; @@ -158,10 +159,11 @@ mod tests { #[tokio::test] #[traced_test] async fn integration_smoke() -> Result { - let (server, nameserver, http_url) = Server::spawn_for_tests().await?; + let dir = tempfile::tempdir()?; + let server = Server::spawn_for_tests(dir.path()).await?; let pkarr_relay = { - let mut url = http_url.clone(); + let mut url = server.http_url().expect("http is bound"); url.set_path("/pkarr"); url }; @@ -179,7 +181,7 @@ mod tests { pkarr.publish(&signed_packet).await?; - let resolver = test_resolver(nameserver); + let resolver = test_resolver(server.dns_addr()); let res = resolver.lookup_endpoint_by_id(&endpoint_id, origin).await?; assert_eq!(res.endpoint_id, endpoint_id); @@ -225,6 +227,7 @@ mod tests { #[traced_test] #[ignore = "flaky"] async fn integration_mainline() -> Result { + let dir = tempfile::tempdir()?; let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64); // run a mainline testnet @@ -232,9 +235,13 @@ mod tests { let bootstrap = testnet.bootstrap.clone(); // spawn our server with mainline support - let (server, nameserver, _http_url) = - Server::spawn_for_tests_with_options(Some(BootstrapOption::Custom(bootstrap)), None) - .await?; + let server = Server::spawn_for_tests_with_options( + dir.path(), + Some(BootstrapOption::Custom(bootstrap)), + None, + None, + ) + .await?; let origin = "irohdns.example."; @@ -254,7 +261,7 @@ mod tests { pkarr.publish(&signed_packet, None).await.anyerr()?; // resolve via DNS from our server, which will lookup from our DHT - let resolver = test_resolver(nameserver); + let resolver = test_resolver(server.dns_addr()); let res = resolver.lookup_endpoint_by_id(&endpoint_id, origin).await?; assert_eq!(res.endpoint_id, endpoint_id); diff --git a/iroh-dns-server/src/server.rs b/iroh-dns-server/src/server.rs index 361e1899b8..f6861bea30 100644 --- a/iroh-dns-server/src/server.rs +++ b/iroh-dns-server/src/server.rs @@ -1,10 +1,16 @@ //! The main server which combines the DNS and HTTP(S) servers. use std::sync::Arc; +#[cfg(test)] +use std::{net::SocketAddr, path::Path}; use iroh_metrics::service::start_metrics_server; use n0_error::{Result, StdResultExt}; use tracing::info; +#[cfg(test)] +use url::Url; +#[cfg(test)] +use crate::http::HttpsConfig; use crate::{ config::Config, dns::{DnsHandler, DnsServer}, @@ -19,7 +25,7 @@ pub async fn run_with_config_until_ctrl_c(config: Config) -> Result<()> { let metrics = Arc::new(Metrics::default()); let zone_store_options = config.zone_store.clone().unwrap_or_default(); let mut store = ZoneStore::persistent( - Config::signed_packet_store_path()?, + config.signed_packet_store_path()?, zone_store_options.into(), metrics.clone(), )?; @@ -49,6 +55,7 @@ impl Server { /// * A HTTP server task, if `config.http` is not empty /// * A HTTPS server task, if `config.https` is not empty pub async fn spawn(config: Config, store: ZoneStore, metrics: Arc) -> Result { + let cert_cache_dir = config.data_dir()?.join("cert_cache"); let dns_handler = DnsHandler::new(store.clone(), &config.dns, metrics.clone())?; let state = AppState { @@ -73,6 +80,7 @@ impl Server { config.https, config.pkarr_put_rate_limit, state.clone(), + cert_cache_dir, ) .await?; let dns_server = DnsServer::spawn(config.dns, state.dns_handler.clone()).await?; @@ -111,17 +119,19 @@ impl Server { /// It returns the server handle, the [`SocketAddr`] of the DNS server and the [`Url`] of the /// HTTP server. #[cfg(test)] - pub async fn spawn_for_tests() -> Result<(Self, std::net::SocketAddr, url::Url)> { - Self::spawn_for_tests_with_options(None, None).await + pub async fn spawn_for_tests(dir: impl AsRef) -> Result { + Self::spawn_for_tests_with_options(dir, None, None, 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_options( + dir: impl AsRef, mainline: Option, options: Option, - ) -> Result<(Self, std::net::SocketAddr, url::Url)> { + https: Option, + ) -> Result { use std::net::{IpAddr, Ipv4Addr}; use crate::config::MetricsConfig; @@ -131,8 +141,9 @@ impl Server { config.dns.bind_addr = Some(IpAddr::V4(Ipv4Addr::LOCALHOST)); config.http.as_mut().unwrap().port = 0; config.http.as_mut().unwrap().bind_addr = Some(IpAddr::V4(Ipv4Addr::LOCALHOST)); - config.https = None; + config.https = https; config.metrics = Some(MetricsConfig::disabled()); + config.data_dir = Some(dir.as_ref().to_owned()); let mut store = ZoneStore::in_memory(options.unwrap_or_default(), Default::default())?; if let Some(bootstrap) = mainline { @@ -140,9 +151,36 @@ impl Server { store = store.with_mainline_fallback(bootstrap); } let server = Self::spawn(config, store, Default::default()).await?; - let dns_addr = server.dns_server.local_addr(); - let http_addr = server.http_server.http_addr().expect("http is set"); - let http_url = format!("http://{http_addr}").parse::().anyerr()?; - Ok((server, dns_addr, http_url)) + Ok(server) + } + + #[cfg(test)] + pub(crate) fn dns_addr(&self) -> SocketAddr { + self.dns_server.local_addr() + } + + #[cfg(test)] + pub(crate) fn http_url(&self) -> Option { + let http_addr = self.http_server.http_addr()?; + Some( + format!("http://{http_addr}") + .parse::() + .expect("valid url"), + ) + } + + #[cfg(test)] + pub(crate) fn https_url(&self) -> Option { + let https_addr = self.https_addr()?; + Some( + format!("https://{https_addr}") + .parse::() + .expect("valid url"), + ) + } + + #[cfg(test)] + pub(crate) fn https_addr(&self) -> Option { + self.http_server.https_addr() } } From ba86f895ca58f2eea8d38beac8bca09bab11427e Mon Sep 17 00:00:00 2001 From: Frando Date: Mon, 8 Dec 2025 12:13:31 +0100 Subject: [PATCH 2/7] fix: bind to localhost in tests --- iroh-dns-server/src/http.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iroh-dns-server/src/http.rs b/iroh-dns-server/src/http.rs index bf1ada8097..c1162e31e0 100644 --- a/iroh-dns-server/src/http.rs +++ b/iroh-dns-server/src/http.rs @@ -277,6 +277,8 @@ async fn metrics_middleware( #[cfg(test)] mod tests { + use std::net::Ipv4Addr; + use hickory_resolver::{ config::{NameServerConfig, ResolverConfig, ResolverOpts}, name_server::TokioConnectionProvider, @@ -299,7 +301,7 @@ mod tests { let dir = tempfile::tempdir()?; let https_config = HttpsConfig { port: 0, - bind_addr: None, + bind_addr: Some(Ipv4Addr::LOCALHOST.into()), domains: vec!["localhost".to_string()], cert_mode: crate::http::CertMode::SelfSigned, letsencrypt_contact: None, From 59ed0f64bad83c96a2ffdf35928253c4e8f1ddab Mon Sep 17 00:00:00 2001 From: Frando Date: Mon, 8 Dec 2025 12:13:44 +0100 Subject: [PATCH 3/7] chore: fmt --- iroh-dns-server/src/http/doh.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/iroh-dns-server/src/http/doh.rs b/iroh-dns-server/src/http/doh.rs index aa5ff514f3..99454f94a5 100644 --- a/iroh-dns-server/src/http/doh.rs +++ b/iroh-dns-server/src/http/doh.rs @@ -25,7 +25,6 @@ mod extract; mod response; use self::extract::{DnsMimeType, DnsRequestBody, DnsRequestQuery}; - #[cfg(test)] pub(crate) use self::response::DnsResponse; From a2dededd3f75740e23b0b7efb7577f32d73d42e5 Mon Sep 17 00:00:00 2001 From: Frando Date: Mon, 8 Dec 2025 12:14:17 +0100 Subject: [PATCH 4/7] chore: clippy --- iroh-dns-server/src/config.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/iroh-dns-server/src/config.rs b/iroh-dns-server/src/config.rs index ae0569b528..9944d9c12a 100644 --- a/iroh-dns-server/src/config.rs +++ b/iroh-dns-server/src/config.rs @@ -177,16 +177,14 @@ impl Config { pub fn data_dir(&self) -> Result { let dir = if let Some(dir) = &self.data_dir { dir.clone() + } else if let Some(val) = env::var_os("IROH_DNS_DATA_DIR") { + PathBuf::from(val) } else { - if let Some(val) = env::var_os("IROH_DNS_DATA_DIR") { - PathBuf::from(val) - } else { - let path = dirs_next::data_dir().std_context( - "operating environment provides no directory for application data", - )?; + let path = dirs_next::data_dir().std_context( + "operating environment provides no directory for application data", + )?; - path.join("iroh-dns") - } + path.join("iroh-dns") }; Ok(dir) } From 4cfa448aee2b842f49c7791b1d5d2bc92967a366 Mon Sep 17 00:00:00 2001 From: Frando Date: Mon, 8 Dec 2025 12:18:32 +0100 Subject: [PATCH 5/7] fixup: feature flags --- Cargo.lock | 153 +--------------------------------- iroh-dns-server/Cargo.toml | 4 +- iroh-dns-server/src/config.rs | 5 +- 3 files changed, 7 insertions(+), 155 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0b3ced3817..63952a7046 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1098,15 +1098,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - [[package]] name = "enum-as-inner" version = "0.6.1" @@ -1185,21 +1176,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1883,22 +1859,6 @@ dependencies = [ "webpki-roots", ] -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - [[package]] name = "hyper-util" version = "0.1.19" @@ -1918,11 +1878,9 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2 0.6.1", - "system-configuration", "tokio", "tower-service", "tracing", - "windows-registry", ] [[package]] @@ -2763,23 +2721,6 @@ dependencies = [ "n0-future", ] -[[package]] -name = "native-tls" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework 2.11.1", - "security-framework-sys", - "tempfile", -] - [[package]] name = "netdev" version = "0.38.2" @@ -3022,50 +2963,12 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" -[[package]] -name = "openssl" -version = "0.10.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "openssl-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" -[[package]] -name = "openssl-sys" -version = "0.9.111" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "parking" version = "2.2.1" @@ -3216,12 +3119,6 @@ dependencies = [ "spki", ] -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - [[package]] name = "plotters" version = "0.3.7" @@ -3687,21 +3584,16 @@ checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "base64", "bytes", - "encoding_rs", "futures-core", "futures-util", - "h2", "http 1.4.0", "http-body", "http-body-util", "hyper", "hyper-rustls", - "hyper-tls", "hyper-util", "js-sys", "log", - "mime", - "native-tls", "percent-encoding", "pin-project-lite", "quinn", @@ -3712,7 +3604,6 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-native-tls", "tokio-rustls", "tokio-util", "tower", @@ -3842,7 +3733,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.5.1", + "security-framework", ] [[package]] @@ -3879,7 +3770,7 @@ dependencies = [ "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki", - "security-framework 3.5.1", + "security-framework", "security-framework-sys", "webpki-root-certs 0.26.11", "windows-sys 0.59.0", @@ -3966,19 +3857,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - [[package]] name = "security-framework" version = "3.5.1" @@ -4601,16 +4479,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.4" @@ -5021,12 +4889,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.5" @@ -5408,17 +5270,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "windows-registry" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" -dependencies = [ - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - [[package]] name = "windows-result" version = "0.3.4" diff --git a/iroh-dns-server/Cargo.toml b/iroh-dns-server/Cargo.toml index 86cafcdeaf..9040c561b3 100644 --- a/iroh-dns-server/Cargo.toml +++ b/iroh-dns-server/Cargo.toml @@ -65,7 +65,9 @@ hickory-resolver = "0.25.0" iroh = { path = "../iroh" } rand = "0.9.2" rand_chacha = "0.9" -reqwest = "0.12.24" +reqwest = { version = "0.12", default-features = false, features = [ + "rustls-tls", +] } serde_json = "1.0.145" tempfile = "3.23.0" tracing-test = "0.2.5" diff --git a/iroh-dns-server/src/config.rs b/iroh-dns-server/src/config.rs index 9944d9c12a..32d6e7c8c7 100644 --- a/iroh-dns-server/src/config.rs +++ b/iroh-dns-server/src/config.rs @@ -180,9 +180,8 @@ impl Config { } else if let Some(val) = env::var_os("IROH_DNS_DATA_DIR") { PathBuf::from(val) } else { - let path = dirs_next::data_dir().std_context( - "operating environment provides no directory for application data", - )?; + let path = dirs_next::data_dir() + .std_context("operating environment provides no directory for application data")?; path.join("iroh-dns") }; From b2c1c2db4d811475342cb185037c9211ff30ec65 Mon Sep 17 00:00:00 2001 From: Frando Date: Mon, 8 Dec 2025 12:21:54 +0100 Subject: [PATCH 6/7] fixup feature flags --- Cargo.lock | 1 + iroh-dns-server/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 63952a7046..7e4fa1d89b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3586,6 +3586,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", + "h2", "http 1.4.0", "http-body", "http-body-util", diff --git a/iroh-dns-server/Cargo.toml b/iroh-dns-server/Cargo.toml index 9040c561b3..fb2b28e377 100644 --- a/iroh-dns-server/Cargo.toml +++ b/iroh-dns-server/Cargo.toml @@ -66,7 +66,7 @@ iroh = { path = "../iroh" } rand = "0.9.2" rand_chacha = "0.9" reqwest = { version = "0.12", default-features = false, features = [ - "rustls-tls", + "rustls-tls", "http2" ] } serde_json = "1.0.145" tempfile = "3.23.0" From 280d9f13cdf22d46b330425f1e7ad308f597af35 Mon Sep 17 00:00:00 2001 From: Frando Date: Fri, 12 Dec 2025 15:00:34 +0100 Subject: [PATCH 7/7] fixup --- iroh-dns-server/src/http.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/iroh-dns-server/src/http.rs b/iroh-dns-server/src/http.rs index c1162e31e0..50be38a682 100644 --- a/iroh-dns-server/src/http.rs +++ b/iroh-dns-server/src/http.rs @@ -291,12 +291,13 @@ mod tests { }; use n0_error::StdResultExt; use rand::SeedableRng; + use tracing_test::traced_test; use crate::{http::HttpsConfig, server::Server}; #[tokio::test] + #[traced_test] async fn test_doh() -> n0_error::Result { - tracing_subscriber::fmt::init(); let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(0); let dir = tempfile::tempdir()?; let https_config = HttpsConfig {