summaryrefslogtreecommitdiffstats
path: root/src/source-windivert.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/source-windivert.c')
-rw-r--r--src/source-windivert.c1001
1 files changed, 1001 insertions, 0 deletions
diff --git a/src/source-windivert.c b/src/source-windivert.c
new file mode 100644
index 0000000..347d2e7
--- /dev/null
+++ b/src/source-windivert.c
@@ -0,0 +1,1001 @@
+/* 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 <jacob@evengx.com>
+ *
+ * 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 "packet.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 <winsock2.h>
+#include <windows.h>
+#include <iptypes.h>
+#include <winerror.h>
+// 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("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 */
+#include "action-globals.h"
+#include "win32-syscall.h"
+
+typedef struct WinDivertThreadVars_ {
+ WinDivertHandle filter_handle;
+
+ int thread_num;
+ 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
+#ifndef ERROR_INVALID_IMAGE_HASH
+#define ERROR_INVALID_IMAGE_HASH 577L
+#endif
+#ifndef ERROR_DATA_NOT_ACCEPTED
+#define ERROR_DATA_NOT_ACCEPTED 592L
+#endif
+
+/**
+ * \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("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)
+{
+ SCTime_t now = TimeGet();
+ (void)QueryPerformanceCounter((LARGE_INTEGER *)&wd_tv->qpc_start_count);
+
+ wd_tv->qpc_start_time = (uint64_t)SCTIME_SECS(now) * (1000 * 1000) + (uint64_t)SCTIME_SECS(now);
+
+ (void)QueryPerformanceFrequency((LARGE_INTEGER *)&wd_tv->qpc_freq_usec);
+ /* \bug: clock drift? */
+ wd_tv->qpc_freq_usec /= 1000 * 1000;
+}
+
+/**
+ * \brief WinDivert timestamp to a SCTime_t
+ */
+static SCTime_t WinDivertTimestampToTimeStamp(WinDivertThreadVars *wd_tv, INT64 timestamp_count)
+{
+ struct timeval tv;
+
+ int64_t qpc_delta = (int64_t)timestamp_count - wd_tv->qpc_start_count;
+ int64_t unix_usec = wd_tv->qpc_start_time;
+ if (wd_tv->qpc_freq_usec) {
+ unix_usec += qpc_delta / wd_tv->qpc_freq_usec;
+ }
+
+ tv.tv_sec = (long)(unix_usec / (1000 * 1000));
+ tv.tv_usec = (long)(unix_usec - (int64_t)tv.tv_sec * (1000 * 1000));
+
+ return SCTIME_FROM_TIMEVAL(&tv);
+}
+
+/**
+ * \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("Invalid filter \"%s\" supplied to WinDivert: %s at position "
+ "%" PRId32 "",
+ filter_str, error_str, error_pos);
+ SCReturnInt(-1);
+ }
+
+ /* initialize the queue */
+ SCMutexLock(&g_wd_init_lock);
+
+ if (g_wd_num >= WINDIVERT_MAX_QUEUE) {
+ SCLogError("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("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 *);
+TmEcode VerdictWinDivertThreadInit(ThreadVars *, const void *, void **);
+TmEcode VerdictWinDivertThreadDeinit(ThreadVars *, void *);
+
+/* Decode functions */
+TmEcode DecodeWinDivert(ThreadVars *, Packet *, void *);
+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;
+
+ // Indicate that the thread is actually running its application level code (i.e., it can poll
+ // packets)
+ TmThreadsSetFlag(tv, THV_RUNNING);
+
+ 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 = WinDivertTimestampToTimeStamp(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) {
+ 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("initdata == NULL");
+ SCReturnInt(TM_ECODE_FAILED);
+ }
+
+ WinDivertQueueVars *wd_qv = WinDivertGetQueue(wd_tv->thread_num);
+
+ if (wd_qv == NULL) {
+ SCLogError("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("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("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("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)
+{
+ 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);
+
+#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 (PacketCheckAction(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;
+ *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)
+{
+ SCEnter();
+
+ IPV4Hdr *ip4h = (IPV4Hdr *)GET_PKT_DATA(p);
+ IPV6Hdr *ip6h = (IPV6Hdr *)GET_PKT_DATA(p);
+ DecodeThreadVars *d_tv = (DecodeThreadVars *)data;
+
+ BUG_ON(PKT_IS_PSEUDOPKT(p));
+
+ 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));
+ } else if (IPV6_GET_RAW_VER(ip6h) == 6) {
+ SCLogDebug("IPv6 packet");
+ DecodeIPV6(tv, d_tv, p, GET_PKT_DATA(p), GET_PKT_LEN(p));
+ } 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("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(void)
+{
+#ifdef UNITTESTS
+ UtRegisterTest("SourceWinDivertTestIfaceMatchFilter",
+ SourceWinDivertTestIfaceMatchFilter);
+#endif
+}
+
+#endif /* WINDIVERT */