github.com/philhug/dnscontrol@v0.2.4-0.20180625181521-921fa9849001/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 // Test 1 of each record type 233 234 func mustNewRR(s string) dns.RR { 235 r, err := dns.NewRR(s) 236 if err != nil { 237 panic(err) 238 } 239 return r 240 } 241 242 func TestWriteZoneFileEach(t *testing.T) { 243 // Each rtype should be listed in this test exactly once. 244 // If an rtype has more than one variations, add a test like TestWriteZoneFileCaa to test each. 245 var d []dns.RR 246 // #rtype_variations 247 d = append(d, mustNewRR(`4.5 300 IN PTR y.bosun.org.`)) // Wouldn't actually be in this domain. 248 d = append(d, mustNewRR(`bosun.org. 300 IN A 1.2.3.4`)) 249 d = append(d, mustNewRR(`bosun.org. 300 IN MX 1 bosun.org.`)) 250 d = append(d, mustNewRR(`bosun.org. 300 IN TXT "my text"`)) 251 d = append(d, mustNewRR(`bosun.org. 300 IN AAAA 4500:fe::1`)) 252 d = append(d, mustNewRR(`bosun.org. 300 IN SRV 10 10 9999 foo.com.`)) 253 d = append(d, mustNewRR(`bosun.org. 300 IN CAA 0 issue "letsencrypt.org"`)) 254 d = append(d, mustNewRR(`_443._tcp.bosun.org. 300 IN TLSA 3 1 1 abcdef0`)) // Label must be _port._proto 255 d = append(d, mustNewRR(`sub.bosun.org. 300 IN NS bosun.org.`)) // Must be a label with no other records. 256 d = append(d, mustNewRR(`x.bosun.org. 300 IN CNAME bosun.org.`)) // Must be a label with no other records. 257 buf := &bytes.Buffer{} 258 WriteZoneFile(buf, d, "bosun.org") 259 if buf.String() != testdataZFEach { 260 t.Log(buf.String()) 261 t.Log(testdataZFEach) 262 t.Fatalf("Zone file does not match.") 263 } 264 parseAndRegen(t, buf, testdataZFEach) 265 } 266 267 var testdataZFEach = `$TTL 300 268 4.5. IN PTR y.bosun.org. 269 @ IN A 1.2.3.4 270 IN MX 1 bosun.org. 271 IN TXT "my text" 272 IN AAAA 4500:fe::1 273 IN SRV 10 10 9999 foo.com. 274 IN CAA 0 issue "letsencrypt.org" 275 _443._tcp IN TLSA 3 1 1 abcdef0 276 sub IN NS bosun.org. 277 x IN CNAME bosun.org. 278 ` 279 280 // Test sorting 281 282 func TestWriteZoneFileOrder(t *testing.T) { 283 var records []dns.RR 284 for i, td := range []string{ 285 "@", 286 "@", 287 "@", 288 "stackoverflow.com.", 289 "*", 290 "foo", 291 "bar.foo", 292 "hip.foo", 293 "mup", 294 "a.mup", 295 "bzt.mup", 296 "aaa.bzt.mup", 297 "zzz.bzt.mup", 298 "nnn.mup", 299 "zt.mup", 300 "zap", 301 } { 302 name := dnsutil.AddOrigin(td, "stackoverflow.com.") 303 r, _ := dns.NewRR(fmt.Sprintf("%s 300 IN A 1.2.3.%d", name, i)) 304 records = append(records, r) 305 } 306 307 buf := &bytes.Buffer{} 308 WriteZoneFile(buf, records, "stackoverflow.com.") 309 // Compare 310 if buf.String() != testdataOrder { 311 t.Log("Found:") 312 t.Log(buf.String()) 313 t.Log("Expected:") 314 t.Log(testdataOrder) 315 t.Fatalf("Zone file does not match.") 316 } 317 parseAndRegen(t, buf, testdataOrder) 318 319 // Now shuffle the list many times and make sure it still works: 320 for iteration := 5; iteration > 0; iteration-- { 321 // Randomize the list: 322 perm := rand.Perm(len(records)) 323 for i, v := range perm { 324 records[i], records[v] = records[v], records[i] 325 } 326 // Generate 327 buf := &bytes.Buffer{} 328 WriteZoneFile(buf, records, "stackoverflow.com.") 329 // Compare 330 if buf.String() != testdataOrder { 331 t.Log(buf.String()) 332 t.Log(testdataOrder) 333 t.Fatalf("Zone file does not match.") 334 } 335 parseAndRegen(t, buf, testdataOrder) 336 } 337 } 338 339 var testdataOrder = `$TTL 300 340 @ IN A 1.2.3.0 341 IN A 1.2.3.1 342 IN A 1.2.3.2 343 IN A 1.2.3.3 344 * IN A 1.2.3.4 345 foo IN A 1.2.3.5 346 bar.foo IN A 1.2.3.6 347 hip.foo IN A 1.2.3.7 348 mup IN A 1.2.3.8 349 a.mup IN A 1.2.3.9 350 bzt.mup IN A 1.2.3.10 351 aaa.bzt.mup IN A 1.2.3.11 352 zzz.bzt.mup IN A 1.2.3.12 353 nnn.mup IN A 1.2.3.13 354 zt.mup IN A 1.2.3.14 355 zap IN A 1.2.3.15 356 ` 357 358 // func formatLine 359 360 func TestFormatLine(t *testing.T) { 361 tests := []struct { 362 lengths []int 363 fields []string 364 expected string 365 }{ 366 {[]int{2, 2, 0}, []string{"a", "b", "c"}, "a b c"}, 367 {[]int{2, 2, 0}, []string{"aaaaa", "b", "c"}, "aaaaa b c"}, 368 } 369 for _, ts := range tests { 370 actual := formatLine(ts.lengths, ts.fields) 371 if actual != ts.expected { 372 t.Errorf("\"%s\" != \"%s\"", actual, ts.expected) 373 } 374 } 375 } 376 377 // func zoneLabelLess 378 379 func TestZoneLabelLess(t *testing.T) { 380 /* 381 The zone should sort in prefix traversal order: 382 383 @ 384 * 385 foo 386 bar.foo 387 hip.foo 388 mup 389 a.mup 390 bzt.mup 391 *.bzt.mup 392 1.bzt.mup 393 2.bzt.mup 394 10.bzt.mup 395 aaa.bzt.mup 396 zzz.bzt.mup 397 nnn.mup 398 zt.mup 399 zap 400 */ 401 402 var tests = []struct { 403 e1, e2 string 404 expected bool 405 }{ 406 {"@", "@", false}, 407 {"@", "*", true}, 408 {"@", "b", true}, 409 {"*", "@", false}, 410 {"*", "*", false}, 411 {"*", "b", true}, 412 {"foo", "foo", false}, 413 {"foo", "bar", false}, 414 {"bar", "foo", true}, 415 {"a.mup", "mup", false}, 416 {"mup", "a.mup", true}, 417 {"a.mup", "a.mup", false}, 418 {"a.mup", "bzt.mup", true}, 419 {"a.mup", "aa.mup", true}, 420 {"zt.mup", "aaa.bzt.mup", false}, 421 {"aaa.bzt.mup", "mup", false}, 422 {"*.bzt.mup", "aaa.bzt.mup", true}, 423 {"1.bzt.mup", "aaa.bzt.mup", true}, 424 {"1.bzt.mup", "2.bzt.mup", true}, 425 {"10.bzt.mup", "2.bzt.mup", false}, 426 {"nnn.mup", "aaa.bzt.mup", false}, 427 {`www\.miek.nl`, `www.miek.nl`, false}, 428 } 429 430 for _, test := range tests { 431 actual := zoneLabelLess(test.e1, test.e2) 432 if test.expected != actual { 433 t.Errorf("%v: expected (%v) got (%v)\n", test.e1, test.e2, actual) 434 } 435 actual = zoneLabelLess(test.e2, test.e1) 436 // The reverse should work too: 437 var expected bool 438 if test.e1 == test.e2 { 439 expected = false 440 } else { 441 expected = !test.expected 442 } 443 if expected != actual { 444 t.Errorf("%v: expected (%v) got (%v)\n", test.e1, test.e2, actual) 445 } 446 } 447 } 448 449 func TestZoneRrtypeLess(t *testing.T) { 450 /* 451 In zonefiles we want to list SOAs, then NSs, then all others. 452 */ 453 454 var tests = []struct { 455 e1, e2 uint16 456 expected bool 457 }{ 458 {dns.TypeSOA, dns.TypeSOA, false}, 459 {dns.TypeSOA, dns.TypeA, true}, 460 {dns.TypeSOA, dns.TypeTXT, true}, 461 {dns.TypeSOA, dns.TypeNS, true}, 462 {dns.TypeNS, dns.TypeSOA, false}, 463 {dns.TypeNS, dns.TypeA, true}, 464 {dns.TypeNS, dns.TypeTXT, true}, 465 {dns.TypeNS, dns.TypeNS, false}, 466 {dns.TypeA, dns.TypeSOA, false}, 467 {dns.TypeA, dns.TypeA, false}, 468 {dns.TypeA, dns.TypeTXT, true}, 469 {dns.TypeA, dns.TypeNS, false}, 470 {dns.TypeMX, dns.TypeSOA, false}, 471 {dns.TypeMX, dns.TypeA, false}, 472 {dns.TypeMX, dns.TypeTXT, true}, 473 {dns.TypeMX, dns.TypeNS, false}, 474 } 475 476 for _, test := range tests { 477 actual := zoneRrtypeLess(test.e1, test.e2) 478 if test.expected != actual { 479 t.Errorf("%v: expected (%v) got (%v)\n", test.e1, test.e2, actual) 480 } 481 actual = zoneRrtypeLess(test.e2, test.e1) 482 // The reverse should work too: 483 var expected bool 484 if test.e1 == test.e2 { 485 expected = false 486 } else { 487 expected = !test.expected 488 } 489 if expected != actual { 490 t.Errorf("%v: expected (%v) got (%v)\n", test.e1, test.e2, actual) 491 } 492 } 493 }