diff options
-rw-r--r-- | middleware/canonical.go | 11 | ||||
-rw-r--r-- | middleware/etcd/cname_test.go | 3 | ||||
-rw-r--r-- | middleware/etcd/group_test.go | 3 | ||||
-rw-r--r-- | middleware/etcd/multi_test.go | 3 | ||||
-rw-r--r-- | middleware/etcd/other_test.go | 3 | ||||
-rw-r--r-- | middleware/etcd/setup_test.go | 3 | ||||
-rw-r--r-- | middleware/file/closest.go | 64 | ||||
-rw-r--r-- | middleware/file/closest_test.go | 37 | ||||
-rw-r--r-- | middleware/file/dnssec_test.go | 56 | ||||
-rw-r--r-- | middleware/file/file.go | 20 | ||||
-rw-r--r-- | middleware/file/lookup.go | 115 | ||||
-rw-r--r-- | middleware/file/lookup_test.go | 9 | ||||
-rw-r--r-- | middleware/file/tree/tree.go | 98 | ||||
-rw-r--r-- | middleware/file/wildcard_test.go | 222 | ||||
-rw-r--r-- | middleware/testing/helpers.go | 36 |
15 files changed, 579 insertions, 104 deletions
diff --git a/middleware/canonical.go b/middleware/canonical.go index 5d51231c2..9dacba78c 100644 --- a/middleware/canonical.go +++ b/middleware/canonical.go @@ -8,17 +8,20 @@ import ( // Less returns <0 when a is less than b, 0 when they are equal and // >0 when a is larger than b. -// The function order names in DNSSEC canonical order. +// The function orders names in DNSSEC canonical order: RFC 4034s section-6.1 // // See http://bert-hubert.blogspot.co.uk/2015/10/how-to-do-fast-canonical-ordering-of.html -// for a blog article on how we do this. And https://tools.ietf.org/html/rfc4034#section-6.1 . +// for a blog article on this implementation: func Less(a, b string) int { i := 1 aj := len(a) bj := len(b) for { - ai, _ := dns.PrevLabel(a, i) - bi, _ := dns.PrevLabel(b, i) + ai, oka := dns.PrevLabel(a, i) + bi, okb := dns.PrevLabel(b, i) + if oka && okb { + return 0 + } // sadly this []byte will allocate... ab := []byte(a[ai:aj]) toLowerAndDDD(ab) diff --git a/middleware/etcd/cname_test.go b/middleware/etcd/cname_test.go index d3e76c65a..dabecbee4 100644 --- a/middleware/etcd/cname_test.go +++ b/middleware/etcd/cname_test.go @@ -21,8 +21,7 @@ func TestCnameLookup(t *testing.T) { defer delete(t, etc, serv.Key) } for _, tc := range dnsTestCasesCname { - m := new(dns.Msg) - m.SetQuestion(dns.Fqdn(tc.Qname), tc.Qtype) + m := tc.Msg() rec := middleware.NewResponseRecorder(&middleware.TestResponseWriter{}) _, err := etc.ServeDNS(ctx, rec, m) diff --git a/middleware/etcd/group_test.go b/middleware/etcd/group_test.go index ae1d7a500..3bbc014fa 100644 --- a/middleware/etcd/group_test.go +++ b/middleware/etcd/group_test.go @@ -23,8 +23,7 @@ func TestGroupLookup(t *testing.T) { defer delete(t, etc, serv.Key) } for _, tc := range dnsTestCasesGroup { - m := new(dns.Msg) - m.SetQuestion(dns.Fqdn(tc.Qname), tc.Qtype) + m := tc.Msg() rec := middleware.NewResponseRecorder(&middleware.TestResponseWriter{}) _, err := etc.ServeDNS(ctx, rec, m) diff --git a/middleware/etcd/multi_test.go b/middleware/etcd/multi_test.go index 5327a4abe..25ba6c761 100644 --- a/middleware/etcd/multi_test.go +++ b/middleware/etcd/multi_test.go @@ -26,8 +26,7 @@ func TestMultiLookup(t *testing.T) { defer delete(t, etcMulti, serv.Key) } for _, tc := range dnsTestCasesMulti { - m := new(dns.Msg) - m.SetQuestion(dns.Fqdn(tc.Qname), tc.Qtype) + m := tc.Msg() rec := middleware.NewResponseRecorder(&middleware.TestResponseWriter{}) _, err := etcMulti.ServeDNS(ctx, rec, m) diff --git a/middleware/etcd/other_test.go b/middleware/etcd/other_test.go index a00f742b6..018aedfaa 100644 --- a/middleware/etcd/other_test.go +++ b/middleware/etcd/other_test.go @@ -25,8 +25,7 @@ func TestOtherLookup(t *testing.T) { defer delete(t, etc, serv.Key) } for _, tc := range dnsTestCasesOther { - m := new(dns.Msg) - m.SetQuestion(dns.Fqdn(tc.Qname), tc.Qtype) + m := tc.Msg() rec := middleware.NewResponseRecorder(&middleware.TestResponseWriter{}) _, err := etc.ServeDNS(ctx, rec, m) diff --git a/middleware/etcd/setup_test.go b/middleware/etcd/setup_test.go index c14ed74f0..39ec538ee 100644 --- a/middleware/etcd/setup_test.go +++ b/middleware/etcd/setup_test.go @@ -65,8 +65,7 @@ func TestLookup(t *testing.T) { defer delete(t, etc, serv.Key) } for _, tc := range dnsTestCases { - m := new(dns.Msg) - m.SetQuestion(dns.Fqdn(tc.Qname), tc.Qtype) + m := tc.Msg() rec := middleware.NewResponseRecorder(&middleware.TestResponseWriter{}) _, err := etc.ServeDNS(ctx, rec, m) diff --git a/middleware/file/closest.go b/middleware/file/closest.go new file mode 100644 index 000000000..0c5fd722d --- /dev/null +++ b/middleware/file/closest.go @@ -0,0 +1,64 @@ +package file + +import "github.com/miekg/dns" + +// ClosestEncloser returns the closest encloser for rr. +func (z *Zone) ClosestEncloser(rr dns.RR) string { + // tree/tree.go does not store a parent *Node pointer, so we can't + // just follow up the tree. TODO(miek): fix. + + offset, end := dns.NextLabel(rr.Header().Name, 0) + for !end { + elem := z.Tree.Get(rr) + if elem != nil { + return elem.Name() + } + rr.Header().Name = rr.Header().Name[offset:] + + offset, end = dns.NextLabel(rr.Header().Name, offset) + } + + return z.SOA.Header().Name +} + +// nameErrorProof finds the closest encloser and return an NSEC that proofs +// the wildcard does not exist and an NSEC that proofs the name does no exist. +func (z *Zone) nameErrorProof(rr dns.RR) []dns.RR { + elem := z.Tree.Prev(rr) + if elem == nil { + return nil + } + nsec := z.lookupNSEC(elem, true) + nsecIndex := 0 + for i := 0; i < len(nsec); i++ { + if nsec[i].Header().Rrtype == dns.TypeNSEC { + nsecIndex = i + break + } + } + + ce := z.ClosestEncloser(rr) + wildcard := "*." + ce + rr.Header().Name = wildcard + elem = z.Tree.Prev(rr) + if elem == nil { + // Root? + return nil + } + nsec1 := z.lookupNSEC(elem, true) + nsec1Index := 0 + for i := 0; i < len(nsec1); i++ { + if nsec1[i].Header().Rrtype == dns.TypeNSEC { + nsec1Index = i + break + } + } + + // Check for duplicate NSEC. + if nsec[nsecIndex].Header().Name == nsec1[nsec1Index].Header().Name && + nsec[nsecIndex].(*dns.NSEC).NextDomain == nsec1[nsec1Index].(*dns.NSEC).NextDomain { + return nsec + } + + return append(nsec, nsec1...) +} diff --git a/middleware/file/closest_test.go b/middleware/file/closest_test.go new file mode 100644 index 000000000..db0b718b2 --- /dev/null +++ b/middleware/file/closest_test.go @@ -0,0 +1,37 @@ +package file + +import ( + "strings" + "testing" + + "github.com/miekg/dns" +) + +func TestClosestEncloser(t *testing.T) { + z, err := Parse(strings.NewReader(dbMiekNL), testzone, "stdin") + if err != nil { + t.Fatalf("expect no error when reading zone, got %q", err) + } + + tests := []struct { + in, out string + }{ + {"miek.nl.", "miek.nl."}, + {"www.miek.nl.", "www.miek.nl."}, + + {"blaat.miek.nl.", "miek.nl."}, + {"blaat.www.miek.nl.", "www.miek.nl."}, + {"www.blaat.miek.nl.", "miek.nl."}, + {"blaat.a.miek.nl.", "a.miek.nl."}, + } + + mk, _ := dns.TypeToRR[dns.TypeA] + rr := mk() + for _, tc := range tests { + rr.Header().Name = tc.in + ce := z.ClosestEncloser(rr) + if ce != tc.out { + t.Errorf("expected ce to be %s for %s, got %s", tc.out, tc.in, ce) + } + } +} diff --git a/middleware/file/dnssec_test.go b/middleware/file/dnssec_test.go index 57cce90c5..dc19235da 100644 --- a/middleware/file/dnssec_test.go +++ b/middleware/file/dnssec_test.go @@ -14,55 +14,90 @@ import ( var dnssecTestCases = []coretest.Case{ { - Qname: "miek.nl.", Qtype: dns.TypeSOA, + Qname: "miek.nl.", Qtype: dns.TypeSOA, Do: true, Answer: []dns.RR{ + // because we sort, this look fishy, but it is OK. + coretest.RRSIG("miek.nl. 1800 IN RRSIG SOA 8 2 1800 20160426031301 20160327031301 12051 miek.nl. FIrzy07acBbtyQczy1dc="), coretest.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"), }, }, { - Qname: "miek.nl.", Qtype: dns.TypeAAAA, + Qname: "miek.nl.", Qtype: dns.TypeAAAA, Do: true, Answer: []dns.RR{ coretest.AAAA("miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"), + coretest.RRSIG("miek.nl. 1800 IN RRSIG AAAA 8 2 1800 20160426031301 20160327031301 12051 miek.nl. SsRT="), }, }, { - Qname: "miek.nl.", Qtype: dns.TypeMX, + Qname: "miek.nl.", Qtype: dns.TypeMX, Do: true, Answer: []dns.RR{ coretest.MX("miek.nl. 1800 IN MX 1 aspmx.l.google.com."), coretest.MX("miek.nl. 1800 IN MX 10 aspmx2.googlemail.com."), coretest.MX("miek.nl. 1800 IN MX 10 aspmx3.googlemail.com."), coretest.MX("miek.nl. 1800 IN MX 5 alt1.aspmx.l.google.com."), coretest.MX("miek.nl. 1800 IN MX 5 alt2.aspmx.l.google.com."), + coretest.RRSIG("miek.nl. 1800 IN RRSIG MX 8 2 1800 20160426031301 20160327031301 12051 miek.nl. kLqG+iOr="), }, }, { - Qname: "www.miek.nl.", Qtype: dns.TypeA, + Qname: "www.miek.nl.", Qtype: dns.TypeA, Do: true, Answer: []dns.RR{ coretest.CNAME("www.miek.nl. 1800 IN CNAME a.miek.nl."), }, Extra: []dns.RR{ coretest.A("a.miek.nl. 1800 IN A 139.162.196.78"), - coretest.AAAA("a.miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"), + coretest.RRSIG("a.miek.nl. 1800 IN RRSIG A 8 3 1800 20160426031301 20160327031301 12051 miek.nl. lxLotCjWZ3kihTxk="), }, }, { - Qname: "a.miek.nl.", Qtype: dns.TypeSRV, + // NoData + Qname: "a.miek.nl.", Qtype: dns.TypeSRV, Do: true, Ns: []dns.RR{ + coretest.NSEC("a.miek.nl. 14400 IN NSEC archive.miek.nl. A AAAA RRSIG NSEC"), + coretest.RRSIG("a.miek.nl. 14400 IN RRSIG NSEC 8 3 14400 20160426031301 20160327031301 12051 miek.nl. GqnF6cutipmSHEao="), + coretest.RRSIG("miek.nl. 1800 IN RRSIG SOA 8 2 1800 20160426031301 20160327031301 12051 miek.nl. FIrzy07acBbtyQczy1dc="), coretest.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"), }, }, { - Qname: "b.miek.nl.", Qtype: dns.TypeA, + Qname: "b.miek.nl.", Qtype: dns.TypeA, Do: true, Rcode: dns.RcodeNameError, Ns: []dns.RR{ + coretest.NSEC("archive.miek.nl. 14400 IN NSEC go.dns.miek.nl. CNAME RRSIG NSEC"), + coretest.RRSIG("archive.miek.nl. 14400 IN RRSIG NSEC 8 3 14400 20160426031301 20160327031301 12051 miek.nl. jEpx8lcp4do5fWXg="), + coretest.NSEC("miek.nl. 14400 IN NSEC a.miek.nl. A NS SOA MX AAAA RRSIG NSEC DNSKEY"), + coretest.RRSIG("miek.nl. 14400 IN RRSIG NSEC 8 2 14400 20160426031301 20160327031301 12051 miek.nl. mFfc3r/9PSC1H6oSpdC"), + coretest.RRSIG("miek.nl. 1800 IN RRSIG SOA 8 2 1800 20160426031301 20160327031301 12051 miek.nl. FIrzy07acBbtyQczy1dc="), + coretest.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"), + }, + }, + { + Qname: "b.blaat.miek.nl.", Qtype: dns.TypeA, Do: true, + Rcode: dns.RcodeNameError, + Ns: []dns.RR{ + coretest.NSEC("archive.miek.nl. 14400 IN NSEC go.dns.miek.nl. CNAME RRSIG NSEC"), + coretest.RRSIG("archive.miek.nl. 14400 IN RRSIG NSEC 8 3 14400 20160426031301 20160327031301 12051 miek.nl. jEpx8lcp4do5fWXg="), + coretest.NSEC("miek.nl. 14400 IN NSEC a.miek.nl. A NS SOA MX AAAA RRSIG NSEC DNSKEY"), + coretest.RRSIG("miek.nl. 14400 IN RRSIG NSEC 8 2 14400 20160426031301 20160327031301 12051 miek.nl. mFfc3r/9PSC1H6oSpdC"), + coretest.RRSIG("miek.nl. 1800 IN RRSIG SOA 8 2 1800 20160426031301 20160327031301 12051 miek.nl. FIrzy07acBbtyQczy1dc="), + coretest.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"), + }, + }, + { + Qname: "b.a.miek.nl.", Qtype: dns.TypeA, Do: true, + Rcode: dns.RcodeNameError, + Ns: []dns.RR{ + // dedupped NSEC, because 1 nsec tells all + coretest.NSEC("a.miek.nl. 14400 IN NSEC archive.miek.nl. A AAAA RRSIG NSEC"), + coretest.RRSIG("a.miek.nl. 14400 IN RRSIG NSEC 8 3 14400 20160426031301 20160327031301 12051 miek.nl. GqnF6cut/RRGPQ1QGQE1ipmSHEao="), + coretest.RRSIG("miek.nl. 1800 IN RRSIG SOA 8 2 1800 20160426031301 20160327031301 12051 miek.nl. FIrzy07acBbtyQczy1dc="), coretest.SOA("miek.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. 1282630057 14400 3600 604800 14400"), }, }, } -// TODO(miek): enable -func testLookupDNSSEC(t *testing.T) { +func TestLookupDNSSEC(t *testing.T) { zone, err := Parse(strings.NewReader(dbMiekNL_signed), testzone, "stdin") if err != nil { t.Fatalf("expect no error when reading zone, got %q", err) @@ -72,8 +107,7 @@ func testLookupDNSSEC(t *testing.T) { ctx := context.TODO() for _, tc := range dnssecTestCases { - m := new(dns.Msg) - m.SetQuestion(dns.Fqdn(tc.Qname), tc.Qtype) + m := tc.Msg() rec := middleware.NewResponseRecorder(&middleware.TestResponseWriter{}) _, err := fm.ServeDNS(ctx, rec, m) diff --git a/middleware/file/file.go b/middleware/file/file.go index 237515a57..c005b41cc 100644 --- a/middleware/file/file.go +++ b/middleware/file/file.go @@ -39,7 +39,7 @@ func (f File) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i return xfr.ServeDNS(ctx, w, r) } - rrs, extra, result := z.Lookup(qname, state.QType(), state.Do()) + answer, ns, extra, result := z.Lookup(qname, state.QType(), state.Do()) m := new(dns.Msg) m.SetReply(r) @@ -47,18 +47,17 @@ func (f File) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i switch result { case Success: - // case? - m.Answer = rrs + m.Answer = answer + m.Ns = ns m.Extra = extra - // Ns section case NameError: + m.Ns = ns m.Rcode = dns.RcodeNameError fallthrough case NoData: - // case? - m.Ns = rrs - default: - // TODO + m.Ns = ns + case ServerFailure: + return dns.RcodeServerFailure, nil } m, _ = state.Scrub(m) w.WriteMsg(m) @@ -78,6 +77,11 @@ func Parse(f io.Reader, origin, fileName string) (*Zone, error) { z.SOA = x.RR.(*dns.SOA) continue } + if x.RR.Header().Rrtype == dns.TypeRRSIG { + if x, ok := x.RR.(*dns.RRSIG); ok && x.TypeCovered == dns.TypeSOA { + z.SIG = append(z.SIG, x) + } + } z.Insert(x.RR) } return z, nil diff --git a/middleware/file/lookup.go b/middleware/file/lookup.go index 2798b0b23..7c4d92abe 100644 --- a/middleware/file/lookup.go +++ b/middleware/file/lookup.go @@ -1,6 +1,10 @@ package file -import "github.com/miekg/dns" +import ( + "github.com/miekg/coredns/middleware/file/tree" + + "github.com/miekg/dns" +) // Result is the result of a Lookup type Result int @@ -8,19 +12,17 @@ type Result int const ( Success Result = iota NameError - NoData // aint no offical NoData return code. + NoData + ServerFailure ) // Lookup looks up qname and qtype in the zone, when do is true DNSSEC are included as well. -// Two sets of records are returned, one for the answer and one for the additional section. -func (z *Zone) Lookup(qname string, qtype uint16, do bool) ([]dns.RR, []dns.RR, Result) { - // TODO(miek): implement DNSSEC +// Three sets of records are returned, one for the answer, one for authority and one for the additional section. +func (z *Zone) Lookup(qname string, qtype uint16, do bool) ([]dns.RR, []dns.RR, []dns.RR, Result) { var rr dns.RR mk, known := dns.TypeToRR[qtype] if !known { - return nil, nil, NameError - // Uhm...? - // rr = new(RFC3597) + return nil, nil, nil, ServerFailure } else { rr = mk() } @@ -28,41 +30,106 @@ func (z *Zone) Lookup(qname string, qtype uint16, do bool) ([]dns.RR, []dns.RR, return z.lookupSOA(do) } + // Misuse rr to be a question. + rr.Header().Rrtype = qtype rr.Header().Name = qname + elem := z.Tree.Get(rr) if elem == nil { - return []dns.RR{z.SOA}, nil, NameError + if elem == nil { + return z.nameError(elem, rr, do) + } } + rrs := elem.Types(dns.TypeCNAME) if len(rrs) > 0 { // should only ever be 1 actually; TODO(miek) check for this? - // lookup target from the cname rr.Header().Name = rrs[0].(*dns.CNAME).Target - elem := z.Tree.Get(rr) - if elem == nil { - return rrs, nil, Success - } - return rrs, elem.All(), Success + return z.lookupCNAME(rrs, rr, do) } rrs = elem.Types(qtype) if len(rrs) == 0 { - return []dns.RR{z.SOA}, nil, NoData + return z.noData(elem, do) + } + + if do { + sigs := elem.Types(dns.TypeRRSIG) + sigs = signatureForSubType(sigs, qtype) + if len(sigs) > 0 { + rrs = append(rrs, sigs...) + } } - // Need to check sub-type on RRSIG records to only include the correctly - // typed ones. - return rrs, nil, Success + return rrs, nil, nil, Success } -func (z *Zone) lookupSOA(do bool) ([]dns.RR, []dns.RR, Result) { - return []dns.RR{z.SOA}, nil, Success +func (z *Zone) noData(elem *tree.Elem, do bool) ([]dns.RR, []dns.RR, []dns.RR, Result) { + soa, _, _, _ := z.lookupSOA(do) + nsec := z.lookupNSEC(elem, do) + return nil, append(soa, nsec...), nil, Success } -// signatureForSubType range through the signature and return the correct -// ones for the subtype. -func (z *Zone) signatureForSubType(rrs []dns.RR, subtype uint16, do bool) []dns.RR { +func (z *Zone) nameError(elem *tree.Elem, rr dns.RR, do bool) ([]dns.RR, []dns.RR, []dns.RR, Result) { + ret := []dns.RR{z.SOA} + if do { + ret = append(ret, z.SIG...) + ret = append(ret, z.nameErrorProof(rr)...) + } + return nil, ret, nil, NameError +} + +func (z *Zone) lookupSOA(do bool) ([]dns.RR, []dns.RR, []dns.RR, Result) { + if do { + ret := append([]dns.RR{z.SOA}, z.SIG...) + return ret, nil, nil, Success + } + return []dns.RR{z.SOA}, nil, nil, Success +} + +// lookupNSEC looks up nsec and sigs. +func (z *Zone) lookupNSEC(elem *tree.Elem, do bool) []dns.RR { if !do { return nil } + nsec := elem.Types(dns.TypeNSEC) + if do { + sigs := elem.Types(dns.TypeRRSIG) + sigs = signatureForSubType(sigs, dns.TypeNSEC) + if len(sigs) > 0 { + nsec = append(nsec, sigs...) + } + } + return nsec +} + +func (z *Zone) lookupCNAME(rrs []dns.RR, rr dns.RR, do bool) ([]dns.RR, []dns.RR, []dns.RR, Result) { + elem := z.Tree.Get(rr) + if elem == nil { + return rrs, nil, nil, Success + } + extra := cnameForType(elem.All(), rr.Header().Rrtype) + if do { + sigs := elem.Types(dns.TypeRRSIG) + sigs = signatureForSubType(sigs, rr.Header().Rrtype) + if len(sigs) > 0 { + extra = append(extra, sigs...) + } + } + return rrs, nil, extra, Success +} + +func cnameForType(targets []dns.RR, origQtype uint16) []dns.RR { + ret := []dns.RR{} + for _, target := range targets { + if target.Header().Rrtype == origQtype { + ret = append(ret, target) + } + } + return ret +} + +// signatureForSubType range through the signature and return the correct +// ones for the subtype. +func signatureForSubType(rrs []dns.RR, subtype uint16) []dns.RR { sigs := []dns.RR{} for _, sig := range rrs { if s, ok := sig.(*dns.RRSIG); ok { diff --git a/middleware/file/lookup_test.go b/middleware/file/lookup_test.go index 61d1e2182..9b4e31cc8 100644 --- a/middleware/file/lookup_test.go +++ b/middleware/file/lookup_test.go @@ -43,7 +43,6 @@ var dnsTestCases = []coretest.Case{ Extra: []dns.RR{ coretest.A("a.miek.nl. 1800 IN A 139.162.196.78"), - coretest.AAAA("a.miek.nl. 1800 IN AAAA 2a01:7e00::f03c:91ff:fef1:6735"), }, }, { @@ -61,7 +60,10 @@ var dnsTestCases = []coretest.Case{ }, } -const testzone = "miek.nl." +const ( + testzone = "miek.nl." + testzone1 = "dnssex.nl." +) func TestLookup(t *testing.T) { zone, err := Parse(strings.NewReader(dbMiekNL), testzone, "stdin") @@ -73,8 +75,7 @@ func TestLookup(t *testing.T) { ctx := context.TODO() for _, tc := range dnsTestCases { - m := new(dns.Msg) - m.SetQuestion(dns.Fqdn(tc.Qname), tc.Qtype) + m := tc.Msg() rec := middleware.NewResponseRecorder(&middleware.TestResponseWriter{}) _, err := fm.ServeDNS(ctx, rec, m) diff --git a/middleware/file/tree/tree.go b/middleware/file/tree/tree.go index 234060eba..ca616ad67 100644 --- a/middleware/file/tree/tree.go +++ b/middleware/file/tree/tree.go @@ -13,12 +13,11 @@ // Heavily modified by Miek Gieben for use in DNS zones. package tree -// TODO(miek): locking? lockfree +// TODO(miek): locking? lockfree would be nice. Will probably go for fine grained locking on the name level. // TODO(miek): fix docs import ( - "strings" - + "github.com/miekg/coredns/middleware" "github.com/miekg/dns" ) @@ -47,7 +46,7 @@ func newElem(rr dns.RR) *Elem { return &e } -// Types returns the types from with type qtype from e. +// Types returns the RRs with type qtype from e. func (e *Elem) Types(qtype uint16) []dns.RR { if rrs, ok := e.m[qtype]; ok { // TODO(miek): length should never be zero here. @@ -56,6 +55,7 @@ func (e *Elem) Types(qtype uint16) []dns.RR { return nil } +// All returns all RRs from e, regardless of type. func (e *Elem) All() []dns.RR { list := []dns.RR{} for _, rrs := range e.m { @@ -64,6 +64,15 @@ func (e *Elem) All() []dns.RR { return list } +// Return the domain name for this element. +func (e *Elem) Name() string { + for _, rrs := range e.m { + return rrs[0].Header().Name + } + return "" +} + +// Insert inserts rr into e. If rr is equal to existing rrs this is a noop. func (e *Elem) Insert(rr dns.RR) { t := rr.Header().Rrtype if e.m == nil { @@ -110,26 +119,14 @@ func (e *Elem) Delete(rr dns.RR) (empty bool) { return } -// TODO(miek): need case ignore compare that is more efficient. func Less(a *Elem, rr dns.RR) int { - aname := "" - for _, ar := range a.m { - aname = strings.ToLower(ar[0].Header().Name) - break - } - rname := strings.ToLower(rr.Header().Name) - if aname == rname { - return 0 - } - if aname < rname { - return -1 - } - return 1 + return middleware.Less(rr.Header().Name, a.Name()) } // Assuming the same type and name this will check if the rdata is equal as well. func equalRdata(a, b dns.RR) bool { switch x := a.(type) { + // TODO(miek): more types, i.e. all types. case *dns.A: return x.A.Equal(b.(*dns.A).A) case *dns.AAAA: @@ -259,8 +256,7 @@ func (t *Tree) Len() int { return t.Count } -// Get returns the first match of q in the Tree. If insertion without -// replacement is used, this is probably not what you want. +// Get returns the first match of rr in the Tree. func (t *Tree) Get(rr dns.RR) *Elem { if t.Root == nil { return nil @@ -287,11 +283,8 @@ func (n *Node) search(rr dns.RR) *Node { return n } -// Insert inserts the Comparable e into the Tree at the first match found -// with e or when a nil node is reached. Insertion without replacement can -// specified by ensuring that e.Compare() never returns 0. If insert without -// replacement is performed, a distinct query Comparable must be used that -// can return 0 with a Compare() call. +// Insert inserts rr into the Tree at the first match found +// with e or when a nil node is reached. func (t *Tree) Insert(rr dns.RR) { var d int t.Root, d = t.Root.insert(rr) @@ -340,8 +333,7 @@ func (n *Node) insert(rr dns.RR) (root *Node, d int) { return } -// DeleteMin deletes the node with the minimum value in the tree. If insertion without -// replacement has been used, the left-most minimum will be deleted. +// DeleteMin deletes the node with the minimum value in the tree. func (t *Tree) DeleteMin() { if t.Root == nil { return @@ -369,8 +361,7 @@ func (n *Node) deleteMin() (root *Node, d int) { return } -// DeleteMax deletes the node with the maximum value in the tree. If insertion without -// replacement has been used, the right-most maximum will be deleted. +// DeleteMax deletes the node with the maximum value in the tree. func (t *Tree) DeleteMax() { if t.Root == nil { return @@ -401,7 +392,7 @@ func (n *Node) deleteMax() (root *Node, d int) { return } -// Delete removes rr from the tree, is the node turns empty, that node is return with DeleteNode. +// Delete removes rr from the tree, is the node turns empty, that node is deleted with DeleteNode. func (t *Tree) Delete(rr dns.RR) { if t.Root == nil { return @@ -420,9 +411,7 @@ func (t *Tree) Delete(rr dns.RR) { } } -// DeleteNode deletes the node that matches e according to Compare(). Note that Compare must -// identify the target node uniquely and in cases where non-unique keys are used, -// attributes used to break ties must be used to determine tree ordering during insertion. +// DeleteNode deletes the node that matches rr according to Less(). func (t *Tree) DeleteNode(rr dns.RR) { if t.Root == nil { return @@ -469,8 +458,7 @@ func (n *Node) delete(rr dns.RR) (root *Node, d int) { return } -// Return the minimum value stored in the tree. This will be the left-most minimum value if -// insertion without replacement has been used. +// Min returns the minimum value stored in the tree. func (t *Tree) Min() *Elem { if t.Root == nil { return nil @@ -484,8 +472,7 @@ func (n *Node) min() *Node { return n } -// Return the maximum value stored in the tree. This will be the right-most maximum value if -// insertion without replacement has been used. +// Max returns the maximum value stored in the tree. func (t *Tree) Max() *Elem { if t.Root == nil { return nil @@ -499,8 +486,8 @@ func (n *Node) max() *Node { return n } -// Floor returns the greatest value equal to or less than the query q according to q.Compare(). -func (t *Tree) Floor(rr dns.RR) *Elem { +// Prev returns the greatest value equal to or less than the rr according to Less(). +func (t *Tree) Prev(rr dns.RR) *Elem { if t.Root == nil { return nil } @@ -528,10 +515,8 @@ func (n *Node) floor(rr dns.RR) *Node { return n } -// TODO(successor, predecessor) - -// Ceil returns the smallest value equal to or greater than the query q according to q.Compare(). -func (t *Tree) Ceil(rr dns.RR) *Elem { +// Next returns the smallest value equal to or greater than the rr according to Less(). +func (t *Tree) Next(rr dns.RR) *Elem { if t.Root == nil { return nil } @@ -559,6 +544,33 @@ func (n *Node) ceil(rr dns.RR) *Node { return n } +// Do performs fn on all values stored in the tree. A boolean is returned indicating whether the +// Do traversal was interrupted by an Operation returning true. If fn alters stored values' sort +// relationships, future tree operation behaviors are undefined. +func (t *Tree) Do(fn func(e *Elem) bool) bool { + if t.Root == nil { + return false + } + return t.Root.do(fn) +} + +func (n *Node) do(fn func(e *Elem) bool) (done bool) { + if n.Left != nil { + done = n.Left.do(fn) + if done { + return + } + } + done = fn(n.Elem) + if done { + return + } + if n.Right != nil { + done = n.Right.do(fn) + } + return +} + /* Copyright ©2012 The bíogo Authors. All rights reserved. diff --git a/middleware/file/wildcard_test.go b/middleware/file/wildcard_test.go new file mode 100644 index 000000000..81b799955 --- /dev/null +++ b/middleware/file/wildcard_test.go @@ -0,0 +1,222 @@ +package file + +import ( + "sort" + "strings" + "testing" + + "github.com/miekg/coredns/middleware" + coretest "github.com/miekg/coredns/middleware/testing" + + "github.com/miekg/dns" + "golang.org/x/net/context" +) + +var dnssecWildcardTestCases = []coretest.Case{ + { + Qname: "blaat.dnssex.nl.", Qtype: dns.TypeTXT, Do: true, + Answer: []dns.RR{}, + }, +} + +func testLookupDNSSECWildcard(t *testing.T) { + zone, err := Parse(strings.NewReader(dbMiekNL_signed), testzone1, "stdin") + if err != nil { + t.Fatalf("expect no error when reading zone, got %q", err) + } + + fm := File{Next: coretest.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testzone1: zone}, Names: []string{testzone1}}} + ctx := context.TODO() + + for _, tc := range dnssecWildcardTestCases { + m := tc.Msg() + + rec := middleware.NewResponseRecorder(&middleware.TestResponseWriter{}) + _, err := fm.ServeDNS(ctx, rec, m) + if err != nil { + t.Errorf("expected no error, got %v\n", err) + return + } + resp := rec.Msg() + + sort.Sort(coretest.RRSet(resp.Answer)) + sort.Sort(coretest.RRSet(resp.Ns)) + sort.Sort(coretest.RRSet(resp.Extra)) + + if resp.Rcode != tc.Rcode { + t.Errorf("rcode is %q, expected %q", dns.RcodeToString[resp.Rcode], dns.RcodeToString[tc.Rcode]) + t.Logf("%v\n", resp) + continue + } + + if len(resp.Answer) != len(tc.Answer) { + t.Errorf("answer for %q contained %d results, %d expected", tc.Qname, len(resp.Answer), len(tc.Answer)) + t.Logf("%v\n", resp) + continue + } + if len(resp.Ns) != len(tc.Ns) { + t.Errorf("authority for %q contained %d results, %d expected", tc.Qname, len(resp.Ns), len(tc.Ns)) + t.Logf("%v\n", resp) + continue + } + if len(resp.Extra) != len(tc.Extra) { + t.Errorf("additional for %q contained %d results, %d expected", tc.Qname, len(resp.Extra), len(tc.Extra)) + t.Logf("%v\n", resp) + continue + } + + if !coretest.CheckSection(t, tc, coretest.Answer, resp.Answer) { + t.Logf("%v\n", resp) + } + if !coretest.CheckSection(t, tc, coretest.Ns, resp.Ns) { + t.Logf("%v\n", resp) + } + if !coretest.CheckSection(t, tc, coretest.Extra, resp.Extra) { + t.Logf("%v\n", resp) + } + } +} + +const dbMiekNL_wildcard_signed = ` +; File written on Tue Mar 29 21:02:24 2016 +; dnssec_signzone version 9.10.3-P4-Ubuntu +dnssex.nl. 1800 IN SOA linode.atoom.net. miek.miek.nl. ( + 1459281744 ; serial + 14400 ; refresh (4 hours) + 3600 ; retry (1 hour) + 604800 ; expire (1 week) + 14400 ; minimum (4 hours) + ) + 1800 RRSIG SOA 8 2 1800 ( + 20160428190224 20160329190224 14460 dnssex.nl. + CA/Y3m9hCOiKC/8ieSOv8SeP964BUdG/8MC3 + WtKljUosK9Z9bBGrVizDjjqgq++lyH8BZJcT + aabAsERs4xj5PRtcxicwQXZACX5VYjXHQeZm + CyytFU5wq2gcXSmvUH86zZzftx3RGPvn1aOo + TlcvoC3iF8fYUCpROlUS0YR8Cdw= ) + 1800 NS omval.tednet.nl. + 1800 NS linode.atoom.net. + 1800 NS ns-ext.nlnetlabs.nl. + 1800 RRSIG NS 8 2 1800 ( + 20160428190224 20160329190224 14460 dnssex.nl. + dLIeEvP86jj5nd3orv9bH7hTvkblF4Na0sbl + k6fJA6ha+FPN1d6Pig3NNEEVQ/+wlOp/JTs2 + v07L7roEEUCbBprI8gMSld2gFDwNLW3DAB4M + WD/oayYdAnumekcLzhgvWixTABjWAGRTGQsP + sVDFXsGMf9TGGC9FEomgkCVeNC0= ) + 1800 A 139.162.196.78 + 1800 RRSIG A 8 2 1800 ( + 20160428190224 20160329190224 14460 dnssex.nl. + LKJKLzPiSEDWOLAag2YpfD5EJCuDcEAJu+FZ + Xy+4VyOv9YvRHCTL4vbrevOo5+XymY2RxU1q + j+6leR/Fe7nlreSj2wzAAk2bIYn4m6r7hqeO + aKZsUFfpX8cNcFtGEywfHndCPELbRxFeEziP + utqHFLPNMX5nYCpS28w4oJ5sAnM= ) + 1800 TXT "Doing It Safe Is Better" + 1800 RRSIG TXT 8 2 1800 ( + 20160428190224 20160329190224 14460 dnssex.nl. + f6S+DUfJK1UYdOb3AHgUXzFTTtu+yLp/Fv7S + Hv0CAGhXAVw+nBbK719igFvBtObS33WKwzxD + 1pQNMaJcS6zeevtD+4PKB1KDC4fyJffeEZT6 + E30jGR8Y29/xA+Fa4lqDNnj9zP3b8TiABCle + ascY5abkgWCALLocFAzFJQ/27YQ= ) + 1800 AAAA 2a01:7e00::f03c:91ff:fef1:6735 + 1800 RRSIG AAAA 8 2 1800 ( + 20160428190224 20160329190224 14460 dnssex.nl. + PWcPSawEUBAfCuv0liEOQ8RYe7tfNW4rubIJ + LE+dbrub1DUer3cWrDoCYFtOufvcbkYJQ2CQ + AGjJmAQ5J2aqYDOPMrKa615V0KT3ifbZJcGC + gkIic4U/EXjaQpRoLdDzR9MyVXOmbA6sKYzj + ju1cNkLqM8D7Uunjl4pIr6rdSFo= ) + 14400 NSEC *.dnssex.nl. A NS SOA TXT AAAA RRSIG NSEC DNSKEY + 14400 RRSIG NSEC 8 2 14400 ( + 20160428190224 20160329190224 14460 dnssex.nl. + oIvM6JZIlNc1aNKGTxv58ApSnDr1nDPPgnD9 + 9oJZRIn7eb5WnpeDz2H3z5+x6Bhlp5hJJaUp + KJ3Ss6Jg/IDnrmIvKmgq6L6gHj1Y1IiHmmU8 + VeZTRzdTsDx/27OsN23roIvsytjveNSEMfIm + iLZ23x5kg1kBdJ9p3xjYHm5lR+8= ) + 1800 DNSKEY 256 3 8 ( + AwEAAazSO6uvLPEVknDA8yxjFe8nnAMU7txp + wb19k55hQ81WV3G4bpBM1NdN6sbYHrkXaTNx + 2bQWAkvX6pz0XFx3z/MPhW+vkakIWFYpyQ7R + AT5LIJfToVfiCDiyhhF0zVobKBInO9eoGjd9 + BAW3TUt+LmNAO/Ak5D5BX7R3CuA7v9k7 + ) ; ZSK; alg = RSASHA256; key id = 14460 + 1800 DNSKEY 257 3 8 ( + AwEAAbyeaV9zg0IqdtgYoqK5jJ239anzwG2i + gvH1DxSazLyaoNvEkCIvPgMLW/JWfy7Z1mQp + SMy9DtzL5pzRyQgw7kIeXLbi6jufUFd9pxN+ + xnzKLf9mY5AcnGToTrbSL+jnMT67wG+c34+Q + PeVfucHNUePBxsbz2+4xbXiViSQyCQGv + ) ; KSK; alg = RSASHA256; key id = 18772 + 1800 RRSIG DNSKEY 8 2 1800 ( + 20160428190224 20160329190224 14460 dnssex.nl. + cFSFtJE+DBGNxb52AweFaVHBe5Ue5MDpqNdC + TIneUnEhP2m+vK4zJ/TraK0WdQFpsX63pod8 + PZ9y03vHUfewivyonCCBD3DcNdoU9subhN22 + tez9Ct8Z5/9E4RAz7orXal4M1VUEhRcXSEH8 + SJW20mfVsqJAiKqqNeGB/pAj23I= ) + 1800 RRSIG DNSKEY 8 2 1800 ( + 20160428190224 20160329190224 18772 dnssex.nl. + oiiwo/7NYacePqohEp50261elhm6Dieh4j2S + VZGAHU5gqLIQeW9CxKJKtSCkBVgUo4cvO4Rn + 2tzArAuclDvBrMXRIoct8u7f96moeFE+x5FI + DYqICiV6k449ljj9o4t/5G7q2CRsEfxZKpTI + A/L0+uDk0RwVVzL45+TnilcsmZs= ) +*.dnssex.nl. 1800 IN TXT "Doing It Safe Is Better" + 1800 RRSIG TXT 8 2 1800 ( + 20160428190224 20160329190224 14460 dnssex.nl. + FUZSTyvZfeuuOpCmNzVKOfITRHJ6/ygjmnnb + XGBxVUyQjoLuYXwD5XqZWGw4iKH6QeSDfGCx + 4MPqA4qQmW7Wwth7mat9yMfA4+p2sO84bysl + 7/BG9+W2G+q1uQiM9bX9V42P2X/XuW5Y/t9Y + 8u1sljQ7D8WwS6naH/vbaJxnDBw= ) + 14400 NSEC a.dnssex.nl. TXT RRSIG NSEC + 14400 RRSIG NSEC 8 2 14400 ( + 20160428190224 20160329190224 14460 dnssex.nl. + os6INm6q2eXknD5z8TpfbK00uxVbQefMvHcR + /RNX/kh0xXvzAaaDOV+Ge/Ko+2dXnKP+J1LY + G9ffXNpdbaQy5ygzH5F041GJst4566GdG/jt + 7Z7vLHYxEBTpZfxo+PLsXQXH3VTemZyuWyDf + qJzafXJVH1F0nDrcXmMlR6jlBHA= ) +www.dnssex.nl. 1800 IN CNAME a.dnssex.nl. + 1800 RRSIG CNAME 8 3 1800 ( + 20160428190224 20160329190224 14460 dnssex.nl. + Omv42q/uVvdNsWQoSrQ6m6w6U7r7Abga7uF4 + 25b3gZlse0C+WyMyGFMGUbapQm7azvBpreeo + uKJHjzd+ufoG+Oul6vU9vyoj+ejgHzGLGbJQ + HftfP+UqP5SWvAaipP/LULTWKPuiBcLDLiBI + PGTfsq0DB6R+qCDTV0fNnkgxEBQ= ) + 14400 NSEC dnssex.nl. CNAME RRSIG NSEC + 14400 RRSIG NSEC 8 3 14400 ( + 20160428190224 20160329190224 14460 dnssex.nl. + TBN3ddfZW+kC84/g3QlNNJMeLZoyCalPQylt + KXXLPGuxfGpl3RYRY8KaHbP+5a8MnHjqjuMB + Lofb7yKMFxpSzMh8E36vnOqry1mvkSakNj9y + 9jM8PwDjcpYUwn/ql76MsmNgEV5CLeQ7lyH4 + AOrL79yOSQVI3JHJIjKSiz88iSw= ) +a.dnssex.nl. 1800 IN A 139.162.196.78 + 1800 RRSIG A 8 3 1800 ( + 20160428190224 20160329190224 14460 dnssex.nl. + OXHpFj9nSpKi5yA/ULH7MOpGAWfyJ2yC/2xa + Pw0fqSY4QvcRt+V3adcFA4H9+P1b32GpxEjB + lXmCJID+H4lYkhUR4r4IOZBVtKG2SJEBZXip + pH00UkOIBiXxbGzfX8VL04v2G/YxUgLW57kA + aknaeTOkJsO20Y+8wmR9EtzaRFI= ) + 1800 AAAA 2a01:7e00::f03c:91ff:fef1:6735 + 1800 RRSIG AAAA 8 3 1800 ( + 20160428190224 20160329190224 14460 dnssex.nl. + jrepc/VnRzJypnrG0WDEqaAr3HMjWrPxJNX0 + 86gbFjZG07QxBmrA1rj0jM9YEWTjjyWb2tT7 + lQhzKDYX/0XdOVUeeOM4FoSks80V+pWR8fvj + AZ5HmX69g36tLosMDKNR4lXcrpv89QovG4Hr + /r58fxEKEFJqrLDjMo6aOrg+uKA= ) + 14400 NSEC www.dnssex.nl. A AAAA RRSIG NSEC + 14400 RRSIG NSEC 8 3 14400 ( + 20160428190224 20160329190224 14460 dnssex.nl. + S+UM62wXRNNFN3QDWK5YFWUbHBXC4aqaqinZ + A2ZDeC+IQgyw7vazPz7cLI5T0YXXks0HTMlr + soEjKnnRZsqSO9EuUavPNE1hh11Jjm0fB+5+ + +Uro0EmA5Dhgc0Z2VpbXVQEhNDf/pI1gem15 + RffN2tBYNykZn4Has2ySgRaaRYQ= )` diff --git a/middleware/testing/helpers.go b/middleware/testing/helpers.go index f87025896..dc0fa4c38 100644 --- a/middleware/testing/helpers.go +++ b/middleware/testing/helpers.go @@ -27,11 +27,26 @@ type Case struct { Qname string Qtype uint16 Rcode int + Do bool Answer []dns.RR Ns []dns.RR Extra []dns.RR } +func (c Case) Msg() *dns.Msg { + m := new(dns.Msg) + m.SetQuestion(dns.Fqdn(c.Qname), c.Qtype) + if c.Do { + o := new(dns.OPT) + o.Hdr.Name = "." + o.Hdr.Rrtype = dns.TypeOPT + o.SetDo() + o.SetUDPSize(4096) + m.Extra = []dns.RR{o} + } + return m +} + func A(rr string) *dns.A { r, _ := dns.NewRR(rr); return r.(*dns.A) } func AAAA(rr string) *dns.AAAA { r, _ := dns.NewRR(rr); return r.(*dns.AAAA) } func CNAME(rr string) *dns.CNAME { r, _ := dns.NewRR(rr); return r.(*dns.CNAME) } @@ -41,6 +56,8 @@ func NS(rr string) *dns.NS { r, _ := dns.NewRR(rr); return r.(*dns.NS) } func PTR(rr string) *dns.PTR { r, _ := dns.NewRR(rr); return r.(*dns.PTR) } func TXT(rr string) *dns.TXT { r, _ := dns.NewRR(rr); return r.(*dns.TXT) } func MX(rr string) *dns.MX { r, _ := dns.NewRR(rr); return r.(*dns.MX) } +func RRSIG(rr string) *dns.RRSIG { r, _ := dns.NewRR(rr); return r.(*dns.RRSIG) } +func NSEC(rr string) *dns.NSEC { r, _ := dns.NewRR(rr); return r.(*dns.NSEC) } func CheckSection(t *testing.T, tc Case, sect Section, rr []dns.RR) bool { section := []dns.RR{} @@ -86,6 +103,25 @@ func CheckSection(t *testing.T, tc Case, sect Section, rr []dns.RR) bool { t.Errorf("rr %d should have a Target of %q, but has %q", i, section[i].(*dns.SRV).Target, x.Target) return false } + case *dns.RRSIG: + if x.TypeCovered != section[i].(*dns.RRSIG).TypeCovered { + t.Errorf("rr %d should have a TypeCovered of %d, but has %d", i, section[i].(*dns.RRSIG).TypeCovered, x.TypeCovered) + return false + } + if x.Labels != section[i].(*dns.RRSIG).Labels { + t.Errorf("rr %d should have a Labels of %d, but has %d", i, section[i].(*dns.RRSIG).Labels, x.Labels) + return false + } + if x.SignerName != section[i].(*dns.RRSIG).SignerName { + t.Errorf("rr %d should have a SignerName of %d, but has %d", i, section[i].(*dns.RRSIG).SignerName, x.SignerName) + return false + } + case *dns.NSEC: + if x.NextDomain != section[i].(*dns.NSEC).NextDomain { + t.Errorf("rr %d should have a NextDomain of %d, but has %d", i, section[i].(*dns.NSEC).NextDomain, x.NextDomain) + return false + } + // TypeBitMap case *dns.A: if x.A.String() != section[i].(*dns.A).A.String() { t.Errorf("rr %d should have a Address of %q, but has %q", i, section[i].(*dns.A).A.String(), x.A.String()) |