feat(webrtc): auto-derive the WebRTC public IP + suppress IPv6 from the proxy egress

Reuse the single timezone="auto" egress lookup to also make WebRTC coherent:
- prepare_session_geo() returns (timezone, webrtc_ip) from one discovery;
  resolve_session_timezone delegates to it (timezone-only).
- launcher/async _build_env set STEALTHFOX_WEBRTC_PUBLIC_IP to the proxy egress
  (IPv4, when the caller has not pinned it) so the srflx candidate matches the
  proxy, and STEALTHFOX_WEBRTC_DISABLE_IPV6=1 to drop the leaking IPv6 host.
- prefs: baseline sets zoom.stealth.webrtc.disable_ipv6=true; the dead
  media.peerconnection.ice.disableIPv6 pref is removed.
- export prepare_session_geo / SessionGeo.

Needs a binary with the nICEr IPv6 patch to take full effect (env/pref are
no-ops on firefox-7); the public-IP srflx already works on firefox-7.

tests: tests/test_geo.py prepare_session_geo combos; full unit suite 436 green
plus live ICE verification against the patched build.
This commit is contained in:
feder-cr 2026-06-06 06:27:28 +02:00
parent 26fa962d24
commit db1d3ec359
6 changed files with 202 additions and 43 deletions

View file

@ -16,6 +16,7 @@ from invisible_playwright._geo import (
_proxy_is_set,
discover_egress_ip,
ip_to_timezone,
prepare_session_geo,
resolve_session_timezone,
)
@ -286,3 +287,69 @@ def test_resolve_proxy_failure_raises(monkeypatch):
resolve_session_timezone("auto", SOCKS)
with pytest.raises(GeoTimezoneError):
resolve_session_timezone("", SOCKS)
# ──────────────────────────────────────────────────────────────────────
# prepare_session_geo — timezone + WebRTC IP from ONE egress lookup
# ──────────────────────────────────────────────────────────────────────
@pytest.mark.unit
def test_geo_auto_proxy_sets_tz_and_webrtc(stub_egress):
g = prepare_session_geo("", SOCKS, want_webrtc=True)
assert g.timezone == "America/New_York"
assert g.webrtc_ip == "203.0.113.7" # proxy egress (IPv4) → synthetic srflx
@pytest.mark.unit
def test_geo_explicit_proxy_keeps_tz_but_still_sets_webrtc(stub_egress):
# an explicit zone doesn't resolve tz, but WebRTC still gets the proxy IP.
g = prepare_session_geo("Asia/Tokyo", SOCKS, want_webrtc=True)
assert g.timezone == "Asia/Tokyo"
assert g.webrtc_ip == "203.0.113.7"
@pytest.mark.unit
def test_geo_want_webrtc_false_skips_webrtc(stub_egress):
# caller already pinned the env var → don't auto-derive.
g = prepare_session_geo("auto", SOCKS, want_webrtc=False)
assert g.timezone == "America/New_York"
assert g.webrtc_ip is None
@pytest.mark.unit
def test_geo_no_proxy_no_webrtc(stub_egress):
g = prepare_session_geo("auto", None, want_webrtc=True)
assert g.timezone == "America/New_York" # resolved from host IP
assert g.webrtc_ip is None # no proxy → no synthetic srflx needed
@pytest.mark.unit
def test_geo_ipv6_egress_no_webrtc(monkeypatch):
monkeypatch.setattr(_geo, "discover_egress_ip", lambda *a, **k: "2001:db8::1")
monkeypatch.setattr(_geo, "ip_to_timezone", lambda ip, mmdb: "Europe/Berlin")
import invisible_playwright.download as dl
monkeypatch.setattr(dl, "ensure_geoip_mmdb", lambda *a, **k: "fake.mmdb")
g = prepare_session_geo("auto", SOCKS, want_webrtc=True)
assert g.timezone == "Europe/Berlin"
assert g.webrtc_ip is None # IPv6 not injected as a srflx candidate
@pytest.mark.unit
def test_geo_explicit_proxy_discovery_fail_is_best_effort(monkeypatch):
# explicit tz + proxy + discovery fails → keep tz, webrtc None, NO raise.
def boom(*a, **k):
raise GeoTimezoneError("down")
monkeypatch.setattr(_geo, "discover_egress_ip", boom)
g = prepare_session_geo("Asia/Tokyo", SOCKS, want_webrtc=True)
assert g.timezone == "Asia/Tokyo"
assert g.webrtc_ip is None
@pytest.mark.unit
def test_geo_auto_proxy_discovery_fail_raises(monkeypatch):
def boom(*a, **k):
raise GeoTimezoneError("down")
monkeypatch.setattr(_geo, "discover_egress_ip", boom)
with pytest.raises(GeoTimezoneError):
prepare_session_geo("auto", SOCKS, want_webrtc=True)