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) }