...

Source file src/github.com/letsencrypt/boulder/bdns/dns.go

Documentation: github.com/letsencrypt/boulder/bdns

     1  package bdns
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"errors"
     7  	"fmt"
     8  	"net"
     9  	"strconv"
    10  	"strings"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/jmhodges/clock"
    15  	"github.com/miekg/dns"
    16  	"github.com/prometheus/client_golang/prometheus"
    17  
    18  	blog "github.com/letsencrypt/boulder/log"
    19  	"github.com/letsencrypt/boulder/metrics"
    20  )
    21  
    22  func parseCidr(network string, comment string) net.IPNet {
    23  	_, net, err := net.ParseCIDR(network)
    24  	if err != nil {
    25  		panic(fmt.Sprintf("error parsing %s (%s): %s", network, comment, err))
    26  	}
    27  	return *net
    28  }
    29  
    30  var (
    31  	// Private CIDRs to ignore
    32  	privateNetworks = []net.IPNet{
    33  		// RFC1918
    34  		// 10.0.0.0/8
    35  		{
    36  			IP:   []byte{10, 0, 0, 0},
    37  			Mask: []byte{255, 0, 0, 0},
    38  		},
    39  		// 172.16.0.0/12
    40  		{
    41  			IP:   []byte{172, 16, 0, 0},
    42  			Mask: []byte{255, 240, 0, 0},
    43  		},
    44  		// 192.168.0.0/16
    45  		{
    46  			IP:   []byte{192, 168, 0, 0},
    47  			Mask: []byte{255, 255, 0, 0},
    48  		},
    49  		// RFC5735
    50  		// 127.0.0.0/8
    51  		{
    52  			IP:   []byte{127, 0, 0, 0},
    53  			Mask: []byte{255, 0, 0, 0},
    54  		},
    55  		// RFC1122 Section 3.2.1.3
    56  		// 0.0.0.0/8
    57  		{
    58  			IP:   []byte{0, 0, 0, 0},
    59  			Mask: []byte{255, 0, 0, 0},
    60  		},
    61  		// RFC3927
    62  		// 169.254.0.0/16
    63  		{
    64  			IP:   []byte{169, 254, 0, 0},
    65  			Mask: []byte{255, 255, 0, 0},
    66  		},
    67  		// RFC 5736
    68  		// 192.0.0.0/24
    69  		{
    70  			IP:   []byte{192, 0, 0, 0},
    71  			Mask: []byte{255, 255, 255, 0},
    72  		},
    73  		// RFC 5737
    74  		// 192.0.2.0/24
    75  		{
    76  			IP:   []byte{192, 0, 2, 0},
    77  			Mask: []byte{255, 255, 255, 0},
    78  		},
    79  		// 198.51.100.0/24
    80  		{
    81  			IP:   []byte{198, 51, 100, 0},
    82  			Mask: []byte{255, 255, 255, 0},
    83  		},
    84  		// 203.0.113.0/24
    85  		{
    86  			IP:   []byte{203, 0, 113, 0},
    87  			Mask: []byte{255, 255, 255, 0},
    88  		},
    89  		// RFC 3068
    90  		// 192.88.99.0/24
    91  		{
    92  			IP:   []byte{192, 88, 99, 0},
    93  			Mask: []byte{255, 255, 255, 0},
    94  		},
    95  		// RFC 2544, Errata 423
    96  		// 198.18.0.0/15
    97  		{
    98  			IP:   []byte{198, 18, 0, 0},
    99  			Mask: []byte{255, 254, 0, 0},
   100  		},
   101  		// RFC 3171
   102  		// 224.0.0.0/4
   103  		{
   104  			IP:   []byte{224, 0, 0, 0},
   105  			Mask: []byte{240, 0, 0, 0},
   106  		},
   107  		// RFC 1112
   108  		// 240.0.0.0/4
   109  		{
   110  			IP:   []byte{240, 0, 0, 0},
   111  			Mask: []byte{240, 0, 0, 0},
   112  		},
   113  		// RFC 919 Section 7
   114  		// 255.255.255.255/32
   115  		{
   116  			IP:   []byte{255, 255, 255, 255},
   117  			Mask: []byte{255, 255, 255, 255},
   118  		},
   119  		// RFC 6598
   120  		// 100.64.0.0/10
   121  		{
   122  			IP:   []byte{100, 64, 0, 0},
   123  			Mask: []byte{255, 192, 0, 0},
   124  		},
   125  	}
   126  	// Sourced from https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
   127  	// where Global, Source, or Destination is False
   128  	privateV6Networks = []net.IPNet{
   129  		parseCidr("::/128", "RFC 4291: Unspecified Address"),
   130  		parseCidr("::1/128", "RFC 4291: Loopback Address"),
   131  		parseCidr("::ffff:0:0/96", "RFC 4291: IPv4-mapped Address"),
   132  		parseCidr("100::/64", "RFC 6666: Discard Address Block"),
   133  		parseCidr("2001::/23", "RFC 2928: IETF Protocol Assignments"),
   134  		parseCidr("2001:2::/48", "RFC 5180: Benchmarking"),
   135  		parseCidr("2001:db8::/32", "RFC 3849: Documentation"),
   136  		parseCidr("2001::/32", "RFC 4380: TEREDO"),
   137  		parseCidr("fc00::/7", "RFC 4193: Unique-Local"),
   138  		parseCidr("fe80::/10", "RFC 4291: Section 2.5.6 Link-Scoped Unicast"),
   139  		parseCidr("ff00::/8", "RFC 4291: Section 2.7"),
   140  		// We disable validations to IPs under the 6to4 anycase prefix because
   141  		// there's too much risk of a malicious actor advertising the prefix and
   142  		// answering validations for a 6to4 host they do not control.
   143  		// https://community.letsencrypt.org/t/problems-validating-ipv6-against-host-running-6to4/18312/9
   144  		parseCidr("2002::/16", "RFC 7526: 6to4 anycast prefix deprecated"),
   145  	}
   146  )
   147  
   148  // Client queries for DNS records
   149  type Client interface {
   150  	LookupTXT(context.Context, string) (txts []string, err error)
   151  	LookupHost(context.Context, string) ([]net.IP, error)
   152  	LookupCAA(context.Context, string) ([]*dns.CAA, string, error)
   153  }
   154  
   155  // impl represents a client that talks to an external resolver
   156  type impl struct {
   157  	dnsClient                exchanger
   158  	servers                  ServerProvider
   159  	allowRestrictedAddresses bool
   160  	maxTries                 int
   161  	clk                      clock.Clock
   162  	log                      blog.Logger
   163  
   164  	queryTime         *prometheus.HistogramVec
   165  	totalLookupTime   *prometheus.HistogramVec
   166  	timeoutCounter    *prometheus.CounterVec
   167  	idMismatchCounter *prometheus.CounterVec
   168  }
   169  
   170  var _ Client = &impl{}
   171  
   172  type exchanger interface {
   173  	Exchange(m *dns.Msg, a string) (*dns.Msg, time.Duration, error)
   174  }
   175  
   176  // New constructs a new DNS resolver object that utilizes the
   177  // provided list of DNS servers for resolution.
   178  func New(
   179  	readTimeout time.Duration,
   180  	servers ServerProvider,
   181  	stats prometheus.Registerer,
   182  	clk clock.Clock,
   183  	maxTries int,
   184  	log blog.Logger,
   185  ) Client {
   186  	dnsClient := new(dns.Client)
   187  
   188  	// Set timeout for underlying net.Conn
   189  	dnsClient.ReadTimeout = readTimeout
   190  	dnsClient.Net = "udp"
   191  
   192  	queryTime := prometheus.NewHistogramVec(
   193  		prometheus.HistogramOpts{
   194  			Name:    "dns_query_time",
   195  			Help:    "Time taken to perform a DNS query",
   196  			Buckets: metrics.InternetFacingBuckets,
   197  		},
   198  		[]string{"qtype", "result", "resolver"},
   199  	)
   200  	totalLookupTime := prometheus.NewHistogramVec(
   201  		prometheus.HistogramOpts{
   202  			Name:    "dns_total_lookup_time",
   203  			Help:    "Time taken to perform a DNS lookup, including all retried queries",
   204  			Buckets: metrics.InternetFacingBuckets,
   205  		},
   206  		[]string{"qtype", "result", "retries", "resolver"},
   207  	)
   208  	timeoutCounter := prometheus.NewCounterVec(
   209  		prometheus.CounterOpts{
   210  			Name: "dns_timeout",
   211  			Help: "Counter of various types of DNS query timeouts",
   212  		},
   213  		[]string{"qtype", "type", "resolver", "isTLD"},
   214  	)
   215  	idMismatchCounter := prometheus.NewCounterVec(
   216  		prometheus.CounterOpts{
   217  			Name: "dns_id_mismatch",
   218  			Help: "Counter of DNS ErrId errors sliced by query type and resolver",
   219  		},
   220  		[]string{"qtype", "resolver"},
   221  	)
   222  	stats.MustRegister(queryTime, totalLookupTime, timeoutCounter, idMismatchCounter)
   223  
   224  	return &impl{
   225  		dnsClient:                dnsClient,
   226  		servers:                  servers,
   227  		allowRestrictedAddresses: false,
   228  		maxTries:                 maxTries,
   229  		clk:                      clk,
   230  		queryTime:                queryTime,
   231  		totalLookupTime:          totalLookupTime,
   232  		timeoutCounter:           timeoutCounter,
   233  		idMismatchCounter:        idMismatchCounter,
   234  		log:                      log,
   235  	}
   236  }
   237  
   238  // NewTest constructs a new DNS resolver object that utilizes the
   239  // provided list of DNS servers for resolution and will allow loopback addresses.
   240  // This constructor should *only* be called from tests (unit or integration).
   241  func NewTest(
   242  	readTimeout time.Duration,
   243  	servers ServerProvider,
   244  	stats prometheus.Registerer,
   245  	clk clock.Clock,
   246  	maxTries int,
   247  	log blog.Logger) Client {
   248  	resolver := New(readTimeout, servers, stats, clk, maxTries, log)
   249  	resolver.(*impl).allowRestrictedAddresses = true
   250  	return resolver
   251  }
   252  
   253  // exchangeOne performs a single DNS exchange with a randomly chosen server
   254  // out of the server list, returning the response, time, and error (if any).
   255  // We assume that the upstream resolver requests and validates DNSSEC records
   256  // itself.
   257  func (dnsClient *impl) exchangeOne(ctx context.Context, hostname string, qtype uint16) (resp *dns.Msg, err error) {
   258  	m := new(dns.Msg)
   259  	// Set question type
   260  	m.SetQuestion(dns.Fqdn(hostname), qtype)
   261  	// Set the AD bit in the query header so that the resolver knows that
   262  	// we are interested in this bit in the response header. If this isn't
   263  	// set the AD bit in the response is useless (RFC 6840 Section 5.7).
   264  	// This has no security implications, it simply allows us to gather
   265  	// metrics about the percentage of responses that are secured with
   266  	// DNSSEC.
   267  	m.AuthenticatedData = true
   268  	// Tell the resolver that we're willing to receive responses up to 4096 bytes.
   269  	// This happens sometimes when there are a very large number of CAA records
   270  	// present.
   271  	m.SetEdns0(4096, false)
   272  
   273  	servers, err := dnsClient.servers.Addrs()
   274  	if err != nil {
   275  		return nil, fmt.Errorf("failed to list DNS servers: %w", err)
   276  	}
   277  	chosenServerIndex := 0
   278  	chosenServer := servers[chosenServerIndex]
   279  
   280  	// Strip off the IP address part of the server address because
   281  	// we talk to the same server on multiple ports, and don't want
   282  	// to blow up the cardinality.
   283  	chosenServerIP, _, err := net.SplitHostPort(chosenServer)
   284  	if err != nil {
   285  		return
   286  	}
   287  
   288  	start := dnsClient.clk.Now()
   289  	client := dnsClient.dnsClient
   290  	qtypeStr := dns.TypeToString[qtype]
   291  	tries := 1
   292  	defer func() {
   293  		result := "failed"
   294  		if resp != nil {
   295  			result = dns.RcodeToString[resp.Rcode]
   296  		}
   297  		dnsClient.totalLookupTime.With(prometheus.Labels{
   298  			"qtype":    qtypeStr,
   299  			"result":   result,
   300  			"retries":  strconv.Itoa(tries),
   301  			"resolver": chosenServerIP,
   302  		}).Observe(dnsClient.clk.Since(start).Seconds())
   303  	}()
   304  	for {
   305  		ch := make(chan dnsResp, 1)
   306  
   307  		// Strip off the IP address part of the server address because
   308  		// we talk to the same server on multiple ports, and don't want
   309  		// to blow up the cardinality.
   310  		// Note: validateServerAddress() has already checked net.SplitHostPort()
   311  		// and ensures that chosenServer can't be a bare port, e.g. ":1337"
   312  		chosenServerIP, _, err = net.SplitHostPort(chosenServer)
   313  		if err != nil {
   314  			return
   315  		}
   316  
   317  		go func() {
   318  			rsp, rtt, err := client.Exchange(m, chosenServer)
   319  			result := "failed"
   320  			if rsp != nil {
   321  				result = dns.RcodeToString[rsp.Rcode]
   322  			}
   323  			if err != nil {
   324  				logDNSError(dnsClient.log, chosenServer, hostname, m, rsp, err)
   325  				if err == dns.ErrId {
   326  					dnsClient.idMismatchCounter.With(prometheus.Labels{
   327  						"qtype":    qtypeStr,
   328  						"resolver": chosenServerIP,
   329  					}).Inc()
   330  				}
   331  			}
   332  			dnsClient.queryTime.With(prometheus.Labels{
   333  				"qtype":    qtypeStr,
   334  				"result":   result,
   335  				"resolver": chosenServerIP,
   336  			}).Observe(rtt.Seconds())
   337  			ch <- dnsResp{m: rsp, err: err}
   338  		}()
   339  		select {
   340  		case <-ctx.Done():
   341  			if ctx.Err() == context.DeadlineExceeded {
   342  				dnsClient.timeoutCounter.With(prometheus.Labels{
   343  					"qtype":    qtypeStr,
   344  					"type":     "deadline exceeded",
   345  					"resolver": chosenServerIP,
   346  					"isTLD":    isTLD(hostname),
   347  				}).Inc()
   348  			} else if ctx.Err() == context.Canceled {
   349  				dnsClient.timeoutCounter.With(prometheus.Labels{
   350  					"qtype":    qtypeStr,
   351  					"type":     "canceled",
   352  					"resolver": chosenServerIP,
   353  					"isTLD":    isTLD(hostname),
   354  				}).Inc()
   355  			} else {
   356  				dnsClient.timeoutCounter.With(prometheus.Labels{
   357  					"qtype":    qtypeStr,
   358  					"type":     "unknown",
   359  					"resolver": chosenServerIP,
   360  				}).Inc()
   361  			}
   362  			err = ctx.Err()
   363  			return
   364  		case r := <-ch:
   365  			if r.err != nil {
   366  				var operr *net.OpError
   367  				ok := errors.As(r.err, &operr)
   368  				isRetryable := ok && operr.Temporary()
   369  				hasRetriesLeft := tries < dnsClient.maxTries
   370  				if isRetryable && hasRetriesLeft {
   371  					tries++
   372  					// Chose a new server to retry the query with by incrementing the
   373  					// chosen server index modulo the number of servers. This ensures that
   374  					// if one dns server isn't available we retry with the next in the
   375  					// list.
   376  					chosenServerIndex = (chosenServerIndex + 1) % len(servers)
   377  					chosenServer = servers[chosenServerIndex]
   378  					continue
   379  				} else if isRetryable && !hasRetriesLeft {
   380  					dnsClient.timeoutCounter.With(prometheus.Labels{
   381  						"qtype":    qtypeStr,
   382  						"type":     "out of retries",
   383  						"resolver": chosenServerIP,
   384  						"isTLD":    isTLD(hostname),
   385  					}).Inc()
   386  				}
   387  			}
   388  			resp, err = r.m, r.err
   389  			return
   390  		}
   391  	}
   392  
   393  }
   394  
   395  // isTLD returns a simplified view of whether something is a TLD: does it have
   396  // any dots in it? This returns true or false as a string, and is meant solely
   397  // for Prometheus metrics.
   398  func isTLD(hostname string) string {
   399  	if strings.Contains(hostname, ".") {
   400  		return "false"
   401  	} else {
   402  		return "true"
   403  	}
   404  }
   405  
   406  type dnsResp struct {
   407  	m   *dns.Msg
   408  	err error
   409  }
   410  
   411  // LookupTXT sends a DNS query to find all TXT records associated with
   412  // the provided hostname which it returns along with the returned
   413  // DNS authority section.
   414  func (dnsClient *impl) LookupTXT(ctx context.Context, hostname string) ([]string, error) {
   415  	var txt []string
   416  	dnsType := dns.TypeTXT
   417  	r, err := dnsClient.exchangeOne(ctx, hostname, dnsType)
   418  	errWrap := wrapErr(dnsType, hostname, r, err)
   419  	if errWrap != nil {
   420  		return nil, errWrap
   421  	}
   422  
   423  	for _, answer := range r.Answer {
   424  		if answer.Header().Rrtype == dnsType {
   425  			if txtRec, ok := answer.(*dns.TXT); ok {
   426  				txt = append(txt, strings.Join(txtRec.Txt, ""))
   427  			}
   428  		}
   429  	}
   430  
   431  	return txt, err
   432  }
   433  
   434  func isPrivateV4(ip net.IP) bool {
   435  	for _, net := range privateNetworks {
   436  		if net.Contains(ip) {
   437  			return true
   438  		}
   439  	}
   440  	return false
   441  }
   442  
   443  func isPrivateV6(ip net.IP) bool {
   444  	for _, net := range privateV6Networks {
   445  		if net.Contains(ip) {
   446  			return true
   447  		}
   448  	}
   449  	return false
   450  }
   451  
   452  func (dnsClient *impl) lookupIP(ctx context.Context, hostname string, ipType uint16) ([]dns.RR, error) {
   453  	resp, err := dnsClient.exchangeOne(ctx, hostname, ipType)
   454  	errWrap := wrapErr(ipType, hostname, resp, err)
   455  	if errWrap != nil {
   456  		return nil, errWrap
   457  	}
   458  	return resp.Answer, nil
   459  }
   460  
   461  // LookupHost sends a DNS query to find all A and AAAA records associated with
   462  // the provided hostname. This method assumes that the external resolver will
   463  // chase CNAME/DNAME aliases and return relevant records. It will retry
   464  // requests in the case of temporary network errors. It returns an error if
   465  // both the A and AAAA lookups fail or are empty, but succeeds otherwise.
   466  func (dnsClient *impl) LookupHost(ctx context.Context, hostname string) ([]net.IP, error) {
   467  	var recordsA, recordsAAAA []dns.RR
   468  	var errA, errAAAA error
   469  	var wg sync.WaitGroup
   470  
   471  	wg.Add(1)
   472  	go func() {
   473  		defer wg.Done()
   474  		recordsA, errA = dnsClient.lookupIP(ctx, hostname, dns.TypeA)
   475  	}()
   476  	wg.Add(1)
   477  	go func() {
   478  		defer wg.Done()
   479  		recordsAAAA, errAAAA = dnsClient.lookupIP(ctx, hostname, dns.TypeAAAA)
   480  	}()
   481  	wg.Wait()
   482  
   483  	var addrsA []net.IP
   484  	if errA == nil {
   485  		for _, answer := range recordsA {
   486  			if answer.Header().Rrtype == dns.TypeA {
   487  				a, ok := answer.(*dns.A)
   488  				if ok && a.A.To4() != nil && (!isPrivateV4(a.A) || dnsClient.allowRestrictedAddresses) {
   489  					addrsA = append(addrsA, a.A)
   490  				}
   491  			}
   492  		}
   493  		if len(addrsA) == 0 {
   494  			errA = fmt.Errorf("no valid A records found for %s", hostname)
   495  		}
   496  	}
   497  
   498  	var addrsAAAA []net.IP
   499  	if errAAAA == nil {
   500  		for _, answer := range recordsAAAA {
   501  			if answer.Header().Rrtype == dns.TypeAAAA {
   502  				aaaa, ok := answer.(*dns.AAAA)
   503  				if ok && aaaa.AAAA.To16() != nil && (!isPrivateV6(aaaa.AAAA) || dnsClient.allowRestrictedAddresses) {
   504  					addrsAAAA = append(addrsAAAA, aaaa.AAAA)
   505  				}
   506  			}
   507  		}
   508  		if len(addrsAAAA) == 0 {
   509  			errAAAA = fmt.Errorf("no valid AAAA records found for %s", hostname)
   510  		}
   511  	}
   512  
   513  	if errA != nil && errAAAA != nil {
   514  		// Construct a new error from both underlying errors. We can only use %w for
   515  		// one of them, because the go error unwrapping protocol doesn't support
   516  		// branching. We don't use ProblemDetails and SubProblemDetails here, because
   517  		// this error will get wrapped in a DNSError and further munged by higher
   518  		// layers in the stack.
   519  		return nil, fmt.Errorf("%w; %s", errA, errAAAA)
   520  	}
   521  
   522  	return append(addrsA, addrsAAAA...), nil
   523  }
   524  
   525  // LookupCAA sends a DNS query to find all CAA records associated with
   526  // the provided hostname and the complete dig-style RR `response`. This
   527  // response is quite verbose, however it's only populated when the CAA
   528  // response is non-empty.
   529  func (dnsClient *impl) LookupCAA(ctx context.Context, hostname string) ([]*dns.CAA, string, error) {
   530  	dnsType := dns.TypeCAA
   531  	r, err := dnsClient.exchangeOne(ctx, hostname, dnsType)
   532  
   533  	// Special case: when checking CAA for non-TLD names, treat NXDOMAIN as a
   534  	// successful response containing an empty set of records. This can come up in
   535  	// situations where records were provisioned for validation (e.g. TXT records
   536  	// for DNS-01 challenge) and then removed after validation but before CAA
   537  	// rechecking. But allow NXDOMAIN for TLDs to fall through to the error code
   538  	// below, so we don't issue for gTLDs that have been removed by ICANN.
   539  	if err == nil && r.Rcode == dns.RcodeNameError && strings.Contains(hostname, ".") {
   540  		return nil, "", nil
   541  	}
   542  
   543  	errWrap := wrapErr(dnsType, hostname, r, err)
   544  	if errWrap != nil {
   545  		return nil, "", errWrap
   546  	}
   547  
   548  	var CAAs []*dns.CAA
   549  	for _, answer := range r.Answer {
   550  		if caaR, ok := answer.(*dns.CAA); ok {
   551  			CAAs = append(CAAs, caaR)
   552  		}
   553  	}
   554  	var response string
   555  	if len(CAAs) > 0 {
   556  		response = r.String()
   557  	}
   558  	return CAAs, response, nil
   559  }
   560  
   561  // logDNSError logs the provided err result from making a query for hostname to
   562  // the chosenServer. If the err is a `dns.ErrId` instance then the Base64
   563  // encoded bytes of the query (and if not-nil, the response) in wire format
   564  // is logged as well. This function is called from exchangeOne only for the case
   565  // where an error occurs querying a hostname that indicates a problem between
   566  // the VA and the chosenServer.
   567  func logDNSError(
   568  	logger blog.Logger,
   569  	chosenServer string,
   570  	hostname string,
   571  	msg, resp *dns.Msg,
   572  	underlying error) {
   573  	// We don't expect logDNSError to be called with a nil msg or err but
   574  	// if it happens return early. We allow resp to be nil.
   575  	if msg == nil || len(msg.Question) == 0 || underlying == nil {
   576  		return
   577  	}
   578  	queryType := dns.TypeToString[msg.Question[0].Qtype]
   579  
   580  	// If the error indicates there was a query/response ID mismatch then we want
   581  	// to log more detail.
   582  	if underlying == dns.ErrId {
   583  		packedMsgBytes, err := msg.Pack()
   584  		if err != nil {
   585  			logger.Errf("logDNSError failed to pack msg: %v", err)
   586  			return
   587  		}
   588  		encodedMsg := base64.StdEncoding.EncodeToString(packedMsgBytes)
   589  
   590  		var encodedResp string
   591  		var respQname string
   592  		if resp != nil {
   593  			packedRespBytes, err := resp.Pack()
   594  			if err != nil {
   595  				logger.Errf("logDNSError failed to pack resp: %v", err)
   596  				return
   597  			}
   598  			encodedResp = base64.StdEncoding.EncodeToString(packedRespBytes)
   599  			if len(resp.Answer) > 0 && resp.Answer[0].Header() != nil {
   600  				respQname = resp.Answer[0].Header().Name
   601  			}
   602  		}
   603  
   604  		logger.Infof(
   605  			"logDNSError ID mismatch chosenServer=[%s] hostname=[%s] respHostname=[%s] queryType=[%s] msg=[%s] resp=[%s] err=[%s]",
   606  			chosenServer,
   607  			hostname,
   608  			respQname,
   609  			queryType,
   610  			encodedMsg,
   611  			encodedResp,
   612  			underlying)
   613  	} else {
   614  		// Otherwise log a general DNS error
   615  		logger.Infof("logDNSError chosenServer=[%s] hostname=[%s] queryType=[%s] err=[%s]",
   616  			chosenServer,
   617  			hostname,
   618  			queryType,
   619  			underlying)
   620  	}
   621  }
   622  

View as plain text