@@ -145,7 +145,7 @@ func cgoLookupServicePort(hints *_C_struct_addrinfo, network, service string) (p
145
145
return 0 , & DNSError {Err : "unknown port" , Name : network + "/" + service , IsNotFound : true }
146
146
}
147
147
148
- func cgoLookupHostIP (network , name string ) (addrs []IPAddr , err error ) {
148
+ func cgoLookupHostIP (network , name string ) (addrs []IPAddr , cname string , err error ) {
149
149
acquireThread ()
150
150
defer releaseThread ()
151
151
@@ -162,7 +162,7 @@ func cgoLookupHostIP(network, name string) (addrs []IPAddr, err error) {
162
162
163
163
h , err := syscall .BytePtrFromString (name )
164
164
if err != nil {
165
- return nil , & DNSError {Err : err .Error (), Name : name }
165
+ return nil , "" , & DNSError {Err : err .Error (), Name : name }
166
166
}
167
167
var res * _C_struct_addrinfo
168
168
gerrno , err := _C_getaddrinfo ((* _C_char )(unsafe .Pointer (h )), nil , & hints , & res )
@@ -189,10 +189,20 @@ func cgoLookupHostIP(network, name string) (addrs []IPAddr, err error) {
189
189
isTemporary = addrinfoErrno (gerrno ).Temporary ()
190
190
}
191
191
192
- return nil , & DNSError {Err : err .Error (), Name : name , IsNotFound : isErrorNoSuchHost , IsTemporary : isTemporary }
192
+ return nil , "" , & DNSError {Err : err .Error (), Name : name , IsNotFound : isErrorNoSuchHost , IsTemporary : isTemporary }
193
193
}
194
194
defer _C_freeaddrinfo (res )
195
195
196
+ if res != nil {
197
+ cname = _C_GoString (* _C_ai_canonname (res ))
198
+ if cname == "" {
199
+ cname = name
200
+ }
201
+ if len (cname ) > 0 && cname [len (cname )- 1 ] != '.' {
202
+ cname += "."
203
+ }
204
+ }
205
+
196
206
for r := res ; r != nil ; r = * _C_ai_next (r ) {
197
207
// We only asked for SOCK_STREAM, but check anyhow.
198
208
if * _C_ai_socktype (r ) != _C_SOCK_STREAM {
@@ -209,12 +219,13 @@ func cgoLookupHostIP(network, name string) (addrs []IPAddr, err error) {
209
219
addrs = append (addrs , addr )
210
220
}
211
221
}
212
- return addrs , nil
222
+ return addrs , cname , nil
213
223
}
214
224
215
225
func cgoLookupIP (ctx context.Context , network , name string ) (addrs []IPAddr , err error ) {
216
226
return doBlockingWithCtx (ctx , func () ([]IPAddr , error ) {
217
- return cgoLookupHostIP (network , name )
227
+ addrs , _ , err := cgoLookupHostIP (network , name )
228
+ return addrs , err
218
229
})
219
230
}
220
231
@@ -295,45 +306,97 @@ func cgoSockaddr(ip IP, zone string) (*_C_struct_sockaddr, _C_socklen_t) {
295
306
return nil , 0
296
307
}
297
308
298
- func cgoLookupCNAME (ctx context.Context , name string ) (cname string , err error , completed bool ) {
299
- resources , err := resSearch (ctx , name , int (dnsmessage .TypeCNAME ), int (dnsmessage .ClassINET ))
309
+ // cgoLookupCanonicalName returns the host canonical name.
310
+ func cgoLookupCanonicalName (ctx context.Context , network string , name string ) (cname string , err error ) {
311
+ return doBlockingWithCtx (ctx , func () (string , error ) {
312
+ _ , cname , err := cgoLookupHostIP (network , name )
313
+ return cname , err
314
+ })
315
+ }
316
+
317
+ // cgoLookupCNAME queries the CNAME resource using cgo resSearch.
318
+ // It returns the last CNAME found in the entire CNAME chain or the queried name when
319
+ // query returns with no answer resources.
320
+ func cgoLookupCNAME (ctx context.Context , name string ) (cname string , err error ) {
321
+ msg , err := resSearch (ctx , name , int (dnsmessage .TypeCNAME ), int (dnsmessage .ClassINET ))
322
+
323
+ noData := false
324
+ if err != nil {
325
+ var dnsErr * DNSError
326
+ if ! errors .As (err , & dnsErr ) {
327
+ // Not a DNS error.
328
+ return "" , err
329
+ } else if dnsErr .isNoData && msg != nil {
330
+ // DNS query succeeded, without error code (like NXDOMAIN),
331
+ // but it has zero answer records.
332
+ noData = true
333
+ } else {
334
+ return "" , err
335
+ }
336
+ }
337
+
338
+ var p dnsmessage.Parser
339
+ _ , err = p .Start (msg )
300
340
if err != nil {
301
- return
341
+ return "" , & DNSError { Err : errCannotUnmarshalDNSMessage . Error (), Name : name }
302
342
}
303
- cname , err = parseCNAMEFromResources (resources )
343
+
344
+ q , err := p .Question ()
304
345
if err != nil {
305
- return "" , err , false
346
+ return "" , & DNSError {Err : errCannotUnmarshalDNSMessage .Error (), Name : name }
347
+ }
348
+
349
+ // Multiple questions, this should never happen.
350
+ if err := p .SkipQuestion (); err != dnsmessage .ErrSectionDone {
351
+ return "" , & DNSError {Err : errCannotUnmarshalDNSMessage .Error (), Name : name }
352
+ }
353
+
354
+ if noData {
355
+ return q .Name .String (), nil
306
356
}
307
- return cname , nil , true
357
+
358
+ // Using name from question, not the one provided in function arguments,
359
+ // because of possible search domain in resolv.conf.
360
+ cname , err = lastCNAMEinChain (q .Name , p )
361
+ if err != nil {
362
+ return "" , & DNSError {
363
+ Err : err .Error (),
364
+ Name : name ,
365
+ }
366
+ }
367
+
368
+ return cname , nil
308
369
}
309
370
371
+ // errCgoDNSLookupFailed is returned from resSearch on systems with non thread safe h_errno.
372
+ var errCgoDNSLookupFailed = errors .New ("res_nsearch lookup failed" )
373
+
310
374
// resSearch will make a call to the 'res_nsearch' routine in the C library
311
375
// and parse the output as a slice of DNS resources.
312
- func resSearch (ctx context.Context , hostname string , rtype , class int ) ([]dnsmessage.Resource , error ) {
313
- return doBlockingWithCtx (ctx , func () ([]dnsmessage.Resource , error ) {
376
+ // In case of an error, the msg might be populated with a raw DNS response (it might
377
+ // be partial or with junk after the DNS message).
378
+ func resSearch (ctx context.Context , hostname string , rtype , class int ) (msg []byte , err error ) {
379
+ return doBlockingWithCtx (ctx , func () ([]byte , error ) {
314
380
return cgoResSearch (hostname , rtype , class )
315
381
})
316
382
}
317
383
318
- func cgoResSearch (hostname string , rtype , class int ) ([]dnsmessage. Resource , error ) {
384
+ func cgoResSearch (hostname string , rtype , class int ) ([]byte , error ) {
319
385
acquireThread ()
320
386
defer releaseThread ()
321
387
322
- state := (* _C_struct___res_state )(_C_malloc (unsafe .Sizeof (_C_struct___res_state {})))
323
- defer _C_free (unsafe .Pointer (state ))
388
+ var state * _C_struct___res_state
389
+ if unsafe .Sizeof (_C_struct___res_state {}) != 0 {
390
+ state = (* _C_struct___res_state )(_C_malloc (unsafe .Sizeof (_C_struct___res_state {})))
391
+ defer _C_free (unsafe .Pointer (state ))
392
+ * state = _C_struct___res_state {}
393
+ }
394
+
324
395
if err := _C_res_ninit (state ); err != nil {
325
396
return nil , errors .New ("res_ninit failure: " + err .Error ())
326
397
}
327
398
defer _C_res_nclose (state )
328
399
329
- // Some res_nsearch implementations (like macOS) do not set errno.
330
- // They set h_errno, which is not per-thread and useless to us.
331
- // res_nsearch returns the size of the DNS response packet.
332
- // But if the DNS response packet contains failure-like response codes,
333
- // res_search returns -1 even though it has copied the packet into buf,
334
- // giving us no way to find out how big the packet is.
335
- // For now, we are willing to take res_search's word that there's nothing
336
- // useful in the response, even though there *is* a response.
337
400
bufSize := maxDNSPacketSize
338
401
buf := (* _C_uchar )(_C_malloc (uintptr (bufSize )))
339
402
defer _C_free (unsafe .Pointer (buf ))
@@ -345,10 +408,44 @@ func cgoResSearch(hostname string, rtype, class int) ([]dnsmessage.Resource, err
345
408
346
409
var size int
347
410
for {
348
- size , _ = _C_res_nsearch (state , (* _C_char )(unsafe .Pointer (s )), class , rtype , buf , bufSize )
411
+ var herrno int
412
+ var err error
413
+ size , herrno , err = _C_res_nsearch (state , (* _C_char )(unsafe .Pointer (s )), class , rtype , buf , bufSize )
349
414
if size <= 0 || size > 0xffff {
350
- return nil , errors .New ("res_nsearch failure" )
415
+ // Copy from c to go memory.
416
+ msgC := unsafe .Slice ((* byte )(unsafe .Pointer (buf )), bufSize )
417
+ msg := make ([]byte , len (msgC ))
418
+ copy (msg , msgC )
419
+
420
+ // We use -1 to indicate that h_errno is available, -2 otherwise.
421
+ if size == - 1 {
422
+ if herrno == _C_HOST_NOT_FOUND || herrno == _C_NO_DATA {
423
+ return msg , & DNSError {
424
+ Err : errNoSuchHost .Error (),
425
+ IsNotFound : true ,
426
+ isNoData : herrno == _C_NO_DATA ,
427
+ Name : hostname ,
428
+ }
429
+ }
430
+
431
+ if err != nil {
432
+ return msg , & DNSError {
433
+ Err : "dns lookup failure: " + err .Error (),
434
+ IsTemporary : herrno == _C_TRY_AGAIN ,
435
+ Name : hostname ,
436
+ }
437
+ }
438
+
439
+ return msg , & DNSError {
440
+ Err : "dns lookup failure" ,
441
+ IsTemporary : herrno == _C_TRY_AGAIN ,
442
+ Name : hostname ,
443
+ }
444
+ }
445
+
446
+ return msg , errCgoDNSLookupFailed
351
447
}
448
+
352
449
if size <= bufSize {
353
450
break
354
451
}
@@ -359,14 +456,9 @@ func cgoResSearch(hostname string, rtype, class int) ([]dnsmessage.Resource, err
359
456
buf = (* _C_uchar )(_C_malloc (uintptr (bufSize )))
360
457
}
361
458
362
- var p dnsmessage.Parser
363
- if _ , err := p .Start (unsafe .Slice ((* byte )(unsafe .Pointer (buf )), size )); err != nil {
364
- return nil , err
365
- }
366
- p .SkipAllQuestions ()
367
- resources , err := p .AllAnswers ()
368
- if err != nil {
369
- return nil , err
370
- }
371
- return resources , nil
459
+ // Copy from c to go memory.
460
+ msgC := unsafe .Slice ((* byte )(unsafe .Pointer (buf )), size )
461
+ msg := make ([]byte , len (msgC ))
462
+ copy (msg , msgC )
463
+ return msg , nil
372
464
}
0 commit comments