From patchwork Mon May 30 03:42:18 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: William Tu X-Patchwork-Id: 1636778 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=mMo3ozCe; dkim-atps=neutral Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::138; helo=smtp1.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Received: from smtp1.osuosl.org (smtp1.osuosl.org [IPv6:2605:bc80:3010::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 4LBLpH1jRBz9s5V for ; Mon, 30 May 2022 13:42:47 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id 2F7A584174; Mon, 30 May 2022 03:42:45 +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 MpADfoYmTk5c; Mon, 30 May 2022 03:42:43 +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 3D72C83F35; Mon, 30 May 2022 03:42:42 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 14238C0032; Mon, 30 May 2022 03:42:42 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp1.osuosl.org (smtp1.osuosl.org [140.211.166.138]) by lists.linuxfoundation.org (Postfix) with ESMTP id 4E4CFC002D for ; Mon, 30 May 2022 03:42:41 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id 3485283F57 for ; Mon, 30 May 2022 03:42:41 +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 oEVSfUwehTkW for ; Mon, 30 May 2022 03:42:39 +0000 (UTC) X-Greylist: whitelisted by SQLgrey-1.8.0 Received: from mail-pg1-x52e.google.com (mail-pg1-x52e.google.com [IPv6:2607:f8b0:4864:20::52e]) by smtp1.osuosl.org (Postfix) with ESMTPS id 3659F83F35 for ; Mon, 30 May 2022 03:42:39 +0000 (UTC) Received: by mail-pg1-x52e.google.com with SMTP id v15so9037049pgk.11 for ; Sun, 29 May 2022 20:42:39 -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=J5sF3mFqGhllRcP7qULp4gu/qXOO1PJJQQbj1rC48Rg=; b=mMo3ozCeDGx35t9BjOcWaMtSQ//Ah0HGWsMwgurSwxKJ6KZ/0kCVZdrZ/k3kTkLY3S 22AuHDgaMEBftGjonL3YHiOtFIKpR+po0LhX5Mck3mPMKh4kghHXwTDvM6BOGMl802eX izwgcP/em4wyNWuszKMHSqjSY5egyU+LvcyQu5qCHGkRVQwV4GZSsqladY+AvFChsTd1 6e266RN/4v/FxxxK0qPLl4OjsXbBB/UurjYV0pgpXe6SYOInygr+jVjMusk1jfcfz/LK 1C+bhH7J6/CXqMyx/JT0H9X8RIYTCPlIY7jXd5LHZCiRj8rybkzZ2oD9SAmTLHQsxRAx TTUA== 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=J5sF3mFqGhllRcP7qULp4gu/qXOO1PJJQQbj1rC48Rg=; b=xX6cfJZ5SF1JEo82V3dZHwcSprze4w2BOFPLa4IyAEUADTsyLNHuIG1ui1FAQxX63a stnTG3oswYFFnBXtjo/M6ZHrDCyJ6yVOtVOIPwjtEk2P0Yh9cRSoAhk5ECQctEAl9uNR P6NNev7cRIBr8YsbDEdvU0NK+Dz1rZbQuzawbEUVhlZYmjJUNxwNPtOxiAzYcNOI7qZZ 6O8uxw2BN5zO4Qr9fSjvoqH5Ji8MtQP0D574TWeqOU8WRH2XeZLwINdVz+nLmLaqwtXa QW2HdEbY8TNys1WSKqAQrFLxchUYOsBo0OQm+B98bVbcRCdcHdCDe7YgjqkxyUyyIp0L FuyQ== X-Gm-Message-State: AOAM5307JCYeId2HWBOmmuAINln0Sj9l5i3OHd1JYbSC0O0zvNawuPAB uEsZK19aZNf0dwxTe3EZbTe63jJ6AIM= X-Google-Smtp-Source: ABdhPJxVzwQxP9PJWXPXQ6qEBYe6LyEjoPcgrLmaB+KxP72ErLiMK5xCBaPxLZi7eJR8KXzoGY4zHw== X-Received: by 2002:a62:1dcc:0:b0:519:17be:89be with SMTP id d195-20020a621dcc000000b0051917be89bemr19404791pfd.30.1653882157531; Sun, 29 May 2022 20:42:37 -0700 (PDT) Received: from sc9-mailhost2.vmware.com ([66.170.99.2]) by smtp.gmail.com with ESMTPSA id f5-20020a63dc45000000b003f5d4d4f947sm7473126pgj.78.2022.05.29.20.42.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 29 May 2022 20:42:36 -0700 (PDT) From: William Tu To: dev@openvswitch.org Date: Sun, 29 May 2022 20:42:18 -0700 Message-Id: <20220530034218.416-1-u9012063@gmail.com> X-Mailer: git-send-email 2.33.0.windows.2 MIME-Version: 1.0 Subject: [ovs-dev] [PATCH v2] 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 Signed-off-by: William Tu --- 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 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 @@ + @@ -401,6 +402,7 @@ +