diff options
Diffstat (limited to 'src/net/lookup_windows.go')
-rw-r--r-- | src/net/lookup_windows.go | 435 |
1 files changed, 435 insertions, 0 deletions
diff --git a/src/net/lookup_windows.go b/src/net/lookup_windows.go new file mode 100644 index 0000000..9ff39c7 --- /dev/null +++ b/src/net/lookup_windows.go @@ -0,0 +1,435 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package net + +import ( + "context" + "internal/syscall/windows" + "os" + "runtime" + "syscall" + "unsafe" +) + +const _WSAHOST_NOT_FOUND = syscall.Errno(11001) + +func winError(call string, err error) error { + switch err { + case _WSAHOST_NOT_FOUND: + return errNoSuchHost + } + return os.NewSyscallError(call, err) +} + +func getprotobyname(name string) (proto int, err error) { + p, err := syscall.GetProtoByName(name) + if err != nil { + return 0, winError("getprotobyname", err) + } + return int(p.Proto), nil +} + +// lookupProtocol looks up IP protocol name and returns correspondent protocol number. +func lookupProtocol(ctx context.Context, name string) (int, error) { + // GetProtoByName return value is stored in thread local storage. + // Start new os thread before the call to prevent races. + type result struct { + proto int + err error + } + ch := make(chan result) // unbuffered + go func() { + acquireThread() + defer releaseThread() + runtime.LockOSThread() + defer runtime.UnlockOSThread() + proto, err := getprotobyname(name) + select { + case ch <- result{proto: proto, err: err}: + case <-ctx.Done(): + } + }() + select { + case r := <-ch: + if r.err != nil { + if proto, err := lookupProtocolMap(name); err == nil { + return proto, nil + } + + dnsError := &DNSError{Err: r.err.Error(), Name: name} + if r.err == errNoSuchHost { + dnsError.IsNotFound = true + } + r.err = dnsError + } + return r.proto, r.err + case <-ctx.Done(): + return 0, mapErr(ctx.Err()) + } +} + +func (r *Resolver) lookupHost(ctx context.Context, name string) ([]string, error) { + ips, err := r.lookupIP(ctx, "ip", name) + if err != nil { + return nil, err + } + addrs := make([]string, 0, len(ips)) + for _, ip := range ips { + addrs = append(addrs, ip.String()) + } + return addrs, nil +} + +// preferGoOverWindows reports whether the resolver should use the +// pure Go implementation rather than making win32 calls to ask the +// kernel for its answer. +func (r *Resolver) preferGoOverWindows() bool { + conf := systemConf() + order := conf.hostLookupOrder(r, "") // name is unused + return order != hostLookupCgo +} + +func (r *Resolver) lookupIP(ctx context.Context, network, name string) ([]IPAddr, error) { + if r.preferGoOverWindows() { + return r.goLookupIP(ctx, network, name) + } + // TODO(bradfitz,brainman): use ctx more. See TODO below. + + var family int32 = syscall.AF_UNSPEC + switch ipVersion(network) { + case '4': + family = syscall.AF_INET + case '6': + family = syscall.AF_INET6 + } + + getaddr := func() ([]IPAddr, error) { + acquireThread() + defer releaseThread() + hints := syscall.AddrinfoW{ + Family: family, + Socktype: syscall.SOCK_STREAM, + Protocol: syscall.IPPROTO_IP, + } + var result *syscall.AddrinfoW + name16p, err := syscall.UTF16PtrFromString(name) + if err != nil { + return nil, &DNSError{Name: name, Err: err.Error()} + } + e := syscall.GetAddrInfoW(name16p, nil, &hints, &result) + if e != nil { + err := winError("getaddrinfow", e) + dnsError := &DNSError{Err: err.Error(), Name: name} + if err == errNoSuchHost { + dnsError.IsNotFound = true + } + return nil, dnsError + } + defer syscall.FreeAddrInfoW(result) + addrs := make([]IPAddr, 0, 5) + for ; result != nil; result = result.Next { + addr := unsafe.Pointer(result.Addr) + switch result.Family { + case syscall.AF_INET: + a := (*syscall.RawSockaddrInet4)(addr).Addr + addrs = append(addrs, IPAddr{IP: IPv4(a[0], a[1], a[2], a[3])}) + case syscall.AF_INET6: + a := (*syscall.RawSockaddrInet6)(addr).Addr + zone := zoneCache.name(int((*syscall.RawSockaddrInet6)(addr).Scope_id)) + addrs = append(addrs, IPAddr{IP: IP{a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]}, Zone: zone}) + default: + return nil, &DNSError{Err: syscall.EWINDOWS.Error(), Name: name} + } + } + return addrs, nil + } + + type ret struct { + addrs []IPAddr + err error + } + + var ch chan ret + if ctx.Err() == nil { + ch = make(chan ret, 1) + go func() { + addr, err := getaddr() + ch <- ret{addrs: addr, err: err} + }() + } + + select { + case r := <-ch: + return r.addrs, r.err + case <-ctx.Done(): + // TODO(bradfitz,brainman): cancel the ongoing + // GetAddrInfoW? It would require conditionally using + // GetAddrInfoEx with lpOverlapped, which requires + // Windows 8 or newer. I guess we'll need oldLookupIP, + // newLookupIP, and newerLookUP. + // + // For now we just let it finish and write to the + // buffered channel. + return nil, &DNSError{ + Name: name, + Err: ctx.Err().Error(), + IsTimeout: ctx.Err() == context.DeadlineExceeded, + } + } +} + +func (r *Resolver) lookupPort(ctx context.Context, network, service string) (int, error) { + if r.preferGoOverWindows() { + return lookupPortMap(network, service) + } + + // TODO(bradfitz): finish ctx plumbing. Nothing currently depends on this. + acquireThread() + defer releaseThread() + var stype int32 + switch network { + case "tcp4", "tcp6": + stype = syscall.SOCK_STREAM + case "udp4", "udp6": + stype = syscall.SOCK_DGRAM + } + hints := syscall.AddrinfoW{ + Family: syscall.AF_UNSPEC, + Socktype: stype, + Protocol: syscall.IPPROTO_IP, + } + var result *syscall.AddrinfoW + e := syscall.GetAddrInfoW(nil, syscall.StringToUTF16Ptr(service), &hints, &result) + if e != nil { + if port, err := lookupPortMap(network, service); err == nil { + return port, nil + } + err := winError("getaddrinfow", e) + dnsError := &DNSError{Err: err.Error(), Name: network + "/" + service} + if err == errNoSuchHost { + dnsError.IsNotFound = true + } + return 0, dnsError + } + defer syscall.FreeAddrInfoW(result) + if result == nil { + return 0, &DNSError{Err: syscall.EINVAL.Error(), Name: network + "/" + service} + } + addr := unsafe.Pointer(result.Addr) + switch result.Family { + case syscall.AF_INET: + a := (*syscall.RawSockaddrInet4)(addr) + return int(syscall.Ntohs(a.Port)), nil + case syscall.AF_INET6: + a := (*syscall.RawSockaddrInet6)(addr) + return int(syscall.Ntohs(a.Port)), nil + } + return 0, &DNSError{Err: syscall.EINVAL.Error(), Name: network + "/" + service} +} + +func (r *Resolver) lookupCNAME(ctx context.Context, name string) (string, error) { + if r.preferGoOverWindows() { + return r.goLookupCNAME(ctx, name) + } + // TODO(bradfitz): finish ctx plumbing. Nothing currently depends on this. + acquireThread() + defer releaseThread() + var rec *syscall.DNSRecord + e := syscall.DnsQuery(name, syscall.DNS_TYPE_CNAME, 0, nil, &rec, nil) + // windows returns DNS_INFO_NO_RECORDS if there are no CNAME-s + if errno, ok := e.(syscall.Errno); ok && errno == syscall.DNS_INFO_NO_RECORDS { + // if there are no aliases, the canonical name is the input name + return absDomainName(name), nil + } + if e != nil { + return "", &DNSError{Err: winError("dnsquery", e).Error(), Name: name} + } + defer syscall.DnsRecordListFree(rec, 1) + + resolved := resolveCNAME(syscall.StringToUTF16Ptr(name), rec) + cname := windows.UTF16PtrToString(resolved) + return absDomainName(cname), nil +} + +func (r *Resolver) lookupSRV(ctx context.Context, service, proto, name string) (string, []*SRV, error) { + if r.preferGoOverWindows() { + return r.goLookupSRV(ctx, service, proto, name) + } + // TODO(bradfitz): finish ctx plumbing. Nothing currently depends on this. + acquireThread() + defer releaseThread() + var target string + if service == "" && proto == "" { + target = name + } else { + target = "_" + service + "._" + proto + "." + name + } + var rec *syscall.DNSRecord + e := syscall.DnsQuery(target, syscall.DNS_TYPE_SRV, 0, nil, &rec, nil) + if e != nil { + return "", nil, &DNSError{Err: winError("dnsquery", e).Error(), Name: target} + } + defer syscall.DnsRecordListFree(rec, 1) + + srvs := make([]*SRV, 0, 10) + for _, p := range validRecs(rec, syscall.DNS_TYPE_SRV, target) { + v := (*syscall.DNSSRVData)(unsafe.Pointer(&p.Data[0])) + srvs = append(srvs, &SRV{absDomainName(syscall.UTF16ToString((*[256]uint16)(unsafe.Pointer(v.Target))[:])), v.Port, v.Priority, v.Weight}) + } + byPriorityWeight(srvs).sort() + return absDomainName(target), srvs, nil +} + +func (r *Resolver) lookupMX(ctx context.Context, name string) ([]*MX, error) { + if r.preferGoOverWindows() { + return r.goLookupMX(ctx, name) + } + // TODO(bradfitz): finish ctx plumbing. Nothing currently depends on this. + acquireThread() + defer releaseThread() + var rec *syscall.DNSRecord + e := syscall.DnsQuery(name, syscall.DNS_TYPE_MX, 0, nil, &rec, nil) + if e != nil { + return nil, &DNSError{Err: winError("dnsquery", e).Error(), Name: name} + } + defer syscall.DnsRecordListFree(rec, 1) + + mxs := make([]*MX, 0, 10) + for _, p := range validRecs(rec, syscall.DNS_TYPE_MX, name) { + v := (*syscall.DNSMXData)(unsafe.Pointer(&p.Data[0])) + mxs = append(mxs, &MX{absDomainName(windows.UTF16PtrToString(v.NameExchange)), v.Preference}) + } + byPref(mxs).sort() + return mxs, nil +} + +func (r *Resolver) lookupNS(ctx context.Context, name string) ([]*NS, error) { + if r.preferGoOverWindows() { + return r.goLookupNS(ctx, name) + } + // TODO(bradfitz): finish ctx plumbing. Nothing currently depends on this. + acquireThread() + defer releaseThread() + var rec *syscall.DNSRecord + e := syscall.DnsQuery(name, syscall.DNS_TYPE_NS, 0, nil, &rec, nil) + if e != nil { + return nil, &DNSError{Err: winError("dnsquery", e).Error(), Name: name} + } + defer syscall.DnsRecordListFree(rec, 1) + + nss := make([]*NS, 0, 10) + for _, p := range validRecs(rec, syscall.DNS_TYPE_NS, name) { + v := (*syscall.DNSPTRData)(unsafe.Pointer(&p.Data[0])) + nss = append(nss, &NS{absDomainName(syscall.UTF16ToString((*[256]uint16)(unsafe.Pointer(v.Host))[:]))}) + } + return nss, nil +} + +func (r *Resolver) lookupTXT(ctx context.Context, name string) ([]string, error) { + if r.preferGoOverWindows() { + return r.goLookupTXT(ctx, name) + } + // TODO(bradfitz): finish ctx plumbing. Nothing currently depends on this. + acquireThread() + defer releaseThread() + var rec *syscall.DNSRecord + e := syscall.DnsQuery(name, syscall.DNS_TYPE_TEXT, 0, nil, &rec, nil) + if e != nil { + return nil, &DNSError{Err: winError("dnsquery", e).Error(), Name: name} + } + defer syscall.DnsRecordListFree(rec, 1) + + txts := make([]string, 0, 10) + for _, p := range validRecs(rec, syscall.DNS_TYPE_TEXT, name) { + d := (*syscall.DNSTXTData)(unsafe.Pointer(&p.Data[0])) + s := "" + for _, v := range (*[1 << 10]*uint16)(unsafe.Pointer(&(d.StringArray[0])))[:d.StringCount:d.StringCount] { + s += windows.UTF16PtrToString(v) + } + txts = append(txts, s) + } + return txts, nil +} + +func (r *Resolver) lookupAddr(ctx context.Context, addr string) ([]string, error) { + if r.preferGoOverWindows() { + return r.goLookupPTR(ctx, addr) + } + + // TODO(bradfitz): finish ctx plumbing. Nothing currently depends on this. + acquireThread() + defer releaseThread() + arpa, err := reverseaddr(addr) + if err != nil { + return nil, err + } + var rec *syscall.DNSRecord + e := syscall.DnsQuery(arpa, syscall.DNS_TYPE_PTR, 0, nil, &rec, nil) + if e != nil { + return nil, &DNSError{Err: winError("dnsquery", e).Error(), Name: addr} + } + defer syscall.DnsRecordListFree(rec, 1) + + ptrs := make([]string, 0, 10) + for _, p := range validRecs(rec, syscall.DNS_TYPE_PTR, arpa) { + v := (*syscall.DNSPTRData)(unsafe.Pointer(&p.Data[0])) + ptrs = append(ptrs, absDomainName(windows.UTF16PtrToString(v.Host))) + } + return ptrs, nil +} + +const dnsSectionMask = 0x0003 + +// returns only results applicable to name and resolves CNAME entries +func validRecs(r *syscall.DNSRecord, dnstype uint16, name string) []*syscall.DNSRecord { + cname := syscall.StringToUTF16Ptr(name) + if dnstype != syscall.DNS_TYPE_CNAME { + cname = resolveCNAME(cname, r) + } + rec := make([]*syscall.DNSRecord, 0, 10) + for p := r; p != nil; p = p.Next { + // in case of a local machine, DNS records are returned with DNSREC_QUESTION flag instead of DNS_ANSWER + if p.Dw&dnsSectionMask != syscall.DnsSectionAnswer && p.Dw&dnsSectionMask != syscall.DnsSectionQuestion { + continue + } + if p.Type != dnstype { + continue + } + if !syscall.DnsNameCompare(cname, p.Name) { + continue + } + rec = append(rec, p) + } + return rec +} + +// returns the last CNAME in chain +func resolveCNAME(name *uint16, r *syscall.DNSRecord) *uint16 { + // limit cname resolving to 10 in case of an infinite CNAME loop +Cname: + for cnameloop := 0; cnameloop < 10; cnameloop++ { + for p := r; p != nil; p = p.Next { + if p.Dw&dnsSectionMask != syscall.DnsSectionAnswer { + continue + } + if p.Type != syscall.DNS_TYPE_CNAME { + continue + } + if !syscall.DnsNameCompare(name, p.Name) { + continue + } + name = (*syscall.DNSPTRData)(unsafe.Pointer(&r.Data[0])).Host + continue Cname + } + break + } + return name +} + +// concurrentThreadsLimit returns the number of threads we permit to +// run concurrently doing DNS lookups. +func concurrentThreadsLimit() int { + return 500 +} |