github.com/c12o16h1/go/src@v0.0.0-20200114212001-5a151c0f00ed/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 "io/ioutil" 13 "os" 14 "os/exec" 15 "regexp" 16 "sort" 17 "strings" 18 "syscall" 19 "testing" 20 "time" 21 ) 22 23 func 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. 42 func 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 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 { 175 return b[3:] 176 } 177 return b 178 } 179 f, err := ioutil.TempFile("", "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 = ioutil.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 = ioutil.ReadFile(f.Name()) 202 if err != nil { 203 return nil, err 204 } 205 return removeUTF8BOM(out), nil 206 } 207 208 func netshSpeaksEnglish(t *testing.T) bool { 209 out, err := runCmd("netsh", "help") 210 if err != nil { 211 t.Fatal(err) 212 } 213 return bytes.Contains(out, []byte("The following commands are available:")) 214 } 215 216 func netshInterfaceIPShowInterface(ipver string, ifaces map[string]bool) error { 217 out, err := runCmd("netsh", "interface", ipver, "show", "interface", "level=verbose") 218 if err != nil { 219 return err 220 } 221 // interface information is listed like: 222 // 223 //Interface Local Area Connection Parameters 224 //---------------------------------------------- 225 //IfLuid : ethernet_6 226 //IfIndex : 11 227 //State : connected 228 //Metric : 10 229 //... 230 var name string 231 lines := bytes.Split(out, []byte{'\r', '\n'}) 232 for _, line := range lines { 233 if bytes.HasPrefix(line, []byte("Interface ")) && bytes.HasSuffix(line, []byte(" Parameters")) { 234 f := line[len("Interface "):] 235 f = f[:len(f)-len(" Parameters")] 236 name = string(f) 237 continue 238 } 239 var isup bool 240 switch string(line) { 241 case "State : connected": 242 isup = true 243 case "State : disconnected": 244 isup = false 245 default: 246 continue 247 } 248 if name != "" { 249 if v, ok := ifaces[name]; ok && v != isup { 250 return fmt.Errorf("%s:%s isup=%v: ipv4 and ipv6 report different interface state", ipver, name, isup) 251 } 252 ifaces[name] = isup 253 name = "" 254 } 255 } 256 return nil 257 } 258 259 func TestInterfacesWithNetsh(t *testing.T) { 260 if !netshSpeaksEnglish(t) { 261 t.Skip("English version of netsh required for this test") 262 } 263 264 toString := func(name string, isup bool) string { 265 if isup { 266 return name + ":up" 267 } 268 return name + ":down" 269 } 270 271 ift, err := Interfaces() 272 if err != nil { 273 t.Fatal(err) 274 } 275 have := make([]string, 0) 276 for _, ifi := range ift { 277 have = append(have, toString(ifi.Name, ifi.Flags&FlagUp != 0)) 278 } 279 sort.Strings(have) 280 281 ifaces := make(map[string]bool) 282 err = netshInterfaceIPShowInterface("ipv6", ifaces) 283 if err != nil { 284 t.Fatal(err) 285 } 286 err = netshInterfaceIPShowInterface("ipv4", ifaces) 287 if err != nil { 288 t.Fatal(err) 289 } 290 want := make([]string, 0) 291 for name, isup := range ifaces { 292 want = append(want, toString(name, isup)) 293 } 294 sort.Strings(want) 295 296 if strings.Join(want, "/") != strings.Join(have, "/") { 297 t.Fatalf("unexpected interface list %q, want %q", have, want) 298 } 299 } 300 301 func netshInterfaceIPv4ShowAddress(name string, netshOutput []byte) []string { 302 // Address information is listed like: 303 // 304 //Configuration for interface "Local Area Connection" 305 // DHCP enabled: Yes 306 // IP Address: 10.0.0.2 307 // Subnet Prefix: 10.0.0.0/24 (mask 255.255.255.0) 308 // IP Address: 10.0.0.3 309 // Subnet Prefix: 10.0.0.0/24 (mask 255.255.255.0) 310 // Default Gateway: 10.0.0.254 311 // Gateway Metric: 0 312 // InterfaceMetric: 10 313 // 314 //Configuration for interface "Loopback Pseudo-Interface 1" 315 // DHCP enabled: No 316 // IP Address: 127.0.0.1 317 // Subnet Prefix: 127.0.0.0/8 (mask 255.0.0.0) 318 // InterfaceMetric: 50 319 // 320 addrs := make([]string, 0) 321 var addr, subnetprefix string 322 var processingOurInterface bool 323 lines := bytes.Split(netshOutput, []byte{'\r', '\n'}) 324 for _, line := range lines { 325 if !processingOurInterface { 326 if !bytes.HasPrefix(line, []byte("Configuration for interface")) { 327 continue 328 } 329 if !bytes.Contains(line, []byte(`"`+name+`"`)) { 330 continue 331 } 332 processingOurInterface = true 333 continue 334 } 335 if len(line) == 0 { 336 break 337 } 338 if bytes.Contains(line, []byte("Subnet Prefix:")) { 339 f := bytes.Split(line, []byte{':'}) 340 if len(f) == 2 { 341 f = bytes.Split(f[1], []byte{'('}) 342 if len(f) == 2 { 343 f = bytes.Split(f[0], []byte{'/'}) 344 if len(f) == 2 { 345 subnetprefix = string(bytes.TrimSpace(f[1])) 346 if addr != "" && subnetprefix != "" { 347 addrs = append(addrs, addr+"/"+subnetprefix) 348 } 349 } 350 } 351 } 352 } 353 addr = "" 354 if bytes.Contains(line, []byte("IP Address:")) { 355 f := bytes.Split(line, []byte{':'}) 356 if len(f) == 2 { 357 addr = string(bytes.TrimSpace(f[1])) 358 } 359 } 360 } 361 return addrs 362 } 363 364 func netshInterfaceIPv6ShowAddress(name string, netshOutput []byte) []string { 365 // Address information is listed like: 366 // 367 //Address ::1 Parameters 368 //--------------------------------------------------------- 369 //Interface Luid : Loopback Pseudo-Interface 1 370 //Scope Id : 0.0 371 //Valid Lifetime : infinite 372 //Preferred Lifetime : infinite 373 //DAD State : Preferred 374 //Address Type : Other 375 //Skip as Source : false 376 // 377 //Address XXXX::XXXX:XXXX:XXXX:XXXX%11 Parameters 378 //--------------------------------------------------------- 379 //Interface Luid : Local Area Connection 380 //Scope Id : 0.11 381 //Valid Lifetime : infinite 382 //Preferred Lifetime : infinite 383 //DAD State : Preferred 384 //Address Type : Other 385 //Skip as Source : false 386 // 387 388 // TODO: need to test ipv6 netmask too, but netsh does not outputs it 389 var addr string 390 addrs := make([]string, 0) 391 lines := bytes.Split(netshOutput, []byte{'\r', '\n'}) 392 for _, line := range lines { 393 if addr != "" { 394 if len(line) == 0 { 395 addr = "" 396 continue 397 } 398 if string(line) != "Interface Luid : "+name { 399 continue 400 } 401 addrs = append(addrs, addr) 402 addr = "" 403 continue 404 } 405 if !bytes.HasPrefix(line, []byte("Address")) { 406 continue 407 } 408 if !bytes.HasSuffix(line, []byte("Parameters")) { 409 continue 410 } 411 f := bytes.Split(line, []byte{' '}) 412 if len(f) != 3 { 413 continue 414 } 415 // remove scope ID if present 416 f = bytes.Split(f[1], []byte{'%'}) 417 418 // netsh can create IPv4-embedded IPv6 addresses, like fe80::5efe:192.168.140.1. 419 // Convert these to all hexadecimal fe80::5efe:c0a8:8c01 for later string comparisons. 420 ipv4Tail := regexp.MustCompile(`:\d+\.\d+\.\d+\.\d+$`) 421 if ipv4Tail.Match(f[0]) { 422 f[0] = []byte(ParseIP(string(f[0])).String()) 423 } 424 425 addr = string(bytes.ToLower(bytes.TrimSpace(f[0]))) 426 } 427 return addrs 428 } 429 430 func TestInterfaceAddrsWithNetsh(t *testing.T) { 431 if !netshSpeaksEnglish(t) { 432 t.Skip("English version of netsh required for this test") 433 } 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. 493 func 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 506 func 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 601 nextWant: 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 }