[ovs-dev] [PATCH v5 1/4] datapath-windows: Support for custom VXLAN tunnel port

Sorin Vinturis svinturis at cloudbasesolutions.com
Thu May 21 11:30:02 UTC 2015


The kernel datapath supports only port 4789 for VXLAN tunnel creation.
Added support in order to allow for the VXLAN tunnel port to be
configurable to any port number set by the userspace.

The patch also checks to see if an existing WFP filter, for the
necessary UDP tunnel port, is already created before adding a new one.
This is a double check, because currently the userspace also verifies
this, but it is necessary to avoid future issues.

Custom VXLAN tunnel port requires the addition of a new WFP filter
with the new UDP tunnel port. The creation of a new WFP filter is
triggered in OvsInitVxlanTunnel function and the removal of the WFP
filter in OvsCleanupVxlanTunnel function.
But the latter functions are running at IRQL = DISPATCH_LEVEL, due
to the NDIS RW lock acquisition, and all WFP calls must be running at
IRQL = PASSIVE_LEVEL. This is why I have created a system thread which
records all filter addition/removal requests into a list for later
processing by the system thread. The ThreadStart routine processes all
received requests at IRQL = PASSIVE_LEVEL, which is the required IRQL
for the necessary WFP calls for adding/removal of the WFP filters.

The WFP filter for the default VXLAN port 4789 is not added anymore at
filter attach. All WFP filters for the tunnel ports are added when the
tunnel ports are initialized and are removed at cleanup. WFP operation
status is then reported to userspace.

It is necessary that OvsTunnelFilterUninitialize function is called
after OvsClearAllSwitchVports in order to allow for the added WFP
filters to be removed. OvsTunnelFilterUninitialize function closes the
global engine handle used by most of the WFP calls, including filter
removal.

Signed-off-by: Sorin Vinturis <svinturis at cloudbasesolutions.com>
Reported-by: Alin Gabriel Serdean <aserdean at cloudbasesolutions.com>
Reported-at: https://github.com/openvswitch/ovs-issues/issues/66
---
 datapath-windows/ovsext/Datapath.c             |   8 +-
 datapath-windows/ovsext/Netlink/Netlink.c      |   2 +
 datapath-windows/ovsext/Netlink/NetlinkError.h |  19 +-
 datapath-windows/ovsext/Switch.c               |   2 +-
 datapath-windows/ovsext/Tunnel.h               |   6 -
 datapath-windows/ovsext/TunnelFilter.c         | 880 ++++++++++++++++++++-----
 datapath-windows/ovsext/TunnelIntf.h           |  15 +
 datapath-windows/ovsext/Vport.c                | 463 ++++++++++---
 datapath-windows/ovsext/Vport.h                |  12 +-
 datapath-windows/ovsext/Vxlan.c                | 102 ++-
 datapath-windows/ovsext/Vxlan.h                |  13 +-
 11 files changed, 1247 insertions(+), 275 deletions(-)

diff --git a/datapath-windows/ovsext/Datapath.c b/datapath-windows/ovsext/Datapath.c
index 7646f0a..d482825 100644
--- a/datapath-windows/ovsext/Datapath.c
+++ b/datapath-windows/ovsext/Datapath.c
@@ -670,7 +670,6 @@ OvsCleanupDevice(PDEVICE_OBJECT deviceObject,
     return OvsCompleteIrpRequest(irp, (ULONG_PTR)0, status);
 }
 
-
 /*
  * --------------------------------------------------------------------------
  * IOCTL function handler for the device.
@@ -925,9 +924,12 @@ exit:
     KeMemoryBarrier();
     instance->inUse = 0;
 
-    /* Should not complete a pending IRP unless proceesing is completed */
+    /* Should not complete a pending IRP unless proceesing is completed. */
     if (status == STATUS_PENDING) {
-        return status;
+        /* STATUS_PENDING is returned by the NL handler when the request is
+         * to be processed later, so we mark the IRP as pending and complete
+         * it in another thread when the request is processed. */
+        IoMarkIrpPending(irp);
        return status;
     }
     return OvsCompleteIrpRequest(irp, (ULONG_PTR)replyLen, status);
 }
diff --git a/datapath-windows/ovsext/Netlink/Netlink.c b/datapath-windows/ovsext/Netlink/Netlink.c
index 589e3a1..a62d760 100644
--- a/datapath-windows/ovsext/Netlink/Netlink.c
+++ b/datapath-windows/ovsext/Netlink/Netlink.c
@@ -112,6 +112,8 @@ NlBuildErrorMsg(POVS_MESSAGE msgIn, POVS_MESSAGE_ERROR msgError, UINT errorCode)
 {
     NL_BUFFER nlBuffer;
 
+    ASSERT(errorCode != NL_ERROR_PENDING);
+
     NlBufInit(&nlBuffer, (PCHAR)msgError, sizeof *msgError);
     NlFillNlHdr(&nlBuffer, NLMSG_ERROR, 0,
                 msgIn->nlMsg.nlmsgSeq, msgIn->nlMsg.nlmsgPid);
diff --git a/datapath-windows/ovsext/Netlink/NetlinkError.h b/datapath-windows/ovsext/Netlink/NetlinkError.h
index 53c935f..eefa89e 100644
--- a/datapath-windows/ovsext/Netlink/NetlinkError.h
+++ b/datapath-windows/ovsext/Netlink/NetlinkError.h
@@ -195,14 +195,16 @@ typedef enum _NL_ERROR_
     NL_ERROR_TIMEDOUT = ((ULONG)-138),
     /* The given text file is busy */
     NL_ERROR_TXTBSY = ((ULONG)-139),
-    /*the operation would block */
+    /* The operation would block */
     NL_ERROR_WOULDBLOCK = ((ULONG)-140),
+    /* The operation is not finished */
+    NL_ERROR_PENDING = ((ULONG)-141),
 } NL_ERROR;
 
 static __inline
 NlMapStatusToNlErr(NTSTATUS status)
 {
-    NL_ERROR ret = NL_ERROR_INVAL;
+    NL_ERROR ret;
 
     switch (status)
     {
@@ -215,7 +217,20 @@ NlMapStatusToNlErr(NTSTATUS status)
     case STATUS_SUCCESS:
       ret = NL_ERROR_SUCCESS;
       break;
+    case STATUS_PENDING:
+      ret = NL_ERROR_PENDING;
+      break;
+    case STATUS_CANCELLED:
+      ret = NL_ERROR_CANCELED;
+      break;
+    case STATUS_INVALID_PARAMETER:
+      ret = NL_ERROR_INVAL;
+      break;
+    case STATUS_OBJECT_NAME_EXISTS:
+      ret = NL_ERROR_EXIST;
+      break;
     default:
+      ret = NL_ERROR_OTHER;
       break;
     }
 
diff --git a/datapath-windows/ovsext/Switch.c b/datapath-windows/ovsext/Switch.c
index 032153d..416bcc0 100644
--- a/datapath-windows/ovsext/Switch.c
+++ b/datapath-windows/ovsext/Switch.c
@@ -263,8 +263,8 @@ OvsDeleteSwitch(POVS_SWITCH_CONTEXT switchContext)
     if (switchContext)
     {
         dpNo = switchContext->dpNo;
-        OvsUninitTunnelFilter(gOvsExtDriverObject);
         OvsClearAllSwitchVports(switchContext);
+        OvsUninitTunnelFilter(gOvsExtDriverObject);
         OvsUninitSwitchContext(switchContext);
     }
     OVS_LOG_TRACE("Exit: deleted switch %p  dpNo: %d", switchContext, dpNo);
diff --git a/datapath-windows/ovsext/Tunnel.h b/datapath-windows/ovsext/Tunnel.h
index 2978bb3..2c45e35 100644
--- a/datapath-windows/ovsext/Tunnel.h
+++ b/datapath-windows/ovsext/Tunnel.h
@@ -32,12 +32,6 @@ typedef struct OVS_TUNNEL_PENDED_PACKET_
    FWPS_CLASSIFY_OUT *classifyOut;
 } OVS_TUNNEL_PENDED_PACKET;
 
-/* Shared global data. */
-
-extern UINT16 configNewDestPort;
-
-extern UINT32 gCalloutIdV4;
-
 //
 // Shared function prototypes
 //
diff --git a/datapath-windows/ovsext/TunnelFilter.c b/datapath-windows/ovsext/TunnelFilter.c
index c2186eb..0dedca2 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);
-
-    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;
-        }
+                           filterID);
 
-        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,565 @@ 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)
+{
+    if (irp) {
+        UINT32 replyLen = 0;
+
+        if (callback) {
+            callback(context, status, &replyLen);
+            /* Release the context passed to the callback function. */
+            OvsFreeMemory(context);
+        }
+        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);
+}
diff --git a/datapath-windows/ovsext/TunnelIntf.h b/datapath-windows/ovsext/TunnelIntf.h
index 82a5145..eb8c1d5 100644
--- a/datapath-windows/ovsext/TunnelIntf.h
+++ b/datapath-windows/ovsext/TunnelIntf.h
@@ -17,6 +17,10 @@
 #ifndef __TUNNEL_INTF_H_
 #define __TUNNEL_INTF_H_ 1
 
+typedef VOID(*PFNTunnelVportPendingOp)(PVOID context,
+                                       NTSTATUS status,
+                                       UINT32 *replyLen);
+
 /* Tunnel callout driver load/unload functions */
 NTSTATUS OvsInitTunnelFilter(PDRIVER_OBJECT driverObject, PVOID deviceObject);
 
@@ -26,4 +30,15 @@ VOID OvsRegisterSystemProvider(PVOID deviceObject);
 
 VOID OvsUnregisterSystemProvider();
 
+NTSTATUS OvsTunelFilterCreate(PIRP irp,
+                              UINT16 filterPort,
+                              UINT64 *filterID,
+                              PFNTunnelVportPendingOp callback,
+                              PVOID context);
+
+NTSTATUS OvsTunelFilterDelete(PIRP irp,
+                              UINT64 filterID,
+                              PFNTunnelVportPendingOp callback,
+                              PVOID context);
+
 #endif /* __TUNNEL_INTF_H_ */
diff --git a/datapath-windows/ovsext/Vport.c b/datapath-windows/ovsext/Vport.c
index 1423ace..5c8af0c 100644
--- a/datapath-windows/ovsext/Vport.c
+++ b/datapath-windows/ovsext/Vport.c
@@ -47,6 +47,19 @@
 
 #define OVS_VPORT_DEFAULT_WAIT_TIME_MICROSEC    100
 
+/* Context structure used to pass back and forth information to the tunnel
+ * filter threads. */
+typedef struct _OVS_TUNFLT_INIT_CONTEXT {
+    POVS_SWITCH_CONTEXT switchContext;
+    UINT32 outputLength;
+    PVOID outputBuffer;
+    PVOID inputBuffer;
+    POVS_VPORT_ENTRY vport;
+    BOOLEAN hvDelete;
+    BOOLEAN ovsDelete;
+} OVS_TUNFLT_INIT_CONTEXT, *POVS_TUNFLT_INIT_CONTEXT;
+
+
 extern POVS_SWITCH_CONTEXT gOvsSwitchContext;
 
 static VOID OvsInitVportWithPortParam(POVS_VPORT_ENTRY vport,
@@ -69,6 +82,13 @@ static POVS_VPORT_ENTRY OvsFindVportByHvNameW(POVS_SWITCH_CONTEXT switchContext,
 static NDIS_STATUS InitHvVportCommon(POVS_SWITCH_CONTEXT switchContext,
                                      POVS_VPORT_ENTRY vport,
                                      BOOLEAN newPort);
+static VOID OvsTunnelVportPendingInit(PVOID context,
+                                      NTSTATUS status,
+                                      UINT32 *replyLen);
+static VOID OvsTunnelVportPendingUninit(PVOID context,
+                                        NTSTATUS status,
+                                        UINT32 *replyLen);
+
 
 /*
  * Functions implemented in relaton to NDIS port manipulation.
@@ -246,7 +266,7 @@ HvDeletePort(POVS_SWITCH_CONTEXT switchContext,
      * delete will delete the vport.
     */
     if (vport) {
-        OvsRemoveAndDeleteVport(switchContext, vport, TRUE, FALSE, NULL);
+        OvsRemoveAndDeleteVport(NULL, switchContext, vport, TRUE, FALSE);
     } else {
         OVS_LOG_WARN("Vport not present.");
     }
@@ -537,7 +557,7 @@ HvDeleteNic(POVS_SWITCH_CONTEXT switchContext,
     portNo = vport->portNo;
     if (vport->portType == NdisSwitchPortTypeExternal &&
         vport->nicIndex != 0) {
-        OvsRemoveAndDeleteVport(switchContext, vport, TRUE, FALSE, NULL);
+        OvsRemoveAndDeleteVport(NULL, switchContext, vport, TRUE, FALSE);
     }
     vport->nicState = NdisSwitchNicStateUnknown;
     vport->ovsState = OVS_STATE_PORT_CREATED;
@@ -850,11 +870,14 @@ OvsInitPhysNicVport(POVS_VPORT_ENTRY physExtVport,
  * --------------------------------------------------------------------------
  */
 NTSTATUS
-OvsInitTunnelVport(POVS_VPORT_ENTRY vport,
+OvsInitTunnelVport(PVOID userContext,
+                   POVS_VPORT_ENTRY vport,
                    OVS_VPORT_TYPE ovsType,
                    UINT16 dstPort)
 {
     NTSTATUS status = STATUS_SUCCESS;
+    POVS_USER_PARAMS_CONTEXT usrParamsCtx =
+        (POVS_USER_PARAMS_CONTEXT)userContext;
 
     vport->isBridgeInternal = FALSE;
     vport->ovsType = ovsType;
@@ -865,8 +888,26 @@ OvsInitTunnelVport(POVS_VPORT_ENTRY vport,
     case OVS_VPORT_TYPE_GRE64:
         break;
     case OVS_VPORT_TYPE_VXLAN:
-        status = OvsInitVxlanTunnel(vport, dstPort);
+    {
+        POVS_TUNFLT_INIT_CONTEXT tunnelContext = NULL;
+
+        tunnelContext = OvsAllocateMemory(sizeof(*tunnelContext));
+        if (tunnelContext == NULL) {
+            status = STATUS_INSUFFICIENT_RESOURCES;
+            break;
+        }
+        tunnelContext->inputBuffer = usrParamsCtx->inputBuffer;
+        tunnelContext->outputBuffer = usrParamsCtx->outputBuffer;
+        tunnelContext->outputLength = usrParamsCtx->outputLength;
+        tunnelContext->vport = vport;
+
+        status = OvsInitVxlanTunnel(usrParamsCtx->irp,
+                                    vport,
+                                    dstPort,
+                                    OvsTunnelVportPendingInit,
+                                    (PVOID)tunnelContext);
         break;
+    }
     default:
         ASSERT(0);
     }
@@ -1012,7 +1053,6 @@ InitOvsVportCommon(POVS_SWITCH_CONTEXT switchContext,
 
     switch(vport->ovsType) {
     case OVS_VPORT_TYPE_VXLAN:
-        ASSERT(switchContext->vxlanVport == NULL);
         switchContext->vxlanVport = vport;
         switchContext->numNonHvVports++;
         break;
@@ -1055,30 +1095,26 @@ InitOvsVportCommon(POVS_SWITCH_CONTEXT switchContext,
  * port being removed from OVS userspace.
  * --------------------------------------------------------------------------
  */
-VOID
-OvsRemoveAndDeleteVport(POVS_SWITCH_CONTEXT switchContext,
+NTSTATUS
+OvsRemoveAndDeleteVport(PVOID usrParamsContext,
+                        POVS_SWITCH_CONTEXT switchContext,
                         POVS_VPORT_ENTRY vport,
                         BOOLEAN hvDelete,
-                        BOOLEAN ovsDelete,
-                        BOOLEAN *vportDeallocated)
+                        BOOLEAN ovsDelete)
 {
+    NTSTATUS status = STATUS_SUCCESS;
+    POVS_USER_PARAMS_CONTEXT usrParamsCtx =
+        (POVS_USER_PARAMS_CONTEXT)usrParamsContext;
     BOOLEAN hvSwitchPort = FALSE;
     BOOLEAN deletedOnOvs = FALSE, deletedOnHv = FALSE;
 
-    if (vportDeallocated) {
-        *vportDeallocated = FALSE;
-    }
-
     if (vport->isExternal) {
         if (vport->nicIndex == 0) {
             ASSERT(switchContext->numPhysicalNics == 0);
             switchContext->virtualExternalPortId = 0;
             switchContext->virtualExternalVport = NULL;
             OvsFreeMemoryWithTag(vport, OVS_VPORT_POOL_TAG);
-            if (vportDeallocated) {
-                *vportDeallocated = TRUE;
-            }
-            return;
+            return STATUS_SUCCESS;
         } else {
             ASSERT(switchContext->numPhysicalNics);
             switchContext->numPhysicalNics--;
@@ -1096,9 +1132,35 @@ OvsRemoveAndDeleteVport(POVS_SWITCH_CONTEXT switchContext,
         }
         break;
     case OVS_VPORT_TYPE_VXLAN:
-        OvsCleanupVxlanTunnel(vport);
+    {
+        POVS_TUNFLT_INIT_CONTEXT tunnelContext = NULL;
+        PIRP irp = NULL;
+
+        if (usrParamsCtx) {
+            tunnelContext = OvsAllocateMemory(sizeof(*tunnelContext));
+            if (tunnelContext == NULL) {
+                status = STATUS_INSUFFICIENT_RESOURCES;
+                break;
+            }
+            tunnelContext->switchContext = switchContext;
+            tunnelContext->inputBuffer = usrParamsCtx->inputBuffer;
+            tunnelContext->outputBuffer = usrParamsCtx->outputBuffer;
+            tunnelContext->outputLength = usrParamsCtx->outputLength;
+            tunnelContext->hvDelete = hvDelete;
+            tunnelContext->ovsDelete = ovsDelete;
+            tunnelContext->vport = vport;
+
+            irp = usrParamsCtx->irp;
+        }
+
+        status = OvsCleanupVxlanTunnel(irp,
+                                       vport,
+                                       OvsTunnelVportPendingUninit,
+                                       tunnelContext);
+
         switchContext->vxlanVport = NULL;
         break;
+    }
     case OVS_VPORT_TYPE_GRE:
     case OVS_VPORT_TYPE_GRE64:
         break;
@@ -1108,55 +1170,56 @@ OvsRemoveAndDeleteVport(POVS_SWITCH_CONTEXT switchContext,
         break;
     }
 
-    /*
-     * 'hvDelete' == TRUE indicates that the port should be removed from the
-     * 'portIdHashArray', while 'ovsDelete' == TRUE indicates that the port
-     * should be removed from 'portNoHashArray' and the 'ovsPortNameHashArray'.
-     *
-     * Both 'hvDelete' and 'ovsDelete' can be set to TRUE by the caller.
-     */
-    if (vport->isPresentOnHv == TRUE) {
-        deletedOnHv = TRUE;
-    }
-    if (vport->portNo == OVS_DPPORT_NUMBER_INVALID) {
-        deletedOnOvs = TRUE;
-    }
-
-    if (hvDelete && !deletedOnHv) {
-        vport->isPresentOnHv = TRUE;
-
-        /* Remove the port from the relevant lists. */
-        RemoveEntryList(&vport->portIdLink);
-        InitializeListHead(&vport->portIdLink);
-        deletedOnHv = TRUE;
-    }
-    if (ovsDelete && !deletedOnOvs) {
-        vport->portNo = OVS_DPPORT_NUMBER_INVALID;
-        vport->ovsName[0] = '\0';
+    if (NT_SUCCESS(status)) {
+        /*
+         * 'hvDelete' == TRUE indicates that the port should be removed from the
+         * 'portIdHashArray', while 'ovsDelete' == TRUE indicates that the port
+         * should be removed from 'portNoHashArray' and the 'ovsPortNameHashArray'.
+         *
+         * Both 'hvDelete' and 'ovsDelete' can be set to TRUE by the caller.
+         */
+        if (vport->isPresentOnHv == TRUE) {
+            deletedOnHv = TRUE;
+        }
+        if (vport->portNo == OVS_DPPORT_NUMBER_INVALID) {
+            deletedOnOvs = TRUE;
+        }
 
-        /* Remove the port from the relevant lists. */
-        RemoveEntryList(&vport->ovsNameLink);
-        InitializeListHead(&vport->ovsNameLink);
-        RemoveEntryList(&vport->portNoLink);
-        InitializeListHead(&vport->portNoLink);
-        deletedOnOvs = TRUE;
-    }
+        if (hvDelete && !deletedOnHv) {
+            vport->isPresentOnHv = TRUE;
 
-    /*
-     * Deallocate the port if it has been deleted on the Hyper-V switch as well
-     * as OVS userspace.
-     */
-    if (deletedOnHv && deletedOnOvs) {
-        if (hvSwitchPort) {
-            switchContext->numHvVports--;
-        } else {
-            switchContext->numNonHvVports--;
+            /* Remove the port from the relevant lists. */
+            RemoveEntryList(&vport->portIdLink);
+            InitializeListHead(&vport->portIdLink);
+            deletedOnHv = TRUE;
         }
-        OvsFreeMemoryWithTag(vport, OVS_VPORT_POOL_TAG);
-        if (vportDeallocated) {
-            *vportDeallocated = TRUE;
+        if (ovsDelete && !deletedOnOvs) {
+            vport->portNo = OVS_DPPORT_NUMBER_INVALID;
+            vport->ovsName[0] = '\0';
+
+            /* Remove the port from the relevant lists. */
+            RemoveEntryList(&vport->ovsNameLink);
+            InitializeListHead(&vport->ovsNameLink);
+            RemoveEntryList(&vport->portNoLink);
+            InitializeListHead(&vport->portNoLink);
+            deletedOnOvs = TRUE;
+        }
+
+        /*
+         * Deallocate the port if it has been deleted on the Hyper-V switch as well
+         * as OVS userspace.
+         */
+        if (deletedOnHv && deletedOnOvs) {
+            if (hvSwitchPort) {
+                switchContext->numHvVports--;
+            } else {
+                switchContext->numNonHvVports--;
+            }
+            OvsFreeMemoryWithTag(vport, OVS_VPORT_POOL_TAG);
         }
     }
+
+    return status;
 }
 
 NDIS_STATUS
@@ -1294,7 +1357,7 @@ OvsClearAllSwitchVports(POVS_SWITCH_CONTEXT switchContext)
         LIST_FORALL_SAFE(head, link, next) {
             POVS_VPORT_ENTRY vport;
             vport = CONTAINING_RECORD(link, OVS_VPORT_ENTRY, portIdLink);
-            OvsRemoveAndDeleteVport(switchContext, vport, TRUE, TRUE, NULL);
+            OvsRemoveAndDeleteVport(NULL, switchContext, vport, TRUE, TRUE);
         }
     }
     /*
@@ -1302,9 +1365,8 @@ OvsClearAllSwitchVports(POVS_SWITCH_CONTEXT switchContext)
      * 'portIdHashArray'.
      */
     if (switchContext->virtualExternalVport) {
-        OvsRemoveAndDeleteVport(switchContext,
-            (POVS_VPORT_ENTRY)switchContext->virtualExternalVport, TRUE, TRUE,
-            NULL);
+        OvsRemoveAndDeleteVport(NULL, switchContext,
+            (POVS_VPORT_ENTRY)switchContext->virtualExternalVport, TRUE, TRUE);
     }
 
     for (UINT hash = 0; hash < OVS_MAX_VPORT_ARRAY_SIZE; hash++) {
@@ -1317,7 +1379,7 @@ OvsClearAllSwitchVports(POVS_SWITCH_CONTEXT switchContext)
             ASSERT(OvsIsTunnelVportType(vport->ovsType) ||
                    (vport->ovsType == OVS_VPORT_TYPE_INTERNAL &&
                     vport->isBridgeInternal) || vport->isPresentOnHv == TRUE);
-            OvsRemoveAndDeleteVport(switchContext, vport, TRUE, TRUE, NULL);
+            OvsRemoveAndDeleteVport(NULL, switchContext, vport, TRUE, TRUE);
         }
     }
 
@@ -1895,7 +1957,7 @@ Cleanup:
 
 /*
  * --------------------------------------------------------------------------
- *  Command Handler for 'OVS_VPORT_CMD_NEW'.
+ *  Command Handler for 'OVS_VPORT_CMD_GET'.
  *
  *  The function handles the initial call to setup the dump state, as well as
  *  subsequent calls to continue dumping data.
@@ -2020,8 +2082,6 @@ OvsNewVportCmdHandler(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
     } else {
         ASSERT(OvsIsTunnelVportType(portType) ||
                (portType == OVS_VPORT_TYPE_INTERNAL && isBridgeInternal));
-        ASSERT(OvsGetTunnelVport(gOvsSwitchContext, portType) == NULL ||
-               !OvsIsTunnelVportType(portType));
 
         vport = (POVS_VPORT_ENTRY)OvsAllocateVport();
         if (vport == NULL) {
@@ -2031,11 +2091,23 @@ OvsNewVportCmdHandler(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
         vportAllocated = TRUE;
 
         if (OvsIsTunnelVportType(portType)) {
-            status = OvsInitTunnelVport(vport, portType, VXLAN_UDP_PORT);
+            UINT16 udpPortDest = VXLAN_UDP_PORT;
+            PNL_ATTR attr = NlAttrFindNested(vportAttrs[OVS_VPORT_ATTR_OPTIONS],
+                                             OVS_TUNNEL_ATTR_DST_PORT);
+            if (attr) {
+                udpPortDest = NlAttrGetU16(attr);
+            }
+
+            status = OvsInitTunnelVport(usrParamsCtx,
+                                        vport,
+                                        portType,
+                                        udpPortDest);
+
             nlError = NlMapStatusToNlErr(status);
         } else {
             OvsInitBridgeInternalVport(vport);
         }
+
         vportInitialized = TRUE;
 
         if (nlError == NL_ERROR_SUCCESS) {
@@ -2047,6 +2119,8 @@ OvsNewVportCmdHandler(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
              * corresponding hyper-v switch part.
              */
             vport->isPresentOnHv = TRUE;
+        } else {
+            goto Cleanup;
         }
     }
 
@@ -2106,14 +2180,14 @@ OvsNewVportCmdHandler(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
 Cleanup:
     NdisReleaseRWLock(gOvsSwitchContext->dispatchLock, &lockState);
 
-    if (nlError != NL_ERROR_SUCCESS) {
+    if ((nlError != NL_ERROR_SUCCESS) && (nlError != NL_ERROR_PENDING)) {
         POVS_MESSAGE_ERROR msgError = (POVS_MESSAGE_ERROR)
             usrParamsCtx->outputBuffer;
 
         if (vport && vportAllocated == TRUE) {
             if (vportInitialized == TRUE) {
                 if (OvsIsTunnelVportType(portType)) {
-                    OvsCleanupVxlanTunnel(vport);
+                    OvsCleanupVxlanTunnel(NULL, vport, NULL, NULL);
                 }
             }
             OvsFreeMemoryWithTag(vport, OVS_VPORT_POOL_TAG);
@@ -2123,7 +2197,7 @@ Cleanup:
         *replyLen = msgError->nlMsg.nlmsgLen;
     }
 
-    return STATUS_SUCCESS;
+    return (status == STATUS_PENDING) ? STATUS_PENDING : STATUS_SUCCESS;
 }
 
 
@@ -2293,22 +2367,30 @@ OvsDeleteVportCmdHandler(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
         goto Cleanup;
     }
 
-    status = OvsCreateMsgFromVport(vport, msgIn, usrParamsCtx->outputBuffer,
-                                   usrParamsCtx->outputLength,
-                                   gOvsSwitchContext->dpNo);
-
     /*
      * Mark the port as deleted from OVS userspace. If the port does not exist
      * on the Hyper-V switch, it gets deallocated. Otherwise, it stays.
      */
-    OvsRemoveAndDeleteVport(gOvsSwitchContext, vport, FALSE, TRUE, NULL);
+    status = OvsRemoveAndDeleteVport(usrParamsCtx,
+                                     gOvsSwitchContext,
+                                     vport,
+                                     FALSE,
+                                     TRUE);
+    if (STATUS_PENDING == status) {
+        nlError = NL_ERROR_PENDING;
+        goto Cleanup;
+    }
+
+    status = OvsCreateMsgFromVport(vport, msgIn, usrParamsCtx->outputBuffer,
+                                   usrParamsCtx->outputLength,
+                                   gOvsSwitchContext->dpNo);
 
     *replyLen = msgOut->nlMsg.nlmsgLen;
 
 Cleanup:
     NdisReleaseRWLock(gOvsSwitchContext->dispatchLock, &lockState);
 
-    if (nlError != NL_ERROR_SUCCESS) {
+    if ((nlError != NL_ERROR_SUCCESS) && (nlError != NL_ERROR_PENDING)) {
         POVS_MESSAGE_ERROR msgError = (POVS_MESSAGE_ERROR)
             usrParamsCtx->outputBuffer;
 
@@ -2316,5 +2398,224 @@ Cleanup:
         *replyLen = msgError->nlMsg.nlmsgLen;
     }
 
-    return STATUS_SUCCESS;
+    return (status == STATUS_PENDING) ? STATUS_PENDING : STATUS_SUCCESS;
+}
+
+static VOID
+OvsTunnelVportPendingUninit(PVOID context,
+                            NTSTATUS status,
+                            UINT32 *replyLen)
+{
+    POVS_TUNFLT_INIT_CONTEXT tunnelContext =
+        (POVS_TUNFLT_INIT_CONTEXT) context;
+    POVS_SWITCH_CONTEXT switchContext = tunnelContext->switchContext;
+    POVS_VPORT_ENTRY vport = tunnelContext->vport;
+    POVS_MESSAGE msgIn = (POVS_MESSAGE)tunnelContext->inputBuffer;
+    POVS_MESSAGE msgOut = (POVS_MESSAGE)tunnelContext->outputBuffer;
+    NL_ERROR nlError = NL_ERROR_SUCCESS;
+    BOOLEAN deletedOnHv = FALSE;
+    BOOLEAN deletedOnOvs = FALSE;
+    BOOLEAN hvSwitchPort = FALSE;
+    BOOLEAN error = TRUE;
+
+    do {
+        if (vport->isExternal && vport->nicIndex) {
+            hvSwitchPort = TRUE;
+        }
+
+        /*
+         * 'hvDelete' == TRUE indicates that the port should be removed from the
+         * 'portIdHashArray', while 'ovsDelete' == TRUE indicates that the port
+         * should be removed from 'portNoHashArray' and the 'ovsPortNameHashArray'.
+         *
+         * Both 'hvDelete' and 'ovsDelete' can be set to TRUE by the caller.
+         */
+        if (vport->isPresentOnHv == TRUE) {
+            deletedOnHv = TRUE;
+        }
+        if (vport->portNo == OVS_DPPORT_NUMBER_INVALID) {
+            deletedOnOvs = TRUE;
+        }
+
+        if (tunnelContext->hvDelete && !deletedOnHv) {
+            vport->isPresentOnHv = TRUE;
+
+            /* Remove the port from the relevant lists. */
+            RemoveEntryList(&vport->portIdLink);
+            InitializeListHead(&vport->portIdLink);
+            deletedOnHv = TRUE;
+        }
+        if (tunnelContext->ovsDelete && !deletedOnOvs) {
+            vport->portNo = OVS_DPPORT_NUMBER_INVALID;
+            vport->ovsName[0] = '\0';
+
+            /* Remove the port from the relevant lists. */
+            RemoveEntryList(&vport->ovsNameLink);
+            InitializeListHead(&vport->ovsNameLink);
+            RemoveEntryList(&vport->portNoLink);
+            InitializeListHead(&vport->portNoLink);
+            deletedOnOvs = TRUE;
+        }
+
+        /*
+         * Deallocate the port if it has been deleted on the Hyper-V switch as well
+         * as OVS userspace.
+         */
+        if (deletedOnHv && deletedOnOvs) {
+            if (hvSwitchPort) {
+                switchContext->numHvVports--;
+            }
+            else {
+                switchContext->numNonHvVports--;
+            }
+            OvsFreeMemoryWithTag(vport, OVS_VPORT_POOL_TAG);
+        }
+
+        if (!NT_SUCCESS(status)) {
+            nlError = NlMapStatusToNlErr(status);
+            break;
+        }
+
+        OvsCreateMsgFromVport(vport,
+                              msgIn,
+                              msgOut,
+                              tunnelContext->outputLength,
+                              switchContext->dpNo);
+
+        *replyLen = msgOut->nlMsg.nlmsgLen;
+
+        error = FALSE;
+    } while (error);
+
+    if (error) {
+        POVS_MESSAGE_ERROR msgError = (POVS_MESSAGE_ERROR) msgOut;
+
+        NlBuildErrorMsg(msgIn, msgError, nlError);
+        *replyLen = msgError->nlMsg.nlmsgLen;
+    }
+}
+
+static VOID
+OvsTunnelVportPendingInit(PVOID context,
+                          NTSTATUS status,
+                          UINT32 *replyLen)
+{
+    POVS_TUNFLT_INIT_CONTEXT tunnelContext =
+        (POVS_TUNFLT_INIT_CONTEXT) context;
+    POVS_VPORT_ENTRY vport = tunnelContext->vport;
+    POVS_MESSAGE msgIn = (POVS_MESSAGE)tunnelContext->inputBuffer;
+    POVS_MESSAGE msgOut = (POVS_MESSAGE)tunnelContext->outputBuffer;
+    PCHAR portName;
+    ULONG portNameLen = 0;
+    UINT32 portType = 0;
+    NL_ERROR nlError = NL_ERROR_SUCCESS;
+    BOOLEAN error = TRUE;
+
+    do {
+        if (!NT_SUCCESS(status)) {
+            nlError = NlMapStatusToNlErr(status);
+            break;
+        }
+
+        static const NL_POLICY ovsVportPolicy[] = {
+            [OVS_VPORT_ATTR_PORT_NO] = { .type = NL_A_U32, .optional = TRUE },
+            [OVS_VPORT_ATTR_TYPE] = { .type = NL_A_U32, .optional = FALSE },
+            [OVS_VPORT_ATTR_NAME] = { .type = NL_A_STRING, .maxLen = IFNAMSIZ,
+            .optional = FALSE },
+            [OVS_VPORT_ATTR_UPCALL_PID] = { .type = NL_A_UNSPEC,
+            .optional = FALSE },
+            [OVS_VPORT_ATTR_OPTIONS] = { .type = NL_A_NESTED, .optional = TRUE },
+        };
+
+        PNL_ATTR vportAttrs[ARRAY_SIZE(ovsVportPolicy)];
+
+        /* input buffer has been validated while validating write dev op. */
+        ASSERT(msgIn != NULL);
+
+        /* Output buffer has been validated while validating transact dev op. */
+        ASSERT(msgOut != NULL && tunnelContext->outputLength >= sizeof *msgOut);
+
+        if (!NlAttrParse((PNL_MSG_HDR)msgIn,
+            NLMSG_HDRLEN + GENL_HDRLEN + OVS_HDRLEN,
+            NlMsgAttrsLen((PNL_MSG_HDR)msgIn),
+            ovsVportPolicy, vportAttrs, ARRAY_SIZE(vportAttrs))) {
+            nlError = NL_ERROR_INVAL;
+            break;
+        }
+
+        portName = NlAttrGet(vportAttrs[OVS_VPORT_ATTR_NAME]);
+        portNameLen = NlAttrGetSize(vportAttrs[OVS_VPORT_ATTR_NAME]);
+        portType = NlAttrGetU32(vportAttrs[OVS_VPORT_ATTR_TYPE]);
+
+        if (vport->portNo != OVS_DPPORT_NUMBER_INVALID) {
+            nlError = NL_ERROR_EXIST;
+            break;
+        }
+
+        vport->ovsState = OVS_STATE_CONNECTED;
+        vport->nicState = NdisSwitchNicStateConnected;
+
+        /*
+         * Allow the vport to be deleted, because there is no
+         * corresponding hyper-v switch part.
+         */
+        vport->isPresentOnHv = TRUE;
+
+        if (vportAttrs[OVS_VPORT_ATTR_PORT_NO] != NULL) {
+            /*
+             * XXX: when we implement the limit for OVS port number to be
+             * MAXUINT16, we'll need to check the port number received from the
+             * userspace.
+             */
+            vport->portNo =
+                NlAttrGetU32(vportAttrs[OVS_VPORT_ATTR_PORT_NO]);
+        } else {
+            vport->portNo =
+                OvsComputeVportNo(gOvsSwitchContext);
+            if (vport->portNo == OVS_DPPORT_NUMBER_INVALID) {
+                nlError = NL_ERROR_NOMEM;
+                break;
+            }
+        }
+
+        /* The ovs port name must be uninitialized. */
+        ASSERT(vport->ovsName[0] == '\0');
+        ASSERT(portNameLen <= OVS_MAX_PORT_NAME_LENGTH);
+
+        RtlCopyMemory(vport->ovsName, portName, portNameLen);
+        /* if we don't have options, then vport->portOptions will be NULL */
+        vport->portOptions = vportAttrs[OVS_VPORT_ATTR_OPTIONS];
+
+        /*
+         * XXX: when we implement OVS_DP_ATTR_USER_FEATURES in datapath,
+         * we'll need to check the OVS_DP_F_VPORT_PIDS flag: if it is set,
+         * it means we have an array of pids, instead of a single pid.
+         * ATM we assume we have one pid only.
+         */
+        vport->upcallPid =
+            NlAttrGetU32(vportAttrs[OVS_VPORT_ATTR_UPCALL_PID]);
+
+        status = InitOvsVportCommon(gOvsSwitchContext, vport);
+        ASSERT(status == STATUS_SUCCESS);
+
+        OvsCreateMsgFromVport(vport,
+                              msgIn,
+                              msgOut,
+                              tunnelContext->outputLength,
+                              gOvsSwitchContext->dpNo);
+
+        *replyLen = msgOut->nlMsg.nlmsgLen;
+
+        error = FALSE;
+    } while (error);
+
+    if (error) {
+        POVS_MESSAGE_ERROR msgError = (POVS_MESSAGE_ERROR) msgOut;
+
+        OvsCleanupVxlanTunnel(NULL, vport, NULL, NULL);
+        OvsFreeMemory(vport);
+
+        NlBuildErrorMsg(msgIn, msgError, nlError);
+        *replyLen = msgError->nlMsg.nlmsgLen;
+    }
 }
diff --git a/datapath-windows/ovsext/Vport.h b/datapath-windows/ovsext/Vport.h
index 348fbfd..4cfda30 100644
--- a/datapath-windows/ovsext/Vport.h
+++ b/datapath-windows/ovsext/Vport.h
@@ -207,15 +207,15 @@ OvsIsBridgeInternalVport(POVS_VPORT_ENTRY vport)
     return vport->isBridgeInternal == TRUE;
 }
 
-VOID OvsRemoveAndDeleteVport(POVS_SWITCH_CONTEXT switchContext,
-                             POVS_VPORT_ENTRY vport,
-                             BOOLEAN hvDelete, BOOLEAN ovsDelete,
-                             BOOLEAN *vportDeallocated);
+NTSTATUS OvsRemoveAndDeleteVport(PVOID usrParamsCtx, 
+                                 POVS_SWITCH_CONTEXT switchContext,
+                                 POVS_VPORT_ENTRY vport,
+                                 BOOLEAN hvDelete, BOOLEAN ovsDelete);
 
 NDIS_STATUS InitOvsVportCommon(POVS_SWITCH_CONTEXT switchContext,
                                POVS_VPORT_ENTRY vport);
-NTSTATUS OvsInitTunnelVport(POVS_VPORT_ENTRY vport, OVS_VPORT_TYPE ovsType,
-                            UINT16 dstport);
+NTSTATUS OvsInitTunnelVport(PVOID usrParamsCtx, POVS_VPORT_ENTRY vport,
+                            OVS_VPORT_TYPE ovsType, UINT16 dstport);
 NTSTATUS OvsInitBridgeInternalVport(POVS_VPORT_ENTRY vport);
 
 POVS_VPORT_ENTRY OvsAllocateVport(VOID);
diff --git a/datapath-windows/ovsext/Vxlan.c b/datapath-windows/ovsext/Vxlan.c
index 8c57185..bf16e35 100644
--- a/datapath-windows/ovsext/Vxlan.c
+++ b/datapath-windows/ovsext/Vxlan.c
@@ -50,14 +50,57 @@
 extern POVS_SWITCH_CONTEXT gOvsSwitchContext;
 
 /*
+ *----------------------------------------------------------------------------
+ * This function verifies if the VXLAN tunnel already exists, in order to
+ * avoid sending a duplicate request to the WFP base filtering engine.
+ *----------------------------------------------------------------------------
+ */
+static BOOLEAN
+OvsIsTunnelFilterCreated(POVS_SWITCH_CONTEXT switchContext,
+                         UINT16 udpPortDest)
+{
+    for (UINT hash = 0; hash < OVS_MAX_VPORT_ARRAY_SIZE; hash++) {
+        PLIST_ENTRY head, link, next;
+
+        head = &(switchContext->portNoHashArray[hash & OVS_VPORT_MASK]);
+        LIST_FORALL_SAFE(head, link, next) {
+            POVS_VPORT_ENTRY vport = NULL;
+            POVS_VXLAN_VPORT vxlanPort = NULL;
+            vport = CONTAINING_RECORD(link, OVS_VPORT_ENTRY, portNoLink);
+            vxlanPort = (POVS_VXLAN_VPORT)vport->priv;
+            if (vxlanPort) {
+                if ((udpPortDest == vxlanPort->dstPort)) {
+                    /* The VXLAN tunnel was already created. */
+                    return TRUE;
+                }
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+/*
+ *----------------------------------------------------------------------------
+ * This function allocates and initializes the OVS_VXLAN_VPORT. The function
+ * also creates a WFP tunnel filter for the necessary destination port. The
+ * tunnel filter create request is passed to the tunnel filter threads that
+ * will complete the request at a later time when IRQL is lowered to
+ * PASSIVE_LEVEL.
+ * 
  * udpDestPort: the vxlan is set as payload to a udp frame. If the destination
  * port of an udp frame is udpDestPort, we understand it to be vxlan.
+ *----------------------------------------------------------------------------
  */
 NTSTATUS
-OvsInitVxlanTunnel(POVS_VPORT_ENTRY vport,
-                   UINT16 udpDestPort)
+OvsInitVxlanTunnel(PIRP irp,
+                   POVS_VPORT_ENTRY vport,
+                   UINT16 udpDestPort,
+                   PFNTunnelVportPendingOp callback,
+                   PVOID tunnelContext)
 {
-    POVS_VXLAN_VPORT vxlanPort;
+    NTSTATUS status = STATUS_SUCCESS;
+    POVS_VXLAN_VPORT vxlanPort = NULL;
 
     vxlanPort = OvsAllocateMemoryWithTag(sizeof (*vxlanPort),
                                          OVS_VXLAN_POOL_TAG);
@@ -67,28 +110,56 @@ OvsInitVxlanTunnel(POVS_VPORT_ENTRY vport,
 
     RtlZeroMemory(vxlanPort, sizeof(*vxlanPort));
     vxlanPort->dstPort = udpDestPort;
-    /*
-     * since we are installing the WFP filter before the port is created
-     * We need to check if it is the same number
-     * XXX should be removed later
-     */
-    ASSERT(vxlanPort->dstPort == VXLAN_UDP_PORT);
     vport->priv = (PVOID)vxlanPort;
 
-    return STATUS_SUCCESS;
-}
+    if (!OvsIsTunnelFilterCreated(gOvsSwitchContext, udpDestPort)) {
+        status = OvsTunelFilterCreate(irp,
+                                      udpDestPort,
+                                      &vxlanPort->filterID,
+                                      callback,
+                                      tunnelContext);
+    } else {
+        status = STATUS_OBJECT_NAME_EXISTS;
+    }
 
+    return status;
+}
 
-VOID
-OvsCleanupVxlanTunnel(POVS_VPORT_ENTRY vport)
+/*
+ *----------------------------------------------------------------------------
+ * This function releases the OVS_VXLAN_VPORT. The function also deletes the
+ * WFP tunnel filter previously created. The tunnel filter delete request is
+ * passed to the tunnel filter threads that will complete the request at a
+ * later time when IRQL is lowered to PASSIVE_LEVEL.
+ *----------------------------------------------------------------------------
+ */
+NTSTATUS
+OvsCleanupVxlanTunnel(PIRP irp,
+                      POVS_VPORT_ENTRY vport,
+                      PFNTunnelVportPendingOp callback,
+                      PVOID tunnelContext)
 {
+    NTSTATUS status = STATUS_SUCCESS;
+    POVS_VXLAN_VPORT vxlanPort = NULL;
+
     if (vport->ovsType != OVS_VPORT_TYPE_VXLAN ||
         vport->priv == NULL) {
-        return;
+        return STATUS_SUCCESS;
+    }
+
+    vxlanPort = (POVS_VXLAN_VPORT)vport->priv;
+
+    if (vxlanPort->filterID != 0) {
+        status = OvsTunelFilterDelete(irp,
+                                      vxlanPort->filterID,
+                                      callback,
+                                      tunnelContext);
     }
 
     OvsFreeMemoryWithTag(vport->priv, OVS_VXLAN_POOL_TAG);
     vport->priv = NULL;
+
+    return status;
 }
 
 
@@ -475,9 +546,6 @@ OvsSlowPathDecapVxlan(const PNET_BUFFER_LIST packet,
             break;
         }
 
-        /* XXX Should be tested against the dynamic port # in the VXLAN vport */
-        ASSERT(udp->dest == RtlUshortByteSwap(VXLAN_UDP_PORT));
-
         VxlanHeader = (VXLANHdr *)OvsGetPacketBytes(packet,
                                                     sizeof(*VxlanHeader),
                                                     layers.l7Offset,
diff --git a/datapath-windows/ovsext/Vxlan.h b/datapath-windows/ovsext/Vxlan.h
index d84796d..248a5dc 100644
--- a/datapath-windows/ovsext/Vxlan.h
+++ b/datapath-windows/ovsext/Vxlan.h
@@ -24,6 +24,7 @@ typedef struct _OVS_VXLAN_VPORT {
     UINT64 outPkts;
     UINT64 slowInPkts;
     UINT64 slowOutPkts;
+    UINT64 filterID;
     /*
      * To be filled
      */
@@ -47,10 +48,16 @@ typedef struct VXLANHdr {
     UINT32   reserved2:8;
 } VXLANHdr;
 
-NTSTATUS OvsInitVxlanTunnel(POVS_VPORT_ENTRY vport,
-                            UINT16 udpDestPort);
+NTSTATUS OvsInitVxlanTunnel(PIRP irp,
+                            POVS_VPORT_ENTRY vport,
+                            UINT16 udpDestPort,
+                            PFNTunnelVportPendingOp callback,
+                            PVOID tunnelContext);
 
-VOID OvsCleanupVxlanTunnel(POVS_VPORT_ENTRY vport);
+NTSTATUS OvsCleanupVxlanTunnel(PIRP irp,
+                               POVS_VPORT_ENTRY vport,
+                               PFNTunnelVportPendingOp callback,
+                               PVOID tunnelContext);
 
 NDIS_STATUS OvsSlowPathDecapVxlan(const PNET_BUFFER_LIST packet,
                                   OvsIPv4TunnelKey *tunnelKey);
-- 
1.9.0.msysgit.0



More information about the dev mailing list