]> Cypherpunks.ru repositories - gostls13.git/blob - src/net/net_windows_test.go
cmd/compile/internal/inline: score call sites exposed by inlines
[gostls13.git] / src / net / net_windows_test.go
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         "internal/testenv"
12         "io"
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 := 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                 // netsh was observed to sometimes hang on this builder.
211                 // We have not observed failures on windows-arm64-11, so for the
212                 // moment we are leaving the test enabled elsewhere on the theory
213                 // that it may have been a platform bug fixed in Windows 11.
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         // interface information is listed like:
234         //
235         //Interface Local Area Connection Parameters
236         //----------------------------------------------
237         //IfLuid                             : ethernet_6
238         //IfIndex                            : 11
239         //State                              : connected
240         //Metric                             : 10
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         sort.Strings(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         sort.Strings(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         // Address information is listed like:
313         //
314         //Configuration for interface "Local Area Connection"
315         //    DHCP enabled:                         Yes
316         //    IP Address:                           10.0.0.2
317         //    Subnet Prefix:                        10.0.0.0/24 (mask 255.255.255.0)
318         //    IP Address:                           10.0.0.3
319         //    Subnet Prefix:                        10.0.0.0/24 (mask 255.255.255.0)
320         //    Default Gateway:                      10.0.0.254
321         //    Gateway Metric:                       0
322         //    InterfaceMetric:                      10
323         //
324         //Configuration for interface "Loopback Pseudo-Interface 1"
325         //    DHCP enabled:                         No
326         //    IP Address:                           127.0.0.1
327         //    Subnet Prefix:                        127.0.0.0/8 (mask 255.0.0.0)
328         //    InterfaceMetric:                      50
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         // Address information is listed like:
376         //
377         //Address ::1 Parameters
378         //---------------------------------------------------------
379         //Interface Luid     : Loopback Pseudo-Interface 1
380         //Scope Id           : 0.0
381         //Valid Lifetime     : infinite
382         //Preferred Lifetime : infinite
383         //DAD State          : Preferred
384         //Address Type       : Other
385         //Skip as Source     : false
386         //
387         //Address XXXX::XXXX:XXXX:XXXX:XXXX%11 Parameters
388         //---------------------------------------------------------
389         //Interface Luid     : Local Area Connection
390         //Scope Id           : 0.11
391         //Valid Lifetime     : infinite
392         //Preferred Lifetime : infinite
393         //DAD State          : Preferred
394         //Address Type       : Other
395         //Skip as Source     : false
396         //
397
398         // TODO: need to test ipv6 netmask too, but netsh does not outputs it
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                 // remove scope ID if present
426                 f = bytes.Split(f[1], []byte{'%'})
427
428                 // netsh can create IPv4-embedded IPv6 addresses, like fe80::5efe:192.168.140.1.
429                 // Convert these to all hexadecimal fe80::5efe:c0a8:8c01 for later string comparisons.
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                 // Skip the interface if it's down.
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                                         // netsh does not output netmask for ipv6, so ignore ipv6 mask
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                                         // netsh does not output netmask for ipv6, so ignore ipv6 mask
482                                         have = append(have, addr.IP.String())
483                                 }
484                         }
485                 }
486                 sort.Strings(have)
487
488                 want := netshInterfaceIPv4ShowAddress(ifi.Name, outIPV4)
489                 wantIPv6 := netshInterfaceIPv6ShowAddress(ifi.Name, outIPV6)
490                 want = append(want, wantIPv6...)
491                 sort.Strings(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 // check that getmac exists as a powershell command, and that it
500 // speaks English.
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                         // no MAC address for loopback interfaces
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         // getmac output looks like:
535         //
536         //Connection Name:  Local Area Connection
537         //Network Adapter:  Intel Gigabit Network Connection
538         //Physical Address: XX-XX-XX-XX-XX-XX
539         //Transport Name:   \Device\Tcpip_{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
540         //
541         //Connection Name:  Wireless Network Connection
542         //Network Adapter:  Wireles WLAN Card
543         //Physical Address: XX-XX-XX-XX-XX-XX
544         //Transport Name:   Media disconnected
545         //
546         //Connection Name:  Bluetooth Network Connection
547         //Network Adapter:  Bluetooth Device (Personal Area Network)
548         //Physical Address: N/A
549         //Transport Name:   Hardware not present
550         //
551         //Connection Name:  VMware Network Adapter VMnet8
552         //Network Adapter:  VMware Virtual Ethernet Adapter for VMnet8
553         //Physical Address: Disabled
554         //Transport Name:   Disconnected
555         //
556         want := make(map[string]string)
557         group := make(map[string]string) // name / values for single adapter
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                         // skip these
575                         return
576                 }
577                 addr := strings.ToLower(getValue("Physical Address"))
578                 if addr == "disabled" || addr == "n/a" {
579                         // skip these
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                 // We could not find the interface in getmac output by name.
618                 // But sometimes getmac lists many interface names
619                 // for the same MAC address. If that is the case here,
620                 // and we can match at least one of those names,
621                 // let's ignore the other names.
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 }