github.com/dara-project/godist@v0.0.0-20200823115410-e0c80c8f0c78/src/net/net_windows_test.go (about)

     1  // Copyright 2014 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  	"bufio"
     9  	"bytes"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"os"
    14  	"os/exec"
    15  	"regexp"
    16  	"sort"
    17  	"strings"
    18  	"syscall"
    19  	"testing"
    20  	"time"
    21  )
    22  
    23  func toErrno(err error) (syscall.Errno, bool) {
    24  	operr, ok := err.(*OpError)
    25  	if !ok {
    26  		return 0, false
    27  	}
    28  	syserr, ok := operr.Err.(*os.SyscallError)
    29  	if !ok {
    30  		return 0, false
    31  	}
    32  	errno, ok := syserr.Err.(syscall.Errno)
    33  	if !ok {
    34  		return 0, false
    35  	}
    36  	return errno, true
    37  }
    38  
    39  // TestAcceptIgnoreSomeErrors tests that windows TCPListener.AcceptTCP
    40  // handles broken connections. It verifies that broken connections do
    41  // not affect future connections.
    42  func TestAcceptIgnoreSomeErrors(t *testing.T) {
    43  	recv := func(ln Listener, ignoreSomeReadErrors bool) (string, error) {
    44  		c, err := ln.Accept()
    45  		if err != nil {
    46  			// Display windows errno in error message.
    47  			errno, ok := toErrno(err)
    48  			if !ok {
    49  				return "", err
    50  			}
    51  			return "", fmt.Errorf("%v (windows errno=%d)", err, errno)
    52  		}
    53  		defer c.Close()
    54  
    55  		b := make([]byte, 100)
    56  		n, err := c.Read(b)
    57  		if err == nil || err == io.EOF {
    58  			return string(b[:n]), nil
    59  		}
    60  		errno, ok := toErrno(err)
    61  		if ok && ignoreSomeReadErrors && (errno == syscall.ERROR_NETNAME_DELETED || errno == syscall.WSAECONNRESET) {
    62  			return "", nil
    63  		}
    64  		return "", err
    65  	}
    66  
    67  	send := func(addr string, data string) error {
    68  		c, err := Dial("tcp", addr)
    69  		if err != nil {
    70  			return err
    71  		}
    72  		defer c.Close()
    73  
    74  		b := []byte(data)
    75  		n, err := c.Write(b)
    76  		if err != nil {
    77  			return err
    78  		}
    79  		if n != len(b) {
    80  			return fmt.Errorf(`Only %d chars of string "%s" sent`, n, data)
    81  		}
    82  		return nil
    83  	}
    84  
    85  	if envaddr := os.Getenv("GOTEST_DIAL_ADDR"); envaddr != "" {
    86  		// In child process.
    87  		c, err := Dial("tcp", envaddr)
    88  		if err != nil {
    89  			t.Fatal(err)
    90  		}
    91  		fmt.Printf("sleeping\n")
    92  		time.Sleep(time.Minute) // process will be killed here
    93  		c.Close()
    94  	}
    95  
    96  	ln, err := Listen("tcp", "127.0.0.1:0")
    97  	if err != nil {
    98  		t.Fatal(err)
    99  	}
   100  	defer ln.Close()
   101  
   102  	// Start child process that connects to our listener.
   103  	cmd := exec.Command(os.Args[0], "-test.run=TestAcceptIgnoreSomeErrors")
   104  	cmd.Env = append(os.Environ(), "GOTEST_DIAL_ADDR="+ln.Addr().String())
   105  	stdout, err := cmd.StdoutPipe()
   106  	if err != nil {
   107  		t.Fatalf("cmd.StdoutPipe failed: %v", err)
   108  	}
   109  	err = cmd.Start()
   110  	if err != nil {
   111  		t.Fatalf("cmd.Start failed: %v\n", err)
   112  	}
   113  	outReader := bufio.NewReader(stdout)
   114  	for {
   115  		s, err := outReader.ReadString('\n')
   116  		if err != nil {
   117  			t.Fatalf("reading stdout failed: %v", err)
   118  		}
   119  		if s == "sleeping\n" {
   120  			break
   121  		}
   122  	}
   123  	defer cmd.Wait() // ignore error - we know it is getting killed
   124  
   125  	const alittle = 100 * time.Millisecond
   126  	time.Sleep(alittle)
   127  	cmd.Process.Kill() // the only way to trigger the errors
   128  	time.Sleep(alittle)
   129  
   130  	// Send second connection data (with delay in a separate goroutine).
   131  	result := make(chan error)
   132  	go func() {
   133  		time.Sleep(alittle)
   134  		err := send(ln.Addr().String(), "abc")
   135  		if err != nil {
   136  			result <- err
   137  		}
   138  		result <- nil
   139  	}()
   140  	defer func() {
   141  		err := <-result
   142  		if err != nil {
   143  			t.Fatalf("send failed: %v", err)
   144  		}
   145  	}()
   146  
   147  	// Receive first or second connection.
   148  	s, err := recv(ln, true)
   149  	if err != nil {
   150  		t.Fatalf("recv failed: %v", err)
   151  	}
   152  	switch s {
   153  	case "":
   154  		// First connection data is received, let's get second connection data.
   155  	case "abc":
   156  		// First connection is lost forever, but that is ok.
   157  		return
   158  	default:
   159  		t.Fatalf(`"%s" received from recv, but "" or "abc" expected`, s)
   160  	}
   161  
   162  	// Get second connection data.
   163  	s, err = recv(ln, false)
   164  	if err != nil {
   165  		t.Fatalf("recv failed: %v", err)
   166  	}
   167  	if s != "abc" {
   168  		t.Fatalf(`"%s" received from recv, but "abc" expected`, s)
   169  	}
   170  }
   171  
   172  func isWindowsXP(t *testing.T) bool {
   173  	v, err := syscall.GetVersion()
   174  	if err != nil {
   175  		t.Fatalf("GetVersion failed: %v", err)
   176  	}
   177  	major := byte(v)
   178  	return major < 6
   179  }
   180  
   181  func runCmd(args ...string) ([]byte, error) {
   182  	removeUTF8BOM := func(b []byte) []byte {
   183  		if len(b) >= 3 && b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF {
   184  			return b[3:]
   185  		}
   186  		return b
   187  	}
   188  	f, err := ioutil.TempFile("", "netcmd")
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  	f.Close()
   193  	defer os.Remove(f.Name())
   194  	cmd := fmt.Sprintf(`%s | Out-File "%s" -encoding UTF8`, strings.Join(args, " "), f.Name())
   195  	out, err := exec.Command("powershell", "-Command", cmd).CombinedOutput()
   196  	if err != nil {
   197  		if len(out) != 0 {
   198  			return nil, fmt.Errorf("%s failed: %v: %q", args[0], err, string(removeUTF8BOM(out)))
   199  		}
   200  		var err2 error
   201  		out, err2 = ioutil.ReadFile(f.Name())
   202  		if err2 != nil {
   203  			return nil, err2
   204  		}
   205  		if len(out) != 0 {
   206  			return nil, fmt.Errorf("%s failed: %v: %q", args[0], err, string(removeUTF8BOM(out)))
   207  		}
   208  		return nil, fmt.Errorf("%s failed: %v", args[0], err)
   209  	}
   210  	out, err = ioutil.ReadFile(f.Name())
   211  	if err != nil {
   212  		return nil, err
   213  	}
   214  	return removeUTF8BOM(out), nil
   215  }
   216  
   217  func netshSpeaksEnglish(t *testing.T) bool {
   218  	out, err := runCmd("netsh", "help")
   219  	if err != nil {
   220  		t.Fatal(err)
   221  	}
   222  	return bytes.Contains(out, []byte("The following commands are available:"))
   223  }
   224  
   225  func netshInterfaceIPShowInterface(ipver string, ifaces map[string]bool) error {
   226  	out, err := runCmd("netsh", "interface", ipver, "show", "interface", "level=verbose")
   227  	if err != nil {
   228  		return err
   229  	}
   230  	// interface information is listed like:
   231  	//
   232  	//Interface Local Area Connection Parameters
   233  	//----------------------------------------------
   234  	//IfLuid                             : ethernet_6
   235  	//IfIndex                            : 11
   236  	//State                              : connected
   237  	//Metric                             : 10
   238  	//...
   239  	var name string
   240  	lines := bytes.Split(out, []byte{'\r', '\n'})
   241  	for _, line := range lines {
   242  		if bytes.HasPrefix(line, []byte("Interface ")) && bytes.HasSuffix(line, []byte(" Parameters")) {
   243  			f := line[len("Interface "):]
   244  			f = f[:len(f)-len(" Parameters")]
   245  			name = string(f)
   246  			continue
   247  		}
   248  		var isup bool
   249  		switch string(line) {
   250  		case "State                              : connected":
   251  			isup = true
   252  		case "State                              : disconnected":
   253  			isup = false
   254  		default:
   255  			continue
   256  		}
   257  		if name != "" {
   258  			if v, ok := ifaces[name]; ok && v != isup {
   259  				return fmt.Errorf("%s:%s isup=%v: ipv4 and ipv6 report different interface state", ipver, name, isup)
   260  			}
   261  			ifaces[name] = isup
   262  			name = ""
   263  		}
   264  	}
   265  	return nil
   266  }
   267  
   268  func TestInterfacesWithNetsh(t *testing.T) {
   269  	if isWindowsXP(t) {
   270  		t.Skip("Windows XP netsh command does not provide required functionality")
   271  	}
   272  	if !netshSpeaksEnglish(t) {
   273  		t.Skip("English version of netsh required for this test")
   274  	}
   275  
   276  	toString := func(name string, isup bool) string {
   277  		if isup {
   278  			return name + ":up"
   279  		}
   280  		return name + ":down"
   281  	}
   282  
   283  	ift, err := Interfaces()
   284  	if err != nil {
   285  		t.Fatal(err)
   286  	}
   287  	have := make([]string, 0)
   288  	for _, ifi := range ift {
   289  		have = append(have, toString(ifi.Name, ifi.Flags&FlagUp != 0))
   290  	}
   291  	sort.Strings(have)
   292  
   293  	ifaces := make(map[string]bool)
   294  	err = netshInterfaceIPShowInterface("ipv6", ifaces)
   295  	if err != nil {
   296  		t.Fatal(err)
   297  	}
   298  	err = netshInterfaceIPShowInterface("ipv4", ifaces)
   299  	if err != nil {
   300  		t.Fatal(err)
   301  	}
   302  	want := make([]string, 0)
   303  	for name, isup := range ifaces {
   304  		want = append(want, toString(name, isup))
   305  	}
   306  	sort.Strings(want)
   307  
   308  	if strings.Join(want, "/") != strings.Join(have, "/") {
   309  		t.Fatalf("unexpected interface list %q, want %q", have, want)
   310  	}
   311  }
   312  
   313  func netshInterfaceIPv4ShowAddress(name string, netshOutput []byte) []string {
   314  	// Address information is listed like:
   315  	//
   316  	//Configuration for interface "Local Area Connection"
   317  	//    DHCP enabled:                         Yes
   318  	//    IP Address:                           10.0.0.2
   319  	//    Subnet Prefix:                        10.0.0.0/24 (mask 255.255.255.0)
   320  	//    IP Address:                           10.0.0.3
   321  	//    Subnet Prefix:                        10.0.0.0/24 (mask 255.255.255.0)
   322  	//    Default Gateway:                      10.0.0.254
   323  	//    Gateway Metric:                       0
   324  	//    InterfaceMetric:                      10
   325  	//
   326  	//Configuration for interface "Loopback Pseudo-Interface 1"
   327  	//    DHCP enabled:                         No
   328  	//    IP Address:                           127.0.0.1
   329  	//    Subnet Prefix:                        127.0.0.0/8 (mask 255.0.0.0)
   330  	//    InterfaceMetric:                      50
   331  	//
   332  	addrs := make([]string, 0)
   333  	var addr, subnetprefix string
   334  	var processingOurInterface bool
   335  	lines := bytes.Split(netshOutput, []byte{'\r', '\n'})
   336  	for _, line := range lines {
   337  		if !processingOurInterface {
   338  			if !bytes.HasPrefix(line, []byte("Configuration for interface")) {
   339  				continue
   340  			}
   341  			if !bytes.Contains(line, []byte(`"`+name+`"`)) {
   342  				continue
   343  			}
   344  			processingOurInterface = true
   345  			continue
   346  		}
   347  		if len(line) == 0 {
   348  			break
   349  		}
   350  		if bytes.Contains(line, []byte("Subnet Prefix:")) {
   351  			f := bytes.Split(line, []byte{':'})
   352  			if len(f) == 2 {
   353  				f = bytes.Split(f[1], []byte{'('})
   354  				if len(f) == 2 {
   355  					f = bytes.Split(f[0], []byte{'/'})
   356  					if len(f) == 2 {
   357  						subnetprefix = string(bytes.TrimSpace(f[1]))
   358  						if addr != "" && subnetprefix != "" {
   359  							addrs = append(addrs, addr+"/"+subnetprefix)
   360  						}
   361  					}
   362  				}
   363  			}
   364  		}
   365  		addr = ""
   366  		if bytes.Contains(line, []byte("IP Address:")) {
   367  			f := bytes.Split(line, []byte{':'})
   368  			if len(f) == 2 {
   369  				addr = string(bytes.TrimSpace(f[1]))
   370  			}
   371  		}
   372  	}
   373  	return addrs
   374  }
   375  
   376  func netshInterfaceIPv6ShowAddress(name string, netshOutput []byte) []string {
   377  	// Address information is listed like:
   378  	//
   379  	//Address ::1 Parameters
   380  	//---------------------------------------------------------
   381  	//Interface Luid     : Loopback Pseudo-Interface 1
   382  	//Scope Id           : 0.0
   383  	//Valid Lifetime     : infinite
   384  	//Preferred Lifetime : infinite
   385  	//DAD State          : Preferred
   386  	//Address Type       : Other
   387  	//Skip as Source     : false
   388  	//
   389  	//Address XXXX::XXXX:XXXX:XXXX:XXXX%11 Parameters
   390  	//---------------------------------------------------------
   391  	//Interface Luid     : Local Area Connection
   392  	//Scope Id           : 0.11
   393  	//Valid Lifetime     : infinite
   394  	//Preferred Lifetime : infinite
   395  	//DAD State          : Preferred
   396  	//Address Type       : Other
   397  	//Skip as Source     : false
   398  	//
   399  
   400  	// TODO: need to test ipv6 netmask too, but netsh does not outputs it
   401  	var addr string
   402  	addrs := make([]string, 0)
   403  	lines := bytes.Split(netshOutput, []byte{'\r', '\n'})
   404  	for _, line := range lines {
   405  		if addr != "" {
   406  			if len(line) == 0 {
   407  				addr = ""
   408  				continue
   409  			}
   410  			if string(line) != "Interface Luid     : "+name {
   411  				continue
   412  			}
   413  			addrs = append(addrs, addr)
   414  			addr = ""
   415  			continue
   416  		}
   417  		if !bytes.HasPrefix(line, []byte("Address")) {
   418  			continue
   419  		}
   420  		if !bytes.HasSuffix(line, []byte("Parameters")) {
   421  			continue
   422  		}
   423  		f := bytes.Split(line, []byte{' '})
   424  		if len(f) != 3 {
   425  			continue
   426  		}
   427  		// remove scope ID if present
   428  		f = bytes.Split(f[1], []byte{'%'})
   429  
   430  		// netsh can create IPv4-embedded IPv6 addresses, like fe80::5efe:192.168.140.1.
   431  		// Convert these to all hexadecimal fe80::5efe:c0a8:8c01 for later string comparisons.
   432  		ipv4Tail := regexp.MustCompile(`:\d+\.\d+\.\d+\.\d+$`)
   433  		if ipv4Tail.Match(f[0]) {
   434  			f[0] = []byte(ParseIP(string(f[0])).String())
   435  		}
   436  
   437  		addr = string(bytes.ToLower(bytes.TrimSpace(f[0])))
   438  	}
   439  	return addrs
   440  }
   441  
   442  func TestInterfaceAddrsWithNetsh(t *testing.T) {
   443  	if isWindowsXP(t) {
   444  		t.Skip("Windows XP netsh command does not provide required functionality")
   445  	}
   446  	if !netshSpeaksEnglish(t) {
   447  		t.Skip("English version of netsh required for this test")
   448  	}
   449  
   450  	outIPV4, err := runCmd("netsh", "interface", "ipv4", "show", "address")
   451  	if err != nil {
   452  		t.Fatal(err)
   453  	}
   454  	outIPV6, err := runCmd("netsh", "interface", "ipv6", "show", "address", "level=verbose")
   455  	if err != nil {
   456  		t.Fatal(err)
   457  	}
   458  
   459  	ift, err := Interfaces()
   460  	if err != nil {
   461  		t.Fatal(err)
   462  	}
   463  	for _, ifi := range ift {
   464  		// Skip the interface if it's down.
   465  		if (ifi.Flags & FlagUp) == 0 {
   466  			continue
   467  		}
   468  		have := make([]string, 0)
   469  		addrs, err := ifi.Addrs()
   470  		if err != nil {
   471  			t.Fatal(err)
   472  		}
   473  		for _, addr := range addrs {
   474  			switch addr := addr.(type) {
   475  			case *IPNet:
   476  				if addr.IP.To4() != nil {
   477  					have = append(have, addr.String())
   478  				}
   479  				if addr.IP.To16() != nil && addr.IP.To4() == nil {
   480  					// netsh does not output netmask for ipv6, so ignore ipv6 mask
   481  					have = append(have, addr.IP.String())
   482  				}
   483  			case *IPAddr:
   484  				if addr.IP.To4() != nil {
   485  					have = append(have, addr.String())
   486  				}
   487  				if addr.IP.To16() != nil && addr.IP.To4() == nil {
   488  					// netsh does not output netmask for ipv6, so ignore ipv6 mask
   489  					have = append(have, addr.IP.String())
   490  				}
   491  			}
   492  		}
   493  		sort.Strings(have)
   494  
   495  		want := netshInterfaceIPv4ShowAddress(ifi.Name, outIPV4)
   496  		wantIPv6 := netshInterfaceIPv6ShowAddress(ifi.Name, outIPV6)
   497  		want = append(want, wantIPv6...)
   498  		sort.Strings(want)
   499  
   500  		if strings.Join(want, "/") != strings.Join(have, "/") {
   501  			t.Errorf("%s: unexpected addresses list %q, want %q", ifi.Name, have, want)
   502  		}
   503  	}
   504  }
   505  
   506  // check that getmac exists as a powershell command, and that it
   507  // speaks English.
   508  func checkGetmac(t *testing.T) {
   509  	out, err := runCmd("getmac", "/?")
   510  	if err != nil {
   511  		if strings.Contains(err.Error(), "term 'getmac' is not recognized as the name of a cmdlet") {
   512  			t.Skipf("getmac not available")
   513  		}
   514  		t.Fatal(err)
   515  	}
   516  	if !bytes.Contains(out, []byte("network adapters on a system")) {
   517  		t.Skipf("skipping test on non-English system")
   518  	}
   519  }
   520  
   521  func TestInterfaceHardwareAddrWithGetmac(t *testing.T) {
   522  	if isWindowsXP(t) {
   523  		t.Skip("Windows XP does not have powershell command")
   524  	}
   525  	checkGetmac(t)
   526  
   527  	ift, err := Interfaces()
   528  	if err != nil {
   529  		t.Fatal(err)
   530  	}
   531  	have := make(map[string]string)
   532  	for _, ifi := range ift {
   533  		if ifi.Flags&FlagLoopback != 0 {
   534  			// no MAC address for loopback interfaces
   535  			continue
   536  		}
   537  		have[ifi.Name] = ifi.HardwareAddr.String()
   538  	}
   539  
   540  	out, err := runCmd("getmac", "/fo", "list", "/v")
   541  	if err != nil {
   542  		t.Fatal(err)
   543  	}
   544  	// getmac output looks like:
   545  	//
   546  	//Connection Name:  Local Area Connection
   547  	//Network Adapter:  Intel Gigabit Network Connection
   548  	//Physical Address: XX-XX-XX-XX-XX-XX
   549  	//Transport Name:   \Device\Tcpip_{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
   550  	//
   551  	//Connection Name:  Wireless Network Connection
   552  	//Network Adapter:  Wireles WLAN Card
   553  	//Physical Address: XX-XX-XX-XX-XX-XX
   554  	//Transport Name:   Media disconnected
   555  	//
   556  	//Connection Name:  Bluetooth Network Connection
   557  	//Network Adapter:  Bluetooth Device (Personal Area Network)
   558  	//Physical Address: N/A
   559  	//Transport Name:   Hardware not present
   560  	//
   561  	//Connection Name:  VMware Network Adapter VMnet8
   562  	//Network Adapter:  VMware Virtual Ethernet Adapter for VMnet8
   563  	//Physical Address: Disabled
   564  	//Transport Name:   Disconnected
   565  	//
   566  	want := make(map[string]string)
   567  	group := make(map[string]string) // name / values for single adapter
   568  	getValue := func(name string) string {
   569  		value, found := group[name]
   570  		if !found {
   571  			t.Fatalf("%q has no %q line in it", group, name)
   572  		}
   573  		if value == "" {
   574  			t.Fatalf("%q has empty %q value", group, name)
   575  		}
   576  		return value
   577  	}
   578  	processGroup := func() {
   579  		if len(group) == 0 {
   580  			return
   581  		}
   582  		tname := strings.ToLower(getValue("Transport Name"))
   583  		if tname == "n/a" {
   584  			// skip these
   585  			return
   586  		}
   587  		addr := strings.ToLower(getValue("Physical Address"))
   588  		if addr == "disabled" || addr == "n/a" {
   589  			// skip these
   590  			return
   591  		}
   592  		addr = strings.Replace(addr, "-", ":", -1)
   593  		cname := getValue("Connection Name")
   594  		want[cname] = addr
   595  		group = make(map[string]string)
   596  	}
   597  	lines := bytes.Split(out, []byte{'\r', '\n'})
   598  	for _, line := range lines {
   599  		if len(line) == 0 {
   600  			processGroup()
   601  			continue
   602  		}
   603  		i := bytes.IndexByte(line, ':')
   604  		if i == -1 {
   605  			t.Fatalf("line %q has no : in it", line)
   606  		}
   607  		group[string(line[:i])] = string(bytes.TrimSpace(line[i+1:]))
   608  	}
   609  	processGroup()
   610  
   611  	dups := make(map[string][]string)
   612  	for name, addr := range want {
   613  		if _, ok := dups[addr]; !ok {
   614  			dups[addr] = make([]string, 0)
   615  		}
   616  		dups[addr] = append(dups[addr], name)
   617  	}
   618  
   619  nextWant:
   620  	for name, wantAddr := range want {
   621  		if haveAddr, ok := have[name]; ok {
   622  			if haveAddr != wantAddr {
   623  				t.Errorf("unexpected MAC address for %q - %v, want %v", name, haveAddr, wantAddr)
   624  			}
   625  			continue
   626  		}
   627  		// We could not find the interface in getmac output by name.
   628  		// But sometimes getmac lists many interface names
   629  		// for the same MAC address. If that is the case here,
   630  		// and we can match at least one of those names,
   631  		// let's ignore the other names.
   632  		if dupNames, ok := dups[wantAddr]; ok && len(dupNames) > 1 {
   633  			for _, dupName := range dupNames {
   634  				if haveAddr, ok := have[dupName]; ok && haveAddr == wantAddr {
   635  					continue nextWant
   636  				}
   637  			}
   638  		}
   639  		t.Errorf("getmac lists %q, but it could not be found among Go interfaces %v", name, have)
   640  	}
   641  }