package core import ( "net" "sync" "time" ) // PingResult contains the result of a ping to a server. type PingResult struct { Ms int64 // round-trip time in ms, -1 if failed Color string } // PingAddress pings a single address using TCP connect (ICMP requires elevated privileges). // Returns round-trip time in ms or -1 on failure. func PingAddress(address string, timeout time.Duration) int64 { if timeout == 0 { timeout = 3 * time.Second } start := time.Now() conn, err := net.DialTimeout("tcp", address+":443", timeout) if err != nil { return -1 } conn.Close() return time.Since(start).Milliseconds() } // PingConfig pings a VPN config and returns the result. func PingConfig(config *VpnConfig) PingResult { link := TryParseLink(config.Link) if link == nil { return PingResult{Ms: -1, Color: "#666"} } ms := PingAddress(link.Address, 3*time.Second) if ms < 0 { return PingResult{Ms: -1, Color: "#666"} } return PingResult{Ms: ms, Color: GetPingColor(ms)} } // PingAllConfigs pings all configs concurrently. func PingAllConfigs(configs []VpnConfig) []PingResult { results := make([]PingResult, len(configs)) var wg sync.WaitGroup for i := range configs { wg.Add(1) go func(idx int) { defer wg.Done() results[idx] = PingConfig(&configs[idx]) }(i) } wg.Wait() return results } // GetPingColor returns a color hex code based on ping latency. func GetPingColor(ms int64) string { switch { case ms < 50: return "#4CAF50" // green case ms < 100: return "#8BC34A" // light green case ms < 150: return "#CDDC39" // lime case ms < 200: return "#FFC107" // amber case ms < 300: return "#FF9800" // orange default: return "#E53935" // red } }