summaryrefslogtreecommitdiffstats
path: root/extcap
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:34:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:34:10 +0000
commite4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc (patch)
tree68cb5ef9081156392f1dd62a00c6ccc1451b93df /extcap
parentInitial commit. (diff)
downloadwireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.tar.xz
wireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.zip
Adding upstream version 4.2.2.upstream/4.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'extcap')
-rw-r--r--extcap/.editorconfig9
-rw-r--r--extcap/CMakeLists.txt387
-rw-r--r--extcap/androiddump.c2801
-rw-r--r--extcap/ciscodump.c2512
-rw-r--r--extcap/dpauxmon.c589
-rw-r--r--extcap/dpauxmon_user.h59
-rw-r--r--extcap/etl.c784
-rw-r--r--extcap/etl.h48
-rw-r--r--extcap/etw_message.c419
-rw-r--r--extcap/etw_message.h59
-rw-r--r--extcap/etw_ndiscap.c709
-rw-r--r--extcap/etwdump.c312
-rw-r--r--extcap/extcap-base.c427
-rw-r--r--extcap/extcap-base.h129
-rw-r--r--extcap/falcodump.cpp1016
-rw-r--r--extcap/randpktdump.c368
-rw-r--r--extcap/sdjournal.c472
-rw-r--r--extcap/ssh-base.c226
-rw-r--r--extcap/ssh-base.h85
-rw-r--r--extcap/sshdump.c694
-rw-r--r--extcap/udpdump.c490
-rw-r--r--extcap/wifidump.c747
22 files changed, 13342 insertions, 0 deletions
diff --git a/extcap/.editorconfig b/extcap/.editorconfig
new file mode 100644
index 0000000..6f7bf68
--- /dev/null
+++ b/extcap/.editorconfig
@@ -0,0 +1,9 @@
+#
+# Editor configuration
+#
+# https://editorconfig.org
+#
+
+[{ciscodump,randpktdump,ssh-base,sshdump,udpdump,wifidump}.[ch]]
+indent_style = tab
+indent_size = tab
diff --git a/extcap/CMakeLists.txt b/extcap/CMakeLists.txt
new file mode 100644
index 0000000..48c4516
--- /dev/null
+++ b/extcap/CMakeLists.txt
@@ -0,0 +1,387 @@
+# CMakeLists.txt
+#
+# Wireshark - Network traffic analyzer
+# By Gerald Combs <gerald@wireshark.org>
+# Copyright 1998 Gerald Combs
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+
+if(EXTCAP_ANDROIDDUMP_LIBPCAP)
+ set(ANDROIDDUMP_USE_LIBPCAP 1)
+endif()
+
+if(LIBSSH_FOUND)
+ include(CMakePushCheckState)
+ cmake_push_check_state()
+ set(CMAKE_REQUIRED_INCLUDES ${LIBSSH_INCLUDE_DIRS})
+ set(CMAKE_REQUIRED_LIBRARIES ${LIBSSH_LIBRARIES})
+ check_function_exists(ssh_userauth_agent LIBSSH_USERAUTH_AGENT_FOUND)
+ cmake_pop_check_state()
+endif()
+
+# Ensure "run/extcap" exists
+# add_custom_command(OUTPUT "${DATAFILE_DIR}/extcap"
+# COMMAND ${CMAKE_COMMAND} -E make_directory
+# "${DATAFILE_DIR}/extcap"
+# )
+# list(APPEND copy_data_files_depends "${DATAFILE_DIR}/extcap")
+
+
+macro(set_extcap_executable_properties _executable)
+ set_target_properties(${_executable} PROPERTIES
+ LINK_FLAGS "${WS_LINK_FLAGS}"
+ FOLDER "Executables/Extcaps"
+ INSTALL_RPATH "${EXTCAP_INSTALL_RPATH}"
+ )
+ if(MSVC)
+ set_target_properties(${_executable} PROPERTIES LINK_FLAGS_DEBUG "${WS_MSVC_DEBUG_LINK_FLAGS}")
+ endif()
+
+ set(PROGLIST ${PROGLIST} ${_executable})
+
+ if(CMAKE_CONFIGURATION_TYPES)
+ set_target_properties(${_executable} PROPERTIES
+ RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/run/extcap
+ RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/run/Debug/extcap
+ RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/run/Release/extcap
+ RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_BINARY_DIR}/run/MinSizeRel/extcap
+ RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}/run/RelWithDebInfo/extcap
+ )
+ else()
+ set_target_properties(${_executable} PROPERTIES
+ RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/run/extcap
+ )
+ if(ENABLE_APPLICATION_BUNDLE)
+ if(NOT CMAKE_CFG_INTDIR STREQUAL ".")
+ # Xcode
+ set_target_properties(${_executable} PROPERTIES
+ RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/run/$<CONFIG>/Wireshark.app/Contents/MacOS/extcap
+ )
+ else()
+ set_target_properties(${_executable} PROPERTIES
+ RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/run/Wireshark.app/Contents/MacOS/extcap
+ )
+ # Create a convenience link from run/<name> to its respective
+ # target in the application bundle.
+ add_custom_target(${_executable}-symlink
+ COMMAND ln -s -f
+ Wireshark.app/Contents/MacOS/extcap/${_executable}
+ ${CMAKE_BINARY_DIR}/run/${_executable}
+ )
+ add_dependencies(${_executable} ${_executable}-symlink)
+ endif()
+ endif()
+ endif()
+
+ if (MINGW)
+ set_target_properties(${_executable} PROPERTIES
+ LINK_OPTIONS -municode
+ )
+ endif()
+endmacro()
+
+macro(set_extlog_executable_properties _executable)
+ set_target_properties(${_executable} PROPERTIES
+ RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/run/extcap
+ )
+ if(ENABLE_APPLICATION_BUNDLE)
+ if(NOT CMAKE_CFG_INTDIR STREQUAL ".")
+ # Xcode
+ set_target_properties(${_executable} PROPERTIES
+ RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/run/$<CONFIG>/Logray.app/Contents/MacOS/extcap
+ )
+ else()
+ set_target_properties(${_executable} PROPERTIES
+ RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/run/Logray.app/Contents/MacOS/extcap
+ )
+ # Create a convenience link from run/<name> to its respective
+ # target in the application bundle.
+ add_custom_target(${_executable}-symlink
+ COMMAND ln -s -f
+ Logray.app/Contents/MacOS/extcap/${_executable}
+ ${CMAKE_BINARY_DIR}/run/${_executable}
+ )
+ add_dependencies(${_executable} ${_executable}-symlink)
+ endif()
+ endif()
+endmacro()
+
+add_custom_target(extcaps)
+
+add_library(extcap-base OBJECT extcap-base.c)
+if(LIBSSH_FOUND)
+ add_library(ssh-base OBJECT ssh-base.c)
+ target_include_directories(ssh-base SYSTEM
+ PRIVATE
+ ${LIBSSH_INCLUDE_DIRS}
+ )
+endif()
+
+if(BUILD_androiddump)
+ if(EXTCAP_ANDROIDDUMP_LIBPCAP)
+ if(HAVE_LIBPCAP)
+ set(androiddump_LIBS
+ ui
+ wiretap
+ wsutil
+ ${WIN_WS2_32_LIBRARY}
+ $<$<BOOL:${PCAP_FOUND}>:pcap::pcap>
+ )
+ else()
+ message(FATAL_ERROR "You are trying to build androiddump with libpcap but do not have it")
+ endif()
+ else()
+ set(androiddump_LIBS
+ ui
+ wiretap
+ wsutil
+ ${CMAKE_DL_LIBS}
+ ${WIN_WS2_32_LIBRARY}
+ )
+ endif()
+ set(androiddump_FILES
+ $<TARGET_OBJECTS:cli_main>
+ $<TARGET_OBJECTS:extcap-base>
+ androiddump.c
+ )
+
+ set_executable_resources(androiddump "Androiddump")
+ add_executable(androiddump ${androiddump_FILES})
+ set_extcap_executable_properties(androiddump)
+ target_link_libraries(androiddump ${androiddump_LIBS})
+ install(TARGETS androiddump RUNTIME DESTINATION ${EXTCAP_INSTALL_LIBDIR})
+ add_dependencies(extcaps androiddump)
+endif()
+
+if(BUILD_sshdump AND LIBSSH_FOUND)
+ set(sshdump_LIBS
+ wsutil
+ ${CMAKE_DL_LIBS}
+ ${WIN_WS2_32_LIBRARY}
+ ${LIBSSH_LIBRARIES}
+ )
+ set(sshdump_FILES
+ $<TARGET_OBJECTS:cli_main>
+ $<TARGET_OBJECTS:extcap-base>
+ $<TARGET_OBJECTS:ssh-base>
+ sshdump.c
+ )
+
+ set_executable_resources(sshdump "Sshdump")
+ add_executable(sshdump ${sshdump_FILES})
+ set_extcap_executable_properties(sshdump)
+ target_link_libraries(sshdump ${sshdump_LIBS})
+ target_include_directories(sshdump SYSTEM PRIVATE ${LIBSSH_INCLUDE_DIRS})
+ install(TARGETS sshdump RUNTIME DESTINATION ${EXTCAP_INSTALL_LIBDIR})
+ add_dependencies(extcaps sshdump)
+elseif (BUILD_sshdump)
+ #message( WARNING "Cannot find libssh, cannot build sshdump" )
+endif()
+
+if(BUILD_ciscodump AND LIBSSH_FOUND)
+ set(ciscodump_LIBS
+ writecap
+ wsutil
+ ${CMAKE_DL_LIBS}
+ ${WIN_WS2_32_LIBRARY}
+ ${LIBSSH_LIBRARIES}
+ )
+ set(ciscodump_FILES
+ $<TARGET_OBJECTS:cli_main>
+ $<TARGET_OBJECTS:extcap-base>
+ $<TARGET_OBJECTS:ssh-base>
+ ciscodump.c
+ )
+
+ set_executable_resources(ciscodump "Ciscodump")
+ add_executable(ciscodump ${ciscodump_FILES})
+ set_extcap_executable_properties(ciscodump)
+ target_link_libraries(ciscodump ${ciscodump_LIBS})
+ target_include_directories(ciscodump SYSTEM PRIVATE ${LIBSSH_INCLUDE_DIRS})
+ install(TARGETS ciscodump RUNTIME DESTINATION ${EXTCAP_INSTALL_LIBDIR})
+ add_dependencies(extcaps ciscodump)
+elseif (BUILD_ciscodump)
+ #message( WARNING "Cannot find libssh, cannot build ciscodump" )
+endif()
+
+if(BUILD_wifidump AND LIBSSH_FOUND)
+ set(wifidump_LIBS
+ writecap
+ wsutil
+ ${CMAKE_DL_LIBS}
+ ${WIN_WS2_32_LIBRARY}
+ ${LIBSSH_LIBRARIES}
+ )
+ set(wifidump_FILES
+ $<TARGET_OBJECTS:cli_main>
+ $<TARGET_OBJECTS:extcap-base>
+ $<TARGET_OBJECTS:ssh-base>
+ wifidump.c
+ )
+
+ set_executable_resources(wifidump "Wifidump")
+ add_executable(wifidump ${wifidump_FILES})
+ set_extcap_executable_properties(wifidump)
+ target_link_libraries(wifidump ${wifidump_LIBS})
+ target_include_directories(wifidump SYSTEM PRIVATE ${LIBSSH_INCLUDE_DIRS})
+ install(TARGETS wifidump RUNTIME DESTINATION ${EXTCAP_INSTALL_LIBDIR})
+ add_dependencies(extcaps wifidump)
+elseif (BUILD_wifidump)
+ #message( WARNING "Cannot find libssh, cannot build wifidump" )
+endif()
+
+if(BUILD_dpauxmon AND HAVE_LIBNL3)
+ set(dpauxmon_LIBS
+ wsutil
+ writecap
+ ${GLIB2_LIBRARIES}
+ ${CMAKE_DL_LIBS}
+ ${NL_LIBRARIES}
+ )
+ set(dpauxmon_FILES
+ $<TARGET_OBJECTS:extcap-base>
+ dpauxmon.c
+ )
+
+ set_executable_resources(dpauxmon "dpauxmon")
+ add_executable(dpauxmon ${dpauxmon_FILES})
+ set_extcap_executable_properties(dpauxmon)
+ target_link_libraries(dpauxmon ${dpauxmon_LIBS})
+ target_include_directories(dpauxmon SYSTEM PRIVATE ${NL_INCLUDE_DIRS})
+ install(TARGETS dpauxmon RUNTIME DESTINATION ${EXTCAP_INSTALL_LIBDIR})
+ add_dependencies(extcaps dpauxmon)
+elseif (BUILD_dpauxmon)
+ #message( WARNING "Cannot find libnl3, cannot build dpauxmon" )
+endif()
+
+if(BUILD_udpdump)
+ set(udpdump_LIBS
+ wsutil
+ ${CMAKE_DL_LIBS}
+ ${WIN_WS2_32_LIBRARY}
+ wsutil
+ writecap
+ )
+ set(udpdump_FILES
+ $<TARGET_OBJECTS:cli_main>
+ $<TARGET_OBJECTS:extcap-base>
+ udpdump.c
+ )
+
+ set_executable_resources(udpdump "udpdump")
+ add_executable(udpdump ${udpdump_FILES})
+ set_extcap_executable_properties(udpdump)
+ target_link_libraries(udpdump ${udpdump_LIBS})
+ install(TARGETS udpdump RUNTIME DESTINATION ${EXTCAP_INSTALL_LIBDIR})
+ add_dependencies(extcaps udpdump)
+endif()
+
+if(BUILD_randpktdump)
+ set(randpktdump_LIBS
+ randpkt_core
+ wiretap
+ wsutil
+ ${CMAKE_DL_LIBS}
+ ${WIN_WS2_32_LIBRARY}
+ )
+ set(randpktdump_FILES
+ $<TARGET_OBJECTS:cli_main>
+ $<TARGET_OBJECTS:extcap-base>
+ randpktdump.c
+ )
+
+ set_executable_resources(randpktdump "randpktdump")
+ add_executable(randpktdump ${randpktdump_FILES})
+ set_extcap_executable_properties(randpktdump)
+ target_link_libraries(randpktdump ${randpktdump_LIBS})
+ install(TARGETS randpktdump RUNTIME DESTINATION ${EXTCAP_INSTALL_LIBDIR})
+ add_dependencies(extcaps randpktdump)
+endif()
+
+
+if(BUILD_etwdump AND WIN32)
+ set(etwdump_LIBS
+ wiretap
+ wsutil
+ tdh
+ wevtapi
+ rpcrt4
+ ${CMAKE_DL_LIBS}
+ ${WIN_WS2_32_LIBRARY}
+ )
+ set(etwdump_FILES
+ $<TARGET_OBJECTS:cli_main>
+ $<TARGET_OBJECTS:extcap-base>
+ etwdump.c
+ etl.c
+ etw_message.c
+ etw_ndiscap.c
+ )
+
+ set_executable_resources(etwdump "etwdump")
+ add_executable(etwdump ${etwdump_FILES})
+ set_extcap_executable_properties(etwdump)
+ target_link_libraries(etwdump ${etwdump_LIBS})
+ install(TARGETS etwdump RUNTIME DESTINATION ${EXTCAP_INSTALL_LIBDIR})
+ add_dependencies(extcaps etwdump)
+endif()
+
+if(BUILD_sdjournal AND SYSTEMD_FOUND)
+ set(sdjournal_LIBS
+ writecap
+ wsutil
+ ${CMAKE_DL_LIBS}
+ ${SYSTEMD_LIBRARIES}
+ )
+ set(sdjournal_FILES
+ $<TARGET_OBJECTS:extcap-base>
+ sdjournal.c
+ )
+
+ set_executable_resources(sdjournal "sdjournal")
+ add_executable(sdjournal ${sdjournal_FILES})
+ set_extcap_executable_properties(sdjournal)
+ target_link_libraries(sdjournal ${sdjournal_LIBS})
+ target_include_directories(sdjournal SYSTEM PRIVATE ${SYSTEMD_INCLUDE_DIRS})
+ install(TARGETS sdjournal RUNTIME DESTINATION ${EXTCAP_INSTALL_LIBDIR})
+ add_dependencies(extcaps sdjournal)
+endif()
+
+if(BUILD_falcodump AND SINSP_FOUND)
+ set(falcodump_LIBS
+ wsutil
+ ${SINSP_LINK_LIBRARIES}
+ ${CMAKE_DL_LIBS}
+ ${GCRYPT_LIBRARIES}
+ )
+ set(falcodump_FILES
+ $<TARGET_OBJECTS:extcap-base>
+ falcodump.cpp
+ )
+
+ set_executable_resources(falcodump "falcodump")
+ add_executable(falcodump ${falcodump_FILES})
+ set_extlog_executable_properties(falcodump)
+ target_link_libraries(falcodump ${falcodump_LIBS})
+ target_include_directories(falcodump SYSTEM PRIVATE ${SINSP_INCLUDE_DIRS})
+ install(TARGETS falcodump RUNTIME DESTINATION ${EXTCAP_INSTALL_LIBDIR})
+ add_dependencies(extcaps falcodump)
+
+ # XXX Hack; We need to fix this in falcosecurity-libs.
+ target_compile_definitions(falcodump PRIVATE HAVE_STRLCPY=1)
+
+endif()
+
+#
+# Editor modelines - https://www.wireshark.org/tools/modelines.html
+#
+# Local variables:
+# c-basic-offset: 8
+# tab-width: 8
+# indent-tabs-mode: t
+# End:
+#
+# vi: set shiftwidth=8 tabstop=8 noexpandtab:
+# :indentSize=8:tabSize=8:noTabs=false:
+#
diff --git a/extcap/androiddump.c b/extcap/androiddump.c
new file mode 100644
index 0000000..7ba573a
--- /dev/null
+++ b/extcap/androiddump.c
@@ -0,0 +1,2801 @@
+/* androiddump.c
+ * androiddump is extcap tool used to capture Android specific stuff
+ *
+ * Copyright 2015, Michal Labedzki for Tieto Corporation
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "config.h"
+#define WS_LOG_DOMAIN "androiddump"
+
+#include "extcap-base.h"
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <wsutil/strtoi.h>
+#include <wsutil/filesystem.h>
+#include <wsutil/privileges.h>
+#include <wsutil/report_message.h>
+#include <wsutil/please_report_bug.h>
+#include <wsutil/wslog.h>
+#include <wsutil/cmdarg_err.h>
+#include <wsutil/inet_addr.h>
+#include <wsutil/exported_pdu_tlvs.h>
+
+#include "ui/failure_message.h"
+
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+
+#ifdef HAVE_UNISTD_H
+ #include <unistd.h>
+#endif
+
+#ifdef HAVE_SYS_SOCKET_H
+ #include <sys/socket.h>
+#endif
+
+#ifdef HAVE_ARPA_INET_H
+ #include <arpa/inet.h>
+#endif
+
+#ifdef HAVE_SYS_TIME_H
+ #include <sys/time.h>
+#endif
+
+/* Configuration options */
+/* #define ANDROIDDUMP_USE_LIBPCAP */
+
+#define PCAP_GLOBAL_HEADER_LENGTH 24
+#define PCAP_RECORD_HEADER_LENGTH 16
+
+#ifdef ANDROIDDUMP_USE_LIBPCAP
+ #include <pcap.h>
+ #include <pcap-bpf.h>
+ #include <pcap/bluetooth.h>
+
+ #ifndef DLT_BLUETOOTH_H4_WITH_PHDR
+ #define DLT_BLUETOOTH_H4_WITH_PHDR 201
+ #endif
+
+ #ifndef DLT_WIRESHARK_UPPER_PDU
+ #define DLT_WIRESHARK_UPPER_PDU 252
+ #endif
+
+ #ifndef PCAP_TSTAMP_PRECISION_MICRO
+ #define PCAP_TSTAMP_PRECISION_MICRO 0
+ #endif
+
+ #ifndef PCAP_TSTAMP_PRECISION_NANO
+ #define PCAP_TSTAMP_PRECISION_NANO 1
+ #endif
+#else
+ #include "wiretap/wtap.h"
+ #include "wiretap/pcap-encap.h"
+#endif
+
+#include <cli_main.h>
+
+#ifdef ANDROIDDUMP_USE_LIBPCAP
+ #define EXTCAP_ENCAP_BLUETOOTH_H4_WITH_PHDR DLT_BLUETOOTH_H4_WITH_PHDR
+ #define EXTCAP_ENCAP_WIRESHARK_UPPER_PDU DLT_WIRESHARK_UPPER_PDU
+ #define EXTCAP_ENCAP_ETHERNET DLT_EN10MB
+ #define EXTCAP_ENCAP_LINUX_SLL DLT_LINUX_SLL
+ #define EXTCAP_ENCAP_IEEE802_11_RADIO DLT_IEEE802_11_RADIO
+ #define EXTCAP_ENCAP_NETLINK DLT_NETLINK
+#else
+ #define EXTCAP_ENCAP_BLUETOOTH_H4_WITH_PHDR WTAP_ENCAP_BLUETOOTH_H4_WITH_PHDR
+ #define EXTCAP_ENCAP_WIRESHARK_UPPER_PDU WTAP_ENCAP_WIRESHARK_UPPER_PDU
+ #define EXTCAP_ENCAP_ETHERNET WTAP_ENCAP_ETHERNET
+ #define EXTCAP_ENCAP_LINUX_SLL WTAP_ENCAP_SLL
+ #define EXTCAP_ENCAP_IEEE802_11_RADIO WTAP_ENCAP_IEEE_802_11_RADIOTAP
+ #define EXTCAP_ENCAP_NETLINK WTAP_ENCAP_NETLINK
+#endif
+
+#define INTERFACE_ANDROID_LOGCAT_MAIN "android-logcat-main"
+#define INTERFACE_ANDROID_LOGCAT_SYSTEM "android-logcat-system"
+#define INTERFACE_ANDROID_LOGCAT_RADIO "android-logcat-radio"
+#define INTERFACE_ANDROID_LOGCAT_EVENTS "android-logcat-events"
+#define INTERFACE_ANDROID_LOGCAT_TEXT_MAIN "android-logcat-text-main"
+#define INTERFACE_ANDROID_LOGCAT_TEXT_SYSTEM "android-logcat-text-system"
+#define INTERFACE_ANDROID_LOGCAT_TEXT_RADIO "android-logcat-text-radio"
+#define INTERFACE_ANDROID_LOGCAT_TEXT_EVENTS "android-logcat-text-events"
+#define INTERFACE_ANDROID_LOGCAT_TEXT_CRASH "android-logcat-text-crash"
+#define INTERFACE_ANDROID_BLUETOOTH_HCIDUMP "android-bluetooth-hcidump"
+#define INTERFACE_ANDROID_BLUETOOTH_EXTERNAL_PARSER "android-bluetooth-external-parser"
+#define INTERFACE_ANDROID_BLUETOOTH_BTSNOOP_NET "android-bluetooth-btsnoop-net"
+#define INTERFACE_ANDROID_TCPDUMP "android-tcpdump"
+#define INTERFACE_ANDROID_TCPDUMP_FORMAT INTERFACE_ANDROID_TCPDUMP "-%s"
+#define INTERFACE_ANDROID_TCPDUMP_SERIAL_FORMAT INTERFACE_ANDROID_TCPDUMP_FORMAT "-%s"
+
+#define ANDROIDDUMP_VERSION_MAJOR "1"
+#define ANDROIDDUMP_VERSION_MINOR "1"
+#define ANDROIDDUMP_VERSION_RELEASE "0"
+
+#define SERIAL_NUMBER_LENGTH_MAX 512
+#define MODEL_NAME_LENGTH_MAX 64
+
+#define PACKET_LENGTH 65535
+
+#define SOCKET_RW_TIMEOUT_MS 2000
+#define SOCKET_CONNECT_TIMEOUT_TRIES 10
+#define SOCKET_CONNECT_DELAY_US 1000 /* (1000us = 1ms) * SOCKET_CONNECT_TIMEOUT_TRIES (10) = 10ms worst-case */
+
+#define ADB_HEX4_FORMAT "%04zx"
+#define ADB_HEX4_LEN 4
+
+#define BTSNOOP_HDR_LEN 16
+
+enum exit_code {
+ EXIT_CODE_SUCCESS = 0,
+ EXIT_CODE_CANNOT_GET_INTERFACES_LIST = 1,
+ EXIT_CODE_UNKNOWN_ENCAPSULATION_WIRETAP,
+ EXIT_CODE_UNKNOWN_ENCAPSULATION_LIBPCAP,
+ EXIT_CODE_CANNOT_SAVE_WIRETAP_DUMP,
+ EXIT_CODE_CANNOT_SAVE_LIBPCAP_DUMP,
+ EXIT_CODE_NO_INTERFACE_SPECIFIED,
+ EXIT_CODE_INVALID_INTERFACE,
+ EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_1,
+ EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_2,
+ EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_3,
+ EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_4,
+ EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_5,
+ EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_6,
+ EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_7,
+ EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_8,
+ EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_9,
+ EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_10,
+ EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_11,
+ EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_12,
+ EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_13,
+ EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_14,
+ EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_15,
+ EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_16,
+ EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_17,
+ EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_18,
+ EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_19,
+ EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_20,
+ EXIT_CODE_ERROR_WHILE_SENDING_ADB_PACKET_1,
+ EXIT_CODE_ERROR_WHILE_SENDING_ADB_PACKET_2,
+ EXIT_CODE_ERROR_WHILE_SENDING_ADB_PACKET_3,
+ EXIT_CODE_ERROR_WHILE_SENDING_ADB_PACKET_4,
+ EXIT_CODE_ERROR_WHILE_RECEIVING_ADB_PACKET_STATUS,
+ EXIT_CODE_ERROR_WHILE_RECEIVING_ADB_PACKET_DATA,
+ EXIT_CODE_INVALID_SOCKET_INTERFACES_LIST,
+ EXIT_CODE_INVALID_SOCKET_1,
+ EXIT_CODE_INVALID_SOCKET_2,
+ EXIT_CODE_INVALID_SOCKET_3,
+ EXIT_CODE_INVALID_SOCKET_4,
+ EXIT_CODE_INVALID_SOCKET_5,
+ EXIT_CODE_INVALID_SOCKET_6,
+ EXIT_CODE_INVALID_SOCKET_7,
+ EXIT_CODE_INVALID_SOCKET_8,
+ EXIT_CODE_INVALID_SOCKET_9,
+ EXIT_CODE_INVALID_SOCKET_10,
+ EXIT_CODE_INVALID_SOCKET_11,
+ EXIT_CODE_GENERIC = -1
+};
+
+enum {
+ EXTCAP_BASE_OPTIONS_ENUM,
+ OPT_HELP,
+ OPT_VERSION,
+ OPT_CONFIG_ADB_SERVER_IP,
+ OPT_CONFIG_ADB_SERVER_TCP_PORT,
+ OPT_CONFIG_LOGCAT_TEXT,
+ OPT_CONFIG_LOGCAT_IGNORE_LOG_BUFFER,
+ OPT_CONFIG_LOGCAT_CUSTOM_OPTIONS,
+ OPT_CONFIG_BT_SERVER_TCP_PORT,
+ OPT_CONFIG_BT_FORWARD_SOCKET,
+ OPT_CONFIG_BT_LOCAL_IP,
+ OPT_CONFIG_BT_LOCAL_TCP_PORT
+};
+
+static struct ws_option longopts[] = {
+ EXTCAP_BASE_OPTIONS,
+ { "help", ws_no_argument, NULL, OPT_HELP},
+ { "version", ws_no_argument, NULL, OPT_VERSION},
+ { "adb-server-ip", ws_required_argument, NULL, OPT_CONFIG_ADB_SERVER_IP},
+ { "adb-server-tcp-port", ws_required_argument, NULL, OPT_CONFIG_ADB_SERVER_TCP_PORT},
+ { "logcat-text", ws_optional_argument, NULL, OPT_CONFIG_LOGCAT_TEXT},
+ { "logcat-ignore-log-buffer", ws_optional_argument, NULL, OPT_CONFIG_LOGCAT_IGNORE_LOG_BUFFER},
+ { "logcat-custom-options", ws_required_argument, NULL, OPT_CONFIG_LOGCAT_CUSTOM_OPTIONS},
+ { "bt-server-tcp-port", ws_required_argument, NULL, OPT_CONFIG_BT_SERVER_TCP_PORT},
+ { "bt-forward-socket", ws_required_argument, NULL, OPT_CONFIG_BT_FORWARD_SOCKET},
+ { "bt-local-ip", ws_required_argument, NULL, OPT_CONFIG_BT_LOCAL_IP},
+ { "bt-local-tcp-port", ws_required_argument, NULL, OPT_CONFIG_BT_LOCAL_TCP_PORT},
+ { 0, 0, 0, 0 }
+};
+
+struct interface_t {
+ const char *display_name;
+ const char *interface_name;
+ struct interface_t *next;
+};
+
+struct exported_pdu_header {
+ uint16_t tag;
+ uint16_t length;
+/* unsigned char value[0]; */
+};
+
+
+typedef struct _own_pcap_bluetooth_h4_header {
+ uint32_t direction;
+} own_pcap_bluetooth_h4_header;
+
+typedef struct pcap_hdr_s {
+ uint32_t magic_number; /* magic number */
+ uint16_t version_major; /* major version number */
+ uint16_t version_minor; /* minor version number */
+ int32_t thiszone; /* GMT to local correction */
+ uint32_t sigfigs; /* accuracy of timestamps */
+ uint32_t snaplen; /* max length of captured packets, in octets */
+ uint32_t network; /* data link type */
+} pcap_hdr_t;
+
+
+typedef struct pcaprec_hdr_s {
+ uint32_t ts_sec; /* timestamp seconds */
+ uint32_t ts_usec; /* timestamp microseconds */
+ uint32_t incl_len; /* number of octets of packet saved in file */
+ uint32_t orig_len; /* actual length of packet */
+} pcaprec_hdr_t;
+
+/* This fix compilator warning like "warning: cast from 'char *' to 'uint32_t *' (aka 'unsigned int *') increases required alignment from 1 to 4 " */
+typedef union {
+ char *value_char;
+ uint8_t *value_u8;
+ uint16_t *value_u16;
+ uint32_t *value_u32;
+ uint64_t *value_u64;
+ int8_t *value_i8;
+ int16_t *value_i16;
+ int32_t *value_i32;
+ int64_t *value_i64;
+ own_pcap_bluetooth_h4_header *value_own_pcap_bluetooth_h4_header;
+} data_aligned_t;
+
+#define SET_DATA(dest, type, src) \
+ { \
+ data_aligned_t data_aligned; \
+ \
+ data_aligned.value_char = src; \
+ dest = data_aligned.type; \
+ }
+
+struct extcap_dumper {
+ int encap;
+ union {
+#ifdef ANDROIDDUMP_USE_LIBPCAP
+ pcap_dumper_t *pcap;
+#else
+ wtap_dumper *wtap;
+#endif
+ } dumper;
+};
+
+/* Globals */
+static int endless_loop = 1;
+
+/* Functions */
+static inline int is_specified_interface(const char *interface, const char *interface_prefix) {
+ return !strncmp(interface, interface_prefix, strlen(interface_prefix));
+}
+
+static bool is_logcat_interface(const char *interface) {
+ return is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_MAIN) ||
+ is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_SYSTEM) ||
+ is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_RADIO) ||
+ is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_EVENTS);
+}
+
+static bool is_logcat_text_interface(const char *interface) {
+ return is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_TEXT_MAIN) ||
+ is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_TEXT_SYSTEM) ||
+ is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_TEXT_RADIO) ||
+ is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_TEXT_EVENTS) ||
+ is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_TEXT_CRASH);
+}
+
+static char* get_serial_from_interface(char *interface)
+{
+ static const char* const iface_prefix[] = {
+ INTERFACE_ANDROID_LOGCAT_MAIN,
+ INTERFACE_ANDROID_LOGCAT_SYSTEM,
+ INTERFACE_ANDROID_LOGCAT_RADIO,
+ INTERFACE_ANDROID_LOGCAT_EVENTS,
+ INTERFACE_ANDROID_LOGCAT_TEXT_MAIN,
+ INTERFACE_ANDROID_LOGCAT_TEXT_SYSTEM,
+ INTERFACE_ANDROID_LOGCAT_TEXT_RADIO,
+ INTERFACE_ANDROID_LOGCAT_TEXT_EVENTS,
+ INTERFACE_ANDROID_LOGCAT_TEXT_CRASH,
+ INTERFACE_ANDROID_BLUETOOTH_HCIDUMP,
+ INTERFACE_ANDROID_BLUETOOTH_EXTERNAL_PARSER,
+ INTERFACE_ANDROID_BLUETOOTH_BTSNOOP_NET,
+ NULL
+ };
+ int i;
+ const char* curr = NULL;
+ for (i = 0; (curr = iface_prefix[i]); i++) {
+ if (is_specified_interface(interface, curr) &&
+ strlen(interface) > strlen(curr) + 1) {
+ return interface + strlen(curr) + 1;
+ }
+ }
+ return NULL;
+}
+
+static const char* interface_to_logbuf(char* interface)
+{
+ const char *const adb_log_main = "log:main";
+ const char *const adb_log_system = "log:system";
+ const char *const adb_log_radio = "log:radio";
+ const char *const adb_log_events = "log:events";
+ const char *logbuf = NULL;
+
+ if (is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_MAIN))
+ logbuf = adb_log_main;
+ else if (is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_SYSTEM))
+ logbuf = adb_log_system;
+ else if (is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_RADIO))
+ logbuf = adb_log_radio;
+ else if (is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_EVENTS))
+ logbuf = adb_log_events;
+ return logbuf;
+}
+
+/*
+ * General errors and warnings are reported through ws_warning() in
+ * androiddump.
+ *
+ * Unfortunately, ws_warning() may be a macro, so we do it by calling
+ * g_logv() with the appropriate arguments.
+ */
+static void
+androiddump_cmdarg_err(const char *msg_format, va_list ap)
+{
+ ws_logv(LOG_DOMAIN_CAPCHILD, LOG_LEVEL_WARNING, msg_format, ap);
+}
+
+static void useSndTimeout(socket_handle_t sock) {
+ int res;
+#ifdef _WIN32
+ const DWORD socket_timeout = SOCKET_RW_TIMEOUT_MS;
+
+ res = setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char *) &socket_timeout, (socklen_t)sizeof(socket_timeout));
+#else
+ const struct timeval socket_timeout = {
+ .tv_sec = SOCKET_RW_TIMEOUT_MS / 1000,
+ .tv_usec = (SOCKET_RW_TIMEOUT_MS % 1000) * 1000
+ };
+
+ res = setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &socket_timeout, (socklen_t)sizeof(socket_timeout));
+#endif
+ if (res != 0)
+ ws_debug("Can't set socket timeout, using default");
+}
+
+static void useNonBlockingConnectTimeout(socket_handle_t sock) {
+ int res_snd;
+ int res_rcv;
+#ifdef _WIN32
+ const DWORD socket_timeout = SOCKET_RW_TIMEOUT_MS;
+ unsigned long non_blocking = 1;
+
+ res_snd = setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char *) &socket_timeout, sizeof(socket_timeout));
+ res_rcv = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char *) &socket_timeout, sizeof(socket_timeout));
+
+ /* set socket to non-blocking */
+ ioctlsocket(sock, FIONBIO, &non_blocking);
+#else
+ const struct timeval socket_timeout = {
+ .tv_sec = SOCKET_RW_TIMEOUT_MS / 1000,
+ .tv_usec = (SOCKET_RW_TIMEOUT_MS % 1000) * 1000
+ };
+
+ res_snd = setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &socket_timeout, sizeof(socket_timeout));
+ res_rcv = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &socket_timeout, sizeof(socket_timeout));
+#endif
+ if (res_snd != 0)
+ ws_debug("Can't set socket timeout, using default");
+ if (res_rcv != 0)
+ ws_debug("Can't set socket timeout, using default");
+}
+
+static void useNormalConnectTimeout(socket_handle_t sock) {
+ int res_rcv;
+#ifdef _WIN32
+ const DWORD socket_timeout = 0;
+ unsigned long non_blocking = 0;
+
+ res_rcv = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char *) &socket_timeout, sizeof(socket_timeout));
+ ioctlsocket(sock, FIONBIO, &non_blocking);
+#else
+ const struct timeval socket_timeout = {
+ .tv_sec = SOCKET_RW_TIMEOUT_MS / 1000,
+ .tv_usec = (SOCKET_RW_TIMEOUT_MS % 1000) * 1000
+ };
+
+ res_rcv = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &socket_timeout, sizeof(socket_timeout));
+#endif
+ if (res_rcv != 0)
+ ws_debug("Can't set socket timeout, using default");
+}
+
+static struct extcap_dumper extcap_dumper_open(char *fifo, int encap) {
+ struct extcap_dumper extcap_dumper;
+
+#ifdef ANDROIDDUMP_USE_LIBPCAP
+ pcap_t *pcap;
+
+ pcap = pcap_open_dead_with_tstamp_precision(encap, PACKET_LENGTH, PCAP_TSTAMP_PRECISION_NANO);
+ extcap_dumper.dumper.pcap = pcap_dump_open(pcap, fifo);
+ if (!extcap_dumper.dumper.pcap) {
+ ws_warning("Can't open %s for saving packets: %s", fifo, pcap_geterr(pcap));
+ pcap_close(pcap);
+ exit(EXIT_CODE_CANNOT_SAVE_LIBPCAP_DUMP);
+ }
+ extcap_dumper.encap = encap;
+ if (pcap_dump_flush(extcap_dumper.dumper.pcap) == -1) {
+ ws_warning("Write to %s failed: %s", fifo, g_strerror(errno));
+ }
+#else
+ wtap_dump_params params = WTAP_DUMP_PARAMS_INIT;
+ int file_type_subtype;
+ int err = 0;
+ char *err_info = NULL;
+
+ wtap_init(false);
+
+ params.encap = encap;
+ params.snaplen = PACKET_LENGTH;
+ file_type_subtype = wtap_pcap_nsec_file_type_subtype();
+ extcap_dumper.dumper.wtap = wtap_dump_open(fifo, file_type_subtype, WTAP_UNCOMPRESSED, &params, &err, &err_info);
+ if (!extcap_dumper.dumper.wtap) {
+ cfile_dump_open_failure_message(fifo, err, err_info, file_type_subtype);
+ exit(EXIT_CODE_CANNOT_SAVE_WIRETAP_DUMP);
+ }
+ extcap_dumper.encap = encap;
+ if (!wtap_dump_flush(extcap_dumper.dumper.wtap, &err)) {
+ cfile_dump_open_failure_message(fifo, err, NULL, file_type_subtype);
+ exit(EXIT_CODE_CANNOT_SAVE_WIRETAP_DUMP);
+ }
+#endif
+
+ return extcap_dumper;
+}
+
+static bool extcap_dumper_dump(struct extcap_dumper extcap_dumper,
+ char *fifo, char *buffer,
+ ssize_t captured_length, ssize_t reported_length,
+ time_t seconds, int nanoseconds) {
+#ifdef ANDROIDDUMP_USE_LIBPCAP
+ struct pcap_pkthdr pcap_header;
+
+ pcap_header.caplen = (bpf_u_int32) captured_length;
+ pcap_header.len = (bpf_u_int32) reported_length;
+ pcap_header.ts.tv_sec = seconds;
+ pcap_header.ts.tv_usec = nanoseconds / 1000;
+
+ pcap_dump((u_char *) extcap_dumper.dumper.pcap, &pcap_header, buffer);
+ if (pcap_dump_flush(extcap_dumper.dumper.pcap) == -1) {
+ ws_warning("Write to %s failed: %s", fifo, g_strerror(errno));
+ }
+#else
+ int err = 0;
+ char *err_info;
+ wtap_rec rec;
+
+ rec.rec_type = REC_TYPE_PACKET;
+ rec.presence_flags = WTAP_HAS_TS;
+ rec.rec_header.packet_header.caplen = (uint32_t) captured_length;
+ rec.rec_header.packet_header.len = (uint32_t) reported_length;
+
+ rec.ts.secs = seconds;
+ rec.ts.nsecs = (int) nanoseconds;
+
+ rec.block = NULL;
+
+/* NOTE: Try to handle pseudoheaders manually */
+ if (extcap_dumper.encap == EXTCAP_ENCAP_BLUETOOTH_H4_WITH_PHDR) {
+ uint32_t *direction;
+
+ SET_DATA(direction, value_u32, buffer)
+
+ rec.rec_header.packet_header.pseudo_header.bthci.sent = GINT32_FROM_BE(*direction) ? 0 : 1;
+
+ rec.rec_header.packet_header.len -= (uint32_t)sizeof(own_pcap_bluetooth_h4_header);
+ rec.rec_header.packet_header.caplen -= (uint32_t)sizeof(own_pcap_bluetooth_h4_header);
+
+ buffer += sizeof(own_pcap_bluetooth_h4_header);
+ }
+ rec.rec_header.packet_header.pkt_encap = extcap_dumper.encap;
+
+ if (!wtap_dump(extcap_dumper.dumper.wtap, &rec, (const uint8_t *) buffer, &err, &err_info)) {
+ cfile_write_failure_message(NULL, fifo, err, err_info, 0,
+ wtap_dump_file_type_subtype(extcap_dumper.dumper.wtap));
+ return false;
+ }
+
+ if (!wtap_dump_flush(extcap_dumper.dumper.wtap, &err)) {
+ cfile_write_failure_message(NULL, fifo, err, NULL, 0,
+ wtap_dump_file_type_subtype(extcap_dumper.dumper.wtap));
+ return false;
+ }
+#endif
+
+ return true;
+}
+
+
+static socket_handle_t adb_connect(const char *server_ip, unsigned short *server_tcp_port) {
+ socket_handle_t sock;
+ socklen_t length;
+ struct sockaddr_in server;
+ struct sockaddr_in client;
+ int status;
+ int tries = 0;
+
+ memset(&server, 0x0, sizeof(server));
+
+ server.sin_family = AF_INET;
+ server.sin_port = GINT16_TO_BE(*server_tcp_port);
+ ws_inet_pton4(server_ip, (ws_in4_addr *)&(server.sin_addr.s_addr));
+
+ if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {
+ ws_warning("Cannot open system TCP socket: %s", strerror(errno));
+ return INVALID_SOCKET;
+ }
+
+ useNonBlockingConnectTimeout(sock);
+ while (tries < SOCKET_CONNECT_TIMEOUT_TRIES) {
+ status = connect(sock, (struct sockaddr *) &server, (socklen_t)sizeof(server));
+ tries += 1;
+
+#ifdef _WIN32
+ if ((status == SOCKET_ERROR) && (WSAGetLastError() == WSAEWOULDBLOCK)) {
+ struct timeval timeout = {
+ .tv_sec = 0,
+ .tv_usec = SOCKET_CONNECT_DELAY_US,
+ };
+ fd_set fdset;
+ FD_ZERO(&fdset);
+ FD_SET(sock, &fdset);
+ if ((select(0, NULL, &fdset, NULL, &timeout) != 0) && (FD_ISSET(sock, &fdset))) {
+ status = 0;
+ }
+ }
+#endif
+
+ if (status != SOCKET_ERROR)
+ break;
+ g_usleep(SOCKET_CONNECT_DELAY_US);
+ }
+ useNormalConnectTimeout(sock);
+
+ if (status == SOCKET_ERROR) {
+#if 0
+/* NOTE: This does not work well - make significant delay while initializing Wireshark.
+ Do fork() then call "adb" also does not make sense, because there is need to
+ do something like sleep(1) to ensure adb is started... system() cannot be used
+ on Windows, because open console window. This helper does not work as expected,
+ so disable it and user must ensure that adb is started (adb start-server,
+ but also all other command start-server automatically)
+*/
+#ifdef _WIN32
+ if (_execlp("adb", "adb", "start-server", NULL)) {
+#else
+ if (execlp("adb", "adb", "start-server", NULL)) {
+#endif
+ errmsg("WARNING: Cannot execute system command to start adb: %s", strerror(errno));
+ closesocket(sock);
+ return INVALID_SOCKET;
+ };
+
+ if (connect(sock, (struct sockaddr *) &server, (socklen_t)sizeof(server)) == SOCKET_ERROR) {
+ ws_warning("Cannot connect to ADB: <%s> Please check that adb daemon is running.", strerror(errno));
+ closesocket(sock);
+ return INVALID_SOCKET;
+ }
+#else
+ ws_debug("Cannot connect to ADB: <%s> Please check that adb daemon is running.", strerror(errno));
+ closesocket(sock);
+ return INVALID_SOCKET;
+#endif
+ }
+
+ length = sizeof(client);
+ if (getsockname(sock, (struct sockaddr *) &client, &length)) {
+ ws_warning("getsockname: %s", strerror(errno));
+ closesocket(sock);
+ return INVALID_SOCKET;
+ }
+
+ if (length != sizeof(client)) {
+ ws_warning("incorrect length");
+ closesocket(sock);
+ return INVALID_SOCKET;
+ }
+
+ ws_debug("Client port %u", GUINT16_FROM_BE(client.sin_port));
+
+ return sock;
+}
+
+
+static char *adb_send_and_receive(socket_handle_t sock, const char *adb_service,
+ char *buffer, size_t buffer_length, size_t *data_length) {
+ size_t used_buffer_length;
+ size_t bytes_to_read;
+ uint32_t length;
+ ssize_t result;
+ char status[4];
+ char tmp_buffer;
+ size_t adb_service_length;
+
+ adb_service_length = strlen(adb_service);
+ if (adb_service_length > INT_MAX) {
+ ws_warning("Service name too long when sending <%s> to ADB daemon", adb_service);
+ if (data_length)
+ *data_length = 0;
+ return NULL;
+ }
+
+ /* 8 bytes of hex length + terminating NUL */
+ if (buffer_length < 9) {
+ ws_warning("Buffer for response too short while sending <%s> to ADB daemon", adb_service);
+ if (data_length)
+ *data_length = 0;
+ return NULL;
+ }
+
+ snprintf(buffer, buffer_length, ADB_HEX4_FORMAT, adb_service_length);
+ result = send(sock, buffer, ADB_HEX4_LEN, 0);
+ if (result < ADB_HEX4_LEN) {
+ ws_warning("Error while sending <%s> length to ADB daemon", adb_service);
+ return NULL;
+ }
+
+ result = send(sock, adb_service, (int) adb_service_length, 0);
+ if (result != (ssize_t) adb_service_length) {
+ ws_warning("Error while sending <%s> to ADB daemon", adb_service);
+ if (data_length)
+ *data_length = 0;
+ return NULL;
+ }
+
+ used_buffer_length = 0;
+ while (used_buffer_length < 8) {
+ bytes_to_read = buffer_length - used_buffer_length;
+ if (bytes_to_read > INT_MAX)
+ bytes_to_read = INT_MAX;
+ result = recv(sock, buffer + used_buffer_length, (int)bytes_to_read, 0);
+
+ if (result <= 0) {
+ ws_warning("Broken socket connection while fetching reply status for <%s>", adb_service);
+ if (data_length)
+ *data_length = 0;
+ return NULL;
+ }
+
+ used_buffer_length += result;
+ }
+
+ memcpy(status, buffer, 4);
+ tmp_buffer = buffer[8];
+ buffer[8] = '\0';
+ if (!ws_hexstrtou32(buffer + 4, NULL, &length)) {
+ ws_warning("Invalid reply length <%s> while reading reply for <%s>", buffer + 4, adb_service);
+ if (data_length)
+ *data_length = 0;
+ return NULL;
+ }
+ buffer[8] = tmp_buffer;
+
+ if (buffer_length < length + 8) {
+ ws_warning("Buffer for response too short while sending <%s> to ADB daemon", adb_service);
+ if (data_length)
+ *data_length = 0;
+ return NULL;
+ }
+
+ while (used_buffer_length < length + 8) {
+ bytes_to_read = buffer_length - used_buffer_length;
+ if (bytes_to_read > INT_MAX)
+ bytes_to_read = INT_MAX;
+ result = recv(sock, buffer + used_buffer_length, (int)bytes_to_read, 0);
+
+ if (result <= 0) {
+ ws_warning("Broken socket connection while reading reply for <%s>", adb_service);
+ if (data_length)
+ *data_length = 0;
+ return NULL;
+ }
+
+ used_buffer_length += result;
+ }
+
+ if (data_length)
+ *data_length = used_buffer_length - 8;
+
+ if (memcmp(status, "OKAY", 4)) {
+ ws_warning("Error while receiving by ADB for <%s>", adb_service);
+ if (data_length)
+ *data_length = 0;
+ return NULL;
+ }
+
+ return buffer + 8;
+}
+
+
+static char *adb_send_and_read(socket_handle_t sock, const char *adb_service, char *buffer,
+ int buffer_length, ssize_t *data_length) {
+ ssize_t used_buffer_length;
+ ssize_t result;
+ char status[4];
+ size_t adb_service_length;
+
+ adb_service_length = strlen(adb_service);
+ snprintf(buffer, buffer_length, ADB_HEX4_FORMAT, adb_service_length);
+
+ result = send(sock, buffer, ADB_HEX4_LEN, 0);
+ if (result < ADB_HEX4_LEN) {
+ ws_warning("Error while sending <%s> to ADB daemon", adb_service);
+ return NULL;
+ }
+
+ result = send(sock, adb_service, (int) adb_service_length, 0);
+ if (result != (ssize_t) adb_service_length) {
+ ws_warning("Error while sending <%s> to ADB", adb_service);
+ if (data_length)
+ *data_length = 0;
+ return NULL;
+ }
+
+ used_buffer_length = 0;
+ while (used_buffer_length < 4) {
+ result = recv(sock, buffer + used_buffer_length, (int)(buffer_length - used_buffer_length), 0);
+
+ if (result <= 0) {
+ ws_warning("Broken socket connection while fetching reply status for <%s>", adb_service);
+
+ return NULL;
+ }
+
+ used_buffer_length += result;
+ }
+
+ memcpy(status, buffer, 4);
+
+ while (result > 0) {
+ result= recv(sock, buffer + used_buffer_length, (int)(buffer_length - used_buffer_length), 0);
+
+ if (result < 0) {
+ ws_warning("Broken socket connection while reading reply for <%s>", adb_service);
+
+ return NULL;
+ } else if (result == 0) {
+ break;
+ }
+
+ used_buffer_length += result;
+ }
+
+ if (data_length)
+ *data_length = used_buffer_length - 4;
+
+ if (memcmp(status, "OKAY", 4)) {
+ ws_warning("Error while receiving by ADB for <%s>", adb_service);
+ if (data_length)
+ *data_length = 0;
+ return NULL;
+ }
+
+ return buffer + 4;
+}
+
+
+static int adb_send(socket_handle_t sock, const char *adb_service) {
+ char buffer[5];
+ int used_buffer_length;
+ ssize_t result;
+ size_t adb_service_length;
+
+ adb_service_length = strlen(adb_service);
+ snprintf(buffer, sizeof(buffer), ADB_HEX4_FORMAT, adb_service_length);
+
+ result = send(sock, buffer, ADB_HEX4_LEN, 0);
+ if (result < ADB_HEX4_LEN) {
+ ws_warning("Error while sending <%s> to ADB daemon", adb_service);
+ return EXIT_CODE_ERROR_WHILE_SENDING_ADB_PACKET_1;
+ }
+
+ result = send(sock, adb_service, (int) adb_service_length, 0);
+ if (result != (ssize_t) adb_service_length) {
+ ws_warning("Error while sending <%s> to ADB", adb_service);
+ return EXIT_CODE_ERROR_WHILE_SENDING_ADB_PACKET_1;
+ }
+
+ used_buffer_length = 0;
+ while (used_buffer_length < 4) {
+ result = recv(sock, buffer + used_buffer_length, 4 - used_buffer_length, 0);
+
+ if (result <= 0) {
+ ws_warning("Broken socket connection while fetching reply status for <%s>", adb_service);
+
+ return EXIT_CODE_ERROR_WHILE_RECEIVING_ADB_PACKET_STATUS;
+ }
+
+ used_buffer_length += (int)result;
+ }
+
+ if (memcmp(buffer, "OKAY", 4)) {
+ ws_debug("Error while receiving by ADB for <%s>", adb_service);
+
+ return EXIT_CODE_ERROR_WHILE_RECEIVING_ADB_PACKET_DATA;
+ }
+
+ return EXIT_CODE_SUCCESS;
+}
+
+
+static socket_handle_t
+adb_connect_transport(const char *server_ip, unsigned short *server_tcp_port,
+ const char* serial_number)
+{
+ static const char *const adb_transport_serial_templace = "host:transport:%s";
+ static const char *const adb_transport_any = "host:transport-any";
+ char transport_buf[80];
+ const char* transport = transport_buf;
+ socket_handle_t sock;
+ ssize_t result;
+
+ sock = adb_connect(server_ip, server_tcp_port);
+ if (sock == INVALID_SOCKET) {
+ ws_warning("Error while connecting to adb server");
+ return sock;
+ }
+
+ if (!serial_number) {
+ transport = adb_transport_any;
+ } else {
+ result = snprintf(transport_buf, sizeof(transport_buf), adb_transport_serial_templace, serial_number);
+ if (result <= 0 || result > (int)sizeof(transport_buf)) {
+ ws_warning("Error while completing adb packet for transport");
+ closesocket(sock);
+ return INVALID_SOCKET;
+ }
+ }
+
+ result = adb_send(sock, transport);
+ if (result) {
+ ws_warning("Error while setting adb transport for <%s>", transport_buf);
+ closesocket(sock);
+ return INVALID_SOCKET;
+ }
+ return sock;
+}
+
+
+static void new_interface(extcap_parameters * extcap_conf, const char *interface_id,
+ const char *model_name, const char *serial_number, const char *display_name)
+{
+ char *interface = ws_strdup_printf("%s-%s", interface_id, serial_number);
+ char *ifdisplay = ws_strdup_printf("%s %s %s", display_name, model_name, serial_number);
+
+ if (is_specified_interface(interface, INTERFACE_ANDROID_BLUETOOTH_HCIDUMP) ||
+ is_specified_interface(interface, INTERFACE_ANDROID_BLUETOOTH_EXTERNAL_PARSER) ||
+ is_specified_interface(interface, INTERFACE_ANDROID_BLUETOOTH_BTSNOOP_NET)) {
+
+ extcap_base_register_interface_ext(extcap_conf, interface, ifdisplay, 99, "BluetoothH4", "Bluetooth HCI UART transport layer plus pseudo-header" );
+ } else if (is_logcat_interface(interface) || is_logcat_text_interface(interface)) {
+ extcap_base_register_interface(extcap_conf, interface, ifdisplay, 252, "Upper PDU" );
+ } else if (is_specified_interface(interface, INTERFACE_ANDROID_TCPDUMP)) {
+ extcap_base_register_interface(extcap_conf, interface, ifdisplay, 1, "Ethernet");
+ }
+ g_free(interface);
+ g_free(ifdisplay);
+}
+
+
+static void new_fake_interface_for_list_dlts(extcap_parameters * extcap_conf,
+ const char *ifname)
+{
+ if (is_specified_interface(ifname, INTERFACE_ANDROID_BLUETOOTH_HCIDUMP) ||
+ is_specified_interface(ifname, INTERFACE_ANDROID_BLUETOOTH_EXTERNAL_PARSER) ||
+ is_specified_interface(ifname, INTERFACE_ANDROID_BLUETOOTH_BTSNOOP_NET)) {
+ extcap_base_register_interface_ext(extcap_conf, ifname, ifname, 99, "BluetoothH4", "Bluetooth HCI UART transport layer plus pseudo-header" );
+ } else if (is_logcat_interface(ifname) || is_logcat_text_interface(ifname)) {
+ extcap_base_register_interface(extcap_conf, ifname, ifname, 252, "Upper PDU" );
+ } else if (is_specified_interface(ifname, INTERFACE_ANDROID_TCPDUMP)) {
+ extcap_base_register_interface(extcap_conf, ifname, ifname, 1, "Ethernet");
+ }
+}
+
+
+static int add_tcpdump_interfaces(extcap_parameters * extcap_conf, const char *adb_server_ip, unsigned short *adb_server_tcp_port, const char *serial_number)
+{
+ static const char *const adb_tcpdump_list = "shell:tcpdump -D";
+ static const char *const regex_ifaces = "\\d+\\.(?<iface>\\S+)(\\s+?(?:(?:\\(.*\\))*)(\\s*?\\[(?<flags>.*?)\\])?)?";
+ static char recv_buffer[PACKET_LENGTH];
+ char *response;
+ ssize_t data_length;
+ socket_handle_t sock;
+ GRegex* regex = NULL;
+ GError *err = NULL;
+ GMatchInfo *match = NULL;
+ char* tok;
+ char iface_name[80];
+ bool flags_supported;
+
+ sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number);
+ if (sock == INVALID_SOCKET) {
+ ws_warning("Failed to connect to adb server");
+ return EXIT_CODE_GENERIC;
+ }
+
+ response = adb_send_and_read(sock, adb_tcpdump_list, recv_buffer, sizeof(recv_buffer), &data_length);
+ closesocket(sock);
+
+ if (!response) {
+ ws_warning("Failed to get list of available tcpdump interfaces");
+ return EXIT_CODE_GENERIC;
+ }
+ response[data_length] = '\0';
+
+ regex = g_regex_new(regex_ifaces, G_REGEX_RAW, (GRegexMatchFlags)0, &err);
+ if (!regex) {
+ ws_warning("Failed to compile regex for tcpdump interface matching");
+ return EXIT_CODE_GENERIC;
+ }
+
+ flags_supported = (strstr(response, "[") != 0) && (strstr(response, "]") != 0);
+
+ tok = strtok(response, "\n");
+ while (tok != NULL) {
+ g_regex_match(regex, tok, (GRegexMatchFlags)0, &match);
+ if (g_match_info_matches(match)) {
+ char *iface = g_match_info_fetch_named(match, "iface");
+ char *flags = g_match_info_fetch_named(match, "flags");
+
+ if (!flags_supported || (flags && strstr(flags, "Up"))) {
+ snprintf(iface_name, sizeof(iface_name), INTERFACE_ANDROID_TCPDUMP_FORMAT, iface);
+ new_interface(extcap_conf, iface_name, iface, serial_number, "Android tcpdump");
+ }
+ g_free(flags);
+ g_free(iface);
+ }
+ g_match_info_free(match);
+ tok = strtok(NULL, "\n");
+ }
+ g_regex_unref(regex);
+ return 0;
+}
+
+
+static int register_interfaces(extcap_parameters * extcap_conf, const char *adb_server_ip, unsigned short *adb_server_tcp_port) {
+ static char packet[PACKET_LENGTH];
+ static char helpful_packet[PACKET_LENGTH];
+ char check_port_buf[80];
+ char *response;
+ char *device_list;
+ ssize_t data_length;
+ size_t device_length;
+ socket_handle_t sock;
+ const char *adb_check_port_templace = "shell:cat /proc/%s/net/tcp";
+ const char *adb_devices = "host:devices-l";
+ const char *adb_api_level = "shell:getprop ro.build.version.sdk";
+ const char *adb_hcidump_version = "shell:hcidump --version";
+ const char *adb_ps_droid_bluetooth = "shell:ps droid.bluetooth";
+ const char *adb_ps_bluetooth_app = "shell:ps com.android.bluetooth";
+ const char *adb_ps_with_grep = "shell:ps | grep com.android.bluetooth";
+ const char *adb_ps_all_with_grep = "shell:ps -A | grep com.*android.bluetooth";
+ char serial_number[SERIAL_NUMBER_LENGTH_MAX];
+ char model_name[MODEL_NAME_LENGTH_MAX];
+ int result;
+ char *pos;
+ char *i_pos;
+ char *model_pos;
+ char *device_pos;
+ char *prev_pos;
+ int api_level;
+ int disable_interface;
+
+/* NOTE: It seems that "adb devices" and "adb shell" closed connection
+ so cannot send next command after them, there is need to reconnect */
+
+ sock = adb_connect(adb_server_ip, adb_server_tcp_port);
+ if (sock == INVALID_SOCKET)
+ return EXIT_CODE_INVALID_SOCKET_INTERFACES_LIST;
+
+ device_list = adb_send_and_receive(sock, adb_devices, packet, sizeof(packet), &device_length);
+ closesocket(sock);
+
+ if (!device_list) {
+ ws_warning("Cannot get list of interfaces from devices");
+
+ return EXIT_CODE_CANNOT_GET_INTERFACES_LIST;
+ }
+
+ device_list[device_length] = '\0';
+ pos = (char *) device_list;
+
+ while (pos < (char *) (device_list + device_length)) {
+ prev_pos = pos;
+ pos = strchr(pos, ' ');
+ i_pos = pos;
+ result = (int) (pos - prev_pos);
+ pos = strchr(pos, '\n') + 1;
+ if (result >= (int) sizeof(serial_number)) {
+ ws_warning("Serial number too long, ignore device");
+ continue;
+ }
+ memcpy(serial_number, prev_pos, result);
+ serial_number[result] = '\0';
+
+ model_name[0] = '\0';
+ model_pos = g_strstr_len(i_pos, pos - i_pos, "model:");
+ if (model_pos) {
+ device_pos = g_strstr_len(i_pos, pos - i_pos, "device:");
+ if (device_pos && device_pos - model_pos - 6 - 1 < MODEL_NAME_LENGTH_MAX) {
+ memcpy(model_name, model_pos + 6, device_pos - model_pos - 6 - 1);
+ model_name[device_pos - model_pos - 6 - 1] = '\0';
+ }
+ }
+
+ if (model_name[0] == '\0')
+ strcpy(model_name, "unknown");
+
+ ws_debug("Processing device: \"%s\" <%s>" , serial_number, model_name);
+
+ /* Function will only add tcpdump interfaces if tcpdump is present on the device */
+ result = add_tcpdump_interfaces(extcap_conf, adb_server_ip, adb_server_tcp_port, serial_number );
+ if (result) {
+ ws_warning("Error while adding tcpdump interfaces");
+ }
+
+ sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number);
+ if (sock == INVALID_SOCKET) continue;
+
+ response = adb_send_and_read(sock, adb_api_level, helpful_packet, sizeof(helpful_packet), &data_length);
+ closesocket(sock);
+
+ if (!response) {
+ ws_warning("Error on socket: <%s>", helpful_packet);
+ continue;
+ }
+
+ response[data_length] = '\0';
+ api_level = (int) g_ascii_strtoll(response, NULL, 10);
+ ws_debug("Android API Level for %s is %i", serial_number, api_level);
+
+ if (api_level < 21) {
+ new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_MAIN, model_name, serial_number, "Android Logcat Main");
+ new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_SYSTEM, model_name, serial_number, "Android Logcat System");
+ new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_RADIO, model_name, serial_number, "Android Logcat Radio");
+ new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_EVENTS, model_name, serial_number, "Android Logcat Events");
+
+ new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_TEXT_MAIN, model_name, serial_number, "Android Logcat Main");
+ new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_TEXT_SYSTEM, model_name, serial_number, "Android Logcat System");
+ new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_TEXT_RADIO, model_name, serial_number, "Android Logcat Radio");
+ new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_TEXT_EVENTS, model_name, serial_number, "Android Logcat Events");
+ } else {
+ new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_TEXT_MAIN, model_name, serial_number, "Android Logcat Main");
+ new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_TEXT_SYSTEM, model_name, serial_number, "Android Logcat System");
+ new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_TEXT_RADIO, model_name, serial_number, "Android Logcat Radio");
+ new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_TEXT_EVENTS, model_name, serial_number, "Android Logcat Events");
+ new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_TEXT_CRASH, model_name, serial_number, "Android Logcat Crash");
+ }
+
+ if (api_level >= 5 && api_level < 17) {
+ disable_interface = 0;
+
+ sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number);
+ if (sock == INVALID_SOCKET) continue;
+
+ response = adb_send_and_read(sock, adb_hcidump_version, helpful_packet, sizeof(helpful_packet), &data_length);
+ closesocket(sock);
+
+ if (!response || data_length < 1) {
+ ws_warning("Error while getting hcidump version by <%s> (%p len=%"PRIdMAX")",
+ adb_hcidump_version, (void*)response, (intmax_t)data_length);
+ ws_debug("Android hcidump version for %s is unknown", serial_number);
+ disable_interface = 1;
+ } else {
+ response[data_length] = '\0';
+
+ if (g_ascii_strtoull(response, NULL, 10) == 0) {
+ ws_debug("Android hcidump version for %s is unknown", serial_number);
+ disable_interface = 1;
+ } else {
+ ws_debug("Android hcidump version for %s is %s", serial_number, response);
+ }
+ }
+
+ if (!disable_interface) {
+ new_interface(extcap_conf, INTERFACE_ANDROID_BLUETOOTH_HCIDUMP, model_name, serial_number, "Android Bluetooth Hcidump");
+ }
+ }
+
+ if (api_level >= 17 && api_level < 21) {
+ disable_interface = 0;
+ sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number);
+ if (sock == INVALID_SOCKET) continue;
+
+ response = adb_send_and_read(sock, adb_ps_droid_bluetooth, helpful_packet, sizeof(helpful_packet), &data_length);
+ closesocket(sock);
+ if (!response || data_length < 1) {
+ ws_warning("Error while getting Bluetooth application process id by <%s> "
+ "(%p len=%"PRIdMAX")", adb_ps_droid_bluetooth, (void*)response, (intmax_t)data_length);
+ ws_debug( "Android Bluetooth application PID for %s is unknown", serial_number);
+ disable_interface = 1;
+ } else {
+ char *data_str;
+ char pid[16];
+
+ memset(pid, 0, sizeof(pid));
+ response[data_length] = '\0';
+
+ data_str = strchr(response, '\n');
+ if (data_str && sscanf(data_str, "%*s %15s", pid) == 1) {
+ ws_debug("Android Bluetooth application PID for %s is %s", serial_number, pid);
+
+ result = snprintf(check_port_buf, sizeof(check_port_buf), adb_check_port_templace, pid);
+ if (result <= 0 || result > (int)sizeof(check_port_buf)) {
+ ws_warning("Error while completing adb packet");
+ return EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_6;
+ }
+
+ sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number);
+ if (sock == INVALID_SOCKET) continue;
+
+ response = adb_send_and_read(sock, check_port_buf, helpful_packet, sizeof(helpful_packet), &data_length);
+ closesocket(sock);
+
+ if (!response) {
+ disable_interface = 1;
+ } else {
+ response[data_length] = '\0';
+
+ data_str = strchr(response, '\n');
+ if (data_str && sscanf(data_str, "%*s %15s", pid) == 1 && strlen(pid) > 10 && strcmp(pid + 9, "10EA") == 0) {
+ ws_debug("Bluedroid External Parser Port for %s is %s", serial_number, pid + 9);
+ } else {
+ disable_interface = 1;
+ ws_debug("Bluedroid External Parser Port for %s is unknown", serial_number);
+ }
+ }
+ } else {
+ disable_interface = 1;
+ ws_debug("Android Bluetooth application PID for %s is unknown", serial_number);
+ }
+ }
+
+ if (!disable_interface) {
+ new_interface(extcap_conf, INTERFACE_ANDROID_BLUETOOTH_EXTERNAL_PARSER, model_name, serial_number, "Android Bluetooth External Parser");
+ }
+ }
+
+ if (api_level >= 21) {
+ const char* ps_cmd;
+ disable_interface = 0;
+
+ if (api_level >= 26) {
+ ps_cmd = adb_ps_all_with_grep;
+ } else if (api_level >= 24) {
+ ps_cmd = adb_ps_with_grep;
+ } else if (api_level >= 23) {
+ ps_cmd = adb_ps_bluetooth_app;
+ } else {
+ ps_cmd = adb_ps_droid_bluetooth;
+ }
+ sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number);
+ if (sock == INVALID_SOCKET) continue;
+
+ response = adb_send_and_read(sock, ps_cmd, helpful_packet, sizeof(helpful_packet), &data_length);
+ closesocket(sock);
+
+ if (!response || data_length < 1) {
+ ws_warning("Error while getting Bluetooth application process id by <%s> "
+ "(%p len=%"PRIdMAX")", ps_cmd, (void*)response, (intmax_t)data_length);
+ ws_debug("Android Bluetooth application PID for %s is unknown", serial_number);
+ disable_interface = 1;
+ } else {
+ char *data_str;
+ char pid[16];
+
+ memset(pid, 0, sizeof(pid));
+ response[data_length] = '\0';
+
+ if (api_level >= 24)
+ data_str = response;
+ else
+ data_str = strchr(response, '\n');
+
+ if (data_str && sscanf(data_str, "%*s %15s", pid) == 1) {
+ ws_debug("Android Bluetooth application PID for %s is %s", serial_number, pid);
+
+ result = snprintf(check_port_buf, sizeof(check_port_buf), adb_check_port_templace, pid);
+ if (result <= 0 || result > (int)sizeof(check_port_buf)) {
+ ws_warning("Error while completing adb packet");
+ return EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_9;
+ }
+
+ sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number);
+ if (sock == INVALID_SOCKET) continue;
+
+ response = adb_send_and_read(sock, check_port_buf, helpful_packet, sizeof(helpful_packet), &data_length);
+ closesocket(sock);
+
+ if (!response) {
+ disable_interface = 1;
+ } else {
+ response[data_length] = '\0';
+ data_str = strtok(response, "\n");
+ while (data_str != NULL) {
+ if (sscanf(data_str, "%*s %15s", pid) == 1 && strlen(pid) > 10 && strcmp(pid + 9, "22A8") == 0) {
+ ws_debug("Btsnoop Net Port for %s is %s", serial_number, pid + 9);
+ break;
+ }
+ data_str = strtok(NULL, "\n");
+ }
+ if (data_str == NULL) {
+ disable_interface = 1;
+ ws_debug("Btsnoop Net Port for %s is unknown", serial_number);
+ }
+ }
+ } else {
+ disable_interface = 1;
+ ws_debug("Android Bluetooth application PID for %s is unknown", serial_number);
+ }
+ }
+
+ if (!disable_interface) {
+ new_interface(extcap_conf, INTERFACE_ANDROID_BLUETOOTH_BTSNOOP_NET, model_name, serial_number, "Android Bluetooth Btsnoop Net");
+ }
+ }
+ }
+
+ return EXIT_CODE_SUCCESS;
+}
+
+static int list_config(char *interface) {
+ int ret = EXIT_CODE_INVALID_INTERFACE;
+ unsigned inc = 0;
+
+ if (!interface) {
+ ws_warning("No interface specified.");
+ return EXIT_CODE_NO_INTERFACE_SPECIFIED;
+ }
+
+ if (is_specified_interface(interface, INTERFACE_ANDROID_BLUETOOTH_EXTERNAL_PARSER)) {
+ printf("arg {number=%u}{call=--adb-server-ip}{display=ADB Server IP Address}{type=string}{default=127.0.0.1}\n", inc++);
+ printf("arg {number=%u}{call=--adb-server-tcp-port}{display=ADB Server TCP Port}{type=integer}{range=0,65535}{default=5037}\n", inc++);
+ printf("arg {number=%u}{call=--bt-server-tcp-port}{display=Bluetooth Server TCP Port}{type=integer}{range=0,65535}{default=4330}\n", inc++);
+ printf("arg {number=%u}{call=--bt-forward-socket}{display=Forward Bluetooth Socket}{type=boolean}{default=false}\n", inc++);
+ printf("arg {number=%u}{call=--bt-local-ip}{display=Bluetooth Local IP Address}{type=string}{default=127.0.0.1}\n", inc++);
+ printf("arg {number=%u}{call=--bt-local-tcp-port}{display=Bluetooth Local TCP Port}{type=integer}{range=0,65535}{default=4330}{tooltip=Used to do \"adb forward tcp:LOCAL_TCP_PORT tcp:SERVER_TCP_PORT\"}\n", inc++);
+ ret = EXIT_CODE_SUCCESS;
+ } else if (is_specified_interface(interface, INTERFACE_ANDROID_BLUETOOTH_HCIDUMP) ||
+ is_specified_interface(interface, INTERFACE_ANDROID_BLUETOOTH_BTSNOOP_NET) ||
+ is_specified_interface(interface, INTERFACE_ANDROID_TCPDUMP)) {
+ printf("arg {number=%u}{call=--adb-server-ip}{display=ADB Server IP Address}{type=string}{default=127.0.0.1}\n", inc++);
+ printf("arg {number=%u}{call=--adb-server-tcp-port}{display=ADB Server TCP Port}{type=integer}{range=0,65535}{default=5037}\n", inc++);
+ ret = EXIT_CODE_SUCCESS;
+ } else if (is_logcat_interface(interface)) {
+ printf("arg {number=%u}{call=--adb-server-ip}{display=ADB Server IP Address}{type=string}{default=127.0.0.1}\n", inc++);
+ printf("arg {number=%u}{call=--adb-server-tcp-port}{display=ADB Server TCP Port}{type=integer}{range=0,65535}{default=5037}\n", inc++);
+ printf("arg {number=%u}{call=--logcat-text}{display=Use text logcat}{type=boolean}{default=false}\n", inc++);
+ printf("arg {number=%u}{call=--logcat-ignore-log-buffer}{display=Ignore log buffer}{type=boolean}{default=false}\n", inc++);
+ printf("arg {number=%u}{call=--logcat-custom-options}{display=Custom logcat parameters}{type=string}\n", inc++);
+ ret = EXIT_CODE_SUCCESS;
+ } else if (is_logcat_text_interface(interface)) {
+ printf("arg {number=%u}{call=--adb-server-ip}{display=ADB Server IP Address}{type=string}{default=127.0.0.1}\n", inc++);
+ printf("arg {number=%u}{call=--adb-server-tcp-port}{display=ADB Server TCP Port}{type=integer}{range=0,65535}{default=5037}\n", inc++);
+ printf("arg {number=%u}{call=--logcat-ignore-log-buffer}{display=Ignore log buffer}{type=boolean}{default=false}\n", inc++);
+ printf("arg {number=%u}{call=--logcat-custom-options}{display=Custom logcat parameters}{type=string}\n", inc++);
+ ret = EXIT_CODE_SUCCESS;
+ }
+
+ if (ret != EXIT_CODE_SUCCESS)
+ ws_warning("Invalid interface: <%s>", interface);
+ else
+ extcap_config_debug(&inc);
+
+ return ret;
+}
+
+/*----------------------------------------------------------------------------*/
+/* Android Bluetooth Hcidump */
+/*----------------------------------------------------------------------------*/
+
+static int capture_android_bluetooth_hcidump(char *interface, char *fifo,
+ const char *adb_server_ip, unsigned short *adb_server_tcp_port) {
+ struct extcap_dumper extcap_dumper;
+ static char data[PACKET_LENGTH];
+ static char packet[PACKET_LENGTH];
+ ssize_t length;
+ ssize_t used_buffer_length = 0;
+ socket_handle_t sock = INVALID_SOCKET;
+ const char *adb_shell_hcidump = "shell:hcidump -R -t";
+ const char *adb_shell_su_hcidump = "shell:su -c hcidump -R -t";
+ int result;
+ char *serial_number;
+ time_t ts = 0;
+ unsigned int captured_length;
+ int64_t hex;
+ char *hex_data;
+ char *new_hex_data;
+ own_pcap_bluetooth_h4_header *h4_header;
+ int64_t raw_length = 0;
+ int64_t frame_length;
+ int ms = 0;
+ struct tm date;
+ char direction_character;
+
+ SET_DATA(h4_header, value_own_pcap_bluetooth_h4_header, packet);
+
+ extcap_dumper = extcap_dumper_open(fifo, EXTCAP_ENCAP_BLUETOOTH_H4_WITH_PHDR);
+
+ serial_number = get_serial_from_interface(interface);
+ sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number);
+ if (sock == INVALID_SOCKET)
+ return EXIT_CODE_INVALID_SOCKET_3;
+
+ result = adb_send(sock, adb_shell_hcidump);
+ if (result) {
+ ws_warning("Error while starting capture by sending command: %s", adb_shell_hcidump);
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ while (endless_loop) {
+ char *i_position;
+
+ errno = 0;
+ length = recv(sock, data + used_buffer_length, (int)(PACKET_LENGTH - used_buffer_length), 0);
+ if (errno == EAGAIN
+#if EWOULDBLOCK != EAGAIN
+ || errno == EWOULDBLOCK
+#endif
+ ) {
+ continue;
+ }
+ else if (errno != 0) {
+ ws_warning("ERROR capture: %s", strerror(errno));
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ if (length <= 0) {
+ ws_warning("Broken socket connection.");
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ used_buffer_length += length;
+ i_position = (char *) memchr(data, '\n', used_buffer_length);
+ if (i_position && i_position < data + used_buffer_length) {
+ char *state_line_position = i_position + 1;
+
+ if (!strncmp(data, "/system/bin/sh: hcidump: not found", 34)) {
+ ws_warning("Command not found for <%s>", adb_shell_hcidump);
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ i_position = (char *) memchr(i_position + 1, '\n', used_buffer_length);
+ if (i_position) {
+ i_position += 1;
+ if (!strncmp(state_line_position, "Can't access device: Permission denied", 38)) {
+ ws_warning("No permission for command <%s>", adb_shell_hcidump);
+ used_buffer_length = 0;
+ closesocket(sock);
+ sock = INVALID_SOCKET;
+ break;
+ }
+ memmove(data, i_position, used_buffer_length - (i_position - data));
+ used_buffer_length = used_buffer_length - (ssize_t)(i_position - data);
+ break;
+ }
+ }
+ }
+
+ if (sock == INVALID_SOCKET) {
+ sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number);
+ if (sock == INVALID_SOCKET)
+ return EXIT_CODE_INVALID_SOCKET_4;
+
+ result = adb_send(sock, adb_shell_su_hcidump);
+ if (result) {
+ ws_warning("Error while starting capture by sending command: <%s>", adb_shell_su_hcidump);
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ used_buffer_length = 0;
+ while (endless_loop) {
+ char *i_position;
+
+ errno = 0;
+ length = recv(sock, data + used_buffer_length, (int)(PACKET_LENGTH - used_buffer_length), 0);
+ if (errno == EAGAIN
+#if EWOULDBLOCK != EAGAIN
+ || errno == EWOULDBLOCK
+#endif
+ ) {
+ continue;
+ }
+ else if (errno != 0) {
+ ws_warning("ERROR capture: %s", strerror(errno));
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ if (length <= 0) {
+ ws_warning("Broken socket connection.");
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ used_buffer_length += length;
+ i_position = (char *) memchr(data, '\n', used_buffer_length);
+ if (i_position && i_position < data + used_buffer_length) {
+ if (!strncmp(data, "/system/bin/sh: su: not found", 29)) {
+ ws_warning("Command 'su' not found for <%s>", adb_shell_su_hcidump);
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ i_position = (char *) memchr(i_position + 1, '\n', used_buffer_length);
+ if (i_position) {
+ i_position += 1;
+ memmove(data, i_position, used_buffer_length - (i_position - data));
+ used_buffer_length = used_buffer_length - (ssize_t)(i_position - data);
+ break;
+ }
+ }
+ }
+ }
+
+ while (endless_loop) {
+ errno = 0;
+ length = recv(sock, data + used_buffer_length, (int)(PACKET_LENGTH - used_buffer_length), 0);
+ if (errno == EAGAIN
+#if EWOULDBLOCK != EAGAIN
+ || errno == EWOULDBLOCK
+#endif
+ ) {
+ continue;
+ }
+ else if (errno != 0) {
+ ws_warning("ERROR capture: %s", strerror(errno));
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ if (length <= 0) {
+ ws_warning("Broken socket connection.");
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ while (endless_loop) {
+ if (used_buffer_length + length >= 1) {
+ hex_data = data + 29;
+ hex = g_ascii_strtoll(hex_data, &new_hex_data, 16);
+
+ if ((hex == 0x01 && used_buffer_length + length >= 4) ||
+ (hex == 0x02 && used_buffer_length + length >= 5) ||
+ (hex == 0x04 && used_buffer_length + length >= 3)) {
+
+ if (hex == 0x01) {
+ hex_data = new_hex_data;
+ hex = g_ascii_strtoll(hex_data, &new_hex_data, 16);
+ if (hex < 0 || hex >= 256 || hex_data == new_hex_data) {
+ ws_warning("data format %s", strerror(errno));
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ hex_data = new_hex_data;
+ hex = g_ascii_strtoll(hex_data, &new_hex_data, 16);
+ if (hex < 0 || hex >= 256 || hex_data == new_hex_data) {
+ ws_warning("data format %s", strerror(errno));
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ hex_data = new_hex_data;
+ hex = g_ascii_strtoll(hex_data, &new_hex_data, 16);
+
+ raw_length = hex + 4;
+ } else if (hex == 0x04) {
+ hex_data = new_hex_data;
+ hex = g_ascii_strtoll(hex_data, &new_hex_data, 16);
+ if (hex < 0 || hex >= 256 || hex_data == new_hex_data) {
+ ws_warning("data format %s", strerror(errno));
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ hex_data = new_hex_data;
+ hex = g_ascii_strtoll(hex_data, &new_hex_data, 16);
+
+ raw_length = hex + 3;
+ } else if (hex == 0x02) {
+ hex_data = new_hex_data;
+ hex = g_ascii_strtoll(hex_data, &new_hex_data, 16);
+ if (hex < 0 || hex >= 256 || hex_data == new_hex_data) {
+ ws_warning("data format %s", strerror(errno));
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ hex_data = new_hex_data;
+ hex = g_ascii_strtoll(hex_data, &new_hex_data, 16);
+ if (hex < 0 || hex >= 256 || hex_data == new_hex_data) {
+ ws_warning("data format %s", strerror(errno));
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ hex_data = new_hex_data;
+ hex = g_ascii_strtoll(hex_data, &new_hex_data, 16);
+ raw_length = hex + 5;
+
+ hex_data = new_hex_data;
+ hex = g_ascii_strtoll(hex_data, &new_hex_data, 16);
+ raw_length += hex << 8;
+ }
+
+ } else {
+ ws_warning("bad raw stream");
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+ } else {
+ used_buffer_length += length;
+ break;
+ }
+
+ frame_length = raw_length * 3 + (raw_length / 20) * 4 + ((raw_length % 20) ? 2 : -2) + 29;
+
+ if ((used_buffer_length + length) < frame_length) {
+ used_buffer_length += length;
+ break;
+ }
+
+ if (8 == sscanf(data, "%04d-%02d-%02d %02d:%02d:%02d.%06d %c",
+ &date.tm_year, &date.tm_mon, &date.tm_mday, &date.tm_hour,
+ &date.tm_min, &date.tm_sec, &ms, &direction_character)) {
+
+ ws_debug("time %04d-%02d-%02d %02d:%02d:%02d.%06d %c",
+ date.tm_year, date.tm_mon, date.tm_mday, date.tm_hour,
+ date.tm_min, date.tm_sec, ms, direction_character);
+ date.tm_mon -= 1;
+ date.tm_year -= 1900;
+ date.tm_isdst = -1;
+ ts = mktime(&date);
+
+ new_hex_data = data + 29;
+ }
+
+ captured_length = 0;
+
+ while ((long)(new_hex_data - data + sizeof(own_pcap_bluetooth_h4_header)) < frame_length) {
+ hex_data = new_hex_data;
+ hex = g_ascii_strtoll(hex_data, &new_hex_data, 16);
+
+ packet[sizeof(own_pcap_bluetooth_h4_header) + captured_length] = (char) hex;
+ captured_length += 1;
+ }
+
+ h4_header->direction = GINT32_TO_BE(direction_character == '>');
+
+ endless_loop = extcap_dumper_dump(extcap_dumper, fifo, packet,
+ captured_length + sizeof(own_pcap_bluetooth_h4_header),
+ captured_length + sizeof(own_pcap_bluetooth_h4_header),
+ ts,
+ ms * 1000);
+
+ memmove(data, data + frame_length, (size_t)(used_buffer_length + length - frame_length));
+ used_buffer_length = (ssize_t)(used_buffer_length + length - frame_length);
+ length = 0;
+ }
+ }
+
+ closesocket(sock);
+ return EXIT_CODE_SUCCESS;
+}
+
+/*----------------------------------------------------------------------------*/
+/* Android Bluetooth External Parser */
+/*----------------------------------------------------------------------------*/
+
+#define BLUEDROID_H4_PACKET_TYPE 0
+#define BLUEDROID_TIMESTAMP_SIZE 8
+#define BLUEDROID_H4_SIZE 1
+
+static const uint64_t BLUEDROID_TIMESTAMP_BASE = UINT64_C(0x00dcddb30f2f8000);
+
+#define BLUEDROID_H4_PACKET_TYPE_HCI_CMD 0x01
+#define BLUEDROID_H4_PACKET_TYPE_ACL 0x02
+#define BLUEDROID_H4_PACKET_TYPE_SCO 0x03
+#define BLUEDROID_H4_PACKET_TYPE_HCI_EVT 0x04
+
+#define BLUEDROID_DIRECTION_SENT 0
+#define BLUEDROID_DIRECTION_RECV 1
+
+static int adb_forward(char *serial_number, const char *adb_server_ip, unsigned short *adb_server_tcp_port,
+ unsigned short local_tcp_port, unsigned short server_tcp_port) {
+ socket_handle_t sock;
+ int result;
+ static char helpful_packet[PACKET_LENGTH];
+ static const char *adb_forward_template = "%s%s:forward:tcp:%05u;tcp:%05u";
+
+ sock = adb_connect(adb_server_ip, adb_server_tcp_port);
+ if (sock == INVALID_SOCKET)
+ return EXIT_CODE_INVALID_SOCKET_5;
+
+ result = snprintf(helpful_packet, PACKET_LENGTH, adb_forward_template, (serial_number) ? "host-serial:" : "host", (serial_number) ? serial_number: "", local_tcp_port, server_tcp_port);
+ if (result <= 0 || result > PACKET_LENGTH) {
+ ws_warning("Error while completing adb packet");
+ closesocket(sock);
+ return EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_12;
+ }
+
+ result = adb_send(sock, helpful_packet);
+ closesocket(sock);
+
+ return result;
+}
+
+static int capture_android_bluetooth_external_parser(char *interface,
+ char *fifo, const char *adb_server_ip, unsigned short *adb_server_tcp_port,
+ unsigned short *bt_server_tcp_port, unsigned int bt_forward_socket, const char *bt_local_ip,
+ unsigned short *bt_local_tcp_port) {
+ struct extcap_dumper extcap_dumper;
+ static char buffer[PACKET_LENGTH];
+ uint64_t *timestamp;
+ char *packet = buffer + BLUEDROID_TIMESTAMP_SIZE - sizeof(own_pcap_bluetooth_h4_header); /* skip timestamp (8 bytes) and reuse its space for header */
+ own_pcap_bluetooth_h4_header *h4_header;
+ uint8_t *payload = packet + sizeof(own_pcap_bluetooth_h4_header);
+ const char *adb_tcp_bluedroid_external_parser_template = "tcp:%05u";
+ socklen_t slen;
+ ssize_t length;
+ ssize_t used_buffer_length = 0;
+ uint64_t ts;
+ socket_handle_t sock;
+ struct sockaddr_in server;
+ int captured_length;
+ char *serial_number;
+ static unsigned int id = 1;
+ struct sockaddr_in client;
+
+ SET_DATA(timestamp, value_u64, buffer);
+ SET_DATA(h4_header, value_own_pcap_bluetooth_h4_header, packet);
+
+ extcap_dumper = extcap_dumper_open(fifo, EXTCAP_ENCAP_BLUETOOTH_H4_WITH_PHDR);
+ serial_number = get_serial_from_interface(interface);
+
+ if (bt_forward_socket) {
+ if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {
+ ws_warning("Cannot open system TCP socket: %s", strerror(errno));
+ return EXIT_CODE_GENERIC;
+ }
+
+ ws_debug("Using config: Server TCP Port=%u, Local IP=%s, Local TCP Port=%u",
+ *bt_server_tcp_port, bt_local_ip, *bt_local_tcp_port);
+
+ if (*bt_local_tcp_port != 0) {
+ int result;
+
+ result = adb_forward(serial_number, adb_server_ip, adb_server_tcp_port, *bt_local_tcp_port, *bt_server_tcp_port);
+ ws_debug("DO: adb forward tcp:%u (local) tcp:%u (remote) result=%i",
+ *bt_local_tcp_port, *bt_server_tcp_port, result);
+ }
+
+ memset(&server, 0 , sizeof(server));
+ server.sin_family = AF_INET;
+ server.sin_port = GINT16_TO_BE(*bt_local_tcp_port);
+ ws_inet_pton4(bt_local_ip, (ws_in4_addr *)&(server.sin_addr.s_addr));
+
+ useSndTimeout(sock);
+
+ if (connect(sock, (struct sockaddr *) &server, sizeof(server)) == SOCKET_ERROR) {
+ ws_warning("<%s> Please check that adb daemon is running.", strerror(errno));
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ slen = (socklen_t)sizeof(client);
+ if (getsockname(sock, (struct sockaddr *) &client, &slen)) {
+ ws_warning("getsockname: %s", strerror(errno));
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ if (slen != sizeof(client)) {
+ ws_warning("incorrect length");
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ ws_debug("Client port %u", GUINT16_FROM_BE(client.sin_port));
+ } else {
+ int result;
+
+ sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number);
+ if (sock == INVALID_SOCKET)
+ return EXIT_CODE_INVALID_SOCKET_6;
+
+ result = snprintf((char *) buffer, PACKET_LENGTH, adb_tcp_bluedroid_external_parser_template, *bt_server_tcp_port);
+ if (result <= 0 || result > PACKET_LENGTH) {
+ ws_warning("Error while completing adb packet");
+ closesocket(sock);
+ return EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_14;
+ }
+
+ result = adb_send(sock, buffer);
+ if (result) {
+ ws_warning("Error while forwarding adb port");
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+ }
+
+ while (endless_loop) {
+ errno = 0;
+ length = recv(sock, buffer + used_buffer_length, (int)(PACKET_LENGTH - used_buffer_length), 0);
+ if (errno == EAGAIN
+#if EWOULDBLOCK != EAGAIN
+ || errno == EWOULDBLOCK
+#endif
+ ) {
+ continue;
+ }
+ else if (errno != 0) {
+ ws_warning("ERROR capture: %s", strerror(errno));
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ if (length <= 0) {
+ if (bt_forward_socket) {
+ /* NOTE: Workaround... It seems that Bluedroid is slower and we can connect to socket that are not really ready... */
+ ws_warning("Broken socket connection. Try reconnect.");
+ closesocket(sock);
+
+ if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {
+ ws_warning("%s", strerror(errno));
+ return EXIT_CODE_GENERIC;
+ }
+
+ server.sin_family = AF_INET;
+ server.sin_port = GINT16_TO_BE(*bt_local_tcp_port);
+ ws_inet_pton4(bt_local_ip, (ws_in4_addr *)&(server.sin_addr.s_addr));
+
+ useSndTimeout(sock);
+
+ if (connect(sock, (struct sockaddr *) &server, sizeof(server)) == SOCKET_ERROR) {
+ ws_warning("ERROR reconnect: <%s> Please check that adb daemon is running.", strerror(errno));
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+ } else {
+ ws_warning("Broken socket connection.");
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ continue;
+ }
+
+ used_buffer_length += length;
+
+ ws_debug("Received: length=%"PRIdMAX, (intmax_t)length);
+
+ while (((payload[BLUEDROID_H4_PACKET_TYPE] == BLUEDROID_H4_PACKET_TYPE_HCI_CMD || payload[BLUEDROID_H4_PACKET_TYPE] == BLUEDROID_H4_PACKET_TYPE_SCO) &&
+ used_buffer_length >= BLUEDROID_TIMESTAMP_SIZE + BLUEDROID_H4_SIZE + 2 + 1 &&
+ BLUEDROID_TIMESTAMP_SIZE + BLUEDROID_H4_SIZE + 2 + payload[BLUEDROID_H4_SIZE + 2] + 1 <= used_buffer_length) ||
+ (payload[BLUEDROID_H4_PACKET_TYPE] == BLUEDROID_H4_PACKET_TYPE_ACL &&
+ used_buffer_length >= BLUEDROID_TIMESTAMP_SIZE + BLUEDROID_H4_SIZE + 2 + 2 &&
+ BLUEDROID_TIMESTAMP_SIZE + BLUEDROID_H4_SIZE + 2 + payload[BLUEDROID_H4_SIZE + 2] + (payload[BLUEDROID_H4_SIZE + 2 + 1] << 8) + 2 <= used_buffer_length) ||
+ (payload[BLUEDROID_H4_PACKET_TYPE] == BLUEDROID_H4_PACKET_TYPE_SCO &&
+ used_buffer_length >= BLUEDROID_TIMESTAMP_SIZE + BLUEDROID_H4_SIZE + 2 + 1 &&
+ BLUEDROID_TIMESTAMP_SIZE + BLUEDROID_H4_SIZE + 2 + payload[BLUEDROID_H4_SIZE + 2] + 1 <= used_buffer_length) ||
+ (payload[BLUEDROID_H4_PACKET_TYPE] == BLUEDROID_H4_PACKET_TYPE_HCI_EVT &&
+ used_buffer_length >= BLUEDROID_TIMESTAMP_SIZE + BLUEDROID_H4_SIZE + 1 + 1 &&
+ BLUEDROID_TIMESTAMP_SIZE + BLUEDROID_H4_SIZE + 1 + payload[BLUEDROID_H4_SIZE + 1] + 1 <= used_buffer_length)) {
+
+ ts = GINT64_FROM_BE(*timestamp);
+
+ switch (payload[BLUEDROID_H4_PACKET_TYPE]) {
+ case BLUEDROID_H4_PACKET_TYPE_HCI_CMD:
+ h4_header->direction = GINT32_TO_BE(BLUEDROID_DIRECTION_SENT);
+
+ captured_length = (unsigned int)sizeof(own_pcap_bluetooth_h4_header) + payload[3] + 4;
+
+ length = sizeof(own_pcap_bluetooth_h4_header) + BLUEDROID_H4_SIZE + 2 + 1 + payload[3];
+
+ break;
+ case BLUEDROID_H4_PACKET_TYPE_ACL:
+ h4_header->direction = (payload[2] & 0x80) ? GINT32_TO_BE(BLUEDROID_DIRECTION_RECV) : GINT32_TO_BE(BLUEDROID_DIRECTION_SENT);
+
+ captured_length = (unsigned int)sizeof(own_pcap_bluetooth_h4_header) + payload[3] + (payload[3 + 1] << 8) + 5;
+
+ length = sizeof(own_pcap_bluetooth_h4_header) + BLUEDROID_H4_SIZE + 2 + 2 + payload[3] + (ssize_t)(payload[3 + 1] << 8);
+
+ break;
+ case BLUEDROID_H4_PACKET_TYPE_SCO:
+ h4_header->direction = (payload[2] & 0x80) ? GINT32_TO_BE(BLUEDROID_DIRECTION_RECV) : GINT32_TO_BE(BLUEDROID_DIRECTION_SENT);
+
+ captured_length = (unsigned int)sizeof(own_pcap_bluetooth_h4_header) + payload[3] + 4;
+
+ length = sizeof(own_pcap_bluetooth_h4_header) + BLUEDROID_H4_SIZE + 2 + 1 + payload[3];
+
+ break;
+ case BLUEDROID_H4_PACKET_TYPE_HCI_EVT:
+ h4_header->direction = GINT32_TO_BE(BLUEDROID_DIRECTION_RECV);
+
+ captured_length = (unsigned int)sizeof(own_pcap_bluetooth_h4_header) + payload[2] + 3;
+
+ length = sizeof(own_pcap_bluetooth_h4_header) + BLUEDROID_H4_SIZE + 1 + 1 + payload[2];
+
+ break;
+ default:
+ ws_warning("Invalid stream");
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ ws_debug("\t Packet %u: used_buffer_length=%"PRIdMAX" length=%"PRIdMAX" captured_length=%i type=0x%02x", id, (intmax_t)used_buffer_length, (intmax_t)length, captured_length, payload[BLUEDROID_H4_PACKET_TYPE]);
+ if (payload[BLUEDROID_H4_PACKET_TYPE] == BLUEDROID_H4_PACKET_TYPE_HCI_EVT)
+ ws_debug("\t Packet: %02x %02x %02x", (unsigned int) payload[0], (unsigned int) payload[1], (unsigned int)payload[2]);
+ id +=1;
+
+ ts -= BLUEDROID_TIMESTAMP_BASE;
+
+ endless_loop = extcap_dumper_dump(extcap_dumper, fifo, packet,
+ captured_length,
+ captured_length,
+ (uint32_t)(ts / 1000000),
+ ((uint32_t)(ts % 1000000)) * 1000);
+
+ used_buffer_length -= length - sizeof(own_pcap_bluetooth_h4_header) + BLUEDROID_TIMESTAMP_SIZE;
+ if (used_buffer_length < 0) {
+ ws_warning("Internal Negative used buffer length.");
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+ memmove(buffer, packet + length, used_buffer_length);
+ }
+ }
+
+ closesocket(sock);
+ return EXIT_CODE_SUCCESS;
+}
+
+/*----------------------------------------------------------------------------*/
+/* Android Btsnoop Net */
+/*----------------------------------------------------------------------------*/
+
+static int capture_android_bluetooth_btsnoop_net(char *interface, char *fifo,
+ const char *adb_server_ip, unsigned short *adb_server_tcp_port) {
+ struct extcap_dumper extcap_dumper;
+ static char packet[PACKET_LENGTH];
+ ssize_t length;
+ ssize_t used_buffer_length = 0;
+ socket_handle_t sock;
+ const char *adb_tcp_btsnoop_net = "tcp:8872";
+ int result;
+ char *serial_number;
+ uint64_t ts;
+ static const uint64_t BTSNOOP_TIMESTAMP_BASE = UINT64_C(0x00dcddb30f2f8000);
+ uint32_t *reported_length;
+ uint32_t *captured_length;
+ uint32_t *flags;
+/* uint32_t *cumulative_dropped_packets; */
+ uint64_t *timestamp;
+ char *payload = packet + sizeof(own_pcap_bluetooth_h4_header) + 24;
+ own_pcap_bluetooth_h4_header *h4_header;
+
+ SET_DATA(reported_length, value_u32, packet + sizeof(own_pcap_bluetooth_h4_header) + 0);
+ SET_DATA(captured_length, value_u32, packet + sizeof(own_pcap_bluetooth_h4_header) + 4);
+ SET_DATA(flags, value_u32, packet + sizeof(own_pcap_bluetooth_h4_header) + 8);
+/* SET_DATA(cumulative_dropped_packets, value_u32, packet + sizeof(own_pcap_bluetooth_h4_header) + 12); */
+ SET_DATA(timestamp, value_u64, packet + sizeof(own_pcap_bluetooth_h4_header) + 16);
+ SET_DATA(h4_header, value_own_pcap_bluetooth_h4_header, payload - sizeof(own_pcap_bluetooth_h4_header));
+
+ extcap_dumper = extcap_dumper_open(fifo, EXTCAP_ENCAP_BLUETOOTH_H4_WITH_PHDR);
+ serial_number = get_serial_from_interface(interface);
+ sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number);
+ if (sock == INVALID_SOCKET)
+ return EXIT_CODE_INVALID_SOCKET_7;
+
+ result = adb_send(sock, adb_tcp_btsnoop_net);
+ if (result) {
+ ws_warning("Error while sending command <%s>", adb_tcp_btsnoop_net);
+ closesocket(sock);
+ return EXIT_CODE_ERROR_WHILE_SENDING_ADB_PACKET_2;
+ }
+
+ /* Read "btsnoop" header - 16 bytes */
+ while (used_buffer_length < BTSNOOP_HDR_LEN) {
+ length = recv(sock, packet + used_buffer_length, (int)(BTSNOOP_HDR_LEN - used_buffer_length), 0);
+ if (length <= 0) {
+ ws_warning("Broken socket connection.");
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+ used_buffer_length += length;
+ }
+ used_buffer_length = 0;
+
+ while (endless_loop) {
+ errno = 0;
+ length = recv(sock, packet + used_buffer_length + sizeof(own_pcap_bluetooth_h4_header),
+ (int)(PACKET_LENGTH - sizeof(own_pcap_bluetooth_h4_header) - used_buffer_length), 0);
+ if (errno == EAGAIN
+#if EWOULDBLOCK != EAGAIN
+ || errno == EWOULDBLOCK
+#endif
+ ) {
+ continue;
+ }
+ else if (errno != 0) {
+ ws_warning("ERROR capture: %s", strerror(errno));
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ if (length <= 0) {
+ ws_warning("Broken socket connection.");
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ used_buffer_length += length;
+
+ while (used_buffer_length >= 24 &&
+ used_buffer_length >= (int) (24 + GINT32_FROM_BE(*captured_length))) {
+ int32_t direction;
+
+ ts = GINT64_FROM_BE(*timestamp);
+ ts -= BTSNOOP_TIMESTAMP_BASE;
+
+ direction = GINT32_FROM_BE(*flags) & 0x01;
+ h4_header->direction = GINT32_TO_BE(direction);
+
+ endless_loop = extcap_dumper_dump(extcap_dumper, fifo,
+ payload - sizeof(own_pcap_bluetooth_h4_header),
+ GINT32_FROM_BE(*captured_length) + sizeof(own_pcap_bluetooth_h4_header),
+ GINT32_FROM_BE(*reported_length) + sizeof(own_pcap_bluetooth_h4_header),
+ (uint32_t)(ts / 1000000),
+ ((uint32_t)(ts % 1000000)) * 1000);
+
+ used_buffer_length -= 24 + GINT32_FROM_BE(*captured_length);
+ if (used_buffer_length < 0) {
+ ws_warning("Internal Negative used buffer length.");
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ if (used_buffer_length > 0)
+ memmove(packet + sizeof(own_pcap_bluetooth_h4_header), payload + GINT32_FROM_BE(*captured_length), used_buffer_length);
+ }
+ }
+
+ closesocket(sock);
+ return EXIT_CODE_SUCCESS;
+}
+
+/*----------------------------------------------------------------------------*/
+/* Android Logcat Text*/
+/*----------------------------------------------------------------------------*/
+
+
+static int capture_android_logcat_text(char *interface, char *fifo,
+ const char *adb_server_ip, unsigned short *adb_server_tcp_port,
+ int logcat_ignore_log_buffer, const char *logcat_custom_parameter) {
+ struct extcap_dumper extcap_dumper;
+ static char packet[PACKET_LENGTH];
+ ssize_t length;
+ size_t used_buffer_length = 0;
+ socket_handle_t sock;
+ const char *protocol_name;
+ size_t exported_pdu_headers_size = 0;
+ struct exported_pdu_header exported_pdu_header_protocol_normal;
+ struct exported_pdu_header *exported_pdu_header_protocol;
+ struct exported_pdu_header exported_pdu_header_end = {0, 0};
+ static const char *wireshark_protocol_logcat_text = "logcat_text_threadtime";
+ const char *adb_logcat_template = "shell:export ANDROID_LOG_TAGS=\"\" ; exec logcat -v threadtime%s%s %s";
+ char *serial_number = NULL;
+ int result;
+ char *pos;
+ const char *logcat_buffer;
+ const char *logcat_log_buffer;
+
+ extcap_dumper = extcap_dumper_open(fifo, EXTCAP_ENCAP_WIRESHARK_UPPER_PDU);
+
+ exported_pdu_header_protocol_normal.tag = GUINT16_TO_BE(EXP_PDU_TAG_DISSECTOR_NAME);
+ exported_pdu_header_protocol_normal.length = GUINT16_TO_BE(strlen(wireshark_protocol_logcat_text) + 2);
+
+ serial_number = get_serial_from_interface(interface);
+ sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number);
+ if (sock == INVALID_SOCKET)
+ return EXIT_CODE_INVALID_SOCKET_8;
+
+ if (is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_MAIN) || is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_TEXT_MAIN))
+ logcat_buffer = " -b main";
+ else if (is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_SYSTEM) || is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_TEXT_SYSTEM))
+ logcat_buffer = " -b system";
+ else if (is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_RADIO) || is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_TEXT_RADIO))
+ logcat_buffer = " -b radio";
+ else if (is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_EVENTS) || is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_TEXT_EVENTS))
+ logcat_buffer = " -b events";
+ else if (is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_TEXT_CRASH))
+ logcat_buffer = " -b crash";
+ else {
+ ws_warning("Unknown interface: <%s>", interface);
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ if (logcat_ignore_log_buffer)
+ logcat_log_buffer = " -T 1";
+ else
+ logcat_log_buffer = "";
+
+ if (!logcat_custom_parameter)
+ logcat_custom_parameter = "";
+
+ result = snprintf((char *) packet, PACKET_LENGTH, adb_logcat_template, logcat_buffer, logcat_log_buffer, logcat_custom_parameter);
+ if (result <= 0 || result > PACKET_LENGTH) {
+ ws_warning("Error while completing adb packet");
+ closesocket(sock);
+ return EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_17;
+ }
+
+ result = adb_send(sock, packet);
+ if (result) {
+ ws_warning("Error while sending command <%s>", packet);
+ closesocket(sock);
+ return EXIT_CODE_ERROR_WHILE_SENDING_ADB_PACKET_3;
+ }
+
+ protocol_name = wireshark_protocol_logcat_text;
+ exported_pdu_header_protocol = &exported_pdu_header_protocol_normal;
+
+ memcpy(packet, exported_pdu_header_protocol, sizeof(struct exported_pdu_header));
+ exported_pdu_headers_size += sizeof(struct exported_pdu_header);
+
+ memcpy(packet + exported_pdu_headers_size, protocol_name, GUINT16_FROM_BE(exported_pdu_header_protocol->length) - 2);
+ exported_pdu_headers_size += GUINT16_FROM_BE(exported_pdu_header_protocol->length);
+
+ packet[exported_pdu_headers_size - 1] = 0;
+ packet[exported_pdu_headers_size - 2] = 0;
+
+ memcpy(packet + exported_pdu_headers_size, &exported_pdu_header_end, sizeof(struct exported_pdu_header));
+ exported_pdu_headers_size += sizeof(struct exported_pdu_header) + GUINT16_FROM_BE(exported_pdu_header_end.length);
+
+ used_buffer_length = 0;
+ while (endless_loop) {
+ errno = 0;
+ length = recv(sock, packet + exported_pdu_headers_size + used_buffer_length, (int)(PACKET_LENGTH - exported_pdu_headers_size - used_buffer_length), 0);
+ if (errno == EAGAIN
+#if EWOULDBLOCK != EAGAIN
+ || errno == EWOULDBLOCK
+#endif
+ ) {
+ continue;
+ }
+ else if (errno != 0) {
+ ws_warning("ERROR capture: %s", strerror(errno));
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ if (length <= 0) {
+ ws_warning("Broken socket connection. Try reconnect.");
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ used_buffer_length += length;
+
+ while (used_buffer_length > 0 && (pos = (char *) memchr(packet + exported_pdu_headers_size, '\n', used_buffer_length))) {
+ int ms;
+ struct tm* date;
+ time_t seconds;
+ time_t secs = 0;
+ int nsecs = 0;
+ time_t t;
+
+ length = (ssize_t)(pos - packet) + 1;
+
+ t = time(NULL);
+ date = localtime(&t);
+ if (!date)
+ continue;
+ if (6 == sscanf(packet + exported_pdu_headers_size, "%d-%d %d:%d:%d.%d", &date->tm_mon, &date->tm_mday, &date->tm_hour,
+ &date->tm_min, &date->tm_sec, &ms)) {
+ date->tm_mon -= 1;
+ date->tm_isdst = -1;
+ seconds = mktime(date);
+ secs = (time_t) seconds;
+ nsecs = (int) (ms * 1e6);
+ }
+
+ endless_loop = extcap_dumper_dump(extcap_dumper, fifo, packet,
+ length,
+ length,
+ secs, nsecs);
+
+ memmove(packet + exported_pdu_headers_size, packet + length, used_buffer_length + exported_pdu_headers_size - length);
+ used_buffer_length -= length - exported_pdu_headers_size;
+ }
+ }
+
+ closesocket(sock);
+ return EXIT_CODE_SUCCESS;
+}
+
+/*----------------------------------------------------------------------------*/
+/* Android Logger / Logcat */
+/*----------------------------------------------------------------------------*/
+
+static int capture_android_logcat(char *interface, char *fifo,
+ const char *adb_server_ip, unsigned short *adb_server_tcp_port) {
+ struct extcap_dumper extcap_dumper;
+ static char packet[PACKET_LENGTH];
+ ssize_t length;
+ size_t used_buffer_length = 0;
+ socket_handle_t sock;
+ const char *protocol_name;
+ size_t exported_pdu_headers_size = 0;
+ struct exported_pdu_header exported_pdu_header_protocol_events;
+ struct exported_pdu_header exported_pdu_header_protocol_normal;
+ struct exported_pdu_header *exported_pdu_header_protocol;
+ struct exported_pdu_header exported_pdu_header_end = {0, 0};
+ static const char *wireshark_protocol_logcat = "logcat";
+ static const char *wireshark_protocol_logcat_events = "logcat_events";
+ const char *adb_command;
+ uint16_t *payload_length;
+ uint16_t *try_header_size;
+ uint32_t *timestamp_secs;
+ uint32_t *timestamp_nsecs;
+ uint16_t header_size;
+ int result;
+ char *serial_number = NULL;
+
+ extcap_dumper = extcap_dumper_open(fifo, EXTCAP_ENCAP_WIRESHARK_UPPER_PDU);
+
+ exported_pdu_header_protocol_events.tag = GUINT16_TO_BE(EXP_PDU_TAG_DISSECTOR_NAME);
+ exported_pdu_header_protocol_events.length = GUINT16_TO_BE(strlen(wireshark_protocol_logcat_events) + 2);
+
+ exported_pdu_header_protocol_normal.tag = GUINT16_TO_BE(EXP_PDU_TAG_DISSECTOR_NAME);
+ exported_pdu_header_protocol_normal.length = GUINT16_TO_BE(strlen(wireshark_protocol_logcat) + 2);
+
+ serial_number = get_serial_from_interface(interface);
+ sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number);
+ if (sock == INVALID_SOCKET)
+ return EXIT_CODE_INVALID_SOCKET_9;
+
+ adb_command = interface_to_logbuf(interface);
+ if (!adb_command) {
+ ws_warning("Unknown interface: <%s>", interface);
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ result = adb_send(sock, adb_command);
+ if (result) {
+ ws_warning("Error while sending command <%s>", adb_command);
+ closesocket(sock);
+ return EXIT_CODE_ERROR_WHILE_SENDING_ADB_PACKET_4;
+ }
+
+ if (is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_EVENTS))
+ {
+ protocol_name = wireshark_protocol_logcat_events;
+ exported_pdu_header_protocol = &exported_pdu_header_protocol_events;
+ } else {
+ protocol_name = wireshark_protocol_logcat;
+ exported_pdu_header_protocol = &exported_pdu_header_protocol_normal;
+ }
+
+ memcpy(packet, exported_pdu_header_protocol, sizeof(struct exported_pdu_header));
+ exported_pdu_headers_size += sizeof(struct exported_pdu_header);
+
+ memcpy(packet + exported_pdu_headers_size, protocol_name, GUINT16_FROM_BE(exported_pdu_header_protocol->length) - 2);
+ exported_pdu_headers_size += GUINT16_FROM_BE(exported_pdu_header_protocol->length);
+
+ packet[exported_pdu_headers_size - 1] = 0;
+ packet[exported_pdu_headers_size - 2] = 0;
+
+ memcpy(packet + exported_pdu_headers_size, &exported_pdu_header_end, sizeof(struct exported_pdu_header));
+ exported_pdu_headers_size += sizeof(struct exported_pdu_header) + GUINT16_FROM_BE(exported_pdu_header_end.length);
+
+ SET_DATA(payload_length, value_u16, packet + exported_pdu_headers_size + 0);
+ SET_DATA(try_header_size, value_u16, packet + exported_pdu_headers_size + 2);
+ SET_DATA(timestamp_secs, value_u32, packet + exported_pdu_headers_size + 12);
+ SET_DATA(timestamp_nsecs, value_u32, packet + exported_pdu_headers_size + 16);
+
+ while (endless_loop) {
+ errno = 0;
+ length = recv(sock, packet + exported_pdu_headers_size + used_buffer_length, (int)(PACKET_LENGTH - exported_pdu_headers_size - used_buffer_length), 0);
+ if (errno == EAGAIN
+#if EWOULDBLOCK != EAGAIN
+ || errno == EWOULDBLOCK
+#endif
+ ) {
+ continue;
+ }
+ else if (errno != 0) {
+ ws_warning("ERROR capture: %s", strerror(errno));
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ if (length <= 0) {
+ while (endless_loop) {
+ ws_warning("Broken socket connection. Try reconnect.");
+ used_buffer_length = 0;
+ closesocket(sock);
+
+ sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number);
+ if (sock == INVALID_SOCKET)
+ return EXIT_CODE_INVALID_SOCKET_10;
+
+ result = adb_send(sock, adb_command);
+ if (result) {
+ ws_warning("WARNING: Error while sending command <%s>", adb_command);
+ continue;
+ }
+
+ break;
+ }
+ }
+
+ used_buffer_length += length + exported_pdu_headers_size;
+
+ if (*try_header_size != 24)
+ header_size = 20;
+ else
+ header_size = *try_header_size;
+
+ length = (*payload_length) + header_size + (ssize_t)exported_pdu_headers_size;
+
+ while (used_buffer_length >= exported_pdu_headers_size + header_size && (size_t)length <= used_buffer_length) {
+ endless_loop = extcap_dumper_dump(extcap_dumper, fifo, packet,
+ length,
+ length,
+ *timestamp_secs, *timestamp_nsecs);
+
+ memmove(packet + exported_pdu_headers_size, packet + length, used_buffer_length - length);
+ used_buffer_length -= length;
+ used_buffer_length += exported_pdu_headers_size;
+
+
+ length = (*payload_length) + header_size + (ssize_t)exported_pdu_headers_size;
+
+ if (*try_header_size != 24)
+ header_size = 20;
+ else
+ header_size = *try_header_size;
+ }
+ used_buffer_length -= exported_pdu_headers_size;
+ }
+
+ closesocket(sock);
+
+ return EXIT_CODE_SUCCESS;
+}
+
+
+/*----------------------------------------------------------------------------*/
+/* Android Wifi Tcpdump */
+/* The Tcpdump sends data in pcap format. So for using the extcap_dumper we */
+/* need to unpack the pcap and then send the packet data to the dumper. */
+/*----------------------------------------------------------------------------*/
+static int capture_android_tcpdump(char *interface, char *fifo,
+ char *capture_filter, const char *adb_server_ip,
+ unsigned short *adb_server_tcp_port) {
+ static const char *const adb_shell_tcpdump_format = "exec:tcpdump -U -n -s 0 -u -i %s -w - %s 2>/dev/null";
+ static const char *const regex_interface = INTERFACE_ANDROID_TCPDUMP "-(?<iface>.*?)-(?<serial>.*)";
+ struct extcap_dumper extcap_dumper;
+ static char data[PACKET_LENGTH];
+ ssize_t length;
+ ssize_t used_buffer_length = 0;
+ ssize_t frame_length=0;
+ socket_handle_t sock;
+ int result;
+ char *iface = NULL;
+ char *serial_number = NULL;
+ bool nanosecond_timestamps;
+ bool swap_byte_order;
+ pcap_hdr_t *global_header;
+ pcaprec_hdr_t p_header;
+ GRegex *regex = NULL;
+ GError *err = NULL;
+ GMatchInfo *match = NULL;
+ char *tcpdump_cmd = NULL;
+ char *quoted_filter = NULL;
+
+ regex = g_regex_new(regex_interface, G_REGEX_RAW, (GRegexMatchFlags)0, &err);
+ if (!regex) {
+ ws_warning("Failed to compile regex for tcpdump interface");
+ return EXIT_CODE_GENERIC;
+ }
+
+ g_regex_match(regex, interface, (GRegexMatchFlags)0, &match);
+ if (!g_match_info_matches(match)) {
+ ws_warning("Failed to determine iface name and serial number");
+ g_regex_unref(regex);
+ return EXIT_CODE_GENERIC;
+ }
+
+ iface = g_match_info_fetch_named(match, "iface");
+ serial_number = g_match_info_fetch_named(match, "serial");
+ g_match_info_free(match);
+ g_regex_unref(regex);
+
+ /* First check for the device if it is connected or not */
+ sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number);
+ g_free(serial_number);
+ if (sock == INVALID_SOCKET) {
+ g_free(iface);
+ return EXIT_CODE_INVALID_SOCKET_11;
+ }
+
+ quoted_filter = g_shell_quote(capture_filter ? capture_filter : "");
+ tcpdump_cmd = ws_strdup_printf(adb_shell_tcpdump_format, iface, quoted_filter);
+ g_free(iface);
+ g_free(quoted_filter);
+ result = adb_send(sock, tcpdump_cmd);
+ g_free(tcpdump_cmd);
+ if (result) {
+ ws_warning("Error while setting adb transport");
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ while (used_buffer_length < PCAP_GLOBAL_HEADER_LENGTH) {
+ errno = 0;
+ length = recv(sock, data + used_buffer_length, (int)(PCAP_GLOBAL_HEADER_LENGTH - used_buffer_length), 0);
+ if (errno == EAGAIN
+#if EWOULDBLOCK != EAGAIN
+ || errno == EWOULDBLOCK
+#endif
+ ) {
+ continue;
+ }
+ else if (errno != 0) {
+ ws_warning("ERROR capture: %s", strerror(errno));
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ if (length <= 0) {
+ ws_warning("Broken socket connection.");
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ used_buffer_length += length;
+ }
+
+ global_header = (pcap_hdr_t*) data;
+ switch (global_header->magic_number) {
+ case 0xa1b2c3d4:
+ swap_byte_order = false;
+ nanosecond_timestamps = false;
+ break;
+ case 0xd4c3b2a1:
+ swap_byte_order = true;
+ nanosecond_timestamps = false;
+ break;
+ case 0xa1b23c4d:
+ swap_byte_order = false;
+ nanosecond_timestamps = true;
+ break;
+ case 0x4d3cb2a1:
+ swap_byte_order = true;
+ nanosecond_timestamps = true;
+ break;
+ default:
+ ws_warning("Received incorrect magic");
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+ int encap = (int)(swap_byte_order ? GUINT32_SWAP_LE_BE(global_header->network) : global_header->network);
+#ifndef ANDROIDDUMP_USE_LIBPCAP
+ encap = wtap_pcap_encap_to_wtap_encap(encap);
+#endif
+ extcap_dumper = extcap_dumper_open(fifo, encap);
+
+ used_buffer_length = 0;
+ while (endless_loop) {
+ ssize_t offset = 0;
+
+ errno = 0;
+ length = recv(sock, data + used_buffer_length, (int)(PACKET_LENGTH - used_buffer_length), 0);
+ if (errno == EAGAIN
+#if EWOULDBLOCK != EAGAIN
+ || errno == EWOULDBLOCK
+#endif
+ ) {
+ continue;
+ }
+ else if (errno != 0) {
+ ws_warning("ERROR capture: %s", strerror(errno));
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ if (length <= 0) {
+ ws_warning("Broken socket connection.");
+ closesocket(sock);
+ return EXIT_CODE_GENERIC;
+ }
+
+ used_buffer_length += length;
+
+ while ((used_buffer_length - offset) > PCAP_RECORD_HEADER_LENGTH) {
+ p_header = *((pcaprec_hdr_t*) (data + offset));
+ if (swap_byte_order) {
+ p_header.ts_sec = GUINT32_SWAP_LE_BE(p_header.ts_sec);
+ p_header.ts_usec = GUINT32_SWAP_LE_BE(p_header.ts_usec);
+ p_header.incl_len = GUINT32_SWAP_LE_BE(p_header.incl_len);
+ p_header.orig_len = GUINT32_SWAP_LE_BE(p_header.orig_len);
+ }
+ if (!nanosecond_timestamps) {
+ p_header.ts_usec = p_header.ts_usec * 1000;
+ }
+
+ frame_length = p_header.incl_len + PCAP_RECORD_HEADER_LENGTH;
+ if ((used_buffer_length - offset) < frame_length) {
+ break; /* wait for complete packet */
+ }
+
+ /* It was observed that some times tcpdump reports the length of packet as '0' and that leads to the
+ * ( Warn Error "Less data was read than was expected" while reading )
+ * So to avoid this error we are checking for length of packet before passing it to dumper.
+ */
+ if (p_header.incl_len > 0) {
+ endless_loop = extcap_dumper_dump(extcap_dumper, fifo, data + offset + PCAP_RECORD_HEADER_LENGTH,
+ p_header.incl_len, p_header.orig_len, p_header.ts_sec, p_header.ts_usec);
+ }
+
+ offset += frame_length;
+ }
+
+ if (offset < used_buffer_length) {
+ memmove(data, data + offset, used_buffer_length - offset);
+ }
+ used_buffer_length -= offset;
+ }
+
+ closesocket(sock);
+ return EXIT_CODE_SUCCESS;
+}
+
+int main(int argc, char *argv[]) {
+ char *err_msg;
+ static const struct report_message_routines androiddummp_report_routines = {
+ failure_message,
+ failure_message,
+ open_failure_message,
+ read_failure_message,
+ write_failure_message,
+ cfile_open_failure_message,
+ cfile_dump_open_failure_message,
+ cfile_read_failure_message,
+ cfile_write_failure_message,
+ cfile_close_failure_message
+ };
+ int ret = EXIT_CODE_GENERIC;
+ int option_idx = 0;
+ int result;
+ const char *adb_server_ip = NULL;
+ unsigned short *adb_server_tcp_port = NULL;
+ unsigned int logcat_text = 0;
+ unsigned int logcat_ignore_log_buffer = 0;
+ const char *logcat_custom_parameter = NULL;
+ const char *default_adb_server_ip = "127.0.0.1";
+ unsigned short default_adb_server_tcp_port = 5037;
+ unsigned short local_adb_server_tcp_port;
+ unsigned short local_bt_server_tcp_port;
+ unsigned short local_bt_local_tcp_port;
+ unsigned short *bt_server_tcp_port = NULL;
+ unsigned int bt_forward_socket = 0;
+ const char *bt_local_ip = NULL;
+ unsigned short *bt_local_tcp_port = NULL;
+ unsigned short default_bt_server_tcp_port = 4330;
+ const char *default_bt_local_ip = "127.0.0.1";
+ unsigned short default_bt_local_tcp_port = 4330;
+ extcap_parameters * extcap_conf = NULL;
+ char *help_url;
+ char *help_header = NULL;
+
+ cmdarg_err_init(androiddump_cmdarg_err, androiddump_cmdarg_err);
+
+ /* Initialize log handler early so we can have proper logging during startup. */
+ extcap_log_init("androiddump");
+
+ /*
+ * Get credential information for later use.
+ */
+ init_process_policies();
+
+ /*
+ * Attempt to get the pathname of the directory containing the
+ * executable file.
+ */
+ err_msg = configuration_init(argv[0], NULL);
+ if (err_msg != NULL) {
+ ws_warning("Can't get pathname of directory containing the extcap program: %s.",
+ err_msg);
+ g_free(err_msg);
+ }
+
+ init_report_message("androiddump", &androiddummp_report_routines);
+
+ extcap_conf = g_new0(extcap_parameters, 1);
+
+ help_url = data_file_url("androiddump.html");
+ extcap_base_set_util_info(extcap_conf, argv[0], ANDROIDDUMP_VERSION_MAJOR, ANDROIDDUMP_VERSION_MINOR,
+ ANDROIDDUMP_VERSION_RELEASE, help_url);
+ g_free(help_url);
+
+ help_header = ws_strdup_printf(
+ " %s --extcap-interfaces [--adb-server-ip=<arg>] [--adb-server-tcp-port=<arg>]\n"
+ " %s --extcap-interface=INTERFACE --extcap-dlts\n"
+ " %s --extcap-interface=INTERFACE --extcap-config\n"
+ " %s --extcap-interface=INTERFACE --fifo=PATH_FILENAME --capture\n"
+ "\nINTERFACE has the form TYPE-DEVICEID:\n"
+ "\t""For example: "INTERFACE_ANDROID_BLUETOOTH_BTSNOOP_NET"-W3D7N15C29005648""\n"
+ "\n"
+ "\tTYPE is one of:\n"
+ "\t"INTERFACE_ANDROID_LOGCAT_MAIN"\n"
+ "\t"INTERFACE_ANDROID_LOGCAT_SYSTEM"\n"
+ "\t"INTERFACE_ANDROID_LOGCAT_RADIO"\n"
+ "\t"INTERFACE_ANDROID_LOGCAT_EVENTS"\n"
+ "\t"INTERFACE_ANDROID_LOGCAT_TEXT_MAIN"\n"
+ "\t"INTERFACE_ANDROID_LOGCAT_TEXT_SYSTEM"\n"
+ "\t"INTERFACE_ANDROID_LOGCAT_TEXT_RADIO"\n"
+ "\t"INTERFACE_ANDROID_LOGCAT_TEXT_EVENTS"\n"
+ "\t"INTERFACE_ANDROID_LOGCAT_TEXT_CRASH"\n"
+ "\t"INTERFACE_ANDROID_BLUETOOTH_HCIDUMP"\n"
+ "\t"INTERFACE_ANDROID_BLUETOOTH_EXTERNAL_PARSER"\n"
+ "\t"INTERFACE_ANDROID_BLUETOOTH_BTSNOOP_NET"\n"
+ "\t"INTERFACE_ANDROID_TCPDUMP"\n"
+ "\n"
+ "\t""DEVICEID is the identifier of the device provided by Android SDK (see \"adb devices\")\n"
+ "\t""For example: W3D7N15C29005648""\n",
+
+ argv[0], argv[0], argv[0], argv[0]);
+ extcap_help_add_header(extcap_conf, help_header);
+ g_free(help_header);
+
+ extcap_help_add_option(extcap_conf, "--help", "print this help");
+ extcap_help_add_option(extcap_conf, "--adb-server-ip <IP>", "the IP address of the ADB server");
+ extcap_help_add_option(extcap_conf, "--adb-server-tcp-port <port>", "the TCP port of the ADB server");
+ extcap_help_add_option(extcap_conf, "--logcat-text", "use logcat text format");
+ extcap_help_add_option(extcap_conf, "--logcat-ignore-log-buffer", "ignore log buffer");
+ extcap_help_add_option(extcap_conf, "--logcat-custom-options <text>", "use custom logcat parameters");
+ extcap_help_add_option(extcap_conf, "--bt-server-tcp-port <port>", "bluetooth server TCP port");
+ extcap_help_add_option(extcap_conf, "--bt-forward-socket <path>", "bluetooth forward socket");
+ extcap_help_add_option(extcap_conf, "--bt-local-ip <IP>", "the bluetooth local IP");
+ extcap_help_add_option(extcap_conf, "--bt-local-tcp-port <port>", "the bluetooth local TCP port");
+
+ ws_opterr = 0;
+ ws_optind = 0;
+
+ if (argc == 1) {
+ extcap_help_print(extcap_conf);
+ ret = EXIT_CODE_SUCCESS;
+ goto end;
+ }
+
+ while ((result = ws_getopt_long(argc, argv, "", longopts, &option_idx)) != -1) {
+ switch (result) {
+
+ case OPT_VERSION:
+ extcap_version_print(extcap_conf);
+ ret = EXIT_CODE_SUCCESS;
+ goto end;
+ case OPT_HELP:
+ extcap_help_print(extcap_conf);
+ ret = EXIT_CODE_SUCCESS;
+ goto end;
+ case OPT_CONFIG_ADB_SERVER_IP:
+ adb_server_ip = ws_optarg;
+ break;
+ case OPT_CONFIG_ADB_SERVER_TCP_PORT:
+ adb_server_tcp_port = &local_adb_server_tcp_port;
+ if (!ws_optarg){
+ ws_warning("Impossible exception. Parameter required argument, but there is no it right now.");
+ goto end;
+ }
+ if (!ws_strtou16(ws_optarg, NULL, adb_server_tcp_port)) {
+ ws_warning("Invalid adb server TCP port: %s", ws_optarg);
+ goto end;
+ }
+ break;
+ case OPT_CONFIG_LOGCAT_TEXT:
+ if (ws_optarg && !*ws_optarg)
+ logcat_text = true;
+ else
+ logcat_text = (g_ascii_strncasecmp(ws_optarg, "TRUE", 4) == 0);
+ break;
+ case OPT_CONFIG_LOGCAT_IGNORE_LOG_BUFFER:
+ if (ws_optarg == NULL || (ws_optarg && !*ws_optarg))
+ logcat_ignore_log_buffer = true;
+ else
+ logcat_ignore_log_buffer = (g_ascii_strncasecmp(ws_optarg, "TRUE", 4) == 0);
+ break;
+ case OPT_CONFIG_LOGCAT_CUSTOM_OPTIONS:
+ if (ws_optarg == NULL || (ws_optarg && *ws_optarg == '\0')) {
+ logcat_custom_parameter = NULL;
+ break;
+ }
+
+ if (g_regex_match_simple("(^|\\s)-[bBcDfgLnpPrv]", ws_optarg, G_REGEX_RAW, (GRegexMatchFlags)0)) {
+ ws_error("Found prohibited option in logcat-custom-options");
+ return EXIT_CODE_GENERIC;
+ }
+
+ logcat_custom_parameter = ws_optarg;
+
+ break;
+ case OPT_CONFIG_BT_SERVER_TCP_PORT:
+ bt_server_tcp_port = &local_bt_server_tcp_port;
+ if (!ws_optarg){
+ ws_warning("Impossible exception. Parameter required argument, but there is no it right now.");
+ goto end;
+ }
+ if (!ws_strtou16(ws_optarg, NULL, bt_server_tcp_port)) {
+ ws_warning("Invalid bluetooth server TCP port: %s", ws_optarg);
+ goto end;
+ }
+ break;
+ case OPT_CONFIG_BT_FORWARD_SOCKET:
+ bt_forward_socket = (g_ascii_strncasecmp(ws_optarg, "TRUE", 4) == 0);
+ break;
+ case OPT_CONFIG_BT_LOCAL_IP:
+ bt_local_ip = ws_optarg;
+ break;
+ case OPT_CONFIG_BT_LOCAL_TCP_PORT:
+ bt_local_tcp_port = &local_bt_local_tcp_port;
+ if (!ws_optarg){
+ ws_warning("Impossible exception. Parameter required argument, but there is no it right now.");
+ goto end;
+ }
+ if (!ws_strtou16(ws_optarg, NULL, bt_local_tcp_port)) {
+ ws_warning("Invalid bluetooth local tcp port: %s", ws_optarg);
+ goto end;
+ }
+ break;
+ default:
+ if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg))
+ {
+ ws_warning("Invalid argument <%s>. Try --help.\n", argv[ws_optind - 1]);
+ goto end;
+ }
+ }
+ }
+
+ if (!adb_server_ip)
+ adb_server_ip = default_adb_server_ip;
+
+ if (!adb_server_tcp_port)
+ adb_server_tcp_port = &default_adb_server_tcp_port;
+
+ if (!bt_server_tcp_port)
+ bt_server_tcp_port = &default_bt_server_tcp_port;
+
+ if (!bt_local_ip)
+ bt_local_ip = default_bt_local_ip;
+
+ if (!bt_local_tcp_port)
+ bt_local_tcp_port = &default_bt_local_tcp_port;
+
+ err_msg = ws_init_sockets();
+ if (err_msg != NULL) {
+ ws_warning("ERROR: %s", err_msg);
+ g_free(err_msg);
+ ws_warning("%s", please_report_bug());
+ goto end;
+ }
+
+ extcap_cmdline_debug(argv, argc);
+
+ if (extcap_conf->do_list_interfaces)
+ register_interfaces(extcap_conf, adb_server_ip, adb_server_tcp_port);
+
+ /* NOTE:
+ * extcap implementation calls androiddump --extcap-dlts for each interface.
+ * The only way to know whether an interface exists or not is to go through the
+ * whole process of listing all interfaces (i.e. calling register_interfaces
+ * function). Since being a system resource heavy operation and repeated for
+ * each interface instead register a fake interface to be returned for dlt
+ * listing only purpose
+ */
+ if (extcap_conf->do_list_dlts) {
+ new_fake_interface_for_list_dlts(extcap_conf, extcap_conf->interface);
+ }
+
+ if (extcap_base_handle_interface(extcap_conf)) {
+ ret = EXIT_CODE_SUCCESS;
+ goto end;
+ }
+
+ if (extcap_conf->show_config) {
+ ret = list_config(extcap_conf->interface);
+ goto end;
+ }
+
+ if (extcap_conf->capture) {
+ if (extcap_conf->interface && is_logcat_interface(extcap_conf->interface))
+ if (logcat_text)
+ ret = capture_android_logcat_text(extcap_conf->interface,
+ extcap_conf->fifo, adb_server_ip, adb_server_tcp_port,
+ logcat_ignore_log_buffer, logcat_custom_parameter);
+ else
+ ret = capture_android_logcat(extcap_conf->interface,
+ extcap_conf->fifo, adb_server_ip, adb_server_tcp_port);
+ else if (extcap_conf->interface && is_logcat_text_interface(extcap_conf->interface))
+ ret = capture_android_logcat_text(extcap_conf->interface,
+ extcap_conf->fifo, adb_server_ip, adb_server_tcp_port,
+ logcat_ignore_log_buffer, logcat_custom_parameter);
+ else if (extcap_conf->interface && is_specified_interface(extcap_conf->interface, INTERFACE_ANDROID_BLUETOOTH_HCIDUMP))
+ ret = capture_android_bluetooth_hcidump(extcap_conf->interface, extcap_conf->fifo, adb_server_ip, adb_server_tcp_port);
+ else if (extcap_conf->interface && is_specified_interface(extcap_conf->interface, INTERFACE_ANDROID_BLUETOOTH_EXTERNAL_PARSER))
+ ret = capture_android_bluetooth_external_parser(extcap_conf->interface, extcap_conf->fifo, adb_server_ip, adb_server_tcp_port,
+ bt_server_tcp_port, bt_forward_socket, bt_local_ip, bt_local_tcp_port);
+ else if (extcap_conf->interface && (is_specified_interface(extcap_conf->interface, INTERFACE_ANDROID_BLUETOOTH_BTSNOOP_NET)))
+ ret = capture_android_bluetooth_btsnoop_net(extcap_conf->interface, extcap_conf->fifo, adb_server_ip, adb_server_tcp_port);
+ else if (extcap_conf->interface && (is_specified_interface(extcap_conf->interface,INTERFACE_ANDROID_TCPDUMP)))
+ ret = capture_android_tcpdump(extcap_conf->interface, extcap_conf->fifo, extcap_conf->capture_filter, adb_server_ip, adb_server_tcp_port);
+
+ goto end;
+ }
+
+ /* no action was given, assume success */
+ ret = EXIT_CODE_SUCCESS;
+
+end:
+ /* clean up stuff */
+ extcap_base_cleanup(&extcap_conf);
+#ifndef ANDROIDDUMP_USE_LIBPCAP
+ wtap_cleanup();
+#endif
+
+ return ret;
+}
+
+/*
+ * Editor modelines - https://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * vi: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */
diff --git a/extcap/ciscodump.c b/extcap/ciscodump.c
new file mode 100644
index 0000000..54751ed
--- /dev/null
+++ b/extcap/ciscodump.c
@@ -0,0 +1,2512 @@
+/* ciscodump.c
+ * ciscodump is extcap tool used to capture data using a ssh on a remote cisco router
+ *
+ * Copyright 2015, Dario Lombardo
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "config.h"
+#define WS_LOG_DOMAIN "ciscodump"
+
+#include <extcap/extcap-base.h>
+#include <wsutil/interface.h>
+#include <wsutil/strtoi.h>
+#include <wsutil/filesystem.h>
+#include <wsutil/privileges.h>
+#include <wsutil/please_report_bug.h>
+#include <wsutil/wslog.h>
+#include <extcap/ssh-base.h>
+#include <writecap/pcapio.h>
+
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include <wsutil/time_util.h>
+#include <wsutil/ws_strptime.h>
+
+#include <cli_main.h>
+
+#define CISCODUMP_VERSION_MAJOR "1"
+#define CISCODUMP_VERSION_MINOR "0"
+#define CISCODUMP_VERSION_RELEASE "0"
+
+/* The read timeout in msec */
+#define CISCODUMP_READ_TIMEOUT_MSEC 300
+
+#define CISCODUMP_EXTCAP_INTERFACE "ciscodump"
+#define SSH_READ_BLOCK_SIZE 1024
+#define SSH_READ_TIMEOUT_MSES 10000
+#define SSH_READ_TIMEOUT_USEC (SSH_READ_TIMEOUT_MSES*1000)
+
+#define WIRESHARK_CAPTURE "WSC"
+#define WIRESHARK_CAPTURE_POINT "WSC_P"
+#define WIRESHARK_CAPTURE_BUFFER "WSC_B"
+#define WIRESHARK_CAPTURE_ACCESSLIST "WSC_ACL"
+
+#define PCAP_SNAPLEN 0xffff
+
+#define PACKET_MAX_SIZE 65535
+
+#define MINIMUM_IOS_MAJOR 12
+#define MINIMUM_IOS_MINOR 4
+#define MINIMUM_IOS_XE_MAJOR_16 16
+#define MINIMUM_IOS_XE_MINOR_16 1
+#define MINIMUM_IOS_XE_MAJOR_17 17
+#define MINIMUM_IOS_XE_MINOR_17 1
+#define MINIMUM_ASA_MAJOR 8
+#define MINIMUM_ASA_MINOR 4
+
+#define READ_PROMPT_ERROR -1
+#define READ_PROMPT_EOLN 0
+#define READ_PROMPT_PROMPT 1
+#define READ_PROMPT_TOO_LONG 2
+
+#define READ_LINE_ERROR -1
+#define READ_LINE_EOLN 0
+#define READ_LINE_TIMEOUT 1
+#define READ_LINE_TOO_LONG 2
+
+/* Type of Cisco device */
+typedef enum {
+ CISCO_UNKNOWN,
+ CISCO_IOS,
+ CISCO_IOS_XE_16,
+ CISCO_IOS_XE_17,
+ CISCO_ASA
+} CISCO_SW_TYPE;
+
+/* Status of the parser */
+enum {
+ CISCODUMP_PARSER_STARTING,
+ CISCODUMP_PARSER_IN_PACKET,
+ CISCODUMP_PARSER_IN_HEADER,
+ CISCODUMP_PARSER_END_PACKET,
+ CISCODUMP_PARSER_UNKNOWN
+};
+
+enum {
+ EXTCAP_BASE_OPTIONS_ENUM,
+ OPT_HELP,
+ OPT_VERSION,
+ OPT_REMOTE_HOST,
+ OPT_REMOTE_PORT,
+ OPT_REMOTE_USERNAME,
+ OPT_REMOTE_PASSWORD,
+ OPT_REMOTE_INTERFACE,
+ OPT_REMOTE_FILTER,
+ OPT_SSHKEY,
+ OPT_SSHKEY_PASSPHRASE,
+ OPT_PROXYCOMMAND,
+ OPT_REMOTE_COUNT
+};
+
+static char prompt_str[SSH_READ_BLOCK_SIZE + 1];
+static int32_t prompt_len = -1;
+CISCO_SW_TYPE global_sw_type = CISCO_UNKNOWN;
+static bool send_output_quit = false; /* IOS XE 17: send quit during output */
+
+static struct ws_option longopts[] = {
+ EXTCAP_BASE_OPTIONS,
+ { "help", ws_no_argument, NULL, OPT_HELP},
+ { "version", ws_no_argument, NULL, OPT_VERSION},
+ SSH_BASE_OPTIONS,
+ { 0, 0, 0, 0}
+};
+
+static void graceful_shutdown_cb(void)
+{
+ if (global_sw_type == CISCO_IOS_XE_17) {
+ send_output_quit = true;
+ }
+}
+
+/* Replaces needle with rep in line */
+static char* str_replace_char(char *line, char needle, char rep)
+{
+ for(int i = 0; line[i] != '\0'; i++) {
+ if (line[i] == needle) {
+ line[i] = rep;
+ }
+ }
+
+ return line;
+}
+
+/* Replaces CR with LN */
+static char* crtoln(char *line)
+{
+ return str_replace_char(line, '\r', '\n');
+}
+
+static char* interfaces_list_to_filter(GSList* interfaces, unsigned int remote_port)
+{
+ GString* filter = g_string_new(NULL);
+ GSList* cur;
+
+ if (interfaces) {
+ g_string_append_printf(filter, "deny tcp host %s any eq %u, deny tcp any eq %u host %s",
+ (char*)interfaces->data, remote_port, remote_port, (char*)interfaces->data);
+ cur = g_slist_next(interfaces);
+ while (cur) {
+ g_string_append_printf(filter, ", deny tcp host %s any eq %u, deny tcp any eq %u host %s",
+ (char*)cur->data, remote_port, remote_port, (char*)cur->data);
+ cur = g_slist_next(cur);
+ }
+ g_string_append_printf(filter, ", permit ip any any");
+ }
+
+ return g_string_free(filter, false);
+}
+
+static char* local_interfaces_to_filter(const unsigned int remote_port)
+{
+ GSList* interfaces = local_interfaces_to_list();
+ char* filter = interfaces_list_to_filter(interfaces, remote_port);
+ g_slist_free_full(interfaces, g_free);
+ return filter;
+}
+
+/* Read bytes from the channel with no escape character.
+ * If bytes == -1, read all data (until timeout). If outbuf != NULL, data are stored there
+ */
+static int read_output_bytes_any(ssh_channel channel, int bytes, char* outbuf)
+{
+ char chr;
+ int total;
+ int bytes_read;
+
+ total = (bytes > 0 ? bytes : G_MAXINT);
+ bytes_read = 0;
+
+ while(ssh_channel_read_timeout(channel, &chr, 1, 0, CISCODUMP_READ_TIMEOUT_MSEC) > 0 && bytes_read < total) {
+ ws_noisy("%c %02x", chr, chr);
+ if (outbuf)
+ outbuf[bytes_read] = chr;
+ bytes_read++;
+ }
+ if (outbuf)
+ outbuf[bytes_read+1] = '\0';
+ return EXIT_SUCCESS;
+}
+
+/* Read bytes from the channel. Recognize escape char '^'.
+ * If bytes == -1, read all data (until timeout). If outbuf != NULL, data are stored there
+ */
+static int read_output_bytes(ssh_channel channel, int bytes, char* outbuf)
+{
+ char chr;
+ int total;
+ int bytes_read;
+
+ total = (bytes > 0 ? bytes : G_MAXINT);
+ bytes_read = 0;
+
+ while(ssh_channel_read_timeout(channel, &chr, 1, 0, CISCODUMP_READ_TIMEOUT_MSEC) > 0 && bytes_read < total) {
+ ws_noisy("%c %02x", chr, chr);
+ if (chr == '^')
+ return EXIT_FAILURE;
+ if (outbuf)
+ outbuf[bytes_read] = chr;
+ bytes_read++;
+ }
+ return EXIT_SUCCESS;
+}
+
+/* Reads input to buffer and parses EOL
+ * If line is NULL, just received count of characters in len is calculated
+ * It returns:
+ * READ_LINE_ERROR - any ssh error occured
+ * READ_LINE_EOLN - EOLN found, line/len contains \0 terminated string
+ * READ_LINE_TIMEOUT - reading ended with timeout, line/len contains \0 terminate prompt
+ * READ_LINE_TOO_LONG - buffer is full with no EOLN nor PROMPT found, line is filled with NOT \0 terminated data
+ */
+static int ssh_channel_read_line_timeout(ssh_channel channel, char *line, int *len, int max_len) {
+ char chr;
+ int rlen = 0;
+
+ *len = 0;
+ do {
+ rlen = ssh_channel_read_timeout(channel, &chr, 1, false, CISCODUMP_READ_TIMEOUT_MSEC);
+ ws_noisy("%c %02x %d", chr, chr, rlen);
+ if (rlen == SSH_ERROR) {
+ ws_warning("Error reading from channel");
+ return READ_LINE_ERROR;
+ } else if (rlen > 0) {
+ if (chr != '\n') {
+ /* Ignore \r */
+ if (chr != '\r') {
+ if (line) {
+ line[*len] = chr;
+ }
+ (*len)++;
+ }
+ } else {
+ /* Parse the current line */
+ if (line) {
+ line[*len] = '\0';
+ }
+ return READ_LINE_EOLN;
+ }
+ } else {
+ return READ_LINE_TIMEOUT;
+ }
+ } while (*len < max_len);
+
+ return READ_LINE_TOO_LONG;
+}
+
+/* Reads input to buffer and parses EOL or prompt_str PROMPT
+ * It returns:
+ * READ_PROMPT_ERROR - any ssh error occured
+ * READ_PROMPT_EOLN - EOLN found, line/len contains \0 terminated string
+ * READ_PROMPT_PROMPT - reading ended and it ends with PROMPT, line/len contains \0 terminate prompt
+ * READ_PROMPT_TOO_LONG - buffer is full with no EOLN nor PROMPT found, line is filled with NOT \0 terminated data
+ */
+static int ssh_channel_read_prompt(ssh_channel channel, char *line, uint32_t *len, uint32_t max_len) {
+ char chr;
+ int rlen = 0;
+ int64_t start_time = g_get_monotonic_time();
+
+ do {
+ rlen = ssh_channel_read_timeout(channel, &chr, 1, false, CISCODUMP_READ_TIMEOUT_MSEC);
+ ws_noisy("%c %02x %d", chr, chr, rlen);
+ if (rlen == SSH_ERROR) {
+ ws_warning("Error reading from channel");
+ return READ_PROMPT_ERROR;
+ } else if (rlen > 0) {
+ if (chr != '\n') {
+ line[*len] = chr;
+ (*len)++;
+ } else {
+ /* Parse the current line */
+ line[*len] = '\0';
+ ws_noisy(" exiting: READ_PROMPT_EOLN (%d/%d)", *len, max_len);
+ return READ_PROMPT_EOLN;
+ }
+ } else {
+ int64_t cur_time = g_get_monotonic_time();
+
+ /* ssh timeout, we might be on prompt */
+ /* IOS, IOS-XE: check if line has same length as prompt and if it match prompt */
+ if ((*len == (uint32_t)prompt_len) && (0 == strncmp(line, prompt_str, prompt_len))) {
+ line[*len] = '\0';
+ ws_noisy(" exiting: READ_PROMPT_PROMPT (%d/%d)", *len, max_len);
+ return READ_PROMPT_PROMPT;
+ }
+ /* ASA: check if line begins with \r and has same length as prompt and if it match prompt */
+ if ((line[0] == '\r') && (*len == (uint32_t)prompt_len+1) && (0 == strncmp(line+1, prompt_str, prompt_len))) {
+ line[*len] = '\0';
+ ws_noisy(" exiting: READ_PROMPT_PROMPT (%d/%d)", *len, max_len);
+ return READ_PROMPT_PROMPT;
+ }
+ /* no prompt found, so we continue in waiting for data, but we should check global timeout */
+ if ((cur_time-start_time) > SSH_READ_TIMEOUT_USEC) {
+ line[*len] = '\0';
+ ws_noisy(" exiting: READ_PROMPT_ERROR");
+ return READ_PROMPT_ERROR;
+ }
+ }
+ } while (!extcap_end_application && (*len < max_len));
+
+ ws_noisy(" exiting: READ_PROMPT_TOO_LONG (%d/%d/%d)", *len, max_len, extcap_end_application);
+ line[*len] = '\0';
+ return READ_PROMPT_TOO_LONG;
+}
+
+static int ssh_channel_wait_prompt(ssh_channel channel, char *line, uint32_t *len, uint32_t max_len) {
+ char line2[SSH_READ_BLOCK_SIZE + 1];
+ uint32_t len2;
+ int status;
+
+ memset(line2, 0x0, SSH_READ_BLOCK_SIZE + 1);
+ line[0] = '\0';
+ *len = 0;
+ do {
+ len2 = 0;
+ switch (status = ssh_channel_read_prompt(channel, line2, &len2, SSH_READ_BLOCK_SIZE)) {
+ case READ_PROMPT_EOLN:
+ *len = (uint32_t)g_strlcat(line, line2, max_len);
+ len2 = 0;
+ break;
+ case READ_PROMPT_PROMPT:
+ *len = (uint32_t)g_strlcat(line, line2, max_len);
+ len2 = 0;
+ break;
+ default:
+ /* We do not have better solution for that cases */
+ /* Just terminate the line and return error */
+ *len = (uint32_t)g_strlcat(line, line2, max_len);
+ line[max_len] = '\0';
+ ws_noisy("Returning READ_PROMPT_ERROR (%d/%d)", *len, max_len);
+ return READ_PROMPT_ERROR;
+ }
+ } while (status == READ_PROMPT_EOLN);
+
+ ws_noisy("Returning READ_PROMPT_PROMPT (%d/%d)", *len, max_len);
+ return READ_PROMPT_PROMPT;
+}
+
+/* true if prompt and no error text in response. false otherwise */
+/* Note: It do not catch all CISCO CLI errors, but many of them */
+static bool ssh_channel_wait_prompt_check_error(ssh_channel channel, char *line, uint32_t *len, uint32_t max_len, char *error_re) {
+ /* Did we received prompt? */
+ if (ssh_channel_wait_prompt(channel, line, len, max_len) != READ_PROMPT_PROMPT) {
+ return false;
+ }
+
+ /* Is there ERROR: text in output? */
+ if (NULL != g_strstr_len(line, -1, "ERROR:")) {
+ return false;
+ }
+
+ /* Is there ERROR: text in output? */
+ if (NULL != g_strstr_len(line, -1, "% Invalid input detected at")) {
+ return false;
+ }
+
+ /* Is there error_re text in output? */
+ if (error_re &&
+ g_regex_match_simple(error_re, line, (GRegexCompileFlags) (G_REGEX_CASELESS | G_REGEX_RAW), 0)
+ ) {
+ return false;
+ }
+
+ return true;
+}
+
+static void ciscodump_cleanup_ios(ssh_channel channel, const char* iface, const char* cfilter)
+{
+ char* iface_copy = g_strdup(iface);
+ char* iface_one;
+ char* str = NULL;
+ int wscp_cnt = 1;
+ char* wscp_str = NULL;
+
+ extcap_end_application = false;
+ if (channel) {
+ ws_debug("Removing configuration...");
+ read_output_bytes(channel, -1, NULL);
+
+ wscp_cnt = 1;
+ for (str = iface_copy; ; str = NULL) {
+ iface_one = strtok(str, ",");
+ if (iface_one == NULL)
+ break;
+
+ wscp_str = g_strdup_printf("%s_%d", WIRESHARK_CAPTURE_POINT, wscp_cnt);
+ wscp_cnt++;
+
+ ssh_channel_printf(channel, "monitor capture point stop %s\n", wscp_str);
+ ssh_channel_printf(channel, "no monitor capture point ip cef %s %s\n", wscp_str, iface_one);
+
+ g_free(wscp_str);
+ wscp_str = NULL;
+ }
+
+ ssh_channel_printf(channel, "no monitor capture buffer %s\n", WIRESHARK_CAPTURE_BUFFER);
+ if (cfilter) {
+ ssh_channel_printf(channel, "configure terminal\n");
+ ssh_channel_printf(channel, "no ip access-list ex %s\n", WIRESHARK_CAPTURE_ACCESSLIST);
+ }
+
+ read_output_bytes(channel, -1, NULL);
+ ws_debug("Configuration removed");
+ }
+
+ g_free(iface_copy);
+}
+
+static void ciscodump_cleanup_ios_xe_16(ssh_channel channel, const char* cfilter)
+{
+ if (channel) {
+ ws_debug("Removing configuration...");
+ read_output_bytes(channel, -1, NULL);
+
+ ssh_channel_printf(channel, "monitor capture %s stop\n", WIRESHARK_CAPTURE);
+ ssh_channel_printf(channel, "no monitor capture %s\n", WIRESHARK_CAPTURE);
+ if (cfilter) {
+ ssh_channel_printf(channel, "configure terminal\n");
+ ssh_channel_printf(channel, "no ip access-list extended %s\n", WIRESHARK_CAPTURE_ACCESSLIST);
+ ssh_channel_printf(channel, "\nend\n");
+ }
+
+ read_output_bytes(channel, -1, NULL);
+ ws_debug("Configuration removed");
+ }
+}
+
+static void ciscodump_cleanup_ios_xe_17(ssh_channel channel, const char* cfilter)
+{
+ if (channel) {
+ ws_debug("Removing configuration...");
+ read_output_bytes(channel, -1, NULL);
+
+ ssh_channel_printf(channel, "monitor capture %s stop\n", WIRESHARK_CAPTURE);
+ ssh_channel_printf(channel, "no monitor capture %s\n", WIRESHARK_CAPTURE);
+ if (cfilter) {
+ ssh_channel_printf(channel, "configure terminal\n");
+ ssh_channel_printf(channel, "no ip access-list extended %s\n", WIRESHARK_CAPTURE_ACCESSLIST);
+ ssh_channel_printf(channel, "\nend\n");
+ }
+
+ read_output_bytes(channel, -1, NULL);
+ ws_debug("Configuration removed");
+ }
+}
+
+static void ciscodump_cleanup_asa(ssh_channel channel, const char* cfilter)
+{
+ if (channel) {
+ ws_debug("Removing configuration...");
+ read_output_bytes(channel, -1, NULL);
+
+ ssh_channel_printf(channel, "no capture %s\n", WIRESHARK_CAPTURE);
+ if (cfilter) {
+ ssh_channel_printf(channel, "configure terminal\n");
+ ssh_channel_printf(channel, "clear configure access-list %s\n", WIRESHARK_CAPTURE_ACCESSLIST);
+ ssh_channel_printf(channel, "\nend\n", WIRESHARK_CAPTURE_ACCESSLIST);
+ }
+
+ read_output_bytes(channel, -1, NULL);
+ ws_debug("Configuration removed");
+ }
+}
+
+static void ciscodump_cleanup(ssh_channel channel, const char* iface, const char* cfilter, CISCO_SW_TYPE sw_type)
+{
+ ws_debug("Starting config cleanup");
+ switch (sw_type) {
+ case CISCO_IOS:
+ ciscodump_cleanup_ios(channel, iface, cfilter);
+ break;
+ case CISCO_IOS_XE_16:
+ ciscodump_cleanup_ios_xe_16(channel, cfilter);
+ break;
+ case CISCO_IOS_XE_17:
+ ciscodump_cleanup_ios_xe_17(channel, cfilter);
+ break;
+ case CISCO_ASA:
+ ciscodump_cleanup_asa(channel, cfilter);
+ break;
+ case CISCO_UNKNOWN:
+ break;
+ }
+ ws_debug("Config cleanup finished");
+}
+
+static void packets_captured_count_ios(char *line, uint32_t *max, bool *running) {
+ char** part;
+
+ *max = 0;
+
+ ws_debug("Analyzing response: %s", line);
+
+ /* Read count of packets */
+ part = g_regex_split_simple(
+ "Packets :\\s*(\\d+)",
+ line, G_REGEX_CASELESS, 0);
+ if (*part && *(part+1)) {
+ /* RE matched */
+ if (strlen(*(part+1)) > 0) {
+ ws_strtou32(*(part+1), NULL, max);
+ }
+ }
+ g_strfreev(part);
+
+ *running = false;
+ if (g_regex_match_simple("Status : Active", line, (GRegexCompileFlags) (G_REGEX_CASELESS | G_REGEX_RAW), 0)) {
+ *running = true;
+ }
+ ws_debug("Count of packets: %d", *max);
+ ws_debug("Capture is running: %d", *running);
+}
+
+static void packets_captured_count_ios_xe_16(char *line, uint32_t *max, bool *running) {
+ char** part;
+
+ *max = 0;
+
+ ws_debug("Analyzing response: %s", line);
+
+ part = g_regex_split_simple(
+ "packets in buf\\s+:\\s+(\\d+)",
+ line, G_REGEX_CASELESS, 0);
+ if (*part && *(part+1)) {
+ /* RE matched */
+ if (strlen(*(part+1)) > 0) {
+ ws_strtou32(*(part+1), NULL, max);
+ }
+ }
+ g_strfreev(part);
+
+ *running = false;
+ /* Check if capture is running */
+ if (g_regex_match_simple("Status : Active", line, (GRegexCompileFlags) (G_REGEX_CASELESS | G_REGEX_RAW), 0)) {
+ *running = true;
+ }
+ ws_debug("Count of packets: %d", *max);
+ ws_debug("Capture is running: %d", *running);
+}
+
+static void packets_captured_count_asa(char *line, uint32_t *max, bool *running) {
+ char** part;
+
+ *max = 0;
+
+ ws_debug("Analyzing response: %s", line);
+
+ /* Read count of packets */
+ part = g_regex_split_simple(
+ "(\\d+) packets captured",
+ line, G_REGEX_CASELESS, 0);
+ if (*part && *(part+1)) {
+ /* RE matched */
+ if (strlen(*(part+1)) > 0) {
+ ws_strtou32(*(part+1), NULL, max);
+ }
+ }
+ g_strfreev(part);
+
+ if (running != NULL) {
+ *running = false;
+ /* Check if capture is running */
+ if (g_regex_match_simple("\\[Capturing -", line, (GRegexCompileFlags) (G_REGEX_CASELESS | G_REGEX_RAW), 0)) {
+ *running = true;
+ }
+ ws_debug("Capture is running: %d", *running);
+ }
+ ws_debug("Count of packets: %d", *max);
+}
+
+static int parse_line_ios(uint8_t* packet, unsigned* offset, char* line, int status, time_t *pkt_time, uint32_t *pkt_usec)
+{
+ char** parts;
+ char** part;
+ uint32_t value;
+ size_t size;
+
+ if (strlen(line) <= 1) {
+ if (status == CISCODUMP_PARSER_IN_PACKET)
+ return CISCODUMP_PARSER_END_PACKET;
+ else
+ return status;
+ }
+
+/*
+22:45:44.700 UTC Nov 27 2021 : IPv4 LES CEF : Fa4.320 None
+
+0F1A2C00: B0FAEBC7 A8620050 0zkG(b.P
+0F1A2C10: 568494FB 08004588 004EF201 40003F11 V..{..E..Nr.@.?.
+0F1A2C20: 5263AC10 1F60AC10 7F31D0DF 00A1003A Rc,..`,..1P_.!.:
+0F1A2C30: D1753030 02010104 07707269 76617465 Qu00.....private
+0F1A2C40: A0220204 1A00A2E8 02010002 01003014 "...."h......0.
+0F1A2C50: 3012060E 2B060104 0182CB21 01040103 0...+.....K!....
+0F1A2C60: 06000500 00 .....
+
+22:45:44.700 UTC Nov 27 2021 : IPv4 LES CEF : Fa4.320 None
+
+0F1A2C00: B0FAEBC7 A8620050 0zkG(b.P
+0F1A2C10: 568494FB 08004588 004E7FFB 40003F11 V..{..E..N.{@.?.
+0F1A2C20: EB72AC10 1F60AC10 5828E872 00A1003A kr,..`,.X(hr.!.:
+0F1A2C30: 2B393030 02010104 07707269 76617465 +900.....private
+0F1A2C40: A0220204 07836B18 02010002 01003014 "....k.......0.
+0F1A2C50: 3012060E 2B060104 0182CB21 01040103 0...+.....K!....
+0F1A2C60: 06000500 00 .....
+*/
+
+ /* we got the packet header */
+ /* The packet header is a line like: */
+ /* 16:09:37.171 ITA Mar 18 2016 : IPv4 LES CEF : Gi0/1 None */
+ parts = g_regex_split_simple(
+ "^(\\d{2}:\\d{2}:\\d{2}).(\\d+) (\\w+) (\\w+ \\d+ \\d+) :",
+ line, G_REGEX_CASELESS, 0);
+ if (parts && *(parts+1)) {
+ /* RE matched */
+ char* cp;
+ struct tm tm;
+ /* Date without msec, with timezone */
+ char* d1 = g_strdup_printf("%s %s %s", *(parts+1), *(parts+3), *(parts+4));
+ /* Date without msec, without timezone */
+ char* d2 = g_strdup_printf("%s %s", *(parts+1), *(parts+4));
+
+ memset(&tm, 0x0, sizeof(struct tm));
+
+ cp = ws_strptime_p(d1, "%H:%M:%S %Z %b %d %Y", &tm);
+ if (!cp || (*cp != '\0')) {
+ /* Time zone parse failed */
+ cp = ws_strptime_p(d2, "%H:%M:%S %b %d %Y", &tm);
+ if (!cp || (*cp != '\0')) {
+ /* Time parse failed, use now */
+ time_t t;
+ struct tm *tm2;
+
+ t = time(0);
+ tm2 = localtime(&t);
+ memcpy(&tm, tm2, sizeof(struct tm));
+ }
+ }
+ ws_strtou32(*(parts+2), NULL, pkt_usec);
+ *pkt_usec *= 1000;
+ *pkt_time = mktime(&tm);
+
+ g_strfreev(parts);
+ return CISCODUMP_PARSER_IN_HEADER;
+ }
+ g_strfreev(parts);
+
+ /* we got a line of the packet */
+ /* A line looks like */
+ /* <address>: <1st group> <2nd group> <3rd group> <4th group> <ascii representation> */
+ /* ABCDEF01: 01020304 05060708 090A0B0C 0D0E0F10 ................ */
+ /* Note that any of the 4 groups are optional and that a group can be 1 to 4 bytes long */
+ parts = g_regex_split_simple(
+ "^[\\dA-F]{8,8}:\\s+([\\dA-F]{2,8})\\s+([\\dA-F]{2,8}){0,1}\\s+([\\dA-F]{2,8}){0,1}\\s+([\\dA-F]{2,8}){0,1}.*",
+ line, G_REGEX_CASELESS, 0);
+
+ part = parts;
+ if (*part && *(part+1)) {
+ /* There is at least one match. Skip first string */
+ part++;
+ while(*part) {
+ /* RE matched */
+ if (strlen(*part) > 1) {
+ ws_hexstrtou32(*part, NULL, &value);
+ value = g_ntohl(value);
+ size = strlen(*part) / 2;
+ memcpy(packet + *offset, &value, size);
+ *offset += (uint32_t)size;
+ }
+ part++;
+ }
+ }
+ g_strfreev(parts);
+ return CISCODUMP_PARSER_IN_PACKET;
+}
+
+static int parse_line_ios_xe_16(uint8_t* packet, unsigned* offset, char* line)
+{
+ char** parts;
+ char** part;
+ uint32_t value;
+ size_t size;
+
+ if (strlen(line) <= 1) {
+ return CISCODUMP_PARSER_END_PACKET;
+ }
+
+ /*
+0
+ 0000: 00000C07 AC154C5D 3C259068 08004500 ......L]<%.h..E.
+ 0010: 00547549 40003F01 B582C0A8 4983C0A8 .TuI@.?.....I...
+ 0020: 46090800 B28E456E 00030000 00000000 F.....En........
+ 0030: 00000000 00000000 00000000 00000000 ................
+ 0040: 00000000 00000000 00000000 00000000 ................
+ 0050: 00000000 00000000 00000000 00000000 ................
+ 0060: 0000 ..
+
+1
+ 0000: 4C5D3C25 9068A49B CD904C74 08004500 L]<%.h....Lt..E.
+ 0010: 00547549 4000FF01 F581C0A8 4609C0A8 .TuI@.......F...
+ 0020: 49830000 BA8E456E 00030000 00000000 I.....En........
+ 0030: 00000000 00000000 00000000 00000000 ................
+ 0040: 00000000 00000000 00000000 00000000 ................
+ 0050: 00000000 00000000 00000000 00000000 ................
+ 0060: 0000 ..
+*/
+
+ /* we got the packet header */
+ /*0*/
+ if (g_regex_match_simple("^\\d+$", line, (GRegexCompileFlags) (G_REGEX_CASELESS | G_REGEX_RAW), 0)) {
+ return CISCODUMP_PARSER_IN_HEADER;
+ }
+
+ /* we got a line of the packet */
+ /* A line looks like */
+ /* 0000: 00000C07 AC154C5D 3C259068 08004500 ......L]<%.h..E. */
+ /* ... */
+ /* 0060: 0000 .. */
+ /* Note that any of the 4 groups are optional and that a group can be 1 to 8 bytes long */
+ parts = g_regex_split_simple(
+ "^\\s+[0-9A-F]{4,4}: ([0-9A-F]{2,8}) ([0-9A-F]{2,8}){0,1} ([0-9A-F]{2,8}){0,1} ([0-9A-F]{2,8}){0,1}\\s+.*",
+ line, G_REGEX_CASELESS, 0);
+
+ part = parts;
+ if (*part && *(part+1)) {
+ /* There is at least one match. Skip first string */
+ part++;
+ while(*part) {
+ if (strlen(*part) > 1) {
+ ws_hexstrtou32(*part, NULL, &value);
+ value = g_ntohl(value);
+ size = strlen(*part) / 2;
+ memcpy(packet + *offset, &value, size);
+ *offset += (uint32_t)size;
+ }
+ part++;
+ }
+ }
+ g_strfreev(parts);
+
+ return CISCODUMP_PARSER_IN_PACKET;
+}
+
+static int parse_line_ios_xe_17(uint8_t* packet, unsigned* offset, char* line)
+{
+ char** parts;
+ char** part;
+ uint8_t value;
+
+ if (strlen(line) <= 1) {
+ return CISCODUMP_PARSER_END_PACKET;
+ }
+
+ /*
+0000 6c 5e 3b 88 5e 80 6c 5e 3b 88 5e 80 08 00 45 00 l^;.^.l^;.^...E.
+0010 00 28 9b 00 00 00 ff 06 0f 02 c0 a8 c8 01 c0 a8 .(..............
+0020 c8 7a 7e 77 00 31 4c ba ba a5 92 d3 0a eb 50 10 .z~w.1L.......P.
+0030 10 20 6a 20 00 00 00 00 00 00 00 00 . j ........
+*/
+
+ /* we got a line of the packet */
+ /* A line looks like */
+ /* 0000 6c 5e 3b 88 5e 80 6c 5e 3b 88 5e 80 08 00 45 00 l^;.^.l^;.^...E */
+ /* ... */
+ /* 0030 10 20 6a 20 00 00 00 00 00 00 00 00 . j ........ */
+ /* Note that any of the 4 groups are optional and that a group can be 1 to 8 bytes long */
+ parts = g_regex_split_simple(
+ "^\\s*[0-9A-F]{4,4} ([0-9A-F]{2}) ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1}\\s+.*",
+ line, G_REGEX_CASELESS, 0);
+
+ part = parts;
+ if (*part && *(part+1)) {
+ /* There is at least one match. Skip first string */
+ part++;
+ while(*part) {
+ if (strlen(*part) > 1) {
+ ws_hexstrtou8(*part, NULL, &value);
+ memcpy(packet + *offset, &value, 1);
+ *offset += 1;
+ }
+ part++;
+ }
+ }
+ g_strfreev(parts);
+
+ return CISCODUMP_PARSER_IN_PACKET;
+}
+
+static int parse_line_asa(uint8_t* packet, unsigned* offset, char* line, uint32_t *current_max, time_t *pkt_time, uint32_t *pkt_usec)
+{
+ char** parts;
+ char** part;
+ uint16_t value;
+ size_t size;
+ uint32_t new_max;
+
+ if (strlen(line) <= 1) {
+ return CISCODUMP_PARSER_UNKNOWN;
+ }
+
+ /*
+4599 packets captured
+
+ 1: 20:40:01.108469 10.124.255.212 > 10.124.255.5 icmp: echo request
+0x0000 a453 0ef3 7fc0 74ad 98e5 0004 0800 4500 .S....t.......E.
+0x0010 0054 dc73 4000 4001 4a63 0a7c ffd4 0a7c .T.s@.@.Jc.|...|
+0x0020 ff05 0800 275b d0a4 0000 0000 0000 0000 ....'[..........
+0x0030 0000 0000 0000 0000 0000 0000 0000 0000 ................
+0x0040 0000 0000 0000 0000 0000 0000 0000 0000 ................
+0x0050 0000 0000 0000 0000 0000 0000 0000 0000 ................
+0x0060 0000 ..
+1 packet shown
+*/
+
+ /* Update count of available packets */
+ /* 4599 packets captured */
+ packets_captured_count_asa(line, &new_max, NULL);
+ if (new_max > 0) {
+ *current_max = new_max;
+ return CISCODUMP_PARSER_IN_HEADER;
+ }
+
+ /* we got the packet header */
+ /* 1: 20:40:01.108469 10.124.255.212 > 10.124.255.5 icmp: echo request */
+ parts = g_regex_split_simple("^\\s*\\d+:\\s+(\\d+):(\\d+):(\\d+)\\.(\\d+)\\s+", line, (GRegexCompileFlags) (G_REGEX_CASELESS | G_REGEX_RAW), 0);
+ if (parts && *(parts+1)) {
+ /* RE matched */
+ struct tm *tm;
+ time_t t;
+
+ t = time(0);
+ tm = localtime(&t);
+ ws_strtoi32(*(parts+1), NULL, &(tm->tm_hour));
+ ws_strtoi32(*(parts+2), NULL, &(tm->tm_min));
+ ws_strtoi32(*(parts+3), NULL, &(tm->tm_sec));
+ ws_strtou32(*(parts+4), NULL, pkt_usec);
+ *pkt_time = mktime(tm);
+
+ g_strfreev(parts);
+ return CISCODUMP_PARSER_IN_HEADER;
+ }
+ g_strfreev(parts);
+
+ /* we got the packet tail */
+ /* 1 packet shown */
+ if (g_regex_match_simple("^\\s*1 packet shown.*$", line, (GRegexCompileFlags) (G_REGEX_CASELESS | G_REGEX_RAW), 0)) {
+ return CISCODUMP_PARSER_END_PACKET;
+ }
+
+ /* we got a line of the packet */
+ /* A line looks like */
+ /* 0x<address>: <1st group> <...> <8th group> <5th group> <ascii representation> */
+ /* 0x0000 a453 0ef3 7fc0 74ad 98e5 0004 0800 4500 -.S....t.......E. */
+ /* 0x0060 0000 .. */
+ /* Note that any of the 8 groups are optional and that a group can be 1 to 8 bytes long */
+ parts = g_regex_split_simple(
+ "^0x[0-9A-F]{4,4}\\s+([0-9A-F]{2,4}) ([0-9A-F]{2,4}){0,1} ([0-9A-F]{2,4}){0,1} ([0-9A-F]{2,4}){0,1} ([0-9A-F]{2,4}){0,1} ([0-9A-F]{2,4}){0,1} ([0-9A-F]{2,4}){0,1} ([0-9A-F]{2,4}){0,1}\\s+.*",
+ line, G_REGEX_CASELESS, 0);
+
+ part = parts;
+ if (*part && *(part+1)) {
+ /* There is at least one match. Skip first string */
+ part++;
+ while(*part) {
+ if (strlen(*part) > 1) {
+ ws_hexstrtou16(*part, NULL, &value);
+ value = g_ntohs(value);
+ size = strlen(*part) / 2;
+ memcpy(packet + *offset, &value, size);
+ *offset += (uint32_t)size;
+ }
+ part++;
+ }
+ }
+ g_strfreev(parts);
+
+ return CISCODUMP_PARSER_IN_PACKET;
+}
+
+/* IOS: Reads response and parses buffer till prompt received */
+static int process_buffer_response_ios(ssh_channel channel, uint8_t* packet, FILE* fp, const uint32_t count, uint32_t *processed_packets)
+{
+ char line[SSH_READ_BLOCK_SIZE + 1];
+ uint32_t read_packets = 1;
+ int status = CISCODUMP_PARSER_STARTING;
+ int loop_end = 0;
+ unsigned packet_size = 0;
+ time_t pkt_time = 0;
+ uint32_t pkt_usec = 0;
+ uint32_t len = 0;
+
+ /* Process response */
+ do {
+
+ loop_end = 0;
+ /* Read input till EOLN or prompt */
+ switch (ssh_channel_read_prompt(channel, line, &len, SSH_READ_BLOCK_SIZE)) {
+ case READ_PROMPT_EOLN:
+ status = parse_line_ios(packet, &packet_size, line, status, &pkt_time, &pkt_usec);
+
+ if (status == CISCODUMP_PARSER_END_PACKET) {
+ ws_debug("Read packet %d\n", read_packets);
+ if (read_packets > *processed_packets) {
+ int err;
+ uint64_t bytes_written;
+
+ ws_debug("Exporting packet %d\n", *processed_packets);
+ /* dump the packet to the pcap file */
+ if (!libpcap_write_packet(fp,
+ pkt_time, pkt_usec,
+ packet_size, packet_size, packet, &bytes_written, &err)) {
+ ws_debug("Error in libpcap_write_packet(): %s", g_strerror(err));
+ break;
+ }
+ fflush(fp);
+ ws_debug("Dumped packet %u size: %u\n", *processed_packets, packet_size);
+ (*processed_packets)++;
+ }
+ packet_size = 0;
+ read_packets++;
+ }
+ break;
+ case READ_PROMPT_PROMPT:
+ ws_debug("Prompt found");
+ loop_end = 1;
+ break;
+ default:
+ /* We do not have better solution for that cases */
+ if (extcap_end_application) {
+ /* End by signal causes timeout so we decrease priority */
+ ws_debug("Timeout or response was too long\n");
+ } else {
+ ws_warning("Timeout or response was too long\n");
+ }
+ return false;
+ }
+ len = 0;
+ ws_debug("loop end detection %d %d %d %d", extcap_end_application, loop_end, *processed_packets, count);
+ } while ((!extcap_end_application) && (!loop_end) && (*processed_packets < count));
+
+ return true;
+}
+
+/* IOS: Queries buffer content and reads it */
+static void ssh_loop_read_ios(ssh_channel channel, FILE* fp, const uint32_t count)
+{
+ char line[SSH_READ_BLOCK_SIZE + 1];
+ uint8_t* packet;
+ uint32_t processed_packets = 0;
+ bool running = true;
+ uint32_t current_max = 0;
+ uint32_t new_max;
+
+ /* This is big enough to put on the heap */
+ packet = (uint8_t*)g_malloc(PACKET_MAX_SIZE);
+
+ do {
+ uint32_t len = 0;
+
+ /* Query count of available packets in buffer */
+ if (ssh_channel_printf(channel, "show monitor capture buffer %s parameters\n", WIRESHARK_CAPTURE_BUFFER) == EXIT_FAILURE) {
+ g_free(packet);
+ return;
+ }
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ g_free(packet);
+ return;
+ }
+ ws_debug("Read: %s", line);
+ packets_captured_count_ios(line, &new_max, &running);
+ ws_debug("Max counts %d %d", current_max, new_max);
+ if (new_max == current_max) {
+ /* There is no change in count of available packets, repeat the loop */
+ continue;
+ } else if (new_max < current_max) {
+ /* Buffer was cleared, stop */
+ g_free(packet);
+ return;
+ }
+ current_max = new_max;
+ ws_debug("New packet count %d\n", current_max);
+
+ /* Dump buffer */
+ if (ssh_channel_printf(channel, "show monitor capture buffer %s dump\n", WIRESHARK_CAPTURE_BUFFER) == EXIT_FAILURE) {
+ g_free(packet);
+ return;
+ }
+
+ /* Process buffer */
+ if (!process_buffer_response_ios(channel, packet, fp, count, &processed_packets)) {
+ g_free(packet);
+ return;
+ }
+ } while (!extcap_end_application && running && (processed_packets < count));
+
+ g_free(packet);
+
+ /* Discard any subsequent messages */
+ read_output_bytes_any(channel, -1, NULL);
+}
+
+/* IOS-XE 16: Reads response and parses buffer till prompt received */
+static int process_buffer_response_ios_xe_16(ssh_channel channel, uint8_t* packet, FILE* fp, const uint32_t count, uint32_t *processed_packets)
+{
+ char line[SSH_READ_BLOCK_SIZE + 1];
+ uint32_t read_packets = 1;
+ int status = CISCODUMP_PARSER_STARTING;
+ int loop_end = 0;
+ unsigned packet_size = 0;
+ uint32_t len = 0;
+
+ /* Process response */
+ do {
+ loop_end = 0;
+ /* Read input till EOLN or prompt */
+ switch (ssh_channel_read_prompt(channel, line, &len, SSH_READ_BLOCK_SIZE)) {
+ case READ_PROMPT_EOLN:
+ status = parse_line_ios_xe_16(packet, &packet_size, line);
+
+ if (status == CISCODUMP_PARSER_END_PACKET) {
+ ws_debug("Read packet %d\n", read_packets);
+ if (read_packets > *processed_packets) {
+ int err;
+ int64_t cur_time = g_get_real_time();
+ uint64_t bytes_written = 0;
+
+ ws_debug("Exporting packet %d\n", *processed_packets);
+ /* dump the packet to the pcap file */
+ if (!libpcap_write_packet(fp,
+ (uint32_t)(cur_time / G_USEC_PER_SEC), (uint32_t)(cur_time % G_USEC_PER_SEC),
+ packet_size, packet_size, packet, &bytes_written, &err)) {
+ ws_debug("Error in libpcap_write_packet(): %s", g_strerror(err));
+ break;
+ }
+ fflush(fp);
+ ws_debug("Dumped packet %u size: %u\n", *processed_packets, packet_size);
+ (*processed_packets)++;
+ }
+ packet_size = 0;
+ read_packets++;
+ }
+ break;
+ case READ_PROMPT_PROMPT:
+ ws_debug("Prompt found");
+ loop_end = 1;
+ break;
+ default:
+ /* We do not have better solution for that cases */
+ if (extcap_end_application) {
+ /* End by signal causes timeout so we decrease priority */
+ ws_debug("Timeout or response was too long\n");
+ } else {
+ ws_warning("Timeout or response was too long\n");
+ }
+ return false;
+ }
+ len = 0;
+ ws_debug("loop end detection %d %d %d %d", extcap_end_application, loop_end, *processed_packets, count);
+ } while ((!extcap_end_application) && (!loop_end) && (*processed_packets < count));
+
+ return true;
+}
+
+/* IOS-XE 17: Reads response and parses buffer till prompt received */
+static int process_buffer_response_ios_xe_17(ssh_channel channel, uint8_t* packet, FILE* fp, const uint32_t count, uint32_t *processed_packets)
+{
+ char line[SSH_READ_BLOCK_SIZE + 1];
+ uint32_t read_packets = 1;
+ int status = CISCODUMP_PARSER_STARTING;
+ int loop_end = 0;
+ unsigned packet_size = 0;
+ uint32_t len = 0;
+
+ /* Process response */
+ do {
+ loop_end = 0;
+ /* Read input till EOLN */
+ switch (ssh_channel_read_line_timeout(channel, line, &len, SSH_READ_BLOCK_SIZE)) {
+ case READ_PROMPT_EOLN:
+ /* Starting message? Ignore... */
+ if (NULL != g_strstr_len(line, -1, "Starting the packet display")) {
+ break;
+ }
+
+ /* End of dump message? End loop. */
+ if (NULL != g_strstr_len(line, -1, "Started capture point :")) {
+ loop_end = 1;
+ break;
+ }
+
+ status = parse_line_ios_xe_17(packet, &packet_size, line);
+ ws_debug("Packet offset %d\n", packet_size);
+
+ if ((status == CISCODUMP_PARSER_END_PACKET) && (packet_size > 0)) {
+ ws_debug("Read packet %d\n", read_packets);
+ if (read_packets > *processed_packets) {
+ int err;
+ int64_t cur_time = g_get_real_time();
+ uint64_t bytes_written;
+
+ ws_debug("Exporting packet %d\n", *processed_packets);
+ /* dump the packet to the pcap file */
+ if (!libpcap_write_packet(fp,
+ (uint32_t)(cur_time / G_USEC_PER_SEC), (uint32_t)(cur_time % G_USEC_PER_SEC),
+ packet_size, packet_size, packet, &bytes_written, &err)) {
+ ws_debug("Error in libpcap_write_packet(): %s", g_strerror(err));
+ break;
+ }
+ fflush(fp);
+ ws_debug("Dumped packet %u size: %u\n", *processed_packets, packet_size);
+ (*processed_packets)++;
+ }
+ packet_size = 0;
+ read_packets++;
+ }
+ break;
+ case READ_LINE_TIMEOUT:
+ /* Timeout is OK during reading of IOS XE 17 buffer */
+ break;
+ default:
+ /* We do not have better solution for that cases */
+ ws_warning("Error or response was too long\n");
+ return false;
+ }
+ len = 0;
+ ws_debug("loop end detection %d %d %d %d", extcap_end_application, loop_end, *processed_packets, count);
+ } while ((!extcap_end_application) && (!loop_end) && (*processed_packets < count));
+
+ return true;
+}
+
+/* IOS-XE 16: Queries buffer content and reads it */
+static void ssh_loop_read_ios_xe_16(ssh_channel channel, FILE* fp, const uint32_t count)
+{
+ char line[SSH_READ_BLOCK_SIZE + 1];
+ uint8_t* packet;
+ uint32_t processed_packets = 0;
+ bool running = true;
+ uint32_t current_max = 0;
+ uint32_t new_max;
+
+ /* This is big enough to put on the heap */
+ packet = (uint8_t*)g_malloc(PACKET_MAX_SIZE);
+
+ do {
+ uint32_t len = 0;
+
+ /* Query count of available packets in buffer */
+ if (ssh_channel_printf(channel, "show monitor capture %s buffer | inc packets in buf\nshow monitor capture %s | inc Status :\n", WIRESHARK_CAPTURE, WIRESHARK_CAPTURE) == EXIT_FAILURE) {
+ g_free(packet);
+ return;
+ }
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ g_free(packet);
+ return;
+ }
+ ws_debug("Read: %s", line);
+ packets_captured_count_ios_xe_16(line, &new_max, &running);
+ ws_debug("Max counts %d %d", current_max, new_max);
+ if (new_max == current_max) {
+ /* There is no change in count of available packets, repeat the loop */
+ continue;
+ } else if (new_max < current_max) {
+ /* Buffer was cleared, stop */
+ g_free(packet);
+ return;
+ }
+ current_max = new_max;
+ ws_debug("New packet count %d\n", current_max);
+
+ /* Dump buffer */
+ if (ssh_channel_printf(channel, "show monitor capture %s buffer dump\n", WIRESHARK_CAPTURE) == EXIT_FAILURE) {
+ g_free(packet);
+ return;
+ }
+
+ /* Process buffer */
+ if (!process_buffer_response_ios_xe_16(channel, packet, fp, count, &processed_packets)) {
+ g_free(packet);
+ return;
+ }
+ } while (!extcap_end_application && running && (processed_packets < count));
+
+ g_free(packet);
+
+ /* Discard any subsequent messages */
+ read_output_bytes_any(channel, -1, NULL);
+}
+
+/* IOS-XE 17: Queries buffer content and reads it */
+static void ssh_loop_read_ios_xe_17(ssh_channel channel, FILE* fp, const uint32_t count)
+{
+ uint8_t* packet;
+ uint32_t processed_packets = 0;
+ bool running = true;
+
+ /* This is big enough to put on the heap */
+ packet = (uint8_t*)g_malloc(PACKET_MAX_SIZE);
+
+ do {
+ //uint32_t len = 0;
+
+ /* Process buffer */
+ if (!process_buffer_response_ios_xe_17(channel, packet, fp, count, &processed_packets)) {
+ g_free(packet);
+ return;
+ }
+ if (extcap_end_application && send_output_quit) {
+ ws_debug("Terminating the output\n");
+ ssh_channel_printf(channel, "\n\x1e\n");
+ }
+ } while (!extcap_end_application && running && (processed_packets < count));
+
+ g_free(packet);
+
+ /* Discard any subsequent messages */
+ read_output_bytes_any(channel, -1, NULL);
+}
+
+/* ASA: Reads response and parses buffer till prompt end of packet received */
+static int process_buffer_response_asa(ssh_channel channel, uint8_t* packet, FILE* fp, const uint32_t count, uint32_t *processed_packets, uint32_t *current_max)
+{
+ char line[SSH_READ_BLOCK_SIZE + 1];
+ uint32_t read_packets = 1;
+ int status = CISCODUMP_PARSER_STARTING;
+ int loop_end = 0;
+ unsigned packet_size = 0;
+
+ do {
+ time_t pkt_time = 0;
+ uint32_t pkt_usec = 0;
+ uint32_t len = 0;
+
+ /* Dump buffer */
+ if (ssh_channel_printf(channel, "show cap %s packet-number %ld dump\n", WIRESHARK_CAPTURE, (*processed_packets)+1) == EXIT_FAILURE) {
+ return false;
+ }
+
+ /* Process response */
+ do {
+ loop_end = 0;
+ /* Read input till EOLN or prompt */
+ switch (ssh_channel_read_prompt(channel, line, &len, SSH_READ_BLOCK_SIZE)) {
+ case READ_PROMPT_EOLN:
+ status = parse_line_asa(packet, &packet_size, line, current_max, &pkt_time, &pkt_usec);
+
+ if (status == CISCODUMP_PARSER_END_PACKET) {
+ ws_debug("Read packet %d\n", read_packets);
+ int err;
+ uint64_t bytes_written;
+
+ ws_debug("Exporting packet %d\n", *processed_packets);
+ /* dump the packet to the pcap file */
+ if (!libpcap_write_packet(fp,
+ pkt_time, pkt_usec,
+ packet_size, packet_size, packet, &bytes_written, &err)) {
+ ws_debug("Error in libpcap_write_packet(): %s", g_strerror(err));
+ break;
+ }
+ fflush(fp);
+ ws_debug("Dumped packet %u size: %u\n", *processed_packets, packet_size);
+ (*processed_packets)++;
+ packet_size = 0;
+ read_packets++;
+ loop_end = 1;
+ }
+ break;
+ case READ_PROMPT_PROMPT:
+ ws_debug("Prompt found");
+ loop_end = 1;
+ break;
+ default:
+ /* We do not have better solution for that cases */
+ if (extcap_end_application) {
+ /* End by signal causes timeout so we decrease priority */
+ ws_debug("Timeout or response was too long\n");
+ } else {
+ ws_warning("Timeout or response was too long\n");
+ }
+ return false;
+ }
+ len = 0;
+ ws_debug("loop end detection1 %d %d", *processed_packets, count);
+ } while (!extcap_end_application && !loop_end);
+ ws_debug("loop end detection2 %d %d %d", extcap_end_application, *processed_packets, count);
+ } while (!extcap_end_application && (*processed_packets < *current_max) && ((*processed_packets < count)));
+
+ return true;
+}
+
+/* ASA: Queries buffer content and reads it */
+static void ssh_loop_read_asa(ssh_channel channel, FILE* fp, const uint32_t count)
+{
+ char line[SSH_READ_BLOCK_SIZE + 1];
+ uint8_t* packet;
+ uint32_t processed_packets = 0;
+ uint32_t current_max = 0;
+ bool running = true;
+ uint32_t new_max;
+
+ /* This is big enough to put on the heap */
+ packet = (uint8_t*)g_malloc(PACKET_MAX_SIZE);
+
+ do {
+ uint32_t len = 0;
+
+ /* Query count of available packets in buffer */
+ if (ssh_channel_printf(channel, "show cap %s packet-number 0 | inc packets captured\nshow cap | inc %s\n", WIRESHARK_CAPTURE, WIRESHARK_CAPTURE) == EXIT_FAILURE) {
+ g_free(packet);
+ return;
+ }
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ g_free(packet);
+ return;
+ }
+ ws_debug("Read: %s", line);
+ packets_captured_count_asa(line, &new_max, &running);
+ ws_debug("Max counts %d %d", current_max, new_max);
+ if (new_max == current_max) {
+ /* There is no change in count of available packets, repeat the loop */
+ continue;
+ } else if (new_max < current_max) {
+ /* Buffer was cleared, stop */
+ g_free(packet);
+ return;
+ }
+ current_max = new_max;
+ ws_debug("New packet count %d\n", current_max);
+
+ /* Process buffer */
+ if (!process_buffer_response_asa(channel, packet, fp, count, &processed_packets, &current_max)) {
+ g_free(packet);
+ return;
+ }
+ } while (!extcap_end_application && running && (processed_packets < count));
+
+ g_free(packet);
+
+ /* Discard any subsequent messages */
+ read_output_bytes_any(channel, -1, NULL);
+}
+
+
+static void ssh_loop_read(ssh_channel channel, FILE* fp, const uint32_t count _U_, CISCO_SW_TYPE sw_type)
+{
+ ws_debug("Starting reading loop");
+ switch (sw_type) {
+ case CISCO_IOS:
+ ssh_loop_read_ios(channel, fp, count);
+ break;
+ case CISCO_IOS_XE_16:
+ ssh_loop_read_ios_xe_16(channel, fp, count);
+ break;
+ case CISCO_IOS_XE_17:
+ ssh_loop_read_ios_xe_17(channel, fp, count);
+ break;
+ case CISCO_ASA:
+ ssh_loop_read_asa(channel, fp, count);
+ break;
+ case CISCO_UNKNOWN:
+ break;
+ }
+ ws_debug("Reading loop finished");
+}
+
+static int detect_host_prompt(ssh_channel channel)
+{
+ char line[SSH_READ_BLOCK_SIZE + 1];
+ int len = 0;
+ char prompt_2[SSH_READ_BLOCK_SIZE + 1];
+
+ /* Discard any login message */
+ if (read_output_bytes(channel, -1, NULL) == EXIT_FAILURE)
+ return EXIT_FAILURE;
+
+ if (ssh_channel_printf(channel, "\n") == EXIT_FAILURE)
+ return EXIT_FAILURE;
+
+ /* Check if there is any response to empty line */
+ switch (ssh_channel_read_line_timeout(channel, line, &len, SSH_READ_BLOCK_SIZE)) {
+ case READ_LINE_EOLN:
+ break;
+ default:
+ return EXIT_FAILURE;
+ }
+
+ if (ssh_channel_printf(channel, "\n") == EXIT_FAILURE)
+ return EXIT_FAILURE;
+
+ /* Read prompt_str and level char */
+ switch (ssh_channel_read_line_timeout(channel, line, &len, SSH_READ_BLOCK_SIZE)) {
+ case READ_LINE_EOLN:
+ break;
+ default:
+ return EXIT_FAILURE;
+ }
+ if (len > 0) {
+ g_strlcpy(prompt_str, line, SSH_READ_BLOCK_SIZE + 1);
+
+ /* Is there hashtag at the end => enabled mode? */
+ if (prompt_str[strlen(prompt_str)-1] != '#') {
+ /* Is there hashtag and space (ASA) at the end => enabled mode? */
+ if ((prompt_str[strlen(prompt_str)-2] != '#') || (prompt_str[strlen(prompt_str)-1] != ' ')) {
+ return EXIT_FAILURE;
+ }
+ }
+ prompt_len = (int32_t)strlen(prompt_str);
+ } else {
+ return EXIT_FAILURE;
+ }
+
+ if (ssh_channel_printf(channel, "\n") == EXIT_FAILURE)
+ return EXIT_FAILURE;
+
+ /* Read prompt_str and level char again */
+ switch (ssh_channel_read_line_timeout(channel, line, &len, SSH_READ_BLOCK_SIZE)) {
+ case READ_LINE_EOLN:
+ break;
+ default:
+ return EXIT_FAILURE;
+ }
+ if (len > 0) {
+ g_strlcpy(prompt_2, line, SSH_READ_BLOCK_SIZE + 1);
+ /* Does second prompt_str match first one? */
+ if (0 == g_strcmp0(prompt_str, prompt_2)) {
+ ws_debug("Detected prompt %s", prompt_str);
+ return true;
+ }
+ }
+
+ return EXIT_FAILURE;
+}
+
+static int check_ios_version(ssh_channel channel, CISCO_SW_TYPE *sw_type)
+{
+ char* cmdline_version = "show version | include Version\n";
+ const char* msg_ios = "Cisco IOS Software";
+ const char* msg_ios_xe = "Cisco IOS XE Software";
+ const char* msg_asa = "Cisco Adaptive Security Appliance Software";
+ const char* msg_version = "Version ";
+ char version[255];
+ int sw_major = 0;
+ int sw_minor = 0;
+ char* cur;
+
+ memset(version, 0x0, 255);
+
+ /* Discard any login message */
+ if (read_output_bytes(channel, -1, NULL) == EXIT_FAILURE)
+ return false;
+
+ if (ssh_channel_write(channel, cmdline_version, (uint32_t)strlen(cmdline_version)) == SSH_ERROR)
+ return false;
+ if (read_output_bytes(channel, 255, version) == EXIT_FAILURE)
+ return false;
+
+ /* Discard any subsequent text */
+ if (read_output_bytes(channel, -1, NULL) == EXIT_FAILURE)
+ return false;
+
+ /* We should check IOS XE first as its version contains IOS string too */
+ cur = g_strstr_len(version, strlen(version), msg_ios_xe);
+ if (cur) {
+ *sw_type = CISCO_IOS_XE_16;
+ cur += strlen(msg_ios_xe);
+ } else {
+ cur = g_strstr_len(version, strlen(version), msg_ios);
+ if (cur) {
+ *sw_type = CISCO_IOS;
+ cur += strlen(msg_ios);
+ } else {
+ cur = g_strstr_len(version, strlen(version), msg_asa);
+ if (cur) {
+ *sw_type = CISCO_ASA;
+ cur += strlen(msg_asa);
+ }
+ }
+ }
+
+ if (*sw_type != CISCO_UNKNOWN && cur) {
+ cur = g_strstr_len(cur, 255-strlen(cur), msg_version);
+ if (cur) {
+ cur += strlen(msg_version);
+ if (sscanf(cur, "%u.%u", &sw_major, &sw_minor) != 2)
+ return false;
+
+ switch (*sw_type) {
+ case CISCO_IOS:
+ ws_debug("Current IOS version: %u.%u", sw_major, sw_minor);
+ if ((sw_major > MINIMUM_IOS_MAJOR) || (sw_major == MINIMUM_IOS_MAJOR && sw_minor >= MINIMUM_IOS_MINOR)) {
+ return true;
+ }
+ break;
+ case CISCO_IOS_XE_16:
+ ws_debug("Current IOS XE version: %u.%u", sw_major, sw_minor);
+ if ((sw_major > MINIMUM_IOS_XE_MAJOR_17) || (sw_major == MINIMUM_IOS_XE_MAJOR_17 && sw_minor >= MINIMUM_IOS_XE_MINOR_17)) {
+ *sw_type = CISCO_IOS_XE_17;
+ return true;
+ }
+ if ((sw_major > MINIMUM_IOS_XE_MAJOR_16) || (sw_major == MINIMUM_IOS_XE_MAJOR_16 && sw_minor >= MINIMUM_IOS_XE_MINOR_16)) {
+ *sw_type = CISCO_IOS_XE_16;
+ return true;
+ }
+ break;
+ case CISCO_ASA:
+ ws_debug("Current ASA version: %u.%u", sw_major, sw_minor);
+ if ((sw_major > MINIMUM_ASA_MAJOR) || (sw_major == MINIMUM_ASA_MAJOR && sw_minor >= MINIMUM_ASA_MINOR)) {
+ return true;
+ }
+ break;
+ default:
+ return false;
+ }
+ ws_warning("Recognized software type, but minimal version requirements were not met\n");
+ return false;
+ } else {
+ ws_warning("Recognized software type %d, but unrecognized version\n", *sw_type);
+ }
+ } else {
+ ws_warning("Unrecognized type of control software.");
+ }
+
+ return false;
+}
+
+static bool run_capture_ios(ssh_channel channel, const char* iface, const char* cfilter, const uint32_t count)
+{
+ char* cmdline = NULL;
+ int ret = 0;
+ char line[SSH_READ_BLOCK_SIZE + 1];
+ uint32_t len;
+ char* iface_copy = g_strdup(iface);
+ char* iface_one;
+ char* str = NULL;
+ int wscp_cnt = 1;
+ char* wscp_str = NULL;
+
+ if (ssh_channel_printf(channel, "terminal length 0\n") == EXIT_FAILURE)
+ goto error;
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+
+ if (ssh_channel_printf(channel, "monitor capture buffer %s max-size 9500\n", WIRESHARK_CAPTURE_BUFFER) == EXIT_FAILURE)
+ goto error;
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+
+ if (count > 0) {
+ if (ssh_channel_printf(channel, "monitor capture buffer %s limit packet-count %u\n", WIRESHARK_CAPTURE_BUFFER, count) == EXIT_FAILURE)
+ goto error;
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+ }
+
+ if (cfilter) {
+ char* multiline_filter;
+ char* chr;
+
+ if (ssh_channel_printf(channel, "configure terminal\n") == EXIT_FAILURE)
+ goto error;
+
+ if (ssh_channel_printf(channel, "ip access-list extended %s\n", WIRESHARK_CAPTURE_ACCESSLIST) == EXIT_FAILURE)
+ goto error;
+
+ multiline_filter = g_strdup(cfilter);
+ chr = multiline_filter;
+ while((chr = g_strstr_len(chr, strlen(chr), ",")) != NULL) {
+ chr[0] = '\n';
+ ws_debug("Splitting filter into multiline");
+ }
+ ret = ssh_channel_write(channel, multiline_filter, (uint32_t)strlen(multiline_filter));
+ g_free(multiline_filter);
+ if (ret == SSH_ERROR)
+ goto error;
+
+ if (ssh_channel_printf(channel, "\nend\n") == EXIT_FAILURE)
+ goto error;
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+
+ if (ssh_channel_printf(channel, "monitor capture buffer %s filter access-list %s\n",
+ WIRESHARK_CAPTURE_BUFFER, WIRESHARK_CAPTURE_ACCESSLIST) == EXIT_FAILURE)
+ goto error;
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+ }
+
+ wscp_cnt = 1;
+ for (str = iface_copy; ; str = NULL) {
+ iface_one = strtok(str, ",");
+ if (iface_one == NULL)
+ break;
+
+ wscp_str = g_strdup_printf("%s_%d", WIRESHARK_CAPTURE_POINT, wscp_cnt);
+ wscp_cnt++;
+
+ if (0 == g_strcmp0(iface_one, "process-switched")) {
+ cmdline = g_strdup_printf("monitor capture point ip process-switched %s both", wscp_str);
+ } else if (0 == g_strcmp0(iface_one, "from-us")) {
+ cmdline = g_strdup_printf("monitor capture point ip process-switched %s from-us", wscp_str);
+ } else {
+ cmdline = g_strdup_printf("monitor capture point ip cef %s %s both", wscp_str, iface_one);
+ }
+
+ if (ssh_channel_printf(channel, "%s\n", cmdline) == EXIT_FAILURE)
+ goto error;
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+
+ if (ssh_channel_printf(channel, "monitor capture point associate %s %s \n", wscp_str,
+ WIRESHARK_CAPTURE_BUFFER) == EXIT_FAILURE)
+ goto error;
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+
+ g_free(cmdline);
+ cmdline = NULL;
+ }
+
+ wscp_cnt = 1;
+ for (str = iface_copy; ; str = NULL) {
+ iface_one = strtok(str, ",");
+ if (iface_one == NULL)
+ break;
+
+ wscp_str = g_strdup_printf("%s_%d", WIRESHARK_CAPTURE_POINT, wscp_cnt);
+ wscp_cnt++;
+
+ if (ssh_channel_printf(channel, "monitor capture point start %s\n", wscp_str) == EXIT_FAILURE)
+ goto error;
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+
+ g_free(wscp_str);
+ wscp_str = NULL;
+ }
+
+ g_free(iface_copy);
+ return true;
+error:
+ g_free(wscp_str);
+ g_free(iface_copy);
+ g_free(cmdline);
+ ws_warning("Error running ssh remote command");
+
+ ssh_channel_close(channel);
+ ssh_channel_free(channel);
+ return false;
+}
+
+static bool run_capture_ios_xe_16(ssh_channel channel, const char* iface, const char* cfilter, const uint32_t count)
+{
+ int ret = 0;
+ char line[SSH_READ_BLOCK_SIZE + 1];
+ uint32_t len;
+ char* iface_copy = g_strdup(iface);
+ char* iface_one;
+ char* str = NULL;
+
+ if (ssh_channel_printf(channel, "terminal length 0\n") == EXIT_FAILURE)
+ goto error;
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+
+ if (ssh_channel_printf(channel, "monitor capture %s limit packet-len 9500\n", WIRESHARK_CAPTURE) == EXIT_FAILURE)
+ goto error;
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+
+ if (count > 0) {
+ if (ssh_channel_printf(channel, "monitor capture %s limit packets %u\n", WIRESHARK_CAPTURE, count) == EXIT_FAILURE)
+ goto error;
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+ }
+
+ if (cfilter) {
+ char* multiline_filter;
+ char* chr;
+
+ if (ssh_channel_printf(channel, "configure terminal\n") == EXIT_FAILURE)
+ goto error;
+
+ if (ssh_channel_printf(channel, "ip access-list extended %s\n", WIRESHARK_CAPTURE_ACCESSLIST) == EXIT_FAILURE)
+ goto error;
+
+ multiline_filter = g_strdup(cfilter);
+ chr = multiline_filter;
+ while((chr = g_strstr_len(chr, strlen(chr), ",")) != NULL) {
+ chr[0] = '\n';
+ ws_debug("Splitting filter into multiline");
+ }
+ ret = ssh_channel_write(channel, multiline_filter, (uint32_t)strlen(multiline_filter));
+ g_free(multiline_filter);
+ if (ret == SSH_ERROR)
+ goto error;
+
+ if (ssh_channel_printf(channel, "\nend\n") == EXIT_FAILURE)
+ goto error;
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+
+ if (ssh_channel_printf(channel, "monitor capture %s access-list %s\n",
+ WIRESHARK_CAPTURE, WIRESHARK_CAPTURE_ACCESSLIST) == EXIT_FAILURE)
+ goto error;
+ } else {
+ if (ssh_channel_printf(channel, "monitor capture %s match any\n",
+ WIRESHARK_CAPTURE) == EXIT_FAILURE)
+ goto error;
+ }
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+
+ for (str = iface_copy; ; str = NULL) {
+ iface_one = strtok(str, ",");
+ if (iface_one == NULL)
+ break;
+
+ if (0 == g_strcmp0(iface_one, "control-plane")) {
+ if (ssh_channel_printf(channel, "monitor capture %s control-plane both\n", WIRESHARK_CAPTURE
+ ) == EXIT_FAILURE)
+ goto error;
+ } else {
+ if (ssh_channel_printf(channel, "monitor capture %s interface %s both\n", WIRESHARK_CAPTURE,
+ iface_one) == EXIT_FAILURE)
+ goto error;
+ }
+ }
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+
+ if (ssh_channel_printf(channel, "monitor capture %s start\n", WIRESHARK_CAPTURE) == EXIT_FAILURE)
+ goto error;
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE,
+ "(Capture is not Supported|Unable to activate Capture)")
+ ) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+
+ g_free(iface_copy);
+ return true;
+error:
+ g_free(iface_copy);
+ ws_warning("Error running ssh remote command");
+
+ ssh_channel_close(channel);
+ ssh_channel_free(channel);
+ return false;
+}
+
+static bool run_capture_ios_xe_17(ssh_channel channel, const char* iface, const char* cfilter, const uint32_t count)
+{
+ int ret = 0;
+ char line[SSH_READ_BLOCK_SIZE + 1];
+ uint32_t len;
+ char* iface_copy = g_strdup(iface);
+ char* iface_one;
+ char* str = NULL;
+
+ if (ssh_channel_printf(channel, "terminal length 0\n") == EXIT_FAILURE)
+ goto error;
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+
+ if (ssh_channel_printf(channel, "monitor capture %s limit packet-len 9500\n", WIRESHARK_CAPTURE) == EXIT_FAILURE)
+ goto error;
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+
+ if (count > 0) {
+ if (ssh_channel_printf(channel, "monitor capture %s limit packets %u\n", WIRESHARK_CAPTURE, count) == EXIT_FAILURE)
+ goto error;
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+ }
+
+ if (cfilter) {
+ char* multiline_filter;
+ char* chr;
+
+ if (ssh_channel_printf(channel, "configure terminal\n") == EXIT_FAILURE)
+ goto error;
+
+ if (ssh_channel_printf(channel, "ip access-list extended %s\n", WIRESHARK_CAPTURE_ACCESSLIST) == EXIT_FAILURE)
+ goto error;
+
+ multiline_filter = g_strdup(cfilter);
+ chr = multiline_filter;
+ while((chr = g_strstr_len(chr, strlen(chr), ",")) != NULL) {
+ chr[0] = '\n';
+ ws_debug("Splitting filter into multiline");
+ }
+ ret = ssh_channel_write(channel, multiline_filter, (uint32_t)strlen(multiline_filter));
+ g_free(multiline_filter);
+ if (ret == SSH_ERROR)
+ goto error;
+
+ if (ssh_channel_printf(channel, "\nend\n") == EXIT_FAILURE)
+ goto error;
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+
+ if (ssh_channel_printf(channel, "monitor capture %s access-list %s\n",
+ WIRESHARK_CAPTURE, WIRESHARK_CAPTURE_ACCESSLIST) == EXIT_FAILURE)
+ goto error;
+ } else {
+ if (ssh_channel_printf(channel, "monitor capture %s match any\n",
+ WIRESHARK_CAPTURE) == EXIT_FAILURE)
+ goto error;
+ }
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+
+ for (str = iface_copy; ; str = NULL) {
+ iface_one = strtok(str, ",");
+ if (iface_one == NULL)
+ break;
+
+ if (0 == g_strcmp0(iface_one, "control-plane")) {
+ if (ssh_channel_printf(channel, "monitor capture %s control-plane both\n", WIRESHARK_CAPTURE
+ ) == EXIT_FAILURE)
+ goto error;
+ } else {
+ if (ssh_channel_printf(channel, "monitor capture %s interface %s both\n", WIRESHARK_CAPTURE,
+ iface_one) == EXIT_FAILURE)
+ goto error;
+ }
+ }
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+
+ if (ssh_channel_printf(channel, "monitor capture %s start display dump\n", WIRESHARK_CAPTURE) == EXIT_FAILURE)
+ goto error;
+
+/*
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE,
+ "(Capture is not Supported|Unable to activate Capture)")
+ ) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+*/
+
+ g_free(iface_copy);
+ return true;
+error:
+ g_free(iface_copy);
+ ws_warning("Error running ssh remote command");
+
+ ssh_channel_close(channel);
+ ssh_channel_free(channel);
+ return false;
+}
+
+static bool run_capture_asa(ssh_channel channel, const char* iface, const char* cfilter)
+{
+ char* cmdline = NULL;
+ char line[SSH_READ_BLOCK_SIZE + 1];
+ uint32_t len;
+ char *sep;
+ bool process_filter = true;
+ char* iface_copy = g_strdup(iface);
+ char* iface_one;
+ char* str = NULL;
+
+ if (ssh_channel_printf(channel, "terminal pager 0\n") == EXIT_FAILURE)
+ goto error;
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+
+ for (str = iface_copy; ; str = NULL) {
+ iface_one = strtok(str, ",");
+ if (iface_one == NULL)
+ break;
+
+ if (0 == g_strcmp0(iface_one, "asp-drop")) {
+ /* asp-drop: asp-drop: capture %s type asp-drop all packet-length 9216 !INCLUDE-DECRYPTED
+ */
+ cmdline = g_strdup_printf("capture %s type asp-drop all packet-length 9216", WIRESHARK_CAPTURE);
+ } else if (NULL != (sep = g_strstr_len(iface_one, -1, "---"))) {
+ /* Interface type separator found. We support:
+ * isakmp---ifname: capture %s type isakmp packet-length 32810 interface %s
+ * // webvpn---ifname: capture %s type webvpn user %s !NO FILTER !INCLUDE-DECRYPTED
+ * lacp---ifname: capture %s type lacp interface %s packet-length 9216 !NO FILTER !INCLUDE-DECRYPTED
+ * tls-proxy---ifname: capture %s type tls-proxy packet-length 9216 interface %s
+ * inline-tag---ifname: capture %s type inline-tag packet-length 9216 interface %s
+ * raw-data---ifname: capture %s type rawdata packet-length 9216 interface %s
+ *
+ * We support /decrypted for some of it:
+ * isakmp/decrypted---ifname
+ * tls-proxy/decrypted---ifname
+ * inline-tag/decrypted---ifname
+ * raw-data/decrypted---ifname
+ */
+ char* ifname = sep+3;
+
+ if (strstr(iface_one, "isakmp")) {
+ if (strstr(iface_one, "/decrypted")) {
+ cmdline = g_strdup_printf("capture %s type isakmp include-decrypted packet-length 32810 interface %s", WIRESHARK_CAPTURE, ifname);
+ } else {
+ cmdline = g_strdup_printf("capture %s type isakmp packet-length 32810 interface %s", WIRESHARK_CAPTURE, ifname);
+ }
+ /* Completelly different output
+ } else if (strstr(iface_one, "webvpn")) {
+ cmdline = g_strdup_printf("capture %s type webvpn user %s", WIRESHARK_CAPTURE, ifname);
+ process_filter = false;
+ */
+ } else if (strstr(iface_one, "lacp")) {
+ cmdline = g_strdup_printf("capture %s type lacp interface %s packet-length 9216", WIRESHARK_CAPTURE, ifname);
+ process_filter = false;
+ } else if (strstr(iface_one, "tls-proxy")) {
+ if (strstr(iface_one, "/decrypted")) {
+ cmdline = g_strdup_printf("capture %s type tls-proxy include-decrypted packet-length 9216 interface %s", WIRESHARK_CAPTURE, ifname);
+ } else {
+ cmdline = g_strdup_printf("capture %s type tls-proxy packet-length 9216 interface %s", WIRESHARK_CAPTURE, ifname);
+ }
+ } else if (strstr(iface_one, "inline-tag")) {
+ if (strstr(iface_one, "/decrypted")) {
+ cmdline = g_strdup_printf("capture %s type inline-tag include-decrypted packet-length 9216 interface %s", WIRESHARK_CAPTURE, ifname);
+ } else {
+ cmdline = g_strdup_printf("capture %s type inline-tag packet-length 9216 interface %s", WIRESHARK_CAPTURE, ifname);
+ }
+ } else if (strstr(iface_one, "raw-data")) {
+ if (strstr(iface_one, "/decrypted")) {
+ cmdline = g_strdup_printf("capture %s type raw-data include-decrypted packet-length 9216 interface %s", WIRESHARK_CAPTURE, ifname);
+ } else {
+ cmdline = g_strdup_printf("capture %s type raw-data packet-length 9216 interface %s", WIRESHARK_CAPTURE, ifname);
+ }
+ } else {
+ ws_warning("Unknown interface type : %s", iface_one);
+ goto error;
+ }
+ } else {
+ /* Just interface name */
+ cmdline = g_strdup_printf("capture %s type raw-data packet-length 9216 interface %s", WIRESHARK_CAPTURE, iface_one);
+ }
+
+ if (ssh_channel_printf(channel, "%s\n", cmdline) == EXIT_FAILURE)
+ goto error;
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+
+ g_free(cmdline);
+ cmdline = NULL;
+ }
+
+ if (process_filter && cfilter) {
+ char* multiline_filter;
+ char* chr;
+ char* start;
+
+ if (ssh_channel_printf(channel, "configure terminal\n") == EXIT_FAILURE)
+ goto error;
+
+ multiline_filter = g_strdup(cfilter);
+ start = multiline_filter;
+ while((chr = g_strstr_len(start, strlen(start), ",")) != NULL) {
+ chr[0] = '\0';
+ ws_debug("Splitting filter into multiline");
+ if (ssh_channel_printf(channel, "access-list %s %s\n", WIRESHARK_CAPTURE_ACCESSLIST, start) == EXIT_FAILURE)
+ goto error;
+ start = chr+1;
+ }
+
+ if (ssh_channel_printf(channel, "access-list %s %s\n", WIRESHARK_CAPTURE_ACCESSLIST, start) == EXIT_FAILURE)
+ goto error;
+
+ if (ssh_channel_printf(channel, "\nend\n") == EXIT_FAILURE)
+ goto error;
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+
+ if (ssh_channel_printf(channel, "capture %s access-list %s\n", WIRESHARK_CAPTURE, WIRESHARK_CAPTURE_ACCESSLIST) == EXIT_FAILURE)
+ goto error;
+
+ if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) {
+ ws_warning("Received response: %s", crtoln(line));
+ goto error;
+ }
+ }
+
+ g_free(iface_copy);
+ return true;
+error:
+ g_free(iface_copy);
+ g_free(cmdline);
+ ws_warning("Error running ssh remote command");
+
+ ssh_channel_close(channel);
+ ssh_channel_free(channel);
+ return false;
+}
+
+static ssh_channel open_channel(ssh_session sshs)
+{
+ ssh_channel channel;
+
+ channel = ssh_channel_new(sshs);
+ if (!channel)
+ return NULL;
+
+ if (ssh_channel_open_session(channel) != SSH_OK)
+ goto error;
+
+ if (ssh_channel_request_pty(channel) != SSH_OK)
+ goto error;
+
+ if (ssh_channel_change_pty_size(channel, 80, 24) != SSH_OK)
+ goto error;
+
+ if (ssh_channel_request_shell(channel) != SSH_OK)
+ goto error;
+
+ return channel;
+
+error:
+ ws_warning("Error running ssh remote command");
+
+ ssh_channel_close(channel);
+ ssh_channel_free(channel);
+ return NULL;
+}
+
+static bool run_capture(ssh_channel channel, const char* iface, const char* cfilter, const uint32_t count, CISCO_SW_TYPE sw_type)
+{
+ switch (sw_type) {
+ case CISCO_IOS:
+ return run_capture_ios(channel, iface, cfilter, count);
+ case CISCO_IOS_XE_16:
+ return run_capture_ios_xe_16(channel, iface, cfilter, count);
+ case CISCO_IOS_XE_17:
+ return run_capture_ios_xe_17(channel, iface, cfilter, count);
+ case CISCO_ASA:
+ return run_capture_asa(channel, iface, cfilter);
+ case CISCO_UNKNOWN:
+ ws_warning("Unsupported cisco software. It will not collect any data most probably!");
+ return false;
+ }
+
+ return false;
+}
+
+static int ssh_open_remote_connection(const ssh_params_t* ssh_params, const char* iface, const char* cfilter,
+ const uint32_t count, const char* fifo)
+{
+ ssh_session sshs;
+ ssh_channel channel;
+ FILE* fp = stdout;
+ uint64_t bytes_written = 0;
+ int err;
+ int ret = EXIT_FAILURE;
+ char* err_info = NULL;
+
+ if (g_strcmp0(fifo, "-")) {
+ /* Open or create the output file */
+ fp = fopen(fifo, "wb");
+ if (!fp) {
+ ws_warning("Error creating output file: %s", g_strerror(errno));
+ return EXIT_FAILURE;
+ }
+ }
+
+ if (!libpcap_write_file_header(fp, 1, PCAP_SNAPLEN, false, &bytes_written, &err)) {
+ ws_warning("Can't write pcap file header");
+ goto cleanup;
+ }
+
+ fflush(fp);
+
+ ws_debug("Create first ssh session");
+ sshs = create_ssh_connection(ssh_params, &err_info);
+ if (!sshs) {
+ ws_warning("Error creating connection: %s", err_info);
+ goto cleanup;
+ }
+
+ channel = open_channel(sshs);
+ if (!channel) {
+ ret = EXIT_FAILURE;
+ goto cleanup;
+ }
+
+ if (!detect_host_prompt(channel))
+ goto cleanup;
+
+ if (!check_ios_version(channel, &global_sw_type))
+ goto cleanup;
+
+ /* clean up and exit */
+ ciscodump_cleanup(channel, iface, cfilter, global_sw_type);
+
+ if (!run_capture(channel, iface, cfilter, count, global_sw_type)) {
+ ret = EXIT_FAILURE;
+ goto cleanup;
+ }
+
+ /* read from channel and write into fp */
+ ssh_loop_read(channel, fp, count, global_sw_type);
+
+ /* Read loop can be terminated by signal or QUIT command in
+ * mid of long "show" command and its reading can take really
+ * long time. So we terminate ssh session and then
+ * create new one to cleanup configuration
+ */
+ ws_debug("Disconnect first ssh session");
+ ssh_channel_close(channel);
+ ssh_channel_free(channel);
+ ssh_cleanup(&sshs, &channel);
+
+ ws_debug("Create second ssh session");
+ sshs = create_ssh_connection(ssh_params, &err_info);
+ if (!sshs) {
+ ws_warning("Error creating connection: %s", err_info);
+ goto cleanup;
+ }
+
+ channel = open_channel(sshs);
+ if (!channel) {
+ ret = EXIT_FAILURE;
+ goto cleanup;
+ }
+
+ /* clean up and exit */
+ ciscodump_cleanup(channel, iface, cfilter, global_sw_type);
+
+ ssh_channel_close(channel);
+ ssh_channel_free(channel);
+ ssh_cleanup(&sshs, &channel);
+
+ ret = EXIT_SUCCESS;
+cleanup:
+ if (fp != stdout)
+ fclose(fp);
+
+ return ret;
+}
+
+static int list_config(char *interface, unsigned int remote_port)
+{
+ unsigned inc = 0;
+ char* ipfilter;
+
+ if (!interface) {
+ ws_warning("No interface specified.");
+ return EXIT_FAILURE;
+ }
+
+ if (g_strcmp0(interface, CISCODUMP_EXTCAP_INTERFACE)) {
+ ws_warning("interface must be %s", CISCODUMP_EXTCAP_INTERFACE);
+ return EXIT_FAILURE;
+ }
+
+ ipfilter = local_interfaces_to_filter(remote_port);
+
+ printf("arg {number=%u}{call=--remote-host}{display=Remote SSH server address}"
+ "{type=string}{tooltip=The remote SSH host. It can be both "
+ "an IP address or a hostname}{required=true}{group=Server}\n", inc++);
+ printf("arg {number=%u}{call=--remote-port}{display=Remote SSH server port}"
+ "{type=unsigned}{default=22}{tooltip=The remote SSH host port (1-65535)}"
+ "{range=1,65535}{group=Server}\n", inc++);
+ printf("arg {number=%u}{call=--remote-username}{display=Remote SSH server username}"
+ "{type=string}{default=%s}{tooltip=The remote SSH username. If not provided, "
+ "the current user will be used}{group=Authentication}\n", inc++, g_get_user_name());
+ printf("arg {number=%u}{call=--remote-password}{display=Remote SSH server password}"
+ "{type=password}{tooltip=The SSH password, used when other methods (SSH agent "
+ "or key files) are unavailable.}{group=Authentication}\n", inc++);
+ printf("arg {number=%u}{call=--sshkey}{display=Path to SSH private key}"
+ "{type=fileselect}{tooltip=The path on the local filesystem of the private ssh key}"
+ "{group=Authentication}\n", inc++);
+ printf("arg {number=%u}{call=--proxycommand}{display=ProxyCommand}"
+ "{type=string}{tooltip=The command to use as proxy for the SSH connection}"
+ "{group=Authentication}\n", inc++);
+ printf("arg {number=%u}{call--sshkey-passphrase}{display=SSH key passphrase}"
+ "{type=password}{tooltip=Passphrase to unlock the SSH private key}"
+ "{group=Authentication\n", inc++);
+ printf("arg {number=%u}{call=--remote-interface}{display=Remote interface}"
+ "{type=string}{required=true}{tooltip=The remote network interface used for capture"
+ "}{group=Capture}\n", inc++);
+ printf("arg {number=%u}{call=--remote-filter}{display=Remote capture filter}"
+ "{type=string}{tooltip=The remote capture filter}", inc++);
+ if (ipfilter)
+ printf("{default=%s}", ipfilter);
+ printf("{group=Capture}\n");
+ printf("arg {number=%u}{call=--remote-count}{display=Packets to capture}"
+ "{type=unsigned}{required=true}{tooltip=The number of remote packets to capture.}"
+ "{group=Capture}\n", inc++);
+
+ extcap_config_debug(&inc);
+
+ g_free(ipfilter);
+
+ return EXIT_SUCCESS;
+}
+
+int main(int argc, char *argv[])
+{
+ char* err_msg;
+ int result;
+ int option_idx = 0;
+ ssh_params_t* ssh_params = ssh_params_new();
+ char* remote_interface = NULL;
+ char* remote_filter = NULL;
+ uint32_t count = 0;
+ int ret = EXIT_FAILURE;
+ extcap_parameters * extcap_conf = g_new0(extcap_parameters, 1);
+ char* help_url;
+ char* help_header = NULL;
+
+ /* Initialize log handler early so we can have proper logging during startup. */
+ extcap_log_init("ciscodump");
+
+ /*
+ * Get credential information for later use.
+ */
+ init_process_policies();
+
+ /*
+ * Attempt to get the pathname of the directory containing the
+ * executable file.
+ */
+ err_msg = configuration_init(argv[0], NULL);
+ if (err_msg != NULL) {
+ ws_warning("Can't get pathname of directory containing the extcap program: %s.",
+ err_msg);
+ g_free(err_msg);
+ }
+
+ help_url = data_file_url("ciscodump.html");
+ extcap_base_set_util_info(extcap_conf, argv[0], CISCODUMP_VERSION_MAJOR, CISCODUMP_VERSION_MINOR,
+ CISCODUMP_VERSION_RELEASE, help_url);
+ add_libssh_info(extcap_conf);
+ g_free(help_url);
+ extcap_base_register_interface(extcap_conf, CISCODUMP_EXTCAP_INTERFACE, "Cisco remote capture", 147, "Remote capture dependent DLT");
+ if (!extcap_base_register_graceful_shutdown_cb(extcap_conf, graceful_shutdown_cb)) {
+ ret = EXIT_FAILURE;
+ goto end;
+ }
+
+ help_header = ws_strdup_printf(
+ " %s --extcap-interfaces\n"
+ " %s --extcap-interface=%s --extcap-dlts\n"
+ " %s --extcap-interface=%s --extcap-config\n"
+ " %s --extcap-interface=%s --remote-host myhost --remote-port 22222 "
+ "--remote-username myuser --remote-interface gigabit0/0 "
+ "--fifo=FILENAME --capture\n", argv[0], argv[0], CISCODUMP_EXTCAP_INTERFACE, argv[0],
+ CISCODUMP_EXTCAP_INTERFACE, argv[0], CISCODUMP_EXTCAP_INTERFACE);
+ extcap_help_add_header(extcap_conf, help_header);
+ g_free(help_header);
+
+ extcap_help_add_option(extcap_conf, "--help", "print this help");
+ extcap_help_add_option(extcap_conf, "--version", "print the version");
+ extcap_help_add_option(extcap_conf, "--remote-host <host>", "the remote SSH host");
+ extcap_help_add_option(extcap_conf, "--remote-port <port>", "the remote SSH port (default: 22)");
+ extcap_help_add_option(extcap_conf, "--remote-username <username>", "the remote SSH username (default: the current user)");
+ extcap_help_add_option(extcap_conf, "--remote-password <password>", "the remote SSH password. "
+ "If not specified, ssh-agent and ssh-key are used");
+ extcap_help_add_option(extcap_conf, "--sshkey <public key path>", "the path of the ssh key");
+ extcap_help_add_option(extcap_conf, "--sshkey-passphrase <public key passphrase>", "the passphrase to unlock public ssh");
+ extcap_help_add_option(extcap_conf, "--proxycommand <proxy command>", "the command to use as proxy for the ssh connection");
+ extcap_help_add_option(extcap_conf, "--remote-interface <iface>", "the remote capture interface");
+ extcap_help_add_option(extcap_conf, "--remote-filter <filter>", "a filter for remote capture "
+ "(default: don't capture data for all interfaces IPs)");
+
+ ws_opterr = 0;
+ ws_optind = 0;
+
+ if (argc == 1) {
+ extcap_help_print(extcap_conf);
+ goto end;
+ }
+
+ while ((result = ws_getopt_long(argc, argv, ":", longopts, &option_idx)) != -1) {
+
+ switch (result) {
+
+ case OPT_HELP:
+ extcap_help_print(extcap_conf);
+ ret = EXIT_SUCCESS;
+ goto end;
+
+ case OPT_VERSION:
+ extcap_version_print(extcap_conf);
+ goto end;
+
+ case OPT_REMOTE_HOST:
+ g_free(ssh_params->host);
+ ssh_params->host = g_strdup(ws_optarg);
+ break;
+
+ case OPT_REMOTE_PORT:
+ if (!ws_strtou16(ws_optarg, NULL, &ssh_params->port) || ssh_params->port == 0) {
+ ws_warning("Invalid port: %s", ws_optarg);
+ goto end;
+ }
+ break;
+
+ case OPT_REMOTE_USERNAME:
+ g_free(ssh_params->username);
+ ssh_params->username = g_strdup(ws_optarg);
+ break;
+
+ case OPT_REMOTE_PASSWORD:
+ g_free(ssh_params->password);
+ ssh_params->password = g_strdup(ws_optarg);
+ memset(ws_optarg, 'X', strlen(ws_optarg));
+ break;
+
+ case OPT_SSHKEY:
+ g_free(ssh_params->sshkey_path);
+ ssh_params->sshkey_path = g_strdup(ws_optarg);
+ break;
+
+ case OPT_SSHKEY_PASSPHRASE:
+ g_free(ssh_params->sshkey_passphrase);
+ ssh_params->sshkey_passphrase = g_strdup(ws_optarg);
+ memset(ws_optarg, 'X', strlen(ws_optarg));
+ break;
+
+ case OPT_PROXYCOMMAND:
+ g_free(ssh_params->proxycommand);
+ ssh_params->proxycommand = g_strdup(ws_optarg);
+ break;
+
+ case OPT_REMOTE_INTERFACE:
+ g_free(remote_interface);
+ remote_interface = g_strdup(ws_optarg);
+ break;
+
+ case OPT_REMOTE_FILTER:
+ g_free(remote_filter);
+ remote_filter = g_strdup(ws_optarg);
+ break;
+
+ case OPT_REMOTE_COUNT:
+ if (!ws_strtou32(ws_optarg, NULL, &count)) {
+ ws_warning("Invalid packet count: %s", ws_optarg);
+ goto end;
+ }
+ break;
+
+ case ':':
+ /* missing option argument */
+ ws_warning("Option '%s' requires an argument", argv[ws_optind - 1]);
+ break;
+
+ default:
+ if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg)) {
+ ws_warning("Invalid option: %s", argv[ws_optind - 1]);
+ goto end;
+ }
+ }
+ }
+
+ extcap_cmdline_debug(argv, argc);
+
+ if (ws_optind != argc) {
+ ws_warning("Unexpected extra option: %s", argv[ws_optind]);
+ goto end;
+ }
+
+ if (extcap_base_handle_interface(extcap_conf)) {
+ ret = EXIT_SUCCESS;
+ goto end;
+ }
+
+ if (extcap_conf->show_config) {
+ unsigned int port;
+ if (!ws_strtou16(ws_optarg, NULL, &ssh_params->port) || ssh_params->port == 0) {
+ port = 22;
+ } else {
+ port = ssh_params->port;
+ }
+ ret = list_config(extcap_conf->interface, port);
+ goto end;
+ }
+
+ err_msg = ws_init_sockets();
+ if (err_msg != NULL) {
+ ws_warning("ERROR: %s", err_msg);
+ g_free(err_msg);
+ ws_warning("%s", please_report_bug());
+ goto end;
+ }
+
+ if (extcap_conf->capture) {
+ if (!ssh_params->host) {
+ ws_warning("Missing parameter: --remote-host");
+ goto end;
+ }
+
+ if (!remote_interface) {
+ ws_warning("ERROR: No interface specified (--remote-interface)");
+ goto end;
+ }
+ if (count == 0) {
+ ws_warning("ERROR: count of packets must be specified (--remote-count)");
+ goto end;
+ }
+ ssh_params->debug = extcap_conf->debug;
+ ret = ssh_open_remote_connection(ssh_params, remote_interface,
+ remote_filter, count, extcap_conf->fifo);
+ } else {
+ ws_debug("You should not come here... maybe some parameter missing?");
+ ret = EXIT_FAILURE;
+ }
+
+end:
+ ssh_params_free(ssh_params);
+ g_free(remote_interface);
+ g_free(remote_filter);
+ extcap_base_cleanup(&extcap_conf);
+ return ret;
+}
+
+/*
+ * Editor modelines - https://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 8
+ * tab-width: 8
+ * indent-tabs-mode: t
+ * End:
+ *
+ * vi: set shiftwidth=8 tabstop=8 noexpandtab:
+ * :indentSize=8:tabSize=8:noTabs=false:
+ */
diff --git a/extcap/dpauxmon.c b/extcap/dpauxmon.c
new file mode 100644
index 0000000..68280a9
--- /dev/null
+++ b/extcap/dpauxmon.c
@@ -0,0 +1,589 @@
+/* dpauxmon.c
+ * dpauxmon is an extcap tool used to monitor DisplayPort AUX channel traffic
+ * coming in from the kernel via generic netlink
+ * Copyright 2018, Dirk Eibach, Guntermann & Drunck GmbH <dirk.eibach@gdsys.cc>
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "config.h"
+#define WS_LOG_DOMAIN "dpauxmon"
+
+#include <wireshark.h>
+
+#include "extcap-base.h"
+
+#include <wsutil/strtoi.h>
+#include <wsutil/filesystem.h>
+#include <wsutil/privileges.h>
+#include <wsutil/wslog.h>
+#include <writecap/pcapio.h>
+
+#include <netlink/netlink.h>
+#include <netlink/genl/genl.h>
+#include <netlink/genl/ctrl.h>
+#include <netlink/genl/mngt.h>
+
+#include <errno.h>
+
+#include <linux/genetlink.h>
+
+#include "dpauxmon_user.h"
+
+#define PCAP_SNAPLEN 128
+
+#define DPAUXMON_EXTCAP_INTERFACE "dpauxmon"
+#define DPAUXMON_VERSION_MAJOR "0"
+#define DPAUXMON_VERSION_MINOR "1"
+#define DPAUXMON_VERSION_RELEASE "0"
+
+FILE* pcap_fp = NULL;
+
+enum {
+ EXTCAP_BASE_OPTIONS_ENUM,
+ OPT_HELP,
+ OPT_VERSION,
+ OPT_INTERFACE_ID,
+};
+
+static struct ws_option longopts[] = {
+ EXTCAP_BASE_OPTIONS,
+ /* Generic application options */
+ { "help", ws_no_argument, NULL, OPT_HELP},
+ { "version", ws_no_argument, NULL, OPT_VERSION},
+ /* Interfaces options */
+ { "interface_id", ws_required_argument, NULL, OPT_INTERFACE_ID},
+ { 0, 0, 0, 0 }
+};
+
+static struct nla_policy dpauxmon_attr_policy[DPAUXMON_ATTR_MAX + 1] = {
+ [DPAUXMON_ATTR_IFINDEX] = { .type = NLA_U32 },
+ [DPAUXMON_ATTR_FROM_SOURCE] = { .type = NLA_FLAG },
+ [DPAUXMON_ATTR_TIMESTAMP] = { .type = NLA_MSECS },
+};
+
+struct family_handler_args {
+ const char *group;
+ int id;
+};
+
+static int list_config(char *interface)
+{
+ unsigned inc = 0;
+
+ if (!interface) {
+ ws_warning("No interface specified.");
+ return EXIT_FAILURE;
+ }
+
+ if (g_strcmp0(interface, DPAUXMON_EXTCAP_INTERFACE)) {
+ ws_warning("interface must be %s", DPAUXMON_EXTCAP_INTERFACE);
+ return EXIT_FAILURE;
+ }
+
+ printf("arg {number=%u}{call=--interface_id}{display=Interface index}"
+ "{type=unsigned}{range=1,65535}{default=%u}{tooltip=The dpauxmon interface index}\n",
+ inc++, 0);
+
+ extcap_config_debug(&inc);
+
+ return EXIT_SUCCESS;
+}
+
+static int setup_dumpfile(const char* fifo, FILE** fp)
+{
+ uint64_t bytes_written = 0;
+ int err;
+
+ if (!g_strcmp0(fifo, "-")) {
+ *fp = stdout;
+ return EXIT_SUCCESS;
+ }
+
+ *fp = fopen(fifo, "wb");
+ if (!(*fp)) {
+ ws_warning("Error creating output file: %s", g_strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ if (!libpcap_write_file_header(*fp, 275, PCAP_SNAPLEN, false, &bytes_written, &err)) {
+ ws_warning("Can't write pcap file header");
+ return EXIT_FAILURE;
+ }
+
+ fflush(*fp);
+
+ return EXIT_SUCCESS;
+}
+
+static int dump_packet(FILE* fp, const char* buf, const uint32_t buflen, uint64_t ts_usecs)
+{
+ uint64_t bytes_written = 0;
+ int err;
+ int ret = EXIT_SUCCESS;
+
+ if (!libpcap_write_packet(fp, ts_usecs / 1000000, ts_usecs % 1000000, buflen, buflen, buf, &bytes_written, &err)) {
+ ws_warning("Can't write packet");
+ ret = EXIT_FAILURE;
+ }
+
+ fflush(fp);
+
+ return ret;
+}
+
+static int error_handler(struct sockaddr_nl *nla _U_, struct nlmsgerr *err,
+ void *arg)
+{
+ int *ret = (int*)arg;
+ *ret = err->error;
+ return NL_STOP;
+}
+
+static int ack_handler(struct nl_msg *msg _U_, void *arg)
+{
+ int *ret = (int*)arg;
+ *ret = 0;
+ return NL_STOP;
+}
+
+static int family_handler(struct nl_msg *msg, void *arg)
+{
+ struct family_handler_args *grp = (struct family_handler_args *)arg;
+ struct nlattr *tb[CTRL_ATTR_MAX + 1];
+ struct genlmsghdr *gnlh = (struct genlmsghdr *)nlmsg_data(nlmsg_hdr(msg));
+ struct nlattr *mcgrp;
+ int rem_mcgrp;
+
+ nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
+ genlmsg_attrlen(gnlh, 0), NULL);
+
+ if (!tb[CTRL_ATTR_MCAST_GROUPS])
+ return NL_SKIP;
+
+ nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mcgrp) {
+ struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1];
+
+ nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX,
+ (struct nlattr *)nla_data(mcgrp), nla_len(mcgrp), NULL);
+
+ if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME] ||
+ !tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID])
+ continue;
+
+ if (strncmp((const char*)nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]),
+ grp->group,
+ nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME])))
+ continue;
+
+ grp->id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]);
+
+ break;
+ }
+
+ return NL_SKIP;
+}
+
+static int nl_get_multicast_id(struct nl_sock *sock, int family,
+ const char *group)
+{
+ struct nl_msg *msg;
+ struct nl_cb *cb;
+ int ret, ctrlid;
+ struct family_handler_args grp = {
+ .group = group,
+ .id = -ENOENT,
+ };
+
+ msg = nlmsg_alloc();
+ if (!msg)
+ return -ENOMEM;
+
+ cb = nl_cb_alloc(NL_CB_DEFAULT);
+ if (!cb) {
+ ret = -ENOMEM;
+ goto out_fail_cb;
+ }
+
+ ctrlid = genl_ctrl_resolve(sock, "nlctrl");
+
+ genlmsg_put(msg, 0, 0, ctrlid, 0, 0, CTRL_CMD_GETFAMILY, 0);
+
+ ret = -ENOBUFS;
+ NLA_PUT_U16(msg, CTRL_ATTR_FAMILY_ID, family);
+
+ ret = nl_send_auto_complete(sock, msg);
+ if (ret < 0)
+ goto nla_put_failure;
+
+ ret = 1;
+
+ nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &ret);
+ nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &ret);
+ nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, family_handler, &grp);
+
+ while (ret > 0)
+ nl_recvmsgs(sock, cb);
+
+ if (ret == 0)
+ ret = grp.id;
+nla_put_failure:
+ nl_cb_put(cb);
+out_fail_cb:
+ nlmsg_free(msg);
+ return ret;
+}
+
+/*
+ * netlink callback handlers
+ */
+
+static int nl_receive_timeout(struct nl_sock* sk, struct sockaddr_nl* nla, unsigned char** buf, struct ucred** creds)
+{
+ struct pollfd fds = {nl_socket_get_fd(sk), POLLIN, 0};
+ int poll_res = poll(&fds, 1, 500);
+
+ if (poll_res < 0) {
+ ws_debug("poll() failed in nl_receive_timeout");
+ g_usleep(500000);
+ return -nl_syserr2nlerr(errno);
+ }
+
+ return poll_res ? nl_recv(sk, nla, buf, creds) : 0;
+}
+
+static int send_start(struct nl_sock *sock, int family, unsigned int interface_id)
+{
+ struct nl_msg *msg;
+ void *hdr;
+ int err;
+ int res = 0;
+
+ msg = nlmsg_alloc();
+ if (msg == NULL) {
+ ws_critical("Unable to allocate netlink message");
+ return -ENOMEM;
+ }
+
+ hdr = genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, 0,
+ DPAUXMON_CMD_START, 1);
+ if (hdr == NULL) {
+ ws_critical("Unable to write genl header");
+ res = -ENOMEM;
+ goto out_free;
+ }
+
+ if ((err = nla_put_u32(msg, DPAUXMON_ATTR_IFINDEX, interface_id)) < 0) {
+ ws_critical("Unable to add attribute: %s", nl_geterror(err));
+ res = -EIO;
+ goto out_free;
+ }
+
+ if ((err = nl_send_auto_complete(sock, msg)) < 0)
+ ws_debug("Starting monitor failed, already running? :%s", nl_geterror(err));
+
+out_free:
+ nlmsg_free(msg);
+ return res;
+}
+
+static void send_stop(struct nl_sock *sock, int family, unsigned int interface_id)
+{
+ struct nl_msg *msg;
+ void *hdr;
+ int err;
+
+ msg = nlmsg_alloc();
+ if (msg == NULL) {
+ ws_critical("Unable to allocate netlink message");
+ return;
+ }
+
+ hdr = genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, 0,
+ DPAUXMON_CMD_STOP, 1);
+ if (hdr == NULL) {
+ ws_critical("Unable to write genl header");
+ goto out_free;
+ }
+
+ if ((err = nla_put_u32(msg, DPAUXMON_ATTR_IFINDEX, interface_id)) < 0) {
+ ws_critical("Unable to add attribute: %s", nl_geterror(err));
+ goto out_free;
+ }
+
+ if ((err = nl_send_auto_complete(sock, msg)) < 0) {
+ ws_critical("Unable to send message: %s", nl_geterror(err));
+ goto out_free;
+ }
+
+out_free:
+ nlmsg_free(msg);
+}
+
+static int handle_data(struct nl_cache_ops *unused _U_, struct genl_cmd *cmd _U_,
+ struct genl_info *info, void *arg _U_)
+{
+ unsigned char *data;
+ uint32_t data_size;
+ uint64_t ts = 0;
+ uint8_t packet[21] = { 0x00 };
+
+ if (!info->attrs[DPAUXMON_ATTR_DATA])
+ return NL_SKIP;
+
+ data = (unsigned char*)nla_data(info->attrs[DPAUXMON_ATTR_DATA]);
+ data_size = nla_len(info->attrs[DPAUXMON_ATTR_DATA]);
+
+ if (data_size > 19) {
+ ws_debug("Invalid packet size %u", data_size);
+ return NL_SKIP;
+ }
+
+ if (info->attrs[DPAUXMON_ATTR_TIMESTAMP])
+ ts = nla_get_msecs(info->attrs[DPAUXMON_ATTR_TIMESTAMP]);
+
+ packet[1] = info->attrs[DPAUXMON_ATTR_FROM_SOURCE] ? 0x01 : 0x00;
+
+ memcpy(&packet[2], data, data_size);
+
+ if (dump_packet(pcap_fp, packet, data_size + 2, ts) == EXIT_FAILURE)
+ extcap_end_application = true;
+
+ return NL_OK;
+}
+
+static int parse_cb(struct nl_msg *msg, void *arg _U_)
+{
+ return genl_handle_msg(msg, NULL);
+}
+
+static struct genl_cmd cmds[] = {
+#if 0
+ {
+ .c_id = DPAUXMON_CMD_START,
+ .c_name = "dpauxmon start",
+ .c_maxattr = DPAUXMON_ATTR_MAX,
+ .c_attr_policy = dpauxmon_attr_policy,
+ .c_msg_parser = &handle_start,
+ },
+ {
+ .c_id = DPAUXMON_CMD_STOP,
+ .c_name = "dpauxmon stop",
+ .c_maxattr = DPAUXMON_ATTR_MAX,
+ .c_attr_policy = dpauxmon_attr_policy,
+ .c_msg_parser = &handle_stop,
+ },
+#endif
+ {
+ .c_id = DPAUXMON_CMD_DATA,
+ .c_name = "dpauxmon data",
+ .c_maxattr = DPAUXMON_ATTR_MAX,
+ .c_attr_policy = dpauxmon_attr_policy,
+ .c_msg_parser = &handle_data,
+ },
+};
+
+#define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0]))
+
+static struct genl_ops ops = {
+ .o_name = "dpauxmon",
+ .o_cmds = cmds,
+ .o_ncmds = ARRAY_SIZE(cmds),
+};
+
+struct nl_sock *sock;
+
+static void run_listener(const char* fifo, unsigned int interface_id)
+{
+ int err;
+ int grp;
+ struct nl_cb *socket_cb;
+
+ if (setup_dumpfile(fifo, &pcap_fp) == EXIT_FAILURE) {
+ if (pcap_fp)
+ goto close_out;
+ }
+
+ if (!(sock = nl_socket_alloc())) {
+ ws_critical("Unable to allocate netlink socket");
+ goto close_out;
+ }
+
+ if ((err = nl_connect(sock, NETLINK_GENERIC)) < 0) {
+ ws_critical("Unable to connect netlink socket: %s",
+ nl_geterror(err));
+ goto free_out;
+ }
+
+ if ((err = genl_register_family(&ops)) < 0) {
+ ws_critical("Unable to register Generic Netlink family: %s",
+ nl_geterror(err));
+ goto err_out;
+ }
+
+ if ((err = genl_ops_resolve(sock, &ops)) < 0) {
+ ws_critical("Unable to resolve family name: %s",
+ nl_geterror(err));
+ goto err_out;
+ }
+
+ /* register notification handler callback */
+ if ((err = nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM,
+ parse_cb, NULL)) < 0) {
+ ws_critical("Unable to modify valid message callback %s",
+ nl_geterror(err));
+ goto err_out;
+ }
+
+ grp = nl_get_multicast_id(sock, ops.o_id, "notify");
+ nl_socket_add_membership(sock, grp);
+
+ if (!(socket_cb = nl_socket_get_cb(sock))) {
+ ws_warning("Can't overwrite recv callback");
+ } else {
+ nl_cb_overwrite_recv(socket_cb, nl_receive_timeout);
+ nl_cb_put(socket_cb);
+ }
+
+ err = send_start(sock, ops.o_id, interface_id);
+ if (err)
+ goto err_out;
+
+ nl_socket_disable_seq_check(sock);
+
+ ws_debug("DisplayPort AUX monitor running on interface %u", interface_id);
+
+ while(!extcap_end_application) {
+ if ((err = nl_recvmsgs_default(sock)) < 0)
+ ws_warning("Unable to receive message: %s", nl_geterror(err));
+ }
+
+ send_stop(sock, ops.o_id, interface_id);
+
+err_out:
+ nl_close(sock);
+free_out:
+ nl_socket_free(sock);
+close_out:
+ fclose(pcap_fp);
+}
+
+int main(int argc, char *argv[])
+{
+ char* configuration_init_error;
+ int option_idx = 0;
+ int result;
+ unsigned int interface_id = 0;
+ int ret = EXIT_FAILURE;
+ extcap_parameters* extcap_conf = g_new0(extcap_parameters, 1);
+ char* help_header = NULL;
+
+ /* Initialize log handler early so we can have proper logging during startup. */
+ extcap_log_init("dpauxmon");
+
+ /*
+ * Get credential information for later use.
+ */
+ init_process_policies();
+
+ /*
+ * Attempt to get the pathname of the directory containing the
+ * executable file.
+ */
+ configuration_init_error = configuration_init(argv[0], NULL);
+ if (configuration_init_error != NULL) {
+ ws_warning("Can't get pathname of directory containing the extcap program: %s.",
+ configuration_init_error);
+ g_free(configuration_init_error);
+ }
+
+ extcap_base_set_util_info(extcap_conf, argv[0], DPAUXMON_VERSION_MAJOR, DPAUXMON_VERSION_MINOR, DPAUXMON_VERSION_RELEASE,
+ NULL);
+ extcap_base_register_interface(extcap_conf, DPAUXMON_EXTCAP_INTERFACE, "DisplayPort AUX channel monitor capture", 275, "DisplayPort AUX channel monitor");
+
+ help_header = ws_strdup_printf(
+ " %s --extcap-interfaces\n"
+ " %s --extcap-interface=%s --extcap-dlts\n"
+ " %s --extcap-interface=%s --extcap-config\n"
+ " %s --extcap-interface=%s --interface_id 0 --fifo myfifo --capture",
+ argv[0], argv[0], DPAUXMON_EXTCAP_INTERFACE, argv[0], DPAUXMON_EXTCAP_INTERFACE, argv[0], DPAUXMON_EXTCAP_INTERFACE);
+ extcap_help_add_header(extcap_conf, help_header);
+ g_free(help_header);
+ extcap_help_add_option(extcap_conf, "--help", "print this help");
+ extcap_help_add_option(extcap_conf, "--version", "print the version");
+ extcap_help_add_option(extcap_conf, "--port <port> ", "the dpauxmon interface index");
+
+ ws_opterr = 0;
+ ws_optind = 0;
+
+ if (argc == 1) {
+ extcap_help_print(extcap_conf);
+ goto end;
+ }
+
+ while ((result = ws_getopt_long(argc, argv, ":", longopts, &option_idx)) != -1) {
+ switch (result) {
+
+ case OPT_HELP:
+ extcap_help_print(extcap_conf);
+ ret = EXIT_SUCCESS;
+ goto end;
+
+ case OPT_VERSION:
+ extcap_version_print(extcap_conf);
+ goto end;
+
+ case OPT_INTERFACE_ID:
+ if (!ws_strtou32(ws_optarg, NULL, &interface_id)) {
+ ws_warning("Invalid interface id: %s", ws_optarg);
+ goto end;
+ }
+ break;
+
+ case ':':
+ /* missing option argument */
+ ws_warning("Option '%s' requires an argument", argv[ws_optind - 1]);
+ break;
+
+ default:
+ if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg)) {
+ ws_warning("Invalid option: %s", argv[ws_optind - 1]);
+ goto end;
+ }
+ }
+ }
+
+ extcap_cmdline_debug(argv, argc);
+
+ if (ws_optind != argc) {
+ ws_warning("Unexpected extra option: %s", argv[ws_optind]);
+ goto end;
+ }
+
+ if (extcap_base_handle_interface(extcap_conf)) {
+ ret = EXIT_SUCCESS;
+ goto end;
+ }
+
+ if (!extcap_base_register_graceful_shutdown_cb(extcap_conf, NULL)) {
+ ret = EXIT_SUCCESS;
+ goto end;
+ }
+
+ if (extcap_conf->show_config) {
+ ret = list_config(extcap_conf->interface);
+ goto end;
+ }
+
+ if (extcap_conf->capture)
+ run_listener(extcap_conf->fifo, interface_id);
+
+end:
+ /* clean up stuff */
+ extcap_base_cleanup(&extcap_conf);
+ return ret;
+}
diff --git a/extcap/dpauxmon_user.h b/extcap/dpauxmon_user.h
new file mode 100644
index 0000000..645e4b8
--- /dev/null
+++ b/extcap/dpauxmon_user.h
@@ -0,0 +1,59 @@
+/** @file
+ *
+ * Copyright 2018, Dirk Eibach, Guntermann & Drunck GmbH <dirk.eibach@gdsys.cc>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef DPAUXMON_USER_H_
+#define DPAUXMON_USER_H_
+
+#include <linux/types.h>
+
+/*
+ * Generic Netlink Interface for DisplayPort AUX channel monitoring
+ */
+
+/*
+ * enum dpauxmon_cmd - supported dpauxmon netlink commands
+ *
+ * @__DPAUXMON_CMD_UNSPEC: unspecified command to catch errors
+ *
+ * @DPAUXMON_CMD_START: start monitoring on %DPAUXMON_ATTR_IFINDEX
+ * @DPAUXMON_CMD_STOP: stop monitoring on %DPAUXMON_ATTR_IFINDEX
+ * @DPAUXMON_CMD_DATA: captured data from %DPAUXMON_ATTR_IFINDEX
+ */
+enum dpauxmon_cmd {
+ __DPAUXMON_CMD_UNSPEC,
+ DPAUXMON_CMD_START,
+ DPAUXMON_CMD_STOP,
+ DPAUXMON_CMD_DATA,
+
+ /* keep last */
+ __DPAUXMON_CMD_MAX,
+ DPAUXMON_CMD_MAX = __DPAUXMON_CMD_MAX - 1,
+};
+
+/*
+ * enum dpauxmon_attr - dpauxmon netlink attributes
+ *
+ * @__DPAUXMON_ATTR_UNSPEC: unspecified attribute to catch errors
+ *
+ * @DPAUXMON_ATTR_IFINDEX: index of dpauxmon unit to operate on
+ * @DPAUXMON_ATTR_DATA: dpauxmon data payload
+ * @DPAUXMON_ATTR_FROM_SOURCE: data payload is sent from source
+ * @DPAUXMON_ATTR_TIMESTAMP: data payload is sent from source
+ */
+enum dpauxmon_attr {
+ __DPAUXMON_ATTR_UNSPEC,
+ DPAUXMON_ATTR_IFINDEX, /* NLA_U32 */
+ DPAUXMON_ATTR_DATA, /* NLA_BINARY */
+ DPAUXMON_ATTR_FROM_SOURCE, /* NLA_FLAG */
+ DPAUXMON_ATTR_TIMESTAMP, /* NLA_MSECS */
+
+ /* keep last */
+ __DPAUXMON_ATTR_AFTER_LAST,
+ DPAUXMON_ATTR_MAX = __DPAUXMON_ATTR_AFTER_LAST - 1
+};
+
+#endif
diff --git a/extcap/etl.c b/extcap/etl.c
new file mode 100644
index 0000000..78f2bf2
--- /dev/null
+++ b/extcap/etl.c
@@ -0,0 +1,784 @@
+/* etl.c
+ *
+ * Copyright 2020, Odysseus Yang
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * Reads an ETL file and writes out a pcap file with LINKTYPE_ETW.
+ *
+ * https://docs.microsoft.com/en-us/windows/win32/etw/event-tracing-portal
+ */
+
+#include "config.h"
+#define WS_LOG_DOMAIN "etwdump"
+
+#include "etl.h"
+#include "wsutil/ws_getopt.h"
+#include "wsutil/strtoi.h"
+#include "etw_message.h"
+
+#include <rpc.h>
+#include <winevt.h>
+
+#define MAX_PACKET_SIZE 0xFFFF
+#define G_NSEC_PER_SEC 1000000000
+#define ADD_OFFSET_TO_POINTER(buffer, offset) (((PBYTE)buffer) + offset)
+#define ROUND_UP_COUNT(Count,Pow2) \
+ ( ((Count)+(Pow2)-1) & (~(((int)(Pow2))-1)) )
+
+extern int g_include_undecidable_event;
+
+//Microsoft-Windows-Wmbclass-Opn
+const GUID mbb_provider = { 0xA42FE227, 0xA7BF, 0x4483, {0xA5, 0x02, 0x6B, 0xCD, 0xA4, 0x28, 0xCD, 0x96} };
+// Microsoft-Windows-NDIS-PacketCapture
+const GUID ndis_capture_provider = { 0x2ed6006e, 0x4729, 0x4609, 0xb4, 0x23, 0x3e, 0xe7, 0xbc, 0xd6, 0x78, 0xef };
+
+EXTERN_C const GUID DECLSPEC_SELECTANY EventTraceGuid = { 0x68fdd900, 0x4a3e, 0x11d1, {0x84, 0xf4, 0x00, 0x00, 0xf8, 0x04, 0x64, 0xe3} };
+EXTERN_C const GUID DECLSPEC_SELECTANY ImageIdGuid = { 0xb3e675d7, 0x2554, 0x4f18, { 0x83, 0xb, 0x27, 0x62, 0x73, 0x25, 0x60, 0xde } };
+EXTERN_C const GUID DECLSPEC_SELECTANY SystemConfigExGuid = { 0x9b79ee91, 0xb5fd, 0x41c0, { 0xa2, 0x43, 0x42, 0x48, 0xe2, 0x66, 0xe9, 0xd0 } };
+EXTERN_C const GUID DECLSPEC_SELECTANY EventMetadataGuid = { 0xbbccf6c1, 0x6cd1, 0x48c4, {0x80, 0xff, 0x83, 0x94, 0x82, 0xe3, 0x76, 0x71 } };
+EXTERN_C const GUID DECLSPEC_SELECTANY ZeroGuid = { 0 };
+
+typedef struct _WTAP_ETL_RECORD {
+ EVENT_HEADER EventHeader; // Event header
+ ETW_BUFFER_CONTEXT BufferContext; // Buffer context
+ ULONG UserDataLength;
+ ULONG MessageLength;
+ ULONG ProviderLength;
+} WTAP_ETL_RECORD;
+
+enum {
+ OPT_PROVIDER,
+ OPT_KEYWORD,
+ OPT_LEVEL,
+};
+
+static struct ws_option longopts[] = {
+ { "p", ws_required_argument, NULL, OPT_PROVIDER},
+ { "k", ws_required_argument, NULL, OPT_KEYWORD},
+ { "l", ws_required_argument, NULL, OPT_LEVEL},
+ { 0, 0, 0, 0 }
+};
+
+typedef struct _PROVIDER_FILTER {
+ GUID ProviderId;
+ ULONG64 Keyword;
+ UCHAR Level;
+} PROVIDER_FILTER;
+
+char g_err_info[FILENAME_MAX] = { 0 };
+int g_err = ERROR_SUCCESS;
+static wtap_dumper* g_pdh = NULL;
+extern ULONGLONG g_num_events;
+static PROVIDER_FILTER g_provider_filters[32] = { 0 };
+static BOOL g_is_live_session = false;
+
+static void WINAPI event_callback(PEVENT_RECORD ev);
+void etw_dump_write_opn_event(PEVENT_RECORD ev, ULARGE_INTEGER timestamp);
+void etw_dump_write_ndiscap_event(PEVENT_RECORD ev, ULARGE_INTEGER timestamp);
+void etw_dump_write_general_event(PEVENT_RECORD ev, ULARGE_INTEGER timestamp);
+void etw_dump_write_event_head_only(PEVENT_RECORD ev, ULARGE_INTEGER timestamp);
+void wtap_etl_rec_dump(char* etl_record, ULONG total_packet_length, ULONG original_packet_length, unsigned int interface_id, BOOLEAN is_inbound, ULARGE_INTEGER timestamp, int pkt_encap, char* comment, unsigned short comment_length);
+wtap_dumper* etw_dump_open(const char* pcapng_filename, int* err, char** err_info);
+
+DWORD GetPropertyValue(WCHAR* ProviderId, EVT_PUBLISHER_METADATA_PROPERTY_ID PropertyId, PEVT_VARIANT* Value)
+{
+ BOOL bRet;
+ DWORD err = ERROR_SUCCESS;
+ PEVT_VARIANT value = NULL;
+ DWORD bufSize = 0;
+ DWORD bufUsedOrReqd = 0;
+
+ EVT_HANDLE pubHandle = EvtOpenPublisherMetadata(NULL, ProviderId, NULL, GetThreadLocale(), 0);
+ if (pubHandle == NULL)
+ {
+ return GetLastError();
+ }
+
+ /*
+ * Get required size for property
+ */
+ bRet = EvtGetPublisherMetadataProperty(
+ pubHandle,
+ PropertyId,
+ 0,
+ bufSize,
+ value,
+ &bufUsedOrReqd);
+
+ if (!bRet && ((err = GetLastError()) != ERROR_INSUFFICIENT_BUFFER))
+ {
+ return err;
+ }
+ else if (bRet) /* Didn't expect this to succeed */
+ {
+ return ERROR_INVALID_STATE;
+ }
+
+ value = (PEVT_VARIANT)g_malloc(bufUsedOrReqd);
+ if (!value)
+ {
+ return ERROR_INSUFFICIENT_BUFFER;
+ }
+ bufSize = bufUsedOrReqd;
+
+ /*
+ * Get the property value
+ */
+ bRet = EvtGetPublisherMetadataProperty(
+ pubHandle,
+ PropertyId,
+ 0,
+ bufSize,
+ value,
+ &bufUsedOrReqd);
+ if (!bRet)
+ {
+ g_free(value);
+ return GetLastError();
+ }
+
+ *Value = value;
+ return ERROR_SUCCESS;
+}
+
+wtap_open_return_val etw_dump(const char* etl_filename, const char* pcapng_filename, const char* params, int* err, char** err_info)
+{
+ EVENT_TRACE_LOGFILE log_file = { 0 };
+ WCHAR w_etl_filename[FILENAME_MAX] = { 0 };
+ wtap_open_return_val returnVal = WTAP_OPEN_MINE;
+
+ SUPER_EVENT_TRACE_PROPERTIES super_trace_properties = { 0 };
+ super_trace_properties.prop.Wnode.BufferSize = sizeof(SUPER_EVENT_TRACE_PROPERTIES);
+ super_trace_properties.prop.Wnode.ClientContext = 2;
+ super_trace_properties.prop.Wnode.Flags = WNODE_FLAG_TRACED_GUID;
+ super_trace_properties.prop.LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
+ super_trace_properties.prop.LogFileMode = EVENT_TRACE_REAL_TIME_MODE;
+ TRACEHANDLE traceControllerHandle = (TRACEHANDLE)INVALID_HANDLE_VALUE;
+ TRACEHANDLE trace_handle = INVALID_PROCESSTRACE_HANDLE;
+
+ SecureZeroMemory(g_provider_filters, sizeof(g_provider_filters));
+ SecureZeroMemory(g_err_info, FILENAME_MAX);
+ g_err = ERROR_SUCCESS;
+ g_num_events = 0;
+ g_is_live_session = false;
+
+ if (params)
+ {
+ int opt_result = 0;
+ int option_idx = 0;
+ int provider_idx = 0;
+ char** params_array = NULL;
+ int params_array_num = 0;
+ WCHAR provider_id[FILENAME_MAX] = { 0 };
+ ULONG convert_level = 0;
+
+ params_array = g_strsplit(params, " ", -1);
+ while (params_array[params_array_num])
+ {
+ params_array_num++;
+ }
+
+ ws_optind = 0;
+ while ((opt_result = ws_getopt_long(params_array_num, params_array, ":", longopts, &option_idx)) != -1) {
+ switch (opt_result) {
+ case OPT_PROVIDER:
+ mbstowcs(provider_id, ws_optarg, FILENAME_MAX);
+ if (UuidFromString(provider_id, &g_provider_filters[provider_idx].ProviderId) == RPC_S_INVALID_STRING_UUID)
+ {
+ PEVT_VARIANT value = NULL;
+
+ *err = GetPropertyValue(
+ provider_id,
+ EvtPublisherMetadataPublisherGuid,
+ &value);
+
+ /*
+ * Copy returned GUID locally
+ */
+ if (*err == ERROR_SUCCESS)
+ {
+ if (value->Type == EvtVarTypeGuid && value->GuidVal)
+ {
+ g_provider_filters[provider_idx].ProviderId = *(value->GuidVal);
+ }
+ else
+ {
+ *err = ERROR_INVALID_DATA;
+ }
+ }
+ else
+ {
+ *err_info = ws_strdup_printf("Cannot convert provider %s to a GUID, err is 0x%x", ws_optarg, *err);
+ return WTAP_OPEN_ERROR;
+ }
+
+ g_free(value);
+ }
+
+ if (IsEqualGUID(&g_provider_filters[0].ProviderId, &ZeroGuid))
+ {
+ *err = ERROR_INVALID_PARAMETER;
+ *err_info = ws_strdup_printf("Provider %s is zero, err is 0x%x", ws_optarg, *err);
+ return WTAP_OPEN_ERROR;
+ }
+ provider_idx++;
+ break;
+ case OPT_KEYWORD:
+ if (provider_idx == 0)
+ {
+ *err = ERROR_INVALID_PARAMETER;
+ *err_info = ws_strdup_printf("-k parameter must follow -p, err is 0x%x", *err);
+ return WTAP_OPEN_ERROR;
+ }
+
+ g_provider_filters[provider_idx - 1].Keyword = _strtoui64(ws_optarg, NULL, 0);
+ if (!g_provider_filters[provider_idx - 1].Keyword)
+ {
+ *err = ERROR_INVALID_PARAMETER;
+ *err_info = ws_strdup_printf("Keyword %s cannot be converted, err is 0x%x", ws_optarg, *err);
+ return WTAP_OPEN_ERROR;
+ }
+ break;
+ case OPT_LEVEL:
+ if (provider_idx == 0)
+ {
+ *err = ERROR_INVALID_PARAMETER;
+ *err_info = ws_strdup_printf("-l parameter must follow -p, err is 0x%x", *err);
+ return WTAP_OPEN_ERROR;
+ }
+
+ convert_level = strtoul(ws_optarg, NULL, 0);
+ if (convert_level > UCHAR_MAX)
+ {
+ *err = ERROR_INVALID_PARAMETER;
+ *err_info = ws_strdup_printf("Level %s is bigger than 0xff, err is 0x%x", ws_optarg, *err);
+ return WTAP_OPEN_ERROR;
+ }
+ if (!convert_level)
+ {
+ *err = ERROR_INVALID_PARAMETER;
+ *err_info = ws_strdup_printf("Level %s cannot be converted, err is 0x%x", ws_optarg, *err);
+ return WTAP_OPEN_ERROR;
+ }
+
+ g_provider_filters[provider_idx - 1].Level = (UCHAR)convert_level;
+ break;
+ }
+ }
+ g_strfreev(params_array);
+ }
+
+ /* do/while(false) is used to jump out of loop so no complex nested if/else is needed */
+ do
+ {
+ /* Read ETW from an etl file */
+ if (etl_filename)
+ {
+ mbstowcs(w_etl_filename, etl_filename, FILENAME_MAX);
+
+ log_file.LogFileName = w_etl_filename;
+ log_file.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD;
+ log_file.EventRecordCallback = event_callback;
+ log_file.Context = NULL;
+ }
+ else
+ {
+ /*
+ * Try the best to stop the leftover session since extcap has no way to cleanup when stop capturing. See issue
+ * https://gitlab.com/wireshark/wireshark/-/issues/17131
+ */
+ ControlTrace((TRACEHANDLE)NULL, LOGGER_NAME, &super_trace_properties.prop, EVENT_TRACE_CONTROL_STOP);
+
+ g_is_live_session = true;
+
+ log_file.LoggerName = LOGGER_NAME;
+ log_file.LogFileName = NULL;
+ log_file.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD;
+ log_file.EventRecordCallback = event_callback;
+ log_file.BufferCallback = NULL;
+ log_file.Context = NULL;
+
+ *err = StartTrace(
+ &traceControllerHandle,
+ log_file.LoggerName,
+ &super_trace_properties.prop);
+ if (*err != ERROR_SUCCESS)
+ {
+ *err_info = ws_strdup_printf("StartTrace failed with 0x%x", *err);
+ returnVal = WTAP_OPEN_ERROR;
+ break;
+ }
+
+ for (int i = 0; i < ARRAYSIZE(g_provider_filters); i++)
+ {
+ if (IsEqualGUID(&g_provider_filters[i].ProviderId, &ZeroGuid))
+ {
+ break;
+ }
+ *err = EnableTraceEx(
+ &g_provider_filters[i].ProviderId,
+ NULL,
+ traceControllerHandle,
+ true,
+ g_provider_filters[i].Level,
+ g_provider_filters[i].Keyword,
+ 0,
+ 0,
+ NULL);
+ if (*err != ERROR_SUCCESS)
+ {
+ *err_info = ws_strdup_printf("EnableTraceEx failed with 0x%x", *err);
+ returnVal = WTAP_OPEN_ERROR;
+ break;
+ }
+ }
+ }
+
+ trace_handle = OpenTrace(&log_file);
+ if (trace_handle == INVALID_PROCESSTRACE_HANDLE) {
+ *err = GetLastError();
+ *err_info = ws_strdup_printf("OpenTrace failed with 0x%x", *err);
+ returnVal = WTAP_OPEN_NOT_MINE;
+ break;
+ }
+
+ g_pdh = etw_dump_open(pcapng_filename, err, err_info);
+ if (g_pdh == NULL)
+ {
+ returnVal = WTAP_OPEN_ERROR;
+ break;
+ }
+
+ *err = ProcessTrace(&trace_handle, 1, 0, 0);
+ if (*err != ERROR_SUCCESS) {
+ returnVal = WTAP_OPEN_ERROR;
+ *err_info = ws_strdup_printf("ProcessTrace failed with 0x%x", *err);
+ break;
+ }
+
+ if (g_err != ERROR_SUCCESS)
+ {
+ *err = g_err;
+ *err_info = g_strdup(g_err_info);
+ returnVal = WTAP_OPEN_ERROR;
+ break;
+ }
+
+ if (!g_num_events) {
+ *err = ERROR_NO_DATA;
+ *err_info = ws_strdup_printf("Didn't find any etw event");
+ returnVal = WTAP_OPEN_NOT_MINE;
+ break;
+ }
+ } while (false);
+
+ if (trace_handle != INVALID_PROCESSTRACE_HANDLE)
+ {
+ CloseTrace(trace_handle);
+ }
+ if (g_pdh != NULL)
+ {
+ if (*err == ERROR_SUCCESS)
+ {
+ if (!wtap_dump_close(g_pdh, NULL, err, err_info))
+ {
+ returnVal = WTAP_OPEN_ERROR;
+ }
+ }
+ else
+ {
+ int err_ignore;
+ char* err_info_ignore = NULL;
+ if (!wtap_dump_close(g_pdh, NULL, &err_ignore, &err_info_ignore))
+ {
+ returnVal = WTAP_OPEN_ERROR;
+ g_free(err_info_ignore);
+ }
+ }
+ }
+ return returnVal;
+}
+
+BOOL is_event_filtered_out(PEVENT_RECORD ev)
+{
+ if (g_is_live_session)
+ {
+ return false;
+ }
+
+ if (IsEqualGUID(&g_provider_filters[0].ProviderId, &ZeroGuid))
+ {
+ return false;
+ }
+
+ for (int i = 0; i < ARRAYSIZE(g_provider_filters); i++)
+ {
+ if (IsEqualGUID(&g_provider_filters[i].ProviderId, &ev->EventHeader.ProviderId))
+ {
+ return false;
+ }
+ if (IsEqualGUID(&g_provider_filters[i].ProviderId, &ZeroGuid))
+ {
+ break;
+ }
+ }
+
+ return true;
+}
+
+static void WINAPI event_callback(PEVENT_RECORD ev)
+{
+ ULARGE_INTEGER timestamp;
+ g_num_events++;
+
+ if (is_event_filtered_out(ev))
+ {
+ return;
+ }
+
+ /*
+ * 100ns since 1/1/1601 -> usec since 1/1/1970.
+ * The offset of 11644473600 seconds can be calculated with a couple of calls to SystemTimeToFileTime.
+ */
+ timestamp.QuadPart = (ev->EventHeader.TimeStamp.QuadPart / 10) - 11644473600000000ll;
+
+ /* Write OPN events that needs mbim sub dissector */
+ if (IsEqualGUID(&ev->EventHeader.ProviderId, &mbb_provider))
+ {
+ etw_dump_write_opn_event(ev, timestamp);
+ }
+ else if (IsEqualGUID(&ev->EventHeader.ProviderId, &ndis_capture_provider))
+ {
+ etw_dump_write_ndiscap_event(ev, timestamp);
+ }
+ /* Write any event form other providers other than above */
+ else
+ {
+ etw_dump_write_general_event(ev, timestamp);
+ }
+}
+
+wtap_dumper* etw_dump_open(const char* pcapng_filename, int* err, char** err_info)
+{
+ wtap_dump_params params = { 0 };
+ GArray* shb_hdrs = NULL;
+ wtap_block_t shb_hdr;
+ wtapng_iface_descriptions_t* idb_info;
+ GArray* idb_datas;
+ wtap_block_t idb_data;
+ wtapng_if_descr_mandatory_t* descr_mand;
+
+ wtap_dumper* pdh = NULL;
+
+ shb_hdrs = g_array_new(false, false, sizeof(wtap_block_t));
+ shb_hdr = wtap_block_create(WTAP_BLOCK_SECTION);
+ g_array_append_val(shb_hdrs, shb_hdr);
+
+ /* In the future, may create multiple WTAP_BLOCK_IF_ID_AND_INFO separately for IP packet */
+ idb_info = g_new(wtapng_iface_descriptions_t, 1);
+ idb_datas = g_array_new(false, false, sizeof(wtap_block_t));
+ idb_data = wtap_block_create(WTAP_BLOCK_IF_ID_AND_INFO);
+ descr_mand = (wtapng_if_descr_mandatory_t*)wtap_block_get_mandatory_data(idb_data);
+ descr_mand->tsprecision = WTAP_TSPREC_USEC;
+ descr_mand->wtap_encap = WTAP_ENCAP_ETW;
+ /* Timestamp for each pcapng packet is usec units, so time_units_per_second need be set to 10^6 */
+ descr_mand->time_units_per_second = G_USEC_PER_SEC;
+ g_array_append_val(idb_datas, idb_data);
+ idb_info->interface_data = idb_datas;
+
+ params.encap = WTAP_ENCAP_ETW;
+ params.snaplen = 0;
+ params.tsprec = WTAP_TSPREC_USEC;
+ params.shb_hdrs = shb_hdrs;
+ params.idb_inf = idb_info;
+
+ pdh = wtap_dump_open(pcapng_filename, wtap_pcapng_file_type_subtype(), WTAP_UNCOMPRESSED, &params, err, err_info);
+
+ if (shb_hdrs)
+ {
+ wtap_block_array_free(shb_hdrs);
+ }
+ if (params.idb_inf)
+ {
+ if (params.idb_inf->interface_data)
+ {
+ wtap_block_array_free(params.idb_inf->interface_data);
+ }
+ g_free(params.idb_inf);
+ params.idb_inf = NULL;
+ }
+
+ return pdh;
+}
+
+ULONG wtap_etl_record_buffer_init(WTAP_ETL_RECORD** out_etl_record, PEVENT_RECORD ev, BOOLEAN include_user_data, WCHAR* message, WCHAR* provider_name)
+{
+ ULONG total_packet_length = sizeof(WTAP_ETL_RECORD);
+ WTAP_ETL_RECORD* etl_record = NULL;
+ ULONG user_data_length = 0;
+ ULONG user_data_offset = 0;
+ ULONG message_offset = 0;
+ ULONG provider_name_offset = 0;
+ ULONG message_length = 0;
+ ULONG provider_name_length = 0;
+
+ if (include_user_data)
+ {
+ if (ev->UserDataLength < MAX_PACKET_SIZE)
+ {
+ user_data_length = ev->UserDataLength;
+ }
+ else
+ {
+ user_data_length = MAX_PACKET_SIZE;
+ }
+ user_data_offset = sizeof(WTAP_ETL_RECORD);
+ total_packet_length += ROUND_UP_COUNT(user_data_length, sizeof(LONG));
+ }
+ if (message && message[0] != L'\0')
+ {
+ message_offset = total_packet_length;
+ message_length = (ULONG)((wcslen(message) + 1) * sizeof(WCHAR));
+ total_packet_length += ROUND_UP_COUNT(message_length, sizeof(LONG));
+ }
+ if (provider_name && provider_name[0] != L'\0')
+ {
+ provider_name_offset = total_packet_length;
+ provider_name_length = (ULONG)((wcslen(provider_name) + 1) * sizeof(WCHAR));
+ total_packet_length += ROUND_UP_COUNT(provider_name_length, sizeof(LONG));
+ }
+
+ etl_record = g_malloc(total_packet_length);
+ SecureZeroMemory(etl_record, total_packet_length);
+ etl_record->EventHeader = ev->EventHeader;
+ etl_record->BufferContext = ev->BufferContext;
+ etl_record->UserDataLength = user_data_length;
+ etl_record->MessageLength = message_length;
+ etl_record->ProviderLength = provider_name_length;
+
+ if (user_data_offset)
+ {
+ memcpy(ADD_OFFSET_TO_POINTER(etl_record, user_data_offset), ev->UserData, user_data_length);
+ }
+ if (message_offset)
+ {
+ memcpy(ADD_OFFSET_TO_POINTER(etl_record, message_offset), message, message_length);
+ }
+ if (provider_name_offset)
+ {
+ memcpy(ADD_OFFSET_TO_POINTER(etl_record, provider_name_offset), provider_name, provider_name_length);
+ }
+
+ *out_etl_record = etl_record;
+ return total_packet_length;
+}
+
+void wtap_etl_add_interface(int pkt_encap, char* interface_name, unsigned short interface_name_length, char* interface_desc, unsigned short interface_desc_length)
+{
+ wtap_block_t idb_data;
+ wtapng_if_descr_mandatory_t* descr_mand;
+ char* err_info;
+ int err;
+
+ idb_data = wtap_block_create(WTAP_BLOCK_IF_ID_AND_INFO);
+ descr_mand = (wtapng_if_descr_mandatory_t*)wtap_block_get_mandatory_data(idb_data);
+ descr_mand->wtap_encap = pkt_encap;
+ descr_mand->tsprecision = WTAP_TSPREC_USEC;
+ /* Timestamp for each pcapng packet is usec units, so time_units_per_second need be set to 10^6 */
+ descr_mand->time_units_per_second = G_USEC_PER_SEC;
+ if (interface_name_length) {
+ wtap_block_add_string_option(idb_data, OPT_IDB_NAME, interface_name, interface_name_length);
+ }
+ if (interface_desc_length) {
+ wtap_block_add_string_option(idb_data, OPT_IDB_DESCRIPTION, interface_desc, interface_desc_length);
+ }
+ if(!wtap_dump_add_idb(g_pdh, idb_data, &err, &err_info)) {
+ g_err = err;
+ sprintf_s(g_err_info, sizeof(g_err_info), "wtap_dump failed, %s", err_info);
+ g_free(err_info);
+ }
+}
+
+void wtap_etl_rec_dump(char* etl_record, ULONG total_packet_length, ULONG original_packet_length, unsigned int interface_id, BOOLEAN is_inbound, ULARGE_INTEGER timestamp, int pkt_encap, char* comment, unsigned short comment_length)
+{
+ char* err_info;
+ int err;
+ wtap_rec rec = { 0 };
+
+ wtap_rec_init(&rec);
+ rec.rec_header.packet_header.caplen = total_packet_length;
+ rec.rec_header.packet_header.len = original_packet_length;
+ rec.rec_header.packet_header.pkt_encap = pkt_encap;
+ rec.rec_header.packet_header.interface_id = interface_id;
+ rec.presence_flags = WTAP_HAS_INTERFACE_ID;
+ rec.block = wtap_block_create(WTAP_BLOCK_PACKET);
+ wtap_block_add_uint32_option(rec.block, OPT_PKT_FLAGS, is_inbound ? PACK_FLAGS_DIRECTION_INBOUND : PACK_FLAGS_DIRECTION_OUTBOUND);
+ if (comment_length) {
+ wtap_block_add_string_option(rec.block, OPT_COMMENT, comment, comment_length);
+ }
+ /* Convert usec of the timestamp into nstime_t */
+ rec.ts.secs = (time_t)(timestamp.QuadPart / G_USEC_PER_SEC);
+ rec.ts.nsecs = (int)(((timestamp.QuadPart % G_USEC_PER_SEC) * G_NSEC_PER_SEC) / G_USEC_PER_SEC);
+
+ /* and save the packet */
+ if (!wtap_dump(g_pdh, &rec, (uint8_t*)etl_record, &err, &err_info)) {
+ g_err = err;
+ sprintf_s(g_err_info, sizeof(g_err_info), "wtap_dump failed, %s", err_info);
+ g_free(err_info);
+ }
+
+ /* Only flush when live session */
+ if (g_is_live_session && !wtap_dump_flush(g_pdh, &err)) {
+ g_err = err;
+ sprintf_s(g_err_info, sizeof(g_err_info), "wtap_dump failed, 0x%x", err);
+ }
+ wtap_rec_cleanup(&rec);
+}
+
+void etw_dump_write_opn_event(PEVENT_RECORD ev, ULARGE_INTEGER timestamp)
+{
+ WTAP_ETL_RECORD* etl_record = NULL;
+ ULONG total_packet_length = 0;
+ BOOLEAN is_inbound = false;
+ /* 0x80000000 mask the function to host message */
+ is_inbound = ((*(INT32*)(ev->UserData)) & 0x80000000) ? true : false;
+ total_packet_length = wtap_etl_record_buffer_init(&etl_record, ev, true, NULL, NULL);
+ wtap_etl_rec_dump((char*)etl_record, total_packet_length, total_packet_length, 0, is_inbound, timestamp, WTAP_ENCAP_ETW, NULL, 0);
+ g_free(etl_record);
+}
+
+void etw_dump_write_event_head_only(PEVENT_RECORD ev, ULARGE_INTEGER timestamp)
+{
+ WTAP_ETL_RECORD* etl_record = NULL;
+ ULONG total_packet_length = 0;
+ total_packet_length = wtap_etl_record_buffer_init(&etl_record, ev, false, NULL, NULL);
+ wtap_etl_rec_dump((char*)etl_record, total_packet_length, total_packet_length, 0, false, timestamp, WTAP_ENCAP_ETW, NULL, 0);
+ g_free(etl_record);
+}
+
+void etw_dump_write_general_event(PEVENT_RECORD ev, ULARGE_INTEGER timestamp)
+{
+ PTRACE_EVENT_INFO pInfo = NULL;
+ PBYTE pUserData = NULL;
+ PBYTE pEndOfUserData = NULL;
+ DWORD PointerSize = 0;
+ PROPERTY_KEY_VALUE* prop_arr = NULL;
+ DWORD dwTopLevelPropertyCount = 0;
+ DWORD dwSizeofArray = 0;
+ WCHAR wszMessageBuffer[MAX_LOG_LINE_LENGTH] = { 0 };
+ WCHAR formatMessage[MAX_LOG_LINE_LENGTH] = { 0 };
+
+ WTAP_ETL_RECORD* etl_record = NULL;
+ ULONG total_packet_length = 0;
+ BOOLEAN is_message_dumped = false;
+
+ do
+ {
+ /* Skip EventTrace events */
+ if (ev->EventHeader.Flags & EVENT_HEADER_FLAG_CLASSIC_HEADER &&
+ IsEqualGUID(&ev->EventHeader.ProviderId, &EventTraceGuid))
+ {
+ /*
+ * The first event in every ETL file contains the data from the file header.
+ * This is the same data as was returned in the EVENT_TRACE_LOGFILEW by
+ * OpenTrace. Since we've already seen this information, we'll skip this
+ * event.
+ */
+ break;
+ }
+
+ /* Skip events injected by the XPerf tracemerger - they will never be decodable */
+ if (IsEqualGUID(&ev->EventHeader.ProviderId, &ImageIdGuid) ||
+ IsEqualGUID(&ev->EventHeader.ProviderId, &SystemConfigExGuid) ||
+ IsEqualGUID(&ev->EventHeader.ProviderId, &EventMetadataGuid))
+ {
+ break;
+ }
+
+ if (!get_event_information(ev, &pInfo))
+ {
+ break;
+ }
+
+ /* Skip those events without format message since most of them need special logic to decode like NDIS-PackCapture */
+ if (pInfo->EventMessageOffset <= 0)
+ {
+ break;
+ }
+
+ if (EVENT_HEADER_FLAG_32_BIT_HEADER == (ev->EventHeader.Flags & EVENT_HEADER_FLAG_32_BIT_HEADER))
+ {
+ PointerSize = 4;
+ }
+ else
+ {
+ PointerSize = 8;
+ }
+
+ pUserData = (PBYTE)ev->UserData;
+ pEndOfUserData = (PBYTE)ev->UserData + ev->UserDataLength;
+
+ dwTopLevelPropertyCount = pInfo->TopLevelPropertyCount;
+ if (dwTopLevelPropertyCount > 0)
+ {
+ prop_arr = g_malloc(sizeof(PROPERTY_KEY_VALUE) * dwTopLevelPropertyCount);
+ dwSizeofArray = dwTopLevelPropertyCount * sizeof(PROPERTY_KEY_VALUE);
+ SecureZeroMemory(prop_arr, dwSizeofArray);
+ }
+
+ StringCbCopy(formatMessage, MAX_LOG_LINE_LENGTH, (LPWSTR)ADD_OFFSET_TO_POINTER(pInfo, pInfo->EventMessageOffset));
+
+ for (USHORT i = 0; i < dwTopLevelPropertyCount; i++)
+ {
+ pUserData = extract_properties(ev, pInfo, PointerSize, i, pUserData, pEndOfUserData, &prop_arr[i]);
+ if (NULL == pUserData)
+ {
+ break;
+ }
+ }
+
+ format_message(formatMessage, prop_arr, dwTopLevelPropertyCount, wszMessageBuffer, sizeof(wszMessageBuffer));
+
+ total_packet_length = wtap_etl_record_buffer_init(&etl_record, ev, false, wszMessageBuffer, (WCHAR*)ADD_OFFSET_TO_POINTER(pInfo, pInfo->ProviderNameOffset));
+ wtap_etl_rec_dump((char*)etl_record, total_packet_length, total_packet_length, 0, false, timestamp, WTAP_ENCAP_ETW, NULL, 0);
+ g_free(etl_record);
+
+ is_message_dumped = true;
+ } while (false);
+
+ if (NULL != prop_arr)
+ {
+ g_free(prop_arr);
+ prop_arr = NULL;
+ }
+ if (NULL != pInfo)
+ {
+ g_free(pInfo);
+ pInfo = NULL;
+ }
+
+ if (!is_message_dumped && g_include_undecidable_event)
+ {
+ etw_dump_write_event_head_only(ev, timestamp);
+ }
+}
+
+/*
+ * Editor modelines - https://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * vi: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */
diff --git a/extcap/etl.h b/extcap/etl.h
new file mode 100644
index 0000000..9480d24
--- /dev/null
+++ b/extcap/etl.h
@@ -0,0 +1,48 @@
+/** @file
+ *
+ * Copyright 2020, Odysseus Yang
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef __W_ETL_H__
+#define __W_ETL_H__
+
+#include "wiretap/wtap.h"
+#include "ws_symbol_export.h"
+#include "wiretap/wtap-int.h"
+
+#include <glib.h>
+#include <stdlib.h>
+#include <tdh.h>
+#include <guiddef.h>
+
+#define LOGGER_NAME L"wireshark etwdump"
+
+typedef struct
+{
+ EVENT_TRACE_PROPERTIES prop;
+ char padding[64];
+} SUPER_EVENT_TRACE_PROPERTIES;
+
+wtap_open_return_val etw_dump(const char* etl_filename, const char* pcapng_filename, const char* params, int* err, char** err_info);
+
+#endif
+
+
+/*
+ * Editor modelines - https://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * vi: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */
diff --git a/extcap/etw_message.c b/extcap/etw_message.c
new file mode 100644
index 0000000..c99eaea
--- /dev/null
+++ b/extcap/etw_message.c
@@ -0,0 +1,419 @@
+/* etw_message.h
+ *
+ * Copyright 2020, Odysseus Yang
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "config.h"
+#define WS_LOG_DOMAIN "etwdump"
+
+#include "etw_message.h"
+#include <wsutil/wslog.h>
+ULONGLONG g_num_events = 0;
+
+VOID format_message(WCHAR* lpszMessage, PROPERTY_KEY_VALUE* propArray, DWORD dwPropertyCount, WCHAR* lpszOutBuffer, DWORD dwOutBufferCount)
+{
+ DWORD startLoc = 0;
+ int percent_loc = 0;
+
+ for (int i = 0; lpszMessage[i] != L'\0';)
+ {
+ if (lpszMessage[i] != L'%')
+ {
+ i++;
+ continue;
+ }
+ if (lpszMessage[i + 1] == '%')
+ {
+ i += 2;
+ continue;
+ }
+
+ percent_loc = i;
+ i++;
+
+ if (iswdigit(lpszMessage[i]))
+ {
+ DWORD dwDigitalCount = 0;
+ WCHAR smallBuffer[MAX_SMALL_BUFFER] = { 0 };
+ while (iswdigit(lpszMessage[i]))
+ {
+ if (dwDigitalCount < (MAX_SMALL_BUFFER - 1))
+ {
+ smallBuffer[dwDigitalCount] = lpszMessage[i];
+ }
+ dwDigitalCount++;
+ i++;
+ }
+
+ /* We are not parsing this */
+ if (dwDigitalCount >= (MAX_SMALL_BUFFER - 1))
+ {
+ continue;
+ }
+ DWORD num = _wtoi(smallBuffer);
+ /* We are not parsing this */
+ if (num == 0 || num > dwPropertyCount || propArray[num - 1].value[0] == L'\0')
+ {
+ continue;
+ }
+
+ if (lpszMessage[i] == L'!' && lpszMessage[i + 1] == L'S' && lpszMessage[i + 2] == L'!')
+ {
+ i += 3;
+ }
+
+ /* We have everything */
+ lpszMessage[percent_loc] = L'\0';
+ StringCbCat(lpszOutBuffer, dwOutBufferCount, lpszMessage + startLoc);
+ StringCbCat(lpszOutBuffer, dwOutBufferCount, propArray[num - 1].value);
+ startLoc = i;
+ continue; // for
+ }
+ }
+ StringCbCat(lpszOutBuffer, dwOutBufferCount, lpszMessage + startLoc);
+}
+
+/*
+* Get the length of the property data. For MOF-based events, the size is inferred from the data type
+* of the property. For manifest-based events, the property can specify the size of the property value
+* using the length attribute. The length attribue can specify the size directly or specify the name
+* of another property in the event data that contains the size. If the property does not include the
+* length attribute, the size is inferred from the data type. The length will be zero for variable
+* length, null-terminated strings and structures.
+*/
+DWORD GetPropertyLength(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo, USHORT i, PUSHORT PropertyLength)
+{
+ DWORD status = ERROR_SUCCESS;
+ PROPERTY_DATA_DESCRIPTOR DataDescriptor = { 0 };
+ DWORD PropertySize = 0;
+
+ /*
+ * If the property is a binary blob and is defined in a manifest, the property can
+ * specify the blob's size or it can point to another property that defines the
+ * blob's size. The PropertyParamLength flag tells you where the blob's size is defined.
+ */
+ if ((pInfo->EventPropertyInfoArray[i].Flags & PropertyParamLength) == PropertyParamLength)
+ {
+ DWORD Length = 0; // Expects the length to be defined by a UINT16 or UINT32
+ DWORD j = pInfo->EventPropertyInfoArray[i].lengthPropertyIndex;
+ DataDescriptor.PropertyName = ((ULONGLONG)(pInfo)+(ULONGLONG)pInfo->EventPropertyInfoArray[j].NameOffset);
+ DataDescriptor.ArrayIndex = ULONG_MAX;
+ status = TdhGetPropertySize(pEvent, 0, NULL, 1, &DataDescriptor, &PropertySize);
+ status = TdhGetProperty(pEvent, 0, NULL, 1, &DataDescriptor, PropertySize, (PBYTE)&Length);
+ *PropertyLength = (USHORT)Length;
+ }
+ else
+ {
+ if (pInfo->EventPropertyInfoArray[i].length > 0)
+ {
+ *PropertyLength = pInfo->EventPropertyInfoArray[i].length;
+ }
+ else
+ {
+ /*
+ * If the property is a binary blob and is defined in a MOF class, the extension
+ * qualifier is used to determine the size of the blob. However, if the extension
+ * is IPAddrV6, you must set the PropertyLength variable yourself because the
+ * EVENT_PROPERTY_INFO.length field will be zero.
+ */
+ if (TDH_INTYPE_BINARY == pInfo->EventPropertyInfoArray[i].nonStructType.InType &&
+ TDH_OUTTYPE_IPV6 == pInfo->EventPropertyInfoArray[i].nonStructType.OutType)
+ {
+ *PropertyLength = (USHORT)sizeof(IN6_ADDR);
+ }
+ else if (TDH_INTYPE_UNICODESTRING == pInfo->EventPropertyInfoArray[i].nonStructType.InType ||
+ TDH_INTYPE_ANSISTRING == pInfo->EventPropertyInfoArray[i].nonStructType.InType ||
+ (pInfo->EventPropertyInfoArray[i].Flags & PropertyStruct) == PropertyStruct)
+ {
+ *PropertyLength = pInfo->EventPropertyInfoArray[i].length;
+ }
+ else
+ {
+ ws_debug("Event %d Unexpected length of 0 for intype %d and outtype %d", g_num_events,
+ pInfo->EventPropertyInfoArray[i].nonStructType.InType,
+ pInfo->EventPropertyInfoArray[i].nonStructType.OutType);
+
+ status = ERROR_EVT_INVALID_EVENT_DATA;
+ goto cleanup;
+ }
+ }
+ }
+cleanup:
+ return status;
+}
+
+DWORD GetArraySize(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo, USHORT i, PUSHORT ArraySize)
+{
+ DWORD status = ERROR_SUCCESS;
+ PROPERTY_DATA_DESCRIPTOR DataDescriptor = { 0 };
+ DWORD PropertySize = 0;
+
+ if ((pInfo->EventPropertyInfoArray[i].Flags & PropertyParamCount) == PropertyParamCount)
+ {
+ /* Expects the count to be defined by a UINT16 or UINT32 */
+ DWORD Count = 0;
+ DWORD j = pInfo->EventPropertyInfoArray[i].countPropertyIndex;
+ DataDescriptor.PropertyName = ((ULONGLONG)(pInfo)+(ULONGLONG)(pInfo->EventPropertyInfoArray[j].NameOffset));
+ DataDescriptor.ArrayIndex = ULONG_MAX;
+ status = TdhGetPropertySize(pEvent, 0, NULL, 1, &DataDescriptor, &PropertySize);
+ status = TdhGetProperty(pEvent, 0, NULL, 1, &DataDescriptor, PropertySize, (PBYTE)&Count);
+ *ArraySize = (USHORT)Count;
+ }
+ else
+ {
+ *ArraySize = pInfo->EventPropertyInfoArray[i].count;
+ }
+ return status;
+}
+
+DWORD GetMapInfo(PEVENT_RECORD pEvent, LPWSTR pMapName, PEVENT_MAP_INFO* pMapInfo)
+{
+ DWORD status = ERROR_SUCCESS;
+ DWORD MapSize = 0;
+
+ /* Retrieve the required buffer size for the map info. */
+ status = TdhGetEventMapInformation(pEvent, pMapName, *pMapInfo, &MapSize);
+ if (ERROR_INSUFFICIENT_BUFFER == status)
+ {
+ *pMapInfo = (PEVENT_MAP_INFO)g_malloc(MapSize);
+ if (*pMapInfo == NULL)
+ {
+ status = ERROR_OUTOFMEMORY;
+ goto cleanup;
+ }
+ /* Retrieve the map info. */
+ status = TdhGetEventMapInformation(pEvent, pMapName, *pMapInfo, &MapSize);
+ }
+
+ if (ERROR_NOT_FOUND == status)
+ {
+ /* This case is okay. */
+ status = ERROR_SUCCESS;
+ }
+
+cleanup:
+
+ return status;
+}
+
+
+PBYTE extract_properties(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo, DWORD PointerSize, USHORT i, PBYTE pUserData, PBYTE pEndOfUserData, PROPERTY_KEY_VALUE* pExtract)
+{
+ TDHSTATUS status = ERROR_SUCCESS;
+ USHORT PropertyLength = 0;
+ USHORT UserDataConsumed = 0;
+ /* Last member of a structure */
+ DWORD LastMember = 0;
+ USHORT ArraySize = 0;
+ PEVENT_MAP_INFO pMapInfo = NULL;
+ WCHAR formatted_data[MAX_LOG_LINE_LENGTH];
+ DWORD formatted_data_size = sizeof(formatted_data);
+ LPWSTR oversize_formatted_data = NULL;
+
+ do
+ {
+ StringCbCopy(pExtract->key, sizeof(pExtract->key), (PWCHAR)((PBYTE)(pInfo)+pInfo->EventPropertyInfoArray[i].NameOffset));
+ /* Get the length of the property. */
+ status = GetPropertyLength(pEvent, pInfo, i, &PropertyLength);
+ if (ERROR_SUCCESS != status)
+ {
+ StringCbPrintf(pExtract->value, sizeof(pExtract->value), L"%s: GetPropertyLength failed 0x%x", pExtract->key, status);
+ break;
+ }
+
+ /* Get the size of the array if the property is an array. */
+ status = GetArraySize(pEvent, pInfo, i, &ArraySize);
+ if (ERROR_SUCCESS != status)
+ {
+ StringCbPrintf(pExtract->value, sizeof(pExtract->value), L"%s: GetArraySize failed 0x%x", pExtract->key, status);
+ break;
+ }
+
+ /* Add [] for an array property */
+ if (ArraySize > 1)
+ {
+ StringCbCat(pExtract->value, sizeof(pExtract->value), L"[");
+ }
+
+ for (USHORT k = 0; k < ArraySize; k++)
+ {
+ /* Add array item separator "," */
+ if (k > 0)
+ {
+ StringCbCat(pExtract->value, sizeof(pExtract->value), L",");
+ }
+ /* If the property is a structure, print the members of the structure. */
+ if ((pInfo->EventPropertyInfoArray[i].Flags & PropertyStruct) == PropertyStruct)
+ {
+ /* Add {} for an array property */
+ StringCbCat(pExtract->value, sizeof(pExtract->value), L"{");
+ /* Add struct member separator ";" */
+ if (k > 0)
+ {
+ StringCbCat(pExtract->value, sizeof(pExtract->value), L";");
+ }
+ LastMember = pInfo->EventPropertyInfoArray[i].structType.StructStartIndex +
+ pInfo->EventPropertyInfoArray[i].structType.NumOfStructMembers;
+
+ for (USHORT j = pInfo->EventPropertyInfoArray[i].structType.StructStartIndex; j < LastMember; j++)
+ {
+ pUserData = extract_properties(pEvent, pInfo, PointerSize, j, pUserData, pEndOfUserData, pExtract);
+ if (NULL == pUserData)
+ {
+ StringCbPrintf(pExtract->value, sizeof(pExtract->value), L"%s: extract_properties of member %d failed 0x%x", pExtract->key, j, status);
+ break;
+ }
+ }
+ StringCbCat(pExtract->value, sizeof(pExtract->value), L"}");
+ }
+ else
+ {
+ /* Get the name/value mapping only at the first time if the property specifies a value map. */
+ if (pMapInfo == NULL)
+ {
+ status = GetMapInfo(pEvent,
+ (PWCHAR)((PBYTE)(pInfo)+pInfo->EventPropertyInfoArray[i].nonStructType.MapNameOffset),
+ &pMapInfo);
+
+ if (ERROR_SUCCESS != status)
+ {
+ StringCbPrintf(pExtract->value, sizeof(pExtract->value), L"%s: GetMapInfo failed 0x%x", pExtract->key, status);
+ break;
+ }
+ }
+
+ /* Get the size of the buffer required for the formatted data. */
+
+ status = TdhFormatProperty(
+ pInfo,
+ pMapInfo,
+ PointerSize,
+ pInfo->EventPropertyInfoArray[i].nonStructType.InType,
+ pInfo->EventPropertyInfoArray[i].nonStructType.OutType,
+ PropertyLength,
+ (USHORT)(pEndOfUserData - pUserData),
+ pUserData,
+ &formatted_data_size,
+ formatted_data,
+ &UserDataConsumed);
+
+ if (ERROR_INSUFFICIENT_BUFFER == status)
+ {
+ if (oversize_formatted_data)
+ {
+ g_free(oversize_formatted_data);
+ oversize_formatted_data = NULL;
+ }
+
+ oversize_formatted_data = (LPWSTR)g_malloc(formatted_data_size);
+ if (oversize_formatted_data == NULL)
+ {
+ status = ERROR_OUTOFMEMORY;
+ StringCbPrintf(pExtract->value, sizeof(pExtract->value), L"%s: Allocate FormattedData memory (size %d) for array item %d failed 0x%x", pExtract->key, formatted_data_size, k, status);
+ break;
+ }
+
+ /* Retrieve the formatted data. */
+ status = TdhFormatProperty(
+ pInfo,
+ pMapInfo,
+ PointerSize,
+ pInfo->EventPropertyInfoArray[i].nonStructType.InType,
+ pInfo->EventPropertyInfoArray[i].nonStructType.OutType,
+ PropertyLength,
+ (USHORT)(pEndOfUserData - pUserData),
+ pUserData,
+ &formatted_data_size,
+ oversize_formatted_data,
+ &UserDataConsumed);
+ }
+
+ if (ERROR_SUCCESS == status)
+ {
+ if (formatted_data_size > sizeof(formatted_data) && oversize_formatted_data != NULL)
+ {
+ /* Any oversize FormattedData will be truncated */
+ StringCbCat(pExtract->value, sizeof(pExtract->value), oversize_formatted_data);
+ }
+ else
+ {
+ StringCbCat(pExtract->value, sizeof(pExtract->value), formatted_data);
+ }
+ pUserData += UserDataConsumed;
+ }
+ else
+ {
+ StringCbPrintf(pExtract->value, sizeof(pExtract->value), L"%s: TdhFormatProperty for array item %d failed 0x%x", pExtract->key, k, status);
+ break;
+ }
+ }
+ }
+ /* Add [] for an array property */
+ if (ArraySize > 1)
+ {
+ StringCbCat(pExtract->value, sizeof(pExtract->value), L"]");
+ }
+ } while (false);
+
+ if (oversize_formatted_data)
+ {
+ g_free(oversize_formatted_data);
+ oversize_formatted_data = NULL;
+ }
+ if (pMapInfo)
+ {
+ g_free(pMapInfo);
+ pMapInfo = NULL;
+ }
+
+ return (ERROR_SUCCESS == status) ? pUserData : NULL;
+}
+
+
+BOOL get_event_information(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO* pInfo)
+{
+ BOOL bReturn = false;
+ DWORD status;
+ DWORD BufferSize = 0;
+
+ /* Retrieve the required buffer size for the event metadata. */
+ status = TdhGetEventInformation(pEvent, 0, NULL, *pInfo, &BufferSize);
+ if (ERROR_INSUFFICIENT_BUFFER == status)
+ {
+ *pInfo = (TRACE_EVENT_INFO*)g_malloc(BufferSize);
+ if (*pInfo == NULL)
+ {
+ ws_debug("Event %d GetEventInformation Failed to allocate memory for event info (size=%lu).", g_num_events, BufferSize);
+ goto Exit;
+ }
+ /* Retrieve the event metadata. */
+ status = TdhGetEventInformation(pEvent, 0, NULL, *pInfo, &BufferSize);
+ }
+
+ if (ERROR_SUCCESS != status)
+ {
+ goto Exit;
+ }
+ bReturn = true;
+Exit:
+
+ return bReturn;
+}
+
+/*
+ * Editor modelines - https://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * vi: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */
diff --git a/extcap/etw_message.h b/extcap/etw_message.h
new file mode 100644
index 0000000..017849a
--- /dev/null
+++ b/extcap/etw_message.h
@@ -0,0 +1,59 @@
+/** @file
+ *
+ * Copyright 2020, Odysseus Yang
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef __W_ETW_MESSAGE_H__
+#define __W_ETW_MESSAGE_H__
+
+#include <glib.h>
+
+#include <windows.h>
+#include <SDKDDKVer.h>
+#include <strsafe.h>
+#include <evntcons.h>
+#include <tdh.h>
+#include <stdlib.h>
+
+#define MAX_SMALL_BUFFER 4
+#define MAX_LOG_LINE_LENGTH 1024
+#define MAX_KEY_LENGTH 64
+
+typedef struct Property_Key_Value
+{
+ WCHAR key[MAX_KEY_LENGTH];
+ WCHAR value[MAX_LOG_LINE_LENGTH];
+} PROPERTY_KEY_VALUE;
+
+typedef struct in6_addr {
+ union {
+ UCHAR Byte[16];
+ USHORT Word[8];
+ } u;
+} IN6_ADDR, * PIN6_ADDR, FAR* LPIN6_ADDR;
+
+VOID format_message(WCHAR* lpszMessage, PROPERTY_KEY_VALUE* propArray, DWORD dwPropertyCount, WCHAR* lpszOutBuffer, DWORD dwOutBufferCount);
+BOOL get_event_information(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO* pInfo);
+PBYTE extract_properties(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo, DWORD PointerSize, USHORT i, PBYTE pUserData, PBYTE pEndOfUserData, PROPERTY_KEY_VALUE* pExtract);
+
+#endif
+
+
+/*
+ * Editor modelines - https://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * vi: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */
diff --git a/extcap/etw_ndiscap.c b/extcap/etw_ndiscap.c
new file mode 100644
index 0000000..d7aab65
--- /dev/null
+++ b/extcap/etw_ndiscap.c
@@ -0,0 +1,709 @@
+/* etw_ndiscap.c
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+* Reads IP packets from an Windows event trace logfile or an Windows event trace live session
+* and write out a pcap file with LINKTYPE_ETHERNET, LINKTYPE_RAW or LINKTYPE_IEEE802_11.
+* The major code of this file is from https://github.com/microsoft/etl2pcapng with some changes by Odysseus Yang.
+* The changes mainly include but not limited
+* 1. calling pcapng APIs instead of writing the data in the pcapng binary format by its own implementation in etl2pcapng.
+* 2. Optimize the process of adding pcapng interfaces so it doesn't need process the same Windows event trace logfile twice,
+ that not only impacts the performance, but also breaks Wireshark live capture function.
+*/
+
+#define WIN32_LEAN_AND_MEAN 1
+#include <windows.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <evntrace.h>
+#include <evntcons.h>
+#include <tdh.h>
+#include <strsafe.h>
+#include <winsock2.h>
+#include <netiodef.h>
+
+// inet_ipv6.h and netiodef.h define exactly the same stuff, like _IPV6_ROUTING_HEADER and IP6F_OFF_MASK.
+// So wiretap/wtap.h cannot be directly included in this file. Defines below three WTAP_ENCAP types with the value in wtap.h for compile
+#define WTAP_ENCAP_ETHERNET 1
+#define WTAP_ENCAP_RAW_IP 7
+#define WTAP_ENCAP_IEEE_802_11 20
+
+#define MAX_PACKET_SIZE 0xFFFF
+
+// From the ndiscap manifest
+#define KW_MEDIA_WIRELESS_WAN 0x200
+#define KW_MEDIA_NATIVE_802_11 0x10000
+#define KW_PACKET_START 0x40000000
+#define KW_PACKET_END 0x80000000
+#define KW_SEND 0x100000000
+#define KW_RECEIVE 0x200000000
+
+#define tidPacketFragment 1001
+#define tidPacketMetadata 1002
+#define tidVMSwitchPacketFragment 1003
+
+// From: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/windot11/ns-windot11-dot11_extsta_recv_context
+#pragma pack(push,8)
+typedef struct _NDIS_OBJECT_HEADER {
+ unsigned char Type;
+ unsigned char Revision;
+ unsigned short Size;
+} NDIS_OBJECT_HEADER, * PNDIS_OBJECT_HEADER;
+
+typedef struct DOT11_EXTSTA_RECV_CONTEXT {
+ NDIS_OBJECT_HEADER Header;
+ unsigned long uReceiveFlags;
+ unsigned long uPhyId;
+ unsigned long uChCenterFrequency;
+ unsigned short usNumberOfMPDUsReceived;
+ long lRSSI;
+ unsigned char ucDataRate;
+ unsigned long uSizeMediaSpecificInfo;
+ void *pvMediaSpecificInfo;
+ unsigned long long ullTimestamp;
+} DOT11_EXTSTA_RECV_CONTEXT, * PDOT11_EXTSTA_RECV_CONTEXT;
+#pragma pack(pop)
+
+// From: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/windot11/ne-windot11-_dot11_phy_type
+#define DOT11_PHY_TYPE_NAMES_MAX 10
+static const char* DOT11_PHY_TYPE_NAMES[] = {
+ "Unknown", // dot11_phy_type_unknown = 0
+ "Fhss", // dot11_phy_type_fhss = 1
+ "Dsss", // dot11_phy_type_dsss = 2
+ "IrBaseband", // dot11_phy_type_irbaseband = 3
+ "802.11a", // dot11_phy_type_ofdm = 4
+ "802.11b", // dot11_phy_type_hrdsss = 5
+ "802.11g", // dot11_phy_type_erp = 6
+ "802.11n", // dot11_phy_type_ht = 7
+ "802.11ac", // dot11_phy_type_vht = 8
+ "802.11ad", // dot11_phy_type_dmg = 9
+ "802.11ax" // dot11_phy_type_he = 10
+};
+
+unsigned long long NumFramesConverted = 0;
+char AuxFragBuf[MAX_PACKET_SIZE] = {0};
+unsigned long AuxFragBufOffset = 0;
+
+DOT11_EXTSTA_RECV_CONTEXT PacketMetadata;
+BOOLEAN AddWlanMetadata = false;
+
+typedef struct _NDIS_NET_BUFFER_LIST_8021Q_INFO {
+ union {
+ struct {
+ UINT32 UserPriority : 3; // 802.1p priority
+ UINT32 CanonicalFormatId : 1; // always 0
+ UINT32 VlanId : 12; // VLAN Identification
+ UINT32 Reserved : 16; // set to 0 for ethernet
+ } TagHeader;
+
+ struct {
+ UINT32 UserPriority : 3; // 802.1p priority
+ UINT32 CanonicalFormatId : 1; // always 0
+ UINT32 VlanId : 12; // VLAN Identification
+ UINT32 WMMInfo : 4;
+ UINT32 Reserved : 12; // set to 0 for Wireless LAN
+ } WLanTagHeader;
+
+ PVOID Value;
+ };
+} NDIS_NET_BUFFER_LIST_8021Q_INFO, *PNDIS_NET_BUFFER_LIST_8021Q_INFO;
+
+// The max OOB data size might increase in the future. If it becomes larger than MaxNetBufferListInfo,
+// this tool will print a warning and the value of MaxNetBufferListInfo in the code should be increased.
+// From: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/nblinfo/ne-nblinfo-ndis_net_buffer_list_info
+#define MaxNetBufferListInfo 200
+#define Ieee8021QNetBufferListInfo 4
+PBYTE OobData[MaxNetBufferListInfo];
+
+typedef struct _VMSWITCH_SOURCE_INFO {
+ unsigned long SourcePortId;
+ char* SourcePortName;
+ char* SourceNicName;
+ char* SourceNicType;
+} VMSWITCH_SOURCE_INFO, *PVMSWITCH_SOURCE_INFO;
+
+typedef struct _VMSWITCH_PACKET_FRAGMENT {
+ unsigned long SourcePortId;
+ unsigned long DestinationCount;
+ short VlanId;
+} VMSWITCH_PACKET_FRAGMENT, *PVMSWITCH_PACKET_FRAGMENT;
+
+BOOLEAN CurrentPacketIsVMSwitchPacketFragment = false;
+VMSWITCH_PACKET_FRAGMENT VMSwitchPacketFragment;
+
+struct INTERFACE {
+ struct INTERFACE* Next;
+ unsigned long LowerIfIndex;
+ unsigned long MiniportIfIndex;
+ unsigned long PcapNgIfIndex;
+ int PktEncapType;
+ short VlanId;
+
+ BOOLEAN IsVMNic;
+ VMSWITCH_SOURCE_INFO VMNic;
+};
+
+#define IFACE_HT_SIZE 100
+struct INTERFACE* InterfaceHashTable[IFACE_HT_SIZE] = {0};
+unsigned long NumInterfaces = 0;
+
+void wtap_etl_rec_dump(char* etl_record, ULONG total_packet_length, ULONG original_packet_length, unsigned int interface_id, BOOLEAN is_inbound, ULARGE_INTEGER timestamp, int pkt_encap, char* comment, unsigned short comment_length);
+void wtap_etl_add_interface(int pkt_encap, char* interface_name, unsigned short interface_name_length, char* interface_desc, unsigned short interface_desc_length);
+
+extern char g_err_info[FILENAME_MAX];
+extern int g_err;
+
+unsigned long HashInterface(unsigned long LowerIfIndex)
+{
+ if (CurrentPacketIsVMSwitchPacketFragment) {
+ return VMSwitchPacketFragment.SourcePortId * (VMSwitchPacketFragment.VlanId + 1);
+ } else {
+ return LowerIfIndex;
+ }
+}
+
+struct INTERFACE* GetInterface(unsigned long LowerIfIndex)
+{
+ struct INTERFACE* Iface = InterfaceHashTable[HashInterface(LowerIfIndex) % IFACE_HT_SIZE];
+ while (Iface != NULL) {
+ if (CurrentPacketIsVMSwitchPacketFragment) {
+ if (Iface->IsVMNic &&
+ Iface->LowerIfIndex == LowerIfIndex &&
+ Iface->VlanId == VMSwitchPacketFragment.VlanId &&
+ Iface->VMNic.SourcePortId == VMSwitchPacketFragment.SourcePortId) {
+ return Iface;
+ }
+ } else {
+ if (!Iface->IsVMNic && Iface->LowerIfIndex == LowerIfIndex && Iface->VlanId == 0) {
+ return Iface;
+ }
+ }
+ Iface = Iface->Next;
+ }
+ return NULL;
+}
+
+struct INTERFACE* AddInterface(PEVENT_RECORD ev, unsigned long LowerIfIndex, unsigned long MiniportIfIndex, int Type)
+{
+ struct INTERFACE** Iface = &InterfaceHashTable[HashInterface(LowerIfIndex) % IFACE_HT_SIZE];
+ struct INTERFACE* NewIface = malloc(sizeof(struct INTERFACE));
+
+#define IF_STRING_MAX_SIZE 128
+ char IfName[IF_STRING_MAX_SIZE];
+ size_t IfNameLength = 0;
+ char IfDesc[IF_STRING_MAX_SIZE];
+ size_t IfDescLength = 0;
+ //etw pcagng interface will be 0 always, network pcagng interface will start with 1
+ static PcapNgIfIndex = 1;
+
+ if (NewIface == NULL) {
+ g_err = ERROR_OUTOFMEMORY;
+ sprintf_s(g_err_info, sizeof(g_err_info), "malloc failed to allocate memory for NewIface");
+ exit(1);
+ }
+
+ NewIface->LowerIfIndex = LowerIfIndex;
+ NewIface->MiniportIfIndex = MiniportIfIndex;
+ NewIface->PktEncapType = Type;
+ NewIface->VlanId = 0;
+ NewIface->IsVMNic = false;
+
+ if (CurrentPacketIsVMSwitchPacketFragment) {
+
+ NewIface->IsVMNic = true;
+
+ wchar_t Buffer[8192];
+ PROPERTY_DATA_DESCRIPTOR Desc;
+ int Err;
+
+ // SourceNicName
+ Desc.PropertyName = (unsigned long long)(L"SourceNicName");
+ Desc.ArrayIndex = ULONG_MAX;
+ ULONG ParamNameSize = 0;
+ (void)TdhGetPropertySize(ev, 0, NULL, 1, &Desc, &ParamNameSize);
+ NewIface->VMNic.SourceNicName = malloc((ParamNameSize / sizeof(wchar_t)) + 1);
+ if (NewIface->VMNic.SourceNicName == NULL) {
+ g_err = ERROR_OUTOFMEMORY;
+ sprintf_s(g_err_info, sizeof(g_err_info), "malloc failed to allocate memory for NewIface->VMNic.SourceNicName");
+ exit(1);
+ }
+ Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(Buffer), (PBYTE)Buffer);
+ if (Err != NO_ERROR) {
+ Buffer[0] = L'\0';
+ }
+ Buffer[ParamNameSize / sizeof(wchar_t) + 1] = L'\0';
+ WideCharToMultiByte(CP_ACP,
+ 0,
+ Buffer,
+ -1,
+ NewIface->VMNic.SourceNicName,
+ ParamNameSize / sizeof(wchar_t) + 1,
+ NULL,
+ NULL);
+ NewIface->VMNic.SourceNicName[wcslen(Buffer)] = '\0';
+
+ // SourcePortName
+ Desc.PropertyName = (unsigned long long)(L"SourcePortName");
+ Desc.ArrayIndex = ULONG_MAX;
+ (void)TdhGetPropertySize(ev, 0, NULL, 1, &Desc, &ParamNameSize);
+ NewIface->VMNic.SourcePortName = malloc((ParamNameSize / sizeof(wchar_t)) + 1);
+ if (NewIface->VMNic.SourcePortName == NULL) {
+ g_err = ERROR_OUTOFMEMORY;
+ sprintf_s(g_err_info, sizeof(g_err_info), "malloc failed to allocate memory for NewIface->VMNic.SourcePortName");
+ exit(1);
+ }
+ Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(Buffer), (PBYTE)Buffer);
+ if (Err != NO_ERROR) {
+ Buffer[0] = L'\0';
+ }
+ Buffer[ParamNameSize / sizeof(wchar_t) + 1] = L'\0';
+ WideCharToMultiByte(CP_ACP,
+ 0,
+ Buffer,
+ -1,
+ NewIface->VMNic.SourcePortName,
+ ParamNameSize / sizeof(wchar_t) + 1,
+ NULL,
+ NULL);
+ NewIface->VMNic.SourcePortName[wcslen(Buffer)] = '\0';
+
+ // SourceNicType
+ Desc.PropertyName = (unsigned long long)(L"SourceNicType");
+ Desc.ArrayIndex = ULONG_MAX;
+ (void)TdhGetPropertySize(ev, 0, NULL, 1, &Desc, &ParamNameSize);
+ NewIface->VMNic.SourceNicType = malloc((ParamNameSize / sizeof(wchar_t)) + 1);
+ if (NewIface->VMNic.SourceNicType == NULL) {
+ g_err = ERROR_OUTOFMEMORY;
+ sprintf_s(g_err_info, sizeof(g_err_info), "malloc failed to allocate memory for NewIface->VMNic.SourceNicType");
+ exit(1);
+ }
+ Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(Buffer), (PBYTE)Buffer);
+ if (Err != NO_ERROR) {
+ Buffer[0] = L'\0';
+ }
+ Buffer[ParamNameSize / sizeof(wchar_t) + 1] = L'\0';
+ WideCharToMultiByte(CP_ACP,
+ 0,
+ Buffer,
+ -1,
+ NewIface->VMNic.SourceNicType,
+ ParamNameSize / sizeof(wchar_t) + 1,
+ NULL,
+ NULL);
+ NewIface->VMNic.SourceNicType[wcslen(Buffer)] = '\0';
+
+
+ NewIface->VMNic.SourcePortId = VMSwitchPacketFragment.SourcePortId;
+ NewIface->VlanId = VMSwitchPacketFragment.VlanId;
+ }
+
+ NewIface->Next = *Iface;
+
+ *Iface = NewIface;
+ NumInterfaces++;
+
+ NewIface->PcapNgIfIndex = PcapNgIfIndex;
+ PcapNgIfIndex++;
+ memset(IfName, 0, sizeof(IfName));
+ memset(IfDesc, 0, sizeof(IfDesc));
+ switch (NewIface->PktEncapType) {
+ case WTAP_ENCAP_ETHERNET:
+ if (NewIface->IsVMNic) {
+ printf("IF: medium=%s\tID=%u\tIfIndex=%u\tVlanID=%i",
+ NewIface->VMNic.SourceNicType,
+ NewIface->PcapNgIfIndex,
+ NewIface->VMNic.SourcePortId,
+ NewIface->VlanId
+ );
+ StringCchPrintfA(
+ IfName,
+ IF_STRING_MAX_SIZE,
+ "%s:%s:%lu:%i",
+ NewIface->VMNic.SourcePortName,
+ NewIface->VMNic.SourceNicType,
+ NewIface->VMNic.SourcePortId,
+ NewIface->VlanId
+ );
+ }
+ else {
+ printf("IF: medium=eth\tID=%u\tIfIndex=%u\tVlanID=%i", NewIface->PcapNgIfIndex, NewIface->LowerIfIndex, NewIface->VlanId);
+ StringCchPrintfA(IfName, IF_STRING_MAX_SIZE, "eth:%lu:%i", NewIface->LowerIfIndex, NewIface->VlanId);
+ }
+ break;
+ case WTAP_ENCAP_IEEE_802_11:
+ printf("IF: medium=wifi ID=%u\tIfIndex=%u", NewIface->PcapNgIfIndex, NewIface->LowerIfIndex);
+ StringCchPrintfA(IfName, IF_STRING_MAX_SIZE, "wifi:%lu", NewIface->LowerIfIndex);
+ break;
+ case WTAP_ENCAP_RAW_IP:
+ printf("IF: medium=mbb ID=%u\tIfIndex=%u", NewIface->PcapNgIfIndex, NewIface->LowerIfIndex);
+ StringCchPrintfA(IfName, IF_STRING_MAX_SIZE, "mbb:%lu", NewIface->LowerIfIndex);
+ break;
+ }
+ StringCchLengthA(IfName, IF_STRING_MAX_SIZE, &IfNameLength);
+
+ if (NewIface->LowerIfIndex != NewIface->MiniportIfIndex) {
+ printf("\t(LWF over IfIndex %u)", NewIface->MiniportIfIndex);
+ StringCchPrintfA(IfDesc, IF_STRING_MAX_SIZE, "LWF over IfIndex %lu", NewIface->MiniportIfIndex);
+ StringCchLengthA(IfDesc, IF_STRING_MAX_SIZE, &IfDescLength);
+ }
+
+ if (NewIface->VlanId != 0) {
+ StringCchPrintfA(IfDesc + IfDescLength, IF_STRING_MAX_SIZE, " VlanID=%i ", NewIface->VlanId);
+ StringCchLengthA(IfDesc, IF_STRING_MAX_SIZE, &IfDescLength);
+ }
+
+ printf("\n");
+
+ wtap_etl_add_interface(NewIface->PktEncapType, IfName, (unsigned short)IfNameLength, IfDesc, (unsigned short)IfDescLength);
+ return NewIface;
+}
+
+void ParseVmSwitchPacketFragment(PEVENT_RECORD ev)
+{
+ // Parse the current VMSwitch packet event for use elsewhere.
+ // NB: Here we only do per-packet parsing. For any event fields that only need to be
+ // parsed once and written into an INTERFACE, we do the parsing in AddInterface.
+
+ PROPERTY_DATA_DESCRIPTOR Desc;
+ int Err;
+ PNDIS_NET_BUFFER_LIST_8021Q_INFO pNblVlanInfo;
+
+ // Get VLAN from OOB
+ unsigned long OobLength;
+ Desc.PropertyName = (unsigned long long)L"OOBDataSize";
+ Desc.ArrayIndex = ULONG_MAX;
+ Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(OobLength), (PBYTE)&OobLength);
+ if (Err != NO_ERROR) {
+ g_err = Err;
+ sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty OobLength failed, err is 0x%x", Err);
+ return;
+ }
+
+ if (OobLength > sizeof(OobData)) {
+ g_err = ERROR_INVALID_DATA;
+ sprintf_s(g_err_info, sizeof(g_err_info), "OOB data of %lu bytes too large to fit in hardcoded buffer of size %lu", OobLength, (unsigned long)sizeof(OobData));
+ return;
+ }
+
+ Desc.PropertyName = (unsigned long long)L"OOBData";
+ Desc.ArrayIndex = ULONG_MAX;
+ Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, OobLength, (PBYTE)&OobData);
+ if (Err != NO_ERROR) {
+ g_err = Err;
+ sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty OobData failed, err is 0x%x", Err);
+ return;
+ }
+
+ pNblVlanInfo = (PNDIS_NET_BUFFER_LIST_8021Q_INFO)&OobData[Ieee8021QNetBufferListInfo];
+ VMSwitchPacketFragment.VlanId = pNblVlanInfo->TagHeader.VlanId;
+
+ // SourcePortId
+ Desc.PropertyName = (unsigned long long)L"SourcePortId";
+ Desc.ArrayIndex = ULONG_MAX;
+ Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(VMSwitchPacketFragment.SourcePortId), (PBYTE)&VMSwitchPacketFragment.SourcePortId);
+ if (Err != NO_ERROR) {
+ g_err = Err;
+ sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty SourcePortId failed, err is 0x%x", Err);
+ return;
+ }
+
+ // DestinationCount
+ Desc.PropertyName = (unsigned long long)L"DestinationCount";
+ Desc.ArrayIndex = ULONG_MAX;
+ Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(VMSwitchPacketFragment.DestinationCount), (PBYTE)&VMSwitchPacketFragment.DestinationCount);
+ if (Err != NO_ERROR) {
+ g_err = Err;
+ sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty DestinationCount failed, err is 0x%x", Err);
+ return;
+ }
+}
+
+void etw_dump_write_ndiscap_event(PEVENT_RECORD ev, ULARGE_INTEGER timestamp)
+{
+ int Err;
+ unsigned long LowerIfIndex;
+
+ struct INTERFACE* Iface;
+ unsigned long FragLength;
+ PROPERTY_DATA_DESCRIPTOR Desc;
+ int Type;
+ unsigned long TotalFragmentLength;
+ unsigned long InferredOriginalFragmentLength = 0;
+ PETHERNET_HEADER EthHdr;
+ PIPV4_HEADER Ipv4Hdr;
+ PIPV6_HEADER Ipv6Hdr;
+
+ if ((ev->EventHeader.EventDescriptor.Id != tidPacketFragment &&
+ ev->EventHeader.EventDescriptor.Id != tidPacketMetadata &&
+ ev->EventHeader.EventDescriptor.Id != tidVMSwitchPacketFragment)) {
+ return;
+ }
+
+ CurrentPacketIsVMSwitchPacketFragment = (ev->EventHeader.EventDescriptor.Id == tidVMSwitchPacketFragment);
+ if (CurrentPacketIsVMSwitchPacketFragment) {
+ ParseVmSwitchPacketFragment(ev);
+ }
+
+ Desc.PropertyName = (unsigned long long)L"LowerIfIndex";
+ Desc.ArrayIndex = ULONG_MAX;
+ Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(LowerIfIndex), (PBYTE)&LowerIfIndex);
+ if (Err != NO_ERROR) {
+ g_err = Err;
+ sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty LowerIfIndex failed, err is 0x%x", Err);
+ return;
+ }
+
+ Iface = GetInterface(LowerIfIndex);
+
+ if (!!(ev->EventHeader.EventDescriptor.Keyword & KW_MEDIA_NATIVE_802_11)) {
+ Type = WTAP_ENCAP_IEEE_802_11;
+ } else if (!!(ev->EventHeader.EventDescriptor.Keyword & KW_MEDIA_WIRELESS_WAN)) {
+ Type = WTAP_ENCAP_RAW_IP;
+ } else {
+ Type = WTAP_ENCAP_ETHERNET;
+ }
+
+ // Record the IfIndex if it's a new one.
+ if (Iface == NULL) {
+ unsigned long MiniportIfIndex;
+ Desc.PropertyName = (unsigned long long)L"MiniportIfIndex";
+ Desc.ArrayIndex = ULONG_MAX;
+ Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(MiniportIfIndex), (PBYTE)&MiniportIfIndex);
+ if (Err != NO_ERROR) {
+ g_err = Err;
+ sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty MiniportIfIndex failed, err is 0x%x", Err);
+ return;
+ }
+ Iface = AddInterface(
+ ev,
+ LowerIfIndex,
+ MiniportIfIndex,
+ Type
+ );
+ } else if (Iface->PktEncapType != Type) {
+ printf("WARNING: inconsistent media type in packet events!\n");
+ }
+
+ if (Iface == NULL) {
+ // We generated the list of interfaces directly from the
+ // packet traces themselves, so there must be a bug.
+ g_err = ERROR_INVALID_DATA;
+ sprintf_s(g_err_info, sizeof(g_err_info), "Packet with unrecognized IfIndex");
+ exit(1);
+ }
+
+ // Save off Ndis/Wlan metadata to be added to the next packet
+ if (ev->EventHeader.EventDescriptor.Id == tidPacketMetadata) {
+ unsigned long MetadataLength = 0;
+ Desc.PropertyName = (unsigned long long)L"MetadataSize";
+ Desc.ArrayIndex = ULONG_MAX;
+ Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(MetadataLength), (PBYTE)&MetadataLength);
+ if (Err != NO_ERROR) {
+ g_err = Err;
+ sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty MetadataSize failed, err is 0x%x", Err);
+ return;
+ }
+
+ if (MetadataLength != sizeof(PacketMetadata)) {
+ g_err = ERROR_INVALID_DATA;
+ sprintf_s(g_err_info, sizeof(g_err_info), "Unknown Metadata length. Expected %lu, got %lu", (unsigned long)sizeof(DOT11_EXTSTA_RECV_CONTEXT), MetadataLength);
+ return;
+ }
+
+ Desc.PropertyName = (unsigned long long)L"Metadata";
+ Desc.ArrayIndex = ULONG_MAX;
+ Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, MetadataLength, (PBYTE)&PacketMetadata);
+ if (Err != NO_ERROR) {
+ g_err = Err;
+ sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty Metadata failed, err is 0x%x", Err);
+ return;
+ }
+
+ AddWlanMetadata = true;
+ return;
+ }
+
+ // N.B.: Here we are querying the FragmentSize property to get the
+ // total size of the packet, and then reading that many bytes from
+ // the Fragment property. This is unorthodox (normally you are
+ // supposed to use TdhGetPropertySize to get the size of a property)
+ // but required due to the way ndiscap puts packet contents in
+ // multiple adjacent properties (which happen to be contiguous in
+ // memory).
+
+ Desc.PropertyName = (unsigned long long)L"FragmentSize";
+ Desc.ArrayIndex = ULONG_MAX;
+ Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(FragLength), (PBYTE)&FragLength);
+ if (Err != NO_ERROR) {
+ g_err = Err;
+ sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty FragmentSize failed, err is 0x%x", Err);
+ return;
+ }
+
+ if (FragLength > RTL_NUMBER_OF(AuxFragBuf) - AuxFragBufOffset) {
+ g_err = ERROR_INVALID_DATA;
+ sprintf_s(g_err_info, sizeof(g_err_info), "Packet too large (size = %u) and skipped", AuxFragBufOffset + FragLength);
+ return;
+ }
+
+ Desc.PropertyName = (unsigned long long)L"Fragment";
+ Desc.ArrayIndex = ULONG_MAX;
+ Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, FragLength, (PBYTE)(AuxFragBuf + AuxFragBufOffset));
+ if (Err != NO_ERROR) {
+ g_err = Err;
+ sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty Fragment failed, err is 0x%x", Err);
+ return;
+ }
+
+ // The KW_PACKET_START and KW_PACKET_END keywords are used as follows:
+ // -A single-event packet has both KW_PACKET_START and KW_PACKET_END.
+ // -A multi-event packet consists of an event with KW_PACKET_START followed
+ // by an event with KW_PACKET_END, with zero or more events with neither
+ // keyword in between.
+ //
+ // So, we accumulate fragments in AuxFragBuf until KW_PACKET_END is
+ // encountered, then call PcapNgWriteEnhancedPacket and start over. There's
+ // no need for us to even look for KW_PACKET_START.
+ //
+ // NB: Starting with Windows 8.1, only single-event packets are traced.
+ // This logic is here to support packet captures from older systems.
+
+ if (!!(ev->EventHeader.EventDescriptor.Keyword & KW_PACKET_END)) {
+
+ if (ev->EventHeader.EventDescriptor.Keyword & KW_MEDIA_NATIVE_802_11 &&
+ AuxFragBuf[1] & 0x40) {
+ // Clear Protected bit in the case of 802.11
+ // Ndis captures will be decrypted in the etl file
+
+ AuxFragBuf[1] = AuxFragBuf[1] & 0xBF; // _1011_1111_ - Clear "Protected Flag"
+ }
+
+ // COMMENT_MAX_SIZE must be multiple of 4
+ #define COMMENT_MAX_SIZE 256
+ char Comment[COMMENT_MAX_SIZE] = { 0 };
+ size_t CommentLength = 0;
+
+ if (AddWlanMetadata) {
+ if (PacketMetadata.uPhyId > DOT11_PHY_TYPE_NAMES_MAX) {
+ PacketMetadata.uPhyId = 0; // Set to unknown if outside known bounds.
+ }
+
+ Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d ProcessorNumber=%d Packet Metadata: ReceiveFlags:0x%x, PhyType:%s, CenterCh:%u, NumMPDUsReceived:%u, RSSI:%d, DataRate:%u",
+ ev->EventHeader.ProcessId,
+ ev->BufferContext.ProcessorNumber,
+ PacketMetadata.uReceiveFlags,
+ DOT11_PHY_TYPE_NAMES[PacketMetadata.uPhyId],
+ PacketMetadata.uChCenterFrequency,
+ PacketMetadata.usNumberOfMPDUsReceived,
+ PacketMetadata.lRSSI,
+ PacketMetadata.ucDataRate);
+
+ AddWlanMetadata = false;
+ memset(&PacketMetadata, 0, sizeof(DOT11_EXTSTA_RECV_CONTEXT));
+ } else if (CurrentPacketIsVMSwitchPacketFragment) {
+ if (VMSwitchPacketFragment.DestinationCount > 0) {
+ Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d ProcessorNumber=%d VlanId=%d SrcPortId=%d SrcNicType=%s SrcNicName=%s SrcPortName=%s DstNicCount=%d",
+ ev->EventHeader.ProcessId,
+ ev->BufferContext.ProcessorNumber,
+ Iface->VlanId,
+ Iface->VMNic.SourcePortId,
+ Iface->VMNic.SourceNicType,
+ Iface->VMNic.SourceNicName,
+ Iface->VMNic.SourcePortName,
+ VMSwitchPacketFragment.DestinationCount
+ );
+ } else {
+ Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d ProcessorNumber=%d VlanId=%d SrcPortId=%d SrcNicType=%s SrcNicName=%s SrcPortName=%s",
+ ev->EventHeader.ProcessId,
+ ev->BufferContext.ProcessorNumber,
+ Iface->VlanId,
+ Iface->VMNic.SourcePortId,
+ Iface->VMNic.SourceNicType,
+ Iface->VMNic.SourceNicName,
+ Iface->VMNic.SourcePortName
+ );
+ }
+ } else {
+ Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d ProcessorNumber=%d", ev->EventHeader.ProcessId, ev->BufferContext.ProcessorNumber);
+ }
+
+ if (Err != NO_ERROR) {
+ printf("Failed converting comment to string with error: %u\n", Err);
+ } else {
+ Err = StringCchLengthA(Comment, COMMENT_MAX_SIZE, &CommentLength);
+
+ if (Err != NO_ERROR) {
+ printf("Failed getting length of comment string with error: %u\n", Err);
+ CommentLength = 0;
+ memset(Comment, 0, COMMENT_MAX_SIZE);
+ }
+ }
+
+ TotalFragmentLength = AuxFragBufOffset + FragLength;
+
+ // Parse the packet to see if it's truncated. If so, try to recover the original length.
+ if (Type == WTAP_ENCAP_ETHERNET) {
+ if (TotalFragmentLength >= sizeof(ETHERNET_HEADER)) {
+ EthHdr = (PETHERNET_HEADER)AuxFragBuf;
+ if (ntohs(EthHdr->Type) == ETHERNET_TYPE_IPV4 &&
+ TotalFragmentLength >= sizeof(IPV4_HEADER) + sizeof(ETHERNET_HEADER)) {
+ Ipv4Hdr = (PIPV4_HEADER)(EthHdr + 1);
+ InferredOriginalFragmentLength = ntohs(Ipv4Hdr->TotalLength) + sizeof(ETHERNET_HEADER);
+ } else if (ntohs(EthHdr->Type) == ETHERNET_TYPE_IPV6 &&
+ TotalFragmentLength >= sizeof(IPV6_HEADER) + sizeof(ETHERNET_HEADER)) {
+ Ipv6Hdr = (PIPV6_HEADER)(EthHdr + 1);
+ InferredOriginalFragmentLength = ntohs(Ipv6Hdr->PayloadLength) + sizeof(IPV6_HEADER) + sizeof(ETHERNET_HEADER);
+ }
+ }
+ } else if (Type == WTAP_ENCAP_RAW_IP) {
+ // Raw frames begins with an IPv4/6 header.
+ if (TotalFragmentLength >= sizeof(IPV4_HEADER)) {
+ Ipv4Hdr = (PIPV4_HEADER)AuxFragBuf;
+ if (Ipv4Hdr->Version == 4) {
+ InferredOriginalFragmentLength = ntohs(Ipv4Hdr->TotalLength) + sizeof(ETHERNET_HEADER);
+ } else if (Ipv4Hdr->Version == 6) {
+ Ipv6Hdr = (PIPV6_HEADER)(AuxFragBuf);
+ InferredOriginalFragmentLength = ntohs(Ipv6Hdr->PayloadLength) + sizeof(IPV6_HEADER) + sizeof(ETHERNET_HEADER);
+ }
+ }
+ }
+
+ wtap_etl_rec_dump(AuxFragBuf,
+ TotalFragmentLength,
+ // For LSO v2 packets, inferred original fragment length is ignored since length field in IP header is not filled.
+ InferredOriginalFragmentLength <= TotalFragmentLength ? TotalFragmentLength : InferredOriginalFragmentLength,
+ Iface->PcapNgIfIndex,
+ !(ev->EventHeader.EventDescriptor.Keyword & KW_SEND),
+ timestamp,
+ Type,
+ Comment,
+ (unsigned short)CommentLength
+ );
+
+ AuxFragBufOffset = 0;
+ NumFramesConverted++;
+ } else {
+ AuxFragBufOffset += FragLength;
+ }
+}
+
+
+/*
+ * Editor modelines - https://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * vi: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */
diff --git a/extcap/etwdump.c b/extcap/etwdump.c
new file mode 100644
index 0000000..ea803a7
--- /dev/null
+++ b/extcap/etwdump.c
@@ -0,0 +1,312 @@
+/* etwdump.c
+ * etwdump is an extcap tool used to dump etw to pcapng
+ *
+ * Copyright 2020, Odysseus Yang
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "config.h"
+#define WS_LOG_DOMAIN "etwdump"
+
+#include "extcap-base.h"
+
+#include <wsutil/strtoi.h>
+#include <wsutil/filesystem.h>
+#include <wsutil/privileges.h>
+#include <wsutil/please_report_bug.h>
+#include <wsutil/wslog.h>
+
+#include <cli_main.h>
+#include <wsutil/cmdarg_err.h>
+#include "etl.h"
+
+#include <signal.h>
+
+/* extcap-interface has to be unique, or it may use wrong option output by a different extcapbin */
+#define ETW_EXTCAP_INTERFACE "etwdump"
+#define ETWDUMP_VERSION_MAJOR "1"
+#define ETWDUMP_VERSION_MINOR "0"
+#define ETWDUMP_VERSION_RELEASE "0"
+
+enum {
+ EXTCAP_BASE_OPTIONS_ENUM,
+ OPT_HELP,
+ OPT_VERSION,
+ OPT_INCLUDE_UNDECIDABLE_EVENT,
+ OPT_ETLFILE,
+ OPT_PARAMS
+};
+
+static struct ws_option longopts[] = {
+ EXTCAP_BASE_OPTIONS,
+ { "help", ws_no_argument, NULL, OPT_HELP},
+ { "version", ws_no_argument, NULL, OPT_VERSION},
+ { "iue", ws_optional_argument, NULL, OPT_INCLUDE_UNDECIDABLE_EVENT},
+ { "etlfile", ws_required_argument, NULL, OPT_ETLFILE},
+ { "params", ws_required_argument, NULL, OPT_PARAMS},
+ { 0, 0, 0, 0 }
+};
+
+int g_include_undecidable_event = false;
+
+void SignalHandler(_U_ int signal)
+{
+ SUPER_EVENT_TRACE_PROPERTIES super_trace_properties = { 0 };
+ super_trace_properties.prop.Wnode.BufferSize = sizeof(SUPER_EVENT_TRACE_PROPERTIES);
+ super_trace_properties.prop.Wnode.ClientContext = 2;
+ super_trace_properties.prop.Wnode.Flags = WNODE_FLAG_TRACED_GUID;
+ super_trace_properties.prop.LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
+ super_trace_properties.prop.LogFileMode = EVENT_TRACE_REAL_TIME_MODE;
+ /* Close trace when press CONTROL+C when running this console alone */
+ ControlTrace((TRACEHANDLE)NULL, LOGGER_NAME, &super_trace_properties.prop, EVENT_TRACE_CONTROL_STOP);
+}
+
+static void help(extcap_parameters* extcap_conf)
+{
+ extcap_help_print(extcap_conf);
+}
+
+static int list_config(char* interface)
+{
+ unsigned inc = 0;
+
+ if (!interface) {
+ ws_warning("No interface specified.");
+ return EXIT_FAILURE;
+ }
+
+ if (g_strcmp0(interface, ETW_EXTCAP_INTERFACE)) {
+ ws_warning("Interface must be %s", ETW_EXTCAP_INTERFACE);
+ return EXIT_FAILURE;
+ }
+ /*
+ * required=true agu will be displayed before required=false on UI
+ *
+ * Empty etlfile and unempty params, read etw events from a live session with the params as the filter
+ * Unempty etlfile and empty params, read etw events from the etl file without filter
+ * Unempty etlfile and unemtpy params, read etw events from the etl file with the params as the filter
+ * Empty eltfile and empty params, invalid
+ */
+ printf("arg {number=%u}{call=--etlfile}{display=etl file}"
+ "{type=fileselect}{tooltip=Select etl file to display in Wireshark}{required=false}{group=Capture}\n",
+ inc++);
+ printf("arg {number=%u}{call=--params}{display=filter parameters}"
+ "{type=string}{tooltip=Input providers, keyword and level filters for the etl file and live session}{group=Capture}\n",
+ inc++);
+ /*
+ * The undecidable events are those that either don't have sub-dissector or don't have anthing meaningful to display except for the EVENT_HEADER.
+ */
+ printf("arg {number=%u}{call=--iue}{display=Should undecidable events be included}"
+ "{type=boolflag}{default=false}{tooltip=Choose if the undecidable event is included}{group=Capture}\n",
+ inc++);
+
+ extcap_config_debug(&inc);
+ return EXIT_SUCCESS;
+}
+
+int main(int argc, char* argv[])
+{
+ char* err_msg;
+ int option_idx = 0;
+ int result;
+ int ret = EXIT_FAILURE;
+
+ char* etlfile = NULL;
+ char* params = NULL;
+
+ extcap_parameters* extcap_conf = g_new0(extcap_parameters, 1);
+ char* help_url;
+ char* help_header = NULL;
+
+ /* Initialize log handler early so we can have proper logging during startup. */
+ extcap_log_init("etwdump");
+
+ /*
+ * Get credential information for later use.
+ */
+ init_process_policies();
+
+ /*
+ * Attempt to get the pathname of the directory containing the
+ * executable file.
+ */
+ err_msg = configuration_init(argv[0], NULL);
+ if (err_msg != NULL) {
+ ws_warning("Can't get pathname of directory containing the extcap program: %s.",
+ err_msg);
+ g_free(err_msg);
+ }
+
+ help_url = data_file_url("etwdump.html");
+ extcap_base_set_util_info(extcap_conf, argv[0], ETWDUMP_VERSION_MAJOR, ETWDUMP_VERSION_MINOR,
+ ETWDUMP_VERSION_RELEASE, help_url);
+ g_free(help_url);
+ extcap_base_register_interface(extcap_conf, ETW_EXTCAP_INTERFACE, "Event Tracing for Windows (ETW) reader", 290, "DLT_ETW");
+
+ help_header = ws_strdup_printf(
+ " %s --extcap-interfaces\n"
+ " %s --extcap-interface=%s --extcap-dlts\n"
+ " %s --extcap-interface=%s --extcap-config\n"
+ " %s --extcap-interface=%s --etlfile c:\\wwansvc.etl \n"
+ "--fifo=FILENAME --capture\n", argv[0], argv[0], ETW_EXTCAP_INTERFACE, argv[0], ETW_EXTCAP_INTERFACE,
+ argv[0], ETW_EXTCAP_INTERFACE);
+ extcap_help_add_header(extcap_conf, help_header);
+ g_free(help_header);
+
+ extcap_help_add_option(extcap_conf, "--help", "print this help");
+ extcap_help_add_option(extcap_conf, "--version", "print the version");
+ extcap_help_add_option(extcap_conf, "--etlfile <filename>", "A etl filename");
+ extcap_help_add_option(extcap_conf, "--iue", "Choose if undecidable event is included");
+
+ if (argc == 1) {
+ help(extcap_conf);
+ goto end;
+ }
+
+ while ((result = ws_getopt_long(argc, argv, ":", longopts, &option_idx)) != -1) {
+ switch (result) {
+ case OPT_VERSION:
+ extcap_version_print(extcap_conf);
+ ret = EXIT_SUCCESS;
+ goto end;
+
+ case OPT_HELP:
+ help(extcap_conf);
+ ret = EXIT_SUCCESS;
+ goto end;
+
+ case OPT_ETLFILE:
+ etlfile = g_strdup(ws_optarg);
+ break;
+
+ case OPT_PARAMS:
+ /* Add params as the prefix since getopt_long will ignore the first argument always */
+ params = ws_strdup_printf("params %s", ws_optarg);
+ break;
+
+ case OPT_INCLUDE_UNDECIDABLE_EVENT:
+ g_include_undecidable_event = true;
+ break;
+
+ case ':':
+ /* missing option argument */
+ ws_warning("Option '%s' requires an argument", argv[ws_optind - 1]);
+ break;
+
+ default:
+ /* Handle extcap specific options */
+ if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg))
+ {
+ ws_warning("Invalid option: %s", argv[ws_optind - 1]);
+ goto end;
+ }
+ }
+ }
+
+ extcap_cmdline_debug(argv, argc);
+
+ if (extcap_base_handle_interface(extcap_conf)) {
+ ret = EXIT_SUCCESS;
+ goto end;
+ }
+
+ if (extcap_conf->show_config) {
+ ret = list_config(extcap_conf->interface);
+ goto end;
+ }
+
+ if (extcap_conf->capture) {
+
+ if (g_strcmp0(extcap_conf->interface, ETW_EXTCAP_INTERFACE)) {
+ ws_warning("ERROR: invalid interface");
+ goto end;
+ }
+
+ if (etlfile == NULL && params == NULL)
+ {
+ ws_warning("ERROR: Both --etlfile and --params arguments are empty");
+ goto end;
+ }
+
+ wtap_init(false);
+
+ signal(SIGINT, SignalHandler);
+
+ switch(etw_dump(etlfile, extcap_conf->fifo, params, &ret, &err_msg))
+ {
+ case WTAP_OPEN_ERROR:
+ if (err_msg != NULL) {
+ ws_warning("etw_dump failed: %s.",
+ err_msg);
+ g_free(err_msg);
+ }
+ else
+ {
+ ws_warning("etw_dump failed");
+ }
+ break;
+ case WTAP_OPEN_NOT_MINE:
+ if (etlfile == NULL)
+ {
+ if (err_msg != NULL) {
+ ws_warning("The live session didn't capture any event. Error message: %s.",
+ err_msg);
+ g_free(err_msg);
+ }
+ else
+ {
+ ws_warning("The live session didn't capture any event");
+ }
+ }
+ else
+ {
+ if (err_msg != NULL) {
+ ws_warning("The file %s is not etl format. Error message: %s.",
+ etlfile, err_msg);
+ g_free(err_msg);
+ }
+ else
+ {
+ ws_warning("The file %s is not etl format", etlfile);
+ }
+ }
+ break;
+ case WTAP_OPEN_MINE:
+ ret = EXIT_SUCCESS;
+ break;
+ }
+ }
+
+end:
+ /* clean up stuff */
+ extcap_base_cleanup(&extcap_conf);
+
+ if (etlfile != NULL)
+ {
+ g_free(etlfile);
+ }
+ if (params != NULL)
+ {
+ g_free(params);
+ }
+
+ return ret;
+}
+
+/*
+ * Editor modelines - https://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * vi: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */
diff --git a/extcap/extcap-base.c b/extcap/extcap-base.c
new file mode 100644
index 0000000..13d42b7
--- /dev/null
+++ b/extcap/extcap-base.c
@@ -0,0 +1,427 @@
+/* extcap-base.c
+ * Base function for extcaps
+ *
+ * Copyright 2015, Dario Lombardo
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "config.h"
+#define WS_LOG_DOMAIN LOG_DOMAIN_EXTCAP
+
+#include "extcap-base.h"
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <wsutil/wslog.h>
+
+#include <wsutil/ws_assert.h>
+
+#include "ws_attributes.h"
+
+enum extcap_options {
+ EXTCAP_BASE_OPTIONS_ENUM
+};
+
+typedef struct _extcap_interface
+{
+ char * interface;
+ char * description;
+
+ uint16_t dlt;
+ char * dltname;
+ char * dltdescription;
+} extcap_interface;
+
+typedef struct _extcap_option {
+ char * optname;
+ char * optdesc;
+} extcap_option_t;
+
+static FILE *custom_log = NULL;
+
+/* used to inform to extcap application that end of application is requested */
+bool extcap_end_application = false;
+/* graceful shutdown callback, can be null */
+void (*extcap_graceful_shutdown_cb)(void) = NULL;
+
+static void extcap_init_log_file(const char *filename);
+
+/* Called from signals */
+#ifdef _WIN32
+static BOOL WINAPI
+extcap_exit_from_loop(DWORD dwCtrlType _U_)
+#else
+static void extcap_exit_from_loop(int signo _U_)
+#endif /* _WIN32 */
+{
+ ws_debug("Exiting from main loop by signal");
+ extcap_end_application = true;
+ if (extcap_graceful_shutdown_cb != NULL) {
+ extcap_graceful_shutdown_cb();
+ }
+#ifdef _WIN32
+ return true;
+#endif /* _WIN32 */
+}
+
+void extcap_base_register_interface(extcap_parameters * extcap, const char * interface, const char * ifdescription, uint16_t dlt, const char * dltdescription )
+{
+ extcap_base_register_interface_ext(extcap, interface, ifdescription, dlt, NULL, dltdescription );
+}
+
+void extcap_base_register_interface_ext(extcap_parameters * extcap,
+ const char * interface, const char * ifdescription,
+ uint16_t dlt, const char * dltname, const char * dltdescription )
+{
+ extcap_interface * iface;
+
+ if (interface == NULL)
+ return;
+
+ iface = g_new0(extcap_interface, 1);
+
+ iface->interface = g_strdup(interface);
+ iface->description = g_strdup(ifdescription);
+ iface->dlt = dlt;
+ iface->dltname = g_strdup(dltname);
+ iface->dltdescription = g_strdup(dltdescription);
+
+ extcap->interfaces = g_list_append(extcap->interfaces, (void *) iface);
+}
+
+bool extcap_base_register_graceful_shutdown_cb(extcap_parameters * extcap _U_, void (*callback)(void))
+{
+#ifndef _WIN32
+ struct sigaction sig_handler = { .sa_handler = extcap_exit_from_loop };
+#endif
+
+ extcap_end_application = false;
+ extcap_graceful_shutdown_cb = callback;
+#ifdef _WIN32
+ if (!SetConsoleCtrlHandler(extcap_exit_from_loop, true)) {
+ ws_warning("Can't set console handler");
+ return false;
+ }
+#else
+ /* Catch signals to be able to cleanup config later */
+ if (sigaction(SIGINT, &sig_handler, NULL)) {
+ ws_warning("Can't set SIGINT signal handler");
+ return false;
+ }
+ if (sigaction(SIGTERM, &sig_handler, NULL)) {
+ ws_warning("Can't set SIGTERM signal handler");
+ return false;
+ }
+ if (sigaction(SIGPIPE, &sig_handler, NULL)) {
+ ws_warning("Can't set SIGPIPE signal handler");
+ return false;
+ }
+#endif /* _WIN32 */
+
+ return true;
+}
+
+void extcap_base_set_util_info(extcap_parameters * extcap, const char * exename, const char * major,
+ const char * minor, const char * release, const char * helppage)
+{
+ extcap->exename = g_path_get_basename(exename);
+
+ ws_assert(major);
+ if (!minor)
+ ws_assert(!release);
+
+ extcap->version = ws_strdup_printf("%s%s%s%s%s",
+ major,
+ minor ? "." : "",
+ minor ? minor : "",
+ release ? "." : "",
+ release ? release : "");
+ extcap->helppage = g_strdup(helppage);
+}
+
+void extcap_base_set_compiled_with(extcap_parameters * extcap, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ extcap->compiled_with = ws_strdup_vprintf(fmt, ap);
+ va_end(ap);
+}
+
+void extcap_base_set_running_with(extcap_parameters * extcap, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ extcap->running_with = ws_strdup_vprintf(fmt, ap);
+ va_end(ap);
+}
+
+void extcap_log_init(const char *progname)
+{
+ ws_log_init(progname, NULL);
+ /* extcaps cannot write debug information to parent on stderr. */
+ ws_log_console_writer_set_use_stdout(true);
+ ws_noisy("Extcap log initialization finished");
+}
+
+uint8_t extcap_base_parse_options(extcap_parameters * extcap, int result, char * optargument)
+{
+ uint8_t ret = 1;
+ enum ws_log_level level;
+
+ switch (result) {
+ case EXTCAP_OPT_LOG_LEVEL:
+ level = ws_log_set_level_str(optargument);
+ if (level == LOG_LEVEL_NONE) {
+ /* Invalid log level string. */
+ ret = 0;
+ }
+ else if (level <= LOG_LEVEL_DEBUG) {
+ extcap->debug = true;
+ }
+ break;
+ case EXTCAP_OPT_LOG_FILE:
+ extcap_init_log_file(optargument);
+ break;
+ case EXTCAP_OPT_LIST_INTERFACES:
+ extcap->do_list_interfaces = 1;
+ break;
+ case EXTCAP_OPT_VERSION:
+ extcap->ws_version = g_strdup(optargument);
+ extcap->do_version = 1;
+ break;
+ case EXTCAP_OPT_LIST_DLTS:
+ extcap->do_list_dlts = 1;
+ break;
+ case EXTCAP_OPT_INTERFACE:
+ extcap->interface = g_strdup(optargument);
+ break;
+ case EXTCAP_OPT_CONFIG:
+ extcap->show_config = 1;
+ break;
+ case EXTCAP_OPT_CAPTURE:
+ extcap->capture = 1;
+ break;
+ case EXTCAP_OPT_CAPTURE_FILTER:
+ extcap->capture_filter = g_strdup(optargument);
+ break;
+ case EXTCAP_OPT_FIFO:
+ extcap->fifo = g_strdup(optargument);
+ break;
+ default:
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static void extcap_iface_print(void * data, void * userdata _U_)
+{
+ extcap_interface * iface = (extcap_interface *)data;
+
+ printf("interface {value=%s}", iface->interface);
+ if (iface->description != NULL)
+ printf ("{display=%s}\n", iface->description);
+ else
+ printf ("\n");
+}
+
+static int extcap_iface_compare(gconstpointer a, gconstpointer b)
+{
+ const extcap_interface * iface_a = (const extcap_interface *)a;
+
+ return (g_strcmp0(iface_a->interface, (const char *) b));
+}
+
+static void extcap_print_version(extcap_parameters * extcap)
+{
+ printf("extcap {version=%s}", extcap->version != NULL ? extcap->version : "unknown");
+ if (extcap->helppage != NULL)
+ printf("{help=%s}", extcap->helppage);
+ printf("\n");
+}
+
+static int extcap_iface_listall(extcap_parameters * extcap, uint8_t list_ifs)
+{
+ if (list_ifs) {
+ if (g_list_length(extcap->interfaces) > 0) {
+ extcap_print_version(extcap);
+ g_list_foreach(extcap->interfaces, extcap_iface_print, extcap);
+ }
+ } else if (extcap->do_version) {
+ extcap_print_version(extcap);
+ } else {
+ GList * element = NULL;
+ extcap_interface * iface = NULL;
+ if ((element = g_list_find_custom(extcap->interfaces, extcap->interface, extcap_iface_compare)) == NULL)
+ return 0;
+
+ iface = (extcap_interface *) element->data;
+ printf("dlt {number=%u}{name=%s}", iface->dlt, iface->dltname != NULL ? iface->dltname : iface->interface);
+ if (iface->description != NULL)
+ printf ("{display=%s}\n", iface->dltdescription);
+ else
+ printf ("\n");
+ }
+
+ return 1;
+}
+
+uint8_t extcap_base_handle_interface(extcap_parameters * extcap)
+{
+ /* A fifo must be provided for capture */
+ if (extcap->capture && (extcap->fifo == NULL || strlen(extcap->fifo) <= 0)) {
+ extcap->capture = 0;
+ ws_error("Extcap Error: No FIFO pipe provided");
+ return 0;
+ }
+
+ if (extcap->do_list_interfaces) {
+ return extcap_iface_listall(extcap, 1);
+ } else if (extcap->do_version || extcap->do_list_dlts) {
+ return extcap_iface_listall(extcap, 0);
+ }
+
+ return 0;
+}
+
+static void extcap_iface_free(void * data)
+{
+ extcap_interface * iface = (extcap_interface *)data;
+ g_free(iface->interface);
+ g_free(iface->description);
+ g_free(iface->dltname);
+ g_free(iface->dltdescription);
+ g_free(iface);
+}
+
+static void extcap_help_option_free(void * option)
+{
+ extcap_option_t* o = (extcap_option_t*)option;
+ g_free(o->optname);
+ g_free(o->optdesc);
+ g_free(o);
+}
+
+void extcap_base_cleanup(extcap_parameters ** extcap)
+{
+ g_list_free_full((*extcap)->interfaces, extcap_iface_free);
+ g_free((*extcap)->exename);
+ g_free((*extcap)->fifo);
+ g_free((*extcap)->interface);
+ g_free((*extcap)->version);
+ g_free((*extcap)->compiled_with);
+ g_free((*extcap)->running_with);
+ g_free((*extcap)->helppage);
+ g_free((*extcap)->help_header);
+ g_free((*extcap)->ws_version);
+ g_list_free_full((*extcap)->help_options, extcap_help_option_free);
+ g_free(*extcap);
+ *extcap = NULL;
+}
+
+static void extcap_print_option(void * option, void * user_data _U_)
+{
+ extcap_option_t* o = (extcap_option_t*)option;
+ printf("\t%s: %s\n", o->optname, o->optdesc);
+}
+
+void extcap_version_print(extcap_parameters * extcap)
+{
+ printf("%s version %s\n", extcap->exename, extcap->version);
+ if (extcap->compiled_with != NULL)
+ printf("Compiled with %s\n", extcap->compiled_with);
+ if (extcap->running_with != NULL)
+ printf("Running with %s\n", extcap->running_with);
+}
+
+void extcap_help_print(extcap_parameters * extcap)
+{
+ printf("\nWireshark - %s v%s\n\n", extcap->exename, extcap->version);
+ printf("Usage:\n");
+ printf("%s", extcap->help_header);
+ printf("\n");
+ printf("Options:\n");
+ g_list_foreach(extcap->help_options, extcap_print_option, NULL);
+ printf("\n");
+}
+
+void extcap_help_add_option(extcap_parameters * extcap, const char * help_option_name, const char * help_option_desc)
+{
+ extcap_option_t* o = g_new0(extcap_option_t, 1);
+ o->optname = g_strdup(help_option_name);
+ o->optdesc = g_strdup(help_option_desc);
+
+ extcap->help_options = g_list_append(extcap->help_options, o);
+}
+
+void extcap_help_add_header(extcap_parameters * extcap, char * help_header)
+{
+ extcap->help_header = g_strdup(help_header);
+ extcap_help_add_option(extcap, "--extcap-interfaces", "list the extcap Interfaces");
+ extcap_help_add_option(extcap, "--extcap-dlts", "list the DLTs");
+ extcap_help_add_option(extcap, "--extcap-interface <iface>", "specify the extcap interface");
+ extcap_help_add_option(extcap, "--extcap-config", "list the additional configuration for an interface");
+ extcap_help_add_option(extcap, "--capture", "run the capture");
+ extcap_help_add_option(extcap, "--extcap-capture-filter <filter>", "the capture filter");
+ extcap_help_add_option(extcap, "--fifo <file>", "dump data to file or fifo");
+ extcap_help_add_option(extcap, "--extcap-version", "print tool version");
+ extcap_help_add_option(extcap, "--log-level", "Set the log level");
+ extcap_help_add_option(extcap, "--log-file", "Set a log file to log messages in addition to the console");
+}
+
+static void extcap_init_log_file(const char* filename)
+{
+ if (!filename || strlen(filename) == 0)
+ ws_error("Missing log file name");
+ custom_log = fopen(filename, "w");
+ if (!custom_log)
+ ws_error("Can't open custom log file: %s (%s)", filename, strerror(errno));
+ ws_log_add_custom_file(custom_log);
+}
+
+void extcap_config_debug(unsigned* count)
+{
+ printf("arg {number=%u}{call=--log-level}{display=Set the log level}"
+ "{type=selector}{tooltip=Set the log level}{required=false}"
+ "{group=Debug}\n", *count);
+ printf("value {arg=%u}{value=message}{display=Message}{default=true}\n", *count);
+ printf("value {arg=%u}{value=info}{display=Info}\n", *count);
+ printf("value {arg=%u}{value=debug}{display=Debug}\n", *count);
+ printf("value {arg=%u}{value=noisy}{display=Noisy}\n", *count);
+ (*count)++;
+ printf("arg {number=%u}{call=--log-file}{display=Use a file for logging}"
+ "{type=fileselect}{tooltip=Set a file where log messages are written}{required=false}"
+ "{group=Debug}\n", (*count)++);
+}
+
+void extcap_cmdline_debug(char** ar, const unsigned n)
+{
+ GString* cmdline = g_string_new("cmdline: ");
+ unsigned i;
+ for (i = 0; i < n; i++)
+ g_string_append_printf(cmdline, "%s ", ar[i]);
+ ws_debug("%s", cmdline->str);
+ g_string_free(cmdline, true);
+}
+
+/*
+ * Editor modelines - https://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * vi: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */
diff --git a/extcap/extcap-base.h b/extcap/extcap-base.h
new file mode 100644
index 0000000..4bbfb1b
--- /dev/null
+++ b/extcap/extcap-base.h
@@ -0,0 +1,129 @@
+/** @file
+ *
+ * Base function for extcaps
+ *
+ * Copyright 2016, Dario Lombardo
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#ifndef __EXTCAP_BASE_H__
+#define __EXTCAP_BASE_H__
+
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <wsutil/ws_getopt.h>
+
+#ifdef _WIN32
+#include <io.h>
+#endif
+
+#include <wsutil/socket.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+#define EXTCAP_BASE_OPTIONS_ENUM \
+ EXTCAP_OPT_LIST_INTERFACES, \
+ EXTCAP_OPT_VERSION, \
+ EXTCAP_OPT_LIST_DLTS, \
+ EXTCAP_OPT_INTERFACE, \
+ EXTCAP_OPT_CONFIG, \
+ EXTCAP_OPT_CAPTURE, \
+ EXTCAP_OPT_CAPTURE_FILTER, \
+ EXTCAP_OPT_FIFO, \
+ EXTCAP_OPT_LOG_LEVEL, \
+ EXTCAP_OPT_LOG_FILE
+
+
+#define EXTCAP_BASE_OPTIONS \
+ { "extcap-interfaces", ws_no_argument, NULL, EXTCAP_OPT_LIST_INTERFACES}, \
+ { "extcap-version", ws_optional_argument, NULL, EXTCAP_OPT_VERSION}, \
+ { "extcap-dlts", ws_no_argument, NULL, EXTCAP_OPT_LIST_DLTS}, \
+ { "extcap-interface", ws_required_argument, NULL, EXTCAP_OPT_INTERFACE}, \
+ { "extcap-config", ws_no_argument, NULL, EXTCAP_OPT_CONFIG}, \
+ { "capture", ws_no_argument, NULL, EXTCAP_OPT_CAPTURE}, \
+ { "extcap-capture-filter", ws_required_argument, NULL, EXTCAP_OPT_CAPTURE_FILTER}, \
+ { "fifo", ws_required_argument, NULL, EXTCAP_OPT_FIFO}, \
+ { "log-level", ws_required_argument, NULL, EXTCAP_OPT_LOG_LEVEL}, \
+ { "log-file", ws_required_argument, NULL, EXTCAP_OPT_LOG_FILE}
+
+typedef struct _extcap_parameters
+{
+ char * exename;
+ char * fifo;
+ char * interface;
+ char * capture_filter;
+
+ char * version;
+ char * compiled_with;
+ char * running_with;
+ char * helppage;
+ uint8_t capture;
+ uint8_t show_config;
+
+ char * ws_version;
+
+ /* private content */
+ GList * interfaces;
+ uint8_t do_version;
+ uint8_t do_list_dlts;
+ uint8_t do_list_interfaces;
+
+ char * help_header;
+ GList * help_options;
+
+ bool debug;
+} extcap_parameters;
+
+/* used to inform to extcap application that end of application is requested */
+extern bool extcap_end_application;
+
+void extcap_base_register_interface(extcap_parameters * extcap, const char * interface, const char * ifdescription, uint16_t dlt, const char * dltdescription );
+void extcap_base_register_interface_ext(extcap_parameters * extcap, const char * interface, const char * ifdescription, uint16_t dlt, const char * dltname, const char * dltdescription );
+
+/* used to inform extcap framework that graceful shutdown supported by the extcap
+ */
+bool extcap_base_register_graceful_shutdown_cb(extcap_parameters * extcap, void (*callback)(void));
+
+void extcap_base_set_util_info(extcap_parameters * extcap, const char * exename, const char * major, const char * minor, const char * release, const char * helppage);
+void extcap_base_set_compiled_with(extcap_parameters * extcap, const char *fmt, ...);
+void extcap_base_set_running_with(extcap_parameters * extcap, const char *fmt, ...);
+uint8_t extcap_base_parse_options(extcap_parameters * extcap, int result, char * optargument);
+uint8_t extcap_base_handle_interface(extcap_parameters * extcap);
+void extcap_base_cleanup(extcap_parameters ** extcap);
+void extcap_help_add_header(extcap_parameters * extcap, char * help_header);
+void extcap_help_add_option(extcap_parameters * extcap, const char * help_option_name, const char * help_optionn_desc);
+void extcap_version_print(extcap_parameters * extcap);
+void extcap_help_print(extcap_parameters * extcap);
+void extcap_cmdline_debug(char** ar, const unsigned n);
+void extcap_config_debug(unsigned* count);
+void extcap_base_help(void);
+void extcap_log_init(const char *progname);
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+#endif // __EXTCAP_BASE_H__
+
+/*
+ * Editor modelines - https://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * vi: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */
diff --git a/extcap/falcodump.cpp b/extcap/falcodump.cpp
new file mode 100644
index 0000000..b871064
--- /dev/null
+++ b/extcap/falcodump.cpp
@@ -0,0 +1,1016 @@
+/* falcodump.cpp
+ * Falcodump is an extcap tool which dumps logs using Falco source plugins.
+ * https://falco.org/docs/plugins/
+ *
+ * Adapted from sdjournal.
+ * Copyright 2022, Gerald Combs and Dario Lombardo
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * To do:
+ * - Pull plugin source description from list_open_params?
+ * - Add filtering.
+ * - Add an option to dump plugin fields.
+ * - Add options for credentials.
+ * - Let the user create preconfigured interfaces.
+ * - Exit more cleanly (see MRs 2063 and 7673).
+ * - Better config schema default value parsing? Would likely require a schema change.
+ * - Make sure all types are handled in parse_schema_properties.
+ * - Handle "required" config schema annotation (Okta).
+ */
+
+#include "config.h"
+
+#include <sinsp.h>
+#include <plugin_manager.h>
+
+#define WS_LOG_DOMAIN "falcodump"
+
+#include <extcap/extcap-base.h>
+
+#include <wsutil/file_util.h>
+#include <wsutil/filesystem.h>
+#include <wsutil/json_dumper.h>
+#include <wsutil/privileges.h>
+#include <wsutil/utf8_entities.h>
+#include <wsutil/wsjson.h>
+#include <wsutil/wslog.h>
+
+#define FALCODUMP_VERSION_MAJOR "1"
+#define FALCODUMP_VERSION_MINOR "0"
+#define FALCODUMP_VERSION_RELEASE "0"
+
+#define FALCODUMP_PLUGIN_PLACEHOLDER "<plugin name>"
+
+// We load our plugins and fetch their configs before we set our log level.
+// #define DEBUG_JSON_PARSING
+// #define DEBUG_SINSP
+
+enum {
+ EXTCAP_BASE_OPTIONS_ENUM,
+ OPT_HELP,
+ OPT_VERSION,
+ OPT_PLUGIN_API_VERSION,
+ OPT_PLUGIN_SOURCE,
+ OPT_SCHEMA_PROPERTIES_START,
+};
+
+// "s3DownloadConcurrency": {
+// "type": "integer",
+// "description": "Controls the number of background goroutines used to download S3 files (Default: 1)"
+// },
+struct config_properties {
+ std::string name;
+ std::string display; // "title" property || name
+ std::string option; // Command line option including leading dashes (lowercase display name)
+ int option_index; // Starts from OPT_SCHEMA_PROPERTIES_START
+ std::string type; // "boolean", "integer", "string", "enum", "BEGIN_CONFIG_PROPERTIES", or "END_CONFIG_PROPERTIES"
+ std::string description;
+ std::string default_value;
+ std::vector<std::string>enum_values;
+ std::string current_value;
+};
+
+struct plugin_configuration {
+ std::vector<struct config_properties> property_list;
+
+ std::string json_config() {
+ json_dumper dumper = {};
+ dumper.output_string = g_string_new(NULL);
+
+ json_dumper_begin_object(&dumper);
+
+ for (const auto &prop : property_list) {
+ if (prop.type == "BEGIN_CONFIG_PROPERTIES") {
+ json_dumper_set_member_name(&dumper, prop.name.c_str());
+ json_dumper_begin_object(&dumper);
+ continue;
+ } else if (prop.type == "END_CONFIG_PROPERTIES") {
+ json_dumper_end_object(&dumper);
+ continue;
+ }
+
+ if (prop.current_value == prop.default_value) {
+ continue;
+ }
+
+ json_dumper_set_member_name(&dumper, prop.name.c_str());
+ if (prop.type == "string" || prop.type == "selector") {
+ json_dumper_value_string(&dumper, prop.current_value.c_str());
+ } else {
+ json_dumper_value_anyf(&dumper, "%s", prop.current_value.c_str());
+ }
+ }
+
+ json_dumper_end_object(&dumper);
+ json_dumper_finish(&dumper);
+ std::string config_blob = dumper.output_string->str;
+ ws_debug("configuration: %s", dumper.output_string->str);
+ g_string_free(dumper.output_string, TRUE);
+ return config_blob;
+ }
+};
+
+// Read a line without trailing (CR)LF. Returns -1 on failure. Copied from addr_resolv.c.
+// XXX Use g_file_get_contents or GMappedFile instead?
+static int
+fgetline(char *buf, int size, FILE *fp)
+{
+ if (fgets(buf, size, fp)) {
+ int len = (int)strcspn(buf, "\r\n");
+ buf[len] = '\0';
+ return len;
+ }
+ return -1;
+}
+
+static const size_t MAX_AWS_LINELEN = 2048;
+void print_cloudtrail_aws_profile_config(int arg_num, const char *display, const char *description) {
+ char buf[MAX_AWS_LINELEN];
+ char profile[MAX_AWS_LINELEN];
+ FILE *aws_fp;
+ std::set<std::string>profiles;
+
+ // Look in files as specified in https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html
+ char *cred_path = g_strdup(g_getenv("AWS_SHARED_CREDENTIALS_FILE"));
+ if (cred_path == NULL) {
+ cred_path = g_build_filename(g_get_home_dir(), ".aws", "credentials", (gchar *)NULL);
+ }
+
+ aws_fp = ws_fopen(cred_path, "r");
+ g_free(cred_path);
+
+ if (aws_fp != NULL) {
+ while (fgetline(buf, sizeof(buf), aws_fp) >= 0) {
+ if (sscanf(buf, "[%2047[^]]s]", profile) == 1) {
+ if (strcmp(profile, "default") == 0) {
+ continue;
+ }
+ profiles.insert(profile);
+ }
+ }
+ fclose(aws_fp);
+ }
+
+ char *conf_path = g_strdup(g_getenv("AWS_CONFIG_FILE"));
+ if (conf_path == NULL) {
+ conf_path = g_build_filename(g_get_home_dir(), ".aws", "config", (gchar *)NULL);
+ }
+
+ aws_fp = ws_fopen(conf_path, "r");
+ g_free(conf_path);
+
+ if (aws_fp != NULL) {
+ while (fgetline(buf, sizeof(buf), aws_fp) >= 0) {
+ if (sscanf(buf, "[profile %2047[^]]s]", profile) == 1) {
+ if (strcmp(profile, "default") == 0) {
+ continue;
+ }
+ profiles.insert(profile);
+ }
+ }
+ fclose(aws_fp);
+ }
+
+ const char *aws_profile_env = g_getenv("AWS_PROFILE");
+ for (auto &profile : profiles) {
+ if (aws_profile_env && profile == aws_profile_env) {
+ aws_profile_env = nullptr;
+ }
+ }
+ if (aws_profile_env) {
+ profiles.insert(aws_profile_env);
+ }
+
+ printf(
+ "arg {number=%d}"
+ "{call=--cloudtrail-aws-profile}"
+ "{display=%s}"
+ "{type=editselector}"
+ "{tooltip=%s}"
+ "{group=Capture}"
+ "\n",
+ arg_num, display, description);
+ printf ("value {arg=%d}{value=}{display=Default}{default=true}\n", arg_num);
+ for (auto &profile : profiles) {
+ printf(
+ "value {arg=%d}"
+ "{value=%s}"
+ "{display=%s}"
+ "\n",
+ arg_num, profile.c_str(), profile.c_str());
+ }
+}
+
+void print_cloudtrail_aws_region_config(int arg_num, const char *display, const char *description) {
+ // printf ' "%s",\n' $(aws ec2 describe-regions --all-regions --query "Regions[].{Name:RegionName}" --output text) | sort
+ std::set<std::string> regions = {
+ "af-south-1",
+ "ap-east-1",
+ "ap-northeast-1",
+ "ap-northeast-2",
+ "ap-northeast-3",
+ "ap-south-1",
+ "ap-south-2",
+ "ap-southeast-1",
+ "ap-southeast-2",
+ "ap-southeast-3",
+ "ap-southeast-4",
+ "ca-central-1",
+ "eu-central-1",
+ "eu-central-2",
+ "eu-north-1",
+ "eu-south-1",
+ "eu-south-2",
+ "eu-west-1",
+ "eu-west-2",
+ "eu-west-3",
+ "me-central-1",
+ "me-south-1",
+ "sa-east-1",
+ "us-east-1",
+ "us-east-2",
+ "us-west-1",
+ "us-west-2",
+ };
+
+ const char *aws_region_env = g_getenv("AWS_REGION");
+ for (auto &region : regions) {
+ if (aws_region_env && region == aws_region_env) {
+ aws_region_env = nullptr;
+ }
+ }
+ if (aws_region_env) {
+ regions.insert(aws_region_env);
+ }
+
+ printf(
+ "arg {number=%d}"
+ "{call=--cloudtrail-aws-region}"
+ "{display=%s}"
+ "{type=editselector}"
+ "{tooltip=%s}"
+ "{group=Capture}"
+ "\n",
+ arg_num, display, description);
+ printf ("value {arg=%d}{value=}{display=From profile}{default=true}\n", arg_num);
+
+ for (auto &region : regions) {
+ printf(
+ "value {arg=%d}"
+ "{value=%s}"
+ "{display=%s}"
+ "\n",
+ arg_num, region.c_str(), region.c_str());
+ }
+}
+
+
+// Load our plugins. This should match the behavior of the Falco Bridge dissector.
+static void load_plugins(sinsp &inspector) {
+ WS_DIR *dir;
+ WS_DIRENT *file;
+ char *plugin_paths[] = {
+ g_build_filename(get_plugins_dir_with_version(), "falco", NULL),
+ g_build_filename(get_plugins_pers_dir_with_version(), "falco", NULL)
+ };
+
+ for (size_t idx = 0; idx < 2; idx++) {
+ char *plugin_path = plugin_paths[idx];
+ if ((dir = ws_dir_open(plugin_path, 0, NULL)) != NULL) {
+ while ((file = ws_dir_read_name(dir)) != NULL) {
+ char *libname = g_build_filename(plugin_path, ws_dir_get_name(file), NULL);
+ try {
+ auto plugin = inspector.register_plugin(libname);
+ ws_debug("Registered plugin %s via %s", plugin->name().c_str(), libname);
+ } catch (sinsp_exception &e) {
+ ws_warning("%s", e.what());
+ }
+ g_free(libname);
+ }
+ ws_dir_close(dir);
+ }
+ g_free(plugin_path);
+ }
+}
+
+// Given a key, try to find its value in a JSON object.
+// Returns (value, true) on success, or (err_str, false) on failure.
+const std::pair<const std::string,bool> find_json_object_value(const std::string &object_blob, const std::string &key, int value_type) {
+ std::vector<jsmntok_t> tokens;
+ int num_tokens = json_parse(object_blob.c_str(), NULL, 0);
+
+ switch (num_tokens) {
+ case JSMN_ERROR_INVAL:
+ return std::pair<std::string,bool>("invalid", false);
+ case JSMN_ERROR_PART:
+ return std::pair<std::string,bool>("incomplete", false);
+ default:
+ break;
+ }
+
+ tokens.resize(num_tokens);
+ json_parse(object_blob.c_str(), tokens.data(), num_tokens);
+ for (int idx = 0; idx < num_tokens - 1; idx++) {
+ jsmntok_t &k_tok = tokens[idx];
+ jsmntok_t &v_tok = tokens[idx+1];
+ std::string cur_key = object_blob.substr(k_tok.start, k_tok.end - k_tok.start);
+ if (cur_key == key && k_tok.type == JSMN_STRING && v_tok.type == value_type) {
+ std::string value = object_blob.substr(v_tok.start, v_tok.end - v_tok.start);
+ return std::pair<std::string,bool>(value, true);
+ }
+#ifdef DEBUG_JSON_PARSING
+ else if (cur_key == key) ws_warning("|%s(%d): %s(%d)|\n", cur_key.c_str(), k_tok.type, object_blob.substr(v_tok.start, v_tok.end - v_tok.start).c_str(), v_tok.type);
+#endif
+ }
+ return std::pair<const std::string,bool>("", false);
+}
+
+// Given an RFC 6901-style JSON pointer, try to find its value in a JSON object.
+// Returns (value, true) on success, or (err_str, false) on failure.
+const std::pair<const std::string,bool> find_json_pointer_value(const std::string &object_blob, const std::string &pointer, int value_type) {
+ std::string blob = object_blob;
+ std::istringstream ob_stream(pointer);
+ std::string token;
+ while (std::getline(ob_stream, token, '/')) {
+ if (token == "#" || token.empty()) {
+ continue;
+ }
+ std::pair<std::string,bool> jv = find_json_object_value(blob, token, value_type);
+ if (!jv.second) {
+#ifdef DEBUG_JSON_PARSING
+ ws_warning("JSON pointer %s not found at %s", blob.c_str(), token.c_str());
+#endif
+ return std::pair<std::string,bool>("", false);
+ }
+ blob = jv.first;
+ }
+#ifdef DEBUG_JSON_PARSING
+ ws_warning("JSON pointer %s = %s ... %s", pointer.c_str(), blob.substr(0, 10).c_str(), blob.substr(blob.size() - 10, 10).c_str());
+#endif
+ return std::pair<const std::string,bool>(blob, true);
+}
+
+// Convert a JSON array to a string vector.
+// Returns (vector, true) on success, or (err_str, false) on failure.
+const std::pair<std::vector<std::string>,bool> get_json_array(const std::string &array_blob) {
+ std::vector<jsmntok_t> tokens;
+ int num_tokens = json_parse(array_blob.c_str(), NULL, 0);
+
+ switch (num_tokens) {
+ case JSMN_ERROR_INVAL:
+ return std::pair<std::vector<std::string>,bool>(std::vector<std::string>{"invalid"}, false);
+ case JSMN_ERROR_PART:
+ {
+ return std::pair<std::vector<std::string>,bool>(std::vector<std::string>{"incomplete"}, false);
+ }
+ default:
+ break;
+ }
+
+ tokens.resize(num_tokens);
+ json_parse(array_blob.c_str(), tokens.data(), num_tokens);
+ std::vector<std::string> elements;
+ // First token is the full array.
+ for (int idx = 1; idx < num_tokens; idx++) {
+ jsmntok_t &el_tok = tokens[idx];
+ elements.push_back(array_blob.substr(el_tok.start, el_tok.end - el_tok.start));
+ }
+#ifdef DEBUG_JSON_PARSING
+ ws_warning("%s: %d", array_blob.c_str(), (int)elements.size());
+#endif
+ return std::pair<std::vector<std::string>,bool>(elements, true);
+}
+
+// Given a JSON blob containing a schema properties object, add each property to the
+// given plugin config.
+const std::pair<const std::string,bool> get_schema_properties(const std::string props_blob, int &opt_idx, const std::string option_prefix, const std::string plugin_name, std::vector<struct config_properties> &property_list) {
+ std::vector<jsmntok_t> tokens;
+ int num_tokens = json_parse(props_blob.c_str(), NULL, 0);
+
+ switch (num_tokens) {
+ case JSMN_ERROR_INVAL:
+ return std::pair<std::string,bool>("invalid", false);
+ case JSMN_ERROR_PART:
+ return std::pair<std::string,bool>("incomplete", false);
+ default:
+ break;
+ }
+
+ tokens.resize(num_tokens);
+ json_parse(props_blob.c_str(), tokens.data(), num_tokens);
+
+ if (tokens[0].type != JSMN_OBJECT) {
+ return std::pair<std::string,bool>("malformed", false);
+ }
+
+ char *plugin_name_lower = g_ascii_strdown(plugin_name.c_str(), -1);
+
+ // We have an object blob which contains a list of properties and property blobs, e.g.
+ // { "property_1": { "type": ... }, "prob_blob_1": { "properties": { "prop_blob_2": { "type": ... } } }
+ // Skip over the outer { ... } and process the contents as pairs.
+ for (int idx = 1; idx < num_tokens - 2; idx++) {
+ jsmntok_t &n_tok = tokens[idx];
+ jsmntok_t &p_tok = tokens[idx+1];
+
+ std::string name = props_blob.substr(n_tok.start, n_tok.end - n_tok.start);
+ std::string display = name;
+ std::string property_blob = props_blob.substr(p_tok.start, p_tok.end - p_tok.start);
+ std::vector<std::string> enum_values;
+
+ // XXX Check for errors?
+ int prop_tokens = json_parse(property_blob.c_str(), NULL, 0);
+ switch (prop_tokens) {
+ case JSMN_ERROR_INVAL:
+ return std::pair<std::string,bool>("invalid property", false);
+ case JSMN_ERROR_PART:
+ return std::pair<std::string,bool>("incomplete property", false);
+ default:
+ break;
+ }
+#ifdef DEBUG_JSON_PARSING
+ ws_warning("property %s [%d]\n", name.c_str(), prop_tokens);
+#endif
+
+ std::pair<std::string,bool> jv = find_json_object_value(property_blob, "properties", JSMN_OBJECT);
+ if (jv.second) {
+ config_properties properties = {
+ name,
+ display,
+ "",
+ -1,
+ "BEGIN_CONFIG_PROPERTIES",
+ "",
+ "",
+ enum_values,
+ "",
+ };
+ property_list.push_back(properties);
+ get_schema_properties(jv.first, opt_idx, option_prefix + "-" + name, plugin_name, property_list);
+ properties = {
+ name,
+ display,
+ "",
+ -1,
+ "END_CONFIG_PROPERTIES",
+ "",
+ "",
+ enum_values,
+ "",
+ };
+ property_list.push_back(properties);
+ idx += prop_tokens;
+ continue;
+ }
+
+ jv = find_json_object_value(property_blob, "title", JSMN_STRING);
+ if (jv.second) {
+ display = jv.first;
+ }
+ // else split+capitalize "name"?
+
+ jv = find_json_object_value(property_blob, "type", JSMN_STRING);
+ if (!jv.second) {
+ return std::pair<std::string,bool>("missing type", false);
+ }
+ std::string type = jv.first;
+ jv = find_json_object_value(property_blob, "description", JSMN_STRING);
+ if (!jv.second) {
+ return std::pair<std::string,bool>("missing description", false);
+ }
+ std::string description = jv.first;
+ std::string default_value;
+ jv = find_json_object_value(property_blob, "default", JSMN_STRING);
+ if (jv.second) {
+ default_value = jv.first;
+ } else {
+ std::string default_pfx = "(Default: ";
+ size_t pfx_pos = description.rfind(default_pfx);
+ if (pfx_pos != std::string::npos) {
+ default_value = description.substr(pfx_pos + default_pfx.size());
+ pfx_pos = default_value.rfind(")");
+ default_value = default_value.erase(pfx_pos);
+ }
+ }
+ jv = find_json_object_value(property_blob, "enum", JSMN_ARRAY);
+ if (jv.second) {
+ const std::pair<std::vector<std::string>,bool> ja = get_json_array(jv.first);
+ if (ja.second) {
+ enum_values = ja.first;
+ type = "selector";
+ }
+ }
+#ifdef DEBUG_JSON_PARSING
+ ws_warning("%s: %s, %s, [%d]\n", name.c_str(), type.c_str(), description.c_str(), (int)enum_values.size());
+#endif
+ const char *call = g_ascii_strdown(name.c_str(), -1);
+ config_properties properties = {
+ name,
+ display,
+ std::string() + plugin_name_lower + option_prefix + "-" + call, // Command line option (lowercase plugin + display name)
+ opt_idx,
+ type,
+ description,
+ default_value,
+ enum_values,
+ default_value,
+ };
+ property_list.push_back(properties);
+ g_free((gpointer)call);
+ idx += prop_tokens;
+ opt_idx++;
+ }
+ g_free(plugin_name_lower);
+ return std::pair<std::string,bool>("",true);
+}
+
+// Wherein we try to implement a sufficiently complete JSON Schema parser.
+// Given a plugin config schema like the following:
+//{
+// "$schema": "http://json-schema.org/draft-04/schema#",
+// "$ref": "#/definitions/PluginConfig",
+// "definitions": {
+// "PluginConfig": {
+// "properties": {
+// "s3DownloadConcurrency": {
+// "type": "integer",
+// "title": "S3 download concurrency",
+// "description": "Controls the number of background goroutines used to download S3 files (Default: 1)",
+// "default": 1
+// },
+// [ ... ]
+// "aws": {
+// "$schema": "http://json-schema.org/draft-04/schema#",
+// "$ref": "#/definitions/PluginConfigAWS"
+// }
+// },
+// "additionalProperties": true,
+// "type": "object"
+// },
+// "PluginConfigAWS": {
+// "properties": {
+// "profile": {
+// "type": "string",
+// "title": "Shared AWS Config Profile",
+// "description": "If non-empty",
+// "default": "''"
+// },
+// [ ... ]
+// },
+// "additionalProperties": true,
+// "type": "object"
+// }
+// }
+//}
+// find the plugin properties and parse them using parse_schema_properties.
+// https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-basic-meta
+
+static bool get_plugin_config_schema(const std::shared_ptr<sinsp_plugin> &plugin, plugin_configuration &plugin_config)
+{
+ ss_plugin_schema_type schema_type = SS_PLUGIN_SCHEMA_JSON;
+ std::string schema_blob = plugin->get_init_schema(schema_type);
+ std::string config_name;
+ std::pair<std::string,bool> jv;
+
+#ifdef DEBUG_JSON_PARSING
+ ws_warning("raw schema: %s\n", schema_blob.c_str());
+#endif
+
+ int ref_cnt = 0;
+ std::string::size_type ref_pos = 0;
+ while ((ref_pos = schema_blob.find("\"$ref\"", ref_pos)) != std::string::npos) {
+ ref_cnt++;
+ ref_pos += 5;
+ }
+
+ // Dereference all of our $ref pairs.
+ // This is kind of janky, but makes subsequent parsing more simple.
+ for (int ref_idx = 0; ref_idx < ref_cnt; ref_idx++) {
+ jv = find_json_object_value(schema_blob, "$ref", JSMN_STRING);
+ if (!jv.second) {
+ break;
+ }
+ const std::string ref_pointer = jv.first;
+ jv = find_json_pointer_value(schema_blob, ref_pointer, JSMN_OBJECT);
+ if (!jv.second) {
+ ws_warning("Unable to find $ref %s.", ref_pointer.c_str());
+ return false;
+ }
+ const std::string ref_body = jv.first.substr(1, jv.first.size() - 2);
+
+ std::vector<jsmntok_t> tokens;
+ int num_tokens = json_parse(schema_blob.c_str(), NULL, 0);
+
+ switch (num_tokens) {
+ case JSMN_ERROR_INVAL:
+ ws_warning("Invalid schema.");
+ return false;
+ case JSMN_ERROR_PART:
+ {
+ ws_warning("Incomplete schema.");
+ return false;
+ }
+ default:
+ break;
+ }
+
+#ifdef DEBUG_JSON_PARSING
+ ws_warning("searching for $ref: %s\n", ref_pointer.c_str());
+#endif
+ tokens.resize(num_tokens);
+ json_parse(schema_blob.c_str(), tokens.data(), num_tokens);
+ std::vector<std::string> elements;
+ // First token is the full array.
+ for (int idx = 0; idx < num_tokens - 1; idx++) {
+ if (tokens[idx].type != JSMN_STRING && tokens[idx+1].type != JSMN_STRING) {
+ continue;
+ }
+ auto key_tok = &tokens[idx];
+ auto val_tok = &tokens[idx+1];
+ std::string key = schema_blob.substr(key_tok->start, key_tok->end - key_tok->start);
+ std::string value = schema_blob.substr(val_tok->start, val_tok->end - val_tok->start);
+ if (key != "$ref" || value != ref_pointer) {
+ continue;
+ }
+ try {
+#ifdef DEBUG_JSON_PARSING
+ ws_warning("replacing: %s\n", schema_blob.substr(key_tok->start - 1, val_tok->end - key_tok->start + 2).c_str());
+#endif
+ schema_blob.replace(key_tok->start - 1, val_tok->end - key_tok->start + 2, ref_body);
+ } catch (std::out_of_range const&) {
+ ws_warning("Unknown reference %s.", key.c_str());
+ return false;
+ }
+ }
+ }
+
+#ifdef DEBUG_JSON_PARSING
+ ws_warning("cooked schema: %s\n", schema_blob.c_str());
+#endif
+
+ // XXX Should each sub-schema ref be in its own category?
+ jv = find_json_object_value(schema_blob, "properties", JSMN_OBJECT);
+ if (!jv.second) {
+ ws_warning("ERROR: Interface \"%s\" has an %s configuration schema.", plugin->name().c_str(), schema_blob.c_str());
+ return false;
+ }
+ int opt_idx = OPT_SCHEMA_PROPERTIES_START;
+ jv = get_schema_properties(jv.first, opt_idx, "", plugin->name(), plugin_config.property_list);
+ if (!jv.second) {
+ ws_warning("ERROR: Interface \"%s\" has an unsupported or invalid configuration schema: %s", plugin->name().c_str(), jv.first.c_str());
+ return false;
+ }
+
+ return true;
+}
+
+// For each loaded plugin, get its name and properties.
+static bool get_source_plugins(sinsp &inspector, std::map<std::string, struct plugin_configuration> &plugin_configs) {
+ const auto plugin_manager = inspector.get_plugin_manager();
+
+ // XXX sinsp_plugin_manager::sources() can return different names, e.g. aws_cloudtrail vs cloudtrail.
+ try {
+ for (auto &plugin : plugin_manager->plugins()) {
+ if (plugin->caps() & CAP_SOURCING) {
+ plugin_configuration plugin_config = {};
+ if (!get_plugin_config_schema(plugin, plugin_config)) {
+ return false;
+ }
+ plugin_configs[plugin->name()] = plugin_config;
+ }
+ }
+ } catch (sinsp_exception &e) {
+ ws_warning("%s", e.what());
+ return false;
+ }
+ return true;
+}
+
+// Build our command line options based on our source plugins.
+static const std::vector<ws_option> get_longopts(const std::map<std::string, struct plugin_configuration> &plugin_configs) {
+ std::vector<ws_option> longopts;
+ struct ws_option base_longopts[] = {
+ EXTCAP_BASE_OPTIONS,
+ { "help", ws_no_argument, NULL, OPT_HELP},
+ { "version", ws_no_argument, NULL, OPT_VERSION},
+ { "plugin-api-version", ws_no_argument, NULL, OPT_PLUGIN_API_VERSION},
+ { "plugin-source", ws_required_argument, NULL, OPT_PLUGIN_SOURCE },
+ { 0, 0, 0, 0}
+ };
+ int idx;
+ for (idx = 0; base_longopts[idx].name; idx++) {
+ longopts.push_back(base_longopts[idx]);
+ }
+ for (const auto &it : plugin_configs) {
+ const struct plugin_configuration plugin_configs = it.second;
+ for (const auto &prop : plugin_configs.property_list) {
+ ws_option option = { g_strdup(prop.option.c_str()), ws_required_argument, NULL, prop.option_index };
+ longopts.push_back(option);
+ }
+ }
+ longopts.push_back(base_longopts[idx]);
+ return longopts;
+}
+
+// Show the configuration for a given plugin/interface.
+static int show_config(const std::string &interface, const struct plugin_configuration &plugin_config)
+{
+ unsigned arg_num = 0;
+// char* plugin_filter;
+
+ if (interface.empty()) {
+ ws_warning("ERROR: No interface specified.");
+ return EXIT_FAILURE;
+ }
+
+ printf(
+ "arg {number=%u}"
+ "{call=--plugin-source}"
+ "{display=Log data URL}"
+ "{type=string}"
+ "{tooltip=The plugin data source. This is usually a URL.}"
+ "{placeholder=Enter a source URL" UTF8_HORIZONTAL_ELLIPSIS "}"
+ "{required=true}"
+ "{group=Capture}\n",
+ arg_num++);
+// if (plugin_filter)
+// printf("{default=%s}", plugin_filter);
+ for (const auto &properties : plugin_config.property_list) {
+ if (properties.option_index < OPT_SCHEMA_PROPERTIES_START) {
+ continue;
+ }
+ std::string default_value;
+ if (!properties.default_value.empty()) {
+ default_value = "{default=" + properties.default_value + "}";
+ }
+ if (properties.option == "cloudtrail-aws-profile") {
+ print_cloudtrail_aws_profile_config(arg_num, properties.display.c_str(), properties.description.c_str());
+ } else if (properties.option == "cloudtrail-aws-region") {
+ print_cloudtrail_aws_region_config(arg_num, properties.display.c_str(), properties.description.c_str());
+ } else {
+ printf(
+ "arg {number=%d}"
+ "{call=--%s}"
+ "{display=%s}"
+ "{type=%s}"
+ "%s"
+ "{tooltip=%s}"
+ "{group=Capture}"
+ "\n",
+ arg_num, properties.option.c_str(), properties.display.c_str(), properties.type.c_str(), default_value.c_str(), properties.description.c_str());
+ if (properties.enum_values.size() > 0) {
+ for (const auto &enum_val : properties.enum_values) {
+ printf(
+ "value {arg=%d}"
+ "{value=%s}"
+ "{display=%s}"
+ "%s"
+ "\n",
+ arg_num, enum_val.c_str(), enum_val.c_str(), enum_val == default_value ? "{default=true}" : "");
+ }
+ }
+ }
+ arg_num++;
+ }
+ extcap_config_debug(&arg_num);
+
+ return EXIT_SUCCESS;
+}
+
+int main(int argc, char **argv)
+{
+ char* configuration_init_error;
+ int result;
+ int option_idx = 0;
+ int ret = EXIT_FAILURE;
+ extcap_parameters* extcap_conf = g_new0(extcap_parameters, 1);
+ std::map<std::string, struct plugin_configuration> plugin_configs;
+ char* help_url;
+ char* help_header = NULL;
+ sinsp inspector;
+ std::string plugin_source;
+
+ /* Initialize log handler early so we can have proper logging during startup. */
+ extcap_log_init("falcodump");
+
+ /*
+ * Get credential information for later use.
+ */
+ init_process_policies();
+
+ /*
+ * Attempt to get the pathname of the directory containing the
+ * executable file.
+ */
+ configuration_init_error = configuration_init(argv[0], "Logray");
+ if (configuration_init_error != NULL) {
+ ws_warning("Can't get pathname of directory containing the extcap program: %s.",
+ configuration_init_error);
+ g_free(configuration_init_error);
+ }
+
+ load_plugins(inspector);
+
+ if (!get_source_plugins(inspector, plugin_configs)) {
+ goto end;
+ }
+
+ for (auto iter = plugin_configs.begin(); iter != plugin_configs.end(); ++iter) {
+ // We don't have a Falco source plugins DLT, so use USER0 (147).
+ // Additional info available via plugin->description() and plugin->event_source().
+ extcap_base_register_interface(extcap_conf, iter->first.c_str(), "Falco plugin", 147, "USER0");
+ }
+
+ help_url = data_file_url("falcodump.html");
+ extcap_base_set_util_info(extcap_conf, argv[0], FALCODUMP_VERSION_MAJOR, FALCODUMP_VERSION_MINOR,
+ FALCODUMP_VERSION_RELEASE, help_url);
+ g_free(help_url);
+
+ help_header = ws_strdup_printf(
+ " %s --extcap-interfaces\n"
+ " %s --extcap-interface=%s --extcap-dlts\n"
+ " %s --extcap-interface=%s --extcap-config\n"
+ " %s --extcap-interface=%s --fifo=<filename> --capture --plugin-source=<source url>\n",
+ argv[0],
+ argv[0], FALCODUMP_PLUGIN_PLACEHOLDER,
+ argv[0], FALCODUMP_PLUGIN_PLACEHOLDER,
+ argv[0], FALCODUMP_PLUGIN_PLACEHOLDER);
+ extcap_help_add_header(extcap_conf, help_header);
+ g_free(help_header);
+ extcap_help_add_option(extcap_conf, "--help", "print this help");
+ extcap_help_add_option(extcap_conf, "--version", "print the version");
+ extcap_help_add_option(extcap_conf, "--plugin-api-version", "print the Falco plugin API version");
+ extcap_help_add_option(extcap_conf, "--plugin-source", "plugin source URL");
+
+ for (const auto &it : plugin_configs) {
+ const struct plugin_configuration plugin_configs = it.second;
+ for (const auto &prop : plugin_configs.property_list) {
+ if (prop.option_index < OPT_SCHEMA_PROPERTIES_START) {
+ continue;
+ }
+ extcap_help_add_option(extcap_conf, g_strdup_printf("--%s", prop.option.c_str()), g_strdup(prop.description.c_str()));
+ }
+ }
+
+ ws_opterr = 0;
+ ws_optind = 0;
+
+ if (argc == 1) {
+ extcap_help_print(extcap_conf);
+ goto end;
+ }
+
+ static const std::vector<ws_option> longopts = get_longopts(plugin_configs);
+ while ((result = ws_getopt_long(argc, argv, ":", longopts.data(), &option_idx)) != -1) {
+
+ switch (result) {
+
+ case OPT_HELP:
+ extcap_help_print(extcap_conf);
+ ret = EXIT_SUCCESS;
+ goto end;
+
+ case OPT_VERSION:
+ extcap_version_print(extcap_conf);
+ ret = EXIT_SUCCESS;
+ goto end;
+
+ case OPT_PLUGIN_API_VERSION:
+ fprintf(stdout, "Falco plugin API version %s\n", inspector.get_plugin_api_version());
+ ret = EXIT_SUCCESS;
+ goto end;
+
+ case OPT_PLUGIN_SOURCE:
+ plugin_source = ws_optarg;
+ break;
+
+ case ':':
+ /* missing option argument */
+ ws_warning("Option '%s' requires an argument", argv[ws_optind - 1]);
+ break;
+
+ default:
+ if (result >= OPT_SCHEMA_PROPERTIES_START) {
+ bool found = false;
+ for (auto &it : plugin_configs) {
+ struct plugin_configuration *plugin_config = &it.second;
+ for (auto &prop : plugin_config->property_list) {
+ if (prop.option_index == result) {
+ prop.current_value = ws_optarg;
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ break;
+ }
+ }
+ if (!found) {
+ ws_warning("Invalid option: %s", argv[ws_optind - 1]);
+ goto end;
+ }
+ } else if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg)) {
+ ws_warning("Invalid option: %s", argv[ws_optind - 1]);
+ goto end;
+ }
+ }
+ }
+
+ extcap_cmdline_debug(argv, argc);
+
+ if (plugin_configs.size() < 1) {
+ ws_warning("No source plugins found.");
+ goto end;
+ }
+
+ if (extcap_base_handle_interface(extcap_conf)) {
+ ret = EXIT_SUCCESS;
+ goto end;
+ }
+
+ if (extcap_conf->show_config) {
+ ret = show_config(extcap_conf->interface, plugin_configs.at(extcap_conf->interface));
+ goto end;
+ }
+
+ if (extcap_conf->capture) {
+ if (plugin_source.empty()) {
+ ws_warning("Missing or invalid parameter: --plugin-source");
+ goto end;
+ }
+
+ std::shared_ptr<sinsp_plugin> plugin_interface;
+ const auto plugin_manager = inspector.get_plugin_manager();
+ for (auto &plugin : plugin_manager->plugins()) {
+ if (plugin->name() == extcap_conf->interface) {
+ plugin_interface = plugin;
+ }
+ }
+
+ if (plugin_interface == nullptr) {
+ ws_warning("Unable to find interface %s", extcap_conf->interface);
+ goto end;
+ }
+
+ sinsp_dumper dumper;
+#ifdef DEBUG_SINSP
+ inspector.set_debug_mode(true);
+ inspector.set_log_stderr();
+#endif
+ try {
+ std::string init_err;
+ plugin_interface->init(plugin_configs[extcap_conf->interface].json_config().c_str(), init_err);
+ if (!init_err.empty()) {
+ ws_warning("%s", init_err.c_str());
+ goto end;
+ }
+ inspector.open_plugin(extcap_conf->interface, plugin_source);
+ // scap_dump_open handles "-"
+ dumper.open(&inspector, extcap_conf->fifo, false);
+ } catch (sinsp_exception &e) {
+ dumper.close();
+ ws_warning("%s", e.what());
+ goto end;
+ }
+ sinsp_evt *evt;
+ ws_noisy("Starting capture loop.");
+ while (!extcap_end_application) {
+ try {
+ int32_t res = inspector.next(&evt);
+ switch (res) {
+ case SCAP_TIMEOUT:
+ case SCAP_FILTERED_EVENT:
+ break;
+ case SCAP_SUCCESS:
+ dumper.dump(evt);
+ dumper.flush();
+ break;
+ default:
+ ws_noisy("Inspector exited with %d", res);
+ extcap_end_application = true;
+ break;
+ }
+ } catch (sinsp_exception &e) {
+ ws_warning("%s", e.what());
+ goto end;
+ }
+ }
+ ws_noisy("Closing dumper and inspector.");
+ dumper.close();
+ inspector.close();
+ ret = EXIT_SUCCESS;
+ } else {
+ ws_debug("You should not come here... maybe some parameter missing?");
+ }
+
+end:
+ /* clean up stuff */
+ extcap_base_cleanup(&extcap_conf);
+ return ret;
+}
diff --git a/extcap/randpktdump.c b/extcap/randpktdump.c
new file mode 100644
index 0000000..6d51835
--- /dev/null
+++ b/extcap/randpktdump.c
@@ -0,0 +1,368 @@
+/* randpktdump.c
+ * randpktdump is an extcap tool used to generate random data for testing/educational purpose
+ *
+ * Copyright 2015, Dario Lombardo
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "config.h"
+#define WS_LOG_DOMAIN "randpktdump"
+
+#include "extcap-base.h"
+
+#include "randpkt_core/randpkt_core.h"
+#include <wsutil/strtoi.h>
+#include <wsutil/filesystem.h>
+#include <wsutil/privileges.h>
+#include <wsutil/socket.h>
+#include <wsutil/please_report_bug.h>
+#include <wsutil/wslog.h>
+
+#include <cli_main.h>
+#include <wsutil/cmdarg_err.h>
+
+#define RANDPKT_EXTCAP_INTERFACE "randpkt"
+#define RANDPKTDUMP_VERSION_MAJOR "0"
+#define RANDPKTDUMP_VERSION_MINOR "1"
+#define RANDPKTDUMP_VERSION_RELEASE "0"
+
+enum {
+ EXTCAP_BASE_OPTIONS_ENUM,
+ OPT_HELP,
+ OPT_VERSION,
+ OPT_MAXBYTES,
+ OPT_COUNT,
+ OPT_DELAY,
+ OPT_RANDOM_TYPE,
+ OPT_ALL_RANDOM,
+ OPT_TYPE
+};
+
+static struct ws_option longopts[] = {
+ EXTCAP_BASE_OPTIONS,
+ { "help", ws_no_argument, NULL, OPT_HELP},
+ { "version", ws_no_argument, NULL, OPT_VERSION},
+ { "maxbytes", ws_required_argument, NULL, OPT_MAXBYTES},
+ { "count", ws_required_argument, NULL, OPT_COUNT},
+ { "delay", ws_required_argument, NULL, OPT_DELAY},
+ { "random-type", ws_no_argument, NULL, OPT_RANDOM_TYPE},
+ { "all-random", ws_no_argument, NULL, OPT_ALL_RANDOM},
+ { "type", ws_required_argument, NULL, OPT_TYPE},
+ { 0, 0, 0, 0 }
+};
+
+
+static void help(extcap_parameters* extcap_conf)
+{
+ unsigned i = 0;
+ char** abbrev_list;
+ char** longname_list;
+
+ extcap_help_print(extcap_conf);
+
+ printf("\nPacket types:\n");
+ randpkt_example_list(&abbrev_list, &longname_list);
+ while (abbrev_list[i] && longname_list[i]) {
+ printf("\t%-16s%s\n", abbrev_list[i], longname_list[i]);
+ i++;
+ }
+ printf("\n");
+ g_strfreev(abbrev_list);
+ g_strfreev(longname_list);
+}
+
+static int list_config(char *interface)
+{
+ unsigned inc = 0;
+ unsigned i = 0;
+ char** abbrev_list;
+ char** longname_list;
+
+ if (!interface) {
+ ws_warning("No interface specified.");
+ return EXIT_FAILURE;
+ }
+
+ if (g_strcmp0(interface, RANDPKT_EXTCAP_INTERFACE)) {
+ ws_warning("Interface must be %s", RANDPKT_EXTCAP_INTERFACE);
+ return EXIT_FAILURE;
+ }
+
+ printf("arg {number=%u}{call=--maxbytes}{display=Max bytes in a packet}"
+ "{type=unsigned}{range=1,5000}{default=5000}{tooltip=The max number of bytes in a packet}\n",
+ inc++);
+ printf("arg {number=%u}{call=--count}{display=Number of packets}"
+ "{type=long}{default=1000}{tooltip=Number of packets to generate}\n",
+ inc++);
+ printf("arg {number=%u}{call=--delay}{display=Packet delay (ms)}"
+ "{type=long}{default=0}{tooltip=Milliseconds to wait after writing each packet}\n",
+ inc++);
+ printf("arg {number=%u}{call=--random-type}{display=Random type}"
+ "{type=boolflag}{default=false}{tooltip=The packets type is randomly chosen}\n",
+ inc++);
+ printf("arg {number=%u}{call=--all-random}{display=All random packets}"
+ "{type=boolflag}{default=false}{tooltip=Packet type for each packet is randomly chosen}\n",
+ inc++);
+
+ /* Now the types */
+ printf("arg {number=%u}{call=--type}{display=Type of packet}"
+ "{type=selector}{tooltip=Type of packet to generate}\n",
+ inc);
+ randpkt_example_list(&abbrev_list, &longname_list);
+ while (abbrev_list[i] && longname_list[i]) {
+ printf("value {arg=%u}{value=%s}{display=%s}\n", inc, abbrev_list[i], longname_list[i]);
+ i++;
+ }
+ g_strfreev(abbrev_list);
+ g_strfreev(longname_list);
+ inc++;
+
+ extcap_config_debug(&inc);
+
+ return EXIT_SUCCESS;
+}
+
+static void randpktdump_cmdarg_err(const char *msg_format, va_list ap)
+{
+ ws_logv(LOG_DOMAIN_CAPCHILD, LOG_LEVEL_WARNING, msg_format, ap);
+}
+
+int main(int argc, char *argv[])
+{
+ char* err_msg;
+ int option_idx = 0;
+ int result;
+ uint16_t maxbytes = 5000;
+ uint64_t count = 1000;
+ uint64_t packet_delay_ms = 0;
+ int random_type = false;
+ int all_random = false;
+ char* type = NULL;
+ int produce_type = -1;
+ int file_type_subtype = WTAP_FILE_TYPE_SUBTYPE_UNKNOWN;
+ randpkt_example *example;
+ wtap_dumper* savedump;
+ int ret = EXIT_FAILURE;
+
+ extcap_parameters * extcap_conf = g_new0(extcap_parameters, 1);
+ char* help_url;
+ char* help_header = NULL;
+
+ cmdarg_err_init(randpktdump_cmdarg_err, randpktdump_cmdarg_err);
+
+ /* Initialize log handler early so we can have proper logging during startup. */
+ extcap_log_init("randpktdump");
+
+ /*
+ * Get credential information for later use.
+ */
+ init_process_policies();
+
+ /*
+ * Attempt to get the pathname of the directory containing the
+ * executable file.
+ */
+ err_msg = configuration_init(argv[0], NULL);
+ if (err_msg != NULL) {
+ ws_warning("Can't get pathname of directory containing the extcap program: %s.",
+ err_msg);
+ g_free(err_msg);
+ }
+
+ help_url = data_file_url("randpktdump.html");
+ extcap_base_set_util_info(extcap_conf, argv[0], RANDPKTDUMP_VERSION_MAJOR, RANDPKTDUMP_VERSION_MINOR,
+ RANDPKTDUMP_VERSION_RELEASE, help_url);
+ g_free(help_url);
+ extcap_base_register_interface(extcap_conf, RANDPKT_EXTCAP_INTERFACE, "Random packet generator", 147, "Generator dependent DLT");
+
+ help_header = ws_strdup_printf(
+ " %s --extcap-interfaces\n"
+ " %s --extcap-interface=%s --extcap-dlts\n"
+ " %s --extcap-interface=%s --extcap-config\n"
+ " %s --extcap-interface=%s --type dns --count 10 "
+ "--fifo=FILENAME --capture\n", argv[0], argv[0], RANDPKT_EXTCAP_INTERFACE, argv[0], RANDPKT_EXTCAP_INTERFACE,
+ argv[0], RANDPKT_EXTCAP_INTERFACE);
+ extcap_help_add_header(extcap_conf, help_header);
+ g_free(help_header);
+
+ extcap_help_add_option(extcap_conf, "--help", "print this help");
+ extcap_help_add_option(extcap_conf, "--version", "print the version");
+ extcap_help_add_option(extcap_conf, "--maxbytes <bytes>", "max bytes per pack");
+ extcap_help_add_option(extcap_conf, "--count <num>", "number of packets to generate");
+ extcap_help_add_option(extcap_conf, "--delay <ms>", "milliseconds to wait after writing each packet");
+ extcap_help_add_option(extcap_conf, "--random-type", "one random type is chosen for all packets");
+ extcap_help_add_option(extcap_conf, "--all-random", "a random type is chosen for each packet");
+ extcap_help_add_option(extcap_conf, "--type <type>", "the packet type");
+
+ if (argc == 1) {
+ help(extcap_conf);
+ goto end;
+ }
+
+ while ((result = ws_getopt_long(argc, argv, ":", longopts, &option_idx)) != -1) {
+ switch (result) {
+ case OPT_VERSION:
+ extcap_version_print(extcap_conf);
+ ret = EXIT_SUCCESS;
+ goto end;
+
+ case OPT_HELP:
+ help(extcap_conf);
+ ret = EXIT_SUCCESS;
+ goto end;
+
+ case OPT_MAXBYTES:
+ if (!ws_strtou16(ws_optarg, NULL, &maxbytes)) {
+ ws_warning("Invalid parameter maxbytes: %s (max value is %u)",
+ ws_optarg, UINT16_MAX);
+ goto end;
+ }
+ break;
+
+ case OPT_COUNT:
+ if (!ws_strtou64(ws_optarg, NULL, &count)) {
+ ws_warning("Invalid packet count: %s", ws_optarg);
+ goto end;
+ }
+ break;
+
+ case OPT_DELAY:
+ if (!ws_strtou64(ws_optarg, NULL, &packet_delay_ms)) {
+ ws_warning("Invalid packet delay: %s", ws_optarg);
+ goto end;
+ }
+ break;
+
+ case OPT_RANDOM_TYPE:
+ random_type = true;
+ break;
+
+ case OPT_ALL_RANDOM:
+ all_random = true;
+ break;
+
+ case OPT_TYPE:
+ g_free(type);
+ type = g_strdup(ws_optarg);
+ break;
+
+ case ':':
+ /* missing option argument */
+ ws_warning("Option '%s' requires an argument", argv[ws_optind - 1]);
+ break;
+
+ default:
+ /* Handle extcap specific options */
+ if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg))
+ {
+ ws_warning("Invalid option: %s", argv[ws_optind - 1]);
+ goto end;
+ }
+ }
+ }
+
+ extcap_cmdline_debug(argv, argc);
+
+ if (extcap_base_handle_interface(extcap_conf)) {
+ ret = EXIT_SUCCESS;
+ goto end;
+ }
+
+ if (extcap_conf->show_config) {
+ ret = list_config(extcap_conf->interface);
+ goto end;
+ }
+
+ /* Some sanity checks */
+ if ((random_type) && (all_random)) {
+ ws_warning("You can specify only one between: --random-type, --all-random");
+ goto end;
+ }
+
+ /* Wireshark sets the type, even when random options are selected. We don't want it */
+ if (random_type || all_random) {
+ g_free(type);
+ type = NULL;
+ }
+
+ err_msg = ws_init_sockets();
+ if (err_msg != NULL) {
+ ws_warning("ERROR: %s", err_msg);
+ g_free(err_msg);
+ ws_warning("%s", please_report_bug());
+ goto end;
+ }
+
+ if (extcap_conf->capture) {
+
+ if (g_strcmp0(extcap_conf->interface, RANDPKT_EXTCAP_INTERFACE)) {
+ ws_warning("ERROR: invalid interface");
+ goto end;
+ }
+
+ wtap_init(false);
+
+ if (file_type_subtype == WTAP_FILE_TYPE_SUBTYPE_UNKNOWN) {
+ file_type_subtype = wtap_pcapng_file_type_subtype();
+ }
+
+ if (!all_random) {
+ produce_type = randpkt_parse_type(type);
+
+ example = randpkt_find_example(produce_type);
+ if (!example)
+ goto end;
+
+ ws_debug("Generating packets: %s", example->abbrev);
+
+ randpkt_example_init(example, extcap_conf->fifo, maxbytes, file_type_subtype);
+ randpkt_loop(example, count, packet_delay_ms);
+ randpkt_example_close(example);
+ } else {
+ produce_type = randpkt_parse_type(NULL);
+ example = randpkt_find_example(produce_type);
+ if (!example)
+ goto end;
+ randpkt_example_init(example, extcap_conf->fifo, maxbytes, file_type_subtype);
+
+ while (count-- > 0) {
+ randpkt_loop(example, 1, packet_delay_ms);
+ produce_type = randpkt_parse_type(NULL);
+
+ savedump = example->dump;
+
+ example = randpkt_find_example(produce_type);
+ if (!example)
+ goto end;
+ example->dump = savedump;
+ }
+ randpkt_example_close(example);
+ }
+ ret = EXIT_SUCCESS;
+ }
+
+end:
+ /* clean up stuff */
+ g_free(type);
+ extcap_base_cleanup(&extcap_conf);
+
+ return ret;
+}
+
+/*
+ * Editor modelines - https://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 8
+ * tab-width: 8
+ * indent-tabs-mode: t
+ * End:
+ *
+ * vi: set shiftwidth=8 tabstop=8 noexpandtab:
+ * :indentSize=8:tabSize=8:noTabs=false:
+ */
diff --git a/extcap/sdjournal.c b/extcap/sdjournal.c
new file mode 100644
index 0000000..879fea8
--- /dev/null
+++ b/extcap/sdjournal.c
@@ -0,0 +1,472 @@
+/* sdjournal.c
+ * sdjournal is an extcap tool used to dump systemd journal entries.
+ *
+ * Adapted from sshdump.
+ * Copyright 2018, Gerald Combs and Dario Lombardo
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * To do:
+ * - Add an option for sd_journal_open flags, e.g. SD_JOURNAL_LOCAL_ONLY.
+ * - Add journalctl options - --boot, --machine, --directory, etc.
+ */
+
+#include "config.h"
+#define WS_LOG_DOMAIN "sdjournal"
+
+#include <extcap/extcap-base.h>
+#include <wsutil/interface.h>
+#include <wsutil/file_util.h>
+#include <wsutil/filesystem.h>
+#include <wsutil/privileges.h>
+#include <wsutil/wslog.h>
+#include <writecap/pcapio.h>
+#include <wiretap/wtap.h>
+
+#include <systemd/sd-journal.h>
+#include <systemd/sd-id128.h>
+
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+
+#define SDJOURNAL_VERSION_MAJOR "1"
+#define SDJOURNAL_VERSION_MINOR "0"
+#define SDJOURNAL_VERSION_RELEASE "0"
+
+#define SDJOURNAL_EXTCAP_INTERFACE "sdjournal"
+#define BLOCK_TYPE_SYSTEMD_JOURNAL_EXPORT 0x00000009
+
+enum {
+ EXTCAP_BASE_OPTIONS_ENUM,
+ OPT_HELP,
+ OPT_VERSION,
+ OPT_START_FROM
+};
+
+static struct ws_option longopts[] = {
+ EXTCAP_BASE_OPTIONS,
+ { "help", ws_no_argument, NULL, OPT_HELP},
+ { "version", ws_no_argument, NULL, OPT_VERSION},
+ { "start-from", ws_required_argument, NULL, OPT_START_FROM},
+ { 0, 0, 0, 0}
+};
+
+#define FLD_BOOT_ID "_BOOT_ID="
+#define FLD_BOOT_ID_LEN (8 + 1 + 33 + 1)
+
+// The Journal Export Format specification doesn't place limits on entry
+// lengths or lines per entry. We do.
+#define ENTRY_BUF_LENGTH WTAP_MAX_PACKET_SIZE_STANDARD
+#define MAX_EXPORT_ENTRY_LENGTH (ENTRY_BUF_LENGTH - 4 - 4 - 4) // Block type - total length - total length
+
+static int sdj_dump_entries(sd_journal *jnl, FILE* fp)
+{
+ int ret = EXIT_SUCCESS;
+ uint8_t *entry_buff = g_new(uint8_t, ENTRY_BUF_LENGTH);
+ int jr = 0;
+
+ /*
+ * Read journal entries and write them as packets. Output must
+ * match `journalctl --output=export`.
+ */
+ while (jr == 0) {
+ char *cursor;
+ uint64_t pkt_rt_ts, mono_ts;
+ sd_id128_t boot_id;
+ char boot_id_str[FLD_BOOT_ID_LEN] = FLD_BOOT_ID;
+ uint32_t block_type = BLOCK_TYPE_SYSTEMD_JOURNAL_EXPORT;
+ uint32_t data_end = 8; // Block type + total length
+ const void *fld_data;
+ size_t fld_len;
+ uint64_t bytes_written = 0;
+ int err;
+
+ memcpy(entry_buff, &block_type, 4);
+
+ jr = sd_journal_next(jnl);
+ ws_debug("sd_journal_next: %d", jr);
+ if (jr < 0) {
+ ws_warning("Error fetching journal entry: %s", g_strerror(jr));
+ goto end;
+ } else if (jr == 0) {
+ sd_journal_wait(jnl, (uint64_t) -1);
+ continue;
+ }
+
+ jr = sd_journal_get_cursor(jnl, &cursor);
+ if (jr < 0) {
+ ws_warning("Error fetching cursor: %s", g_strerror(jr));
+ goto end;
+ }
+ data_end += snprintf(entry_buff+data_end, MAX_EXPORT_ENTRY_LENGTH-data_end, "__CURSOR=%s\n", cursor);
+ free(cursor);
+
+ jr = sd_journal_get_realtime_usec(jnl, &pkt_rt_ts);
+ if (jr < 0) {
+ ws_warning("Error fetching realtime timestamp: %s", g_strerror(jr));
+ goto end;
+ }
+ data_end += snprintf(entry_buff+data_end, MAX_EXPORT_ENTRY_LENGTH-data_end, "__REALTIME_TIMESTAMP=%" PRIu64 "\n", pkt_rt_ts);
+
+ jr = sd_journal_get_monotonic_usec(jnl, &mono_ts, &boot_id);
+ if (jr < 0) {
+ ws_warning("Error fetching monotonic timestamp: %s", g_strerror(jr));
+ goto end;
+ }
+ sd_id128_to_string(boot_id, boot_id_str + strlen(FLD_BOOT_ID));
+ data_end += snprintf(entry_buff+data_end, MAX_EXPORT_ENTRY_LENGTH-data_end, "__MONOTONIC_TIMESTAMP=%" PRIu64 "\n%s\n", mono_ts, boot_id_str);
+ ws_debug("Entry header is %u bytes", data_end);
+
+ SD_JOURNAL_FOREACH_DATA(jnl, fld_data, fld_len) {
+ uint8_t *eq_ptr = (uint8_t *) memchr(fld_data, '=', fld_len);
+ if (!eq_ptr) {
+ ws_warning("Invalid field.");
+ goto end;
+ }
+ if (g_utf8_validate((const char *) fld_data, (ssize_t) fld_len, NULL)) {
+ // Allow for two trailing newlines, one here and one
+ // at the end of the buffer.
+ if (fld_len > MAX_EXPORT_ENTRY_LENGTH-data_end-2) {
+ ws_debug("Breaking on UTF-8 field: %u + %zd", data_end, fld_len);
+ break;
+ }
+ memcpy(entry_buff+data_end, fld_data, fld_len);
+ data_end += (uint32_t) fld_len;
+ entry_buff[data_end] = '\n';
+ data_end++;
+ } else {
+ // \n + 64-bit size + \n + trailing \n = 11
+ if (fld_len > MAX_EXPORT_ENTRY_LENGTH-data_end-11) {
+ ws_debug("Breaking on binary field: %u + %zd", data_end, fld_len);
+ break;
+ }
+ ptrdiff_t name_len = eq_ptr - (const uint8_t *) fld_data;
+ uint64_t le_data_len;
+ le_data_len = htole64(fld_len - name_len - 1);
+ memcpy(entry_buff+data_end, fld_data, name_len);
+ data_end+= name_len;
+ entry_buff[data_end] = '\n';
+ data_end++;
+ memcpy(entry_buff+data_end, &le_data_len, 8);
+ data_end += 8;
+ memcpy(entry_buff+data_end, (const uint8_t *) fld_data + name_len + 1, fld_len - name_len);
+ data_end += fld_len - name_len;
+ }
+ }
+
+ if (data_end % 4) {
+ size_t pad_len = 4 - (data_end % 4);
+ memset(entry_buff+data_end, '\0', pad_len);
+ data_end += pad_len;
+ }
+
+ uint32_t total_len = data_end + 4;
+ memcpy (entry_buff+4, &total_len, 4);
+ memcpy (entry_buff+data_end, &total_len, 4);
+
+ ws_debug("Attempting to write %u bytes", total_len);
+ if (!pcapng_write_block(fp, entry_buff, total_len, &bytes_written, &err)) {
+ ws_warning("Can't write event: %s", strerror(err));
+ ret = EXIT_FAILURE;
+ break;
+ }
+
+ fflush(fp);
+ }
+
+end:
+ g_free(entry_buff);
+ return ret;
+}
+
+static int sdj_start_export(const int start_from_entries, const bool start_from_end, const char* fifo)
+{
+ FILE* fp = stdout;
+ uint64_t bytes_written = 0;
+ int err;
+ sd_journal *jnl = NULL;
+ sd_id128_t boot_id;
+ char boot_id_str[FLD_BOOT_ID_LEN] = FLD_BOOT_ID;
+ int ret = EXIT_FAILURE;
+ char* err_info = NULL;
+ char *appname;
+ bool success;
+ int jr = 0;
+
+ if (g_strcmp0(fifo, "-")) {
+ /* Open or create the output file */
+ fp = fopen(fifo, "wb");
+ if (fp == NULL) {
+ ws_warning("Error creating output file: %s (%s)", fifo, g_strerror(errno));
+ return EXIT_FAILURE;
+ }
+ }
+
+
+ appname = ws_strdup_printf(SDJOURNAL_EXTCAP_INTERFACE " (Wireshark) %s.%s.%s",
+ SDJOURNAL_VERSION_MAJOR, SDJOURNAL_VERSION_MINOR, SDJOURNAL_VERSION_RELEASE);
+ success = pcapng_write_section_header_block(fp,
+ NULL, /* Comment */
+ NULL, /* HW */
+ NULL, /* OS */
+ appname,
+ -1, /* section_length */
+ &bytes_written,
+ &err);
+ g_free(appname);
+
+ if (!success) {
+ ws_warning("Can't write pcapng file header");
+ goto cleanup;
+ }
+
+ jr = sd_journal_open(&jnl, 0);
+ if (jr < 0) {
+ ws_warning("Error opening journal: %s", g_strerror(jr));
+ goto cleanup;
+ }
+
+ jr = sd_id128_get_boot(&boot_id);
+ if (jr < 0) {
+ ws_warning("Error fetching system boot ID: %s", g_strerror(jr));
+ goto cleanup;
+ }
+
+ sd_id128_to_string(boot_id, boot_id_str + strlen(FLD_BOOT_ID));
+ jr = sd_journal_add_match(jnl, boot_id_str, strlen(boot_id_str));
+ if (jr < 0) {
+ ws_warning("Error adding match: %s", g_strerror(jr));
+ goto cleanup;
+ }
+
+ // According to the documentation, fields *might be* truncated to 64K.
+ // Let's assume that 2048 is a good balance between fetching entire fields
+ // and being able to fit as many fields as possible into a packet.
+ sd_journal_set_data_threshold(jnl, 2048);
+
+ if (start_from_end) {
+ ws_debug("Attempting to seek %d entries from the end", start_from_entries);
+ jr = sd_journal_seek_tail(jnl);
+ if (jr < 0) {
+ ws_warning("Error starting at end: %s", g_strerror(jr));
+ goto cleanup;
+ }
+ jr = sd_journal_previous_skip(jnl, (uint64_t) start_from_entries + 1);
+ if (jr < 0) {
+ ws_warning("Error skipping backward: %s", g_strerror(jr));
+ goto cleanup;
+ }
+ } else {
+ ws_debug("Attempting to seek %d entries from the beginning", start_from_entries);
+ jr = sd_journal_seek_head(jnl);
+ if (jr < 0) {
+ ws_warning("Error starting at beginning: %s", g_strerror(jr));
+ goto cleanup;
+ }
+ if (start_from_entries > 0) {
+ jr = sd_journal_next_skip(jnl, (uint64_t) start_from_entries);
+ if (jr < 0) {
+ ws_warning("Error skipping forward: %s", g_strerror(jr));
+ goto cleanup;
+ }
+ }
+ }
+
+ /* read from channel and write into fp */
+ if (sdj_dump_entries(jnl, fp) != 0) {
+ ws_warning("Error dumping entries");
+ goto cleanup;
+ }
+
+ ret = EXIT_SUCCESS;
+
+cleanup:
+ if (jnl) {
+ sd_journal_close(jnl);
+ }
+
+ if (err_info) {
+ ws_warning("%s", err_info);
+ }
+
+ g_free(err_info);
+
+ /* clean up and exit */
+ if (g_strcmp0(fifo, "-")) {
+ fclose(fp);
+ }
+ return ret;
+}
+
+static int list_config(char *interface)
+{
+ unsigned inc = 0;
+
+ if (!interface) {
+ ws_warning("ERROR: No interface specified.");
+ return EXIT_FAILURE;
+ }
+
+ if (g_strcmp0(interface, SDJOURNAL_EXTCAP_INTERFACE)) {
+ ws_warning("ERROR: interface must be %s", SDJOURNAL_EXTCAP_INTERFACE);
+ return EXIT_FAILURE;
+ }
+
+ printf("arg {number=%u}{call=--start-from}{display=Starting position}"
+ "{type=string}{tooltip=The journal starting position. Values "
+ "with a leading \"+\" start from the beginning, similar to the "
+ "\"tail\" command}{required=false}{group=Journal}\n", inc++);
+
+ extcap_config_debug(&inc);
+
+ return EXIT_SUCCESS;
+}
+
+int main(int argc, char **argv)
+{
+ char* configuration_init_error;
+ int result;
+ int option_idx = 0;
+ int start_from_entries = 10;
+ bool start_from_end = true;
+ int ret = EXIT_FAILURE;
+ extcap_parameters* extcap_conf = g_new0(extcap_parameters, 1);
+ char* help_url;
+ char* help_header = NULL;
+
+ /* Initialize log handler early so we can have proper logging during startup. */
+ extcap_log_init("sdjournal");
+
+ /*
+ * Get credential information for later use.
+ */
+ init_process_policies();
+
+ /*
+ * Attempt to get the pathname of the directory containing the
+ * executable file.
+ */
+ configuration_init_error = configuration_init(argv[0], NULL);
+ if (configuration_init_error != NULL) {
+ ws_warning("Can't get pathname of directory containing the extcap program: %s.",
+ configuration_init_error);
+ g_free(configuration_init_error);
+ }
+
+ help_url = data_file_url("sdjournal.html");
+ extcap_base_set_util_info(extcap_conf, argv[0], SDJOURNAL_VERSION_MAJOR, SDJOURNAL_VERSION_MINOR,
+ SDJOURNAL_VERSION_RELEASE, help_url);
+ g_free(help_url);
+ // We don't have an SDJOURNAL DLT, so use USER0 (147).
+ extcap_base_register_interface(extcap_conf, SDJOURNAL_EXTCAP_INTERFACE, "systemd Journal Export", 147, "USER0");
+
+ help_header = ws_strdup_printf(
+ " %s --extcap-interfaces\n"
+ " %s --extcap-interface=%s --extcap-dlts\n"
+ " %s --extcap-interface=%s --extcap-config\n"
+ " %s --extcap-interface=%s --start-from=+0 --fifo=FILENAME --capture\n",
+ argv[0],
+ argv[0], SDJOURNAL_EXTCAP_INTERFACE,
+ argv[0], SDJOURNAL_EXTCAP_INTERFACE,
+ argv[0], SDJOURNAL_EXTCAP_INTERFACE);
+ extcap_help_add_header(extcap_conf, help_header);
+ g_free(help_header);
+ extcap_help_add_option(extcap_conf, "--help", "print this help");
+ extcap_help_add_option(extcap_conf, "--version", "print the version");
+ extcap_help_add_option(extcap_conf, "--start-from <entry count>", "starting position");
+
+ ws_opterr = 0;
+ ws_optind = 0;
+
+ if (argc == 1) {
+ extcap_help_print(extcap_conf);
+ goto end;
+ }
+
+ while ((result = ws_getopt_long(argc, argv, ":", longopts, &option_idx)) != -1) {
+
+ switch (result) {
+
+ case OPT_HELP:
+ extcap_help_print(extcap_conf);
+ ret = EXIT_SUCCESS;
+ goto end;
+
+ case OPT_VERSION:
+ extcap_version_print(extcap_conf);
+ ret = EXIT_SUCCESS;
+ goto end;
+
+ case OPT_START_FROM:
+ start_from_entries = (int) strtol(ws_optarg, NULL, 10);
+ if (errno == EINVAL) {
+ ws_warning("Invalid entry count: %s", ws_optarg);
+ goto end;
+ }
+ if (strlen(ws_optarg) > 0 && ws_optarg[0] == '+') {
+ start_from_end = false;
+ }
+ if (start_from_entries < 0) {
+ start_from_end = true;
+ start_from_entries *= -1;
+ }
+ ws_debug("start %d from %s", start_from_entries, start_from_end ? "end" : "beginning");
+ break;
+
+ case ':':
+ /* missing option argument */
+ ws_warning("Option '%s' requires an argument", argv[ws_optind - 1]);
+ break;
+
+ default:
+ if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg)) {
+ ws_warning("Invalid option: %s", argv[ws_optind - 1]);
+ goto end;
+ }
+ }
+ }
+
+ extcap_cmdline_debug(argv, argc);
+
+ if (extcap_base_handle_interface(extcap_conf)) {
+ ret = EXIT_SUCCESS;
+ goto end;
+ }
+
+ if (extcap_conf->show_config) {
+ ret = list_config(extcap_conf->interface);
+ goto end;
+ }
+
+ if (extcap_conf->capture) {
+ ret = sdj_start_export(start_from_entries, start_from_end, extcap_conf->fifo);
+ } else {
+ ws_debug("You should not come here... maybe some parameter missing?");
+ ret = EXIT_FAILURE;
+ }
+
+end:
+ /* clean up stuff */
+ extcap_base_cleanup(&extcap_conf);
+ return ret;
+}
+
+/*
+ * Editor modelines - https://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 8
+ * tab-width: 8
+ * indent-tabs-mode: t
+ * End:
+ *
+ * vi: set shiftwidth=8 tabstop=8 noexpandtab:
+ * :indentSize=8:tabSize=8:noTabs=false:
+ */
diff --git a/extcap/ssh-base.c b/extcap/ssh-base.c
new file mode 100644
index 0000000..124b825
--- /dev/null
+++ b/extcap/ssh-base.c
@@ -0,0 +1,226 @@
+/* ssh-base.c
+ * ssh-base has base utility functions to connect to hosts via ssh
+ *
+ * Copyright 2016, Dario Lombardo
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "config.h"
+#define WS_LOG_DOMAIN LOG_DOMAIN_EXTCAP
+
+#include "ssh-base.h"
+
+#include <extcap/extcap-base.h>
+#include <string.h>
+#include <libssh/callbacks.h>
+#include <ws_attributes.h>
+#include <wsutil/wslog.h>
+
+static void extcap_log(int priority _U_, const char *function, const char *buffer, void *userdata _U_)
+{
+ ws_debug("[%s] %s", function, buffer);
+}
+
+void add_libssh_info(extcap_parameters * extcap_conf)
+{
+ extcap_base_set_compiled_with(extcap_conf, "libssh version %s", SSH_STRINGIFY(LIBSSH_VERSION));
+ extcap_base_set_running_with(extcap_conf, "libssh version %s", ssh_version(0));
+}
+
+ssh_session create_ssh_connection(const ssh_params_t* ssh_params, char** err_info)
+{
+ ssh_session sshs;
+ char* username = NULL;
+ unsigned port;
+
+ /* Open session and set options */
+ sshs = ssh_new();
+ if (sshs == NULL) {
+ *err_info = g_strdup("Can't create ssh session");
+ return NULL;
+ }
+
+ if (!ssh_params->host) {
+ *err_info = g_strdup("Hostname needed");
+ goto failure;
+ }
+
+ if (ssh_options_set(sshs, SSH_OPTIONS_HOST, ssh_params->host)) {
+ *err_info = ws_strdup_printf("Can't set the host: %s", ssh_params->host);
+ goto failure;
+ }
+
+ /* Load the configurations already present in the system configuration file. */
+ /* They will be overwritten by the user-provided configurations. */
+ if (ssh_options_parse_config(sshs, NULL) != 0) {
+ *err_info = g_strdup("Unable to load the configuration file");
+ goto failure;
+ }
+
+ if (ssh_params->debug) {
+ int debug_level = SSH_LOG_INFO;
+ ssh_options_set(sshs, SSH_OPTIONS_LOG_VERBOSITY, &debug_level);
+ ssh_set_log_callback(extcap_log);
+ }
+
+ if (ssh_params->port != 0) {
+ port = ssh_params->port;
+ if (ssh_options_set(sshs, SSH_OPTIONS_PORT, &port)) {
+ *err_info = ws_strdup_printf("Can't set the port: %u", port);
+ goto failure;
+ }
+ }
+
+ if (ssh_params->proxycommand) {
+ if (ssh_options_set(sshs, SSH_OPTIONS_PROXYCOMMAND, ssh_params->proxycommand)) {
+ *err_info = ws_strdup_printf("Can't set the ProxyCommand: %s", ssh_params->proxycommand);
+ goto failure;
+ }
+ }
+
+ if (ssh_params->username) {
+ if (ssh_options_set(sshs, SSH_OPTIONS_USER, ssh_params->username)) {
+ *err_info = ws_strdup_printf("Can't set the username: %s", ssh_params->username);
+ goto failure;
+ }
+ }
+
+ ssh_options_get(sshs, SSH_OPTIONS_USER, &username);
+ ssh_options_get_port(sshs, &port);
+
+ ws_log(LOG_DOMAIN_CAPCHILD, LOG_LEVEL_INFO, "Opening ssh connection to %s@%s:%u", username,
+ ssh_params->host, port);
+
+ ssh_string_free_char(username);
+
+ /* Connect to server */
+ if (ssh_connect(sshs) != SSH_OK) {
+ *err_info = ws_strdup_printf("Connection error: %s", ssh_get_error(sshs));
+ goto failure;
+ }
+
+ /* If a public key path has been provided, try to authenticate using it */
+ if (ssh_params->sshkey_path) {
+ ssh_key pkey = ssh_key_new();
+ int ret;
+
+ ws_info("Connecting using public key in %s...", ssh_params->sshkey_path);
+ ret = ssh_pki_import_privkey_file(ssh_params->sshkey_path, ssh_params->sshkey_passphrase, NULL, NULL, &pkey);
+
+ if (ret == SSH_OK) {
+ if (ssh_userauth_publickey(sshs, NULL, pkey) == SSH_AUTH_SUCCESS) {
+ ws_info("done");
+ ssh_key_free(pkey);
+ return sshs;
+ }
+ }
+ ssh_key_free(pkey);
+ ws_info("failed (%s)", ssh_get_error(sshs));
+ }
+
+ /* Workaround: it may happen that libssh closes socket in meantime and any next ssh_ call fails so we should detect it in advance */
+ if (ssh_get_fd(sshs) != (socket_t)-1) {
+ /* If a password has been provided and all previous attempts failed, try to use it */
+ if (ssh_params->password) {
+ ws_info("Connecting using password...");
+ if (ssh_userauth_password(sshs, ssh_params->username, ssh_params->password) == SSH_AUTH_SUCCESS) {
+ ws_info("done");
+ return sshs;
+ }
+ ws_info("failed");
+ }
+ } else {
+ ws_info("ssh connection closed before password authentication");
+ }
+
+ /* Workaround: it may happen that libssh closes socket in meantime and any next ssh_ call fails so we should detect it in advance */
+ if (ssh_get_fd(sshs) != (socket_t)-1) {
+ /* Try to authenticate using standard public key */
+ ws_info("Connecting using standard public key...");
+ if (ssh_userauth_publickey_auto(sshs, NULL, NULL) == SSH_AUTH_SUCCESS) {
+ ws_info("done");
+ return sshs;
+ }
+ ws_info("failed");
+ } else {
+ ws_info("ssh connection closed before public key authentication");
+ }
+
+ *err_info = ws_strdup_printf("Can't find a valid authentication. Disconnecting.");
+
+ /* All authentication failed. Disconnect and return */
+ ssh_disconnect(sshs);
+
+failure:
+ ssh_free(sshs);
+ return NULL;
+}
+
+int ssh_channel_printf(ssh_channel channel, const char* fmt, ...)
+{
+ char* buf;
+ va_list arg;
+ int ret = EXIT_SUCCESS;
+
+ va_start(arg, fmt);
+ buf = ws_strdup_vprintf(fmt, arg);
+ ws_debug("%s", buf);
+ if (ssh_channel_write(channel, buf, (uint32_t)strlen(buf)) == SSH_ERROR)
+ ret = EXIT_FAILURE;
+ va_end(arg);
+ g_free(buf);
+
+ return ret;
+}
+
+void ssh_cleanup(ssh_session* sshs, ssh_channel* channel)
+{
+ if (*channel) {
+ ssh_channel_send_eof(*channel);
+ ssh_channel_close(*channel);
+ ssh_channel_free(*channel);
+ *channel = NULL;
+ }
+
+ if (*sshs) {
+ ssh_disconnect(*sshs);
+ ssh_free(*sshs);
+ *sshs = NULL;
+ }
+}
+
+ssh_params_t* ssh_params_new(void)
+{
+ return g_new0(ssh_params_t, 1);
+}
+
+void ssh_params_free(ssh_params_t* ssh_params)
+{
+ if (!ssh_params)
+ return;
+ g_free(ssh_params->host);
+ g_free(ssh_params->username);
+ g_free(ssh_params->password);
+ g_free(ssh_params->sshkey_path);
+ g_free(ssh_params->sshkey_passphrase);
+ g_free(ssh_params->proxycommand);
+ g_free(ssh_params);
+}
+
+/*
+ * Editor modelines - https://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 8
+ * tab-width: 8
+ * indent-tabs-mode: t
+ * End:
+ *
+ * vi: set shiftwidth=8 tabstop=8 noexpandtab:
+ * :indentSize=8:tabSize=8:noTabs=false:
+ */
diff --git a/extcap/ssh-base.h b/extcap/ssh-base.h
new file mode 100644
index 0000000..8283bff
--- /dev/null
+++ b/extcap/ssh-base.h
@@ -0,0 +1,85 @@
+/** @file
+ *
+ * ssh-base has base utility functions to connect to hosts via ssh
+ *
+ * Copyright 2016, Dario Lombardo
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef __SSHBASE_H__
+#define __SSHBASE_H__
+
+#include <libssh/libssh.h>
+
+#include <glib.h>
+
+#include <extcap/extcap-base.h>
+
+#ifndef STDERR_FILENO
+#define STDERR_FILENO 2
+#endif
+
+#ifndef STDOUT_FILENO
+#define STDOUT_FILENO 1
+#endif
+
+#define SSH_BASE_OPTIONS \
+ { "remote-host", ws_required_argument, NULL, OPT_REMOTE_HOST}, \
+ { "remote-port", ws_required_argument, NULL, OPT_REMOTE_PORT}, \
+ { "remote-username", ws_required_argument, NULL, OPT_REMOTE_USERNAME}, \
+ { "remote-password", ws_required_argument, NULL, OPT_REMOTE_PASSWORD}, \
+ { "remote-interface", ws_required_argument, NULL, OPT_REMOTE_INTERFACE}, \
+ { "remote-filter", ws_required_argument, NULL, OPT_REMOTE_FILTER}, \
+ { "remote-count", ws_required_argument, NULL, OPT_REMOTE_COUNT}, \
+ { "sshkey", ws_required_argument, NULL, OPT_SSHKEY}, \
+ { "sshkey-passphrase", ws_required_argument, NULL, OPT_SSHKEY_PASSPHRASE}, \
+ { "proxycommand", ws_required_argument, NULL, OPT_PROXYCOMMAND}
+
+typedef struct _ssh_params {
+ char* host;
+ uint16_t port;
+ char* username;
+ char* password;
+ char* sshkey_path;
+ char* sshkey_passphrase;
+ char* proxycommand;
+ bool debug;
+} ssh_params_t;
+
+/* Add libssh version information to an extcap_parameters structure */
+void add_libssh_info(extcap_parameters * extcap_conf);
+
+/* Create a ssh connection using all the possible authentication menthods */
+ssh_session create_ssh_connection(const ssh_params_t* ssh_params, char** err_info);
+
+/* Write a formatted message in the channel */
+int ssh_channel_printf(ssh_channel channel, const char* fmt, ...);
+
+/* Clean the current ssh session and channel. */
+void ssh_cleanup(ssh_session* sshs, ssh_channel* channel);
+
+/* Init the ssh_params_t structure */
+ssh_params_t* ssh_params_new(void);
+
+/* Clean the ssh params */
+void ssh_params_free(ssh_params_t* ssh_params);
+
+#endif
+
+/*
+ * Editor modelines - https://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 8
+ * tab-width: 8
+ * indent-tabs-mode: t
+ * End:
+ *
+ * vi: set shiftwidth=8 tabstop=8 noexpandtab:
+ * :indentSize=8:tabSize=8:noTabs=false:
+ */
diff --git a/extcap/sshdump.c b/extcap/sshdump.c
new file mode 100644
index 0000000..1286473
--- /dev/null
+++ b/extcap/sshdump.c
@@ -0,0 +1,694 @@
+/* sshdump.c
+ * sshdump is extcap tool used to capture data using a remote ssh host
+ *
+ * Copyright 2015, Dario Lombardo
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "config.h"
+#define WS_LOG_DOMAIN "sshdump"
+
+#include <extcap/extcap-base.h>
+#include <extcap/ssh-base.h>
+#include <wsutil/interface.h>
+#include <wsutil/file_util.h>
+#include <wsutil/strtoi.h>
+#include <wsutil/filesystem.h>
+#include <wsutil/privileges.h>
+#include <wsutil/please_report_bug.h>
+#include <wsutil/wslog.h>
+
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include <cli_main.h>
+
+static char* sshdump_extcap_interface;
+#ifdef _WIN32
+#define DEFAULT_SSHDUMP_EXTCAP_INTERFACE "sshdump.exe"
+#else
+#define DEFAULT_SSHDUMP_EXTCAP_INTERFACE "sshdump"
+#endif
+
+#define SSHDUMP_VERSION_MAJOR "1"
+#define SSHDUMP_VERSION_MINOR "2"
+#define SSHDUMP_VERSION_RELEASE "0"
+
+#define SSH_READ_BLOCK_SIZE 256
+
+enum {
+ EXTCAP_BASE_OPTIONS_ENUM,
+ OPT_HELP,
+ OPT_VERSION,
+ OPT_REMOTE_HOST,
+ OPT_REMOTE_PORT,
+ OPT_REMOTE_USERNAME,
+ OPT_REMOTE_PASSWORD,
+ OPT_REMOTE_INTERFACE,
+ OPT_REMOTE_CAPTURE_COMMAND_SELECT,
+ OPT_REMOTE_CAPTURE_COMMAND,
+ OPT_REMOTE_FILTER,
+ OPT_SSHKEY,
+ OPT_SSHKEY_PASSPHRASE,
+ OPT_PROXYCOMMAND,
+ OPT_REMOTE_COUNT,
+ OPT_REMOTE_SUDO, // Deprecated
+ OPT_REMOTE_PRIV,
+ OPT_REMOTE_PRIV_USER,
+ OPT_REMOTE_NOPROM
+};
+
+static struct ws_option longopts[] = {
+ EXTCAP_BASE_OPTIONS,
+ { "help", ws_no_argument, NULL, OPT_HELP},
+ { "version", ws_no_argument, NULL, OPT_VERSION},
+ SSH_BASE_OPTIONS,
+ { "remote-capture-command-select", ws_required_argument, NULL, OPT_REMOTE_CAPTURE_COMMAND_SELECT},
+ { "remote-capture-command", ws_required_argument, NULL, OPT_REMOTE_CAPTURE_COMMAND},
+ { "remote-sudo", ws_no_argument, NULL, OPT_REMOTE_SUDO }, // Deprecated
+ { "remote-priv", ws_required_argument, NULL, OPT_REMOTE_PRIV },
+ { "remote-priv-user", ws_required_argument, NULL, OPT_REMOTE_PRIV_USER },
+ { "remote-noprom", ws_no_argument, NULL, OPT_REMOTE_NOPROM },
+ { 0, 0, 0, 0}
+};
+
+static char* interfaces_list_to_filter(GSList* if_list, unsigned int remote_port);
+
+static int ssh_loop_read(ssh_channel channel, FILE* fp)
+{
+ int nbytes;
+ int ret = EXIT_SUCCESS;
+ char buffer[SSH_READ_BLOCK_SIZE];
+
+ /* read from stdin until data are available */
+ while (ssh_channel_is_open(channel) && !ssh_channel_is_eof(channel)) {
+ nbytes = ssh_channel_read(channel, buffer, SSH_READ_BLOCK_SIZE, 0);
+ if (nbytes < 0) {
+ ws_warning("Error reading from channel");
+ goto end;
+ }
+ if (nbytes == 0) {
+ break;
+ }
+ if (fwrite(buffer, 1, nbytes, fp) != (unsigned)nbytes) {
+ ws_warning("Error writing to fifo");
+ ret = EXIT_FAILURE;
+ goto end;
+ }
+ fflush(fp);
+ }
+
+ /* read loop finished... maybe something wrong happened. Read from stderr */
+ while (ssh_channel_is_open(channel) && !ssh_channel_is_eof(channel)) {
+ nbytes = ssh_channel_read(channel, buffer, SSH_READ_BLOCK_SIZE, 1);
+ if (nbytes < 0) {
+ ws_warning("Error reading from channel");
+ goto end;
+ }
+ if (fwrite(buffer, 1, nbytes, stderr) != (unsigned)nbytes) {
+ ws_warning("Error writing to stderr");
+ break;
+ }
+ }
+
+end:
+ if (ssh_channel_send_eof(channel) != SSH_OK) {
+ ws_warning("Error sending EOF in ssh channel");
+ ret = EXIT_FAILURE;
+ }
+ return ret;
+}
+
+static char* local_interfaces_to_filter(const uint16_t remote_port)
+{
+ GSList* interfaces = local_interfaces_to_list();
+ char* filter = interfaces_list_to_filter(interfaces, remote_port);
+ g_slist_free_full(interfaces, g_free);
+ return filter;
+}
+
+static ssh_channel run_ssh_command(ssh_session sshs, const char* capture_command_select,
+ const char* capture_command, const char* privilege, bool noprom,
+ const char* iface, const char* cfilter, const uint32_t count)
+{
+ char* cmdline = NULL;
+ ssh_channel channel;
+ char** ifaces_array = NULL;
+ int ifaces_array_num = 0;
+ GString *ifaces_string;
+ char *ifaces = NULL;
+ char* quoted_iface = NULL;
+ char* quoted_filter = NULL;
+ char* count_str = NULL;
+ unsigned int remote_port = 22;
+
+ channel = ssh_channel_new(sshs);
+ if (!channel) {
+ ws_warning("Can't create channel");
+ return NULL;
+ }
+
+ if (ssh_channel_open_session(channel) != SSH_OK) {
+ ws_warning("Can't open session");
+ ssh_channel_free(channel);
+ return NULL;
+ }
+
+ ssh_options_get_port(sshs, &remote_port);
+
+ if (capture_command_select == NULL || !g_strcmp0(capture_command_select, "other")) {
+ if (capture_command && *capture_command) {
+ cmdline = g_strdup(capture_command);
+ ws_debug("Remote capture command has disabled other options");
+ } else {
+ capture_command_select = "tcpdump";
+ }
+ }
+
+ /* escape parameters to go save with the shell */
+ if (!g_strcmp0(capture_command_select, "tcpdump")) {
+ quoted_iface = iface ? g_shell_quote(iface) : NULL;
+ quoted_filter = g_shell_quote(cfilter ? cfilter : "");
+ if (count > 0)
+ count_str = ws_strdup_printf("-c %u", count);
+
+ cmdline = ws_strdup_printf("%s tcpdump -U %s%s %s -w - %s %s",
+ privilege,
+ quoted_iface ? "-i " : "",
+ quoted_iface ? quoted_iface : "",
+ noprom ? "-p" : "",
+ count_str ? count_str : "",
+ quoted_filter);
+ } else if (!g_strcmp0(capture_command_select, "dumpcap")) {
+ if (iface) {
+ ifaces_array = g_strsplit(iface, " ", -1);
+ ifaces_string = g_string_new(NULL);
+ while (ifaces_array[ifaces_array_num])
+ {
+ quoted_iface = g_shell_quote(ifaces_array[ifaces_array_num]);
+ g_string_append_printf(ifaces_string, "-i %s ", quoted_iface);
+ ifaces_array_num++;
+ }
+ ifaces = g_string_free(ifaces_string, false);
+ }
+ quoted_filter = g_shell_quote(cfilter ? cfilter : "");
+ if (count > 0)
+ count_str = ws_strdup_printf("-c %u", count);
+
+ cmdline = ws_strdup_printf("%s dumpcap %s %s -w - %s -f %s",
+ privilege,
+ noprom ? "-p" : "",
+ ifaces ? ifaces : "",
+ count_str ? count_str : "",
+ quoted_filter);
+
+ g_free(ifaces);
+ g_strfreev(ifaces_array);
+ }
+
+ ws_debug("Running: %s", cmdline);
+ if (ssh_channel_request_exec(channel, cmdline) != SSH_OK) {
+ ws_warning("Can't request exec");
+ ssh_channel_close(channel);
+ ssh_channel_free(channel);
+ channel = NULL;
+ }
+
+ g_free(quoted_iface);
+ g_free(quoted_filter);
+ g_free(cmdline);
+ g_free(count_str);
+
+ return channel;
+}
+
+static int ssh_open_remote_connection(const ssh_params_t* params, const char* iface, const char* cfilter,
+ const char* capture_command_select, const char* capture_command, const char* privilege,
+ bool noprom, const uint32_t count, const char* fifo)
+{
+ ssh_session sshs = NULL;
+ ssh_channel channel = NULL;
+ FILE* fp = stdout;
+ int ret = EXIT_FAILURE;
+ char* err_info = NULL;
+
+ if (g_strcmp0(fifo, "-")) {
+ /* Open or create the output file */
+ fp = fopen(fifo, "wb");
+ if (fp == NULL) {
+ ws_warning("Error creating output file: %s (%s)", fifo, g_strerror(errno));
+ return EXIT_FAILURE;
+ }
+ }
+
+ sshs = create_ssh_connection(params, &err_info);
+
+ if (!sshs) {
+ ws_warning("Error creating connection.");
+ goto cleanup;
+ }
+
+ channel = run_ssh_command(sshs, capture_command_select, capture_command, privilege, noprom, iface, cfilter, count);
+
+ if (!channel) {
+ ws_warning("Can't run ssh command.");
+ goto cleanup;
+ }
+
+ /* read from channel and write into fp */
+ if (ssh_loop_read(channel, fp) != EXIT_SUCCESS) {
+ ws_warning("Error in read loop.");
+ ret = EXIT_FAILURE;
+ goto cleanup;
+ }
+
+ ret = EXIT_SUCCESS;
+cleanup:
+ if (err_info)
+ ws_warning("%s", err_info);
+ g_free(err_info);
+
+ /* clean up and exit */
+ ssh_cleanup(&sshs, &channel);
+
+ if (g_strcmp0(fifo, "-"))
+ fclose(fp);
+ return ret;
+}
+
+static char* interfaces_list_to_filter(GSList* interfaces, unsigned int remote_port)
+{
+ GString* filter = g_string_new(NULL);
+ GSList* cur;
+
+ // If no port is given, assume the default one. This might not be
+ // correct if the port is looked up from the ssh config file, but it is
+ // better than nothing.
+ if (remote_port == 0) {
+ remote_port = 22;
+ }
+
+ if (!interfaces) {
+ g_string_append_printf(filter, "not port %u", remote_port);
+ } else {
+ g_string_append_printf(filter, "not ((host %s", (char*)interfaces->data);
+ cur = g_slist_next(interfaces);
+ while (cur) {
+ g_string_append_printf(filter, " or host %s", (char*)cur->data);
+ cur = g_slist_next(cur);
+ }
+ g_string_append_printf(filter, ") and port %u)", remote_port);
+ }
+ return g_string_free(filter, false);
+}
+
+static int list_config(char *interface, unsigned int remote_port)
+{
+ unsigned inc = 0;
+ char* ipfilter;
+
+ if (!interface) {
+ ws_warning("ERROR: No interface specified.");
+ return EXIT_FAILURE;
+ }
+
+ if (g_strcmp0(interface, sshdump_extcap_interface)) {
+ ws_warning("ERROR: interface must be %s", sshdump_extcap_interface);
+ return EXIT_FAILURE;
+ }
+
+ ipfilter = local_interfaces_to_filter(remote_port);
+
+ printf("arg {number=%u}{call=--remote-host}{display=Remote SSH server address}"
+ "{type=string}{tooltip=The remote SSH host. It can be both "
+ "an IP address or a hostname}{required=true}{group=Server}\n", inc++);
+ printf("arg {number=%u}{call=--remote-port}{display=Remote SSH server port}"
+ "{type=unsigned}{default=22}{tooltip=The remote SSH host port (1-65535)}"
+ "{range=1,65535}{group=Server}\n", inc++);
+ printf("arg {number=%u}{call=--remote-username}{display=Remote SSH server username}"
+ "{type=string}{tooltip=The remote SSH username. If not provided, "
+ "the current user will be used}{group=Authentication}\n", inc++);
+ printf("arg {number=%u}{call=--remote-password}{display=Remote SSH server password}"
+ "{type=password}{tooltip=The SSH password, used when other methods (SSH agent "
+ "or key files) are unavailable.}{group=Authentication}\n", inc++);
+ printf("arg {number=%u}{call=--sshkey}{display=Path to SSH private key}"
+ "{type=fileselect}{tooltip=The path on the local filesystem of the private SSH key (OpenSSH format)}"
+ "{mustexist=true}{group=Authentication}\n", inc++);
+ printf("arg {number=%u}{call=--sshkey-passphrase}{display=SSH key passphrase}"
+ "{type=password}{tooltip=Passphrase to unlock the SSH private key}{group=Authentication}\n",
+ inc++);
+ printf("arg {number=%u}{call=--proxycommand}{display=ProxyCommand}"
+ "{type=string}{tooltip=The command to use as proxy for the SSH connection}"
+ "{group=Authentication}\n", inc++);
+ printf("arg {number=%u}{call=--remote-interface}{display=Remote interface}"
+ "{type=string}{tooltip=The remote network interface used for capture"
+ "}{group=Capture}\n", inc++);
+ printf("arg {number=%u}{call=--remote-capture-command-select}{display=Remote capture command selection}"
+ "{type=radio}{tooltip=The remote capture command to build a command line for}{group=Capture}\n", inc);
+ printf("value {arg=%u}{value=dumpcap}{display=dumpcap}\n", inc);
+ printf("value {arg=%u}{value=tcpdump}{display=tcpdump}{default=true}\n", inc);
+ printf("value {arg=%u}{value=other}{display=Other:}\n", inc++);
+ printf("arg {number=%u}{call=--remote-capture-command}{display=Remote capture command}"
+ "{type=string}{tooltip=The remote command used to capture}{group=Capture}\n", inc++);
+ // Deprecated
+ //printf("arg {number=%u}{call=--remote-sudo}{display=Use sudo on the remote machine}"
+ // "{type=boolflag}{tooltip=Prepend the capture command with sudo on the remote machine}"
+ // "{group=Capture}\n", inc++);
+ printf("arg {number=%u}{call=--remote-priv}{display=Gain capture privilege on the remote machine}"
+ "{type=radio}{tooltip=Optionally prepend the capture command with sudo or doas on the remote machine}"
+ "{group=Capture}\n", inc);
+ printf("value {arg=%u}{value=none}{display=none}{default=true}\n", inc);
+ printf("value {arg=%u}{value=sudo}{display=sudo}\n", inc);
+ printf("value {arg=%u}{value=doas -n}{display=doas}\n", inc++);
+ printf("arg {number=%u}{call=--remote-priv-user}{display=Privileged user name for sudo or doas}"
+ "{type=string}{tooltip=User name of privileged user to execute the capture command on the remote machine}"
+ "{group=Capture}\n", inc++);
+ printf("arg {number=%u}{call=--remote-noprom}{display=No promiscuous mode}"
+ "{type=boolflag}{tooltip=Don't use promiscuous mode on the remote machine}{group=Capture}"
+ "\n", inc++);
+ printf("arg {number=%u}{call=--remote-filter}{display=Remote capture filter}{type=string}"
+ "{tooltip=The remote capture filter}", inc++);
+ if (ipfilter)
+ printf("{default=%s}", ipfilter);
+ printf("{group=Capture}\n");
+ printf("arg {number=%u}{call=--remote-count}{display=Packets to capture}"
+ "{type=unsigned}{default=0}{tooltip=The number of remote packets to capture. (Default: inf)}"
+ "{group=Capture}\n", inc++);
+
+ extcap_config_debug(&inc);
+
+ g_free(ipfilter);
+
+ return EXIT_SUCCESS;
+}
+
+static char* concat_filters(const char* extcap_filter, const char* remote_filter)
+{
+ if (!extcap_filter && remote_filter)
+ return g_strdup(remote_filter);
+
+ if (!remote_filter && extcap_filter)
+ return g_strdup(extcap_filter);
+
+ if (!remote_filter && !extcap_filter)
+ return NULL;
+
+ return ws_strdup_printf("(%s) and (%s)", extcap_filter, remote_filter);
+}
+
+int main(int argc, char *argv[])
+{
+ char* err_msg;
+ int result;
+ int option_idx = 0;
+ ssh_params_t* ssh_params = ssh_params_new();
+ char* remote_interface = NULL;
+ char* remote_capture_command_select = NULL;
+ char* remote_capture_command = NULL;
+ char* remote_filter = NULL;
+ uint32_t count = 0;
+ int ret = EXIT_FAILURE;
+ extcap_parameters* extcap_conf = g_new0(extcap_parameters, 1);
+ char* help_url;
+ char* help_header = NULL;
+ char* priv = NULL;
+ char* priv_user = NULL;
+ bool noprom = false;
+ char* interface_description = g_strdup("SSH remote capture");
+
+ /* Initialize log handler early so we can have proper logging during startup. */
+ extcap_log_init("sshdump");
+
+ sshdump_extcap_interface = g_path_get_basename(argv[0]);
+
+ /*
+ * Get credential information for later use.
+ */
+ init_process_policies();
+
+ /*
+ * Attempt to get the pathname of the directory containing the
+ * executable file.
+ */
+ err_msg = configuration_init(argv[0], NULL);
+ if (err_msg != NULL) {
+ ws_warning("Can't get pathname of directory containing the extcap program: %s.",
+ err_msg);
+ g_free(err_msg);
+ }
+
+ help_url = data_file_url("sshdump.html");
+ extcap_base_set_util_info(extcap_conf, argv[0], SSHDUMP_VERSION_MAJOR, SSHDUMP_VERSION_MINOR,
+ SSHDUMP_VERSION_RELEASE, help_url);
+ g_free(help_url);
+ add_libssh_info(extcap_conf);
+ if (g_strcmp0(sshdump_extcap_interface, DEFAULT_SSHDUMP_EXTCAP_INTERFACE)) {
+ char* temp = interface_description;
+ interface_description = ws_strdup_printf("%s, custom version", interface_description);
+ g_free(temp);
+ }
+ extcap_base_register_interface(extcap_conf, sshdump_extcap_interface, interface_description, 147, "Remote capture dependent DLT");
+ g_free(interface_description);
+
+ help_header = ws_strdup_printf(
+ " %s --extcap-interfaces\n"
+ " %s --extcap-interface=%s --extcap-dlts\n"
+ " %s --extcap-interface=%s --extcap-config\n"
+ " %s --extcap-interface=%s --remote-host myhost --remote-port 22222 "
+ "--remote-username myuser --remote-interface eth2 --remote-capture-command 'tcpdump -U -i eth0 -w -' "
+ "--fifo=FILENAME --capture\n", argv[0], argv[0], sshdump_extcap_interface, argv[0],
+ sshdump_extcap_interface, argv[0], sshdump_extcap_interface);
+ extcap_help_add_header(extcap_conf, help_header);
+ g_free(help_header);
+ extcap_help_add_option(extcap_conf, "--help", "print this help");
+ extcap_help_add_option(extcap_conf, "--version", "print the version");
+ extcap_help_add_option(extcap_conf, "--remote-host <host>", "the remote SSH host");
+ extcap_help_add_option(extcap_conf, "--remote-port <port>", "the remote SSH port");
+ extcap_help_add_option(extcap_conf, "--remote-username <username>", "the remote SSH username");
+ extcap_help_add_option(extcap_conf, "--remote-password <password>", "the remote SSH password. If not specified, ssh-agent and ssh-key are used");
+ extcap_help_add_option(extcap_conf, "--sshkey <private key path>", "the path of the SSH key (OpenSSH format)");
+ extcap_help_add_option(extcap_conf, "--sshkey-passphrase <private key passphrase>", "the passphrase to unlock private SSH key");
+ extcap_help_add_option(extcap_conf, "--proxycommand <proxy command>", "the command to use as proxy for the SSH connection");
+ extcap_help_add_option(extcap_conf, "--remote-interface <iface>", "the remote capture interface");
+ extcap_help_add_option(extcap_conf, "--remote-capture-command-select <selection>", "dumpcap, tcpdump or other remote capture command");
+ extcap_help_add_option(extcap_conf, "--remote-capture-command <capture command>", "the remote capture command");
+ //extcap_help_add_option(extcap_conf, "--remote-sudo", "use sudo on the remote machine to capture"); // Deprecated
+ extcap_help_add_option(extcap_conf, "--remote-priv <selection>", "none, sudo or doas");
+ extcap_help_add_option(extcap_conf, "--remote-priv-user <username>", "privileged user name");
+ extcap_help_add_option(extcap_conf, "--remote-noprom", "don't use promiscuous mode on the remote machine");
+ extcap_help_add_option(extcap_conf, "--remote-filter <filter>", "a filter for remote capture (default: don't listen on local interfaces IPs)");
+ extcap_help_add_option(extcap_conf, "--remote-count <count>", "the number of packets to capture");
+
+ ws_opterr = 0;
+ ws_optind = 0;
+
+ if (argc == 1) {
+ extcap_help_print(extcap_conf);
+ goto end;
+ }
+
+ while ((result = ws_getopt_long(argc, argv, ":", longopts, &option_idx)) != -1) {
+
+ switch (result) {
+
+ case OPT_HELP:
+ extcap_help_print(extcap_conf);
+ ret = EXIT_SUCCESS;
+ goto end;
+
+ case OPT_VERSION:
+ extcap_version_print(extcap_conf);
+ ret = EXIT_SUCCESS;
+ goto end;
+
+ case OPT_REMOTE_HOST:
+ g_free(ssh_params->host);
+ ssh_params->host = g_strdup(ws_optarg);
+ break;
+
+ case OPT_REMOTE_PORT:
+ if (!ws_strtou16(ws_optarg, NULL, &ssh_params->port) || ssh_params->port == 0) {
+ ws_warning("Invalid port: %s", ws_optarg);
+ goto end;
+ }
+ break;
+
+ case OPT_REMOTE_USERNAME:
+ g_free(ssh_params->username);
+ ssh_params->username = g_strdup(ws_optarg);
+ break;
+
+ case OPT_REMOTE_PASSWORD:
+ g_free(ssh_params->password);
+ ssh_params->password = g_strdup(ws_optarg);
+ memset(ws_optarg, 'X', strlen(ws_optarg));
+ break;
+
+ case OPT_SSHKEY:
+ g_free(ssh_params->sshkey_path);
+ ssh_params->sshkey_path = g_strdup(ws_optarg);
+ break;
+
+ case OPT_SSHKEY_PASSPHRASE:
+ g_free(ssh_params->sshkey_passphrase);
+ ssh_params->sshkey_passphrase = g_strdup(ws_optarg);
+ memset(ws_optarg, 'X', strlen(ws_optarg));
+ break;
+
+ case OPT_PROXYCOMMAND:
+ g_free(ssh_params->proxycommand);
+ ssh_params->proxycommand = g_strdup(ws_optarg);
+ break;
+
+ case OPT_REMOTE_INTERFACE:
+ g_free(remote_interface);
+ remote_interface = g_strdup(ws_optarg);
+ break;
+
+ case OPT_REMOTE_CAPTURE_COMMAND_SELECT:
+ g_free(remote_capture_command_select);
+ remote_capture_command_select = g_strdup(ws_optarg);
+ break;
+
+ case OPT_REMOTE_CAPTURE_COMMAND:
+ g_free(remote_capture_command);
+ remote_capture_command = g_strdup(ws_optarg);
+ break;
+
+ case OPT_REMOTE_SUDO:
+ // Deprecated
+ g_free(priv);
+ priv = g_strdup("sudo");
+ break;
+
+ case OPT_REMOTE_PRIV:
+ g_free(priv);
+ priv = g_strdup(ws_optarg);
+ break;
+
+ case OPT_REMOTE_PRIV_USER:
+ g_free(priv_user);
+ priv_user = g_strdup(ws_optarg);
+ break;
+
+ case OPT_REMOTE_FILTER:
+ g_free(remote_filter);
+ remote_filter = g_strdup(ws_optarg);
+ break;
+
+ case OPT_REMOTE_COUNT:
+ if (!ws_strtou32(ws_optarg, NULL, &count)) {
+ ws_warning("Invalid value for count: %s", ws_optarg);
+ goto end;
+ }
+ break;
+
+ case OPT_REMOTE_NOPROM:
+ noprom = true;
+ break;
+
+ case ':':
+ /* missing option argument */
+ ws_warning("Option '%s' requires an argument", argv[ws_optind - 1]);
+ break;
+
+ default:
+ if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg)) {
+ ws_warning("Invalid option: %s", argv[ws_optind - 1]);
+ goto end;
+ }
+ }
+ }
+
+ extcap_cmdline_debug(argv, argc);
+
+ if (extcap_base_handle_interface(extcap_conf)) {
+ ret = EXIT_SUCCESS;
+ goto end;
+ }
+
+ if (extcap_conf->show_config) {
+ ret = list_config(extcap_conf->interface, ssh_params->port);
+ goto end;
+ }
+
+ err_msg = ws_init_sockets();
+ if (err_msg != NULL) {
+ ws_warning("ERROR: %s", err_msg);
+ g_free(err_msg);
+ ws_warning("%s", please_report_bug());
+ goto end;
+ }
+
+ if (extcap_conf->capture) {
+ char* filter;
+ char* privilege;
+
+ if (!ssh_params->host) {
+ ws_warning("Missing parameter: --remote-host");
+ goto end;
+ }
+
+ if ((priv) && g_strcmp0(priv, "none") && strlen(g_strstrip(priv))) {
+ if ((priv_user) && strlen(g_strstrip(priv_user)))
+ /* Both sudo and doas use the same command line option */
+ privilege = g_strconcat(priv, " -u ", priv_user, NULL);
+ else
+ privilege = g_strdup(priv);
+ } else {
+ privilege = g_strdup("");
+ }
+
+ // This may result in the use of a different port number than was given in
+ // the default filter string, as presented in the config dialog. The default
+ // given is always using the default SSH port since there's no remote SSH port
+ // given on the command line to get the extcap arguments.
+ // However the remote SSH port used here is the one given on the command line
+ // when the capture us started, which is the indended one.
+ // And this is only happening when no remote filter is specified on the command
+ // line to start the capture.
+ if (remote_filter == NULL)
+ remote_filter = local_interfaces_to_filter(ssh_params->port);
+ filter = concat_filters(extcap_conf->capture_filter, remote_filter);
+ ssh_params->debug = extcap_conf->debug;
+ ret = ssh_open_remote_connection(ssh_params, remote_interface,
+ filter, remote_capture_command_select, remote_capture_command,
+ privilege, noprom, count, extcap_conf->fifo);
+ g_free(filter);
+ g_free(privilege);
+ } else {
+ ws_debug("You should not come here... maybe some parameter missing?");
+ ret = EXIT_FAILURE;
+ }
+
+end:
+ /* clean up stuff */
+ ssh_params_free(ssh_params);
+ g_free(remote_capture_command_select);
+ g_free(remote_capture_command);
+ g_free(remote_interface);
+ g_free(remote_filter);
+ g_free(priv);
+ g_free(priv_user);
+ extcap_base_cleanup(&extcap_conf);
+ return ret;
+}
+
+/*
+ * Editor modelines - https://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 8
+ * tab-width: 8
+ * indent-tabs-mode: t
+ * End:
+ *
+ * vi: set shiftwidth=8 tabstop=8 noexpandtab:
+ * :indentSize=8:tabSize=8:noTabs=false:
+ */
diff --git a/extcap/udpdump.c b/extcap/udpdump.c
new file mode 100644
index 0000000..7f4602a
--- /dev/null
+++ b/extcap/udpdump.c
@@ -0,0 +1,490 @@
+/* udpdump.c
+ * udpdump is an extcap tool used to get packets exported from a source (like a network device or a GSMTAP producer) that
+ * are dumped to a pcap file
+ *
+ * Copyright 2016, Dario Lombardo <lomato@gmail.com>
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "config.h"
+#define WS_LOG_DOMAIN "udpdump"
+
+#include <extcap/extcap-base.h>
+
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <stdlib.h>
+
+#ifdef HAVE_SYS_TIME_H
+ #include <sys/time.h>
+#endif
+
+#ifdef HAVE_NETINET_IN_H
+ #include <netinet/in.h>
+#endif
+
+#include <string.h>
+#include <errno.h>
+
+#ifdef HAVE_UNISTD_H
+ #include <unistd.h>
+#endif
+
+#include <writecap/pcapio.h>
+#include <wiretap/wtap.h>
+#include <wsutil/strtoi.h>
+#include <wsutil/inet_addr.h>
+#include <wsutil/filesystem.h>
+#include <wsutil/privileges.h>
+#include <wsutil/socket.h>
+#include <wsutil/please_report_bug.h>
+#include <wsutil/wslog.h>
+#include <wsutil/pint.h>
+#include <wsutil/exported_pdu_tlvs.h>
+
+#include <cli_main.h>
+
+#define PCAP_SNAPLEN 0xffff
+
+#define UDPDUMP_DEFAULT_PORT 5555
+
+#define UDPDUMP_EXTCAP_INTERFACE "udpdump"
+#define UDPDUMP_VERSION_MAJOR "0"
+#define UDPDUMP_VERSION_MINOR "1"
+#define UDPDUMP_VERSION_RELEASE "0"
+
+#define PKT_BUF_SIZE 65535
+
+#define UDPDUMP_EXPORT_HEADER_LEN 40
+
+enum {
+ EXTCAP_BASE_OPTIONS_ENUM,
+ OPT_HELP,
+ OPT_VERSION,
+ OPT_PORT,
+ OPT_PAYLOAD
+};
+
+static struct ws_option longopts[] = {
+ EXTCAP_BASE_OPTIONS,
+ /* Generic application options */
+ { "help", ws_no_argument, NULL, OPT_HELP},
+ { "version", ws_no_argument, NULL, OPT_VERSION},
+ /* Interfaces options */
+ { "port", ws_required_argument, NULL, OPT_PORT},
+ { "payload", ws_required_argument, NULL, OPT_PAYLOAD},
+ { 0, 0, 0, 0 }
+};
+
+static int list_config(char *interface)
+{
+ unsigned inc = 0;
+
+ if (!interface) {
+ ws_warning("No interface specified.");
+ return EXIT_FAILURE;
+ }
+
+ printf("arg {number=%u}{call=--port}{display=Listen port}"
+ "{type=unsigned}{range=1,65535}{default=%u}{tooltip=The port the receiver listens on}\n",
+ inc++, UDPDUMP_DEFAULT_PORT);
+ printf("arg {number=%u}{call=--payload}{display=Payload type}"
+ "{type=string}{default=data}{tooltip=The type used to describe the payload in the exported pdu format}\n",
+ inc++);
+
+ extcap_config_debug(&inc);
+
+ return EXIT_SUCCESS;
+}
+
+static int setup_listener(const uint16_t port, socket_handle_t* sock)
+{
+ int optval;
+ struct sockaddr_in serveraddr;
+#ifndef _WIN32
+ struct timeval timeout = { 1, 0 };
+#endif
+
+ *sock = socket(AF_INET, SOCK_DGRAM, 0);
+
+ if (*sock == INVALID_SOCKET) {
+ ws_warning("Error opening socket: %s", strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ optval = 1;
+ if (setsockopt(*sock, SOL_SOCKET, SO_REUSEADDR, (char*)&optval, (socklen_t)sizeof(int)) < 0) {
+ ws_warning("Can't set socket option SO_REUSEADDR: %s", strerror(errno));
+ goto cleanup_setup_listener;
+ }
+
+#ifndef _WIN32
+ if (setsockopt (*sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, (socklen_t)sizeof(timeout)) < 0) {
+ ws_warning("Can't set socket option SO_RCVTIMEO: %s", strerror(errno));
+ goto cleanup_setup_listener;
+ }
+#endif
+
+ memset(&serveraddr, 0x0, sizeof(serveraddr));
+ serveraddr.sin_family = AF_INET;
+ serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
+ serveraddr.sin_port = htons(port);
+
+ if (bind(*sock, (struct sockaddr *)&serveraddr, (socklen_t)sizeof(serveraddr)) < 0) {
+ ws_warning("Error on binding: %s", strerror(errno));
+ goto cleanup_setup_listener;
+ }
+
+ return EXIT_SUCCESS;
+
+cleanup_setup_listener:
+ closesocket(*sock);
+ return EXIT_FAILURE;
+
+}
+
+static int setup_dumpfile(const char* fifo, FILE** fp)
+{
+ uint64_t bytes_written = 0;
+ int err;
+
+ if (!g_strcmp0(fifo, "-")) {
+ *fp = stdout;
+ return EXIT_SUCCESS;
+ }
+
+ *fp = fopen(fifo, "wb");
+ if (!(*fp)) {
+ ws_warning("Error creating output file: %s", g_strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ if (!libpcap_write_file_header(*fp, 252, PCAP_SNAPLEN, false, &bytes_written, &err)) {
+ ws_warning("Can't write pcap file header: %s", g_strerror(err));
+ return EXIT_FAILURE;
+ }
+
+ fflush(*fp);
+
+ return EXIT_SUCCESS;
+}
+
+static void add_proto_name(uint8_t* mbuf, unsigned* offset, const char* proto_name)
+{
+ size_t proto_str_len = strlen(proto_name);
+ uint16_t proto_name_len = (uint16_t)((proto_str_len + 3) & 0xfffffffc);
+
+ phton16(mbuf + *offset, EXP_PDU_TAG_DISSECTOR_NAME);
+ *offset += 2;
+ phton16(mbuf + *offset, proto_name_len);
+ *offset += 2;
+
+ memcpy(mbuf + *offset, proto_name, proto_str_len);
+ *offset += proto_name_len;
+}
+
+static void add_ip_source_address(uint8_t* mbuf, unsigned* offset, uint32_t source_address)
+{
+ phton16(mbuf + *offset, EXP_PDU_TAG_IPV4_SRC);
+ *offset += 2;
+ phton16(mbuf + *offset, 4);
+ *offset += 2;
+ memcpy(mbuf + *offset, &source_address, 4);
+ *offset += 4;
+}
+
+static void add_ip_dest_address(uint8_t* mbuf, unsigned* offset, uint32_t dest_address)
+{
+ phton16(mbuf + *offset, EXP_PDU_TAG_IPV4_DST);
+ *offset += 2;
+ phton16(mbuf + *offset, 4);
+ *offset += 2;
+ memcpy(mbuf + *offset, &dest_address, 4);
+ *offset += 4;
+}
+
+static void add_udp_source_port(uint8_t* mbuf, unsigned* offset, uint16_t src_port)
+{
+ uint32_t port = htonl(src_port);
+
+ phton16(mbuf + *offset, EXP_PDU_TAG_SRC_PORT);
+ *offset += 2;
+ phton16(mbuf + *offset, 4);
+ *offset += 2;
+ memcpy(mbuf + *offset, &port, 4);
+ *offset += 4;
+}
+
+static void add_udp_dst_port(uint8_t* mbuf, unsigned* offset, uint16_t dst_port)
+{
+ uint32_t port = htonl(dst_port);
+
+ phton16(mbuf + *offset, EXP_PDU_TAG_DST_PORT);
+ *offset += 2;
+ phton16(mbuf + *offset, 4);
+ *offset += 2;
+ memcpy(mbuf + *offset, &port, 4);
+ *offset += 4;
+}
+
+static void add_end_options(uint8_t* mbuf, unsigned* offset)
+{
+ memset(mbuf + *offset, 0x0, 4);
+ *offset += 4;
+}
+
+static int dump_packet(const char* proto_name, const uint16_t listenport, const char* buf,
+ const ssize_t buflen, const struct sockaddr_in clientaddr, FILE* fp)
+{
+ uint8_t* mbuf;
+ unsigned offset = 0;
+ int64_t curtime = g_get_real_time();
+ uint64_t bytes_written = 0;
+ int err;
+ int ret = EXIT_SUCCESS;
+
+ /* The space we need is the standard header + variable lengths */
+ mbuf = (uint8_t*)g_malloc0(UDPDUMP_EXPORT_HEADER_LEN + ((strlen(proto_name) + 3) & 0xfffffffc) + buflen);
+
+ add_proto_name(mbuf, &offset, proto_name);
+ add_ip_source_address(mbuf, &offset, clientaddr.sin_addr.s_addr);
+ add_ip_dest_address(mbuf, &offset, WS_IN4_LOOPBACK);
+ add_udp_source_port(mbuf, &offset, clientaddr.sin_port);
+ add_udp_dst_port(mbuf, &offset, listenport);
+ add_end_options(mbuf, &offset);
+
+ memcpy(mbuf + offset, buf, buflen);
+ offset += (unsigned)buflen;
+
+ if (!libpcap_write_packet(fp,
+ (uint32_t)(curtime / G_USEC_PER_SEC), (uint32_t)(curtime % G_USEC_PER_SEC),
+ offset, offset, mbuf, &bytes_written, &err)) {
+ ws_warning("Can't write packet: %s", g_strerror(err));
+ ret = EXIT_FAILURE;
+ }
+
+ fflush(fp);
+
+ g_free(mbuf);
+ return ret;
+}
+
+static void run_listener(const char* fifo, const uint16_t port, const char* proto_name)
+{
+ struct sockaddr_in clientaddr;
+ socklen_t clientlen = sizeof(clientaddr);
+ socket_handle_t sock;
+ char* buf;
+ ssize_t buflen;
+ FILE* fp = NULL;
+
+ if (setup_dumpfile(fifo, &fp) == EXIT_FAILURE) {
+ if (fp)
+ fclose(fp);
+ return;
+ }
+
+ if (setup_listener(port, &sock) == EXIT_FAILURE)
+ return;
+
+ ws_debug("Listener running on port %u", port);
+
+ buf = (char*)g_malloc(PKT_BUF_SIZE);
+ while(!extcap_end_application) {
+ memset(buf, 0x0, PKT_BUF_SIZE);
+
+ buflen = recvfrom(sock, buf, PKT_BUF_SIZE, 0, (struct sockaddr *)&clientaddr, &clientlen);
+ if (buflen < 0) {
+ switch(errno) {
+ case EAGAIN:
+ case EINTR:
+ break;
+ default:
+#ifdef _WIN32
+ {
+ wchar_t *errmsg = NULL;
+ int err = WSAGetLastError();
+ FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, err,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPWSTR)&errmsg, 0, NULL);
+ ws_warning("Error in recvfrom: %S (err=%d)", errmsg, err);
+ LocalFree(errmsg);
+ }
+#else
+ ws_warning("Error in recvfrom: %s (errno=%d)", strerror(errno), errno);
+#endif
+ extcap_end_application = true;
+ break;
+ }
+ } else {
+ if (dump_packet(proto_name, port, buf, buflen, clientaddr, fp) == EXIT_FAILURE)
+ extcap_end_application = true;
+ }
+ }
+
+ fclose(fp);
+ closesocket(sock);
+ g_free(buf);
+}
+
+int main(int argc, char *argv[])
+{
+ char* err_msg;
+ int option_idx = 0;
+ int result;
+ uint16_t port = 0;
+ int ret = EXIT_FAILURE;
+ extcap_parameters* extcap_conf = g_new0(extcap_parameters, 1);
+ char* help_url;
+ char* help_header = NULL;
+ char* payload = NULL;
+ char* port_msg = NULL;
+
+ /* Initialize log handler early so we can have proper logging during startup. */
+ extcap_log_init("udpdump");
+
+ /*
+ * Get credential information for later use.
+ */
+ init_process_policies();
+
+ /*
+ * Attempt to get the pathname of the directory containing the
+ * executable file.
+ */
+ err_msg = configuration_init(argv[0], NULL);
+ if (err_msg != NULL) {
+ ws_warning("Can't get pathname of directory containing the extcap program: %s.",
+ err_msg);
+ g_free(err_msg);
+ }
+
+ help_url = data_file_url("udpdump.html");
+ extcap_base_set_util_info(extcap_conf, argv[0], UDPDUMP_VERSION_MAJOR, UDPDUMP_VERSION_MINOR, UDPDUMP_VERSION_RELEASE,
+ help_url);
+ g_free(help_url);
+ extcap_base_register_interface(extcap_conf, UDPDUMP_EXTCAP_INTERFACE, "UDP Listener remote capture", 252, "Exported PDUs");
+
+ help_header = ws_strdup_printf(
+ " %s --extcap-interfaces\n"
+ " %s --extcap-interface=%s --extcap-dlts\n"
+ " %s --extcap-interface=%s --extcap-config\n"
+ " %s --extcap-interface=%s --port 5555 --fifo myfifo --capture",
+ argv[0], argv[0], UDPDUMP_EXTCAP_INTERFACE, argv[0], UDPDUMP_EXTCAP_INTERFACE, argv[0], UDPDUMP_EXTCAP_INTERFACE);
+ extcap_help_add_header(extcap_conf, help_header);
+ g_free(help_header);
+ extcap_help_add_option(extcap_conf, "--help", "print this help");
+ extcap_help_add_option(extcap_conf, "--version", "print the version");
+ port_msg = ws_strdup_printf("the port to listens on. Default: %u", UDPDUMP_DEFAULT_PORT);
+ extcap_help_add_option(extcap_conf, "--port <port>", port_msg);
+ g_free(port_msg);
+
+ ws_opterr = 0;
+ ws_optind = 0;
+
+ if (argc == 1) {
+ extcap_help_print(extcap_conf);
+ goto end;
+ }
+
+ while ((result = ws_getopt_long(argc, argv, ":", longopts, &option_idx)) != -1) {
+ switch (result) {
+
+ case OPT_HELP:
+ extcap_help_print(extcap_conf);
+ ret = EXIT_SUCCESS;
+ goto end;
+
+ case OPT_VERSION:
+ extcap_version_print(extcap_conf);
+ goto end;
+
+ case OPT_PORT:
+ if (!ws_strtou16(ws_optarg, NULL, &port)) {
+ ws_warning("Invalid port: %s", ws_optarg);
+ goto end;
+ }
+ break;
+
+ case OPT_PAYLOAD:
+ g_free(payload);
+ payload = g_strdup(ws_optarg);
+ break;
+
+ case ':':
+ /* missing option argument */
+ ws_warning("Option '%s' requires an argument", argv[ws_optind - 1]);
+ break;
+
+ default:
+ if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg)) {
+ ws_warning("Invalid option: %s", argv[ws_optind - 1]);
+ goto end;
+ }
+ }
+ }
+
+ extcap_cmdline_debug(argv, argc);
+
+ if (ws_optind != argc) {
+ ws_warning("Unexpected extra option: %s", argv[ws_optind]);
+ goto end;
+ }
+
+ if (extcap_base_handle_interface(extcap_conf)) {
+ ret = EXIT_SUCCESS;
+ goto end;
+ }
+
+ if (!extcap_base_register_graceful_shutdown_cb(extcap_conf, NULL)) {
+ ret = EXIT_SUCCESS;
+ goto end;
+ }
+
+ if (extcap_conf->show_config) {
+ ret = list_config(extcap_conf->interface);
+ goto end;
+ }
+
+ if (!payload)
+ payload = g_strdup("data");
+
+ err_msg = ws_init_sockets();
+ if (err_msg != NULL) {
+ ws_warning("Error: %s", err_msg);
+ g_free(err_msg);
+ ws_warning("%s", please_report_bug());
+ goto end;
+ }
+
+ if (port == 0)
+ port = UDPDUMP_DEFAULT_PORT;
+
+ if (extcap_conf->capture)
+ run_listener(extcap_conf->fifo, port, payload);
+
+end:
+ /* clean up stuff */
+ extcap_base_cleanup(&extcap_conf);
+ g_free(payload);
+ return ret;
+}
+
+/*
+ * Editor modelines - https://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 8
+ * tab-width: 8
+ * indent-tabs-mode: t
+ * End:
+ *
+ * vi: set shiftwidth=8 tabstop=8 noexpandtab:
+ * :indentSize=8:tabSize=8:noTabs=false:
+ */
diff --git a/extcap/wifidump.c b/extcap/wifidump.c
new file mode 100644
index 0000000..489118e
--- /dev/null
+++ b/extcap/wifidump.c
@@ -0,0 +1,747 @@
+/* wifidump.c
+ * wifidump is an extcap tool used to capture Wi-Fi frames using a remote ssh host
+ *
+ * Adapted from sshdump.
+ *
+ * Copyright 2022, Adrian Granados <adrian@intuitibits.com>
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "config.h"
+#define WS_LOG_DOMAIN "wifidump"
+
+#include <extcap/extcap-base.h>
+#include <extcap/ssh-base.h>
+#include <wsutil/interface.h>
+#include <wsutil/file_util.h>
+#include <wsutil/strtoi.h>
+#include <wsutil/filesystem.h>
+#include <wsutil/privileges.h>
+#include <wsutil/please_report_bug.h>
+#include <wsutil/wslog.h>
+
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include <cli_main.h>
+
+static char* wifidump_extcap_interface;
+#ifdef _WIN32
+#define DEFAULT_WIFIDUMP_EXTCAP_INTERFACE "wifidump.exe"
+#else
+#define DEFAULT_WIFIDUMP_EXTCAP_INTERFACE "wifidump"
+#endif
+
+#define WIFIDUMP_VERSION_MAJOR "1"
+#define WIFIDUMP_VERSION_MINOR "0"
+#define WIFIDUMP_VERSION_RELEASE "0"
+
+#define SSH_READ_BLOCK_SIZE 256
+
+enum {
+ EXTCAP_BASE_OPTIONS_ENUM,
+ OPT_HELP,
+ OPT_VERSION,
+ OPT_REMOTE_HOST,
+ OPT_REMOTE_PORT,
+ OPT_REMOTE_USERNAME,
+ OPT_REMOTE_PASSWORD,
+ OPT_REMOTE_INTERFACE,
+ OPT_REMOTE_CHANNEL_FREQUENCY,
+ OPT_REMOTE_CHANNEL_WIDTH,
+ OPT_REMOTE_FILTER,
+ OPT_SSHKEY,
+ OPT_SSHKEY_PASSPHRASE,
+ OPT_PROXYCOMMAND,
+ OPT_REMOTE_COUNT
+};
+
+static struct ws_option longopts[] = {
+ EXTCAP_BASE_OPTIONS,
+ { "help", ws_no_argument, NULL, OPT_HELP},
+ { "version", ws_no_argument, NULL, OPT_VERSION},
+ SSH_BASE_OPTIONS,
+ { "remote-channel-frequency", ws_required_argument, NULL, OPT_REMOTE_CHANNEL_FREQUENCY},
+ { "remote-channel-width", ws_required_argument, NULL, OPT_REMOTE_CHANNEL_WIDTH},
+ { 0, 0, 0, 0}
+};
+
+static const char * remote_capture_functions =
+"\n"
+"function iface_down {\n"
+" local iface=$1\n"
+" sudo ip link set $iface down > /dev/null 2>&1\n"
+"}\n"
+"\n"
+"function iface_up {\n"
+" local iface=$1\n"
+" sudo ip link set $iface up > /dev/null 2>&1\n"
+"}\n"
+"\n"
+"function iface_monitor {\n"
+" local iface=$1\n"
+" sudo iw dev $iface set monitor control otherbss > /dev/null 2>&1 ||\n"
+" sudo iw dev $iface set type monitor control otherbss > /dev/null 2>&1\n"
+"}\n"
+"\n"
+"function iface_scan {\n"
+" local iface=$1\n"
+" iface_down $iface &&\n"
+" sudo iw dev $iface set type managed > /dev/null 2>&1 &&\n"
+" iface_up $iface &&\n"
+" sudo iw dev $iface scan > /dev/null 2>&1\n"
+"}\n"
+"\n"
+"function iface_config {\n"
+" local iface=$1\n"
+" local freq=$2\n"
+" local ch_width=$3\n"
+" local center_freq=$4\n"
+" if [ $freq -eq $center_freq ]; then\n"
+" sudo iw dev $1 set freq $freq $ch_width 2>&1\n"
+" else\n"
+" sudo iw dev $1 set freq $freq $ch_width $center_freq 2>&1\n"
+" fi\n"
+"}\n"
+"\n"
+"function iface_start {\n"
+" local iface=$1\n"
+" local count=$2\n"
+" local filter=\"${@:3}\"\n"
+" if [ $count -gt 0 ]; then\n"
+" sudo tcpdump -i $iface -U -w - -c $count $filter\n"
+" else\n"
+" sudo tcpdump -i $iface -U -w - $filter\n"
+" fi\n"
+"}\n"
+"\n"
+"function capture_generic {\n"
+" local iface=$1\n"
+" local freq=$2\n"
+" local ch_width=$3\n"
+" local center_freq=$4\n"
+" local count=$5\n"
+" local filter=\"${@:6}\"\n"
+" if ! { iwconfig $iface | grep Monitor > /dev/null 2>&1; }; then\n"
+" iface_down $iface &&\n"
+" iface_monitor $iface &&\n"
+" iface_up $iface\n"
+" else\n"
+" iface_monitor $iface\n"
+" fi\n"
+" iface_config $iface $freq $ch_width $center_freq &&\n"
+" iface_start $iface $count $filter\n"
+"}\n"
+"\n"
+"function capture_iwlwifi {\n"
+" local iface=$1\n"
+" local freq=$2\n"
+" local ch_width=$3\n"
+" local center_freq=$4\n"
+" local count=$5\n"
+" local filter=\"${@:6}\"\n"
+" INDEX=`sudo iw dev $iface info | grep wiphy | grep -Eo '[0-9]+'`\n"
+" sudo iw phy phy${INDEX} channels | grep $freq | grep -i disabled > /dev/null 2>&1 &&\n"
+" iface_scan $iface\n"
+" MON=${iface}mon\n"
+" sudo iw $iface interface add $MON type monitor flags none > /dev/null 2>&1\n"
+" iface_up $MON &&\n"
+" iface_down $iface &&\n"
+" iface_config $MON $freq $ch_width $center_freq &&\n"
+" iface_start $MON $count $filter\n"
+"}\n"
+"\n"
+"function capture {\n"
+" local iface=$1\n"
+" local freq=$2\n"
+" local ch_width=$3\n"
+" local center_freq=$4\n"
+" local count=$5\n"
+" local filter=\"${@:6}\"\n"
+" if [ \"$iface\" == \"auto\" ]; then\n"
+" iface=`sudo iw dev | grep -i interface | awk '{ print $2 }' | sort | head -n 1`\n"
+" fi\n"
+" local driver=`/usr/sbin/ethtool -i $iface | grep driver | awk '{ print $2 }'`\n"
+" if [ $driver = \"iwlwifi\" ]; then\n"
+" capture_iwlwifi $iface $freq $ch_width $center_freq $count $filter\n"
+" else\n"
+" capture_generic $iface $freq $ch_width $center_freq $count $filter\n"
+" fi\n"
+"}\n"
+"\n";
+
+static unsigned int wifi_freqs_2dot4_5ghz[] = {
+ 2412, 2417, 2422, 2427, 2432, 2437, 2442, 2447, 2452, 2457, 2462,
+ 2467, 2472, 2484,
+ 5180, 5200, 5220, 5240, 5260, 5280, 5300, 5320, 5500, 5520, 5540, 5560, 5580,
+ 5600, 5620, 5640, 5660, 5680, 5700, 5720, 5745, 5765, 5785, 5805, 5825,
+ 0
+};
+
+static unsigned int freq_to_channel(unsigned int freq_mhz) {
+ if (freq_mhz == 2484)
+ return 14;
+ else if (freq_mhz >= 2412 && freq_mhz <= 2484)
+ return ((freq_mhz - 2412) / 5) + 1;
+ else if (freq_mhz >= 5160 && freq_mhz <= 5885)
+ return ((freq_mhz - 5180) / 5) + 36;
+ else if (freq_mhz >= 5955 && freq_mhz <= 7115)
+ return ((freq_mhz - 5955) / 5) + 1;
+ else
+ return 0;
+}
+
+static const char *freq_to_band(unsigned int freq_mhz)
+{
+ if (freq_mhz >= 2412 && freq_mhz <= 2484)
+ return "2.4 GHz";
+ else if (freq_mhz >= 5160 && freq_mhz <= 5885)
+ return "5 GHz";
+ else if (freq_mhz >= 5955 && freq_mhz <= 7115)
+ return "6 GHz";
+ else
+ return NULL;
+}
+
+static unsigned int center_freq(unsigned int freq_mhz, unsigned int ch_width_mhz) {
+
+ unsigned int start_freq;
+
+ if (ch_width_mhz == 20) {
+ return freq_mhz;
+ }
+ else if (ch_width_mhz == 40) {
+ if (freq_mhz >= 5180 && freq_mhz <= 5720) {
+ for (start_freq = 5180; start_freq <= 5700; start_freq += ch_width_mhz) {
+ if (freq_mhz >= start_freq && freq_mhz <= (start_freq + 20))
+ return ((start_freq * 2) + 20) / 2;
+ }
+ }
+ else if (freq_mhz >= 5745 && freq_mhz <= 5765)
+ return 5755;
+ else if (freq_mhz >= 5785 && freq_mhz <= 5805)
+ return 5795;
+ else if (freq_mhz >= 5955 && freq_mhz <= 7095) {
+ for (start_freq = 5955; start_freq <= 7075; start_freq += ch_width_mhz) {
+ if (freq_mhz >= start_freq && freq_mhz <= (start_freq + 20))
+ return ((start_freq * 2) + 20) / 2;
+ }
+ }
+ }
+ else if (ch_width_mhz == 80) {
+ if (freq_mhz >= 5180 && freq_mhz <= 5660) {
+ for (start_freq = 5180; start_freq <= 5660; start_freq += ch_width_mhz) {
+ if (freq_mhz >= start_freq && freq_mhz <= (start_freq + 60))
+ return ((start_freq * 2) + 60) / 2;
+ }
+ }
+ else if (freq_mhz >= 5745 && freq_mhz <= 5805)
+ return 5775;
+ else if (freq_mhz >= 5955 && freq_mhz <= 7055) {
+ for (start_freq = 5955; start_freq <= 6995; start_freq += ch_width_mhz) {
+ if (freq_mhz >= start_freq && freq_mhz <= (start_freq + 60))
+ return ((start_freq * 2) + 60) / 2;
+ }
+ }
+ }
+ else if (ch_width_mhz == 160) {
+ if (freq_mhz >= 5180 && freq_mhz <= 5640) {
+ for (start_freq = 5180; start_freq <= 5500; start_freq += ch_width_mhz) {
+ if (freq_mhz >= start_freq && freq_mhz <= (start_freq + 140))
+ return ((start_freq * 2) + 140) / 2;
+ }
+ }
+ else if (freq_mhz >= 5955 && freq_mhz <= 7055) {
+ for (start_freq = 5955; start_freq <= 6915; start_freq += ch_width_mhz) {
+ if (freq_mhz >= start_freq && freq_mhz <= (start_freq + 140))
+ return ((start_freq * 2) + 140) / 2;
+ }
+ }
+ }
+
+ return -1;
+}
+
+static int ssh_loop_read(ssh_channel channel, FILE* fp)
+{
+ int nbytes;
+ int ret = EXIT_SUCCESS;
+ char buffer[SSH_READ_BLOCK_SIZE];
+
+ /* read from stdin until data are available */
+ while (ssh_channel_is_open(channel) && !ssh_channel_is_eof(channel)) {
+ nbytes = ssh_channel_read(channel, buffer, SSH_READ_BLOCK_SIZE, 0);
+ if (nbytes < 0) {
+ ws_warning("Error reading from channel");
+ goto end;
+ }
+ if (nbytes == 0) {
+ break;
+ }
+ if (fwrite(buffer, 1, nbytes, fp) != (unsigned)nbytes) {
+ ws_warning("Error writing to fifo");
+ ret = EXIT_FAILURE;
+ goto end;
+ }
+ fflush(fp);
+ }
+
+ /* read loop finished... maybe something wrong happened. Read from stderr */
+ while (ssh_channel_is_open(channel) && !ssh_channel_is_eof(channel)) {
+ nbytes = ssh_channel_read(channel, buffer, SSH_READ_BLOCK_SIZE, 1);
+ if (nbytes < 0) {
+ ws_warning("Error reading from channel");
+ goto end;
+ }
+ if (fwrite(buffer, 1, nbytes, stderr) != (unsigned)nbytes) {
+ ws_warning("Error writing to stderr");
+ break;
+ }
+ }
+
+end:
+ if (ssh_channel_send_eof(channel) != SSH_OK) {
+ ws_warning("Error sending EOF in ssh channel");
+ ret = EXIT_FAILURE;
+ }
+ return ret;
+}
+
+static ssh_channel run_ssh_command(ssh_session sshs, const char* capture_functions,
+ const char* iface, const uint16_t channel_frequency, const uint16_t channel_width,
+ const uint16_t center_frequency, const char* cfilter, const uint32_t count)
+{
+ char* cmdline;
+ ssh_channel channel;
+ char* quoted_iface = NULL;
+ char* quoted_filter = NULL;
+ char* count_str = NULL;
+ unsigned int remote_port = 22;
+
+ channel = ssh_channel_new(sshs);
+ if (!channel) {
+ ws_warning("Can't create channel");
+ return NULL;
+ }
+
+ if (ssh_channel_open_session(channel) != SSH_OK) {
+ ws_warning("Can't open session");
+ ssh_channel_free(channel);
+ return NULL;
+ }
+
+ ssh_options_get_port(sshs, &remote_port);
+
+ quoted_iface = iface ? g_shell_quote(iface) : NULL;
+ quoted_filter = g_shell_quote(cfilter ? cfilter : "");
+ cmdline = ws_strdup_printf("%s capture %s %u %u %u %u %s",
+ capture_functions,
+ quoted_iface ? quoted_iface : "auto",
+ channel_frequency,
+ channel_width,
+ center_frequency,
+ count,
+ quoted_filter);
+
+ ws_debug("Running: %s", cmdline);
+ if (ssh_channel_request_exec(channel, cmdline) != SSH_OK) {
+ ws_warning("Can't request exec");
+ ssh_channel_close(channel);
+ ssh_channel_free(channel);
+ channel = NULL;
+ }
+
+ g_free(quoted_iface);
+ g_free(quoted_filter);
+ g_free(cmdline);
+ g_free(count_str);
+
+ return channel;
+}
+
+static int ssh_open_remote_connection(const ssh_params_t* params, const char* capture_functions,
+ const char* iface, const uint16_t channel_frequency, const uint16_t channel_width,
+ const uint16_t center_frequency, const char* cfilter, const uint32_t count, const char* fifo)
+{
+ ssh_session sshs = NULL;
+ ssh_channel channel = NULL;
+ FILE* fp = stdout;
+ int ret = EXIT_FAILURE;
+ char* err_info = NULL;
+
+ if (g_strcmp0(fifo, "-")) {
+ /* Open or create the output file */
+ fp = fopen(fifo, "wb");
+ if (fp == NULL) {
+ ws_warning("Error creating output file: %s (%s)", fifo, g_strerror(errno));
+ return EXIT_FAILURE;
+ }
+ }
+
+ sshs = create_ssh_connection(params, &err_info);
+
+ if (!sshs) {
+ ws_warning("Error creating connection.");
+ goto cleanup;
+ }
+
+ channel = run_ssh_command(sshs, capture_functions, iface, channel_frequency,
+ channel_width, center_frequency, cfilter, count);
+
+ if (!channel) {
+ ws_warning("Can't run ssh command.");
+ goto cleanup;
+ }
+
+ /* read from channel and write into fp */
+ if (ssh_loop_read(channel, fp) != EXIT_SUCCESS) {
+ ws_warning("Error in read loop.");
+ ret = EXIT_FAILURE;
+ goto cleanup;
+ }
+
+ ret = EXIT_SUCCESS;
+cleanup:
+ if (err_info)
+ ws_warning("%s", err_info);
+ g_free(err_info);
+
+ /* clean up and exit */
+ ssh_cleanup(&sshs, &channel);
+
+ if (g_strcmp0(fifo, "-"))
+ fclose(fp);
+ return ret;
+}
+
+static int list_config(char *interface)
+{
+ unsigned inc = 0;
+ int i, psc;
+
+ if (!interface) {
+ ws_warning("ERROR: No interface specified.");
+ return EXIT_FAILURE;
+ }
+
+ if (g_strcmp0(interface, wifidump_extcap_interface)) {
+ ws_warning("ERROR: interface must be %s", wifidump_extcap_interface);
+ return EXIT_FAILURE;
+ }
+
+ // Server tab
+ printf("arg {number=%u}{call=--remote-host}{display=Remote SSH server address}"
+ "{type=string}{tooltip=The remote SSH host. It can be both "
+ "an IP address or a hostname}{required=true}{group=Server}\n", inc++);
+ printf("arg {number=%u}{call=--remote-port}{display=Remote SSH server port}"
+ "{type=unsigned}{tooltip=The remote SSH host port (1-65535)}"
+ "{range=1,65535}{group=Server}\n", inc++);
+
+ // Authentication tab
+ printf("arg {number=%u}{call=--remote-username}{display=Remote SSH server username}"
+ "{type=string}{tooltip=The remote SSH username. If not provided, "
+ "the current user will be used}{group=Authentication}\n", inc++);
+ printf("arg {number=%u}{call=--remote-password}{display=Remote SSH server password}"
+ "{type=password}{tooltip=The SSH password, used when other methods (SSH agent "
+ "or key files) are unavailable.}{group=Authentication}\n", inc++);
+ printf("arg {number=%u}{call=--sshkey}{display=Path to SSH private key}"
+ "{type=fileselect}{tooltip=The path on the local filesystem of the private ssh key}"
+ "{mustexist=true}{group=Authentication}\n", inc++);
+ printf("arg {number=%u}{call=--sshkey-passphrase}{display=SSH key passphrase}"
+ "{type=password}{tooltip=Passphrase to unlock the SSH private key}{group=Authentication}\n",
+ inc++);
+
+ // Capture tab
+ printf("arg {number=%u}{call=--remote-interface}{display=Remote interface}"
+ "{type=string}{tooltip=The remote network interface used to capture"
+ "}{default=auto}{group=Capture}\n", inc++);
+ printf("arg {number=%u}{call=--remote-channel-frequency}{display=Remote channel}"
+ "{type=selector}{tooltip=The remote channel used to capture}{group=Capture}\n", inc);
+
+ unsigned int freq = 0;
+ for (i = 0; (freq = wifi_freqs_2dot4_5ghz[i]); i++) {
+ printf("value {arg=%u}{value=%u}{display=%s, Channel %u}\n", inc, freq, freq_to_band(freq), freq_to_channel(freq));
+ }
+
+ for (freq = 5955, psc = 3; freq <= 7115; freq += 20, psc++) {
+ printf("value {arg=%u}{value=%u}{display=%s, Channel %u%s}\n", inc, freq,
+ freq_to_band(freq), freq_to_channel(freq), (psc % 4 == 0) ? " (PSC)" : "");
+ }
+ inc++;
+
+ printf("arg {number=%u}{call=--remote-channel-width}{display=Remote channel width}"
+ "{type=selector}{tooltip=The remote channel width used to capture}"
+ "{group=Capture}\n", inc);
+ printf("value {arg=%u}{value=20}{display=20 MHz}\n", inc);
+ printf("value {arg=%u}{value=40}{display=40 MHz}\n", inc);
+ printf("value {arg=%u}{value=80}{display=80 MHz}\n", inc);
+ printf("value {arg=%u}{value=160}{display=160 MHz}\n", inc);
+ inc++;
+
+ printf("arg {number=%u}{call=--remote-filter}{display=Remote capture filter}{type=string}"
+ "{tooltip=The remote capture filter}{group=Capture}\n", inc++);
+ printf("arg {number=%u}{call=--remote-count}{display=Frames to capture}"
+ "{type=unsigned}{tooltip=The number of remote frames to capture.}"
+ "{group=Capture}\n", inc++);
+
+ extcap_config_debug(&inc);
+
+ return EXIT_SUCCESS;
+}
+
+static char* concat_filters(const char* extcap_filter, const char* remote_filter)
+{
+ if (!extcap_filter && remote_filter)
+ return g_strdup(remote_filter);
+
+ if (!remote_filter && extcap_filter)
+ return g_strdup(extcap_filter);
+
+ if (!remote_filter && !extcap_filter)
+ return NULL;
+
+ return ws_strdup_printf("(%s) and (%s)", extcap_filter, remote_filter);
+}
+
+int main(int argc, char *argv[])
+{
+ char* err_msg;
+ int result;
+ int option_idx = 0;
+ ssh_params_t* ssh_params = ssh_params_new();
+ char* remote_interface = NULL;
+ uint16_t remote_channel_frequency = 0;
+ uint16_t remote_channel_width = 0;
+ uint16_t remote_center_frequency = 0;
+ char* remote_filter = NULL;
+ uint32_t count = 0;
+ int ret = EXIT_FAILURE;
+ extcap_parameters* extcap_conf = g_new0(extcap_parameters, 1);
+ char* help_url;
+ char* help_header = NULL;
+ char* interface_description = g_strdup("Wi-Fi remote capture");
+
+ /* Initialize log handler early so we can have proper logging during startup. */
+ extcap_log_init("wifidump");
+
+ wifidump_extcap_interface = g_path_get_basename(argv[0]);
+
+ /*
+ * Get credential information for later use.
+ */
+ init_process_policies();
+
+ /*
+ * Attempt to get the pathname of the directory containing the
+ * executable file.
+ */
+ err_msg = configuration_init(argv[0], NULL);
+ if (err_msg != NULL) {
+ ws_warning("Can't get pathname of directory containing the extcap program: %s.",
+ err_msg);
+ g_free(err_msg);
+ }
+
+ help_url = data_file_url("wifidump.html");
+ extcap_base_set_util_info(extcap_conf, argv[0], WIFIDUMP_VERSION_MAJOR, WIFIDUMP_VERSION_MINOR,
+ WIFIDUMP_VERSION_RELEASE, help_url);
+ g_free(help_url);
+ add_libssh_info(extcap_conf);
+ if (g_strcmp0(wifidump_extcap_interface, DEFAULT_WIFIDUMP_EXTCAP_INTERFACE)) {
+ char* temp = interface_description;
+ interface_description = ws_strdup_printf("%s, custom version", interface_description);
+ g_free(temp);
+ }
+ extcap_base_register_interface(extcap_conf, wifidump_extcap_interface, interface_description, 147, "Remote capture dependent DLT");
+ g_free(interface_description);
+
+ help_header = ws_strdup_printf(
+ " %s --extcap-interfaces\n"
+ " %s --extcap-interface=%s --extcap-dlts\n"
+ " %s --extcap-interface=%s --extcap-config\n"
+ " %s --extcap-interface=%s --remote-host myhost --remote-port 22222 "
+ "--remote-username myuser --remote-interface wlan0 --remote-channel-frequency 5180 "
+ "--remote-channel-width 40 --fifo=FILENAME --capture\n", argv[0], argv[0], wifidump_extcap_interface, argv[0],
+ wifidump_extcap_interface, argv[0], wifidump_extcap_interface);
+ extcap_help_add_header(extcap_conf, help_header);
+ g_free(help_header);
+ extcap_help_add_option(extcap_conf, "--help", "print this help");
+ extcap_help_add_option(extcap_conf, "--version", "print the version");
+ extcap_help_add_option(extcap_conf, "--remote-host <host>", "the remote SSH host");
+ extcap_help_add_option(extcap_conf, "--remote-port <port>", "the remote SSH port");
+ extcap_help_add_option(extcap_conf, "--remote-username <username>", "the remote SSH username");
+ extcap_help_add_option(extcap_conf, "--remote-password <password>", "the remote SSH password. If not specified, ssh-agent and ssh-key are used");
+ extcap_help_add_option(extcap_conf, "--sshkey <public key path>", "the path of the ssh key");
+ extcap_help_add_option(extcap_conf, "--sshkey-passphrase <public key passphrase>", "the passphrase to unlock public ssh");
+ extcap_help_add_option(extcap_conf, "--remote-interface <iface>", "the remote capture interface");
+ extcap_help_add_option(extcap_conf, "--remote-channel-frequency <channel_frequency>", "the remote channel frequency in MHz");
+ extcap_help_add_option(extcap_conf, "--remote-channel-width <channel_width>", "the remote channel width in MHz");
+ extcap_help_add_option(extcap_conf, "--remote-filter <filter>", "a filter for remote capture");
+ extcap_help_add_option(extcap_conf, "--remote-count <count>", "the number of frames to capture");
+
+ ws_opterr = 0;
+ ws_optind = 0;
+
+ if (argc == 1) {
+ extcap_help_print(extcap_conf);
+ goto end;
+ }
+
+ while ((result = ws_getopt_long(argc, argv, ":", longopts, &option_idx)) != -1) {
+
+ switch (result) {
+
+ case OPT_HELP:
+ extcap_help_print(extcap_conf);
+ ret = EXIT_SUCCESS;
+ goto end;
+
+ case OPT_VERSION:
+ extcap_version_print(extcap_conf);
+ ret = EXIT_SUCCESS;
+ goto end;
+
+ case OPT_REMOTE_HOST:
+ g_free(ssh_params->host);
+ ssh_params->host = g_strdup(ws_optarg);
+ break;
+
+ case OPT_REMOTE_PORT:
+ if (!ws_strtou16(ws_optarg, NULL, &ssh_params->port) || ssh_params->port == 0) {
+ ws_warning("Invalid port: %s", ws_optarg);
+ goto end;
+ }
+ break;
+
+ case OPT_REMOTE_USERNAME:
+ g_free(ssh_params->username);
+ ssh_params->username = g_strdup(ws_optarg);
+ break;
+
+ case OPT_REMOTE_PASSWORD:
+ g_free(ssh_params->password);
+ ssh_params->password = g_strdup(ws_optarg);
+ memset(ws_optarg, 'X', strlen(ws_optarg));
+ break;
+
+ case OPT_SSHKEY:
+ g_free(ssh_params->sshkey_path);
+ ssh_params->sshkey_path = g_strdup(ws_optarg);
+ break;
+
+ case OPT_SSHKEY_PASSPHRASE:
+ g_free(ssh_params->sshkey_passphrase);
+ ssh_params->sshkey_passphrase = g_strdup(ws_optarg);
+ memset(ws_optarg, 'X', strlen(ws_optarg));
+ break;
+
+ case OPT_REMOTE_INTERFACE:
+ g_free(remote_interface);
+ remote_interface = g_strdup(ws_optarg);
+ break;
+
+ case OPT_REMOTE_CHANNEL_FREQUENCY:
+ if (!ws_strtou16(ws_optarg, NULL, &remote_channel_frequency)) {
+ ws_warning("Invalid channel frequency: %s", ws_optarg);
+ goto end;
+ }
+ break;
+
+ case OPT_REMOTE_CHANNEL_WIDTH:
+ if (!ws_strtou16(ws_optarg, NULL, &remote_channel_width)) {
+ ws_warning("Invalid channel width: %s", ws_optarg);
+ goto end;
+ }
+ break;
+
+ case OPT_REMOTE_FILTER:
+ g_free(remote_filter);
+ remote_filter = g_strdup(ws_optarg);
+ break;
+
+ case OPT_REMOTE_COUNT:
+ if (!ws_strtou32(ws_optarg, NULL, &count)) {
+ ws_warning("Invalid value for count: %s", ws_optarg);
+ goto end;
+ }
+ break;
+
+ case ':':
+ /* missing option argument */
+ ws_warning("Option '%s' requires an argument", argv[ws_optind - 1]);
+ break;
+
+ default:
+ if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg)) {
+ ws_warning("Invalid option: %s", argv[ws_optind - 1]);
+ goto end;
+ }
+ }
+ }
+
+ extcap_cmdline_debug(argv, argc);
+
+ if (extcap_base_handle_interface(extcap_conf)) {
+ ret = EXIT_SUCCESS;
+ goto end;
+ }
+
+ if (extcap_conf->show_config) {
+ ret = list_config(extcap_conf->interface);
+ goto end;
+ }
+
+ err_msg = ws_init_sockets();
+ if (err_msg != NULL) {
+ ws_warning("ERROR: %s", err_msg);
+ g_free(err_msg);
+ ws_warning("%s", please_report_bug());
+ goto end;
+ }
+
+ if (extcap_conf->capture) {
+ char* filter;
+
+ if (!ssh_params->host) {
+ ws_warning("Missing parameter: --remote-host");
+ goto end;
+ }
+ remote_center_frequency = center_freq(remote_channel_frequency, remote_channel_width);
+ filter = concat_filters(extcap_conf->capture_filter, remote_filter);
+ ssh_params->debug = extcap_conf->debug;
+ ret = ssh_open_remote_connection(ssh_params, remote_capture_functions,
+ remote_interface, remote_channel_frequency, remote_channel_width, remote_center_frequency,
+ filter, count, extcap_conf->fifo);
+ g_free(filter);
+ } else {
+ ws_debug("You should not come here... maybe some parameter missing?");
+ ret = EXIT_FAILURE;
+ }
+
+end:
+ /* clean up stuff */
+ ssh_params_free(ssh_params);
+ g_free(remote_interface);
+ g_free(remote_filter);
+ extcap_base_cleanup(&extcap_conf);
+ return ret;
+}
+
+/*
+ * Editor modelines - https://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 8
+ * tab-width: 8
+ * indent-tabs-mode: t
+ * End:
+ *
+ * vi: set shiftwidth=8 tabstop=8 noexpandtab:
+ * :indentSize=8:tabSize=8:noTabs=false:
+ */