Developing lightweight computation at the DSG edge
Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
LightKone
go-ping
Commits
77403bd6
Commit
77403bd6
authored
Feb 01, 2016
by
Cameron Sparr
Browse files
First commit of go-ping library
parent
5b436d02
Changes
4
Hide whitespace changes
Inline
Side-by-side
README.md
View file @
77403bd6
# go-ping
ICMP Ping library for Go
[

](https://godoc.org/github.com/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
{
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".
## Note on Linux Support:
This library attempts to send an
"unprivileged" ping via UDP. On linux, this must be enabled by setting
```
sysctl net.ipv4.ping_group_range=0
```
If you do not wish to do this, you can set
`pinger.SetPrivileged(true)`
and
run as super-user.
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.
cmd/ping/ping.go
0 → 100644
View file @
77403bd6
package
main
import
(
"flag"
"fmt"
"time"
"github.com/sparrc/go-ping"
)
var
usage
=
`
Usage:
ping [-c count] [-i interval] [-t timeout] host
Examples:
# ping google continuously
ping www.google.com
# ping google 5 times
ping -c 5 www.google.com
# ping google 5 times at 500ms intervals
ping -c 5 -i 500ms www.google.com
# ping google for 10 seconds
ping -t 10s www.google.com
`
func
main
()
{
timeout
:=
flag
.
Duration
(
"t"
,
time
.
Second
*
100000
,
""
)
interval
:=
flag
.
Duration
(
"i"
,
time
.
Second
,
""
)
count
:=
flag
.
Int
(
"c"
,
-
1
,
""
)
privileged
:=
flag
.
Bool
(
"privileged"
,
false
,
""
)
flag
.
Usage
=
func
()
{
fmt
.
Printf
(
usage
)
}
flag
.
Parse
()
if
flag
.
NArg
()
==
0
{
flag
.
Usage
()
return
}
host
:=
flag
.
Arg
(
0
)
pinger
,
err
:=
ping
.
NewPinger
(
host
)
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
)
}
pinger
.
Count
=
*
count
pinger
.
Interval
=
*
interval
pinger
.
Timeout
=
*
timeout
pinger
.
SetPrivileged
(
*
privileged
)
fmt
.
Printf
(
"PING %s (%s):
\n
"
,
pinger
.
Addr
(),
pinger
.
IPAddr
())
pinger
.
Run
()
}
ping
0 → 100755
View file @
77403bd6
File added
ping.go
0 → 100644
View file @
77403bd6
// 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
(
"fmt"
"math"
"math/rand"
"net"
"os"
"os/signal"
"sync"
"syscall"
"time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
const
(
timeSliceLength
=
8
protocolICMP
=
1
protocolIPv6ICMP
=
58
)
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
}
return
&
Pinger
{
ipaddr
:
ipaddr
,
addr
:
addr
,
Interval
:
time
.
Second
,
Timeout
:
time
.
Second
*
100000
,
Count
:
-
1
,
network
:
"udp"
,
ipv4
:
ipv4
,
size
:
timeSliceLength
,
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
int
// Debug runs in debug mode
Debug
bool
// Number of packets sent
PacketsSent
int
// Number of packets received
PacketsRecv
int
// rtts is all of the Rtts
rtts
[]
time
.
Duration
// OnRecv is called when Pinger receives and processes a packet
OnRecv
func
(
*
Packet
)
// OnFinish is called when Pinger exits
OnFinish
func
(
*
Statistics
)
// stop chan bool
done
chan
bool
ipaddr
*
net
.
IPAddr
addr
string
ipv4
bool
source
string
size
int
sequence
int
network
string
}
type
packet
struct
{
bytes
[]
byte
nbytes
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
// NBytes is the number of bytes in the message.
Nbytes
int
// Seq is the ICMP sequence number.
Seq
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
.
ipaddr
=
ipaddr
p
.
addr
=
ipaddr
.
String
()
}
// IPAddr returns the ip address of the target host.
func
(
p
*
Pinger
)
IPAddr
()
*
net
.
IPAddr
{
return
p
.
ipaddr
}
// 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
{
ipaddr
,
err
:=
net
.
ResolveIPAddr
(
"ip4:icmp"
,
addr
)
if
err
!=
nil
{
return
err
}
p
.
addr
=
addr
p
.
ipaddr
=
ipaddr
return
nil
}
// Addr returns the string ip address of the target host.
func
(
p
*
Pinger
)
Addr
()
string
{
return
p
.
addr
}
// 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
.
network
=
"ip"
}
else
{
p
.
network
=
"udp"
}
}
// Privileged returns whether pinger is running in privileged mode.
func
(
p
*
Pinger
)
Privileged
()
bool
{
return
p
.
network
==
"ip"
}
// 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
()
{
p
.
run
()
}
func
(
p
*
Pinger
)
run
()
{
var
conn
*
icmp
.
PacketConn
if
p
.
ipv4
{
if
conn
=
p
.
listen
(
ipv4Proto
[
p
.
network
],
p
.
source
);
conn
==
nil
{
return
}
}
else
{
if
conn
=
p
.
listen
(
ipv6Proto
[
p
.
network
],
p
.
source
);
conn
==
nil
{
return
}
}
defer
conn
.
Close
()
defer
p
.
finish
()
var
wg
sync
.
WaitGroup
recv
:=
make
(
chan
*
packet
,
5
)
wg
.
Add
(
1
)
go
p
.
recvICMP
(
conn
,
recv
,
&
wg
)
err
:=
p
.
sendICMP
(
conn
)
if
err
!=
nil
{
fmt
.
Println
(
err
.
Error
())
}
timeout
:=
time
.
NewTicker
(
p
.
Timeout
)
interval
:=
time
.
NewTicker
(
p
.
Interval
)
c
:=
make
(
chan
os
.
Signal
,
1
)
signal
.
Notify
(
c
,
os
.
Interrupt
)
signal
.
Notify
(
c
,
syscall
.
SIGTERM
)
for
{
select
{
case
<-
c
:
close
(
p
.
done
)
case
<-
p
.
done
:
wg
.
Wait
()
return
case
<-
timeout
.
C
:
close
(
p
.
done
)
wg
.
Wait
()
return
case
<-
interval
.
C
:
err
=
p
.
sendICMP
(
conn
)
if
err
!=
nil
{
fmt
.
Println
(
"FATAL: "
,
err
.
Error
())
}
case
r
:=
<-
recv
:
err
:=
p
.
processPacket
(
r
)
if
err
!=
nil
{
fmt
.
Println
(
"FATAL: "
,
err
.
Error
())
}
default
:
if
p
.
Count
>
0
&&
p
.
PacketsRecv
>=
p
.
Count
{
close
(
p
.
done
)
wg
.
Wait
()
return
}
}
}
}
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
,
)
{
defer
wg
.
Done
()
for
{
select
{
case
<-
p
.
done
:
return
default
:
bytes
:=
make
([]
byte
,
512
)
conn
.
SetReadDeadline
(
time
.
Now
()
.
Add
(
time
.
Millisecond
*
100
))
n
,
_
,
err
:=
conn
.
ReadFrom
(
bytes
)
if
err
!=
nil
{
if
neterr
,
ok
:=
err
.
(
*
net
.
OpError
);
ok
{
if
neterr
.
Timeout
()
{
// Read timeout
continue
}
else
{
close
(
p
.
done
)
return
}
}
}
recv
<-
&
packet
{
bytes
:
bytes
,
nbytes
:
n
}
}
}
}
func
(
p
*
Pinger
)
processPacket
(
recv
*
packet
)
error
{
var
bytes
[]
byte
var
proto
int
if
p
.
ipv4
{
if
p
.
network
==
"ip"
{
bytes
=
ipv4Payload
(
recv
.
bytes
)
}
else
{
bytes
=
recv
.
bytes
}
proto
=
protocolICMP
}
else
{
bytes
=
recv
.
bytes
proto
=
protocolIPv6ICMP
}
var
m
*
icmp
.
Message
var
err
error
if
m
,
err
=
icmp
.
ParseMessage
(
proto
,
bytes
[
:
recv
.
nbytes
]);
err
!=
nil
{
return
fmt
.
Errorf
(
"Error parsing icmp message"
)
}
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
,
}
switch
pkt
:=
m
.
Body
.
(
type
)
{
case
*
icmp
.
Echo
:
outPkt
.
Rtt
=
time
.
Since
(
bytesToTime
(
pkt
.
Data
[
:
timeSliceLength
]))
outPkt
.
Seq
=
pkt
.
Seq
p
.
PacketsRecv
+=
1
default
:
// Very bad, not sure how this can happen
return
fmt
.
Errorf
(
"Error, invalid ICMP echo reply. Body type: %T, %s"
,
pkt
,
pkt
)
}
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
.
network
==
"udp"
{
dst
=
&
net
.
UDPAddr
{
IP
:
p
.
ipaddr
.
IP
,
Zone
:
p
.
ipaddr
.
Zone
}
}
t
:=
timeToBytes
(
time
.
Now
())
if
p
.
size
-
timeSliceLength
!=
0
{
t
=
append
(
t
,
byteSliceOfSize
(
p
.
size
-
timeSliceLength
)
...
)
}
bytes
,
err
:=
(
&
icmp
.
Message
{
Type
:
typ
,
Code
:
0
,
Body
:
&
icmp
.
Echo
{
ID
:
rand
.
Intn
(
65535
),
Seq
:
p
.
sequence
,
Data
:
t
,
},
})
.
Marshal
(
nil
)
if
err
!=
nil
{
return
err
}
for
{
if
_
,
err
:=
conn
.
WriteTo
(
bytes
,
dst
);
err
!=
nil
{
if
neterr
,
ok
:=
err
.
(
*
net
.
OpError
);
ok
{
if
neterr
.
Err
==
syscall
.
ENOBUFS
{
continue
}
}
}
p
.
PacketsSent
+=
1
p
.
sequence
+=
1
break
}
return
nil
}
func
(
p
*
Pinger
)
listen
(
netProto
string
,
source
string
)
*
icmp
.
PacketConn
{
conn
,
err
:=
icmp
.
ListenPacket
(
netProto
,
source
)
if
err
!=
nil
{
fmt
.
Printf
(
"Error listening for ICMP packets: %s
\n
"
,
err
.
Error
())
close
(
p
.
done
)
return
nil
}
return
conn
}
func
byteSliceOfSize
(
n
int
)
[]
byte
{
b
:=
make
([]
byte
,
n
)
for
i
:=
0
;
i
<
len
(
b
);
i
++
{