1package dns 2 3import ( 4 "io" 5 "io/ioutil" 6 "net" 7 "os" 8 "strings" 9 "testing" 10) 11 12func TestParseZoneGenerate(t *testing.T) { 13 zone := "$ORIGIN example.org.\n$GENERATE 10-12 foo${2,3,d} IN A 127.0.0.$" 14 15 wantRRs := []RR{ 16 &A{Hdr: RR_Header{Name: "foo012.example.org."}, A: net.ParseIP("127.0.0.10")}, 17 &A{Hdr: RR_Header{Name: "foo013.example.org."}, A: net.ParseIP("127.0.0.11")}, 18 &A{Hdr: RR_Header{Name: "foo014.example.org."}, A: net.ParseIP("127.0.0.12")}, 19 } 20 wantIdx := 0 21 22 tok := ParseZone(strings.NewReader(zone), "", "") 23 for x := range tok { 24 if wantIdx >= len(wantRRs) { 25 t.Fatalf("expected %d RRs, but got more", len(wantRRs)) 26 } 27 if x.Error != nil { 28 t.Fatalf("expected no error, but got %s", x.Error) 29 } 30 if got, want := x.RR.Header().Name, wantRRs[wantIdx].Header().Name; got != want { 31 t.Fatalf("expected name %s, but got %s", want, got) 32 } 33 a, ok := x.RR.(*A) 34 if !ok { 35 t.Fatalf("expected *A RR, but got %T", x.RR) 36 } 37 if got, want := a.A, wantRRs[wantIdx].(*A).A; !got.Equal(want) { 38 t.Fatalf("expected A with IP %v, but got %v", got, want) 39 } 40 wantIdx++ 41 } 42 43 if wantIdx != len(wantRRs) { 44 t.Errorf("too few records, expected %d, got %d", len(wantRRs), wantIdx) 45 } 46} 47 48func TestParseZoneInclude(t *testing.T) { 49 50 tmpfile, err := ioutil.TempFile("", "dns") 51 if err != nil { 52 t.Fatalf("could not create tmpfile for test: %s", err) 53 } 54 defer os.Remove(tmpfile.Name()) 55 56 if _, err := tmpfile.WriteString("foo\tIN\tA\t127.0.0.1"); err != nil { 57 t.Fatalf("unable to write content to tmpfile %q: %s", tmpfile.Name(), err) 58 } 59 if err := tmpfile.Close(); err != nil { 60 t.Fatalf("could not close tmpfile %q: %s", tmpfile.Name(), err) 61 } 62 63 zone := "$ORIGIN example.org.\n$INCLUDE " + tmpfile.Name() + "\nbar\tIN\tA\t127.0.0.2" 64 65 var got int 66 tok := ParseZone(strings.NewReader(zone), "", "") 67 for x := range tok { 68 if x.Error != nil { 69 t.Fatalf("expected no error, but got %s", x.Error) 70 } 71 switch x.RR.Header().Name { 72 case "foo.example.org.", "bar.example.org.": 73 default: 74 t.Fatalf("expected foo.example.org. or bar.example.org., but got %s", x.RR.Header().Name) 75 } 76 got++ 77 } 78 79 if expected := 2; got != expected { 80 t.Errorf("failed to parse zone after include, expected %d records, got %d", expected, got) 81 } 82 83 os.Remove(tmpfile.Name()) 84 85 tok = ParseZone(strings.NewReader(zone), "", "") 86 for x := range tok { 87 if x.Error == nil { 88 t.Fatalf("expected first token to contain an error but it didn't") 89 } 90 if !strings.Contains(x.Error.Error(), "failed to open") || 91 !strings.Contains(x.Error.Error(), tmpfile.Name()) || 92 !strings.Contains(x.Error.Error(), "no such file or directory") { 93 t.Fatalf(`expected error to contain: "failed to open", %q and "no such file or directory" but got: %s`, 94 tmpfile.Name(), x.Error) 95 } 96 } 97} 98 99func TestZoneParserIncludeDisallowed(t *testing.T) { 100 tmpfile, err := ioutil.TempFile("", "dns") 101 if err != nil { 102 t.Fatalf("could not create tmpfile for test: %s", err) 103 } 104 defer os.Remove(tmpfile.Name()) 105 106 if _, err := tmpfile.WriteString("foo\tIN\tA\t127.0.0.1"); err != nil { 107 t.Fatalf("unable to write content to tmpfile %q: %s", tmpfile.Name(), err) 108 } 109 if err := tmpfile.Close(); err != nil { 110 t.Fatalf("could not close tmpfile %q: %s", tmpfile.Name(), err) 111 } 112 113 zp := NewZoneParser(strings.NewReader("$INCLUDE "+tmpfile.Name()), "example.org.", "") 114 115 for _, ok := zp.Next(); ok; _, ok = zp.Next() { 116 } 117 118 const expect = "$INCLUDE directive not allowed" 119 if err := zp.Err(); err == nil || !strings.Contains(err.Error(), expect) { 120 t.Errorf("expected error to contain %q, got %v", expect, err) 121 } 122} 123 124func TestZoneParserAddressAAAA(t *testing.T) { 125 tests := []struct { 126 record string 127 want *AAAA 128 }{ 129 { 130 record: "1.example.org. 600 IN AAAA ::1", 131 want: &AAAA{Hdr: RR_Header{Name: "1.example.org."}, AAAA: net.IPv6loopback}, 132 }, 133 { 134 record: "2.example.org. 600 IN AAAA ::FFFF:127.0.0.1", 135 want: &AAAA{Hdr: RR_Header{Name: "2.example.org."}, AAAA: net.ParseIP("::FFFF:127.0.0.1")}, 136 }, 137 } 138 139 for _, tc := range tests { 140 got, err := NewRR(tc.record) 141 if err != nil { 142 t.Fatalf("expected no error, but got %s", err) 143 } 144 aaaa, ok := got.(*AAAA) 145 if !ok { 146 t.Fatalf("expected *AAAA RR, but got %T", aaaa) 147 } 148 if g, w := aaaa.AAAA, tc.want.AAAA; !g.Equal(w) { 149 t.Fatalf("expected AAAA with IP %v, but got %v", g, w) 150 } 151 } 152} 153 154func TestZoneParserAddressBad(t *testing.T) { 155 records := []string{ 156 "1.bad.example.org. 600 IN A ::1", 157 "2.bad.example.org. 600 IN A ::FFFF:127.0.0.1", 158 "3.bad.example.org. 600 IN AAAA 127.0.0.1", 159 } 160 161 for _, record := range records { 162 const expect = "bad A" 163 if got, err := NewRR(record); err == nil || !strings.Contains(err.Error(), expect) { 164 t.Errorf("NewRR(%v) = %v, want err to contain %q", record, got, expect) 165 } 166 } 167} 168 169func TestParseTA(t *testing.T) { 170 rr, err := NewRR(` Ta 0 0 0`) 171 if err != nil { 172 t.Fatalf("expected no error, but got %s", err) 173 } 174 if rr == nil { 175 t.Fatal(`expected a normal RR, but got nil`) 176 } 177} 178 179var errTestReadError = &Error{"test error"} 180 181type errReader struct{} 182 183func (errReader) Read(p []byte) (int, error) { return 0, errTestReadError } 184 185func TestParseZoneReadError(t *testing.T) { 186 rr, err := ReadRR(errReader{}, "") 187 if err == nil || !strings.Contains(err.Error(), errTestReadError.Error()) { 188 t.Errorf("expected error to contain %q, but got %v", errTestReadError, err) 189 } 190 if rr != nil { 191 t.Errorf("expected a nil RR, but got %v", rr) 192 } 193} 194 195func TestUnexpectedNewline(t *testing.T) { 196 zone := ` 197example.com. 60 PX 1981000 TXT 1K 199` 200 zp := NewZoneParser(strings.NewReader(zone), "example.com.", "") 201 for _, ok := zp.Next(); ok; _, ok = zp.Next() { 202 } 203 204 const expect = `dns: unexpected newline: "\n" at line: 2:18` 205 if err := zp.Err(); err == nil || err.Error() != expect { 206 t.Errorf("expected error to contain %q, got %v", expect, err) 207 } 208 209 // Test that newlines inside braces still work. 210 zone = ` 211example.com. 60 PX ( 2121000 TXT 1K ) 213` 214 zp = NewZoneParser(strings.NewReader(zone), "example.com.", "") 215 216 var count int 217 for _, ok := zp.Next(); ok; _, ok = zp.Next() { 218 count++ 219 } 220 221 if count != 1 { 222 t.Errorf("expected 1 record, got %d", count) 223 } 224 225 if err := zp.Err(); err != nil { 226 t.Errorf("unexpected error: %v", err) 227 } 228} 229 230func BenchmarkNewRR(b *testing.B) { 231 const name1 = "12345678901234567890123456789012345.12345678.123." 232 const s = name1 + " 3600 IN MX 10 " + name1 233 234 for n := 0; n < b.N; n++ { 235 _, err := NewRR(s) 236 if err != nil { 237 b.Fatal(err) 238 } 239 } 240} 241 242func BenchmarkReadRR(b *testing.B) { 243 const name1 = "12345678901234567890123456789012345.12345678.123." 244 const s = name1 + " 3600 IN MX 10 " + name1 + "\n" 245 246 for n := 0; n < b.N; n++ { 247 r := struct{ io.Reader }{strings.NewReader(s)} 248 // r is now only an io.Reader and won't benefit from the 249 // io.ByteReader special-case in zlexer.Next. 250 251 _, err := ReadRR(r, "") 252 if err != nil { 253 b.Fatal(err) 254 } 255 } 256} 257 258const benchZone = ` 259foo. IN A 10.0.0.1 ; this is comment 1 260foo. IN A ( 261 10.0.0.2 ; this is comment 2 262) 263; this is comment 3 264foo. IN A 10.0.0.3 265foo. IN A ( 10.0.0.4 ); this is comment 4 266 267foo. IN A 10.0.0.5 268; this is comment 5 269 270foo. IN A 10.0.0.6 271 272foo. IN DNSKEY 256 3 5 AwEAAb+8l ; this is comment 6 273foo. IN NSEC miek.nl. TXT RRSIG NSEC; this is comment 7 274foo. IN TXT "THIS IS TEXT MAN"; this is comment 8 275` 276 277func BenchmarkParseZone(b *testing.B) { 278 for n := 0; n < b.N; n++ { 279 for tok := range ParseZone(strings.NewReader(benchZone), "example.org.", "") { 280 if tok.Error != nil { 281 b.Fatal(tok.Error) 282 } 283 } 284 } 285} 286 287func BenchmarkZoneParser(b *testing.B) { 288 for n := 0; n < b.N; n++ { 289 zp := NewZoneParser(strings.NewReader(benchZone), "example.org.", "") 290 291 for _, ok := zp.Next(); ok; _, ok = zp.Next() { 292 } 293 294 if err := zp.Err(); err != nil { 295 b.Fatal(err) 296 } 297 } 298} 299