Version 1.0.1 with server

master
J. Fernando Sánchez 5 years ago
parent 011f9e57ce
commit de74385715

@ -0,0 +1,60 @@
# Accept the Go version for the image to be set as a build argument.
# Default to Go 1.11
ARG GO_VERSION=1.11
# First stage: build the executable.
FROM golang:${GO_VERSION}-alpine AS builder
# Create the user and group files that will be used in the running container to
# run the process as an unprivileged user.
# RUN mkdir /user && \
# echo 'nobody:x:65534:65534:nobody:/:' > /user/passwd && \
# echo 'nobody:x:65534:' > /user/group
# Install the Certificate-Authority certificates for the app to be able to make
# calls to HTTPS endpoints.
# Git is required for fetching the dependencies.
# libcap might be needed in the future for setcap
RUN apk add --no-cache ca-certificates git
# Set the working directory outside $GOPATH to enable the support for modules.
WORKDIR /src
# Fetch dependencies first; they are less susceptible to change on every build
# and will therefore be cached for speeding up the next build
COPY ./go.mod ./go.sum ./
RUN go mod download
# Import the code from the context.
COPY ./ ./
# Build the executable to `/app`. Mark the build as statically linked.
RUN CGO_ENABLED=0 go build \
-installsuffix 'static' \
-o /app .
# Add ping privileges
# RUN setcap cap_net_raw+ep /app
# Final stage: the running container.
FROM scratch AS final
# Import the user and group files from the first stage.
# COPY --from=builder /user/group /user/passwd /etc/
# Import the Certificate-Authority certificates for enabling HTTPS.
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# Import the compiled executable from the first stage.
COPY --from=builder /app /app
# Declare the port on which the webserver will be exposed.
# As we're going to run the executable as an unprivileged user, we can't bind
# to ports below 1024.
EXPOSE 8080
# Perform any further action as an unprivileged user.
# USER nobody:nobody
# Run the compiled binary.
ENTRYPOINT ["/app"]

@ -1,5 +1,8 @@
build:
CGO_ENABLED=0 go build -ldflags="-s -w" -o pingish
docker-build:
docker build -t balkian/pingish .
run: build
./pingish -c 10 -host www.google.es

@ -5,6 +5,20 @@ Just copy the binary to the container, and problem solved.
The binary has to be built with `CGO_ENABLED=0` to avoid problems with alpine-based images.
Example of use:
```
pingish www.google.es
```
# Server
You can start a server that will accept requests on `/ping?host=<target>`, using the `--server` flag. e.g.
```
pingish --server
```
# TROUBLESHOOTING
To run it as a normal user in ubuntu, you might need to configure your host first: `sudo sysctl -w net.ipv4.ping_group_range="0 2147483647"`

@ -1,23 +1,81 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"net"
"net/http"
"os"
"strconv"
"strings"
"sync"
"time"
"github.com/tatsushid/go-fastping"
)
func main() {
name := flag.String("host", "www.google.es", "Hostname to ping")
count := flag.Int("c", 3, "Number of times to wait for the ")
flag.Parse()
type Response struct {
Host string
Up bool
RTT []time.Duration `json:"RTT,omitempty"`
}
var PING_COUNT = 1
func homeHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "<h1>Ping a hostname</h1>"+
"<form action=\"/ping/\" method=\"GET\">"+
"<input type=\"text\" name=\"host\"><br>"+
"<input type=\"submit\" value=\"Ping\">"+
"</form>")
return
}
func pingHandler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("host")
if name == "" {
tokens := strings.Split(r.URL.Path, "/")
if len(tokens) < 3 || tokens[2] == "" {
http.Error(w, "You have to specify a hostname to ping", http.StatusBadRequest)
return
}
name = tokens[2]
}
times := PING_COUNT
count := r.URL.Query().Get("count")
fmt.Printf("Requested %s %s times\n", name, count)
if count != "" {
if counts, err := strconv.Atoi(count); err == nil {
times = counts
}
}
up, rtt, err := CheckHost(name, times)
response := Response{Host: name, Up: up, RTT: rtt}
js, err := json.Marshal(response)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
if !up {
http.Error(w, string(js), http.StatusNotFound)
return
}
w.Write(js)
adds, err := net.LookupHost(*name)
}
func CheckHost(name string, times int) (bool, []time.Duration, error) {
fmt.Printf("Checking %s\n", name)
adds, err := net.LookupHost(name)
if err != nil {
panic(err)
return false, nil, err
}
fmt.Println("List of addresses:")
for _, add := range adds {
@ -25,21 +83,56 @@ func main() {
}
p := fastping.NewPinger()
ra, err := net.ResolveIPAddr("ip4:icmp", *name)
ra, err := net.ResolveIPAddr("ip4:icmp", name)
if err != nil {
fmt.Println(err)
os.Exit(1)
return false, nil, err
}
rtttimes := make([]time.Duration, 0)
recv := make(chan time.Duration)
p.AddIPAddr(ra)
p.OnRecv = func(addr *net.IPAddr, rtt time.Duration) {
fmt.Printf("IP Addr: %s receive, RTT: %v\n", addr.String(), rtt)
recv <- rtt
}
for i := 0; i < *count; i++ {
var wg sync.WaitGroup
wg.Add(1)
go func() {
// Do work
for t := range recv {
rtttimes = append(rtttimes, t)
fmt.Printf("Ping %s: %s\n", name, t)
}
wg.Done()
}()
for i := 0; i < times; i++ {
err = p.Run()
if err != nil {
fmt.Println(err)
}
}
close(recv)
wg.Wait()
fmt.Println("finished")
return len(rtttimes) > 0, rtttimes, nil
}
func main() {
name := flag.String("host", "www.google.es", "Hostname to ping")
count := flag.Int("c", 3, "Number of ping attempts")
serve := flag.Bool("server", false, "Start the http server")
address := flag.String("address", ":8080", "Host and port to start the http server on")
flag.Parse()
if !*serve {
_, _, err := CheckHost(*name, *count)
if err == nil {
fmt.Printf("could not find host %s: %s\n", *name, err)
os.Exit(1)
}
os.Exit(0)
}
http.HandleFunc("/", homeHandler)
http.HandleFunc("/ping/", pingHandler)
log.Fatal(http.ListenAndServe(*address, nil))
}

Loading…
Cancel
Save