Skip to content

Commit

Permalink
Update ping binary to behave more like iputils ping
Browse files Browse the repository at this point in the history
  • Loading branch information
glinton committed Jul 3, 2019
1 parent beb3b1b commit 1983bc2
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 17 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

Simple (ICMP) library patterned after net/http.

Originally inspired by [sparrc/go-ping](https://github.com/sparrc/go-ping)


### Installation

Expand Down Expand Up @@ -50,7 +52,7 @@ System installed `ping` binaries generally have `setuid` attributes set, thus al
setcap cap_net_raw=eip /path/to/your/application
```

Since this library can utilize unprivileged raw sockets on Linux and Darwin (`udp4` or `udp6` as the network). On Linux, the system group of the user running the application must be allowed to create unprivileged ICMP sockets. [See man pages icmp(7) for `ping_group_range`](http://man7.org/linux/man-pages/man7/icmp.7.html).
This library tries to initialize a privileged ICMP socket before falling back to unprivileged raw sockets on Linux and Darwin (`udp4` or `udp6` as the network). On Linux, the system group of the user running the application must be allowed to create unprivileged ICMP sockets if desired. [See man pages icmp(7) for `ping_group_range`](http://man7.org/linux/man-pages/man7/icmp.7.html).

To allow a range of groups access to create unprivileged icmp sockets on linux (ipv4 or ipv6), run:

Expand Down
102 changes: 102 additions & 0 deletions cmd/ping/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
## ping

Send ICMP ECHO_REQUEST to network hosts

`ping` uses the ICMP protocol's mandatory ECHO_REQUEST datagram to elicit an ICMP ECHO_RESPONSE from a host or gateway. ECHO_REQUEST datagrams (pings) have an IP and ICMP header, followed by an arbitrary number of padbytes used to fill out the packet.

`ping` works with both IPv4 and IPv6. Using only one of them explicitly can be enforced by specifying -4 or -6.


#### Installation

```sh
go get github.com/glinton/ping/cmd/ping
# to run:
$GOPATH/bin/ping google.com
```


#### Usage
```
ping [-h] [-4] [-6] [-c count] [-i interval] [-I interface]
[-w deadline] [-W timeout] [-s packetsize] destination
-4
Use IPv4 only.
-6
Use IPv6 only.
-c count
Stop after sending count ECHO_REQUEST packets. With deadline
option, ping waits for count ECHO_REPLY packets, until the timeout
expires.
-h
Show help.
-i interval
Wait interval seconds between sending each packet. The default is
to wait for one second between each packet normally. Interval values
less than 200 milliseconds (200ms) are not allowed.
-I interface
interface is either an address, or an interface name. If interface
is an address, it sets source address to specified interface
address. If interface in an interface name, it sets source
interface to specified interface.
-s packetsize
Specifies the number of data bytes to be sent. The default is 56,
which translates into 64 ICMP data bytes when combined with the 8
bytes of ICMP header data.
-w deadline
Specify a timeout, in seconds, before ping exits regardless of how
many packets have been sent or received. In this case ping does not
stop after count packet are sent, it waits either for deadline
expire or until count probes are answered or for some error
notification from network.
-W timeout
Time to wait for a response, in seconds. The option affects only
timeout in absence of any responses.
```


#### Examples

```
# ping google continuously
ping google.com
# ping google 5 times
ping -c 5 google.com
# ping google 5 times at 500ms intervals
ping -c 5 -i .5 google.com
# ping google for 10 seconds
ping -w 10 google.com
```


#### Notes Regarding ICMP Socket Permissions

System installed `ping` binaries generally have `setuid` attributes set, thus allowing them to utilize privileged ICMP sockets. This should work for applications built with this library as well, but a better approach would be to give the application the capability to create privileged ICMP sockets. To do so, run the following as the `root` user (not applicable to Windows).

```
setcap cap_net_raw=eip /gopath/bin/ping
```

If you desire to utilize unprivileged raw sockets on Linux, the system group of the user running ping must be allowed to create unprivileged ICMP sockets. [See man pages icmp(7) for `ping_group_range`](http://man7.org/linux/man-pages/man7/icmp.7.html).

To allow a range of groups access to create unprivileged icmp sockets on linux (ipv4 or ipv6), run:

```
sudo sysctl -w net.ipv4.ping_group_range="GROUPID_START GROUPID_END"
```

If you plan to run your application as `root`, the aforementioned commmand is not necessary.

On Windows, running a terminal as admin should not be necessary.
67 changes: 51 additions & 16 deletions cmd/ping/ping.go → cmd/ping/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import (
"fmt"
"math"
"net"
"os"
"os/signal"
"strings"
"sync"
"syscall"
"time"

"github.com/glinton/ping"
Expand All @@ -18,11 +21,11 @@ func main() {
ipv4 := flag.Bool("-4", false, "")
ipv6 := flag.Bool("-6", false, "")
count := flag.Int("c", 0, "")
interval := flag.Duration("i", time.Second, "")
interval := flag.Float64("i", 1, "")
iface := flag.String("I", "", "")
size := flag.Int("-s", 0, "")
deadline := flag.Duration("w", time.Millisecond*5000, "")
timeout := flag.Duration("W", time.Second, "")
deadline := flag.Float64("w", 0, "")
timeout := flag.Float64("W", 1, "")

flag.Usage = func() {
fmt.Print(usage)
Expand Down Expand Up @@ -52,12 +55,28 @@ func main() {
return
}

tick := time.NewTicker(*interval)
defer tick.Stop()
fmt.Printf("PING %s (%s) %d bytes of data.\n", destination, host.String(), len(data))

ctx := term(context.Background())

wg := &sync.WaitGroup{}
ctx, cancel := context.WithTimeout(context.Background(), *deadline)
defer cancel()
var cancel context.CancelFunc
if *deadline > 0 {
ctx, cancel = context.WithTimeout(ctx, time.Duration(*deadline*float64(time.Second)))
defer cancel()
}

if *timeout <= 0 {
*timeout = 2
}

if *interval < .2 {
fmt.Println("ping: cannot flood; minimal interval allowed for user is 200ms")
return
}

tick := time.NewTicker(time.Duration(*interval * float64(time.Second)))
defer tick.Stop()

chanLength := 100
if *count > 0 {
Expand All @@ -73,8 +92,6 @@ func main() {
name = strings.TrimSuffix(names[0], ".")
}

fmt.Printf("PING %s (%s) %d bytes of data.\n", destination, host.String(), len(data))

req := ping.Request{
Dst: net.ParseIP(host.String()),
Src: net.ParseIP(getAddr(*iface)),
Expand All @@ -85,7 +102,7 @@ func main() {
case <-ctx.Done():
goto finish
case <-tick.C:
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(*timeout*float64(time.Second)))
defer cancel()

packetsSent++
Expand Down Expand Up @@ -209,6 +226,25 @@ func getAddr(ipOrIface string) string {
return ""
}

// Handle signals in the fanciest way. Thanks to:
// https://github.com/influxdata/influxdb/blob/v2.0.0-alpha.14/kit/signals/context.go
func term(ctx context.Context) context.Context {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)

ctx, cancel := context.WithCancel(ctx)
go func() {
defer cancel()
select {
case <-ctx.Done():
return
case <-sigCh:
return
}
}()
return ctx
}

var usage = `
NAME
ping - send ICMP ECHO_REQUEST to network hosts
Expand All @@ -220,8 +256,8 @@ SYNOPSIS
DESCRIPTION
ping uses the ICMP protocol's mandatory ECHO_REQUEST datagram to elicit
an ICMP ECHO_RESPONSE from a host or gateway. ECHO_REQUEST datagrams
(pings) have an IP and ICMP header, followed by a struct timeval and
then an arbitrary number of padbytes used to fill out the packet.
(pings) have an IP and ICMP header, followed by an arbitrary number
of padbytes used to fill out the packet.
ping works with both IPv4 and IPv6. Using only one of them explicitly
can be enforced by specifying -4 or -6.
Expand Down Expand Up @@ -266,8 +302,7 @@ OPTIONS
-W timeout
Time to wait for a response, in seconds. The option affects only
timeout in absence of any responses, otherwise ping waits for two
seconds.
timeout in absence of any responses.
EXAMPLES
# ping google continuously
Expand All @@ -277,8 +312,8 @@ EXAMPLES
ping -c 5 www.google.com
# ping google 5 times at 500ms intervals
ping -c 5 -i 500ms www.google.com
ping -c 5 -i .5 www.google.com
# ping google for 10 seconds
ping -w 10s www.google.com
ping -w 10 www.google.com
`

0 comments on commit 1983bc2

Please sign in to comment.