Source file src/net/lookup_windows.go

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

View as plain text