Сравнение7 мин чтения

socks5 и socks5h: что шесть клиентов на самом деле отправляют прокси

socks5:// не везде означает локальный DNS. Мы записали, что curl, Requests, HTTPX, aiohttp, Playwright и Node отправляют SOCKS5-прокси для каждой схемы URL.

На этой странице

В curl схема socks5:// резолвит имя целевого хоста на вашей машине, а socks5h:// передаёт имя прокси. Другие клиенты этому правилу не следуют: в этом тесте три из шести отправили имя хоста с обычным socks5://, а два из них отказались принимать socks5h:// ещё до подключения.

Клиент (проверенная версия)socks5:// отправляет проксиsocks5h://
curl 8.22.0IPv4-адрес (резолвинг локальный)имя хоста
Requests 2.34.2 + PySocks 1.7.1IPv4-адрес (резолвинг локальный)имя хоста
HTTPX 0.28.1 + socksio 1.0.0имя хостаимя хоста
aiohttp 3.14.3 + aiohttp-socks 0.12.0имя хостаValueError, соединения нет
Playwright 1.63.0 (Chromium 153.0.8010.12)имя хостаnet::ERR_NO_SUPPORTED_PROXIES, соединения нет
Node.js 24.20.0 + socks-proxy-agent 10.1.0IPv4-адрес (резолвинг локальный)имя хоста

В каждой ячейке записано, что получил логирующий SOCKS5-сервер на 127.0.0.1, когда клиента просили открыть http://dnsprobe.example/ — имя, которое существует только в локальном файле hosts и указывает на 127.0.0.2. «IPv4-адрес» значит, что прокси увидел 127.0.0.2 и так и не узнал имя. «Имя хоста» значит, что прокси получил dnsprobe.example и резолвить его пришлось бы ему. Для цели https:// каждый клиент дал тот же результат.

Почему тип адреса важен

Запрос SOCKS5 содержит один адрес назначения, и байт ATYP сообщает его тип: X'01' — IPv4, X'03' — доменное имя, X'04' — IPv6. RFC 1928, раздел 4 Клиент, который отправляет доменное имя, оставляет DNS прокси. Клиент, который отправляет IP-адрес, уже выполнил поиск сам. Из этого следуют три вещи:

  1. Запрос видит ваш собственный резолвер. DNS-запрос для целевого хоста уходит на резолвер, настроенный на вашей машине, мимо соединения с прокси. В документации Requests это сказано прямо: socks5 «causes the DNS resolution to happen on the client, rather than on the proxy server». Requests: SOCKS
  2. Ответ может оказаться не для того места. RFC 7871 отмечает, что многие авторитетные серверы имён «return different responses based on the perceived topological location of the user» и судят о нём по адресу, с которого пришёл запрос, — обычно это ваш резолвер. RFC 7871, раздел 1 Поэтому имя CDN, разрешённое локально, может указывать на сервер, выбранный для вашей сети, и прокси подключится именно к этому адресу. Выходной IP находится в нужном вам регионе, но сервер, до которого он дошёл, был выбран для вашего, так что контент может оказаться от другого региона. Это следует из того, как строится запрос; loopback-тест никакой CDN не измерял.
  3. Имена, которые может разрешить только прокси, не работают. С именем, которое не резолвится локально, curl 8.22.0 остановился на curl: (6) Could not resolve host: dnsprobe-missing.example, Requests бросил ConnectionError с текстом [Errno -2] Name or service not known, а socks-proxy-agent упал с getaddrinfo ENOTFOUND dnsprobe-missing.example. Ни один из них не отправил прокси запрос. С socks5h:// все три отправили имя прокси.

В руководстве «HTTP или SOCKS5» разобрано, какое место занимает DNS при выборе протокола прокси. Дальше речь о том, как выбрать правильную схему для каждого клиента.

Что писать в конфигурации

КлиентОтправляет прокси имя хостаЧего избегать
curlsocks5h:// или --socks5-hostnamesocks5:// и --socks5 резолвят локально
Requests + PySockssocks5h://socks5:// резолвит локально
HTTPX + socksiosocks5:// или socks5h://socks:// бросает ValueError
aiohttp + aiohttp-sockssocks5://socks5h:// бросает ValueError
Playwright Chromiumsocks5://socks5h:// падает при навигации
Node socks-proxy-agentsocks5h:// или socks://socks5:// резолвит локально

Единого URL, подходящего всем шести, нет. Если одна настройка PROXY_URL используется несколькими инструментами, то socks5h:// ломает aiohttp-socks и Playwright, а socks5:// заставляет curl, Requests и socks-proxy-agent резолвить локально. Задавайте схему отдельно для каждого клиента.

Два клиента, которые не принимают socks5h://, падают в разные моменты. aiohttp-socks бросает исключение из ProxyConnector.from_url(), до открытия сокета:

code
ValueError: Invalid scheme component: socks5h

Playwright принимает прокси в chromium.launch() и падает на первой навигации, не обращаясь к прокси:

code
page.goto: net::ERR_NO_SUPPORTED_PROXIES at http://dnsprobe.example/

Документация Playwright приводит socks5://myproxy.com:3128 как SOCKS-форму параметра server. Playwright: параметр proxy Остальные настройки запуска и контекста — в руководстве по прокси в Playwright.

Голая схема socks:// ведёт себя так же непоследовательно. Chromium и socks-proxy-agent восприняли её как SOCKS5 и отправили имя хоста. curl начал с приветствия SOCKS4 (первый байт 0x04), на которое сервер, понимающий только SOCKS5, ответить не может. Requests бросил ValueError: Unable to determine SOCKS version from socks://127.0.0.1:11080, HTTPX — ValueError: Unknown scheme for proxy URL URL('socks://127.0.0.1:11080'), а aiohttp-socks — ValueError: Invalid scheme component: socks.

Две детали установки для Python: Requests нужен extra requests[socks], а HTTPX — httpx[socks]. Без них Requests бросает Missing dependencies for SOCKS support, а HTTPX бросает ImportError с упоминанием socksio; как исправить обе ошибки, включая случай, когда URL прокси приходит из ALL_PROXY, разобрано в отдельном руководстве. Полный код клиента есть в руководстве по прокси в Python Requests и в руководстве по асинхронному прокси в HTTPX.

В этом тесте URL прокси передавался каждому клиенту явно. Если у вас он берётся из ALL_PROXY или HTTPS_PROXY, сначала убедитесь, какую переменную читает клиент: в руководстве по переменным окружения прокси это расписано по клиентам, а NO_PROXY matching, tested показывает, что правила обхода тоже различаются от клиента к клиенту.

Как проверить свою настройку

Чтобы увидеть, что отправляет ваш клиент, учётная запись у прокси-провайдера не нужна. Скачайте socks5_log.py — Python-скрипт на 42 строки без зависимостей. Он завершает приветствие SOCKS5, печатает тип адреса и адрес каждого запроса, а затем отвечает «general SOCKS server failure», так что никуда ничего не пересылается.

Дайте машине тестовое имя, известное только файлу hosts. Не используйте localhost: Chromium отправляет запросы к localhost-именам напрямую, а не через прокси. Chromium: implicit bypass rules Добавьте в /etc/hosts строку:

code
127.0.0.2 dnsprobe.example

Запустите сервер в одном терминале и отправьте по одному запросу на схему из другого:

sh
python3 socks5_log.py 1080
curl -x socks5://127.0.0.1:1080 http://dnsprobe.example/
curl -x socks5h://127.0.0.1:1080 http://dnsprobe.example/

С curl 8.18.0 сервер напечатал:

code
LISTENING 127.0.0.1:1080
CMD=1 IPv4 127.0.0.2 port=80
CMD=1 DOMAIN dnsprobe.example port=80

Первый запрос пришёл как IP-адрес, значит, curl разрешил имя локально. Второй пришёл как имя. Обе команды curl завершаются строкой curl: (97) cannot complete SOCKS5 connection to dnsprobe.example. (1) — это отказ сервера, так и должно быть. Замените curl своим клиентом и своей настройкой прокси и прочитайте те же два поля. Если строки нет вообще, клиент отверг схему или обошёл прокси.

Параметры удалённого DNS помимо схемы

Некоторые клиенты дают тот же выбор в виде параметра, обычно он называется rdns:

  • curl: в тесте --socks5-hostname host:port вёл себя как socks5h://, а --socks5 host:port — как socks5://. curl man page
  • Requests: отдельного параметра нет. SOCKS-менеджер urllib3 выставляет флаг rdns в PySocks по схеме: False для socks5, True для socks5h. Исходный код urllib3 Сам PySocks при прямом использовании принимает rdns в set_proxy() и документирует значение по умолчанию True. PySocks README
  • aiohttp-socks: ProxyConnector принимает rdns, и в README указано «default is True for socks5». aiohttp-socks README В тесте ProxyConnector.from_url("socks5://127.0.0.1:11080", rdns=False) отправил 127.0.0.2, а rdns=True — имя хоста.
  • HTTPX: версия 0.28.1 направляет socks5 и socks5h в один и тот же SOCKS-транспорт, так что схема ничего не меняет. Исходный код HTTPX
  • socks-proxy-agent: флаг lookup задаётся только схемой: socks5 и socks4 резолвят локально, socks5h, socks4a и socks — нет. Исходный код socks-proxy-agent
  • Chromium: параметра нет. В его документации сказано, что для SOCKSv5 «name resolution is always done proxy side». Документация Chromium

Метод и ограничения

Записано 4 октября 2026 года на Ubuntu 26.04 (x86_64) с Python 3.14.4 и Node.js 24.20.0. Версии — те, что в первой таблице, плюс urllib3 2.8.0, httpcore 1.0.9, python-socks 3.1.1 и пакет socks 2.8.10. curl 8.18.0 запускался рядом с 8.22.0 и отправил те же адреса. Имя, которое резолвится только в IPv6, пришло как адрес IPv6 от curl, Requests и socks-proxy-agent и как имя хоста от остальных трёх.

Лаборатория — это один loopback-сервер, который отклоняет каждый запрос. Она ничего не говорит ни о каком прокси-сервисе, о SOCKS5 с аутентификацией или UDP, о macOS, Windows, Firefox или WebKit. Значения по умолчанию в библиотеках меняются, поэтому проверяйте те версии, которые используете сами. Архив лаборатории содержит сервер, шесть клиентских скриптов, раннер и README; сырой вывод всех 62 случаев — в results.json.

Связанные материалы

ipvolt строит прокси-инфраструктуру для разработчиков и агентов. Вступите в лист ожидания — одно письмо, когда откроется доступ.

Источники

  1. curl man page: --proxy (socks5:// and socks5h://)
  2. RFC 1928: SOCKS Protocol Version 5, section 4 (address types)
  3. Requests: SOCKS proxies
  4. urllib3 2.8.0 source: contrib/socks.py (scheme to rdns)
  5. PySocks README: set_proxy and rdns
  6. HTTPX: SOCKS proxies
  7. HTTPX 0.28.1 source: proxy schemes in _transports/default.py
  8. aiohttp-socks 0.12.0 README: ProxyConnector and rdns
  9. python-socks 3.1.1 source: parse_proxy_url schemes
  10. socks-proxy-agent source: parseSocksURL
  11. Chromium network docs: SOCKSv5 proxy scheme and implicit bypass rules
  12. Playwright: browserType.launch proxy option
  13. RFC 7871: Client Subnet in DNS Queries, section 1

Теги:ProxiesTroubleshooting