Fix DNS failure on network change #9
@@ -341,8 +341,9 @@ async fn diagnose(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check upstream (async, no locks held)
|
// Check upstream (async, no locks held)
|
||||||
|
let upstream = *ctx.upstream.lock().unwrap();
|
||||||
let (upstream_matched, upstream_detail) =
|
let (upstream_matched, upstream_detail) =
|
||||||
forward_query_for_diagnose(&domain_lower, ctx.upstream, ctx.timeout).await;
|
forward_query_for_diagnose(&domain_lower, upstream, ctx.timeout).await;
|
||||||
steps.push(DiagnoseStep {
|
steps.push(DiagnoseStep {
|
||||||
source: "upstream".to_string(),
|
source: "upstream".to_string(),
|
||||||
matched: upstream_matched,
|
matched: upstream_matched,
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ pub struct ServerCtx {
|
|||||||
pub services: Mutex<ServiceStore>,
|
pub services: Mutex<ServiceStore>,
|
||||||
pub lan_peers: Mutex<PeerStore>,
|
pub lan_peers: Mutex<PeerStore>,
|
||||||
pub forwarding_rules: Vec<ForwardingRule>,
|
pub forwarding_rules: Vec<ForwardingRule>,
|
||||||
pub upstream: SocketAddr,
|
pub upstream: Mutex<SocketAddr>,
|
||||||
|
pub upstream_auto: bool, // true = auto-detected, false = explicitly configured
|
||||||
|
pub upstream_port: u16,
|
||||||
pub timeout: Duration,
|
pub timeout: Duration,
|
||||||
pub proxy_tld: String,
|
pub proxy_tld: String,
|
||||||
pub proxy_tld_suffix: String, // pre-computed ".{tld}" to avoid per-query allocation
|
pub proxy_tld_suffix: String, // pre-computed ".{tld}" to avoid per-query allocation
|
||||||
@@ -132,7 +134,7 @@ pub async fn handle_query(
|
|||||||
} else {
|
} else {
|
||||||
let upstream =
|
let upstream =
|
||||||
crate::system_dns::match_forwarding_rule(&qname, &ctx.forwarding_rules)
|
crate::system_dns::match_forwarding_rule(&qname, &ctx.forwarding_rules)
|
||||||
.unwrap_or(ctx.upstream);
|
.unwrap_or_else(|| *ctx.upstream.lock().unwrap());
|
||||||
match forward_query(&query, upstream, ctx.timeout).await {
|
match forward_query(&query, upstream, ctx.timeout).await {
|
||||||
Ok(resp) => {
|
Ok(resp) => {
|
||||||
ctx.cache.lock().unwrap().insert(&qname, qtype, &resp);
|
ctx.cache.lock().unwrap().insert(&qname, qtype, &resp);
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ impl PeerStore {
|
|||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.peers.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Multicast ---
|
// --- Multicast ---
|
||||||
|
|||||||
45
src/main.rs
45
src/main.rs
@@ -129,7 +129,9 @@ async fn main() -> numa::Result<()> {
|
|||||||
services: Mutex::new(service_store),
|
services: Mutex::new(service_store),
|
||||||
lan_peers: Mutex::new(numa::lan::PeerStore::new(config.lan.peer_timeout_secs)),
|
lan_peers: Mutex::new(numa::lan::PeerStore::new(config.lan.peer_timeout_secs)),
|
||||||
forwarding_rules,
|
forwarding_rules,
|
||||||
upstream,
|
upstream: Mutex::new(upstream),
|
||||||
|
upstream_auto: config.upstream.address.is_empty(),
|
||||||
|
upstream_port: config.upstream.port,
|
||||||
timeout: Duration::from_millis(config.upstream.timeout_ms),
|
timeout: Duration::from_millis(config.upstream.timeout_ms),
|
||||||
proxy_tld_suffix: if config.proxy.tld.is_empty() {
|
proxy_tld_suffix: if config.proxy.tld.is_empty() {
|
||||||
String::new()
|
String::new()
|
||||||
@@ -240,6 +242,14 @@ async fn main() -> numa::Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Spawn upstream re-detection (only for auto-detected upstream)
|
||||||
|
if ctx.upstream_auto {
|
||||||
|
let redetect_ctx = Arc::clone(&ctx);
|
||||||
|
tokio::spawn(async move {
|
||||||
|
upstream_redetect_loop(redetect_ctx).await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Spawn LAN service discovery
|
// Spawn LAN service discovery
|
||||||
if config.lan.enabled {
|
if config.lan.enabled {
|
||||||
let lan_ctx = Arc::clone(&ctx);
|
let lan_ctx = Arc::clone(&ctx);
|
||||||
@@ -264,6 +274,39 @@ async fn main() -> numa::Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn upstream_redetect_loop(ctx: Arc<numa::ctx::ServerCtx>) {
|
||||||
|
use numa::system_dns::discover_system_dns;
|
||||||
|
|
||||||
|
let mut interval = tokio::time::interval(Duration::from_secs(30));
|
||||||
|
interval.tick().await; // skip immediate tick
|
||||||
|
|
||||||
|
loop {
|
||||||
|
interval.tick().await;
|
||||||
|
|
||||||
|
let dns_info = discover_system_dns();
|
||||||
|
let new_addr = match dns_info.default_upstream {
|
||||||
|
Some(addr) => addr,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
let new_upstream: SocketAddr = match format!("{}:{}", new_addr, ctx.upstream_port).parse() {
|
||||||
|
Ok(addr) => addr,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut upstream = ctx.upstream.lock().unwrap();
|
||||||
|
let current = *upstream;
|
||||||
|
if new_upstream != current {
|
||||||
|
*upstream = new_upstream;
|
||||||
|
drop(upstream);
|
||||||
|
info!("upstream changed: {} → {}", current, new_upstream);
|
||||||
|
|
||||||
|
// Flush stale LAN peers from old network
|
||||||
|
ctx.lan_peers.lock().unwrap().clear();
|
||||||
|
info!("flushed LAN peers after network change");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn load_blocklists(ctx: &ServerCtx, lists: &[String]) {
|
async fn load_blocklists(ctx: &ServerCtx, lists: &[String]) {
|
||||||
let downloaded = download_blocklists(lists).await;
|
let downloaded = download_blocklists(lists).await;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user