1package dns
2
3import (
4	"fmt"
5	"regexp"
6	"strconv"
7	"strings"
8	"testing"
9)
10
11const maxPrintableLabel = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789x"
12
13var (
14	longDomain = maxPrintableLabel[:53] + strings.TrimSuffix(
15		strings.Join([]string{".", ".", ".", ".", "."}, maxPrintableLabel[:49]), ".")
16
17	reChar              = regexp.MustCompile(`.`)
18	i                   = -1
19	maxUnprintableLabel = reChar.ReplaceAllStringFunc(maxPrintableLabel, func(ch string) string {
20		if i++; i >= 32 {
21			i = 0
22		}
23		return fmt.Sprintf("\\%03d", i)
24	})
25
26	// These are the longest possible domain names in presentation format.
27	longestDomain            = maxPrintableLabel[:61] + strings.Join([]string{".", ".", ".", "."}, maxPrintableLabel)
28	longestUnprintableDomain = maxUnprintableLabel[:61*4] + strings.Join([]string{".", ".", ".", "."}, maxUnprintableLabel)
29)
30
31func TestPackNoSideEffect(t *testing.T) {
32	m := new(Msg)
33	m.SetQuestion(Fqdn("example.com."), TypeNS)
34
35	a := new(Msg)
36	o := &OPT{
37		Hdr: RR_Header{
38			Name:   ".",
39			Rrtype: TypeOPT,
40		},
41	}
42	o.SetUDPSize(DefaultMsgSize)
43
44	a.Extra = append(a.Extra, o)
45	a.SetRcode(m, RcodeBadVers)
46
47	a.Pack()
48	if a.Rcode != RcodeBadVers {
49		t.Errorf("after pack: Rcode is expected to be BADVERS")
50	}
51}
52
53func TestPackExtendedBadCookie(t *testing.T) {
54	m := new(Msg)
55	m.SetQuestion(Fqdn("example.com."), TypeNS)
56
57	a := new(Msg)
58	a.SetReply(m)
59	o := &OPT{
60		Hdr: RR_Header{
61			Name:   ".",
62			Rrtype: TypeOPT,
63		},
64	}
65	o.SetUDPSize(DefaultMsgSize)
66	a.Extra = append(a.Extra, o)
67
68	a.SetRcode(m, RcodeBadCookie)
69
70	edns0 := a.IsEdns0()
71	if edns0 == nil {
72		t.Fatal("Expected OPT RR")
73	}
74	// SetExtendedRcode is only called as part of `Pack()`, hence at this stage,
75	// the OPT RR is not set yet.
76	if edns0.ExtendedRcode() == RcodeBadCookie&0xFFFFFFF0 {
77		t.Errorf("ExtendedRcode is expected to not be BADCOOKIE before Pack")
78	}
79
80	a.Pack()
81
82	edns0 = a.IsEdns0()
83	if edns0 == nil {
84		t.Fatal("Expected OPT RR")
85	}
86
87	if edns0.ExtendedRcode() != RcodeBadCookie&0xFFFFFFF0 {
88		t.Errorf("ExtendedRcode is expected to be BADCOOKIE after Pack")
89	}
90}
91
92func TestUnPackExtendedRcode(t *testing.T) {
93	m := new(Msg)
94	m.SetQuestion(Fqdn("example.com."), TypeNS)
95
96	a := new(Msg)
97	a.SetReply(m)
98	o := &OPT{
99		Hdr: RR_Header{
100			Name:   ".",
101			Rrtype: TypeOPT,
102		},
103	}
104	o.SetUDPSize(DefaultMsgSize)
105	a.Extra = append(a.Extra, o)
106
107	a.SetRcode(m, RcodeBadCookie)
108
109	packed, err := a.Pack()
110	if err != nil {
111		t.Fatalf("Could not unpack %v", a)
112	}
113
114	unpacked := new(Msg)
115	if err := unpacked.Unpack(packed); err != nil {
116		t.Fatalf("Failed to unpack message")
117	}
118
119	if unpacked.Rcode != RcodeBadCookie {
120		t.Fatalf("Rcode should be matching RcodeBadCookie (%d), got (%d)", RcodeBadCookie, unpacked.Rcode)
121	}
122}
123
124func TestUnpackDomainName(t *testing.T) {
125	var cases = []struct {
126		label          string
127		input          string
128		expectedOutput string
129		expectedError  string
130	}{
131		{"empty domain",
132			"\x00",
133			".",
134			""},
135		{"long label",
136			"?" + maxPrintableLabel + "\x00",
137			maxPrintableLabel + ".",
138			""},
139		{"unprintable label",
140			"?" + regexp.MustCompile(`\\[0-9]+`).ReplaceAllStringFunc(maxUnprintableLabel,
141				func(escape string) string {
142					n, _ := strconv.ParseInt(escape[1:], 10, 8)
143					return string(rune(n))
144				}) + "\x00",
145			maxUnprintableLabel + ".",
146			""},
147		{"long domain",
148			"5" + strings.Replace(longDomain, ".", "1", -1) + "\x00",
149			longDomain + ".",
150			""},
151		{"compression pointer",
152			// an unrealistic but functional test referencing an offset _inside_ a label
153			"\x03foo" + "\x05\x03com\x00" + "\x07example" + "\xC0\x05",
154			"foo.\\003com\\000.example.com.",
155			""},
156		{"too long domain",
157			"6" + "x" + strings.Replace(longDomain, ".", "1", -1) + "\x00",
158			"",
159			ErrLongDomain.Error()},
160		{"too long by pointer",
161			// a matryoshka doll name to get over 255 octets after expansion via internal pointers
162			string([]byte{
163				// 11 length values, first to last
164				40, 37, 34, 31, 28, 25, 22, 19, 16, 13, 0,
165				// 12 filler values
166				120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120,
167				// 10 pointers, last to first
168				192, 10, 192, 9, 192, 8, 192, 7, 192, 6, 192, 5, 192, 4, 192, 3, 192, 2, 192, 1,
169			}),
170			"",
171			ErrLongDomain.Error()},
172		{"long by pointer",
173			// a matryoshka doll name _not_ exceeding 255 octets after expansion
174			string([]byte{
175				// 11 length values, first to last
176				37, 34, 31, 28, 25, 22, 19, 16, 13, 10, 0,
177				// 9 filler values
178				120, 120, 120, 120, 120, 120, 120, 120, 120,
179				// 10 pointers, last to first
180				192, 10, 192, 9, 192, 8, 192, 7, 192, 6, 192, 5, 192, 4, 192, 3, 192, 2, 192, 1,
181			}),
182			"" +
183				(`\"\031\028\025\022\019\016\013\010\000xxxxxxxxx` +
184					`\192\010\192\009\192\008\192\007\192\006\192\005\192\004\192\003\192\002.`) +
185				(`\031\028\025\022\019\016\013\010\000xxxxxxxxx` +
186					`\192\010\192\009\192\008\192\007\192\006\192\005\192\004\192\003.`) +
187				(`\028\025\022\019\016\013\010\000xxxxxxxxx` +
188					`\192\010\192\009\192\008\192\007\192\006\192\005\192\004.`) +
189				(`\025\022\019\016\013\010\000xxxxxxxxx` +
190					`\192\010\192\009\192\008\192\007\192\006\192\005.`) +
191				`\022\019\016\013\010\000xxxxxxxxx\192\010\192\009\192\008\192\007\192\006.` +
192				`\019\016\013\010\000xxxxxxxxx\192\010\192\009\192\008\192\007.` +
193				`\016\013\010\000xxxxxxxxx\192\010\192\009\192\008.` +
194				`\013\010\000xxxxxxxxx\192\010\192\009.` +
195				`\010\000xxxxxxxxx\192\010.` +
196				`\000xxxxxxxxx.`,
197			""},
198		{"truncated name", "\x07example\x03", "", "dns: buffer size too small"},
199		{"non-absolute name", "\x07example\x03com", "", "dns: buffer size too small"},
200		{"compression pointer cycle (too many)", "\xC0\x00", "", "dns: too many compression pointers"},
201		{"compression pointer cycle (too long)",
202			"\x03foo" + "\x03bar" + "\x07example" + "\xC0\x04",
203			"",
204			ErrLongDomain.Error()},
205		{"forward compression pointer", "\x02\xC0\xFF\xC0\x01", "", ErrBuf.Error()},
206		{"reserved compression pointer 0b10", "\x07example\x80", "", "dns: bad rdata"},
207		{"reserved compression pointer 0b01", "\x07example\x40", "", "dns: bad rdata"},
208	}
209	for _, test := range cases {
210		output, idx, err := UnpackDomainName([]byte(test.input), 0)
211		if test.expectedOutput != "" && output != test.expectedOutput {
212			t.Errorf("%s: expected %s, got %s", test.label, test.expectedOutput, output)
213		}
214		if test.expectedError == "" && err != nil {
215			t.Errorf("%s: expected no error, got %d %v", test.label, idx, err)
216		} else if test.expectedError != "" && (err == nil || err.Error() != test.expectedError) {
217			t.Errorf("%s: expected error %s, got %d %v", test.label, test.expectedError, idx, err)
218		}
219	}
220}
221
222func TestPackDomainNameCompressionMap(t *testing.T) {
223	expected := map[string]struct{}{
224		`www\.this.is.\131an.example.org.`: {},
225		`is.\131an.example.org.`:           {},
226		`\131an.example.org.`:              {},
227		`example.org.`:                     {},
228		`org.`:                             {},
229	}
230
231	msg := make([]byte, 256)
232	for _, compress := range []bool{true, false} {
233		compression := make(map[string]int)
234
235		_, err := PackDomainName(`www\.this.is.\131an.example.org.`, msg, 0, compression, compress)
236		if err != nil {
237			t.Fatalf("PackDomainName failed: %v", err)
238		}
239
240		if !compressionMapsEqual(expected, compression) {
241			t.Errorf("expected compression maps to be equal\n%s", compressionMapsDifference(expected, compression))
242		}
243	}
244}
245
246func TestPackDomainNameNSECTypeBitmap(t *testing.T) {
247	ownername := "some-very-long-ownername.com."
248	msg := &Msg{
249		Compress: true,
250		Answer: []RR{
251			&NS{
252				Hdr: RR_Header{
253					Name:   ownername,
254					Rrtype: TypeNS,
255					Class:  ClassINET,
256				},
257				Ns: "ns1.server.com.",
258			},
259			&NSEC{
260				Hdr: RR_Header{
261					Name:   ownername,
262					Rrtype: TypeNSEC,
263					Class:  ClassINET,
264				},
265				NextDomain: "a.com.",
266				TypeBitMap: []uint16{TypeNS, TypeNSEC},
267			},
268		},
269	}
270
271	// Pack msg and then unpack into msg2
272	buf, err := msg.Pack()
273	if err != nil {
274		t.Fatalf("msg.Pack failed: %v", err)
275	}
276
277	var msg2 Msg
278	if err := msg2.Unpack(buf); err != nil {
279		t.Fatalf("msg2.Unpack failed: %v", err)
280	}
281
282	if !IsDuplicate(msg.Answer[1], msg2.Answer[1]) {
283		t.Error("message differs after packing and unpacking")
284
285		// Print NSEC RR for both cases
286		t.Logf("expected: %v", msg.Answer[1])
287		t.Logf("got:      %v", msg2.Answer[1])
288	}
289}
290
291func TestPackUnpackManyCompressionPointers(t *testing.T) {
292	m := new(Msg)
293	m.Compress = true
294	m.SetQuestion("example.org.", TypeNS)
295
296	for domain := "a."; len(domain) < maxDomainNameWireOctets; domain += "a." {
297		m.Answer = append(m.Answer, &NS{Hdr: RR_Header{Name: domain, Rrtype: TypeNS, Class: ClassINET}, Ns: "example.org."})
298
299		b, err := m.Pack()
300		if err != nil {
301			t.Fatalf("Pack failed for %q and %d records with: %v", domain, len(m.Answer), err)
302		}
303
304		var m2 Msg
305		if err := m2.Unpack(b); err != nil {
306			t.Fatalf("Unpack failed for %q and %d records with: %v", domain, len(m.Answer), err)
307		}
308	}
309}
310
311func TestLenDynamicA(t *testing.T) {
312	for _, rr := range []RR{
313		testRR("example.org. A"),
314		testRR("example.org. AAAA"),
315		testRR("example.org. L32"),
316	} {
317		msg := make([]byte, Len(rr))
318		off, err := PackRR(rr, msg, 0, nil, false)
319		if err != nil {
320			t.Fatalf("PackRR failed for %T: %v", rr, err)
321		}
322		if off != len(msg) {
323			t.Errorf("Len(rr) wrong for %T: Len(rr) = %d, PackRR(rr) = %d", rr, len(msg), off)
324		}
325	}
326}
327