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