152 lines
3.6 KiB
Go
152 lines
3.6 KiB
Go
package nameserver
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
func loadSerial(path string) (uint32, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return 0, nil // first start
|
|
}
|
|
return 0, err
|
|
}
|
|
|
|
s := strings.TrimSpace(string(data))
|
|
val, err := strconv.ParseUint(s, 10, 32)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return uint32(val), nil
|
|
}
|
|
|
|
func saveSerial(path string, serial uint32) error {
|
|
tmp := path + ".tmp"
|
|
|
|
data := []byte(fmt.Sprintf("%d\n", serial))
|
|
|
|
// write temp file
|
|
if err := os.WriteFile(tmp, data, 0644); err != nil {
|
|
return err
|
|
}
|
|
|
|
// atomic replace
|
|
return os.Rename(tmp, path)
|
|
}
|
|
|
|
func nextSerial(old uint32) uint32 {
|
|
today := time.Now().Format("20060102")
|
|
|
|
oldStr := fmt.Sprintf("%d", old)
|
|
|
|
if strings.HasPrefix(oldStr, today) {
|
|
return old + 1
|
|
}
|
|
|
|
newSerial, _ := strconv.Atoi(today + "00")
|
|
|
|
if uint32(newSerial) <= old {
|
|
return old + 1
|
|
}
|
|
|
|
return uint32(newSerial)
|
|
}
|
|
|
|
// 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, err := loadSerial(n.Config.General.Serialpath)
|
|
|
|
if err != nil {
|
|
n.Logger.Errorw("Could not load temp serial",
|
|
"error", err.Error())
|
|
}
|
|
if serial == 0 {
|
|
serial = uint32(time.Now().Unix())
|
|
}
|
|
// Add SOA
|
|
//Refresh = 30s → Slaves fragen alle 30s nach Änderungen
|
|
//Retry = 10s → Wenn Master nicht erreichbar, probiert der Slave alle 10s erneut
|
|
//Expire = 604800s (1w) → Wie lange der Slave die Zone noch behält, falls Master ausfällt
|
|
//Minimum TTL = 20s → Resolver cachen die TXT-Einträge nur kurz
|
|
//SOAstring := fmt.Sprintf("%s. SOA %s. %s. %s 5 10 604800 20", strings.ToLower(n.Config.General.Domain), strings.ToLower(n.Config.General.Nsname), strings.ToLower(n.Config.General.Nsadmin), serial)
|
|
n.SOA = &dns.SOA{
|
|
Hdr: dns.RR_Header{
|
|
Name: dns.Fqdn(n.Config.General.Domain),
|
|
Rrtype: dns.TypeSOA,
|
|
Class: dns.ClassINET,
|
|
Ttl: 5,
|
|
},
|
|
Ns: dns.Fqdn(n.Config.General.Nsname),
|
|
Mbox: dns.Fqdn(n.Config.General.Nsadmin),
|
|
Serial: serial,
|
|
Refresh: 5,
|
|
Retry: 10,
|
|
Expire: 604800,
|
|
Minttl: 1,
|
|
}
|
|
}
|
|
|
|
func sendNotify(zone string, slaveAddr string) error {
|
|
m := new(dns.Msg)
|
|
m.SetNotify(dns.Fqdn(zone)) // Set opcode to NOTIFY
|
|
m.Authoritative = true // Must be authoritative
|
|
|
|
c := new(dns.Client)
|
|
_, _, err := c.Exchange(m, slaveAddr)
|
|
return err
|
|
}
|
|
|
|
func (n *Nameserver) BumpSerial() error {
|
|
n.mu.Lock()
|
|
defer n.mu.Unlock()
|
|
n.SOA.Serial = nextSerial(n.SOA.Serial)
|
|
|
|
for _, slave := range n.Config.General.SlaveHosts {
|
|
slave := slave + ":53"
|
|
if err := sendNotify(n.SOA.Hdr.Name, slave); err != nil {
|
|
n.Logger.Errorw("Failed to notify slave", "slave", slave, "err", err)
|
|
} else {
|
|
n.Logger.Debugw("Notify send to slave", "slave", slave)
|
|
}
|
|
}
|
|
return saveSerial(n.Config.General.Serialpath, n.SOA.Serial)
|
|
}
|
|
|
|
func (n *Nameserver) appendRR(rr dns.RR) {
|
|
addDomain := rr.Header().Name
|
|
_, ok := n.Domains[addDomain]
|
|
if !ok {
|
|
n.Domains[addDomain] = Records{
|
|
Records: []dns.RR{rr}, // initialisiere Records
|
|
NS: []dns.RR{}, // leeres NS-Slice, sonst Fehler
|
|
}
|
|
} 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)
|
|
}
|