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