github.com/fjballest/golang@v0.0.0-20151209143359-e4c5fe594ca8/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  	"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 isWindowsXP(t *testing.T) bool {
   172  	v, err := syscall.GetVersion()
   173  	if err != nil {
   174  		t.Fatalf("GetVersion failed: %v", err)
   175  	}
   176  	major := byte(v)
   177  	return major < 6
   178  }
   179  
   180  func runCmd(args ...string) ([]byte, error) {
   181  	removeUTF8BOM := func(b []byte) []byte {
   182  		if len(b) >= 3 && b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF {
   183  			return b[3:]
   184  		}
   185  		return b
   186  	}
   187  	f, err := ioutil.TempFile("", "netcmd")
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  	f.Close()
   192  	defer os.Remove(f.Name())
   193  	cmd := fmt.Sprintf(`%s | Out-File "%s" -encoding UTF8`, strings.Join(args, " "), f.Name())
   194  	out, err := exec.Command("powershell", "-Command", cmd).CombinedOutput()
   195  	if err != nil {
   196  		if len(out) != 0 {
   197  			return nil, fmt.Errorf("%s failed: %v: %q", args[0], err, string(removeUTF8BOM(out)))
   198  		}
   199  		var err2 error
   200  		out, err2 = ioutil.ReadFile(f.Name())
   201  		if err2 != nil {
   202  			return nil, err2
   203  		}
   204  		if len(out) != 0 {
   205  			return nil, fmt.Errorf("%s failed: %v: %q", args[0], err, string(removeUTF8BOM(out)))
   206  		}
   207  		return nil, fmt.Errorf("%s failed: %v", args[0], err)
   208  	}
   209  	out, err = ioutil.ReadFile(f.Name())
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  	return removeUTF8BOM(out), nil
   214  }
   215  
   216  func netshInterfaceIPShowConfig() ([]string, error) {
   217  	out, err := runCmd("netsh", "interface", "ip", "show", "config")
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  	lines := bytes.Split(out, []byte{'\r', '\n'})
   222  	names := make([]string, 0)
   223  	for _, line := range lines {
   224  		f := bytes.Split(line, []byte{'"'})
   225  		if len(f) == 3 {
   226  			names = append(names, string(f[1]))
   227  		}
   228  	}
   229  	return names, nil
   230  }
   231  
   232  func TestInterfacesWithNetsh(t *testing.T) {
   233  	if isWindowsXP(t) {
   234  		t.Skip("Windows XP netsh command does not provide required functionality")
   235  	}
   236  	ift, err := Interfaces()
   237  	if err != nil {
   238  		t.Fatal(err)
   239  	}
   240  	have := make([]string, 0)
   241  	for _, ifi := range ift {
   242  		have = append(have, ifi.Name)
   243  	}
   244  	sort.Strings(have)
   245  
   246  	want, err := netshInterfaceIPShowConfig()
   247  	if err != nil {
   248  		t.Fatal(err)
   249  	}
   250  	sort.Strings(want)
   251  
   252  	if strings.Join(want, "/") != strings.Join(have, "/") {
   253  		t.Fatalf("unexpected interface list %q, want %q", have, want)
   254  	}
   255  }
   256  
   257  func netshInterfaceIPv4ShowAddress(name string) ([]string, error) {
   258  	out, err := runCmd("netsh", "interface", "ipv4", "show", "address", "name=\""+name+"\"")
   259  	if err != nil {
   260  		return nil, err
   261  	}
   262  	// adress information is listed like:
   263  	//    IP Address:                           10.0.0.2
   264  	//    Subnet Prefix:                        10.0.0.0/24 (mask 255.255.255.0)
   265  	//    IP Address:                           10.0.0.3
   266  	//    Subnet Prefix:                        10.0.0.0/24 (mask 255.255.255.0)
   267  	addrs := make([]string, 0)
   268  	var addr, subnetprefix string
   269  	lines := bytes.Split(out, []byte{'\r', '\n'})
   270  	for _, line := range lines {
   271  		if bytes.Contains(line, []byte("Subnet Prefix:")) {
   272  			f := bytes.Split(line, []byte{':'})
   273  			if len(f) == 2 {
   274  				f = bytes.Split(f[1], []byte{'('})
   275  				if len(f) == 2 {
   276  					f = bytes.Split(f[0], []byte{'/'})
   277  					if len(f) == 2 {
   278  						subnetprefix = string(bytes.TrimSpace(f[1]))
   279  						if addr != "" && subnetprefix != "" {
   280  							addrs = append(addrs, addr+"/"+subnetprefix)
   281  						}
   282  					}
   283  				}
   284  			}
   285  		}
   286  		addr = ""
   287  		if bytes.Contains(line, []byte("IP Address:")) {
   288  			f := bytes.Split(line, []byte{':'})
   289  			if len(f) == 2 {
   290  				addr = string(bytes.TrimSpace(f[1]))
   291  			}
   292  		}
   293  	}
   294  	return addrs, nil
   295  }
   296  
   297  func netshInterfaceIPv6ShowAddress(name string) ([]string, error) {
   298  	// TODO: need to test ipv6 netmask too, but netsh does not outputs it
   299  	out, err := runCmd("netsh", "interface", "ipv6", "show", "address", "interface=\""+name+"\"")
   300  	if err != nil {
   301  		return nil, err
   302  	}
   303  	addrs := make([]string, 0)
   304  	lines := bytes.Split(out, []byte{'\r', '\n'})
   305  	for _, line := range lines {
   306  		if !bytes.HasPrefix(line, []byte("Address")) {
   307  			continue
   308  		}
   309  		if !bytes.HasSuffix(line, []byte("Parameters")) {
   310  			continue
   311  		}
   312  		f := bytes.Split(line, []byte{' '})
   313  		if len(f) != 3 {
   314  			continue
   315  		}
   316  		// remove scope ID if present
   317  		f = bytes.Split(f[1], []byte{'%'})
   318  		addrs = append(addrs, string(bytes.TrimSpace(f[0])))
   319  	}
   320  	return addrs, nil
   321  }
   322  
   323  func TestInterfaceAddrsWithNetsh(t *testing.T) {
   324  	t.Skip("skipping test; see https://golang.org/issue/12811")
   325  	if isWindowsXP(t) {
   326  		t.Skip("Windows XP netsh command does not provide required functionality")
   327  	}
   328  	ift, err := Interfaces()
   329  	if err != nil {
   330  		t.Fatal(err)
   331  	}
   332  	for _, ifi := range ift {
   333  		have := make([]string, 0)
   334  		addrs, err := ifi.Addrs()
   335  		if err != nil {
   336  			t.Fatal(err)
   337  		}
   338  		for _, addr := range addrs {
   339  			switch addr := addr.(type) {
   340  			case *IPNet:
   341  				if addr.IP.To4() != nil {
   342  					have = append(have, addr.String())
   343  				}
   344  				if addr.IP.To16() != nil && addr.IP.To4() == nil {
   345  					// netsh does not output netmask for ipv6, so ignore ipv6 mask
   346  					have = append(have, addr.IP.String())
   347  				}
   348  			case *IPAddr:
   349  				if addr.IP.To4() != nil {
   350  					have = append(have, addr.String())
   351  				}
   352  				if addr.IP.To16() != nil && addr.IP.To4() == nil {
   353  					// netsh does not output netmask for ipv6, so ignore ipv6 mask
   354  					have = append(have, addr.IP.String())
   355  				}
   356  			}
   357  		}
   358  		sort.Strings(have)
   359  
   360  		want, err := netshInterfaceIPv4ShowAddress(ifi.Name)
   361  		if err != nil {
   362  			t.Fatal(err)
   363  		}
   364  		wantIPv6, err := netshInterfaceIPv6ShowAddress(ifi.Name)
   365  		if err != nil {
   366  			t.Fatal(err)
   367  		}
   368  		want = append(want, wantIPv6...)
   369  		sort.Strings(want)
   370  
   371  		if strings.Join(want, "/") != strings.Join(have, "/") {
   372  			t.Errorf("%s: unexpected addresses list %q, want %q", ifi.Name, have, want)
   373  		}
   374  	}
   375  }
   376  
   377  func TestInterfaceHardwareAddrWithGetmac(t *testing.T) {
   378  	t.Skip("skipping test; see https://golang.org/issue/12691")
   379  	if isWindowsXP(t) {
   380  		t.Skip("Windows XP does not have powershell command")
   381  	}
   382  	ift, err := Interfaces()
   383  	if err != nil {
   384  		t.Fatal(err)
   385  	}
   386  	have := make([]string, 0)
   387  	for _, ifi := range ift {
   388  		if ifi.Flags&FlagLoopback != 0 {
   389  			// no MAC for loopback interfaces
   390  			continue
   391  		}
   392  		have = append(have, ifi.Name+"="+ifi.HardwareAddr.String())
   393  	}
   394  	sort.Strings(have)
   395  
   396  	out, err := runCmd("getmac", "/fo", "list", "/v")
   397  	if err != nil {
   398  		t.Fatal(err)
   399  	}
   400  	// getmac output looks like:
   401  	//
   402  	//Connection Name:  Local Area Connection
   403  	//Network Adapter:  Intel Gigabit Network Connection
   404  	//Physical Address: XX-XX-XX-XX-XX-XX
   405  	//Transport Name:   \Device\Tcpip_{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
   406  	//
   407  	//Connection Name:  Wireless Network Connection
   408  	//Network Adapter:  Wireles WLAN Card
   409  	//Physical Address: XX-XX-XX-XX-XX-XX
   410  	//Transport Name:   Media disconnected
   411  	//
   412  	//Connection Name:  Bluetooth Network Connection
   413  	//Network Adapter:  Bluetooth Device (Personal Area Network)
   414  	//Physical Address: XX-XX-XX-XX-XX-XX
   415  	//Transport Name:   Media disconnected
   416  	//
   417  	want := make([]string, 0)
   418  	var name string
   419  	lines := bytes.Split(out, []byte{'\r', '\n'})
   420  	for _, line := range lines {
   421  		if bytes.Contains(line, []byte("Connection Name:")) {
   422  			f := bytes.Split(line, []byte{':'})
   423  			if len(f) != 2 {
   424  				t.Fatal("unexpected \"Connection Name\" line: %q", line)
   425  			}
   426  			name = string(bytes.TrimSpace(f[1]))
   427  			if name == "" {
   428  				t.Fatal("empty name on \"Connection Name\" line: %q", line)
   429  			}
   430  		}
   431  		if bytes.Contains(line, []byte("Physical Address:")) {
   432  			if name == "" {
   433  				t.Fatal("no matching name found: %q", string(out))
   434  			}
   435  			f := bytes.Split(line, []byte{':'})
   436  			if len(f) != 2 {
   437  				t.Fatal("unexpected \"Physical Address\" line: %q", line)
   438  			}
   439  			addr := string(bytes.TrimSpace(f[1]))
   440  			if addr == "" {
   441  				t.Fatal("empty address on \"Physical Address\" line: %q", line)
   442  			}
   443  			addr = strings.Replace(addr, "-", ":", -1)
   444  			want = append(want, name+"="+addr)
   445  			name = ""
   446  		}
   447  	}
   448  	sort.Strings(want)
   449  
   450  	if strings.Join(want, "/") != strings.Join(have, "/") {
   451  		t.Fatalf("unexpected MAC addresses %q, want %q", have, want)
   452  	}
   453  }