// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+// This file is intended to be used in non-cgo builds of darwin binaries,
+// in particular when cross-compiling a darwin binary from a non-darwin machine.
+// All OS calls on darwin have to be done via C libraries, and this code makes such
+// calls with the runtime's help. (It is a C call but does not require the cgo tool to
+// be compiled, and as such it is possible to build even when cross-compiling.)
+//
+// The specific C library calls are to res_init and res_search from /usr/lib/system/libsystem_info.dylib.
+// Unfortunately, an ordinary C program calling these names would actually end up with
+// res_9_init and res_9_search from /usr/lib/libresolv.dylib, not libsystem_info.
+// It may well be that the libsystem_info routines are completely unused on macOS systems
+// except for this code. At the least, they have the following problems:
+//
+// - TypeALL requests do not work, so if we want both IPv4 and IPv6 addresses,
+// we have to do two requests, one for TypeA and one for TypeAAAA.
+// - TypeCNAME requests hang indefinitely.
+// - TypePTR requests fail unconditionally.
+// - Detailed error information is stored in the global h_errno value,
+// which cannot be accessed safely (it is not per-thread like errno).
+// - The routines may not be safe to call from multiple threads.
+// If you run net.test under lldb, that emits syslog prints to stderr
+// that suggest double-free problems. (If not running under lldb,
+// it is unclear where the syslog prints go, if anywhere.)
+//
+// This code is marked for deletion. If it is to be revived, it should be changed to use
+// res_9_init and res_9_search from libresolv and special care should be paid to
+// error detail and thread safety.
+
// +build !netgo,!cgo
// +build darwin
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
+ // The 4-suffix indicates IPv4, TypeA lookups.
+ // The 6-suffix indicates IPv6, TypeAAAA lookups.
+ // If resSearch is updated to call the libresolv res_9_search (see comment at top of file),
+ // it may be possible to make one call for TypeALL
+ // and get both address kinds out.
+ r4, err4 := resSearch(ctx, name, int32(dnsmessage.TypeA), int32(dnsmessage.ClassINET))
+ if err4 == nil {
+ addrs, err4 = appendHostsFromResources(addrs, r4)
+ }
+ r6, err6 := resSearch(ctx, name, int32(dnsmessage.TypeAAAA), int32(dnsmessage.ClassINET))
+ if err6 == nil {
+ addrs, err6 = appendHostsFromResources(addrs, r6)
+ }
+ if err4 != nil && err6 != nil {
+ return nil, err4, false
}
return addrs, nil, true
}
}
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))
+ // The 4-suffix indicates IPv4, TypeA lookups.
+ // The 6-suffix indicates IPv6, TypeAAAA lookups.
+ // If resSearch is updated to call the libresolv res_9_search (see comment at top of file),
+ // it may be possible to make one call for TypeALL (when vers != '6' and vers != '4')
+ // and get both address kinds out.
+ var r4, r6 []dnsmessage.Resource
+ var err4, err6 error
+ vers := ipVersion(network)
+ if vers != '6' {
+ r4, err4 = resSearch(ctx, name, int32(dnsmessage.TypeA), int32(dnsmessage.ClassINET))
+ if err4 == nil {
+ addrs, err4 = appendIPsFromResources(addrs, r4)
+ }
}
- if err != nil {
- return
+ if vers != '4' {
+ r6, err6 = resSearch(ctx, name, int32(dnsmessage.TypeAAAA), int32(dnsmessage.ClassINET))
+ if err6 == nil {
+ addrs, err6 = appendIPsFromResources(addrs, r6)
+ }
}
-
- addrs, err = parseIPsFromResources(resources)
- if err != nil {
- return
+ if err4 != nil && err6 != nil {
+ return nil, err4, false
}
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))
+ resources, err := resSearch(ctx, name, int32(dnsmessage.TypeCNAME), int32(dnsmessage.ClassINET))
if err != nil {
return
}
}
func cgoLookupPTR(ctx context.Context, addr string) (ptrs []string, err error, completed bool) {
- resources, err := resolverGetResources(ctx, addr, int32(dnsmessage.TypePTR), int32(dnsmessage.ClassINET))
+ resources, err := resSearch(ctx, addr, int32(dnsmessage.TypePTR), int32(dnsmessage.ClassINET))
if err != nil {
return
}
}
var (
- resInitOnce sync.Once
- errCode int32
+ resInitOnce sync.Once
+ resInitResult int32
)
-// resolverGetResources will make a call to the 'res_search' routine in libSystem
+// resSearch 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) {
-
+func resSearch(ctx context.Context, hostname string, rtype, class int32) ([]dnsmessage.Resource, error) {
+ // We have to use res_init and res_search, but these do not set errno on failure.
+ // (They set h_errno, which is a global int shared by all threads and therefore
+ // racy to use.)
+ // https://opensource.apple.com/source/Libinfo/Libinfo-517.200.9/dns.subproj/res_query.c.auto.html
resInitOnce.Do(func() {
- errCode = res_init()
+ resInitResult = 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 {
+ if resInitResult < 0 {
+ return nil, errors.New("res_init failure")
+ }
+
+ // res_search does not set errno.
+ // It returns the size of the DNS response packet.
+ // But if the DNS response packet contains failure-like response codes,
+ // res_search returns -1 even though it has copied the packet into buf,
+ // giving us no way to find out how big the packet is.
+ // For now, we are willing to take res_search's word that there's nothing
+ // useful in the response, even though there *is* a response.
+ name := make([]byte, len(hostname)+1) // +1 for NUL at end for C
+ copy(name, hostname)
+ var buf [1024]byte
+ size, _ := res_search(&name[0], class, rtype, &buf[0], int32(len(buf)))
+ if size <= 0 {
+ return nil, errors.New("res_search failure")
+ }
+
+ var p dnsmessage.Parser
+ if _, err := p.Start(buf[:size]); err != nil {
return nil, err
}
-
- var dnsParser dnsmessage.Parser
- if _, err := dnsParser.Start(responseBuffer[:]); err != nil {
+ p.SkipAllQuestions()
+ resources, err := p.AllAnswers()
+ if 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
+func copyBytes(x []byte) []byte {
+ y := make([]byte, len(x))
+ copy(y, x)
+ return y
+}
+func appendHostsFromResources(answers []string, resources []dnsmessage.Resource) ([]string, error) {
for i := range resources {
switch resources[i].Header.Type {
case dnsmessage.TypeA:
b := resources[i].Body.(*dnsmessage.AResource)
- answers = append(answers, string(b.A[:]))
+ answers = append(answers, IP(b.A[:]).String())
case dnsmessage.TypeAAAA:
b := resources[i].Body.(*dnsmessage.AAAAResource)
- answers = append(answers, string(b.AAAA[:]))
+ answers = append(answers, IP(b.AAAA[:]).String())
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
-
+func appendIPsFromResources(answers []IPAddr, resources []dnsmessage.Resource) ([]IPAddr, error) {
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})
+ answers = append(answers, IPAddr{IP: IP(copyBytes(b.A[:]))})
case dnsmessage.TypeAAAA:
b := resources[i].Body.(*dnsmessage.AAAAResource)
- ip, zone := parseIPv6Zone(string(b.AAAA[:]))
- answers = append(answers, IPAddr{IP: ip, Zone: zone})
+ answers = append(answers, IPAddr{IP: IP(copyBytes(b.AAAA[:]))})
default:
return nil, errors.New("could not parse an A or AAAA response from message buffer")
}
--- /dev/null
+// 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"
+ "strings"
+ "testing"
+)
+
+func TestPseudoCgoLookupHost(t *testing.T) {
+ addrs, err, ok := cgoLookupHost(context.Background(), "google.com")
+ t.Logf("cgoLookupHost google.com: %v %v %v", addrs, err, ok)
+ if !ok {
+ t.Fatal("cgoLookupHost ok=false")
+ }
+ if err != nil {
+ t.Fatalf("cgoLookupHost: %v", err)
+ }
+ // cgoLookupHost need not return IPv4 before IPv6 in general,
+ // but for the current implementation it does.
+ // If that changes, this test will need updating.
+ if len(addrs) < 1 || strings.Count(addrs[0], ".") != 3 || !strings.Contains(addrs[len(addrs)-1], "::") {
+ t.Fatalf("cgoLookupHost google.com = %v, want IPv4 and IPv6", addrs)
+ }
+}
+
+func TestPseudoCgoLookupIP(t *testing.T) {
+ ips, err, ok := cgoLookupIP(context.Background(), "ip", "google.com")
+ t.Logf("cgoLookupIP google.com: %v %v %v", ips, err, ok)
+ if !ok {
+ t.Fatal("cgoLookupIP ok=false")
+ }
+ if err != nil {
+ t.Fatalf("cgoLookupIP: %v", err)
+ }
+ // cgoLookupIP need not return IPv4 before IPv6 in general,
+ // but for the current implementation it does.
+ // If that changes, this test will need updating.
+ if len(ips) < 1 || len(ips[0].IP) != 4 || len(ips[len(ips)-1].IP) != 16 {
+ t.Fatalf("cgoLookupIP google.com = %v, want IPv4 and IPv6", ips)
+ }
+}
+
+func TestPseudoCgoLookupCNAME(t *testing.T) {
+ t.Skip("res_search on macOS hangs in TypeCNAME queries (even in plain C programs)")
+
+ cname, err, ok := cgoLookupCNAME(context.Background(), "redirect.swtch.com")
+ t.Logf("cgoLookupCNAME redirect.swtch.com: %v %v %v", cname, err, ok)
+ if !ok {
+ t.Fatal("cgoLookupCNAME ok=false")
+ }
+ if err != nil {
+ t.Fatalf("cgoLookupCNAME: %v", err)
+ }
+ if !strings.HasSuffix(cname, ".com") {
+ t.Fatalf("cgoLookupCNAME redirect.swtch.com = %v, want *.com", cname)
+ }
+}
+
+func TestPseudoCgoLookupPTR(t *testing.T) {
+ t.Skip("res_search on macOS does not support TypePTR")
+
+ ptrs, err, ok := cgoLookupPTR(context.Background(), "8.8.8.8")
+ t.Logf("cgoLookupPTR 8.8.8.8: %v %v %v", ptrs, err, ok)
+ if !ok {
+ t.Fatal("cgoLookupPTR ok=false")
+ }
+ if err != nil {
+ t.Fatalf("cgoLookupPTR: %v", err)
+ }
+ if len(ptrs) < 1 || ptrs[0] != "google-public-dns-a.google.com" {
+ t.Fatalf("cgoLookupPTR = %v, want google-public-dns-a.google.com", ptrs)
+ }
+}