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  }