From 48f7d55f27518d31d5a316669e6ad282b134082c Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Mon, 28 Mar 2016 21:18:16 +0100 Subject: [PATCH] Get positive dnssec stuff going --- middleware/etcd/cname_test.go | 3 +- middleware/etcd/group_test.go | 3 +- middleware/etcd/multi_test.go | 3 +- middleware/etcd/other_test.go | 3 +- middleware/etcd/setup_test.go | 3 +- middleware/file/dnssec_test.go | 25 ++++++++----- middleware/file/file.go | 5 +++ middleware/file/lookup.go | 67 +++++++++++++++++++++++++--------- middleware/file/lookup_test.go | 4 +- middleware/file/tree/tree.go | 39 ++++++++------------ middleware/testing/helpers.go | 36 ++++++++++++++++++ 11 files changed, 126 insertions(+), 65 deletions(-) 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/dnssec_test.go b/middleware/file/dnssec_test.go index 57cce90c5..4b6888a82 100644 --- a/middleware/file/dnssec_test.go +++ b/middleware/file/dnssec_test.go @@ -14,44 +14,50 @@ 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, + Qname: "a.miek.nl.", Qtype: dns.TypeSRV, Do: true, Ns: []dns.RR{ + 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"), }, }, + /* HAHA nsec... shit. { Qname: "b.miek.nl.", Qtype: dns.TypeA, Rcode: dns.RcodeNameError, @@ -59,10 +65,10 @@ var dnssecTestCases = []coretest.Case{ 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 +78,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..2e04dbe92 100644 --- a/middleware/file/file.go +++ b/middleware/file/file.go @@ -78,6 +78,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..20ec1ce00 100644 --- a/middleware/file/lookup.go +++ b/middleware/file/lookup.go @@ -14,16 +14,17 @@ const ( // 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 var rr dns.RR mk, known := dns.TypeToRR[qtype] if !known { - return nil, nil, NameError - // Uhm...? - // rr = new(RFC3597) + an, ad, _ := z.lookupSOA(do) + return an, ad, NameError + // Uhm...? rr = new(RFC3597) ?? } else { rr = mk() } + rr.Header().Rrtype = qtype // this is pretty nonobvious + if qtype == dns.TypeSOA { return z.lookupSOA(do) } @@ -31,38 +32,68 @@ func (z *Zone) Lookup(qname string, qtype uint16, do bool) ([]dns.RR, []dns.RR, rr.Header().Name = qname elem := z.Tree.Get(rr) if elem == nil { - return []dns.RR{z.SOA}, nil, NameError + an, ad, _ := z.lookupSOA(do) + return an, ad, NameError } + 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 + an, ad, _ := z.lookupSOA(do) + return an, ad, NoData + } + 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 } func (z *Zone) lookupSOA(do bool) ([]dns.RR, []dns.RR, Result) { + if do { + ret := append([]dns.RR{z.SOA}, z.SIG...) + return ret, nil, Success + } return []dns.RR{z.SOA}, nil, Success } +func (z *Zone) lookupCNAME(rrs []dns.RR, rr dns.RR, do bool) ([]dns.RR, []dns.RR, Result) { + elem := z.Tree.Get(rr) + if elem == nil { + return rrs, 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, 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 (z *Zone) signatureForSubType(rrs []dns.RR, subtype uint16, do bool) []dns.RR { - if !do { - return nil - } +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..5e755f040 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"), }, }, { @@ -73,8 +72,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..7f51b89f0 100644 --- a/middleware/file/tree/tree.go +++ b/middleware/file/tree/tree.go @@ -47,7 +47,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 +56,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 +65,7 @@ func (e *Elem) All() []dns.RR { return list } +// 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 { @@ -130,6 +132,7 @@ func Less(a *Elem, rr dns.RR) int { // 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 +262,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 +289,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 +339,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 +367,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 +398,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 +417,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 +464,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 +478,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,7 +492,7 @@ func (n *Node) max() *Node { return n } -// Floor returns the greatest value equal to or less than the query q according to q.Compare(). +// Floor returns the greatest value equal to or less than the rr according to Less(). func (t *Tree) Floor(rr dns.RR) *Elem { if t.Root == nil { return nil @@ -528,9 +521,7 @@ 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(). +// Ceil returns the smallest value equal to or greater than the rr according to Less(). func (t *Tree) Ceil(rr dns.RR) *Elem { if t.Root == nil { return nil 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())