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 "internal/testenv" 12 "io" 13 "os" 14 "os/exec" 15 "regexp" 16 "slices" 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 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 { 175 return b[3:] 176 } 177 return b 178 } 179 f, err := os.CreateTemp("", "netcmd") 180 if err != nil { 181 return nil, err 182 } 183 f.Close() 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() 187 if err != nil { 188 if len(out) != 0 { 189 return nil, fmt.Errorf("%s failed: %v: %q", args[0], err, string(removeUTF8BOM(out))) 190 } 191 var err2 error 192 out, err2 = os.ReadFile(f.Name()) 193 if err2 != nil { 194 return nil, err2 195 } 196 if len(out) != 0 { 197 return nil, fmt.Errorf("%s failed: %v: %q", args[0], err, string(removeUTF8BOM(out))) 198 } 199 return nil, fmt.Errorf("%s failed: %v", args[0], err) 200 } 201 out, err = os.ReadFile(f.Name()) 202 if err != nil { 203 return nil, err 204 } 205 return removeUTF8BOM(out), nil 206} 207 208func 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) 215 } 216 out, err := runCmd("netsh", "help") 217 if err != nil { 218 t.Fatal(err) 219 } 220 if bytes.Contains(out, []byte("The following helper DLL cannot be loaded")) { 221 t.Skipf("powershell failure:\n%s", err) 222 } 223 if !bytes.Contains(out, []byte("The following commands are available:")) { 224 t.Skipf("powershell does not speak English:\n%s", out) 225 } 226} 227 228func netshInterfaceIPShowInterface(ipver string, ifaces map[string]bool) error { 229 out, err := runCmd("netsh", "interface", ipver, "show", "interface", "level=verbose") 230 if err != nil { 231 return err 232 } 233 // interface information is listed like: 234 // 235 //Interface Local Area Connection Parameters 236 //---------------------------------------------- 237 //IfLuid : ethernet_6 238 //IfIndex : 11 239 //State : connected 240 //Metric : 10 241 //... 242 var name string 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")] 248 name = string(f) 249 continue 250 } 251 var isup bool 252 switch string(line) { 253 case "State : connected": 254 isup = true 255 case "State : disconnected": 256 isup = false 257 default: 258 continue 259 } 260 if name != "" { 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) 263 } 264 ifaces[name] = isup 265 name = "" 266 } 267 } 268 return nil 269} 270 271func TestInterfacesWithNetsh(t *testing.T) { 272 checkNetsh(t) 273 274 toString := func(name string, isup bool) string { 275 if isup { 276 return name + ":up" 277 } 278 return name + ":down" 279 } 280 281 ift, err := Interfaces() 282 if err != nil { 283 t.Fatal(err) 284 } 285 have := make([]string, 0) 286 for _, ifi := range ift { 287 have = append(have, toString(ifi.Name, ifi.Flags&FlagUp != 0)) 288 } 289 slices.Sort(have) 290 291 ifaces := make(map[string]bool) 292 err = netshInterfaceIPShowInterface("ipv6", ifaces) 293 if err != nil { 294 t.Fatal(err) 295 } 296 err = netshInterfaceIPShowInterface("ipv4", ifaces) 297 if err != nil { 298 t.Fatal(err) 299 } 300 want := make([]string, 0) 301 for name, isup := range ifaces { 302 want = append(want, toString(name, isup)) 303 } 304 slices.Sort(want) 305 306 if strings.Join(want, "/") != strings.Join(have, "/") { 307 t.Fatalf("unexpected interface list %q, want %q", have, want) 308 } 309} 310 311func netshInterfaceIPv4ShowAddress(name string, netshOutput []byte) []string { 312 // Address information is listed like: 313 // 314 //Configuration for interface "Local Area Connection" 315 // DHCP enabled: Yes 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 321 // Gateway Metric: 0 322 // InterfaceMetric: 10 323 // 324 //Configuration for interface "Loopback Pseudo-Interface 1" 325 // DHCP enabled: No 326 // IP Address: 127.0.0.1 327 // Subnet Prefix: 127.0.0.0/8 (mask 255.0.0.0) 328 // InterfaceMetric: 50 329 // 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")) { 337 continue 338 } 339 if !bytes.Contains(line, []byte(`"`+name+`"`)) { 340 continue 341 } 342 processingOurInterface = true 343 continue 344 } 345 if len(line) == 0 { 346 break 347 } 348 if bytes.Contains(line, []byte("Subnet Prefix:")) { 349 f := bytes.Split(line, []byte{':'}) 350 if len(f) == 2 { 351 f = bytes.Split(f[1], []byte{'('}) 352 if len(f) == 2 { 353 f = bytes.Split(f[0], []byte{'/'}) 354 if len(f) == 2 { 355 subnetprefix = string(bytes.TrimSpace(f[1])) 356 if addr != "" && subnetprefix != "" { 357 addrs = append(addrs, addr+"/"+subnetprefix) 358 } 359 } 360 } 361 } 362 } 363 addr = "" 364 if bytes.Contains(line, []byte("IP Address:")) { 365 f := bytes.Split(line, []byte{':'}) 366 if len(f) == 2 { 367 addr = string(bytes.TrimSpace(f[1])) 368 } 369 } 370 } 371 return addrs 372} 373 374func netshInterfaceIPv6ShowAddress(name string, netshOutput []byte) []string { 375 // Address information is listed like: 376 // 377 //Address ::1 Parameters 378 //--------------------------------------------------------- 379 //Interface Luid : Loopback Pseudo-Interface 1 380 //Scope Id : 0.0 381 //Valid Lifetime : infinite 382 //Preferred Lifetime : infinite 383 //DAD State : Preferred 384 //Address Type : Other 385 //Skip as Source : false 386 // 387 //Address XXXX::XXXX:XXXX:XXXX:XXXX%11 Parameters 388 //--------------------------------------------------------- 389 //Interface Luid : Local Area Connection 390 //Scope Id : 0.11 391 //Valid Lifetime : infinite 392 //Preferred Lifetime : infinite 393 //DAD State : Preferred 394 //Address Type : Other 395 //Skip as Source : false 396 // 397 398 // TODO: need to test ipv6 netmask too, but netsh does not outputs it 399 var addr string 400 addrs := make([]string, 0) 401 lines := bytes.Split(netshOutput, []byte{'\r', '\n'}) 402 for _, line := range lines { 403 if addr != "" { 404 if len(line) == 0 { 405 addr = "" 406 continue 407 } 408 if string(line) != "Interface Luid : "+name { 409 continue 410 } 411 addrs = append(addrs, addr) 412 addr = "" 413 continue 414 } 415 if !bytes.HasPrefix(line, []byte("Address")) { 416 continue 417 } 418 if !bytes.HasSuffix(line, []byte("Parameters")) { 419 continue 420 } 421 f := bytes.Split(line, []byte{' '}) 422 if len(f) != 3 { 423 continue 424 } 425 // remove scope ID if present 426 f = bytes.Split(f[1], []byte{'%'}) 427 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()) 433 } 434 435 addr = string(bytes.ToLower(bytes.TrimSpace(f[0]))) 436 } 437 return addrs 438} 439 440func TestInterfaceAddrsWithNetsh(t *testing.T) { 441 checkNetsh(t) 442 443 outIPV4, err := runCmd("netsh", "interface", "ipv4", "show", "address") 444 if err != nil { 445 t.Fatal(err) 446 } 447 outIPV6, err := runCmd("netsh", "interface", "ipv6", "show", "address", "level=verbose") 448 if err != nil { 449 t.Fatal(err) 450 } 451 452 ift, err := Interfaces() 453 if err != nil { 454 t.Fatal(err) 455 } 456 for _, ifi := range ift { 457 // Skip the interface if it's down. 458 if (ifi.Flags & FlagUp) == 0 { 459 continue 460 } 461 have := make([]string, 0) 462 addrs, err := ifi.Addrs() 463 if err != nil { 464 t.Fatal(err) 465 } 466 for _, addr := range addrs { 467 switch addr := addr.(type) { 468 case *IPNet: 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 case *IPAddr: 477 if addr.IP.To4() != nil { 478 have = append(have, addr.String()) 479 } 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()) 483 } 484 } 485 } 486 slices.Sort(have) 487 488 want := netshInterfaceIPv4ShowAddress(ifi.Name, outIPV4) 489 wantIPv6 := netshInterfaceIPv6ShowAddress(ifi.Name, outIPV6) 490 want = append(want, wantIPv6...) 491 slices.Sort(want) 492 493 if strings.Join(want, "/") != strings.Join(have, "/") { 494 t.Errorf("%s: unexpected addresses list %q, want %q", ifi.Name, have, want) 495 } 496 } 497} 498 499// check that getmac exists as a powershell command, and that it 500// speaks English. 501func checkGetmac(t *testing.T) { 502 out, err := runCmd("getmac", "/?") 503 if err != nil { 504 if strings.Contains(err.Error(), "term 'getmac' is not recognized as the name of a cmdlet") { 505 t.Skipf("getmac not available") 506 } 507 t.Fatal(err) 508 } 509 if !bytes.Contains(out, []byte("network adapters on a system")) { 510 t.Skipf("skipping test on non-English system") 511 } 512} 513 514func TestInterfaceHardwareAddrWithGetmac(t *testing.T) { 515 checkGetmac(t) 516 517 ift, err := Interfaces() 518 if err != nil { 519 t.Fatal(err) 520 } 521 have := make(map[string]string) 522 for _, ifi := range ift { 523 if ifi.Flags&FlagLoopback != 0 { 524 // no MAC address for loopback interfaces 525 continue 526 } 527 have[ifi.Name] = ifi.HardwareAddr.String() 528 } 529 530 out, err := runCmd("getmac", "/fo", "list", "/v") 531 if err != nil { 532 t.Fatal(err) 533 } 534 // getmac output looks like: 535 // 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} 540 // 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 545 // 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 550 // 551 //Connection Name: VMware Network Adapter VMnet8 552 //Network Adapter: VMware Virtual Ethernet Adapter for VMnet8 553 //Physical Address: Disabled 554 //Transport Name: Disconnected 555 // 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] 560 if !found { 561 t.Fatalf("%q has no %q line in it", group, name) 562 } 563 if value == "" { 564 t.Fatalf("%q has empty %q value", group, name) 565 } 566 return value 567 } 568 processGroup := func() { 569 if len(group) == 0 { 570 return 571 } 572 tname := strings.ToLower(getValue("Transport Name")) 573 if tname == "n/a" { 574 // skip these 575 return 576 } 577 addr := strings.ToLower(getValue("Physical Address")) 578 if addr == "disabled" || addr == "n/a" { 579 // skip these 580 return 581 } 582 addr = strings.ReplaceAll(addr, "-", ":") 583 cname := getValue("Connection Name") 584 want[cname] = addr 585 group = make(map[string]string) 586 } 587 lines := bytes.Split(out, []byte{'\r', '\n'}) 588 for _, line := range lines { 589 if len(line) == 0 { 590 processGroup() 591 continue 592 } 593 i := bytes.IndexByte(line, ':') 594 if i == -1 { 595 t.Fatalf("line %q has no : in it", line) 596 } 597 group[string(line[:i])] = string(bytes.TrimSpace(line[i+1:])) 598 } 599 processGroup() 600 601 dups := make(map[string][]string) 602 for name, addr := range want { 603 if _, ok := dups[addr]; !ok { 604 dups[addr] = make([]string, 0) 605 } 606 dups[addr] = append(dups[addr], name) 607 } 608 609nextWant: 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) 614 } 615 continue 616 } 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 { 625 continue nextWant 626 } 627 } 628 } 629 t.Errorf("getmac lists %q, but it could not be found among Go interfaces %v", name, have) 630 } 631} 632