diff --git a/docs/per-context-patches.md b/docs/per-context-patches.md index 5f71c08..fc7c941 100644 --- a/docs/per-context-patches.md +++ b/docs/per-context-patches.md @@ -617,7 +617,7 @@ bundles are shipped in the wheel. | Audio seed | Derived from the identity (NewBrowser) or `randint(1, 2^32-1)` (NewContext) | Never 0 | | Timezone | From preset, or `timezone` in `CAMOU_CONFIG` | The init script calls `setTimezone()` only for an explicit value; otherwise the C++ side falls back to `CAMOU_CONFIG` (set from geoip at launch) or the browser default. | | Speech voices | `_generate_random_voice_subset()` | Follows the measured model in `voice-manifests.json`: Windows gets the display language's OneCore pack plus its legacy Desktop voices at their measured rate; macOS the compact + Eloquence base plus rare downloads; Linux speech-dispatcher's espeak-ng list. Seeded by the identity. NOT from presets. | -| WebRTC IP | Not set by default | User sets via `window.setWebRTCIPv4()`. NewContext init script defaults to empty string `""` | +| WebRTC IP | Not set by default | NewContext's `webrtc_ip` (or the proxy's exit IP) goes to `window.setWebRTCIPv4()` or `window.setWebRTCIPv6()` by address family; an invalid address raises `InvalidIP`. Without one, the init script calls `setWebRTCIPv4("")` | | Geolocation | User parameter or geoip detection | Via Playwright `context.setGeolocation()` | ### Key Files diff --git a/pythonlib/camoufox/async_api.py b/pythonlib/camoufox/async_api.py index e05c813..52cb498 100644 --- a/pythonlib/camoufox/async_api.py +++ b/pythonlib/camoufox/async_api.py @@ -204,7 +204,7 @@ async def AsyncNewContext( preset: A fingerprint preset dict to use. If None, fpgen draws a new identity. os: Target OS for the drawn identity ("windows", "macos", "linux"). ff_version: Firefox major version to claim in the UA. Defaults to the browser's own. - webrtc_ip: IPv4 address to spoof for WebRTC ICE candidates. + webrtc_ip: IPv4 or IPv6 address to spoof for WebRTC ICE candidates. proxy: Per-context proxy (Playwright format: {"server": "...", "username": "...", "password": "..."}). Unless webrtc_ip and timezone_id are both given, they are looked up from the proxy's exit IP; InvalidIP is raised if that lookup fails. diff --git a/pythonlib/camoufox/fingerprints.py b/pythonlib/camoufox/fingerprints.py index 752076c..9e4ab16 100644 --- a/pythonlib/camoufox/fingerprints.py +++ b/pythonlib/camoufox/fingerprints.py @@ -11,6 +11,7 @@ from random import Random, choice, randint, randrange from typing import Any, Dict, FrozenSet, List, Optional, Set, Tuple from camoufox._warnings import FallbackWarning +from camoufox.ip import valid_ipv4, validate_ip from camoufox.pkgman import load_yaml from camoufox.webgl import sample_webgl @@ -1759,8 +1760,10 @@ def _build_init_script(values: Dict[str, Any]) -> str: # WebRTC IP ip = values.get('webrtcIP') if ip: + validate_ip(ip) + fn_name = 'setWebRTCIPv4' if valid_ipv4(ip) else 'setWebRTCIPv6' lines.append( - f' if (typeof w.setWebRTCIPv4 === "function") w.setWebRTCIPv4({_json.dumps(ip)});' + f' if (typeof w.{fn_name} === "function") w.{fn_name}({_json.dumps(ip)});' ) else: lines.append( diff --git a/pythonlib/camoufox/sync_api.py b/pythonlib/camoufox/sync_api.py index be07644..5cd65d7 100644 --- a/pythonlib/camoufox/sync_api.py +++ b/pythonlib/camoufox/sync_api.py @@ -182,7 +182,7 @@ def NewContext( preset: A fingerprint preset dict to use. If None, fpgen draws a new identity. os: Target OS for the drawn identity ("windows", "macos", "linux"). ff_version: Firefox major version to claim in the UA. Defaults to the browser's own. - webrtc_ip: IPv4 address to spoof for WebRTC ICE candidates. + webrtc_ip: IPv4 or IPv6 address to spoof for WebRTC ICE candidates. proxy: Per-context proxy (Playwright format: {"server": "...", "username": "...", "password": "..."}). Unless webrtc_ip and timezone_id are both given, they are looked up from the proxy's exit IP; InvalidIP is raised if that lookup fails. diff --git a/pythonlib/tests/test_webrtc_ip_setter.py b/pythonlib/tests/test_webrtc_ip_setter.py new file mode 100644 index 0000000..45dd224 --- /dev/null +++ b/pythonlib/tests/test_webrtc_ip_setter.py @@ -0,0 +1,23 @@ +"""The per-context init script hands a WebRTC IP to the setter for its family.""" + +import pytest + +from camoufox.exceptions import InvalidIP +from camoufox.fingerprints import _build_init_script + + +def test_ipv4_goes_to_the_ipv4_setter(): + script = _build_init_script({"webrtcIP": "203.0.113.7"}) + assert 'w.setWebRTCIPv4("203.0.113.7")' in script + assert "setWebRTCIPv6(" not in script + + +def test_ipv6_goes_to_the_ipv6_setter(): + script = _build_init_script({"webrtcIP": "2001:db8::7"}) + assert 'w.setWebRTCIPv6("2001:db8::7")' in script + assert "2001:db8::7" not in script.split("setWebRTCIPv6")[0] + + +def test_an_invalid_address_is_refused(): + with pytest.raises(InvalidIP): + _build_init_script({"webrtcIP": "not-an-ip"})