From 1983bc2fd5de3ea00aa5457bbc8774300e889db9 Mon Sep 17 00:00:00 2001 From: greg linton Date: Wed, 3 Jul 2019 11:30:28 -0600 Subject: [PATCH] Update ping binary to behave more like iputils ping --- README.md | 4 +- cmd/ping/README.md | 102 ++++++++++++++++++++++++++++++++++ cmd/ping/{ping.go => main.go} | 67 ++++++++++++++++------ 3 files changed, 156 insertions(+), 17 deletions(-) create mode 100644 cmd/ping/README.md rename cmd/ping/{ping.go => main.go} (82%) diff --git a/README.md b/README.md index 5959ef8..f33483a 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ Simple (ICMP) library patterned after net/http. +Originally inspired by [sparrc/go-ping](https://github.com/sparrc/go-ping) + ### Installation @@ -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: diff --git a/cmd/ping/README.md b/cmd/ping/README.md new file mode 100644 index 0000000..6836e98 --- /dev/null +++ b/cmd/ping/README.md @@ -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. diff --git a/cmd/ping/ping.go b/cmd/ping/main.go similarity index 82% rename from cmd/ping/ping.go rename to cmd/ping/main.go index bfb11bd..ad0a891 100644 --- a/cmd/ping/ping.go +++ b/cmd/ping/main.go @@ -7,8 +7,11 @@ import ( "fmt" "math" "net" + "os" + "os/signal" "strings" "sync" + "syscall" "time" "github.com/glinton/ping" @@ -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) @@ -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 { @@ -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)), @@ -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++ @@ -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 @@ -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. @@ -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 @@ -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 `