From patchwork Sat May 28 13:04:33 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: William Tu X-Patchwork-Id: 1636605 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: bilbo.ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20210112 header.b=ojesCXs3; dkim-atps=neutral Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=140.211.166.138; helo=smtp1.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Received: from smtp1.osuosl.org (smtp1.osuosl.org [140.211.166.138]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by bilbo.ozlabs.org (Postfix) with ESMTPS id 4L9MNB52Hjz9sFr for ; Sat, 28 May 2022 23:05:14 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id 52F2884CC2; Sat, 28 May 2022 13:05:12 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id uO99kbQ5g53s; Sat, 28 May 2022 13:05:10 +0000 (UTC) Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [IPv6:2605:bc80:3010:104::8cd3:938]) by smtp1.osuosl.org (Postfix) with ESMTPS id 6B58784CAD; Sat, 28 May 2022 13:05:09 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 3B95DC0039; Sat, 28 May 2022 13:05:09 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp4.osuosl.org (smtp4.osuosl.org [140.211.166.137]) by lists.linuxfoundation.org (Postfix) with ESMTP id 504ACC002D for ; Sat, 28 May 2022 13:05:08 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id 2D4C342A36 for ; Sat, 28 May 2022 13:05:08 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Authentication-Results: smtp4.osuosl.org (amavisd-new); dkim=pass (2048-bit key) header.d=gmail.com Received: from smtp4.osuosl.org ([127.0.0.1]) by localhost (smtp4.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id PH50VtfoSXKv for ; Sat, 28 May 2022 13:05:06 +0000 (UTC) X-Greylist: whitelisted by SQLgrey-1.8.0 Received: from mail-pj1-x102f.google.com (mail-pj1-x102f.google.com [IPv6:2607:f8b0:4864:20::102f]) by smtp4.osuosl.org (Postfix) with ESMTPS id 1C68842A0B for ; Sat, 28 May 2022 13:05:06 +0000 (UTC) Received: by mail-pj1-x102f.google.com with SMTP id q92-20020a17090a17e500b001e0817e77f6so9355150pja.5 for ; Sat, 28 May 2022 06:05:06 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=7hjXCGmrimEHXH1YtSGC9jezbFpWltuPJDKRRKZl/LQ=; b=ojesCXs3O4rgmr7CzFozqwwisSrXxhi/Ut6vzirTrGuefm6VZXeuspr3F0uQLmhT36 QWCj3hO8K1eNhMucre7S02nJPPcBtOKG3YdX67OYxUCzFmYCF2PD4rQxCeshAyEjGrGx MXzfW+uX3qlTRrVHcFLqanq8c/WKm3Kk4SieY2FkDvNY8zDCCqnZQejgumuZmOYFFJ1r 9/846+HlExyMC6IW+hw7muBAqlQDI6mvD+W7GGwz8U/hLuL2spUPUqQBQqzzD0viEDMQ 17WlMzeXSjFJh37Gn/RW3MxLHVnyK1kuzzTjRHcEFl8yCqIhZ1S7bhId9PjmLYRXRLao YSBA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=7hjXCGmrimEHXH1YtSGC9jezbFpWltuPJDKRRKZl/LQ=; b=6xZRVdw2cDhsjyJzLtTkCzyEqTpEdDCqGASm6EEjIL+HcKxxG04jWuPv3liRTRg2Ew t6brnrtLDm/rMmkk9KdfYJBo5U0RShR/aDFIJgqGhqwsUFYoM458fy3mYUHYfAMkYwZW x1I4wjdQ5uM2udQ8A8m9l6SGfAXcQ+dW0btjjTg76HneEB2CiyNeb/wMvr+PdBH+iyLM /K0UAH9oXVsfH85wTmJ1TaMLZMT7RH0yP7bO8GKdNfA9BVbSXdOpy3dgZbJvGCs2Uag/ +K7X0Ax9f+UpU3RVguueR/fEPGcj05NaBv6FoC7mfAiU6Uv8TKXt0/OsBo6WReEMzE/a n/Iw== X-Gm-Message-State: AOAM530NFOBNKeJWZmKNyvA+u86w0e5/BuL3VR5R3+sB/nwosQjJ9WSc /L3CF8+bLo4XLfJ+GOlkS6L1TK7R6to= X-Google-Smtp-Source: ABdhPJwGQN2d3nIxxlOqdpsSTVGCNBaR7H8q1WLNWyupCohIyiuYwyJDH7WhN3r0oynmKH9mObjZ4g== X-Received: by 2002:a17:90a:4a03:b0:1df:4583:cb26 with SMTP id e3-20020a17090a4a0300b001df4583cb26mr13345397pjh.173.1653743104510; Sat, 28 May 2022 06:05:04 -0700 (PDT) Received: from sc9-mailhost3.vmware.com ([66.170.99.2]) by smtp.gmail.com with ESMTPSA id i10-20020a17090332ca00b0016156e0c3cbsm5705825plr.156.2022.05.28.06.05.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 28 May 2022 06:05:03 -0700 (PDT) From: William Tu To: dev@openvswitch.org Date: Sat, 28 May 2022 06:04:33 -0700 Message-Id: <20220528130433.713-1-u9012063@gmail.com> X-Mailer: git-send-email 2.33.0.windows.2 MIME-Version: 1.0 Subject: [ovs-dev] [PATCH] datapath-windows: Add ERSPAN v1 support. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" 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 Signed-off-by: William Tu --- 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 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 1e107340f..101965b28 100644 --- a/NEWS +++ b/NEWS @@ -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 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..51e447d30 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..3526753b0 --- /dev/null +++ b/datapath-windows/ovsext/Erspan.c @@ -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; +} diff --git a/datapath-windows/ovsext/Erspan.h b/datapath-windows/ovsext/Erspan.h new file mode 100644 index 000000000..d52c26c9b --- /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 @@ + @@ -401,6 +402,7 @@ +