Files
go-alived/internal/health/ping.go
2025-12-08 22:23:45 +08:00

130 lines
2.2 KiB
Go

package health
import (
"context"
"fmt"
"net"
"time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)
type PingChecker struct {
name string
host string
count int
timeout time.Duration
}
func NewPingChecker(name string, config map[string]interface{}) (*PingChecker, error) {
host, ok := config["host"].(string)
if !ok {
return nil, fmt.Errorf("ping checker: missing or invalid 'host' field")
}
count := 1
if c, ok := config["count"]; ok {
switch v := c.(type) {
case int:
count = v
case float64:
count = int(v)
}
}
timeout := 2 * time.Second
if t, ok := config["timeout"].(string); ok {
if d, err := time.ParseDuration(t); err == nil {
timeout = d
}
}
return &PingChecker{
name: name,
host: host,
count: count,
timeout: timeout,
}, nil
}
func (c *PingChecker) Name() string {
return c.name
}
func (c *PingChecker) Type() string {
return "ping"
}
func (c *PingChecker) Check(ctx context.Context) CheckResult {
addr, err := net.ResolveIPAddr("ip4", c.host)
if err != nil {
return CheckResultFailure
}
conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
return CheckResultFailure
}
defer conn.Close()
successCount := 0
for i := 0; i < c.count; i++ {
select {
case <-ctx.Done():
return CheckResultFailure
default:
}
if c.sendPing(conn, addr) {
successCount++
}
}
if successCount > 0 {
return CheckResultSuccess
}
return CheckResultFailure
}
func (c *PingChecker) sendPing(conn *icmp.PacketConn, addr *net.IPAddr) bool {
msg := icmp.Message{
Type: ipv4.ICMPTypeEcho,
Code: 0,
Body: &icmp.Echo{
ID: 1234,
Seq: 1,
Data: []byte("go-alived-ping"),
},
}
msgBytes, err := msg.Marshal(nil)
if err != nil {
return false
}
if _, err := conn.WriteTo(msgBytes, addr); err != nil {
return false
}
conn.SetReadDeadline(time.Now().Add(c.timeout))
reply := make([]byte, 1500)
n, _, err := conn.ReadFrom(reply)
if err != nil {
return false
}
parsedMsg, err := icmp.ParseMessage(ipv4.ICMPTypeEchoReply.Protocol(), reply[:n])
if err != nil {
return false
}
if parsedMsg.Type == ipv4.ICMPTypeEchoReply {
return true
}
return false
}