Coverage Report

Created: 2026-05-14 06:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wireshark/epan/dissectors/packet-gelf.c
Line
Count
Source
1
/* packet-gelf.c
2
 * Routines for Graylog Extended Log Format (GELF) dissection
3
 *
4
 * Slava Bacherikov <slava@bacher09.org>
5
 *
6
 * Wireshark - Network traffic analyzer
7
 * By Gerald Combs <gerald@wireshark.org>
8
 * Copyright 1998
9
 *
10
 * SPDX-License-Identifier: GPL-2.0-or-later
11
 */
12
13
#include "config.h"
14
15
#include <epan/packet.h>
16
#include <epan/expert.h>
17
#include <epan/to_str.h>
18
#include <epan/reassemble.h>
19
20
0
#define HEADER_GZIP 0x1f8b
21
0
#define HEADER_CHUNKED 0x1e0f
22
/* not sure if this really used
23
seen this here: https://github.com/lusis/gelfd/blob/229cf5f1f913a35db648b195300d1aaae841d522/lib/gelfd.rb#L7 */
24
0
#define HEADER_UNCOMPRESSED 0x1f3c
25
0
#define HEADER_UNCOMPRESSED_PLAIN 0x7b22 // json payload without real header
26
27
/* minimal size of json message with only required fields */
28
0
#define MIN_PLAIN_MSG 48
29
0
#define MIN_ZLIB_MSG  46
30
31
/* make 32 bit message id from 64 bit message id */
32
0
#define BUILD_MESSAGE_ID(X) ((X[3] << 3 | X[2] << 2 | X[1] << 1 | X[0]) ^ \
33
0
                            (X[4] << 3 | X[5] << 2 | X[6] << 1 | X[7]))
34
35
36
void proto_register_gelf(void);
37
void proto_reg_handoff_gelf(void);
38
39
static dissector_handle_t json_handle;
40
static int proto_gelf;
41
static dissector_handle_t gelf_udp_handle;
42
43
static int ett_gelf;
44
static int hf_gelf_pdu_type;
45
static int hf_gelf_pdu_message_id;
46
static int hf_gelf_pdu_chunk_number;
47
static int hf_gelf_pdu_chunk_count;
48
static int hf_gelf_pdu_chunked;
49
50
static const value_string gelf_udp_types[] = {
51
    { HEADER_GZIP, "gzip" },
52
    { 0x7801, "zlib" },
53
    { 0x785e, "zlib" },
54
    { 0x789c, "zlib" },
55
    { 0x78da, "zlib" },
56
    { HEADER_CHUNKED, "chunked" },
57
    { HEADER_UNCOMPRESSED, "uncompressed" },
58
    { HEADER_UNCOMPRESSED_PLAIN, "uncompressed plain json" },
59
    { 0, NULL }
60
};
61
62
static reassembly_table gelf_udp_reassembly_table;
63
64
static int ett_gelf_fragment;
65
static int ett_gelf_fragments;
66
67
static int hf_gelf_fragments;
68
static int hf_gelf_fragment;
69
static int hf_gelf_fragment_overlap;
70
static int hf_gelf_fragment_overlap_conflict;
71
static int hf_gelf_fragment_multiple_tails;
72
static int hf_gelf_fragment_too_long_fragment;
73
static int hf_gelf_fragment_error;
74
static int hf_gelf_fragment_count;
75
static int hf_gelf_reassembled_in;
76
static int hf_gelf_reassembled_length;
77
78
static const fragment_items gelf_fragment_items = {
79
    &ett_gelf_fragment,
80
    &ett_gelf_fragments,
81
    &hf_gelf_fragments,
82
    &hf_gelf_fragment,
83
    &hf_gelf_fragment_overlap,
84
    &hf_gelf_fragment_overlap_conflict,
85
    &hf_gelf_fragment_multiple_tails,
86
    &hf_gelf_fragment_too_long_fragment,
87
    &hf_gelf_fragment_error,
88
    &hf_gelf_fragment_count,
89
    &hf_gelf_reassembled_in,
90
    &hf_gelf_reassembled_length,
91
    NULL,
92
    "GELF fragments"
93
};
94
95
static expert_field ei_gelf_invalid_header;
96
static expert_field ei_gelf_broken_compression;
97
98
static inline bool
99
0
is_simple_zlib(uint16_t header) {
100
0
    return header == 0x7801 || header == 0x785e || header == 0x789c || header == 0x78da;
101
0
}
102
103
static int
104
dissect_gelf_simple_udp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, uint16_t header,
105
                        proto_item* pdu_item)
106
0
{
107
0
    int len;
108
0
    tvbuff_t *next_tvb;
109
110
0
    len = tvb_captured_length(tvb);
111
0
    if (header == HEADER_GZIP || is_simple_zlib(header)) {
112
0
        next_tvb = tvb_child_uncompress_zlib(tvb, tvb, 0, len);
113
0
        if (next_tvb) {
114
0
            add_new_data_source(pinfo, next_tvb, "compressed data");
115
0
            call_dissector(json_handle, next_tvb, pinfo, tree);
116
0
        } else {
117
0
            expert_add_info(pinfo, pdu_item, &ei_gelf_broken_compression);
118
0
        }
119
0
        return len;
120
0
    } else if (header == HEADER_UNCOMPRESSED) {
121
0
        next_tvb = tvb_new_subset_remaining(tvb, 2);
122
0
        if (next_tvb) {
123
0
            call_dissector(json_handle, next_tvb, pinfo, tree);
124
0
        }
125
0
        return len;
126
0
    } else if (header == HEADER_UNCOMPRESSED_PLAIN) {
127
0
        if (call_dissector(json_handle, tvb, pinfo, tree) == 0) {
128
0
            return 0;
129
0
        }
130
0
        return len;
131
0
    }
132
0
    return 0;
133
0
}
134
135
static int
136
dissect_gelf(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, bool heur_check)
137
0
{
138
0
    uint16_t header;
139
0
    unsigned captured_length;
140
0
    proto_item *it;
141
142
0
    captured_length = tvb_captured_length(tvb);
143
144
0
    if (captured_length < 2)
145
0
        return 0;
146
147
0
    header = tvb_get_ntohs(tvb, 0);
148
149
0
    if (heur_check) {
150
0
        unsigned min_len;
151
0
        uint8_t number, count;
152
153
0
        switch(header) {
154
0
            case HEADER_GZIP:
155
0
                min_len = MIN_ZLIB_MSG;
156
0
                break;
157
0
            case HEADER_UNCOMPRESSED_PLAIN:
158
0
                min_len = MIN_PLAIN_MSG;
159
0
                break;
160
0
            case HEADER_UNCOMPRESSED:
161
0
                min_len = MIN_PLAIN_MSG + 2;
162
0
                break;
163
0
            case HEADER_CHUNKED:
164
                /* 10 bytes is chunked header + 2 bytes of data */
165
0
                min_len = 10 + 2;
166
0
                break;
167
0
            default:
168
0
                if (is_simple_zlib(header)) {
169
0
                    min_len = MIN_ZLIB_MSG;
170
0
                } else {
171
0
                    return 0;
172
0
                }
173
0
                break;
174
0
        }
175
176
0
        if (tvb_reported_length(tvb) < min_len)
177
0
            return 0;
178
179
0
        if (header == HEADER_CHUNKED && captured_length >= 10) {
180
0
            number = tvb_get_uint8(tvb, 10);
181
0
            count = tvb_get_uint8(tvb, 11);
182
0
            if (number >= count)
183
0
                return 0;
184
0
        }
185
0
    }
186
187
188
0
    proto_item *ti = proto_tree_add_item(tree, proto_gelf, tvb, 0, -1, ENC_NA);
189
0
    proto_tree *gelf_tree = proto_item_add_subtree(ti, ett_gelf);
190
0
    proto_item *pdu_item = proto_tree_add_item(gelf_tree, hf_gelf_pdu_type, tvb, 0, 2, ENC_BIG_ENDIAN);
191
0
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "GELF");
192
193
0
    if (header == HEADER_CHUNKED) {
194
0
        uint32_t number, count, short_id, data_len;
195
0
        GByteArray *bytes;
196
0
        char message_id[17];
197
0
        bool more_frags;
198
0
        fragment_head *fd_head;
199
200
0
        message_id[0] = '\0';
201
0
        bytes = g_byte_array_sized_new(8);
202
203
0
        it = proto_tree_add_boolean(gelf_tree, hf_gelf_pdu_chunked, tvb, 0, 2, true);
204
0
        proto_item_set_generated(it);
205
0
        proto_tree_add_bytes_item(gelf_tree, hf_gelf_pdu_message_id, tvb, 2, 8, ENC_BIG_ENDIAN, bytes,
206
0
                                  NULL, NULL);
207
0
        proto_tree_add_item_ret_uint(gelf_tree, hf_gelf_pdu_chunk_number, tvb, 10, 1, ENC_BIG_ENDIAN,
208
0
                                     &number);
209
0
        proto_tree_add_item_ret_uint(gelf_tree, hf_gelf_pdu_chunk_count, tvb, 11, 1, ENC_BIG_ENDIAN,
210
0
                                     &count);
211
0
        bytes_to_hexstr(message_id, bytes->data, 8);
212
0
        message_id[16] = '\0';
213
        // HACK: convert 64 bit message id to 32 bit :)
214
0
        short_id = BUILD_MESSAGE_ID(bytes->data);
215
0
        g_byte_array_free(bytes, true);
216
0
        col_add_fstr(pinfo->cinfo, COL_INFO, "Chunked packet: id: %s, number %u, count %u", message_id,
217
0
                     number, count);
218
0
        data_len = tvb_captured_length_remaining(tvb, 12);
219
0
        more_frags = (count == number + 1) ? false : true;
220
0
        fd_head = fragment_add_seq_check(&gelf_udp_reassembly_table, tvb, 12, pinfo, short_id, NULL, number,
221
0
                                         data_len, more_frags);
222
0
        if (fd_head != NULL) {
223
0
            tvbuff_t *newtvb;
224
0
            newtvb = process_reassembled_data(tvb, 12, pinfo, "Reassembled GELF", fd_head,
225
0
                                              &gelf_fragment_items, NULL, gelf_tree);
226
0
            if (newtvb != NULL) {
227
0
                uint16_t newheader = tvb_get_ntohs(newtvb, 0);
228
0
                dissect_gelf_simple_udp(newtvb, pinfo, tree, newheader, pdu_item);
229
0
           }
230
0
        }
231
0
        return captured_length;
232
0
    } else {
233
0
        it = proto_tree_add_boolean(gelf_tree, hf_gelf_pdu_chunked, tvb, 0, 2, false);
234
0
        proto_item_set_generated(it);
235
236
0
        switch(header) {
237
0
            case HEADER_GZIP:
238
0
                col_set_str(pinfo->cinfo, COL_INFO, "GZIP");
239
0
                break;
240
0
            case HEADER_UNCOMPRESSED_PLAIN:
241
0
                col_set_str(pinfo->cinfo, COL_INFO, "uncompressed plain");
242
0
                break;
243
0
            case HEADER_UNCOMPRESSED:
244
0
                col_set_str(pinfo->cinfo, COL_INFO, "uncompressed");
245
0
                break;
246
0
            default:
247
0
                if (is_simple_zlib(header)) {
248
0
                    col_set_str(pinfo->cinfo, COL_INFO, "ZLIB");
249
0
                } else {
250
0
                    expert_add_info_format(pinfo, pdu_item, &ei_gelf_invalid_header,
251
0
                                           "Invalid header magic");
252
0
                    return 0;
253
0
                }
254
0
                break;
255
0
        }
256
257
0
        return dissect_gelf_simple_udp(tvb, pinfo, tree, header, pdu_item);
258
0
    }
259
0
}
260
261
static int
262
0
dissect_gelf_udp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) {
263
0
    return dissect_gelf(tvb, pinfo, tree, false);
264
0
}
265
266
static bool
267
dissect_gelf_heur_udp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
268
0
{
269
0
    if (dissect_gelf(tvb, pinfo, tree, true) > 0) {
270
0
        return true;
271
0
    } else {
272
0
        return false;
273
0
    }
274
0
}
275
276
void
277
proto_register_gelf(void)
278
15
{
279
15
    static  hf_register_info hf[] = {
280
15
        { &hf_gelf_pdu_type,
281
15
            {
282
15
                "GELF Type", "gelf.type", FT_UINT16,
283
15
                BASE_HEX, VALS(gelf_udp_types), 0x0,
284
15
                NULL, HFILL
285
15
            }
286
15
        },
287
15
        { &hf_gelf_pdu_message_id,
288
15
            {
289
15
                "Message id", "gelf.chunk.msg_id", FT_BYTES,
290
15
                BASE_NONE, NULL, 0x0,
291
15
                NULL, HFILL
292
15
            }
293
15
        },
294
15
        { &hf_gelf_pdu_chunk_number,
295
15
            {
296
15
                "Chunk number", "gelf.chunk.number", FT_UINT8,
297
15
                BASE_DEC, NULL, 0x0,
298
15
                NULL, HFILL
299
15
            }
300
15
        },
301
15
        { &hf_gelf_pdu_chunk_count,
302
15
            {
303
15
                "Chunk count", "gelf.chunk.count", FT_UINT8,
304
15
                BASE_DEC, NULL, 0x0,
305
15
                NULL, HFILL
306
15
            }
307
15
        },
308
15
        { &hf_gelf_pdu_chunked,
309
15
            {
310
15
                "Chunked message", "gelf.chunked", FT_BOOLEAN,
311
15
                BASE_NONE, NULL, 0x0,
312
15
                NULL, HFILL
313
15
            }
314
15
        }
315
15
        /* Fragmentation */,
316
15
        { &hf_gelf_fragments,
317
15
            {
318
15
                "GELF fragments", "gelf.fragments", FT_NONE, BASE_NONE,
319
15
                NULL, 0x00, NULL, HFILL
320
15
            }
321
15
        },
322
15
        { &hf_gelf_fragment,
323
15
            {
324
15
                "GELF fragment", "gelf.fragment", FT_FRAMENUM, BASE_NONE,
325
15
                NULL, 0x00, NULL, HFILL
326
15
            }
327
15
        },
328
15
        { &hf_gelf_fragment_overlap,
329
15
            {
330
15
                "GELF fragment overlap", "gelf.fragment.overlap", FT_BOOLEAN,
331
15
                BASE_NONE, NULL, 0x00, NULL, HFILL
332
15
            }
333
15
        },
334
15
        { &hf_gelf_fragment_overlap_conflict,
335
15
            {
336
15
                "GELF fragment overlapping with conflicting data",
337
15
                "gelf.fragment.overlap.conflicts", FT_BOOLEAN, BASE_NONE,
338
15
                NULL, 0x00, NULL, HFILL
339
15
            }
340
15
        },
341
15
        { &hf_gelf_fragment_multiple_tails,
342
15
            {
343
15
                "GELF has multiple tail fragments",
344
15
                "gelf.fragment.multiple_tails", FT_BOOLEAN, BASE_NONE,
345
15
                NULL, 0x00, NULL, HFILL
346
15
            }
347
15
        },
348
15
        { &hf_gelf_fragment_too_long_fragment,
349
15
            {
350
15
                "GELF fragment too long", "gelf.fragment.too_long_fragment",
351
15
                FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL
352
15
            }
353
15
        },
354
15
        { &hf_gelf_fragment_error,
355
15
            {
356
15
                "GELF defragmentation error", "gelf.fragment.error", FT_FRAMENUM,
357
15
                BASE_NONE, NULL, 0x00, NULL, HFILL
358
15
            }
359
15
        },
360
15
        { &hf_gelf_fragment_count,
361
15
            {
362
15
                "GELF fragment count", "gelf.fragment.count", FT_UINT32, BASE_DEC,
363
15
                NULL, 0x00, NULL, HFILL
364
15
            }
365
15
        },
366
15
        { &hf_gelf_reassembled_in,
367
15
            {
368
15
                "Reassembled GELF in frame", "gelf.reassembled.in", FT_FRAMENUM, BASE_NONE,
369
15
                NULL, 0x00, "This GELF packet is reassembled in this frame", HFILL
370
15
            }
371
15
        },
372
15
        { &hf_gelf_reassembled_length,
373
15
            {
374
15
                "Reassembled GELF length", "gelf.reassembled.length", FT_UINT32, BASE_DEC,
375
15
                NULL, 0x00, "The total length of the reassembled payload", HFILL
376
15
            }
377
15
        },
378
15
    };
379
380
15
    static ei_register_info ei_gelf[] = {
381
15
        { &ei_gelf_invalid_header,
382
15
            {
383
15
                "gelf.invalid_header", PI_MALFORMED, PI_ERROR, "Invalid header", EXPFILL
384
15
            }
385
15
        },
386
15
        { &ei_gelf_broken_compression,
387
15
            {
388
15
                "gelf.broken_compression", PI_MALFORMED, PI_ERROR, "Can't uncompress message", EXPFILL
389
15
            }
390
15
        }
391
15
    };
392
393
15
    static int *ett[] = {
394
15
        &ett_gelf,
395
15
        &ett_gelf_fragment,
396
15
        &ett_gelf_fragments
397
15
    };
398
399
15
    expert_module_t *expert_gelf;
400
401
15
    proto_gelf = proto_register_protocol("Graylog Extended Log Format", "GELF", "gelf");
402
15
    gelf_udp_handle = register_dissector("gelf-udp", dissect_gelf_udp, proto_gelf);
403
15
    proto_register_field_array(proto_gelf, hf, array_length(hf));
404
15
    proto_register_subtree_array(ett, array_length(ett));
405
15
    expert_gelf = expert_register_protocol(proto_gelf);
406
15
    expert_register_field_array(expert_gelf, ei_gelf, array_length(ei_gelf));
407
15
    reassembly_table_register(&gelf_udp_reassembly_table, &addresses_reassembly_table_functions);
408
15
}
409
410
411
void
412
proto_reg_handoff_gelf(void)
413
15
{
414
15
    dissector_add_for_decode_as("udp.port", gelf_udp_handle);
415
15
    heur_dissector_add("udp", dissect_gelf_heur_udp,  "GELF over UDP", "gelf_udp", proto_gelf,
416
15
                       HEURISTIC_DISABLE);
417
15
    json_handle = find_dissector_add_dependency("json", proto_gelf);
418
15
}
419
420
/*
421
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
422
 *
423
 * Local variables:
424
 * c-basic-offset: 4
425
 * tab-width: 8
426
 * indent-tabs-mode: nil
427
 * End:
428
 *
429
 * vi: set shiftwidth=4 tabstop=8 expandtab:
430
 * :indentSize=4:tabSize=8:noTabs=true:
431
 */