From 4ea0ec0e720139f94540c96aec2b3a5c663913a4 Mon Sep 17 00:00:00 2001 From: Kyle Stemen Date: Wed, 15 Nov 2023 22:35:15 -0800 Subject: [PATCH] Enable building on Windows --- CMakeLists.txt | 33 ++++++++++--- README.md | 44 ++++++++++++++--- dissector.cpp | 131 ++++++++++++++++++++++++++++--------------------- 3 files changed, 140 insertions(+), 68 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 38619f0..a8e0258 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.21) project(vs_protocol - VERSION 1.0 + VERSION 1.0.1 DESCRIPTION "Wireshark dissector the Vintage Story" ) @@ -8,8 +8,20 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Wireshark 4.0 CONFIG REQUIRED) -find_package(PkgConfig REQUIRED) -pkg_check_modules(ZSTD REQUIRED libzstd) +find_package(PkgConfig) +if(PkgConfig_FOUND) + pkg_check_modules(ZSTD REQUIRED libzstd) +else() + find_package(zstd CONFIG REQUIRED) + message(WARNING "test ${zstd_FOUND}") + message(WARNING "test2 ${ZSTD_INCLUDE_DIRS}") + message(WARNING "test2 ${zstd_INCLUDE_DIRS}") + get_property(dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES) + message(WARNING "test3 ${dirs}") + foreach(dir ${dirs}) + message(STATUS "dir='${dir}'") + endforeach() +endif() SET(PLUGIN_DIR "${Wireshark_PLUGIN_INSTALL_DIR}" CACHE STRING "Directory to install the dissector plugin" @@ -43,9 +55,18 @@ target_link_libraries(vintage_story PRIVATE epan) # Include the zstd library. Wireshark will have builtin support for zstd # through the `tvb_child_uncompress_zstd` function in the 4.2 release, but it # is not in a stable release yet. -target_link_libraries(vintage_story PRIVATE ${ZSTD_LIBRARIES}) -target_include_directories(vintage_story PRIVATE ${ZSTD_INCLUDE_DIRS}) -target_compile_options(vintage_story PRIVATE ${ZSTD_CFLAGS_OTHER}) +if(PkgConfig_FOUND) + # libzstd was found with the pkgconfig program (typically true on Linux). + # pkgconfig sets ZSTD_LIBRARIES, ZSTD_INCLUDE_DIRS, and ZSTD_CFLAGS_OTHER. + target_link_libraries(vintage_story PRIVATE ${ZSTD_LIBRARIES}) + target_include_directories(vintage_story PRIVATE ${ZSTD_INCLUDE_DIRS}) + target_compile_options(vintage_story PRIVATE ${ZSTD_CFLAGS_OTHER}) +else() + # libzstd was found through its cmake package (typically true on Windows). + # Linking against the cmake library name will automatically add the include + # directories to the build path. + target_link_libraries(vintage_story PRIVATE zstd::libzstd_shared) +endif() # This is an epan plugin. So install into ${CMAKE_INSTALL_LIBDIR}/epan, so that # Wireshark loads it. If it were installed directly into the versioned plugins diff --git a/README.md b/README.md index bc1a87a..b97fe8d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ## Supported features -Currently only Linux is supported. +Currently only Linux and Windows are supported. Connections to multiplayer servers use TCP, which Wireshark can capture and decode. It does not work in single player mode. Single player mode uses a dummy connection that does not use TCP, such that Wireshark cannot see its traffic. @@ -15,14 +15,14 @@ Only the outer protobuf is parsed. Some protobufs have inner fields which are se Wireshark has some standard named folders. The actual location of these folders vary in different distros. The exact locations can be found in the folders tab of the About Wireshark dialog in the Help menu. Below is an example from Fedora 38. ![screenshot](https://raw.githubusercontent.com/bluelightning32/vs-protocol/main/doc/wireshark_folders.png) -The final files consist of a shared library plugin (`libvintage_story.so`) and a Protocol Buffer definition (`vintage_story.proto`). These files may either be installed in a global location for all users (requires root access), or in a directory for one user (does not require root access). +The final files consist of a shared library plugin (`libvintage_story.so` on Linux or `vintage_story.dll` on Windows) and a Protocol Buffer definition (`vintage_story.proto`). These files may either be installed in a global location for all users (requires root/admin access), or in a directory for one user (does not require root access). * Global installation - * Install `libvintage_story.so` in the "Global Plugins" folder (typically /usr/lib64/wireshark/plugins/4.0). - * Install `vintage_story.proto` in the "Global configuration" folder (typically /usr/share/wireshark). + * Install `libvintage_story.so` (Linux) or `vintage_story.dll` (Windows) in the epan subfolder of the "Global Plugins" folder (typically /usr/lib64/wireshark/plugins/4.0/epan on Linux or "C:\Program Files\Wireshark\plugins\4.0\epan" on Windows). + * Install `vintage_story.proto` in the protobuf subfolder "Global configuration" folder (typically /usr/share/wireshark/protobuf on Linux or "C:\Program Files\Wireshark\protobuf" on Windows). * User installation - * Install `libvintage_story.so` in the "Personal Plugins" folder (typically ~/.local/lib/wireshark/plugins/4.0). Create the folder if it does not exist yet. - * Install `vintage_story.proto` in the "Global configuration" folder (typically ~/.config/wireshark). Create the folder if it does not exist yet. + * Install `libvintage_story.so` (Linux) or `vintage_story.dll` (Windows) in the "Personal Plugins" folder (typically ~/.local/lib/wireshark/plugins/4.0 on Linux or "C:\Users\username\AppData\Roaming\Wireshark\plugins\4.0" on Windows). Create the folder if it does not exist yet. + * Install `vintage_story.proto` in the protobuf subfolder of the "Global configuration" folder (typically ~/.config/wireshark/protobuf on Linux or "C:\Users\username\AppData\Roaming\Wireshark\protobuf" on Windows). Create the folder if it does not exist yet. If it worked, the plugin will be listed on the Plugins tab of the About dialog. ![screenshot](https://raw.githubusercontent.com/bluelightning32/vs-protocol/main/doc/wireshark_plugins.png) @@ -42,7 +42,7 @@ The setting can be found in the Wireshark Preferences dialog, under the 'Protoco Those very large packets add more Protobuf fields to the tree than Wireshark supports by default. This shows up as a warning 'Adding protobuf.field.number would put more than xxx items in the tree -- possible infinite loop' in the protocol tree. This limit can be increased through the gui.max\_tree\_items preference, but doing so does seem to make the GUI less responsive on the large packets. ![screenshot](https://raw.githubusercontent.com/bluelightning32/vs-protocol/main/doc/max_tree_elements.png) -## Building +## Building on Linux Building requires the following to be installed: * A C++ compiler @@ -71,6 +71,36 @@ Finally build the dissector and install it. make install ``` +## Building on Windows + +Before the dissector can be built on Windows, Wireshark must be built from source to obtain the lib and header files. Those files are not available through any of the Wireshark precompiled installers. The official [Wireshark build directions](https://www.wireshark.org/docs/wsdg_html_chunked/ChSetupWindows) have all of the details, but the major points are summarized below. Note that it is not necessary to install the Qt dependency called out in the official directions. However, winflexbison3 does need to be installed in order to build Wireshark. + +Install Visual Studio. The free community edition is sufficient. Within the Visual Studio installer, make sure the install cmake option is selected. + +Download and unpack the Wireshark source code. + +Start "x64 Native Tools Command Prompt for VS 2022" from the start menu. Create a temporary directory for Wireshark to download more build dependencies into ("development" is used below). The `-DBUILD_wireshark=off` option tells Wireshark to skip building the GUI, because the GUI is not necesary for building the plugin. This way Qt does not need to be installed to build the rest of Wireshark. +``` +mkdir %USERPROFILE%\development +set WIRESHARK_BASE_DIR=%USERPROFILE%\development +mkdir build +cd build +cmake -G "Visual Studio 17 2022" -A x64 ..\ -DBUILD_wireshark=off +cmake --build . --prefix %USERPROFILE%\development\wireshark_install --config Release +cmake --install . --prefix %USERPROFILE%\development\wireshark_install --config Release +``` + +Download the vs-protocol source code and go to that directory inside the native tools command prompt. The `-DCMAKE_PREFIX_PATH` option tells CMake how to find dependencies. It needs to point to both the libraries build by Wireshark, and to the zstd dependency that was downloaded during the Wireshark build. +``` +mkdir build +cd build +cmake ..\ -DCMAKE_PREFIX_PATH=%USERPROFILE%\development\wireshark_install;%USERPROFILE%\development\wireshark-win64-libs-4.0\zstd-1.5.2-1-win64ws\installed\x64-windows +cmake --build . --config Release +cmake --install . --config Release --prefix . +``` + +The files (`vintage_story.dll` and `vintage_story.proto`) are installed in the wrong location. Copy them to the correct locations. As described in the earlier installation directories section. + ## Notes on dependencies Vintage Story uses ZStandard to compress large packets. So the dissector must be able to decompress ZStandard. Wireshark 4.2 has builtin support for zstd, but 4.2 is still labelled as a developer release. The stable version, 4.0, does not export a ZStandard function to plugins. So instead vs-protocol links directly against libzstd. diff --git a/dissector.cpp b/dissector.cpp index 0d2aaf0..011fdd1 100644 --- a/dissector.cpp +++ b/dissector.cpp @@ -1,6 +1,7 @@ #include // for std::max #include #include // for std::size +#include // for std::numeric_limits #include // for std::ostringstream #include @@ -38,11 +39,11 @@ void SetInfoColumn(packet_info *pinfo, col_add_str(pinfo->cinfo, COL_INFO, joined.c_str()); } -long DecodeVarInt(tvbuff_t *payload, size_t &offset) { +long DecodeVarInt(tvbuff_t *payload, gint &offset) { long val = 0; bool more = true; int shift = 0; - while (offset < tvb_captured_length(payload) && more) { + while (tvb_captured_length_remaining(payload, offset) > 0 && more) { unsigned char read = tvb_get_guint8(payload, offset); val |= (read & 0x7F) << shift; more = read & 0x80; @@ -52,10 +53,16 @@ long DecodeVarInt(tvbuff_t *payload, size_t &offset) { return val; } -size_t GetProtoBufEnd(tvbuff_t *payload) { - size_t offset = 0; - while (offset < tvb_captured_length(payload)) { - size_t prev_offset = offset; +gint GetProtoBufEnd(tvbuff_t *payload) { + if (tvb_captured_length(payload) > + static_cast(std::numeric_limits::max())) { + std::cerr << "Captured packet size, " << tvb_captured_length(payload) + << ", is beyond gint range." << std::endl; + return 0; + } + gint offset = 0; + while (tvb_captured_length_remaining(payload, offset) > 0) { + gint prev_offset = offset; long tag = DecodeVarInt(payload, offset); if (tag == 0) { offset = prev_offset; @@ -73,13 +80,13 @@ size_t GetProtoBufEnd(tvbuff_t *payload) { offset += DecodeVarInt(payload, offset); } } - return std::min(offset, static_cast(tvb_captured_length(payload))); + return std::min(offset, static_cast(tvb_captured_length(payload))); } int FindVarInt(tvbuff_t *payload, int find_tag, bool &found) { found = false; - size_t offset = 0; - while (offset < tvb_captured_length(payload)) { + gint offset = 0; + while (tvb_captured_length_remaining(payload, offset) > 0) { long tag = DecodeVarInt(payload, offset); if (tag == 0) { return -1; @@ -113,7 +120,7 @@ std::string DecodePacket(bool is_server, tvbuff_t *payload, // The serializer pads short messages with nulls to a length that's a // multiple of 16. The protobuf dissector prints errors if they are // passed to it. So use some heuristics to find the end of the message. - size_t payload_end = GetProtoBufEnd(payload); + gint payload_end = GetProtoBufEnd(payload); tvbuff_t *unpadded = tvb_new_subset_length(payload, 0, payload_end); call_dissector_with_data( protobuf_handle, unpadded, pinfo, tree, @@ -140,7 +147,7 @@ std::string DecodePacket(bool is_server, tvbuff_t *payload, out << "id=" << id; } } else { - size_t offset = 0; + gint offset = 0; long first_tag = DecodeVarInt(payload, offset); if (first_tag == 10) { out << "ServerIdentification"; @@ -153,7 +160,7 @@ std::string DecodePacket(bool is_server, tvbuff_t *payload, // The client serializer pads short messages with nulls up to length 16. // The protobuf dissector prints errors if they are passed to it. So use // some heuristics to find the end of the message. - size_t payload_end = GetProtoBufEnd(payload); + gint payload_end = GetProtoBufEnd(payload); tvbuff_t *unpadded = tvb_new_subset_length(payload, 0, payload_end); call_dissector_with_data( protobuf_handle, unpadded, pinfo, tree, @@ -179,7 +186,7 @@ std::string DecodePacket(bool is_server, tvbuff_t *payload, out << "id=" << id; } } else { - size_t offset = 0; + gint offset = 0; long first_tag = DecodeVarInt(payload, offset); if (first_tag == 18) { out << "ClientIdentification"; @@ -191,7 +198,7 @@ std::string DecodePacket(bool is_server, tvbuff_t *payload, return out.str(); } -int Dissect(tvbuff_t *buffer, packet_info *pinfo, proto_tree *tree, +gint Dissect(tvbuff_t *buffer, packet_info *pinfo, proto_tree *tree, void* /*data*/) { if (tvb_captured_length(buffer) < tvb_reported_length(buffer)) { // Don't attempt to decode packets that were truncated because the snapshot @@ -212,9 +219,9 @@ int Dissect(tvbuff_t *buffer, packet_info *pinfo, proto_tree *tree, bool is_server = (pinfo->srcport == pinfo->match_uint); std::vector packet_names; - size_t offset = 0; - while (offset < tvb_captured_length(buffer)) { - int remaining = tvb_reported_length_remaining(buffer, offset); + gint offset = 0; + while (tvb_captured_length_remaining(buffer, offset) > 0) { + gint remaining = tvb_reported_length_remaining(buffer, offset); if (remaining < 4) { // The packet_length field is 4 bytes. If there are fewer than 4 bytes // remaining, that means the packet was truncated. Setting @@ -293,10 +300,17 @@ int Dissect(tvbuff_t *buffer, packet_info *pinfo, proto_tree *tree, size_t final_decomp_size = ZSTD_decompress(decomp_buffer, decomp_size, compressed_payload, packet_length); + if (final_decomp_size > std::numeric_limits::max()) { + std::cerr << "Decompressed size, " << final_decomp_size + << ", is too large to fit in a Wireshark buffer." + << std::endl; + return 0; + } // Wrap the decompressed data in a tvbuff_t. payload = tvb_new_child_real_data(buffer, decomp_buffer, - final_decomp_size, final_decomp_size); + static_cast(final_decomp_size), + static_cast(final_decomp_size)); add_new_data_source(pinfo, payload, "Decompressed payload"); } packet_names.push_back(DecodePacket(is_server, payload, pinfo, subtree)); @@ -316,54 +330,58 @@ void ProtoRegiser() { static hf_register_info fields[] = { { &hf_compressed, { - .name = "compressed", - .abbrev = "vs.compressed", - .type = FT_BOOLEAN, - .display = 32, - .strings = nullptr, - .bitmask = 0x80000000, - .blurb = nullptr, + // Visual Studio complains about mixing designated initializers and + // non-designated initializers (from HFILL). So avoid using designated + // initializers. + /*.name =*/ "compressed", + /*.abbrev =*/ "vs.compressed", + /*.type =*/ FT_BOOLEAN, + /*.display =*/ 32, + /*.strings =*/ nullptr, + /*.bitmask =*/ 0x80000000, + /*.blurb =*/ nullptr, HFILL } }, { &hf_packet_length, { - .name = "packet length", - .abbrev = "vs.length", - .type = FT_UINT32, - .display = BASE_DEC, - .strings = nullptr, - .bitmask = 0x7fffffff, - .blurb = nullptr, + /*.name =*/ "packet length", + /*.abbrev =*/ "vs.length", + /*.type =*/ FT_UINT32, + /*.display =*/ BASE_DEC, + /*.strings =*/ nullptr, + /*.bitmask =*/ 0x7fffffff, + /*.blurb =*/ nullptr, HFILL } }, { &hf_compressed_payload, { - .name = "compressed payload", - .abbrev = "vs.compressed_payload", - .type = FT_BYTES, - .display = BASE_NONE, - .strings = nullptr, - .bitmask = 0, - .blurb = nullptr, + /*.name =*/ "compressed payload", + /*.abbrev =*/ "vs.compressed_payload", + /*.type =*/ FT_BYTES, + /*.display =*/ BASE_NONE, + /*.strings =*/ nullptr, + /*.bitmask =*/ 0, + /*.blurb =*/ nullptr, HFILL } }, { &hf_padding, { - .name = "0 padding", - .abbrev = "vs.zero_padding", - .type = FT_BYTES, - .display = BASE_NONE, - .strings = nullptr, - .bitmask = 0, - .blurb = nullptr, + /*.name =*/ "0 padding", + /*.abbrev =*/ "vs.zero_padding", + /*.type =*/ FT_BYTES, + /*.display =*/ BASE_NONE, + /*.strings =*/ nullptr, + /*.bitmask =*/ 0, + /*.blurb =*/ nullptr, HFILL } }, }; - proto_register_field_array(proto_id, fields, std::size(fields)); + proto_register_field_array(proto_id, fields, + static_cast(std::size(fields))); // This does not need to be static. `proto_register_subtree_array` only uses // it as an output array. @@ -371,7 +389,8 @@ void ProtoRegiser() { &protocol_ett, &compressed_payload_ett, }; - proto_register_subtree_array(subtree_expansions, std::size(subtree_expansions)); + proto_register_subtree_array(subtree_expansions, + static_cast(std::size(subtree_expansions))); } void ProtoRegHandoff() { @@ -381,16 +400,20 @@ void ProtoRegHandoff() { } // namespace -extern "C" { +#if defined(_WIN32) || defined __CYGWIN__ +#define EXPORT extern "C" __declspec(dllexport) +#else +#define EXPORT extern "C" __attribute__((visibility("default"))) +#endif // These must be declared extern so that the symbols are exported in the .so // library, so that Wireshark can find them. Otherwise, the plugin will fail to // load with a 'has no plugin_version symbol' error. -extern const char plugin_version[] = "1.0.0"; -extern const int plugin_want_major = WIRESHARK_VERSION_MAJOR; -extern const int plugin_want_minor = WIRESHARK_VERSION_MINOR; +EXPORT const char plugin_version[] = "1.0.1"; +EXPORT const int plugin_want_major = WIRESHARK_VERSION_MAJOR; +EXPORT const int plugin_want_minor = WIRESHARK_VERSION_MINOR; -void plugin_register() +EXPORT void plugin_register() { // This must be static. Wireshark references it after this function returns. static proto_plugin plug; @@ -400,6 +423,4 @@ void plugin_register() proto_register_plugin(&plug); } -} // end extern "C" - } // namespace vintage_story