From f70b8733ea215dace251d26c8ad608008b051c10 Mon Sep 17 00:00:00 2001 From: Dan Fuhry Date: Wed, 8 Mar 2023 13:11:08 -0500 Subject: [PATCH] Support CNAMEs and the ANY query type Add support for CNAMEs both inside and outside of the zones set by this plugin. Add support for the ANY question type. CNAME support is robust against excessive stack depth and loops. Includes tests for all changes. Signed-off-by: Dan Fuhry --- records.go | 36 +++++++++++++++++++++++++++++++++++- records_test.go | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/records.go b/records.go index 4a414f7..319a801 100644 --- a/records.go +++ b/records.go @@ -9,6 +9,8 @@ import ( "github.com/miekg/dns" ) +const maxCnameStackDepth = 10 + // Records is the plugin handler. type Records struct { origins []string // for easy matching, these strings are the index in the map m. @@ -34,15 +36,38 @@ func (re *Records) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms nxdomain := true var soa dns.RR + cnameStack := make(map[string]struct{}, 0) + +resolveLoop: for _, r := range re.m[zone] { + if _, ok := cnameStack[qname]; ok { + log.Errorf("detected loop in CNAME chain, name [%s] already processed", qname) + goto servfail + } + if len(cnameStack) > maxCnameStackDepth { + log.Errorf("maximum CNAME stack depth of %d exceeded", maxCnameStackDepth) + goto servfail + } + if r.Header().Rrtype == dns.TypeSOA && soa == nil { soa = r } if r.Header().Name == qname { nxdomain = false - if r.Header().Rrtype == state.QType() { + if r.Header().Rrtype == state.QType() || r.Header().Rrtype == dns.TypeCNAME || state.QType() == dns.TypeANY { m.Answer = append(m.Answer, r) } + if r.Header().Rrtype == dns.TypeCNAME { + cnameStack[qname] = struct{}{} + qname = r.(*dns.CNAME).Target + if plugin.Zones(re.origins).Matches(qname) == "" { + // if the CNAME target isn't a record in this zone, break and return. + // The administrator can configure the `finalize` plugin (https://coredns.io/explugins/finalize/) + // to complete resolution of these names. + break resolveLoop + } + goto resolveLoop + } } } @@ -64,6 +89,15 @@ func (re *Records) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms w.WriteMsg(m) return dns.RcodeSuccess, nil + +servfail: + m.Rcode = dns.RcodeServerFailure + m.Answer = nil + if soa != nil { + m.Ns = []dns.RR{soa} + } + w.WriteMsg(m) + return dns.RcodeServerFailure, nil } // Name implements the plugin.Handle interface. diff --git a/records_test.go b/records_test.go index ef9d11d..fda394d 100644 --- a/records_test.go +++ b/records_test.go @@ -7,7 +7,7 @@ import ( "github.com/coredns/coredns/plugin/pkg/dnstest" "github.com/coredns/coredns/plugin/test" - "github.com/caddyserver/caddy" + "github.com/coredns/caddy" "github.com/miekg/dns" ) @@ -76,6 +76,25 @@ func TestLookupNoSOA(t *testing.T) { records { example.org. 60 IN MX 10 mx.example.org. mx.example.org. 60 IN A 127.0.0.1 + cname.example.org. 60 IN CNAME mx.example.org. + dualstack.example.org. 60 IN A 127.0.0.1 + dualstack.example.org. 60 IN AAAA ::1 + cnameloop1.example.org. 60 IN CNAME cnameloop2.example.org. + cnameloop2.example.org. 60 IN CNAME cnameloop1.example.org. + cnameext.example.org. 60 IN CNAME mx.example.net. + + cnamedepth.example.org. 60 IN CNAME cnamedepth1.example.org. + cnamedepth1.example.org. 60 IN CNAME cnamedepth2.example.org. + cnamedepth2.example.org. 60 IN CNAME cnamedepth3.example.org. + cnamedepth3.example.org. 60 IN CNAME cnamedepth4.example.org. + cnamedepth4.example.org. 60 IN CNAME cnamedepth5.example.org. + cnamedepth5.example.org. 60 IN CNAME cnamedepth6.example.org. + cnamedepth6.example.org. 60 IN CNAME cnamedepth7.example.org. + cnamedepth7.example.org. 60 IN CNAME cnamedepth8.example.org. + cnamedepth8.example.org. 60 IN CNAME cnamedepth9.example.org. + cnamedepth9.example.org. 60 IN CNAME cnamedepth10.example.org. + cnamedepth10.example.org. 60 IN CNAME cnamedepth11.example.org. + cnamedepth11.example.org. 60 IN A 127.0.0.1 } ` @@ -122,6 +141,34 @@ var testCasesNoSOA = []test.Case{ { Qname: "mx.example.org.", Qtype: dns.TypeAAAA, }, + { + Qname: "dualstack.example.org.", Qtype: dns.TypeANY, + Answer: []dns.RR{ + test.A("dualstack.example.org. 60 IN A 127.0.0.1"), + test.AAAA("dualstack.example.org. 60 IN AAAA ::1"), + }, + }, + { + Qname: "cname.example.org.", Qtype: dns.TypeA, + Answer: []dns.RR{ + test.CNAME("cname.example.org. 60 IN CNAME mx.example.org."), + test.A("mx.example.org. 60 IN A 127.0.0.1"), + }, + }, + { + Qname: "cnameext.example.org.", Qtype: dns.TypeA, + Answer: []dns.RR{ + test.CNAME("cnameext.example.org. 60 IN CNAME mx.example.net."), + }, + }, + { + Rcode: dns.RcodeServerFailure, + Qname: "cnameloop1.example.org.", Qtype: dns.TypeA, + }, + { + Rcode: dns.RcodeServerFailure, + Qname: "cnamedepth.example.org.", Qtype: dns.TypeA, + }, } func TestLookupMultipleOrigins(t *testing.T) {