diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a198643 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,41 @@ +name: Release +permissions: + contents: write + +on: + release: + types: [ created ] + +jobs: + compile: + strategy: + fail-fast: false + matrix: + os: [ linux, darwin, windows ] + arch: [ amd64, 386 ] + exclude: + - os: darwin + arch: 386 + name: Release - ${{matrix.os}}-${{matrix.arch}} + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.20" + + - name: compile + run: | + GOOS=${{matrix.os}} GOARCH=${{matrix.arch}} make + + - name: Upload release assets + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_name: pinger-${{github.event.release.tag_name}}-${{matrix.os}}-${{matrix.arch}} + asset_path: pinger + asset_content_type: application/octal-stream diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..055d997 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,27 @@ +name: Test + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.20' + + - name: Fmt + run: go fmt $(go list ./... | grep -v /vendor/) + - name: Vet + run: go vet $(go list ./... | grep -v /vendor/) + - name: Test + run: go test -race $(go list ./... | grep -v /vendor/) + diff --git a/.gitignore b/.gitignore index adb36c8..f8b2950 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -*.exe \ No newline at end of file +pinger +*.exe diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 69fb79a..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,46 +0,0 @@ -# This file is a template, and might need editing before it works on your project. -# You can copy and paste this template into a new `.gitlab-ci.yml` file. -# You should not add this template to an existing `.gitlab-ci.yml` file by using the `include:` keyword. -# -# To contribute improvements to CI/CD templates, please follow the Development guide at: -# https://docs.gitlab.com/ee/development/cicd/templates.html -# This specific template is located at: -# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Go.gitlab-ci.yml - -stages: - - test - - build - - release - -format: - image: golang:latest - stage: test - script: - - go fmt $(go list ./... | grep -v /vendor/) - - go vet $(go list ./... | grep -v /vendor/) - - go test -race $(go list ./... | grep -v /vendor/) - -compile: - image: golang:latest - stage: build - script: - - mkdir -p bin - - GOOS=windows GOARCH=386 go build -o bin/pinger-32.exe ./... - - GOOS=windows GOARCH=amd64 go build -o bin/pinger-x64.exe ./... - - GOOS=linux GOARCH=386 go build -o bin/pinger-linux-x86 ./... - - GOOS=linux GOARCH=amd64 go build -o bin/pinger-linux-x64 ./... - artifacts: - paths: - - bin - expire_in: 1 week - -upload: - stage: release - image: curlimages/curl:latest - rules: - - if: $CI_COMMIT_TAG - script: - - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file bin/pinger-32.exe "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/release/${CI_COMMIT_TAG}/pinger-32.exe"' - - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file bin/pinger-x64.exe "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/release/${CI_COMMIT_TAG}/pinger-x64.exe"' - - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file bin/pinger-linux-x86 "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/release/${CI_COMMIT_TAG}/pinger-linux-x86"' - - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file bin/pinger-linux-x64 "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/release/${CI_COMMIT_TAG}/pinger-linux-x64"' \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..965f691 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023-2026 Henrik Hautakoski + +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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c01b168 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ + +GO=go + +pinger : main.go + $(GO) build -o $@ $^ diff --git a/README.md b/README.md new file mode 100644 index 0000000..71dd48d --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# Pinger + +**Pinger** is a lightweight, high-performance network utility written in Go. +It enables users to send ICMP echo requests (pings) to specified hosts, facilitating network diagnostics and monitoring. + +## Features + +* Send ICMP echo requests to specified hosts. +* Measure round-trip time (RTT) for each ping. +* Support for both IPv4 and IPv6 addresses. +* Configurable number of ping attempts and intervals. +* Lightweight and efficient, suitable for scripting and automation. + +## Installation + +### Prerequisites + +* Go (version 1.20 or later) installed on your system. +* make + +### Steps + +1. Clone the repository: + +```bash +git clone https://github.com/pnx/pinger.git +cd pinger +``` + +2. Build the application: + +```bash +make +``` + +## Usage + +See `./pinger -h` + +### Example + +```bash +./pinger --udp -t 30s example.com +``` + +This command sends UDP echo requests to example.com, stopping after 30 seconds + +```bash +./pinger -c 8 -i 1s example.com +``` + +This command sends 8 ICMP echo requests to example.com, with a 1-second interval between pings. + +NOTE: this requires root privileges + +## License + +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. diff --git a/go.mod b/go.mod index 2479bd0..d240580 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module pinger -go 1.19 +go 1.20 require ( github.com/pborman/getopt/v2 v2.1.0 @@ -9,7 +9,7 @@ require ( require ( github.com/google/uuid v1.3.0 // indirect - golang.org/x/net v0.5.0 // indirect - golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect - golang.org/x/sys v0.4.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sync v0.1.0 // indirect + golang.org/x/sys v0.6.0 // indirect ) diff --git a/go.sum b/go.sum index d532404..8b4cd58 100644 --- a/go.sum +++ b/go.sum @@ -4,9 +4,9 @@ github.com/pborman/getopt/v2 v2.1.0 h1:eNfR+r+dWLdWmV8g5OlpyrTYHkhVNxHBdN2cCrJmO github.com/pborman/getopt/v2 v2.1.0/go.mod h1:4NtW75ny4eBw9fO1bhtNdYTlZKYX5/tBLtsOpwKIKd0= github.com/prometheus-community/pro-bing v0.1.0 h1:zjzLGhfNPP0bP1OlzGB+SJcguOViw7df12LPg2vUJh8= github.com/prometheus-community/pro-bing v0.1.0/go.mod h1:BpWlHurD9flHtzq8wrh8QGWYz9ka9z9ZJAyOel8ej58= -golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/main.go b/main.go index e347c43..d3f7f20 100644 --- a/main.go +++ b/main.go @@ -8,13 +8,11 @@ import ( "time" "github.com/pborman/getopt/v2" - "github.com/prometheus-community/pro-bing" + probing "github.com/prometheus-community/pro-bing" ) // printStats prints statistics from a ping. -func printStats(pinger *probing.Pinger) { - stats := pinger.Statistics() - +func printStats(stats *probing.Statistics) { fmt.Printf("%d packets transmitted, %d packets received, %.2f%% packet loss\n", stats.PacketsSent, stats.PacketsRecv, stats.PacketLoss) @@ -22,8 +20,9 @@ func printStats(pinger *probing.Pinger) { stats.MinRtt, stats.AvgRtt, stats.MaxRtt, stats.StdDevRtt) } -func eventLoop(pinger *probing.Pinger) { - ticker := time.NewTicker(time.Second * 4) +func eventLoop(pinger *probing.Pinger, ticker *time.Ticker) { + defer ticker.Stop() + defer pinger.Stop() // Setup signals on term and interrupt. sig := make(chan os.Signal, 1) @@ -32,13 +31,12 @@ func eventLoop(pinger *probing.Pinger) { for { select { // Got signal. stop pinger and exit goroutine - case <-sig: - ticker.Stop() - pinger.Stop() + case s := <-sig: + fmt.Printf("Recived signal: %s, exiting.\n", s) return // Ticker ticks. print stats. case <-ticker.C: - printStats(pinger) + printStats(pinger.Statistics()) } } } @@ -51,6 +49,7 @@ func main() { count := getopt.IntLong("count", 'c', 0, "Stop after this many packages has been sent (and received). If this option is not specified, pinger will operate until interrupted.") timeout := getopt.DurationLong("timeout", 't', 0, "Exit the program after this time is reached, regardless of how many packets have been received.") interval := getopt.DurationLong("interval", 'i', time.Second*2, "Wait time between each packet send") + statsInterval := getopt.DurationLong("stats-interval", 0, time.Second*4, "How often stats should be printed to the console.") proto_udp := getopt.BoolLong("udp", 0, "Send UDP ping instead of a raw IMCP ping, IMCP required super-user privileges") record_rtt := getopt.BoolLong("rtt", 0, "Keep a record of rtts of all received packets.") @@ -58,7 +57,7 @@ func main() { getopt.Parse() if *version { - fmt.Println("Version 0.0.2") + fmt.Println("Version 0.0.4") os.Exit(0) } @@ -79,17 +78,17 @@ func main() { pinger.Source = *source pinger.Count = *count pinger.RecordRtts = *record_rtt + pinger.OnFinish = printStats if timeout != nil && *timeout > 0 { pinger.Timeout = *timeout } // Enter event loop in another goroutine. - go eventLoop(pinger) + go eventLoop(pinger, time.NewTicker(*statsInterval)) // Run pinger in main thread. fmt.Println("PING", pinger.Addr()) if err = pinger.Run(); err != nil { fmt.Println("Error:", err) - return } }