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