blob: 544df7fd6c1b0bd9682060da348e4bc02cc92c75 [file] [log] [blame]
// 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)