diff --git a/src/forward.rs b/src/forward.rs index 09157cb..caf2f5e 100644 --- a/src/forward.rs +++ b/src/forward.rs @@ -85,6 +85,20 @@ impl UpstreamPool { self.primary = primary; } + /// Update the primary upstream if `new_addr` (parsed with `port`) differs + /// from the current preferred upstream. Returns `true` if the pool changed. + pub fn maybe_update_primary(&mut self, new_addr: &str, port: u16) -> bool { + let Ok(new_sock) = format!("{}:{}", new_addr, port).parse::() else { + return false; + }; + let new_upstream = Upstream::Udp(new_sock); + if self.preferred() == Some(&new_upstream) { + return false; + } + self.primary = vec![new_upstream]; + true + } + pub fn label(&self) -> String { match self.preferred() { Some(u) => { @@ -469,4 +483,33 @@ mod tests { assert_eq!(result.header.id, 0xABCD); assert_eq!(result.answers.len(), 1); } + + #[test] + fn maybe_update_primary_swaps_when_different() { + let mut pool = UpstreamPool::new( + vec![Upstream::Udp("1.2.3.4:53".parse().unwrap())], + vec![Upstream::Udp("8.8.8.8:53".parse().unwrap())], + ); + assert!(pool.maybe_update_primary("5.6.7.8", 53)); + assert_eq!(pool.preferred().unwrap().to_string(), "5.6.7.8:53"); + } + + #[test] + fn maybe_update_primary_noop_when_same() { + let mut pool = UpstreamPool::new( + vec![Upstream::Udp("1.2.3.4:53".parse().unwrap())], + vec![], + ); + assert!(!pool.maybe_update_primary("1.2.3.4", 53)); + } + + #[test] + fn maybe_update_primary_rejects_invalid_addr() { + let mut pool = UpstreamPool::new( + vec![Upstream::Udp("1.2.3.4:53".parse().unwrap())], + vec![], + ); + assert!(!pool.maybe_update_primary("not-an-ip", 53)); + assert_eq!(pool.preferred().unwrap().to_string(), "1.2.3.4:53"); + } } diff --git a/src/main.rs b/src/main.rs index e92e63d..9e2d2f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -610,17 +610,10 @@ async fn network_watch_loop(ctx: Arc) { .default_upstream .or_else(numa::system_dns::detect_dhcp_dns) .unwrap_or_else(|| QUAD9_IP.to_string()); - if let Ok(new_sock) = - format!("{}:{}", new_addr, ctx.upstream_port).parse::() - { - let new_upstream = Upstream::Udp(new_sock); - let mut pool = ctx.upstream_pool.lock().unwrap(); - let current = pool.preferred().cloned(); - if current.as_ref() != Some(&new_upstream) { - info!("upstream changed: {} → {}", pool.label(), new_upstream); - pool.set_primary(vec![new_upstream]); - changed = true; - } + let mut pool = ctx.upstream_pool.lock().unwrap(); + if pool.maybe_update_primary(&new_addr, ctx.upstream_port) { + info!("upstream changed → {}", pool.label()); + changed = true; } }