diff mbox series

[ovs-dev,v2] datapath-windows: Add ERSPAN v1 support.

Message ID 20220530034218.416-1-u9012063@gmail.com
State Changes Requested
Headers show
Series [ovs-dev,v2] datapath-windows: Add ERSPAN v1 support. | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/intel-ovs-compilation success test: success

Commit Message

William Tu May 30, 2022, 3:42 a.m. UTC
Similar to Linux kernel datapath and userspace datapath,
the patch adds ERSPAN v1 implementation to Windows datapath.
The otpions and interface remains the same, ex: a ERSPAN
tunnel can be created by:
  PS> ovs-vsctl add-port br-tun erspan1 -- set int erspan1 type=erspan
      options:remote_ip=171.31.2.183 options:local_ip=171.31.2.163
      options:erspan_ver=1
  PS> ovs-vsctl -- set int erspan1 options:erspan_idx=0
  PS> ovs-vsctl -- set int erspan1 options:key=0

Tested on Windows server 2019. The bridge configuration is similar to
GRE setup mentioned here[1], but replace GRE with ERSPAN tunnel type.
1. https://cloudbase.it/open-vswitch-2-5-hyper-v-gre-part-3/
A short demo using One Linux server and one Windows server:
https://youtu.be/_01lWBRnrd4

Cc: Alin-Gabriel Serdean <aserdean@ovn.org>
Signed-off-by: William Tu <u9012063@gmail.com>
---
v2: fix tab and replaced with space
    Tested-at:
    https://github.com/williamtu/ovs/actions/runs/2406446843
---
 Documentation/faq/releases.rst         |   2 +-
 NEWS                                   |   1 +
 datapath-windows/automake.mk           |   2 +
 datapath-windows/ovsext/Actions.c      |  18 ++
 datapath-windows/ovsext/BufferMgmt.c   |   2 +-
 datapath-windows/ovsext/Erspan.c       | 403 +++++++++++++++++++++++++
 datapath-windows/ovsext/Erspan.h       | 197 ++++++++++++
 datapath-windows/ovsext/Flow.c         |   7 +
 datapath-windows/ovsext/Flow.h         |   4 +-
 datapath-windows/ovsext/Gre.h          |   5 +-
 datapath-windows/ovsext/Util.h         |   1 +
 datapath-windows/ovsext/Vport.c        |  11 +
 datapath-windows/ovsext/Vport.h        |   8 +-
 datapath-windows/ovsext/ovsext.vcxproj |   2 +
 14 files changed, 657 insertions(+), 6 deletions(-)
 create mode 100644 datapath-windows/ovsext/Erspan.c
 create mode 100644 datapath-windows/ovsext/Erspan.h

Comments

Alin Serdean Aug. 9, 2022, 1:57 a.m. UTC | #1
Sorry for the late response.

Patch looks good just a few nits below.

-----Original Message-----
From: William Tu <u9012063@gmail.com> 
Sent: Monday, May 30, 2022 6:42 AM
To: dev@openvswitch.org
Cc: pweisong@vmware.com; Alin-Gabriel Serdean <aserdean@ovn.org>
Subject: [PATCH v2] datapath-windows: Add ERSPAN v1 support.

Similar to Linux kernel datapath and userspace datapath, the patch adds
ERSPAN v1 implementation to Windows datapath.
The otpions and interface remains the same, ex: a ERSPAN tunnel can be
created by:
  PS> ovs-vsctl add-port br-tun erspan1 -- set int erspan1 type=erspan
      options:remote_ip=171.31.2.183 options:local_ip=171.31.2.163
      options:erspan_ver=1
  PS> ovs-vsctl -- set int erspan1 options:erspan_idx=0
  PS> ovs-vsctl -- set int erspan1 options:key=0

Tested on Windows server 2019. The bridge configuration is similar to GRE
setup mentioned here[1], but replace GRE with ERSPAN tunnel type.
1. https://cloudbase.it/open-vswitch-2-5-hyper-v-gre-part-3/
A short demo using One Linux server and one Windows server:
https://youtu.be/_01lWBRnrd4

Cc: Alin-Gabriel Serdean <aserdean@ovn.org>
Signed-off-by: William Tu <u9012063@gmail.com>
---
v2: fix tab and replaced with space
    Tested-at:
    https://github.com/williamtu/ovs/actions/runs/2406446843
---
 Documentation/faq/releases.rst         |   2 +-
 NEWS                                   |   1 +
 datapath-windows/automake.mk           |   2 +
 datapath-windows/ovsext/Actions.c      |  18 ++
 datapath-windows/ovsext/BufferMgmt.c   |   2 +-
 datapath-windows/ovsext/Erspan.c       | 403 +++++++++++++++++++++++++
 datapath-windows/ovsext/Erspan.h       | 197 ++++++++++++
 datapath-windows/ovsext/Flow.c         |   7 +
 datapath-windows/ovsext/Flow.h         |   4 +-
 datapath-windows/ovsext/Gre.h          |   5 +-
 datapath-windows/ovsext/Util.h         |   1 +
 datapath-windows/ovsext/Vport.c        |  11 +
 datapath-windows/ovsext/Vport.h        |   8 +-
 datapath-windows/ovsext/ovsext.vcxproj |   2 +
 14 files changed, 657 insertions(+), 6 deletions(-)  create mode 100644
datapath-windows/ovsext/Erspan.c  create mode 100644
datapath-windows/ovsext/Erspan.h

diff --git a/Documentation/faq/releases.rst b/Documentation/faq/releases.rst
index c12ffaf4a..af882f589 100644
--- a/Documentation/faq/releases.rst
+++ b/Documentation/faq/releases.rst
@@ -146,7 +146,7 @@ Q: Are all features available with all datapaths?
     Tunnel - GRE-IPv6               4.18           2.6          2.6      NO
     Tunnel - VXLAN-IPv6             4.3            2.6          2.6      NO
     Tunnel - Geneve-IPv6            4.4            2.6          2.6
2.18
-    Tunnel - ERSPAN                 4.18           2.10         2.10     NO
+    Tunnel - ERSPAN                 4.18           2.10         2.10
2.18
[AS] Unfortunately this needs to be version 3.0
     Tunnel - ERSPAN-IPv6            4.18           2.10         2.10     NO
     Tunnel - GTP-U                  NO             NO           2.14     NO
     Tunnel - Bareudp                5.7            NO           NO       NO
diff --git a/NEWS b/NEWS
index eece0d0b2..8874b2592 100644
--- a/NEWS
+++ b/NEWS
@@ -29,6 +29,7 @@ Post-v2.17.0
    - Windows:
      * Conntrack support for TCPv6, UDPv6, ICMPv6, FTPv6.
      * IPv6 Geneve tunnel support.
+     * IPv4 ERSPAN tunnel support.
 
 
 v2.17.0 - 17 Feb 2022
diff --git a/datapath-windows/automake.mk b/datapath-windows/automake.mk
index 60b3d6033..2e040d799 100644
--- a/datapath-windows/automake.mk
+++ b/datapath-windows/automake.mk
@@ -28,6 +28,8 @@ EXTRA_DIST += \
 	datapath-windows/ovsext/Debug.h \
 	datapath-windows/ovsext/DpInternal.h\
 	datapath-windows/ovsext/Driver.c \
+	datapath-windows/ovsext/Erspan.c \
+	datapath-windows/ovsext/Erspan.h \
 	datapath-windows/ovsext/Ethernet.h \
 	datapath-windows/ovsext/Event.c \
 	datapath-windows/ovsext/Event.h \
diff --git a/datapath-windows/ovsext/Actions.c
b/datapath-windows/ovsext/Actions.c
index 0f7f78932..425a61bda 100644
--- a/datapath-windows/ovsext/Actions.c
+++ b/datapath-windows/ovsext/Actions.c
@@ -34,6 +34,7 @@
 #include "Vport.h"
 #include "Vxlan.h"
 #include "Geneve.h"
+#include "Erspan.h"
 #include "IpFragment.h"
 
 #ifdef OVS_DBG_MOD
@@ -46,6 +47,8 @@
 typedef struct _OVS_ACTION_STATS {
     UINT64 rxGre;
     UINT64 txGre;
+    UINT64 rxErspan;
+    UINT64 txErspan;
     UINT64 rxVxlan;
     UINT64 txVxlan;
     UINT64 rxStt;
@@ -223,6 +226,9 @@ OvsDetectTunnelRxPkt(OvsForwardingContext *ovsFwdCtx,
             case OVS_VPORT_TYPE_GRE:
                 ovsActionStats.rxGre++;
                 break;
+            case OVS_VPORT_TYPE_ERSPAN:
+                ovsActionStats.rxErspan++;
+                break;
             }
         }
     }
@@ -310,6 +316,9 @@ OvsDetectTunnelPkt(OvsForwardingContext *ovsFwdCtx,
             case OVS_VPORT_TYPE_GRE:
                 ovsActionStats.txGre++;
                 break;
+            case OVS_VPORT_TYPE_ERSPAN:
+                ovsActionStats.txErspan++;
+                break;
             case OVS_VPORT_TYPE_VXLAN:
                 ovsActionStats.txVxlan++;
                 break;
@@ -665,6 +674,11 @@ OvsTunnelPortTx(OvsForwardingContext *ovsFwdCtx)
                              &ovsFwdCtx->tunKey, ovsFwdCtx->switchContext,
                              &ovsFwdCtx->layers, &newNbl, &switchFwdInfo);
         break;
+    case OVS_VPORT_TYPE_ERSPAN:
+        status = OvsEncapErspan(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, @@ -762,6 +776,10 @@
OvsTunnelPortRx(OvsForwardingContext *ovsFwdCtx)
         status = OvsDecapGre(ovsFwdCtx->switchContext, ovsFwdCtx->curNbl,
                              &ovsFwdCtx->tunKey, &newNbl);
         break;
+    case OVS_VPORT_TYPE_ERSPAN:
+        status = OvsDecapErspan(ovsFwdCtx->switchContext,
ovsFwdCtx->curNbl,
+                                &ovsFwdCtx->tunKey, &newNbl);
+        break;
     case OVS_VPORT_TYPE_VXLAN:
         status = OvsDecapVxlan(ovsFwdCtx->switchContext, ovsFwdCtx->curNbl,
                                &ovsFwdCtx->tunKey, &newNbl); diff --git
a/datapath-windows/ovsext/BufferMgmt.c
b/datapath-windows/ovsext/BufferMgmt.c
index acf3c13a2..4bbedc38a 100644
--- a/datapath-windows/ovsext/BufferMgmt.c
+++ b/datapath-windows/ovsext/BufferMgmt.c
@@ -1610,7 +1610,7 @@ copymultiple_error:
  * OvsCompleteNBL --
  *
  *     This function tries to free the NBL allocated by OVS buffer
- *     management module. If it trigger the completion of the parent
+ *     management module. If it triggers the completion of the parent
  *     NBL, it will recursively call itself. If it trigger the completion
  *     of external NBL, it will be returned to the caller. The caller
  *     is responsible to call API to return to upper layer.
diff --git a/datapath-windows/ovsext/Erspan.c
b/datapath-windows/ovsext/Erspan.c
new file mode 100644
index 000000000..6a57062d9
--- /dev/null
+++ b/datapath-windows/ovsext/Erspan.c
@@ -0,0 +1,403 @@
+/*
+ * Copyright (c) 2022 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 "Atomic.h"
+#include "Debug.h"
+#include "Flow.h"
+#include "Gre.h"
+#include "IpHelper.h"
+#include "NetProto.h"
+#include "Offload.h"
+#include "PacketIO.h"
+#include "PacketParser.h"
+#include "Switch.h"
+#include "User.h"
+#include "Util.h"
+#include "Vport.h"
+#include "Erspan.h"
+
+#ifdef OVS_DBG_MOD
+#undef OVS_DBG_MOD
+#endif
+#define OVS_DBG_MOD OVS_DBG_GRE
+
+static NDIS_STATUS
+OvsDoEncapErspan(POVS_VPORT_ENTRY vport,
+                 PNET_BUFFER_LIST curNbl,
+                 const OvsIPTunnelKey *tunKey,
+                 const POVS_FWD_INFO fwdInfo,
+                 POVS_PACKET_HDR_INFO layers,
+                 POVS_SWITCH_CONTEXT switchContext,
+                 PNET_BUFFER_LIST *newNbl);
+
+/*
+ * 
+-----------------------------------------------------------------------
+---
+ * OvsInitErspanTunnel --
+ *    Initialize ERSPAN tunnel module.
+ * 
+-----------------------------------------------------------------------
+---
+ */
+NTSTATUS
+OvsInitErspanTunnel(POVS_VPORT_ENTRY vport) {
+    POVS_ERSPAN_VPORT ersPort;
+    ersPort = (POVS_ERSPAN_VPORT)OvsAllocateMemoryWithTag(sizeof(*ersPort),
+
OVS_ERSPAN_POOL_TAG);
+    if (!ersPort) {
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+
+    RtlZeroMemory(ersPort, sizeof(*ersPort));
+    vport->priv = (PVOID)ersPort;
+    return STATUS_SUCCESS;
+}
+
+/*
+ * 
+-----------------------------------------------------------------------
+---
+ * OvsCleanupErspanTunnel --
+ *    Cleanup ERSPAN Tunnel module.
+ * 
+-----------------------------------------------------------------------
+---
+ */
+void
+OvsCleanupErspanTunnel(POVS_VPORT_ENTRY vport) {
+    if (vport->ovsType != OVS_VPORT_TYPE_ERSPAN ||
+        vport->priv == NULL) {
+        return;
+    }
+    OvsFreeMemoryWithTag(vport->priv, OVS_ERSPAN_POOL_TAG);
+    vport->priv = NULL;
+}
+
+/*
+ * 
+-----------------------------------------------------------------------
+---
+ * OvsEncapErspan --
+ *     Encapsulates a packet with an ERSPAN header.
+ * 
+-----------------------------------------------------------------------
+---
+ */
+NDIS_STATUS
+OvsEncapErspan(POVS_VPORT_ENTRY vport,
+               PNET_BUFFER_LIST curNbl,
+               OvsIPTunnelKey *tunKey,
+               POVS_SWITCH_CONTEXT switchContext,
+               POVS_PACKET_HDR_INFO layers,
+               PNET_BUFFER_LIST *newNbl,
+               POVS_FWD_INFO switchFwdInfo) {
+    OVS_FWD_INFO fwdInfo;
+    NDIS_STATUS status;
+
+    if (tunKey->dst.si_family != AF_INET) {
+        return NDIS_STATUS_FAILURE;
+    }
+
+    status = OvsLookupIPhFwdInfo(tunKey->src, tunKey->dst, &fwdInfo);
+    if (status != STATUS_SUCCESS) {
+        OvsFwdIPHelperRequest(NULL, 0, tunKey, NULL, NULL, NULL);
+        return NDIS_STATUS_FAILURE;
+    }
+
+    RtlCopyMemory(switchFwdInfo->value, fwdInfo.value, sizeof 
+ fwdInfo.value);
+
+    status = OvsDoEncapErspan(vport, curNbl, tunKey, &fwdInfo, layers,
+                              switchContext, newNbl);
+    return status;
+}
+
+/*
+ * 
+-----------------------------------------------------------------------
+---
+ * OvsDoEncapErspan --
+ *    Internal utility function which actually does the ERSPAN encap.
+ * 
+-----------------------------------------------------------------------
+---
+ */
+NDIS_STATUS
+OvsDoEncapErspan(POVS_VPORT_ENTRY vport,
+                 PNET_BUFFER_LIST curNbl,
+                 const OvsIPTunnelKey *tunKey,
+                 const POVS_FWD_INFO fwdInfo,
+                 POVS_PACKET_HDR_INFO layers,
+                 POVS_SWITCH_CONTEXT switchContext,
+                 PNET_BUFFER_LIST *newNbl) {
+    NDIS_STATUS status;
+    PNET_BUFFER curNb;
+    PMDL curMdl;
+    PUINT8 bufferStart;
+    EthHdr *ethHdr;
+    IPHdr *ipHdr;
+    PGREHdr greHdr;
+    PERSPANHdr ersHdr;
+    POVS_ERSPAN_VPORT vportErs;
+    UINT32 headRoom = GreTunHdrSize(GRE_SEQ) + 8;
[AS} What does "8" represent, can we use a constant for it?
+    UINT32 packetLength;
+    ULONG mss = 0;
+    UINT32 seqno = 0;
+    ASSERT(*newNbl == NULL);
+
+    if (tunKey->flags & OVS_TNL_F_CSUM) {
+        OVS_LOG_ERROR("ERSPAN does not support GRE-CSUM");
+        return NDIS_STATUS_FAILURE;
+    }
+
+    curNb = NET_BUFFER_LIST_FIRST_NB(curNbl);
+    packetLength = NET_BUFFER_DATA_LENGTH(curNb);
+
+    if (layers->isTcp) {
+        mss = OVSGetTcpMSS(curNbl);
+
+        OVS_LOG_TRACE("MSS %u packet len %u", mss,
+                      packetLength);
+        if (mss) {
+            OVS_LOG_TRACE("l4Offset %d", layers->l4Offset);
+            *newNbl = OvsTcpSegmentNBL(switchContext, curNbl, layers,
+                                       mss, headRoom, FALSE);
+            if (*newNbl == NULL) {
+                OVS_LOG_ERROR("Unable to segment NBL");
+                return NDIS_STATUS_FAILURE;
+            }
+            /* Clear out LSO flags after this point */
+            NET_BUFFER_LIST_INFO(*newNbl, TcpLargeSendNetBufferListInfo) =
0;
+        }
+    }
+
+    vportErs = (POVS_ERSPAN_VPORT)GetOvsVportPriv(vport);
+    ASSERT(vportErs);
+
+    /* If we didn't split the packet above, make a copy now. */
+    if (*newNbl == NULL) {
+        *newNbl = OvsPartialCopyNBL(switchContext, curNbl, 0, headRoom,
+                                    FALSE /* NBL info */);
+        if (*newNbl == NULL) {
+            OVS_LOG_ERROR("Unable to copy NBL");
+            return NDIS_STATUS_FAILURE;
+        }
+
+        NDIS_TCP_IP_CHECKSUM_NET_BUFFER_LIST_INFO csumInfo;
+        csumInfo.Value = NET_BUFFER_LIST_INFO(curNbl,
+
TcpIpChecksumNetBufferListInfo);
+        status = OvsApplySWChecksumOnNB(layers, *newNbl, &csumInfo);
+        if (status != NDIS_STATUS_SUCCESS) {
+            goto ret_error;
+        }
+    }
+
+    curNbl = *newNbl;
+    for (curNb = NET_BUFFER_LIST_FIRST_NB(curNbl); curNb != NULL;
+         curNb = curNb->Next) {
+        status = NdisRetreatNetBufferDataStart(curNb, headRoom, 0, NULL);
+        if (status != NDIS_STATUS_SUCCESS) {
+            goto ret_error;
+        }
+
+        curMdl = NET_BUFFER_CURRENT_MDL(curNb);
+        bufferStart = (PUINT8)OvsGetMdlWithLowPriority(curMdl);
+        if (!bufferStart) {
+            status = NDIS_STATUS_RESOURCES;
+            goto ret_error;
+        }
+
+        bufferStart += NET_BUFFER_CURRENT_MDL_OFFSET(curNb);
+        if (NET_BUFFER_NEXT_NB(curNb)) {
+            OVS_LOG_TRACE("nb length %u next %u",
+                          NET_BUFFER_DATA_LENGTH(curNb),
+                          NET_BUFFER_DATA_LENGTH(curNb->Next));
+        }
+
+        /* Outer L2 header */
+        ethHdr = (EthHdr *)bufferStart;
+        NdisMoveMemory(ethHdr->Destination, fwdInfo->dstMacAddr,
+                       sizeof ethHdr->Destination);
+        NdisMoveMemory(ethHdr->Source, fwdInfo->srcMacAddr,
+                       sizeof ethHdr->Source);
+        ethHdr->Type = htons(ETH_TYPE_IPV4);
+
+        /* Outer IP header */
+        ipHdr = (IPHdr *)((PCHAR)ethHdr + sizeof *ethHdr);
+        ipHdr->ihl = sizeof *ipHdr / 4;
+        ipHdr->version = IPPROTO_IPV4;
+        ipHdr->tos = tunKey->tos;
+        ipHdr->tot_len = htons(NET_BUFFER_DATA_LENGTH(curNb) - sizeof
*ethHdr);
+        ipHdr->id = (uint16)atomic_add64(&vportErs->ipId,
+                                         NET_BUFFER_DATA_LENGTH(curNb));
+        ipHdr->frag_off = (tunKey->flags & OVS_TNL_F_DONT_FRAGMENT) ?
+                          IP_DF_NBO : 0;
+        ipHdr->ttl = tunKey->ttl ? tunKey->ttl : 64;
+        ipHdr->protocol = IPPROTO_GRE;
+
+        ASSERT(OvsIphAddrEquals(&tunKey->dst, &fwdInfo->dstIphAddr));
+        ASSERT(OvsIphAddrEquals(&tunKey->src, &fwdInfo->srcIphAddr) ||
+               OvsIphIsZero(&tunKey->src));
+
+        ipHdr->saddr = fwdInfo->srcIphAddr.Ipv4.sin_addr.s_addr;
+        ipHdr->daddr = fwdInfo->dstIphAddr.Ipv4.sin_addr.s_addr;
+
+        ipHdr->check = 0;
+        ipHdr->check = IPChecksum((UINT8 *)ipHdr, sizeof *ipHdr, 0);
+
+        /* GRE base header */
+        greHdr = (GREHdr *)((PCHAR)ipHdr + sizeof *ipHdr);
+        greHdr->flags = GRE_SEQ; /* ERSPAN has fixed 8B GRE header */
+        greHdr->protocolType = htons(ETH_P_ERSPAN);
+
+        /* GRE sequence number */
+        PCHAR currentOffset = (PCHAR)greHdr + sizeof *greHdr;
+        seqno = htonl(vportErs->seqno++);
+        RtlCopyMemory(currentOffset, &seqno, sizeof seqno);
+
+        /* Build ERSPAN base header */
+        ersHdr = (ERSPANHdr *)((PCHAR)greHdr + 8);
[AS] Again constant here
+        ersHdr->ver = ERSPAN_VERSION;
+        ersHdr->cos = TosToCos(ipHdr->tos);
+        ersHdr->en = ERSPAN_ENCAP_NOVLAN;
+        ersHdr->t = 0;
+
+        SetVlan(ersHdr, vportErs->vlan);
+        /* Use tunnel ID as session ID */
+        UINT16 key = (UINT16)(tunKey->tunnelId >> 32);
+        SetSessionId(ersHdr, key);
+        ersHdr->index = 0;
+    }
+    return STATUS_SUCCESS;
+
+ret_error:
+    OvsCompleteNBL(switchContext, *newNbl, TRUE);
+    *newNbl = NULL;
+    return status;
+}
+
+/*
+ * 
+-----------------------------------------------------------------------
+---
+ * OvsDecapErspan --
+ *    Decapsulates a packet with an ERSPAN header.
+ * 
+-----------------------------------------------------------------------
+---
+ */
+NDIS_STATUS
+OvsDecapErspan(POVS_SWITCH_CONTEXT switchContext,
+               PNET_BUFFER_LIST curNbl,
+               OvsIPTunnelKey *tunKey,
+               PNET_BUFFER_LIST *newNbl) {
+    PNET_BUFFER curNb;
+    PMDL curMdl;
+    EthHdr *ethHdr;
+    IPHdr *ipHdr;
+    GREHdr *greHdr;
+    ERSPANHdr *ersHdr;
+    UINT32 tunnelSize, packetLength;
+    UINT32 maxGreLen;
+    PUINT8 bufferStart;
+    NDIS_STATUS status = NDIS_STATUS_SUCCESS;
+    PCHAR tempBuf = NULL;
+    OVS_PACKET_HDR_INFO layers;
+    const UINT32 greHdrLen = 8, ersHdrLen = 8;
[AS] Again can we define constants for them or reuse any?
+
+    ASSERT(*newNbl == NULL);
+    *newNbl = NULL;
+
+    status = OvsExtractLayers(curNbl, &layers);
+    if (status != NDIS_STATUS_SUCCESS) {
+        return status;
+    }
+
+    curNb = NET_BUFFER_LIST_FIRST_NB(curNbl);
+    packetLength = NET_BUFFER_DATA_LENGTH(curNb);
+    curMdl = NET_BUFFER_CURRENT_MDL(curNb);
+    tunnelSize = greHdrLen + ersHdrLen;
+    if (packetLength <= tunnelSize) {
+        return NDIS_STATUS_INVALID_LENGTH;
+    }
+    maxGreLen = GreMaxLengthFromLayers(&layers) + ersHdrLen;
+
+    /* Get a contiguous buffer for the maximum length of a GRE header */
+    bufferStart = NdisGetDataBuffer(curNb, maxGreLen, NULL, 1, 0);
+    if (!bufferStart) {
+        /* Documentation is unclear on where the packet can be fragmented.
+         * For the moment allocate the buffer needed to get the maximum
length
+         * of a GRE header contiguous */
+         tempBuf = OvsAllocateMemoryWithTag(maxGreLen, OVS_GRE_POOL_TAG);
+         if (!tempBuf) {
+            status = NDIS_STATUS_RESOURCES;
+            goto end;
+         }
+         RtlZeroMemory(tempBuf, maxGreLen);
+         bufferStart = NdisGetDataBuffer(curNb, maxGreLen, tempBuf,
+                                         1, 0);
+         if (!bufferStart) {
+            status = NDIS_STATUS_RESOURCES;
+            goto end;
+         }
+    }
+
+    ethHdr = (EthHdr *)bufferStart;
+    ipHdr = (IPHdr *)(bufferStart + layers.l3Offset);
+
+    tunKey->src.Ipv4.sin_addr.s_addr = ipHdr->saddr;
+    tunKey->src.Ipv4.sin_family = AF_INET;
+    tunKey->dst.Ipv4.sin_addr.s_addr = ipHdr->daddr;
+    tunKey->dst.Ipv4.sin_family = AF_INET;
+    tunKey->tos = ipHdr->tos;
+    tunKey->ttl = ipHdr->ttl;
+    tunKey->pad = 0;
+
+    greHdr = (GREHdr *)(bufferStart + layers.l4Offset);
+    ersHdr = (ERSPANHdr *)(bufferStart + layers.l4Offset + ersHdrLen);
+
+    tunnelSize = GreTunHdrSizeFromLayers(greHdr->flags, &layers) + 8;
+
+    /* Verify the packet length after looking at the GRE flags */
+    if (packetLength <= tunnelSize) {
+        status = NDIS_STATUS_INVALID_LENGTH;
+        goto end;
+    }
+
+    /* Validate if ERSPAN header protocol type */
+    if (greHdr->protocolType != htons(ETH_P_ERSPAN)) {
+        status = STATUS_NDIS_INVALID_PACKET;
+        goto end;
+    }
+
+    if (greHdr->flags & GRE_KEY || greHdr->flags & GRE_CSUM) {
+        status = STATUS_NDIS_INVALID_PACKET;
+        goto end;
+    }
+
+    /*
+     * Create a copy of the NBL so that we have all the headers in one MDL.
+     */
+    *newNbl = OvsPartialCopyNBL(switchContext, curNbl,
+                                tunnelSize, 0,
+                                TRUE /* copy NBL info */);
+    if (*newNbl == NULL) {
+        status = NDIS_STATUS_RESOURCES;
+        goto end;
+    }
+    curNbl = *newNbl;
+    curNb = NET_BUFFER_LIST_FIRST_NB(curNbl);
+    tunKey->tunnelId = (UINT64)ntohl(ersHdr->session_id) << 32;
+
+    /* Clear out the receive flag for the inner packet. */
+    NET_BUFFER_LIST_INFO(curNbl, TcpIpChecksumNetBufferListInfo) = 0;
+    NdisAdvanceNetBufferDataStart(curNb, GreTunHdrSize(GRE_SEQ) +
ersHdrLen, FALSE,
+                                  NULL);
+end:
+    if (tempBuf) {
+        OvsFreeMemoryWithTag(tempBuf, OVS_ERSPAN_POOL_TAG);
+        tempBuf = NULL;
+    }
+
+    return status;
+}
diff --git a/datapath-windows/ovsext/Erspan.h
b/datapath-windows/ovsext/Erspan.h
new file mode 100644
index 000000000..0c91148e0
--- /dev/null
+++ b/datapath-windows/ovsext/Erspan.h
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2022 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.
+ */
+
+#ifndef __ERSPAN_H_
+#define __ERSPAN_H_ 1
+
+#include "Flow.h"
+#include "IpHelper.h"
+#include "NetProto.h"
+
+/*
+ * GRE header for ERSPAN type I encapsulation (4 octets [34:37])
+ *      0                   1                   2                   3
+ *      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ *     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *     |0|0|0|0|0|00000|000000000|00000|    Protocol Type for ERSPAN   |
+ *     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ *  The Type I ERSPAN frame format is based on the barebones IP + GRE
+ *  encapsulation (as described above) on top of the raw mirrored frame.
+ *  There is no extra ERSPAN header.
+ *
+ *
+ * GRE header for ERSPAN type II and II encapsulation (8 octets [34:41])
+ *       0                   1                   2                   3
+ *      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ *     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *     |0|0|0|1|0|00000|000000000|00000|    Protocol Type for ERSPAN   |
+ *     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *     |      Sequence Number (increments per packet per session)      |
+ *     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ *  Note that in the above GRE header [RFC1701] out of the C, R, K, S,
+ *  s, Recur, Flags, Version fields only S (bit 03) is set to 1. The
+ *  other fields are set to zero, so only a sequence number follows.
+ *
+ *  ERSPAN Version 1 (Type II) header (8 octets [42:49])
+ *  0                   1                   2                   3
+ *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |  Ver  |          VLAN         | COS | En|T|    Session ID     |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |      Reserved         |                  Index                |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ *
+ *  ERSPAN Version 2 (Type III) header (12 octets [42:49])
+ *  0                   1                   2                   3
+ *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |  Ver  |          VLAN         | COS |BSO|T|     Session ID    |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                          Timestamp                            |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |             SGT               |P|    FT   |   Hw ID   |D|Gra|O|
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ *      Platform Specific SubHeader (8 octets, optional)
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |  Platf ID |               Platform Specific Info              |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                  Platform Specific Info                       |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * GRE proto ERSPAN type I/II = 0x88BE, type III = 0x22EB  */
+
+typedef struct _OVS_ERSPAN_VPORT {
+    UINT64 ipId;
+    UINT32 seqno;
+    UINT16 vlan;
+    UINT8 index;
+} OVS_ERSPAN_VPORT, *POVS_ERSPAN_VPORT;
+
+typedef struct _ERSPANHdr {
+#ifdef WORDS_BIGENDIAN
+    UINT8 ver:4,
+          vlan_upper:4;
+    UINT8 vlan:8;
+    UINT8 cos:3,
+          en:2,
+          t:1,
+          session_id_upper:2;
+    UINT8 session_id:8;
+#else
+    UINT8 vlan_upper:4,
+          ver:4;
+    UINT8 vlan:8;
+    UINT8 session_id_upper:2,
+          t:1,
+          en:2,
+          cos:3;
+    UINT8 session_id:8;
+#endif
+    UINT32 index;
+} ERSPANHdr, *PERSPANHdr;
+
+
+#define ETH_P_ERSPAN    0x88BE
+#define ETH_P_ERSPAN2   0x22EB
+#define ERSPAN_VERSION  0x1
+
+#define VER_MASK    0xf000
+#define VLAN_MASK   0x0fff
+#define COS_MASK    0xe000
+#define EN_MASK     0x1800
+#define T_MASK      0x0400
+#define ID_MASK     0x03ff
+#define INDEX_MASK  0xfffff
+
+#define ERSPAN_VERSION2 0x2 /* ERSPAN type III*/
+#define BSO_MASK    EN_MASK
+#define SGT_MASK    0xffff0000
+#define P_MASK      0x8000
+#define FT_MASK     0x7c00
+#define HWID_MASK   0x03f0
+#define DIR_MASK    0x0008
+#define GRA_MASK    0x0006
+#define O_MASK      0x0001
+
+#define HWID_OFFSET    4
+#define DIR_OFFSET     3
+
+enum erspan_encap_type {
+    ERSPAN_ENCAP_NOVLAN = 0x0,  /* originally without VLAN tag */
+    ERSPAN_ENCAP_ISL = 0x1,     /* originally ISL encapsulated */
+    ERSPAN_ENCAP_8021Q = 0x2,   /* originally 802.1Q encapsulated */
+    ERSPAN_ENCAP_INFRAME = 0x3, /* VLAN tag perserved in frame */ };
+
+NTSTATUS OvsInitErspanTunnel(POVS_VPORT_ENTRY vport);
+
+VOID OvsCleanupErspanTunnel(POVS_VPORT_ENTRY vport);
+
+NDIS_STATUS OvsEncapErspan(POVS_VPORT_ENTRY vport,
+    PNET_BUFFER_LIST curNbl,
+    OvsIPTunnelKey* tunKey,
+    POVS_SWITCH_CONTEXT switchContext,
+    POVS_PACKET_HDR_INFO layers,
+    PNET_BUFFER_LIST* newNbl,
+    POVS_FWD_INFO switchFwdInfo);
+
+NDIS_STATUS OvsDecapErspan(POVS_SWITCH_CONTEXT switchContext,
+    PNET_BUFFER_LIST curNbl,
+    OvsIPTunnelKey* tunKey,
+    PNET_BUFFER_LIST* newNbl);
+
+static inline void
+SetSessionId(ERSPANHdr *ershdr, UINT16 id) {
+    ershdr->session_id = id & 0xff;
+    ershdr->session_id_upper = (id >> 8) & 0x3; }
+
+static inline UINT16
+GetSessionId(const ERSPANHdr *ershdr)
+{
+    return (ershdr->session_id_upper << 8) + ershdr->session_id; }
+
+static inline void
+SetVlan(ERSPANHdr *ershdr, UINT16 vlan) {
+    ershdr->vlan = vlan & 0xff;
+    ershdr->vlan_upper = (vlan >> 8) & 0xf; }
+
+static inline UINT16
+GetVlan(const ERSPANHdr *ershdr)
+{
+    return (ershdr->vlan_upper << 8) + ershdr->vlan; }
+
+static inline UINT8
+TosToCos(UINT8 tos)
+{
+    UINT8 dscp, cos;
+
+    dscp = tos >> 2;
+    cos = dscp >> 3;
+    return cos;
+}
+
+#endif /* __ERSPAN_H_ */
diff --git a/datapath-windows/ovsext/Flow.c b/datapath-windows/ovsext/Flow.c
index 08fba4c4d..96c32561b 100644
--- a/datapath-windows/ovsext/Flow.c
+++ b/datapath-windows/ovsext/Flow.c
@@ -1914,6 +1914,10 @@ OvsTunnelAttrToIPTunnelKey(PNL_ATTR attr,
             tunKey->flags |= OVS_TNL_F_GENEVE_OPT;
             hasOpt = 1;
             break;
+        case OVS_TUNNEL_KEY_ATTR_ERSPAN_OPTS:
+            tunKey->flags |= OVS_TNL_F_ERSPAN_OPT;
+            hasOpt = 1;
+            break;
         default:
             // XXX: Support OVS_TUNNEL_KEY_ATTR_VXLAN_OPTS
             return STATUS_INVALID_PARAMETER; @@ -3263,6 +3267,9 @@
OvsTunKeyAttrSize(void)
          + NlAttrTotalSize(0)    /* OVS_TUNNEL_KEY_ATTR_CSUM */
          + NlAttrTotalSize(0)    /* OVS_TUNNEL_KEY_ATTR_OAM */
          + NlAttrTotalSize(256)  /* OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS */
+         /* Mutually exclusive with OVS_TUNNEL_KEY_ATTR_VXLAN_OPTS,
+          * and OVS_TUNNEL_KEY_ATTR_ERSPAN_OPTS
+          */
          + NlAttrTotalSize(2)    /* OVS_TUNNEL_KEY_ATTR_TP_SRC */
          + NlAttrTotalSize(2);   /* OVS_TUNNEL_KEY_ATTR_TP_DST */
 }
diff --git a/datapath-windows/ovsext/Flow.h b/datapath-windows/ovsext/Flow.h
index 8f7214124..4b45546a7 100644
--- a/datapath-windows/ovsext/Flow.h
+++ b/datapath-windows/ovsext/Flow.h
@@ -97,7 +97,9 @@ OvsTunnelAttrToIPTunnelKey(PNL_ATTR attr, OvsIPTunnelKey
*tunKey);
 #define OVS_TNL_F_CRT_OPT               (1 << 4)
 #define OVS_TNL_F_GENEVE_OPT            (1 << 5)
 #define OVS_TNL_F_VXLAN_OPT             (1 << 6)
+#define OVS_TNL_F_ERSPAN_OPT             (1 << 7)
 
-#define OVS_TNL_HAS_OPTIONS             (OVS_TNL_F_GENEVE_OPT |
OVS_TNL_F_VXLAN_OPT)
+#define OVS_TNL_HAS_OPTIONS             (OVS_TNL_F_GENEVE_OPT |
OVS_TNL_F_VXLAN_OPT | \
+                                         OVS_TNL_F_ERSPAN_OPT)
 
 #endif /* __FLOW_H_ */
diff --git a/datapath-windows/ovsext/Gre.h b/datapath-windows/ovsext/Gre.h
index 144b6195e..37adf03a4 100644
--- a/datapath-windows/ovsext/Gre.h
+++ b/datapath-windows/ovsext/Gre.h
@@ -44,7 +44,7 @@ typedef struct _OVS_GRE_VPORT {
  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  */
 
-typedef struct GREHdr {
+typedef struct _GREHdr {
     UINT16 flags;
     UINT16 protocolType;
 } GREHdr, *PGREHdr;
@@ -54,6 +54,7 @@ typedef struct GREHdr {
 /* GRE Flags*/
 #define GRE_CSUM    0x0080
 #define GRE_KEY     0x0020
+#define GRE_SEQ     0x0010
 /* The maximum GRE header length that we can process */  #define
OVS_MAX_GRE_LGTH (sizeof(EthHdr) + sizeof(IPHdr) + sizeof(GREHdr) + 12)
 
@@ -99,6 +100,7 @@ GreTunHdrSize(UINT16 flags)
     UINT32 sum = sizeof(EthHdr) + sizeof(IPHdr) + sizeof(GREHdr);
     sum += (flags & GRE_CSUM) ? 4 : 0;
     sum += (flags & GRE_KEY) ? 4 : 0;
+    sum += (flags & GRE_SEQ) ? 4 : 0;
 
     return sum;
 }
@@ -109,6 +111,7 @@ GreTunHdrSizeFromLayers(UINT16 flags,
POVS_PACKET_HDR_INFO layers)
     UINT32 sum = layers->l4Offset + sizeof(GREHdr);
     sum += (flags & GRE_CSUM) ? 4 : 0;
     sum += (flags & GRE_KEY) ? 4 : 0;
+    sum += (flags & GRE_SEQ) ? 4 : 0;
 
     return sum;
 }
diff --git a/datapath-windows/ovsext/Util.h b/datapath-windows/ovsext/Util.h
index f63a885a9..807a4695e 100644
--- a/datapath-windows/ovsext/Util.h
+++ b/datapath-windows/ovsext/Util.h
@@ -35,6 +35,7 @@
 #define OVS_VPORT_POOL_TAG              'PSVO'
 #define OVS_STT_POOL_TAG                'RSVO'
 #define OVS_GRE_POOL_TAG                'GSVO'
+#define OVS_ERSPAN_POOL_TAG             'GSVO'
 #define OVS_TUNFLT_POOL_TAG             'WSVO'
 #define OVS_RECIRC_POOL_TAG             'CSVO'
 #define OVS_CT_POOL_TAG                 'CTVO'
diff --git a/datapath-windows/ovsext/Vport.c
b/datapath-windows/ovsext/Vport.c index 9f1587f44..5d325dd88 100644
--- a/datapath-windows/ovsext/Vport.c
+++ b/datapath-windows/ovsext/Vport.c
@@ -28,6 +28,7 @@
 #include "Vport.h"
 #include "Vxlan.h"
 #include "Geneve.h"
+#include "Erspan.h"
 
 #ifdef OVS_DBG_MOD
 #undef OVS_DBG_MOD
@@ -1088,6 +1089,9 @@ OvsInitTunnelVport(PVOID userContext,
     case OVS_VPORT_TYPE_GRE:
         status = OvsInitGreTunnel(vport);
         break;
+    case OVS_VPORT_TYPE_ERSPAN:
+        status = OvsInitErspanTunnel(vport);
+        break;
     case OVS_VPORT_TYPE_VXLAN:
     {
         POVS_TUNFLT_INIT_CONTEXT tunnelContext = NULL; @@ -1253,6 +1257,7
@@ InitOvsVportCommon(POVS_SWITCH_CONTEXT switchContext,
 
     switch(vport->ovsType) {
     case OVS_VPORT_TYPE_GRE:
+    case OVS_VPORT_TYPE_ERSPAN:
     case OVS_VPORT_TYPE_VXLAN:
     case OVS_VPORT_TYPE_STT:
     case OVS_VPORT_TYPE_GENEVE:
@@ -1342,6 +1347,9 @@ OvsRemoveAndDeleteVport(PVOID usrParamsContext,
     case OVS_VPORT_TYPE_GRE:
         OvsCleanupGreTunnel(vport);
         break;
+    case OVS_VPORT_TYPE_ERSPAN:
+        OvsCleanupErspanTunnel(vport);
+        break;
     case OVS_VPORT_TYPE_NETDEV:
         if (vport->isExternal) {
             if (vport->nicIndex == 0) { @@ -2292,6 +2300,9 @@
OvsNewVportCmdHandler(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
             case OVS_VPORT_TYPE_GRE:
                 nwProto = IPPROTO_GRE;
                 break;
+            case OVS_VPORT_TYPE_ERSPAN:
+                nwProto = IPPROTO_GRE;
+                break;
             case OVS_VPORT_TYPE_VXLAN:
                 transportPortDest = VXLAN_UDP_PORT;
                 nwProto = IPPROTO_UDP;
diff --git a/datapath-windows/ovsext/Vport.h
b/datapath-windows/ovsext/Vport.h index 32cbf8bcc..516cfc1d8 100644
--- a/datapath-windows/ovsext/Vport.h
+++ b/datapath-windows/ovsext/Vport.h
@@ -22,6 +22,7 @@
 #include "Switch.h"
 #include "VxLan.h"
 #include "Geneve.h"
+#include "Erspan.h"
 
 #define OVS_MAX_DPPORTS             MAXUINT16
 #define OVS_DPPORT_NUMBER_INVALID   OVS_MAX_DPPORTS
@@ -181,7 +182,8 @@ OvsIsTunnelVportType(OVS_VPORT_TYPE ovsType)
     return ovsType == OVS_VPORT_TYPE_VXLAN ||
            ovsType == OVS_VPORT_TYPE_GENEVE ||
            ovsType == OVS_VPORT_TYPE_STT ||
-           ovsType == OVS_VPORT_TYPE_GRE;
+           ovsType == OVS_VPORT_TYPE_GRE ||
+           ovsType == OVS_VPORT_TYPE_ERSPAN;
 }
 
 
@@ -252,6 +254,7 @@ GetPortFromPriv(POVS_VPORT_ENTRY vport)
     ASSERT(vportPriv);
     switch(vport->ovsType) {
     case OVS_VPORT_TYPE_GRE:
+    case OVS_VPORT_TYPE_ERSPAN:
         break;
     case OVS_VPORT_TYPE_STT:
         dstPort = ((POVS_STT_VPORT)vportPriv)->dstPort;
@@ -265,7 +268,8 @@ GetPortFromPriv(POVS_VPORT_ENTRY vport)
     default:
         ASSERT(! "Port is not a tunnel port");
     }
-    ASSERT(dstPort || vport->ovsType == OVS_VPORT_TYPE_GRE);
+    ASSERT(dstPort || vport->ovsType == OVS_VPORT_TYPE_GRE ||
+           vport->ovsType == OVS_VPORT_TYPE_ERSPAN);
     return dstPort;
 }
 
diff --git a/datapath-windows/ovsext/ovsext.vcxproj
b/datapath-windows/ovsext/ovsext.vcxproj
index 7a2cbd2de..e19db7ae8 100644
--- a/datapath-windows/ovsext/ovsext.vcxproj
+++ b/datapath-windows/ovsext/ovsext.vcxproj
@@ -161,6 +161,7 @@
     <ClInclude Include="Flow.h" />
     <ClInclude Include="Geneve.h" />
     <ClInclude Include="Gre.h" />
+    <ClInclude Include="Erspan.h" />
     <ClInclude Include="IpFragment.h" />
     <ClInclude Include="IpHelper.h" />
     <ClInclude Include="Jhash.h" />
@@ -401,6 +402,7 @@
     <ClCompile Include="Conntrack.c" />
     <ClCompile Include="Debug.c" />
     <ClCompile Include="Driver.c" />
+    <ClCompile Include="Erspan.c" />
     <ClCompile Include="Event.c" />
     <ClCompile Include="Flow.c" />
     <ClCompile Include="Geneve.c" />
--
2.33.0.windows.2
diff mbox series

Patch

diff --git a/Documentation/faq/releases.rst b/Documentation/faq/releases.rst
index c12ffaf4a..af882f589 100644
--- a/Documentation/faq/releases.rst
+++ b/Documentation/faq/releases.rst
@@ -146,7 +146,7 @@  Q: Are all features available with all datapaths?
     Tunnel - GRE-IPv6               4.18           2.6          2.6      NO
     Tunnel - VXLAN-IPv6             4.3            2.6          2.6      NO
     Tunnel - Geneve-IPv6            4.4            2.6          2.6      2.18
-    Tunnel - ERSPAN                 4.18           2.10         2.10     NO
+    Tunnel - ERSPAN                 4.18           2.10         2.10     2.18
     Tunnel - ERSPAN-IPv6            4.18           2.10         2.10     NO
     Tunnel - GTP-U                  NO             NO           2.14     NO
     Tunnel - Bareudp                5.7            NO           NO       NO
diff --git a/NEWS b/NEWS
index eece0d0b2..8874b2592 100644
--- a/NEWS
+++ b/NEWS
@@ -29,6 +29,7 @@  Post-v2.17.0
    - Windows:
      * Conntrack support for TCPv6, UDPv6, ICMPv6, FTPv6.
      * IPv6 Geneve tunnel support.
+     * IPv4 ERSPAN tunnel support.
 
 
 v2.17.0 - 17 Feb 2022
diff --git a/datapath-windows/automake.mk b/datapath-windows/automake.mk
index 60b3d6033..2e040d799 100644
--- a/datapath-windows/automake.mk
+++ b/datapath-windows/automake.mk
@@ -28,6 +28,8 @@  EXTRA_DIST += \
 	datapath-windows/ovsext/Debug.h \
 	datapath-windows/ovsext/DpInternal.h\
 	datapath-windows/ovsext/Driver.c \
+	datapath-windows/ovsext/Erspan.c \
+	datapath-windows/ovsext/Erspan.h \
 	datapath-windows/ovsext/Ethernet.h \
 	datapath-windows/ovsext/Event.c \
 	datapath-windows/ovsext/Event.h \
diff --git a/datapath-windows/ovsext/Actions.c b/datapath-windows/ovsext/Actions.c
index 0f7f78932..425a61bda 100644
--- a/datapath-windows/ovsext/Actions.c
+++ b/datapath-windows/ovsext/Actions.c
@@ -34,6 +34,7 @@ 
 #include "Vport.h"
 #include "Vxlan.h"
 #include "Geneve.h"
+#include "Erspan.h"
 #include "IpFragment.h"
 
 #ifdef OVS_DBG_MOD
@@ -46,6 +47,8 @@ 
 typedef struct _OVS_ACTION_STATS {
     UINT64 rxGre;
     UINT64 txGre;
+    UINT64 rxErspan;
+    UINT64 txErspan;
     UINT64 rxVxlan;
     UINT64 txVxlan;
     UINT64 rxStt;
@@ -223,6 +226,9 @@  OvsDetectTunnelRxPkt(OvsForwardingContext *ovsFwdCtx,
             case OVS_VPORT_TYPE_GRE:
                 ovsActionStats.rxGre++;
                 break;
+            case OVS_VPORT_TYPE_ERSPAN:
+                ovsActionStats.rxErspan++;
+                break;
             }
         }
     }
@@ -310,6 +316,9 @@  OvsDetectTunnelPkt(OvsForwardingContext *ovsFwdCtx,
             case OVS_VPORT_TYPE_GRE:
                 ovsActionStats.txGre++;
                 break;
+            case OVS_VPORT_TYPE_ERSPAN:
+                ovsActionStats.txErspan++;
+                break;
             case OVS_VPORT_TYPE_VXLAN:
                 ovsActionStats.txVxlan++;
                 break;
@@ -665,6 +674,11 @@  OvsTunnelPortTx(OvsForwardingContext *ovsFwdCtx)
                              &ovsFwdCtx->tunKey, ovsFwdCtx->switchContext,
                              &ovsFwdCtx->layers, &newNbl, &switchFwdInfo);
         break;
+    case OVS_VPORT_TYPE_ERSPAN:
+        status = OvsEncapErspan(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,
@@ -762,6 +776,10 @@  OvsTunnelPortRx(OvsForwardingContext *ovsFwdCtx)
         status = OvsDecapGre(ovsFwdCtx->switchContext, ovsFwdCtx->curNbl,
                              &ovsFwdCtx->tunKey, &newNbl);
         break;
+    case OVS_VPORT_TYPE_ERSPAN:
+        status = OvsDecapErspan(ovsFwdCtx->switchContext, ovsFwdCtx->curNbl,
+                                &ovsFwdCtx->tunKey, &newNbl);
+        break;
     case OVS_VPORT_TYPE_VXLAN:
         status = OvsDecapVxlan(ovsFwdCtx->switchContext, ovsFwdCtx->curNbl,
                                &ovsFwdCtx->tunKey, &newNbl);
diff --git a/datapath-windows/ovsext/BufferMgmt.c b/datapath-windows/ovsext/BufferMgmt.c
index acf3c13a2..4bbedc38a 100644
--- a/datapath-windows/ovsext/BufferMgmt.c
+++ b/datapath-windows/ovsext/BufferMgmt.c
@@ -1610,7 +1610,7 @@  copymultiple_error:
  * OvsCompleteNBL --
  *
  *     This function tries to free the NBL allocated by OVS buffer
- *     management module. If it trigger the completion of the parent
+ *     management module. If it triggers the completion of the parent
  *     NBL, it will recursively call itself. If it trigger the completion
  *     of external NBL, it will be returned to the caller. The caller
  *     is responsible to call API to return to upper layer.
diff --git a/datapath-windows/ovsext/Erspan.c b/datapath-windows/ovsext/Erspan.c
new file mode 100644
index 000000000..6a57062d9
--- /dev/null
+++ b/datapath-windows/ovsext/Erspan.c
@@ -0,0 +1,403 @@ 
+/*
+ * Copyright (c) 2022 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 "Atomic.h"
+#include "Debug.h"
+#include "Flow.h"
+#include "Gre.h"
+#include "IpHelper.h"
+#include "NetProto.h"
+#include "Offload.h"
+#include "PacketIO.h"
+#include "PacketParser.h"
+#include "Switch.h"
+#include "User.h"
+#include "Util.h"
+#include "Vport.h"
+#include "Erspan.h"
+
+#ifdef OVS_DBG_MOD
+#undef OVS_DBG_MOD
+#endif
+#define OVS_DBG_MOD OVS_DBG_GRE
+
+static NDIS_STATUS
+OvsDoEncapErspan(POVS_VPORT_ENTRY vport,
+                 PNET_BUFFER_LIST curNbl,
+                 const OvsIPTunnelKey *tunKey,
+                 const POVS_FWD_INFO fwdInfo,
+                 POVS_PACKET_HDR_INFO layers,
+                 POVS_SWITCH_CONTEXT switchContext,
+                 PNET_BUFFER_LIST *newNbl);
+
+/*
+ * --------------------------------------------------------------------------
+ * OvsInitErspanTunnel --
+ *    Initialize ERSPAN tunnel module.
+ * --------------------------------------------------------------------------
+ */
+NTSTATUS
+OvsInitErspanTunnel(POVS_VPORT_ENTRY vport)
+{
+    POVS_ERSPAN_VPORT ersPort;
+    ersPort = (POVS_ERSPAN_VPORT)OvsAllocateMemoryWithTag(sizeof(*ersPort),
+                                                          OVS_ERSPAN_POOL_TAG);
+    if (!ersPort) {
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+
+    RtlZeroMemory(ersPort, sizeof(*ersPort));
+    vport->priv = (PVOID)ersPort;
+    return STATUS_SUCCESS;
+}
+
+/*
+ * --------------------------------------------------------------------------
+ * OvsCleanupErspanTunnel --
+ *    Cleanup ERSPAN Tunnel module.
+ * --------------------------------------------------------------------------
+ */
+void
+OvsCleanupErspanTunnel(POVS_VPORT_ENTRY vport)
+{
+    if (vport->ovsType != OVS_VPORT_TYPE_ERSPAN ||
+        vport->priv == NULL) {
+        return;
+    }
+    OvsFreeMemoryWithTag(vport->priv, OVS_ERSPAN_POOL_TAG);
+    vport->priv = NULL;
+}
+
+/*
+ * --------------------------------------------------------------------------
+ * OvsEncapErspan --
+ *     Encapsulates a packet with an ERSPAN header.
+ * --------------------------------------------------------------------------
+ */
+NDIS_STATUS
+OvsEncapErspan(POVS_VPORT_ENTRY vport,
+               PNET_BUFFER_LIST curNbl,
+               OvsIPTunnelKey *tunKey,
+               POVS_SWITCH_CONTEXT switchContext,
+               POVS_PACKET_HDR_INFO layers,
+               PNET_BUFFER_LIST *newNbl,
+               POVS_FWD_INFO switchFwdInfo)
+{
+    OVS_FWD_INFO fwdInfo;
+    NDIS_STATUS status;
+
+    if (tunKey->dst.si_family != AF_INET) {
+        return NDIS_STATUS_FAILURE;
+    }
+
+    status = OvsLookupIPhFwdInfo(tunKey->src, tunKey->dst, &fwdInfo);
+    if (status != STATUS_SUCCESS) {
+        OvsFwdIPHelperRequest(NULL, 0, tunKey, NULL, NULL, NULL);
+        return NDIS_STATUS_FAILURE;
+    }
+
+    RtlCopyMemory(switchFwdInfo->value, fwdInfo.value, sizeof fwdInfo.value);
+
+    status = OvsDoEncapErspan(vport, curNbl, tunKey, &fwdInfo, layers,
+                              switchContext, newNbl);
+    return status;
+}
+
+/*
+ * --------------------------------------------------------------------------
+ * OvsDoEncapErspan --
+ *    Internal utility function which actually does the ERSPAN encap.
+ * --------------------------------------------------------------------------
+ */
+NDIS_STATUS
+OvsDoEncapErspan(POVS_VPORT_ENTRY vport,
+                 PNET_BUFFER_LIST curNbl,
+                 const OvsIPTunnelKey *tunKey,
+                 const POVS_FWD_INFO fwdInfo,
+                 POVS_PACKET_HDR_INFO layers,
+                 POVS_SWITCH_CONTEXT switchContext,
+                 PNET_BUFFER_LIST *newNbl)
+{
+    NDIS_STATUS status;
+    PNET_BUFFER curNb;
+    PMDL curMdl;
+    PUINT8 bufferStart;
+    EthHdr *ethHdr;
+    IPHdr *ipHdr;
+    PGREHdr greHdr;
+    PERSPANHdr ersHdr;
+    POVS_ERSPAN_VPORT vportErs;
+    UINT32 headRoom = GreTunHdrSize(GRE_SEQ) + 8;
+    UINT32 packetLength;
+    ULONG mss = 0;
+    UINT32 seqno = 0;
+    ASSERT(*newNbl == NULL);
+
+    if (tunKey->flags & OVS_TNL_F_CSUM) {
+        OVS_LOG_ERROR("ERSPAN does not support GRE-CSUM");
+        return NDIS_STATUS_FAILURE;
+    }
+
+    curNb = NET_BUFFER_LIST_FIRST_NB(curNbl);
+    packetLength = NET_BUFFER_DATA_LENGTH(curNb);
+
+    if (layers->isTcp) {
+        mss = OVSGetTcpMSS(curNbl);
+
+        OVS_LOG_TRACE("MSS %u packet len %u", mss,
+                      packetLength);
+        if (mss) {
+            OVS_LOG_TRACE("l4Offset %d", layers->l4Offset);
+            *newNbl = OvsTcpSegmentNBL(switchContext, curNbl, layers,
+                                       mss, headRoom, FALSE);
+            if (*newNbl == NULL) {
+                OVS_LOG_ERROR("Unable to segment NBL");
+                return NDIS_STATUS_FAILURE;
+            }
+            /* Clear out LSO flags after this point */
+            NET_BUFFER_LIST_INFO(*newNbl, TcpLargeSendNetBufferListInfo) = 0;
+        }
+    }
+
+    vportErs = (POVS_ERSPAN_VPORT)GetOvsVportPriv(vport);
+    ASSERT(vportErs);
+
+    /* If we didn't split the packet above, make a copy now. */
+    if (*newNbl == NULL) {
+        *newNbl = OvsPartialCopyNBL(switchContext, curNbl, 0, headRoom,
+                                    FALSE /* NBL info */);
+        if (*newNbl == NULL) {
+            OVS_LOG_ERROR("Unable to copy NBL");
+            return NDIS_STATUS_FAILURE;
+        }
+
+        NDIS_TCP_IP_CHECKSUM_NET_BUFFER_LIST_INFO csumInfo;
+        csumInfo.Value = NET_BUFFER_LIST_INFO(curNbl,
+                                              TcpIpChecksumNetBufferListInfo);
+        status = OvsApplySWChecksumOnNB(layers, *newNbl, &csumInfo);
+        if (status != NDIS_STATUS_SUCCESS) {
+            goto ret_error;
+        }
+    }
+
+    curNbl = *newNbl;
+    for (curNb = NET_BUFFER_LIST_FIRST_NB(curNbl); curNb != NULL;
+         curNb = curNb->Next) {
+        status = NdisRetreatNetBufferDataStart(curNb, headRoom, 0, NULL);
+        if (status != NDIS_STATUS_SUCCESS) {
+            goto ret_error;
+        }
+
+        curMdl = NET_BUFFER_CURRENT_MDL(curNb);
+        bufferStart = (PUINT8)OvsGetMdlWithLowPriority(curMdl);
+        if (!bufferStart) {
+            status = NDIS_STATUS_RESOURCES;
+            goto ret_error;
+        }
+
+        bufferStart += NET_BUFFER_CURRENT_MDL_OFFSET(curNb);
+        if (NET_BUFFER_NEXT_NB(curNb)) {
+            OVS_LOG_TRACE("nb length %u next %u",
+                          NET_BUFFER_DATA_LENGTH(curNb),
+                          NET_BUFFER_DATA_LENGTH(curNb->Next));
+        }
+
+        /* Outer L2 header */
+        ethHdr = (EthHdr *)bufferStart;
+        NdisMoveMemory(ethHdr->Destination, fwdInfo->dstMacAddr,
+                       sizeof ethHdr->Destination);
+        NdisMoveMemory(ethHdr->Source, fwdInfo->srcMacAddr,
+                       sizeof ethHdr->Source);
+        ethHdr->Type = htons(ETH_TYPE_IPV4);
+
+        /* Outer IP header */
+        ipHdr = (IPHdr *)((PCHAR)ethHdr + sizeof *ethHdr);
+        ipHdr->ihl = sizeof *ipHdr / 4;
+        ipHdr->version = IPPROTO_IPV4;
+        ipHdr->tos = tunKey->tos;
+        ipHdr->tot_len = htons(NET_BUFFER_DATA_LENGTH(curNb) - sizeof *ethHdr);
+        ipHdr->id = (uint16)atomic_add64(&vportErs->ipId,
+                                         NET_BUFFER_DATA_LENGTH(curNb));
+        ipHdr->frag_off = (tunKey->flags & OVS_TNL_F_DONT_FRAGMENT) ?
+                          IP_DF_NBO : 0;
+        ipHdr->ttl = tunKey->ttl ? tunKey->ttl : 64;
+        ipHdr->protocol = IPPROTO_GRE;
+
+        ASSERT(OvsIphAddrEquals(&tunKey->dst, &fwdInfo->dstIphAddr));
+        ASSERT(OvsIphAddrEquals(&tunKey->src, &fwdInfo->srcIphAddr) ||
+               OvsIphIsZero(&tunKey->src));
+
+        ipHdr->saddr = fwdInfo->srcIphAddr.Ipv4.sin_addr.s_addr;
+        ipHdr->daddr = fwdInfo->dstIphAddr.Ipv4.sin_addr.s_addr;
+
+        ipHdr->check = 0;
+        ipHdr->check = IPChecksum((UINT8 *)ipHdr, sizeof *ipHdr, 0);
+
+        /* GRE base header */
+        greHdr = (GREHdr *)((PCHAR)ipHdr + sizeof *ipHdr);
+        greHdr->flags = GRE_SEQ; /* ERSPAN has fixed 8B GRE header */
+        greHdr->protocolType = htons(ETH_P_ERSPAN);
+
+        /* GRE sequence number */
+        PCHAR currentOffset = (PCHAR)greHdr + sizeof *greHdr;
+        seqno = htonl(vportErs->seqno++);
+        RtlCopyMemory(currentOffset, &seqno, sizeof seqno);
+
+        /* Build ERSPAN base header */
+        ersHdr = (ERSPANHdr *)((PCHAR)greHdr + 8);
+        ersHdr->ver = ERSPAN_VERSION;
+        ersHdr->cos = TosToCos(ipHdr->tos);
+        ersHdr->en = ERSPAN_ENCAP_NOVLAN;
+        ersHdr->t = 0;
+
+        SetVlan(ersHdr, vportErs->vlan);
+        /* Use tunnel ID as session ID */
+        UINT16 key = (UINT16)(tunKey->tunnelId >> 32);
+        SetSessionId(ersHdr, key);
+        ersHdr->index = 0;
+    }
+    return STATUS_SUCCESS;
+
+ret_error:
+    OvsCompleteNBL(switchContext, *newNbl, TRUE);
+    *newNbl = NULL;
+    return status;
+}
+
+/*
+ * --------------------------------------------------------------------------
+ * OvsDecapErspan --
+ *    Decapsulates a packet with an ERSPAN header.
+ * --------------------------------------------------------------------------
+ */
+NDIS_STATUS
+OvsDecapErspan(POVS_SWITCH_CONTEXT switchContext,
+               PNET_BUFFER_LIST curNbl,
+               OvsIPTunnelKey *tunKey,
+               PNET_BUFFER_LIST *newNbl)
+{
+    PNET_BUFFER curNb;
+    PMDL curMdl;
+    EthHdr *ethHdr;
+    IPHdr *ipHdr;
+    GREHdr *greHdr;
+    ERSPANHdr *ersHdr;
+    UINT32 tunnelSize, packetLength;
+    UINT32 maxGreLen;
+    PUINT8 bufferStart;
+    NDIS_STATUS status = NDIS_STATUS_SUCCESS;
+    PCHAR tempBuf = NULL;
+    OVS_PACKET_HDR_INFO layers;
+    const UINT32 greHdrLen = 8, ersHdrLen = 8;
+
+    ASSERT(*newNbl == NULL);
+    *newNbl = NULL;
+
+    status = OvsExtractLayers(curNbl, &layers);
+    if (status != NDIS_STATUS_SUCCESS) {
+        return status;
+    }
+
+    curNb = NET_BUFFER_LIST_FIRST_NB(curNbl);
+    packetLength = NET_BUFFER_DATA_LENGTH(curNb);
+    curMdl = NET_BUFFER_CURRENT_MDL(curNb);
+    tunnelSize = greHdrLen + ersHdrLen;
+    if (packetLength <= tunnelSize) {
+        return NDIS_STATUS_INVALID_LENGTH;
+    }
+    maxGreLen = GreMaxLengthFromLayers(&layers) + ersHdrLen;
+
+    /* Get a contiguous buffer for the maximum length of a GRE header */
+    bufferStart = NdisGetDataBuffer(curNb, maxGreLen, NULL, 1, 0);
+    if (!bufferStart) {
+        /* Documentation is unclear on where the packet can be fragmented.
+         * For the moment allocate the buffer needed to get the maximum length
+         * of a GRE header contiguous */
+         tempBuf = OvsAllocateMemoryWithTag(maxGreLen, OVS_GRE_POOL_TAG);
+         if (!tempBuf) {
+            status = NDIS_STATUS_RESOURCES;
+            goto end;
+         }
+         RtlZeroMemory(tempBuf, maxGreLen);
+         bufferStart = NdisGetDataBuffer(curNb, maxGreLen, tempBuf,
+                                         1, 0);
+         if (!bufferStart) {
+            status = NDIS_STATUS_RESOURCES;
+            goto end;
+         }
+    }
+
+    ethHdr = (EthHdr *)bufferStart;
+    ipHdr = (IPHdr *)(bufferStart + layers.l3Offset);
+
+    tunKey->src.Ipv4.sin_addr.s_addr = ipHdr->saddr;
+    tunKey->src.Ipv4.sin_family = AF_INET;
+    tunKey->dst.Ipv4.sin_addr.s_addr = ipHdr->daddr;
+    tunKey->dst.Ipv4.sin_family = AF_INET;
+    tunKey->tos = ipHdr->tos;
+    tunKey->ttl = ipHdr->ttl;
+    tunKey->pad = 0;
+
+    greHdr = (GREHdr *)(bufferStart + layers.l4Offset);
+    ersHdr = (ERSPANHdr *)(bufferStart + layers.l4Offset + ersHdrLen);
+
+    tunnelSize = GreTunHdrSizeFromLayers(greHdr->flags, &layers) + 8;
+
+    /* Verify the packet length after looking at the GRE flags */
+    if (packetLength <= tunnelSize) {
+        status = NDIS_STATUS_INVALID_LENGTH;
+        goto end;
+    }
+
+    /* Validate if ERSPAN header protocol type */
+    if (greHdr->protocolType != htons(ETH_P_ERSPAN)) {
+        status = STATUS_NDIS_INVALID_PACKET;
+        goto end;
+    }
+
+    if (greHdr->flags & GRE_KEY || greHdr->flags & GRE_CSUM) {
+        status = STATUS_NDIS_INVALID_PACKET;
+        goto end;
+    }
+
+    /*
+     * Create a copy of the NBL so that we have all the headers in one MDL.
+     */
+    *newNbl = OvsPartialCopyNBL(switchContext, curNbl,
+                                tunnelSize, 0,
+                                TRUE /* copy NBL info */);
+    if (*newNbl == NULL) {
+        status = NDIS_STATUS_RESOURCES;
+        goto end;
+    }
+    curNbl = *newNbl;
+    curNb = NET_BUFFER_LIST_FIRST_NB(curNbl);
+    tunKey->tunnelId = (UINT64)ntohl(ersHdr->session_id) << 32;
+
+    /* Clear out the receive flag for the inner packet. */
+    NET_BUFFER_LIST_INFO(curNbl, TcpIpChecksumNetBufferListInfo) = 0;
+    NdisAdvanceNetBufferDataStart(curNb, GreTunHdrSize(GRE_SEQ) + ersHdrLen, FALSE,
+                                  NULL);
+end:
+    if (tempBuf) {
+        OvsFreeMemoryWithTag(tempBuf, OVS_ERSPAN_POOL_TAG);
+        tempBuf = NULL;
+    }
+
+    return status;
+}
diff --git a/datapath-windows/ovsext/Erspan.h b/datapath-windows/ovsext/Erspan.h
new file mode 100644
index 000000000..0c91148e0
--- /dev/null
+++ b/datapath-windows/ovsext/Erspan.h
@@ -0,0 +1,197 @@ 
+/*
+ * Copyright (c) 2022 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.
+ */
+
+#ifndef __ERSPAN_H_
+#define __ERSPAN_H_ 1
+
+#include "Flow.h"
+#include "IpHelper.h"
+#include "NetProto.h"
+
+/*
+ * GRE header for ERSPAN type I encapsulation (4 octets [34:37])
+ *      0                   1                   2                   3
+ *      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ *     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *     |0|0|0|0|0|00000|000000000|00000|    Protocol Type for ERSPAN   |
+ *     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ *  The Type I ERSPAN frame format is based on the barebones IP + GRE
+ *  encapsulation (as described above) on top of the raw mirrored frame.
+ *  There is no extra ERSPAN header.
+ *
+ *
+ * GRE header for ERSPAN type II and II encapsulation (8 octets [34:41])
+ *       0                   1                   2                   3
+ *      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ *     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *     |0|0|0|1|0|00000|000000000|00000|    Protocol Type for ERSPAN   |
+ *     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *     |      Sequence Number (increments per packet per session)      |
+ *     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ *  Note that in the above GRE header [RFC1701] out of the C, R, K, S,
+ *  s, Recur, Flags, Version fields only S (bit 03) is set to 1. The
+ *  other fields are set to zero, so only a sequence number follows.
+ *
+ *  ERSPAN Version 1 (Type II) header (8 octets [42:49])
+ *  0                   1                   2                   3
+ *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |  Ver  |          VLAN         | COS | En|T|    Session ID     |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |      Reserved         |                  Index                |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ *
+ *  ERSPAN Version 2 (Type III) header (12 octets [42:49])
+ *  0                   1                   2                   3
+ *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |  Ver  |          VLAN         | COS |BSO|T|     Session ID    |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                          Timestamp                            |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |             SGT               |P|    FT   |   Hw ID   |D|Gra|O|
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ *      Platform Specific SubHeader (8 octets, optional)
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |  Platf ID |               Platform Specific Info              |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                  Platform Specific Info                       |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * GRE proto ERSPAN type I/II = 0x88BE, type III = 0x22EB
+ */
+
+typedef struct _OVS_ERSPAN_VPORT {
+    UINT64 ipId;
+    UINT32 seqno;
+    UINT16 vlan;
+    UINT8 index;
+} OVS_ERSPAN_VPORT, *POVS_ERSPAN_VPORT;
+
+typedef struct _ERSPANHdr {
+#ifdef WORDS_BIGENDIAN
+    UINT8 ver:4,
+          vlan_upper:4;
+    UINT8 vlan:8;
+    UINT8 cos:3,
+          en:2,
+          t:1,
+          session_id_upper:2;
+    UINT8 session_id:8;
+#else
+    UINT8 vlan_upper:4,
+          ver:4;
+    UINT8 vlan:8;
+    UINT8 session_id_upper:2,
+          t:1,
+          en:2,
+          cos:3;
+    UINT8 session_id:8;
+#endif
+    UINT32 index;
+} ERSPANHdr, *PERSPANHdr;
+
+
+#define ETH_P_ERSPAN    0x88BE
+#define ETH_P_ERSPAN2   0x22EB
+#define ERSPAN_VERSION  0x1
+
+#define VER_MASK    0xf000
+#define VLAN_MASK   0x0fff
+#define COS_MASK    0xe000
+#define EN_MASK     0x1800
+#define T_MASK      0x0400
+#define ID_MASK     0x03ff
+#define INDEX_MASK  0xfffff
+
+#define ERSPAN_VERSION2 0x2 /* ERSPAN type III*/
+#define BSO_MASK    EN_MASK
+#define SGT_MASK    0xffff0000
+#define P_MASK      0x8000
+#define FT_MASK     0x7c00
+#define HWID_MASK   0x03f0
+#define DIR_MASK    0x0008
+#define GRA_MASK    0x0006
+#define O_MASK      0x0001
+
+#define HWID_OFFSET    4
+#define DIR_OFFSET     3
+
+enum erspan_encap_type {
+    ERSPAN_ENCAP_NOVLAN = 0x0,  /* originally without VLAN tag */
+    ERSPAN_ENCAP_ISL = 0x1,     /* originally ISL encapsulated */
+    ERSPAN_ENCAP_8021Q = 0x2,   /* originally 802.1Q encapsulated */
+    ERSPAN_ENCAP_INFRAME = 0x3, /* VLAN tag perserved in frame */
+};
+
+NTSTATUS OvsInitErspanTunnel(POVS_VPORT_ENTRY vport);
+
+VOID OvsCleanupErspanTunnel(POVS_VPORT_ENTRY vport);
+
+NDIS_STATUS OvsEncapErspan(POVS_VPORT_ENTRY vport,
+    PNET_BUFFER_LIST curNbl,
+    OvsIPTunnelKey* tunKey,
+    POVS_SWITCH_CONTEXT switchContext,
+    POVS_PACKET_HDR_INFO layers,
+    PNET_BUFFER_LIST* newNbl,
+    POVS_FWD_INFO switchFwdInfo);
+
+NDIS_STATUS OvsDecapErspan(POVS_SWITCH_CONTEXT switchContext,
+    PNET_BUFFER_LIST curNbl,
+    OvsIPTunnelKey* tunKey,
+    PNET_BUFFER_LIST* newNbl);
+
+static inline void
+SetSessionId(ERSPANHdr *ershdr, UINT16 id)
+{
+    ershdr->session_id = id & 0xff;
+    ershdr->session_id_upper = (id >> 8) & 0x3;
+}
+
+static inline UINT16
+GetSessionId(const ERSPANHdr *ershdr)
+{
+    return (ershdr->session_id_upper << 8) + ershdr->session_id;
+}
+
+static inline void
+SetVlan(ERSPANHdr *ershdr, UINT16 vlan)
+{
+    ershdr->vlan = vlan & 0xff;
+    ershdr->vlan_upper = (vlan >> 8) & 0xf;
+}
+
+static inline UINT16
+GetVlan(const ERSPANHdr *ershdr)
+{
+    return (ershdr->vlan_upper << 8) + ershdr->vlan;
+}
+
+static inline UINT8
+TosToCos(UINT8 tos)
+{
+    UINT8 dscp, cos;
+
+    dscp = tos >> 2;
+    cos = dscp >> 3;
+    return cos;
+}
+
+#endif /* __ERSPAN_H_ */
diff --git a/datapath-windows/ovsext/Flow.c b/datapath-windows/ovsext/Flow.c
index 08fba4c4d..96c32561b 100644
--- a/datapath-windows/ovsext/Flow.c
+++ b/datapath-windows/ovsext/Flow.c
@@ -1914,6 +1914,10 @@  OvsTunnelAttrToIPTunnelKey(PNL_ATTR attr,
             tunKey->flags |= OVS_TNL_F_GENEVE_OPT;
             hasOpt = 1;
             break;
+        case OVS_TUNNEL_KEY_ATTR_ERSPAN_OPTS:
+            tunKey->flags |= OVS_TNL_F_ERSPAN_OPT;
+            hasOpt = 1;
+            break;
         default:
             // XXX: Support OVS_TUNNEL_KEY_ATTR_VXLAN_OPTS
             return STATUS_INVALID_PARAMETER;
@@ -3263,6 +3267,9 @@  OvsTunKeyAttrSize(void)
          + NlAttrTotalSize(0)    /* OVS_TUNNEL_KEY_ATTR_CSUM */
          + NlAttrTotalSize(0)    /* OVS_TUNNEL_KEY_ATTR_OAM */
          + NlAttrTotalSize(256)  /* OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS */
+         /* Mutually exclusive with OVS_TUNNEL_KEY_ATTR_VXLAN_OPTS,
+          * and OVS_TUNNEL_KEY_ATTR_ERSPAN_OPTS
+          */
          + NlAttrTotalSize(2)    /* OVS_TUNNEL_KEY_ATTR_TP_SRC */
          + NlAttrTotalSize(2);   /* OVS_TUNNEL_KEY_ATTR_TP_DST */
 }
diff --git a/datapath-windows/ovsext/Flow.h b/datapath-windows/ovsext/Flow.h
index 8f7214124..4b45546a7 100644
--- a/datapath-windows/ovsext/Flow.h
+++ b/datapath-windows/ovsext/Flow.h
@@ -97,7 +97,9 @@  OvsTunnelAttrToIPTunnelKey(PNL_ATTR attr, OvsIPTunnelKey *tunKey);
 #define OVS_TNL_F_CRT_OPT               (1 << 4)
 #define OVS_TNL_F_GENEVE_OPT            (1 << 5)
 #define OVS_TNL_F_VXLAN_OPT             (1 << 6)
+#define OVS_TNL_F_ERSPAN_OPT             (1 << 7)
 
-#define OVS_TNL_HAS_OPTIONS             (OVS_TNL_F_GENEVE_OPT | OVS_TNL_F_VXLAN_OPT)
+#define OVS_TNL_HAS_OPTIONS             (OVS_TNL_F_GENEVE_OPT | OVS_TNL_F_VXLAN_OPT | \
+                                         OVS_TNL_F_ERSPAN_OPT)
 
 #endif /* __FLOW_H_ */
diff --git a/datapath-windows/ovsext/Gre.h b/datapath-windows/ovsext/Gre.h
index 144b6195e..37adf03a4 100644
--- a/datapath-windows/ovsext/Gre.h
+++ b/datapath-windows/ovsext/Gre.h
@@ -44,7 +44,7 @@  typedef struct _OVS_GRE_VPORT {
  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  */
 
-typedef struct GREHdr {
+typedef struct _GREHdr {
     UINT16 flags;
     UINT16 protocolType;
 } GREHdr, *PGREHdr;
@@ -54,6 +54,7 @@  typedef struct GREHdr {
 /* GRE Flags*/
 #define GRE_CSUM    0x0080
 #define GRE_KEY     0x0020
+#define GRE_SEQ     0x0010
 /* The maximum GRE header length that we can process */
 #define OVS_MAX_GRE_LGTH (sizeof(EthHdr) + sizeof(IPHdr) + sizeof(GREHdr) + 12)
 
@@ -99,6 +100,7 @@  GreTunHdrSize(UINT16 flags)
     UINT32 sum = sizeof(EthHdr) + sizeof(IPHdr) + sizeof(GREHdr);
     sum += (flags & GRE_CSUM) ? 4 : 0;
     sum += (flags & GRE_KEY) ? 4 : 0;
+    sum += (flags & GRE_SEQ) ? 4 : 0;
 
     return sum;
 }
@@ -109,6 +111,7 @@  GreTunHdrSizeFromLayers(UINT16 flags, POVS_PACKET_HDR_INFO layers)
     UINT32 sum = layers->l4Offset + sizeof(GREHdr);
     sum += (flags & GRE_CSUM) ? 4 : 0;
     sum += (flags & GRE_KEY) ? 4 : 0;
+    sum += (flags & GRE_SEQ) ? 4 : 0;
 
     return sum;
 }
diff --git a/datapath-windows/ovsext/Util.h b/datapath-windows/ovsext/Util.h
index f63a885a9..807a4695e 100644
--- a/datapath-windows/ovsext/Util.h
+++ b/datapath-windows/ovsext/Util.h
@@ -35,6 +35,7 @@ 
 #define OVS_VPORT_POOL_TAG              'PSVO'
 #define OVS_STT_POOL_TAG                'RSVO'
 #define OVS_GRE_POOL_TAG                'GSVO'
+#define OVS_ERSPAN_POOL_TAG             'GSVO'
 #define OVS_TUNFLT_POOL_TAG             'WSVO'
 #define OVS_RECIRC_POOL_TAG             'CSVO'
 #define OVS_CT_POOL_TAG                 'CTVO'
diff --git a/datapath-windows/ovsext/Vport.c b/datapath-windows/ovsext/Vport.c
index 9f1587f44..5d325dd88 100644
--- a/datapath-windows/ovsext/Vport.c
+++ b/datapath-windows/ovsext/Vport.c
@@ -28,6 +28,7 @@ 
 #include "Vport.h"
 #include "Vxlan.h"
 #include "Geneve.h"
+#include "Erspan.h"
 
 #ifdef OVS_DBG_MOD
 #undef OVS_DBG_MOD
@@ -1088,6 +1089,9 @@  OvsInitTunnelVport(PVOID userContext,
     case OVS_VPORT_TYPE_GRE:
         status = OvsInitGreTunnel(vport);
         break;
+    case OVS_VPORT_TYPE_ERSPAN:
+        status = OvsInitErspanTunnel(vport);
+        break;
     case OVS_VPORT_TYPE_VXLAN:
     {
         POVS_TUNFLT_INIT_CONTEXT tunnelContext = NULL;
@@ -1253,6 +1257,7 @@  InitOvsVportCommon(POVS_SWITCH_CONTEXT switchContext,
 
     switch(vport->ovsType) {
     case OVS_VPORT_TYPE_GRE:
+    case OVS_VPORT_TYPE_ERSPAN:
     case OVS_VPORT_TYPE_VXLAN:
     case OVS_VPORT_TYPE_STT:
     case OVS_VPORT_TYPE_GENEVE:
@@ -1342,6 +1347,9 @@  OvsRemoveAndDeleteVport(PVOID usrParamsContext,
     case OVS_VPORT_TYPE_GRE:
         OvsCleanupGreTunnel(vport);
         break;
+    case OVS_VPORT_TYPE_ERSPAN:
+        OvsCleanupErspanTunnel(vport);
+        break;
     case OVS_VPORT_TYPE_NETDEV:
         if (vport->isExternal) {
             if (vport->nicIndex == 0) {
@@ -2292,6 +2300,9 @@  OvsNewVportCmdHandler(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
             case OVS_VPORT_TYPE_GRE:
                 nwProto = IPPROTO_GRE;
                 break;
+            case OVS_VPORT_TYPE_ERSPAN:
+                nwProto = IPPROTO_GRE;
+                break;
             case OVS_VPORT_TYPE_VXLAN:
                 transportPortDest = VXLAN_UDP_PORT;
                 nwProto = IPPROTO_UDP;
diff --git a/datapath-windows/ovsext/Vport.h b/datapath-windows/ovsext/Vport.h
index 32cbf8bcc..516cfc1d8 100644
--- a/datapath-windows/ovsext/Vport.h
+++ b/datapath-windows/ovsext/Vport.h
@@ -22,6 +22,7 @@ 
 #include "Switch.h"
 #include "VxLan.h"
 #include "Geneve.h"
+#include "Erspan.h"
 
 #define OVS_MAX_DPPORTS             MAXUINT16
 #define OVS_DPPORT_NUMBER_INVALID   OVS_MAX_DPPORTS
@@ -181,7 +182,8 @@  OvsIsTunnelVportType(OVS_VPORT_TYPE ovsType)
     return ovsType == OVS_VPORT_TYPE_VXLAN ||
            ovsType == OVS_VPORT_TYPE_GENEVE ||
            ovsType == OVS_VPORT_TYPE_STT ||
-           ovsType == OVS_VPORT_TYPE_GRE;
+           ovsType == OVS_VPORT_TYPE_GRE ||
+           ovsType == OVS_VPORT_TYPE_ERSPAN;
 }
 
 
@@ -252,6 +254,7 @@  GetPortFromPriv(POVS_VPORT_ENTRY vport)
     ASSERT(vportPriv);
     switch(vport->ovsType) {
     case OVS_VPORT_TYPE_GRE:
+    case OVS_VPORT_TYPE_ERSPAN:
         break;
     case OVS_VPORT_TYPE_STT:
         dstPort = ((POVS_STT_VPORT)vportPriv)->dstPort;
@@ -265,7 +268,8 @@  GetPortFromPriv(POVS_VPORT_ENTRY vport)
     default:
         ASSERT(! "Port is not a tunnel port");
     }
-    ASSERT(dstPort || vport->ovsType == OVS_VPORT_TYPE_GRE);
+    ASSERT(dstPort || vport->ovsType == OVS_VPORT_TYPE_GRE ||
+           vport->ovsType == OVS_VPORT_TYPE_ERSPAN);
     return dstPort;
 }
 
diff --git a/datapath-windows/ovsext/ovsext.vcxproj b/datapath-windows/ovsext/ovsext.vcxproj
index 7a2cbd2de..e19db7ae8 100644
--- a/datapath-windows/ovsext/ovsext.vcxproj
+++ b/datapath-windows/ovsext/ovsext.vcxproj
@@ -161,6 +161,7 @@ 
     <ClInclude Include="Flow.h" />
     <ClInclude Include="Geneve.h" />
     <ClInclude Include="Gre.h" />
+    <ClInclude Include="Erspan.h" />
     <ClInclude Include="IpFragment.h" />
     <ClInclude Include="IpHelper.h" />
     <ClInclude Include="Jhash.h" />
@@ -401,6 +402,7 @@ 
     <ClCompile Include="Conntrack.c" />
     <ClCompile Include="Debug.c" />
     <ClCompile Include="Driver.c" />
+    <ClCompile Include="Erspan.c" />
     <ClCompile Include="Event.c" />
     <ClCompile Include="Flow.c" />
     <ClCompile Include="Geneve.c" />