Всем привет! На связи Сергей Баширов, ведущий разработчик из R&D-команды Cloud.ru. Сегодня поговорим о фильтрах чтения и отображения в TShark. Поделюсь лайфхаком: как быстро и просто посмотреть доступные поля для любого протокола без AI-ассистентов и поиска. Но сначала пара слов о том, как я докатился до такой жизни.

Мы с коллегами уже рассказывали о том, как исследовали возможности параллельной файловой системы pNFS: раз и два. На этом пути нам пришлось засучить рукава, отладить и исправить баги реализа��ии клиента и сервера в ядре Linux. Кстати, мы уже занесли часть фиксов в upstream, но сегодня речь пойдет не об этом.

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

А при чем тут TShark

Поначалу мы использовали только Wireshark, ведь у него удобный и понятный GUI-интерфейс. Собирали трассы на dev-сервере, скачивали на ноутбук и открывали. Потом выяснилось, что из коробки не поддерживается разбор экстентов pNFS-файла. Невелика беда, ведь Wireshark поддерживает внешние плагины, в том числе написанные на Lua. Сделали небольшой скрипт для разбора экстентов, положили его по пути поиска плагинов в домашней папке пользователя, и все заработало.

Когда мы добрались до отладки зависаний файловой системы, размеры собираемых трасс вдруг оказались очень большими и долго скачивались на ноутбук. А еще большие файлы трасс лагают и долго обрабатываются в самом Wireshark, если начинаешь активно пользоваться фильтрами. В принципе, можно нарезать большую трассу на несколько маленьких кусочков. Потом примерно соотнести по времени какие два-три кусочка трассы ну��ны и скачивать только их. Но это уже не так удобно и быстро. Последней же каплей стали сложновоспроизводимые проблемы, которые возникают не при каждом прогоне тестов. В подобных случаях хочется быстро узнать: воспроизвелось или нет. И только потом обрабатывать и скачивать трассы.

Так мы решили осваивать консольный вариант утилиты — TShark, чтобы проверять какие-то вещи прямо на dev-сервере. Он поддерживает те же самые плагины, что и Wireshark, поэтому мы легко смогли подцепить наш Lua-скрипт для разбора экстентов. Я не буду останавливаться на базовых действиях вроде просмотра списка интерфейсов, захвата трафика на интерфейсе, захвата трафика по IP-адресу и подобных. Это все легко ищется в интернете, да и на Хабре тоже есть. Подчеркну только, что возможности фильтров непосредственно в процессе захвата трафика ограничены, поэтому обычно трассу записывают в файл, а потом уже анализируют.

Там есть какие-то поля

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

TShark по умолчанию делает один проход по файлу трассы. Это быстро, просто и не требует буферизации, но имеет свои ограничения. Поля, которые зависят от других полей и требуют заглядывать в предыдущие или последующие пакеты, могут остаться незаполненными. Поэтому есть опция командной стоки «-2», с которо�� TShark делает два прохода по файлу трассы. И как раз на втором проходе дозаполняются те самые зависимые поля. Посмотрим разницу на примере с трассой pNFS.

$ tshark -r capture.pcap | tail -n 4
 9617 67.822645862  192.168.1.4 → 192.168.1.1  NFS 402 V4 Call LAYOUTCOMMIT
 9618 67.823145566  192.168.1.1 → 192.168.1.4  NFS 242 V4 Reply (Call In 9617) LAYOUTCOMMIT
 9619 67.825076053  192.168.1.4 → 192.168.1.1  NFS 334 V4 Call LAYOUTGET
 9620 67.825179615  192.168.1.1 → 192.168.1.4  NFS 310 V4 Reply (Call In 9619) LAYOUTGET
$ tshark -2 -r capture.pcap | tail -n 4
 9617 67.822645862  192.168.1.4 → 192.168.1.1  NFS 402 V4 Call (Reply In 9618) LAYOUTCOMMIT
 9618 67.823145566  192.168.1.1 → 192.168.1.4  NFS 242 V4 Reply (Call In 9617) LAYOUTCOMMIT
 9619 67.825076053  192.168.1.4 → 192.168.1.1  NFS 334 V4 Call (Reply In 9620) LAYOUTGET
 9620 67.825179615  192.168.1.1 → 192.168.1.4  NFS 310 V4 Reply (Call In 9619) LAYOUTGET

Видно, что во втором случае для запросов заполняется поле «Reply In». То есть в зависимости от того, какие поля сообщения нас интересуют, можно выбирать однопроходный режим анализа или двухпроходный.

Фильтры бывают разные

В TShark есть фильтр чтения — опция «-R» и фильтр отображения — опция «-Y». Оба используют одинаковый синтаксис для записи выражений. Фильтр чтения имеет смысл только при двухфазном режиме анализа, потому как применяется во время первого прохода по файлу трассы. Отсеянные пакеты далее не участвуют во втором проходе. Поля, которые не заполняются на первом проходе, например наш «Reply In» в запросе, недоступны в этом фильтре. На втором проходе применяется фильтр отображения перед выводом на экран или записью в файл. И в нем уже доступны все возможные поля. А для однофазного режима анализа рекомендуется просто использовать фильтр отображения. Посмотрим все это на нашем примере с «Reply In».

$ # Исходные сообщения
$ tshark -2 -r capture.pcap | tail -n 2
 9619 67.825076053  192.168.1.4 → 192.168.1.1  NFS 334 V4 Call (Reply In 9620) LAYOUTGET
 9620 67.825179615  192.168.1.1 → 192.168.1.4  NFS 310 V4 Reply (Call In 9619) LAYOUTGET
$
$ # Однопроходный анализ с фильтром отображения
$ tshark -r capture.pcap -Y "rpc.reqframe == 9620"
$
$ # Однопроходный анализ с фильтром чтения
$ tshark -r capture.pcap -R "rpc.reqframe == 9620"
tshark: -R without -2 is deprecated. For single-pass filtering use -Y.
$
$ # Двухпроходный анализ с фильтром отображения
$ tshark -2 -r capture.pcap -Y "rpc.reqframe == 9620"
 9619 67.825076053  192.168.1.4 → 192.168.1.1  NFS 334 V4 Call (Reply In 9620) LAYOUTGET
$
$ # Двухпроходный анализ с фильтром чтения
$ tshark -2 -r capture.pcap -R "rpc.reqframe == 9620"
$

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

С фильтрами все понятно и можно было бы завершить статью, но есть один нюанс: непонятно откуда берутся названия полей. Как, например, догадаться, что поле «Reply In» в фильтре называется «rpc.reqframe»?

В поисках нужного поля

Названия полей и уровень их вложенности могут отличаться от интуитивного ожидания и того, как они определены в спецификации. Когда мы исправляли проблему битых файлов при перезагрузке pNFS-сервера на лету, то на одном из этапов нужно было реализовать поддержку коммитов в режиме grace period. Соответственно, чтобы проверить работоспособность новой фичи, нужно было найти такие коммиты в трассе.

По спецификации NFS 4.1 у нас есть COMPOUND-запрос, внутри него есть подзапрос LAYOUTCOMMIT, внутри последнего есть аргумент loca_reclaim типа bool. Попытки с наскока угадать название фильтра не прокатили. Попробовал поискать в интернете, но тема очень узкоспециализированная, примеров именно для этого поля не нашел. Тогда решил воспользоваться ИИ-ассистентом.

Режим ИИ от гугла сначала предложил вариант с неправильным полем «nfs.procedure_v4» и несуществующим полем «nfs.pnfs.loca_reclaim». На самом деле он еще и путает коды операций, поэтому я указал код вручную.

Еще одна итерация. Добавил в запрос ссылку на документацию. Стало немного лучше. Первая половина фильтра правильная, но иско��ое поле «nfs.loca_reclaim» все ещё вымышленное.

Перплексити мощный, он сможет! Беру сразу уточненную версию запроса с дополнительными деталями. Результат тоже не работает. Поля «nfs.reclaim?» не существует. Но как потом оказалось, он был очень близок.

Дальше я уже сам полез в документацию разбираться. Она весьма скудная, по ней разве что можно угадывать. Слово «reclaim» там встречается несколько раз и ИИ запутался с выбором правильного варианта. А поле, которое я искал, называется «nfs.reclaim4». Вложенности как в спецификации никакой нету, это верхнеуровневое поле.

В итоге собираю все детали вместе и проверяю. Работает!

$ tshark -r capture.pcap -Y "nfs.opcode == 49 && nfs.reclaim4 == 1"
 4620 33.284607978  192.168.1.2 → 192.168.1.1  NFS 358 V4 Call LAYOUTCOMMIT
 4622 33.294198211  192.168.1.2 → 192.168.1.1  NFS 358 V4 Call LAYOUTCOMMIT
 4624 33.300496328  192.168.1.2 → 192.168.1.1  NFS 358 V4 Call LAYOUTCOMMIT
 4626 33.311359444  192.168.1.2 → 192.168.1.1  NFS 358 V4 Call LAYOUTCOMMIT
 4628 33.318656121  192.168.1.2 → 192.168.1.1  NFS 358 V4 Call LAYOUTCOMMIT

И где же обещанный лайфхак

Размышляя о том, как бы все это делать прямо в консоли, попроще, я зацепился за две опции из man по TShark. Сначала поигрался с опцией «-G». Она показывает информацию о доступных протоколах и полях в рантайме. Поскольку часть плагинов встроены в само приложение на этапе компиляции, а другая часть подгружается динамич��ски из внешних библиотек и скриптов, то это хороший способ узнать о фактически доступных протоколах в конкретном рабочем окружении. Однако, информативность описания полей здесь ничем не лучше, чем в документации на сайте. Те же самые короткие фразы и много похожих названий. Если не знаешь, что конкретно искать — сложно.

А вот вторая опция «-T», которая управляет форматом вывода, и натолкнула меня на этот лайфхак. Наверное, для экспертов в TShark ничего нового. Но кому-то точно может пригодиться. Рассматривая, как выглядит полная распечатка пакета в различных форматах, я заметил, что в JSON ключи называются точно так же, как и поля в фильтрах чтения и отображения. Поэтому, если распечатать один тестовый пакет в формате JSON, то можно просто увидеть все возможные поля, по которым доступна фильтрация. А ещё можно сравнить выводы однопроходного и двухпроходного анализов, чтобы узнать для каких полей реально требуется двухпроходный разбор пакетов.

Например, посмотрим на запрос LAYOUTCOMMIT в pNFS.

$ tshark -r capture.pcap -Y "frame.number == 8999" -T json
{
   "rpc.programversion":"4",
   "nfs.procedure_v4":"1",
   "Tag: <EMPTY>":{
      "rpc.opaque_length":"0",
      "nfs.tag":""
   },
   "nfs.minorversion":"2",
   "nfs.ops.count":"4",
   "nfs.ops.count_tree":{
      "nfs.opcode":"22",
      "nfs.opcode_tree":{
         "FileHandle":{
            "nfs.fh.length":"32",
            "nfs.fh.hash":"0xe5c2e6fc",
            "nfs.fhandle":"01:00:06:81:e4:a7:99:9f:95:78:45:fc:af:71:31:9a:c6:11:9a:aa:87:00:00:00:00:00:00:00:3b:38:d7:8c"
         }
      },
      "nfs.opcode":"49",
      "nfs.opcode_tree":{
         "nfs.offset4":"0",
         "nfs.length4":"276824064",
         "nfs.reclaim4":"0",
         "nfs.stateid":"00:00:00:58:48:62:c9:68:c5:b0:cc:3c:1d:00:00:00",
         "nfs.stateid_tree":{
            "nfs.stateid.hash":"0x0e1f",
            "nfs.stateid.seqid":"88",
            "nfs.stateid.other":"48:62:c9:68:c5:b0:cc:3c:1d:00:00:00",
            "nfs.stateid.other_hash":"0x94a82cb6"
         },
         "nfs.newoffset":"1",
         "nfs.offset4":"276824063",
         "nfs.newtime":"0",
         "nfs.layouttype":"3",
         "layout update: <DATA>":{
            "rpc.opaque_length":"48",
            "nfs.layoutupdate":"00:00:00:01:01:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:10:77:a0:00:00:00:00:00:00:08:60:00:00:00:00:00:00:00:00:00:00:00:00:00"
         }
      }
   }
}

А теперь попробуем найти все запросы LAYOUTRETURN для одного и того же файла, используя поле «nfs.fh.hash», которое подсмотрели в JSON-выводе выше.

$ tshark -r capture.pcap -Y "nfs.opcode == 51 && nfs.fh.hash == 0xe5c2e6fc"
 7458 60.063533748  192.168.1.4 → 192.168.1.1  NFS 330 V4 Call LAYOUTRETURN
 7497 60.251840677  192.168.1.4 → 192.168.1.1  NFS 330 V4 Call LAYOUTRETURN
 7622 60.683075903  192.168.1.4 → 192.168.1.1  NFS 330 V4 Call LAYOUTRETURN
 7884 61.526904818  192.168.1.4 → 192.168.1.1  NFS 330 V4 Call LAYOUTRETURN
 8352 63.149364410  192.168.1.4 → 192.168.1.1  NFS 330 V4 Call LAYOUTRETURN
 9248 66.455303356  192.168.1.4 → 192.168.1.1  NFS 330 V4 Call LAYOUTRETURN
 9282 66.583361699  192.168.1.4 → 192.168.1.1  NFS 330 V4 Call LAYOUTRETURN

Работает! Как по мне, то стало намного проще и быстрее разбирать трассы, не переключаясь из консоли.

Заключение

Мы в команде стали часто применять TShark. Это очень удобный и гибкий инструмент. Потратив немного времени на его изучение, можно получить солидное ускорение в работе с сетевым трафиком.

А какими утилитами и фильтрами пользуетесь вы, когда отлаживаете сетевые приложения? Давайте обсудим необычные варианты в комментариях?

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