blob: 32076228bfe2edc7f12805bdffe1ce2bf7176d5c [file] [log] [blame]
Jan Schär4a180222024-07-29 16:32:54 +02001// Package dns provides a DNS server for resolving services against.
2package dns
3
4import (
5 "context"
6 "fmt"
7 "net/netip"
8 "runtime/debug"
9 "strconv"
10 "sync/atomic"
11 "time"
12
13 "github.com/miekg/dns"
14
15 "source.monogon.dev/osbase/supervisor"
16)
17
18// Service is a DNS server service with configurable handlers.
19//
20// The number and names of handlers is fixed when New is called. For each name
21// in handlerNames there is a corresponding pointer to a handler in the handlers
22// slice at the same index, which can be atomically updated at runtime through
23// its atomic.Pointer via the SetHandler function.
24type Service struct {
25 handlerNames []string
26 handlers []atomic.Pointer[Handler]
27}
28
29type serviceCtx struct {
30 service *Service
31 ctx context.Context
32}
33
34// New creates a Service instance. DNS handlers with the names given in
35// handlerNames must be set with SetHandler. When serving DNS queries, they will
36// be tried in the order they appear here. Doing it this way instead of directly
37// passing a []Handler avoids circular dependencies.
38func New(handlerNames []string) *Service {
39 return &Service{
40 handlerNames: handlerNames,
41 handlers: make([]atomic.Pointer[Handler], len(handlerNames)),
42 }
43}
44
45// Run runs the DNS service.
46func (s *Service) Run(ctx context.Context) error {
47 addr4 := "127.0.0.1:53"
48 addr6 := "[::1]:53"
49 supervisor.Run(ctx, "udp4", func(ctx context.Context) error {
50 return s.runListener(ctx, addr4, "udp")
51 })
52 supervisor.Run(ctx, "tcp4", func(ctx context.Context) error {
53 return s.runListener(ctx, addr4, "tcp")
54 })
55 supervisor.Run(ctx, "udp6", func(ctx context.Context) error {
56 return s.runListener(ctx, addr6, "udp")
57 })
58 supervisor.Run(ctx, "tcp6", func(ctx context.Context) error {
59 return s.runListener(ctx, addr6, "tcp")
60 })
61 supervisor.Signal(ctx, supervisor.SignalHealthy)
62 supervisor.Signal(ctx, supervisor.SignalDone)
63 return nil
64}
65
66// RunListenerAddr runs a DNS listener on a specific address.
67func (s *Service) RunListenerAddr(ctx context.Context, addr string) error {
68 supervisor.Run(ctx, "udp", func(ctx context.Context) error {
69 return s.runListener(ctx, addr, "udp")
70 })
71 supervisor.Run(ctx, "tcp", func(ctx context.Context) error {
72 return s.runListener(ctx, addr, "tcp")
73 })
74 supervisor.Signal(ctx, supervisor.SignalHealthy)
75 supervisor.Signal(ctx, supervisor.SignalDone)
76 return nil
77}
78
79func (s *Service) runListener(ctx context.Context, addr string, network string) error {
80 handler := &serviceCtx{service: s, ctx: ctx}
81 server := &dns.Server{Addr: addr, Net: network, ReusePort: true, Handler: handler}
82 server.NotifyStartedFunc = func() {
83 supervisor.Signal(ctx, supervisor.SignalHealthy)
84 supervisor.Logger(ctx).Infof("DNS server listening on %s %s", addr, network)
85 go func() {
86 <-ctx.Done()
87 server.Shutdown()
88 }()
89 }
90 return server.ListenAndServe()
91}
92
93// Requests
94
95// Request represents an incoming DNS query that is being handled.
96type Request struct {
97 // Reply is the reply that will be sent, and should be filled in by the
98 // handler. It is guaranteed to contain exactly one question.
99 Reply *dns.Msg
100 // Writer will be used to send the reply, and contains network information.
101 Writer dns.ResponseWriter
102
103 // Qopt is the OPT record from the query, or nil if not present.
104 Qopt *dns.OPT
105 // Ropt, if non-nil, is the OPT record that will be added to the reply. The
106 // handler can modify this as needed. Ropt is nil when Qopt is nil.
107 Ropt *dns.OPT
108
109 // Qname contains the current question name. This is different from the
110 // original question in Reply.Question[0].Name if a CNAME has been followed
111 // already.
112 Qname string
113
114 // QnameCanonical contains the canonicalized name of the question. This means
115 // that ASCII letters are lowercased.
116 QnameCanonical string
117
118 // Qtype contains the question type for convenient access.
119 Qtype uint16
120
121 // Handled is set to true when the current question name has been handled and
122 // no other handlers should be attempted.
123 Handled bool
124
125 // done is set to true when a reply has been sent. When a CNAME is
126 // encountered, Handled is set to true, but done is false.
127 done bool
128}
129
130// SetAuthoritative marks the reply as authoritative.
131func (r *Request) SetAuthoritative() {
132 // Only set the AA bit if the question has not yet been redirected by CNAME.
133 // See RFC 1034 6.2.7
134 if r.Qname == r.Reply.Question[0].Name {
135 r.Reply.Authoritative = true
136 }
137}
138
139// SendReply sends the reply. It may only be called once.
140func (r *Request) SendReply() {
141 if r.Handled {
142 panic("SendReply called twice for the same DNS request")
143 }
144 r.Handled = true
145 r.done = true
146
147 if r.Ropt != nil {
148 r.Reply.Extra = append(r.Reply.Extra, r.Ropt)
149 } else {
150 // Cannot use extended RCODEs without an OPT, so replace with SERVFAIL.
151 if r.Reply.Rcode > 0xF {
152 r.Reply.Rcode = dns.RcodeServerFailure
153 }
154 }
155
156 size := uint16(0)
157 if r.Writer.RemoteAddr().Network() == "tcp" {
158 size = dns.MaxMsgSize
159 } else if r.Qopt != nil {
160 size = r.Qopt.UDPSize()
161 }
162 if size < dns.MinMsgSize {
163 size = dns.MinMsgSize
164 }
165 r.Reply.Truncate(int(size))
166 if !r.Reply.Compress && r.Reply.Len() >= 1024 {
167 r.Reply.Compress = true
168 }
169
170 r.Writer.WriteMsg(r.Reply)
171}
172
173// SendRcode sets the reply RCODE and sends the reply.
174func (r *Request) SendRcode(rcode int) {
175 r.Reply.Rcode = rcode
176 r.SendReply()
177}
178
179// AddExtendedError adds an Extended DNS Error Option if the reply has an OPT.
180// See RFC 8914.
181func (r *Request) AddExtendedError(infoCode uint16, extraText string) {
182 if r.Ropt != nil {
183 r.Ropt.Option = append(r.Ropt.Option, &dns.EDNS0_EDE{InfoCode: infoCode, ExtraText: extraText})
184 }
185}
186
187// AddCNAME adds a CNAME record to the answer section, and either sends the
188// reply if the query is for the CNAME itself, or else marks the lookup to be
189// restarted at the new name. target must be fully qualified.
190func (r *Request) AddCNAME(target string, ttl uint32) {
191 rr := new(dns.CNAME)
192 rr.Hdr = dns.RR_Header{Name: r.Qname, Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: ttl}
193 rr.Target = target
194 r.Reply.Answer = append(r.Reply.Answer, rr)
195
196 if r.Qtype == dns.TypeCNAME || r.Qtype == dns.TypeANY {
197 r.SendReply()
198 } else {
199 r.Handled = true
200 r.Qname = target
201 r.QnameCanonical = dns.CanonicalName(r.Qname)
202 }
203}
204
205// Handlers
206
207// Handler can handle DNS requests. The handler should first inspect the query
208// and decide if it wants to handle it. If not, it should return immediately.
209// The next handler will then be tried. Otherwise, it should fill in the Reply,
210// and then call SendReply. The Answer section may already contain CNAMEs that
211// have been followed.
212type Handler interface {
213 HandleDNS(r *Request)
214}
215
216// SetHandler sets the handler of the given name. This name must have been
217// registered when creating the Service. As long as SetHandler has not been
218// called for a registered name, any queries that are not already handled by an
219// earlier handler in the sequence return SERVFAIL. SetHandler may be called
220// multiple times, each call replaces the previous handler of the same name.
221func (s *Service) SetHandler(name string, h Handler) {
222 for i, iname := range s.handlerNames {
223 if iname == name {
224 s.handlers[i].Store(&h)
225 return
226 }
227 }
228 panic(fmt.Sprintf("Attempted to set undeclared DNS handler: %q", name))
229}
230
231// EmptyDNSHandler is a handler that does not handle any queries. It can be used
232// as a placeholder with SetHandler when a handler is inactive.
233type EmptyDNSHandler struct{}
234
235func (EmptyDNSHandler) HandleDNS(*Request) {}
236
237// Serving requests
238
239// advertiseUDPSize is the maximum message size that we advertise in the OPT RR
240// of UDP messages. This is calculated as the minimum IPv6 MTU (1280) minus size
241// of IPv6 (40) and UDP (8) headers.
242const advertiseUDPSize = 1232
243
244// ServeDNS implements dns.Handler.
245func (s *serviceCtx) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
246 defer func() {
247 if rec := recover(); rec != nil {
248 supervisor.Logger(s.ctx).Errorf("panic in DNS handler: %v, stacktrace: %s", rec, string(debug.Stack()))
249 }
250 }()
251
252 // Only QUERY opcode is implemented.
253 if r.Opcode != dns.OpcodeQuery {
254 m := new(dns.Msg)
255 m.SetRcode(r, dns.RcodeNotImplemented)
256 w.WriteMsg(m)
257 return
258 }
259
260 if r.Truncated {
261 m := new(dns.Msg)
262 m.RecursionAvailable = true
263 m.SetRcode(r, dns.RcodeFormatError)
264 w.WriteMsg(m)
265 return
266 }
267
268 // Look for an OPT RR.
269 var opt *dns.OPT
270 for _, rr := range r.Extra {
271 if rr, ok := rr.(*dns.OPT); ok {
272 if opt != nil {
273 // RFC 6891 6.1.1
274 // If a query has more than one OPT RR, FORMERR MUST be returned.
275 m := new(dns.Msg)
276 m.RecursionAvailable = true
277 m.SetRcode(r, dns.RcodeFormatError)
278 w.WriteMsg(m)
279 return
280 }
281 opt = rr
282 }
283 }
284
285 // RFC 6891 6.1.3
286 // If the VERSION of the query is not implemented, BADVERS MUST be returned.
287 if opt != nil && opt.Version() != 0 {
288 m := new(dns.Msg)
289 m.RecursionAvailable = true
290 m.SetRcode(r, dns.RcodeBadVers)
291 m.SetEdns0(advertiseUDPSize, false)
292 w.WriteMsg(m)
293 return
294 }
295
296 // If the OPT name is not the root, the OPT is invalid.
297 if opt != nil && opt.Hdr.Name != "." {
298 m := new(dns.Msg)
299 m.RecursionAvailable = true
300 m.SetRcode(r, dns.RcodeFormatError)
301 w.WriteMsg(m)
302 return
303 }
304
305 // Reuse the query message as the reply message.
306 r.Response = true
307 r.Authoritative = false
308 r.RecursionAvailable = true
309 r.Zero = false
310 r.AuthenticatedData = false
311 r.Rcode = dns.RcodeSuccess
312 r.Extra = nil
313
314 req := &Request{
315 Reply: r,
316 Writer: w,
317 Qopt: opt,
318 }
319 if opt != nil {
320 req.Ropt = new(dns.OPT)
321 req.Ropt.Hdr.Name = "."
322 req.Ropt.Hdr.Rrtype = dns.TypeOPT
323 req.Ropt.SetUDPSize(advertiseUDPSize)
324 if opt.Do() {
325 req.Ropt.SetDo()
326 }
327 }
328
329 // Refuse queries that don't have exactly one question of class INET, or that
330 // have non-empty answer or authority sections.
331 if len(r.Question) != 1 || r.Question[0].Qclass != dns.ClassINET || len(r.Answer) != 0 || len(r.Ns) != 0 {
332 r.Answer = nil
333 r.Ns = nil
334 req.SendRcode(dns.RcodeRefused)
335 return
336 }
337 req.Qtype = r.Question[0].Qtype
338 req.Qname = r.Question[0].Name
339 req.QnameCanonical = dns.CanonicalName(req.Qname)
340
341 switch req.Qtype {
342 case dns.TypeOPT:
343 // OPT is a pseudo-RR and may only appear in the additional section.
344 req.SendRcode(dns.RcodeFormatError)
345 return
346 case dns.TypeAXFR, dns.TypeIXFR:
347 // Zone transfer is not supported.
348 req.AddExtendedError(dns.ExtendedErrorCodeNotSupported, "")
349 req.SendRcode(dns.RcodeRefused)
350 return
351 }
352
353 // If we encounter a CNAME, DNS resolution must be restarted with the new
354 // name. That's what this loop is for.
355 i := 0
356 seen := make(map[string]bool)
357 for {
358 prevName := req.QnameCanonical
359 s.service.HandleDNS(req)
360 if req.done {
361 break
362 }
363 req.Handled = false
364 i++
365 seen[prevName] = true
366 if seen[req.QnameCanonical] || i > 7 {
367 if seen[req.QnameCanonical] {
368 req.AddExtendedError(dns.ExtendedErrorCodeOther, "CNAME loop")
369 } else {
370 req.AddExtendedError(dns.ExtendedErrorCodeOther, "too many CNAME redirects")
371 }
372 req.SendRcode(dns.RcodeServerFailure)
373 break
374 }
375 }
376}
377
378func (s *Service) HandleDNS(r *Request) {
379 start := time.Now()
380
381 // Handle localhost.
382 if IsSubDomain("localhost.", r.QnameCanonical) {
383 handleLocalhost(r)
384 return
385 }
386 if IsSubDomain("127.in-addr.arpa.", r.QnameCanonical) ||
387 IsSubDomain("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.", r.QnameCanonical) {
388 handleLocalhostPtr(r)
389 return
390 }
391
392 // Serve NXDOMAIN for the "invalid." domain, see RFC 6761.
393 if IsSubDomain("invalid.", r.QnameCanonical) {
394 r.SetAuthoritative()
395 r.SendRcode(dns.RcodeNameError)
396 return
397 }
398
399 for i := range s.handlers {
400 handler := s.handlers[i].Load()
401 if handler == nil {
402 // The application is still starting up. Fail queries instead of leaking
403 // local queries to the Internet and sending the wrong reply.
404 r.AddExtendedError(dns.ExtendedErrorCodeNotReady, fmt.Sprintf("%s handler not ready", s.handlerNames[i]))
405 r.SendRcode(dns.RcodeServerFailure)
406 handlerDuration.WithLabelValues(s.handlerNames[i], "not_ready").Observe(time.Since(start).Seconds())
407 return
408 }
409 (*handler).HandleDNS(r)
410 if r.Handled {
411 rcode, ok := dns.RcodeToString[r.Reply.Rcode]
412 if !ok {
413 // There are 4096 possible Rcodes, so it's probably still fine to put it
414 // in a metric label.
415 rcode = strconv.Itoa(r.Reply.Rcode)
416 }
417 if !r.done {
418 rcode = "redirected"
419 }
420 handlerDuration.WithLabelValues(s.handlerNames[i], rcode).Observe(time.Since(start).Seconds())
421 return
422 }
423 }
424
425 // No handler can handle this request.
426 r.SendRcode(dns.RcodeRefused)
427}
428
429var (
430 localhostA = netip.MustParseAddr("127.0.0.1").AsSlice()
431 localhostAAAA = netip.MustParseAddr("::1").AsSlice()
432)
433
434const localhostTtl = 60 * 5
435
436func handleLocalhost(r *Request) {
437 r.SetAuthoritative()
438 if r.Qtype == dns.TypeA || r.Qtype == dns.TypeANY {
439 rr := new(dns.A)
440 rr.Hdr = dns.RR_Header{Name: r.Qname, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: localhostTtl}
441 rr.A = localhostA
442 r.Reply.Answer = append(r.Reply.Answer, rr)
443 }
444 if r.Qtype == dns.TypeAAAA || r.Qtype == dns.TypeANY {
445 rr := new(dns.AAAA)
446 rr.Hdr = dns.RR_Header{Name: r.Qname, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: localhostTtl}
447 rr.AAAA = localhostAAAA
448 r.Reply.Answer = append(r.Reply.Answer, rr)
449 }
450 r.SendReply()
451}
452
453func handleLocalhostPtr(r *Request) {
454 r.SetAuthoritative()
455 ip, bits, extra := ParseReverse(r.QnameCanonical)
456 if extra {
457 // Name with extra labels does not exist (e.g. foo.1.0.0.127.in-addr.arpa.)
458 r.Reply.Rcode = dns.RcodeNameError
459 } else if bits != ip.BitLen() {
460 // Partial reverse name (e.g. 127.in-addr.arpa.) exists but has no records.
461 } else if r.Qtype == dns.TypePTR || r.Qtype == dns.TypeANY {
462 rr := new(dns.PTR)
463 rr.Hdr = dns.RR_Header{Name: r.Qname, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: localhostTtl}
464 rr.Ptr = "localhost."
465 r.Reply.Answer = append(r.Reply.Answer, rr)
466 }
467 r.SendReply()
468}