...

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

Documentation: github.com/letsencrypt/boulder/bdns

     1  package bdns
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  
     8  	"github.com/miekg/dns"
     9  )
    10  
    11  // Error wraps a DNS error with various relevant information
    12  type Error struct {
    13  	recordType uint16
    14  	hostname   string
    15  	// Exactly one of rCode or underlying should be set.
    16  	underlying error
    17  	rCode      int
    18  
    19  	// Optional: If the resolver returned extended error information, it will be stored here.
    20  	// https://www.rfc-editor.org/rfc/rfc8914
    21  	extended *dns.EDNS0_EDE
    22  }
    23  
    24  // extendedDNSError returns non-nil if the input message contained an OPT RR
    25  // with an EDE option. https://www.rfc-editor.org/rfc/rfc8914.
    26  func extendedDNSError(msg *dns.Msg) *dns.EDNS0_EDE {
    27  	opt := msg.IsEdns0()
    28  	if opt != nil {
    29  		for _, opt := range opt.Option {
    30  			ede, ok := opt.(*dns.EDNS0_EDE)
    31  			if !ok {
    32  				continue
    33  			}
    34  			return ede
    35  		}
    36  	}
    37  	return nil
    38  }
    39  
    40  // wrapErr returns a non-nil error if err is non-nil or if resp.Rcode is not dns.RcodeSuccess.
    41  // The error includes appropriate details about the DNS query that failed.
    42  func wrapErr(queryType uint16, hostname string, resp *dns.Msg, err error) error {
    43  	if err != nil {
    44  		return Error{
    45  			recordType: queryType,
    46  			hostname:   hostname,
    47  			underlying: err,
    48  			extended:   nil,
    49  		}
    50  	}
    51  	if resp.Rcode != dns.RcodeSuccess {
    52  		return Error{
    53  			recordType: queryType,
    54  			hostname:   hostname,
    55  			rCode:      resp.Rcode,
    56  			underlying: nil,
    57  			extended:   extendedDNSError(resp),
    58  		}
    59  	}
    60  	return nil
    61  }
    62  
    63  // A copy of miekg/dns's mapping of error codes to strings. We tweak it slightly so all DNSSEC-related
    64  // errors say "DNSSEC" at the beginning.
    65  // https://pkg.go.dev/github.com/miekg/dns#ExtendedErrorCodeToString
    66  // Also note that not all of these codes can currently be emitted by Unbound. See Unbound's
    67  // announcement post for EDE: https://blog.nlnetlabs.nl/extended-dns-error-support-for-unbound/
    68  var extendedErrorCodeToString = map[uint16]string{
    69  	dns.ExtendedErrorCodeOther:                      "Other",
    70  	dns.ExtendedErrorCodeUnsupportedDNSKEYAlgorithm: "DNSSEC: Unsupported DNSKEY Algorithm",
    71  	dns.ExtendedErrorCodeUnsupportedDSDigestType:    "DNSSEC: Unsupported DS Digest Type",
    72  	dns.ExtendedErrorCodeStaleAnswer:                "Stale Answer",
    73  	dns.ExtendedErrorCodeForgedAnswer:               "Forged Answer",
    74  	dns.ExtendedErrorCodeDNSSECIndeterminate:        "DNSSEC: Indeterminate",
    75  	dns.ExtendedErrorCodeDNSBogus:                   "DNSSEC: Bogus",
    76  	dns.ExtendedErrorCodeSignatureExpired:           "DNSSEC: Signature Expired",
    77  	dns.ExtendedErrorCodeSignatureNotYetValid:       "DNSSEC: Signature Not Yet Valid",
    78  	dns.ExtendedErrorCodeDNSKEYMissing:              "DNSSEC: DNSKEY Missing",
    79  	dns.ExtendedErrorCodeRRSIGsMissing:              "DNSSEC: RRSIGs Missing",
    80  	dns.ExtendedErrorCodeNoZoneKeyBitSet:            "DNSSEC: No Zone Key Bit Set",
    81  	dns.ExtendedErrorCodeNSECMissing:                "DNSSEC: NSEC Missing",
    82  	dns.ExtendedErrorCodeCachedError:                "Cached Error",
    83  	dns.ExtendedErrorCodeNotReady:                   "Not Ready",
    84  	dns.ExtendedErrorCodeBlocked:                    "Blocked",
    85  	dns.ExtendedErrorCodeCensored:                   "Censored",
    86  	dns.ExtendedErrorCodeFiltered:                   "Filtered",
    87  	dns.ExtendedErrorCodeProhibited:                 "Prohibited",
    88  	dns.ExtendedErrorCodeStaleNXDOMAINAnswer:        "Stale NXDOMAIN Answer",
    89  	dns.ExtendedErrorCodeNotAuthoritative:           "Not Authoritative",
    90  	dns.ExtendedErrorCodeNotSupported:               "Not Supported",
    91  	dns.ExtendedErrorCodeNoReachableAuthority:       "No Reachable Authority",
    92  	dns.ExtendedErrorCodeNetworkError:               "Network Error between Resolver and Authority",
    93  	dns.ExtendedErrorCodeInvalidData:                "Invalid Data",
    94  }
    95  
    96  func (d Error) Error() string {
    97  	var detail, additional string
    98  	if d.underlying != nil {
    99  		if netErr, ok := d.underlying.(*net.OpError); ok {
   100  			if netErr.Timeout() {
   101  				detail = detailDNSTimeout
   102  			} else {
   103  				detail = detailDNSNetFailure
   104  			}
   105  			// Note: we check d.underlying here even though `Timeout()` does this because the call to `netErr.Timeout()` above only
   106  			// happens for `*net.OpError` underlying types!
   107  		} else if d.underlying == context.DeadlineExceeded {
   108  			detail = detailDNSTimeout
   109  		} else if d.underlying == context.Canceled {
   110  			detail = detailCanceled
   111  		} else {
   112  			detail = detailServerFailure
   113  		}
   114  	} else if d.rCode != dns.RcodeSuccess {
   115  		detail = dns.RcodeToString[d.rCode]
   116  		if explanation, ok := rcodeExplanations[d.rCode]; ok {
   117  			additional = " - " + explanation
   118  		}
   119  	} else {
   120  		detail = detailServerFailure
   121  	}
   122  
   123  	if d.extended == nil {
   124  		return fmt.Sprintf("DNS problem: %s looking up %s for %s%s", detail,
   125  			dns.TypeToString[d.recordType], d.hostname, additional)
   126  	}
   127  
   128  	summary := extendedErrorCodeToString[d.extended.InfoCode]
   129  	if summary == "" {
   130  		summary = fmt.Sprintf("Unknown Extended DNS Error code %d", d.extended.InfoCode)
   131  	}
   132  	result := fmt.Sprintf("DNS problem: looking up %s for %s: %s",
   133  		dns.TypeToString[d.recordType], d.hostname, summary)
   134  	if d.extended.ExtraText != "" {
   135  		result = result + ": " + d.extended.ExtraText
   136  	}
   137  	return result
   138  }
   139  
   140  const detailDNSTimeout = "query timed out"
   141  const detailCanceled = "query timed out (and was canceled)"
   142  const detailDNSNetFailure = "networking error"
   143  const detailServerFailure = "server failure at resolver"
   144  
   145  // rcodeExplanations provide additional friendly explanatory text to be included in DNS
   146  // error messages, for select inscrutable RCODEs.
   147  var rcodeExplanations = map[int]string{
   148  	dns.RcodeNameError:     "check that a DNS record exists for this domain",
   149  	dns.RcodeServerFailure: "the domain's nameservers may be malfunctioning",
   150  }
   151  

View as plain text