From 45775dff7642855a3ef6e7b749bd3ecf5e9e1f54 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Sat, 4 May 2013 22:28:44 +0200 Subject: [PATCH 01/36] Remove the radix.Radix dependency Put everything in maps. This removes to a major dependency and makes Go DNS only depend on the core Go packages. This will probably also be faster than the current setup -- although this needs to be benchmarked. --- server.go | 64 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/server.go b/server.go index 1ee8059b..01753755 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.0" +var Version = "v1.1" // 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 + lastbyte byte + seendot bool = true + ) + // TODO(mg): check for . + for i := 0; i < len(q); i++ { + if seendot { + if h, ok := mux.z[q[lastdot:]]; 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") + if handler != nil { + return handler + } + return nil } // 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 { From 07d0e083664d86f81a458081f7a9cb1215d81208 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Sat, 4 May 2013 23:21:01 +0200 Subject: [PATCH 02/36] Remove radix from core go dns --- server.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/server.go b/server.go index 01753755..963a37a9 100644 --- a/server.go +++ b/server.go @@ -169,14 +169,14 @@ func (mux *ServeMux) match(q string, t uint16) Handler { defer mux.m.RUnlock() var ( handler Handler - lastdot int + lastdot int = -1 lastbyte byte seendot bool = true ) // TODO(mg): check for . for i := 0; i < len(q); i++ { if seendot { - if h, ok := mux.z[q[lastdot:]]; ok { + if h, ok := mux.z[q[lastdot+1:]]; ok { if t != TypeDS { return h } else { @@ -194,10 +194,12 @@ func (mux *ServeMux) match(q string, t uint16) Handler { } lastbyte = q[i] } - if handler != nil { - return handler + // Check for the root zone too, this only delays NXDOMAIN, because if we serve . it + // will be catched above. + if h, ok := mux.z["."]; ok { + return h } - return nil + return handler } // Handle adds a handler to the ServeMux for pattern. From 4c7989886b368da91830bf6daa67372b0f22fffe Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Sat, 4 May 2013 23:46:57 +0200 Subject: [PATCH 03/36] First pass at making zonedata more mem. efficient --- zone.go | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/zone.go b/zone.go index 46840d7d..8f171571 100644 --- a/zone.go +++ b/zone.go @@ -4,7 +4,6 @@ package dns import ( "fmt" - "github.com/miekg/radix" "math/rand" "runtime" "sort" @@ -13,15 +12,15 @@ import ( "time" ) -// Zone represents a DNS zone. It's safe for concurrent use by +// 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 + 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 + Labels map[string]*ZoneData // Zone data, indexed by label *sync.RWMutex } @@ -31,7 +30,7 @@ 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 +39,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. @@ -65,7 +64,7 @@ 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. @@ -82,7 +81,7 @@ func NewZone(origin string) *Zone { z := new(Zone) z.Origin = Fqdn(strings.ToLower(origin)) z.olabels = SplitLabels(z.Origin) - z.Radix = radix.New() + z.Labels = make(map[string]*ZoneData) z.RWMutex = new(sync.RWMutex) z.ModTime = time.Now().UTC() return z @@ -90,7 +89,6 @@ func NewZone(origin string) *Zone { // ZoneData holds all the RRs having their owner name equal to 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 @@ -98,9 +96,8 @@ type ZoneData struct { } // 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) @@ -404,9 +401,9 @@ 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. +// 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 @@ -492,7 +489,7 @@ 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. +// 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 { @@ -610,7 +607,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 From c798e9ca6350398730d014ef51dc86ec36ec2de3 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Sun, 5 May 2013 09:37:04 +0200 Subject: [PATCH 04/36] More Zone api changes Fix the tests for the new Zone api. --- zone.go | 163 +++++++++++++++++---------------------------------- zone_test.go | 36 ++++-------- 2 files changed, 65 insertions(+), 134 deletions(-) diff --git a/zone.go b/zone.go index 8f171571..ac5930dc 100644 --- a/zone.go +++ b/zone.go @@ -3,10 +3,9 @@ package dns // A structure for handling zone data import ( - "fmt" "math/rand" "runtime" - "sort" +// "sort" "strings" "sync" "time" @@ -20,7 +19,7 @@ type Zone struct { 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 - Labels map[string]*ZoneData // Zone data, indexed by label + Names map[string]*ZoneData // Zone data, indexed by name *sync.RWMutex } @@ -81,7 +80,7 @@ func NewZone(origin string) *Zone { z := new(Zone) z.Origin = Fqdn(strings.ToLower(origin)) z.olabels = SplitLabels(z.Origin) - z.Labels = make(map[string]*ZoneData) + z.Names = make(map[string]*ZoneData) z.RWMutex = new(sync.RWMutex) z.ModTime = time.Now().UTC() return z @@ -104,39 +103,6 @@ func NewZoneData() *ZoneData { 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 @@ -190,25 +156,22 @@ 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 { 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 + zd, ok := z.Names[r.Header().Name] + if !ok { 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 = NewZoneData() switch t := r.Header().Rrtype; t { case TypeRRSIG: sigtype := r.(*RRSIG).TypeCovered @@ -222,24 +185,24 @@ 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 return nil } z.Unlock() - zd.Value.(*ZoneData).Lock() - defer zd.Value.(*ZoneData).Unlock() + zd.Lock() + defer zd.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.Signatures[sigtype] = append(zd.Signatures[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 } @@ -247,45 +210,44 @@ 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 { + zd, ok := z.Names[r.Header().Name] + if !ok { defer z.Unlock() return nil } z.Unlock() - zd.Value.(*ZoneData).Lock() - defer zd.Value.(*ZoneData).Unlock() + zd.Lock() + defer zd.Unlock() remove := false 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.Signatures[sigtype] { if r == zr { - zd.Value.(*ZoneData).Signatures[sigtype] = append(zd.Value.(*ZoneData).Signatures[sigtype][:i], zd.Value.(*ZoneData).Signatures[sigtype][i+1:]...) + zd.Signatures[sigtype] = append(zd.Signatures[sigtype][:i], zd.Signatures[sigtype][i+1:]...) remove = true } } 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.Signatures[sigtype]) == 0 { + delete(zd.Signatures, 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:]...) + zd.RR[t] = append(zd.RR[t][:i], zd.RR[t][i+1:]...) remove = true } } 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) } } } @@ -299,9 +261,9 @@ func (z *Zone) Remove(r RR) error { 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.Signatures) == 0 { + // Entire node is empty, remove it from the Zone too + delete(z.Names, r.Header().Name) } return nil } @@ -309,11 +271,10 @@ 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) + delete(z.Names, s) if len(s) > 1 && s[0] == '*' && s[1] == '.' { z.Wildcard-- if z.Wildcard < 0 { @@ -328,24 +289,24 @@ func (z *Zone) RemoveName(s string) error { 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 { + zd, ok := z.Names[s] + if !ok { defer z.Unlock() return nil } z.Unlock() - zd.Value.(*ZoneData).Lock() - defer zd.Value.(*ZoneData).Unlock() + zd.Lock() + defer zd.Unlock() 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.Signatures { + delete(zd.Signatures, 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) } } return nil @@ -360,9 +321,8 @@ func (z *Zone) RemoveRRset(s string, t uint16) error { // 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 { - apex, e := z.Find(z.Origin) - if !e { - fmt.Printf("%#v\n", apex) + apex, ok := z.Names[z.Origin] + if !ok { return nil } return apex @@ -372,29 +332,14 @@ func (z *Zone) Apex() *ZoneData { // 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) { +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 { @@ -417,6 +362,7 @@ func (z *Zone) isSubDomain(child string) bool { // // signing error // } // // Admire your signed zone... +/* func (z *Zone) Sign(keys map[*DNSKEY]PrivateKey, config *SignatureConfig) error { z.Lock() z.ModTime = time.Now().UTC() @@ -431,22 +377,22 @@ func (z *Zone) Sign(keys map[*DNSKEY]PrivateKey, config *SignatureConfig) error } 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(wg, keys, keytags, config, zonChan, errChan) } - apex, e := z.Radix.Find(toRadixName(z.Origin)) - if !e { + apex := z.Apex() + if apex == nil { return ErrSoa } - config.Minttl = apex.Value.(*ZoneData).RR[TypeSOA][0].(*SOA).Minttl + config.Minttl = apex.RR[TypeSOA][0].(*SOA).Minttl next := apex.Next() - radChan <- apex + zonChan <- apex var err error Sign: @@ -455,11 +401,11 @@ Sign: case err = <-errChan: break Sign default: - radChan <- next + zonChan <- next next = next.Next() } } - close(radChan) + close(zonChan) close(errChan) if err != nil { return err @@ -469,7 +415,7 @@ Sign: } // 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(wg *sync.WaitGroup, keys map[*DNSKEY]PrivateKey, keytags map[*DNSKEY]uint16, config *SignatureConfig, in chan *ZoneData, err chan error) { defer wg.Done() for { select { @@ -595,6 +541,7 @@ func (node *ZoneData) Sign(next string, keys map[*DNSKEY]PrivateKey, keytags map } return nil } +*/ // Return the signature for the typecovered and make with the keytag. It // returns the index of the RRSIG and the RRSIG itself. diff --git a/zone_test.go b/zone_test.go index e643d673..de4c1c33 100644 --- a/zone_test.go +++ b/zone_test.go @@ -2,29 +2,13 @@ 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 +16,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) } } From 76b81135e501b61e817a592c1ce2b3d009901d63 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Sun, 5 May 2013 09:52:37 +0200 Subject: [PATCH 05/36] Update documentation --- zone.go | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/zone.go b/zone.go index ac5930dc..9fb2a5f9 100644 --- a/zone.go +++ b/zone.go @@ -5,7 +5,7 @@ package dns import ( "math/rand" "runtime" -// "sort" + "sort" "strings" "sync" "time" @@ -86,7 +86,7 @@ func NewZone(origin string) *Zone { return z } -// ZoneData holds all the RRs having their owner name equal to Name. +// ZoneData holds all the RRs for a specific owner name. type ZoneData struct { 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 @@ -312,14 +312,8 @@ func (z *Zone) RemoveRRset(s string, t uint16) error { 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. func (z *Zone) Apex() *ZoneData { apex, ok := z.Names[z.Origin] if !ok { From c38ec4ee6ce62045a6956dc6888dc046e8466ef6 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Sun, 5 May 2013 19:38:09 +0200 Subject: [PATCH 06/36] Fix signing Fix the signing - the memory usage of the Zone structure is bad - and needs to be fixed somehow. --- zone.go | 81 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 49 insertions(+), 32 deletions(-) diff --git a/zone.go b/zone.go index 9fb2a5f9..28fcf5c6 100644 --- a/zone.go +++ b/zone.go @@ -14,12 +14,13 @@ import ( // 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 - Names map[string]*ZoneData // Zone data, indexed by name + 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 + Names map[string]*ZoneData // Zone data, indexed by name + sortedNames []string // All names in the zone, but sorted (for nsec) *sync.RWMutex } @@ -83,9 +84,14 @@ func NewZone(origin string) *Zone { z.Names = make(map[string]*ZoneData) z.RWMutex = new(sync.RWMutex) z.ModTime = time.Now().UTC() + z.sortedNames = make([]string, 0) return z } +// 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 { RR map[uint16][]RR // Map of the RR type to the RR @@ -105,13 +111,7 @@ func NewZoneData() *ZoneData { // 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 @@ -186,6 +186,10 @@ func (z *Zone) Insert(r RR) error { zd.RR[t] = append(zd.RR[t], r) } z.Names[r.Header().Name] = zd + i := sort.SearchStrings(z.sortedNames, r.Header().Name) + z.sortedNames = append(z.sortedNames, "") + copy(z.sortedNames[i+1:], z.sortedNames[:i]) + z.sortedNames[i] = r.Header().Name return nil } z.Unlock() @@ -314,7 +318,10 @@ func (z *Zone) RemoveRRset(s string, t uint16) error { // 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 is safe for concurrent use. func (z *Zone) Apex() *ZoneData { + z.RLock() + defer z.RUnlock() apex, ok := z.Names[z.Origin] if !ok { return nil @@ -322,10 +329,8 @@ func (z *Zone) Apex() *ZoneData { 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. +// 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() @@ -356,7 +361,6 @@ func (z *Zone) isSubDomain(child string) bool { // // signing error // } // // Admire your signed zone... -/* func (z *Zone) Sign(keys map[*DNSKEY]PrivateKey, config *SignatureConfig) error { z.Lock() z.ModTime = time.Now().UTC() @@ -364,7 +368,7 @@ 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() @@ -377,26 +381,22 @@ func (z *Zone) Sign(keys map[*DNSKEY]PrivateKey, config *SignatureConfig) error wg := new(sync.WaitGroup) wg.Add(config.SignerRoutines) for i := 0; i < config.SignerRoutines; i++ { - go signerRoutine(wg, keys, keytags, config, zonChan, errChan) + go signerRoutine(z, wg, keys, keytags, config, zonChan, errChan) } + var err error apex := z.Apex() if apex == nil { return ErrSoa } config.Minttl = apex.RR[TypeSOA][0].(*SOA).Minttl - next := apex.Next() - zonChan <- apex - - var err error Sign: - for next.Value.(*ZoneData).Name != z.Origin { + for name := range z.Names { select { case err = <-errChan: break Sign default: - zonChan <- next - next = next.Next() + zonChan <- z.Names[name] } } close(zonChan) @@ -409,15 +409,29 @@ Sign: } // 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 *ZoneData, 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, name) + if z.sortedNames[i] == name { + if i+1 > len(z.sortedNames) { + next = z.Origin + } else { + next = z.sortedNames[i+1] + } + } + e := node.Sign(next, keys, keytags, config) if e != nil { err <- e return @@ -439,7 +453,11 @@ func (node *ZoneData) Sign(next string, keys map[*DNSKEY]PrivateKey, keytags map 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) @@ -477,7 +495,7 @@ func (node *ZoneData) Sign(next string, keys map[*DNSKEY]PrivateKey, keytags map } } 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) @@ -535,7 +553,6 @@ func (node *ZoneData) Sign(next string, keys map[*DNSKEY]PrivateKey, keytags map } return nil } -*/ // Return the signature for the typecovered and make with the keytag. It // returns the index of the RRSIG and the RRSIG itself. From ba747fd510c2a7ea74354fb8d3c82a68bd18c7be Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Sun, 5 May 2013 20:30:39 +0200 Subject: [PATCH 07/36] Add some more tests --- parse_test.go | 26 ++++++++++++++++++-------- zone.go | 2 +- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/parse_test.go b/parse_test.go index a40f5cfc..e77af257 100644 --- a/parse_test.go +++ b/parse_test.go @@ -141,28 +141,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 +172,15 @@ z3.miek.nl. IN NSEC miek.nl. TXT RRSIG NSEC` t.Fail() } i++ + z.Insert(x.RR) + } + if len(z.sortedNames) != 3 { + t.Log("Length not 3?") + t.Fail() + } + if z.sortedNames[0] != "z1.miek.nl." || z.sortedNames[1] != "z2.miek.nl." || z.sortedNames[2] != "z3.miek.nl." { + t.Log("Not all names correct") + t.Fail() } } diff --git a/zone.go b/zone.go index 28fcf5c6..d3f895dd 100644 --- a/zone.go +++ b/zone.go @@ -188,7 +188,7 @@ func (z *Zone) Insert(r RR) error { z.Names[r.Header().Name] = zd i := sort.SearchStrings(z.sortedNames, r.Header().Name) z.sortedNames = append(z.sortedNames, "") - copy(z.sortedNames[i+1:], z.sortedNames[:i]) + copy(z.sortedNames[i+1:], z.sortedNames[i:]) z.sortedNames[i] = r.Header().Name return nil } From d53d9eab812f3c0401ce64fc138c05a08ac46cbf Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Sun, 5 May 2013 20:30:44 +0200 Subject: [PATCH 08/36] gofmt --- defaults.go | 18 +++++++++--------- dns.go | 4 ++-- dnssec.go | 14 +++++++------- dnssec_test.go | 2 +- edns.go | 6 +++--- keygen.go | 4 ++-- kscan.go | 4 ++-- msg.go | 26 +++++++++++++------------- server.go | 2 +- tlsa.go | 2 +- tsig.go | 12 ++++++------ update.go | 10 +++++----- xfr.go | 4 ++-- zscan.go | 20 ++++++++++---------- 14 files changed, 64 insertions(+), 64 deletions(-) diff --git a/defaults.go b/defaults.go index ed3168a3..808f5ab4 100644 --- a/defaults.go +++ b/defaults.go @@ -102,7 +102,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 +115,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 +153,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 +250,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 +262,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 +272,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..9a5e091c 100644 --- a/dns.go +++ b/dns.go @@ -8,7 +8,7 @@ // 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/dnssec.go b/dnssec.go index 87b7b0b0..dcfb5e1d 100644 --- a/dnssec.go +++ b/dnssec.go @@ -8,7 +8,7 @@ // // Requesting DNSSEC information for a zone is done by adding the DO (DNSSEC OK) bit // to an request. -// +// // m := new(dns.Msg) // m.SetEdns0(4096, true) // @@ -61,7 +61,7 @@ const ( const ( _ = iota SHA1 // RFC 4034 - SHA256 // RFC 4509 + SHA256 // RFC 4509 GOST94 // RFC 5933 SHA384 // Experimental SHA512 // Experimental @@ -309,7 +309,7 @@ func (rr *RRSIG) Sign(k PrivateKey, rrset []RR) error { // Verify validates an RRSet with the signature and key. This is only the // cryptographic test, the signature validity period must be checked separately. -// This function copies the rdata of some RRs (to lowercase domain names) for the validation to work. +// This function copies the rdata of some RRs (to lowercase domain names) for the validation to work. func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error { // First the easy checks if len(rrset) == 0 { @@ -423,7 +423,7 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error { return ErrAlg } -// ValidityPeriod uses RFC1982 serial arithmetic to calculate +// ValidityPeriod uses RFC1982 serial arithmetic to calculate // if a signature period is valid. func (rr *RRSIG) ValidityPeriod() bool { utc := time.Now().UTC().Unix() @@ -443,7 +443,7 @@ func (s *RRSIG) sigBuf() []byte { return sigbuf } -// setPublicKeyInPrivate sets the public key in the private key. +// setPublicKeyInPrivate sets the public key in the private key. func (k *DNSKEY) setPublicKeyInPrivate(p PrivateKey) bool { switch t := p.(type) { case *dsa.PrivateKey: @@ -606,7 +606,7 @@ func exponentToBuf(_E int) []byte { return buf } -// Set the public key for X and Y for Curve. The two +// Set the public key for X and Y for Curve. The two // values are just concatenated. func curveToBuf(_X, _Y *big.Int) []byte { buf := _X.Bytes() @@ -614,7 +614,7 @@ func curveToBuf(_X, _Y *big.Int) []byte { return buf } -// Set the public key for X and Y for Curve. The two +// Set the public key for X and Y for Curve. The two // values are just concatenated. func dsaToBuf(_Q, _P, _G, _Y *big.Int) []byte { t := byte((len(_G.Bytes()) - 64) / 8) diff --git a/dnssec_test.go b/dnssec_test.go index 3179c241..4228d1a8 100644 --- a/dnssec_test.go +++ b/dnssec_test.go @@ -220,7 +220,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/edns.go b/edns.go index e6f6804d..1aa6cace 100644 --- a/edns.go +++ b/edns.go @@ -1,7 +1,7 @@ // EDNS0 // -// EDNS0 is an extension mechanism for the DNS defined in RFC 2671. It defines a -// standard RR type, the OPT RR, which is then completely abused. +// EDNS0 is an extension mechanism for the DNS defined in RFC 2671. 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) @@ -296,7 +296,7 @@ func (e *EDNS0_SUBNET) String() (s string) { // The UPDATE_LEASE EDNS0 (draft RFC) option is used to tell the server to set // an expiration on an update RR. This is helpful for clients that cannot clean // up after themselves. This is a draft RFC and more information can be found at -// http://files.dns-sd.org/draft-sekar-dns-ul.txt +// http://files.dns-sd.org/draft-sekar-dns-ul.txt // // o := new(dns.OPT) // o.Hdr.Name = "." diff --git a/keygen.go b/keygen.go index bcb74bc7..bc1a0797 100644 --- a/keygen.go +++ b/keygen.go @@ -17,7 +17,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 +88,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..e70396d3 100644 --- a/kscan.go +++ b/kscan.go @@ -10,13 +10,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/msg.go b/msg.go index c2f339b7..1d90fa33 100644 --- a/msg.go +++ b/msg.go @@ -129,8 +129,8 @@ var TypeToString = map[uint16]string{ TypeL32: "L32", TypeL64: "L64", TypeLP: "LP", - TypeEUI48: "EUI48", - TypeEUI64: "EUI64", + TypeEUI48: "EUI48", + TypeEUI64: "EUI64", TypeTKEY: "TKEY", // Meta RR TypeTSIG: "TSIG", // Meta RR TypeAXFR: "AXFR", // Meta RR @@ -183,14 +183,14 @@ var RcodeToString = map[int]string{ RcodeNXRrset: "NXRRSET", RcodeNotAuth: "NOTAUTH", RcodeNotZone: "NOTZONE", - RcodeBadSig: "BADSIG", // Also known as RcodeBadVers, see RFC 6891 -// RcodeBadVers: "BADVERS", - RcodeBadKey: "BADKEY", - RcodeBadTime: "BADTIME", - RcodeBadMode: "BADMODE", - RcodeBadName: "BADNAME", - RcodeBadAlg: "BADALG", - RcodeBadTrunc: "BADTRUNC", + RcodeBadSig: "BADSIG", // Also known as RcodeBadVers, see RFC 6891 + // RcodeBadVers: "BADVERS", + RcodeBadKey: "BADKEY", + RcodeBadTime: "BADTIME", + RcodeBadMode: "BADMODE", + RcodeBadName: "BADNAME", + RcodeBadAlg: "BADALG", + RcodeBadTrunc: "BADTRUNC", } // Rather than write the usual handful of routines to pack and @@ -639,7 +639,7 @@ func packStructValue(val reflect.Value, msg []byte, off int, compression map[str copy(msg[off:off+hex.DecodedLen(len(s))], h) off += hex.DecodedLen(len(s)) case `dns:"size"`: - // the size is already encoded in the RR, we can safely use the + // the size is already encoded in the RR, we can safely use the // length of string. String is RAW (not encoded in hex, nor base64) copy(msg[off:off+len(s)], s) off += len(s) @@ -1369,10 +1369,10 @@ func (dns *Msg) String() string { // Len return the message length when in (un)compressed wire format. // If dns.Compress is true compression it is taken into account, currently -// this only counts owner name compression. There is no check for +// this only counts owner name compression. There is no check for // nil valued sections (allocated, but contain no RRs). func (dns *Msg) Len() int { - // Message header is always 12 bytes + // Message header is always 12 bytes l := 12 var compression map[string]int if dns.Compress { diff --git a/server.go b/server.go index 963a37a9..f4ea2a58 100644 --- a/server.go +++ b/server.go @@ -194,7 +194,7 @@ func (mux *ServeMux) match(q string, t uint16) Handler { } lastbyte = q[i] } - // Check for the root zone too, this only delays NXDOMAIN, because if we serve . it + // Check for the root zone too, this only delays NXDOMAIN, because if we serve . it // will be catched above. if h, ok := mux.z["."]; ok { return h diff --git a/tlsa.go b/tlsa.go index 136b4b18..d3bc3b02 100644 --- a/tlsa.go +++ b/tlsa.go @@ -62,7 +62,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..7aaade74 100644 --- a/tsig.go +++ b/tsig.go @@ -1,10 +1,10 @@ // 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 +23,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 +148,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 +205,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/update.go b/update.go index 53986372..eb2bf5a7 100644 --- a/update.go +++ b/update.go @@ -1,12 +1,12 @@ // 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 +18,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..748deaf3 100644 --- a/xfr.go +++ b/xfr.go @@ -127,7 +127,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 +147,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/zscan.go b/zscan.go index fa703e66..0d934244 100644 --- a/zscan.go +++ b/zscan.go @@ -84,7 +84,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 @@ -119,15 +119,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 +137,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 +776,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 +816,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 +866,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 +877,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": From 6cb4f9cf698d0a2583a014107e3cec5ed0e4b6c5 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Sun, 5 May 2013 21:03:34 +0200 Subject: [PATCH 09/36] Fix remove too --- zone.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/zone.go b/zone.go index d3f895dd..ac686cf9 100644 --- a/zone.go +++ b/zone.go @@ -268,6 +268,11 @@ func (z *Zone) Remove(r RR) error { if len(zd.RR) == 0 && len(zd.Signatures) == 0 { // Entire node is empty, remove it from the Zone too delete(z.Names, r.Header().Name) + i := sort.SearchStrings(z.sortedNames, r.Header().Name) + // we actually removed something if we are here, so i must be something sensible + copy(z.sortedNames[i:], z.sortedNames[i+1:]) + z.sortedNames[len(z.sortedNames)-1] = "" + z.sortedNames = z.sortedNames[:len(z.sortedNames)-1] } return nil } From 9a8079966621f317750aacbb58c3f252b6922e6e Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Sun, 5 May 2013 21:52:21 +0200 Subject: [PATCH 10/36] Fix remaining zone.Remove* functions Fix the remove function and make them more like zone.Remove() --- parse_test.go | 5 +++++ zone.go | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/parse_test.go b/parse_test.go index e77af257..0e312106 100644 --- a/parse_test.go +++ b/parse_test.go @@ -182,6 +182,11 @@ z2.miek.nl. IN NSEC miek.nl. TXT RRSIG NSEC` t.Log("Not all names correct") t.Fail() } + z.RemoveName("z2.miek.nl.") + if z.sortedNames[0] != "z1.miek.nl." || z.sortedNames[1] != "z3.miek.nl." { + t.Logf("Not all names correct %v\n", z.sortedNames) + t.Fail() + } } func TestDomainName(t *testing.T) { diff --git a/zone.go b/zone.go index ac686cf9..dd784931 100644 --- a/zone.go +++ b/zone.go @@ -284,6 +284,13 @@ func (z *Zone) RemoveName(s string) error { z.ModTime = time.Now().UTC() defer z.Unlock() delete(z.Names, s) + i := sort.SearchStrings(z.sortedNames, s) + if z.sortedNames[i] == s { + copy(z.sortedNames[i:], z.sortedNames[i+1:]) + z.sortedNames[len(z.sortedNames)-1] = "" + z.sortedNames = z.sortedNames[:len(z.sortedNames)-1] + } + if len(s) > 1 && s[0] == '*' && s[1] == '.' { z.Wildcard-- if z.Wildcard < 0 { From 7710e3ff812561d889140318bfc54dce59bb36bb Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Sun, 5 May 2013 21:59:43 +0200 Subject: [PATCH 11/36] Update readme --- README.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.markdown b/README.markdown index e525d4b1..0859bfff 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; From aaf475fae3299d7f25e4e52135bfa656badb6f48 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Sun, 5 May 2013 22:35:00 +0200 Subject: [PATCH 12/36] In progress refactering of Remove* --- zone.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/zone.go b/zone.go index dd784931..577d7ed6 100644 --- a/zone.go +++ b/zone.go @@ -234,11 +234,9 @@ func (z *Zone) Remove(r RR) error { remove = true } } - if remove { - // If every Signature of the covering type is removed, removed the type from the map - if len(zd.Signatures[sigtype]) == 0 { - delete(zd.Signatures, sigtype) - } + // If every Signature of the covering type is removed, removed the type from the map + if len(zd.Signatures[sigtype]) == 0 { + delete(zd.Signatures, sigtype) } default: for i, zr := range zd.RR[t] { @@ -248,11 +246,9 @@ func (z *Zone) Remove(r RR) error { remove = true } } - if remove { - // If every RR of this type is removed, removed the type from the map - if len(zd.RR[t]) == 0 { - delete(zd.RR, t) - } + // If every RR of this type is removed, removed the type from the map + if len(zd.RR[t]) == 0 { + delete(zd.RR, t) } } if !remove { @@ -313,18 +309,24 @@ func (z *Zone) RemoveRRset(s string, t uint16) error { z.Unlock() zd.Lock() defer zd.Unlock() + remove := false switch t { case TypeRRSIG: // empty all signature maps for cover, _ := range zd.Signatures { delete(zd.Signatures, cover) + remove = true } default: // empty all rr maps for t, _ := range zd.RR { delete(zd.RR, t) + remove = true } } + if !remove { + return nil + } return nil } From 77bba78ef3dc225fe4218e2d5f1ce2b589b874a0 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Mon, 6 May 2013 10:59:53 +0200 Subject: [PATCH 13/36] Make removing work --- zone.go | 57 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/zone.go b/zone.go index 577d7ed6..e02c6c6e 100644 --- a/zone.go +++ b/zone.go @@ -215,26 +215,23 @@ func (z *Zone) Insert(r RR) error { // this is a no-op. func (z *Zone) Remove(r RR) error { z.Lock() - z.ModTime = time.Now().UTC() zd, ok := z.Names[r.Header().Name] if !ok { defer z.Unlock() return nil } + z.ModTime = time.Now().UTC() z.Unlock() zd.Lock() defer zd.Unlock() - remove := false switch t := r.Header().Rrtype; t { case TypeRRSIG: sigtype := r.(*RRSIG).TypeCovered for i, zr := range zd.Signatures[sigtype] { if r == zr { zd.Signatures[sigtype] = append(zd.Signatures[sigtype][:i], zd.Signatures[sigtype][i+1:]...) - remove = true } } - // If every Signature of the covering type is removed, removed the type from the map if len(zd.Signatures[sigtype]) == 0 { delete(zd.Signatures, sigtype) } @@ -243,24 +240,12 @@ func (z *Zone) Remove(r RR) error { // Matching RR if r == zr { zd.RR[t] = append(zd.RR[t][:i], zd.RR[t][i+1:]...) - remove = true } } - // If every RR of this type is removed, removed the type from the map 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.RR) == 0 && len(zd.Signatures) == 0 { // Entire node is empty, remove it from the Zone too delete(z.Names, r.Header().Name) @@ -269,6 +254,12 @@ func (z *Zone) Remove(r RR) error { copy(z.sortedNames[i:], z.sortedNames[i+1:]) z.sortedNames[len(z.sortedNames)-1] = "" z.sortedNames = z.sortedNames[:len(z.sortedNames)-1] + if len(r.Header().Name) > 1 && r.Header().Name[0] == '*' && r.Header().Name[1] == '.' { + z.Wildcard-- + if z.Wildcard < 0 { + z.Wildcard = 0 + } + } } return nil } @@ -277,15 +268,18 @@ func (z *Zone) Remove(r RR) error { // method is when processing a RemoveName dynamic update packet. func (z *Zone) RemoveName(s string) error { z.Lock() + _, ok := z.Names[s] + if !ok { + defer z.Unlock() + return nil + } z.ModTime = time.Now().UTC() defer z.Unlock() delete(z.Names, s) i := sort.SearchStrings(z.sortedNames, s) - if z.sortedNames[i] == s { - copy(z.sortedNames[i:], z.sortedNames[i+1:]) - z.sortedNames[len(z.sortedNames)-1] = "" - z.sortedNames = z.sortedNames[:len(z.sortedNames)-1] - } + copy(z.sortedNames[i:], z.sortedNames[i+1:]) + z.sortedNames[len(z.sortedNames)-1] = "" + z.sortedNames = z.sortedNames[:len(z.sortedNames)-1] if len(s) > 1 && s[0] == '*' && s[1] == '.' { z.Wildcard-- @@ -300,32 +294,41 @@ 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, ok := z.Names[s] if !ok { defer z.Unlock() return nil } + z.ModTime = time.Now().UTC() z.Unlock() zd.Lock() defer zd.Unlock() - remove := false switch t { case TypeRRSIG: // empty all signature maps for cover, _ := range zd.Signatures { delete(zd.Signatures, cover) - remove = true } default: // empty all rr maps for t, _ := range zd.RR { delete(zd.RR, t) - remove = true } } - if !remove { - return nil + if len(zd.RR) == 0 && len(zd.Signatures) == 0 { + // Entire node is empty, remove it from the Zone too + delete(z.Names, s) + i := sort.SearchStrings(z.sortedNames, s) + // we actually removed something if we are here, so i must be something sensible + copy(z.sortedNames[i:], z.sortedNames[i+1:]) + z.sortedNames[len(z.sortedNames)-1] = "" + z.sortedNames = z.sortedNames[:len(z.sortedNames)-1] + if len(s) > 1 && s[0] == '*' && s[1] == '.' { + z.Wildcard-- + if z.Wildcard < 0 { + z.Wildcard = 0 + } + } } return nil } From 2a79dc91a7d979a5c496e92a3423f973f1706bc3 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Mon, 6 May 2013 11:07:22 +0200 Subject: [PATCH 14/36] Remove node locks --- zone.go | 34 +++++++++------------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/zone.go b/zone.go index e02c6c6e..525b278b 100644 --- a/zone.go +++ b/zone.go @@ -11,6 +11,8 @@ import ( "time" ) +// TODO(mg): NSEC3 + // Zone represents a DNS zone. It's safe for concurrent use by // multilpe goroutines. type Zone struct { @@ -97,7 +99,6 @@ type ZoneData struct { 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 } // NewZoneData creates a new zone data element. @@ -105,7 +106,6 @@ func NewZoneData() *ZoneData { zd := new(ZoneData) zd.RR = make(map[uint16][]RR) zd.Signatures = make(map[uint16][]*RRSIG) - zd.RWMutex = new(sync.RWMutex) return zd } @@ -158,15 +158,14 @@ Types: // 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} } - - z.Lock() z.ModTime = time.Now().UTC() zd, ok := z.Names[r.Header().Name] if !ok { - 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++ @@ -192,9 +191,6 @@ func (z *Zone) Insert(r RR) error { z.sortedNames[i] = r.Header().Name return nil } - z.Unlock() - zd.Lock() - defer zd.Unlock() // Name already there switch t := r.Header().Rrtype; t { case TypeRRSIG: @@ -215,15 +211,12 @@ func (z *Zone) Insert(r RR) error { // this is a no-op. func (z *Zone) Remove(r RR) error { z.Lock() + defer z.Unlock() zd, ok := z.Names[r.Header().Name] if !ok { - defer z.Unlock() return nil } z.ModTime = time.Now().UTC() - z.Unlock() - zd.Lock() - defer zd.Unlock() switch t := r.Header().Rrtype; t { case TypeRRSIG: sigtype := r.(*RRSIG).TypeCovered @@ -268,19 +261,17 @@ func (z *Zone) Remove(r RR) error { // method is when processing a RemoveName dynamic update packet. func (z *Zone) RemoveName(s string) error { z.Lock() + defer z.Unlock() _, ok := z.Names[s] if !ok { - defer z.Unlock() return nil } z.ModTime = time.Now().UTC() - defer z.Unlock() delete(z.Names, s) i := sort.SearchStrings(z.sortedNames, s) copy(z.sortedNames[i:], z.sortedNames[i+1:]) z.sortedNames[len(z.sortedNames)-1] = "" z.sortedNames = z.sortedNames[:len(z.sortedNames)-1] - if len(s) > 1 && s[0] == '*' && s[1] == '.' { z.Wildcard-- if z.Wildcard < 0 { @@ -294,15 +285,12 @@ 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() + defer z.Unlock() zd, ok := z.Names[s] if !ok { - defer z.Unlock() return nil } z.ModTime = time.Now().UTC() - z.Unlock() - zd.Lock() - defer zd.Unlock() switch t { case TypeRRSIG: // empty all signature maps @@ -457,16 +445,12 @@ func signerRoutine(z *Zone, wg *sync.WaitGroup, keys map[*DNSKEY]PrivateKey, key } } -// 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. +// 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 From 709d11aa2cdffd15c3061cd5d49b5501e5828924 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Mon, 6 May 2013 22:23:13 +0200 Subject: [PATCH 15/36] update documentation --- msg.go | 3 +++ zscan.go | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/msg.go b/msg.go index 1d90fa33..d8300ca2 100644 --- a/msg.go +++ b/msg.go @@ -666,6 +666,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 @@ -1028,6 +1029,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/zscan.go b/zscan.go index 0d934244..b6bfff40 100644 --- a/zscan.go +++ b/zscan.go @@ -96,7 +96,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. @@ -140,7 +140,7 @@ func ReadRR(q io.Reader, filename string) (RR, error) { // // 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) From 49ffb70c33022ec44f3f74490e06e19b697a0fa2 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Mon, 6 May 2013 22:51:22 +0200 Subject: [PATCH 16/36] Add nsec3 stuff --- zone.go | 59 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/zone.go b/zone.go index 525b278b..672ea41f 100644 --- a/zone.go +++ b/zone.go @@ -16,13 +16,16 @@ import ( // 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 - Names map[string]*ZoneData // Zone data, indexed by name - sortedNames []string // All names in the zone, but sorted (for nsec) + 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 + Dnssec bool // This zone has signatures + ModTime time.Time // When is the zone last modified + Names map[string]*ZoneData // Zone data, indexed by name + nextNames []string // All names in the zone, but sorted (for NSEC) + next3Names []string // All hashed names in the zone, but sorted (for NSEC3) + nsec3Param *NSEC3PARAM // The NSEC3 parameters for this zone (if applicable), when nil -> NSEC *sync.RWMutex } @@ -86,7 +89,7 @@ func NewZone(origin string) *Zone { z.Names = make(map[string]*ZoneData) z.RWMutex = new(sync.RWMutex) z.ModTime = time.Now().UTC() - z.sortedNames = make([]string, 0) + z.nextNames = make([]string, 0) return z } @@ -185,10 +188,10 @@ func (z *Zone) Insert(r RR) error { zd.RR[t] = append(zd.RR[t], r) } z.Names[r.Header().Name] = zd - i := sort.SearchStrings(z.sortedNames, r.Header().Name) - z.sortedNames = append(z.sortedNames, "") - copy(z.sortedNames[i+1:], z.sortedNames[i:]) - z.sortedNames[i] = r.Header().Name + i := sort.SearchStrings(z.nextNames, r.Header().Name) + z.nextNames = append(z.nextNames, "") + copy(z.nextNames[i+1:], z.nextNames[i:]) + z.nextNames[i] = r.Header().Name return nil } // Name already there @@ -242,11 +245,11 @@ func (z *Zone) Remove(r RR) error { if len(zd.RR) == 0 && len(zd.Signatures) == 0 { // Entire node is empty, remove it from the Zone too delete(z.Names, r.Header().Name) - i := sort.SearchStrings(z.sortedNames, r.Header().Name) + i := sort.SearchStrings(z.nextNames, r.Header().Name) // we actually removed something if we are here, so i must be something sensible - copy(z.sortedNames[i:], z.sortedNames[i+1:]) - z.sortedNames[len(z.sortedNames)-1] = "" - z.sortedNames = z.sortedNames[:len(z.sortedNames)-1] + copy(z.nextNames[i:], z.nextNames[i+1:]) + z.nextNames[len(z.nextNames)-1] = "" + z.nextNames = z.nextNames[:len(z.nextNames)-1] if len(r.Header().Name) > 1 && r.Header().Name[0] == '*' && r.Header().Name[1] == '.' { z.Wildcard-- if z.Wildcard < 0 { @@ -268,10 +271,10 @@ func (z *Zone) RemoveName(s string) error { } z.ModTime = time.Now().UTC() delete(z.Names, s) - i := sort.SearchStrings(z.sortedNames, s) - copy(z.sortedNames[i:], z.sortedNames[i+1:]) - z.sortedNames[len(z.sortedNames)-1] = "" - z.sortedNames = z.sortedNames[:len(z.sortedNames)-1] + i := sort.SearchStrings(z.nextNames, s) + copy(z.nextNames[i:], z.nextNames[i+1:]) + z.nextNames[len(z.nextNames)-1] = "" + z.nextNames = z.nextNames[:len(z.nextNames)-1] if len(s) > 1 && s[0] == '*' && s[1] == '.' { z.Wildcard-- if z.Wildcard < 0 { @@ -306,11 +309,11 @@ func (z *Zone) RemoveRRset(s string, t uint16) error { if len(zd.RR) == 0 && len(zd.Signatures) == 0 { // Entire node is empty, remove it from the Zone too delete(z.Names, s) - i := sort.SearchStrings(z.sortedNames, s) + i := sort.SearchStrings(z.nextNames, s) // we actually removed something if we are here, so i must be something sensible - copy(z.sortedNames[i:], z.sortedNames[i+1:]) - z.sortedNames[len(z.sortedNames)-1] = "" - z.sortedNames = z.sortedNames[:len(z.sortedNames)-1] + copy(z.nextNames[i:], z.nextNames[i+1:]) + z.nextNames[len(z.nextNames)-1] = "" + z.nextNames = z.nextNames[:len(z.nextNames)-1] if len(s) > 1 && s[0] == '*' && s[1] == '.' { z.Wildcard-- if z.Wildcard < 0 { @@ -428,12 +431,12 @@ func signerRoutine(z *Zone, wg *sync.WaitGroup, keys map[*DNSKEY]PrivateKey, key name = node.RR[x][0].Header().Name break } - i := sort.SearchStrings(z.sortedNames, name) - if z.sortedNames[i] == name { - if i+1 > len(z.sortedNames) { + i := sort.SearchStrings(z.nextNames, name) + if z.nextNames[i] == name { + if i+1 > len(z.nextNames) { next = z.Origin } else { - next = z.sortedNames[i+1] + next = z.nextNames[i+1] } } e := node.Sign(next, keys, keytags, config) From e1151d6edc66586958d50a64ce128fb779e73de9 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Tue, 7 May 2013 09:44:21 +0200 Subject: [PATCH 17/36] Update the Zone structure for NSEC and NSEC3 --- parse_test.go | 8 +++--- types.go | 1 + zone.go | 73 ++++++++++++++++++++++++++++----------------------- 3 files changed, 45 insertions(+), 37 deletions(-) diff --git a/parse_test.go b/parse_test.go index 0e312106..928b4d57 100644 --- a/parse_test.go +++ b/parse_test.go @@ -174,17 +174,17 @@ z2.miek.nl. IN NSEC miek.nl. TXT RRSIG NSEC` i++ z.Insert(x.RR) } - if len(z.sortedNames) != 3 { + if len(z.securityConfig.nsecNames) != 3 { t.Log("Length not 3?") t.Fail() } - if z.sortedNames[0] != "z1.miek.nl." || z.sortedNames[1] != "z2.miek.nl." || z.sortedNames[2] != "z3.miek.nl." { + if z.securityConfig.nsecNames[0] != "z1.miek.nl." || z.securityConfig.nsecNames[1] != "z2.miek.nl." || z.securityConfig.nsecNames[2] != "z3.miek.nl." { t.Log("Not all names correct") t.Fail() } z.RemoveName("z2.miek.nl.") - if z.sortedNames[0] != "z1.miek.nl." || z.sortedNames[1] != "z3.miek.nl." { - t.Logf("Not all names correct %v\n", z.sortedNames) + if z.securityConfig.nsecNames[0] != "z1.miek.nl." || z.securityConfig.nsecNames[1] != "z3.miek.nl." { + t.Logf("Not all names correct %v\n", z.securityConfig.nsecNames) t.Fail() } } diff --git a/types.go b/types.go index 7a5a56db..d9ebd81c 100644 --- a/types.go +++ b/types.go @@ -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/zone.go b/zone.go index 672ea41f..53a54571 100644 --- a/zone.go +++ b/zone.go @@ -11,24 +11,31 @@ import ( "time" ) -// TODO(mg): NSEC3 - // 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 - Dnssec bool // This zone has signatures - ModTime time.Time // When is the zone last modified - Names map[string]*ZoneData // Zone data, indexed by name - nextNames []string // All names in the zone, but sorted (for NSEC) - next3Names []string // All hashed names in the zone, but sorted (for NSEC3) - nsec3Param *NSEC3PARAM // The NSEC3 parameters for this zone (if applicable), when nil -> NSEC + 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 + Names map[string]*ZoneData // Zone data, indexed by owner name + *securityConfig // 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 securityConfig 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) } @@ -89,7 +96,7 @@ func NewZone(origin string) *Zone { z.Names = make(map[string]*ZoneData) z.RWMutex = new(sync.RWMutex) z.ModTime = time.Now().UTC() - z.nextNames = make([]string, 0) + z.securityConfig = &securityConfig{make([]string, 0), make([]string, 0)} return z } @@ -188,10 +195,10 @@ func (z *Zone) Insert(r RR) error { zd.RR[t] = append(zd.RR[t], r) } z.Names[r.Header().Name] = zd - i := sort.SearchStrings(z.nextNames, r.Header().Name) - z.nextNames = append(z.nextNames, "") - copy(z.nextNames[i+1:], z.nextNames[i:]) - z.nextNames[i] = r.Header().Name + i := sort.SearchStrings(z.securityConfig.nsecNames, r.Header().Name) + z.securityConfig.nsecNames = append(z.securityConfig.nsecNames, "") + copy(z.securityConfig.nsecNames[i+1:], z.securityConfig.nsecNames[i:]) + z.securityConfig.nsecNames[i] = r.Header().Name return nil } // Name already there @@ -245,11 +252,11 @@ func (z *Zone) Remove(r RR) error { if len(zd.RR) == 0 && len(zd.Signatures) == 0 { // Entire node is empty, remove it from the Zone too delete(z.Names, r.Header().Name) - i := sort.SearchStrings(z.nextNames, r.Header().Name) + i := sort.SearchStrings(z.securityConfig.nsecNames, r.Header().Name) // we actually removed something if we are here, so i must be something sensible - copy(z.nextNames[i:], z.nextNames[i+1:]) - z.nextNames[len(z.nextNames)-1] = "" - z.nextNames = z.nextNames[:len(z.nextNames)-1] + copy(z.securityConfig.nsecNames[i:], z.securityConfig.nsecNames[i+1:]) + z.securityConfig.nsecNames[len(z.securityConfig.nsecNames)-1] = "" + z.securityConfig.nsecNames = z.securityConfig.nsecNames[:len(z.securityConfig.nsecNames)-1] if len(r.Header().Name) > 1 && r.Header().Name[0] == '*' && r.Header().Name[1] == '.' { z.Wildcard-- if z.Wildcard < 0 { @@ -271,10 +278,10 @@ func (z *Zone) RemoveName(s string) error { } z.ModTime = time.Now().UTC() delete(z.Names, s) - i := sort.SearchStrings(z.nextNames, s) - copy(z.nextNames[i:], z.nextNames[i+1:]) - z.nextNames[len(z.nextNames)-1] = "" - z.nextNames = z.nextNames[:len(z.nextNames)-1] + i := sort.SearchStrings(z.securityConfig.nsecNames, s) + copy(z.securityConfig.nsecNames[i:], z.securityConfig.nsecNames[i+1:]) + z.securityConfig.nsecNames[len(z.securityConfig.nsecNames)-1] = "" + z.securityConfig.nsecNames = z.securityConfig.nsecNames[:len(z.securityConfig.nsecNames)-1] if len(s) > 1 && s[0] == '*' && s[1] == '.' { z.Wildcard-- if z.Wildcard < 0 { @@ -309,11 +316,11 @@ func (z *Zone) RemoveRRset(s string, t uint16) error { if len(zd.RR) == 0 && len(zd.Signatures) == 0 { // Entire node is empty, remove it from the Zone too delete(z.Names, s) - i := sort.SearchStrings(z.nextNames, s) + i := sort.SearchStrings(z.securityConfig.nsecNames, s) // we actually removed something if we are here, so i must be something sensible - copy(z.nextNames[i:], z.nextNames[i+1:]) - z.nextNames[len(z.nextNames)-1] = "" - z.nextNames = z.nextNames[:len(z.nextNames)-1] + copy(z.securityConfig.nsecNames[i:], z.securityConfig.nsecNames[i+1:]) + z.securityConfig.nsecNames[len(z.securityConfig.nsecNames)-1] = "" + z.securityConfig.nsecNames = z.securityConfig.nsecNames[:len(z.securityConfig.nsecNames)-1] if len(s) > 1 && s[0] == '*' && s[1] == '.' { z.Wildcard-- if z.Wildcard < 0 { @@ -431,12 +438,12 @@ func signerRoutine(z *Zone, wg *sync.WaitGroup, keys map[*DNSKEY]PrivateKey, key name = node.RR[x][0].Header().Name break } - i := sort.SearchStrings(z.nextNames, name) - if z.nextNames[i] == name { - if i+1 > len(z.nextNames) { + i := sort.SearchStrings(z.securityConfig.nsecNames, name) + if z.securityConfig.nsecNames[i] == name { + if i+1 > len(z.securityConfig.nsecNames) { next = z.Origin } else { - next = z.nextNames[i+1] + next = z.securityConfig.nsecNames[i+1] } } e := node.Sign(next, keys, keytags, config) From cfc222de3d61d4f1c2dc2d8c6b49398dbb407c53 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Tue, 7 May 2013 10:17:01 +0200 Subject: [PATCH 18/36] Add Sign3 --- zone.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/zone.go b/zone.go index 53a54571..dfc539cb 100644 --- a/zone.go +++ b/zone.go @@ -423,6 +423,12 @@ 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(z *Zone, wg *sync.WaitGroup, keys map[*DNSKEY]PrivateKey, keytags map[*DNSKEY]uint16, config *SignatureConfig, in chan *ZoneData, err chan error) { next := "" From 2fd0e458df9712f9477d3cd06c62e022f2671ccc Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Tue, 7 May 2013 10:33:20 +0200 Subject: [PATCH 19/36] Use Signature to be more in line with the non-plural RR --- zone.go | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/zone.go b/zone.go index dfc539cb..5badd260 100644 --- a/zone.go +++ b/zone.go @@ -107,7 +107,7 @@ func NewZone(origin string) *Zone { // ZoneData holds all the RRs for a specific owner name. type ZoneData struct { 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 + 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 } @@ -115,7 +115,7 @@ type ZoneData struct { func NewZoneData() *ZoneData { zd := new(ZoneData) zd.RR = make(map[uint16][]RR) - zd.Signatures = make(map[uint16][]*RRSIG) + zd.Signature = make(map[uint16][]*RRSIG) return zd } @@ -131,8 +131,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" } } @@ -147,8 +147,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" } } @@ -157,8 +157,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" } } @@ -184,7 +184,7 @@ func (z *Zone) Insert(r RR) error { 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 { @@ -205,7 +205,7 @@ func (z *Zone) Insert(r RR) error { 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: if r.Header().Name != z.Origin { zd.NonAuth = true @@ -230,13 +230,13 @@ func (z *Zone) Remove(r RR) error { switch t := r.Header().Rrtype; t { case TypeRRSIG: sigtype := r.(*RRSIG).TypeCovered - for i, zr := range zd.Signatures[sigtype] { + for i, zr := range zd.Signature[sigtype] { if r == zr { - zd.Signatures[sigtype] = append(zd.Signatures[sigtype][:i], zd.Signatures[sigtype][i+1:]...) + zd.Signature[sigtype] = append(zd.Signature[sigtype][:i], zd.Signature[sigtype][i+1:]...) } } - if len(zd.Signatures[sigtype]) == 0 { - delete(zd.Signatures, sigtype) + if len(zd.Signature[sigtype]) == 0 { + delete(zd.Signature, sigtype) } default: for i, zr := range zd.RR[t] { @@ -249,7 +249,7 @@ func (z *Zone) Remove(r RR) error { delete(zd.RR, t) } } - if len(zd.RR) == 0 && len(zd.Signatures) == 0 { + 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.securityConfig.nsecNames, r.Header().Name) @@ -304,8 +304,8 @@ func (z *Zone) RemoveRRset(s string, t uint16) error { switch t { case TypeRRSIG: // empty all signature maps - for cover, _ := range zd.Signatures { - delete(zd.Signatures, cover) + for cover, _ := range zd.Signature { + delete(zd.Signature, cover) } default: // empty all rr maps @@ -313,7 +313,7 @@ func (z *Zone) RemoveRRset(s string, t uint16) error { delete(zd.RR, t) } } - if len(zd.RR) == 0 && len(zd.Signatures) == 0 { + 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.securityConfig.nsecNames, s) @@ -508,14 +508,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{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 @@ -536,7 +536,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 @@ -551,20 +551,20 @@ 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:]...) } } } From e6e7649112668bf8744e924ec3265aaceda0910d Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Tue, 7 May 2013 19:41:14 +0200 Subject: [PATCH 20/36] move functions closer together --- zone.go | 56 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/zone.go b/zone.go index 5badd260..c6523328 100644 --- a/zone.go +++ b/zone.go @@ -15,7 +15,8 @@ import ( // 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 + olen int // Origin length + 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 @@ -82,7 +83,7 @@ func newSignatureConfig() *SignatureConfig { // 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 = "." @@ -92,6 +93,7 @@ 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.Names = make(map[string]*ZoneData) z.RWMutex = new(sync.RWMutex) @@ -106,9 +108,9 @@ func NewZone(origin string) *Zone { // ZoneData holds all the RRs for a specific owner name. type ZoneData struct { - 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 - NonAuth bool // Always false, except for NSsets that differ from z.Origin + NonAuth bool // Always false, except for NSsets that differ from z.Origin } // NewZoneData creates a new zone data element. @@ -174,6 +176,8 @@ func (z *Zone) Insert(r RR) error { return &Error{Err: "out of zone data", Name: r.Header().Name} } z.ModTime = time.Now().UTC() + // Remove the origin from the ownername of the RR + r.Header().Name = r.Header().Name[:len(r.Header().Name)-z.olen-1] zd, ok := z.Names[r.Header().Name] if !ok { // Check if it's a wildcard name @@ -360,6 +364,28 @@ func (z *Zone) isSubDomain(child string) bool { return compareLabelsSlice(z.olabels, strings.ToLower(child)) == len(z.olabels) } +// 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. @@ -611,25 +637,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 -} From 57cdce3f582c6928b7c6cc39b8629232cda0237c Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Tue, 7 May 2013 20:03:45 +0200 Subject: [PATCH 21/36] Add a isWildcard function --- zone.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/zone.go b/zone.go index c6523328..688f6c72 100644 --- a/zone.go +++ b/zone.go @@ -181,7 +181,7 @@ func (z *Zone) Insert(r RR) error { zd, ok := z.Names[r.Header().Name] if !ok { // Check if it's a wildcard name - if len(r.Header().Name) > 1 && r.Header().Name[0] == '*' && r.Header().Name[1] == '.' { + if isWildcard(r.Header().Name) { z.Wildcard++ } zd = NewZoneData() @@ -261,7 +261,7 @@ func (z *Zone) Remove(r RR) error { copy(z.securityConfig.nsecNames[i:], z.securityConfig.nsecNames[i+1:]) z.securityConfig.nsecNames[len(z.securityConfig.nsecNames)-1] = "" z.securityConfig.nsecNames = z.securityConfig.nsecNames[:len(z.securityConfig.nsecNames)-1] - if len(r.Header().Name) > 1 && r.Header().Name[0] == '*' && r.Header().Name[1] == '.' { + if isWildcard(r.Header().Name) { z.Wildcard-- if z.Wildcard < 0 { z.Wildcard = 0 @@ -286,7 +286,7 @@ func (z *Zone) RemoveName(s string) error { copy(z.securityConfig.nsecNames[i:], z.securityConfig.nsecNames[i+1:]) z.securityConfig.nsecNames[len(z.securityConfig.nsecNames)-1] = "" z.securityConfig.nsecNames = z.securityConfig.nsecNames[:len(z.securityConfig.nsecNames)-1] - if len(s) > 1 && s[0] == '*' && s[1] == '.' { + if isWildcard(s) { z.Wildcard-- if z.Wildcard < 0 { z.Wildcard = 0 @@ -597,7 +597,7 @@ func (node *ZoneData) Sign(next string, keys map[*DNSKEY]PrivateKey, keytags map 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 { @@ -637,3 +637,11 @@ func jitterDuration(d time.Duration) time.Duration { } return -time.Duration(jitter) } + +// isWildcard returns true when s is a wildcard name (first label is a "*") +func isWildcard(s string) bool { + if s == "*" || s == "*." { + return true + } + return false +} From 1133a3999622ba1ced3ec3d3df6b7cd05dc4ab22 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Tue, 7 May 2013 22:04:30 +0200 Subject: [PATCH 22/36] Beter test and isWildcard function --- parse_test.go | 2 +- zone.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/parse_test.go b/parse_test.go index 928b4d57..84c133ad 100644 --- a/parse_test.go +++ b/parse_test.go @@ -179,7 +179,7 @@ z2.miek.nl. IN NSEC miek.nl. TXT RRSIG NSEC` t.Fail() } if z.securityConfig.nsecNames[0] != "z1.miek.nl." || z.securityConfig.nsecNames[1] != "z2.miek.nl." || z.securityConfig.nsecNames[2] != "z3.miek.nl." { - t.Log("Not all names correct") + t.Logf("Not all names correct %v\n", z.securityConfig.nsecNames) t.Fail() } z.RemoveName("z2.miek.nl.") diff --git a/zone.go b/zone.go index 688f6c72..d042e112 100644 --- a/zone.go +++ b/zone.go @@ -640,7 +640,7 @@ func jitterDuration(d time.Duration) time.Duration { // isWildcard returns true when s is a wildcard name (first label is a "*") func isWildcard(s string) bool { - if s == "*" || s == "*." { + if s == "*." { return true } return false From fbea2567e5dcabd06c59c0849995ce5586389969 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Tue, 7 May 2013 22:07:20 +0200 Subject: [PATCH 23/36] Remove some stuff --- zone.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/zone.go b/zone.go index d042e112..8359f660 100644 --- a/zone.go +++ b/zone.go @@ -11,6 +11,10 @@ import ( "time" ) +// 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 { @@ -176,8 +180,6 @@ func (z *Zone) Insert(r RR) error { return &Error{Err: "out of zone data", Name: r.Header().Name} } z.ModTime = time.Now().UTC() - // Remove the origin from the ownername of the RR - r.Header().Name = r.Header().Name[:len(r.Header().Name)-z.olen-1] zd, ok := z.Names[r.Header().Name] if !ok { // Check if it's a wildcard name From 204a4c739373c93c46dbde38c17eced20457a078 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Wed, 8 May 2013 20:47:58 +0200 Subject: [PATCH 24/36] isWildcard is useless - to look for it during a query --- zone.go | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/zone.go b/zone.go index 8359f660..e743743c 100644 --- a/zone.go +++ b/zone.go @@ -21,7 +21,6 @@ type Zone struct { Origin string // Origin of the zone olen int // Origin length 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 Names map[string]*ZoneData // Zone data, indexed by owner name @@ -182,10 +181,6 @@ func (z *Zone) Insert(r RR) error { z.ModTime = time.Now().UTC() zd, ok := z.Names[r.Header().Name] if !ok { - // Check if it's a wildcard name - if isWildcard(r.Header().Name) { - z.Wildcard++ - } zd = NewZoneData() switch t := r.Header().Rrtype; t { case TypeRRSIG: @@ -263,12 +258,6 @@ func (z *Zone) Remove(r RR) error { copy(z.securityConfig.nsecNames[i:], z.securityConfig.nsecNames[i+1:]) z.securityConfig.nsecNames[len(z.securityConfig.nsecNames)-1] = "" z.securityConfig.nsecNames = z.securityConfig.nsecNames[:len(z.securityConfig.nsecNames)-1] - if isWildcard(r.Header().Name) { - z.Wildcard-- - if z.Wildcard < 0 { - z.Wildcard = 0 - } - } } return nil } @@ -288,12 +277,6 @@ func (z *Zone) RemoveName(s string) error { copy(z.securityConfig.nsecNames[i:], z.securityConfig.nsecNames[i+1:]) z.securityConfig.nsecNames[len(z.securityConfig.nsecNames)-1] = "" z.securityConfig.nsecNames = z.securityConfig.nsecNames[:len(z.securityConfig.nsecNames)-1] - if isWildcard(s) { - z.Wildcard-- - if z.Wildcard < 0 { - z.Wildcard = 0 - } - } return nil } @@ -327,12 +310,6 @@ func (z *Zone) RemoveRRset(s string, t uint16) error { copy(z.securityConfig.nsecNames[i:], z.securityConfig.nsecNames[i+1:]) z.securityConfig.nsecNames[len(z.securityConfig.nsecNames)-1] = "" z.securityConfig.nsecNames = z.securityConfig.nsecNames[:len(z.securityConfig.nsecNames)-1] - if len(s) > 1 && s[0] == '*' && s[1] == '.' { - z.Wildcard-- - if z.Wildcard < 0 { - z.Wildcard = 0 - } - } } return nil } @@ -639,11 +616,3 @@ func jitterDuration(d time.Duration) time.Duration { } return -time.Duration(jitter) } - -// isWildcard returns true when s is a wildcard name (first label is a "*") -func isWildcard(s string) bool { - if s == "*." { - return true - } - return false -} From a5a709cd2a2462712914caef927b278c3c182fbf Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Thu, 9 May 2013 09:30:38 +0200 Subject: [PATCH 25/36] Rename securityConfig to sortedNames --- parse_test.go | 10 ++++----- zone.go | 58 +++++++++++++++++++++++++-------------------------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/parse_test.go b/parse_test.go index 84c133ad..60ff37f8 100644 --- a/parse_test.go +++ b/parse_test.go @@ -174,17 +174,17 @@ z2.miek.nl. IN NSEC miek.nl. TXT RRSIG NSEC` i++ z.Insert(x.RR) } - if len(z.securityConfig.nsecNames) != 3 { + if len(z.sortedNames.nsecNames) != 3 { t.Log("Length not 3?") t.Fail() } - if z.securityConfig.nsecNames[0] != "z1.miek.nl." || z.securityConfig.nsecNames[1] != "z2.miek.nl." || z.securityConfig.nsecNames[2] != "z3.miek.nl." { - t.Logf("Not all names correct %v\n", z.securityConfig.nsecNames) + 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.securityConfig.nsecNames[0] != "z1.miek.nl." || z.securityConfig.nsecNames[1] != "z3.miek.nl." { - t.Logf("Not all names correct %v\n", z.securityConfig.nsecNames) + 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/zone.go b/zone.go index e743743c..92bee9cb 100644 --- a/zone.go +++ b/zone.go @@ -18,20 +18,20 @@ import ( // Zone represents a DNS zone. It's safe for concurrent use by // multilpe goroutines. type Zone struct { - 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 - *securityConfig // Sorted names for either NSEC or NSEC3 + 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 securityConfig struct { +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 @@ -101,7 +101,7 @@ func NewZone(origin string) *Zone { z.Names = make(map[string]*ZoneData) z.RWMutex = new(sync.RWMutex) z.ModTime = time.Now().UTC() - z.securityConfig = &securityConfig{make([]string, 0), make([]string, 0)} + z.sortedNames = &sortedNames{make([]string, 0), make([]string, 0)} return z } @@ -196,10 +196,10 @@ func (z *Zone) Insert(r RR) error { zd.RR[t] = append(zd.RR[t], r) } z.Names[r.Header().Name] = zd - i := sort.SearchStrings(z.securityConfig.nsecNames, r.Header().Name) - z.securityConfig.nsecNames = append(z.securityConfig.nsecNames, "") - copy(z.securityConfig.nsecNames[i+1:], z.securityConfig.nsecNames[i:]) - z.securityConfig.nsecNames[i] = r.Header().Name + 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 } // Name already there @@ -253,11 +253,11 @@ func (z *Zone) Remove(r RR) error { 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.securityConfig.nsecNames, 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.securityConfig.nsecNames[i:], z.securityConfig.nsecNames[i+1:]) - z.securityConfig.nsecNames[len(z.securityConfig.nsecNames)-1] = "" - z.securityConfig.nsecNames = z.securityConfig.nsecNames[:len(z.securityConfig.nsecNames)-1] + 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 } @@ -273,10 +273,10 @@ func (z *Zone) RemoveName(s string) error { } z.ModTime = time.Now().UTC() delete(z.Names, s) - i := sort.SearchStrings(z.securityConfig.nsecNames, s) - copy(z.securityConfig.nsecNames[i:], z.securityConfig.nsecNames[i+1:]) - z.securityConfig.nsecNames[len(z.securityConfig.nsecNames)-1] = "" - z.securityConfig.nsecNames = z.securityConfig.nsecNames[:len(z.securityConfig.nsecNames)-1] + 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 } @@ -305,11 +305,11 @@ func (z *Zone) RemoveRRset(s string, t uint16) error { 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.securityConfig.nsecNames, s) + i := sort.SearchStrings(z.sortedNames.nsecNames, s) // we actually removed something if we are here, so i must be something sensible - copy(z.securityConfig.nsecNames[i:], z.securityConfig.nsecNames[i+1:]) - z.securityConfig.nsecNames[len(z.securityConfig.nsecNames)-1] = "" - z.securityConfig.nsecNames = z.securityConfig.nsecNames[:len(z.securityConfig.nsecNames)-1] + 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 } @@ -449,12 +449,12 @@ func signerRoutine(z *Zone, wg *sync.WaitGroup, keys map[*DNSKEY]PrivateKey, key name = node.RR[x][0].Header().Name break } - i := sort.SearchStrings(z.securityConfig.nsecNames, name) - if z.securityConfig.nsecNames[i] == name { - if i+1 > len(z.securityConfig.nsecNames) { + 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.securityConfig.nsecNames[i+1] + next = z.sortedNames.nsecNames[i+1] } } e := node.Sign(next, keys, keytags, config) From d18bb64db1494ea46bd05c7662a43b5c7ea04ca3 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Thu, 9 May 2013 09:52:16 +0200 Subject: [PATCH 26/36] Add notification function on zone changed --- zone.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/zone.go b/zone.go index 92bee9cb..0522fc89 100644 --- a/zone.go +++ b/zone.go @@ -29,6 +29,11 @@ type Zone struct { // 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 + // If not nil, this function is called after each modification + // current == nil, update != nil -> update RR was added to the zone + // current != nil, update != nil -> update RR is changed from current to update + // current != nil, update == nil -> current RR is removed from the zone + NotifyFunc func(current, update RR) } type sortedNames struct { From e47e7192c72dd9f5399e2154b41266cae7227441 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Thu, 9 May 2013 10:35:03 +0200 Subject: [PATCH 27/36] Update docs for NotifyFunc --- zone.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/zone.go b/zone.go index 0522fc89..09a49bab 100644 --- a/zone.go +++ b/zone.go @@ -29,10 +29,8 @@ type Zone struct { // 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 - // If not nil, this function is called after each modification - // current == nil, update != nil -> update RR was added to the zone - // current != nil, update != nil -> update RR is changed from current to update - // current != nil, update == nil -> current RR is removed from the zone + // If not nil, this function is called after each modification, current is + // the current RR in the zone and update is the new one. NotifyFunc func(current, update RR) } From bb6de9dc2c837ae3c22eaffb9361cdb41437ea7c Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Thu, 9 May 2013 10:41:41 +0200 Subject: [PATCH 28/36] better doc for the notifyfunc --- zone.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zone.go b/zone.go index 09a49bab..76feefbb 100644 --- a/zone.go +++ b/zone.go @@ -29,8 +29,8 @@ type Zone struct { // 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 - // If not nil, this function is called after each modification, current is - // the current RR in the zone and update is the new one. + // If not nil, this function is called after each modification, with the current + // RR and the updated one. NotifyFunc func(current, update RR) } From 99878f2cdf87fa33812732451c8f63a291b8dbab Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Fri, 10 May 2013 15:36:32 +0200 Subject: [PATCH 29/36] documentation --- zone.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/zone.go b/zone.go index 76feefbb..621392f4 100644 --- a/zone.go +++ b/zone.go @@ -29,9 +29,8 @@ type Zone struct { // 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 - // If not nil, this function is called after each modification, with the current - // RR and the updated one. - NotifyFunc func(current, update RR) + // If not nil, this function is called after each modification. + NotifyFunc func(inserted, deleted RR) } type sortedNames struct { From 48a8ce58cd1f78c1310381f1bbc33baac2d113f3 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Fri, 10 May 2013 15:40:56 +0200 Subject: [PATCH 30/36] documentation --- zone.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone.go b/zone.go index 621392f4..7d7f9208 100644 --- a/zone.go +++ b/zone.go @@ -74,7 +74,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 } From 0363410587de3e2fb3b0de6d39e204f0a0a85123 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Fri, 10 May 2013 17:51:18 +0200 Subject: [PATCH 31/36] NotifyFunc is a bit too much --- zone.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/zone.go b/zone.go index 7d7f9208..4c98cc51 100644 --- a/zone.go +++ b/zone.go @@ -29,8 +29,6 @@ type Zone struct { // 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 - // If not nil, this function is called after each modification. - NotifyFunc func(inserted, deleted RR) } type sortedNames struct { From 5537ac771d2bb02dd402aa223cfb7aafbc0690bd Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Sun, 12 May 2013 15:56:12 +0200 Subject: [PATCH 32/36] Recheck the server mux match logic And a test to make sure it all works. The root zone can still be configured as a last resort (wildcard) match. --- server.go | 6 ++---- server_test.go | 10 ++++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/server.go b/server.go index f4ea2a58..4935e3c5 100644 --- a/server.go +++ b/server.go @@ -69,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 @@ -173,7 +173,6 @@ func (mux *ServeMux) match(q string, t uint16) Handler { lastbyte byte seendot bool = true ) - // TODO(mg): check for . for i := 0; i < len(q); i++ { if seendot { if h, ok := mux.z[q[lastdot+1:]]; ok { @@ -194,8 +193,7 @@ func (mux *ServeMux) match(q string, t uint16) Handler { } lastbyte = q[i] } - // Check for the root zone too, this only delays NXDOMAIN, because if we serve . it - // will be catched above. + // Wildcard match, if we have found nothing try the root zone as a last resort. if h, ok := mux.z["."]; ok { return h } diff --git a/server_test.go b/server_test.go index 0932567d..b6e93f51 100644 --- a/server_test.go +++ b/server_test.go @@ -96,3 +96,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") + } +} From 98b677a97c0770d4713c23aed2236bec66eba1ec Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Sun, 12 May 2013 16:07:06 +0200 Subject: [PATCH 33/36] Update the copy right for the original GO files --- LICENSE | 2 +- clientconfig.go | 1 + dns.go | 2 +- msg.go | 1 + rawmsg.go | 2 +- types.go | 4 ++-- 6 files changed, 7 insertions(+), 5 deletions(-) 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/clientconfig.go b/clientconfig.go index f6284fed..174350fa 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/dns.go b/dns.go index 9a5e091c..050e7297 100644 --- a/dns.go +++ b/dns.go @@ -1,7 +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. -// 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. diff --git a/msg.go b/msg.go index d8300ca2..1bf68b09 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. 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/types.go b/types.go index d9ebd81c..34b9c846 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 From f595bf7a06794b85c26cb0d237f6fa614783a48b Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Sun, 12 May 2013 16:09:52 +0200 Subject: [PATCH 34/36] Set license on test files --- client.go | 4 ++++ client_test.go | 4 ++++ dns_test.go | 4 ++++ dnssec_test.go | 4 ++++ dyn_test.go | 4 ++++ example_test.go | 4 ++++ labels_test.go | 4 ++++ nsec3_test.go | 4 ++++ parse_test.go | 4 ++++ server_test.go | 4 ++++ zone_test.go | 4 ++++ 11 files changed, 44 insertions(+) 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 e96ff7c3..e4a63329 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/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_test.go b/dnssec_test.go index 4228d1a8..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 ( 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/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/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/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/parse_test.go b/parse_test.go index 60ff37f8..e14de117 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 ( diff --git a/server_test.go b/server_test.go index b6e93f51..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 ( diff --git a/zone_test.go b/zone_test.go index de4c1c33..22338fd7 100644 --- a/zone_test.go +++ b/zone_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 "testing" From b4ec8ac33ea08fc9684b972205eb4dde1fc95cd7 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Sun, 12 May 2013 16:11:13 +0200 Subject: [PATCH 35/36] Fix copyright --- ex/chaos/chaos.go | 4 ++++ ex/check-soa/check-soa.go | 4 ++++ ex/q/q.go | 4 ++++ ex/reflect/reflect.go | 4 ++++ 4 files changed, 16 insertions(+) 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 fcc1cf80..a134ccd9 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. From 81941d442283c4d0f321cbd14950de6440c59e5b Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Sun, 12 May 2013 16:15:52 +0200 Subject: [PATCH 36/36] Update all copyright notices --- defaults.go | 4 ++++ dnssec.go | 4 +++- edns.go | 4 ++++ keygen.go | 4 ++++ kscan.go | 4 ++++ labels.go | 4 ++++ nsecx.go | 4 ++++ scanner.go | 4 ++++ tlsa.go | 4 ++++ tsig.go | 4 ++++ update.go | 4 ++++ xfr.go | 4 ++++ zgenerate.go | 4 ++++ zone.go | 4 ++++ zscan.go | 4 ++++ zscan_rr.go | 4 ++++ 16 files changed, 63 insertions(+), 1 deletion(-) diff --git a/defaults.go b/defaults.go index 808f5ab4..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 ( diff --git a/dnssec.go b/dnssec.go index dcfb5e1d..8d68b312 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/edns.go b/edns.go index 1aa6cace..579fb4cc 100644 --- a/edns.go +++ b/edns.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. + // EDNS0 // // EDNS0 is an extension mechanism for the DNS defined in RFC 2671. It defines a diff --git a/keygen.go b/keygen.go index bc1a0797..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 ( diff --git a/kscan.go b/kscan.go index e70396d3..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 ( 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/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/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/tlsa.go b/tlsa.go index d3bc3b02..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 ( diff --git a/tsig.go b/tsig.go index 7aaade74..26b307d9 100644 --- a/tsig.go +++ b/tsig.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. + // TRANSACTION SIGNATURE (TSIG) // // An TSIG or transaction signature adds a HMAC TSIG record to each message sent. diff --git a/update.go b/update.go index eb2bf5a7..6e6f1947 100644 --- a/update.go +++ b/update.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. + // DYNAMIC UPDATES // // Dynamic updates reuses the DNS message format, but renames three of diff --git a/xfr.go b/xfr.go index 748deaf3..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. 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 4c98cc51..abea956a 100644 --- a/zone.go +++ b/zone.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 structure for handling zone data diff --git a/zscan.go b/zscan.go index b6bfff40..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 ( diff --git a/zscan_rr.go b/zscan_rr.go index a1c46335..3251c1e1 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 (