diff options
Diffstat (limited to 'datapath-windows/ovsext/Ip6Fragment.c')
-rw-r--r-- | datapath-windows/ovsext/Ip6Fragment.c | 808 |
1 files changed, 808 insertions, 0 deletions
diff --git a/datapath-windows/ovsext/Ip6Fragment.c b/datapath-windows/ovsext/Ip6Fragment.c new file mode 100644 index 000000000..0b710905d --- /dev/null +++ b/datapath-windows/ovsext/Ip6Fragment.c @@ -0,0 +1,808 @@ +/* + * 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 "Conntrack.h" +#include "Ip6Fragment.h" +#include "Util.h" +#include "Jhash.h" +#include "NetProto.h" +#include "PacketParser.h" + +static OVS_IP6FRAG_THREAD_CTX ip6FragThreadCtx; +static PNDIS_RW_LOCK_EX ovsIp6FragmentHashLockObj; +static UINT64 ip6TotalEntries; +static PLIST_ENTRY OvsIp6FragTable; + +#define MIN_FRAGMENT_SIZE 400 +#define MAX_IPDATAGRAM_SIZE 65535 +#define MAX_FRAGMENTS MAX_IPDATAGRAM_SIZE/MIN_FRAGMENT_SIZE + 1 + +static __inline UINT32 +OvsGetIP6FragmentHash(POVS_IP6FRAG_KEY fragKey) +{ + UINT32 arr[11]; + arr[0] = (UINT32)fragKey->id; + arr[1] = (UINT32)(((UINT32*)(&(fragKey->sAddr)))[0]); + arr[2] = (UINT32)(((UINT32*)(&(fragKey->sAddr)))[1]); + arr[3] = (UINT32)(((UINT32*)(&(fragKey->sAddr)))[2]); + arr[4] = (UINT32)(((UINT32*)(&(fragKey->sAddr)))[3]); + arr[5] = (UINT32)(((UINT32*)(&(fragKey->dAddr)))[0]); + arr[6] = (UINT32)(((UINT32*)(&(fragKey->dAddr)))[1]); + arr[7] = (UINT32)(((UINT32*)(&(fragKey->dAddr)))[2]); + arr[8] = (UINT32)(((UINT32*)(&(fragKey->dAddr)))[3]); + arr[9] = (UINT32)((fragKey->tunnelId & 0xFFFFFFFF00000000LL) >> 32); + arr[10] = (UINT32)(fragKey->tunnelId & 0xFFFFFFFFLL); + return OvsJhashWords(arr, 11, OVS_HASH_BASIS); +} + +static VOID +OvsIp6FragmentEntryDelete(POVS_IP6FRAG_ENTRY entry, BOOLEAN checkExpiry) +{ + NdisAcquireSpinLock(&(entry->lockObj)); + if (!entry->markedForDelete && checkExpiry) { + UINT64 currentTime; + NdisGetCurrentSystemTime((LARGE_INTEGER *)¤tTime); + if (entry->expiration > currentTime) { + NdisReleaseSpinLock(&(entry->lockObj)); + return; + } + } + + POVS_FRAGMENT6_LIST head = entry->head; + POVS_FRAGMENT6_LIST temp = NULL; + while (head) { + temp = head; + head = head->next; + OvsFreeMemoryWithTag(temp->pbuff, OVS_IP6FRAG_POOL_TAG); + OvsFreeMemoryWithTag(temp, OVS_IP6FRAG_POOL_TAG); + } + RemoveEntryList(&entry->link); + ip6TotalEntries--; + NdisReleaseSpinLock(&(entry->lockObj)); + NdisFreeSpinLock(&(entry->lockObj)); + if (entry->beforeFragHdrLen > 0) { + OvsFreeMemoryWithTag(entry->beforeFragHdrBuf, OVS_IP6FRAG_POOL_TAG); + } + + if (entry->fragHdrLen > 0) { + OvsFreeMemoryWithTag(entry->fragHdrBuf, OVS_IP6FRAG_POOL_TAG); + } + + if (entry->behindFragHdrLen > 0) { + OvsFreeMemoryWithTag(entry->behindFragHdrBuf, OVS_IP6FRAG_POOL_TAG); + } + + OvsFreeMemoryWithTag(entry, OVS_IP6FRAG_POOL_TAG); +} + +static VOID +OvsIp6FragmentEntryCleaner(PVOID data) +{ + POVS_IP6FRAG_THREAD_CTX context = (POVS_IP6FRAG_THREAD_CTX)data; + PLIST_ENTRY link, next; + POVS_IP6FRAG_ENTRY entry; + LOCK_STATE_EX lockState; + BOOLEAN success = TRUE; + + while (success) { + if (ovsIp6FragmentHashLockObj == NULL) { + /* Lock has been freed by 'OvsCleanupIpFragment()' */ + break; + } + NdisAcquireRWLockWrite(ovsIp6FragmentHashLockObj, &lockState, 0); + if (context->exit) { + NdisReleaseRWLock(ovsIp6FragmentHashLockObj, &lockState); + break; + } + + /* Set the timeout for the thread and cleanup. */ + UINT64 currentTime, threadSleepTimeout; + NdisGetCurrentSystemTime((LARGE_INTEGER *)¤tTime); + threadSleepTimeout = currentTime + IP6FRAG_CLEANUP_INTERVAL; + for (int i = 0; i < IP6_FRAG_HASH_TABLE_SIZE && ip6TotalEntries; i++) { + LIST_FORALL_SAFE(&OvsIp6FragTable[i], link, next) { + entry = CONTAINING_RECORD(link, OVS_IP6FRAG_ENTRY, link); + OvsIp6FragmentEntryDelete(entry, TRUE); + } + } + + NdisReleaseRWLock(ovsIp6FragmentHashLockObj, &lockState); + KeWaitForSingleObject(&context->event, Executive, KernelMode, + FALSE, (LARGE_INTEGER *)&threadSleepTimeout); + } + + PsTerminateSystemThread(STATUS_SUCCESS); +} + +NDIS_STATUS OvsInitIp6Fragment(POVS_SWITCH_CONTEXT context) +{ + NDIS_STATUS status; + HANDLE threadHandle = NULL; + + OVS_LOG_INFO("Init ipv6 fragment."); + ovsIp6FragmentHashLockObj = NdisAllocateRWLock(context->NdisFilterHandle); + if (ovsIp6FragmentHashLockObj == NULL) { + return STATUS_INSUFFICIENT_RESOURCES; + } + + /* Init the Hash Buffer */ + OvsIp6FragTable = OvsAllocateMemoryWithTag(sizeof(LIST_ENTRY) + * IP6_FRAG_HASH_TABLE_SIZE, + OVS_IP6FRAG_POOL_TAG); + if (OvsIp6FragTable == NULL) { + NdisFreeRWLock(ovsIp6FragmentHashLockObj); + ovsIp6FragmentHashLockObj = NULL; + return STATUS_INSUFFICIENT_RESOURCES; + } + + + for (int i = 0; i < IP6_FRAG_HASH_TABLE_SIZE; i++) { + InitializeListHead(&OvsIp6FragTable[i]); + } + + /* Init Cleaner Thread */ + KeInitializeEvent(&ip6FragThreadCtx.event, NotificationEvent, FALSE); + status = PsCreateSystemThread(&threadHandle, SYNCHRONIZE, NULL, NULL, + NULL, OvsIp6FragmentEntryCleaner, + &ip6FragThreadCtx); + + if (status != STATUS_SUCCESS) { + OvsFreeMemoryWithTag(OvsIp6FragTable, OVS_IPFRAG_POOL_TAG); + OvsIp6FragTable = NULL; + NdisFreeRWLock(ovsIp6FragmentHashLockObj); + ovsIp6FragmentHashLockObj = NULL; + return status; + } + + ObReferenceObjectByHandle(threadHandle, SYNCHRONIZE, NULL, KernelMode, + &ip6FragThreadCtx.threadObject, NULL); + ZwClose(threadHandle); + threadHandle = NULL; + return STATUS_SUCCESS; +} + +static __inline POVS_IP6FRAG_ENTRY +OvsLookupIP6Frag(POVS_IP6FRAG_KEY fragKey, UINT32 hash) +{ + POVS_IP6FRAG_ENTRY entry; + PLIST_ENTRY link; + LOCK_STATE_EX lockState; + + NdisAcquireRWLockRead(ovsIp6FragmentHashLockObj, &lockState, 0); + LIST_FORALL(&OvsIp6FragTable[hash & IP6_FRAG_HASH_TABLE_MASK], link) { + entry = CONTAINING_RECORD(link, OVS_IP6FRAG_ENTRY, link); + NdisAcquireSpinLock(&(entry->lockObj)); + if (RtlCompareMemory(&entry->fragKey.dAddr, &fragKey->dAddr, + sizeof(fragKey->dAddr)) == sizeof(fragKey->dAddr) && + RtlCompareMemory(&entry->fragKey.sAddr, &fragKey->sAddr, + sizeof(fragKey->sAddr)) == sizeof(fragKey->sAddr) && + entry->fragKey.id == fragKey->id && + entry->fragKey.tunnelId == fragKey->tunnelId) { + NdisReleaseSpinLock(&(entry->lockObj)); + NdisReleaseRWLock(ovsIp6FragmentHashLockObj, &lockState); + return entry; + } + NdisReleaseSpinLock(&(entry->lockObj)); + } + NdisReleaseRWLock(ovsIp6FragmentHashLockObj, &lockState); + return NULL; +} + +VOID OvsCleanupIp6Fragment(VOID) +{ + PLIST_ENTRY link, next; + POVS_IP6FRAG_ENTRY entry; + LOCK_STATE_EX lockState; + + ip6FragThreadCtx.exit = 1; + KeSetEvent(&ip6FragThreadCtx.event, 0, FALSE); + KeWaitForSingleObject(ip6FragThreadCtx.threadObject, Executive, + KernelMode, FALSE, NULL); + ObDereferenceObject(ip6FragThreadCtx.threadObject); + NdisAcquireRWLockWrite(ovsIp6FragmentHashLockObj, &lockState, 0); + if (OvsIp6FragTable) { + for (int i = 0; i < IP6_FRAG_HASH_TABLE_SIZE && ip6TotalEntries; i++) { + LIST_FORALL_SAFE(&OvsIp6FragTable[i], link, next) { + entry = CONTAINING_RECORD(link, OVS_IP6FRAG_ENTRY, link); + OvsIp6FragmentEntryDelete(entry, FALSE); + } + } + OvsFreeMemoryWithTag(OvsIp6FragTable, OVS_IP6FRAG_POOL_TAG); + OvsIp6FragTable = NULL; + } + NdisReleaseRWLock(ovsIp6FragmentHashLockObj, &lockState); + NdisFreeRWLock(ovsIp6FragmentHashLockObj); + ovsIp6FragmentHashLockObj = NULL; +} + +PCHAR +OvsBuildNewIpv6Hdr(EthHdr *eth, POVS_IP6FRAG_ENTRY entry, + POVS_PACKET_HDR_INFO layers, + UINT32 *pktLen) +{ + IPv6Hdr *ipHdr = NULL; + IPv6Hdr *newIpHdr = NULL; + PCHAR ipv6StdPtr = NULL; + PCHAR packetBuf = NULL; + UINT32 packetLen = 0; + + ipHdr = (IPv6Hdr *)((PCHAR)eth + layers->l3Offset); + if (layers->l4Offset + entry->totalLen > MAX_IPDATAGRAM_SIZE) { + return NULL; + } + + packetLen = (layers->l3Offset + sizeof(IPv6Hdr) + + entry->beforeFragHdrLen + entry->behindFragHdrLen + entry->totalLen); + packetBuf = (CHAR*)OvsAllocateMemoryWithTag(packetLen, OVS_IP6FRAG_POOL_TAG); + if (packetBuf == NULL) { + return NULL; + } + *pktLen = packetLen; + + NdisMoveMemory(packetBuf, eth, layers->l3Offset + sizeof(IPv6Hdr)); + IPv6ExtHdr *extHdr = (IPv6ExtHdr *)((PCHAR)packetBuf + layers->l3Offset + + sizeof(IPv6Hdr)); + ipv6StdPtr = (PCHAR)extHdr; + newIpHdr = (IPv6Hdr *)(packetBuf + layers->l3Offset); + newIpHdr->payload_len = htons(entry->beforeFragHdrLen + + entry->behindFragHdrLen + entry->totalLen); + + /* Copy extension header to new packet buf. */ + if (entry->beforeFragHdrLen > 0) { + NdisMoveMemory(ipv6StdPtr, entry->beforeFragHdrBuf, + entry->beforeFragHdrLen); + } + + if (entry->behindFragHdrLen > 0) { + NdisMoveMemory((ipv6StdPtr + entry->beforeFragHdrLen), + entry->behindFragHdrBuf, + entry->behindFragHdrLen); + } + + /* Fix next header. */ + if (entry->beforeFragHdrLen > 0) { + extHdr = (IPv6ExtHdr *)((PCHAR)extHdr + entry->priorFragEleOffset); + extHdr->nextHeader = ((IPv6FragHdr *)(entry->fragHdrBuf))->nextHeader; + } + + if (entry->beforeFragHdrLen == 0) { + if (entry->behindFragHdrLen == 0) { + newIpHdr->nexthdr = entry->fragKey.protocol; + } else { + newIpHdr->nexthdr = ((IPv6FragHdr *)(entry->fragHdrBuf))->nextHeader; + } + } + + return packetBuf; +} + + +NDIS_STATUS +OvsIpv6Reassemble(POVS_SWITCH_CONTEXT switchContext, + PNET_BUFFER_LIST *curNbl, + OvsCompletionList *completionList, + NDIS_SWITCH_PORT_ID sourcePort, + POVS_IP6FRAG_ENTRY entry, + POVS_PACKET_HDR_INFO layers) +{ + NDIS_STATUS status = NDIS_STATUS_SUCCESS; + NDIS_STRING filterReason; + POVS_BUFFER_CONTEXT ctx; + PNET_BUFFER curNb; + EthHdr *eth; + CHAR *packetBuf; + POVS_FRAGMENT6_LIST head = NULL; + PNET_BUFFER_LIST newNbl = NULL; + UINT16 packetHeaderLen; + UINT32 packetLen; + + curNb = NET_BUFFER_LIST_FIRST_NB(*curNbl); + ASSERT(NET_BUFFER_NEXT_NB(curNb) == NULL); + + OVS_LOG_INFO("Process ipv6 reassemble, entry total length is %d.", + entry->totalLen); + eth = (EthHdr*)NdisGetDataBuffer(curNb, layers->l4Offset, + NULL, 1, 0); + if (!eth) { + return NDIS_STATUS_INVALID_PACKET; + } + + packetBuf = OvsBuildNewIpv6Hdr(eth, entry, layers, &packetLen); + if (!packetBuf) { + return NDIS_STATUS_INVALID_PACKET; + } + + head = entry->head; + packetHeaderLen = (layers->l3Offset + sizeof(IPv6Hdr) + + entry->beforeFragHdrLen + entry->behindFragHdrLen); + while (head) { + if ((UINT32)(packetHeaderLen + (head->offset * 8) + head->len) > packetLen) { + status = NDIS_STATUS_INVALID_DATA; + goto cleanup; + } + NdisMoveMemory(packetBuf + packetHeaderLen + (head->offset * 8), + head->pbuff, head->len); + head = head->next; + } + /* Create new nbl from the flat buffer */ + newNbl = OvsAllocateNBLFromBuffer(switchContext, packetBuf, packetLen); + if (newNbl == NULL) { + OVS_LOG_ERROR("Insufficient resources, failed to allocate newNbl"); + status = NDIS_STATUS_RESOURCES; + goto cleanup; + } + + /* Complete the fragment NBL */ + ctx = (POVS_BUFFER_CONTEXT)NET_BUFFER_LIST_CONTEXT_DATA_START(*curNbl); + if (ctx->flags & OVS_BUFFER_NEED_COMPLETE) { + RtlInitUnicodeString(&filterReason, L"Complete last fragment"); + OvsAddPktCompletionList(completionList, TRUE, sourcePort, *curNbl, 1, + &filterReason); + } else { + OvsCompleteNBL(switchContext, *curNbl, TRUE); + } + /* Store mru in the ovs buffer context. */ + ctx = (POVS_BUFFER_CONTEXT)NET_BUFFER_LIST_CONTEXT_DATA_START(newNbl); + ctx->mru = entry->mru; + *curNbl = newNbl; +cleanup: + OvsFreeMemoryWithTag(packetBuf, OVS_IP6FRAG_POOL_TAG); + entry->markedForDelete = TRUE; + return status; +} + +NDIS_STATUS +OvsProcessIpv6Fragment(POVS_SWITCH_CONTEXT switchContext, + PNET_BUFFER_LIST *curNbl, + OvsCompletionList *completionList, + NDIS_SWITCH_PORT_ID sourcePort, + POVS_PACKET_HDR_INFO layers, ovs_be64 tunnelId, + OvsFlowKey *key) +{ + NDIS_STATUS status = NDIS_STATUS_PENDING; + PNET_BUFFER curNb; + UINT32 hash; + UINT64 currentTime; + EthHdr *eth; + IPv6Hdr *ip6Hdr = NULL; + OVS_IP6FRAG_KEY frag6Key; + POVS_IP6FRAG_ENTRY entry; + POVS_FRAGMENT6_LIST fragStorage; + LOCK_STATE_EX htLockState; + IP6_PktExtHeader_Meta pktMeta = {0}; + + curNb = NET_BUFFER_LIST_FIRST_NB(*curNbl); + ASSERT(NET_BUFFER_NEXT_NB(curNb) == NULL); + + OVS_LOG_INFO("Process ipv6 fragment."); + eth = (EthHdr*)NdisGetDataBuffer(curNb, layers->l4Offset, + NULL, 1, 0); + if (eth == NULL) { + return NDIS_STATUS_INVALID_PACKET; + } + + ip6Hdr = (IPv6Hdr *)((PCHAR)eth + layers->l3Offset); + status = OvsGetPacketMeta(&pktMeta, eth, key, layers); + if (status != NDIS_STATUS_SUCCESS) { + return status; + } + + fragStorage = (POVS_FRAGMENT6_LIST) + OvsAllocateMemoryWithTag(sizeof(OVS_FRAGMENT6_LIST), + OVS_IP6FRAG_POOL_TAG); + if (fragStorage == NULL) { + OVS_LOG_ERROR("Insufficient resources, fail to allocate fragStorage"); + return NDIS_STATUS_RESOURCES; + } + + fragStorage->len = pktMeta.dataPayloadLen; + fragStorage->offset = pktMeta.fragOffset; + fragStorage->next = NULL; + fragStorage->pbuff = (CHAR *)OvsAllocateMemoryWithTag(fragStorage->len, + OVS_IP6FRAG_POOL_TAG); + if (fragStorage->pbuff == NULL) { + OVS_LOG_ERROR("Insufficient resources, fail to allocate pbuff"); + OvsFreeMemoryWithTag(fragStorage, OVS_IP6FRAG_POOL_TAG); + return NDIS_STATUS_RESOURCES; + } + + if (OvsGetPacketBytes(*curNbl, pktMeta.dataPayloadLen, + layers->l4Offset, + fragStorage->pbuff) == NULL) { + status = NDIS_STATUS_RESOURCES; + OVS_LOG_ERROR("Get packet bytes fail, pkt len is %d, offset is %d.", + pktMeta.dataPayloadLen, layers->l4Offset); + goto payload_copy_error; + } + + frag6Key.sAddr = ip6Hdr->saddr; + frag6Key.dAddr = ip6Hdr->daddr; + frag6Key.tunnelId = tunnelId; + frag6Key.id = pktMeta.ident; + + hash = OvsGetIP6FragmentHash(&frag6Key); + entry = OvsLookupIP6Frag(&frag6Key, hash); + if (entry == NULL) { + entry = (POVS_IP6FRAG_ENTRY) + OvsAllocateMemoryWithTag(sizeof(OVS_IP6FRAG_ENTRY), + OVS_IP6FRAG_POOL_TAG); + if (entry == NULL) { + status = NDIS_STATUS_RESOURCES; + goto payload_copy_error; + } + /* Copy the fragmeny key. */ + NdisZeroMemory(entry, sizeof(OVS_IP6FRAG_ENTRY)); + NdisMoveMemory(&(entry->fragKey), &frag6Key, sizeof(OVS_IP6FRAG_KEY)); + /* Init MRU. */ + entry->mru = pktMeta.pktMru; + entry->recvdLen = fragStorage->len; + entry->head = entry->tail = fragStorage; + entry->numFragments = 1; + + if (!pktMeta.fragOffset) { + /* First packet, fragment offset is 0 */ + OVS_LOG_INFO("before fragment extension header len:%d " + "fragment extension header len:%d " + "behind fragment extension header len :%d " + "last element before fragment offset %d", + pktMeta.beforeFragExtHdrLen, + pktMeta.fragExtHdrLen, + pktMeta.behindFragExtHdrLen, + pktMeta.priorFragEleOffset); + /* We could get all ext header info from first fragment packet. */ + status = OvsStorageIpv6ExtHeader(entry, pktMeta.beforeFragExtHdrLen, + pktMeta.fragExtHdrLen, + pktMeta.behindFragExtHdrLen, + pktMeta.priorFragEleOffset, + (PCHAR) eth, layers); + if (status != NDIS_STATUS_SUCCESS) { + OVS_LOG_INFO("StorageIpv6 header fails, parse failed."); + OvsFreeMemoryWithTag(entry, OVS_IP6FRAG_POOL_TAG); + goto payload_copy_error; + } + + entry->fragKey.protocol = pktMeta.protocol; + OVS_LOG_INFO("First packet, protocol is %d.", + entry->fragKey.protocol); + } + + if (!pktMeta.flags) { + /* It's the last fragment, it demonstrates the packet was arrived + * out of order, we calculate the complte packet total length. */ + entry->totalLen = pktMeta.fragOffset * 8 + pktMeta.dataPayloadLen; + } + + NdisGetCurrentSystemTime((LARGE_INTEGER *)¤tTime); + entry->expiration = currentTime + IP6FRAG_ENTRY_TIMEOUT; + + /* Init the sync-lock. */ + NdisAllocateSpinLock(&(entry->lockObj)); + NdisAcquireRWLockWrite(ovsIp6FragmentHashLockObj, &htLockState, 0); + InsertHeadList(&OvsIp6FragTable[hash & IP6_FRAG_HASH_TABLE_MASK], + &entry->link); + + ip6TotalEntries++; + NdisReleaseRWLock(ovsIp6FragmentHashLockObj, &htLockState); + return NDIS_STATUS_PENDING; + } else { + /* Acquire the entry lock. */ + NdisAcquireSpinLock(&(entry->lockObj)); + NdisGetCurrentSystemTime((LARGE_INTEGER *)¤tTime); + if (currentTime > entry->expiration || + (entry->numFragments == MAX_FRAGMENTS)) { + /* Mark the entry for delete. */ + OVS_LOG_ERROR("Will delete the fragment numbers."); + entry->markedForDelete = TRUE; + goto fragment_error; + } + + if (!pktMeta.fragOffset) { + status = OvsStorageIpv6ExtHeader(entry, pktMeta.behindFragExtHdrLen, + pktMeta.fragExtHdrLen, + pktMeta.behindFragExtHdrLen, + pktMeta.priorFragEleOffset, + (PCHAR) eth, + layers); + if (status != NDIS_STATUS_SUCCESS) { + OVS_LOG_ERROR("IPv6 Extension header not valid."); + goto fragment_error; + } + + entry->fragKey.protocol = pktMeta.protocol; + } + + if (!pktMeta.flags) { + entry->totalLen = pktMeta.fragOffset * 8 + pktMeta.dataPayloadLen; + } + + /* Find the element offset just large than fragment and insert the + * fragment before it. */ + POVS_FRAGMENT6_LIST next = entry->head; + POVS_FRAGMENT6_LIST prev = entry->tail; + if (prev != NULL && prev->offset < pktMeta.fragOffset) { + next = NULL; + goto found; + } + prev = NULL; + for (next = entry->head; next != NULL; next = next->next) { + if (next->offset > fragStorage->offset) { + break; + } + prev = next; + } +found: + /*Check for overlap. */ + if (prev) { + /* i bytes overlap. */ + int i = ((prev->offset * 8) + prev->len) - (fragStorage->offset * 8); + if (i > 0) { + OVS_LOG_ERROR("IPv6 fragment error, prev offset %d, pre len " + "%d, frag offset %d", + prev->offset, prev->len, fragStorage->offset); + goto fragment_error; + } + } + if (next) { + /* i bytes overlap. */ + int i = ((fragStorage->offset * 8) + fragStorage->len) - + (next->offset * 8); + if (i > 0) { + OVS_LOG_ERROR("IPv6 fragment error, frag offset %d, frag " + "len %d, next offset %d.", + fragStorage->offset, fragStorage->len, + next->offset); + goto fragment_error; + } + } + + if (entry->recvdLen + fragStorage->len > entry->recvdLen) { + entry->recvdLen += fragStorage->len; + } else { + /* Overflow, ignore the fragment.*/ + OVS_LOG_ERROR("IPv6 fragment error, entry recv len %d, frag " + "len %d.", entry->recvdLen, fragStorage->len); + goto fragment_error; + } + + /*Insert. */ + if (prev) { + prev->next = fragStorage; + fragStorage->next = next; + } else { + fragStorage->next = next; + entry->head = fragStorage; + } + if (!next) { + entry->tail = fragStorage; + } + + /*Update Maximum Receive Unit */ + entry->mru = entry->mru > pktMeta.pktMru ? entry->mru : pktMeta.pktMru; + entry->numFragments++; + + OVS_LOG_INFO("Max mru is %d, entry total length %d, entry recv length %d, " + "extension header length is %d", entry->mru, + entry->totalLen, entry->recvdLen, + entry->behindFragHdrLen); + if (entry->recvdLen == (entry->totalLen - entry->behindFragHdrLen)) { + /* when exist ipv6 extension field behind ipv6 fragment field, + * the ipv6 extension field will be regard as "data", the totalLen + * represent the "fragment data length" + "ipv6 extension length + * behind fragment". However, the recvdLen only represents the + * data length, thus when we judge is or not receive a complete + * packet, we should use + * (entry->totalLen - entry->behindFragHdrLen) == entry->recvdLen */ + status = OvsIpv6Reassemble(switchContext, curNbl, completionList, + sourcePort, entry, layers); + } + NdisReleaseSpinLock(&(entry->lockObj)); + return status; + } + +fragment_error: + status = NDIS_STATUS_INVALID_PACKET; + /* Release the entry lock. */ + NdisReleaseSpinLock(&(entry->lockObj)); + +payload_copy_error: + OVS_LOG_ERROR("Payload error, exits."); + OvsFreeMemoryWithTag(fragStorage->pbuff, OVS_IP6FRAG_POOL_TAG); + OvsFreeMemoryWithTag(fragStorage, OVS_IP6FRAG_POOL_TAG); + return status; +} + +NDIS_STATUS +OvsGetPacketMeta(PIP6_PktExtHeader_Meta pktMeta, EthHdr *eth, + OvsFlowKey *key, POVS_PACKET_HDR_INFO layers) +{ + IPv6Hdr *ip6Hdr = NULL; + IPv6ExtHdr *extHdr = NULL; + UINT8 nextHdr; + + ip6Hdr = (IPv6Hdr *)((PCHAR)eth + layers->l3Offset); + if (!ip6Hdr) { + return NDIS_STATUS_INVALID_PACKET; + } + + nextHdr = ip6Hdr->nexthdr; + pktMeta->firstHdr = nextHdr; + + if ((nextHdr == SOCKET_IPPROTO_HOPOPTS) || + (nextHdr == SOCKET_IPPROTO_ROUTING) || + (nextHdr == SOCKET_IPPROTO_DSTOPTS) || + (nextHdr == SOCKET_IPPROTO_FRAGMENT)) { + extHdr = (IPv6ExtHdr *)((PCHAR)ip6Hdr + sizeof(IPv6Hdr)); + pktMeta->firstHdrPtr = extHdr; + } else { + return NDIS_STATUS_INVALID_PACKET; + } + + for (;;) { + if ((nextHdr != SOCKET_IPPROTO_HOPOPTS) + && (nextHdr != SOCKET_IPPROTO_ROUTING) + && (nextHdr != SOCKET_IPPROTO_DSTOPTS) + && (nextHdr != SOCKET_IPPROTO_AH) + && (nextHdr != SOCKET_IPPROTO_FRAGMENT)) { + /* + * It's either a terminal header (e.g., TCP, UDP, Icmpv6) or one we + * don't understand. In either case, we're done with the + * packet, so use it to fill in 'nw_proto'. + */ + pktMeta->protocol = nextHdr; + break; + } + + if (nextHdr == SOCKET_IPPROTO_HOPOPTS || + nextHdr == SOCKET_IPPROTO_ROUTING || + nextHdr == SOCKET_IPPROTO_DSTOPTS || + nextHdr == SOCKET_IPPROTO_AH) { + UINT8 len = extHdr->hdrExtLen; + nextHdr = extHdr->nextHeader; + if (nextHdr == SOCKET_IPPROTO_FRAGMENT) { + pktMeta->beforeFragElePtr = (PCHAR)(extHdr); + } + + if (nextHdr == SOCKET_IPPROTO_AH) { + extHdr = (IPv6ExtHdr *)((PCHAR)extHdr + (len + 2) * 4); + pktMeta->extHdrTotalLen += ((len + 2) * 4); + } else { + extHdr = (IPv6ExtHdr *)((PCHAR)extHdr + (len + 1) * 8); + pktMeta->extHdrTotalLen += ((len + 1) * 8); + } + } else if (nextHdr == SOCKET_IPPROTO_FRAGMENT) { + IPv6FragHdr *fragHdr = (IPv6FragHdr *)extHdr; + pktMeta->ident = fragHdr->ident; + pktMeta->beforeFragExtHdrLen = pktMeta->extHdrTotalLen; + pktMeta->fragExtHdrLen = sizeof(IPv6FragHdr); + pktMeta->extHdrTotalLen += sizeof(IPv6FragHdr); + pktMeta->fragOffset = (ntohs(fragHdr->offlg) + & IP6F_OFF_HOST_ORDER_MASK) >> 3; + pktMeta->flags = ntohs(fragHdr->offlg) & 0x01; + nextHdr = extHdr->nextHeader; + extHdr = (IPv6ExtHdr *)((PCHAR)extHdr + sizeof(IPv6FragHdr)); + if (key->ipv6Key.nwFrag == OVS_FRAG_TYPE_LATER) { + pktMeta->protocol = SOCKET_IPPROTO_FRAGMENT; + break; + } + } + } + + pktMeta->dataPayloadLen = (ntohs(ip6Hdr->payload_len) - + pktMeta->extHdrTotalLen); + OVS_LOG_INFO("playload len %d, extotalLen %d, datapyaload len %d.", + ntohs(ip6Hdr->payload_len), + pktMeta->extHdrTotalLen, + pktMeta->dataPayloadLen); + pktMeta->behindFragExtHdrLen = (pktMeta->extHdrTotalLen - + pktMeta->beforeFragExtHdrLen - + pktMeta->fragExtHdrLen); + pktMeta->pktMru = (layers->l3Offset + sizeof(IPv6Hdr) + + ntohs(ip6Hdr->payload_len)); + if (pktMeta->beforeFragElePtr) { + pktMeta->priorFragEleOffset = (UINT16)((PCHAR)pktMeta->beforeFragElePtr - + (PCHAR)pktMeta->firstHdrPtr); + } + + return NDIS_STATUS_SUCCESS; +} + +/* + *----------------------------------------------------------------------------- + * OvsStorageIpv6ExtHeader -- + * In some scenario, we need to storage the ipv6 option header, this + * function is used to do it, we could divide ipv6 option field into + * three parts, including "option field before fragment + * field", "fragment field", "option field behind fragment field". The + * reason store extension header is that it's convenient to copy the + * specified to the fragment header. + *----------------------------------------------------------------------------- + */ +NDIS_STATUS +OvsStorageIpv6ExtHeader(POVS_IP6FRAG_ENTRY entry, + UINT16 beforeFragHdrLen, + UINT16 fragHdrLen, + UINT16 behindFragHdrLen, + UINT16 priorFragEleOffset, + CHAR *pktBuf, + POVS_PACKET_HDR_INFO layers) +{ + NDIS_STATUS status = NDIS_STATUS_SUCCESS; + + if (beforeFragHdrLen) { + entry->beforeFragHdrBuf = + OvsAllocateMemoryWithTag(beforeFragHdrLen, + OVS_IP6FRAG_POOL_TAG); + if (entry->beforeFragHdrBuf == NULL) { + goto beforeFragHdrError; + } + entry->beforeFragHdrLen = beforeFragHdrLen; + entry->priorFragEleOffset = priorFragEleOffset; + } + + if (fragHdrLen) { + entry->fragHdrBuf = OvsAllocateMemoryWithTag(fragHdrLen, + OVS_IP6FRAG_POOL_TAG); + if (entry->fragHdrBuf == NULL) { + goto fragHdrError; + } + entry->fragHdrLen = fragHdrLen; + } + + if (behindFragHdrLen) { + entry->behindFragHdrBuf = + OvsAllocateMemoryWithTag(behindFragHdrLen, + OVS_IP6FRAG_POOL_TAG); + if (entry->behindFragHdrBuf == NULL) { + goto behindFragHdrError; + } + entry->behindFragHdrLen = behindFragHdrLen; + } + + if (entry->beforeFragHdrLen) { + NdisMoveMemory(entry->beforeFragHdrBuf, + pktBuf + layers->l3Offset + sizeof(IPv6Hdr), + entry->beforeFragHdrLen); + } + + if (entry->fragHdrLen) { + NdisMoveMemory(entry->fragHdrBuf, + (pktBuf + layers->l3Offset + + sizeof(IPv6Hdr) + beforeFragHdrLen), + entry->fragHdrLen); + } + + if (entry->behindFragHdrLen) { + NdisMoveMemory(entry->behindFragHdrBuf, + (pktBuf + layers->l3Offset + sizeof(IPv6Hdr) + + beforeFragHdrLen + fragHdrLen), + entry->behindFragHdrLen); + } + + return status; + +behindFragHdrError: +fragHdrError: + if (entry->fragHdrBuf) { + OvsFreeMemoryWithTag(entry->fragHdrBuf, OVS_IP6FRAG_POOL_TAG); + } +beforeFragHdrError: + if (entry->beforeFragHdrBuf) { + OvsFreeMemoryWithTag(entry->beforeFragHdrBuf, OVS_IP6FRAG_POOL_TAG); + } + status = NDIS_STATUS_RESOURCES; + OVS_LOG_ERROR("Storage header fails due to header."); + return status; +} |