Руководство
Утечка DNS: как закрыть WebRTC, заголовки и резолвер
Утечка DNS, кандидат WebRTC или заголовок раскрывают реальный адрес раньше, чем важен выходной IP. Закройте канал через socks5h, дамп ICE и echo-тест.
Утечка DNS возникает, когда имя хоста резолвится до появления прокси-туннеля. HTTP-запрос может выходить через gw.weproxy.com.tr, а запрос к резолверу всё ещё уходит в офисный DNS, домашний роутер или DoH-эндпоинт браузера. В этом журнале остаются реальный IP клиента и запрошенное имя. ICE-кандидаты WebRTC и заголовок Forwarded открывают второй и третий канал в той же сессии.
Этот разбор отделяет TCP-рукопожатие от порядка резолвинга, показывает разницу socks5h и локального DNS и объясняет, почему UDP браузера часто остаётся вне HTTP-профиля. Примеры на cURL, Python и Node.js. Задача — совместить выходной IP и плоскость идентичности (DNS, ICE, заголовки). Чистый выход бесполезен, пока утечка открыта.
Утечка DNS происходит раньше выходного IP
Открыть https://example.com — это две задачи. Резолвинг спрашивает, на какой адрес указывает имя. Транспорт спрашивает, как TCP (и TLS) доходит до этого адреса. Прокси обычно берёт только вторую задачу. Если первая остаётся на клиенте, это утечка DNS.
Резолвер видит IP источника запроса, точное имя (QNAME) и тип (A, AAAA, HTTPS). Сайт назначения этот журнал не видит. Его видит оператор резолвера. Сбор логов, отчёты прозрачности или криво настроенный роутер превращают след в операционный риск.
DNS over HTTPS сам по себе проблему не закрывает. DoH — тоже HTTPS-запрос. Если он обходит профиль прокси, резолвер по-прежнему видит реальный IP клиента и QNAME. Если DoH идёт через прокси, резолвер видит выход прокси. Вопрос не «DNS зашифрован?», а «с какого IP ушёл запрос?».
Практическая проверка: возьмите имя, которым управляете, например probe-20261008.example.com. Поставьте клиент за прокси, запросите имя один раз и прочитайте авторитативный DNS-лог. Если источник — не ожидаемый выход, утечка DNS подтверждена. Публичные страницы «DNS leak test» показывают ту же идею. Свой лог сильнее: видно, какой резолвер ответил.
TCP-рукопожатие и порядок резолвинга
Трёхстороннее рукопожатие TCP (SYN, SYN-ACK, ACK) начинается только после того, как адрес известен. С прокси этот адрес не всегда origin. На HTTP-прокси клиент сначала открывает TCP к прокси, затем просит туннель через CONNECT. SOCKS5 делает то же самое управляющим каналом. Рукопожатие с origin идёт внутри туннеля, когда туннель уже поднят.
Порядок примерно такой:
- Клиент резолвит имя прокси (
gw.weproxy.com.tr). Этот запрос может остаться локальным. Это имя шлюза, не целевого сайта. - Клиент открывает TCP к прокси и проходит аутентификацию.
- Клиент либо отдаёт имя хоста прокси, либо сначала резолвит его сам.
- Прокси открывает TCP к origin (и резолвит имя, если на шаге 3 ушло имя, а не IP).
- Клиент отправляет TLS ClientHello внутри туннеля. SNI несёт имя origin. Это метаданные, отдельные от DNS.
Если шаг 3 резолвит локально, утечка DNS завершена до рукопожатия с origin. Тот факт, что TLS «идёт через прокси», эту строку журнала не стирает.
HTTP CONNECT оставляет имя хоста прокси
HTTP-прокси несёт HTTPS методом CONNECT. Клиент шлёт CONNECT example.com:443 HTTP/1.1. Имя хоста уходит на прокси. Здоровый клиент не превращает это имя в A-запись заранее. Прокси открывает TCP к origin, возвращает 200 Connection Established, клиент заканчивает TLS внутри туннеля.
В этой модели DNS-запрос к цели не должен попадать в резолвер клиента. Если попадает, библиотека вызвала getaddrinfo до CONNECT. Часть старых HTTP-клиентов заявляет, что прокси настроен, и всё равно резолвит локально. Проверяйте логом. Не верьте одной подписи протокола.
На обычном HTTP клиент шлёт абсолютный URL: GET http://example.com/path. Резолвинг снова на стороне прокси. Повседневная работа почти всегда идёт по пути TLS CONNECT.
Шлюз WeProxy ждёт имя пользователя и пароль на gw.weproxy.com.tr:8989. Если в пароле есть @, : или /, закодируйте их в URL. Иначе клиент примет имя пользователя за хост, и CONNECT не начнётся. Это ошибка соединения, не утечка, но их часто путают.
SOCKS5, локальный DNS и socks5h
SOCKS5 не смотрит байты приложения. RFC 1928 передаёт назначение как IPv4 (ATYP 0x01), доменное имя (ATYP 0x03) или IPv6 (ATYP 0x04). Доменное имя означает, что резолвит прокси. IPv4 означает, что клиент уже резолвил сам.
socks5h — не отдельный протокол. Это схема, которой curl и многие библиотеки говорят «отправь имя хоста на прокси». В curl --socks5 резолвит локально. --socks5-hostname и socks5h:// используют ATYP 0x03. Тот же пароль, тот же порт, другой путь DNS.
Не путайте это с UDP. Классический DNS — это UDP/53, но удалённый DNS через SOCKS5 не требует UDP ASSOCIATE. Клиент не шлёт сам запрос. Он передаёт имя хоста в управляющем канале, а прокси пользуется своим резолвером. UDP ASSOCIATE нужен для игр, голоса и части путей STUN. Слои разделены в разборе UDP-прокси и в сравнении HTTP и SOCKS5.
Статические residential-пакеты в Турции дают HTTP и обычный SOCKS5. UDP ASSOCIATE на этих пакетах нет. Это не запрещает удалённый DNS через socks5h. SOCKS5 без UDP всё ещё может нести имя хоста как ATYP 0x03. Задачам, которым нужен UDP, смотрите матрицу локаций. Привязывать тест утечки DNS к значку UDP — ложный негатив.
Почему утечка WebRTC обходит прокси
WebRTC — это сбор кандидатов IP для медиа. RTCPeerConnection строит host-кандидат с локальных интерфейсов, server-reflexive через STUN и relay через TURN. Host и server-reflexive — UDP-сокеты, которые не проходят через HTTP-прокси страницы. Профиль «весь трафик браузера» часто туннелирует только TCP 80/443.
Выход страницы может выглядеть как residential, а в строке ICE остаётся домашний или офисный IP. Пир или собственный скрипт страницы может прочитать этот кандидат. Типы кандидатов описаны в RTCPeerConnection на MDN. Это устройство API, а не случайный сбой.
Запустите фрагмент в том профиле, который уходит в работу. Если typ host или typ srflx — не выход прокси, утечка WebRTC открыта.
const pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
})
pc.createDataChannel('leak-check')
pc.onicecandidate = (event) => {
if (event.candidate) console.log(event.candidate.candidate)
}
const offer = await pc.createOffer()
await pc.setLocalDescription(offer)
Закрытие — это политика, не лозунг. Поставьте Chromium WebRtcIPHandlingPolicy в disable_non_proxied_udp, чтобы UDP вне прокси перестал порождать кандидатов. Корпоративная политика или флаг --force-webrtc-ip-handling-policy=disable_non_proxied_udp делают то же самое. Повторите дамп. Список должен опустеть или остаться только relay. Флаг «включён» не доказательство, пока строки ICE не изменились.
Антидетект-браузеры могут вшить политику в профиль. Всё равно снимите ICE один раз на профиль. При клонировании флаг иногда слетает.
Утечка заголовков и боковой путь IPv6
DNS и WebRTC выдают клиента на сетевом уровне. Утечка заголовка — это когда прокси или клиент дописывает IP клиента в запрос, который доходит до origin. Исправный forward-прокси адрес клиента не добавляет. Не путайте reverse-прокси (nginx, CDN) с forward-прокси. X-Forwarded-For на reverse-прокси — его собственная схема. Если ваш выходной прокси пересылает это значение на origin, это отдельная ошибка.
Forwarded и X-Forwarded-For
Смотрите заголовки X-Forwarded-For, Forwarded (RFC 7239), X-Real-IP, Via, Client-IP. Отправьте один запрос через прокси на echo-эндпоинт. Если в отражении видны офисный IP, внутренний адрес профиля или цепочка Via, которую добавил прокси, запишите это.
curl -sS -x "http://USER:[email protected]:8989" \
"https://httpbin.org/headers"
USER и PASSWORD — учётные данные из панели. Если этих заголовков нет, на этом запросе утечка заголовка не видна. Если есть, отделите «это добавил наш код» (interceptor в axios, расширение) от «это добавил промежуточный узел». Библиотеки иногда ставят X-Forwarded-For: 10.x «для удобства». Origin принимает значение как настоящее.
Happy Eyeballs и IPv6
Если у клиента есть рабочий IPv6, а профиль прокси только IPv4, Happy Eyeballs (RFC 8305) может гонять AAAA против A. IPv6, ушедший прямо к провайдеру, в прокси не попадает. Симптом: echo по IPv4 показывает прокси, echo по IPv6 — домашний префикс.
Сравните оба состояния проверкой IPv6: профиль включён и выключен. Если задаче IPv6 не нужен, отключите IPv6 в этом профиле. Это узкое исправление. Если IPv6 нужен, выход тоже должен быть IPv6. Иначе трафик, который вы считаете проксированным, проксирован лишь наполовину. Выходы IPv6-прокси закрывают второй случай. Сами по себе они не закрывают утечку IPv4.
Сравнение каналов утечки
| Канал | Что утекает | Типичная причина | Как закрыть |
|---|---|---|---|
| DNS | Реальный IP + QNAME | Локальный резолвинг на SOCKS5, DoH вне прокси | socks5h / --socks5-hostname, DoH внутрь туннеля |
| WebRTC | Host и srflx IP | UDP браузера вне прокси | disable_non_proxied_udp, дамп ICE |
| Заголовок | IP клиента на origin | X-Forwarded-For, Forwarded, Via | Echo-тест, убрать interceptor |
| IPv6 | Префикс провайдера | Happy Eyeballs, прокси только IPv4 | Выключить IPv6 в профиле или взять IPv6-выход |
| SNI | Имя origin (не ваш IP) | TLS ClientHello | Отдельные метаданные; это не утечка DNS |
Не принимайте SNI за утечку DNS. SNI несёт имя origin внутри TLS, и origin это имя и так ждёт. Проблема в резолвере, которого origin не видит: он пишет то же имя рядом с вашим реальным IP.
Пошаговые тесты: cURL, Python и Node.js
Учётные данные ниже — заглушки. Сначала запишите адрес без прокси через инструмент My IP. Повторите с прокси или вызовите api.ipify.org. Если адреса совпали, туннель не поднят. К тесту DNS не переходите.
cURL
# HTTP-прокси: CONNECT несёт имя хоста
curl -sS -x "http://USER:[email protected]:8989" https://api.ipify.org
echo
# SOCKS5, локальный DNS — риск утечки
curl -sS --socks5 "USER:[email protected]:8989" https://api.ipify.org
echo
# SOCKS5, удалённый DNS
curl -sS --socks5-hostname "USER:[email protected]:8989" https://api.ipify.org
echo
Вторая и третья команды могут напечатать один и тот же выходной IP. Разница DNS на IP-echo не видна. Она видна в авторитативном логе имени, которым вы управляете. Если третья команда пишет выход, а вторая — IP клиента, диагноз готов.
Python
requests пишет имя хоста в CONNECT, когда HTTPS идёт через HTTP-прокси. Для SOCKS5 нужен requests[socks] (PySocks). socks5:// на многих установках резолвит локально. Удалённый DNS требует схему socks5h://.
import requests
proxies = {
"http": "socks5h://USER:[email protected]:8989",
"https": "socks5h://USER:[email protected]:8989",
}
response = requests.get("https://api.ipify.org", proxies=proxies, timeout=30)
print(response.text)
Если хватает HTTP-прокси, используйте схему http:// и не ставьте PySocks. Интеграция Python показывает тот же формат шлюза. При trust_env=True переменная HTTP_PROXY перекрывает словарь в коде. На время теста поставьте Session.trust_env = False, иначе будете ловить ситуацию «в файле socks5h, процесс ходит по HTTP».
Node.js
Встроенный fetch в Node не подхватывает системный прокси. Для HTTP-прокси ProxyAgent из undici кладёт имя хоста в CONNECT. Для SOCKS5 с удалённым DNS используйте socks5h:// и socks-proxy-agent, чтобы агент отправил ATYP 0x03. Обёртка, которая вызывает dns.lookup и затем соединяется с IP, возвращает утечку.
import { ProxyAgent, fetch } from 'undici'
const proxy = new ProxyAgent('http://USER:[email protected]:8989')
const response = await fetch('https://api.ipify.org', { dispatcher: proxy })
console.log(await response.text())
Интеграция Node.js использует ту же форму шлюза. Агент, внедрённый через global-agent или NODE_OPTIONS, может затенить ProxyAgent в файле. Запускайте тест в чистом процессе.
Лимиты, sticky-сессия и пул
После закрытия утечки тот же выходной IP всё ещё упирается в лимит цели. 429 и Retry-After (семантика HTTP — в RFC 9110) появляются, когда слишком много параллельных запросов сидит на одном адресе. Sticky-сессия держит cookie и local storage. Она же копит счётчик на этом IP. Пул с ротацией на каждый запрос размазывает счётчик и рвёт непрерывность сессии.
Не смешивайте две задачи. Ротация не закрывает утечку DNS. На каждом новом выходе резолвер всё ещё видит ваш реальный IP. Сначала DNS и WebRTC, потом политика пула.
import time
import requests
def get_with_backoff(url, proxies, attempts=5):
delay = 1.0
response = None
for _ in range(attempts):
response = requests.get(url, proxies=proxies, timeout=30)
if response.status_code != 429:
return response
retry_after = response.headers.get("Retry-After", "")
time.sleep(float(retry_after) if retry_after.isdigit() else delay)
delay = min(delay * 2, 30)
return response
Пауза — не способ «пробить» блокировку. Если цель отвечает блокировкой аккаунта или пустым телом вместо 429, больший пул усиливает ошибку. Снизьте параллелизм, соблюдайте Retry-After и выбирайте географию только когда задаче правда нужен этот выход.
Для шагов с более высоким доверием подходит ротируемый residential-пул IP. Если одна и та же идентичность должна держаться часами, статическая residential-инфраструктура прокси фиксирует один выход. Задачам со слабой защитой и жёсткой задержкой ближе решения datacenter-прокси. Продукт выбирают после теста утечки. Сначала канал, потом продукт.
Для задач с целью в Турции турецкий выход помогает слою geo сайта и вашему DNS-логу рассказывать одну и ту же страну. Соответствие локаций — на странице прокси-локаций Турции. Турецкий выход на сайт другой страны — не ошибка протокола. Это лишняя несостыковка, если задача её не требует.
GDPR, KVKK и robots.txt
IP-адрес является персональными данными, когда по нему можно определить человека. В ЕС определение лежит в регламенте 2016/679 на EUR-Lex. В Турции закон № 6698 (KVKK) опубликован на mevzuat.gov.tr. Этот текст — не юридическая консультация. DNS-лог и кандидат WebRTC могут унести ваш реальный IP стороннему резолверу или скрипту страницы. Если в собранных данных есть чужие IP, цель и срок хранения нужно разбирать отдельно.
robots.txt — не список контроля доступа. RFC 9309 описывает его как добровольное предпочтение обхода. То, что URL открывается через прокси, не снимает запрет. Условия сайта, правила персональных данных GDPR или KVKK и допустимое использование WeProxy действуют вместе. Автоматизация остаётся на поверхностях, которые публичны и разрешены.
Чеклист на выходе WeProxy
- Запишите IP без прокси через My IP.
- Повторите через HTTP
CONNECTилиsocks5h. Не продолжайте, если адрес не сменился. - Прочитайте авторитативный DNS-лог своего имени. Источник, который не равен выходу, означает локальный резолвинг.
- Снимите ICE в браузере. Если
srflx— домашний IP, примените политику WebRTC и снимите дамп снова. - На echo-эндпоинте найдите
X-Forwarded-ForиForwarded. - Если IPv6 включён, повторите проверку IPv6 внутри профиля прокси.
- При
429сначала поправьте параллелизм иRetry-After. Не пытайтесь закрыть утечку DNS более крупным пулом. - После теста вычистите учётные данные из истории оболочки и логов CI. Пароль внутри URL превращает тест утечки в отдельный инцидент.
Когда утечка DNS закрыта, кандидат WebRTC совпадает с прокси, а запрос к origin не несёт IP клиента, выходной IP остаётся единственной плоскостью идентичности. Sticky или ротация, residential или datacenter — полезные вопросы после этого. Менять продукт в обратном порядке не меняет того, что записал резолвер.









