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  }