probehost2/main.go

254 lines
8.7 KiB
Go
Raw Permalink Normal View History

2021-12-19 01:16:27 +00:00
package main
2021-12-23 00:35:47 +00:00
import (
"fmt"
"net"
"net/http"
"os"
"os/exec"
"strconv"
"strings"
2024-08-07 21:23:06 +00:00
"time"
2021-12-31 00:38:13 +00:00
log "github.com/sirupsen/logrus"
flag "github.com/spf13/pflag"
2021-12-19 01:16:27 +00:00
)
2023-02-07 18:17:30 +00:00
var logStdout = log.New()
var logFile = log.New()
2021-12-23 00:35:47 +00:00
2024-08-07 20:56:19 +00:00
var listenPort = 8080 // port to listen on
2023-02-07 18:17:30 +00:00
var disableXForwardedFor bool // whether to disable parsing the X-Forwarded-For header or not
2024-08-07 20:56:19 +00:00
var allowPrivate bool // whether to allow private IP ranges or not
2024-08-07 21:23:06 +00:00
var requestTTL = 180 // maximum request time to live in seconds
2021-12-24 14:32:27 +00:00
2021-12-23 00:35:47 +00:00
func init() {
2023-02-07 18:17:30 +00:00
logStdout.SetFormatter(&log.TextFormatter{
FullTimestamp: true})
2023-02-07 18:17:30 +00:00
logStdout.SetOutput(os.Stdout)
logStdout.SetLevel(log.InfoLevel)
2023-02-07 18:17:30 +00:00
logFilePath := "probehost2.log"
if val, exists := os.LookupEnv("PROBEHOST_LOGPATH"); exists {
logFilePath = val
}
2023-02-07 18:17:30 +00:00
_, allowPrivate = os.LookupEnv("PROBEHOST_ALLOW_PRIVATE")
_, disableXForwardedFor = os.LookupEnv("PROBEHOST_DISABLE_X_FORWARDED_FOR")
if val, exists := os.LookupEnv("PROBEHOST_LISTEN_PORT"); exists {
var err error
2023-02-07 18:17:30 +00:00
listenPort, err = strconv.Atoi(val)
if err != nil {
2023-02-07 18:17:30 +00:00
logStdout.Fatal("Failed to read PROBEHOST_LISTEN_PORT: ", err.Error())
}
}
2023-02-07 18:17:30 +00:00
2024-08-07 21:23:06 +00:00
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())
}
}
2023-02-07 18:17:30 +00:00
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")
2024-08-07 21:23:06 +00:00
flag.IntVar(&requestTTL, "request-ttl", requestTTL, "sets the maximum request time to live in seconds")
flag.Parse()
2023-02-07 18:17:30 +00:00
logpath, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0660)
if err != nil {
2023-02-07 18:17:30 +00:00
logStdout.Fatal("Failed to initialize the logFile: ", err.Error())
}
2023-02-07 18:17:30 +00:00
logFile.SetLevel(log.InfoLevel)
logFile.SetOutput(logpath)
logFile.Info("probehost2 initialized")
2021-12-23 00:35:47 +00:00
}
2023-02-07 18:17:30 +00:00
// runner runs the given command with the given args and returns stdout as string. Also logs all executed commands and their exit state.
func runner(remoteip string, command string, args ...string) string {
2023-02-07 18:17:30 +00:00
logFile.WithFields(log.Fields{
"remote_ip": remoteip,
"command": fmt.Sprint(command, args),
}).Info("request initiated:")
cmd, err := exec.Command(command, args...).Output()
if err != nil {
2023-02-07 18:17:30 +00:00
logStdout.WithFields(log.Fields{
"remote_ip": remoteip,
"command": fmt.Sprint(command, args),
"error": err.Error(),
}).Warn("request failed:")
2023-02-07 18:17:30 +00:00
logFile.WithFields(log.Fields{
"remote_ip": remoteip,
"command": fmt.Sprint(command, args),
"error": err.Error(),
}).Warn("request failed:")
} else {
2023-02-07 18:17:30 +00:00
logFile.WithFields(log.Fields{
"remote_ip": remoteip,
"command": fmt.Sprint(command, args),
}).Info("request succeeded:")
}
return string(cmd)
2021-12-19 01:16:27 +00:00
}
2024-08-07 20:56:19 +00:00
// validatehosts checks the given host+port combinations for validity and returns valid hosts + valid ports separately.
func validatehosts(hosts []string) ([]string, []string) {
2023-02-07 18:17:30 +00:00
var validHosts []string
var validPorts []string
for _, host := range hosts {
split := strings.Split(host, "_")
host = split[0]
if hostparse := net.ParseIP(host); hostparse != nil {
2023-02-07 18:17:30 +00:00
if (net.IP.IsPrivate(hostparse) || net.IP.IsLoopback(hostparse)) && allowPrivate {
validHosts = append(validHosts, host)
} else if !(net.IP.IsPrivate(hostparse) || net.IP.IsLoopback(hostparse)) {
2023-02-07 18:17:30 +00:00
validHosts = append(validHosts, host)
}
} else if _, err := net.LookupIP(host); err == nil {
2023-02-07 18:17:30 +00:00
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 {
2023-02-07 18:17:30 +00:00
validPorts = append(validPorts, port)
} else {
2023-02-07 18:17:30 +00:00
validPorts = append(validPorts, "0")
}
} else {
2023-02-07 18:17:30 +00:00
validPorts = append(validPorts, "0")
}
}
2023-02-07 18:17:30 +00:00
return validHosts, validPorts
}
2023-02-07 18:17:30 +00:00
// parseopts matches the given user options to the valid optionmap.
func parseopts(options []string, cmdopts map[string]string) []string {
var opts []string
for _, opt := range options {
opts = append(opts, cmdopts[opt])
}
return opts
2021-12-30 01:58:33 +00:00
}
2023-02-07 18:17:30 +00:00
// prerunner processes the incoming request to send it to runner.
func prerunner(req *http.Request, cmd string, cmdopts map[string]string, defaultopts []string) string {
geturl := strings.Split(req.URL.String(), "/")
targets := strings.Split(geturl[2], ",")
hosts, ports := validatehosts(targets)
var opts []string
opts = append(opts, defaultopts...)
if len(geturl) > 3 && len(geturl[3]) > 0 {
options := strings.Split(geturl[3], ",")
opts = append(opts, parseopts(options, cmdopts)...)
}
var res string
var args []string
2023-02-07 18:17:30 +00:00
remoteaddr := req.RemoteAddr
if req.Header.Get("X-Forwarded-For") != "" && !disableXForwardedFor {
remoteaddr = req.Header.Get("X-Forwarded-For")
}
for i, host := range hosts {
2022-05-29 12:26:09 +00:00
runargs := append(args, opts...)
if ports[i] != "0" && cmd == "nping" {
2022-05-29 12:26:09 +00:00
runargs = append(runargs, "-p"+ports[i])
}
2022-05-29 12:26:09 +00:00
runargs = append(runargs, host)
res = fmt.Sprint(res, runner(remoteaddr, cmd, runargs...), "\n")
}
return res
2021-12-31 00:07:43 +00:00
}
2023-02-07 18:17:30 +00:00
// ping is the response handler for the ping command. It defines the allowed options.
2021-12-31 00:07:43 +00:00
func ping(w http.ResponseWriter, req *http.Request) {
cmd := "ping"
cmdopts := map[string]string{
"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",
}
var defaultopts []string
defaultopts = append(defaultopts, "-c10")
res := prerunner(req, cmd, cmdopts, defaultopts)
if strings.TrimSpace(res) == "" {
2022-05-29 13:05:13 +00:00
http.Error(w, "500: Internal Server Error", http.StatusInternalServerError)
} else {
_, _ = fmt.Fprint(w, strings.TrimSpace(res), "\n")
}
2021-12-24 01:10:42 +00:00
}
2023-02-07 18:17:30 +00:00
// mtr is the response handler for the mtr command. It defines the allowed options.
2021-12-24 01:10:42 +00:00
func mtr(w http.ResponseWriter, req *http.Request) {
cmd := "mtr"
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",
"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
defaultopts = append(defaultopts, "-r", "-w", "-c10")
res := prerunner(req, cmd, cmdopts, defaultopts)
if strings.TrimSpace(res) == "" {
2022-05-29 13:05:13 +00:00
http.Error(w, "500: Internal Server Error", http.StatusInternalServerError)
} else {
_, _ = fmt.Fprint(w, strings.TrimSpace(res), "\n")
}
2021-12-19 01:16:27 +00:00
}
2023-02-07 18:17:30 +00:00
// traceroute is the response handler for the traceroute command. It defines the allowed options.
2021-12-28 03:02:54 +00:00
func traceroute(w http.ResponseWriter, req *http.Request) {
cmd := "traceroute"
cmdopts := map[string]string{
"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",
}
var defaultopts []string
//defaultopts = append(defaultopts) // no default options for traceroute
res := prerunner(req, cmd, cmdopts, defaultopts)
if strings.TrimSpace(res) == "" {
2022-05-29 13:05:13 +00:00
http.Error(w, "500: Internal Server Error", http.StatusInternalServerError)
} else {
_, _ = fmt.Fprint(w, strings.TrimSpace(res), "\n")
}
}
2023-02-07 18:17:30 +00:00
// 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) == "" {
2022-05-29 13:05:13 +00:00
http.Error(w, "500: Internal Server Error", http.StatusInternalServerError)
} else {
_, _ = fmt.Fprint(w, strings.TrimSpace(res), "\n")
}
2021-12-28 03:02:54 +00:00
}
2021-12-19 01:16:27 +00:00
func main() {
http.HandleFunc("/ping/", ping)
http.HandleFunc("/mtr/", mtr)
http.HandleFunc("/tracert/", traceroute)
http.HandleFunc("/traceroute/", traceroute)
http.HandleFunc("/nping/", nping)
2024-08-07 21:23:06 +00:00
server := &http.Server{
Addr: fmt.Sprint(":", listenPort),
ReadHeaderTimeout: time.Duration(requestTTL) * time.Second,
}
2023-02-07 18:17:30 +00:00
logStdout.Info("Serving on :", listenPort)
logFile.Info("Serving on :", listenPort)
2024-08-07 21:23:06 +00:00
_ = server.ListenAndServe()
}