pax_global_header00006660000000000000000000000064145730470740014525gustar00rootroot0000000000000052 comment=ac3b40f1f0a7438a429e9bf6f2bc2a94ba286e39 golang-github-prometheus-community-pro-bing-0.4.0/000077500000000000000000000000001457304707400222035ustar00rootroot00000000000000golang-github-prometheus-community-pro-bing-0.4.0/.circleci/000077500000000000000000000000001457304707400240365ustar00rootroot00000000000000golang-github-prometheus-community-pro-bing-0.4.0/.circleci/config.yml000066400000000000000000000014451457304707400260320ustar00rootroot00000000000000--- version: 2.1 orbs: goreleaser: hubci/goreleaser@2.3.0 executors: golang: docker: - image: cimg/go:1.20 jobs: build: executor: golang steps: - checkout - run: go mod download - run: make workflows: version: 2 pro-bing: jobs: - build: filters: tags: only: /.*/ - goreleaser/release: name: test-release version: '1.16.2' go-version: '1.20' dry-run: true requires: - build filters: tags: only: /.*/ - goreleaser/release: name: release version: '1.16.2' go-version: '1.20' requires: - build filters: tags: only: /^v.*/ branches: ignore: /.*/ golang-github-prometheus-community-pro-bing-0.4.0/.editorconfig000066400000000000000000000003311457304707400246550ustar00rootroot00000000000000# 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-prometheus-community-pro-bing-0.4.0/.github/000077500000000000000000000000001457304707400235435ustar00rootroot00000000000000golang-github-prometheus-community-pro-bing-0.4.0/.github/dependabot.yml000066400000000000000000000001561457304707400263750ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "gomod" directory: "/" schedule: interval: "monthly" golang-github-prometheus-community-pro-bing-0.4.0/.github/workflows/000077500000000000000000000000001457304707400256005ustar00rootroot00000000000000golang-github-prometheus-community-pro-bing-0.4.0/.github/workflows/golangci-lint.yml000066400000000000000000000023131457304707400310510ustar00rootroot00000000000000--- # This action is synced from https://github.com/prometheus/prometheus name: golangci-lint on: push: paths: - "go.sum" - "go.mod" - "**.go" - "scripts/errcheck_excludes.txt" - ".github/workflows/golangci-lint.yml" - ".golangci.yml" pull_request: permissions: # added using https://github.com/step-security/secure-repo contents: read jobs: golangci: permissions: contents: read # for actions/checkout to fetch code pull-requests: read # for golangci/golangci-lint-action to fetch pull requests name: lint runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: install Go uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: 1.22.x - name: Install snmp_exporter/generator dependencies run: sudo apt-get update && sudo apt-get -y install libsnmp-dev if: github.repository == 'prometheus/snmp_exporter' - name: Lint uses: golangci/golangci-lint-action@3cfe3a4abbb849e10058ce4af15d205b6da42804 # v4.0.0 with: version: v1.55.2 golang-github-prometheus-community-pro-bing-0.4.0/.gitignore000066400000000000000000000000141457304707400241660ustar00rootroot00000000000000/ping /dist golang-github-prometheus-community-pro-bing-0.4.0/.golangci.yml000066400000000000000000000005151457304707400245700ustar00rootroot00000000000000--- linters: enable: - misspell - revive issues: exclude-rules: - path: _test.go linters: - errcheck linters-settings: revive: rules: # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-parameter - name: unused-parameter severity: warning disabled: true golang-github-prometheus-community-pro-bing-0.4.0/.goreleaser.yaml000066400000000000000000000014151457304707400252760ustar00rootroot00000000000000project_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-prometheus-community-pro-bing-0.4.0/CODE_OF_CONDUCT.md000066400000000000000000000002301457304707400247750ustar00rootroot00000000000000# Prometheus Community Code of Conduct Prometheus follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). golang-github-prometheus-community-pro-bing-0.4.0/CONTRIBUTING.md000066400000000000000000000035531457304707400244420ustar00rootroot00000000000000# 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 [#pro-bing](https://gophers.slack.com/archives/C019J5E26U8/p1673599762771949) channel of the [Gophers Slack](https://invite.slack.golangbridge.org/). ## Pull Requests [Fork the repo on GitHub](https://github.com/prometheus-community/pro-bing/fork) and clone it to your local machine. ```bash git clone https://github.com/YOUR_USERNAME/pro-bing.git && cd pro-bing ``` 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/prometheus-community/pro-bing/compare) in the main `prometheus-community/pro-bing` 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-prometheus-community-pro-bing-0.4.0/LICENSE000066400000000000000000000021541457304707400232120ustar00rootroot00000000000000The MIT License (MIT) Copyright 2022 The Prometheus Authors Copyright 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-prometheus-community-pro-bing-0.4.0/MAINTAINERS.md000066400000000000000000000001551457304707400243000ustar00rootroot00000000000000# Maintainers * Ben Kochie @SuperQ * Matthias Loibl @metalmatze golang-github-prometheus-community-pro-bing-0.4.0/Makefile000066400000000000000000000014121457304707400236410ustar00rootroot00000000000000GO ?= 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-prometheus-community-pro-bing-0.4.0/README.md000066400000000000000000000146371457304707400234750ustar00rootroot00000000000000# pro-bing [![PkgGoDev](https://pkg.go.dev/badge/github.com/prometheus-community/pro-bing)](https://pkg.go.dev/github.com/prometheus-community/pro-bing) [![Circle CI](https://circleci.com/gh/prometheus-community/pro-bing.svg?style=svg)](https://circleci.com/gh/prometheus-community/pro-bing) A simple but powerful ICMP echo (ping) library for Go, inspired by [go-ping](https://github.com/go-ping/ping) & [go-fastping](https://github.com/tatsushid/go-fastping). Here is a very simple example that sends and receives three packets: ```go pinger, err := probing.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 := probing.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 *probing.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 *probing.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 *probing.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/prometheus-community/pro-bing/blob/master/cmd/ping/ping.go). ## Installation ``` go get -u github.com/prometheus-community/pro-bing ``` To install the native Go ping executable: ```bash go get -u github.com/prometheus-community/pro-bing/... $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. This library supports setting the `SO_MARK` socket option which is equivalent to the `-m mark` flag in standard ping binaries on linux. Setting this option requires the `CAP_NET_ADMIN` capability (via `setcap` or elevated privileges). You can set a mark (ex: 100) with `pinger.SetMark(100)` in your code. Setting the "Don't Fragment" bit is supported under Linux which is equivalent to `ping -Mdo`. You can enable this with `pinger.SetDoNotFragment(true)`. ### 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. ## HTTP This library also provides support for HTTP probing. Here is a trivial example: ```go httpCaller := probing.NewHttpCaller("https://www.google.com", probing.WithHTTPCallerCallFrequency(time.Second), probing.WithHTTPCallerOnResp(func(suite *probing.TraceSuite, info *probing.HTTPCallInfo) { fmt.Printf("got resp, status code: %d, latency: %s\n", info.StatusCode, suite.GetGeneralEnd().Sub(suite.GetGeneralStart()), ) }), ) // Listen for Ctrl-C. c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) go func() { <-c httpCaller.Stop() }() httpCaller.Run() ``` Library provides a rich list of options available for a probing. You can check the full list of available options in a generated doc. ### Callbacks HTTPCaller uses `net/http/httptrace` pkg to provide an API to track specific request event, e.g. tls handshake start. It is highly recommended to check the httptrace library [doc](https://pkg.go.dev/net/http/httptrace) to understand the purpose of provided callbacks. Nevertheless, httptrace callbacks are concurrent-unsafe, our implementation provides a concurrent-safe API. In addition to that, each callback contains a TraceSuite object which provides an Extra field which you can use to propagate your data across them and a number of timer fields, which are set prior to the execution of a corresponding callback. ### Target RPS & performance Library provides two options, allowing to manipulate your call load: `callFrequency` & `maxConcurrentCalls`. In case you set `callFrequency` to a value X, but it can't be achieved during the execution - you will need to try increasing a number of `maxConcurrentCalls`. Moreover, your callbacks might directly influence an execution performance. For a full documentation, please refer to the generated [doc](https://pkg.go.dev/github.com/prometheus-community/pro-bing). ## Maintainers and Getting Help: This repo was originally in the personal account of [sparrc](https://github.com/sparrc), but is now maintained by the [Prometheus Community](https://prometheus.io/community). ## Contributing Refer to [CONTRIBUTING.md](https://github.com/prometheus-community/pro-bing/blob/master/CONTRIBUTING.md) golang-github-prometheus-community-pro-bing-0.4.0/SECURITY.md000066400000000000000000000002541457304707400237750ustar00rootroot00000000000000# Reporting a security issue The Prometheus security policy, including how to report vulnerabilities, can be found here: golang-github-prometheus-community-pro-bing-0.4.0/cmd/000077500000000000000000000000001457304707400227465ustar00rootroot00000000000000golang-github-prometheus-community-pro-bing-0.4.0/cmd/ping/000077500000000000000000000000001457304707400237035ustar00rootroot00000000000000golang-github-prometheus-community-pro-bing-0.4.0/cmd/ping/ping.go000066400000000000000000000044711457304707400251750ustar00rootroot00000000000000package main import ( "flag" "fmt" "os" "os/signal" "time" probing "github.com/prometheus-community/pro-bing" ) 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 := probing.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 *probing.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 *probing.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 *probing.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-prometheus-community-pro-bing-0.4.0/go.mod000066400000000000000000000003071457304707400233110ustar00rootroot00000000000000module github.com/prometheus-community/pro-bing go 1.19 require ( github.com/google/uuid v1.6.0 golang.org/x/net v0.21.0 golang.org/x/sync v0.6.0 ) require golang.org/x/sys v0.17.0 // indirect golang-github-prometheus-community-pro-bing-0.4.0/go.sum000066400000000000000000000011561457304707400233410ustar00rootroot00000000000000github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang-github-prometheus-community-pro-bing-0.4.0/http.go000066400000000000000000000506761457304707400235270ustar00rootroot00000000000000package probing import ( "bytes" "context" "crypto/tls" "io" "net/http" "net/http/httptrace" "sync" "time" ) const ( defaultHTTPCallFrequency = time.Second defaultHTTPMaxConcurrentCalls = 1 defaultHTTPMethod = http.MethodGet defaultTimeout = time.Second * 10 ) type httpCallerOptions struct { client *http.Client callFrequency time.Duration maxConcurrentCalls int host string headers http.Header method string body []byte timeout time.Duration isValidResponse func(response *http.Response, body []byte) bool onDNSStart func(suite *TraceSuite, info httptrace.DNSStartInfo) onDNSDone func(suite *TraceSuite, info httptrace.DNSDoneInfo) onConnStart func(suite *TraceSuite, network, addr string) onConnDone func(suite *TraceSuite, network, addr string, err error) onTLSStart func(suite *TraceSuite) onTLSDone func(suite *TraceSuite, state tls.ConnectionState, err error) onWroteHeaders func(suite *TraceSuite) onFirstByteReceived func(suite *TraceSuite) onReq func(suite *TraceSuite) onResp func(suite *TraceSuite, info *HTTPCallInfo) logger Logger } // HTTPCallerOption represents a function type for a functional parameter passed to a NewHttpCaller constructor. type HTTPCallerOption func(options *httpCallerOptions) // WithHTTPCallerClient is a functional parameter for a HTTPCaller which specifies a http.Client. func WithHTTPCallerClient(client *http.Client) HTTPCallerOption { return func(options *httpCallerOptions) { options.client = client } } // WithHTTPCallerCallFrequency is a functional parameter for a HTTPCaller which specifies a call frequency. // If this option is not provided the default one will be used. You can check default value in const // defaultHTTPCallFrequency. func WithHTTPCallerCallFrequency(frequency time.Duration) HTTPCallerOption { return func(options *httpCallerOptions) { options.callFrequency = frequency } } // WithHTTPCallerMaxConcurrentCalls is a functional parameter for a HTTPCaller which specifies a number of // maximum concurrent calls. If this option is not provided the default one will be used. You can check default value in const // defaultHTTPMaxConcurrentCalls. func WithHTTPCallerMaxConcurrentCalls(max int) HTTPCallerOption { return func(options *httpCallerOptions) { options.maxConcurrentCalls = max } } // WithHTTPCallerHeaders is a functional parameter for a HTTPCaller which specifies headers that should be // set in request. // To override a Host header use a WithHTTPCallerHost method. func WithHTTPCallerHeaders(headers http.Header) HTTPCallerOption { return func(options *httpCallerOptions) { options.headers = headers } } // WithHTTPCallerMethod is a functional parameter for a HTTPCaller which specifies a method that should be // set in request. If this option is not provided the default one will be used. You can check default value in const // defaultHTTPMethod. func WithHTTPCallerMethod(method string) HTTPCallerOption { return func(options *httpCallerOptions) { options.method = method } } // WithHTTPCallerHost is a functional parameter for a HTTPCaller which allowed to override a host header. func WithHTTPCallerHost(host string) HTTPCallerOption { return func(options *httpCallerOptions) { options.host = host } } // WithHTTPCallerBody is a functional parameter for a HTTPCaller which specifies a body that should be set // in request. func WithHTTPCallerBody(body []byte) HTTPCallerOption { return func(options *httpCallerOptions) { options.body = body } } // WithHTTPCallerTimeout is a functional parameter for a HTTPCaller which specifies request timeout. // If this option is not provided the default one will be used. You can check default value in const defaultTimeout. func WithHTTPCallerTimeout(timeout time.Duration) HTTPCallerOption { return func(options *httpCallerOptions) { options.timeout = timeout } } // WithHTTPCallerIsValidResponse is a functional parameter for a HTTPCaller which specifies a function that // will be used to assess whether a response is valid. If not specified, all responses will be treated as valid. // You can read more explanation about this parameter in HTTPCaller annotation. func WithHTTPCallerIsValidResponse(isValid func(response *http.Response, body []byte) bool) HTTPCallerOption { return func(options *httpCallerOptions) { options.isValidResponse = isValid } } // WithHTTPCallerOnDNSStart is a functional parameter for a HTTPCaller which specifies a callback that will be // called when dns resolving starts. You can read more explanation about this parameter in HTTPCaller annotation. func WithHTTPCallerOnDNSStart(onDNSStart func(suite *TraceSuite, info httptrace.DNSStartInfo)) HTTPCallerOption { return func(options *httpCallerOptions) { options.onDNSStart = onDNSStart } } // WithHTTPCallerOnDNSDone is a functional parameter for a HTTPCaller which specifies a callback that will be // called when dns resolving ended. You can read more explanation about this parameter in HTTPCaller annotation. func WithHTTPCallerOnDNSDone(onDNSDone func(suite *TraceSuite, info httptrace.DNSDoneInfo)) HTTPCallerOption { return func(options *httpCallerOptions) { options.onDNSDone = onDNSDone } } // WithHTTPCallerOnConnStart is a functional parameter for a HTTPCaller which specifies a callback that will be // called when connection establishment started. You can read more explanation about this parameter in HTTPCaller // annotation. func WithHTTPCallerOnConnStart(onConnStart func(suite *TraceSuite, network, addr string)) HTTPCallerOption { return func(options *httpCallerOptions) { options.onConnStart = onConnStart } } // WithHTTPCallerOnConnDone is a functional parameter for a HTTPCaller which specifies a callback that will be // called when connection establishment finished. You can read more explanation about this parameter in HTTPCaller // annotation. func WithHTTPCallerOnConnDone(conConnDone func(suite *TraceSuite, network, addr string, err error)) HTTPCallerOption { return func(options *httpCallerOptions) { options.onConnDone = conConnDone } } // WithHTTPCallerOnTLSStart is a functional parameter for a HTTPCaller which specifies a callback that will be // called when tls handshake started. You can read more explanation about this parameter in HTTPCaller annotation. func WithHTTPCallerOnTLSStart(onTLSStart func(suite *TraceSuite)) HTTPCallerOption { return func(options *httpCallerOptions) { options.onTLSStart = onTLSStart } } // WithHTTPCallerOnTLSDone is a functional parameter for a HTTPCaller which specifies a callback that will be // called when tls handshake ended. You can read more explanation about this parameter in HTTPCaller annotation. func WithHTTPCallerOnTLSDone(onTLSDone func(suite *TraceSuite, state tls.ConnectionState, err error)) HTTPCallerOption { return func(options *httpCallerOptions) { options.onTLSDone = onTLSDone } } // WithHTTPCallerOnWroteRequest is a functional parameter for a HTTPCaller which specifies a callback that will be // called when request has been written. You can read more explanation about this parameter in HTTPCaller annotation. func WithHTTPCallerOnWroteRequest(onWroteRequest func(suite *TraceSuite)) HTTPCallerOption { return func(options *httpCallerOptions) { options.onWroteHeaders = onWroteRequest } } // WithHTTPCallerOnFirstByteReceived is a functional parameter for a HTTPCaller which specifies a callback that will be // called when first response byte has been received. You can read more explanation about this parameter in HTTPCaller // annotation. func WithHTTPCallerOnFirstByteReceived(onGotFirstByte func(suite *TraceSuite)) HTTPCallerOption { return func(options *httpCallerOptions) { options.onFirstByteReceived = onGotFirstByte } } // WithHTTPCallerOnReq is a functional parameter for a HTTPCaller which specifies a callback that will be // called before the start of the http call execution. You can read more explanation about this parameter in HTTPCaller // annotation. func WithHTTPCallerOnReq(onReq func(suite *TraceSuite)) HTTPCallerOption { return func(options *httpCallerOptions) { options.onReq = onReq } } // WithHTTPCallerOnResp is a functional parameter for a HTTPCaller which specifies a callback that will be // called when response is received. You can read more explanation about this parameter in HTTPCaller annotation. func WithHTTPCallerOnResp(onResp func(suite *TraceSuite, info *HTTPCallInfo)) HTTPCallerOption { return func(options *httpCallerOptions) { options.onResp = onResp } } // WithHTTPCallerLogger is a functional parameter for a HTTPCaller which specifies a logger. // If not specified, logs will be omitted. func WithHTTPCallerLogger(logger Logger) HTTPCallerOption { return func(options *httpCallerOptions) { options.logger = logger } } // NewHttpCaller returns a new HTTPCaller. URL parameter is the only required one, other options might be specified via // functional parameters, otherwise default values will be used where applicable. func NewHttpCaller(url string, options ...HTTPCallerOption) *HTTPCaller { opts := httpCallerOptions{ callFrequency: defaultHTTPCallFrequency, maxConcurrentCalls: defaultHTTPMaxConcurrentCalls, method: defaultHTTPMethod, timeout: defaultTimeout, client: &http.Client{}, } for _, opt := range options { opt(&opts) } return &HTTPCaller{ client: opts.client, callFrequency: opts.callFrequency, maxConcurrentCalls: opts.maxConcurrentCalls, url: url, host: opts.host, headers: opts.headers, method: opts.method, body: opts.body, timeout: opts.timeout, isValidResponse: opts.isValidResponse, workChan: make(chan struct{}, opts.maxConcurrentCalls), doneChan: make(chan struct{}), onDNSStart: opts.onDNSStart, onDNSDone: opts.onDNSDone, onConnStart: opts.onConnStart, onConnDone: opts.onConnDone, onTLSStart: opts.onTLSStart, onTLSDone: opts.onTLSDone, onWroteHeaders: opts.onWroteHeaders, onFirstByteReceived: opts.onFirstByteReceived, onReq: opts.onReq, onResp: opts.onResp, logger: opts.logger, } } // HTTPCaller represents a prober performing http calls and collecting relevant statistics. type HTTPCaller struct { client *http.Client // callFrequency is a parameter which specifies how often to send a new request. You might need to increase // maxConcurrentCalls value to achieve required value. callFrequency time.Duration // maxConcurrentCalls is a maximum number of calls that might be performed concurrently. In other words, // a number of "workers" that will try to perform probing concurrently. // Default number is specified in defaultHTTPMaxConcurrentCalls maxConcurrentCalls int // url is an url which will be used in all probe requests, mandatory in constructor. url string // host allows to override a Host header host string // headers are headers that which will be used in all probe requests, default are none. headers http.Header // method is a http request method which will be used in all probe requests, // default is specified in defaultHTTPMethod method string // body is a http request body which will be used in all probe requests, default is none. body []byte // timeout is a http call timeout, default is specified in defaultTimeout. timeout time.Duration // isValidResponse is a function that will be used to validate whether a response is valid up to clients choice. // You can think of it as a verification that response contains data that you expected. This information will be // passed back in HTTPCallInfo during an onResp callback and HTTPStatistics during an onFinish callback // or a Statistics call. isValidResponse func(response *http.Response, body []byte) bool workChan chan struct{} doneChan chan struct{} doneWg sync.WaitGroup // All callbacks except onReq and onResp are based on a httptrace callbacks, meaning they are called at the time // and contain signature same as you would expect in httptrace library. In addition to that each callback has a // TraceSuite as a first argument, which will help you to propagate data between these callbacks. You can read more // about it in TraceSuite annotation. // onDNSStart is a callback which is called when a dns lookup starts. It's based on a httptrace.DNSStart callback. onDNSStart func(suite *TraceSuite, info httptrace.DNSStartInfo) // onDNSDone is a callback which is called when a dns lookup ends. It's based on a httptrace.DNSDone callback. onDNSDone func(suite *TraceSuite, info httptrace.DNSDoneInfo) // onConnStart is a callback which is called when a connection dial starts. It's based on a httptrace.ConnectStart // callback. onConnStart func(suite *TraceSuite, network, addr string) // onConnDone is a callback which is called when a connection dial ends. It's based on a httptrace.ConnectDone // callback. onConnDone func(suite *TraceSuite, network, addr string, err error) // onTLSStart is a callback which is called when a tls handshake starts. It's based on a httptrace.TLSHandshakeStart // callback. onTLSStart func(suite *TraceSuite) // onTLSDone is a callback which is called when a tls handshake ends. It's based on a httptrace.TLSHandshakeDone // callback. onTLSDone func(suite *TraceSuite, state tls.ConnectionState, err error) // onWroteHeaders is a callback which is called when request headers where written. It's based on a // httptrace.WroteHeaders callback. onWroteHeaders func(suite *TraceSuite) // onFirstByteReceived is a callback which is called when first response bytes were received. It's based on a // httptrace.GotFirstResponseByte callback. onFirstByteReceived func(suite *TraceSuite) // onReq is a custom callback which is called before http client starts request execution. onReq func(suite *TraceSuite) // onResp is a custom callback which is called when a response is received. onResp func(suite *TraceSuite, info *HTTPCallInfo) // logger is a logger implementation, default is none. logger Logger } // Stop gracefully stops the execution of a HTTPCaller. func (c *HTTPCaller) Stop() { close(c.doneChan) c.doneWg.Wait() } // Run starts execution of a probing. func (c *HTTPCaller) Run() { c.run(context.Background()) } // RunWithContext starts execution of a probing and allows providing a context. func (c *HTTPCaller) RunWithContext(ctx context.Context) { c.run(ctx) } func (c *HTTPCaller) run(ctx context.Context) { c.runWorkScheduler(ctx) c.runCallers(ctx) c.doneWg.Wait() } func (c *HTTPCaller) runWorkScheduler(ctx context.Context) { c.doneWg.Add(1) go func() { defer c.doneWg.Done() ticker := time.NewTicker(c.callFrequency) defer ticker.Stop() for { select { case <-ticker.C: c.workChan <- struct{}{} case <-ctx.Done(): return case <-c.doneChan: return } } }() } func (c *HTTPCaller) runCallers(ctx context.Context) { for i := 0; i < c.maxConcurrentCalls; i++ { c.doneWg.Add(1) go func() { defer c.doneWg.Done() for { logger := c.logger if logger == nil { logger = NoopLogger{} } select { case <-c.workChan: if err := c.makeCall(ctx); err != nil { logger.Errorf("failed making a call: %v", err) } case <-ctx.Done(): return case <-c.doneChan: return } } }() } } // TraceSuite is a struct that is passed to each callback. It contains a bunch of time helpers, that you can use with // a corresponding getter. These timers are set before making a corresponding callback, meaning that when an onDNSStart // callback will be called - TraceSuite will already have filled dnsStart field. In addition to that, it contains // an Extra field of type any which you can use in any custom way you might need. Before each callback call, mutex // is used, meaning all operations inside your callback are concurrent-safe. // Keep in mind, that if your http client set up to follow redirects - timers will be overwritten. type TraceSuite struct { mu sync.Mutex generalStart time.Time generalEnd time.Time dnsStart time.Time dnsEnd time.Time connStart time.Time connEnd time.Time tlsStart time.Time tlsEnd time.Time wroteHeaders time.Time firstByteReceived time.Time Extra any } // GetGeneralStart returns a general http request execution start time. func (s *TraceSuite) GetGeneralStart() time.Time { return s.generalStart } // GetGeneralEnd returns a general http response time. func (s *TraceSuite) GetGeneralEnd() time.Time { return s.generalEnd } // GetDNSStart returns a time of a dns lookup start. func (s *TraceSuite) GetDNSStart() time.Time { return s.dnsStart } // GetDNSEnd returns a time of a dns lookup send. func (s *TraceSuite) GetDNSEnd() time.Time { return s.dnsEnd } // GetConnStart returns a time of a connection dial start. func (s *TraceSuite) GetConnStart() time.Time { return s.connStart } // GetConnEnd returns a time of a connection dial end. func (s *TraceSuite) GetConnEnd() time.Time { return s.connEnd } // GetTLSStart returns a time of a tls handshake start. func (s *TraceSuite) GetTLSStart() time.Time { return s.tlsStart } // GetTLSEnd returns a time of a tls handshake end. func (s *TraceSuite) GetTLSEnd() time.Time { return s.tlsEnd } // GetWroteHeaders returns a time when request headers were written. func (s *TraceSuite) GetWroteHeaders() time.Time { return s.wroteHeaders } // GetFirstByteReceived returns a time when first response bytes were received. func (s *TraceSuite) GetFirstByteReceived() time.Time { return s.firstByteReceived } func (c *HTTPCaller) getClientTrace(suite *TraceSuite) *httptrace.ClientTrace { return &httptrace.ClientTrace{ DNSStart: func(info httptrace.DNSStartInfo) { suite.mu.Lock() defer suite.mu.Unlock() suite.dnsStart = time.Now() if c.onDNSStart != nil { c.onDNSStart(suite, info) } }, DNSDone: func(info httptrace.DNSDoneInfo) { suite.mu.Lock() defer suite.mu.Unlock() suite.dnsEnd = time.Now() if c.onDNSDone != nil { c.onDNSDone(suite, info) } }, ConnectStart: func(network, addr string) { suite.mu.Lock() defer suite.mu.Unlock() suite.connStart = time.Now() if c.onConnStart != nil { c.onConnStart(suite, network, addr) } }, ConnectDone: func(network, addr string, err error) { suite.mu.Lock() defer suite.mu.Unlock() suite.connEnd = time.Now() if c.onConnDone != nil { c.onConnDone(suite, network, addr, err) } }, TLSHandshakeStart: func() { suite.mu.Lock() defer suite.mu.Unlock() suite.tlsStart = time.Now() if c.onTLSStart != nil { c.onTLSStart(suite) } }, TLSHandshakeDone: func(state tls.ConnectionState, err error) { suite.mu.Lock() defer suite.mu.Unlock() suite.tlsEnd = time.Now() if c.onTLSDone != nil { c.onTLSDone(suite, state, err) } }, WroteHeaders: func() { suite.mu.Lock() defer suite.mu.Unlock() suite.wroteHeaders = time.Now() if c.onWroteHeaders != nil { c.onWroteHeaders(suite) } }, GotFirstResponseByte: func() { suite.mu.Lock() defer suite.mu.Unlock() suite.firstByteReceived = time.Now() if c.onFirstByteReceived != nil { c.onFirstByteReceived(suite) } }, } } func (c *HTTPCaller) makeCall(ctx context.Context) error { ctx, cancel := context.WithTimeout(ctx, c.timeout) defer cancel() suite := TraceSuite{ generalStart: time.Now(), } traceCtx := httptrace.WithClientTrace(ctx, c.getClientTrace(&suite)) req, err := http.NewRequestWithContext(traceCtx, c.method, c.url, bytes.NewReader(c.body)) if err != nil { return err } req.Header = c.headers if c.host != "" { req.Host = c.host } if c.onReq != nil { suite.mu.Lock() c.onReq(&suite) suite.mu.Unlock() } resp, err := c.client.Do(req) if err != nil { return err } body, err := io.ReadAll(resp.Body) if err != nil { return err } resp.Body.Close() isValidResponse := true if c.isValidResponse != nil { isValidResponse = c.isValidResponse(resp, body) } if c.onResp != nil { suite.mu.Lock() defer suite.mu.Unlock() suite.generalEnd = time.Now() c.onResp(&suite, &HTTPCallInfo{ StatusCode: resp.StatusCode, IsValidResponse: isValidResponse, }) } return nil } // HTTPCallInfo represents a data set which passed as a function argument to an onResp callback. type HTTPCallInfo struct { // StatusCode is a response status code StatusCode int // IsValidResponse represents a fact of whether a response is treated as valid. You can read more about it in // HTTPCaller annotation. IsValidResponse bool } golang-github-prometheus-community-pro-bing-0.4.0/http_test.go000066400000000000000000000222551457304707400245560ustar00rootroot00000000000000package probing import ( "context" "crypto/tls" "net/http" "net/http/httptest" "runtime/debug" "sync" "testing" "time" ) // TODO: figure out how to test onDNS callback func getTestHTTPClientServer() (*http.Client, *httptest.Server) { srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })) return srv.Client(), srv } func TestHTTPCaller_MakeCall_OnReq(t *testing.T) { client, srv := getTestHTTPClientServer() defer srv.Close() var callbackCalled bool httpCaller := NewHttpCaller(srv.URL, WithHTTPCallerClient(client), WithHTTPCallerOnReq(func(suite *TraceSuite) { AssertTimeNonZero(t, suite.generalStart) AssertTimeZero(t, suite.generalEnd) AssertTimeZero(t, suite.connStart) AssertTimeZero(t, suite.connEnd) AssertTimeZero(t, suite.tlsStart) AssertTimeZero(t, suite.tlsEnd) AssertTimeZero(t, suite.wroteHeaders) AssertTimeZero(t, suite.firstByteReceived) callbackCalled = true })) err := httpCaller.makeCall(context.Background()) AssertNoError(t, err) AssertTrue(t, callbackCalled) } func TestHTTPCaller_MakeCall_OnConnStart(t *testing.T) { client, srv := getTestHTTPClientServer() defer srv.Close() var callbackCalled bool httpCaller := NewHttpCaller(srv.URL, WithHTTPCallerClient(client), WithHTTPCallerOnConnStart(func(suite *TraceSuite, network, addr string) { AssertTimeNonZero(t, suite.generalStart) AssertTimeZero(t, suite.generalEnd) AssertTimeNonZero(t, suite.connStart) AssertTimeZero(t, suite.connEnd) AssertTimeZero(t, suite.tlsStart) AssertTimeZero(t, suite.tlsEnd) AssertTimeZero(t, suite.wroteHeaders) AssertTimeZero(t, suite.firstByteReceived) callbackCalled = true })) err := httpCaller.makeCall(context.Background()) AssertNoError(t, err) AssertTrue(t, callbackCalled) } func TestHTTPCaller_MakeCall_OnConnDone(t *testing.T) { client, srv := getTestHTTPClientServer() defer srv.Close() var callbackCalled bool httpCaller := NewHttpCaller(srv.URL, WithHTTPCallerClient(client), WithHTTPCallerOnConnDone(func(suite *TraceSuite, network, addr string, err error) { AssertTimeNonZero(t, suite.generalStart) AssertTimeZero(t, suite.generalEnd) AssertTimeNonZero(t, suite.connStart) AssertTimeNonZero(t, suite.connEnd) AssertTimeZero(t, suite.tlsStart) AssertTimeZero(t, suite.tlsEnd) AssertTimeZero(t, suite.wroteHeaders) AssertTimeZero(t, suite.firstByteReceived) callbackCalled = true })) err := httpCaller.makeCall(context.Background()) AssertNoError(t, err) AssertTrue(t, callbackCalled) } func TestHTTPCaller_MakeCall_OnTLSStart(t *testing.T) { client, srv := getTestHTTPClientServer() defer srv.Close() var callbackCalled bool httpCaller := NewHttpCaller(srv.URL, WithHTTPCallerClient(client), WithHTTPCallerOnTLSStart(func(suite *TraceSuite) { AssertTimeNonZero(t, suite.generalStart) AssertTimeZero(t, suite.generalEnd) AssertTimeNonZero(t, suite.connStart) AssertTimeNonZero(t, suite.connEnd) AssertTimeNonZero(t, suite.tlsStart) AssertTimeZero(t, suite.tlsEnd) AssertTimeZero(t, suite.wroteHeaders) AssertTimeZero(t, suite.firstByteReceived) callbackCalled = true })) err := httpCaller.makeCall(context.Background()) AssertNoError(t, err) AssertTrue(t, callbackCalled) } func TestHTTPCaller_MakeCall_OnTLSDone(t *testing.T) { client, srv := getTestHTTPClientServer() defer srv.Close() var callbackCalled bool httpCaller := NewHttpCaller(srv.URL, WithHTTPCallerClient(client), WithHTTPCallerOnTLSDone(func(suite *TraceSuite, state tls.ConnectionState, err error) { AssertTimeNonZero(t, suite.generalStart) AssertTimeZero(t, suite.generalEnd) AssertTimeNonZero(t, suite.connStart) AssertTimeNonZero(t, suite.connEnd) AssertTimeNonZero(t, suite.tlsStart) AssertTimeNonZero(t, suite.tlsEnd) AssertTimeZero(t, suite.wroteHeaders) AssertTimeZero(t, suite.firstByteReceived) callbackCalled = true })) err := httpCaller.makeCall(context.Background()) AssertNoError(t, err) AssertTrue(t, callbackCalled) } func TestHTTPCaller_MakeCall_OnWroteHeaders(t *testing.T) { client, srv := getTestHTTPClientServer() defer srv.Close() var callbackCalled bool httpCaller := NewHttpCaller(srv.URL, WithHTTPCallerClient(client), WithHTTPCallerOnWroteRequest(func(suite *TraceSuite) { AssertTimeNonZero(t, suite.generalStart) AssertTimeZero(t, suite.generalEnd) AssertTimeNonZero(t, suite.connStart) AssertTimeNonZero(t, suite.connEnd) AssertTimeNonZero(t, suite.tlsStart) AssertTimeNonZero(t, suite.tlsEnd) AssertTimeNonZero(t, suite.wroteHeaders) AssertTimeZero(t, suite.firstByteReceived) callbackCalled = true })) err := httpCaller.makeCall(context.Background()) AssertNoError(t, err) AssertTrue(t, callbackCalled) } func TestHTTPCaller_MakeCall_OnFirstByteReceived(t *testing.T) { client, srv := getTestHTTPClientServer() defer srv.Close() var callbackCalled bool httpCaller := NewHttpCaller(srv.URL, WithHTTPCallerClient(client), WithHTTPCallerOnFirstByteReceived(func(suite *TraceSuite) { AssertTimeNonZero(t, suite.generalStart) AssertTimeZero(t, suite.generalEnd) AssertTimeNonZero(t, suite.connStart) AssertTimeNonZero(t, suite.connEnd) AssertTimeNonZero(t, suite.tlsStart) AssertTimeNonZero(t, suite.tlsEnd) AssertTimeNonZero(t, suite.wroteHeaders) AssertTimeNonZero(t, suite.firstByteReceived) callbackCalled = true })) err := httpCaller.makeCall(context.Background()) AssertNoError(t, err) AssertTrue(t, callbackCalled) } func TestHTTPCaller_MakeCall_OnResp(t *testing.T) { client, srv := getTestHTTPClientServer() defer srv.Close() var callbackCalled bool httpCaller := NewHttpCaller(srv.URL, WithHTTPCallerClient(client), WithHTTPCallerOnResp(func(suite *TraceSuite, info *HTTPCallInfo) { AssertTimeNonZero(t, suite.generalStart) AssertTimeNonZero(t, suite.generalEnd) AssertTimeNonZero(t, suite.connStart) AssertTimeNonZero(t, suite.connEnd) AssertTimeNonZero(t, suite.tlsStart) AssertTimeNonZero(t, suite.tlsEnd) AssertTimeNonZero(t, suite.wroteHeaders) AssertTimeNonZero(t, suite.firstByteReceived) callbackCalled = true })) err := httpCaller.makeCall(context.Background()) AssertNoError(t, err) AssertTrue(t, callbackCalled) } func TestHTTPCaller_MakeCall_IsValidResponse(t *testing.T) { client, srv := getTestHTTPClientServer() defer srv.Close() t.Run("no callback", func(t *testing.T) { var callbackCalled bool httpCaller := NewHttpCaller(srv.URL, WithHTTPCallerClient(client), WithHTTPCallerOnResp(func(suite *TraceSuite, info *HTTPCallInfo) { AssertTrue(t, info.IsValidResponse) callbackCalled = true }), ) err := httpCaller.makeCall(context.Background()) AssertNoError(t, err) AssertTrue(t, callbackCalled) }) t.Run("false callback", func(t *testing.T) { var callbackCalled bool httpCaller := NewHttpCaller(srv.URL, WithHTTPCallerClient(client), WithHTTPCallerIsValidResponse(func(response *http.Response, body []byte) bool { return false }), WithHTTPCallerOnResp(func(suite *TraceSuite, info *HTTPCallInfo) { AssertFalse(t, info.IsValidResponse) callbackCalled = true }), ) err := httpCaller.makeCall(context.Background()) AssertNoError(t, err) AssertTrue(t, callbackCalled) }) t.Run("true callback", func(t *testing.T) { var callbackCalled bool httpCaller := NewHttpCaller(srv.URL, WithHTTPCallerClient(client), WithHTTPCallerIsValidResponse(func(response *http.Response, body []byte) bool { return true }), WithHTTPCallerOnResp(func(suite *TraceSuite, info *HTTPCallInfo) { AssertTrue(t, info.IsValidResponse) callbackCalled = true }), ) err := httpCaller.makeCall(context.Background()) AssertNoError(t, err) AssertTrue(t, callbackCalled) }) } func TestHTTPCaller_RunWithContext(t *testing.T) { client, srv := getTestHTTPClientServer() defer srv.Close() var callsCount int var callsCountMu sync.Mutex httpCaller := NewHttpCaller(srv.URL, WithHTTPCallerMaxConcurrentCalls(5), WithHTTPCallerCallFrequency(time.Second/5), WithHTTPCallerClient(client), WithHTTPCallerTimeout(time.Second), WithHTTPCallerOnReq(func(suite *TraceSuite) { callsCountMu.Lock() defer callsCountMu.Unlock() callsCount++ }), ) ctx := context.Background() go func() { httpCaller.RunWithContext(ctx) }() time.Sleep(time.Second) done := make(chan struct{}) go func() { ctx.Done() done <- struct{}{} }() select { case <-time.After(time.Second * 2): t.Errorf("Timed out on a shutdown, meaning workload wasn't processed, Stack: \n%s", string(debug.Stack())) case <-done: } httpCaller.Stop() AssertIntGreaterOrEqual(t, callsCount, 5) } func AssertTimeZero(t *testing.T, tm time.Time) { t.Helper() if !tm.IsZero() { t.Errorf("Expected zero time, got non zero time, Stack: \n%s", string(debug.Stack())) } } func AssertTimeNonZero(t *testing.T, tm time.Time) { t.Helper() if tm.IsZero() { t.Errorf("Expected non zero time, got zero time, Stack: \n%s", string(debug.Stack())) } } func AssertIntGreaterOrEqual(t *testing.T, expected, actual int) { t.Helper() if actual < expected { t.Errorf("Exptected value to be less then %v, got %v, Stack: \n%s", expected, actual, string(debug.Stack())) } } golang-github-prometheus-community-pro-bing-0.4.0/logger.go000066400000000000000000000022331457304707400240110ustar00rootroot00000000000000package probing 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-prometheus-community-pro-bing-0.4.0/packetconn.go000066400000000000000000000037771457304707400246750ustar00rootroot00000000000000package probing 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) SetMark(m uint) error SetDoNotFragment() error SetBroadcastFlag() error } 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) { ttl := -1 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) { ttl := -1 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-prometheus-community-pro-bing-0.4.0/ping.go000066400000000000000000000551211457304707400234730ustar00rootroot00000000000000// Package probing is a simple but powerful ICMP echo (ping) library. // // Here is a very simple example that sends and receives three packets: // // pinger, err := probing.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 := probing.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 *probing.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 *probing.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 probing import ( "bytes" "context" "errors" "fmt" "log" "math" "math/rand" "net" "runtime" "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 networkIP = "ip" networkIPv4 = "ip4" networkIPv6 = "ip6" ) var ( ipv4Proto = map[string]string{"icmp": "ip4:icmp", "udp": "udp4"} ipv6Proto = map[string]string{"icmp": "ip6:ipv6-icmp", "udp": "udp6"} ErrMarkNotSupported = errors.New("setting SO_MARK socket option is not supported on this platform") ErrDFNotSupported = errors.New("setting do-not-fragment bit is not supported on this platform") ) // 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: networkIP, 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 // ResolveTimeout specifies a timeout to resolve an IP address or domain name ResolveTimeout 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) // OnSendError is called when an error occurs while Pinger attempts to send a packet OnSendError func(*Packet, error) // OnRecvError is called when an error occurs while Pinger attempts to receive a packet OnRecvError func(error) // 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 // mark is a SO_MARK (fwmark) set on outgoing icmp packets mark uint // df when true sets the do-not-fragment bit in the outer IP or IPv6 header df bool // 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 addr net.Addr } // 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") } var ( ipaddr *net.IPAddr err error ) if p.ResolveTimeout > time.Duration(0) { var ( ctx = context.Background() ips []net.IP ) ctx, cancel := context.WithTimeout(ctx, p.ResolveTimeout) defer cancel() ips, err = net.DefaultResolver.LookupIP(ctx, p.network, p.addr) if err != nil { return err } if len(ips) == 0 { return fmt.Errorf("lookup %s failed: no addresses found", p.addr) } ipaddr = &net.IPAddr{IP: ips[0]} for _, ip := range ips { if p.network == networkIPv6 { if ip.To4() == nil && ip.To16() != nil { ipaddr = &net.IPAddr{IP: ip} break } continue } if ip.To4() != nil { ipaddr = &net.IPAddr{IP: ip} } } } else { 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 networkIPv4: p.network = networkIPv4 case networkIPv6: p.network = networkIPv6 default: p.network = networkIP } } // 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 } // SetMark sets a mark intended to be set on outgoing ICMP packets. func (p *Pinger) SetMark(m uint) { p.mark = m } // Mark returns the mark to be set on outgoing ICMP packets. func (p *Pinger) Mark() uint { return p.mark } // SetDoNotFragment sets the do-not-fragment bit in the outer IP header to the desired value. func (p *Pinger) SetDoNotFragment(df bool) { p.df = df } // 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 { return p.RunWithContext(context.Background()) } // RunWithContext runs the pinger with a context. This is a blocking function that will exit when it's // done or if the context is canceled. If Count or Interval are not specified, it will run continuously until // it is interrupted. func (p *Pinger) RunWithContext(ctx context.Context) 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() if p.mark != 0 { if err := conn.SetMark(p.mark); err != nil { return fmt.Errorf("error setting mark: %v", err) } } if p.df { if err := conn.SetDoNotFragment(); err != nil { return fmt.Errorf("error setting do-not-fragment: %v", err) } } conn.SetTTL(p.TTL) return p.run(ctx, conn) } func (p *Pinger) run(ctx context.Context, conn packetConn) error { if err := conn.SetFlagTTL(); err != nil { return err } defer p.finish() recv := make(chan *packet, 5) defer close(recv) if p.OnSetup != nil { p.OnSetup() } g, ctx := errgroup.WithContext(ctx) g.Go(func() error { select { case <-ctx.Done(): p.Stop() return ctx.Err() case <-p.done: } return nil }) 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() { 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() { if p.OnFinish != nil { p.OnFinish(p.Statistics()) } } // 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 var loss float64 if sent > 0 { 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 { // Try to set broadcast flag if errors.Is(err, syscall.EACCES) && runtime.GOOS == "linux" { if e := conn.SetBroadcastFlag(); e != nil { p.logger.Warnf("had EACCES syscall error, check your local firewall") } p.logger.Infof("Pinging a broadcast address") continue } if p.OnSendError != nil { outPkt := &Packet{ Nbytes: len(msgBytes), IPAddr: p.ipaddr, Addr: p.addr, Seq: p.sequence, ID: p.id, } p.OnSendError(outPkt, err) } if neterr, ok := err.(*net.OpError); ok { if neterr.Err == syscall.ENOBUFS { continue } } return err } if p.OnSend != nil { outPkt := &Packet{ Nbytes: len(msgBytes), IPAddr: p.ipaddr, Addr: p.addr, Seq: p.sequence, ID: p.id, } p.OnSend(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 = time.Now().UnixNano() // getSeed returns a goroutine-safe unique seed func getSeed() int64 { return atomic.AddInt64(&seed, 1) } // stripIPv4Header strips IPv4 header bytes if present // https://github.com/golang/go/commit/3b5be4522a21df8ce52a06a0c4ba005c89a8590f func stripIPv4Header(n int, b []byte) int { if len(b) < 20 { return n } l := int(b[0]&0x0f) << 2 if 20 > l || l > len(b) { return n } if b[0]>>4 != 4 { return n } copy(b, b[l:]) return n - l } golang-github-prometheus-community-pro-bing-0.4.0/ping_test.go000066400000000000000000000510071457304707400245310ustar00rootroot00000000000000package probing import ( "bytes" "context" "errors" "net" "runtime/debug" "sync" "sync/atomic" "testing" "time" "github.com/google/uuid" "golang.org/x/net/icmp" "golang.org/x/net/ipv4" ) var testAddr net.Addr = &net.IPAddr{ IP: net.IPv4(127, 0, 0, 1), } 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.Fatalf("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 := makeTestPacket(msgBytes) 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.Fatalf("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 := makeTestPacket(msgBytes) 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.Fatalf("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 := makeTestPacket(msgBytes) 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.Fatalf("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 := makeTestPacket(msgBytes) 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.Fatalf("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 := makeTestPacket(msgBytes) 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 := makeTestPacket(msgBytes) 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) } } func TestStatisticsZeroDivision(t *testing.T) { p := New("localhost") err := p.Resolve() AssertNoError(t, err) AssertEqualStrings(t, "localhost", p.Addr()) p.PacketsSent = 0 stats := p.Statistics() // If packets were not sent (due to send errors), ensure that // PacketLoss is 0 instead of NaN due to zero division if stats.PacketLoss != 0 { t.Errorf("Expected %v, got %v", 0, stats.PacketLoss) } } // 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 } // makeTestPacket emulates a packet with the message msg which come from testAddr func makeTestPacket(msg []byte) packet { return packet{ bytes: msg, nbytes: len(msg), ttl: 24, addr: testAddr, } } 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.Fatalf("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 := makeTestPacket(msgBytes) 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.Fatalf("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 := makeTestPacket(msgBytes) 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) SetMark(m uint) error { return nil } func (c testPacketConn) SetDoNotFragment() error { return nil } func (c testPacketConn) SetBroadcastFlag() error { return nil } func (c testPacketConn) ReadFrom(b []byte) (n int, ttl int, src net.Addr, err error) { return 0, 0, testAddr, 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(context.Background(), 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, testAddr, 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(context.Background(), 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 m sync.Mutex writeDone int32 buf []byte dst net.Addr } func (c *testPacketConnOK) WriteTo(b []byte, dst net.Addr) (int, error) { c.m.Lock() defer c.m.Unlock() 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) { c.m.Lock() defer c.m.Unlock() if atomic.LoadInt32(&c.writeDone) == 0 { return 0, 0, testAddr, 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, testAddr, 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(context.Background(), 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) } func TestRunWithTimeoutContext(t *testing.T) { pinger := New("127.0.0.1") err := pinger.Resolve() AssertNoError(t, err) conn := new(testPacketConnOK) start := time.Now() ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() err = pinger.run(ctx, conn) AssertTrue(t, errors.Is(err, context.DeadlineExceeded)) elapsedTime := time.Since(start) AssertTrue(t, elapsedTime < 10*time.Second) stats := pinger.Statistics() AssertTrue(t, stats != nil) if stats == nil { t.FailNow() } AssertTrue(t, stats.PacketsSent > 0) AssertTrue(t, stats.PacketsRecv > 0) } func TestRunWithBackgroundContext(t *testing.T) { pinger := New("127.0.0.1") pinger.Count = 10 pinger.Interval = 100 * time.Millisecond err := pinger.Resolve() AssertNoError(t, err) conn := new(testPacketConnOK) err = pinger.run(context.Background(), conn) AssertTrue(t, err == nil) stats := pinger.Statistics() AssertTrue(t, stats != nil) if stats == nil { t.FailNow() } AssertTrue(t, stats.PacketsRecv == 10) } func TestSetResolveTimeout(t *testing.T) { p := New("www.google.com") p.Count = 3 p.Timeout = 5 * time.Second p.ResolveTimeout = 2 * time.Second err := p.Resolve() AssertNoError(t, err) err = p.SetAddr("www.google.com ") AssertError(t, err, "") err = p.SetAddr("127.0.0.1 ") AssertError(t, err, "") err = p.SetAddr("127.0.0.1") AssertNoError(t, err) } golang-github-prometheus-community-pro-bing-0.4.0/utils_linux.go000066400000000000000000000074251457304707400251210ustar00rootroot00000000000000//go:build linux // +build linux package probing import ( "errors" "os" "reflect" "syscall" "golang.org/x/net/icmp" ) // 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" { return ID == p.id } return true } // SetMark sets the SO_MARK socket option on outgoing ICMP packets. // Setting this option requires CAP_NET_ADMIN. func (c *icmpConn) SetMark(mark uint) error { fd, err := getFD(c.c) if err != nil { return err } return os.NewSyscallError( "setsockopt", syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, int(mark)), ) } // SetMark sets the SO_MARK socket option on outgoing ICMP packets. // Setting this option requires CAP_NET_ADMIN. func (c *icmpv4Conn) SetMark(mark uint) error { fd, err := getFD(c.icmpConn.c) if err != nil { return err } return os.NewSyscallError( "setsockopt", syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, int(mark)), ) } // SetMark sets the SO_MARK socket option on outgoing ICMP packets. // Setting this option requires CAP_NET_ADMIN. func (c *icmpV6Conn) SetMark(mark uint) error { fd, err := getFD(c.icmpConn.c) if err != nil { return err } return os.NewSyscallError( "setsockopt", syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, int(mark)), ) } // SetDoNotFragment sets the do-not-fragment bit in the IP header of outgoing ICMP packets. func (c *icmpConn) SetDoNotFragment() error { fd, err := getFD(c.c) if err != nil { return err } return os.NewSyscallError( "setsockopt", syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_MTU_DISCOVER, syscall.IP_PMTUDISC_DO), ) } // SetDoNotFragment sets the do-not-fragment bit in the IP header of outgoing ICMP packets. func (c *icmpv4Conn) SetDoNotFragment() error { fd, err := getFD(c.icmpConn.c) if err != nil { return err } return os.NewSyscallError( "setsockopt", syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_MTU_DISCOVER, syscall.IP_PMTUDISC_DO), ) } // SetDoNotFragment sets the do-not-fragment bit in the IPv6 header of outgoing ICMPv6 packets. func (c *icmpV6Conn) SetDoNotFragment() error { fd, err := getFD(c.icmpConn.c) if err != nil { return err } return os.NewSyscallError( "setsockopt", syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_MTU_DISCOVER, syscall.IP_PMTUDISC_DO), ) } func (c *icmpConn) SetBroadcastFlag() error { fd, err := getFD(c.c) if err != nil { return err } return os.NewSyscallError( "setsockopt", syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1), ) } func (c *icmpv4Conn) SetBroadcastFlag() error { fd, err := getFD(c.icmpConn.c) if err != nil { return err } return os.NewSyscallError( "setsockopt", syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1), ) } func (c *icmpV6Conn) SetBroadcastFlag() error { fd, err := getFD(c.icmpConn.c) if err != nil { return err } return os.NewSyscallError( "setsockopt", syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1), ) } // 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 } golang-github-prometheus-community-pro-bing-0.4.0/utils_other.go000066400000000000000000000031161457304707400250740ustar00rootroot00000000000000//go:build !linux && !windows // +build !linux,!windows package probing // 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 { return ID == p.id } // SetMark sets the SO_MARK socket option on outgoing ICMP packets. // Setting this option requires CAP_NET_ADMIN. func (c *icmpConn) SetMark(mark uint) error { return ErrMarkNotSupported } // SetMark sets the SO_MARK socket option on outgoing ICMP packets. // Setting this option requires CAP_NET_ADMIN. func (c *icmpv4Conn) SetMark(mark uint) error { return ErrMarkNotSupported } // SetMark sets the SO_MARK socket option on outgoing ICMP packets. // Setting this option requires CAP_NET_ADMIN. func (c *icmpV6Conn) SetMark(mark uint) error { return ErrMarkNotSupported } // SetDoNotFragment sets the do-not-fragment bit in the IP header of outgoing ICMP packets. func (c *icmpConn) SetDoNotFragment() error { return ErrDFNotSupported } // SetDoNotFragment sets the do-not-fragment bit in the IP header of outgoing ICMP packets. func (c *icmpv4Conn) SetDoNotFragment() error { return ErrDFNotSupported } // SetDoNotFragment sets the do-not-fragment bit in the IPv6 header of outgoing ICMPv6 packets. func (c *icmpV6Conn) SetDoNotFragment() error { return ErrDFNotSupported } // No need for SetBroadcastFlag in non-linux OSes func (c *icmpConn) SetBroadcastFlag() error { return nil } func (c *icmpv4Conn) SetBroadcastFlag() error { return nil } func (c *icmpV6Conn) SetBroadcastFlag() error { return nil } golang-github-prometheus-community-pro-bing-0.4.0/utils_windows.go000066400000000000000000000033671457304707400254550ustar00rootroot00000000000000//go:build windows // +build windows package probing 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 } // SetMark sets the SO_MARK socket option on outgoing ICMP packets. // Setting this option requires CAP_NET_ADMIN. func (c *icmpConn) SetMark(mark uint) error { return ErrMarkNotSupported } // SetMark sets the SO_MARK socket option on outgoing ICMP packets. // Setting this option requires CAP_NET_ADMIN. func (c *icmpv4Conn) SetMark(mark uint) error { return ErrMarkNotSupported } // SetMark sets the SO_MARK socket option on outgoing ICMP packets. // Setting this option requires CAP_NET_ADMIN. func (c *icmpV6Conn) SetMark(mark uint) error { return ErrMarkNotSupported } // SetDoNotFragment sets the do-not-fragment bit in the IP header of outgoing ICMP packets. func (c *icmpConn) SetDoNotFragment() error { return ErrDFNotSupported } // SetDoNotFragment sets the do-not-fragment bit in the IP header of outgoing ICMP packets. func (c *icmpv4Conn) SetDoNotFragment() error { return ErrDFNotSupported } // SetDoNotFragment sets the do-not-fragment bit in the IPv6 header of outgoing ICMPv6 packets. func (c *icmpV6Conn) SetDoNotFragment() error { return ErrDFNotSupported } // No need for SetBroadcastFlag in non-linux OSes func (c *icmpConn) SetBroadcastFlag() error { return nil } func (c *icmpv4Conn) SetBroadcastFlag() error { return nil } func (c *icmpV6Conn) SetBroadcastFlag() error { return nil }