123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- // Copyright 2014 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package icmp_test
- import (
- "errors"
- "flag"
- "fmt"
- "net"
- "os"
- "runtime"
- "sync"
- "testing"
- "time"
- "golang.org/x/net/icmp"
- "golang.org/x/net/internal/iana"
- "golang.org/x/net/ipv4"
- "golang.org/x/net/ipv6"
- "golang.org/x/net/nettest"
- )
- var testDiag = flag.Bool("diag", false, "whether to test ICMP message exchange with external network")
- type diagTest struct {
- network, address string
- protocol int
- m icmp.Message
- }
- func TestDiag(t *testing.T) {
- if !*testDiag {
- t.Skip("avoid external network")
- }
- t.Run("Ping/NonPrivileged", func(t *testing.T) {
- if m, ok := supportsNonPrivilegedICMP(); !ok {
- t.Skip(m)
- }
- for i, dt := range []diagTest{
- {
- "udp4", "0.0.0.0", iana.ProtocolICMP,
- icmp.Message{
- Type: ipv4.ICMPTypeEcho, Code: 0,
- Body: &icmp.Echo{
- ID: os.Getpid() & 0xffff,
- Data: []byte("HELLO-R-U-THERE"),
- },
- },
- },
- {
- "udp6", "::", iana.ProtocolIPv6ICMP,
- icmp.Message{
- Type: ipv6.ICMPTypeEchoRequest, Code: 0,
- Body: &icmp.Echo{
- ID: os.Getpid() & 0xffff,
- Data: []byte("HELLO-R-U-THERE"),
- },
- },
- },
- } {
- if err := doDiag(dt, i); err != nil {
- t.Error(err)
- }
- }
- })
- t.Run("Ping/Privileged", func(t *testing.T) {
- if !nettest.SupportsRawSocket() {
- t.Skipf("not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
- }
- for i, dt := range []diagTest{
- {
- "ip4:icmp", "0.0.0.0", iana.ProtocolICMP,
- icmp.Message{
- Type: ipv4.ICMPTypeEcho, Code: 0,
- Body: &icmp.Echo{
- ID: os.Getpid() & 0xffff,
- Data: []byte("HELLO-R-U-THERE"),
- },
- },
- },
- {
- "ip6:ipv6-icmp", "::", iana.ProtocolIPv6ICMP,
- icmp.Message{
- Type: ipv6.ICMPTypeEchoRequest, Code: 0,
- Body: &icmp.Echo{
- ID: os.Getpid() & 0xffff,
- Data: []byte("HELLO-R-U-THERE"),
- },
- },
- },
- } {
- if err := doDiag(dt, i); err != nil {
- t.Error(err)
- }
- }
- })
- t.Run("Probe/Privileged", func(t *testing.T) {
- if !nettest.SupportsRawSocket() {
- t.Skipf("not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
- }
- for i, dt := range []diagTest{
- {
- "ip4:icmp", "0.0.0.0", iana.ProtocolICMP,
- icmp.Message{
- Type: ipv4.ICMPTypeExtendedEchoRequest, Code: 0,
- Body: &icmp.ExtendedEchoRequest{
- ID: os.Getpid() & 0xffff,
- Local: true,
- Extensions: []icmp.Extension{
- &icmp.InterfaceIdent{
- Class: 3, Type: 1,
- Name: "doesnotexist",
- },
- },
- },
- },
- },
- {
- "ip6:ipv6-icmp", "::", iana.ProtocolIPv6ICMP,
- icmp.Message{
- Type: ipv6.ICMPTypeExtendedEchoRequest, Code: 0,
- Body: &icmp.ExtendedEchoRequest{
- ID: os.Getpid() & 0xffff,
- Local: true,
- Extensions: []icmp.Extension{
- &icmp.InterfaceIdent{
- Class: 3, Type: 1,
- Name: "doesnotexist",
- },
- },
- },
- },
- },
- } {
- if err := doDiag(dt, i); err != nil {
- t.Error(err)
- }
- }
- })
- }
- func doDiag(dt diagTest, seq int) error {
- c, err := icmp.ListenPacket(dt.network, dt.address)
- if err != nil {
- return err
- }
- defer c.Close()
- dst, err := googleAddr(c, dt.protocol)
- if err != nil {
- return err
- }
- if dt.network != "udp6" && dt.protocol == iana.ProtocolIPv6ICMP {
- var f ipv6.ICMPFilter
- f.SetAll(true)
- f.Accept(ipv6.ICMPTypeDestinationUnreachable)
- f.Accept(ipv6.ICMPTypePacketTooBig)
- f.Accept(ipv6.ICMPTypeTimeExceeded)
- f.Accept(ipv6.ICMPTypeParameterProblem)
- f.Accept(ipv6.ICMPTypeEchoReply)
- f.Accept(ipv6.ICMPTypeExtendedEchoReply)
- if err := c.IPv6PacketConn().SetICMPFilter(&f); err != nil {
- return err
- }
- }
- switch m := dt.m.Body.(type) {
- case *icmp.Echo:
- m.Seq = 1 << uint(seq)
- case *icmp.ExtendedEchoRequest:
- m.Seq = 1 << uint(seq)
- }
- wb, err := dt.m.Marshal(nil)
- if err != nil {
- return err
- }
- if n, err := c.WriteTo(wb, dst); err != nil {
- return err
- } else if n != len(wb) {
- return fmt.Errorf("got %v; want %v", n, len(wb))
- }
- rb := make([]byte, 1500)
- if err := c.SetReadDeadline(time.Now().Add(3 * time.Second)); err != nil {
- return err
- }
- n, peer, err := c.ReadFrom(rb)
- if err != nil {
- return err
- }
- rm, err := icmp.ParseMessage(dt.protocol, rb[:n])
- if err != nil {
- return err
- }
- switch {
- case dt.m.Type == ipv4.ICMPTypeEcho && rm.Type == ipv4.ICMPTypeEchoReply:
- fallthrough
- case dt.m.Type == ipv6.ICMPTypeEchoRequest && rm.Type == ipv6.ICMPTypeEchoReply:
- fallthrough
- case dt.m.Type == ipv4.ICMPTypeExtendedEchoRequest && rm.Type == ipv4.ICMPTypeExtendedEchoReply:
- fallthrough
- case dt.m.Type == ipv6.ICMPTypeExtendedEchoRequest && rm.Type == ipv6.ICMPTypeExtendedEchoReply:
- return nil
- default:
- return fmt.Errorf("got %+v from %v; want echo reply or extended echo reply", rm, peer)
- }
- }
- func googleAddr(c *icmp.PacketConn, protocol int) (net.Addr, error) {
- host := "ipv4.google.com"
- if protocol == iana.ProtocolIPv6ICMP {
- host = "ipv6.google.com"
- }
- ips, err := net.LookupIP(host)
- if err != nil {
- return nil, err
- }
- netaddr := func(ip net.IP) (net.Addr, error) {
- switch c.LocalAddr().(type) {
- case *net.UDPAddr:
- return &net.UDPAddr{IP: ip}, nil
- case *net.IPAddr:
- return &net.IPAddr{IP: ip}, nil
- default:
- return nil, errors.New("neither UDPAddr nor IPAddr")
- }
- }
- if len(ips) > 0 {
- return netaddr(ips[0])
- }
- return nil, errors.New("no A or AAAA record")
- }
- func TestConcurrentNonPrivilegedListenPacket(t *testing.T) {
- if testing.Short() {
- t.Skip("avoid external network")
- }
- if m, ok := supportsNonPrivilegedICMP(); !ok {
- t.Skip(m)
- }
- network, address := "udp4", "127.0.0.1"
- if !nettest.SupportsIPv4() {
- network, address = "udp6", "::1"
- }
- const N = 1000
- var wg sync.WaitGroup
- wg.Add(N)
- for i := 0; i < N; i++ {
- go func() {
- defer wg.Done()
- c, err := icmp.ListenPacket(network, address)
- if err != nil {
- t.Error(err)
- return
- }
- c.Close()
- }()
- }
- wg.Wait()
- }
- var (
- nonPrivOnce sync.Once
- nonPrivMsg string
- nonPrivICMP bool
- )
- func supportsNonPrivilegedICMP() (string, bool) {
- nonPrivOnce.Do(func() {
- switch runtime.GOOS {
- case "darwin":
- nonPrivICMP = true
- case "linux":
- for _, t := range []struct{ network, address string }{
- {"udp4", "127.0.0.1"},
- {"udp6", "::1"},
- } {
- c, err := icmp.ListenPacket(t.network, t.address)
- if err != nil {
- nonPrivMsg = "you may need to adjust the net.ipv4.ping_group_range kernel state"
- return
- }
- c.Close()
- }
- nonPrivICMP = true
- default:
- nonPrivMsg = "not supported on " + runtime.GOOS
- }
- })
- return nonPrivMsg, nonPrivICMP
- }
|