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  }