Work on #61: Add support for ICMP

+ Update dependencies
This commit is contained in:
TwinProduction
2020-12-25 00:07:18 -05:00
parent c86173d46f
commit 83a5813daf
1004 changed files with 182274 additions and 64323 deletions

16
vendor/github.com/go-ping/ping/.editorconfig generated vendored Normal file
View File

@ -0,0 +1,16 @@
# https://editorconfig.org
root = true
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
indent_style = space
[Makefile]
indent_style = tab
[*.go]
indent_style = tab

2
vendor/github.com/go-ping/ping/.gitignore generated vendored Normal file
View File

@ -0,0 +1,2 @@
/ping
/dist

6
vendor/github.com/go-ping/ping/.golangci.yml generated vendored Normal file
View File

@ -0,0 +1,6 @@
---
issues:
exclude-rules:
- path: _test.go
linters:
- errcheck

46
vendor/github.com/go-ping/ping/.goreleaser.yml generated vendored Normal file
View File

@ -0,0 +1,46 @@
project_name: ping
before:
hooks:
- go mod download
builds:
- binary: ping
dir: cmd/ping
goarch:
- amd64
- arm
- arm64
goarm:
- 6
- 7
goos:
- darwin
- freebsd
- linux
- windows
archives:
- files:
- LICENSE
- README.md
format_overrides:
- goos: windows
format: zip
wrap_in_directory: true
# TODO: Decide if we want packages (name conflcits with /bin/ping?)
# nfpms:
# homepage: https://github.com/go-ping/ping
# maintainer: 'Go Ping Maintainers <go-ping@example.com>'
# description: Ping written in Go.
# license: MIT
# formats:
# - deb
# - rpm
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-{{ .ShortCommit }}"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'

21
vendor/github.com/go-ping/ping/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Cameron Sparr and contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

32
vendor/github.com/go-ping/ping/Makefile generated vendored Normal file
View File

@ -0,0 +1,32 @@
GO ?= go
GOFMT ?= $(GO)fmt
GOOPTS ?=
GO111MODULE :=
pkgs = ./...
all: style vet build test
.PHONY: build
build:
@echo ">> building ping"
GO111MODULE=$(GO111MODULE) $(GO) build $(GOOPTS) ./cmd/ping
.PHONY: style
style:
@echo ">> checking code style"
@fmtRes=$$($(GOFMT) -d $$(find . -path ./vendor -prune -o -name '*.go' -print)); \
if [ -n "$${fmtRes}" ]; then \
echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \
echo "Please ensure you are using $$($(GO) version) for formatting code."; \
exit 1; \
fi
.PHONY: test
test:
@echo ">> running all tests"
GO111MODULE=$(GO111MODULE) $(GO) test -race -cover $(GOOPTS) $(pkgs)
.PHONY: vet
vet:
@echo ">> vetting code"
GO111MODULE=$(GO111MODULE) $(GO) vet $(GOOPTS) $(pkgs)

130
vendor/github.com/go-ping/ping/README.md generated vendored Normal file
View File

@ -0,0 +1,130 @@
# go-ping
[![PkgGoDev](https://pkg.go.dev/badge/github.com/go-ping/ping)](https://pkg.go.dev/github.com/go-ping/ping)
[![Circle CI](https://circleci.com/gh/go-ping/ping.svg?style=svg)](https://circleci.com/gh/go-ping/ping)
A simple but powerful ICMP echo (ping) library for Go, inspired by
[go-fastping](https://github.com/tatsushid/go-fastping).
Here is a very simple example that sends and receives three packets:
```go
pinger, err := ping.NewPinger("www.google.com")
if err != nil {
panic(err)
}
pinger.Count = 3
err = pinger.Run() // Blocks until finished.
if err != nil {
panic(err)
}
stats := pinger.Statistics() // get send/receive/rtt stats
```
Here is an example that emulates the traditional UNIX ping command:
```go
pinger, err := ping.NewPinger("www.google.com")
if err != nil {
panic(err)
}
// Listen for Ctrl-C.
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
for _ = range c {
pinger.Stop()
}
}()
pinger.OnRecv = func(pkt *ping.Packet) {
fmt.Printf("%d bytes from %s: icmp_seq=%d time=%v\n",
pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt)
}
pinger.OnFinish = func(stats *ping.Statistics) {
fmt.Printf("\n--- %s ping statistics ---\n", stats.Addr)
fmt.Printf("%d packets transmitted, %d packets received, %v%% packet loss\n",
stats.PacketsSent, stats.PacketsRecv, stats.PacketLoss)
fmt.Printf("round-trip min/avg/max/stddev = %v/%v/%v/%v\n",
stats.MinRtt, stats.AvgRtt, stats.MaxRtt, stats.StdDevRtt)
}
fmt.Printf("PING %s (%s):\n", pinger.Addr(), pinger.IPAddr())
err = pinger.Run()
if err != nil {
panic(err)
}
```
It sends ICMP Echo Request packet(s) and waits for an Echo Reply in
response. If it receives a response, it calls the `OnRecv` callback.
When it's finished, it calls the `OnFinish` callback.
For a full ping example, see
[cmd/ping/ping.go](https://github.com/go-ping/ping/blob/master/cmd/ping/ping.go).
## Installation
```
go get -u github.com/go-ping/ping
```
To install the native Go ping executable:
```bash
go get -u github.com/go-ping/ping/...
$GOPATH/bin/ping
```
## Supported Operating Systems
### Linux
This library attempts to send an "unprivileged" ping via UDP. On Linux,
this must be enabled with the following sysctl command:
```
sudo sysctl -w net.ipv4.ping_group_range="0 2147483647"
```
If you do not wish to do this, you can call `pinger.SetPrivileged(true)`
in your code and then use setcap on your binary to allow it to bind to
raw sockets (or just run it as root):
```
setcap cap_net_raw=+ep /path/to/your/compiled/binary
```
See [this blog](https://sturmflut.github.io/linux/ubuntu/2015/01/17/unprivileged-icmp-sockets-on-linux/)
and the Go [x/net/icmp](https://godoc.org/golang.org/x/net/icmp) package
for more details.
### Windows
You must use `pinger.SetPrivileged(true)`, otherwise you will receive
the following error:
```
socket: The requested protocol has not been configured into the system, or no implementation for it exists.
```
Despite the method name, this should work without the need to elevate
privileges and has been tested on Windows 10. Please note that accessing
packet TTL values is not supported due to limitations in the Go
x/net/ipv4 and x/net/ipv6 packages.
### Plan 9 from Bell Labs
There is no support for Plan 9. This is because the entire `x/net/ipv4`
and `x/net/ipv6` packages are not implemented by the Go programming
language.
## Maintainers and Getting Help:
This repo was originally in the personal account of
[sparrc](https://github.com/sparrc), but is now maintained by the
[go-ping organization](https://github.com/go-ping).
For support and help, you usually find us in the #go-ping channel of
Gophers Slack. See https://invite.slack.golangbridge.org/ for an invite
to the Gophers Slack org.

5
vendor/github.com/go-ping/ping/go.mod generated vendored Normal file
View File

@ -0,0 +1,5 @@
module github.com/go-ping/ping
go 1.14
require golang.org/x/net v0.0.0-20200904194848-62affa334b73

10
vendor/github.com/go-ping/ping/go.sum generated vendored Normal file
View File

@ -0,0 +1,10 @@
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

669
vendor/github.com/go-ping/ping/ping.go generated vendored Normal file
View File

@ -0,0 +1,669 @@
// Package ping is a simple but powerful ICMP echo (ping) library.
//
// Here is a very simple example that sends and receives three packets:
//
// pinger, err := ping.NewPinger("www.google.com")
// if err != nil {
// panic(err)
// }
// pinger.Count = 3
// err = pinger.Run() // blocks until finished
// if err != nil {
// panic(err)
// }
// stats := pinger.Statistics() // get send/receive/rtt stats
//
// Here is an example that emulates the traditional UNIX ping command:
//
// pinger, err := ping.NewPinger("www.google.com")
// if err != nil {
// panic(err)
// }
// // Listen for Ctrl-C.
// c := make(chan os.Signal, 1)
// signal.Notify(c, os.Interrupt)
// go func() {
// for _ = range c {
// pinger.Stop()
// }
// }()
// pinger.OnRecv = func(pkt *ping.Packet) {
// fmt.Printf("%d bytes from %s: icmp_seq=%d time=%v\n",
// pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt)
// }
// pinger.OnFinish = func(stats *ping.Statistics) {
// fmt.Printf("\n--- %s ping statistics ---\n", stats.Addr)
// fmt.Printf("%d packets transmitted, %d packets received, %v%% packet loss\n",
// stats.PacketsSent, stats.PacketsRecv, stats.PacketLoss)
// fmt.Printf("round-trip min/avg/max/stddev = %v/%v/%v/%v\n",
// stats.MinRtt, stats.AvgRtt, stats.MaxRtt, stats.StdDevRtt)
// }
// fmt.Printf("PING %s (%s):\n", pinger.Addr(), pinger.IPAddr())
// err = pinger.Run()
// if err != nil {
// panic(err)
// }
//
// It sends ICMP Echo Request packet(s) and waits for an Echo Reply in response.
// If it receives a response, it calls the OnRecv callback. When it's finished,
// it calls the OnFinish callback.
//
// For a full ping example, see "cmd/ping/ping.go".
//
package ping
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math"
"math/rand"
"net"
"runtime"
"sync"
"syscall"
"time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
const (
timeSliceLength = 8
trackerLength = 8
protocolICMP = 1
protocolIPv6ICMP = 58
)
var (
ipv4Proto = map[string]string{"icmp": "ip4:icmp", "udp": "udp4"}
ipv6Proto = map[string]string{"icmp": "ip6:ipv6-icmp", "udp": "udp6"}
)
// New returns a new Pinger struct pointer.
func New(addr string) *Pinger {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
return &Pinger{
Count: -1,
Interval: time.Second,
RecordRtts: true,
Size: timeSliceLength,
Timeout: time.Second * 100000,
Tracker: r.Int63n(math.MaxInt64),
addr: addr,
done: make(chan bool),
id: r.Intn(math.MaxInt16),
ipaddr: nil,
ipv4: false,
network: "ip",
protocol: "udp",
}
}
// NewPinger returns a new Pinger and resolves the address.
func NewPinger(addr string) (*Pinger, error) {
p := New(addr)
return p, p.Resolve()
}
// Pinger represents a packet sender/receiver.
type Pinger struct {
// Interval is the wait time between each packet send. Default is 1s.
Interval time.Duration
// Timeout specifies a timeout before ping exits, regardless of how many
// packets have been received.
Timeout time.Duration
// Count tells pinger to stop after sending (and receiving) Count echo
// packets. If this option is not specified, pinger will operate until
// interrupted.
Count int
// Debug runs in debug mode
Debug bool
// Number of packets sent
PacketsSent int
// Number of packets received
PacketsRecv int
// If true, keep a record of rtts of all received packets.
// Set to false to avoid memory bloat for long running pings.
RecordRtts bool
// rtts is all of the Rtts
rtts []time.Duration
// OnSend is called when Pinger sends a packet
OnSend func(*Packet)
// OnRecv is called when Pinger receives and processes a packet
OnRecv func(*Packet)
// OnFinish is called when Pinger exits
OnFinish func(*Statistics)
// Size of packet being sent
Size int
// Tracker: Used to uniquely identify packet when non-priviledged
Tracker int64
// Source is the source IP address
Source string
// stop chan bool
done chan bool
ipaddr *net.IPAddr
addr string
ipv4 bool
id int
sequence int
// network is one of "ip", "ip4", or "ip6".
network string
// protocol is "icmp" or "udp".
protocol string
}
type packet struct {
bytes []byte
nbytes int
ttl int
}
// Packet represents a received and processed ICMP echo packet.
type Packet struct {
// Rtt is the round-trip time it took to ping.
Rtt time.Duration
// IPAddr is the address of the host being pinged.
IPAddr *net.IPAddr
// Addr is the string address of the host being pinged.
Addr string
// NBytes is the number of bytes in the message.
Nbytes int
// Seq is the ICMP sequence number.
Seq int
// TTL is the Time To Live on the packet.
Ttl int
}
// Statistics represent the stats of a currently running or finished
// pinger operation.
type Statistics struct {
// PacketsRecv is the number of packets received.
PacketsRecv int
// PacketsSent is the number of packets sent.
PacketsSent int
// PacketLoss is the percentage of packets lost.
PacketLoss float64
// IPAddr is the address of the host being pinged.
IPAddr *net.IPAddr
// Addr is the string address of the host being pinged.
Addr string
// Rtts is all of the round-trip times sent via this pinger.
Rtts []time.Duration
// MinRtt is the minimum round-trip time sent via this pinger.
MinRtt time.Duration
// MaxRtt is the maximum round-trip time sent via this pinger.
MaxRtt time.Duration
// AvgRtt is the average round-trip time sent via this pinger.
AvgRtt time.Duration
// StdDevRtt is the standard deviation of the round-trip times sent via
// this pinger.
StdDevRtt time.Duration
}
// SetIPAddr sets the ip address of the target host.
func (p *Pinger) SetIPAddr(ipaddr *net.IPAddr) {
p.ipv4 = isIPv4(ipaddr.IP)
p.ipaddr = ipaddr
p.addr = ipaddr.String()
}
// IPAddr returns the ip address of the target host.
func (p *Pinger) IPAddr() *net.IPAddr {
return p.ipaddr
}
// Resolve does the DNS lookup for the Pinger address and sets IP protocol.
func (p *Pinger) Resolve() error {
if len(p.addr) == 0 {
return errors.New("addr cannot be empty")
}
ipaddr, err := net.ResolveIPAddr(p.network, p.addr)
if err != nil {
return err
}
p.ipv4 = isIPv4(ipaddr.IP)
p.ipaddr = ipaddr
return nil
}
// SetAddr resolves and sets the ip address of the target host, addr can be a
// DNS name like "www.google.com" or IP like "127.0.0.1".
func (p *Pinger) SetAddr(addr string) error {
oldAddr := p.addr
p.addr = addr
err := p.Resolve()
if err != nil {
p.addr = oldAddr
return err
}
return nil
}
// Addr returns the string ip address of the target host.
func (p *Pinger) Addr() string {
return p.addr
}
// SetNetwork allows configuration of DNS resolution.
// * "ip" will automatically select IPv4 or IPv6.
// * "ip4" will select IPv4.
// * "ip6" will select IPv6.
func (p *Pinger) SetNetwork(n string) {
switch n {
case "ip4":
p.network = "ip4"
case "ip6":
p.network = "ip6"
default:
p.network = "ip"
}
}
// SetPrivileged sets the type of ping pinger will send.
// false means pinger will send an "unprivileged" UDP ping.
// true means pinger will send a "privileged" raw ICMP ping.
// NOTE: setting to true requires that it be run with super-user privileges.
func (p *Pinger) SetPrivileged(privileged bool) {
if privileged {
p.protocol = "icmp"
} else {
p.protocol = "udp"
}
}
// Privileged returns whether pinger is running in privileged mode.
func (p *Pinger) Privileged() bool {
return p.protocol == "icmp"
}
// Run runs the pinger. This is a blocking function that will exit when it's
// done. If Count or Interval are not specified, it will run continuously until
// it is interrupted.
func (p *Pinger) Run() error {
var conn *icmp.PacketConn
var err error
if p.ipaddr == nil {
err = p.Resolve()
}
if err != nil {
return err
}
if p.ipv4 {
if conn, err = p.listen(ipv4Proto[p.protocol]); err != nil {
return err
}
if err = conn.IPv4PacketConn().SetControlMessage(ipv4.FlagTTL, true); runtime.GOOS != "windows" && err != nil {
return err
}
} else {
if conn, err = p.listen(ipv6Proto[p.protocol]); err != nil {
return err
}
if err = conn.IPv6PacketConn().SetControlMessage(ipv6.FlagHopLimit, true); runtime.GOOS != "windows" && err != nil {
return err
}
}
defer conn.Close()
defer p.finish()
var wg sync.WaitGroup
recv := make(chan *packet, 5)
defer close(recv)
wg.Add(1)
//nolint:errcheck
go p.recvICMP(conn, recv, &wg)
err = p.sendICMP(conn)
if err != nil {
return err
}
timeout := time.NewTicker(p.Timeout)
defer timeout.Stop()
interval := time.NewTicker(p.Interval)
defer interval.Stop()
for {
select {
case <-p.done:
wg.Wait()
return nil
case <-timeout.C:
close(p.done)
wg.Wait()
return nil
case <-interval.C:
if p.Count > 0 && p.PacketsSent >= p.Count {
continue
}
err = p.sendICMP(conn)
if err != nil {
// FIXME: this logs as FATAL but continues
fmt.Println("FATAL: ", err.Error())
}
case r := <-recv:
err := p.processPacket(r)
if err != nil {
// FIXME: this logs as FATAL but continues
fmt.Println("FATAL: ", err.Error())
}
}
if p.Count > 0 && p.PacketsRecv >= p.Count {
close(p.done)
wg.Wait()
return nil
}
}
}
func (p *Pinger) Stop() {
close(p.done)
}
func (p *Pinger) finish() {
handler := p.OnFinish
if handler != nil {
s := p.Statistics()
handler(s)
}
}
// Statistics returns the statistics of the pinger. This can be run while the
// pinger is running or after it is finished. OnFinish calls this function to
// get it's finished statistics.
func (p *Pinger) Statistics() *Statistics {
loss := float64(p.PacketsSent-p.PacketsRecv) / float64(p.PacketsSent) * 100
var min, max, total time.Duration
if len(p.rtts) > 0 {
min = p.rtts[0]
max = p.rtts[0]
}
for _, rtt := range p.rtts {
if rtt < min {
min = rtt
}
if rtt > max {
max = rtt
}
total += rtt
}
s := Statistics{
PacketsSent: p.PacketsSent,
PacketsRecv: p.PacketsRecv,
PacketLoss: loss,
Rtts: p.rtts,
Addr: p.addr,
IPAddr: p.ipaddr,
MaxRtt: max,
MinRtt: min,
}
if len(p.rtts) > 0 {
s.AvgRtt = total / time.Duration(len(p.rtts))
var sumsquares time.Duration
for _, rtt := range p.rtts {
sumsquares += (rtt - s.AvgRtt) * (rtt - s.AvgRtt)
}
s.StdDevRtt = time.Duration(math.Sqrt(
float64(sumsquares / time.Duration(len(p.rtts)))))
}
return &s
}
func (p *Pinger) recvICMP(
conn *icmp.PacketConn,
recv chan<- *packet,
wg *sync.WaitGroup,
) error {
defer wg.Done()
for {
select {
case <-p.done:
return nil
default:
bytes := make([]byte, 512)
if err := conn.SetReadDeadline(time.Now().Add(time.Millisecond * 100)); err != nil {
return err
}
var n, ttl int
var err error
if p.ipv4 {
var cm *ipv4.ControlMessage
n, cm, _, err = conn.IPv4PacketConn().ReadFrom(bytes)
if cm != nil {
ttl = cm.TTL
}
} else {
var cm *ipv6.ControlMessage
n, cm, _, err = conn.IPv6PacketConn().ReadFrom(bytes)
if cm != nil {
ttl = cm.HopLimit
}
}
if err != nil {
if neterr, ok := err.(*net.OpError); ok {
if neterr.Timeout() {
// Read timeout
continue
} else {
close(p.done)
return err
}
}
}
select {
case <-p.done:
return nil
case recv <- &packet{bytes: bytes, nbytes: n, ttl: ttl}:
}
}
}
}
func (p *Pinger) processPacket(recv *packet) error {
receivedAt := time.Now()
var proto int
if p.ipv4 {
proto = protocolICMP
} else {
proto = protocolIPv6ICMP
}
var m *icmp.Message
var err error
if m, err = icmp.ParseMessage(proto, recv.bytes); err != nil {
return fmt.Errorf("error parsing icmp message: %s", err.Error())
}
if m.Type != ipv4.ICMPTypeEchoReply && m.Type != ipv6.ICMPTypeEchoReply {
// Not an echo reply, ignore it
return nil
}
outPkt := &Packet{
Nbytes: recv.nbytes,
IPAddr: p.ipaddr,
Addr: p.addr,
Ttl: recv.ttl,
}
switch pkt := m.Body.(type) {
case *icmp.Echo:
// If we are priviledged, we can match icmp.ID
if p.protocol == "icmp" {
// Check if reply from same ID
if pkt.ID != p.id {
return nil
}
}
if len(pkt.Data) < timeSliceLength+trackerLength {
return fmt.Errorf("insufficient data received; got: %d %v",
len(pkt.Data), pkt.Data)
}
tracker := bytesToInt(pkt.Data[timeSliceLength:])
timestamp := bytesToTime(pkt.Data[:timeSliceLength])
if tracker != p.Tracker {
return nil
}
outPkt.Rtt = receivedAt.Sub(timestamp)
outPkt.Seq = pkt.Seq
p.PacketsRecv++
default:
// Very bad, not sure how this can happen
return fmt.Errorf("invalid ICMP echo reply; type: '%T', '%v'", pkt, pkt)
}
if p.RecordRtts {
p.rtts = append(p.rtts, outPkt.Rtt)
}
handler := p.OnRecv
if handler != nil {
handler(outPkt)
}
return nil
}
func (p *Pinger) sendICMP(conn *icmp.PacketConn) error {
var typ icmp.Type
if p.ipv4 {
typ = ipv4.ICMPTypeEcho
} else {
typ = ipv6.ICMPTypeEchoRequest
}
var dst net.Addr = p.ipaddr
if p.protocol == "udp" {
dst = &net.UDPAddr{IP: p.ipaddr.IP, Zone: p.ipaddr.Zone}
}
t := append(timeToBytes(time.Now()), intToBytes(p.Tracker)...)
if remainSize := p.Size - timeSliceLength - trackerLength; remainSize > 0 {
t = append(t, bytes.Repeat([]byte{1}, remainSize)...)
}
body := &icmp.Echo{
ID: p.id,
Seq: p.sequence,
Data: t,
}
msg := &icmp.Message{
Type: typ,
Code: 0,
Body: body,
}
msgBytes, err := msg.Marshal(nil)
if err != nil {
return err
}
for {
if _, err := conn.WriteTo(msgBytes, dst); err != nil {
if neterr, ok := err.(*net.OpError); ok {
if neterr.Err == syscall.ENOBUFS {
continue
}
}
}
handler := p.OnSend
if handler != nil {
outPkt := &Packet{
Nbytes: len(msgBytes),
IPAddr: p.ipaddr,
Addr: p.addr,
Seq: p.sequence,
}
handler(outPkt)
}
p.PacketsSent++
p.sequence++
break
}
return nil
}
func (p *Pinger) listen(netProto string) (*icmp.PacketConn, error) {
conn, err := icmp.ListenPacket(netProto, p.Source)
if err != nil {
close(p.done)
return nil, err
}
return conn, nil
}
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)
}
func isIPv4(ip net.IP) bool {
return len(ip.To4()) == net.IPv4len
}
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 bytesToInt(b []byte) int64 {
return int64(binary.BigEndian.Uint64(b))
}
func intToBytes(tracker int64) []byte {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, uint64(tracker))
return b
}