/*
 * 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.
 * You may obtain a copy of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "precomp.h"

#include "Actions.h"
#include "Conntrack.h"
#include "Debug.h"
#include "Event.h"
#include "Flow.h"
#include "Gre.h"
#include "Jhash.h"
#include "Meter.h"
#include "Mpls.h"
#include "NetProto.h"
#include "Offload.h"
#include "PacketIO.h"
#include "Recirc.h"
#include "Stt.h"
#include "Switch.h"
#include "User.h"
#include "Vport.h"
#include "Vxlan.h"
#include "Geneve.h"
#include "IpFragment.h"

#ifdef OVS_DBG_MOD
#undef OVS_DBG_MOD
#endif
#define OVS_DBG_MOD OVS_DBG_ACTION

#define OVS_DEST_PORTS_ARRAY_MIN_SIZE 2

typedef struct _OVS_ACTION_STATS {
    UINT64 rxGre;
    UINT64 txGre;
    UINT64 rxVxlan;
    UINT64 txVxlan;
    UINT64 rxStt;
    UINT64 txStt;
    UINT64 rxGeneve;
    UINT64 txGeneve;
    UINT64 flowMiss;
    UINT64 flowUserspace;
    UINT64 txTcp;
    UINT32 failedFlowMiss;
    UINT32 noVport;
    UINT32 failedFlowExtract;
    UINT32 noResource;
    UINT32 noCopiedNbl;
    UINT32 failedEncap;
    UINT32 failedDecap;
    UINT32 cannotGrowDest;
    UINT32 zeroActionLen;
    UINT32 failedChecksum;
    UINT32 deferredActionsQueueFull;
    UINT32 deferredActionsExecLimit;
} OVS_ACTION_STATS, *POVS_ACTION_STATS;

OVS_ACTION_STATS ovsActionStats;

/*
 * --------------------------------------------------------------------------
 * OvsInitForwardingCtx --
 *     Function to init/re-init the 'ovsFwdCtx' context as the actions pipeline
 *     is being executed.
 *
 * Result:
 *     NDIS_STATUS_SUCCESS on success
 *     Other NDIS_STATUS upon failure. Upon failure, it is safe to call
 *     OvsCompleteNBLForwardingCtx(), since 'ovsFwdCtx' has been initialized
 *     enough for OvsCompleteNBLForwardingCtx() to do its work.
 * --------------------------------------------------------------------------
 */
static __inline NDIS_STATUS
OvsInitForwardingCtx(OvsForwardingContext *ovsFwdCtx,
                     POVS_SWITCH_CONTEXT switchContext,
                     PNET_BUFFER_LIST curNbl,
                     UINT32 srcVportNo,
                     ULONG sendFlags,
                     PNDIS_SWITCH_FORWARDING_DETAIL_NET_BUFFER_LIST_INFO fwdDetail,
                     OvsCompletionList *completionList,
                     OVS_PACKET_HDR_INFO *layers,
                     BOOLEAN resetTunnelInfo)
{
    ASSERT(ovsFwdCtx);
    ASSERT(switchContext);
    ASSERT(curNbl);
    ASSERT(fwdDetail);

    /*
     * Set values for curNbl and switchContext so upon failures, we have enough
     * information to do cleanup.
     */
    ovsFwdCtx->curNbl = curNbl;
    ovsFwdCtx->switchContext = switchContext;
    ovsFwdCtx->completionList = completionList;
    ovsFwdCtx->fwdDetail = fwdDetail;

    if (fwdDetail->NumAvailableDestinations > 0) {
        /*
         * XXX: even though MSDN says GetNetBufferListDestinations() returns
         * NDIS_STATUS, the header files say otherwise.
         */
        switchContext->NdisSwitchHandlers.GetNetBufferListDestinations(
            switchContext->NdisSwitchContext, curNbl,
            &ovsFwdCtx->destinationPorts);

        ASSERT(ovsFwdCtx->destinationPorts);
        /* Ensure that none of the elements are consumed yet. */
        ASSERT(ovsFwdCtx->destinationPorts->NumElements ==
               fwdDetail->NumAvailableDestinations);
    } else {
        ovsFwdCtx->destinationPorts = NULL;
    }
    ovsFwdCtx->destPortsSizeIn = fwdDetail->NumAvailableDestinations;
    ovsFwdCtx->destPortsSizeOut = 0;
    ovsFwdCtx->srcVportNo = srcVportNo;
    ovsFwdCtx->sendFlags = sendFlags;
    if (layers) {
        ovsFwdCtx->layers = *layers;
    } else {
        RtlZeroMemory(&ovsFwdCtx->layers, sizeof ovsFwdCtx->layers);
    }
    if (resetTunnelInfo) {
        ovsFwdCtx->tunnelTxNic = NULL;
        ovsFwdCtx->tunnelRxNic = NULL;
        RtlZeroMemory(&ovsFwdCtx->tunKey, sizeof ovsFwdCtx->tunKey);
    }

    return NDIS_STATUS_SUCCESS;
}

/*
 * --------------------------------------------------------------------------
 * OvsDoFragmentNbl --
 *     Utility function to Fragment nbl based on mru.
 * --------------------------------------------------------------------------
 */
static __inline VOID
OvsDoFragmentNbl(OvsForwardingContext *ovsFwdCtx, UINT16 mru)
{
    PNET_BUFFER_LIST fragNbl = NULL;
    fragNbl = OvsFragmentNBL(ovsFwdCtx->switchContext,
                             ovsFwdCtx->curNbl,
                             &(ovsFwdCtx->layers),
                             mru, 0, TRUE);

   if (fragNbl != NULL) {
        OvsCompleteNBL(ovsFwdCtx->switchContext, ovsFwdCtx->curNbl, TRUE);
        ovsFwdCtx->curNbl = fragNbl;
    } else {
        OVS_LOG_INFO("Fragment NBL failed for MRU = %u", mru);
    }
}

/*
 * --------------------------------------------------------------------------
 * OvsDetectTunnelRxPkt --
 *     Utility function for an RX packet to detect its tunnel type.
 *
 * Result:
 *  True  - if the tunnel type was detected.
 *  False - if not a tunnel packet or tunnel type not supported.
 * --------------------------------------------------------------------------
 */
static __inline BOOLEAN
OvsDetectTunnelRxPkt(OvsForwardingContext *ovsFwdCtx,
                     const OvsFlowKey *flowKey)
{
    POVS_VPORT_ENTRY tunnelVport = NULL;

    /* XXX: we should also check for the length of the UDP payload to pick
     * packets only if they are at least VXLAN header size.
     */

     /*
      * For some of the tunnel types such as GRE, the dstPort is not applicable
      * since GRE does not have a L4 port. We use '0' for convenience.
      */

    if ((flowKey->l2.dlType == htons(ETH_TYPE_IPV4) &&
        !flowKey->ipKey.nwFrag) ||
        (flowKey->l2.dlType == htons(ETH_TYPE_IPV6) &&
        !flowKey->ipv6Key.nwFrag)) {
        UINT16 dstPort = 0;
        uint8_t nwProto = 0;
        if (flowKey->l2.dlType == htons(ETH_TYPE_IPV6)) {
            dstPort = htons(flowKey->ipv6Key.l4.tpDst);
            nwProto = flowKey->ipv6Key.nwProto;
         } else if (flowKey->l2.dlType == htons(ETH_TYPE_IPV4)) {
            dstPort = htons(flowKey->ipKey.l4.tpDst);
            nwProto = flowKey->ipKey.nwProto;
        }
        ASSERT(nwProto != IPPROTO_GRE || dstPort == 0);

        tunnelVport =
            OvsFindTunnelVportByDstPortAndNWProto(ovsFwdCtx->switchContext,
                                                  dstPort, nwProto);
        if (tunnelVport) {
            switch(tunnelVport->ovsType) {
            case OVS_VPORT_TYPE_STT:
                ovsActionStats.rxStt++;
                break;
            case OVS_VPORT_TYPE_VXLAN:
                ovsActionStats.rxVxlan++;
                break;
            case OVS_VPORT_TYPE_GENEVE:
                ovsActionStats.rxGeneve++;
                break;
            case OVS_VPORT_TYPE_GRE:
                ovsActionStats.rxGre++;
                break;
            }
        }
    }

    // We might get tunnel packets even before the tunnel gets initialized.
    if (tunnelVport) {
        ASSERT(ovsFwdCtx->tunnelRxNic == NULL);
        ovsFwdCtx->tunnelRxNic = tunnelVport;
        return TRUE;
    }

    return FALSE;
}

/*
 * --------------------------------------------------------------------------
 * OvsDetectTunnelPkt --
 *     Utility function to detect if a packet is to be subjected to
 *     tunneling (Tx) or de-tunneling (Rx). Various factors such as source
 *     port, destination port, packet contents, and previously setup tunnel
 *     context are used.
 *
 * Result:
 *  True  - If the packet is to be subjected to tunneling.
 *          In case of invalid tunnel context, the tunneling functionality is
 *          a no-op and is completed within this function itself by consuming
 *          all of the tunneling context.
 *  False - If not a tunnel packet or tunnel type not supported. Caller should
 *          process the packet as a non-tunnel packet.
 * --------------------------------------------------------------------------
 */
static __inline BOOLEAN
OvsDetectTunnelPkt(OvsForwardingContext *ovsFwdCtx,
                   const POVS_VPORT_ENTRY dstVport,
                   const OvsFlowKey *flowKey)
{
    if (OvsIsInternalVportType(dstVport->ovsType)) {
        /*
         * Rx:
         * The source of NBL during tunneling Rx could be the external
         * port or if it is being executed from userspace, the source port is
         * default port.
         */
        BOOLEAN validSrcPort =
            (OvsIsExternalVportByPortId(ovsFwdCtx->switchContext,
                 ovsFwdCtx->fwdDetail->SourcePortId)) ||
            (ovsFwdCtx->fwdDetail->SourcePortId ==
                 NDIS_SWITCH_DEFAULT_PORT_ID);

        if (validSrcPort && OvsDetectTunnelRxPkt(ovsFwdCtx, flowKey)) {
            ASSERT(ovsFwdCtx->tunnelTxNic == NULL);
            ASSERT(ovsFwdCtx->tunnelRxNic != NULL);
            return TRUE;
        }
    } else if (OvsIsTunnelVportType(dstVport->ovsType)) {
        ASSERT(ovsFwdCtx->tunnelRxNic == NULL);

        /*
         * Tx:
         * The destination port is a tunnel port. Encapsulation must be
         * performed only on packets that originate from:
         * - a VIF port
         * - a bridge-internal port (packets generated from userspace)
         * - no port.
         * - tunnel port
         * If the packet will not be encapsulated, consume the tunnel context
         * by clearing it.
         */
        if (ovsFwdCtx->srcVportNo != OVS_DPPORT_NUMBER_INVALID) {

            POVS_VPORT_ENTRY vport = OvsFindVportByPortNo(
                ovsFwdCtx->switchContext, ovsFwdCtx->srcVportNo);

            if (!vport ||
                (vport->ovsType != OVS_VPORT_TYPE_NETDEV &&
                 vport->ovsType != OVS_VPORT_TYPE_INTERNAL &&
                 !OvsIsTunnelVportType(vport->ovsType))) {
                RtlZeroMemory(&ovsFwdCtx->tunKey.dst, sizeof(ovsFwdCtx->tunKey.dst));
            }
        }

        /* Tunnel the packet only if tunnel context is set. */
        if (!OvsIphIsZero(&(ovsFwdCtx->tunKey.dst))) {
            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;
            case OVS_VPORT_TYPE_GENEVE:
               ovsActionStats.txGeneve++;
               break;
            }
            ovsFwdCtx->tunnelTxNic = dstVport;
        }

        return TRUE;
    }

    return FALSE;
}


/*
 * --------------------------------------------------------------------------
 * OvsAddPorts --
 *     Add the specified destination vport into the forwarding context. If the
 *     vport is a VIF/external port, it is added directly to the NBL. If it is
 *     a tunneling port, it is NOT added to the NBL.
 *
 * Result:
 *     NDIS_STATUS_SUCCESS on success
 *     Other NDIS_STATUS upon failure.
 * --------------------------------------------------------------------------
 */
static __inline NDIS_STATUS
OvsAddPorts(OvsForwardingContext *ovsFwdCtx,
            OvsFlowKey *flowKey,
            NDIS_SWITCH_PORT_ID dstPortId,
            BOOLEAN preserveVLAN,
            BOOLEAN preservePriority)
{
    POVS_VPORT_ENTRY vport;
    PNDIS_SWITCH_PORT_DESTINATION fwdPort;
    NDIS_STATUS status;
    POVS_SWITCH_CONTEXT switchContext = ovsFwdCtx->switchContext;

    /*
     * We hold the dispatch lock that protects the list of vports, so vports
     * validated here can be added as destinations safely before we call into
     * NDIS.
     *
     * Some of the vports can be tunnelled ports as well in which case
     * they should be added to a separate list of tunnelled destination ports
     * instead of the VIF ports. The context for the tunnel is settable
     * in OvsForwardingContext.
     */
    vport = OvsFindVportByPortNo(ovsFwdCtx->switchContext, dstPortId);
    if (vport == NULL || vport->ovsState != OVS_STATE_CONNECTED) {
        /*
         * There may be some latency between a port disappearing, and userspace
         * updating the recalculated flows. In the meantime, handle invalid
         * ports gracefully.
         */
        ovsActionStats.noVport++;
        return NDIS_STATUS_SUCCESS;
    }
    ASSERT(vport->nicState == NdisSwitchNicStateConnected);
    vport->stats.txPackets++;
    vport->stats.txBytes +=
        NET_BUFFER_DATA_LENGTH(NET_BUFFER_LIST_FIRST_NB(ovsFwdCtx->curNbl));

    if (OvsDetectTunnelPkt(ovsFwdCtx, vport, flowKey)) {
        return NDIS_STATUS_SUCCESS;
    }

    if (ovsFwdCtx->destPortsSizeOut == ovsFwdCtx->destPortsSizeIn) {
        if (ovsFwdCtx->destPortsSizeIn == 0) {
            ASSERT(ovsFwdCtx->destinationPorts == NULL);
            ASSERT(ovsFwdCtx->fwdDetail->NumAvailableDestinations == 0);
            status =
                switchContext->NdisSwitchHandlers.GrowNetBufferListDestinations(
                    switchContext->NdisSwitchContext, ovsFwdCtx->curNbl,
                    OVS_DEST_PORTS_ARRAY_MIN_SIZE,
                    &ovsFwdCtx->destinationPorts);
            if (status != NDIS_STATUS_SUCCESS) {
                ovsActionStats.cannotGrowDest++;
                return status;
            }
            ovsFwdCtx->destPortsSizeIn =
                ovsFwdCtx->fwdDetail->NumAvailableDestinations;
            ASSERT(ovsFwdCtx->destinationPorts);
        } else {
            ASSERT(ovsFwdCtx->destinationPorts != NULL);
            /*
             * NumElements:
             * A ULONG value that specifies the total number of
             * NDIS_SWITCH_PORT_DESTINATION elements in the
             * NDIS_SWITCH_FORWARDING_DESTINATION_ARRAY structure.
             *
             * NumDestinations:
             * A ULONG value that specifies the number of
             * NDIS_SWITCH_PORT_DESTINATION elements in the
             * NDIS_SWITCH_FORWARDING_DESTINATION_ARRAY structure that
             * specify port destinations.
             *
             * NumAvailableDestinations:
             * A value that specifies the number of unused extensible switch
             * destination ports elements within an NET_BUFFER_LIST structure.
             */
            ASSERT(ovsFwdCtx->destinationPorts->NumElements ==
                   ovsFwdCtx->destPortsSizeIn);
            ASSERT(ovsFwdCtx->destinationPorts->NumDestinations ==
                   ovsFwdCtx->destPortsSizeOut -
                   ovsFwdCtx->fwdDetail->NumAvailableDestinations);
            ASSERT(ovsFwdCtx->fwdDetail->NumAvailableDestinations > 0);
            /*
             * Before we grow the array of destination ports, the current set
             * of ports needs to be committed. Only the ports added since the
             * last commit need to be part of the new update.
             */
            status = switchContext->NdisSwitchHandlers.UpdateNetBufferListDestinations(
                switchContext->NdisSwitchContext, ovsFwdCtx->curNbl,
                ovsFwdCtx->fwdDetail->NumAvailableDestinations,
                ovsFwdCtx->destinationPorts);
            if (status != NDIS_STATUS_SUCCESS) {
                ovsActionStats.cannotGrowDest++;
                return status;
            }
            ASSERT(ovsFwdCtx->destinationPorts->NumElements ==
                   ovsFwdCtx->destPortsSizeIn);
            ASSERT(ovsFwdCtx->destinationPorts->NumDestinations ==
                   ovsFwdCtx->destPortsSizeOut);
            ASSERT(ovsFwdCtx->fwdDetail->NumAvailableDestinations == 0);

            status = switchContext->NdisSwitchHandlers.GrowNetBufferListDestinations(
                switchContext->NdisSwitchContext, ovsFwdCtx->curNbl,
                ovsFwdCtx->destPortsSizeIn, &ovsFwdCtx->destinationPorts);
            if (status != NDIS_STATUS_SUCCESS) {
                ovsActionStats.cannotGrowDest++;
                return status;
            }
            ASSERT(ovsFwdCtx->destinationPorts != NULL);
            ovsFwdCtx->destPortsSizeIn <<= 1;
        }
    }

    ASSERT(ovsFwdCtx->destPortsSizeOut < ovsFwdCtx->destPortsSizeIn);
    fwdPort =
        NDIS_SWITCH_PORT_DESTINATION_AT_ARRAY_INDEX(ovsFwdCtx->destinationPorts,
                                                    ovsFwdCtx->destPortsSizeOut);

    fwdPort->PortId = vport->portId;
    fwdPort->NicIndex = vport->nicIndex;
    fwdPort->IsExcluded = 0;
    fwdPort->PreserveVLAN = preserveVLAN;
    fwdPort->PreservePriority = preservePriority;
    ovsFwdCtx->destPortsSizeOut += 1;

    return NDIS_STATUS_SUCCESS;
}


/*
 * --------------------------------------------------------------------------
 * OvsClearTunTxCtx --
 *     Utility function to clear tx tunneling context.
 * --------------------------------------------------------------------------
 */
static __inline VOID
OvsClearTunTxCtx(OvsForwardingContext *ovsFwdCtx)
{
    ovsFwdCtx->tunnelTxNic = NULL;
    RtlZeroMemory(&ovsFwdCtx->tunKey.dst, sizeof(ovsFwdCtx->tunKey.dst));
}


/*
 * --------------------------------------------------------------------------
 * OvsClearTunRxCtx --
 *     Utility function to clear rx tunneling context.
 * --------------------------------------------------------------------------
 */
static __inline VOID
OvsClearTunRxCtx(OvsForwardingContext *ovsFwdCtx)
{
    ovsFwdCtx->tunnelRxNic = NULL;
    RtlZeroMemory(&ovsFwdCtx->tunKey.dst, sizeof(ovsFwdCtx->tunKey.dst));
}


/*
 * --------------------------------------------------------------------------
 * OvsCompleteNBLForwardingCtx --
 *     This utility function is responsible for freeing/completing an NBL - either
 *     by adding it to a completion list or by freeing it.
 *
 * Side effects:
 *     It also resets the necessary fields in 'ovsFwdCtx'.
 * --------------------------------------------------------------------------
 */
static __inline VOID
OvsCompleteNBLForwardingCtx(OvsForwardingContext *ovsFwdCtx,
                            PCWSTR dropReason)
{
    NDIS_STRING filterReason;

    RtlInitUnicodeString(&filterReason, dropReason);
    if (ovsFwdCtx->completionList) {
        OvsAddPktCompletionList(ovsFwdCtx->completionList, TRUE,
            ovsFwdCtx->fwdDetail->SourcePortId, ovsFwdCtx->curNbl, 1,
            &filterReason);
        ovsFwdCtx->curNbl = NULL;
    } else {
        /* If there is no completionList, we assume this is ovs created NBL */
        ovsFwdCtx->curNbl = OvsCompleteNBL(ovsFwdCtx->switchContext,
                                           ovsFwdCtx->curNbl, TRUE);
        ASSERT(ovsFwdCtx->curNbl == NULL);
    }
    /* XXX: these can be made debug only to save cycles. Ideally the pipeline
     * using these fields should reset the values at the end of the pipeline. */
    ovsFwdCtx->destPortsSizeOut = 0;
    ovsFwdCtx->tunnelTxNic = NULL;
    ovsFwdCtx->tunnelRxNic = NULL;
}

/*
 * --------------------------------------------------------------------------
 * OvsDoFlowLookupOutput --
 *     Function to be used for the second stage of a tunneling workflow, ie.:
 *     - On the encapsulated packet on Tx path, to do a flow extract, flow
 *       lookup and excuting the actions.
 *     - On the decapsulated packet on Rx path, to do a flow extract, flow
 *       lookup and excuting the actions.
 *
 *     XXX: It is assumed that the NBL in 'ovsFwdCtx' is owned by OVS. This is
 *     until the new buffer management framework is adopted.
 *
 * Side effects:
 *     The NBL in 'ovsFwdCtx' is consumed.
 * --------------------------------------------------------------------------
 */
static __inline NDIS_STATUS
OvsDoFlowLookupOutput(OvsForwardingContext* ovsFwdCtx)
{
    OvsFlowKey key = { 0 };
    OvsFlow *flow = NULL;
    UINT64 hash = 0;
    NDIS_STATUS status = NDIS_STATUS_SUCCESS;
    POVS_VPORT_ENTRY vport =
        OvsFindVportByPortNo(ovsFwdCtx->switchContext, ovsFwdCtx->srcVportNo);
    if (vport == NULL || vport->ovsState != OVS_STATE_CONNECTED) {
        OvsCompleteNBLForwardingCtx(ovsFwdCtx,
            L"OVS-Dropped due to internal/tunnel port removal");
        ovsActionStats.noVport++;
        return NDIS_STATUS_SUCCESS;
    }
    ASSERT(vport->nicState == NdisSwitchNicStateConnected);

    /* Assert that in the Rx direction, key is always setup. */
    ASSERT(ovsFwdCtx->tunnelRxNic == NULL || !OvsIphIsZero(&(ovsFwdCtx->tunKey.dst)));
    status =
        OvsExtractFlow(ovsFwdCtx->curNbl, ovsFwdCtx->srcVportNo,
                       &key, &ovsFwdCtx->layers,
                       !OvsIphIsZero(&(ovsFwdCtx->tunKey.dst))? &(ovsFwdCtx->tunKey):NULL);
    if (status != NDIS_STATUS_SUCCESS) {
        OvsCompleteNBLForwardingCtx(ovsFwdCtx,
                                    L"OVS-Flow extract failed");
        ovsActionStats.failedFlowExtract++;
        return status;
    }

    flow = OvsLookupFlow(&ovsFwdCtx->switchContext->datapath, &key, &hash, FALSE);
    if (flow) {
        OvsFlowUsed(flow, ovsFwdCtx->curNbl, &ovsFwdCtx->layers);
        ovsFwdCtx->switchContext->datapath.hits++;
        status = OvsDoExecuteActions(ovsFwdCtx->switchContext,
                                     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, vport,
                          &key,ovsFwdCtx->curNbl,
                          FALSE, &ovsFwdCtx->layers,
                          ovsFwdCtx->switchContext, &missedPackets, &num);
        if (num) {
            OvsQueuePackets(&missedPackets, num);
        }
        if (status == NDIS_STATUS_SUCCESS) {
            /* Complete the packet since it was copied to user buffer. */
            OvsCompleteNBLForwardingCtx(ovsFwdCtx,
                L"OVS-Dropped since packet was copied to userspace");
            ovsActionStats.flowMiss++;
            status = NDIS_STATUS_SUCCESS;
        } else {
            OvsCompleteNBLForwardingCtx(ovsFwdCtx,
                L"OVS-Dropped due to failure to queue to userspace");
            status = NDIS_STATUS_FAILURE;
            ovsActionStats.failedFlowMiss++;
        }
    }

    return status;
}

/*
 * --------------------------------------------------------------------------
 * OvsTunnelPortTx --
 *     The start function for Tx tunneling - encapsulates the packet, and
 *     outputs the packet on the PIF bridge.
 *
 * Side effects:
 *     The NBL in 'ovsFwdCtx' is consumed.
 * --------------------------------------------------------------------------
 */
static __inline NDIS_STATUS
OvsTunnelPortTx(OvsForwardingContext *ovsFwdCtx)
{
    NDIS_STATUS status = NDIS_STATUS_FAILURE;
    PNET_BUFFER_LIST newNbl = NULL;
    UINT32 srcVportNo;
    NDIS_SWITCH_NIC_INDEX srcNicIndex;
    NDIS_SWITCH_PORT_ID srcPortId;
    POVS_BUFFER_CONTEXT ctx;

    /*
     * Setup the source port to be the internal port to as to facilitate the
     * second OvsLookupFlow.
     */
    if (ovsFwdCtx->switchContext->countInternalVports <= 0 ||
        ovsFwdCtx->switchContext->virtualExternalVport == NULL) {
        OvsClearTunTxCtx(ovsFwdCtx);
        OvsCompleteNBLForwardingCtx(ovsFwdCtx,
            L"OVS-Dropped since either internal or external port is absent");
        return NDIS_STATUS_FAILURE;
    }

    ctx = (POVS_BUFFER_CONTEXT)NET_BUFFER_LIST_CONTEXT_DATA_START(ovsFwdCtx->curNbl);
    if (ctx->mru != 0) {
        OvsDoFragmentNbl(ovsFwdCtx, ctx->mru);
    }
    OVS_FWD_INFO switchFwdInfo = { 0 };

    /* Apply the encapsulation. The encapsulation will 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, &switchFwdInfo);
        break;
    case OVS_VPORT_TYPE_VXLAN:
        status = OvsEncapVxlan(ovsFwdCtx->tunnelTxNic, ovsFwdCtx->curNbl,
                               &ovsFwdCtx->tunKey, ovsFwdCtx->switchContext,
                               &ovsFwdCtx->layers, &newNbl, &switchFwdInfo);
        break;
    case OVS_VPORT_TYPE_STT:
        status = OvsEncapStt(ovsFwdCtx->tunnelTxNic, ovsFwdCtx->curNbl,
                             &ovsFwdCtx->tunKey, ovsFwdCtx->switchContext,
                             &ovsFwdCtx->layers, &newNbl, &switchFwdInfo);
        break;
    case OVS_VPORT_TYPE_GENEVE:
        status = OvsEncapGeneve(ovsFwdCtx->tunnelTxNic, ovsFwdCtx->curNbl,
                                &ovsFwdCtx->tunKey, ovsFwdCtx->switchContext,
                                &ovsFwdCtx->layers, &newNbl, &switchFwdInfo);
        break;
    default:
        ASSERT(! "Tx: Unhandled tunnel type");
    }

    /* Reset the tunnel context so that it doesn't get used after this point. */
    OvsClearTunTxCtx(ovsFwdCtx);

    if (status == NDIS_STATUS_SUCCESS && switchFwdInfo.vport != NULL) {
        ASSERT(newNbl);
        /*
         * Save the 'srcVportNo', 'srcPortId', 'srcNicIndex' so that
         * this can be applied to the new NBL later on.
         */
        srcVportNo = switchFwdInfo.vport->portNo;
        srcPortId = switchFwdInfo.vport->portId;
        srcNicIndex = switchFwdInfo.vport->nicIndex;

        OvsCompleteNBLForwardingCtx(ovsFwdCtx,
                                    L"Complete after cloning NBL for encapsulation");
        status = OvsInitForwardingCtx(ovsFwdCtx, ovsFwdCtx->switchContext,
                                      newNbl, srcVportNo, 0,
                                      NET_BUFFER_LIST_SWITCH_FORWARDING_DETAIL(newNbl),
                                      ovsFwdCtx->completionList,
                                      &ovsFwdCtx->layers, FALSE);
        ovsFwdCtx->curNbl = newNbl;
        /* Update the forwarding detail for the new NBL */
        ovsFwdCtx->fwdDetail->SourcePortId = srcPortId;
        ovsFwdCtx->fwdDetail->SourceNicIndex = srcNicIndex;
        status = OvsDoFlowLookupOutput(ovsFwdCtx);
        ASSERT(ovsFwdCtx->curNbl == NULL);
    } else {
        /*
         * XXX: Temporary freeing of the packet until we register a
         * callback to IP helper.
         */
        OvsCompleteNBLForwardingCtx(ovsFwdCtx,
                                    L"OVS-Dropped due to encap failure");
        ovsActionStats.failedEncap++;
        status = NDIS_STATUS_SUCCESS;
    }

    return status;
}

/*
 * --------------------------------------------------------------------------
 * OvsTunnelPortRx --
 *     Decapsulate the incoming NBL based on the tunnel type and goes through
 *     the flow lookup for the inner packet.
 *
 *     Note: IP checksum is validate here, but L4 checksum validation needs
 *     to be done by the corresponding tunnel types.
 *
 * Side effects:
 *     The NBL in 'ovsFwdCtx' is consumed.
 * --------------------------------------------------------------------------
 */
static __inline NDIS_STATUS
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) {
        ovsActionStats.failedChecksum++;
        OVS_LOG_INFO("Packet dropped due to IP checksum failure.");
        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:
        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;
    case OVS_VPORT_TYPE_GENEVE:
        status = OvsDecapGeneve(ovsFwdCtx->switchContext, ovsFwdCtx->curNbl,
                                &ovsFwdCtx->tunKey, &newNbl);
        break;
    default:
        OVS_LOG_ERROR("Rx: Unhandled tunnel type: %d\n",
                      tunnelRxVport->ovsType);
        ASSERT(! "Rx: Unhandled tunnel type");
        status = NDIS_STATUS_NOT_SUPPORTED;
    }

    if (status != NDIS_STATUS_SUCCESS) {
        ovsActionStats.failedDecap++;
        goto dropNbl;
    }

    /*
     * tunnelRxNic and other fields will be cleared, re-init the context
     * before usage.
      */
    OvsCompleteNBLForwardingCtx(ovsFwdCtx, dropReason);

    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;

        status = OvsDoFlowLookupOutput(ovsFwdCtx);
    }
    ASSERT(ovsFwdCtx->curNbl == NULL);
    OvsClearTunRxCtx(ovsFwdCtx);

    return status;

dropNbl:
    OvsCompleteNBLForwardingCtx(ovsFwdCtx,
            L"OVS-dropped due to decap failure");
    OvsClearTunRxCtx(ovsFwdCtx);
    return status;
}


/*
 * --------------------------------------------------------------------------
 * OvsOutputForwardingCtx --
 *     This function outputs an NBL to NDIS or to a tunneling pipeline based on
 *     the ports added so far into 'ovsFwdCtx'.
 *
 * Side effects:
 *     This function consumes the NBL - either by forwarding it successfully to
 *     NDIS, or adding it to the completion list in 'ovsFwdCtx', or freeing it.
 *
 *     Also makes sure that the list of destination ports - tunnel or otherwise is
 *     drained.
 * --------------------------------------------------------------------------
 */
static __inline NDIS_STATUS
OvsOutputForwardingCtx(OvsForwardingContext *ovsFwdCtx)
{
    NDIS_STATUS status = STATUS_SUCCESS;
    POVS_SWITCH_CONTEXT switchContext = ovsFwdCtx->switchContext;
    PCWSTR dropReason;
    POVS_BUFFER_CONTEXT ctx;

    /*
     * Handle the case where the some of the destination ports are tunneled
     * ports - the non-tunneled ports get a unmodified copy of the NBL, and the
     * tunneling pipeline starts when we output the packet to tunneled port.
     */
    if (ovsFwdCtx->destPortsSizeOut > 0) {
        PNET_BUFFER_LIST newNbl = NULL;
        PNET_BUFFER nb;
        UINT32 portsToUpdate =
            ovsFwdCtx->fwdDetail->NumAvailableDestinations -
            (ovsFwdCtx->destPortsSizeIn - ovsFwdCtx->destPortsSizeOut);

        ASSERT(ovsFwdCtx->destinationPorts != NULL);

        /*
         * Create a copy of the packet in order to do encap on it later. Also,
         * don't copy the offload context since the encap'd packet has a
         * different set of headers. This will change when we implement offloads
         * before doing encapsulation.
         */
        if (ovsFwdCtx->tunnelTxNic != NULL || ovsFwdCtx->tunnelRxNic != NULL) {
            POVS_BUFFER_CONTEXT oldCtx, newCtx;
            nb = NET_BUFFER_LIST_FIRST_NB(ovsFwdCtx->curNbl);
            oldCtx = (POVS_BUFFER_CONTEXT)
                NET_BUFFER_LIST_CONTEXT_DATA_START(ovsFwdCtx->curNbl);
            newNbl = OvsPartialCopyNBL(ovsFwdCtx->switchContext,
                                       ovsFwdCtx->curNbl,
                                       0, 0, TRUE /*copy NBL info*/);
            if (newNbl == NULL) {
                status = NDIS_STATUS_RESOURCES;
                ovsActionStats.noCopiedNbl++;
                dropReason = L"Dropped due to failure to create NBL copy.";
                goto dropit;
            }
            newCtx = (POVS_BUFFER_CONTEXT)
                NET_BUFFER_LIST_CONTEXT_DATA_START(newNbl);
            newCtx->mru = oldCtx->mru;
        }

        /* It does not seem like we'll get here unless 'portsToUpdate' > 0. */
        ASSERT(portsToUpdate > 0);
        status = switchContext->NdisSwitchHandlers.UpdateNetBufferListDestinations(
            switchContext->NdisSwitchContext, ovsFwdCtx->curNbl,
            portsToUpdate, ovsFwdCtx->destinationPorts);
        if (status != NDIS_STATUS_SUCCESS) {
            OvsCompleteNBL(ovsFwdCtx->switchContext, newNbl, TRUE);
            ovsActionStats.cannotGrowDest++;
            dropReason = L"Dropped due to failure to update destinations.";
            goto dropit;
        }

        ctx = (POVS_BUFFER_CONTEXT)NET_BUFFER_LIST_CONTEXT_DATA_START(ovsFwdCtx->curNbl);
        if (ctx->mru != 0) {
            OvsDoFragmentNbl(ovsFwdCtx, ctx->mru);
        }

        OvsSendNBLIngress(ovsFwdCtx->switchContext, ovsFwdCtx->curNbl,
                          ovsFwdCtx->sendFlags);
        /* End this pipeline by resetting the corresponding context. */
        ovsFwdCtx->destPortsSizeOut = 0;
        ovsFwdCtx->curNbl = NULL;
        if (newNbl) {
            status = OvsInitForwardingCtx(ovsFwdCtx, ovsFwdCtx->switchContext,
                                          newNbl, ovsFwdCtx->srcVportNo, 0,
                                          NET_BUFFER_LIST_SWITCH_FORWARDING_DETAIL(newNbl),
                                          ovsFwdCtx->completionList,
                                          &ovsFwdCtx->layers, FALSE);
            if (status != NDIS_STATUS_SUCCESS) {
                dropReason = L"Dropped due to resources.";
                goto dropit;
            }
        }
    }

    if (ovsFwdCtx->tunnelTxNic != NULL) {
        status = OvsTunnelPortTx(ovsFwdCtx);
        ASSERT(ovsFwdCtx->tunnelTxNic == NULL);
        ASSERT(OvsIphIsZero(&(ovsFwdCtx->tunKey.dst)));
    } else if (ovsFwdCtx->tunnelRxNic != NULL) {
        status = OvsTunnelPortRx(ovsFwdCtx);
        ASSERT(ovsFwdCtx->tunnelRxNic == NULL);
        ASSERT(OvsIphIsZero(&(ovsFwdCtx->tunKey.dst)));
    }
    ASSERT(ovsFwdCtx->curNbl == NULL);

    return status;

dropit:
    if (status != NDIS_STATUS_SUCCESS) {
        OvsCompleteNBLForwardingCtx(ovsFwdCtx, dropReason);
    }

    return status;
}


/*
 * --------------------------------------------------------------------------
 * OvsLookupFlowOutput --
 *     Utility function for external callers to do flow extract, lookup,
 *     actions execute on a given NBL.
 *
 *     Note: If this is being used from a callback function, make sure that the
 *     arguments specified are still valid in the asynchronous context.
 *
 * Side effects:
 *     This function consumes the NBL.
 * --------------------------------------------------------------------------
 */
VOID
OvsLookupFlowOutput(POVS_SWITCH_CONTEXT switchContext,
                    VOID *compList,
                    PNET_BUFFER_LIST curNbl,
                    POVS_VPORT_ENTRY internalVport)
{
    NDIS_STATUS status;
    OvsForwardingContext ovsFwdCtx;

    /* XXX: make sure comp list was not a stack variable previously. */
    OvsCompletionList *completionList = (OvsCompletionList *)compList;

    /*
     * XXX: can internal port disappear while we are busy doing ARP resolution?
     * It could, but will we get this callback from IP helper in that case. Need
     * to check.
     */
    ASSERT(switchContext->countInternalVports > 0);
    status = OvsInitForwardingCtx(&ovsFwdCtx, switchContext, curNbl,
                                  internalVport->portNo, 0,
                                  NET_BUFFER_LIST_SWITCH_FORWARDING_DETAIL(curNbl),
                                  completionList, NULL, TRUE);
    if (status != NDIS_STATUS_SUCCESS) {
        OvsCompleteNBLForwardingCtx(&ovsFwdCtx,
                                    L"OVS-Dropped due to resources");
        return;
    }

    ASSERT(FALSE);
    /*
     * XXX: We need to acquire the dispatch lock and the datapath lock.
     */

    OvsDoFlowLookupOutput(&ovsFwdCtx);
}


/*
 * --------------------------------------------------------------------------
 * OvsOutputBeforeSetAction --
 *     Function to be called to complete one set of actions on an NBL, before
 *     we start the next one.
 * --------------------------------------------------------------------------
 */
static __inline NDIS_STATUS
OvsOutputBeforeSetAction(OvsForwardingContext *ovsFwdCtx)
{
    PNET_BUFFER_LIST newNbl;
    NDIS_STATUS status;

    /*
     * Create a copy and work on the copy after this point. The original NBL is
     * forwarded. One reason to not use the copy for forwarding is that
     * ports have already been added to the original NBL, and it might be
     * inefficient/impossible to remove/re-add them to the copy. There's no
     * notion of removing the ports, the ports need to be marked as
     * "isExcluded". There's seems no real advantage to retaining the original
     * and sending out the copy instead.
     *
     * XXX: We are copying the offload context here. This is to handle actions
     * such as:
     * outport, pop_vlan(), outport, push_vlan(), outport
     *
     * copy size needs to include inner ether + IP + TCP, need to revisit
     * if we support IP options.
     * XXX Head room needs to include the additional encap.
     * XXX copySize check is not considering multiple NBs.
     */
    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 and save the original source port number */
    UINT32 tempVportNo = ovsFwdCtx->srcVportNo;
    status = OvsOutputForwardingCtx(ovsFwdCtx);
    ASSERT(ovsFwdCtx->curNbl == NULL);
    ASSERT(ovsFwdCtx->destPortsSizeOut == 0);
    ASSERT(ovsFwdCtx->tunnelRxNic == NULL);
    ASSERT(ovsFwdCtx->tunnelTxNic == NULL);

    /* If we didn't make a copy, can't continue. */
    if (newNbl == NULL) {
        ovsActionStats.noCopiedNbl++;
        return NDIS_STATUS_RESOURCES;
    }

    /* Finish the remaining actions with the new NBL */
    if (status != NDIS_STATUS_SUCCESS) {
        OvsCompleteNBL(ovsFwdCtx->switchContext, newNbl, TRUE);
    } else {
        status = OvsInitForwardingCtx(ovsFwdCtx, ovsFwdCtx->switchContext,
                                      newNbl, tempVportNo, 0,
                                      NET_BUFFER_LIST_SWITCH_FORWARDING_DETAIL(newNbl),
                                      ovsFwdCtx->completionList,
                                      &ovsFwdCtx->layers, FALSE);
    }

    return status;
}


/*
 * --------------------------------------------------------------------------
 * 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
OvsPopFieldInPacketBuf(OvsForwardingContext *ovsFwdCtx,
                       UINT32 shiftOffset,
                       UINT32 shiftLength,
                       PUINT8 *bufferData)
{
    PNET_BUFFER curNb;
    PMDL curMdl;
    PUINT8 bufferStart;
    UINT32 packetLen, mdlLen;
    PNET_BUFFER_LIST newNbl;
    NDIS_STATUS status;

    newNbl = OvsPartialCopyNBL(ovsFwdCtx->switchContext, ovsFwdCtx->curNbl,
                               0, 0, TRUE /* copy NBL info */);
    if (!newNbl) {
        ovsActionStats.noCopiedNbl++;
        return NDIS_STATUS_RESOURCES;
    }

    /* 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,
                                  NET_BUFFER_LIST_SWITCH_FORWARDING_DETAIL(newNbl),
                                  NULL, &ovsFwdCtx->layers, FALSE);
    if (status != NDIS_STATUS_SUCCESS) {
        OvsCompleteNBLForwardingCtx(ovsFwdCtx,
                                    L"Dropped due to resouces");
        return NDIS_STATUS_RESOURCES;
    }

    curNb = NET_BUFFER_LIST_FIRST_NB(ovsFwdCtx->curNbl);
    packetLen = NET_BUFFER_DATA_LENGTH(curNb);
    ASSERT(curNb->Next == NULL);
    curMdl = NET_BUFFER_CURRENT_MDL(curNb);
    NdisQueryMdl(curMdl, &bufferStart, &mdlLen, LowPagePriority);
    if (!bufferStart) {
        return NDIS_STATUS_RESOURCES;
    }
    mdlLen -= NET_BUFFER_CURRENT_MDL_OFFSET(curNb);
    /* 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);
    /* XXX At the momemnt !bufferData means it should be treated as VLAN. We
     * should split the function and refactor. */
    if (!bufferData) {
        EthHdr *ethHdr = (EthHdr *)bufferStart;
        if (ethHdr->Type != ETH_TYPE_802_1PQ_NBO) {
            OVS_LOG_ERROR("Invalid ethHdr type %u, nbl %p", ethHdr->Type, ovsFwdCtx->curNbl);
            return NDIS_STATUS_INVALID_PACKET;
        }
    }
    RtlMoveMemory(bufferStart + shiftLength, bufferStart, 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)
{
    NDIS_STATUS status;
    OVS_PACKET_HDR_INFO* layers = &ovsFwdCtx->layers;

    /*
     * 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);

    status = OvsPopFieldInPacketBuf(ovsFwdCtx, shiftOffset, shiftLength,
                                    NULL);

    if (status == NDIS_STATUS_SUCCESS) {
        layers->l3Offset -= (UINT16) shiftLength;
        layers->l4Offset -= (UINT16) shiftLength;
    }

    return status;
}


/*
 * --------------------------------------------------------------------------
 * 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;
    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);
    ASSERT(ethHdr);
    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;
}

/*
 *----------------------------------------------------------------------------
 * OvsUpdateEthHeader --
 *      Updates the ethernet header in ovsFwdCtx.curNbl inline based on the
 *      specified key.
 *----------------------------------------------------------------------------
 */
static __inline NDIS_STATUS
OvsUpdateEthHeader(OvsForwardingContext *ovsFwdCtx,
                   OvsFlowKey *key,
                   const struct ovs_key_ethernet *ethAttr)
{
    PNET_BUFFER curNb;
    PMDL curMdl;
    PUINT8 bufferStart;
    EthHdr *ethHdr;
    UINT32 packetLen, mdlLen;

    curNb = NET_BUFFER_LIST_FIRST_NB(ovsFwdCtx->curNbl);
    ASSERT(curNb->Next == NULL);
    packetLen = NET_BUFFER_DATA_LENGTH(curNb);
    curMdl = NET_BUFFER_CURRENT_MDL(curNb);
    NdisQueryMdl(curMdl, &bufferStart, &mdlLen, LowPagePriority);
    if (!bufferStart) {
        ovsActionStats.noResource++;
        return NDIS_STATUS_RESOURCES;
    }
    mdlLen -= NET_BUFFER_CURRENT_MDL_OFFSET(curNb);
    ASSERT(mdlLen > 0);
    /* Bail out if the L2 header is not in a contiguous buffer. */
    if (MIN(packetLen, mdlLen) < sizeof *ethHdr) {
        ASSERT(FALSE);
        return NDIS_STATUS_FAILURE;
    }
    ethHdr = (EthHdr *)(bufferStart + NET_BUFFER_CURRENT_MDL_OFFSET(curNb));

    RtlCopyMemory(ethHdr->Destination, ethAttr->eth_dst, ETH_ADDR_LENGTH);
    RtlCopyMemory(ethHdr->Source, ethAttr->eth_src, ETH_ADDR_LENGTH);
    /* Update l2 flow key */
    RtlCopyMemory(key->l2.dlDst, ethAttr->eth_dst, ETH_ADDR_LENGTH);
    RtlCopyMemory(key->l2.dlSrc, ethAttr->eth_src, ETH_ADDR_LENGTH);

    return NDIS_STATUS_SUCCESS;
}

/*
 *----------------------------------------------------------------------------
 * OvsGetHeaderBySize --
 *      Tries to retrieve a continuous buffer from 'ovsFwdCtx->curnbl' of size
 *      'size'.
 *      If the original buffer is insufficient it will, try to clone the net
 *      buffer list and force the size.
 *      Returns 'NULL' on failure or a pointer to the first byte of the data
 *      in the first net buffer of the net buffer list 'nbl'.
 *----------------------------------------------------------------------------
 */
PUINT8 OvsGetHeaderBySize(OvsForwardingContext *ovsFwdCtx,
                          UINT32 size)
{
    PNET_BUFFER curNb;
    UINT32 mdlLen, packetLen;
    PMDL curMdl;
    ULONG curMdlOffset;
    PUINT8 start;

    curNb = NET_BUFFER_LIST_FIRST_NB(ovsFwdCtx->curNbl);
    ASSERT(curNb->Next == NULL);
    packetLen = NET_BUFFER_DATA_LENGTH(curNb);
    curMdl = NET_BUFFER_CURRENT_MDL(curNb);
    NdisQueryMdl(curMdl, &start, &mdlLen, LowPagePriority);
    if (!start) {
        ovsActionStats.noResource++;
        return NULL;
    }

    curMdlOffset = NET_BUFFER_CURRENT_MDL_OFFSET(curNb);
    mdlLen -= curMdlOffset;
    ASSERT((INT)mdlLen >= 0);

    /* Count of number of bytes of valid data there are in the first MDL. */
    mdlLen = MIN(packetLen, mdlLen);
    if (mdlLen < size) {
        PNET_BUFFER_LIST newNbl;
        NDIS_STATUS status;
        newNbl = OvsPartialCopyNBL(ovsFwdCtx->switchContext, ovsFwdCtx->curNbl,
                                   size, 0, TRUE /*copy NBL info*/);
        if (!newNbl) {
            ovsActionStats.noCopiedNbl++;
            return NULL;
        }
        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 NULL;
        }

        curNb = NET_BUFFER_LIST_FIRST_NB(ovsFwdCtx->curNbl);
        ASSERT(curNb->Next == NULL);
        curMdl = NET_BUFFER_CURRENT_MDL(curNb);
        NdisQueryMdl(curMdl, &start, &mdlLen, LowPagePriority);
        if (!curMdl) {
            ovsActionStats.noResource++;
            return NULL;
        }
        curMdlOffset = NET_BUFFER_CURRENT_MDL_OFFSET(curNb);
        mdlLen -= curMdlOffset;
        ASSERT(mdlLen >= size);
    }

    return start + curMdlOffset;
}

/*
 *----------------------------------------------------------------------------
 * OvsUpdateUdpPorts --
 *      Updates the UDP source or destination port in ovsFwdCtx.curNbl inline
 *      based on the specified key.
 *----------------------------------------------------------------------------
 */
NDIS_STATUS
OvsUpdateUdpPorts(OvsForwardingContext *ovsFwdCtx,
                  OvsFlowKey *key,
                  const struct ovs_key_udp *udpAttr)
{
    PUINT8 bufferStart;
    OVS_PACKET_HDR_INFO *layers = &ovsFwdCtx->layers;
    UDPHdr *udpHdr = NULL;

    ASSERT(layers->value != 0);

    if (!layers->isUdp) {
        ovsActionStats.noCopiedNbl++;
        return NDIS_STATUS_FAILURE;
    }

    bufferStart = OvsGetHeaderBySize(ovsFwdCtx, layers->l7Offset);
    if (!bufferStart) {
        return NDIS_STATUS_RESOURCES;
    }

    udpHdr = (UDPHdr *)(bufferStart + layers->l4Offset);
    if (udpHdr->check) {
        if (udpHdr->source != udpAttr->udp_src) {
            udpHdr->check = ChecksumUpdate16(udpHdr->check, udpHdr->source,
                                             udpAttr->udp_src);
            udpHdr->source = udpAttr->udp_src;
            key->ipKey.l4.tpSrc = udpAttr->udp_src;
        }
        if (udpHdr->dest != udpAttr->udp_dst) {
            udpHdr->check = ChecksumUpdate16(udpHdr->check, udpHdr->dest,
                                             udpAttr->udp_dst);
            udpHdr->dest = udpAttr->udp_dst;
            key->ipKey.l4.tpDst = udpAttr->udp_dst;
        }
    } else {
        udpHdr->source = udpAttr->udp_src;
        key->ipKey.l4.tpSrc = udpAttr->udp_src;
        udpHdr->dest = udpAttr->udp_dst;
        key->ipKey.l4.tpDst = udpAttr->udp_dst;
    }

    return NDIS_STATUS_SUCCESS;
}

/*
 *----------------------------------------------------------------------------
 * OvsUpdateTcpPorts --
 *      Updates the TCP source or destination port in ovsFwdCtx.curNbl inline
 *      based on the specified key.
 *----------------------------------------------------------------------------
 */
NDIS_STATUS
OvsUpdateTcpPorts(OvsForwardingContext *ovsFwdCtx,
                  OvsFlowKey *key,
                  const struct ovs_key_tcp *tcpAttr)
{
    PUINT8 bufferStart;
    OVS_PACKET_HDR_INFO *layers = &ovsFwdCtx->layers;
    TCPHdr *tcpHdr = NULL;

    ASSERT(layers->value != 0);

    if (!layers->isTcp) {
        ovsActionStats.noCopiedNbl++;
        return NDIS_STATUS_FAILURE;
    }

    bufferStart = OvsGetHeaderBySize(ovsFwdCtx, layers->l7Offset);
    if (!bufferStart) {
        return NDIS_STATUS_RESOURCES;
    }

    tcpHdr = (TCPHdr *)(bufferStart + layers->l4Offset);

    if (tcpHdr->source != tcpAttr->tcp_src) {
        tcpHdr->check = ChecksumUpdate16(tcpHdr->check, tcpHdr->source,
                                         tcpAttr->tcp_src);
        tcpHdr->source = tcpAttr->tcp_src;
        key->ipKey.l4.tpSrc = tcpAttr->tcp_src;
    }
    if (tcpHdr->dest != tcpAttr->tcp_dst) {
        tcpHdr->check = ChecksumUpdate16(tcpHdr->check, tcpHdr->dest,
                                         tcpAttr->tcp_dst);
        tcpHdr->dest = tcpAttr->tcp_dst;
        key->ipKey.l4.tpDst = tcpAttr->tcp_dst;
    }

    return NDIS_STATUS_SUCCESS;
}

/*
 *----------------------------------------------------------------------------
 * OvsUpdateAddressAndPort --
 *      Updates the source/destination IP and port fields in
 *      ovsFwdCtx.curNbl inline based on the specified key.
 *----------------------------------------------------------------------------
 */
NDIS_STATUS
OvsUpdateAddressAndPort(OvsForwardingContext *ovsFwdCtx,
                        UINT32 newAddr, UINT16 newPort,
                        BOOLEAN isSource, BOOLEAN isTx)
{
    PUINT8 bufferStart;
    UINT32 hdrSize;
    OVS_PACKET_HDR_INFO *layers = &ovsFwdCtx->layers;
    IPHdr *ipHdr;
    TCPHdr *tcpHdr = NULL;
    UDPHdr *udpHdr = NULL;
    UINT32 *addrField = NULL;
    UINT16 *portField = NULL;
    UINT16 *checkField = NULL;
    BOOLEAN l4Offload = FALSE;
    NDIS_TCP_IP_CHECKSUM_NET_BUFFER_LIST_INFO csumInfo;
    UINT16  preNatPseudoChecksum = 0;
    BOOLEAN preservePseudoChecksum = FALSE;

    ASSERT(layers->value != 0);

    if (layers->isTcp || layers->isUdp) {
        hdrSize = layers->l4Offset +
                  layers->isTcp ? sizeof (*tcpHdr) : sizeof (*udpHdr);
    } else {
        hdrSize = layers->l3Offset + sizeof (*ipHdr);
    }

    bufferStart = OvsGetHeaderBySize(ovsFwdCtx, hdrSize);
    if (!bufferStart) {
        return NDIS_STATUS_RESOURCES;
    }

    ipHdr = (IPHdr *)(bufferStart + layers->l3Offset);

    if (layers->isTcp) {
        tcpHdr = (TCPHdr *)(bufferStart + layers->l4Offset);
    } else if (layers->isUdp) {
        udpHdr = (UDPHdr *)(bufferStart + layers->l4Offset);
    }

    csumInfo.Value = NET_BUFFER_LIST_INFO(ovsFwdCtx->curNbl,
                                          TcpIpChecksumNetBufferListInfo);

    /*
     * Adjust the IP header inline as dictated by the action, and also update
     * the IP and the TCP checksum for the data modified.
     *
     * In the future, this could be optimized to make one call to
     * ChecksumUpdate32(). Ignoring this for now, since for the most common
     * case, we only update the TTL.
     */
     /*Only tx direction the checksum value will be reset to be PseudoChecksum*/
    if (!isTx) {
        preNatPseudoChecksum = IPPseudoChecksum(&ipHdr->saddr, &ipHdr->daddr,
            tcpHdr ? IPPROTO_TCP : IPPROTO_UDP,
            ntohs(ipHdr->tot_len) - ipHdr->ihl * 4);
    }

    if (isSource) {
        addrField = &ipHdr->saddr;
        if (tcpHdr) {
            portField = &tcpHdr->source;
            checkField = &tcpHdr->check;
            l4Offload = isTx ? (BOOLEAN)csumInfo.Transmit.TcpChecksum :
                        ((BOOLEAN)csumInfo.Receive.TcpChecksumSucceeded ||
                         (BOOLEAN)csumInfo.Receive.TcpChecksumFailed);
        } else if (udpHdr) {
            portField = &udpHdr->source;
            checkField = &udpHdr->check;
            l4Offload = isTx ? (BOOLEAN)csumInfo.Transmit.UdpChecksum :
                        ((BOOLEAN)csumInfo.Receive.UdpChecksumSucceeded ||
                         (BOOLEAN)csumInfo.Receive.UdpChecksumFailed);
        }
        if (!isTx && l4Offload) {
            if (*checkField == preNatPseudoChecksum) {
                preservePseudoChecksum = TRUE;
            }
        }
        if (isTx && l4Offload || preservePseudoChecksum) {
            *checkField = IPPseudoChecksum(&newAddr, &ipHdr->daddr,
                tcpHdr ? IPPROTO_TCP : IPPROTO_UDP,
                ntohs(ipHdr->tot_len) - ipHdr->ihl * 4);
        }
    } else {
        addrField = &ipHdr->daddr;
        if (tcpHdr) {
            portField = &tcpHdr->dest;
            checkField = &tcpHdr->check;
            l4Offload = isTx ? (BOOLEAN)csumInfo.Transmit.TcpChecksum :
                        ((BOOLEAN)csumInfo.Receive.TcpChecksumSucceeded ||
                         (BOOLEAN)csumInfo.Receive.TcpChecksumFailed);
        } else if (udpHdr) {
            portField = &udpHdr->dest;
            checkField = &udpHdr->check;
            l4Offload = isTx ? (BOOLEAN)csumInfo.Transmit.UdpChecksum :
                        ((BOOLEAN)csumInfo.Receive.UdpChecksumSucceeded ||
                         (BOOLEAN)csumInfo.Receive.UdpChecksumFailed);
        }
        if (!isTx && l4Offload) {
            if (*checkField == preNatPseudoChecksum) {
                preservePseudoChecksum = TRUE;
            }
        }

        if (isTx && l4Offload || preservePseudoChecksum) {
            *checkField = IPPseudoChecksum(&ipHdr->saddr, &newAddr,
                tcpHdr ? IPPROTO_TCP : IPPROTO_UDP,
                ntohs(ipHdr->tot_len) - ipHdr->ihl * 4);
        }
    }

    if (*addrField != newAddr) {
        UINT32 oldAddr = *addrField;
        if ((checkField && *checkField != 0) &&
            (!l4Offload || (!isTx && !preservePseudoChecksum))) {
            /* Recompute total checksum. */
            *checkField = ChecksumUpdate32(*checkField, oldAddr,
                                            newAddr);
        }
        if (ipHdr->check != 0) {
            ipHdr->check = ChecksumUpdate32(ipHdr->check, oldAddr,
                                            newAddr);
        }

        *addrField = newAddr;
    }

    if (portField && *portField != newPort) {
        if ((checkField) &&
            (!l4Offload || (!isTx && !preservePseudoChecksum))) {
            /* Recompute total checksum. */
            *checkField = ChecksumUpdate16(*checkField, *portField,
                                           newPort);
        }
        *portField = newPort;
    }
    return NDIS_STATUS_SUCCESS;
}

UINT16
OvsCalculateICMPv6Checksum(struct in6_addr srcAddr,
                        struct in6_addr dstAddr,
                        uint16_t totalLength,
                        uint16_t protocol,
                        uint16_t *icmpStart,
                        uint16_t length)
{
    uint32_t checkSum = 0;
    uint16_t *srcAddressPtr = (uint16_t *)&srcAddr;
    uint16_t *dstAddressPtr = (uint16_t *)&dstAddr;
    uint16_t *value = (uint16_t *)icmpStart;
    int index = 0;

    checkSum = totalLength + protocol;

    for (int i = 0; i < 8; i++) {
        checkSum += ntohs(srcAddressPtr[i]);
        checkSum += ntohs(dstAddressPtr[i]);
    }

    for (index = length; index > 1; index -= 2) {
        checkSum += ntohs(*value);
        value++;
    }

    if (index > 0) {
        checkSum += (uint16_t)(*((uint8_t *)value));
    }

    while ((checkSum >> 16) & 0xffff) {
        checkSum = (checkSum & 0xffff) + ((checkSum >> 16) & 0xffff);
    }

    return htons(~((uint16_t)checkSum));
}

/*
 *-----------------------------------------------------------------------------
 *
 * OvsUpdateAddressAndPortForIpv6--
 *
 *      Update ipv6 address in ovsFwdCtx.curNbl.
 *
 * Results:
 *      None
 *
 * Side effects:
 *      None
 *
 *-----------------------------------------------------------------------------
 */
NDIS_STATUS
OvsUpdateAddressAndPortForIpv6(OvsForwardingContext *ovsFwdCtx,
                               struct in6_addr newAddr, UINT16 newPort,
                               BOOLEAN isSource, BOOLEAN isTx)
{
    PUINT8 bufferStart;
    UINT32 hdrSize;
    OVS_PACKET_HDR_INFO *layers = &ovsFwdCtx->layers;
    IPv6Hdr *ipHdr;
    TCPHdr *tcpHdr = NULL;
    UDPHdr *udpHdr = NULL;
    struct in6_addr *addrField = NULL;
    UINT16 *portField = NULL;
    UINT16 *checkField = NULL;
    BOOLEAN l4Offload = FALSE;
    NDIS_TCP_IP_CHECKSUM_NET_BUFFER_LIST_INFO csumInfo;

    ASSERT(layers->value != 0);

    if (layers->isTcp || layers->isUdp) {
        hdrSize = layers->l4Offset +
                  layers->isTcp ? sizeof (*tcpHdr) : sizeof (*udpHdr);
    } else {
        hdrSize = layers->l3Offset + sizeof (*ipHdr);
    }

    csumInfo.Value = NET_BUFFER_LIST_INFO(ovsFwdCtx->curNbl,
                                          TcpIpChecksumNetBufferListInfo);

    bufferStart = OvsGetHeaderBySize(ovsFwdCtx, hdrSize);
    if (!bufferStart) {
        return NDIS_STATUS_RESOURCES;
    }

    ipHdr = (IPv6Hdr *)(bufferStart + layers->l3Offset);

    if (layers->isTcp) {
        tcpHdr = (TCPHdr *)(bufferStart + layers->l4Offset);
    } else if (layers->isUdp) {
        udpHdr = (UDPHdr *)(bufferStart + layers->l4Offset);
    }

    if (isSource) {
        addrField = &ipHdr->saddr;
        if (tcpHdr) {
            portField = &tcpHdr->source;
            checkField = &tcpHdr->check;
            l4Offload = isTx ? (BOOLEAN)csumInfo.Transmit.TcpChecksum :
                        ((BOOLEAN)csumInfo.Receive.TcpChecksumSucceeded ||
                         (BOOLEAN)csumInfo.Receive.TcpChecksumFailed);
        } else if (udpHdr) {
            portField = &udpHdr->source;
            checkField = &udpHdr->check;
            l4Offload = isTx ? (BOOLEAN)csumInfo.Transmit.UdpChecksum :
                        ((BOOLEAN)csumInfo.Receive.UdpChecksumSucceeded ||
                         (BOOLEAN)csumInfo.Receive.UdpChecksumFailed);
        }
        if (isTx && l4Offload) {
            *checkField = IPv6PseudoChecksum((UINT32 *)&newAddr, (UINT32 *)&ipHdr->daddr,
                                             tcpHdr ? IPPROTO_TCP : IPPROTO_UDP,
                                             ntohs(ipHdr->payload_len) -
                                                  (ovsFwdCtx->layers.l4Offset - ovsFwdCtx->layers.l3Offset));
        }
    } else {
        addrField = &ipHdr->daddr;
        if (tcpHdr) {
            portField = &tcpHdr->dest;
            checkField = &tcpHdr->check;
            l4Offload = isTx ? (BOOLEAN)csumInfo.Transmit.TcpChecksum :
                        ((BOOLEAN)csumInfo.Receive.TcpChecksumSucceeded ||
                         (BOOLEAN)csumInfo.Receive.TcpChecksumFailed);
        } else if (udpHdr) {
            portField = &udpHdr->dest;
            checkField = &udpHdr->check;
            l4Offload = isTx ? (BOOLEAN)csumInfo.Transmit.UdpChecksum :
                        ((BOOLEAN)csumInfo.Receive.UdpChecksumSucceeded ||
                         (BOOLEAN)csumInfo.Receive.UdpChecksumFailed);
        }

        if (isTx && l4Offload) {
            *checkField = IPv6PseudoChecksum((UINT32 *)&ipHdr->saddr, (UINT32 *)&newAddr,
                                             tcpHdr ? IPPROTO_TCP : IPPROTO_UDP,
                                             ntohs(ipHdr->payload_len) -
                                             (ovsFwdCtx->layers.l4Offset - ovsFwdCtx->layers.l3Offset));
        }
    }

    if (memcmp(addrField, &newAddr, sizeof(struct in6_addr))) {
        if ((checkField && *checkField != 0) && (!l4Offload || !isTx)) {
            uint32_t *oldField = (uint32_t *)addrField;
            uint32_t *newField = (uint32_t *)&newAddr;
            *checkField = ChecksumUpdate32(*checkField, oldField[0], newField[0]);
            *checkField = ChecksumUpdate32(*checkField, oldField[1], newField[1]);
            *checkField = ChecksumUpdate32(*checkField, oldField[2], newField[2]);
            *checkField = ChecksumUpdate32(*checkField, oldField[3], newField[3]);
        }

        *addrField = newAddr;

        if (layers->isIcmp) {
            ICMPHdr *icmp =(ICMPHdr *)(bufferStart + layers->l4Offset);
            icmp->checksum = 0x00;
            icmp->checksum = OvsCalculateICMPv6Checksum(ipHdr->saddr, ipHdr->daddr,
                                                        ntohs(ipHdr->payload_len),
                                                        IPPROTO_ICMPV6,
                                                        (uint16_t *)icmp,
                                                        ntohs(ipHdr->payload_len));
        }
    }

    if (portField && *portField != newPort) {
        if ((checkField) && (!l4Offload || !isTx)) {
            /* Recompute total checksum. */
            *checkField = ChecksumUpdate16(*checkField, *portField,
                                           newPort);
        }
        *portField = newPort;
    }

    return NDIS_STATUS_SUCCESS;
}

/*
 *----------------------------------------------------------------------------
 * OvsUpdateIPv4Header --
 *      Updates the IPv4 header in ovsFwdCtx.curNbl inline based on the
 *      specified key.
 *----------------------------------------------------------------------------
 */
NDIS_STATUS
OvsUpdateIPv4Header(OvsForwardingContext *ovsFwdCtx,
                    OvsFlowKey *key,
                    const struct ovs_key_ipv4 *ipAttr)
{
    PUINT8 bufferStart;
    UINT32 hdrSize;
    OVS_PACKET_HDR_INFO *layers = &ovsFwdCtx->layers;
    IPHdr *ipHdr;
    TCPHdr *tcpHdr = NULL;
    UDPHdr *udpHdr = NULL;

    ASSERT(layers->value != 0);

    if (layers->isTcp || layers->isUdp) {
        hdrSize = layers->l4Offset +
                  layers->isTcp ? sizeof (*tcpHdr) : sizeof (*udpHdr);
    } else {
        hdrSize = layers->l3Offset + sizeof (*ipHdr);
    }

    bufferStart = OvsGetHeaderBySize(ovsFwdCtx, hdrSize);
    if (!bufferStart) {
        return NDIS_STATUS_RESOURCES;
    }

    ipHdr = (IPHdr *)(bufferStart + layers->l3Offset);

    if (layers->isTcp) {
        tcpHdr = (TCPHdr *)(bufferStart + layers->l4Offset);
    } else if (layers->isUdp) {
        udpHdr = (UDPHdr *)(bufferStart + layers->l4Offset);
    }

    /*
     * Adjust the IP header inline as dictated by the action, and also update
     * the IP and the TCP checksum for the data modified.
     *
     * In the future, this could be optimized to make one call to
     * ChecksumUpdate32(). Ignoring this for now, since for the most common
     * case, we only update the TTL.
     */
    if (ipHdr->saddr != ipAttr->ipv4_src) {
        if (tcpHdr) {
            tcpHdr->check = ChecksumUpdate32(tcpHdr->check, ipHdr->saddr,
                                             ipAttr->ipv4_src);
        } else if (udpHdr && udpHdr->check) {
            udpHdr->check = ChecksumUpdate32(udpHdr->check, ipHdr->saddr,
                                             ipAttr->ipv4_src);
        }

        if (ipHdr->check != 0) {
            ipHdr->check = ChecksumUpdate32(ipHdr->check, ipHdr->saddr,
                                            ipAttr->ipv4_src);
        }
        ipHdr->saddr = ipAttr->ipv4_src;
        key->ipKey.nwSrc = ipAttr->ipv4_src;
    }
    if (ipHdr->daddr != ipAttr->ipv4_dst) {
        if (tcpHdr) {
            tcpHdr->check = ChecksumUpdate32(tcpHdr->check, ipHdr->daddr,
                                             ipAttr->ipv4_dst);
        } else if (udpHdr && udpHdr->check) {
            udpHdr->check = ChecksumUpdate32(udpHdr->check, ipHdr->daddr,
                                             ipAttr->ipv4_dst);
        }

        if (ipHdr->check != 0) {
            ipHdr->check = ChecksumUpdate32(ipHdr->check, ipHdr->daddr,
                                            ipAttr->ipv4_dst);
        }
        ipHdr->daddr = ipAttr->ipv4_dst;
        key->ipKey.nwDst = ipAttr->ipv4_dst;
    }
    if (ipHdr->protocol != ipAttr->ipv4_proto) {
        UINT16 oldProto = (ipHdr->protocol << 16) & 0xff00;
        UINT16 newProto = (ipAttr->ipv4_proto << 16) & 0xff00;
        if (tcpHdr) {
            tcpHdr->check = ChecksumUpdate16(tcpHdr->check, oldProto, newProto);
        } else if (udpHdr && udpHdr->check) {
            udpHdr->check = ChecksumUpdate16(udpHdr->check, oldProto, newProto);
        }

        if (ipHdr->check != 0) {
            ipHdr->check = ChecksumUpdate16(ipHdr->check, oldProto, newProto);
        }
        ipHdr->protocol = ipAttr->ipv4_proto;
        key->ipKey.nwProto = ipAttr->ipv4_proto;
    }
    if (ipHdr->ttl != ipAttr->ipv4_ttl) {
        UINT16 oldTtl = (ipHdr->ttl) & 0xff;
        UINT16 newTtl = (ipAttr->ipv4_ttl) & 0xff;
        if (ipHdr->check != 0) {
            ipHdr->check = ChecksumUpdate16(ipHdr->check, oldTtl, newTtl);
        }
        ipHdr->ttl = ipAttr->ipv4_ttl;
        key->ipKey.nwTtl = ipAttr->ipv4_ttl;
    }
    if (ipHdr->dscp != (ipAttr->ipv4_tos & 0xfc)) {
        /* ECN + DSCP */
        UINT8 newTos = (ipHdr->tos & 0x3) | (ipAttr->ipv4_tos & 0xfc);
        if (ipHdr->check != 0) {
            ipHdr->check = ChecksumUpdate16(ipHdr->check, ipHdr->tos, newTos);
        }
        ipHdr->tos = newTos;
        key->ipKey.nwTos = newTos;
    }

    return NDIS_STATUS_SUCCESS;
}

/*
 * --------------------------------------------------------------------------
 * OvsExecuteSetAction --
 *      Executes a set() action, but storing the actions into 'ovsFwdCtx'
 * --------------------------------------------------------------------------
 */
static __inline NDIS_STATUS
OvsExecuteSetAction(OvsForwardingContext *ovsFwdCtx,
                    OvsFlowKey *key,
                    UINT64 *hash,
                    const PNL_ATTR a)
{
    enum ovs_key_attr type = NlAttrType(a);
    NDIS_STATUS status = NDIS_STATUS_SUCCESS;

    switch (type) {
    case OVS_KEY_ATTR_ETHERNET:
        status = OvsUpdateEthHeader(ovsFwdCtx, key,
            NlAttrGetUnspec(a, sizeof(struct ovs_key_ethernet)));
        break;

    case OVS_KEY_ATTR_IPV4:
        status = OvsUpdateIPv4Header(ovsFwdCtx, key,
            NlAttrGetUnspec(a, sizeof(struct ovs_key_ipv4)));
        break;

    case OVS_KEY_ATTR_TUNNEL:
    {
        OvsIPTunnelKey tunKey = { 0 };
        tunKey.flow_hash = (uint16)(hash ? *hash : OvsHashFlow(key));
        tunKey.dst_port = key->ipKey.l4.tpDst;
        NTSTATUS convertStatus = OvsTunnelAttrToIPTunnelKey((PNL_ATTR)a, &tunKey);
        status = SUCCEEDED(convertStatus) ? NDIS_STATUS_SUCCESS : NDIS_STATUS_FAILURE;
        ASSERT(status == NDIS_STATUS_SUCCESS);
        RtlCopyMemory(&ovsFwdCtx->tunKey, &tunKey, sizeof ovsFwdCtx->tunKey);
        RtlCopyMemory(&key->tunKey, &tunKey, sizeof key->tunKey);
        break;
    }

    case OVS_KEY_ATTR_UDP:
        status = OvsUpdateUdpPorts(ovsFwdCtx, key,
            NlAttrGetUnspec(a, sizeof(struct ovs_key_udp)));
        break;

    case OVS_KEY_ATTR_TCP:
        status = OvsUpdateTcpPorts(ovsFwdCtx, key,
            NlAttrGetUnspec(a, sizeof(struct ovs_key_tcp)));
        break;

    default:
        OVS_LOG_INFO("Unhandled attribute %#x", type);
        break;
    }
    return status;
}

/*
 * --------------------------------------------------------------------------
 * OvsExecuteRecirc --
 *     The function adds a deferred action to allow the current packet, nbl,
 *     to re-enter datapath packet processing.
 * --------------------------------------------------------------------------
 */
NDIS_STATUS
OvsExecuteRecirc(OvsForwardingContext *ovsFwdCtx,
                 OvsFlowKey *key,
                 const PNL_ATTR actions,
                 int rem)
{
    POVS_DEFERRED_ACTION deferredAction = NULL;
    PNET_BUFFER_LIST newNbl = NULL;

    if (!NlAttrIsLast(actions, rem)) {
        /*
         * Recirc action is the not the last action of the action list, so we
         * need to clone the packet.
         */
        newNbl = OvsPartialCopyNBL(ovsFwdCtx->switchContext, ovsFwdCtx->curNbl,
                                   0, 0, TRUE /*copy NBL info*/);
        /*
         * Skip the recirc action when out of memory, but continue on with the
         * rest of the action list.
         */
        if (newNbl == NULL) {
            ovsActionStats.noCopiedNbl++;
            return NDIS_STATUS_SUCCESS;
        }
    }

    if (newNbl) {
        deferredAction = OvsAddDeferredActions(newNbl, key, &(ovsFwdCtx->layers),
                                               NULL);
    } else {
        deferredAction = OvsAddDeferredActions(ovsFwdCtx->curNbl, key,
                                              &(ovsFwdCtx->layers), NULL);
    }

    if (deferredAction) {
        deferredAction->key.recircId = NlAttrGetU32(actions);
    } else {
        if (newNbl) {
            ovsActionStats.deferredActionsQueueFull++;
            OvsCompleteNBL(ovsFwdCtx->switchContext, newNbl, TRUE);
        }
    }

    return NDIS_STATUS_SUCCESS;
}

/*
 * --------------------------------------------------------------------------
 * OvsExecuteHash --
 *     The function updates datapath hash read from userspace.
 * --------------------------------------------------------------------------
 */
VOID
OvsExecuteHash(OvsFlowKey *key,
               const PNL_ATTR attr)
{
    struct ovs_action_hash *hash_act = NlAttrData(attr);
    UINT32 hash = 0;

    hash = (UINT32)OvsHashFlow(key);
    hash = OvsJhashWords(&hash, 1, hash_act->hash_basis);
    if (!hash)
        hash = 1;

    key->dpHash = hash;
}

/*
 * --------------------------------------------------------------------------
 * OvsOutputUserspaceAction --
 *      This function sends the packet to userspace according to nested
 *      %OVS_USERSPACE_ATTR_* attributes.
 * --------------------------------------------------------------------------
 */
static __inline NDIS_STATUS
OvsOutputUserspaceAction(OvsForwardingContext *ovsFwdCtx,
                         OvsFlowKey *key,
                         const PNL_ATTR attr)
{
    NTSTATUS status = NDIS_STATUS_SUCCESS;
    PNL_ATTR userdataAttr;
    PNL_ATTR egrTunAttr = NULL;
    POVS_PACKET_QUEUE_ELEM elem;
    POVS_PACKET_HDR_INFO layers = &ovsFwdCtx->layers;
    BOOLEAN isRecv = FALSE;
    OVS_FWD_INFO fwdInfo;
    OvsIPTunnelKey tunKey;

    POVS_VPORT_ENTRY vport = OvsFindVportByPortNo(ovsFwdCtx->switchContext,
                                                  ovsFwdCtx->srcVportNo);

    if (vport) {
        if (vport->isExternal ||
            OvsIsTunnelVportType(vport->ovsType)) {
            isRecv = TRUE;
        }
    }

    userdataAttr = NlAttrFindNested(attr, OVS_USERSPACE_ATTR_USERDATA);
    /* Indicate the packet is from egress-tunnel direction */
    egrTunAttr = NlAttrFindNested(attr, OVS_USERSPACE_ATTR_EGRESS_TUN_PORT);

    /* Fill tunnel key to export to usersspace to calculate the template id */
    if (egrTunAttr) {
        RtlZeroMemory(&tunKey,  sizeof tunKey);
        RtlCopyMemory(&tunKey, &ovsFwdCtx->tunKey, sizeof tunKey);
        if (!OvsIphIsZero(&(tunKey.src))) {
            status = OvsLookupIPhFwdInfo(tunKey.src, tunKey.dst, &fwdInfo);
            if (status == NDIS_STATUS_SUCCESS &&
                OvsIphAddrEquals(&(tunKey.dst), &(fwdInfo.dstIphAddr))) {
                OvsCopyIphAddress(&(tunKey.src), &fwdInfo.srcIphAddr);
            }
        }
        tunKey.flow_hash = tunKey.flow_hash ? tunKey.flow_hash : MAXINT16;
    }

    elem = OvsCreateQueueNlPacket(NlAttrData(userdataAttr),
                                  NlAttrGetSize(userdataAttr),
                                  OVS_PACKET_CMD_ACTION,
                                  vport, key,
                                  egrTunAttr ? &(tunKey) : NULL,
                                  ovsFwdCtx->curNbl,
                                  NET_BUFFER_LIST_FIRST_NB(ovsFwdCtx->curNbl),
                                  isRecv,
                                  layers);
    if (elem) {
        LIST_ENTRY missedPackets;
        InitializeListHead(&missedPackets);
        InsertTailList(&missedPackets, &elem->link);
        OvsQueuePackets(&missedPackets, 1);
    } else {
        status = NDIS_STATUS_FAILURE;
    }

    return status;
}

/*
 * --------------------------------------------------------------------------
 * OvsExecuteSampleAction --
 *      Executes actions based on probability, as specified in the nested
 *      %OVS_SAMPLE_ATTR_* attributes.
 * --------------------------------------------------------------------------
 */
static __inline NDIS_STATUS
OvsExecuteSampleAction(OvsForwardingContext *ovsFwdCtx,
                       OvsFlowKey *key,
                       const PNL_ATTR attr)
{
    PNET_BUFFER_LIST newNbl = NULL;
    PNL_ATTR actionsList = NULL;
    PNL_ATTR a = NULL;
    INT rem = 0;

    SRand();
    NL_ATTR_FOR_EACH_UNSAFE(a, rem, NlAttrData(attr), NlAttrGetSize(attr)) {
        switch (NlAttrType(a)) {
        case OVS_SAMPLE_ATTR_PROBABILITY:
        {
            UINT32 probability = NlAttrGetU32(a);

            if (!probability || Rand() > probability) {
                return 0;
            }
            break;
        }
        case OVS_SAMPLE_ATTR_ACTIONS:
            actionsList = a;
            break;
        }
    }

    if (actionsList) {
        rem = NlAttrGetSize(actionsList);
        a = (PNL_ATTR)NlAttrData(actionsList);
    }

    if (!rem) {
        /* Actions list is empty, do nothing */
        return STATUS_SUCCESS;
    }

    /*
     * The only known usage of sample action is having a single user-space
     * action. Treat this usage as a special case.
     */
    if (NlAttrType(a) == OVS_ACTION_ATTR_USERSPACE &&
        NlAttrIsLast(a, rem)) {
        return OvsOutputUserspaceAction(ovsFwdCtx, key, a);
    }

    newNbl = OvsPartialCopyNBL(ovsFwdCtx->switchContext, ovsFwdCtx->curNbl,
                               0, 0, TRUE /*copy NBL info*/);
    if (newNbl == NULL) {
        /*
         * Skip the sample action when out of memory, but continue on with the
         * rest of the action list.
         */
        ovsActionStats.noCopiedNbl++;
        return STATUS_SUCCESS;
    }

    if (!OvsAddDeferredActions(newNbl, key, &(ovsFwdCtx->layers), a)) {
        OVS_LOG_INFO(
            "Deferred actions limit reached, dropping sample action.");
        OvsCompleteNBL(ovsFwdCtx->switchContext, newNbl, TRUE);
    }

    return STATUS_SUCCESS;
}

/*
 * --------------------------------------------------------------------------
 * OvsDoExecuteActions --
 *     Interpret and execute the specified 'actions' on the specified packet
 *     'curNbl'. The expectation is that if the packet needs to be dropped
 *     (completed) for some reason, it is added to 'completionList' so that the
 *     caller can complete the packet. If 'completionList' is NULL, the NBL is
 *     assumed to be generated by OVS and freed up. Otherwise, the function
 *     consumes the NBL by generating a NDIS send indication for the packet.
 *
 *     There are one or more of "clone" NBLs that may get generated while
 *     executing the actions. Upon any failures, the "cloned" NBLs are freed up,
 *     and the caller does not have to worry about them.
 *
 *     Success or failure is returned based on whether the specified actions
 *     were executed successfully on the packet or not.
 * --------------------------------------------------------------------------
 */
NDIS_STATUS
OvsDoExecuteActions(POVS_SWITCH_CONTEXT switchContext,
                    OvsCompletionList *completionList,
                    PNET_BUFFER_LIST curNbl,
                    UINT32 portNo,
                    ULONG sendFlags,
                    OvsFlowKey *key,
                    UINT64 *hash,
                    OVS_PACKET_HDR_INFO *layers,
                    const PNL_ATTR actions,
                    INT actionsLen)
{
    PNL_ATTR a;
    INT rem;
    UINT32 dstPortID;
    OvsForwardingContext ovsFwdCtx;
    PCWSTR dropReason = L"";
    NDIS_STATUS status;
    PNDIS_SWITCH_FORWARDING_DETAIL_NET_BUFFER_LIST_INFO fwdDetail =
        NET_BUFFER_LIST_SWITCH_FORWARDING_DETAIL(curNbl);

    /* XXX: ASSERT that the flow table lock is held. */
    status = OvsInitForwardingCtx(&ovsFwdCtx, switchContext, curNbl, portNo,
                                  sendFlags, fwdDetail, completionList,
                                  layers, TRUE);
    if (status != NDIS_STATUS_SUCCESS) {
        dropReason = L"OVS-initing destination port list failed";
        goto dropit;
    }

    if (actionsLen == 0) {
        dropReason = L"OVS-Dropped due to Flow action";
        ovsActionStats.zeroActionLen++;
        goto dropit;
    }

    NL_ATTR_FOR_EACH_UNSAFE (a, rem, actions, actionsLen) {
        switch(NlAttrType(a)) {
        case OVS_ACTION_ATTR_OUTPUT:
            dstPortID = NlAttrGetU32(a);
            status = OvsAddPorts(&ovsFwdCtx, key, dstPortID,
                                              TRUE, TRUE);
            if (status != NDIS_STATUS_SUCCESS) {
                dropReason = L"OVS-adding destination port failed";
                goto dropit;
            }
            break;

        case OVS_ACTION_ATTR_PUSH_VLAN:
        {
            struct ovs_action_push_vlan *vlan;
            PVOID vlanTagValue;
            PNDIS_NET_BUFFER_LIST_8021Q_INFO vlanTag;

            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;
                }
            }

            vlanTagValue = NET_BUFFER_LIST_INFO(ovsFwdCtx.curNbl,
                                                Ieee8021QNetBufferListInfo);
            if (vlanTagValue != NULL) {
                /*
                 * XXX: We don't support double VLAN tag offload. In such cases,
                 * we need to insert the existing one into the packet buffer,
                 * and add the new one as offload. This will take care of
                 * guest tag-in-tag case as well as OVS rules that specify
                 * tag-in-tag.
                 */
            } else {
                 vlanTagValue = 0;
                 vlanTag = (PNDIS_NET_BUFFER_LIST_8021Q_INFO)(PVOID *)&vlanTagValue;
                 vlan = (struct ovs_action_push_vlan *)NlAttrGet((const PNL_ATTR)a);
                 vlanTag->TagHeader.VlanId = ntohs(vlan->vlan_tci) & 0xfff;
                 vlanTag->TagHeader.UserPriority = ntohs(vlan->vlan_tci) >> 13;
                 vlanTag->TagHeader.CanonicalFormatId = (ntohs(vlan->vlan_tci) >> 12) & 0x1;

                 NET_BUFFER_LIST_INFO(ovsFwdCtx.curNbl,
                                      Ieee8021QNetBufferListInfo) = vlanTagValue;
            }
            break;
        }

        case OVS_ACTION_ATTR_POP_VLAN:
        {
            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;
                }
            }

            if (NET_BUFFER_LIST_INFO(ovsFwdCtx.curNbl,
                                     Ieee8021QNetBufferListInfo) != 0) {
                NET_BUFFER_LIST_INFO(ovsFwdCtx.curNbl,
                                     Ieee8021QNetBufferListInfo) = 0;
            } else {
                /*
                 * The VLAN tag is inserted into the packet buffer. Pop the tag
                 * by packet buffer modification.
                 */
                status = OvsPopVlanInPktBuf(&ovsFwdCtx);
                if (status != NDIS_STATUS_SUCCESS) {
                    OVS_LOG_ERROR("OVS-pop vlan action failed status = %lu", status);
                    dropReason = L"OVS-pop vlan action failed";
                    goto dropit;
                }
            }
            /* Reset vlan header info in flowkey. */
            key->l2.vlanKey.vlanTci = 0;
            key->l2.vlanKey.vlanTpid = 0;
            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_HASH:
        {
            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;
                }
            }

            OvsExecuteHash(key, (const PNL_ATTR)a);
            break;
        }

        case OVS_ACTION_ATTR_CT:
        {
            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;
                }
            }

            PNET_BUFFER_LIST oldNbl = ovsFwdCtx.curNbl;
            status = OvsExecuteConntrackAction(&ovsFwdCtx, key,
                                               (const PNL_ATTR)a);
            if (status != NDIS_STATUS_SUCCESS) {
                /* Pending NBLs are consumed by Defragmentation. */
                if (status != NDIS_STATUS_PENDING) {
                    OVS_LOG_ERROR("CT Action failed status = %lu", status);
                    dropReason = L"OVS-conntrack action failed";
                } else {
                    /* We added a new pending NBL to be consumed later.
                     * Report to the userspace that the action applied
                     * successfully */
                    status = NDIS_STATUS_SUCCESS;
                }
                goto dropit;
            } else if (oldNbl != ovsFwdCtx.curNbl) {
                /*
                 * OvsIpv4Reassemble/OvsIpv6Reassemble consumes the
                 * original NBL and creates a new one and assigns
                 * it to the curNbl of ovsFwdCtx.
                 */
                OvsInitForwardingCtx(&ovsFwdCtx,
                                     ovsFwdCtx.switchContext,
                                     ovsFwdCtx.curNbl,
                                     ovsFwdCtx.srcVportNo,
                                     ovsFwdCtx.sendFlags,
                                     NET_BUFFER_LIST_SWITCH_FORWARDING_DETAIL(ovsFwdCtx.curNbl),
                                     ovsFwdCtx.completionList,
                                     &ovsFwdCtx.layers, FALSE);
                key->ipKey.nwFrag = OVS_FRAG_TYPE_NONE;
                key->ipv6Key.nwFrag = OVS_FRAG_TYPE_NONE;
            }
            break;
        }

        case OVS_ACTION_ATTR_RECIRC:
        {
            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 = OvsExecuteRecirc(&ovsFwdCtx, key, (const PNL_ATTR)a, rem);
            if (status != NDIS_STATUS_SUCCESS) {
                dropReason = L"OVS-recirculation action failed";
                goto dropit;
            }

            if (NlAttrIsLast(a, rem)) {
                goto exit;
            }
            break;
        }

        case OVS_ACTION_ATTR_USERSPACE:
        {
            status = OvsOutputUserspaceAction(&ovsFwdCtx, key,
                                              (const PNL_ATTR)a);
            if (status != NDIS_STATUS_SUCCESS) {
                dropReason = L"OVS-Dropped due to failure to queue to "
                             L"userspace";
                goto dropit;
            }
            dropReason = L"OVS-Completed since packet was copied to "
                         L"userspace";
            break;
        }
        case OVS_ACTION_ATTR_SET:
        {
            OvsIPTunnelKey pre_tunKey = { 0 };
            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;
                }
            }
            RtlCopyMemory(&pre_tunKey, &key->tunKey, sizeof key->tunKey);
            status = OvsExecuteSetAction(&ovsFwdCtx, key, hash,
                                         (const PNL_ATTR)NlAttrGet
                                         ((const PNL_ATTR)a));
            if (status != NDIS_STATUS_SUCCESS) {
                dropReason = L"OVS-set action failed";
                goto dropit;
            }

            if (!OvsIphIsZero(&(key->tunKey.dst)) &&
                 key->l2.offset != OvsGetFlowIPL2Offset(&key->tunKey)) {
                 key->l2.offset = OvsGetFlowIPL2Offset(&ovsFwdCtx.tunKey);
            }
            if (!OvsIphIsZero(&(pre_tunKey.dst))) {
                 /*if pre_tunkey dst is not null in multiple IPV6 Geneve tunnels case,
                  * for broadcast and multicast packet the flow pipeline will set different
                  * tunnel setting on one flow. In such case, it needs to do layers update.
                  * Elsewise it will meet BSOD issue but in IPV4 tunnel the BSOD will not
                  * have construct case to make it happen.
                  */
                 status = OvsExtractLayers(ovsFwdCtx.curNbl, &ovsFwdCtx.layers);
                 if (status != NDIS_STATUS_SUCCESS) {
                     dropReason = L"OVS-set action ExtractLayers failed";
                     goto dropit;
                 }
            }
            break;
        }
        case OVS_ACTION_ATTR_METER: {
            if (OvsMeterExecute(&ovsFwdCtx, NlAttrGetU32(a))) {
                OVS_LOG_INFO("Drop packet");
                dropReason = L"Ovs-meter exceed max rate";
                goto dropit;
            }

            break;
        }
        case OVS_ACTION_ATTR_SAMPLE:
        {
            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 = OvsExecuteSampleAction(&ovsFwdCtx, key,
                                            (const PNL_ATTR)a);
            if (status != NDIS_STATUS_SUCCESS) {
                dropReason = L"OVS-sample action failed";
                goto dropit;
            }
            break;
        }
        default:
            status = NDIS_STATUS_NOT_SUPPORTED;
            break;
        }
    }

    if (ovsFwdCtx.destPortsSizeOut > 0 || ovsFwdCtx.tunnelTxNic != NULL
        || ovsFwdCtx.tunnelRxNic != NULL) {
        status = OvsOutputForwardingCtx(&ovsFwdCtx);
        ASSERT(ovsFwdCtx.curNbl == NULL);
    }

    ASSERT(ovsFwdCtx.destPortsSizeOut == 0);
    ASSERT(ovsFwdCtx.tunnelRxNic == NULL);
    ASSERT(ovsFwdCtx.tunnelTxNic == NULL);

dropit:
    /*
     * If curNbl != NULL, it implies the NBL has not been not freed up so far.
     */
    if (ovsFwdCtx.curNbl) {
        OvsCompleteNBLForwardingCtx(&ovsFwdCtx, dropReason);
    }

exit:
    return status;
}

/*
 * --------------------------------------------------------------------------
 * OvsActionsExecute --
 *     The function interprets and executes the specified 'actions' on the
 *     specified packet 'curNbl'. See 'OvsDoExecuteActions' description for
 *     more details.
 *
 *     Also executes deferred actions added by recirculation or sample
 *     actions.
 * --------------------------------------------------------------------------
 */
NDIS_STATUS
OvsActionsExecute(POVS_SWITCH_CONTEXT switchContext,
                  OvsCompletionList *completionList,
                  PNET_BUFFER_LIST curNbl,
                  UINT32 portNo,
                  ULONG sendFlags,
                  OvsFlowKey *key,
                  UINT64 *hash,
                  OVS_PACKET_HDR_INFO *layers,
                  const PNL_ATTR actions,
                  INT actionsLen)
{
    NDIS_STATUS status;

    status = OvsDoExecuteActions(switchContext, completionList, curNbl,
                                 portNo, sendFlags, key, hash, layers,
                                 actions, actionsLen);

    if (status == STATUS_SUCCESS) {
        status = OvsProcessDeferredActions(switchContext, completionList,
                                           portNo, sendFlags);
    }

    return status;
}

/*
 * --------------------------------------------------------------------------
 * OvsDoRecirc --
 *     The function processes the packet 'curNbl' that re-entered datapath
 *     packet processing after a recirculation action.
 * --------------------------------------------------------------------------
 */
NDIS_STATUS
OvsDoRecirc(POVS_SWITCH_CONTEXT switchContext,
            OvsCompletionList *completionList,
            PNET_BUFFER_LIST curNbl,
            OvsFlowKey *key,
            UINT32 srcPortNo,
            OVS_PACKET_HDR_INFO *layers)
{
    NDIS_STATUS status;
    OvsFlow *flow;
    OvsForwardingContext ovsFwdCtx = { 0 };
    UINT64 hash = 0;
    ASSERT(layers);

    OvsInitForwardingCtx(&ovsFwdCtx, switchContext, curNbl,
                         srcPortNo, 0,
                         NET_BUFFER_LIST_SWITCH_FORWARDING_DETAIL(curNbl),
                         completionList, layers, TRUE);
    ASSERT(ovsFwdCtx.switchContext);

    flow = OvsLookupFlow(&ovsFwdCtx.switchContext->datapath, key, &hash, FALSE);
    if (flow) {
        UINT32 level = OvsDeferredActionsLevelGet();

        if (level > DEFERRED_ACTION_EXEC_LEVEL) {
            OvsCompleteNBLForwardingCtx(&ovsFwdCtx,
                L"OVS-Dropped due to deferred actions execution level limit \
                  reached");
            ovsActionStats.deferredActionsExecLimit++;
            ovsFwdCtx.curNbl = NULL;
            return NDIS_STATUS_FAILURE;
        }

        OvsFlowUsed(flow, ovsFwdCtx.curNbl, &ovsFwdCtx.layers);
        ovsFwdCtx.switchContext->datapath.hits++;

        OvsDeferredActionsLevelInc();

        status = OvsDoExecuteActions(ovsFwdCtx.switchContext,
                                     ovsFwdCtx.completionList,
                                     ovsFwdCtx.curNbl,
                                     ovsFwdCtx.srcVportNo,
                                     ovsFwdCtx.sendFlags,
                                     key, &hash, &ovsFwdCtx.layers,
                                     flow->actions, flow->actionsLen);
        ovsFwdCtx.curNbl = NULL;

        OvsDeferredActionsLevelDec();
    } else {
        POVS_VPORT_ENTRY vport = NULL;
        LIST_ENTRY missedPackets;
        UINT32 num = 0;

        ovsFwdCtx.switchContext->datapath.misses++;
        InitializeListHead(&missedPackets);
        vport = OvsFindVportByPortNo(switchContext, srcPortNo);
        if (vport == NULL || vport->ovsState != OVS_STATE_CONNECTED) {
            OvsCompleteNBLForwardingCtx(&ovsFwdCtx,
                L"OVS-Dropped due to port removal");
            ovsActionStats.noVport++;
            return NDIS_STATUS_SUCCESS;
        }
        status = OvsCreateAndAddPackets(NULL, 0, OVS_PACKET_CMD_MISS,
                                        vport, key, ovsFwdCtx.curNbl,
                                        OvsIsExternalVportByPortId(switchContext,
                                            vport->portId),
                                        &ovsFwdCtx.layers,
                                        ovsFwdCtx.switchContext,
                                        &missedPackets, &num);
        if (num) {
            OvsQueuePackets(&missedPackets, num);
        }
        if (status == NDIS_STATUS_SUCCESS) {
            /* Complete the packet since it was copied to user buffer. */
            OvsCompleteNBLForwardingCtx(&ovsFwdCtx,
                L"OVS-Dropped since packet was copied to userspace");
            ovsActionStats.flowMiss++;
        } else {
            OvsCompleteNBLForwardingCtx(&ovsFwdCtx,
                L"OVS-Dropped due to failure to queue to userspace");
            ovsActionStats.failedFlowMiss++;
            status = NDIS_STATUS_FAILURE;
        }
    }

    return status;
}
