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	reChar              = regexp.MustCompile(`.`)
17	i                   = -1
18	maxUnprintableLabel = reChar.ReplaceAllStringFunc(maxPrintableLabel, func(ch string) string {
19		if i++; i >= 32 {
20			i = 0
21		}
22		return fmt.Sprintf("\\%03d", i)
23	})
24)
25
26func TestPackNoSideEffect(t *testing.T) {
27	m := new(Msg)
28	m.SetQuestion(Fqdn("example.com."), TypeNS)
29
30	a := new(Msg)
31	o := &OPT{
32		Hdr: RR_Header{
33			Name:   ".",
34			Rrtype: TypeOPT,
35		},
36	}
37	o.SetUDPSize(DefaultMsgSize)
38
39	a.Extra = append(a.Extra, o)
40	a.SetRcode(m, RcodeBadVers)
41
42	a.Pack()
43	if a.Rcode != RcodeBadVers {
44		t.Errorf("after pack: Rcode is expected to be BADVERS")
45	}
46}
47
48func TestUnpackDomainName(t *testing.T) {
49	var cases = []struct {
50		label          string
51		input          string
52		expectedOutput string
53		expectedError  string
54	}{
55		{"empty domain",
56			"\x00",
57			".",
58			""},
59		{"long label",
60			string(63) + maxPrintableLabel + "\x00",
61			maxPrintableLabel + ".",
62			""},
63		{"unprintable label",
64			string(63) + regexp.MustCompile(`\\[0-9]+`).ReplaceAllStringFunc(maxUnprintableLabel,
65				func(escape string) string {
66					n, _ := strconv.ParseInt(escape[1:], 10, 8)
67					return string(n)
68				}) + "\x00",
69			maxUnprintableLabel + ".",
70			""},
71		{"long domain",
72			string(53) + strings.Replace(longDomain, ".", string(49), -1) + "\x00",
73			longDomain + ".",
74			""},
75		{"compression pointer",
76			// an unrealistic but functional test referencing an offset _inside_ a label
77			"\x03foo" + "\x05\x03com\x00" + "\x07example" + "\xC0\x05",
78			"foo.\\003com\\000.example.com.",
79			""},
80
81		{"too long domain",
82			string(54) + "x" + strings.Replace(longDomain, ".", string(49), -1) + "\x00",
83			"x" + longDomain + ".",
84			ErrLongDomain.Error()},
85		{"too long by pointer",
86			// a matryoshka doll name to get over 255 octets after expansion via internal pointers
87			string([]byte{
88				// 11 length values, first to last
89				40, 37, 34, 31, 28, 25, 22, 19, 16, 13, 0,
90				// 12 filler values
91				120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120,
92				// 10 pointers, last to first
93				192, 10, 192, 9, 192, 8, 192, 7, 192, 6, 192, 5, 192, 4, 192, 3, 192, 2, 192, 1,
94			}),
95			"",
96			ErrLongDomain.Error()},
97		{"long by pointer",
98			// a matryoshka doll name _not_ exceeding 255 octets after expansion
99			string([]byte{
100				// 11 length values, first to last
101				37, 34, 31, 28, 25, 22, 19, 16, 13, 10, 0,
102				// 9 filler values
103				120, 120, 120, 120, 120, 120, 120, 120, 120,
104				// 10 pointers, last to first
105				192, 10, 192, 9, 192, 8, 192, 7, 192, 6, 192, 5, 192, 4, 192, 3, 192, 2, 192, 1,
106			}),
107			"" +
108				(`\"\031\028\025\022\019\016\013\010\000xxxxxxxxx` +
109					`\192\010\192\009\192\008\192\007\192\006\192\005\192\004\192\003\192\002.`) +
110				(`\031\028\025\022\019\016\013\010\000xxxxxxxxx` +
111					`\192\010\192\009\192\008\192\007\192\006\192\005\192\004\192\003.`) +
112				(`\028\025\022\019\016\013\010\000xxxxxxxxx` +
113					`\192\010\192\009\192\008\192\007\192\006\192\005\192\004.`) +
114				(`\025\022\019\016\013\010\000xxxxxxxxx` +
115					`\192\010\192\009\192\008\192\007\192\006\192\005.`) +
116				`\022\019\016\013\010\000xxxxxxxxx\192\010\192\009\192\008\192\007\192\006.` +
117				`\019\016\013\010\000xxxxxxxxx\192\010\192\009\192\008\192\007.` +
118				`\016\013\010\000xxxxxxxxx\192\010\192\009\192\008.` +
119				`\013\010\000xxxxxxxxx\192\010\192\009.` +
120				`\010\000xxxxxxxxx\192\010.` +
121				`\000xxxxxxxxx.`,
122			""},
123		{"truncated name", "\x07example\x03", "", "dns: buffer size too small"},
124		{"non-absolute name", "\x07example\x03com", "", "dns: buffer size too small"},
125		{"compression pointer cycle",
126			"\x03foo" + "\x03bar" + "\x07example" + "\xC0\x04",
127			"",
128			"dns: too many compression pointers"},
129		{"reserved compression pointer 0b10", "\x07example\x80", "", "dns: bad rdata"},
130		{"reserved compression pointer 0b01", "\x07example\x40", "", "dns: bad rdata"},
131	}
132	for _, test := range cases {
133		output, idx, err := UnpackDomainName([]byte(test.input), 0)
134		if test.expectedOutput != "" && output != test.expectedOutput {
135			t.Errorf("%s: expected %s, got %s", test.label, test.expectedOutput, output)
136		}
137		if test.expectedError == "" && err != nil {
138			t.Errorf("%s: expected no error, got %d %v", test.label, idx, err)
139		} else if test.expectedError != "" && (err == nil || err.Error() != test.expectedError) {
140			t.Errorf("%s: expected error %s, got %d %v", test.label, test.expectedError, idx, err)
141		}
142	}
143}
144
145func TestPackDomainNameNSECTypeBitmap(t *testing.T) {
146	ownername := "some-very-long-ownername.com."
147	msg := &Msg{
148		Compress: true,
149		Answer: []RR{
150			&NS{
151				Hdr: RR_Header{
152					Name:   ownername,
153					Rrtype: TypeNS,
154					Class:  ClassINET,
155				},
156				Ns: "ns1.server.com.",
157			},
158			&NSEC{
159				Hdr: RR_Header{
160					Name:   ownername,
161					Rrtype: TypeNSEC,
162					Class:  ClassINET,
163				},
164				NextDomain: "a.com.",
165				TypeBitMap: []uint16{TypeNS, TypeNSEC},
166			},
167		},
168	}
169
170	// Pack msg and then unpack into msg2
171	buf, err := msg.Pack()
172	if err != nil {
173		t.Fatalf("msg.Pack failed: %v", err)
174	}
175
176	var msg2 Msg
177	if err := msg2.Unpack(buf); err != nil {
178		t.Fatalf("msg2.Unpack failed: %v", err)
179	}
180
181	if !IsDuplicate(msg.Answer[1], msg2.Answer[1]) {
182		t.Error("message differs after packing and unpacking")
183
184		// Print NSEC RR for both cases
185		t.Logf("expected: %v", msg.Answer[1])
186		t.Logf("got:      %v", msg2.Answer[1])
187	}
188}
189