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