summaryrefslogtreecommitdiff
path: root/datapath-windows/ovsext/TunnelFilter.c
diff options
context:
space:
mode:
Diffstat (limited to 'datapath-windows/ovsext/TunnelFilter.c')
-rw-r--r--datapath-windows/ovsext/TunnelFilter.c881
1 files changed, 725 insertions, 156 deletions
diff --git a/datapath-windows/ovsext/TunnelFilter.c b/datapath-windows/ovsext/TunnelFilter.c
index c2186eb42..a47e0aa5b 100644
--- a/datapath-windows/ovsext/TunnelFilter.c
+++ b/datapath-windows/ovsext/TunnelFilter.c
@@ -48,27 +48,24 @@
/* Infinite timeout */
#define INFINITE 0xFFFFFFFF
-/*
- * The provider name should always match the provider string from the install
- * file.
- */
+/* The provider name should always match the provider string from the install
+ * file. */
#define OVS_TUNNEL_PROVIDER_NAME L"Open vSwitch"
-/*
- * The provider description should always contain the OVS service description
- * string from the install file.
- */
+/* The provider description should always contain the OVS service description
+ * string from the install file. */
#define OVS_TUNNEL_PROVIDER_DESC L"Open vSwitch Extension tunnel provider"
/* The session name isn't required but it's useful for diagnostics. */
#define OVS_TUNNEL_SESSION_NAME L"OVS tunnel session"
-/* Configurable parameters (addresses and ports are in host order) */
-UINT16 configNewDestPort = VXLAN_UDP_PORT;
+/* Maximum number of tunnel threads to be created. */
+#define OVS_TUNFLT_MAX_THREADS 8
/*
* Callout and sublayer GUIDs
*/
+
// b16b0a6e-2b2a-41a3-8b39-bd3ffc855ff8
DEFINE_GUID(
OVS_TUNNEL_CALLOUT_V4,
@@ -106,41 +103,108 @@ DEFINE_GUID(
);
/*
- * Callout driver global variables
+ * Callout driver type definitions
*/
-PDEVICE_OBJECT gDeviceObject;
+typedef enum _OVS_TUNFLT_OPERATION {
+ OVS_TUN_FILTER_CREATE = 0,
+ OVS_TUN_FILTER_DELETE
+} OVS_TUNFLT_OPERATION;
+
+typedef struct _OVS_TUNFLT_REQUEST {
+ LIST_ENTRY entry;
+ /* Tunnel filter destination port. */
+ UINT16 port;
+ /* XXX: We also need to specify the tunnel L4 protocol, because there are
+ * different protocols that can use the same destination port.*/
+ union {
+ /* Tunnel filter identification used for filter deletion. */
+ UINT64 delID;
+ /* Pointer used to return filter ID to the caller on filter creation. */
+ PUINT64 addID;
+ }filterID;
+ /* Requested operation to be performed. */
+ OVS_TUNFLT_OPERATION operation;
+ /* Current I/O request to be completed when requested
+ * operation is finished. */
+ PIRP irp;
+ /* Callback function called before completing the IRP. */
+ PFNTunnelVportPendingOp callback;
+ /* Context passed to the callback function. */
+ PVOID context;
+} OVS_TUNFLT_REQUEST, *POVS_TUNFLT_REQUEST;
+
+typedef struct _OVS_TUNFLT_REQUEST_LIST {
+ /* SpinLock for syncronizing access to the requests list. */
+ NDIS_SPIN_LOCK spinlock;
+ /* Head of the requests list. */
+ LIST_ENTRY head;
+ /* Number of requests in the list. This variable is used by
+ * InterlockedCompareExchange function and needs to be aligned
+ * at 32-bit boundaries. */
+ UINT32 numEntries;
+} OVS_TUNFLT_REQUEST_LIST, *POVS_TUNFLT_REQUEST_LIST;
+
+typedef struct _OVS_TUNFLT_THREAD_CONTEXT {
+ /* Thread identification. */
+ UINT threadID;
+ /* Thread's engine session handle. */
+ HANDLE engineSession;
+ /* Reference of the thread object. */
+ PVOID threadObject;
+ /* Requests queue list. */
+ OVS_TUNFLT_REQUEST_LIST listRequests;
+ /* Event signaling that there are requests to process. */
+ KEVENT requestEvent;
+ /* Event for stopping thread execution. */
+ KEVENT stopEvent;
+} OVS_TUNFLT_THREAD_CONTEXT, *POVS_TUNFLT_THREAD_CONTEXT;
+
+KSTART_ROUTINE OvsTunnelFilterThreadProc;
+
+static NTSTATUS OvsTunnelFilterStartThreads();
+static NTSTATUS OvsTunnelFilterThreadStart(POVS_TUNFLT_THREAD_CONTEXT threadCtx);
+static VOID OvsTunnelFilterStopThreads();
+static VOID OvsTunnelFilterThreadStop(POVS_TUNFLT_THREAD_CONTEXT threadCtx,
+ BOOLEAN signalEvent);
+static NTSTATUS OvsTunnelFilterThreadInit(POVS_TUNFLT_THREAD_CONTEXT threadCtx);
+static VOID OvsTunnelFilterThreadUninit(POVS_TUNFLT_THREAD_CONTEXT threadCtx);
-HANDLE gEngineHandle = NULL;
-HANDLE gTunnelProviderBfeHandle = NULL;
-HANDLE gTunnelInitBfeHandle = NULL;
-UINT32 gCalloutIdV4;
+/*
+ * Callout driver global variables
+ */
+static PDEVICE_OBJECT gDeviceObject = NULL;
+static HANDLE gEngineHandle = NULL;
+static HANDLE gTunnelProviderBfeHandle = NULL;
+static HANDLE gTunnelInitBfeHandle = NULL;
+static HANDLE gBfeSubscriptionHandle = NULL;
+static UINT32 gCalloutIdV4 = 0;
+static OVS_TUNFLT_THREAD_CONTEXT gTunnelThreadCtx[OVS_TUNFLT_MAX_THREADS] = { 0 };
-/* Callout driver implementation */
+/*
+ * Callout driver implementation.
+ */
NTSTATUS
-OvsTunnelEngineOpen(HANDLE *handle)
+OvsTunnelEngineOpen(HANDLE *engineSession)
{
NTSTATUS status = STATUS_SUCCESS;
FWPM_SESSION session = { 0 };
- /* The session name isn't required but may be useful for diagnostics. */
- session.displayData.name = OVS_TUNNEL_SESSION_NAME;
/*
* Set an infinite wait timeout, so we don't have to handle FWP_E_TIMEOUT
* errors while waiting to acquire the transaction lock.
*/
session.txnWaitTimeoutInMSec = INFINITE;
- session.flags = FWPM_SESSION_FLAG_DYNAMIC;
/* The authentication service should always be RPC_C_AUTHN_DEFAULT. */
status = FwpmEngineOpen(NULL,
RPC_C_AUTHN_DEFAULT,
NULL,
&session,
- handle);
+ engineSession);
if (!NT_SUCCESS(status)) {
- OVS_LOG_ERROR("Fail to open filtering engine session, status: %x.",
+ OVS_LOG_ERROR("Failed to open filtering engine session, status: %x.",
status);
}
@@ -148,23 +212,23 @@ OvsTunnelEngineOpen(HANDLE *handle)
}
VOID
-OvsTunnelEngineClose(HANDLE *handle)
+OvsTunnelEngineClose(HANDLE *engineSession)
{
- if (*handle) {
- FwpmEngineClose(*handle);
- *handle = NULL;
+ if (*engineSession) {
+ FwpmEngineClose(*engineSession);
+ *engineSession = NULL;
}
}
VOID
-OvsTunnelAddSystemProvider(HANDLE handle)
+OvsTunnelAddSystemProvider(HANDLE engineSession)
{
NTSTATUS status = STATUS_SUCCESS;
BOOLEAN inTransaction = FALSE;
FWPM_PROVIDER provider = { 0 };
do {
- status = FwpmTransactionBegin(handle, 0);
+ status = FwpmTransactionBegin(engineSession, 0);
if (!NT_SUCCESS(status)) {
break;
}
@@ -180,7 +244,7 @@ OvsTunnelAddSystemProvider(HANDLE handle)
*/
provider.flags = FWPM_PROVIDER_FLAG_PERSISTENT;
- status = FwpmProviderAdd(handle,
+ status = FwpmProviderAdd(engineSession,
&provider,
NULL);
if (!NT_SUCCESS(status)) {
@@ -191,7 +255,7 @@ OvsTunnelAddSystemProvider(HANDLE handle)
}
}
- status = FwpmTransactionCommit(handle);
+ status = FwpmTransactionCommit(engineSession);
if (!NT_SUCCESS(status)) {
break;
}
@@ -200,30 +264,30 @@ OvsTunnelAddSystemProvider(HANDLE handle)
} while (inTransaction);
if (inTransaction){
- FwpmTransactionAbort(handle);
+ FwpmTransactionAbort(engineSession);
}
}
VOID
-OvsTunnelRemoveSystemProvider(HANDLE handle)
+OvsTunnelRemoveSystemProvider(HANDLE engineSession)
{
NTSTATUS status = STATUS_SUCCESS;
BOOLEAN inTransaction = FALSE;
do {
- status = FwpmTransactionBegin(handle, 0);
+ status = FwpmTransactionBegin(engineSession, 0);
if (!NT_SUCCESS(status)) {
break;
}
inTransaction = TRUE;
- status = FwpmProviderDeleteByKey(handle,
+ status = FwpmProviderDeleteByKey(engineSession,
&OVS_TUNNEL_PROVIDER_KEY);
if (!NT_SUCCESS(status)) {
break;
}
- status = FwpmTransactionCommit(handle);
+ status = FwpmTransactionCommit(engineSession);
if (!NT_SUCCESS(status)) {
break;
}
@@ -232,29 +296,30 @@ OvsTunnelRemoveSystemProvider(HANDLE handle)
} while (inTransaction);
if (inTransaction){
- FwpmTransactionAbort(handle);
+ FwpmTransactionAbort(engineSession);
}
}
NTSTATUS
-OvsTunnelAddFilter(PWSTR filterName,
+OvsTunnelAddFilter(HANDLE engineSession,
+ PWSTR filterName,
const PWSTR filterDesc,
USHORT remotePort,
FWP_DIRECTION direction,
UINT64 context,
const GUID *filterKey,
const GUID *layerKey,
- const GUID *calloutKey)
+ const GUID *calloutKey,
+ UINT64 *filterID)
{
NTSTATUS status = STATUS_SUCCESS;
FWPM_FILTER filter = {0};
FWPM_FILTER_CONDITION filterConditions[3] = {0};
UINT conditionIndex;
- UNREFERENCED_PARAMETER(remotePort);
- UNREFERENCED_PARAMETER(direction);
-
- filter.filterKey = *filterKey;
+ if (filterKey) {
+ filter.filterKey = *filterKey;
+ }
filter.layerKey = *layerKey;
filter.displayData.name = (wchar_t*)filterName;
filter.displayData.description = (wchar_t*)filterDesc;
@@ -284,64 +349,18 @@ OvsTunnelAddFilter(PWSTR filterName,
filter.numFilterConditions = conditionIndex;
- status = FwpmFilterAdd(gEngineHandle,
+ status = FwpmFilterAdd(engineSession,
&filter,
NULL,
- NULL);
+ filterID);
return status;
}
-NTSTATUS
-OvsTunnelRemoveFilter(const GUID *filterKey,
- const GUID *sublayerKey)
-{
- NTSTATUS status = STATUS_SUCCESS;
- BOOLEAN inTransaction = FALSE;
-
- do {
- status = FwpmTransactionBegin(gEngineHandle, 0);
- if (!NT_SUCCESS(status)) {
- break;
- }
-
- inTransaction = TRUE;
-
- /*
- * We have to delete the filter first since it references the
- * sublayer. If we tried to delete the sublayer first, it would fail
- * with FWP_ERR_IN_USE.
- */
- status = FwpmFilterDeleteByKey(gEngineHandle,
- filterKey);
- if (!NT_SUCCESS(status)) {
- break;
- }
-
- status = FwpmSubLayerDeleteByKey(gEngineHandle,
- sublayerKey);
- if (!NT_SUCCESS(status)) {
- break;
- }
-
- status = FwpmTransactionCommit(gEngineHandle);
- if (!NT_SUCCESS(status)){
- break;
- }
-
- inTransaction = FALSE;
- } while (inTransaction);
-
- if (inTransaction) {
- FwpmTransactionAbort(gEngineHandle);
- }
- return status;
-}
-
/*
* --------------------------------------------------------------------------
- * This function registers callouts and filters that intercept UDP traffic at
- * WFP FWPM_LAYER_DATAGRAM_DATA_V4
+ * This function registers callouts for intercepting UDP traffic at WFP
+ * FWPM_LAYER_DATAGRAM_DATA_V4 layer.
* --------------------------------------------------------------------------
*/
NTSTATUS
@@ -368,10 +387,7 @@ OvsTunnelRegisterDatagramDataCallouts(const GUID *layerKey,
sCallout.flags = FWP_CALLOUT_FLAG_CONDITIONAL_ON_FLOW;
#endif
- status = FwpsCalloutRegister(deviceObject,
- &sCallout,
- calloutId);
-
+ status = FwpsCalloutRegister(deviceObject, &sCallout, calloutId);
if (!NT_SUCCESS(status)) {
goto Exit;
}
@@ -384,24 +400,11 @@ OvsTunnelRegisterDatagramDataCallouts(const GUID *layerKey,
mCallout.displayData = displayData;
mCallout.applicableLayer = *layerKey;
- status = FwpmCalloutAdd(gEngineHandle,
- &mCallout,
- NULL,
- NULL);
-
+ status = FwpmCalloutAdd(gEngineHandle, &mCallout, NULL, NULL);
if (!NT_SUCCESS(status)) {
goto Exit;
}
- status = OvsTunnelAddFilter(L"Datagram-Data OVS Filter (Inbound)",
- L"address/port for UDP",
- configNewDestPort,
- FWP_DIRECTION_INBOUND,
- 0,
- &OVS_TUNNEL_FILTER_KEY,
- layerKey,
- calloutKey);
-
Exit:
if (!NT_SUCCESS(status)){
@@ -416,24 +419,16 @@ Exit:
/*
* --------------------------------------------------------------------------
- * This function registers dynamic callouts and filters that intercept UDP
- * Callouts and filters will be removed during De-Initialize.
+ * This function registers non-dynamic callouts for intercepting UDP traffic.
+ * Callouts will be removed during un-initializing phase.
* --------------------------------------------------------------------------
*/
NTSTATUS
OvsTunnelRegisterCallouts(VOID *deviceObject)
{
- NTSTATUS status = STATUS_SUCCESS;
- FWPM_SUBLAYER OvsTunnelSubLayer;
-
- BOOLEAN engineOpened = FALSE;
- BOOLEAN inTransaction = FALSE;
-
- status = OvsTunnelEngineOpen(&gEngineHandle);
- if (!NT_SUCCESS(status)) {
- goto Exit;
- }
- engineOpened = TRUE;
+ NTSTATUS status = STATUS_SUCCESS;
+ BOOLEAN inTransaction = FALSE;
+ FWPM_SUBLAYER OvsTunnelSubLayer;
status = FwpmTransactionBegin(gEngineHandle, 0);
if (!NT_SUCCESS(status)) {
@@ -476,22 +471,17 @@ Exit:
if (inTransaction) {
FwpmTransactionAbort(gEngineHandle);
}
- if (engineOpened) {
- OvsTunnelEngineClose(&gEngineHandle);
- }
}
return status;
}
VOID
-OvsTunnelUnregisterCallouts(VOID)
+OvsTunnelUnregisterCallouts()
{
- OvsTunnelRemoveFilter(&OVS_TUNNEL_FILTER_KEY,
- &OVS_TUNNEL_SUBLAYER);
FwpsCalloutUnregisterById(gCalloutIdV4);
+ FwpmSubLayerDeleteByKey(gEngineHandle, &OVS_TUNNEL_SUBLAYER);
FwpmCalloutDeleteById(gEngineHandle, gCalloutIdV4);
- OvsTunnelEngineClose(&gEngineHandle);
}
VOID
@@ -499,16 +489,22 @@ OvsTunnelFilterUninitialize(PDRIVER_OBJECT driverObject)
{
UNREFERENCED_PARAMETER(driverObject);
+ OvsTunnelFilterStopThreads();
+
OvsTunnelUnregisterCallouts();
- IoDeleteDevice(gDeviceObject);
+ OvsTunnelEngineClose(&gEngineHandle);
+
+ if (gDeviceObject) {
+ IoDeleteDevice(gDeviceObject);
+ }
}
NTSTATUS
OvsTunnelFilterInitialize(PDRIVER_OBJECT driverObject)
{
- NTSTATUS status = STATUS_SUCCESS;
- UNICODE_STRING deviceName;
+ NTSTATUS status = STATUS_SUCCESS;
+ UNICODE_STRING deviceName;
RtlInitUnicodeString(&deviceName,
L"\\Device\\OvsTunnelFilter");
@@ -522,21 +518,31 @@ OvsTunnelFilterInitialize(PDRIVER_OBJECT driverObject)
&gDeviceObject);
if (!NT_SUCCESS(status)){
+ OVS_LOG_ERROR("Failed to create tunnel filter device, status: %x.",
+ status);
+ goto Exit;
+ }
+
+ status = OvsTunnelFilterStartThreads();
+ if (!NT_SUCCESS(status)){
+ goto Exit;
+ }
+
+ status = OvsTunnelEngineOpen(&gEngineHandle);
+ if (!NT_SUCCESS(status)){
goto Exit;
}
status = OvsTunnelRegisterCallouts(gDeviceObject);
+ if (!NT_SUCCESS(status)) {
+ OVS_LOG_ERROR("Failed to register callout, status: %x.",
+ status);
+ }
Exit:
if (!NT_SUCCESS(status)){
- if (gEngineHandle != NULL) {
- OvsTunnelUnregisterCallouts();
- }
-
- if (gDeviceObject) {
- IoDeleteDevice(gDeviceObject);
- }
+ OvsTunnelFilterUninitialize(driverObject);
}
return status;
@@ -546,16 +552,16 @@ VOID NTAPI
OvsTunnelProviderBfeCallback(PVOID context,
FWPM_SERVICE_STATE bfeState)
{
- HANDLE handle = NULL;
+ HANDLE engineSession = NULL;
DBG_UNREFERENCED_PARAMETER(context);
if (FWPM_SERVICE_RUNNING == bfeState) {
- OvsTunnelEngineOpen(&handle);
- if (handle) {
- OvsTunnelAddSystemProvider(handle);
+ OvsTunnelEngineOpen(&engineSession);
+ if (engineSession) {
+ OvsTunnelAddSystemProvider(engineSession);
}
- OvsTunnelEngineClose(&handle);
+ OvsTunnelEngineClose(&engineSession);
}
}
@@ -599,16 +605,16 @@ VOID
OvsRegisterSystemProvider(PVOID deviceObject)
{
NTSTATUS status = STATUS_SUCCESS;
- HANDLE handle = NULL;
+ HANDLE engineSession = NULL;
status = OvsSubscribeTunnelProviderBfeStateChanges(deviceObject);
if (NT_SUCCESS(status)) {
if (FWPM_SERVICE_RUNNING == FwpmBfeStateGet()) {
- OvsTunnelEngineOpen(&handle);
- if (handle) {
- OvsTunnelAddSystemProvider(handle);
+ OvsTunnelEngineOpen(&engineSession);
+ if (engineSession) {
+ OvsTunnelAddSystemProvider(engineSession);
}
- OvsTunnelEngineClose(&handle);
+ OvsTunnelEngineClose(&engineSession);
OvsUnsubscribeTunnelProviderBfeStateChanges();
}
@@ -617,13 +623,13 @@ OvsRegisterSystemProvider(PVOID deviceObject)
VOID OvsUnregisterSystemProvider()
{
- HANDLE handle = NULL;
+ HANDLE engineSession = NULL;
- OvsTunnelEngineOpen(&handle);
- if (handle) {
- OvsTunnelRemoveSystemProvider(handle);
+ OvsTunnelEngineOpen(&engineSession);
+ if (engineSession) {
+ OvsTunnelRemoveSystemProvider(engineSession);
}
- OvsTunnelEngineClose(&handle);
+ OvsTunnelEngineClose(&engineSession);
OvsUnsubscribeTunnelProviderBfeStateChanges();
}
@@ -711,3 +717,566 @@ VOID OvsUninitTunnelFilter(PDRIVER_OBJECT driverObject)
OvsTunnelFilterUninitialize(driverObject);
OvsUnsubscribeTunnelInitBfeStateChanges();
}
+
+NTSTATUS
+OvsTunnelAddFilterEx(HANDLE engineSession,
+ UINT32 filterPort,
+ UINT64 *filterID)
+{
+ NTSTATUS status = STATUS_SUCCESS;
+
+ status = OvsTunnelAddFilter(engineSession,
+ L"Datagram-Data OVS Filter (Inbound)",
+ L"address/port for UDP",
+ (USHORT)filterPort,
+ FWP_DIRECTION_INBOUND,
+ 0,
+ NULL,
+ &FWPM_LAYER_DATAGRAM_DATA_V4,
+ &OVS_TUNNEL_CALLOUT_V4,
+ filterID);
+ if (!NT_SUCCESS(status)) {
+ OVS_LOG_ERROR("Failed to add tunnel filter for port: %d, status: %x.",
+ filterPort, status);
+ } else {
+ OVS_LOG_INFO("Filter added, filter port: %d, filter ID: %d.",
+ filterPort, *filterID);
+ }
+
+ return status;
+}
+
+NTSTATUS
+OvsTunnelRemoveFilterEx(HANDLE engineSession,
+ UINT64 filterID)
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ BOOLEAN error = TRUE;
+
+ do {
+ if (filterID == 0) {
+ OVS_LOG_INFO("No tunnel filter to remove.");
+ break;
+ }
+
+ status = FwpmFilterDeleteById(engineSession, filterID);
+ if (!NT_SUCCESS(status)) {
+ OVS_LOG_ERROR("Failed to remove tunnel with filter ID: %d,\
+ status: %x.", filterID, status);
+ break;
+ }
+ OVS_LOG_INFO("Filter removed, filter ID: %d.",
+ filterID);
+
+ error = FALSE;
+ } while (error);
+
+ return status;
+}
+
+NTSTATUS
+OvsTunnelFilterExecuteAction(HANDLE engineSession,
+ POVS_TUNFLT_REQUEST request)
+{
+ NTSTATUS status = STATUS_SUCCESS;
+
+ switch (request->operation)
+ {
+ case OVS_TUN_FILTER_CREATE:
+ status = OvsTunnelAddFilterEx(engineSession,
+ request->port,
+ request->filterID.addID);
+ break;
+ case OVS_TUN_FILTER_DELETE:
+ status = OvsTunnelRemoveFilterEx(engineSession,
+ request->filterID.delID);
+ break;
+ default:
+ status = STATUS_NOT_SUPPORTED;
+ break;
+ }
+
+ return status;
+}
+
+VOID
+OvsTunnelFilterRequestPopList(POVS_TUNFLT_REQUEST_LIST listRequests,
+ PLIST_ENTRY head,
+ UINT32 *count)
+{
+ NdisAcquireSpinLock(&listRequests->spinlock);
+
+ if (!IsListEmpty(&listRequests->head)) {
+ PLIST_ENTRY PrevEntry;
+ PLIST_ENTRY NextEntry;
+
+ NextEntry = listRequests->head.Flink;
+ PrevEntry = listRequests->head.Blink;
+
+ head->Flink = NextEntry;
+ NextEntry->Blink = head;
+
+ head->Blink = PrevEntry;
+ PrevEntry->Flink = head;
+
+ *count = listRequests->numEntries;
+
+ InitializeListHead(&listRequests->head);
+ listRequests->numEntries = 0;
+ }
+
+ NdisReleaseSpinLock(&listRequests->spinlock);
+}
+
+VOID
+OvsTunnelFilterRequestPush(POVS_TUNFLT_REQUEST_LIST listRequests,
+ POVS_TUNFLT_REQUEST request)
+{
+ NdisAcquireSpinLock(&listRequests->spinlock);
+
+ InsertTailList(&listRequests->head, &(request->entry));
+ listRequests->numEntries++;
+
+ NdisReleaseSpinLock(&listRequests->spinlock);
+}
+
+VOID
+OvsTunnelFilterThreadPush(POVS_TUNFLT_REQUEST request)
+{
+ UINT32 threadIndex;
+
+ threadIndex = request->port % OVS_TUNFLT_MAX_THREADS;
+
+ OvsTunnelFilterRequestPush(
+ &gTunnelThreadCtx[threadIndex].listRequests,
+ request);
+
+ KeSetEvent(&gTunnelThreadCtx[threadIndex].requestEvent,
+ IO_NO_INCREMENT,
+ FALSE);
+}
+
+VOID
+OvsTunnelFilterCompleteRequest(PIRP irp,
+ PFNTunnelVportPendingOp callback,
+ PVOID context,
+ NTSTATUS status)
+{
+ UINT32 replyLen = 0;
+
+ if (callback) {
+ callback(context, status, &replyLen);
+ /* Release the context passed to the callback function. */
+ OvsFreeMemory(context);
+ }
+
+ if (irp) {
+ OvsCompleteIrpRequest(irp, (ULONG_PTR)replyLen, status);
+ }
+}
+
+VOID
+OvsTunnelFilterRequestListProcess(POVS_TUNFLT_THREAD_CONTEXT threadCtx)
+{
+ POVS_TUNFLT_REQUEST request = NULL;
+ PLIST_ENTRY link = NULL;
+ PLIST_ENTRY next = NULL;
+ LIST_ENTRY head;
+ NTSTATUS status = STATUS_SUCCESS;
+ UINT32 count = 0;
+ BOOLEAN inTransaction = FALSE;
+ BOOLEAN error = TRUE;
+
+ do
+ {
+ if (!InterlockedCompareExchange(
+ (LONG volatile *)&threadCtx->listRequests.numEntries, 0, 0)) {
+ OVS_LOG_INFO("Nothing to do... request list is empty.");
+ break;
+ }
+
+ status = FwpmTransactionBegin(threadCtx->engineSession, 0);
+ if (!NT_SUCCESS(status)) {
+ OVS_LOG_ERROR("Failed to start transaction, status: %x.",
+ status);
+ break;
+ }
+ inTransaction = TRUE;
+
+ InitializeListHead(&head);
+ OvsTunnelFilterRequestPopList(&threadCtx->listRequests, &head, &count);
+
+ LIST_FORALL_SAFE(&head, link, next) {
+ request = CONTAINING_RECORD(link, OVS_TUNFLT_REQUEST, entry);
+
+ status = OvsTunnelFilterExecuteAction(threadCtx->engineSession,
+ request);
+ if (!NT_SUCCESS(status)) {
+ RemoveEntryList(&request->entry);
+ count--;
+
+ /* Complete the IRP with the failure status. */
+ OvsTunnelFilterCompleteRequest(request->irp,
+ request->callback,
+ request->context,
+ status);
+ OvsFreeMemory(request);
+ request = NULL;
+ } else {
+ error = FALSE;
+ }
+ }
+
+ if (error) {
+ /* No successful requests were made, so there is no point to commit
+ * the transaction. */
+ break;
+ }
+
+ status = FwpmTransactionCommit(threadCtx->engineSession);
+ if (!NT_SUCCESS(status)){
+ OVS_LOG_ERROR("Failed to commit transaction, status: %x.",
+ status);
+ break;
+ }
+
+ inTransaction = FALSE;
+ } while (inTransaction);
+
+ if (inTransaction) {
+ FwpmTransactionAbort(threadCtx->engineSession);
+ OVS_LOG_ERROR("Failed to execute request, status: %x.\
+ Transaction aborted.", status);
+ }
+
+ /* Complete the requests successfully executed with the transaction commit
+ * status. */
+ while (count) {
+ request = (POVS_TUNFLT_REQUEST)RemoveHeadList(&head);
+ count--;
+
+ OvsTunnelFilterCompleteRequest(request->irp,
+ request->callback,
+ request->context,
+ status);
+ OvsFreeMemory(request);
+ request = NULL;
+ }
+}
+
+/*
+ *----------------------------------------------------------------------------
+ * System thread routine that handles tunnel filter create/delete requests.
+ *----------------------------------------------------------------------------
+ */
+_Use_decl_annotations_
+VOID
+OvsTunnelFilterThreadProc(PVOID context)
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ POVS_TUNFLT_THREAD_CONTEXT threadCtx = (POVS_TUNFLT_THREAD_CONTEXT)context;
+ PKEVENT eventArray[2] = { 0 };
+ ULONG count = 0;
+ BOOLEAN exit = FALSE;
+ BOOLEAN error = TRUE;
+
+ OVS_LOG_INFO("Starting OVS Tunnel system thread %d.",
+ threadCtx->threadID);
+
+ eventArray[0] = &threadCtx->stopEvent;
+ eventArray[1] = &threadCtx->requestEvent;
+ count = ARRAY_SIZE(eventArray);
+
+ do {
+ status = OvsTunnelFilterThreadInit(threadCtx);
+ if (!NT_SUCCESS(status)) {
+ OVS_LOG_ERROR("Failed to initialize tunnel filter thread %d.",
+ threadCtx->threadID);
+ break;
+ }
+
+ do {
+ status = KeWaitForMultipleObjects(count,
+ (PVOID)eventArray,
+ WaitAny,
+ Executive,
+ KernelMode,
+ FALSE,
+ NULL,
+ NULL);
+ switch (status) {
+ case STATUS_WAIT_1:
+ /* Start processing requests. */
+ OvsTunnelFilterRequestListProcess(threadCtx);
+ break;
+ default:
+ /* Finish processing the received requests and exit. */
+ OvsTunnelFilterRequestListProcess(threadCtx);
+ exit = TRUE;
+ break;
+ }
+ } while (!exit);
+
+ OvsTunnelFilterThreadUninit(threadCtx);
+
+ error = FALSE;
+ } while (error);
+
+ OVS_LOG_INFO("Terminating OVS Tunnel system thread %d.",
+ threadCtx->threadID);
+
+ PsTerminateSystemThread(STATUS_SUCCESS);
+};
+
+static NTSTATUS
+OvsTunnelFilterStartThreads()
+{
+ NTSTATUS status = STATUS_SUCCESS;
+
+ for (UINT index = 0; index < OVS_TUNFLT_MAX_THREADS; index++) {
+ gTunnelThreadCtx[index].threadID = index;
+
+ status = OvsTunnelFilterThreadStart(&gTunnelThreadCtx[index]);
+ if (!NT_SUCCESS(status)) {
+ OVS_LOG_ERROR("Failed to start tunnel filter thread %d.", index);
+ break;
+ }
+ }
+
+ return status;
+}
+
+static NTSTATUS
+OvsTunnelFilterThreadStart(POVS_TUNFLT_THREAD_CONTEXT threadCtx)
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ HANDLE threadHandle = NULL;
+ BOOLEAN error = TRUE;
+
+ do {
+ status = PsCreateSystemThread(&threadHandle,
+ SYNCHRONIZE,
+ NULL,
+ NULL,
+ NULL,
+ OvsTunnelFilterThreadProc,
+ threadCtx);
+ if (!NT_SUCCESS(status)) {
+ OVS_LOG_ERROR("Failed to create tunnel thread, status: %x.",
+ status);
+ break;
+ }
+
+ ObReferenceObjectByHandle(threadHandle,
+ SYNCHRONIZE,
+ NULL,
+ KernelMode,
+ &threadCtx->threadObject,
+ NULL);
+ ZwClose(threadHandle);
+ threadHandle = NULL;
+
+ error = FALSE;
+ } while (error);
+
+ return status;
+}
+
+static VOID
+OvsTunnelFilterStopThreads()
+{
+ /* Signal all threads to stop and ignore all subsequent requests. */
+ for (UINT index = 0; index < OVS_TUNFLT_MAX_THREADS; index++) {
+ OvsTunnelFilterThreadStop(&gTunnelThreadCtx[index], TRUE);
+ }
+
+ /* Wait for all threads to finish processing the requests. */
+ for (UINT index = 0; index < OVS_TUNFLT_MAX_THREADS; index++) {
+ OvsTunnelFilterThreadStop(&gTunnelThreadCtx[index], FALSE);
+ }
+}
+
+static VOID
+OvsTunnelFilterThreadStop(POVS_TUNFLT_THREAD_CONTEXT threadCtx,
+ BOOLEAN signalEvent)
+{
+ if (signalEvent) {
+ /* Signal stop thread event. */
+ OVS_LOG_INFO("Received stop event for OVS Tunnel system thread %d.",
+ threadCtx->threadID);
+ KeSetEvent(&threadCtx->stopEvent, IO_NO_INCREMENT, FALSE);
+ } else {
+ /* Wait for the tunnel thread to finish. */
+ KeWaitForSingleObject(threadCtx->threadObject,
+ Executive,
+ KernelMode,
+ FALSE,
+ NULL);
+
+ ObDereferenceObject(threadCtx->threadObject);
+ }
+}
+
+static NTSTATUS
+OvsTunnelFilterThreadInit(POVS_TUNFLT_THREAD_CONTEXT threadCtx)
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ BOOLEAN error = TRUE;
+
+ do {
+ /* Create thread's engine session object. */
+ status = OvsTunnelEngineOpen(&threadCtx->engineSession);
+ if (!NT_SUCCESS(status)) {
+ break;
+ }
+
+ NdisAllocateSpinLock(&threadCtx->listRequests.spinlock);
+
+ InitializeListHead(&threadCtx->listRequests.head);
+
+ KeInitializeEvent(&threadCtx->stopEvent,
+ NotificationEvent,
+ FALSE);
+
+ KeInitializeEvent(&threadCtx->requestEvent,
+ SynchronizationEvent,
+ FALSE);
+
+ error = FALSE;
+ } while (error);
+
+ return status;
+}
+
+static VOID
+OvsTunnelFilterThreadUninit(POVS_TUNFLT_THREAD_CONTEXT threadCtx)
+{
+ if (threadCtx->engineSession) {
+ /* Close thread's FWPM session. */
+ OvsTunnelEngineClose(&threadCtx->engineSession);
+
+ NdisFreeSpinLock(&threadCtx->listRequests.spinlock);
+ }
+}
+
+NTSTATUS
+OvsTunnelFilterQueueRequest(PIRP irp,
+ UINT16 remotePort,
+ UINT64 *filterID,
+ OVS_TUNFLT_OPERATION operation,
+ PFNTunnelVportPendingOp callback,
+ PVOID tunnelContext)
+{
+ POVS_TUNFLT_REQUEST request = NULL;
+ NTSTATUS status = STATUS_PENDING;
+ BOOLEAN error = TRUE;
+ UINT64 timeout = 0;
+
+ do {
+ /* Verify if the stop event was signaled. */
+ if (STATUS_SUCCESS == KeWaitForSingleObject(
+ &gTunnelThreadCtx[0].stopEvent,
+ Executive,
+ KernelMode,
+ FALSE,
+ (LARGE_INTEGER *)&timeout)) {
+ /* The stop event is signaled. Completed the IRP with
+ * STATUS_CANCELLED. */
+ status = STATUS_CANCELLED;
+ break;
+ }
+
+ if (NULL == filterID) {
+ OVS_LOG_ERROR("Invalid request.");
+ status = STATUS_INVALID_PARAMETER;
+ break;
+ }
+
+ request = (POVS_TUNFLT_REQUEST) OvsAllocateMemory(sizeof(*request));
+ if (NULL == request) {
+ OVS_LOG_ERROR("Failed to allocate list item.");
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ break;
+ }
+
+ request->port = remotePort;
+ request->operation = operation;
+ switch (operation) {
+ case OVS_TUN_FILTER_CREATE:
+ request->filterID.addID = filterID;
+ break;
+ case OVS_TUN_FILTER_DELETE:
+ request->filterID.delID = *filterID;
+ break;
+ }
+ request->irp = irp;
+ request->callback = callback;
+ request->context = tunnelContext;
+
+ OvsTunnelFilterThreadPush(request);
+
+ error = FALSE;
+ } while (error);
+
+ if (error) {
+ OvsTunnelFilterCompleteRequest(irp, callback, tunnelContext, status);
+ if (request) {
+ OvsFreeMemory(request);
+ request = NULL;
+ }
+ }
+
+ return status;
+}
+
+/*
+ * --------------------------------------------------------------------------
+ * This function adds a new WFP filter for the received port and returns the
+ * ID of the created WFP filter.
+ *
+ * Note:
+ * All necessary calls to the WFP filtering engine must be running at IRQL =
+ * PASSIVE_LEVEL. Because the function is called at IRQL = DISPATCH_LEVEL,
+ * we register an OVS_TUN_FILTER_CREATE request that will be processed by
+ * the tunnel filter thread routine at IRQL = PASSIVE_LEVEL.
+ * --------------------------------------------------------------------------
+ */
+NTSTATUS
+OvsTunelFilterCreate(PIRP irp,
+ UINT16 filterPort,
+ UINT64 *filterID,
+ PFNTunnelVportPendingOp callback,
+ PVOID tunnelContext)
+{
+ return OvsTunnelFilterQueueRequest(irp,
+ filterPort,
+ filterID,
+ OVS_TUN_FILTER_CREATE,
+ callback,
+ tunnelContext);
+}
+
+/*
+ * --------------------------------------------------------------------------
+ * This function removes a WFP filter using the received filter ID.
+ *
+ * Note:
+ * All necessary calls to the WFP filtering engine must be running at IRQL =
+ * PASSIVE_LEVEL. Because the function is called at IRQL = DISPATCH_LEVEL,
+ * we register an OVS_TUN_FILTER_DELETE request that will be processed by
+ * the tunnel filter thread routine at IRQL = PASSIVE_LEVEL.
+ * --------------------------------------------------------------------------
+ */
+NTSTATUS
+OvsTunelFilterDelete(PIRP irp,
+ UINT64 filterID,
+ PFNTunnelVportPendingOp callback,
+ PVOID tunnelContext)
+{
+ return OvsTunnelFilterQueueRequest(irp,
+ 0,
+ &filterID,
+ OVS_TUN_FILTER_DELETE,
+ callback,
+ tunnelContext);
+}