mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
* 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
151 lines
3.6 KiB
Go
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, " ")
|
|
}
|