major refactoring

pull/18/head
Hugo Landau 10 years ago
parent 12b30fa15c
commit db0f0b6bbb

@ -1,16 +0,0 @@
package abstract
import "github.com/miekg/dns"
type Backend interface {
// Lookup all resource records having a given fully-qualified owner name,
// regardless of type or class. Returns a slice of all those resource records
// or an error.
//
// The returned slice may contain both authoritative and non-authoritative records
// (for example, NS records for delegations and glue records.)
//
// The existence of wildcard records will be determined by doing a lookup for a name
// like "*.example.com", so there is no need to process the wildcard logic other than
// to make sure such a lookup functions correctly.
Lookup(qname string) (rrs []dns.RR, err error)
}

@ -9,11 +9,10 @@ import "fmt"
import "strings"
import "net"
import "github.com/hlandau/ncdns/namecoin"
import "github.com/hlandau/ncdns/ncerr"
import "github.com/hlandau/madns/merr"
import "github.com/hlandau/ncdns/util"
import "github.com/hlandau/ncdns/abstract"
type ncBackend struct {
type Backend struct {
//s *Server
nc namecoin.NamecoinConn
cache lru.Cache // items are of type *Domain
@ -46,8 +45,8 @@ type Config struct {
}
// Creates a new Namecoin backend.
func New(cfg *Config) (backend abstract.Backend, err error) {
b := &ncBackend{}
func New(cfg *Config) (backend *Backend, err error) {
b := &Backend{}
b.cfg = *cfg
b.nc.Username = cfg.RPCUsername
@ -85,7 +84,7 @@ func toNamecoinName(basename string) (string, error) {
return "d/" + basename, nil
}
func (b *ncBackend) getNamecoinEntry(name string) (*domain, error) {
func (b *Backend) getNamecoinEntry(name string) (*domain, error) {
if dd, ok := b.cache.Get(name); ok {
d := dd.(*domain)
return d, nil
@ -100,7 +99,7 @@ func (b *ncBackend) getNamecoinEntry(name string) (*domain, error) {
return d, nil
}
func (b *ncBackend) getNamecoinEntryLL(name string) (*domain, error) {
func (b *Backend) getNamecoinEntryLL(name string) (*domain, error) {
v, err := b.nc.Query(name)
if err != nil {
log.Infoe(err, "namecoin query failed: ", err)
@ -118,7 +117,7 @@ func (b *ncBackend) getNamecoinEntryLL(name string) (*domain, error) {
return d, nil
}
func (b *ncBackend) jsonToDomain(v string) (dd *domain, err error) {
func (b *Backend) jsonToDomain(v string) (dd *domain, err error) {
d := &domain{}
ncv := &ncValue{}
@ -135,7 +134,7 @@ func (b *ncBackend) jsonToDomain(v string) (dd *domain, err error) {
}
type btx struct {
b *ncBackend
b *Backend
qname string
subname, basename, rootname string
@ -147,7 +146,7 @@ func (tx *btx) determineDomain() (subname, basename, rootname string, err error)
parts := strings.Split(qname, ".")
if len(parts) < 2 {
if parts[0] != "bit" {
err = ncerr.ErrNotInZone
err = merr.ErrNotInZone
return
}
@ -172,7 +171,7 @@ func (tx *btx) determineDomain() (subname, basename, rootname string, err error)
}
}
err = ncerr.ErrNotInZone
err = merr.ErrNotInZone
return
}
@ -187,7 +186,7 @@ func (tx *btx) Do() (rrs []dns.RR, err error) {
if tx.rootname == "" {
// REFUSED
return nil, ncerr.ErrNotInZone
return nil, merr.ErrNotInZone
}
if tx.subname == "" && tx.basename == "" {
@ -294,7 +293,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 == ncerr.ErrNoResults {
if err == merr.ErrNoResults {
err = nil
}
@ -337,14 +336,14 @@ func (tx *btx) _findNCValue(ncv *ncValue, isubname, subname string, depth int,
if !ok {
sub, ok = ncv.Map["*"]
if !ok {
return nil, "", ncerr.ErrNoSuchDomain
return nil, "", merr.ErrNoSuchDomain
}
}
return tx._findNCValue(sub, rest, head + "." + subname, depth+1, shortCircuitFunc)
}
if shortCircuitFunc != nil {
return nil, subname, ncerr.ErrNoSuchDomain
return nil, subname, merr.ErrNoSuchDomain
}
return ncv, subname, nil
@ -571,7 +570,7 @@ func (ncv *ncValue) GetDSs() (dss []dns.DS, err error) {
// f[a]("", "a.b.c.d.e.f.g.zzz.bit")
// Do low-level queries against an abstract zone file.
func (b *ncBackend) Lookup(qname string) (rrs []dns.RR, err error) {
func (b *Backend) Lookup(qname string) (rrs []dns.RR, err error) {
btx := &btx{}
btx.b = b
btx.qname = qname

@ -0,0 +1,17 @@
package main
import "github.com/hlandau/degoutils/config"
import "github.com/hlandau/degoutils/log"
import "github.com/hlandau/ncdns/server"
func main() {
cfg := server.ServerConfig{}
config := config.Configurator{
ProgramName: "ncdns",
ConfigFilePaths: []string { "etc/ncdns.conf", "/etc/ncdns/ncdns.conf", },
}
config.ParseFatal(&cfg)
s, err := server.NewServer(&cfg)
log.Fatale(err)
s.Run()
}

@ -2,7 +2,7 @@ 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 "github.com/hlandau/madns/merr"
import "github.com/hlandau/ncdns/namecoin/extratypes"
import "sync/atomic"
@ -58,7 +58,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 "", ncerr.ErrNoSuchDomain
return "", merr.ErrNoSuchDomain
}
return "", r.Error
}

@ -1,618 +0,0 @@
package main
import "github.com/miekg/dns"
import "github.com/hlandau/degoutils/log"
import "os/signal"
import "os"
import "syscall"
import "fmt"
import "strings"
import "sort"
import "github.com/hlandau/degoutils/config"
import "github.com/hlandau/ncdns/ncerr"
import "github.com/hlandau/ncdns/abstract"
import "github.com/hlandau/ncdns/backend"
// A Go daemon to serve Namecoin domain records via DNS.
// This daemon is intended to be used in one of the following situations:
//
// 1. It is desired to mirror a domain name suffix (bit.suffix) to the .bit TLD.
// Accordingly, bit.suffix is delegated to one or more servers each running this daemon.
//
// 2. It is desired to act as an authoritative server for the .bit TLD directly.
// For example, a recursive DNS resolver is configured to override the root zone and use
// a server running this daemon for .bit. Or .bit is added to the root zone (when pigs fly).
//
// If the Unbound recursive DNS resolver were used:
// unbound.conf:
// server:
// stub-zone:
// name: bit
// stub-addr: 127.0.0.1@1153
//
// This daemon currently requires namecoind or a compatible daemon running with JSON-RPC interface.
// The name_* API calls are used to obtain .bit domain information.
func main() {
cfg := ServerConfig {}
config := config.Configurator{
ProgramName: "ncdns",
ConfigFilePaths: []string { "etc/ncdns.conf", "/etc/ncdns/ncdns.conf", },
}
config.ParseFatal(&cfg)
s := NewServer(&cfg)
s.Run()
}
func NewServer(cfg *ServerConfig) *Server {
s := &Server{}
s.cfg = *cfg
return s
}
func (s *Server) loadKey(fn, privateFn string) (k *dns.DNSKEY, privatek dns.PrivateKey, err error) {
f, err := os.Open(fn)
if err != nil {
return
}
rr, err := dns.ReadRR(f, fn)
if err != nil {
return
}
k, ok := rr.(*dns.DNSKEY)
if !ok {
err = fmt.Errorf("Loaded record from key file, but it wasn't a DNSKEY")
return
}
privatef, err := os.Open(privateFn)
if err != nil {
return
}
privatek, err = k.ReadPrivateKey(privatef, privateFn)
log.Fatale(err)
return
}
func (s *Server) Run() {
var err error
s.mux = dns.NewServeMux()
s.mux.HandleFunc(".", s.handle)
// key setup
s.ksk, s.kskPrivate, err = s.loadKey(s.cfg.PublicKey, s.cfg.PrivateKey)
log.Fatale(err, "error reading KSK key")
if s.cfg.ZonePublicKey != "" {
s.zsk, s.zskPrivate, err = s.loadKey(s.cfg.ZonePublicKey, s.cfg.ZonePrivateKey)
log.Fatale(err, "error reading ZSK key")
} else {
s.zsk = &dns.DNSKEY{}
s.zsk.Hdr.Rrtype = dns.TypeDNSKEY
s.zsk.Hdr.Class = dns.ClassINET
s.zsk.Hdr.Ttl = 3600
s.zsk.Algorithm = dns.RSASHA256
s.zsk.Protocol = 3
s.zsk.Flags = dns.ZONE
s.zskPrivate, err = s.zsk.Generate(2048)
log.Fatale(err)
}
bcfg := &backend.Config {
RPCUsername: s.cfg.NamecoinRPCUsername,
RPCPassword: s.cfg.NamecoinRPCPassword,
RPCAddress: s.cfg.NamecoinRPCAddress,
CacheMaxEntries: s.cfg.CacheMaxEntries,
SelfName: s.cfg.SelfName,
SelfIP: s.cfg.SelfIP,
}
s.b, err = backend.New(bcfg)
log.Fatale(err)
// run
s.udpListener = s.runListener("udp")
s.tcpListener = s.runListener("tcp")
log.Info("Ready.")
// wait
sig := make(chan os.Signal)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
for {
s := <-sig
fmt.Printf("Signal %v received, stopping.", s)
break
}
}
type Server struct {
mux *dns.ServeMux
udpListener *dns.Server
tcpListener *dns.Server
ksk *dns.DNSKEY
kskPrivate dns.PrivateKey
zsk *dns.DNSKEY
zskPrivate dns.PrivateKey
cfg ServerConfig
b abstract.Backend
}
type ServerConfig struct {
Bind string `default:":53" usage:"Address to bind to (e.g. 0.0.0.0:53)"`
PublicKey string `default:"ncdns.key" usage:"Path to the DNSKEY KSK public key file"`
PrivateKey string `default:"ncdns.private" usage:"Path to the KSK's corresponding private key file"`
ZonePublicKey string `default:"" usage:"Path to the DNSKEY ZSK public key file; if one is not specified, a temporary one is generated on startup and used only for the duration of that process"`
ZonePrivateKey string `default:"" usage:"Path to the ZSK's corresponding private key file"`
NamecoinRPCUsername string `default:"" usage:"Namecoin RPC username"`
NamecoinRPCPassword string `default:"" usage:"Namecoin RPC password"`
NamecoinRPCAddress string `default:"localhost:8336" usage:"Namecoin RPC server address"`
CacheMaxEntries int `default:"1000" usage:"Maximum name cache entries"`
SelfIP string `default:"127.127.127.127" usage:"The canonical IP address for this service"`
SelfName string `default:"" usage:"Canonical name for this nameserver (default: autogenerated psuedo-hostname resolving to SelfIP; SelfIP is not used if this is set)"`
}
func (s *Server) doRunListener(ds *dns.Server) {
err := ds.ListenAndServe()
log.Fatale(err)
}
func (s *Server) runListener(net string) *dns.Server {
ds := &dns.Server {
Addr: s.cfg.Bind,
Net: net,
Handler: s.mux,
}
go s.doRunListener(ds)
return ds
}
type Tx struct {
req *dns.Msg
res *dns.Msg
qname string
qtype uint16
qclass uint16
s *Server
rcode int
typesAtQname map[uint16]struct{}
additionalQueue map[string]struct{}
soa *dns.SOA
delegationPoint string // domain name at which the selected delegation was found
// The query was made for the selected delegation's name.
// i.e., if a lookup a.b.c.d has been made, and b.c.d has been chosen as the
// closest available delegation to serve, this is false. Whereas if b.c.d is
// queried, this is true.
queryIsAtDelegationPoint bool
// Add a 'consolation SOA' to the Authority section?
// Usually set when there are no results. This has to be done later, because
// we add DNSKEYs (if requested) at a later time and need to be able to quash
// this at that time in case adding DNSKEYs means an answer has stopped being
// empty of results.
consolationSOA bool
// Don't NSEC for having no answers. Used for qtype==DS.
suppressNSEC bool
}
func (s *Server) handle(rw dns.ResponseWriter, reqMsg *dns.Msg) {
tx := Tx{}
tx.req = reqMsg
tx.res = &dns.Msg{}
tx.res.SetReply(tx.req)
tx.res.Authoritative = true
tx.res.Compress = true
tx.s = s
tx.rcode = 0
tx.typesAtQname = map[uint16]struct{}{}
tx.additionalQueue = map[string]struct{}{}
opt := tx.req.IsEdns0()
if opt != nil {
tx.res.Extra = append(tx.res.Extra, opt)
}
for _, q := range tx.req.Question {
tx.qname = strings.ToLower(q.Name)
tx.qtype = q.Qtype
tx.qclass = q.Qclass
if q.Qclass != dns.ClassINET && q.Qclass != dns.ClassANY {
continue
}
err := tx.addAnswers()
if err != nil {
if err == ncerr.ErrNoResults {
tx.rcode = 0
} else if err == ncerr.ErrNoSuchDomain {
tx.rcode = dns.RcodeNameError
} else if tx.rcode == 0 {
log.Infoe(err, "Handler error, doing SERVFAIL")
tx.rcode = dns.RcodeServerFailure
}
break
}
}
tx.res.SetRcode(tx.req, tx.rcode)
//log.Info("response: ", res.String())
err := rw.WriteMsg(tx.res)
log.Infoe(err, "Couldn't write response: " + tx.res.String())
}
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 = ncerr.ErrNoResults
}
return
}
func rrsetHasType(rrs []dns.RR, t uint16) dns.RR {
for i := range rrs {
if rrs[i].Header().Rrtype == t {
return rrs[i]
}
}
return nil
}
func (tx *Tx) addAnswers() error {
err := tx.addAnswersMain()
if err != nil {
return err
}
// If we are at the zone apex...
if _, ok := tx.typesAtQname[dns.TypeSOA]; tx.soa != nil && ok {
// Add DNSKEYs.
if tx.istype(dns.TypeDNSKEY) {
tx.s.ksk.Hdr.Name = tx.soa.Hdr.Name
tx.s.zsk.Hdr.Name = tx.s.ksk.Hdr.Name
tx.res.Answer = append(tx.res.Answer, tx.s.ksk)
tx.res.Answer = append(tx.res.Answer, tx.s.zsk)
// cancel sending a consolation SOA since we're giving DNSKEY answers
tx.consolationSOA = false
}
tx.typesAtQname[dns.TypeDNSKEY] = struct{}{}
}
//
if tx.consolationSOA && tx.soa != nil {
tx.res.Ns = append(tx.res.Ns, tx.soa)
}
err = tx.addNSEC()
if err != nil {
return err
}
err = tx.addAdditional()
if err != nil {
return err
}
err = tx.signResponse()
if err != nil {
return err
}
return nil
}
func (tx *Tx) addAnswersMain() error {
var soa *dns.SOA
var origq []dns.RR
var origerr error
var firsterr error
var nss []dns.RR
firstNSAtLen := -1
firstSOAAtLen := -1
// We have to find out the zone root by trying to find SOA for progressively shorter domain names.
norig := strings.TrimRight(tx.qname, ".")
n := norig
A:
for len(n) > 0 {
rrs, err := tx.blookup(n)
if len(n) == len(norig) { // keep track of the results for the original qname
origq = rrs
origerr = err
}
if err == nil { // success
for i := range rrs {
t := rrs[i].Header().Rrtype
switch t {
case dns.TypeSOA:
// found the apex of the closest zone for which we are authoritative
// We haven't found any nameservers at this point, so we can serve without worrying about delegations.
if soa == nil {
soa = rrs[i].(*dns.SOA)
}
// We have found a SOA record at this level. This is preferred over everything
// so we can break now.
if firstSOAAtLen < 0 {
firstSOAAtLen = len(n)
}
break A
case dns.TypeNS:
// found an NS on the path; we are not authoritative for this owner or anything under it
// We need to return Authority data regardless of the nature of the query.
nss = rrs
// There could also be a SOA record at this level that we haven't reached yet.
if firstNSAtLen < 0 {
firstNSAtLen = len(n)
tx.delegationPoint = dns.Fqdn(n)
log.Info("DELEGATION POINT: ", tx.delegationPoint)
if n == norig {
tx.queryIsAtDelegationPoint = true
}
}
default:
}
}
} else if firsterr == nil {
firsterr = err
}
nidx := strings.Index(n, ".")
if nidx < 0 {
break
}
n = n[nidx+1:]
}
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 ncerr.ErrNotInZone
}
tx.soa = soa
if firstSOAAtLen >= firstNSAtLen {
// We got a SOA and zero or more NSes at the same level; we're not a delegation.
return tx.addAnswersAuthoritative(origq, origerr)
} else {
// We have a delegation.
return tx.addAnswersDelegation(nss)
}
}
func (tx *Tx) addAnswersAuthoritative(rrs []dns.RR, origerr error) error {
log.Info("AUTHORITATIVE")
// A call to blookup either succeeds or fails.
//
// If it fails:
// ErrNotInZone -- you're looking fundamentally in the wrong place; if there is no other
// appropriate zone, fail with REFUSED
// ErrNoSuchDomain -- there are no records at this name of ANY type, nor are there at any
// direct or indirect descendant domain; fail with NXDOMAIN
// ErrNoResults -- There are no records of the given type of class. However, there are
// other records at the given domain and/or records at a direct or
// indirect descendant domain; NOERROR
// any other error -- SERVFAIL
//
// If it succeeds:
// If there are zero records, treat the response as ErrNoResults above. Otherwise, each record
// can be classified into one of the following categories:
//
// - A NS record not at the zone apex and thus not authoritative (handled in addAnswersDelegation)
//
// - A record not within the zone and thus not authoritative (glue records)
//
// - A CNAME record (must not be glue) (TODO: DNAME)
//
// - Any other record
if origerr != nil {
return origerr
}
cn := rrsetHasType(rrs, dns.TypeCNAME)
if cn != nil && !tx.istype(dns.TypeCNAME) {
// We have an alias.
// TODO: check that the CNAME record is actually in the zone and not some bizarro CNAME glue record
return tx.addAnswersCNAME(cn.(*dns.CNAME))
}
// Add every record which was requested.
for i := range rrs {
t := rrs[i].Header().Rrtype
if tx.istype(t) {
tx.res.Answer = append(tx.res.Answer, rrs[i])
}
// Keep track of the types that really do exist here in case we have to NSEC.
tx.typesAtQname[t] = struct{}{}
}
if len(tx.res.Answer) == 0 {
// no matching records, hand out the SOA (done later, might be quashed)
tx.consolationSOA = true
}
return nil
}
func (tx *Tx) addAnswersCNAME(cn *dns.CNAME) error {
tx.res.Answer = append(tx.res.Answer, cn)
return nil
}
func (tx *Tx) addAnswersDelegation(nss []dns.RR) error {
log.Info("DELEGATION")
if tx.qtype == dns.TypeDS /* don't use istype, must not match ANY */ &&
tx.queryIsAtDelegationPoint {
// If type DS was requested specifically (not ANY), we have to act like
// we're handling things authoritatively and hand out a consolation SOA
// record and NOT hand out NS records. These still go in the Authority
// section though.
//
// If a DS record exists, it's given; if one doesn't, an NSEC record is
// given.
added := false
for _, ns := range nss {
t := ns.Header().Rrtype
if t == dns.TypeDS {
added = true
tx.res.Answer = append(tx.res.Answer, ns)
}
}
if added {
tx.suppressNSEC = true
} else {
tx.consolationSOA = true
}
} else {
tx.res.Authoritative = false
// Note that this is not authoritative data and thus does not get signed.
for _, ns := range nss {
t := ns.Header().Rrtype
if t == dns.TypeNS || t == dns.TypeDS {
tx.res.Ns = append(tx.res.Ns, ns)
}
if t == dns.TypeNS {
ns_ := ns.(*dns.NS)
tx.queueAdditional(ns_.Ns)
}
if t == dns.TypeDS {
tx.suppressNSEC = true
}
}
}
// Nonauthoritative NS records are still included in the NSEC extant types list
tx.typesAtQname[dns.TypeNS] = struct{}{}
return nil
}
func (tx *Tx) queueAdditional(name string) {
tx.additionalQueue[name] = struct{}{}
}
func (tx *Tx) addNSEC() error {
if !tx.useDNSSEC() || tx.suppressNSEC {
return nil
}
// NSEC replies should be given in the following circumstances:
//
// - No ANSWER SECTION responses for type requested, qtype != DS
// - No ANSWER SECTION responses for type requested, qtype == DS
// - Wildcard, no data responses
// - Wildcard, data response
// - Name error response
// - Direct NSEC request
if len(tx.res.Answer) == 0 {
log.Info("adding NSEC3")
err := tx.addNSEC3RR()
if err != nil {
return err
}
}
return nil
}
func (tx *Tx) addNSEC3RR() error {
// deny the name
err := tx.addNSEC3RRActual(tx.qname, tx.typesAtQname)
if err != nil {
return err
}
// DEVEVER.BIT.
// deny DEVEVER.BIT. (DS)
// deny *.BIT.
// deny the existence of a wildcard that could have served the name
return nil
}
func (tx *Tx) addNSEC3RRActual(name string, tset map[uint16]struct{}) error {
tbm := []uint16{}
for t, _ := range tset {
tbm = append(tbm, t)
}
sort.Sort(uint16Slice(tbm))
nsr1n := dns.HashName(tx.qname, dns.SHA1, 1, "8F")
nsr1nn := stepName(nsr1n)
nsr1 := &dns.NSEC3 {
Hdr: dns.RR_Header {
Name: dns.Fqdn(nsr1n + "." + tx.soa.Hdr.Name),
Rrtype: dns.TypeNSEC3,
Class: dns.ClassINET,
Ttl: 600,
},
Hash: dns.SHA1,
Flags: 0,
Iterations: 1,
SaltLength: 1,
Salt: "8F",
HashLength: uint8(len(nsr1nn)),
NextDomain: nsr1nn,
TypeBitMap: tbm,
}
tx.res.Ns = append(tx.res.Ns, nsr1)
return nil
}
func (tx *Tx) addAdditional() error {
for aname := range tx.additionalQueue {
err := tx.addAdditionalItem(aname)
if err != nil {
// eat the error
//return err
}
}
return nil
}
func (tx *Tx) addAdditionalItem(aname string) error {
log.Info("ADDITIONAL: ", aname)
rrs, err := tx.blookup(aname)
if err != nil {
return err
}
for _, rr := range rrs {
t := rr.Header().Rrtype
if t == dns.TypeA || t == dns.TypeAAAA {
tx.res.Extra = append(tx.res.Extra, rr)
}
}
return nil
}
// © 2014 Hugo Landau <hlandau@devever.net> GPLv3 or later

@ -1,57 +0,0 @@
// Error types for processing DNS requests.
package ncerr
import "github.com/miekg/dns"
import "fmt"
// An Error interface which allows an associated rcode to be queried.
type Error interface {
error
// Returns the rcode which this error should be represented as in the DNS protocol.
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
}
// Used to generate an Error which has a particular rcode. Otherwise like fmt.Errorf.
func Rerrorf(rcode int, fmts string, args ...interface{}) Error {
re := &rerr{}
re.e = fmt.Errorf(fmts, args...)
re.rcode = rcode
return re
}
// Standard errors.
// Represents NXDOMAIN. Used when the name requested lies within a zone for
// which this server is authoritative, but does not exist.
//
// Note that a name is considered to exist if there exist any records of any
// type at a name, even if those records were not requested or sent. A name is
// also considered to exist if there are any names under it.
//
// In other words, b.c should return NOERROR even if it has no records of any
// type if there is a record at a.b.c, or so on.
var ErrNoSuchDomain = Rerrorf(dns.RcodeNameError, "no such domain")
// Represents REFUSED, which we use when a request is received for a zone for
// which the server is not authoritative.
var ErrNotInZone = Rerrorf(dns.RcodeRefused, "domain not in zone")
// Represents NOERROR. This error is used when NXDOMAIN is not an appropriate
// response code, but no results were returned. (DNS also uses NOERROR when results
// are returned, but we return nil in that case.)
var ErrNoResults = Rerrorf(0, "no results")

@ -0,0 +1,153 @@
package server
import "github.com/hlandau/madns"
import "github.com/hlandau/degoutils/log"
import "github.com/hlandau/ncdns/backend"
import "github.com/miekg/dns"
import "os"
import "fmt"
import "os/signal"
import "syscall"
type Server struct {
cfg ServerConfig
engine madns.Engine
mux *dns.ServeMux
udpListener *dns.Server
tcpListener *dns.Server
}
type ServerConfig struct {
Bind string `default:":53" usage:"Address to bind to (e.g. 0.0.0.0:53)"`
PublicKey string `default:"ncdns.key" usage:"Path to the DNSKEY KSK public key file"`
PrivateKey string `default:"ncdns.private" usage:"Path to the KSK's corresponding private key file"`
ZonePublicKey string `default:"" usage:"Path to the DNSKEY ZSK public key file; if one is not specified, a temporary one is generated on startup and used only for the duration of that process"`
ZonePrivateKey string `default:"" usage:"Path to the ZSK's corresponding private key file"`
NamecoinRPCUsername string `default:"" usage:"Namecoin RPC username"`
NamecoinRPCPassword string `default:"" usage:"Namecoin RPC password"`
NamecoinRPCAddress string `default:"localhost:8336" usage:"Namecoin RPC server address"`
CacheMaxEntries int `default:"1000" usage:"Maximum name cache entries"`
SelfName string `default:"" usage:"Canonical name for this nameserver (default: autogenerated psuedo-hostname resolving to SelfIP; SelfIP is not used if this is set)"`
SelfIP string `default:"127.127.127.127" usage:"The canonical IP address for this service"`
}
func NewServer(cfg *ServerConfig) (s *Server, err error) {
s = &Server{}
s.cfg = *cfg
bcfg := &backend.Config {
RPCUsername: cfg.NamecoinRPCUsername,
RPCPassword: cfg.NamecoinRPCPassword,
CacheMaxEntries: cfg.CacheMaxEntries,
SelfName: cfg.SelfName,
SelfIP: cfg.SelfIP,
}
b, err := backend.New(bcfg)
if err != nil {
return
}
// key setup
ksk, kskPrivate, err := s.loadKey(cfg.PublicKey, cfg.PrivateKey)
log.Fatale(err, "error reading KSK key")
var zsk *dns.DNSKEY
var zskPrivate dns.PrivateKey
if cfg.ZonePublicKey != "" {
zsk, zskPrivate, err = s.loadKey(cfg.ZonePublicKey, cfg.ZonePrivateKey)
log.Fatale(err, "error reading ZSK key")
} else {
zsk = &dns.DNSKEY{}
zsk.Hdr.Rrtype = dns.TypeDNSKEY
zsk.Hdr.Class = dns.ClassINET
zsk.Hdr.Ttl = 3600
zsk.Algorithm = dns.RSASHA256
zsk.Protocol = 3
zsk.Flags = dns.ZONE
zskPrivate, err = zsk.Generate(2048)
log.Fatale(err)
}
ecfg := &madns.EngineConfig {
Backend: b,
KSK: ksk,
KSKPrivate: kskPrivate,
ZSK: zsk,
ZSKPrivate: zskPrivate,
}
e, err := madns.NewEngine(ecfg)
if err != nil {
return
}
s.engine = e
return
}
func (s *Server) loadKey(fn, privateFn string) (k *dns.DNSKEY, privatek dns.PrivateKey, err error) {
f, err := os.Open(fn)
if err != nil {
return
}
rr, err := dns.ReadRR(f, fn)
if err != nil {
return
}
k, ok := rr.(*dns.DNSKEY)
if !ok {
err = fmt.Errorf("Loaded record from key file, but it wasn't a DNSKEY")
return
}
privatef, err := os.Open(privateFn)
if err != nil {
return
}
privatek, err = k.ReadPrivateKey(privatef, privateFn)
log.Fatale(err)
return
}
func (s *Server) Run() {
s.mux = dns.NewServeMux()
s.mux.Handle(".", s.engine)
s.udpListener = s.runListener("udp")
s.tcpListener = s.runListener("tcp")
log.Info("Ready.")
// wait
sig := make(chan os.Signal)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
for {
s := <-sig
fmt.Printf("Signal %v received, stopping.", s)
break
}
}
func (s *Server) doRunListener(ds *dns.Server) {
err := ds.ListenAndServe()
log.Fatale(err)
}
func (s *Server) runListener(net string) *dns.Server {
ds := &dns.Server {
Addr: s.cfg.Bind,
Net: net,
Handler: s.mux,
}
go s.doRunListener(ds)
return ds
}

@ -1,191 +0,0 @@
package main
import "encoding/base32"
import "fmt"
import "github.com/miekg/dns"
import "github.com/hlandau/degoutils/log"
import "time"
// 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
}
// 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(hashB32Hex)
log.Panice(err, hashB32Hex)
for i := len(b)-1; i>=0; i-- {
b[i] += 1
if b[i] != 0 { // didn't rollover, don't need to continue
break
}
}
return base32.HexEncoding.EncodeToString(b)
}
// Returns true iff a type should be covered by a RRSIG.
func shouldSignType(t uint16, isAuthoritySection bool) bool {
switch t {
case dns.TypeOPT:
return false
case dns.TypeNS:
return !isAuthoritySection
default:
return true
}
}
// Returns true iff a client requested DNSSEC.
func (tx *Tx) useDNSSEC() bool {
opt := tx.req.IsEdns0()
if opt == nil {
return false
}
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
}
}
// 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 {
ttl := rr.Header().Ttl
if ttl > x {
x = ttl
}
}
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")
}
maxttl := rraMaxTTL(rra)
exp := time.Duration(maxttl)*time.Second + time.Duration(10)*time.Minute
log.Info("maxttl: ", maxttl, " expiration: ", exp)
now := time.Now()
rrsig := &dns.RRSIG {
Hdr: dns.RR_Header { Ttl: maxttl, },
Algorithm: dns.RSASHA256,
Expiration: uint32(now.Add(exp).Unix()),
Inception: uint32(now.Add(time.Duration(-10)*time.Minute).Unix()),
SignerName: dns.Fqdn(tx.soa.Hdr.Name),
}
pk := tx.s.zskPrivate
if useKSK {
pk = tx.s.kskPrivate
rrsig.KeyTag = tx.s.ksk.KeyTag()
} else {
rrsig.KeyTag = tx.s.zsk.KeyTag()
}
err := rrsig.Sign(pk, rra)
if err != nil {
return nil, err
}
return rrsig, nil
}
// Used by signResponse.
func (tx *Tx) signResponseSection(rra *[]dns.RR) error {
if len(*rra) == 0 {
return nil
}
//log.Info("sign section: ", *rra)
i := 0
a := []dns.RR{}
pt := (*rra)[0].Header().Rrtype
t := uint16(0)
origrra := *rra
for i < len(origrra) {
for i < len(origrra) {
t = (*rra)[i].Header().Rrtype
if t != pt {
break
}
a = append(a, origrra[i])
i++
}
if shouldSignType(pt, (rra == &tx.res.Ns) ) {
useKSK := (pt == dns.TypeDNSKEY)
if useKSK {
srr, err := tx.signRRs(a, true)
if err != nil {
return err
}
*rra = append(*rra, srr)
}
srr, err := tx.signRRs(a, false)
if err != nil {
return err
}
*rra = append(*rra, srr)
}
pt = t
a = []dns.RR{}
}
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
}
for _, r := range []*[]dns.RR { &tx.res.Answer, &tx.res.Ns, /*&tx.res.Extra*/ } {
err := tx.signResponseSection(r)
if err != nil {
log.Infoe(err, "fail signResponse")
return err
}
}
log.Info("done signResponse")
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] }
func (p uint16Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
Loading…
Cancel
Save