github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/libnetwork/resolver.go (about) 1 package libnetwork 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "math/rand" 8 "net" 9 "strconv" 10 "strings" 11 "sync" 12 "time" 13 14 "github.com/containerd/log" 15 "github.com/docker/docker/libnetwork/types" 16 "github.com/miekg/dns" 17 "go.opentelemetry.io/otel" 18 "go.opentelemetry.io/otel/attribute" 19 "go.opentelemetry.io/otel/codes" 20 "go.opentelemetry.io/otel/trace" 21 "golang.org/x/sync/semaphore" 22 "golang.org/x/time/rate" 23 ) 24 25 // DNSBackend represents a backend DNS resolver used for DNS name 26 // resolution. All the queries to the resolver are forwarded to the 27 // backend resolver. 28 type DNSBackend interface { 29 // ResolveName resolves a service name to an IPv4 or IPv6 address by searching 30 // the networks the sandbox is connected to. For IPv6 queries, second return 31 // value will be true if the name exists in docker domain but doesn't have an 32 // IPv6 address. Such queries shouldn't be forwarded to external nameservers. 33 ResolveName(ctx context.Context, name string, iplen int) ([]net.IP, bool) 34 // ResolveIP returns the service name for the passed in IP. IP is in reverse dotted 35 // notation; the format used for DNS PTR records 36 ResolveIP(ctx context.Context, name string) string 37 // ResolveService returns all the backend details about the containers or hosts 38 // backing a service. Its purpose is to satisfy an SRV query 39 ResolveService(ctx context.Context, name string) ([]*net.SRV, []net.IP) 40 // ExecFunc allows a function to be executed in the context of the backend 41 // on behalf of the resolver. 42 ExecFunc(f func()) error 43 // NdotsSet queries the backends ndots dns option settings 44 NdotsSet() bool 45 // HandleQueryResp passes the name & IP from a response to the backend. backend 46 // can use it to maintain any required state about the resolution 47 HandleQueryResp(name string, ip net.IP) 48 } 49 50 const ( 51 dnsPort = "53" 52 ptrIPv4domain = ".in-addr.arpa." 53 ptrIPv6domain = ".ip6.arpa." 54 respTTL = 600 55 maxExtDNS = 3 // max number of external servers to try 56 extIOTimeout = 4 * time.Second 57 maxConcurrent = 1024 58 logInterval = 2 * time.Second 59 ) 60 61 type extDNSEntry struct { 62 IPStr string 63 port uint16 // for testing 64 HostLoopback bool 65 } 66 67 // Resolver is the embedded DNS server in Docker. It operates by listening on 68 // the container's loopback interface for DNS queries. 69 type Resolver struct { 70 backend DNSBackend 71 extDNSList [maxExtDNS]extDNSEntry 72 server *dns.Server 73 conn *net.UDPConn 74 tcpServer *dns.Server 75 tcpListen *net.TCPListener 76 err error 77 listenAddress string 78 proxyDNS bool 79 startCh chan struct{} 80 logger *log.Entry 81 82 fwdSem *semaphore.Weighted // Limit the number of concurrent external DNS requests in-flight 83 logInverval rate.Sometimes // Rate-limit logging about hitting the fwdSem limit 84 } 85 86 // NewResolver creates a new instance of the Resolver 87 func NewResolver(address string, proxyDNS bool, backend DNSBackend) *Resolver { 88 return &Resolver{ 89 backend: backend, 90 proxyDNS: proxyDNS, 91 listenAddress: address, 92 err: fmt.Errorf("setup not done yet"), 93 startCh: make(chan struct{}, 1), 94 fwdSem: semaphore.NewWeighted(maxConcurrent), 95 logInverval: rate.Sometimes{Interval: logInterval}, 96 } 97 } 98 99 func (r *Resolver) log(ctx context.Context) *log.Entry { 100 if r.logger == nil { 101 return log.G(ctx) 102 } 103 return r.logger 104 } 105 106 // SetupFunc returns the setup function that should be run in the container's 107 // network namespace. 108 func (r *Resolver) SetupFunc(port int) func() { 109 return func() { 110 var err error 111 112 // DNS operates primarily on UDP 113 r.conn, err = net.ListenUDP("udp", &net.UDPAddr{ 114 IP: net.ParseIP(r.listenAddress), 115 Port: port, 116 }) 117 if err != nil { 118 r.err = fmt.Errorf("error in opening name server socket %v", err) 119 return 120 } 121 122 // Listen on a TCP as well 123 r.tcpListen, err = net.ListenTCP("tcp", &net.TCPAddr{ 124 IP: net.ParseIP(r.listenAddress), 125 Port: port, 126 }) 127 if err != nil { 128 r.err = fmt.Errorf("error in opening name TCP server socket %v", err) 129 return 130 } 131 r.err = nil 132 } 133 } 134 135 // Start starts the name server for the container. 136 func (r *Resolver) Start() error { 137 r.startCh <- struct{}{} 138 defer func() { <-r.startCh }() 139 140 // make sure the resolver has been setup before starting 141 if r.err != nil { 142 return r.err 143 } 144 145 if err := r.setupIPTable(); err != nil { 146 return fmt.Errorf("setting up IP table rules failed: %v", err) 147 } 148 149 s := &dns.Server{Handler: dns.HandlerFunc(r.serveDNS), PacketConn: r.conn} 150 r.server = s 151 go func() { 152 if err := s.ActivateAndServe(); err != nil { 153 r.log(context.TODO()).WithError(err).Error("[resolver] failed to start PacketConn DNS server") 154 } 155 }() 156 157 tcpServer := &dns.Server{Handler: dns.HandlerFunc(r.serveDNS), Listener: r.tcpListen} 158 r.tcpServer = tcpServer 159 go func() { 160 if err := tcpServer.ActivateAndServe(); err != nil { 161 r.log(context.TODO()).WithError(err).Error("[resolver] failed to start TCP DNS server") 162 } 163 }() 164 return nil 165 } 166 167 // Stop stops the name server for the container. A stopped resolver can be 168 // reused after running the SetupFunc again. 169 func (r *Resolver) Stop() { 170 r.startCh <- struct{}{} 171 defer func() { <-r.startCh }() 172 173 if r.server != nil { 174 r.server.Shutdown() //nolint:errcheck 175 } 176 if r.tcpServer != nil { 177 r.tcpServer.Shutdown() //nolint:errcheck 178 } 179 r.conn = nil 180 r.tcpServer = nil 181 r.err = fmt.Errorf("setup not done yet") 182 r.fwdSem = semaphore.NewWeighted(maxConcurrent) 183 } 184 185 // SetExtServers configures the external nameservers the resolver should use 186 // when forwarding queries. 187 func (r *Resolver) SetExtServers(extDNS []extDNSEntry) { 188 l := len(extDNS) 189 if l > maxExtDNS { 190 l = maxExtDNS 191 } 192 for i := 0; i < l; i++ { 193 r.extDNSList[i] = extDNS[i] 194 } 195 } 196 197 // NameServer returns the IP of the DNS resolver for the containers. 198 func (r *Resolver) NameServer() string { 199 return r.listenAddress 200 } 201 202 // ResolverOptions returns resolv.conf options that should be set. 203 func (r *Resolver) ResolverOptions() []string { 204 return []string{"ndots:0"} 205 } 206 207 //nolint:gosec // The RNG is not used in a security-sensitive context. 208 var ( 209 shuffleRNG = rand.New(rand.NewSource(time.Now().Unix())) 210 shuffleRNGMu sync.Mutex 211 ) 212 213 func shuffleAddr(addr []net.IP) []net.IP { 214 shuffleRNGMu.Lock() 215 defer shuffleRNGMu.Unlock() 216 for i := len(addr) - 1; i > 0; i-- { 217 r := shuffleRNG.Intn(i + 1) //nolint:gosec // gosec complains about the use of rand here. It should be fine. 218 addr[i], addr[r] = addr[r], addr[i] 219 } 220 return addr 221 } 222 223 func createRespMsg(query *dns.Msg) *dns.Msg { 224 resp := &dns.Msg{} 225 resp.SetReply(query) 226 resp.RecursionAvailable = true 227 228 return resp 229 } 230 231 func (r *Resolver) handleMXQuery(ctx context.Context, query *dns.Msg) (*dns.Msg, error) { 232 name := query.Question[0].Name 233 addrv4, _ := r.backend.ResolveName(ctx, name, types.IPv4) 234 addrv6, _ := r.backend.ResolveName(ctx, name, types.IPv6) 235 236 if addrv4 == nil && addrv6 == nil { 237 return nil, nil 238 } 239 240 // We were able to resolve the name. Respond with an empty list with 241 // RcodeSuccess/NOERROR so that email clients can treat it as "implicit MX" 242 // [RFC 5321 Section-5.1] and issue a Type A/AAAA query for the name. 243 244 resp := createRespMsg(query) 245 return resp, nil 246 } 247 248 func (r *Resolver) handleIPQuery(ctx context.Context, query *dns.Msg, ipType int) (*dns.Msg, error) { 249 var ( 250 addr []net.IP 251 ipv6Miss bool 252 name = query.Question[0].Name 253 ) 254 addr, ipv6Miss = r.backend.ResolveName(ctx, name, ipType) 255 256 if addr == nil && ipv6Miss { 257 // Send a reply without any Answer sections 258 r.log(ctx).Debugf("[resolver] lookup name %s present without IPv6 address", name) 259 resp := createRespMsg(query) 260 return resp, nil 261 } 262 if addr == nil { 263 return nil, nil 264 } 265 266 r.log(ctx).Debugf("[resolver] lookup for %s: IP %v", name, addr) 267 268 resp := createRespMsg(query) 269 if len(addr) > 1 { 270 addr = shuffleAddr(addr) 271 } 272 if ipType == types.IPv4 { 273 for _, ip := range addr { 274 resp.Answer = append(resp.Answer, &dns.A{ 275 Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: respTTL}, 276 A: ip, 277 }) 278 } 279 } else { 280 for _, ip := range addr { 281 resp.Answer = append(resp.Answer, &dns.AAAA{ 282 Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: respTTL}, 283 AAAA: ip, 284 }) 285 } 286 } 287 return resp, nil 288 } 289 290 func (r *Resolver) handlePTRQuery(ctx context.Context, query *dns.Msg) (*dns.Msg, error) { 291 ptr := query.Question[0].Name 292 name, after, found := strings.Cut(ptr, ptrIPv4domain) 293 if !found || after != "" { 294 name, after, found = strings.Cut(ptr, ptrIPv6domain) 295 } 296 if !found || after != "" { 297 // Not a known IPv4 or IPv6 PTR domain. 298 // Maybe the external DNS servers know what to do with the query? 299 return nil, nil 300 } 301 302 host := r.backend.ResolveIP(ctx, name) 303 if host == "" { 304 return nil, nil 305 } 306 307 r.log(ctx).Debugf("[resolver] lookup for IP %s: name %s", name, host) 308 fqdn := dns.Fqdn(host) 309 310 resp := createRespMsg(query) 311 resp.Answer = append(resp.Answer, &dns.PTR{ 312 Hdr: dns.RR_Header{Name: ptr, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: respTTL}, 313 Ptr: fqdn, 314 }) 315 return resp, nil 316 } 317 318 func (r *Resolver) handleSRVQuery(ctx context.Context, query *dns.Msg) (*dns.Msg, error) { 319 svc := query.Question[0].Name 320 srv, ip := r.backend.ResolveService(ctx, svc) 321 322 if len(srv) == 0 { 323 return nil, nil 324 } 325 if len(srv) != len(ip) { 326 return nil, fmt.Errorf("invalid reply for SRV query %s", svc) 327 } 328 329 resp := createRespMsg(query) 330 331 for i, r := range srv { 332 resp.Answer = append(resp.Answer, &dns.SRV{ 333 Hdr: dns.RR_Header{Name: svc, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: respTTL}, 334 Port: r.Port, 335 Target: r.Target, 336 }) 337 resp.Extra = append(resp.Extra, &dns.A{ 338 Hdr: dns.RR_Header{Name: r.Target, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: respTTL}, 339 A: ip[i], 340 }) 341 } 342 return resp, nil 343 } 344 345 func (r *Resolver) serveDNS(w dns.ResponseWriter, query *dns.Msg) { 346 var ( 347 resp *dns.Msg 348 err error 349 ) 350 351 if query == nil || len(query.Question) == 0 { 352 return 353 } 354 355 queryName := query.Question[0].Name 356 queryType := query.Question[0].Qtype 357 358 ctx, span := otel.Tracer("").Start(context.Background(), "resolver.serveDNS", trace.WithAttributes( 359 attribute.String("libnet.resolver.query.name", queryName), 360 attribute.String("libnet.resolver.query.type", dns.TypeToString[queryType]), 361 )) 362 defer span.End() 363 364 switch queryType { 365 case dns.TypeA: 366 resp, err = r.handleIPQuery(ctx, query, types.IPv4) 367 case dns.TypeAAAA: 368 resp, err = r.handleIPQuery(ctx, query, types.IPv6) 369 case dns.TypeMX: 370 resp, err = r.handleMXQuery(ctx, query) 371 case dns.TypePTR: 372 resp, err = r.handlePTRQuery(ctx, query) 373 case dns.TypeSRV: 374 resp, err = r.handleSRVQuery(ctx, query) 375 default: 376 r.log(ctx).Debugf("[resolver] query type %s is not supported by the embedded DNS and will be forwarded to external DNS", dns.TypeToString[queryType]) 377 } 378 379 reply := func(msg *dns.Msg) { 380 if err = w.WriteMsg(msg); err != nil { 381 r.log(ctx).WithError(err).Error("[resolver] failed to write response") 382 span.RecordError(err) 383 span.SetStatus(codes.Error, "WriteMsg failed") 384 // Make a best-effort attempt to send a failure response to the 385 // client so it doesn't have to wait for a timeout if the failure 386 // has to do with the content of msg rather than the connection. 387 if msg.Rcode != dns.RcodeServerFailure { 388 if err := w.WriteMsg(new(dns.Msg).SetRcode(query, dns.RcodeServerFailure)); err != nil { 389 r.log(ctx).WithError(err).Error("[resolver] writing ServFail response also failed") 390 span.RecordError(err) 391 } 392 } 393 } 394 } 395 396 if err != nil { 397 r.log(ctx).WithError(err).Errorf("[resolver] failed to handle query: %s (%s)", queryName, dns.TypeToString[queryType]) 398 reply(new(dns.Msg).SetRcode(query, dns.RcodeServerFailure)) 399 return 400 } 401 402 if resp != nil { 403 // We are the authoritative DNS server for this request so it's 404 // on us to truncate the response message to the size limit 405 // negotiated by the client. 406 maxSize := dns.MinMsgSize 407 if w.LocalAddr().Network() == "tcp" { 408 maxSize = dns.MaxMsgSize 409 } else { 410 if optRR := query.IsEdns0(); optRR != nil { 411 if udpsize := int(optRR.UDPSize()); udpsize > maxSize { 412 maxSize = udpsize 413 } 414 } 415 } 416 resp.Truncate(maxSize) 417 span.AddEvent("found local record", trace.WithAttributes( 418 attribute.String("libnet.resolver.resp", resp.String()), 419 )) 420 reply(resp) 421 return 422 } 423 424 if r.proxyDNS { 425 // If the user sets ndots > 0 explicitly and the query is 426 // in the root domain don't forward it out. We will return 427 // failure and let the client retry with the search domain 428 // attached. 429 if (queryType == dns.TypeA || queryType == dns.TypeAAAA) && r.backend.NdotsSet() && 430 !strings.Contains(strings.TrimSuffix(queryName, "."), ".") { 431 resp = createRespMsg(query) 432 } else { 433 resp = r.forwardExtDNS(ctx, w.LocalAddr().Network(), query) 434 } 435 } 436 437 if resp == nil { 438 // We were unable to get an answer from any of the upstream DNS 439 // servers or the backend doesn't support proxying DNS requests. 440 resp = new(dns.Msg).SetRcode(query, dns.RcodeServerFailure) 441 } 442 reply(resp) 443 } 444 445 const defaultPort = "53" 446 447 func (r *Resolver) dialExtDNS(proto string, server extDNSEntry) (net.Conn, error) { 448 port := defaultPort 449 if server.port != 0 { 450 port = strconv.FormatUint(uint64(server.port), 10) 451 } 452 addr := net.JoinHostPort(server.IPStr, port) 453 454 if server.HostLoopback { 455 return net.DialTimeout(proto, addr, extIOTimeout) 456 } 457 458 var ( 459 extConn net.Conn 460 dialErr error 461 ) 462 err := r.backend.ExecFunc(func() { 463 extConn, dialErr = net.DialTimeout(proto, addr, extIOTimeout) 464 }) 465 if err != nil { 466 return nil, err 467 } 468 if dialErr != nil { 469 return nil, dialErr 470 } 471 472 return extConn, nil 473 } 474 475 func (r *Resolver) forwardExtDNS(ctx context.Context, proto string, query *dns.Msg) *dns.Msg { 476 ctx, span := otel.Tracer("").Start(ctx, "resolver.forwardExtDNS") 477 defer span.End() 478 479 for _, extDNS := range r.extDNSList { 480 if extDNS.IPStr == "" { 481 break 482 } 483 484 // limits the number of outstanding concurrent queries. 485 ctx, cancel := context.WithTimeout(ctx, extIOTimeout) 486 err := r.fwdSem.Acquire(ctx, 1) 487 cancel() 488 489 if err != nil { 490 if errors.Is(err, context.DeadlineExceeded) { 491 r.logInverval.Do(func() { 492 r.log(ctx).Errorf("[resolver] more than %v concurrent queries", maxConcurrent) 493 }) 494 } 495 return new(dns.Msg).SetRcode(query, dns.RcodeRefused) 496 } 497 resp := func() *dns.Msg { 498 defer r.fwdSem.Release(1) 499 return r.exchange(ctx, proto, extDNS, query) 500 }() 501 if resp == nil { 502 continue 503 } 504 505 switch resp.Rcode { 506 case dns.RcodeServerFailure, dns.RcodeRefused: 507 // Server returned FAILURE: continue with the next external DNS server 508 // Server returned REFUSED: this can be a transitional status, so continue with the next external DNS server 509 r.log(ctx).Debugf("[resolver] external DNS %s:%s returned failure:\n%s", proto, extDNS.IPStr, resp) 510 continue 511 } 512 answers := 0 513 for _, rr := range resp.Answer { 514 h := rr.Header() 515 switch h.Rrtype { 516 case dns.TypeA: 517 answers++ 518 ip := rr.(*dns.A).A 519 r.log(ctx).Debugf("[resolver] received A record %q for %q from %s:%s", ip, h.Name, proto, extDNS.IPStr) 520 r.backend.HandleQueryResp(h.Name, ip) 521 case dns.TypeAAAA: 522 answers++ 523 ip := rr.(*dns.AAAA).AAAA 524 r.log(ctx).Debugf("[resolver] received AAAA record %q for %q from %s:%s", ip, h.Name, proto, extDNS.IPStr) 525 r.backend.HandleQueryResp(h.Name, ip) 526 } 527 } 528 if len(resp.Answer) == 0 { 529 r.log(ctx).Debugf("[resolver] external DNS %s:%s returned response with no answers:\n%s", proto, extDNS.IPStr, resp) 530 } 531 resp.Compress = true 532 span.AddEvent("response from upstream server", trace.WithAttributes( 533 attribute.String("libnet.resolver.resp", resp.String()), 534 )) 535 return resp 536 } 537 538 span.AddEvent("no response from upstream servers") 539 return nil 540 } 541 542 func (r *Resolver) exchange(ctx context.Context, proto string, extDNS extDNSEntry, query *dns.Msg) *dns.Msg { 543 ctx, span := otel.Tracer("").Start(ctx, "resolver.exchange", trace.WithAttributes( 544 attribute.String("libnet.resolver.upstream.proto", proto), 545 attribute.String("libnet.resolver.upstream.address", extDNS.IPStr), 546 attribute.Bool("libnet.resolver.upstream.host-loopback", extDNS.HostLoopback))) 547 defer span.End() 548 549 extConn, err := r.dialExtDNS(proto, extDNS) 550 if err != nil { 551 r.log(ctx).WithError(err).Warn("[resolver] connect failed") 552 span.RecordError(err) 553 span.SetStatus(codes.Error, "dialExtDNS failed") 554 return nil 555 } 556 defer extConn.Close() 557 558 logger := r.log(ctx).WithFields(log.Fields{ 559 "dns-server": extConn.RemoteAddr().Network() + ":" + extConn.RemoteAddr().String(), 560 "client-addr": extConn.LocalAddr().Network() + ":" + extConn.LocalAddr().String(), 561 "question": query.Question[0].String(), 562 }) 563 logger.Debug("[resolver] forwarding query") 564 565 resp, _, err := (&dns.Client{ 566 Timeout: extIOTimeout, 567 // Following the robustness principle, make a best-effort 568 // attempt to receive oversized response messages without 569 // truncating them on our end to forward verbatim to the client. 570 // Some DNS servers (e.g. Mikrotik RouterOS) don't support 571 // EDNS(0) and may send replies over UDP longer than 512 bytes 572 // regardless of what size limit, if any, was advertized in the 573 // query message. Note that ExchangeWithConn will override this 574 // value if it detects an EDNS OPT record in query so only 575 // oversized replies to non-EDNS queries will benefit. 576 UDPSize: dns.MaxMsgSize, 577 }).ExchangeWithConn(query, &dns.Conn{Conn: extConn}) 578 if err != nil { 579 logger.WithError(err).Error("[resolver] failed to query external DNS server") 580 span.RecordError(err) 581 span.SetStatus(codes.Error, "ExchangeWithConn failed") 582 return nil 583 } 584 585 if resp == nil { 586 // Should be impossible, so make noise if it happens anyway. 587 logger.Error("[resolver] external DNS returned empty response") 588 span.SetStatus(codes.Error, "External DNS returned empty response") 589 } 590 return resp 591 }