По пути к QUIC: что лежит в основе HTTP/3

https://blog.cloudflare.com/the-road-to-quic/
  • Перевод
Новая веха Интернет-истории начинается на наших глазах: можно считать, что HTTP/3 уже объявлен. В конце октября Mark Nottingham из IETF предложил уже определиться с названием для нового протокола, надо которым IETF корпит с 2015 года. Так вместо QUIC-подобных названий появилось громкое HTTP/3. Западные издания уже писали об этом и даже не один раз. История QUIC началась в недрах Корпорации добра в 2012 году, с тех пор только серверы Google поддерживали HTTP-over-QUIC соединения, однако время идет и вот уже Facebook начал внедрять эту технологию (7 ноября, Facebook и LiteSpeed осуществили первое взаимодействие по HTTP/3); на данный момент доля сайтов, поддерживающих QUIC – 1,2%. Наконец, рабочая группа WebRTC тоже смотрит в сторону QUIC (плюс см. QUIC API), так что в обозримом будущем реалтайм видео/аудио будет ходить по QUIC вместо RTP/RTCP. Поэтому мы решили, что будет здорово раскрыть подробности IETF QUIC: специально для Хабра мы подготовили перевод лонгрида, расставляющего точки над i. Enjoy!

QUIC (Quick UDP Internet Connections) – это новый, шифрованный по умолчанию, протокол транспортного уровня, который имеет множество улучшений HTTP: как для ускорения трафика, так и для повышения уровня безопасности. Также QUIC имеет долгосрочную цель – в итоге заменить TCP и TLS. В этой статье мы рассмотрим как ключевые фишки QUIC и почему веб выиграет за счет них, так и проблемы поддержки этого абсолютно нового протокола.

По факту существует два протокола с таким именем: Google QUIC (gQUIC), изначальный протокол, который разработали инженеры Google несколько лет назад, который после ряда экспериментов был принят IETF (Internet Engineering Task Force) в целях стандартизации.

IETF QUIC (далее – просто QUIC) уже имеет настолько сильные расхождения с gQUIC, что может считаться отдельным протоколом. От формата пакетов до хендшейка и мэппинга HTTP – QUIC улучшил оригинальную архитектуру gQUIC благодаря сотрудничеству со многим организациями и разработчиками, которые преследуют единую цель: сделать Интернет быстрее и безопаснее.

Итак, какие улучшения предлагает QUIC?

Встроенная безопасность (и производительность)


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

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

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


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

Шифрование также может быть эффективно против «косности» – феномена, которые не дает использовать гибкость протокола на практике из-за неверных предположений в реализациях (ossification – то, что из-за чего долго откладывали выкладку TLS 1.3. Выложили только после нескольких изменений, которые предотвратят нежелательные блоки для новых ревизий TLS).

Блокировка начала очереди (Head-of-line blocking)


Одним из главных улучшений, которое нам принес HTTP/2, это возможность объединять разные HTTP-запросы в одном TCP-соединении. Это позволяет приложениям на HTTP/2 параллельно обрабатывать запросы и лучше использовать сетевой канал.

Конечно, это было значительным шагом вперед. Потому что ранее приложениям нужно было инициировать множество TCP+TLS соединений, если они хотели одновременно обрабатывать несколько HTTP-запросов (например, когда браузеру нужно получить и CSS, и JavaScript чтобы отрисовать страницу). Создание новых соединений требует множественных хендшейков, а также инициализацию окна перегрузки: это означает замедление рендеринга страницы. Объединенные HTTP-запросы позволяют избежать этого.



Однако здесь есть недостаток: так как множественные запросы/ответы передаются по тому же TCP-соединению, они все одинаковы зависимы от потери пакетов, даже если потерянные данные касаются лишь одного из запросов. Это и называется «блокировкой начала очереди».

QUIC идет глубже и дает первоклассную поддержку для объединения запросов, например, разные HTTP-запросы могут быть расценены как разные транспортные QUIC-запросы, но при этом все они будут использовать одно и то же соединение QUIC – то есть дополнительные хендшейки не нужны, есть единое состояние перегрузки, запросы QUIC доставляются независимо – в итоге, в большинстве случаев потеря пакетов затрагивает только один запрос.

Таким образом можно существенно сократить время на, например, полный рендеринг веб-страницы (CSS, JavaScript, картинки и прочие ресурсы), особенно в случае перегруженной сети с высокой потерей пакетов.

Так просто, да?


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

QUIC спроектирован, чтобы доставляться поверх UDP-датаграмм, дабы облегчить разработку и избежать проблем с сетевыми устройствами, которые отбрасывают пакеты неизвестных протоколов (потому что большинство устройств поддерживают UDP). Также это позволяет QUIC жить в user-space, поэтому, например, браузеры смогут внедрять новые фишки протокола и доносить их до конечных пользователей, не дожидаясь обновлений ОС.

Тем не менее, благая цель – уменьшить сетевые проблемы – делает более трудным защиту пакетов и их правильный роутинг.

Один NAT чтобы всех воедино созвать и единою черною волей сковать


Обычно NAT-роутеры работают с TCP-соединениями, используя кортеж из 4 значений (исходные IP и порт плюс IP и порт назначения), а также отслеживая TCP SYN, ACK и FIN-пакеты, переданные по сети; роутеры могут определять, когда установилось новое соединение и когда закончилось. Поэтому возможно точное управление привязками NAT (связи между внутренними и внешними IP и портами).

В случае QUIC это пока невозможно, т.к. современные NAT-роутеры еще не знают про QUIC, поэтому они обычно делают даунгрейд к дефолтной и менее точной обработке UDP, что означает таймауты произвольной (иногда малой) длительности, которые могут влиять на длительные соединения.

Когда происходит перепривязка (например, из-за таймаута), устройство вне периметра NAT начинает получать пакеты из другого источника, из-за чего невозможно поддерживать соединение используя только лишь кортеж из 4 значений.


И дело не только в NAT! Одна из фишек QUIC называется connection migration и позволяет устройствам по их усмотрению переносить соединения на другие IP-адреса/пути. Например, мобильный клиент сможет перенести QUIC-соединение с мобильной сети на уже известную WiFi-сеть (пользователь зашел в любимую кофейню и т.п.).

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

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



Чтобы избежать этого, операторам может понадобиться внедрить более умный балансировщик на 4 уровне. Этого можно добиться программно, не затрагивая сами пограничные роутеры (для примера см. проект Katran от Facebook).

QPACK


Другой полезной особенностью HTTP/2 было сжатие заголовков (HPACK), которое позволяет конечным устройствам уменьшать размер пересылаемых данных за счет отбрасывания ненужного в запросах и ответах.

В частности, помимо прочих техник, HPACK использует динамические таблицы с заголовками, которые уже были отправлены/получены от прошлых HTTP-запросов/ответов, что позволяет устройствам ссылаться в новых запросах/ответах на ранее встречавшиеся заголовки (вместо того, чтобы снова их передавать).

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

В случае HTTP/2 поверх TCP эта синхронизация прозрачна, потому что транспортный уровень (TCP) обеспечивает доставку запросов/ответов в том же порядке, в каком они они были отправлены. То есть отправить декодеру инструкции по обновлению таблиц можно в простом запросе/ответе. Но в случае QUIC все намного сложнее.

QUIC может доставлять множественные HTTP-запросы/ответы по разным направлениям одновременно, что означает, что QUIC гарантирует порядок доставки в рамках одного направления, при это такой гарантии нет в случае множества направлений.

Например, если клиент отправляет HTTP-запрос А в QUIC-потоке А, а также запрос B в потоке B, то из-за перестановки пакетов или сетевых потерь, сервер получит запрос B до запроса А. И если запрос B был закодирован так, как было указано в заголовке запросе А, то сервер просто не сможет декодировать запрос B, так как он еще не видел запрос А.

В протоколе gQUIC эту проблему решили, просто сделав все заголовки (но не тела) HTTP-запросов/ответов последовательными в рамках одного gQUIC-потока. Это дало гарантию, что все заголовки придут в нужном порядке, что бы ни случилось. Это весьма простая схема, с ее помощью существующие решения могут продолжать использовать код, заточенный под HTTP/2; с другой стороны, это увеличивает вероятность блокировки начала очереди, снижать которую как раз и призван QUIC. Поэтому рабочая группа по QUIC из IETF разработала новый маппинг между HTTP и QUIC (HTTP/QUIC), а также новый принцип сжатия заголовков – QPACK.

В последнем драфте спецификаций HTTP/QUIC и QPACK каждый обмен HTTP-запросом/ответом использует свой собственный двунаправленный поток QUIC, так что блокировка начала очереди не возникает. Также, ради поддержки QPACK, каждый участник создает два дополнительных, однонаправленных потока QUIC, один для отправки обновлений таблиц, другой – для подтверждения их получения. Таким образом, кодер QPACK может использовать ссылку на динамическую таблицу только после того, как ее получение подтвердил декодер.

Преломляя отражение


Общая проблема основанных на UDP протоколов – их восприимчивость к атакам отражения, когда атакующий заставляет некий сервер отправлять огромное количество данных жертве. Атакующий подменяет свой IP, чтобы сервер думал, что запрос данных приходил с адреса жертвы.


Эта разновидность атаки может быть очень эффективна, когда ответ сервер несравнимо больше, чем запрос. В таком случае говорят об «усилении».

TCP обычно не используется для таких атак, потому что пакеты в изначальном хендшейке (SYN, SYN+ACK, …) имеют одинаковую длину, поэтому у них нет потенциала для «усиления».

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

Протокол QUIC также определяет явный механизм верификации источника: сервер, вместо того чтобы отдавать большой ответ, отправляет только retry-пакет с уникальным токеном, который клиент затем отправит серверу в новом пакете. Так у сервера есть бОльшая уверенность, что у клиента не подменный IP-адрес и можно завершить хендшейк. Минус решения – увеличивается время хендшейка, вместо одного прохода уже потребуется два.

Альтернативное решение заключается в уменьшении ответа сервера до размера, при котором атака отражения становится менее эффективной – например, с помощью сертификатов ECDSA (обычно они значительно меньше, чем RSA). Мы также экспериментировали с механизмом сжатия TLS-сертификатов, используя off-the-shelf алгоритмы сжатия вроде zlib и brotli; это фича, которая впервые появилась в gQUIC, но сейчас не поддерживается в TLS.

Производительность UDP


Одна из постоянных проблем QUIC – это ныне существующие железо и софт, которые не способны работать с QUIC. Мы уже рассматривали, как QUIC пытается справляться с сетевыми миддлбоксами вроде роутеров, однако другая потенциально проблемная зона это производительность отправки/получения данных между QUIC-устройствами по UDP. Долгие годы прикладывались усилия, чтобы оптимизировать реализации TCP насколько это возможно, включая встроенные возможности по разгрузке в софте (например, операционные системы) и в железе (сетевые интерфейсы), но ничто из этого не касается UDP.

Однако это лишь вопрос времени, пока реализации QUIC превзойдут эти улучшения и преимущества. Взгляните на недавние усилия внедрить разгрузку UDP на Linux, которая позволила бы приложениям объединять и передавать множественные UDP-сегменты между user-space и kernel-space сетевым стеком по затратам примерно одного сегмента; другой пример – поддержка zerocopy для сокетов в Linux, благодаря которой приложения смогли бы избегать затрат по копированию user-space памяти в kernel-space.

Заключение


Подобно HTTP/2 и TLS 1.3, протокол QUIC должен привнести массу новых фичей, которые повысят производительность и безопасность как веб-сайтов, так и других участник инфраструктуры Интернета. Рабочая группа IETF намеревается выкатить первую версию спецификаций QUIC к концу года, так что пора задуматься, как мы можем взять максимум от преимуществ QUIC.

Voximplant

176,00

Облачная платформа голосовой и видеотелефонии

Поделиться публикацией

Похожие публикации

Комментарии 24
    +1
    Я правильно понимаю, что в случае с quick Роскомпозор будет в пролете с блокировками по домену? Раз вся метадата шифруется и SNI наружу не светит.

      +3
      именно. в целом оно и сейчас почти не у кого не работает, ибо dpi нужен, поэтому проще по ip блочить
        0
        интересно, какие новые обоснования блокировок общих ИПов будут использованы. Происки западных IT специалистов?
          0
          Так делают уже сейчас, и никаких обоснований не ищут.
            0
            обоснования для слабаков
              0

              В "войне" с Телеграмом забанили почти 20 млн IP адресов — и ничего.
              Причем Ростелеком до сих пор не успокоился — на сегодня остались заблокированными 3.8 млн адресов. Подробнее: https://usher2.club
              Обоснований не будет — будет самая тупая и действенная блокировка по IP.

            0

            Как бы не получилось наоборот: вместо "не работает — не фильтрует" как бы не сделали "не осилили фильтр — обрубим интернет, мы заботимся о вас!"

            +3
            пользователь зашел в любимую кофейню

            … и там вместо интернета ему предложили авторизоваться в гостевой wifi и посмотреть рекламу. С некоторых пор стал удалять wifi «любимых кофеен» и прочих публичных мест, просто чтобы lte не прерывался на идиотскую веб-авторизацию при переключении от сети к сети.

            P.S. Пользуясь случаем, передаю горячий привет авторам соответствующего закона, из-за чего публичные wifi сети вынуждены возиться с регистрацией и отслеживанием юзеров!

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

            После этой детали становится понятно, что будущего у протокола еще меньше, чем у ipv6. Операторы и так не могут порой нормально сеть отстроить (на ipv4 и http/1.1, а тут я позвоню в ТП со словами, что у меня какой-то там QUIC плохо работает. В общем, для владельцев сайтов безопаснее будет QUIC не включать *грустный смайлик*
              0
              Ну Google это никак не мешает его использовать уже здесь и сейчас
                0

                Если оно не будет работать у всех и каждого железобетонно, то использование такого даст им больше проигрыша, чем пользы.


                Правда, они уже используют, так что, похоже, с проблемами либо справились, либо не так они страшны, либо fallback научились быстро включать. В любом случае, вместо простого http 1.0 получаем не просто сложный http 2.0, а ещё более интересный уж с ежом, в котором ещё меньше людей схожу разберутся.

                  0
                  QUIC в любом случае включается с фолбеком на «обычный» HTTP/2. Точнее, наоборот: это в пределах обычного HTTP/2-соединения браузеру предлагается проапгрейдиться до QUIC через ALTSVC-фрейм, где описывается версия QUIC и адрес для соединения. Если браузер понял этот фрейм и смог соединиться, то он ставит у себя внутри маленький таймер (если память не изменяет, порядка 15 минут), в пределах которого к этому сайту запросы бегают по QUIC. Таймер истёк — повторяем всё снова.

                  Вот если сайт перестал отвечать по QUIC в пределах действия таймера, тогда действительно всё пока грустно, по крайней мере в десктопном хроме.
                0
                > что будущего у протокола еще меньше, чем у ipv6

                www.google.com/intl/en/ipv6/statistics.html
                25%
                Ничего так «отсутствие будущего»

                У меня же, в личной статистике ssh на 100% серверов идет по IPv6. То что IPv6 на России не очень распространен не означает, что в мире он мертв.

                > В общем, для владельцев сайтов безопаснее будет QUIC не включать

                И опять же, Google не знает об этом, не спрашивал и ачекалин, вот и включил, работает себе по QUIC прямо сейчас с клиентами использующими Google Chrome и Chromium. Для клиентов не умеющих в QUIC есть даунгрейд до http/2 и http/1.1
                Так же, как сейчас http/2 даунгрейдится до http/1.1, если клиент не поддерживает http/2, так будет на веб-серверах и с QUIC. Да, никто не мешает прямо здесь и сейчас попробовать QUIC у себя, его поддержка есть в Caddy®
                –4

                Какая то переусложненная !@#$%&. С поддержкой http2 то не все хорошо, а жить с этим UDP чудом с закэшированными заголовками это наверное тот ещё геммор. Надеюсь в таком виде его не введут никогда.

                  +2
                  даёшь rolling-release HTTP!
                    +1
                    Слушайте, у меня с этим QUIC какой-то винегрет. С одной стороны он должен заменить TCP (4 уровень OSI), а с другой работает поверх UDP (т.е. выше 4 уровня) и является продолжением HTTP/2 (т.е. 7 уровень).
                      0

                      Да, меня тоже смущает, что все сетевые уровни смешали в кучу. Поэтому будет сложно с роутерами и прочим. Роутер не должен ничего знать про вышележащие протоколы, а они не должны заниматься транспортом. Иначе придется делать софтверные роутеры на node.js, которые будут обновляться со скоростью обновления пакетов )

                        +1
                        которые будут обновляться

                        Пока один из авторов 193 пакетов, составляющих роутер, не отзовет свое творение из публичного распространения, а другой не впишет в код своего модуля майнер.
                        0
                        Давно UDP стало выше 4 уровня? Ни TCP, ни UDP не вписывались в теоретическую модель OSI, а потому «Transmission Control Protocol (TCP) and the User Datagram Protocol (UDP) of the Internet Protocol Suite are commonly categorized as layer-4 protocols within OSI»
                        Так уж вышло, что пока теоретики придумывали модель OSI практики сделали сети в современном виде у них не спросив. OSI это не очень работающая теория.
                          +1

                          Не один распространненый протокол не соответствует OSI полностью, поскольку это просто теоретическая модель. И чем выше уровень, тем больше разбег. То что все говорят о HTTP, как о 7м уровне, это некая условность.

                          0

                          Не спец в сетях, но возникли вопросы.


                          Наконец, рабочая группа WebRTC тоже смотрит в сторону QUIC (плюс см. QUIC API), так что в обозримом будущем у нас будут видеозвонки с реалтайм-голосом.

                          А сейчас не реалтайм голос передается?


                          Чтобы избежать этого, операторам может понадобиться внедрить более умный балансировщик на 4 уровне.

                          Это не убъет всю производительность железа?

                            0

                            Про реалтайм было невнято написано – спасибо, исправил.


                            Наконец, рабочая группа WebRTC тоже смотрит в сторону QUIC (плюс см. QUIC API), так что в обозримом будущем реалтайм видео/аудио будет ходить по QUIC вместо RTP/RTCP.

                            Про производительность железа – хороший вопрос. Думаю, что ответ будет «это зависит от оптимизации этих самых софтверных балансировщиков». Например, упомянутый Katran называет себя высокопроизводительным.

                            0

                            Хоть и несколько спорная и не совсем понятная штука, но… За только один лишь connection migration я могу простить ему почти все грехи. Не понимаю, почему этого не сделали раньше, да хоть для того же tcp. Если мне доведётся делать проект с необходимостью реализации своего протокола, я наверное выберу именно quic (не обязательно с http, именно сам quic).

                              0
                              Connection migration не сделали раньше, вероятно, потому что отправлять какой-то connection id было дороговато, в добавок к обычной четвёрке «два адреса, два порта». Сейчас размер connection id равен 64 бита, вроде бы, с пожеланием возможности использовать 128 бит, что несколько накладно для медленных соединений.
                              К тому же потребность в этом возникла не так уж и давно.
                              0
                              Таким образом, кодер QPACK может использовать ссылку на динамическую таблицу только после того, как ее получение подтвердил декодер.

                              Не совсем так. Можно использовать настройки, позволяющие кодеру рискнуть блокировкой (в каких-то пределах; см. настройки). Это позволяет увеличить компрессию.

                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                              Самое читаемое