Full Code of digineo/go-ping for AI

master d7f777972058 cached
39 files
62.1 KB
21.4k tokens
96 symbols
1 requests
Download .txt
Repository: digineo/go-ping
Branch: master
Commit: d7f777972058
Files: 39
Total size: 62.1 KB

Directory structure:
gitextract_u0ueinq6/

├── .codecov.yml
├── .github/
│   ├── build-all
│   └── workflows/
│       ├── build.yml
│       └── golangci-lint.yml
├── .gitignore
├── .golangci.yml
├── LICENSE
├── README.md
├── cmd/
│   ├── common.mk
│   ├── multiping/
│   │   ├── Makefile
│   │   ├── README.md
│   │   ├── destination.go
│   │   ├── destination_test.go
│   │   ├── main.go
│   │   ├── resolve.go
│   │   └── ui.go
│   ├── ping-monitor/
│   │   ├── Makefile
│   │   └── main.go
│   ├── ping-test/
│   │   ├── Makefile
│   │   ├── README.md
│   │   └── main.go
│   └── pingnet/
│       ├── Makefile
│       └── main.go
├── error.go
├── go.mod
├── go.sum
├── monitor/
│   ├── history.go
│   ├── history_test.go
│   ├── metrics.go
│   ├── monitor.go
│   └── target.go
├── payload.go
├── pinger.go
├── pinger_linux.go
├── pinger_other.go
├── pinger_test.go
├── receiving.go
├── request.go
└── sending.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .codecov.yml
================================================
ignore:
- cmd


================================================
FILE: .github/build-all
================================================
#!/bin/sh -e

for mf in $(find cmd -name Makefile); do
  make -C "$(dirname $mf)"
done


================================================
FILE: .github/workflows/build.yml
================================================
name: build

on:
- push
- pull_request

jobs:
  build:
    name: Tests
    runs-on: ubuntu-latest
    steps:

    - name: Set up Go 1.x
      uses: actions/setup-go@v5
      with:
        go-version: ^1.23
      id: go

    - name: Check out code into the Go module directory
      uses: actions/checkout@v5

    - name: Build commands
      run: .github/build-all

    - name: Run tests
      run: sudo go test -v -coverprofile coverage.txt ./...

    - name: Upload coverage report
      uses: codecov/codecov-action@v5
      with:
        files: coverage.txt


================================================
FILE: .github/workflows/golangci-lint.yml
================================================
name: golangci-lint

on:
- push
- pull_request

jobs:
  golangci:
    name: lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-go@v5
        with:
          go-version: ^1.23
      - name: golangci-lint
        uses: golangci/golangci-lint-action@v8
        with:
          version: v2.4.0


================================================
FILE: .gitignore
================================================
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib

# Test binary, build with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/

# Build fragments
cmd/multiping/multiping
cmd/multiping/multiping.log
cmd/ping-test/ping-test
cmd/ping-test/ping-test.log
cmd/ping-monitor/ping-monitor
cmd/pingnet/pingnet


================================================
FILE: .golangci.yml
================================================
version: "2"
linters:
  exclusions:
    generated: lax
    presets:
      - comments
      - common-false-positives
      - legacy
      - std-error-handling
    rules:
      - linters:
          - errcheck
        path: \.go
    paths:
      - third_party$
      - builtin$
      - examples$
formatters:
  exclusions:
    generated: lax
    paths:
      - third_party$
      - builtin$
      - examples$


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018 Digineo GmbH

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.


================================================
FILE: README.md
================================================
# go-ping

[![GoDoc](https://godoc.org/github.com/digineo/go-ping?status.svg)](https://godoc.org/github.com/digineo/go-ping)
[![Build Status](https://github.com/digineo/go-ping/workflows/build/badge.svg?branch=master)](https://github.com/digineo/go-ping/actions)
[![Codecov](https://codecov.io/gh/digineo/go-ping/branch/master/graph/badge.svg)](https://codecov.io/gh/digineo/go-ping)
[![Go Report Card](https://goreportcard.com/badge/github.com/digineo/go-ping)](https://goreportcard.com/report/github.com/digineo/go-ping)

A simple ICMP Echo implementation, based on [golang.org/x/net/icmp][net-icmp].

Some sample programs are provided in `cmd/`:

- [**`ping-test`**][ping-test] is a really simple ping clone
- [**`multiping`**][multiping] provides an interactive TUI to ping multiple hosts
- [**`ping-monitor`**][monitor] pings multiple hosts in parallel, but just prints the summary every so often
- [**`pingnet`**][pingnet] allows to ping every host in a CIDR range (e.g. 0.0.0.0/0 :-))

[net-icmp]: https://godoc.org/golang.org/x/net/icmp
[ping-test]: https://github.com/digineo/go-ping/tree/master/cmd/ping-test
[multiping]: https://github.com/digineo/go-ping/tree/master/cmd/multiping
[monitor]: https://github.com/digineo/go-ping/tree/master/cmd/ping-monitor
[pingnet]: https://github.com/digineo/go-ping/tree/master/cmd/pingnet

## Features

- [x] IPv4 and IPv6 support
- [x] Unicast and multicast support
- [x] configurable retry amount and timeout duration
- [x] configurable payload size (and content)
- [x] round trip time measurement

## Contribute

Simply fork and create a pull-request. We'll try to respond in a timely
fashion.

## Software using this library

* [Ping Exporter for Prometheus](https://github.com/czerwonk/ping_exporter)

Please create a pull request to get your software listed.

## License

MIT License, Copyright (c) 2018 Digineo GmbH

<https://www.digineo.de>


================================================
FILE: cmd/common.mk
================================================
.PHONY: all
all: $(TARGET)

$(TARGET):
	go build -o $@

.PHONY: clean
clean:
	rm -f $(TARGET) $(TARGET).log


================================================
FILE: cmd/multiping/Makefile
================================================
TARGET = multiping

include ../common.mk

.PHONY: test
test: all
	go test ./...
	./$(TARGET) golang.org cloudflare.com 2>$(TARGET).log
	cat $(TARGET).log


================================================
FILE: cmd/multiping/README.md
================================================
# multiping

Just like regular `ping`, but measures round trip time to multiple
hosts, all at once.

The user interface is similar to [mtr](https://www.bitwizard.nl/mtr/):

![Screenshot of multiping in action](multiping.png)


## Installation

Installing is as easy as:

```
$ go get -u github.com/digineo/go-ping/cmd/multiping
```

This will download and build this program and install it into `$GOPATH/bin/`
(assuming you have the Go toolchain installed).

To run it, you need elevated privileges (as mentioned in the
[README of ping-test](../ping-test)). You can either run it as root (or
via `sudo`; both not recommended), or enable the program to only open
raw sockets (via `setcap`, but Linux-only):

```
$ sudo setcap cap_net_raw+ep $GOPATH/bin/multiping
```

## Running

Assuming `$GOPATH/bin` is in your `$PATH`, just call it with a list
of hosts and IP addresses (currently only IPv4):

```
$ multiping golang.org google.com 127.0.0.1
```

### Options

To get a list of available options, run `multiping -h`:

```
Usage of ./multiping:
  -bind4 string
    	IPv4 bind address (default "0.0.0.0")
  -bind6 string
    	IPv6 bind address (default "::")
  -buf uint
    	buffer size for statistics (default 50)
  -interval duration
    	polling interval (default 1s)
  -resolve duration
    	timeout for DNS lookups (default 1.5s)
  -s uint
    	size of payload in bytes (default 56)
  -timeout duration
    	timeout for a single echo request (default 1s)
```

## Roadmap

- [x] cleanup UI code (this is a bit of a mess)
- [ ] add more features
  - [ ] different display modes (`mtr` has different views)
  - [x] move "last error" column into a log area at the bottom
  - [ ] increase/decrease interval and/or timeout with `-`/`+` keys
- [x] fill IPv6 options with life (once the library has IPv6 support)
- [x] use something more sophisticated than `net.ResolveIPAddress` to
  get a list of all A/AAAA records for a given domain name
  - [ ] this propably needs an off-switch


================================================
FILE: cmd/multiping/destination.go
================================================
package main

import (
	"log"
	"math"
	"net"
	"sync"
	"time"

	ping "github.com/digineo/go-ping"
)

type history struct {
	received int
	lost     int
	results  []time.Duration // ring, start index = .received%len
	mtx      sync.RWMutex
}

type destination struct {
	host   string
	remote *net.IPAddr
	*history
}

type stat struct {
	pktSent int
	pktLoss float64
	last    time.Duration
	best    time.Duration
	worst   time.Duration
	mean    time.Duration
	stddev  time.Duration
}

func (u *destination) ping(pinger *ping.Pinger) {
	rtt, err := pinger.Ping(u.remote, opts.timeout)
	if err != nil {
		log.Printf("[yellow]%s[white]: %v", u.host, err)
	}
	u.addResult(rtt, err)
}

func (s *history) addResult(rtt time.Duration, err error) {
	s.mtx.Lock()
	if err == nil {
		s.results[s.received%len(s.results)] = rtt
		s.received++
	} else {
		s.lost++
	}
	s.mtx.Unlock()
}

func (s *history) compute() (st stat) {
	s.mtx.RLock()
	defer s.mtx.RUnlock()

	if s.received == 0 {
		if s.lost > 0 {
			st.pktLoss = 1.0
		}
		return
	}

	collection := s.results[:]
	st.pktSent = s.received + s.lost
	size := len(s.results)
	st.last = collection[(s.received-1)%size]

	// we don't yet have filled the buffer
	if s.received <= size {
		collection = s.results[:s.received]
		size = s.received
	}

	st.pktLoss = float64(s.lost) / float64(s.received+s.lost)
	st.best, st.worst = collection[0], collection[0]

	total := time.Duration(0)
	for _, rtt := range collection {
		if rtt < st.best {
			st.best = rtt
		}
		if rtt > st.worst {
			st.worst = rtt
		}
		total += rtt
	}

	st.mean = time.Duration(float64(total) / float64(size))

	stddevNum := float64(0)
	for _, rtt := range collection {
		dev := float64(rtt - st.mean)
		stddevNum += dev * dev
	}
	st.stddev = time.Duration(math.Sqrt(stddevNum / float64(size)))

	return
}


================================================
FILE: cmd/multiping/destination_test.go
================================================
package main

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestComputeStats(t *testing.T) {
	assert := assert.New(t)
	const (
		z  = time.Duration(0)
		ms = time.Millisecond
		µs = time.Microsecond
		ns = time.Nanosecond
	)

	testcases := []struct {
		title    string
		results  []time.Duration
		received int
		lost     int

		last   time.Duration
		best   time.Duration
		worst  time.Duration
		mean   time.Duration
		stddev time.Duration
		loss   float64
	}{
		{
			title:    "simplest case",
			results:  []time.Duration{},
			received: 0,
			last:     z, best: z, worst: z, mean: z, stddev: z,
		},
		{
			title:    "another simple case",
			results:  []time.Duration{ms},
			received: 1,
			last:     ms, best: ms, worst: ms, mean: ms, stddev: z,
		},
		{
			title:    "same as before, but sent>len(res)",
			results:  []time.Duration{ms},
			received: 3,
			last:     ms, best: ms, worst: ms, mean: ms, stddev: z,
		},
		{
			title:    "same as before, but sent<len(res)",
			results:  []time.Duration{ms, ms, 5 * ms},
			received: 2,
			last:     ms, best: ms, worst: ms, mean: ms, stddev: z,
		},
		{
			title:    "different numbers, manually calculated",
			results:  []time.Duration{ms, 2 * ms},
			received: 2,
			last:     2 * ms,
			best:     ms,
			worst:    2 * ms,
			mean:     1500 * µs,
			stddev:   500 * µs,
		},
		{
			title:    "wilder numbers",
			results:  []time.Duration{6 * ms, 2 * ms, 14 * ms, 11 * ms},
			received: 6,
			lost:     2,
			last:     2 * ms, // res[received%len]
			best:     2 * ms,
			worst:    14 * ms,
			mean:     8250 * µs, // (6000+2000+14000+11000)/4
			stddev:   4602988,   // 4602988.15988
			loss:     0.25,      // sent = 6+2
		},
		{
			title:    "verifying captured data",
			received: 50,
			lost:     7,
			loss:     0.1228, // 7 / 57

			last:   488619758,
			best:   451327200,
			worst:  492082650,
			mean:   487287379,
			stddev: 9356133,

			results: []time.Duration{
				478427841, 486727913, 489902185, 490369676, 489957386,
				490784152, 491390728, 491012043, 491313203, 489869560,
				488634310, 451590351, 480933928, 451431418, 491046095,
				492017348, 488906398, 490187284, 490733777, 490418928,
				490627269, 490710944, 491339118, 491300740, 490320794,
				489706066, 487735713, 488153523, 490988560, 490293234,
				492082650, 490784586, 488731408, 488008147, 487630508,
				490190288, 490712289, 489931645, 490608008, 490625639,
				491721463, 451327200, 491615584, 490238328, 489234608,
				488510694, 488807517, 489176334, 488981822, 488619758,
			},
		},
	}

	for i, tc := range testcases {
		h := history{received: tc.received, results: tc.results, lost: tc.lost}
		subject := h.compute()

		assert.Equal(tc.best, subject.best, "test case #%d (%s): best", i, tc.title)
		assert.Equal(tc.last, subject.last, "test case #%d (%s): last", i, tc.title)
		assert.Equal(tc.worst, subject.worst, "test case #%d (%s): worst", i, tc.title)
		assert.Equal(tc.mean, subject.mean, "test case #%d (%s): mean", i, tc.title)
		assert.Equal(tc.stddev, subject.stddev, "test case #%d (%s): stddev", i, tc.title)
		assert.Equal(tc.received+tc.lost, subject.pktSent, "test case #%d (%s): pktSent", i, tc.title)
		assert.InDelta(tc.loss, subject.pktLoss, 0.0001, "test case #%d (%s): pktLoss", i, tc.title)
	}
}


================================================
FILE: cmd/multiping/main.go
================================================
package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"time"

	ping "github.com/digineo/go-ping"
)

var opts = struct {
	timeout         time.Duration
	interval        time.Duration
	payloadSize     uint
	statBufferSize  uint
	bind4           string
	bind6           string
	dests           []*destination
	resolverTimeout time.Duration
}{
	timeout:         1000 * time.Millisecond,
	interval:        1000 * time.Millisecond,
	bind4:           "0.0.0.0",
	bind6:           "::",
	payloadSize:     56,
	statBufferSize:  50,
	resolverTimeout: 1500 * time.Millisecond,
}

var (
	pinger *ping.Pinger
	tui    *userInterface
)

func main() {
	flag.Usage = func() {
		fmt.Fprintln(os.Stderr, "Usage:", os.Args[0], "[options] host [host [...]]")
		flag.PrintDefaults()
	}

	flag.DurationVar(&opts.timeout, "timeout", opts.timeout, "timeout for a single echo request")
	flag.DurationVar(&opts.interval, "interval", opts.interval, "polling interval")
	flag.UintVar(&opts.payloadSize, "s", opts.payloadSize, "size of payload in bytes")
	flag.UintVar(&opts.statBufferSize, "buf", opts.statBufferSize, "buffer size for statistics")
	flag.StringVar(&opts.bind4, "bind4", opts.bind4, "IPv4 bind address")
	flag.StringVar(&opts.bind6, "bind6", opts.bind6, "IPv6 bind address")
	flag.DurationVar(&opts.resolverTimeout, "resolve", opts.resolverTimeout, "timeout for DNS lookups")
	flag.Parse()

	log.SetFlags(0)

	for _, host := range flag.Args() {
		remotes, err := resolve(host, opts.resolverTimeout)
		if err != nil {
			log.Printf("error resolving host %s: %v", host, err)
			continue
		}

		for _, remote := range remotes {
			if v4 := remote.IP.To4() != nil; v4 && opts.bind4 == "" || !v4 && opts.bind6 == "" {
				continue
			}

			ipaddr := remote // need to create a copy
			dst := destination{
				host:   host,
				remote: &ipaddr,
				history: &history{
					results: make([]time.Duration, opts.statBufferSize),
				},
			}

			opts.dests = append(opts.dests, &dst)
		}
	}

	if instance, err := ping.New(opts.bind4, opts.bind6); err == nil {
		if instance.PayloadSize() != uint16(opts.payloadSize) {
			instance.SetPayloadSize(uint16(opts.payloadSize))
		}
		pinger = instance
		defer pinger.Close()
	} else {
		panic(err)
	}

	go work()

	tui = buildTUI(opts.dests)
	go tui.update(time.Second)

	if err := tui.Run(); err != nil {
		panic(err)
	}
}

func work() {
	for {
		for i, u := range opts.dests {
			go func(u *destination, i int) {
				u.ping(pinger)
			}(u, i)
		}
		time.Sleep(opts.interval)
	}
}


================================================
FILE: cmd/multiping/resolve.go
================================================
package main

import (
	"context"
	"net"
	"strings"
	"time"
)

func resolve(addr string, timeout time.Duration) ([]net.IPAddr, error) {
	if strings.ContainsRune(addr, '%') {
		ipaddr, err := net.ResolveIPAddr("ip", addr)
		if err != nil {
			return nil, err
		}
		return []net.IPAddr{*ipaddr}, nil
	}

	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()

	return net.DefaultResolver.LookupIPAddr(ctx, addr)
}


================================================
FILE: cmd/multiping/ui.go
================================================
package main

import (
	"fmt"
	"log"
	"strconv"
	"time"

	"github.com/gdamore/tcell/v2"
	"github.com/rivo/tview"
)

type userInterface struct {
	app          *tview.Application
	grid         *tview.Grid
	table        *tview.Table
	destinations []*destination
}

var coldef = [...]struct {
	title   string
	align   int
	initVal func(*destination) string
	content func(*stat) string
}{
	{
		title:   "host",
		align:   tview.AlignLeft,
		initVal: func(d *destination) string { return d.host },
	},
	{
		title:   "address",
		align:   tview.AlignLeft,
		initVal: func(d *destination) string { return d.remote.IP.String() },
	},
	{
		title:   "sent",
		align:   tview.AlignRight,
		content: func(st *stat) string { return strconv.Itoa(st.pktSent) },
	},
	{
		title:   "loss",
		align:   tview.AlignRight,
		content: func(st *stat) string { return fmt.Sprintf("%0.1f%%", st.pktLoss*100) },
	},
	{
		title:   "last",
		align:   tview.AlignRight,
		content: func(st *stat) string { return ts(st.last) },
	},
	{
		title:   "best",
		align:   tview.AlignRight,
		content: func(st *stat) string { return ts(st.best) },
	},
	{
		title:   "worst",
		align:   tview.AlignRight,
		content: func(st *stat) string { return ts(st.worst) },
	},
	{
		title:   "mean",
		align:   tview.AlignRight,
		content: func(st *stat) string { return ts(st.mean) },
	},
	{
		title:   "stddev",
		align:   tview.AlignRight,
		content: func(st *stat) string { return ts(st.stddev) },
	},
}

func buildTUI(destinations []*destination) *userInterface {
	ui := &userInterface{
		app:          tview.NewApplication(),
		table:        tview.NewTable().SetBorders(false).SetFixed(2, 0),
		grid:         tview.NewGrid().SetRows(3, 0, 10).SetColumns(0),
		destinations: destinations,
	}

	title := tview.NewTextView().
		SetDynamicColors(true).
		SetTextAlign(tview.AlignCenter).
		SetText("[yellow]multiping[white]   press q to exit")

	logs := tview.NewTextView().
		SetDynamicColors(true).
		SetWrap(false)
	log.SetFlags(log.Ltime | log.LUTC)
	log.SetOutput(logs)

	ui.grid.AddItem(title, 0, 0, 1, 1, 0, 0, false)
	ui.grid.AddItem(ui.table, 1, 0, 1, 1, 0, 0, true)
	ui.grid.AddItem(logs, 2, 0, 1, 1, 0, 0, false)

	// setup controls
	ui.app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		switch event.Key() {
		case tcell.KeyEscape, tcell.KeyCtrlC:
			ui.app.Stop()
			return nil
		case tcell.KeyRune:
			if event.Rune() == 'q' {
				ui.app.Stop()
				return nil
			}
		}
		return event
	})

	// build header
	for col, def := range coldef {
		cell := tview.NewTableCell(def.title).SetAlign(def.align)
		if col == 2 {
			cell.SetExpansion(1)
		}
		ui.table.SetCell(0, col, cell)
	}

	// prepare data list
	for r, dst := range destinations {
		for c, def := range coldef {
			var cell *tview.TableCell
			if def.initVal != nil {
				cell = tview.NewTableCell(def.initVal(dst))
			} else {
				cell = tview.NewTableCell("n/a")
			}
			ui.table.SetCell(r+2, c, cell.SetAlign(def.align))
		}
	}

	return ui
}

func (ui *userInterface) Run() error {
	ui.app.SetRoot(ui.grid, true).SetFocus(ui.table)
	return ui.app.Run()
}

func (ui *userInterface) update(interval time.Duration) {
	time.Sleep(interval)
	for {
		for i, u := range ui.destinations {
			stats := u.compute()
			r := i + 2

			for col, def := range coldef {
				if def.content != nil {
					ui.table.GetCell(r, col).SetText(def.content(&stats))
				}
			}
		}
		ui.app.Draw()
		time.Sleep(interval)
	}
}

const tsDividend = float64(time.Millisecond) / float64(time.Nanosecond)

func ts(dur time.Duration) string {
	if 10*time.Microsecond < dur && dur < time.Second {
		return fmt.Sprintf("%0.2f", float64(dur.Nanoseconds())/tsDividend)
	}
	return dur.String()
}


================================================
FILE: cmd/ping-monitor/Makefile
================================================
TARGET = ping-monitor

include ../common.mk


================================================
FILE: cmd/ping-monitor/main.go
================================================
package main

import (
	"flag"
	"fmt"
	"net"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/digineo/go-ping"
	"github.com/digineo/go-ping/monitor"
)

var (
	pingInterval        = 5 * time.Second
	pingTimeout         = 4 * time.Second
	reportInterval      = 60 * time.Second
	size           uint = 56
	pinger         *ping.Pinger
	targets        []string
)

func main() {
	flag.Usage = func() {
		fmt.Fprintln(os.Stderr, "Usage:", os.Args[0], "[options] host [host [...]]")
		flag.PrintDefaults()
	}

	flag.DurationVar(&pingInterval, "pingInterval", pingInterval, "interval for ICMP echo requests")
	flag.DurationVar(&pingTimeout, "pingTimeout", pingTimeout, "timeout for ICMP echo request")
	flag.DurationVar(&reportInterval, "reportInterval", reportInterval, "interval for reports")
	flag.UintVar(&size, "size", size, "size of additional payload data")
	flag.Parse()

	if n := flag.NArg(); n == 0 {
		// Targets empty?
		flag.Usage()
		os.Exit(1)
	} else if n > int(^byte(0)) {
		// Too many targets?
		fmt.Println("Too many targets")
		os.Exit(1)
	}

	// Bind to sockets
	if p, err := ping.New("0.0.0.0", "::"); err != nil {
		fmt.Printf("Unable to bind: %s\nRunning as root?\n", err)
		os.Exit(2)
	} else {
		pinger = p
	}
	pinger.SetPayloadSize(uint16(size))
	defer pinger.Close()

	// Create monitor
	monitor := monitor.New(pinger, pingInterval, pingTimeout)
	defer monitor.Stop()

	// Add targets
	targets = flag.Args()
	for i, target := range targets {
		ipAddr, err := net.ResolveIPAddr("", target)
		if err != nil {
			fmt.Printf("invalid target '%s': %s", target, err)
			continue
		}
		monitor.AddTargetDelayed(string([]byte{byte(i)}), *ipAddr, 10*time.Millisecond*time.Duration(i))
	}

	// Start report routine
	ticker := time.NewTicker(reportInterval)
	defer ticker.Stop()
	go func() {
		for range ticker.C {
			for i, metrics := range monitor.ExportAndClear() {
				fmt.Printf("%s: %+v\n", targets[[]byte(i)[0]], *metrics)
			}
		}
	}()

	// Handle SIGINT and SIGTERM.
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
	fmt.Println("received", <-ch)
}


================================================
FILE: cmd/ping-test/Makefile
================================================
TARGET = ping-test

include ../common.mk

.PHONY: test
test: all
	@truncate -s0 $(TARGET).log
	@./$(TARGET) -4 golang.org      2>>$(TARGET).log
	@./$(TARGET) -6 golang.org      2>>$(TARGET).log
	@./$(TARGET) -4 cloudflare.com  2>>$(TARGET).log
	@./$(TARGET) -6 cloudflare.com  2>>$(TARGET).log

	@echo error log:
	@cat $(TARGET).log


================================================
FILE: cmd/ping-test/README.md
================================================
# ping-test

This is a sample program to demonstrate the usage of this library.
It is not intended for production use.

## How-to

**Building/installing** does not require any special steps. Either run
`go get` (to install it directly in `$GOPATH/bin/ping-test`)

```
$ go get -u github.com/digineo/go-ping/cmd/ping-test
```

or skip installing it (and build it yourself):

```
$ go get -u -d github.com/digineo/go-ping
$ cd $GOPATH/src/github.com/digineo/go-ping/cmd/ping-test
$ go build    # this creates ./ping-test
```

**Running** `ping-test` requires elevated privileges, since normal users
cannot open ICMP sockets.

To circumvent this, either run the binary as root, e.g. via `sudo`
 *(not recommended!)*

```
$ sudo ./ping-test -4 golang.org
ping golang.org (216.58.211.113) rtt=11.869403ms
$ sudo ./ping-test -6 golang.org
ping golang.org (2a00:1450:400e:809::2011) rtt=11.412907ms
```

Better yet, allow the binary to only open raw sockets (via `capabilities(7)`):

```
$ sudo setcap cap_net_raw+ep ./ping-test
$ ./ping-test -4 golang.org
ping golang.org (216.58.211.113) rtt=11.772573ms
$ ./ping-test -6 golang.org
ping golang.org (2a00:1450:400e:809::2011) rtt=11.31439ms
```

Note, that you'll need to re-apply the `setcap` command everytime the
binary changes (i.e. after `go build`).

Also, since configuring the system capabilities is a Linux feature, you
may need to resort to Docker or VM environments, if you want to try
this binary, but don't trust its source code. Or you like living in the
danger zone and don't mind the occasional system crash introduced by
running code found on the internet with root privileges :-)


================================================
FILE: cmd/ping-test/main.go
================================================
package main

import (
	"flag"
	"fmt"
	"log"
	"net"
	"os"
	"time"

	ping "github.com/digineo/go-ping"
)

var (
	args           []string
	attempts       uint = 3
	timeout             = time.Second
	proto4, proto6 bool
	size           uint = 56
	bind           string

	destination string
	remoteAddr  *net.IPAddr
	pinger      *ping.Pinger
)

func main() {
	flag.Usage = func() {
		fmt.Fprintln(os.Stderr, "Usage:", os.Args[0], "[options] host [host [...]]")
		flag.PrintDefaults()
	}

	flag.UintVar(&attempts, "attempts", attempts, "number of attempts")
	flag.DurationVar(&timeout, "timeout", timeout, "timeout for a single echo request")
	flag.UintVar(&size, "s", size, "size of additional payload data")
	flag.BoolVar(&proto4, "4", proto4, "use IPv4 (mutually exclusive with -6)")
	flag.BoolVar(&proto6, "6", proto6, "use IPv6 (mutually exclusive with -4)")
	flag.StringVar(&bind, "bind", "", "IPv4 or IPv6 bind address (defaults to 0.0.0.0 for IPv4 and :: for IPv6)")
	flag.Parse()

	if proto4 == proto6 {
		log.Fatalf("need exactly one of -4 and -6 flags")
	}

	if bind == "" {
		if proto4 {
			bind = "0.0.0.0"
		} else if proto6 {
			bind = "::"
		}
	}

	args := flag.Args()
	destination := args[0]

	if proto4 {
		if r, err := net.ResolveIPAddr("ip4", destination); err != nil {
			panic(err)
		} else {
			remoteAddr = r
		}

		if p, err := ping.New(bind, ""); err != nil {
			panic(err)
		} else {
			pinger = p
		}
	} else if proto6 {
		if r, err := net.ResolveIPAddr("ip6", destination); err != nil {
			panic(err)
		} else {
			remoteAddr = r
		}

		if p, err := ping.New("", bind); err != nil {
			panic(err)
		} else {
			pinger = p
		}
	}
	defer pinger.Close()

	if pinger.PayloadSize() != uint16(size) {
		pinger.SetPayloadSize(uint16(size))
	}

	if remoteAddr.IP.IsLinkLocalMulticast() {
		multicastPing()
	} else {
		unicastPing()
	}
}

func unicastPing() {
	rtt, err := pinger.PingAttempts(remoteAddr, timeout, int(attempts))

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	fmt.Printf("ping %s (%s) rtt=%v\n", destination, remoteAddr, rtt)
}

func multicastPing() {
	fmt.Printf("multicast ping to %s (%s)\n", args[0], destination)

	responses, err := pinger.PingMulticast(remoteAddr, timeout)

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	for response := range responses {
		fmt.Printf("%+v\n", response)
	}
}


================================================
FILE: cmd/pingnet/Makefile
================================================
TARGET = pingnet

include ../common.mk

.PHONY: test
test: all
	./$(TARGET) -c 1 -w 100ms -f 127.0.0.1/24


================================================
FILE: cmd/pingnet/main.go
================================================
package main

import (
	"flag"
	"fmt"
	"log"
	"net"
	"os"
	"runtime"
	"strings"
	"sync"
	"time"

	ping "github.com/digineo/go-ping"
	"gopkg.in/cheggaaa/pb.v1"
)

var (
	timeout  = 5 * time.Second
	attempts = 3
	poolSize = 2 * runtime.NumCPU()
	interval = 100 * time.Millisecond
	ifname   = ""
	bind6    = "::"
	bind4    = "0.0.0.0"
	size     = uint(56)
	force    bool
	verbose  bool
	mark     uint
	pinger   *ping.Pinger
)

type workGenerator struct {
	ip  net.IP
	net *net.IPNet
}

func (w *workGenerator) size() uint64 {
	ones, bits := w.net.Mask.Size()
	return 1 << uint64(bits-ones)
}

func (w *workGenerator) each(callback func(net.IP) error) error {
	// adapted from http://play.golang.org/p/m8TNTtygK0
	inc := func(ip net.IP) net.IP {
		res := make(net.IP, len(ip))
		copy(res, ip)
		for j := len(res) - 1; j >= 0; j-- {
			res[j]++
			if res[j] > 0 {
				break
			}
		}
		return res
	}
	for ip := w.ip.Mask(w.net.Mask); w.net.Contains(ip); ip = inc(ip) {
		if err := callback(ip); err != nil {
			return err
		}
	}
	return nil
}

type result struct {
	addr net.IPAddr
	rtt  time.Duration
	err  error
}

func main() {
	log.SetFlags(0)

	flag.Usage = func() {
		fmt.Fprintln(os.Stderr, "Usage:", os.Args[0], "[options] CIDR [CIDR [...]]")
		flag.PrintDefaults()
	}

	flag.IntVar(&attempts, "c", attempts, "number of ping attempts per address")
	flag.DurationVar(&timeout, "w", timeout, "timeout for a single echo request")
	flag.DurationVar(&interval, "i", interval, "CIDR iteration interval")
	flag.UintVar(&size, "s", size, "size of additional payload data")
	flag.StringVar(&bind4, "4", bind4, "IPv4 bind address")
	flag.StringVar(&bind6, "6", bind6, "IPv6 bind address")
	flag.StringVar(&ifname, "I", ifname, "interface name/IPv6 zone")
	flag.IntVar(&poolSize, "P", poolSize, "concurrency level")
	flag.BoolVar(&force, "f", force, "sanity flag needed if you want to ping more than 4096 hosts (/20)")
	flag.BoolVar(&verbose, "v", verbose, "also print out unreachable addresses")
	flag.UintVar(&mark, "m", mark, "set socket mark (SO_MARK) to this value")
	flag.Parse()

	// simple error checking
	if bind4 == "" && bind6 == "" {
		log.Fatal("need at least an IPv4 (-bind4 flag) or IPv6 (-bind6 flag) address to bind to")
	}
	if poolSize <= 0 {
		log.Fatal("concurrency level (-P flag) must be > 0")
	}
	if attempts <= 0 {
		log.Fatal("number of ping attempts (-c flag) must be > 0")
	}

	// parse CIDR arguments
	total := uint64(0)
	generator := make([]*workGenerator, 0, flag.NArg())
	for _, cidr := range flag.Args() {
		ip, ipnet, err := net.ParseCIDR(cidr)
		if err != nil {
			log.Println(err)
			continue
		}
		w := &workGenerator{ip: ip, net: ipnet}
		generator = append(generator, w)
		total += w.size()
	}

	if total == 0 {
		// no (valid) CIDR argument given
		flag.Usage()
		os.Exit(1)
	} else if total > 4096 && !force {
		// expanding all arguments yields too many addresses
		log.Printf("You want to ping %d hosts. If that is correct, try again with -f flag", total)
		os.Exit(1)
	}

	if p, err := ping.New(bind4, bind6); err != nil {
		log.Fatal(err)
	} else {
		pinger = p
	}

	if mark > 0 {
		pinger.SetMark(mark)
	}

	// prepare worker
	wg := &sync.WaitGroup{}
	wg.Add(poolSize)
	ips := make(chan net.IPAddr, poolSize)
	res := make(chan *result, poolSize)

	for i := 0; i < poolSize; i++ {
		go func() {
			for ip := range ips {
				var err error
				var rtt time.Duration
				for i := 1; ; i++ {
					rtt, err = pinger.PingAttempts(&ip, timeout, attempts)
					if err == nil || !strings.Contains(err.Error(), "no buffer space available") {
						break
					}
					time.Sleep(timeout * time.Duration(i))
				}

				res <- &result{addr: ip, rtt: rtt, err: err}
			}
			wg.Done()
		}()
	}

	// printer
	pr := &sync.WaitGroup{}
	pr.Add(1)
	go func() {
		bar := pb.New64(int64(total))
		bar.ShowBar = true
		bar.ShowTimeLeft = true
		bar.ShowCounters = true
		bar.Start()
		const clear = "\x1b[2K\r" // ansi delete line + CR

		for r := range res {
			bar.Increment()
			if r.err == nil {
				log.Printf("%s%s - rtt=%v", clear, r.addr.IP, r.rtt)
				bar.Update()
			} else if verbose {
				log.Printf("%s%s - %v", clear, r.addr, r.err)
				bar.Update()
			}
		}

		bar.Finish()
		pr.Done()
	}()

	// yield all IP addresses
	for _, g := range generator {
		g.each(func(ip net.IP) error {
			ips <- net.IPAddr{IP: ip, Zone: ifname}
			time.Sleep(interval)
			return nil
		})
	}

	// wait for worker and printer to finish
	close(ips)
	wg.Wait()
	close(res)
	pr.Wait()
}


================================================
FILE: error.go
================================================
package ping

import "errors"

var (
	errClosed   = errors.New("pinger closed")
	errNotBound = errors.New("need at least one bind address")
)

// timeoutError implements the net.Error interface. Originally taken from
// https://github.com/golang/go/blob/release-branch.go1.8/src/net/net.go#L505-L509
type timeoutError struct{}

func (e *timeoutError) Error() string   { return "i/o timeout" }
func (e *timeoutError) Timeout() bool   { return true }
func (e *timeoutError) Temporary() bool { return true }


================================================
FILE: go.mod
================================================
module github.com/digineo/go-ping

go 1.23.0

require (
	github.com/digineo/go-logwrap v0.0.0-20181106161722-a178c58ea3f0
	github.com/gdamore/tcell/v2 v2.9.0
	github.com/rivo/tview v0.0.0-20250625164341-a4a78f1e05cb
	github.com/stretchr/testify v1.11.0
	golang.org/x/net v0.43.0
	gopkg.in/cheggaaa/pb.v1 v1.0.28
)

require (
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/fatih/color v1.7.0 // indirect
	github.com/gdamore/encoding v1.0.1 // indirect
	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
	github.com/mattn/go-colorable v0.1.0 // indirect
	github.com/mattn/go-isatty v0.0.20 // indirect
	github.com/mattn/go-runewidth v0.0.16 // indirect
	github.com/pmezard/go-difflib v1.0.0 // indirect
	github.com/rivo/uniseg v0.4.7 // indirect
	golang.org/x/sys v0.35.0 // indirect
	golang.org/x/term v0.34.0 // indirect
	golang.org/x/text v0.28.0 // indirect
	gopkg.in/yaml.v3 v3.0.1 // indirect
)


================================================
FILE: go.sum
================================================
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/digineo/go-logwrap v0.0.0-20181106161722-a178c58ea3f0 h1:OT/LKmj81wMymnWXaKaKBR9n1vPlu+GC0VVKaZP6kzs=
github.com/digineo/go-logwrap v0.0.0-20181106161722-a178c58ea3f0/go.mod h1:DmqdumeAKGQNU5E8MN0ruT5ZGx8l/WbAsMbXCXcSEts=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
github.com/gdamore/tcell/v2 v2.9.0 h1:N6t+eqK7/xwtRPwxzs1PXeRWnm0H9l02CrgJ7DLn1ys=
github.com/gdamore/tcell/v2 v2.9.0/go.mod h1:8/ZoqM9rxzYphT9tH/9LnunhV9oPBqwS8WHGYm5nrmo=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-colorable v0.1.0 h1:v2XXALHHh6zHfYTJ+cSkwtyffnaOyR1MXaA91mTrb8o=
github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/tview v0.0.0-20250625164341-a4a78f1e05cb h1:n7UJ8X9UnrTZBYXnd1kAIBc067SWyuPIrsocjketYW8=
github.com/rivo/tview v0.0.0-20250625164341-a4a78f1e05cb/go.mod h1:cSfIYfhpSGCjp3r/ECJb+GKS7cGJnqV8vfjQPwoXyfY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk=
gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: monitor/history.go
================================================
package monitor

import (
	"math"
	"sort"
	"sync"
	"time"
)

// Result stores the information about a single ping, in particular
// the round-trip time or whether the packet was lost.
type Result struct {
	RTT  time.Duration
	Lost bool
}

// History represents the ping history for a single node/device.
type History struct {
	results  []Result
	count    int
	position int
	sync.RWMutex
}

// NewHistory creates a new History object with a specific capacity
func NewHistory(capacity int) History {
	return History{
		results: make([]Result, capacity),
	}
}

// AddResult saves a ping result into the internal history.
func (h *History) AddResult(rtt time.Duration, err error) {
	h.Lock()

	h.results[h.position] = Result{RTT: rtt, Lost: err != nil}
	h.position = (h.position + 1) % cap(h.results)

	if h.count < cap(h.results) {
		h.count++
	}

	h.Unlock()
}

func (h *History) clear() {
	h.count = 0
	h.position = 0
}

// ComputeAndClear aggregates the result history into a single data point and clears the result set.
func (h *History) ComputeAndClear() *Metrics {
	h.Lock()
	result := h.compute()
	h.clear()
	h.Unlock()
	return result
}

// Compute aggregates the result history into a single data point.
func (h *History) Compute() *Metrics {
	h.RLock()
	defer h.RUnlock()
	return h.compute()
}

func (h *History) compute() *Metrics {
	numFailure := 0
	numTotal := h.count
	µsPerMs := 1.0 / float64(time.Millisecond)

	if numTotal == 0 {
		return nil
	}

	data := make([]float64, 0, numTotal)
	var best, worst, mean, stddev, total, sumSquares float64
	var extremeFound bool

	for i := 0; i < numTotal; i++ {
		curr := &h.results[i]
		if curr.Lost {
			numFailure++
		} else {
			rtt := float64(curr.RTT) * µsPerMs
			data = append(data, rtt)

			if !extremeFound || rtt < best {
				best = rtt
			}
			if !extremeFound || rtt > worst {
				worst = rtt
			}

			extremeFound = true
			total += rtt
		}
	}

	size := float64(numTotal - numFailure)
	mean = total / size
	for _, rtt := range data {
		diff := rtt - mean
		sumSquares += diff * diff
	}
	stddev = math.Sqrt(sumSquares / size)

	median := math.NaN()
	if l := len(data); l > 0 {
		sort.Float64Slice(data).Sort()
		if l%2 == 0 {
			median = (data[l/2-1] + data[l/2]) / 2
		} else {
			median = data[l/2]
		}
	}

	return &Metrics{
		PacketsSent: numTotal,
		PacketsLost: numFailure,
		Best:        float32(best),
		Worst:       float32(worst),
		Median:      float32(median),
		Mean:        float32(mean),
		StdDev:      float32(stddev),
	}
}


================================================
FILE: monitor/history_test.go
================================================
package monitor

import (
	"fmt"
	"math"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func BenchmarkAddResult(b *testing.B) {
	h := NewHistory(8)
	for i := 0; i < b.N; i++ {
		h.AddResult(time.Duration(i), nil) // 1 allocc
	}
}

func BenchmarkCompute(b *testing.B) {
	h := NewHistory(8)
	for i := 0; i < b.N; i++ {
		h.AddResult(time.Duration(i), nil) // 1 alloc
		h.Compute()                        // 2 allocs
	}
}

func TestCompute(t *testing.T) {
	assert := assert.New(t)
	const dur = 100 * time.Millisecond
	err := fmt.Errorf("i/o timeout")

	{ // empty list
		h := NewHistory(4)
		assert.Nil(h.Compute())
	}

	{ // one failed entry
		h := NewHistory(4)
		h.AddResult(2, err)

		metrics := h.Compute()
		assert.EqualValues(1, metrics.PacketsSent)
		assert.EqualValues(1, metrics.PacketsLost)
		assert.EqualValues(0, metrics.Best)
		assert.EqualValues(0, metrics.Worst)
		assert.True(math.IsNaN(float64(metrics.Median)))
		assert.True(math.IsNaN(float64(metrics.Mean)))
		assert.True(math.IsNaN(float64(metrics.StdDev)))
	}

	{ // populate with 5 entries
		h := NewHistory(8)
		h.AddResult(0, nil)
		h.AddResult(dur, nil)
		h.AddResult(dur, nil)
		h.AddResult(0, err)
		h.AddResult(dur, nil)

		assert.Equal(h.count, 5)
		assert.EqualValues(1, h.Compute().PacketsLost)
	}

	{ // test median
		h := NewHistory(5)
		h.AddResult(3*dur, nil)
		h.AddResult(2*dur, nil)
		h.AddResult(1*dur, nil)
		h.AddResult(0*dur, nil)
		assert.EqualValues(150, h.Compute().Median)

		h.AddResult(4*dur, nil)
		assert.EqualValues(200, h.Compute().Median)
	}

	{
		// test zero variance
		h := NewHistory(8)
		h.AddResult(dur, nil)
		h.AddResult(dur, nil)
		h.AddResult(0, err)

		metrics := h.Compute()
		assert.EqualValues(100, metrics.Best)
		assert.EqualValues(100, metrics.Worst)
		assert.EqualValues(100, metrics.Mean)
		assert.EqualValues(100, metrics.Median)
		assert.EqualValues(0, metrics.StdDev)
		assert.EqualValues(3, metrics.PacketsSent)
		assert.EqualValues(1, metrics.PacketsLost)

		// results getting worse
		h.AddResult(2*dur, nil)
		h.AddResult(dur, nil)
		h.AddResult(0, err)

		metrics = h.Compute()
		assert.EqualValues(100, metrics.Best)
		assert.EqualValues(200, metrics.Worst)
		assert.EqualValues(125, metrics.Mean)
		assert.EqualValues(100, metrics.Median)
		assert.InDelta(43.30127, float64(metrics.StdDev), 0.000001)
		assert.EqualValues(6, metrics.PacketsSent)
		assert.EqualValues(2, metrics.PacketsLost)

		// finally something better
		h.AddResult(0, nil)
		metrics = h.Compute()
		assert.EqualValues(0, metrics.Best)
		assert.EqualValues(200, metrics.Worst)
		assert.EqualValues(100, metrics.Mean)
		assert.EqualValues(100, metrics.Median)
		assert.InDelta(63.2455, float64(metrics.StdDev), 0.0001)
		assert.EqualValues(7, metrics.PacketsSent)
		assert.EqualValues(2, metrics.PacketsLost)
	}
}

func TestHistoryCapacity(t *testing.T) {
	assert := assert.New(t)
	err := fmt.Errorf("i/o timeout")

	h := NewHistory(3)
	assert.Equal(h.count, 0)
	h.AddResult(1, nil)
	h.AddResult(2, err)
	assert.Equal(h.count, 2)
	assert.Equal(h.position, 2)
	h.AddResult(1, nil)
	assert.Equal(h.count, 3)
	assert.Equal(h.position, 0)

	h.AddResult(0, nil)
	assert.Equal(h.count, 3)
	assert.Equal(h.position, 1)
	assert.EqualValues(1, h.Compute().PacketsLost)

	// overwrite lost packet result
	h.AddResult(0, nil)
	assert.EqualValues(0, h.Compute().PacketsLost)

	// clear
	h.ComputeAndClear()
	assert.Equal(h.count, 0)
	assert.Equal(h.position, 0)
}


================================================
FILE: monitor/metrics.go
================================================
package monitor

// Metrics is a dumb data point computed from a history of Results.
type Metrics struct {
	PacketsSent int     // number of packets sent
	PacketsLost int     // number of packets lost
	Best        float32 // best rtt in ms
	Worst       float32 // worst rtt in ms
	Median      float32 // median rtt in ms
	Mean        float32 // mean rtt in ms
	StdDev      float32 // std deviation in ms
}


================================================
FILE: monitor/monitor.go
================================================
package monitor

import (
	"net"
	"sync"
	"time"

	"github.com/digineo/go-ping"
)

// Monitor manages the goroutines responsible for collecting Ping RTT data.
type Monitor struct {
	HistorySize int // Number of results per target to keep

	pinger   *ping.Pinger
	interval time.Duration
	targets  map[string]*Target
	mtx      sync.RWMutex
	timeout  time.Duration
}

const defaultHistorySize = 10

// New creates and configures a new Ping instance. You need to call
// AddTarget()/RemoveTarget() to manage monitored targets.
func New(pinger *ping.Pinger, interval, timeout time.Duration) *Monitor {
	return &Monitor{
		pinger:      pinger,
		interval:    interval,
		timeout:     timeout,
		targets:     make(map[string]*Target),
		HistorySize: defaultHistorySize,
	}
}

// Stop brings the monitoring gracefully to a halt.
func (p *Monitor) Stop() {
	p.mtx.Lock()
	for id := range p.targets {
		p.removeTarget(id)
	}
	p.pinger.Close()
	p.mtx.Unlock()
}

// AddTarget adds a target to the monitored list. If the target with the given
// ID already exists, it is removed first and then readded. This allows
// the easy restart of the monitoring.
func (p *Monitor) AddTarget(key string, addr net.IPAddr) (err error) {
	return p.AddTargetDelayed(key, addr, 0)
}

// AddTargetDelayed is AddTarget with a startup delay
func (p *Monitor) AddTargetDelayed(key string, addr net.IPAddr, startupDelay time.Duration) (err error) {
	p.mtx.Lock()
	defer p.mtx.Unlock()

	target, err := newTarget(p.interval, p.timeout, startupDelay, p.HistorySize, p.pinger, addr)
	if err != nil {
		return err
	}
	p.removeTarget(key)
	p.targets[key] = target
	return
}

// RemoveTarget removes a target from the monitoring list.
func (p *Monitor) RemoveTarget(key string) {
	p.mtx.Lock()
	p.removeTarget(key)
	p.mtx.Unlock()
}

// Stops monitoring a target and removes it from the list (if the list includes
// the target). Needs to be locked externally!
func (p *Monitor) removeTarget(key string) {
	target, found := p.targets[key]
	if !found {
		return
	}
	target.Stop()
	delete(p.targets, key)
}

// ExportAndClear calculates the metrics for each monitored target, cleans the result set and
// returns it as a simple map.
func (p *Monitor) ExportAndClear() map[string]*Metrics {
	return p.export(true)
}

// Export calculates the metrics for each monitored target and returns it as a simple map.
func (p *Monitor) Export() map[string]*Metrics {
	return p.export(false)
}

func (p *Monitor) export(clear bool) map[string]*Metrics {
	m := make(map[string]*Metrics)

	p.mtx.RLock()
	defer p.mtx.RUnlock()

	for id, target := range p.targets {
		if metrics := target.Compute(clear); metrics != nil {
			m[id] = metrics
		}
	}
	return m
}


================================================
FILE: monitor/target.go
================================================
package monitor

import (
	"net"
	"sync"
	"time"

	ping "github.com/digineo/go-ping"
)

// Target is a unit of work
type Target struct {
	pinger   *ping.Pinger
	addr     net.IPAddr
	interval time.Duration
	timeout  time.Duration
	stop     chan struct{}
	history  History
	wg       sync.WaitGroup
}

// newTarget starts a new monitoring goroutine
func newTarget(interval, timeout, startupDelay time.Duration, historySize int, pinger *ping.Pinger, addr net.IPAddr) (*Target, error) {
	n := &Target{
		pinger:   pinger,
		addr:     addr,
		interval: interval,
		timeout:  timeout,
		stop:     make(chan struct{}),
		history:  NewHistory(historySize),
	}
	n.wg.Add(1)
	go n.run(startupDelay)
	return n, nil
}

func (n *Target) run(startupDelay time.Duration) {
	if startupDelay > 0 {
		select {
		case <-time.After(startupDelay):
		case <-n.stop:
		}
	}

	tick := time.NewTicker(n.interval)
	for {
		select {
		case <-n.stop:
			tick.Stop()
			n.wg.Done()
			return
		case <-tick.C:
			go n.ping()
		}
	}
}

// Stop gracefully stops the monitoring.
func (n *Target) Stop() {
	close(n.stop)
	n.wg.Wait()
}

// Compute returns the computed ping metrics for this node and optonally clears the result set.
func (n *Target) Compute(clear bool) *Metrics {
	if clear {
		return n.history.ComputeAndClear()
	}
	return n.history.Compute()
}

func (n *Target) ping() {
	n.history.AddResult(n.pinger.Ping(&n.addr, n.timeout))
}


================================================
FILE: payload.go
================================================
package ping

import (
	"math/rand"
	"time"

	"github.com/digineo/go-logwrap"
)

var (
	log = &logwrap.Instance{}

	// SetLogger allows updating the Logger. For details, see
	// "github.com/digineo/go-logwrap".Instance.SetLogger.
	SetLogger = log.SetLogger

	// SA1019: rand.Seed has been deprecated, provide package-local RNG
	rng = rand.New(rand.NewSource(time.Now().UnixNano()))
)

// Payload represents additional data appended to outgoing ICMP Echo
// Requests.
type Payload []byte

// Resize will assign a new payload of the given size to p.
func (p *Payload) Resize(size uint16) {
	buf := make([]byte, size)
	if _, err := rng.Read(buf); err != nil {
		log.Errorf("error resizing payload: %v", err)
		return
	}
	*p = Payload(buf)
}


================================================
FILE: pinger.go
================================================
package ping

import (
	"net"
	"os"
	"sync"

	"golang.org/x/net/icmp"
)

const (
	// ProtocolICMP is the number of the Internet Control Message Protocol
	// (see golang.org/x/net/internal/iana.ProtocolICMP)
	ProtocolICMP = 1

	// ProtocolICMPv6 is the IPv6 Next Header value for ICMPv6
	// see golang.org/x/net/internal/iana.ProtocolIPv6ICMP
	ProtocolICMPv6 = 58
)

// default sequence counter for this process
var sequence uint32

// Pinger is a instance for ICMP echo requests
type Pinger struct {
	LogUnexpectedPackets bool // increases log verbosity
	Id                   uint16
	SequenceCounter      *uint32

	payload   Payload
	payloadMu sync.RWMutex

	requests map[uint32]request // currently running requests
	mtx      sync.RWMutex       // lock for the requests map
	conn4    net.PacketConn
	conn6    net.PacketConn
	write4   sync.Mutex // lock for conn4.WriteTo
	write6   sync.Mutex // lock for conn6.WriteTo
	wg       sync.WaitGroup
}

// New creates a new Pinger. This will open the raw socket and start the
// receiving logic. You'll need to call Close() to cleanup.
func New(bind4, bind6 string) (*Pinger, error) {
	// open sockets
	conn4, err := connectICMP("ip4:icmp", bind4)
	if err != nil {
		return nil, err
	}

	conn6, err := connectICMP("ip6:ipv6-icmp", bind6)
	if err != nil {
		if conn4 != nil {
			conn4.Close()
		}
		return nil, err
	}

	if conn4 == nil && conn6 == nil {
		return nil, errNotBound
	}

	pinger := Pinger{
		conn4:           conn4,
		conn6:           conn6,
		Id:              uint16(os.Getpid()),
		SequenceCounter: &sequence,
		requests:        make(map[uint32]request),
	}
	pinger.SetPayloadSize(56)

	if conn4 != nil {
		pinger.wg.Add(1)
		go pinger.receiver(ProtocolICMP, pinger.conn4)
	}
	if conn6 != nil {
		pinger.wg.Add(1)
		go pinger.receiver(ProtocolICMPv6, pinger.conn6)
	}

	return &pinger, nil
}

// Close will close the ICMP socket.
func (pinger *Pinger) Close() {
	pinger.close(pinger.conn4)
	pinger.close(pinger.conn6)
	pinger.wg.Wait()
}

// connectICMP opens a new ICMP connection, if network and address are not empty.
func connectICMP(network, address string) (*icmp.PacketConn, error) {
	if network == "" || address == "" {
		return nil, nil
	}

	return icmp.ListenPacket(network, address)
}

func (pinger *Pinger) close(conn net.PacketConn) {
	if conn != nil {
		conn.Close()
	}
}

func (pinger *Pinger) removeRequest(idseq uint32) {
	pinger.mtx.Lock()
	delete(pinger.requests, idseq)
	pinger.mtx.Unlock()
}

// SetPayloadSize resizes additional payload data to the given size. The
// payload will subsequently be appended to outgoing ICMP Echo Requests.
//
// The default payload size is 56, resulting in 64 bytes for the ICMP packet.
func (pinger *Pinger) SetPayloadSize(size uint16) {
	pinger.payloadMu.Lock()
	pinger.payload.Resize(size)
	pinger.payloadMu.Unlock()
}

// SetPayload allows you to overwrite the current payload with your own data.
func (pinger *Pinger) SetPayload(data []byte) {
	pinger.payloadMu.Lock()
	pinger.payload = Payload(data)
	pinger.payloadMu.Unlock()
}

// PayloadSize retrieves the current payload size.
func (pinger *Pinger) PayloadSize() uint16 {
	pinger.payloadMu.RLock()
	defer pinger.payloadMu.RUnlock()
	return uint16(len(pinger.payload))
}


================================================
FILE: pinger_linux.go
================================================
package ping

import (
	"errors"
	"os"
	"reflect"
	"syscall"

	"golang.org/x/net/icmp"
)

// getFD gets the system file descriptor for an icmp.PacketConn
func getFD(c *icmp.PacketConn) (uintptr, error) {
	v := reflect.ValueOf(c).Elem().FieldByName("c").Elem()
	if v.Elem().Kind() != reflect.Struct {
		return 0, errors.New("invalid type")
	}

	fd := v.Elem().FieldByName("conn").FieldByName("fd")
	if fd.Elem().Kind() != reflect.Struct {
		return 0, errors.New("invalid type")
	}

	pfd := fd.Elem().FieldByName("pfd")
	if pfd.Kind() != reflect.Struct {
		return 0, errors.New("invalid type")
	}

	return uintptr(pfd.FieldByName("Sysfd").Int()), nil
}

func (pinger *Pinger) SetMark(mark uint) error {
	conn4, ok := pinger.conn4.(*icmp.PacketConn)
	if !ok {
		return errors.New("invalid connection type")
	}

	fd, err := getFD(conn4)
	if err != nil {
		return err
	}

	err = os.NewSyscallError(
		"setsockopt",
		syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, int(mark)),
	)

	if err != nil {
		return err
	}

	conn6, ok := pinger.conn6.(*icmp.PacketConn)
	if !ok {
		return errors.New("invalid connection type")
	}

	fd, err = getFD(conn6)
	if err != nil {
		return err
	}

	return os.NewSyscallError(
		"setsockopt",
		syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, int(mark)),
	)
}


================================================
FILE: pinger_other.go
================================================
//go:build !linux

package ping

import "errors"

func (pinger *Pinger) SetMark(mark uint) error {
	return errors.New("setting SO_MARK socket option is not supported on this platform")
}


================================================
FILE: pinger_test.go
================================================
package ping

import (
	"net"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestPinger(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)

	pinger, err := New("0.0.0.0", "::")
	require.NoError(err)
	require.NotNil(pinger)
	defer pinger.Close()

	for _, target := range []string{"127.0.0.1", "::1"} {
		rtt, err := pinger.PingAttempts(&net.IPAddr{IP: net.ParseIP(target)}, time.Second, 2)
		assert.NoError(err, target)
		assert.NotZero(rtt, target)
	}
}


================================================
FILE: receiving.go
================================================
package ping

import (
	"fmt"
	"net"
	"time"

	"golang.org/x/net/icmp"
	"golang.org/x/net/ipv4"
	"golang.org/x/net/ipv6"
)

// receiver listens on the raw socket and correlates ICMP Echo Replys with
// currently running requests.
func (pinger *Pinger) receiver(proto int, conn net.PacketConn) {
	rb := make([]byte, 1500)

	// read incoming packets
	for {
		if n, source, err := conn.ReadFrom(rb); err != nil {
			if netErr, ok := err.(net.Error); !ok || !netErr.Temporary() { //nolint:staticcheck
				break // socket gone
			}
		} else {
			pinger.receive(proto, rb[:n], source.(*net.IPAddr).IP, time.Now())
		}
	}

	// close running requests
	pinger.mtx.RLock()
	for _, req := range pinger.requests {
		req.handleReply(errClosed, nil, nil)
	}
	pinger.mtx.RUnlock()

	// Close() waits for us
	pinger.wg.Done()
}

// receive takes the raw message and tries to evaluate an ICMP response.
// If that succeeds, the body will given to process() for further processing.
func (pinger *Pinger) receive(proto int, bytes []byte, addr net.IP, t time.Time) {
	// parse message
	m, err := icmp.ParseMessage(proto, bytes)
	if err != nil {
		return
	}

	// evaluate message
	switch m.Type {
	case ipv4.ICMPTypeEchoReply, ipv6.ICMPTypeEchoReply:
		pinger.process(m.Body, nil, addr, &t)

	case ipv4.ICMPTypeDestinationUnreachable, ipv6.ICMPTypeDestinationUnreachable:
		body := m.Body.(*icmp.DstUnreach)
		if body == nil {
			return
		}

		var bodyData []byte
		switch proto {
		case ProtocolICMP:
			// parse header of original IPv4 packet
			hdr, err := ipv4.ParseHeader(body.Data)
			if err != nil {
				return
			}
			bodyData = body.Data[hdr.Len:]
		case ProtocolICMPv6:
			// parse header of original IPv6 packet (we don't need the actual
			// header, but want to detect parsing errors)
			_, err := ipv6.ParseHeader(body.Data)
			if err != nil {
				return
			}
			bodyData = body.Data[ipv6.HeaderLen:]
		default:
			return
		}

		// parse ICMP message after the IP header
		msg, err := icmp.ParseMessage(proto, bodyData)
		if err != nil {
			return
		}
		pinger.process(msg.Body, fmt.Errorf("%s", m.Type), nil, nil)
	}
}

// process will finish a currently running Echo Request, if the body is
// an ICMP Echo reply to a request from us.
func (pinger *Pinger) process(body icmp.MessageBody, result error, addr net.IP, tRecv *time.Time) {
	echo, ok := body.(*icmp.Echo)
	if !ok || echo == nil {
		if pinger.LogUnexpectedPackets {
			log.Infof("expected *icmp.Echo, got %#v", body)
		}
		return
	}

	idseq := (uint32(uint16(echo.ID)) << 16) | uint32(uint16(echo.Seq))

	// search for existing running echo request
	pinger.mtx.Lock()
	req := pinger.requests[idseq]
	if _, ok := req.(*simpleRequest); ok {
		// a simpleRequest is finished on the first reply
		delete(pinger.requests, idseq)
	}
	pinger.mtx.Unlock()

	if req != nil {
		req.handleReply(result, addr, tRecv)
	}
}


================================================
FILE: request.go
================================================
package ping

import (
	"net"
	"sync"
	"time"
)

type request interface {
	init()
	close()
	handleReply(error, net.IP, *time.Time)
}

// A multiRequest is a currently running ICMP echo request waiting for multple answers.
type multiRequest struct {
	tStart  time.Time // when was the request packet sent?
	replies chan Reply
	closed  bool
	mtx     sync.RWMutex
}

// Reply is a reply to a multicast echo request
type Reply struct {
	Address  net.IP
	Duration time.Duration
}

// A simpleRequest is a currently running ICMP echo request waiting for a single answer.
type simpleRequest struct {
	wait    chan struct{}
	result  error
	tStart  time.Time  // when was this packet sent?
	tFinish *time.Time // if and when was the reply received?
}

// handleReply is responsible for finishing this request.
// It takes an error as failure reason.
func (req *simpleRequest) handleReply(err error, _ net.IP, tRecv *time.Time) {
	req.result = err

	// update tFinish only if no error present and value wasn't previously set
	if err == nil && tRecv != nil && req.tFinish == nil {
		req.tFinish = tRecv
	}
	req.close()
}

func (req *simpleRequest) init() {
	req.wait = make(chan struct{})
	req.tStart = time.Now()
}

func (req *simpleRequest) close() {
	defer func() {
		// Double-closing is very unlikely, but a race condition may
		// happen when sending fails and a reply is received anyway.
		recover()
	}()

	close(req.wait)
}

func (req *simpleRequest) roundTripTime() (time.Duration, error) {
	if req.result != nil {
		return 0, req.result
	}
	if req.tFinish == nil {
		return 0, nil
	}
	return req.tFinish.Sub(req.tStart), nil
}

func (req *multiRequest) init() {
	req.replies = make(chan Reply)
	req.tStart = time.Now()
}

func (req *multiRequest) close() {
	req.mtx.Lock()
	req.closed = true
	close(req.replies)
	req.mtx.Unlock()
}

// handleReply is responsible for adding a result to the result set
func (req *multiRequest) handleReply(err error, addr net.IP, tRecv *time.Time) {
	if err != nil {
		return
	}
	// avoid blocking
	go func() {
		req.mtx.RLock()
		defer req.mtx.RUnlock()

		if !req.closed {
			req.replies <- Reply{
				Address:  addr,
				Duration: tRecv.Sub(req.tStart),
			}
		}
	}()
}


================================================
FILE: sending.go
================================================
package ping

import (
	"context"
	"errors"
	"net"
	"sync"
	"sync/atomic"
	"time"

	"golang.org/x/net/icmp"
	"golang.org/x/net/ipv4"
	"golang.org/x/net/ipv6"
)

// PingAttempts sends ICMP echo requests with a timeout per request, retrying upto `attempt` times .
// Will finish early on success and return the round trip time of the last ping.
func (pinger *Pinger) PingAttempts(destination *net.IPAddr, timeout time.Duration, attempts int) (rtt time.Duration, err error) {
	if attempts < 1 {
		err = errors.New("zero attempts")
	} else {
		for i := 0; i < attempts; i++ {
			rtt, err = pinger.Ping(destination, timeout)
			if err == nil {
				break // success
			}
		}
	}
	return
}

// Ping sends a single Echo Request and waits for an answer. It returns
// the round trip time (RTT) if a reply is received in time.
func (pinger *Pinger) Ping(destination *net.IPAddr, timeout time.Duration) (time.Duration, error) {
	ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(timeout))
	defer cancel()
	return pinger.PingContext(ctx, destination)
}

// PingContext sends a single Echo Request and waits for an answer. It returns
// the round trip time (RTT) if a reply is received before cancellation of the context.
func (pinger *Pinger) PingContext(ctx context.Context, destination *net.IPAddr) (time.Duration, error) {
	req := simpleRequest{}

	idseq, err := pinger.sendRequest(destination, &req)
	if err != nil {
		return 0, err
	}

	// wait for answer
	select {
	case <-req.wait:
		// already dequeued
		err = req.result
	case <-ctx.Done():
		// dequeue request
		pinger.removeRequest(idseq)
		err = &timeoutError{}
	}

	if err != nil {
		return 0, err
	}
	return req.roundTripTime()
}

// PingMulticast sends a single echo request and returns a channel for the responses.
// The channel will be closed on termination of the context.
// An error is returned if the sending of the echo request fails.
func (pinger *Pinger) PingMulticast(destination *net.IPAddr, wait time.Duration) (<-chan Reply, error) {
	ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(wait))
	defer cancel()
	return pinger.PingMulticastContext(ctx, destination)
}

// PingMulticastContext does the same as PingMulticast but receives a context
func (pinger *Pinger) PingMulticastContext(ctx context.Context, destination *net.IPAddr) (<-chan Reply, error) {
	req := multiRequest{}

	idseq, err := pinger.sendRequest(destination, &req)
	if err != nil {
		return nil, err
	}

	go func() {
		<-ctx.Done()

		// dequeue request
		pinger.removeRequest(idseq)

		req.close()
	}()

	return req.replies, nil
}

// sendRequest marshals the payload and sends the packet.
// It returns the combined id+sequence number and an error if the sending failed.
func (pinger *Pinger) sendRequest(destination *net.IPAddr, req request) (uint32, error) {
	id := uint16(pinger.Id)
	seq := uint16(atomic.AddUint32(pinger.SequenceCounter, 1))

	idseq := (uint32(id) << 16) | uint32(seq)

	pinger.payloadMu.RLock()
	defer pinger.payloadMu.RUnlock()

	// build packet
	wm := icmp.Message{
		Code: 0,
		Body: &icmp.Echo{
			ID:   int(id),
			Seq:  int(seq),
			Data: pinger.payload,
		},
	}

	// Protocol specifics
	var conn net.PacketConn
	var lock *sync.Mutex
	if destination.IP.To4() != nil {
		wm.Type = ipv4.ICMPTypeEcho
		conn = pinger.conn4
		lock = &pinger.write4
	} else {
		wm.Type = ipv6.ICMPTypeEchoRequest
		conn = pinger.conn6
		lock = &pinger.write6
	}

	// serialize packet
	wb, err := wm.Marshal(nil)
	if err != nil {
		return idseq, err
	}

	// enqueue in currently running requests
	pinger.mtx.Lock()
	pinger.requests[idseq] = req
	pinger.mtx.Unlock()

	// start measurement (tStop is set in the receiving end)
	lock.Lock()
	req.init()

	// send request
	_, err = conn.WriteTo(wb, destination)
	lock.Unlock()

	// send failed, need to remove request from list
	if err != nil {
		req.close()
		pinger.removeRequest(idseq)

		return idseq, err
	}

	return idseq, nil
}
Download .txt
gitextract_u0ueinq6/

├── .codecov.yml
├── .github/
│   ├── build-all
│   └── workflows/
│       ├── build.yml
│       └── golangci-lint.yml
├── .gitignore
├── .golangci.yml
├── LICENSE
├── README.md
├── cmd/
│   ├── common.mk
│   ├── multiping/
│   │   ├── Makefile
│   │   ├── README.md
│   │   ├── destination.go
│   │   ├── destination_test.go
│   │   ├── main.go
│   │   ├── resolve.go
│   │   └── ui.go
│   ├── ping-monitor/
│   │   ├── Makefile
│   │   └── main.go
│   ├── ping-test/
│   │   ├── Makefile
│   │   ├── README.md
│   │   └── main.go
│   └── pingnet/
│       ├── Makefile
│       └── main.go
├── error.go
├── go.mod
├── go.sum
├── monitor/
│   ├── history.go
│   ├── history_test.go
│   ├── metrics.go
│   ├── monitor.go
│   └── target.go
├── payload.go
├── pinger.go
├── pinger_linux.go
├── pinger_other.go
├── pinger_test.go
├── receiving.go
├── request.go
└── sending.go
Download .txt
SYMBOL INDEX (96 symbols across 22 files)

FILE: cmd/multiping/destination.go
  type history (line 13) | type history struct
    method addResult (line 44) | func (s *history) addResult(rtt time.Duration, err error) {
    method compute (line 55) | func (s *history) compute() (st stat) {
  type destination (line 20) | type destination struct
    method ping (line 36) | func (u *destination) ping(pinger *ping.Pinger) {
  type stat (line 26) | type stat struct

FILE: cmd/multiping/destination_test.go
  function TestComputeStats (line 10) | func TestComputeStats(t *testing.T) {

FILE: cmd/multiping/main.go
  function main (line 37) | func main() {
  function work (line 99) | func work() {

FILE: cmd/multiping/resolve.go
  function resolve (line 10) | func resolve(addr string, timeout time.Duration) ([]net.IPAddr, error) {

FILE: cmd/multiping/ui.go
  type userInterface (line 13) | type userInterface struct
    method Run (line 136) | func (ui *userInterface) Run() error {
    method update (line 141) | func (ui *userInterface) update(interval time.Duration) {
  function buildTUI (line 73) | func buildTUI(destinations []*destination) *userInterface {
  constant tsDividend (line 159) | tsDividend = float64(time.Millisecond) / float64(time.Nanosecond)
  function ts (line 161) | func ts(dur time.Duration) string {

FILE: cmd/ping-monitor/main.go
  function main (line 25) | func main() {

FILE: cmd/ping-test/main.go
  function main (line 27) | func main() {
  function unicastPing (line 94) | func unicastPing() {
  function multicastPing (line 105) | func multicastPing() {

FILE: cmd/pingnet/main.go
  type workGenerator (line 33) | type workGenerator struct
    method size (line 38) | func (w *workGenerator) size() uint64 {
    method each (line 43) | func (w *workGenerator) each(callback func(net.IP) error) error {
  type result (line 64) | type result struct
  function main (line 70) | func main() {

FILE: error.go
  type timeoutError (line 12) | type timeoutError struct
    method Error (line 14) | func (e *timeoutError) Error() string   { return "i/o timeout" }
    method Timeout (line 15) | func (e *timeoutError) Timeout() bool   { return true }
    method Temporary (line 16) | func (e *timeoutError) Temporary() bool { return true }

FILE: monitor/history.go
  type Result (line 12) | type Result struct
  type History (line 18) | type History struct
    method AddResult (line 33) | func (h *History) AddResult(rtt time.Duration, err error) {
    method clear (line 46) | func (h *History) clear() {
    method ComputeAndClear (line 52) | func (h *History) ComputeAndClear() *Metrics {
    method Compute (line 61) | func (h *History) Compute() *Metrics {
    method compute (line 67) | func (h *History) compute() *Metrics {
  function NewHistory (line 26) | func NewHistory(capacity int) History {

FILE: monitor/history_test.go
  function BenchmarkAddResult (line 12) | func BenchmarkAddResult(b *testing.B) {
  function BenchmarkCompute (line 19) | func BenchmarkCompute(b *testing.B) {
  function TestCompute (line 27) | func TestCompute(t *testing.T) {
  function TestHistoryCapacity (line 118) | func TestHistoryCapacity(t *testing.T) {

FILE: monitor/metrics.go
  type Metrics (line 4) | type Metrics struct

FILE: monitor/monitor.go
  type Monitor (line 12) | type Monitor struct
    method Stop (line 37) | func (p *Monitor) Stop() {
    method AddTarget (line 49) | func (p *Monitor) AddTarget(key string, addr net.IPAddr) (err error) {
    method AddTargetDelayed (line 54) | func (p *Monitor) AddTargetDelayed(key string, addr net.IPAddr, startu...
    method RemoveTarget (line 68) | func (p *Monitor) RemoveTarget(key string) {
    method removeTarget (line 76) | func (p *Monitor) removeTarget(key string) {
    method ExportAndClear (line 87) | func (p *Monitor) ExportAndClear() map[string]*Metrics {
    method Export (line 92) | func (p *Monitor) Export() map[string]*Metrics {
    method export (line 96) | func (p *Monitor) export(clear bool) map[string]*Metrics {
  constant defaultHistorySize (line 22) | defaultHistorySize = 10
  function New (line 26) | func New(pinger *ping.Pinger, interval, timeout time.Duration) *Monitor {

FILE: monitor/target.go
  type Target (line 12) | type Target struct
    method run (line 37) | func (n *Target) run(startupDelay time.Duration) {
    method Stop (line 59) | func (n *Target) Stop() {
    method Compute (line 65) | func (n *Target) Compute(clear bool) *Metrics {
    method ping (line 72) | func (n *Target) ping() {
  function newTarget (line 23) | func newTarget(interval, timeout, startupDelay time.Duration, historySiz...

FILE: payload.go
  type Payload (line 23) | type Payload
    method Resize (line 26) | func (p *Payload) Resize(size uint16) {

FILE: pinger.go
  constant ProtocolICMP (line 14) | ProtocolICMP = 1
  constant ProtocolICMPv6 (line 18) | ProtocolICMPv6 = 58
  type Pinger (line 25) | type Pinger struct
    method Close (line 85) | func (pinger *Pinger) Close() {
    method close (line 100) | func (pinger *Pinger) close(conn net.PacketConn) {
    method removeRequest (line 106) | func (pinger *Pinger) removeRequest(idseq uint32) {
    method SetPayloadSize (line 116) | func (pinger *Pinger) SetPayloadSize(size uint16) {
    method SetPayload (line 123) | func (pinger *Pinger) SetPayload(data []byte) {
    method PayloadSize (line 130) | func (pinger *Pinger) PayloadSize() uint16 {
  function New (line 44) | func New(bind4, bind6 string) (*Pinger, error) {
  function connectICMP (line 92) | func connectICMP(network, address string) (*icmp.PacketConn, error) {

FILE: pinger_linux.go
  function getFD (line 13) | func getFD(c *icmp.PacketConn) (uintptr, error) {
  method SetMark (line 32) | func (pinger *Pinger) SetMark(mark uint) error {

FILE: pinger_other.go
  method SetMark (line 7) | func (pinger *Pinger) SetMark(mark uint) error {

FILE: pinger_test.go
  function TestPinger (line 12) | func TestPinger(t *testing.T) {

FILE: receiving.go
  method receiver (line 15) | func (pinger *Pinger) receiver(proto int, conn net.PacketConn) {
  method receive (line 42) | func (pinger *Pinger) receive(proto int, bytes []byte, addr net.IP, t ti...
  method process (line 92) | func (pinger *Pinger) process(body icmp.MessageBody, result error, addr ...

FILE: request.go
  type request (line 9) | type request interface
  type multiRequest (line 16) | type multiRequest struct
    method init (line 74) | func (req *multiRequest) init() {
    method close (line 79) | func (req *multiRequest) close() {
    method handleReply (line 87) | func (req *multiRequest) handleReply(err error, addr net.IP, tRecv *ti...
  type Reply (line 24) | type Reply struct
  type simpleRequest (line 30) | type simpleRequest struct
    method handleReply (line 39) | func (req *simpleRequest) handleReply(err error, _ net.IP, tRecv *time...
    method init (line 49) | func (req *simpleRequest) init() {
    method close (line 54) | func (req *simpleRequest) close() {
    method roundTripTime (line 64) | func (req *simpleRequest) roundTripTime() (time.Duration, error) {

FILE: sending.go
  method PingAttempts (line 18) | func (pinger *Pinger) PingAttempts(destination *net.IPAddr, timeout time...
  method Ping (line 34) | func (pinger *Pinger) Ping(destination *net.IPAddr, timeout time.Duratio...
  method PingContext (line 42) | func (pinger *Pinger) PingContext(ctx context.Context, destination *net....
  method PingMulticast (line 70) | func (pinger *Pinger) PingMulticast(destination *net.IPAddr, wait time.D...
  method PingMulticastContext (line 77) | func (pinger *Pinger) PingMulticastContext(ctx context.Context, destinat...
  method sendRequest (line 99) | func (pinger *Pinger) sendRequest(destination *net.IPAddr, req request) ...
Condensed preview — 39 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (71K chars).
[
  {
    "path": ".codecov.yml",
    "chars": 14,
    "preview": "ignore:\n- cmd\n"
  },
  {
    "path": ".github/build-all",
    "chars": 87,
    "preview": "#!/bin/sh -e\n\nfor mf in $(find cmd -name Makefile); do\n  make -C \"$(dirname $mf)\"\ndone\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "chars": 562,
    "preview": "name: build\n\non:\n- push\n- pull_request\n\njobs:\n  build:\n    name: Tests\n    runs-on: ubuntu-latest\n    steps:\n\n    - name"
  },
  {
    "path": ".github/workflows/golangci-lint.yml",
    "chars": 344,
    "preview": "name: golangci-lint\n\non:\n- push\n- pull_request\n\njobs:\n  golangci:\n    name: lint\n    runs-on: ubuntu-latest\n    steps:\n "
  },
  {
    "path": ".gitignore",
    "chars": 448,
    "preview": "# Binaries for programs and plugins\n*.exe\n*.dll\n*.so\n*.dylib\n\n# Test binary, build with `go test -c`\n*.test\n\n# Output of"
  },
  {
    "path": ".golangci.yml",
    "chars": 405,
    "preview": "version: \"2\"\nlinters:\n  exclusions:\n    generated: lax\n    presets:\n      - comments\n      - common-false-positives\n    "
  },
  {
    "path": "LICENSE",
    "chars": 1069,
    "preview": "MIT License\n\nCopyright (c) 2018 Digineo GmbH\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
  },
  {
    "path": "README.md",
    "chars": 1898,
    "preview": "# go-ping\n\n[![GoDoc](https://godoc.org/github.com/digineo/go-ping?status.svg)](https://godoc.org/github.com/digineo/go-p"
  },
  {
    "path": "cmd/common.mk",
    "chars": 108,
    "preview": ".PHONY: all\nall: $(TARGET)\n\n$(TARGET):\n\tgo build -o $@\n\n.PHONY: clean\nclean:\n\trm -f $(TARGET) $(TARGET).log\n"
  },
  {
    "path": "cmd/multiping/Makefile",
    "chars": 154,
    "preview": "TARGET = multiping\n\ninclude ../common.mk\n\n.PHONY: test\ntest: all\n\tgo test ./...\n\t./$(TARGET) golang.org cloudflare.com 2"
  },
  {
    "path": "cmd/multiping/README.md",
    "chars": 1982,
    "preview": "# multiping\n\nJust like regular `ping`, but measures round trip time to multiple\nhosts, all at once.\n\nThe user interface "
  },
  {
    "path": "cmd/multiping/destination.go",
    "chars": 1812,
    "preview": "package main\n\nimport (\n\t\"log\"\n\t\"math\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\tping \"github.com/digineo/go-ping\"\n)\n\ntype history struct "
  },
  {
    "path": "cmd/multiping/destination_test.go",
    "chars": 3302,
    "preview": "package main\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestComputeStats(t *testing.T) "
  },
  {
    "path": "cmd/multiping/main.go",
    "chars": 2499,
    "preview": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"time\"\n\n\tping \"github.com/digineo/go-ping\"\n)\n\nvar opts = struct {\n\tt"
  },
  {
    "path": "cmd/multiping/resolve.go",
    "chars": 440,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"strings\"\n\t\"time\"\n)\n\nfunc resolve(addr string, timeout time.Duration) ([]net.I"
  },
  {
    "path": "cmd/multiping/ui.go",
    "chars": 3699,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\ntype"
  },
  {
    "path": "cmd/ping-monitor/Makefile",
    "chars": 44,
    "preview": "TARGET = ping-monitor\n\ninclude ../common.mk\n"
  },
  {
    "path": "cmd/ping-monitor/main.go",
    "chars": 2104,
    "preview": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/digineo/go-ping\"\n\t\"gith"
  },
  {
    "path": "cmd/ping-test/Makefile",
    "chars": 333,
    "preview": "TARGET = ping-test\n\ninclude ../common.mk\n\n.PHONY: test\ntest: all\n\t@truncate -s0 $(TARGET).log\n\t@./$(TARGET) -4 golang.or"
  },
  {
    "path": "cmd/ping-test/README.md",
    "chars": 1642,
    "preview": "# ping-test\n\nThis is a sample program to demonstrate the usage of this library.\nIt is not intended for production use.\n\n"
  },
  {
    "path": "cmd/ping-test/main.go",
    "chars": 2344,
    "preview": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"net\"\n\t\"os\"\n\t\"time\"\n\n\tping \"github.com/digineo/go-ping\"\n)\n\nvar (\n\targs    "
  },
  {
    "path": "cmd/pingnet/Makefile",
    "chars": 106,
    "preview": "TARGET = pingnet\n\ninclude ../common.mk\n\n.PHONY: test\ntest: all\n\t./$(TARGET) -c 1 -w 100ms -f 127.0.0.1/24\n"
  },
  {
    "path": "cmd/pingnet/main.go",
    "chars": 4489,
    "preview": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"net\"\n\t\"os\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tping \"github.com/digin"
  },
  {
    "path": "error.go",
    "chars": 505,
    "preview": "package ping\n\nimport \"errors\"\n\nvar (\n\terrClosed   = errors.New(\"pinger closed\")\n\terrNotBound = errors.New(\"need at least"
  },
  {
    "path": "go.mod",
    "chars": 915,
    "preview": "module github.com/digineo/go-ping\n\ngo 1.23.0\n\nrequire (\n\tgithub.com/digineo/go-logwrap v0.0.0-20181106161722-a178c58ea3f"
  },
  {
    "path": "go.sum",
    "chars": 6667,
    "preview": "github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.m"
  },
  {
    "path": "monitor/history.go",
    "chars": 2503,
    "preview": "package monitor\n\nimport (\n\t\"math\"\n\t\"sort\"\n\t\"sync\"\n\t\"time\"\n)\n\n// Result stores the information about a single ping, in pa"
  },
  {
    "path": "monitor/history_test.go",
    "chars": 3466,
    "preview": "package monitor\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc BenchmarkAddR"
  },
  {
    "path": "monitor/metrics.go",
    "chars": 406,
    "preview": "package monitor\n\n// Metrics is a dumb data point computed from a history of Results.\ntype Metrics struct {\n\tPacketsSent "
  },
  {
    "path": "monitor/monitor.go",
    "chars": 2705,
    "preview": "package monitor\n\nimport (\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/digineo/go-ping\"\n)\n\n// Monitor manages the goroutines res"
  },
  {
    "path": "monitor/target.go",
    "chars": 1413,
    "preview": "package monitor\n\nimport (\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\tping \"github.com/digineo/go-ping\"\n)\n\n// Target is a unit of work\ntype"
  },
  {
    "path": "payload.go",
    "chars": 738,
    "preview": "package ping\n\nimport (\n\t\"math/rand\"\n\t\"time\"\n\n\t\"github.com/digineo/go-logwrap\"\n)\n\nvar (\n\tlog = &logwrap.Instance{}\n\n\t// S"
  },
  {
    "path": "pinger.go",
    "chars": 3242,
    "preview": "package ping\n\nimport (\n\t\"net\"\n\t\"os\"\n\t\"sync\"\n\n\t\"golang.org/x/net/icmp\"\n)\n\nconst (\n\t// ProtocolICMP is the number of the I"
  },
  {
    "path": "pinger_linux.go",
    "chars": 1392,
    "preview": "package ping\r\n\r\nimport (\r\n\t\"errors\"\r\n\t\"os\"\r\n\t\"reflect\"\r\n\t\"syscall\"\r\n\r\n\t\"golang.org/x/net/icmp\"\r\n)\r\n\r\n// getFD gets the s"
  },
  {
    "path": "pinger_other.go",
    "chars": 196,
    "preview": "//go:build !linux\r\n\r\npackage ping\r\n\r\nimport \"errors\"\r\n\r\nfunc (pinger *Pinger) SetMark(mark uint) error {\r\n\treturn errors"
  },
  {
    "path": "pinger_test.go",
    "chars": 529,
    "preview": "package ping\n\nimport (\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/re"
  },
  {
    "path": "receiving.go",
    "chars": 2865,
    "preview": "package ping\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n\n\t\"golang.org/x/net/icmp\"\n\t\"golang.org/x/net/ipv4\"\n\t\"golang.org/x/net/ipv6\""
  },
  {
    "path": "request.go",
    "chars": 2204,
    "preview": "package ping\n\nimport (\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n)\n\ntype request interface {\n\tinit()\n\tclose()\n\thandleReply(error, net.IP, *"
  },
  {
    "path": "sending.go",
    "chars": 3969,
    "preview": "package ping\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"golang.org/x/net/icmp\"\n\t\"golang.org"
  }
]

About this extraction

This page contains the full source code of the digineo/go-ping GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 39 files (62.1 KB), approximately 21.4k tokens, and a symbol index with 96 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!