Skip to content

Commit

Permalink
net: use libSystem bindings for DNS resolution on macos if cgo is una…
Browse files Browse the repository at this point in the history
…vailable

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 <[email protected]>
TryBot-Result: Gobot Gobot <[email protected]>
Reviewed-by: Keith Randall <[email protected]>
  • Loading branch information
grantseltzer authored and bradfitz committed Apr 4, 2019
1 parent bead358 commit f6b42a5
Show file tree
Hide file tree
Showing 8 changed files with 386 additions and 0 deletions.
209 changes: 209 additions & 0 deletions src/net/cgo_darwin_stub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build !netgo,!cgo
// +build darwin

package net

import (
"context"
"errors"
"sync"

"golang.org/x/net/dns/dnsmessage"
)

type addrinfoErrno int

func (eai addrinfoErrno) Error() string { return "<nil>" }
func (eai addrinfoErrno) Temporary() bool { return false }
func (eai addrinfoErrno) Timeout() bool { return false }

func cgoLookupHost(ctx context.Context, name string) (addrs []string, err error, completed bool) {
resources, err := resolverGetResources(ctx, name, int32(dnsmessage.TypeALL), int32(dnsmessage.ClassINET))
if err != nil {
return
}
addrs, err = parseHostsFromResources(resources)
if err != nil {
return
}
return addrs, nil, true
}

func cgoLookupPort(ctx context.Context, network, service string) (port int, err error, completed bool) {
port, err = goLookupPort(network, service) // we can just use netgo lookup
return port, err, err == nil
}

func cgoLookupIP(ctx context.Context, network, name string) (addrs []IPAddr, err error, completed bool) {

var resources []dnsmessage.Resource
switch ipVersion(network) {
case '4':
resources, err = resolverGetResources(ctx, name, int32(dnsmessage.TypeA), int32(dnsmessage.ClassINET))
case '6':
resources, err = resolverGetResources(ctx, name, int32(dnsmessage.TypeAAAA), int32(dnsmessage.ClassINET))
default:
resources, err = resolverGetResources(ctx, name, int32(dnsmessage.TypeALL), int32(dnsmessage.ClassINET))
}
if err != nil {
return
}

addrs, err = parseIPsFromResources(resources)
if err != nil {
return
}

return addrs, nil, true
}

func cgoLookupCNAME(ctx context.Context, name string) (cname string, err error, completed bool) {
resources, err := resolverGetResources(ctx, name, int32(dnsmessage.TypeCNAME), int32(dnsmessage.ClassINET))
if err != nil {
return
}
cname, err = parseCNAMEFromResources(resources)
if err != nil {
return "", err, false
}
return cname, nil, true
}

func cgoLookupPTR(ctx context.Context, addr string) (ptrs []string, err error, completed bool) {
resources, err := resolverGetResources(ctx, addr, int32(dnsmessage.TypePTR), int32(dnsmessage.ClassINET))
if err != nil {
return
}
ptrs, err = parsePTRsFromResources(resources)
if err != nil {
return
}
return ptrs, nil, true
}

var (
resInitOnce sync.Once
errCode int32
)

// resolverGetResources will make a call to the 'res_search' routine in libSystem
// and parse the output as a slice of resource resources which can then be parsed
func resolverGetResources(ctx context.Context, hostname string, rtype, class int32) ([]dnsmessage.Resource, error) {

resInitOnce.Do(func() {
errCode = res_init()
})
if errCode < 0 {
return nil, errors.New("could not initialize name resolver data")
}

var byteHostname = []byte(hostname)
var responseBuffer [512]byte
var size int32

size, errCode = res_search(&byteHostname[0], class, rtype, &responseBuffer[0], int32(len(responseBuffer)))
if errCode != 0 {
return nil, errors.New("could not complete domain resolution return code " + string(errCode))
}
if size == 0 {
return nil, errors.New("received empty response")
}

var msg dnsmessage.Message
err := msg.Unpack(responseBuffer[:])
if err != nil {
return nil, err
}

var dnsParser dnsmessage.Parser
if _, err := dnsParser.Start(responseBuffer[:]); err != nil {
return nil, err
}

var resources []dnsmessage.Resource
for {
r, err := dnsParser.Answer()
if err == dnsmessage.ErrSectionDone {
break
}
if err != nil {
return nil, err
}
resources = append(resources, r)
}
return resources, nil
}

func parseHostsFromResources(resources []dnsmessage.Resource) ([]string, error) {
var answers []string

for i := range resources {
switch resources[i].Header.Type {
case dnsmessage.TypeA:
b := resources[i].Body.(*dnsmessage.AResource)
answers = append(answers, string(b.A[:]))
case dnsmessage.TypeAAAA:
b := resources[i].Body.(*dnsmessage.AAAAResource)
answers = append(answers, string(b.AAAA[:]))
default:
return nil, errors.New("could not parse an A or AAAA response from message buffer")
}
}
return answers, nil
}

func parseIPsFromResources(resources []dnsmessage.Resource) ([]IPAddr, error) {
var answers []IPAddr

for i := range resources {
switch resources[i].Header.Type {
case dnsmessage.TypeA:
b := resources[i].Body.(*dnsmessage.AResource)
ip := parseIPv4(string(b.A[:]))
answers = append(answers, IPAddr{IP: ip})
case dnsmessage.TypeAAAA:
b := resources[i].Body.(*dnsmessage.AAAAResource)
ip, zone := parseIPv6Zone(string(b.AAAA[:]))
answers = append(answers, IPAddr{IP: ip, Zone: zone})
default:
return nil, errors.New("could not parse an A or AAAA response from message buffer")
}
}
return answers, nil
}

func parseCNAMEFromResources(resources []dnsmessage.Resource) (string, error) {
if len(resources) == 0 {
return "", errors.New("no CNAME record received")
}
c, ok := resources[0].Body.(*dnsmessage.CNAMEResource)
if !ok {
return "", errors.New("could not parse CNAME record")
}
return c.CNAME.String(), nil
}

func parsePTRsFromResources(resources []dnsmessage.Resource) ([]string, error) {
var answers []string
for i := range resources {
switch resources[i].Header.Type {
case dnsmessage.TypePTR:
p := resources[0].Body.(*dnsmessage.PTRResource)
answers = append(answers, p.PTR.String())
default:
return nil, errors.New("could not parse a PTR response from message buffer")

}
}
return answers, nil
}

// res_init and res_search are defined in runtime/lookup_darwin.go

func res_init() int32

func res_search(dname *byte, class int32, rtype int32, answer *byte, anslen int32) (int32, int32)
1 change: 1 addition & 0 deletions src/net/cgo_stub.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// license that can be found in the LICENSE file.

// +build !cgo netgo
// +build !darwin

package net

Expand Down
5 changes: 5 additions & 0 deletions src/net/conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ func initConfVal() {
// their own DNS requests. So always use cgo instead, which
// avoids that.
if runtime.GOOS == "darwin" {
// Normally we force netGo to be true if building without cgo enabled.
// On Darwin, we can call libc even if cgo is not enabled, so only set netGo to true
// if explicitly requested.
confVal.netGo = dnsMode == "go"

confVal.forceCgoLookupHost = true
return
}
Expand Down
35 changes: 35 additions & 0 deletions src/runtime/lookup_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package runtime

import (
"unsafe"
)

//go:linkname res_init net.res_init
//go:nosplit
//go:cgo_unsafe_args
func res_init() int32 {
return libcCall(unsafe.Pointer(funcPC(res_init_trampoline)), nil)
}
func res_init_trampoline()

//go:linkname res_search net.res_search
//go:nosplit
//go:cgo_unsafe_args
func res_search(dname *byte, class int32, rtype int32, answer *byte, anslen int32) (int32, int32) {
args := struct {
dname *byte
class, rtype int32
answer *byte
anslen, retSize, retErr int32
}{dname, class, rtype, answer, anslen, 0, 0}
libcCall(unsafe.Pointer(funcPC(res_search_trampoline)), unsafe.Pointer(&args))
return args.retSize, args.retErr
}
func res_search_trampoline()

//go:cgo_import_dynamic libc_res_search res_search "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic libc_res_init res_init "/usr/lib/libSystem.B.dylib"
50 changes: 50 additions & 0 deletions src/runtime/lookup_darwin_386.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#include "go_asm.h"
#include "go_tls.h"
#include "textflag.h"

TEXT runtime·res_init_trampoline(SB),NOSPLIT,$0
PUSHL BP
MOVL SP, BP
SUBL $8, SP
CALL libc_res_init(SB)
CMPL AX, $-1
JNE ok
CALL libc_error(SB)
ok:
MOVL BP, SP
POPL BP
RET

TEXT runtime·res_search_trampoline(SB),NOSPLIT,$0
PUSHL BP
MOVL SP, BP
SUBL $24, SP
MOVL 32(SP), CX
MOVL 16(CX), AX // arg 5 anslen
MOVL AX, 16(SP)
MOVL 12(CX), AX // arg 4 answer
MOVL AX, 12(SP)
MOVL 8(CX), AX // arg 3 type
MOVL AX, 8(SP)
MOVL 4(CX), AX // arg 2 class
MOVL AX, 4(SP)
MOVL 0(CX), AX // arg 1 name
MOVL AX, 0(SP)
CALL libc_res_search(SB)
XORL DX, DX
CMPL AX, $-1
JNE ok
CALL libc_error(SB)
MOVL (AX), DX
XORL AX, AX
ok:
MOVL 32(SP), CX
MOVL AX, 20(CX)
MOVL DX, 24(CX)
MOVL BP, SP
POPL BP
RET
40 changes: 40 additions & 0 deletions src/runtime/lookup_darwin_amd64.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#include "go_asm.h"
#include "go_tls.h"
#include "textflag.h"

TEXT runtime·res_init_trampoline(SB),NOSPLIT,$0
PUSHQ BP
MOVQ SP, BP
CALL libc_res_init(SB)
CMPQ AX, $-1
JNE ok
CALL libc_error(SB)
ok:
POPQ BP
RET

TEXT runtime·res_search_trampoline(SB),NOSPLIT,$0
PUSHQ BP
MOVQ SP, BP
MOVQ DI, BX // move DI into BX to preserve struct addr
MOVL 24(BX), R8 // arg 5 anslen
MOVQ 16(BX), CX // arg 4 answer
MOVL 12(BX), DX // arg 3 type
MOVL 8(BX), SI // arg 2 class
MOVQ 0(BX), DI // arg 1 name
CALL libc_res_search(SB)
XORL DX, DX
CMPQ AX, $-1
JNE ok
CALL libc_error(SB)
MOVLQSX (AX), DX // move return from libc_error into DX
XORL AX, AX // size on error is 0
ok:
MOVQ AX, 28(BX) // size
MOVQ DX, 32(BX) // error code
POPQ BP
RET
25 changes: 25 additions & 0 deletions src/runtime/lookup_darwin_arm.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// System calls and other sys.stuff for ARM64, Darwin
// System calls are implemented in libSystem, this file contains
// trampolines that convert from Go to C calling convention.

#include "go_asm.h"
#include "go_tls.h"
#include "textflag.h"

// On darwin/arm, the runtime always uses runtime/cgo
// for resolution. This will just exit with a nominal
// exit code.

TEXT runtime·res_search_trampoline(SB),NOSPLIT,$0
MOVW $90, R0
BL libc_exit(SB)
RET

TEXT runtime·res_init_trampoline(SB),NOSPLIT,$0
MOVW $91, R0
BL libc_exit(SB)
RET
Loading

0 comments on commit f6b42a5

Please sign in to comment.