From 54a08af19a80dffa8a7a2ca59852ec24818c57e3 Mon Sep 17 00:00:00 2001 From: Tatsushi Demachi Date: Fri, 6 Sep 2013 00:06:12 +0900 Subject: [PATCH] Initial import. --- cmd/ping/ping.go | 73 ++++++++++ fastping.go | 372 +++++++++++++++++++++++++++++++++++++++++++++++ fastping_test.go | 207 ++++++++++++++++++++++++++ icmp.go | 158 ++++++++++++++++++++ 4 files changed, 810 insertions(+) create mode 100644 cmd/ping/ping.go create mode 100644 fastping.go create mode 100644 fastping_test.go create mode 100644 icmp.go diff --git a/cmd/ping/ping.go b/cmd/ping/ping.go new file mode 100644 index 0000000..56e81e2 --- /dev/null +++ b/cmd/ping/ping.go @@ -0,0 +1,73 @@ +package main + +import ( + "fmt" + "github.com/tatsushid/go-fastping" + "net" + "os" + "os/signal" + "syscall" + "time" +) + +type response struct { + addr *net.IPAddr + rtt time.Duration +} + +func main() { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: %s {hostname}\n", os.Args[0]) + os.Exit(1) + } + p := fastping.NewPinger() + ra, err := net.ResolveIPAddr("ip4:icmp", os.Args[1]) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + results := make(map[string]*response) + results[ra.String()] = nil + p.AddIPAddr(ra) + + onRecv, onIdle := make(chan *response), make(chan bool) + p.AddHandler("receive", func(addr *net.IPAddr, t time.Duration) { + onRecv <- &response{addr: addr, rtt: t} + }) + p.AddHandler("idle", func() { + onIdle <- true + }) + + p.MaxRTT = time.Second + quit := p.RunLoop() + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + signal.Notify(c, syscall.SIGTERM) + +loop: + for { + select { + case <-c: + fmt.Println("get interrupted") + break loop + case res := <-onRecv: + if _, ok := results[res.addr.String()]; ok { + results[res.addr.String()] = res + } + case <-onIdle: + for host, r := range results { + if r == nil { + fmt.Printf("%s : unreachable %v\n", host, time.Now()) + } else { + fmt.Printf("%s : %v %v\n", host, r.rtt, time.Now()) + } + results[host] = nil + } + } + } + wait := make(chan bool) + quit <- wait + <-wait +} diff --git a/fastping.go b/fastping.go new file mode 100644 index 0000000..9b70b98 --- /dev/null +++ b/fastping.go @@ -0,0 +1,372 @@ +// go-fastping is a Go language port of Marc Lehmann's AnyEvent::FastPing Perl +// module to send ICMP ECHO REQUEST packets quickly. Original Perl module is +// available at +// http://search.cpan.org/~mlehmann/AnyEvent-FastPing-2.01/ +// +// It hasn't been fully implemented original functions yet and only for IPv4 +// now. +// +// Here is an example: +// +// p := fastping.NewPinger() +// ra, err := net.ResolveIPAddr("ip4:icmp", os.Args[1]) +// if err != nil { +// fmt.Println(err) +// os.Exit(1) +// } +// p.AddIPAddr(ra) +// p.AddHandler("receive", func(addr *net.IPAddr, rtt time.Duration) { +// fmt.Printf("IP Addr: %s receive, RTT: %v\n", addr.String(), rtt) +// }) +// p.AddHandler("idle", func() { +// fmt.Println("finish") +// }) +// p.Run() +// +// It sends an ICMP packet and wait a response. If it receives a response, +// it calls "receive" callback. After that, MaxRTT time passed, it calls +// "idle" callback. If you need more example, please see "cmd/ping/ping.go". +// +// This library needs to run as a superuser for sending ICMP packets so when +// you run go test, please run as a following +// +// sudo go test +// +package fastping + +import ( + "errors" + "fmt" + "log" + "math/rand" + "net" + "syscall" + "time" +) + +func init() { + log.SetFlags(log.Lmicroseconds) + log.SetPrefix("Debug: ") +} + +func timeToBytes(t time.Time) []byte { + nsec := t.UnixNano() + b := make([]byte, 8) + for i := uint8(0); i < 8; i++ { + b[i] = byte((nsec >> ((7 - i) * 8)) & 0xff) + } + return b +} + +func bytesToTime(b []byte) time.Time { + var nsec int64 + for i := uint8(0); i < 8; i++ { + nsec += int64(b[i]) << ((7 - i) * 8) + } + return time.Unix(nsec/1000000000, nsec%1000000000) +} + +type packet struct { + bytes []byte + addr *net.IPAddr +} + +// Pinger represents ICMP packet sender/receiver +type Pinger struct { + id int + seq int + // key string is IPAddr.String() + addrs map[string]*net.IPAddr + // Number of (nano,milli)seconds of an idle timeout. Once it passed, + // the library calls an idle callback function. It is also used for an + // interval time of RunLoop() method + MaxRTT time.Duration + handlers map[string]interface{} + // If Debug is true, it prints debug messages to stdout. + Debug bool +} + +// It returns a new Pinger +func NewPinger() *Pinger { + rand.Seed(time.Now().UnixNano()) + p := &Pinger{ + id: rand.Intn(0xffff), + seq: rand.Intn(0xffff), + addrs: make(map[string]*net.IPAddr), + MaxRTT: time.Second, + handlers: make(map[string]interface{}), + Debug: false, + } + return p +} + +// Add an IP address to Pinger. ipaddr arg should be a string like "192.0.2.1". +func (p *Pinger) AddIP(ipaddr string) error { + addr := net.ParseIP(ipaddr) + if addr == nil { + return errors.New(fmt.Sprintf("%s is not a valid textual representation of an IP address", ipaddr)) + } + p.addrs[addr.String()] = &net.IPAddr{IP: addr} + return nil +} + +// Add an IP address to Pinger. ip arg should be a net.IPAddr pointer. +func (p *Pinger) AddIPAddr(ip *net.IPAddr) { + p.addrs[ip.String()] = ip +} + +// Add event handler to Pinger. event arg should be "receive" or "idle" string. +// +// "receive" handler should be +// +// func(addr *net.IPAddr, rtt time.Duration) +// +// type function. The handler is called with a response packet's source address +// and its elapsed time when Pinger receives a response packet. +// +// "idle" handler should be +// +// func() +// +// type function. The handler is called when MaxRTT time passed. For more +// detail, please see Run() and RunLoop(). +func (p *Pinger) AddHandler(event string, handler interface{}) error { + switch event { + case "receive": + if hdl, ok := handler.(func(*net.IPAddr, time.Duration)); ok { + p.handlers[event] = hdl + } else { + errors.New(fmt.Sprintf("Receive event handler should be `func(*net.IPAddr, time.Duration)`")) + } + case "idle": + if hdl, ok := handler.(func()); ok { + p.handlers[event] = hdl + } else { + errors.New(fmt.Sprintf("Idle event handler should be `func()`")) + } + } + return errors.New(fmt.Sprintf("No such event: %s", event)) +} + +// Invoke a single send/receive procedure. It sends packets to all hosts which +// have already been added by AddIP() etc. and wait those responses. When it +// receives a response, it calls "receive" handler registered by AddHander(). +// After MaxRTT seconds, it calls "idle" handler and returns to caller. It +// means it blocks until MaxRTT seconds passed. For the purpose of +// sending/receiving packets over and over, use RunLoop(). +func (p *Pinger) Run() { + p.run(true, make(chan chan<- bool)) +} + +// Invode send/receive procedure repeatedly. It sends packets to all hosts which +// have already been added by AddIP() etc. and wait those responses. When it +// receives a response, it calls "receive" handler registered by AddHander(). +// After MaxRTT seconds, it calls "idle" handler, resend packets and wait those +// response. MaxRTT works as an interval time. +// +// This is a non-blocking method so immediately returns with a channel value. +// If you want to stop sending packets, send a channel value of bool type to it +// and wait for graceful shutdown. For example, +// +// quit := p.RunLoop() +// wait := make(chan bool) +// quit <- wait +// <-wait +// +// For more detail, please see "cmd/ping/ping.go". +func (p *Pinger) RunLoop() chan<- chan<- bool { + quit := make(chan chan<- bool) + go p.run(false, quit) + return quit +} + +func (p *Pinger) run(once bool, quit <-chan chan<- bool) { + p.debugln("Run(): Start") + conn, err := net.ListenIP("ip4:icmp", &net.IPAddr{IP: net.IPv4zero}) + if err != nil { + panic(err) + } + defer func() { + if err := conn.Close(); err != nil { + panic(err) + } + }() + + var join chan<- bool + recv, stoprecv, waitjoin := make(chan *packet), make(chan chan<- bool), make(chan bool) + + p.debugln("Run(): call recvICMP4()") + go p.recvICMP4(conn, recv, stoprecv) + + p.debugln("Run(): call sendICMP4()") + queue := p.sendICMP4(conn) + + ticker := time.NewTicker(p.MaxRTT) + +mainloop: + for { + select { + case join = <-quit: + p.debugln("Run(): <-quit") + p.debugln("Run(): stoprecv <- waitjoin") + stoprecv <- waitjoin + break mainloop + case <-ticker.C: + if handler, ok := p.handlers["idle"]; ok && handler != nil { + if hdl, ok := handler.(func()); ok { + hdl() + } + } + if once { + p.debugln("Run(): stoprecv <- waitjoin") + stoprecv <- waitjoin + break mainloop + } + p.debugln("Run(): call sendICMP4()") + queue = p.sendICMP4(conn) + case r := <-recv: + p.debugln("Run(): <-recv") + p.procRecv(r, queue) + } + } + + ticker.Stop() + + p.debugln("Run(): <-waitjoin") + <-waitjoin + if !once { + p.debugln("Run(): join <- true") + join <- true + } + p.debugln("Run(): End") + return +} + +func (p *Pinger) sendICMP4(conn *net.IPConn) map[string]*net.IPAddr { + p.debugln("sendICMP4(): Start") + p.id = rand.Intn(0xffff) + p.seq = rand.Intn(0xffff) + queue := make(map[string]*net.IPAddr) + qlen := 0 + sent := make(chan bool) + for k, v := range p.addrs { + bytes, err := (&icmpMessage{ + Type: icmpv4EchoRequest, Code: 0, + Body: &icmpEcho{ + ID: p.id, Seq: p.seq, + Data: timeToBytes(time.Now()), + }, + }).Marshal() + if err != nil { + panic(err) + } + + queue[k] = v + qlen++ + + p.debugln("sendICMP4(): Invoke goroutine") + go func(ra *net.IPAddr, b []byte) { + for { + if _, _, err := conn.WriteMsgIP(bytes, nil, ra); err != nil { + if neterr, ok := err.(*net.OpError); ok { + if neterr.Err == syscall.ENOBUFS { + continue + } + } + } + break + } + p.debugln("sendICMP4(): WriteMsgIP End") + sent <- true + }(v, bytes) + } + for i := 0; i < qlen; i++ { + p.debugln("sendICMP4(): wait goroutine") + <-sent + p.debugln("sendICMP4(): join goroutine") + } + p.debugln("sendICMP4(): End") + return queue +} + +func (p *Pinger) recvICMP4(conn *net.IPConn, recv chan<- *packet, stoprecv <-chan chan<- bool) { + p.debugln("recvICMP4(): Start") + for { + select { + case join := <-stoprecv: + p.debugln("recvICMP4(): <-stoprecv") + p.debugln("recvICMP4(): join <- true") + join <- true + return + default: + } + + bytes := make([]byte, 512) + conn.SetReadDeadline(time.Now().Add(time.Millisecond * 100)) + p.debugln("recvICMP4(): ReadMsgIP Start") + _, _, _, ra, err := conn.ReadMsgIP(bytes, nil) + p.debugln("recvICMP4(): ReadMsgIP End") + if err != nil { + if neterr, ok := err.(*net.OpError); ok { + if neterr.Timeout() { + p.debugln("recvICMP4(): Read Timeout") + continue + } else { + p.debugln("recvICMP4(): OpError happen", err) + return + } + } + } + p.debugln("recvICMP4(): p.recv <- packet") + recv <- &packet{bytes: bytes, addr: ra} + } +} + +func (p *Pinger) procRecv(recv *packet, queue map[string]*net.IPAddr) { + addr := recv.addr.String() + if _, ok := p.addrs[addr]; !ok { + return + } + + bytes := ipv4Payload(recv.bytes) + var m *icmpMessage + var err error + if m, err = parseICMPMessage(bytes); err != nil { + return + } + + if m.Type != icmpv4EchoReply { + return + } + + var rtt time.Duration + switch pkt := m.Body.(type) { + case *icmpEcho: + if pkt.ID == p.id && pkt.Seq == p.seq { + rtt = time.Since(bytesToTime(pkt.Data)) + } + default: + return + } + + if _, ok := queue[addr]; ok { + delete(queue, addr) + if handler, ok := p.handlers["receive"]; ok { + if hdl, ok := handler.(func(*net.IPAddr, time.Duration)); ok { + hdl(recv.addr, rtt) + } + } + } +} + +func (p *Pinger) debugln(args ...interface{}) { + if p.Debug { + log.Println(args...) + } +} + +func (p *Pinger) debugf(format string, args ...interface{}) { + if p.Debug { + log.Printf(format, args...) + } +} diff --git a/fastping_test.go b/fastping_test.go new file mode 100644 index 0000000..3bd1d0f --- /dev/null +++ b/fastping_test.go @@ -0,0 +1,207 @@ +package fastping + +import ( + "net" + "sync" + "testing" + "time" +) + +type addHostTest struct { + host string + addr *net.IPAddr + expect bool +} + +var addHostTests = []addHostTest{ + {host: "127.0.0.1", addr: &net.IPAddr{IP: net.IPv4(127, 0, 0, 1)}, expect: true}, + {host: "localhost", addr: &net.IPAddr{IP: net.IPv4(127, 0, 0, 1)}, expect: false}, +} + +func TestAddIP(t *testing.T) { + p := NewPinger() + + for _, tt := range addHostTests { + if ok := p.AddIP(tt.host); ok != nil { + if tt.expect != false { + t.Errorf("AddIP failed: got %v, expected %v", ok, tt.expect) + } + } + } + for _, tt := range addHostTests { + if tt.expect { + if !p.addrs[tt.host].IP.Equal(tt.addr.IP) { + t.Errorf("AddIP didn't save IPAddr: %v", tt.host) + } + } + } +} + +func TestRun(t *testing.T) { + p := NewPinger() + + if err := p.AddIP("127.0.0.1"); err != nil { + t.Fatalf("AddIP failed: %v", err) + } + + if err := p.AddIP("127.0.0.100"); err != nil { + t.Fatalf("AddIP failed: %v", err) + } + + found1, found100 := false, false + called, idle := false, false + p.AddHandler("receive", func(ip *net.IPAddr, d time.Duration) { + called = true + if ip.String() == "127.0.0.1" { + found1 = true + } else if ip.String() == "127.0.0.100" { + found100 = true + } + }) + p.AddHandler("idle", func() { + idle = true + }) + p.Run() + if !called { + t.Fatalf("Pinger didn't get any responses") + } + if !idle { + t.Fatalf("Pinger didn't call OnIdle function") + } + if !found1 { + t.Fatalf("Pinger `127.0.0.1` didn't respond") + } + if found100 { + t.Fatalf("Pinger `127.0.0.100` responded") + } +} + +func TestMultiRun(t *testing.T) { + p1 := NewPinger() + p2 := NewPinger() + + if err := p1.AddIP("127.0.0.1"); err != nil { + t.Fatalf("AddIP 1 failed: %v", err) + } + + if err := p2.AddIP("127.0.0.1"); err != nil { + t.Fatalf("AddIP 1 failed: %v", err) + } + + var mu sync.Mutex + res1 := 0 + p1.AddHandler("receive", func(*net.IPAddr, time.Duration) { + mu.Lock() + res1++ + mu.Unlock() + }) + + res2 := 0 + p2.AddHandler("receive", func(*net.IPAddr, time.Duration) { + mu.Lock() + res2++ + mu.Unlock() + }) + p1.MaxRTT, p2.MaxRTT = time.Millisecond*100, time.Millisecond*100 + + p1.Run() + if res1 == 0 { + t.Fatalf("Pinger 1 didn't get any responses") + } + if res2 > 0 { + t.Fatalf("Pinger 2 got response") + } + + res1, res2 = 0, 0 + p2.Run() + if res1 > 0 { + t.Fatalf("Pinger 1 got response") + } + if res2 == 0 { + t.Fatalf("Pinger 2 didn't get any responses") + } + + res1, res2 = 0, 0 + go p1.Run() + go p2.Run() + time.Sleep(time.Millisecond * 200) + mu.Lock() + if res1 != 1 { + t.Fatalf("Pinger 1 didn't get correct response") + } + if res2 != 1 { + t.Fatalf("Pinger 2 didn't get correct response") + } + mu.Unlock() +} + +func TestRunLoop(t *testing.T) { + p := NewPinger() + + if err := p.AddIP("127.0.0.1"); err != nil { + t.Fatalf("AddIP failed: %v", err) + } + p.MaxRTT = time.Millisecond * 100 + + recvCount, idleCount := 0, 0 + p.AddHandler("receive", func(*net.IPAddr, time.Duration) { + recvCount++ + }) + p.AddHandler("idle", func() { + idleCount++ + }) + + quit := p.RunLoop() + time.Sleep(time.Millisecond * 250) + wait := make(chan bool) + quit <- wait + <-wait + + if recvCount < 2 { + t.Fatalf("Pinger recieve count less than 2") + } + if idleCount < 2 { + t.Fatalf("Pinger idle count less than 2") + } +} + +func TestTimeToBytes(t *testing.T) { + // 2009-11-10 23:00:00 +0000 UTC = 1257894000000000000 + expect := []byte{0x11, 0x74, 0xef, 0xed, 0xab, 0x18, 0x60, 0x00} + tm, err := time.Parse(time.RFC3339, "2009-11-10T23:00:00Z") + if err != nil { + t.Errorf("time.Parse failed: %v", err) + } + b := timeToBytes(tm) + for i := 0; i < 8; i++ { + if b[i] != expect[i] { + t.Errorf("timeToBytes failed: got %v, expected: %v", b, expect) + break + } + } +} + +func TestBytesToTime(t *testing.T) { + // 2009-11-10 23:00:00 +0000 UTC = 1257894000000000000 + b := []byte{0x11, 0x74, 0xef, 0xed, 0xab, 0x18, 0x60, 0x00} + expect, err := time.Parse(time.RFC3339, "2009-11-10T23:00:00Z") + if err != nil { + t.Errorf("time.Parse failed: %v", err) + } + tm := bytesToTime(b) + if !tm.Equal(expect) { + t.Errorf("bytesToTime failed: got %v, expected: %v", tm.UTC(), expect.UTC()) + } +} + +func TestTimeToBytesToTime(t *testing.T) { + tm, err := time.Parse(time.RFC3339, "2009-11-10T23:00:00Z") + if err != nil { + t.Errorf("time.Parse failed: %v", err) + } + b := timeToBytes(tm) + tm2 := bytesToTime(b) + if !tm.Equal(tm2) { + t.Errorf("bytesToTime failed: got %v, expected: %v", tm2.UTC(), tm.UTC()) + } +} diff --git a/icmp.go b/icmp.go new file mode 100644 index 0000000..b89d740 --- /dev/null +++ b/icmp.go @@ -0,0 +1,158 @@ +// Almost all this code is taken from go net ipraw_test.go implementation. +// It's under a BSD-style license. + +// Copyright (c) 2012 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Copyright 2009 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 fastping + +import ( + "errors" +) + +func ipv4Payload(b []byte) []byte { + if len(b) < 20 { + return b + } + hdrlen := int(b[0]&0x0f) << 2 + return b[hdrlen:] +} + +const ( + icmpv4EchoRequest = 8 + icmpv4EchoReply = 0 + icmpv6EchoRequest = 128 + icmpv6EchoReply = 129 +) + +// icmpMessage represents an ICMP message. +type icmpMessage struct { + Type int // type + Code int // code + Checksum int // checksum + Body icmpMessageBody // body +} + +// icmpMessageBody represents an ICMP message body. +type icmpMessageBody interface { + Len() int + Marshal() ([]byte, error) +} + +// Marshal returns the binary enconding of the ICMP echo request or +// reply message m. +func (m *icmpMessage) Marshal() ([]byte, error) { + b := []byte{byte(m.Type), byte(m.Code), 0, 0} + if m.Body != nil && m.Body.Len() != 0 { + mb, err := m.Body.Marshal() + if err != nil { + return nil, err + } + b = append(b, mb...) + } + switch m.Type { + case icmpv6EchoRequest, icmpv6EchoReply: + return b, nil + } + csumcv := len(b) - 1 // checksum coverage + s := uint32(0) + for i := 0; i < csumcv; i += 2 { + s += uint32(b[i+1])<<8 | uint32(b[i]) + } + if csumcv&1 == 0 { + s += uint32(b[csumcv]) + } + s = s>>16 + s&0xffff + s = s + s>>16 + // Place checksum back in header; using ^= avoids the + // assumption the checksum bytes are zero. + b[2] ^= byte(^s) + b[3] ^= byte(^s >> 8) + return b, nil +} + +// parseICMPMessage parses b as an ICMP message. +func parseICMPMessage(b []byte) (*icmpMessage, error) { + msglen := len(b) + if msglen < 4 { + return nil, errors.New("message too short") + } + m := &icmpMessage{Type: int(b[0]), Code: int(b[1]), Checksum: int(b[2])<<8 | int(b[3])} + if msglen > 4 { + var err error + switch m.Type { + case icmpv4EchoRequest, icmpv4EchoReply, icmpv6EchoRequest, icmpv6EchoReply: + m.Body, err = parseICMPEcho(b[4:]) + if err != nil { + return nil, err + } + } + } + return m, nil +} + +// imcpEcho represenets an ICMP echo request or reply message body. +type icmpEcho struct { + ID int // identifier + Seq int // sequence number + Data []byte // data +} + +// Len returns message length in bytes +func (p *icmpEcho) Len() int { + if p == nil { + return 0 + } + return 4 + len(p.Data) +} + +// Marshal returns the binary enconding of the ICMP echo request or +// reply message body p. +func (p *icmpEcho) Marshal() ([]byte, error) { + b := make([]byte, 4+len(p.Data)) + b[0], b[1] = byte(p.ID>>8), byte(p.ID) + b[2], b[3] = byte(p.Seq>>8), byte(p.Seq) + copy(b[4:], p.Data) + return b, nil +} + +// parseICMPEcho parses b as an ICMP echo request or reply message +// body. +func parseICMPEcho(b []byte) (*icmpEcho, error) { + bodylen := len(b) + p := &icmpEcho{ID: int(b[0])<<8 | int(b[1]), Seq: int(b[2])<<8 | int(b[3])} + if bodylen > 4 { + p.Data = make([]byte, bodylen-4) + copy(p.Data, b[4:]) + } + return p, nil +}