Skip to content

Commit 41bc1ff

Browse files
committed
net: rework CNAME handling
1 parent 352c883 commit 41bc1ff

18 files changed

+477
-126
lines changed

Diff for: src/internal/syscall/unix/net_darwin.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,11 @@ func syscall_syscall6X(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err
119119
//go:linkname syscall_syscall9 syscall.syscall9
120120
func syscall_syscall9(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err syscall.Errno)
121121

122+
// Based on https://opensource.apple.com/source/libresolv/libresolv-65/resolv.h.auto.html
122123
type ResState struct {
123-
unexported [69]uintptr
124+
_ [496]byte
125+
Res_h_errno int32
126+
_ [52]byte
124127
}
125128

126129
//go:cgo_import_dynamic libresolv_res_9_ninit res_9_ninit "/usr/lib/libresolv.9.dylib"

Diff for: src/net/cgo_stub.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,22 @@ func cgoLookupIP(ctx context.Context, network, name string) (addrs []IPAddr, err
3131
panic("cgo stub: cgo not available")
3232
}
3333

34-
func cgoLookupCNAME(ctx context.Context, name string) (cname string, err error, completed bool) {
34+
func cgoLookupCNAME(ctx context.Context, name string) (cname string, err error) {
3535
panic("cgo stub: cgo not available")
3636
}
3737

3838
func cgoLookupPTR(ctx context.Context, addr string) (ptrs []string, err error) {
3939
panic("cgo stub: cgo not available")
4040
}
41+
42+
func cgoLookupCanonicalName(ctx context.Context, network string, name string) (cname string, err error) {
43+
panic("cgo stub: cgo not available")
44+
}
45+
46+
type stubError struct{}
47+
48+
func (stubError) Error() string {
49+
panic("cgo stub: cgo not available")
50+
}
51+
52+
var errCgoDNSLookupFailed = stubError{}

Diff for: src/net/cgo_unix.go

+128-36
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ func cgoLookupServicePort(hints *_C_struct_addrinfo, network, service string) (p
145145
return 0, &DNSError{Err: "unknown port", Name: network + "/" + service, IsNotFound: true}
146146
}
147147

148-
func cgoLookupHostIP(network, name string) (addrs []IPAddr, err error) {
148+
func cgoLookupHostIP(network, name string) (addrs []IPAddr, cname string, err error) {
149149
acquireThread()
150150
defer releaseThread()
151151

@@ -162,7 +162,7 @@ func cgoLookupHostIP(network, name string) (addrs []IPAddr, err error) {
162162

163163
h, err := syscall.BytePtrFromString(name)
164164
if err != nil {
165-
return nil, &DNSError{Err: err.Error(), Name: name}
165+
return nil, "", &DNSError{Err: err.Error(), Name: name}
166166
}
167167
var res *_C_struct_addrinfo
168168
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) {
189189
isTemporary = addrinfoErrno(gerrno).Temporary()
190190
}
191191

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}
193193
}
194194
defer _C_freeaddrinfo(res)
195195

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+
196206
for r := res; r != nil; r = *_C_ai_next(r) {
197207
// We only asked for SOCK_STREAM, but check anyhow.
198208
if *_C_ai_socktype(r) != _C_SOCK_STREAM {
@@ -209,12 +219,13 @@ func cgoLookupHostIP(network, name string) (addrs []IPAddr, err error) {
209219
addrs = append(addrs, addr)
210220
}
211221
}
212-
return addrs, nil
222+
return addrs, cname, nil
213223
}
214224

215225
func cgoLookupIP(ctx context.Context, network, name string) (addrs []IPAddr, err error) {
216226
return doBlockingWithCtx(ctx, func() ([]IPAddr, error) {
217-
return cgoLookupHostIP(network, name)
227+
addrs, _, err := cgoLookupHostIP(network, name)
228+
return addrs, err
218229
})
219230
}
220231

@@ -295,45 +306,97 @@ func cgoSockaddr(ip IP, zone string) (*_C_struct_sockaddr, _C_socklen_t) {
295306
return nil, 0
296307
}
297308

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)
300340
if err != nil {
301-
return
341+
return "", &DNSError{Err: errCannotUnmarshalDNSMessage.Error(), Name: name}
302342
}
303-
cname, err = parseCNAMEFromResources(resources)
343+
344+
q, err := p.Question()
304345
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
306356
}
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
308369
}
309370

371+
// errCgoDNSLookupFailed is returned from resSearch on systems with non thread safe h_errno.
372+
var errCgoDNSLookupFailed = errors.New("res_nsearch lookup failed")
373+
310374
// resSearch will make a call to the 'res_nsearch' routine in the C library
311375
// 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) {
314380
return cgoResSearch(hostname, rtype, class)
315381
})
316382
}
317383

318-
func cgoResSearch(hostname string, rtype, class int) ([]dnsmessage.Resource, error) {
384+
func cgoResSearch(hostname string, rtype, class int) ([]byte, error) {
319385
acquireThread()
320386
defer releaseThread()
321387

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+
324395
if err := _C_res_ninit(state); err != nil {
325396
return nil, errors.New("res_ninit failure: " + err.Error())
326397
}
327398
defer _C_res_nclose(state)
328399

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.
337400
bufSize := maxDNSPacketSize
338401
buf := (*_C_uchar)(_C_malloc(uintptr(bufSize)))
339402
defer _C_free(unsafe.Pointer(buf))
@@ -345,10 +408,44 @@ func cgoResSearch(hostname string, rtype, class int) ([]dnsmessage.Resource, err
345408

346409
var size int
347410
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)
349414
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
351447
}
448+
352449
if size <= bufSize {
353450
break
354451
}
@@ -359,14 +456,9 @@ func cgoResSearch(hostname string, rtype, class int) ([]dnsmessage.Resource, err
359456
buf = (*_C_uchar)(_C_malloc(uintptr(bufSize)))
360457
}
361458

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
372464
}

Diff for: src/net/cgo_unix_cgo.go

+9
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package net
1717
#include <unistd.h>
1818
#include <string.h>
1919
#include <stdlib.h>
20+
#include <netdb.h>
2021
2122
#ifndef EAI_NODATA
2223
#define EAI_NODATA -5
@@ -30,6 +31,12 @@ package net
3031
import "C"
3132
import "unsafe"
3233

34+
const (
35+
_C_HOST_NOT_FOUND = C.HOST_NOT_FOUND
36+
_C_TRY_AGAIN = C.TRY_AGAIN
37+
_C_NO_DATA = C.NO_DATA
38+
)
39+
3340
const (
3441
_C_AF_INET = C.AF_INET
3542
_C_AF_INET6 = C.AF_INET6
@@ -56,10 +63,12 @@ type (
5663
_C_struct_sockaddr = C.struct_sockaddr
5764
)
5865

66+
func _C_GoString(p *_C_char) string { return C.GoString(p) }
5967
func _C_malloc(n uintptr) unsafe.Pointer { return C.malloc(C.size_t(n)) }
6068
func _C_free(p unsafe.Pointer) { C.free(p) }
6169

6270
func _C_ai_addr(ai *_C_struct_addrinfo) **_C_struct_sockaddr { return &ai.ai_addr }
71+
func _C_ai_canonname(ai *_C_struct_addrinfo) **_C_char { return &ai.ai_canonname }
6372
func _C_ai_family(ai *_C_struct_addrinfo) *_C_int { return &ai.ai_family }
6473
func _C_ai_flags(ai *_C_struct_addrinfo) *_C_int { return &ai.ai_flags }
6574
func _C_ai_next(ai *_C_struct_addrinfo) **_C_struct_addrinfo { return &ai.ai_next }

Diff for: src/net/cgo_unix_cgo_darwin.go

+10
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,13 @@ import (
1919
// This will cause a compile error when the size of
2020
// unix.ResState is too small.
2121
type _ [unsafe.Sizeof(unix.ResState{}) - unsafe.Sizeof(C.struct___res_state{})]byte
22+
23+
// This will cause a compile error when:
24+
// unsafe.Sizeof(new(unix.ResState).Res_h_errno) != unsafe.Sizeof(new(C.struct___res_state).res_h_errno)
25+
type _ [unsafe.Sizeof(new(unix.ResState).Res_h_errno) - unsafe.Sizeof(new(C.struct___res_state).res_h_errno)]byte
26+
type _ [unsafe.Sizeof(new(C.struct___res_state).res_h_errno) - unsafe.Sizeof(new(unix.ResState).Res_h_errno)]byte
27+
28+
// This will cause a compile error when:
29+
// unsafe.Offsetof(new(unix.ResState).Res_h_errno) != unsafe.Offsetof(new(C.struct___res_state).res_h_errno)
30+
type _ [unsafe.Offsetof(new(unix.ResState).Res_h_errno) - unsafe.Offsetof(new(C.struct___res_state).res_h_errno)]byte
31+
type _ [unsafe.Offsetof(new(C.struct___res_state).res_h_errno) - unsafe.Offsetof(new(unix.ResState).Res_h_errno)]byte

Diff for: src/net/cgo_unix_cgo_res.go

+44-3
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,30 @@ package net
1717
#include <string.h>
1818
#include <arpa/nameser.h>
1919
#include <resolv.h>
20+
#include <netdb.h>
21+
22+
#ifdef __GLIBC__
23+
#define is_glibc 1
24+
#else
25+
#define is_glibc 0
26+
#endif
27+
28+
int c_res_search(const char *dname, int class, int type, unsigned char *answer, int anslen, int *herrno) {
29+
int ret = res_search(dname, class, type, answer, anslen);
30+
31+
if (ret < 0) {
32+
*herrno = h_errno;
33+
}
34+
35+
return ret;
36+
}
2037
2138
#cgo !android,!openbsd LDFLAGS: -lresolv
2239
*/
2340
import "C"
2441

42+
import "runtime"
43+
2544
type _C_struct___res_state = struct{}
2645

2746
func _C_res_ninit(state *_C_struct___res_state) error {
@@ -32,7 +51,29 @@ func _C_res_nclose(state *_C_struct___res_state) {
3251
return
3352
}
3453

35-
func _C_res_nsearch(state *_C_struct___res_state, dname *_C_char, class, typ int, ans *_C_uchar, anslen int) (int, error) {
36-
x, err := C.res_search(dname, C.int(class), C.int(typ), ans, C.int(anslen))
37-
return int(x), err
54+
const isGlibc = C.is_glibc == 1
55+
56+
func _C_res_nsearch(state *_C_struct___res_state, dname *_C_char, class, typ int, ans *_C_uchar, anslen int) (ret int, herrno int, err error) {
57+
var h C.int
58+
x, err := C.c_res_search(dname, C.int(class), C.int(typ), ans, C.int(anslen), &h)
59+
60+
if x <= 0 {
61+
if runtime.GOOS == "linux" {
62+
// On glibc and musl h_errno is a thread-safe macro:
63+
// https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=ed4d0184a16db455d626e64722daf9ca4b71742a;hp=c0b338331e8eb0979b909479d6aa9fd1cddd63ec
64+
// http://git.etalabs.net/cgit/musl/commit/?id=9d0b8b92a508c328e7eac774847f001f80dfb5ff
65+
if isGlibc {
66+
return -1, int(h), err
67+
}
68+
// musl does not set errno with the cause of the failure.
69+
return -1, int(h), nil
70+
}
71+
72+
// On Openbsd h_errno is not thread-safe.
73+
// Android h_errno is also thread-safe: https://android.googlesource.com/platform/bionic/+/589afca/libc/dns/resolv/res_state.c
74+
// but the h_errno doesn't seem to be set on noSuchHost.
75+
return -2, 0, nil
76+
}
77+
78+
return int(x), 0, nil
3879
}

0 commit comments

Comments
 (0)