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