Coverage Report

Created: 2025-08-28 06:24

/src/ndpi/src/lib/protocols/collectd.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * collectd.c
3
 *
4
 * Copyright (C) 2022-23 - ntop.org
5
 *
6
 * nDPI is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU Lesser General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * nDPI is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public License
17
 * along with nDPI.  If not, see <http://www.gnu.org/licenses/>.
18
 *
19
 */
20
21
22
#include "ndpi_protocol_ids.h"
23
24
#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_COLLECTD
25
26
#include "ndpi_api.h"
27
#include "ndpi_private.h"
28
29
0
#define COLLECTD_MIN_BLOCKS_REQUIRED 3
30
0
#define COLLECTD_MAX_BLOCKS_TO_DISSECT 5
31
32
0
#define COLLECTD_ENCR_AES256_MIN_BLOCK_SIZE 6
33
0
#define COLLECTD_ENCR_AES256_IV_SIZE 16
34
35
enum collectd_type {
36
  COLLECTD_TYPE_HOST            = 0x0000,
37
  COLLECTD_TYPE_TIME            = 0x0001,
38
  COLLECTD_TYPE_TIME_HR         = 0x0008,
39
  COLLECTD_TYPE_PLUGIN          = 0x0002,
40
  COLLECTD_TYPE_PLUGIN_INSTANCE = 0x0003,
41
  COLLECTD_TYPE_TYPE            = 0x0004,
42
  COLLECTD_TYPE_TYPE_INSTANCE   = 0x0005,
43
  COLLECTD_TYPE_VALUES          = 0x0006,
44
  COLLECTD_TYPE_INTERVAL        = 0x0007,
45
  COLLECTD_TYPE_INTERVAL_HR     = 0x0009,
46
  COLLECTD_TYPE_MESSAGE         = 0x0100,
47
  COLLECTD_TYPE_SEVERITY        = 0x0101,
48
  COLLECTD_TYPE_SIGN_SHA256     = 0x0200,
49
  COLELCTD_TYPE_ENCR_AES256     = 0x0210,
50
};
51
52
static u_int16_t const collectd_types[] = {
53
  COLLECTD_TYPE_HOST, COLLECTD_TYPE_TIME, COLLECTD_TYPE_TIME_HR, COLLECTD_TYPE_PLUGIN,
54
  COLLECTD_TYPE_PLUGIN_INSTANCE, COLLECTD_TYPE_TYPE, COLLECTD_TYPE_TYPE_INSTANCE,
55
  COLLECTD_TYPE_VALUES, COLLECTD_TYPE_INTERVAL, COLLECTD_TYPE_INTERVAL_HR,
56
  COLLECTD_TYPE_MESSAGE, COLLECTD_TYPE_SEVERITY, COLLECTD_TYPE_SIGN_SHA256,
57
  COLELCTD_TYPE_ENCR_AES256
58
};
59
static const size_t collectd_types_length = NDPI_ARRAY_LENGTH(collectd_types);
60
61
static void ndpi_int_collectd_add_connection(struct ndpi_detection_module_struct * const ndpi_struct,
62
                                             struct ndpi_flow_struct * const flow)
63
0
{
64
0
  NDPI_LOG_INFO(ndpi_struct, "found collectd\n");
65
0
  ndpi_set_detected_protocol(ndpi_struct, flow,
66
0
                             NDPI_PROTOCOL_COLLECTD,
67
0
                             NDPI_PROTOCOL_UNKNOWN,
68
0
                             NDPI_CONFIDENCE_DPI);
69
0
}
70
71
static u_int16_t npdi_int_collectd_block_size(struct ndpi_packet_struct const * const packet,
72
                                              u_int16_t const block_offset)
73
0
{
74
0
  if (block_offset + 4 > packet->payload_packet_len)
75
0
  {
76
0
    return 0;
77
0
  }
78
79
0
  u_int16_t next_block = ntohs(get_u_int16_t(packet->payload, block_offset + 2));
80
0
  if (block_offset + next_block > packet->payload_packet_len ||
81
0
      (u_int16_t)(block_offset + next_block) <= block_offset /* possible overflow or next_block is zero */)
82
0
  {
83
0
    return 0;
84
0
  }
85
86
0
  return next_block;
87
0
}
88
89
static int ndpi_int_collectd_check_type(u_int16_t block_type)
90
0
{
91
0
  size_t i;
92
93
0
  for (i = 0; i < collectd_types_length; ++i)
94
0
  {
95
0
    if (block_type == collectd_types[i])
96
0
    {
97
0
      return 0;
98
0
    }
99
0
  }
100
101
0
  return 1;
102
0
}
103
104
static void ndpi_int_collectd_dissect_hostname(struct ndpi_flow_struct * const flow,
105
                                               struct ndpi_packet_struct const * const packet,
106
                                               u_int16_t block_length)
107
0
{
108
0
  ndpi_hostname_sni_set(flow, &packet->payload[4], block_length, NDPI_HOSTNAME_NORM_ALL);
109
0
}
110
111
static int ndpi_int_collectd_dissect_username(struct ndpi_flow_struct * const flow,
112
                                              struct ndpi_packet_struct const * const packet)
113
0
{
114
0
  u_int16_t username_length = ntohs(get_u_int16_t(packet->payload, 4));
115
116
0
  if(username_length > packet->payload_packet_len -
117
0
     COLLECTD_ENCR_AES256_MIN_BLOCK_SIZE -
118
0
     COLLECTD_ENCR_AES256_IV_SIZE)
119
0
  {
120
0
    return 1;
121
0
  }
122
123
0
  size_t sz_len = ndpi_min(sizeof(flow->protos.collectd.client_username) - 1, username_length);
124
0
  memcpy(flow->protos.collectd.client_username, &packet->payload[6], sz_len);
125
0
  flow->protos.collectd.client_username[sz_len] = '\0';
126
127
0
  return 0;
128
0
}
129
130
static void ndpi_search_collectd(struct ndpi_detection_module_struct *ndpi_struct,
131
                                 struct ndpi_flow_struct *flow)
132
0
{
133
0
  struct ndpi_packet_struct const * const packet = &ndpi_struct->packet;
134
0
  u_int16_t num_blocks;
135
0
  u_int16_t block_offset = 0, block_type, block_length;
136
0
  u_int16_t hostname_length = 0;
137
138
0
  NDPI_LOG_DBG(ndpi_struct, "search collectd\n");
139
140
0
  for (num_blocks = 0; num_blocks < COLLECTD_MAX_BLOCKS_TO_DISSECT;
141
0
       ++num_blocks, block_offset += block_length)
142
0
  {
143
0
    block_length = npdi_int_collectd_block_size(packet, block_offset);
144
0
    if (block_length == 0)
145
0
    {
146
0
      break;
147
0
    }
148
149
0
    block_type = ntohs(get_u_int16_t(packet->payload, block_offset));
150
0
    if (ndpi_int_collectd_check_type(block_type) != 0)
151
0
    {
152
0
      break;
153
0
    } else {
154
0
      if (block_type == COLLECTD_TYPE_HOST)
155
0
      {
156
        /*
157
         * Dissect the hostname later, when we are sure that it is
158
         * the collectd protocol.
159
         */
160
0
        if(block_length > 4)
161
0
          hostname_length = block_length - 4; /* Ignore type and length fields */
162
0
      } else if (block_type == COLELCTD_TYPE_ENCR_AES256) {
163
        /*
164
         * The encrypted data block is a special case.
165
         * It is the only dissectable block as everything else in it
166
         * is encrypted.
167
         */
168
0
        if (block_length != packet->payload_packet_len ||
169
0
            block_length < COLLECTD_ENCR_AES256_MIN_BLOCK_SIZE ||
170
0
            ndpi_int_collectd_dissect_username(flow, packet) != 0)
171
0
        {
172
0
          NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
173
0
        } else {
174
0
          ndpi_int_collectd_add_connection(ndpi_struct, flow);
175
0
        }
176
0
        return;
177
0
      }
178
0
    }
179
0
  }
180
181
0
  if (num_blocks < COLLECTD_MIN_BLOCKS_REQUIRED)
182
0
  {
183
0
    NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
184
0
    return;
185
0
  }
186
187
0
  if (hostname_length > 0)
188
0
    ndpi_int_collectd_dissect_hostname(flow, packet, hostname_length);
189
190
0
  ndpi_int_collectd_add_connection(ndpi_struct, flow);
191
0
}
192
193
void init_collectd_dissector(struct ndpi_detection_module_struct *ndpi_struct)
194
1
{
195
1
  register_dissector("collectd", ndpi_struct,
196
1
                     ndpi_search_collectd,
197
1
                     NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_UDP_WITH_PAYLOAD,
198
1
                     1, NDPI_PROTOCOL_COLLECTD);
199
1
}