Skip to content

fix: authoritative NODATA with the zone SOA for unconfigured record types - #222

Merged
dmachard merged 1 commit into
dmachard:mainfrom
neermilov:fix/nodata-soa-authoritative
Nov 12, 2026
Merged

dmachard merged 1 commit into
dmachard:mainfrom
neermilov:fix/nodata-soa-authoritative

Conversation

@neermilov

@neermilov neermilov commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #147 / #148: the expected output in #147 was flags: qr aa with the zone SOA, but the fix stopped at a bare NOERROR. This PR completes it.

Problem

A query for a record type the record does not have (e.g. AAAA on an IPv4-only record) is answered with a bare NOERROR: no AA, no SOA. Without the SOA the answer cannot be negatively cached (RFC 2308), so resolvers re-ask on every lookup.

$ dig @127.0.0.1 -p 1053 +norec app.example.org AAAA
;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0

Impact

Observed on our authoritative servers (IPv4-only backends):

  • 59% of all queries for one zone (146k of 248k in ~45 minutes) were AAAA re-asks for a single name.
  • Quad9 answered SERVFAIL for AAAA lookups of such names (5 of 6 attempts); an empty non-authoritative answer without a SOA looks like a lame delegation.

Change

The answer now sets AA and carries the zone SOA, which GSLB takes from the next plugin (e.g. file), with the TTL capped at the SOA MINIMUM.

$ dig @127.0.0.1 -p 1053 +norec app.example.org AAAA
;; flags: qr aa; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0
;; AUTHORITY SECTION:
example.org.  300  IN  SOA  ns1.example.org. hostmaster.example.org. 1 7200 1800 259200 300
  • No next plugin, or no SOA from it: the answer is unchanged.
  • fail-closed answers are unchanged: they depend on backend health and should not be negatively cached.
  • ECS scope is unchanged (/0).

Tests

Unit tests added for the three cases above. In TestServeDNS_FallthroughECSScopeMustBeZero one assertion is adjusted: the next plugin is now called for the SOA lookup, so the test checks that it does not receive the AAAA question.

go test -race ./... and golangci-lint run pass. Integration tests were not run.

Reproduce

.:1053 {
    bind 127.0.0.1
    file db.example.org example.org
    gslb {
        zone example.org. gslb.example.org.yml
    }
}
; db.example.org
$ORIGIN example.org.
@    3600 IN SOA ns1.example.org. hostmaster.example.org. 1 7200 1800 259200 300
@    3600 IN NS  ns1.example.org.
ns1  3600 IN A   192.0.2.1
# gslb.example.org.yml
records:
  "app.example.org.":
    mode: failover
    backends:
      - address: 192.0.2.10
        enable: true
        assume_healthy: true

…ypes

A query for a record type the record does not have (e.g. AAAA on an
IPv4-only record) was answered with a bare NOERROR: no AA, no SOA.
Without the SOA the answer cannot be negatively cached (RFC 2308).

The answer now sets AA and carries the zone SOA, taken from the next
plugin, with the TTL capped at the SOA MINIMUM. Without a SOA from the
next plugin the answer is unchanged. Fail-closed answers are unchanged.
@neermilov

Copy link
Copy Markdown
Contributor Author

cc @haosb

@dmachard

Copy link
Copy Markdown
Owner

Thanks for fixing this issue! Solid RFC 2308 fix and clean tests. LGTM!

@dmachard
dmachard marked this pull request as ready for review November 12, 2026 13:31
@dmachard
dmachard merged commit 5e4157e into dmachard:main Nov 12, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants