gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/net/icmp/diag_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 icmp_test
     6  
     7  import (
     8  	"errors"
     9  	"flag"
    10  	"fmt"
    11  	"net"
    12  	"os"
    13  	"runtime"
    14  	"sync"
    15  	"testing"
    16  	"time"
    17  
    18  	"gitee.com/ks-custle/core-gm/net/icmp"
    19  	"gitee.com/ks-custle/core-gm/net/internal/iana"
    20  	"gitee.com/ks-custle/core-gm/net/ipv4"
    21  	"gitee.com/ks-custle/core-gm/net/ipv6"
    22  	"gitee.com/ks-custle/core-gm/net/nettest"
    23  )
    24  
    25  var testDiag = flag.Bool("diag", false, "whether to test ICMP message exchange with external network")
    26  
    27  type diagTest struct {
    28  	network, address string
    29  	protocol         int
    30  	m                icmp.Message
    31  }
    32  
    33  func TestDiag(t *testing.T) {
    34  	if !*testDiag {
    35  		t.Skip("avoid external network")
    36  	}
    37  
    38  	t.Run("Ping/NonPrivileged", func(t *testing.T) {
    39  		if m, ok := supportsNonPrivilegedICMP(); !ok {
    40  			t.Skip(m)
    41  		}
    42  		for i, dt := range []diagTest{
    43  			{
    44  				"udp4", "0.0.0.0", iana.ProtocolICMP,
    45  				icmp.Message{
    46  					Type: ipv4.ICMPTypeEcho, Code: 0,
    47  					Body: &icmp.Echo{
    48  						ID:   os.Getpid() & 0xffff,
    49  						Data: []byte("HELLO-R-U-THERE"),
    50  					},
    51  				},
    52  			},
    53  
    54  			{
    55  				"udp6", "::", iana.ProtocolIPv6ICMP,
    56  				icmp.Message{
    57  					Type: ipv6.ICMPTypeEchoRequest, Code: 0,
    58  					Body: &icmp.Echo{
    59  						ID:   os.Getpid() & 0xffff,
    60  						Data: []byte("HELLO-R-U-THERE"),
    61  					},
    62  				},
    63  			},
    64  		} {
    65  			if err := doDiag(dt, i); err != nil {
    66  				t.Error(err)
    67  			}
    68  		}
    69  	})
    70  	t.Run("Ping/Privileged", func(t *testing.T) {
    71  		if !nettest.SupportsRawSocket() {
    72  			t.Skipf("not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
    73  		}
    74  		for i, dt := range []diagTest{
    75  			{
    76  				"ip4:icmp", "0.0.0.0", iana.ProtocolICMP,
    77  				icmp.Message{
    78  					Type: ipv4.ICMPTypeEcho, Code: 0,
    79  					Body: &icmp.Echo{
    80  						ID:   os.Getpid() & 0xffff,
    81  						Data: []byte("HELLO-R-U-THERE"),
    82  					},
    83  				},
    84  			},
    85  
    86  			{
    87  				"ip6:ipv6-icmp", "::", iana.ProtocolIPv6ICMP,
    88  				icmp.Message{
    89  					Type: ipv6.ICMPTypeEchoRequest, Code: 0,
    90  					Body: &icmp.Echo{
    91  						ID:   os.Getpid() & 0xffff,
    92  						Data: []byte("HELLO-R-U-THERE"),
    93  					},
    94  				},
    95  			},
    96  		} {
    97  			if err := doDiag(dt, i); err != nil {
    98  				t.Error(err)
    99  			}
   100  		}
   101  	})
   102  	t.Run("Probe/Privileged", func(t *testing.T) {
   103  		if !nettest.SupportsRawSocket() {
   104  			t.Skipf("not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
   105  		}
   106  		for i, dt := range []diagTest{
   107  			{
   108  				"ip4:icmp", "0.0.0.0", iana.ProtocolICMP,
   109  				icmp.Message{
   110  					Type: ipv4.ICMPTypeExtendedEchoRequest, Code: 0,
   111  					Body: &icmp.ExtendedEchoRequest{
   112  						ID:    os.Getpid() & 0xffff,
   113  						Local: true,
   114  						Extensions: []icmp.Extension{
   115  							&icmp.InterfaceIdent{
   116  								Class: 3, Type: 1,
   117  								Name: "doesnotexist",
   118  							},
   119  						},
   120  					},
   121  				},
   122  			},
   123  
   124  			{
   125  				"ip6:ipv6-icmp", "::", iana.ProtocolIPv6ICMP,
   126  				icmp.Message{
   127  					Type: ipv6.ICMPTypeExtendedEchoRequest, Code: 0,
   128  					Body: &icmp.ExtendedEchoRequest{
   129  						ID:    os.Getpid() & 0xffff,
   130  						Local: true,
   131  						Extensions: []icmp.Extension{
   132  							&icmp.InterfaceIdent{
   133  								Class: 3, Type: 1,
   134  								Name: "doesnotexist",
   135  							},
   136  						},
   137  					},
   138  				},
   139  			},
   140  		} {
   141  			if err := doDiag(dt, i); err != nil {
   142  				t.Error(err)
   143  			}
   144  		}
   145  	})
   146  }
   147  
   148  func doDiag(dt diagTest, seq int) error {
   149  	c, err := icmp.ListenPacket(dt.network, dt.address)
   150  	if err != nil {
   151  		return err
   152  	}
   153  	defer c.Close()
   154  
   155  	dst, err := googleAddr(c, dt.protocol)
   156  	if err != nil {
   157  		return err
   158  	}
   159  
   160  	if dt.network != "udp6" && dt.protocol == iana.ProtocolIPv6ICMP {
   161  		var f ipv6.ICMPFilter
   162  		f.SetAll(true)
   163  		f.Accept(ipv6.ICMPTypeDestinationUnreachable)
   164  		f.Accept(ipv6.ICMPTypePacketTooBig)
   165  		f.Accept(ipv6.ICMPTypeTimeExceeded)
   166  		f.Accept(ipv6.ICMPTypeParameterProblem)
   167  		f.Accept(ipv6.ICMPTypeEchoReply)
   168  		f.Accept(ipv6.ICMPTypeExtendedEchoReply)
   169  		if err := c.IPv6PacketConn().SetICMPFilter(&f); err != nil {
   170  			return err
   171  		}
   172  	}
   173  
   174  	switch m := dt.m.Body.(type) {
   175  	case *icmp.Echo:
   176  		m.Seq = 1 << uint(seq)
   177  	case *icmp.ExtendedEchoRequest:
   178  		m.Seq = 1 << uint(seq)
   179  	}
   180  	wb, err := dt.m.Marshal(nil)
   181  	if err != nil {
   182  		return err
   183  	}
   184  	if n, err := c.WriteTo(wb, dst); err != nil {
   185  		return err
   186  	} else if n != len(wb) {
   187  		return fmt.Errorf("got %v; want %v", n, len(wb))
   188  	}
   189  
   190  	rb := make([]byte, 1500)
   191  	if err := c.SetReadDeadline(time.Now().Add(3 * time.Second)); err != nil {
   192  		return err
   193  	}
   194  	n, peer, err := c.ReadFrom(rb)
   195  	if err != nil {
   196  		return err
   197  	}
   198  	rm, err := icmp.ParseMessage(dt.protocol, rb[:n])
   199  	if err != nil {
   200  		return err
   201  	}
   202  	switch {
   203  	case dt.m.Type == ipv4.ICMPTypeEcho && rm.Type == ipv4.ICMPTypeEchoReply:
   204  		fallthrough
   205  	case dt.m.Type == ipv6.ICMPTypeEchoRequest && rm.Type == ipv6.ICMPTypeEchoReply:
   206  		fallthrough
   207  	case dt.m.Type == ipv4.ICMPTypeExtendedEchoRequest && rm.Type == ipv4.ICMPTypeExtendedEchoReply:
   208  		fallthrough
   209  	case dt.m.Type == ipv6.ICMPTypeExtendedEchoRequest && rm.Type == ipv6.ICMPTypeExtendedEchoReply:
   210  		return nil
   211  	default:
   212  		return fmt.Errorf("got %+v from %v; want echo reply or extended echo reply", rm, peer)
   213  	}
   214  }
   215  
   216  func googleAddr(c *icmp.PacketConn, protocol int) (net.Addr, error) {
   217  	host := "ipv4.google.com"
   218  	if protocol == iana.ProtocolIPv6ICMP {
   219  		host = "ipv6.google.com"
   220  	}
   221  	ips, err := net.LookupIP(host)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  	netaddr := func(ip net.IP) (net.Addr, error) {
   226  		switch c.LocalAddr().(type) {
   227  		case *net.UDPAddr:
   228  			return &net.UDPAddr{IP: ip}, nil
   229  		case *net.IPAddr:
   230  			return &net.IPAddr{IP: ip}, nil
   231  		default:
   232  			return nil, errors.New("neither UDPAddr nor IPAddr")
   233  		}
   234  	}
   235  	if len(ips) > 0 {
   236  		return netaddr(ips[0])
   237  	}
   238  	return nil, errors.New("no A or AAAA record")
   239  }
   240  
   241  func TestConcurrentNonPrivilegedListenPacket(t *testing.T) {
   242  	if testing.Short() {
   243  		t.Skip("avoid external network")
   244  	}
   245  	if m, ok := supportsNonPrivilegedICMP(); !ok {
   246  		t.Skip(m)
   247  	}
   248  
   249  	network, address := "udp4", "127.0.0.1"
   250  	if !nettest.SupportsIPv4() {
   251  		network, address = "udp6", "::1"
   252  	}
   253  	const N = 1000
   254  	var wg sync.WaitGroup
   255  	wg.Add(N)
   256  	for i := 0; i < N; i++ {
   257  		go func() {
   258  			defer wg.Done()
   259  			c, err := icmp.ListenPacket(network, address)
   260  			if err != nil {
   261  				t.Error(err)
   262  				return
   263  			}
   264  			c.Close()
   265  		}()
   266  	}
   267  	wg.Wait()
   268  }
   269  
   270  var (
   271  	nonPrivOnce sync.Once
   272  	nonPrivMsg  string
   273  	nonPrivICMP bool
   274  )
   275  
   276  func supportsNonPrivilegedICMP() (string, bool) {
   277  	nonPrivOnce.Do(func() {
   278  		switch runtime.GOOS {
   279  		case "darwin", "ios":
   280  			nonPrivICMP = true
   281  		case "linux":
   282  			for _, t := range []struct{ network, address string }{
   283  				{"udp4", "127.0.0.1"},
   284  				{"udp6", "::1"},
   285  			} {
   286  				c, err := icmp.ListenPacket(t.network, t.address)
   287  				if err != nil {
   288  					nonPrivMsg = "you may need to adjust the net.ipv4.ping_group_range kernel state"
   289  					return
   290  				}
   291  				c.Close()
   292  			}
   293  			nonPrivICMP = true
   294  		default:
   295  			nonPrivMsg = "not supported on " + runtime.GOOS
   296  		}
   297  	})
   298  	return nonPrivMsg, nonPrivICMP
   299  }