github.com/teknogeek/dnscontrol/v2@v2.10.1-0.20200227202244-ae299b55ba42/pkg/prettyzone/sorting.go (about) 1 package prettyzone 2 3 // Generate zonefiles. 4 // This generates a zonefile that prioritizes beauty over efficiency. 5 6 import ( 7 "bytes" 8 "log" 9 "strconv" 10 "strings" 11 12 "github.com/StackExchange/dnscontrol/v2/models" 13 ) 14 15 type zoneGenData struct { 16 Origin string 17 DefaultTTL uint32 18 Records models.Records 19 Comments []string 20 } 21 22 func (z *zoneGenData) Len() int { return len(z.Records) } 23 func (z *zoneGenData) Swap(i, j int) { z.Records[i], z.Records[j] = z.Records[j], z.Records[i] } 24 func (z *zoneGenData) Less(i, j int) bool { 25 a, b := z.Records[i], z.Records[j] 26 27 // Sort by name. 28 compA, compB := a.NameFQDN, b.NameFQDN 29 if compA != compB { 30 if a.Name == "@" { 31 compA = "@" 32 } 33 if b.Name == "@" { 34 compB = "@" 35 } 36 return zoneLabelLess(compA, compB) 37 } 38 39 // sub-sort by type 40 if a.Type != b.Type { 41 return zoneRrtypeLess(a.Type, b.Type) 42 } 43 44 // sub-sort within type: 45 switch a.Type { // #rtype_variations 46 case "A": 47 ta2, tb2 := a.GetTargetIP(), b.GetTargetIP() 48 ipa, ipb := ta2.To4(), tb2.To4() 49 if ipa == nil || ipb == nil { 50 log.Fatalf("should not happen: IPs are not 4 bytes: %#v %#v", ta2, tb2) 51 } 52 return bytes.Compare(ipa, ipb) == -1 53 case "AAAA": 54 ta2, tb2 := a.GetTargetIP(), b.GetTargetIP() 55 ipa, ipb := ta2.To16(), tb2.To16() 56 if ipa == nil || ipb == nil { 57 log.Fatalf("should not happen: IPs are not 16 bytes: %#v %#v", ta2, tb2) 58 } 59 return bytes.Compare(ipa, ipb) == -1 60 case "MX": 61 // sort by priority. If they are equal, sort by Mx. 62 if a.MxPreference == b.MxPreference { 63 return a.GetTargetField() < b.GetTargetField() 64 } 65 return a.MxPreference < b.MxPreference 66 case "SRV": 67 //ta2, tb2 := a.(*dns.SRV), b.(*dns.SRV) 68 pa, pb := a.SrvPort, b.SrvPort 69 if pa != pb { 70 return pa < pb 71 } 72 pa, pb = a.SrvPriority, b.SrvPriority 73 if pa != pb { 74 return pa < pb 75 } 76 pa, pb = a.SrvWeight, b.SrvWeight 77 if pa != pb { 78 return pa < pb 79 } 80 case "PTR": 81 //ta2, tb2 := a.(*dns.PTR), b.(*dns.PTR) 82 pa, pb := a.GetTargetField(), b.GetTargetField() 83 if pa != pb { 84 return pa < pb 85 } 86 case "CAA": 87 //ta2, tb2 := a.(*dns.CAA), b.(*dns.CAA) 88 // sort by tag 89 pa, pb := a.CaaTag, b.CaaTag 90 if pa != pb { 91 return pa < pb 92 } 93 // then flag 94 fa, fb := a.CaaFlag, b.CaaFlag 95 if fa != fb { 96 // flag set goes before ones without flag set 97 return fa > fb 98 } 99 default: 100 // pass through. String comparison is sufficient. 101 } 102 return a.String() < b.String() 103 } 104 105 func zoneLabelLess(a, b string) bool { 106 // Compare two zone labels for the purpose of sorting the RRs in a Zone. 107 108 // If they are equal, we are done. All other code is simplified 109 // because we can assume a!=b. 110 if a == b { 111 return false 112 } 113 114 // Sort @ at the top, then *, then everything else lexigraphically. 115 // i.e. @ always is less. * is is less than everything but @. 116 if a == "@" { 117 return true 118 } 119 if b == "@" { 120 return false 121 } 122 if a == "*" { 123 return true 124 } 125 if b == "*" { 126 return false 127 } 128 129 // Split into elements and match up last elements to first. Compare the 130 // first non-equal elements. 131 132 as := strings.Split(a, ".") 133 bs := strings.Split(b, ".") 134 ia := len(as) - 1 135 ib := len(bs) - 1 136 137 var min int 138 if ia < ib { 139 min = len(as) - 1 140 } else { 141 min = len(bs) - 1 142 } 143 144 // Skip the matching highest elements, then compare the next item. 145 for i, j := ia, ib; min >= 0; i, j, min = i-1, j-1, min-1 { 146 // Compare as[i] < bs[j] 147 // Sort @ at the top, then *, then everything else. 148 // i.e. @ always is less. * is is less than everything but @. 149 // If both are numeric, compare as integers, otherwise as strings. 150 151 if as[i] != bs[j] { 152 153 // If the first element is *, it is always less. 154 if i == 0 && as[i] == "*" { 155 return true 156 } 157 if j == 0 && bs[j] == "*" { 158 return false 159 } 160 161 // If the elements are both numeric, compare as integers: 162 au, aerr := strconv.ParseUint(as[i], 10, 64) 163 bu, berr := strconv.ParseUint(bs[j], 10, 64) 164 if aerr == nil && berr == nil { 165 return au < bu 166 } 167 // otherwise, compare as strings: 168 return as[i] < bs[j] 169 } 170 } 171 // The min top elements were equal, so the shorter name is less. 172 return ia < ib 173 } 174 175 func zoneRrtypeLess(a, b string) bool { 176 // Compare two RR types for the purpose of sorting the RRs in a Zone. 177 178 if a == b { 179 return false 180 } 181 182 // List SOAs, NSs, etc. then all others alphabetically. 183 184 for _, t := range []string{"SOA", "NS", "CNAME", 185 "A", "AAAA", "MX", "SRV", "TXT", 186 } { 187 if a == t { 188 return true 189 } 190 if b == t { 191 return false 192 } 193 } 194 return a < b 195 }