fix: replace stale magic DNS records on IP change (#1906)

Magic DNS updates are full snapshots, so appending routes keeps old IPs and returns duplicate A records. Replace each client's previous routes on update and add a regression test to ensure hostname resolution keeps only the latest IP.
This commit is contained in:
fanyang
2026-02-16 13:20:11 +08:00
committed by GitHub
parent fe4e77979d
commit aa24d09aa2
2 changed files with 186 additions and 8 deletions
@@ -140,6 +140,19 @@ impl MagicDnsServerInstanceData {
}
}
async fn keep_zone_authoritative(&self, zone: &str) {
if let Err(e) = self
.update_dns_records(std::iter::empty::<&Route>(), zone)
.await
{
tracing::error!(
"Failed to keep DNS zone {} authoritative after route prune: {:?}",
zone,
e
);
}
}
fn do_system_config(&self, zone: &str) -> Result<(), anyhow::Error> {
if let Some(c) = &self.system_config {
c.set_dns(&OSConfig {
@@ -183,10 +196,25 @@ impl MagicDnsServerRpc for MagicDnsServerInstanceData {
return Err(anyhow::anyhow!("No remote addr").into());
};
let zone = input.zone.clone();
self.route_infos
.entry(zone.clone())
.or_default()
.insert_many(remote_addr.clone().into(), input.routes);
let remote_addr: url::Url = remote_addr.clone().into();
let mut zone_removed = false;
if let Some(mut routes_by_addr) = self.route_infos.get_mut(&zone) {
routes_by_addr.remove(&remote_addr);
if !input.routes.is_empty() {
routes_by_addr.insert_many(remote_addr, input.routes);
}
zone_removed = routes_by_addr.is_empty();
} else if !input.routes.is_empty() {
let mut routes_by_addr = MultiMap::new();
routes_by_addr.insert_many(remote_addr, input.routes);
self.route_infos.insert(zone.clone(), routes_by_addr);
}
if zone_removed {
self.route_infos.remove(&zone);
self.keep_zone_authoritative(&zone).await;
}
self.update().await;
Ok(Default::default())
@@ -438,11 +466,19 @@ impl RpcServerHook for MagicDnsServerInstanceData {
return;
};
let remote_addr = remote_addr.into();
let mut removed_zones = vec![];
for mut item in self.route_infos.iter_mut() {
item.value_mut().remove(&remote_addr);
if item.value().is_empty() {
removed_zones.push(item.key().clone());
}
}
for zone in &removed_zones {
self.route_infos.remove(zone);
}
for zone in removed_zones {
self.keep_zone_authoritative(&zone).await;
}
self.route_infos.retain(|_, v| !v.is_empty());
self.route_infos.shrink_to_fit();
self.update().await;
}
}