1package dns
2
3import (
4	"encoding/hex"
5	"fmt"
6	"net"
7	"reflect"
8	"strings"
9	"testing"
10)
11
12func TestCompressLength(t *testing.T) {
13	m := new(Msg)
14	m.SetQuestion("miek.nl", TypeMX)
15	ul := m.Len()
16	m.Compress = true
17	if ul != m.Len() {
18		t.Fatalf("should be equal")
19	}
20}
21
22// Does the predicted length match final packed length?
23func TestMsgCompressLength(t *testing.T) {
24	makeMsg := func(question string, ans, ns, e []RR) *Msg {
25		msg := new(Msg)
26		msg.SetQuestion(Fqdn(question), TypeANY)
27		msg.Answer = append(msg.Answer, ans...)
28		msg.Ns = append(msg.Ns, ns...)
29		msg.Extra = append(msg.Extra, e...)
30		msg.Compress = true
31		return msg
32	}
33
34	name1 := "12345678901234567890123456789012345.12345678.123."
35	rrA := testRR(name1 + " 3600 IN A 192.0.2.1")
36	rrMx := testRR(name1 + " 3600 IN MX 10 " + name1)
37	tests := []*Msg{
38		makeMsg(name1, []RR{rrA}, nil, nil),
39		makeMsg(name1, []RR{rrMx, rrMx}, nil, nil)}
40
41	for _, msg := range tests {
42		predicted := msg.Len()
43		buf, err := msg.Pack()
44		if err != nil {
45			t.Error(err)
46		}
47		if predicted < len(buf) {
48			t.Errorf("predicted compressed length is wrong: predicted %s (len=%d) %d, actual %d",
49				msg.Question[0].Name, len(msg.Answer), predicted, len(buf))
50		}
51	}
52}
53
54func TestMsgLength(t *testing.T) {
55	makeMsg := func(question string, ans, ns, e []RR) *Msg {
56		msg := new(Msg)
57		msg.Compress = true
58		msg.SetQuestion(Fqdn(question), TypeANY)
59		msg.Answer = append(msg.Answer, ans...)
60		msg.Ns = append(msg.Ns, ns...)
61		msg.Extra = append(msg.Extra, e...)
62		return msg
63	}
64
65	name1 := "12345678901234567890123456789012345.12345678.123."
66	rrA := testRR(name1 + " 3600 IN A 192.0.2.1")
67	rrMx := testRR(name1 + " 3600 IN MX 10 " + name1)
68	tests := []*Msg{
69		makeMsg(name1, []RR{rrA}, nil, nil),
70		makeMsg(name1, []RR{rrMx, rrMx}, nil, nil)}
71
72	for _, msg := range tests {
73		predicted := msg.Len()
74		buf, err := msg.Pack()
75		if err != nil {
76			t.Error(err)
77		}
78		if predicted < len(buf) {
79			t.Errorf("predicted length is wrong: predicted %s (len=%d), actual %d",
80				msg.Question[0].Name, predicted, len(buf))
81		}
82	}
83}
84
85func TestCompressionLenHelper(t *testing.T) {
86	c := make(map[string]int)
87	compressionLenHelper(c, "example.com", 12)
88	if c["example.com"] != 12 {
89		t.Errorf("bad %d", c["example.com"])
90	}
91	if c["com"] != 20 {
92		t.Errorf("bad %d", c["com"])
93	}
94
95	// Test boundaries
96	c = make(map[string]int)
97	// foo label starts at 16379
98	// com label starts at 16384
99	compressionLenHelper(c, "foo.com", 16379)
100	if c["foo.com"] != 16379 {
101		t.Errorf("bad %d", c["foo.com"])
102	}
103	// com label is accessible
104	if c["com"] != 16383 {
105		t.Errorf("bad %d", c["com"])
106	}
107
108	c = make(map[string]int)
109	// foo label starts at 16379
110	// com label starts at 16385 => outside range
111	compressionLenHelper(c, "foo.com", 16380)
112	if c["foo.com"] != 16380 {
113		t.Errorf("bad %d", c["foo.com"])
114	}
115	// com label is NOT accessible
116	if c["com"] != 0 {
117		t.Errorf("bad %d", c["com"])
118	}
119
120	c = make(map[string]int)
121	compressionLenHelper(c, "example.com", 16375)
122	if c["example.com"] != 16375 {
123		t.Errorf("bad %d", c["example.com"])
124	}
125	// com starts AFTER 16384
126	if c["com"] != 16383 {
127		t.Errorf("bad %d", c["com"])
128	}
129
130	c = make(map[string]int)
131	compressionLenHelper(c, "example.com", 16376)
132	if c["example.com"] != 16376 {
133		t.Errorf("bad %d", c["example.com"])
134	}
135	// com starts AFTER 16384
136	if c["com"] != 0 {
137		t.Errorf("bad %d", c["com"])
138	}
139}
140
141func TestCompressionLenSearch(t *testing.T) {
142	c := make(map[string]int)
143	compressed, ok, fullSize := compressionLenSearch(c, "a.b.org.")
144	if compressed != 0 || ok || fullSize != 14 {
145		panic(fmt.Errorf("Failed: compressed:=%d, ok:=%v, fullSize:=%d", compressed, ok, fullSize))
146	}
147	c["org."] = 3
148	compressed, ok, fullSize = compressionLenSearch(c, "a.b.org.")
149	if compressed != 4 || !ok || fullSize != 8 {
150		panic(fmt.Errorf("Failed: compressed:=%d, ok:=%v, fullSize:=%d", compressed, ok, fullSize))
151	}
152	c["b.org."] = 5
153	compressed, ok, fullSize = compressionLenSearch(c, "a.b.org.")
154	if compressed != 6 || !ok || fullSize != 4 {
155		panic(fmt.Errorf("Failed: compressed:=%d, ok:=%v, fullSize:=%d", compressed, ok, fullSize))
156	}
157	// Not found long compression
158	c["x.b.org."] = 5
159	compressed, ok, fullSize = compressionLenSearch(c, "a.b.org.")
160	if compressed != 6 || !ok || fullSize != 4 {
161		panic(fmt.Errorf("Failed: compressed:=%d, ok:=%v, fullSize:=%d", compressed, ok, fullSize))
162	}
163	// Found long compression
164	c["a.b.org."] = 5
165	compressed, ok, fullSize = compressionLenSearch(c, "a.b.org.")
166	if compressed != 8 || !ok || fullSize != 0 {
167		panic(fmt.Errorf("Failed: compressed:=%d, ok:=%v, fullSize:=%d", compressed, ok, fullSize))
168	}
169}
170
171func TestMsgLength2(t *testing.T) {
172	// Serialized replies
173	var testMessages = []string{
174		// google.com. IN A?
175		"064e81800001000b0004000506676f6f676c6503636f6d0000010001c00c00010001000000050004adc22986c00c00010001000000050004adc22987c00c00010001000000050004adc22988c00c00010001000000050004adc22989c00c00010001000000050004adc2298ec00c00010001000000050004adc22980c00c00010001000000050004adc22981c00c00010001000000050004adc22982c00c00010001000000050004adc22983c00c00010001000000050004adc22984c00c00010001000000050004adc22985c00c00020001000000050006036e7331c00cc00c00020001000000050006036e7332c00cc00c00020001000000050006036e7333c00cc00c00020001000000050006036e7334c00cc0d800010001000000050004d8ef200ac0ea00010001000000050004d8ef220ac0fc00010001000000050004d8ef240ac10e00010001000000050004d8ef260a0000290500000000050000",
176		// amazon.com. IN A? (reply has no EDNS0 record)
177		// TODO(miek): this one is off-by-one, need to find out why
178		//"6de1818000010004000a000806616d617a6f6e03636f6d0000010001c00c000100010000000500044815c2d4c00c000100010000000500044815d7e8c00c00010001000000050004b02062a6c00c00010001000000050004cdfbf236c00c000200010000000500140570646e733408756c747261646e73036f726700c00c000200010000000500150570646e733508756c747261646e7304696e666f00c00c000200010000000500160570646e733608756c747261646e7302636f02756b00c00c00020001000000050014036e7331037033310664796e656374036e657400c00c00020001000000050006036e7332c0cfc00c00020001000000050006036e7333c0cfc00c00020001000000050006036e7334c0cfc00c000200010000000500110570646e733108756c747261646e73c0dac00c000200010000000500080570646e7332c127c00c000200010000000500080570646e7333c06ec0cb00010001000000050004d04e461fc0eb00010001000000050004cc0dfa1fc0fd00010001000000050004d04e471fc10f00010001000000050004cc0dfb1fc12100010001000000050004cc4a6c01c121001c000100000005001020010502f3ff00000000000000000001c13e00010001000000050004cc4a6d01c13e001c0001000000050010261000a1101400000000000000000001",
179		// yahoo.com. IN A?
180		"fc2d81800001000300070008057961686f6f03636f6d0000010001c00c00010001000000050004628afd6dc00c00010001000000050004628bb718c00c00010001000000050004cebe242dc00c00020001000000050006036e7336c00cc00c00020001000000050006036e7338c00cc00c00020001000000050006036e7331c00cc00c00020001000000050006036e7332c00cc00c00020001000000050006036e7333c00cc00c00020001000000050006036e7334c00cc00c00020001000000050006036e7335c00cc07b0001000100000005000444b48310c08d00010001000000050004448eff10c09f00010001000000050004cb54dd35c0b100010001000000050004628a0b9dc0c30001000100000005000477a0f77cc05700010001000000050004ca2bdfaac06900010001000000050004caa568160000290500000000050000",
181		// microsoft.com. IN A?
182		"f4368180000100020005000b096d6963726f736f667403636f6d0000010001c00c0001000100000005000440040b25c00c0001000100000005000441373ac9c00c0002000100000005000e036e7331046d736674036e657400c00c00020001000000050006036e7332c04fc00c00020001000000050006036e7333c04fc00c00020001000000050006036e7334c04fc00c00020001000000050006036e7335c04fc04b000100010000000500044137253ec04b001c00010000000500102a010111200500000000000000010001c0650001000100000005000440043badc065001c00010000000500102a010111200600060000000000010001c07700010001000000050004d5c7b435c077001c00010000000500102a010111202000000000000000010001c08900010001000000050004cf2e4bfec089001c00010000000500102404f800200300000000000000010001c09b000100010000000500044137e28cc09b001c00010000000500102a010111200f000100000000000100010000290500000000050000",
183		// google.com. IN MX?
184		"724b8180000100050004000b06676f6f676c6503636f6d00000f0001c00c000f000100000005000c000a056173706d78016cc00cc00c000f0001000000050009001404616c7431c02ac00c000f0001000000050009001e04616c7432c02ac00c000f0001000000050009002804616c7433c02ac00c000f0001000000050009003204616c7434c02ac00c00020001000000050006036e7332c00cc00c00020001000000050006036e7333c00cc00c00020001000000050006036e7334c00cc00c00020001000000050006036e7331c00cc02a00010001000000050004adc2421bc02a001c00010000000500102a00145040080c01000000000000001bc04200010001000000050004adc2461bc05700010001000000050004adc2451bc06c000100010000000500044a7d8f1bc081000100010000000500044a7d191bc0ca00010001000000050004d8ef200ac09400010001000000050004d8ef220ac0a600010001000000050004d8ef240ac0b800010001000000050004d8ef260a0000290500000000050000",
185		// reddit.com. IN A?
186		"12b98180000100080000000c0672656464697403636f6d0000020001c00c0002000100000005000f046175733204616b616d036e657400c00c000200010000000500070475736534c02dc00c000200010000000500070475737733c02dc00c000200010000000500070475737735c02dc00c00020001000000050008056173696131c02dc00c00020001000000050008056173696139c02dc00c00020001000000050008056e73312d31c02dc00c0002000100000005000a076e73312d313935c02dc02800010001000000050004c30a242ec04300010001000000050004451f1d39c05600010001000000050004451f3bc7c0690001000100000005000460073240c07c000100010000000500046007fb81c090000100010000000500047c283484c090001c00010000000500102a0226f0006700000000000000000064c0a400010001000000050004c16c5b01c0a4001c000100000005001026001401000200000000000000000001c0b800010001000000050004c16c5bc3c0b8001c0001000000050010260014010002000000000000000000c30000290500000000050000",
187	}
188
189	for i, hexData := range testMessages {
190		// we won't fail the decoding of the hex
191		input, _ := hex.DecodeString(hexData)
192
193		m := new(Msg)
194		m.Unpack(input)
195		m.Compress = true
196		lenComp := m.Len()
197		b, _ := m.Pack()
198		pacComp := len(b)
199		m.Compress = false
200		lenUnComp := m.Len()
201		b, _ = m.Pack()
202		pacUnComp := len(b)
203		if pacComp+1 != lenComp {
204			t.Errorf("msg.Len(compressed)=%d actual=%d for test %d", lenComp, pacComp, i)
205		}
206		if pacUnComp+1 != lenUnComp {
207			t.Errorf("msg.Len(uncompressed)=%d actual=%d for test %d", lenUnComp, pacUnComp, i)
208		}
209	}
210}
211
212func TestMsgLengthCompressionMalformed(t *testing.T) {
213	// SOA with empty hostmaster, which is illegal
214	soa := &SOA{Hdr: RR_Header{Name: ".", Rrtype: TypeSOA, Class: ClassINET, Ttl: 12345},
215		Ns:      ".",
216		Mbox:    "",
217		Serial:  0,
218		Refresh: 28800,
219		Retry:   7200,
220		Expire:  604800,
221		Minttl:  60}
222	m := new(Msg)
223	m.Compress = true
224	m.Ns = []RR{soa}
225	m.Len() // Should not crash.
226}
227
228func TestMsgCompressLength2(t *testing.T) {
229	msg := new(Msg)
230	msg.Compress = true
231	msg.SetQuestion(Fqdn("bliep."), TypeANY)
232	msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "blaat.", Rrtype: 0x21, Class: 0x1, Ttl: 0x3c}, Port: 0x4c57, Target: "foo.bar."})
233	msg.Extra = append(msg.Extra, &A{Hdr: RR_Header{Name: "foo.bar.", Rrtype: 0x1, Class: 0x1, Ttl: 0x3c}, A: net.IP{0xac, 0x11, 0x0, 0x3}})
234	predicted := msg.Len()
235	buf, err := msg.Pack()
236	if err != nil {
237		t.Error(err)
238	}
239	if predicted != len(buf) {
240		t.Errorf("predicted compressed length is wrong: predicted %s (len=%d) %d, actual %d",
241			msg.Question[0].Name, len(msg.Answer), predicted, len(buf))
242	}
243}
244
245func TestMsgCompressLengthLargeRecords(t *testing.T) {
246	msg := new(Msg)
247	msg.Compress = true
248	msg.SetQuestion("my.service.acme.", TypeSRV)
249	j := 1
250	for i := 0; i < 250; i++ {
251		target := fmt.Sprintf("host-redis-1-%d.test.acme.com.node.dc1.consul.", i)
252		msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "redis.service.consul.", Class: 1, Rrtype: TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target})
253		msg.Extra = append(msg.Extra, &CNAME{Hdr: RR_Header{Name: target, Class: 1, Rrtype: TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.%d.%d.", j, i)})
254	}
255	predicted := msg.Len()
256	buf, err := msg.Pack()
257	if err != nil {
258		t.Error(err)
259	}
260	if predicted != len(buf) {
261		t.Fatalf("predicted compressed length is wrong: predicted %s (len=%d) %d, actual %d", msg.Question[0].Name, len(msg.Answer), predicted, len(buf))
262	}
263}
264
265func TestCompareCompressionMapsForANY(t *testing.T) {
266	msg := new(Msg)
267	msg.Compress = true
268	msg.SetQuestion("a.service.acme.", TypeANY)
269	// Be sure to have more than 14bits
270	for i := 0; i < 2000; i++ {
271		target := fmt.Sprintf("host.app-%d.x%d.test.acme.", i%250, i)
272		msg.Answer = append(msg.Answer, &AAAA{Hdr: RR_Header{Name: target, Rrtype: TypeAAAA, Class: ClassINET, Ttl: 0x3c}, AAAA: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, byte(i / 255), byte(i % 255)}})
273		msg.Answer = append(msg.Answer, &A{Hdr: RR_Header{Name: target, Rrtype: TypeA, Class: ClassINET, Ttl: 0x3c}, A: net.IP{127, 0, byte(i / 255), byte(i % 255)}})
274		if msg.Len() > 16384 {
275			break
276		}
277	}
278	for labelSize := 0; labelSize < 63; labelSize++ {
279		msg.SetQuestion(fmt.Sprintf("a%s.service.acme.", strings.Repeat("x", labelSize)), TypeANY)
280
281		compressionFake := make(map[string]int)
282		lenFake := compressedLenWithCompressionMap(msg, compressionFake)
283
284		compressionReal := make(map[string]int)
285		buf, err := msg.packBufferWithCompressionMap(nil, compressionReal)
286		if err != nil {
287			t.Fatal(err)
288		}
289		if lenFake != len(buf) {
290			t.Fatalf("padding= %d ; Predicted len := %d != real:= %d", labelSize, lenFake, len(buf))
291		}
292		if !reflect.DeepEqual(compressionFake, compressionReal) {
293			t.Fatalf("padding= %d ; Fake Compression Map != Real Compression Map\n*** Real:= %v\n\n***Fake:= %v", labelSize, compressionReal, compressionFake)
294		}
295	}
296}
297
298func TestCompareCompressionMapsForSRV(t *testing.T) {
299	msg := new(Msg)
300	msg.Compress = true
301	msg.SetQuestion("a.service.acme.", TypeSRV)
302	// Be sure to have more than 14bits
303	for i := 0; i < 2000; i++ {
304		target := fmt.Sprintf("host.app-%d.x%d.test.acme.", i%250, i)
305		msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "redis.service.consul.", Class: ClassINET, Rrtype: TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target})
306		msg.Extra = append(msg.Extra, &A{Hdr: RR_Header{Name: target, Rrtype: TypeA, Class: ClassINET, Ttl: 0x3c}, A: net.IP{127, 0, byte(i / 255), byte(i % 255)}})
307		if msg.Len() > 16384 {
308			break
309		}
310	}
311	for labelSize := 0; labelSize < 63; labelSize++ {
312		msg.SetQuestion(fmt.Sprintf("a%s.service.acme.", strings.Repeat("x", labelSize)), TypeAAAA)
313
314		compressionFake := make(map[string]int)
315		lenFake := compressedLenWithCompressionMap(msg, compressionFake)
316
317		compressionReal := make(map[string]int)
318		buf, err := msg.packBufferWithCompressionMap(nil, compressionReal)
319		if err != nil {
320			t.Fatal(err)
321		}
322		if lenFake != len(buf) {
323			t.Fatalf("padding= %d ; Predicted len := %d != real:= %d", labelSize, lenFake, len(buf))
324		}
325		if !reflect.DeepEqual(compressionFake, compressionReal) {
326			t.Fatalf("padding= %d ; Fake Compression Map != Real Compression Map\n*** Real:= %v\n\n***Fake:= %v", labelSize, compressionReal, compressionFake)
327		}
328	}
329}
330
331func TestMsgCompressLengthLargeRecordsWithPaddingPermutation(t *testing.T) {
332	msg := new(Msg)
333	msg.Compress = true
334	msg.SetQuestion("my.service.acme.", TypeSRV)
335
336	for i := 0; i < 250; i++ {
337		target := fmt.Sprintf("host-redis-x-%d.test.acme.com.node.dc1.consul.", i)
338		msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "redis.service.consul.", Class: 1, Rrtype: TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target})
339		msg.Extra = append(msg.Extra, &CNAME{Hdr: RR_Header{Name: target, Class: ClassINET, Rrtype: TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.x.%d.", i)})
340	}
341	for labelSize := 1; labelSize < 63; labelSize++ {
342		msg.SetQuestion(fmt.Sprintf("my.%s.service.acme.", strings.Repeat("x", labelSize)), TypeSRV)
343		predicted := msg.Len()
344		buf, err := msg.Pack()
345		if err != nil {
346			t.Error(err)
347		}
348		if predicted != len(buf) {
349			t.Fatalf("padding= %d ; predicted compressed length is wrong: predicted %s (len=%d) %d, actual %d", labelSize, msg.Question[0].Name, len(msg.Answer), predicted, len(buf))
350		}
351	}
352}
353
354func TestMsgCompressLengthLargeRecordsAllValues(t *testing.T) {
355	msg := new(Msg)
356	msg.Compress = true
357	msg.SetQuestion("redis.service.consul.", TypeSRV)
358	for i := 0; i < 900; i++ {
359		target := fmt.Sprintf("host-redis-%d-%d.test.acme.com.node.dc1.consul.", i/256, i%256)
360		msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "redis.service.consul.", Class: 1, Rrtype: TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target})
361		msg.Extra = append(msg.Extra, &CNAME{Hdr: RR_Header{Name: target, Class: ClassINET, Rrtype: TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.%d.%d.", i/256, i%256)})
362		predicted := msg.Len()
363		buf, err := msg.Pack()
364		if err != nil {
365			t.Error(err)
366		}
367		if predicted != len(buf) {
368			t.Fatalf("predicted compressed length is wrong for %d records: predicted %s (len=%d) %d, actual %d", i, msg.Question[0].Name, len(msg.Answer), predicted, len(buf))
369		}
370	}
371}
372