Resolv merge conflicts

This commit is contained in:
Miek Gieben 2013-05-12 19:08:37 +02:00
commit c66494c6c5
39 changed files with 459 additions and 355 deletions

View File

@ -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: As this is fork of the official Go code the same license applies:

View File

@ -33,8 +33,8 @@ A not-so-up-to-date-list-that-may-be-actually-current:
* UDP/TCP queries, IPv4 and IPv6; * UDP/TCP queries, IPv4 and IPv6;
* RFC 1035 zone file parsing ($INCLUDE, $ORIGIN, $TTL and $GENERATE [for all record types] * RFC 1035 zone file parsing ($INCLUDE, $ORIGIN, $TTL and $GENERATE [for all record types]
are supported); are supported);
* Fast: * Fast:
* Reply speed around ~ 50K qps (faster hardware results in more qps); * 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; * Parsing RRs with ~ 100K RR/s, that's 5M records in about 50 seconds;
* Server side programming (mimicking the net/http package); * Server side programming (mimicking the net/http package);
* Client side programming; * Client side programming;

View File

@ -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 package dns
// A client implementation. // A client implementation.

View File

@ -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 package dns
import ( import (

View File

@ -1,6 +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 // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Extensions of the original work are copyright (c) 2011 Miek Gieben
package dns package dns

View File

@ -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 package dns
import ( import (
@ -102,7 +106,7 @@ func (dns *Msg) SetAxfr(z string) *Msg {
} }
// SetTsig appends a TSIG RR to the message. // 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. // additional section. The Tsig is calculated when the message is being send.
func (dns *Msg) SetTsig(z, algo string, fudge, timesigned int64) *Msg { func (dns *Msg) SetTsig(z, algo string, fudge, timesigned int64) *Msg {
t := new(TSIG) t := new(TSIG)
@ -115,7 +119,7 @@ func (dns *Msg) SetTsig(z, algo string, fudge, timesigned int64) *Msg {
return dns 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. // TSIG should always the last RR in a message.
func (dns *Msg) SetEdns0(udpsize uint16, do bool) *Msg { func (dns *Msg) SetEdns0(udpsize uint16, do bool) *Msg {
e := new(OPT) e := new(OPT)
@ -153,7 +157,7 @@ func (dns *Msg) IsEdns0() *OPT {
} }
// IsDomainName checks if s is a valid domainname, it returns // 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. // When false is returned the labelcount and length are not defined.
func IsDomainName(s string) (uint8, uint8, bool) { // copied from net package. func IsDomainName(s string) (uint8, uint8, bool) { // copied from net package.
// TODO(mg): check for \DDD // TODO(mg): check for \DDD
@ -250,9 +254,9 @@ func Fqdn(s string) string {
// Copied from the official Go code // Copied from the official Go code
// ReverseAddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP // 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 // address addr suitable for rDNS (PTR) record lookup or an error if it fails
// to parse the IP address. // to parse the IP address.
func ReverseAddr(addr string) (arpa string, err error) { func ReverseAddr(addr string) (arpa string, err error) {
ip := net.ParseIP(addr) ip := net.ParseIP(addr)
if ip == nil { 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])) + "." + 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 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.")) 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-- { for i := len(ip) - 1; i >= 0; i-- {
v := ip[i] v := ip[i]
buf = append(buf, hexDigit[v&0xF]) 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, hexDigit[v>>4])
buf = append(buf, '.') 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."...) buf = append(buf, "ip6.arpa."...)
return string(buf), nil return string(buf), nil
} }

6
dns.go
View File

@ -1,14 +1,14 @@
// 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 // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // 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. // Package dns implements a full featured interface to the Domain Name System.
// Server- and client-side programming is supported. // Server- and client-side programming is supported.
// The package allows complete control over what is send out to the DNS. The package // 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. // 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. // TSIG, EDNS0, dynamic updates, notifies and DNSSEC validation/signing.
// Note that domain names MUST be fully qualified, before sending them, unqualified // Note that domain names MUST be fully qualified, before sending them, unqualified
// names in a message will result in a packing failure. // 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. // the authority section: in.Ns and the additional section: in.Extra.
// //
// Each of these sections (except the Question section) contain a []RR. Basic // 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: // the Answer section:
// //
// if t, ok := in.Answer[0].(*TXT); ok { // if t, ok := in.Answer[0].(*TXT); ok {

View File

@ -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 package dns
import ( import (

View File

@ -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 // DNSSEC
// //

View File

@ -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 package dns
import ( import (
@ -220,7 +224,7 @@ Coefficient: UuRoNqe7YHnKmQzE6iDWKTMIWTuoqqrFAmXPmKQnC+Y+BQzOVEHUo9bXdDnoI9hzXP1
/* /*
return return
// This key was generate with LDNS: // 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 // Show that we have al the RSA parameters and can check them
// here to see what I came up with // here to see what I came up with
key := new(RR_DNSKEY) key := new(RR_DNSKEY)

View File

@ -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 package dns
// Find better solution // Find better solution

View File

@ -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
// //
// EDNS0 is an extension mechanism for the DNS defined in RFC 2671 and updated // EDNS0 is an extension mechanism for the DNS defined in RFC 2671 and updated
// by RFC 6891. It defines a // by RFC 6891. It defines a standard RR type, the OPT RR, which is then completely
// standard RR type, the OPT RR, which is then completely abused. // abused.
// Basic use pattern for creating an (empty) OPT RR: // Basic use pattern for creating an (empty) OPT RR:
// //
// o := new(dns.OPT) // o := new(dns.OPT)

View File

@ -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 // Chaos is a small program that prints the version.bind and hostname.bind
// for each address of the nameserver given as argument. // for each address of the nameserver given as argument.
package main package main

View File

@ -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. // Go equivalent of the "DNS & BIND" book check-soa program.
package main package main

View File

@ -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. // 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 // 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 // features, like -check which checks if a packet is correctly signed (without

View File

@ -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 // Reflect is a small name server which sends back the IP address of its client, the
// recursive resolver. // recursive resolver.
// When queried for type A (resp. AAAA), it sends back the IPv4 (resp. v6) address. // When queried for type A (resp. AAAA), it sends back the IPv4 (resp. v6) address.

View File

@ -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 package dns
import ( import (

View File

@ -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 package dns
import ( import (
@ -17,7 +21,7 @@ const _FORMAT = "Private-key-format: v1.3\n"
type PrivateKey interface{} type PrivateKey interface{}
// Generate generates a DNSKEY of the given bit size. // 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 // The Algorithm in the key must be set as this will define
// what kind of DNSKEY will be generated. // what kind of DNSKEY will be generated.
// The ECDSA algorithms imply a fixed keysize, in that case // 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 // 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. // It needs some info from the key (hashing, keytag), so its a method of the DNSKEY.
func (r *DNSKEY) PrivateKeyString(p PrivateKey) (s string) { func (r *DNSKEY) PrivateKeyString(p PrivateKey) (s string) {
switch t := p.(type) { switch t := p.(type) {

View File

@ -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 package dns
import ( import (
@ -10,13 +14,13 @@ import (
) )
func (k *DNSKEY) NewPrivateKey(s string) (PrivateKey, error) { 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+"\n"), "")
} }
return k.ReadPrivateKey(strings.NewReader(s), "") 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. // only used in error reporting.
// The public key must be // The public key must be
// known, because some cryptographics algorithms embed the public inside the privatekey. // known, because some cryptographics algorithms embed the public inside the privatekey.

View File

@ -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 package dns
// Holds a bunch of helper functions for dealing with labels. // Holds a bunch of helper functions for dealing with labels.

View File

@ -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 package dns
import ( import (

4
msg.go
View File

@ -1,6 +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 // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // 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() - // DNS packet assembly, see RFC 1035. Converting from - Unpack() -
// and to - Pack() - wire format. // and to - Pack() - wire format.
@ -670,6 +671,7 @@ func structValue(any interface{}) reflect.Value {
return reflect.ValueOf(any).Elem() return reflect.ValueOf(any).Elem()
} }
// PackStruct packs any structure to wire format.
func PackStruct(any interface{}, msg []byte, off int) (off1 int, err error) { func PackStruct(any interface{}, msg []byte, off int) (off1 int, err error) {
off, err = packStructValue(structValue(any), msg, off, nil, false) off, err = packStructValue(structValue(any), msg, off, nil, false)
return off, err return off, err
@ -1052,6 +1054,8 @@ func unpackUint16(msg []byte, off int) (v uint16, off1 int) {
return 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) { func UnpackStruct(any interface{}, msg []byte, off int) (off1 int, err error) {
off, err = unpackStructValue(structValue(any), msg, off) off, err = unpackStructValue(structValue(any), msg, off)
return off, err return off, err

View File

@ -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 package dns
import ( import (

View File

@ -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 package dns
import ( import (

View File

@ -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 package dns
import ( import (
@ -141,28 +145,29 @@ func TestDotInName(t *testing.T) {
} }
func TestParseZone(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} zone := `z3.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 z1.miek.nl. 86400 IN NSEC miek.nl. TXT RRSIG NSEC
$TTL 100 $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), "", "") to := ParseZone(strings.NewReader(zone), "", "")
i := 0 i := 0
z := NewZone("miek.nl.")
for x := range to { for x := range to {
if x.Error == nil { if x.Error == nil {
switch i { switch i {
case 0: case 1:
if x.RR.Header().Name != "z1.miek.nl." { if x.RR.Header().Name != "z1.miek.nl." {
t.Log("Failed to parse z1") t.Log("Failed to parse z1")
t.Fail() t.Fail()
} }
case 1: case 2:
if x.RR.Header().Name != "z2.miek.nl." { if x.RR.Header().Name != "z2.miek.nl." {
t.Log("Failed to parse z2") t.Log("Failed to parse z2")
t.Fail() t.Fail()
} }
case 2: case 0:
if x.RR.String() != "z3.miek.nl.\t100\tIN\tNSEC\tmiek.nl. TXT RRSIG NSEC" { if x.RR.Header().Name != "z3.miek.nl." {
t.Logf("Failed to parse z3 %s", x.RR.String()) t.Logf("Failed to parse z3 %s")
t.Fail() t.Fail()
} }
} }
@ -171,6 +176,20 @@ z3.miek.nl. IN NSEC miek.nl. TXT RRSIG NSEC`
t.Fail() t.Fail()
} }
i++ 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()
} }
} }

View File

@ -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 // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

View File

@ -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 package dns
// Implement a simple scanner, return a byte stream from an io reader. // Implement a simple scanner, return a byte stream from an io reader.

View File

@ -7,7 +7,6 @@
package dns package dns
import ( import (
"github.com/miekg/radix"
"net" "net"
"sync" "sync"
"time" "time"
@ -28,7 +27,7 @@ type ResponseWriter interface {
Write([]byte) (int, error) Write([]byte) (int, error)
// Close closes the connection. // Close closes the connection.
Close() error Close() error
// TsigStatus returns the status of the Tsig. // TsigStatus returns the status of the Tsig.
TsigStatus() error TsigStatus() error
// TsigTimersOnly sets the tsig timers only boolean. // TsigTimersOnly sets the tsig timers only boolean.
TsigTimersOnly(bool) TsigTimersOnly(bool)
@ -49,19 +48,19 @@ type response struct {
} }
// ServeMux is an DNS request multiplexer. It matches the // 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 // registered patterns add calls the handler for the pattern
// that most closely matches the zone name. ServeMux is DNSSEC aware, meaning // 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 // that queries for the DS record are redirected to the parent zone (if that
// is also registered), otherwise the child gets the query. // is also registered), otherwise the child gets the query.
// ServeMux is also safe for concurrent access from multiple goroutines. // ServeMux is also safe for concurrent access from multiple goroutines.
type ServeMux struct { type ServeMux struct {
r *radix.Radix z map[string]Handler
m *sync.RWMutex m *sync.RWMutex
} }
// NewServeMux allocates and returns a new ServeMux. // 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. // DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = NewServeMux() 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"} var Authors = []string{"Miek Gieben", "Ask Bjørn Hansen", "Dave Cheney", "Dusty Wilson", "Peter van Dijk"}
// Version holds the current version. // Version holds the current version.
var Version = "v1.1" var Version = "v1.2"
// The HandlerFunc type is an adapter to allow the use of // The HandlerFunc type is an adapter to allow the use of
// ordinary functions as DNS handlers. If f is a function // 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) f(w, r)
} }
// FailedHandler returns a HandlerFunc // FailedHandler returns a HandlerFunc
// returns SERVFAIL for every request it gets. // returns SERVFAIL for every request it gets.
func HandleFailed(w ResponseWriter, r *Msg) { func HandleFailed(w ResponseWriter, r *Msg) {
m := new(Msg) m := new(Msg)
@ -165,27 +164,40 @@ func ListenAndServe(addr string, network string, handler Handler) error {
return server.ListenAndServe() return server.ListenAndServe()
} }
func (mux *ServeMux) match(zone string, t uint16) Handler { func (mux *ServeMux) match(q string, t uint16) Handler {
mux.m.RLock() mux.m.RLock()
defer mux.m.RUnlock() defer mux.m.RUnlock()
if h, e := mux.r.Find(toRadixName(zone)); e { var (
// If we got queried for a DS record, we must see if we handler Handler
// if we also serve the parent. We then redirect the query to it. lastdot int = -1
if t != TypeDS { lastbyte byte
return h.Value.(Handler) 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 lastbyte = q[i]
return h.Value.(Handler)
} else {
if h == nil {
return nil
}
return h.Value.(Handler)
} }
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. // 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) panic("dns: invalid pattern " + pattern)
} }
mux.m.Lock() mux.m.Lock()
mux.r.Insert(toRadixName(Fqdn(pattern)), handler) mux.z[Fqdn(pattern)] = handler
mux.m.Unlock() mux.m.Unlock()
} }
@ -209,7 +221,7 @@ func (mux *ServeMux) HandleRemove(pattern string) {
panic("dns: invalid pattern " + pattern) panic("dns: invalid pattern " + pattern)
} }
mux.m.Lock() mux.m.Lock()
mux.r.Remove(toRadixName(Fqdn(pattern))) delete(mux.z, Fqdn(pattern))
mux.m.Unlock() mux.m.Unlock()
} }
@ -301,7 +313,7 @@ forever:
for { for {
rw, e := l.AcceptTCP() rw, e := l.AcceptTCP()
if e != nil { if e != nil {
// don't bail out, but wait for a new request // don't bail out, but wait for a new request
continue continue
} }
if srv.ReadTimeout != 0 { if srv.ReadTimeout != 0 {

View File

@ -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 package dns
import ( import (
@ -96,3 +100,13 @@ func TestDotAsCatchAllWildcard(t *testing.T) {
t.Error("boe. match failed") 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")
}
}

View File

@ -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 package dns
import ( import (
@ -62,7 +66,7 @@ func (r *TLSA) Sign(usage, selector, matchingType int, cert *x509.Certificate) (
func (r *TLSA) Verify(cert *x509.Certificate) error { func (r *TLSA) Verify(cert *x509.Certificate) error {
c, err := CertificateToDANE(r.Selector, r.MatchingType, cert) c, err := CertificateToDANE(r.Selector, r.MatchingType, cert)
if err != nil { if err != nil {
return err // Not also ErrSig? return err // Not also ErrSig?
} }
if r.Certificate == c { if r.Certificate == c {
return nil return nil

16
tsig.go
View File

@ -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) // 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. // The supported algorithms include: HmacMD5, HmacSHA1 and HmacSHA256.
// //
// Basic use pattern when querying with a TSIG name "axfr." (note that these key names // 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==": // "so6ZGir4GPAqINNh9U5c3A==":
// //
// c := new(dns.Client) // c := new(dns.Client)
@ -23,7 +27,7 @@
// c := new(dns.Client) // c := new(dns.Client)
// c.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} // c.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="}
// m := new(dns.Msg) // m := new(dns.Msg)
// m.SetAxfr("miek.nl.") // m.SetAxfr("miek.nl.")
// m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix()) // m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix())
// t, err := c.TransferIn(m, "85.223.71.124:53") // t, err := c.TransferIn(m, "85.223.71.124:53")
// for r := range t { /* ... */ } // for r := range t { /* ... */ }
@ -148,12 +152,12 @@ type timerWireFmt struct {
// TsigGenerate fills out the TSIG record attached to the message. // TsigGenerate fills out the TSIG record attached to the message.
// The message should contain // 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 // time fudge (defaults to 300 seconds) and the current time
// The TSIG MAC is saved in that Tsig RR. // 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 // When TsigGenerate is called for the first time requestMAC is set to the empty string and
// timersOnly is false. // 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) { func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, string, error) {
if m.IsTsig() == nil { if m.IsTsig() == nil {
panic("dns: TSIG not last RR in additional") 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 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 // If the signature does not validate err contains the
// error, otherwise it is nil. // error, otherwise it is nil.
func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error { func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error {

View File

@ -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 // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // 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 package dns
@ -19,6 +19,7 @@ import (
// Wire constants and supported types. // Wire constants and supported types.
const ( const (
// valid RR_Header.Rrtype and Question.qtype // valid RR_Header.Rrtype and Question.qtype
TypeNone uint16 = 0
TypeA uint16 = 1 TypeA uint16 = 1
TypeNS uint16 = 2 TypeNS uint16 = 2
TypeMD uint16 = 3 TypeMD uint16 = 3

View File

@ -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
// //
// Dynamic updates reuses the DNS message format, but renames three of // Dynamic updates reuses the DNS message format, but renames three of
// the sections. Question is Zone, Answer is Prerequisite, Authority is // the sections. Question is Zone, Answer is Prerequisite, Authority is
// Update, only the Additional is not renamed. See RFC 2136 for the gory details. // 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 // 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 // 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. // DNS function shows which functions exist to specify the prerequisites.
// //
// 3.2.4 - Table Of Metavalues Used In Prerequisite Section // 3.2.4 - Table Of Metavalues Used In Prerequisite Section
@ -18,21 +22,21 @@
// NONE ANY empty Name is not in use NameNotUsed // NONE ANY empty Name is not in use NameNotUsed
// NONE rrset empty RRset does not exist RRsetNotUsed // NONE rrset empty RRset does not exist RRsetNotUsed
// zone rrset rr RRset exists (value dep) Used // zone rrset rr RRset exists (value dep) Used
// //
// The prerequisite section can also be left empty. // The prerequisite section can also be left empty.
// If you have decided on the prerequisites you can tell what RRs should // 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 // be added or deleted. The next table shows the options you have and
// what functions to call. // what functions to call.
// //
// 3.4.2.6 - Table Of Metavalues Used In Update Section // 3.4.2.6 - Table Of Metavalues Used In Update Section
// //
// CLASS TYPE RDATA Meaning Function // CLASS TYPE RDATA Meaning Function
// --------------------------------------------------------------- // ---------------------------------------------------------------
// ANY ANY empty Delete all RRsets from name RemoveName // ANY ANY empty Delete all RRsets from name RemoveName
// ANY rrset empty Delete an RRset RemoveRRset // ANY rrset empty Delete an RRset RemoveRRset
// NONE rrset rr Delete an RR from RRset Remove // NONE rrset rr Delete an RR from RRset Remove
// zone rrset rr Add to an RRset Insert // zone rrset rr Add to an RRset Insert
// //
package dns package dns
// NameUsed sets the RRs in the prereq section to // NameUsed sets the RRs in the prereq section to

8
xfr.go
View File

@ -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 package dns
// Envelope is used when doing [IA]XFR with a remote server. // 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") 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 // the packet. If first is true the first RR must be a SOA
// if false, the last one should be a SOA. // if false, the last one should be a SOA.
func checkXfrSOA(in *Msg, first bool) bool { 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 // Errors are signaled via the error pointer, when an error occurs the function
// sets the error and returns (it does not close the channel). // sets the error and returns (it does not close the channel).
// TSIG and enveloping is handled by TransferOut. // TSIG and enveloping is handled by TransferOut.
// //
// Basic use pattern for sending an AXFR: // Basic use pattern for sending an AXFR:
// //
// // q contains the AXFR request // // q contains the AXFR request

View File

@ -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 package dns
import ( import (

447
zone.go
View File

@ -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 package dns
// A structure for handling zone data // A structure for handling zone data
import ( import (
"fmt"
"github.com/miekg/radix"
"math/rand" "math/rand"
"runtime" "runtime"
"sort" "sort"
@ -13,25 +15,42 @@ import (
"time" "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. // multilpe goroutines.
type Zone struct { type Zone struct {
Origin string // Origin of the zone Origin string // Origin of the zone
olabels []string // origin cut up in labels, just to speed up the isSubDomain method olen int // Origin length
Wildcard int // Whenever we see a wildcard name, this is incremented olabels []string // Origin cut up in labels, just to speed up the isSubDomain method
expired bool // Slave zone is expired expired bool // Slave zone is expired
ModTime time.Time // When is the zone last modified ModTime time.Time // When is the zone last modified
*radix.Radix // Zone data Names map[string]*ZoneData // Zone data, indexed by owner name
*sortedNames // Sorted names for either NSEC or NSEC3
*sync.RWMutex *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 type uint16Slice []uint16
func (p uint16Slice) Len() int { return len(p) } func (p uint16Slice) Len() int { return len(p) }
func (p uint16Slice) Less(i, j int) bool { return p[i] < p[j] } 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] } 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: // is copied from OpenDNSSEC. See:
// https://wiki.opendnssec.org/display/DOCS/kasp.xml // https://wiki.opendnssec.org/display/DOCS/kasp.xml
type SignatureConfig struct { type SignatureConfig struct {
@ -40,7 +59,7 @@ type SignatureConfig struct {
// When the end of the validity approaches, how much time should remain // When the end of the validity approaches, how much time should remain
// before we start to resign. Typical value is 3 days. // before we start to resign. Typical value is 3 days.
Refresh time.Duration 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. // expiration time to ensure not all signatures expire a the same time.
// Typical value is 12 hours, which means the actual jitter value is // Typical value is 12 hours, which means the actual jitter value is
// between -12..0..+12. // between -12..0..+12.
@ -57,7 +76,7 @@ type SignatureConfig struct {
// SignerRoutines specifies the number of signing goroutines, if not // SignerRoutines specifies the number of signing goroutines, if not
// set runtime.NumCPU() + 1 is used as the value. // set runtime.NumCPU() + 1 is used as the value.
SignerRoutines int 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 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} 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. // 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 // HonorSepFlag is set to true, SignerRoutines is set to runtime.NumCPU() + 1. The
// Minttl value is zero. // Minttl value is zero.
var DefaultSignatureConfig = newSignatureConfig() 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 { func NewZone(origin string) *Zone {
if origin == "" { if origin == "" {
origin = "." origin = "."
@ -81,74 +100,37 @@ func NewZone(origin string) *Zone {
} }
z := new(Zone) z := new(Zone)
z.Origin = Fqdn(strings.ToLower(origin)) z.Origin = Fqdn(strings.ToLower(origin))
z.olen = len(z.Origin)
z.olabels = SplitLabels(z.Origin) z.olabels = SplitLabels(z.Origin)
z.Radix = radix.New() z.Names = make(map[string]*ZoneData)
z.RWMutex = new(sync.RWMutex) z.RWMutex = new(sync.RWMutex)
z.ModTime = time.Now().UTC() z.ModTime = time.Now().UTC()
z.sortedNames = &sortedNames{make([]string, 0), make([]string, 0)}
return z 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 { type ZoneData struct {
Name string // Domain name for this node RR map[uint16][]RR // Map of the RR type to the RR
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
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
NonAuth bool // Always false, except for NSsets that differ from z.Origin
*sync.RWMutex
} }
// NewZoneData creates a new zone data element. // NewZoneData creates a new zone data element.
func NewZoneData(s string) *ZoneData { func NewZoneData() *ZoneData {
zd := new(ZoneData) zd := new(ZoneData)
zd.Name = s
zd.RR = make(map[uint16][]RR) zd.RR = make(map[uint16][]RR)
zd.Signatures = make(map[uint16][]*RRSIG) zd.Signature = make(map[uint16][]*RRSIG)
zd.RWMutex = new(sync.RWMutex)
return zd 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 returns a string representation of a ZoneData. There is no
// String for the entire zone, because this will (most likely) take up // String for the entire zone, because this will (most likely) take up
// a huge amount of memory. Basic use pattern for printing an entire // a huge amount of memory.
// zone:
//
// // z contains the zone
// z.Radix.NextDo(func(i interface{}) {
// fmt.Printf("%s", i.(*dns.ZoneData).String()) })
//
func (zd *ZoneData) String() string { func (zd *ZoneData) String() string {
var ( var (
s string s string
@ -158,8 +140,8 @@ func (zd *ZoneData) String() string {
// There is only one SOA, but it may have multiple sigs // There is only one SOA, but it may have multiple sigs
if soa, ok := zd.RR[TypeSOA]; ok { if soa, ok := zd.RR[TypeSOA]; ok {
s += soa[0].String() + "\n" s += soa[0].String() + "\n"
if _, ok := zd.Signatures[TypeSOA]; ok { if _, ok := zd.Signature[TypeSOA]; ok {
for _, sig := range zd.Signatures[TypeSOA] { for _, sig := range zd.Signature[TypeSOA] {
s += sig.String() + "\n" s += sig.String() + "\n"
} }
} }
@ -174,8 +156,8 @@ Types:
} }
s += rr.String() + "\n" s += rr.String() + "\n"
} }
if _, ok := zd.Signatures[t]; ok { if _, ok := zd.Signature[t]; ok {
for _, rr := range zd.Signatures[t] { for _, rr := range zd.Signature[t] {
s += rr.String() + "\n" s += rr.String() + "\n"
} }
} }
@ -184,8 +166,8 @@ Types:
// There is only one NSEC, but it may have multiple sigs // There is only one NSEC, but it may have multiple sigs
if soa, ok := zd.RR[TypeNSEC]; ok { if soa, ok := zd.RR[TypeNSEC]; ok {
s += soa[0].String() + "\n" s += soa[0].String() + "\n"
if _, ok := zd.Signatures[TypeNSEC]; ok { if _, ok := zd.Signature[TypeNSEC]; ok {
for _, sig := range zd.Signatures[TypeNSEC] { for _, sig := range zd.Signature[TypeNSEC] {
s += sig.String() + "\n" s += sig.String() + "\n"
} }
} }
@ -193,29 +175,21 @@ Types:
return s return s
} }
// Insert inserts the RR r into the zone. There is no check for duplicate data, although // Insert inserts the RR r into the zone.
// Remove will remove all duplicates.
func (z *Zone) Insert(r RR) error { func (z *Zone) Insert(r RR) error {
z.Lock()
defer z.Unlock()
if !z.isSubDomain(r.Header().Name) { if !z.isSubDomain(r.Header().Name) {
return &Error{Err: "out of zone data", Name: 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() z.ModTime = time.Now().UTC()
zd, exact := z.Radix.Find(key) zd, ok := z.Names[r.Header().Name]
if !exact { if !ok {
// Not an exact match, so insert new value zd = NewZoneData()
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)
switch t := r.Header().Rrtype; t { switch t := r.Header().Rrtype; t {
case TypeRRSIG: case TypeRRSIG:
sigtype := r.(*RRSIG).TypeCovered sigtype := r.(*RRSIG).TypeCovered
zd.Signatures[sigtype] = append(zd.Signatures[sigtype], r.(*RRSIG)) zd.Signature[sigtype] = append(zd.Signature[sigtype], r.(*RRSIG))
case TypeNS: case TypeNS:
// NS records with other names than z.Origin are non-auth // NS records with other names than z.Origin are non-auth
if r.Header().Name != z.Origin { if r.Header().Name != z.Origin {
@ -225,24 +199,25 @@ func (z *Zone) Insert(r RR) error {
default: default:
zd.RR[t] = append(zd.RR[t], r) 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 return nil
} }
z.Unlock()
zd.Value.(*ZoneData).Lock()
defer zd.Value.(*ZoneData).Unlock()
// Name already there // Name already there
switch t := r.Header().Rrtype; t { switch t := r.Header().Rrtype; t {
case TypeRRSIG: case TypeRRSIG:
sigtype := r.(*RRSIG).TypeCovered 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: case TypeNS:
if r.Header().Name != z.Origin { if r.Header().Name != z.Origin {
zd.Value.(*ZoneData).NonAuth = true zd.NonAuth = true
} }
fallthrough fallthrough
default: default:
zd.Value.(*ZoneData).RR[t] = append(zd.Value.(*ZoneData).RR[t], r) zd.RR[t] = append(zd.RR[t], r)
} }
return nil 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, // Remove removes the RR r from the zone. If the RR can not be found,
// this is a no-op. // this is a no-op.
func (z *Zone) Remove(r RR) error { func (z *Zone) Remove(r RR) error {
key := toRadixName(r.Header().Name)
z.Lock() z.Lock()
z.ModTime = time.Now().UTC() defer z.Unlock()
zd, exact := z.Radix.Find(key) zd, ok := z.Names[r.Header().Name]
if !exact { if !ok {
defer z.Unlock()
return nil return nil
} }
z.Unlock() z.ModTime = time.Now().UTC()
zd.Value.(*ZoneData).Lock()
defer zd.Value.(*ZoneData).Unlock()
remove := false
switch t := r.Header().Rrtype; t { switch t := r.Header().Rrtype; t {
case TypeRRSIG: case TypeRRSIG:
sigtype := r.(*RRSIG).TypeCovered sigtype := r.(*RRSIG).TypeCovered
for i, zr := range zd.Value.(*ZoneData).Signatures[sigtype] { for i, zr := range zd.Signature[sigtype] {
if r == zr { if r == zr {
zd.Value.(*ZoneData).Signatures[sigtype] = append(zd.Value.(*ZoneData).Signatures[sigtype][:i], zd.Value.(*ZoneData).Signatures[sigtype][i+1:]...) zd.Signature[sigtype] = append(zd.Signature[sigtype][:i], zd.Signature[sigtype][i+1:]...)
remove = true
} }
} }
if remove { if len(zd.Signature[sigtype]) == 0 {
// If every Signature of the covering type is removed, removed the type from the map delete(zd.Signature, sigtype)
if len(zd.Value.(*ZoneData).Signatures[sigtype]) == 0 {
delete(zd.Value.(*ZoneData).Signatures, sigtype)
}
} }
default: default:
for i, zr := range zd.Value.(*ZoneData).RR[t] { for i, zr := range zd.RR[t] {
// Matching RR // Matching RR
if r == zr { if r == zr {
zd.Value.(*ZoneData).RR[t] = append(zd.Value.(*ZoneData).RR[t][:i], zd.Value.(*ZoneData).RR[t][i+1:]...) zd.RR[t] = append(zd.RR[t][:i], zd.RR[t][i+1:]...)
remove = true
} }
} }
if remove { if len(zd.RR[t]) == 0 {
// If every RR of this type is removed, removed the type from the map delete(zd.RR, t)
if len(zd.Value.(*ZoneData).RR[t]) == 0 {
delete(zd.Value.(*ZoneData).RR, t)
}
} }
} }
if !remove { if len(zd.RR) == 0 && len(zd.Signature) == 0 {
return nil // 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)
if len(r.Header().Name) > 1 && r.Header().Name[0] == '*' && r.Header().Name[1] == '.' { // we actually removed something if we are here, so i must be something sensible
z.Wildcard-- copy(z.sortedNames.nsecNames[i:], z.sortedNames.nsecNames[i+1:])
if z.Wildcard < 0 { z.sortedNames.nsecNames[len(z.sortedNames.nsecNames)-1] = ""
z.Wildcard = 0 z.sortedNames.nsecNames = z.sortedNames.nsecNames[:len(z.sortedNames.nsecNames)-1]
}
}
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)
} }
return nil 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 // 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. // method is when processing a RemoveName dynamic update packet.
func (z *Zone) RemoveName(s string) error { func (z *Zone) RemoveName(s string) error {
key := toRadixName(s)
z.Lock() z.Lock()
z.ModTime = time.Now().UTC()
defer z.Unlock() defer z.Unlock()
z.Radix.Remove(key) _, ok := z.Names[s]
if len(s) > 1 && s[0] == '*' && s[1] == '.' { if !ok {
z.Wildcard-- return nil
if z.Wildcard < 0 {
z.Wildcard = 0
}
} }
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 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. // Typical use of this method is when processing a RemoveRRset dynamic update packet.
func (z *Zone) RemoveRRset(s string, t uint16) error { func (z *Zone) RemoveRRset(s string, t uint16) error {
z.Lock() z.Lock()
z.ModTime = time.Now().UTC() defer z.Unlock()
zd, exact := z.Radix.Find(toRadixName(s)) zd, ok := z.Names[s]
if !exact { if !ok {
defer z.Unlock()
return nil return nil
} }
z.Unlock() z.ModTime = time.Now().UTC()
zd.Value.(*ZoneData).Lock()
defer zd.Value.(*ZoneData).Unlock()
switch t { switch t {
case TypeRRSIG: case TypeRRSIG:
// empty all signature maps // empty all signature maps
for covert, _ := range zd.Value.(*ZoneData).Signatures { for cover, _ := range zd.Signature {
delete(zd.Value.(*ZoneData).Signatures, covert) delete(zd.Signature, cover)
} }
default: default:
// empty all rr maps // empty all rr maps
for t, _ := range zd.Value.(*ZoneData).RR { for t, _ := range zd.RR {
delete(zd.Value.(*ZoneData).RR, t) 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 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. // 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: // Apex is safe for concurrent use.
//
// 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.
func (z *Zone) Apex() *ZoneData { func (z *Zone) Apex() *ZoneData {
apex, e := z.Find(z.Origin) z.RLock()
if !e { defer z.RUnlock()
fmt.Printf("%#v\n", apex) apex, ok := z.Names[z.Origin]
if !ok {
return nil return nil
} }
return apex return apex
} }
// Find looks up the ownername s in the zone and returns the // Find looks up the ownername s in the zone and returns the data or nil
// data and true when an exact match is found. If an exact find isn't // when nothing can be found. Find is safe for concurrent use.
// possible the first parent node with a non-nil Value is returned and func (z *Zone) Find(s string) *ZoneData {
// the boolean is false.
func (z *Zone) Find(s string) (node *ZoneData, exact bool) {
z.RLock() z.RLock()
defer z.RUnlock() defer z.RUnlock()
n, e := z.Radix.Find(toRadixName(s)) node, ok := z.Names[s]
if n == nil { if !ok {
return nil, false return nil
} }
node = n.Value.(*ZoneData) return node
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
} }
func (z *Zone) isSubDomain(child string) bool { func (z *Zone) isSubDomain(child string) bool {
return compareLabelsSlice(z.olabels, strings.ToLower(child)) == len(z.olabels) return compareLabelsSlice(z.olabels, strings.ToLower(child)) == len(z.olabels)
} }
// Sign (re)signs the zone z with the given keys. // compareLabels behaves exactly as CompareLabels expect that l1 is already
// NSECs and RRSIGs are added as needed. // a tokenize (in labels) version of the domain name. This saves memory and is faster.
// The public keys themselves are not added to the zone. 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 // If config is nil DefaultSignatureConfig is used. The signatureConfig
// describes how the zone must be signed and if the SEP flag (for KSK) // describes how the zone must be signed and if the SEP flag (for KSK)
// should be honored. If signatures approach their expriration time, they // 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 { if config == nil {
config = DefaultSignatureConfig config = DefaultSignatureConfig
} }
// Pre-calc the key tag // Pre-calc the key tags
keytags := make(map[*DNSKEY]uint16) keytags := make(map[*DNSKEY]uint16)
for k, _ := range keys { for k, _ := range keys {
keytags[k] = k.KeyTag() keytags[k] = k.KeyTag()
} }
errChan := make(chan error) errChan := make(chan error)
radChan := make(chan *radix.Radix, config.SignerRoutines*2) zonChan := make(chan *ZoneData, config.SignerRoutines*2)
// Start the signer goroutines // Start the signer goroutines
wg := new(sync.WaitGroup) wg := new(sync.WaitGroup)
wg.Add(config.SignerRoutines) wg.Add(config.SignerRoutines)
for i := 0; i < config.SignerRoutines; i++ { 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 var err error
apex := z.Apex()
if apex == nil {
return ErrSoa
}
config.Minttl = apex.RR[TypeSOA][0].(*SOA).Minttl
Sign: Sign:
for next.Value.(*ZoneData).Name != z.Origin { for name := range z.Names {
select { select {
case err = <-errChan: case err = <-errChan:
break Sign break Sign
default: default:
radChan <- next zonChan <- z.Names[name]
next = next.Next()
} }
} }
close(radChan) close(zonChan)
close(errChan) close(errChan)
if err != nil { if err != nil {
return err return err
@ -471,16 +432,36 @@ Sign:
return nil 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. // 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() defer wg.Done()
for { for {
select { select {
case data, ok := <-in: case node, ok := <-in:
if !ok { if !ok {
return 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 { if e != nil {
err <- e err <- e
return 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, // Sign signs a single ZoneData node.
// during the execution. It is important that the nodes' next record does not // The caller must take care that the zone itself is also locked for writing.
// change. The caller must take care that the zone itself is also locked for writing. // For a more complete description see zone.Sign.
// For a more complete description see zone.Sign.
// Note, because this method has no (direct) // Note, because this method has no (direct)
// access to the zone's SOA record, the SOA's Minttl value should be set in *config. // 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 { 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] n, nsecok := node.RR[TypeNSEC]
bitmap := []uint16{TypeNSEC, TypeRRSIG} bitmap := []uint16{TypeNSEC, TypeRRSIG}
bitmapEqual := true bitmapEqual := true
name := ""
for t, _ := range node.RR { for t, _ := range node.RR {
if name == "" {
name = node.RR[t][0].Header().Name
}
if nsecok { if nsecok {
// Check if the current (if available) nsec has these types too // Check if the current (if available) nsec has these types too
// Grr O(n^2) // 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 { if n[0].(*NSEC).NextDomain != next || !bitmapEqual {
n[0].(*NSEC).NextDomain = next n[0].(*NSEC).NextDomain = next
n[0].(*NSEC).TypeBitMap = bitmap n[0].(*NSEC).TypeBitMap = bitmap
node.Signatures[TypeNSEC] = nil // drop all sigs node.Signature[TypeNSEC] = nil // drop all sigs
} }
} else { } else {
// No NSEC at all, create one // 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 nsec.TypeBitMap = bitmap
node.RR[TypeNSEC] = []RR{nsec} 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 // 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 if q == nil || now.Sub(uint32ToTime(q.Expiration)) < config.Refresh { // no there, are almost expired
s := new(RRSIG) s := new(RRSIG)
s.SignerName = k.Hdr.Name s.SignerName = k.Hdr.Name
@ -579,27 +560,27 @@ func (node *ZoneData) Sign(next string, keys map[*DNSKEY]PrivateKey, keytags map
return e return e
} }
if q != nil { if q != nil {
node.Signatures[t][j] = s // replace the signature node.Signature[t][j] = s // replace the signature
} else { } 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 // 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 // s is another slice
for i1, s1 := range s { for i1, s1 := range s {
if now.Sub(uint32ToTime(s1.Expiration)) < config.Refresh { if now.Sub(uint32ToTime(s1.Expiration)) < config.Refresh {
// can only happen if made with an unknown key, drop the sig // 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 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. // returns the index of the RRSIG and the RRSIG itself.
func signatures(signatures []*RRSIG, keytag uint16) (int, *RRSIG) { func signatures(signatures []*RRSIG, keytag uint16) (int, *RRSIG) {
for i, s := range signatures { for i, s := range signatures {
@ -610,7 +591,7 @@ func signatures(signatures []*RRSIG, keytag uint16) (int, *RRSIG) {
return 0, nil 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. // can be used as the RRSIG's inception or expiration times.
func timeToUint32(t time.Time) uint32 { func timeToUint32(t time.Time) uint32 {
mod := (t.Unix() / year68) - 1 mod := (t.Unix() / year68) - 1
@ -639,25 +620,3 @@ func jitterDuration(d time.Duration) time.Duration {
} }
return -time.Duration(jitter) 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
}

View File

@ -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 package dns
import "testing" 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) { func TestInsert(t *testing.T) {
z := NewZone("miek.nl.") z := NewZone("miek.nl.")
mx, _ := NewRR("foo.miek.nl. MX 10 mx.miek.nl.") mx, _ := NewRR("foo.miek.nl. MX 10 mx.miek.nl.")
z.Insert(mx) z.Insert(mx)
_, exact := z.Find("foo.miek.nl.") node := z.Find("foo.miek.nl.")
if exact != true { if node == nil {
t.Fail() // insert broken? t.Fail()
} }
} }
@ -32,14 +20,14 @@ func TestRemove(t *testing.T) {
z := NewZone("miek.nl.") z := NewZone("miek.nl.")
mx, _ := NewRR("foo.miek.nl. MX 10 mx.miek.nl.") mx, _ := NewRR("foo.miek.nl. MX 10 mx.miek.nl.")
z.Insert(mx) z.Insert(mx)
zd, exact := z.Find("foo.miek.nl.") node := z.Find("foo.miek.nl.")
if exact != true { if node == nil {
t.Fail() // insert broken? t.Fail()
} }
z.Remove(mx) z.Remove(mx)
zd, exact = z.Find("foo.miek.nl.") node = z.Find("foo.miek.nl.")
if exact != false { if node != nil {
println(zd.String()) println(node.String())
t.Errorf("zd(%s) exact(%s) still exists", zd, exact) // it should no longer be in the zone t.Errorf("node(%s) still exists", node)
} }
} }

View File

@ -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 package dns
import ( import (
@ -84,7 +88,7 @@ func (e *ParseError) Error() (s string) {
type lex struct { type lex struct {
token string // text of the token 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. value uint8 // value: _STRING, _BLANK, etc.
line int // line in the file line int // line in the file
column int // column in the file column int // column in the file
@ -96,7 +100,7 @@ type lex struct {
type Token struct { type Token struct {
RR // the scanned resource record when error is not nil RR // the scanned resource record when error is not nil
Error *ParseError // when an error occured, this has the error specifics 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. // 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 return r.RR, nil
} }
// ParseZone reads a RFC 1035 style one from r. It returns Tokens on the // 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. // 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 // 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 // in error reporting. The string origin is used as the initial origin, as
// if the file would start with: $ORIGIN origin . // if the file would start with: $ORIGIN origin .
// The directives $INCLUDE, $ORIGIN, $TTL and $GENERATE are supported. // The directives $INCLUDE, $ORIGIN, $TTL and $GENERATE are supported.
// The channel t is closed by ParseZone when the end of r is reached. // 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: // zone data:
// //
// for x := range dns.ParseZone(strings.NewReader(z), "", "") { // 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: // Comments specified after an RR (and on the same line!) are returned too:
// //
// foo. IN A 10.0.0.1 ; this is a comment // 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. // RR are discarded. Comments on a line by themselves are discarded too.
func ParseZone(r io.Reader, origin, file string) chan Token { func ParseZone(r io.Reader, origin, file string) chan Token {
return parseZoneHelper(r, origin, file, 10000) return parseZoneHelper(r, origin, file, 10000)
@ -776,7 +780,7 @@ func classToInt(token string) (uint16, bool) {
return uint16(class), true return uint16(class), true
} }
// Extract the rr number from TYPExxx // Extract the rr number from TYPExxx
func typeToInt(token string) (uint16, bool) { func typeToInt(token string) (uint16, bool) {
typ, ok := strconv.Atoi(token[4:]) typ, ok := strconv.Atoi(token[4:])
if ok != nil { if ok != nil {
@ -816,7 +820,7 @@ func stringToTtl(token string) (uint32, bool) {
return s + i, true return s + i, true
} }
// Parse LOC records' <digits>[.<digits>][mM] into a // Parse LOC records' <digits>[.<digits>][mM] into a
// mantissa exponent format. Token should contain the entire // mantissa exponent format. Token should contain the entire
// string (i.e. no spaces allowed) // string (i.e. no spaces allowed)
func stringToCm(token string) (e, m uint8, ok bool) { func stringToCm(token string) (e, m uint8, ok bool) {
@ -866,7 +870,7 @@ func appendOrigin(name, origin string) string {
return name + "." + origin return name + "." + origin
} }
// LOC record helper function // LOC record helper function
func locCheckNorth(token string, latitude uint32) (uint32, bool) { func locCheckNorth(token string, latitude uint32) (uint32, bool) {
switch token { switch token {
case "n", "N": case "n", "N":
@ -877,7 +881,7 @@ func locCheckNorth(token string, latitude uint32) (uint32, bool) {
return latitude, false return latitude, false
} }
// LOC record helper function // LOC record helper function
func locCheckEast(token string, longitude uint32) (uint32, bool) { func locCheckEast(token string, longitude uint32) (uint32, bool) {
switch token { switch token {
case "e", "E": case "e", "E":

View File

@ -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 package dns
import ( import (