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