func BenchmarkLookup(b *testing.B) { zone, err := Parse(strings.NewReader(dbMiekNL), testzone, "stdin") if err != nil { return } fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone: zone}, Names: []string{testzone}}} ctx := context.TODO() rec := middleware.NewResponseRecorder(&test.ResponseWriter{}) tc := test.Case{ Qname: "www.miek.nl.", Qtype: dns.TypeA, Answer: []dns.RR{ test.CNAME("www.miek.nl. 1800 IN CNAME a.miek.nl."), test.A("a.miek.nl. 1800 IN A 139.162.196.78"), }, } m := tc.Msg() b.ResetTimer() for i := 0; i < b.N; i++ { fm.ServeDNS(ctx, rec, m) } }
func TestDebugLookupFalse(t *testing.T) { for _, serv := range servicesDebug { set(t, etc, serv.Key, 0, serv) defer delete(t, etc, serv.Key) } for _, tc := range dnsTestCasesDebugFalse { m := tc.Msg() rec := middleware.NewResponseRecorder(&test.ResponseWriter{}) _, err := etc.ServeDNS(ctxt, rec, m) if err != nil { t.Errorf("expected no error, got %v\n", err) continue } resp := rec.Msg() sort.Sort(test.RRSet(resp.Answer)) sort.Sort(test.RRSet(resp.Ns)) sort.Sort(test.RRSet(resp.Extra)) if !test.Header(t, tc, resp) { t.Logf("%v\n", resp) continue } if !test.Section(t, tc, test.Answer, resp.Answer) { t.Logf("%v\n", resp) } if !test.Section(t, tc, test.Ns, resp.Ns) { t.Logf("%v\n", resp) } if !test.Section(t, tc, test.Extra, resp.Extra) { t.Logf("%v\n", resp) } } }
func TestLookup(t *testing.T) { for _, serv := range services { set(t, etc, serv.Key, 0, serv) defer delete(t, etc, serv.Key) } for _, tc := range dnsTestCases { m := tc.Msg() rec := middleware.NewResponseRecorder(&test.ResponseWriter{}) _, err := etc.ServeDNS(ctxt, rec, m) if err != nil { t.Errorf("expected no error, got: %v for %s %s\n", err, m.Question[0].Name, dns.Type(m.Question[0].Qtype)) return } resp := rec.Msg() sort.Sort(test.RRSet(resp.Answer)) sort.Sort(test.RRSet(resp.Ns)) sort.Sort(test.RRSet(resp.Extra)) if !test.Header(t, tc, resp) { t.Logf("%v\n", resp) continue } if !test.Section(t, tc, test.Answer, resp.Answer) { t.Logf("%v\n", resp) } if !test.Section(t, tc, test.Ns, resp.Ns) { t.Logf("%v\n", resp) } if !test.Section(t, tc, test.Extra, resp.Extra) { t.Logf("%v\n", resp) } } }
func BenchmarkLookupDNSSEC(b *testing.B) { zone, err := Parse(strings.NewReader(dbMiekNL_signed), testzone, "stdin") if err != nil { return } fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone: zone}, Names: []string{testzone}}} ctx := context.TODO() rec := middleware.NewResponseRecorder(&test.ResponseWriter{}) tc := test.Case{ Qname: "b.miek.nl.", Qtype: dns.TypeA, Do: true, Rcode: dns.RcodeNameError, Ns: []dns.RR{ test.NSEC("archive.miek.nl. 14400 IN NSEC go.dns.miek.nl. CNAME RRSIG NSEC"), test.RRSIG("archive.miek.nl. 14400 IN RRSIG NSEC 8 3 14400 20160426031301 20160327031301 12051 miek.nl. jEpx8lcp4do5fWXg="), test.NSEC("miek.nl. 14400 IN NSEC a.miek.nl. A NS SOA MX AAAA RRSIG NSEC DNSKEY"), test.RRSIG("miek.nl. 14400 IN RRSIG NSEC 8 2 14400 20160426031301 20160327031301 12051 miek.nl. mFfc3r/9PSC1H6oSpdC"), test.RRSIG("miek.nl. 1800 IN RRSIG SOA 8 2 1800 20160426031301 20160327031301 12051 miek.nl. FIrzy07acBbtyQczy1dc="), test.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"), }, } m := tc.Msg() b.ResetTimer() for i := 0; i < b.N; i++ { fm.ServeDNS(ctx, rec, m) } }
func TestLoggedStatus(t *testing.T) { var f bytes.Buffer var next erroringMiddleware rule := Rule{ NameScope: ".", Format: DefaultLogFormat, Log: log.New(&f, "", 0), } logger := Logger{ Rules: []Rule{rule}, Next: next, } ctx := context.TODO() r := new(dns.Msg) r.SetQuestion("example.org.", dns.TypeA) rec := middleware.NewResponseRecorder(&test.ResponseWriter{}) rcode, _ := logger.ServeDNS(ctx, rec, r) if rcode != 0 { t.Error("Expected rcode to be 0 - was", rcode) } logged := f.String() if !strings.Contains(logged, "A IN example.org. udp false 512") { t.Error("Expected it to be logged. Logged string -", logged) } }
func (l Logger) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { state := middleware.State{W: w, Req: r} for _, rule := range l.Rules { if middleware.Name(rule.NameScope).Matches(state.Name()) { responseRecorder := middleware.NewResponseRecorder(w) rcode, err := l.Next.ServeDNS(ctx, responseRecorder, r) if rcode > 0 { // There was an error up the chain, but no response has been written yet. // The error must be handled here so the log entry will record the response size. if l.ErrorFunc != nil { l.ErrorFunc(responseRecorder, r, rcode) } else { rc := middleware.RcodeToString(rcode) answer := new(dns.Msg) answer.SetRcode(r, rcode) state.SizeAndDo(answer) metrics.Report(state, metrics.Dropped, rc, answer.Len(), time.Now()) w.WriteMsg(answer) } rcode = 0 } rep := middleware.NewReplacer(r, responseRecorder, CommonLogEmptyValue) rule.Log.Println(rep.Replace(rule.Format)) return rcode, err } } return l.Next.ServeDNS(ctx, w, r) }
func TestStubCycle(t *testing.T) { // reuse servics from stub_test.go for _, serv := range servicesStub { set(t, etc, serv.Key, 0, serv) defer delete(t, etc, serv.Key) } etc.updateStubZones() for _, tc := range dnsTestCasesCycleStub { m := tc.Msg() if tc.Do { // add our wacky edns fluff m.Extra[0] = ednsStub } rec := middleware.NewResponseRecorder(&test.ResponseWriter{}) _, err := etc.ServeDNS(ctxt, rec, m) if err == nil { t.Errorf("expected error, got none") continue } // err should have been, set msg is nil, CoreDNS middlware handling takes // care of proper error to client. } }
func TestLookupNil(t *testing.T) { fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone: nil}, Names: []string{testzone}}} ctx := context.TODO() m := dnsTestCases[0].Msg() rec := middleware.NewResponseRecorder(&test.ResponseWriter{}) fm.ServeDNS(ctx, rec, m) }
func TestErrors(t *testing.T) { buf := bytes.Buffer{} em := ErrorHandler{Log: log.New(&buf, "", 0)} testErr := errors.New("test error") tests := []struct { next middleware.Handler expectedCode int expectedLog string expectedErr error }{ { next: genErrorHandler(dns.RcodeSuccess, nil), expectedCode: dns.RcodeSuccess, expectedLog: "", expectedErr: nil, }, { next: genErrorHandler(dns.RcodeNotAuth, testErr), expectedCode: dns.RcodeNotAuth, expectedLog: fmt.Sprintf("[ERROR %d %s] %v\n", dns.RcodeNotAuth, "example.org. A", testErr), expectedErr: testErr, }, } ctx := context.TODO() req := new(dns.Msg) req.SetQuestion("example.org.", dns.TypeA) for i, tc := range tests { em.Next = tc.next buf.Reset() rec := middleware.NewResponseRecorder(&test.ResponseWriter{}) code, err := em.ServeDNS(ctx, rec, req) if err != tc.expectedErr { t.Errorf("Test %d: Expected error %v, but got %v", i, tc.expectedErr, err) } if code != tc.expectedCode { t.Errorf("Test %d: Expected status code %d, but got %d", i, tc.expectedCode, code) } if log := buf.String(); !strings.Contains(log, tc.expectedLog) { t.Errorf("Test %d: Expected log %q, but got %q", i, tc.expectedLog, log) } } }
func (m Metrics) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { state := middleware.State{W: w, Req: r} qname := state.QName() zone := middleware.Zones(m.ZoneNames).Matches(qname) if zone == "" { zone = "." } // Record response to get status code and size of the reply. rw := middleware.NewResponseRecorder(w) status, err := m.Next.ServeDNS(ctx, rw, r) Report(state, zone, rw.Rcode(), rw.Size(), rw.Start()) return status, err }
func TestStubLookup(t *testing.T) { for _, serv := range servicesStub { set(t, etc, serv.Key, 0, serv) defer delete(t, etc, serv.Key) } etc.updateStubZones() for _, tc := range dnsTestCasesStub { m := tc.Msg() rec := middleware.NewResponseRecorder(&test.ResponseWriter{}) _, err := etc.ServeDNS(ctxt, rec, m) if err != nil { if tc.Rcode != dns.RcodeServerFailure { t.Errorf("expected no error, got %v\n", err) } // This is OK, we expect this backend to *not* work. continue } resp := rec.Msg() if resp == nil { // etcd not running? continue } sort.Sort(test.RRSet(resp.Answer)) sort.Sort(test.RRSet(resp.Ns)) sort.Sort(test.RRSet(resp.Extra)) if !test.Header(t, tc, resp) { t.Logf("%v\n", resp) continue } if !test.Section(t, tc, test.Answer, resp.Answer) { t.Logf("%v\n", resp) } if !test.Section(t, tc, test.Ns, resp.Ns) { t.Logf("%v\n", resp) } if !test.Section(t, tc, test.Extra, resp.Extra) { t.Logf("%v\n", resp) } } }
func TestLookupZone(t *testing.T) { zone, err := file.Parse(strings.NewReader(dbMiekNL), "miek.nl.", "stdin") if err != nil { return } fm := file.File{Next: test.ErrorHandler(), Zones: file.Zones{Z: map[string]*file.Zone{"miek.nl.": zone}, Names: []string{"miek.nl."}}} dnskey, rm1, rm2 := newKey(t) defer rm1() defer rm2() dh := NewDnssec([]string{"miek.nl."}, []*DNSKEY{dnskey}, fm) ctx := context.TODO() for _, tc := range dnsTestCases { m := tc.Msg() rec := middleware.NewResponseRecorder(&test.ResponseWriter{}) _, err := dh.ServeDNS(ctx, rec, m) if err != nil { t.Errorf("expected no error, got %v\n", err) return } resp := rec.Msg() sort.Sort(test.RRSet(resp.Answer)) sort.Sort(test.RRSet(resp.Ns)) sort.Sort(test.RRSet(resp.Extra)) if !test.Header(t, tc, resp) { t.Logf("%v\n", resp) continue } if !test.Section(t, tc, test.Answer, resp.Answer) { t.Logf("%v\n", resp) } if !test.Section(t, tc, test.Ns, resp.Ns) { t.Logf("%v\n", resp) } if !test.Section(t, tc, test.Extra, resp.Extra) { t.Logf("%v\n", resp) } } }
func TestLookupENT(t *testing.T) { zone, err := Parse(strings.NewReader(dbMiekENTNL), testzone, "stdin") if err != nil { t.Fatalf("expect no error when reading zone, got %q", err) } fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone: zone}, Names: []string{testzone}}} ctx := context.TODO() for _, tc := range entTestCases { m := tc.Msg() rec := middleware.NewResponseRecorder(&test.ResponseWriter{}) _, err := fm.ServeDNS(ctx, rec, m) if err != nil { t.Errorf("expected no error, got %v\n", err) return } resp := rec.Msg() sort.Sort(test.RRSet(resp.Answer)) sort.Sort(test.RRSet(resp.Ns)) sort.Sort(test.RRSet(resp.Extra)) if !test.Header(t, tc, resp) { t.Logf("%v\n", resp) continue } if !test.Section(t, tc, test.Answer, resp.Answer) { t.Logf("%v\n", resp) } if !test.Section(t, tc, test.Ns, resp.Ns) { t.Logf("%v\n", resp) } if !test.Section(t, tc, test.Extra, resp.Extra) { t.Logf("%v\n", resp) } } }
func TestMultiLookup(t *testing.T) { etcMulti := etc etcMulti.Zones = []string{"skydns.test.", "miek.nl."} etcMulti.Next = test.ErrorHandler() for _, serv := range servicesMulti { set(t, etcMulti, serv.Key, 0, serv) defer delete(t, etcMulti, serv.Key) } for _, tc := range dnsTestCasesMulti { m := tc.Msg() rec := middleware.NewResponseRecorder(&test.ResponseWriter{}) _, err := etcMulti.ServeDNS(ctxt, rec, m) if err != nil { t.Errorf("expected no error, got %v\n", err) return } resp := rec.Msg() sort.Sort(test.RRSet(resp.Answer)) sort.Sort(test.RRSet(resp.Ns)) sort.Sort(test.RRSet(resp.Extra)) if !test.Header(t, tc, resp) { t.Logf("%v\n", resp) continue } if !test.Section(t, tc, test.Answer, resp.Answer) { t.Logf("%v\n", resp) } if !test.Section(t, tc, test.Ns, resp.Ns) { t.Logf("%v\n", resp) } if !test.Section(t, tc, test.Extra, resp.Extra) { t.Logf("%v\n", resp) } } }
func TestVisibleErrorWithPanic(t *testing.T) { const panicMsg = "I'm a panic" eh := ErrorHandler{ Debug: true, Next: middleware.HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { panic(panicMsg) }), } ctx := context.TODO() req := new(dns.Msg) req.SetQuestion("example.org.", dns.TypeA) rec := middleware.NewResponseRecorder(&test.ResponseWriter{}) code, err := eh.ServeDNS(ctx, rec, req) if code != 0 { t.Errorf("Expected error handler to return 0 (it should write to response), got status %d", code) } if err != nil { t.Errorf("Expected error handler to return nil error (it should panic!), but got '%v'", err) } }
func TestLookupDNSKEY(t *testing.T) { dnskey, rm1, rm2 := newKey(t) defer rm1() defer rm2() dh := NewDnssec([]string{"miek.nl."}, []*DNSKEY{dnskey}, test.ErrorHandler()) ctx := context.TODO() for _, tc := range dnssecTestCases { m := tc.Msg() rec := middleware.NewResponseRecorder(&test.ResponseWriter{}) _, err := dh.ServeDNS(ctx, rec, m) if err != nil { t.Errorf("expected no error, got %v\n", err) return } resp := rec.Msg() sort.Sort(test.RRSet(resp.Answer)) sort.Sort(test.RRSet(resp.Ns)) sort.Sort(test.RRSet(resp.Extra)) if !test.Header(t, tc, resp) { t.Logf("%v\n", resp) continue } if !test.Section(t, tc, test.Answer, resp.Answer) { t.Logf("%v\n", resp) } if !test.Section(t, tc, test.Ns, resp.Ns) { t.Logf("%v\n", resp) } if !test.Section(t, tc, test.Extra, resp.Extra) { t.Logf("%v\n", resp) } } }
func TestChaos(t *testing.T) { em := Chaos{ Version: version, Authors: map[string]bool{"Miek Gieben": true}, } tests := []struct { next middleware.Handler qname string qtype uint16 expectedCode int expectedReply string expectedErr error }{ { next: genHandler(dns.RcodeSuccess, nil), qname: "version.bind", expectedCode: dns.RcodeSuccess, expectedReply: version, expectedErr: nil, }, { next: genHandler(dns.RcodeSuccess, nil), qname: "authors.bind", expectedCode: dns.RcodeSuccess, expectedReply: "Miek Gieben", expectedErr: nil, }, { next: genHandler(dns.RcodeSuccess, nil), qname: "authors.bind", qtype: dns.TypeSRV, expectedCode: dns.RcodeSuccess, expectedErr: nil, }, } ctx := context.TODO() for i, tc := range tests { req := new(dns.Msg) if tc.qtype == 0 { tc.qtype = dns.TypeTXT } req.SetQuestion(dns.Fqdn(tc.qname), tc.qtype) req.Question[0].Qclass = dns.ClassCHAOS em.Next = tc.next rec := middleware.NewResponseRecorder(&test.ResponseWriter{}) code, err := em.ServeDNS(ctx, rec, req) if err != tc.expectedErr { t.Errorf("Test %d: Expected error %v, but got %v", i, tc.expectedErr, err) } if code != int(tc.expectedCode) { t.Errorf("Test %d: Expected status code %d, but got %d", i, tc.expectedCode, code) } if tc.expectedReply != "" { answer := rec.Msg().Answer[0].(*dns.TXT).Txt[0] if answer != tc.expectedReply { t.Errorf("Test %d: Expected answer %s, but got %s", i, tc.expectedReply, answer) } } } }
func TestRewrite(t *testing.T) { rw := Rewrite{ Next: middleware.HandlerFunc(msgPrinter), Rules: []Rule{ NewSimpleRule("from.nl.", "to.nl."), NewSimpleRule("CH", "IN"), NewSimpleRule("ANY", "HINFO"), }, noRevert: true, } tests := []struct { from string fromT uint16 fromC uint16 to string toT uint16 toC uint16 }{ {"from.nl.", dns.TypeA, dns.ClassINET, "to.nl.", dns.TypeA, dns.ClassINET}, {"a.nl.", dns.TypeA, dns.ClassINET, "a.nl.", dns.TypeA, dns.ClassINET}, {"a.nl.", dns.TypeA, dns.ClassCHAOS, "a.nl.", dns.TypeA, dns.ClassINET}, {"a.nl.", dns.TypeANY, dns.ClassINET, "a.nl.", dns.TypeHINFO, dns.ClassINET}, // name is rewritten, type is not. {"from.nl.", dns.TypeANY, dns.ClassINET, "to.nl.", dns.TypeANY, dns.ClassINET}, // name is not, type is, but class is, because class is the 2nd rule. {"a.nl.", dns.TypeANY, dns.ClassCHAOS, "a.nl.", dns.TypeANY, dns.ClassINET}, } ctx := context.TODO() for i, tc := range tests { m := new(dns.Msg) m.SetQuestion(tc.from, tc.fromT) m.Question[0].Qclass = tc.fromC rec := middleware.NewResponseRecorder(&test.ResponseWriter{}) rw.ServeDNS(ctx, rec, m) resp := rec.Msg() if resp.Question[0].Name != tc.to { t.Errorf("Test %d: Expected Name to be '%s' but was '%s'", i, tc.to, resp.Question[0].Name) } if resp.Question[0].Qtype != tc.toT { t.Errorf("Test %d: Expected Type to be '%d' but was '%d'", i, tc.toT, resp.Question[0].Qtype) } if resp.Question[0].Qclass != tc.toC { t.Errorf("Test %d: Expected Class to be '%d' but was '%d'", i, tc.toC, resp.Question[0].Qclass) } } /* regexps := [][]string{ {"/reg/", ".*", "/to", ""}, {"/r/", "[a-z]+", "/toaz", "!.html|"}, {"/url/", "a([a-z0-9]*)s([A-Z]{2})", "/to/{path}", ""}, {"/ab/", "ab", "/ab?{query}", ".txt|"}, {"/ab/", "ab", "/ab?type=html&{query}", ".html|"}, {"/abc/", "ab", "/abc/{file}", ".html|"}, {"/abcd/", "ab", "/a/{dir}/{file}", ".html|"}, {"/abcde/", "ab", "/a#{fragment}", ".html|"}, {"/ab/", `.*\.jpg`, "/ajpg", ""}, {"/reggrp", `/ad/([0-9]+)([a-z]*)`, "/a{1}/{2}", ""}, {"/reg2grp", `(.*)`, "/{1}", ""}, {"/reg3grp", `(.*)/(.*)/(.*)`, "/{1}{2}{3}", ""}, } for _, regexpRule := range regexps { var ext []string if s := strings.Split(regexpRule[3], "|"); len(s) > 1 { ext = s[:len(s)-1] } rule, err := NewComplexRule(regexpRule[0], regexpRule[1], regexpRule[2], 0, ext, nil) if err != nil { t.Fatal(err) } rw.Rules = append(rw.Rules, rule) } */ /* statusTests := []struct { status int base string to string regexp string statusExpected bool }{ {400, "/status", "", "", true}, {400, "/ignore", "", "", false}, {400, "/", "", "^/ignore", false}, {400, "/", "", "(.*)", true}, {400, "/status", "", "", true}, } for i, s := range statusTests { urlPath := fmt.Sprintf("/status%d", i) rule, err := NewComplexRule(s.base, s.regexp, s.to, s.status, nil, nil) if err != nil { t.Fatalf("Test %d: No error expected for rule but found %v", i, err) } rw.Rules = []Rule{rule} req, err := http.NewRequest("GET", urlPath, nil) if err != nil { t.Fatalf("Test %d: Could not create HTTP request: %v", i, err) } rec := httptest.NewRecorder() code, err := rw.ServeHTTP(rec, req) if err != nil { t.Fatalf("Test %d: No error expected for handler but found %v", i, err) } if s.statusExpected { if rec.Body.String() != "" { t.Errorf("Test %d: Expected empty body but found %s", i, rec.Body.String()) } if code != s.status { t.Errorf("Test %d: Expected status code %d found %d", i, s.status, code) } } else { if code != 0 { t.Errorf("Test %d: Expected no status code found %d", i, code) } } } */ }
func TestLoadBalance(t *testing.T) { rm := RoundRobin{Next: handler()} // the first X records must be cnames after this test tests := []struct { answer []dns.RR extra []dns.RR cnameAnswer int cnameExtra int }{ { answer: []dns.RR{ newCNAME("cname1.region2.skydns.test. 300 IN CNAME cname2.region2.skydns.test."), newCNAME("cname2.region2.skydns.test. 300 IN CNAME cname3.region2.skydns.test."), newCNAME("cname5.region2.skydns.test. 300 IN CNAME cname6.region2.skydns.test."), newCNAME("cname6.region2.skydns.test. 300 IN CNAME endpoint.region2.skydns.test."), newA("endpoint.region2.skydns.test. 300 IN A 10.240.0.1"), }, cnameAnswer: 4, }, { answer: []dns.RR{ newA("endpoint.region2.skydns.test. 300 IN A 10.240.0.1"), newCNAME("cname.region2.skydns.test. 300 IN CNAME endpoint.region2.skydns.test."), }, cnameAnswer: 1, }, { answer: []dns.RR{ newA("endpoint.region2.skydns.test. 300 IN A 10.240.0.1"), newA("endpoint.region2.skydns.test. 300 IN A 10.240.0.2"), newCNAME("cname2.region2.skydns.test. 300 IN CNAME cname3.region2.skydns.test."), newA("endpoint.region2.skydns.test. 300 IN A 10.240.0.3"), }, extra: []dns.RR{ newA("endpoint.region2.skydns.test. 300 IN A 10.240.0.1"), newAAAA("endpoint.region2.skydns.test. 300 IN AAAA ::1"), newCNAME("cname2.region2.skydns.test. 300 IN CNAME cname3.region2.skydns.test."), newA("endpoint.region2.skydns.test. 300 IN A 10.240.0.3"), newAAAA("endpoint.region2.skydns.test. 300 IN AAAA ::2"), }, cnameAnswer: 1, cnameExtra: 1, }, } rec := middleware.NewResponseRecorder(&test.ResponseWriter{}) for i, test := range tests { req := new(dns.Msg) req.SetQuestion("region2.skydns.test.", dns.TypeSRV) req.Answer = test.answer req.Extra = test.extra _, err := rm.ServeDNS(context.TODO(), rec, req) if err != nil { t.Errorf("Test %d: Expected no error, but got %s", i, err) continue } cname := 0 for _, r := range rec.Msg().Answer { if r.Header().Rrtype != dns.TypeCNAME { break } cname++ } if cname != test.cnameAnswer { t.Errorf("Test %d: Expected %d cnames in Answer, but got %d", i, test.cnameAnswer, cname) } cname = 0 for _, r := range rec.Msg().Extra { if r.Header().Rrtype != dns.TypeCNAME { break } cname++ } if cname != test.cnameExtra { t.Errorf("Test %d: Expected %d cname in Extra, but got %d", i, test.cnameExtra, cname) } } }