Refactor core
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/miekg/dns"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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 {
|
||||
if opt.Version() != 0 {
|
||||
// Only EDNS0 is standardized
|
||||
m.MsgHdr.Rcode = dns.RcodeBadVers
|
||||
m.SetEdns0(512, false)
|
||||
} else {
|
||||
// We can safely do this as we know that we're not setting other OPT RRs within acme-dns.
|
||||
m.SetEdns0(512, false)
|
||||
if r.Opcode == dns.OpcodeQuery {
|
||||
n.readQuery(m)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if r.Opcode == dns.OpcodeQuery {
|
||||
n.readQuery(m)
|
||||
}
|
||||
}
|
||||
_ = w.WriteMsg(m)
|
||||
}
|
||||
|
||||
func (n *Nameserver) readQuery(m *dns.Msg) {
|
||||
var authoritative = false
|
||||
for _, que := range m.Question {
|
||||
if rr, rc, auth, err := n.answer(que); err == nil {
|
||||
if auth {
|
||||
authoritative = auth
|
||||
}
|
||||
m.MsgHdr.Rcode = rc
|
||||
m.Answer = append(m.Answer, rr...)
|
||||
}
|
||||
}
|
||||
m.MsgHdr.Authoritative = authoritative
|
||||
if authoritative {
|
||||
if m.MsgHdr.Rcode == dns.RcodeNameError {
|
||||
m.Ns = append(m.Ns, n.SOA)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Nameserver) answer(q dns.Question) ([]dns.RR, int, bool, error) {
|
||||
var rcode int
|
||||
var err error
|
||||
var txtRRs []dns.RR
|
||||
var authoritative = n.isAuthoritative(q)
|
||||
if !n.isOwnChallenge(q.Name) && !n.answeringForDomain(q.Name) {
|
||||
rcode = dns.RcodeNameError
|
||||
}
|
||||
r, _ := n.getRecord(q)
|
||||
if q.Qtype == dns.TypeTXT {
|
||||
if n.isOwnChallenge(q.Name) {
|
||||
txtRRs, err = n.answerOwnChallenge(q)
|
||||
} else {
|
||||
txtRRs, err = n.answerTXT(q)
|
||||
}
|
||||
if err == nil {
|
||||
r = append(r, txtRRs...)
|
||||
}
|
||||
}
|
||||
if len(r) > 0 {
|
||||
// Make sure that we return NOERROR if there were dynamic records for the domain
|
||||
rcode = dns.RcodeSuccess
|
||||
}
|
||||
n.Logger.Debugw("Answering question for domain",
|
||||
"qtype", dns.TypeToString[q.Qtype],
|
||||
"domain", q.Name,
|
||||
"rcode", dns.RcodeToString[rcode])
|
||||
return r, rcode, authoritative, nil
|
||||
}
|
||||
|
||||
func (n *Nameserver) answerTXT(q dns.Question) ([]dns.RR, error) {
|
||||
var ra []dns.RR
|
||||
subdomain := sanitizeDomainQuestion(q.Name)
|
||||
atxt, err := n.DB.GetTXTForDomain(subdomain)
|
||||
if err != nil {
|
||||
n.Logger.Errorw("Error while trying to get record",
|
||||
"error", err.Error())
|
||||
return ra, err
|
||||
}
|
||||
for _, v := range atxt {
|
||||
if len(v) > 0 {
|
||||
r := new(dns.TXT)
|
||||
r.Hdr = dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 1}
|
||||
r.Txt = append(r.Txt, v)
|
||||
ra = append(ra, r)
|
||||
}
|
||||
}
|
||||
return ra, nil
|
||||
}
|
||||
|
||||
func (n *Nameserver) isAuthoritative(q dns.Question) bool {
|
||||
if n.answeringForDomain(q.Name) {
|
||||
return true
|
||||
}
|
||||
domainParts := strings.Split(strings.ToLower(q.Name), ".")
|
||||
for i := range domainParts {
|
||||
if n.answeringForDomain(strings.Join(domainParts[i:], ".")) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isOwnChallenge checks if the query is for the domain of this acme-dns instance. Used for answering its own ACME challenges
|
||||
func (n *Nameserver) isOwnChallenge(name string) bool {
|
||||
domainParts := strings.SplitN(name, ".", 2)
|
||||
if len(domainParts) == 2 {
|
||||
if strings.ToLower(domainParts[0]) == "_acme-challenge" {
|
||||
domain := strings.ToLower(domainParts[1])
|
||||
if !strings.HasSuffix(domain, ".") {
|
||||
domain = domain + "."
|
||||
}
|
||||
if domain == n.OwnDomain {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// answeringForDomain checks if we have any records for a domain
|
||||
func (n *Nameserver) answeringForDomain(name string) bool {
|
||||
if n.OwnDomain == strings.ToLower(name) {
|
||||
return true
|
||||
}
|
||||
_, ok := n.Domains[strings.ToLower(name)]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (n *Nameserver) getRecord(q dns.Question) ([]dns.RR, error) {
|
||||
var rr []dns.RR
|
||||
var cnames []dns.RR
|
||||
domain, ok := n.Domains[strings.ToLower(q.Name)]
|
||||
if !ok {
|
||||
return rr, fmt.Errorf("no records for domain %s", q.Name)
|
||||
}
|
||||
for _, ri := range domain.Records {
|
||||
if ri.Header().Rrtype == q.Qtype {
|
||||
rr = append(rr, ri)
|
||||
}
|
||||
if ri.Header().Rrtype == dns.TypeCNAME {
|
||||
cnames = append(cnames, ri)
|
||||
}
|
||||
}
|
||||
if len(rr) == 0 {
|
||||
return cnames, nil
|
||||
}
|
||||
return rr, nil
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/acme-dns/acme-dns/pkg/acmedns"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Records is a slice of ResourceRecords
|
||||
type Records struct {
|
||||
Records []dns.RR
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func InitAndStart(config *acmedns.AcmeDnsConfig, db acmedns.AcmednsDB, logger *zap.SugaredLogger, errChan chan error) []acmedns.AcmednsNS {
|
||||
dnsservers := make([]acmedns.AcmednsNS, 0)
|
||||
|
||||
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"
|
||||
if strings.HasSuffix(config.General.Proto, "4") {
|
||||
udpProto += "4"
|
||||
tcpProto += "4"
|
||||
} else if strings.HasSuffix(config.General.Proto, "6") {
|
||||
udpProto += "6"
|
||||
tcpProto += "6"
|
||||
}
|
||||
dnsServerUDP := NewDNSServer(config, db, logger, udpProto)
|
||||
dnsservers = append(dnsservers, dnsServerUDP)
|
||||
dnsServerUDP.ParseRecords()
|
||||
dnsServerTCP := NewDNSServer(config, db, logger, tcpProto)
|
||||
dnsservers = append(dnsservers, dnsServerTCP)
|
||||
dnsServerTCP.ParseRecords()
|
||||
go dnsServerUDP.Start(errChan)
|
||||
go dnsServerTCP.Start(errChan)
|
||||
} else {
|
||||
dnsServer := NewDNSServer(config, db, logger, config.General.Proto)
|
||||
dnsservers = append(dnsservers, dnsServer)
|
||||
dnsServer.ParseRecords()
|
||||
go dnsServer.Start(errChan)
|
||||
}
|
||||
return dnsservers
|
||||
}
|
||||
|
||||
// NewDNSServer parses the DNS records from config and returns a new DNSServer struct
|
||||
func NewDNSServer(config *acmedns.AcmeDnsConfig, db acmedns.AcmednsDB, logger *zap.SugaredLogger, proto string) acmedns.AcmednsNS {
|
||||
// dnsServerTCP := NewDNSServer(DB, Config.General.Listen, tcpProto, Config.General.Domain)
|
||||
server := Nameserver{Config: config, DB: db, Logger: logger}
|
||||
server.Server = &dns.Server{Addr: config.General.Listen, Net: proto}
|
||||
domain := config.General.Domain
|
||||
if !strings.HasSuffix(domain, ".") {
|
||||
domain = domain + "."
|
||||
}
|
||||
server.OwnDomain = strings.ToLower(domain)
|
||||
server.DB = db
|
||||
server.personalAuthKey = ""
|
||||
server.Domains = make(map[string]Records)
|
||||
return &server
|
||||
}
|
||||
|
||||
func (n *Nameserver) Start(errorChannel chan error) {
|
||||
n.errChan = errorChannel
|
||||
dns.HandleFunc(".", n.handleRequest)
|
||||
n.Logger.Infow("Starting DNS listener",
|
||||
"addr", n.Server.Addr,
|
||||
"proto", n.Server.Net)
|
||||
err := n.Server.ListenAndServe()
|
||||
if err != nil {
|
||||
errorChannel <- err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/miekg/dns"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ParseRecords parses a slice of DNS record string
|
||||
func (n *Nameserver) ParseRecords() {
|
||||
for _, v := range n.Config.General.StaticRecords {
|
||||
rr, err := dns.NewRR(strings.ToLower(v))
|
||||
if err != nil {
|
||||
n.Logger.Errorw("Could not parse RR from config",
|
||||
"error", err.Error(),
|
||||
"rr", v)
|
||||
continue
|
||||
}
|
||||
// Add parsed RR
|
||||
n.appendRR(rr)
|
||||
}
|
||||
// Create serial
|
||||
serial := time.Now().Format("2006010215")
|
||||
// Add SOA
|
||||
SOAstring := fmt.Sprintf("%s. SOA %s. %s. %s 28800 7200 604800 86400", strings.ToLower(n.Config.General.Domain), strings.ToLower(n.Config.General.Nsname), strings.ToLower(n.Config.General.Nsadmin), serial)
|
||||
soarr, err := dns.NewRR(SOAstring)
|
||||
if err != nil {
|
||||
n.Logger.Errorw("Error while adding SOA record",
|
||||
"error", err.Error(),
|
||||
"soa", SOAstring)
|
||||
} else {
|
||||
n.appendRR(soarr)
|
||||
n.SOA = soarr
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Nameserver) appendRR(rr dns.RR) {
|
||||
addDomain := rr.Header().Name
|
||||
_, ok := n.Domains[addDomain]
|
||||
if !ok {
|
||||
n.Domains[addDomain] = Records{[]dns.RR{rr}}
|
||||
} else {
|
||||
drecs := n.Domains[addDomain]
|
||||
drecs.Records = append(drecs.Records, rr)
|
||||
n.Domains[addDomain] = drecs
|
||||
}
|
||||
n.Logger.Debugw("Adding new record to domain",
|
||||
"recordtype", dns.TypeToString[rr.Header().Rrtype],
|
||||
"domain", addDomain)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package nameserver
|
||||
|
||||
import "strings"
|
||||
|
||||
func sanitizeDomainQuestion(d string) string {
|
||||
dom := strings.ToLower(d)
|
||||
firstDot := strings.Index(d, ".")
|
||||
if firstDot > 0 {
|
||||
dom = dom[0:firstDot]
|
||||
}
|
||||
return dom
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package nameserver
|
||||
|
||||
import "github.com/miekg/dns"
|
||||
|
||||
// SetOwnAuthKey sets the ACME challenge token for completing dns validation for acme-dns server itself
|
||||
func (n *Nameserver) SetOwnAuthKey(key string) {
|
||||
n.personalAuthKey = key
|
||||
}
|
||||
|
||||
// answerOwnChallenge answers to ACME challenge for acme-dns own certificate
|
||||
func (n *Nameserver) answerOwnChallenge(q dns.Question) ([]dns.RR, error) {
|
||||
r := new(dns.TXT)
|
||||
r.Hdr = dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 1}
|
||||
r.Txt = append(r.Txt, n.personalAuthKey)
|
||||
return []dns.RR{r}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user