Прим. переводчика: Джефф Хьюстон — главный научный сотрудник Азиатско-Тихоокеанского сетевого информационного центра (APNIC). Специализируется на исследованиях, связанных с инфраструктурой Интернета, IP-технологиями и политикой распределения адресов. В этой статье он рассказывает о преимуществах и недостатках QUIC, сравнивает его с TCP и пытается разобраться в причинах сравнительно низкой распространенности протокола.

Существует распространенное мнение, что транспортный протокол QUIC (RFC 9000) — просто очередное расширение оригинального транспортного протокола TCP (см. RFC 9293 и RFC 793). С этим мнением трудно согласиться. Я рассматриваю QUIC как значительный сдвиг в наборе транспортных возможностей, доступных приложениям при обеспечении конфиденциальности связи, целостности управления сеансами и гибкости. QUIC реализует иную коммуникационную модель, значительно более подходящую к различному поведению приложений. Кроме того, он намного быстрее TCP. Прогнозирую, что со временем QUIC заменит TCP в глобальной сети. Так что для меня QUIC — больше, чем просто «пара изменений в TCP». В этой статье я сравниваю TCP и QUIC и рассматриваю изменения, которые QUIC привнес в механизм передачи данных.

Но сначала давайте вспомним, что такое TCP.

Пара слов о TCP

TCP является воплощением сквозного принципа (доставки) в общей архитектуре Интернета. В него встроена функциональность, дополняющая базовую модель доставки дейтаграмм режимом сквозной сигнализации (для надежности), секвенированием, адаптивным управлением потоком, потоковой передачей.

TCP — двусторонний полнодуплексный протокол. Это означает, что TCP поддерживает одновременную отправку и получение данных обеими сторонами в контексте одного TCP-соединения. Вместо того чтобы навязывать состояние в сети для поддержки соединения, TCP использует синхронизированное состояние между двумя конечными точками. Устройство протокола гарантирует, что все локальные изменения состояния передаются удаленной стороне и подтверждаются ею без какого-либо участия сети.

TCP — потоковый протокол. Поток данных от отправителя будет восприниматься получателем точно в таком порядке, в каком он был сгенерирован отправителем. TCP — истинный потоковый протокол, и сетевые операции на уровне приложений непрозрачны. Другие транспортные протоколы явно инкапсулируют каждую транзакцию приложения; для каждой операции write отправителя должна быть соответствующая операция read получателя. Таким образом, сегментация потока данных и логическая структура записей сохраняется при передаче данных. TCP явно не сохраняет такую неявную структуру данных, поэтому в сетевом протоколе операции write и read явно не сопряжены. Например, TCP-приложение может последовательно записать в сетевое соединение три блока данных, которые могут быть собраны удаленным считывателем за одну операцию чтения. Приложение может пометить поток собственными границами записей, если такие границы присутствуют в данных.

Концепция срочных данных (urgent data) обеспечивает рудиментарный уровень форматирования потока в TCP. В ней отправитель может пометить конец сегмента данных, на который требуется обратить внимание получателя. Сегмент данных TCP с последним байтом сегмента срочных данных может пометить эту точку в данных, а принимающий TCP-процесс передаст эту метку принимающему приложению.

TCP-соединение идентифицируется хостами на обоих концах с помощью конечного упорядоченного списка из 5 элементов, определяющего параметры TCP/IP-соединения (далее 5-tuple). Список включает идентификатор протокола, IP-адрес источника, порт источника, IP-адрес назначения и порт назначения.

Установка TCP-соединения требует трехстороннего рукопожатия (handshake), гарантирующего, что обе стороны соединения одинаково представляют значения последовательности байтов удаленной стороны. Соединение устанавливается следующим образом: локальная система посылает удаленному хосту начальный номер последовательности на удаленный порт с помощью SYN-пакета. Удаленная система отвечает ACK’ом с номером начальной последовательности и номером начальной последовательности удаленного хоста в ответном SYN-пакете. Локальный хост отвечает ACK’ом с номером удаленной последовательности. Такие handshake-пакеты являются обычными TCP-пакетами и не несут полезную нагрузку в виде данных. В этот момент TCP переходит в режим надежного управления потоком данных.

Рисунок 1. Трехстороннее TCP-рукопожатие
Рисунок 1. Трехстороннее TCP-рукопожатие

TCP — протокол со скользящим окном. Поток данных представляет собой последовательность пронумерованных байтов. Отправитель сохраняет копию всех отправленных, но еще не полученных данных в локальном буфере отправки. Приняв сегмент данных, начальный порядковый номер которого является следующим ожидаемым сегментом данных, получатель отправляет подтверждение (ACK) обратно отправителю с порядковым номером конца полученного сегмента данных. Это позволяет отправителю отбросить в локальном буфере отправки все данные, чей порядковый номер меньше, чем номер, полученный в подтверждении (ACK), и сдвинуть окно отправки. Если полученные данные не соответствуют очередности, отправителю отправляется подтверждение (ACK) с порядковым номером последних полученных данных. Кроме того, сообщение ACK включает доступный размер буфера приемника (окно приема). Объем неподтвержденных данных не должен превышать размер окна получателя. Отправитель должен постоянно следить за тем, чтобы объем неподтвержденных данных, находящихся в сети, был меньше объявленного размера окна приема и общей емкости локального буфера отправки.

TCP — протокол управления потоком с ACK-clocking’ом: в статическом режиме работы без потерь каждый полученный пакет ACK указывает на то, что определенный объем данных был получен (и, следовательно, удален из сети). Вместе с этим анонсируется окно приема, которое позволяет отправителю ввести в сеть тот же объем данных, который был удален получателем. Другими словами, частота, с которой посылаются пакеты, зависит от того, как часто отправитель получает ACK’и.

Однако TCP не обязан знать о доступной пропускной способности сетевого пути. За это отвечает алгоритм управления, реализованный на передающем конце. Тот пытается установить динамическое равновесие между объемом данных, передаваемых в рамках данного сеанса TCP, и параллельными сеансами TCP, которые задействуют те же сегменты пути. Управление потоком не прописано в спецификации TCP, поэтому в настоящее время используется несколько алгоритмов. Основная их часть использует индуцированную нестабильность TCP, медленно «раздувая» окно отправки для каждого полученного ACK’а и резко сокращая его в ответ на потерю пакета (3 дублирующих ACK’а). Увеличение частоты отправки прекратится после заполнения буфера отправки (другими словами, отправителю больше некуда поместить данные, поэтому он вынужден ждать ответный ACK, прежде чем возобновить передачу). Отправка также прекратится, когда сеть более не сможет принимать данные из-за заполнения буферов сети (в этом случае попытка отправить новые данные приведет к потере пакетов; отправитель об этом узнает из дублирующих ACK'ов).

У такого процесса имеется ряд следствий, влияющих на качество сервиса. Во-первых, TCP ведет себя адаптивно, а не предиктивно. Алгоритмы управления потоком предназначены для увеличения скорости потока данных и эффективного использования доступной пропускной способности сети. Однако они резко снижают объемы передаваемых данных при изменении доступной пропускной способности из-за перегрузки сети или при динамических изменениях на сетевом пути, уменьшающих пропускную способность. Во-вторых, в пустой сети поток TCP стремится заполнить сетевой тракт данными, оптимизируя частоту отправки (пока буфер отправки больше, чем пропускная способность сетевого потока). Если еще один TCP-поток задействует тот же путь, их алгоритмы управления будут взаимодействовать таким образом, что оба потока постепенно стабилизируются и на каждый придется примерно половина доступной пропускной способности сети. В общем смысле TCP пытается вести себя справедливо, стараясь равномерно распределять сетевые ресурсы между всеми активными потоками. Налицо явное противоречие между эффективностью использования сети и предсказуемостью производительности сеанса. В случае TCP пропускная способность не всегда предсказуема, зато сеть используется эффективно и по-максимуму.

TCP и TLS

Работа с TLS ведется на отдельном уровне. После завершения TCP-рукопожатия стороны переходят к фазе согласования TLS, в рамках которой договариваются об аутентификации и создают сеансовый ключ для шифрования данных сессии.

TLS начинается с обмена авторизациями. В версии TLS 1.3 (актуальная версия этого протокола) клиент посылает приветствие (client hello) с поддерживаемой версией TLS, наборами алгоритмов шифрования, именем сервиса и строкой случайных байтов («рандом клиента»). Сервер отвечает своим приветствием (server hello). Оно содержит сертификат открытого ключа, рандом сервера, выбранный набор алгоритмов шифрования и цифровую подпись приветствий. Оба адресата теперь знают рандомы друг друга и выбранный набор алгоритмов шифрования, поэтому могут сгенерировать мастер-секрет для шифрования сеанса. Клиент отправляет fin, сообщая, что защищенный симметричный сеансовый ключ готов к использованию (см. Рисунок 2).

Более ранние версии TLS использовали дополнительные пакеты в обмене приветствиями, что увеличивало время TLS-рукопожатия.

QUIC

Теперь пора поговорить о QUIC. Транспортный протокол QUIC был разработан для решения ряда проблем, присущих TCP и TLS. В частности, он оптимизирует работу с шифрованным трафиком, позволяя быстрее настраивать сеанс. Кроме того, QUIC способствует эволюции транспортных механизмов и позволяет избежать «окостенения» протокола TCP.

Это весьма грубое упрощение, но на низшем уровне QUIC — обычный TCP, инкапсулированный и зашифрованный в UDP. Для внешней сети QUIC выглядит как двунаправленная последовательность пакетов UDP с полезной нагрузкой. С точки зрения эндпоинтов QUIC вполне подходит на роль надежного полнодуплексного протокола передачи данных. Но даже на этом уровне у QUIC есть ряд преимуществ перед TCP.

Первое заключается в намеренном сокрытии от сети параметров управления транспортным потоком. Практика развертывания промежуточного сетевого ПО, которое переписывает значения управления TCP-потоком, влияя на поведение приложения, не особо популярна на прикладном уровне. Сокрытие параметров управления потоком от сети, безусловно, делает ее невозможной.

Второе переносит ответственность за работу транспортного протокола с платформы на приложение. Между приложениями и платформой давно становились непростые отношения. Изменения в TCP на уровне ядра проводятся через обновление ПО платформы, и приложениям зачастую приходится ждать, пока нововведения дойдут до платформы, чтобы воспользоваться их преимуществами. Например, чтобы задействовать алгоритм управления потоком TCP BBR, приложению придется ждать, когда тот будет интегрирован в платформу. Используя базовый UDP-интерфейс для транспортных сервисов платформы, контроль потока и шифрование при желании можно перенести в само приложение. Миграция транспортного кода из ядра в пространство пользователя может несколько снизить скорость работы, зато приложение получит полный контроль над транспортным сервисом и сможет работать в режиме, который не только не зависит от платформы, но и скрыт от нее. В итоге среда приложений получит более высокий уровень контроля и гибкости.

Однако QUIC — гораздо больше, чем простая UDP-обертка поверх TCP. Давайте рассмотрим этот протокол подробнее.

Соединения QUIC

Соединение QUIC — состояние, общее для клиента и сервера. QUIC использует комбинацию двух чисел (по одному, выбранному каждой стороной) для формирования пары идентификаторов соединения. Они выполняют роль постоянных ID для сессии QUIC.

Основная функция идентификатора соединения — гарантировать, что изменения в адресации на нижних уровнях протокола (адреса источника IP и номера портов источника UDP) не приведут к тому, что пакеты QUIC-соединения будут отброшены при изменении внешнего IP-адреса конечной точки. Каждый эндпоинт выбирает идентификатор соединения, используя специфический для реализации (и, возможно, для развертывания) метод, который позволяет привязывать полученные пакеты с этим ID к соответствующему соединению QUIC.

Получив пакет с тем же идентификатором соединения и другим IP-адресом или UDP-портом, эндпоинт проверяет принадлежность нового адреса пиру, посылая на новый адрес фрейм, содержащий случайные данные, и ожидая эхо-ответа с теми же данными. Такой обмен вызовами и ответами осуществляется в рамках установленного криптографического состояния, что существенно осложняет перехват сессии. После проверки нового адреса обе конечные точки продолжают обмен данными.

Такой механизм особенно удобен при согласовании различных форм поведения трансляции сетевых адресов (NAT). NAT имеют доступ к информации о транспортном слое. В TCP они будут пытаться поддерживать состояние трансляции до тех пор, пока не получат флаг FIN, завершающий обмен. UDP не предоставляет таких внешних подсказок о завершении сеанса, и NAT склонны интерпретировать период молчания как сигнал к разрыву состояния трансляции. В этом случае очередной исходящий пакет может получить от NAT новый адрес источника и/или номер порта источника UDP. Он также пригодится для возобновления сеанса, если соединение простаивало в течение длительного времени и привязка NAT истекла по таймауту. В TCP изменение любого из четырех полей адреса и порта в 5-tuple соединения приведет к тому, что пакет будет исключен из TCP-сессии. В QUIC дело обстоит иначе: постоянный идентификатор соединения позволяет получателю связать новый адрес отправителя с существующим соединением.

Подобная гибкость QUIC в отношении адресов очень помогает при изменениях на уровне сети. Например, устройство может переключаться между WiFi и сотовой связью, при этом активная транспортная сессия QUIC будет сохраняться.

Рукопожатие при установке QUIC-соединения 

В протоколе QUIC соединение начинается с рукопожатия, в котором сразу определяются параметры коммуникации и криптографическая составляющая QUIC-TLS. Трехстороннее рукопожатие TCP и трехстороннее рукопожатие TLS 1.3 объединяются в один трехпакетный обмен (Рисунок 2). В результате экономится время, затрачиваемое на полный круг (Round Trip Time, RTT). Для коротких сессий это позволяет значительно повысить производительность.

Рисунок 2. Сравнение рукопожатий TCP/TLS и QUIC
Рисунок 2. Сравнение рукопожатий TCP/TLS и QUIC

Нулевое рукопожатие (0-RTT) в QUIC также позволяет клиенту отправлять на сервер зашифрованные полезные данные уже в первом пакете, используя согласованные параметры предыдущего соединения и идентификатор TLS 1.3 с ранее выданным сервером ключом (PSK), хотя подобный 0-RTT-обмен потенциально уязвим для атак повторного воспроизведения.

Пакеты и фреймы

В протоколе QUIC по соединению передаются пакеты. У каждого пакета есть индивидуальный 62-битный номер. Повторная передача пронумерованного пакета не предусмотрена. В случае необходимости данные передаются в новом пакете со следующим по порядку номером. Таким образом, существует четкое различие между приемом оригинального пакета и повторной передачей данных.

В одну дейтаграмму UDP можно поместить сразу несколько пакетов QUIC. Дейтаграммы UDP QUIC не должны фрагментироваться на IP-уровне, и если path MTU discovery не производится, QUIC предполагает, что сетевой путь поддерживает полезную нагрузку UDP размером 1200 байт.

Клиент QUIC увеличивает дейтаграммы UDP, несущие начальные пакеты, по крайней мере, до наименьшего допустимого максимального размера дейтаграммы в 1200 байт, добавляя к начальному пакету фреймы-прокладки или объединяя набор начальных пакетов. Полезная нагрузка всех дейтаграмм UDP, несущих пакеты Initial, содержащие сигнал ACK, заполняется до наименьшего допустимого максимального размера дейтаграммы в 1200 байт. Отправка UDP-дейтаграмм такого размера гарантирует, что сетевой путь поддерживает приемлемый Path Maximum Transmission Unit (PMTU) в обоих направлениях. Кроме того, подобное увеличение начальных пакетов позволяет снизить «выгодность» атак усиления (amplification attacks) для атакующей стороны, связанную с ответами сервера на непроверенный адрес клиента.

Пакеты QUIC шифруются по отдельности — для расшифровки не нужно ждать, пока пакеты полностью придут. В TCP это невозможно, поскольку записи шифрования находятся в потоке байтов, а стек протокола не знает о границах верхнего уровня в этом потоке. Дополнительным следствием шифрования каждого пакета является запрет на фрагментацию IP-пакетов QUIC. Реализации QUIC обычно консервативно подходят к выбору максимального размера пакета, минимизируя риск их фрагментации.

Получатель QUIC отправляет ACK с наибольшим полученным номером пакета, а также список всех полученных смежных блоков пакетов с меньшим номером, если в полученной последовательности пакетов есть пробелы. Поскольку в QUIC используются специально определенные фреймы ACK, можно закодировать до 256 таких диапазонов номеров в одном фрейме, в то время как TCP SACK ограничен тремя такими диапазонами номеров. Таким образом, QUIC обеспечивает более подробное представление о потере и переупорядочивании пакетов, повышая устойчивость к потерям пакетов и эффективность их восстановления. Потерянные пакеты повторно не передаются. Восстановление данных выполняется в контексте каждого потока QUIC.

Потоки QUIC

Соединение QUIC далее разбивается на потоки. Каждый поток предоставляет приложению абстракцию упорядоченного потока байтов, аналогичную по своей природе потоку байтов TCP. Соединение в QUIC позволяет использовать произвольное количество параллельных потоков. Приложения могут устанавливать относительный приоритет потоков.

Поскольку соединение уже выполнило end-to-end-сопряжение и установило контекст шифрования, потоки создаются с минимальной затратой ресурсов. Один фрейм потока может открывать, передавать данные и закрываться в пределах одного пакета, или поток может существовать в течение всего времени существования соединения.

Для сравнения, TCP-сессию можно мультиплексировать на потоки, но все они подчиняются единому состоянию управления потоком. То есть если в TCP получатель сообщает отправителю об окне нулевого размера, все мультиплексированные потоки блокируются.

Каждый поток QUIC идентифицируется уникальным идентификатором, два младших бита которого используются для определения того, какой эндпоинт инициировал поток и двунаправленности или однонаправленности потока. Поток байтов сегментируется на фреймы данных, а смещение фрейма потока эквивалентно номеру последовательности TCP, который используется для упорядочивания доставки фреймов данных, обнаружения потерь и повторной передачи.

Эндпоинты QUIC могут распределять канал между различными потоками и устанавливать приоритет передачи фреймов различных потоков на основе информации от приложения. Это обеспечивает эффективное восстановление потерь, контроль загруженности и выполнение операций по управлению потоком, способных существенно повлиять на производительность приложения.

Дейтаграммы QUIC

В дополнение к надежным (reliable) потокам, QUIC также поддерживает ненадежный (unreliable), но защищенный сервис доставки данных с помощью фреймов DATAGRAM, которые не передаются повторно при потерях. Когда приложение отправляет дейтаграмму через QUIC-соединение, QUIC генерирует фрейм DATAGRAM и отправляет его в первом доступном пакете. Когда эндпоинт QUICK получает корректный фрейм DATAGRAM, ожидается, что данные будут немедленно переданы приложению. Эти фреймы DATAGRAM не связаны ни с каким потоком.

Если полученный пакет содержит только фреймы DATAGRAM, то фрейм ACK можно отложить, так как отправитель в любом случае не будет повторно передавать фрейм при отсутствии ACK. Это ненадежный сервис дейтаграмм. Если отправитель обнаруживает, что пакет, содержащий определенный фрейм DATAGRAM, мог быть потерян, реализация протокола может уведомить приложение, что, по ее мнению, дейтаграмма была потеряна. Аналогичным образом, если пакет, содержащий фрейм DATAGRAM, подтверждается, реализация может уведомить приложение-отправитель о том, что дейтаграмма была успешно передана и получена.

Фреймы QUIC

Каждый пакет содержит последовательность фреймов. У фреймов есть поле типа и данные, зависящие от типа. Стандарт QUIC определяет 20 различных типов фреймов. Как и флаги TCP, они передают информацию о состоянии потоков и самого соединения.

Типы фреймов включают в себя padding, ping (или keepalive), фреймы ACK для номеров принятых пакетов, которые сами содержат счетчики ECN и диапазоны ACK, а также фреймы потоковых данных и фреймы дейтаграмм.

Общая схема соединений, потоков и фреймов QUIC показана на Рисунке 3.

Рисунок 3. Логическая организация QUIC
Рисунок 3. Логическая организация QUIC

Восстановление и управление потоком в QUIC

Пакеты QUIC содержат один или несколько фреймов. QUIC обнаруживает потери на основе этих пакетов, а не отдельных фреймов. Для каждого подтвержденного пакета все фреймы, содержащиеся в этом пакете, считаются полученными. Пакет считается потерянным, если он не подтвержден, в то время как пакет, отправленный позже, был подтвержден. Кроме того, пакет считается потерянным при превышении соответствующего порога ожидания.

QUIC использует два пороговых значения для определения потери. Первый — порог переупорядочивания пакетов (packet reordering threshold) t. После подтверждения пакета x все неподтвержденные пакеты с номером меньше x – t считаются потерянными. Второй связан с измеренной QUIC длительностью полного круга (RTT). Время ожидания (waiting time) w вычисляется применением к RTT некоторого весового коэффициента. Предположим, что время последнего подтверждения равно t. Тогда все неподтвержденные пакеты, отправленные до момента t – w, будут считаться потерянными.

Для восстановления все фреймы в потерянных пакетах (если связанный с ними поток предусматривает повторную передачу) будут помещены в новые пакеты и отправлены получателю. Сам потерянный пакет повторно не передается.

Как и в случае с приемным окном в TCP, QUIC содержит механизм, позволяющий получателю контролировать максимальный объем данных, который отправитель может послать в отдельном потоке, а также лимитировать объем данных во всех потоках. Кроме того, как и в TCP, алгоритм управления для надежных потоков не определен в QUIC, хотя один такой контроллер загруженноси на стороне отправителя описан в RFC 9002. Он подобен алгоритму New Reno в TCP.

Проблемы QUIC

Работа с RPC

IP-хосты обычно поддерживают только два транспортных протокола — UDP и TCP. UDP — простой протокол доставки дейтаграмм. Доставка данных, инкапсулированных с помощью UDP, не гарантируется. TCP, как говорилось выше, «заточен» под надежность. Протокол TCP устраняет потери пакетов или изменение последовательности доставленных пакетов.

Существует и другая модель, а именно модель удаленного вызова процедур (RPC). Она эмулирует вызовы процедур. Вместо потока байтов в TCP или дейтаграмм в UDP, RPC обеспечивает надежную доставку запросов и ответов, при этом ответ однозначно связан с запросом. Пожалуй, самым известным на сегодня фреймворком RPC является gRPC. Он основан на HTTP/2, и поэтому подвержен так называемой блокировке head-of-line («затор в очереди») на транспортном уровне, когда более поздние запросы вынуждены дожидаться завершения более ранних.

Проблема здесь в том, что надежный байтовый поток не является подходящей абстракцией для RPC, поскольку ядром RPC является парадигма запроса/ответа, которая больше соответствует надежному обмену сообщениями (со всеми вытекающими отсюда последствиями). Эффективный фреймворк RPC должен обрабатывать потерянные, неправильно упорядоченные и дублированные сообщения, а пространство идентификаторов должно связывать запросы с ответами. Базовый транспорт сообщений должен обрабатывать сообщения произвольного размера, то есть уметь разбивать их на пакеты внутри транспорта.

Фреймворк двунаправленных потоков вполне соответствует модели коммуникаций RPC. В нем каждый RPC может быть сопоставлен с отдельным потоком. Поток надежен и последователен. Фрейминг данных не предусмотрен в QUIC, и добавление структуры записи в поток RPC (если это необходимо) — задача приложения. Затраты на вызов невелики, поскольку зашифрованное end-to-end-соединение уже установлено.

Конечно, кажется, что HTTPS ведет себя больше как RPC, нежели как надежный поток байтов. От этого могут выиграть приложения, работающие через HTTP(S), такие как gRPC, и интерфейсы RESTful API.

Балансировка нагрузки

В современном мире управления масштабами балансировщик нагрузки зачастую размещается на нескольких серверах. В мире TCP балансировщик нагрузки обычно относит пакеты к одной TCP-сессии с помощью значения 5-tuple: протокола, IP-адресов и портов, предполагая что оно не меняется на протяжении всей TCP-сессии.

В случае с QUIC таких гарантий нет. Балансировка нагрузки на основании 5-tuple может сработать, однако если клиент находится за NAT, который производит «агрессивную» перепривязку, то любой такой подход к балансировке нагрузки провалится. Причина в том, что UDP не предоставляет сигналы сессии NAT, поэтому никто априори не может гарантировать, что привязка NAT (и представленные адрес и порт источника) не изменятся в течение всей сессии QUIC. Теоретически IPv6 может задействовать Flow-ID для передачи прокси-поля, постоянного для потока, но Flow-ID ограничен по размеру и не гарантирует уникальности. Кроме того, сетевая IPv6-инфраструктура и конечные хосты относятся к нему весьма неоднозначно.

Эта тема затрагивает основной постулат современной инфраструктуры высокопроизводительных серверов в открытом Интернете. Потоки данных используют TCP, а DNS — UDP. Передача постоянных потоков данных большого объема по UDP может не подойти для внутренних оптимизаций, используемых в серверных сетях доставки контента.

Защита от DDOS-атак

Следующая проблема — подверженность DOS-атакам. Злоумышленник может отправить большой объем пакетов, вынуждая сервер тратить время на их расшифровку. Чтобы успешно применить этот метод для TLS/TCP, нужно угадать номер последовательности TCP и размер окна. Только в этом случае пакет будет принят и передан декодеру TLS. QUIC же предварительно не фильтрует пакеты перед вызовом декодера.

С другой стороны, шифрование сеанса задействует симметричные криптоалгоритмы. При декодировании они меньше нагружают получателя, нежели асимметричные. Достаточно ли этого различия, чтобы строить крупномасштабные QUIC-платформы, устойчивые к DOS? Сомневаюсь, что есть однозначный ответ. Пожалуй, это больше вопрос разработки более полного фреймворка шифрования, который сам по себе крайне необходим в публичном Интернете.

Приватный QUIC

Способен ли QUIC задействовать «нулевой» алгоритм шифрования TLS в приватных случаях?

Мир не ограничивается публичным Интернетом. Встает вопрос: так ли уж необходимо шифрование/дешифрование пакетов в окружениях, которые изначально приватны? Хотя QUIC имеет ряд преимуществ при реализации сложных стратегий в датацентрах (его многопотоковые возможности), издержки, связанные с шифрованием, могут оказаться слишком высокими.

Конечно, ничто не мешает выпустить версию протокола с нулевым алгоритмом шифрования. Увы, она сможет общаться только с другими реализациями самой себя. Строго говоря, если убрать шифрование, это уже не будет QUIC. Такой протокол не сможет работать с QUIC.

QUIC и OpenSSL

Если у QUIC столь явные преимущества перед TCP, почему он до сих пор не используется повсеместно? Статистика показывает, что QUIC используется примерно в 30% веб-сессий (см., например, отчет Cloudflare's Radar). Однако если измерить долю браузеров, поддерживающих QUIC, показатель совместимости подскочит до 60%.

Есть несколько причин, по которым реальное использование QUIC так отстает. Первая заключается в том, что в браузере Chrome решение о переходе на QUIC до сих пор принимается на уровне контента. То есть клиент сначала приходит на сайт по HTTP/2 (TCP/TLS), узнает, поддерживает ли сервер QUIC, а уже при втором посещении может переключиться на QUIC. Однако все не так просто, поскольку соединения HTTP/2 персистентны. Если второй визит достаточно близок по времени к первому, сессия HTTP/2 останется открытой; соответственно, все взаимодействие пойдет через нее. Браузер Safari переключается на QUIC сразу, поскольку умеет работать с записями SVCB в DNS. Увы, доля рынка Safari относительно мала по сравнению с QUIC.

Вторая причина кроется в окружении веб-сервера. Многие серверы используют библиотеку OpenSSL TLS. На ноябрь 2022 года OpenSSL не включает поддержку QUIC. QUIC поддерживается BoringSSL, но, как известно, BoringSSL — это форк OpenSSL, разработанный для удовлетворения потребностей Google. Остальным он может не подойти. Google не рекомендует сторонним компаниям полагаться на BoringSSL. Еще есть QuicTLS — форк OpenSSL, поддерживаемый Akamai и Google. Подобная раздробленность в среде OpenSSL не особо помогает процессу. Итог закономерен: разработчики многих серверных окружений ждут, когда библиотека QUIC станет частью OpenSSL. Работа над OpenSSL 3.0.0 сначала несколько затормозила весь процесс, а затем разработчики OpenSSL подлили масла в огонь, объявив о намерении выпустить полнофункциональную реализацию QUIC. Разработка нового стека протоколов QUIC способна отбросить поддержку QUIC в OpenSSL на месяцы, а то и на годы. Это обстоятельство может стать основным препятствием для глобального развертывания QUIC под видом HTTP/3 в Интернете.

Заключение

Из всей этой затеи с QUIC можно сделать несколько выводов.

Любая полноценная коммуникационная среда должна обеспечивать конфиденциальность и целостность сообщений, которые по ней передаются. Прошло время, когда открытые протоколы представляли собой приемлемый компромисс между эффективностью, скоростью и конфиденциальностью. В наши дни все сетевые транзакции в публичном Интернете должны шифроваться. Модель QUIC, которая упаковывает набор транзакций между клиентом и сервером в единое шифруемое состояние, — вполне рациональное техническое решение.

Шифрование больше не роскошь, а обязательный компонент всех публичных Интернет-транзакций. При этом в идеале шифрование в сетевых транзакциях не должно сказываться на производительности и скорости.

Сетевые транзакции бывают разных видов. UDP и TCP в некотором смысле представляют собой два полюса относительно широкого спектра. Первый изначально оказался слишком восприимчив к злоупотреблениям, поэтому все перешли на TCP. Проблема в том, что TCP был разработан как эффективный однопотоковый протокол, и прикрутить к нему множество сеансов, короткие транзакции, глобальное управление загруженностью и совместное шифрование оказалось чрезвычайно сложно.

Сейчас в экосистеме Интернета доминируют приложения, в то время как платформы и сети превращаются в предмет потребления. Появляется новая модель, в которой приложение имеет собственный транспортный сервис. Это не просто модель клиент/сервер, типичная для HTTP, — она была расширена до привязанного к конкретным приложениям разрешения имен с помощью DNS по HTTPS. Вполне вероятно, что такая тенденция сохранится и в будущем.

Если взглянуть еще шире, успех Интернета объясняется переносом ответственности за предоставление услуг с сети на конечную систему. Это позволило повысить эффективность использования общей сетевой основы и перенести издержки, связанные с пакетизацией сетевых транзакций, на конечные системы. Роль инноваторов перешла от крупных и громоздких операторов связи к динамичному миру платформенного ПО. Успех компании Microsoft с ее ОС Windows отнюдь не был случайностью. В этом смысле QUIC делает еще один шаг вперед и переносит роль инноватора с платформ на приложения как раз тогда, когда значимость платформ в экосистеме снижается. То есть появление ориентированной на приложения и более быстрой транспортной модели, включающей в себя комплексное шифрование, было неизбежно.

P.S.

Читайте также в нашем блоге:

Комментарии (4)


  1. Busla
    25.04.2023 08:06
    +2

    Установка TCP-соединения требует трехстороннего рукопожатия (handshake)

    рукопожатие трехэтапное, а сторон всего две: Client и Server


    1. WQS100
      25.04.2023 08:06
      +5

      а сторон всего две: Client и Server

      и товарищ майор /s


      1. Surrogate
        25.04.2023 08:06

        Это что пока Товарищ Майор не разрешит соединение не установится?


        1. vanxant
          25.04.2023 08:06

          В случае с quic в РФ всё так. Есть короткий белый список тех, кому можно - например vk. Всё остально режется тспу