88 "net"
99 "net/http"
1010 "net/url"
11+ "os"
12+ "strings"
1113 "time"
1214
1315 "golang.org/x/net/proxy"
@@ -27,6 +29,57 @@ type IPInfo struct {
2729
2830const maxIPCheckResponseBytes = 1024 * 1024
2931
32+ func ipCheckServiceURLs () []string {
33+ if fromEnv := parseIPCheckServiceURLsFromEnv (); len (fromEnv ) > 0 {
34+ return fromEnv
35+ }
36+
37+ return []string {
38+ "https://api.ip2location.io/?format=json" ,
39+ "http://ip-api.com/json/" ,
40+ "https://api.ip.sb/geoip" ,
41+ "https://ipwho.is/" ,
42+ "https://ipapi.co/json/" ,
43+ "https://api.myip.com" ,
44+ "https://api64.ipify.org?format=json" ,
45+ }
46+ }
47+
48+ func parseIPCheckServiceURLsFromEnv () []string {
49+ raw := strings .TrimSpace (os .Getenv ("SBPM_IPCHECK_URLS" ))
50+ if raw == "" {
51+ return nil
52+ }
53+
54+ parts := strings .FieldsFunc (raw , func (r rune ) bool {
55+ switch r {
56+ case ',' , '\n' , '\r' , '\t' , ' ' :
57+ return true
58+ default :
59+ return false
60+ }
61+ })
62+
63+ out := make ([]string , 0 , len (parts ))
64+ seen := make (map [string ]struct {}, len (parts ))
65+ for _ , part := range parts {
66+ part = strings .TrimSpace (part )
67+ if part == "" {
68+ continue
69+ }
70+ if ! strings .HasPrefix (part , "http://" ) && ! strings .HasPrefix (part , "https://" ) {
71+ continue
72+ }
73+ if _ , ok := seen [part ]; ok {
74+ continue
75+ }
76+ seen [part ] = struct {}{}
77+ out = append (out , part )
78+ }
79+
80+ return out
81+ }
82+
3083func waitForTCPReady (addr string , maxWait time.Duration ) error {
3184 deadline := time .Now ().Add (maxWait )
3285 backoff := 50 * time .Millisecond
@@ -54,13 +107,12 @@ func waitForTCPReady(addr string, maxWait time.Duration) error {
54107func CheckProxyIP (proxyAddr string , username string , password string ) (* IPInfo , error ) {
55108 log .Printf ("[IPCheck] Starting IP check for proxy: %s (auth: %v)" , proxyAddr , username != "" )
56109
57- // Measure latency start time
58- startTime := time .Now ()
59-
60110 if err := waitForTCPReady (proxyAddr , 2 * time .Second ); err != nil {
61111 return nil , fmt .Errorf ("proxy not ready: %w" , err )
62112 }
63113
114+ httpStartTime := time .Now ()
115+
64116 // Build proxy URL with authentication if provided
65117 proxyURLStr := "http://"
66118 if username != "" && password != "" {
@@ -88,11 +140,7 @@ func CheckProxyIP(proxyAddr string, username string, password string) (*IPInfo,
88140 }
89141 defer transport .CloseIdleConnections ()
90142
91- // Use ip2location.io API (free tier, no API key needed for basic usage)
92- services := []string {
93- "https://api.ip2location.io/?format=json" ,
94- "http://ip-api.com/json/" ,
95- }
143+ services := ipCheckServiceURLs ()
96144
97145 var lastErr error
98146 var httpErr error
@@ -101,7 +149,7 @@ func CheckProxyIP(proxyAddr string, username string, password string) (*IPInfo,
101149 info , err := checkWithService (client , service )
102150 if err == nil && info .IP != "" {
103151 // Calculate latency in milliseconds
104- info .Latency = int (time .Since (startTime ).Milliseconds ())
152+ info .Latency = int (time .Since (httpStartTime ).Milliseconds ())
105153 info .Transport = "http"
106154 log .Printf ("[IPCheck] Success! IP: %s, Location: %s, Latency: %dms" , info .IP , info .Location , info .Latency )
107155 return info , nil
@@ -115,7 +163,8 @@ func CheckProxyIP(proxyAddr string, username string, password string) (*IPInfo,
115163
116164 // Try with SOCKS5 if HTTP fails
117165 log .Printf ("[IPCheck] HTTP proxy failed (%v), trying SOCKS5..." , httpErr )
118- result , err := checkWithSOCKS5 (proxyAddr , username , password , services , startTime )
166+ socksStartTime := time .Now ()
167+ result , err := checkWithSOCKS5 (proxyAddr , username , password , services , socksStartTime )
119168 if err == nil {
120169 result .Transport = "socks5"
121170 if httpErr != nil {
@@ -178,7 +227,14 @@ func checkWithSOCKS5(proxyAddr string, username string, password string, service
178227}
179228
180229func checkWithService (client * http.Client , serviceURL string ) (* IPInfo , error ) {
181- resp , err := client .Get (serviceURL )
230+ req , err := http .NewRequest (http .MethodGet , serviceURL , nil )
231+ if err != nil {
232+ return nil , fmt .Errorf ("failed to create request: %v" , err )
233+ }
234+ req .Header .Set ("User-Agent" , "sb-proxy-manager/1.0" )
235+ req .Header .Set ("Accept" , "application/json" )
236+
237+ resp , err := client .Do (req )
182238 if err != nil {
183239 return nil , fmt .Errorf ("request failed: %v" , err )
184240 }
@@ -224,6 +280,8 @@ func checkWithService(client *http.Client, serviceURL string) (*IPInfo, error) {
224280 // ip2location.io uses "country_code", ip-api uses "countryCode"
225281 if countryCode , ok := result ["country_code" ].(string ); ok {
226282 info .CountryCode = countryCode
283+ } else if countryCode , ok := result ["cc" ].(string ); ok {
284+ info .CountryCode = countryCode
227285 } else if countryCode , ok := result ["countryCode" ].(string ); ok {
228286 info .CountryCode = countryCode
229287 }
@@ -277,10 +335,7 @@ func CheckDirectIP() (*IPInfo, error) {
277335 Timeout : 10 * time .Second ,
278336 }
279337
280- services := []string {
281- "https://api.ip2location.io/?format=json" ,
282- "http://ip-api.com/json/" ,
283- }
338+ services := ipCheckServiceURLs ()
284339
285340 var lastErr error
286341 for _ , service := range services {
0 commit comments