package utils import ( "errors" "fmt" "net" "net/url" "strconv" "strings" "syscall" "time" ) // URLtoListenIPandPort for a given internal URL, // find the correct IP/Interface to listen to. func URLtoListenIPandPort(u string) (string, error) { parsedURL, err := url.Parse(u) if err != nil { return "", fmt.Errorf("URLtoListenIPandPort parse error: %w", err) } callURL := parsedURL.Host if parsedURL.Port() == "" { switch parsedURL.Scheme { case "http": callURL = callURL + ":80" case "https": callURL = callURL + ":443" } } conn, err := net.Dial("udp", callURL) if err != nil { return "", fmt.Errorf("URLtoListenIPandPort UDP call error: %w", err) } ipToListen := strings.Split(conn.LocalAddr().String(), ":")[0] portToListen, err := checkAndPickPort(ipToListen, 3500) if err != nil { return "", fmt.Errorf("URLtoListenIPandPort port error: %w", err) } res := net.JoinHostPort(ipToListen, portToListen) return res, nil } func checkAndPickPort(ip string, port int) (string, error) { const maxAttempts = 1000 for attempt := 1; attempt <= maxAttempts; attempt++ { conn, err := net.Listen("tcp", net.JoinHostPort(ip, strconv.Itoa(port))) if err != nil { if errors.Is(err, syscall.EADDRINUSE) { if attempt == maxAttempts { break } port++ continue } return "", fmt.Errorf("port pick error: %w", err) } conn.Close() return strconv.Itoa(port), nil } return "", fmt.Errorf("port pick error. Exceeded maximum attempts") } // HostPortIsAlive - We use this function to periodically // health check the selected device and decide if we want // to keep the entry in the list or to remove it. func HostPortIsAlive(h string) bool { conn, err := net.DialTimeout("tcp", h, time.Duration(2*time.Second)) if err != nil { return false } conn.Close() return true }