package proxyutils import ( "context" "fmt" "net" "net/url" "strings" "time" errorutil "github.com/projectdiscovery/utils/errors" "github.com/remeh/sizedwaitgroup" ) type proxyResult struct { AliveProxy string Error error } // ProxyProbeConcurrency (default 8) var ProxyProbeConcurrency = 8 const ( SOCKS5 = "socks5" HTTP = "http" HTTPS = "https" ) // GetAnyAliveProxy takes proxies as input and returns the first alive proxy // or returns error if all of them not alive func GetAnyAliveProxy(timeoutInSec int, proxies ...string) (string, error) { sg := sizedwaitgroup.New(ProxyProbeConcurrency) resChan := make(chan proxyResult, 4) ctx, cancel := context.WithCancel(context.Background()) go func() { for _, v := range proxies { // skip iterating if alive proxy is found select { case <-ctx.Done(): return default: proxy, err := GetProxyURL(v) if err != nil { resChan <- proxyResult{Error: err} continue } sg.Add() go func(proxyAddr url.URL) { defer sg.Done() select { case <-ctx.Done(): return case resChan <- testProxyConn(proxyAddr, timeoutInSec): cancel() } }(proxy) } } sg.Wait() close(resChan) }() errstack := []string{} for { result, ok := <-resChan if !ok { break } if result.AliveProxy != "" { // found alive proxy return now return result.AliveProxy, nil } else if result.Error != nil { errstack = append(errstack, result.Error.Error()) } } // all proxies are dead return "", errorutil.NewWithTag("proxyutils", "all proxies are dead got : %v", strings.Join(errstack, " : ")) } // dial and test if proxy is open func testProxyConn(proxyAddr url.URL, timeoutInSec int) proxyResult { p := proxyResult{} if Conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%s", proxyAddr.Hostname(), proxyAddr.Port()), time.Duration(timeoutInSec)*time.Second); err == nil { _ = Conn.Close() p.AliveProxy = proxyAddr.String() } else { p.Error = err } return p } // GetProxyURL returns a Proxy URL after validating if given proxy url is valid func GetProxyURL(proxyAddr string) (url.URL, error) { if url, err := url.Parse(proxyAddr); err == nil && isSupportedProtocol(url.Scheme) { return *url, nil } return url.URL{}, errorutil.New("invalid proxy format (It should be http[s]/socks5://[username:password@]host:port)").WithTag("proxyutils") } // isSupportedProtocol checks given protocols are supported func isSupportedProtocol(value string) bool { return value == HTTP || value == HTTPS || value == SOCKS5 }