github.com/teknogeek/dnscontrol/v2@v2.10.1-0.20200227202244-ae299b55ba42/pkg/prettyzone/prettyzone.go (about) 1 package prettyzone 2 3 // Generate zonefiles. 4 // This generates a zonefile that prioritizes beauty over efficiency. 5 6 import ( 7 "fmt" 8 "io" 9 "sort" 10 "strings" 11 12 "github.com/StackExchange/dnscontrol/v2/models" 13 "github.com/miekg/dns" 14 ) 15 16 // MostCommonTTL returns the most common TTL in a set of records. If there is 17 // a tie, the highest TTL is selected. This makes the results consistent. 18 // NS records are not included in the analysis because Tom said so. 19 func MostCommonTTL(records models.Records) uint32 { 20 // Index the TTLs in use: 21 d := make(map[uint32]int) 22 for _, r := range records { 23 if r.Type != "NS" { 24 d[r.TTL]++ 25 } 26 } 27 // Find the largest count: 28 var mc int 29 for _, value := range d { 30 if value > mc { 31 mc = value 32 } 33 } 34 // Find the largest key with that count: 35 var mk uint32 36 for key, value := range d { 37 if value == mc { 38 if key > mk { 39 mk = key 40 } 41 } 42 } 43 return mk 44 } 45 46 // WriteZoneFileRR is a helper for when you have []dns.RR instead of models.Records 47 func WriteZoneFileRR(w io.Writer, records []dns.RR, origin string) error { 48 return WriteZoneFileRC(w, models.RRstoRCs(records, origin), origin, 0, nil) 49 } 50 51 // WriteZoneFileRC writes a beautifully formatted zone file. 52 func WriteZoneFileRC(w io.Writer, records models.Records, origin string, defaultTTL uint32, comments []string) error { 53 // This function prioritizes beauty over output size. 54 // * The zone records are sorted by label, grouped by subzones to 55 // be easy to read and pleasant to the eye. 56 // * Within a label, SOA and NS records are listed first. 57 // * MX records are sorted numericly by preference value. 58 // * SRV records are sorted numericly by port, then priority, then weight. 59 // * A records are sorted by IP address, not lexicographically. 60 // * Repeated labels are removed. 61 // * $TTL is used to eliminate clutter. The most common TTL value is used. 62 // * "@" is used instead of the apex domain name. 63 64 if defaultTTL == 0 { 65 defaultTTL = MostCommonTTL(records) 66 } 67 68 z := PrettySort(records, origin, defaultTTL, comments) 69 70 return z.generateZoneFileHelper(w) 71 } 72 73 func PrettySort(records models.Records, origin string, defaultTTL uint32, comments []string) *zoneGenData { 74 if defaultTTL == 0 { 75 defaultTTL = MostCommonTTL(records) 76 } 77 z := &zoneGenData{ 78 Origin: origin + ".", 79 DefaultTTL: defaultTTL, 80 Comments: comments, 81 } 82 if z.DefaultTTL == 0 { 83 z.DefaultTTL = 300 84 } 85 z.Records = nil 86 for _, r := range records { 87 z.Records = append(z.Records, r) 88 } 89 return z 90 } 91 92 // generateZoneFileHelper creates a pretty zonefile. 93 func (z *zoneGenData) generateZoneFileHelper(w io.Writer) error { 94 95 nameShortPrevious := "" 96 97 sort.Sort(z) 98 if z.DefaultTTL == 0 { 99 z.DefaultTTL = 300 100 } 101 fmt.Fprintln(w, "$TTL", z.DefaultTTL) 102 for _, comment := range z.Comments { 103 for _, line := range strings.Split(comment, "\n") { 104 if line != "" { 105 fmt.Fprintln(w, ";", line) 106 } 107 } 108 } 109 for i, rr := range z.Records { 110 111 // Fake types are commented out. 112 prefix := "" 113 _, ok := dns.StringToType[rr.Type] 114 if !ok { 115 prefix = ";" 116 } 117 118 // name 119 nameShort := rr.Name 120 name := nameShort 121 if (prefix == "") && (i > 0 && nameShort == nameShortPrevious) { 122 name = "" 123 } else { 124 name = nameShort 125 } 126 nameShortPrevious = nameShort 127 128 // ttl 129 ttl := "" 130 if rr.TTL != z.DefaultTTL && rr.TTL != 0 { 131 ttl = fmt.Sprint(rr.TTL) 132 } 133 134 // type 135 typeStr := rr.Type 136 137 // the remaining line 138 target := rr.GetTargetCombined() 139 140 fmt.Fprintf(w, "%s%s\n", 141 prefix, formatLine([]int{10, 5, 2, 5, 0}, []string{name, ttl, "IN", typeStr, target})) 142 } 143 return nil 144 } 145 146 func formatLine(lengths []int, fields []string) string { 147 c := 0 148 result := "" 149 for i, length := range lengths { 150 item := fields[i] 151 for len(result) < c { 152 result += " " 153 } 154 if item != "" { 155 result += item + " " 156 } 157 c += length + 1 158 } 159 return strings.TrimRight(result, " ") 160 }