From 000c03d3c1823f97aa47e53f7dc31ee4819aadd2 Mon Sep 17 00:00:00 2001 From: Jack O'Sullivan Date: Sun, 2 Aug 2026 21:49:40 +0100 Subject: [PATCH] ci/docs: Fall back to discovered servers for AXFR The DNS reference generator transfers each zone from the nameservers public NS discovery returns. The home IPv6 reverse zone (0.d.4.0.0.c.7.9.e.0.a.2.ip6.arpa) is delegated only to Hurricane Electric, which refuses AXFR, so generation aborted before committing any zone. Our own authoritative servers (ns1/ns2.h.nul.ie) serve that zone and permit transfers but aren't in its public delegation, as their addresses are dynamic. When a zone's delegated servers all refuse, retry against the union of nameservers discovered for every other zone. That pool includes ns1/ns2.h.nul.ie via the h.nul.ie NS records, resolved to their current addresses at query time, so the reverse zone transfers from the same reachable servers h.nul.ie already uses. Zones whose own servers work are unaffected; the fallback only runs after their transfers fail. Co-Authored-By: Claude Opus 4.8 --- ci/update-docs-dns.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/ci/update-docs-dns.py b/ci/update-docs-dns.py index 621a037..eeebb3c 100644 --- a/ci/update-docs-dns.py +++ b/ci/update-docs-dns.py @@ -144,9 +144,19 @@ def discover_nameservers(domains: list[str], port: int) -> dict[str, list[str]]: return discovered -def transfer_domain(port: int, domain: str, servers: list[str]) -> list[Record]: +def transfer_domain( + port: int, domain: str, servers: list[str], fallback: list[str] = () +) -> list[Record]: + # A zone may be delegated publicly to servers that refuse AXFR (e.g. HE serving + # reverse DNS) while our own authoritative servers, discovered for other zones, + # will transfer it. Try the delegated servers first, then fall back to those. + ordered = list(servers) + for server in fallback: + if server not in ordered: + ordered.append(server) + errors = [] - for server in servers: + for server in ordered: try: return transfer(server, port, domain) except RuntimeError as error: @@ -325,8 +335,13 @@ def main() -> int: try: nameservers = discover_nameservers(args.domain, args.port) + fallback = [] + for servers in nameservers.values(): + for server in servers: + if server not in fallback: + fallback.append(server) transferred = [ - (domain, transfer_domain(args.port, domain, nameservers[domain])) + (domain, transfer_domain(args.port, domain, nameservers[domain], fallback)) for domain in args.domain ] except RuntimeError as error: