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