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