Пишем плагин-диссектор для Wireshark

    imageWireshark — одна из незаменимых утилит для «прослушки» сети, при работе с сетевыми протоколами. В состав программы уже входит некое количество диссекторов1, которое помогают детально рассмотреть пакеты базовых протоколов. Но при работе над проприетарным протоколом компании Nortel я столкнулся с отсутсвием подходящего диссектора. А нужен он был как воздух. Выход был очевиден — написать свой. Что я и сделал.
    Таким образом, имея небольшой опыт написания плагина «анатома» под Wireshark, я решил поделиться знаниями и опытом с сообществом. Ну и для себя записать, на случай, если в будущем понадобится.

    Диссекция — лат. dissectio, от dissecare, рассекать

    Примечание 1: при написании статьи я предполагал, что читатель знаком с утилитой Wireshark, а также умеет пользоваться ее базовыми возможностями.

    Примечание 2: так как я работаю под Linux, то и плагин писал под эту ОС. Под Windows мой плагин тоже должен собираться. Отличия, я думаю, будут только в make файлах.

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

    Протокол Foo


    В двух словах я опишу придуманный протокол, который назову незамысловатым именем Foo. Пусть этот протокол будет над протоколом UDP и будет иметь следующую структуру:
    • 1 байт — версия протокола
    • 1 байт — тип пакета
    • 1 байт — всевозможные флаги
    • 1 байт — какая-то булева переменная
    • 4 байт — длина полезной нагрузки
    • От 0 до 200 байт — полезная нагрузка

    Настройка окружения


    Перед тем как начать писать плагин, необходимо что-то поставить, что-то создать, где-то что-то изменить. Вот что Wireshark просит установить:
    • python (думаю любой 2-ой версии, но точно не 3-ей)
    • cmake
    • bison
    • flex
    Полный список необходимых библиотек и утилит находится здесь.

    После установки всего необходимого качаем исходники Wireshark той версии, для которой будем писать плагин. В моем случае исходники стабильной ветки 1.6:
        $ cd $HOME
        $ svn co http://buildbot.wireshark.org/trunk-1.6/ wireshark

    Чтобы убедиться в работоспособности компиляции, а так же в том, что все необходимые утилиты и библиотеки установлены, запустите:
        $ cd wireshark
        $ ./autogen.sh
        $ ./configure

    Если все в порядке, то переходим в папку plugins и создаем там свою папку с названием протокола:
        $ cd $HOME/wireshark/plugins
        $ mkdir foo
        $ cd foo
        $ touch packet-foo.c

    Каркас


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

    packet-foo.c
    #ifdef HAVE_CONFIG_H
    # include "config.h"
    #endif
    
    #include <epan/packet.h>
    
    #define FOO_PORT 35000
    /* Номер UDP порта, на котором мы предполагаем будет находится FOO траффик. */
    
    static int proto_foo = -1;
    /* При регистрации протокола переменная затрется идентификатором протокола. */
    
    void proto_register_foo(void)
    {
        proto_foo = proto_register_protocol (
            "FOO Protocol", /* полное имя */
            "FOO",          /* короткое имя */
            "foo"           /* аббревиатура */
            );
    }

    В самом начале мы вызываем функцию proto_register_protocol(), которая регистрирует протокол и выдает его уникальный идентификатор. Функции мы передаем три разных имени нашего протокола, которые будут выводится в различных частях программы. (К примеру, имена «FOO Protocol» и «FOO» используются в Настройках Wireshark’а, а аббревиатура «foo» используется в поле фильтра.)

    Далее нам нужно назначить функцию, которая будет вызываться в случае необходимости расшифровать пойманный foo-пакет. Делается это вызовом функции create_dissector_handle().
    void proto_reg_handoff_foo(void)
    {
        static dissector_handle_t foo_handle;
    
        foo_handle = create_dissector_handle(dissect_foo, proto_foo);
        dissector_add_uint("udp.port", FOO_PORT, foo_handle);
    }

    Тут мы регистрируем обработчик-диссектор, который выполняет “грязную” работу разложения протокола по составу и привязываем его к протоколу proto_foo. Затем мы связываем наш обработчик с UDP портом, на котором мы предполагаем будет ходить трафик. Таким образом Wireshark вызовет “нас”, когда на заданном нами порту (35000) появится какая-то активность.

    Есть соглашение, согласно которому фукнции proto_register_foo() и proto_reg_handoff_foo() необходимо написать в конце нашего исходного файла.

    Теперь осталось самое главное — написать сам код диссектора.
    static void dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
    {
        col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO");
        col_clear(pinfo->cinfo, COL_INFO);
    }

    Эта функция будет вызываться для детальной работы над пакетом, который ей передан. Данные пакета хранятся в специальном буфере — tvb. В pinfo кладем общую информацию о протоколе. А в tree происходят основные изменения, когда определяется конечный вид протокола в выводе Wireshark'а.
    В первой строке мы устанавливаем значение в колонку Protocol. Во второй — очищаем Info колонку. (Обе колонки как правило показываются во время захвата пакетов.)

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

    Сборка плагина


    Для сборки (под Linux) нам необходимо обзавестись некоторыми файлами в нашей директории: Makefile.am, Makefile.common и moduleinfo.h.
    • Makefile.am — UNIX/Linux make файл
    • Makefile.common — общий make файл для UNIX/Linux и Windows, содержит имена файлов плагина
    • Makefile.nmake — make файл для Windows
    • moduleinfo.h — содержит версию плагина
    • moduleinfo.nmake — содержит информацию о версиях DLL для Windows
    • packet-foo.c — наш исходный код для диссектора
    Кстати говоря, не обязательно зарываться в таком количестве make файлов, так как Wireshark предоставляет способ проще — использовать cmake. Благодаря этому количество изменений в make файлах сводится к минимуму. Для этого, создаем CMakeLists.txt в папке с нашим плагином со следующим содержанием:
    set(DISSECTOR_SRC
            packet-foo.c
    )
    
    set(PLUGIN_FILES
            plugin.c
            ${DISSECTOR_SRC}
    )
    
    set(CLEAN_FILES
            ${PLUGIN_FILES}
    )
    
    if (WERROR)
            set_source_files_properties(
                    ${CLEAN_FILES}
                    PROPERTIES
                    COMPILE_FLAGS -Werror
            )
    endif()
    
    include_directories(${CMAKE_CURRENT_SOURCE_DIR})
    
    register_dissector_files(plugin.c
            plugin
            ${DISSECTOR_SRC}
    )
    
    add_library(foo ${LINK_MODE_MODULE}
            ${PLUGIN_FILES}
    )
    set_target_properties(foo PROPERTIES PREFIX "")
    set_target_properties(foo PROPERTIES LINK_FLAGS "${WS_LINK_FLAGS}")
    
    target_link_libraries(foo epan)
    
    install(TARGETS foo
            LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/@CPACK_PACKAGE_NAME@/plugins/${CPACK_PACKAGE_VERSION} NAMELINK_SKIP
            RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}/@CPACK_PACKAGE_NAME@/plugins/${CPACK_PACKAGE_VERSION}
            ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/@CPACK_PACKAGE_NAME@/plugins/${CPACK_PACKAGE_VERSION}
    )

    Примечание: можно не запоминать каждый раз, как выглядит CMakeLists.txt файл, а скопировать его из папки любого существующего плагина, к примеру, interlink. И затем заменить все упоминания interlink в этом файле на foo.

    Затем даем знать о нашем плагине тулзам автоматизации процесса сборки.
        $ cd $HOME/wireshark/
        $ vim CMakeLists.txt

    Добавляем строчку «plugins/foo» учитывая отступы:
    
        .......
        if(ENABLE_PLUGINS)
            set(HAVE_PLUGINS 1)
            set(PLUGIN_DIR="${DATAFILE_DIR}/plugins/${CPACK_PACKAGE_VERSION}")
            set(PLUGIN_SRC_DIRS
                plugins/foo
                plugins/asn1
                plugins/docsis
                .......

    Теперь создаем директорию, в которой будут храниться созданные во время компиляции и линковки файлы. Следом запускаем сам процесс сборки. Следующие шаги выполняем из директории $HOME/wireshark/
        $ mkdir build
        $ cd build
        $ cmake ..
        $ make foo

    При успешной компиляции мы получим foo.so библиотеку в папке $HOME/wireshark/build/lib/.

    Тестовый запуск


    Копируем файл foo.so в дирекуторию /usr/lib/wireshark/plugins/1.6.0/ и запускаем Wireshark. Заходим в «Help -> About Wireshark», переходим на вкладку Plugins и в появившемся списке находим наш плагин — foo.so.

    Чтобы удостовериться, что плагин распознает наш протокол, стартуем захват пакетов и пускаем по сети пакеты с нашим протоколом. (Для примера я на скорую руку написал программку, которая посылает по сети UDP пакет FOO протокола. Ее код можно найти в конце статьи.)

    Рабочий диссектор


    Теперь, когда у нас есть каркас плагина, который пока ничего полезного для нас не делает, но как минимум запускается, мы «распишем» ему наш протокол. Самая простая вещь, которую мы можем сделать — определить границы нашего протокола.

    Для этого, в первую очередь, мы создаем ветку на нашем дереве (tree), в которую будем помещать результаты декодирования. Диссектор вызывается в двух различных случаях: в одном случае, когда необходимо получить сводную информацию о пакете, и в другом случае, когда нужно вывести детальную информацию о том же пакете. Если указатель на tree равен NULL, значит у нас запрашивают только сводную информацию. Иначе нам предлагается заполнить tree детальной информацией, которая будет выведена пользователю Wireshark. С учетом этого наш диссектор приобретает вид:
    static void dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
    {
        col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO");
        col_clear(pinfo->cinfo, COL_INFO);
    
        if (tree) {
            proto_item *ti = NULL;
            ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, FALSE);
        }
    }

    Здесь мы добавили ветку в дерево (tree) вызовом proto_tree_add_item(). Эта ветка будет содержать все детали о нашем foo протоколе. Так же мы помечаем область данных в пакете, которую использует наш протокол. В нашем случае — это вся область за границей данных UDP пакета, так как мы не рассматриваем случай, когда за нашим протоколом содержится еще один.

    Параметры функции proto_tree_add_item():
    • tree — все дерево информации о нашем пакете
    • proto_foo — идентификатор нашего протокола
    • tvb — блок данных (данные, переданные нашим протоколом)
    • 0 — указываем, начиная с какой позиции данные в блоке tvb являются данными нашего foo протокола
    • -1 — количество байт, занимаемое нашим протоколом ("-1" означает «до конца блока данных»)
    • FALSE — указывается порядок байт (TRUE — если байты расположены в сетевом порядке.)
    После этих изменений Wireshark начнет определять, где начинается и кончается область нашего протокола и помечает ее как “FOO Protocol”.

    Следующим шагом будет добавление деталей. Для этого шага потребуется создание нескольких массивов и дополнительных вызовов функций, который помогут при расшифровке. Изменения коснутся тела функции proto_register_foo(), показанной ранее.

    Добавим два статических массива в начало proto_register_foo(). Потом зарегистрируем эти массивы после вызова функции proto_register_protocol().
    void proto_register_foo(void)
    {
        static hf_register_info hf[] = {
            { &hf_foo_hdr_version,
                { "FOO Header Version", "foo.hdr.version",
                FT_UINT8, BASE_DEC,
                NULL, 0x0,
                NULL, HFILL }
            },
            { &hf_foo_hdr_type,
                { "FOO Header Type", "foo.hdr.type",
                FT_UINT8, BASE_DEC,
                NULL, 0x0,
                NULL, HFILL }
            }
        };
    
        static gint *ett[] = { &ett_foo };
        proto_foo = proto_register_protocol (
            "FOO Protocol",
            "FOO", 
            "foo" );
    
        proto_register_field_array(proto_foo, hf, array_length(hf));
        proto_register_subtree_array(ett, array_length(ett));
    }

    А сразу после объявления глобальной переменной proto_foo добавим еще 3 объявления:
    static gint ett_foo = -1;
    static int hf_foo_hdr_version = -1;
    static int hf_foo_hdr_type = -1;

    Теперь улучшим отображение нашего протокола:
    if (tree) {
        proto_item *ti = NULL;
        proto_tree *foo_tree = NULL;
    
        ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, FALSE);
        foo_tree = proto_item_add_subtree(ti, ett_foo);
        proto_tree_add_item(foo_tree, hf_foo_hdr_version, tvb, 0, 1, FALSE);
        proto_tree_add_item(foo_tree, hf_foo_hdr_type, tvb, 1, 1, FALSE);
    }

    Теперь знания Wireshark о нашем протоколе становятся более детальными. Пока мы лишь распознали первые 2 байта нашего foo протокола, которые отвечают за версию протокола и тип пакета соответственно.

    Вызов функции proto_item_add_subtree() добавил дополнительную ветвь (foo_tree) с деталями о foo протоколе в дерево (tree) информации о всем пришедшем пакете. В foo_tree мы будет детально описывать наш протокол.
    Переменной ett_foo контролируется “развертка” (expansion) дерева информации о протоколе в выводе программы Wireshark. Эта переменная запоминает, должен ли наш протокол быть развернутым в то время, пока мы передвигаемся по пакетам.
    proto_tree_add_item() на этот раз использует переменную hf_foo_hdr_version для вывода значения в нужном формате. hdr_version — 1 байт из tvb, начиная с позиции 0. hdr_type — 1 байт из tvb, начиная с позиции 1.

    Если мы посмотрим на объявление hf_foo_hdr_version в статическом массиве, то увидим детально описанное поле, где
    • hf_foo_hdr_version — индекс ветки
    • FOO Header Version — именование поля
    • foo.hdr.version — это строчка, используемая для фильтрации пакетов (такой вид строчки будет в поле фильтра)
    • FT_UNIT8 — указывает на тип и размер элемента, который мы читаем из блока данных tvb. В данном случае указывает, что поле «версия протокола» занимает 1 байт типа unsigned integer. (Другие возможные типы можно найти в файле wireshark/epan/ftypes/ftypes.h)
    • BASE_DEC — для числовых типов указывае, т в какой системе выводить числа. (Также может быть BASE_HEX или BASE_OCT. Для нечисловых типов используем BASE_NONE.)
    Как используется остальная часть — увидим ниже.

    Теперь можно снова собрать плагин и запустить его вместе с Wireshark. Мы увидим, что вывод Wireshark'а стал более пригодным.

    Чтож, завершим работу по расшифровке нашего foo-протокола. Для этого нам понадобится объявить еще несколько глобальных переменных и сделать несколько дополнительных вызовов:
    ...
    static int hf_foo_hdr_flags   = -1;
    static int hf_foo_hdr_bool    = -1;
    static int hf_foo_pl_len      = -1;
    static int hf_foo_payload     = -1;
    ...
    
    void proto_register_foo(void)
    {
            ...
            { &hf_foo_hdr_flags,
                { "FOO Header Flags", "foo.hdr.flags",
                FT_UINT8, BASE_HEX,
                NULL, 0x0,
                NULL, HFILL }
            },
            { &hf_foo_hdr_bool,
                { "FOO Header Boolean", "foo.hdr.bool",
                FT_BOOLEAN, BASE_NONE,
                NULL, 0x0,
                NULL, HFILL }
            },
            { &hf_foo_pl_len,
                { "FOO Payload Length", "foo.pl_len",
                FT_UINT8, BASE_DEC,
                NULL, 0x0,
                NULL, HFILL }
            },
            { &hf_foo_payload,
                { "FOO Payload", "foo.payload",
                FT_STRING, BASE_NONE,
                NULL, 0x0,
                NULL, HFILL }
            }
        ...
    }
    
    static void dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
    {
        ...
        if (tree) {
            gint offset = 0;
            proto_item *ti = NULL;
            proto_tree *foo_tree = NULL;
    
            ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, FALSE);
            foo_tree = proto_item_add_subtree(ti, ett_foo);
            proto_tree_add_item(foo_tree, hf_foo_hdr_version, tvb, offset, 1, FALSE); offset += 1;
            proto_tree_add_item(foo_tree, hf_foo_hdr_type, tvb, offset, 1, FALSE); offset += 1;
            proto_tree_add_item(foo_tree, hf_foo_hdr_flags, tvb, offset, 1, FALSE); offset += 1;
            proto_tree_add_item(foo_tree, hf_foo_hdr_bool, tvb, offset, 1, FALSE); offset += 1;
            proto_tree_add_item(foo_tree, hf_foo_pl_len, tvb, offset, 4, TRUE); offset += 4;
            proto_tree_add_item(foo_tree, hf_foo_payload, tvb, offset, -1, FALSE);
        }
    }
    

    Таким образом мы расшифровали все биты нашего придуманного протокола.
    Можно видеть различные типы элементов, которые мы использовали во время расшифровки: FT_BOOLEAN, FT_STRING и FT_UINT8. А так же, в случае с нечисловыми элементами, использовали BASE_NONE.

    Теперь Wireshark имеет достаточно хорошее представление о нашем протоколе. И на этом шаге можно было бы остановиться, если бы такой детализации хватало всегда. У нас пока еще есть поле flags, которое придется раскладывать побитово вручную. А также, в случае, если версия протокола неизвестна, можно ли доверять выводу? Ну и самый важный момент — диссектор будет генерировать исключение, если пакет придет без поля payload и с нулевым значением pl_len. Итак, есть место для совершенствования плагина. Продолжим…

    Добавляем деталей в вывод


    Совершенству нет предела! Так и в нашем случае. Начем с именования версии и типа пришедшего пакета, что позволит гораздо быстрее воспринимать информацию при просмотре пакета. Для этого нам потребуются два массива:
    static const value_string packetversions[] = {
        { 1, "Version 1" },
        { 0, NULL }
    };
    
    static const value_string packettypes[] = {
        { 1, "Ping request" },
        { 2, "Ping acknowledgment" },
        { 3, "Print payload" },
        { 0, NULL }
    };

    Зависимость в массивах очень простая — «значение, имя значния». Таким образом, при просмотре пакета мы будем видеть не голый номер типа пакета и вспоминать, что он значит, а сразу увидим описание типа. Для доступа к этим массивам будем использовать VALS-макросы, которые предоставляются самим Wireshark.
            { &hf_foo_hdr_version,
                { "FOO Header Version", "foo.hdr.version",
                FT_UINT8, BASE_DEC,
                VALS(packetversions), 0x0,
                NULL, HFILL }
            },
            { &hf_foo_hdr_type,
                { "FOO Header Type", "foo.hdr.type",
                FT_UINT8, BASE_DEC,
                VALS(packettypes), 0x0,
                NULL, HFILL }
            }

    С версией и типом пакета мы определились. Теперь более детально распишем флаги.
    #define FOO_FIRST_FLAG      0x01
    #define FOO_SECOND_FLAG     0x02
    #define FOO_ONEMORE_FLAG    0x04
    
    static int hf_foo_flags_first   = -1;
    static int hf_foo_flags_second  = -1;
    static int hf_foo_flags_onemore = -1;
    
    void proto_register_foo(void) {
        ...
            { &hf_foo_flags_first,
                { "FOO first flag", "foo.hdr.flags.first",
                FT_BOOLEAN, FT_INT8,
                NULL, FOO_FIRST_FLAG,
                NULL, HFILL }
            },
            { &hf_foo_flags_second,
                { "FOO second flag", "foo.hdr.flags.second",
                FT_BOOLEAN, FT_INT8,
                NULL, FOO_SECOND_FLAG,
                NULL, HFILL }
            },
            { &hf_foo_flags_onemore,
                { "FOO onemore flag", "foo.hdr.flags.onemore",
                FT_BOOLEAN, FT_INT8,
                NULL, FOO_ONEMORE_FLAG,
                NULL, HFILL }
            }
        ...
    }
    
    static void dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) {
        ...
                proto_tree_add_item(foo_tree, hf_foo_hdr_flags, tvb, offset, 1, FALSE); 
                proto_tree_add_item(foo_tree, hf_foo_flags_first, tvb, offset, 1, FALSE);
                proto_tree_add_item(foo_tree, hf_foo_flags_second, tvb, offset, 1, FALSE);
                proto_tree_add_item(foo_tree, hf_foo_flags_onemore, tvb, offset, 1, FALSE); offset += 1;
        ...
    }

    Так как флаг — это один бит информации со значениями «1» или «0», то мы используем тип FT_BOOLEAN. Также мы задаем маску для каждого флага шестым параметром (FOO_FIRST_FLAG, FOO_SECOND_FLAG, FOO_ONEMORE_FLAG), чтобы определить, какой бит из байта интерпретировать. Обратите внимание на то, что мы используем одно и то же значение переменной offset для всех флагов.

    Вывод теперь гораздо читабельнее. Но мы не остановимся и сделам его еще более информативным. Обратите внимание: Wireshark именует пакеты как «FOO Protocol». Это имя мы задали при регистрации протокола. Wireshark предоставляет возможность добавить дополнительную ифнормацию к этому имени. Выведем в этом поле информацию о типе протокола. Для этого нам понадобиться получить значение типа, используя tvb_get_guint8()2. Полученное значение выведем в двух местах: сразу за именем протокола и в колонке Info.
    static void dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
    {
        guint8 packet_version = tvb_get_guint8(tvb, 0);
        guint8 packet_type    = tvb_get_guint8(tvb, 1);
        guint32 packet_pl_len = 0;
        
        col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO");
        col_clear(pinfo->cinfo, COL_INFO);
    
        if (tree) {
            gint offset = 0;
            proto_item *ti = NULL;
            proto_tree *foo_tree = NULL;
    
            ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, FALSE);
            
            foo_tree = proto_item_add_subtree(ti, ett_foo);
            proto_tree_add_item(foo_tree, hf_foo_hdr_version, tvb, offset, 1, FALSE); offset += 1;
            
            switch ( packet_version ) {
                case 1:
                    col_add_fstr(pinfo->cinfo, COL_INFO, "Type: %s", 
                            val_to_str(packet_type, packettypes, "Unknown (0x%02x)"));
                    proto_item_append_text(ti, ", Type: %s", 
                            val_to_str(packet_type, packettypes, "Unknown (0x%02x)"));
                    proto_tree_add_item(foo_tree, hf_foo_hdr_type, tvb, offset, 1, FALSE); offset += 1;
                    proto_tree_add_item(foo_tree, hf_foo_hdr_flags, tvb, offset, 1, FALSE); 
                    proto_tree_add_item(foo_tree, hf_foo_flags_first, tvb, offset, 1, FALSE);
                    proto_tree_add_item(foo_tree, hf_foo_flags_second, tvb, offset, 1, FALSE);
                    proto_tree_add_item(foo_tree, hf_foo_flags_onemore, tvb, offset, 1, FALSE); offset += 1;
                    proto_tree_add_item(foo_tree, hf_foo_hdr_bool, tvb, offset, 1, FALSE); offset += 1;
                    proto_tree_add_item(foo_tree, hf_foo_pl_len, tvb, offset, 4, TRUE);
                    packet_pl_len = tvb_get_ntohl(tvb, offset); offset += 4;
                    if ( packet_pl_len ) 
                        proto_tree_add_item(foo_tree, hf_foo_payload, tvb, offset, -1, FALSE);
                    break;
                default:
                    col_add_fstr(pinfo->cinfo, COL_INFO, "Unknown version of Foo protocol (0x%02x)", packet_version);
            }
        }
    }

    Полученное значние мы передаем макросу val_to_str(), который для переданного значения возвращает строку-описание, либо заданную нами строку, в случае, если описание не найдено.
    Так же мы пофиксили ошибку при неправильной обработке payload, когда выбрасывалось исключение.
    И в дополнение добавили ветвление для разных версий протокола.

    Дополнение


    Пример рабочего диссектора для протокола Foo


    tvb_get_guint8()
    Помимо функции tvb_get_guint8() имеется ряд других, описание которых можно найти в файле wireshark/epan/tvbuff.h

    Архив
    Архив с рабочими исходными файлами. Включает:
    • packet-foo.c — исходный код плагина диссектора
    • CMakeLists.txt — cmake файл для плагина
    • send-foo-packet.c — исходных код программы посылающей foo-пакет
    Листинг программы посылающей foo пакет на порт 35000 *
    #include <arpa/inet.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <fcntl.h>
    
    #define FOO_PORT    35000
    #define BUFFER_SIZE 210
    
    struct _message {
       unsigned char pck_version;      // = argv[1]
       unsigned char pck_type;         // = 1 | 3
       unsigned char pck_flags;        // = rand
       unsigned char pck_boolean;      // = rand
       unsigned int  pck_payload_len;  // = strlen(argv[2])
    };
    
    int main(int argc, char ** argv) {
       if ( argc != 3 ) {
          printf("Usage: %s <version> <ping|\"text\">\n", argv[0]);
          return 1;
       }
    
       struct sockaddr_in cli_addr;
       int s, cli_len = sizeof(cli_addr);
       char buf[BUFFER_SIZE];
       struct _message msg;
       msg.pck_version = atoi(argv[1]);
       msg.pck_payload_len = 0;
    
       unsigned int randomData = open("/dev/urandom", O_RDONLY);
       unsigned int myRandomInteger;
       read(randomData, &myRandomInteger, sizeof(myRandomInteger));
       msg.pck_flags  |= myRandomInteger%8;
       read(randomData, &myRandomInteger, sizeof(myRandomInteger));
       msg.pck_boolean = myRandomInteger%2;
       close(randomData);
    
       if ( (s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1 ) {
          perror("socket");
          exit(1);
       }
    
       memset((char*)&cli_addr, 0, sizeof(cli_addr));
       cli_addr.sin_family      = AF_INET;
       cli_addr.sin_port        = htons(FOO_PORT);
       cli_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
       
       memset(buf, 0, BUFFER_SIZE);
    
       if ( ! strcmp(argv[2], "ping") ) {
          msg.pck_type = 1;
       } else {
          msg.pck_type = 3;
          msg.pck_payload_len = (strlen(argv[2])<200)?strlen(argv[2]):200;
          strncpy(buf+sizeof(struct _message), argv[2], (strlen(argv[2])<200)?strlen(argv[2]):199);
       }
       
       memcpy(buf, (char*)&msg, sizeof(struct _message));
       
       if ( sendto(s, buf, sizeof(struct _message)+msg.pck_payload_len,
                   0, (struct sockaddr*)&cli_addr, cli_len) == -1 ) {
          perror("sendto");
          exit(1);
       }
       
       exit(0);
    }
      * программа без защиты от дурака
    Поделиться публикацией

    Комментарии 18

      0
      В апстрим заслали?
        +1
        Нет.
          +1
          Думаете, что не пригодится большинству? Или хочется причесать сначала код? По-моему, очень полезная контрибуция.
            0
            Честно говоря, я не думал над этим. Если время будет, то попробую заслать.
            Хотя, я считаю, что ценность статьи в том, что я подробно расписал весь процесс создания плагина, а не приведенный код. Когда мне нужно было написать плагин, источников на этот счет было очень мало и ни одного на русском языке. А код можно подглядеть в соседнем плагине. :-)
              0
              Ценность статьи, конечно, не в коде, но если кто-то в будущем столкнется с проприетарным протоколом компании Nortel, наличие вашего диссектора в стандартной поставке сэкономит человеку время (как вариант, высвободит на написание какой-либо другой полезной статьи). :-)
                +1
                А, вон вы о чем. Я в начале сделал оговорку, что не могу ни в каком виде упоминать закрытый протокол, ни как он выглядит, ни тем более выкладывать диссектор для него. В противном случае меня затаскают по судам. Сейчас это модно. :-)
                Тот диссектор, который я писал для работы, распространяется внутри компании. Поэтому те, кому он может понадобится, уже с ним работают.
                  0
                  Слона-то я и не заметил. :-) Спасибо за разъяснения.
                    0
                    А как же clean room reverse engineering?
          0
          Нет.
            0
            Промахнулся. Отвечал на первый коммент.
          • НЛО прилетело и опубликовало эту надпись здесь
            • НЛО прилетело и опубликовало эту надпись здесь
                0
                Спасибо за доброе слово.
                0
                Добавил в избранное; может дойдут руки починить диссектор AMQP, а то он только 0-8 SPEC понимает…
                  +1
                  Если хотя бы один человек применит статью в своей практике, то значит я не зря потратил время. :-)
                  0
                  Большое спасибо!
                  Довольно часто использую Wireshark.
                  С таким подробным туториалом описать любой протокол будет ещё проще.
                  Обязательно положу в избранное и при случае воспользуюсь.
                    0
                    Можно ли научить вайршарк читать дамп из файла по мере наполнения? Допустим дамп собирается с помощью tcpdump на удаленном хосте, а файловая система его подмонтирована по nfs. Демон rpcap использовать нет возможности.
                      0
                      Для TCP сильно сложнее написать?

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

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