blob: 7dd670c968417347cba98ac6b108fd7761ccd8f9 [file] [log] [blame]
Jan Schär4a180222024-07-29 16:32:54 +02001package dns
2
3import (
4 "net"
5 "testing"
6
7 "github.com/miekg/dns"
8
9 "source.monogon.dev/osbase/net/dns/test"
10)
11
12func testQuery(t *testing.T, service *Service, query, wantReply *dns.Msg) {
13 t.Helper()
14 wantReply.RecursionAvailable = true
15 testMsg(t, service, query, wantReply)
16}
17
18func testMsg(t *testing.T, service *Service, query, wantReply *dns.Msg) {
19 sCtx := &serviceCtx{service: service}
20 t.Helper()
21 wantReply.Response = true
22 writer := &testWriter{addr: &net.UDPAddr{}}
23 sCtx.ServeDNS(writer, query)
24 if got, want := writer.msg.String(), wantReply.String(); got != want {
25 t.Errorf("Want reply:\n%s\nGot:\n%s", want, got)
26 }
27}
28
29func TestBuiltinHandlers(t *testing.T) {
30 service := New(nil)
31
32 cases := []struct {
33 name string
34 qtype uint16
35 rcode int
36 answer []dns.RR
37 }{
38 {
39 name: "localhost.",
40 qtype: dns.TypeA,
41 answer: []dns.RR{test.RR("localhost. 300 IN A 127.0.0.1")},
42 },
43 {
44 name: "foo.bar.localhost.",
45 qtype: dns.TypeA,
46 answer: []dns.RR{test.RR("foo.bar.localhost. 300 IN A 127.0.0.1")},
47 },
48 {
49 name: "localhost.",
50 qtype: dns.TypeAAAA,
51 answer: []dns.RR{test.RR("localhost. 300 IN AAAA ::1")},
52 },
53 {
54 name: "localhost.",
55 qtype: dns.TypeANY,
56 answer: []dns.RR{
57 test.RR("localhost. 300 IN A 127.0.0.1"),
58 test.RR("localhost. 300 IN AAAA ::1"),
59 },
60 },
61 {
62 name: "localhost.",
63 qtype: dns.TypeMX,
64 },
65 {
66 name: "1.0.0.127.in-addr.arpa.",
67 qtype: dns.TypePTR,
68 answer: []dns.RR{test.RR("1.0.0.127.in-addr.arpa. 300 IN PTR localhost.")},
69 },
70 {
71 name: "1.0.0.127.in-addr.arpa.",
72 qtype: dns.TypeNS,
73 },
74 {
75 name: "2.127.in-addr.arpa.",
76 qtype: dns.TypePTR,
77 },
78 {
79 name: "foo.127.in-addr.arpa.",
80 qtype: dns.TypePTR,
81 rcode: dns.RcodeNameError,
82 },
83 {
84 name: "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.",
85 qtype: dns.TypePTR,
86 answer: []dns.RR{test.RR("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa. 300 IN PTR localhost.")},
87 },
88 {
89 name: "foo.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.",
90 qtype: dns.TypePTR,
91 rcode: dns.RcodeNameError,
92 },
93 {
94 name: "invalid.",
95 qtype: dns.TypeA,
96 rcode: dns.RcodeNameError,
97 },
98 {
99 name: "foo.bar.invalid.",
100 qtype: dns.TypeA,
101 rcode: dns.RcodeNameError,
102 },
103 }
104
105 for _, c := range cases {
106 query := new(dns.Msg)
107 query.SetQuestion(c.name, c.qtype)
108 query.RecursionDesired = false
109 wantReply := query.Copy()
110 wantReply.Authoritative = true
111 wantReply.Rcode = c.rcode
112 wantReply.Answer = c.answer
113 testQuery(t, service, query, wantReply)
114 }
115}
116
117type handlerFunc func(*Request)
118
119func (f handlerFunc) HandleDNS(r *Request) {
120 f(r)
121}
122
123func TestCustomHandlers(t *testing.T) {
124 service := New([]string{"handler1", "handler2"})
125 service.SetHandler("handler2", handlerFunc(func(r *Request) {
126 if IsSubDomain("example.com.", r.QnameCanonical) {
127 r.SetAuthoritative()
128 if r.Qtype == dns.TypeA || r.Qtype == dns.TypeANY {
129 rr := new(dns.A)
130 rr.Hdr = dns.RR_Header{Name: r.Qname, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 300}
131 rr.A = net.IP{1, 2, 3, 4}
132 r.Reply.Answer = append(r.Reply.Answer, rr)
133 }
134 r.SendReply()
135 }
136 }))
137
138 // Because handler1 is not yet set, this query should fail.
139 query := new(dns.Msg)
140 query.SetQuestion("example.com.", dns.TypeA)
141 wantReply := query.Copy()
142 wantReply.Rcode = dns.RcodeServerFailure
143 testQuery(t, service, query, wantReply)
144
145 service.SetHandler("handler1", EmptyDNSHandler{})
146
147 // Now, we should get the result from handler2.
148 query = new(dns.Msg)
149 query.SetQuestion("example.com.", dns.TypeA)
150 wantReply = query.Copy()
151 wantReply.Authoritative = true
152 wantReply.Answer = []dns.RR{test.RR("example.com. 300 IN A 1.2.3.4")}
153 testQuery(t, service, query, wantReply)
154
155 service.SetHandler("handler1", handlerFunc(func(r *Request) {
156 if IsSubDomain("example.com.", r.QnameCanonical) {
157 r.SetAuthoritative()
158 if r.Qtype == dns.TypeA || r.Qtype == dns.TypeANY {
159 rr := new(dns.A)
160 rr.Hdr = dns.RR_Header{Name: r.Qname, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 300}
161 rr.A = net.IP{5, 6, 7, 8}
162 r.Reply.Answer = append(r.Reply.Answer, rr)
163 }
164 r.SendReply()
165 }
166 }))
167
168 // Handlers can be updated, and are tried in the order in which they were
169 // declared when creating the Service.
170 query = new(dns.Msg)
171 query.SetQuestion("example.com.", dns.TypeA)
172 wantReply = query.Copy()
173 wantReply.Authoritative = true
174 wantReply.Answer = []dns.RR{test.RR("example.com. 300 IN A 5.6.7.8")}
175 testQuery(t, service, query, wantReply)
176
177 // Names which are not handled by any handler get refused.
178 query = new(dns.Msg)
179 query.SetQuestion("example.net.", dns.TypeA)
180 wantReply = query.Copy()
181 wantReply.Rcode = dns.RcodeRefused
182 testQuery(t, service, query, wantReply)
183}
184
185func TestRedirect(t *testing.T) {
186 service := New([]string{"handler1", "handler2"})
187 service.SetHandler("handler1", handlerFunc(func(r *Request) {
188 if IsSubDomain("example.net.", r.QnameCanonical) {
189 r.SetAuthoritative()
190 if r.Qtype == dns.TypeA || r.Qtype == dns.TypeANY {
191 rr := new(dns.A)
192 rr.Hdr = dns.RR_Header{Name: r.Qname, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 300}
193 rr.A = net.IP{1, 2, 3, 4}
194 r.Reply.Answer = append(r.Reply.Answer, rr)
195 }
196 r.SendReply()
197 }
198 }))
199 service.SetHandler("handler2", handlerFunc(func(r *Request) {
200 if IsSubDomain("example.com.", r.QnameCanonical) {
201 switch r.QnameCanonical {
202 case "1.example.com.":
203 r.AddCNAME("2.example.com.", 30)
204 case "2.example.com.":
205 r.AddCNAME("example.net.", 30)
206
207 case "loop.example.com.":
208 r.AddCNAME("loop.example.com.", 30)
209
210 case "loop1.example.com.":
211 r.AddCNAME("loop2.example.com.", 30)
212 case "loop2.example.com.":
213 r.AddCNAME("loop3.example.com.", 30)
214 case "loop3.example.com.":
215 r.AddCNAME("loop1.example.com.", 30)
216
217 case "chain1.example.com.":
218 r.AddCNAME("chain2.example.com.", 30)
219 case "chain2.example.com.":
220 r.AddCNAME("chain3.example.com.", 30)
221 case "chain3.example.com.":
222 r.AddCNAME("chain4.example.com.", 30)
223 case "chain4.example.com.":
224 r.AddCNAME("chain5.example.com.", 30)
225 case "chain5.example.com.":
226 r.AddCNAME("chain6.example.com.", 30)
227 case "chain6.example.com.":
228 r.AddCNAME("chain7.example.com.", 30)
229 case "chain7.example.com.":
230 r.AddCNAME("chain8.example.com.", 30)
231 case "chain8.example.com.":
232 r.AddCNAME("chain9.example.com.", 30)
233 case "chain9.example.com.":
234 r.AddCNAME("chain10.example.com.", 30)
235
236 default:
237 r.SendRcode(dns.RcodeNameError)
238 }
239 }
240 }))
241
242 // CNAME redirects are followed.
243 query := new(dns.Msg)
244 query.SetQuestion("1.example.com.", dns.TypeA)
245 wantReply := query.Copy()
246 wantReply.Answer = []dns.RR{
247 test.RR("1.example.com. 30 IN CNAME 2.example.com."),
248 test.RR("2.example.com. 30 IN CNAME example.net."),
249 test.RR("example.net. 300 IN A 1.2.3.4"),
250 }
251 testQuery(t, service, query, wantReply)
252
253 // Queries of type CNAME or ANY do not follow the redirect.
254 query = new(dns.Msg)
255 query.SetQuestion("1.example.com.", dns.TypeCNAME)
256 wantReply = query.Copy()
257 wantReply.Answer = []dns.RR{test.RR("1.example.com. 30 IN CNAME 2.example.com.")}
258 testQuery(t, service, query, wantReply)
259
260 query = new(dns.Msg)
261 query.SetQuestion("1.example.com.", dns.TypeANY)
262 wantReply = query.Copy()
263 wantReply.Answer = []dns.RR{test.RR("1.example.com. 30 IN CNAME 2.example.com.")}
264 testQuery(t, service, query, wantReply)
265
266 // Loops are detected.
267 query = new(dns.Msg)
268 query.SetQuestion("loop.example.com.", dns.TypeA)
269 wantReply = query.Copy()
270 wantReply.Answer = []dns.RR{test.RR("loop.example.com. 30 IN CNAME loop.example.com.")}
271 wantReply.Rcode = dns.RcodeServerFailure
272 testQuery(t, service, query, wantReply)
273
274 // Loops are detected.
275 query = new(dns.Msg)
276 query.SetQuestion("loop1.example.com.", dns.TypeA)
277 wantReply = query.Copy()
278 wantReply.Answer = []dns.RR{
279 test.RR("loop1.example.com. 30 IN CNAME loop2.example.com."),
280 test.RR("loop2.example.com. 30 IN CNAME loop3.example.com."),
281 test.RR("loop3.example.com. 30 IN CNAME loop1.example.com."),
282 }
283 wantReply.Rcode = dns.RcodeServerFailure
284 testQuery(t, service, query, wantReply)
285
286 // Number of redirects is limited.
287 query = new(dns.Msg)
288 query.SetQuestion("chain1.example.com.", dns.TypeA)
289 wantReply = query.Copy()
290 wantReply.Answer = []dns.RR{
291 test.RR("chain1.example.com. 30 IN CNAME chain2.example.com."),
292 test.RR("chain2.example.com. 30 IN CNAME chain3.example.com."),
293 test.RR("chain3.example.com. 30 IN CNAME chain4.example.com."),
294 test.RR("chain4.example.com. 30 IN CNAME chain5.example.com."),
295 test.RR("chain5.example.com. 30 IN CNAME chain6.example.com."),
296 test.RR("chain6.example.com. 30 IN CNAME chain7.example.com."),
297 test.RR("chain7.example.com. 30 IN CNAME chain8.example.com."),
298 test.RR("chain8.example.com. 30 IN CNAME chain9.example.com."),
299 }
300 wantReply.Rcode = dns.RcodeServerFailure
301 testQuery(t, service, query, wantReply)
302}
303
304func TestFlags(t *testing.T) {
305 service := New(nil)
306
307 query := new(dns.Msg)
308 query.SetQuestion("localhost.", dns.TypeA)
309
310 // Set flags which should be copied to the reply.
311 query.RecursionDesired = true
312 query.CheckingDisabled = true
313
314 wantReply := query.Copy()
315 wantReply.Authoritative = true
316 wantReply.Answer = []dns.RR{test.RR("localhost. 300 IN A 127.0.0.1")}
317
318 // Set flags which should be ignored.
319 query.Authoritative = true
320 query.RecursionAvailable = true
321 query.Zero = true
322 query.AuthenticatedData = true
323 query.Rcode = dns.RcodeRefused
324
325 testQuery(t, service, query, wantReply)
326}
327
328func TestOPT(t *testing.T) {
329 service := New(nil)
330
331 query := new(dns.Msg)
332 query.SetQuestion("localhost.", dns.TypeA)
333 wantReply := query.Copy()
334 wantReply.Authoritative = true
335 wantReply.Answer = []dns.RR{test.RR("localhost. 300 IN A 127.0.0.1")}
336 wantReply.SetEdns0(advertiseUDPSize, false)
337 query.SetEdns0(512, false)
338 testQuery(t, service, query, wantReply)
339
340 // DNSSEC ok flag.
341 query = new(dns.Msg)
342 query.SetQuestion("localhost.", dns.TypeA)
343 wantReply = query.Copy()
344 wantReply.Authoritative = true
345 wantReply.Answer = []dns.RR{test.RR("localhost. 300 IN A 127.0.0.1")}
346 wantReply.SetEdns0(advertiseUDPSize, true)
347 query.SetEdns0(512, true)
348 testQuery(t, service, query, wantReply)
349}
350
351func TestInvalidQuery(t *testing.T) {
352 service := New(nil)
353
354 // Valid query.
355 query := new(dns.Msg)
356 query.SetQuestion("localhost.", dns.TypeA)
357 wantReply := query.Copy()
358 wantReply.Authoritative = true
359 wantReply.Answer = []dns.RR{test.RR("localhost. 300 IN A 127.0.0.1")}
360 testQuery(t, service, query, wantReply)
361
362 // Not query opcode.
363 query = new(dns.Msg)
364 query.SetQuestion("localhost.", dns.TypeA)
365 query.Opcode = dns.OpcodeNotify
366 wantReply = query.Copy()
367 wantReply.RecursionDesired = false
368 wantReply.Rcode = dns.RcodeNotImplemented
369 testMsg(t, service, query, wantReply)
370
371 // Truncated.
372 query = new(dns.Msg)
373 query.SetQuestion("localhost.", dns.TypeA)
374 wantReply = query.Copy()
375 wantReply.Rcode = dns.RcodeFormatError
376 query.Truncated = true
377 testQuery(t, service, query, wantReply)
378
379 // Multiple OPTs.
380 query = new(dns.Msg)
381 query.SetQuestion("localhost.", dns.TypeA)
382 wantReply = query.Copy()
383 wantReply.Rcode = dns.RcodeFormatError
384 query.SetEdns0(512, false)
385 query.SetEdns0(512, false)
386 testQuery(t, service, query, wantReply)
387
388 // Unknown OPT version.
389 query = new(dns.Msg)
390 query.SetQuestion("localhost.", dns.TypeA)
391 wantReply = query.Copy()
392 wantReply.Rcode = dns.RcodeBadVers
393 wantReply.SetEdns0(advertiseUDPSize, false)
394 query.SetEdns0(512, false)
395 query.Extra[0].(*dns.OPT).SetVersion(1)
396 testQuery(t, service, query, wantReply)
397
398 // Invalid OPT name.
399 query = new(dns.Msg)
400 query.SetQuestion("localhost.", dns.TypeA)
401 wantReply = query.Copy()
402 wantReply.Rcode = dns.RcodeFormatError
403 query.SetEdns0(512, false)
404 query.Extra[0].(*dns.OPT).Hdr.Name = "localhost."
405 testQuery(t, service, query, wantReply)
406
407 // No question.
408 query = new(dns.Msg)
409 query.Id = dns.Id()
410 wantReply = query.Copy()
411 wantReply.Rcode = dns.RcodeRefused
412 testQuery(t, service, query, wantReply)
413
414 // Multiple questions.
415 query = new(dns.Msg)
416 query.Id = dns.Id()
417 query.Question = []dns.Question{
418 {Name: "localhost.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
419 {Name: "localhost.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET},
420 }
421 wantReply = query.Copy()
422 wantReply.Rcode = dns.RcodeRefused
423 testQuery(t, service, query, wantReply)
424
425 // OPT qtype.
426 query = new(dns.Msg)
427 query.SetQuestion("localhost.", dns.TypeOPT)
428 wantReply = query.Copy()
429 wantReply.Rcode = dns.RcodeFormatError
430 testQuery(t, service, query, wantReply)
431
432 // Zone transfer.
433 query = new(dns.Msg)
434 query.SetQuestion("localhost.", dns.TypeAXFR)
435 wantReply = query.Copy()
436 wantReply.Rcode = dns.RcodeRefused
437 testQuery(t, service, query, wantReply)
438
439 // Zone transfer.
440 query = new(dns.Msg)
441 query.SetQuestion("localhost.", dns.TypeIXFR)
442 wantReply = query.Copy()
443 wantReply.Rcode = dns.RcodeRefused
444 testQuery(t, service, query, wantReply)
445}