github.com/pmoroney/dnscontrol@v0.2.4-0.20171024134423-fad98f73f44a/providers/bind/prettyzone_test.go (about) 1 package bind 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "math/rand" 8 "testing" 9 10 "github.com/miekg/dns" 11 "github.com/miekg/dns/dnsutil" 12 ) 13 14 func parseAndRegen(t *testing.T, buf *bytes.Buffer, expected string) { 15 // Take a zonefile, parse it, then generate a zone. We should 16 // get back the same string. 17 // This is used after any WriteZoneFile test as an extra verification step. 18 19 // Parse the output: 20 var parsed []dns.RR 21 for x := range dns.ParseZone(buf, "bosun.org", "bosun.org.zone") { 22 if x.Error != nil { 23 log.Fatalf("Error in zonefile: %v", x.Error) 24 } else { 25 parsed = append(parsed, x.RR) 26 } 27 } 28 // Generate it back: 29 buf2 := &bytes.Buffer{} 30 WriteZoneFile(buf2, parsed, "bosun.org.") 31 32 // Compare: 33 if buf2.String() != expected { 34 t.Fatalf("Regenerated zonefile does not match: got=(\n%v\n)\nexpected=(\n%v\n)\n", buf2.String(), expected) 35 } 36 } 37 38 func TestMostCommonTtl(t *testing.T) { 39 var records []dns.RR 40 var g, e uint32 41 r1, _ := dns.NewRR("bosun.org. 100 IN A 1.1.1.1") 42 r2, _ := dns.NewRR("bosun.org. 200 IN A 1.1.1.1") 43 r3, _ := dns.NewRR("bosun.org. 300 IN A 1.1.1.1") 44 r4, _ := dns.NewRR("bosun.org. 400 IN NS foo.bosun.org.") 45 r5, _ := dns.NewRR("bosun.org. 400 IN NS bar.bosun.org.") 46 47 // All records are TTL=100 48 records = nil 49 records, e = append(records, r1, r1, r1), 100 50 g = mostCommonTtl(records) 51 if e != g { 52 t.Fatalf("expected %d; got %d\n", e, g) 53 } 54 55 // Mixture of TTLs with an obvious winner. 56 records = nil 57 records, e = append(records, r1, r2, r2), 200 58 g = mostCommonTtl(records) 59 if e != g { 60 t.Fatalf("expected %d; got %d\n", e, g) 61 } 62 63 // 3-way tie. Largest TTL should be used. 64 records = nil 65 records, e = append(records, r1, r2, r3), 300 66 g = mostCommonTtl(records) 67 if e != g { 68 t.Fatalf("expected %d; got %d\n", e, g) 69 } 70 71 // NS records are ignored. 72 records = nil 73 records, e = append(records, r1, r4, r5), 100 74 g = mostCommonTtl(records) 75 if e != g { 76 t.Fatalf("expected %d; got %d\n", e, g) 77 } 78 79 } 80 81 // func WriteZoneFile 82 83 func TestWriteZoneFileSimple(t *testing.T) { 84 r1, _ := dns.NewRR("bosun.org. 300 IN A 192.30.252.153") 85 r2, _ := dns.NewRR("bosun.org. 300 IN A 192.30.252.154") 86 r3, _ := dns.NewRR("www.bosun.org. 300 IN CNAME bosun.org.") 87 buf := &bytes.Buffer{} 88 WriteZoneFile(buf, []dns.RR{r1, r2, r3}, "bosun.org.") 89 expected := `$TTL 300 90 @ IN A 192.30.252.153 91 IN A 192.30.252.154 92 www IN CNAME bosun.org. 93 ` 94 if buf.String() != expected { 95 t.Log(buf.String()) 96 t.Log(expected) 97 t.Fatalf("Zone file does not match.") 98 } 99 100 parseAndRegen(t, buf, expected) 101 } 102 103 func TestWriteZoneFileSimpleTtl(t *testing.T) { 104 r1, _ := dns.NewRR("bosun.org. 100 IN A 192.30.252.153") 105 r2, _ := dns.NewRR("bosun.org. 100 IN A 192.30.252.154") 106 r3, _ := dns.NewRR("bosun.org. 100 IN A 192.30.252.155") 107 r4, _ := dns.NewRR("www.bosun.org. 300 IN CNAME bosun.org.") 108 buf := &bytes.Buffer{} 109 WriteZoneFile(buf, []dns.RR{r1, r2, r3, r4}, "bosun.org.") 110 expected := `$TTL 100 111 @ IN A 192.30.252.153 112 IN A 192.30.252.154 113 IN A 192.30.252.155 114 www 300 IN CNAME bosun.org. 115 ` 116 if buf.String() != expected { 117 t.Log(buf.String()) 118 t.Log(expected) 119 t.Fatalf("Zone file does not match.") 120 } 121 122 parseAndRegen(t, buf, expected) 123 } 124 125 func TestWriteZoneFileMx(t *testing.T) { 126 //exhibits explicit ttls and long name 127 r1, _ := dns.NewRR(`bosun.org. 300 IN TXT "aaa"`) 128 r2, _ := dns.NewRR(`bosun.org. 300 IN TXT "bbb"`) 129 r2.(*dns.TXT).Txt[0] = `b"bb` 130 r3, _ := dns.NewRR("bosun.org. 300 IN MX 1 ASPMX.L.GOOGLE.COM.") 131 r4, _ := dns.NewRR("bosun.org. 300 IN MX 5 ALT1.ASPMX.L.GOOGLE.COM.") 132 r5, _ := dns.NewRR("bosun.org. 300 IN MX 10 ASPMX3.GOOGLEMAIL.COM.") 133 r6, _ := dns.NewRR("bosun.org. 300 IN A 198.252.206.16") 134 r7, _ := dns.NewRR("*.bosun.org. 600 IN A 198.252.206.16") 135 r8, _ := dns.NewRR(`_domainkey.bosun.org. 300 IN TXT "vvvv"`) 136 r9, _ := dns.NewRR(`google._domainkey.bosun.org. 300 IN TXT "\"foo\""`) 137 buf := &bytes.Buffer{} 138 WriteZoneFile(buf, []dns.RR{r1, r2, r3, r4, r5, r6, r7, r8, r9}, "bosun.org") 139 if buf.String() != testdataZFMX { 140 t.Log(buf.String()) 141 t.Log(testdataZFMX) 142 t.Fatalf("Zone file does not match.") 143 } 144 parseAndRegen(t, buf, testdataZFMX) 145 } 146 147 var testdataZFMX = `$TTL 300 148 @ IN A 198.252.206.16 149 IN MX 1 ASPMX.L.GOOGLE.COM. 150 IN MX 5 ALT1.ASPMX.L.GOOGLE.COM. 151 IN MX 10 ASPMX3.GOOGLEMAIL.COM. 152 IN TXT "aaa" 153 IN TXT "b\"bb" 154 * 600 IN A 198.252.206.16 155 _domainkey IN TXT "vvvv" 156 google._domainkey IN TXT "\"foo\"" 157 ` 158 159 func TestWriteZoneFileSrv(t *testing.T) { 160 //exhibits explicit ttls and long name 161 r1, _ := dns.NewRR(`bosun.org. 300 IN SRV 10 10 9999 foo.com.`) 162 r2, _ := dns.NewRR(`bosun.org. 300 IN SRV 10 20 5050 foo.com.`) 163 r3, _ := dns.NewRR(`bosun.org. 300 IN SRV 10 10 5050 foo.com.`) 164 r4, _ := dns.NewRR(`bosun.org. 300 IN SRV 20 10 5050 foo.com.`) 165 r5, _ := dns.NewRR(`bosun.org. 300 IN SRV 10 10 5050 foo.com.`) 166 buf := &bytes.Buffer{} 167 WriteZoneFile(buf, []dns.RR{r1, r2, r3, r4, r5}, "bosun.org") 168 if buf.String() != testdataZFSRV { 169 t.Log(buf.String()) 170 t.Log(testdataZFSRV) 171 t.Fatalf("Zone file does not match.") 172 } 173 parseAndRegen(t, buf, testdataZFSRV) 174 } 175 176 var testdataZFSRV = `$TTL 300 177 @ IN SRV 10 10 5050 foo.com. 178 IN SRV 10 10 5050 foo.com. 179 IN SRV 10 20 5050 foo.com. 180 IN SRV 20 10 5050 foo.com. 181 IN SRV 10 10 9999 foo.com. 182 ` 183 184 func TestWriteZoneFilePtr(t *testing.T) { 185 //exhibits explicit ttls and long name 186 r1, _ := dns.NewRR(`bosun.org. 300 IN PTR chell.bosun.org`) 187 r2, _ := dns.NewRR(`bosun.org. 300 IN PTR barney.bosun.org.`) 188 r3, _ := dns.NewRR(`bosun.org. 300 IN PTR alex.bosun.org.`) 189 buf := &bytes.Buffer{} 190 WriteZoneFile(buf, []dns.RR{r1, r2, r3}, "bosun.org") 191 if buf.String() != testdataZFPTR { 192 t.Log(buf.String()) 193 t.Log(testdataZFPTR) 194 t.Fatalf("Zone file does not match.") 195 } 196 parseAndRegen(t, buf, testdataZFPTR) 197 } 198 199 var testdataZFPTR = `$TTL 300 200 @ IN PTR alex.bosun.org. 201 IN PTR barney.bosun.org. 202 IN PTR chell.bosun.org. 203 ` 204 205 func TestWriteZoneFileCaa(t *testing.T) { 206 //exhibits explicit ttls and long name 207 r1, _ := dns.NewRR(`bosun.org. 300 IN CAA 0 issuewild ";"`) 208 r2, _ := dns.NewRR(`bosun.org. 300 IN CAA 0 issue "letsencrypt.org"`) 209 r3, _ := dns.NewRR(`bosun.org. 300 IN CAA 1 iodef "http://example.com"`) 210 r4, _ := dns.NewRR(`bosun.org. 300 IN CAA 0 iodef "https://example.com"`) 211 r5, _ := dns.NewRR(`bosun.org. 300 IN CAA 0 iodef "https://example.net"`) 212 r6, _ := dns.NewRR(`bosun.org. 300 IN CAA 1 iodef "mailto:example.com"`) 213 buf := &bytes.Buffer{} 214 WriteZoneFile(buf, []dns.RR{r1, r2, r3, r4, r5, r6}, "bosun.org") 215 if buf.String() != testdataZFCAA { 216 t.Log(buf.String()) 217 t.Log(testdataZFCAA) 218 t.Fatalf("Zone file does not match.") 219 } 220 parseAndRegen(t, buf, testdataZFCAA) 221 } 222 223 var testdataZFCAA = `$TTL 300 224 @ IN CAA 1 iodef "http://example.com" 225 IN CAA 1 iodef "mailto:example.com" 226 IN CAA 0 iodef "https://example.com" 227 IN CAA 0 iodef "https://example.net" 228 IN CAA 0 issue "letsencrypt.org" 229 IN CAA 0 issuewild ";" 230 ` 231 232 func TestWriteZoneFileOrder(t *testing.T) { 233 var records []dns.RR 234 for i, td := range []string{ 235 "@", 236 "@", 237 "@", 238 "stackoverflow.com.", 239 "*", 240 "foo", 241 "bar.foo", 242 "hip.foo", 243 "mup", 244 "a.mup", 245 "bzt.mup", 246 "aaa.bzt.mup", 247 "zzz.bzt.mup", 248 "nnn.mup", 249 "zt.mup", 250 "zap", 251 } { 252 name := dnsutil.AddOrigin(td, "stackoverflow.com.") 253 r, _ := dns.NewRR(fmt.Sprintf("%s 300 IN A 1.2.3.%d", name, i)) 254 records = append(records, r) 255 } 256 257 buf := &bytes.Buffer{} 258 WriteZoneFile(buf, records, "stackoverflow.com.") 259 // Compare 260 if buf.String() != testdataOrder { 261 t.Log("Found:") 262 t.Log(buf.String()) 263 t.Log("Expected:") 264 t.Log(testdataOrder) 265 t.Fatalf("Zone file does not match.") 266 } 267 parseAndRegen(t, buf, testdataOrder) 268 269 // Now shuffle the list many times and make sure it still works: 270 for iteration := 5; iteration > 0; iteration-- { 271 // Randomize the list: 272 perm := rand.Perm(len(records)) 273 for i, v := range perm { 274 records[i], records[v] = records[v], records[i] 275 //fmt.Println(i, v) 276 } 277 // Generate 278 buf := &bytes.Buffer{} 279 WriteZoneFile(buf, records, "stackoverflow.com.") 280 // Compare 281 if buf.String() != testdataOrder { 282 t.Log(buf.String()) 283 t.Log(testdataOrder) 284 t.Fatalf("Zone file does not match.") 285 } 286 parseAndRegen(t, buf, testdataOrder) 287 } 288 } 289 290 var testdataOrder = `$TTL 300 291 @ IN A 1.2.3.0 292 IN A 1.2.3.1 293 IN A 1.2.3.2 294 IN A 1.2.3.3 295 * IN A 1.2.3.4 296 foo IN A 1.2.3.5 297 bar.foo IN A 1.2.3.6 298 hip.foo IN A 1.2.3.7 299 mup IN A 1.2.3.8 300 a.mup IN A 1.2.3.9 301 bzt.mup IN A 1.2.3.10 302 aaa.bzt.mup IN A 1.2.3.11 303 zzz.bzt.mup IN A 1.2.3.12 304 nnn.mup IN A 1.2.3.13 305 zt.mup IN A 1.2.3.14 306 zap IN A 1.2.3.15 307 ` 308 309 // func formatLine 310 311 func TestFormatLine(t *testing.T) { 312 tests := []struct { 313 lengths []int 314 fields []string 315 expected string 316 }{ 317 {[]int{2, 2, 0}, []string{"a", "b", "c"}, "a b c"}, 318 {[]int{2, 2, 0}, []string{"aaaaa", "b", "c"}, "aaaaa b c"}, 319 } 320 for _, ts := range tests { 321 actual := formatLine(ts.lengths, ts.fields) 322 if actual != ts.expected { 323 t.Errorf("\"%s\" != \"%s\"", actual, ts.expected) 324 } 325 } 326 } 327 328 // func zoneLabelLess 329 330 func TestZoneLabelLess(t *testing.T) { 331 /* 332 The zone should sort in prefix traversal order: 333 334 @ 335 * 336 foo 337 bar.foo 338 hip.foo 339 mup 340 a.mup 341 bzt.mup 342 *.bzt.mup 343 1.bzt.mup 344 2.bzt.mup 345 10.bzt.mup 346 aaa.bzt.mup 347 zzz.bzt.mup 348 nnn.mup 349 zt.mup 350 zap 351 */ 352 353 var tests = []struct { 354 e1, e2 string 355 expected bool 356 }{ 357 {"@", "@", false}, 358 {"@", "*", true}, 359 {"@", "b", true}, 360 {"*", "@", false}, 361 {"*", "*", false}, 362 {"*", "b", true}, 363 {"foo", "foo", false}, 364 {"foo", "bar", false}, 365 {"bar", "foo", true}, 366 {"a.mup", "mup", false}, 367 {"mup", "a.mup", true}, 368 {"a.mup", "a.mup", false}, 369 {"a.mup", "bzt.mup", true}, 370 {"a.mup", "aa.mup", true}, 371 {"zt.mup", "aaa.bzt.mup", false}, 372 {"aaa.bzt.mup", "mup", false}, 373 {"*.bzt.mup", "aaa.bzt.mup", true}, 374 {"1.bzt.mup", "aaa.bzt.mup", true}, 375 {"1.bzt.mup", "2.bzt.mup", true}, 376 {"10.bzt.mup", "2.bzt.mup", false}, 377 {"nnn.mup", "aaa.bzt.mup", false}, 378 {`www\.miek.nl`, `www.miek.nl`, false}, 379 } 380 381 for _, test := range tests { 382 actual := zoneLabelLess(test.e1, test.e2) 383 if test.expected != actual { 384 t.Errorf("%v: expected (%v) got (%v)\n", test.e1, test.e2, actual) 385 } 386 actual = zoneLabelLess(test.e2, test.e1) 387 // The reverse should work too: 388 var expected bool 389 if test.e1 == test.e2 { 390 expected = false 391 } else { 392 expected = !test.expected 393 } 394 if expected != actual { 395 t.Errorf("%v: expected (%v) got (%v)\n", test.e1, test.e2, actual) 396 } 397 } 398 } 399 400 func TestZoneRrtypeLess(t *testing.T) { 401 /* 402 In zonefiles we want to list SOAs, then NSs, then all others. 403 */ 404 405 var tests = []struct { 406 e1, e2 uint16 407 expected bool 408 }{ 409 {dns.TypeSOA, dns.TypeSOA, false}, 410 {dns.TypeSOA, dns.TypeA, true}, 411 {dns.TypeSOA, dns.TypeTXT, true}, 412 {dns.TypeSOA, dns.TypeNS, true}, 413 {dns.TypeNS, dns.TypeSOA, false}, 414 {dns.TypeNS, dns.TypeA, true}, 415 {dns.TypeNS, dns.TypeTXT, true}, 416 {dns.TypeNS, dns.TypeNS, false}, 417 {dns.TypeA, dns.TypeSOA, false}, 418 {dns.TypeA, dns.TypeA, false}, 419 {dns.TypeA, dns.TypeTXT, true}, 420 {dns.TypeA, dns.TypeNS, false}, 421 {dns.TypeMX, dns.TypeSOA, false}, 422 {dns.TypeMX, dns.TypeA, false}, 423 {dns.TypeMX, dns.TypeTXT, true}, 424 {dns.TypeMX, dns.TypeNS, false}, 425 } 426 427 for _, test := range tests { 428 actual := zoneRrtypeLess(test.e1, test.e2) 429 if test.expected != actual { 430 t.Errorf("%v: expected (%v) got (%v)\n", test.e1, test.e2, actual) 431 } 432 actual = zoneRrtypeLess(test.e2, test.e1) 433 // The reverse should work too: 434 var expected bool 435 if test.e1 == test.e2 { 436 expected = false 437 } else { 438 expected = !test.expected 439 } 440 if expected != actual { 441 t.Errorf("%v: expected (%v) got (%v)\n", test.e1, test.e2, actual) 442 } 443 } 444 }