Re-added tests
This commit is contained in:
@@ -0,0 +1,354 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/acme-dns/acme-dns/pkg/acmedns"
|
||||
"github.com/acme-dns/acme-dns/pkg/database"
|
||||
"github.com/erikstmartin/go-testdb"
|
||||
"github.com/miekg/dns"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zaptest/observer"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type resolver struct {
|
||||
server string
|
||||
}
|
||||
|
||||
var records = []string{
|
||||
"auth.example.org. A 192.168.1.100",
|
||||
"ns1.auth.example.org. A 192.168.1.101",
|
||||
"cn.example.org CNAME something.example.org.",
|
||||
"!''b', unparseable ",
|
||||
"ns2.auth.example.org. A 192.168.1.102",
|
||||
}
|
||||
|
||||
func loggerHasEntryWithMessage(message string, logObserver *observer.ObservedLogs) bool {
|
||||
if len(logObserver.FilterMessage(message).All()) > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func fakeConfigAndLogger() (acmedns.AcmeDnsConfig, *zap.SugaredLogger, *observer.ObservedLogs) {
|
||||
c := acmedns.AcmeDnsConfig{}
|
||||
c.Database.Engine = "sqlite"
|
||||
c.Database.Connection = ":memory:"
|
||||
obsCore, logObserver := observer.New(zap.DebugLevel)
|
||||
obsLogger := zap.New(obsCore).Sugar()
|
||||
return c, obsLogger, logObserver
|
||||
}
|
||||
|
||||
func setupDNS() (acmedns.AcmednsNS, acmedns.AcmednsDB, *observer.ObservedLogs) {
|
||||
config, logger, logObserver := fakeConfigAndLogger()
|
||||
config.General.Domain = "auth.example.org"
|
||||
config.General.Listen = "127.0.0.1:15353"
|
||||
config.General.Proto = "udp"
|
||||
config.General.Nsname = "ns1.auth.example.org"
|
||||
config.General.Nsadmin = "admin.example.org"
|
||||
config.General.StaticRecords = records
|
||||
config.General.Debug = false
|
||||
db, _ := database.Init(&config, logger)
|
||||
server := Nameserver{Config: &config, DB: db, Logger: logger, personalAuthKey: ""}
|
||||
server.Domains = make(map[string]Records)
|
||||
server.Server = &dns.Server{Addr: config.General.Listen, Net: config.General.Proto}
|
||||
server.ParseRecords()
|
||||
server.OwnDomain = "auth.example.org."
|
||||
return &server, db, logObserver
|
||||
}
|
||||
|
||||
func (r *resolver) lookup(host string, qtype uint16) (*dns.Msg, error) {
|
||||
msg := new(dns.Msg)
|
||||
msg.Id = dns.Id()
|
||||
msg.Question = make([]dns.Question, 1)
|
||||
msg.Question[0] = dns.Question{Name: dns.Fqdn(host), Qtype: qtype, Qclass: dns.ClassINET}
|
||||
in, err := dns.Exchange(msg, r.server)
|
||||
if err != nil {
|
||||
return in, fmt.Errorf("Error querying the server [%v]", err)
|
||||
}
|
||||
if in != nil && in.Rcode != dns.RcodeSuccess {
|
||||
return in, fmt.Errorf("Received error from the server [%s]", dns.RcodeToString[in.Rcode])
|
||||
}
|
||||
return in, nil
|
||||
}
|
||||
|
||||
func hasExpectedTXTAnswer(answer []dns.RR, cmpTXT string) error {
|
||||
for _, record := range answer {
|
||||
// We expect only one answer, so no need to loop through the answer slice
|
||||
if rec, ok := record.(*dns.TXT); ok {
|
||||
for _, txtValue := range rec.Txt {
|
||||
if txtValue == cmpTXT {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errmsg := fmt.Sprintf("Got answer of unexpected type [%q]", answer[0])
|
||||
return errors.New(errmsg)
|
||||
}
|
||||
}
|
||||
return errors.New("Expected answer not found")
|
||||
}
|
||||
|
||||
func TestQuestionDBError(t *testing.T) {
|
||||
config, logger, _ := fakeConfigAndLogger()
|
||||
config.General.Listen = "127.0.0.1:15353"
|
||||
config.General.Proto = "udp"
|
||||
config.General.Domain = "auth.example.org"
|
||||
config.General.Nsname = "ns1.auth.example.org"
|
||||
config.General.Nsadmin = "admin.example.org"
|
||||
config.General.StaticRecords = records
|
||||
config.General.Debug = false
|
||||
db, _ := database.Init(&config, logger)
|
||||
server := Nameserver{Config: &config, DB: db, Logger: logger, personalAuthKey: ""}
|
||||
server.Domains = make(map[string]Records)
|
||||
server.ParseRecords()
|
||||
testdb.SetQueryWithArgsFunc(func(query string, args []driver.Value) (result driver.Rows, err error) {
|
||||
columns := []string{"Username", "Password", "Subdomain", "Value", "LastActive"}
|
||||
return testdb.RowsFromSlice(columns, [][]driver.Value{}), errors.New("Prepared query error")
|
||||
})
|
||||
|
||||
defer testdb.Reset()
|
||||
|
||||
tdb, err := sql.Open("testdb", "")
|
||||
if err != nil {
|
||||
t.Errorf("Got error: %v", err)
|
||||
}
|
||||
oldDb := db.GetBackend()
|
||||
|
||||
db.SetBackend(tdb)
|
||||
defer db.SetBackend(oldDb)
|
||||
|
||||
q := dns.Question{Name: dns.Fqdn("whatever.tld"), Qtype: dns.TypeTXT, Qclass: dns.ClassINET}
|
||||
_, err = server.answerTXT(q)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error but got none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
config, logger, logObserver := fakeConfigAndLogger()
|
||||
config.General.Listen = "127.0.0.1:15353"
|
||||
config.General.Proto = "udp"
|
||||
config.General.Domain = ")"
|
||||
config.General.Nsname = "ns1.auth.example.org"
|
||||
config.General.Nsadmin = "admin.example.org"
|
||||
config.General.StaticRecords = records
|
||||
config.General.Debug = false
|
||||
config.General.StaticRecords = []string{}
|
||||
db, _ := database.Init(&config, logger)
|
||||
server := Nameserver{Config: &config, DB: db, Logger: logger, personalAuthKey: ""}
|
||||
server.Domains = make(map[string]Records)
|
||||
server.ParseRecords()
|
||||
if !loggerHasEntryWithMessage("Error while adding SOA record", logObserver) {
|
||||
t.Errorf("Expected SOA parsing to return error, but did not find one")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveA(t *testing.T) {
|
||||
server, _, _ := setupDNS()
|
||||
errChan := make(chan error, 1)
|
||||
waitLock := sync.Mutex{}
|
||||
waitLock.Lock()
|
||||
server.SetNotifyStartedFunc(waitLock.Unlock)
|
||||
go server.Start(errChan)
|
||||
waitLock.Lock()
|
||||
resolv := resolver{server: "127.0.0.1:15353"}
|
||||
answer, err := resolv.lookup("auth.example.org", dns.TypeA)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(answer.Answer) == 0 {
|
||||
t.Error("No answer for DNS query")
|
||||
return
|
||||
}
|
||||
|
||||
_, err = resolv.lookup("nonexistent.domain.tld", dns.TypeA)
|
||||
if err == nil {
|
||||
t.Errorf("Was expecting error because of NXDOMAIN but got none")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestEDNS(t *testing.T) {
|
||||
resolv := resolver{server: "127.0.0.1:15353"}
|
||||
answer, _ := resolv.lookup("auth.example.org", dns.TypeOPT)
|
||||
if answer.Rcode != dns.RcodeSuccess {
|
||||
t.Errorf("Was expecing NOERROR rcode for OPT query, but got [%s] instead.", dns.RcodeToString[answer.Rcode])
|
||||
}
|
||||
}
|
||||
|
||||
func TestEDNSA(t *testing.T) {
|
||||
msg := new(dns.Msg)
|
||||
msg.Id = dns.Id()
|
||||
msg.Question = make([]dns.Question, 1)
|
||||
msg.Question[0] = dns.Question{Name: dns.Fqdn("auth.example.org"), Qtype: dns.TypeA, Qclass: dns.ClassINET}
|
||||
// Set EDNS0 with DO=1
|
||||
msg.SetEdns0(512, true)
|
||||
in, err := dns.Exchange(msg, "127.0.0.1:15353")
|
||||
if err != nil {
|
||||
t.Errorf("Error querying the server [%v]", err)
|
||||
}
|
||||
if in != nil && in.Rcode != dns.RcodeSuccess {
|
||||
t.Errorf("Received error from the server [%s]", dns.RcodeToString[in.Rcode])
|
||||
}
|
||||
opt := in.IsEdns0()
|
||||
if opt == nil {
|
||||
t.Errorf("Should have got OPT back")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEDNSBADVERS(t *testing.T) {
|
||||
msg := new(dns.Msg)
|
||||
msg.Id = dns.Id()
|
||||
msg.Question = make([]dns.Question, 1)
|
||||
msg.Question[0] = dns.Question{Name: dns.Fqdn("auth.example.org"), Qtype: dns.TypeA, Qclass: dns.ClassINET}
|
||||
// Set EDNS0 with version 1
|
||||
o := new(dns.OPT)
|
||||
o.SetVersion(1)
|
||||
o.Hdr.Name = "."
|
||||
o.Hdr.Rrtype = dns.TypeOPT
|
||||
msg.Extra = append(msg.Extra, o)
|
||||
in, err := dns.Exchange(msg, "127.0.0.1:15353")
|
||||
if err != nil {
|
||||
t.Errorf("Error querying the server [%v]", err)
|
||||
}
|
||||
if in != nil && in.Rcode != dns.RcodeBadVers {
|
||||
t.Errorf("Received unexpected rcode from the server [%s]", dns.RcodeToString[in.Rcode])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveCNAME(t *testing.T) {
|
||||
resolv := resolver{server: "127.0.0.1:15353"}
|
||||
expected := "cn.example.org. 3600 IN CNAME something.example.org."
|
||||
answer, err := resolv.lookup("cn.example.org", dns.TypeCNAME)
|
||||
if err != nil {
|
||||
t.Errorf("Got unexpected error: %s", err)
|
||||
}
|
||||
if len(answer.Answer) != 1 {
|
||||
t.Errorf("Expected exactly 1 RR in answer, but got %d instead.", len(answer.Answer))
|
||||
}
|
||||
if answer.Answer[0].Header().Rrtype != dns.TypeCNAME {
|
||||
t.Errorf("Expected a CNAME answer, but got [%s] instead.", dns.TypeToString[answer.Answer[0].Header().Rrtype])
|
||||
}
|
||||
if answer.Answer[0].String() != expected {
|
||||
t.Errorf("Expected CNAME answer [%s] but got [%s] instead.", expected, answer.Answer[0].String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthoritative(t *testing.T) {
|
||||
resolv := resolver{server: "127.0.0.1:15353"}
|
||||
answer, _ := resolv.lookup("nonexistent.auth.example.org", dns.TypeA)
|
||||
if answer.Rcode != dns.RcodeNameError {
|
||||
t.Errorf("Was expecing NXDOMAIN rcode, but got [%s] instead.", dns.RcodeToString[answer.Rcode])
|
||||
}
|
||||
if len(answer.Ns) != 1 {
|
||||
t.Errorf("Was expecting exactly one answer (SOA) for invalid subdomain, but got %d", len(answer.Ns))
|
||||
}
|
||||
if answer.Ns[0].Header().Rrtype != dns.TypeSOA {
|
||||
t.Errorf("Was expecting SOA record as answer for NXDOMAIN but got [%s]", dns.TypeToString[answer.Ns[0].Header().Rrtype])
|
||||
}
|
||||
if !answer.MsgHdr.Authoritative {
|
||||
t.Errorf("Was expecting authoritative bit to be set")
|
||||
}
|
||||
nanswer, _ := resolv.lookup("nonexsitent.nonauth.tld", dns.TypeA)
|
||||
if len(nanswer.Answer) > 0 {
|
||||
t.Errorf("Didn't expect answers for non authotitative domain query")
|
||||
}
|
||||
if nanswer.MsgHdr.Authoritative {
|
||||
t.Errorf("Authoritative bit should not be set for non-authoritative domain.")
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func TestResolveTXT(t *testing.T) {
|
||||
_, db, _ := setupDNS()
|
||||
resolv := resolver{server: "127.0.0.1:15353"}
|
||||
validTXT := "______________valid_response_______________"
|
||||
|
||||
atxt, err := db.Register(acmedns.Cidrslice{})
|
||||
if err != nil {
|
||||
t.Errorf("Could not initiate db record: [%v]", err)
|
||||
return
|
||||
}
|
||||
atxt.Value = validTXT
|
||||
|
||||
err = db.Update(atxt.ACMETxtPost)
|
||||
if err != nil {
|
||||
t.Errorf("Could not update db record: [%v]", err)
|
||||
return
|
||||
}
|
||||
|
||||
for i, test := range []struct {
|
||||
subDomain string
|
||||
expTXT string
|
||||
getAnswer bool
|
||||
validAnswer bool
|
||||
}{
|
||||
{atxt.Subdomain, validTXT, true, true},
|
||||
{atxt.Subdomain, "invalid", true, false},
|
||||
{"a097455b-52cc-4569-90c8-7a4b97c6eba8", validTXT, false, false},
|
||||
} {
|
||||
answer, err := resolv.lookup(test.subDomain+".auth.example.org", dns.TypeTXT)
|
||||
if err != nil {
|
||||
if test.getAnswer {
|
||||
t.Fatalf("Test %d: Expected answer but got: %v", i, err)
|
||||
}
|
||||
} else {
|
||||
if !test.getAnswer {
|
||||
t.Errorf("Test %d: Expected no answer, but got one.", i)
|
||||
}
|
||||
}
|
||||
|
||||
if len(answer.Answer) > 0 {
|
||||
if !test.getAnswer && answer.Answer[0].Header().Rrtype != dns.TypeSOA {
|
||||
t.Errorf("Test %d: Expected no answer, but got: [%q]", i, answer)
|
||||
}
|
||||
if test.getAnswer {
|
||||
err = hasExpectedTXTAnswer(answer.Answer, test.expTXT)
|
||||
if err != nil {
|
||||
if test.validAnswer {
|
||||
t.Errorf("Test %d: %v", i, err)
|
||||
}
|
||||
} else {
|
||||
if !test.validAnswer {
|
||||
t.Errorf("Test %d: Answer was not expected to be valid, answer [%q], compared to [%s]", i, answer, test.expTXT)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if test.getAnswer {
|
||||
t.Errorf("Test %d: Expected answer, but didn't get one", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
func TestCaseInsensitiveResolveA(t *testing.T) {
|
||||
resolv := resolver{server: "127.0.0.1:15353"}
|
||||
answer, err := resolv.lookup("aUtH.eXAmpLe.org", dns.TypeA)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
|
||||
if len(answer.Answer) == 0 {
|
||||
t.Error("No answer for DNS query")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCaseInsensitiveResolveSOA(t *testing.T) {
|
||||
resolv := resolver{server: "127.0.0.1:15353"}
|
||||
answer, _ := resolv.lookup("doesnotexist.aUtH.eXAmpLe.org", dns.TypeSOA)
|
||||
if answer.Rcode != dns.RcodeNameError {
|
||||
t.Errorf("Was expecing NXDOMAIN rcode, but got [%s] instead.", dns.RcodeToString[answer.Rcode])
|
||||
}
|
||||
|
||||
if len(answer.Ns) == 0 {
|
||||
t.Error("No SOA answer for DNS query")
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
func (n *Nameserver) handleRequest(w dns.ResponseWriter, r *dns.Msg) {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
|
||||
// handle edns0
|
||||
opt := r.IsEdns0()
|
||||
if opt != nil {
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/acme-dns/acme-dns/pkg/acmedns"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"go.uber.org/zap"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Records is a slice of ResourceRecords
|
||||
@@ -15,21 +14,23 @@ type Records struct {
|
||||
}
|
||||
|
||||
type Nameserver struct {
|
||||
Config *acmedns.AcmeDnsConfig
|
||||
DB acmedns.AcmednsDB
|
||||
Logger *zap.SugaredLogger
|
||||
Server *dns.Server
|
||||
OwnDomain string
|
||||
SOA dns.RR
|
||||
personalAuthKey string
|
||||
Domains map[string]Records
|
||||
errChan chan error
|
||||
Config *acmedns.AcmeDnsConfig
|
||||
DB acmedns.AcmednsDB
|
||||
Logger *zap.SugaredLogger
|
||||
Server *dns.Server
|
||||
OwnDomain string
|
||||
NotifyStartedFunc func()
|
||||
SOA dns.RR
|
||||
personalAuthKey string
|
||||
Domains map[string]Records
|
||||
errChan chan error
|
||||
}
|
||||
|
||||
func InitAndStart(config *acmedns.AcmeDnsConfig, db acmedns.AcmednsDB, logger *zap.SugaredLogger, errChan chan error) []acmedns.AcmednsNS {
|
||||
dnsservers := make([]acmedns.AcmednsNS, 0)
|
||||
|
||||
waitLock := sync.Mutex{}
|
||||
if strings.HasPrefix(config.General.Proto, "both") {
|
||||
|
||||
// Handle the case where DNS server should be started for both udp and tcp
|
||||
udpProto := "udp"
|
||||
tcpProto := "tcp"
|
||||
@@ -46,13 +47,22 @@ func InitAndStart(config *acmedns.AcmeDnsConfig, db acmedns.AcmednsDB, logger *z
|
||||
dnsServerTCP := NewDNSServer(config, db, logger, tcpProto)
|
||||
dnsservers = append(dnsservers, dnsServerTCP)
|
||||
dnsServerTCP.ParseRecords()
|
||||
// wait for the server to get started to proceed
|
||||
waitLock.Lock()
|
||||
dnsServerUDP.SetNotifyStartedFunc(waitLock.Unlock)
|
||||
go dnsServerUDP.Start(errChan)
|
||||
waitLock.Lock()
|
||||
dnsServerTCP.SetNotifyStartedFunc(waitLock.Unlock)
|
||||
go dnsServerTCP.Start(errChan)
|
||||
waitLock.Lock()
|
||||
} else {
|
||||
dnsServer := NewDNSServer(config, db, logger, config.General.Proto)
|
||||
dnsservers = append(dnsservers, dnsServer)
|
||||
dnsServer.ParseRecords()
|
||||
waitLock.Lock()
|
||||
dnsServer.SetNotifyStartedFunc(waitLock.Unlock)
|
||||
go dnsServer.Start(errChan)
|
||||
waitLock.Lock()
|
||||
}
|
||||
return dnsservers
|
||||
}
|
||||
@@ -67,7 +77,6 @@ func NewDNSServer(config *acmedns.AcmeDnsConfig, db acmedns.AcmednsDB, logger *z
|
||||
domain = domain + "."
|
||||
}
|
||||
server.OwnDomain = strings.ToLower(domain)
|
||||
server.DB = db
|
||||
server.personalAuthKey = ""
|
||||
server.Domains = make(map[string]Records)
|
||||
return &server
|
||||
@@ -79,8 +88,15 @@ func (n *Nameserver) Start(errorChannel chan error) {
|
||||
n.Logger.Infow("Starting DNS listener",
|
||||
"addr", n.Server.Addr,
|
||||
"proto", n.Server.Net)
|
||||
if n.NotifyStartedFunc != nil {
|
||||
n.Server.NotifyStartedFunc = n.NotifyStartedFunc
|
||||
}
|
||||
err := n.Server.ListenAndServe()
|
||||
if err != nil {
|
||||
errorChannel <- err
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Nameserver) SetNotifyStartedFunc(fun func()) {
|
||||
n.Server.NotifyStartedFunc = fun
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user