mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-11-10 17:26:10 +08:00
166 lines
3.9 KiB
Go
166 lines
3.9 KiB
Go
package prettyzone
|
|
|
|
// Generate zonefiles.
|
|
// This generates a zonefile that prioritizes beauty over efficiency.
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/StackExchange/dnscontrol/v4/models"
|
|
"github.com/StackExchange/dnscontrol/v4/pkg/txtutil"
|
|
"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
|
|
}
|
|
|
|
// WriteZoneFileRC writes a beautifully formatted zone file.
|
|
func WriteZoneFileRC(w io.Writer, records models.Records, origin string, defaultTTL uint32, 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.
|
|
|
|
if defaultTTL == 0 {
|
|
defaultTTL = MostCommonTTL(records)
|
|
}
|
|
|
|
z := PrettySort(records, origin, defaultTTL, comments)
|
|
|
|
return z.generateZoneFileHelper(w)
|
|
}
|
|
|
|
// PrettySort sorts the records in a pretty order.
|
|
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,
|
|
}
|
|
if z.DefaultTTL == 0 {
|
|
z.DefaultTTL = 300
|
|
}
|
|
z.Records = nil
|
|
z.Records = append(z.Records, records...)
|
|
sort.Sort(z)
|
|
return z
|
|
}
|
|
|
|
// generateZoneFileHelper creates a pretty zonefile.
|
|
func (z *ZoneGenData) generateZoneFileHelper(w io.Writer) error {
|
|
|
|
nameShortPrevious := ""
|
|
|
|
sort.Sort(z)
|
|
if z.DefaultTTL == 0 {
|
|
z.DefaultTTL = 300
|
|
}
|
|
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 = ""
|
|
}
|
|
nameShortPrevious = nameShort
|
|
|
|
// ttl
|
|
ttl := ""
|
|
if rr.TTL != z.DefaultTTL && rr.TTL != 0 {
|
|
ttl = fmt.Sprint(rr.TTL)
|
|
}
|
|
|
|
// type
|
|
typeStr := rr.Type
|
|
if rr.Type == "UNKNOWN" {
|
|
typeStr = rr.UnknownTypeName
|
|
}
|
|
|
|
// the remaining line
|
|
target := rr.GetTargetCombinedFunc(txtutil.EncodeQuoted)
|
|
|
|
// comment
|
|
comment := ""
|
|
if cp, ok := rr.Metadata["cloudflare_proxy"]; ok {
|
|
if cp == "true" {
|
|
comment = " ; CF_PROXY_ON"
|
|
}
|
|
}
|
|
|
|
fmt.Fprintf(w, "%s%s%s\n",
|
|
prefix, FormatLine([]int{10, 5, 2, 5, 0}, []string{name, ttl, "IN", typeStr, target}), comment)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FormatLine formats a zonefile line.
|
|
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, " ")
|
|
}
|