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
12package net
13
14import (
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.
27const 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.
32type addrinfoErrno int
33
34func (eai addrinfoErrno) Error() string   { return _C_gai_strerror(_C_int(eai)) }
35func (eai addrinfoErrno) Temporary() bool { return eai == _C_EAI_AGAIN }
36func (eai addrinfoErrno) Timeout() bool   { return false }
37
38// isAddrinfoErrno is just for testing purposes.
39func (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.
46func 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
87func 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
98func 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
123func 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
164func 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
224func 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
238const (
239	nameinfoLen    = 64
240	maxNameinfoLen = 4096
241)
242
243func 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
258func 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
287func 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
297func 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.
311func 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
317func 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