/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 | | /* **************************************************** */ |