@@ -4,15 +4,19 @@ import (
44 "context"
55
66 "github.com/coredns/coredns/plugin"
7+ "github.com/coredns/coredns/plugin/pkg/upstream"
78 "github.com/coredns/coredns/request"
89
910 "github.com/miekg/dns"
1011)
1112
13+ const maxCnameStackDepth = 10
14+
1215// Records is the plugin handler.
1316type Records struct {
14- origins []string // for easy matching, these strings are the index in the map m.
15- m map [string ][]dns.RR
17+ origins []string // for easy matching, these strings are the index in the map m.
18+ m map [string ][]dns.RR
19+ upstream * upstream.Upstream
1620
1721 Next plugin.Handler
1822}
@@ -33,16 +37,55 @@ func (re *Records) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms
3337 m .Authoritative = true
3438
3539 nxdomain := true
40+ // cnameMaybeUpstream tracks whether we are currently trying to resolve a CNAME. We always look for a match among
41+ // the records handled by this plugin first, then we go upstream. This is required to enforce stack depth and loop
42+ // detection.
43+ cnameMaybeUpstream := false
3644 var soa dns.RR
45+ cnameStack := make (map [string ]struct {}, 0 )
46+
47+ resolveLoop:
3748 for _ , r := range re .m [zone ] {
49+ if _ , ok := cnameStack [qname ]; ok {
50+ log .Errorf ("detected loop in CNAME chain, name [%s] already processed" , qname )
51+ goto servfail
52+ }
53+ if len (cnameStack ) > maxCnameStackDepth {
54+ log .Errorf ("maximum CNAME stack depth of %d exceeded" , maxCnameStackDepth )
55+ goto servfail
56+ }
57+
3858 if r .Header ().Rrtype == dns .TypeSOA && soa == nil {
3959 soa = r
4060 }
4161 if r .Header ().Name == qname {
4262 nxdomain = false
43- if r .Header ().Rrtype == state .QType () {
63+ if r .Header ().Rrtype == state .QType () || r . Header (). Rrtype == dns . TypeCNAME {
4464 m .Answer = append (m .Answer , r )
4565 }
66+ if r .Header ().Rrtype == dns .TypeCNAME {
67+ cnameStack [qname ] = struct {}{}
68+ qname = r .(* dns.CNAME ).Target
69+ cnameMaybeUpstream = true
70+ // restart resolution with new query name
71+ goto resolveLoop
72+ } else {
73+ // If we found a match but the record type in the zone we control isn't
74+ // another CNAME, that means we have reached the end of our chain and we
75+ // don't need to go upstream.
76+ cnameMaybeUpstream = false
77+ }
78+ }
79+ }
80+
81+ if cnameMaybeUpstream {
82+ // we've found a CNAME but it doesn't point to a record managed by this
83+ // plugin. In these cases we always restart with upstream.
84+ msgs , err := re .upstream .Lookup (ctx , state , qname , state .QType ())
85+ if err == nil && len (msgs .Answer ) > 0 {
86+ for _ , ans := range msgs .Answer {
87+ m .Answer = append (m .Answer , ans )
88+ }
4689 }
4790 }
4891
@@ -64,14 +107,25 @@ func (re *Records) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms
64107
65108 w .WriteMsg (m )
66109 return dns .RcodeSuccess , nil
110+
111+ servfail:
112+ m .Rcode = dns .RcodeServerFailure
113+ m .Answer = nil
114+ if soa != nil {
115+ m .Ns = []dns.RR {soa }
116+ }
117+ w .WriteMsg (m )
118+ return dns .RcodeServerFailure , nil
67119}
68120
69121// Name implements the plugin.Handle interface.
70122func (re * Records ) Name () string { return "records" }
71123
72124// New returns a pointer to a new and intialized Records.
73125func New () * Records {
74- re := new (Records )
75- re .m = make (map [string ][]dns.RR )
126+ re := & Records {
127+ m : make (map [string ][]dns.RR ),
128+ upstream : upstream .New (),
129+ }
76130 return re
77131}
0 commit comments