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

View as plain text