1package dns
2
3import (
4	"fmt"
5	"regexp"
6	"strconv"
7	"strings"
8	"testing"
9)
10
11const (
12	maxPrintableLabel = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789x"
13	tooLongLabel      = maxPrintableLabel + "x"
14)
15
16var (
17	longDomain = maxPrintableLabel[:53] + strings.TrimSuffix(
18		strings.Join([]string{".", ".", ".", ".", "."}, maxPrintableLabel[:49]), ".")
19	reChar              = regexp.MustCompile(`.`)
20	i                   = -1
21	maxUnprintableLabel = reChar.ReplaceAllStringFunc(maxPrintableLabel, func(ch string) string {
22		if i++; i >= 32 {
23			i = 0
24		}
25		return fmt.Sprintf("\\%03d", i)
26	})
27)
28
29func TestPackNoSideEffect(t *testing.T) {
30	m := new(Msg)
31	m.SetQuestion(Fqdn("example.com."), TypeNS)
32
33	a := new(Msg)
34	o := &OPT{
35		Hdr: RR_Header{
36			Name:   ".",
37			Rrtype: TypeOPT,
38		},
39	}
40	o.SetUDPSize(DefaultMsgSize)
41
42	a.Extra = append(a.Extra, o)
43	a.SetRcode(m, RcodeBadVers)
44
45	a.Pack()
46	if a.Rcode != RcodeBadVers {
47		t.Errorf("after pack: Rcode is expected to be BADVERS")
48	}
49}
50
51func TestUnpackDomainName(t *testing.T) {
52	var cases = []struct {
53		label          string
54		input          string
55		expectedOutput string
56		expectedError  string
57	}{
58		{"empty domain",
59			"\x00",
60			".",
61			""},
62		{"long label",
63			string(63) + maxPrintableLabel + "\x00",
64			maxPrintableLabel + ".",
65			""},
66		{"unprintable label",
67			string(63) + regexp.MustCompile(`\\[0-9]+`).ReplaceAllStringFunc(maxUnprintableLabel,
68				func(escape string) string {
69					n, _ := strconv.ParseInt(escape[1:], 10, 8)
70					return string(n)
71				}) + "\x00",
72			maxUnprintableLabel + ".",
73			""},
74		{"long domain",
75			string(53) + strings.Replace(longDomain, ".", string(49), -1) + "\x00",
76			longDomain + ".",
77			""},
78		{"compression pointer",
79			// an unrealistic but functional test referencing an offset _inside_ a label
80			"\x03foo" + "\x05\x03com\x00" + "\x07example" + "\xC0\x05",
81			"foo.\\003com\\000.example.com.",
82			""},
83
84		{"too long domain",
85			string(54) + "x" + strings.Replace(longDomain, ".", string(49), -1) + "\x00",
86			"x" + longDomain + ".",
87			ErrLongDomain.Error()},
88		{"too long by pointer",
89			// a matryoshka doll name to get over 255 octets after expansion via internal pointers
90			string([]byte{
91				// 11 length values, first to last
92				40, 37, 34, 31, 28, 25, 22, 19, 16, 13, 0,
93				// 12 filler values
94				120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120,
95				// 10 pointers, last to first
96				192, 10, 192, 9, 192, 8, 192, 7, 192, 6, 192, 5, 192, 4, 192, 3, 192, 2, 192, 1,
97			}),
98			"",
99			ErrLongDomain.Error()},
100		{"long by pointer",
101			// a matryoshka doll name _not_ exceeding 255 octets after expansion
102			string([]byte{
103				// 11 length values, first to last
104				37, 34, 31, 28, 25, 22, 19, 16, 13, 10, 0,
105				// 9 filler values
106				120, 120, 120, 120, 120, 120, 120, 120, 120,
107				// 10 pointers, last to first
108				192, 10, 192, 9, 192, 8, 192, 7, 192, 6, 192, 5, 192, 4, 192, 3, 192, 2, 192, 1,
109			}),
110			"" +
111				(`\"\031\028\025\022\019\016\013\010\000xxxxxxxxx` +
112					`\192\010\192\009\192\008\192\007\192\006\192\005\192\004\192\003\192\002.`) +
113				(`\031\028\025\022\019\016\013\010\000xxxxxxxxx` +
114					`\192\010\192\009\192\008\192\007\192\006\192\005\192\004\192\003.`) +
115				(`\028\025\022\019\016\013\010\000xxxxxxxxx` +
116					`\192\010\192\009\192\008\192\007\192\006\192\005\192\004.`) +
117				(`\025\022\019\016\013\010\000xxxxxxxxx` +
118					`\192\010\192\009\192\008\192\007\192\006\192\005.`) +
119				`\022\019\016\013\010\000xxxxxxxxx\192\010\192\009\192\008\192\007\192\006.` +
120				`\019\016\013\010\000xxxxxxxxx\192\010\192\009\192\008\192\007.` +
121				`\016\013\010\000xxxxxxxxx\192\010\192\009\192\008.` +
122				`\013\010\000xxxxxxxxx\192\010\192\009.` +
123				`\010\000xxxxxxxxx\192\010.` +
124				`\000xxxxxxxxx.`,
125			""},
126		{"truncated name", "\x07example\x03", "", "dns: buffer size too small"},
127		{"non-absolute name", "\x07example\x03com", "", "dns: buffer size too small"},
128		{"compression pointer cycle",
129			"\x03foo" + "\x03bar" + "\x07example" + "\xC0\x04",
130			"",
131			"dns: too many compression pointers"},
132		{"reserved compression pointer 0b10", "\x07example\x80", "", "dns: bad rdata"},
133		{"reserved compression pointer 0b01", "\x07example\x40", "", "dns: bad rdata"},
134	}
135	for _, test := range cases {
136		output, idx, err := UnpackDomainName([]byte(test.input), 0)
137		if test.expectedOutput != "" && output != test.expectedOutput {
138			t.Errorf("%s: expected %s, got %s", test.label, test.expectedOutput, output)
139		}
140		if test.expectedError == "" && err != nil {
141			t.Errorf("%s: expected no error, got %d %v", test.label, idx, err)
142		} else if test.expectedError != "" && (err == nil || err.Error() != test.expectedError) {
143			t.Errorf("%s: expected error %s, got %d %v", test.label, test.expectedError, idx, err)
144		}
145	}
146}
147