Coverage Report

Created: 2025-12-27 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wireshark/epan/dissectors/packet-collectd.c
Line
Count
Source
1
/* packet-collectd.c
2
 * Routines for collectd (http://collectd.org/) network plugin dissection
3
 *
4
 * https://github.com/collectd/collectd/wiki/Binary-protocol
5
 *
6
 * Copyright 2008 Bruno Premont <bonbons at linux-vserver.org>
7
 * Copyright 2009-2013 Florian Forster <octo at collectd.org>
8
 *
9
 * Wireshark - Network traffic analyzer
10
 * By Gerald Combs <gerald@wireshark.org>
11
 * Copyright 1998 Gerald Combs
12
 *
13
 * SPDX-License-Identifier: GPL-2.0-or-later
14
 */
15
16
#include "config.h"
17
18
#include <epan/packet.h>
19
#include <epan/expert.h>
20
#include <epan/proto_data.h>
21
#include <epan/stats_tree.h>
22
#include <epan/to_str.h>
23
#include <epan/uat.h>
24
#include <epan/exceptions.h>
25
26
#include <wsutil/str_util.h>
27
#include <wsutil/wsgcrypt.h>
28
29
0
#define STR_NONNULL(str) ((str) ? ((const char*)str) : "(null)")
30
31
0
#define TYPE_HOST            0x0000
32
0
#define TYPE_TIME            0x0001
33
0
#define TYPE_TIME_HR         0x0008
34
0
#define TYPE_PLUGIN          0x0002
35
0
#define TYPE_PLUGIN_INSTANCE 0x0003
36
0
#define TYPE_TYPE            0x0004
37
0
#define TYPE_TYPE_INSTANCE   0x0005
38
0
#define TYPE_VALUES          0x0006
39
0
#define TYPE_INTERVAL        0x0007
40
0
#define TYPE_INTERVAL_HR     0x0009
41
0
#define TYPE_MESSAGE         0x0100
42
0
#define TYPE_SEVERITY        0x0101
43
0
#define TYPE_SIGN_SHA256     0x0200
44
0
#define TYPE_ENCR_AES256     0x0210
45
46
void proto_register_collectd(void);
47
48
static dissector_handle_t collectd_handle;
49
50
6
#define TAP_DATA_KEY 0
51
6
#define COL_DATA_KEY 1
52
53
typedef struct value_data_s {
54
  const char *host;
55
  int host_off;
56
  int host_len;
57
  uint64_t time_value;
58
  int time_off;
59
  uint64_t interval;
60
  int interval_off;
61
  const char *plugin;
62
  int plugin_off;
63
  int plugin_len;
64
  const char *plugin_instance;
65
  int plugin_instance_off;
66
  int plugin_instance_len;
67
  const char *type;
68
  int type_off;
69
  int type_len;
70
  const char *type_instance;
71
  int type_instance_off;
72
  int type_instance_len;
73
} value_data_t;
74
75
typedef struct notify_data_s {
76
  const char *host;
77
  int host_off;
78
  int host_len;
79
  uint64_t time_value;
80
  int time_off;
81
  uint64_t severity;
82
  int severity_off;
83
  const char *message;
84
  int message_off;
85
  int message_len;
86
} notify_data_t;
87
88
struct string_counter_s;
89
typedef struct string_counter_s string_counter_t;
90
struct string_counter_s
91
{
92
  const char *string;
93
  int    count;
94
  string_counter_t *next;
95
};
96
97
typedef struct tap_data_s {
98
  int values_num;
99
100
  string_counter_t *hosts;
101
  string_counter_t *plugins;
102
  string_counter_t *types;
103
} tap_data_t;
104
105
typedef struct column_data_s {
106
  unsigned pkt_plugins;
107
  unsigned pkt_values;
108
  unsigned pkt_messages;
109
  unsigned pkt_unknown;
110
  unsigned pkt_errors;
111
112
  const char *pkt_host;
113
} column_data_t;
114
115
static const value_string part_names[] = {
116
  { TYPE_VALUES,          "VALUES" },
117
  { TYPE_TIME,            "TIME" },
118
  { TYPE_TIME_HR,         "TIME_HR" },
119
  { TYPE_INTERVAL,        "INTERVAL" },
120
  { TYPE_INTERVAL_HR,     "INTERVAL_HR" },
121
  { TYPE_HOST,            "HOST" },
122
  { TYPE_PLUGIN,          "PLUGIN" },
123
  { TYPE_PLUGIN_INSTANCE, "PLUGIN_INSTANCE" },
124
  { TYPE_TYPE,            "TYPE" },
125
  { TYPE_TYPE_INSTANCE,   "TYPE_INSTANCE" },
126
  { TYPE_MESSAGE,         "MESSAGE" },
127
  { TYPE_SEVERITY,        "SEVERITY" },
128
  { TYPE_SIGN_SHA256,     "SIGNATURE" },
129
  { TYPE_ENCR_AES256,     "ENCRYPTED_DATA" },
130
  { 0, NULL }
131
};
132
133
0
#define TYPE_VALUE_COUNTER  0x00
134
0
#define TYPE_VALUE_GAUGE    0x01
135
0
#define TYPE_VALUE_DERIVE   0x02
136
0
#define TYPE_VALUE_ABSOLUTE 0x03
137
static const value_string valuetypenames[] = {
138
  { TYPE_VALUE_COUNTER,   "COUNTER" },
139
  { TYPE_VALUE_GAUGE,     "GAUGE" },
140
  { TYPE_VALUE_DERIVE,    "DERIVE" },
141
  { TYPE_VALUE_ABSOLUTE,  "ABSOLUTE" },
142
  { 0, NULL }
143
};
144
145
#define SEVERITY_FAILURE  0x01
146
#define SEVERITY_WARNING  0x02
147
#define SEVERITY_OKAY     0x04
148
static const val64_string severity_names[] = {
149
  { SEVERITY_FAILURE,  "FAILURE" },
150
  { SEVERITY_WARNING,  "WARNING" },
151
  { SEVERITY_OKAY,     "OKAY" },
152
  { 0, NULL }
153
};
154
155
14
#define UDP_PORT_COLLECTD 25826 /* Not IANA registered */
156
157
static int proto_collectd;
158
static int tap_collectd                = -1;
159
160
static int hf_collectd_type;
161
static int hf_collectd_length;
162
static int hf_collectd_data;
163
static int hf_collectd_data_host;
164
static int hf_collectd_data_time;
165
static int hf_collectd_data_interval;
166
static int hf_collectd_data_plugin;
167
static int hf_collectd_data_plugin_inst;
168
static int hf_collectd_data_type;
169
static int hf_collectd_data_type_inst;
170
static int hf_collectd_data_valcnt;
171
static int hf_collectd_val_type;
172
static int hf_collectd_val_counter;
173
static int hf_collectd_val_gauge;
174
static int hf_collectd_val_derive;
175
static int hf_collectd_val_absolute;
176
static int hf_collectd_val_unknown;
177
static int hf_collectd_data_severity;
178
static int hf_collectd_data_message;
179
static int hf_collectd_data_sighash;
180
static int hf_collectd_data_sighash_status;
181
static int hf_collectd_data_initvec;
182
static int hf_collectd_data_username_len;
183
static int hf_collectd_data_username;
184
static int hf_collectd_data_encrypted;
185
186
static int ett_collectd;
187
static int ett_collectd_string;
188
static int ett_collectd_integer;
189
static int ett_collectd_part_value;
190
static int ett_collectd_value;
191
static int ett_collectd_valinfo;
192
static int ett_collectd_signature;
193
static int ett_collectd_encryption;
194
static int ett_collectd_dispatch;
195
static int ett_collectd_invalid_length;
196
static int ett_collectd_unknown;
197
198
static int st_collectd_packets = -1;
199
static int st_collectd_values  = -1;
200
static int st_collectd_values_hosts   = -1;
201
static int st_collectd_values_plugins = -1;
202
static int st_collectd_values_types   = -1;
203
204
static expert_field ei_collectd_type;
205
static expert_field ei_collectd_invalid_length;
206
static expert_field ei_collectd_data_valcnt;
207
static expert_field ei_collectd_garbage;
208
static expert_field ei_collectd_sighash_bad;
209
210
/* Prototype for the handoff function */
211
void proto_reg_handoff_collectd (void);
212
213
typedef struct {
214
  char *username;
215
  char *password;
216
217
  bool cipher_hd_created;
218
  bool md_hd_created;
219
  gcry_cipher_hd_t cipher_hd;
220
  gcry_md_hd_t md_hd;
221
222
} uat_collectd_record_t;
223
224
static uat_collectd_record_t *uat_collectd_records;
225
226
static uat_t *collectd_uat;
227
static unsigned num_uat;
228
229
UAT_CSTRING_CB_DEF(uat_collectd_records, username, uat_collectd_record_t)
230
UAT_CSTRING_CB_DEF(uat_collectd_records, password, uat_collectd_record_t)
231
232
static void*
233
0
uat_collectd_record_copy_cb(void* n, const void* o, size_t size _U_) {
234
0
  uat_collectd_record_t* new_rec = (uat_collectd_record_t *)n;
235
0
  const uat_collectd_record_t* old_rec = (const uat_collectd_record_t *)o;
236
237
0
  new_rec->username = g_strdup(old_rec->username);
238
0
  new_rec->password = g_strdup(old_rec->password);
239
240
0
  new_rec->cipher_hd_created = FALSE;
241
0
  new_rec->md_hd_created = FALSE;
242
243
0
  return new_rec;
244
0
}
245
246
static bool
247
0
uat_collectd_record_update_cb(void* r, char** err _U_) {
248
0
  uat_collectd_record_t* rec = (uat_collectd_record_t *)r;
249
250
0
  if (rec->cipher_hd_created) {
251
0
    gcry_cipher_close(rec->cipher_hd);
252
0
    rec->cipher_hd_created = false;
253
0
  }
254
0
  if (rec->md_hd_created) {
255
0
    gcry_md_close(rec->md_hd);
256
0
    rec->md_hd_created = false;
257
0
  }
258
259
0
  return true;
260
0
}
261
262
static void
263
0
uat_collectd_record_free_cb(void* r) {
264
0
  uat_collectd_record_t* rec = (uat_collectd_record_t *)r;
265
266
0
  g_free(rec->username);
267
0
  g_free(rec->password);
268
269
0
  if (rec->cipher_hd_created) {
270
0
    gcry_cipher_close(rec->cipher_hd);
271
0
    rec->cipher_hd_created = false;
272
0
  }
273
0
  if (rec->md_hd_created) {
274
0
    gcry_md_close(rec->md_hd);
275
0
    rec->md_hd_created = false;
276
0
  }
277
0
}
278
279
static uat_collectd_record_t*
280
collectd_get_record(const char* username)
281
0
{
282
0
  uat_collectd_record_t *record = NULL;
283
0
  for (unsigned i = 0; i < num_uat; ++i) {
284
0
    record = &uat_collectd_records[i];
285
0
    if (strcmp(username, record->username) == 0) {
286
0
      return record;
287
0
    }
288
0
  }
289
0
  return NULL;
290
0
}
291
292
static gcry_cipher_hd_t*
293
collectd_get_cipher(const char* username)
294
0
{
295
0
  uat_collectd_record_t *record = collectd_get_record(username);
296
0
  if (record == NULL) {
297
0
    return NULL;
298
0
  }
299
0
  if (record->cipher_hd_created) {
300
0
    return &record->cipher_hd;
301
0
  }
302
0
  gcry_error_t err;
303
0
  unsigned char password_hash[32];
304
0
  DISSECTOR_ASSERT(record->password);
305
0
  gcry_md_hash_buffer(GCRY_MD_SHA256, password_hash, record->password, strlen(record->password));
306
0
  if (gcry_cipher_open(&record->cipher_hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_OFB, 0)) {
307
0
    gcry_cipher_close(record->cipher_hd);
308
0
    ws_debug("error opening aes256 cipher handle");
309
0
    return NULL;
310
0
  }
311
312
0
  err = gcry_cipher_setkey(record->cipher_hd, password_hash, sizeof(password_hash));
313
0
  if (err != 0) {
314
0
    gcry_cipher_close(record->cipher_hd);
315
0
    ws_debug("error setting key");
316
0
    return NULL;
317
0
  }
318
0
  record->cipher_hd_created = true;
319
0
  return &record->cipher_hd;
320
0
}
321
322
static gcry_md_hd_t*
323
collectd_get_md(const char* username)
324
0
{
325
0
  uat_collectd_record_t *record = collectd_get_record(username);
326
0
  if (record == NULL) {
327
0
    return NULL;
328
0
  }
329
0
  if (record->md_hd_created) {
330
0
    gcry_md_reset(record->md_hd);
331
0
    return &record->md_hd;
332
0
  }
333
0
  gcry_error_t err;
334
0
  DISSECTOR_ASSERT(record->password);
335
0
  err = gcry_md_open(&record->md_hd, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC);
336
0
  if (err != 0) {
337
0
    gcry_md_close(record->md_hd);
338
0
    ws_debug("error opening sha256 message digest handle: %s", gcry_strerror(err));
339
0
    return NULL;
340
0
  }
341
342
0
  err = gcry_md_setkey(record->md_hd, record->password, strlen(record->password));
343
0
  if (err != 0) {
344
0
    gcry_md_close(record->md_hd);
345
0
    ws_debug("error setting key: %s", gcry_strerror(err));
346
0
    return NULL;
347
0
  }
348
0
  record->md_hd_created = true;
349
0
  return &record->md_hd;
350
0
}
351
352
static nstime_t
353
collectd_time_to_nstime (uint64_t t)
354
0
{
355
0
  nstime_t nstime = NSTIME_INIT_ZERO;
356
0
  nstime.secs = (time_t) (t / 1073741824);
357
0
  nstime.nsecs = (int) (((double) (t % 1073741824)) / 1.073741824);
358
359
0
  return (nstime);
360
0
}
361
362
static void
363
collectd_stats_tree_init (stats_tree *st)
364
0
{
365
0
  st_collectd_packets = stats_tree_create_node (st, "Packets", 0, STAT_DT_INT, false);
366
0
  st_collectd_values = stats_tree_create_node (st, "Values", 0, STAT_DT_INT, true);
367
368
0
  st_collectd_values_hosts = stats_tree_create_pivot (st, "By host",
369
0
                 st_collectd_values);
370
0
  st_collectd_values_plugins = stats_tree_create_pivot (st, "By plugin",
371
0
                    st_collectd_values);
372
0
  st_collectd_values_types = stats_tree_create_pivot (st, "By type",
373
0
                  st_collectd_values);
374
0
} /* void collectd_stats_tree_init */
375
376
static tap_packet_status
377
collectd_stats_tree_packet (stats_tree *st, packet_info *pinfo _U_,
378
          epan_dissect_t *edt _U_, const void *user_data, tap_flags_t flags _U_)
379
0
{
380
0
  const tap_data_t *td;
381
0
  string_counter_t *sc;
382
383
0
  td = (const tap_data_t *)user_data;
384
0
  if (td == NULL)
385
0
    return (TAP_PACKET_DONT_REDRAW);
386
387
0
  tick_stat_node (st, "Packets", 0, false);
388
0
  increase_stat_node (st, "Values", 0, true, td->values_num);
389
390
0
  for (sc = td->hosts; sc != NULL; sc = sc->next)
391
0
  {
392
0
    int i;
393
0
    for (i = 0; i < sc->count; i++)
394
0
      stats_tree_tick_pivot (st, st_collectd_values_hosts,
395
0
                 sc->string);
396
0
  }
397
398
0
  for (sc = td->plugins; sc != NULL; sc = sc->next)
399
0
  {
400
0
    int i;
401
0
    for (i = 0; i < sc->count; i++)
402
0
      stats_tree_tick_pivot (st, st_collectd_values_plugins,
403
0
                 sc->string);
404
0
  }
405
406
0
  for (sc = td->types; sc != NULL; sc = sc->next)
407
0
  {
408
0
    int i;
409
0
    for (i = 0; i < sc->count; i++)
410
0
      stats_tree_tick_pivot (st, st_collectd_values_types,
411
0
                 sc->string);
412
0
  }
413
414
0
  return (TAP_PACKET_REDRAW);
415
0
} /* int collectd_stats_tree_packet */
416
417
static void
418
collectd_stats_tree_register (void)
419
14
{
420
14
  stats_tree_register ("collectd", "collectd", "Collectd", 0,
421
14
           collectd_stats_tree_packet,
422
14
           collectd_stats_tree_init, NULL);
423
14
} /* void register_collectd_stat_trees */
424
425
static void
426
collectd_proto_tree_add_assembled_metric (tvbuff_t *tvb,
427
    int offset, int length,
428
    value_data_t const *vdispatch, proto_tree *root)
429
0
{
430
0
  proto_item *root_item;
431
0
  proto_tree *subtree;
432
0
  nstime_t nstime;
433
434
0
  subtree = proto_tree_add_subtree(root, tvb, offset + 6, length - 6,
435
0
      ett_collectd_dispatch, &root_item, "Assembled metric");
436
0
  proto_item_set_generated (root_item);
437
438
0
  proto_tree_add_string (subtree, hf_collectd_data_host, tvb,
439
0
      vdispatch->host_off, vdispatch->host_len,
440
0
      STR_NONNULL (vdispatch->host));
441
442
0
  proto_tree_add_string (subtree, hf_collectd_data_plugin, tvb,
443
0
      vdispatch->plugin_off, vdispatch->plugin_len,
444
0
      STR_NONNULL (vdispatch->plugin));
445
446
0
  if (vdispatch->plugin_instance)
447
0
    proto_tree_add_string (subtree,
448
0
        hf_collectd_data_plugin_inst, tvb,
449
0
        vdispatch->plugin_instance_off,
450
0
        vdispatch->plugin_instance_len,
451
0
        vdispatch->plugin_instance);
452
453
0
  proto_tree_add_string (subtree, hf_collectd_data_type, tvb,
454
0
      vdispatch->type_off, vdispatch->type_len,
455
0
      STR_NONNULL (vdispatch->type));
456
457
0
  if (vdispatch->type_instance)
458
0
    proto_tree_add_string (subtree,
459
0
        hf_collectd_data_type_inst, tvb,
460
0
        vdispatch->type_instance_off,
461
0
        vdispatch->type_instance_len,
462
0
        vdispatch->type_instance);
463
464
0
  nstime = collectd_time_to_nstime (vdispatch->time_value);
465
0
  proto_tree_add_time (subtree, hf_collectd_data_time, tvb,
466
0
      vdispatch->time_off, /* length = */ 8, &nstime);
467
468
0
  nstime = collectd_time_to_nstime (vdispatch->interval);
469
0
  proto_tree_add_time (subtree, hf_collectd_data_interval, tvb,
470
0
      vdispatch->interval_off, /* length = */ 8, &nstime);
471
0
}
472
473
static void
474
collectd_proto_tree_add_assembled_notification (tvbuff_t *tvb,
475
    int offset, int length,
476
    notify_data_t const *ndispatch, proto_tree *root)
477
0
{
478
0
  proto_item *root_item;
479
0
  proto_tree *subtree;
480
0
  nstime_t nstime;
481
482
0
  subtree = proto_tree_add_subtree(root, tvb, offset + 6, length - 6,
483
0
      ett_collectd_dispatch, &root_item, "Assembled notification");
484
0
  proto_item_set_generated (root_item);
485
486
0
  proto_tree_add_string (subtree, hf_collectd_data_host, tvb,
487
0
      ndispatch->host_off, ndispatch->host_len,
488
0
      STR_NONNULL (ndispatch->host));
489
490
0
  nstime = collectd_time_to_nstime (ndispatch->time_value);
491
0
  proto_tree_add_time (subtree, hf_collectd_data_time, tvb,
492
0
      ndispatch->time_off, /* length = */ 8, &nstime);
493
494
0
  proto_tree_add_uint64 (subtree, hf_collectd_data_severity, tvb,
495
0
      ndispatch->severity_off, /* length = */ 8,
496
0
      ndispatch->severity);
497
498
0
  proto_tree_add_string (subtree, hf_collectd_data_message, tvb,
499
0
      ndispatch->message_off, ndispatch->message_len,
500
0
      ndispatch->message);
501
0
}
502
503
static int
504
dissect_collectd_string (tvbuff_t *tvb, packet_info *pinfo, int type_hf,
505
       int offset, int *ret_offset, int *ret_length,
506
       const char **ret_string, proto_tree *tree_root,
507
       proto_item **ret_item)
508
0
{
509
0
  proto_tree *pt;
510
0
  proto_item *pi;
511
0
  int type;
512
0
  int length;
513
0
  int size;
514
515
0
  size = tvb_reported_length_remaining (tvb, offset);
516
0
  if (size < 4)
517
0
  {
518
    /* This should never happen, because `dissect_collectd' checks
519
     * for this condition already. */
520
0
    return (-1);
521
0
  }
522
523
0
  type   = tvb_get_ntohs(tvb, offset);
524
0
  length = tvb_get_ntohs(tvb, offset + 2);
525
526
0
  pt = proto_tree_add_subtree_format(tree_root, tvb, offset, length,
527
0
          ett_collectd_string, &pi, "collectd %s segment: ",
528
0
          val_to_str_const (type, part_names, "UNKNOWN"));
529
530
0
  if (length > size)
531
0
  {
532
0
    proto_item_append_text(pt, "Length = %i <BAD>", length);
533
0
    expert_add_info_format(pinfo, pt, &ei_collectd_invalid_length,
534
0
          "String part with invalid part length: "
535
0
          "Part is longer than rest of package.");
536
0
    return (-1);
537
0
  }
538
539
0
  *ret_offset = offset + 4;
540
0
  *ret_length = length - 4;
541
542
0
  proto_tree_add_uint (pt, hf_collectd_type, tvb, offset, 2, type);
543
0
  proto_tree_add_uint (pt, hf_collectd_length, tvb, offset + 2, 2, length);
544
0
  proto_tree_add_item_ret_string (pt, type_hf, tvb, *ret_offset, *ret_length, ENC_ASCII, pinfo->pool, (const uint8_t**)ret_string);
545
546
0
  proto_item_append_text(pt, "\"%s\"", *ret_string);
547
548
0
  if (ret_item != NULL)
549
0
    *ret_item = pi;
550
551
0
  return 0;
552
0
} /* int dissect_collectd_string */
553
554
static int
555
dissect_collectd_integer (tvbuff_t *tvb, packet_info *pinfo, int type_hf,
556
        int offset, int *ret_offset, uint64_t *ret_value,
557
        proto_tree *tree_root, proto_item **ret_item)
558
0
{
559
0
  proto_tree *pt;
560
0
  proto_item *pi;
561
0
  int type;
562
0
  int length;
563
0
  int size;
564
565
0
  size = tvb_reported_length_remaining (tvb, offset);
566
0
  if (size < 4)
567
0
  {
568
    /* This should never happen, because `dissect_collectd' checks
569
     * for this condition already. */
570
0
    return (-1);
571
0
  }
572
573
0
  type   = tvb_get_ntohs(tvb, offset);
574
0
  length = tvb_get_ntohs(tvb, offset + 2);
575
576
0
  if (size < 12)
577
0
  {
578
0
    pt = proto_tree_add_subtree_format(tree_root, tvb, offset, -1,
579
0
            ett_collectd_integer, NULL, "collectd %s segment: <BAD>",
580
0
            val_to_str_const (type, part_names, "UNKNOWN"));
581
582
0
    proto_tree_add_uint (pt, hf_collectd_type, tvb, offset, 2,
583
0
             type);
584
0
    proto_tree_add_uint (pt, hf_collectd_length, tvb, offset + 2, 2,
585
0
             length);
586
0
    proto_tree_add_expert_format(pt, pinfo, &ei_collectd_garbage, tvb, offset + 4, -1,
587
0
            "Garbage at end of packet: Length = %i <BAD>",
588
0
            size - 4);
589
590
0
    return (-1);
591
0
  }
592
593
0
  if (length != 12)
594
0
  {
595
0
    pt = proto_tree_add_subtree_format(tree_root, tvb, offset, -1,
596
0
            ett_collectd_integer, &pi, "collectd %s segment: <BAD>",
597
0
            val_to_str_const (type, part_names, "UNKNOWN"));
598
599
0
    proto_tree_add_uint (pt, hf_collectd_type, tvb, offset, 2,
600
0
             type);
601
0
    pi = proto_tree_add_uint (pt, hf_collectd_length, tvb,
602
0
            offset + 2, 2, length);
603
0
    expert_add_info_format(pinfo, pi, &ei_collectd_invalid_length,
604
0
          "Invalid length field for an integer part.");
605
606
0
    return (-1);
607
0
  }
608
609
0
  *ret_offset = offset + 4;
610
0
  *ret_value = tvb_get_ntoh64 (tvb, offset + 4);
611
612
  /* Convert the version 4.* time format to the version 5.* time format. */
613
0
  if ((type == TYPE_TIME) || (type == TYPE_INTERVAL))
614
0
    *ret_value *= 1073741824;
615
616
  /* Create an entry in the protocol tree for this part. The value is
617
   * printed depending on the "type" variable: TIME{,_HR} as absolute
618
   * time, INTERVAL{,_HR} as relative time, uint64 otherwise. */
619
0
  if ((type == TYPE_TIME) || (type == TYPE_TIME_HR))
620
0
  {
621
0
    nstime_t nstime;
622
0
    char *strtime;
623
624
0
    nstime = collectd_time_to_nstime (*ret_value);
625
0
    strtime = abs_time_to_str (pinfo->pool, &nstime, ABSOLUTE_TIME_LOCAL, /* show_zone = */ true);
626
0
    pt = proto_tree_add_subtree_format(tree_root, tvb, offset, length,
627
0
            ett_collectd_integer, &pi, "collectd %s segment: %s",
628
0
            val_to_str_const (type, part_names, "UNKNOWN"),
629
0
            STR_NONNULL (strtime));
630
0
  }
631
0
  else if ((type == TYPE_INTERVAL) || (type == TYPE_INTERVAL_HR))
632
0
  {
633
0
    nstime_t nstime;
634
0
    char *strtime;
635
636
0
    nstime = collectd_time_to_nstime (*ret_value);
637
0
    strtime = rel_time_to_str (pinfo->pool, &nstime);
638
0
    pt = proto_tree_add_subtree_format(tree_root, tvb, offset, length,
639
0
            ett_collectd_integer, &pi, "collectd %s segment: %s",
640
0
            val_to_str_const (type, part_names, "UNKNOWN"),
641
0
            strtime);
642
0
  }
643
0
  else
644
0
  {
645
0
    pt = proto_tree_add_subtree_format(tree_root, tvb, offset, length,
646
0
            ett_collectd_integer, &pi, "collectd %s segment: %"PRIu64,
647
0
            val_to_str_const (type, part_names, "UNKNOWN"),
648
0
            *ret_value);
649
0
  }
650
651
0
  if (ret_item != NULL)
652
0
    *ret_item = pi;
653
654
0
  proto_tree_add_uint (pt, hf_collectd_type, tvb, offset, 2, type);
655
0
  proto_tree_add_uint (pt, hf_collectd_length, tvb, offset + 2, 2,
656
0
           length);
657
0
  if ((type == TYPE_TIME) || (type == TYPE_INTERVAL)
658
0
      || (type == TYPE_TIME_HR) || (type == TYPE_INTERVAL_HR))
659
0
  {
660
0
    nstime_t nstime;
661
662
0
    nstime = collectd_time_to_nstime (*ret_value);
663
0
    proto_tree_add_time (pt, type_hf, tvb, offset + 4, 8, &nstime);
664
0
  }
665
0
  else
666
0
  {
667
0
    proto_tree_add_item (pt, type_hf, tvb, offset + 4, 8, ENC_BIG_ENDIAN);
668
0
  }
669
670
0
  return 0;
671
0
} /* int dissect_collectd_integer */
672
673
static void
674
dissect_collectd_values(tvbuff_t *tvb, int msg_off, int val_cnt,
675
      proto_tree *collectd_tree)
676
0
{
677
0
  proto_tree *values_tree, *value_tree;
678
0
  int i;
679
680
0
  values_tree = proto_tree_add_subtree_format(collectd_tree, tvb, msg_off + 6, val_cnt * 9,
681
0
          ett_collectd_value, NULL, "%d value%s", val_cnt,
682
0
          plurality (val_cnt, "", "s"));
683
684
0
  for (i = 0; i < val_cnt; i++)
685
0
  {
686
0
    int value_offset;
687
688
0
    int value_type_offset;
689
0
    uint8_t value_type;
690
691
    /* Calculate the offsets of the type byte and the actual value. */
692
0
    value_offset = msg_off + 6
693
0
        + val_cnt  /* value types */
694
0
        + (i * 8); /* previous values */
695
696
0
    value_type_offset = msg_off + 6 + i;
697
0
    value_type = tvb_get_uint8 (tvb, value_type_offset);
698
699
0
    switch (value_type) {
700
0
    case TYPE_VALUE_COUNTER:
701
0
    {
702
0
      uint64_t val64;
703
704
0
      val64 = tvb_get_ntoh64 (tvb, value_offset);
705
0
      value_tree = proto_tree_add_subtree_format(values_tree, tvb, msg_off + 6,
706
0
              val_cnt * 9, ett_collectd_valinfo, NULL,
707
0
              "Counter: %"PRIu64, val64);
708
709
0
      proto_tree_add_item (value_tree, hf_collectd_val_type,
710
0
               tvb, value_type_offset, 1, ENC_BIG_ENDIAN);
711
0
      proto_tree_add_item (value_tree,
712
0
               hf_collectd_val_counter, tvb,
713
0
               value_offset, 8, ENC_BIG_ENDIAN);
714
0
      break;
715
0
    }
716
717
0
    case TYPE_VALUE_GAUGE:
718
0
    {
719
0
      double val;
720
721
0
      val = tvb_get_letohieee_double (tvb, value_offset);
722
0
      value_tree = proto_tree_add_subtree_format(values_tree, tvb, msg_off + 6,
723
0
              val_cnt * 9, ett_collectd_valinfo, NULL,
724
0
              "Gauge: %g", val);
725
726
0
      proto_tree_add_item (value_tree, hf_collectd_val_type,
727
0
               tvb, value_type_offset, 1, ENC_BIG_ENDIAN);
728
      /* Set the `little endian' flag to true here, because
729
       * collectd stores doubles in x86 representation. */
730
0
      proto_tree_add_item (value_tree, hf_collectd_val_gauge,
731
0
               tvb, value_offset, 8, ENC_LITTLE_ENDIAN);
732
0
      break;
733
0
    }
734
735
0
    case TYPE_VALUE_DERIVE:
736
0
    {
737
0
      int64_t val64;
738
739
0
      val64 = tvb_get_ntoh64 (tvb, value_offset);
740
0
      value_tree = proto_tree_add_subtree_format(values_tree, tvb, msg_off + 6,
741
0
              val_cnt * 9, ett_collectd_valinfo, NULL,
742
0
              "Derive: %"PRIi64, val64);
743
744
0
      proto_tree_add_item (value_tree, hf_collectd_val_type,
745
0
               tvb, value_type_offset, 1, ENC_BIG_ENDIAN);
746
0
      proto_tree_add_item (value_tree,
747
0
               hf_collectd_val_derive, tvb,
748
0
               value_offset, 8, ENC_BIG_ENDIAN);
749
0
      break;
750
0
    }
751
752
0
    case TYPE_VALUE_ABSOLUTE:
753
0
    {
754
0
      uint64_t val64;
755
756
0
      val64 = tvb_get_ntoh64 (tvb, value_offset);
757
0
      value_tree = proto_tree_add_subtree_format(values_tree, tvb, msg_off + 6,
758
0
              val_cnt * 9, ett_collectd_valinfo, NULL,
759
0
              "Absolute: %"PRIu64, val64);
760
761
0
      proto_tree_add_item (value_tree, hf_collectd_val_type,
762
0
               tvb, value_type_offset, 1, ENC_BIG_ENDIAN);
763
0
      proto_tree_add_item (value_tree,
764
0
               hf_collectd_val_absolute, tvb,
765
0
               value_offset, 8, ENC_BIG_ENDIAN);
766
0
      break;
767
0
    }
768
769
0
    default:
770
0
    {
771
0
      uint64_t val64;
772
773
0
      val64 = tvb_get_ntoh64 (tvb, value_offset);
774
0
      value_tree = proto_tree_add_subtree_format(values_tree, tvb, msg_off + 6,
775
0
              val_cnt * 9, ett_collectd_valinfo, NULL,
776
0
              "Unknown: %"PRIx64,
777
0
              val64);
778
779
0
      proto_tree_add_item (value_tree, hf_collectd_val_type,
780
0
               tvb, value_type_offset, 1, ENC_BIG_ENDIAN);
781
0
      proto_tree_add_item (value_tree, hf_collectd_val_unknown,
782
0
               tvb, value_offset, 8, ENC_BIG_ENDIAN);
783
0
      break;
784
0
    }
785
0
    } /* switch (value_type) */
786
0
  } /* for (i = 0; i < val_cnt; i++) */
787
0
} /* void dissect_collectd_values */
788
789
static int
790
dissect_collectd_part_values (tvbuff_t *tvb, packet_info *pinfo, int offset,
791
            value_data_t *vdispatch, proto_tree *tree_root)
792
0
{
793
0
  proto_tree *pt;
794
0
  proto_item *pi;
795
0
  int type;
796
0
  int length;
797
0
  int size;
798
0
  int values_count;
799
0
  int corrected_values_count;
800
801
0
  size = tvb_reported_length_remaining (tvb, offset);
802
0
  if (size < 4)
803
0
  {
804
    /* This should never happen, because `dissect_collectd' checks
805
     * for this condition already. */
806
0
    return (-1);
807
0
  }
808
809
0
  type   = tvb_get_ntohs (tvb, offset);
810
0
  length = tvb_get_ntohs (tvb, offset + 2);
811
812
0
  if (size < 15)
813
0
  {
814
0
    pt = proto_tree_add_subtree_format(tree_root, tvb, offset, -1,
815
0
            ett_collectd_part_value, NULL, "collectd %s segment: <BAD>",
816
0
            val_to_str_const (type, part_names, "UNKNOWN"));
817
818
0
    proto_tree_add_uint (pt, hf_collectd_type, tvb, offset, 2, type);
819
0
    proto_tree_add_uint (pt, hf_collectd_length, tvb, offset + 2, 2,
820
0
             length);
821
0
    proto_tree_add_expert_format(pt, pinfo, &ei_collectd_garbage, tvb, offset + 4, -1,
822
0
            "Garbage at end of packet: Length = %i <BAD>",
823
0
            size - 4);
824
0
    return (-1);
825
0
  }
826
827
0
  if ((length < 15) || ((length % 9) != 6))
828
0
  {
829
0
    pt = proto_tree_add_subtree_format(tree_root, tvb, offset, -1,
830
0
            ett_collectd_part_value, &pi, "collectd %s segment: <BAD>",
831
0
            val_to_str_const (type, part_names, "UNKNOWN"));
832
833
0
    proto_tree_add_uint (pt, hf_collectd_type, tvb, offset, 2, type);
834
0
    pi = proto_tree_add_uint (pt, hf_collectd_length, tvb,
835
0
            offset + 2, 2, length);
836
0
    expert_add_info_format(pinfo, pi, &ei_collectd_invalid_length,
837
0
          "Invalid length field for a values part.");
838
839
0
    return (-1);
840
0
  }
841
842
0
  values_count = tvb_get_ntohs (tvb, offset + 4);
843
0
  corrected_values_count = (length - 6) / 9;
844
845
0
  if (values_count != corrected_values_count)
846
0
  {
847
0
    pt = proto_tree_add_subtree_format(tree_root, tvb, offset, length,
848
0
          ett_collectd_part_value, NULL,
849
0
            "collectd %s segment: %d (%d) value%s <BAD>",
850
0
            val_to_str_const (type, part_names, "UNKNOWN"),
851
0
            values_count, corrected_values_count,
852
0
            plurality(values_count, "", "s"));
853
0
  }
854
0
  else
855
0
  {
856
0
    pt = proto_tree_add_subtree_format(tree_root, tvb, offset, length,
857
0
          ett_collectd_part_value, NULL,
858
0
            "collectd %s segment: %d value%s",
859
0
            val_to_str_const (type, part_names, "UNKNOWN"),
860
0
            values_count,
861
0
            plurality(values_count, "", "s"));
862
0
  }
863
864
0
  proto_tree_add_uint (pt, hf_collectd_type, tvb, offset, 2, type);
865
0
  proto_tree_add_uint (pt, hf_collectd_length, tvb, offset + 2, 2, length);
866
867
0
  pi = proto_tree_add_item (pt, hf_collectd_data_valcnt, tvb,
868
0
          offset + 4, 2, ENC_BIG_ENDIAN);
869
0
  if (values_count != corrected_values_count)
870
0
    expert_add_info(pinfo, pi, &ei_collectd_data_valcnt);
871
872
0
  values_count = corrected_values_count;
873
874
0
  dissect_collectd_values (tvb, offset, values_count, pt);
875
0
  collectd_proto_tree_add_assembled_metric (tvb, offset + 6, length - 6,
876
0
      vdispatch, pt);
877
878
0
  return 0;
879
0
} /* void dissect_collectd_part_values */
880
881
static int
882
dissect_collectd_signature (tvbuff_t *tvb, packet_info *pinfo,
883
          int offset, proto_tree *tree_root)
884
0
{
885
0
  proto_item *pi;
886
0
  proto_tree *pt;
887
0
  int type;
888
0
  int length;
889
0
  int size;
890
0
  const char *username;
891
892
0
  size = tvb_reported_length_remaining (tvb, offset);
893
0
  if (size < 4)
894
0
  {
895
    /* This should never happen, because `dissect_collectd' checks
896
     * for this condition already. */
897
0
    return (-1);
898
0
  }
899
900
0
  type   = tvb_get_ntohs (tvb, offset);
901
0
  length = tvb_get_ntohs (tvb, offset + 2);
902
903
0
  if (size < 36) /* remaining packet size too small for signature */
904
0
  {
905
0
    pt = proto_tree_add_subtree_format(tree_root, tvb, offset, -1,
906
0
            ett_collectd_signature, NULL, "collectd %s segment: <BAD>",
907
0
            val_to_str_const (type, part_names, "UNKNOWN"));
908
909
0
    proto_tree_add_uint (pt, hf_collectd_type, tvb, offset, 2, type);
910
0
    proto_tree_add_uint (pt, hf_collectd_length, tvb, offset + 2, 2,
911
0
             length);
912
0
    proto_tree_add_expert_format(pt, pinfo, &ei_collectd_garbage, tvb, offset + 4, -1,
913
0
            "Garbage at end of packet: Length = %i <BAD>",
914
0
            size - 4);
915
0
    return (-1);
916
0
  }
917
918
0
  if (length < 36)
919
0
  {
920
0
    pt = proto_tree_add_subtree_format(tree_root, tvb, offset, -1,
921
0
            ett_collectd_signature, NULL, "collectd %s segment: <BAD>",
922
0
            val_to_str_const (type, part_names, "UNKNOWN"));
923
924
0
    proto_tree_add_uint (pt, hf_collectd_type, tvb, offset, 2, type);
925
0
    pi = proto_tree_add_uint (pt, hf_collectd_length, tvb,
926
0
            offset + 2, 2, length);
927
0
    expert_add_info_format(pinfo, pi, &ei_collectd_invalid_length,
928
0
          "Invalid length field for a signature part.");
929
930
0
    return (-1);
931
0
  }
932
933
0
  pt = proto_tree_add_subtree_format(tree_root, tvb, offset, length,
934
0
          ett_collectd_signature, NULL, "collectd %s segment: HMAC-SHA-256",
935
0
          val_to_str_const (type, part_names, "UNKNOWN"));
936
937
0
  proto_tree_add_uint (pt, hf_collectd_type, tvb, offset, 2, type);
938
0
  proto_tree_add_uint (pt, hf_collectd_length, tvb, offset + 2, 2,
939
0
           length);
940
  // proto_tree_add_checksum adds two ti but only returns the first,
941
  // which makes it hard to move the username after the second item,
942
  // so extract the string directly, then add a username item later.
943
  //
944
  // XXX - Are we sure this string is ASCII? Probably UTF-8 these days.
945
  // The same goes for all the other strings in the protocol.
946
0
  username = (char*)tvb_get_string_enc(pinfo->pool, tvb, offset + 36, length - 36, ENC_ASCII);
947
0
  uint8_t *hash = NULL;
948
0
  gcry_md_hd_t *md_hd = collectd_get_md(username);
949
0
  if (md_hd) {
950
0
    uint8_t *buffer = tvb_memdup(pinfo->pool, tvb, offset + 36, tvb_reported_length_remaining(tvb, offset + 36));
951
0
    gcry_md_write(*md_hd, buffer, size - 36);
952
0
    hash = gcry_md_read(*md_hd, GCRY_MD_SHA256);
953
0
    if (hash == NULL) {
954
0
      ws_debug("gcry_md_read failed");
955
0
    }
956
0
  }
957
0
  proto_tree_add_checksum_bytes(pt, tvb, offset + 4, hf_collectd_data_sighash,
958
0
    hf_collectd_data_sighash_status, &ei_collectd_sighash_bad, pinfo,
959
0
    hash, 32, hash ? PROTO_CHECKSUM_VERIFY : PROTO_CHECKSUM_NO_FLAGS);
960
0
  proto_tree_add_item(pt, hf_collectd_data_username, tvb, offset + 36, length - 36, ENC_ASCII);
961
0
  return 0;
962
0
} /* int dissect_collectd_signature */
963
964
/* We recurse after decrypting. In practice encryption is always the first
965
 * part and contains everything, so we could avoid recursion by checking
966
 * for it at the start of dissect_collect and not try to decrypt encrypted
967
 * parts in other positions. */
968
static int
969
// NOLINTNEXTLINE(misc-no-recursion)
970
dissect_collectd_parts(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_);
971
972
static int
973
// NOLINTNEXTLINE(misc-no-recursion)
974
dissect_collectd_encrypted(tvbuff_t *tvb, packet_info *pinfo,
975
         int offset, proto_tree *tree_root)
976
0
{
977
0
  proto_item *pi;
978
0
  proto_tree *pt;
979
0
  int type;
980
0
  int length;
981
0
  int size;
982
0
  int username_length;
983
0
  const char *username;
984
985
0
  size = tvb_reported_length_remaining (tvb, offset);
986
0
  if (size < 4)
987
0
  {
988
    /* This should never happen, because `dissect_collectd' checks
989
     * for this condition already. */
990
0
    return (-1);
991
0
  }
992
993
0
  type   = tvb_get_ntohs (tvb, offset);
994
0
  length = tvb_get_ntohs (tvb, offset + 2);
995
996
0
  if (size < 42) /* remaining packet size too small for signature */
997
0
  {
998
0
    pt = proto_tree_add_subtree_format(tree_root, tvb, offset, -1,
999
0
            ett_collectd_encryption, NULL, "collectd %s segment: <BAD>",
1000
0
            val_to_str_const (type, part_names, "UNKNOWN"));
1001
1002
0
    proto_tree_add_uint (pt, hf_collectd_type, tvb, offset, 2, type);
1003
0
    proto_tree_add_uint (pt, hf_collectd_length, tvb, offset + 2, 2,
1004
0
             length);
1005
0
    proto_tree_add_expert_format(pt, pinfo, &ei_collectd_garbage, tvb, offset + 4, -1,
1006
0
            "Garbage at end of packet: Length = %i <BAD>",
1007
0
            size - 4);
1008
0
    return (-1);
1009
0
  }
1010
1011
0
  if (length < 42)
1012
0
  {
1013
0
    pt = proto_tree_add_subtree_format(tree_root, tvb, offset, -1,
1014
0
            ett_collectd_encryption, NULL, "collectd %s segment: <BAD>",
1015
0
            val_to_str_const (type, part_names, "UNKNOWN"));
1016
1017
0
    proto_tree_add_uint (pt, hf_collectd_type, tvb, offset, 2, type);
1018
0
    pi = proto_tree_add_uint (pt, hf_collectd_length, tvb,
1019
0
            offset + 2, 2, length);
1020
0
    expert_add_info_format(pinfo, pi, &ei_collectd_invalid_length,
1021
0
          "Invalid length field for an encryption part.");
1022
1023
0
    return (-1);
1024
0
  }
1025
1026
0
  username_length = tvb_get_ntohs (tvb, offset + 4);
1027
0
  if (username_length > (length - 42))
1028
0
  {
1029
0
    pt = proto_tree_add_subtree_format(tree_root, tvb, offset, -1,
1030
0
            ett_collectd_encryption, NULL, "collectd %s segment: <BAD>",
1031
0
            val_to_str_const (type, part_names, "UNKNOWN"));
1032
1033
0
    proto_tree_add_uint (pt, hf_collectd_type, tvb, offset, 2, type);
1034
0
    proto_tree_add_uint (pt, hf_collectd_length, tvb,
1035
0
             offset + 2, 2, length);
1036
0
    pi = proto_tree_add_uint (pt, hf_collectd_data_username_len, tvb,
1037
0
            offset + 4, 2, length);
1038
0
    expert_add_info_format(pinfo, pi, &ei_collectd_invalid_length,
1039
0
          "Invalid username length field for an encryption part.");
1040
1041
0
    return (-1);
1042
0
  }
1043
1044
0
  pt = proto_tree_add_subtree_format(tree_root, tvb, offset, length,
1045
0
          ett_collectd_encryption, NULL, "collectd %s segment: AES-256",
1046
0
          val_to_str_const (type, part_names, "UNKNOWN"));
1047
1048
0
  proto_tree_add_uint(pt, hf_collectd_type, tvb, offset, 2, type);
1049
0
  offset += 2;
1050
0
  proto_tree_add_uint(pt, hf_collectd_length, tvb, offset, 2, length);
1051
0
  offset += 2;
1052
0
  proto_tree_add_uint(pt, hf_collectd_data_username_len, tvb, offset, 2, username_length);
1053
0
  offset += 2;
1054
0
  proto_tree_add_item_ret_string(pt, hf_collectd_data_username, tvb, offset, username_length, ENC_ASCII, pinfo->pool, (const uint8_t**)&username);
1055
0
  offset += username_length;
1056
1057
0
  proto_tree_add_item(pt, hf_collectd_data_initvec, tvb,
1058
0
           offset, 16, ENC_NA);
1059
0
  offset += 16;
1060
1061
0
  int buffer_size = length - (22 + username_length);
1062
  // Must be >= 20 (checked above)
1063
0
  proto_tree_add_item(pt, hf_collectd_data_encrypted, tvb,
1064
0
          offset,
1065
0
          buffer_size, ENC_NA);
1066
0
  gcry_cipher_hd_t *cipher_hd;
1067
0
  cipher_hd = collectd_get_cipher(username);
1068
0
  if (cipher_hd) {
1069
0
    gcry_error_t err;
1070
0
    uint8_t iv[16];
1071
0
    tvb_memcpy(tvb, iv, offset - 16, 16);
1072
0
    err = gcry_cipher_setiv(*cipher_hd, iv, 16);
1073
0
    if (err != 0) {
1074
0
      ws_debug("error setting key: %s", gcry_strerror(err));
1075
0
      return 0; // Should there be another return code for this?
1076
0
    }
1077
0
    uint8_t *buffer = tvb_memdup(pinfo->pool, tvb, offset, buffer_size);
1078
0
    err = gcry_cipher_decrypt(*cipher_hd, buffer, buffer_size, NULL, 0);
1079
0
    if (err != 0) {
1080
0
      ws_debug("gcry_cipher_decrypt failed: %s", gcry_strerror(err));
1081
0
      return 0; // Should there be another return code for this?
1082
0
    }
1083
0
    tvbuff_t *decrypted_tvb = tvb_new_child_real_data(tvb, buffer, buffer_size, buffer_size);
1084
0
    add_new_data_source(pinfo, decrypted_tvb, "Decrypted collectd");
1085
0
    uint8_t hash[20];
1086
0
    gcry_md_hash_buffer(GCRY_MD_SHA1, hash, buffer + 20, buffer_size - 20);
1087
0
    proto_tree_add_checksum_bytes(pt, decrypted_tvb, 0, hf_collectd_data_sighash,
1088
0
      hf_collectd_data_sighash_status, &ei_collectd_sighash_bad, pinfo,
1089
0
      hash, 20, PROTO_CHECKSUM_VERIFY);
1090
0
    if (tvb_memeql(decrypted_tvb, 0, hash, 20) == 0) {
1091
      // We recurse here, but consumed 22 + username_len bytes
1092
      // so we'll run out of packet before stack exhaustion.
1093
0
      dissect_collectd_parts(tvb_new_subset_remaining(decrypted_tvb, 20), pinfo, tree_root, NULL);
1094
0
    }
1095
0
  }
1096
0
  return 0;
1097
0
} /* int dissect_collectd_encrypted */
1098
1099
static int
1100
stats_account_string (wmem_allocator_t *scope, string_counter_t **ret_list, const char *new_value)
1101
0
{
1102
0
  string_counter_t *entry;
1103
1104
0
  if (ret_list == NULL)
1105
0
    return (-1);
1106
1107
0
  if (new_value == NULL)
1108
0
    new_value = "(null)";
1109
1110
0
  for (entry = *ret_list; entry != NULL; entry = entry->next)
1111
0
    if (strcmp (new_value, entry->string) == 0)
1112
0
    {
1113
0
      entry->count++;
1114
0
      return 0;
1115
0
    }
1116
1117
0
  entry = (string_counter_t *)wmem_alloc0 (scope, sizeof (*entry));
1118
0
  entry->string = wmem_strdup (scope, new_value);
1119
0
  entry->count = 1;
1120
0
  entry->next = *ret_list;
1121
1122
0
  *ret_list = entry;
1123
1124
0
  return 0;
1125
0
}
1126
1127
static int
1128
// NOLINTNEXTLINE(misc-no-recursion)
1129
dissect_collectd_parts(tvbuff_t *tvb, packet_info *pinfo, proto_tree *collectd_tree, void* data _U_)
1130
3
{
1131
3
  int offset;
1132
3
  int size;
1133
3
  value_data_t vdispatch;
1134
3
  notify_data_t ndispatch;
1135
3
  int status;
1136
3
  proto_item *pi;
1137
3
  proto_tree *pt;
1138
1139
3
  memset(&vdispatch, '\0', sizeof(vdispatch));
1140
3
  memset(&ndispatch, '\0', sizeof(ndispatch));
1141
1142
3
  tap_data_t *tap_data = p_get_proto_data(pinfo->pool, pinfo, proto_collectd, TAP_DATA_KEY);
1143
3
  column_data_t *col_data = p_get_proto_data(pinfo->pool, pinfo, proto_collectd, COL_DATA_KEY);
1144
1145
3
  status = 0;
1146
3
  offset = 0;
1147
3
  size = tvb_reported_length(tvb);
1148
4
  while ((size > 0) && (status == 0))
1149
4
  {
1150
4
    int part_type;
1151
4
    int part_length;
1152
1153
    /* Check if there are at least four bytes left first.
1154
     * Four bytes are used to read the type and the length
1155
     * of the next part. If there's less, there's some garbage
1156
     * at the end of the packet. */
1157
4
    if (size < 4)
1158
2
    {
1159
2
      proto_tree_add_expert_format(collectd_tree, pinfo, &ei_collectd_garbage, tvb,
1160
2
              offset, -1,
1161
2
              "Garbage at end of packet: Length = %i <BAD>",
1162
2
              size);
1163
2
      col_data->pkt_errors++;
1164
2
      break;
1165
2
    }
1166
1167
    /* dissect a message entry */
1168
2
    part_type = tvb_get_ntohs (tvb, offset);
1169
2
    part_length  = tvb_get_ntohs (tvb, offset + 2);
1170
1171
    /* Check if the length of the part is in the valid range. Don't
1172
     * confuse this with the above: Here we check the information
1173
     * provided in the packet.. */
1174
2
    if ((part_length < 4) || (part_length > size))
1175
1
    {
1176
1
      pt = proto_tree_add_subtree_format(collectd_tree, tvb,
1177
1
              offset, part_length, ett_collectd_invalid_length, NULL,
1178
1
              "collectd %s segment: Length = %i <BAD>",
1179
1
              val_to_str_const (part_type, part_names, "UNKNOWN"),
1180
1
              part_length);
1181
1182
1
      proto_tree_add_uint (pt, hf_collectd_type, tvb, offset,
1183
1
               2, part_type);
1184
1
      pi = proto_tree_add_uint (pt, hf_collectd_length, tvb,
1185
1
               offset + 2, 2, part_length);
1186
1187
1
      if (part_length < 4)
1188
0
        expert_add_info_format(pinfo, pi, &ei_collectd_invalid_length,
1189
0
              "Bad part length: Is %i, expected at least 4",
1190
0
              part_length);
1191
1
      else
1192
1
        expert_add_info_format(pinfo, pi, &ei_collectd_invalid_length,
1193
1
              "Bad part length: Larger than remaining packet size.");
1194
1195
1
      col_data->pkt_errors++;
1196
1
      break;
1197
1
    }
1198
1199
    /* The header information looks okay, let's tend to the actual
1200
     * payload in this part. */
1201
1
    switch (part_type) {
1202
0
    case TYPE_HOST:
1203
0
    {
1204
0
      status = dissect_collectd_string (tvb, pinfo,
1205
0
          hf_collectd_data_host,
1206
0
          offset,
1207
0
          &vdispatch.host_off,
1208
0
          &vdispatch.host_len,
1209
0
          &vdispatch.host,
1210
0
          collectd_tree, /* item = */ NULL);
1211
0
      if (status != 0)
1212
0
        col_data->pkt_errors++;
1213
0
      else
1214
0
      {
1215
0
        if (col_data->pkt_host == NULL)
1216
0
          col_data->pkt_host = vdispatch.host;
1217
0
        ndispatch.host_off = vdispatch.host_off;
1218
0
        ndispatch.host_len = vdispatch.host_len;
1219
0
        ndispatch.host = vdispatch.host;
1220
0
      }
1221
1222
0
      break;
1223
0
    }
1224
1225
0
    case TYPE_PLUGIN:
1226
0
    {
1227
0
      status = dissect_collectd_string (tvb, pinfo,
1228
0
          hf_collectd_data_plugin,
1229
0
          offset,
1230
0
          &vdispatch.plugin_off,
1231
0
          &vdispatch.plugin_len,
1232
0
          &vdispatch.plugin,
1233
0
          collectd_tree, /* item = */ NULL);
1234
0
      if (status != 0)
1235
0
        col_data->pkt_errors++;
1236
0
      else
1237
0
        col_data->pkt_plugins++;
1238
1239
0
      break;
1240
0
    }
1241
1242
0
    case TYPE_PLUGIN_INSTANCE:
1243
0
    {
1244
0
      status = dissect_collectd_string (tvb, pinfo,
1245
0
          hf_collectd_data_plugin_inst,
1246
0
          offset,
1247
0
          &vdispatch.plugin_instance_off,
1248
0
          &vdispatch.plugin_instance_len,
1249
0
          &vdispatch.plugin_instance,
1250
0
          collectd_tree, /* item = */ NULL);
1251
0
      if (status != 0)
1252
0
        col_data->pkt_errors++;
1253
1254
0
      break;
1255
0
    }
1256
1257
0
    case TYPE_TYPE:
1258
0
    {
1259
0
      status = dissect_collectd_string (tvb, pinfo,
1260
0
          hf_collectd_data_type,
1261
0
          offset,
1262
0
          &vdispatch.type_off,
1263
0
          &vdispatch.type_len,
1264
0
          &vdispatch.type,
1265
0
          collectd_tree, /* item = */ NULL);
1266
0
      if (status != 0)
1267
0
        col_data->pkt_errors++;
1268
1269
0
      break;
1270
0
    }
1271
1272
0
    case TYPE_TYPE_INSTANCE:
1273
0
    {
1274
0
      status = dissect_collectd_string (tvb, pinfo,
1275
0
          hf_collectd_data_type_inst,
1276
0
          offset,
1277
0
          &vdispatch.type_instance_off,
1278
0
          &vdispatch.type_instance_len,
1279
0
          &vdispatch.type_instance,
1280
0
          collectd_tree, /* item = */ NULL);
1281
0
      if (status != 0)
1282
0
        col_data->pkt_errors++;
1283
1284
0
      break;
1285
0
    }
1286
1287
0
    case TYPE_TIME:
1288
0
    case TYPE_TIME_HR:
1289
0
    {
1290
0
      pi = NULL;
1291
0
      status = dissect_collectd_integer (tvb, pinfo,
1292
0
          hf_collectd_data_time,
1293
0
          offset,
1294
0
          &vdispatch.time_off,
1295
0
          &vdispatch.time_value,
1296
0
          collectd_tree, &pi);
1297
0
      if (status != 0)
1298
0
        col_data->pkt_errors++;
1299
1300
0
      break;
1301
0
    }
1302
1303
0
    case TYPE_INTERVAL:
1304
0
    case TYPE_INTERVAL_HR:
1305
0
    {
1306
0
      status = dissect_collectd_integer (tvb, pinfo,
1307
0
          hf_collectd_data_interval,
1308
0
          offset,
1309
0
          &vdispatch.interval_off,
1310
0
          &vdispatch.interval,
1311
0
          collectd_tree, /* item = */ NULL);
1312
0
      if (status != 0)
1313
0
        col_data->pkt_errors++;
1314
1315
0
      break;
1316
0
    }
1317
1318
0
    case TYPE_VALUES:
1319
0
    {
1320
0
      status = dissect_collectd_part_values (tvb, pinfo,
1321
0
          offset,
1322
0
          &vdispatch,
1323
0
          collectd_tree);
1324
0
      if (status != 0)
1325
0
        col_data->pkt_errors++;
1326
0
      else
1327
0
        col_data->pkt_values++;
1328
1329
0
      tap_data->values_num++;
1330
0
      stats_account_string (pinfo->pool,
1331
0
                &tap_data->hosts,
1332
0
                vdispatch.host);
1333
0
      stats_account_string (pinfo->pool,
1334
0
                &tap_data->plugins,
1335
0
                vdispatch.plugin);
1336
0
      stats_account_string (pinfo->pool,
1337
0
                &tap_data->types,
1338
0
                vdispatch.type);
1339
1340
0
      break;
1341
0
    }
1342
1343
0
    case TYPE_MESSAGE:
1344
0
    {
1345
0
      pi = NULL;
1346
0
      status = dissect_collectd_string (tvb, pinfo,
1347
0
          hf_collectd_data_message,
1348
0
          offset,
1349
0
          &ndispatch.message_off,
1350
0
          &ndispatch.message_len,
1351
0
          &ndispatch.message,
1352
0
          collectd_tree, &pi);
1353
0
      if (status != 0)
1354
0
      {
1355
0
        col_data->pkt_errors++;
1356
0
        break;
1357
0
      }
1358
0
      col_data->pkt_messages++;
1359
1360
0
      pt = proto_item_get_subtree (pi);
1361
1362
0
      collectd_proto_tree_add_assembled_notification (tvb,
1363
0
          offset + 4, part_length - 1,
1364
0
          &ndispatch, pt);
1365
1366
0
      break;
1367
0
    }
1368
1369
0
    case TYPE_SEVERITY:
1370
0
    {
1371
0
      pi = NULL;
1372
0
      status = dissect_collectd_integer (tvb, pinfo,
1373
0
          hf_collectd_data_severity,
1374
0
          offset,
1375
0
          &ndispatch.severity_off,
1376
0
          &ndispatch.severity,
1377
0
          collectd_tree, &pi);
1378
0
      if (status != 0)
1379
0
        col_data->pkt_errors++;
1380
0
      else
1381
0
      {
1382
0
        proto_item_set_text (pi,
1383
0
            "collectd SEVERITY segment: "
1384
0
            "%s (%"PRIu64")",
1385
0
            val64_to_str_const (ndispatch.severity, severity_names, "UNKNOWN"),
1386
0
            ndispatch.severity);
1387
0
      }
1388
1389
0
      break;
1390
0
    }
1391
1392
0
    case TYPE_SIGN_SHA256:
1393
0
    {
1394
0
      status = dissect_collectd_signature (tvb, pinfo,
1395
0
                   offset,
1396
0
                   collectd_tree);
1397
0
      if (status != 0)
1398
0
        col_data->pkt_errors++;
1399
1400
0
      break;
1401
0
    }
1402
1403
0
    case TYPE_ENCR_AES256:
1404
0
    {
1405
0
      status = dissect_collectd_encrypted (tvb, pinfo,
1406
0
          offset, collectd_tree);
1407
0
      if (status != 0)
1408
0
        col_data->pkt_errors++;
1409
1410
0
      break;
1411
0
    }
1412
1413
1
    default:
1414
1
    {
1415
1
      col_data->pkt_unknown++;
1416
1
      pt = proto_tree_add_subtree_format(collectd_tree, tvb,
1417
1
              offset, part_length, ett_collectd_unknown, NULL,
1418
1
              "collectd %s segment: %i bytes",
1419
1
              val_to_str_const(part_type, part_names, "UNKNOWN"),
1420
1
              part_length);
1421
1422
1
      pi = proto_tree_add_uint (pt, hf_collectd_type, tvb,
1423
1
              offset, 2, part_type);
1424
1
      proto_tree_add_uint (pt, hf_collectd_length, tvb,
1425
1
              offset + 2, 2, part_length);
1426
1
      proto_tree_add_item (pt, hf_collectd_data, tvb,
1427
1
               offset + 4, part_length - 4, ENC_NA);
1428
1429
1
      expert_add_info_format(pinfo, pi, &ei_collectd_type,
1430
1
            "Unknown part type %#x. Cannot decode data.",
1431
1
            part_type);
1432
1
    }
1433
1
    } /* switch (part_type) */
1434
1435
1
    offset  += part_length;
1436
1
    size    -= part_length;
1437
1
  } /* while ((size > 4) && (status == 0)) */
1438
1439
3
  return tvb_captured_length(tvb);
1440
3
}
1441
1442
static int
1443
dissect_collectd (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
1444
3
{
1445
3
  proto_item *pi;
1446
3
  proto_tree *collectd_tree;
1447
1448
3
  col_set_str(pinfo->cinfo, COL_PROTOCOL, "collectd");
1449
3
  col_clear(pinfo->cinfo, COL_INFO);
1450
1451
3
  tap_data_t *tap_data = wmem_new0(pinfo->pool, tap_data_t);
1452
3
  p_add_proto_data(pinfo->pool, pinfo, proto_collectd, TAP_DATA_KEY, tap_data);
1453
1454
3
  column_data_t *col_data = wmem_new0(pinfo->pool, column_data_t);
1455
3
  p_add_proto_data(pinfo->pool, pinfo, proto_collectd, COL_DATA_KEY, col_data);
1456
1457
  /* create the collectd protocol tree */
1458
3
  pi = proto_tree_add_item(tree, proto_collectd, tvb, 0, -1, ENC_NA);
1459
3
  collectd_tree = proto_item_add_subtree(pi, ett_collectd);
1460
1461
3
  dissect_collectd_parts(tvb, pinfo, collectd_tree, data);
1462
1463
  /* Put summary information in columns */
1464
3
  col_add_fstr(pinfo->cinfo, COL_INFO, "Host=%s, %2d value%s for %d plugin%s %d message%s",
1465
3
      col_data->pkt_host,
1466
3
      col_data->pkt_values, plurality(col_data->pkt_values, " ", "s"),
1467
3
      col_data->pkt_plugins, plurality(col_data->pkt_plugins, ", ", "s,"),
1468
3
      col_data->pkt_messages, plurality(col_data->pkt_messages, ", ", "s"));
1469
1470
3
  if (col_data->pkt_unknown) {
1471
1
    col_append_fstr(pinfo->cinfo, COL_INFO, ", %d unknown",
1472
1
      col_data->pkt_unknown);
1473
1
  }
1474
1475
3
  if (col_data->pkt_errors) {
1476
3
    col_add_fstr(pinfo->cinfo, COL_INFO, ", %d error%s",
1477
3
      col_data->pkt_errors, plurality(col_data->pkt_errors, "", "s"));
1478
3
  }
1479
1480
  /* Dispatch tap data. */
1481
3
  tap_queue_packet(tap_collectd, pinfo, tap_data);
1482
1483
3
  return tvb_captured_length(tvb);
1484
3
} /* void dissect_collectd */
1485
1486
void proto_register_collectd(void)
1487
14
{
1488
14
  expert_module_t* expert_collectd;
1489
14
  module_t *collectd_module;
1490
1491
  /* Setup list of header fields */
1492
14
  static hf_register_info hf[] = {
1493
14
    { &hf_collectd_type,
1494
14
      { "Type", "collectd.type", FT_UINT16, BASE_HEX,
1495
14
        VALS(part_names), 0x0, NULL, HFILL }
1496
14
    },
1497
14
    { &hf_collectd_length,
1498
14
      { "Length", "collectd.len", FT_UINT16, BASE_DEC,
1499
14
        NULL, 0x0, NULL, HFILL }
1500
14
    },
1501
14
    { &hf_collectd_data,
1502
14
      { "Payload", "collectd.data", FT_BYTES, BASE_NONE,
1503
14
        NULL, 0x0, NULL, HFILL }
1504
14
    },
1505
14
    { &hf_collectd_data_host,
1506
14
      { "Host name", "collectd.data.host", FT_STRING, BASE_NONE,
1507
14
        NULL, 0x0, NULL, HFILL }
1508
14
    },
1509
14
    { &hf_collectd_data_interval,
1510
14
      { "Interval", "collectd.data.interval", FT_RELATIVE_TIME, BASE_NONE,
1511
14
        NULL, 0x0, NULL, HFILL }
1512
14
    },
1513
14
    { &hf_collectd_data_time,
1514
14
      { "Timestamp", "collectd.data.time", FT_ABSOLUTE_TIME, ABSOLUTE_TIME_LOCAL,
1515
14
        NULL, 0x0, NULL, HFILL }
1516
14
    },
1517
14
    { &hf_collectd_data_plugin,
1518
14
      { "Plugin", "collectd.data.plugin", FT_STRING, BASE_NONE,
1519
14
        NULL, 0x0, NULL, HFILL }
1520
14
    },
1521
14
    { &hf_collectd_data_plugin_inst,
1522
14
      { "Plugin instance", "collectd.data.plugin.inst", FT_STRING, BASE_NONE,
1523
14
        NULL, 0x0, NULL, HFILL }
1524
14
    },
1525
14
    { &hf_collectd_data_type,
1526
14
      { "Type", "collectd.data.type", FT_STRING, BASE_NONE,
1527
14
        NULL, 0x0, NULL, HFILL }
1528
14
    },
1529
14
    { &hf_collectd_data_type_inst,
1530
14
      { "Type instance", "collectd.data.type.inst", FT_STRING, BASE_NONE,
1531
14
        NULL, 0x0, NULL, HFILL }
1532
14
    },
1533
14
    { &hf_collectd_data_valcnt,
1534
14
      { "Value count", "collectd.data.valcnt", FT_UINT16, BASE_DEC,
1535
14
        NULL, 0x0, NULL, HFILL }
1536
14
    },
1537
14
    { &hf_collectd_val_type,
1538
14
      { "Value type", "collectd.val.type", FT_UINT8, BASE_HEX,
1539
14
        VALS(valuetypenames), 0x0, NULL, HFILL }
1540
14
    },
1541
14
    { &hf_collectd_val_counter,
1542
14
      { "Counter value", "collectd.val.counter", FT_UINT64, BASE_DEC,
1543
14
        NULL, 0x0, NULL, HFILL }
1544
14
    },
1545
14
    { &hf_collectd_val_gauge,
1546
14
      { "Gauge value", "collectd.val.gauge", FT_DOUBLE, BASE_NONE,
1547
14
        NULL, 0x0, NULL, HFILL }
1548
14
    },
1549
14
    { &hf_collectd_val_derive,
1550
14
      { "Derive value", "collectd.val.derive", FT_INT64, BASE_DEC,
1551
14
        NULL, 0x0, NULL, HFILL }
1552
14
    },
1553
14
    { &hf_collectd_val_absolute,
1554
14
      { "Absolute value", "collectd.val.absolute", FT_UINT64, BASE_DEC,
1555
14
        NULL, 0x0, NULL, HFILL }
1556
14
    },
1557
14
    { &hf_collectd_val_unknown,
1558
14
      { "Value of unknown type", "collectd.val.unknown", FT_UINT64, BASE_HEX,
1559
14
        NULL, 0x0, NULL, HFILL }
1560
14
    },
1561
14
    { &hf_collectd_data_severity,
1562
14
      { "Severity", "collectd.data.severity", FT_UINT64, BASE_HEX | BASE_VAL64_STRING,
1563
14
        VALS64(severity_names),
1564
14
        0x0, NULL, HFILL }
1565
14
    },
1566
14
    { &hf_collectd_data_message,
1567
14
      { "Message", "collectd.data.message", FT_STRING, BASE_NONE,
1568
14
        NULL, 0x0, NULL, HFILL }
1569
14
    },
1570
14
    { &hf_collectd_data_sighash,
1571
14
      { "Signature", "collectd.data.sighash", FT_BYTES, BASE_NONE,
1572
14
        NULL, 0x0, NULL, HFILL }
1573
14
    },
1574
14
    { &hf_collectd_data_sighash_status,
1575
14
      { "Signature", "collectd.data.sighash.status", FT_UINT8, BASE_NONE,
1576
14
        VALS(proto_checksum_vals), 0x0, NULL, HFILL }
1577
14
    },
1578
14
    { &hf_collectd_data_initvec,
1579
14
      { "Init vector", "collectd.data.initvec", FT_BYTES, BASE_NONE,
1580
14
        NULL, 0x0, NULL, HFILL }
1581
14
    },
1582
14
    { &hf_collectd_data_username_len,
1583
14
      { "Username length", "collectd.data.username_length", FT_UINT16, BASE_DEC,
1584
14
        NULL, 0x0, NULL, HFILL }
1585
14
    },
1586
14
    { &hf_collectd_data_username,
1587
14
      { "Username", "collectd.data.username", FT_STRING, BASE_NONE,
1588
14
        NULL, 0x0, NULL, HFILL }
1589
14
    },
1590
14
    { &hf_collectd_data_encrypted,
1591
14
      { "Encrypted data", "collectd.data.encrypted", FT_BYTES, BASE_NONE,
1592
14
        NULL, 0x0, NULL, HFILL }
1593
14
    },
1594
14
  };
1595
1596
  /* Setup protocol subtree array */
1597
14
  static int *ett[] = {
1598
14
    &ett_collectd,
1599
14
    &ett_collectd_string,
1600
14
    &ett_collectd_integer,
1601
14
    &ett_collectd_part_value,
1602
14
    &ett_collectd_value,
1603
14
    &ett_collectd_valinfo,
1604
14
    &ett_collectd_signature,
1605
14
    &ett_collectd_encryption,
1606
14
    &ett_collectd_dispatch,
1607
14
    &ett_collectd_invalid_length,
1608
14
    &ett_collectd_unknown,
1609
14
  };
1610
1611
14
  static ei_register_info ei[] = {
1612
14
    { &ei_collectd_invalid_length, { "collectd.invalid_length", PI_MALFORMED, PI_ERROR, "Invalid length", EXPFILL }},
1613
14
    { &ei_collectd_garbage, { "collectd.garbage", PI_MALFORMED, PI_ERROR, "Garbage at end of packet", EXPFILL }},
1614
14
    { &ei_collectd_data_valcnt, { "collectd.data.valcnt.mismatch", PI_MALFORMED, PI_WARN, "Number of values and length of part do not match. Assuming length is correct.", EXPFILL }},
1615
14
    { &ei_collectd_type, { "collectd.type.unknown", PI_UNDECODED, PI_NOTE, "Unknown part type", EXPFILL }},
1616
14
    { &ei_collectd_sighash_bad, { "collectd.data.sighash.bad", PI_CHECKSUM, PI_ERROR, "Bad hash", EXPFILL }},
1617
14
  };
1618
1619
  /* Register the protocol name and description */
1620
14
  proto_collectd = proto_register_protocol("collectd network data", "collectd", "collectd");
1621
1622
  /* Required function calls to register the header fields and subtrees used */
1623
14
  proto_register_field_array(proto_collectd, hf, array_length(hf));
1624
14
  proto_register_subtree_array(ett, array_length(ett));
1625
14
  expert_collectd = expert_register_protocol(proto_collectd);
1626
14
  expert_register_field_array(expert_collectd, ei, array_length(ei));
1627
1628
14
  collectd_module = prefs_register_protocol(proto_collectd, NULL);
1629
1630
14
  static uat_field_t collectd_uat_flds[] = {
1631
14
    UAT_FLD_CSTRING(uat_collectd_records, username, "Username", "Username"),
1632
14
    UAT_FLD_CSTRING(uat_collectd_records, password, "Password", "Password"),
1633
14
    UAT_END_FIELDS
1634
14
  };
1635
1636
14
  collectd_uat = uat_new("collectd Authentication",
1637
14
    sizeof(uat_collectd_record_t),
1638
14
    "collectd",
1639
14
    true,
1640
14
    &uat_collectd_records,
1641
14
    &num_uat,
1642
14
    UAT_AFFECTS_DISSECTION,
1643
14
    NULL,
1644
14
    uat_collectd_record_copy_cb,
1645
14
    uat_collectd_record_update_cb,
1646
14
    uat_collectd_record_free_cb,
1647
14
    NULL,
1648
14
    NULL,
1649
14
    collectd_uat_flds);
1650
1651
14
  prefs_register_uat_preference(collectd_module, "auth", "Authentication", "A table of user credentials for verifying signatures and decrypting encrypted packets", collectd_uat);
1652
1653
14
  tap_collectd = register_tap ("collectd");
1654
1655
14
  collectd_handle = register_dissector("collectd", dissect_collectd, proto_collectd);
1656
14
}
1657
1658
void proto_reg_handoff_collectd (void)
1659
14
{
1660
14
  dissector_add_uint_with_preference("udp.port", UDP_PORT_COLLECTD, collectd_handle);
1661
1662
14
  collectd_stats_tree_register ();
1663
14
} /* void proto_reg_handoff_collectd */
1664
1665
/*
1666
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
1667
 *
1668
 * Local variables:
1669
 * c-basic-offset: 8
1670
 * tab-width: 8
1671
 * indent-tabs-mode: t
1672
 * End:
1673
 *
1674
 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
1675
 * :indentSize=8:tabSize=8:noTabs=false:
1676
 */