/src/suricata8/src/output-json.c
Line | Count | Source |
1 | | /* Copyright (C) 2007-2023 Open Information Security Foundation |
2 | | * |
3 | | * You can copy, redistribute or modify this Program under the terms of |
4 | | * the GNU General Public License version 2 as published by the Free |
5 | | * Software Foundation. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * version 2 along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
15 | | * 02110-1301, USA. |
16 | | */ |
17 | | |
18 | | /** |
19 | | * \file |
20 | | * |
21 | | * \author Tom DeCanio <td@npulsetech.com> |
22 | | * |
23 | | * Logs detection and monitoring events in JSON format. |
24 | | * |
25 | | */ |
26 | | |
27 | | #include "suricata-common.h" |
28 | | #include "flow.h" |
29 | | #include "conf.h" |
30 | | |
31 | | #include "util-debug.h" |
32 | | #include "util-time.h" |
33 | | #include "util-var-name.h" |
34 | | #include "util-macset.h" |
35 | | |
36 | | #include "util-unittest.h" |
37 | | #include "util-unittest-helper.h" |
38 | | |
39 | | #include "detect-engine.h" |
40 | | #include "util-classification-config.h" |
41 | | #include "util-syslog.h" |
42 | | |
43 | | /* Internal output plugins */ |
44 | | #include "output-eve-syslog.h" |
45 | | #include "output-eve-null.h" |
46 | | |
47 | | #include "output.h" |
48 | | #include "output-json.h" |
49 | | |
50 | | #include "util-byte.h" |
51 | | #include "util-print.h" |
52 | | #include "util-proto-name.h" |
53 | | #include "util-optimize.h" |
54 | | #include "util-buffer.h" |
55 | | #include "util-logopenfile.h" |
56 | | #include "util-log-redis.h" |
57 | | #include "util-device-private.h" |
58 | | #include "util-validate.h" |
59 | | |
60 | | #include "flow-var.h" |
61 | | #include "flow-bit.h" |
62 | | #include "flow-storage.h" |
63 | | |
64 | | #include "source-pcap-file-helper.h" |
65 | | |
66 | 4 | #define DEFAULT_LOG_FILENAME "eve.json" |
67 | 78 | #define MODULE_NAME "OutputJSON" |
68 | | |
69 | | #define MAX_JSON_SIZE 2048 |
70 | | |
71 | | static void OutputJsonDeInitCtx(OutputCtx *); |
72 | | static void CreateEveCommunityFlowId(SCJsonBuilder *js, const Flow *f, const uint16_t seed); |
73 | | static int CreateJSONEther( |
74 | | SCJsonBuilder *parent, const Packet *p, const Flow *f, enum SCOutputJsonLogDirection dir); |
75 | | |
76 | | static const char *TRAFFIC_ID_PREFIX = "traffic/id/"; |
77 | | static const char *TRAFFIC_LABEL_PREFIX = "traffic/label/"; |
78 | | static size_t traffic_id_prefix_len = 0; |
79 | | static size_t traffic_label_prefix_len = 0; |
80 | | |
81 | | const JsonAddrInfo json_addr_info_zero; |
82 | | |
83 | | void OutputJsonRegister (void) |
84 | 78 | { |
85 | 78 | OutputRegisterModule(MODULE_NAME, "eve-log", OutputJsonInitCtx); |
86 | | |
87 | 78 | traffic_id_prefix_len = strlen(TRAFFIC_ID_PREFIX); |
88 | 78 | traffic_label_prefix_len = strlen(TRAFFIC_LABEL_PREFIX); |
89 | | |
90 | | // Register output file types that use the new eve filetype registration |
91 | | // API. |
92 | 78 | SyslogInitialize(); |
93 | 78 | NullLogInitialize(); |
94 | 78 | } |
95 | | |
96 | | json_t *SCJsonString(const char *val) |
97 | 0 | { |
98 | 0 | if (val == NULL){ |
99 | 0 | return NULL; |
100 | 0 | } |
101 | 0 | json_t * retval = json_string(val); |
102 | 0 | char retbuf[MAX_JSON_SIZE] = {0}; |
103 | 0 | if (retval == NULL) { |
104 | 0 | uint32_t u = 0; |
105 | 0 | uint32_t offset = 0; |
106 | 0 | for (u = 0; u < strlen(val); u++) { |
107 | 0 | if (isprint(val[u])) { |
108 | 0 | PrintBufferData(retbuf, &offset, MAX_JSON_SIZE-1, "%c", |
109 | 0 | val[u]); |
110 | 0 | } else { |
111 | 0 | PrintBufferData(retbuf, &offset, MAX_JSON_SIZE-1, |
112 | 0 | "\\x%02X", val[u]); |
113 | 0 | } |
114 | 0 | } |
115 | 0 | retbuf[offset] = '\0'; |
116 | 0 | retval = json_string(retbuf); |
117 | 0 | } |
118 | 0 | return retval; |
119 | 0 | } |
120 | | |
121 | | /* Default Sensor ID value */ |
122 | | static int64_t sensor_id = -1; /* -1 = not defined */ |
123 | | |
124 | | void EveFileInfo(SCJsonBuilder *jb, const File *ff, const uint64_t tx_id, const uint16_t flags) |
125 | 172k | { |
126 | 172k | SCJbSetStringFromBytes(jb, "filename", ff->name, ff->name_len); |
127 | | |
128 | 172k | if (ff->sid_cnt > 0) { |
129 | 32.5k | SCJbOpenArray(jb, "sid"); |
130 | 96.6k | for (uint32_t i = 0; ff->sid != NULL && i < ff->sid_cnt; i++) { |
131 | 64.1k | SCJbAppendUint(jb, ff->sid[i]); |
132 | 64.1k | } |
133 | 32.5k | SCJbClose(jb); |
134 | 32.5k | } |
135 | | |
136 | | #ifdef HAVE_MAGIC |
137 | | if (ff->magic) |
138 | | SCJbSetString(jb, "magic", (char *)ff->magic); |
139 | | #endif |
140 | 172k | SCJbSetBool(jb, "gaps", ff->flags & FILE_HAS_GAPS); |
141 | 172k | switch (ff->state) { |
142 | 157k | case FILE_STATE_CLOSED: |
143 | 157k | JB_SET_STRING(jb, "state", "CLOSED"); |
144 | 157k | if (ff->flags & FILE_MD5) { |
145 | 3.04k | SCJbSetHex(jb, "md5", (uint8_t *)ff->md5, (uint32_t)sizeof(ff->md5)); |
146 | 3.04k | } |
147 | 157k | if (ff->flags & FILE_SHA1) { |
148 | 3.04k | SCJbSetHex(jb, "sha1", (uint8_t *)ff->sha1, (uint32_t)sizeof(ff->sha1)); |
149 | 3.04k | } |
150 | 157k | break; |
151 | 3.12k | case FILE_STATE_TRUNCATED: |
152 | 3.12k | JB_SET_STRING(jb, "state", "TRUNCATED"); |
153 | 3.12k | break; |
154 | 0 | case FILE_STATE_ERROR: |
155 | 0 | JB_SET_STRING(jb, "state", "ERROR"); |
156 | 0 | break; |
157 | 11.8k | default: |
158 | 11.8k | JB_SET_STRING(jb, "state", "UNKNOWN"); |
159 | 11.8k | break; |
160 | 172k | } |
161 | | |
162 | 172k | if (ff->flags & FILE_SHA256) { |
163 | 160k | SCJbSetHex(jb, "sha256", (uint8_t *)ff->sha256, (uint32_t)sizeof(ff->sha256)); |
164 | 160k | } |
165 | | |
166 | 172k | if (flags & FILE_STORED) { |
167 | 118k | JB_SET_TRUE(jb, "stored"); |
168 | 118k | SCJbSetUint(jb, "file_id", ff->file_store_id); |
169 | 118k | } else { |
170 | 53.6k | JB_SET_FALSE(jb, "stored"); |
171 | 53.6k | if (flags & FILE_STORE) { |
172 | 53.6k | JB_SET_TRUE(jb, "storing"); |
173 | 53.6k | } |
174 | 53.6k | } |
175 | | |
176 | 172k | SCJbSetUint(jb, "size", FileTrackedSize(ff)); |
177 | 172k | if (ff->end > 0) { |
178 | 18.8k | SCJbSetUint(jb, "start", ff->start); |
179 | 18.8k | SCJbSetUint(jb, "end", ff->end); |
180 | 18.8k | } |
181 | 172k | SCJbSetUint(jb, "tx_id", tx_id); |
182 | 172k | } |
183 | | |
184 | | static void EveAddPacketVars(const Packet *p, SCJsonBuilder *js_vars) |
185 | 0 | { |
186 | 0 | if (p == NULL || p->pktvar == NULL) { |
187 | 0 | return; |
188 | 0 | } |
189 | 0 | PktVar *pv = p->pktvar; |
190 | 0 | bool open = false; |
191 | 0 | while (pv != NULL) { |
192 | 0 | if (pv->key || pv->id > 0) { |
193 | 0 | if (!open) { |
194 | 0 | SCJbOpenArray(js_vars, "pktvars"); |
195 | 0 | open = true; |
196 | 0 | } |
197 | 0 | SCJbStartObject(js_vars); |
198 | |
|
199 | 0 | if (pv->key != NULL) { |
200 | 0 | uint32_t offset = 0; |
201 | 0 | uint8_t keybuf[pv->key_len + 1]; |
202 | 0 | PrintStringsToBuffer(keybuf, &offset, pv->key_len + 1, pv->key, pv->key_len); |
203 | 0 | SCJbSetPrintAsciiString(js_vars, (char *)keybuf, pv->value, pv->value_len); |
204 | 0 | } else { |
205 | 0 | const char *varname = VarNameStoreLookupById(pv->id, VAR_TYPE_PKT_VAR); |
206 | 0 | SCJbSetPrintAsciiString(js_vars, varname, pv->value, pv->value_len); |
207 | 0 | } |
208 | 0 | SCJbClose(js_vars); |
209 | 0 | } |
210 | 0 | pv = pv->next; |
211 | 0 | } |
212 | 0 | if (open) { |
213 | 0 | SCJbClose(js_vars); |
214 | 0 | } |
215 | 0 | } |
216 | | |
217 | | /** |
218 | | * \brief Check if string s has prefix prefix. |
219 | | * |
220 | | * \retval true if string has prefix |
221 | | * \retval false if string does not have prefix |
222 | | * |
223 | | * TODO: Move to file with other string handling functions. |
224 | | */ |
225 | | static bool SCStringHasPrefix(const char *s, const char *prefix) |
226 | 14.4k | { |
227 | 14.4k | if (strncmp(s, prefix, strlen(prefix)) == 0) { |
228 | 36 | return true; |
229 | 36 | } |
230 | 14.4k | return false; |
231 | 14.4k | } |
232 | | |
233 | | static void EveAddFlowVars(const Flow *f, SCJsonBuilder *js_root, SCJsonBuilder **js_traffic) |
234 | 86.9k | { |
235 | 86.9k | if (f == NULL || f->flowvar == NULL) { |
236 | 0 | return; |
237 | 0 | } |
238 | 86.9k | SCJsonBuilder *js_flowvars = NULL; |
239 | 86.9k | SCJsonBuilder *js_traffic_id = NULL; |
240 | 86.9k | SCJsonBuilder *js_traffic_label = NULL; |
241 | 86.9k | SCJsonBuilder *js_flowints = NULL; |
242 | 86.9k | SCJsonBuilder *js_entropyvals = NULL; |
243 | 86.9k | SCJsonBuilder *js_flowbits = NULL; |
244 | 86.9k | GenericVar *gv = f->flowvar; |
245 | 190k | while (gv != NULL) { |
246 | 103k | if (gv->type == DETECT_FLOWVAR || gv->type == DETECT_FLOWINT) { |
247 | 21.6k | FlowVar *fv = (FlowVar *)gv; |
248 | 21.6k | if (fv->datatype == FLOWVAR_TYPE_STR && fv->key == NULL) { |
249 | 73 | const char *varname = VarNameStoreLookupById(fv->idx, |
250 | 73 | VAR_TYPE_FLOW_VAR); |
251 | 73 | if (varname) { |
252 | 73 | if (js_flowvars == NULL) { |
253 | 65 | js_flowvars = SCJbNewArray(); |
254 | 65 | if (js_flowvars == NULL) |
255 | 0 | break; |
256 | 65 | } |
257 | | |
258 | 73 | SCJbStartObject(js_flowvars); |
259 | 73 | SCJbSetPrintAsciiString( |
260 | 73 | js_flowvars, varname, fv->data.fv_str.value, fv->data.fv_str.value_len); |
261 | 73 | SCJbClose(js_flowvars); |
262 | 73 | } |
263 | 21.5k | } else if (fv->datatype == FLOWVAR_TYPE_STR && fv->key != NULL) { |
264 | 0 | if (js_flowvars == NULL) { |
265 | 0 | js_flowvars = SCJbNewArray(); |
266 | 0 | if (js_flowvars == NULL) |
267 | 0 | break; |
268 | 0 | } |
269 | | |
270 | 0 | uint8_t keybuf[fv->keylen + 1]; |
271 | 0 | uint32_t offset = 0; |
272 | 0 | PrintStringsToBuffer(keybuf, &offset, fv->keylen + 1, fv->key, fv->keylen); |
273 | |
|
274 | 0 | SCJbStartObject(js_flowvars); |
275 | 0 | SCJbSetPrintAsciiString(js_flowvars, (const char *)keybuf, fv->data.fv_str.value, |
276 | 0 | fv->data.fv_str.value_len); |
277 | 0 | SCJbClose(js_flowvars); |
278 | 21.5k | } else if (fv->datatype == FLOWVAR_TYPE_FLOAT) { |
279 | 9.10k | const char *varname = VarNameStoreLookupById(fv->idx, VAR_TYPE_FLOW_FLOAT); |
280 | 9.10k | if (varname) { |
281 | 9.10k | if (js_entropyvals == NULL) { |
282 | 9.10k | js_entropyvals = SCJbNewObject(); |
283 | 9.10k | if (js_entropyvals == NULL) |
284 | 0 | break; |
285 | 9.10k | } |
286 | 9.10k | SCJbSetFloat(js_entropyvals, varname, fv->data.fv_float.value); |
287 | 9.10k | } |
288 | | |
289 | 12.4k | } else if (fv->datatype == FLOWVAR_TYPE_INT) { |
290 | 12.4k | const char *varname = VarNameStoreLookupById(fv->idx, |
291 | 12.4k | VAR_TYPE_FLOW_INT); |
292 | 12.4k | if (varname) { |
293 | 12.4k | if (js_flowints == NULL) { |
294 | 11.9k | js_flowints = SCJbNewObject(); |
295 | 11.9k | if (js_flowints == NULL) |
296 | 0 | break; |
297 | 11.9k | } |
298 | 12.4k | SCJbSetUint(js_flowints, varname, fv->data.fv_int.value); |
299 | 12.4k | } |
300 | 12.4k | } |
301 | 81.6k | } else if (gv->type == DETECT_FLOWBITS) { |
302 | 81.5k | FlowBit *fb = (FlowBit *)gv; |
303 | 81.5k | const char *varname = VarNameStoreLookupById(fb->idx, |
304 | 81.5k | VAR_TYPE_FLOW_BIT); |
305 | 81.5k | if (varname) { |
306 | 80.4k | if (SCStringHasPrefix(varname, TRAFFIC_ID_PREFIX)) { |
307 | 779 | if (js_traffic_id == NULL) { |
308 | 500 | js_traffic_id = SCJbNewArray(); |
309 | 500 | if (unlikely(js_traffic_id == NULL)) { |
310 | 0 | break; |
311 | 0 | } |
312 | 500 | } |
313 | 779 | SCJbAppendString(js_traffic_id, &varname[traffic_id_prefix_len]); |
314 | 79.6k | } else if (SCStringHasPrefix(varname, TRAFFIC_LABEL_PREFIX)) { |
315 | 1.35k | if (js_traffic_label == NULL) { |
316 | 1.19k | js_traffic_label = SCJbNewArray(); |
317 | 1.19k | if (unlikely(js_traffic_label == NULL)) { |
318 | 0 | break; |
319 | 0 | } |
320 | 1.19k | } |
321 | 1.35k | SCJbAppendString(js_traffic_label, &varname[traffic_label_prefix_len]); |
322 | 78.3k | } else { |
323 | 78.3k | if (js_flowbits == NULL) { |
324 | 66.2k | js_flowbits = SCJbNewArray(); |
325 | 66.2k | if (unlikely(js_flowbits == NULL)) |
326 | 0 | break; |
327 | 66.2k | } |
328 | 78.3k | SCJbAppendString(js_flowbits, varname); |
329 | 78.3k | } |
330 | 80.4k | } |
331 | 81.5k | } |
332 | 103k | gv = gv->next; |
333 | 103k | } |
334 | 86.9k | if (js_flowbits) { |
335 | 66.2k | SCJbClose(js_flowbits); |
336 | 66.2k | SCJbSetObject(js_root, "flowbits", js_flowbits); |
337 | 66.2k | SCJbFree(js_flowbits); |
338 | 66.2k | } |
339 | 86.9k | if (js_flowints) { |
340 | 11.9k | SCJbClose(js_flowints); |
341 | 11.9k | SCJbSetObject(js_root, "flowints", js_flowints); |
342 | 11.9k | SCJbFree(js_flowints); |
343 | 11.9k | } |
344 | 86.9k | if (js_entropyvals) { |
345 | 9.10k | SCJbClose(js_entropyvals); |
346 | 9.10k | SCJbSetObject(js_root, "entropy", js_entropyvals); |
347 | 9.10k | SCJbFree(js_entropyvals); |
348 | 9.10k | } |
349 | 86.9k | if (js_flowvars) { |
350 | 65 | SCJbClose(js_flowvars); |
351 | 65 | SCJbSetObject(js_root, "flowvars", js_flowvars); |
352 | 65 | SCJbFree(js_flowvars); |
353 | 65 | } |
354 | | |
355 | 86.9k | if (js_traffic_id != NULL || js_traffic_label != NULL) { |
356 | 1.64k | *js_traffic = SCJbNewObject(); |
357 | 1.64k | if (likely(*js_traffic != NULL)) { |
358 | 1.64k | if (js_traffic_id != NULL) { |
359 | 500 | SCJbClose(js_traffic_id); |
360 | 500 | SCJbSetObject(*js_traffic, "id", js_traffic_id); |
361 | 500 | SCJbFree(js_traffic_id); |
362 | 500 | } |
363 | 1.64k | if (js_traffic_label != NULL) { |
364 | 1.19k | SCJbClose(js_traffic_label); |
365 | 1.19k | SCJbSetObject(*js_traffic, "label", js_traffic_label); |
366 | 1.19k | SCJbFree(js_traffic_label); |
367 | 1.19k | } |
368 | 1.64k | SCJbClose(*js_traffic); |
369 | 1.64k | } |
370 | 1.64k | } |
371 | 86.9k | } |
372 | | |
373 | | void EveAddMetadata(const Packet *p, const Flow *f, SCJsonBuilder *js) |
374 | 16.8M | { |
375 | 16.8M | if ((p && p->pktvar) || (f && f->flowvar)) { |
376 | 86.9k | SCJsonBuilder *js_vars = SCJbNewObject(); |
377 | 86.9k | if (js_vars) { |
378 | 86.9k | if (f && f->flowvar) { |
379 | 86.9k | SCJsonBuilder *js_traffic = NULL; |
380 | 86.9k | EveAddFlowVars(f, js_vars, &js_traffic); |
381 | 86.9k | if (js_traffic != NULL) { |
382 | 1.64k | SCJbSetObject(js, "traffic", js_traffic); |
383 | 1.64k | SCJbFree(js_traffic); |
384 | 1.64k | } |
385 | 86.9k | } |
386 | 86.9k | if (p && p->pktvar) { |
387 | 17 | EveAddPacketVars(p, js_vars); |
388 | 17 | } |
389 | 86.9k | SCJbClose(js_vars); |
390 | 86.9k | SCJbSetObject(js, "metadata", js_vars); |
391 | 86.9k | SCJbFree(js_vars); |
392 | 86.9k | } |
393 | 86.9k | } |
394 | 16.8M | } |
395 | | |
396 | | void EveAddCommonOptions(const OutputJsonCommonSettings *cfg, const Packet *p, const Flow *f, |
397 | | SCJsonBuilder *js, enum SCOutputJsonLogDirection dir) |
398 | 16.8M | { |
399 | 16.8M | if (cfg->include_suricata_version) { |
400 | 0 | SCJbSetString(js, "suricata_version", PROG_VER); |
401 | 0 | } |
402 | 16.8M | if (cfg->include_metadata) { |
403 | 16.8M | EveAddMetadata(p, f, js); |
404 | 16.8M | } |
405 | 16.8M | if (cfg->include_ethernet) { |
406 | 0 | CreateJSONEther(js, p, f, dir); |
407 | 0 | } |
408 | 16.8M | if (cfg->include_community_id && f != NULL) { |
409 | 0 | CreateEveCommunityFlowId(js, f, cfg->community_id_seed); |
410 | 0 | } |
411 | 16.8M | if (f != NULL && f->tenant_id > 0) { |
412 | 0 | SCJbSetUint(js, "tenant_id", f->tenant_id); |
413 | 0 | } |
414 | 16.8M | } |
415 | | |
416 | | /** |
417 | | * \brief Jsonify a packet |
418 | | * |
419 | | * \param p Packet |
420 | | * \param js JSON object |
421 | | * \param max_length If non-zero, restricts the number of packet data bytes handled. |
422 | | */ |
423 | | void EvePacket(const Packet *p, SCJsonBuilder *js, uint32_t max_length) |
424 | 13.8M | { |
425 | 13.8M | uint32_t max_len = max_length == 0 ? GET_PKT_LEN(p) : max_length; |
426 | 13.8M | SCJbSetBase64(js, "packet", GET_PKT_DATA(p), max_len); |
427 | | |
428 | 13.8M | if (!SCJbOpenObject(js, "packet_info")) { |
429 | 0 | return; |
430 | 0 | } |
431 | 13.8M | if (!SCJbSetUint(js, "linktype", p->datalink)) { |
432 | 0 | SCJbClose(js); |
433 | 0 | return; |
434 | 0 | } |
435 | | |
436 | 13.8M | const char *dl_name = DatalinkValueToName(p->datalink); |
437 | | |
438 | | // Intentionally ignore the return value from SCJbSetString and proceed |
439 | | // so the jb object is closed |
440 | 13.8M | (void)SCJbSetString(js, "linktype_name", dl_name == NULL ? "n/a" : dl_name); |
441 | | |
442 | 13.8M | SCJbClose(js); |
443 | 13.8M | } |
444 | | |
445 | | /** \brief jsonify tcp flags field |
446 | | * Only add 'true' fields in an attempt to keep things reasonably compact. |
447 | | */ |
448 | | void EveTcpFlags(const uint8_t flags, SCJsonBuilder *js) |
449 | 403k | { |
450 | 403k | if (flags & TH_SYN) |
451 | 231k | JB_SET_TRUE(js, "syn"); |
452 | 403k | if (flags & TH_FIN) |
453 | 212k | JB_SET_TRUE(js, "fin"); |
454 | 403k | if (flags & TH_RST) |
455 | 125k | JB_SET_TRUE(js, "rst"); |
456 | 403k | if (flags & TH_PUSH) |
457 | 291k | JB_SET_TRUE(js, "psh"); |
458 | 403k | if (flags & TH_ACK) |
459 | 360k | JB_SET_TRUE(js, "ack"); |
460 | 403k | if (flags & TH_URG) |
461 | 129k | JB_SET_TRUE(js, "urg"); |
462 | 403k | if (flags & TH_ECN) |
463 | 143k | JB_SET_TRUE(js, "ecn"); |
464 | 403k | if (flags & TH_CWR) |
465 | 58.6k | JB_SET_TRUE(js, "cwr"); |
466 | 403k | } |
467 | | |
468 | | void JsonAddrInfoInit(const Packet *p, enum SCOutputJsonLogDirection dir, JsonAddrInfo *addr) |
469 | 7.45M | { |
470 | 7.45M | char srcip[46] = {0}, dstip[46] = {0}; |
471 | 7.45M | Port sp, dp; |
472 | | |
473 | 7.45M | switch (dir) { |
474 | 7.41M | case LOG_DIR_PACKET: |
475 | 7.41M | if (PacketIsIPv4(p)) { |
476 | 432k | PrintInet(AF_INET, (const void *)GET_IPV4_SRC_ADDR_PTR(p), |
477 | 432k | srcip, sizeof(srcip)); |
478 | 432k | PrintInet(AF_INET, (const void *)GET_IPV4_DST_ADDR_PTR(p), |
479 | 432k | dstip, sizeof(dstip)); |
480 | 6.97M | } else if (PacketIsIPv6(p)) { |
481 | 6.27M | PrintInet(AF_INET6, (const void *)GET_IPV6_SRC_ADDR(p), |
482 | 6.27M | srcip, sizeof(srcip)); |
483 | 6.27M | PrintInet(AF_INET6, (const void *)GET_IPV6_DST_ADDR(p), |
484 | 6.27M | dstip, sizeof(dstip)); |
485 | 6.27M | } else { |
486 | | /* Not an IP packet so don't do anything */ |
487 | 707k | return; |
488 | 707k | } |
489 | 6.70M | sp = p->sp; |
490 | 6.70M | dp = p->dp; |
491 | 6.70M | break; |
492 | 38.0k | case LOG_DIR_FLOW: |
493 | 38.0k | case LOG_DIR_FLOW_TOSERVER: |
494 | 38.0k | if ((PKT_IS_TOSERVER(p))) { |
495 | 27.4k | if (PacketIsIPv4(p)) { |
496 | 27.4k | PrintInet(AF_INET, (const void *)GET_IPV4_SRC_ADDR_PTR(p), |
497 | 27.4k | srcip, sizeof(srcip)); |
498 | 27.4k | PrintInet(AF_INET, (const void *)GET_IPV4_DST_ADDR_PTR(p), |
499 | 27.4k | dstip, sizeof(dstip)); |
500 | 27.4k | } else if (PacketIsIPv6(p)) { |
501 | 0 | PrintInet(AF_INET6, (const void *)GET_IPV6_SRC_ADDR(p), |
502 | 0 | srcip, sizeof(srcip)); |
503 | 0 | PrintInet(AF_INET6, (const void *)GET_IPV6_DST_ADDR(p), |
504 | 0 | dstip, sizeof(dstip)); |
505 | 0 | } |
506 | 27.4k | sp = p->sp; |
507 | 27.4k | dp = p->dp; |
508 | 27.4k | } else { |
509 | 10.6k | if (PacketIsIPv4(p)) { |
510 | 10.6k | PrintInet(AF_INET, (const void *)GET_IPV4_DST_ADDR_PTR(p), |
511 | 10.6k | srcip, sizeof(srcip)); |
512 | 10.6k | PrintInet(AF_INET, (const void *)GET_IPV4_SRC_ADDR_PTR(p), |
513 | 10.6k | dstip, sizeof(dstip)); |
514 | 10.6k | } else if (PacketIsIPv6(p)) { |
515 | 52 | PrintInet(AF_INET6, (const void *)GET_IPV6_DST_ADDR(p), |
516 | 52 | srcip, sizeof(srcip)); |
517 | 52 | PrintInet(AF_INET6, (const void *)GET_IPV6_SRC_ADDR(p), |
518 | 52 | dstip, sizeof(dstip)); |
519 | 52 | } |
520 | 10.6k | sp = p->dp; |
521 | 10.6k | dp = p->sp; |
522 | 10.6k | } |
523 | 38.0k | break; |
524 | 2.83k | case LOG_DIR_FLOW_TOCLIENT: |
525 | 2.83k | if ((PKT_IS_TOCLIENT(p))) { |
526 | 2.53k | if (PacketIsIPv4(p)) { |
527 | 2.53k | PrintInet(AF_INET, (const void *)GET_IPV4_SRC_ADDR_PTR(p), |
528 | 2.53k | srcip, sizeof(srcip)); |
529 | 2.53k | PrintInet(AF_INET, (const void *)GET_IPV4_DST_ADDR_PTR(p), |
530 | 2.53k | dstip, sizeof(dstip)); |
531 | 2.53k | } else if (PacketIsIPv6(p)) { |
532 | 0 | PrintInet(AF_INET6, (const void *)GET_IPV6_SRC_ADDR(p), |
533 | 0 | srcip, sizeof(srcip)); |
534 | 0 | PrintInet(AF_INET6, (const void *)GET_IPV6_DST_ADDR(p), |
535 | 0 | dstip, sizeof(dstip)); |
536 | 0 | } |
537 | 2.53k | sp = p->sp; |
538 | 2.53k | dp = p->dp; |
539 | 2.53k | } else { |
540 | 292 | if (PacketIsIPv4(p)) { |
541 | 292 | PrintInet(AF_INET, (const void *)GET_IPV4_DST_ADDR_PTR(p), |
542 | 292 | srcip, sizeof(srcip)); |
543 | 292 | PrintInet(AF_INET, (const void *)GET_IPV4_SRC_ADDR_PTR(p), |
544 | 292 | dstip, sizeof(dstip)); |
545 | 292 | } else if (PacketIsIPv6(p)) { |
546 | 0 | PrintInet(AF_INET6, (const void *)GET_IPV6_DST_ADDR(p), |
547 | 0 | srcip, sizeof(srcip)); |
548 | 0 | PrintInet(AF_INET6, (const void *)GET_IPV6_SRC_ADDR(p), |
549 | 0 | dstip, sizeof(dstip)); |
550 | 0 | } |
551 | 292 | sp = p->dp; |
552 | 292 | dp = p->sp; |
553 | 292 | } |
554 | 2.83k | break; |
555 | 0 | default: |
556 | 0 | DEBUG_VALIDATE_BUG_ON(1); |
557 | 0 | return; |
558 | 7.45M | } |
559 | | |
560 | 6.74M | strlcpy(addr->src_ip, srcip, JSON_ADDR_LEN); |
561 | 6.74M | strlcpy(addr->dst_ip, dstip, JSON_ADDR_LEN); |
562 | | |
563 | 6.74M | switch (p->proto) { |
564 | 3.59k | case IPPROTO_UDP: |
565 | 6.40M | case IPPROTO_TCP: |
566 | 6.41M | case IPPROTO_SCTP: |
567 | 6.41M | addr->sp = sp; |
568 | 6.41M | addr->dp = dp; |
569 | 6.41M | addr->log_port = true; |
570 | 6.41M | break; |
571 | 335k | default: |
572 | 335k | addr->log_port = false; |
573 | 335k | break; |
574 | 6.74M | } |
575 | | |
576 | 6.74M | if (SCProtoNameValid(PacketGetIPProto(p))) { |
577 | 6.69M | strlcpy(addr->proto, known_proto[PacketGetIPProto(p)], sizeof(addr->proto)); |
578 | 6.69M | } else { |
579 | 48.3k | snprintf(addr->proto, sizeof(addr->proto), "%" PRIu32, PacketGetIPProto(p)); |
580 | 48.3k | } |
581 | 6.74M | } |
582 | | |
583 | 0 | #define COMMUNITY_ID_BUF_SIZE 64 |
584 | | |
585 | | static bool CalculateCommunityFlowIdv4(const Flow *f, |
586 | | const uint16_t seed, unsigned char *base64buf) |
587 | 0 | { |
588 | 0 | struct { |
589 | 0 | uint16_t seed; |
590 | 0 | uint32_t src; |
591 | 0 | uint32_t dst; |
592 | 0 | uint8_t proto; |
593 | 0 | uint8_t pad0; |
594 | 0 | uint16_t sp; |
595 | 0 | uint16_t dp; |
596 | 0 | } __attribute__((__packed__)) ipv4; |
597 | |
|
598 | 0 | uint32_t src = f->src.addr_data32[0]; |
599 | 0 | uint32_t dst = f->dst.addr_data32[0]; |
600 | 0 | uint16_t sp = f->sp; |
601 | 0 | if (f->proto == IPPROTO_ICMP) |
602 | 0 | sp = f->icmp_s.type; |
603 | 0 | sp = htons(sp); |
604 | 0 | uint16_t dp = f->dp; |
605 | 0 | if (f->proto == IPPROTO_ICMP) |
606 | 0 | dp = f->icmp_d.type; |
607 | 0 | dp = htons(dp); |
608 | |
|
609 | 0 | ipv4.seed = htons(seed); |
610 | 0 | if (ntohl(src) < ntohl(dst) || (src == dst && ntohs(sp) < ntohs(dp))) { |
611 | 0 | ipv4.src = src; |
612 | 0 | ipv4.dst = dst; |
613 | 0 | ipv4.sp = sp; |
614 | 0 | ipv4.dp = dp; |
615 | 0 | } else { |
616 | 0 | ipv4.src = dst; |
617 | 0 | ipv4.dst = src; |
618 | 0 | ipv4.sp = dp; |
619 | 0 | ipv4.dp = sp; |
620 | 0 | } |
621 | 0 | ipv4.proto = f->proto; |
622 | 0 | ipv4.pad0 = 0; |
623 | |
|
624 | 0 | uint8_t hash[20]; |
625 | 0 | if (SCSha1HashBuffer((const uint8_t *)&ipv4, sizeof(ipv4), hash, sizeof(hash)) == 1) { |
626 | 0 | strlcpy((char *)base64buf, "1:", COMMUNITY_ID_BUF_SIZE); |
627 | 0 | unsigned long out_len = COMMUNITY_ID_BUF_SIZE - 2; |
628 | 0 | if (SCBase64Encode(hash, sizeof(hash), base64buf + 2, &out_len) == SC_BASE64_OK) { |
629 | 0 | return true; |
630 | 0 | } |
631 | 0 | } |
632 | 0 | return false; |
633 | 0 | } |
634 | | |
635 | | static bool CalculateCommunityFlowIdv6(const Flow *f, |
636 | | const uint16_t seed, unsigned char *base64buf) |
637 | 0 | { |
638 | 0 | struct { |
639 | 0 | uint16_t seed; |
640 | 0 | uint32_t src[4]; |
641 | 0 | uint32_t dst[4]; |
642 | 0 | uint8_t proto; |
643 | 0 | uint8_t pad0; |
644 | 0 | uint16_t sp; |
645 | 0 | uint16_t dp; |
646 | 0 | } __attribute__((__packed__)) ipv6; |
647 | |
|
648 | 0 | uint16_t sp = f->sp; |
649 | 0 | if (f->proto == IPPROTO_ICMPV6) |
650 | 0 | sp = f->icmp_s.type; |
651 | 0 | sp = htons(sp); |
652 | 0 | uint16_t dp = f->dp; |
653 | 0 | if (f->proto == IPPROTO_ICMPV6) |
654 | 0 | dp = f->icmp_d.type; |
655 | 0 | dp = htons(dp); |
656 | |
|
657 | 0 | ipv6.seed = htons(seed); |
658 | 0 | int cmp_r = memcmp(&f->src, &f->dst, sizeof(f->src)); |
659 | 0 | if ((cmp_r < 0) || (cmp_r == 0 && ntohs(sp) < ntohs(dp))) { |
660 | 0 | memcpy(&ipv6.src, &f->src.addr_data32, 16); |
661 | 0 | memcpy(&ipv6.dst, &f->dst.addr_data32, 16); |
662 | 0 | ipv6.sp = sp; |
663 | 0 | ipv6.dp = dp; |
664 | 0 | } else { |
665 | 0 | memcpy(&ipv6.src, &f->dst.addr_data32, 16); |
666 | 0 | memcpy(&ipv6.dst, &f->src.addr_data32, 16); |
667 | 0 | ipv6.sp = dp; |
668 | 0 | ipv6.dp = sp; |
669 | 0 | } |
670 | 0 | ipv6.proto = f->proto; |
671 | 0 | ipv6.pad0 = 0; |
672 | |
|
673 | 0 | uint8_t hash[20]; |
674 | 0 | if (SCSha1HashBuffer((const uint8_t *)&ipv6, sizeof(ipv6), hash, sizeof(hash)) == 1) { |
675 | 0 | strlcpy((char *)base64buf, "1:", COMMUNITY_ID_BUF_SIZE); |
676 | 0 | unsigned long out_len = COMMUNITY_ID_BUF_SIZE - 2; |
677 | 0 | if (SCBase64Encode(hash, sizeof(hash), base64buf + 2, &out_len) == SC_BASE64_OK) { |
678 | 0 | return true; |
679 | 0 | } |
680 | 0 | } |
681 | 0 | return false; |
682 | 0 | } |
683 | | |
684 | | static void CreateEveCommunityFlowId(SCJsonBuilder *js, const Flow *f, const uint16_t seed) |
685 | 0 | { |
686 | 0 | unsigned char buf[COMMUNITY_ID_BUF_SIZE]; |
687 | 0 | if (f->flags & FLOW_IPV4) { |
688 | 0 | if (CalculateCommunityFlowIdv4(f, seed, buf)) { |
689 | 0 | SCJbSetString(js, "community_id", (const char *)buf); |
690 | 0 | } |
691 | 0 | } else if (f->flags & FLOW_IPV6) { |
692 | 0 | if (CalculateCommunityFlowIdv6(f, seed, buf)) { |
693 | 0 | SCJbSetString(js, "community_id", (const char *)buf); |
694 | 0 | } |
695 | 0 | } |
696 | 0 | } |
697 | | |
698 | | void CreateEveFlowId(SCJsonBuilder *js, const Flow *f) |
699 | 16.8M | { |
700 | 16.8M | if (f == NULL) { |
701 | 4.12M | return; |
702 | 4.12M | } |
703 | 12.7M | uint64_t flow_id = FlowGetId(f); |
704 | 12.7M | SCJbSetUint(js, "flow_id", flow_id); |
705 | 12.7M | if (f->parent_id) { |
706 | 2.14k | SCJbSetUint(js, "parent_id", f->parent_id); |
707 | 2.14k | } |
708 | 12.7M | } |
709 | | |
710 | | void JSONFormatAndAddMACAddr(SCJsonBuilder *js, const char *key, const uint8_t *val, bool is_array) |
711 | 0 | { |
712 | 0 | char eth_addr[19]; |
713 | 0 | (void) snprintf(eth_addr, 19, "%02x:%02x:%02x:%02x:%02x:%02x", |
714 | 0 | val[0], val[1], val[2], val[3], val[4], val[5]); |
715 | 0 | if (is_array) { |
716 | 0 | SCJbAppendString(js, eth_addr); |
717 | 0 | } else { |
718 | 0 | SCJbSetString(js, key, eth_addr); |
719 | 0 | } |
720 | 0 | } |
721 | | |
722 | | /* only required to traverse the MAC address set */ |
723 | | typedef struct JSONMACAddrInfo { |
724 | | SCJsonBuilder *src, *dst; |
725 | | } JSONMACAddrInfo; |
726 | | |
727 | | static int MacSetIterateToJSON(uint8_t *val, MacSetSide side, void *data) |
728 | 0 | { |
729 | 0 | JSONMACAddrInfo *info = (JSONMACAddrInfo*) data; |
730 | 0 | if (side == MAC_SET_DST) { |
731 | 0 | JSONFormatAndAddMACAddr(info->dst, NULL, val, true); |
732 | 0 | } else { |
733 | 0 | JSONFormatAndAddMACAddr(info->src, NULL, val, true); |
734 | 0 | } |
735 | 0 | return 0; |
736 | 0 | } |
737 | | |
738 | | static int CreateJSONEther( |
739 | | SCJsonBuilder *js, const Packet *p, const Flow *f, enum SCOutputJsonLogDirection dir) |
740 | 0 | { |
741 | 0 | if (p != NULL) { |
742 | | /* this is a packet context, so we need to add scalar fields */ |
743 | 0 | if (PacketIsEthernet(p)) { |
744 | 0 | const EthernetHdr *ethh = PacketGetEthernet(p); |
745 | 0 | SCJbOpenObject(js, "ether"); |
746 | 0 | SCJbSetUint(js, "ether_type", SCNtohs(ethh->eth_type)); |
747 | 0 | const uint8_t *src; |
748 | 0 | const uint8_t *dst; |
749 | 0 | switch (dir) { |
750 | 0 | case LOG_DIR_FLOW_TOSERVER: |
751 | | // fallthrough |
752 | 0 | case LOG_DIR_FLOW: |
753 | 0 | if (PKT_IS_TOCLIENT(p)) { |
754 | 0 | src = ethh->eth_dst; |
755 | 0 | dst = ethh->eth_src; |
756 | 0 | } else { |
757 | 0 | src = ethh->eth_src; |
758 | 0 | dst = ethh->eth_dst; |
759 | 0 | } |
760 | 0 | break; |
761 | 0 | case LOG_DIR_FLOW_TOCLIENT: |
762 | 0 | if (PKT_IS_TOSERVER(p)) { |
763 | 0 | src = ethh->eth_dst; |
764 | 0 | dst = ethh->eth_src; |
765 | 0 | } else { |
766 | 0 | src = ethh->eth_src; |
767 | 0 | dst = ethh->eth_dst; |
768 | 0 | } |
769 | 0 | break; |
770 | 0 | case LOG_DIR_PACKET: |
771 | 0 | default: |
772 | 0 | src = ethh->eth_src; |
773 | 0 | dst = ethh->eth_dst; |
774 | 0 | break; |
775 | 0 | } |
776 | 0 | JSONFormatAndAddMACAddr(js, "src_mac", src, false); |
777 | 0 | JSONFormatAndAddMACAddr(js, "dest_mac", dst, false); |
778 | 0 | SCJbClose(js); |
779 | 0 | } else if (f != NULL) { |
780 | | /* When pseudopackets do not have associated ethernet metadata, |
781 | | use the first set of mac addresses stored with their flow. |
782 | | The first set of macs should come from the flow's first packet, |
783 | | providing the most fitting representation of the event's ethernet. */ |
784 | 0 | MacSet *ms = FlowGetStorageById(f, MacSetGetFlowStorageID()); |
785 | 0 | if (ms != NULL && MacSetSize(ms) > 0) { |
786 | 0 | uint8_t *src = MacSetGetFirst(ms, MAC_SET_SRC); |
787 | 0 | uint8_t *dst = MacSetGetFirst(ms, MAC_SET_DST); |
788 | 0 | if (dst != NULL && src != NULL) { |
789 | 0 | SCJbOpenObject(js, "ether"); |
790 | 0 | JSONFormatAndAddMACAddr(js, "src_mac", src, false); |
791 | 0 | JSONFormatAndAddMACAddr(js, "dest_mac", dst, false); |
792 | 0 | SCJbClose(js); |
793 | 0 | } |
794 | 0 | } |
795 | 0 | } |
796 | 0 | } else if (f != NULL) { |
797 | | /* we are creating an ether object in a flow context, so we need to |
798 | | append to arrays */ |
799 | 0 | MacSet *ms = FlowGetStorageById(f, MacSetGetFlowStorageID()); |
800 | 0 | if (ms != NULL && MacSetSize(ms) > 0) { |
801 | 0 | SCJbOpenObject(js, "ether"); |
802 | 0 | JSONMACAddrInfo info; |
803 | 0 | info.dst = SCJbNewArray(); |
804 | 0 | info.src = SCJbNewArray(); |
805 | 0 | int ret = MacSetForEach(ms, MacSetIterateToJSON, &info); |
806 | 0 | if (unlikely(ret != 0)) { |
807 | | /* should not happen, JSONFlowAppendMACAddrs is sane */ |
808 | 0 | SCJbFree(info.dst); |
809 | 0 | SCJbFree(info.src); |
810 | 0 | SCJbClose(js); |
811 | 0 | return ret; |
812 | 0 | } |
813 | 0 | SCJbClose(info.dst); |
814 | 0 | SCJbClose(info.src); |
815 | | /* case is handling netflow too so may need to revert */ |
816 | 0 | if (dir == LOG_DIR_FLOW_TOCLIENT) { |
817 | 0 | SCJbSetObject(js, "dest_macs", info.src); |
818 | 0 | SCJbSetObject(js, "src_macs", info.dst); |
819 | 0 | } else { |
820 | 0 | DEBUG_VALIDATE_BUG_ON(dir != LOG_DIR_FLOW_TOSERVER && dir != LOG_DIR_FLOW); |
821 | 0 | SCJbSetObject(js, "dest_macs", info.dst); |
822 | 0 | SCJbSetObject(js, "src_macs", info.src); |
823 | 0 | } |
824 | 0 | SCJbFree(info.dst); |
825 | 0 | SCJbFree(info.src); |
826 | 0 | SCJbClose(js); |
827 | 0 | } |
828 | 0 | } |
829 | 0 | return 0; |
830 | 0 | } |
831 | | |
832 | | SCJsonBuilder *CreateEveHeader(const Packet *p, enum SCOutputJsonLogDirection dir, |
833 | | const char *event_type, JsonAddrInfo *addr, OutputJsonCtx *eve_ctx) |
834 | 7.45M | { |
835 | 7.45M | char timebuf[64]; |
836 | 7.45M | const Flow *f = (const Flow *)p->flow; |
837 | | |
838 | 7.45M | SCJsonBuilder *js = SCJbNewObject(); |
839 | 7.45M | if (unlikely(js == NULL)) { |
840 | 0 | return NULL; |
841 | 0 | } |
842 | | |
843 | 7.45M | CreateIsoTimeString(p->ts, timebuf, sizeof(timebuf)); |
844 | | |
845 | 7.45M | SCJbSetString(js, "timestamp", timebuf); |
846 | | |
847 | 7.45M | CreateEveFlowId(js, f); |
848 | | |
849 | | /* sensor id */ |
850 | 7.45M | if (sensor_id >= 0) { |
851 | 0 | SCJbSetUint(js, "sensor_id", sensor_id); |
852 | 0 | } |
853 | | |
854 | | /* input interface */ |
855 | 7.45M | if (p->livedev) { |
856 | 0 | SCJbSetString(js, "in_iface", p->livedev->dev); |
857 | 0 | } |
858 | | |
859 | | /* pcap_cnt */ |
860 | 7.45M | if (p->pcap_cnt != 0) { |
861 | 7.43M | SCJbSetUint(js, "pcap_cnt", p->pcap_cnt); |
862 | 7.43M | } |
863 | | |
864 | 7.45M | if (event_type) { |
865 | 7.45M | SCJbSetString(js, "event_type", event_type); |
866 | 7.45M | } |
867 | | |
868 | | /* vlan */ |
869 | 7.45M | if (p->vlan_idx > 0) { |
870 | 649 | SCJbOpenArray(js, "vlan"); |
871 | 649 | SCJbAppendUint(js, p->vlan_id[0]); |
872 | 649 | if (p->vlan_idx > 1) { |
873 | 2 | SCJbAppendUint(js, p->vlan_id[1]); |
874 | 2 | } |
875 | 649 | if (p->vlan_idx > 2) { |
876 | 0 | SCJbAppendUint(js, p->vlan_id[2]); |
877 | 0 | } |
878 | 649 | SCJbClose(js); |
879 | 649 | } |
880 | | |
881 | | /* 5-tuple */ |
882 | 7.45M | JsonAddrInfo addr_info = json_addr_info_zero; |
883 | 7.45M | if (addr == NULL) { |
884 | 7.41M | JsonAddrInfoInit(p, dir, &addr_info); |
885 | 7.41M | addr = &addr_info; |
886 | 7.41M | } |
887 | 7.45M | if (addr->src_ip[0] != '\0') { |
888 | 6.74M | SCJbSetString(js, "src_ip", addr->src_ip); |
889 | 6.74M | } |
890 | 7.45M | if (addr->log_port) { |
891 | 6.41M | SCJbSetUint(js, "src_port", addr->sp); |
892 | 6.41M | } |
893 | 7.45M | if (addr->dst_ip[0] != '\0') { |
894 | 6.74M | SCJbSetString(js, "dest_ip", addr->dst_ip); |
895 | 6.74M | } |
896 | 7.45M | if (addr->log_port) { |
897 | 6.41M | SCJbSetUint(js, "dest_port", addr->dp); |
898 | 6.41M | } |
899 | 7.45M | if (addr->proto[0] != '\0') { |
900 | 6.74M | SCJbSetString(js, "proto", addr->proto); |
901 | 6.74M | } |
902 | | |
903 | | /* ip version */ |
904 | 7.45M | if (PacketIsIPv4(p)) { |
905 | 473k | SCJbSetUint(js, "ip_v", 4); |
906 | 6.97M | } else if (PacketIsIPv6(p)) { |
907 | 6.27M | SCJbSetUint(js, "ip_v", 6); |
908 | 6.27M | } |
909 | | |
910 | | /* icmp */ |
911 | 7.45M | switch (p->proto) { |
912 | 3.06k | case IPPROTO_ICMP: |
913 | 3.06k | if (PacketIsICMPv4(p)) { |
914 | 2.90k | SCJbSetUint(js, "icmp_type", p->icmp_s.type); |
915 | 2.90k | SCJbSetUint(js, "icmp_code", p->icmp_s.code); |
916 | 2.90k | } |
917 | 3.06k | break; |
918 | 89.9k | case IPPROTO_ICMPV6: |
919 | 89.9k | if (PacketIsICMPv6(p)) { |
920 | 89.6k | SCJbSetUint(js, "icmp_type", PacketGetICMPv6(p)->type); |
921 | 89.6k | SCJbSetUint(js, "icmp_code", PacketGetICMPv6(p)->code); |
922 | 89.6k | } |
923 | 89.9k | break; |
924 | 7.45M | } |
925 | | |
926 | 7.45M | SCJbSetString(js, "pkt_src", PktSrcToString(p->pkt_src)); |
927 | | |
928 | 7.45M | if (eve_ctx != NULL) { |
929 | 7.45M | EveAddCommonOptions(&eve_ctx->cfg, p, f, js, dir); |
930 | 7.45M | } |
931 | | |
932 | 7.45M | return js; |
933 | 7.45M | } |
934 | | |
935 | | SCJsonBuilder *CreateEveHeaderWithTxId(const Packet *p, enum SCOutputJsonLogDirection dir, |
936 | | const char *event_type, JsonAddrInfo *addr, uint64_t tx_id, OutputJsonCtx *eve_ctx) |
937 | 1.85M | { |
938 | 1.85M | SCJsonBuilder *js = CreateEveHeader(p, dir, event_type, addr, eve_ctx); |
939 | 1.85M | if (unlikely(js == NULL)) |
940 | 0 | return NULL; |
941 | | |
942 | | /* tx id for correlation with other events */ |
943 | 1.85M | SCJbSetUint(js, "tx_id", tx_id); |
944 | | |
945 | 1.85M | return js; |
946 | 1.85M | } |
947 | | |
948 | | int OutputJSONMemBufferCallback(const char *str, size_t size, void *data) |
949 | 0 | { |
950 | 0 | OutputJSONMemBufferWrapper *wrapper = data; |
951 | 0 | MemBuffer **memb = wrapper->buffer; |
952 | |
|
953 | 0 | if (MEMBUFFER_OFFSET(*memb) + size >= MEMBUFFER_SIZE(*memb)) { |
954 | 0 | MemBufferExpand(memb, wrapper->expand_by); |
955 | 0 | } |
956 | |
|
957 | 0 | DEBUG_VALIDATE_BUG_ON(size > UINT32_MAX); |
958 | 0 | MemBufferWriteRaw((*memb), (const uint8_t *)str, (uint32_t)size); |
959 | 0 | return 0; |
960 | 0 | } |
961 | | |
962 | | int OutputJSONBuffer(json_t *js, LogFileCtx *file_ctx, MemBuffer **buffer) |
963 | 0 | { |
964 | 0 | if (file_ctx->sensor_name) { |
965 | 0 | json_object_set_new(js, "host", |
966 | 0 | json_string(file_ctx->sensor_name)); |
967 | 0 | } |
968 | |
|
969 | 0 | if (file_ctx->is_pcap_offline) { |
970 | 0 | json_object_set_new(js, "pcap_filename", json_string(PcapFileGetFilename())); |
971 | 0 | } |
972 | |
|
973 | 0 | if (file_ctx->prefix) { |
974 | 0 | MemBufferWriteRaw((*buffer), (const uint8_t *)file_ctx->prefix, file_ctx->prefix_len); |
975 | 0 | } |
976 | |
|
977 | 0 | OutputJSONMemBufferWrapper wrapper = { |
978 | 0 | .buffer = buffer, |
979 | 0 | .expand_by = JSON_OUTPUT_BUFFER_SIZE |
980 | 0 | }; |
981 | |
|
982 | 0 | int r = json_dump_callback(js, OutputJSONMemBufferCallback, &wrapper, |
983 | 0 | file_ctx->json_flags); |
984 | 0 | if (r != 0) |
985 | 0 | return TM_ECODE_OK; |
986 | | |
987 | 0 | LogFileWrite(file_ctx, *buffer); |
988 | 0 | return 0; |
989 | 0 | } |
990 | | |
991 | | void OutputJsonBuilderBuffer( |
992 | | ThreadVars *tv, const Packet *p, Flow *f, SCJsonBuilder *js, OutputJsonThreadCtx *ctx) |
993 | 16.8M | { |
994 | 16.8M | LogFileCtx *file_ctx = ctx->file_ctx; |
995 | 16.8M | MemBuffer **buffer = &ctx->buffer; |
996 | 16.8M | if (file_ctx->sensor_name) { |
997 | 0 | SCJbSetString(js, "host", file_ctx->sensor_name); |
998 | 0 | } |
999 | | |
1000 | 16.8M | if (file_ctx->is_pcap_offline) { |
1001 | 0 | SCJbSetString(js, "pcap_filename", PcapFileGetFilename()); |
1002 | 0 | } |
1003 | | |
1004 | 16.8M | SCEveRunCallbacks(tv, p, f, js); |
1005 | | |
1006 | 16.8M | SCJbClose(js); |
1007 | | |
1008 | 16.8M | MemBufferReset(*buffer); |
1009 | | |
1010 | 16.8M | if (file_ctx->prefix) { |
1011 | 0 | MemBufferWriteRaw((*buffer), (const uint8_t *)file_ctx->prefix, file_ctx->prefix_len); |
1012 | 0 | } |
1013 | | |
1014 | 16.8M | size_t jslen = SCJbLen(js); |
1015 | 16.8M | DEBUG_VALIDATE_BUG_ON(SCJbLen(js) > UINT32_MAX); |
1016 | 16.8M | size_t remaining = MEMBUFFER_SIZE(*buffer) - MEMBUFFER_OFFSET(*buffer); |
1017 | 16.8M | if (jslen >= remaining) { |
1018 | 77 | size_t expand_by = jslen + 1 - remaining; |
1019 | 77 | if (MemBufferExpand(buffer, (uint32_t)expand_by) < 0) { |
1020 | 0 | if (!ctx->too_large_warning) { |
1021 | | /* Log a warning once, and include enough of the log |
1022 | | * message to hopefully identify the event_type. */ |
1023 | 0 | char partial[120]; |
1024 | 0 | size_t partial_len = MIN(sizeof(partial), jslen); |
1025 | 0 | memcpy(partial, SCJbPtr(js), partial_len - 1); |
1026 | 0 | partial[partial_len - 1] = '\0'; |
1027 | 0 | SCLogWarning("Formatted JSON EVE record too large, will be dropped: %s", partial); |
1028 | 0 | ctx->too_large_warning = true; |
1029 | 0 | } |
1030 | 0 | return; |
1031 | 0 | } |
1032 | 77 | } |
1033 | | |
1034 | 16.8M | MemBufferWriteRaw((*buffer), SCJbPtr(js), (uint32_t)jslen); |
1035 | 16.8M | LogFileWrite(file_ctx, *buffer); |
1036 | 16.8M | } |
1037 | | |
1038 | | static inline enum LogFileType FileTypeFromConf(const char *typestr) |
1039 | 4 | { |
1040 | 4 | enum LogFileType log_filetype = LOGFILE_TYPE_NOTSET; |
1041 | | |
1042 | 4 | if (typestr == NULL) { |
1043 | 0 | log_filetype = LOGFILE_TYPE_FILE; |
1044 | 4 | } else if (strcmp(typestr, "file") == 0 || strcmp(typestr, "regular") == 0) { |
1045 | 4 | log_filetype = LOGFILE_TYPE_FILE; |
1046 | 4 | } else if (strcmp(typestr, "unix_dgram") == 0) { |
1047 | 0 | log_filetype = LOGFILE_TYPE_UNIX_DGRAM; |
1048 | 0 | } else if (strcmp(typestr, "unix_stream") == 0) { |
1049 | 0 | log_filetype = LOGFILE_TYPE_UNIX_STREAM; |
1050 | 0 | } else if (strcmp(typestr, "redis") == 0) { |
1051 | | #ifdef HAVE_LIBHIREDIS |
1052 | | log_filetype = LOGFILE_TYPE_REDIS; |
1053 | | #else |
1054 | 0 | FatalError("redis JSON output option is not compiled"); |
1055 | 0 | #endif |
1056 | 0 | } |
1057 | 4 | SCLogDebug("type %s, file type value %d", typestr, log_filetype); |
1058 | 4 | return log_filetype; |
1059 | 4 | } |
1060 | | |
1061 | | static int LogFileTypePrepare( |
1062 | | OutputJsonCtx *json_ctx, enum LogFileType log_filetype, SCConfNode *conf) |
1063 | 4 | { |
1064 | | |
1065 | 4 | if (log_filetype == LOGFILE_TYPE_FILE || log_filetype == LOGFILE_TYPE_UNIX_DGRAM || |
1066 | 4 | log_filetype == LOGFILE_TYPE_UNIX_STREAM) { |
1067 | 4 | if (SCConfLogOpenGeneric(conf, json_ctx->file_ctx, DEFAULT_LOG_FILENAME, 1) < 0) { |
1068 | 0 | return -1; |
1069 | 0 | } |
1070 | 4 | } |
1071 | | #ifdef HAVE_LIBHIREDIS |
1072 | | else if (log_filetype == LOGFILE_TYPE_REDIS) { |
1073 | | SCLogRedisInit(); |
1074 | | SCConfNode *redis_node = SCConfNodeLookupChild(conf, "redis"); |
1075 | | if (!json_ctx->file_ctx->sensor_name) { |
1076 | | char hostname[1024]; |
1077 | | gethostname(hostname, 1023); |
1078 | | json_ctx->file_ctx->sensor_name = SCStrdup(hostname); |
1079 | | } |
1080 | | if (json_ctx->file_ctx->sensor_name == NULL) { |
1081 | | return -1; |
1082 | | } |
1083 | | |
1084 | | if (SCConfLogOpenRedis(redis_node, json_ctx->file_ctx) < 0) { |
1085 | | return -1; |
1086 | | } |
1087 | | } |
1088 | | #endif |
1089 | 0 | else if (log_filetype == LOGFILE_TYPE_FILETYPE) { |
1090 | 0 | if (json_ctx->file_ctx->threaded) { |
1091 | | /* Prepare for threaded log output. */ |
1092 | 0 | if (!SCLogOpenThreadedFile(NULL, NULL, json_ctx->file_ctx)) { |
1093 | 0 | return -1; |
1094 | 0 | } |
1095 | 0 | } |
1096 | 0 | if (json_ctx->filetype->Init(conf, json_ctx->file_ctx->threaded, |
1097 | 0 | &json_ctx->file_ctx->filetype.init_data) < 0) { |
1098 | 0 | return -1; |
1099 | 0 | } |
1100 | 0 | if (json_ctx->filetype->ThreadInit) { |
1101 | 0 | if (json_ctx->filetype->ThreadInit(json_ctx->file_ctx->filetype.init_data, 0, |
1102 | 0 | &json_ctx->file_ctx->filetype.thread_data) < 0) { |
1103 | 0 | return -1; |
1104 | 0 | } |
1105 | 0 | } |
1106 | 0 | json_ctx->file_ctx->filetype.filetype = json_ctx->filetype; |
1107 | 0 | } |
1108 | | |
1109 | 4 | return 0; |
1110 | 4 | } |
1111 | | |
1112 | | /** |
1113 | | * \brief Create a new LogFileCtx for "fast" output style. |
1114 | | * \param conf The configuration node for this output. |
1115 | | * \return A LogFileCtx pointer on success, NULL on failure. |
1116 | | */ |
1117 | | OutputInitResult OutputJsonInitCtx(SCConfNode *conf) |
1118 | 2 | { |
1119 | 2 | OutputInitResult result = { NULL, false }; |
1120 | 2 | OutputCtx *output_ctx = NULL; |
1121 | | |
1122 | 2 | OutputJsonCtx *json_ctx = SCCalloc(1, sizeof(OutputJsonCtx)); |
1123 | 2 | if (unlikely(json_ctx == NULL)) { |
1124 | 0 | SCLogDebug("could not create new OutputJsonCtx"); |
1125 | 0 | return result; |
1126 | 0 | } |
1127 | | |
1128 | | /* First lookup a sensor-name value in this outputs configuration |
1129 | | * node (deprecated). If that fails, lookup the global one. */ |
1130 | 2 | const char *sensor_name = SCConfNodeLookupChildValue(conf, "sensor-name"); |
1131 | 2 | if (sensor_name != NULL) { |
1132 | 0 | SCLogWarning("Found deprecated eve-log setting \"sensor-name\". " |
1133 | 0 | "Please set sensor-name globally."); |
1134 | 0 | } |
1135 | 2 | else { |
1136 | 2 | (void)SCConfGet("sensor-name", &sensor_name); |
1137 | 2 | } |
1138 | | |
1139 | 2 | json_ctx->file_ctx = LogFileNewCtx(); |
1140 | 2 | if (unlikely(json_ctx->file_ctx == NULL)) { |
1141 | 0 | SCLogDebug("AlertJsonInitCtx: Could not create new LogFileCtx"); |
1142 | 0 | goto error_exit; |
1143 | 0 | } |
1144 | | |
1145 | 2 | if (sensor_name) { |
1146 | 0 | json_ctx->file_ctx->sensor_name = SCStrdup(sensor_name); |
1147 | 0 | if (json_ctx->file_ctx->sensor_name == NULL) { |
1148 | 0 | goto error_exit; |
1149 | 0 | } |
1150 | 2 | } else { |
1151 | 2 | json_ctx->file_ctx->sensor_name = NULL; |
1152 | 2 | } |
1153 | | |
1154 | 2 | output_ctx = SCCalloc(1, sizeof(OutputCtx)); |
1155 | 2 | if (unlikely(output_ctx == NULL)) { |
1156 | 0 | goto error_exit; |
1157 | 0 | } |
1158 | | |
1159 | 2 | output_ctx->data = json_ctx; |
1160 | 2 | output_ctx->DeInit = OutputJsonDeInitCtx; |
1161 | | |
1162 | 2 | if (conf) { |
1163 | 2 | const char *output_s = SCConfNodeLookupChildValue(conf, "filetype"); |
1164 | | // Backwards compatibility |
1165 | 2 | if (output_s == NULL) { |
1166 | 0 | output_s = SCConfNodeLookupChildValue(conf, "type"); |
1167 | 0 | } |
1168 | | |
1169 | 2 | enum LogFileType log_filetype = FileTypeFromConf(output_s); |
1170 | 2 | if (log_filetype == LOGFILE_TYPE_NOTSET) { |
1171 | 0 | SCEveFileType *filetype = SCEveFindFileType(output_s); |
1172 | 0 | if (filetype != NULL) { |
1173 | 0 | log_filetype = LOGFILE_TYPE_FILETYPE; |
1174 | 0 | json_ctx->filetype = filetype; |
1175 | 0 | } else |
1176 | 0 | FatalError("Invalid JSON output option: %s", output_s); |
1177 | 0 | } |
1178 | | |
1179 | 2 | const char *prefix = SCConfNodeLookupChildValue(conf, "prefix"); |
1180 | 2 | if (prefix != NULL) |
1181 | 0 | { |
1182 | 0 | SCLogInfo("Using prefix '%s' for JSON messages", prefix); |
1183 | 0 | json_ctx->file_ctx->prefix = SCStrdup(prefix); |
1184 | 0 | if (json_ctx->file_ctx->prefix == NULL) |
1185 | 0 | { |
1186 | 0 | FatalError("Failed to allocate memory for eve-log.prefix setting."); |
1187 | 0 | } |
1188 | 0 | json_ctx->file_ctx->prefix_len = (uint32_t)strlen(prefix); |
1189 | 0 | } |
1190 | | |
1191 | | /* Threaded file output */ |
1192 | 2 | const SCConfNode *threaded = SCConfNodeLookupChild(conf, "threaded"); |
1193 | 2 | if (threaded && threaded->val && SCConfValIsTrue(threaded->val)) { |
1194 | 0 | SCLogConfig("Threaded EVE logging configured"); |
1195 | 0 | json_ctx->file_ctx->threaded = true; |
1196 | 2 | } else { |
1197 | 2 | json_ctx->file_ctx->threaded = false; |
1198 | 2 | } |
1199 | 2 | if (LogFileTypePrepare(json_ctx, log_filetype, conf) < 0) { |
1200 | 0 | goto error_exit; |
1201 | 0 | } |
1202 | | |
1203 | 2 | const char *sensor_id_s = SCConfNodeLookupChildValue(conf, "sensor-id"); |
1204 | 2 | if (sensor_id_s != NULL) { |
1205 | 0 | if (StringParseUint64((uint64_t *)&sensor_id, 10, 0, sensor_id_s) < 0) { |
1206 | 0 | FatalError("Failed to initialize JSON output, " |
1207 | 0 | "invalid sensor-id: %s", |
1208 | 0 | sensor_id_s); |
1209 | 0 | } |
1210 | 0 | } |
1211 | | |
1212 | | /* Check if top-level metadata should be logged. */ |
1213 | 2 | const SCConfNode *metadata = SCConfNodeLookupChild(conf, "metadata"); |
1214 | 2 | if (metadata && metadata->val && SCConfValIsFalse(metadata->val)) { |
1215 | 0 | SCLogConfig("Disabling eve metadata logging."); |
1216 | 0 | json_ctx->cfg.include_metadata = false; |
1217 | 2 | } else { |
1218 | 2 | json_ctx->cfg.include_metadata = true; |
1219 | 2 | } |
1220 | | |
1221 | | /* Check if ethernet information should be logged. */ |
1222 | 2 | const SCConfNode *ethernet = SCConfNodeLookupChild(conf, "ethernet"); |
1223 | 2 | if (ethernet && ethernet->val && SCConfValIsTrue(ethernet->val)) { |
1224 | 0 | SCLogConfig("Enabling Ethernet MAC address logging."); |
1225 | 0 | json_ctx->cfg.include_ethernet = true; |
1226 | 2 | } else { |
1227 | 2 | json_ctx->cfg.include_ethernet = false; |
1228 | 2 | } |
1229 | | |
1230 | 2 | const SCConfNode *suriver = SCConfNodeLookupChild(conf, "suricata-version"); |
1231 | 2 | if (suriver && suriver->val && SCConfValIsTrue(suriver->val)) { |
1232 | 0 | SCLogConfig("Enabling Suricata version logging."); |
1233 | 0 | json_ctx->cfg.include_suricata_version = true; |
1234 | 2 | } else { |
1235 | 2 | json_ctx->cfg.include_suricata_version = false; |
1236 | 2 | } |
1237 | | |
1238 | | /* See if we want to enable the community id */ |
1239 | 2 | const SCConfNode *community_id = SCConfNodeLookupChild(conf, "community-id"); |
1240 | 2 | if (community_id && community_id->val && SCConfValIsTrue(community_id->val)) { |
1241 | 0 | SCLogConfig("Enabling eve community_id logging."); |
1242 | 0 | json_ctx->cfg.include_community_id = true; |
1243 | 2 | } else { |
1244 | 2 | json_ctx->cfg.include_community_id = false; |
1245 | 2 | } |
1246 | 2 | const char *cid_seed = SCConfNodeLookupChildValue(conf, "community-id-seed"); |
1247 | 2 | if (cid_seed != NULL) { |
1248 | 0 | if (StringParseUint16(&json_ctx->cfg.community_id_seed, |
1249 | 0 | 10, 0, cid_seed) < 0) |
1250 | 0 | { |
1251 | 0 | FatalError("Failed to initialize JSON output, " |
1252 | 0 | "invalid community-id-seed: %s", |
1253 | 0 | cid_seed); |
1254 | 0 | } |
1255 | 0 | } |
1256 | | |
1257 | | /* Do we have a global eve xff configuration? */ |
1258 | 2 | const SCConfNode *xff = SCConfNodeLookupChild(conf, "xff"); |
1259 | 2 | if (xff != NULL) { |
1260 | 2 | json_ctx->xff_cfg = SCCalloc(1, sizeof(HttpXFFCfg)); |
1261 | 2 | if (likely(json_ctx->xff_cfg != NULL)) { |
1262 | 2 | HttpXFFGetCfg(conf, json_ctx->xff_cfg); |
1263 | 2 | } |
1264 | 2 | } |
1265 | | |
1266 | 2 | const char *pcapfile_s = SCConfNodeLookupChildValue(conf, "pcap-file"); |
1267 | 2 | if (pcapfile_s != NULL && SCConfValIsTrue(pcapfile_s)) { |
1268 | 0 | json_ctx->file_ctx->is_pcap_offline = |
1269 | 0 | (SCRunmodeGet() == RUNMODE_PCAP_FILE || SCRunmodeGet() == RUNMODE_UNIX_SOCKET); |
1270 | 0 | } |
1271 | 2 | json_ctx->file_ctx->type = log_filetype; |
1272 | 2 | } |
1273 | | |
1274 | 2 | SCLogDebug("returning output_ctx %p", output_ctx); |
1275 | | |
1276 | 2 | result.ctx = output_ctx; |
1277 | 2 | result.ok = true; |
1278 | 2 | return result; |
1279 | | |
1280 | 0 | error_exit: |
1281 | 0 | if (json_ctx->file_ctx) { |
1282 | 0 | if (json_ctx->file_ctx->prefix) { |
1283 | 0 | SCFree(json_ctx->file_ctx->prefix); |
1284 | 0 | } |
1285 | 0 | LogFileFreeCtx(json_ctx->file_ctx); |
1286 | 0 | } |
1287 | 0 | SCFree(json_ctx); |
1288 | |
|
1289 | 0 | if (output_ctx) { |
1290 | 0 | SCFree(output_ctx); |
1291 | 0 | } |
1292 | 0 | return result; |
1293 | 2 | } |
1294 | | |
1295 | | static void OutputJsonDeInitCtx(OutputCtx *output_ctx) |
1296 | 0 | { |
1297 | 0 | OutputJsonCtx *json_ctx = (OutputJsonCtx *)output_ctx->data; |
1298 | 0 | LogFileCtx *logfile_ctx = json_ctx->file_ctx; |
1299 | 0 | if (logfile_ctx->dropped) { |
1300 | 0 | SCLogWarning("%" PRIu64 " events were dropped due to slow or " |
1301 | 0 | "disconnected socket", |
1302 | 0 | logfile_ctx->dropped); |
1303 | 0 | } |
1304 | 0 | if (json_ctx->xff_cfg != NULL) { |
1305 | 0 | SCFree(json_ctx->xff_cfg); |
1306 | 0 | } |
1307 | 0 | LogFileFreeCtx(logfile_ctx); |
1308 | 0 | SCFree(json_ctx); |
1309 | 0 | SCFree(output_ctx); |
1310 | 0 | } |