HTTP CONNECT против SOCKS5: Низкоуровневое сравнение сетевых прокси

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

При проектировании сетевой инфраструктуры, систем сбора данных (веб-парсинга) или инфраструктуры для мультиаккаунтинга разработчики сталкиваются с выбором протокола проксирования. На практике базовый выбор сводится к двум технологиям: HTTP CONNECT и SOCKS5.

На поверхностном уровне кажется, что оба протокола делают одно и то же — пробрасывают TCP-пакеты от клиента к целевому серверу через промежуточный узел. Однако на уровнях L5 и L7 модели OSI между ними есть различия в архитектуре рукопожатий (handshake), объеме служебных данных (overhead), методах аутентификации и обработке DNS-запросов.

Давайте разберем как происходит работа протокола HTTP CONNECT и SOCKS5 и какие между ними различия.

Архитектура и сетевая модель: L5 vs L7

Сетевые прокси-протоколы классифицируются по уровню модели OSI, на котором происходит обработка и коммутация трафика.

Сравнение L5 и L7

HTTP CONNECT (RFC 9110)

Запрос CONNECT был введен в спецификации HTTP/1.1, для организации сквозного туннелирования.

Протокол работает на прикладном уровне (L7). Это означает, что промежуточный прокси-сервер должен быть полноценным HTTP-сервером: он парсит входящие текстовые заголовки, обрабатывает код метода, проверяет заголовки Host и Proxy-Authorization. И только после того, как туннель согласован, прокси-сервер переходит в режим прозрачного бинарного моста, переставая анализировать проходящие байты.

SOCKS5 (RFC 1928)

Протокол SOCKS5 (Sockets Protocol Version 5) функционирует на сеансовом уровне (L5).

SOCKS5 ничего не знает о протоколах прикладного уровня, проходящих через него (HTTP, FTP, SMTP, SSH или сырые бинарные сокеты). Он устанавливает сеанс связи между клиентом и прокси-сервером до того, как начнется обмен данными прикладного уровня. Прокси-сервер считывает только свой бинарный заголовок установления соединения, после чего транслирует TCP-сегменты или UDP-датаграммы напрямую на целевой IP-адрес.

Сравнение процедур рукопожатия

Скорость установления соединения (latency/RTT) напрямую зависит от количества сетевых итераций, необходимых для согласования сессии.

Сравнение рукопожатий

Текстовый Handshake HTTP CONNECT

После того как клиент установил TCP-соединение с прокси-сервером (порт 8080, 3128 и др.), он отправляет ASCII-текстовый HTTP-запрос:

CONNECT target.example.com:443 HTTP/1.1\r\n
Host: target.example.com:443\r\n
User-Agent: Mozilla/5.0 (X11; Linux x86_64)\r\n
Proxy-Authorization: Basic dXNlcjpwYXNzd29yZA==\r\n
Proxy-Connection: Keep-Alive\r\n
\r\n

Если аутентификация прошла успешно и целевой хост доступен, прокси-сервер отвечает:

HTTP/1.1 200 Connection Established\r\n
Proxy-Agent: Squid/6.6\r\n
\r\n

После двух символов перевода строки \r\n\r\n (CRLF) канал считается открытым. Начиная со следующего байта, всё, что отправляет клиент (например, TLS ClientHello), транслируется на target.example.com:443.

⚠️ Важно: Если прокси требует аутентификации, он вернет статус HTTP/1.1 407 Proxy Authentication Required. Клиенту придется закрыть соединение или повторить запрос с заголовком Proxy-Authorization.

Бинарный Handshake SOCKS5

В SOCKS5 обмен данными происходит не текстом, а строго разложенными по байтам структурами (структурами C).

ЭТАП 1: Согласование метода аутентификации

Клиент отправляет пакет выбора метода:

Смещение (Byte) Поле Значение Описание
0 VER 0x05 Версия протокола SOCKS (5)
1 NMETHODS 0x01 Количество поддерживаемых методов аутентификации
2 METHODS 0x00 Метод: 0x00 (NO AUTH), 0x02 (USER/PASS)

Сервер отвечает 2 байтами:

  • Byte 0: 0x05 (Версия)

  • Byte 1: 0x00 (Выбран метод 0x00 — без аутентификации)

Если выбран метод 0x02 (Username/Password, происходит дополнительный шаг: клиент отправляет байты с версией субпротокола 0x01, длиной логина, логином, длиной пароля и паролем. Это добавляет еще 1 RTT.

ЭТАП 2: Запрос на установление соединения

Клиент отправляет команду подключения:

Byte Поле Значение Описание
0 VER 0x05 Версия SOCKS5
1 CMD 0x01 Команда: 0x01 (CONNECT), 0x02 (BIND), 0x03 (UDP)
2 RSV 0x00 Зарезервированный байт (всегда 0x00)
3 ATYP 0x03 Тип адреса: 0x01 (IPv4), 0x03 (Domain), 0x04 (IPv6)
4 DST.ADDR 0x12 ... Длина домена (1 байт) + ASCII-строка домена
N DST.PORT 0x01 0xBB Порт целевого узла в Big-Endian (2 байта, e.g. 443)

Ответ прокси-сервера при успешном подключении (REP = 0x00):

Byte Поле Значение Описание
0 VER 0x05 Версия SOCKS5
1 REP 0x00 Статус: 0x00 (Succeeded / Успешно)
2 RSV 0x00 Зарезервировано
3 ATYP 0x01 Тип привязанного адреса BND.ADDR (IPv4)
4..7 BND.ADDR 192.0.2.1 IP-адрес, выделенный прокси для выходящего соединения
8..9 BND.PORT 0x1F 0x90 Порт, выделенный прокси (Big-Endian)

Анализ оверхеда, задержек (RTT) и производительности

При проектировании высоконагруженных сетевых систем критично оценивать затраты трафика на служебные заголовки и задержки на фазе открытия сокета.

Байтовый оверхед

Сравним объем служебных данных, передаваемых по сети до момента отправки полезного payload (например, первого байта TLS ClientHello):

HTTP CONNECT Overhead:
Req: CONNECT target.com:443 HTTP/1.1\r\nHost: ...\r\nProxy-Auth: ...\r\n\r\n  ~ 180-250 bytes
Res: HTTP/1.1 200 Connection Established\r\nProxy-Agent: ...\r\n\r\n      ~ 60-120 bytes
ИТОГО ОВЕРХЕД: ~ 240–370 байт

SOCKS5 Overhead (без Auth, ATYP Domain Name):
Req1 (Methods): 0x05 0x01 0x00                                             = 3 bytes
Res1 (Method Choice): 0x05 0x00                                            = 2 bytes
Req2 (Connect): 0x05 0x01 0x00 0x03 0x0A target.com 0x01 0xBB               = 17 bytes
Res2 (Response): 0x05 0x00 0x00 0x01 192.0.2.1 0x1F 0x90                   = 10 bytes
ИТОГО ОВЕРХЕД: 32 байта

Вывод по оверхеду: На этапе согласования соединения SOCKS5 передает в 7–10 раз меньше служебных байт, чем HTTP CONNECT. На миллионах коротких сессий (при парсинге) SOCKS5 существенно экономит полосу пропускания и снижает нагрузку на CPU прокси-сервера (за счет отсутствия строкового парсинга ASCII-заголовков).

Сравнение оверхэда

Сравнение RTT и latency

  • HTTP CONNECT: Потребляет 1 RTT после установления TCP-сессии для обмена текстовыми заголовками.

  • SOCKS5: Без аутентификации требует 2 RTT (согласование метода + команда CONNECT). При аутентификации по логину и паролю — 3 RTT.

Хотя у SOCKS5 больше сетевых итераций (RTT) на старте, бинарные пакеты обрабатываются сетевым стеком прокси за микросекунды. HTTP CONNECT требует 1 RTT, но вызов regex или строкового парсера HTTP-заголовков на стороне прокси под высокой нагрузкой может съесть выигранное на RTT время.

Поддержка UDP, BIND и DNS-резолвинг

Работа с UDP и BIND

Главное функциональное ограничение классического HTTP CONNECT — полное отсутствие поддержки протокола UDP. Через HTTP CONNECT нельзя проксировать DNS over UDP, WebRTC, VoIP-трафик, P2P/Torrent или QUIC (HTTP/3).

SOCKS5 нативно поддерживает UDP с помощью команды UDP ASSOCIATE (CMD = 0x03).

  1. Клиент отправляет UDP ASSOCIATE на SOCKS5-сервер по TCP-каналу.

  2. Сервер открывает на своей стороне UDP-порт и возвращает его IP и порт клиенту (BND.ADDR, BND.PORT).

  3. Клиент инкапсулирует свои UDP-датаграммы в простой SOCKS5 UDP-заголовок (всего 3 байта оверхеда + адрес назначения) и шлет их на выделенный UDP-порт прокси.

Команда BIND (CMD = 0x02) в SOCKS5 позволяет прокси-серверу принимать входящие TCP-соединения из внешнего мира (например, для входящих активных соединений в FTP или P2P-сетях). В HTTP CONNECT аналогов этой функции нет.

Сравнительная матрица

Утечка DNS и SOCKS5h

При работе с прокси критически важно, где именно происходит резолвинг доменного имени в IP-адрес: на машине клиента или на прокси-сервере.

Если клиент сначала сам запрашивает IP у своего локального DNS-провайдера, а потом отправляет IP-адрес в прокси — возникает утечка DNS. Локальный провайдер видит, к каким доменам обращается пользователь.

  • HTTP CONNECT: Клиент всегда передает доменное имя в первой строке: CONNECT target.com:443 HTTP/1.1. Резолвинг домена всегда выполняется на стороне прокси-сервера.

  • SOCKS5: Протокол поддерживает два формата. В поле ATYP можно передать IPv4 (0x01), IPv6 (0x04) или имя домена (0x03).

    • Если клиент передает IPv4 (ATYP = 0x01), значит, он выполнил DNS-запрос локально.

    • Если клиент передает доменную строку (ATYP = 0x03), резолвинг выполняет прокси-сервер. В утилитах и библиотеках этот режим называется SOCKS5h.

Утечки

Безопасность, шифрование и DPI-детекция

Шифрование канала

Ни HTTP CONNECT, ни SOCKS5 не предоставляют встроенного шифрования управляющего трафика между клиентом и прокси-сервером. Данные передаются в открытом виде.

Если вы используете SOCKS5 или HTTP CONNECT без внешнего TLS-оборачивания (например, без проксирования самого прокси через HTTPS), любой промежуточный узел (ISP, фаервол, DPI) видит:

  • Для HTTP CONNECT: заголовки CONNECT target.com:443 и данные Proxy-Authorization.

  • Для SOCKS5: факт подключения, методы аутентификации и домен/IP целевого сервера.

Однако, когда внутри сформированного туннеля устанавливается HTTPS-соединение, целевые данные (HTML, JSON, изображения) шифруются конечным ключом TLS между клиентом и целевым сервером (target.com). Прокси-сервер не может расшифровать этот трафик.

Обнаружение DPI

Современные системы глубокого анализа пакетов (DPI) легко идентифицируют оба протокола по их сигнатурам:

  1. Сигнатура SOCKS5: DPI ищет в начале TCP-сессии байты 0x05 0x01 или 0x05 0x02. Поскольку эти байты идут первыми же пакетами после SYN/ACK, незашифрованный SOCKS5 блокируется провайдерами за доли секунды.

  2. Сигнатура HTTP CONNECT: Текстовые строки CONNECT и HTTP/1.1 на нестандартных портах (не 80/443) подпадают под фильтры L7-детекции.

Для защиты от DPI прокси-протоколы инкапсулируют в TLS (HTTPS-проксирование) или используют специализированные транспортные протоколы (Shadowsocks, VLESS, Trojan, MASQUE).

Практическая реализация и примеры кода

Настройка в cURL

Подключение через HTTP CONNECT:

curl -v -x http://user:password@proxy.example.com:8080 https://target.example.com

Подключение через SOCKS5 с удаленным DNS-резолвингом (SOCKS5h — предотвращает утечку DNS):

curl -v -x socks5h://user:password@proxy.example.com:1080 https://target.example.com

⚠️ Обратите внимание на схему socks5h://. Если указать socks5://, cURL отрезолвит IP-адрес target.example.com через ваш локальный DNS-сервер и передаст на прокси готовый IP.

Python: Сравнение requests и httpx

Для использования SOCKS5 в стандартной библиотеке requests требуется установить дополнительный модуль PySocks:

pip install "requests[socks]" httpx httpx-socks

Использование HTTP CONNECT и SOCKS5 в requests:

import requests

# 1. HTTP CONNECT прокси
proxies_http = {
    "http": "http://user:pass@proxy.example.com:8080",
    "https": "http://user:pass@proxy.example.com:8080",
}
response_http = requests.get("https://httpbin.org/ip", proxies=proxies_http)
print("HTTP Proxy IP:", response_http.json())

# 2. SOCKS5h (Remote DNS) прокси
proxies_socks = {
    "http": "socks5h://user:pass@proxy.example.com:1080",
    "https": "socks5h://user:pass@proxy.example.com:1080",
}
response_socks = requests.get("https://httpbin.org/ip", proxies=proxies_socks)
print("SOCKS5 Proxy IP:", response_socks.json())

Асинхронная реализация в httpx:

import asyncio
import httpx
from httpx_socks import AsyncProxyTransport

async def fetch_via_socks():
    # Используем socks5h для удаленного резолвинга DNS
    transport = AsyncProxyTransport.from_url('socks5h://user:pass@proxy.example.com:1080')
    
    async with httpx.AsyncClient(transport=transport) as client:
        response = await client.get('https://httpbin.org/ip')
        print("Async SOCKS5 IP:", response.json())

asyncio.run(fetch_via_socks())

Go: Реализация SOCKS5 и HTTP CONNECT

В языке Go работа с HTTP CONNECT встроена в пакет net/http, а для SOCKS5 используется официальный субмодуль golang.org/x/net/proxy.

package main

import (
    "fmt"
    "io"
    "net/http"
    "net/url"
    "golang.org/x/net/proxy"
    "context"
    "net"
)

func main() {
    // 1. HTTP CONNECT Proxy
    proxyURL, _ := url.Parse("http://user:pass@proxy.example.com:8080")
    httpClient := &http.Client{
        Transport: &http.Transport{
            Proxy: http.ProxyURL(proxyURL),
        },
    }
    resp, err := httpClient.Get("https://httpbin.org/ip")
    if err == nil {
        body, _ := io.ReadAll(resp.Body)
        fmt.Println("HTTP CONNECT Result:", string(body))
        resp.Body.Close()
    }

    // 2. SOCKS5 Proxy с поддержкой Remote DNS
    dialer, err := proxy.SOCKS5("tcp", "proxy.example.com:1080", &proxy.Auth{
        User: "user",
        Password: "pass",
    }, proxy.Direct)
    if err != nil {
        panic(err)
    }

    socksTransport := &http.Transport{
        DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
            return dialer.Dial(network, addr)
        },
    }

    socksClient := &http.Client{Transport: socksTransport}
    respSocks, err := socksClient.Get("https://httpbin.org/ip")
    if err == nil {
        body, _ := io.ReadAll(respSocks.Body)
        fmt.Println("SOCKS5 Result:", string(body))
        respSocks.Body.Close()
    }
}

Альтернативный взгляд и эволюция: HTTP/2 CONNECT и MASQUE (HTTP/3)

Считать SOCKS5 единственным универсальным выбором для сложных задач было бы ошибкой. Протоколы семейства HTTP прошли значительную эволюцию:

  1. Extended CONNECT в HTTP/2: Позволяет мультиплексировать множество туннелируемых TCP-потоков внутри одного единственного TLS-соединения с прокси-сервером. Это решает проблему высоких задержек на установление множественных TCP-соединений.

  2. MASQUE / UDP proxying over HTTP/3: Позволяет проксировать UDP-датаграммы внутри QUIC-потоков HTTP/3. MASQUE превосходит SOCKS5 по устойчивости к потерям пакетов и обеспечивает встроенное TLS 1.3 шифрование туннеля по умолчанию.

Нетривиальный факт: Взаимные блокировки при использовании TCP over TCP

Использование SOCKS5 или HTTP CONNECT для создания TCP-туннеля, внутри которого затем поднимается еще одно TCP-соединение (например, OpenVPN в режиме TCP или внутренний HTTPS-запрос), создает так называемый феномен TCP Meltdown (TCP over TCP Meltdown).

Если на внешнем сетевом канале между клиентом и прокси происходят потери пакетов, Таймеры Повторной Передачи внешнего TCP-соединения и внутреннего TCP-соединения начинают накладываться друг на друга. Внутренний TCP-стек начинает повторно отправлять пакеты, считая их потерянными, что еще сильнее забивает буферы внешнего сокета. В результате пропускная способность канала падает практически до нуля. Чтобы избежать этого эффекта, для внутренних туннелей предпочтительнее использовать протоколы поверх UDP (что делает SOCKS5 с его UDP ASSOCIATE намного эффективнее).

Оцените статью
autoparse.tech
Добавить комментарий