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