1
0
mirror of https://github.com/StackExchange/dnscontrol.git synced 2024-05-11 05:55:12 +00:00
Tom Limoncelli 9812ecd9ff BIND: Improve SOA serial number handling (#651)
* github.com/miekg/dns
* Greatly simplify the logic for handling serial numbers. Related code was all over the place. Now it is abstracted into one testable method makeSoa. This simplifies code in many other places.
* Update docs/_providers/bind.md: Edit old text. Add SOA description.
* SOA records are now treated like any other record internally. You still can't specify them in dnsconfig.js, but that's by design.
* The URL for issue 491 was wrong in many places
* BIND: Clarify GENERATE_ZONEFILE message
2020-02-23 13:58:49 -05:00

151 lines
3.6 KiB
Go

package prettyzone
// Generate zonefiles.
// This generates a zonefile that prioritizes beauty over efficiency.
import (
"fmt"
"io"
"sort"
"strings"
"github.com/StackExchange/dnscontrol/v2/models"
"github.com/miekg/dns"
)
// mostCommonTTL returns the most common TTL in a set of records. If there is
// a tie, the highest TTL is selected. This makes the results consistent.
// NS records are not included in the analysis because Tom said so.
func mostCommonTTL(records models.Records) uint32 {
// Index the TTLs in use:
d := make(map[uint32]int)
for _, r := range records {
if r.Type != "NS" {
d[r.TTL]++
}
}
// Find the largest count:
var mc int
for _, value := range d {
if value > mc {
mc = value
}
}
// Find the largest key with that count:
var mk uint32
for key, value := range d {
if value == mc {
if key > mk {
mk = key
}
}
}
return mk
}
// WriteZoneFileRR is a helper for when you have []dns.RR instead of models.Records
func WriteZoneFileRR(w io.Writer, records []dns.RR, origin string) error {
return WriteZoneFileRC(w, models.RRstoRCs(records, origin), origin, nil)
}
// WriteZoneFileRC writes a beautifully formatted zone file.
func WriteZoneFileRC(w io.Writer, records models.Records, origin string, comments []string) error {
// This function prioritizes beauty over output size.
// * The zone records are sorted by label, grouped by subzones to
// be easy to read and pleasant to the eye.
// * Within a label, SOA and NS records are listed first.
// * MX records are sorted numericly by preference value.
// * SRV records are sorted numericly by port, then priority, then weight.
// * A records are sorted by IP address, not lexicographically.
// * Repeated labels are removed.
// * $TTL is used to eliminate clutter. The most common TTL value is used.
// * "@" is used instead of the apex domain name.
z := PrettySort(records, origin, mostCommonTTL(records), comments)
return z.generateZoneFileHelper(w)
}
func PrettySort(records models.Records, origin string, defaultTTL uint32, comments []string) *zoneGenData {
if defaultTTL == 0 {
defaultTTL = mostCommonTTL(records)
}
z := &zoneGenData{
Origin: origin + ".",
DefaultTTL: defaultTTL,
Comments: comments,
}
z.Records = nil
for _, r := range records {
z.Records = append(z.Records, r)
}
return z
}
// generateZoneFileHelper creates a pretty zonefile.
func (z *zoneGenData) generateZoneFileHelper(w io.Writer) error {
nameShortPrevious := ""
sort.Sort(z)
fmt.Fprintln(w, "$TTL", z.DefaultTTL)
for _, comment := range z.Comments {
for _, line := range strings.Split(comment, "\n") {
if line != "" {
fmt.Fprintln(w, ";", line)
}
}
}
for i, rr := range z.Records {
// Fake types are commented out.
prefix := ""
_, ok := dns.StringToType[rr.Type]
if !ok {
prefix = ";"
}
// name
nameShort := rr.Name
name := nameShort
if (prefix == "") && (i > 0 && nameShort == nameShortPrevious) {
name = ""
} else {
name = nameShort
}
nameShortPrevious = nameShort
// ttl
ttl := ""
if rr.TTL != z.DefaultTTL && rr.TTL != 0 {
ttl = fmt.Sprint(rr.TTL)
}
// type
typeStr := rr.Type
// the remaining line
target := rr.GetTargetCombined()
fmt.Fprintf(w, "%s%s\n",
prefix, formatLine([]int{10, 5, 2, 5, 0}, []string{name, ttl, "IN", typeStr, target}))
}
return nil
}
func formatLine(lengths []int, fields []string) string {
c := 0
result := ""
for i, length := range lengths {
item := fields[i]
for len(result) < c {
result += " "
}
if item != "" {
result += item + " "
}
c += length + 1
}
return strings.TrimRight(result, " ")
}