- Divert driver and library now fully support multi-threaded code. This

includes making the driver handle I/O requests in parallel, and changing
  the library such that it uses overlapped I/O.  Overall the package is
  now significantly faster.
- The divert driver now only adds WFP callouts when a filter is set,
  instead of when a handle is created.
- The divert driver now attempts to avoid adding WFP callouts that are
  superfluous with respect to the filter.
- The divert driver now explicitly deletes filters and callouts during
  cleanup.  This appears to resolve a bug where network connectivity is
  sometimes lost.
- Added a new sample program: passthru.exe.  This example doesn't do anything
  interesting but is useful for speed testing.
- Rename build.sh to mingw-build.sh to avoid confusion.
This commit is contained in:
basil00
2011-11-08 23:54:01 +08:00
parent 88c7e404ca
commit a205259bec
8 changed files with 542 additions and 139 deletions
+51 -43
View File
@@ -212,6 +212,8 @@ static HMODULE DivertLoadCoInstaller(LPWSTR divert_dll);
static BOOLEAN DivertDriverFiles(LPWSTR *divert_dir_ptr,
LPWSTR *divert_sys_ptr, LPWSTR *divert_inf_ptr, LPWSTR *divert_dll_ptr);
static BOOLEAN DivertDriverInstall(VOID);
static BOOL DivertIoControl(HANDLE handle, DWORD code, UINT64 arg, PVOID buf,
UINT len, UINT *iolen);
static BOOL DivertCompileFilter(const char *filter_str,
divert_ioctl_filter_t filter, UINT8 *fp);
static int __cdecl DivertFilterTokenNameCompare(const void *a, const void *b);
@@ -476,6 +478,45 @@ DivertDriverInstallExit:
return installed;
}
/*
* Perform a DeviceIoControl.
*/
static BOOL DivertIoControl(HANDLE handle, DWORD code, UINT64 arg, PVOID buf,
UINT len, UINT *iolen)
{
struct divert_ioctl_s ioctl;
DWORD iolen0;
OVERLAPPED overlapped;
ioctl.version = DIVERT_VERSION;
ioctl.magic = DIVERT_MAGIC;
ioctl.reserved = 0x0;
ioctl.arg = arg;
overlapped.Offset = 0;
overlapped.OffsetHigh = 0;
overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (overlapped.hEvent == NULL)
{
return FALSE;
}
if (!DeviceIoControl(handle, code, &ioctl, sizeof(ioctl), buf, (DWORD)len,
&iolen0, &overlapped))
{
if (GetLastError() != ERROR_IO_PENDING ||
!GetOverlappedResult(handle, &overlapped, &iolen0, TRUE))
{
CloseHandle(overlapped.hEvent);
return FALSE;
}
}
CloseHandle(overlapped.hEvent);
if (iolen != NULL)
{
*iolen = (UINT)iolen0;
}
return TRUE;
}
/*
* Open a handle to the Divert device.
*/
@@ -484,7 +525,7 @@ extern HANDLE DivertOpen(const char *filter)
struct divert_ioctl_s ioctl;
struct divert_ioctl_filter_s ioctl_filter[DIVERT_FILTER_MAXLEN];
UINT8 filter_len;
DWORD err, iolen;
DWORD err;
HANDLE handle;
// Parse the filter:
@@ -500,7 +541,8 @@ extern HANDLE DivertOpen(const char *filter)
// Attempt to open the Divert device:
handle = CreateFile(L"\\\\.\\Divert", GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, INVALID_HANDLE_VALUE);
0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
INVALID_HANDLE_VALUE);
if (handle == INVALID_HANDLE_VALUE)
{
err = GetLastError();
@@ -524,13 +566,9 @@ extern HANDLE DivertOpen(const char *filter)
}
// Set the filter:
ioctl.version = DIVERT_VERSION;
ioctl.magic = DIVERT_MAGIC;
ioctl.reserved = 0x0;
ioctl.arg = (UINT64)NULL;
if (!DeviceIoControl(handle, IOCTL_DIVERT_SET_FILTER, &ioctl,
sizeof(ioctl), ioctl_filter,
filter_len*sizeof(struct divert_ioctl_filter_s), &iolen, NULL))
if (!DivertIoControl(handle, IOCTL_DIVERT_SET_FILTER, (UINT64)NULL,
ioctl_filter, filter_len*sizeof(struct divert_ioctl_filter_s),
NULL))
{
CloseHandle(handle);
return INVALID_HANDLE_VALUE;
@@ -546,23 +584,8 @@ extern HANDLE DivertOpen(const char *filter)
extern BOOL DivertRecv(HANDLE handle, PVOID pPacket, UINT packetLen,
PDIVERT_ADDRESS addr, UINT *readlen)
{
struct divert_ioctl_s ioctl;
DWORD readlen0;
ioctl.version = DIVERT_VERSION;
ioctl.magic = DIVERT_MAGIC;
ioctl.reserved = 0x0;
ioctl.arg = (UINT64)addr;
if (!DeviceIoControl(handle, IOCTL_DIVERT_RECV, &ioctl, sizeof(ioctl),
pPacket, packetLen, &readlen0, NULL))
{
return FALSE;
}
if (readlen != NULL)
{
*readlen = (UINT)readlen0;
}
return TRUE;
return DivertIoControl(handle, IOCTL_DIVERT_RECV, (UINT64)addr, pPacket,
packetLen, readlen);
}
/*
@@ -571,23 +594,8 @@ extern BOOL DivertRecv(HANDLE handle, PVOID pPacket, UINT packetLen,
extern BOOL DivertSend(HANDLE handle, PVOID pPacket, UINT packetLen,
PDIVERT_ADDRESS addr, UINT *writelen)
{
struct divert_ioctl_s ioctl;
DWORD writelen0;
ioctl.version = DIVERT_VERSION;
ioctl.magic = DIVERT_MAGIC;
ioctl.reserved = 0x0;
ioctl.arg = (UINT64)addr;
if (!DeviceIoControl(handle, IOCTL_DIVERT_SEND, &ioctl, sizeof(ioctl),
pPacket, packetLen, &writelen0, NULL))
{
return FALSE;
}
if (writelen != NULL)
{
*writelen = writelen0;
}
return TRUE;
return DivertIoControl(handle, IOCTL_DIVERT_SEND, (UINT64)addr, pPacket,
packetLen, writelen);
}
/*
+6 -4
View File
@@ -856,6 +856,12 @@ The sample programs are:
other traffic it simply drops.
This is similar to the Linux <tt>iptables</tt> command with the
<tt>-j REJECT</tt> option.</li>
<li><tt>passthru.exe</tt>: A simple program that simply re-injects every
packet it captures.
This example is multi-threaded, where multiple threads are processing
packets from a single handle.
This example is useful for performance testing, and as a starting point
for more interesting applications.</li>
</ul>
</p><p>
The samples are intended for educational purposes only, and are not
@@ -922,10 +928,6 @@ They are
This is necessary to prevent packet loops and deadlocks.
In the future we intend to implement priorities for <tt>divert</tt>
handles to allow packets to be seen by multiple <tt>divert</tt> handles.
<li><i>Speed</i>:
The <tt>divert</tt> driver is not re-entrant, and thus is not as
efficient as it could be.
In the future we plan to rectify this.
</ul>
</p>
+1
View File
@@ -1,4 +1,5 @@
DIRS= \
netdump \
netfilter \
passthru \
webfilter
+1
View File
@@ -0,0 +1 @@
!INCLUDE $(NTMAKEENV)\makefile.def
+117
View File
@@ -0,0 +1,117 @@
/*
* passthru.c
* (C) 2011, all rights reserved,
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* DESCRIPTION:
* This program does nothing except divert packets and re-inject them. This is
* useful for performance testing.
*
* usage: netdump.exe divert-filter num-threads
*/
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include "divert.h"
#define MAXBUF 2048
static DWORD passthru(LPVOID arg);
/*
* Entry.
*/
int __cdecl main(int argc, char **argv)
{
int num_threads, i;
HANDLE handle, thread;
if (argc != 3)
{
fprintf(stderr, "usage: %s filter num-threads\n", argv[0]);
exit(EXIT_FAILURE);
}
num_threads = atoi(argv[2]);
if (num_threads < 1 || num_threads > 64)
{
fprintf(stderr, "error: invalid number of threads\n");
exit(EXIT_FAILURE);
}
// Divert traffic matching the filter:
handle = DivertOpen(argv[1]);
if (handle == INVALID_HANDLE_VALUE)
{
if (GetLastError() == ERROR_INVALID_PARAMETER)
{
fprintf(stderr, "error: filter syntax error\n");
exit(EXIT_FAILURE);
}
fprintf(stderr, "error: failed to open Divert device (%d)\n",
GetLastError());
exit(EXIT_FAILURE);
}
// Start the threads
for (i = 1; i < num_threads; i++)
{
thread = CreateThread(NULL, 1, passthru, (LPVOID)handle, 0, NULL);
if (thread == NULL)
{
fprintf(stderr, "error: failed to start passthru thread (%u)\n",
GetLastError());
exit(EXIT_FAILURE);
}
}
// Main thread:
passthru((LPVOID)handle);
return 0;
}
// Passthru thread.
static DWORD passthru(LPVOID arg)
{
char packet[MAXBUF];
UINT packet_len;
DIVERT_ADDRESS addr;
HANDLE handle = (HANDLE)arg;
// Main loop:
while (TRUE)
{
// Read a matching packet.
if (!DivertRecv(handle, packet, sizeof(packet), &addr, &packet_len))
{
fprintf(stderr, "warning: failed to read packet (%d)\n",
GetLastError());
continue;
}
// Re-inject the matching packet.
if (!DivertSend(handle, packet, packet_len, &addr, NULL))
{
fprintf(stderr, "warning: failed to reinject packet (%d)\n",
GetLastError());
}
}
}
+36
View File
@@ -0,0 +1,36 @@
# sources
# (C) 2011, all rights reserved,
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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
# along with this program. If not, see <http://www.gnu.org/licenses/>.
!IF "$(_BUILDARCH)" == "x86"
CPU=i386
!ELSE
CPU=$(_BUILDARCH)
!ENDIF
TARGETNAME=passthru
TARGETTYPE=PROGRAM
TARGETPATH=..\..\install
TARGETLIBS=\
$(SDK_LIB_PATH)\setupapi.lib \
$(SDK_LIB_PATH)\user32.lib \
$(SDK_LIB_PATH)\ws2_32.lib \
$(TARGETPATH)\$(CPU)\divert.lib
UMTYPE=console
UMENTRY=main
USE_MSVCRT=1
INCLUDES=$(DDK_INC_PATH);$(KMDF_INC_PATH)\$(KMDF_VER_PATH);..\..\include
SOURCES=passthru.c
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/bash
#
# build.sh
# mingw-build.sh
# (C) 2011, all rights reserved,
#
# This program is free software: you can redistribute it and/or modify
+329 -91
View File
@@ -124,7 +124,12 @@ struct context_s
// Sublayer GUIDs.
GUID callout_guid[DIVERT_CONTEXT_NUMLAYERS];
// Callout GUIDs.
GUID filter_guid[DIVERT_CONTEXT_NUMLAYERS];
// Filter GUIDs.
BOOL registered[DIVERT_CONTEXT_NUMLAYERS];
// What is registered?
HANDLE engine_handle; // WFP engine handle.
LONG filter_on; // Is filter on?
struct filter_s filter[DIVERT_FILTER_MAXLEN];
// Packet filter.
};
@@ -286,10 +291,10 @@ static void divert_read_service(context_t context);
static BOOLEAN divert_context_verify(context_t context, context_state_t state);
extern VOID divert_create(IN WDFDEVICE device, IN WDFREQUEST request,
IN WDFFILEOBJECT object);
extern NTSTATUS divert_register_callout(context_t context, UINT idx,
wchar_t *sublayer_name, wchar_t *sublayer_desc,
wchar_t *callout_name, wchar_t *callout_desc,
wchar_t *filter_name, wchar_t *filter_desc);
static NTSTATUS divert_register_callouts(context_t context, BOOL is_inbound,
BOOL is_outbound, BOOL is_ipv4, BOOL is_ipv6);
static NTSTATUS divert_register_callout(context_t context, UINT idx,
BOOL is_inbound, BOOL is_outbound, BOOL is_ipv4, BOOL is_ipv6);
extern VOID divert_timer(IN WDFTIMER timer);
extern VOID divert_cleanup(IN WDFFILEOBJECT object);
extern VOID divert_close(IN WDFFILEOBJECT object);
@@ -340,6 +345,10 @@ static BOOL divert_filter(PNET_BUFFER buffer, UINT32 if_idx, UINT32 sub_if_idx,
BOOL outbound, filter_t filter);
static BOOL divert_filter_compile(divert_ioctl_filter_t ioctl_filter,
size_t ioctl_filter_len, filter_t filter);
static void divert_filter_analyze(filter_t filter, BOOL *is_inbound,
BOOL *is_outbound, BOOL *ip_ipv4, BOOL *is_ipv6);
static BOOL divert_filter_test(filter_t filter, UINT8 ip, UINT8 protocol,
UINT8 field, UINT32 arg);
/*
* Driver entry routine.
@@ -405,7 +414,7 @@ extern NTSTATUS DriverEntry(IN PDRIVER_OBJECT driver_obj,
return status;
}
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queue_config,
WdfIoQueueDispatchSequential);
WdfIoQueueDispatchParallel);
queue_config.EvtIoRead = NULL;
queue_config.EvtIoWrite = NULL;
queue_config.EvtIoDeviceControl = divert_ioctl;
@@ -487,55 +496,13 @@ static BOOLEAN divert_context_verify(context_t context, context_state_t state)
extern VOID divert_create(IN WDFDEVICE device, IN WDFREQUEST request,
IN WDFFILEOBJECT object)
{
static wchar_t *sublayer_name[DIVERT_CONTEXT_NUMLAYERS] =
{
L"DivertSubLayerOutboundIPv4",
L"DivertSubLayerInboundIPv4",
L"DivertSubLayerOutboundIPv6",
L"DivertSubLayerInboundIPv6"
};
static wchar_t *sublayer_desc[DIVERT_CONTEXT_NUMLAYERS] =
{
L"Divert sublayer (outbound IPv4)",
L"Divert sublayer (inbound IPv4)",
L"Divert sublayer (outbound IPv6)",
L"Divert sublayer (inbound IPv6)"
};
static wchar_t *callout_name[DIVERT_CONTEXT_NUMLAYERS] =
{
L"DivertCalloutOutboundIPv4",
L"DivertCalloutInboundIPv4",
L"DivertCalloutOutboundIPv6",
L"DivertCalloutInboundIPv6"
};
static wchar_t *callout_desc[DIVERT_CONTEXT_NUMLAYERS] =
{
L"Divert callout (outbound IPv4)",
L"Divert callout (inbound IPv4)",
L"Divert callout (outbound IPv6)",
L"Divert callout (inbound IPv6)"
};
static wchar_t *filter_name[DIVERT_CONTEXT_NUMLAYERS] =
{
L"DivertFilterOutboundIPv4",
L"DivertFilterInboundIPv4",
L"DivertFilterOutboundIPv6",
L"DivertFilterInboundIPv6"
};
static wchar_t *filter_desc[DIVERT_CONTEXT_NUMLAYERS] =
{
L"Divert filter (outbound IPv4)",
L"Divert filter (inbound IPv4)",
L"Divert filter (outbound IPv6)",
L"Divert filter (inbound IPv6)"
};
NET_BUFFER_LIST_POOL_PARAMETERS pool_params;
WDF_IO_QUEUE_CONFIG queue_config;
WDF_TIMER_CONFIG timer_config;
WDF_OBJECT_ATTRIBUTES timer_attributes;
FWPM_SESSION0 session;
NTSTATUS status = STATUS_SUCCESS;
UINT8 i, j = 0;
UINT8 i;
context_t context = divert_context_get(object);
DEBUG("CREATE: creating a new divert context (context=%p)", context);
@@ -558,6 +525,11 @@ extern VOID divert_create(IN WDFDEVICE device, IN WDFREQUEST request,
context->filter[i].success = DIVERT_FILTER_RESULT_REJECT;
context->filter[i].failure = DIVERT_FILTER_RESULT_REJECT;
}
for (i = 0; i < DIVERT_CONTEXT_NUMLAYERS; i++)
{
context->registered[i] = FALSE;
}
context->filter_on = FALSE;
KeInitializeSpinLock(&context->lock);
InitializeListHead(&context->packet_queue);
for (i = 0; i < DIVERT_CONTEXT_NUMLAYERS; i++)
@@ -574,6 +546,12 @@ extern VOID divert_create(IN WDFDEVICE device, IN WDFREQUEST request,
DEBUG_ERROR("failed to create callout GUID", status);
goto divert_create_exit;
}
status = ExUuidCreate(&context->filter_guid[i]);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("failed to create filter GUID", status);
goto divert_create_exit;
}
}
RtlZeroMemory(&pool_params, sizeof(pool_params));
pool_params.Header.Type = NDIS_OBJECT_TYPE_DEFAULT;
@@ -617,31 +595,6 @@ extern VOID divert_create(IN WDFDEVICE device, IN WDFREQUEST request,
DEBUG_ERROR("failed to create WFP engine handle", status);
goto divert_create_exit;
}
status = FwpmTransactionBegin0(context->engine_handle, 0);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("failed to begin WFP transaction", status);
goto divert_create_exit;
}
for (j = 0; j < DIVERT_CONTEXT_NUMLAYERS; j++)
{
status = divert_register_callout(context, j, sublayer_name[j],
sublayer_desc[j], callout_name[j], callout_desc[j], filter_name[j],
filter_desc[j]);
if (!NT_SUCCESS(status))
{
FwpmTransactionAbort0(context->engine_handle);
goto divert_create_exit;
}
}
status = FwpmTransactionCommit0(context->engine_handle);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("failed to commit WFP transaction", status);
goto divert_create_exit;
}
// Open for business:
context->state = DIVERT_CONTEXT_STATE_OPEN;
WdfTimerStart(context->timer,
WDF_REL_TIMEOUT_IN_MS(DIVERT_PACKET_TIMEOUT));
@@ -667,10 +620,6 @@ divert_create_exit:
{
FwpmEngineClose0(context->engine_handle);
}
for (i = 0; i < j; i++)
{
FwpsCalloutUnregisterByKey0(&context->callout_guid[i]);
}
context->state = DIVERT_CONTEXT_STATE_INVALID;
}
@@ -678,37 +627,130 @@ divert_create_exit:
}
/*
* Add a WFP filter.
* Register all WFP callouts.
*/
extern NTSTATUS divert_register_callout(context_t context, UINT idx,
wchar_t *sublayer_name, wchar_t *sublayer_desc,
wchar_t *callout_name, wchar_t *callout_desc,
wchar_t *filter_name, wchar_t *filter_desc)
static NTSTATUS divert_register_callouts(context_t context, BOOL is_inbound,
BOOL is_outbound, BOOL is_ipv4, BOOL is_ipv6)
{
UINT8 i;
NTSTATUS status;
status = FwpmTransactionBegin0(context->engine_handle, 0);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("failed to begin WFP transaction", status);
goto divert_register_callouts_exit;
}
for (i = 0; i < DIVERT_CONTEXT_NUMLAYERS; i++)
{
status = divert_register_callout(context, i, is_inbound, is_outbound,
is_ipv4, is_ipv6);
if (!NT_SUCCESS(status))
{
FwpmTransactionAbort0(context->engine_handle);
goto divert_register_callouts_exit;
}
}
status = FwpmTransactionCommit0(context->engine_handle);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("failed to commit WFP transaction", status);
goto divert_register_callouts_exit;
}
divert_register_callouts_exit:
if (!NT_SUCCESS(status))
{
for (i = 0; i < DIVERT_CONTEXT_NUMLAYERS; i++)
{
if (context->registered[i])
{
FwpsCalloutUnregisterByKey0(&context->callout_guid[i]);
context->registered[i] = FALSE;
}
}
}
return status;
}
/*
* Register a WFP callout.
*/
static NTSTATUS divert_register_callout(context_t context, UINT idx,
BOOL is_inbound, BOOL is_outbound, BOOL is_ipv4, BOOL is_ipv6)
{
static wchar_t *sublayer_name[DIVERT_CONTEXT_NUMLAYERS] =
{
L"DivertSubLayerOutboundIPv4",
L"DivertSubLayerInboundIPv4",
L"DivertSubLayerOutboundIPv6",
L"DivertSubLayerInboundIPv6"
};
static wchar_t *sublayer_desc[DIVERT_CONTEXT_NUMLAYERS] =
{
L"Divert sublayer (outbound IPv4)",
L"Divert sublayer (inbound IPv4)",
L"Divert sublayer (outbound IPv6)",
L"Divert sublayer (inbound IPv6)"
};
static wchar_t *callout_name[DIVERT_CONTEXT_NUMLAYERS] =
{
L"DivertCalloutOutboundIPv4",
L"DivertCalloutInboundIPv4",
L"DivertCalloutOutboundIPv6",
L"DivertCalloutInboundIPv6"
};
static wchar_t *callout_desc[DIVERT_CONTEXT_NUMLAYERS] =
{
L"Divert callout (outbound IPv4)",
L"Divert callout (inbound IPv4)",
L"Divert callout (outbound IPv6)",
L"Divert callout (inbound IPv6)"
};
static wchar_t *filter_name[DIVERT_CONTEXT_NUMLAYERS] =
{
L"DivertFilterOutboundIPv4",
L"DivertFilterInboundIPv4",
L"DivertFilterOutboundIPv6",
L"DivertFilterInboundIPv6"
};
static wchar_t *filter_desc[DIVERT_CONTEXT_NUMLAYERS] =
{
L"Divert filter (outbound IPv4)",
L"Divert filter (inbound IPv4)",
L"Divert filter (outbound IPv6)",
L"Divert filter (inbound IPv6)"
};
GUID layer;
FWPM_SUBLAYER0 sublayer;
FWPS_CALLOUT0 scallout;
FWPM_CALLOUT0 mcallout;
FWPM_FILTER0 filter;
BOOL registered = FALSE;
BOOL required, registered = FALSE;
divert_callout_t callout;
NTSTATUS status;
switch (idx)
{
case DIVERT_CONTEXT_OUTBOUND_IPV4_LAYER:
required = (is_outbound && is_ipv4);
layer = FWPM_LAYER_OUTBOUND_IPPACKET_V4;
callout = divert_classify_outbound_v4_callout;
break;
case DIVERT_CONTEXT_INBOUND_IPV4_LAYER:
required = (is_inbound && is_ipv4);
layer = FWPM_LAYER_INBOUND_IPPACKET_V4;
callout = divert_classify_inbound_v4_callout;
break;
case DIVERT_CONTEXT_OUTBOUND_IPV6_LAYER:
required = (is_outbound && is_ipv6);
layer = FWPM_LAYER_OUTBOUND_IPPACKET_V6;
callout = divert_classify_outbound_v6_callout;
break;
case DIVERT_CONTEXT_INBOUND_IPV6_LAYER:
required = (is_inbound && is_ipv6);
layer = FWPM_LAYER_INBOUND_IPPACKET_V6;
callout = divert_classify_inbound_v6_callout;
break;
@@ -716,10 +758,15 @@ extern NTSTATUS divert_register_callout(context_t context, UINT idx,
return STATUS_INVALID_PARAMETER;
}
if (!required)
{
return STATUS_SUCCESS;
}
RtlZeroMemory(&sublayer, sizeof(sublayer));
sublayer.subLayerKey = context->sublayer_guid[idx];
sublayer.displayData.name = sublayer_name;
sublayer.displayData.description = sublayer_desc;
sublayer.displayData.name = sublayer_name[idx];
sublayer.displayData.description = sublayer_desc[idx];
sublayer.weight = FWP_EMPTY;
RtlZeroMemory(&scallout, sizeof(scallout));
scallout.calloutKey = context->callout_guid[idx];
@@ -728,13 +775,14 @@ extern NTSTATUS divert_register_callout(context_t context, UINT idx,
scallout.flowDeleteFn = NULL;
RtlZeroMemory(&mcallout, sizeof(mcallout));
mcallout.calloutKey = context->callout_guid[idx];
mcallout.displayData.name = callout_name;
mcallout.displayData.description = callout_desc;
mcallout.displayData.name = callout_name[idx];
mcallout.displayData.description = callout_desc[idx];
mcallout.applicableLayer = layer;
RtlZeroMemory(&filter, sizeof(filter));
filter.filterKey = context->filter_guid[idx];
filter.layerKey = layer;
filter.displayData.name = filter_name;
filter.displayData.description = filter_desc;
filter.displayData.name = filter_name[idx];
filter.displayData.description = filter_desc[idx];
filter.action.type = FWP_ACTION_CALLOUT_TERMINATING;
filter.action.calloutKey = context->callout_guid[idx];
filter.subLayerKey = context->sublayer_guid[idx];
@@ -766,6 +814,7 @@ extern NTSTATUS divert_register_callout(context_t context, UINT idx,
DEBUG_ERROR("failed to add WFP filter", status);
goto divert_register_callout_error;
}
context->registered[idx] = TRUE;
return STATUS_SUCCESS;
@@ -833,6 +882,7 @@ extern VOID divert_cleanup(IN WDFFILEOBJECT object)
UINT i;
context_t context = divert_context_get(object);
packet_t packet;
NTSTATUS status;
DEBUG("CLEANUP: cleaning up divert context (context=%p)", context);
@@ -856,10 +906,51 @@ extern VOID divert_cleanup(IN WDFFILEOBJECT object)
WdfIoQueuePurge(context->read_queue, NULL, NULL);
WdfObjectDelete(context->read_queue);
WdfObjectDelete(context->timer);
status = FwpmTransactionBegin0(context->engine_handle, 0);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("failed to begin WFP transaction", status);
goto divert_cleanup_exit;
}
for (i = 0; i < DIVERT_CONTEXT_NUMLAYERS; i++)
{
if (!context->registered[i])
{
continue;
}
status = FwpmFilterDeleteByKey0(context->engine_handle,
context->filter_guid+i);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("failed delete WFP filter", status);
FwpmTransactionAbort0(context->engine_handle);
goto divert_cleanup_exit;
}
status = FwpmSubLayerDeleteByKey0(context->engine_handle,
context->sublayer_guid+i);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("failed delete WFP sub-layer", status);
FwpmTransactionAbort0(context->engine_handle);
goto divert_cleanup_exit;
}
}
status = FwpmTransactionCommit0(context->engine_handle);
if (!NT_SUCCESS(status))
{
DEBUG_ERROR("failed to commit WFP transaction", status);
goto divert_cleanup_exit;
}
divert_cleanup_exit:
FwpmEngineClose0(context->engine_handle);
for (i = 0; i < DIVERT_CONTEXT_NUMLAYERS; i++)
{
FwpsCalloutUnregisterByKey0(&context->callout_guid[i]);
if (context->registered[i])
{
FwpsCalloutUnregisterByKey0(&context->callout_guid[i]);
}
}
NdisFreeNetBufferPool(context->pool_handle);
}
@@ -1327,6 +1418,16 @@ extern VOID divert_ioctl(IN WDFQUEUE queue, IN WDFREQUEST request,
break;
case IOCTL_DIVERT_SET_FILTER:
{
BOOL is_inbound, is_outbound, is_ipv4, is_ipv6;
if (InterlockedExchange(&context->filter_on, TRUE) == TRUE)
{
status = STATUS_INVALID_DEVICE_REQUEST;
DEBUG_ERROR("duplicate SET_FILTER ioctl", status);
goto divert_ioctl_exit;
}
filter = (divert_ioctl_filter_t)outbuf;
filter_len = outbuflen;
if (!divert_filter_compile(filter, filter_len, context->filter))
@@ -1335,8 +1436,14 @@ extern VOID divert_ioctl(IN WDFQUEUE queue, IN WDFREQUEST request,
DEBUG_ERROR("failed to compile filter", status);
goto divert_ioctl_exit;
}
break;
divert_filter_analyze(context->filter, &is_inbound, &is_outbound,
&is_ipv4, &is_ipv6);
status = divert_register_callouts(context, is_inbound,
is_outbound, is_ipv4, is_ipv6);
break;
}
default:
status = STATUS_INVALID_DEVICE_REQUEST;
DEBUG_ERROR("failed to complete I/O control; invalid request",
@@ -2303,6 +2410,137 @@ static BOOL divert_filter(PNET_BUFFER buffer, UINT32 if_idx, UINT32 sub_if_idx,
return FALSE;
}
/*
* Analyze the given filter.
*/
static void divert_filter_analyze(filter_t filter, BOOL *is_inbound,
BOOL *is_outbound, BOOL *is_ipv4, BOOL *is_ipv6)
{
BOOL result;
// False filter?
result = divert_filter_test(filter, 0, DIVERT_FILTER_PROTOCOL_NONE,
DIVERT_FILTER_FIELD_ZERO, 0);
if (!result)
{
*is_inbound = FALSE;
*is_outbound = FALSE;
*is_ipv4 = FALSE;
*is_ipv6 = FALSE;
return;
}
// Inbound?
result = divert_filter_test(filter, 0, DIVERT_FILTER_PROTOCOL_NONE,
DIVERT_FILTER_FIELD_INBOUND, 1);
if (result)
{
result = divert_filter_test(filter, 0, DIVERT_FILTER_PROTOCOL_NONE,
DIVERT_FILTER_FIELD_OUTBOUND, 0);
}
*is_inbound = result;
// Outbound?
result = divert_filter_test(filter, 0, DIVERT_FILTER_PROTOCOL_NONE,
DIVERT_FILTER_FIELD_OUTBOUND, 1);
if (result)
{
result = divert_filter_test(filter, 0, DIVERT_FILTER_PROTOCOL_NONE,
DIVERT_FILTER_FIELD_INBOUND, 0);
}
*is_outbound = result;
// IPv4?
result = divert_filter_test(filter, 0, DIVERT_FILTER_PROTOCOL_NONE,
DIVERT_FILTER_FIELD_IP, 1);
if (result)
{
result = divert_filter_test(filter, 0, DIVERT_FILTER_PROTOCOL_NONE,
DIVERT_FILTER_FIELD_IPV6, 0);
}
*is_ipv4 = result;
// Ipv6?
result = divert_filter_test(filter, 0, DIVERT_FILTER_PROTOCOL_NONE,
DIVERT_FILTER_FIELD_IPV6, 1);
if (result)
{
result = divert_filter_test(filter, 0, DIVERT_FILTER_PROTOCOL_NONE,
DIVERT_FILTER_FIELD_IP, 0);
}
*is_ipv6 = result;
}
/*
* Test a filter for any packet where field = arg.
*/
static BOOL divert_filter_test(filter_t filter, UINT8 ip, UINT8 protocol,
UINT8 field, UINT32 arg)
{
BOOL known = FALSE;
BOOL result = FALSE;
if (ip == DIVERT_FILTER_RESULT_ACCEPT)
{
return TRUE;
}
if (ip == DIVERT_FILTER_RESULT_REJECT)
{
return FALSE;
}
if (ip > DIVERT_FILTER_MAXLEN)
{
return FALSE;
}
if (filter[ip].protocol == protocol &&
filter[ip].field == field)
{
known = TRUE;
switch (filter[ip].test)
{
case DIVERT_FILTER_TEST_EQ:
result = (arg == filter[ip].arg[0]);
break;
case DIVERT_FILTER_TEST_NEQ:
result = (arg != filter[ip].arg[0]);
break;
case DIVERT_FILTER_TEST_LT:
result = (arg < filter[ip].arg[0]);
break;
case DIVERT_FILTER_TEST_LEQ:
result = (arg <= filter[ip].arg[0]);
break;
case DIVERT_FILTER_TEST_GT:
result = (arg > filter[ip].arg[0]);
break;
case DIVERT_FILTER_TEST_GEQ:
result = (arg >= filter[ip].arg[0]);
break;
default:
result = FALSE;
break;
}
}
if (!known)
{
result = divert_filter_test(filter, filter[ip].success, protocol,
field, arg);
if (result)
{
return TRUE;
}
return divert_filter_test(filter, filter[ip].failure, protocol, field,
arg);
}
else
{
ip = (result? filter[ip].success: filter[ip].failure);
return divert_filter_test(filter, ip, protocol, field, arg);
}
}
/*
* Compile a divert filter from an IOCTL.
*/