go-ping / ping Goto Github PK
View Code? Open in Web Editor NEWICMP Ping library for Go
License: MIT License
ICMP Ping library for Go
License: MIT License
Pinger works. All is fine except TTL is zero. The system is windows 10.
package main
import (
"fmt"
"time"
"github.com/sparrc/go-ping"
)
func main() {
host := "192.168.2.1"
pinger, _ := ping.NewPinger( host )
pinger.Count = 1
start := time.Now()
pinger.Run()
end := time.Now()
fmt.Println("elapsed:", end.Sub(start))
fmt.Println(pinger.Statistics())
}
Output:
elapsed: 101.549586ms
&{1 1 0 192.168.2.1 192.168.2.1 [1.427179ms] 1.427179ms 1.427179ms 1.427179ms 0s}
I am wondering if there is a way to make the Run() function execute faster. The actual ping itself only take 1.43 ms, but the entire function takes 101 ms. This seems like a lot of overhead. Also, the call to time.Now() only takes about 980ns. I get similar results across Windows, Linux and MacOS.
How can the be sped up?
The following should be handled more gracefully and not default to write to stdout.
My application that uses this library does not want extra output I can not control as it is trying to output JSON.
https://github.com/sparrc/go-ping/blob/4e5b6552494c8005c60de6c60b50ebaefc69e592/ping.go#L562
I stumbled upon channel lockup, when trying to send received data, while either one of the following occurs in func (p *Pinger) run():
This occurs, because run func is not receiving data from recvICMP any more if above conditions are true and recvICMP is still trying to send received data to run func via recv channel. I Used select with both recv channel and p.done channel.
I attached the diff patch.
Calling the Stop()
method on a pinger with a timeout or count configured can cause a panic, if the Stop()
is called after the Pinger has exceeded either the timeout or ping count:
panic: close of closed channel
goroutine 10 [running]:
github.com/sparrc/go-ping.(*Pinger).Stop(...)
/go/pkg/mod/github.com/sparrc/[email protected]/ping.go:345
There's no way to safely prevent this panic externally without some kind of race condition.
First off, this is an awesome library!
The title pretty much says it all. I'd like to set up an ICMP listener (and logger) on Windows.
Can go-ping be used for this purpose?
I would like to be notified when a ping fails in a specified timeout.
Example I have a long running pinger hitting 8.8.8.8
, whenever a probe does not get a response within 1 second, I would like a callback to fire informing me of the same, so my code can do something with that information.
It appears that the only way to do so currently is call Statistics()
regularly and do some math to figure out if something lossy happened recently or not.
Hi i created the following go function.
When i do not use a timeout, it works. When i set a timeout it says one packet send 0 recieved.
Then it stops.
Perry
// Test internet connection by sending 3 pings to www.google.com.
func testInternet(hostname string) (ok bool) {
pinger, err := ping.NewPinger(hostname)
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.Count = 3
pinger.Timeout = 5 * 100000
pinger.SetPrivileged(true)
pinger.Run()
fmt.Printf("Package Send : %d, Package Recieved %d\n", pinger.PacketsSent, pinger.PacketsRecv)
return (pinger.PacketsRecv > 0)
}
do U think "recv <- &packet{bytes: bytes[:n], nbytes: n, ttl: ttl}" replace of "recv <- &packet{bytes: bytes, nbytes: n, ttl: ttl}" will be better?
json is very slow, comparing normal C /bin/ping
to Go ping
, there's a lot of added latency.
$ /bin/ping -c 10 localhost
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.085 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.071 ms
64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.076 ms
64 bytes from localhost (127.0.0.1): icmp_seq=4 ttl=64 time=0.081 ms
64 bytes from localhost (127.0.0.1): icmp_seq=5 ttl=64 time=0.063 ms
64 bytes from localhost (127.0.0.1): icmp_seq=6 ttl=64 time=0.100 ms
64 bytes from localhost (127.0.0.1): icmp_seq=7 ttl=64 time=0.082 ms
64 bytes from localhost (127.0.0.1): icmp_seq=8 ttl=64 time=0.050 ms
64 bytes from localhost (127.0.0.1): icmp_seq=9 ttl=64 time=0.101 ms
64 bytes from localhost (127.0.0.1): icmp_seq=10 ttl=64 time=0.102 ms
$ ./ping --privileged -c 10 localhost
PING localhost (127.0.0.1):
62 bytes from 127.0.0.1: icmp_seq=0 time=638.779µs
62 bytes from 127.0.0.1: icmp_seq=1 time=188.839µs
62 bytes from 127.0.0.1: icmp_seq=2 time=148.038µs
62 bytes from 127.0.0.1: icmp_seq=3 time=473.847µs
62 bytes from 127.0.0.1: icmp_seq=4 time=336.945µs
62 bytes from 127.0.0.1: icmp_seq=5 time=344.765µs
62 bytes from 127.0.0.1: icmp_seq=6 time=270.598µs
62 bytes from 127.0.0.1: icmp_seq=7 time=96.763µs
62 bytes from 127.0.0.1: icmp_seq=8 time=415.721µs
62 bytes from 127.0.0.1: icmp_seq=9 time=195.21µs
Like a memory leak
I keep looping around, and memory keeps increasing
like:
for {
pinger, err := ping.NewPinger("192.168.80.2")
pinger.SetPrivileged(true)
if err != nil {
panic(err)
}
pinger.Count = 2
pinger.Timeout = 10 * time.Millisecond
pinger.Run() // blocks until finished
stats := pinger.Statistics()
fmt.Println(len(stats.Rtts) == 0)
time.Sleep(time.Millisecond * 1)
}
Process hangs for 27h46m40s
(default timeout) even if I set Count
to 2 if pinging a host that is down.
Automatically add an adjusted timeout if Count
is set - like Count * Interval
.
Would you consider a PR?
335 fmt.Println("get package: ",p.PacketsRecv)
336 if p.Count > 0 && p.PacketsRecv >= p.Count {
close(p.done)
wg.Wait()
return
}
change p.PacketsRecv to p.PacketsSent it work , but it impossible to collect the last package after using p.PacketsSent :(
24 bytes from 52.34.112.193: icmp_seq=0 time=186.8937ms
get package: 1
get package: 1
get package: 1
24 bytes from 52.34.112.193: icmp_seq=2 time=188.8935ms
get package: 2
get package: 2
get package: 2
24 bytes from 52.34.112.193: icmp_seq=4 time=189.8887ms
get package: 3
get package: 3
24 bytes from 52.34.112.193: icmp_seq=5 time=188.89ms
get package: 4
get package: 4
24 bytes from 52.34.112.193: icmp_seq=6 time=188.8929ms
get package: 5
get package: 5
24 bytes from 52.34.112.193: icmp_seq=7 time=188.8929ms
get package: 6
get package: 6
24 bytes from 52.34.112.193: icmp_seq=8 time=195.8847ms
get package: 7
get package: 7
get package: 7
get package: 7
get package: 7
get package: 7
get package: 7
get package: 7
get package: 7
When I paralleled ping different ip on a windows 7 host, I found stats.PacketRecv got mixed, and some unreachable ip get a full PacketRecv
When sending an unsuccessful ping request, it is no longer possible to end an application in a proper fashion, i.e. Ctrl-C does not work. I suspect the timeout code does not end all goroutines properly.
Example code:
pinger, err := ping.NewPinger(addr)
if err != nil {
log.Fatal(err)
}
pinger.Timeout = time.Seconds * 10
pinger.Count = 3
pinger.SetPrivileged(true)
pinger.Run()
stats := pinger.Statistics()
log.Println(stats)
version: github.com/go-ping/ping v0.0.0-20200918120429-e8ae07c3cec8
Run following code on Windows 10, it says not implemented on windows/amd64
.
package main
import (
"fmt"
"github.com/go-ping/ping"
)
func main() {
pinger, err := ping.NewPinger("180.76.76.76")
pinger.SetPrivileged(true)
pinger.Count = 3
err = pinger.Run()
if err != nil {
fmt.Println(err)
}
}
Currently the NewPinger()
defaults to automatic "ip" network selection. It would be useful to be explicit about selecting IPv4 vs IPv6, Then the ping cmd can allow for flags -4
and -6
to enforce which mode to use.
The simplest way would be to change it to something like:
func NewPinger(n string, addr string) (*Pinger, error) {
ipaddr, err := net.ResolveIPAddr(n, addr)
The issues with raw socket support means that if the caller does not set privileged mode correctly, or if the user runs the executable without the required capab, the Pinger.Run()
method can return immediately without sending any ping packets.
There is currently no direct way of differentiating between these problems caused by an invalid environment versus a successful run, as there is no return value from Run
. The code simply logs the error to stdout, which isn't very helpful.
As a workaround, you can either check that Statistics.PacketsSent
is non-zero, or you can check for the invocation of the OnFinish
handler, which won't get invoked if setting up the ICMP listener fails, although neither of these are ideal.
I think Run
should return an error in these circumstances, although sadly this would break backwards compat on the interface.
I'm working on a long-running ping prober and would like an option to disable the timeout.
I'm currently playing around with a replacement "Forever" channel function.
type Forever struct {
C <-chan int
}
func forever() *Forever {
C := make(chan int)
return &Forever{C: C}
}
PING www.google.com (64.233.189.103):
Error listening for ICMP packets: socket: The requested protocol has not been configured into the system, or no implementation for it exists.
This merge request appears to have created some duplicate fields: #15
duplicate field name in struct literal: network
duplicate field name in struct literal: ipv4
for i := range iplist {
pingers[iplist[i]], _ = ping.NewPinger(iplist[i])
}
timer := time.NewTimer(time.Second * 10)
wg := sync.WaitGroup{}
for _, v := range pingers {
wg.Add(1)
go func() {
if v != nil {
v.Run()
}
wg.Done()
}()
}
loop:
for {
select {
case <-timer.C:
for _, v := range pingers {
if v != nil {
v.Stop()
}
}
break loop
}
}
wg.Wait()
for _, v := range pingers {
fmt.Println(v.Statistics())
if true { // v.Statistics().PacketLoss >= 0.0 {
warningList = append(warningList, v.Statistics())
}
}
I run some code like this, which ping two ip address and hope they can stop after 10 seconds, but it just print as follows:
&{0 0 NaN 11.238.144.242 11.238.144.242 [] 0s 0s 0s 0s}
&{20 20 0 11.238.144.86 11.238.144.86 [62.531µs 115.415µs 66.564µs 46.571µs 55.519µs 41.02µs 52.507µs 36.631µs 47.61µs 48.643µs 130.216µs 53.706µs 67.566µs 43.757µs 49.207µs 43.15µs 41.336µs 96.105µs 58.361µs 47.551µs] 36.631µs 130.216µs 60.198µs 24.562µs}
It just seems like there is one Pinger didn't work.
So what I want to ask is, can I start mutilple Pingers like this? Or there is some mistake in my test code?
Hi @sparrc, first of all thank you for package. Started using it and ran into an issue and wanted to run it by you in case I am not using it correctly.
I have created a small function as follows
func PingCheck(HostToPing string) *ping.Statistics {
pinger, err := ping.NewPinger(HostToPing)
if err != nil {
log.Error(err)
}
pinger.OnRecv = func(pkt *ping.Packet) {
log.Infof("%d bytes from %s: icmp_seq=%d time=%v\n",
pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt)
}
pinger.OnFinish = func(stats *ping.Statistics) {
log.Infof("\n--- %s ping statistics ---\n", stats.Addr)
log.Infof("%d packets transmitted, %d packets received, %v%% packet loss\n",
stats.PacketsSent, stats.PacketsRecv, stats.PacketLoss)
log.Infof("round-trip min/avg/max/stddev = %v/%v/%v/%v\n",
stats.MinRtt, stats.AvgRtt, stats.MaxRtt, stats.StdDevRtt)
}
pinger.Count = 30
pinger.Timeout = 1 * time.Second
pinger.Interval = 1
pinger.SetPrivileged(true)
log.Infof("PING %s (%s):\n", pinger.Addr(), pinger.IPAddr())
pinger.Run()
stats := pinger.Statistics()
return stats
}
Here's the code that calls it from a test case
time.AfterFunc(1*time.Second, func() { <-- time.AfterFunc spawns a new go routine, maybe the issue?
stats = utils.PingCheck(tests.TestIp)
injectionPacketLoss = stats.PacketLoss
t.Logf("during injection: packetLoss: %.2f", injectionPacketLoss)
assert.True(t, injectionPacketLoss >= 50)
}
During the duration of the ping test, I am introducing packet 50% packet loss. The library is returning the following results
--- 172.217.7.142 ping statistics ---
INFO[2019-10-11T15:13:22Z] 30 packets transmitted, 1 packets received, 96.66666666666667% packet loss
INFO[2019-10-11T15:13:22Z] round-trip min/avg/max/stddev = 5.806085ms/5.806085ms/5.806085ms/0s
Linux Ping is returning results that I am expecting, which is about 50% packet loss. Trying to understand why the library is returning a much higher packet loss, i.e. 96.6% loss ?
Linux Ping
ping -c 30 172.217.7.142
PING 172.217.7.142 (172.217.7.142) 56(84) bytes of data.
64 bytes from 172.217.7.142: icmp_seq=2 ttl=48 time=5.41 ms
64 bytes from 172.217.7.142: icmp_seq=5 ttl=48 time=5.38 ms
64 bytes from 172.217.7.142: icmp_seq=7 ttl=48 time=5.40 ms
64 bytes from 172.217.7.142: icmp_seq=8 ttl=48 time=5.45 ms
64 bytes from 172.217.7.142: icmp_seq=9 ttl=48 time=5.46 ms
64 bytes from 172.217.7.142: icmp_seq=10 ttl=48 time=5.44 ms
64 bytes from 172.217.7.142: icmp_seq=11 ttl=48 time=5.44 ms
64 bytes from 172.217.7.142: icmp_seq=13 ttl=48 time=5.46 ms
64 bytes from 172.217.7.142: icmp_seq=15 ttl=48 time=5.63 ms
64 bytes from 172.217.7.142: icmp_seq=16 ttl=48 time=5.54 ms
64 bytes from 172.217.7.142: icmp_seq=17 ttl=48 time=5.62 ms
64 bytes from 172.217.7.142: icmp_seq=18 ttl=48 time=5.42 ms
64 bytes from 172.217.7.142: icmp_seq=21 ttl=48 time=5.61 ms
64 bytes from 172.217.7.142: icmp_seq=23 ttl=48 time=5.34 ms
64 bytes from 172.217.7.142: icmp_seq=25 ttl=48 time=5.40 ms
64 bytes from 172.217.7.142: icmp_seq=27 ttl=48 time=6.05 ms
64 bytes from 172.217.7.142: icmp_seq=28 ttl=48 time=5.39 ms
64 bytes from 172.217.7.142: icmp_seq=30 ttl=48 time=5.56 ms
--- 172.217.7.142 ping statistics ---
30 packets transmitted, 15 received, 50% packet loss, time 29194ms
rtt min/avg/max/mdev = 5.346/5.504/6.055/0.162 ms
Thank you in advance for any help you can provide.
go run cmd/ping/ping.go
./ping.go:97:3: duplicate field name in struct literal: network
./ping.go:98:3: duplicate field name in struct literal: ipv4
Run as root, but have a error: Error listening for ICMP packets: socket: permission denied
slice bounds out of range happen when i run ipv4 , i guess the root cause is ipv4Payload method
func (p *Pinger) processPacket(recv *packet) error {
var bytes []byte
var proto int
if p.ipv4 {
if p.network == "ip" {
bytes = ipv4Payload(recv) // this method should change recv.nbytes when process ipv4Header
} else {
bytes = recv.bytes
}
proto = protocolICMP
} else {
bytes = recv.bytes
proto = protocolIPv6ICMP
}
var m *icmp.Message
var err error
// if no change recv.nbytes, the bytes will slice bounds out of range
if m, err = icmp.ParseMessage(proto, bytes[:recv.nbytes]); err != nil {
return fmt.Errorf("Error parsing icmp message")
}
}
func ipv4Payload(recv *packet) []byte {
b := recv.bytes
if len(b) < ipv4.HeaderLen {
return b
}
hdrlen := int(b[0]&0x0f) << 2
recv.nbytes -= hdrlen // this is need
return b[hdrlen:]
}
There is a problem with CPU utilization on the for/select block on line 293 of ping.go. Essentially when you have a large number of pingers running the "default" case causes the for loop to spin up the CPU. I had 700% CPU utilization on my mac.
The fix for this seems to be to move the default case outside of the select block. I haven't tested all the permutations here.
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())
}
}
// moved outside of the select block
if p.Count > 0 && p.PacketsRecv >= p.Count {
close(p.done)
wg.Wait()
return
}
}
Hello,
While using the go-ping library we've encountered an edge case where the library panics. The following is the code to reproduce the issue:
package main
import (
"log"
"time"
ping "github.com/sparrc/go-ping"
)
func main() {
for {
pinger, err := ping.NewPinger("www.google.com")
if err != nil {
panic(err)
}
pinger.Count = 3
pinger.Timeout = time.Second * 5
pinger.SetPrivileged(true)
pinger.Run() // blocks until finished
if pinger.Statistics().PacketsRecv <= 0 {
log.Print("ip not responding to pings")
}
time.Sleep(2)
}
}
I've tried to dig into the stacktrace observed as shown below:
2017/07/18 04:50:30 http: panic serving 10.60.7.163:42758: runtime error: slice bounds out of range
goroutine 29197 [running]:
net/http.(*conn).serve.func1(0xc42009e700)
/usr/local/go/src/net/http/server.go:1491 +0x12a
panic(0x8b2580, 0xc4200100d0)
/usr/local/go/src/runtime/panic.go:458 +0x243
vendor/github.com/sparrc/go-ping.(*Pinger).processPacket(0xc420362000, 0xc42035c760, 0xc420200b68, 0x1)
vendor/github.com/sparrc/go-ping/ping.go:437 +0x3ab
vendor/github.com/sparrc/go-ping.(*Pinger).run(0xc420362000)
vendor/github.com/sparrc/go-ping/ping.go:310 +0x667
vendor/github.com/sparrc/go-ping.(*Pinger).Run(0xc420362000)
My reasoning is as follows:
pkt.Data
doesn't have enough bytes inside, the hardcoded constant of timeSliceLength
https://github.com/sparrc/go-ping/blob/master/ping.go#L63 is going to make it go out of range.pkt.Data[]
is verified before it's evaluated with timeSliceLength
to avoid panic.*icmp.Echo
before it's used. Is there something else besides m.Body.(type)
that can reveal this information?Any ideas what would be the best way to solve this? Let me know if you need any additional details in order to repro this. Happy to help.
how can i get the ttl
in ping result?
i tried to get it in ipv4Payload
but the result is bad
type IPHeader struct {
version_ihl byte
dscp_ecn byte
total_length uint16
identification uint16
offset uint16
ttl byte
protocol byte
checksum uint16
source_address uint32
dest_address uint32
}
func ipv4Payload(b []byte) []byte {
if len(b) < ipv4.HeaderLen {
return b
}
hdrlen := int(b[0]&0x0f) << 2
fmt.Println(unsafe.Pointer(&b[hdrlen:][0]))
fmt.Println((*IPHeader)(unsafe.Pointer(&b[hdrlen:][6])))
return b[hdrlen:]
}
i have study golang for only 3 weeks i can not solve this problem
pinger.PacketsRecv = 0
i tried to find the reason, and i got some message
var wg sync.WaitGroup
recv := make(chan *packet, 5)
defer close(recv)
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)
defer timeout.Stop()
interval := time.NewTicker(p.Interval)
defer interval.Stop()
for {
select {
case <-p.done:
wg.Wait()
return
case <-timeout.C:
close(p.done)
wg.Wait()
return
case <-interval.C:
if p.Count > 0 && p.PacketsSent >= p.Count {
continue
}
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())
}
}
if p.Count > 0 && p.PacketsRecv >= p.Count {
close(p.done)
wg.Wait()
return
}
}
i need to ping almost 16000 server ,if i use more goroutine to do this , block happen frequently
i debug my code , i found the reason
in ping.go 406
`
defer wg.Done()
for {
select {
case <-p.done:
fmt.Println("ok")
return
default:
bytes := make([]byte, 512)
conn.SetReadDeadline(time.Now().Add(time.Millisecond * 100))
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
}
}
}
fmt.Println(len(recv))
recv <- &packet{bytes: bytes, nbytes: n, ttl: ttl}
}
}
`
the wg.Wait()
is blocked by p.recvICMP(conn, recv, &wg)
and in the func recvICMP i foud in the for cycle
recv <- &packet{bytes: bytes, nbytes: n, ttl: ttl}
it will block by send a msg to the channel recv
but the chan recv := make(chan *packet, 5)
only 5 size
if i do more goroutine i found the size 5 is not enough, so it will be blocked because the full channel ,
ok in some coincidence time the main goroutine dose not do the comsume func
case r := <-recv
,
it is do close(p.done)
then do wg.Wait()
block to wait recvICMP
but in recvICMP
case <-p.done: fmt.Println("ok") return
this code will not effective because the block send msg to channel recv,so will not do
defer wg.Done()
then they all block like a deadlock
i sorry to my poor english
i don't konw i say my question clear
now i just do this to solve recv := make(chan *packet, 100)
by the way do many goroutine it has some wrong result ,some ip can ping right but pinger.PacketsRecv = 0
Hello,
maybe i am getting this wrong. I want to print out the packet rtt which works fine with the onrecv function but i also want to print out when the ping times out.
How would i get the information if there was a timeout ?
Best regards
I want to specify the source ip address while using this library. Is there any way to do so?
A bug was introduced in d046b24
The following code is responsible:
// Check if reply from same ID
body := m.Body.(*icmp.Echo)
if body.ID != p.id {
return nil
}
This code breaks when SetPrivileged(false) because the kernel is responsible for setting body.ID:
So even if I set the ID in the code, the kernel's ping_table will set it based on the value in the ping_table.
Send Echo: &icmp.Echo{ID:1234, Seq:1, Data:[]uint8{0x31, 0x32, 0x33}}
Receive Echo: &icmp.Echo{ID:12655, Seq:1, Data:[]uint8{0x31, 0x32, 0x33}}
So checking the ID will fail due to the mismatching IDs.
4 packets transmitted, 5 packets received, -25% packet loss
Count on Unix ping specifies the number of packets to send, whereas in this library, it waits for Count received packets.
This means that you will see more than the desired number of packets sent if you get any packet loss, or your ping time is higher than your interval.
Replicable if you run with the arguments "-c 10 -i 1ms"; you will see more than 10 packets sent, 10 packets received.
Can you implement for all parameter like ping on linux :)
usage: ping [-AaDdfnoQqRrv] [-c count] [-G sweepmaxsize]
[-g sweepminsize] [-h sweepincrsize] [-i wait]
[-l preload] [-M mask | time] [-m ttl] [-p pattern]
[-S src_addr] [-s packetsize] [-t timeout][-W waittime]
[-z tos] host
ping [-AaDdfLnoQqRrv] [-c count] [-I iface] [-i wait]
[-l preload] [-M mask | time] [-m ttl] [-p pattern] [-S src_addr]
[-s packetsize] [-T ttl] [-t timeout] [-W waittime]
[-z tos] mcast-group
Thank you
Mon
I tried basic Pinger as follows:
for {
pinger, err := ping.NewPinger("8.8.8.8")
pinger.SetPrivileged(true)
if err != nil {
panic(err)
}
pinger.Count = 3
pinger.Interval = time.Second
pinger.Timeout = 3 * time.Second
pinger.SetPrivileged(true)
pinger.Run() // blocks until finished
stats := pinger.Statistics()
log.Println("%v", len(stats.Rtts) == 0)
log.Println("%v", (stats))
}
Where start ping at t = t0 and pinger run shall be blocked for 3 secs and returns the status.
I start new pinger again for getting continuous ping statistics.
I tried finally differently by registering the callback and start the New Pinger on receiving the callback as follows:
func main() {
pinger, err := ping.NewPinger("8.8.8.8")
if err != nil {
fmt.Println("Pinger Create failed: %s", err.Error())
return err
}
pinger.Count = 3
pinger.Interval = time.Second
pinger.Timeout = 3 * time.Second
pinger.SetPrivileged(true)
pinger.OnFinish = onFinishCb
err = pinger.Run()
if err != nil {
fmt.Println("Pinger Run failed: %s", err.Error())
return err
}
wg := &sync.WaitGroup{}
wg.Add(1)
go shutdownHandler(wg)
wg.Wait()
}
// onFinishCb is called when ping is finished.
func onFinishCb(stats *ping.Statistics) {
t.latency = uint64(stats.AvgRtt / time.Millisecond)
pinger, err := ping.NewPinger(t.peerIP)
if err != nil {
fmt.Println("Pinger Create failed: %s", err.Error())
// return err
}
pinger.Count = 3
pinger.Interval = time.Second
pinger.Timeout = 3 * time.Second
pinger.SetPrivileged(true)
pinger.OnFinish = onFinishCb
fmt.Println("Latency :%v", t.latency)
err = pinger.Run()
if err != nil {
fmt.Println("Pinger Run failed: %s", err.Error())
}
}
In either case I am finding memory of this main process increases from 1 MB to 8 MB in 4-5 mins (with top command).
Please provide your inputs.
It's probably just my opinion, but I think having Printf's when an error occurs is not the best option. There is no easy way to divert the error messages to something else, so you practically have no choice but to have the error messages end up at stdout.
May I suggest adding a feature to divert the output (to a different stream, to a logger, ....) or adding a function that disables the messages and just return an error?
This library , when I am using for unprivileged ping throws "Error listening for ICMP packets: socket: permission denied" error with go version go1.7.4 .
what I am doing is-
func (p *Ping) doPing() (latency, jitter, packetLoss float64, err error) {
timeout := time.Second*1000
interval := time.Second
count := 5
host := p.ipAddr
pinger, cmdErr := ping.NewPinger(host)
if cmdErr != nil {
glog.Error("Failed to ping " + p.ipAddr)
err = cmdErr
return
}
pinger.OnRecv = func(pkt *ping.Packet) {
fmt.Println("receiving ICMP packets")
}
pinger.OnFinish = func(stats *ping.Statistics) {
fmt.Println("--- %s ping statistics ---", stats.Addr)
fmt.Println("%d pakets transmitted, %d packets received, %v%% packet loss",
stats.PacketsSent, stats.PacketsRecv, stats.PacketLoss)
fmt.Println("round-trip - min %d ,avg %d ,max %d ,stddev %d",
stats.MinRtt, stats.AvgRtt, stats.MaxRtt, stats.StdDevRtt)
}
pinger.Count = count
pinger.Interval = interval
pinger.Timeout = timeout
pinger.SetPrivileged(false)
pinger.Run()
stats := pinger.Statistics()
latency = float64(stats.AvgRtt)
jitter = float64(stats.StdDevRtt)
packetLoss = stats.PacketLoss
return
}
ping.Statistics docs says This can be run while the pinger is running or after it is finished
https://github.com/sparrc/go-ping/blob/4e5b6552494c8005c60de6c60b50ebaefc69e592/ping.go#L356-L359
But the only safe places to run ping.Statistics are in ping.OnRecv and ping.OnFinish.
There is no sync mechanics to protect fields like PacketsRecv
, PacketsSent
, Rtts
while they are being updated in processPacket
Imagine your pinger.Run
goroutine is rescheduled and put to sleep just before https://github.com/sparrc/go-ping/blob/4e5b6552494c8005c60de6c60b50ebaefc69e592/ping.go#L499 PacketsRecv is updated, but Rtts
is not. And your another goroutine calls pinger.Statistics
on the same pinger - it will get inconsistent data.
But there is more, as far as I know, gouroutines can be rescheduled in the middle of statement, like p.PacketsSent++
or append
. Then call to Statistics
will read corrupted data.
Options:
Statistics
to be thread-safeStatistics
There are a variety of open issues and pull requests, most dating back to early 2019 but some as far as 2017. Additionally there have been no commits in over a year.
Are you still interested in maintaining this, @sparrc? If not, would you be amenable to giving commit rights to others?
What is the plan for go-ping
?
Hi!
I have the same issue, as in #47
Here is my simple code:
package main
import (
"github.com/sparrc/go-ping"
)
func main() {
pinger, err := ping.NewPinger("google.com")
if err != nil {
panic(err)
}
pinger.SetPrivileged(true)
pinger.Count = 3
pinger.Run()
}
And the error:
panic: runtime error: slice bounds out of range
goroutine 1 [running]:
github.com/sparrc/go-ping.(*Pinger).processPacket(0xc0000929c0, 0xc0000fa090, 0x4, 0x3)
/home/g0rbe/go/src/github.com/sparrc/go-ping/ping.go:460 +0x828
github.com/sparrc/go-ping.(*Pinger).run(0xc0000929c0)
/home/g0rbe/go/src/github.com/sparrc/go-ping/ping.go:331 +0x500
github.com/sparrc/go-ping.(*Pinger).Run(...)
/home/g0rbe/go/src/github.com/sparrc/go-ping/ping.go:278
main.main()
Your sample (cmd/ping/ping) also panic.
(Fresh download, executed as root, running on Arch linux)
I don't see any id/seq number on ping sent, running multiples ping from same host will mix results.
I'm trying ping in parallel a hole C class (254 ip addresses) but I realize the I need to add a few delay between the creation of every goroutine or otherwise the latency of every ping increases.
¿any ideas what could this be?
Thank you
Problem:
Non pingable IP addresses are receiving ping results from other ping requests when run in parallel.
Example Code:
package main
import (
"fmt"
"sync"
"github.com/sparrc/go-ping"
)
// PingCheck --
func PingCheck(HostToPing string) ping.Statistics {
pinger, err := ping.NewPinger(HostToPing)
if err != nil {
panic(err)
}
pinger.Count = 1
pinger.Timeout = 1000000000
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() // blocks until finished
stats := pinger.Statistics() // get send/receive/rtt stats
return stats
}
func main() {
hosts := [...]string{"8.8.8.8", "1.2.3.4", "4.4.4.4"}
sliceLength := len(hosts)
var wg sync.WaitGroup
wg.Add(sliceLength)
for i := 0; i < sliceLength; i++ {
go func(i int) {
defer wg.Done()
host := hosts[i]
stats := PingCheck(host)
fmt.Println(host, ": ", stats)
}(i)
}
wg.Wait()
}
// 8.8.8.8 & 4.4.4.4 should work
// 1.2.3.4 should not but occasionally it returns with stats. This ip does not exist on the internet!
Output:
austinb-3GLM:agent austinb$ while true; do ./this && sleep 1; done | grep 1.2.3.4
PING 1.2.3.4 (1.2.3.4):
62 bytes from 1.2.3.4: icmp_seq=0 time=16.122ms
--- 1.2.3.4 ping statistics ---
1.2.3.4 : {1 1 0 1.2.3.4 1.2.3.4 [16.122ms] 16.122ms 16.122ms 16.122ms 0s}
PING 1.2.3.4 (1.2.3.4):
--- 1.2.3.4 ping statistics ---
1.2.3.4 : {0 1 100 1.2.3.4 1.2.3.4 [] 0s 0s 0s 0s}
PING 1.2.3.4 (1.2.3.4):
--- 1.2.3.4 ping statistics ---
1.2.3.4 : {0 1 100 1.2.3.4 1.2.3.4 [] 0s 0s 0s 0s}
PING 1.2.3.4 (1.2.3.4):
--- 1.2.3.4 ping statistics ---
1.2.3.4 : {0 1 100 1.2.3.4 1.2.3.4 [] 0s 0s 0s 0s}
PING 1.2.3.4 (1.2.3.4):
62 bytes from 1.2.3.4: icmp_seq=0 time=14.365ms
--- 1.2.3.4 ping statistics ---
1.2.3.4 : {1 1 0 1.2.3.4 1.2.3.4 [14.365ms] 14.365ms 14.365ms 14.365ms 0s}
PING 1.2.3.4 (1.2.3.4):
62 bytes from 1.2.3.4: icmp_seq=0 time=14.198ms
--- 1.2.3.4 ping statistics ---
1.2.3.4 : {1 1 0 1.2.3.4 1.2.3.4 [14.198ms] 14.198ms 14.198ms 14.198ms 0s}
PING 1.2.3.4 (1.2.3.4):
62 bytes from 1.2.3.4: icmp_seq=0 time=15.194ms
--- 1.2.3.4 ping statistics ---
1.2.3.4 : {1 1 0 1.2.3.4 1.2.3.4 [15.194ms] 15.194ms 15.194ms 15.194ms 0s}
PING 1.2.3.4 (1.2.3.4):
--- 1.2.3.4 ping statistics ---
1.2.3.4 : {0 1 100 1.2.3.4 1.2.3.4 [] 0s 0s 0s 0s}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.