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