Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support CNAMEs, including out-of-zone #4

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 59 additions & 5 deletions records.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@ import (
"context"

"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/pkg/upstream"
"github.com/coredns/coredns/request"

"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.
m map[string][]dns.RR
origins []string // for easy matching, these strings are the index in the map m.
m map[string][]dns.RR
upstream *upstream.Upstream

Next plugin.Handler
}
Expand All @@ -33,16 +37,55 @@ func (re *Records) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms
m.Authoritative = true

nxdomain := true
// cnameMaybeUpstream tracks whether we are currently trying to resolve a CNAME. We always look for a match among
// the records handled by this plugin first, then we go upstream. This is required to enforce stack depth and loop
// detection.
cnameMaybeUpstream := false
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if servfail is right answer here. It could be, but I think a response with a dangling cname might be preferable to a servfail. Not sure, need to check RFCs/other implementations.

}

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 {
m.Answer = append(m.Answer, r)
}
if r.Header().Rrtype == dns.TypeCNAME {
cnameStack[qname] = struct{}{}
qname = r.(*dns.CNAME).Target
cnameMaybeUpstream = true
// restart resolution with new query name
goto resolveLoop
} else {
// If we found a match but the record type in the zone we control isn't
// another CNAME, that means we have reached the end of our chain and we
// don't need to go upstream.
cnameMaybeUpstream = false
}
}
}

if cnameMaybeUpstream {
// we've found a CNAME but it doesn't point to a record managed by this
// plugin. In these cases we always restart with upstream.
msgs, err := re.upstream.Lookup(ctx, state, qname, state.QType())
if err == nil && len(msgs.Answer) > 0 {
for _, ans := range msgs.Answer {
m.Answer = append(m.Answer, ans)
}
}
}

Expand All @@ -64,14 +107,25 @@ 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.
func (re *Records) Name() string { return "records" }

// New returns a pointer to a new and intialized Records.
func New() *Records {
re := new(Records)
re.m = make(map[string][]dns.RR)
re := &Records{
m: make(map[string][]dns.RR),
upstream: upstream.New(),
}
return re
}
44 changes: 41 additions & 3 deletions records_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -74,8 +74,25 @@ var testCases = []test.Case{
func TestLookupNoSOA(t *testing.T) {
const input = `
records {
example.org. 60 IN MX 10 mx.example.org.
mx.example.org. 60 IN A 127.0.0.1
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.
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
}
`

Expand Down Expand Up @@ -122,6 +139,27 @@ var testCasesNoSOA = []test.Case{
{
Qname: "mx.example.org.", Qtype: dns.TypeAAAA,
},
{
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) {
Expand Down