Skip to content

Commit 1c15aaf

Browse files
committed
Support for CNAMEs, including out-of-zone
Add support for CNAMEs both inside and outside of the zones set by this plugin. CNAME support is robust against excessive stack depth and loops. Includes tests for all changes. Signed-off-by: Dan Fuhry <dan@fuhry.com>
1 parent a3157e7 commit 1c15aaf

File tree

2 files changed

+100
-8
lines changed

2 files changed

+100
-8
lines changed

records.go

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
1316
type 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.
70122
func (re *Records) Name() string { return "records" }
71123

72124
// New returns a pointer to a new and intialized Records.
73125
func 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
}

records_test.go

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"github.com/coredns/coredns/plugin/pkg/dnstest"
88
"github.com/coredns/coredns/plugin/test"
99

10-
"github.com/caddyserver/caddy"
10+
"github.com/coredns/caddy"
1111
"github.com/miekg/dns"
1212
)
1313

@@ -74,8 +74,25 @@ var testCases = []test.Case{
7474
func TestLookupNoSOA(t *testing.T) {
7575
const input = `
7676
records {
77-
example.org. 60 IN MX 10 mx.example.org.
78-
mx.example.org. 60 IN A 127.0.0.1
77+
example.org. 60 IN MX 10 mx.example.org.
78+
mx.example.org. 60 IN A 127.0.0.1
79+
cname.example.org. 60 IN CNAME mx.example.org.
80+
cnameloop1.example.org. 60 IN CNAME cnameloop2.example.org.
81+
cnameloop2.example.org. 60 IN CNAME cnameloop1.example.org.
82+
cnameext.example.org. 60 IN CNAME mx.example.net.
83+
84+
cnamedepth.example.org. 60 IN CNAME cnamedepth1.example.org.
85+
cnamedepth1.example.org. 60 IN CNAME cnamedepth2.example.org.
86+
cnamedepth2.example.org. 60 IN CNAME cnamedepth3.example.org.
87+
cnamedepth3.example.org. 60 IN CNAME cnamedepth4.example.org.
88+
cnamedepth4.example.org. 60 IN CNAME cnamedepth5.example.org.
89+
cnamedepth5.example.org. 60 IN CNAME cnamedepth6.example.org.
90+
cnamedepth6.example.org. 60 IN CNAME cnamedepth7.example.org.
91+
cnamedepth7.example.org. 60 IN CNAME cnamedepth8.example.org.
92+
cnamedepth8.example.org. 60 IN CNAME cnamedepth9.example.org.
93+
cnamedepth9.example.org. 60 IN CNAME cnamedepth10.example.org.
94+
cnamedepth10.example.org. 60 IN CNAME cnamedepth11.example.org.
95+
cnamedepth11.example.org. 60 IN A 127.0.0.1
7996
}
8097
`
8198

@@ -122,6 +139,27 @@ var testCasesNoSOA = []test.Case{
122139
{
123140
Qname: "mx.example.org.", Qtype: dns.TypeAAAA,
124141
},
142+
{
143+
Qname: "cname.example.org.", Qtype: dns.TypeA,
144+
Answer: []dns.RR{
145+
test.CNAME("cname.example.org. 60 IN CNAME mx.example.org."),
146+
test.A("mx.example.org. 60 IN A 127.0.0.1"),
147+
},
148+
},
149+
{
150+
Qname: "cnameext.example.org.", Qtype: dns.TypeA,
151+
Answer: []dns.RR{
152+
test.CNAME("cnameext.example.org. 60 IN CNAME mx.example.net."),
153+
},
154+
},
155+
{
156+
Rcode: dns.RcodeServerFailure,
157+
Qname: "cnameloop1.example.org.", Qtype: dns.TypeA,
158+
},
159+
{
160+
Rcode: dns.RcodeServerFailure,
161+
Qname: "cnamedepth.example.org.", Qtype: dns.TypeA,
162+
},
125163
}
126164

127165
func TestLookupMultipleOrigins(t *testing.T) {

0 commit comments

Comments
 (0)