aboutsummaryrefslogtreecommitdiff
path: root/internal/monitor/icmp.go
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-01-19 19:41:22 -0800
committerFuwn <[email protected]>2026-01-19 19:41:22 -0800
commit5cb6f53da0f5927edad01e854432eb0b70371b89 (patch)
tree133824d444dc38134996eadb30a34ed63b44cfd1 /internal/monitor/icmp.go
parentfeat: Add disable_ping_tooltips option to hide ping hover details (diff)
downloadkaze-5cb6f53da0f5927edad01e854432eb0b70371b89.tar.xz
kaze-5cb6f53da0f5927edad01e854432eb0b70371b89.zip
feat: Add ICMP, DNS, and GraphQL monitor types
Add three new monitor types with full support: - ICMP: Ping monitoring with configurable packet count, tracks packet loss and average RTT. Marks degraded on partial packet loss. - DNS: DNS resolution monitoring supporting A, AAAA, CNAME, MX, and TXT records. Optional custom DNS server and validation of expected IPs/CNAME. - GraphQL: GraphQL endpoint monitoring with query execution, variable support, error detection, and content validation. All new monitors include retry support, response time tracking, and integrate with existing display options (round_response_time, etc). GraphQL monitors also support SSL certificate tracking.
Diffstat (limited to 'internal/monitor/icmp.go')
-rw-r--r--internal/monitor/icmp.go150
1 files changed, 150 insertions, 0 deletions
diff --git a/internal/monitor/icmp.go b/internal/monitor/icmp.go
new file mode 100644
index 0000000..8b1385c
--- /dev/null
+++ b/internal/monitor/icmp.go
@@ -0,0 +1,150 @@
+package monitor
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/Fuwn/kaze/internal/config"
+ "github.com/go-ping/ping"
+)
+
+// ICMPMonitor monitors hosts using ICMP ping
+type ICMPMonitor struct {
+ name string
+ target string
+ interval time.Duration
+ timeout time.Duration
+ retries int
+ roundResponseTime bool
+ roundUptime bool
+ count int // Number of ping packets to send
+}
+
+// NewICMPMonitor creates a new ICMP monitor
+func NewICMPMonitor(cfg config.MonitorConfig) (*ICMPMonitor, error) {
+ // Default to 4 pings if not specified
+ count := 4
+ if cfg.PingCount > 0 {
+ count = cfg.PingCount
+ }
+
+ return &ICMPMonitor{
+ name: cfg.Name,
+ target: cfg.Target,
+ interval: cfg.Interval.Duration,
+ timeout: cfg.Timeout.Duration,
+ retries: cfg.Retries,
+ roundResponseTime: cfg.RoundResponseTime,
+ roundUptime: cfg.RoundUptime,
+ count: count,
+ }, nil
+}
+
+// Name returns the monitor's name
+func (m *ICMPMonitor) Name() string {
+ return m.name
+}
+
+// Type returns the monitor type
+func (m *ICMPMonitor) Type() string {
+ return "icmp"
+}
+
+// Target returns the monitor target
+func (m *ICMPMonitor) Target() string {
+ return m.target
+}
+
+// Interval returns the check interval
+func (m *ICMPMonitor) Interval() time.Duration {
+ return m.interval
+}
+
+// Retries returns the number of retry attempts
+func (m *ICMPMonitor) Retries() int {
+ return m.retries
+}
+
+// HideSSLDays returns whether to hide SSL days from display
+func (m *ICMPMonitor) HideSSLDays() bool {
+ return false // ICMP doesn't use SSL
+}
+
+// RoundResponseTime returns whether to round response time
+func (m *ICMPMonitor) RoundResponseTime() bool {
+ return m.roundResponseTime
+}
+
+// RoundUptime returns whether to round uptime percentage
+func (m *ICMPMonitor) RoundUptime() bool {
+ return m.roundUptime
+}
+
+// Check performs the ICMP ping check
+func (m *ICMPMonitor) Check(ctx context.Context) *Result {
+ result := &Result{
+ MonitorName: m.name,
+ Timestamp: time.Now(),
+ }
+
+ pinger, err := ping.NewPinger(m.target)
+ if err != nil {
+ result.Status = StatusDown
+ result.Error = fmt.Errorf("failed to create pinger: %w", err)
+ return result
+ }
+
+ // Configure pinger
+ pinger.Count = m.count
+ pinger.Timeout = m.timeout
+ pinger.SetPrivileged(false) // Use unprivileged mode (UDP) by default
+
+ // Run with context cancellation support
+ done := make(chan error, 1)
+ go func() {
+ done <- pinger.Run()
+ }()
+
+ select {
+ case <-ctx.Done():
+ pinger.Stop()
+ result.Status = StatusDown
+ result.Error = fmt.Errorf("ping cancelled: %w", ctx.Err())
+ return result
+ case err := <-done:
+ if err != nil {
+ result.Status = StatusDown
+ result.Error = fmt.Errorf("ping failed: %w", err)
+ return result
+ }
+ }
+
+ stats := pinger.Statistics()
+
+ // If no packets were received, mark as down
+ if stats.PacketsRecv == 0 {
+ result.Status = StatusDown
+ result.Error = fmt.Errorf("no packets received (100%% packet loss)")
+ result.ResponseTime = m.timeout
+ return result
+ }
+
+ // Use average RTT as response time
+ result.ResponseTime = stats.AvgRtt
+
+ // Determine status based on packet loss
+ packetLoss := float64(stats.PacketsSent-stats.PacketsRecv) / float64(stats.PacketsSent) * 100
+
+ if packetLoss == 0 {
+ result.Status = StatusUp
+ } else if packetLoss < 50 {
+ result.Status = StatusDegraded
+ result.Error = fmt.Errorf("%.0f%% packet loss", packetLoss)
+ } else {
+ result.Status = StatusDown
+ result.Error = fmt.Errorf("%.0f%% packet loss", packetLoss)
+ }
+
+ return result
+}