plugin/file: stop self-referential DNAME loops (#8418)

This commit is contained in:
houyuwushang
2026-08-11 04:26:01 +08:00
committed by GitHub
parent c2ca1b2c23
commit ea2eea57a7
2 changed files with 45 additions and 11 deletions

View File

@@ -118,6 +118,11 @@ func (z *Zone) Lookup(ctx context.Context, state request.Request, qname string)
// Only one DNAME is allowed per name. We just pick the first one to synthesize from. // Only one DNAME is allowed per name. We just pick the first one to synthesize from.
dname := dnamerrs[0] dname := dnamerrs[0]
if cname := synthesizeCNAME(state.Name(), dname.(*dns.DNAME)); cname != nil { if cname := synthesizeCNAME(state.Name(), dname.(*dns.DNAME)); cname != nil {
// A DNAME substitution that does not change the name can only loop.
if dns.CanonicalName(cname.Hdr.Name) == dns.CanonicalName(cname.Target) {
return nil, nil, nil, ServerFailure
}
var ( var (
answer, ns, extra []dns.RR answer, ns, extra []dns.RR
rcode Result rcode Result

View File

@@ -11,6 +11,9 @@ import (
const loopDB = `example.com. 500 IN SOA ns1.outside.com. root.example.com. 3 604800 86400 2419200 604800 const loopDB = `example.com. 500 IN SOA ns1.outside.com. root.example.com. 3 604800 86400 2419200 604800
example.com. 500 IN NS ns1.outside.com. example.com. 500 IN NS ns1.outside.com.
a.example.com. 500 IN CNAME b.example.com. a.example.com. 500 IN CNAME b.example.com.
alias.example.com. 500 IN DNAME alias.example.com.
redirect.example.com. 500 IN DNAME target.example.com.
www.target.example.com. 500 IN A 192.0.2.1
*.foo.example.com. 500 IN CNAME bar.foo.example.com.` *.foo.example.com. 500 IN CNAME bar.foo.example.com.`
func TestFileLoop(t *testing.T) { func TestFileLoop(t *testing.T) {
@@ -31,18 +34,44 @@ func TestFileLoop(t *testing.T) {
} }
defer i.Stop() defer i.Stop()
tests := []struct {
name string
qname string
wantRcode int
checkAnswer bool
wantAnswerTypes []uint16
}{
{"wildcard CNAME", "something.foo.example.com.", dns.RcodeServerFailure, false, nil},
{"self-referential DNAME", "www.alias.example.com.", dns.RcodeServerFailure, true, nil},
{"non-looping DNAME", "www.redirect.example.com.", dns.RcodeSuccess, true, []uint16{dns.TypeDNAME, dns.TypeCNAME, dns.TypeA}},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
m := new(dns.Msg) m := new(dns.Msg)
m.SetQuestion("something.foo.example.com.", dns.TypeA) m.SetQuestion(tc.qname, dns.TypeA)
r, err := dns.Exchange(m, udp) r, err := dns.Exchange(m, udp)
if err != nil { if err != nil {
t.Fatalf("Could not exchange msg: %s", err) t.Fatalf("Could not exchange msg: %s", err)
} }
// This should not loop, don't really care about the correctness of the answer. if r.Rcode != tc.wantRcode {
// Currently we return servfail in the file lookup.go file. t.Errorf("Rcode should be %d, got %d", tc.wantRcode, r.Rcode)
// For now: document current behavior in this test. }
if r.Rcode != dns.RcodeServerFailure { if !tc.checkAnswer {
t.Errorf("Rcode should be dns.RcodeServerFailure: %d", r.Rcode) return
}
if len(r.Answer) != len(tc.wantAnswerTypes) {
t.Fatalf("Expected %d answer records, got %d", len(tc.wantAnswerTypes), len(r.Answer))
}
for i, qtype := range tc.wantAnswerTypes {
if r.Answer[i].Header().Rrtype != qtype {
t.Errorf("Answer %d should have type %s, got %s", i, dns.TypeToString[qtype], dns.TypeToString[r.Answer[i].Header().Rrtype])
}
}
if len(tc.wantAnswerTypes) == 0 && (len(r.Ns) != 0 || len(r.Extra) != 0) {
t.Errorf("Response sections should be empty, got %d answer, %d authority, and %d additional records", len(r.Answer), len(r.Ns), len(r.Extra))
}
})
} }
} }