master
c 1,893 lines 55.3 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <pthread.h>
6
7 #include "libnetdata/libnetdata.h"
8 #include "ebpf_library.h"
9 #include "../ebpf.h"
10 #include "../ebpf_process.h"
11 #include "../ebpf_socket.h"
12 #include <ifaddrs.h>
13
14 /*****************************************************************
15 *
16 * DIMENSION WRITING FUNCTIONS
17 *
18 *****************************************************************/
19
20 void write_chart_dimension(const char *dim, long long value)
21 {
22 printf("SET %s = %lld\n", dim, value);
23 }
24
25 void ebpf_write_global_dimension(char *name, char *id, char *algorithm)
26 {
27 printf("DIMENSION %s %s %s 1 1\n", name, id, algorithm);
28 }
29
30 void ebpf_create_global_dimension(void *ptr, int end)
31 {
32 netdata_publish_syscall_t *move = ptr;
33
34 int i = 0;
35 while (move && i < end) {
36 ebpf_write_global_dimension(move->name, move->dimension, move->algorithm);
37
38 move = move->next;
39 i++;
40 }
41 }
42
43 /*****************************************************************
44 *
45 * CHART WRITING FUNCTIONS
46 *
47 *****************************************************************/
48
49 void write_count_chart(char *name, char *family, netdata_publish_syscall_t *move, uint32_t end)
50 {
51 ebpf_write_begin_chart(family, name, "");
52
53 uint32_t i;
54 for (i = 0; move && i < end; i++) {
55 write_chart_dimension(move->name, move->ncall);
56 move = move->next;
57 }
58
59 ebpf_write_end_chart();
60 }
61
62 void write_err_chart(char *name, char *family, netdata_publish_syscall_t *move, int end)
63 {
64 ebpf_write_begin_chart(family, name, "");
65
66 int i;
67 for (i = 0; move && i < end; i++) {
68 write_chart_dimension(move->name, move->nerr);
69 move = move->next;
70 }
71
72 ebpf_write_end_chart();
73 }
74
75 void ebpf_one_dimension_write_charts(char *family, char *chart, char *dim, long long v1)
76 {
77 ebpf_write_begin_chart(family, chart, "");
78
79 write_chart_dimension(dim, v1);
80
81 ebpf_write_end_chart();
82 }
83
84 void write_io_chart(char *chart, char *family, char *dwrite, long long vwrite, char *dread, long long vread)
85 {
86 ebpf_write_begin_chart(family, chart, "");
87
88 write_chart_dimension(dwrite, vwrite);
89 write_chart_dimension(dread, vread);
90
91 ebpf_write_end_chart();
92 }
93
94 void write_histogram_chart(char *family, char *name, const uint64_t *hist, char **dimensions, uint32_t end)
95 {
96 ebpf_write_begin_chart(family, name, "");
97
98 uint32_t i;
99 for (i = 0; i < end; i++) {
100 write_chart_dimension(dimensions[i], (long long)hist[i]);
101 }
102
103 ebpf_write_end_chart();
104
105 fflush(stdout);
106 }
107
108 /*****************************************************************
109 *
110 * CHART CREATION FUNCTIONS
111 *
112 *****************************************************************/
113
114 void ebpf_write_chart_cmd(
115 char *type,
116 char *id,
117 char *suffix,
118 char *title,
119 char *units,
120 char *family,
121 char *charttype,
122 char *context,
123 int order,
124 int update_every,
125 char *module)
126 {
127 printf(
128 "CHART %s.%s%s '' '%s' '%s' '%s' '%s' '%s' %d %d '' 'ebpf.plugin' '%s'\n",
129 type,
130 id,
131 suffix,
132 title,
133 units,
134 (family) ? family : "",
135 (context) ? context : "",
136 (charttype) ? charttype : "",
137 order,
138 update_every,
139 module);
140 }
141
142 void ebpf_write_chart_obsolete(
143 char *type,
144 const char *id,
145 char *suffix,
146 char *title,
147 char *units,
148 char *family,
149 char *charttype,
150 const char *context,
151 int order,
152 int update_every)
153 {
154 printf(
155 "CHART %s.%s%s '' '%s' '%s' '%s' '%s' '%s' %d %d 'obsolete'\n",
156 type,
157 id,
158 suffix,
159 title,
160 units,
161 (family) ? family : "",
162 (context) ? context : "",
163 (charttype) ? charttype : "",
164 order,
165 update_every);
166 }
167
168 void ebpf_create_chart(
169 char *type,
170 char *id,
171 char *title,
172 char *units,
173 char *family,
174 char *context,
175 char *charttype,
176 int order,
177 void (*ncd)(void *, int),
178 void *move,
179 int end,
180 int update_every,
181 char *module)
182 {
183 ebpf_write_chart_cmd(type, id, "", title, units, family, charttype, context, order, update_every, module);
184
185 if (ncd) {
186 ncd(move, end);
187 }
188 }
189
190 /*****************************************************************
191 *
192 * ARAL STATISTIC CHARTS
193 *
194 *****************************************************************/
195
196 int ebpf_statistic_create_aral_chart(char *name, ebpf_module_t *em)
197 {
198 static int priority = NETDATA_EBPF_ORDER_STAT_ARAL_BEGIN;
199 static netdata_mutex_t priority_mutex;
200 static int priority_mutex_initialized = 0;
201
202 if (!priority_mutex_initialized) {
203 netdata_mutex_init(&priority_mutex);
204 priority_mutex_initialized = 1;
205 }
206
207 char *mem = NETDATA_EBPF_STAT_DIMENSION_MEMORY;
208 char *aral = NETDATA_EBPF_STAT_DIMENSION_ARAL;
209
210 snprintfz(em->memory_usage, NETDATA_EBPF_CHART_MEM_LENGTH - 1, "aral_%s_size", name);
211 snprintfz(em->memory_allocations, NETDATA_EBPF_CHART_MEM_LENGTH - 1, "aral_%s_alloc", name);
212
213 netdata_mutex_lock(&priority_mutex);
214 int ret_priority = priority;
215 priority += 2;
216 netdata_mutex_unlock(&priority_mutex);
217
218 ebpf_write_chart_cmd(
219 NETDATA_MONITORING_FAMILY,
220 em->memory_usage,
221 "",
222 "Bytes allocated for ARAL.",
223 "bytes",
224 NETDATA_EBPF_FAMILY,
225 NETDATA_EBPF_CHART_TYPE_STACKED,
226 "netdata.ebpf_aral_stat_size",
227 ret_priority,
228 em->update_every,
229 NETDATA_EBPF_MODULE_NAME_PROCESS);
230
231 ebpf_write_global_dimension(mem, mem, ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]);
232
233 ebpf_write_chart_cmd(
234 NETDATA_MONITORING_FAMILY,
235 em->memory_allocations,
236 "",
237 "Calls to allocate memory.",
238 "calls",
239 NETDATA_EBPF_FAMILY,
240 NETDATA_EBPF_CHART_TYPE_STACKED,
241 "netdata.ebpf_aral_stat_alloc",
242 ret_priority + 1,
243 em->update_every,
244 NETDATA_EBPF_MODULE_NAME_PROCESS);
245
246 ebpf_write_global_dimension(aral, aral, ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]);
247
248 return ret_priority;
249 }
250
251 void ebpf_statistic_obsolete_aral_chart(ebpf_module_t *em, int prio)
252 {
253 ebpf_write_chart_obsolete(
254 NETDATA_MONITORING_FAMILY,
255 em->memory_usage,
256 "",
257 "Bytes allocated for ARAL.",
258 "bytes",
259 NETDATA_EBPF_FAMILY,
260 NETDATA_EBPF_CHART_TYPE_STACKED,
261 "netdata.ebpf_aral_stat_size",
262 prio++,
263 em->update_every);
264
265 ebpf_write_chart_obsolete(
266 NETDATA_MONITORING_FAMILY,
267 em->memory_allocations,
268 "",
269 "Calls to allocate memory.",
270 "calls",
271 NETDATA_EBPF_FAMILY,
272 NETDATA_EBPF_CHART_TYPE_STACKED,
273 "netdata.ebpf_aral_stat_alloc",
274 prio++,
275 em->update_every);
276 }
277
278 void ebpf_send_data_aral_chart(ARAL *memory, ebpf_module_t *em)
279 {
280 if (!memory)
281 return;
282
283 char *mem = NETDATA_EBPF_STAT_DIMENSION_MEMORY;
284 char *aral = NETDATA_EBPF_STAT_DIMENSION_ARAL;
285
286 struct aral_statistics *stats = aral_get_statistics(memory);
287
288 ebpf_write_begin_chart(NETDATA_MONITORING_FAMILY, em->memory_usage, "");
289 write_chart_dimension(mem, (long long)stats->structures.allocated_bytes);
290 ebpf_write_end_chart();
291
292 ebpf_write_begin_chart(NETDATA_MONITORING_FAMILY, em->memory_allocations, "");
293 write_chart_dimension(aral, (long long)stats->structures.allocations);
294 ebpf_write_end_chart();
295 }
296
297 /*****************************************************************
298 *
299 * CONFIG FILE PARSER FUNCTIONS
300 *
301 *****************************************************************/
302
303 void ebpf_how_to_load(const char *ptr)
304 {
305 if (!strcasecmp(ptr, EBPF_CFG_LOAD_MODE_RETURN))
306 ebpf_set_thread_mode(MODE_RETURN);
307 else if (!strcasecmp(ptr, EBPF_CFG_LOAD_MODE_DEFAULT))
308 ebpf_set_thread_mode(MODE_ENTRY);
309 else
310 netdata_log_error("the option %s for \"ebpf load mode\" is not a valid option.", ptr);
311 }
312
313 void ebpf_set_apps_mode(netdata_apps_integration_flags_t value)
314 {
315 int i;
316 for (i = 0; i < EBPF_MODULE_FUNCTION_IDX; i++) {
317 ebpf_modules[i].apps_charts = value;
318 }
319 }
320
321 void ebpf_update_interval(int update_every)
322 {
323 int i;
324
325 int value = (int)inicfg_get_number(&collector_config, EBPF_GLOBAL_SECTION, EBPF_CFG_UPDATE_EVERY, update_every);
326
327 for (i = 0; ebpf_modules[i].info.thread_name; i++) {
328 ebpf_modules[i].update_every = value;
329 }
330 }
331
332 void ebpf_update_table_size()
333 {
334 uint32_t value = (uint32_t)inicfg_get_number(
335 &collector_config, EBPF_GLOBAL_SECTION, EBPF_CFG_PID_SIZE, ND_EBPF_DEFAULT_PID_SIZE);
336 for (int i = 0; ebpf_modules[i].info.thread_name; i++) {
337 ebpf_modules[i].pid_map_size = value;
338 }
339 }
340
341 void ebpf_update_lifetime()
342 {
343 uint32_t value =
344 (uint32_t)inicfg_get_number(&collector_config, EBPF_GLOBAL_SECTION, EBPF_CFG_LIFETIME, EBPF_DEFAULT_LIFETIME);
345
346 for (int i = 0; ebpf_modules[i].info.thread_name; i++) {
347 ebpf_modules[i].lifetime = value;
348 }
349 }
350
351 void ebpf_set_load_mode(netdata_ebpf_load_mode_t load, netdata_ebpf_load_mode_t origin)
352 {
353 int i;
354 for (i = 0; ebpf_modules[i].info.thread_name; i++) {
355 ebpf_modules[i].load &= ~NETDATA_EBPF_LOAD_METHODS;
356 ebpf_modules[i].load |= load | origin;
357 }
358 }
359
360 void ebpf_update_load_mode(const char *str, netdata_ebpf_load_mode_t origin)
361 {
362 netdata_ebpf_load_mode_t load = epbf_convert_string_to_load_mode(str);
363
364 ebpf_set_load_mode(load, origin);
365 }
366
367 void ebpf_update_map_per_core()
368 {
369 int value = inicfg_get_boolean(&collector_config, EBPF_GLOBAL_SECTION, EBPF_CFG_MAPS_PER_CORE, CONFIG_BOOLEAN_YES);
370
371 for (int i = 0; ebpf_modules[i].info.thread_name; i++) {
372 ebpf_modules[i].maps_per_core = value;
373 }
374 }
375
376 void ebpf_set_ipc_value(const char *integration)
377 {
378 if (!strcmp(integration, NETDATA_EBPF_IPC_INTEGRATION_SHM))
379 integration_with_collectors = NETDATA_EBPF_INTEGRATION_SHM;
380 else if (!strcmp(integration, NETDATA_EBPF_IPC_INTEGRATION_SOCKET))
381 integration_with_collectors = NETDATA_EBPF_INTEGRATION_SOCKET;
382 else
383 integration_with_collectors = NETDATA_EBPF_INTEGRATION_DISABLED;
384 }
385
386 void ebpf_parse_ipc_section()
387 {
388 const char *integration = inicfg_get(
389 &collector_config,
390 NETDATA_EBPF_IPC_SECTION,
391 NETDATA_EBPF_IPC_INTEGRATION,
392 NETDATA_EBPF_IPC_INTEGRATION_DISABLED);
393 ebpf_set_ipc_value(integration);
394
395 ipc_sockets.default_bind_to = inicfg_get(
396 &collector_config, NETDATA_EBPF_IPC_SECTION, NETDATA_EBPF_IPC_BIND_TO, NETDATA_EBPF_IPC_BIND_TO_DEFAULT);
397
398 ipc_sockets.backlog =
399 (int)inicfg_get_number(&collector_config, NETDATA_EBPF_IPC_SECTION, NETDATA_EBPF_IPC_BACKLOG, 20);
400 }
401
402 void ebpf_set_thread_mode(netdata_run_mode_t lmode)
403 {
404 int i;
405 for (i = 0; i < EBPF_MODULE_FUNCTION_IDX; i++) {
406 ebpf_modules[i].mode = lmode;
407 }
408 }
409
410 void ebpf_enable_specific_chart(ebpf_module_t *em, int disable_cgroup)
411 {
412 ebpf_module_enabled_set(em, NETDATA_THREAD_EBPF_RUNNING);
413
414 if (!disable_cgroup) {
415 em->cgroup_charts = CONFIG_BOOLEAN_YES;
416 }
417
418 em->global_charts = CONFIG_BOOLEAN_YES;
419 }
420
421 void ebpf_enable_chart(int idx, int disable_cgroup)
422 {
423 int i;
424 for (i = 0; ebpf_modules[i].info.thread_name; i++) {
425 if (i == idx) {
426 ebpf_enable_specific_chart(&ebpf_modules[i], disable_cgroup);
427 break;
428 }
429 }
430 }
431
432 int ebpf_load_collector_config(char *path, int *disable_cgroups, int update_every)
433 {
434 char lpath[4096];
435 netdata_ebpf_load_mode_t origin;
436
437 snprintf(lpath, 4095, "%s/%s", path, NETDATA_EBPF_CONFIG_FILE);
438 if (!inicfg_load(&collector_config, lpath, 0, NULL)) {
439 snprintf(lpath, 4095, "%s/%s", path, NETDATA_EBPF_OLD_CONFIG_FILE);
440 if (!inicfg_load(&collector_config, lpath, 0, NULL)) {
441 return -1;
442 }
443 origin = EBPF_LOADED_FROM_STOCK;
444 } else
445 origin = EBPF_LOADED_FROM_USER;
446
447 read_collector_values(disable_cgroups, update_every, origin);
448 ebpf_parse_ipc_section();
449
450 return 0;
451 }
452
453 void ebpf_load_thread_config()
454 {
455 int i;
456 for (i = 0; i < EBPF_MODULE_FUNCTION_IDX; i++) {
457 ebpf_update_module(&ebpf_modules[i], default_btf, running_on_kernel, isrh);
458 }
459 }
460
461 void read_collector_values(int *disable_cgroups, int update_every, netdata_ebpf_load_mode_t origin)
462 {
463 const char *value;
464 if (inicfg_exists(&collector_config, EBPF_GLOBAL_SECTION, "load"))
465 value = inicfg_get(&collector_config, EBPF_GLOBAL_SECTION, "load", EBPF_CFG_LOAD_MODE_DEFAULT);
466 else
467 value = inicfg_get(&collector_config, EBPF_GLOBAL_SECTION, EBPF_CFG_LOAD_MODE, EBPF_CFG_LOAD_MODE_DEFAULT);
468
469 ebpf_how_to_load(value);
470
471 btf_path = inicfg_get(&collector_config, EBPF_GLOBAL_SECTION, EBPF_CFG_PROGRAM_PATH, EBPF_DEFAULT_BTF_PATH);
472
473 #ifdef LIBBPF_MAJOR_VERSION
474 default_btf = ebpf_load_btf_file(btf_path, EBPF_DEFAULT_BTF_FILE);
475 #endif
476
477 value = inicfg_get(&collector_config, EBPF_GLOBAL_SECTION, EBPF_CFG_TYPE_FORMAT, EBPF_CFG_DEFAULT_PROGRAM);
478
479 ebpf_update_load_mode(value, origin);
480
481 ebpf_update_interval(update_every);
482
483 ebpf_update_table_size();
484
485 ebpf_update_lifetime();
486
487 uint32_t enabled = inicfg_get_boolean(&collector_config, EBPF_GLOBAL_SECTION, "disable apps", CONFIG_BOOLEAN_NO);
488 if (!enabled) {
489 // `application` is a positive option, but the legacy `disable apps`
490 // setting is negative. Preserve the original compatibility semantics.
491 enabled = inicfg_get_boolean(&collector_config, EBPF_GLOBAL_SECTION, EBPF_CFG_APPLICATION, CONFIG_BOOLEAN_YES);
492 enabled = (enabled == CONFIG_BOOLEAN_NO) ? CONFIG_BOOLEAN_YES : CONFIG_BOOLEAN_NO;
493 }
494
495 ebpf_set_apps_mode(!enabled);
496
497 enabled = inicfg_get_boolean(&collector_config, EBPF_GLOBAL_SECTION, EBPF_CFG_CGROUP, CONFIG_BOOLEAN_NO);
498 *disable_cgroups = (enabled == CONFIG_BOOLEAN_NO) ? CONFIG_BOOLEAN_YES : CONFIG_BOOLEAN_NO;
499
500 ebpf_update_map_per_core();
501
502 enabled = inicfg_get_boolean(
503 &collector_config,
504 EBPF_PROGRAMS_SECTION,
505 ebpf_modules[EBPF_MODULE_PROCESS_IDX].info.config_name,
506 CONFIG_BOOLEAN_YES);
507 if (enabled) {
508 ebpf_enable_chart(EBPF_MODULE_PROCESS_IDX, *disable_cgroups);
509 }
510
511 enabled = inicfg_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "network viewer", CONFIG_BOOLEAN_NO);
512 if (!enabled)
513 enabled = inicfg_get_boolean(
514 &collector_config,
515 EBPF_PROGRAMS_SECTION,
516 ebpf_modules[EBPF_MODULE_SOCKET_IDX].info.config_name,
517 CONFIG_BOOLEAN_NO);
518 if (enabled) {
519 ebpf_enable_chart(EBPF_MODULE_SOCKET_IDX, *disable_cgroups);
520 }
521
522 enabled = inicfg_get_boolean(
523 &collector_config, EBPF_PROGRAMS_SECTION, "network connection monitoring", CONFIG_BOOLEAN_YES);
524 if (!enabled)
525 enabled =
526 inicfg_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "network connections", CONFIG_BOOLEAN_YES);
527
528 network_viewer_opt.enabled = enabled;
529 if (enabled) {
530 if (!ebpf_module_enabled_get(&ebpf_modules[EBPF_MODULE_SOCKET_IDX]))
531 ebpf_enable_chart(EBPF_MODULE_SOCKET_IDX, *disable_cgroups);
532
533 parse_network_viewer_section(&collector_config);
534 ebpf_parse_service_name_section(&collector_config);
535 }
536
537 enabled = inicfg_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "cachestat", CONFIG_BOOLEAN_NO);
538 if (enabled) {
539 ebpf_enable_chart(EBPF_MODULE_CACHESTAT_IDX, *disable_cgroups);
540 }
541
542 enabled = inicfg_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "sync", CONFIG_BOOLEAN_YES);
543 if (enabled) {
544 ebpf_enable_chart(EBPF_MODULE_SYNC_IDX, *disable_cgroups);
545 }
546
547 enabled = inicfg_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "dcstat", CONFIG_BOOLEAN_NO);
548 if (enabled) {
549 ebpf_enable_chart(EBPF_MODULE_DCSTAT_IDX, *disable_cgroups);
550 }
551
552 enabled = inicfg_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "swap", CONFIG_BOOLEAN_NO);
553 if (enabled) {
554 ebpf_enable_chart(EBPF_MODULE_SWAP_IDX, *disable_cgroups);
555 }
556
557 enabled = inicfg_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "vfs", CONFIG_BOOLEAN_NO);
558 if (enabled) {
559 ebpf_enable_chart(EBPF_MODULE_VFS_IDX, *disable_cgroups);
560 }
561
562 enabled = inicfg_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "filesystem", CONFIG_BOOLEAN_NO);
563 if (enabled) {
564 ebpf_enable_chart(EBPF_MODULE_FILESYSTEM_IDX, *disable_cgroups);
565 }
566
567 enabled = inicfg_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "disk", CONFIG_BOOLEAN_NO);
568 if (enabled) {
569 ebpf_enable_chart(EBPF_MODULE_DISK_IDX, *disable_cgroups);
570 }
571
572 enabled = inicfg_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "mount", CONFIG_BOOLEAN_YES);
573 if (enabled) {
574 ebpf_enable_chart(EBPF_MODULE_MOUNT_IDX, *disable_cgroups);
575 }
576
577 enabled = inicfg_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "fd", CONFIG_BOOLEAN_YES);
578 if (enabled) {
579 ebpf_enable_chart(EBPF_MODULE_FD_IDX, *disable_cgroups);
580 }
581
582 enabled = inicfg_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "hardirq", CONFIG_BOOLEAN_YES);
583 if (enabled) {
584 ebpf_enable_chart(EBPF_MODULE_HARDIRQ_IDX, *disable_cgroups);
585 }
586
587 enabled = inicfg_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "softirq", CONFIG_BOOLEAN_YES);
588 if (enabled) {
589 ebpf_enable_chart(EBPF_MODULE_SOFTIRQ_IDX, *disable_cgroups);
590 }
591
592 enabled = inicfg_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "oomkill", CONFIG_BOOLEAN_YES);
593 if (enabled) {
594 ebpf_enable_chart(EBPF_MODULE_OOMKILL_IDX, *disable_cgroups);
595 }
596
597 enabled = inicfg_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "shm", CONFIG_BOOLEAN_YES);
598 if (enabled) {
599 ebpf_enable_chart(EBPF_MODULE_SHM_IDX, *disable_cgroups);
600 }
601
602 enabled = inicfg_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "mdflush", CONFIG_BOOLEAN_NO);
603 if (enabled) {
604 ebpf_enable_chart(EBPF_MODULE_MDFLUSH_IDX, *disable_cgroups);
605 }
606 }
607
608 /**
609 * Link hostname
610 *
611 * @param out is the output link list
612 * @param in the hostname to add to list.
613 */
614 static void ebpf_link_hostname(ebpf_network_viewer_hostname_list_t **out, ebpf_network_viewer_hostname_list_t *in)
615 {
616 if (likely(*out)) {
617 ebpf_network_viewer_hostname_list_t *move = *out;
618 for (; move->next; move = move->next) {
619 if (move->hash == in->hash && !strcmp(move->value, in->value)) {
620 netdata_log_info("The hostname %s was already inserted, it will be ignored.", in->value);
621 freez(in->value);
622 simple_pattern_free(in->value_pattern);
623 freez(in);
624 return;
625 }
626 }
627
628 move->next = in;
629 } else {
630 *out = in;
631 }
632 #ifdef NETDATA_INTERNAL_CHECKS
633 netdata_log_info(
634 "Adding value %s to %s hostname list used on network viewer",
635 in->value,
636 (*out == network_viewer_opt.included_hostnames) ? "included" : "excluded");
637 #endif
638 }
639
640 /**
641 * Link Hostnames
642 *
643 * Parse the list of hostnames to create the link list.
644 * This is not associated with the IP, because simple patterns like *example* cannot be resolved to IP.
645 *
646 * @param out is the output link list
647 * @param parse is a pointer with the text to parser.
648 */
649 static void ebpf_link_hostnames(const char *parse)
650 {
651 // No value
652 if (unlikely(!parse))
653 return;
654
655 char *move = strdupz(parse);
656 char *clean = move;
657 while (likely(move)) {
658 // Find the first valid value
659 while (isspace(*move))
660 move++;
661
662 // No valid value found
663 if (unlikely(!*move)) {
664 freez(clean);
665 return;
666 }
667
668 // Find space that ends the list
669 char *end = strchr(move, ' ');
670 if (end) {
671 *end++ = '\0';
672 }
673
674 bool neg = false;
675 if (*move == '!') {
676 neg = true;
677 move++;
678 }
679
680 ebpf_network_viewer_hostname_list_t *hostname = callocz(1, sizeof(ebpf_network_viewer_hostname_list_t));
681 hostname->value = strdupz(move);
682 hostname->hash = simple_hash(move);
683 hostname->value_pattern = simple_pattern_create(move, NULL, SIMPLE_PATTERN_EXACT, true);
684
685 ebpf_link_hostname(
686 (!neg) ? &network_viewer_opt.included_hostnames : &network_viewer_opt.excluded_hostnames, hostname);
687
688 move = end;
689 }
690 freez(clean);
691 }
692
693 void parse_network_viewer_section(struct config *cfg)
694 {
695 network_viewer_opt.hostname_resolution_enabled =
696 inicfg_get_boolean(cfg, EBPF_NETWORK_VIEWER_SECTION, EBPF_CONFIG_RESOLVE_HOSTNAME, CONFIG_BOOLEAN_NO);
697
698 network_viewer_opt.service_resolution_enabled =
699 inicfg_get_boolean(cfg, EBPF_NETWORK_VIEWER_SECTION, EBPF_CONFIG_RESOLVE_SERVICE, CONFIG_BOOLEAN_YES);
700
701 const char *value = inicfg_get(cfg, EBPF_NETWORK_VIEWER_SECTION, EBPF_CONFIG_PORTS, NULL);
702 ebpf_parse_ports(value);
703
704 if (network_viewer_opt.hostname_resolution_enabled) {
705 value = inicfg_get(cfg, EBPF_NETWORK_VIEWER_SECTION, EBPF_CONFIG_HOSTNAMES, NULL);
706 ebpf_link_hostnames(value);
707 } else {
708 netdata_log_info("Name resolution is disabled, collector will not parse \"hostnames\" list.");
709 }
710
711 value = inicfg_get(cfg, EBPF_NETWORK_VIEWER_SECTION, "ips", NULL);
712 ebpf_parse_ips_unsafe(value);
713 }
714
715 /*****************************************************************
716 *
717 * IP PARSING FUNCTIONS
718 *
719 *****************************************************************/
720
721 /**
722 * Netmask
723 *
724 * Copied from iprange (https://github.com/firehol/iprange/blob/master/iprange.h)
725 *
726 * @param prefix create the netmask based in the CIDR value.
727 *
728 * @return
729 */
730 static inline in_addr_t ebpf_netmask(int prefix)
731 {
732 if (prefix == 0)
733 return (~((in_addr_t)-1));
734 else
735 return (in_addr_t)(~((1 << (32 - prefix)) - 1));
736 }
737
738 /**
739 * Broadcast
740 *
741 * Copied from iprange (https://github.com/firehol/iprange/blob/master/iprange.h)
742 *
743 * @param addr is the ip address
744 * @param prefix is the CIDR value.
745 *
746 * @return It returns the last address of the range
747 */
748 static inline in_addr_t ebpf_broadcast(in_addr_t addr, int prefix)
749 {
750 return (addr | ~ebpf_netmask(prefix));
751 }
752
753 /**
754 * Network
755 *
756 * Copied from iprange (https://github.com/firehol/iprange/blob/master/iprange.h)
757 *
758 * @param addr is the ip address
759 * @param prefix is the CIDR value.
760 *
761 * @return It returns the first address of the range.
762 */
763 static inline in_addr_t ebpf_ipv4_network(in_addr_t addr, int prefix)
764 {
765 return (addr & ebpf_netmask(prefix));
766 }
767
768 /**
769 * Calculate ipv6 first address
770 *
771 * @param out the address to store the first address.
772 * @param in the address used to do the math.
773 * @param prefix number of bits used to calculate the address
774 */
775 static void get_ipv6_first_addr(union netdata_ip_t *out, union netdata_ip_t *in, uint64_t prefix)
776 {
777 uint64_t mask, tmp;
778 uint64_t ret[2];
779
780 memcpy(ret, in->addr32, sizeof(union netdata_ip_t));
781
782 if (prefix == 128) {
783 memcpy(out->addr32, in->addr32, sizeof(union netdata_ip_t));
784 return;
785 } else if (!prefix) {
786 ret[0] = ret[1] = 0;
787 memcpy(out->addr32, ret, sizeof(union netdata_ip_t));
788 return;
789 } else if (prefix <= 64) {
790 ret[1] = 0ULL;
791
792 tmp = be64toh(ret[0]);
793 mask = 0xFFFFFFFFFFFFFFFFULL << (64 - prefix);
794 tmp &= mask;
795 ret[0] = htobe64(tmp);
796 } else {
797 mask = 0xFFFFFFFFFFFFFFFFULL << (128 - prefix);
798 tmp = be64toh(ret[1]);
799 tmp &= mask;
800 ret[1] = htobe64(tmp);
801 }
802
803 memcpy(out->addr32, ret, sizeof(union netdata_ip_t));
804 }
805
806 /**
807 * Get IPV6 Last Address
808 *
809 * @param out the address to store the last address.
810 * @param in the address used to do the math.
811 * @param prefix number of bits used to calculate the address
812 */
813 static void get_ipv6_last_addr(union netdata_ip_t *out, union netdata_ip_t *in, uint64_t prefix)
814 {
815 uint64_t mask, tmp;
816 uint64_t ret[2];
817 memcpy(ret, in->addr32, sizeof(union netdata_ip_t));
818
819 if (prefix == 128) {
820 memcpy(out->addr32, in->addr32, sizeof(union netdata_ip_t));
821 return;
822 } else if (!prefix) {
823 ret[0] = ret[1] = 0xFFFFFFFFFFFFFFFF;
824 memcpy(out->addr32, ret, sizeof(union netdata_ip_t));
825 return;
826 } else if (prefix <= 64) {
827 ret[1] = 0xFFFFFFFFFFFFFFFFULL;
828
829 tmp = be64toh(ret[0]);
830 mask = 0xFFFFFFFFFFFFFFFFULL << (64 - prefix);
831 tmp |= ~mask;
832 ret[0] = htobe64(tmp);
833 } else {
834 mask = 0xFFFFFFFFFFFFFFFFULL << (128 - prefix);
835 tmp = be64toh(ret[1]);
836 tmp |= ~mask;
837 ret[1] = htobe64(tmp);
838 }
839
840 memcpy(out->addr32, ret, sizeof(union netdata_ip_t));
841 }
842
843 /**
844 * IP to network long
845 *
846 * @param dst the vector to store the result
847 * @param ip the source ip given by our users.
848 * @param domain the ip domain (IPV4 or IPV6)
849 * @param source the original string
850 *
851 * @return it returns 0 on success and -1 otherwise.
852 */
853 static inline int ebpf_ip2nl(uint8_t *dst, const char *ip, int domain, char *source)
854 {
855 if (inet_pton(domain, ip, dst) <= 0) {
856 netdata_log_error("The address specified (%s) is invalid ", source);
857 return -1;
858 }
859
860 return 0;
861 }
862
863 /**
864 * Clean IP structure
865 *
866 * Clean the allocated list.
867 *
868 * @param clean the list that will be cleaned
869 */
870 void ebpf_clean_ip_structure(ebpf_network_viewer_ip_list_t **clean)
871 {
872 ebpf_network_viewer_ip_list_t *move = *clean;
873 while (move) {
874 ebpf_network_viewer_ip_list_t *next = move->next;
875 freez(move->value);
876 freez(move);
877 move = next;
878 }
879 *clean = NULL;
880 }
881
882 /**
883 * Clean port structure
884 *
885 * Clean the allocated list.
886 *
887 * @param clean the list that will be cleaned
888 */
889 void ebpf_clean_port_structure(ebpf_network_viewer_port_list_t **clean)
890 {
891 ebpf_network_viewer_port_list_t *move = *clean;
892 while (move) {
893 ebpf_network_viewer_port_list_t *next = move->next;
894 freez(move->value);
895 freez(move);
896 move = next;
897 }
898 *clean = NULL;
899 }
900
901 /**
902 * Parse IP List
903 *
904 * Parse IP list and link it.
905 *
906 * @param out a pointer to store the link list
907 * @param ip the value given as parameter
908 */
909 static void ebpf_parse_ip_list_unsafe(void **out, const char *ip)
910 {
911 ebpf_network_viewer_ip_list_t **list = (ebpf_network_viewer_ip_list_t **)out;
912
913 char *ipdup = strdupz(ip);
914 union netdata_ip_t first = {};
915 union netdata_ip_t last = {};
916 const char *is_ipv6;
917 if (*ip == '*' && *(ip + 1) == '\0') {
918 memset(first.addr8, 0, sizeof(first.addr8));
919 memset(last.addr8, 0xFF, sizeof(last.addr8));
920
921 is_ipv6 = ip;
922
923 ebpf_clean_ip_structure(list);
924 goto storethisip;
925 }
926
927 char *end = strdupz(ip);
928 char *clean_end = end;
929 // Move while I cannot find a separator
930 while (*end && *end != '/' && *end != '-')
931 end++;
932
933 // We will use only the classic IPV6 for while, but we could consider the base 85 in a near future
934 // https://tools.ietf.org/html/rfc1924
935 is_ipv6 = strchr(ip, ':');
936
937 int select;
938 if (*end && !is_ipv6) { // IPV4 range
939 select = (*end == '/') ? 0 : 1;
940 *end++ = '\0';
941 if (*end == '!') {
942 netdata_log_info("The exclusion cannot be in the second part of the range %s, it will be ignored.", ipdup);
943 goto cleanipdup;
944 }
945
946 if (!select) { // CIDR
947 select = ebpf_ip2nl(first.addr8, ip, AF_INET, ipdup);
948 if (select)
949 goto cleanipdup;
950
951 select = (int)str2i(end);
952 if (select < NETDATA_MINIMUM_IPV4_CIDR || select > NETDATA_MAXIMUM_IPV4_CIDR) {
953 netdata_log_info("The specified CIDR %s is not valid, the IP %s will be ignored.", end, ip);
954 goto cleanipdup;
955 }
956
957 uint32_t ipv4_test = htonl(ebpf_ipv4_network(ntohl(first.addr32[0]), select));
958 if (first.addr32[0] != ipv4_test) {
959 first.addr32[0] = ipv4_test;
960 struct in_addr ipv4_convert;
961 ipv4_convert.s_addr = ipv4_test;
962 char ipv4_msg[INET_ADDRSTRLEN];
963 if (inet_ntop(AF_INET, &ipv4_convert, ipv4_msg, INET_ADDRSTRLEN))
964 netdata_log_info("The network value of CIDR %s was updated for %s .", ipdup, ipv4_msg);
965 }
966
967 last.addr32[0] = htonl(ebpf_broadcast(ntohl(first.addr32[0]), select));
968 } else { // Range
969 select = ebpf_ip2nl(first.addr8, ip, AF_INET, ipdup);
970 if (select)
971 goto cleanipdup;
972
973 select = ebpf_ip2nl(last.addr8, end, AF_INET, ipdup);
974 if (select)
975 goto cleanipdup;
976 }
977
978 if (ntohl(first.addr32[0]) > ntohl(last.addr32[0])) {
979 netdata_log_info(
980 "The specified range %s is invalid, the second address is smallest than the first, it will be ignored.",
981 ipdup);
982 goto cleanipdup;
983 }
984 } else if (is_ipv6) { // IPV6
985 if (!*end) { // Unique
986 select = ebpf_ip2nl(first.addr8, ip, AF_INET6, ipdup);
987 if (select)
988 goto cleanipdup;
989
990 memcpy(last.addr8, first.addr8, sizeof(first.addr8));
991 } else if (*end == '-') {
992 *end++ = 0x00;
993 if (*end == '!') {
994 netdata_log_info(
995 "The exclusion cannot be in the second part of the range %s, it will be ignored.", ipdup);
996 goto cleanipdup;
997 }
998
999 select = ebpf_ip2nl(first.addr8, ip, AF_INET6, ipdup);
1000 if (select)
1001 goto cleanipdup;
1002
1003 select = ebpf_ip2nl(last.addr8, end, AF_INET6, ipdup);
1004 if (select)
1005 goto cleanipdup;
1006 } else { // CIDR
1007 *end++ = 0x00;
1008 if (*end == '!') {
1009 netdata_log_info(
1010 "The exclusion cannot be in the second part of the range %s, it will be ignored.", ipdup);
1011 goto cleanipdup;
1012 }
1013
1014 select = str2i(end);
1015 if (select < 0 || select > 128) {
1016 netdata_log_info("The CIDR %s is not valid, the address %s will be ignored.", end, ip);
1017 goto cleanipdup;
1018 }
1019
1020 uint64_t prefix = (uint64_t)select;
1021 select = ebpf_ip2nl(first.addr8, ip, AF_INET6, ipdup);
1022 if (select)
1023 goto cleanipdup;
1024
1025 get_ipv6_last_addr(&last, &first, prefix);
1026
1027 union netdata_ip_t ipv6_test;
1028 get_ipv6_first_addr(&ipv6_test, &first, prefix);
1029
1030 if (memcmp(first.addr8, ipv6_test.addr8, sizeof(union netdata_ip_t)) != 0) {
1031 memcpy(first.addr8, ipv6_test.addr8, sizeof(union netdata_ip_t));
1032
1033 struct in6_addr ipv6_convert;
1034 memcpy(ipv6_convert.s6_addr, ipv6_test.addr8, sizeof(union netdata_ip_t));
1035
1036 char ipv6_msg[INET6_ADDRSTRLEN];
1037 if (inet_ntop(AF_INET6, &ipv6_convert, ipv6_msg, INET6_ADDRSTRLEN))
1038 netdata_log_info("The network value of CIDR %s was updated for %s .", ipdup, ipv6_msg);
1039 }
1040 }
1041
1042 if ((be64toh(*(uint64_t *)&first.addr64[1]) > be64toh(*(uint64_t *)&last.addr64[1]) &&
1043 memcmp(first.addr64, last.addr64, sizeof(uint64_t)) == 0) ||
1044 (be64toh(*(uint64_t *)&first.addr64) > be64toh(*(uint64_t *)&last.addr64))) {
1045 netdata_log_info(
1046 "The specified range %s is invalid, the second address is smallest than the first, it will be ignored.",
1047 ipdup);
1048 goto cleanipdup;
1049 }
1050 } else { // Unique ip
1051 select = ebpf_ip2nl(first.addr8, ip, AF_INET, ipdup);
1052 if (select)
1053 goto cleanipdup;
1054
1055 memcpy(last.addr8, first.addr8, sizeof(first.addr8));
1056 }
1057
1058 ebpf_network_viewer_ip_list_t *store;
1059
1060 storethisip:
1061 store = callocz(1, sizeof(ebpf_network_viewer_ip_list_t));
1062 store->value = ipdup;
1063 store->hash = simple_hash(ipdup);
1064 store->ver = (uint8_t)(!is_ipv6) ? AF_INET : AF_INET6;
1065 memcpy(store->first.addr8, first.addr8, sizeof(first.addr8));
1066 memcpy(store->last.addr8, last.addr8, sizeof(last.addr8));
1067
1068 ebpf_fill_ip_list_unsafe(list, store, "socket");
1069 return;
1070
1071 cleanipdup:
1072 freez(ipdup);
1073 freez(clean_end);
1074 }
1075
1076 /**
1077 * Check if the ip is inside a IP range
1078 *
1079 * @param rfirst the first ip address of the range
1080 * @param rlast the last ip address of the range
1081 * @param cmpfirst the first ip to compare
1082 * @param cmplast the last ip to compare
1083 * @param family the IP family
1084 *
1085 * @return It returns 1 if the IP is inside the range and 0 otherwise
1086 */
1087 static int ebpf_is_ip_inside_range(
1088 union netdata_ip_t *rfirst,
1089 union netdata_ip_t *rlast,
1090 union netdata_ip_t *cmpfirst,
1091 union netdata_ip_t *cmplast,
1092 int family)
1093 {
1094 if (family == AF_INET) {
1095 if ((rfirst->addr32[0] <= cmpfirst->addr32[0]) && (rlast->addr32[0] >= cmplast->addr32[0]))
1096 return 1;
1097 } else {
1098 if (memcmp(rfirst->addr8, cmpfirst->addr8, sizeof(union netdata_ip_t)) <= 0 &&
1099 memcmp(rlast->addr8, cmplast->addr8, sizeof(union netdata_ip_t)) >= 0) {
1100 return 1;
1101 }
1102 }
1103 return 0;
1104 }
1105
1106 /**
1107 * Fill IP list
1108 *
1109 * @param out a pointer to link list.
1110 * @param in the structure that will be linked.
1111 * @param table the modified table.
1112 */
1113 void ebpf_fill_ip_list_unsafe(
1114 ebpf_network_viewer_ip_list_t **out,
1115 ebpf_network_viewer_ip_list_t *in,
1116 char *table __maybe_unused)
1117 {
1118 if (in->ver == AF_INET) {
1119 in->first.addr32[0] = ntohl(in->first.addr32[0]);
1120 in->last.addr32[0] = ntohl(in->last.addr32[0]);
1121 }
1122 if (likely(*out)) {
1123 ebpf_network_viewer_ip_list_t *move = *out;
1124 while (move) {
1125 if (in->ver == move->ver &&
1126 ebpf_is_ip_inside_range(&move->first, &move->last, &in->first, &in->last, in->ver)) {
1127 #ifdef NETDATA_DEV_MODE
1128 netdata_log_info(
1129 "The range/value (%s) is inside the range/value (%s) already inserted, it will be ignored.",
1130 in->value,
1131 move->value);
1132 #endif
1133 freez(in->value);
1134 freez(in);
1135 return;
1136 }
1137 move = move->next;
1138 }
1139 move = *out;
1140 while (move->next)
1141 move = move->next;
1142 move->next = in;
1143 } else {
1144 *out = in;
1145 }
1146
1147 #ifdef NETDATA_DEV_MODE
1148 char first[256], last[512];
1149 if (in->ver == AF_INET) {
1150 netdata_log_info(
1151 "Adding values %s: (%u - %u) to %s IP list \"%s\" used on network viewer",
1152 in->value,
1153 in->first.addr32[0],
1154 in->last.addr32[0],
1155 (*out == network_viewer_opt.included_ips) ? "included" : "excluded",
1156 table);
1157 } else {
1158 if (inet_ntop(AF_INET6, in->first.addr8, first, INET6_ADDRSTRLEN) &&
1159 inet_ntop(AF_INET6, in->last.addr8, last, INET6_ADDRSTRLEN))
1160 netdata_log_info(
1161 "Adding values %s - %s to %s IP list \"%s\" used on network viewer",
1162 first,
1163 last,
1164 (*out == network_viewer_opt.included_ips) ? "included" : "excluded",
1165 table);
1166 }
1167 #endif
1168 }
1169
1170 /**
1171 * Parse IP Range
1172 *
1173 * Parse the IP ranges given and create Network Viewer IP Structure
1174 *
1175 * @param ptr is a pointer with the text to parse.
1176 */
1177 void ebpf_parse_ips_unsafe(const char *ptr)
1178 {
1179 // No value
1180 if (unlikely(!ptr))
1181 return;
1182
1183 while (likely(ptr)) {
1184 // Move forward until next valid character
1185 while (isspace(*ptr))
1186 ptr++;
1187
1188 // No valid value found
1189 if (unlikely(!*ptr))
1190 return;
1191
1192 // Find space that ends the list
1193 char *end = strchr(ptr, ' ');
1194 if (end) {
1195 *end++ = '\0';
1196 }
1197
1198 bool neg = false;
1199 if (*ptr == '!') {
1200 neg = true;
1201 ptr++;
1202 }
1203
1204 if (isascii(*ptr)) {
1205 ebpf_parse_ip_list_unsafe(
1206 neg ? (void **)&network_viewer_opt.excluded_ips : (void **)&network_viewer_opt.included_ips, ptr);
1207 }
1208
1209 ptr = end;
1210 }
1211 }
1212 /*****************************************************************
1213 *
1214 * FUNCTIONS TO CREATE CHARTS
1215 *
1216 *****************************************************************/
1217
1218 /**
1219 * Create apps for module
1220 *
1221 * Create apps chart that will be used with specific module
1222 *
1223 * @param em the module main structure.
1224 * @param root a pointer for the targets.
1225 */
1226 void ebpf_create_apps_for_module(ebpf_module_t *em, ebpf_target_t *root)
1227 {
1228 if (ebpf_module_enabled_get(em) < NETDATA_THREAD_EBPF_STOPPING && em->apps_charts && em->functions.apps_routine)
1229 em->functions.apps_routine(em, root);
1230 }
1231
1232 /**
1233 * Create apps charts
1234 *
1235 * Call ebpf_create_chart to create the charts on apps submenu.
1236 *
1237 * @param root a pointer for the targets.
1238 */
1239 void ebpf_create_apps_charts(ebpf_target_t *root)
1240 {
1241 // if (unlikely(!ebpf_pids))
1242 // return;
1243
1244 struct ebpf_target *w;
1245 int newly_added = 0;
1246
1247 for (w = root; w; w = w->next) {
1248 if (w->target)
1249 continue;
1250
1251 if (unlikely(w->processes && (debug_enabled || w->debug_enabled))) {
1252 struct ebpf_pid_on_target *pid_on_target;
1253
1254 fprintf(
1255 stderr,
1256 "ebpf.plugin: target '%s' has aggregated %u process%s:",
1257 w->name,
1258 w->processes,
1259 (w->processes == 1) ? "" : "es");
1260
1261 for (pid_on_target = w->root_pid; pid_on_target; pid_on_target = pid_on_target->next) {
1262 fprintf(stderr, " %d", pid_on_target->pid);
1263 }
1264
1265 fputc('\n', stderr);
1266 }
1267
1268 if (!w->exposed && w->processes) {
1269 newly_added++;
1270 w->exposed = 1;
1271 if (debug_enabled || w->debug_enabled)
1272 debug_log_int("%s just added - regenerating charts.", w->name);
1273 }
1274 }
1275
1276 if (newly_added) {
1277 int i;
1278 for (i = 0; i < EBPF_MODULE_FUNCTION_IDX; i++) {
1279 if (!(collect_pids & (1 << i)))
1280 continue;
1281
1282 ebpf_module_t *current = &ebpf_modules[i];
1283 ebpf_create_apps_for_module(current, root);
1284 }
1285 }
1286 }
1287
1288 /*****************************************************************
1289 *
1290 * FUNCTIONS TO READ GLOBAL HASH TABLES
1291 *
1292 *****************************************************************/
1293
1294 /**
1295 * Read Global Table Stats
1296 *
1297 * Read data from specified table (map_fd) using array allocated inside thread(values) and storing
1298 * them in stats vector starting from the first position.
1299 *
1300 * For PID tables is recommended to use a function to parse the specific data.
1301 *
1302 * @param stats vector used to store data
1303 * @param values helper to read data from hash tables.
1304 * @param map_fd table that has data
1305 * @param maps_per_core Is necessary to read data from all cores?
1306 * @param begin initial value to query hash table
1307 * @param end last value that will not be used.
1308 */
1309 void ebpf_read_global_table_stats(
1310 netdata_idx_t *stats,
1311 netdata_idx_t *values,
1312 int map_fd,
1313 int maps_per_core,
1314 uint32_t begin,
1315 uint32_t end)
1316 {
1317 uint32_t idx;
1318 int before = (maps_per_core) ? ebpf_nprocs : 1;
1319
1320 for (idx = begin; idx < end; idx++) {
1321 if (!bpf_map_lookup_elem(map_fd, &idx, values)) {
1322 netdata_idx_t total = 0;
1323 int i;
1324 for (i = 0; i < before; i++)
1325 total += values[i];
1326
1327 stats[idx - begin] = total;
1328 }
1329 }
1330 }
1331
1332 /**
1333 * Check if the ip is inside a IP range
1334 *
1335 * @param rfirst the first ip address of the range
1336 * @param rlast the last ip address of the range
1337 * @param cmpfirst the first ip to compare
1338 * @param cmplast the last ip to compare
1339 * @param family the IP family
1340 *
1341 * @return It returns 1 if the IP is inside the range and 0 otherwise
1342 */
1343
1344 static inline void fill_port_list(ebpf_network_viewer_port_list_t **out, ebpf_network_viewer_port_list_t *in)
1345 {
1346 if (likely(*out)) {
1347 ebpf_network_viewer_port_list_t *move = *out;
1348 uint16_t first = ntohs(in->first);
1349 uint16_t last = ntohs(in->last);
1350 while (move) {
1351 uint16_t cmp_first = ntohs(move->first);
1352 uint16_t cmp_last = ntohs(move->last);
1353 if (cmp_first <= first && first <= cmp_last && cmp_first <= last && last <= cmp_last) {
1354 netdata_log_info(
1355 "The range/value (%u, %u) is inside the range/value (%u, %u) already inserted, it will be ignored.",
1356 first,
1357 last,
1358 cmp_first,
1359 cmp_last);
1360 freez(in->value);
1361 freez(in);
1362 return;
1363 } else if (first <= cmp_first && cmp_first <= last && first <= cmp_last && cmp_last <= last) {
1364 netdata_log_info(
1365 "The range (%u, %u) is bigger than previous range (%u, %u) already inserted, the previous will be ignored.",
1366 first,
1367 last,
1368 cmp_first,
1369 cmp_last);
1370 freez(move->value);
1371 move->value = in->value;
1372 move->first = in->first;
1373 move->last = in->last;
1374 freez(in);
1375 return;
1376 }
1377
1378 move = move->next;
1379 }
1380 move = *out;
1381 while (move->next)
1382 move = move->next;
1383 move->next = in;
1384 } else {
1385 *out = in;
1386 }
1387
1388 #ifdef NETDATA_INTERNAL_CHECKS
1389 netdata_log_info(
1390 "Adding values %s( %u, %u) to %s port list used on network viewer",
1391 in->value,
1392 in->first,
1393 in->last,
1394 (*out == network_viewer_opt.included_port) ? "included" : "excluded");
1395 #endif
1396 }
1397
1398 /**
1399 * Parse Service List
1400 *
1401 * @param out a pointer to store the link list
1402 * @param service the service used to create the structure that will be linked.
1403 */
1404 static void ebpf_parse_service_list(void **out, const char *service)
1405 {
1406 ebpf_network_viewer_port_list_t **list = (ebpf_network_viewer_port_list_t **)out;
1407 struct servent *serv = getservbyname((const char *)service, "tcp");
1408 if (!serv)
1409 serv = getservbyname((const char *)service, "udp");
1410
1411 if (!serv) {
1412 netdata_log_info("Cannot resolve the service '%s' with protocols TCP and UDP, it will be ignored", service);
1413 return;
1414 }
1415
1416 ebpf_network_viewer_port_list_t *w = callocz(1, sizeof(ebpf_network_viewer_port_list_t));
1417 w->value = strdupz(service);
1418 w->hash = simple_hash(service);
1419
1420 w->first = w->last = (uint16_t)serv->s_port;
1421
1422 fill_port_list(list, w);
1423 }
1424
1425 /**
1426 * Parse port list
1427 *
1428 * Parse an allocated port list with the range given
1429 *
1430 * @param out a pointer to store the link list
1431 * @param range the informed range for the user.
1432 */
1433 static void ebpf_parse_port_list(void **out, const char *range_param)
1434 {
1435 char *range = strdupz(range_param);
1436
1437 int first, last;
1438 ebpf_network_viewer_port_list_t **list = (ebpf_network_viewer_port_list_t **)out;
1439
1440 char *copied = strdupz(range);
1441 if (*range == '*' && *(range + 1) == '\0') {
1442 first = 1;
1443 last = 65535;
1444
1445 ebpf_clean_port_structure(list);
1446 goto fillenvpl;
1447 }
1448
1449 char *end = range;
1450 //Move while I cannot find a separator
1451 while (*end && *end != ':' && *end != '-')
1452 end++;
1453
1454 //It has a range
1455 if (likely(*end)) {
1456 *end++ = '\0';
1457 if (*end == '!') {
1458 netdata_log_info(
1459 "The exclusion cannot be in the second part of the range, the range %s will be ignored.", copied);
1460 freez(copied);
1461 freez(range);
1462 return;
1463 }
1464 last = str2i((const char *)end);
1465 } else {
1466 last = 0;
1467 }
1468
1469 first = str2i((const char *)range);
1470 if (first < NETDATA_MINIMUM_PORT_VALUE || first > NETDATA_MAXIMUM_PORT_VALUE) {
1471 netdata_log_info("The first port %d of the range \"%s\" is invalid and it will be ignored!", first, copied);
1472 freez(copied);
1473 freez(range);
1474 return;
1475 }
1476
1477 if (!last)
1478 last = first;
1479
1480 if (last < NETDATA_MINIMUM_PORT_VALUE || last > NETDATA_MAXIMUM_PORT_VALUE) {
1481 netdata_log_info(
1482 "The second port %d of the range \"%s\" is invalid and the whole range will be ignored!", last, copied);
1483 freez(copied);
1484 freez(range);
1485 return;
1486 }
1487
1488 if (first > last) {
1489 netdata_log_info(
1490 "The specified order %s is wrong, the smallest value is always the first, it will be ignored!", copied);
1491 freez(copied);
1492 freez(range);
1493 return;
1494 }
1495
1496 ebpf_network_viewer_port_list_t *w;
1497 fillenvpl:
1498 w = callocz(1, sizeof(ebpf_network_viewer_port_list_t));
1499 w->value = copied;
1500 w->hash = simple_hash(copied);
1501 w->first = (uint16_t)first;
1502 w->last = (uint16_t)last;
1503 w->cmp_first = (uint16_t)first;
1504 w->cmp_last = (uint16_t)last;
1505
1506 fill_port_list(list, w);
1507 freez(range);
1508 }
1509
1510 /**
1511 * Parse Port Range
1512 *
1513 * Parse the port ranges given and create Network Viewer Port Structure
1514 *
1515 * @param ptr is a pointer with the text to parse.
1516 */
1517 void ebpf_parse_ports(const char *ptr)
1518 {
1519 // No value
1520 if (unlikely(!ptr))
1521 return;
1522
1523 while (likely(ptr)) {
1524 // Move forward until next valid character
1525 while (isspace(*ptr))
1526 ptr++;
1527
1528 // No valid value found
1529 if (unlikely(!*ptr))
1530 return;
1531
1532 // Find space that ends the list
1533 char *end = strchr(ptr, ' ');
1534 if (end) {
1535 *end++ = '\0';
1536 }
1537
1538 bool neg = false;
1539 if (*ptr == '!') {
1540 neg = true;
1541 ptr++;
1542 }
1543
1544 if (isdigit(*ptr)) { // Parse port
1545 ebpf_parse_port_list(
1546 neg ? (void **)&network_viewer_opt.excluded_port : (void **)&network_viewer_opt.included_port, ptr);
1547 } else if (isalpha(*ptr)) { // Parse service
1548 ebpf_parse_service_list(
1549 neg ? (void **)&network_viewer_opt.excluded_port : (void **)&network_viewer_opt.included_port, ptr);
1550 } else if (*ptr == '*') { // All
1551 ebpf_parse_port_list(
1552 neg ? (void **)&network_viewer_opt.excluded_port : (void **)&network_viewer_opt.included_port, ptr);
1553 }
1554
1555 ptr = end;
1556 }
1557 }
1558
1559 /*****************************************************************
1560 *
1561 * FUNCTIONS TO DEFINE OPTIONS
1562 *
1563 *****************************************************************/
1564
1565 /**
1566 * Define labels used to generate charts
1567 *
1568 * @param is structure with information about number of calls made for a function.
1569 * @param pio structure used to generate charts.
1570 * @param dim a pointer for the dimensions name
1571 * @param name a pointer for the tensor with the name of the functions.
1572 * @param algorithm a vector with the algorithms used to make the charts
1573 * @param end the number of elements in the previous 4 arguments.
1574 */
1575 void ebpf_global_labels(
1576 netdata_syscall_stat_t *is,
1577 netdata_publish_syscall_t *pio,
1578 char **dim,
1579 char **name,
1580 int *algorithm,
1581 int end)
1582 {
1583 int i;
1584
1585 netdata_syscall_stat_t *prev = NULL;
1586 netdata_publish_syscall_t *publish_prev = NULL;
1587 for (i = 0; i < end; i++) {
1588 if (prev) {
1589 prev->next = &is[i];
1590 }
1591 prev = &is[i];
1592
1593 pio[i].dimension = dim[i];
1594 pio[i].name = name[i];
1595 pio[i].algorithm = ebpf_algorithms[algorithm[i]];
1596 if (publish_prev) {
1597 publish_prev->next = &pio[i];
1598 }
1599 publish_prev = &pio[i];
1600 }
1601 }
1602
1603 /**
1604 * Disable all Global charts
1605 *
1606 * Disable charts
1607 */
1608 void disable_all_global_charts()
1609 {
1610 int i;
1611 for (i = 0; ebpf_modules[i].info.thread_name; i++) {
1612 ebpf_module_enabled_set(&ebpf_modules[i], NETDATA_THREAD_EBPF_NOT_RUNNING);
1613 ebpf_modules[i].global_charts = 0;
1614 }
1615 }
1616
1617 /**
1618 * Disable Cgroups
1619 *
1620 * Disable charts for apps loading only global charts.
1621 */
1622 void ebpf_disable_cgroups()
1623 {
1624 int i;
1625 for (i = 0; ebpf_modules[i].info.thread_name; i++) {
1626 ebpf_modules[i].cgroup_charts = 0;
1627 }
1628 }
1629
1630 /**
1631 * Update Disabled Plugins
1632 *
1633 * This function calls ebpf_update_stats to update statistics for collector.
1634 *
1635 * @param em a pointer to `struct ebpf_module`
1636 */
1637 void ebpf_update_disabled_plugin_stats(ebpf_module_t *em)
1638 {
1639 netdata_mutex_lock(&lock);
1640 ebpf_update_stats(&plugin_statistics, em);
1641 netdata_mutex_unlock(&lock);
1642 }
1643
1644 /**
1645 * Print help on standard error for user knows how to use the collector.
1646 */
1647 void ebpf_print_help()
1648 {
1649 fprintf(
1650 stderr,
1651 "\n"
1652 " Netdata ebpf.plugin %s\n"
1653 " Copyright 2018-2025 Netdata Inc.\n"
1654 " Released under GNU General Public License v3 or later.\n"
1655 "\n"
1656 " This eBPF.plugin is a data collector plugin for netdata.\n"
1657 "\n"
1658 " This plugin only accepts long options with one or two dashes. The available command line options are:\n"
1659 "\n"
1660 " SECONDS Set the data collection frequency.\n"
1661 "\n"
1662 " [-]-help Show this help.\n"
1663 "\n"
1664 " [-]-version Show software version.\n"
1665 "\n"
1666 " [-]-global Disable charts per application and cgroup.\n"
1667 "\n"
1668 " [-]-all Enable all chart groups (global, apps, and cgroup), unless -g is also given.\n"
1669 "\n"
1670 " [-]-cachestat Enable charts related to process run time.\n"
1671 "\n"
1672 " [-]-dcstat Enable charts related to directory cache.\n"
1673 "\n"
1674 " [-]-disk Enable charts related to disk monitoring.\n"
1675 "\n"
1676 " [-]-filesystem Enable chart related to filesystem run time.\n"
1677 "\n"
1678 " [-]-hardirq Enable chart related to hard IRQ latency.\n"
1679 "\n"
1680 " [-]-mdflush Enable charts related to multi-device flush.\n"
1681 "\n"
1682 " [-]-mount Enable charts related to mount monitoring.\n"
1683 "\n"
1684 " [-]-net Enable network viewer charts.\n"
1685 "\n"
1686 " [-]-oomkill Enable chart related to OOM kill tracking.\n"
1687 "\n"
1688 " [-]-process Enable charts related to process run time.\n"
1689 "\n"
1690 " [-]-return Run the collector in return mode.\n"
1691 "\n"
1692 " [-]-shm Enable chart related to shared memory tracking.\n"
1693 "\n"
1694 " [-]-softirq Enable chart related to soft IRQ latency.\n"
1695 "\n"
1696 " [-]-sync Enable chart related to sync run time.\n"
1697 "\n"
1698 " [-]-swap Enable chart related to swap run time.\n"
1699 "\n"
1700 " [-]-vfs Enable chart related to vfs run time.\n"
1701 "\n"
1702 " [-]-legacy Load legacy eBPF programs.\n"
1703 "\n"
1704 " [-]-core Use CO-RE when available(Working in progress).\n"
1705 "\n",
1706 NETDATA_VERSION);
1707 }
1708
1709 /*****************************************************************
1710 *
1711 * TRACEPOINT MANAGEMENT FUNCTIONS
1712 *
1713 *****************************************************************/
1714
1715 /**
1716 * Enable a tracepoint.
1717 *
1718 * @return 0 on success, -1 on error.
1719 */
1720 int ebpf_enable_tracepoint(ebpf_tracepoint_t *tp)
1721 {
1722 int test = ebpf_is_tracepoint_enabled(tp->class, tp->event);
1723
1724 // err?
1725 if (test == -1) {
1726 return -1;
1727 }
1728 // disabled?
1729 else if (test == 0) {
1730 // enable it then.
1731 if (ebpf_enable_tracing_values(tp->class, tp->event)) {
1732 return -1;
1733 }
1734 }
1735
1736 // enabled now or already was.
1737 tp->enabled = true;
1738
1739 return 0;
1740 }
1741
1742 /**
1743 * Disable a tracepoint if it's enabled.
1744 *
1745 * @return 0 on success, -1 on error.
1746 */
1747 int ebpf_disable_tracepoint(ebpf_tracepoint_t *tp)
1748 {
1749 int test = ebpf_is_tracepoint_enabled(tp->class, tp->event);
1750
1751 // err?
1752 if (test == -1) {
1753 return -1;
1754 }
1755 // enabled?
1756 else if (test == 1) {
1757 // disable it then.
1758 if (ebpf_disable_tracing_values(tp->class, tp->event)) {
1759 return -1;
1760 }
1761 }
1762
1763 // disable now or already was.
1764 tp->enabled = false;
1765
1766 return 0;
1767 }
1768
1769 /**
1770 * Enable multiple tracepoints on a list of tracepoints which end when the
1771 * class is NULL.
1772 *
1773 * @return the number of successful enables.
1774 */
1775 uint32_t ebpf_enable_tracepoints(ebpf_tracepoint_t *tps)
1776 {
1777 uint32_t cnt = 0;
1778 for (int i = 0; tps[i].class != NULL; i++) {
1779 if (ebpf_enable_tracepoint(&tps[i]) == -1) {
1780 netdata_log_error("Failed to enable tracepoint %s:%s", tps[i].class, tps[i].event);
1781 } else {
1782 cnt++;
1783 }
1784 }
1785 return cnt;
1786 }
1787
1788 /*****************************************************************
1789 *
1790 * AUXILIARY FUNCTIONS USED DURING INITIALIZATION
1791 *
1792 *****************************************************************/
1793
1794 /**
1795 * Read Local Ports
1796 *
1797 * Parse /proc/net/{tcp,udp} and get the ports Linux is listening.
1798 *
1799 * @param filename the proc file to parse.
1800 * @param proto is the magic number associated to the protocol file we are reading.
1801 */
1802 void read_local_ports(char *filename, uint8_t proto)
1803 {
1804 procfile *ff = procfile_open(filename, " \t:", PROCFILE_FLAG_DEFAULT);
1805 if (!ff)
1806 return;
1807
1808 ff = procfile_readall(ff);
1809 if (!ff)
1810 return;
1811
1812 size_t lines = procfile_lines(ff), l;
1813 netdata_passive_connection_t values = {.counter = 0, .tgid = 0, .pid = 0};
1814 for (l = 0; l < lines; l++) {
1815 size_t words = procfile_linewords(ff, l);
1816 // This is header or end of file
1817 if (unlikely(words < 14))
1818 continue;
1819
1820 // https://elixir.bootlin.com/linux/v5.7.8/source/include/net/tcp_states.h
1821 // 0A = TCP_LISTEN
1822 if (strcmp("0A", procfile_lineword(ff, l, 5)))
1823 continue;
1824
1825 // Read local port
1826 uint16_t port = (uint16_t)strtol(procfile_lineword(ff, l, 2), NULL, 16);
1827 update_listen_table(htons(port), proto, &values);
1828 }
1829
1830 procfile_close(ff);
1831 }
1832
1833 /**
1834 * Read Local addresseses
1835 *
1836 * Read the local address from the interfaces.
1837 */
1838 void ebpf_read_local_addresses_unsafe()
1839 {
1840 struct ifaddrs *ifaddr, *ifa;
1841 if (getifaddrs(&ifaddr) == -1) {
1842 netdata_log_error(
1843 "Cannot get the local IP addresses, it is no possible to do separation between inbound and outbound connections");
1844 return;
1845 }
1846
1847 char *notext = {"No text representation"};
1848 for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
1849 if (ifa->ifa_addr == NULL)
1850 continue;
1851
1852 if ((ifa->ifa_addr->sa_family != AF_INET) && (ifa->ifa_addr->sa_family != AF_INET6))
1853 continue;
1854
1855 ebpf_network_viewer_ip_list_t *w = callocz(1, sizeof(ebpf_network_viewer_ip_list_t));
1856
1857 int family = ifa->ifa_addr->sa_family;
1858 w->ver = (uint8_t)family;
1859 char text[INET6_ADDRSTRLEN];
1860 if (family == AF_INET) {
1861 struct sockaddr_in *in = (struct sockaddr_in *)ifa->ifa_addr;
1862
1863 w->first.addr32[0] = in->sin_addr.s_addr;
1864 w->last.addr32[0] = in->sin_addr.s_addr;
1865
1866 if (inet_ntop(AF_INET, w->first.addr8, text, INET6_ADDRSTRLEN)) {
1867 w->value = strdupz(text);
1868 w->hash = simple_hash(text);
1869 } else {
1870 w->value = strdupz(notext);
1871 w->hash = simple_hash(notext);
1872 }
1873 } else {
1874 struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)ifa->ifa_addr;
1875
1876 memcpy(w->first.addr8, (void *)&in6->sin6_addr, sizeof(struct in6_addr));
1877 memcpy(w->last.addr8, (void *)&in6->sin6_addr, sizeof(struct in6_addr));
1878
1879 if (inet_ntop(AF_INET6, w->first.addr8, text, INET6_ADDRSTRLEN)) {
1880 w->value = strdupz(text);
1881 w->hash = simple_hash(text);
1882 } else {
1883 w->value = strdupz(notext);
1884 w->hash = simple_hash(notext);
1885 }
1886 }
1887
1888 ebpf_fill_ip_list_unsafe(
1889 (family == AF_INET) ? &network_viewer_opt.ipv4_local_ip : &network_viewer_opt.ipv6_local_ip, w, "selector");
1890 }
1891
1892 freeifaddrs(ifaddr);
1893 }