Source file src/net/cgo_unix.go

     1  // Copyright 2011 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  // This file is called cgo_unix.go, but to allow syscalls-to-libc-based
     6  // implementations to share the code, it does not use cgo directly.
     7  // Instead of C.foo it uses _C_foo, which is defined in either
     8  // cgo_unix_cgo.go or cgo_unix_syscall.go
     9  
    10  //go:build !netgo && ((cgo && unix) || darwin)
    11  
    12  package net
    13  
    14  import (
    15  	"context"
    16  	"errors"
    17  	"internal/bytealg"
    18  	"net/netip"
    19  	"syscall"
    20  	"unsafe"
    21  
    22  	"golang.org/x/net/dns/dnsmessage"
    23  )
    24  
    25  // cgoAvailable set to true to indicate that the cgo resolver
    26  // is available on this system.
    27  const cgoAvailable = true
    28  
    29  // An addrinfoErrno represents a getaddrinfo, getnameinfo-specific
    30  // error number. It's a signed number and a zero value is a non-error
    31  // by convention.
    32  type addrinfoErrno int
    33  
    34  func (eai addrinfoErrno) Error() string   { return _C_gai_strerror(_C_int(eai)) }
    35  func (eai addrinfoErrno) Temporary() bool { return eai == _C_EAI_AGAIN }
    36  func (eai addrinfoErrno) Timeout() bool   { return false }
    37  
    38  // isAddrinfoErrno is just for testing purposes.
    39  func (eai addrinfoErrno) isAddrinfoErrno() {}
    40  
    41  // doBlockingWithCtx executes a blocking function in a separate goroutine when the provided
    42  // context is cancellable. It is intended for use with calls that don't support context
    43  // cancellation (cgo, syscalls). blocking func may still be running after this function finishes.
    44  // For the duration of the execution of the blocking function, the thread is 'acquired' using [acquireThread],
    45  // blocking might not be executed when the context gets canceled early.
    46  func doBlockingWithCtx[T any](ctx context.Context, lookupName string, blocking func() (T, error)) (T, error) {
    47  	if err := acquireThread(ctx); err != nil {
    48  		var zero T
    49  		return zero, &DNSError{
    50  			Name:      lookupName,
    51  			Err:       mapErr(err).Error(),
    52  			IsTimeout: err == context.DeadlineExceeded,
    53  		}
    54  	}
    55  
    56  	if ctx.Done() == nil {
    57  		defer releaseThread()
    58  		return blocking()
    59  	}
    60  
    61  	type result struct {
    62  		res T
    63  		err error
    64  	}
    65  
    66  	res := make(chan result, 1)
    67  	go func() {
    68  		defer releaseThread()
    69  		var r result
    70  		r.res, r.err = blocking()
    71  		res <- r
    72  	}()
    73  
    74  	select {
    75  	case r := <-res:
    76  		return r.res, r.err
    77  	case <-ctx.Done():
    78  		var zero T
    79  		return zero, &DNSError{
    80  			Name:      lookupName,
    81  			Err:       mapErr(ctx.Err()).Error(),
    82  			IsTimeout: ctx.Err() == context.DeadlineExceeded,
    83  		}
    84  	}
    85  }
    86  
    87  func cgoLookupHost(ctx context.Context, name string) (hosts []string, err error) {
    88  	addrs, err := cgoLookupIP(ctx, "ip", name)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	for _, addr := range addrs {
    93  		hosts = append(hosts, addr.String())
    94  	}
    95  	return hosts, nil
    96  }
    97  
    98  func cgoLookupPort(ctx context.Context, network, service string) (port int, err error) {
    99  	var hints _C_struct_addrinfo
   100  	switch network {
   101  	case "ip": // no hints
   102  	case "tcp", "tcp4", "tcp6":
   103  		*_C_ai_socktype(&hints) = _C_SOCK_STREAM
   104  		*_C_ai_protocol(&hints) = _C_IPPROTO_TCP
   105  	case "udp", "udp4", "udp6":
   106  		*_C_ai_socktype(&hints) = _C_SOCK_DGRAM
   107  		*_C_ai_protocol(&hints) = _C_IPPROTO_UDP
   108  	default:
   109  		return 0, &DNSError{Err: "unknown network", Name: network + "/" + service}
   110  	}
   111  	switch ipVersion(network) {
   112  	case '4':
   113  		*_C_ai_family(&hints) = _C_AF_INET
   114  	case '6':
   115  		*_C_ai_family(&hints) = _C_AF_INET6
   116  	}
   117  
   118  	return doBlockingWithCtx(ctx, network+"/"+service, func() (int, error) {
   119  		return cgoLookupServicePort(&hints, network, service)
   120  	})
   121  }
   122  
   123  func cgoLookupServicePort(hints *_C_struct_addrinfo, network, service string) (port int, err error) {
   124  	cservice, err := syscall.ByteSliceFromString(service)
   125  	if err != nil {
   126  		return 0, &DNSError{Err: err.Error(), Name: network + "/" + service}
   127  	}
   128  	// Lowercase the C service name.
   129  	for i, b := range cservice[:len(service)] {
   130  		cservice[i] = lowerASCII(b)
   131  	}
   132  	var res *_C_struct_addrinfo
   133  	gerrno, err := _C_getaddrinfo(nil, (*_C_char)(unsafe.Pointer(&cservice[0])), hints, &res)
   134  	if gerrno != 0 {
   135  		switch gerrno {
   136  		case _C_EAI_SYSTEM:
   137  			if err == nil { // see golang.org/issue/6232
   138  				err = syscall.EMFILE
   139  			}
   140  			return 0, newDNSError(err, network+"/"+service, "")
   141  		case _C_EAI_SERVICE, _C_EAI_NONAME: // Darwin returns EAI_NONAME.
   142  			return 0, newDNSError(errUnknownPort, network+"/"+service, "")
   143  		default:
   144  			return 0, newDNSError(addrinfoErrno(gerrno), network+"/"+service, "")
   145  		}
   146  	}
   147  	defer _C_freeaddrinfo(res)
   148  
   149  	for r := res; r != nil; r = *_C_ai_next(r) {
   150  		switch *_C_ai_family(r) {
   151  		case _C_AF_INET:
   152  			sa := (*syscall.RawSockaddrInet4)(unsafe.Pointer(*_C_ai_addr(r)))
   153  			p := (*[2]byte)(unsafe.Pointer(&sa.Port))
   154  			return int(p[0])<<8 | int(p[1]), nil
   155  		case _C_AF_INET6:
   156  			sa := (*syscall.RawSockaddrInet6)(unsafe.Pointer(*_C_ai_addr(r)))
   157  			p := (*[2]byte)(unsafe.Pointer(&sa.Port))
   158  			return int(p[0])<<8 | int(p[1]), nil
   159  		}
   160  	}
   161  	return 0, newDNSError(errUnknownPort, network+"/"+service, "")
   162  }
   163  
   164  func cgoLookupHostIP(network, name string) (addrs []IPAddr, err error) {
   165  	var hints _C_struct_addrinfo
   166  	*_C_ai_flags(&hints) = cgoAddrInfoFlags
   167  	*_C_ai_socktype(&hints) = _C_SOCK_STREAM
   168  	*_C_ai_family(&hints) = _C_AF_UNSPEC
   169  	switch ipVersion(network) {
   170  	case '4':
   171  		*_C_ai_family(&hints) = _C_AF_INET
   172  	case '6':
   173  		*_C_ai_family(&hints) = _C_AF_INET6
   174  	}
   175  
   176  	h, err := syscall.BytePtrFromString(name)
   177  	if err != nil {
   178  		return nil, &DNSError{Err: err.Error(), Name: name}
   179  	}
   180  	var res *_C_struct_addrinfo
   181  	gerrno, err := _C_getaddrinfo((*_C_char)(unsafe.Pointer(h)), nil, &hints, &res)
   182  	if gerrno != 0 {
   183  		switch gerrno {
   184  		case _C_EAI_SYSTEM:
   185  			if err == nil {
   186  				// err should not be nil, but sometimes getaddrinfo returns
   187  				// gerrno == _C_EAI_SYSTEM with err == nil on Linux.
   188  				// The report claims that it happens when we have too many
   189  				// open files, so use syscall.EMFILE (too many open files in system).
   190  				// Most system calls would return ENFILE (too many open files),
   191  				// so at the least EMFILE should be easy to recognize if this
   192  				// comes up again. golang.org/issue/6232.
   193  				err = syscall.EMFILE
   194  			}
   195  			return nil, newDNSError(err, name, "")
   196  		case _C_EAI_NONAME, _C_EAI_NODATA:
   197  			return nil, newDNSError(errNoSuchHost, name, "")
   198  		default:
   199  			return nil, newDNSError(addrinfoErrno(gerrno), name, "")
   200  		}
   201  
   202  	}
   203  	defer _C_freeaddrinfo(res)
   204  
   205  	for r := res; r != nil; r = *_C_ai_next(r) {
   206  		// We only asked for SOCK_STREAM, but check anyhow.
   207  		if *_C_ai_socktype(r) != _C_SOCK_STREAM {
   208  			continue
   209  		}
   210  		switch *_C_ai_family(r) {
   211  		case _C_AF_INET:
   212  			sa := (*syscall.RawSockaddrInet4)(unsafe.Pointer(*_C_ai_addr(r)))
   213  			addr := IPAddr{IP: copyIP(sa.Addr[:])}
   214  			addrs = append(addrs, addr)
   215  		case _C_AF_INET6:
   216  			sa := (*syscall.RawSockaddrInet6)(unsafe.Pointer(*_C_ai_addr(r)))
   217  			addr := IPAddr{IP: copyIP(sa.Addr[:]), Zone: zoneCache.name(int(sa.Scope_id))}
   218  			addrs = append(addrs, addr)
   219  		}
   220  	}
   221  	return addrs, nil
   222  }
   223  
   224  func cgoLookupIP(ctx context.Context, network, name string) (addrs []IPAddr, err error) {
   225  	return doBlockingWithCtx(ctx, name, func() ([]IPAddr, error) {
   226  		return cgoLookupHostIP(network, name)
   227  	})
   228  }
   229  
   230  // These are roughly enough for the following:
   231  //
   232  //	 Source		Encoding			Maximum length of single name entry
   233  //	 Unicast DNS		ASCII or			<=253 + a NUL terminator
   234  //				Unicode in RFC 5892		252 * total number of labels + delimiters + a NUL terminator
   235  //	 Multicast DNS	UTF-8 in RFC 5198 or		<=253 + a NUL terminator
   236  //				the same as unicast DNS ASCII	<=253 + a NUL terminator
   237  //	 Local database	various				depends on implementation
   238  const (
   239  	nameinfoLen    = 64
   240  	maxNameinfoLen = 4096
   241  )
   242  
   243  func cgoLookupPTR(ctx context.Context, addr string) (names []string, err error) {
   244  	ip, err := netip.ParseAddr(addr)
   245  	if err != nil {
   246  		return nil, &DNSError{Err: "invalid address", Name: addr}
   247  	}
   248  	sa, salen := cgoSockaddr(IP(ip.AsSlice()), ip.Zone())
   249  	if sa == nil {
   250  		return nil, &DNSError{Err: "invalid address " + ip.String(), Name: addr}
   251  	}
   252  
   253  	return doBlockingWithCtx(ctx, addr, func() ([]string, error) {
   254  		return cgoLookupAddrPTR(addr, sa, salen)
   255  	})
   256  }
   257  
   258  func cgoLookupAddrPTR(addr string, sa *_C_struct_sockaddr, salen _C_socklen_t) (names []string, err error) {
   259  	var gerrno int
   260  	var b []byte
   261  	for l := nameinfoLen; l <= maxNameinfoLen; l *= 2 {
   262  		b = make([]byte, l)
   263  		gerrno, err = cgoNameinfoPTR(b, sa, salen)
   264  		if gerrno == 0 || gerrno != _C_EAI_OVERFLOW {
   265  			break
   266  		}
   267  	}
   268  	if gerrno != 0 {
   269  		switch gerrno {
   270  		case _C_EAI_SYSTEM:
   271  			if err == nil { // see golang.org/issue/6232
   272  				err = syscall.EMFILE
   273  			}
   274  			return nil, newDNSError(err, addr, "")
   275  		case _C_EAI_NONAME:
   276  			return nil, newDNSError(errNoSuchHost, addr, "")
   277  		default:
   278  			return nil, newDNSError(addrinfoErrno(gerrno), addr, "")
   279  		}
   280  	}
   281  	if i := bytealg.IndexByte(b, 0); i != -1 {
   282  		b = b[:i]
   283  	}
   284  	return []string{absDomainName(string(b))}, nil
   285  }
   286  
   287  func cgoSockaddr(ip IP, zone string) (*_C_struct_sockaddr, _C_socklen_t) {
   288  	if ip4 := ip.To4(); ip4 != nil {
   289  		return cgoSockaddrInet4(ip4), _C_socklen_t(syscall.SizeofSockaddrInet4)
   290  	}
   291  	if ip6 := ip.To16(); ip6 != nil {
   292  		return cgoSockaddrInet6(ip6, zoneCache.index(zone)), _C_socklen_t(syscall.SizeofSockaddrInet6)
   293  	}
   294  	return nil, 0
   295  }
   296  
   297  func cgoLookupCNAME(ctx context.Context, name string) (cname string, err error, completed bool) {
   298  	resources, err := resSearch(ctx, name, int(dnsmessage.TypeCNAME), int(dnsmessage.ClassINET))
   299  	if err != nil {
   300  		return
   301  	}
   302  	cname, err = parseCNAMEFromResources(resources)
   303  	if err != nil {
   304  		return "", err, false
   305  	}
   306  	return cname, nil, true
   307  }
   308  
   309  // resSearch will make a call to the 'res_nsearch' routine in the C library
   310  // and parse the output as a slice of DNS resources.
   311  func resSearch(ctx context.Context, hostname string, rtype, class int) ([]dnsmessage.Resource, error) {
   312  	return doBlockingWithCtx(ctx, hostname, func() ([]dnsmessage.Resource, error) {
   313  		return cgoResSearch(hostname, rtype, class)
   314  	})
   315  }
   316  
   317  func cgoResSearch(hostname string, rtype, class int) ([]dnsmessage.Resource, error) {
   318  	resStateSize := unsafe.Sizeof(_C_struct___res_state{})
   319  	var state *_C_struct___res_state
   320  	if resStateSize > 0 {
   321  		mem := _C_malloc(resStateSize)
   322  		defer _C_free(mem)
   323  		memSlice := unsafe.Slice((*byte)(mem), resStateSize)
   324  		clear(memSlice)
   325  		state = (*_C_struct___res_state)(unsafe.Pointer(&memSlice[0]))
   326  	}
   327  	if err := _C_res_ninit(state); err != nil {
   328  		return nil, errors.New("res_ninit failure: " + err.Error())
   329  	}
   330  	defer _C_res_nclose(state)
   331  
   332  	// Some res_nsearch implementations (like macOS) do not set errno.
   333  	// They set h_errno, which is not per-thread and useless to us.
   334  	// res_nsearch returns the size of the DNS response packet.
   335  	// But if the DNS response packet contains failure-like response codes,
   336  	// res_search returns -1 even though it has copied the packet into buf,
   337  	// giving us no way to find out how big the packet is.
   338  	// For now, we are willing to take res_search's word that there's nothing
   339  	// useful in the response, even though there *is* a response.
   340  	bufSize := maxDNSPacketSize
   341  	buf := (*_C_uchar)(_C_malloc(uintptr(bufSize)))
   342  	defer _C_free(unsafe.Pointer(buf))
   343  
   344  	s, err := syscall.BytePtrFromString(hostname)
   345  	if err != nil {
   346  		return nil, err
   347  	}
   348  
   349  	var size int
   350  	for {
   351  		size := _C_res_nsearch(state, (*_C_char)(unsafe.Pointer(s)), class, rtype, buf, bufSize)
   352  		if size <= 0 || size > 0xffff {
   353  			return nil, errors.New("res_nsearch failure")
   354  		}
   355  		if size <= bufSize {
   356  			break
   357  		}
   358  
   359  		// Allocate a bigger buffer to fit the entire msg.
   360  		_C_free(unsafe.Pointer(buf))
   361  		bufSize = size
   362  		buf = (*_C_uchar)(_C_malloc(uintptr(bufSize)))
   363  	}
   364  
   365  	var p dnsmessage.Parser
   366  	if _, err := p.Start(unsafe.Slice((*byte)(unsafe.Pointer(buf)), size)); err != nil {
   367  		return nil, err
   368  	}
   369  	p.SkipAllQuestions()
   370  	resources, err := p.AllAnswers()
   371  	if err != nil {
   372  		return nil, err
   373  	}
   374  	return resources, nil
   375  }
   376  

View as plain text