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
5package net
6
7import (
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
23func 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.
42func 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
172func 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
181func 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
217func 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
225func 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
268func 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
313func 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
376func 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
442func 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.
508func 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
521func 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	for name, wantAddr := range want {
612		haveAddr, ok := have[name]
613		if !ok {
614			t.Errorf("getmac lists %q, but it could not be found among Go interfaces %v", name, have)
615			continue
616		}
617		if haveAddr != wantAddr {
618			t.Errorf("unexpected MAC address for %q - %v, want %v", name, haveAddr, wantAddr)
619			continue
620		}
621	}
622}
623