diff options
author | Sairam Venugopal <vsairam@vmware.com> | 2016-12-16 14:28:11 -0800 |
---|---|---|
committer | Gurucharan Shetty <guru@ovn.org> | 2016-12-20 09:33:03 -0800 |
commit | 7fd123b627f35a7da6aa534276760405d66ccbbe (patch) | |
tree | ae8a3f402ee6073e646c64587f363c7deb8b84eb | |
parent | 5e422c9ef2c4bf60a7856ae19a3686e9f63f3e62 (diff) | |
download | openvswitch-7fd123b627f35a7da6aa534276760405d66ccbbe.tar.gz |
datapath-windows: Conntrack - Introduce support for tracking related connections
Introduce a new table to track related connections. This table will be
used to track FTP data connections based on the control connection. There
is a new Conntrack-ftp.c to parse incoming FTP messages to determine the
related data ports. It creates a new entry in the related connections
tracker table. If there is a matching FTP data connection, then the state
for that connection is marked as RELATED.
Signed-off-by: Sairam Venugopal <vsairam@vmware.com>
Acked-by: Alin Gabriel Serdean <aserdean@cloudbasesolutions.com>
Signed-off-by: Gurucharan Shetty <guru@ovn.org>
-rw-r--r-- | datapath-windows/automake.mk | 2 | ||||
-rw-r--r-- | datapath-windows/ovsext/Conntrack-ftp.c | 237 | ||||
-rw-r--r-- | datapath-windows/ovsext/Conntrack-related.c | 299 | ||||
-rw-r--r-- | datapath-windows/ovsext/ovsext.vcxproj | 2 |
4 files changed, 540 insertions, 0 deletions
diff --git a/datapath-windows/automake.mk b/datapath-windows/automake.mk index 88aa50ae6..53983ae67 100644 --- a/datapath-windows/automake.mk +++ b/datapath-windows/automake.mk @@ -12,8 +12,10 @@ EXTRA_DIST += \ datapath-windows/ovsext/Atomic.h \ datapath-windows/ovsext/BufferMgmt.c \ datapath-windows/ovsext/BufferMgmt.h \ + datapath-windows/ovsext/Conntrack-ftp.c \ datapath-windows/ovsext/Conntrack-icmp.c \ datapath-windows/ovsext/Conntrack-other.c \ + datapath-windows/ovsext/Conntrack-related.c \ datapath-windows/ovsext/Conntrack-tcp.c \ datapath-windows/ovsext/Conntrack.c \ datapath-windows/ovsext/Conntrack.h \ diff --git a/datapath-windows/ovsext/Conntrack-ftp.c b/datapath-windows/ovsext/Conntrack-ftp.c new file mode 100644 index 000000000..6830dfafa --- /dev/null +++ b/datapath-windows/ovsext/Conntrack-ftp.c @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2016 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 "PacketParser.h" + +/* Eg: 227 Entering Passive Mode (a1,a2,a3,a4,p1,p2)*/ +#define FTP_PASV_RSP_PREFIX "227" + +typedef enum FTP_TYPE { + FTP_TYPE_PASV = 1, + FTP_TYPE_ACTIVE +} FTP_TYPE; + +static __inline UINT32 +OvsStrncmp(const char *s1, const char *s2, size_t n) +{ + if (!s1 || !s2) { + return 0; + } + + const char *s2end = s2 + n; + while (s2 < s2end && *s2 != '\0' && toupper(*s1) == toupper(*s2)) { + s1++, s2++; + } + + if (s2end == s2) { + return 0; + } + + return (UINT32)(toupper(*s1) - toupper(*s2)); +} + +static __inline VOID +OvsStrlcpy(char *dest, const char *src, size_t size) +{ + /* XXX Replace ret with strlen(src) instead. */ + size_t ret = size; + if (size) { + size_t len = (ret >= size) ? size - 1 : ret; + memcpy(dest, src, len); + dest[len] = '\0'; + } +} + +/* + *--------------------------------------------------------------------------- + * OvsCtExtractNumbers + * Returns an array of numbers after parsing the string. + * Eg: PASV: 192,168,0,1,5,6 -> {192,168,0,1,5,6} + * EPRT: 192.168.0.1 -> {192,168,0,1} + * + *--------------------------------------------------------------------------- + */ +static __inline NDIS_STATUS +OvsCtExtractNumbers(char *buf, + UINT32 bufLen, + UINT32 arr[], + UINT32 arrLen, + char delimiter) +{ + if (!buf) { + return NDIS_STATUS_INVALID_PACKET; + } + + UINT32 i = 0; + + while (*buf != '\0') { + if (i >= bufLen || i >= arrLen) { + /* Non-standard FTP command */ + return NDIS_STATUS_INVALID_PARAMETER; + } + + /* Parse the number */ + if (*buf >= '0' && *buf <= '9') { + arr[i] = arr[i] * 10 + *buf - '0'; + } else if (*buf == delimiter) { + i++; + } else { + /* End of FTP response is either ) or \r\n */ + if (*buf == ')' || *buf == '\r' || *buf == '\n') { + return NDIS_STATUS_SUCCESS; + } + /* Could be non-numerals or space */ + } + buf++; + } + + /* Parsing ended without the correct format */ + return NDIS_STATUS_INVALID_PARAMETER; +} + +/* + *---------------------------------------------------------------------------- + * OvsCtHandleFtp + * Extract the FTP control data from the packet and created a related + * entry if it's a valid connection. This method doesn't support extended + * FTP yet. Supports PORT and PASV commands. + * Eg: + * 'PORT 192,168,137,103,192,22\r\n' -> '192.168.137.103' and 49174 + * '227 Entering Passive Mode (192,168,137,104,194,14)\r\n' gets extracted + * to '192.168.137.104' and 49678 + *---------------------------------------------------------------------------- + */ +NDIS_STATUS +OvsCtHandleFtp(PNET_BUFFER_LIST curNbl, + OvsFlowKey *key, + OVS_PACKET_HDR_INFO *layers, + UINT64 currentTime, + POVS_CT_ENTRY entry, + BOOLEAN request) +{ + NDIS_STATUS status; + FTP_TYPE ftpType = 0; + const char *buf; + char temp[256] = { 0 }; + char ftpMsg[256] = { 0 }; + + TCPHdr tcpStorage; + const TCPHdr *tcp; + tcp = OvsGetTcp(curNbl, layers->l4Offset, &tcpStorage); + if (!tcp) { + return NDIS_STATUS_INVALID_PACKET; + } + + UINT32 len = OvsGetTcpPayloadLength(curNbl); + if (len > sizeof(temp)) { + /* We only care up to 256 */ + len = sizeof(temp); + } + + buf = OvsGetPacketBytes(curNbl, len, + layers->l4Offset + TCP_HDR_LEN(tcp), + temp); + if (buf == NULL) { + return NDIS_STATUS_INVALID_PACKET; + } + + OvsStrlcpy((char *)ftpMsg, (char *)buf, min(len, sizeof(ftpMsg))); + char *req = NULL; + + if (request) { + if ((len >= 5) && (OvsStrncmp("PORT", ftpMsg, 4) == 0)) { + ftpType = FTP_TYPE_ACTIVE; + req = ftpMsg + 4; + } + } else { + if ((len >= 4) && (OvsStrncmp(FTP_PASV_RSP_PREFIX, ftpMsg, 3) == 0)) { + ftpType = FTP_TYPE_PASV; + /* There are various formats for PASV command. We try to support + * some of them. This has been addressed by RFC 2428 - EPSV. + * Eg: + * 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). + * 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2 + * 227 Entering Passive Mode. h1,h2,h3,h4,p1,p2 + * 227 =h1,h2,h3,h4,p1,p2 + */ + char *paren; + paren = strchr(ftpMsg, '('); + if (paren) { + req = paren + 1; + } else { + /* PASV command without ( */ + req = ftpMsg + 3; + } + } + } + + if (req == NULL) { + /* Not a PORT/PASV control packet */ + return NDIS_STATUS_SUCCESS; + } + + UINT32 arr[6] = {0}; + status = OvsCtExtractNumbers(req, len, arr, 6, ','); + + if (status != NDIS_STATUS_SUCCESS) { + return status; + } + + UINT32 ip = ntohl((arr[0] << 24) | (arr[1] << 16) | + (arr[2] << 8) | arr[3]); + UINT16 port = ntohs(((arr[4] << 8) | arr[5])); + + switch (ftpType) { + case FTP_TYPE_PASV: + /* Ensure that the command states Server's IP address */ + ASSERT(ip == key->ipKey.nwSrc); + + OvsCtRelatedEntryCreate(key->ipKey.nwProto, + key->l2.dlType, + /* Server's IP */ + ip, + /* Use intended client's IP */ + key->ipKey.nwDst, + /* Dynamic port opened on server */ + port, + /* We don't know the client port */ + 0, + currentTime, + entry); + break; + case FTP_TYPE_ACTIVE: + OvsCtRelatedEntryCreate(key->ipKey.nwProto, + key->l2.dlType, + /* Server's default IP address */ + key->ipKey.nwDst, + /* Client's IP address */ + ip, + /* FTP Data Port is 20 */ + ntohs(IPPORT_FTP_DATA), + /* Port opened up on Client */ + port, + currentTime, + entry); + break; + default: + OVS_LOG_ERROR("invalid ftp type:%d", ftpType); + status = NDIS_STATUS_INVALID_PARAMETER; + break; + } + + return status; +} diff --git a/datapath-windows/ovsext/Conntrack-related.c b/datapath-windows/ovsext/Conntrack-related.c new file mode 100644 index 000000000..2d95bc2f1 --- /dev/null +++ b/datapath-windows/ovsext/Conntrack-related.c @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2016 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 "Jhash.h" + +static PLIST_ENTRY ovsCtRelatedTable; /* Holds related entries */ +static UINT64 ctTotalRelatedEntries; +static OVS_CT_THREAD_CTX ctRelThreadCtx; +static PNDIS_RW_LOCK_EX ovsCtRelatedLockObj; +extern POVS_SWITCH_CONTEXT gOvsSwitchContext; + +static __inline UINT32 +OvsExtractCtRelatedKeyHash(OVS_CT_KEY *key) +{ + UINT32 hsrc, hdst,hash; + hsrc = OvsJhashBytes((UINT32*) &key->src, sizeof(key->src), 0); + hdst = OvsJhashBytes((UINT32*) &key->dst, sizeof(key->dst), 0); + hash = hsrc ^ hdst; /* TO identify reverse traffic */ + return hash; +} + +static __inline BOOLEAN +OvsCtRelatedKeyAreSame(OVS_CT_KEY incomingKey, OVS_CT_KEY entryKey) +{ + /* FTP PASV - Client initiates the connection from unknown port */ + if ((incomingKey.dst.addr.ipv4 == entryKey.src.addr.ipv4) && + (incomingKey.dst.port == entryKey.src.port) && + (incomingKey.src.addr.ipv4 == entryKey.dst.addr.ipv4) && + (incomingKey.dl_type == entryKey.dl_type) && + (incomingKey.nw_proto == entryKey.nw_proto)) { + return TRUE; + } + + /* FTP ACTIVE - Server initiates the connection */ + if ((incomingKey.src.addr.ipv4 == entryKey.src.addr.ipv4) && + (incomingKey.src.port == entryKey.src.port) && + (incomingKey.dst.addr.ipv4 == entryKey.dst.addr.ipv4) && + (incomingKey.dst.port == entryKey.dst.port) && + (incomingKey.dl_type == entryKey.dl_type) && + (incomingKey.nw_proto == entryKey.nw_proto)) { + return TRUE; + } + + return FALSE; +} + +/* + *--------------------------------------------------------------------------- + * OvsCtRelatedLookup + * Checks the related connections table for an entry that matches the + * incoming connection. If there is a matching entry, then it returns + * the pointer to the original control connection. + * + *--------------------------------------------------------------------------- + */ +POVS_CT_ENTRY +OvsCtRelatedLookup(OVS_CT_KEY key, UINT64 currentTime) +{ + PLIST_ENTRY link, next; + POVS_CT_REL_ENTRY entry; + LOCK_STATE_EX lockState; + + NdisAcquireRWLockRead(ovsCtRelatedLockObj, &lockState, 0); + + if (!ctTotalRelatedEntries) { + NdisReleaseRWLock(ovsCtRelatedLockObj, &lockState); + return NULL; + } + + for (int i = 0; i < CT_HASH_TABLE_SIZE; i++) { + /* XXX - Scan the table based on the hash instead */ + LIST_FORALL_SAFE(&ovsCtRelatedTable[i], link, next) { + entry = CONTAINING_RECORD(link, OVS_CT_REL_ENTRY, link); + if (entry->expiration > currentTime) { + if (OvsCtRelatedKeyAreSame(key, entry->key)) { + NdisReleaseRWLock(ovsCtRelatedLockObj, &lockState); + return entry->parent; + } + } + } + } + NdisReleaseRWLock(ovsCtRelatedLockObj, &lockState); + return NULL; +} + +static __inline VOID +OvsCtRelatedEntryDelete(POVS_CT_REL_ENTRY entry) +{ + RemoveEntryList(&entry->link); + OvsFreeMemoryWithTag(entry, OVS_CT_POOL_TAG); + ctTotalRelatedEntries--; +} + +NDIS_STATUS +OvsCtRelatedEntryCreate(UINT8 ipProto, + UINT16 dl_type, + UINT32 serverIp, + UINT32 clientIp, + UINT16 serverPort, + UINT16 clientPort, + UINT64 currentTime, + POVS_CT_ENTRY parent) +{ + LOCK_STATE_EX lockState; + POVS_CT_REL_ENTRY entry; + entry = OvsAllocateMemoryWithTag(sizeof(OVS_CT_REL_ENTRY), + OVS_CT_POOL_TAG); + if (!entry) { + return NDIS_STATUS_RESOURCES; + } + + RtlZeroMemory(entry, sizeof(struct OVS_CT_REL_ENTRY)); + entry->expiration = currentTime + (CT_INTERVAL_SEC * 60); + entry->key.src.addr.ipv4 = serverIp; + entry->key.dst.addr.ipv4 = clientIp; + entry->key.nw_proto = ipProto; + entry->key.dl_type = dl_type; + entry->key.src.port = serverPort; + entry->key.dst.port = clientPort; + entry->parent = parent; + + UINT32 hash = OvsExtractCtRelatedKeyHash(&entry->key); + + NdisAcquireRWLockWrite(ovsCtRelatedLockObj, &lockState, 0); + InsertHeadList(&ovsCtRelatedTable[hash & CT_HASH_TABLE_MASK], + &entry->link); + ctTotalRelatedEntries++; + NdisReleaseRWLock(ovsCtRelatedLockObj, &lockState); + + return NDIS_STATUS_SUCCESS; +} + +static __inline NDIS_STATUS +OvsCtRelatedFlush() +{ + PLIST_ENTRY link, next; + POVS_CT_REL_ENTRY entry; + + LOCK_STATE_EX lockState; + NdisAcquireRWLockWrite(ovsCtRelatedLockObj, &lockState, 0); + + if (ctTotalRelatedEntries) { + for (int i = 0; i < CT_HASH_TABLE_SIZE; i++) { + LIST_FORALL_SAFE(&ovsCtRelatedTable[i], link, next) { + entry = CONTAINING_RECORD(link, OVS_CT_REL_ENTRY, link); + OvsCtRelatedEntryDelete(entry); + } + } + } + + NdisReleaseRWLock(ovsCtRelatedLockObj, &lockState); + return NDIS_STATUS_SUCCESS; +} + +/* XXX - Create a wrapper for managing Tables used by Connection Trackers */ + +/* + *---------------------------------------------------------------------------- + * ovsCtRelatedEntryCleaner + * Runs periodically and cleans up the related connections tracker + *---------------------------------------------------------------------------- + */ +VOID +ovsCtRelatedEntryCleaner(PVOID data) +{ + POVS_CT_THREAD_CTX context = (POVS_CT_THREAD_CTX)data; + PLIST_ENTRY link, next; + POVS_CT_REL_ENTRY entry; + BOOLEAN success = TRUE; + + while (success) { + LOCK_STATE_EX lockState; + NdisAcquireRWLockWrite(ovsCtRelatedLockObj, &lockState, 0); + if (context->exit) { + NdisReleaseRWLock(ovsCtRelatedLockObj, &lockState); + break; + } + + /* Set the timeout for the thread and cleanup */ + UINT64 currentTime, threadSleepTimeout; + NdisGetCurrentSystemTime((LARGE_INTEGER *)¤tTime); + threadSleepTimeout = currentTime + CT_CLEANUP_INTERVAL; + + if (ctTotalRelatedEntries) { + for (int i = 0; i < CT_HASH_TABLE_SIZE; i++) { + LIST_FORALL_SAFE(&ovsCtRelatedTable[i], link, next) { + entry = CONTAINING_RECORD(link, OVS_CT_REL_ENTRY, link); + if (entry->expiration < currentTime) { + OvsCtRelatedEntryDelete(entry); + } + } + } + } + NdisReleaseRWLock(ovsCtRelatedLockObj, &lockState); + KeWaitForSingleObject(&context->event, Executive, KernelMode, + FALSE, (LARGE_INTEGER *)&threadSleepTimeout); + } + + PsTerminateSystemThread(STATUS_SUCCESS); +} + +/* + *---------------------------------------------------------------------------- + * OvsInitCtRelated + * Initialize the components used by Related Connections Tracker + *---------------------------------------------------------------------------- + */ +NTSTATUS +OvsInitCtRelated(POVS_SWITCH_CONTEXT context) +{ + NTSTATUS status; + HANDLE threadHandle = NULL; + ctTotalRelatedEntries = 0; + + /* Init the sync-lock */ + ovsCtRelatedLockObj = NdisAllocateRWLock(context->NdisFilterHandle); + if (ovsCtRelatedLockObj == NULL) { + return STATUS_INSUFFICIENT_RESOURCES; + } + + /* Init the Hash Buffer */ + ovsCtRelatedTable = OvsAllocateMemoryWithTag(sizeof(LIST_ENTRY) + * CT_HASH_TABLE_SIZE, + OVS_CT_POOL_TAG); + if (ovsCtRelatedTable == NULL) { + NdisFreeRWLock(ovsCtRelatedLockObj); + ovsCtRelatedLockObj = NULL; + return STATUS_INSUFFICIENT_RESOURCES; + } + + for (int i = 0; i < CT_HASH_TABLE_SIZE; i++) { + InitializeListHead(&ovsCtRelatedTable[i]); + } + + /* Init CT Cleaner Thread */ + KeInitializeEvent(&ctRelThreadCtx.event, NotificationEvent, FALSE); + status = PsCreateSystemThread(&threadHandle, SYNCHRONIZE, NULL, NULL, + NULL, ovsCtRelatedEntryCleaner, + &ctRelThreadCtx); + + if (status != STATUS_SUCCESS) { + NdisFreeRWLock(ovsCtRelatedLockObj); + ovsCtRelatedLockObj = NULL; + + OvsFreeMemoryWithTag(ovsCtRelatedTable, OVS_CT_POOL_TAG); + ovsCtRelatedTable = NULL; + + return status; + } + + ObReferenceObjectByHandle(threadHandle, SYNCHRONIZE, NULL, KernelMode, + &ctRelThreadCtx.threadObject, NULL); + ZwClose(threadHandle); + threadHandle = NULL; + return STATUS_SUCCESS; +} + +/* + *---------------------------------------------------------------------------- + * OvsCleanupCtRelated + * Cleanup memory and thread that were spawned for tracking related entry + *---------------------------------------------------------------------------- + */ +VOID +OvsCleanupCtRelated(VOID) +{ + LOCK_STATE_EX lockState; + NdisAcquireRWLockWrite(ovsCtRelatedLockObj, &lockState, 0); + ctRelThreadCtx.exit = 1; + KeSetEvent(&ctRelThreadCtx.event, 0, FALSE); + NdisReleaseRWLock(ovsCtRelatedLockObj, &lockState); + + KeWaitForSingleObject(ctRelThreadCtx.threadObject, Executive, + KernelMode, FALSE, NULL); + ObDereferenceObject(ctRelThreadCtx.threadObject); + + if (ovsCtRelatedTable) { + OvsCtRelatedFlush(); + OvsFreeMemoryWithTag(ovsCtRelatedTable, OVS_CT_POOL_TAG); + ovsCtRelatedTable = NULL; + } + + NdisFreeRWLock(ovsCtRelatedLockObj); + ovsCtRelatedLockObj = NULL; +} diff --git a/datapath-windows/ovsext/ovsext.vcxproj b/datapath-windows/ovsext/ovsext.vcxproj index 77530fdd3..e311a097a 100644 --- a/datapath-windows/ovsext/ovsext.vcxproj +++ b/datapath-windows/ovsext/ovsext.vcxproj @@ -178,6 +178,8 @@ <ItemGroup> <ClCompile Include="Actions.c" /> <ClCompile Include="BufferMgmt.c" /> + <ClCompile Include="Conntrack-related.c" /> + <ClCompile Include="Conntrack-ftp.c" /> <ClCompile Include="Conntrack-icmp.c" /> <ClCompile Include="Conntrack-other.c" /> <ClCompile Include="Conntrack-tcp.c" /> |