summaryrefslogtreecommitdiffstats
path: root/src/output-eve-stream.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/output-eve-stream.c')
-rw-r--r--src/output-eve-stream.c442
1 files changed, 442 insertions, 0 deletions
diff --git a/src/output-eve-stream.c b/src/output-eve-stream.c
new file mode 100644
index 0000000..446fb3e
--- /dev/null
+++ b/src/output-eve-stream.c
@@ -0,0 +1,442 @@
+/* Copyright (C) 2023 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
+ */
+
+#include "suricata-common.h"
+#include "packet.h"
+#include "detect.h"
+#include "flow.h"
+#include "conf.h"
+
+#include "threads.h"
+#include "tm-threads.h"
+#include "threadvars.h"
+#include "util-debug.h"
+
+#include "decode-ipv4.h"
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-mpm.h"
+#include "detect-reference.h"
+
+#include "output.h"
+#include "output-json.h"
+#include "output-json-flow.h"
+#include "output-eve-stream.h"
+
+#include "stream-tcp.h"
+
+#include "util-unittest.h"
+#include "util-unittest-helper.h"
+#include "util-classification-config.h"
+#include "util-privs.h"
+#include "util-print.h"
+#include "util-proto-name.h"
+#include "util-logopenfile.h"
+#include "util-time.h"
+#include "util-buffer.h"
+
+#include "action-globals.h"
+
+#define MODULE_NAME "EveStreamLog"
+
+#define LOG_DROP_ALERTS 1
+
+typedef struct EveStreamOutputCtx_ {
+ uint16_t trigger_flags; /**< presence of flags in packet trigger logging. 0xffff for all. */
+ OutputJsonCtx *eve_ctx;
+} EveStreamOutputCtx;
+
+typedef struct EveStreamLogThread_ {
+ EveStreamOutputCtx *stream_ctx;
+ OutputJsonThreadCtx *ctx;
+} EveStreamLogThread;
+
+static TmEcode EveStreamLogThreadInit(ThreadVars *t, const void *initdata, void **data)
+{
+ EveStreamLogThread *aft = SCCalloc(1, sizeof(EveStreamLogThread));
+ if (unlikely(aft == NULL))
+ return TM_ECODE_FAILED;
+
+ if (initdata == NULL) {
+ SCLogDebug("Error getting context for EveLogDrop. \"initdata\" argument NULL");
+ goto error_exit;
+ }
+
+ /** Use the Output Context (file pointer and mutex) */
+ aft->stream_ctx = ((OutputCtx *)initdata)->data;
+ aft->ctx = CreateEveThreadCtx(t, aft->stream_ctx->eve_ctx);
+ if (!aft->ctx) {
+ goto error_exit;
+ }
+
+ *data = (void *)aft;
+ return TM_ECODE_OK;
+
+error_exit:
+ SCFree(aft);
+ return TM_ECODE_FAILED;
+}
+
+static TmEcode EveStreamLogThreadDeinit(ThreadVars *t, void *data)
+{
+ EveStreamLogThread *aft = (EveStreamLogThread *)data;
+ if (aft == NULL) {
+ return TM_ECODE_OK;
+ }
+
+ FreeEveThreadCtx(aft->ctx);
+
+ /* clear memory */
+ memset(aft, 0, sizeof(*aft));
+
+ SCFree(aft);
+ return TM_ECODE_OK;
+}
+
+static void EveStreamOutputCtxFree(EveStreamOutputCtx *ctx)
+{
+ if (ctx != NULL) {
+ SCFree(ctx);
+ }
+}
+
+static void EveStreamLogDeInitCtxSub(OutputCtx *output_ctx)
+{
+ OutputDropLoggerDisable();
+
+ EveStreamOutputCtx *ctx = output_ctx->data;
+ SCFree(ctx);
+ SCLogDebug("cleaning up sub output_ctx %p", output_ctx);
+ SCFree(output_ctx);
+}
+
+static uint16_t SetFlag(ConfNode *conf, const char *opt, const uint16_t inflag)
+{
+ const char *v = ConfNodeLookupChildValue(conf, opt);
+ if (v != NULL && ConfValIsTrue(v)) {
+ return inflag;
+ }
+ return 0;
+}
+
+static OutputInitResult EveStreamLogInitCtxSub(ConfNode *conf, OutputCtx *parent_ctx)
+{
+ OutputInitResult result = { NULL, false };
+ OutputJsonCtx *ajt = parent_ctx->data;
+
+ EveStreamOutputCtx *ctx = SCCalloc(1, sizeof(*ctx));
+ if (ctx == NULL)
+ return result;
+
+ OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx));
+ if (unlikely(output_ctx == NULL)) {
+ EveStreamOutputCtxFree(ctx);
+ return result;
+ }
+
+ if (conf) {
+ // TODO add all flags
+
+ ctx->trigger_flags |= SetFlag(conf, "event-set", STREAM_PKT_FLAG_EVENTSET);
+ ctx->trigger_flags |= SetFlag(conf, "state-update", STREAM_PKT_FLAG_STATE_UPDATE);
+ ctx->trigger_flags |=
+ SetFlag(conf, "spurious-retransmission", STREAM_PKT_FLAG_SPURIOUS_RETRANSMISSION);
+
+ ctx->trigger_flags |= SetFlag(conf, "all", 0xFFFF);
+ SCLogDebug("trigger_flags %04x", ctx->trigger_flags);
+ }
+ ctx->eve_ctx = ajt;
+
+ output_ctx->data = ctx;
+ output_ctx->DeInit = EveStreamLogDeInitCtxSub;
+
+ result.ctx = output_ctx;
+ result.ok = true;
+
+ SCLogWarning("eve.stream facility is EXPERIMENTAL and can change w/o notice");
+ return result;
+}
+
+void EveAddFlowTcpStreamFlags(const TcpStream *stream, const char *name, JsonBuilder *jb)
+{
+ jb_open_array(jb, name);
+ if (stream->flags & STREAMTCP_STREAM_FLAG_HAS_GAP)
+ jb_append_string(jb, "has_gap");
+ if (stream->flags & STREAMTCP_STREAM_FLAG_NOREASSEMBLY)
+ jb_append_string(jb, "noreassembly");
+ if (stream->flags & STREAMTCP_STREAM_FLAG_KEEPALIVE)
+ jb_append_string(jb, "keepalive");
+ if (stream->flags & STREAMTCP_STREAM_FLAG_DEPTH_REACHED)
+ jb_append_string(jb, "depth_reached");
+ if (stream->flags & STREAMTCP_STREAM_FLAG_TRIGGER_RAW)
+ jb_append_string(jb, "trigger_raw");
+ if (stream->flags & STREAMTCP_STREAM_FLAG_TIMESTAMP)
+ jb_append_string(jb, "timestamp");
+ if (stream->flags & STREAMTCP_STREAM_FLAG_ZERO_TIMESTAMP)
+ jb_append_string(jb, "zero_timestamp");
+ if (stream->flags & STREAMTCP_STREAM_FLAG_APPPROTO_DETECTION_COMPLETED)
+ jb_append_string(jb, "appproto_detection_completed");
+ if (stream->flags & STREAMTCP_STREAM_FLAG_APPPROTO_DETECTION_SKIPPED)
+ jb_append_string(jb, "appproto_detection_skipped");
+ if (stream->flags & STREAMTCP_STREAM_FLAG_NEW_RAW_DISABLED)
+ jb_append_string(jb, "new_raw_disabled");
+ if (stream->flags & STREAMTCP_STREAM_FLAG_DISABLE_RAW)
+ jb_append_string(jb, "disable_raw");
+ if (stream->flags & STREAMTCP_STREAM_FLAG_RST_RECV)
+ jb_append_string(jb, "rst_recv");
+ jb_close(jb);
+}
+
+void EveAddFlowTcpFlags(const TcpSession *ssn, const char *name, JsonBuilder *jb)
+{
+ jb_open_object(jb, "flags");
+
+ jb_open_array(jb, name);
+ if (ssn->flags & STREAMTCP_FLAG_MIDSTREAM) {
+ jb_append_string(jb, "midstream");
+ }
+ if (ssn->flags & STREAMTCP_FLAG_MIDSTREAM_ESTABLISHED) {
+ jb_append_string(jb, "midstream_established");
+ }
+ if (ssn->flags & STREAMTCP_FLAG_MIDSTREAM_SYNACK) {
+ jb_append_string(jb, "midstream_synack");
+ }
+ if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) {
+ jb_append_string(jb, "timestamp");
+ }
+ if (ssn->flags & STREAMTCP_FLAG_SERVER_WSCALE) {
+ jb_append_string(jb, "server_wscale");
+ }
+ if (ssn->flags & STREAMTCP_FLAG_CLOSED_BY_RST) {
+ jb_append_string(jb, "closed_by_rst");
+ }
+ if (ssn->flags & STREAMTCP_FLAG_4WHS) {
+ jb_append_string(jb, "4whs");
+ }
+ if (ssn->flags & STREAMTCP_FLAG_DETECTION_EVASION_ATTEMPT) {
+ jb_append_string(jb, "detect_evasion_attempt");
+ }
+ if (ssn->flags & STREAMTCP_FLAG_CLIENT_SACKOK) {
+ jb_append_string(jb, "client_sackok");
+ }
+ if (ssn->flags & STREAMTCP_FLAG_CLIENT_SACKOK) {
+ jb_append_string(jb, "sackok");
+ }
+ if (ssn->flags & STREAMTCP_FLAG_3WHS_CONFIRMED) {
+ jb_append_string(jb, "3whs_confirmed");
+ }
+ if (ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED) {
+ jb_append_string(jb, "app_layer_disabled");
+ }
+ if (ssn->flags & STREAMTCP_FLAG_BYPASS) {
+ jb_append_string(jb, "bypass");
+ }
+ if (ssn->flags & STREAMTCP_FLAG_TCP_FAST_OPEN) {
+ jb_append_string(jb, "tcp_fast_open");
+ }
+ if (ssn->flags & STREAMTCP_FLAG_TFO_DATA_IGNORED) {
+ jb_append_string(jb, "tfo_data_ignored");
+ }
+ jb_close(jb);
+ jb_close(jb);
+}
+
+static void LogStream(const TcpStream *stream, JsonBuilder *js)
+{
+ jb_set_uint(js, "isn", stream->isn);
+ jb_set_uint(js, "next_seq", stream->next_seq);
+ jb_set_uint(js, "last_ack", stream->last_ack);
+ jb_set_uint(js, "next_win", stream->next_win);
+ if (!(stream->flags & STREAMTCP_STREAM_FLAG_NOREASSEMBLY)) {
+ jb_set_uint(js, "base_seq", stream->base_seq);
+ jb_set_uint(js, "segs_right_edge", stream->segs_right_edge);
+ }
+ jb_set_uint(js, "window", stream->window);
+ jb_set_uint(js, "wscale", stream->wscale);
+
+ EveAddFlowTcpStreamFlags(stream, "flags", js);
+}
+
+/**
+ * \brief Log the stream packets
+ *
+ * \param tv Pointer the current thread variables
+ * \param data Pointer to the EveStreamLogThread struct
+ * \param p Pointer the packet which is being logged
+ *
+ * \retval 0 on succes
+ */
+static int EveStreamLogger(ThreadVars *tv, void *thread_data, const Packet *p)
+{
+ EveStreamLogThread *td = thread_data;
+ EveStreamOutputCtx *ctx = td->stream_ctx;
+
+ JsonAddrInfo addr = json_addr_info_zero;
+ JsonAddrInfoInit(p, LOG_DIR_PACKET, &addr);
+
+ JsonBuilder *js = CreateEveHeader(p, LOG_DIR_PACKET, "stream_tcp", &addr, ctx->eve_ctx);
+ if (unlikely(js == NULL))
+ return TM_ECODE_OK;
+
+ if (p->flow != NULL) {
+ if (p->flowflags & FLOW_PKT_TOSERVER) {
+ jb_set_string(js, "direction", "to_server");
+ } else {
+ jb_set_string(js, "direction", "to_client");
+ }
+ }
+
+ jb_open_object(js, "stream_tcp");
+ jb_open_object(js, "packet");
+
+ if (PKT_IS_IPV4(p)) {
+ jb_set_uint(js, "len", IPV4_GET_IPLEN(p));
+ jb_set_uint(js, "tos", IPV4_GET_IPTOS(p));
+ jb_set_uint(js, "ttl", IPV4_GET_IPTTL(p));
+ jb_set_uint(js, "ipid", IPV4_GET_IPID(p));
+ } else if (PKT_IS_IPV6(p)) {
+ jb_set_uint(js, "len", IPV6_GET_PLEN(p));
+ jb_set_uint(js, "tc", IPV6_GET_CLASS(p));
+ jb_set_uint(js, "hoplimit", IPV6_GET_HLIM(p));
+ jb_set_uint(js, "flowlbl", IPV6_GET_FLOW(p));
+ }
+ if (PKT_IS_TCP(p)) {
+ jb_set_uint(js, "tcpseq", TCP_GET_SEQ(p));
+ jb_set_uint(js, "tcpack", TCP_GET_ACK(p));
+ jb_set_uint(js, "tcpwin", TCP_GET_WINDOW(p));
+ jb_set_bool(js, "syn", TCP_ISSET_FLAG_SYN(p) ? true : false);
+ jb_set_bool(js, "ack", TCP_ISSET_FLAG_ACK(p) ? true : false);
+ jb_set_bool(js, "psh", TCP_ISSET_FLAG_PUSH(p) ? true : false);
+ jb_set_bool(js, "rst", TCP_ISSET_FLAG_RST(p) ? true : false);
+ jb_set_bool(js, "urg", TCP_ISSET_FLAG_URG(p) ? true : false);
+ jb_set_bool(js, "fin", TCP_ISSET_FLAG_FIN(p) ? true : false);
+ jb_set_uint(js, "tcpres", TCP_GET_RAW_X2(p->tcph));
+ jb_set_uint(js, "tcpurgp", TCP_GET_URG_POINTER(p));
+
+ jb_open_array(js, "flags");
+ if (p->tcpvars.stream_pkt_flags & STREAM_PKT_FLAG_RETRANSMISSION)
+ jb_append_string(js, "retransmission");
+ if (p->tcpvars.stream_pkt_flags & STREAM_PKT_FLAG_SPURIOUS_RETRANSMISSION)
+ jb_append_string(js, "spurious_retransmission");
+ if (p->tcpvars.stream_pkt_flags & STREAM_PKT_FLAG_KEEPALIVE)
+ jb_append_string(js, "keepalive");
+ if (p->tcpvars.stream_pkt_flags & STREAM_PKT_FLAG_KEEPALIVEACK)
+ jb_append_string(js, "keepalive_ack");
+ if (p->tcpvars.stream_pkt_flags & STREAM_PKT_FLAG_WINDOWUPDATE)
+ jb_append_string(js, "window_update");
+
+ if (p->tcpvars.stream_pkt_flags & STREAM_PKT_FLAG_EVENTSET)
+ jb_append_string(js, "event_set");
+ if (p->tcpvars.stream_pkt_flags & STREAM_PKT_FLAG_STATE_UPDATE)
+ jb_append_string(js, "state_update");
+ if (p->tcpvars.stream_pkt_flags & STREAM_PKT_FLAG_DUP_ACK)
+ jb_append_string(js, "dup_ack");
+ if (p->tcpvars.stream_pkt_flags & STREAM_PKT_FLAG_DSACK)
+ jb_append_string(js, "dsack");
+ if (p->tcpvars.stream_pkt_flags & STREAM_PKT_FLAG_ACK_UNSEEN_DATA)
+ jb_append_string(js, "ack_unseen_data");
+ if (p->tcpvars.stream_pkt_flags & STREAM_PKT_FLAG_TCP_PORT_REUSE)
+ jb_append_string(js, "tcp_port_reuse");
+ if (p->tcpvars.stream_pkt_flags & STREAM_PKT_FLAG_TCP_ZERO_WIN_PROBE)
+ jb_append_string(js, "zero_window_probe");
+ if (p->tcpvars.stream_pkt_flags & STREAM_PKT_FLAG_TCP_ZERO_WIN_PROBE_ACK)
+ jb_append_string(js, "zero_window_probe_ack");
+ jb_close(js);
+ }
+ jb_close(js);
+
+ jb_open_object(js, "session");
+ if (p->flow != NULL && p->flow->protoctx != NULL) {
+ const TcpSession *ssn = p->flow->protoctx;
+ const char *tcp_state = StreamTcpStateAsString(ssn->state);
+ if (tcp_state != NULL)
+ jb_set_string(js, "state", tcp_state);
+ if (p->tcpvars.stream_pkt_flags & STREAM_PKT_FLAG_STATE_UPDATE) {
+ const char *tcp_pstate = StreamTcpStateAsString(ssn->pstate);
+ if (tcp_pstate != NULL)
+ jb_set_string(js, "pstate", tcp_pstate);
+ }
+ EveAddFlowTcpFlags(ssn, "flags", js);
+
+ jb_open_object(js, "client");
+ LogStream(&ssn->client, js);
+ jb_close(js);
+ jb_open_object(js, "server");
+ LogStream(&ssn->server, js);
+ jb_close(js);
+ }
+ jb_close(js);
+
+ if (p->tcpvars.stream_pkt_flags & STREAM_PKT_FLAG_EVENTSET) {
+ jb_open_array(js, "events");
+ for (int i = 0; i < p->events.cnt; i++) {
+ uint8_t event_code = p->events.events[i];
+ bool is_decode = EVENT_IS_DECODER_PACKET_ERROR(event_code);
+ if (is_decode)
+ continue;
+ if (event_code >= DECODE_EVENT_MAX)
+ continue;
+ const char *event = DEvents[event_code].event_name;
+ if (event == NULL)
+ continue;
+ jb_append_string(js, event);
+ }
+ jb_close(js);
+ }
+
+ if (p->drop_reason != 0) {
+ const char *str = PacketDropReasonToString(p->drop_reason);
+ jb_set_string(js, "reason", str);
+ }
+
+ /* Close stream. */
+ jb_close(js);
+
+ OutputJsonBuilderBuffer(js, td->ctx);
+ jb_free(js);
+
+ return TM_ECODE_OK;
+}
+
+/**
+ * \brief Check if we need to log this packet
+ *
+ * \param tv Pointer the current thread variables
+ * \param p Pointer the packet which is tested
+ *
+ * \retval bool TRUE or FALSE
+ */
+static int EveStreamLogCondition(ThreadVars *tv, void *data, const Packet *p)
+{
+ EveStreamLogThread *td = data;
+ EveStreamOutputCtx *ctx = td->stream_ctx;
+
+ return (p->proto == IPPROTO_TCP &&
+ (ctx->trigger_flags == 0xffff ||
+ (p->tcpvars.stream_pkt_flags & ctx->trigger_flags) != 0));
+}
+
+void EveStreamLogRegister(void)
+{
+ OutputRegisterPacketSubModule(LOGGER_JSON_STREAM, "eve-log", MODULE_NAME, "eve-log.stream",
+ EveStreamLogInitCtxSub, EveStreamLogger, EveStreamLogCondition, EveStreamLogThreadInit,
+ EveStreamLogThreadDeinit, NULL);
+}