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