github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/net/cgo_unix.go (about)

     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 cancelled 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  		isTemporary := false
   136  		switch gerrno {
   137  		case _C_EAI_SYSTEM:
   138  			if err == nil { // see golang.org/issue/6232
   139  				err = syscall.EMFILE
   140  			}
   141  		case _C_EAI_SERVICE, _C_EAI_NONAME: // Darwin returns EAI_NONAME.
   142  			return 0, &DNSError{Err: "unknown port", Name: network + "/" + service, IsNotFound: true}
   143  		default:
   144  			err = addrinfoErrno(gerrno)
   145  			isTemporary = addrinfoErrno(gerrno).Temporary()
   146  		}
   147  		return 0, &DNSError{Err: err.Error(), Name: network + "/" + service, IsTemporary: isTemporary}
   148  	}
   149  	defer _C_freeaddrinfo(res)
   150  
   151  	for r := res; r != nil; r = *_C_ai_next(r) {
   152  		switch *_C_ai_family(r) {
   153  		case _C_AF_INET:
   154  			sa := (*syscall.RawSockaddrInet4)(unsafe.Pointer(*_C_ai_addr(r)))
   155  			p := (*[2]byte)(unsafe.Pointer(&sa.Port))
   156  			return int(p[0])<<8 | int(p[1]), nil
   157  		case _C_AF_INET6:
   158  			sa := (*syscall.RawSockaddrInet6)(unsafe.Pointer(*_C_ai_addr(r)))
   159  			p := (*[2]byte)(unsafe.Pointer(&sa.Port))
   160  			return int(p[0])<<8 | int(p[1]), nil
   161  		}
   162  	}
   163  	return 0, &DNSError{Err: "unknown port", Name: network + "/" + service, IsNotFound: true}
   164  }
   165  
   166  func cgoLookupHostIP(network, name string) (addrs []IPAddr, err error) {
   167  	var hints _C_struct_addrinfo
   168  	*_C_ai_flags(&hints) = cgoAddrInfoFlags
   169  	*_C_ai_socktype(&hints) = _C_SOCK_STREAM
   170  	*_C_ai_family(&hints) = _C_AF_UNSPEC
   171  	switch ipVersion(network) {
   172  	case '4':
   173  		*_C_ai_family(&hints) = _C_AF_INET
   174  	case '6':
   175  		*_C_ai_family(&hints) = _C_AF_INET6
   176  	}
   177  
   178  	h, err := syscall.BytePtrFromString(name)
   179  	if err != nil {
   180  		return nil, &DNSError{Err: err.Error(), Name: name}
   181  	}
   182  	var res *_C_struct_addrinfo
   183  	gerrno, err := _C_getaddrinfo((*_C_char)(unsafe.Pointer(h)), nil, &hints, &res)
   184  	if gerrno != 0 {
   185  		isErrorNoSuchHost := false
   186  		isTemporary := false
   187  		switch gerrno {
   188  		case _C_EAI_SYSTEM:
   189  			if err == nil {
   190  				// err should not be nil, but sometimes getaddrinfo returns
   191  				// gerrno == _C_EAI_SYSTEM with err == nil on Linux.
   192  				// The report claims that it happens when we have too many
   193  				// open files, so use syscall.EMFILE (too many open files in system).
   194  				// Most system calls would return ENFILE (too many open files),
   195  				// so at the least EMFILE should be easy to recognize if this
   196  				// comes up again. golang.org/issue/6232.
   197  				err = syscall.EMFILE
   198  			}
   199  		case _C_EAI_NONAME, _C_EAI_NODATA:
   200  			err = errNoSuchHost
   201  			isErrorNoSuchHost = true
   202  		default:
   203  			err = addrinfoErrno(gerrno)
   204  			isTemporary = addrinfoErrno(gerrno).Temporary()
   205  		}
   206  
   207  		return nil, &DNSError{Err: err.Error(), Name: name, IsNotFound: isErrorNoSuchHost, IsTemporary: isTemporary}
   208  	}
   209  	defer _C_freeaddrinfo(res)
   210  
   211  	for r := res; r != nil; r = *_C_ai_next(r) {
   212  		// We only asked for SOCK_STREAM, but check anyhow.
   213  		if *_C_ai_socktype(r) != _C_SOCK_STREAM {
   214  			continue
   215  		}
   216  		switch *_C_ai_family(r) {
   217  		case _C_AF_INET:
   218  			sa := (*syscall.RawSockaddrInet4)(unsafe.Pointer(*_C_ai_addr(r)))
   219  			addr := IPAddr{IP: copyIP(sa.Addr[:])}
   220  			addrs = append(addrs, addr)
   221  		case _C_AF_INET6:
   222  			sa := (*syscall.RawSockaddrInet6)(unsafe.Pointer(*_C_ai_addr(r)))
   223  			addr := IPAddr{IP: copyIP(sa.Addr[:]), Zone: zoneCache.name(int(sa.Scope_id))}
   224  			addrs = append(addrs, addr)
   225  		}
   226  	}
   227  	return addrs, nil
   228  }
   229  
   230  func cgoLookupIP(ctx context.Context, network, name string) (addrs []IPAddr, err error) {
   231  	return doBlockingWithCtx(ctx, name, func() ([]IPAddr, error) {
   232  		return cgoLookupHostIP(network, name)
   233  	})
   234  }
   235  
   236  // These are roughly enough for the following:
   237  //
   238  //	 Source		Encoding			Maximum length of single name entry
   239  //	 Unicast DNS		ASCII or			<=253 + a NUL terminator
   240  //				Unicode in RFC 5892		252 * total number of labels + delimiters + a NUL terminator
   241  //	 Multicast DNS	UTF-8 in RFC 5198 or		<=253 + a NUL terminator
   242  //				the same as unicast DNS ASCII	<=253 + a NUL terminator
   243  //	 Local database	various				depends on implementation
   244  const (
   245  	nameinfoLen    = 64
   246  	maxNameinfoLen = 4096
   247  )
   248  
   249  func cgoLookupPTR(ctx context.Context, addr string) (names []string, err error) {
   250  	ip, err := netip.ParseAddr(addr)
   251  	if err != nil {
   252  		return nil, &DNSError{Err: "invalid address", Name: addr}
   253  	}
   254  	sa, salen := cgoSockaddr(IP(ip.AsSlice()), ip.Zone())
   255  	if sa == nil {
   256  		return nil, &DNSError{Err: "invalid address " + ip.String(), Name: addr}
   257  	}
   258  
   259  	return doBlockingWithCtx(ctx, addr, func() ([]string, error) {
   260  		return cgoLookupAddrPTR(addr, sa, salen)
   261  	})
   262  }
   263  
   264  func cgoLookupAddrPTR(addr string, sa *_C_struct_sockaddr, salen _C_socklen_t) (names []string, err error) {
   265  	var gerrno int
   266  	var b []byte
   267  	for l := nameinfoLen; l <= maxNameinfoLen; l *= 2 {
   268  		b = make([]byte, l)
   269  		gerrno, err = cgoNameinfoPTR(b, sa, salen)
   270  		if gerrno == 0 || gerrno != _C_EAI_OVERFLOW {
   271  			break
   272  		}
   273  	}
   274  	if gerrno != 0 {
   275  		isErrorNoSuchHost := false
   276  		isTemporary := false
   277  		switch gerrno {
   278  		case _C_EAI_SYSTEM:
   279  			if err == nil { // see golang.org/issue/6232
   280  				err = syscall.EMFILE
   281  			}
   282  		case _C_EAI_NONAME:
   283  			err = errNoSuchHost
   284  			isErrorNoSuchHost = true
   285  		default:
   286  			err = addrinfoErrno(gerrno)
   287  			isTemporary = addrinfoErrno(gerrno).Temporary()
   288  		}
   289  		return nil, &DNSError{Err: err.Error(), Name: addr, IsTemporary: isTemporary, IsNotFound: isErrorNoSuchHost}
   290  	}
   291  	if i := bytealg.IndexByte(b, 0); i != -1 {
   292  		b = b[:i]
   293  	}
   294  	return []string{absDomainName(string(b))}, nil
   295  }
   296  
   297  func cgoSockaddr(ip IP, zone string) (*_C_struct_sockaddr, _C_socklen_t) {
   298  	if ip4 := ip.To4(); ip4 != nil {
   299  		return cgoSockaddrInet4(ip4), _C_socklen_t(syscall.SizeofSockaddrInet4)
   300  	}
   301  	if ip6 := ip.To16(); ip6 != nil {
   302  		return cgoSockaddrInet6(ip6, zoneCache.index(zone)), _C_socklen_t(syscall.SizeofSockaddrInet6)
   303  	}
   304  	return nil, 0
   305  }
   306  
   307  func cgoLookupCNAME(ctx context.Context, name string) (cname string, err error, completed bool) {
   308  	resources, err := resSearch(ctx, name, int(dnsmessage.TypeCNAME), int(dnsmessage.ClassINET))
   309  	if err != nil {
   310  		return
   311  	}
   312  	cname, err = parseCNAMEFromResources(resources)
   313  	if err != nil {
   314  		return "", err, false
   315  	}
   316  	return cname, nil, true
   317  }
   318  
   319  // resSearch will make a call to the 'res_nsearch' routine in the C library
   320  // and parse the output as a slice of DNS resources.
   321  func resSearch(ctx context.Context, hostname string, rtype, class int) ([]dnsmessage.Resource, error) {
   322  	return doBlockingWithCtx(ctx, hostname, func() ([]dnsmessage.Resource, error) {
   323  		return cgoResSearch(hostname, rtype, class)
   324  	})
   325  }
   326  
   327  func cgoResSearch(hostname string, rtype, class int) ([]dnsmessage.Resource, error) {
   328  	resStateSize := unsafe.Sizeof(_C_struct___res_state{})
   329  	var state *_C_struct___res_state
   330  	if resStateSize > 0 {
   331  		mem := _C_malloc(resStateSize)
   332  		defer _C_free(mem)
   333  		memSlice := unsafe.Slice((*byte)(mem), resStateSize)
   334  		clear(memSlice)
   335  		state = (*_C_struct___res_state)(unsafe.Pointer(&memSlice[0]))
   336  	}
   337  	if err := _C_res_ninit(state); err != nil {
   338  		return nil, errors.New("res_ninit failure: " + err.Error())
   339  	}
   340  	defer _C_res_nclose(state)
   341  
   342  	// Some res_nsearch implementations (like macOS) do not set errno.
   343  	// They set h_errno, which is not per-thread and useless to us.
   344  	// res_nsearch returns the size of the DNS response packet.
   345  	// But if the DNS response packet contains failure-like response codes,
   346  	// res_search returns -1 even though it has copied the packet into buf,
   347  	// giving us no way to find out how big the packet is.
   348  	// For now, we are willing to take res_search's word that there's nothing
   349  	// useful in the response, even though there *is* a response.
   350  	bufSize := maxDNSPacketSize
   351  	buf := (*_C_uchar)(_C_malloc(uintptr(bufSize)))
   352  	defer _C_free(unsafe.Pointer(buf))
   353  
   354  	s, err := syscall.BytePtrFromString(hostname)
   355  	if err != nil {
   356  		return nil, err
   357  	}
   358  
   359  	var size int
   360  	for {
   361  		size := _C_res_nsearch(state, (*_C_char)(unsafe.Pointer(s)), class, rtype, buf, bufSize)
   362  		if size <= 0 || size > 0xffff {
   363  			return nil, errors.New("res_nsearch failure")
   364  		}
   365  		if size <= bufSize {
   366  			break
   367  		}
   368  
   369  		// Allocate a bigger buffer to fit the entire msg.
   370  		_C_free(unsafe.Pointer(buf))
   371  		bufSize = size
   372  		buf = (*_C_uchar)(_C_malloc(uintptr(bufSize)))
   373  	}
   374  
   375  	var p dnsmessage.Parser
   376  	if _, err := p.Start(unsafe.Slice((*byte)(unsafe.Pointer(buf)), size)); err != nil {
   377  		return nil, err
   378  	}
   379  	p.SkipAllQuestions()
   380  	resources, err := p.AllAnswers()
   381  	if err != nil {
   382  		return nil, err
   383  	}
   384  	return resources, nil
   385  }