Coverage Report

Created: 2025-11-09 07:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ndpi/src/lib/ndpi_community_id.c
Line
Count
Source
1
/*
2
 * ndpi_community_id.c
3
 *
4
 * Copyright (C) 2011-25 - ntop.org and contributors
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
#include <stdlib.h>
22
#include <errno.h>
23
#include <sys/types.h>
24
25
#include "ndpi_api.h"
26
#include "ndpi_config.h"
27
#include "ndpi_includes.h"
28
29
#include <time.h>
30
#ifndef WIN32
31
#include <unistd.h>
32
#endif
33
34
#if defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__
35
#include <sys/endian.h>
36
#endif
37
38
#include "ndpi_sha1.h"
39
40
3
#define NDPI_ICMP6_ECHO_REQUEST   128
41
3
#define NDPI_ICMP6_ECHO_REPLY   129
42
5
#define NDPI_MLD_LISTENER_QUERY   130
43
5
#define NDPI_MLD_LISTENER_REPORT  131
44
45
7
#define NDPI_ROUTER_SOLICIT   133
46
7
#define NDPI_ROUTER_ADVERT    134
47
8
#define NDPI_NEIGHBOR_SOLICIT   135
48
8
#define NDPI_NEIGHBOR_ADVERT    136
49
50
7
#define NDPI_ICMP_ECHOREPLY   0
51
7
#define NDPI_ICMP_ECHO      8
52
7
#define NDPI_ICMP_ROUTERADVERT    9
53
7
#define NDPI_ICMP_ROUTERSOLICIT   10
54
3
#define NDPI_ICMP_TIMESTAMP   13
55
3
#define NDPI_ICMP_TIMESTAMPREPLY  14
56
2
#define NDPI_ICMP_INFO_REQUEST    15
57
2
#define NDPI_ICMP_INFO_REPLY    16
58
5
#define NDPI_ICMP_MASKREQ   17
59
5
#define NDPI_ICMP_MASKREPLY   18
60
61
6
#define NDPI_ICMP6_WRUREQUEST   139
62
6
#define NDPI_ICMP6_WRUREPLY   140
63
64
/* **************************************************** */
65
66
2.55k
static u_int16_t ndpi_community_id_buf_copy(u_int8_t * const dst, const void * const src, u_int16_t len) {
67
2.55k
  memcpy(dst, src, len);
68
69
2.55k
  return len;
70
2.55k
}
71
72
/* **************************************************** */
73
74
/*
75
  https://github.com/corelight/community-id-spec/blob/bda913f617389df07cdaa23606e11bbd318e265c/community-id.py#L56
76
*/
77
38
static u_int8_t ndpi_community_id_icmp_type_to_code_v4(u_int8_t icmp_type, u_int8_t icmp_code, int *is_one_way) {
78
38
  *is_one_way = 0;
79
80
38
  switch(icmp_type) {
81
2
  case NDPI_ICMP_ECHO:
82
2
    return NDPI_ICMP_ECHOREPLY;
83
5
  case NDPI_ICMP_ECHOREPLY:
84
5
    return NDPI_ICMP_ECHO;
85
2
  case NDPI_ICMP_TIMESTAMP:
86
2
    return NDPI_ICMP_TIMESTAMPREPLY;
87
1
  case NDPI_ICMP_TIMESTAMPREPLY:
88
1
    return NDPI_ICMP_TIMESTAMP;
89
1
  case NDPI_ICMP_INFO_REQUEST:
90
1
    return NDPI_ICMP_INFO_REPLY;
91
1
  case NDPI_ICMP_INFO_REPLY:
92
1
    return NDPI_ICMP_INFO_REQUEST;
93
4
  case NDPI_ICMP_ROUTERSOLICIT:
94
4
    return NDPI_ICMP_ROUTERADVERT;
95
3
  case NDPI_ICMP_ROUTERADVERT:
96
3
    return NDPI_ICMP_ROUTERSOLICIT;
97
4
  case NDPI_ICMP_MASKREQ:
98
4
    return NDPI_ICMP_MASKREPLY;
99
1
  case NDPI_ICMP_MASKREPLY:
100
1
    return NDPI_ICMP_MASKREQ;
101
14
  default:
102
14
    *is_one_way = 1;
103
14
    return icmp_code;
104
38
  }
105
38
}
106
107
/* **************************************************** */
108
109
/*
110
  https://github.com/corelight/community-id-spec/blob/bda913f617389df07cdaa23606e11bbd318e265c/community-id.py#L83
111
*/
112
35
static u_int8_t ndpi_community_id_icmp_type_to_code_v6(u_int8_t icmp_type, u_int8_t icmp_code, int *is_one_way) {
113
35
  *is_one_way = 0;
114
115
35
  switch(icmp_type) {
116
2
  case NDPI_ICMP6_ECHO_REQUEST:
117
2
    return NDPI_ICMP6_ECHO_REPLY;
118
1
  case NDPI_ICMP6_ECHO_REPLY:
119
1
    return NDPI_ICMP6_ECHO_REQUEST;
120
2
  case NDPI_ROUTER_SOLICIT:
121
2
    return NDPI_ROUTER_ADVERT;
122
5
  case NDPI_ROUTER_ADVERT:
123
5
    return NDPI_ROUTER_SOLICIT;
124
3
  case NDPI_NEIGHBOR_SOLICIT:
125
3
    return NDPI_NEIGHBOR_ADVERT;
126
5
  case NDPI_NEIGHBOR_ADVERT:
127
5
    return NDPI_NEIGHBOR_SOLICIT;
128
3
  case NDPI_MLD_LISTENER_QUERY:
129
3
    return NDPI_MLD_LISTENER_REPORT;
130
2
  case NDPI_MLD_LISTENER_REPORT:
131
2
    return NDPI_MLD_LISTENER_QUERY;
132
3
  case NDPI_ICMP6_WRUREQUEST:
133
3
    return NDPI_ICMP6_WRUREPLY;
134
3
  case NDPI_ICMP6_WRUREPLY:
135
3
    return NDPI_ICMP6_WRUREQUEST;
136
  // Home Agent Address Discovery Request Message and reply
137
2
  case 144:
138
2
    return 145;
139
2
  case 145:
140
2
    return 144;
141
2
  default:
142
2
    *is_one_way = 1;
143
2
    return icmp_code;
144
35
  }
145
35
}
146
147
/* **************************************************** */
148
149
/* 
150
  https://github.com/corelight/community-id-spec/blob/bda913f617389df07cdaa23606e11bbd318e265c/community-id.py#L164
151
*/
152
188
static int ndpi_community_id_peer_v4_is_less_than(u_int32_t ip1, u_int32_t ip2, u_int16_t p1, u_int16_t p2) {
153
188
  int comp = memcmp(&ip1, &ip2, sizeof(u_int32_t));
154
188
  return comp < 0 || (comp == 0 && p1 < p2);
155
188
}
156
157
/* **************************************************** */
158
159
233
static int ndpi_community_id_peer_v6_is_less_than(const struct ndpi_in6_addr *ip1, const struct ndpi_in6_addr *ip2, u_int16_t p1, u_int16_t p2) {
160
233
  int comp = memcmp(ip1, ip2, sizeof(struct ndpi_in6_addr));
161
162
233
  return comp < 0 || (comp == 0 && p1 < p2);
163
233
}
164
165
/* **************************************************** */
166
167
437
void ndpi_string_sha1_hash(const uint8_t *message, size_t len, u_char *hash /* 20-bytes */) {
168
437
  SHA1_CTX ctx;
169
170
437
  SHA1Init(&ctx);
171
437
  SHA1Update(&ctx, message, len);
172
437
  SHA1Final(hash, &ctx);
173
437
}
174
175
/* **************************************************** */
176
177
/*
178
  https://github.com/corelight/community-id-spec/blob/bda913f617389df07cdaa23606e11bbd318e265c/community-id.py#L285
179
*/
180
static int ndpi_community_id_finalize_and_compute_hash(u_int8_t *comm_buf, u_int16_t off, u_int8_t l4_proto,
181
                   u_int16_t src_port, u_int16_t dst_port,
182
437
                   char *hash_buf, size_t hash_buf_len) {
183
437
  u_int8_t pad = 0;
184
437
  uint32_t hash[5];
185
437
  char *community_id;
186
187
  /* L4 proto */
188
437
  off += ndpi_community_id_buf_copy(&comm_buf[off], &l4_proto, sizeof(l4_proto));
189
190
  /* Pad */
191
437
  off += ndpi_community_id_buf_copy(&comm_buf[off], &pad, sizeof(pad));
192
193
  /* Source and destination ports */
194
437
  switch(l4_proto) {
195
43
  case IPPROTO_ICMP:
196
83
  case IPPROTO_ICMPV6:
197
109
  case NDPI_SCTP_PROTOCOL_TYPE:
198
133
  case IPPROTO_UDP:
199
185
  case IPPROTO_TCP:
200
185
    off += ndpi_community_id_buf_copy(&comm_buf[off], &src_port, sizeof(src_port));
201
185
    off += ndpi_community_id_buf_copy(&comm_buf[off], &dst_port, sizeof(dst_port));
202
185
    break;
203
437
  }
204
205
  /* Compute SHA1 */
206
437
  ndpi_string_sha1_hash(comm_buf, off, (u_char*)hash);
207
208
  /* Base64 encoding */
209
437
  community_id = ndpi_base64_encode((u_int8_t*)hash, sizeof(hash));
210
211
437
  if(community_id == NULL)
212
24
    return -1;
213
214
#if 0 /* Debug Info */
215
  printf("Hex output: ");
216
  for(int i = 0; i < off; i++)
217
    printf("%.2x ", comm_buf[i]);
218
  printf("\n");
219
220
  printf("Sha1 sum: ");
221
  for(int i = 0; i < 5; i++)
222
    printf("%.2x ", ntohl(hash[i]));
223
  printf("\n");
224
225
  printf("Base64: %s\n", community_id);
226
#endif
227
228
413
  if(hash_buf_len < 2 || hash_buf_len-2 < strlen(community_id)+1) {
229
206
    ndpi_free(community_id);
230
206
    return -1;
231
206
  }
232
233
  /* Writing hash */
234
207
  hash_buf[0] = '1';
235
207
  hash_buf[1] = ':';
236
207
  strcpy(&hash_buf[2], community_id);
237
207
  ndpi_free(community_id);
238
239
207
  return 0;
240
413
}
241
242
/* **************************************************** */
243
244
/*
245
  NOTE:
246
  - Leave fields empty/zero when information is missing (e.g. with ICMP ports are zero)
247
  - The hash_buf most be 30+1 bits or longer
248
  - Return code: 0 = OK, -1 otherwise
249
*/
250
251
int ndpi_flowv4_flow_hash(u_int8_t l4_proto, u_int32_t src_ip, u_int32_t dst_ip,
252
                          u_int16_t src_port, u_int16_t dst_port,
253
                          u_int8_t icmp_type, u_int8_t icmp_code,
254
202
                          u_char *hash_buf, u_int8_t hash_buf_len) {
255
  /*
256
    Input buffer (40 bytes)
257
     2 - Seed 
258
    16 - IPv6 src 
259
    16 - IPv6 dst
260
     1 - L4 proto
261
     1 - Pad 
262
     2 - Port src
263
     2 - Port dst
264
  */
265
202
  u_int8_t comm_buf[40] = { 0 };
266
202
  u_int16_t off = 0;
267
202
  u_int16_t seed = 0;
268
202
  u_int32_t *ip_a_ptr, *ip_b_ptr;
269
202
  u_int16_t port_a, port_b;
270
202
  int icmp_one_way = 0;
271
272
  /* Adjust the ports according to the specs */
273
202
  switch(l4_proto) {
274
38
  case IPPROTO_ICMP:
275
38
    src_port = icmp_type;
276
38
    dst_port = ndpi_community_id_icmp_type_to_code_v4(icmp_type, icmp_code, &icmp_one_way);
277
38
    break;
278
8
  case NDPI_SCTP_PROTOCOL_TYPE:
279
23
  case IPPROTO_UDP:
280
33
  case IPPROTO_TCP:
281
    /* src/dst port ok */
282
33
    break;
283
131
  default:
284
131
    src_port = dst_port = 0;
285
131
    break;
286
202
  }
287
288
  /* Convert tuple to NBO */
289
202
  src_ip = htonl(src_ip);
290
202
  dst_ip = htonl(dst_ip);
291
202
  src_port = htons(src_port);
292
202
  dst_port = htons(dst_port);
293
294
  /*
295
    The community id hash doesn't have the definition of client and server, it just sorts IP addresses
296
    and ports to make sure the smaller ip address is the first. This performs this check and
297
    possibly swap client ip and port.
298
  */
299
202
  if(icmp_one_way || ndpi_community_id_peer_v4_is_less_than(src_ip, dst_ip, src_port, dst_port)) {
300
52
    ip_a_ptr = &src_ip, ip_b_ptr = &dst_ip;
301
52
    port_a = src_port, port_b = dst_port;
302
150
  } else {
303
    /* swap flow peers */
304
150
    ip_a_ptr = &dst_ip, ip_b_ptr = &src_ip;
305
150
    port_a = dst_port, port_b = src_port;
306
150
  }
307
308
  /* Seed */
309
202
  off = ndpi_community_id_buf_copy(&comm_buf[off], &seed, sizeof(seed));
310
311
  /* Source and destination IPs */
312
202
  off += ndpi_community_id_buf_copy(&comm_buf[off], ip_a_ptr, sizeof(src_ip));
313
202
  off += ndpi_community_id_buf_copy(&comm_buf[off], ip_b_ptr, sizeof(dst_ip));
314
315
202
  return ndpi_community_id_finalize_and_compute_hash(comm_buf, off,
316
202
           l4_proto, port_a, port_b, (char*)hash_buf, hash_buf_len);
317
202
}
318
319
/* **************************************************** */
320
321
int ndpi_flowv6_flow_hash(u_int8_t l4_proto, const struct ndpi_in6_addr *src_ip, const struct ndpi_in6_addr *dst_ip,
322
                          u_int16_t src_port, u_int16_t dst_port,
323
                          u_int8_t icmp_type, u_int8_t icmp_code,
324
235
                          u_char *hash_buf, u_int8_t hash_buf_len) {
325
235
  u_int8_t comm_buf[40] = { 0 };
326
235
  u_int16_t off = 0;
327
235
  u_int16_t seed = 0;
328
235
  const struct ndpi_in6_addr *ip_a_ptr, *ip_b_ptr;
329
235
  u_int16_t port_a, port_b;
330
235
  int icmp_one_way = 0;
331
332
235
  switch(l4_proto) {
333
35
  case IPPROTO_ICMPV6:
334
35
    src_port = icmp_type;
335
35
    dst_port = ndpi_community_id_icmp_type_to_code_v6(icmp_type, icmp_code, &icmp_one_way);
336
35
    break;
337
18
  case NDPI_SCTP_PROTOCOL_TYPE:
338
27
  case IPPROTO_UDP:
339
69
  case IPPROTO_TCP:
340
    /* src/dst port ok */
341
69
    break;
342
131
  default:
343
131
    src_port = dst_port = 0;
344
131
    break;
345
235
  }
346
347
  /* Convert tuple to NBO */
348
235
  src_port = htons(src_port);
349
235
  dst_port = htons(dst_port);
350
351
235
  if(icmp_one_way || ndpi_community_id_peer_v6_is_less_than(src_ip, dst_ip, src_port, dst_port)) {
352
103
    ip_a_ptr = src_ip, ip_b_ptr = dst_ip;
353
103
    port_a = src_port, port_b = dst_port;
354
132
  } else {
355
132
    ip_a_ptr = dst_ip, ip_b_ptr = src_ip;
356
132
    port_a = dst_port, port_b = src_port;
357
132
  }
358
359
  /* Seed */
360
235
  off = ndpi_community_id_buf_copy(&comm_buf[off], &seed, sizeof(seed));
361
362
  /* Source and destination IPs */
363
235
  off += ndpi_community_id_buf_copy(&comm_buf[off], ip_a_ptr, sizeof(struct ndpi_in6_addr));
364
235
  off += ndpi_community_id_buf_copy(&comm_buf[off], ip_b_ptr, sizeof(struct ndpi_in6_addr));
365
366
235
  return ndpi_community_id_finalize_and_compute_hash(comm_buf, off,
367
235
           l4_proto, port_a, port_b, (char*)hash_buf, hash_buf_len);
368
235
}
369
370
/* **************************************************** */