@@ -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
@@ -26,6 +26,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
@@ -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 \
@@ -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);
@@ -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.
new file mode 100644
@@ -0,0 +1,404 @@
+/*
+ * 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;
+}
new file mode 100644
@@ -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_ */
@@ -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 */
}
@@ -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_ */
@@ -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;
}
@@ -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'
@@ -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;
@@ -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;
}
@@ -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" />
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> --- 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 | 404 +++++++++++++++++++++++++ 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, 658 insertions(+), 6 deletions(-) create mode 100644 datapath-windows/ovsext/Erspan.c create mode 100644 datapath-windows/ovsext/Erspan.h