diff --git a/CHANGELOG.md b/CHANGELOG.md index b5225e1a1..e05def33e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ If you are upgrading from v0.16.x, replace the binary (or run `docker pull`). If ## Changed ## Fixed +- DNS: The DNSSEC resolver queries a single nameserver at a time, working around a `hickory-resolver` race that cancels the TCP retry when two nameservers return a truncated response in parallel. +- Troubleshoot tool: + - MX records are resolved through the DNSSEC-validating resolver, matching the resolver used by the delivery path. + - A TLSA lookup that fails or returns bogus records stops the delivery attempt for that host, instead of continuing without DANE. - WebUI: A failed update no longer takes an `Application` offline. ## [0.16.22] - 2026-09-13 diff --git a/crates/common/src/config/smtp/resolver.rs b/crates/common/src/config/smtp/resolver.rs index cedf56066..a23a51f84 100644 --- a/crates/common/src/config/smtp/resolver.rs +++ b/crates/common/src/config/smtp/resolver.rs @@ -214,6 +214,7 @@ impl Resolvers { let config_dnssec = resolver_config.clone(); let mut opts_dnssec = opts.clone(); opts_dnssec.validate = true; + opts_dnssec.num_concurrent_reqs = 1; let dnssec = DnssecResolver { resolver: TokioResolver::builder_with_config( @@ -343,6 +344,7 @@ impl Default for Resolvers { let config_dnssec = config.clone(); let mut opts_dnssec = opts.clone(); opts_dnssec.validate = true; + opts_dnssec.num_concurrent_reqs = 1; Self { dns: MessageAuthenticator::new(config, opts).expect("Failed to build DNS resolver"), diff --git a/crates/http/src/api/diagnose.rs b/crates/http/src/api/diagnose.rs index 9c2e7dde8..e6c757fe6 100644 --- a/crates/http/src/api/diagnose.rs +++ b/crates/http/src/api/diagnose.rs @@ -230,14 +230,7 @@ async fn delivery_diagnose( // Lookup MX let now = Instant::now(); - let mxs = match server - .core - .smtp - .resolvers - .dns - .mx_lookup(&domain, Some(&server.inner.cache.dns_mx)) - .await - { + let mxs = match server.mx_lookup(domain.as_str()).await { Ok(mxs) => mxs, Err(err) => { tx.send(DeliveryStage::MxLookupError { @@ -419,7 +412,7 @@ async fn delivery_diagnose( }) .await?; - None + continue 'outer; } Ok(TlsaResult::Missing) => { tx.send(DeliveryStage::TlsaNotFound { @@ -440,14 +433,17 @@ async fn delivery_diagnose( reason: "No TLSA records found for MX".to_string(), }) .await?; + + None } else { tx.send(DeliveryStage::TlsaLookupError { elapsed: now.elapsed_ms(), reason: err.to_string(), }) .await?; + + continue 'outer; } - None } };