Source file
src/net/net_windows_test.go
1
2
3
4
5 package net
6
7 import (
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
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
40
41
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
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
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)
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
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()
124
125 const alittle = 100 * time.Millisecond
126 time.Sleep(alittle)
127 cmd.Process.Kill()
128 time.Sleep(alittle)
129
130
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
148 s, err := recv(ln, true)
149 if err != nil {
150 t.Fatalf("recv failed: %v", err)
151 }
152 switch s {
153 case "":
154
155 case "abc":
156
157 return
158 default:
159 t.Fatalf(`"%s" received from recv, but "" or "abc" expected`, s)
160 }
161
162
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 := 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
208 func checkNetsh(t *testing.T) {
209 if testenv.Builder() == "windows-arm64-10" {
210
211
212
213
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
228 func 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
234
235
236
237
238
239
240
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
271 func 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
311 func netshInterfaceIPv4ShowAddress(name string, netshOutput []byte) []string {
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
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
374 func netshInterfaceIPv6ShowAddress(name string, netshOutput []byte) []string {
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
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
426 f = bytes.Split(f[1], []byte{'%'})
427
428
429
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
440 func 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
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
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
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
500
501 func 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
514 func 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
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
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556 want := make(map[string]string)
557 group := make(map[string]string)
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
575 return
576 }
577 addr := strings.ToLower(getValue("Physical Address"))
578 if addr == "disabled" || addr == "n/a" {
579
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
609 nextWant:
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
618
619
620
621
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
View as plain text