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