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 "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
39
40
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
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
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)
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
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()
123
124 const alittle = 100 * time.Millisecond
125 time.Sleep(alittle)
126 cmd.Process.Kill()
127 time.Sleep(alittle)
128
129
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
147 s, err := recv(ln, true)
148 if err != nil {
149 t.Fatalf("recv failed: %v", err)
150 }
151 switch s {
152 case "":
153
154 case "abc":
155
156 return
157 default:
158 t.Fatalf(`"%s" received from recv, but "" or "abc" expected`, s)
159 }
160
161
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 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
220 func 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
226
227
228
229
230
231
232
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
263 func 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
303 func netshInterfaceIPv4ShowAddress(name string, netshOutput []byte) []string {
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
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
366 func netshInterfaceIPv6ShowAddress(name string, netshOutput []byte) []string {
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
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
418 f = bytes.Split(f[1], []byte{'%'})
419
420
421
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
432 func 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
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
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
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
492
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
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
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548 want := make(map[string]string)
549 group := make(map[string]string)
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
567 return
568 }
569 addr := strings.ToLower(getValue("Physical Address"))
570 if addr == "disabled" || addr == "n/a" {
571
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
610
611
612
613
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
View as plain text