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