Сайдкар: таймзона браузера берётся из IP узла, а не жёстко МСК (#3187)

camoufox 0.5.5 при geoip=True кладёт координаты из GeoIP-записи exit-IP
безусловно, а timezone — через setdefault, поэтому ручной
config["timezone"]="Europe/Moscow" перебивал зону из IP. Узлы пула 13 и 15
(T2 NSK, UTC+7) и 14 (МегаФон Самара, UTC+4, RIPE 17.09) заявляли МСК при
сибирских/самарских координатах, а библиотека писала LeakWarning на каждом
запуске (4715 штук за 13 ч при 665 запусках). Ротация узла идёт внутри
подсети оператора, смена узла релончит браузер — зона с запуска не протухает.

Тест: в kwargs запуска нет ключей timezone/geolocation:* в config, geoip=True.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
bot-backend 2026-09-17 12:49:37 +05:00
parent 34642e1dd5
commit c871f804cf
2 changed files with 53 additions and 4 deletions

View file

@ -895,10 +895,16 @@ async def _launch_browser(provider: str, proxy_override: str | None = None) -> N
"block_images": _BLOCK_IMAGES_BY_PROVIDER.get(provider, _BLOCK_IMAGES_DEFAULT_FALLBACK), "block_images": _BLOCK_IMAGES_BY_PROVIDER.get(provider, _BLOCK_IMAGES_DEFAULT_FALLBACK),
# Анти-leak: WebRTC может слить реальный IP контейнера мимо прокси → деанон. # Анти-leak: WebRTC может слить реальный IP контейнера мимо прокси → деанон.
"block_webrtc": True, "block_webrtc": True,
# Форсим таймзону +3 МСК. geoip берёт tz одного IP на старте, а БэкКоннект # Таймзону НЕ задаём (#3187): её ставит geoip=True по exit-IP на запуске —
# ротирует IP на каждый запрос (видели Ульяновск UTC+4 при выходе СПб/МСК) → # из той же GeoIP-записи, что и координаты geolocation. Жёсткий
# геолик tz↔IP. Жёсткий Europe/Moscow убирает рассинхрон для РФ-мобильного. # Europe/Moscow в config (было) camoufox 0.5.5 применял поверх geoip
"config": {"timezone": "Europe/Moscow"}, # (timezone — setdefault), а координаты брал из IP: для узлов вне UTC+3
# браузер заявлял МСК при новосибирских координатах и адресе (узлы 13/15 —
# T2 NSK, UTC+7; 14 — МегаФон Самара, UTC+4 по RIPE, 17.09.2026) и писал
# LeakWarning «Please use the geoip parameter» на каждом запуске. Ротация
# узла пула меняет IP внутри той же подсети оператора (health-логи 48 ч:
# 176.59.137.x/176.59.144.x у 15, 178.176.78-79.x у 14), а смена узла
# релончит браузер (_launched_proxy) — зона, взятая на запуске, не протухает.
} }
if proxy is not None: if proxy is not None:
kwargs["proxy"] = proxy kwargs["proxy"] = proxy

View file

@ -149,3 +149,46 @@ def test_ensure_browser_env_then_pool_relaunches(monkeypatch: pytest.MonkeyPatch
asyncio.run(_run()) asyncio.run(_run())
assert calls == [("avito", None), ("avito", "http://pool:8080")] assert calls == [("avito", None), ("avito", "http://pool:8080")]
# ── #3187: гео-параметры запуска camoufox ────────────────────────────────────────
def test_launch_leaves_timezone_to_geoip(monkeypatch: pytest.MonkeyPatch) -> None:
"""Зона и координаты — только от geoip по exit-IP, вручную в config не задаются.
camoufox 0.5.5 (utils.launch_options): geoip кладёт в config координаты
безусловно, а timezone через setdefault, то есть ручной config["timezone"]
перебивает зону из IP, и координаты с зоной расходятся (узел в Новосибирске
с заявленной МСК). Ключи 'timezone' и 'geolocation:*' в config ровно те,
на которые библиотека пишет LeakWarning (warn_manual_config).
"""
import sys
import types
captured: dict[str, object] = {}
class _FakeCamoufox:
def __init__(self, **kwargs: object) -> None:
captured.update(kwargs)
async def __aenter__(self) -> object:
return object()
fake_mod = types.ModuleType("camoufox.async_api")
fake_mod.AsyncCamoufox = _FakeCamoufox # type: ignore[attr-defined]
monkeypatch.setitem(sys.modules, "camoufox", types.ModuleType("camoufox"))
monkeypatch.setitem(sys.modules, "camoufox.async_api", fake_mod)
asyncio.run(server._launch_browser("avito", proxy_override="http://u:p@176.59.137.239:8080"))
assert captured["geoip"] is True
assert captured["proxy"] == {
"server": "http://176.59.137.239:8080",
"username": "u",
"password": "p",
}
config = captured.get("config", {})
assert isinstance(config, dict)
manual_geo = sorted(k for k in config if k == "timezone" or k.startswith("geolocation:"))
assert manual_geo == []