Skip to content

Commit 61cf6e2

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 61cf6e2

File tree

2 files changed

+89
-8
lines changed

2 files changed

+89
-8
lines changed

records.go

Lines changed: 48 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
}
@@ -34,15 +38,43 @@ func (re *Records) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms
3438

3539
nxdomain := true
3640
var soa dns.RR
41+
cnameStack := make(map[string]struct{}, 0)
42+
43+
resolveLoop:
3744
for _, r := range re.m[zone] {
45+
if _, ok := cnameStack[qname]; ok {
46+
log.Errorf("detected loop in CNAME chain, name [%s] already processed", qname)
47+
goto servfail
48+
}
49+
if len(cnameStack) > maxCnameStackDepth {
50+
log.Errorf("maximum CNAME stack depth of %d exceeded", maxCnameStackDepth)
51+
goto servfail
52+
}
53+
3854
if r.Header().Rrtype == dns.TypeSOA && soa == nil {
3955
soa = r
4056
}
4157
if r.Header().Name == qname {
4258
nxdomain = false
43-
if r.Header().Rrtype == state.QType() {
59+
if r.Header().Rrtype == state.QType() || r.Header().Rrtype == dns.TypeCNAME {
4460
m.Answer = append(m.Answer, r)
4561
}
62+
if r.Header().Rrtype == dns.TypeCNAME {
63+
cnameStack[qname] = struct{}{}
64+
qname = r.(*dns.CNAME).Target
65+
if plugin.Zones(re.origins).Matches(qname) == "" {
66+
// if the CNAME target isn't a record in this zone, restart with upstream.
67+
msgs, err := re.upstream.Lookup(ctx, state, qname, state.QType())
68+
if err != nil {
69+
return dns.RcodeServerFailure, err
70+
}
71+
for _, ans := range msgs.Answer {
72+
m.Answer = append(m.Answer, ans)
73+
}
74+
break resolveLoop
75+
}
76+
goto resolveLoop
77+
}
4678
}
4779
}
4880

@@ -64,14 +96,25 @@ func (re *Records) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms
6496

6597
w.WriteMsg(m)
6698
return dns.RcodeSuccess, nil
99+
100+
servfail:
101+
m.Rcode = dns.RcodeServerFailure
102+
m.Answer = nil
103+
if soa != nil {
104+
m.Ns = []dns.RR{soa}
105+
}
106+
w.WriteMsg(m)
107+
return dns.RcodeServerFailure, nil
67108
}
68109

69110
// Name implements the plugin.Handle interface.
70111
func (re *Records) Name() string { return "records" }
71112

72113
// New returns a pointer to a new and intialized Records.
73114
func New() *Records {
74-
re := new(Records)
75-
re.m = make(map[string][]dns.RR)
115+
re := &Records{
116+
m: make(map[string][]dns.RR),
117+
upstream: upstream.New(),
118+
}
76119
return re
77120
}

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)