]> Cypherpunks repositories - gostls13.git/commitdiff
net: use libSystem bindings for DNS resolution on macos if cgo is unavailable
authorgrant <grantseltzer@gmail.com>
Wed, 3 Apr 2019 14:21:21 +0000 (14:21 +0000)
committerBrad Fitzpatrick <bradfitz@golang.org>
Thu, 4 Apr 2019 19:36:23 +0000 (19:36 +0000)
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: 3c3ff6bfa7e4811f206f3b119a867c841a016e10
GitHub-Pull-Request: golang/go#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>
src/net/cgo_darwin_stub.go [new file with mode: 0644]
src/net/cgo_stub.go
src/net/conf.go
src/runtime/lookup_darwin.go [new file with mode: 0644]
src/runtime/lookup_darwin_386.s [new file with mode: 0644]
src/runtime/lookup_darwin_amd64.s [new file with mode: 0644]
src/runtime/lookup_darwin_arm.s [new file with mode: 0644]
src/runtime/lookup_darwin_arm64.s [new file with mode: 0644]

diff --git a/src/net/cgo_darwin_stub.go b/src/net/cgo_darwin_stub.go
new file mode 100644 (file)
index 0000000..544df7f
--- /dev/null
@@ -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)
index 041f8af12966a4363c3c4822061f7b956e478b40..aa15ab4dc1043d4a48fbd3eba84f594924ceb4f1 100644 (file)
@@ -3,6 +3,7 @@
 // license that can be found in the LICENSE file.
 
 // +build !cgo netgo
+// +build !darwin
 
 package net
 
index 971b1a399a1bb842c89deb90c13932b8be790caa..1c88f096bad33185a154689481b056c3358d0035 100644 (file)
@@ -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
        }
diff --git a/src/runtime/lookup_darwin.go b/src/runtime/lookup_darwin.go
new file mode 100644 (file)
index 0000000..c39b937
--- /dev/null
@@ -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"
diff --git a/src/runtime/lookup_darwin_386.s b/src/runtime/lookup_darwin_386.s
new file mode 100644 (file)
index 0000000..4995e51
--- /dev/null
@@ -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
diff --git a/src/runtime/lookup_darwin_amd64.s b/src/runtime/lookup_darwin_amd64.s
new file mode 100644 (file)
index 0000000..bfe70c2
--- /dev/null
@@ -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
diff --git a/src/runtime/lookup_darwin_arm.s b/src/runtime/lookup_darwin_arm.s
new file mode 100644 (file)
index 0000000..bf69d21
--- /dev/null
@@ -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
diff --git a/src/runtime/lookup_darwin_arm64.s b/src/runtime/lookup_darwin_arm64.s
new file mode 100644 (file)
index 0000000..31061e1
--- /dev/null
@@ -0,0 +1,21 @@
+// 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"
+
+// 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