diff --git a/backend.go b/backend.go index 226a7fd..184f001 100644 --- a/backend.go +++ b/backend.go @@ -8,6 +8,8 @@ import "encoding/hex" import "fmt" import "strings" import "net" +import "github.com/hlandau/ncdns/namecoin" +import "github.com/hlandau/ncdns/ncerr" type Backend interface { // Lookup all resource records having a given fully-qualified owner name, @@ -25,7 +27,7 @@ type Backend interface { type ncBackend struct { s *Server - nc NamecoinConn + nc namecoin.NamecoinConn cache lru.Cache // items are of type *Domain } @@ -125,7 +127,7 @@ func (tx *Btx) determineDomain() (subname, basename, rootname string, err error) parts := strings.Split(qname, ".") if len(parts) < 2 { if parts[0] != "bit" { - err = ErrNotInZone + err = ncerr.ErrNotInZone return } @@ -150,7 +152,7 @@ func (tx *Btx) determineDomain() (subname, basename, rootname string, err error) } } - err = ErrNotInZone + err = ncerr.ErrNotInZone return } @@ -165,7 +167,7 @@ func (tx *Btx) Do() (rrs []dns.RR, err error) { if tx.rootname == "" { // REFUSED - return nil, ErrNotInZone + return nil, ncerr.ErrNotInZone } if tx.subname == "" && tx.basename == "" { @@ -272,7 +274,7 @@ func (tx *Btx) doUserDomain() (rrs []dns.RR, err error) { func (tx *Btx) doUnderDomain(d *Domain) (rrs []dns.RR, err error) { rrs, err = tx.addAnswersUnderNCValue(d.ncv, tx.subname) - if err == ErrNoResults { + if err == ncerr.ErrNoResults { err = nil } @@ -315,14 +317,14 @@ func (tx *Btx) _findNCValue(ncv *ncValue, isubname, subname string, depth int, if !ok { sub, ok = ncv.Map["*"] if !ok { - return nil, "", ErrNoSuchDomain + return nil, "", ncerr.ErrNoSuchDomain } } return tx._findNCValue(sub, rest, head + "." + subname, depth+1, shortCircuitFunc) } if shortCircuitFunc != nil { - return nil, subname, ErrNoSuchDomain + return nil, subname, ncerr.ErrNoSuchDomain } return ncv, subname, nil @@ -493,16 +495,6 @@ func (ncv *ncValue) GetDSs() (dss []dns.DS, err error) { // f[b]("a", "b.c.d.e.f.g.zzz.bit") // f[a]("", "a.b.c.d.e.f.g.zzz.bit") -func absname(n string) string { - if n == "" { - return "." - } - if n[len(n)-1] != '.' { - return n + "." - } - return n -} - // Do low-level queries against an abstract zone file. func (b *ncBackend) Lookup(qname string) (rrs []dns.RR, err error) { btx := &Btx{} diff --git a/namecoin.go b/namecoin/namecoin.go similarity index 93% rename from namecoin.go rename to namecoin/namecoin.go index a0fa33d..8b60d6f 100644 --- a/namecoin.go +++ b/namecoin/namecoin.go @@ -1,10 +1,14 @@ -package main +package namecoin + +// btcjson had to be modified a bit to get correct error reporting. import "github.com/hlandauf/btcjson" +import "github.com/hlandau/ncdns/ncerr" + import "encoding/json" import "sync/atomic" import "fmt" -//import "github.com/hlandau/degoutils/log" +// Used for generating IDs for JSON-RPC requests. var idCounter int32 = 0 func newID() int32 { @@ -108,7 +112,7 @@ func (nc *NamecoinConn) Query(name string) (v string, err error) { if r.Error != nil { //log.Info("RPC error: ", r.Error) if r.Error.Code == -4 { - return "", ErrNoSuchDomain + return "", ncerr.ErrNoSuchDomain } return "", r.Error } diff --git a/ncdns.go b/ncdns.go index bb3f8f2..0698be2 100644 --- a/ncdns.go +++ b/ncdns.go @@ -8,6 +8,7 @@ import "fmt" import "strings" import "sort" import "github.com/hlandau/degoutils/config" +import "github.com/hlandau/ncdns/ncerr" // A Go daemon to serve Namecoin domain records via DNS. // This daemon is intended to be used in one of the following situations: @@ -161,10 +162,6 @@ func (s *Server) runListener(net string) *dns.Server { return ds } -var ErrNoSuchDomain = rerrorf(dns.RcodeNameError, "no such domain") -var ErrNotInZone = rerrorf(dns.RcodeRefused, "domain not in zone") -var ErrNoResults = rerrorf(0, "no results") - type Tx struct { req *dns.Msg res *dns.Msg @@ -224,9 +221,9 @@ func (s *Server) handle(rw dns.ResponseWriter, reqMsg *dns.Msg) { err := tx.addAnswers() if err != nil { - if err == ErrNoResults { + if err == ncerr.ErrNoResults { tx.rcode = 0 - } else if err == ErrNoSuchDomain { + } else if err == ncerr.ErrNoSuchDomain { tx.rcode = dns.RcodeNameError } else if tx.rcode == 0 { log.Infoe(err, "Handler error, doing SERVFAIL") @@ -248,7 +245,7 @@ func (tx *Tx) blookup(qname string) (rrs []dns.RR, err error) { log.Info("blookup: ", qname) rrs, err = tx.s.b.Lookup(qname) if err == nil && len(rrs) == 0 { - err = ErrNoResults + err = ncerr.ErrNoResults } return } @@ -380,7 +377,7 @@ A: if soa == nil { // If we didn't even get a SOA at any point, we don't have any appropriate zone for this query. - return ErrNotInZone + return ncerr.ErrNotInZone } tx.soa = soa diff --git a/ncerr/ncerr.go b/ncerr/ncerr.go new file mode 100644 index 0000000..6172bfa --- /dev/null +++ b/ncerr/ncerr.go @@ -0,0 +1,35 @@ +package ncerr +import "github.com/miekg/dns" +import "fmt" + +// An Error interface which allows an associated rcode to be queried. +type Error interface { + error + Rcode() int +} + +type Rerr struct { + error + e error + rcode int +} + +func (re *Rerr) Error() string { + return re.e.Error() +} + +func (re *Rerr) Rcode() int { + return re.rcode +} + +func rerrorf(rcode int, fmts string, args ...interface{}) Error { + re := &Rerr{} + re.e = fmt.Errorf(fmts, args...) + re.rcode = rcode + return re +} + +// Standard errors. +var ErrNoSuchDomain = rerrorf(dns.RcodeNameError, "no such domain") +var ErrNotInZone = rerrorf(dns.RcodeRefused, "domain not in zone") +var ErrNoResults = rerrorf(0, "no results") diff --git a/util.go b/util.go index 14cc09f..83434c2 100644 --- a/util.go +++ b/util.go @@ -6,6 +6,18 @@ import "github.com/miekg/dns" import "github.com/hlandau/degoutils/log" import "time" +// miekg/dns demands a superflous trailing dot, this makes sure it is correctly appended. +func absname(n string) string { + if n == "" { + return "." + } + if n[len(n)-1] != '.' { + return n + "." + } + return n +} + +// Split a domain name a.b.c.d.e into parts a (the head) and b.c.d.e (the rest). func splitDomainHead(name string) (head string, rest string, err error) { parts := strings.Split(name, ".") @@ -18,32 +30,25 @@ func splitDomainHead(name string) (head string, rest string, err error) { return } -// unused -func splitDomainName(name string) (parts []string) { - if len(name) == 0 { - return - } - - if name[len(name)-1] == '.' { - name = name[0:len(name)-1] - } - - parts = strings.Split(name, ".") - - return +// Determines if a transaction should be considered to have the given query type. +// Returns true iff the query type was qtype or ANY. +func (tx *Tx) istype(qtype uint16) bool { + return tx.qtype == qtype || tx.qtype == dns.TypeANY } -func (tx *Tx) istype(x uint16) bool { - return tx.qtype == x || tx.qtype == dns.TypeANY -} - -func stepName(n string) string { - if len(n) == 0 { +// This is used in NSEC3 hash generation. A hash like ...decafbad has one added +// to it so that it becomes ...decafbae. This is needed because NSEC3's hashes +// are inclusive-exclusive (i.e. "[,)"), and we want a hash that covers only the +// name specified. +// +// Takes a hash in base32hex form. +func stepName(hashB32Hex string) string { + if len(hashB32Hex) == 0 { return "" } - b, err := base32.HexEncoding.DecodeString(n) - log.Panice(err, n) + b, err := base32.HexEncoding.DecodeString(hashB32Hex) + log.Panice(err, hashB32Hex) for i := len(b)-1; i>=0; i-- { b[i] += 1 @@ -55,8 +60,8 @@ func stepName(n string) string { return base32.HexEncoding.EncodeToString(b) } +// Returns true iff a type should be covered by a RRSIG. func shouldSignType(t uint16, isAuthoritySection bool) bool { - //log.Info("shouldSignType ", t, " ", isAuthoritySection) switch t { case dns.TypeOPT: return false @@ -67,6 +72,7 @@ func shouldSignType(t uint16, isAuthoritySection bool) bool { } } +// Returns true iff a client requested DNSSEC. func (tx *Tx) useDNSSEC() bool { opt := tx.req.IsEdns0() if opt == nil { @@ -75,38 +81,18 @@ func (tx *Tx) useDNSSEC() bool { return opt.Do() } +// Sets an rcode for the response if there is no error rcode currently set for +// the response. The idea is to return the rcode corresponding to the first +// error which occurs. func (tx *Tx) setRcode(x int) { if tx.rcode == 0 { tx.rcode = x } } -type Error interface { - error - Rcode() int -} - -type Rerr struct { - error - e error - rcode int -} - -func (re *Rerr) Error() string { - return re.e.Error() -} - -func (re *Rerr) Rcode() int { - return re.rcode -} - -func rerrorf(rcode int, fmts string, args ...interface{}) Error { - re := &Rerr{} - re.e = fmt.Errorf(fmts, args...) - re.rcode = rcode - return re -} +// Determines the maximum TTL for a slice of resource records. +// Returns 0 if the slice is empty. func rraMaxTTL(rra []dns.RR) uint32 { x := uint32(0) for _, rr := range rra { @@ -118,6 +104,7 @@ func rraMaxTTL(rra []dns.RR) uint32 { return x } +// Used by signResponseSection. func (tx *Tx) signRRs(rra []dns.RR, useKSK bool) (dns.RR, error) { if len(rra) == 0 { return nil, fmt.Errorf("no RRs to such") @@ -152,6 +139,7 @@ func (tx *Tx) signRRs(rra []dns.RR, useKSK bool) (dns.RR, error) { return rrsig, nil } +// Used by signResponse. func (tx *Tx) signResponseSection(rra *[]dns.RR) error { if len(*rra) == 0 { return nil @@ -202,6 +190,8 @@ func (tx *Tx) signResponseSection(rra *[]dns.RR) error { return nil } +// This is called to append RRSIGs to the response based on the current records in the Answer and +// Authority sections of the response. Records in the Additional section are not signed. func (tx *Tx) signResponse() error { if !tx.useDNSSEC() { return nil @@ -219,6 +209,7 @@ func (tx *Tx) signResponse() error { return nil } +// Used for sorting RRTYPE lists for encoding into type bit maps. type uint16Slice []uint16 func (p uint16Slice) Len() int { return len(p) } func (p uint16Slice) Less(i, j int) bool { return p[i] < p[j] }