# QUIC и HTTP/3 - современный транспорт поверх UDP _Протоколы · LinuxLab Knowledge Base_ **TL;DR:** QUIC - транспорт поверх UDP. TLS 1.3 встроен (1 RTT, 0-RTT для resume). Multiplexing без head-of-line blocking. Connection migration (Wi-Fi → 4G без drop). HTTP/3 = HTTP-семантика поверх QUIC. ## Зачем QUIC HTTP/2 решил большую часть проблем HTTP/1.1 (мультиплексирование, бинарность, header compression). Но **HTTP/2 поверх TCP** имеет фундаментальный изъян: **head-of-line blocking на уровне транспорта**. Если в TCP-stream'е потерян один пакет, **весь поток ждёт ретрансмита**. HTTP/2 умеет parallel-streams **в приложении**, но всё это идёт через один TCP-сокет, и потерянный пакет блокирует все streams сразу. В мобильных сетях это особенно болезненно: высокий jitter, частые потери. И HTTP/2 на мобиле часто медленнее HTTP/1.1. QUIC решает это, **двигая транспорт в user-space над UDP**: - Мультиплексирование на уровне самого транспорта - TLS 1.3 встроен (нет отдельного handshake'а) - Connection ID вместо 4-tuple (соединение переживает смену IP) - 0-RTT resume ## Чем QUIC отличается от TCP | Признак | TCP+TLS | QUIC | |---------|---------|------| | Транспорт | в kernel | user-space (поверх UDP) | | TLS | отдельный layer (RFC 5246) | встроен (TLS 1.3 message в QUIC frame) | | Handshake до encrypted-данных | 2-3 RTT | 1 RTT (или 0-RTT для известных) | | Stream multiplexing | нет (один поток) | да, независимые streams | | HOL-blocking | да (на уровне TCP) | нет (между streams) | | Connection ID | 4-tuple (src/dst IP:port) | random ID, переживает смену IP | | Где крутится | OS (sendfile-friendly) | user-space (CPU нагрузка выше) | ## Handshake QUIC vs TCP+TLS TCP+TLS 1.3: ``` client server │ ── SYN ────────────►│ │ ◄── SYN/ACK ────────│ <- 1 RTT TCP │ ── ACK + ClientHello ─►│ │ ◄── ServerHello + Cert │ <- 2 RTT для encrypted │ ── Finished + HTTP req ►│ Total: ~2 RTT до данных ``` QUIC: ``` client server │ ── Initial(ClientHello) UDP ──►│ │ ◄── Initial(ServerHello+Cert) ─│ <- 1 RTT │ ── Handshake done + HTTP req ─►│ Total: ~1 RTT ``` С 0-RTT (resume известного сервера), данные можно слать сразу с первым пакетом. Резко меньше задержка для repeat-visit'ов. ## 0-RTT, и его опасности При повторном connect'е клиент использует session-ticket с прошлой сессии и шлёт **early data** ещё до подтверждения handshake'а. Замечательно для performance, но: - **Replay-attack**: атакующий может перехватить early-data и переслать. Если запрос меняет state (POST), реплей опасен. - **Server-side нужно ограничить**: only **idempotent**-методы (GET, HEAD), либо полный replay-protection через nonce. В nginx: ```nginx ssl_early_data on; # И блокировать non-idempotent методы: if ($request_method !~ ^(GET|HEAD)$) { return 405; } ``` ## Stream multiplexing В QUIC есть **streams**, независимые двунаправленные потоки внутри одного connection'а. Каждый stream имеет свой ID, свои retransmit'ы, свой flow control. ``` QUIC Connection ├── Stream 0 (control) ├── Stream 4 (HTTP request 1), потерял пакет, ретрансмитит ├── Stream 8 (HTTP request 2), продолжает работать └── Stream 12 (HTTP request 3), продолжает работать ``` В TCP всё это было бы заблокировано на одном пакете. В QUIC только Stream 4. ## Connection migration Connection identifier, random byte-string в QUIC header'е, не привязанный к IP. Клиент сменил Wi-Fi на 4G: - source IP меняется - source UDP port меняется - **Connection ID** остаётся тот же Сервер видит новый IP с тем же CID → молча обновляет 4-tuple, соединение продолжается. TCP бы порвался и пришлось бы переустанавливать. Полезно для: - Mobile-приложений (метро/улица) - Long-running uploads - WebRTC over QUIC ## QPACK, header compression без HOL HTTP/2 использует **HPACK**, компрессия с общим dictionary. Проблема: оба endpoint'а должны знать состояние dictionary, иначе декодирование сломается. На потерянных пакетах в HTTP/2-over-TCP это работало (TCP гарантирует order). В QUIC не работает (streams независимы). **QPACK**, переработка HPACK: dictionary в отдельном stream'е, с координацией updates. Сложнее, но позволяет header-compression без HOL. ## HTTP/3, это HTTP-семантика над QUIC HTTP/3, это «как HTTP/2 запросы и ответы упакованы в QUIC streams». Семантика та же: - Methods (GET/POST/...), URL, headers, body - Status codes (200/404/...) Отличия от HTTP/2: - QPACK вместо HPACK (header compression) - Server Push не поддерживается (snimали с roadmap) - Каждый HTTP request в своём QUIC stream Для приложения **разница минимальна**, те же `curl https://...`, тот же fetch() в браузере. Транспорт делает за вас. ## ALPN-discovery, как клиент узнаёт что сервер умеет h3 Браузер **сначала** идёт по HTTPS/TCP, при handshake получает заголовок `Alt-Svc`: ``` Alt-Svc: h3=":443"; ma=86400 ``` Это говорит «попробуй h3 на UDP/443 в следующий раз». Браузер кэширует и при следующем запросе сразу использует QUIC. Альтернатива, **HTTPS DNS RR** (RFC 9460): сервер декларирует поддержку h3 в DNS: ``` example.com. 300 IN HTTPS 1 . alpn="h3,h2" ``` ## Поддержка в стеке | Софт | Версия с h3 | Note | |------|-------------|------| | nginx | 1.25+ | полная поддержка | | Caddy | 2.6+ | default-on | | Apache | mod_http3 (експ.) | в работе | | HAProxy | 2.6+ | через quictls | | curl | 7.66+ с `--http3` | требует quiche/msh3 backend | | wget2 | 2.0+ | да | | Cloudflare/Fastly/Cloud LB | да, default | прозрачно | | Chrome / Firefox / Safari | да, по умолчанию | feature-detection | ## Включить HTTP/3 в nginx ```nginx server { listen 443 ssl; # TCP/TLS listen 443 quic reuseport; # UDP/QUIC ssl_protocols TLSv1.3; # QUIC требует TLS 1.3 ssl_certificate /etc/ssl/fullchain.pem; ssl_certificate_key /etc/ssl/privkey.pem; add_header Alt-Svc 'h3=":443"; ma=86400'; ssl_early_data on; # 0-RTT # ... остальное идентично TCP } ``` Не забыть открыть **UDP/443** в firewall'е! ```bash ufw allow 443/udp iptables -A INPUT -p udp --dport 443 -j ACCEPT ``` ## Производительность Реальные цифры (зависят от RTT и потерь): - **Low RTT, low loss** (датацентр): QUIC чуть медленнее TCP (CPU overhead user-space) - **High RTT, low loss** (CDN, edge): QUIC выигрывает за счёт меньшего handshake'а - **High RTT, high loss** (мобильная сеть): QUIC выигрывает значительно, нет HOL blocking Cloudflare замеряли: QUIC в среднем на 5-15% быстрее на page-load для мобильных, до 30% на плохих сетях. Для cable/fiber, статпогрешность. CPU: QUIC потребляет на 30-50% больше CPU чем TCP, потому что user-space (нет kernel sendfile, нет TSO/GSO до недавнего offload). В 2026 многие NIC и kernel-version'ы получают QUIC offload (UDP-segmentation offload + TLS-offload), разрыв сокращается. ## Запрет middlebox-вмешательства TCP открыт для middlebox-инспекции (заголовки в plaintext, sequence numbers видно). QUIC **зашифровано почти полностью**: только connection-ID и часть header'а в открытом виде. Middlebox не может: - Modify packets (broken) - Track state (только видит UDP-flow) - DPI на содержимое С точки зрения op-team: - **Firewall** должен пропускать UDP/443 (часто блокировано «по привычке» enterprise-проксями) - **NAT-таблицы** должны правильно ageing UDP-flow (default 30s мало для QUIC, лучше 5+ min) - **DPI/IDS** теряет visibility, security ratio shifted ## Когда что-то пошло не так - **Браузер не использует h3**, нет `Alt-Svc` header'а или DNS HTTPS RR. Curl: `curl --http3 -v https://example.com`. - **`quictls` не собирается**, nginx требует BoringSSL/quictls (форки OpenSSL с QUIC API). Стандартный OpenSSL до 3.5 не поддерживает. - **UDP/443 не работает**, firewall (90% случаев), либо load balancer не пропускает UDP. AWS ALB не поддерживает h3, нужен NLB. - **0-RTT replay-атака**, смотри предупреждения выше. Никогда не разрешай non-idempotent через 0-RTT. - **CPU нагрузка на QUIC-сервере высокая**, включить kernel UDP-GSO, TLS-offload где доступно. - **Connection migration не работает**, некоторые middlebox'ы дропают пакеты с непривычным CID. Чаще всего работает прозрачно. - **Старые клиенты не могут подключиться**, QUIC v1 ≠ draft-29. nginx 1.25+ только v1, старые curl-builds, draft-29. ## Когда выбирать QUIC - **Mobile-heavy traffic**, реальный win - **High-RTT клиенты** (другие континенты), handshake выигрывает - **Many small requests**, мультиплексирование без HOL Когда **не** выбирать или подождать: - Внутренний RPC в датацентре, TCP+TLS proven, QUIC-overhead не оправдан - Long-running streams (file upload, video), TCP неплохо справляется - Если стек/firewall/observability не готовы, добавит сложности ## Команды ```bash curl --http3 -v https://cloudflare-quic.com ``` Принудительно через HTTP/3 - проверка что клиент и сервер договорились ```bash curl -I https://example.com | grep -i alt-svc ``` Получить Alt-Svc header - анонсирует ли сервер h3 поверх h2/h1 ```bash tcpdump -ni any 'udp port 443' -w quic.pcap ``` Захватить QUIC-трафик для анализа в Wireshark - decrypt только с keys ```bash ufw allow 443/udp ``` Открыть UDP/443 для QUIC в firewall - частая забывка после конфига nginx ```bash openssl s_client -connect example.com:443 -alpn h3 ``` Проверить ALPN-negotiation для h3 на TCP - не настоящий QUIC, но проверка cert и ALPN ```bash dig +short HTTPS example.com ``` DNS HTTPS RR - современный способ декларации поддержки h3 в DNS ```bash nginx -t && nginx -s reload ``` После добавления listen quic - проверить конфиг и перезагрузить без даунтайма ## См. также - [HTTP/1.1, HTTP/2, HTTP/3](/kb/http-protocol.md) - [HTTP/2 internals - binary framing, HPACK, stream multiplexing](/kb/http2-internals.md) - [UDP - User Datagram Protocol](/kb/udp-basics.md) - [TLS handshake](/kb/tls-handshake.md) - [TLS-сертификаты - X.509, цепочка доверия, Let's Encrypt](/kb/tls-certificates.md)