package app import ( "fmt" "strconv" "strings" "time" egressutilpkg "selective-vpn-api/app/egressutil" ) func egressProbeExternalIPInNetns(client TransportClient, ns string) (string, error) { endpoints := egressLimitEndpointsForNetns(egressIPEndpoints()) ip, errs := egressutilpkg.ProbeFirstSuccess(endpoints, func(rawURL string) (string, error) { return egressProbeURLInNetns(client, ns, rawURL, egressIdentityProbeTimeout) }) if strings.TrimSpace(ip) != "" { return ip, nil } if len(errs) == 0 { return "", fmt.Errorf("egress probe endpoints are not configured") } return "", fmt.Errorf("%s", egressJoinErrorsCompact(errs)) } func egressProbeExternalIPInNetnsViaProxy(client TransportClient, ns, proxyURL string) (string, error) { proxy := strings.TrimSpace(proxyURL) if proxy == "" { return "", fmt.Errorf("proxy url is empty") } endpoints := egressLimitEndpointsForNetns(egressIPEndpoints()) ip, errs := egressutilpkg.ProbeFirstSuccess(endpoints, func(rawURL string) (string, error) { return egressProbeURLInNetnsViaProxy(client, ns, rawURL, proxy, egressIdentityProbeTimeout) }) if strings.TrimSpace(ip) != "" { return ip, nil } if len(errs) == 0 { return "", fmt.Errorf("egress probe endpoints are not configured") } return "", fmt.Errorf("%s", egressJoinErrorsCompact(errs)) } func egressProbeURLViaInterface(rawURL, iface string, timeout time.Duration) (string, error) { curl := egressutilpkg.ResolveCurlPath() sec := egressutilpkg.TimeoutSec(timeout) if curl != "" { args := []string{ "-4", "-fsSL", "--max-time", strconv.Itoa(sec), "--connect-timeout", "2", "--interface", iface, rawURL, } stdout, stderr, code, err := runCommandTimeout(timeout+time.Second, curl, args...) if err != nil || code != 0 { return "", transportCommandError(shellJoinArgs(append([]string{curl}, args...)), stdout, stderr, code, err) } return egressutilpkg.ParseIPFromBody(stdout) } wget := egressutilpkg.ResolveWgetPath() if wget == "" { return "", fmt.Errorf("curl/wget are not available for interface-bound egress probe") } bindAddr := egressInterfaceBindAddress(iface) if bindAddr == "" { return "", fmt.Errorf("cannot resolve IPv4 address for interface %q", iface) } args := []string{ "-4", "-q", "-T", strconv.Itoa(sec), "-O", "-", "--bind-address", bindAddr, rawURL, } stdout, stderr, code, err := runCommandTimeout(timeout+time.Second, wget, args...) if err != nil || code != 0 { return "", transportCommandError(shellJoinArgs(append([]string{wget}, args...)), stdout, stderr, code, err) } return egressutilpkg.ParseIPFromBody(stdout) } func egressProbeURLInNetns(client TransportClient, ns, rawURL string, timeout time.Duration) (string, error) { sec := egressutilpkg.TimeoutSec(timeout) resolveHost, resolvePort, resolveIP := egressutilpkg.ResolvedHostForURL(rawURL) curlBin := egressutilpkg.ResolveCurlPath() if curlBin == "" { return "", fmt.Errorf("curl is not available for netns probe") } curlArgs := []string{ "-4", "-fsSL", "--max-time", strconv.Itoa(sec), "--connect-timeout", "2", } if resolveHost != "" && resolveIP != "" && resolvePort > 0 { curlArgs = append(curlArgs, "--resolve", fmt.Sprintf("%s:%d:%s", resolveHost, resolvePort, resolveIP)) } curlArgs = append(curlArgs, rawURL) curlCmd := append([]string{curlBin}, curlArgs...) name, args, err := transportNetnsExecCommand(client, ns, curlCmd...) if err != nil { return "", err } stdout, stderr, code, runErr := runCommandTimeout(timeout+time.Second, name, args...) if runErr != nil || code != 0 { return "", transportCommandError(shellJoinArgs(append([]string{name}, args...)), stdout, stderr, code, runErr) } return egressutilpkg.ParseIPFromBody(stdout) } func egressProbeURLInNetnsViaProxy( client TransportClient, ns string, rawURL string, proxyURL string, timeout time.Duration, ) (string, error) { curlBin := egressutilpkg.ResolveCurlPath() if curlBin == "" { return "", fmt.Errorf("curl is not available for proxy probe") } sec := egressutilpkg.TimeoutSec(timeout) args := []string{ "-4", "-fsSL", "--max-time", strconv.Itoa(sec), "--connect-timeout", "3", "--proxy", strings.TrimSpace(proxyURL), rawURL, } cmd := append([]string{curlBin}, args...) name, netnsArgs, err := transportNetnsExecCommand(client, ns, cmd...) if err != nil { return "", err } stdout, stderr, code, runErr := runCommandTimeout(timeout+time.Second, name, netnsArgs...) if runErr != nil || code != 0 { return "", transportCommandError(shellJoinArgs(append([]string{name}, netnsArgs...)), stdout, stderr, code, runErr) } return egressutilpkg.ParseIPFromBody(stdout) }