summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSairam Venugopal <vsairam@vmware.com>2016-12-16 14:28:11 -0800
committerGurucharan Shetty <guru@ovn.org>2016-12-20 09:33:03 -0800
commit7fd123b627f35a7da6aa534276760405d66ccbbe (patch)
treeae8a3f402ee6073e646c64587f363c7deb8b84eb
parent5e422c9ef2c4bf60a7856ae19a3686e9f63f3e62 (diff)
downloadopenvswitch-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.mk2
-rw-r--r--datapath-windows/ovsext/Conntrack-ftp.c237
-rw-r--r--datapath-windows/ovsext/Conntrack-related.c299
-rw-r--r--datapath-windows/ovsext/ovsext.vcxproj2
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 *)&currentTime);
+ 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" />