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.
23 func toErrno(err error) (syscall.Errno, bool) {
24 operr, ok := err.(*OpError)
28 syserr, ok := operr.Err.(*os.SyscallError)
32 errno, ok := syserr.Err.(syscall.Errno)
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) {
46 // Display windows errno in error message.
47 errno, ok := toErrno(err)
51 return "", fmt.Errorf("%v (windows errno=%d)", err, errno)
55 b := make([]byte, 100)
57 if err == nil || err == io.EOF {
58 return string(b[:n]), nil
60 errno, ok := toErrno(err)
61 if ok && ignoreSomeReadErrors && (errno == syscall.ERROR_NETNAME_DELETED || errno == syscall.WSAECONNRESET) {
67 send := func(addr string, data string) error {
68 c, err := Dial("tcp", addr)
80 return fmt.Errorf(`Only %d chars of string "%s" sent`, n, data)
85 if envaddr := os.Getenv("GOTEST_DIAL_ADDR"); envaddr != "" {
87 c, err := Dial("tcp", envaddr)
91 fmt.Printf("sleeping\n")
92 time.Sleep(time.Minute) // process will be killed here
96 ln, err := Listen("tcp", "127.0.0.1:0")
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()
107 t.Fatalf("cmd.StdoutPipe failed: %v", err)
111 t.Fatalf("cmd.Start failed: %v\n", err)
113 outReader := bufio.NewReader(stdout)
115 s, err := outReader.ReadString('\n')
117 t.Fatalf("reading stdout failed: %v", err)
119 if s == "sleeping\n" {
123 defer cmd.Wait() // ignore error - we know it is getting killed
125 const alittle = 100 * time.Millisecond
127 cmd.Process.Kill() // the only way to trigger the errors
130 // Send second connection data (with delay in a separate goroutine).
131 result := make(chan error)
134 err := send(ln.Addr().String(), "abc")
143 t.Fatalf("send failed: %v", err)
147 // Receive first or second connection.
148 s, err := recv(ln, true)
150 t.Fatalf("recv failed: %v", err)
154 // First connection data is received, let's get second connection data.
156 // First connection is lost forever, but that is ok.
159 t.Fatalf(`"%s" received from recv, but "" or "abc" expected`, s)
162 // Get second connection data.
163 s, err = recv(ln, false)
165 t.Fatalf("recv failed: %v", err)
168 t.Fatalf(`"%s" received from recv, but "abc" expected`, s)
172 func runCmd(args ...string) ([]byte, error) {
173 removeUTF8BOM := func(b []byte) []byte {
174 if len(b) >= 3 && b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF {
179 f, err := os.CreateTemp("", "netcmd")
184 defer os.Remove(f.Name())
185 cmd := fmt.Sprintf(`%s | Out-File "%s" -encoding UTF8`, strings.Join(args, " "), f.Name())
186 out, err := exec.Command("powershell", "-Command", cmd).CombinedOutput()
189 return nil, fmt.Errorf("%s failed: %v: %q", args[0], err, string(removeUTF8BOM(out)))
192 out, err2 = os.ReadFile(f.Name())
197 return nil, fmt.Errorf("%s failed: %v: %q", args[0], err, string(removeUTF8BOM(out)))
199 return nil, fmt.Errorf("%s failed: %v", args[0], err)
201 out, err = os.ReadFile(f.Name())
205 return removeUTF8BOM(out), nil
208 func checkNetsh(t *testing.T) {
209 if testenv.Builder() == "windows-arm64-10" {
210 // netsh was observed to sometimes hang on this builder.
211 // We have not observed failures on windows-arm64-11, so for the
212 // moment we are leaving the test enabled elsewhere on the theory
213 // that it may have been a platform bug fixed in Windows 11.
214 testenv.SkipFlaky(t, 52082)
216 out, err := runCmd("netsh", "help")
220 if bytes.Contains(out, []byte("The following helper DLL cannot be loaded")) {
221 t.Skipf("powershell failure:\n%s", err)
223 if !bytes.Contains(out, []byte("The following commands are available:")) {
224 t.Skipf("powershell does not speak English:\n%s", out)
228 func netshInterfaceIPShowInterface(ipver string, ifaces map[string]bool) error {
229 out, err := runCmd("netsh", "interface", ipver, "show", "interface", "level=verbose")
233 // interface information is listed like:
235 //Interface Local Area Connection Parameters
236 //----------------------------------------------
237 //IfLuid : ethernet_6
243 lines := bytes.Split(out, []byte{'\r', '\n'})
244 for _, line := range lines {
245 if bytes.HasPrefix(line, []byte("Interface ")) && bytes.HasSuffix(line, []byte(" Parameters")) {
246 f := line[len("Interface "):]
247 f = f[:len(f)-len(" Parameters")]
252 switch string(line) {
253 case "State : connected":
255 case "State : disconnected":
261 if v, ok := ifaces[name]; ok && v != isup {
262 return fmt.Errorf("%s:%s isup=%v: ipv4 and ipv6 report different interface state", ipver, name, isup)
271 func TestInterfacesWithNetsh(t *testing.T) {
274 toString := func(name string, isup bool) string {
278 return name + ":down"
281 ift, err := Interfaces()
285 have := make([]string, 0)
286 for _, ifi := range ift {
287 have = append(have, toString(ifi.Name, ifi.Flags&FlagUp != 0))
291 ifaces := make(map[string]bool)
292 err = netshInterfaceIPShowInterface("ipv6", ifaces)
296 err = netshInterfaceIPShowInterface("ipv4", ifaces)
300 want := make([]string, 0)
301 for name, isup := range ifaces {
302 want = append(want, toString(name, isup))
306 if strings.Join(want, "/") != strings.Join(have, "/") {
307 t.Fatalf("unexpected interface list %q, want %q", have, want)
311 func netshInterfaceIPv4ShowAddress(name string, netshOutput []byte) []string {
312 // Address information is listed like:
314 //Configuration for interface "Local Area Connection"
316 // IP Address: 10.0.0.2
317 // Subnet Prefix: 10.0.0.0/24 (mask 255.255.255.0)
318 // IP Address: 10.0.0.3
319 // Subnet Prefix: 10.0.0.0/24 (mask 255.255.255.0)
320 // Default Gateway: 10.0.0.254
322 // InterfaceMetric: 10
324 //Configuration for interface "Loopback Pseudo-Interface 1"
326 // IP Address: 127.0.0.1
327 // Subnet Prefix: 127.0.0.0/8 (mask 255.0.0.0)
328 // InterfaceMetric: 50
330 addrs := make([]string, 0)
331 var addr, subnetprefix string
332 var processingOurInterface bool
333 lines := bytes.Split(netshOutput, []byte{'\r', '\n'})
334 for _, line := range lines {
335 if !processingOurInterface {
336 if !bytes.HasPrefix(line, []byte("Configuration for interface")) {
339 if !bytes.Contains(line, []byte(`"`+name+`"`)) {
342 processingOurInterface = true
348 if bytes.Contains(line, []byte("Subnet Prefix:")) {
349 f := bytes.Split(line, []byte{':'})
351 f = bytes.Split(f[1], []byte{'('})
353 f = bytes.Split(f[0], []byte{'/'})
355 subnetprefix = string(bytes.TrimSpace(f[1]))
356 if addr != "" && subnetprefix != "" {
357 addrs = append(addrs, addr+"/"+subnetprefix)
364 if bytes.Contains(line, []byte("IP Address:")) {
365 f := bytes.Split(line, []byte{':'})
367 addr = string(bytes.TrimSpace(f[1]))
374 func netshInterfaceIPv6ShowAddress(name string, netshOutput []byte) []string {
375 // Address information is listed like:
377 //Address ::1 Parameters
378 //---------------------------------------------------------
379 //Interface Luid : Loopback Pseudo-Interface 1
381 //Valid Lifetime : infinite
382 //Preferred Lifetime : infinite
383 //DAD State : Preferred
384 //Address Type : Other
385 //Skip as Source : false
387 //Address XXXX::XXXX:XXXX:XXXX:XXXX%11 Parameters
388 //---------------------------------------------------------
389 //Interface Luid : Local Area Connection
391 //Valid Lifetime : infinite
392 //Preferred Lifetime : infinite
393 //DAD State : Preferred
394 //Address Type : Other
395 //Skip as Source : false
398 // TODO: need to test ipv6 netmask too, but netsh does not outputs it
400 addrs := make([]string, 0)
401 lines := bytes.Split(netshOutput, []byte{'\r', '\n'})
402 for _, line := range lines {
408 if string(line) != "Interface Luid : "+name {
411 addrs = append(addrs, addr)
415 if !bytes.HasPrefix(line, []byte("Address")) {
418 if !bytes.HasSuffix(line, []byte("Parameters")) {
421 f := bytes.Split(line, []byte{' '})
425 // remove scope ID if present
426 f = bytes.Split(f[1], []byte{'%'})
428 // netsh can create IPv4-embedded IPv6 addresses, like fe80::5efe:192.168.140.1.
429 // Convert these to all hexadecimal fe80::5efe:c0a8:8c01 for later string comparisons.
430 ipv4Tail := regexp.MustCompile(`:\d+\.\d+\.\d+\.\d+$`)
431 if ipv4Tail.Match(f[0]) {
432 f[0] = []byte(ParseIP(string(f[0])).String())
435 addr = string(bytes.ToLower(bytes.TrimSpace(f[0])))
440 func TestInterfaceAddrsWithNetsh(t *testing.T) {
443 outIPV4, err := runCmd("netsh", "interface", "ipv4", "show", "address")
447 outIPV6, err := runCmd("netsh", "interface", "ipv6", "show", "address", "level=verbose")
452 ift, err := Interfaces()
456 for _, ifi := range ift {
457 // Skip the interface if it's down.
458 if (ifi.Flags & FlagUp) == 0 {
461 have := make([]string, 0)
462 addrs, err := ifi.Addrs()
466 for _, addr := range addrs {
467 switch addr := addr.(type) {
469 if addr.IP.To4() != nil {
470 have = append(have, addr.String())
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())
477 if addr.IP.To4() != nil {
478 have = append(have, addr.String())
480 if addr.IP.To16() != nil && addr.IP.To4() == nil {
481 // netsh does not output netmask for ipv6, so ignore ipv6 mask
482 have = append(have, addr.IP.String())
488 want := netshInterfaceIPv4ShowAddress(ifi.Name, outIPV4)
489 wantIPv6 := netshInterfaceIPv6ShowAddress(ifi.Name, outIPV6)
490 want = append(want, wantIPv6...)
493 if strings.Join(want, "/") != strings.Join(have, "/") {
494 t.Errorf("%s: unexpected addresses list %q, want %q", ifi.Name, have, want)
499 // check that getmac exists as a powershell command, and that it
501 func checkGetmac(t *testing.T) {
502 out, err := runCmd("getmac", "/?")
504 if strings.Contains(err.Error(), "term 'getmac' is not recognized as the name of a cmdlet") {
505 t.Skipf("getmac not available")
509 if !bytes.Contains(out, []byte("network adapters on a system")) {
510 t.Skipf("skipping test on non-English system")
514 func TestInterfaceHardwareAddrWithGetmac(t *testing.T) {
517 ift, err := Interfaces()
521 have := make(map[string]string)
522 for _, ifi := range ift {
523 if ifi.Flags&FlagLoopback != 0 {
524 // no MAC address for loopback interfaces
527 have[ifi.Name] = ifi.HardwareAddr.String()
530 out, err := runCmd("getmac", "/fo", "list", "/v")
534 // getmac output looks like:
536 //Connection Name: Local Area Connection
537 //Network Adapter: Intel Gigabit Network Connection
538 //Physical Address: XX-XX-XX-XX-XX-XX
539 //Transport Name: \Device\Tcpip_{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
541 //Connection Name: Wireless Network Connection
542 //Network Adapter: Wireles WLAN Card
543 //Physical Address: XX-XX-XX-XX-XX-XX
544 //Transport Name: Media disconnected
546 //Connection Name: Bluetooth Network Connection
547 //Network Adapter: Bluetooth Device (Personal Area Network)
548 //Physical Address: N/A
549 //Transport Name: Hardware not present
551 //Connection Name: VMware Network Adapter VMnet8
552 //Network Adapter: VMware Virtual Ethernet Adapter for VMnet8
553 //Physical Address: Disabled
554 //Transport Name: Disconnected
556 want := make(map[string]string)
557 group := make(map[string]string) // name / values for single adapter
558 getValue := func(name string) string {
559 value, found := group[name]
561 t.Fatalf("%q has no %q line in it", group, name)
564 t.Fatalf("%q has empty %q value", group, name)
568 processGroup := func() {
572 tname := strings.ToLower(getValue("Transport Name"))
577 addr := strings.ToLower(getValue("Physical Address"))
578 if addr == "disabled" || addr == "n/a" {
582 addr = strings.ReplaceAll(addr, "-", ":")
583 cname := getValue("Connection Name")
585 group = make(map[string]string)
587 lines := bytes.Split(out, []byte{'\r', '\n'})
588 for _, line := range lines {
593 i := bytes.IndexByte(line, ':')
595 t.Fatalf("line %q has no : in it", line)
597 group[string(line[:i])] = string(bytes.TrimSpace(line[i+1:]))
601 dups := make(map[string][]string)
602 for name, addr := range want {
603 if _, ok := dups[addr]; !ok {
604 dups[addr] = make([]string, 0)
606 dups[addr] = append(dups[addr], name)
610 for name, wantAddr := range want {
611 if haveAddr, ok := have[name]; ok {
612 if haveAddr != wantAddr {
613 t.Errorf("unexpected MAC address for %q - %v, want %v", name, haveAddr, wantAddr)
617 // We could not find the interface in getmac output by name.
618 // But sometimes getmac lists many interface names
619 // for the same MAC address. If that is the case here,
620 // and we can match at least one of those names,
621 // let's ignore the other names.
622 if dupNames, ok := dups[wantAddr]; ok && len(dupNames) > 1 {
623 for _, dupName := range dupNames {
624 if haveAddr, ok := have[dupName]; ok && haveAddr == wantAddr {
629 t.Errorf("getmac lists %q, but it could not be found among Go interfaces %v", name, have)