Skip to content

Commit f6b42a5

Browse files
grantseltzerbradfitz
authored andcommitted
net: use libSystem bindings for DNS resolution on macos if cgo is unavailable
This change adds directives to link the res_search function in libSystem. The corresponding Go function is then used in `lookup_darwin.go` for resolution when cgo is disabled. This makes DNS resolution logic more reliable as macOS has some unique quirks such as the `/etc/resolver/` directory for specifying nameservers. Fixes #12524 Change-Id: I367263c4951383965b3ef6491196152f78e614b1 GitHub-Last-Rev: 3c3ff6b GitHub-Pull-Request: #30686 Reviewed-on: https://go-review.googlesource.com/c/go/+/166297 Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Keith Randall <khr@golang.org>
1 parent bead358 commit f6b42a5

8 files changed

Lines changed: 386 additions & 0 deletions

File tree

src/net/cgo_darwin_stub.go

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
// Copyright 2019 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// +build !netgo,!cgo
6+
// +build darwin
7+
8+
package net
9+
10+
import (
11+
"context"
12+
"errors"
13+
"sync"
14+
15+
"golang.org/x/net/dns/dnsmessage"
16+
)
17+
18+
type addrinfoErrno int
19+
20+
func (eai addrinfoErrno) Error() string { return "<nil>" }
21+
func (eai addrinfoErrno) Temporary() bool { return false }
22+
func (eai addrinfoErrno) Timeout() bool { return false }
23+
24+
func cgoLookupHost(ctx context.Context, name string) (addrs []string, err error, completed bool) {
25+
resources, err := resolverGetResources(ctx, name, int32(dnsmessage.TypeALL), int32(dnsmessage.ClassINET))
26+
if err != nil {
27+
return
28+
}
29+
addrs, err = parseHostsFromResources(resources)
30+
if err != nil {
31+
return
32+
}
33+
return addrs, nil, true
34+
}
35+
36+
func cgoLookupPort(ctx context.Context, network, service string) (port int, err error, completed bool) {
37+
port, err = goLookupPort(network, service) // we can just use netgo lookup
38+
return port, err, err == nil
39+
}
40+
41+
func cgoLookupIP(ctx context.Context, network, name string) (addrs []IPAddr, err error, completed bool) {
42+
43+
var resources []dnsmessage.Resource
44+
switch ipVersion(network) {
45+
case '4':
46+
resources, err = resolverGetResources(ctx, name, int32(dnsmessage.TypeA), int32(dnsmessage.ClassINET))
47+
case '6':
48+
resources, err = resolverGetResources(ctx, name, int32(dnsmessage.TypeAAAA), int32(dnsmessage.ClassINET))
49+
default:
50+
resources, err = resolverGetResources(ctx, name, int32(dnsmessage.TypeALL), int32(dnsmessage.ClassINET))
51+
}
52+
if err != nil {
53+
return
54+
}
55+
56+
addrs, err = parseIPsFromResources(resources)
57+
if err != nil {
58+
return
59+
}
60+
61+
return addrs, nil, true
62+
}
63+
64+
func cgoLookupCNAME(ctx context.Context, name string) (cname string, err error, completed bool) {
65+
resources, err := resolverGetResources(ctx, name, int32(dnsmessage.TypeCNAME), int32(dnsmessage.ClassINET))
66+
if err != nil {
67+
return
68+
}
69+
cname, err = parseCNAMEFromResources(resources)
70+
if err != nil {
71+
return "", err, false
72+
}
73+
return cname, nil, true
74+
}
75+
76+
func cgoLookupPTR(ctx context.Context, addr string) (ptrs []string, err error, completed bool) {
77+
resources, err := resolverGetResources(ctx, addr, int32(dnsmessage.TypePTR), int32(dnsmessage.ClassINET))
78+
if err != nil {
79+
return
80+
}
81+
ptrs, err = parsePTRsFromResources(resources)
82+
if err != nil {
83+
return
84+
}
85+
return ptrs, nil, true
86+
}
87+
88+
var (
89+
resInitOnce sync.Once
90+
errCode int32
91+
)
92+
93+
// resolverGetResources will make a call to the 'res_search' routine in libSystem
94+
// and parse the output as a slice of resource resources which can then be parsed
95+
func resolverGetResources(ctx context.Context, hostname string, rtype, class int32) ([]dnsmessage.Resource, error) {
96+
97+
resInitOnce.Do(func() {
98+
errCode = res_init()
99+
})
100+
if errCode < 0 {
101+
return nil, errors.New("could not initialize name resolver data")
102+
}
103+
104+
var byteHostname = []byte(hostname)
105+
var responseBuffer [512]byte
106+
var size int32
107+
108+
size, errCode = res_search(&byteHostname[0], class, rtype, &responseBuffer[0], int32(len(responseBuffer)))
109+
if errCode != 0 {
110+
return nil, errors.New("could not complete domain resolution return code " + string(errCode))
111+
}
112+
if size == 0 {
113+
return nil, errors.New("received empty response")
114+
}
115+
116+
var msg dnsmessage.Message
117+
err := msg.Unpack(responseBuffer[:])
118+
if err != nil {
119+
return nil, err
120+
}
121+
122+
var dnsParser dnsmessage.Parser
123+
if _, err := dnsParser.Start(responseBuffer[:]); err != nil {
124+
return nil, err
125+
}
126+
127+
var resources []dnsmessage.Resource
128+
for {
129+
r, err := dnsParser.Answer()
130+
if err == dnsmessage.ErrSectionDone {
131+
break
132+
}
133+
if err != nil {
134+
return nil, err
135+
}
136+
resources = append(resources, r)
137+
}
138+
return resources, nil
139+
}
140+
141+
func parseHostsFromResources(resources []dnsmessage.Resource) ([]string, error) {
142+
var answers []string
143+
144+
for i := range resources {
145+
switch resources[i].Header.Type {
146+
case dnsmessage.TypeA:
147+
b := resources[i].Body.(*dnsmessage.AResource)
148+
answers = append(answers, string(b.A[:]))
149+
case dnsmessage.TypeAAAA:
150+
b := resources[i].Body.(*dnsmessage.AAAAResource)
151+
answers = append(answers, string(b.AAAA[:]))
152+
default:
153+
return nil, errors.New("could not parse an A or AAAA response from message buffer")
154+
}
155+
}
156+
return answers, nil
157+
}
158+
159+
func parseIPsFromResources(resources []dnsmessage.Resource) ([]IPAddr, error) {
160+
var answers []IPAddr
161+
162+
for i := range resources {
163+
switch resources[i].Header.Type {
164+
case dnsmessage.TypeA:
165+
b := resources[i].Body.(*dnsmessage.AResource)
166+
ip := parseIPv4(string(b.A[:]))
167+
answers = append(answers, IPAddr{IP: ip})
168+
case dnsmessage.TypeAAAA:
169+
b := resources[i].Body.(*dnsmessage.AAAAResource)
170+
ip, zone := parseIPv6Zone(string(b.AAAA[:]))
171+
answers = append(answers, IPAddr{IP: ip, Zone: zone})
172+
default:
173+
return nil, errors.New("could not parse an A or AAAA response from message buffer")
174+
}
175+
}
176+
return answers, nil
177+
}
178+
179+
func parseCNAMEFromResources(resources []dnsmessage.Resource) (string, error) {
180+
if len(resources) == 0 {
181+
return "", errors.New("no CNAME record received")
182+
}
183+
c, ok := resources[0].Body.(*dnsmessage.CNAMEResource)
184+
if !ok {
185+
return "", errors.New("could not parse CNAME record")
186+
}
187+
return c.CNAME.String(), nil
188+
}
189+
190+
func parsePTRsFromResources(resources []dnsmessage.Resource) ([]string, error) {
191+
var answers []string
192+
for i := range resources {
193+
switch resources[i].Header.Type {
194+
case dnsmessage.TypePTR:
195+
p := resources[0].Body.(*dnsmessage.PTRResource)
196+
answers = append(answers, p.PTR.String())
197+
default:
198+
return nil, errors.New("could not parse a PTR response from message buffer")
199+
200+
}
201+
}
202+
return answers, nil
203+
}
204+
205+
// res_init and res_search are defined in runtime/lookup_darwin.go
206+
207+
func res_init() int32
208+
209+
func res_search(dname *byte, class int32, rtype int32, answer *byte, anslen int32) (int32, int32)

src/net/cgo_stub.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// license that can be found in the LICENSE file.
44

55
// +build !cgo netgo
6+
// +build !darwin
67

78
package net
89

src/net/conf.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ func initConfVal() {
7070
// their own DNS requests. So always use cgo instead, which
7171
// avoids that.
7272
if runtime.GOOS == "darwin" {
73+
// Normally we force netGo to be true if building without cgo enabled.
74+
// On Darwin, we can call libc even if cgo is not enabled, so only set netGo to true
75+
// if explicitly requested.
76+
confVal.netGo = dnsMode == "go"
77+
7378
confVal.forceCgoLookupHost = true
7479
return
7580
}

src/runtime/lookup_darwin.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2019 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package runtime
6+
7+
import (
8+
"unsafe"
9+
)
10+
11+
//go:linkname res_init net.res_init
12+
//go:nosplit
13+
//go:cgo_unsafe_args
14+
func res_init() int32 {
15+
return libcCall(unsafe.Pointer(funcPC(res_init_trampoline)), nil)
16+
}
17+
func res_init_trampoline()
18+
19+
//go:linkname res_search net.res_search
20+
//go:nosplit
21+
//go:cgo_unsafe_args
22+
func res_search(dname *byte, class int32, rtype int32, answer *byte, anslen int32) (int32, int32) {
23+
args := struct {
24+
dname *byte
25+
class, rtype int32
26+
answer *byte
27+
anslen, retSize, retErr int32
28+
}{dname, class, rtype, answer, anslen, 0, 0}
29+
libcCall(unsafe.Pointer(funcPC(res_search_trampoline)), unsafe.Pointer(&args))
30+
return args.retSize, args.retErr
31+
}
32+
func res_search_trampoline()
33+
34+
//go:cgo_import_dynamic libc_res_search res_search "/usr/lib/libSystem.B.dylib"
35+
//go:cgo_import_dynamic libc_res_init res_init "/usr/lib/libSystem.B.dylib"

src/runtime/lookup_darwin_386.s

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2019 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
#include "go_asm.h"
6+
#include "go_tls.h"
7+
#include "textflag.h"
8+
9+
TEXT runtime·res_init_trampoline(SB),NOSPLIT,$0
10+
PUSHL BP
11+
MOVL SP, BP
12+
SUBL $8, SP
13+
CALL libc_res_init(SB)
14+
CMPL AX, $-1
15+
JNE ok
16+
CALL libc_error(SB)
17+
ok:
18+
MOVL BP, SP
19+
POPL BP
20+
RET
21+
22+
TEXT runtime·res_search_trampoline(SB),NOSPLIT,$0
23+
PUSHL BP
24+
MOVL SP, BP
25+
SUBL $24, SP
26+
MOVL 32(SP), CX
27+
MOVL 16(CX), AX // arg 5 anslen
28+
MOVL AX, 16(SP)
29+
MOVL 12(CX), AX // arg 4 answer
30+
MOVL AX, 12(SP)
31+
MOVL 8(CX), AX // arg 3 type
32+
MOVL AX, 8(SP)
33+
MOVL 4(CX), AX // arg 2 class
34+
MOVL AX, 4(SP)
35+
MOVL 0(CX), AX // arg 1 name
36+
MOVL AX, 0(SP)
37+
CALL libc_res_search(SB)
38+
XORL DX, DX
39+
CMPL AX, $-1
40+
JNE ok
41+
CALL libc_error(SB)
42+
MOVL (AX), DX
43+
XORL AX, AX
44+
ok:
45+
MOVL 32(SP), CX
46+
MOVL AX, 20(CX)
47+
MOVL DX, 24(CX)
48+
MOVL BP, SP
49+
POPL BP
50+
RET

src/runtime/lookup_darwin_amd64.s

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2019 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
#include "go_asm.h"
6+
#include "go_tls.h"
7+
#include "textflag.h"
8+
9+
TEXT runtime·res_init_trampoline(SB),NOSPLIT,$0
10+
PUSHQ BP
11+
MOVQ SP, BP
12+
CALL libc_res_init(SB)
13+
CMPQ AX, $-1
14+
JNE ok
15+
CALL libc_error(SB)
16+
ok:
17+
POPQ BP
18+
RET
19+
20+
TEXT runtime·res_search_trampoline(SB),NOSPLIT,$0
21+
PUSHQ BP
22+
MOVQ SP, BP
23+
MOVQ DI, BX // move DI into BX to preserve struct addr
24+
MOVL 24(BX), R8 // arg 5 anslen
25+
MOVQ 16(BX), CX // arg 4 answer
26+
MOVL 12(BX), DX // arg 3 type
27+
MOVL 8(BX), SI // arg 2 class
28+
MOVQ 0(BX), DI // arg 1 name
29+
CALL libc_res_search(SB)
30+
XORL DX, DX
31+
CMPQ AX, $-1
32+
JNE ok
33+
CALL libc_error(SB)
34+
MOVLQSX (AX), DX // move return from libc_error into DX
35+
XORL AX, AX // size on error is 0
36+
ok:
37+
MOVQ AX, 28(BX) // size
38+
MOVQ DX, 32(BX) // error code
39+
POPQ BP
40+
RET

src/runtime/lookup_darwin_arm.s

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2015 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// System calls and other sys.stuff for ARM64, Darwin
6+
// System calls are implemented in libSystem, this file contains
7+
// trampolines that convert from Go to C calling convention.
8+
9+
#include "go_asm.h"
10+
#include "go_tls.h"
11+
#include "textflag.h"
12+
13+
// On darwin/arm, the runtime always uses runtime/cgo
14+
// for resolution. This will just exit with a nominal
15+
// exit code.
16+
17+
TEXT runtime·res_search_trampoline(SB),NOSPLIT,$0
18+
MOVW $90, R0
19+
BL libc_exit(SB)
20+
RET
21+
22+
TEXT runtime·res_init_trampoline(SB),NOSPLIT,$0
23+
MOVW $91, R0
24+
BL libc_exit(SB)
25+
RET

0 commit comments

Comments
 (0)