// 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" "fmt" "net" "os" "runtime" "sync" "testing" "time" "golang.org/x/net/icmp" "golang.org/x/net/internal/iana" "golang.org/x/net/internal/nettest" "golang.org/x/net/ipv4" "golang.org/x/net/ipv6" ) func googleAddr(c *icmp.PacketConn, protocol int) (net.Addr, error) { const host = "www.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") } } for _, ip := range ips { switch protocol { case iana.ProtocolICMP: if ip.To4() != nil { return netaddr(ip) } case iana.ProtocolIPv6ICMP: if ip.To16() != nil && ip.To4() == nil { return netaddr(ip) } } } return nil, errors.New("no A or AAAA record") } type pingTest struct { network, address string protocol int mtype icmp.Type } var nonPrivilegedPingTests = []pingTest{ {"udp4", "0.0.0.0", iana.ProtocolICMP, ipv4.ICMPTypeEcho}, {"udp6", "::", iana.ProtocolIPv6ICMP, ipv6.ICMPTypeEchoRequest}, } func TestNonPrivilegedPing(t *testing.T) { if testing.Short() { t.Skip("avoid external network") } switch runtime.GOOS { case "darwin": case "linux": t.Log("you may need to adjust the net.ipv4.ping_group_range kernel state") default: t.Skipf("not supported on %s", runtime.GOOS) } for i, tt := range nonPrivilegedPingTests { if err := doPing(tt, i); err != nil { t.Error(err) } } } var privilegedPingTests = []pingTest{ {"ip4:icmp", "0.0.0.0", iana.ProtocolICMP, ipv4.ICMPTypeEcho}, {"ip6:ipv6-icmp", "::", iana.ProtocolIPv6ICMP, ipv6.ICMPTypeEchoRequest}, } func TestPrivilegedPing(t *testing.T) { if testing.Short() { t.Skip("avoid external network") } if m, ok := nettest.SupportsRawIPSocket(); !ok { t.Skip(m) } for i, tt := range privilegedPingTests { if err := doPing(tt, i); err != nil { t.Error(err) } } } func doPing(tt pingTest, seq int) error { c, err := icmp.ListenPacket(tt.network, tt.address) if err != nil { return err } defer c.Close() dst, err := googleAddr(c, tt.protocol) if err != nil { return err } if tt.network != "udp6" && tt.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) if err := c.IPv6PacketConn().SetICMPFilter(&f); err != nil { return err } } wm := icmp.Message{ Type: tt.mtype, Code: 0, Body: &icmp.Echo{ ID: os.Getpid() & 0xffff, Seq: 1 << uint(seq), Data: []byte("HELLO-R-U-THERE"), }, } wb, err := wm.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(tt.protocol, rb[:n]) if err != nil { return err } switch rm.Type { case ipv4.ICMPTypeEchoReply, ipv6.ICMPTypeEchoReply: return nil default: return fmt.Errorf("got %+v from %v; want echo reply", rm, peer) } } func TestConcurrentNonPrivilegedListenPacket(t *testing.T) { if testing.Short() { t.Skip("avoid external network") } switch runtime.GOOS { case "darwin": case "linux": t.Log("you may need to adjust the net.ipv4.ping_group_range kernel state") default: t.Skipf("not supported on %s", runtime.GOOS) } 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() }