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.
dname := dnamerrs[0]
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 (
answer, ns, extra []dns.RR
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
example.com. 500 IN NS ns1.outside.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.`
func TestFileLoop(t *testing.T) {
@@ -31,18 +34,44 @@ func TestFileLoop(t *testing.T) {
}
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.SetQuestion("something.foo.example.com.", dns.TypeA)
m.SetQuestion(tc.qname, dns.TypeA)
r, err := dns.Exchange(m, udp)
if err != nil {
t.Fatalf("Could not exchange msg: %s", err)
}
// This should not loop, don't really care about the correctness of the answer.
// Currently we return servfail in the file lookup.go file.
// For now: document current behavior in this test.
if r.Rcode != dns.RcodeServerFailure {
t.Errorf("Rcode should be dns.RcodeServerFailure: %d", r.Rcode)
if r.Rcode != tc.wantRcode {
t.Errorf("Rcode should be %d, got %d", tc.wantRcode, r.Rcode)
}
if !tc.checkAnswer {
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))
}
})
}
}