diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:39:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:39:49 +0000 |
commit | a0aa2307322cd47bbf416810ac0292925e03be87 (patch) | |
tree | 37076262a026c4b48c8a0e84f44ff9187556ca35 /src/util-macset.c | |
parent | Initial commit. (diff) | |
download | suricata-a0aa2307322cd47bbf416810ac0292925e03be87.tar.xz suricata-a0aa2307322cd47bbf416810ac0292925e03be87.zip |
Adding upstream version 1:7.0.3.upstream/1%7.0.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/util-macset.c')
-rw-r--r-- | src/util-macset.c | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/src/util-macset.c b/src/util-macset.c new file mode 100644 index 0000000..9853a32 --- /dev/null +++ b/src/util-macset.c @@ -0,0 +1,431 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Sascha Steinbiss <sascha.steinbiss@dcso.de> + * + * Set-like data store for MAC addresses. Implemented as array for memory + * locality reasons as the expected number of items is typically low. + * + */ + +#include "suricata-common.h" +#include "suricata.h" +#include "flow-util.h" +#include "flow-private.h" +#include "flow-storage.h" +#include "util-macset.h" +#include "util-unittest.h" +#include "util-unittest-helper.h" + +typedef uint8_t MacAddr[6]; +typedef enum { + EMPTY_SET, /* no address inserted yet */ + SINGLE_MAC, /* we have a single pair of addresses (likely) */ + MULTI_MAC /* we have multiple addresses per flow */ +} MacSetState; + +struct MacSet_ { + /* static store for a single MAC address per side */ + MacAddr singles[2]; + /* state determines how addresses are stored per side: + - SINGLE_MAC uses static locations allocated with the MacSet + itself to store a single address (most likely case) + - MULTI_MAC is used when more than one distinct address + is detected (causes another allocation and linear-time add) */ + MacSetState state[2]; + /* buffer for multiple MACs per flow and direction */ + MacAddr *buf[2]; + int size, + last[2]; +}; + +FlowStorageId g_macset_storage_id = { .id = -1 }; + +void MacSetRegisterFlowStorage(void) +{ + ConfNode *root = ConfGetNode("outputs"); + ConfNode *node = NULL; + /* we only need to register if at least one enabled 'eve-log' output + has the ethernet setting enabled */ + if (root != NULL) { + TAILQ_FOREACH(node, &root->head, next) { + if (node->val && strcmp(node->val, "eve-log") == 0) { + const char *enabled = ConfNodeLookupChildValue(node->head.tqh_first, "enabled"); + if (enabled != NULL && ConfValIsTrue(enabled)) { + const char *ethernet = ConfNodeLookupChildValue(node->head.tqh_first, "ethernet"); + if (ethernet != NULL && ConfValIsTrue(ethernet)) { + g_macset_storage_id = FlowStorageRegister("macset", sizeof(void *), + NULL, (void(*)(void *)) MacSetFree); + return; + } + } + } + } + } +} + +bool MacSetFlowStorageEnabled(void) +{ + return (g_macset_storage_id.id != -1); +} + + +MacSet *MacSetInit(int size) +{ + MacSet *ms = NULL; + if (!FLOW_CHECK_MEMCAP(sizeof(*ms))) { + return NULL; + } + ms = SCCalloc(1, sizeof(*ms)); + if (unlikely(ms == NULL)) { + SCLogError("Unable to allocate MacSet memory"); + return NULL; + } + (void) SC_ATOMIC_ADD(flow_memuse, (sizeof(*ms))); + ms->state[MAC_SET_SRC] = ms->state[MAC_SET_DST] = EMPTY_SET; + if (size < 3) { + /* we want to make sure we have at space for at least 3 items to + fit MACs during the initial extension to MULTI_MAC storage */ + size = 3; + } + ms->size = size; + ms->last[MAC_SET_SRC] = ms->last[MAC_SET_DST] = 0; + return ms; +} + +FlowStorageId MacSetGetFlowStorageID(void) +{ + return g_macset_storage_id; +} + +static inline void MacUpdateEntry(MacSet *ms, uint8_t *addr, int side, ThreadVars *tv, uint16_t ctr) +{ + switch (ms->state[side]) { + case EMPTY_SET: + memcpy(ms->singles[side], addr, sizeof(MacAddr)); + ms->state[side] = SINGLE_MAC; + if (tv != NULL) + StatsSetUI64(tv, ctr, 1); + break; + case SINGLE_MAC: + if (unlikely(memcmp(addr, ms->singles[side], sizeof(MacAddr)) != 0)) { + if (ms->buf[side] == NULL) { + if (!FLOW_CHECK_MEMCAP(ms->size * sizeof(MacAddr))) { + /* in this case there is not much we can do */ + return; + } + ms->buf[side] = SCCalloc(ms->size, sizeof(MacAddr)); + if (unlikely(ms->buf[side] == NULL)) { + SCLogError("Unable to allocate " + "MacSet memory"); + return; + } + (void) SC_ATOMIC_ADD(flow_memuse, (ms->size * sizeof(MacAddr))); + } + memcpy(ms->buf[side], ms->singles[side], sizeof(MacAddr)); + memcpy(ms->buf[side] + 1, addr, sizeof(MacAddr)); + ms->last[side] = 2; + if (tv != NULL) + StatsSetUI64(tv, ctr, 2); + ms->state[side] = MULTI_MAC; + } + break; + case MULTI_MAC: + if (unlikely(ms->last[side] == ms->size)) { + /* MacSet full, ignore item. We intentionally do not output + any warning in order not to stall packet processing */ + return; + } + /* If the set is non-empty... */ + if (ms->last[side] > 0) { + /* ...we search for duplicates in the set to decide whether + we need to insert the current item. We do this backwards, + since we expect the latest item to match more likely than + the first */ + for (int i = ms->last[side] - 1; i >= 0; i--) { + uint8_t *addr2 = (uint8_t*) ((ms->buf[side]) + i); + /* If we find a match, we return early with no action */ + if (likely(memcmp(addr2, addr, sizeof(MacAddr)) == 0)) { + return; + } + } + } + /* Otherwise, we insert the new address at the end */ + memcpy(ms->buf[side] + ms->last[side], addr, sizeof(MacAddr)); + ms->last[side]++; + if (tv != NULL) + StatsSetUI64(tv, ctr, ms->last[side]); + break; + } +} + +void MacSetAddWithCtr(MacSet *ms, uint8_t *src_addr, uint8_t *dst_addr, ThreadVars *tv, + uint16_t ctr_src, uint16_t ctr_dst) +{ + if (ms == NULL) + return; + MacUpdateEntry(ms, src_addr, MAC_SET_SRC, tv, ctr_src); + MacUpdateEntry(ms, dst_addr, MAC_SET_DST, tv, ctr_dst); +} + +void MacSetAdd(MacSet *ms, uint8_t *src_addr, uint8_t *dst_addr) +{ + MacSetAddWithCtr(ms, src_addr, dst_addr, NULL, 0, 0); +} + +static inline int MacSetIterateSide(const MacSet *ms, MacSetIteratorFunc IterFunc, + MacSetSide side, void *data) +{ + int ret = 0; + switch (ms->state[side]) { + case EMPTY_SET: + return 0; + case SINGLE_MAC: + ret = IterFunc((uint8_t*) ms->singles[side], side, data); + if (unlikely(ret != 0)) { + return ret; + } + break; + case MULTI_MAC: + for (int i = 0; i < ms->last[side]; i++) { + ret = IterFunc((uint8_t*) ms->buf[side][i], side, data); + if (unlikely(ret != 0)) { + return ret; + } + } + break; + } + return 0; +} + +int MacSetForEach(const MacSet *ms, MacSetIteratorFunc IterFunc, void *data) +{ + int ret = 0; + if (ms == NULL) + return 0; + + ret = MacSetIterateSide(ms, IterFunc, MAC_SET_SRC, data); + if (ret != 0) { + return ret; + } + return MacSetIterateSide(ms, IterFunc, MAC_SET_DST, data); +} + +int MacSetSize(const MacSet *ms) +{ + int size = 0; + if (ms == NULL) + return 0; + + switch(ms->state[MAC_SET_SRC]) { + case EMPTY_SET: + /* pass */ + break; + case SINGLE_MAC: + size += 1; + break; + case MULTI_MAC: + size += ms->last[MAC_SET_SRC]; + break; + } + switch(ms->state[MAC_SET_DST]) { + case EMPTY_SET: + /* pass */ + break; + case SINGLE_MAC: + size += 1; + break; + case MULTI_MAC: + size += ms->last[MAC_SET_DST]; + break; + } + return size; +} + +void MacSetFree(MacSet *ms) +{ + size_t total_free = 0; + if (ms == NULL) + return; + if (ms->buf[MAC_SET_SRC] != NULL) { + SCFree(ms->buf[MAC_SET_SRC]); + total_free += ms->size * sizeof(MacAddr); + } + if (ms->buf[MAC_SET_DST] != NULL) { + SCFree(ms->buf[MAC_SET_DST]); + total_free += ms->size * sizeof(MacAddr); + } + SCFree(ms); + total_free += sizeof(*ms); + (void) SC_ATOMIC_SUB(flow_memuse, total_free); +} + +#ifdef UNITTESTS + +static int CheckTest1Membership(uint8_t *addr, MacSetSide side, void *data) +{ + int *i = (int*) data; + switch (*i) { + case 0: + if (addr[5] != 1) return 1; + break; + case 1: + if (addr[5] != 2) return 1; + break; + case 2: + if (addr[5] != 3) return 1; + break; + } + (*i)++; + return 0; +} + +static int MacSetTest01(void) +{ + MacSet *ms = NULL; + int ret = 0, i = 0; + MacAddr addr1 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, + addr2 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x2}, + addr3 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x3}; + SC_ATOMIC_SET(flow_config.memcap, 10000); + + ms = MacSetInit(10); + FAIL_IF_NULL(ms); + FAIL_IF_NOT(MacSetSize(ms) == 0); + + ret = MacSetForEach(ms, CheckTest1Membership, &i); + FAIL_IF_NOT(ret == 0); + + MacSetAdd(ms, addr1, addr2); + FAIL_IF_NOT(MacSetSize(ms) == 2); + + ret = MacSetForEach(ms, CheckTest1Membership, &i); + FAIL_IF_NOT(ret == 0); + + MacSetAdd(ms, addr1, addr3); + FAIL_IF_NOT(MacSetSize(ms) == 3); + + i = 0; + ret = MacSetForEach(ms, CheckTest1Membership, &i); + FAIL_IF_NOT(ret == 0); + + MacSetFree(ms); + PASS; +} + +static int MacSetTest02(void) +{ + MacSet *ms = NULL; + int ret = 0, i = 0; + SC_ATOMIC_SET(flow_config.memcap, 10000); + + ms = MacSetInit(10); + FAIL_IF_NULL(ms); + FAIL_IF_NOT(MacSetSize(ms) == 0); + + for (i = 1; i < 100; i++) { + MacAddr addr1 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, + addr2 = {0x1, 0x0, 0x0, 0x0, 0x0, 0x2}; + MacSetAdd(ms, addr1, addr2); + } + FAIL_IF_NOT(MacSetSize(ms) == 2); + + ret = MacSetForEach(ms, CheckTest1Membership, &i); + FAIL_IF_NOT(ret == 0); + + MacSetFree(ms); + PASS; +} + +static int MacSetTest03(void) +{ + MacSet *ms = NULL; + SC_ATOMIC_SET(flow_config.memcap, 10000); + + ms = MacSetInit(10); + FAIL_IF_NULL(ms); + FAIL_IF_NOT(MacSetSize(ms) == 0); + + for (uint8_t i = 1; i < 100; i++) { + MacAddr addr1 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, + addr2 = {0x1, 0x0, 0x0, 0x0, 0x0, 0x1}; + addr1[5] = i; + addr2[5] = i; + MacSetAdd(ms, addr1, addr2); + } + FAIL_IF_NOT(MacSetSize(ms) == 20); + + MacSetFree(ms); + PASS; +} + +static int MacSetTest04(void) +{ + MacSet *ms = NULL; + SC_ATOMIC_SET(flow_config.memcap, 2); + + ms = MacSetInit(10); + FAIL_IF_NOT_NULL(ms); + + PASS; +} + +static int MacSetTest05(void) +{ + MacSet *ms = NULL; + int ret = 0; + SC_ATOMIC_SET(flow_config.memcap, 64); + + ms = MacSetInit(10); + FAIL_IF_NULL(ms); + FAIL_IF_NOT(MacSetSize(ms) == 0); + + for (uint8_t i = 1; i < 100; i++) { + MacAddr addr1 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, + addr2 = {0x1, 0x0, 0x0, 0x0, 0x0, 0x1}; + addr1[5] = i; + addr2[5] = i; + MacSetAdd(ms, addr1, addr2); + } + FAIL_IF_NOT(MacSetSize(ms) == 2); + + int i2 = 100; + ret = MacSetForEach(ms, CheckTest1Membership, &i2); + FAIL_IF_NOT(ret == 0); + + MacSetFree(ms); + PASS; +} + +#endif /* UNITTESTS */ + +void MacSetRegisterTests(void) +{ + +#ifdef UNITTESTS + UtRegisterTest("MacSetTest01", MacSetTest01); + UtRegisterTest("MacSetTest02", MacSetTest02); + UtRegisterTest("MacSetTest03", MacSetTest03); + UtRegisterTest("MacSetTest04", MacSetTest04); + UtRegisterTest("MacSetTest05", MacSetTest05); +#endif + + return; +} |