← Назад в базу знаний

Почему IPv6 прокси утекает через WebRTC в Chrome и как это закрыть

Почему IPv6 прокси утекает через WebRTC в Chrome и как это закрыть

Что вообще происходит

WebRTC в Chrome — это не один протокол, а зоопарк из ICE, STUN, TURN, DTLS и SRTP. Чтобы установить peer-to-peer соединение, браузеру нужно узнать свой публичный адрес. Он опрашивает STUN-серверы, те отвечают: «твой адрес такой-то». И вот тут начинается веселье.

Проблема в том, что Chrome собирает ICE-кандидатов со всех сетевых интерфейсов. Не только с того, через который идёт прокси. Если у машины есть IPv6-адрес — глобальный или через туннель — он попадёт в список кандидатов. Даже если весь остальной трафик идёт через SOCKS5.

STUN-запрос уходит по UDP. SOCKS5 поддерживает UDP ASSOCIATE, но многие клиенты и библиотеки это игнорируют. Chrome в том числе — он шлёт STUN напрямую, минуя прокси, если системный прокси не настроен на уровне ОС.

Почему именно IPv6

С IPv4 проще: NAT. Домашний роутер подменяет адрес, и STUN видит только внешний IP шлюза. Утечка есть, но она показывает адрес провайдера, а не машины.

С IPv6 NAT нет по определению. Каждое устройство получает глобально маршрутизируемый адрес. `/64` на домашнюю сеть — норма. И этот адрес уходит в STUN-запрос как есть. Плюс Chrome добавляет host-кандидатов — локальные адреса интерфейсов, включая link-local `fe80::/10` и уникальные локальные `fc00::/7`.

STUN-сервер видит запрос с source-адресом. Если IPv6 идёт мимо прокси — утечка. Причём mDNS-обфускация, которую Chrome включает для host-кандидатов, работает только для приватных IPv4 и link-local. Глобальный IPv6 утекает открытым текстом.

Как выглядит утечка снаружи

Любой сайт может запустить RTCPeerConnection и собрать кандидатов. Код на JavaScript — пять строк:

```javascript

const pc = new RTCPeerConnection({iceServers: [{urls: 'stun:stun.l.google.com:19302'}]});

pc.createDataChannel('x');

pc.onicecandidate = e => console.log(e.candidate?.candidate);

pc.createOffer().then(o => pc.setLocalDescription(o));

```

Через секунду в консоли появятся строки вида `candidate:842163049 1 udp 1677729535 2a00:1234:5678::1 54321 typ srflx`. Адрес `2a00:1234:5678::1` — ваш реальный. Прокси тут ни при чём.

Проверить можно на `browserleaks.com/webrtc` или `ipleak.net`. Если видите IPv6, которого не должно быть — утечка.

Почему прокси не спасает

Корпоративный прокси или расширение вроде FoxyProxy настраивают HTTP/HTTPS трафик. SOCKS5 чуть шире. Но WebRTC использует UDP. HTTP-прокси UDP не умеет вообще. SOCKS5 умеет через UDP ASSOCIATE, но:

1. Chrome не отправляет STUN через SOCKS5 UDP ASSOCIATE — это открытый баг с 2015 года.

2. Расширения-прокси не перехватывают WebRTC API.

3. Системный прокси в Windows/macOS на WebRTC не влияет.

Итого: браузер шлёт UDP-пакеты напрямую в сеть. Если IPv6-маршрут есть — пакет уйдёт.

Отключаем WebRTC целиком

Самый тупой и надёжный способ. В Chrome: `chrome://flags/#disable-webrtc` → Enabled. Перезапуск. Всё, WebRTC мёртв. Минус — видеозвонки в Google Meet, Discord в браузере, Jitsi перестанут работать.

Политикой через реестр или `policy.json`:

```json

{

"WebRtcIPHandlingPolicy": "disable_non_proxied_udp",

"WebRtcUdpPortRange": ""

}

```

`disable_non_proxied_udp` — компромисс. WebRTC работает, но только через прокси. Если прокси UDP не поддерживает — соединение не установится. Для большинства задач это ок.

Настройка политики правильно

`WebRtcIPHandlingPolicy` принимает четыре значения. Разберём:

| Значение | Поведение | Утечка IPv6 |

|---|---|---|

| `default` | Все интерфейсы | Да |

| `default_public_and_private_interfaces` | Все, но маскирует приватные | Частично |

| `default_public_interface_only` | Только публичный | Нет, если маршрут один |

| `disable_non_proxied_udp` | Только через прокси | Нет |

Для Linux — файл `/etc/opt/chrome/policies/managed/webrtc.json`. Для Windows — `HKLM\SOFTWARE\Policies\Google\Chrome\WebRtcIPHandlingPolicy` = `disable_non_proxied_udp`. Для macOS — `defaults write com.google.Chrome WebRtcIPHandlingPolicy -string disable_non_proxied_udp`.

Проверить: `chrome://policy`. Политика должна быть в списке с source `Platform`.

Отключаем IPv6 на уровне ОС

Радикально, но работает. Linux:

```bash

sysctl -w net.ipv6.conf.all.disable_ipv6=1

sysctl -w net.ipv6.conf.default.disable_ipv6=1

```

Постоянно — в `/etc/sysctl.d/99-no-ipv6.conf`. Windows: снять галочку в свойствах адаптера или `Disable-NetAdapterBinding -Name "Ethernet" -ComponentID ms_tcpip6`.

Минус очевиден: IPv6-only ресурсы станут недоступны. Google, Cloudflare, Hetzner давно отдают AAAA. Часть CDN на IPv6 быстрее. Но если задача — не светить адрес, это самый простой путь.

Маршрутизация IPv6 через прокси

Если IPv6 нужен, но утечка недопустима — маршрутизируем весь IPv6 через туннель. SOCKS5 с IPv6 в lexic.ml работает через `udp associate`. Настраиваем системно через tun2socks или аналоги.

Пример с `redsocks` для TCP и отдельный UDP-форвардер:

```bash

/etc/redsocks.conf

redsocks {

local_ip = 127.0.0.1;

local_port = 12345;

ip = 2001:db8::1;

port = 1080;

type = socks5;

}

```

Плюс iptables/ip6tables:

```bash

ip6tables -t nat -A OUTPUT -p udp --dport 19302 -j REDIRECT --to-port 12345

```

Тут важно: STUN по умолчанию на порту 3478 и 19302 (Google). Блокируем или редиректим. Если редиректим — STUN-сервер увидит адрес прокси, что нам и нужно.

Кейс: корпоративный ноутбук в гостевой сети

Инженер подключается к клиентскому Wi-Fi с IPv6 от провайдера. Прокси настроен в браузере. Заходит на внутренний портал — тот палит его реальный IPv6 через WebRTC и логирует. Причина: политика `default`, IPv6-маршрут есть, STUN ушёл напрямую на `stun.l.google.com:19302`. Решение: развернули GPO с `WebRtcIPHandlingPolicy=disable_non_proxied_udp` на 400 машин. Через сутки проверили — утечек нет. Побочка: часть сотрудников не смогла звонить в Teams через браузер, перешли на десктопный клиент.

Кейс: анонимный парсинг

Сервис парсит сайты через пул IPv6-прокси. Каждый воркер — отдельный Chrome в Docker. Через неделю заметили: часть сайтов блокирует по реальному адресу хоста, а не прокси. Причина: Chrome в контейнере видел `eth0` с глобальным IPv6 (Docker с `--ipv6`), слал host-кандидатов. Решение: в `docker-compose.yml` добавили `sysctls: net.ipv6.conf.all.disable_ipv6=1` для контейнеров. Плюс флаг `--disable-features=WebRtcHideLocalIpsWithMdns` убрали, оставили дефолтную mDNS-обфускацию. Утечки прекратились, скорость парсинга выросла на 12% — меньше таймаутов на STUN.

Кейс: тестирование геолокации

QA-команда проверяет выдачу для разных стран. Используют прокси, но результаты плавают. Причина: WebRTC отдавал реальный IPv6, сайт определял страну по нему, а не по прокси. Разбирались через Wireshark: видели STUN Binding Request с source-адресом офисной сети. Решение: подняли локальный STUN-сервер, который отвечает фиктивным адресом, и завернули весь UDP:3478/19302 на него через nftables. Дополнительно — `WebRtcIPHandlingPolicy=default_public_interface_only`. Тесты стали воспроизводимыми.

Firefox и Safari — не лучше

Firefox исторически утекает меньше: `media.peerconnection.ice.default_address_only` и `media.peerconnection.ice.no_host` в `about:config`. Но по умолчанию обе выключены. Safari на macOS отдаёт IPv6 неохотнее, но всё равно может. Не думайте, что смена браузера решает проблему.

В Firefox политику можно задать через `policies.json`:

```json

{

"policies": {

"Preferences": {

"media.peerconnection.ice.no_host": true,

"media.peerconnection.ice.default_address_only": true

}

}

}

```

Положите в `/etc/firefox/policies/policies.json` на Linux или в `distribution/` рядом с бинарём на Windows.

Проверка после настройки

Не верьте на слово. Открывайте `chrome://webrtc-internals` — там видно все ICE-кандидаты в реальном времени. Если в списке есть IPv6, которого быть не должно — настройка не сработала.

Второй способ — внешний сервис. `browserleaks.com/webrtc` покажет и публичный, и локальные адреса. Запустите тест трижды: до настройки, после, и после перезапуска браузера. Политики применяются при старте, не на лету.

Третий — tcpdump на интерфейсе:

```bash

sudo tcpdump -i any -n 'udp port 3478 or udp port 19302'

```

Если видите пакеты с вашего реального адреса — утечка жива. Если пусто — либо всё завернуто, либо WebRTC отключён.

Что делать прямо сейчас

Чек-лист без воды:

1. Откройте `browserleaks.com/webrtc`. Запишите, что видно.

2. Если IPv6 совпадает с реальным — ставьте `disable_non_proxied_udp`.

3. Перезапустите браузер. Проверьте снова.

4. Если не помогло — отключайте IPv6 на интерфейсе.

5. Для парка машин — GPO или MDM-профиль.

6. Для контейнеров — `disable_ipv6=1` в sysctl.

7. Для тестов — локальный STUN с подменой.

Утечка WebRTC через IPv6 — не баг, а фича архитектуры. Chrome честно пытается установить соединение кратчайшим путём. Прокси для него — деталь реализации HTTP-стека, не более. Пока это не осознать, любые настройки будут костылём.

✔️Купить прокси