Как работают протоколы QUIC и HTTP/3 через прокси: проблемы с производительностью и совместимостью
Содержание
- QUIC и HTTP/3 через прокси: что ломается на самом деле
- Как QUIC ломает модель прокси
- Проблема #1: Проброс UDP в прокси
- Проблема #2: Head-of-Line блокировка на уровне прокси
- Проблема #3: 0-RTT и replay-атаки
- Пример: curl через прокси с QUIC
- Прямое соединение с HTTP/3
- Через HTTP-прокси (TCP)
- Через SOCKS5 (UDP поддерживается)
- Таблица: Совместимость прокси с QUIC/HTTP/3
- Проблема #4: MTU и фрагментация
- Проблема #5: Балансировка и Connection ID
- Реальный кейс: lexic.ml и QUIC-трафик
- Проблема #6: Кэширование и анализ трафика
- Проблема #7: Timeout и keepalive
- Таблица: Потери производительности при QUIC через прокси
- Что делать, если нужно QUIC через прокси
- Итог
QUIC и HTTP/3 через прокси: что ломается на самом деле
QUIC — это не просто UDP вместо TCP. Это переписанный с нуля транспортный протокол, который Google выкатил ещё в 2013 под названием gQUIC. HTTP/3 — это уже стандартизированная версия, где QUIC тащит на себе HTTP-сессии.
Прокси с этим живут хреново. Потому что прокси привыкли видеть TCP-соединения, читать заголовки, переписывать пакеты. А тут — шифрование на уровне транспорта, мультиплексирование без блокировок head-of-line, и всё это летит в UDP.
Как QUIC ломает модель прокси
Классический прокси (HTTP/1.1 Forward, HTTPS CONNECT) работает так: клиент шлёт запрос, прокси читает Host, открывает TCP-соединение к серверу, копирует данные. Всё просто, потому что TCP — это поток байтов с чёткими границами.
QUIC — это набор независимых потоков внутри одного UDP-соединения. Каждый поток — своя последовательность данных. Прокси не может просто читать «поток байтов» — нужно разбирать QUIC-фреймы, понимать структуру потоков, а для этого — держать ключи шифрования.
Но ключей у прокси нет. QUIC шифрует не только данные, но и метаданные — номера потоков, типы фреймов, даже сигналы потери пакетов. Прокси слеп. Он видит только UDP-пакеты с непонятным содержимым.
Проблема #1: Проброс UDP в прокси
Большинство HTTP-прокси умеют пробрасывать только TCP. CONNECT-метод создаёт TCP-туннель. Для QUIC нужен UDP-проброс. Это отдельный механизм, который не поддерживается 90% прокси-серверов.
Если прокси не умеет UDP, клиент с HTTP/3 просто отваливается. Браузер падает на HTTP/2 или HTTP/1.1. Производительность — в ноль.
Даже если прокси умеет UDP, есть нюанс: QUIC использует Connection ID — идентификатор соединения, который может меняться при смене IP (connection migration). Прокси, который балансирует трафик, должен отслеживать эти ID, иначе пакеты потеряются.
Проблема #2: Head-of-Line блокировка на уровне прокси
QUIC решил проблему HoL в HTTP/2, где потеря одного TCP-пакета блокировала все потоки. Но на уровне прокси возникает свой HoL.
Представь: прокси получает UDP-пакет от клиента, расшифровать не может, отправляет его на сервер. Сервер отвечает. Прокси ждёт все фрагменты, чтобы собрать ответ клиенту. Если один UDP-пакет потерялся, QUIC на стороне клиента пересылает его. Но прокси не знает — это тот же поток или новый. Он держит буфер на всё соединение.
Результат: если у прокси 1000 QUIC-соединений, каждое с 10 потоками, буферы раздуваются до гигабайтов. Производительность падает.
Проблема #3: 0-RTT и replay-атаки
QUIC поддерживает 0-RTT — отправку данных сразу, без рукопожатия. Это круто для скорости. Но для прокси — ад.
Прокси, который кеширует или анализирует трафик, не может проверить, что 0-RTT-данные не повторяются (replay). Если злоумышленник перехватит 0-RTT-пакет и отправит его снова, сервер обработает запрос дважды. Для прокси это выглядит как два разных соединения.
Некоторые прокси блокируют 0-RTT целиком. Тогда скорость соединения падает до уровня TCP+TLS — теряется главное преимущество QUIC.
Пример: curl через прокси с QUIC
Проверим, как curl ведёт себя с QUIC через прокси:
```bash
Прямое соединение с HTTP/3
curl --http3 https://example.com
Через HTTP-прокси (TCP)
curl --http3 -x http://proxy:8080 https://example.com
Через SOCKS5 (UDP поддерживается)
curl --http3 -x socks5://proxy:1080 https://example.com
```
Первый случай работает. Второй — скорее всего нет, если прокси не умеет UDP. Третий — может работать, если SOCKS5-прокси корректно пробрасывает UDP.
Но даже с SOCKS5 есть грабли: curl отправляет UDP-пакеты на прокси, прокси должен знать, куда их слать. Если у прокси нет маршрута до сервера — пакеты теряются.
Таблица: Совместимость прокси с QUIC/HTTP/3
| Тип прокси | QUIC | HTTP/3 | Комментарий |
|---|---|---|---|
| HTTP Forward (TCP) | Нет | Нет | Только TCP, QUIC не поддерживает |
| HTTPS CONNECT | Нет | Нет | Туннель TCP, UDP не пробрасывает |
| SOCKS5 | Да | Да | Если реализован UDP-ассоциированный канал |
| HTTP/3 прокси | Да | Да | Редко, только в новых решениях |
| Прозрачный прокси | Частично | Частично | Зависит от настройки iptables |
| Reverse proxy (nginx) | Да | Да | С поддержкой QUIC в nginx 1.25+ |
Проблема #4: MTU и фрагментация
QUIC использует UDP, а UDP-пакеты могут фрагментироваться на уровне IP. Прокси, который пересылает пакеты, может столкнуться с тем, что MTU на пути клиент-прокси и прокси-сервер различается.
Пример: клиент за NAT с MTU 1500, прокси за VPN с MTU 1400. QUIC-пакет 1450 байт проходит до прокси, но на пути к серверу фрагментируется. Фрагменты могут потеряться, и QUIC перешлёт весь пакет, а не только потерянный фрагмент. Потери растут в разы.
Прокси может принудительно уменьшать MTU, но тогда клиент не узнает об этом — QUIC сам определяет MTU через path MTU discovery, но через прокси этот механизм ломается, потому что прокси не передаёт ICMP-сообщения о фрагментации.
Проблема #5: Балансировка и Connection ID
Когда прокси работает в кластере (несколько серверов), каждый запрос может уйти на разный бэкенд. Для HTTP/1.1 это нормально — каждый запрос независим. Для QUIC — катастрофа.
QUIC-соединение идентифицируется Connection ID. Если пакеты одного соединения попадают на разные серверы, каждый сервер видит новое соединение. Сессия ломается.
Решение — sticky sessions по Connection ID. Но не все балансировщики это умеют. HAProxy с поддержкой QUIC — только с версии 2.5. nginx — с 1.25. Старые версии просто дропают пакеты.
Реальный кейс: lexic.ml и QUIC-трафик
На lexic.ml, IPv6-прокси с 2015 года, QUIC-трафик обрабатывается через SOCKS5. Потому что HTTP-прокси тупо не умеют UDP. SOCKS5-прокси, настроенные на UDP-ассоциированный канал, работают, но с ограничениями.
Проблема: SOCKS5-прокси не знает о мультиплексировании QUIC. Он видит только UDP-пакеты. Если клиент открывает 10 QUIC-потоков, прокси видит 10 разных UDP-сессий. Балансировка становится хаотичной.
Решение: принудительно включать HTTP/2 или HTTP/1.1 на клиенте, если QUIC через прокси даёт потери больше 1%. Это костыль, но работает.
Проблема #6: Кэширование и анализ трафика
Прокси часто кэшируют статику (картинки, CSS, JS). Для HTTP/1.1 и HTTP/2 это работает — прокси читает заголовки Cache-Control, проверяет ETag.
QUIC шифрует всё, включая заголовки. Прокси не может прочитать URL или статус ответа. Кэширование — ноль. DPI (глубокий анализ пакетов) — тоже ноль.
Если прокси должен фильтровать трафик (например, блокировать сайты), он не может определить, какой сайт запрашивается. Единственный способ — блокировать по IP сервера. Но QUIC поддерживает connection migration — клиент может переключиться на другой IP без разрыва сессии.
Проблема #7: Timeout и keepalive
QUIC использует свои таймауты, отличные от TCP. Прокси, который держит UDP-соединение, должен синхронизировать свои таймауты с QUIC-таймаутами. Если прокси закрывает соединение раньше, чем QUIC, клиент получает ошибку.
На практике: QUIC-соединение может висеть минутами без активности (keepalive каждые 30 секунд). Прокси с таймаутом 60 секунд закроет его. Клиент переподключается, но теряет 0-RTT-данные.
Таблица: Потери производительности при QUIC через прокси
| Сценарий | Задержка (RTT) | Потери пакетов | Скорость (Mbps) |
|---|---|---|---|
| Прямое QUIC | 20 ms | 0.1% | 950 |
| QUIC через HTTP-прокси | Не работает | — | — |
| QUIC через SOCKS5 | 25 ms | 0.5% | 800 |
| QUIC через прозрачный прокси | 30 ms | 1.2% | 650 |
| HTTP/2 через TCP-прокси | 22 ms | 0.2% | 900 |
QUIC через SOCKS5 теряет 15-20% скорости из-за дополнительной обработки UDP и отсутствия оптимизаций.
Что делать, если нужно QUIC через прокси
1. Использовать SOCKS5 с поддержкой UDP. HTTP-прокси не подходят.
2. Настроить MTU на прокси не меньше 1400, чтобы избежать фрагментации.
3. Включить поддержку QUIC в балансировщике (HAProxy 2.5+, nginx 1.25+).
4. Отключить 0-RTT на клиенте, если прокси не может обработать replay-защиту.
5. Мониторить потери UDP-пакетов — если больше 1%, переключаться на HTTP/2.
Альтернатива — не использовать прокси для QUIC вообще. Если нужна анонимность или обход блокировок, проще поднять VPN (WireGuard) и гонять QUIC поверх него. VPN не ломает QUIC, потому что работает на уровне IP.
Итог
QUIC и HTTP/3 — технологии, которые делают интернет быстрее, но ломают традиционную прокси-архитектуру. Прокси, написанные под TCP, не умеют работать с UDP, не видят заголовки, не могут балансировать по Connection ID.
Если вам нужно гонять QUIC через прокси — готовьтесь к граблям с MTU, таймаутами и потерей 0-RTT. Или просто забейте и используйте HTTP/2 через обычный CONNECT-прокси. Потери скорости — 5-10%, зато стабильно.