fix(bootstrap): route numa HTTPS via IP-literal bootstrap resolver (#122)
When numa is its own system DNS resolver (HAOS add-on, Pi-hole-style container, /etc/resolv.conf → 127.0.0.1), every numa-originated HTTPS connection — DoH upstream, ODoH relay/target, blocklist CDN — routed its hostname through getaddrinfo() back to numa itself. Cold boot deadlocked; steady state taxed every new TCP connection. 0.14.1's retry-with-backoff masked the startup race but not the underlying self-loop. NumaResolver implements reqwest::dns::Resolve with two lanes: - Per-host overrides (ODoH relay_ip/target_ip) short-circuit DNS entirely, preserving ODoH's zero-plain-DNS-leak property. - Otherwise: A+AAAA in parallel via UDP to IP-literal bootstrap servers, with TCP fallback for UDP-hostile networks. Bootstrap IPs come from upstream.fallback (IP-literal filtered, hostnames skipped with a warning). Empty fallback yields the hardcoded default [9.9.9.9, 1.1.1.1]; the chosen source is logged at startup so the silent default is visible. doh_keepalive_loop now fires its first tick immediately, and keepalive_doh logs failures at WARN — bootstrap issues surface within ~100ms of boot instead of on the first client query. Distinct from UpstreamPool.fallback (client-query failover) which stays untouched: client queries with no configured fallback still SERVFAIL on primary failure rather than silently shadow-routing. Reproducer: tests/docker/self-resolver-loop.sh. Before: 0 blocklist domains, 3072ms SERVFAIL. After: 397k domains, 118ms NOERROR.
This commit is contained in:
@@ -357,12 +357,17 @@ mod tests {
|
||||
|
||||
const RETRY_DELAYS_SECS: &[u64] = &[2, 10, 30];
|
||||
|
||||
pub async fn download_blocklists(lists: &[String]) -> Vec<(String, String)> {
|
||||
let client = reqwest::Client::builder()
|
||||
pub async fn download_blocklists(
|
||||
lists: &[String],
|
||||
resolver: Option<std::sync::Arc<crate::bootstrap_resolver::NumaResolver>>,
|
||||
) -> Vec<(String, String)> {
|
||||
let mut builder = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(30))
|
||||
.gzip(true)
|
||||
.build()
|
||||
.unwrap_or_default();
|
||||
.gzip(true);
|
||||
if let Some(r) = resolver {
|
||||
builder = builder.dns_resolver(r);
|
||||
}
|
||||
let client = builder.build().unwrap_or_default();
|
||||
|
||||
let fetches = lists.iter().map(|url| {
|
||||
let client = &client;
|
||||
|
||||
Reference in New Issue
Block a user