16 Commits
1.0.0 ... 1.2.0

Author SHA1 Message Date
2427e58cf6 add request timeouts 2024-08-07 23:23:06 +02:00
7b47ca1c58 solve small linter nitpicks 2024-08-07 22:56:19 +02:00
e4d0d9bf61 bump deps 2024-08-07 22:51:32 +02:00
6c7564062f make things a bit less ugly 2023-02-07 19:17:30 +01:00
c6ffa0e628 make linter happy 2022-12-29 23:04:13 +01:00
73414736f9 add lint workflow 2022-12-29 19:52:31 +01:00
5c8a39ad75 add user and group to systemd service example 2022-08-23 03:31:56 +02:00
deab05167d docker: trim binary, add standalone traceroute, run as own user 2022-08-23 03:20:01 +02:00
2866c9915d still use dockerhub 2022-05-29 15:53:09 +02:00
d87f9b568b update docker files 2022-05-29 15:32:28 +02:00
188dc88d12 update readme 2022-05-29 15:32:18 +02:00
3b4e96509b explicitly dont handle returns in certain places 2022-05-29 15:09:58 +02:00
a0e42d369a fix command order if host is invalid but port proper 2022-05-29 15:07:01 +02:00
871fe76df5 return proper http errors 2022-05-29 15:05:13 +02:00
2b8510c5ee cleanup nping commands 2022-05-29 14:26:09 +02:00
955f1253af add nping handler, port support and fmt code 2022-05-29 14:20:06 +02:00
8 changed files with 296 additions and 195 deletions

7
.github/workflows/lint.yml vendored Normal file
View File

@ -0,0 +1,7 @@
name: ci
on: [push]
jobs:
test:
uses: byReqz/workflows/.github/workflows/golint_with_codeql.yml@main

View File

@ -1,5 +1,5 @@
# probehost2 # probehost2
an http endpoint to query network diagnosis tools from remote hosts a http endpoint to query network diagnosis tools from remote hosts
- <a href="#probehost2">Overview</a> - <a href="#probehost2">Overview</a>
- <a href="#disclaimer">Disclaimer</a> - <a href="#disclaimer">Disclaimer</a>
@ -15,14 +15,15 @@ an http endpoint to query network diagnosis tools from remote hosts
- <a href="#ping">Ping</a> - <a href="#ping">Ping</a>
- <a href="#mtr">MTR</a> - <a href="#mtr">MTR</a>
- <a href="#traceroute">Traceroute</a> - <a href="#traceroute">Traceroute</a>
- <a href="#nping">Nping</a>
# Disclaimer # Disclaimer
Dont expect good or even mediocre code here. This is my first take at go and is mostly for myself to learn. Suggestions and improvements are welcome. Don't expect good or even mediocre code here. This is my first take at go and is mostly for myself to learn. Suggestions and improvements are welcome.
Please note that this project does not include any kind of rate limiting or other protection. It is therefore heavily advised to only make it publicly reachable if a reverse proxy is in place. A sample config for <a href="caddyserver.com/">Caddy</a> can be found in the `caddy` subfolder. Please note that this project does not include any kind of rate limiting or other protection. It is therefore heavily advised to only make it publicly reachable if a reverse proxy is in place. A sample config for <a href="caddyserver.com/">Caddy</a> can be found in the `caddy` subfolder.
# Installation # Installation
The runtime dependencies are currently `iputils`, `traceroute` and `mtr` (sometimes called `mtr-tiny`). `iputils` and `traceroute` can be substituted by `busybox`. The runtime dependencies are currently `iputils`, `traceroute`, `nping` (usually provided by nmap) and `mtr` (sometimes called `mtr-tiny`). `iputils` and `traceroute` can be substituted by `busybox`.
## Building ## Building
The app can be built with the latest Go toolchain. The app can be built with the latest Go toolchain.
@ -48,7 +49,7 @@ docker build -f docker/Dockerfile . -t byreqz/probehost2:latest
A compose file can also be found in `docker/docker-compose.yml`. A compose file can also be found in `docker/docker-compose.yml`.
## Proxy ## Proxy
Its recommended to only run this app together with a rate-limiting reverse-proxy. An example configuration for <a href="caddyserver.com/">Caddy</a> can be found in the `caddy` subfolder. It's recommended to only run this app together with a rate-limiting reverse-proxy. An example configuration for <a href="caddyserver.com/">Caddy</a> can be found in the `caddy` subfolder.
# Usage # Usage
## Server ## Server
@ -57,10 +58,11 @@ The app currently has 4 runtime flags:
- `-o / --logfilepath` -- sets the log output file - `-o / --logfilepath` -- sets the log output file
- `-x / --disable-x-forwarded-for` -- disables checking for the X-Forwarded-For header - `-x / --disable-x-forwarded-for` -- disables checking for the X-Forwarded-For header
- `-l / --allow-private` -- allows lookups of private IP ranges - `-l / --allow-private` -- allows lookups of private IP ranges
- `--request-ttl` -- sets the maximum request time to live in seconds
All of the Flags also have an accompanying environment value: `PROBEHOST_LOGPATH`, `PROBEHOST_ALLOW_PRIVATE`, `PROBEHOST_LISTEN_PORT` and `PROBEHOST_DISABLE_X_FORWARDED_FOR` but the options given via commandline have priority. All the Flags also have an accompanying environment value: `PROBEHOST_LOGPATH`, `PROBEHOST_ALLOW_PRIVATE`, `PROBEHOST_LISTEN_PORT`, `PROBEHOST_DISABLE_X_FORWARDED_FOR` and `PROBEHOST_REQUEST_TTL` but the options given via commandline have priority.
The app will log every request including the IP thats querying and show failed requests on stdout. The app will log every request including the IP that's querying and show failed requests on stdout.
Requests that contain an X-Forwarded-For header (implying the app is behind a reverse proxy) will automatically log that address instead of the requesting IP (the proxy itself), this can be turned off with -x. Requests that contain an X-Forwarded-For header (implying the app is behind a reverse proxy) will automatically log that address instead of the requesting IP (the proxy itself), this can be turned off with -x.
@ -68,7 +70,7 @@ Requests that contain an X-Forwarded-For header (implying the app is behind a re
### General ### General
The app can be queried via HTTP/HTTPS with the following scheme: The app can be queried via HTTP/HTTPS with the following scheme:
``` ```
https://[address]/[command]/[hosts]/[options] https://[address]/[command]/[host](_[port]),[host].../[options]
``` ```
- [address] = the IP or domain serving the site - [address] = the IP or domain serving the site
@ -76,7 +78,9 @@ https://[address]/[command]/[hosts]/[options]
- ping - ping
- mtr - mtr
- traceroute - traceroute
- [hosts] = can be one or more hosts query, seperated by a comma - nping
- [host] = can be one or more hosts query, seperated by a comma
- [port] = port to be queried, optional
- [options] = options to run the command with, seperated by a comma - [options] = options to run the command with, seperated by a comma
All inputs are validated and invalid input is discarded. If the request contains no valid data, the server will return HTTP 500. All inputs are validated and invalid input is discarded. If the request contains no valid data, the server will return HTTP 500.
@ -131,7 +135,7 @@ Available options are:
- `c10` / `count10`: send 10 pings - `c10` / `count10`: send 10 pings
Example query: Example query:
``` ```sh
$ curl http://localhost:8000/mtr/localhost/c1,z $ curl http://localhost:8000/mtr/localhost/c1,z
Start: 2022-01-02T00:06:56+0100 Start: 2022-01-02T00:06:56+0100
HOST: xxx Loss% Snt Last Avg Best Wrst StDev HOST: xxx Loss% Snt Last Avg Best Wrst StDev
@ -155,9 +159,37 @@ Available options are:
- `b` / `back`: Guess the number of hops in the backward path and print if it differs - `b` / `back`: Guess the number of hops in the backward path and print if it differs
Example query: Example query:
``` ```sh
$ curl http://localhost:8000/tracert/localhost/i $ curl http://localhost:8000/tracert/localhost/i
traceroute to localhost (127.0.0.1), 30 hops max, 60 byte packets traceroute to localhost (127.0.0.1), 30 hops max, 60 byte packets
1 localhost (127.0.0.1) 0.063 ms 0.008 ms 0.006 ms 1 localhost (127.0.0.1) 0.063 ms 0.008 ms 0.006 ms
```
### Nping
The default options are:
- `c3`: send 3 pings
Available options are:
- `4` / `force4`: force IPv4
- `6` / `force6`: force IPv6
- `u` / `udp`: use UDP
- `t` / `tcp`: use TCP
- `v` / `verbose`: be verbose
- `c1` / `count1`: send 1 ping
- `c3` / `count3`: send 3 pings
- `c5` / `count5`: send 5 pings
Example query:
```sh
$ curl localhost:8000/nping/localhost_22
Starting Nping 0.7.92 ( https://nmap.org/nping ) at 2022-05-29 15:28 CEST
SENT (0.0022s) Starting TCP Handshake > localhost:22 (127.0.0.1:22)
RCVD (0.0133s) Handshake with localhost:22 (127.0.0.1:22) completed
SENT (1.0041s) Starting TCP Handshake > localhost:22 (127.0.0.1:22)
RCVD (1.0089s) Handshake with localhost:22 (127.0.0.1:22) completed
SENT (2.0071s) Starting TCP Handshake > localhost:22 (127.0.0.1:22)
RCVD (2.0090s) Handshake with localhost:22 (127.0.0.1:22) completed
Max rtt: 11.130ms | Min rtt: 1.945ms | Avg rtt: 5.965ms
TCP connection attempts: 3 | Successful connections: 3 | Failed: 0 (0.00%)
Nping done: 1 IP address pinged in 2.01 seconds
``` ```

View File

@ -2,11 +2,14 @@ FROM golang:latest as builder
WORKDIR /build WORKDIR /build
COPY . . COPY . .
RUN go get -u RUN go get -u
RUN CGO_ENABLED=0 go build -o probehost2 RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o probehost2
FROM alpine:latest FROM alpine:latest
RUN apk update RUN apk update
RUN apk add mtr iputils RUN apk add mtr iputils nmap-nping traceroute
RUN adduser -D probehost2
COPY --from=builder /build/probehost2 / COPY --from=builder /build/probehost2 /
RUN touch /probehost2.log RUN touch /probehost2.log
RUN chown probehost2:users /probehost2.log
USER probehost2
CMD ["/probehost2"] CMD ["/probehost2"]

View File

@ -10,6 +10,7 @@ services:
- PROBEHOST_ALLOW_PRIVATE=false - PROBEHOST_ALLOW_PRIVATE=false
- PROBEHOST_DISABLE_X_FORWARDED_FOR=false - PROBEHOST_DISABLE_X_FORWARDED_FOR=false
- PROBEHOST_LISTEN_PORT=8000 - PROBEHOST_LISTEN_PORT=8000
- PROBEHOST_REQUEST_TTL=180
ports: ports:
- 1234:8000 - 1234:8000
volumes: volumes:

8
go.mod
View File

@ -2,9 +2,9 @@ module github.com/byReqz/probehost2
go 1.17 go 1.17
require github.com/sirupsen/logrus v1.8.1
require ( require (
github.com/spf13/pflag v1.0.5 // indirect github.com/sirupsen/logrus v1.9.3
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect github.com/spf13/pflag v1.0.5
) )
require golang.org/x/sys v0.23.0 // indirect

20
go.sum
View File

@ -1,14 +1,18 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

394
main.go
View File

@ -1,201 +1,253 @@
package main package main
import ( import (
"fmt" "fmt"
"os" "net"
"os/exec" "net/http"
"strings" "os"
"net/http" "os/exec"
"net" "strconv"
"strconv" "strings"
"time"
log "github.com/sirupsen/logrus"
flag "github.com/spf13/pflag"
log "github.com/sirupsen/logrus"
flag "github.com/spf13/pflag"
) )
var logstdout = log.New() var logStdout = log.New()
var logfile = log.New() var logFile = log.New()
var listenport int var listenPort = 8080 // port to listen on
var disablexforwardedfor bool var disableXForwardedFor bool // whether to disable parsing the X-Forwarded-For header or not
var allowprivate bool var allowPrivate bool // whether to allow private IP ranges or not
var requestTTL = 180 // maximum request time to live in seconds
func init() { func init() {
logstdout.SetFormatter(&log.TextFormatter{ logStdout.SetFormatter(&log.TextFormatter{
FullTimestamp: true}) FullTimestamp: true})
logstdout.SetOutput(os.Stdout) logStdout.SetOutput(os.Stdout)
logstdout.SetLevel(log.InfoLevel) logStdout.SetLevel(log.InfoLevel)
var logfilepath string
if _, exists := os.LookupEnv("PROBEHOST_LOGPATH"); exists == true { logFilePath := "probehost2.log"
logfilepath, _ = os.LookupEnv("PROBEHOST_LOGPATH") if val, exists := os.LookupEnv("PROBEHOST_LOGPATH"); exists {
} else { logFilePath = val
logfilepath = "probehost2.log" }
}
if exists, _ := os.LookupEnv("PROBEHOST_ALLOW_PRIVATE"); exists == "true" {
allowprivate = true
} else {
allowprivate = false
}
if envvalue, exists := os.LookupEnv("PROBEHOST_LISTEN_PORT"); exists == true {
var err error
listenport, err = strconv.Atoi(envvalue)
if err != nil {
logstdout.Fatal("Failed to read PROBEHOST_LISTEN_PORT: ", err.Error())
}
} else {
listenport = 8000
}
if exists, _ := os.LookupEnv("PROBEHOST_DISABLE_X_FORWARDED_FOR"); exists == "true" {
disablexforwardedfor = true
} else {
disablexforwardedfor = false
}
flag.StringVarP(&logfilepath, "logfilepath", "o", logfilepath, "sets the output file for the log")
flag.IntVarP(&listenport, "port", "p", listenport, "sets the port to listen on")
flag.BoolVarP(&disablexforwardedfor, "disable-x-forwarded-for", "x", disablexforwardedfor, "whether to show x-forwarded-for or the requesting IP")
flag.BoolVarP(&allowprivate, "allow-private", "l", allowprivate, "whether to show lookups of private IP ranges")
flag.Parse()
logpath, err := os.OpenFile(logfilepath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0660) _, allowPrivate = os.LookupEnv("PROBEHOST_ALLOW_PRIVATE")
if err != nil { _, disableXForwardedFor = os.LookupEnv("PROBEHOST_DISABLE_X_FORWARDED_FOR")
logstdout.Fatal("Failed to initialize the logfile: ", err.Error())
} if val, exists := os.LookupEnv("PROBEHOST_LISTEN_PORT"); exists {
logfile.SetLevel(log.InfoLevel) var err error
logfile.SetOutput(logpath) listenPort, err = strconv.Atoi(val)
logfile.Info("probehost2 initialized") if err != nil {
logStdout.Fatal("Failed to read PROBEHOST_LISTEN_PORT: ", err.Error())
}
}
if val, exists := os.LookupEnv("PROBEHOST_REQUEST_TTL"); exists {
var err error
requestTTL, err = strconv.Atoi(val)
if err != nil {
logStdout.Fatal("Failed to read PROBEHOST_REQUEST_TTL: ", err.Error())
}
}
flag.StringVarP(&logFilePath, "logFilePath", "o", logFilePath, "sets the output file for the log")
flag.IntVarP(&listenPort, "port", "p", listenPort, "sets the port to listen on")
flag.BoolVarP(&disableXForwardedFor, "disable-x-forwarded-for", "x", disableXForwardedFor, "whether to show x-forwarded-for or the requesting IP")
flag.BoolVarP(&allowPrivate, "allow-private", "l", allowPrivate, "whether to show lookups of private IP ranges")
flag.IntVar(&requestTTL, "request-ttl", requestTTL, "sets the maximum request time to live in seconds")
flag.Parse()
logpath, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0660)
if err != nil {
logStdout.Fatal("Failed to initialize the logFile: ", err.Error())
}
logFile.SetLevel(log.InfoLevel)
logFile.SetOutput(logpath)
logFile.Info("probehost2 initialized")
} }
func runner(remoteip string, command string, args... string) string{ // runner runs the given command with the given args and returns stdout as string. Also logs all executed commands and their exit state.
logfile.WithFields(log.Fields{ func runner(remoteip string, command string, args ...string) string {
"remote_ip": remoteip, logFile.WithFields(log.Fields{
"command": fmt.Sprint(command, args), "remote_ip": remoteip,
}).Info("request initiated:") "command": fmt.Sprint(command, args),
cmd, err := exec.Command(command, args...).Output() }).Info("request initiated:")
if err != nil { cmd, err := exec.Command(command, args...).Output()
logstdout.WithFields(log.Fields{ if err != nil {
"remote_ip": remoteip, logStdout.WithFields(log.Fields{
"command": fmt.Sprint(command, args), "remote_ip": remoteip,
"error": err.Error(), "command": fmt.Sprint(command, args),
}).Warn("request failed:") "error": err.Error(),
logfile.WithFields(log.Fields{ }).Warn("request failed:")
"remote_ip": remoteip, logFile.WithFields(log.Fields{
"command": fmt.Sprint(command, args), "remote_ip": remoteip,
"error": err.Error(), "command": fmt.Sprint(command, args),
}).Warn("request failed:") "error": err.Error(),
} else { }).Warn("request failed:")
logfile.WithFields(log.Fields{ } else {
"remote_ip": remoteip, logFile.WithFields(log.Fields{
"command": fmt.Sprint(command, args), "remote_ip": remoteip,
}).Info("request succeeded:") "command": fmt.Sprint(command, args),
} }).Info("request succeeded:")
return string(cmd) }
return string(cmd)
} }
func validatehosts(hosts []string) []string{ // validatehosts checks the given host+port combinations for validity and returns valid hosts + valid ports separately.
var valid []string func validatehosts(hosts []string) ([]string, []string) {
for _, host := range hosts { var validHosts []string
if hostparse := net.ParseIP(host); hostparse != nil { var validPorts []string
if (net.IP.IsPrivate(hostparse) || net.IP.IsLoopback(hostparse)) && allowprivate { for _, host := range hosts {
valid = append(valid, host) split := strings.Split(host, "_")
} else if ! (net.IP.IsPrivate(hostparse) || net.IP.IsLoopback(hostparse)) { host = split[0]
valid = append(valid, host) if hostparse := net.ParseIP(host); hostparse != nil {
} if (net.IP.IsPrivate(hostparse) || net.IP.IsLoopback(hostparse)) && allowPrivate {
} else if _, err := net.LookupIP(host); err == nil { validHosts = append(validHosts, host)
valid = append(valid, host) } else if !(net.IP.IsPrivate(hostparse) || net.IP.IsLoopback(hostparse)) {
} validHosts = append(validHosts, host)
} }
return valid } else if _, err := net.LookupIP(host); err == nil {
validHosts = append(validHosts, host)
} else {
continue
}
var port string
if len(split) > 1 {
port = split[1]
_, err := strconv.Atoi(port) // validate if port is just an int
if err == nil {
validPorts = append(validPorts, port)
} else {
validPorts = append(validPorts, "0")
}
} else {
validPorts = append(validPorts, "0")
}
}
return validHosts, validPorts
} }
func parseopts(options []string, cmdopts map[string]string) []string{ // parseopts matches the given user options to the valid optionmap.
var opts []string func parseopts(options []string, cmdopts map[string]string) []string {
for _, opt := range options { var opts []string
opts = append(opts, cmdopts[opt]) for _, opt := range options {
} opts = append(opts, cmdopts[opt])
return opts }
return opts
} }
func prerunner(req *http.Request, cmd string, cmdopts map[string]string, defaultopts []string) string{ // prerunner processes the incoming request to send it to runner.
geturl := strings.Split(req.URL.String(), "/") func prerunner(req *http.Request, cmd string, cmdopts map[string]string, defaultopts []string) string {
targets := strings.Split(geturl[2], ",") geturl := strings.Split(req.URL.String(), "/")
hosts := validatehosts(targets) targets := strings.Split(geturl[2], ",")
var opts []string hosts, ports := validatehosts(targets)
opts = append(opts, defaultopts...) var opts []string
if len(geturl) > 3 && len(geturl[3]) > 0 { opts = append(opts, defaultopts...)
options := strings.Split(geturl[3], ",") if len(geturl) > 3 && len(geturl[3]) > 0 {
opts = append(opts, parseopts(options, cmdopts)...) options := strings.Split(geturl[3], ",")
} opts = append(opts, parseopts(options, cmdopts)...)
var res string }
var args []string var res string
var remoteaddr string var args []string
if req.Header.Get("X-Forwarded-For") != "" && disablexforwardedfor != true { remoteaddr := req.RemoteAddr
remoteaddr = req.Header.Get("X-Forwarded-For") if req.Header.Get("X-Forwarded-For") != "" && !disableXForwardedFor {
} else { remoteaddr = req.Header.Get("X-Forwarded-For")
remoteaddr = req.RemoteAddr }
} for i, host := range hosts {
for _, host := range hosts { runargs := append(args, opts...)
args = append(args, opts...) if ports[i] != "0" && cmd == "nping" {
args = append(args, host) runargs = append(runargs, "-p"+ports[i])
res = fmt.Sprint(res, runner(remoteaddr, cmd, args...), "\n") }
} runargs = append(runargs, host)
return res res = fmt.Sprint(res, runner(remoteaddr, cmd, runargs...), "\n")
}
return res
} }
// ping is the response handler for the ping command. It defines the allowed options.
func ping(w http.ResponseWriter, req *http.Request) { func ping(w http.ResponseWriter, req *http.Request) {
cmd := "ping" cmd := "ping"
cmdopts := map[string]string{ cmdopts := map[string]string{
"4": "-4", "6": "-6", "d": "-D", "n": "-n", "v": "-v", "c1": "-c1", "c5": "-c5", "c10": "-c10", "4": "-4", "6": "-6", "d": "-D", "n": "-n", "v": "-v", "c1": "-c1", "c5": "-c5", "c10": "-c10",
"force4": "-4", "force6": "-6", "timestamps": "-D", "nodns": "-n", "verbose": "-v", "count1": "-c1", "count5": "-c5", "count10": "-c10", "force4": "-4", "force6": "-6", "timestamps": "-D", "nodns": "-n", "verbose": "-v", "count1": "-c1", "count5": "-c5", "count10": "-c10",
} }
var defaultopts []string var defaultopts []string
defaultopts = append(defaultopts, "-c10") defaultopts = append(defaultopts, "-c10")
res := prerunner(req, cmd, cmdopts, defaultopts) res := prerunner(req, cmd, cmdopts, defaultopts)
if strings.TrimSpace(res) == "" { if strings.TrimSpace(res) == "" {
fmt.Fprintln(w, http.StatusInternalServerError) http.Error(w, "500: Internal Server Error", http.StatusInternalServerError)
} else { } else {
fmt.Fprint(w, strings.TrimSpace(res), "\n") _, _ = fmt.Fprint(w, strings.TrimSpace(res), "\n")
} }
} }
// mtr is the response handler for the mtr command. It defines the allowed options.
func mtr(w http.ResponseWriter, req *http.Request) { func mtr(w http.ResponseWriter, req *http.Request) {
cmd := "mtr" cmd := "mtr"
cmdopts := map[string]string{ cmdopts := map[string]string{
"4": "-4", "6": "-6", "u": "-u", "t": "-T", "e": "-e", "x": "-x", "n": "-n", "b": "-b", "z": "-z", "c1": "-c1", "c5": "-c5", "c10": "-c10", "4": "-4", "6": "-6", "u": "-u", "t": "-T", "e": "-e", "x": "-x", "n": "-n", "b": "-b", "z": "-z", "c1": "-c1", "c5": "-c5", "c10": "-c10",
"force4": "-4", "force6": "-6", "udp": "-u", "tcp": "-T", "ext": "-e", "xml": "-x", "nodns": "-n", "cmb": "-b", "asn": "-z", "count1": "-c1", "count5": "-c5", "count10": "-c10", "force4": "-4", "force6": "-6", "udp": "-u", "tcp": "-T", "ext": "-e", "xml": "-x", "nodns": "-n", "cmb": "-b", "asn": "-z", "count1": "-c1", "count5": "-c5", "count10": "-c10",
} }
var defaultopts []string var defaultopts []string
defaultopts = append(defaultopts, "-r", "-w", "-c10") defaultopts = append(defaultopts, "-r", "-w", "-c10")
res := prerunner(req, cmd, cmdopts, defaultopts) res := prerunner(req, cmd, cmdopts, defaultopts)
if strings.TrimSpace(res) == "" { if strings.TrimSpace(res) == "" {
fmt.Fprintln(w, http.StatusInternalServerError) http.Error(w, "500: Internal Server Error", http.StatusInternalServerError)
} else { } else {
fmt.Fprint(w, strings.TrimSpace(res), "\n") _, _ = fmt.Fprint(w, strings.TrimSpace(res), "\n")
} }
} }
// traceroute is the response handler for the traceroute command. It defines the allowed options.
func traceroute(w http.ResponseWriter, req *http.Request) { func traceroute(w http.ResponseWriter, req *http.Request) {
cmd := "traceroute" cmd := "traceroute"
cmdopts := map[string]string{ cmdopts := map[string]string{
"4": "-4", "6": "-6", "f": "-F", "i": "-I", "t": "-T", "n": "-n", "u": "-U", "ul": "-UL", "d": "-D", "b": "--back", "4": "-4", "6": "-6", "f": "-F", "i": "-I", "t": "-T", "n": "-n", "u": "-U", "ul": "-UL", "d": "-D", "b": "--back",
"force4": "-4", "force6": "-6", "dnf": "-F", "icmp": "-I", "tcp": "-T", "nodns": "-n", "udp": "-U", "udplite": "-UL", "dccp": "-D", "back": "--back", "force4": "-4", "force6": "-6", "dnf": "-F", "icmp": "-I", "tcp": "-T", "nodns": "-n", "udp": "-U", "udplite": "-UL", "dccp": "-D", "back": "--back",
} }
var defaultopts []string var defaultopts []string
//defaultopts = append(defaultopts) // no default options for traceroute //defaultopts = append(defaultopts) // no default options for traceroute
res := prerunner(req, cmd, cmdopts, defaultopts) res := prerunner(req, cmd, cmdopts, defaultopts)
if strings.TrimSpace(res) == "" { if strings.TrimSpace(res) == "" {
fmt.Fprintln(w, http.StatusInternalServerError) http.Error(w, "500: Internal Server Error", http.StatusInternalServerError)
} else { } else {
fmt.Fprint(w, strings.TrimSpace(res), "\n") _, _ = fmt.Fprint(w, strings.TrimSpace(res), "\n")
} }
}
// nping is the response handler for the nping command. It defines the allowed options.
func nping(w http.ResponseWriter, req *http.Request) {
cmd := "nping"
cmdopts := map[string]string{
"4": "-4", "6": "-6", "u": "--udp", "t": "--tcp-connect", "v": "-v", "c1": "-c1", "c3": "-c3", "c5": "-c5",
"force4": "-4", "force6": "-6", "udp": "--udp", "tcp": "--tcp-connect", "verbose": "-v", "count1": "-c1", "count3": "-c3", "count5": "-c5",
}
var defaultopts []string
defaultopts = append(defaultopts, "-c3")
res := prerunner(req, cmd, cmdopts, defaultopts)
if strings.TrimSpace(res) == "" {
http.Error(w, "500: Internal Server Error", http.StatusInternalServerError)
} else {
_, _ = fmt.Fprint(w, strings.TrimSpace(res), "\n")
}
} }
func main() { func main() {
http.HandleFunc("/ping/", ping) http.HandleFunc("/ping/", ping)
http.HandleFunc("/mtr/", mtr) http.HandleFunc("/mtr/", mtr)
http.HandleFunc("/tracert/", traceroute) http.HandleFunc("/tracert/", traceroute)
http.HandleFunc("/traceroute/", traceroute) http.HandleFunc("/traceroute/", traceroute)
logstdout.Info("Serving on :", listenport) http.HandleFunc("/nping/", nping)
logfile.Info("Serving on :", listenport)
http.ListenAndServe(fmt.Sprint(":", listenport), nil) server := &http.Server{
} Addr: fmt.Sprint(":", listenPort),
ReadHeaderTimeout: time.Duration(requestTTL) * time.Second,
}
logStdout.Info("Serving on :", listenPort)
logFile.Info("Serving on :", listenPort)
_ = server.ListenAndServe()
}

View File

@ -8,6 +8,8 @@ StartLimitBurst=5
StartLimitIntervalSec=20 StartLimitIntervalSec=20
[Service] [Service]
User=1000
Group=1000
Restart=always Restart=always
RestartSec=1 RestartSec=1
ExecStart=/bin/probehost2 --logfilepath "/var/log/probehost2.log" --port 8000 ExecStart=/bin/probehost2 --logfilepath "/var/log/probehost2.log" --port 8000