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  }