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	"os"
13	"os/exec"
14	"regexp"
15	"sort"
16	"strings"
17	"syscall"
18	"testing"
19	"time"
20)
21
22func 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.
41func 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
171func 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
207func checkNetsh(t *testing.T) {
208	out, err := runCmd("netsh", "help")
209	if err != nil {
210		t.Fatal(err)
211	}
212	if bytes.Contains(out, []byte("The following helper DLL cannot be loaded")) {
213		t.Skipf("powershell failure:\n%s", err)
214	}
215	if !bytes.Contains(out, []byte("The following commands are available:")) {
216		t.Skipf("powershell does not speak English:\n%s", out)
217	}
218}
219
220func netshInterfaceIPShowInterface(ipver string, ifaces map[string]bool) error {
221	out, err := runCmd("netsh", "interface", ipver, "show", "interface", "level=verbose")
222	if err != nil {
223		return err
224	}
225	// interface information is listed like:
226	//
227	//Interface Local Area Connection Parameters
228	//----------------------------------------------
229	//IfLuid                             : ethernet_6
230	//IfIndex                            : 11
231	//State                              : connected
232	//Metric                             : 10
233	//...
234	var name string
235	lines := bytes.Split(out, []byte{'\r', '\n'})
236	for _, line := range lines {
237		if bytes.HasPrefix(line, []byte("Interface ")) && bytes.HasSuffix(line, []byte(" Parameters")) {
238			f := line[len("Interface "):]
239			f = f[:len(f)-len(" Parameters")]
240			name = string(f)
241			continue
242		}
243		var isup bool
244		switch string(line) {
245		case "State                              : connected":
246			isup = true
247		case "State                              : disconnected":
248			isup = false
249		default:
250			continue
251		}
252		if name != "" {
253			if v, ok := ifaces[name]; ok && v != isup {
254				return fmt.Errorf("%s:%s isup=%v: ipv4 and ipv6 report different interface state", ipver, name, isup)
255			}
256			ifaces[name] = isup
257			name = ""
258		}
259	}
260	return nil
261}
262
263func TestInterfacesWithNetsh(t *testing.T) {
264	checkNetsh(t)
265
266	toString := func(name string, isup bool) string {
267		if isup {
268			return name + ":up"
269		}
270		return name + ":down"
271	}
272
273	ift, err := Interfaces()
274	if err != nil {
275		t.Fatal(err)
276	}
277	have := make([]string, 0)
278	for _, ifi := range ift {
279		have = append(have, toString(ifi.Name, ifi.Flags&FlagUp != 0))
280	}
281	sort.Strings(have)
282
283	ifaces := make(map[string]bool)
284	err = netshInterfaceIPShowInterface("ipv6", ifaces)
285	if err != nil {
286		t.Fatal(err)
287	}
288	err = netshInterfaceIPShowInterface("ipv4", ifaces)
289	if err != nil {
290		t.Fatal(err)
291	}
292	want := make([]string, 0)
293	for name, isup := range ifaces {
294		want = append(want, toString(name, isup))
295	}
296	sort.Strings(want)
297
298	if strings.Join(want, "/") != strings.Join(have, "/") {
299		t.Fatalf("unexpected interface list %q, want %q", have, want)
300	}
301}
302
303func netshInterfaceIPv4ShowAddress(name string, netshOutput []byte) []string {
304	// Address information is listed like:
305	//
306	//Configuration for interface "Local Area Connection"
307	//    DHCP enabled:                         Yes
308	//    IP Address:                           10.0.0.2
309	//    Subnet Prefix:                        10.0.0.0/24 (mask 255.255.255.0)
310	//    IP Address:                           10.0.0.3
311	//    Subnet Prefix:                        10.0.0.0/24 (mask 255.255.255.0)
312	//    Default Gateway:                      10.0.0.254
313	//    Gateway Metric:                       0
314	//    InterfaceMetric:                      10
315	//
316	//Configuration for interface "Loopback Pseudo-Interface 1"
317	//    DHCP enabled:                         No
318	//    IP Address:                           127.0.0.1
319	//    Subnet Prefix:                        127.0.0.0/8 (mask 255.0.0.0)
320	//    InterfaceMetric:                      50
321	//
322	addrs := make([]string, 0)
323	var addr, subnetprefix string
324	var processingOurInterface bool
325	lines := bytes.Split(netshOutput, []byte{'\r', '\n'})
326	for _, line := range lines {
327		if !processingOurInterface {
328			if !bytes.HasPrefix(line, []byte("Configuration for interface")) {
329				continue
330			}
331			if !bytes.Contains(line, []byte(`"`+name+`"`)) {
332				continue
333			}
334			processingOurInterface = true
335			continue
336		}
337		if len(line) == 0 {
338			break
339		}
340		if bytes.Contains(line, []byte("Subnet Prefix:")) {
341			f := bytes.Split(line, []byte{':'})
342			if len(f) == 2 {
343				f = bytes.Split(f[1], []byte{'('})
344				if len(f) == 2 {
345					f = bytes.Split(f[0], []byte{'/'})
346					if len(f) == 2 {
347						subnetprefix = string(bytes.TrimSpace(f[1]))
348						if addr != "" && subnetprefix != "" {
349							addrs = append(addrs, addr+"/"+subnetprefix)
350						}
351					}
352				}
353			}
354		}
355		addr = ""
356		if bytes.Contains(line, []byte("IP Address:")) {
357			f := bytes.Split(line, []byte{':'})
358			if len(f) == 2 {
359				addr = string(bytes.TrimSpace(f[1]))
360			}
361		}
362	}
363	return addrs
364}
365
366func netshInterfaceIPv6ShowAddress(name string, netshOutput []byte) []string {
367	// Address information is listed like:
368	//
369	//Address ::1 Parameters
370	//---------------------------------------------------------
371	//Interface Luid     : Loopback Pseudo-Interface 1
372	//Scope Id           : 0.0
373	//Valid Lifetime     : infinite
374	//Preferred Lifetime : infinite
375	//DAD State          : Preferred
376	//Address Type       : Other
377	//Skip as Source     : false
378	//
379	//Address XXXX::XXXX:XXXX:XXXX:XXXX%11 Parameters
380	//---------------------------------------------------------
381	//Interface Luid     : Local Area Connection
382	//Scope Id           : 0.11
383	//Valid Lifetime     : infinite
384	//Preferred Lifetime : infinite
385	//DAD State          : Preferred
386	//Address Type       : Other
387	//Skip as Source     : false
388	//
389
390	// TODO: need to test ipv6 netmask too, but netsh does not outputs it
391	var addr string
392	addrs := make([]string, 0)
393	lines := bytes.Split(netshOutput, []byte{'\r', '\n'})
394	for _, line := range lines {
395		if addr != "" {
396			if len(line) == 0 {
397				addr = ""
398				continue
399			}
400			if string(line) != "Interface Luid     : "+name {
401				continue
402			}
403			addrs = append(addrs, addr)
404			addr = ""
405			continue
406		}
407		if !bytes.HasPrefix(line, []byte("Address")) {
408			continue
409		}
410		if !bytes.HasSuffix(line, []byte("Parameters")) {
411			continue
412		}
413		f := bytes.Split(line, []byte{' '})
414		if len(f) != 3 {
415			continue
416		}
417		// remove scope ID if present
418		f = bytes.Split(f[1], []byte{'%'})
419
420		// netsh can create IPv4-embedded IPv6 addresses, like fe80::5efe:192.168.140.1.
421		// Convert these to all hexadecimal fe80::5efe:c0a8:8c01 for later string comparisons.
422		ipv4Tail := regexp.MustCompile(`:\d+\.\d+\.\d+\.\d+$`)
423		if ipv4Tail.Match(f[0]) {
424			f[0] = []byte(ParseIP(string(f[0])).String())
425		}
426
427		addr = string(bytes.ToLower(bytes.TrimSpace(f[0])))
428	}
429	return addrs
430}
431
432func TestInterfaceAddrsWithNetsh(t *testing.T) {
433	checkNetsh(t)
434
435	outIPV4, err := runCmd("netsh", "interface", "ipv4", "show", "address")
436	if err != nil {
437		t.Fatal(err)
438	}
439	outIPV6, err := runCmd("netsh", "interface", "ipv6", "show", "address", "level=verbose")
440	if err != nil {
441		t.Fatal(err)
442	}
443
444	ift, err := Interfaces()
445	if err != nil {
446		t.Fatal(err)
447	}
448	for _, ifi := range ift {
449		// Skip the interface if it's down.
450		if (ifi.Flags & FlagUp) == 0 {
451			continue
452		}
453		have := make([]string, 0)
454		addrs, err := ifi.Addrs()
455		if err != nil {
456			t.Fatal(err)
457		}
458		for _, addr := range addrs {
459			switch addr := addr.(type) {
460			case *IPNet:
461				if addr.IP.To4() != nil {
462					have = append(have, addr.String())
463				}
464				if addr.IP.To16() != nil && addr.IP.To4() == nil {
465					// netsh does not output netmask for ipv6, so ignore ipv6 mask
466					have = append(have, addr.IP.String())
467				}
468			case *IPAddr:
469				if addr.IP.To4() != nil {
470					have = append(have, addr.String())
471				}
472				if addr.IP.To16() != nil && addr.IP.To4() == nil {
473					// netsh does not output netmask for ipv6, so ignore ipv6 mask
474					have = append(have, addr.IP.String())
475				}
476			}
477		}
478		sort.Strings(have)
479
480		want := netshInterfaceIPv4ShowAddress(ifi.Name, outIPV4)
481		wantIPv6 := netshInterfaceIPv6ShowAddress(ifi.Name, outIPV6)
482		want = append(want, wantIPv6...)
483		sort.Strings(want)
484
485		if strings.Join(want, "/") != strings.Join(have, "/") {
486			t.Errorf("%s: unexpected addresses list %q, want %q", ifi.Name, have, want)
487		}
488	}
489}
490
491// check that getmac exists as a powershell command, and that it
492// speaks English.
493func checkGetmac(t *testing.T) {
494	out, err := runCmd("getmac", "/?")
495	if err != nil {
496		if strings.Contains(err.Error(), "term 'getmac' is not recognized as the name of a cmdlet") {
497			t.Skipf("getmac not available")
498		}
499		t.Fatal(err)
500	}
501	if !bytes.Contains(out, []byte("network adapters on a system")) {
502		t.Skipf("skipping test on non-English system")
503	}
504}
505
506func TestInterfaceHardwareAddrWithGetmac(t *testing.T) {
507	checkGetmac(t)
508
509	ift, err := Interfaces()
510	if err != nil {
511		t.Fatal(err)
512	}
513	have := make(map[string]string)
514	for _, ifi := range ift {
515		if ifi.Flags&FlagLoopback != 0 {
516			// no MAC address for loopback interfaces
517			continue
518		}
519		have[ifi.Name] = ifi.HardwareAddr.String()
520	}
521
522	out, err := runCmd("getmac", "/fo", "list", "/v")
523	if err != nil {
524		t.Fatal(err)
525	}
526	// getmac output looks like:
527	//
528	//Connection Name:  Local Area Connection
529	//Network Adapter:  Intel Gigabit Network Connection
530	//Physical Address: XX-XX-XX-XX-XX-XX
531	//Transport Name:   \Device\Tcpip_{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
532	//
533	//Connection Name:  Wireless Network Connection
534	//Network Adapter:  Wireles WLAN Card
535	//Physical Address: XX-XX-XX-XX-XX-XX
536	//Transport Name:   Media disconnected
537	//
538	//Connection Name:  Bluetooth Network Connection
539	//Network Adapter:  Bluetooth Device (Personal Area Network)
540	//Physical Address: N/A
541	//Transport Name:   Hardware not present
542	//
543	//Connection Name:  VMware Network Adapter VMnet8
544	//Network Adapter:  VMware Virtual Ethernet Adapter for VMnet8
545	//Physical Address: Disabled
546	//Transport Name:   Disconnected
547	//
548	want := make(map[string]string)
549	group := make(map[string]string) // name / values for single adapter
550	getValue := func(name string) string {
551		value, found := group[name]
552		if !found {
553			t.Fatalf("%q has no %q line in it", group, name)
554		}
555		if value == "" {
556			t.Fatalf("%q has empty %q value", group, name)
557		}
558		return value
559	}
560	processGroup := func() {
561		if len(group) == 0 {
562			return
563		}
564		tname := strings.ToLower(getValue("Transport Name"))
565		if tname == "n/a" {
566			// skip these
567			return
568		}
569		addr := strings.ToLower(getValue("Physical Address"))
570		if addr == "disabled" || addr == "n/a" {
571			// skip these
572			return
573		}
574		addr = strings.ReplaceAll(addr, "-", ":")
575		cname := getValue("Connection Name")
576		want[cname] = addr
577		group = make(map[string]string)
578	}
579	lines := bytes.Split(out, []byte{'\r', '\n'})
580	for _, line := range lines {
581		if len(line) == 0 {
582			processGroup()
583			continue
584		}
585		i := bytes.IndexByte(line, ':')
586		if i == -1 {
587			t.Fatalf("line %q has no : in it", line)
588		}
589		group[string(line[:i])] = string(bytes.TrimSpace(line[i+1:]))
590	}
591	processGroup()
592
593	dups := make(map[string][]string)
594	for name, addr := range want {
595		if _, ok := dups[addr]; !ok {
596			dups[addr] = make([]string, 0)
597		}
598		dups[addr] = append(dups[addr], name)
599	}
600
601nextWant:
602	for name, wantAddr := range want {
603		if haveAddr, ok := have[name]; ok {
604			if haveAddr != wantAddr {
605				t.Errorf("unexpected MAC address for %q - %v, want %v", name, haveAddr, wantAddr)
606			}
607			continue
608		}
609		// We could not find the interface in getmac output by name.
610		// But sometimes getmac lists many interface names
611		// for the same MAC address. If that is the case here,
612		// and we can match at least one of those names,
613		// let's ignore the other names.
614		if dupNames, ok := dups[wantAddr]; ok && len(dupNames) > 1 {
615			for _, dupName := range dupNames {
616				if haveAddr, ok := have[dupName]; ok && haveAddr == wantAddr {
617					continue nextWant
618				}
619			}
620		}
621		t.Errorf("getmac lists %q, but it could not be found among Go interfaces %v", name, have)
622	}
623}
624