From de743857153b6e5b71b7b0aba8a66fdb70514aad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20Fernando=20S=C3=A1nchez?= Date: Mon, 17 Dec 2018 19:19:26 +0100 Subject: [PATCH] Version 1.0.1 with server --- Dockerfile | 60 ++++++++++++++++++++++++++++ Makefile | 3 ++ README.md | 14 +++++++ main.go | 113 ++++++++++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 180 insertions(+), 10 deletions(-) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bcd7093 --- /dev/null +++ b/Dockerfile @@ -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"] \ No newline at end of file diff --git a/Makefile b/Makefile index 93c616f..5488e53 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/README.md b/README.md index 291846b..6fe2731 100644 --- a/README.md +++ b/README.md @@ -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=`, 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"` diff --git a/main.go b/main.go index 9b08397..a57bd21 100644 --- a/main.go +++ b/main.go @@ -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, "

Ping a hostname

"+ + "
"+ + "
"+ + ""+ + "
") + 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)) }