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