From 35524e27aa4a3c98a45aea8c7a25d8c7465f795a Mon Sep 17 00:00:00 2001 From: krsnaSuraj Date: Wed, 19 Aug 2026 16:57:45 +0000 Subject: [PATCH] atenet/dns: return NODATA for non-A queries on actor zone The actor zone only had a template IN A block. CoreDNS's template plugin returns SERVFAIL when the zone matches a query name but no template block matches the query type (e.g. AAAA queries issued in parallel with A by strict POSIX resolvers). SERVFAIL is treated as a hard failure, breaking intra-cluster resolution for actor images. Add a template IN ANY block with no answer, which returns NOERROR with an empty answer section (NODATA) per RFC 8020 for names that exist but have no records of the requested type. Fixes #888 --- cmd/atenet/internal/dns/corefile.go | 19 ++++++++++++++++++- cmd/atenet/internal/dns/corefile_test.go | 2 ++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/cmd/atenet/internal/dns/corefile.go b/cmd/atenet/internal/dns/corefile.go index 0b301e7e2..2a84e3f63 100644 --- a/cmd/atenet/internal/dns/corefile.go +++ b/cmd/atenet/internal/dns/corefile.go @@ -42,13 +42,30 @@ func buildTemplate() string { // Construct match pattern for ... Both the // actor name and the atespace are DNS-1123 labels (same regex). directives = append(directives, fmt.Sprintf("template IN A %s {", resources.ActorDNSSuffix)) - // Escape the suffix's dots so they match literally; the final \. matches the FQDN's trailing dot. + // Escape the suffix's dots so they match literally; the final \\. matches the FQDN's trailing dot. escapedSuffix := strings.ReplaceAll(resources.ActorDNSSuffix, ".", `\.`) directives = append(directives, fmt.Sprintf(` match "^%s\.%s\.%s\.$"`, resources.ResourceNameRegexPattern, resources.ResourceNameRegexPattern, escapedSuffix)) // Note the %s -- this will be filled with the router IP. directives = append(directives, ` answer "{{ .Name }} 60 IN A %s"`) directives = append(directives, "}") + // Non-A queries for valid actor names must return NODATA (NOERROR with an + // empty answer section), not SERVFAIL. CoreDNS's template plugin returns + // SERVFAIL when a template's zone matches a query name but no template + // block matches the query type (see plugin/template README: "Without + // fallthrough, when the template's ZONE matches a query but no regex match + // then a SERVFAIL response is returned"). Strict POSIX resolvers query A + // and AAAA in parallel and treat SERVFAIL as a hard failure, breaking + // intra-cluster resolution for actor images that don't have AAAA records. + // + // template IN ANY matches every type, and specifying no answer results in + // a response with an empty answer section and the default rcode NOERROR — + // exactly the NODATA response RFC 8020 expects for a name that exists but + // has no records of the requested type. + directives = append(directives, fmt.Sprintf("template IN ANY %s {", resources.ActorDNSSuffix)) + directives = append(directives, fmt.Sprintf(` match "^%s\.%s\.%s\.$"`, resources.ResourceNameRegexPattern, resources.ResourceNameRegexPattern, escapedSuffix)) + directives = append(directives, "}") + // Generate the template. b := strings.Builder{} fmt.Fprintf(&b, "# Generated at %s\n", time.Now()) diff --git a/cmd/atenet/internal/dns/corefile_test.go b/cmd/atenet/internal/dns/corefile_test.go index f13429e47..954f1f700 100644 --- a/cmd/atenet/internal/dns/corefile_test.go +++ b/cmd/atenet/internal/dns/corefile_test.go @@ -40,6 +40,8 @@ func TestMakeCoreFile(t *testing.T) { "template IN A actors.resources.substrate.ate.dev {", `match "^` + resources.ResourceNameRegexPattern + `\.` + resources.ResourceNameRegexPattern + `\.actors\.resources\.substrate\.ate\.dev\.$"`, `answer "{{ .Name }} 60 IN A 10.240.0.10"`, + // Non-A queries must return NODATA, not SERVFAIL. + "template IN ANY actors.resources.substrate.ate.dev {", }, }, {