@@ -99,12 +99,12 @@ func lookupNameservers(fqdn string) ([]string, error) {
9999
100100 zone , err := FindZoneByFqdn (fqdn )
101101 if err != nil {
102- return nil , fmt .Errorf ("could not determine the zone: %w" , err )
102+ return nil , fmt .Errorf ("could not find zone: %w" , err )
103103 }
104104
105105 r , err := dnsQuery (zone , dns .TypeNS , recursiveNameservers , true )
106106 if err != nil {
107- return nil , err
107+ return nil , fmt . Errorf ( "NS call failed: %w" , err )
108108 }
109109
110110 for _ , rr := range r .Answer {
@@ -116,7 +116,8 @@ func lookupNameservers(fqdn string) ([]string, error) {
116116 if len (authoritativeNss ) > 0 {
117117 return authoritativeNss , nil
118118 }
119- return nil , errors .New ("could not determine authoritative nameservers" )
119+
120+ return nil , fmt .Errorf ("[zone=%s] could not determine authoritative nameservers" , zone )
120121}
121122
122123// FindPrimaryNsByFqdn determines the primary nameserver of the zone apex for the given fqdn
@@ -130,7 +131,7 @@ func FindPrimaryNsByFqdn(fqdn string) (string, error) {
130131func FindPrimaryNsByFqdnCustom (fqdn string , nameservers []string ) (string , error ) {
131132 soa , err := lookupSoaByFqdn (fqdn , nameservers )
132133 if err != nil {
133- return "" , err
134+ return "" , fmt . Errorf ( "[fqdn=%s] %w" , fqdn , err )
134135 }
135136 return soa .primaryNs , nil
136137}
@@ -146,7 +147,7 @@ func FindZoneByFqdn(fqdn string) (string, error) {
146147func FindZoneByFqdnCustom (fqdn string , nameservers []string ) (string , error ) {
147148 soa , err := lookupSoaByFqdn (fqdn , nameservers )
148149 if err != nil {
149- return "" , err
150+ return "" , fmt . Errorf ( "[fqdn=%s] %w" , fqdn , err )
150151 }
151152 return soa .zone , nil
152153}
@@ -171,35 +172,35 @@ func lookupSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error)
171172
172173func fetchSoaByFqdn (fqdn string , nameservers []string ) (* soaCacheEntry , error ) {
173174 var err error
174- var in * dns.Msg
175+ var r * dns.Msg
175176
176177 labelIndexes := dns .Split (fqdn )
177178 for _ , index := range labelIndexes {
178179 domain := fqdn [index :]
179180
180- in , err = dnsQuery (domain , dns .TypeSOA , nameservers , true )
181+ r , err = dnsQuery (domain , dns .TypeSOA , nameservers , true )
181182 if err != nil {
182183 continue
183184 }
184185
185- if in == nil {
186+ if r == nil {
186187 continue
187188 }
188189
189- switch in .Rcode {
190+ switch r .Rcode {
190191 case dns .RcodeSuccess :
191192 // Check if we got a SOA RR in the answer section
192- if len (in .Answer ) == 0 {
193+ if len (r .Answer ) == 0 {
193194 continue
194195 }
195196
196197 // CNAME records cannot/should not exist at the root of a zone.
197198 // So we skip a domain when a CNAME is found.
198- if dnsMsgContainsCNAME (in ) {
199+ if dnsMsgContainsCNAME (r ) {
199200 continue
200201 }
201202
202- for _ , ans := range in .Answer {
203+ for _ , ans := range r .Answer {
203204 if soa , ok := ans .(* dns.SOA ); ok {
204205 return newSoaCacheEntry (soa ), nil
205206 }
@@ -208,11 +209,11 @@ func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
208209 // NXDOMAIN
209210 default :
210211 // Any response code other than NOERROR and NXDOMAIN is treated as error
211- return nil , fmt .Errorf ("unexpected response code '%s' for %s " , dns . RcodeToString [ in . Rcode ], domain )
212+ return nil , & DNSError { Message : fmt .Sprintf ("unexpected response for '%s'" , domain ), MsgOut : r }
212213 }
213214 }
214215
215- return nil , fmt .Errorf ("could not find the start of authority for %s%s " , fqdn , formatDNSError ( in , err ))
216+ return nil , & DNSError { Message : fmt .Sprintf ("could not find the start of authority for '%s' " , fqdn ), MsgOut : r , Err : err }
216217}
217218
218219// dnsMsgContainsCNAME checks for a CNAME answer in msg.
@@ -226,16 +227,28 @@ func dnsMsgContainsCNAME(msg *dns.Msg) bool {
226227func dnsQuery (fqdn string , rtype uint16 , nameservers []string , recursive bool ) (* dns.Msg , error ) {
227228 m := createDNSMsg (fqdn , rtype , recursive )
228229
229- var in * dns.Msg
230+ if len (nameservers ) == 0 {
231+ return nil , & DNSError {Message : "empty list of nameservers" }
232+ }
233+
234+ var r * dns.Msg
230235 var err error
236+ var errAll error
231237
232238 for _ , ns := range nameservers {
233- in , err = sendDNSQuery (m , ns )
234- if err == nil && len (in .Answer ) > 0 {
239+ r , err = sendDNSQuery (m , ns )
240+ if err == nil && len (r .Answer ) > 0 {
235241 break
236242 }
243+
244+ errAll = errors .Join (errAll , err )
245+ }
246+
247+ if err != nil {
248+ return r , errAll
237249 }
238- return in , err
250+
251+ return r , nil
239252}
240253
241254func createDNSMsg (fqdn string , rtype uint16 , recursive bool ) * dns.Msg {
@@ -253,37 +266,82 @@ func createDNSMsg(fqdn string, rtype uint16, recursive bool) *dns.Msg {
253266func sendDNSQuery (m * dns.Msg , ns string ) (* dns.Msg , error ) {
254267 if ok , _ := strconv .ParseBool (os .Getenv ("LEGO_EXPERIMENTAL_DNS_TCP_ONLY" )); ok {
255268 tcp := & dns.Client {Net : "tcp" , Timeout : dnsTimeout }
256- in , _ , err := tcp .Exchange (m , ns )
269+ r , _ , err := tcp .Exchange (m , ns )
270+ if err != nil {
271+ return r , & DNSError {Message : "DNS call error" , MsgIn : m , NS : ns , Err : err }
272+ }
257273
258- return in , err
274+ return r , nil
259275 }
260276
261277 udp := & dns.Client {Net : "udp" , Timeout : dnsTimeout }
262- in , _ , err := udp .Exchange (m , ns )
278+ r , _ , err := udp .Exchange (m , ns )
263279
264- if in != nil && in .Truncated {
280+ if r != nil && r .Truncated {
265281 tcp := & dns.Client {Net : "tcp" , Timeout : dnsTimeout }
266282 // If the TCP request succeeds, the "err" will reset to nil
267- in , _ , err = tcp .Exchange (m , ns )
283+ r , _ , err = tcp .Exchange (m , ns )
284+ }
285+
286+ if err != nil {
287+ return r , & DNSError {Message : "DNS call error" , MsgIn : m , NS : ns , Err : err }
268288 }
269289
270- return in , err
290+ return r , nil
271291}
272292
273- func formatDNSError (msg * dns.Msg , err error ) string {
274- var parts []string
293+ // DNSError error related to DNS calls.
294+ type DNSError struct {
295+ Message string
296+ NS string
297+ MsgIn * dns.Msg
298+ MsgOut * dns.Msg
299+ Err error
300+ }
275301
276- if msg != nil {
277- parts = append (parts , dns .RcodeToString [msg .Rcode ])
302+ func (d * DNSError ) Error () string {
303+ var details []string
304+ if d .NS != "" {
305+ details = append (details , "ns=" + d .NS )
278306 }
279307
280- if err != nil {
281- parts = append (parts , err .Error ())
308+ if d .MsgIn != nil && len (d .MsgIn .Question ) > 0 {
309+ details = append (details , fmt .Sprintf ("question='%s'" , formatQuestions (d .MsgIn .Question )))
310+ }
311+
312+ if d .MsgOut != nil {
313+ if d .MsgIn == nil || len (d .MsgIn .Question ) == 0 {
314+ details = append (details , fmt .Sprintf ("question='%s'" , formatQuestions (d .MsgOut .Question )))
315+ }
316+
317+ details = append (details , "code=" + dns .RcodeToString [d .MsgOut .Rcode ])
318+ }
319+
320+ msg := "DNS error"
321+ if d .Message != "" {
322+ msg = d .Message
323+ }
324+
325+ if d .Err != nil {
326+ msg += ": " + d .Err .Error ()
327+ }
328+
329+ if len (details ) > 0 {
330+ msg += " [" + strings .Join (details , ", " ) + "]"
282331 }
283332
284- if len (parts ) > 0 {
285- return ": " + strings .Join (parts , " " )
333+ return msg
334+ }
335+
336+ func (d * DNSError ) Unwrap () error {
337+ return d .Err
338+ }
339+
340+ func formatQuestions (questions []dns.Question ) string {
341+ var parts []string
342+ for _ , question := range questions {
343+ parts = append (parts , strings .ReplaceAll (strings .TrimPrefix (question .String (), ";" ), "\t " , " " ))
286344 }
287345
288- return ""
346+ return strings . Join ( parts , ";" )
289347}
0 commit comments