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//go:build cgo && !netgo && (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris)
6
7package net
8
9/*
10#include <sys/types.h>
11#include <sys/socket.h>
12#include <netinet/in.h>
13#include <netdb.h>
14#include <unistd.h>
15#include <string.h>
16
17// If nothing else defined EAI_OVERFLOW, make sure it has a value.
18#ifndef EAI_OVERFLOW
19#define EAI_OVERFLOW -12
20#endif
21*/
22import "C"
23
24import (
25	"context"
26	"syscall"
27	"unsafe"
28)
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.
33type addrinfoErrno int
34
35func (eai addrinfoErrno) Error() string   { return C.GoString(C.gai_strerror(C.int(eai))) }
36func (eai addrinfoErrno) Temporary() bool { return eai == C.EAI_AGAIN }
37func (eai addrinfoErrno) Timeout() bool   { return false }
38
39type portLookupResult struct {
40	port int
41	err  error
42}
43
44type ipLookupResult struct {
45	addrs []IPAddr
46	cname string
47	err   error
48}
49
50type reverseLookupResult struct {
51	names []string
52	err   error
53}
54
55func cgoLookupHost(ctx context.Context, name string) (hosts []string, err error, completed bool) {
56	addrs, err, completed := cgoLookupIP(ctx, "ip", name)
57	for _, addr := range addrs {
58		hosts = append(hosts, addr.String())
59	}
60	return
61}
62
63func cgoLookupPort(ctx context.Context, network, service string) (port int, err error, completed bool) {
64	var hints C.struct_addrinfo
65	switch network {
66	case "": // no hints
67	case "tcp", "tcp4", "tcp6":
68		hints.ai_socktype = C.SOCK_STREAM
69		hints.ai_protocol = C.IPPROTO_TCP
70	case "udp", "udp4", "udp6":
71		hints.ai_socktype = C.SOCK_DGRAM
72		hints.ai_protocol = C.IPPROTO_UDP
73	default:
74		return 0, &DNSError{Err: "unknown network", Name: network + "/" + service}, true
75	}
76	switch ipVersion(network) {
77	case '4':
78		hints.ai_family = C.AF_INET
79	case '6':
80		hints.ai_family = C.AF_INET6
81	}
82	if ctx.Done() == nil {
83		port, err := cgoLookupServicePort(&hints, network, service)
84		return port, err, true
85	}
86	result := make(chan portLookupResult, 1)
87	go cgoPortLookup(result, &hints, network, service)
88	select {
89	case r := <-result:
90		return r.port, r.err, true
91	case <-ctx.Done():
92		// Since there isn't a portable way to cancel the lookup,
93		// we just let it finish and write to the buffered channel.
94		return 0, mapErr(ctx.Err()), false
95	}
96}
97
98func cgoLookupServicePort(hints *C.struct_addrinfo, network, service string) (port int, err error) {
99	cservice := make([]byte, len(service)+1)
100	copy(cservice, service)
101	// Lowercase the C service name.
102	for i, b := range cservice[:len(service)] {
103		cservice[i] = lowerASCII(b)
104	}
105	var res *C.struct_addrinfo
106	gerrno, err := C.getaddrinfo(nil, (*C.char)(unsafe.Pointer(&cservice[0])), hints, &res)
107	if gerrno != 0 {
108		isTemporary := false
109		switch gerrno {
110		case C.EAI_SYSTEM:
111			if err == nil { // see golang.org/issue/6232
112				err = syscall.EMFILE
113			}
114		default:
115			err = addrinfoErrno(gerrno)
116			isTemporary = addrinfoErrno(gerrno).Temporary()
117		}
118		return 0, &DNSError{Err: err.Error(), Name: network + "/" + service, IsTemporary: isTemporary}
119	}
120	defer C.freeaddrinfo(res)
121
122	for r := res; r != nil; r = r.ai_next {
123		switch r.ai_family {
124		case C.AF_INET:
125			sa := (*syscall.RawSockaddrInet4)(unsafe.Pointer(r.ai_addr))
126			p := (*[2]byte)(unsafe.Pointer(&sa.Port))
127			return int(p[0])<<8 | int(p[1]), nil
128		case C.AF_INET6:
129			sa := (*syscall.RawSockaddrInet6)(unsafe.Pointer(r.ai_addr))
130			p := (*[2]byte)(unsafe.Pointer(&sa.Port))
131			return int(p[0])<<8 | int(p[1]), nil
132		}
133	}
134	return 0, &DNSError{Err: "unknown port", Name: network + "/" + service}
135}
136
137func cgoPortLookup(result chan<- portLookupResult, hints *C.struct_addrinfo, network, service string) {
138	port, err := cgoLookupServicePort(hints, network, service)
139	result <- portLookupResult{port, err}
140}
141
142func cgoLookupIPCNAME(network, name string) (addrs []IPAddr, cname string, err error) {
143	acquireThread()
144	defer releaseThread()
145
146	var hints C.struct_addrinfo
147	hints.ai_flags = cgoAddrInfoFlags
148	hints.ai_socktype = C.SOCK_STREAM
149	hints.ai_family = C.AF_UNSPEC
150	switch ipVersion(network) {
151	case '4':
152		hints.ai_family = C.AF_INET
153	case '6':
154		hints.ai_family = C.AF_INET6
155	}
156
157	h := make([]byte, len(name)+1)
158	copy(h, name)
159	var res *C.struct_addrinfo
160	gerrno, err := C.getaddrinfo((*C.char)(unsafe.Pointer(&h[0])), nil, &hints, &res)
161	if gerrno != 0 {
162		isErrorNoSuchHost := false
163		isTemporary := false
164		switch gerrno {
165		case C.EAI_SYSTEM:
166			if err == nil {
167				// err should not be nil, but sometimes getaddrinfo returns
168				// gerrno == C.EAI_SYSTEM with err == nil on Linux.
169				// The report claims that it happens when we have too many
170				// open files, so use syscall.EMFILE (too many open files in system).
171				// Most system calls would return ENFILE (too many open files),
172				// so at the least EMFILE should be easy to recognize if this
173				// comes up again. golang.org/issue/6232.
174				err = syscall.EMFILE
175			}
176		case C.EAI_NONAME:
177			err = errNoSuchHost
178			isErrorNoSuchHost = true
179		default:
180			err = addrinfoErrno(gerrno)
181			isTemporary = addrinfoErrno(gerrno).Temporary()
182		}
183
184		return nil, "", &DNSError{Err: err.Error(), Name: name, IsNotFound: isErrorNoSuchHost, IsTemporary: isTemporary}
185	}
186	defer C.freeaddrinfo(res)
187
188	if res != nil {
189		cname = C.GoString(res.ai_canonname)
190		if cname == "" {
191			cname = name
192		}
193		if len(cname) > 0 && cname[len(cname)-1] != '.' {
194			cname += "."
195		}
196	}
197	for r := res; r != nil; r = r.ai_next {
198		// We only asked for SOCK_STREAM, but check anyhow.
199		if r.ai_socktype != C.SOCK_STREAM {
200			continue
201		}
202		switch r.ai_family {
203		case C.AF_INET:
204			sa := (*syscall.RawSockaddrInet4)(unsafe.Pointer(r.ai_addr))
205			addr := IPAddr{IP: copyIP(sa.Addr[:])}
206			addrs = append(addrs, addr)
207		case C.AF_INET6:
208			sa := (*syscall.RawSockaddrInet6)(unsafe.Pointer(r.ai_addr))
209			addr := IPAddr{IP: copyIP(sa.Addr[:]), Zone: zoneCache.name(int(sa.Scope_id))}
210			addrs = append(addrs, addr)
211		}
212	}
213	return addrs, cname, nil
214}
215
216func cgoIPLookup(result chan<- ipLookupResult, network, name string) {
217	addrs, cname, err := cgoLookupIPCNAME(network, name)
218	result <- ipLookupResult{addrs, cname, err}
219}
220
221func cgoLookupIP(ctx context.Context, network, name string) (addrs []IPAddr, err error, completed bool) {
222	if ctx.Done() == nil {
223		addrs, _, err = cgoLookupIPCNAME(network, name)
224		return addrs, err, true
225	}
226	result := make(chan ipLookupResult, 1)
227	go cgoIPLookup(result, network, name)
228	select {
229	case r := <-result:
230		return r.addrs, r.err, true
231	case <-ctx.Done():
232		return nil, mapErr(ctx.Err()), false
233	}
234}
235
236func cgoLookupCNAME(ctx context.Context, name string) (cname string, err error, completed bool) {
237	if ctx.Done() == nil {
238		_, cname, err = cgoLookupIPCNAME("ip", name)
239		return cname, err, true
240	}
241	result := make(chan ipLookupResult, 1)
242	go cgoIPLookup(result, "ip", name)
243	select {
244	case r := <-result:
245		return r.cname, r.err, true
246	case <-ctx.Done():
247		return "", mapErr(ctx.Err()), false
248	}
249}
250
251// These are roughly enough for the following:
252//
253// Source		Encoding			Maximum length of single name entry
254// Unicast DNS		ASCII or			<=253 + a NUL terminator
255//			Unicode in RFC 5892		252 * total number of labels + delimiters + a NUL terminator
256// Multicast DNS	UTF-8 in RFC 5198 or		<=253 + a NUL terminator
257//			the same as unicast DNS ASCII	<=253 + a NUL terminator
258// Local database	various				depends on implementation
259const (
260	nameinfoLen    = 64
261	maxNameinfoLen = 4096
262)
263
264func cgoLookupPTR(ctx context.Context, addr string) (names []string, err error, completed bool) {
265	var zone string
266	ip := parseIPv4(addr)
267	if ip == nil {
268		ip, zone = parseIPv6Zone(addr)
269	}
270	if ip == nil {
271		return nil, &DNSError{Err: "invalid address", Name: addr}, true
272	}
273	sa, salen := cgoSockaddr(ip, zone)
274	if sa == nil {
275		return nil, &DNSError{Err: "invalid address " + ip.String(), Name: addr}, true
276	}
277	if ctx.Done() == nil {
278		names, err := cgoLookupAddrPTR(addr, sa, salen)
279		return names, err, true
280	}
281	result := make(chan reverseLookupResult, 1)
282	go cgoReverseLookup(result, addr, sa, salen)
283	select {
284	case r := <-result:
285		return r.names, r.err, true
286	case <-ctx.Done():
287		return nil, mapErr(ctx.Err()), false
288	}
289}
290
291func cgoLookupAddrPTR(addr string, sa *C.struct_sockaddr, salen C.socklen_t) (names []string, err error) {
292	acquireThread()
293	defer releaseThread()
294
295	var gerrno int
296	var b []byte
297	for l := nameinfoLen; l <= maxNameinfoLen; l *= 2 {
298		b = make([]byte, l)
299		gerrno, err = cgoNameinfoPTR(b, sa, salen)
300		if gerrno == 0 || gerrno != C.EAI_OVERFLOW {
301			break
302		}
303	}
304	if gerrno != 0 {
305		isTemporary := false
306		switch gerrno {
307		case C.EAI_SYSTEM:
308			if err == nil { // see golang.org/issue/6232
309				err = syscall.EMFILE
310			}
311		default:
312			err = addrinfoErrno(gerrno)
313			isTemporary = addrinfoErrno(gerrno).Temporary()
314		}
315		return nil, &DNSError{Err: err.Error(), Name: addr, IsTemporary: isTemporary}
316	}
317	for i := 0; i < len(b); i++ {
318		if b[i] == 0 {
319			b = b[:i]
320			break
321		}
322	}
323	return []string{absDomainName(string(b))}, nil
324}
325
326func cgoReverseLookup(result chan<- reverseLookupResult, addr string, sa *C.struct_sockaddr, salen C.socklen_t) {
327	names, err := cgoLookupAddrPTR(addr, sa, salen)
328	result <- reverseLookupResult{names, err}
329}
330
331func cgoSockaddr(ip IP, zone string) (*C.struct_sockaddr, C.socklen_t) {
332	if ip4 := ip.To4(); ip4 != nil {
333		return cgoSockaddrInet4(ip4), C.socklen_t(syscall.SizeofSockaddrInet4)
334	}
335	if ip6 := ip.To16(); ip6 != nil {
336		return cgoSockaddrInet6(ip6, zoneCache.index(zone)), C.socklen_t(syscall.SizeofSockaddrInet6)
337	}
338	return nil, 0
339}
340
341func copyIP(x IP) IP {
342	if len(x) < 16 {
343		return x.To16()
344	}
345	y := make(IP, len(x))
346	copy(y, x)
347	return y
348}
349