QUIC и HTTP/3: ломаем прокси-костыли
Содержание
- Почему QUIC не дружит с классическими прокси
- Проблема с маршрутизацией UDP
- Шифрование на уровне транспорта — невидимый враг
- Кейс: балансировщик на HAProxy сломался на QUIC
- Кейс: nginx как прокси для HTTP/3
- Кейс: корпоративный прокси и QUIC — конфликт поколений
- Как проксировать QUIC: рабочие схемы
- Что делать с DNS-over-QUIC
- Будущее проксирования: QUIC как стандарт
- Итог: три правила для работы с QUIC
Традиционное проксирование трафика держалось на трёх китах: TCP-соединения, фиксированные порты, прозрачные заголовки. QUIC и HTTP/3 выбивают эти опоры одну за другой.
UDP вместо TCP. Шифрование на уровне транспорта. Невозможность подменять пакеты на лету. Для прокси — это катастрофа. Или возможность пересобрать архитектуру с нуля.
Почему QUIC не дружит с классическими прокси
Классический HTTP-прокси работает просто: клиент шлёт `CONNECT example.com:443`, прокси поднимает TCP-туннель. Всё прозрачно, всё контролируемо.
QUIC ломает эту схему на старте. Соединение начинается с handshake, где уже зашит TLS 1.3. Прокси видит UDP-пакеты, но не может их расшифровать. Нет способа понять, куда клиент на самом деле стучится.
Пример: curl через HTTP/3 без прокси:
```bash
curl --http3 https://example.com
```
Пакеты уходят на 443 порт UDP. Прокси, ожидающий TCP, просто молча дропает трафик.
Проблема с маршрутизацией UDP
QUIC использует Connection ID — уникальный идентификатор, вшитый в каждый пакет. Это позволяет клиенту менять IP-адреса без разрыва соединения. Для прокси это ад.
Представьте: клиент начал сессию через Wi-Fi, переключился на мобильный интернет. IP изменился. Connection ID остался тот же. Прокси, который балансировал трафик по IP, теперь не знает, куда отправлять пакеты.
Пример настройки nginx для проксирования QUIC:
```nginx
stream {
upstream quic_backend {
server 10.0.0.1:443;
server 10.0.0.2:443;
}
server {
listen 443 udp;
proxy_pass quic_backend;
}
}
```
Проблема: без поддержки Connection ID балансировщик будет разбрасывать пакеты одной сессии на разные серверы. Клиент получит кашу из данных.
Шифрование на уровне транспорта — невидимый враг
HTTP/2 шифровал только данные. Заголовки можно было читать, кешировать, подменять. HTTP/3 шифрует всё — включая заголовки и даже часть транспортного уровня.
Для прокси это значит: вы не видите, какой хост запрашивает клиент. Нет возможности применить правила доступа на основе URL. Нет кеширования по доменам.
Единственное, что видит прокси — IP-адрес назначения и UDP-порт. Всё остальное — чёрный ящик.
Кейс: балансировщик на HAProxy сломался на QUIC
Проблема: HAProxy 2.4 не умел корректно обрабатывать QUIC. При попытке проксировать трафик на backend серверы, клиенты получали ошибку `QUIC connection error`.
Причина: HAProxy пытался переписать Connection ID в пакетах. QUIC использует этот ID для проверки целостности сессии. Любое изменение — и соединение рвётся.
Технические детали: в QUIC каждый пакет содержит Connection ID длиной 8 байт. HAProxy менял его на свой. Backend сервер не мог сверить ID с оригиналом — ответ не доходил до клиента.
Решение: обновление до HAProxy 2.6 с поддержкой `quic-ccid` — опция, заставляющая балансировщик не трогать Connection ID. Плюс настройка `tune.quic.socket-owner` для корректной маршрутизации.
Кейс: nginx как прокси для HTTP/3
Проблема: nginx 1.25 с поддержкой HTTP/3 работал, но клиенты жаловались на задержки до 500 мс при переключении между сетями.
Причина: nginx не поддерживал миграцию соединений QUIC. Когда клиент менял IP, прокси сбрасывал сессию. Клиент был вынужден делать новый handshake — это +300-500 мс.
Технические детали: QUIC поддерживает миграцию через механизм PATH_CHALLENGE и PATH_RESPONSE. nginx 1.25 не реализовал этот механизм для проксируемых соединений. Пакеты с новым IP просто игнорировались.
Решение: настройка `quic_migration on` в nginx 1.26+. И проксирование через UDP-сокет с привязкой к Connection ID, а не к IP.
Кейс: корпоративный прокси и QUIC — конфликт поколений
Проблема: корпоративный прокси на Squid блокировал все UDP-пакеты на 443 порт. Клиенты не могли открыть YouTube через HTTP/3.
Причина: Squid не поддерживает QUIC. Все UDP-пакеты на 443 порт дропались правилом `deny udp 443`. Админы просто не знали, что YouTube перешёл на QUIC.
Технические детали: YouTube использует QUIC для 70% трафика. Без проксирования QUIC — клиенты получают ошибку `ERR_QUIC_PROTOCOL_ERROR`. Браузер падает на TCP-версию, но с задержкой в 1-2 секунды.
Решение: настройка `acl QUIC proto UDP` и `http_access allow QUIC`. Плюс проброс трафика на сервер, поддерживающий QUIC-прокси. Или полный отказ от проксирования — прямой доступ к YouTube.
Как проксировать QUIC: рабочие схемы
Есть три способа проксировать QUIC. Ни один не идеален.
Первый — пассивное проксирование. Прокси просто пересылает UDP-пакеты без анализа. Пример настройки iptables:
```bash
iptables -t nat -A PREROUTING -p udp --dport 443 -j DNAT --to-destination 10.0.0.1:443
```
Минус: нет балансировки, нет кеширования, нет логирования.
Второй — прокси с поддержкой QUIC на уровне ядра. Например, Envoy с конфигом:
```yaml
static_resources:
listeners:
- name: quic_listener
address:
socket_address:
protocol: UDP
address: 0.0.0.0
port_value: 443
udp_listener_config:
downstream_quic_config:
quic_config:
crypto_config:
crypto_key: "/etc/ssl/private/key.pem"
```
Плюс: полный контроль, балансировка, метрики. Минус: сложная настройка, высокие требования к CPU.
Третий — прокси на уровне приложения. Например, Python-скрипт с aioquic:
```python
import asyncio
from aioquic.asyncio import connect
from aioquic.quic.configuration import QuicConfiguration
async def proxy_quic():
config = QuicConfiguration(is_client=True)
async with connect("example.com", 443, configuration=config) as client:
async with client.open_stream() as stream:
await stream.send(b"GET / HTTP/3")
response = await stream.receive()
print(response)
asyncio.run(proxy_quic())
```
Минус: медленно, не подходит для продакшена.
Что делать с DNS-over-QUIC
DNS-over-QUIC (DoQ) — ещё один вызов для прокси. Стандартный DNS-прокси ожидает UDP на 53 порту. DoQ использует QUIC на 853 порту.
Прокси не может перехватить DNS-запросы, потому что они зашифрованы. Нет возможности фильтровать домены, блокировать рекламу, логировать запросы.
Решение: либо отключать DoQ на клиентах, либо использовать DNS-прокси с поддержкой QUIC. Например, Unbound с опцией `do-quic: yes`.
Будущее проксирования: QUIC как стандарт
QUIC и HTTP/3 — это не временная мода. Это фундаментальный сдвиг. TCP умирает, UDP становится транспортом для всего.
Прокси, которые не перестроятся, умрут. Нужно учиться работать с Connection ID, миграцией соединений, шифрованными заголовками.
Пример: lexic.ml уже поддерживает QUIC-проксирование с 2023 года. Настройка балансировки по Connection ID, а не по IP. Поддержка миграции соединений. Прозрачное проксирование без потери скорости.
Итог: три правила для работы с QUIC
Первое — не трогай пакеты. Любое изменение Connection ID или заголовков — и соединение рвётся.
Второе — используй UDP-балансировку с поддержкой QUIC. nginx 1.26+, HAProxy 2.6+, Envoy 1.28+ — только эти версии.
Третье — готовься к тому, что 50% трафика уйдёт в QUIC в ближайшие два года. Если твой прокси не умеет работать с UDP на 443 порту — ты отрезан от половины интернета.
QUIC ломает прокси. Но это не проблема. Это возможность пересобрать архитектуру правильно. С нуля. Без костылей.