From: Jacob Masen-Smith Date: Wed, 20 Jun 2018 20:44:26 +0000 (-0700) Subject: Adds WinDivert support to Windows builds X-Git-Tag: suricata-4.1.0-rc1~42 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ec77632e84a106ddbcd0baef4e4368b4fe5c5f9e;p=thirdparty%2Fsuricata.git Adds WinDivert support to Windows builds Enables IPS functionality on Windows using the open-source (LGPLv3/GPLv2) WinDivert driver and API. From https://www.reqrypt.org/windivert-doc.html : "WinDivert is a user-mode capture/sniffing/modification/blocking/re-injection package for Windows Vista, Windows Server 2008, Windows 7, and Windows 8. WinDivert can be used to implement user-mode packet filters, packet sniffers, firewalls, NAT, VPNs, tunneling applications, etc., without the need to write kernel-mode code." - adds `--windivert [filter string]` and `--windivert-forward [filter string]` command-line options to enable WinDivert IPS mode. `--windivert[-forward] true` will open a filter for all traffic. See https://www.reqrypt.org/windivert-doc.html#filter_language for more information. Limitation: currently limited to `autofp` runmode. Additionally: - `tmm_modules` now zeroed during `RegisterAllModules` - fixed Windows Vista+ `inet_ntop` call in `PrintInet` - fixed `GetRandom` bug (nonexistent keys) on fresh Windows installs - fixed `RandomGetClock` building on Windows builds - Added WMI queries for MTU --- diff --git a/configure.ac b/configure.ac index bb1821c38c..ec4e6a12d0 100644 --- a/configure.ac +++ b/configure.ac @@ -233,7 +233,7 @@ ;; *-*-mingw32*) CFLAGS="${CFLAGS} -DOS_WIN32" - LDFLAGS="${LDFLAGS} -lws2_32" + LDFLAGS="${LDFLAGS} -lws2_32 -liphlpapi -lwbemuuid -lOle32 -lOleAut32 -lUuid" WINDOWS_PATH="yes" PCAP_LIB_NAME="wpcap" AC_DEFINE([HAVE_NON_POSIX_MKDIR], [1], [mkdir is not POSIX compliant: single arg]) @@ -1051,6 +1051,50 @@ fi fi + # WinDivert support + AC_ARG_ENABLE(windivert, + AS_HELP_STRING([--enable-windivert],[Enable WinDivert support [default=no]]),, + [enable_windivert="no"]) + + # WinDivert can only be enabled on Windows builds + AC_CHECK_DECL([OS_WIN32],,[enable_windivert="no"]) + + if test "x$enable_windivert" = "xyes"; then + # WinDivert requires Vista at a minimum. If the user has selected their own NTDDI_VERSION + # then don't override it. + AC_CHECK_DECL([NTDDI_VERSION],, + [CFLAGS="${CFLAGS} -DNTDDI_VERSION=NTDDI_VISTA -D_WIN32_WINNT=_WIN32_WINNT_VISTA"]) + + AC_DEFINE_UNQUOTED([WINDIVERT],[1],[Enable Windows WinDivert support for inline IDP]) + + AC_ARG_WITH(windivert_include, + [ --with-windivert-include=DIR WinDivert include path], + [with_windivert_include="$withval"],[with_windivert_include="no"]) + AC_ARG_WITH(windivert_libraries, + [ --with-windivert-libraries=DIR WinDivert library path], + [with_windivert_libraries="$withval"],[with_windivert_libraries="no"]) + + if test "$with_windivert_include" != "no"; then + CPPFLAGS="${CPPFLAGS} -I${with_windivert_include}" + fi + + if test "$with_windivert_libraries" != "no"; then + LDFLAGS="${LDFLAGS} -L${with_windivert_libraries}" + fi + + AC_CHECK_HEADER(windivert.h,,WINDIVERT_INC="no") + AC_CHECK_LIB(WinDivert, WinDivertOpen,, WINDIVERT_LIB="no") + + if test "$WINDIVERT_LIB" = "no" || test "$WINDIVERT_INC" = "no"; then + echo + echo " ERROR! WinDivert not found, go get it from" + echo " https://www.reqrypt.org/windivert.html" + echo + exit 1 + fi + fi + # /WinDivert + # prelude AC_ARG_ENABLE(prelude, AS_HELP_STRING([--enable-prelude], [Enable Prelude support for alerts]),,[enable_prelude=no]) @@ -2309,6 +2353,7 @@ SURICATA_BUILD_CONF="Suricata Configuration: Netmap support: ${enable_netmap} DAG enabled: ${enable_dag} Napatech enabled: ${enable_napatech} + WinDivert enabled: ${enable_windivert} Unix socket enabled: ${enable_unixsocket} Detection enabled: ${enable_detection} diff --git a/doc/userguide/setting-up-ipsinline-for-windows.rst b/doc/userguide/setting-up-ipsinline-for-windows.rst new file mode 100644 index 0000000000..cb37078c30 --- /dev/null +++ b/doc/userguide/setting-up-ipsinline-for-windows.rst @@ -0,0 +1,64 @@ +Setting up IPS/inline for Windows +================================= + +This guide explains how to work with Suricata in layer 4 inline mode using +WinDivert on Windows. + +First start by compiling Suricata with WinDivert support. For instructions, see +`Windows Installation +`_. +This documentation has not yet been updated with WinDivert information, so make +sure to add the following flags to `configure`: + +:: + + --enable-windivert=yes --with-windivert-include= --with-windivert-libraries= + +WinDivert.dll and WinDivert.sys must be in the same directory as the Suricata +executable. WinDivert automatically installs the driver when it is run. For more +information about WinDivert, see https://www.reqrypt.org/windivert-doc.html. + +To check if you have WinDivert enabled in your Suricata, enter the following +command in an elevated command prompt or terminal: + +:: + + suricata -c suricata.yaml --windivert [filter string] + +For information on the WinDivert filter language, see +https://www.reqrypt.org/windivert-doc.html#filter_language + +If Suricata is running on a gateway and is meant to protect the network behind +that gateway, you need to run WinDivert at the NETWORK_FORWARD layer. This can +be achieved using the following command: + +:: + + suricata -c suricata.yaml --windivert-forward [filter string] + +The filter is automatically stopped and normal traffic resumes when Suricata is +stopped. + +A quick start is to examine all traffic, in which case you can use the following +command: + +:: + + suricata -c suricata.yaml --windivert[-forward] true + +A few additional examples: + +Only TCP traffic: +:: + + suricata -c suricata.yaml --windivert tcp + +Only TCP traffic on port 80: +:: + + suricata -c suricata.yaml --windivert "tcp.DstPort == 80" + +TCP and ICMP traffic: +:: + + suricata -c suricata.yaml --windivert "tcp or icmp" \ No newline at end of file diff --git a/src/Makefile.am b/src/Makefile.am index e323e26749..b879ab421e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2,6 +2,7 @@ noinst_HEADERS = action-globals.h \ app-layer-nbss.h app-layer-dcerpc-common.h \ debug.h \ flow-private.h queue.h source-nfq-prototypes.h \ + source-windivert-prototypes.h \ suricata-common.h threadvars.h util-binsearch.h \ util-validate.h bin_PROGRAMS = suricata @@ -357,6 +358,7 @@ runmode-pfring.c runmode-pfring.h \ runmode-unittests.c runmode-unittests.h \ runmode-unix-socket.c runmode-unix-socket.h \ runmode-tile.c runmode-tile.h \ +runmode-windivert.c runmode-windivert.h \ runmodes.c runmodes.h \ rust.h \ source-af-packet.c source-af-packet.h \ @@ -373,6 +375,7 @@ source-pcap-file.c source-pcap-file.h \ source-pcap-file-directory-helper.c source-pcap-file-directory-helper.h \ source-pcap-file-helper.c source-pcap-file-helper.h \ source-pfring.c source-pfring.h \ +source-windivert.c source-windivert.h \ stream.c stream.h \ stream-tcp.c stream-tcp.h stream-tcp-private.h \ stream-tcp-inline.c stream-tcp-inline.h \ @@ -502,6 +505,7 @@ util-validate.h util-affinity.h util-affinity.c \ util-var.c util-var.h \ util-var-name.c util-var-name.h \ util-vector.h \ +win32-syscall.c win32-syscall.h \ win32-misc.c win32-misc.h \ win32-service.c win32-service.h \ win32-syslog.h diff --git a/src/decode.h b/src/decode.h index abe92f87bb..64b0103e94 100644 --- a/src/decode.h +++ b/src/decode.h @@ -64,6 +64,7 @@ enum PktSrcEnum { #include "source-af-packet.h" #include "source-mpipe.h" #include "source-netmap.h" +#include "source-windivert.h" #ifdef HAVE_PF_RING_FLOW_OFFLOAD #include "source-pfring.h" #endif @@ -474,6 +475,9 @@ typedef struct Packet_ PfringPacketVars pfring_v; #endif #endif +#ifdef WINDIVERT + WinDivertPacketVars windivert_v; +#endif /* WINDIVERT */ /** libpcap vars: shared by Pcap Live mode and Pcap File mode */ PcapPacketVars pcap_v; diff --git a/src/runmode-unittests.c b/src/runmode-unittests.c index 848e88d8d8..61fd5e6fda 100644 --- a/src/runmode-unittests.c +++ b/src/runmode-unittests.c @@ -122,6 +122,14 @@ #include "util-streaming-buffer.h" #include "util-lua.h" +#ifdef OS_WIN32 +#include "win32-syscall.h" +#endif + +#ifdef WINDIVERT +#include "source-windivert.h" +#endif + #ifdef HAVE_NSS #include #include @@ -218,6 +226,12 @@ static void RegisterUnittests(void) AppLayerUnittestsRegister(); MimeDecRegisterTests(); StreamingBufferRegisterTests(); +#ifdef OS_WIN32 + Win32SyscallRegisterTests(); +#endif +#ifdef WINDIVERT + SourceWinDivertRegisterTests(); +#endif } #endif diff --git a/src/runmode-windivert.c b/src/runmode-windivert.c new file mode 100644 index 0000000000..adccdb8b7f --- /dev/null +++ b/src/runmode-windivert.c @@ -0,0 +1,71 @@ +/* Copyright (C) 2018 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Jacob Masen-Smith + * + * Handling of WinDivert runmodes. + */ + +#include "suricata-common.h" +#include "tm-threads.h" +#include "conf.h" +#include "runmodes.h" +#include "runmode-windivert.h" +#include "output.h" + +#include "util-affinity.h" +#include "util-cpu.h" +#include "util-debug.h" +#include "util-device.h" +#include "util-runmodes.h" +#include "util-time.h" + + +static const char *default_mode; + + +const char *RunModeIpsWinDivertGetDefaultMode(void) { return default_mode; } + +void RunModeIpsWinDivertRegister(void) +{ + default_mode = "autofp"; + + RunModeRegisterNewRunMode( + RUNMODE_WINDIVERT, "autofp", + "Multi-threaded WinDivert IPS mode load-balanced by flow", + RunModeIpsWinDivertAutoFp); +} + +int RunModeIpsWinDivertAutoFp(void) +{ + SCEnter(); + int ret = 0; +#ifdef WINDIVERT + RunModeInitialize(); + + TimeModeSetLive(); + + LiveDeviceHasNoStats(); + + ret = RunModeSetIPSAutoFp(WinDivertGetThread, "ReceiveWinDivert", + "VerdictWinDivert", "DecodeWinDivert"); +#endif /* WINDIVERT */ + return ret; +} diff --git a/src/runmode-windivert.h b/src/runmode-windivert.h new file mode 100644 index 0000000000..5071fcf7a8 --- /dev/null +++ b/src/runmode-windivert.h @@ -0,0 +1,32 @@ +/* Copyright (C) 2018 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Jacob Masen-Smith + * + */ + +#ifndef __RUNMODE_WINDIVERT_H__ +#define __RUNMODE_WINDIVERT_H__ + +int RunModeIpsWinDivertAutoFp(void); +void RunModeIpsWinDivertRegister(void); +const char *RunModeIpsWinDivertGetDefaultMode(void); + +#endif /* __RUNMODE_WINDIVERT_H__ */ \ No newline at end of file diff --git a/src/runmodes.c b/src/runmodes.c index 6001fe37ca..ca72e53e1f 100644 --- a/src/runmodes.c +++ b/src/runmodes.c @@ -147,6 +147,12 @@ static const char *RunModeTranslateModeToName(int runmode) #endif case RUNMODE_UNIX_SOCKET: return "UNIX_SOCKET"; + case RUNMODE_WINDIVERT: +#ifdef WINDIVERT + return "WINDIVERT"; +#else + return "WINDIVERT(DISABLED)"; +#endif default: SCLogError(SC_ERR_UNKNOWN_RUN_MODE, "Unknown runtime mode. Aborting"); exit(EXIT_FAILURE); @@ -220,6 +226,7 @@ void RunModeRegisterRunModes(void) RunModeIdsNflogRegister(); RunModeTileMpipeRegister(); RunModeUnixSocketRegister(); + RunModeIpsWinDivertRegister(); #ifdef UNITTESTS UtRunModeRegister(); #endif @@ -328,6 +335,11 @@ void RunModeDispatch(int runmode, const char *custom_mode) case RUNMODE_NFLOG: custom_mode = RunModeIdsNflogGetDefaultMode(); break; +#ifdef WINDIVERT + case RUNMODE_WINDIVERT: + custom_mode = RunModeIpsWinDivertGetDefaultMode(); + break; +#endif default: SCLogError(SC_ERR_UNKNOWN_RUN_MODE, "Unknown runtime mode. Aborting"); exit(EXIT_FAILURE); diff --git a/src/runmodes.h b/src/runmodes.h index f0bb7735f5..3087ff9a0e 100644 --- a/src/runmodes.h +++ b/src/runmodes.h @@ -40,6 +40,7 @@ enum RunModes { RUNMODE_UNITTEST, RUNMODE_NAPATECH, RUNMODE_UNIX_SOCKET, + RUNMODE_WINDIVERT, RUNMODE_USER_MAX, /* Last standard running mode */ RUNMODE_LIST_KEYWORDS, RUNMODE_LIST_APP_LAYERS, @@ -106,6 +107,7 @@ int RunModeNeedsBypassManager(void); #include "runmode-nflog.h" #include "runmode-unix-socket.h" #include "runmode-netmap.h" +#include "runmode-windivert.h" int threading_set_cpu_affinity; extern float threading_detect_ratio; diff --git a/src/source-windivert-prototypes.h b/src/source-windivert-prototypes.h new file mode 100644 index 0000000000..acb0d7f8ab --- /dev/null +++ b/src/source-windivert-prototypes.h @@ -0,0 +1,31 @@ +/* Copyright (C) 2018 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Jacob Masen-Smith + */ + +#ifndef __SOURCE_WINDIVERT_PROTOTYPES_H__ +#define __SOURCE_WINDIVERT_PROTOTYPES_H__ + +void TmModuleReceiveWinDivertRegister(void); +void TmModuleVerdictWinDivertRegister(void); +void TmModuleDecodeWinDivertRegister(void); + +#endif /* __SOURCE_WINDIVERT_PROTOTYPES_H__ */ diff --git a/src/source-windivert.c b/src/source-windivert.c new file mode 100644 index 0000000000..1c549712d8 --- /dev/null +++ b/src/source-windivert.c @@ -0,0 +1,1021 @@ +/* Copyright (C) 2018 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Jacob Masen-Smith + * + * WinDivert emulation of netfilter_queue functionality to hook into Suricata's + * IPS mode. Supported solely on Windows. + * + */ + +#include "suricata-common.h" +#include "suricata.h" +#include "tm-threads.h" + +#include "util-byte.h" +#include "util-debug.h" +#include "util-device.h" +#include "util-error.h" +#include "util-ioctl.h" +#include "util-privs.h" +#include "util-unittest.h" + +#include "runmodes.h" + +#include "queue.h" + +#include "source-windivert-prototypes.h" +#include "source-windivert.h" + +#ifdef WINDIVERT +// clang-format off +#include +#include +#include +#include +// clang-format on +#endif + +#ifndef WINDIVERT +/* Gracefully handle the case where no WinDivert support is compiled in */ + +TmEcode NoWinDivertSupportExit(ThreadVars *, const void *, void **); + +void TmModuleReceiveWinDivertRegister(void) +{ + tmm_modules[TMM_RECEIVEWINDIVERT].name = "ReceiveWinDivert"; + tmm_modules[TMM_RECEIVEWINDIVERT].ThreadInit = NoWinDivertSupportExit; + tmm_modules[TMM_RECEIVEWINDIVERT].flags = TM_FLAG_RECEIVE_TM; +} + +void TmModuleVerdictWinDivertRegister(void) +{ + tmm_modules[TMM_VERDICTWINDIVERT].name = "VerdictWinDivert"; + tmm_modules[TMM_VERDICTWINDIVERT].ThreadInit = NoWinDivertSupportExit; +} + +void TmModuleDecodeWinDivertRegister(void) +{ + tmm_modules[TMM_DECODEWINDIVERT].name = "DecodeWinDivert"; + tmm_modules[TMM_DECODEWINDIVERT].ThreadInit = NoWinDivertSupportExit; + tmm_modules[TMM_DECODEWINDIVERT].flags = TM_FLAG_DECODE_TM; +} + +TmEcode NoWinDivertSupportExit(ThreadVars *tv, const void *initdata, + void **data) +{ + SCLogError( + SC_ERR_WINDIVERT_NOSUPPORT, + "Error creating thread %s: you do not have support for WinDivert " + "enabled; please recompile with --enable-windivert", + tv->name); + exit(EXIT_FAILURE); +} + +#else /* implied we do have WinDivert support */ + +typedef struct WinDivertThreadVars_ { + WinDivertHandle filter_handle; + + int thread_num; + CaptureStats stats; + int64_t qpc_start_time; + int64_t qpc_start_count; + int64_t qpc_freq_usec; + + TmSlot *slot; + + bool offload_enabled; + + TAILQ_HEAD(, LiveDevice_) live_devices; +} WinDivertThreadVars; + +#define WINDIVERT_MAX_QUEUE 16 +static WinDivertThreadVars g_wd_tv[WINDIVERT_MAX_QUEUE]; +static WinDivertQueueVars g_wd_qv[WINDIVERT_MAX_QUEUE]; +static uint16_t g_wd_num = 0; +static SCMutex g_wd_init_lock = SCMUTEX_INITIALIZER; + +void *WinDivertGetThread(int n) +{ + if (n >= g_wd_num) { + return NULL; + } + return (void *)&g_wd_tv[n]; +} + +void *WinDivertGetQueue(int n) +{ + if (n >= g_wd_num) { + return NULL; + } + return (void *)&g_wd_qv[n]; +} + +// not defined in MinGW winerror.h +#define ERROR_INVALID_IMAGE_HASH 577L +#define ERROR_DATA_NOT_ACCEPTED 592L + +/** + * \brief return an error description for Win32 error values commonly returned + * by WinDivert + */ +static const char *WinDivertGetErrorString(DWORD error_code) +{ + switch (error_code) { + // WinDivertOpen errors + case ERROR_FILE_NOT_FOUND: + return "The driver files WinDivert32.sys or WinDivert64.sys were " + "not found."; + case ERROR_ACCESS_DENIED: + return "Suricata must be run with Administrator privileges."; + case ERROR_INVALID_PARAMETER: + return "The WinDivert packet filter string is invalid."; + case ERROR_INVALID_IMAGE_HASH: + return "The WinDivert32.sys or WinDivert64.sys driver does not " + "have a valid digital signature, or your copy of Windows is " + "not up-to-date. Windows 7 and Server 2008 users need to " + "run Windows Update or install the following patch from " + "Microsoft: http://support.microsoft.com/kb/2949927"; + case ERROR_DRIVER_BLOCKED: + return "This error occurs for various reasons, including: " + "attempting to load the 32-bit WinDivert.sys driver on a " + "64-bit system (or vice versa); the WinDivert.sys driver is " + "blocked by security software; or you are using a " + "virtualization environment that does not support " + "drivers."; + case EPT_S_NOT_REGISTERED: + return "This error occurs when the Base Filtering Engine service " + "has been disabled."; + case ERROR_PROC_NOT_FOUND: + return "The error may occur for Windows Vista users. The " + "solution is to install the following patch from Microsoft: " + "http://support.microsoft.com/kb/2761494."; + + // WinDivertSend errors + case ERROR_HOST_UNREACHABLE: + return "This error occurs when an impostor packet (with " + "pAddr->Impostor set to 1) is injected and the ip.TTL or " + "ipv6.HopLimit field goes to zero. This is a defense of " + "last resort against infinite loops caused by impostor " + "packets."; + case ERROR_DATA_NOT_ACCEPTED: + return "This error is returned when the user application attempts " + "to inject a malformed packet. It may also be returned for " + "valid inbound packets, and the Windows TCP/IP stack " + "rejects the packet for some reason."; + case ERROR_RETRY: + return "The underlying cause of this error is unknown. However, " + "this error usually occurs when certain kinds of " + "anti-virus/firewall/security software is installed, and " + "the error message usually resolves once the offending " + "program is uninstalled. This suggests a software " + "compatibility problem."; + default: + return ""; + } +} + +/** + * \brief logs a WinDivert error at Error level. + */ +#define WinDivertLogError(err_code) \ + do { \ + const char *win_err_str = Win32GetErrorString((err_code), NULL); \ + SCLogError(SC_ERR_WINDIVERT_GENERIC, \ + "WinDivertOpen failed, error %" PRId32 " (0x%08" PRIx32 \ + "): %s %s", \ + (uint32_t)(err_code), (uint32_t)(err_code), win_err_str, \ + WinDivertGetErrorString(err_code)); \ + LocalFree((LPVOID)win_err_str); \ + } while (0); + +/** + * \brief initializes QueryPerformanceCounter values so we can get + * absolute time from WinDivert timestamps. + */ +static void WinDivertInitQPCValues(WinDivertThreadVars *wd_tv) +{ + struct timeval now; + + TimeGet(&now); + (void)QueryPerformanceCounter((LARGE_INTEGER *)&wd_tv->qpc_start_count); + + wd_tv->qpc_start_time = + (uint64_t)now.tv_sec * (1000 * 1000) + (uint64_t)now.tv_usec; + + (void)QueryPerformanceFrequency((LARGE_INTEGER *)&wd_tv->qpc_freq_usec); + /* \bug: clock drift? */ + wd_tv->qpc_freq_usec /= 1000 * 1000; +} + +/** + * \brief gets a timeval from a WinDivert timestamp + */ +static struct timeval WinDivertTimestampToTimeval(WinDivertThreadVars *wd_tv, + INT64 timestamp_count) +{ + struct timeval ts; + + int64_t qpc_delta = (int64_t)timestamp_count - wd_tv->qpc_start_count; + int64_t unix_usec = + wd_tv->qpc_start_time + (qpc_delta / wd_tv->qpc_freq_usec); + + ts.tv_sec = (long)(unix_usec / (1000 * 1000)); + ts.tv_usec = (long)(unix_usec - (int64_t)ts.tv_sec * (1000 * 1000)); + + return ts; +} + +/** + * \brief initialize a WinDivert filter + * + * \param filter a WinDivert filter string as defined at + * https://www.reqrypt.org/windivert-doc.html#filter_language + * + * \retval 0 on success + * \retval -1 on failure + */ +int WinDivertRegisterQueue(bool forward, char *filter_str) +{ + SCEnter(); + int ret = 0; + + WINDIVERT_LAYER layer = + forward ? WINDIVERT_LAYER_NETWORK_FORWARD : WINDIVERT_LAYER_NETWORK; + + /* validate the filter string */ + const char *error_str; + uint32_t error_pos; + bool valid = WinDivertHelperCheckFilter(filter_str, layer, &error_str, + &error_pos); + if (!valid) { + SCLogWarning( + SC_ERR_WINDIVERT_INVALID_FILTER, + "Invalid filter \"%s\" supplied to WinDivert: %s at position " + "%" PRId32 "", + filter_str, error_str, error_pos); + SCReturnInt(SC_ERR_WINDIVERT_INVALID_FILTER); + } + + /* initialize the queue */ + SCMutexLock(&g_wd_init_lock); + + if (g_wd_num >= WINDIVERT_MAX_QUEUE) { + SCLogError(SC_ERR_INVALID_ARGUMENT, + "Too many WinDivert queues specified %" PRId32 "", g_wd_num); + ret = -1; + goto unlock; + } + if (g_wd_num == 0) { + /* on first registration, zero-initialize all array structs */ + memset(&g_wd_tv, 0, sizeof(g_wd_tv)); + memset(&g_wd_qv, 0, sizeof(g_wd_qv)); + } + + /* init thread vars */ + WinDivertThreadVars *wd_tv = &g_wd_tv[g_wd_num]; + wd_tv->thread_num = g_wd_num; + + /* init queue vars */ + WinDivertQueueVars *wd_qv = &g_wd_qv[g_wd_num]; + wd_qv->queue_num = g_wd_num; + + WinDivertInitQPCValues(wd_tv); + + /* copy filter to persistent storage */ + size_t filter_len = strlen(filter_str); + size_t copy_len = + strlcpy(wd_qv->filter_str, filter_str, sizeof(wd_qv->filter_str)); + if (filter_len > copy_len) { + SCLogWarning(SC_ERR_WINDIVERT_TOOLONG_FILTER, + "Queue length exceeds storage by %" PRId32 " bytes", + (int32_t)(filter_len - copy_len)); + ret = -1; + goto unlock; + } + + wd_qv->layer = layer; + wd_qv->priority = + g_wd_num; /* priority set in the order filters are defined */ + wd_qv->flags = 0; /* normal inline function */ + + SCMutexInit(&wd_qv->filter_init_mutex, NULL); + SCMutexInit(&wd_qv->counters_mutex, NULL); + + g_wd_num++; + +unlock: + SCMutexUnlock(&g_wd_init_lock); + + if (ret == 0) { + // stringify queue index to use as thread name descriptor + char wd_num_str[6]; + wd_num_str[sizeof(wd_num_str) - 1] = 0; + snprintf(wd_num_str, sizeof(wd_num_str), "%" PRId16 "", g_wd_num); + + LiveRegisterDevice(wd_num_str); + + SCLogDebug("Queue %" PRId16 " registered", wd_qv->queue_num); + } + + return ret; +} + +/* forward declarations of internal functions */ +/* Receive functions */ +TmEcode ReceiveWinDivertLoop(ThreadVars *, void *, void *); +TmEcode ReceiveWinDivertThreadInit(ThreadVars *, const void *, void **); +TmEcode ReceiveWinDivertThreadDeinit(ThreadVars *, void *); +void ReceiveWinDivertThreadExitStats(ThreadVars *, void *); + +/* Verdict functions */ +TmEcode VerdictWinDivert(ThreadVars *, Packet *, void *, PacketQueue *, + PacketQueue *); +TmEcode VerdictWinDivertThreadInit(ThreadVars *, const void *, void **); +TmEcode VerdictWinDivertThreadDeinit(ThreadVars *, void *); + +/* Decode functions */ +TmEcode DecodeWinDivert(ThreadVars *, Packet *, void *, PacketQueue *, + PacketQueue *); +TmEcode DecodeWinDivertThreadInit(ThreadVars *, const void *, void **); +TmEcode DecodeWinDivertThreadDeinit(ThreadVars *, void *); + +/* internal helper functions */ +static TmEcode WinDivertRecvHelper(ThreadVars *tv, WinDivertThreadVars *); +static TmEcode WinDivertVerdictHelper(ThreadVars *tv, Packet *p); +static TmEcode WinDivertCloseHelper(WinDivertThreadVars *); + +static TmEcode WinDivertCollectFilterDevices(WinDivertThreadVars *, + WinDivertQueueVars *); +static bool WinDivertIfaceMatchFilter(const char *filter_string, int if_index); +static void WinDivertDisableOffloading(WinDivertThreadVars *); +static void WinDivertRestoreOffloading(WinDivertThreadVars *); + +void TmModuleReceiveWinDivertRegister(void) +{ + TmModule *tm_ptr = &tmm_modules[TMM_RECEIVEWINDIVERT]; + + tm_ptr->name = "ReceiveWinDivert"; + tm_ptr->ThreadInit = ReceiveWinDivertThreadInit; + tm_ptr->PktAcqLoop = ReceiveWinDivertLoop; + tm_ptr->ThreadExitPrintStats = ReceiveWinDivertThreadExitStats; + tm_ptr->ThreadDeinit = ReceiveWinDivertThreadDeinit; + tm_ptr->flags = TM_FLAG_RECEIVE_TM; +} + +void TmModuleVerdictWinDivertRegister(void) +{ + TmModule *tm_ptr = &tmm_modules[TMM_VERDICTWINDIVERT]; + + tm_ptr->name = "VerdictWinDivert"; + tm_ptr->ThreadInit = VerdictWinDivertThreadInit; + tm_ptr->Func = VerdictWinDivert; + tm_ptr->ThreadDeinit = VerdictWinDivertThreadDeinit; +} + +void TmModuleDecodeWinDivertRegister(void) +{ + TmModule *tm_ptr = &tmm_modules[TMM_DECODEWINDIVERT]; + + tm_ptr->name = "DecodeWinDivert"; + tm_ptr->ThreadInit = DecodeWinDivertThreadInit; + tm_ptr->Func = DecodeWinDivert; + tm_ptr->ThreadDeinit = DecodeWinDivertThreadDeinit; + tm_ptr->flags = TM_FLAG_DECODE_TM; +} + +/** + * \brief Main WinDivert packet receive pump + */ +TmEcode ReceiveWinDivertLoop(ThreadVars *tv, void *data, void *slot) +{ + SCEnter(); + + WinDivertThreadVars *wd_tv = (WinDivertThreadVars *)data; + wd_tv->slot = ((TmSlot *)slot)->slot_next; + + while (true) { + if (suricata_ctl_flags & SURICATA_STOP) { + SCReturnInt(TM_ECODE_OK); + } + + if (unlikely(WinDivertRecvHelper(tv, wd_tv) != TM_ECODE_OK)) { + SCReturnInt(TM_ECODE_FAILED); + } + + StatsSyncCountersIfSignalled(tv); + } + + SCReturnInt(TM_ECODE_OK); +} + +static TmEcode WinDivertRecvHelper(ThreadVars *tv, WinDivertThreadVars *wd_tv) +{ + SCEnter(); + +#ifdef COUNTERS + WinDivertQueueVars *wd_qv = WinDivertGetQueue(wd_tv->thread_num); +#endif /* COUNTERS */ + + /* make sure we have at least one packet in the packet pool, to prevent us + * from alloc'ing packets at line rate + */ + PacketPoolWait(); + + /* obtain a packet buffer */ + Packet *p = PacketGetFromQueueOrAlloc(); + if (unlikely(p == NULL)) { + SCLogDebug( + "PacketGetFromQueueOrAlloc() - failed to obtain Packet buffer"); + SCReturnInt(TM_ECODE_FAILED); + } + PKT_SET_SRC(p, PKT_SRC_WIRE); + + /* receive packet, depending on offload status. MTU is used as an estimator + * for direct data alloc size, and this is meaningless if large segments are + * coalesced before they reach WinDivert */ + bool success = false; + uint32_t pktlen = 0; + if (wd_tv->offload_enabled) { + /* allocate external, if not already */ + PacketCallocExtPkt(p, MAX_PAYLOAD_SIZE); + + success = + WinDivertRecv(wd_tv->filter_handle, p->ext_pkt, + MAX_PAYLOAD_SIZE, &p->windivert_v.addr, &pktlen); + } else { + success = WinDivertRecv(wd_tv->filter_handle, GET_PKT_DIRECT_DATA(p), + GET_PKT_DIRECT_MAX_SIZE(p), + &p->windivert_v.addr, &pktlen); + } + SET_PKT_LEN(p, pktlen); + + if (!success) { +#ifdef COUNTERS + SCMutexLock(&wd_qv->counters_mutex); + wd_qv->errs++; + SCMutexUnlock(&wd_qv->counters_mutex); +#endif /* COUNTERS */ + + /* ensure packet length is zero to trigger an error in packet decoding + */ + SET_PKT_LEN(p, 0); + + SCLogInfo("WinDivertRecv failed: error %" PRIu32 "", + (uint32_t)(GetLastError())); + SCReturnInt(TM_ECODE_FAILED); + } + SCLogDebug("Packet received, length %" PRId32 "", GET_PKT_LEN(p)); + + p->ts = WinDivertTimestampToTimeval(wd_tv, p->windivert_v.addr.Timestamp); + p->windivert_v.thread_num = wd_tv->thread_num; + +#ifdef COUNTERS + SCMutexLock(&wd_qv->counters_mutex); + wd_qv->pkts++; + wd_qv->bytes += GET_PKT_LEN(p); + SCMutexUnlock(&wd_qv->counters_mutex); +#endif /* COUNTERS */ + + /* Do the packet processing by calling TmThreadsSlotProcessPkt, this will, + * depending on the running mode, pass the packet to the treatment functions + * or push it to a packet pool. So processing time can vary. + */ + if (TmThreadsSlotProcessPkt(tv, wd_tv->slot, p) != TM_ECODE_OK) { + TmqhOutputPacketpool(tv, p); + SCReturnInt(TM_ECODE_FAILED); + } + + SCReturnInt(TM_ECODE_OK); +} + +/** + * \brief Init function for ReceiveWinDivert + * + * ReceiveWinDivertThreadInit sets up receiving packets via WinDivert. + * + * \param tv pointer to generic thread vars + * \param initdata pointer to the interface passed from the user + * \param data out-pointer to the WinDivert-specific thread vars + */ +TmEcode ReceiveWinDivertThreadInit(ThreadVars *tv, const void *initdata, + void **data) +{ + SCEnter(); + TmEcode ret = TM_ECODE_OK; + + WinDivertThreadVars *wd_tv = (WinDivertThreadVars *)initdata; + + if (wd_tv == NULL) { + SCLogError(SC_ERR_INVALID_ARGUMENT, "initdata == NULL"); + SCReturnInt(TM_ECODE_FAILED); + } + + WinDivertQueueVars *wd_qv = WinDivertGetQueue(wd_tv->thread_num); + + if (wd_qv == NULL) { + SCLogError(SC_ERR_INVALID_ARGUMENT, "queue == NULL"); + SCReturnInt(TM_ECODE_FAILED); + } + + SCMutexLock(&wd_qv->filter_init_mutex); + /* does the queue already have an active handle? */ + if (wd_qv->filter_handle != NULL && + wd_qv->filter_handle != INVALID_HANDLE_VALUE) { + goto unlock; + } + + TAILQ_INIT(&wd_tv->live_devices); + + if (WinDivertCollectFilterDevices(wd_tv, wd_qv) == TM_ECODE_OK) { + WinDivertDisableOffloading(wd_tv); + } else { + SCLogWarning(SC_ERR_SYSCALL, + "Failed to obtain network devices for WinDivert filter"); + } + + /* we open now so that we can immediately start handling packets, + * instead of losing however many would occur between registering the + * queue and starting a receive thread. */ + wd_qv->filter_handle = WinDivertOpen(wd_qv->filter_str, wd_qv->layer, + wd_qv->priority, wd_qv->flags); + if (wd_qv->filter_handle == INVALID_HANDLE_VALUE) { + WinDivertLogError(GetLastError()); + ret = TM_ECODE_FAILED; + goto unlock; + } + +unlock: + if (ret == 0) { /* success */ + wd_tv->filter_handle = wd_qv->filter_handle; + + /* set our return context */ + *data = wd_tv; + } + + SCMutexUnlock(&wd_qv->filter_init_mutex); + SCReturnInt(ret); +} + +/** + * \brief collect all devices covered by this filter in the thread vars' + * live devices list + * + * \param wd_tv pointer to WinDivert thread vars + * \param wd_qv pointer to WinDivert queue vars + */ +static TmEcode WinDivertCollectFilterDevices(WinDivertThreadVars *wd_tv, + WinDivertQueueVars *wd_qv) +{ + SCEnter(); + TmEcode ret = TM_ECODE_OK; + + IP_ADAPTER_ADDRESSES *if_info_list; + DWORD err = (DWORD)Win32GetAdaptersAddresses(&if_info_list); + if (err != NO_ERROR) { + ret = TM_ECODE_FAILED; + goto release; + } + + for (IP_ADAPTER_ADDRESSES *if_info = if_info_list; if_info != NULL; + if_info = if_info->Next) { + + if (WinDivertIfaceMatchFilter(wd_qv->filter_str, if_info->IfIndex)) { + SCLogConfig("Found adapter %s matching WinDivert filter %s", + if_info->AdapterName, wd_qv->filter_str); + + LiveDevice *new_ldev = SCCalloc(1, sizeof(LiveDevice)); + if (new_ldev == NULL) { + ret = TM_ECODE_FAILED; + goto release; + } + new_ldev->dev = SCStrdup(if_info->AdapterName); + if (new_ldev->dev == NULL) { + ret = TM_ECODE_FAILED; + goto release; + } + TAILQ_INSERT_TAIL(&wd_tv->live_devices, new_ldev, next); + } else { + SCLogDebug("Adapter %s does not match WinDivert filter %s", + if_info->AdapterName, wd_qv->filter_str); + } + } + +release: + SCFree(if_info_list); + + SCReturnInt(ret); +} + +/** + * \brief test if the specified interface index matches the filter + */ +static bool WinDivertIfaceMatchFilter(const char *filter_string, int if_index) +{ + bool match = false; + + WINDIVERT_ADDRESS if_addr = {}; + if_addr.IfIdx = if_index; + + uint8_t dummy[4] = {4, 4, 4, 4}; + + match = WinDivertHelperEvalFilter(filter_string, WINDIVERT_LAYER_NETWORK, + dummy, sizeof(dummy), &if_addr); + if (!match) { + int err = GetLastError(); + if (err != 0) { + SCLogWarning(SC_ERR_WINDIVERT_GENERIC, + "Failed to evaluate filter: 0x%" PRIx32, err); + } + } + + return match; +} + +/** + * \brief disable offload status on devices for this filter + * + * \param wd_tv pointer to WinDivert thread vars + */ +static void WinDivertDisableOffloading(WinDivertThreadVars *wd_tv) +{ + for (LiveDevice *ldev = TAILQ_FIRST(&wd_tv->live_devices); ldev != NULL; + ldev = TAILQ_NEXT(ldev, next)) { + + if (LiveGetOffload() == 0) { + if (GetIfaceOffloading(ldev->dev, 1, 1) == 1) { + wd_tv->offload_enabled = true; + } + } else { + if (DisableIfaceOffloading(ldev, 1, 1) != 1) { + wd_tv->offload_enabled = true; + } + } + } +} + +/** + * \brief enable offload status on devices for this filter + * + * \param wd_tv pointer to WinDivert thread vars + */ +static void WinDivertRestoreOffloading(WinDivertThreadVars *wd_tv) +{ + for (LiveDevice *ldev = TAILQ_FIRST(&wd_tv->live_devices); ldev != NULL; + ldev = TAILQ_NEXT(ldev, next)) { + + RestoreIfaceOffloading(ldev); + } +} + +/** + * \brief Deinit function releases resources at exit. + * + * \param tv pointer to generic thread vars + * \param data pointer to WinDivert-specific thread vars + */ +TmEcode ReceiveWinDivertThreadDeinit(ThreadVars *tv, void *data) +{ + SCEnter(); + + WinDivertThreadVars *wd_tv = (WinDivertThreadVars *)data; + + SCReturnCT(WinDivertCloseHelper(wd_tv), "TmEcode"); +} + +/** + * \brief ExitStats prints stats to stdout at exit + * + * + * \param tv pointer to generic thread vars + * \param data pointer to WinDivert-specific thread vars + */ +void ReceiveWinDivertThreadExitStats(ThreadVars *tv, void *data) +{ + SCEnter(); + + WinDivertThreadVars *wd_tv = (WinDivertThreadVars *)data; + WinDivertQueueVars *wd_qv = WinDivertGetQueue(wd_tv->thread_num); + if (wd_qv == NULL) { + SCLogError(SC_ERR_INVALID_ARGUMENT, "queue == NULL"); + SCReturn; + } + + SCMutexLock(&wd_qv->counters_mutex); + + SCLogInfo("(%s) Packets %" PRIu32 ", Bytes %" PRIu64 ", Errors %" PRIu32 "", + tv->name, wd_qv->pkts, wd_qv->bytes, wd_qv->errs); + SCLogInfo("(%s) Verdict: Accepted %" PRIu32 ", Dropped %" PRIu32 + ", Replaced %" PRIu32 "", + tv->name, wd_qv->accepted, wd_qv->dropped, wd_qv->replaced); + + SCMutexUnlock(&wd_qv->counters_mutex); + SCReturn; +} + +/** + * \brief WinDivert verdict module packet entry function + */ +TmEcode VerdictWinDivert(ThreadVars *tv, Packet *p, void *data, PacketQueue *pq, + PacketQueue *postpq) +{ + SCEnter(); + + TmEcode ret = TM_ECODE_OK; + + ret = WinDivertVerdictHelper(tv, p); + if (ret != TM_ECODE_OK) { + SCReturnInt(ret); + } + + SCReturnInt(TM_ECODE_OK); +} + +/** + * \brief internal helper function to do the bulk of verdict work + */ +static TmEcode WinDivertVerdictHelper(ThreadVars *tv, Packet *p) +{ + SCEnter(); + WinDivertThreadVars *wd_tv = WinDivertGetThread(p->windivert_v.thread_num); + + /* update counters */ + CaptureStatsUpdate(tv, &wd_tv->stats, p); + +#ifdef COUNTERS + WinDivertQueueVars *wd_qv = WinDivertGetQueue(wd_tv->thread_num); +#endif /* COUNTERS */ + + p->windivert_v.verdicted = true; + + /* can't verdict a "fake" packet */ + if (PKT_IS_PSEUDOPKT(p)) { + SCReturnInt(TM_ECODE_OK); + } + + /* the handle has been closed and we can no longer use it */ + if (wd_tv->filter_handle == INVALID_HANDLE_VALUE || + wd_tv->filter_handle == NULL) { + SCReturnInt(TM_ECODE_OK); + } + + /* we can't verdict tunnel packets without ensuring all encapsulated + * packets are verdicted */ + if (IS_TUNNEL_PKT(p)) { + bool finalVerdict = VerdictTunnelPacket(p); + if (!finalVerdict) { + SCReturnInt(TM_ECODE_OK); + } + + // the action needs to occur on the root packet. + if (p->root != NULL) { + p = p->root; + } + } + + /* DROP simply means we do nothing; the WinDivert driver does the rest. + */ + if (PACKET_TEST_ACTION(p, ACTION_DROP)) { +#ifdef COUNTERS + SCMutexLock(&wd_qv->counters_mutex); + wd_qv->dropped++; + SCMutexUnlock(&wd_qv->counters_mutex); +#endif /* counters */ + + SCReturnInt(TM_ECODE_OK); + } + + bool success = WinDivertSend(wd_tv->filter_handle, GET_PKT_DATA(p), + GET_PKT_LEN(p), &p->windivert_v.addr, NULL); + + if (unlikely(!success)) { + WinDivertLogError(GetLastError()); + SCReturnInt(TM_ECODE_FAILED); + } + +#ifdef COUNTERS + SCMutexLock(&wd_qv->counters_mutex); + wd_qv->accepted++; + SCMutexUnlock(&wd_qv->counters_mutex); +#endif /* counters */ + + SCReturnInt(TM_ECODE_OK); +} + +/** + * \brief init the verdict thread, which is piggybacked off the receive + * thread + */ +TmEcode VerdictWinDivertThreadInit(ThreadVars *tv, const void *initdata, + void **data) +{ + SCEnter(); + + WinDivertThreadVars *wd_tv = (WinDivertThreadVars *)initdata; + + CaptureStatsSetup(tv, &wd_tv->stats); + + *data = wd_tv; + + SCReturnInt(TM_ECODE_OK); +} + +/** + * \brief deinit the verdict thread and shut down the WinDivert driver if + * it's still up. + */ +TmEcode VerdictWinDivertThreadDeinit(ThreadVars *tv, void *data) +{ + SCEnter(); + + WinDivertThreadVars *wd_tv = (WinDivertThreadVars *)data; + + SCReturnCT(WinDivertCloseHelper(wd_tv), "TmEcode"); +} + +/** + * \brief decode a raw packet submitted to suricata from the WinDivert + * driver + * + * All WinDivert packets are IPv4/v6, but do not include the network layer + * to differentiate the two, so instead we must check the version and go + * from there. + */ +TmEcode DecodeWinDivert(ThreadVars *tv, Packet *p, void *data, PacketQueue *pq, + PacketQueue *postpq) +{ + SCEnter(); + + IPV4Hdr *ip4h = (IPV4Hdr *)GET_PKT_DATA(p); + IPV6Hdr *ip6h = (IPV6Hdr *)GET_PKT_DATA(p); + DecodeThreadVars *d_tv = (DecodeThreadVars *)data; + + /* XXX HACK: flow timeout can call us for injected pseudo packets + * see bug: + * https://redmine.openinfosecfoundation.org/issues/1107 + */ + if (PKT_IS_PSEUDOPKT(p)) + SCReturnInt(TM_ECODE_OK); + + DecodeUpdatePacketCounters(tv, d_tv, p); + + if (IPV4_GET_RAW_VER(ip4h) == 4) { + SCLogDebug("IPv4 packet"); + DecodeIPV4(tv, d_tv, p, GET_PKT_DATA(p), GET_PKT_LEN(p), pq); + } else if (IPV6_GET_RAW_VER(ip6h) == 6) { + SCLogDebug("IPv6 packet"); + DecodeIPV6(tv, d_tv, p, GET_PKT_DATA(p), GET_PKT_LEN(p), pq); + } else { + SCLogDebug("packet unsupported by WinDivert, first byte: %02x", + *GET_PKT_DATA(p)); + } + + PacketDecodeFinalize(tv, d_tv, p); + + SCReturnInt(TM_ECODE_OK); +} + +TmEcode DecodeWinDivertThreadInit(ThreadVars *tv, const void *initdata, + void **data) +{ + SCEnter(); + + DecodeThreadVars *d_tv = DecodeThreadVarsAlloc(tv); + if (d_tv == NULL) { + SCReturnInt(TM_ECODE_FAILED); + } + + DecodeRegisterPerfCounters(d_tv, tv); + + *data = d_tv; + + SCReturnInt(TM_ECODE_OK); +} + +TmEcode DecodeWinDivertThreadDeinit(ThreadVars *tv, void *data) +{ + SCEnter(); + + if (data != NULL) { + DecodeThreadVarsFree(tv, data); + } + + SCReturnInt(TM_ECODE_OK); +} + +/** + * \brief helper function for use with ThreadDeinit functions + */ +static TmEcode WinDivertCloseHelper(WinDivertThreadVars *wd_tv) +{ + SCEnter(); + TmEcode ret = TM_ECODE_OK; + + WinDivertQueueVars *wd_qv = WinDivertGetQueue(wd_tv->thread_num); + if (wd_qv == NULL) { + SCLogDebug("No queue could be found for thread num %" PRId32 "", + wd_tv->thread_num); + SCReturnInt(TM_ECODE_FAILED); + } + + SCMutexLock(&wd_qv->filter_init_mutex); + + /* check if there's nothing to close */ + if (wd_qv->filter_handle == INVALID_HANDLE_VALUE || + wd_qv->filter_handle == NULL) { + goto unlock; + } + + if (!WinDivertClose(wd_qv->filter_handle)) { + SCLogError(SC_ERR_FATAL, "WinDivertClose failed: error %" PRIu32 "", + (uint32_t)(GetLastError())); + ret = TM_ECODE_FAILED; + goto unlock; + } + + (void)WinDivertRestoreOffloading(wd_tv); + + wd_qv->filter_handle = NULL; + +unlock: + SCMutexUnlock(&wd_qv->filter_init_mutex); + + if (ret == TM_ECODE_OK) { + SCMutexDestroy(&wd_qv->filter_init_mutex); + SCMutexDestroy(&wd_qv->counters_mutex); + } + + SCReturnInt(ret); +} + +#ifdef UNITTESTS +static int SourceWinDivertTestIfaceMatchFilter(void) +{ + struct testdata { + const char *filter; + int if_index; + bool expected; + }; + + struct testdata tests[] = { + {"true", 11, true}, + {"ifIdx=11", 11, true}, + {"ifIdx==11", 11, true}, + {"ifIdx!=11", 1, true}, + {"ifIdx!=11", 11, false}, + {"ifIdx=3", 4, false}, + {"ifIdx=11 || ifIdx=5", 5, true}, + {"ifIdx=11 || ifIdx=4", 5, false}, + {"ifIdx<3 || ifIdx>7", 8, true}, + {"ifIdx<3 || ifIdx>7", 5, false}, + {"ifIdx>3 or ifIdx<7", 5, true}, + {"ifIdx>3 && ifIdx<7", 5, true}, + {"ifIdx>3 && ifIdx<7", 1, false}, + {"(ifIdx > 3 && ifIdx < 7) or ifIdx == 11", 11, true}}; + + size_t count = (sizeof(tests) / sizeof(tests[0])); + + for (size_t i = 0; i < count; i++) { + struct testdata test = tests[i]; + + bool actual = WinDivertIfaceMatchFilter(test.filter, test.if_index); + if (actual != test.expected) { + printf("WinDivertIfaceMatchFilter(\"%s\", %d) == %d, expected %d\n", + test.filter, test.if_index, actual, test.expected); + FAIL; + } + } + PASS; +} +#endif + +/** + * \brief this function registers unit tests for the WinDivert Source + */ +void SourceWinDivertRegisterTests() +{ +#ifdef UNITTESTS + UtRegisterTest("SourceWinDivertTestIfaceMatchFilter", + SourceWinDivertTestIfaceMatchFilter); +#endif +} + +#endif /* WINDIVERT */ \ No newline at end of file diff --git a/src/source-windivert.h b/src/source-windivert.h new file mode 100644 index 0000000000..71574993ec --- /dev/null +++ b/src/source-windivert.h @@ -0,0 +1,82 @@ +/* Copyright (C) 2018 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * + * \file + * + * \author Jacob Masen-Smith + * + */ + +#ifndef __SOURCE_WINDIVERT_H__ +#define __SOURCE_WINDIVERT_H__ + +#ifdef WINDIVERT + +#include "windivert.h" + +#define WINDIVERT_FILTER_MAXLEN 128 /* from windivert_device.h */ + +typedef void *WinDivertHandle; + +/** + * \brief WinDivertQueueVars is the queue configuration and other miscellaneous + * information about the specific queue/filter. + * + * see https://reqrypt.org/windivert-doc.html#divert_open for more info + */ +typedef struct WinDivertQueueVars_ +{ + int queue_num; + + /* see https://reqrypt.org/windivert-doc.html#filter_language */ + char filter_str[WINDIVERT_FILTER_MAXLEN + 1]; + WINDIVERT_LAYER layer; + int16_t priority; + uint64_t flags; + + WinDivertHandle filter_handle; + /* only needed for setup/teardown; Recv/Send are internally synchronized */ + SCMutex filter_init_mutex; + + /* counters */ + uint32_t pkts; + uint64_t bytes; + uint32_t errs; + uint32_t accepted; + uint32_t dropped; + uint32_t replaced; + SCMutex counters_mutex; +} WinDivertQueueVars; + +typedef struct WinDivertPacketVars_ +{ + int thread_num; + + WINDIVERT_ADDRESS addr; + bool verdicted; +} WinDivertPacketVars; + +int WinDivertRegisterQueue(bool forward, char *filter_str); +void *WinDivertGetThread(int thread); +void *WinDivertGetQueue(int queue); + +void SourceWinDivertRegisterTests(void); + +#endif /* WINDIVERT */ +#endif /* __SOURCE_WINDIVERT_H__ */ \ No newline at end of file diff --git a/src/suricata.c b/src/suricata.c index 2784ad5ef9..8fb17db279 100644 --- a/src/suricata.c +++ b/src/suricata.c @@ -95,6 +95,9 @@ #include "source-netmap.h" #include "source-mpipe.h" +#include "source-windivert.h" +#include "source-windivert-prototypes.h" + #include "respond-reject.h" #include "flow.h" @@ -651,6 +654,10 @@ static void PrintUsage(const char *progname) #endif #ifdef HAVE_MPIPE printf("\t--mpipe : run with tilegx mpipe interface(s)\n"); +#endif +#ifdef WINDIVERT + printf("\t--windivert : run in inline WinDivert mode\n"); + printf("\t--windivert-forward : run in inline WinDivert mode, as a gateway\n"); #endif printf("\t--set name=value : set a configuration value\n"); printf("\n"); @@ -855,6 +862,9 @@ int g_ut_covered; void RegisterAllModules(void) { + // zero all module storage + memset(tmm_modules, 0, TMM_SIZE * sizeof(TmModule)); + /* commanders */ TmModuleUnixManagerRegister(); /* managers */ @@ -912,6 +922,11 @@ void RegisterAllModules(void) /* nflog */ TmModuleReceiveNFLOGRegister(); TmModuleDecodeNFLOGRegister(); + + /* windivert */ + TmModuleReceiveWinDivertRegister(); + TmModuleVerdictWinDivertRegister(); + TmModuleDecodeWinDivertRegister(); } static TmEcode LoadYamlConfig(SCInstance *suri) @@ -1533,6 +1548,10 @@ static TmEcode ParseCommandLine(int argc, char** argv, SCInstance *suri) {"build-info", 0, &build_info, 1}, #ifdef HAVE_MPIPE {"mpipe", optional_argument, 0, 0}, +#endif +#ifdef WINDIVERT + {"windivert", required_argument, 0, 0}, + {"windivert-forward", required_argument, 0, 0}, #endif {"set", required_argument, 0, 0}, #ifdef HAVE_NFLOG @@ -1812,6 +1831,45 @@ static TmEcode ParseCommandLine(int argc, char** argv, SCInstance *suri) } } #endif + else if(strcmp((long_opts[option_index]).name, "windivert-forward") == 0) { +#ifdef WINDIVERT + if (suri->run_mode == RUNMODE_UNKNOWN) { + suri->run_mode = RUNMODE_WINDIVERT; + if (WinDivertRegisterQueue(true, optarg) == -1) { + exit(EXIT_FAILURE); + } + } else if (suri->run_mode == RUNMODE_WINDIVERT) { + if (WinDivertRegisterQueue(true, optarg) == -1) { + exit(EXIT_FAILURE); + } + } else { + SCLogError(SC_ERR_MULTIPLE_RUN_MODE, "more than one run mode " + "has been specified"); + PrintUsage(argv[0]); + exit(EXIT_FAILURE); + } + } + else if(strcmp((long_opts[option_index]).name, "windivert") == 0) { + if (suri->run_mode == RUNMODE_UNKNOWN) { + suri->run_mode = RUNMODE_WINDIVERT; + if (WinDivertRegisterQueue(false, optarg) == -1) { + exit(EXIT_FAILURE); + } + } else if (suri->run_mode == RUNMODE_WINDIVERT) { + if (WinDivertRegisterQueue(false, optarg) == -1) { + exit(EXIT_FAILURE); + } + } else { + SCLogError(SC_ERR_MULTIPLE_RUN_MODE, "more than one run mode " + "has been specified"); + PrintUsage(argv[0]); + exit(EXIT_FAILURE); + } +#else + SCLogError(SC_ERR_WINDIVERT_NOSUPPORT,"WinDivert not enabled. Make sure to pass --enable-windivert to configure when building."); + return TM_ECODE_FAILED; +#endif /* WINDIVERT */ + } else if (strcmp((long_opts[option_index]).name, "set") == 0) { if (optarg != NULL) { /* Quick validation. */ @@ -2405,10 +2463,27 @@ static int ConfigGetCaptureValue(SCInstance *suri) * back on a sane default. */ const char *temp_default_packet_size; if ((ConfGet("default-packet-size", &temp_default_packet_size)) != 1) { + int mtu = 0; int lthread; int nlive; int strip_trailing_plus = 0; switch (suri->run_mode) { +#ifdef WINDIVERT + case RUNMODE_WINDIVERT: + /* by default, WinDivert collects from all devices */ + mtu = GetGlobalMTUWin32(); + + if (mtu > 0) { + g_default_mtu = mtu; + /* SLL_HEADER_LEN is the longest header + 8 for VLAN */ + default_packet_size = mtu + SLL_HEADER_LEN + 8; + break; + } + + g_default_mtu = DEFAULT_MTU; + default_packet_size = DEFAULT_PACKET_SIZE; + break; +#endif /* WINDIVERT */ case RUNMODE_PCAP_DEV: case RUNMODE_AFP_DEV: case RUNMODE_NETMAP: @@ -2420,7 +2495,7 @@ static int ConfigGetCaptureValue(SCInstance *suri) nlive = LiveGetDeviceCount(); for (lthread = 0; lthread < nlive; lthread++) { const char *live_dev = LiveGetDeviceName(lthread); - char dev[32]; + char dev[128]; /* need to be able to support GUID names on Windows */ (void)strlcpy(dev, live_dev, sizeof(dev)); if (strip_trailing_plus) { @@ -2429,7 +2504,7 @@ static int ConfigGetCaptureValue(SCInstance *suri) dev[len-1] = '\0'; } } - int mtu = GetIfaceMTU(dev); + mtu = GetIfaceMTU(dev); g_default_mtu = MAX(mtu, g_default_mtu); unsigned int iface_max_packet_size = GetIfaceMaxPacketSize(dev); diff --git a/src/tm-modules.c b/src/tm-modules.c index 869ba184de..310e969240 100644 --- a/src/tm-modules.c +++ b/src/tm-modules.c @@ -234,6 +234,9 @@ const char * TmModuleTmmIdToString(TmmId id) CASE_CODE (TMM_DETECTLOADER); CASE_CODE (TMM_RECEIVENETMAP); CASE_CODE (TMM_DECODENETMAP); + CASE_CODE (TMM_RECEIVEWINDIVERT); + CASE_CODE (TMM_VERDICTWINDIVERT); + CASE_CODE (TMM_DECODEWINDIVERT); CASE_CODE (TMM_SIZE); } diff --git a/src/tm-threads-common.h b/src/tm-threads-common.h index d55884db81..33a95f9214 100644 --- a/src/tm-threads-common.h +++ b/src/tm-threads-common.h @@ -61,6 +61,9 @@ typedef enum { TMM_STATSLOGGER, TMM_RECEIVENFLOG, TMM_DECODENFLOG, + TMM_RECEIVEWINDIVERT, + TMM_VERDICTWINDIVERT, + TMM_DECODEWINDIVERT, TMM_FLOWMANAGER, TMM_FLOWRECYCLER, diff --git a/src/util-error.c b/src/util-error.c index 6a394da3fa..7fc7dd6f17 100644 --- a/src/util-error.c +++ b/src/util-error.c @@ -352,6 +352,10 @@ const char * SCErrorToString(SCError err) CASE_CODE (SC_WARN_JA3_DISABLED); CASE_CODE (SC_ERR_PCAP_LOG_COMPRESS); CASE_CODE (SC_ERR_FSEEK); + CASE_CODE (SC_ERR_WINDIVERT_GENERIC); + CASE_CODE (SC_ERR_WINDIVERT_NOSUPPORT); + CASE_CODE (SC_ERR_WINDIVERT_INVALID_FILTER); + CASE_CODE (SC_ERR_WINDIVERT_TOOLONG_FILTER); CASE_CODE (SC_ERR_MAX); } diff --git a/src/util-error.h b/src/util-error.h index 4654f046fe..8e9015753f 100644 --- a/src/util-error.h +++ b/src/util-error.h @@ -342,6 +342,10 @@ typedef enum { SC_WARN_JA3_DISABLED, SC_ERR_PCAP_LOG_COMPRESS, SC_ERR_FSEEK, + SC_ERR_WINDIVERT_GENERIC, + SC_ERR_WINDIVERT_NOSUPPORT, + SC_ERR_WINDIVERT_INVALID_FILTER, + SC_ERR_WINDIVERT_TOOLONG_FILTER, SC_ERR_MAX, } SCError; diff --git a/src/util-ioctl.c b/src/util-ioctl.c index 22de89b456..dbbda654f6 100644 --- a/src/util-ioctl.c +++ b/src/util-ioctl.c @@ -44,6 +44,10 @@ #include #endif +#ifdef OS_WIN32 +#include "win32-syscall.h" +#endif + #include "util-ioctl.h" /** @@ -77,6 +81,7 @@ static int GetIfaceMaxHWHeaderLength(const char *pcap_dev) return 8 + SLL_HEADER_LEN; } + /** * \brief output the link MTU * @@ -85,7 +90,7 @@ static int GetIfaceMaxHWHeaderLength(const char *pcap_dev) */ int GetIfaceMTU(const char *pcap_dev) { -#ifdef SIOCGIFMTU +#if defined SIOCGIFMTU struct ifreq ifr; int fd; @@ -106,6 +111,8 @@ int GetIfaceMTU(const char *pcap_dev) SCLogInfo("Found an MTU of %d for '%s'", ifr.ifr_mtu, pcap_dev); return ifr.ifr_mtu; +#elif defined OS_WIN32 + return GetIfaceMTUWin32(pcap_dev); #else /* ioctl is not defined, let's pretend returning 0 is ok */ return 0; @@ -675,6 +682,8 @@ int GetIfaceOffloading(const char *dev, int csum, int other) return GetIfaceOffloadingLinux(dev, csum, other); #elif defined SIOCGIFCAP return GetIfaceOffloadingBSD(dev); +#elif defined OS_WIN32 + return GetIfaceOffloadingWin32(dev, csum, other); #else return 0; #endif @@ -686,6 +695,8 @@ int DisableIfaceOffloading(LiveDevice *dev, int csum, int other) return DisableIfaceOffloadingLinux(dev, csum, other); #elif defined SIOCSIFCAP return DisableIfaceOffloadingBSD(dev); +#elif defined OS_WIN32 + return DisableIfaceOffloadingWin32(dev, csum, other); #else return 0; #endif @@ -699,6 +710,8 @@ void RestoreIfaceOffloading(LiveDevice *dev) RestoreIfaceOffloadingLinux(dev); #elif defined SIOCSIFCAP RestoreIfaceOffloadingBSD(dev); +#elif defined OS_WIN32 + RestoreIfaceOffloadingWin32(dev); #endif } } diff --git a/src/util-ioctl.h b/src/util-ioctl.h index 2d0c74740d..5088438972 100644 --- a/src/util-ioctl.h +++ b/src/util-ioctl.h @@ -24,6 +24,10 @@ #include "suricata-common.h" #include "util-device.h" +#ifdef OS_WIN32 +#include "win32-syscall.h" +#endif + int GetIfaceMTU(const char *pcap_dev); int GetIfaceMaxPacketSize(const char *pcap_dev); int GetIfaceOffloading(const char *dev, int csum, int other); diff --git a/src/util-print.c b/src/util-print.c index f654f4b5c0..c0d72f2d2d 100644 --- a/src/util-print.c +++ b/src/util-print.c @@ -268,7 +268,17 @@ const char *PrintInet(int af, const void *src, char *dst, socklen_t size) { switch (af) { case AF_INET: +#if defined(OS_WIN32) && NTDDI_VERSION >= NTDDI_VISTA +{ + // because Windows has to provide a non-conformant inet_ntop, of + // course! + struct in_addr _src; + memcpy(&_src, src, sizeof(struct in_addr)); + return inet_ntop(af, &_src, dst, size); +} +#else return inet_ntop(af, src, dst, size); +#endif case AF_INET6: /* Format IPv6 without deleting zeroes */ return PrintInetIPv6(src, dst, size); diff --git a/src/util-random.c b/src/util-random.c index b2945e53b6..eef70e2c3e 100644 --- a/src/util-random.c +++ b/src/util-random.c @@ -27,6 +27,8 @@ #include "suricata-common.h" #include "util-random.h" + +#if !(defined(HAVE_WINCRYPT_H) && defined(OS_WIN32)) #if defined(HAVE_CLOCK_GETTIME) static long int RandomGetClock(void) @@ -39,7 +41,7 @@ static long int RandomGetClock(void) return value; } -#elif !(defined(HAVE_WINCRYPT_H) && defined(OS_WIN32)) +#else static long int RandomGetPosix(void) { @@ -53,6 +55,7 @@ static long int RandomGetPosix(void) } #endif +#endif /* !(defined(HAVE_WINCRYPT_H) && defined(OS_WIN32)) */ #if defined(HAVE_WINCRYPT_H) && defined(OS_WIN32) #include @@ -63,9 +66,21 @@ long int RandomGet(void) return 0; HCRYPTPROV p; - if (!(CryptAcquireContext(&p, NULL, NULL, - PROV_RSA_FULL, 0))) { - return -1; + if (!CryptAcquireContext(&p, NULL, NULL, PROV_RSA_FULL, 0)) { + DWORD err = GetLastError(); + SCLogDebug("CryptAcquireContext error: %" PRIu32, (uint32_t)err); + if (err == (DWORD)NTE_BAD_KEYSET) { + /* The key doesn't exist yet, create it */ + if (!CryptAcquireContext(&p, NULL, NULL, PROV_RSA_FULL, + CRYPT_NEWKEYSET)) { + + SCLogDebug("CryptAcquireContext error: %" PRIu32, + (uint32_t)err); + return -1; + } + } else { + return -1; + } } long int value = 0; diff --git a/src/util-strptime.c b/src/util-strptime.c index b5163436d7..e0475ec611 100644 --- a/src/util-strptime.c +++ b/src/util-strptime.c @@ -291,7 +291,7 @@ recurse: sse *= 10; sse += *bp++ - '0'; rulim /= 10; - } while ((sse * 10 <= TIME_MAX) && + } while (((uint64_t)sse * 10 <= TIME_MAX) && rulim && *bp >= '0' && *bp <= '9'); if (sse < 0 || (uint64_t)sse > TIME_MAX) { diff --git a/src/win32-misc.c b/src/win32-misc.c index ed52eb5b7a..1dc28a6926 100644 --- a/src/win32-misc.c +++ b/src/win32-misc.c @@ -52,6 +52,8 @@ void unsetenv(const char *name) SCFree(str); } +/* these functions have been defined on Vista and later */ +#if NTDDI_VERSION < NTDDI_VISTA const char* inet_ntop(int af, const void *src, char *dst, uint32_t cnt) { if (af == AF_INET) @@ -116,5 +118,6 @@ int inet_pton(int af, const char *src, void *dst) return -1; } +#endif #endif /* OS_WIN32 */ diff --git a/src/win32-misc.h b/src/win32-misc.h index 7242f9c448..24bb42e31d 100644 --- a/src/win32-misc.h +++ b/src/win32-misc.h @@ -24,6 +24,8 @@ #ifndef __WIN32_MISC_H__ #define __WIN32_MISC_H__ +#include + #define index strchr #define rindex strrchr @@ -36,8 +38,10 @@ void setenv(const char *name, const char *value, int overwrite); void unsetenv(const char *name); +#if NTDDI_VERSION < NTDDI_VISTA const char* inet_ntop(int af, const void *src, char *dst, uint32_t cnt); int inet_pton(int af, const char *src, void *dst); +#endif #define geteuid() (0) diff --git a/src/win32-syscall.c b/src/win32-syscall.c new file mode 100644 index 0000000000..b17674c58b --- /dev/null +++ b/src/win32-syscall.c @@ -0,0 +1,1691 @@ +/* Copyright (C) 2018 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Jacob Masen-Smith + * + * Isolation for WMI/COM functionality + * + * References: + * https://msdn.microsoft.com/en-us/library/aa390421(v=vs.85).aspx + * https://blogs.msdn.microsoft.com/ndis/2015/03/21/mapping-from-ndis-oids-to-wmi-classes/ + * https://stackoverflow.com/questions/1431103/how-to-obtain-data-from-wmi-using-a-c-application + * https://docs.microsoft.com/en-us/windows-hardware/drivers/network/oid-tcp-offload-parameters + * https://wutils.com/wmi/root/wmi/ms_409/msndis_tcpoffloadcurrentconfig/ + * https://docs.microsoft.com/en-us/windows-hardware/drivers/network/oid-tcp-offload-current-config + * https://wutils.com/wmi/root/wmi/msndis_tcpoffloadparameters/ + */ + +#ifdef OS_WIN32 + +#include +#include + +// clang-format off +#include +#include +#include +#include +#include +#include +#include +// clang-format on + +/* Windows strsafe.h defines _snprintf as an undefined warning type */ +#undef _snprintf +#define _snprintf StringCbPrintfA + +#include "util-debug.h" +#include "util-device.h" +#include "util-mem.h" +#include "util-unittest.h" + +#include "suricata.h" + +#include "win32-syscall.h" + +/** + * \brief return only the GUID portion of the name + */ +static const char *StripPcapPrefix(const char *pcap_dev) +{ + return strchr(pcap_dev, '{'); +} + +/** + * \brief get the adapter address list, which includes IP status/details + * + * Clients MUST FREE the returned list to avoid memory leaks. + */ +uint32_t Win32GetAdaptersAddresses(IP_ADAPTER_ADDRESSES **pif_info_list) +{ + DWORD err = NO_ERROR; + IP_ADAPTER_ADDRESSES *if_info_list; + + ULONG size = 0; + err = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, NULL, &size); + if (err != ERROR_BUFFER_OVERFLOW) { + return err; + } + if_info_list = SCMalloc((size_t)size); + if (if_info_list == NULL) { + return ERROR_NOT_ENOUGH_MEMORY; + } + err = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, if_info_list, &size); + if (err != NO_ERROR) { + SCFree(if_info_list); + return err; + } + + *pif_info_list = if_info_list; + return NO_ERROR; +} + +uint32_t Win32FindAdapterAddresses(IP_ADAPTER_ADDRESSES *if_info_list, + const char *adapter_name, + IP_ADAPTER_ADDRESSES **pif_info) +{ + DWORD ret = NO_ERROR; + adapter_name = StripPcapPrefix(adapter_name); + *pif_info = NULL; + + for (IP_ADAPTER_ADDRESSES *current = if_info_list; current != NULL; + current = current->Next) { + + /* if we find the adapter, return that data */ + if (strncmp(adapter_name, current->AdapterName, strlen(adapter_name)) == + 0) { + + *pif_info = current; + break; + } + } + + if (*pif_info == NULL) { + ret = ERROR_NOT_FOUND; + } + + return ret; +} + +#if NTDDI_VERSION < NTDDI_VISTA + +int GetIfaceMTUWin32(const char *pcap_dev) { return 0; } +int GetGlobalMTUWin32(void) { return 0; } + +int GetIfaceOffloadingWin32(const char *ifname, int csum, int other) +{ + SCLogWarning(SC_ERR_SYSCALL, "Suricata not targeted for Windows Vista or " + "higher. Network offload interrogation not " + "available."); + return -1; +} +int DisableIfaceOffloadingWin32(LiveDevice *ldev, int csum, int other) +{ + SCLogWarning(SC_ERR_SYSCALL, "Suricata not targeted for Windows Vista or " + "higher. Network offload interrogation not " + "available."); + return -1; +} +int RestoreIfaceOffloadingWin32(LiveDevice *ldev) +{ + SCLogWarning(SC_ERR_SYSCALL, "Suricata not targeted for Windows Vista or " + "higher. Network offload interrogation not " + "available."); + return -1; +} + +#else /* NTDDI_VERSION >= NTDDI_VISTA */ + +static HMODULE wmiutils_dll = NULL; + +/** + * \brief obtain the WMI utilities DLL + */ +static HMODULE WmiUtils(void) +{ + if (wmiutils_dll == NULL) { + wmiutils_dll = + LoadLibraryA("C:\\Windows\\System32\\wbem\\wmiutils.dll"); + } + + return wmiutils_dll; +} + +/** + * \brief allocate a BSTR from a converted unsigned integer + */ +static BSTR utob(uint64_t ui) +{ + wchar_t buf[20]; + _ui64tow(ui, buf, 10); + return SysAllocString(buf); +} + +/** + * \brief Get the win32/wmi error string + * + * The caller should use the LocalFree function on the returned pointer to free + * the buffer when it is no longer needed. + */ +const char *Win32GetErrorString(DWORD error_code, HMODULE ext_module) +{ + char *error_string = NULL; + + DWORD flags = + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS; + if (ext_module != NULL) { + flags |= FORMAT_MESSAGE_FROM_HMODULE; + } else { + flags |= FORMAT_MESSAGE_FROM_SYSTEM; + } + + FormatMessageA(flags, ext_module, error_code, 0, (LPTSTR)&error_string, 0, + NULL); + + if (error_string == NULL) { + return ""; + } + + error_string[strlen(error_string) - 2] = 0; // remove line breaks + + return error_string; +} + +#ifdef DEBUG +#define Win32HResultLogDebug(hr) \ + _Win32HResultLog(SC_LOG_DEBUG, (hr), __FILE__, __FUNCTION__, __LINE__) +#else +#define Win32HResultLogDebug(hr) +#endif /* DEBUG */ + +/** + * \brief log an HRESULT + */ +static void _Win32HResultLog(SCLogLevel level, HRESULT hr, const char *file, + const char *function, const int line) +{ + const char *err_str = Win32GetErrorString(hr, WmiUtils()); + SCLog(level, file, function, line, "HRESULT: %s (0x%08" PRIx32 ")", err_str, + (uint32_t)(hr)); + LocalFree((LPVOID)err_str); +} + +/** + * \brief log a WBEM error + */ +#define WbemLogDebug(hr) (_WbemLogDebug)((hr), __FILE__, __FUNCTION__, __LINE__) + +static void _WbemLogDebug(HRESULT hr, const char *file, const char *function, + const int line) +{ +#ifdef DEBUG + IErrorInfo *err_info; + BSTR err_description; + char *err_description_mb = NULL; + + _Win32HResultLog(SC_LOG_DEBUG, hr, file, function, line); + + GetErrorInfo(0, &err_info); + if (!SUCCEEDED( + err_info->lpVtbl->GetDescription(err_info, &err_description))) { + // not much to do when your error log errors out... + goto release; + } + + err_description_mb = SCMalloc(SysStringLen(err_description) + 1); + + if (err_description_mb == NULL) { + // not much to do when your error log errors out... + goto release; + } + + // do the actual multibyte conversion + err_description_mb[SysStringLen(err_description)] = 0; + wcstombs(err_description_mb, err_description, + SysStringLen(err_description)); + + // log the description + SCLog(SC_LOG_DEBUG, file, function, line, "WBEM error: %s", + err_description_mb); + +release: + SCFree(err_description_mb); + SysFreeString(err_description); +#endif /* DEBUG */ +} + +/** + * \brief get the maximum transmissible unit for the specified pcap device name + */ +int GetIfaceMTUWin32(const char *pcap_dev) +{ + DWORD err = NO_ERROR; + + int mtu = 0; + + IP_ADAPTER_ADDRESSES *if_info_list = NULL, *if_info = NULL; + err = Win32GetAdaptersAddresses(&if_info_list); + if (err != NO_ERROR) { + mtu = -1; + goto release; + } + err = Win32FindAdapterAddresses(if_info_list, pcap_dev, &if_info); + if (err != NO_ERROR) { + mtu = -1; + goto release; + } + + mtu = if_info->Mtu; + +release: + SCFree(if_info_list); + + if (err != S_OK) { + const char *errbuf = Win32GetErrorString(err, WmiUtils()); + SCLogWarning(SC_ERR_SYSCALL, + "Failure when trying to get MTU via syscall for '%s': %s " + "(0x%08" PRIx32 ")", + pcap_dev, errbuf, (uint32_t)err); + LocalFree((LPVOID)errbuf); + } else { + SCLogInfo("Found an MTU of %d for '%s'", mtu, pcap_dev); + } + + return mtu; +} + +/** + * \brief get the maximum transmissible unit for all devices on the system + */ +int GetGlobalMTUWin32() +{ + uint32_t mtu = 0; + + DWORD err = NO_ERROR; + IP_ADAPTER_ADDRESSES *if_info_list = NULL; + + /* get a list of all adapters' data */ + err = Win32GetAdaptersAddresses(&if_info_list); + if (err != NO_ERROR) { + goto fail; + } + + /* now search for the right adapter in the list */ + IP_ADAPTER_ADDRESSES *if_info = NULL; + for (if_info = if_info_list; if_info != NULL; if_info = if_info->Next) { + /* -1 (uint) is an invalid value */ + if (if_info->Mtu == (uint32_t)-1) { + continue; + } + + /* we want to return the largest MTU value so we allocate enough */ + mtu = max(mtu, if_info->Mtu); + } + + SCFree(if_info_list); + + SCLogInfo("Found a global MTU of %" PRIu32, mtu); + return (int)mtu; + +fail: + SCFree(if_info_list); + + const char *errbuf = NULL; + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, err, 0, (LPTSTR)&errbuf, 0, NULL); + + SCLogWarning( + SC_ERR_SYSCALL, + "Failure when trying to get global MTU via syscall: %s (%" PRId32 + ")", + errbuf, (uint32_t)err); + + return -1; +} + +#define ReleaseObject(objptr) \ + do { \ + if ((objptr) != NULL) { \ + (objptr)->lpVtbl->Release(objptr); \ + (objptr) = NULL; \ + } \ + } while (0); + +typedef enum Win32TcpOffloadFlags_ { + WIN32_TCP_OFFLOAD_FLAG_NONE = 0, + WIN32_TCP_OFFLOAD_FLAG_CSUM_IP4RX = 1, + WIN32_TCP_OFFLOAD_FLAG_CSUM_IP4TX = 1 << 1, + WIN32_TCP_OFFLOAD_FLAG_CSUM_IP6RX = 1 << 2, + WIN32_TCP_OFFLOAD_FLAG_CSUM_IP6TX = 1 << 3, + WIN32_TCP_OFFLOAD_FLAG_LSOV1_IP4 = 1 << 4, + WIN32_TCP_OFFLOAD_FLAG_LSOV2_IP4 = 1 << 5, + WIN32_TCP_OFFLOAD_FLAG_LSOV2_IP6 = 1 << 6, + + /* aggregates */ + WIN32_TCP_OFFLOAD_FLAG_CSUM = WIN32_TCP_OFFLOAD_FLAG_CSUM_IP4RX | + WIN32_TCP_OFFLOAD_FLAG_CSUM_IP4TX | + WIN32_TCP_OFFLOAD_FLAG_CSUM_IP6RX | + WIN32_TCP_OFFLOAD_FLAG_CSUM_IP6TX, + WIN32_TCP_OFFLOAD_FLAG_CSUM_IP4 = WIN32_TCP_OFFLOAD_FLAG_CSUM_IP4RX | + WIN32_TCP_OFFLOAD_FLAG_CSUM_IP4TX, + WIN32_TCP_OFFLOAD_FLAG_CSUM_IP6 = WIN32_TCP_OFFLOAD_FLAG_CSUM_IP6RX | + WIN32_TCP_OFFLOAD_FLAG_CSUM_IP6TX, + WIN32_TCP_OFFLOAD_FLAG_LSO = WIN32_TCP_OFFLOAD_FLAG_LSOV1_IP4 | + WIN32_TCP_OFFLOAD_FLAG_LSOV2_IP4 | + WIN32_TCP_OFFLOAD_FLAG_LSOV2_IP6, +} Win32TcpOffloadFlags; + +typedef struct ComInstance_ { + IWbemLocator *locator; + IWbemServices *services; +} ComInstance; + +/** + * \brief Creates a COM instance connected to the specified resource + */ +static HRESULT ComInstanceInit(ComInstance *instance, LPCWSTR resource) +{ + HRESULT hr = S_OK; + + instance->locator = NULL; + instance->services = NULL; + + BSTR resource_bstr = SysAllocString(resource); + if (resource_bstr == NULL) { + hr = HRESULT_FROM_WIN32(E_OUTOFMEMORY); + SCLogWarning(SC_ERR_SYSCALL, "Failed to allocate BSTR"); + goto release; + } + + /* connect to COM */ + hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); + if (hr == S_FALSE) { + /* already initialized */ + hr = S_OK; + } else { + if (hr != S_OK) { + SCLogWarning(SC_ERR_SYSCALL, + "COM CoInitializeEx failed: 0x%" PRIx32, (uint32_t)hr); + goto release; + } + hr = CoInitializeSecurity( + NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, + RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL); + if (hr != S_OK) { + SCLogWarning(SC_ERR_SYSCALL, + "COM CoInitializeSecurity failed: 0x%" PRIx32, + (uint32_t)hr); + goto release; + } + } + + /* connect to WMI */ + hr = CoCreateInstance(&CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, + &IID_IWbemLocator, (LPVOID *)&instance->locator); + if (hr != S_OK) { + SCLogWarning(SC_ERR_SYSCALL, "COM CoCreateInstance failed: 0x%" PRIx32, + (uint32_t)hr); + goto release; + } + hr = instance->locator->lpVtbl->ConnectServer( + instance->locator, resource_bstr, NULL, NULL, NULL, 0, NULL, NULL, + &instance->services); + if (hr != S_OK) { + SCLogWarning(SC_ERR_SYSCALL, "COM ConnectServer failed: 0x%" PRIx32, + (uint32_t)hr); + goto release; + } + +release: + SysFreeString(resource_bstr); + + return hr; +} + +/** + * \brief Releases resources for a COM instance. + */ +static void ComInstanceRelease(ComInstance *instance) +{ + if (instance == NULL) { + return; + } + ReleaseObject(instance->services); + ReleaseObject(instance->locator); +} + +/** + * \brief obtains a class definition from COM services + */ +static HRESULT GetWbemClass(ComInstance *instance, LPCWSTR name, + IWbemClassObject **p_class) +{ + HRESULT hr = WBEM_S_NO_ERROR; + BSTR name_bstr = NULL; + + if (instance == NULL || name == NULL || p_class == NULL || + *p_class != NULL) { + hr = HRESULT_FROM_WIN32(E_INVALIDARG); + Win32HResultLogDebug(hr); + goto release; + } + + /* allocate name string */ + name_bstr = SysAllocString(name); + if (name_bstr == NULL) { + hr = HRESULT_FROM_WIN32(E_OUTOFMEMORY); + SCLogWarning(SC_ERR_SYSCALL, "Failed to allocate BSTR"); + goto release; + } + + /* obtain object */ + hr = instance->services->lpVtbl->GetObject(instance->services, name_bstr, + WBEM_FLAG_RETURN_WBEM_COMPLETE, + NULL, p_class, NULL); + if (hr != S_OK) { + WbemLogDebug(hr); + SCLogWarning(SC_ERR_SYSCALL, "WMI GetObject failed: 0x%" PRIx32, + (uint32_t)hr); + goto release; + } + +release: + SysFreeString(name_bstr); + + return hr; +} + +/** + * \brief spawns an empty class instance of the specified type + */ +static HRESULT GetWbemClassInstance(ComInstance *instance, LPCWSTR name, + IWbemClassObject **p_instance) +{ + HRESULT hr = WBEM_S_NO_ERROR; + + IWbemClassObject *class = NULL; + + hr = GetWbemClass(instance, name, &class); + if (hr != WBEM_S_NO_ERROR) { + goto release; + } + + hr = class->lpVtbl->SpawnInstance(class, 0, p_instance); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + SCLogWarning(SC_ERR_SYSCALL, "WMI SpawnInstance failed: 0x%" PRIx32, + (uint32_t)hr); + goto release; + } + +release: + return hr; +} + +typedef struct WbemMethod_ { + ComInstance *com_instance; + + BSTR method_name; + + IWbemClassObject *in_params, *out_params; +} WbemMethod; + +/** + * \brief initializes resources for a WMI method handle + */ +static HRESULT GetWbemMethod(ComInstance *com_instance, LPCWSTR class_name, + LPCWSTR method_name, WbemMethod *method) +{ + HRESULT hr = S_OK; + IWbemClassObject *class = NULL; + + method->com_instance = com_instance; + + BSTR class_name_bstr = SysAllocString(class_name); + if (class_name_bstr == NULL) { + hr = HRESULT_FROM_WIN32(E_OUTOFMEMORY); + SCLogWarning(SC_ERR_SYSCALL, "Failed to allocate BSTR"); + goto release; + } + method->method_name = SysAllocString(method_name); + if (method->method_name == NULL) { + hr = HRESULT_FROM_WIN32(E_OUTOFMEMORY); + SCLogWarning(SC_ERR_SYSCALL, "Failed to allocate BSTR"); + goto release; + } + + /* find our class definition to retrieve parameters */ + hr = GetWbemClass(com_instance, class_name, &class); + if (hr != WBEM_S_NO_ERROR) { + goto release; + } + + /* find the method on the retrieved class */ + hr = class->lpVtbl->GetMethod(class, method_name, 0, &method->in_params, + &method->out_params); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + SCLogWarning(SC_ERR_SYSCALL, "WMI GetMethod failed: 0x%" PRIx32, + (uint32_t)hr); + goto release; + } + +release: + ReleaseObject(class); + + SysFreeString(class_name_bstr); + + return hr; +} + +/** + * \brief Releases resources for a WMI method handle + */ +static void WbemMethodRelease(WbemMethod *method) +{ + if (method == NULL) { + return; + } + ReleaseObject(method->in_params); + ReleaseObject(method->out_params); + + SysFreeString(method->method_name); +} + +typedef struct WbemMethodCall_ { + WbemMethod *method; + + BSTR instance_path; + + IWbemClassObject *in_params; +} WbemMethodCall; + +/** + * \brief generates a single-use WMI method call + */ +static HRESULT GetWbemMethodCall(WbemMethod *method, LPCWSTR instance_path, + WbemMethodCall *call) +{ + HRESULT hr = S_OK; + + call->method = method; + call->instance_path = SysAllocString(instance_path); + if (call->instance_path == NULL) { + hr = HRESULT_FROM_WIN32(E_OUTOFMEMORY); + SCLogWarning(SC_ERR_SYSCALL, "Failed to allocate BSTR: 0x%" PRIx32, + (uint32_t)hr); + goto release; + } + + /* make an instance of the in/out params */ + hr = method->in_params->lpVtbl->SpawnInstance(method->in_params, 0, + &call->in_params); + if (hr != S_OK) { + WbemLogDebug(hr); + SCLogWarning(SC_ERR_SYSCALL, + "WMI SpawnInstance failed on in_params: 0x%" PRIx32, + (uint32_t)hr); + goto release; + } + +release: + return hr; +} + +/** + * \brief releases the WMI method call resources + */ +static void WbemMethodCallRelease(WbemMethodCall *call) +{ + if (call == NULL) { + return; + } + ReleaseObject(call->in_params); + + SysFreeString(call->instance_path); +} + +/** + * \brief executes the method after the client has set applicable parameters. + */ +static HRESULT WbemMethodCallExec(WbemMethodCall *call, + IWbemClassObject **p_out_params) +{ + HRESULT hr = S_OK; + + hr = call->method->com_instance->services->lpVtbl->ExecMethod( + call->method->com_instance->services, call->instance_path, + call->method->method_name, 0, NULL, call->in_params, p_out_params, + NULL); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + SCLogDebug("WMI ExecMethod failed: 0x%" PRIx32, (uint32_t)hr); + goto release; + } + +release: + return hr; +} + +/** + * Obtains an IWbemClassObject named property of a parent IWbemClassObject + */ +static HRESULT WbemGetSubObject(IWbemClassObject *object, LPCWSTR property_name, + IWbemClassObject **sub_object) +{ + HRESULT hr = S_OK; + + VARIANT out_var; + VariantInit(&out_var); + hr = object->lpVtbl->Get(object, property_name, 0, &out_var, NULL, NULL); + if (hr != WBEM_S_NO_ERROR) { + goto release; + } + + IUnknown *unknown = V_UNKNOWN(&out_var); + hr = unknown->lpVtbl->QueryInterface(unknown, &IID_IWbemClassObject, + (void **)sub_object); + if (hr != S_OK) { + SCLogWarning(SC_ERR_SYSCALL, + "WMI QueryInterface (IWbemClassObject) failed: 0x%" PRIx32, + (uint32_t)hr); + goto release; + } + +release: + VariantClear(&out_var); + return hr; +} + +/** + * Obtains an Encapsulation value from an MSNdis_WmiOffload property + */ +static HRESULT GetEncapsulation(IWbemClassObject *object, LPCWSTR category, + LPCWSTR subcategory, ULONG *encapsulation) +{ + HRESULT hr = WBEM_S_NO_ERROR; + + IWbemClassObject *category_object = NULL; + IWbemClassObject *subcategory_object = NULL; + + VARIANT out_var; + VariantInit(&out_var); + + /* get category object */ + hr = WbemGetSubObject(object, category, &category_object); + if (hr != WBEM_S_NO_ERROR) { + goto release; + } + + /* get sub-category object */ + hr = WbemGetSubObject(category_object, subcategory, &subcategory_object); + if (hr != WBEM_S_NO_ERROR) { + goto release; + } + hr = subcategory_object->lpVtbl->Get(subcategory_object, L"Encapsulation", + 0, &out_var, NULL, NULL); + if (hr != WBEM_S_NO_ERROR) { + goto release; + } + *encapsulation = V_UI4(&out_var); + +release: + VariantClear(&out_var); + ReleaseObject(subcategory_object); + ReleaseObject(category_object); + return hr; +} + +static HRESULT GetIUnknown(IWbemClassObject *object, IUnknown **p_unknown) +{ + HRESULT hr = WBEM_S_NO_ERROR; + + if (object == NULL || p_unknown == NULL || *p_unknown != NULL) { + hr = HRESULT_FROM_WIN32(E_INVALIDARG); + Win32HResultLogDebug(hr); + goto release; + } + + hr = object->lpVtbl->QueryInterface(object, &IID_IUnknown, + (void **)p_unknown); + if (hr != S_OK) { + SCLogWarning(SC_ERR_SYSCALL, + "WMI QueryInterface (IUnknown) failed: 0x%" PRIx32, + (uint32_t)hr); + goto release; + } + +release: + return hr; +} + +static HRESULT BuildNdisObjectHeader(ComInstance *instance, uint8_t type, + uint8_t revision, uint16_t size, + IWbemClassObject **p_ndis_object_header) +{ + HRESULT hr = WBEM_S_NO_ERROR; + + if (instance == NULL || p_ndis_object_header == NULL || + *p_ndis_object_header != NULL) { + + hr = HRESULT_FROM_WIN32(E_INVALIDARG); + Win32HResultLogDebug(hr); + goto release; + } + + /* obtain object */ + hr = GetWbemClassInstance(instance, L"MSNdis_ObjectHeader", + p_ndis_object_header); + if (hr != WBEM_S_NO_ERROR) { + goto release; + } + + VARIANT param_variant; + VariantInit(¶m_variant); + IWbemClassObject *ndis_object_header = *p_ndis_object_header; + + /* set parameters */ + V_VT(¶m_variant) = VT_UI1; + V_UI1(¶m_variant) = type; + hr = ndis_object_header->lpVtbl->Put(ndis_object_header, L"Type", 0, + ¶m_variant, 0); + VariantClear(¶m_variant); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + + V_VT(¶m_variant) = VT_UI1; + V_UI1(¶m_variant) = revision; + hr = ndis_object_header->lpVtbl->Put(ndis_object_header, L"Revision", 0, + ¶m_variant, 0); + VariantClear(¶m_variant); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + + /* https://docs.microsoft.com/en-us/windows-hardware/drivers/network/ndis-object-version-issues-for-wmi + */ + V_VT(¶m_variant) = VT_I4; + V_I4(¶m_variant) = size; + hr = ndis_object_header->lpVtbl->Put(ndis_object_header, L"Size", 0, + ¶m_variant, 0); + VariantClear(¶m_variant); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + +release: + return hr; +} + +static HRESULT BuildNdisWmiMethodHeader(ComInstance *instance, + uint64_t net_luid, uint32_t port_number, + uint64_t request_id, uint32_t timeout, + IWbemClassObject **p_ndis_method_header) +{ + HRESULT hr = WBEM_S_NO_ERROR; + + IWbemClassObject *ndis_object_header = NULL; + + if (instance == NULL || p_ndis_method_header == NULL || + *p_ndis_method_header != NULL) { + + hr = HRESULT_FROM_WIN32(E_INVALIDARG); + Win32HResultLogDebug(hr); + goto release; + } + + /* obtain object */ + hr = GetWbemClassInstance(instance, L"MSNdis_WmiMethodHeader", + p_ndis_method_header); + if (hr != WBEM_S_NO_ERROR) { + goto release; + } + + VARIANT param_variant; + VariantInit(¶m_variant); + + /* get embedded MSNdis_ObjectHeader */ + hr = BuildNdisObjectHeader(instance, NDIS_WMI_OBJECT_TYPE_METHOD, + NDIS_WMI_METHOD_HEADER_REVISION_1, 0xFFFF, + &ndis_object_header); + if (hr != WBEM_S_NO_ERROR) { + goto release; + } + V_VT(¶m_variant) = VT_UNKNOWN; + V_UNKNOWN(¶m_variant) = NULL; + hr = GetIUnknown(ndis_object_header, &V_UNKNOWN(¶m_variant)); + if (hr != WBEM_S_NO_ERROR) { + goto release; + } + + IWbemClassObject *ndis_method_header = *p_ndis_method_header; + + /* set parameters */ + hr = ndis_method_header->lpVtbl->Put(ndis_method_header, L"Header", 0, + ¶m_variant, 0); + VariantClear(¶m_variant); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + + V_VT(¶m_variant) = VT_BSTR; + V_BSTR(¶m_variant) = utob(net_luid); + hr = ndis_method_header->lpVtbl->Put(ndis_method_header, L"NetLuid", 0, + ¶m_variant, 0); + VariantClear(¶m_variant); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + + V_VT(¶m_variant) = VT_BSTR; + V_BSTR(¶m_variant) = utob((uint64_t)port_number); + hr = ndis_method_header->lpVtbl->Put(ndis_method_header, L"PortNumber", 0, + ¶m_variant, 0); + VariantClear(¶m_variant); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + + V_VT(¶m_variant) = VT_BSTR; + V_BSTR(¶m_variant) = utob(request_id); + hr = ndis_method_header->lpVtbl->Put(ndis_method_header, L"RequestId", 0, + ¶m_variant, 0); + VariantClear(¶m_variant); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + + V_VT(¶m_variant) = VT_BSTR; + V_BSTR(¶m_variant) = utob((uint64_t)timeout); + hr = ndis_method_header->lpVtbl->Put(ndis_method_header, L"Timeout", 0, + ¶m_variant, 0); + VariantClear(¶m_variant); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + + V_VT(¶m_variant) = VT_BSTR; + V_BSTR(¶m_variant) = utob((uint64_t)0); + hr = ndis_method_header->lpVtbl->Put(ndis_method_header, L"Padding", 0, + ¶m_variant, 0); + VariantClear(¶m_variant); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + +release: + ReleaseObject(ndis_object_header); + + return hr; +} + +/** + * \brief polls the NDIS TCP offloading status, namely LSOv1/v2 + */ +static HRESULT GetNdisOffload(LPCWSTR if_description, uint32_t *offload_flags) +{ + HRESULT hr = S_OK; + + ComInstance instance = {}; + WbemMethod method = {}; + WbemMethodCall call = {}; + + IWbemClassObject *ndis_method_header = NULL; + IWbemClassObject *out_params = NULL; + IWbemClassObject *ndis_offload = NULL; + + if (if_description == NULL) { + SCLogWarning(SC_ERR_SYSCALL, "No description specified for device"); + hr = HRESULT_FROM_WIN32(E_INVALIDARG); + goto release; + } + + LPCWSTR class_name = L"MSNdis_TcpOffloadCurrentConfig"; + LPCWSTR instance_name_fmt = L"%s=\"%s\""; + size_t n_chars = wcslen(class_name) + wcslen(if_description) + + wcslen(instance_name_fmt); + LPWSTR instance_name = SCMalloc((n_chars + 1) * sizeof(wchar_t)); + if (instance_name == NULL) { + SCLogWarning(SC_ERR_SYSCALL, + "Failed to allocate buffer for instance path"); + goto release; + } + instance_name[n_chars] = 0; /* defensively null-terminate */ + hr = StringCchPrintfW(instance_name, n_chars, instance_name_fmt, class_name, + if_description); + if (hr != S_OK) { + SCLogWarning(SC_ERR_SYSCALL, + "Failed to format WMI class instance name: 0x%" PRIx32, + (uint32_t)hr); + goto release; + } + /* method name */ + LPCWSTR method_name = L"WmiQueryCurrentOffloadConfig"; + + /* connect to COM/WMI */ + hr = ComInstanceInit(&instance, L"ROOT\\WMI"); + if (hr != S_OK) { + goto release; + } + + /* obtain method */ + hr = GetWbemMethod(&instance, class_name, method_name, &method); + if (hr != S_OK) { + goto release; + } + + /* make parameter instances */ + hr = GetWbemMethodCall(&method, instance_name, &call); + if (hr != S_OK) { + goto release; + } + + /* build parameters */ + + VARIANT param_variant; + VariantInit(¶m_variant); + + /* Make MSNdis_WmiMethodHeader */ + hr = BuildNdisWmiMethodHeader(&instance, 0, 0, 0, 5, &ndis_method_header); + if (hr != WBEM_S_NO_ERROR) { + goto release; + } + V_VT(¶m_variant) = VT_UNKNOWN; + V_UNKNOWN(¶m_variant) = NULL; + hr = GetIUnknown(ndis_method_header, &V_UNKNOWN(¶m_variant)); + if (hr != WBEM_S_NO_ERROR) { + goto release; + } + + /* Set in_params */ + hr = call.in_params->lpVtbl->Put(call.in_params, L"Header", 0, + ¶m_variant, 0); + VariantClear(¶m_variant); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + + /* execute the method */ + hr = WbemMethodCallExec(&call, &out_params); + if (hr != S_OK) { + size_t if_description_len = wcslen(if_description); + char *if_description_ansi = SCMalloc(if_description_len + 1); + if (if_description_ansi == NULL) { + SCLogWarning(SC_ERR_SYSCALL, + "Failed to allocate buffer for interface description"); + goto release; + } + if_description_ansi[if_description_len] = 0; + wcstombs(if_description_ansi, if_description, if_description_len); + SCLogInfo("Obtaining offload state failed, device \"%s\" may not " + "support offload. Error: 0x%" PRIx32, + if_description_ansi, (uint32_t)hr); + SCFree(if_description_ansi); + Win32HResultLogDebug(hr); + goto release; + } + + /* inspect the result */ + hr = WbemGetSubObject(out_params, L"Offload", &ndis_offload); + if (hr != WBEM_S_NO_ERROR) { + goto release; + } + ULONG encapsulation = 0; + + /* Checksum */ + hr = GetEncapsulation(ndis_offload, L"Checksum", L"IPv4Receive", + &encapsulation); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + if (encapsulation != 0) { + *offload_flags |= WIN32_TCP_OFFLOAD_FLAG_CSUM_IP4RX; + } + hr = GetEncapsulation(ndis_offload, L"Checksum", L"IPv4Transmit", + &encapsulation); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + if (encapsulation != 0) { + *offload_flags |= WIN32_TCP_OFFLOAD_FLAG_CSUM_IP4TX; + } + hr = GetEncapsulation(ndis_offload, L"Checksum", L"IPv6Receive", + &encapsulation); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + if (encapsulation != 0) { + *offload_flags |= WIN32_TCP_OFFLOAD_FLAG_CSUM_IP6RX; + } + hr = GetEncapsulation(ndis_offload, L"Checksum", L"IPv6Transmit", + &encapsulation); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + if (encapsulation != 0) { + *offload_flags |= WIN32_TCP_OFFLOAD_FLAG_CSUM_IP6TX; + } + + /* LsoV1 */ + hr = GetEncapsulation(ndis_offload, L"LsoV1", L"WmiIPv4", &encapsulation); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + if (encapsulation != 0) { + *offload_flags |= WIN32_TCP_OFFLOAD_FLAG_LSOV1_IP4; + } + + /* LsoV2 */ + hr = GetEncapsulation(ndis_offload, L"LsoV2", L"WmiIPv4", &encapsulation); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + if (encapsulation != 0) { + *offload_flags |= WIN32_TCP_OFFLOAD_FLAG_LSOV2_IP4; + } + hr = GetEncapsulation(ndis_offload, L"LsoV2", L"WmiIPv6", &encapsulation); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + if (encapsulation != 0) { + *offload_flags |= WIN32_TCP_OFFLOAD_FLAG_LSOV2_IP6; + } + +release: + ReleaseObject(ndis_method_header); + ReleaseObject(ndis_offload); + ReleaseObject(out_params); + + WbemMethodCallRelease(&call); + WbemMethodRelease(&method); + ComInstanceRelease(&instance); + + return hr; +} + +int GetIfaceOffloadingWin32(const char *pcap_dev, int csum, int other) +{ + SCLogDebug("Querying offloading for device %s", pcap_dev); + + DWORD err = NO_ERROR; + int ret = 0; + uint32_t offload_flags = 0; + + /* WMI uses the description as an identifier... */ + IP_ADAPTER_ADDRESSES *if_info_list = NULL, *if_info = NULL; + err = Win32GetAdaptersAddresses(&if_info_list); + if (err != NO_ERROR) { + ret = -1; + goto release; + } + err = Win32FindAdapterAddresses(if_info_list, pcap_dev, &if_info); + if (err != NO_ERROR) { + ret = -1; + goto release; + } + LPWSTR if_description = if_info->Description; + + /* now query WMI for the offload info */ + err = GetNdisOffload(if_description, &offload_flags); + if (err != S_OK) { + ret = -1; + goto release; + } else if (offload_flags != 0) { + if (csum == 1) { + if ((offload_flags & WIN32_TCP_OFFLOAD_FLAG_CSUM) != 0) { + ret = 1; + } + } + if (other == 1) { + if ((offload_flags & WIN32_TCP_OFFLOAD_FLAG_LSO) != 0) { + ret = 1; + } + } + } + + if (ret == 0) { + SCLogPerf("NIC offloading on %s: Checksum IPv4 Rx: %d Tx: %d IPv6 " + "Rx: %d Tx: %d LSOv1 IPv4: %d LSOv2 IPv4: %d IPv6: %d", + pcap_dev, + (offload_flags & WIN32_TCP_OFFLOAD_FLAG_CSUM_IP4RX) != 0, + (offload_flags & WIN32_TCP_OFFLOAD_FLAG_CSUM_IP4TX) != 0, + (offload_flags & WIN32_TCP_OFFLOAD_FLAG_CSUM_IP6RX) != 0, + (offload_flags & WIN32_TCP_OFFLOAD_FLAG_CSUM_IP6TX) != 0, + (offload_flags & WIN32_TCP_OFFLOAD_FLAG_LSOV1_IP4) != 0, + (offload_flags & WIN32_TCP_OFFLOAD_FLAG_LSOV2_IP4) != 0, + (offload_flags & WIN32_TCP_OFFLOAD_FLAG_LSOV2_IP6) != 0); + } else { + SCLogWarning(SC_ERR_NIC_OFFLOADING, + "NIC offloading on %s: Checksum IPv4 Rx: %d Tx: %d IPv6 " + "Rx: %d Tx: %d LSOv1 IPv4: %d LSOv2 IPv4: %d IPv6: %d", + pcap_dev, + (offload_flags & WIN32_TCP_OFFLOAD_FLAG_CSUM_IP4RX) != 0, + (offload_flags & WIN32_TCP_OFFLOAD_FLAG_CSUM_IP4TX) != 0, + (offload_flags & WIN32_TCP_OFFLOAD_FLAG_CSUM_IP6RX) != 0, + (offload_flags & WIN32_TCP_OFFLOAD_FLAG_CSUM_IP6TX) != 0, + (offload_flags & WIN32_TCP_OFFLOAD_FLAG_LSOV1_IP4) != 0, + (offload_flags & WIN32_TCP_OFFLOAD_FLAG_LSOV2_IP4) != 0, + (offload_flags & WIN32_TCP_OFFLOAD_FLAG_LSOV2_IP6) != 0); + } + +release: + if (ret == -1) { + const char *err_str = Win32GetErrorString(err, WmiUtils()); + SCLogWarning(SC_ERR_SYSCALL, + "Failure when trying to get feature via syscall for '%s': " + "%s (0x%08" PRIx32 ")", + pcap_dev, err_str, (uint32_t)err); + LocalFree((LPVOID)err_str); + } + + SCFree(if_info_list); + + return ret; +} + +static HRESULT +BuildNdisTcpOffloadParameters(ComInstance *instance, uint32_t offload_flags, + bool enable, + IWbemClassObject **p_ndis_tcp_offload_parameters) +{ + HRESULT hr = WBEM_S_NO_ERROR; + + IWbemClassObject *ndis_object_header = NULL; + + if (instance == NULL || p_ndis_tcp_offload_parameters == NULL || + *p_ndis_tcp_offload_parameters != NULL) { + + hr = HRESULT_FROM_WIN32(E_INVALIDARG); + Win32HResultLogDebug(hr); + goto release; + } + + /* obtain object */ + hr = GetWbemClassInstance(instance, L"MSNdis_TcpOffloadParameters", + p_ndis_tcp_offload_parameters); + if (hr != WBEM_S_NO_ERROR) { + goto release; + } + + VARIANT param_variant; + VariantInit(¶m_variant); + + /* get embedded MSNdis_ObjectHeader */ + hr = BuildNdisObjectHeader(instance, NDIS_OBJECT_TYPE_DEFAULT, + NDIS_OFFLOAD_PARAMETERS_REVISION_1, + NDIS_SIZEOF_OFFLOAD_PARAMETERS_REVISION_1, + &ndis_object_header); + if (hr != WBEM_S_NO_ERROR) { + goto release; + } + V_VT(¶m_variant) = VT_UNKNOWN; + V_UNKNOWN(¶m_variant) = NULL; + hr = GetIUnknown(ndis_object_header, &V_UNKNOWN(¶m_variant)); + if (hr != WBEM_S_NO_ERROR) { + goto release; + } + + IWbemClassObject *ndis_tcp_offload_parameters = + *p_ndis_tcp_offload_parameters; + + /* set parameters */ + hr = ndis_tcp_offload_parameters->lpVtbl->Put( + ndis_tcp_offload_parameters, L"Header", 0, ¶m_variant, 0); + VariantClear(¶m_variant); + if (hr != WBEM_S_NO_ERROR) { + Win32HResultLogDebug(hr); + goto release; + } + + /* IPv4 csum */ + V_VT(¶m_variant) = VT_BSTR; + V_BSTR(¶m_variant) = utob(NDIS_OFFLOAD_PARAMETERS_NO_CHANGE); + if (!enable && (offload_flags & WIN32_TCP_OFFLOAD_FLAG_CSUM_IP4) != 0) { + /* this is basically all disabled cases */ + V_BSTR(¶m_variant) = utob(NDIS_OFFLOAD_PARAMETERS_TX_RX_DISABLED); + } else if ((offload_flags & WIN32_TCP_OFFLOAD_FLAG_CSUM_IP4) == + WIN32_TCP_OFFLOAD_FLAG_CSUM_IP4) { + /* implied enable */ + V_BSTR(¶m_variant) = utob(NDIS_OFFLOAD_PARAMETERS_TX_RX_ENABLED); + } else if ((offload_flags & WIN32_TCP_OFFLOAD_FLAG_CSUM_IP4RX) != 0) { + /* implied enable */ + V_BSTR(¶m_variant) = + utob(NDIS_OFFLOAD_PARAMETERS_RX_ENABLED_TX_DISABLED); + } else if ((offload_flags & WIN32_TCP_OFFLOAD_FLAG_CSUM_IP4TX) != 0) { + /* implied enable */ + V_BSTR(¶m_variant) = + utob(NDIS_OFFLOAD_PARAMETERS_TX_ENABLED_RX_DISABLED); + } + hr = ndis_tcp_offload_parameters->lpVtbl->Put( + ndis_tcp_offload_parameters, L"IPv4Checksum", 0, ¶m_variant, 0); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + hr = ndis_tcp_offload_parameters->lpVtbl->Put(ndis_tcp_offload_parameters, + L"TCPIPv4Checksum", 0, + ¶m_variant, 0); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + hr = ndis_tcp_offload_parameters->lpVtbl->Put(ndis_tcp_offload_parameters, + L"UDPIPv4Checksum", 0, + ¶m_variant, 0); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + VariantClear(¶m_variant); + + /* IPv6 csum */ + V_VT(¶m_variant) = VT_BSTR; + V_BSTR(¶m_variant) = utob(NDIS_OFFLOAD_PARAMETERS_NO_CHANGE); + if (!enable && (offload_flags & WIN32_TCP_OFFLOAD_FLAG_CSUM_IP6) != 0) { + /* this is basically all disabled cases */ + V_BSTR(¶m_variant) = utob(NDIS_OFFLOAD_PARAMETERS_TX_RX_DISABLED); + } else if ((offload_flags & WIN32_TCP_OFFLOAD_FLAG_CSUM_IP6) == + WIN32_TCP_OFFLOAD_FLAG_CSUM_IP6) { + /* implied enable */ + V_BSTR(¶m_variant) = utob(NDIS_OFFLOAD_PARAMETERS_TX_RX_ENABLED); + } else if ((offload_flags & WIN32_TCP_OFFLOAD_FLAG_CSUM_IP6RX) != 0) { + /* implied enable */ + V_BSTR(¶m_variant) = + utob(NDIS_OFFLOAD_PARAMETERS_RX_ENABLED_TX_DISABLED); + } else if ((offload_flags & WIN32_TCP_OFFLOAD_FLAG_CSUM_IP6TX) != 0) { + /* implied enable */ + V_BSTR(¶m_variant) = + utob(NDIS_OFFLOAD_PARAMETERS_TX_ENABLED_RX_DISABLED); + } + hr = ndis_tcp_offload_parameters->lpVtbl->Put(ndis_tcp_offload_parameters, + L"TCPIPv6Checksum", 0, + ¶m_variant, 0); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + hr = ndis_tcp_offload_parameters->lpVtbl->Put(ndis_tcp_offload_parameters, + L"UDPIPv6Checksum", 0, + ¶m_variant, 0); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + VariantClear(¶m_variant); + + /* LSOv1 */ + V_VT(¶m_variant) = VT_BSTR; + V_BSTR(¶m_variant) = utob(NDIS_OFFLOAD_PARAMETERS_NO_CHANGE); + if ((offload_flags & WIN32_TCP_OFFLOAD_FLAG_LSOV1_IP4) != 0) { + if (enable) { + V_BSTR(¶m_variant) = + utob(NDIS_OFFLOAD_PARAMETERS_LSOV1_ENABLED); + } else { + V_BSTR(¶m_variant) = + utob(NDIS_OFFLOAD_PARAMETERS_LSOV1_DISABLED); + } + } + hr = ndis_tcp_offload_parameters->lpVtbl->Put( + ndis_tcp_offload_parameters, L"LsoV1", 0, ¶m_variant, 0); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + VariantClear(¶m_variant); + + /* LSOv2 IPv4 */ + V_VT(¶m_variant) = VT_BSTR; + V_BSTR(¶m_variant) = utob(NDIS_OFFLOAD_PARAMETERS_NO_CHANGE); + if ((offload_flags & WIN32_TCP_OFFLOAD_FLAG_LSOV2_IP4) != 0) { + if (enable) { + V_BSTR(¶m_variant) = + utob(NDIS_OFFLOAD_PARAMETERS_LSOV2_ENABLED); + } else { + V_BSTR(¶m_variant) = + utob(NDIS_OFFLOAD_PARAMETERS_LSOV2_DISABLED); + } + } + hr = ndis_tcp_offload_parameters->lpVtbl->Put( + ndis_tcp_offload_parameters, L"LsoV2IPv4", 0, ¶m_variant, 0); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + VariantClear(¶m_variant); + + /* LSOv2 IPv4 */ + V_VT(¶m_variant) = VT_BSTR; + V_BSTR(¶m_variant) = utob(NDIS_OFFLOAD_PARAMETERS_NO_CHANGE); + if ((offload_flags & WIN32_TCP_OFFLOAD_FLAG_LSOV2_IP6) != 0) { + if (enable) { + V_BSTR(¶m_variant) = + utob(NDIS_OFFLOAD_PARAMETERS_LSOV2_ENABLED); + } else { + V_BSTR(¶m_variant) = + utob(NDIS_OFFLOAD_PARAMETERS_LSOV2_DISABLED); + } + } + hr = ndis_tcp_offload_parameters->lpVtbl->Put( + ndis_tcp_offload_parameters, L"LsoV2IPv6", 0, ¶m_variant, 0); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + VariantClear(¶m_variant); + + /* currently unused fields */ + V_VT(¶m_variant) = VT_BSTR; + V_BSTR(¶m_variant) = utob(NDIS_OFFLOAD_PARAMETERS_NO_CHANGE); + hr = ndis_tcp_offload_parameters->lpVtbl->Put( + ndis_tcp_offload_parameters, L"IPSec", 0, ¶m_variant, 0); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + hr = ndis_tcp_offload_parameters->lpVtbl->Put(ndis_tcp_offload_parameters, + L"TcpConnectionIPv4", 0, + ¶m_variant, 0); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + hr = ndis_tcp_offload_parameters->lpVtbl->Put(ndis_tcp_offload_parameters, + L"TcpConnectionIPv6", 0, + ¶m_variant, 0); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + hr = ndis_tcp_offload_parameters->lpVtbl->Put( + ndis_tcp_offload_parameters, L"Flags", 0, ¶m_variant, 0); + if (hr != WBEM_S_NO_ERROR) { + WbemLogDebug(hr); + goto release; + } + /* further fields are for NDIS 6.1+ */ + +release: + VariantClear(¶m_variant); + + return hr; +} + +static HRESULT SetNdisOffload(LPCWSTR if_description, uint32_t offload_flags, + bool enable) +{ + HRESULT hr = S_OK; + + ComInstance instance = {}; + WbemMethod method = {}; + WbemMethodCall call = {}; + + /* param 0 */ + IWbemClassObject *ndis_method_header = NULL; + /* param 1 */ + IWbemClassObject *ndis_tcp_offload_parameters = NULL; + + if (if_description == NULL) { + SCLogWarning(SC_ERR_SYSCALL, "No description specified for device"); + return E_INVALIDARG; + } + + LPCWSTR class_name = L"MSNdis_SetTcpOffloadParameters"; + LPCWSTR instance_name_fmt = L"%s=\"%s\""; + size_t n_chars = wcslen(class_name) + wcslen(if_description) + + wcslen(instance_name_fmt); + LPWSTR instance_name = SCMalloc((n_chars + 1) * sizeof(wchar_t)); + if (instance_name == NULL) { + SCLogWarning(SC_ERR_SYSCALL, + "Failed to allocate buffer for instance path"); + goto release; + } + instance_name[n_chars] = 0; /* defensively null-terminate */ + hr = StringCchPrintfW(instance_name, n_chars, instance_name_fmt, class_name, + if_description); + if (hr != S_OK) { + SCLogWarning(SC_ERR_SYSCALL, + "Failed to format WMI class instance name: 0x%" PRIx32, + (uint32_t)hr); + goto release; + } + + /* method name */ + LPCWSTR method_name = L"WmiSetTcpOffloadParameters"; + + /* connect to COM/WMI */ + hr = ComInstanceInit(&instance, L"ROOT\\WMI"); + if (hr != S_OK) { + goto release; + } + + /* obtain method */ + hr = GetWbemMethod(&instance, class_name, method_name, &method); + if (hr != S_OK) { + goto release; + } + + /* make parameter instances */ + hr = GetWbemMethodCall(&method, instance_name, &call); + if (hr != S_OK) { + goto release; + } + + /* build parameters */ + + VARIANT param_variant; + VariantInit(¶m_variant); + + /* Make MSNdis_WmiMethodHeader */ + hr = BuildNdisWmiMethodHeader(&instance, 0, 0, 0, 5, &ndis_method_header); + if (hr != WBEM_S_NO_ERROR) { + goto release; + } + + V_VT(¶m_variant) = VT_UNKNOWN; + V_UNKNOWN(¶m_variant) = NULL; + hr = GetIUnknown(ndis_method_header, &V_UNKNOWN(¶m_variant)); + if (hr != WBEM_S_NO_ERROR) { + goto release; + } + hr = call.in_params->lpVtbl->Put(call.in_params, L"MethodHeader", 0, + ¶m_variant, 0); + VariantClear(¶m_variant); + if (hr != WBEM_S_NO_ERROR) { + Win32HResultLogDebug(hr); + goto release; + } + + /* Make MSNdis_TcpOffloadParameters */ + hr = BuildNdisTcpOffloadParameters(&instance, offload_flags, enable, + &ndis_tcp_offload_parameters); + if (hr != WBEM_S_NO_ERROR) { + goto release; + } + + V_VT(¶m_variant) = VT_UNKNOWN; + V_UNKNOWN(¶m_variant) = NULL; + hr = GetIUnknown(ndis_tcp_offload_parameters, &V_UNKNOWN(¶m_variant)); + if (hr != WBEM_S_NO_ERROR) { + goto release; + } + hr = call.in_params->lpVtbl->Put(call.in_params, L"TcpOffloadParameters", 0, + ¶m_variant, 0); + VariantClear(¶m_variant); + if (hr != WBEM_S_NO_ERROR) { + Win32HResultLogDebug(hr); + goto release; + } + + /* execute the method */ + hr = WbemMethodCallExec(&call, NULL); + if (hr != S_OK) { + Win32HResultLogDebug(hr); + goto release; + } + +release: + ReleaseObject(ndis_tcp_offload_parameters); + ReleaseObject(ndis_method_header); + + WbemMethodCallRelease(&call); + WbemMethodRelease(&method); + ComInstanceRelease(&instance); + + return hr; +} + +int DisableIfaceOffloadingWin32(LiveDevice *ldev, int csum, int other) +{ + SCLogDebug("Disabling offloading for device %s", ldev->dev); + + int ret = 0; + DWORD err = NO_ERROR; + uint32_t offload_flags = 0; + + if (ldev == NULL) { + return -1; + } + + /* WMI uses the description as an identifier... */ + IP_ADAPTER_ADDRESSES *if_info_list = NULL, *if_info = NULL; + err = Win32GetAdaptersAddresses(&if_info_list); + if (err != NO_ERROR) { + ret = -1; + goto release; + } + err = Win32FindAdapterAddresses(if_info_list, ldev->dev, &if_info); + if (err != NO_ERROR) { + ret = -1; + goto release; + } + LPWSTR if_description = if_info->Description; + + err = GetNdisOffload(if_description, &offload_flags); + if (err != S_OK) { + ret = -1; + goto release; + } + + if (!csum) { + offload_flags &= ~WIN32_TCP_OFFLOAD_FLAG_CSUM; + } + if (!other) { + offload_flags &= ~WIN32_TCP_OFFLOAD_FLAG_LSO; + } + + err = SetNdisOffload(if_description, offload_flags, 0); + if (err != S_OK) { + ret = -1; + goto release; + } + +release: + SCFree(if_info_list); + + return ret; +} + +int RestoreIfaceOffloadingWin32(LiveDevice *ldev) +{ + SCLogDebug("Enabling offloading for device %s", ldev->dev); + + int ret = 0; + DWORD err = NO_ERROR; + + if (ldev == NULL) { + return -1; + } + + /* WMI uses the description as an identifier... */ + IP_ADAPTER_ADDRESSES *if_info_list = NULL, *if_info = NULL; + err = Win32GetAdaptersAddresses(&if_info_list); + if (err != NO_ERROR) { + ret = -1; + goto release; + } + err = Win32FindAdapterAddresses(if_info_list, ldev->dev, &if_info); + if (err != NO_ERROR) { + ret = -1; + goto release; + } + LPWSTR if_description = if_info->Description; + + err = SetNdisOffload(if_description, ldev->offload_orig, 1); + if (err != S_OK) { + ret = -1; + goto release; + } + +release: + SCFree(if_info_list); + + return ret; +} + +#endif /* NTDDI_VERSION >= NTDDI_VISTA */ + +#ifdef UNITTESTS +static int Win32TestStripPcapPrefix(void) +{ + int result = 1; + + const char *name1 = "\\Device\\NPF_{D4A32435-1BA7-4008-93A6-1518AA4BBD9B}"; + const char *expect_name1 = "{D4A32435-1BA7-4008-93A6-1518AA4BBD9B}"; + + const char *name2 = "{D4A32435-1BA7-4008-93A6-1518AA4BBD9B}"; + const char *expect_name2 = "{D4A32435-1BA7-4008-93A6-1518AA4BBD9B}"; + + result &= (strncmp(expect_name1, StripPcapPrefix(name1), + strlen(expect_name1)) == 0); + + result &= (strncmp(expect_name2, StripPcapPrefix(name2), + strlen(expect_name2)) == 0); + + return result; +} +#endif /* UNITTESTS */ + +void Win32SyscallRegisterTests() +{ +#ifdef UNITTESTS + UtRegisterTest("Win32TestStripPcapPrefix", Win32TestStripPcapPrefix); +#endif +} + +#endif /* OS_WIN32 */ \ No newline at end of file diff --git a/src/win32-syscall.h b/src/win32-syscall.h new file mode 100644 index 0000000000..9ceb77fa2b --- /dev/null +++ b/src/win32-syscall.h @@ -0,0 +1,53 @@ +/* Copyright (C) 2018 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Jacob Masen-Smith + * + * Isolation for WMI/COM functionality + */ + +#ifndef __WIN32_SYSCALL_H__ +#define __WIN32_SYSCALL_H__ +#ifdef OS_WIN32 + +#include + +#include + +#include "util-device.h" + +const char *Win32GetErrorString(DWORD error_code, HMODULE ext_module); + +uint32_t Win32GetAdaptersAddresses(IP_ADAPTER_ADDRESSES **pif_info_list); +uint32_t Win32FindAdapterAddresses(IP_ADAPTER_ADDRESSES *if_info_list, + const char *adapter_name, + IP_ADAPTER_ADDRESSES **pif_info); + +int GetIfaceMTUWin32(const char *pcap_dev); +int GetGlobalMTUWin32(void); + +int GetIfaceOffloadingWin32(const char *ifname, int csum, int other); +int DisableIfaceOffloadingWin32(LiveDevice *ldev, int csum, int other); +int RestoreIfaceOffloadingWin32(LiveDevice *ldev); + +void Win32SyscallRegisterTests(void); + +#endif /* OS_WIN32 */ +#endif /* __WIN32_SYSCALL_H__ */ \ No newline at end of file