From 00f03e93bab250f28b1474cf6dc30df1a98ac10f Mon Sep 17 00:00:00 2001 From: Eric Leblond Date: Sun, 7 Sep 2025 19:04:04 +0200 Subject: [PATCH 1/4] eve: move EveAddAppProto to main code Moving it to be able to use it globally later. Ticket: #7888 --- src/output-json-flow.c | 20 -------------------- src/output-json-flow.h | 1 - src/output-json.c | 22 ++++++++++++++++++++++ src/output-json.h | 1 + 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/output-json-flow.c b/src/output-json-flow.c index 2bd5d0129b1f..3459d2e79f2f 100644 --- a/src/output-json-flow.c +++ b/src/output-json-flow.c @@ -175,26 +175,6 @@ static SCJsonBuilder *CreateEveHeaderFromFlow(const Flow *f) return jb; } -void EveAddAppProto(Flow *f, SCJsonBuilder *js) -{ - if (f->alproto) { - SCJbSetString(js, "app_proto", AppProtoToString(f->alproto)); - } - if (f->alproto_ts && f->alproto_ts != f->alproto) { - SCJbSetString(js, "app_proto_ts", AppProtoToString(f->alproto_ts)); - } - if (f->alproto_tc && f->alproto_tc != f->alproto) { - SCJbSetString(js, "app_proto_tc", AppProtoToString(f->alproto_tc)); - } - if (f->alproto_orig != f->alproto && f->alproto_orig != ALPROTO_UNKNOWN) { - SCJbSetString(js, "app_proto_orig", AppProtoToString(f->alproto_orig)); - } - if (f->alproto_expect != f->alproto && f->alproto_expect != ALPROTO_UNKNOWN) { - SCJbSetString(js, "app_proto_expected", AppProtoToString(f->alproto_expect)); - } - -} - void EveAddFlow(Flow *f, SCJsonBuilder *js) { FlowBypassInfo *fc = FlowGetStorageById(f, GetFlowBypassInfoID()); diff --git a/src/output-json-flow.h b/src/output-json-flow.h index 4524370d11a7..362d0610e1df 100644 --- a/src/output-json-flow.h +++ b/src/output-json-flow.h @@ -26,6 +26,5 @@ void JsonFlowLogRegister(void); void EveAddFlow(Flow *f, SCJsonBuilder *js); -void EveAddAppProto(Flow *f, SCJsonBuilder *js); #endif /* SURICATA_OUTPUT_JSON_FLOW_H */ diff --git a/src/output-json.c b/src/output-json.c index 1b7464e35ee4..7c35e3ba7057 100644 --- a/src/output-json.c +++ b/src/output-json.c @@ -831,6 +831,28 @@ static int CreateJSONEther( return 0; } +void EveAddAppProto(const Flow *f, SCJsonBuilder *js) +{ + if (f == NULL) { + return; + } + if (f->alproto) { + SCJbSetString(js, "app_proto", AppProtoToString(f->alproto)); + } + if (f->alproto_ts && f->alproto_ts != f->alproto) { + SCJbSetString(js, "app_proto_ts", AppProtoToString(f->alproto_ts)); + } + if (f->alproto_tc && f->alproto_tc != f->alproto) { + SCJbSetString(js, "app_proto_tc", AppProtoToString(f->alproto_tc)); + } + if (f->alproto_orig != f->alproto && f->alproto_orig != ALPROTO_UNKNOWN) { + SCJbSetString(js, "app_proto_orig", AppProtoToString(f->alproto_orig)); + } + if (f->alproto_expect != f->alproto && f->alproto_expect != ALPROTO_UNKNOWN) { + SCJbSetString(js, "app_proto_expected", AppProtoToString(f->alproto_expect)); + } +} + SCJsonBuilder *CreateEveHeader(const Packet *p, enum SCOutputJsonLogDirection dir, const char *event_type, JsonAddrInfo *addr, OutputJsonCtx *eve_ctx) { diff --git a/src/output-json.h b/src/output-json.h index 1f4fec70d041..938d13c83ca4 100644 --- a/src/output-json.h +++ b/src/output-json.h @@ -110,6 +110,7 @@ void EveAddCommonOptions(const OutputJsonCommonSettings *cfg, const Packet *p, c SCJsonBuilder *js, enum SCOutputJsonLogDirection dir); int OutputJsonLogFlush(ThreadVars *tv, void *thread_data, const Packet *p); void EveAddMetadata(const Packet *p, const Flow *f, SCJsonBuilder *js); +void EveAddAppProto(const Flow *f, SCJsonBuilder *js); int OutputJSONMemBufferCallback(const char *str, size_t size, void *data); From 8b4b42ccf6ed64854cbc549840ce0877337d49a1 Mon Sep 17 00:00:00 2001 From: Eric Leblond Date: Tue, 3 Feb 2026 17:27:51 +0100 Subject: [PATCH 2/4] eve: add version field This will be used to code the version of the log to use so backward compatibility can be achieved: upgrading Suricata without changing the configuration should not trigger major changes in the log format. --- src/output-json.c | 16 ++++++++++++++++ src/output-json.h | 3 +++ suricata.yaml.in | 1 + 3 files changed, 20 insertions(+) diff --git a/src/output-json.c b/src/output-json.c index 7c35e3ba7057..b57dbdde13ce 100644 --- a/src/output-json.c +++ b/src/output-json.c @@ -1265,6 +1265,22 @@ OutputInitResult OutputJsonInitCtx(SCConfNode *conf) } else { json_ctx->cfg.include_suricata_version = false; } + const char *eve_version = SCConfNodeLookupChildValue(conf, "version"); + if (eve_version != NULL) { + if (StringParseUint16(&json_ctx->cfg.eve_version, 10, 0, eve_version) < 0) { + FatalError("Failed to initialize JSON output, " + "invalid EVE version: %s", + eve_version); + } + if (json_ctx->cfg.eve_version > EVE_MAX_VERSION) { + SCLogWarning("Configured EVE version %u is higher than " + "maximum supported version %u, using max version.", + json_ctx->cfg.eve_version, EVE_MAX_VERSION); + json_ctx->cfg.eve_version = EVE_MAX_VERSION; + } + } else { + json_ctx->cfg.eve_version = 1; + } /* See if we want to enable the community id */ const SCConfNode *community_id = SCConfNodeLookupChild(conf, "community-id"); diff --git a/src/output-json.h b/src/output-json.h index 938d13c83ca4..de8cb4c2b84f 100644 --- a/src/output-json.h +++ b/src/output-json.h @@ -37,6 +37,8 @@ void OutputJsonRegister(void); #define JSON_ADDR_LEN 46 #define JSON_PROTO_LEN 16 +#define EVE_MAX_VERSION 2 + /* A struct to contain address info for rendering to JSON. */ typedef struct JsonAddrInfo_ { char src_ip[JSON_ADDR_LEN]; @@ -67,6 +69,7 @@ typedef struct OutputJsonCommonSettings_ { bool include_ethernet; bool include_suricata_version; uint16_t community_id_seed; + uint16_t eve_version; } OutputJsonCommonSettings; /* diff --git a/suricata.yaml.in b/suricata.yaml.in index 3b29513f9dfb..ceff5a9d6080 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -143,6 +143,7 @@ outputs: #metadata: no # Include suricata version. Default no. #suricata-version: yes + version: 2 # include the name of the input pcap file in pcap file processing mode pcap-file: false From a730a241ae4142cd5b7156854dc6190b421b70ac Mon Sep 17 00:00:00 2001 From: Eric Leblond Date: Sun, 7 Sep 2025 19:07:35 +0200 Subject: [PATCH 3/4] eve: log app_proto in all event types The app_proto is logged in a small subset (fileinfo, flow, frame, netflow) in Suricata when it is an interesting information for all events type as it includes detection information and protocol transition. This patch updates the code to log app_proto in all events if there is a Flow available. It is making use of EveAddAppProto function to get interesting information such as original application protocol or difference between server and client side. Backward compatibility is preserved as app_proto information will only be logged when the eve-log.version is greater or equal to 2. Ticket: 7888 --- src/output-json-alert.c | 4 +++- src/output-json-file.c | 6 +++++- src/output-json-frame.c | 8 ++++++-- src/output-json-netflow.c | 22 ++++++++++++++++------ src/output-json.c | 4 ++++ src/output-json.h | 3 ++- 6 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/output-json-alert.c b/src/output-json-alert.c index e5ee355d7cc8..ffc7bca03299 100644 --- a/src/output-json-alert.c +++ b/src/output-json-alert.c @@ -717,7 +717,9 @@ static int AlertJson(ThreadVars *tv, JsonAlertLogThread *aft, const Packet *p) } } - EveAddAppProto(p->flow, jb); + if (json_output_ctx->eve_ctx->cfg.eve_version < EVE_VERSION_GLOBAL_APP_PROTO) { + EveAddAppProto(p->flow, jb); + } if (p->flowflags & FLOW_PKT_TOSERVER) { SCJbSetString(jb, "direction", "to_server"); diff --git a/src/output-json-file.c b/src/output-json-file.c index 9c046b3c7d18..7eb6e742bf55 100644 --- a/src/output-json-file.c +++ b/src/output-json-file.c @@ -189,7 +189,11 @@ SCJsonBuilder *JsonBuildFileInfoRecord(const Packet *p, const File *ff, void *tx break; } - SCJbSetString(js, "app_proto", AppProtoToString(p->flow->alproto)); + /* if eve_ctx is NULL, we are called in filestore context, so for compatibility + * we need to log app_proto */ + if (eve_ctx == NULL || eve_ctx->cfg.eve_version < EVE_VERSION_GLOBAL_APP_PROTO) { + SCJbSetString(js, "app_proto", AppProtoToString(p->flow->alproto)); + } SCJbOpenObject(js, "fileinfo"); if (stored) { diff --git a/src/output-json-frame.c b/src/output-json-frame.c index a27885cc4ab4..6d566fd9ebb3 100644 --- a/src/output-json-frame.c +++ b/src/output-json-frame.c @@ -299,7 +299,9 @@ static int FrameJsonUdp(ThreadVars *tv, JsonFrameLogThread *aft, const Packet *p if (unlikely(jb == NULL)) return TM_ECODE_OK; - SCJbSetString(jb, "app_proto", AppProtoToString(f->alproto)); + if (json_output_ctx->eve_ctx->cfg.eve_version < EVE_VERSION_GLOBAL_APP_PROTO) { + SCJbSetString(jb, "app_proto", AppProtoToString(f->alproto)); + } FrameJsonLogOneFrame(IPPROTO_UDP, frame, p->flow, NULL, p, jb, aft->payload_buffer); OutputJsonBuilderBuffer(tv, p, p->flow, jb, aft->ctx); SCJbFree(jb); @@ -373,7 +375,9 @@ static int FrameJson(ThreadVars *tv, JsonFrameLogThread *aft, const Packet *p) if (unlikely(jb == NULL)) return TM_ECODE_OK; - SCJbSetString(jb, "app_proto", AppProtoToString(p->flow->alproto)); + if (json_output_ctx->eve_ctx->cfg.eve_version < EVE_VERSION_GLOBAL_APP_PROTO) { + SCJbSetString(jb, "app_proto", AppProtoToString(p->flow->alproto)); + } FrameJsonLogOneFrame(IPPROTO_TCP, frame, p->flow, stream, p, jb, aft->payload_buffer); OutputJsonBuilderBuffer(tv, p, p->flow, jb, aft->ctx); SCJbFree(jb); diff --git a/src/output-json-netflow.c b/src/output-json-netflow.c index 6a0d1d2e60a0..2094f9b80d3e 100644 --- a/src/output-json-netflow.c +++ b/src/output-json-netflow.c @@ -176,9 +176,14 @@ static SCJsonBuilder *CreateEveHeaderFromNetFlow(const Flow *f, int dir) } /* JSON format logging */ -static void NetFlowLogEveToServer(SCJsonBuilder *js, Flow *f) +static void NetFlowLogEveToServer(SCJsonBuilder *js, Flow *f, OutputJsonThreadCtx *json_outout_ctx) { - SCJbSetString(js, "app_proto", AppProtoToString(f->alproto_ts ? f->alproto_ts : f->alproto)); + if (json_outout_ctx->ctx->cfg.eve_version < EVE_VERSION_GLOBAL_APP_PROTO) { + SCJbSetString( + js, "app_proto", AppProtoToString(f->alproto_ts ? f->alproto_ts : f->alproto)); + } else { + EveAddAppProto(f, js); + } SCJbOpenObject(js, "netflow"); @@ -226,9 +231,14 @@ static void NetFlowLogEveToServer(SCJsonBuilder *js, Flow *f) } } -static void NetFlowLogEveToClient(SCJsonBuilder *js, Flow *f) +static void NetFlowLogEveToClient(SCJsonBuilder *js, Flow *f, OutputJsonThreadCtx *json_output_ctx) { - SCJbSetString(js, "app_proto", AppProtoToString(f->alproto_tc ? f->alproto_tc : f->alproto)); + if (json_output_ctx->ctx->cfg.eve_version < EVE_VERSION_GLOBAL_APP_PROTO) { + SCJbSetString( + js, "app_proto", AppProtoToString(f->alproto_tc ? f->alproto_tc : f->alproto)); + } else { + EveAddAppProto(f, js); + } SCJbOpenObject(js, "netflow"); @@ -287,7 +297,7 @@ static int JsonNetFlowLogger(ThreadVars *tv, void *thread_data, Flow *f) SCJsonBuilder *jb = CreateEveHeaderFromNetFlow(f, 0); if (unlikely(jb == NULL)) return TM_ECODE_OK; - NetFlowLogEveToServer(jb, f); + NetFlowLogEveToServer(jb, f, jhl); EveAddCommonOptions(&jhl->ctx->cfg, NULL, f, jb, LOG_DIR_FLOW_TOSERVER); OutputJsonBuilderBuffer(tv, NULL, f, jb, jhl); SCJbFree(jb); @@ -297,7 +307,7 @@ static int JsonNetFlowLogger(ThreadVars *tv, void *thread_data, Flow *f) jb = CreateEveHeaderFromNetFlow(f, 1); if (unlikely(jb == NULL)) return TM_ECODE_OK; - NetFlowLogEveToClient(jb, f); + NetFlowLogEveToClient(jb, f, jhl); EveAddCommonOptions(&jhl->ctx->cfg, NULL, f, jb, LOG_DIR_FLOW_TOCLIENT); OutputJsonBuilderBuffer(tv, NULL, f, jb, jhl); SCJbFree(jb); diff --git a/src/output-json.c b/src/output-json.c index b57dbdde13ce..c9babc09ce7f 100644 --- a/src/output-json.c +++ b/src/output-json.c @@ -870,6 +870,10 @@ SCJsonBuilder *CreateEveHeader(const Packet *p, enum SCOutputJsonLogDirection di CreateEveFlowId(js, f); + if (eve_ctx != NULL && eve_ctx->cfg.eve_version >= EVE_VERSION_GLOBAL_APP_PROTO) { + EveAddAppProto(f, js); + } + /* sensor id */ if (sensor_id >= 0) { SCJbSetUint(js, "sensor_id", sensor_id); diff --git a/src/output-json.h b/src/output-json.h index de8cb4c2b84f..c769df277c98 100644 --- a/src/output-json.h +++ b/src/output-json.h @@ -37,7 +37,8 @@ void OutputJsonRegister(void); #define JSON_ADDR_LEN 46 #define JSON_PROTO_LEN 16 -#define EVE_MAX_VERSION 2 +#define EVE_MAX_VERSION 2 +#define EVE_VERSION_GLOBAL_APP_PROTO 2 /* A struct to contain address info for rendering to JSON. */ typedef struct JsonAddrInfo_ { From 48ed06db0651368f6fdf39b46cd299de077135e6 Mon Sep 17 00:00:00 2001 From: Eric Leblond Date: Fri, 6 Feb 2026 08:01:37 +0100 Subject: [PATCH 4/4] eve: log version starting with version 2 Log the version of EVE used in the event. --- etc/schema.json | 4 ++++ src/output-json.c | 3 +++ src/output-json.h | 1 + 3 files changed, 8 insertions(+) diff --git a/etc/schema.json b/etc/schema.json index 9df20702acad..bf923e02194d 100644 --- a/etc/schema.json +++ b/etc/schema.json @@ -8813,6 +8813,10 @@ "tx_id": { "type": "integer" }, + "v": { + "type": "integer", + "description": "Version of EVE in the event" + }, "verdict": { "$ref": "#/$defs/verdict_type" }, diff --git a/src/output-json.c b/src/output-json.c index c9babc09ce7f..2f9d01bd77c5 100644 --- a/src/output-json.c +++ b/src/output-json.c @@ -410,6 +410,9 @@ void EveAddCommonOptions(const OutputJsonCommonSettings *cfg, const Packet *p, c if (cfg->include_community_id && f != NULL) { CreateEveCommunityFlowId(js, f, cfg->community_id_seed); } + if (cfg->eve_version >= EVE_MIN_LOG_VERSION) { + SCJbSetUint(js, "v", cfg->eve_version); + } if (f != NULL && f->tenant_id > 0) { SCJbSetUint(js, "tenant_id", f->tenant_id); } diff --git a/src/output-json.h b/src/output-json.h index c769df277c98..41957028808e 100644 --- a/src/output-json.h +++ b/src/output-json.h @@ -38,6 +38,7 @@ void OutputJsonRegister(void); #define JSON_PROTO_LEN 16 #define EVE_MAX_VERSION 2 +#define EVE_MIN_LOG_VERSION 2 #define EVE_VERSION_GLOBAL_APP_PROTO 2 /* A struct to contain address info for rendering to JSON. */