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 }