1package dns 2 3import ( 4 "io" 5 "io/ioutil" 6 "net" 7 "os" 8 "strings" 9 "testing" 10) 11 12func TestZoneParserGenerate(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 21 wantIdx := 0 22 23 z := NewZoneParser(strings.NewReader(zone), "", "") 24 25 for rr, ok := z.Next(); ok; rr, ok = z.Next() { 26 if wantIdx >= len(wantRRs) { 27 t.Fatalf("expected %d RRs, but got more", len(wantRRs)) 28 } 29 if got, want := rr.Header().Name, wantRRs[wantIdx].Header().Name; got != want { 30 t.Fatalf("expected name %s, but got %s", want, got) 31 } 32 a, okA := rr.(*A) 33 if !okA { 34 t.Fatalf("expected *A RR, but got %T", rr) 35 } 36 if got, want := a.A, wantRRs[wantIdx].(*A).A; !got.Equal(want) { 37 t.Fatalf("expected A with IP %v, but got %v", got, want) 38 } 39 wantIdx++ 40 } 41 42 if err := z.Err(); err != nil { 43 t.Fatalf("expected no error, but got %s", err) 44 } 45 46 if wantIdx != len(wantRRs) { 47 t.Errorf("too few records, expected %d, got %d", len(wantRRs), wantIdx) 48 } 49} 50 51func TestZoneParserInclude(t *testing.T) { 52 53 tmpfile, err := ioutil.TempFile("", "dns") 54 if err != nil { 55 t.Fatalf("could not create tmpfile for test: %s", err) 56 } 57 defer os.Remove(tmpfile.Name()) 58 59 if _, err := tmpfile.WriteString("foo\tIN\tA\t127.0.0.1"); err != nil { 60 t.Fatalf("unable to write content to tmpfile %q: %s", tmpfile.Name(), err) 61 } 62 if err := tmpfile.Close(); err != nil { 63 t.Fatalf("could not close tmpfile %q: %s", tmpfile.Name(), err) 64 } 65 66 zone := "$ORIGIN example.org.\n$INCLUDE " + tmpfile.Name() + "\nbar\tIN\tA\t127.0.0.2" 67 68 var got int 69 z := NewZoneParser(strings.NewReader(zone), "", "") 70 z.SetIncludeAllowed(true) 71 for rr, ok := z.Next(); ok; _, ok = z.Next() { 72 switch rr.Header().Name { 73 case "foo.example.org.", "bar.example.org.": 74 default: 75 t.Fatalf("expected foo.example.org. or bar.example.org., but got %s", rr.Header().Name) 76 } 77 got++ 78 } 79 if err := z.Err(); err != nil { 80 t.Fatalf("expected no error, but got %s", err) 81 } 82 83 if expected := 2; got != expected { 84 t.Errorf("failed to parse zone after include, expected %d records, got %d", expected, got) 85 } 86 87 os.Remove(tmpfile.Name()) 88 89 z = NewZoneParser(strings.NewReader(zone), "", "") 90 z.SetIncludeAllowed(true) 91 z.Next() 92 if err := z.Err(); err == nil || 93 !strings.Contains(err.Error(), "failed to open") || 94 !strings.Contains(err.Error(), tmpfile.Name()) || 95 !strings.Contains(err.Error(), "no such file or directory") { 96 t.Fatalf(`expected error to contain: "failed to open", %q and "no such file or directory" but got: %s`, 97 tmpfile.Name(), err) 98 } 99} 100 101func TestZoneParserIncludeDisallowed(t *testing.T) { 102 tmpfile, err := ioutil.TempFile("", "dns") 103 if err != nil { 104 t.Fatalf("could not create tmpfile for test: %s", err) 105 } 106 defer os.Remove(tmpfile.Name()) 107 108 if _, err := tmpfile.WriteString("foo\tIN\tA\t127.0.0.1"); err != nil { 109 t.Fatalf("unable to write content to tmpfile %q: %s", tmpfile.Name(), err) 110 } 111 if err := tmpfile.Close(); err != nil { 112 t.Fatalf("could not close tmpfile %q: %s", tmpfile.Name(), err) 113 } 114 115 zp := NewZoneParser(strings.NewReader("$INCLUDE "+tmpfile.Name()), "example.org.", "") 116 117 for _, ok := zp.Next(); ok; _, ok = zp.Next() { 118 } 119 120 const expect = "$INCLUDE directive not allowed" 121 if err := zp.Err(); err == nil || !strings.Contains(err.Error(), expect) { 122 t.Errorf("expected error to contain %q, got %v", expect, err) 123 } 124} 125 126func TestZoneParserAddressAAAA(t *testing.T) { 127 tests := []struct { 128 record string 129 want *AAAA 130 }{ 131 { 132 record: "1.example.org. 600 IN AAAA ::1", 133 want: &AAAA{Hdr: RR_Header{Name: "1.example.org."}, AAAA: net.IPv6loopback}, 134 }, 135 { 136 record: "2.example.org. 600 IN AAAA ::FFFF:127.0.0.1", 137 want: &AAAA{Hdr: RR_Header{Name: "2.example.org."}, AAAA: net.ParseIP("::FFFF:127.0.0.1")}, 138 }, 139 } 140 141 for _, tc := range tests { 142 got, err := NewRR(tc.record) 143 if err != nil { 144 t.Fatalf("expected no error, but got %s", err) 145 } 146 aaaa, ok := got.(*AAAA) 147 if !ok { 148 t.Fatalf("expected *AAAA RR, but got %T", got) 149 } 150 if !aaaa.AAAA.Equal(tc.want.AAAA) { 151 t.Fatalf("expected AAAA with IP %v, but got %v", tc.want.AAAA, aaaa.AAAA) 152 } 153 } 154} 155 156func TestZoneParserAddressBad(t *testing.T) { 157 records := []string{ 158 "1.bad.example.org. 600 IN A ::1", 159 "2.bad.example.org. 600 IN A ::FFFF:127.0.0.1", 160 "3.bad.example.org. 600 IN AAAA 127.0.0.1", 161 } 162 163 for _, record := range records { 164 const expect = "bad A" 165 if got, err := NewRR(record); err == nil || !strings.Contains(err.Error(), expect) { 166 t.Errorf("NewRR(%v) = %v, want err to contain %q", record, got, expect) 167 } 168 } 169} 170 171func TestParseTA(t *testing.T) { 172 rr, err := NewRR(` Ta 0 0 0`) 173 if err != nil { 174 t.Fatalf("expected no error, but got %s", err) 175 } 176 if rr == nil { 177 t.Fatal(`expected a normal RR, but got nil`) 178 } 179} 180 181var errTestReadError = &Error{"test error"} 182 183type errReader struct{} 184 185func (errReader) Read(p []byte) (int, error) { return 0, errTestReadError } 186 187func TestParseZoneReadError(t *testing.T) { 188 rr, err := ReadRR(errReader{}, "") 189 if err == nil || !strings.Contains(err.Error(), errTestReadError.Error()) { 190 t.Errorf("expected error to contain %q, but got %v", errTestReadError, err) 191 } 192 if rr != nil { 193 t.Errorf("expected a nil RR, but got %v", rr) 194 } 195} 196 197func TestUnexpectedNewline(t *testing.T) { 198 zone := ` 199example.com. 60 PX 2001000 TXT 1K 201` 202 zp := NewZoneParser(strings.NewReader(zone), "example.com.", "") 203 for _, ok := zp.Next(); ok; _, ok = zp.Next() { 204 } 205 206 const expect = `dns: unexpected newline: "\n" at line: 2:18` 207 if err := zp.Err(); err == nil || err.Error() != expect { 208 t.Errorf("expected error to contain %q, got %v", expect, err) 209 } 210 211 // Test that newlines inside braces still work. 212 zone = ` 213example.com. 60 PX ( 2141000 TXT 1K ) 215` 216 zp = NewZoneParser(strings.NewReader(zone), "example.com.", "") 217 218 var count int 219 for _, ok := zp.Next(); ok; _, ok = zp.Next() { 220 count++ 221 } 222 223 if count != 1 { 224 t.Errorf("expected 1 record, got %d", count) 225 } 226 227 if err := zp.Err(); err != nil { 228 t.Errorf("unexpected error: %v", err) 229 } 230} 231 232func TestParseRFC3597InvalidLength(t *testing.T) { 233 // We need to space separate the 00s otherwise it will exceed the maximum token size 234 // of the zone lexer. 235 _, err := NewRR("example. 3600 CLASS1 TYPE1 \\# 65536 " + strings.Repeat("00 ", 65536)) 236 if err == nil { 237 t.Error("should not have parsed excessively long RFC3579 record") 238 } 239} 240 241func TestParseKnownRRAsRFC3597(t *testing.T) { 242 t.Run("with RDATA", func(t *testing.T) { 243 // This was found by oss-fuzz. 244 _, err := NewRR("example. 3600 tYpe44 \\# 03 75 0100") 245 if err != nil { 246 t.Errorf("failed to parse RFC3579 format: %v", err) 247 } 248 249 rr, err := NewRR("example. 3600 CLASS1 TYPE1 \\# 4 7f000001") 250 if err != nil { 251 t.Fatalf("failed to parse RFC3579 format: %v", err) 252 } 253 254 if rr.Header().Rrtype != TypeA { 255 t.Errorf("expected TypeA (1) Rrtype, but got %v", rr.Header().Rrtype) 256 } 257 258 a, ok := rr.(*A) 259 if !ok { 260 t.Fatalf("expected *A RR, but got %T", rr) 261 } 262 263 localhost := net.IPv4(127, 0, 0, 1) 264 if !a.A.Equal(localhost) { 265 t.Errorf("expected A with IP %v, but got %v", localhost, a.A) 266 } 267 }) 268 t.Run("without RDATA", func(t *testing.T) { 269 rr, err := NewRR("example. 3600 CLASS1 TYPE1 \\# 0") 270 if err != nil { 271 t.Fatalf("failed to parse RFC3579 format: %v", err) 272 } 273 274 if rr.Header().Rrtype != TypeA { 275 t.Errorf("expected TypeA (1) Rrtype, but got %v", rr.Header().Rrtype) 276 } 277 278 a, ok := rr.(*A) 279 if !ok { 280 t.Fatalf("expected *A RR, but got %T", rr) 281 } 282 283 if len(a.A) != 0 { 284 t.Errorf("expected A with empty IP, but got %v", a.A) 285 } 286 }) 287} 288 289func BenchmarkNewRR(b *testing.B) { 290 const name1 = "12345678901234567890123456789012345.12345678.123." 291 const s = name1 + " 3600 IN MX 10 " + name1 292 293 for n := 0; n < b.N; n++ { 294 _, err := NewRR(s) 295 if err != nil { 296 b.Fatal(err) 297 } 298 } 299} 300 301func BenchmarkReadRR(b *testing.B) { 302 const name1 = "12345678901234567890123456789012345.12345678.123." 303 const s = name1 + " 3600 IN MX 10 " + name1 + "\n" 304 305 for n := 0; n < b.N; n++ { 306 r := struct{ io.Reader }{strings.NewReader(s)} 307 // r is now only an io.Reader and won't benefit from the 308 // io.ByteReader special-case in zlexer.Next. 309 310 _, err := ReadRR(r, "") 311 if err != nil { 312 b.Fatal(err) 313 } 314 } 315} 316 317const benchZone = ` 318foo. IN A 10.0.0.1 ; this is comment 1 319foo. IN A ( 320 10.0.0.2 ; this is comment 2 321) 322; this is comment 3 323foo. IN A 10.0.0.3 324foo. IN A ( 10.0.0.4 ); this is comment 4 325 326foo. IN A 10.0.0.5 327; this is comment 5 328 329foo. IN A 10.0.0.6 330 331foo. IN DNSKEY 256 3 5 AwEAAb+8l ; this is comment 6 332foo. IN NSEC miek.nl. TXT RRSIG NSEC; this is comment 7 333foo. IN TXT "THIS IS TEXT MAN"; this is comment 8 334` 335 336func BenchmarkZoneParser(b *testing.B) { 337 for n := 0; n < b.N; n++ { 338 zp := NewZoneParser(strings.NewReader(benchZone), "example.org.", "") 339 340 for _, ok := zp.Next(); ok; _, ok = zp.Next() { 341 } 342 343 if err := zp.Err(); err != nil { 344 b.Fatal(err) 345 } 346 } 347} 348