1// Copyright 2009 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 5package net 6 7import ( 8 "context" 9 "internal/syscall/windows" 10 "os" 11 "runtime" 12 "syscall" 13 "time" 14 "unsafe" 15) 16 17// cgoAvailable set to true to indicate that the cgo resolver 18// is available on Windows. Note that on Windows the cgo resolver 19// does not actually use cgo. 20const cgoAvailable = true 21 22const ( 23 _DNS_ERROR_RCODE_NAME_ERROR = syscall.Errno(9003) 24 _DNS_INFO_NO_RECORDS = syscall.Errno(9501) 25 26 _WSAHOST_NOT_FOUND = syscall.Errno(11001) 27 _WSATRY_AGAIN = syscall.Errno(11002) 28 _WSATYPE_NOT_FOUND = syscall.Errno(10109) 29) 30 31func winError(call string, err error) error { 32 switch err { 33 case _WSAHOST_NOT_FOUND, _DNS_ERROR_RCODE_NAME_ERROR, _DNS_INFO_NO_RECORDS: 34 return errNoSuchHost 35 } 36 return os.NewSyscallError(call, err) 37} 38 39func getprotobyname(name string) (proto int, err error) { 40 p, err := syscall.GetProtoByName(name) 41 if err != nil { 42 return 0, winError("getprotobyname", err) 43 } 44 return int(p.Proto), nil 45} 46 47// lookupProtocol looks up IP protocol name and returns correspondent protocol number. 48func lookupProtocol(ctx context.Context, name string) (int, error) { 49 // GetProtoByName return value is stored in thread local storage. 50 // Start new os thread before the call to prevent races. 51 type result struct { 52 proto int 53 err error 54 } 55 ch := make(chan result) // unbuffered 56 go func() { 57 if err := acquireThread(ctx); err != nil { 58 ch <- result{err: mapErr(err)} 59 return 60 } 61 defer releaseThread() 62 runtime.LockOSThread() 63 defer runtime.UnlockOSThread() 64 proto, err := getprotobyname(name) 65 select { 66 case ch <- result{proto: proto, err: err}: 67 case <-ctx.Done(): 68 } 69 }() 70 select { 71 case r := <-ch: 72 if r.err != nil { 73 if proto, err := lookupProtocolMap(name); err == nil { 74 return proto, nil 75 } 76 r.err = newDNSError(r.err, name, "") 77 } 78 return r.proto, r.err 79 case <-ctx.Done(): 80 return 0, mapErr(ctx.Err()) 81 } 82} 83 84func (r *Resolver) lookupHost(ctx context.Context, name string) ([]string, error) { 85 ips, err := r.lookupIP(ctx, "ip", name) 86 if err != nil { 87 return nil, err 88 } 89 addrs := make([]string, 0, len(ips)) 90 for _, ip := range ips { 91 addrs = append(addrs, ip.String()) 92 } 93 return addrs, nil 94} 95 96func (r *Resolver) lookupIP(ctx context.Context, network, name string) ([]IPAddr, error) { 97 if order, conf := systemConf().hostLookupOrder(r, name); order != hostLookupCgo { 98 return r.goLookupIP(ctx, network, name, order, conf) 99 } 100 101 // TODO(bradfitz,brainman): use ctx more. See TODO below. 102 103 var family int32 = syscall.AF_UNSPEC 104 switch ipVersion(network) { 105 case '4': 106 family = syscall.AF_INET 107 case '6': 108 family = syscall.AF_INET6 109 } 110 111 getaddr := func() ([]IPAddr, error) { 112 if err := acquireThread(ctx); err != nil { 113 return nil, &DNSError{ 114 Name: name, 115 Err: mapErr(err).Error(), 116 IsTimeout: ctx.Err() == context.DeadlineExceeded, 117 } 118 } 119 defer releaseThread() 120 hints := syscall.AddrinfoW{ 121 Family: family, 122 Socktype: syscall.SOCK_STREAM, 123 Protocol: syscall.IPPROTO_IP, 124 } 125 var result *syscall.AddrinfoW 126 name16p, err := syscall.UTF16PtrFromString(name) 127 if err != nil { 128 return nil, newDNSError(err, name, "") 129 } 130 131 dnsConf := getSystemDNSConfig() 132 start := time.Now() 133 134 var e error 135 for i := 0; i < dnsConf.attempts; i++ { 136 e = syscall.GetAddrInfoW(name16p, nil, &hints, &result) 137 if e == nil || e != _WSATRY_AGAIN || time.Since(start) > dnsConf.timeout { 138 break 139 } 140 } 141 if e != nil { 142 return nil, newDNSError(winError("getaddrinfow", e), name, "") 143 } 144 defer syscall.FreeAddrInfoW(result) 145 addrs := make([]IPAddr, 0, 5) 146 for ; result != nil; result = result.Next { 147 addr := unsafe.Pointer(result.Addr) 148 switch result.Family { 149 case syscall.AF_INET: 150 a := (*syscall.RawSockaddrInet4)(addr).Addr 151 addrs = append(addrs, IPAddr{IP: copyIP(a[:])}) 152 case syscall.AF_INET6: 153 a := (*syscall.RawSockaddrInet6)(addr).Addr 154 zone := zoneCache.name(int((*syscall.RawSockaddrInet6)(addr).Scope_id)) 155 addrs = append(addrs, IPAddr{IP: copyIP(a[:]), Zone: zone}) 156 default: 157 return nil, newDNSError(syscall.EWINDOWS, name, "") 158 } 159 } 160 return addrs, nil 161 } 162 163 type ret struct { 164 addrs []IPAddr 165 err error 166 } 167 168 var ch chan ret 169 if ctx.Err() == nil { 170 ch = make(chan ret, 1) 171 go func() { 172 addr, err := getaddr() 173 ch <- ret{addrs: addr, err: err} 174 }() 175 } 176 177 select { 178 case r := <-ch: 179 return r.addrs, r.err 180 case <-ctx.Done(): 181 // TODO(bradfitz,brainman): cancel the ongoing 182 // GetAddrInfoW? It would require conditionally using 183 // GetAddrInfoEx with lpOverlapped, which requires 184 // Windows 8 or newer. I guess we'll need oldLookupIP, 185 // newLookupIP, and newerLookUP. 186 // 187 // For now we just let it finish and write to the 188 // buffered channel. 189 return nil, newDNSError(mapErr(ctx.Err()), name, "") 190 } 191} 192 193func (r *Resolver) lookupPort(ctx context.Context, network, service string) (int, error) { 194 if systemConf().mustUseGoResolver(r) { 195 return lookupPortMap(network, service) 196 } 197 198 // TODO(bradfitz): finish ctx plumbing 199 if err := acquireThread(ctx); err != nil { 200 return 0, &DNSError{ 201 Name: network + "/" + service, 202 Err: mapErr(err).Error(), 203 IsTimeout: ctx.Err() == context.DeadlineExceeded, 204 } 205 } 206 defer releaseThread() 207 208 var hints syscall.AddrinfoW 209 210 switch network { 211 case "ip": // no hints 212 case "tcp", "tcp4", "tcp6": 213 hints.Socktype = syscall.SOCK_STREAM 214 hints.Protocol = syscall.IPPROTO_TCP 215 case "udp", "udp4", "udp6": 216 hints.Socktype = syscall.SOCK_DGRAM 217 hints.Protocol = syscall.IPPROTO_UDP 218 default: 219 return 0, &DNSError{Err: "unknown network", Name: network + "/" + service} 220 } 221 222 switch ipVersion(network) { 223 case '4': 224 hints.Family = syscall.AF_INET 225 case '6': 226 hints.Family = syscall.AF_INET6 227 } 228 229 var result *syscall.AddrinfoW 230 e := syscall.GetAddrInfoW(nil, syscall.StringToUTF16Ptr(service), &hints, &result) 231 if e != nil { 232 if port, err := lookupPortMap(network, service); err == nil { 233 return port, nil 234 } 235 236 // The _WSATYPE_NOT_FOUND error is returned by GetAddrInfoW 237 // when the service name is unknown. We are also checking 238 // for _WSAHOST_NOT_FOUND here to match the cgo (unix) version 239 // cgo_unix.go (cgoLookupServicePort). 240 if e == _WSATYPE_NOT_FOUND || e == _WSAHOST_NOT_FOUND { 241 return 0, newDNSError(errUnknownPort, network+"/"+service, "") 242 } 243 return 0, newDNSError(winError("getaddrinfow", e), network+"/"+service, "") 244 } 245 defer syscall.FreeAddrInfoW(result) 246 if result == nil { 247 return 0, newDNSError(syscall.EINVAL, network+"/"+service, "") 248 } 249 addr := unsafe.Pointer(result.Addr) 250 switch result.Family { 251 case syscall.AF_INET: 252 a := (*syscall.RawSockaddrInet4)(addr) 253 return int(syscall.Ntohs(a.Port)), nil 254 case syscall.AF_INET6: 255 a := (*syscall.RawSockaddrInet6)(addr) 256 return int(syscall.Ntohs(a.Port)), nil 257 } 258 return 0, newDNSError(syscall.EINVAL, network+"/"+service, "") 259} 260 261func (r *Resolver) lookupCNAME(ctx context.Context, name string) (string, error) { 262 if order, conf := systemConf().hostLookupOrder(r, name); order != hostLookupCgo { 263 return r.goLookupCNAME(ctx, name, order, conf) 264 } 265 266 // TODO(bradfitz): finish ctx plumbing 267 if err := acquireThread(ctx); err != nil { 268 return "", &DNSError{ 269 Name: name, 270 Err: mapErr(err).Error(), 271 IsTimeout: ctx.Err() == context.DeadlineExceeded, 272 } 273 } 274 defer releaseThread() 275 var rec *syscall.DNSRecord 276 e := syscall.DnsQuery(name, syscall.DNS_TYPE_CNAME, 0, nil, &rec, nil) 277 // windows returns DNS_INFO_NO_RECORDS if there are no CNAME-s 278 if errno, ok := e.(syscall.Errno); ok && errno == syscall.DNS_INFO_NO_RECORDS { 279 // if there are no aliases, the canonical name is the input name 280 return absDomainName(name), nil 281 } 282 if e != nil { 283 return "", newDNSError(winError("dnsquery", e), name, "") 284 } 285 defer syscall.DnsRecordListFree(rec, 1) 286 287 resolved := resolveCNAME(syscall.StringToUTF16Ptr(name), rec) 288 cname := windows.UTF16PtrToString(resolved) 289 return absDomainName(cname), nil 290} 291 292func (r *Resolver) lookupSRV(ctx context.Context, service, proto, name string) (string, []*SRV, error) { 293 if systemConf().mustUseGoResolver(r) { 294 return r.goLookupSRV(ctx, service, proto, name) 295 } 296 // TODO(bradfitz): finish ctx plumbing 297 if err := acquireThread(ctx); err != nil { 298 return "", nil, &DNSError{ 299 Name: name, 300 Err: mapErr(err).Error(), 301 IsTimeout: ctx.Err() == context.DeadlineExceeded, 302 } 303 } 304 defer releaseThread() 305 var target string 306 if service == "" && proto == "" { 307 target = name 308 } else { 309 target = "_" + service + "._" + proto + "." + name 310 } 311 var rec *syscall.DNSRecord 312 e := syscall.DnsQuery(target, syscall.DNS_TYPE_SRV, 0, nil, &rec, nil) 313 if e != nil { 314 return "", nil, newDNSError(winError("dnsquery", e), name, "") 315 } 316 defer syscall.DnsRecordListFree(rec, 1) 317 318 srvs := make([]*SRV, 0, 10) 319 for _, p := range validRecs(rec, syscall.DNS_TYPE_SRV, target) { 320 v := (*syscall.DNSSRVData)(unsafe.Pointer(&p.Data[0])) 321 srvs = append(srvs, &SRV{absDomainName(syscall.UTF16ToString((*[256]uint16)(unsafe.Pointer(v.Target))[:])), v.Port, v.Priority, v.Weight}) 322 } 323 byPriorityWeight(srvs).sort() 324 return absDomainName(target), srvs, nil 325} 326 327func (r *Resolver) lookupMX(ctx context.Context, name string) ([]*MX, error) { 328 if systemConf().mustUseGoResolver(r) { 329 return r.goLookupMX(ctx, name) 330 } 331 // TODO(bradfitz): finish ctx plumbing. 332 if err := acquireThread(ctx); err != nil { 333 return nil, &DNSError{ 334 Name: name, 335 Err: mapErr(err).Error(), 336 IsTimeout: ctx.Err() == context.DeadlineExceeded, 337 } 338 } 339 defer releaseThread() 340 var rec *syscall.DNSRecord 341 e := syscall.DnsQuery(name, syscall.DNS_TYPE_MX, 0, nil, &rec, nil) 342 if e != nil { 343 return nil, newDNSError(winError("dnsquery", e), name, "") 344 } 345 defer syscall.DnsRecordListFree(rec, 1) 346 347 mxs := make([]*MX, 0, 10) 348 for _, p := range validRecs(rec, syscall.DNS_TYPE_MX, name) { 349 v := (*syscall.DNSMXData)(unsafe.Pointer(&p.Data[0])) 350 mxs = append(mxs, &MX{absDomainName(windows.UTF16PtrToString(v.NameExchange)), v.Preference}) 351 } 352 byPref(mxs).sort() 353 return mxs, nil 354} 355 356func (r *Resolver) lookupNS(ctx context.Context, name string) ([]*NS, error) { 357 if systemConf().mustUseGoResolver(r) { 358 return r.goLookupNS(ctx, name) 359 } 360 // TODO(bradfitz): finish ctx plumbing. 361 if err := acquireThread(ctx); err != nil { 362 return nil, &DNSError{ 363 Name: name, 364 Err: mapErr(err).Error(), 365 IsTimeout: ctx.Err() == context.DeadlineExceeded, 366 } 367 } 368 defer releaseThread() 369 var rec *syscall.DNSRecord 370 e := syscall.DnsQuery(name, syscall.DNS_TYPE_NS, 0, nil, &rec, nil) 371 if e != nil { 372 return nil, newDNSError(winError("dnsquery", e), name, "") 373 } 374 defer syscall.DnsRecordListFree(rec, 1) 375 376 nss := make([]*NS, 0, 10) 377 for _, p := range validRecs(rec, syscall.DNS_TYPE_NS, name) { 378 v := (*syscall.DNSPTRData)(unsafe.Pointer(&p.Data[0])) 379 nss = append(nss, &NS{absDomainName(syscall.UTF16ToString((*[256]uint16)(unsafe.Pointer(v.Host))[:]))}) 380 } 381 return nss, nil 382} 383 384func (r *Resolver) lookupTXT(ctx context.Context, name string) ([]string, error) { 385 if systemConf().mustUseGoResolver(r) { 386 return r.goLookupTXT(ctx, name) 387 } 388 // TODO(bradfitz): finish ctx plumbing. 389 if err := acquireThread(ctx); err != nil { 390 return nil, &DNSError{ 391 Name: name, 392 Err: mapErr(err).Error(), 393 IsTimeout: ctx.Err() == context.DeadlineExceeded, 394 } 395 } 396 defer releaseThread() 397 var rec *syscall.DNSRecord 398 e := syscall.DnsQuery(name, syscall.DNS_TYPE_TEXT, 0, nil, &rec, nil) 399 if e != nil { 400 return nil, newDNSError(winError("dnsquery", e), name, "") 401 } 402 defer syscall.DnsRecordListFree(rec, 1) 403 404 txts := make([]string, 0, 10) 405 for _, p := range validRecs(rec, syscall.DNS_TYPE_TEXT, name) { 406 d := (*syscall.DNSTXTData)(unsafe.Pointer(&p.Data[0])) 407 s := "" 408 for _, v := range (*[1 << 10]*uint16)(unsafe.Pointer(&(d.StringArray[0])))[:d.StringCount:d.StringCount] { 409 s += windows.UTF16PtrToString(v) 410 } 411 txts = append(txts, s) 412 } 413 return txts, nil 414} 415 416func (r *Resolver) lookupAddr(ctx context.Context, addr string) ([]string, error) { 417 if order, conf := systemConf().addrLookupOrder(r, addr); order != hostLookupCgo { 418 return r.goLookupPTR(ctx, addr, order, conf) 419 } 420 421 // TODO(bradfitz): finish ctx plumbing. 422 if err := acquireThread(ctx); err != nil { 423 return nil, &DNSError{ 424 Name: addr, 425 Err: mapErr(err).Error(), 426 IsTimeout: ctx.Err() == context.DeadlineExceeded, 427 } 428 } 429 defer releaseThread() 430 arpa, err := reverseaddr(addr) 431 if err != nil { 432 return nil, err 433 } 434 var rec *syscall.DNSRecord 435 e := syscall.DnsQuery(arpa, syscall.DNS_TYPE_PTR, 0, nil, &rec, nil) 436 if e != nil { 437 return nil, newDNSError(winError("dnsquery", e), addr, "") 438 } 439 defer syscall.DnsRecordListFree(rec, 1) 440 441 ptrs := make([]string, 0, 10) 442 for _, p := range validRecs(rec, syscall.DNS_TYPE_PTR, arpa) { 443 v := (*syscall.DNSPTRData)(unsafe.Pointer(&p.Data[0])) 444 ptrs = append(ptrs, absDomainName(windows.UTF16PtrToString(v.Host))) 445 } 446 return ptrs, nil 447} 448 449const dnsSectionMask = 0x0003 450 451// returns only results applicable to name and resolves CNAME entries. 452func validRecs(r *syscall.DNSRecord, dnstype uint16, name string) []*syscall.DNSRecord { 453 cname := syscall.StringToUTF16Ptr(name) 454 if dnstype != syscall.DNS_TYPE_CNAME { 455 cname = resolveCNAME(cname, r) 456 } 457 rec := make([]*syscall.DNSRecord, 0, 10) 458 for p := r; p != nil; p = p.Next { 459 // in case of a local machine, DNS records are returned with DNSREC_QUESTION flag instead of DNS_ANSWER 460 if p.Dw&dnsSectionMask != syscall.DnsSectionAnswer && p.Dw&dnsSectionMask != syscall.DnsSectionQuestion { 461 continue 462 } 463 if p.Type != dnstype { 464 continue 465 } 466 if !syscall.DnsNameCompare(cname, p.Name) { 467 continue 468 } 469 rec = append(rec, p) 470 } 471 return rec 472} 473 474// returns the last CNAME in chain. 475func resolveCNAME(name *uint16, r *syscall.DNSRecord) *uint16 { 476 // limit cname resolving to 10 in case of an infinite CNAME loop 477Cname: 478 for cnameloop := 0; cnameloop < 10; cnameloop++ { 479 for p := r; p != nil; p = p.Next { 480 if p.Dw&dnsSectionMask != syscall.DnsSectionAnswer { 481 continue 482 } 483 if p.Type != syscall.DNS_TYPE_CNAME { 484 continue 485 } 486 if !syscall.DnsNameCompare(name, p.Name) { 487 continue 488 } 489 name = (*syscall.DNSPTRData)(unsafe.Pointer(&r.Data[0])).Host 490 continue Cname 491 } 492 break 493 } 494 return name 495} 496 497// concurrentThreadsLimit returns the number of threads we permit to 498// run concurrently doing DNS lookups. 499func concurrentThreadsLimit() int { 500 return 500 501} 502