diff --git a/LICENSE b/LICENSE index c9982ca7..5763fa7f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Extensions of the original work are copyright (c) 2010 - 2012 Miek Gieben +Extensions of the original work are copyright (c) 2011 Miek Gieben As this is fork of the official Go code the same license applies: diff --git a/README.markdown b/README.markdown index c550f622..348263eb 100644 --- a/README.markdown +++ b/README.markdown @@ -33,8 +33,8 @@ A not-so-up-to-date-list-that-may-be-actually-current: * UDP/TCP queries, IPv4 and IPv6; * RFC 1035 zone file parsing ($INCLUDE, $ORIGIN, $TTL and $GENERATE [for all record types] are supported); -* Fast: - * Reply speed around ~ 50K qps (faster hardware results in more qps); +* Fast: + * Reply speed around ~ 80K qps (faster hardware results in more qps); * Parsing RRs with ~ 100K RR/s, that's 5M records in about 50 seconds; * Server side programming (mimicking the net/http package); * Client side programming; diff --git a/client.go b/client.go index 69bd1921..2482c58c 100644 --- a/client.go +++ b/client.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dns // A client implementation. diff --git a/client_test.go b/client_test.go index a2c895fc..823dae56 100644 --- a/client_test.go +++ b/client_test.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dns import ( diff --git a/clientconfig.go b/clientconfig.go index ed77e4f0..32d3fca7 100644 --- a/clientconfig.go +++ b/clientconfig.go @@ -1,6 +1,7 @@ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// Extensions of the original work are copyright (c) 2011 Miek Gieben package dns diff --git a/defaults.go b/defaults.go index ed3168a3..a190bbfa 100644 --- a/defaults.go +++ b/defaults.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dns import ( @@ -102,7 +106,7 @@ func (dns *Msg) SetAxfr(z string) *Msg { } // SetTsig appends a TSIG RR to the message. -// This is only a skeleton TSIG RR that is added as the last RR in the +// This is only a skeleton TSIG RR that is added as the last RR in the // additional section. The Tsig is calculated when the message is being send. func (dns *Msg) SetTsig(z, algo string, fudge, timesigned int64) *Msg { t := new(TSIG) @@ -115,7 +119,7 @@ func (dns *Msg) SetTsig(z, algo string, fudge, timesigned int64) *Msg { return dns } -// SetEdns0 appends a EDNS0 OPT RR to the message. +// SetEdns0 appends a EDNS0 OPT RR to the message. // TSIG should always the last RR in a message. func (dns *Msg) SetEdns0(udpsize uint16, do bool) *Msg { e := new(OPT) @@ -153,7 +157,7 @@ func (dns *Msg) IsEdns0() *OPT { } // IsDomainName checks if s is a valid domainname, it returns -// the number of labels, total length and true, when a domain name is valid. +// the number of labels, total length and true, when a domain name is valid. // When false is returned the labelcount and length are not defined. func IsDomainName(s string) (uint8, uint8, bool) { // copied from net package. // TODO(mg): check for \DDD @@ -250,9 +254,9 @@ func Fqdn(s string) string { // Copied from the official Go code -// ReverseAddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP -// address addr suitable for rDNS (PTR) record lookup or an error if it fails -// to parse the IP address. +// ReverseAddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP +// address addr suitable for rDNS (PTR) record lookup or an error if it fails +// to parse the IP address. func ReverseAddr(addr string) (arpa string, err error) { ip := net.ParseIP(addr) if ip == nil { @@ -262,9 +266,9 @@ func ReverseAddr(addr string) (arpa string, err error) { return strconv.Itoa(int(ip[15])) + "." + strconv.Itoa(int(ip[14])) + "." + strconv.Itoa(int(ip[13])) + "." + strconv.Itoa(int(ip[12])) + ".in-addr.arpa.", nil } - // Must be IPv6 + // Must be IPv6 buf := make([]byte, 0, len(ip)*4+len("ip6.arpa.")) - // Add it, in reverse, to the buffer + // Add it, in reverse, to the buffer for i := len(ip) - 1; i >= 0; i-- { v := ip[i] buf = append(buf, hexDigit[v&0xF]) @@ -272,7 +276,7 @@ func ReverseAddr(addr string) (arpa string, err error) { buf = append(buf, hexDigit[v>>4]) buf = append(buf, '.') } - // Append "ip6.arpa." and return (buf already has the final .) + // Append "ip6.arpa." and return (buf already has the final .) buf = append(buf, "ip6.arpa."...) return string(buf), nil } diff --git a/dns.go b/dns.go index b645919b..050e7297 100644 --- a/dns.go +++ b/dns.go @@ -1,14 +1,14 @@ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Extended and bugfixes by Miek Gieben. Copyright 2010-2012. +// Extensions of the original work are copyright (c) 2011 Miek Gieben // Package dns implements a full featured interface to the Domain Name System. // Server- and client-side programming is supported. // The package allows complete control over what is send out to the DNS. The package // API follows the less-is-more principle, by presenting a small, clean interface. // -// The package dns supports (asynchronous) querying/replying, incoming/outgoing AXFR/IXFR, +// The package dns supports (asynchronous) querying/replying, incoming/outgoing AXFR/IXFR, // TSIG, EDNS0, dynamic updates, notifies and DNSSEC validation/signing. // Note that domain names MUST be fully qualified, before sending them, unqualified // names in a message will result in a packing failure. @@ -68,7 +68,7 @@ // the authority section: in.Ns and the additional section: in.Extra. // // Each of these sections (except the Question section) contain a []RR. Basic -// use pattern for accessing the rdata of a TXT RR as the first RR in +// use pattern for accessing the rdata of a TXT RR as the first RR in // the Answer section: // // if t, ok := in.Answer[0].(*TXT); ok { diff --git a/dns_test.go b/dns_test.go index 8638e906..df3d48eb 100644 --- a/dns_test.go +++ b/dns_test.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dns import ( diff --git a/dnssec.go b/dnssec.go index 1541beb8..ec27bf23 100644 --- a/dnssec.go +++ b/dnssec.go @@ -1,4 +1,6 @@ -// Copyright 2012 Miek Gieben. All rights reserved. +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. // DNSSEC // diff --git a/dnssec_test.go b/dnssec_test.go index 3179c241..9456744f 100644 --- a/dnssec_test.go +++ b/dnssec_test.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dns import ( @@ -220,7 +224,7 @@ Coefficient: UuRoNqe7YHnKmQzE6iDWKTMIWTuoqqrFAmXPmKQnC+Y+BQzOVEHUo9bXdDnoI9hzXP1 /* return // This key was generate with LDNS: - // ldns-keygen -a RSASHA256 -r /dev/urandom -b 1024 miek.nl + // ldns-keygen -a RSASHA256 -r /dev/urandom -b 1024 miek.nl // Show that we have al the RSA parameters and can check them // here to see what I came up with key := new(RR_DNSKEY) diff --git a/dyn_test.go b/dyn_test.go index 09986a5e..3868cb0c 100644 --- a/dyn_test.go +++ b/dyn_test.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dns // Find better solution diff --git a/edns.go b/edns.go index a743c8be..2cdbfcb6 100644 --- a/edns.go +++ b/edns.go @@ -1,8 +1,12 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + // EDNS0 // // EDNS0 is an extension mechanism for the DNS defined in RFC 2671 and updated -// by RFC 6891. It defines a -// standard RR type, the OPT RR, which is then completely abused. +// by RFC 6891. It defines a standard RR type, the OPT RR, which is then completely +// abused. // Basic use pattern for creating an (empty) OPT RR: // // o := new(dns.OPT) diff --git a/ex/chaos/chaos.go b/ex/chaos/chaos.go index acd46e05..4979f557 100644 --- a/ex/chaos/chaos.go +++ b/ex/chaos/chaos.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + // Chaos is a small program that prints the version.bind and hostname.bind // for each address of the nameserver given as argument. package main diff --git a/ex/check-soa/check-soa.go b/ex/check-soa/check-soa.go index 0242eb36..3d3a0938 100644 --- a/ex/check-soa/check-soa.go +++ b/ex/check-soa/check-soa.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + // Go equivalent of the "DNS & BIND" book check-soa program. package main diff --git a/ex/q/q.go b/ex/q/q.go index ba12a5e7..d3a4de59 100644 --- a/ex/q/q.go +++ b/ex/q/q.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + // Q is a small utility which acts and behaves like 'dig' from BIND. // It is meant to stay lean and mean, while having a bunch of handy // features, like -check which checks if a packet is correctly signed (without diff --git a/ex/reflect/reflect.go b/ex/reflect/reflect.go index 1c976819..437b070c 100644 --- a/ex/reflect/reflect.go +++ b/ex/reflect/reflect.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + // Reflect is a small name server which sends back the IP address of its client, the // recursive resolver. // When queried for type A (resp. AAAA), it sends back the IPv4 (resp. v6) address. diff --git a/example_test.go b/example_test.go index dc2283d7..a6d9af4b 100644 --- a/example_test.go +++ b/example_test.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dns import ( diff --git a/keygen.go b/keygen.go index bcb74bc7..a5ef00c8 100644 --- a/keygen.go +++ b/keygen.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dns import ( @@ -17,7 +21,7 @@ const _FORMAT = "Private-key-format: v1.3\n" type PrivateKey interface{} // Generate generates a DNSKEY of the given bit size. -// The public part is put inside the DNSKEY record. +// The public part is put inside the DNSKEY record. // The Algorithm in the key must be set as this will define // what kind of DNSKEY will be generated. // The ECDSA algorithms imply a fixed keysize, in that case @@ -88,7 +92,7 @@ func (r *DNSKEY) Generate(bits int) (PrivateKey, error) { } // PrivateKeyString converts a PrivateKey to a string. This -// string has the same format as the private-key-file of BIND9 (Private-key-format: v1.3). +// string has the same format as the private-key-file of BIND9 (Private-key-format: v1.3). // It needs some info from the key (hashing, keytag), so its a method of the DNSKEY. func (r *DNSKEY) PrivateKeyString(p PrivateKey) (s string) { switch t := p.(type) { diff --git a/kscan.go b/kscan.go index 446e8811..95d5a01c 100644 --- a/kscan.go +++ b/kscan.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dns import ( @@ -10,13 +14,13 @@ import ( ) func (k *DNSKEY) NewPrivateKey(s string) (PrivateKey, error) { - if s[len(s)-1] != '\n' { // We need a closing newline + if s[len(s)-1] != '\n' { // We need a closing newline return k.ReadPrivateKey(strings.NewReader(s+"\n"), "") } return k.ReadPrivateKey(strings.NewReader(s), "") } -// NewPrivateKey reads a private key from the io.Reader q. The string file is +// NewPrivateKey reads a private key from the io.Reader q. The string file is // only used in error reporting. // The public key must be // known, because some cryptographics algorithms embed the public inside the privatekey. diff --git a/labels.go b/labels.go index 66de63d1..e9c6115e 100644 --- a/labels.go +++ b/labels.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dns // Holds a bunch of helper functions for dealing with labels. diff --git a/labels_test.go b/labels_test.go index e0ee1bd4..77f17f5e 100644 --- a/labels_test.go +++ b/labels_test.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dns import ( diff --git a/msg.go b/msg.go index f1b25205..b9f9baff 100644 --- a/msg.go +++ b/msg.go @@ -1,6 +1,7 @@ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// Extensions of the original work are copyright (c) 2011 Miek Gieben // DNS packet assembly, see RFC 1035. Converting from - Unpack() - // and to - Pack() - wire format. @@ -670,6 +671,7 @@ func structValue(any interface{}) reflect.Value { return reflect.ValueOf(any).Elem() } +// PackStruct packs any structure to wire format. func PackStruct(any interface{}, msg []byte, off int) (off1 int, err error) { off, err = packStructValue(structValue(any), msg, off, nil, false) return off, err @@ -1052,6 +1054,8 @@ func unpackUint16(msg []byte, off int) (v uint16, off1 int) { return } +// UnpackStruct unpacks a binary message from offset off to the interface +// value given. func UnpackStruct(any interface{}, msg []byte, off int) (off1 int, err error) { off, err = unpackStructValue(structValue(any), msg, off) return off, err diff --git a/nsec3_test.go b/nsec3_test.go index 37c45484..911ab382 100644 --- a/nsec3_test.go +++ b/nsec3_test.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dns import ( diff --git a/nsecx.go b/nsecx.go index 5311afaf..b832873b 100644 --- a/nsecx.go +++ b/nsecx.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dns import ( diff --git a/parse_test.go b/parse_test.go index 427c80d6..c67cf5c0 100644 --- a/parse_test.go +++ b/parse_test.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dns import ( @@ -141,28 +145,29 @@ func TestDotInName(t *testing.T) { } func TestParseZone(t *testing.T) { - zone := `z1.miek.nl. 86400 IN RRSIG NSEC 8 3 86400 20110823011301 20110724011301 12051 miek.nl. lyRljEQFOmajcdo6bBI67DsTlQTGU3ag9vlE07u7ynqt9aYBXyE9mkasAK4V0oI32YGb2pOSB6RbbdHwUmSt+cYhOA49tl2t0Qoi3pH21dicJiupdZuyjfqUEqJlQoEhNXGtP/pRvWjNA4pQeOsOAoWq/BDcWCSQB9mh2LvUOH4= ; {keyid = sksak} -z2.miek.nl. 86400 IN NSEC miek.nl. TXT RRSIG NSEC + zone := `z3.miek.nl. 86400 IN RRSIG NSEC 8 3 86400 20110823011301 20110724011301 12051 miek.nl. lyRljEQFOmajcdo6bBI67DsTlQTGU3ag9vlE07u7ynqt9aYBXyE9mkasAK4V0oI32YGb2pOSB6RbbdHwUmSt+cYhOA49tl2t0Qoi3pH21dicJiupdZuyjfqUEqJlQoEhNXGtP/pRvWjNA4pQeOsOAoWq/BDcWCSQB9mh2LvUOH4= ; {keyid = sksak} +z1.miek.nl. 86400 IN NSEC miek.nl. TXT RRSIG NSEC $TTL 100 -z3.miek.nl. IN NSEC miek.nl. TXT RRSIG NSEC` +z2.miek.nl. IN NSEC miek.nl. TXT RRSIG NSEC` to := ParseZone(strings.NewReader(zone), "", "") i := 0 + z := NewZone("miek.nl.") for x := range to { if x.Error == nil { switch i { - case 0: + case 1: if x.RR.Header().Name != "z1.miek.nl." { t.Log("Failed to parse z1") t.Fail() } - case 1: + case 2: if x.RR.Header().Name != "z2.miek.nl." { t.Log("Failed to parse z2") t.Fail() } - case 2: - if x.RR.String() != "z3.miek.nl.\t100\tIN\tNSEC\tmiek.nl. TXT RRSIG NSEC" { - t.Logf("Failed to parse z3 %s", x.RR.String()) + case 0: + if x.RR.Header().Name != "z3.miek.nl." { + t.Logf("Failed to parse z3 %s") t.Fail() } } @@ -171,6 +176,20 @@ z3.miek.nl. IN NSEC miek.nl. TXT RRSIG NSEC` t.Fail() } i++ + z.Insert(x.RR) + } + if len(z.sortedNames.nsecNames) != 3 { + t.Log("Length not 3?") + t.Fail() + } + if z.sortedNames.nsecNames[0] != "z1.miek.nl." || z.sortedNames.nsecNames[1] != "z2.miek.nl." || z.sortedNames.nsecNames[2] != "z3.miek.nl." { + t.Logf("Not all names correct %v\n", z.sortedNames.nsecNames) + t.Fail() + } + z.RemoveName("z2.miek.nl.") + if z.sortedNames.nsecNames[0] != "z1.miek.nl." || z.sortedNames.nsecNames[1] != "z3.miek.nl." { + t.Logf("Not all names correct %v\n", z.sortedNames.nsecNames) + t.Fail() } } diff --git a/rawmsg.go b/rawmsg.go index 05f89856..59e0d9cc 100644 --- a/rawmsg.go +++ b/rawmsg.go @@ -1,4 +1,4 @@ -// Copyright 2012 Miek Gieben. All rights reserved. +// Copyright 2011 Miek Gieben. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/scanner.go b/scanner.go index c29bc2f3..558c1b29 100644 --- a/scanner.go +++ b/scanner.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dns // Implement a simple scanner, return a byte stream from an io reader. diff --git a/server.go b/server.go index 67671a41..4935e3c5 100644 --- a/server.go +++ b/server.go @@ -7,7 +7,6 @@ package dns import ( - "github.com/miekg/radix" "net" "sync" "time" @@ -28,7 +27,7 @@ type ResponseWriter interface { Write([]byte) (int, error) // Close closes the connection. Close() error - // TsigStatus returns the status of the Tsig. + // TsigStatus returns the status of the Tsig. TsigStatus() error // TsigTimersOnly sets the tsig timers only boolean. TsigTimersOnly(bool) @@ -49,19 +48,19 @@ type response struct { } // ServeMux is an DNS request multiplexer. It matches the -// zone name of each incoming request against a list of +// zone name of each incoming request against a list of // registered patterns add calls the handler for the pattern // that most closely matches the zone name. ServeMux is DNSSEC aware, meaning // that queries for the DS record are redirected to the parent zone (if that // is also registered), otherwise the child gets the query. // ServeMux is also safe for concurrent access from multiple goroutines. type ServeMux struct { - r *radix.Radix + z map[string]Handler m *sync.RWMutex } // NewServeMux allocates and returns a new ServeMux. -func NewServeMux() *ServeMux { return &ServeMux{r: radix.New(), m: new(sync.RWMutex)} } +func NewServeMux() *ServeMux { return &ServeMux{z: make(map[string]Handler), m: new(sync.RWMutex)} } // DefaultServeMux is the default ServeMux used by Serve. var DefaultServeMux = NewServeMux() @@ -70,7 +69,7 @@ var DefaultServeMux = NewServeMux() var Authors = []string{"Miek Gieben", "Ask Bjørn Hansen", "Dave Cheney", "Dusty Wilson", "Peter van Dijk"} // Version holds the current version. -var Version = "v1.1" +var Version = "v1.2" // The HandlerFunc type is an adapter to allow the use of // ordinary functions as DNS handlers. If f is a function @@ -83,7 +82,7 @@ func (f HandlerFunc) ServeDNS(w ResponseWriter, r *Msg) { f(w, r) } -// FailedHandler returns a HandlerFunc +// FailedHandler returns a HandlerFunc // returns SERVFAIL for every request it gets. func HandleFailed(w ResponseWriter, r *Msg) { m := new(Msg) @@ -165,27 +164,40 @@ func ListenAndServe(addr string, network string, handler Handler) error { return server.ListenAndServe() } -func (mux *ServeMux) match(zone string, t uint16) Handler { +func (mux *ServeMux) match(q string, t uint16) Handler { mux.m.RLock() defer mux.m.RUnlock() - if h, e := mux.r.Find(toRadixName(zone)); e { - // If we got queried for a DS record, we must see if we - // if we also serve the parent. We then redirect the query to it. - if t != TypeDS { - return h.Value.(Handler) + var ( + handler Handler + lastdot int = -1 + lastbyte byte + seendot bool = true + ) + for i := 0; i < len(q); i++ { + if seendot { + if h, ok := mux.z[q[lastdot+1:]]; ok { + if t != TypeDS { + return h + } else { + // Continue for DS to see if we have a parent too, if so delegeate to the parent + handler = h + } + } } - if d := h.Up(); d != nil { - return d.Value.(Handler) + + if q[i] == '.' && lastbyte != '\\' { + lastdot = i + seendot = true + } else { + seendot = false } - // No parent zone found, let the original handler take care of it - return h.Value.(Handler) - } else { - if h == nil { - return nil - } - return h.Value.(Handler) + lastbyte = q[i] } - panic("dns: not reached") + // Wildcard match, if we have found nothing try the root zone as a last resort. + if h, ok := mux.z["."]; ok { + return h + } + return handler } // Handle adds a handler to the ServeMux for pattern. @@ -194,7 +206,7 @@ func (mux *ServeMux) Handle(pattern string, handler Handler) { panic("dns: invalid pattern " + pattern) } mux.m.Lock() - mux.r.Insert(toRadixName(Fqdn(pattern)), handler) + mux.z[Fqdn(pattern)] = handler mux.m.Unlock() } @@ -209,7 +221,7 @@ func (mux *ServeMux) HandleRemove(pattern string) { panic("dns: invalid pattern " + pattern) } mux.m.Lock() - mux.r.Remove(toRadixName(Fqdn(pattern))) + delete(mux.z, Fqdn(pattern)) mux.m.Unlock() } @@ -301,7 +313,7 @@ forever: for { rw, e := l.AcceptTCP() if e != nil { - // don't bail out, but wait for a new request + // don't bail out, but wait for a new request continue } if srv.ReadTimeout != 0 { diff --git a/server_test.go b/server_test.go index 0932567d..36dd61a9 100644 --- a/server_test.go +++ b/server_test.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dns import ( @@ -96,3 +100,13 @@ func TestDotAsCatchAllWildcard(t *testing.T) { t.Error("boe. match failed") } } + +func TestRootServer(t *testing.T) { + mux := NewServeMux() + mux.Handle(".", HandlerFunc(HelloServer)) + + handler := mux.match(".", TypeNS) + if handler == nil { + t.Error("root match failed") + } +} diff --git a/tlsa.go b/tlsa.go index 136b4b18..efcb28db 100644 --- a/tlsa.go +++ b/tlsa.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dns import ( @@ -62,7 +66,7 @@ func (r *TLSA) Sign(usage, selector, matchingType int, cert *x509.Certificate) ( func (r *TLSA) Verify(cert *x509.Certificate) error { c, err := CertificateToDANE(r.Selector, r.MatchingType, cert) if err != nil { - return err // Not also ErrSig? + return err // Not also ErrSig? } if r.Certificate == c { return nil diff --git a/tsig.go b/tsig.go index 39b649f3..26b307d9 100644 --- a/tsig.go +++ b/tsig.go @@ -1,10 +1,14 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + // TRANSACTION SIGNATURE (TSIG) // -// An TSIG or transaction signature adds a HMAC TSIG record to each message sent. +// An TSIG or transaction signature adds a HMAC TSIG record to each message sent. // The supported algorithms include: HmacMD5, HmacSHA1 and HmacSHA256. // // Basic use pattern when querying with a TSIG name "axfr." (note that these key names -// must be fully qualified - as they are domain names) and the base64 secret +// must be fully qualified - as they are domain names) and the base64 secret // "so6ZGir4GPAqINNh9U5c3A==": // // c := new(dns.Client) @@ -23,7 +27,7 @@ // c := new(dns.Client) // c.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} // m := new(dns.Msg) -// m.SetAxfr("miek.nl.") +// m.SetAxfr("miek.nl.") // m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix()) // t, err := c.TransferIn(m, "85.223.71.124:53") // for r := range t { /* ... */ } @@ -148,12 +152,12 @@ type timerWireFmt struct { // TsigGenerate fills out the TSIG record attached to the message. // The message should contain -// a "stub" TSIG RR with the algorithm, key name (owner name of the RR), +// a "stub" TSIG RR with the algorithm, key name (owner name of the RR), // time fudge (defaults to 300 seconds) and the current time // The TSIG MAC is saved in that Tsig RR. // When TsigGenerate is called for the first time requestMAC is set to the empty string and // timersOnly is false. -// If something goes wrong an error is returned, otherwise it is nil. +// If something goes wrong an error is returned, otherwise it is nil. func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, string, error) { if m.IsTsig() == nil { panic("dns: TSIG not last RR in additional") @@ -205,7 +209,7 @@ func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, s return mbuf, t.MAC, nil } -// TsigVerify verifies the TSIG on a message. +// TsigVerify verifies the TSIG on a message. // If the signature does not validate err contains the // error, otherwise it is nil. func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error { diff --git a/types.go b/types.go index 611607dc..8e95ef78 100644 --- a/types.go +++ b/types.go @@ -1,7 +1,7 @@ -// copyright 2009 The Go Authors. All rights reserved. +// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Extended and bugfixes by Miek Gieben +// Extensions of the original work are copyright (c) 2011 Miek Gieben package dns @@ -19,6 +19,7 @@ import ( // Wire constants and supported types. const ( // valid RR_Header.Rrtype and Question.qtype + TypeNone uint16 = 0 TypeA uint16 = 1 TypeNS uint16 = 2 TypeMD uint16 = 3 diff --git a/update.go b/update.go index 53986372..6e6f1947 100644 --- a/update.go +++ b/update.go @@ -1,12 +1,16 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + // DYNAMIC UPDATES -// +// // Dynamic updates reuses the DNS message format, but renames three of // the sections. Question is Zone, Answer is Prerequisite, Authority is // Update, only the Additional is not renamed. See RFC 2136 for the gory details. // // You can set a rather complex set of rules for the existence of absence of // certain resource records or names in a zone to specify if resource records -// should be added or removed. The table from RFC 2136 supplemented with the Go +// should be added or removed. The table from RFC 2136 supplemented with the Go // DNS function shows which functions exist to specify the prerequisites. // // 3.2.4 - Table Of Metavalues Used In Prerequisite Section @@ -18,21 +22,21 @@ // NONE ANY empty Name is not in use NameNotUsed // NONE rrset empty RRset does not exist RRsetNotUsed // zone rrset rr RRset exists (value dep) Used -// +// // The prerequisite section can also be left empty. // If you have decided on the prerequisites you can tell what RRs should // be added or deleted. The next table shows the options you have and // what functions to call. // // 3.4.2.6 - Table Of Metavalues Used In Update Section -// +// // CLASS TYPE RDATA Meaning Function // --------------------------------------------------------------- // ANY ANY empty Delete all RRsets from name RemoveName // ANY rrset empty Delete an RRset RemoveRRset // NONE rrset rr Delete an RR from RRset Remove // zone rrset rr Add to an RRset Insert -// +// package dns // NameUsed sets the RRs in the prereq section to diff --git a/xfr.go b/xfr.go index 1f0f588b..022abdf5 100644 --- a/xfr.go +++ b/xfr.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dns // Envelope is used when doing [IA]XFR with a remote server. @@ -127,7 +131,7 @@ func (w *reply) ixfrIn(q *Msg, c chan *Envelope) { panic("dns: not reached") } -// Check if he SOA record exists in the Answer section of +// Check if he SOA record exists in the Answer section of // the packet. If first is true the first RR must be a SOA // if false, the last one should be a SOA. func checkXfrSOA(in *Msg, first bool) bool { @@ -147,7 +151,7 @@ func checkXfrSOA(in *Msg, first bool) bool { // Errors are signaled via the error pointer, when an error occurs the function // sets the error and returns (it does not close the channel). // TSIG and enveloping is handled by TransferOut. -// +// // Basic use pattern for sending an AXFR: // // // q contains the AXFR request diff --git a/zgenerate.go b/zgenerate.go index f8635215..5f3c0a4d 100644 --- a/zgenerate.go +++ b/zgenerate.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dns import ( diff --git a/zone.go b/zone.go index 46840d7d..abea956a 100644 --- a/zone.go +++ b/zone.go @@ -1,10 +1,12 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dns // A structure for handling zone data import ( - "fmt" - "github.com/miekg/radix" "math/rand" "runtime" "sort" @@ -13,25 +15,42 @@ import ( "time" ) -// Zone represents a DNS zone. It's safe for concurrent use by +// TODO(mg): the memory footprint could be reduced when we would chop off the +// the zone's origin from every RR. However they are given to us as pointers +// and as such require copies when we fiddle with them... + +// Zone represents a DNS zone. It's safe for concurrent use by // multilpe goroutines. type Zone struct { - Origin string // Origin of the zone - olabels []string // origin cut up in labels, just to speed up the isSubDomain method - Wildcard int // Whenever we see a wildcard name, this is incremented - expired bool // Slave zone is expired - ModTime time.Time // When is the zone last modified - *radix.Radix // Zone data + Origin string // Origin of the zone + olen int // Origin length + olabels []string // Origin cut up in labels, just to speed up the isSubDomain method + expired bool // Slave zone is expired + ModTime time.Time // When is the zone last modified + Names map[string]*ZoneData // Zone data, indexed by owner name + *sortedNames // Sorted names for either NSEC or NSEC3 *sync.RWMutex + // The zone's security status, supported values are TypeNone for no DNSSEC, + // TypeNSEC for an NSEC type zone and TypeNSEC3 for an NSEC3 signed zone. + Security int } +type sortedNames struct { + // A sorted list of all names in the zone. + nsecNames []string + // A sorted list of all hashed names in the zone, the hash parameters are taken from the NSEC3PARAM + // record located in the zone's apex. + nsec3Names []string +} + +// Used for nsec(3) bitmap sorting type uint16Slice []uint16 func (p uint16Slice) Len() int { return len(p) } func (p uint16Slice) Less(i, j int) bool { return p[i] < p[j] } func (p uint16Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } -// SignatureConfig holds the parameters for zone (re)signing. This +// SignatureConfig holds the parameters for zone (re)signing. This // is copied from OpenDNSSEC. See: // https://wiki.opendnssec.org/display/DOCS/kasp.xml type SignatureConfig struct { @@ -40,7 +59,7 @@ type SignatureConfig struct { // When the end of the validity approaches, how much time should remain // before we start to resign. Typical value is 3 days. Refresh time.Duration - // Jitter is an random amount of time added or subtracted from the + // Jitter is an random amount of time added or subtracted from the // expiration time to ensure not all signatures expire a the same time. // Typical value is 12 hours, which means the actual jitter value is // between -12..0..+12. @@ -57,7 +76,7 @@ type SignatureConfig struct { // SignerRoutines specifies the number of signing goroutines, if not // set runtime.NumCPU() + 1 is used as the value. SignerRoutines int - // SOA Minttl value must be used as the ttl on NSEC/NSEC3 records. + // The zone's SOA Minttl value must be used as the ttl on NSEC/NSEC3 records. Minttl uint32 } @@ -65,13 +84,13 @@ func newSignatureConfig() *SignatureConfig { return &SignatureConfig{time.Duration(4*7*24) * time.Hour, time.Duration(3*24) * time.Hour, time.Duration(12) * time.Hour, time.Duration(300) * time.Second, true, runtime.NumCPU() + 1, 0} } -// DefaultSignaturePolicy has the following values. Validity is 4 weeks, +// DefaultSignaturePolicy has the following values. Validity is 4 weeks, // Refresh is set to 3 days, Jitter to 12 hours and InceptionOffset to 300 seconds. // HonorSepFlag is set to true, SignerRoutines is set to runtime.NumCPU() + 1. The // Minttl value is zero. var DefaultSignatureConfig = newSignatureConfig() -// NewZone creates an initialized zone with Origin set to origin. +// NewZone creates an initialized zone with Origin set to the lower cased origin. func NewZone(origin string) *Zone { if origin == "" { origin = "." @@ -81,74 +100,37 @@ func NewZone(origin string) *Zone { } z := new(Zone) z.Origin = Fqdn(strings.ToLower(origin)) + z.olen = len(z.Origin) z.olabels = SplitLabels(z.Origin) - z.Radix = radix.New() + z.Names = make(map[string]*ZoneData) z.RWMutex = new(sync.RWMutex) z.ModTime = time.Now().UTC() + z.sortedNames = &sortedNames{make([]string, 0), make([]string, 0)} return z } -// ZoneData holds all the RRs having their owner name equal to Name. +// In theory we can remove the ownernames from the RRs, because they are all the same, +// however, cutting the RR and possibly copying into a new structure requires memory too. +// For now: just leave the RRs as-is. + +// ZoneData holds all the RRs for a specific owner name. type ZoneData struct { - Name string // Domain name for this node - RR map[uint16][]RR // Map of the RR type to the RR - Signatures map[uint16][]*RRSIG // DNSSEC signatures for the RRs, stored under type covered - NonAuth bool // Always false, except for NSsets that differ from z.Origin - *sync.RWMutex + RR map[uint16][]RR // Map of the RR type to the RR + Signature map[uint16][]*RRSIG // DNSSEC signatures for the RRs, stored under type covered + NonAuth bool // Always false, except for NSsets that differ from z.Origin } // NewZoneData creates a new zone data element. -func NewZoneData(s string) *ZoneData { +func NewZoneData() *ZoneData { zd := new(ZoneData) - zd.Name = s zd.RR = make(map[uint16][]RR) - zd.Signatures = make(map[uint16][]*RRSIG) - zd.RWMutex = new(sync.RWMutex) + zd.Signature = make(map[uint16][]*RRSIG) return zd } -// toRadixName reverses a domain name so that when we store it in the radix tree -// we preserve the nsec ordering of the zone (this idea was stolen from NSD). -// Each label is also lowercased. -func toRadixName(d string) string { - if d == "" || d == "." { - return "." - } - s := "" - ld := len(d) - if d[ld-1] != '.' { - d = d + "." - ld++ - } - var lastdot int - var lastbyte byte - var lastlastbyte byte - for i := 0; i < len(d); i++ { - if d[i] == '.' { - switch { - case lastbyte != '\\': - fallthrough - case lastbyte == '\\' && lastlastbyte == '\\': - s = d[lastdot:i] + "." + s - lastdot = i + 1 - continue - } - } - lastlastbyte = lastbyte - lastbyte = d[i] - } - return "." + strings.ToLower(s[:len(s)-1]) -} - // String returns a string representation of a ZoneData. There is no // String for the entire zone, because this will (most likely) take up -// a huge amount of memory. Basic use pattern for printing an entire -// zone: -// -// // z contains the zone -// z.Radix.NextDo(func(i interface{}) { -// fmt.Printf("%s", i.(*dns.ZoneData).String()) }) -// +// a huge amount of memory. func (zd *ZoneData) String() string { var ( s string @@ -158,8 +140,8 @@ func (zd *ZoneData) String() string { // There is only one SOA, but it may have multiple sigs if soa, ok := zd.RR[TypeSOA]; ok { s += soa[0].String() + "\n" - if _, ok := zd.Signatures[TypeSOA]; ok { - for _, sig := range zd.Signatures[TypeSOA] { + if _, ok := zd.Signature[TypeSOA]; ok { + for _, sig := range zd.Signature[TypeSOA] { s += sig.String() + "\n" } } @@ -174,8 +156,8 @@ Types: } s += rr.String() + "\n" } - if _, ok := zd.Signatures[t]; ok { - for _, rr := range zd.Signatures[t] { + if _, ok := zd.Signature[t]; ok { + for _, rr := range zd.Signature[t] { s += rr.String() + "\n" } } @@ -184,8 +166,8 @@ Types: // There is only one NSEC, but it may have multiple sigs if soa, ok := zd.RR[TypeNSEC]; ok { s += soa[0].String() + "\n" - if _, ok := zd.Signatures[TypeNSEC]; ok { - for _, sig := range zd.Signatures[TypeNSEC] { + if _, ok := zd.Signature[TypeNSEC]; ok { + for _, sig := range zd.Signature[TypeNSEC] { s += sig.String() + "\n" } } @@ -193,29 +175,21 @@ Types: return s } -// Insert inserts the RR r into the zone. There is no check for duplicate data, although -// Remove will remove all duplicates. +// Insert inserts the RR r into the zone. func (z *Zone) Insert(r RR) error { + z.Lock() + defer z.Unlock() if !z.isSubDomain(r.Header().Name) { return &Error{Err: "out of zone data", Name: r.Header().Name} } - - key := toRadixName(r.Header().Name) - z.Lock() z.ModTime = time.Now().UTC() - zd, exact := z.Radix.Find(key) - if !exact { - // Not an exact match, so insert new value - defer z.Unlock() - // Check if it's a wildcard name - if len(r.Header().Name) > 1 && r.Header().Name[0] == '*' && r.Header().Name[1] == '.' { - z.Wildcard++ - } - zd := NewZoneData(r.Header().Name) + zd, ok := z.Names[r.Header().Name] + if !ok { + zd = NewZoneData() switch t := r.Header().Rrtype; t { case TypeRRSIG: sigtype := r.(*RRSIG).TypeCovered - zd.Signatures[sigtype] = append(zd.Signatures[sigtype], r.(*RRSIG)) + zd.Signature[sigtype] = append(zd.Signature[sigtype], r.(*RRSIG)) case TypeNS: // NS records with other names than z.Origin are non-auth if r.Header().Name != z.Origin { @@ -225,24 +199,25 @@ func (z *Zone) Insert(r RR) error { default: zd.RR[t] = append(zd.RR[t], r) } - z.Radix.Insert(key, zd) + z.Names[r.Header().Name] = zd + i := sort.SearchStrings(z.sortedNames.nsecNames, r.Header().Name) + z.sortedNames.nsecNames = append(z.sortedNames.nsecNames, "") + copy(z.sortedNames.nsecNames[i+1:], z.sortedNames.nsecNames[i:]) + z.sortedNames.nsecNames[i] = r.Header().Name return nil } - z.Unlock() - zd.Value.(*ZoneData).Lock() - defer zd.Value.(*ZoneData).Unlock() // Name already there switch t := r.Header().Rrtype; t { case TypeRRSIG: sigtype := r.(*RRSIG).TypeCovered - zd.Value.(*ZoneData).Signatures[sigtype] = append(zd.Value.(*ZoneData).Signatures[sigtype], r.(*RRSIG)) + zd.Signature[sigtype] = append(zd.Signature[sigtype], r.(*RRSIG)) case TypeNS: if r.Header().Name != z.Origin { - zd.Value.(*ZoneData).NonAuth = true + zd.NonAuth = true } fallthrough default: - zd.Value.(*ZoneData).RR[t] = append(zd.Value.(*ZoneData).RR[t], r) + zd.RR[t] = append(zd.RR[t], r) } return nil } @@ -250,61 +225,43 @@ func (z *Zone) Insert(r RR) error { // Remove removes the RR r from the zone. If the RR can not be found, // this is a no-op. func (z *Zone) Remove(r RR) error { - key := toRadixName(r.Header().Name) z.Lock() - z.ModTime = time.Now().UTC() - zd, exact := z.Radix.Find(key) - if !exact { - defer z.Unlock() + defer z.Unlock() + zd, ok := z.Names[r.Header().Name] + if !ok { return nil } - z.Unlock() - zd.Value.(*ZoneData).Lock() - defer zd.Value.(*ZoneData).Unlock() - remove := false + z.ModTime = time.Now().UTC() switch t := r.Header().Rrtype; t { case TypeRRSIG: sigtype := r.(*RRSIG).TypeCovered - for i, zr := range zd.Value.(*ZoneData).Signatures[sigtype] { + for i, zr := range zd.Signature[sigtype] { if r == zr { - zd.Value.(*ZoneData).Signatures[sigtype] = append(zd.Value.(*ZoneData).Signatures[sigtype][:i], zd.Value.(*ZoneData).Signatures[sigtype][i+1:]...) - remove = true + zd.Signature[sigtype] = append(zd.Signature[sigtype][:i], zd.Signature[sigtype][i+1:]...) } } - if remove { - // If every Signature of the covering type is removed, removed the type from the map - if len(zd.Value.(*ZoneData).Signatures[sigtype]) == 0 { - delete(zd.Value.(*ZoneData).Signatures, sigtype) - } + if len(zd.Signature[sigtype]) == 0 { + delete(zd.Signature, sigtype) } default: - for i, zr := range zd.Value.(*ZoneData).RR[t] { + for i, zr := range zd.RR[t] { // Matching RR if r == zr { - zd.Value.(*ZoneData).RR[t] = append(zd.Value.(*ZoneData).RR[t][:i], zd.Value.(*ZoneData).RR[t][i+1:]...) - remove = true + zd.RR[t] = append(zd.RR[t][:i], zd.RR[t][i+1:]...) } } - if remove { - // If every RR of this type is removed, removed the type from the map - if len(zd.Value.(*ZoneData).RR[t]) == 0 { - delete(zd.Value.(*ZoneData).RR, t) - } + if len(zd.RR[t]) == 0 { + delete(zd.RR, t) } } - if !remove { - return nil - } - - if len(r.Header().Name) > 1 && r.Header().Name[0] == '*' && r.Header().Name[1] == '.' { - z.Wildcard-- - if z.Wildcard < 0 { - z.Wildcard = 0 - } - } - if len(zd.Value.(*ZoneData).RR) == 0 && len(zd.Value.(*ZoneData).Signatures) == 0 { - // Entire node is empty, remove it from the Radix tree - z.Radix.Remove(key) + if len(zd.RR) == 0 && len(zd.Signature) == 0 { + // Entire node is empty, remove it from the Zone too + delete(z.Names, r.Header().Name) + i := sort.SearchStrings(z.sortedNames.nsecNames, r.Header().Name) + // we actually removed something if we are here, so i must be something sensible + copy(z.sortedNames.nsecNames[i:], z.sortedNames.nsecNames[i+1:]) + z.sortedNames.nsecNames[len(z.sortedNames.nsecNames)-1] = "" + z.sortedNames.nsecNames = z.sortedNames.nsecNames[:len(z.sortedNames.nsecNames)-1] } return nil } @@ -312,17 +269,18 @@ func (z *Zone) Remove(r RR) error { // RemoveName removes all the RRs with ownername matching s from the zone. Typical use of this // method is when processing a RemoveName dynamic update packet. func (z *Zone) RemoveName(s string) error { - key := toRadixName(s) z.Lock() - z.ModTime = time.Now().UTC() defer z.Unlock() - z.Radix.Remove(key) - if len(s) > 1 && s[0] == '*' && s[1] == '.' { - z.Wildcard-- - if z.Wildcard < 0 { - z.Wildcard = 0 - } + _, ok := z.Names[s] + if !ok { + return nil } + z.ModTime = time.Now().UTC() + delete(z.Names, s) + i := sort.SearchStrings(z.sortedNames.nsecNames, s) + copy(z.sortedNames.nsecNames[i:], z.sortedNames.nsecNames[i+1:]) + z.sortedNames.nsecNames[len(z.sortedNames.nsecNames)-1] = "" + z.sortedNames.nsecNames = z.sortedNames.nsecNames[:len(z.sortedNames.nsecNames)-1] return nil } @@ -330,83 +288,90 @@ func (z *Zone) RemoveName(s string) error { // Typical use of this method is when processing a RemoveRRset dynamic update packet. func (z *Zone) RemoveRRset(s string, t uint16) error { z.Lock() - z.ModTime = time.Now().UTC() - zd, exact := z.Radix.Find(toRadixName(s)) - if !exact { - defer z.Unlock() + defer z.Unlock() + zd, ok := z.Names[s] + if !ok { return nil } - z.Unlock() - zd.Value.(*ZoneData).Lock() - defer zd.Value.(*ZoneData).Unlock() + z.ModTime = time.Now().UTC() switch t { case TypeRRSIG: // empty all signature maps - for covert, _ := range zd.Value.(*ZoneData).Signatures { - delete(zd.Value.(*ZoneData).Signatures, covert) + for cover, _ := range zd.Signature { + delete(zd.Signature, cover) } default: // empty all rr maps - for t, _ := range zd.Value.(*ZoneData).RR { - delete(zd.Value.(*ZoneData).RR, t) + for t, _ := range zd.RR { + delete(zd.RR, t) } } + if len(zd.RR) == 0 && len(zd.Signature) == 0 { + // Entire node is empty, remove it from the Zone too + delete(z.Names, s) + i := sort.SearchStrings(z.sortedNames.nsecNames, s) + // we actually removed something if we are here, so i must be something sensible + copy(z.sortedNames.nsecNames[i:], z.sortedNames.nsecNames[i+1:]) + z.sortedNames.nsecNames[len(z.sortedNames.nsecNames)-1] = "" + z.sortedNames.nsecNames = z.sortedNames.nsecNames[:len(z.sortedNames.nsecNames)-1] + } return nil } -// Apex returns the zone's apex records (SOA, NS and possibly other). If the +// Apex returns the zone's apex records (SOA, NS and possibly others). If the // apex can not be found (thereby making it an illegal DNS zone) it returns nil. -// Updating the zone's SOA serial, provided the apex exists: -// -// z.Apex.RR[TypeSOA][0].(*SOA).Serial++ -// -// Note the a) this increment is not protected by locks and b) if you use DNSSEC -// you MUST resign the SOA record. +// Apex is safe for concurrent use. func (z *Zone) Apex() *ZoneData { - apex, e := z.Find(z.Origin) - if !e { - fmt.Printf("%#v\n", apex) + z.RLock() + defer z.RUnlock() + apex, ok := z.Names[z.Origin] + if !ok { return nil } return apex } -// Find looks up the ownername s in the zone and returns the -// data and true when an exact match is found. If an exact find isn't -// possible the first parent node with a non-nil Value is returned and -// the boolean is false. -func (z *Zone) Find(s string) (node *ZoneData, exact bool) { +// Find looks up the ownername s in the zone and returns the data or nil +// when nothing can be found. Find is safe for concurrent use. +func (z *Zone) Find(s string) *ZoneData { z.RLock() defer z.RUnlock() - n, e := z.Radix.Find(toRadixName(s)) - if n == nil { - return nil, false + node, ok := z.Names[s] + if !ok { + return nil } - node = n.Value.(*ZoneData) - exact = e - return -} - -// FindFunc works like Find, but the function f is executed on -// each node which has a non-nil Value during the tree traversal. -// If f returns true, that node is returned. -func (z *Zone) FindFunc(s string, f func(interface{}) bool) (*ZoneData, bool, bool) { - z.RLock() - defer z.RUnlock() - zd, e, b := z.Radix.FindFunc(toRadixName(s), f) - if zd == nil { - return nil, false, false - } - return zd.Value.(*ZoneData), e, b + return node } func (z *Zone) isSubDomain(child string) bool { return compareLabelsSlice(z.olabels, strings.ToLower(child)) == len(z.olabels) } -// Sign (re)signs the zone z with the given keys. -// NSECs and RRSIGs are added as needed. -// The public keys themselves are not added to the zone. +// compareLabels behaves exactly as CompareLabels expect that l1 is already +// a tokenize (in labels) version of the domain name. This saves memory and is faster. +func compareLabelsSlice(l1 []string, s2 string) (n int) { + l2 := SplitLabels(s2) + + x1 := len(l1) - 1 + x2 := len(l2) - 1 + for { + if x1 < 0 || x2 < 0 { + break + } + if l1[x1] == l2[x2] { + n++ + } else { + break + } + x1-- + x2-- + } + return +} + +// Sign (re)signs the zone z with the given keys. +// NSECs and RRSIGs are added as needed. +// The public keys themselves are not added to the zone. // If config is nil DefaultSignatureConfig is used. The signatureConfig // describes how the zone must be signed and if the SEP flag (for KSK) // should be honored. If signatures approach their expriration time, they @@ -427,42 +392,38 @@ func (z *Zone) Sign(keys map[*DNSKEY]PrivateKey, config *SignatureConfig) error if config == nil { config = DefaultSignatureConfig } - // Pre-calc the key tag + // Pre-calc the key tags keytags := make(map[*DNSKEY]uint16) for k, _ := range keys { keytags[k] = k.KeyTag() } errChan := make(chan error) - radChan := make(chan *radix.Radix, config.SignerRoutines*2) + zonChan := make(chan *ZoneData, config.SignerRoutines*2) // Start the signer goroutines wg := new(sync.WaitGroup) wg.Add(config.SignerRoutines) for i := 0; i < config.SignerRoutines; i++ { - go signerRoutine(wg, keys, keytags, config, radChan, errChan) + go signerRoutine(z, wg, keys, keytags, config, zonChan, errChan) } - apex, e := z.Radix.Find(toRadixName(z.Origin)) - if !e { - return ErrSoa - } - config.Minttl = apex.Value.(*ZoneData).RR[TypeSOA][0].(*SOA).Minttl - next := apex.Next() - radChan <- apex - var err error + apex := z.Apex() + if apex == nil { + return ErrSoa + } + config.Minttl = apex.RR[TypeSOA][0].(*SOA).Minttl Sign: - for next.Value.(*ZoneData).Name != z.Origin { + for name := range z.Names { select { case err = <-errChan: break Sign default: - radChan <- next - next = next.Next() + zonChan <- z.Names[name] } } - close(radChan) + close(zonChan) close(errChan) if err != nil { return err @@ -471,16 +432,36 @@ Sign: return nil } +// Sign3 (re)signs the zone z with the given keys, NSEC3s and RRSIGs are +// added as needed. Bla bla Identical to zone.Sign. +func (z *Zone) Sign3(keys map[*DNSKEY]PrivateKey, config *SignatureConfig) error { + return nil +} + // signerRoutine is a small helper routine to make the concurrent signing work. -func signerRoutine(wg *sync.WaitGroup, keys map[*DNSKEY]PrivateKey, keytags map[*DNSKEY]uint16, config *SignatureConfig, in chan *radix.Radix, err chan error) { +func signerRoutine(z *Zone, wg *sync.WaitGroup, keys map[*DNSKEY]PrivateKey, keytags map[*DNSKEY]uint16, config *SignatureConfig, in chan *ZoneData, err chan error) { + next := "" defer wg.Done() for { select { - case data, ok := <-in: + case node, ok := <-in: if !ok { return } - e := data.Value.(*ZoneData).Sign(data.Next().Value.(*ZoneData).Name, keys, keytags, config) + name := "" + for x := range node.RR { + name = node.RR[x][0].Header().Name + break + } + i := sort.SearchStrings(z.sortedNames.nsecNames, name) + if z.sortedNames.nsecNames[i] == name { + if i+1 > len(z.sortedNames.nsecNames) { + next = z.Origin + } else { + next = z.sortedNames.nsecNames[i+1] + } + } + e := node.Sign(next, keys, keytags, config) if e != nil { err <- e return @@ -489,20 +470,20 @@ func signerRoutine(wg *sync.WaitGroup, keys map[*DNSKEY]PrivateKey, keytags map[ } } -// Sign signs a single ZoneData node. The zonedata itself is locked for writing, -// during the execution. It is important that the nodes' next record does not -// change. The caller must take care that the zone itself is also locked for writing. -// For a more complete description see zone.Sign. +// Sign signs a single ZoneData node. +// The caller must take care that the zone itself is also locked for writing. +// For a more complete description see zone.Sign. // Note, because this method has no (direct) // access to the zone's SOA record, the SOA's Minttl value should be set in *config. func (node *ZoneData) Sign(next string, keys map[*DNSKEY]PrivateKey, keytags map[*DNSKEY]uint16, config *SignatureConfig) error { - node.Lock() - defer node.Unlock() - n, nsecok := node.RR[TypeNSEC] bitmap := []uint16{TypeNSEC, TypeRRSIG} bitmapEqual := true + name := "" for t, _ := range node.RR { + if name == "" { + name = node.RR[t][0].Header().Name + } if nsecok { // Check if the current (if available) nsec has these types too // Grr O(n^2) @@ -536,14 +517,14 @@ func (node *ZoneData) Sign(next string, keys map[*DNSKEY]PrivateKey, keytags map if n[0].(*NSEC).NextDomain != next || !bitmapEqual { n[0].(*NSEC).NextDomain = next n[0].(*NSEC).TypeBitMap = bitmap - node.Signatures[TypeNSEC] = nil // drop all sigs + node.Signature[TypeNSEC] = nil // drop all sigs } } else { // No NSEC at all, create one - nsec := &NSEC{Hdr: RR_Header{node.Name, TypeNSEC, ClassINET, config.Minttl, 0}, NextDomain: next} + nsec := &NSEC{Hdr: RR_Header{name, TypeNSEC, ClassINET, config.Minttl, 0}, NextDomain: next} nsec.TypeBitMap = bitmap node.RR[TypeNSEC] = []RR{nsec} - node.Signatures[TypeNSEC] = nil // drop all sigs (just in case) + node.Signature[TypeNSEC] = nil // drop all sigs (just in case) } // Walk all keys, and check the sigs @@ -564,7 +545,7 @@ func (node *ZoneData) Sign(next string, keys map[*DNSKEY]PrivateKey, keytags map } } - j, q := signatures(node.Signatures[t], keytags[k]) + j, q := signatures(node.Signature[t], keytags[k]) if q == nil || now.Sub(uint32ToTime(q.Expiration)) < config.Refresh { // no there, are almost expired s := new(RRSIG) s.SignerName = k.Hdr.Name @@ -579,27 +560,27 @@ func (node *ZoneData) Sign(next string, keys map[*DNSKEY]PrivateKey, keytags map return e } if q != nil { - node.Signatures[t][j] = s // replace the signature + node.Signature[t][j] = s // replace the signature } else { - node.Signatures[t] = append(node.Signatures[t], s) // add it + node.Signature[t] = append(node.Signature[t], s) // add it } } } } // All signatures have been made are refreshed. Now check the all signatures for expiraton - for i, s := range node.Signatures { + for i, s := range node.Signature { // s is another slice for i1, s1 := range s { if now.Sub(uint32ToTime(s1.Expiration)) < config.Refresh { // can only happen if made with an unknown key, drop the sig - node.Signatures[i] = append(node.Signatures[i][:i1], node.Signatures[i][i1+1:]...) + node.Signature[i] = append(node.Signature[i][:i1], node.Signature[i][i1+1:]...) } } } return nil } -// Return the signature for the typecovered and make with the keytag. It +// Return the signature for the typecovered and made with the keytag. It // returns the index of the RRSIG and the RRSIG itself. func signatures(signatures []*RRSIG, keytag uint16) (int, *RRSIG) { for i, s := range signatures { @@ -610,7 +591,7 @@ func signatures(signatures []*RRSIG, keytag uint16) (int, *RRSIG) { return 0, nil } -// timeToUint32 translates a time.Time to a 32 bit value which +// timeToUint32 translates a time.Time to a 32 bit value which // can be used as the RRSIG's inception or expiration times. func timeToUint32(t time.Time) uint32 { mod := (t.Unix() / year68) - 1 @@ -639,25 +620,3 @@ func jitterDuration(d time.Duration) time.Duration { } return -time.Duration(jitter) } - -// compareLabels behaves exactly as CompareLabels expect that l1 is already -// a tokenize (in labels) version of the domain name. This saves memory and is faster. -func compareLabelsSlice(l1 []string, s2 string) (n int) { - l2 := SplitLabels(s2) - - x1 := len(l1) - 1 - x2 := len(l2) - 1 - for { - if x1 < 0 || x2 < 0 { - break - } - if l1[x1] == l2[x2] { - n++ - } else { - break - } - x1-- - x2-- - } - return -} diff --git a/zone_test.go b/zone_test.go index e643d673..22338fd7 100644 --- a/zone_test.go +++ b/zone_test.go @@ -1,30 +1,18 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dns import "testing" -func TestRadixName(t *testing.T) { - tests := map[string]string{".": ".", - "www.miek.nl.": ".nl.miek.www", - "miek.nl.": ".nl.miek", - "mi\\.ek.nl.": ".nl.mi\\.ek", - `mi\\.ek.nl.`: `.nl.ek.mi\\`, - "": "."} - for i, o := range tests { - t.Logf("%s %v\n", i, SplitLabels(i)) - if x := toRadixName(i); x != o { - t.Logf("%s should convert to %s, not %s\n", i, o, x) - t.Fail() - } - } -} - func TestInsert(t *testing.T) { z := NewZone("miek.nl.") mx, _ := NewRR("foo.miek.nl. MX 10 mx.miek.nl.") z.Insert(mx) - _, exact := z.Find("foo.miek.nl.") - if exact != true { - t.Fail() // insert broken? + node := z.Find("foo.miek.nl.") + if node == nil { + t.Fail() } } @@ -32,14 +20,14 @@ func TestRemove(t *testing.T) { z := NewZone("miek.nl.") mx, _ := NewRR("foo.miek.nl. MX 10 mx.miek.nl.") z.Insert(mx) - zd, exact := z.Find("foo.miek.nl.") - if exact != true { - t.Fail() // insert broken? + node := z.Find("foo.miek.nl.") + if node == nil { + t.Fail() } z.Remove(mx) - zd, exact = z.Find("foo.miek.nl.") - if exact != false { - println(zd.String()) - t.Errorf("zd(%s) exact(%s) still exists", zd, exact) // it should no longer be in the zone + node = z.Find("foo.miek.nl.") + if node != nil { + println(node.String()) + t.Errorf("node(%s) still exists", node) } } diff --git a/zscan.go b/zscan.go index fa703e66..2beade82 100644 --- a/zscan.go +++ b/zscan.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dns import ( @@ -84,7 +88,7 @@ func (e *ParseError) Error() (s string) { type lex struct { token string // text of the token - err bool // when true, token text has lexer error + err bool // when true, token text has lexer error value uint8 // value: _STRING, _BLANK, etc. line int // line in the file column int // column in the file @@ -96,7 +100,7 @@ type lex struct { type Token struct { RR // the scanned resource record when error is not nil Error *ParseError // when an error occured, this has the error specifics - Comment string // A potential comment positioned after the RR, but on the same line + Comment string // A potential comment positioned after the RR and on the same line } // NewRR reads the RR contained in the string s. Only the first RR is returned. @@ -119,15 +123,15 @@ func ReadRR(q io.Reader, filename string) (RR, error) { return r.RR, nil } -// ParseZone reads a RFC 1035 style one from r. It returns Tokens on the -// returned channel, which consist out the parsed RR, a potential comment or an error. +// ParseZone reads a RFC 1035 style one from r. It returns Tokens on the +// returned channel, which consist out the parsed RR, a potential comment or an error. // If there is an error the RR is nil. The string file is only used // in error reporting. The string origin is used as the initial origin, as // if the file would start with: $ORIGIN origin . // The directives $INCLUDE, $ORIGIN, $TTL and $GENERATE are supported. // The channel t is closed by ParseZone when the end of r is reached. // -// Basic usage pattern when reading from a string (z) containing the +// Basic usage pattern when reading from a string (z) containing the // zone data: // // for x := range dns.ParseZone(strings.NewReader(z), "", "") { @@ -137,10 +141,10 @@ func ReadRR(q io.Reader, filename string) (RR, error) { // } // // Comments specified after an RR (and on the same line!) are returned too: -// +// // foo. IN A 10.0.0.1 ; this is a comment // -// The text "; this is comment" is returned in Token.comment . Comments inside the +// The text "; this is comment" is returned in Token.Comment . Comments inside the // RR are discarded. Comments on a line by themselves are discarded too. func ParseZone(r io.Reader, origin, file string) chan Token { return parseZoneHelper(r, origin, file, 10000) @@ -776,7 +780,7 @@ func classToInt(token string) (uint16, bool) { return uint16(class), true } -// Extract the rr number from TYPExxx +// Extract the rr number from TYPExxx func typeToInt(token string) (uint16, bool) { typ, ok := strconv.Atoi(token[4:]) if ok != nil { @@ -816,7 +820,7 @@ func stringToTtl(token string) (uint32, bool) { return s + i, true } -// Parse LOC records' [.][mM] into a +// Parse LOC records' [.][mM] into a // mantissa exponent format. Token should contain the entire // string (i.e. no spaces allowed) func stringToCm(token string) (e, m uint8, ok bool) { @@ -866,7 +870,7 @@ func appendOrigin(name, origin string) string { return name + "." + origin } -// LOC record helper function +// LOC record helper function func locCheckNorth(token string, latitude uint32) (uint32, bool) { switch token { case "n", "N": @@ -877,7 +881,7 @@ func locCheckNorth(token string, latitude uint32) (uint32, bool) { return latitude, false } -// LOC record helper function +// LOC record helper function func locCheckEast(token string, longitude uint32) (uint32, bool) { switch token { case "e", "E": diff --git a/zscan_rr.go b/zscan_rr.go index 8f2f1d39..dc6af8e6 100644 --- a/zscan_rr.go +++ b/zscan_rr.go @@ -1,3 +1,7 @@ +// Copyright 2011 Miek Gieben. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dns import (