pax_global_header00006660000000000000000000000064141567416520014525gustar00rootroot0000000000000052 comment=4eb09b4e931dd5c97b53989f55b2d5fc7ef4619d golang-github-go-ping-ping-0.0~git20211130.779d1e9/000077500000000000000000000000001415674165200211305ustar00rootroot00000000000000golang-github-go-ping-ping-0.0~git20211130.779d1e9/.circleci/000077500000000000000000000000001415674165200227635ustar00rootroot00000000000000golang-github-go-ping-ping-0.0~git20211130.779d1e9/.circleci/config.yml000066400000000000000000000021151415674165200247520ustar00rootroot00000000000000--- executors: golang: docker: - image: cimg/go:1.15 version: 2.1 jobs: build: executor: golang steps: - checkout - run: go mod download - run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.31.0 - run: golangci-lint run - run: make - run: curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | BINDIR=/home/circleci/.local/bin sh - run: goreleaser release --skip-publish --snapshot - store_artifacts: path: dist release: executor: golang steps: - checkout - run: go mod download - run: curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | BINDIR=/home/circleci/.local/bin sh - run: goreleaser release workflows: version: 2 stuff: jobs: - build: filters: tags: only: /.*/ - release: requires: - build filters: tags: only: /^v.*/ branches: ignore: /.*/ golang-github-go-ping-ping-0.0~git20211130.779d1e9/.editorconfig000066400000000000000000000003311415674165200236020ustar00rootroot00000000000000# https://editorconfig.org root = true [*] end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 indent_style = space [Makefile] indent_style = tab [*.go] indent_style = tab golang-github-go-ping-ping-0.0~git20211130.779d1e9/.gitignore000066400000000000000000000000141415674165200231130ustar00rootroot00000000000000/ping /dist golang-github-go-ping-ping-0.0~git20211130.779d1e9/.golangci.yml000066400000000000000000000001141415674165200235100ustar00rootroot00000000000000--- issues: exclude-rules: - path: _test.go linters: - errcheck golang-github-go-ping-ping-0.0~git20211130.779d1e9/.goreleaser.yml000066400000000000000000000014151415674165200240620ustar00rootroot00000000000000project_name: ping before: hooks: - go mod download builds: - binary: ping dir: cmd/ping goarch: - amd64 - arm - arm64 goarm: - 6 - 7 goos: - darwin - freebsd - linux - windows archives: - files: - LICENSE - README.md format_overrides: - goos: windows format: zip wrap_in_directory: true # TODO: Decide if we want packages (name conflcits with /bin/ping?) # nfpms: # homepage: https://github.com/go-ping/ping # maintainer: 'Go Ping Maintainers ' # description: Ping written in Go. # license: MIT # formats: # - deb # - rpm checksum: name_template: 'checksums.txt' snapshot: name_template: "{{ .Tag }}-{{ .ShortCommit }}" changelog: sort: asc filters: exclude: - '^docs:' - '^test:' golang-github-go-ping-ping-0.0~git20211130.779d1e9/CONTRIBUTING.md000066400000000000000000000033531415674165200233650ustar00rootroot00000000000000# Contributing First off, thanks for taking the time to contribute! Remember that this is open source software so please consider the other people who will read your code. Make it look nice for them, document your logic in comments and add or update the unit test cases. This library is used by various other projects, companies and individuals in live production environments so please discuss any breaking changes with us before making them. Feel free to join us in the #go-ping channel of the [Gophers Slack](https://invite.slack.golangbridge.org/). ## Pull Requests [Fork the repo on GitHub](https://github.com/go-ping/ping/fork) and clone it to your local machine. ```bash git clone https://github.com/YOUR_USERNAME/ping.git && cd ping ``` Here is a guide on [how to configure a remote repository](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/configuring-a-remote-for-a-fork). Check out a new branch, make changes, run tests, commit & sign-off, then push branch to your fork. ```bash $ git checkout -b # edit files $ make style vet test $ git add $ git commit -s $ git push ``` Open a [new pull request](https://github.com/go-ping/ping/compare) in the main `go-ping/ping` repository. Please describe the purpose of your PR and remember link it to any related issues. *We may ask you to rebase your feature branch or squash the commits in order to keep the history clean.* ## Development Guides - Run `make style vet test` before committing your changes. - Document your logic in code comments. - Add tests for bug fixes and new features. - Use UNIX-style (LF) line endings. - End every file with a single blank line. - Use the UTF-8 character set. golang-github-go-ping-ping-0.0~git20211130.779d1e9/LICENSE000066400000000000000000000021121415674165200221310ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2016 Cameron Sparr and contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-go-ping-ping-0.0~git20211130.779d1e9/Makefile000066400000000000000000000014121415674165200225660ustar00rootroot00000000000000GO ?= go GOFMT ?= $(GO)fmt GOOPTS ?= GO111MODULE := pkgs = ./... all: style vet build test .PHONY: build build: @echo ">> building ping" GO111MODULE=$(GO111MODULE) $(GO) build $(GOOPTS) ./cmd/ping .PHONY: style style: @echo ">> checking code style" @fmtRes=$$($(GOFMT) -d $$(find . -path ./vendor -prune -o -name '*.go' -print)); \ if [ -n "$${fmtRes}" ]; then \ echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \ echo "Please ensure you are using $$($(GO) version) for formatting code."; \ exit 1; \ fi .PHONY: test test: @echo ">> running all tests" GO111MODULE=$(GO111MODULE) $(GO) test -race -cover $(GOOPTS) $(pkgs) .PHONY: vet vet: @echo ">> vetting code" GO111MODULE=$(GO111MODULE) $(GO) vet $(GOOPTS) $(pkgs) golang-github-go-ping-ping-0.0~git20211130.779d1e9/README.md000066400000000000000000000101031415674165200224020ustar00rootroot00000000000000# go-ping [![PkgGoDev](https://pkg.go.dev/badge/github.com/go-ping/ping)](https://pkg.go.dev/github.com/go-ping/ping) [![Circle CI](https://circleci.com/gh/go-ping/ping.svg?style=svg)](https://circleci.com/gh/go-ping/ping) A simple but powerful ICMP echo (ping) library for Go, inspired by [go-fastping](https://github.com/tatsushid/go-fastping). Here is a very simple example that sends and receives three packets: ```go pinger, err := ping.NewPinger("www.google.com") if err != nil { panic(err) } pinger.Count = 3 err = pinger.Run() // Blocks until finished. if err != nil { panic(err) } stats := pinger.Statistics() // get send/receive/duplicate/rtt stats ``` Here is an example that emulates the traditional UNIX ping command: ```go pinger, err := ping.NewPinger("www.google.com") if err != nil { panic(err) } // Listen for Ctrl-C. 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.OnDuplicateRecv = func(pkt *ping.Packet) { fmt.Printf("%d bytes from %s: icmp_seq=%d time=%v ttl=%v (DUP!)\n", pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt, pkt.Ttl) } 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()) err = pinger.Run() if err != nil { panic(err) } ``` It sends ICMP Echo Request packet(s) and waits for an Echo Reply in response. If it receives a response, it calls the `OnRecv` callback unless a packet with that sequence number has already been received, in which case it calls the `OnDuplicateRecv` callback. When it's finished, it calls the `OnFinish` callback. For a full ping example, see [cmd/ping/ping.go](https://github.com/go-ping/ping/blob/master/cmd/ping/ping.go). ## Installation ``` go get -u github.com/go-ping/ping ``` To install the native Go ping executable: ```bash go get -u github.com/go-ping/ping/... $GOPATH/bin/ping ``` ## Supported Operating Systems ### Linux This library attempts to send an "unprivileged" ping via UDP. On Linux, this must be enabled with the following sysctl command: ``` sudo sysctl -w net.ipv4.ping_group_range="0 2147483647" ``` If you do not wish to do this, you can call `pinger.SetPrivileged(true)` in your code and then use setcap on your binary to allow it to bind to raw sockets (or just run it as root): ``` setcap cap_net_raw=+ep /path/to/your/compiled/binary ``` See [this blog](https://sturmflut.github.io/linux/ubuntu/2015/01/17/unprivileged-icmp-sockets-on-linux/) and the Go [x/net/icmp](https://godoc.org/golang.org/x/net/icmp) package for more details. ### Windows You must use `pinger.SetPrivileged(true)`, otherwise you will receive the following error: ``` socket: The requested protocol has not been configured into the system, or no implementation for it exists. ``` Despite the method name, this should work without the need to elevate privileges and has been tested on Windows 10. Please note that accessing packet TTL values is not supported due to limitations in the Go x/net/ipv4 and x/net/ipv6 packages. ### Plan 9 from Bell Labs There is no support for Plan 9. This is because the entire `x/net/ipv4` and `x/net/ipv6` packages are not implemented by the Go programming language. ## Maintainers and Getting Help: This repo was originally in the personal account of [sparrc](https://github.com/sparrc), but is now maintained by the [go-ping organization](https://github.com/go-ping). For support and help, you usually find us in the #go-ping channel of Gophers Slack. See https://invite.slack.golangbridge.org/ for an invite to the Gophers Slack org. ## Contributing Refer to [CONTRIBUTING.md](https://github.com/go-ping/ping/blob/master/CONTRIBUTING.md) golang-github-go-ping-ping-0.0~git20211130.779d1e9/cmd/000077500000000000000000000000001415674165200216735ustar00rootroot00000000000000golang-github-go-ping-ping-0.0~git20211130.779d1e9/cmd/ping/000077500000000000000000000000001415674165200226305ustar00rootroot00000000000000golang-github-go-ping-ping-0.0~git20211130.779d1e9/cmd/ping/ping.go000066400000000000000000000044241415674165200241200ustar00rootroot00000000000000package main import ( "flag" "fmt" "os" "os/signal" "time" "github.com/go-ping/ping" ) var usage = ` Usage: ping [-c count] [-i interval] [-t timeout] [--privileged] 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 # Send a privileged raw ICMP ping sudo ping --privileged www.google.com # Send ICMP messages with a 100-byte payload ping -s 100 1.1.1.1 ` func main() { timeout := flag.Duration("t", time.Second*100000, "") interval := flag.Duration("i", time.Second, "") count := flag.Int("c", -1, "") size := flag.Int("s", 24, "") ttl := flag.Int("l", 64, "TTL") privileged := flag.Bool("privileged", false, "") flag.Usage = func() { fmt.Print(usage) } flag.Parse() if flag.NArg() == 0 { flag.Usage() return } host := flag.Arg(0) pinger, err := ping.NewPinger(host) if err != nil { fmt.Println("ERROR:", err) return } // 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 ttl=%v\n", pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt, pkt.Ttl) } pinger.OnDuplicateRecv = func(pkt *ping.Packet) { fmt.Printf("%d bytes from %s: icmp_seq=%d time=%v ttl=%v (DUP!)\n", pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt, pkt.Ttl) } pinger.OnFinish = func(stats *ping.Statistics) { fmt.Printf("\n--- %s ping statistics ---\n", stats.Addr) fmt.Printf("%d packets transmitted, %d packets received, %d duplicates, %v%% packet loss\n", stats.PacketsSent, stats.PacketsRecv, stats.PacketsRecvDuplicates, 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.Size = *size pinger.Interval = *interval pinger.Timeout = *timeout pinger.TTL = *ttl pinger.SetPrivileged(*privileged) fmt.Printf("PING %s (%s):\n", pinger.Addr(), pinger.IPAddr()) err = pinger.Run() if err != nil { fmt.Println("Failed to ping target host:", err) } } golang-github-go-ping-ping-0.0~git20211130.779d1e9/go.mod000066400000000000000000000002771415674165200222440ustar00rootroot00000000000000module github.com/go-ping/ping go 1.14 require ( github.com/google/uuid v1.2.0 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c ) golang-github-go-ping-ping-0.0~git20211130.779d1e9/go.sum000066400000000000000000000022461415674165200222670ustar00rootroot00000000000000github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005 h1:pDMpM2zh2MT0kHy037cKlSby2nEhD50SYqwQk76Nm40= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang-github-go-ping-ping-0.0~git20211130.779d1e9/logger.go000066400000000000000000000022301415674165200227330ustar00rootroot00000000000000package ping import "log" type Logger interface { Fatalf(format string, v ...interface{}) Errorf(format string, v ...interface{}) Warnf(format string, v ...interface{}) Infof(format string, v ...interface{}) Debugf(format string, v ...interface{}) } type StdLogger struct { Logger *log.Logger } func (l StdLogger) Fatalf(format string, v ...interface{}) { l.Logger.Printf("FATAL: "+format, v...) } func (l StdLogger) Errorf(format string, v ...interface{}) { l.Logger.Printf("ERROR: "+format, v...) } func (l StdLogger) Warnf(format string, v ...interface{}) { l.Logger.Printf("WARN: "+format, v...) } func (l StdLogger) Infof(format string, v ...interface{}) { l.Logger.Printf("INFO: "+format, v...) } func (l StdLogger) Debugf(format string, v ...interface{}) { l.Logger.Printf("DEBUG: "+format, v...) } type NoopLogger struct { } func (l NoopLogger) Fatalf(format string, v ...interface{}) { } func (l NoopLogger) Errorf(format string, v ...interface{}) { } func (l NoopLogger) Warnf(format string, v ...interface{}) { } func (l NoopLogger) Infof(format string, v ...interface{}) { } func (l NoopLogger) Debugf(format string, v ...interface{}) { } golang-github-go-ping-ping-0.0~git20211130.779d1e9/packetconn.go000066400000000000000000000036651415674165200236160ustar00rootroot00000000000000package ping import ( "net" "runtime" "time" "golang.org/x/net/icmp" "golang.org/x/net/ipv4" "golang.org/x/net/ipv6" ) type packetConn interface { Close() error ICMPRequestType() icmp.Type ReadFrom(b []byte) (n int, ttl int, src net.Addr, err error) SetFlagTTL() error SetReadDeadline(t time.Time) error WriteTo(b []byte, dst net.Addr) (int, error) SetTTL(ttl int) } type icmpConn struct { c *icmp.PacketConn ttl int } func (c *icmpConn) Close() error { return c.c.Close() } func (c *icmpConn) SetTTL(ttl int) { c.ttl = ttl } func (c *icmpConn) SetReadDeadline(t time.Time) error { return c.c.SetReadDeadline(t) } func (c *icmpConn) WriteTo(b []byte, dst net.Addr) (int, error) { if c.c.IPv6PacketConn() != nil { if err := c.c.IPv6PacketConn().SetHopLimit(c.ttl); err != nil { return 0, err } } if c.c.IPv4PacketConn() != nil { if err := c.c.IPv4PacketConn().SetTTL(c.ttl); err != nil { return 0, err } } return c.c.WriteTo(b, dst) } type icmpv4Conn struct { icmpConn } func (c *icmpv4Conn) SetFlagTTL() error { err := c.c.IPv4PacketConn().SetControlMessage(ipv4.FlagTTL, true) if runtime.GOOS == "windows" { return nil } return err } func (c *icmpv4Conn) ReadFrom(b []byte) (int, int, net.Addr, error) { var ttl int n, cm, src, err := c.c.IPv4PacketConn().ReadFrom(b) if cm != nil { ttl = cm.TTL } return n, ttl, src, err } func (c icmpv4Conn) ICMPRequestType() icmp.Type { return ipv4.ICMPTypeEcho } type icmpV6Conn struct { icmpConn } func (c *icmpV6Conn) SetFlagTTL() error { err := c.c.IPv6PacketConn().SetControlMessage(ipv6.FlagHopLimit, true) if runtime.GOOS == "windows" { return nil } return err } func (c *icmpV6Conn) ReadFrom(b []byte) (int, int, net.Addr, error) { var ttl int n, cm, src, err := c.c.IPv6PacketConn().ReadFrom(b) if cm != nil { ttl = cm.HopLimit } return n, ttl, src, err } func (c icmpV6Conn) ICMPRequestType() icmp.Type { return ipv6.ICMPTypeEchoRequest } golang-github-go-ping-ping-0.0~git20211130.779d1e9/ping.go000066400000000000000000000445641415674165200224310ustar00rootroot00000000000000// Package ping is a simple but powerful ICMP echo (ping) library. // // Here is a very simple example that sends and receives three packets: // // pinger, err := ping.NewPinger("www.google.com") // if err != nil { // panic(err) // } // pinger.Count = 3 // err = pinger.Run() // blocks until finished // if err != nil { // panic(err) // } // stats := pinger.Statistics() // get send/receive/rtt stats // // Here is an example that emulates the traditional UNIX ping command: // // pinger, err := ping.NewPinger("www.google.com") // if err != nil { // panic(err) // } // // Listen for Ctrl-C. // 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()) // err = pinger.Run() // if err != nil { // panic(err) // } // // It sends ICMP Echo Request packet(s) and waits for an Echo Reply in response. // If it receives a response, it calls the OnRecv callback. When it's finished, // it calls the OnFinish callback. // // For a full ping example, see "cmd/ping/ping.go". // package ping import ( "bytes" "errors" "fmt" "log" "math" "math/rand" "net" "sync" "sync/atomic" "syscall" "time" "github.com/google/uuid" "golang.org/x/net/icmp" "golang.org/x/net/ipv4" "golang.org/x/net/ipv6" "golang.org/x/sync/errgroup" ) const ( timeSliceLength = 8 trackerLength = len(uuid.UUID{}) protocolICMP = 1 protocolIPv6ICMP = 58 ) var ( ipv4Proto = map[string]string{"icmp": "ip4:icmp", "udp": "udp4"} ipv6Proto = map[string]string{"icmp": "ip6:ipv6-icmp", "udp": "udp6"} ) // New returns a new Pinger struct pointer. func New(addr string) *Pinger { r := rand.New(rand.NewSource(getSeed())) firstUUID := uuid.New() var firstSequence = map[uuid.UUID]map[int]struct{}{} firstSequence[firstUUID] = make(map[int]struct{}) return &Pinger{ Count: -1, Interval: time.Second, RecordRtts: true, Size: timeSliceLength + trackerLength, Timeout: time.Duration(math.MaxInt64), addr: addr, done: make(chan interface{}), id: r.Intn(math.MaxUint16), trackerUUIDs: []uuid.UUID{firstUUID}, ipaddr: nil, ipv4: false, network: "ip", protocol: "udp", awaitingSequences: firstSequence, TTL: 64, logger: StdLogger{Logger: log.New(log.Writer(), log.Prefix(), log.Flags())}, } } // NewPinger returns a new Pinger and resolves the address. func NewPinger(addr string) (*Pinger, error) { p := New(addr) return p, p.Resolve() } // Pinger represents a 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 // Number of duplicate packets received PacketsRecvDuplicates int // Round trip time statistics minRtt time.Duration maxRtt time.Duration avgRtt time.Duration stdDevRtt time.Duration stddevm2 time.Duration statsMu sync.RWMutex // If true, keep a record of rtts of all received packets. // Set to false to avoid memory bloat for long running pings. RecordRtts bool // rtts is all of the Rtts rtts []time.Duration // OnSetup is called when Pinger has finished setting up the listening socket OnSetup func() // OnSend is called when Pinger sends a packet OnSend func(*Packet) // OnRecv is called when Pinger receives and processes a packet OnRecv func(*Packet) // OnFinish is called when Pinger exits OnFinish func(*Statistics) // OnDuplicateRecv is called when a packet is received that has already been received. OnDuplicateRecv func(*Packet) // Size of packet being sent Size int // Tracker: Used to uniquely identify packets - Deprecated Tracker uint64 // Source is the source IP address Source string // Channel and mutex used to communicate when the Pinger should stop between goroutines. done chan interface{} lock sync.Mutex ipaddr *net.IPAddr addr string // trackerUUIDs is the list of UUIDs being used for sending packets. trackerUUIDs []uuid.UUID ipv4 bool id int sequence int // awaitingSequences are in-flight sequence numbers we keep track of to help remove duplicate receipts awaitingSequences map[uuid.UUID]map[int]struct{} // network is one of "ip", "ip4", or "ip6". network string // protocol is "icmp" or "udp". protocol string logger Logger TTL int } type packet struct { bytes []byte nbytes int ttl 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 // Addr is the string address of the host being pinged. Addr string // NBytes is the number of bytes in the message. Nbytes int // Seq is the ICMP sequence number. Seq int // TTL is the Time To Live on the packet. Ttl int // ID is the ICMP identifier. ID 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 // PacketsRecvDuplicates is the number of duplicate responses there were to a sent packet. PacketsRecvDuplicates 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 } func (p *Pinger) updateStatistics(pkt *Packet) { p.statsMu.Lock() defer p.statsMu.Unlock() p.PacketsRecv++ if p.RecordRtts { p.rtts = append(p.rtts, pkt.Rtt) } if p.PacketsRecv == 1 || pkt.Rtt < p.minRtt { p.minRtt = pkt.Rtt } if pkt.Rtt > p.maxRtt { p.maxRtt = pkt.Rtt } pktCount := time.Duration(p.PacketsRecv) // welford's online method for stddev // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm delta := pkt.Rtt - p.avgRtt p.avgRtt += delta / pktCount delta2 := pkt.Rtt - p.avgRtt p.stddevm2 += delta * delta2 p.stdDevRtt = time.Duration(math.Sqrt(float64(p.stddevm2 / pktCount))) } // SetIPAddr sets the ip address of the target host. func (p *Pinger) SetIPAddr(ipaddr *net.IPAddr) { p.ipv4 = isIPv4(ipaddr.IP) 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 } // Resolve does the DNS lookup for the Pinger address and sets IP protocol. func (p *Pinger) Resolve() error { if len(p.addr) == 0 { return errors.New("addr cannot be empty") } ipaddr, err := net.ResolveIPAddr(p.network, p.addr) if err != nil { return err } p.ipv4 = isIPv4(ipaddr.IP) p.ipaddr = ipaddr return nil } // 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 { oldAddr := p.addr p.addr = addr err := p.Resolve() if err != nil { p.addr = oldAddr return err } return nil } // Addr returns the string ip address of the target host. func (p *Pinger) Addr() string { return p.addr } // SetNetwork allows configuration of DNS resolution. // * "ip" will automatically select IPv4 or IPv6. // * "ip4" will select IPv4. // * "ip6" will select IPv6. func (p *Pinger) SetNetwork(n string) { switch n { case "ip4": p.network = "ip4" case "ip6": p.network = "ip6" default: p.network = "ip" } } // 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.protocol = "icmp" } else { p.protocol = "udp" } } // Privileged returns whether pinger is running in privileged mode. func (p *Pinger) Privileged() bool { return p.protocol == "icmp" } // SetLogger sets the logger to be used to log events from the pinger. func (p *Pinger) SetLogger(logger Logger) { p.logger = logger } // SetID sets the ICMP identifier. func (p *Pinger) SetID(id int) { p.id = id } // ID returns the ICMP identifier. func (p *Pinger) ID() int { return p.id } // 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() error { var conn packetConn var err error if p.Size < timeSliceLength+trackerLength { return fmt.Errorf("size %d is less than minimum required size %d", p.Size, timeSliceLength+trackerLength) } if p.ipaddr == nil { err = p.Resolve() } if err != nil { return err } if conn, err = p.listen(); err != nil { return err } defer conn.Close() conn.SetTTL(p.TTL) return p.run(conn) } func (p *Pinger) run(conn packetConn) error { if err := conn.SetFlagTTL(); err != nil { return err } defer p.finish() recv := make(chan *packet, 5) defer close(recv) if handler := p.OnSetup; handler != nil { handler() } var g errgroup.Group g.Go(func() error { defer p.Stop() return p.recvICMP(conn, recv) }) g.Go(func() error { defer p.Stop() return p.runLoop(conn, recv) }) return g.Wait() } func (p *Pinger) runLoop( conn packetConn, recvCh <-chan *packet, ) error { logger := p.logger if logger == nil { logger = NoopLogger{} } timeout := time.NewTicker(p.Timeout) interval := time.NewTicker(p.Interval) defer func() { p.Stop() interval.Stop() timeout.Stop() }() if err := p.sendICMP(conn); err != nil { return err } for { select { case <-p.done: return nil case <-timeout.C: return nil case r := <-recvCh: err := p.processPacket(r) if err != nil { // FIXME: this logs as FATAL but continues logger.Fatalf("processing received packet: %s", err) } case <-interval.C: if p.Count > 0 && p.PacketsSent >= p.Count { interval.Stop() continue } err := p.sendICMP(conn) if err != nil { // FIXME: this logs as FATAL but continues logger.Fatalf("sending packet: %s", err) } } if p.Count > 0 && p.PacketsRecv >= p.Count { return nil } } } func (p *Pinger) Stop() { p.lock.Lock() defer p.lock.Unlock() open := true select { case _, open = <-p.done: default: } if open { close(p.done) } } 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 { p.statsMu.RLock() defer p.statsMu.RUnlock() sent := p.PacketsSent loss := float64(sent-p.PacketsRecv) / float64(sent) * 100 s := Statistics{ PacketsSent: sent, PacketsRecv: p.PacketsRecv, PacketsRecvDuplicates: p.PacketsRecvDuplicates, PacketLoss: loss, Rtts: p.rtts, Addr: p.addr, IPAddr: p.ipaddr, MaxRtt: p.maxRtt, MinRtt: p.minRtt, AvgRtt: p.avgRtt, StdDevRtt: p.stdDevRtt, } return &s } type expBackoff struct { baseDelay time.Duration maxExp int64 c int64 } func (b *expBackoff) Get() time.Duration { if b.c < b.maxExp { b.c++ } return b.baseDelay * time.Duration(rand.Int63n(1< 0 { t = append(t, bytes.Repeat([]byte{1}, remainSize)...) } body := &icmp.Echo{ ID: p.id, Seq: p.sequence, Data: t, } msg := &icmp.Message{ Type: conn.ICMPRequestType(), Code: 0, Body: body, } msgBytes, err := msg.Marshal(nil) if err != nil { return err } for { if _, err := conn.WriteTo(msgBytes, dst); err != nil { if neterr, ok := err.(*net.OpError); ok { if neterr.Err == syscall.ENOBUFS { continue } } return err } handler := p.OnSend if handler != nil { outPkt := &Packet{ Nbytes: len(msgBytes), IPAddr: p.ipaddr, Addr: p.addr, Seq: p.sequence, ID: p.id, } handler(outPkt) } // mark this sequence as in-flight p.awaitingSequences[currentUUID][p.sequence] = struct{}{} p.PacketsSent++ p.sequence++ if p.sequence > 65535 { newUUID := uuid.New() p.trackerUUIDs = append(p.trackerUUIDs, newUUID) p.awaitingSequences[newUUID] = make(map[int]struct{}) p.sequence = 0 } break } return nil } func (p *Pinger) listen() (packetConn, error) { var ( conn packetConn err error ) if p.ipv4 { var c icmpv4Conn c.c, err = icmp.ListenPacket(ipv4Proto[p.protocol], p.Source) conn = &c } else { var c icmpV6Conn c.c, err = icmp.ListenPacket(ipv6Proto[p.protocol], p.Source) conn = &c } if err != nil { p.Stop() return nil, err } return conn, nil } func bytesToTime(b []byte) time.Time { var nsec int64 for i := uint8(0); i < 8; i++ { nsec += int64(b[i]) << ((7 - i) * 8) } return time.Unix(nsec/1000000000, nsec%1000000000) } func isIPv4(ip net.IP) bool { return len(ip.To4()) == net.IPv4len } func timeToBytes(t time.Time) []byte { nsec := t.UnixNano() b := make([]byte, 8) for i := uint8(0); i < 8; i++ { b[i] = byte((nsec >> ((7 - i) * 8)) & 0xff) } return b } var seed int64 = time.Now().UnixNano() // getSeed returns a goroutine-safe unique seed func getSeed() int64 { return atomic.AddInt64(&seed, 1) } golang-github-go-ping-ping-0.0~git20211130.779d1e9/ping_test.go000066400000000000000000000447541415674165200234710ustar00rootroot00000000000000package ping import ( "bytes" "errors" "fmt" "net" "runtime/debug" "sync/atomic" "testing" "time" "github.com/google/uuid" "golang.org/x/net/icmp" "golang.org/x/net/ipv4" ) func TestProcessPacket(t *testing.T) { pinger := makeTestPinger() shouldBe1 := 0 // this function should be called pinger.OnRecv = func(pkt *Packet) { shouldBe1++ } currentUUID := pinger.getCurrentTrackerUUID() uuidEncoded, err := currentUUID.MarshalBinary() if err != nil { t.Fatal(fmt.Sprintf("unable to marshal UUID binary: %s", err)) } data := append(timeToBytes(time.Now()), uuidEncoded...) if remainSize := pinger.Size - timeSliceLength - trackerLength; remainSize > 0 { data = append(data, bytes.Repeat([]byte{1}, remainSize)...) } body := &icmp.Echo{ ID: pinger.id, Seq: pinger.sequence, Data: data, } pinger.awaitingSequences[currentUUID][pinger.sequence] = struct{}{} msg := &icmp.Message{ Type: ipv4.ICMPTypeEchoReply, Code: 0, Body: body, } msgBytes, _ := msg.Marshal(nil) pkt := packet{ nbytes: len(msgBytes), bytes: msgBytes, ttl: 24, } err = pinger.processPacket(&pkt) AssertNoError(t, err) AssertTrue(t, shouldBe1 == 1) } func TestProcessPacket_IgnoreNonEchoReplies(t *testing.T) { pinger := makeTestPinger() shouldBe0 := 0 // this function should not be called because the tracker is mismatched pinger.OnRecv = func(pkt *Packet) { shouldBe0++ } currentUUID, err := pinger.getCurrentTrackerUUID().MarshalBinary() if err != nil { t.Fatal(fmt.Sprintf("unable to marshal UUID binary: %s", err)) } data := append(timeToBytes(time.Now()), currentUUID...) if remainSize := pinger.Size - timeSliceLength - trackerLength; remainSize > 0 { data = append(data, bytes.Repeat([]byte{1}, remainSize)...) } body := &icmp.Echo{ ID: pinger.id, Seq: pinger.sequence, Data: data, } msg := &icmp.Message{ Type: ipv4.ICMPTypeDestinationUnreachable, Code: 0, Body: body, } msgBytes, _ := msg.Marshal(nil) pkt := packet{ nbytes: len(msgBytes), bytes: msgBytes, ttl: 24, } err = pinger.processPacket(&pkt) AssertNoError(t, err) AssertTrue(t, shouldBe0 == 0) } func TestProcessPacket_IDMismatch(t *testing.T) { pinger := makeTestPinger() pinger.protocol = "icmp" // ID is only checked on "icmp" protocol shouldBe0 := 0 // this function should not be called because the tracker is mismatched pinger.OnRecv = func(pkt *Packet) { shouldBe0++ } currentUUID, err := pinger.getCurrentTrackerUUID().MarshalBinary() if err != nil { t.Fatal(fmt.Sprintf("unable to marshal UUID binary: %s", err)) } data := append(timeToBytes(time.Now()), currentUUID...) if remainSize := pinger.Size - timeSliceLength - trackerLength; remainSize > 0 { data = append(data, bytes.Repeat([]byte{1}, remainSize)...) } body := &icmp.Echo{ ID: 999999, Seq: pinger.sequence, Data: data, } msg := &icmp.Message{ Type: ipv4.ICMPTypeEchoReply, Code: 0, Body: body, } msgBytes, _ := msg.Marshal(nil) pkt := packet{ nbytes: len(msgBytes), bytes: msgBytes, ttl: 24, } err = pinger.processPacket(&pkt) AssertNoError(t, err) AssertTrue(t, shouldBe0 == 0) } func TestProcessPacket_TrackerMismatch(t *testing.T) { pinger := makeTestPinger() shouldBe0 := 0 // this function should not be called because the tracker is mismatched pinger.OnRecv = func(pkt *Packet) { shouldBe0++ } testUUID, err := uuid.New().MarshalBinary() if err != nil { t.Fatal(fmt.Sprintf("unable to marshal UUID binary: %s", err)) } data := append(timeToBytes(time.Now()), testUUID...) if remainSize := pinger.Size - timeSliceLength - trackerLength; remainSize > 0 { data = append(data, bytes.Repeat([]byte{1}, remainSize)...) } body := &icmp.Echo{ ID: pinger.id, Seq: pinger.sequence, Data: data, } msg := &icmp.Message{ Type: ipv4.ICMPTypeEchoReply, Code: 0, Body: body, } msgBytes, _ := msg.Marshal(nil) pkt := packet{ nbytes: len(msgBytes), bytes: msgBytes, ttl: 24, } err = pinger.processPacket(&pkt) AssertNoError(t, err) AssertTrue(t, shouldBe0 == 0) } func TestProcessPacket_LargePacket(t *testing.T) { pinger := makeTestPinger() pinger.Size = 4096 currentUUID, err := pinger.getCurrentTrackerUUID().MarshalBinary() if err != nil { t.Fatal(fmt.Sprintf("unable to marshal UUID binary: %s", err)) } data := append(timeToBytes(time.Now()), currentUUID...) if remainSize := pinger.Size - timeSliceLength - trackerLength; remainSize > 0 { data = append(data, bytes.Repeat([]byte{1}, remainSize)...) } body := &icmp.Echo{ ID: pinger.id, Seq: pinger.sequence, Data: data, } msg := &icmp.Message{ Type: ipv4.ICMPTypeEchoReply, Code: 0, Body: body, } msgBytes, _ := msg.Marshal(nil) pkt := packet{ nbytes: len(msgBytes), bytes: msgBytes, ttl: 24, } err = pinger.processPacket(&pkt) AssertNoError(t, err) } func TestProcessPacket_PacketTooSmall(t *testing.T) { pinger := makeTestPinger() data := []byte("foo") body := &icmp.Echo{ ID: pinger.id, Seq: pinger.sequence, Data: data, } msg := &icmp.Message{ Type: ipv4.ICMPTypeEchoReply, Code: 0, Body: body, } msgBytes, _ := msg.Marshal(nil) pkt := packet{ nbytes: len(msgBytes), bytes: msgBytes, ttl: 24, } err := pinger.processPacket(&pkt) AssertError(t, err, "") } func TestNewPingerValid(t *testing.T) { p := New("www.google.com") err := p.Resolve() AssertNoError(t, err) AssertEqualStrings(t, "www.google.com", p.Addr()) // DNS names should resolve into IP addresses AssertNotEqualStrings(t, "www.google.com", p.IPAddr().String()) AssertTrue(t, isIPv4(p.IPAddr().IP)) AssertFalse(t, p.Privileged()) // Test that SetPrivileged works p.SetPrivileged(true) AssertTrue(t, p.Privileged()) // Test setting to ipv4 address err = p.SetAddr("www.google.com") AssertNoError(t, err) AssertTrue(t, isIPv4(p.IPAddr().IP)) // Test setting to ipv6 address err = p.SetAddr("ipv6.google.com") AssertNoError(t, err) AssertFalse(t, isIPv4(p.IPAddr().IP)) p = New("localhost") err = p.Resolve() AssertNoError(t, err) AssertEqualStrings(t, "localhost", p.Addr()) // DNS names should resolve into IP addresses AssertNotEqualStrings(t, "localhost", p.IPAddr().String()) AssertTrue(t, isIPv4(p.IPAddr().IP)) AssertFalse(t, p.Privileged()) // Test that SetPrivileged works p.SetPrivileged(true) AssertTrue(t, p.Privileged()) // Test setting to ipv4 address err = p.SetAddr("www.google.com") AssertNoError(t, err) AssertTrue(t, isIPv4(p.IPAddr().IP)) // Test setting to ipv6 address err = p.SetAddr("ipv6.google.com") AssertNoError(t, err) AssertFalse(t, isIPv4(p.IPAddr().IP)) p = New("127.0.0.1") err = p.Resolve() AssertNoError(t, err) AssertEqualStrings(t, "127.0.0.1", p.Addr()) AssertTrue(t, isIPv4(p.IPAddr().IP)) AssertFalse(t, p.Privileged()) // Test that SetPrivileged works p.SetPrivileged(true) AssertTrue(t, p.Privileged()) // Test setting to ipv4 address err = p.SetAddr("www.google.com") AssertNoError(t, err) AssertTrue(t, isIPv4(p.IPAddr().IP)) // Test setting to ipv6 address err = p.SetAddr("ipv6.google.com") AssertNoError(t, err) AssertFalse(t, isIPv4(p.IPAddr().IP)) p = New("ipv6.google.com") err = p.Resolve() AssertNoError(t, err) AssertEqualStrings(t, "ipv6.google.com", p.Addr()) // DNS names should resolve into IP addresses AssertNotEqualStrings(t, "ipv6.google.com", p.IPAddr().String()) AssertFalse(t, isIPv4(p.IPAddr().IP)) AssertFalse(t, p.Privileged()) // Test that SetPrivileged works p.SetPrivileged(true) AssertTrue(t, p.Privileged()) // Test setting to ipv4 address err = p.SetAddr("www.google.com") AssertNoError(t, err) AssertTrue(t, isIPv4(p.IPAddr().IP)) // Test setting to ipv6 address err = p.SetAddr("ipv6.google.com") AssertNoError(t, err) AssertFalse(t, isIPv4(p.IPAddr().IP)) // ipv6 localhost: p = New("::1") err = p.Resolve() AssertNoError(t, err) AssertEqualStrings(t, "::1", p.Addr()) AssertFalse(t, isIPv4(p.IPAddr().IP)) AssertFalse(t, p.Privileged()) // Test that SetPrivileged works p.SetPrivileged(true) AssertTrue(t, p.Privileged()) // Test setting to ipv4 address err = p.SetAddr("www.google.com") AssertNoError(t, err) AssertTrue(t, isIPv4(p.IPAddr().IP)) // Test setting to ipv6 address err = p.SetAddr("ipv6.google.com") AssertNoError(t, err) AssertFalse(t, isIPv4(p.IPAddr().IP)) } func TestNewPingerInvalid(t *testing.T) { _, err := NewPinger("127.0.0.0.1") AssertError(t, err, "127.0.0.0.1") _, err = NewPinger("127..0.0.1") AssertError(t, err, "127..0.0.1") // The .invalid tld is guaranteed not to exist by RFC2606. _, err = NewPinger("wtf.invalid.") AssertError(t, err, "wtf.invalid.") _, err = NewPinger(":::1") AssertError(t, err, ":::1") _, err = NewPinger("ipv5.google.com") AssertError(t, err, "ipv5.google.com") } func TestSetIPAddr(t *testing.T) { googleaddr, err := net.ResolveIPAddr("ip", "www.google.com") if err != nil { t.Fatal("Can't resolve www.google.com, can't run tests") } // Create a localhost ipv4 pinger p := New("localhost") err = p.Resolve() AssertNoError(t, err) AssertEqualStrings(t, "localhost", p.Addr()) // set IPAddr to google p.SetIPAddr(googleaddr) AssertEqualStrings(t, googleaddr.String(), p.Addr()) } func TestEmptyIPAddr(t *testing.T) { _, err := NewPinger("") AssertError(t, err, "empty pinger did not return an error") } func TestStatisticsSunny(t *testing.T) { // Create a localhost ipv4 pinger p := New("localhost") err := p.Resolve() AssertNoError(t, err) AssertEqualStrings(t, "localhost", p.Addr()) p.PacketsSent = 10 p.updateStatistics(&Packet{Rtt: time.Duration(1000)}) p.updateStatistics(&Packet{Rtt: time.Duration(1000)}) p.updateStatistics(&Packet{Rtt: time.Duration(1000)}) p.updateStatistics(&Packet{Rtt: time.Duration(1000)}) p.updateStatistics(&Packet{Rtt: time.Duration(1000)}) p.updateStatistics(&Packet{Rtt: time.Duration(1000)}) p.updateStatistics(&Packet{Rtt: time.Duration(1000)}) p.updateStatistics(&Packet{Rtt: time.Duration(1000)}) p.updateStatistics(&Packet{Rtt: time.Duration(1000)}) p.updateStatistics(&Packet{Rtt: time.Duration(1000)}) stats := p.Statistics() if stats.PacketsRecv != 10 { t.Errorf("Expected %v, got %v", 10, stats.PacketsRecv) } if stats.PacketsSent != 10 { t.Errorf("Expected %v, got %v", 10, stats.PacketsSent) } if stats.PacketLoss != 0 { t.Errorf("Expected %v, got %v", 0, stats.PacketLoss) } if stats.MinRtt != time.Duration(1000) { t.Errorf("Expected %v, got %v", time.Duration(1000), stats.MinRtt) } if stats.MaxRtt != time.Duration(1000) { t.Errorf("Expected %v, got %v", time.Duration(1000), stats.MaxRtt) } if stats.AvgRtt != time.Duration(1000) { t.Errorf("Expected %v, got %v", time.Duration(1000), stats.AvgRtt) } if stats.StdDevRtt != time.Duration(0) { t.Errorf("Expected %v, got %v", time.Duration(0), stats.StdDevRtt) } } func TestStatisticsLossy(t *testing.T) { // Create a localhost ipv4 pinger p := New("localhost") err := p.Resolve() AssertNoError(t, err) AssertEqualStrings(t, "localhost", p.Addr()) p.PacketsSent = 20 p.updateStatistics(&Packet{Rtt: time.Duration(10)}) p.updateStatistics(&Packet{Rtt: time.Duration(1000)}) p.updateStatistics(&Packet{Rtt: time.Duration(1000)}) p.updateStatistics(&Packet{Rtt: time.Duration(10000)}) p.updateStatistics(&Packet{Rtt: time.Duration(1000)}) p.updateStatistics(&Packet{Rtt: time.Duration(800)}) p.updateStatistics(&Packet{Rtt: time.Duration(1000)}) p.updateStatistics(&Packet{Rtt: time.Duration(40)}) p.updateStatistics(&Packet{Rtt: time.Duration(100000)}) p.updateStatistics(&Packet{Rtt: time.Duration(1000)}) stats := p.Statistics() if stats.PacketsRecv != 10 { t.Errorf("Expected %v, got %v", 10, stats.PacketsRecv) } if stats.PacketsSent != 20 { t.Errorf("Expected %v, got %v", 20, stats.PacketsSent) } if stats.PacketLoss != 50 { t.Errorf("Expected %v, got %v", 50, stats.PacketLoss) } if stats.MinRtt != time.Duration(10) { t.Errorf("Expected %v, got %v", time.Duration(10), stats.MinRtt) } if stats.MaxRtt != time.Duration(100000) { t.Errorf("Expected %v, got %v", time.Duration(100000), stats.MaxRtt) } if stats.AvgRtt != time.Duration(11585) { t.Errorf("Expected %v, got %v", time.Duration(11585), stats.AvgRtt) } if stats.StdDevRtt != time.Duration(29603) { t.Errorf("Expected %v, got %v", time.Duration(29603), stats.StdDevRtt) } } // Test helpers func makeTestPinger() *Pinger { pinger := New("127.0.0.1") pinger.ipv4 = true pinger.addr = "127.0.0.1" pinger.protocol = "icmp" pinger.id = 123 pinger.Size = 0 return pinger } func AssertNoError(t *testing.T, err error) { t.Helper() if err != nil { t.Errorf("Expected No Error but got %s, Stack:\n%s", err, string(debug.Stack())) } } func AssertError(t *testing.T, err error, info string) { t.Helper() if err == nil { t.Errorf("Expected Error but got %s, %s, Stack:\n%s", err, info, string(debug.Stack())) } } func AssertEqualStrings(t *testing.T, expected, actual string) { t.Helper() if expected != actual { t.Errorf("Expected %s, got %s, Stack:\n%s", expected, actual, string(debug.Stack())) } } func AssertNotEqualStrings(t *testing.T, expected, actual string) { t.Helper() if expected == actual { t.Errorf("Expected %s, got %s, Stack:\n%s", expected, actual, string(debug.Stack())) } } func AssertTrue(t *testing.T, b bool) { t.Helper() if !b { t.Errorf("Expected True, got False, Stack:\n%s", string(debug.Stack())) } } func AssertFalse(t *testing.T, b bool) { t.Helper() if b { t.Errorf("Expected False, got True, Stack:\n%s", string(debug.Stack())) } } func BenchmarkProcessPacket(b *testing.B) { pinger := New("127.0.0.1") pinger.ipv4 = true pinger.addr = "127.0.0.1" pinger.protocol = "ip4:icmp" pinger.id = 123 currentUUID, err := pinger.getCurrentTrackerUUID().MarshalBinary() if err != nil { b.Fatal(fmt.Sprintf("unable to marshal UUID binary: %s", err)) } data := append(timeToBytes(time.Now()), currentUUID...) if remainSize := pinger.Size - timeSliceLength - trackerLength; remainSize > 0 { data = append(data, bytes.Repeat([]byte{1}, remainSize)...) } body := &icmp.Echo{ ID: pinger.id, Seq: pinger.sequence, Data: data, } msg := &icmp.Message{ Type: ipv4.ICMPTypeEchoReply, Code: 0, Body: body, } msgBytes, _ := msg.Marshal(nil) pkt := packet{ nbytes: len(msgBytes), bytes: msgBytes, ttl: 24, } for k := 0; k < b.N; k++ { pinger.processPacket(&pkt) } } func TestProcessPacket_IgnoresDuplicateSequence(t *testing.T) { pinger := makeTestPinger() // pinger.protocol = "icmp" // ID is only checked on "icmp" protocol shouldBe0 := 0 dups := 0 // this function should not be called because the tracker is mismatched pinger.OnRecv = func(pkt *Packet) { shouldBe0++ } pinger.OnDuplicateRecv = func(pkt *Packet) { dups++ } currentUUID := pinger.getCurrentTrackerUUID() uuidEncoded, err := currentUUID.MarshalBinary() if err != nil { t.Fatal(fmt.Sprintf("unable to marshal UUID binary: %s", err)) } data := append(timeToBytes(time.Now()), uuidEncoded...) if remainSize := pinger.Size - timeSliceLength - trackerLength; remainSize > 0 { data = append(data, bytes.Repeat([]byte{1}, remainSize)...) } body := &icmp.Echo{ ID: 123, Seq: 0, Data: data, } // register the sequence as sent pinger.awaitingSequences[currentUUID][0] = struct{}{} msg := &icmp.Message{ Type: ipv4.ICMPTypeEchoReply, Code: 0, Body: body, } msgBytes, _ := msg.Marshal(nil) pkt := packet{ nbytes: len(msgBytes), bytes: msgBytes, ttl: 24, } err = pinger.processPacket(&pkt) AssertNoError(t, err) // receive a duplicate err = pinger.processPacket(&pkt) AssertNoError(t, err) AssertTrue(t, shouldBe0 == 1) AssertTrue(t, dups == 1) AssertTrue(t, pinger.PacketsRecvDuplicates == 1) } type testPacketConn struct{} func (c testPacketConn) Close() error { return nil } func (c testPacketConn) ICMPRequestType() icmp.Type { return ipv4.ICMPTypeEcho } func (c testPacketConn) SetFlagTTL() error { return nil } func (c testPacketConn) SetReadDeadline(t time.Time) error { return nil } func (c testPacketConn) SetTTL(t int) {} func (c testPacketConn) ReadFrom(b []byte) (n int, ttl int, src net.Addr, err error) { return 0, 0, nil, nil } func (c testPacketConn) WriteTo(b []byte, dst net.Addr) (int, error) { return len(b), nil } type testPacketConnBadWrite struct { testPacketConn } func (c testPacketConnBadWrite) WriteTo(b []byte, dst net.Addr) (int, error) { return 0, errors.New("bad write") } func TestRunBadWrite(t *testing.T) { pinger := New("127.0.0.1") pinger.Count = 1 err := pinger.Resolve() AssertNoError(t, err) var conn testPacketConnBadWrite err = pinger.run(conn) AssertTrue(t, err != nil) stats := pinger.Statistics() AssertTrue(t, stats != nil) if stats == nil { t.FailNow() } AssertTrue(t, stats.PacketsSent == 0) AssertTrue(t, stats.PacketsRecv == 0) } type testPacketConnBadRead struct { testPacketConn } func (c testPacketConnBadRead) ReadFrom(b []byte) (n int, ttl int, src net.Addr, err error) { return 0, 0, nil, errors.New("bad read") } func TestRunBadRead(t *testing.T) { pinger := New("127.0.0.1") pinger.Count = 1 err := pinger.Resolve() AssertNoError(t, err) var conn testPacketConnBadRead err = pinger.run(conn) AssertTrue(t, err != nil) stats := pinger.Statistics() AssertTrue(t, stats != nil) if stats == nil { t.FailNow() } AssertTrue(t, stats.PacketsSent == 1) AssertTrue(t, stats.PacketsRecv == 0) } type testPacketConnOK struct { testPacketConn writeDone int32 buf []byte dst net.Addr } func (c *testPacketConnOK) WriteTo(b []byte, dst net.Addr) (int, error) { c.buf = make([]byte, len(b)) c.dst = dst n := copy(c.buf, b) atomic.StoreInt32(&c.writeDone, 1) return n, nil } func (c *testPacketConnOK) ReadFrom(b []byte) (n int, ttl int, src net.Addr, err error) { if atomic.LoadInt32(&c.writeDone) == 0 { return 0, 0, nil, nil } msg, err := icmp.ParseMessage(ipv4.ICMPTypeEcho.Protocol(), c.buf) if err != nil { return 0, 0, nil, err } msg.Type = ipv4.ICMPTypeEchoReply buf, err := msg.Marshal(nil) if err != nil { return 0, 0, nil, err } time.Sleep(10 * time.Millisecond) return copy(b, buf), 64, c.dst, nil } func TestRunOK(t *testing.T) { pinger := New("127.0.0.1") pinger.Count = 1 err := pinger.Resolve() AssertNoError(t, err) conn := new(testPacketConnOK) err = pinger.run(conn) AssertTrue(t, err == nil) stats := pinger.Statistics() AssertTrue(t, stats != nil) if stats == nil { t.FailNow() } AssertTrue(t, stats.PacketsSent == 1) AssertTrue(t, stats.PacketsRecv == 1) AssertTrue(t, stats.MinRtt >= 10*time.Millisecond) AssertTrue(t, stats.MinRtt <= 12*time.Millisecond) } golang-github-go-ping-ping-0.0~git20211130.779d1e9/utils_linux.go000066400000000000000000000005531415674165200240410ustar00rootroot00000000000000// +build linux package ping // Returns the length of an ICMP message. func (p *Pinger) getMessageLength() int { return p.Size + 8 } // Attempts to match the ID of an ICMP packet. func (p *Pinger) matchID(ID int) bool { // On Linux we can only match ID if we are privileged. if p.protocol == "icmp" { if ID != p.id { return false } } return true } golang-github-go-ping-ping-0.0~git20211130.779d1e9/utils_other.go000066400000000000000000000004341415674165200240210ustar00rootroot00000000000000// +build !linux,!windows package ping // Returns the length of an ICMP message. func (p *Pinger) getMessageLength() int { return p.Size + 8 } // Attempts to match the ID of an ICMP packet. func (p *Pinger) matchID(ID int) bool { if ID != p.id { return false } return true } golang-github-go-ping-ping-0.0~git20211130.779d1e9/utils_windows.go000066400000000000000000000006631415674165200243760ustar00rootroot00000000000000// +build windows package ping import ( "golang.org/x/net/ipv4" "golang.org/x/net/ipv6" ) // Returns the length of an ICMP message, plus the IP packet header. func (p *Pinger) getMessageLength() int { if p.ipv4 { return p.Size + 8 + ipv4.HeaderLen } return p.Size + 8 + ipv6.HeaderLen } // Attempts to match the ID of an ICMP packet. func (p *Pinger) matchID(ID int) bool { if ID != p.id { return false } return true }