Commit b222af32 authored by Adphi's avatar Adphi
Browse files

moved to digineo/go-ping

parent 8babb070
# Pinger
# Ping
Pinger incapsulate a [go-ping Pinger](https://github.com/sparrc/go-ping) to provide multiple address support.
Ping is based on [github.com/digineo/go-ping](https://github.com/digineo/go-ping),
more precisely on [github.com/digineo/go-ping/cmd/multiping](https://github.com/digineo/go-ping/tree/master/cmd/multiping)
```go
package main
import (
"context"
"time"
Example usage :
"github.com/sirupsen/logrus"
"gitlab.bertha.cloud/partitio/isi/ping"
)
```go
func main() {
// Initialize context
ctx, cancel := context.WithCancel(context.Background())
// Cancel context after 10 seconds
go func() {
time.Sleep(10 * time.Second)
time.Sleep(5 * time.Second)
cancel()
}()
// Create a Pinger
p, err := NewPinger(ctx, "127.0.0.1", "192.168.255.22")
p, err := ping.NewPinger(ctx, "127.0.0.1", "255.0.0.1", "192.168.12.1")
if err != nil {
panic(err)
logrus.Fatal(err)
}
// Start the pinger in background
defer p.Close()
go p.Run()
// Add a new address while running after 3 seconds
go func() {
time.Sleep(3 * time.Second)
if err := p.AddAddress("192.168.52.112"); err != nil {
panic(err)
}
}()
// Remove an address while running after 5 seconds
go func() {
time.Sleep(5 * time.Second)
if err := p.RemoveAddress("127.0.0.1"); err != nil {
panic(err)
}
}()
// Create a ticker to get the status every second
t := time.NewTicker(time.Second)
for {
select {
case <-t.C:
// Get the status
fmt.Println(p.Status())
sts := p.Statistics()
for _, s := range sts {
logrus.WithFields(logrus.Fields{
"host": s.Addr,
"address": s.IPAddr,
"sent": s.PacketsSent,
"lost": s.PacketLoss,
"received": s.PacketsRecv,
"min": s.MinRtt,
"max": s.MaxRtt,
"mean": s.AvgRtt,
"rtts": s.Rtts,
}).Info()
}
case <-ctx.Done():
fmt.Println(ctx.Err())
return
}
}
}
```
package ping
import (
"math"
"net"
"sync"
"time"
"github.com/digineo/go-ping"
"github.com/sirupsen/logrus"
)
type history struct {
received int
lost int
results []time.Duration // ring, start index = .received%len
mtx sync.RWMutex
}
type destination struct {
host string
remote *net.IPAddr
display string
*history
}
func (u *destination) ping(pinger *ping.Pinger, timeout time.Duration) {
rtt, err := pinger.Ping(u.remote, timeout)
if err != nil {
logrus.Debugf("%s: %v", u.host, err)
}
u.addResult(rtt, err)
}
func (s *history) addResult(rtt time.Duration, err error) {
s.mtx.Lock()
if err == nil {
s.results[s.received%len(s.results)] = rtt
s.received++
} else {
s.lost++
}
s.mtx.Unlock()
}
func (s *history) compute() (st Statistics) {
s.mtx.RLock()
defer s.mtx.RUnlock()
st.PacketsSent = s.received + s.lost
st.PacketsRecv = s.received
if s.received == 0 {
if s.lost > 0 {
st.PacketLoss = 100
st.Rtts = make([]time.Duration, 10)
copy(st.Rtts, s.results)
}
return
}
collection := s.results[:]
st.PacketsSent = s.received + s.lost
size := len(s.results)
// we don't yet have filled the buffer
if s.received <= size {
collection = s.results[:s.received]
size = s.received
}
st.PacketLoss = float64(s.lost) / float64(s.received+s.lost) * 100
st.MinRtt, st.MaxRtt = collection[0], collection[0]
total := time.Duration(0)
for _, rtt := range collection {
if rtt < st.MinRtt {
st.MinRtt = rtt
}
if rtt > st.MaxRtt {
st.MaxRtt = rtt
}
total += rtt
}
st.AvgRtt = time.Duration(float64(total) / float64(size))
stddevNum := float64(0)
for _, rtt := range collection {
stddevNum += math.Pow(float64(rtt-st.AvgRtt), 2)
}
st.StdDevRtt = time.Duration(math.Sqrt(stddevNum / float64(size)))
st.Rtts = make([]time.Duration, 10)
copy(st.Rtts, s.results)
return
}
package ping
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestComputeStats(t *testing.T) {
assert := assert.New(t)
const (
z = time.Duration(0)
ms = time.Millisecond
µs = time.Microsecond
ns = time.Nanosecond
)
testcases := []struct {
title string
results []time.Duration
received int
lost int
best time.Duration
worst time.Duration
mean time.Duration
stddev time.Duration
loss float64
}{
{
title: "simplest case",
results: []time.Duration{},
received: 0,
best: z, worst: z, mean: z, stddev: z,
},
{
title: "another simple case",
results: []time.Duration{ms},
received: 1,
best: ms, worst: ms, mean: ms, stddev: z,
},
{
title: "same as before, but sent>len(res)",
results: []time.Duration{ms},
received: 3,
best: ms, worst: ms, mean: ms, stddev: z,
},
{
title: "same as before, but sent<len(res)",
results: []time.Duration{ms, ms, 5 * ms},
received: 2,
best: ms, worst: ms, mean: ms, stddev: z,
},
{
title: "different numbers, manually calculated",
results: []time.Duration{ms, 2 * ms},
received: 2,
best: ms,
worst: 2 * ms,
mean: 1500 * µs,
stddev: 500 * µs,
},
{
title: "wilder numbers",
results: []time.Duration{6 * ms, 2 * ms, 14 * ms, 11 * ms},
received: 6,
lost: 2,
best: 2 * ms,
worst: 14 * ms,
mean: 8250 * µs, // (6000+2000+14000+11000)/4
stddev: 4602988, // 4602988.15988
loss: 25, // sent = 6+2
},
{
title: "verifying captured data",
received: 50,
lost: 7,
loss: 12.28, // 7 / 57
best: 451327200,
worst: 492082650,
mean: 487287379,
stddev: 9356133,
results: []time.Duration{
478427841, 486727913, 489902185, 490369676, 489957386,
490784152, 491390728, 491012043, 491313203, 489869560,
488634310, 451590351, 480933928, 451431418, 491046095,
492017348, 488906398, 490187284, 490733777, 490418928,
490627269, 490710944, 491339118, 491300740, 490320794,
489706066, 487735713, 488153523, 490988560, 490293234,
492082650, 490784586, 488731408, 488008147, 487630508,
490190288, 490712289, 489931645, 490608008, 490625639,
491721463, 451327200, 491615584, 490238328, 489234608,
488510694, 488807517, 489176334, 488981822, 488619758,
},
},
}
for i, tc := range testcases {
h := history{received: tc.received, results: tc.results, lost: tc.lost}
subject := h.compute()
assert.Equal(tc.best, subject.MinRtt, "test case #%d (%s): best", i, tc.title)
assert.Equal(tc.worst, subject.MaxRtt, "test case #%d (%s): worst", i, tc.title)
assert.Equal(tc.mean, subject.AvgRtt, "test case #%d (%s): mean", i, tc.title)
assert.Equal(tc.stddev, subject.StdDevRtt, "test case #%d (%s): stddev", i, tc.title)
assert.Equal(tc.received+tc.lost, subject.PacketsSent, "test case #%d (%s): pktSent", i, tc.title)
assert.InDelta(tc.loss, subject.PacketLoss, 0.01, "test case #%d (%s): pktLoss", i, tc.title)
}
}
// Pinger incapsulate a [go-ping Pinger](https://github.com/sparrc/go-ping) to provide multiple address support.
//
//# Ping
//
// func main() {
// // Initialize context
//Ping is based on [github.com/digineo/go-ping](https://github.com/digineo/go-ping),
//more precisely on [github.com/digineo/go-ping/cmd/multiping](https://github.com/digineo/go-ping/tree/master/cmd/multiping)
//
//```go
//package main
//
//import (
// "context"
// "time"
//
// "github.com/sirupsen/logrus"
//
// "gitlab.bertha.cloud/partitio/isi/ping"
//)
//
//func main() {
// ctx, cancel := context.WithCancel(context.Background())
// // Cancel context after 10 seconds
// go func() {
// time.Sleep(10 * time.Second)
// time.Sleep(5 * time.Second)
// cancel()
// }()
// // Create a Pinger
// p, err := NewPinger(ctx, "127.0.0.1", "192.168.255.22")
// p, err := ping.NewPinger(ctx, "127.0.0.1", "255.0.0.1", "192.168.12.1")
// if err != nil {
// panic(err)
// logrus.Fatal(err)
// }
// // Start the pinger in background
// defer p.Close()
//
// go p.Run()
//
// // Add a new address while running after 3 seconds
// go func() {
// time.Sleep(3 * time.Second)
// if err := p.AddAddress("192.168.52.112"); err != nil {
// panic(err)
// }
// }()
// // Remove an address while running after 5 seconds
// go func() {
// time.Sleep(5 * time.Second)
// if err := p.RemoveAddress("127.0.0.1"); err != nil {
// panic(err)
// }
// }()
// // Create a ticker to get the status every second
// t := time.NewTicker(time.Second)
// for {
// select {
// case <-t.C:
// // Get the status
// fmt.Println(p.Status())
// sts := p.Statistics()
// for _, s := range sts {
// logrus.WithFields(logrus.Fields{
// "host": s.Addr,
// "address": s.IPAddr,
// "sent": s.PacketsSent,
// "lost": s.PacketLoss,
// "received": s.PacketsRecv,
// "min": s.MinRtt,
// "max": s.MaxRtt,
// "mean": s.AvgRtt,
// "rtts": s.Rtts,
// }).Info()
// }
// case <-ctx.Done():
// fmt.Println(ctx.Err())
// return
// }
// }
//}
//```
package ping
# go-ping
[![GoDoc](https://godoc.org/github.com/sparrc/go-ping?status.svg)](https://godoc.org/github.com/sparrc/go-ping)
[![Circle CI](https://circleci.com/gh/sparrc/go-ping.svg?style=svg)](https://circleci.com/gh/sparrc/go-ping)
ICMP Ping library for Go, inspired by
[go-fastping](https://github.com/tatsushid/go-fastping)
Here is a very simple example that sends & receives 3 packets:
```go
pinger, err := ping.NewPinger("www.google.com")
if err != nil {
panic(err)
}
pinger.Count = 3
pinger.Run() // blocks until finished
stats := pinger.Statistics() // get send/receive/rtt stats
```
Here is an example that emulates the unix ping command:
```go
pinger, err := ping.NewPinger("www.google.com")
if err != nil {
panic(err)
}
// listen for ctrl-C signal
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())
pinger.Run()
```
It sends ICMP packet(s) and waits for a response. If it receives a response,
it calls the "receive" callback. When it's finished, it calls the "finish"
callback.
For a full ping example, see
[cmd/ping/ping.go](https://github.com/sparrc/go-ping/blob/master/cmd/ping/ping.go)
## Installation:
```
go get github.com/sparrc/go-ping
```
To install the native Go ping executable:
```bash
go get github.com/sparrc/go-ping/...
$GOPATH/bin/ping
```
## Note on Linux Support:
This library attempts to send an
"unprivileged" ping via UDP. On linux, this must be enabled by setting
```
sudo sysctl -w net.ipv4.ping_group_range="0 2147483647"
```
If you do not wish to do this, you can set `pinger.SetPrivileged(true)` and
use setcap to allow your binary using go-ping to bind to raw sockets
(or just run as super-user):
```
setcap cap_net_raw=+ep /bin/go-ping
```
See [this blog](https://sturmflut.github.io/linux/ubuntu/2015/01/17/unprivileged-icmp-sockets-on-linux/)
and [the Go icmp library](https://godoc.org/golang.org/x/net/icmp) for more details.
## Note on Windows Support:
You must use `pinger.SetPrivileged(true)`, otherwise you will receive an error:
```
Error listening for ICMP packets: socket: The requested protocol has not been configured into the system, or no implementation for it exists.
```
This should without admin privileges. Tested on Windows 10.
// Package ping is an ICMP ping library seeking to emulate the unix "ping"
// command.
//
// Here is a very simple example that sends & receives 3 packets:
//
// pinger, err := ping.NewPinger("www.google.com")
// if err != nil {
// panic(err)
// }
//
// pinger.Count = 3
// pinger.Run() // blocks until finished
// stats := pinger.Statistics() // get send/receive/rtt stats
//
// Here is an example that emulates the unix ping command:
//
// pinger, err := ping.NewPinger("www.google.com")
// if err != nil {
// fmt.Printf("ERROR: %s\n", err.Error())
// return
// }
//
// 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())
// pinger.Run()
//
// It sends ICMP packet(s) and waits for a response. If it receives a response,
// it calls the "receive" callback. When it's finished, it calls the "finish"
// callback.
//
// For a full ping example, see "cmd/ping/ping.go".
//
package ping
import (
"encoding/json"
"fmt"
"math"
"math/rand"
"net"
"sync"
"sync/atomic"
"syscall"
"time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
const (
timeSliceLength = 8
protocolICMP = 1
protocolIPv6ICMP = 58
rttsMaxSize = 10
)
var (
ipv4Proto = map[string]string{"ip": "ip4:icmp", "udp": "udp4"}
ipv6Proto = map[string]string{"ip": "ip6:ipv6-icmp", "udp": "udp6"}
)
// NewPinger returns a new Pinger struct pointer
func NewPinger(addr string) (*Pinger, error) {
ipaddr, err := net.ResolveIPAddr("ip", addr)
if err != nil {
return nil, err
}
var ipv4 bool
if isIPv4(ipaddr.IP) {
ipv4 = true
} else if isIPv6(ipaddr.IP) {
ipv4 = false
}
r := rand.New(rand.NewSource(time.Now().UnixNano()))
c := atomic.Value{}
c.Store(-1)
ps := atomic.Value{}
ps.Store(0)
pr := atomic.Value{}
pr.Store(0)
return &Pinger{
ipaddr: ipaddr,
addr: addr,
Interval: time.Second,
Timeout: time.Second * 100000,
Count: c,
PacketsSent: ps,
PacketsRecv: pr,
id: r.Intn(math.MaxInt16),
network: "udp",
ipv4: ipv4,
Size: timeSliceLength,
Tracker: r.Int63n(math.MaxInt64),
sent: make(chan bool),
done: make(chan bool),
}, nil
}
// Pinger represents ICMP 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 atomic.Value
// Debug runs in debug mode
Debug bool
// Number of packets sent
PacketsSent atomic.Value
// Number of packets received
PacketsRecv atomic.Value
// rtts is all of the Rtts
rtts []time.Duration
rttsLock sync.Mutex
// OnRecv is called when Pinger receives and processes a packet
OnRecv func(Packet)
// OnFinish is called when Pinger exits