[ovs-dev] [PATCH 3/4] datapath-windows: Conntrack - Introduce support for tracking related connections

Sairam Venugopal vsairam at vmware.com
Thu Dec 1 21:19:03 UTC 2016


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 at vmware.com>
---
 datapath-windows/automake.mk                |   2 +
 datapath-windows/ovsext/Conntrack-ftp.c     | 234 ++++++++++++++++++++++
 datapath-windows/ovsext/Conntrack-related.c | 300 ++++++++++++++++++++++++++++
 datapath-windows/ovsext/ovsext.vcxproj      |   2 +
 4 files changed, 538 insertions(+)
 create mode 100644 datapath-windows/ovsext/Conntrack-ftp.c
 create mode 100644 datapath-windows/ovsext/Conntrack-related.c

diff --git a/datapath-windows/automake.mk b/datapath-windows/automake.mk
index faa1fc6..c53de9f 100644
--- a/datapath-windows/automake.mk
+++ b/datapath-windows/automake.mk
@@ -14,8 +14,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 0000000..f6b3358
--- /dev/null
+++ b/datapath-windows/ovsext/Conntrack-ftp.c
@@ -0,0 +1,234 @@
+/*
+ * 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"
+
+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') {
+                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("227", 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 0000000..66cd034
--- /dev/null
+++ b/datapath-windows/ovsext/Conntrack-related.c
@@ -0,0 +1,300 @@
+/*
+ * 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 77530fd..e311a09 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" />
-- 
2.9.0.windows.1



More information about the dev mailing list