datapath-windows: Refactor sofware offloads and mss
[cascardo/ovs.git] / datapath-windows / ovsext / Actions.c
index 408b9be..5a04541 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014 VMware, Inc.
+ * Copyright (c) 2014, 2016 VMware, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 #include "precomp.h"
 
-#include "Switch.h"
-#include "Vport.h"
+#include "Debug.h"
 #include "Event.h"
-#include "User.h"
-#include "NetProto.h"
 #include "Flow.h"
-#include "Vxlan.h"
-#include "Checksum.h"
+#include "Gre.h"
+#include "Mpls.h"
+#include "NetProto.h"
+#include "Offload.h"
 #include "PacketIO.h"
+#include "Stt.h"
+#include "Switch.h"
+#include "User.h"
+#include "Vport.h"
+#include "Vxlan.h"
 
 #ifdef OVS_DBG_MOD
 #undef OVS_DBG_MOD
 #endif
 #define OVS_DBG_MOD OVS_DBG_ACTION
-#include "Debug.h"
 
 typedef struct _OVS_ACTION_STATS {
+    UINT64 rxGre;
+    UINT64 txGre;
     UINT64 rxVxlan;
     UINT64 txVxlan;
+    UINT64 rxStt;
+    UINT64 txStt;
     UINT64 flowMiss;
     UINT64 flowUserspace;
     UINT64 txTcp;
@@ -202,11 +209,33 @@ OvsDetectTunnelRxPkt(OvsForwardingContext *ovsFwdCtx,
     /* XXX: we should also check for the length of the UDP payload to pick
      * packets only if they are at least VXLAN header size.
      */
-    if (!flowKey->ipKey.nwFrag &&
-        flowKey->ipKey.nwProto == IPPROTO_UDP &&
-        flowKey->ipKey.l4.tpDst == VXLAN_UDP_PORT_NBO) {
-        tunnelVport = ovsFwdCtx->switchContext->vxlanVport;
-        ovsActionStats.rxVxlan++;
+    if (!flowKey->ipKey.nwFrag) {
+        UINT16 dstPort = htons(flowKey->ipKey.l4.tpDst);
+        switch (flowKey->ipKey.nwProto) {
+        case IPPROTO_GRE:
+            tunnelVport = OvsFindTunnelVportByPortType(ovsFwdCtx->switchContext,
+                                                       OVS_VPORT_TYPE_GRE);
+            if (tunnelVport) {
+                ovsActionStats.rxGre++;
+            }
+            break;
+        case IPPROTO_TCP:
+            tunnelVport = OvsFindTunnelVportByDstPort(ovsFwdCtx->switchContext,
+                                                      dstPort,
+                                                      OVS_VPORT_TYPE_STT);
+            if (tunnelVport) {
+                ovsActionStats.rxStt++;
+            }
+            break;
+        case IPPROTO_UDP:
+            tunnelVport = OvsFindTunnelVportByDstPort(ovsFwdCtx->switchContext,
+                                                      dstPort,
+                                                      OVS_VPORT_TYPE_VXLAN);
+            if (tunnelVport) {
+                ovsActionStats.rxVxlan++;
+            }
+            break;
+        }
     }
 
     // We might get tunnel packets even before the tunnel gets initialized.
@@ -248,10 +277,11 @@ OvsDetectTunnelPkt(OvsForwardingContext *ovsFwdCtx,
          * port or if it is being executed from userspace, the source port is
          * default port.
          */
-        BOOLEAN validSrcPort = (ovsFwdCtx->fwdDetail->SourcePortId ==
-                                ovsFwdCtx->switchContext->externalPortId) ||
-                               (ovsFwdCtx->fwdDetail->SourcePortId ==
-                                NDIS_SWITCH_DEFAULT_PORT_ID);
+        BOOLEAN validSrcPort =
+            (ovsFwdCtx->fwdDetail->SourcePortId ==
+                 ovsFwdCtx->switchContext->virtualExternalPortId) ||
+            (ovsFwdCtx->fwdDetail->SourcePortId ==
+                 NDIS_SWITCH_DEFAULT_PORT_ID);
 
         if (validSrcPort && OvsDetectTunnelRxPkt(ovsFwdCtx, flowKey)) {
             ASSERT(ovsFwdCtx->tunnelTxNic == NULL);
@@ -265,25 +295,39 @@ OvsDetectTunnelPkt(OvsForwardingContext *ovsFwdCtx,
         /*
          * Tx:
          * The destination port is a tunnel port. Encapsulation must be
-         * performed only on packets that originate from a VIF port or from
-         * userspace (default port)
+         * performed only on packets that originate from:
+         * - a VIF port
+         * - a bridge-internal port (packets generated from userspace)
+         * - no port.
          *
          * If the packet will not be encapsulated, consume the tunnel context
          * by clearing it.
          */
-        if (ovsFwdCtx->srcVportNo != OVS_DEFAULT_PORT_NO) {
+        if (ovsFwdCtx->srcVportNo != OVS_DPPORT_NUMBER_INVALID) {
 
             POVS_VPORT_ENTRY vport = OvsFindVportByPortNo(
                 ovsFwdCtx->switchContext, ovsFwdCtx->srcVportNo);
 
-            if (!vport || vport->ovsType != OVS_VPORT_TYPE_NETDEV) {
+            if (!vport ||
+                (vport->ovsType != OVS_VPORT_TYPE_NETDEV &&
+                 !OvsIsBridgeInternalVport(vport))) {
                 ovsFwdCtx->tunKey.dst = 0;
             }
         }
 
         /* Tunnel the packet only if tunnel context is set. */
         if (ovsFwdCtx->tunKey.dst != 0) {
-            ovsActionStats.txVxlan++;
+            switch(dstVport->ovsType) {
+            case OVS_VPORT_TYPE_GRE:
+                ovsActionStats.txGre++;
+                break;
+            case OVS_VPORT_TYPE_VXLAN:
+                ovsActionStats.txVxlan++;
+                break;
+            case OVS_VPORT_TYPE_STT:
+                ovsActionStats.txStt++;
+                break;
+            }
             ovsFwdCtx->tunnelTxNic = dstVport;
         }
 
@@ -343,6 +387,10 @@ OvsAddPorts(OvsForwardingContext *ovsFwdCtx,
     vport->stats.txBytes +=
         NET_BUFFER_DATA_LENGTH(NET_BUFFER_LIST_FIRST_NB(ovsFwdCtx->curNbl));
 
+    if (OvsIsBridgeInternalVport(vport)) {
+        return NDIS_STATUS_SUCCESS;
+    }
+
     if (OvsDetectTunnelPkt(ovsFwdCtx, vport, flowKey)) {
         return NDIS_STATUS_SUCCESS;
     }
@@ -523,7 +571,6 @@ OvsDoFlowLookupOutput(OvsForwardingContext *ovsFwdCtx)
     POVS_VPORT_ENTRY vport =
         OvsFindVportByPortNo(ovsFwdCtx->switchContext, ovsFwdCtx->srcVportNo);
     if (vport == NULL || vport->ovsState != OVS_STATE_CONNECTED) {
-        ASSERT(FALSE);  // XXX: let's catch this for now
         OvsCompleteNBLForwardingCtx(ovsFwdCtx,
             L"OVS-Dropped due to internal/tunnel port removal");
         ovsActionStats.noVport++;
@@ -533,9 +580,10 @@ OvsDoFlowLookupOutput(OvsForwardingContext *ovsFwdCtx)
 
     /* Assert that in the Rx direction, key is always setup. */
     ASSERT(ovsFwdCtx->tunnelRxNic == NULL || ovsFwdCtx->tunKey.dst != 0);
-    status = OvsExtractFlow(ovsFwdCtx->curNbl, ovsFwdCtx->srcVportNo,
-                          &key, &ovsFwdCtx->layers, ovsFwdCtx->tunKey.dst != 0 ?
-                                         &ovsFwdCtx->tunKey : NULL);
+    status =
+        OvsExtractFlow(ovsFwdCtx->curNbl, ovsFwdCtx->srcVportNo,
+                       &key, &ovsFwdCtx->layers,
+                       ovsFwdCtx->tunKey.dst != 0 ? &ovsFwdCtx->tunKey : NULL);
     if (status != NDIS_STATUS_SUCCESS) {
         OvsCompleteNBLForwardingCtx(ovsFwdCtx,
                                     L"OVS-Flow extract failed");
@@ -548,23 +596,22 @@ OvsDoFlowLookupOutput(OvsForwardingContext *ovsFwdCtx)
         OvsFlowUsed(flow, ovsFwdCtx->curNbl, &ovsFwdCtx->layers);
         ovsFwdCtx->switchContext->datapath.hits++;
         status = OvsActionsExecute(ovsFwdCtx->switchContext,
-                                 ovsFwdCtx->completionList, ovsFwdCtx->curNbl,
-                                 ovsFwdCtx->srcVportNo, ovsFwdCtx->sendFlags,
-                                 &key, &hash, &ovsFwdCtx->layers,
-                                 flow->actions, flow->actionsLen);
+                                   ovsFwdCtx->completionList, ovsFwdCtx->curNbl,
+                                   ovsFwdCtx->srcVportNo, ovsFwdCtx->sendFlags,
+                                   &key, &hash, &ovsFwdCtx->layers,
+                                   flow->actions, flow->actionsLen);
         ovsFwdCtx->curNbl = NULL;
     } else {
         LIST_ENTRY missedPackets;
         UINT32 num = 0;
         ovsFwdCtx->switchContext->datapath.misses++;
         InitializeListHead(&missedPackets);
-        status = OvsCreateAndAddPackets(NULL, 0, OVS_PACKET_CMD_MISS,
-                          ovsFwdCtx->srcVportNo,
+        status = OvsCreateAndAddPackets(NULL, 0, OVS_PACKET_CMD_MISS, vport,
                           &key,ovsFwdCtx->curNbl,
-                          ovsFwdCtx->tunnelRxNic != NULL, &ovsFwdCtx->layers,
+                          FALSE, &ovsFwdCtx->layers,
                           ovsFwdCtx->switchContext, &missedPackets, &num);
         if (num) {
-            OvsQueuePackets(OVS_DEFAULT_PACKET_QUEUE, &missedPackets, num);
+            OvsQueuePackets(&missedPackets, num);
         }
         if (status == NDIS_STATUS_SUCCESS) {
             /* Complete the packet since it was copied to user buffer. */
@@ -603,10 +650,11 @@ OvsTunnelPortTx(OvsForwardingContext *ovsFwdCtx)
      * Setup the source port to be the internal port to as to facilitate the
      * second OvsLookupFlow.
      */
-    if (ovsFwdCtx->switchContext->internalVport == NULL) {
+    if (ovsFwdCtx->switchContext->internalVport == NULL ||
+        ovsFwdCtx->switchContext->virtualExternalVport == NULL) {
         OvsClearTunTxCtx(ovsFwdCtx);
         OvsCompleteNBLForwardingCtx(ovsFwdCtx,
-            L"OVS-Dropped since internal port is absent");
+            L"OVS-Dropped since either internal or external port is absent");
         return NDIS_STATUS_FAILURE;
     }
     ovsFwdCtx->srcVportNo =
@@ -618,12 +666,21 @@ OvsTunnelPortTx(OvsForwardingContext *ovsFwdCtx)
 
     /* Do the encap. Encap function does not consume the NBL. */
     switch(ovsFwdCtx->tunnelTxNic->ovsType) {
+    case OVS_VPORT_TYPE_GRE:
+        status = OvsEncapGre(ovsFwdCtx->tunnelTxNic, ovsFwdCtx->curNbl,
+                             &ovsFwdCtx->tunKey, ovsFwdCtx->switchContext,
+                             &ovsFwdCtx->layers, &newNbl);
+        break;
     case OVS_VPORT_TYPE_VXLAN:
-        status = OvsEncapVxlan(ovsFwdCtx->curNbl, &ovsFwdCtx->tunKey,
-                               ovsFwdCtx->switchContext,
-                               (VOID *)ovsFwdCtx->completionList,
+        status = OvsEncapVxlan(ovsFwdCtx->tunnelTxNic, ovsFwdCtx->curNbl,
+                               &ovsFwdCtx->tunKey, ovsFwdCtx->switchContext,
                                &ovsFwdCtx->layers, &newNbl);
         break;
+    case OVS_VPORT_TYPE_STT:
+        status = OvsEncapStt(ovsFwdCtx->tunnelTxNic, ovsFwdCtx->curNbl,
+                             &ovsFwdCtx->tunKey, ovsFwdCtx->switchContext,
+                             &ovsFwdCtx->layers, &newNbl);
+        break;
     default:
         ASSERT(! "Tx: Unhandled tunnel type");
     }
@@ -671,6 +728,7 @@ OvsTunnelPortRx(OvsForwardingContext *ovsFwdCtx)
     NDIS_STATUS status = NDIS_STATUS_SUCCESS;
     PNET_BUFFER_LIST newNbl = NULL;
     POVS_VPORT_ENTRY tunnelRxVport = ovsFwdCtx->tunnelRxNic;
+    PCWSTR dropReason = L"OVS-dropped due to new decap packet";
 
     if (OvsValidateIPChecksum(ovsFwdCtx->curNbl, &ovsFwdCtx->layers)
             != NDIS_STATUS_SUCCESS) {
@@ -679,14 +737,27 @@ OvsTunnelPortRx(OvsForwardingContext *ovsFwdCtx)
         goto dropNbl;
     }
 
+    /*
+     * Decap port functions should return a new NBL if it was copied, and
+     * this new NBL should be setup as the ovsFwdCtx->curNbl.
+     */
+
     switch(tunnelRxVport->ovsType) {
+    case OVS_VPORT_TYPE_GRE:
+        status = OvsDecapGre(ovsFwdCtx->switchContext, ovsFwdCtx->curNbl,
+                             &ovsFwdCtx->tunKey, &newNbl);
+        break;
     case OVS_VPORT_TYPE_VXLAN:
-        /*
-         * OvsDoDecapVxlan should return a new NBL if it was copied, and
-         * this new NBL should be setup as the ovsFwdCtx->curNbl.
-         */
-        status = OvsDoDecapVxlan(ovsFwdCtx->switchContext, ovsFwdCtx->curNbl,
-                                                &ovsFwdCtx->tunKey, &newNbl);
+        status = OvsDecapVxlan(ovsFwdCtx->switchContext, ovsFwdCtx->curNbl,
+                               &ovsFwdCtx->tunKey, &newNbl);
+        break;
+    case OVS_VPORT_TYPE_STT:
+        status = OvsDecapStt(ovsFwdCtx->switchContext, ovsFwdCtx->curNbl,
+                             &ovsFwdCtx->tunKey, &newNbl);
+        if (status == NDIS_STATUS_SUCCESS && newNbl == NULL) {
+            /* This was an STT-LSO Fragment */
+            dropReason = L"OVS-STT segment is cached";
+        }
         break;
     default:
         OVS_LOG_ERROR("Rx: Unhandled tunnel type: %d\n",
@@ -704,25 +775,26 @@ OvsTunnelPortRx(OvsForwardingContext *ovsFwdCtx)
      * tunnelRxNic and other fields will be cleared, re-init the context
      * before usage.
       */
-    OvsCompleteNBLForwardingCtx(ovsFwdCtx,
-                                L"OVS-dropped due to new decap packet");
+    OvsCompleteNBLForwardingCtx(ovsFwdCtx, dropReason);
 
-    /* Decapsulated packet is in a new NBL */
-    ovsFwdCtx->tunnelRxNic = tunnelRxVport;
-    OvsInitForwardingCtx(ovsFwdCtx, ovsFwdCtx->switchContext,
-                         newNbl, tunnelRxVport->portNo, 0,
-                         NET_BUFFER_LIST_SWITCH_FORWARDING_DETAIL(newNbl),
-                         ovsFwdCtx->completionList,
-                         &ovsFwdCtx->layers, FALSE);
+    if (newNbl) {
+        /* Decapsulated packet is in a new NBL */
+        ovsFwdCtx->tunnelRxNic = tunnelRxVport;
+        OvsInitForwardingCtx(ovsFwdCtx, ovsFwdCtx->switchContext,
+                             newNbl, tunnelRxVport->portNo, 0,
+                             NET_BUFFER_LIST_SWITCH_FORWARDING_DETAIL(newNbl),
+                             ovsFwdCtx->completionList,
+                             &ovsFwdCtx->layers, FALSE);
 
-    /*
-     * Set the NBL's SourcePortId and SourceNicIndex to default values to
-     * keep NDIS happy when we forward the packet.
-     */
-    ovsFwdCtx->fwdDetail->SourcePortId = NDIS_SWITCH_DEFAULT_PORT_ID;
-    ovsFwdCtx->fwdDetail->SourceNicIndex = 0;
+        /*
+         * Set the NBL's SourcePortId and SourceNicIndex to default values to
+         * keep NDIS happy when we forward the packet.
+         */
+        ovsFwdCtx->fwdDetail->SourcePortId = NDIS_SWITCH_DEFAULT_PORT_ID;
+        ovsFwdCtx->fwdDetail->SourceNicIndex = 0;
 
-    status = OvsDoFlowLookupOutput(ovsFwdCtx);
+        status = OvsDoFlowLookupOutput(ovsFwdCtx);
+    }
     ASSERT(ovsFwdCtx->curNbl == NULL);
     OvsClearTunRxCtx(ovsFwdCtx);
 
@@ -755,6 +827,7 @@ OvsOutputForwardingCtx(OvsForwardingContext *ovsFwdCtx)
 {
     NDIS_STATUS status = STATUS_SUCCESS;
     POVS_SWITCH_CONTEXT switchContext = ovsFwdCtx->switchContext;
+    PCWSTR dropReason;
 
     /*
      * Handle the case where the some of the destination ports are tunneled
@@ -783,6 +856,7 @@ OvsOutputForwardingCtx(OvsForwardingContext *ovsFwdCtx)
             if (newNbl == NULL) {
                 status = NDIS_STATUS_RESOURCES;
                 ovsActionStats.noCopiedNbl++;
+                dropReason = L"Dropped due to failure to create NBL copy.";
                 goto dropit;
             }
         }
@@ -795,6 +869,7 @@ OvsOutputForwardingCtx(OvsForwardingContext *ovsFwdCtx)
         if (status != NDIS_STATUS_SUCCESS) {
             OvsCompleteNBL(ovsFwdCtx->switchContext, newNbl, TRUE);
             ovsActionStats.cannotGrowDest++;
+            dropReason = L"Dropped due to failure to update destinations.";
             goto dropit;
         }
 
@@ -810,8 +885,7 @@ OvsOutputForwardingCtx(OvsForwardingContext *ovsFwdCtx)
                                           ovsFwdCtx->completionList,
                                           &ovsFwdCtx->layers, FALSE);
             if (status != NDIS_STATUS_SUCCESS) {
-                OvsCompleteNBLForwardingCtx(ovsFwdCtx,
-                                            L"Dropped due to resouces");
+                dropReason = L"Dropped due to resouces.";
                 goto dropit;
             }
         }
@@ -832,7 +906,7 @@ OvsOutputForwardingCtx(OvsForwardingContext *ovsFwdCtx)
 
 dropit:
     if (status != NDIS_STATUS_SUCCESS) {
-        OvsCompleteNBLForwardingCtx(ovsFwdCtx, L"Dropped due to XXX");
+        OvsCompleteNBLForwardingCtx(ovsFwdCtx, dropReason);
     }
 
     return status;
@@ -902,7 +976,6 @@ OvsOutputBeforeSetAction(OvsForwardingContext *ovsFwdCtx)
 {
     PNET_BUFFER_LIST newNbl;
     NDIS_STATUS status = NDIS_STATUS_SUCCESS;
-    PNET_BUFFER nb;
 
     /*
      * Create a copy and work on the copy after this point. The original NBL is
@@ -922,14 +995,14 @@ OvsOutputBeforeSetAction(OvsForwardingContext *ovsFwdCtx)
      * XXX Head room needs to include the additional encap.
      * XXX copySize check is not considering multiple NBs.
      */
-    nb = NET_BUFFER_LIST_FIRST_NB(ovsFwdCtx->curNbl);
     newNbl = OvsPartialCopyNBL(ovsFwdCtx->switchContext, ovsFwdCtx->curNbl,
                                0, 0, TRUE /*copy NBL info*/);
 
     ASSERT(ovsFwdCtx->destPortsSizeOut > 0 ||
            ovsFwdCtx->tunnelTxNic != NULL || ovsFwdCtx->tunnelRxNic != NULL);
 
-    /* Send the original packet out */
+    /* Send the original packet out and save the original source port number */
+    UINT32 tempVportNo = ovsFwdCtx->srcVportNo;
     status = OvsOutputForwardingCtx(ovsFwdCtx);
     ASSERT(ovsFwdCtx->curNbl == NULL);
     ASSERT(ovsFwdCtx->destPortsSizeOut == 0);
@@ -947,7 +1020,7 @@ OvsOutputBeforeSetAction(OvsForwardingContext *ovsFwdCtx)
         OvsCompleteNBL(ovsFwdCtx->switchContext, newNbl, TRUE);
     } else {
         status = OvsInitForwardingCtx(ovsFwdCtx, ovsFwdCtx->switchContext,
-                                      newNbl, ovsFwdCtx->srcVportNo, 0,
+                                      newNbl, tempVportNo, 0,
                                       NET_BUFFER_LIST_SWITCH_FORWARDING_DETAIL(newNbl),
                                       ovsFwdCtx->completionList,
                                       &ovsFwdCtx->layers, FALSE);
@@ -959,28 +1032,29 @@ OvsOutputBeforeSetAction(OvsForwardingContext *ovsFwdCtx)
 
 /*
  * --------------------------------------------------------------------------
- * OvsPopVlanInPktBuf --
- *     Function to pop a VLAN tag when the tag is in the packet buffer.
+ * OvsPopFieldInPacketBuf --
+ *     Function to pop a specified field of length 'shiftLength' located at
+ *     'shiftOffset' from the ethernet header. The data on the left of the
+ *     'shiftOffset' is right shifted.
+ *
+ *     Returns a pointer to the new start in 'bufferData'.
  * --------------------------------------------------------------------------
  */
 static __inline NDIS_STATUS
-OvsPopVlanInPktBuf(OvsForwardingContext *ovsFwdCtx)
+OvsPopFieldInPacketBuf(OvsForwardingContext *ovsFwdCtx,
+                       UINT32 shiftOffset,
+                       UINT32 shiftLength,
+                       PUINT8 *bufferData)
 {
     PNET_BUFFER curNb;
     PMDL curMdl;
     PUINT8 bufferStart;
-    ULONG dataLength = sizeof (DL_EUI48) + sizeof (DL_EUI48);
     UINT32 packetLen, mdlLen;
     PNET_BUFFER_LIST newNbl;
     NDIS_STATUS status;
+    PUINT8 tempBuffer[ETH_HEADER_LENGTH];
 
-    /*
-     * Declare a dummy vlanTag structure since we need to compute the size
-     * of shiftLength. The NDIS one is a unionized structure.
-     */
-    NDIS_PACKET_8021Q_INFO vlanTag = {0};
-    ULONG shiftLength = sizeof (vlanTag.TagHeader);
-    PUINT8 tempBuffer[sizeof (DL_EUI48) + sizeof (DL_EUI48)];
+    ASSERT(shiftOffset > ETH_ADDR_LENGTH);
 
     newNbl = OvsPartialCopyNBL(ovsFwdCtx->switchContext, ovsFwdCtx->curNbl,
                                0, 0, TRUE /* copy NBL info */);
@@ -992,8 +1066,8 @@ OvsPopVlanInPktBuf(OvsForwardingContext *ovsFwdCtx)
     /* Complete the original NBL and create a copy to modify. */
     OvsCompleteNBLForwardingCtx(ovsFwdCtx, L"OVS-Dropped due to copy");
 
-    status = OvsInitForwardingCtx(ovsFwdCtx, ovsFwdCtx->switchContext,
-                                  newNbl, ovsFwdCtx->srcVportNo, 0,
+    status = OvsInitForwardingCtx(ovsFwdCtx, ovsFwdCtx->switchContext, newNbl,
+                                  ovsFwdCtx->srcVportNo, 0,
                                   NET_BUFFER_LIST_SWITCH_FORWARDING_DETAIL(newNbl),
                                   NULL, &ovsFwdCtx->layers, FALSE);
     if (status != NDIS_STATUS_SUCCESS) {
@@ -1011,16 +1085,142 @@ OvsPopVlanInPktBuf(OvsForwardingContext *ovsFwdCtx)
         return NDIS_STATUS_RESOURCES;
     }
     mdlLen -= NET_BUFFER_CURRENT_MDL_OFFSET(curNb);
-    /* Bail out if L2 + VLAN header is not contiguous in the first buffer. */
-    if (MIN(packetLen, mdlLen) < sizeof (EthHdr) + shiftLength) {
+    /* Bail out if L2 + shiftLength is not contiguous in the first buffer. */
+    if (MIN(packetLen, mdlLen) < sizeof(EthHdr) + shiftLength) {
         ASSERT(FALSE);
         return NDIS_STATUS_FAILURE;
     }
     bufferStart += NET_BUFFER_CURRENT_MDL_OFFSET(curNb);
-    RtlCopyMemory(tempBuffer, bufferStart, dataLength);
-    RtlCopyMemory(bufferStart + shiftLength, tempBuffer, dataLength);
+    RtlCopyMemory(tempBuffer, bufferStart, shiftOffset);
+    RtlCopyMemory(bufferStart + shiftLength, tempBuffer, shiftOffset);
     NdisAdvanceNetBufferDataStart(curNb, shiftLength, FALSE, NULL);
 
+    if (bufferData) {
+        *bufferData = bufferStart + shiftLength;
+    }
+
+    return NDIS_STATUS_SUCCESS;
+}
+
+
+/*
+ * --------------------------------------------------------------------------
+ * OvsPopVlanInPktBuf --
+ *     Function to pop a VLAN tag when the tag is in the packet buffer.
+ * --------------------------------------------------------------------------
+ */
+static __inline NDIS_STATUS
+OvsPopVlanInPktBuf(OvsForwardingContext *ovsFwdCtx)
+{
+    /*
+     * Declare a dummy vlanTag structure since we need to compute the size
+     * of shiftLength. The NDIS one is a unionized structure.
+     */
+    NDIS_PACKET_8021Q_INFO vlanTag = {0};
+    UINT32 shiftLength = sizeof(vlanTag.TagHeader);
+    UINT32 shiftOffset = sizeof(DL_EUI48) + sizeof(DL_EUI48);
+
+    return OvsPopFieldInPacketBuf(ovsFwdCtx, shiftOffset, shiftLength, NULL);
+}
+
+
+/*
+ * --------------------------------------------------------------------------
+ * OvsActionMplsPop --
+ *     Function to pop the first MPLS label from the current packet.
+ * --------------------------------------------------------------------------
+ */
+static __inline NDIS_STATUS
+OvsActionMplsPop(OvsForwardingContext *ovsFwdCtx,
+                 ovs_be16 ethertype)
+{
+    NDIS_STATUS status = NDIS_STATUS_SUCCESS;
+    OVS_PACKET_HDR_INFO *layers = &ovsFwdCtx->layers;
+    EthHdr *ethHdr = NULL;
+
+    status = OvsPopFieldInPacketBuf(ovsFwdCtx, sizeof(*ethHdr),
+                                    MPLS_HLEN, (PUINT8*)&ethHdr);
+    if (status == NDIS_STATUS_SUCCESS) {
+        if (ethHdr && OvsEthertypeIsMpls(ethHdr->Type)) {
+            ethHdr->Type = ethertype;
+        }
+
+        layers->l3Offset -= MPLS_HLEN;
+        layers->l4Offset -= MPLS_HLEN;
+    }
+
+    return status;
+}
+
+
+/*
+ * --------------------------------------------------------------------------
+ * OvsActionMplsPush --
+ *     Function to push the MPLS label into the current packet.
+ * --------------------------------------------------------------------------
+ */
+static __inline NDIS_STATUS
+OvsActionMplsPush(OvsForwardingContext *ovsFwdCtx,
+                  const struct ovs_action_push_mpls *mpls)
+{
+    NDIS_STATUS status;
+    PNET_BUFFER curNb = NULL;
+    PMDL curMdl = NULL;
+    PUINT8 bufferStart = NULL;
+    OVS_PACKET_HDR_INFO *layers = &ovsFwdCtx->layers;
+    EthHdr *ethHdr = NULL;
+    MPLSHdr *mplsHdr = NULL;
+    UINT32 mdlLen = 0, curMdlOffset = 0;
+    PNET_BUFFER_LIST newNbl;
+
+    newNbl = OvsPartialCopyNBL(ovsFwdCtx->switchContext, ovsFwdCtx->curNbl,
+                               layers->l3Offset, MPLS_HLEN, TRUE);
+    if (!newNbl) {
+        ovsActionStats.noCopiedNbl++;
+        return NDIS_STATUS_RESOURCES;
+    }
+    OvsCompleteNBLForwardingCtx(ovsFwdCtx,
+                                L"Complete after partial copy.");
+
+    status = OvsInitForwardingCtx(ovsFwdCtx, ovsFwdCtx->switchContext,
+                                  newNbl, ovsFwdCtx->srcVportNo, 0,
+                                  NET_BUFFER_LIST_SWITCH_FORWARDING_DETAIL(newNbl),
+                                  NULL, &ovsFwdCtx->layers, FALSE);
+    if (status != NDIS_STATUS_SUCCESS) {
+        OvsCompleteNBLForwardingCtx(ovsFwdCtx,
+                                    L"OVS-Dropped due to resources");
+        return NDIS_STATUS_RESOURCES;
+    }
+
+    curNb = NET_BUFFER_LIST_FIRST_NB(ovsFwdCtx->curNbl);
+    ASSERT(curNb->Next == NULL);
+
+    status = NdisRetreatNetBufferDataStart(curNb, MPLS_HLEN, 0, NULL);
+    if (status != NDIS_STATUS_SUCCESS) {
+        return status;
+    }
+
+    curMdl = NET_BUFFER_CURRENT_MDL(curNb);
+    NdisQueryMdl(curMdl, &bufferStart, &mdlLen, LowPagePriority);
+    if (!curMdl) {
+        ovsActionStats.noResource++;
+        return NDIS_STATUS_RESOURCES;
+    }
+
+    curMdlOffset = NET_BUFFER_CURRENT_MDL_OFFSET(curNb);
+    mdlLen -= curMdlOffset;
+    ASSERT(mdlLen >= MPLS_HLEN);
+
+    ethHdr = (EthHdr *)(bufferStart + curMdlOffset);
+    RtlMoveMemory(ethHdr, (UINT8*)ethHdr + MPLS_HLEN, sizeof(*ethHdr));
+    ethHdr->Type = mpls->mpls_ethertype;
+
+    mplsHdr = (MPLSHdr *)(ethHdr + 1);
+    mplsHdr->lse = mpls->mpls_lse;
+
+    layers->l3Offset += MPLS_HLEN;
+    layers->l4Offset += MPLS_HLEN;
+
     return NDIS_STATUS_SUCCESS;
 }
 
@@ -1303,30 +1503,17 @@ OvsExecuteSetAction(OvsForwardingContext *ovsFwdCtx,
     case OVS_KEY_ATTR_TUNNEL:
     {
         OvsIPv4TunnelKey tunKey;
-
-               status = OvsTunnelAttrToIPv4TunnelKey((PNL_ATTR)a, &tunKey);
+        status = OvsTunnelAttrToIPv4TunnelKey((PNL_ATTR)a, &tunKey);
         ASSERT(status == NDIS_STATUS_SUCCESS);
         tunKey.flow_hash = (uint16)(hash ? *hash : OvsHashFlow(key));
+        tunKey.dst_port = key->ipKey.l4.tpDst;
         RtlCopyMemory(&ovsFwdCtx->tunKey, &tunKey, sizeof ovsFwdCtx->tunKey);
-
         break;
     }
-    case OVS_KEY_ATTR_SKB_MARK:
-    /* XXX: Not relevant to Hyper-V. Return OK */
-    break;
-    case OVS_KEY_ATTR_UNSPEC:
-    case OVS_KEY_ATTR_ENCAP:
-    case OVS_KEY_ATTR_ETHERTYPE:
-    case OVS_KEY_ATTR_IN_PORT:
-    case OVS_KEY_ATTR_VLAN:
-    case OVS_KEY_ATTR_ICMP:
-    case OVS_KEY_ATTR_ICMPV6:
-    case OVS_KEY_ATTR_ARP:
-    case OVS_KEY_ATTR_ND:
-    case __OVS_KEY_ATTR_MAX:
+
     default:
-    OVS_LOG_INFO("Unhandled attribute %#x", type);
-    ASSERT(FALSE);
+        OVS_LOG_INFO("Unhandled attribute %#x", type);
+        break;
     }
     return status;
 }
@@ -1464,6 +1651,50 @@ OvsActionsExecute(POVS_SWITCH_CONTEXT switchContext,
             break;
         }
 
+        case OVS_ACTION_ATTR_PUSH_MPLS:
+        {
+            if (ovsFwdCtx.destPortsSizeOut > 0 || ovsFwdCtx.tunnelTxNic != NULL
+                || ovsFwdCtx.tunnelRxNic != NULL) {
+                status = OvsOutputBeforeSetAction(&ovsFwdCtx);
+                if (status != NDIS_STATUS_SUCCESS) {
+                    dropReason = L"OVS-adding destination failed";
+                    goto dropit;
+                }
+            }
+
+            status = OvsActionMplsPush(&ovsFwdCtx,
+                                       (struct ovs_action_push_mpls *)NlAttrGet
+                                       ((const PNL_ATTR)a));
+            if (status != NDIS_STATUS_SUCCESS) {
+                dropReason = L"OVS-push MPLS action failed";
+                goto dropit;
+            }
+            layers->l3Offset += MPLS_HLEN;
+            layers->l4Offset += MPLS_HLEN;
+            break;
+        }
+
+        case OVS_ACTION_ATTR_POP_MPLS:
+        {
+            if (ovsFwdCtx.destPortsSizeOut > 0 || ovsFwdCtx.tunnelTxNic != NULL
+                || ovsFwdCtx.tunnelRxNic != NULL) {
+                status = OvsOutputBeforeSetAction(&ovsFwdCtx);
+                if (status != NDIS_STATUS_SUCCESS) {
+                    dropReason = L"OVS-adding destination failed";
+                    goto dropit;
+                }
+            }
+
+            status = OvsActionMplsPop(&ovsFwdCtx, NlAttrGetBe16(a));
+            if (status != NDIS_STATUS_SUCCESS) {
+                dropReason = L"OVS-pop MPLS action failed";
+                goto dropit;
+            }
+            layers->l3Offset -= MPLS_HLEN;
+            layers->l4Offset -= MPLS_HLEN;
+            break;
+        }
+
         case OVS_ACTION_ATTR_USERSPACE:
         {
             PNL_ATTR userdataAttr;
@@ -1487,7 +1718,7 @@ OvsActionsExecute(POVS_SWITCH_CONTEXT switchContext,
             elem = OvsCreateQueueNlPacket((PVOID)userdataAttr,
                                     userdataAttr->nlaLen,
                                     OVS_PACKET_CMD_ACTION,
-                                    portNo, key,ovsFwdCtx.curNbl,
+                                    vport, key, ovsFwdCtx.curNbl,
                                     NET_BUFFER_LIST_FIRST_NB(ovsFwdCtx.curNbl),
                                     isRecv,
                                     layers);
@@ -1495,7 +1726,7 @@ OvsActionsExecute(POVS_SWITCH_CONTEXT switchContext,
                 LIST_ENTRY missedPackets;
                 InitializeListHead(&missedPackets);
                 InsertTailList(&missedPackets, &elem->link);
-                OvsQueuePackets(OVS_DEFAULT_PACKET_QUEUE, &missedPackets, 1);
+                OvsQueuePackets(&missedPackets, 1);
                 dropReason = L"OVS-Completed since packet was copied to "
                              L"userspace";
             } else {