Files
ClusterCanvas/service/internal/auth/ping.go
T

90 lines
1.9 KiB
Go

package auth
import (
"fmt"
"net"
"os/exec"
"strings"
"time"
)
const (
PingMethodICMP = "icmp"
PingMethodTCP = "tcp"
defaultPingCount = "1"
defaultPingWaitSec = "2"
defaultTCPTimeout = 3 * time.Second
)
// PingResult is the outcome of a reachability probe.
type PingResult struct {
OK bool
Method string
Error error
}
type pingCommandFunc func(hostIP string) error
type tcpDialFunc func(address string, timeout time.Duration) error
var (
runICMPPing = defaultRunICMPPing
dialTCP = defaultDialTCP
)
// PingHost probes host reachability: ICMP first, then TCP :22 when ICMP is
// unavailable or the host does not respond to ICMP.
func PingHost(hostIP string) PingResult {
hostIP = strings.TrimSpace(hostIP)
if hostIP == "" {
return PingResult{OK: false, Error: fmt.Errorf("host IP is required")}
}
icmpErr := runICMPPing(hostIP)
if icmpErr == nil {
return PingResult{OK: true, Method: PingMethodICMP}
}
address := net.JoinHostPort(hostIP, defaultSSHPort)
tcpErr := dialTCP(address, defaultTCPTimeout)
if tcpErr == nil {
return PingResult{OK: true, Method: PingMethodTCP}
}
return PingResult{
OK: false,
Method: PingMethodTCP,
Error: fmt.Errorf(
"icmp: %v; tcp %s: %w",
icmpErr,
address,
tcpErr,
),
}
}
func defaultRunICMPPing(hostIP string) error {
pingPath, err := exec.LookPath("ping")
if err != nil {
return fmt.Errorf("ping unavailable: %w", err)
}
cmd := exec.Command(pingPath, "-c", defaultPingCount, "-W", defaultPingWaitSec, hostIP)
output, runErr := cmd.CombinedOutput()
if runErr == nil {
return nil
}
combined := strings.TrimSpace(string(output) + " " + runErr.Error())
return fmt.Errorf("%s", strings.TrimSpace(combined))
}
func defaultDialTCP(address string, timeout time.Duration) error {
conn, err := net.DialTimeout("tcp", address, timeout)
if err != nil {
return err
}
_ = conn.Close()
return nil
}