Задержки (а по‑простому тормоза) в работе сети вряд ли кого‑то оставят равнодушным. Мы все очень не любим, когда медленно загружаются наши любимые веб‑ресурсы, или когда обрывается на 99% загрузка какого‑либо файла. Причин тормозов в сети может быть много — от проблем на физическом уровне и до медленной работы на уровне приложений. Сегодня мы поговорим об одной из причин задержек в сети, которую можно выявить с помощью анализатора пакета Wireshark. При этом не имеет особого значения, передается ли наш трафик в открытом виде или прячется за SSL: на верхние уровни мы забираться не будем. Важно только то, что это TCP трафик.

Окна в TCP

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

В механизме скользящего окна используется окно приема на стороне получателя пакетов для управления потоком данных. Принимающий хост указывает размер окна приема в байтах, сохраняет его в ТСР‑заголовке и сообщает передающему хосту, сколько данных он готов сохранить в памяти своего ТСР‑буфера. Именно в этой памяти данные временно хранятся до тех пор, пока они не будут переданы вверх по стеку протоколов на уровень приложений для последующей обработки. В итоге передающий хост может одновременно послать только то количество данных, которое указано в поле Window size (Размер окна) заголовка, полученного в ответ пакета ТСР. Чтобы передающий хост мог послать больше данных, принимающий хост должен отправить ему подтверждение о получении предыдущей порции данных. Он должен также очистить память своего ТСР‑буфера, обработав данные, занимающие эту память.

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

В идеальном мире с постоянной пропускной способностью каналов размер окна действительно был бы постоянным значением. Однако, в реальности загрузка канала у нас постоянно меняется, и вместе с ней меняется и размер Window size. Процесс изменения размеров окна приема действует как в сторону уменьшения, так и увеличения. Так, если сервер способен обработать данные с большей скоростью, он может послать подтверждающий пакет АСК с большим размером окна приема.

Но ниже представлен пример приложения, у которого по каким‑то причинам уменьшился объем памяти, доступной в буфере, в результате чего получатель был вынужден уменьшить размер окна.

В случае, если получателю удалось решить свои проблемы с памятью, окно может быть снова увеличено. Но если быстро решить проблемы не удалось, и есть риск, что следующий блок данных обработать не удастся, получатель может указать размер Window=0. Что произойдет в таком случае?

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

От теории к практике

Теперь давайте вооружимся Wireshark и посмотрим, как выглядит соответствующий трафик.

В примере ниже у нас происходит информационный обмен между 192.168.0.20 и 192.168.0.30. В первых трех ACK пакетах значение Window уменьшается, видимо, у получателя проблемы с памятью и он не может обрабатывать больший объем данных.

Затем получатель отправляет пакет с нулевым окном. Однако после этого, по прошествии небольшого интервала времени, он отправляет обновление информации о размерах окна, в результате чего передача полезной нагрузки возобновляется.

В самом пакете информация о размере окна находится в TCP заголовке:

Теперь посмотрим более печальный случай: когда получателю не удается быстро решить свои проблемы с памятью и значение Window остается нулевым некоторое время.

Здесь мы видим обмен трафиком между двумя узлами. В какой‑то момент один из участников отказывается принимать полезную нагрузку, отправив TCP ZeroWindow. После этого второй участник обмена с некоторой периодичностью отправляет Keep‑Alive пакеты, на которые первый участник продолжает отвечать нулевыми окнами. Пакеты Keep‑Alive также являются служебными и не несут в себе никакой полезной нагрузки.

Как искать

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

Для того чтобы отфильтровать в Wireshark пакеты с определенным размером окна, нужно воспользоваться фильтром tcp.window_size_value.

Например, для того, чтобы найти все пакеты с ненулевым размером окна, нужно указать tcp.window_size_value > 0

Для фильтрации пакетов с ZeroWindow можно, конечно, явно указать фильтр tcp.window_size_value == 0, а можно воспользоваться фильтром

tcp.analysis.zero_window

Для обнаружения Keep_Alive воспользуемся фильтром tcp.analysis.keep_alive

Также узнать, как с течением времени менялось значение TCP Window, можно с помощью графика. Для этого нужно выбрать Statistics → TCP Stream Graph → Window Scaling.

Заключение

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

В завершение рекомендую открытые уроки в Otus, подробнее о них ниже. Занятия пройдут в онлайн-формате и будут полезны сетевым инженерам, сетевым архитекторам и всем, кто глубоко интересуется современными сетевыми технологиями.

  • 5 февраля: ISIS vs. OSPF, или почему же ISIS до сих пор проигрывает битву OSPF в корпоративных сетях? Записаться

  • 18 февраля: Надежность и сети, или почему скорость сходимости STP уже очень давно никому не нравится? Записаться

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


  1. lex899
    04.02.2025 11:41

    Принимающая сторона говорит, что готова принять, условно, три блока данных, отправитель передает их. Получает отправляет подтверждение, после чего отправляются следующие три блока данных и так далее.

    Всю жизнь думал что это работает не так.

    1. Отправляем последовательно (или в любом порядке чтобы навести хаоса) блоки 1, 2, 3.

    2. Если к моменту отправки 4 блока от получателя пришёл ACK на некоторое количество блоков - сдвигаем окно на это количество блоков, продолжаем посылать дальше.

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

    у получателя проблемы с памятью и он не может обрабатывать больший объем данных.

    А если проблемы не с получателем а с сетью и половина пакетов не доходит - размер окна случайно не будет падать?...


  1. BulldozerBSG
    04.02.2025 11:41

    Вопрос "что делать, когда когда окно равно нулю"? остаётся открытым