]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
output-json: add MAC address output 5258/head
authorSascha Steinbiss <satta@debian.org>
Mon, 2 Mar 2020 18:10:30 +0000 (19:10 +0100)
committerVictor Julien <victor@inliniac.net>
Sun, 2 Aug 2020 18:36:44 +0000 (20:36 +0200)
This commit adds MAC address output to the EVE-JSON format. We follow the
remarks made in Redmine ticket #962: for packets, log MAC src/dst as a
scalar field in EVE; for flows, log MAC src/dst as lists in EVE. Field names
are different between flow and packet context to avoid type confusion
(src_mac vs. src_macs). Configuration approach and JSON representation is
taken from previous GitHub PR #2700.

16 files changed:
doc/userguide/output/eve/eve-json-output.rst
src/Makefile.am
src/decode.c
src/decode.h
src/flow-util.c
src/flow-util.h
src/flow-worker.c
src/flow.c
src/flow.h
src/output-json.c
src/output-json.h
src/runmode-unittests.c
src/suricata.c
src/util-macset.c [new file with mode: 0644]
src/util-macset.h [new file with mode: 0644]
suricata.yaml.in

index a8c7ce9320b410f59dbd3a1ee51c70123bf581af..69f8b071fc7ed1374d15d6a1d4dae6271b820662 100644 (file)
@@ -14,6 +14,9 @@ where all these logs go into a single file.
 Each alert, http log, etc will go into this one file: 'eve.json'. This file
 can then be processed by 3rd party tools like Logstash (ELK) or jq.
 
+If ``ethernet`` is set to yes, then ethernet headers will be added to events
+if available.
+
 Output types
 ~~~~~~~~~~~~
 
@@ -33,6 +36,7 @@ Output types::
       #facility: local5
       #level: Info ## possible levels: Emergency, Alert, Critical,
                    ## Error, Warning, Notice, Info, Debug
+      #ethernet: no # log ethernet header in events when available
       #redis:
       #  server: 127.0.0.1
       #  port: 6379
index 0b89e932cb65b611121fa77cb3f1cc42440bfbf1..df3f52d68c619a0547f325d019272b688f97ce21 100755 (executable)
@@ -505,6 +505,7 @@ util-lua-tls.c util-lua-tls.h \
 util-lua-ssh.c util-lua-ssh.h \
 util-lua-hassh.c util-lua-hassh.h \
 util-lua-smtp.c util-lua-smtp.h \
+util-macset.c util-macset.h \
 util-magic.c util-magic.h \
 util-memcmp.c util-memcmp.h \
 util-memcpy.h \
index 8e68a24d764b84ab6ec0de067a1bdb1ad143fc0f..7083e62a182aff325d5b964447caf7c13ea44f17 100644 (file)
@@ -506,6 +506,8 @@ void DecodeRegisterPerfCounters(DecodeThreadVars *dtv, ThreadVars *tv)
     dtv->counter_mpls = StatsRegisterCounter("decoder.mpls", tv);
     dtv->counter_avg_pkt_size = StatsRegisterAvgCounter("decoder.avg_pkt_size", tv);
     dtv->counter_max_pkt_size = StatsRegisterMaxCounter("decoder.max_pkt_size", tv);
+    dtv->counter_max_mac_addrs_src = StatsRegisterMaxCounter("decoder.max_mac_addrs_src", tv);
+    dtv->counter_max_mac_addrs_dst = StatsRegisterMaxCounter("decoder.max_mac_addrs_dst", tv);
     dtv->counter_erspan = StatsRegisterMaxCounter("decoder.erspan", tv);
     dtv->counter_flow_memcap = StatsRegisterCounter("flow.memcap", tv);
 
index aa5591518f802bfd87985434695b031097747c43..ffeb57c845a60bec460fee6b282f3e0a38b8a973 100644 (file)
@@ -631,6 +631,8 @@ typedef struct DecodeThreadVars_
     uint16_t counter_bytes;
     uint16_t counter_avg_pkt_size;
     uint16_t counter_max_pkt_size;
+    uint16_t counter_max_mac_addrs_src;
+    uint16_t counter_max_mac_addrs_dst;
 
     uint16_t counter_invalid;
 
index ea18d053ba5f2bc0ffbb9dc72192d7964f1a3a76..f4dad5dd80aa055b344cfe56b4eeb36da83222e5 100644 (file)
@@ -197,6 +197,16 @@ void FlowInit(Flow *f, const Packet *p)
 
     f->protomap = FlowGetProtoMapping(f->proto);
 
+    if (MacSetFlowStorageEnabled()) {
+        MacSet *ms = FlowGetStorageById(f, MacSetGetFlowStorageID());
+        if (ms != NULL) {
+            MacSetReset(ms);
+        } else {
+            ms = MacSetInit(10);
+            FlowSetStorageById(f, MacSetGetFlowStorageID(), ms);
+        }
+    }
+
     SCReturn;
 }
 
index 96ed814c7436e7e92686616ccbaf02d7ba81e42f..ee952944762424298199523e8d2e3313f64d4c04 100644 (file)
         (f)->sgh_toclient = NULL; \
         GenericVarFree((f)->flowvar); \
         (f)->flowvar = NULL; \
+        if (MacSetFlowStorageEnabled()) { \
+            MacSet *ms = FlowGetStorageById((f), MacSetGetFlowStorageID()); \
+            if (ms != NULL) { \
+                MacSetReset(ms); \
+            } \
+        } \
         RESET_COUNTERS((f)); \
     } while(0)
 
index 18757308f00071d2b2ac3ce0a2292817bfc8413e..489ffefa4f6c03dff01da086a78feb80877800c9 100644 (file)
@@ -75,7 +75,7 @@ typedef struct FlowWorkerThreadData_ {
  */
 static inline TmEcode FlowUpdate(ThreadVars *tv, FlowWorkerThreadData *fw, Packet *p)
 {
-    FlowHandlePacketUpdate(p->flow, p);
+    FlowHandlePacketUpdate(p->flow, p, tv, fw->dtv);
 
     int state = SC_ATOMIC_GET(p->flow->flow_state);
     switch (state) {
index 3459b10cfe04c6a9569e5ad5084cef860709e59a..a0e637ed94d49fa0abc6df9ce554e05c3dd3779c 100644 (file)
@@ -402,6 +402,25 @@ static inline void FlowUpdateTTL(Flow *f, Packet *p, uint8_t ttl)
     }
 }
 
+static inline void FlowUpdateEthernet(ThreadVars *tv, DecodeThreadVars *dtv,
+                                      Flow *f, EthernetHdr *ethh, bool toserver)
+{
+    if (ethh && MacSetFlowStorageEnabled()) {
+        MacSet *ms = FlowGetStorageById(f, MacSetGetFlowStorageID());
+        if (ms != NULL) {
+            if (toserver) {
+                MacSetAddWithCtr(ms, ethh->eth_src, ethh->eth_dst, tv,
+                                 dtv->counter_max_mac_addrs_src,
+                                 dtv->counter_max_mac_addrs_dst);
+            } else {
+                MacSetAddWithCtr(ms, ethh->eth_dst, ethh->eth_src, tv,
+                                 dtv->counter_max_mac_addrs_dst,
+                                 dtv->counter_max_mac_addrs_src);
+            }
+        }
+    }
+}
+
 /** \brief Update Packet and Flow
  *
  *  Updates packet and flow based on the new packet.
@@ -411,7 +430,7 @@ static inline void FlowUpdateTTL(Flow *f, Packet *p, uint8_t ttl)
  *
  *  \note overwrites p::flowflags
  */
-void FlowHandlePacketUpdate(Flow *f, Packet *p)
+void FlowHandlePacketUpdate(Flow *f, Packet *p, ThreadVars *tv, DecodeThreadVars *dtv)
 {
     SCLogDebug("packet %"PRIu64" -- flow %p", p->pcap_cnt, f);
 
@@ -454,6 +473,7 @@ void FlowHandlePacketUpdate(Flow *f, Packet *p)
             f->flags &= ~FLOW_PROTO_DETECT_TS_DONE;
             p->flags |= PKT_PROTO_DETECT_TS_DONE;
         }
+        FlowUpdateEthernet(tv, dtv, f, p->ethh, true);
     } else {
         f->tosrcpktcnt++;
         f->tosrcbytecnt += GET_PKT_LEN(p);
@@ -469,6 +489,7 @@ void FlowHandlePacketUpdate(Flow *f, Packet *p)
             f->flags &= ~FLOW_PROTO_DETECT_TC_DONE;
             p->flags |= PKT_PROTO_DETECT_TC_DONE;
         }
+        FlowUpdateEthernet(tv, dtv, f, p->ethh, false);
     }
 
     if (SC_ATOMIC_GET(f->flow_state) == FLOW_STATE_ESTABLISHED) {
@@ -498,7 +519,6 @@ void FlowHandlePacketUpdate(Flow *f, Packet *p)
         DecodeSetNoPayloadInspectionFlag(p);
     }
 
-
     /* update flow's ttl fields if needed */
     if (PKT_IS_IPV4(p)) {
         FlowUpdateTTL(f, p, IPV4_GET_IPTTL(p));
index f625ada1060af6e866922965fb3f46b70a8407b8..24113ae3b22d0ef87ae5469175c613f86807c065 100644 (file)
@@ -29,6 +29,7 @@
 #include "util-atomic.h"
 #include "util-device.h"
 #include "detect-tag.h"
+#include "util-macset.h"
 #include "util-optimize.h"
 
 /* Part of the flow structure, so we declare it here.
@@ -661,6 +662,6 @@ AppProto FlowGetAppProtocol(const Flow *f);
 void *FlowGetAppState(const Flow *f);
 uint8_t FlowGetDisruptionFlags(const Flow *f, uint8_t flags);
 
-void FlowHandlePacketUpdate(Flow *f, Packet *p);
+void FlowHandlePacketUpdate(Flow *f, Packet *p, ThreadVars *tv, DecodeThreadVars *dtv);
 
 #endif /* __FLOW_H__ */
index e3c2381ea1e3b46fa347b3b20065870c9c42d12d..09db2494d4d7602d8ffdd83618209108336582fb 100644 (file)
@@ -63,6 +63,7 @@
 
 #include "flow-var.h"
 #include "flow-bit.h"
+#include "flow-storage.h"
 
 #include "source-pcap-file.h"
 
@@ -435,12 +436,19 @@ static void EveAddMetadata(const Packet *p, const Flow *f, JsonBuilder *js)
     }
 }
 
+int CreateJSONEther(JsonBuilder *parent, const Packet *p, const MacSet *ms);
+
 void EveAddCommonOptions(const OutputJsonCommonSettings *cfg,
         const Packet *p, const Flow *f, JsonBuilder *js)
 {
     if (cfg->include_metadata) {
         EveAddMetadata(p, f, js);
     }
+    if (cfg->include_ethernet) {
+        MacSet *ms = FlowGetStorageById((Flow*) f, MacSetGetFlowStorageID());
+        if (ms != NULL)
+            CreateJSONEther(js, p, ms);
+    }
     if (cfg->include_community_id && f != NULL) {
         CreateEveCommunityFlowId(js, f, cfg->community_id_seed);
     }
@@ -758,6 +766,80 @@ void CreateEveFlowId(JsonBuilder *js, const Flow *f)
     }
 }
 
+static inline void JSONFormatAndAddMACAddr(JsonBuilder *js, const char *key,
+                                   uint8_t *val, bool is_array)
+{
+    char eth_addr[19];
+    (void) snprintf(eth_addr, 19, "%02x:%02x:%02x:%02x:%02x:%02x",
+                    val[0], val[1], val[2], val[3], val[4], val[5]);
+    if (is_array) {
+        jb_append_string(js, eth_addr);
+    } else {
+        jb_set_string(js, key, eth_addr);
+    }
+}
+
+/* only required to traverse the MAC address set */
+typedef struct JSONMACAddrInfo {
+    JsonBuilder *src, *dst;
+} JSONMACAddrInfo;
+
+static int MacSetIterateToJSON(uint8_t *val, MacSetSide side, void *data)
+{
+    JSONMACAddrInfo *info = (JSONMACAddrInfo*) data;
+    if (side == MAC_SET_DST) {
+        JSONFormatAndAddMACAddr(info->dst, NULL, val, true);
+    } else {
+        JSONFormatAndAddMACAddr(info->src, NULL, val, true);
+    }
+    return 0;
+}
+
+int CreateJSONEther(JsonBuilder *js, const Packet *p, const MacSet *ms)
+{
+    jb_open_object(js, "ether");
+    if (unlikely(js == NULL))
+        return 0;
+    if (p == NULL) {
+        /* we are creating an ether object in a flow context, so we need to
+           append to arrays */
+        if (MacSetSize(ms) > 0) {
+            JSONMACAddrInfo info;
+            info.dst = jb_new_array();
+            info.src = jb_new_array();
+            int ret = MacSetForEach(ms, MacSetIterateToJSON, &info);
+            if (unlikely(ret != 0)) {
+                /* should not happen, JSONFlowAppendMACAddrs is sane */
+                jb_free(info.dst);
+                jb_free(info.src);
+                return ret;
+            }
+            jb_close(info.dst);
+            jb_close(info.src);
+            jb_set_object(js, "dest_macs", info.dst);
+            jb_set_object(js, "src_macs", info.src);
+            jb_free(info.dst);
+            jb_free(info.src);
+        }
+    } else {
+        /* this is a packet context, so we need to add scalar fields */
+        uint8_t *src, *dst;
+        if (p->ethh != NULL) {
+            if ((PKT_IS_TOCLIENT(p))) {
+                src = p->ethh->eth_dst;
+                dst = p->ethh->eth_src;
+            } else {
+                src = p->ethh->eth_src;
+                dst = p->ethh->eth_dst;
+            }
+            JSONFormatAndAddMACAddr(js, "src_mac", src, false);
+            JSONFormatAndAddMACAddr(js, "dest_mac", dst, false);
+        }
+    }
+    jb_close(js);
+    return 0;
+}
+
 JsonBuilder *CreateEveHeader(const Packet *p, enum OutputJsonLogDirection dir,
         const char *event_type, JsonAddrInfo *addr)
 {
@@ -1115,6 +1197,15 @@ OutputInitResult OutputJsonInitCtx(ConfNode *conf)
             json_ctx->cfg.include_metadata = true;
         }
 
+        /* Check if ethernet information should be logged. */
+        const ConfNode *ethernet = ConfNodeLookupChild(conf, "ethernet");
+        if (ethernet && ethernet->val && ConfValIsTrue(ethernet->val)) {
+            SCLogConfig("Enabling Ethernet MAC address logging.");
+            json_ctx->cfg.include_ethernet = true;
+        } else {
+            json_ctx->cfg.include_ethernet = false;
+        }
+
         /* See if we want to enable the community id */
         const ConfNode *community_id = ConfNodeLookupChild(conf, "community-id");
         if (community_id && community_id->val && ConfValIsTrue(community_id->val)) {
@@ -1154,7 +1245,6 @@ OutputInitResult OutputJsonInitCtx(ConfNode *conf)
         json_ctx->file_ctx->type = json_ctx->json_out;
     }
 
-
     SCLogDebug("returning output_ctx %p", output_ctx);
 
     result.ctx = output_ctx;
index 57515d0fbc04f3b3366993c8595e4893255a1353..c34efdf719fd4cbbaae6879b99d8a3af9021cd8f 100644 (file)
@@ -90,6 +90,7 @@ TmEcode JsonLogThreadDeinit(ThreadVars *t, void *data);
 typedef struct OutputJsonCommonSettings_ {
     bool include_metadata;
     bool include_community_id;
+    bool include_ethernet;
     uint16_t community_id_seed;
 } OutputJsonCommonSettings;
 
index 4631d3189230e2a2e398b0599c3864a4d14cc873..67c504cc4356ca61e1d442b398d704498445dd8e 100644 (file)
@@ -92,6 +92,7 @@
 #include "util-pool.h"
 #include "util-byte.h"
 #include "util-proto-name.h"
+#include "util-macset.h"
 #include "util-memrchr.h"
 
 #include "util-mpm-ac.h"
@@ -198,6 +199,7 @@ static void RegisterUnittests(void)
     AppLayerUnittestsRegister();
     MimeDecRegisterTests();
     StreamingBufferRegisterTests();
+    MacSetRegisterTests();
 #ifdef OS_WIN32
     Win32SyscallRegisterTests();
 #endif
index 74cbccef12036fa0d08794de066fcb6275e1746c..3def54f0b008d4c43f959a94657fa3b2d71fde89 100644 (file)
@@ -2470,6 +2470,9 @@ int PostConfLoadedSetup(SCInstance *suri)
     LiveDevRegisterExtension();
 #endif
     RegisterFlowBypassInfo();
+
+    MacSetRegisterFlowStorage();
+
     AppLayerSetup();
 
     /* Suricata will use this umask if provided. By default it will use the
diff --git a/src/util-macset.c b/src/util-macset.c
new file mode 100644 (file)
index 0000000..30e6c6b
--- /dev/null
@@ -0,0 +1,449 @@
+/* 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];
+};
+
+int g_macset_storage_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 (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 != -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(SC_ERR_MEM_ALLOC, "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;
+}
+
+int 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(SC_ERR_MEM_ALLOC, "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 MacSetReset(MacSet *ms)
+{
+    if (ms == NULL)
+        return;
+    ms->state[MAC_SET_SRC] = ms->state[MAC_SET_DST] = EMPTY_SET;
+    ms->last[MAC_SET_SRC] = ms->last[MAC_SET_DST] = 0;
+}
+
+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);
+
+    MacSetReset(ms);
+    FAIL_IF_NOT(MacSetSize(ms) == 0);
+
+    MacSetAdd(ms, addr2, addr3);
+    FAIL_IF_NOT(MacSetSize(ms) == 2);
+
+    i = 1;
+    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;
+    int 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, 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, i = 0;
+    SC_ATOMIC_SET(flow_config.memcap, 10);
+
+    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, 0x1};
+        addr1[5] = i;
+        addr2[5] = i;
+        MacSetAdd(ms, addr1, addr2);
+    }
+    FAIL_IF_NOT(MacSetSize(ms) == 2);
+
+    ret = MacSetForEach(ms, CheckTest1Membership, &i);
+    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("MacSetTest04", MacSetTest05);
+#endif
+
+    return;
+}
diff --git a/src/util-macset.h b/src/util-macset.h
new file mode 100644 (file)
index 0000000..833f9bf
--- /dev/null
@@ -0,0 +1,48 @@
+/* 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>
+ */
+
+#ifndef __UTIL_MACSET_H__
+#define __UTIL_MACSET_H__
+
+typedef struct MacSet_ MacSet;
+typedef enum {
+    MAC_SET_SRC = 0,
+    MAC_SET_DST
+} MacSetSide;
+
+typedef int (*MacSetIteratorFunc)(uint8_t *addr, MacSetSide side, void*);
+
+MacSet *MacSetInit(int size);
+void    MacSetAdd(MacSet*, uint8_t *src_addr, uint8_t *dst_addr);
+void    MacSetAddWithCtr(MacSet*, uint8_t *src_addr, uint8_t *dst_addr, ThreadVars *tv,
+                         uint16_t ctr_src, uint16_t ctr_dst);
+int     MacSetForEach(const MacSet*, MacSetIteratorFunc, void*);
+int     MacSetSize(const MacSet*);
+void    MacSetReset(MacSet*);
+void    MacSetFree(MacSet*);
+void    MacSetRegisterFlowStorage(void);
+int     MacSetGetFlowStorageID(void);
+bool    MacSetFlowStorageEnabled(void);
+void    MacSetRegisterTests(void);
+
+#endif /* __UTIL_MACSET_H__ */
\ No newline at end of file
index c15931cd2d2e6af81da13158a5ea10af93c44503..f747c501406d609b3a4ff4c635c4c138daab73e5 100644 (file)
@@ -93,6 +93,7 @@ outputs:
       #facility: local5
       #level: Info ## possible levels: Emergency, Alert, Critical,
                    ## Error, Warning, Notice, Info, Debug
+      #ethernet: no  # log ethernet header in events when available
       #redis:
       #  server: 127.0.0.1
       #  port: 6379