Qualify relative RDATA names in the zone-file parser
ci / Console typecheck and build (push) Successful in 13s
ci / Go build, vet and test (push) Successful in 20s
ci / Binary with the console embedded (push) Successful in 17s
ci / Container image (push) Failing after 8s

A domain name in the RDATA section written without a trailing dot is
relative to the zone origin (RFC 1035 §5.1), so "www CNAME host" in
example.com. means host.example.com. The qualifier ran on the record
after dns.NewRR had already resolved every RDATA name against the root,
which turns "host" into "host." and makes the two indistinguishable --
relative() saw the trailing dot and left it alone. Every relative CNAME,
NS, MX, PTR, SRV, NAPTR, SOA and SVCB target therefore pointed at the
DNS root, which showed up as a local zone answering a CNAME with
"host." and the alias target holding no local answer. Qualification now
happens in assemble on the token stream, before the record is built,
using a table of the RDATA positions that hold a name per type; names
that already end in a dot stay absolute, so the RPZ targets "." and
"rpz-drop." and the null MX targets are unaffected.

Fix three wrong expectations in the localzone tests while here. The loop
fixture was built with dns.NewRR on "alias.example.com", which cannot
parse because the RR library wants a TTL and class column, so the
expected record now spells them out. The origin-derivation fixture
used "a" as an owner name, and a leading token that names an RR type
reads as an omitted owner, which has no previous owner to inherit on
the first line of a file; the owner is now a word. The merge test
counted the SOA, but it is zone metadata rather than a queryable
record, so two files contribute three records. The holder race test also
looked up a name from the base zone while the holder was serving a store
compiled from other.test., which no answer could satisfy; it now
accepts either store's data.
This commit is contained in:
developer committed 2026-09-27 14:10:25 +00:00
1 parent ad9989b28c
commit a5e54c09df
4 files changed
+1043 -49

No files matched your search

+56
View File
@@ -609,6 +609,62 @@ func TestParseOriginDirective(t *testing.T) {
})
}
// A domain name in the RDATA section that is written without a trailing dot is
// relative to the zone origin (RFC 1035 §5.1). The qualification has to happen
// on the token stream, before dns.NewRR: the library resolves RDATA names
// against the root, so afterwards "host" and "host." are indistinguishable and
// a relative CNAME target would silently point at the root instead.
func TestParseRelativeRdata(t *testing.T) {
// The owners are deliberately not RR type names: a leading token that names
// a type reads as an omitted owner, as assemble documents.
z := mustParse(t, `$ORIGIN example.com.
cn 300 IN CNAME host
ns1sub 300 IN NS ns1.sub
ptral 300 IN PTR host
mail 300 IN MX 10 mailhost
srvone 300 IN SRV 0 1 5060 sip
napone 300 IN NAPTR 100 50 "s" "http+I2R" "" _http._tcp
svcone 300 IN SVCB 1 _sip._tcp
zone1 300 IN SOA ns1 hostmaster 1 3600 600 604800 300
abs 300 IN CNAME host.other.test.
nullmx 300 IN MX 10 .
txtone 300 IN TXT "host"
`, Options{Origin: "example.com."})
checkSummary(t, z, []string{
`2 cn.example.com. 300 IN CNAME host.example.com.`,
`3 ns1sub.example.com. 300 IN NS ns1.sub.example.com.`,
`4 ptral.example.com. 300 IN PTR host.example.com.`,
`5 mail.example.com. 300 IN MX 10 mailhost.example.com.`,
`6 srvone.example.com. 300 IN SRV 0 1 5060 sip.example.com.`,
`7 napone.example.com. 300 IN NAPTR 100 50 "s" "http+I2R" "" _http._tcp.example.com.`,
`8 svcone.example.com. 300 IN SVCB 1 _sip._tcp.example.com.`,
`9 zone1.example.com. 300 IN SOA ns1.example.com. hostmaster.example.com. 1 3600 600 604800 300`,
`10 abs.example.com. 300 IN CNAME host.other.test.`,
`11 nullmx.example.com. 300 IN MX 10 .`,
`12 txtone.example.com. 300 IN TXT "host"`,
})
}
// Each line is assembled on its own and fold continues a statement only while
// parentheses are open, so a record never swallows the line after it -- whatever
// its token count is.
func TestParseRecordsDoNotRunTogether(t *testing.T) {
z := mustParse(t, `$ORIGIN example.com.
ns1 A 192.0.2.53
note TXT "hello world"
@ IN SOA ns1 hostmaster 1 3600 600 604800 300
mail MX 10 mxhost
`, Options{})
if got, want := summaryNoTTL(t, z), []string{
`2 ns1.example.com. IN A 192.0.2.53`,
`3 note.example.com. IN TXT "hello world"`,
`4 example.com. IN SOA ns1.example.com. hostmaster.example.com. 1 3600 600 604800 300`,
`5 mail.example.com. IN MX 10 mxhost.example.com.`,
}; !slices.Equal(got, want) {
t.Errorf("records mismatch\n got: %q\nwant: %q", got, want)
}
}
func TestParseClassHandling(t *testing.T) {
z := mustParse(t, `one IN A 192.0.2.1
two in A 192.0.2.2