diff options
-rw-r--r-- | plugin/template/cname_test.go | 96 | ||||
-rw-r--r-- | plugin/template/template.go | 14 |
2 files changed, 106 insertions, 4 deletions
diff --git a/plugin/template/cname_test.go b/plugin/template/cname_test.go new file mode 100644 index 000000000..c7e81df23 --- /dev/null +++ b/plugin/template/cname_test.go @@ -0,0 +1,96 @@ +package template + +import ( + "context" + "regexp" + "testing" + gotmpl "text/template" + + "github.com/coredns/coredns/plugin/pkg/dnstest" + "github.com/coredns/coredns/plugin/test" + "github.com/coredns/coredns/request" + + "github.com/miekg/dns" +) + +func TestTruncatedCNAME(t *testing.T) { + up := &Upstub{ + Qclass: dns.ClassINET, + Truncated: true, + Case: test.Case{ + Qname: "cname.test.", + Qtype: dns.TypeA, + Rcode: dns.RcodeSuccess, + Answer: []dns.RR{ + test.CNAME("cname.test. 600 IN CNAME test.up"), + test.A("test.up. 600 IN A 1.2.3.4"), + }, + }, + } + + handler := Handler{ + Zones: []string{"."}, + Templates: []template{{ + regex: []*regexp.Regexp{regexp.MustCompile("^cname\\.test\\.$")}, + answer: []*gotmpl.Template{gotmpl.Must(gotmpl.New("answer").Parse(up.Answer[0].String()))}, + qclass: dns.ClassINET, + qtype: dns.TypeA, + zones: []string{"test."}, + upstream: up, + }}, + } + + r := &dns.Msg{Question: []dns.Question{{Name: up.Qname, Qclass: up.Qclass, Qtype: up.Qtype}}} + w := dnstest.NewRecorder(&test.ResponseWriter{}) + + _, err := handler.ServeDNS(context.TODO(), w, r) + + if err != nil { + t.Fatalf("Unexpecetd error %q", err) + } + if w.Msg == nil { + t.Fatalf("Unexpecetd empty response.") + } + if !w.Msg.Truncated { + t.Error("Expected reply to be marked truncated.") + } + err = test.SortAndCheck(w.Msg, up.Case) + if err != nil { + t.Error(err) + } +} + +// Upstub implements an Upstreamer that returns a set response for test purposes +type Upstub struct { + test.Case + Truncated bool + Qclass uint16 +} + +// Lookup returns a set response +func (t *Upstub) Lookup(ctx context.Context, state request.Request, name string, typ uint16) (*dns.Msg, error) { + var answer []dns.RR + // if query type is not CNAME, remove any CNAME with same name as qname from the answer + if t.Qtype != dns.TypeCNAME { + for _, a := range t.Answer { + if c, ok := a.(*dns.CNAME); ok && c.Header().Name == t.Qname { + continue + } + answer = append(answer, a) + } + } else { + answer = t.Answer + } + + return &dns.Msg{ + MsgHdr: dns.MsgHdr{ + Response: true, + Truncated: t.Truncated, + Rcode: t.Rcode, + }, + Question: []dns.Question{{Name: t.Qname, Qtype: t.Qtype, Qclass: t.Qclass}}, + Answer: answer, + Extra: t.Extra, + Ns: t.Ns, + }, nil +} diff --git a/plugin/template/template.go b/plugin/template/template.go index 7f26349d5..148346823 100644 --- a/plugin/template/template.go +++ b/plugin/template/template.go @@ -11,7 +11,6 @@ import ( "github.com/coredns/coredns/plugin/metadata" "github.com/coredns/coredns/plugin/metrics" "github.com/coredns/coredns/plugin/pkg/fall" - "github.com/coredns/coredns/plugin/pkg/upstream" "github.com/coredns/coredns/request" "github.com/miekg/dns" @@ -35,7 +34,12 @@ type template struct { qclass uint16 qtype uint16 fall fall.F - upstream *upstream.Upstream + upstream Upstreamer +} + +// Upstreamer looks up targets of CNAME templates +type Upstreamer interface { + Lookup(ctx context.Context, state request.Request, name string, typ uint16) (*dns.Msg, error) } type templateData struct { @@ -100,8 +104,10 @@ func (h Handler) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) } msg.Answer = append(msg.Answer, rr) if template.upstream != nil && (state.QType() == dns.TypeA || state.QType() == dns.TypeAAAA) && rr.Header().Rrtype == dns.TypeCNAME { - up, _ := template.upstream.Lookup(ctx, state, rr.(*dns.CNAME).Target, state.QType()) - msg.Answer = append(msg.Answer, up.Answer...) + if up, err := template.upstream.Lookup(ctx, state, rr.(*dns.CNAME).Target, state.QType()); err == nil && up != nil { + msg.Truncated = up.Truncated + msg.Answer = append(msg.Answer, up.Answer...) + } } } for _, additional := range template.additional { |