| 
									
										
										
										
											2018-01-08 11:52:25 +01:00
										 |  |  | package template
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import (
 | 
					
						
							| 
									
										
										
										
											2018-04-22 08:34:35 +01:00
										 |  |  | 	"context"
 | 
					
						
							| 
									
										
										
										
											2018-01-08 11:52:25 +01:00
										 |  |  | 	"fmt"
 | 
					
						
							|  |  |  | 	"regexp"
 | 
					
						
							|  |  |  | 	"testing"
 | 
					
						
							|  |  |  | 	gotmpl "text/template"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/plugin/pkg/dnstest"
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:30:58 +01:00
										 |  |  | 	"github.com/coredns/coredns/plugin/pkg/fall"
 | 
					
						
							| 
									
										
										
										
											2018-01-09 21:48:32 +00:00
										 |  |  | 	"github.com/coredns/coredns/plugin/test"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/mholt/caddy"
 | 
					
						
							| 
									
										
										
										
											2018-01-08 11:52:25 +01:00
										 |  |  | 	"github.com/miekg/dns"
 | 
					
						
							|  |  |  | )
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestHandler(t *testing.T) {
 | 
					
						
							|  |  |  | 	exampleDomainATemplate := template{
 | 
					
						
							| 
									
										
										
										
											2018-01-09 21:48:32 +00:00
										 |  |  | 		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,
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:29:19 +00:00
										 |  |  | 		fall:   fall.Root,
 | 
					
						
							| 
									
										
										
										
											2018-01-09 21:48:32 +00:00
										 |  |  | 		zones:  []string{"."},
 | 
					
						
							| 
									
										
										
										
											2018-01-08 11:52:25 +01:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 	exampleDomainANSTemplate := template{
 | 
					
						
							|  |  |  | 		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."))},
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:30:58 +01:00
										 |  |  | 		qclass:     dns.ClassANY,
 | 
					
						
							|  |  |  | 		qtype:      dns.TypeANY,
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:29:19 +00:00
										 |  |  | 		fall:       fall.Root,
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:30:58 +01:00
										 |  |  | 		zones:      []string{"."},
 | 
					
						
							| 
									
										
										
										
											2018-01-08 11:52:25 +01:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 	exampleDomainMXTemplate := template{
 | 
					
						
							|  |  |  | 		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 }}"))},
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:30:58 +01:00
										 |  |  | 		qclass:     dns.ClassANY,
 | 
					
						
							|  |  |  | 		qtype:      dns.TypeANY,
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:29:19 +00:00
										 |  |  | 		fall:       fall.Root,
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:30:58 +01:00
										 |  |  | 		zones:      []string{"."},
 | 
					
						
							| 
									
										
										
										
											2018-01-08 11:52:25 +01:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 	invalidDomainTemplate := template{
 | 
					
						
							| 
									
										
										
										
											2018-01-09 21:48:32 +00:00
										 |  |  | 		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,
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:29:19 +00:00
										 |  |  | 		fall:   fall.Root,
 | 
					
						
							| 
									
										
										
										
											2018-01-09 21:48:32 +00:00
										 |  |  | 		zones:  []string{"."},
 | 
					
						
							| 
									
										
										
										
											2018-01-08 11:52:25 +01:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 	rcodeServfailTemplate := template{
 | 
					
						
							| 
									
										
										
										
											2018-01-09 21:48:32 +00:00
										 |  |  | 		regex:  []*regexp.Regexp{regexp.MustCompile(".*")},
 | 
					
						
							|  |  |  | 		rcode:  dns.RcodeServerFailure,
 | 
					
						
							|  |  |  | 		qclass: dns.ClassANY,
 | 
					
						
							|  |  |  | 		qtype:  dns.TypeANY,
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:29:19 +00:00
										 |  |  | 		fall:   fall.Root,
 | 
					
						
							| 
									
										
										
										
											2018-01-09 21:48:32 +00:00
										 |  |  | 		zones:  []string{"."},
 | 
					
						
							| 
									
										
										
										
											2018-01-08 11:52:25 +01:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 	brokenTemplate := template{
 | 
					
						
							| 
									
										
										
										
											2018-01-09 21:48:32 +00:00
										 |  |  | 		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,
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:29:19 +00:00
										 |  |  | 		fall:   fall.Root,
 | 
					
						
							| 
									
										
										
										
											2018-01-09 21:48:32 +00:00
										 |  |  | 		zones:  []string{"."},
 | 
					
						
							| 
									
										
										
										
											2018-01-08 11:52:25 +01:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 	nonRRTemplate := template{
 | 
					
						
							| 
									
										
										
										
											2018-01-09 21:48:32 +00:00
										 |  |  | 		regex:  []*regexp.Regexp{regexp.MustCompile("[.]example[.]$")},
 | 
					
						
							|  |  |  | 		answer: []*gotmpl.Template{gotmpl.Must(gotmpl.New("answer").Parse("{{ .Name }}"))},
 | 
					
						
							|  |  |  | 		qclass: dns.ClassANY,
 | 
					
						
							|  |  |  | 		qtype:  dns.TypeANY,
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:29:19 +00:00
										 |  |  | 		fall:   fall.Root,
 | 
					
						
							| 
									
										
										
										
											2018-01-09 21:48:32 +00:00
										 |  |  | 		zones:  []string{"."},
 | 
					
						
							| 
									
										
										
										
											2018-01-08 11:52:25 +01:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 	nonRRAdditionalTemplate := template{
 | 
					
						
							|  |  |  | 		regex:      []*regexp.Regexp{regexp.MustCompile("[.]example[.]$")},
 | 
					
						
							|  |  |  | 		additional: []*gotmpl.Template{gotmpl.Must(gotmpl.New("answer").Parse("{{ .Name }}"))},
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:30:58 +01:00
										 |  |  | 		qclass:     dns.ClassANY,
 | 
					
						
							|  |  |  | 		qtype:      dns.TypeANY,
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:29:19 +00:00
										 |  |  | 		fall:       fall.Root,
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:30:58 +01:00
										 |  |  | 		zones:      []string{"."},
 | 
					
						
							| 
									
										
										
										
											2018-01-08 11:52:25 +01:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 	nonRRAuthoritativeTemplate := template{
 | 
					
						
							|  |  |  | 		regex:     []*regexp.Regexp{regexp.MustCompile("[.]example[.]$")},
 | 
					
						
							|  |  |  | 		authority: []*gotmpl.Template{gotmpl.Must(gotmpl.New("authority").Parse("{{ .Name }}"))},
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:30:58 +01:00
										 |  |  | 		qclass:    dns.ClassANY,
 | 
					
						
							|  |  |  | 		qtype:     dns.TypeANY,
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:29:19 +00:00
										 |  |  | 		fall:      fall.Root,
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:30:58 +01:00
										 |  |  | 		zones:     []string{"."},
 | 
					
						
							| 
									
										
										
										
											2018-01-08 11:52:25 +01:00
										 |  |  | 	}
 | 
					
						
							| 
									
										
										
										
											2018-06-21 14:38:29 -04:00
										 |  |  | 	cnameTemplate := template{
 | 
					
						
							|  |  |  | 		regex:  []*regexp.Regexp{regexp.MustCompile("example[.]net[.]")},
 | 
					
						
							|  |  |  | 		answer: []*gotmpl.Template{gotmpl.Must(gotmpl.New("answer").Parse("example.net 60 IN CNAME target.example.com"))},
 | 
					
						
							|  |  |  | 		qclass: dns.ClassANY,
 | 
					
						
							|  |  |  | 		qtype:  dns.TypeANY,
 | 
					
						
							|  |  |  | 		fall:   fall.Root,
 | 
					
						
							|  |  |  | 		zones:  []string{"."},
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							| 
									
										
										
										
											2018-01-08 11:52:25 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	tests := []struct {
 | 
					
						
							|  |  |  | 		tmpl           template
 | 
					
						
							|  |  |  | 		qname          string
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:30:58 +01:00
										 |  |  | 		name           string
 | 
					
						
							| 
									
										
										
										
											2018-01-08 11:52:25 +01:00
										 |  |  | 		qclass         uint16
 | 
					
						
							|  |  |  | 		qtype          uint16
 | 
					
						
							|  |  |  | 		expectedCode   int
 | 
					
						
							|  |  |  | 		expectedErr    string
 | 
					
						
							|  |  |  | 		verifyResponse func(*dns.Msg) error
 | 
					
						
							|  |  |  | 	}{
 | 
					
						
							|  |  |  | 		{
 | 
					
						
							|  |  |  | 			name:         "RcodeServFail",
 | 
					
						
							|  |  |  | 			tmpl:         rcodeServfailTemplate,
 | 
					
						
							|  |  |  | 			qname:        "test.invalid.",
 | 
					
						
							|  |  |  | 			expectedCode: dns.RcodeServerFailure,
 | 
					
						
							|  |  |  | 			verifyResponse: func(r *dns.Msg) error {
 | 
					
						
							|  |  |  | 				return nil
 | 
					
						
							|  |  |  | 			},
 | 
					
						
							|  |  |  | 		},
 | 
					
						
							|  |  |  | 		{
 | 
					
						
							|  |  |  | 			name:         "ExampleDomainNameMismatch",
 | 
					
						
							|  |  |  | 			tmpl:         exampleDomainATemplate,
 | 
					
						
							|  |  |  | 			qclass:       dns.ClassINET,
 | 
					
						
							|  |  |  | 			qtype:        dns.TypeA,
 | 
					
						
							|  |  |  | 			qname:        "test.invalid.",
 | 
					
						
							|  |  |  | 			expectedCode: rcodeFallthrough,
 | 
					
						
							|  |  |  | 		},
 | 
					
						
							|  |  |  | 		{
 | 
					
						
							|  |  |  | 			name:         "BrokenTemplate",
 | 
					
						
							|  |  |  | 			tmpl:         brokenTemplate,
 | 
					
						
							|  |  |  | 			qclass:       dns.ClassINET,
 | 
					
						
							|  |  |  | 			qtype:        dns.TypeANY,
 | 
					
						
							|  |  |  | 			qname:        "test.example.",
 | 
					
						
							|  |  |  | 			expectedCode: dns.RcodeServerFailure,
 | 
					
						
							|  |  |  | 			expectedErr:  `template: answer:1:26: executing "answer" at <index .Match 2>: error calling index: index out of range: 2`,
 | 
					
						
							|  |  |  | 			verifyResponse: func(r *dns.Msg) error {
 | 
					
						
							|  |  |  | 				return nil
 | 
					
						
							|  |  |  | 			},
 | 
					
						
							|  |  |  | 		},
 | 
					
						
							|  |  |  | 		{
 | 
					
						
							|  |  |  | 			name:         "NonRRTemplate",
 | 
					
						
							|  |  |  | 			tmpl:         nonRRTemplate,
 | 
					
						
							|  |  |  | 			qclass:       dns.ClassINET,
 | 
					
						
							|  |  |  | 			qtype:        dns.TypeANY,
 | 
					
						
							|  |  |  | 			qname:        "test.example.",
 | 
					
						
							|  |  |  | 			expectedCode: dns.RcodeServerFailure,
 | 
					
						
							|  |  |  | 			expectedErr:  `dns: not a TTL: "test.example." at line: 1:13`,
 | 
					
						
							|  |  |  | 			verifyResponse: func(r *dns.Msg) error {
 | 
					
						
							|  |  |  | 				return nil
 | 
					
						
							|  |  |  | 			},
 | 
					
						
							|  |  |  | 		},
 | 
					
						
							|  |  |  | 		{
 | 
					
						
							|  |  |  | 			name:         "NonRRAdditionalTemplate",
 | 
					
						
							|  |  |  | 			tmpl:         nonRRAdditionalTemplate,
 | 
					
						
							|  |  |  | 			qclass:       dns.ClassINET,
 | 
					
						
							|  |  |  | 			qtype:        dns.TypeANY,
 | 
					
						
							|  |  |  | 			qname:        "test.example.",
 | 
					
						
							|  |  |  | 			expectedCode: dns.RcodeServerFailure,
 | 
					
						
							|  |  |  | 			expectedErr:  `dns: not a TTL: "test.example." at line: 1:13`,
 | 
					
						
							|  |  |  | 			verifyResponse: func(r *dns.Msg) error {
 | 
					
						
							|  |  |  | 				return nil
 | 
					
						
							|  |  |  | 			},
 | 
					
						
							|  |  |  | 		},
 | 
					
						
							|  |  |  | 		{
 | 
					
						
							|  |  |  | 			name:         "NonRRAuthorityTemplate",
 | 
					
						
							|  |  |  | 			tmpl:         nonRRAuthoritativeTemplate,
 | 
					
						
							|  |  |  | 			qclass:       dns.ClassINET,
 | 
					
						
							|  |  |  | 			qtype:        dns.TypeANY,
 | 
					
						
							|  |  |  | 			qname:        "test.example.",
 | 
					
						
							|  |  |  | 			expectedCode: dns.RcodeServerFailure,
 | 
					
						
							|  |  |  | 			expectedErr:  `dns: not a TTL: "test.example." at line: 1:13`,
 | 
					
						
							|  |  |  | 			verifyResponse: func(r *dns.Msg) error {
 | 
					
						
							|  |  |  | 				return nil
 | 
					
						
							|  |  |  | 			},
 | 
					
						
							|  |  |  | 		},
 | 
					
						
							|  |  |  | 		{
 | 
					
						
							|  |  |  | 			name:   "ExampleDomainMatch",
 | 
					
						
							|  |  |  | 			tmpl:   exampleDomainATemplate,
 | 
					
						
							|  |  |  | 			qclass: dns.ClassINET,
 | 
					
						
							|  |  |  | 			qtype:  dns.TypeA,
 | 
					
						
							|  |  |  | 			qname:  "ip-10-95-12-8.example.",
 | 
					
						
							|  |  |  | 			verifyResponse: func(r *dns.Msg) error {
 | 
					
						
							|  |  |  | 				if len(r.Answer) != 1 {
 | 
					
						
							|  |  |  | 					return fmt.Errorf("expected 1 answer, got %v", len(r.Answer))
 | 
					
						
							|  |  |  | 				}
 | 
					
						
							|  |  |  | 				if r.Answer[0].Header().Rrtype != dns.TypeA {
 | 
					
						
							| 
									
										
										
										
											2018-08-14 17:55:55 +02:00
										 |  |  | 					return fmt.Errorf("expected an A record answer, got %v", dns.TypeToString[r.Answer[0].Header().Rrtype])
 | 
					
						
							| 
									
										
										
										
											2018-01-08 11:52:25 +01:00
										 |  |  | 				}
 | 
					
						
							|  |  |  | 				if r.Answer[0].(*dns.A).A.String() != "10.95.12.8" {
 | 
					
						
							|  |  |  | 					return fmt.Errorf("expected an A record for 10.95.12.8, got %v", r.Answer[0].String())
 | 
					
						
							|  |  |  | 				}
 | 
					
						
							|  |  |  | 				return nil
 | 
					
						
							|  |  |  | 			},
 | 
					
						
							|  |  |  | 		},
 | 
					
						
							|  |  |  | 		{
 | 
					
						
							|  |  |  | 			name:   "ExampleDomainMXMatch",
 | 
					
						
							|  |  |  | 			tmpl:   exampleDomainMXTemplate,
 | 
					
						
							|  |  |  | 			qclass: dns.ClassINET,
 | 
					
						
							|  |  |  | 			qtype:  dns.TypeMX,
 | 
					
						
							|  |  |  | 			qname:  "ip-10-95-12-8.example.",
 | 
					
						
							|  |  |  | 			verifyResponse: func(r *dns.Msg) error {
 | 
					
						
							|  |  |  | 				if len(r.Answer) != 1 {
 | 
					
						
							|  |  |  | 					return fmt.Errorf("expected 1 answer, got %v", len(r.Answer))
 | 
					
						
							|  |  |  | 				}
 | 
					
						
							|  |  |  | 				if r.Answer[0].Header().Rrtype != dns.TypeMX {
 | 
					
						
							| 
									
										
										
										
											2018-08-14 17:55:55 +02:00
										 |  |  | 					return fmt.Errorf("expected an A record answer, got %v", dns.TypeToString[r.Answer[0].Header().Rrtype])
 | 
					
						
							| 
									
										
										
										
											2018-01-08 11:52:25 +01:00
										 |  |  | 				}
 | 
					
						
							|  |  |  | 				if len(r.Extra) != 1 {
 | 
					
						
							|  |  |  | 					return fmt.Errorf("expected 1 extra record, got %v", len(r.Extra))
 | 
					
						
							|  |  |  | 				}
 | 
					
						
							|  |  |  | 				if r.Extra[0].Header().Rrtype != dns.TypeA {
 | 
					
						
							|  |  |  | 					return fmt.Errorf("expected an additional A record, got %v", dns.TypeToString[r.Extra[0].Header().Rrtype])
 | 
					
						
							|  |  |  | 				}
 | 
					
						
							|  |  |  | 				return nil
 | 
					
						
							|  |  |  | 			},
 | 
					
						
							|  |  |  | 		},
 | 
					
						
							|  |  |  | 		{
 | 
					
						
							|  |  |  | 			name:   "ExampleDomainANSMatch",
 | 
					
						
							|  |  |  | 			tmpl:   exampleDomainANSTemplate,
 | 
					
						
							|  |  |  | 			qclass: dns.ClassINET,
 | 
					
						
							|  |  |  | 			qtype:  dns.TypeA,
 | 
					
						
							|  |  |  | 			qname:  "ip-10-95-12-8.example.",
 | 
					
						
							|  |  |  | 			verifyResponse: func(r *dns.Msg) error {
 | 
					
						
							|  |  |  | 				if len(r.Answer) != 1 {
 | 
					
						
							|  |  |  | 					return fmt.Errorf("expected 1 answer, got %v", len(r.Answer))
 | 
					
						
							|  |  |  | 				}
 | 
					
						
							|  |  |  | 				if r.Answer[0].Header().Rrtype != dns.TypeA {
 | 
					
						
							| 
									
										
										
										
											2018-08-14 17:55:55 +02:00
										 |  |  | 					return fmt.Errorf("expected an A record answer, got %v", dns.TypeToString[r.Answer[0].Header().Rrtype])
 | 
					
						
							| 
									
										
										
										
											2018-01-08 11:52:25 +01:00
										 |  |  | 				}
 | 
					
						
							|  |  |  | 				if len(r.Extra) != 1 {
 | 
					
						
							|  |  |  | 					return fmt.Errorf("expected 1 extra record, got %v", len(r.Extra))
 | 
					
						
							|  |  |  | 				}
 | 
					
						
							|  |  |  | 				if r.Extra[0].Header().Rrtype != dns.TypeA {
 | 
					
						
							|  |  |  | 					return fmt.Errorf("expected an additional A record, got %v", dns.TypeToString[r.Extra[0].Header().Rrtype])
 | 
					
						
							|  |  |  | 				}
 | 
					
						
							|  |  |  | 				if len(r.Ns) != 1 {
 | 
					
						
							|  |  |  | 					return fmt.Errorf("expected 1 authoritative record, got %v", len(r.Extra))
 | 
					
						
							|  |  |  | 				}
 | 
					
						
							|  |  |  | 				if r.Ns[0].Header().Rrtype != dns.TypeNS {
 | 
					
						
							|  |  |  | 					return fmt.Errorf("expected an authoritative NS record, got %v", dns.TypeToString[r.Extra[0].Header().Rrtype])
 | 
					
						
							|  |  |  | 				}
 | 
					
						
							|  |  |  | 				return nil
 | 
					
						
							|  |  |  | 			},
 | 
					
						
							|  |  |  | 		},
 | 
					
						
							|  |  |  | 		{
 | 
					
						
							|  |  |  | 			name:         "ExampleInvalidNXDOMAIN",
 | 
					
						
							|  |  |  | 			tmpl:         invalidDomainTemplate,
 | 
					
						
							|  |  |  | 			qclass:       dns.ClassINET,
 | 
					
						
							|  |  |  | 			qtype:        dns.TypeMX,
 | 
					
						
							|  |  |  | 			qname:        "test.invalid.",
 | 
					
						
							|  |  |  | 			expectedCode: dns.RcodeNameError,
 | 
					
						
							|  |  |  | 			verifyResponse: func(r *dns.Msg) error {
 | 
					
						
							|  |  |  | 				if len(r.Answer) != 1 {
 | 
					
						
							|  |  |  | 					return fmt.Errorf("expected 1 answer, got %v", len(r.Answer))
 | 
					
						
							|  |  |  | 				}
 | 
					
						
							|  |  |  | 				if r.Answer[0].Header().Rrtype != dns.TypeSOA {
 | 
					
						
							| 
									
										
										
										
											2018-08-14 17:55:55 +02:00
										 |  |  | 					return fmt.Errorf("expected an SOA record answer, got %v", dns.TypeToString[r.Answer[0].Header().Rrtype])
 | 
					
						
							| 
									
										
										
										
											2018-01-08 11:52:25 +01:00
										 |  |  | 				}
 | 
					
						
							|  |  |  | 				return nil
 | 
					
						
							|  |  |  | 			},
 | 
					
						
							|  |  |  | 		},
 | 
					
						
							| 
									
										
										
										
											2018-06-21 14:38:29 -04:00
										 |  |  | 		{
 | 
					
						
							|  |  |  | 			name:         "CNAMEWithoutUpstream",
 | 
					
						
							|  |  |  | 			tmpl:         cnameTemplate,
 | 
					
						
							|  |  |  | 			qclass:       dns.ClassINET,
 | 
					
						
							|  |  |  | 			qtype:        dns.TypeA,
 | 
					
						
							|  |  |  | 			qname:        "example.net.",
 | 
					
						
							|  |  |  | 			expectedCode: dns.RcodeSuccess,
 | 
					
						
							|  |  |  | 			verifyResponse: func(r *dns.Msg) error {
 | 
					
						
							|  |  |  | 				if len(r.Answer) != 1 {
 | 
					
						
							|  |  |  | 					return fmt.Errorf("expected 1 answer, got %v", len(r.Answer))
 | 
					
						
							|  |  |  | 				}
 | 
					
						
							|  |  |  | 				return nil
 | 
					
						
							|  |  |  | 			},
 | 
					
						
							|  |  |  | 		},
 | 
					
						
							| 
									
										
										
										
											2018-01-08 11:52:25 +01:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ctx := context.TODO()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, tr := range tests {
 | 
					
						
							|  |  |  | 		handler := Handler{
 | 
					
						
							|  |  |  | 			Next:      test.NextHandler(rcodeFallthrough, nil),
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:30:58 +01:00
										 |  |  | 			Zones:     []string{"."},
 | 
					
						
							| 
									
										
										
										
											2018-01-08 11:52:25 +01:00
										 |  |  | 			Templates: []template{tr.tmpl},
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		req := &dns.Msg{
 | 
					
						
							|  |  |  | 			Question: []dns.Question{{
 | 
					
						
							|  |  |  | 				Name:   tr.qname,
 | 
					
						
							|  |  |  | 				Qclass: tr.qclass,
 | 
					
						
							|  |  |  | 				Qtype:  tr.qtype,
 | 
					
						
							|  |  |  | 			}},
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		rec := dnstest.NewRecorder(&test.ResponseWriter{})
 | 
					
						
							|  |  |  | 		code, err := handler.ServeDNS(ctx, rec, req)
 | 
					
						
							|  |  |  | 		if err == nil && tr.expectedErr != "" {
 | 
					
						
							|  |  |  | 			t.Errorf("Test %v expected error: %v, got nothing", tr.name, tr.expectedErr)
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		if err != nil && tr.expectedErr == "" {
 | 
					
						
							|  |  |  | 			t.Errorf("Test %v expected no error got: %v", tr.name, err)
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		if err != nil && tr.expectedErr != "" && err.Error() != tr.expectedErr {
 | 
					
						
							|  |  |  | 			t.Errorf("Test %v expected error: %v, got: %v", tr.name, tr.expectedErr, err)
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		if code != tr.expectedCode {
 | 
					
						
							|  |  |  | 			t.Errorf("Test %v expected response code %v, got %v", tr.name, tr.expectedCode, code)
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		if err == nil && code != rcodeFallthrough {
 | 
					
						
							|  |  |  | 			// only verify if we got no error and expected no error
 | 
					
						
							|  |  |  | 			if err := tr.verifyResponse(rec.Msg); err != nil {
 | 
					
						
							|  |  |  | 				t.Errorf("Test %v could not verify the response: %v", tr.name, err)
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | }
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:30:58 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-15 18:27:47 -08:00
										 |  |  | // TestMultiSection verfies that a corefile with multiple but different template sections works
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:30:58 +01:00
										 |  |  | func TestMultiSection(t *testing.T) {
 | 
					
						
							|  |  |  | 	ctx := context.TODO()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	multisectionConfig := `
 | 
					
						
							|  |  |  | 		# Implicit section (see c.ServerBlockKeys)
 | 
					
						
							|  |  |  | 		# test.:8053 {
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	  # REFUSE IN A for the server zone (test.)
 | 
					
						
							|  |  |  | 		template IN A {
 | 
					
						
							|  |  |  | 			rcode REFUSED
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							| 
									
										
										
										
											2018-01-15 18:27:47 -08:00
										 |  |  | 		# Fallthrough everything IN TXT for test.
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:30:58 +01:00
										 |  |  | 		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\""
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							| 
									
										
										
										
											2018-08-14 17:55:55 +02:00
										 |  |  | 		# Answer example. ip templates and fallthrough otherwise
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:30:58 +01:00
										 |  |  | 		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])
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | }
 | 
					
						
							| 
									
										
										
										
											2018-01-09 22:29:19 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | const rcodeFallthrough = 3841 // reserved for private use, used to indicate a fallthrough
 |