mirror of
https://github.com/coredns/coredns.git
synced 2025-10-27 00:04:15 -04:00
Align plugin/template usage and syntax with other plugins (#1360)
* Align plugin/template usage and syntax with other plugins * Use new fallthrough logic in plugin/template * Use zone name normalization for plugin/template * Test fallthrough parsing in plugin/template * Rework scoping of match checks Most matches are not plugin global but per template. The plugin does only a very rough check while detailed checks are done per-template. Per template checks include: - Zones - Class/Type - Regex - Fallthrough * Remove trailing `.` from fully qualified domain names * Register template metrics with zone/class/type instead of regex * Remove trailing fqdn dot from multiple testcases
This commit is contained in:
committed by
Miek Gieben
parent
a7590897fb
commit
0091e1c9dc
@@ -7,80 +7,100 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/coredns/coredns/plugin/test"
|
||||
"github.com/mholt/caddy"
|
||||
|
||||
gotmpl "text/template"
|
||||
|
||||
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
||||
"github.com/coredns/coredns/plugin/pkg/fall"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func TestHandler(t *testing.T) {
|
||||
rcodeFallthrough := 3841 // reserved for private use, used to indicate a fallthrough
|
||||
exampleDomainATemplate := template{
|
||||
class: dns.ClassINET,
|
||||
qtype: dns.TypeA,
|
||||
regex: []*regexp.Regexp{regexp.MustCompile("(^|[.])ip-10-(?P<b>[0-9]*)-(?P<c>[0-9]*)-(?P<d>[0-9]*)[.]example[.]$")},
|
||||
answer: []*gotmpl.Template{gotmpl.Must(gotmpl.New("answer").Parse("{{ .Name }} 60 IN A 10.{{ .Group.b }}.{{ .Group.c }}.{{ .Group.d }}"))},
|
||||
regex: []*regexp.Regexp{regexp.MustCompile("(^|[.])ip-10-(?P<b>[0-9]*)-(?P<c>[0-9]*)-(?P<d>[0-9]*)[.]example[.]$")},
|
||||
answer: []*gotmpl.Template{gotmpl.Must(gotmpl.New("answer").Parse("{{ .Name }} 60 IN A 10.{{ .Group.b }}.{{ .Group.c }}.{{ .Group.d }}"))},
|
||||
qclass: dns.ClassANY,
|
||||
qtype: dns.TypeANY,
|
||||
fthrough: fall.F{Zones: []string{"."}},
|
||||
zones: []string{"."},
|
||||
}
|
||||
exampleDomainANSTemplate := template{
|
||||
class: dns.ClassINET,
|
||||
qtype: dns.TypeA,
|
||||
regex: []*regexp.Regexp{regexp.MustCompile("(^|[.])ip-10-(?P<b>[0-9]*)-(?P<c>[0-9]*)-(?P<d>[0-9]*)[.]example[.]$")},
|
||||
answer: []*gotmpl.Template{gotmpl.Must(gotmpl.New("answer").Parse("{{ .Name }} 60 IN A 10.{{ .Group.b }}.{{ .Group.c }}.{{ .Group.d }}"))},
|
||||
additional: []*gotmpl.Template{gotmpl.Must(gotmpl.New("additional").Parse("ns0.example. IN A 203.0.113.8"))},
|
||||
authority: []*gotmpl.Template{gotmpl.Must(gotmpl.New("authority").Parse("example. IN NS ns0.example.com."))},
|
||||
qclass: dns.ClassANY,
|
||||
qtype: dns.TypeANY,
|
||||
fthrough: fall.F{Zones: []string{"."}},
|
||||
zones: []string{"."},
|
||||
}
|
||||
exampleDomainMXTemplate := template{
|
||||
class: dns.ClassINET,
|
||||
qtype: dns.TypeMX,
|
||||
regex: []*regexp.Regexp{regexp.MustCompile("(^|[.])ip-10-(?P<b>[0-9]*)-(?P<c>[0-9]*)-(?P<d>[0-9]*)[.]example[.]$")},
|
||||
answer: []*gotmpl.Template{gotmpl.Must(gotmpl.New("answer").Parse("{{ .Name }} 60 MX 10 {{ .Name }}"))},
|
||||
additional: []*gotmpl.Template{gotmpl.Must(gotmpl.New("additional").Parse("{{ .Name }} 60 IN A 10.{{ .Group.b }}.{{ .Group.c }}.{{ .Group.d }}"))},
|
||||
qclass: dns.ClassANY,
|
||||
qtype: dns.TypeANY,
|
||||
fthrough: fall.F{Zones: []string{"."}},
|
||||
zones: []string{"."},
|
||||
}
|
||||
invalidDomainTemplate := template{
|
||||
class: dns.ClassANY,
|
||||
qtype: dns.TypeANY,
|
||||
regex: []*regexp.Regexp{regexp.MustCompile("[.]invalid[.]$")},
|
||||
rcode: dns.RcodeNameError,
|
||||
answer: []*gotmpl.Template{gotmpl.Must(gotmpl.New("answer").Parse("invalid. 60 {{ .Class }} SOA a.invalid. b.invalid. (1 60 60 60 60)"))},
|
||||
regex: []*regexp.Regexp{regexp.MustCompile("[.]invalid[.]$")},
|
||||
rcode: dns.RcodeNameError,
|
||||
answer: []*gotmpl.Template{gotmpl.Must(gotmpl.New("answer").Parse("invalid. 60 {{ .Class }} SOA a.invalid. b.invalid. (1 60 60 60 60)"))},
|
||||
qclass: dns.ClassANY,
|
||||
qtype: dns.TypeANY,
|
||||
fthrough: fall.F{Zones: []string{"."}},
|
||||
zones: []string{"."},
|
||||
}
|
||||
rcodeServfailTemplate := template{
|
||||
class: dns.ClassANY,
|
||||
qtype: dns.TypeANY,
|
||||
regex: []*regexp.Regexp{regexp.MustCompile(".*")},
|
||||
rcode: dns.RcodeServerFailure,
|
||||
regex: []*regexp.Regexp{regexp.MustCompile(".*")},
|
||||
rcode: dns.RcodeServerFailure,
|
||||
qclass: dns.ClassANY,
|
||||
qtype: dns.TypeANY,
|
||||
fthrough: fall.F{Zones: []string{"."}},
|
||||
zones: []string{"."},
|
||||
}
|
||||
brokenTemplate := template{
|
||||
class: dns.ClassINET,
|
||||
qtype: dns.TypeA,
|
||||
regex: []*regexp.Regexp{regexp.MustCompile("[.]example[.]$")},
|
||||
answer: []*gotmpl.Template{gotmpl.Must(gotmpl.New("answer").Parse("{{ .Name }} 60 IN TXT \"{{ index .Match 2 }}\""))},
|
||||
regex: []*regexp.Regexp{regexp.MustCompile("[.]example[.]$")},
|
||||
answer: []*gotmpl.Template{gotmpl.Must(gotmpl.New("answer").Parse("{{ .Name }} 60 IN TXT \"{{ index .Match 2 }}\""))},
|
||||
qclass: dns.ClassANY,
|
||||
qtype: dns.TypeANY,
|
||||
fthrough: fall.F{Zones: []string{"."}},
|
||||
zones: []string{"."},
|
||||
}
|
||||
nonRRTemplate := template{
|
||||
class: dns.ClassINET,
|
||||
qtype: dns.TypeA,
|
||||
regex: []*regexp.Regexp{regexp.MustCompile("[.]example[.]$")},
|
||||
answer: []*gotmpl.Template{gotmpl.Must(gotmpl.New("answer").Parse("{{ .Name }}"))},
|
||||
regex: []*regexp.Regexp{regexp.MustCompile("[.]example[.]$")},
|
||||
answer: []*gotmpl.Template{gotmpl.Must(gotmpl.New("answer").Parse("{{ .Name }}"))},
|
||||
qclass: dns.ClassANY,
|
||||
qtype: dns.TypeANY,
|
||||
fthrough: fall.F{Zones: []string{"."}},
|
||||
zones: []string{"."},
|
||||
}
|
||||
nonRRAdditionalTemplate := template{
|
||||
class: dns.ClassINET,
|
||||
qtype: dns.TypeA,
|
||||
regex: []*regexp.Regexp{regexp.MustCompile("[.]example[.]$")},
|
||||
additional: []*gotmpl.Template{gotmpl.Must(gotmpl.New("answer").Parse("{{ .Name }}"))},
|
||||
qclass: dns.ClassANY,
|
||||
qtype: dns.TypeANY,
|
||||
fthrough: fall.F{Zones: []string{"."}},
|
||||
zones: []string{"."},
|
||||
}
|
||||
nonRRAuthoritativeTemplate := template{
|
||||
class: dns.ClassINET,
|
||||
qtype: dns.TypeA,
|
||||
regex: []*regexp.Regexp{regexp.MustCompile("[.]example[.]$")},
|
||||
authority: []*gotmpl.Template{gotmpl.Must(gotmpl.New("authority").Parse("{{ .Name }}"))},
|
||||
qclass: dns.ClassANY,
|
||||
qtype: dns.TypeANY,
|
||||
fthrough: fall.F{Zones: []string{"."}},
|
||||
zones: []string{"."},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
tmpl template
|
||||
qname string
|
||||
name string
|
||||
qclass uint16
|
||||
qtype uint16
|
||||
name string
|
||||
expectedCode int
|
||||
expectedErr string
|
||||
verifyResponse func(*dns.Msg) error
|
||||
@@ -88,8 +108,6 @@ func TestHandler(t *testing.T) {
|
||||
{
|
||||
name: "RcodeServFail",
|
||||
tmpl: rcodeServfailTemplate,
|
||||
qclass: dns.ClassANY,
|
||||
qtype: dns.TypeANY,
|
||||
qname: "test.invalid.",
|
||||
expectedCode: dns.RcodeServerFailure,
|
||||
verifyResponse: func(r *dns.Msg) error {
|
||||
@@ -221,22 +239,6 @@ func TestHandler(t *testing.T) {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ExampleDomainMismatchType",
|
||||
tmpl: exampleDomainATemplate,
|
||||
qclass: dns.ClassINET,
|
||||
qtype: dns.TypeMX,
|
||||
qname: "ip-10-95-12-8.example.",
|
||||
expectedCode: rcodeFallthrough,
|
||||
},
|
||||
{
|
||||
name: "ExampleDomainMismatchClass",
|
||||
tmpl: exampleDomainATemplate,
|
||||
qclass: dns.ClassCHAOS,
|
||||
qtype: dns.TypeA,
|
||||
qname: "ip-10-95-12-8.example.",
|
||||
expectedCode: rcodeFallthrough,
|
||||
},
|
||||
{
|
||||
name: "ExampleInvalidNXDOMAIN",
|
||||
tmpl: invalidDomainTemplate,
|
||||
@@ -261,6 +263,7 @@ func TestHandler(t *testing.T) {
|
||||
for _, tr := range tests {
|
||||
handler := Handler{
|
||||
Next: test.NextHandler(rcodeFallthrough, nil),
|
||||
Zones: []string{"."},
|
||||
Templates: []template{tr.tmpl},
|
||||
}
|
||||
req := &dns.Msg{
|
||||
@@ -292,3 +295,149 @@ func TestHandler(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMultiSection verfies that a corefile with mutliple but different template sections works
|
||||
func TestMultiSection(t *testing.T) {
|
||||
rcodeFallthrough := 3841 // reserved for private use, used to indicate a fallthrough
|
||||
ctx := context.TODO()
|
||||
|
||||
multisectionConfig := `
|
||||
# Implicit section (see c.ServerBlockKeys)
|
||||
# test.:8053 {
|
||||
|
||||
# REFUSE IN A for the server zone (test.)
|
||||
template IN A {
|
||||
rcode REFUSED
|
||||
}
|
||||
# Fallthrough everyting IN TXT for test.
|
||||
template IN TXT {
|
||||
match "$^"
|
||||
rcode SERVFAIL
|
||||
fallthrough
|
||||
}
|
||||
# Answer CH TXT *.coredns.invalid. / coredns.invalid.
|
||||
template CH TXT coredns.invalid {
|
||||
answer "{{ .Name }} 60 CH TXT \"test\""
|
||||
}
|
||||
# Anwser example. ip templates and fallthrough otherwise
|
||||
template IN A example {
|
||||
match ^ip-10-(?P<b>[0-9]*)-(?P<c>[0-9]*)-(?P<d>[0-9]*)[.]example[.]$
|
||||
answer "{{ .Name }} 60 IN A 10.{{ .Group.b }}.{{ .Group.c }}.{{ .Group.d }}"
|
||||
fallthrough
|
||||
}
|
||||
# Answer MX record requests for ip templates in example. and never fall through
|
||||
template IN MX example {
|
||||
match ^ip-10-(?P<b>[0-9]*)-(?P<c>[0-9]*)-(?P<d>[0-9]*)[.]example[.]$
|
||||
answer "{{ .Name }} 60 IN MX 10 {{ .Name }}"
|
||||
additional "{{ .Name }} 60 IN A 10.{{ .Group.b }}.{{ .Group.c }}.{{ .Group.d }}"
|
||||
}
|
||||
`
|
||||
c := caddy.NewTestController("dns", multisectionConfig)
|
||||
c.ServerBlockKeys = []string{"test.:8053"}
|
||||
|
||||
handler, err := templateParse(c)
|
||||
if err != nil {
|
||||
t.Fatalf("TestMultiSection could not parse config: %v", err)
|
||||
}
|
||||
|
||||
handler.Next = test.NextHandler(rcodeFallthrough, nil)
|
||||
|
||||
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
||||
|
||||
// Asking for test. IN A -> REFUSED
|
||||
|
||||
req := &dns.Msg{Question: []dns.Question{{Name: "some.test.", Qclass: dns.ClassINET, Qtype: dns.TypeA}}}
|
||||
code, err := handler.ServeDNS(ctx, rec, req)
|
||||
if err != nil {
|
||||
t.Fatalf("TestMultiSection expected no error resolving some.test. A, got: %v", err)
|
||||
}
|
||||
if code != dns.RcodeRefused {
|
||||
t.Fatalf("TestMultiSection expected response code REFUSED got: %v", code)
|
||||
}
|
||||
|
||||
// Asking for test. IN TXT -> fallthrough
|
||||
|
||||
req = &dns.Msg{Question: []dns.Question{{Name: "some.test.", Qclass: dns.ClassINET, Qtype: dns.TypeTXT}}}
|
||||
code, err = handler.ServeDNS(ctx, rec, req)
|
||||
if err != nil {
|
||||
t.Fatalf("TestMultiSection expected no error resolving some.test. TXT, got: %v", err)
|
||||
}
|
||||
if code != rcodeFallthrough {
|
||||
t.Fatalf("TestMultiSection expected response code fallthrough got: %v", code)
|
||||
}
|
||||
|
||||
// Asking for coredns.invalid. CH TXT -> TXT "test"
|
||||
|
||||
req = &dns.Msg{Question: []dns.Question{{Name: "coredns.invalid.", Qclass: dns.ClassCHAOS, Qtype: dns.TypeTXT}}}
|
||||
code, err = handler.ServeDNS(ctx, rec, req)
|
||||
if err != nil {
|
||||
t.Fatalf("TestMultiSection expected no error resolving coredns.invalid. TXT, got: %v", err)
|
||||
}
|
||||
if code != dns.RcodeSuccess {
|
||||
t.Fatalf("TestMultiSection expected success response for coredns.invalid. TXT got: %v", code)
|
||||
}
|
||||
if len(rec.Msg.Answer) != 1 {
|
||||
t.Fatalf("TestMultiSection expected one answer for coredns.invalid. TXT got: %v", rec.Msg.Answer)
|
||||
}
|
||||
if rec.Msg.Answer[0].Header().Rrtype != dns.TypeTXT || rec.Msg.Answer[0].(*dns.TXT).Txt[0] != "test" {
|
||||
t.Fatalf("TestMultiSection a \"test\" answer for coredns.invalid. TXT got: %v", rec.Msg.Answer[0])
|
||||
}
|
||||
|
||||
// Asking for an ip template in example
|
||||
|
||||
req = &dns.Msg{Question: []dns.Question{{Name: "ip-10-11-12-13.example.", Qclass: dns.ClassINET, Qtype: dns.TypeA}}}
|
||||
code, err = handler.ServeDNS(ctx, rec, req)
|
||||
if err != nil {
|
||||
t.Fatalf("TestMultiSection expected no error resolving ip-10-11-12-13.example. IN A, got: %v", err)
|
||||
}
|
||||
if code != dns.RcodeSuccess {
|
||||
t.Fatalf("TestMultiSection expected success response ip-10-11-12-13.example. IN A got: %v, %v", code, dns.RcodeToString[code])
|
||||
}
|
||||
if len(rec.Msg.Answer) != 1 {
|
||||
t.Fatalf("TestMultiSection expected one answer for ip-10-11-12-13.example. IN A got: %v", rec.Msg.Answer)
|
||||
}
|
||||
if rec.Msg.Answer[0].Header().Rrtype != dns.TypeA {
|
||||
t.Fatalf("TestMultiSection an A RR answer for ip-10-11-12-13.example. IN A got: %v", rec.Msg.Answer[0])
|
||||
}
|
||||
|
||||
// Asking for an MX ip template in example
|
||||
|
||||
req = &dns.Msg{Question: []dns.Question{{Name: "ip-10-11-12-13.example.", Qclass: dns.ClassINET, Qtype: dns.TypeMX}}}
|
||||
code, err = handler.ServeDNS(ctx, rec, req)
|
||||
if err != nil {
|
||||
t.Fatalf("TestMultiSection expected no error resolving ip-10-11-12-13.example. IN MX, got: %v", err)
|
||||
}
|
||||
if code != dns.RcodeSuccess {
|
||||
t.Fatalf("TestMultiSection expected success response ip-10-11-12-13.example. IN MX got: %v, %v", code, dns.RcodeToString[code])
|
||||
}
|
||||
if len(rec.Msg.Answer) != 1 {
|
||||
t.Fatalf("TestMultiSection expected one answer for ip-10-11-12-13.example. IN MX got: %v", rec.Msg.Answer)
|
||||
}
|
||||
if rec.Msg.Answer[0].Header().Rrtype != dns.TypeMX {
|
||||
t.Fatalf("TestMultiSection an A RR answer for ip-10-11-12-13.example. IN MX got: %v", rec.Msg.Answer[0])
|
||||
}
|
||||
|
||||
// Test that something.example. A does fall through but something.example. MX does not
|
||||
|
||||
req = &dns.Msg{Question: []dns.Question{{Name: "something.example.", Qclass: dns.ClassINET, Qtype: dns.TypeA}}}
|
||||
code, err = handler.ServeDNS(ctx, rec, req)
|
||||
if err != nil {
|
||||
t.Fatalf("TestMultiSection expected no error resolving something.example. IN A, got: %v", err)
|
||||
}
|
||||
if code != rcodeFallthrough {
|
||||
t.Fatalf("TestMultiSection expected a fall through resolving something.example. IN A, got: %v, %v", code, dns.RcodeToString[code])
|
||||
}
|
||||
|
||||
req = &dns.Msg{Question: []dns.Question{{Name: "something.example.", Qclass: dns.ClassINET, Qtype: dns.TypeMX}}}
|
||||
code, err = handler.ServeDNS(ctx, rec, req)
|
||||
if err != nil {
|
||||
t.Fatalf("TestMultiSection expected no error resolving something.example. IN MX, got: %v", err)
|
||||
}
|
||||
if code == rcodeFallthrough {
|
||||
t.Fatalf("TestMultiSection expected no fall through resolving something.example. IN MX")
|
||||
}
|
||||
if code != dns.RcodeNameError {
|
||||
t.Fatalf("TestMultiSection expected NXDOMAIN resolving something.example. IN MX, got %v, %v", code, dns.RcodeToString[code])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user