Qualify relative RDATA names in the zone-file parser
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:
1 parent
ad9989b28c
commit
a5e54c09df
4 files changed
+1043
-49
No files matched your search
@@ -123,9 +123,9 @@ func add(z *zone, rec zonefile.Record) bool {
|
||||
if hdr.Rrtype == dns.TypeNS && !isWild {
|
||||
if key == z.origin {
|
||||
z.ns[hdr.Rrtype] = append(z.ns[hdr.Rrtype], rec.RR)
|
||||
return true
|
||||
} else {
|
||||
z.delegations[key] = true
|
||||
}
|
||||
z.delegations[key] = true
|
||||
}
|
||||
if hdr.Ttl < z.minTTL {
|
||||
z.minTTL = hdr.Ttl
|
||||
@@ -246,8 +246,17 @@ func (s *Store) Lookup(q dns.Question) Result {
|
||||
if qtype == dns.TypeANY {
|
||||
return s.any(best, name)
|
||||
}
|
||||
// A delegation means "ask them, not me".
|
||||
if best.delegations[name] {
|
||||
// A delegation means "ask them, not me": the parent holds no data at or
|
||||
// below the cut, so it must neither answer nor claim NXDOMAIN. The NS
|
||||
// RRset at the cut is the one exception -- only the parent has it, and a
|
||||
// resolver needs it to learn the delegation (RFC 1034 §4.3.3).
|
||||
for d := name; d != ""; d = parentOf(d) {
|
||||
if !best.delegations[d] {
|
||||
continue
|
||||
}
|
||||
if d == name && qtype == dns.TypeNS {
|
||||
break
|
||||
}
|
||||
return Result{Found: false, Delegated: true, Zone: best.origin, Authority: best.soa, TTL: best.minTTL}
|
||||
}
|
||||
if rrs, ok := best.exact[name+"|"+dns.TypeToString[qtype]]; ok {
|
||||
@@ -256,7 +265,8 @@ func (s *Store) Lookup(q dns.Question) Result {
|
||||
// Wildcard: "*.zone" covers one label; RFC 1034 §4.3.3 wants the closest
|
||||
// enclosing wildcard, so try progressively shorter parents.
|
||||
if rrs, ok := closestWildcard(best, name, qtype); ok {
|
||||
return s.result(best, name, qtype, rrs)
|
||||
// A wildcard answer is labelled with the name that was asked for.
|
||||
return s.result(best, name, qtype, synthesize(rrs, name))
|
||||
}
|
||||
// A CNAME at the name answers any other type.
|
||||
if qtype != dns.TypeCNAME {
|
||||
@@ -309,7 +319,7 @@ func (s *Store) any(z *zone, name string) Result {
|
||||
}
|
||||
if len(out) == 0 {
|
||||
if rrs, ok := closestWildcardAny(z, name); ok {
|
||||
out = append(out, rrs...)
|
||||
out = append(out, synthesize(rrs, name)...)
|
||||
}
|
||||
}
|
||||
if len(out) == 0 {
|
||||
@@ -319,13 +329,20 @@ func (s *Store) any(z *zone, name string) Result {
|
||||
}
|
||||
|
||||
func closestWildcard(z *zone, name string, qtype uint16) ([]dns.RR, bool) {
|
||||
// A wildcard only fills in for a name that has no node of its own.
|
||||
if len(z.wild) == 0 || nameExists(z, name) {
|
||||
return nil, false
|
||||
}
|
||||
for parent := parentOf(name); parent != ""; parent = parentOf(parent) {
|
||||
if m, ok := z.wild[parent]; ok {
|
||||
if rrs, ok := m[qtype]; ok {
|
||||
return rrs, true
|
||||
}
|
||||
}
|
||||
if z.delegations[parent] {
|
||||
// A real node (or a delegation) is the closest encloser, and only a
|
||||
// wildcard directly below it can answer: a wildcard never reaches
|
||||
// past a name that exists.
|
||||
if z.delegations[parent] || nameExists(z, parent) {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
@@ -333,6 +350,9 @@ func closestWildcard(z *zone, name string, qtype uint16) ([]dns.RR, bool) {
|
||||
}
|
||||
|
||||
func closestWildcardAny(z *zone, name string) ([]dns.RR, bool) {
|
||||
if len(z.wild) == 0 || nameExists(z, name) {
|
||||
return nil, false
|
||||
}
|
||||
for parent := parentOf(name); parent != ""; parent = parentOf(parent) {
|
||||
if m, ok := z.wild[parent]; ok {
|
||||
var out []dns.RR
|
||||
@@ -343,6 +363,9 @@ func closestWildcardAny(z *zone, name string) ([]dns.RR, bool) {
|
||||
return out, true
|
||||
}
|
||||
}
|
||||
if z.delegations[parent] || nameExists(z, parent) {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
@@ -355,7 +378,28 @@ func parentOf(name string) string {
|
||||
return name[i+1:]
|
||||
}
|
||||
|
||||
// synthesize copies rrs with the owner name replaced by qname, because a
|
||||
// wildcard answer is labelled with the name that was asked for. The store is
|
||||
// shared read-only, so the records are copied instead of rewritten in place.
|
||||
func synthesize(rrs []dns.RR, qname string) []dns.RR {
|
||||
out := make([]dns.RR, 0, len(rrs))
|
||||
for _, rr := range rrs {
|
||||
cp := dns.Copy(rr)
|
||||
if cp == nil {
|
||||
continue
|
||||
}
|
||||
cp.Header().Name = qname
|
||||
out = append(out, cp)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func nameExists(z *zone, name string) bool {
|
||||
// The apex always exists: it carries the SOA and the NS set, and neither
|
||||
// is indexed as a queryable record.
|
||||
if name == z.origin {
|
||||
return true
|
||||
}
|
||||
prefix := name + "|"
|
||||
for k := range z.exact {
|
||||
if strings.HasPrefix(k, prefix) {
|
||||
|
||||
Reference in new issue
Block a user