fix: anti-stall — IP check budgets, capped timers, fetch wait_for, obfuscation auto-fallback, empty validate short-circuit, unittest suite
Made-with: Cursor
This commit is contained in:
@@ -44,6 +44,8 @@ async def validate_proxies(
|
||||
timeout_seconds: float,
|
||||
on_progress: Callable[[int, int], None] | None = None,
|
||||
) -> list[str]:
|
||||
if not proxy_urls:
|
||||
return []
|
||||
sem = asyncio.Semaphore(max(1, concurrency))
|
||||
ok: list[str] = []
|
||||
lock = asyncio.Lock()
|
||||
@@ -86,50 +88,69 @@ async def check_chain_exit_ip(
|
||||
timeout_seconds: float,
|
||||
) -> str | None:
|
||||
"""Query the IP-check URL through the local chain proxy.
|
||||
Returns the exit IP string on success, or None on failure.
|
||||
Tries the configured URL first, then falls back to alternatives."""
|
||||
urls = [check_url] + [u for u in _IP_FALLBACKS if u != check_url]
|
||||
t = httpx.Timeout(timeout_seconds, connect=min(10.0, timeout_seconds))
|
||||
Bounded total time — never stacks one slow request per fallback URL forever."""
|
||||
per = max(5.0, min(20.0, float(timeout_seconds)))
|
||||
budget = max(15.0, min(60.0, float(timeout_seconds) * 2 + 5.0))
|
||||
urls = [check_url] + [u for u in _IP_FALLBACKS if u != check_url][:2]
|
||||
|
||||
async def _run() -> str | None:
|
||||
t = httpx.Timeout(per, connect=min(8.0, per))
|
||||
try:
|
||||
async with httpx.AsyncClient(
|
||||
proxy=listen_proxy,
|
||||
timeout=t,
|
||||
verify=False,
|
||||
follow_redirects=True,
|
||||
) as c:
|
||||
for url in urls:
|
||||
try:
|
||||
r = await c.get(url)
|
||||
if r.status_code == 200:
|
||||
ip = _parse_ip(r.text)
|
||||
if ip:
|
||||
return ip
|
||||
except Exception:
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(
|
||||
proxy=listen_proxy,
|
||||
timeout=t,
|
||||
verify=False,
|
||||
follow_redirects=True,
|
||||
) as c:
|
||||
for url in urls:
|
||||
try:
|
||||
r = await c.get(url)
|
||||
if r.status_code == 200:
|
||||
ip = _parse_ip(r.text)
|
||||
if ip:
|
||||
return ip
|
||||
except Exception:
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
return await asyncio.wait_for(_run(), timeout=budget)
|
||||
except asyncio.TimeoutError:
|
||||
log.debug("check_chain_exit_ip timed out after %.1fs", budget)
|
||||
return None
|
||||
|
||||
|
||||
async def get_direct_ip(check_url: str, timeout_seconds: float = 15.0) -> str | None:
|
||||
"""Get our own exit IP without the proxy chain (so we can compare)."""
|
||||
urls = [check_url] + [u for u in _IP_FALLBACKS if u != check_url]
|
||||
t = httpx.Timeout(timeout_seconds)
|
||||
per = max(5.0, min(15.0, float(timeout_seconds)))
|
||||
budget = max(12.0, min(45.0, float(timeout_seconds) * 2))
|
||||
urls = [check_url] + [u for u in _IP_FALLBACKS if u != check_url][:2]
|
||||
|
||||
async def _run() -> str | None:
|
||||
t = httpx.Timeout(per)
|
||||
try:
|
||||
async with httpx.AsyncClient(
|
||||
timeout=t,
|
||||
verify=False,
|
||||
follow_redirects=True,
|
||||
) as c:
|
||||
for url in urls:
|
||||
try:
|
||||
r = await c.get(url)
|
||||
if r.status_code == 200:
|
||||
ip = _parse_ip(r.text)
|
||||
if ip:
|
||||
return ip
|
||||
except Exception:
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(
|
||||
timeout=t,
|
||||
verify=False,
|
||||
follow_redirects=True,
|
||||
) as c:
|
||||
for url in urls:
|
||||
try:
|
||||
r = await c.get(url)
|
||||
if r.status_code == 200:
|
||||
ip = _parse_ip(r.text)
|
||||
if ip:
|
||||
return ip
|
||||
except Exception:
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
return await asyncio.wait_for(_run(), timeout=budget)
|
||||
except asyncio.TimeoutError:
|
||||
log.debug("get_direct_ip timed out after %.1fs", budget)
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user