Message ID | 20161216222812.128904-4-vsairam@vmware.com |
---|---|
State | Accepted |
Headers | show |
Acked-by: Alin Gabriel Serdean <aserdean@cloudbasesolutions.com> > -----Original Message----- > From: ovs-dev-bounces@openvswitch.org [mailto:ovs-dev- > bounces@openvswitch.org] On Behalf Of Sairam Venugopal > Sent: Saturday, December 17, 2016 12:28 AM > To: dev@openvswitch.org > Subject: [ovs-dev] [PATCH v3 3/4] 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> > --- > datapath-windows/automake.mk | 2 + > datapath-windows/ovsext/Conntrack-ftp.c | 237 > ++++++++++++++++++++++ > datapath-windows/ovsext/Conntrack-related.c | 299 > ++++++++++++++++++++++++++++ > datapath-windows/ovsext/ovsext.vcxproj | 2 + > 4 files changed, 540 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 88aa50a..53983ae 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 0000000..6830dfa > --- /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 0000000..2d95bc2 > --- /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 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 > > _______________________________________________ > dev mailing list > dev@openvswitch.org > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
diff --git a/datapath-windows/automake.mk b/datapath-windows/automake.mk index 88aa50a..53983ae 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 0000000..6830dfa --- /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 0000000..2d95bc2 --- /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 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" />
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> --- datapath-windows/automake.mk | 2 + datapath-windows/ovsext/Conntrack-ftp.c | 237 ++++++++++++++++++++++ datapath-windows/ovsext/Conntrack-related.c | 299 ++++++++++++++++++++++++++++ datapath-windows/ovsext/ovsext.vcxproj | 2 + 4 files changed, 540 insertions(+) create mode 100644 datapath-windows/ovsext/Conntrack-ftp.c create mode 100644 datapath-windows/ovsext/Conntrack-related.c