v0.0.3 - To be come a daemon.
This commit is contained in:
57
README.md
Normal file
57
README.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# Ping Service
|
||||||
|
|
||||||
|
A Go-based monitoring service that periodically pings IP addresses from a configurable input source (file, HTTP, or Unix socket), applies cooldown periods to avoid frequent pings, optionally performs traceroute on successes, and outputs JSON results to a destination (file, HTTP, or socket). Includes health checks and metrics.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- Reads IPs from file, HTTP endpoint, or Unix socket.
|
||||||
|
- Configurable ping interval and per-IP cooldown.
|
||||||
|
- Optional traceroute (ICMP/TCP) with max hops.
|
||||||
|
- JSON output with ping stats and traceroute details.
|
||||||
|
- HTTP health endpoints: `/health`, `/ready`, `/metrics`.
|
||||||
|
- Graceful shutdown and verbose logging support.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
Edit `config.yaml`:
|
||||||
|
```yaml
|
||||||
|
input_file: "http://localhost:8080" # Or file path or socket
|
||||||
|
output_file: "http://localhost:8081" # Or file path or socket
|
||||||
|
interval_seconds: 30 # Poll interval
|
||||||
|
cooldown_minutes: 10 # Min time between same-IP pings
|
||||||
|
enable_traceroute: true # Enable traceroute
|
||||||
|
traceroute_max_hops: 30 # Max TTL
|
||||||
|
health_check_port: 8090 # Health server port
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
```bash
|
||||||
|
go build -o ping_service
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation as Service (Linux)
|
||||||
|
```bash
|
||||||
|
chmod +x install.sh
|
||||||
|
sudo ./install.sh
|
||||||
|
sudo systemctl start ping-service
|
||||||
|
```
|
||||||
|
|
||||||
|
- Check status: `sudo systemctl status ping-service`
|
||||||
|
- View logs: `sudo journalctl -u ping-service -f`
|
||||||
|
- Stop: `sudo systemctl stop ping-service`
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
Run directly:
|
||||||
|
```bash
|
||||||
|
./ping_service -config config.yaml -verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
For testing HTTP I/O:
|
||||||
|
- Run `python3 input_http_server.py` (serves IPs on port 8080).
|
||||||
|
- Run `python3 output_http_server.py` (receives results on port 8081).
|
||||||
|
|
||||||
|
## Health Checks
|
||||||
|
- `curl http://localhost:8090/health` (status, uptime, stats)
|
||||||
|
- `curl http://localhost:8090/ready` (readiness)
|
||||||
|
- `curl http://localhost:8090/metrics` (Prometheus metrics)
|
||||||
|
|
||||||
|
Version: 0.0.3
|
||||||
|
Dependencies: `go-ping/ping`, `gopkg.in/yaml.v
|
||||||
@@ -10,3 +10,4 @@ interval_seconds: 30
|
|||||||
cooldown_minutes: 10
|
cooldown_minutes: 10
|
||||||
enable_traceroute: true
|
enable_traceroute: true
|
||||||
traceroute_max_hops: 30
|
traceroute_max_hops: 30
|
||||||
|
health_check_port: 8090
|
||||||
|
|||||||
33
install.sh
Normal file
33
install.sh
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Installing ping-service..."
|
||||||
|
|
||||||
|
# Create user
|
||||||
|
sudo useradd -r -s /bin/false pingservice || true
|
||||||
|
|
||||||
|
# Create directories
|
||||||
|
sudo mkdir -p /opt/ping-service
|
||||||
|
sudo mkdir -p /var/log/ping-service
|
||||||
|
|
||||||
|
# Copy files
|
||||||
|
sudo cp ping_service /opt/ping-service/
|
||||||
|
sudo cp config.yaml /opt/ping-service/
|
||||||
|
sudo chown -R pingservice:pingservice /opt/ping-service
|
||||||
|
sudo chown -R pingservice:pingservice /var/log/ping-service
|
||||||
|
|
||||||
|
# Install systemd service
|
||||||
|
sudo cp ping-service.service /etc/systemd/system/
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable ping-service
|
||||||
|
sudo systemctl start ping-service
|
||||||
|
|
||||||
|
echo "✅ Installation complete!"
|
||||||
|
echo ""
|
||||||
|
echo "Useful commands:"
|
||||||
|
echo " sudo systemctl status ping-service # Check status"
|
||||||
|
echo " sudo systemctl stop ping-service # Stop service"
|
||||||
|
echo " sudo systemctl start ping-service # Start service"
|
||||||
|
echo " sudo systemctl restart ping-service # Restart service"
|
||||||
|
echo " sudo journalctl -u ping-service -f # View logs"
|
||||||
|
echo " curl http://localhost:8090/health # Health check"
|
||||||
238
ping_service.go
238
ping_service.go
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -11,41 +12,46 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-ping/ping"
|
"github.com/go-ping/ping"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const VERSION = "1.0.0"
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
InputFile string `yaml:"input_file"`
|
InputFile string `yaml:"input_file"`
|
||||||
OutputFile string `yaml:"output_file"`
|
OutputFile string `yaml:"output_file"`
|
||||||
IntervalSeconds int `yaml:"interval_seconds"`
|
IntervalSeconds int `yaml:"interval_seconds"`
|
||||||
CooldownMinutes int `yaml:"cooldown_minutes"`
|
CooldownMinutes int `yaml:"cooldown_minutes"`
|
||||||
EnableTraceroute bool `yaml:"enable_traceroute"`
|
EnableTraceroute bool `yaml:"enable_traceroute"`
|
||||||
TracerouteMaxHops int `yaml:"traceroute_max_hops"`
|
TracerouteMaxHops int `yaml:"traceroute_max_hops"`
|
||||||
|
HealthCheckPort int `yaml:"health_check_port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PingResult struct {
|
type PingResult struct {
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
Sent int `json:"sent"`
|
Sent int `json:"sent"`
|
||||||
Received int `json:"received"`
|
Received int `json:"received"`
|
||||||
PacketLoss float64 `json:"packet_loss"`
|
PacketLoss float64 `json:"packet_loss"`
|
||||||
AvgRtt time.Duration `json:"avg_rtt"`
|
AvgRtt time.Duration `json:"avg_rtt"`
|
||||||
Timestamp time.Time `json:"timestamp"`
|
Timestamp time.Time `json:"timestamp"`
|
||||||
Error string `json:"error,omitempty"`
|
Error string `json:"error,omitempty"`
|
||||||
Traceroute *TracerouteResult `json:"traceroute,omitempty"`
|
Traceroute *TracerouteResult `json:"traceroute,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TracerouteResult struct {
|
type TracerouteResult struct {
|
||||||
Method string `json:"method"` // "icmp" or "tcp"
|
Method string `json:"method"`
|
||||||
Hops []TracerouteHop `json:"hops"`
|
Hops []TracerouteHop `json:"hops"`
|
||||||
Completed bool `json:"completed"`
|
Completed bool `json:"completed"`
|
||||||
Error string `json:"error,omitempty"`
|
Error string `json:"error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TracerouteHop struct {
|
type TracerouteHop struct {
|
||||||
@@ -55,11 +61,23 @@ type TracerouteHop struct {
|
|||||||
Timeout bool `json:"timeout,omitempty"`
|
Timeout bool `json:"timeout,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HealthStatus struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Uptime string `json:"uptime"`
|
||||||
|
LastRun time.Time `json:"last_run"`
|
||||||
|
TotalPings int64 `json:"total_pings"`
|
||||||
|
SuccessfulPings int64 `json:"successful_pings"`
|
||||||
|
FailedPings int64 `json:"failed_pings"`
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// cooldownCache stores IP -> last ping time
|
|
||||||
cooldownCache = make(map[string]time.Time)
|
cooldownCache = make(map[string]time.Time)
|
||||||
cacheMux sync.Mutex
|
cacheMux sync.Mutex
|
||||||
verbose bool
|
verbose bool
|
||||||
|
startTime time.Time
|
||||||
|
health HealthStatus
|
||||||
|
healthMux sync.RWMutex
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -67,12 +85,19 @@ func main() {
|
|||||||
configPath := flag.String("config", "config.yaml", "Path to config file")
|
configPath := flag.String("config", "config.yaml", "Path to config file")
|
||||||
verboseFlag := flag.Bool("v", false, "Enable verbose logging")
|
verboseFlag := flag.Bool("v", false, "Enable verbose logging")
|
||||||
flag.BoolVar(verboseFlag, "verbose", false, "Enable verbose logging")
|
flag.BoolVar(verboseFlag, "verbose", false, "Enable verbose logging")
|
||||||
|
versionFlag := flag.Bool("version", false, "Show version")
|
||||||
help := flag.Bool("help", false, "Show help message")
|
help := flag.Bool("help", false, "Show help message")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
if *versionFlag {
|
||||||
|
fmt.Printf("ping-service version %s\n", VERSION)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
if *help {
|
if *help {
|
||||||
fmt.Println("Ping Service - Monitor network endpoints via ping")
|
fmt.Println("Ping Service - Monitor network endpoints via ping and traceroute")
|
||||||
fmt.Println("\nUsage:")
|
fmt.Printf("Version: %s\n\n", VERSION)
|
||||||
|
fmt.Println("Usage:")
|
||||||
flag.PrintDefaults()
|
flag.PrintDefaults()
|
||||||
fmt.Println("\nConfig file format (YAML):")
|
fmt.Println("\nConfig file format (YAML):")
|
||||||
fmt.Println(" input_file: Path/URL to file containing IPs (one per line)")
|
fmt.Println(" input_file: Path/URL to file containing IPs (one per line)")
|
||||||
@@ -81,10 +106,18 @@ func main() {
|
|||||||
fmt.Println(" cooldown_minutes: Minimum time between pings for same IP")
|
fmt.Println(" cooldown_minutes: Minimum time between pings for same IP")
|
||||||
fmt.Println(" enable_traceroute: Enable traceroute after successful ping")
|
fmt.Println(" enable_traceroute: Enable traceroute after successful ping")
|
||||||
fmt.Println(" traceroute_max_hops: Maximum TTL for traceroute (default: 30)")
|
fmt.Println(" traceroute_max_hops: Maximum TTL for traceroute (default: 30)")
|
||||||
|
fmt.Println(" health_check_port: HTTP port for health checks (default: 8090)")
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
verbose = *verboseFlag
|
verbose = *verboseFlag
|
||||||
|
startTime = time.Now()
|
||||||
|
|
||||||
|
// Initialize health status
|
||||||
|
health = HealthStatus{
|
||||||
|
Status: "starting",
|
||||||
|
Version: VERSION,
|
||||||
|
}
|
||||||
|
|
||||||
config := loadConfig(*configPath)
|
config := loadConfig(*configPath)
|
||||||
if config.CooldownMinutes == 0 {
|
if config.CooldownMinutes == 0 {
|
||||||
@@ -93,19 +126,131 @@ func main() {
|
|||||||
if config.TracerouteMaxHops == 0 {
|
if config.TracerouteMaxHops == 0 {
|
||||||
config.TracerouteMaxHops = 30
|
config.TracerouteMaxHops = 30
|
||||||
}
|
}
|
||||||
|
if config.HealthCheckPort == 0 {
|
||||||
|
config.HealthCheckPort = 8090
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start health check server
|
||||||
|
go startHealthCheckServer(config.HealthCheckPort)
|
||||||
|
|
||||||
|
// Setup graceful shutdown
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
sigChan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
sig := <-sigChan
|
||||||
|
log.Printf("Received signal %v, shutting down gracefully...", sig)
|
||||||
|
updateHealth("shutting_down")
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
|
||||||
ticker := time.NewTicker(time.Duration(config.IntervalSeconds) * time.Second)
|
ticker := time.NewTicker(time.Duration(config.IntervalSeconds) * time.Second)
|
||||||
log.Printf("App started. Cooldown set to %d mins. Polling every %ds...", config.CooldownMinutes, config.IntervalSeconds)
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
log.Printf("App started. Version: %s", VERSION)
|
||||||
|
log.Printf("Cooldown set to %d mins. Polling every %ds...", config.CooldownMinutes, config.IntervalSeconds)
|
||||||
if config.EnableTraceroute {
|
if config.EnableTraceroute {
|
||||||
log.Printf("Traceroute enabled (max %d hops)", config.TracerouteMaxHops)
|
log.Printf("Traceroute enabled (max %d hops)", config.TracerouteMaxHops)
|
||||||
}
|
}
|
||||||
|
log.Printf("Health check available at http://localhost:%d/health", config.HealthCheckPort)
|
||||||
|
|
||||||
|
updateHealth("running")
|
||||||
|
|
||||||
|
// Main loop
|
||||||
for {
|
for {
|
||||||
process(config)
|
select {
|
||||||
<-ticker.C
|
case <-ctx.Done():
|
||||||
|
log.Println("Shutdown complete")
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
process(config)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func startHealthCheckServer(port int) {
|
||||||
|
http.HandleFunc("/health", healthCheckHandler)
|
||||||
|
http.HandleFunc("/ready", readinessHandler)
|
||||||
|
http.HandleFunc("/metrics", metricsHandler)
|
||||||
|
|
||||||
|
addr := fmt.Sprintf(":%d", port)
|
||||||
|
log.Printf("Starting health check server on %s", addr)
|
||||||
|
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
ReadTimeout: 5 * time.Second,
|
||||||
|
WriteTimeout: 5 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
|
log.Printf("Health check server error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
healthMux.RLock()
|
||||||
|
defer healthMux.RUnlock()
|
||||||
|
|
||||||
|
health.Uptime = time.Since(startTime).String()
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(health)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readinessHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
healthMux.RLock()
|
||||||
|
defer healthMux.RUnlock()
|
||||||
|
|
||||||
|
if health.Status == "running" {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(`{"ready": true}`))
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusServiceUnavailable)
|
||||||
|
w.Write([]byte(`{"ready": false}`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func metricsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
healthMux.RLock()
|
||||||
|
defer healthMux.RUnlock()
|
||||||
|
|
||||||
|
// Prometheus-style metrics
|
||||||
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
|
fmt.Fprintf(w, "# HELP ping_service_total_pings Total number of pings\n")
|
||||||
|
fmt.Fprintf(w, "# TYPE ping_service_total_pings counter\n")
|
||||||
|
fmt.Fprintf(w, "ping_service_total_pings %d\n", health.TotalPings)
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "# HELP ping_service_successful_pings Successful pings\n")
|
||||||
|
fmt.Fprintf(w, "# TYPE ping_service_successful_pings counter\n")
|
||||||
|
fmt.Fprintf(w, "ping_service_successful_pings %d\n", health.SuccessfulPings)
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "# HELP ping_service_failed_pings Failed pings\n")
|
||||||
|
fmt.Fprintf(w, "# TYPE ping_service_failed_pings counter\n")
|
||||||
|
fmt.Fprintf(w, "ping_service_failed_pings %d\n", health.FailedPings)
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "# HELP ping_service_uptime_seconds Uptime in seconds\n")
|
||||||
|
fmt.Fprintf(w, "# TYPE ping_service_uptime_seconds gauge\n")
|
||||||
|
fmt.Fprintf(w, "ping_service_uptime_seconds %.0f\n", time.Since(startTime).Seconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateHealth(status string) {
|
||||||
|
healthMux.Lock()
|
||||||
|
defer healthMux.Unlock()
|
||||||
|
health.Status = status
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateStats(lastRun time.Time, successful, failed int) {
|
||||||
|
healthMux.Lock()
|
||||||
|
defer healthMux.Unlock()
|
||||||
|
health.LastRun = lastRun
|
||||||
|
health.TotalPings += int64(successful + failed)
|
||||||
|
health.SuccessfulPings += int64(successful)
|
||||||
|
health.FailedPings += int64(failed)
|
||||||
|
}
|
||||||
|
|
||||||
func loadConfig(path string) *Config {
|
func loadConfig(path string) *Config {
|
||||||
f, err := os.ReadFile(path)
|
f, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -120,6 +265,8 @@ func loadConfig(path string) *Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func process(cfg *Config) {
|
func process(cfg *Config) {
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
// 1. Read IPs
|
// 1. Read IPs
|
||||||
data, err := readSource(cfg.InputFile)
|
data, err := readSource(cfg.InputFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -141,7 +288,7 @@ func process(cfg *Config) {
|
|||||||
if verbose {
|
if verbose {
|
||||||
log.Printf("Skipping %s (cooldown: %v remaining)", ip, time.Duration(cfg.CooldownMinutes)*time.Minute-time.Since(last))
|
log.Printf("Skipping %s (cooldown: %v remaining)", ip, time.Duration(cfg.CooldownMinutes)*time.Minute-time.Since(last))
|
||||||
}
|
}
|
||||||
continue // Skip this IP
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ipsToPing = append(ipsToPing, ip)
|
ipsToPing = append(ipsToPing, ip)
|
||||||
@@ -163,13 +310,21 @@ func process(cfg *Config) {
|
|||||||
// 2. Perform Pings
|
// 2. Perform Pings
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
results := make([]PingResult, len(ipsToPing))
|
results := make([]PingResult, len(ipsToPing))
|
||||||
|
successful := 0
|
||||||
|
failed := 0
|
||||||
|
|
||||||
for i, ip := range ipsToPing {
|
for i, ip := range ipsToPing {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(idx int, targetIP string) {
|
go func(idx int, targetIP string) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
results[idx] = runPing(targetIP)
|
results[idx] = runPing(targetIP)
|
||||||
|
|
||||||
|
if results[idx].Error == "" && results[idx].Received > 0 {
|
||||||
|
successful++
|
||||||
|
} else {
|
||||||
|
failed++
|
||||||
|
}
|
||||||
|
|
||||||
// If ping successful and traceroute enabled, do traceroute
|
// If ping successful and traceroute enabled, do traceroute
|
||||||
if cfg.EnableTraceroute && results[idx].Error == "" && results[idx].Received > 0 {
|
if cfg.EnableTraceroute && results[idx].Error == "" && results[idx].Received > 0 {
|
||||||
if verbose {
|
if verbose {
|
||||||
@@ -177,7 +332,7 @@ func process(cfg *Config) {
|
|||||||
}
|
}
|
||||||
results[idx].Traceroute = runTraceroute(targetIP, cfg.TracerouteMaxHops)
|
results[idx].Traceroute = runTraceroute(targetIP, cfg.TracerouteMaxHops)
|
||||||
}
|
}
|
||||||
|
|
||||||
if verbose {
|
if verbose {
|
||||||
if results[idx].Error != "" {
|
if results[idx].Error != "" {
|
||||||
log.Printf("Pinged %s: ERROR - %s", results[idx].IP, results[idx].Error)
|
log.Printf("Pinged %s: ERROR - %s", results[idx].IP, results[idx].Error)
|
||||||
@@ -194,25 +349,25 @@ func process(cfg *Config) {
|
|||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
|
// Update stats
|
||||||
|
updateStats(time.Now(), successful, failed)
|
||||||
|
|
||||||
// 3. Write Output
|
// 3. Write Output
|
||||||
outputData, _ := json.MarshalIndent(results, "", " ")
|
outputData, _ := json.MarshalIndent(results, "", " ")
|
||||||
err = writeDestination(cfg.OutputFile, outputData)
|
err = writeDestination(cfg.OutputFile, outputData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Output Error: %v", err)
|
log.Printf("Output Error: %v", err)
|
||||||
} else if verbose {
|
} else if verbose {
|
||||||
log.Printf("Wrote results to %s", cfg.OutputFile)
|
log.Printf("Wrote results to %s (took %v)", cfg.OutputFile, time.Since(startTime))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logic: If socket exists, dial it. If not, listen on it.
|
|
||||||
func handleSocket(path string, data []byte, mode string) ([]byte, error) {
|
func handleSocket(path string, data []byte, mode string) ([]byte, error) {
|
||||||
if _, err := os.Stat(path); err == nil {
|
if _, err := os.Stat(path); err == nil {
|
||||||
// SOCKET EXISTS: Connect as Client
|
|
||||||
conn, err := net.DialTimeout("unix", path, 2*time.Second)
|
conn, err := net.DialTimeout("unix", path, 2*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If we can't connect, the socket might be "stale" (file exists but no one listening)
|
|
||||||
os.Remove(path)
|
os.Remove(path)
|
||||||
return handleSocket(path, data, mode) // Recursive call to create listener
|
return handleSocket(path, data, mode)
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
@@ -223,7 +378,6 @@ func handleSocket(path string, data []byte, mode string) ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// SOCKET DOES NOT EXIST: Create as Server
|
|
||||||
l, err := net.Listen("unix", path)
|
l, err := net.Listen("unix", path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -231,7 +385,6 @@ func handleSocket(path string, data []byte, mode string) ([]byte, error) {
|
|||||||
defer l.Close()
|
defer l.Close()
|
||||||
defer os.Remove(path)
|
defer os.Remove(path)
|
||||||
|
|
||||||
// Set a timeout so the app doesn't hang forever if no one connects
|
|
||||||
l.(*net.UnixListener).SetDeadline(time.Now().Add(10 * time.Second))
|
l.(*net.UnixListener).SetDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
|
||||||
conn, err := l.Accept()
|
conn, err := l.Accept()
|
||||||
@@ -294,7 +447,6 @@ func runPing(ip string) PingResult {
|
|||||||
|
|
||||||
pinger.Count = 3
|
pinger.Count = 3
|
||||||
pinger.Timeout = time.Second * 5
|
pinger.Timeout = time.Second * 5
|
||||||
// pinger.SetPrivileged(true) // Uncomment if running on Linux/Windows as admin
|
|
||||||
|
|
||||||
err = pinger.Run()
|
err = pinger.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -316,16 +468,14 @@ func runTraceroute(ip string, maxHops int) *TracerouteResult {
|
|||||||
Hops: []TracerouteHop{},
|
Hops: []TracerouteHop{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try ICMP traceroute first
|
|
||||||
if tr := tryICMPTraceroute(ip, maxHops); tr != nil {
|
if tr := tryICMPTraceroute(ip, maxHops); tr != nil {
|
||||||
return tr
|
return tr
|
||||||
}
|
}
|
||||||
|
|
||||||
// If ICMP fails, try TCP traceroute on common ports
|
|
||||||
if verbose {
|
if verbose {
|
||||||
log.Printf("ICMP traceroute failed for %s, trying TCP...", ip)
|
log.Printf("ICMP traceroute failed for %s, trying TCP...", ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, port := range []int{80, 443, 22} {
|
for _, port := range []int{80, 443, 22} {
|
||||||
if tr := tryTCPTraceroute(ip, port, maxHops); tr != nil {
|
if tr := tryTCPTraceroute(ip, port, maxHops); tr != nil {
|
||||||
tr.Method = fmt.Sprintf("tcp/%d", port)
|
tr.Method = fmt.Sprintf("tcp/%d", port)
|
||||||
@@ -339,8 +489,7 @@ func runTraceroute(ip string, maxHops int) *TracerouteResult {
|
|||||||
|
|
||||||
func tryICMPTraceroute(ip string, maxHops int) *TracerouteResult {
|
func tryICMPTraceroute(ip string, maxHops int) *TracerouteResult {
|
||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
|
|
||||||
// Detect OS and use appropriate command
|
|
||||||
switch {
|
switch {
|
||||||
case fileExists("/usr/bin/traceroute"):
|
case fileExists("/usr/bin/traceroute"):
|
||||||
cmd = exec.Command("traceroute", "-m", strconv.Itoa(maxHops), "-n", "-q", "1", ip)
|
cmd = exec.Command("traceroute", "-m", strconv.Itoa(maxHops), "-n", "-q", "1", ip)
|
||||||
@@ -349,7 +498,6 @@ func tryICMPTraceroute(ip string, maxHops int) *TracerouteResult {
|
|||||||
case fileExists("/bin/traceroute"):
|
case fileExists("/bin/traceroute"):
|
||||||
cmd = exec.Command("traceroute", "-m", strconv.Itoa(maxHops), "-n", "-q", "1", ip)
|
cmd = exec.Command("traceroute", "-m", strconv.Itoa(maxHops), "-n", "-q", "1", ip)
|
||||||
default:
|
default:
|
||||||
// Try without full path
|
|
||||||
cmd = exec.Command("traceroute", "-m", strconv.Itoa(maxHops), "-n", "-q", "1", ip)
|
cmd = exec.Command("traceroute", "-m", strconv.Itoa(maxHops), "-n", "-q", "1", ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,12 +511,10 @@ func tryICMPTraceroute(ip string, maxHops int) *TracerouteResult {
|
|||||||
|
|
||||||
func tryTCPTraceroute(ip string, port int, maxHops int) *TracerouteResult {
|
func tryTCPTraceroute(ip string, port int, maxHops int) *TracerouteResult {
|
||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
|
|
||||||
// Try tcptraceroute if available
|
|
||||||
if fileExists("/usr/bin/tcptraceroute") || fileExists("/usr/sbin/tcptraceroute") {
|
if fileExists("/usr/bin/tcptraceroute") || fileExists("/usr/sbin/tcptraceroute") {
|
||||||
cmd = exec.Command("tcptraceroute", "-m", strconv.Itoa(maxHops), "-n", "-q", "1", ip, strconv.Itoa(port))
|
cmd = exec.Command("tcptraceroute", "-m", strconv.Itoa(maxHops), "-n", "-q", "1", ip, strconv.Itoa(port))
|
||||||
} else {
|
} else {
|
||||||
// Try traceroute with TCP (-T flag on Linux)
|
|
||||||
cmd = exec.Command("traceroute", "-T", "-p", strconv.Itoa(port), "-m", strconv.Itoa(maxHops), "-n", "-q", "1", ip)
|
cmd = exec.Command("traceroute", "-T", "-p", strconv.Itoa(port), "-m", strconv.Itoa(maxHops), "-n", "-q", "1", ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,10 +534,8 @@ func parseTracerouteOutput(output string, method string) *TracerouteResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lines := strings.Split(output, "\n")
|
lines := strings.Split(output, "\n")
|
||||||
|
|
||||||
// Regex to match: " 1 192.168.1.1 1.234 ms"
|
|
||||||
hopRegex := regexp.MustCompile(`^\s*(\d+)\s+([0-9.]+)\s+([0-9.]+)\s*ms`)
|
hopRegex := regexp.MustCompile(`^\s*(\d+)\s+([0-9.]+)\s+([0-9.]+)\s*ms`)
|
||||||
// Regex to match timeouts: " 1 * * *"
|
|
||||||
timeoutRegex := regexp.MustCompile(`^\s*(\d+)\s+\*`)
|
timeoutRegex := regexp.MustCompile(`^\s*(\d+)\s+\*`)
|
||||||
|
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
@@ -425,4 +569,4 @@ func parseTracerouteOutput(output string, method string) *TracerouteResult {
|
|||||||
func fileExists(path string) bool {
|
func fileExists(path string) bool {
|
||||||
_, err := os.Stat(path)
|
_, err := os.Stat(path)
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
28
ping_service.service
Normal file
28
ping_service.service
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Network Ping and Traceroute Service
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=pingservice
|
||||||
|
Group=pingservice
|
||||||
|
WorkingDirectory=/opt/ping-service
|
||||||
|
ExecStart=/opt/ping-service/ping_service -config /opt/ping-service/config.yaml -v
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
# Security hardening
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=true
|
||||||
|
ReadWritePaths=/opt/ping-service
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
SyslogIdentifier=ping-service
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
Reference in New Issue
Block a user