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