@cryptotaxi247 / netdata-1 / commits / 007f6ad4c

detect sockets direction (#16861)

Costa Tsaousis committed Jan 29, 2024 at 09:51 UTC 007f6ad4cea262ca904635facb332e2dbd81dd0d
22 files changed +871 -404
CMakeLists.txt
+2 -1
@@ -1946,7 +1946,8 @@ if(ENABLE_PLUGIN_EBPF)
1946 endif()
1947
1948 if(ENABLE_PLUGIN_LOCAL_LISTENERS)
1949 - set(LOCAL_LISTENERS_FILES collectors/plugins.d/local_listeners.c)
1949 + set(LOCAL_LISTENERS_FILES collectors/plugins.d/local_listeners.c
1950 + collectors/plugins.d/local-sockets.h)
1951
1952 add_executable(local-listeners ${LOCAL_LISTENERS_FILES})
1953 target_link_libraries(local-listeners libnetdata)
collectors/cgroups.plugin/sys_fs_cgroup.c
+2 -2
@@ -1308,8 +1308,8 @@ static inline int update_memory_limits(struct cgroup *cg) {
1308 return 1;
1309 }
1310 } else {
1311 - char buffer[30 + 1];
1312 - int ret = read_file(*filename, buffer, 30);
1311 + char buffer[32];
1312 + int ret = read_txt_file(*filename, buffer, sizeof(buffer));
1313 if(ret) {
1314 collector_error("Cannot refresh cgroup %s memory limit by reading '%s'. Will not update its limit anymore.", cg->id, *filename);
1315 freez(*filename);
collectors/debugfs.plugin/debugfs_zswap.c
+1 -1
@@ -370,7 +370,7 @@ static int debugfs_is_zswap_enabled()
370 snprintfz(filename, FILENAME_MAX, "/sys/module/zswap/parameters/enabled"); // host prefix is not needed here
371 char state[ZSWAP_STATE_SIZE + 1];
372
373 - int ret = read_file(filename, state, ZSWAP_STATE_SIZE);
373 + int ret = read_txt_file(filename, state, sizeof(state));
374
375 if (unlikely(!ret && !strcmp(state, "Y"))) {
376 return 0;
collectors/debugfs.plugin/sys_devices_virtual_powercap.c
+1 -1
@@ -27,7 +27,7 @@ static struct zone_t *get_rapl_zone(const char *control_type __maybe_unused, str
27 snprintfz(temp, FILENAME_MAX, "%s/%s", dirname, "name");
28
29 char name[FILENAME_MAX + 1] = "";
30 - if (read_file(temp, name, sizeof(name) - 1) != 0)
30 + if (read_txt_file(temp, name, sizeof(name)) != 0)
31 return NULL;
32
33 char *trimmed = trim(name);
collectors/plugins.d/local-sockets.h new
+569
@@ -0,0 +1,569 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_LOCAL_SOCKETS_H
4 +#define NETDATA_LOCAL_SOCKETS_H
5 +
6 +#include "libnetdata/libnetdata.h"
7 +
8 +struct local_socket;
9 +#define SIMPLE_HASHTABLE_VALUE_TYPE struct local_socket
10 +#define SIMPLE_HASHTABLE_NAME _LOCAL_SOCKET
11 +#include "libnetdata/simple_hashtable.h"
12 +
13 +union ipv46;
14 +#define SIMPLE_HASHTABLE_VALUE_TYPE union ipv46
15 +#define SIMPLE_HASHTABLE_NAME _LOCAL_IP
16 +#include "libnetdata/simple_hashtable.h"
17 +
18 +struct local_port;
19 +#define SIMPLE_HASHTABLE_VALUE_TYPE struct local_port
20 +#define SIMPLE_HASHTABLE_NAME _LOCAL_PORT
21 +#include "libnetdata/simple_hashtable.h"
22 +
23 +struct local_socket_state;
24 +typedef void (*local_sockets_cb_t)(struct local_socket_state *state, struct local_socket *n, void *data);
25 +
26 +typedef struct local_socket_state {
27 + struct {
28 + bool listening;
29 + bool inbound;
30 + bool outbound;
31 + bool local;
32 + bool tcp4;
33 + bool tcp6;
34 + bool udp4;
35 + bool udp6;
36 + bool pid;
37 + bool cmdline;
38 + bool comm;
39 + size_t max_errors;
40 +
41 + local_sockets_cb_t cb;
42 + void *data;
43 + } config;
44 +
45 + struct {
46 + size_t pid_fds_processed;
47 + size_t pid_fds_failed;
48 + size_t errors_encountered;
49 + } stats;
50 +
51 + SIMPLE_HASHTABLE_LOCAL_SOCKET sockets_hashtable;
52 + SIMPLE_HASHTABLE_LOCAL_IP local_ips_hashtable;
53 + SIMPLE_HASHTABLE_LOCAL_PORT listening_ports_hashtable;
54 +} LS_STATE;
55 +
56 +// --------------------------------------------------------------------------------------------------------------------
57 +
58 +typedef enum __attribute__((packed)) {
59 + SOCKET_DIRECTION_LISTEN = (1 << 0), // a listening socket
60 + SOCKET_DIRECTION_INBOUND = (1 << 1), // an inbound socket connecting a remote system to a local listening socket
61 + SOCKET_DIRECTION_OUTBOUND = (1 << 2), // a socket initiated by this system, connecting to another system
62 + SOCKET_DIRECTION_LOCAL = (1 << 3), // the socket connecting 2 localhost applications
63 +} SOCKET_DIRECTION;
64 +
65 +#ifndef TASK_COMM_LEN
66 +#define TASK_COMM_LEN 16
67 +#endif
68 +
69 +union ipv46 {
70 + uint32_t ipv4;
71 + struct in6_addr ipv6;
72 +};
73 +
74 +struct local_port {
75 + uint16_t protocol;
76 + uint16_t family;
77 + uint16_t port;
78 +};
79 +
80 +struct socket_endpoint {
81 + uint16_t port;
82 + union ipv46 ip;
83 +};
84 +
85 +static inline void ipv6_to_in6_addr(const char *ipv6_str, struct in6_addr *d) {
86 + char buf[9];
87 +
88 + for (size_t k = 0; k < 4; ++k) {
89 + memcpy(buf, ipv6_str + (k * 8), 8);
90 + buf[sizeof(buf) - 1] = '\0';
91 + d->s6_addr32[k] = strtoul(buf, NULL, 16);
92 + }
93 +}
94 +
95 +typedef struct local_socket {
96 + unsigned int inode;
97 +
98 + uint16_t protocol;
99 + uint16_t family;
100 + int state;
101 + struct socket_endpoint local;
102 + struct socket_endpoint remote;
103 + pid_t pid;
104 +
105 + SOCKET_DIRECTION direction;
106 +
107 + char comm[TASK_COMM_LEN];
108 + char *cmdline;
109 +
110 + struct local_port local_port_key;
111 +
112 + XXH64_hash_t local_ip_hash;
113 + XXH64_hash_t remote_ip_hash;
114 + XXH64_hash_t local_port_hash;
115 +} LOCAL_SOCKET;
116 +
117 +// --------------------------------------------------------------------------------------------------------------------
118 +
119 +static inline void ll_log(LS_STATE *ls, const char *format, ...) __attribute__ ((format(__printf__, 2, 3)));
120 +static inline void ll_log(LS_STATE *ls, const char *format, ...) {
121 + if(++ls->stats.errors_encountered >= ls->config.max_errors)
122 + return;
123 +
124 + char buf[16384];
125 + va_list args;
126 + va_start(args, format);
127 + vsnprintf(buf, sizeof(buf), format, args);
128 + va_end(args);
129 +
130 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "LOCAL-LISTENERS: %s", buf);
131 +}
132 +
133 +// --------------------------------------------------------------------------------------------------------------------
134 +
135 +static void foreach_local_socket_call_cb_and_cleanup(LS_STATE *ls) {
136 + for (unsigned int i = 0; i < ls->sockets_hashtable.size; i++) {
137 + SIMPLE_HASHTABLE_SLOT_LOCAL_SOCKET *sl = &ls->sockets_hashtable.hashtable[i];
138 + LOCAL_SOCKET *n = SIMPLE_HASHTABLE_SLOT_DATA(sl);
139 + if(!n) continue;
140 +
141 + if((ls->config.listening && n->direction & SOCKET_DIRECTION_LISTEN) ||
142 + (ls->config.local && n->direction & SOCKET_DIRECTION_LOCAL) ||
143 + (ls->config.inbound && n->direction & SOCKET_DIRECTION_INBOUND) ||
144 + (ls->config.outbound && n->direction & SOCKET_DIRECTION_OUTBOUND)
145 + ) {
146 + // we have to call the callback for this socket
147 + if (ls->config.cb)
148 + ls->config.cb(ls, n, ls->config.data);
149 + }
150 +
151 + freez(n->cmdline);
152 + freez(n);
153 + }
154 +}
155 +
156 +// --------------------------------------------------------------------------------------------------------------------
157 +
158 +static inline void fix_cmdline(char* str) {
159 + char *s = str;
160 +
161 + // map invalid characters to underscores
162 + while(*s) {
163 + if(*s == '|' || iscntrl(*s)) *s = '_';
164 + s++;
165 + }
166 +}
167 +
168 +static inline bool associate_inode_with_pid(LS_STATE *ls, unsigned int inode, pid_t pid) {
169 + SIMPLE_HASHTABLE_SLOT_LOCAL_SOCKET *sl = simple_hashtable_get_slot_LOCAL_SOCKET(&ls->sockets_hashtable, inode, &inode, false);
170 + LOCAL_SOCKET *n = SIMPLE_HASHTABLE_SLOT_DATA(sl);
171 + if(!n) return false;
172 +
173 + n->pid = pid;
174 +
175 + if(ls->config.cmdline || ls->config.comm) {
176 + char cmdline[8192] = "";
177 + char filename[FILENAME_MAX + 1];
178 + snprintfz(filename, FILENAME_MAX, "%s/proc/%d/cmdline", netdata_configured_host_prefix, pid);
179 +
180 + if(ls->config.cmdline) {
181 + if (read_proc_cmdline(filename, cmdline, sizeof(cmdline)))
182 + ll_log(ls, "cannot open file: %s\n", filename);
183 + else {
184 + fix_cmdline(cmdline);
185 +
186 + char *s = trim(cmdline);
187 +
188 + if(s) {
189 + // replace it
190 + freez(n->cmdline);
191 + n->cmdline = strdupz(s);
192 + }
193 + }
194 + }
195 +
196 + if(ls->config.comm) {
197 + n->comm[0] = '\0';
198 + snprintfz(filename, FILENAME_MAX, "%s/proc/%d/comm", netdata_configured_host_prefix, pid);
199 + if (read_txt_file(filename, n->comm, sizeof(n->comm)))
200 + ll_log(ls, "cannot open file: %s\n", filename);
201 + else {
202 + size_t len = strlen(n->comm);
203 + if(n->comm[len - 1] == '\n')
204 + n->comm[len - 1] = '\0';
205 + }
206 + }
207 + }
208 +
209 + return true;
210 +}
211 +
212 +// ----------------------------------------------------------------------------
213 +
214 +static inline bool find_all_sockets_in_proc(LS_STATE *ls, const char *proc_filename) {
215 + DIR *proc_dir, *fd_dir;
216 + struct dirent *proc_entry, *fd_entry;
217 + char path_buffer[FILENAME_MAX + 1];
218 +
219 + proc_dir = opendir(proc_filename);
220 + if (proc_dir == NULL) {
221 + ll_log(ls, "cannot opendir() '%s'", proc_filename);
222 + ls->stats.pid_fds_failed++;
223 + return false;
224 + }
225 +
226 + while ((proc_entry = readdir(proc_dir)) != NULL) {
227 + // Check if directory entry is a PID by seeing if the name is made up of digits only
228 + int is_pid = 1;
229 + for (char *c = proc_entry->d_name; *c != '\0'; c++) {
230 + if (*c < '0' || *c > '9') {
231 + is_pid = 0;
232 + break;
233 + }
234 + }
235 +
236 + if (!is_pid)
237 + continue;
238 +
239 + // Build the path to the fd directory of the process
240 + snprintfz(path_buffer, FILENAME_MAX, "%s/%s/fd/", proc_filename, proc_entry->d_name);
241 +
242 + fd_dir = opendir(path_buffer);
243 + if (fd_dir == NULL) {
244 + ll_log(ls, "cannot opendir() '%s'", path_buffer);
245 +
246 + ls->stats.pid_fds_failed++;
247 + continue;
248 + }
249 +
250 + while ((fd_entry = readdir(fd_dir)) != NULL) {
251 + if(!strcmp(fd_entry->d_name, ".") || !strcmp(fd_entry->d_name, ".."))
252 + continue;
253 +
254 + char link_path[FILENAME_MAX + 1];
255 + char link_target[FILENAME_MAX + 1];
256 + unsigned inode;
257 +
258 + // Build the path to the file descriptor link
259 + snprintfz(link_path, FILENAME_MAX, "%s/%s", path_buffer, fd_entry->d_name);
260 +
261 + ssize_t len = readlink(link_path, link_target, sizeof(link_target) - 1);
262 + if (len == -1) {
263 + ll_log(ls, "cannot read link '%s'", link_path);
264 +
265 + ls->stats.pid_fds_failed++;
266 + continue;
267 + }
268 + link_target[len] = '\0';
269 +
270 + ls->stats.pid_fds_processed++;
271 +
272 + // If the link target indicates a socket, print its inode number
273 + if (sscanf(link_target, "socket:[%u]", &inode) == 1)
274 + associate_inode_with_pid(ls, inode, (pid_t)strtoul(proc_entry->d_name, NULL, 10));
275 + }
276 +
277 + closedir(fd_dir);
278 + }
279 +
280 + closedir(proc_dir);
281 + return true;
282 +}
283 +
284 +// ----------------------------------------------------------------------------
285 +
286 +static bool is_ipv4_mapped_ipv6_address(const struct in6_addr *addr) {
287 + // An IPv4-mapped IPv6 address starts with 80 bits of zeros followed by 16 bits of ones
288 + static const unsigned char ipv4_mapped_prefix[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF };
289 + return memcmp(addr->s6_addr, ipv4_mapped_prefix, 12) == 0;
290 +}
291 +
292 +static bool is_loopback_address(const void *ip, uint16_t family) {
293 + if (family == AF_INET) {
294 + // For IPv4, loopback addresses are in the 127.0.0.0/8 range
295 + const uint32_t addr = ntohl(*((const uint32_t *)ip)); // Convert to host byte order for comparison
296 + return (addr >> 24) == 127; // Check if the first byte is 127
297 + } else if (family == AF_INET6) {
298 + // Check if the address is an IPv4-mapped IPv6 address
299 + const struct in6_addr *ipv6_addr = (const struct in6_addr *)ip;
300 + if (is_ipv4_mapped_ipv6_address(ipv6_addr)) {
301 + // Extract the last 32 bits (IPv4 address) and check if it's in the 127.0.0.0/8 range
302 + const uint32_t ipv4_addr = ntohl(*((const uint32_t *)(ipv6_addr->s6_addr + 12)));
303 + return (ipv4_addr >> 24) == 127;
304 + }
305 +
306 + // For IPv6, loopback address is ::1
307 + const struct in6_addr loopback_ipv6 = IN6ADDR_LOOPBACK_INIT;
308 + return memcmp(ipv6_addr, &loopback_ipv6, sizeof(struct in6_addr)) == 0;
309 + }
310 +
311 + return false;
312 +}
313 +
314 +static bool is_zero_address(const void *ip, uint16_t family) {
315 + if (family == AF_INET) {
316 + // For IPv4, check if the address is not 0.0.0.0
317 + const uint32_t zero_ipv4 = 0; // Zero address in network byte order
318 + return memcmp(ip, &zero_ipv4, sizeof(uint32_t)) == 0;
319 + } else if (family == AF_INET6) {
320 + // For IPv6, check if the address is not ::
321 + const struct in6_addr zero_ipv6 = IN6ADDR_ANY_INIT;
322 + return memcmp(ip, &zero_ipv6, sizeof(struct in6_addr)) == 0;
323 + }
324 +
325 + return false;
326 +}
327 +
328 +static inline bool read_proc_net_x(LS_STATE *ls, const char *filename, uint16_t family, uint16_t protocol) {
329 + if(family != AF_INET && family != AF_INET6)
330 + return false;
331 +
332 + FILE *fp;
333 + char *line = NULL;
334 + size_t len = 0;
335 + ssize_t read;
336 +
337 + fp = fopen(filename, "r");
338 + if (fp == NULL)
339 + return false;
340 +
341 + ssize_t min_line_length = (family == AF_INET) ? 105 : 155;
342 + size_t counter = 0;
343 +
344 + // Read line by line
345 + while ((read = getline(&line, &len, fp)) != -1) {
346 + if(counter++ == 0) continue; // skip the first line
347 +
348 + if(read < min_line_length) {
349 + ll_log(ls, "too small line No %zu of filename '%s': %s", counter, filename, line);
350 + continue;
351 + }
352 +
353 + unsigned int local_address, local_port, state, remote_address, remote_port, inode = 0;
354 + char local_address6[33], remote_address6[33];
355 +
356 + if(family == AF_INET) {
357 + if (sscanf(line, "%*d: %X:%X %X:%X %X %*X:%*X %*X:%*X %*X %*d %*d %u",
358 + &local_address, &local_port, &remote_address, &remote_port, &state, &inode) != 6) {
359 + ll_log(ls, "cannot parse ipv4 line No %zu of filename '%s': %s", counter, filename, line);
360 + continue;
361 + }
362 + }
363 + else if(family == AF_INET6) {
364 + if(sscanf(line, "%*d: %32[0-9A-Fa-f]:%X %32[0-9A-Fa-f]:%X %X %*X:%*X %*X:%*X %*X %*d %*d %u",
365 + local_address6, &local_port, remote_address6, &remote_port, &state, &inode) != 6) {
366 + ll_log(ls, "cannot parse ipv6 line No %zu of filename '%s': %s", counter, filename, line);
367 + continue;
368 + }
369 + }
370 + if(!inode) continue;
371 +
372 + SIMPLE_HASHTABLE_SLOT_LOCAL_SOCKET *sl = simple_hashtable_get_slot_LOCAL_SOCKET(&ls->sockets_hashtable, inode, &inode, true);
373 + LOCAL_SOCKET *n = SIMPLE_HASHTABLE_SLOT_DATA(sl);
374 + if(n) {
375 + ll_log(ls, "inode %u given on line %zu of filename '%s', already exists in hashtable - ignoring duplicate", inode, counter, filename);
376 + continue;
377 + }
378 +
379 + // allocate a new socket and index it
380 +
381 + n = (LOCAL_SOCKET *)callocz(1, sizeof(LOCAL_SOCKET));
382 +
383 + if(family == AF_INET) {
384 + n->local.ip.ipv4 = local_address;
385 + n->remote.ip.ipv4 = remote_address;
386 + }
387 + else if(family == AF_INET6) {
388 + ipv6_to_in6_addr(local_address6, &n->local.ip.ipv6);
389 + ipv6_to_in6_addr(remote_address6, &n->remote.ip.ipv6);
390 + }
391 +
392 + n->direction = 0;
393 + n->protocol = protocol;
394 + n->family = family;
395 + n->state = (int)state;
396 + n->inode = inode;
397 + n->local.port = local_port;
398 + n->remote.port = remote_port;
399 + n->protocol = protocol;
400 +
401 + n->local_port_key.port = n->local.port;
402 + n->local_port_key.family = n->family;
403 + n->local_port_key.protocol = n->protocol;
404 +
405 + n->local_ip_hash = XXH3_64bits(&n->local.ip, sizeof(n->local.ip));
406 + n->remote_ip_hash = XXH3_64bits(&n->remote.ip, sizeof(n->remote.ip));
407 + n->local_port_hash = XXH3_64bits(&n->local_port_key, sizeof(n->local_port_key));
408 +
409 + simple_hashtable_set_slot_LOCAL_SOCKET(&ls->sockets_hashtable, sl, inode, n);
410 +
411 + if(!is_zero_address(&n->local.ip, n->family)) {
412 + // put all the local IPs into the local_ips hashtable
413 + // so, we learn all local IPs the system has
414 +
415 + SIMPLE_HASHTABLE_SLOT_LOCAL_IP *sl_ip =
416 + simple_hashtable_get_slot_LOCAL_IP(&ls->local_ips_hashtable, n->local_ip_hash, &n->local.ip, true);
417 +
418 + union ipv46 *ip = SIMPLE_HASHTABLE_SLOT_DATA(sl_ip);
419 + if(!ip)
420 + simple_hashtable_set_slot_LOCAL_IP(&ls->local_ips_hashtable, sl_ip, n->local_ip_hash, &n->local.ip);
421 + }
422 +
423 + if((n->protocol == IPPROTO_TCP && n->state == TCP_LISTEN) || is_zero_address(&n->local.ip, n->family) || is_zero_address(&n->remote.ip, n->family)) {
424 + // the socket is either in a TCP LISTEN, or
425 + // the remote address is zero
426 + n->direction |= SOCKET_DIRECTION_LISTEN;
427 + }
428 + else if(is_loopback_address(&n->local.ip, n->family) || is_loopback_address(&n->remote.ip, n->family)) {
429 + // the local IP address is loopback
430 + n->direction |= SOCKET_DIRECTION_LOCAL;
431 + }
432 + else {
433 + // we can't say yet if it is inbound or outboud
434 + // so, mark it as both inbound and outbound
435 + n->direction |= SOCKET_DIRECTION_INBOUND | SOCKET_DIRECTION_OUTBOUND;
436 + }
437 +
438 + if(n->direction & SOCKET_DIRECTION_LISTEN) {
439 + // for the listening sockets, keep a hashtable with all the local ports
440 + // so that we will be able to detect INBOUND sockets
441 +
442 + SIMPLE_HASHTABLE_SLOT_LOCAL_PORT *sl_port =
443 + simple_hashtable_get_slot_LOCAL_PORT(&ls->listening_ports_hashtable, n->local_port_hash, &n->local_port_key, true);
444 +
445 + struct local_port *port = SIMPLE_HASHTABLE_SLOT_DATA(sl_port);
446 + if(!port)
447 + simple_hashtable_set_slot_LOCAL_PORT(&ls->listening_ports_hashtable, sl_port, n->local_port_hash, &n->local_port_key);
448 + }
449 + }
450 +
451 + fclose(fp);
452 +
453 + if (line)
454 + freez(line);
455 +
456 + return true;
457 +}
458 +
459 +// --------------------------------------------------------------------------------------------------------------------
460 +
461 +static inline void local_sockets_detect_directions(LS_STATE *ls) {
462 + for (unsigned int i = 0; i < ls->sockets_hashtable.size; i++) {
463 + SIMPLE_HASHTABLE_SLOT_LOCAL_SOCKET *sl = &ls->sockets_hashtable.hashtable[i];
464 + LOCAL_SOCKET *n = SIMPLE_HASHTABLE_SLOT_DATA(sl);
465 + if (!n) continue;
466 +
467 + if ((n->direction & (SOCKET_DIRECTION_INBOUND|SOCKET_DIRECTION_OUTBOUND)) !=
468 + (SOCKET_DIRECTION_INBOUND|SOCKET_DIRECTION_OUTBOUND))
469 + continue;
470 +
471 + // check if the remote IP is one of our local IPs
472 + {
473 + SIMPLE_HASHTABLE_SLOT_LOCAL_IP *sl_ip =
474 + simple_hashtable_get_slot_LOCAL_IP(&ls->local_ips_hashtable, n->remote_ip_hash, &n->remote.ip, false);
475 +
476 + union ipv46 *d = SIMPLE_HASHTABLE_SLOT_DATA(sl_ip);
477 + if (d) {
478 + // the remote IP of this socket is one of our local IPs
479 + n->direction &= ~(SOCKET_DIRECTION_INBOUND|SOCKET_DIRECTION_OUTBOUND);
480 + n->direction |= SOCKET_DIRECTION_LOCAL;
481 + continue;
482 + }
483 + }
484 +
485 + // check if the local port is one of our listening ports
486 + {
487 + SIMPLE_HASHTABLE_SLOT_LOCAL_PORT *sl_port =
488 + simple_hashtable_get_slot_LOCAL_PORT(&ls->listening_ports_hashtable, n->local_port_hash, &n->local_port_key, false);
489 +
490 + struct local_port *port = SIMPLE_HASHTABLE_SLOT_DATA(sl_port); // do not reference this pointer - is invalid
491 + if(port) {
492 + // the local port of this socket is a port we listen to
493 + n->direction &= ~SOCKET_DIRECTION_OUTBOUND;
494 + }
495 + else
496 + n->direction &= ~SOCKET_DIRECTION_INBOUND;
497 + }
498 + }
499 +}
500 +
501 +// --------------------------------------------------------------------------------------------------------------------
502 +
503 +static inline void local_sockets_process(LS_STATE *ls) {
504 + char path[FILENAME_MAX + 1];
505 +
506 + simple_hashtable_init_LOCAL_SOCKET(&ls->sockets_hashtable, 65535);
507 + simple_hashtable_init_LOCAL_IP(&ls->local_ips_hashtable, 1024);
508 + simple_hashtable_init_LOCAL_PORT(&ls->listening_ports_hashtable, 1024);
509 +
510 + if(ls->config.tcp4) {
511 + snprintfz(path, FILENAME_MAX, "%s/proc/net/tcp", netdata_configured_host_prefix);
512 + read_proc_net_x(ls, path, AF_INET, IPPROTO_TCP);
513 + }
514 +
515 + if(ls->config.udp4) {
516 + snprintfz(path, FILENAME_MAX, "%s/proc/net/udp", netdata_configured_host_prefix);
517 + read_proc_net_x(ls, path, AF_INET, IPPROTO_UDP);
518 + }
519 +
520 + if(ls->config.tcp6) {
521 + snprintfz(path, FILENAME_MAX, "%s/proc/net/tcp6", netdata_configured_host_prefix);
522 + read_proc_net_x(ls, path, AF_INET6, IPPROTO_TCP);
523 + }
524 +
525 + if(ls->config.udp6) {
526 + snprintfz(path, FILENAME_MAX, "%s/proc/net/udp6", netdata_configured_host_prefix);
527 + read_proc_net_x(ls, path, AF_INET6, IPPROTO_UDP);
528 + }
529 +
530 + if(ls->config.cmdline || ls->config.comm || ls->config.pid) {
531 + snprintfz(path, FILENAME_MAX, "%s/proc", netdata_configured_host_prefix);
532 + find_all_sockets_in_proc(ls, path);
533 + }
534 +
535 + // detect the directions of the sockets
536 + if(ls->config.inbound || ls->config.outbound || ls->config.local)
537 + local_sockets_detect_directions(ls);
538 +
539 + // this will call the callback for each socket and free the memory we use
540 + foreach_local_socket_call_cb_and_cleanup(ls);
541 +
542 + // free the hashtable
543 + simple_hashtable_destroy_LOCAL_PORT(&ls->listening_ports_hashtable);
544 + simple_hashtable_destroy_LOCAL_IP(&ls->local_ips_hashtable);
545 + simple_hashtable_destroy_LOCAL_SOCKET(&ls->sockets_hashtable);
546 +}
547 +
548 +static inline void ipv6_address_to_txt(struct in6_addr *in6_addr, char *dst) {
549 + struct sockaddr_in6 sa = { 0 };
550 +
551 + sa.sin6_family = AF_INET6;
552 + sa.sin6_port = htons(0);
553 + sa.sin6_addr = *in6_addr;
554 +
555 + // Convert to human-readable format
556 + if (inet_ntop(AF_INET6, &(sa.sin6_addr), dst, INET6_ADDRSTRLEN) == NULL)
557 + *dst = '\0';
558 +}
559 +
560 +static inline void ipv4_address_to_txt(uint32_t ip, char *dst) {
561 + uint8_t octets[4];
562 + octets[0] = ip & 0xFF;
563 + octets[1] = (ip >> 8) & 0xFF;
564 + octets[2] = (ip >> 16) & 0xFF;
565 + octets[3] = (ip >> 24) & 0xFF;
566 + sprintf(dst, "%u.%u.%u.%u", octets[0], octets[1], octets[2], octets[3]);
567 +}
568 +
569 +#endif //NETDATA_LOCAL_SOCKETS_H
collectors/plugins.d/local_listeners.c
+226 -362
@@ -1,400 +1,264 @@
1 -#include "libnetdata/libnetdata.h"
2 -#include "libnetdata/required_dummies.h"
3 -
4 -#include <stdio.h>
5 -#include <stdlib.h>
6 -#include <stdbool.h>
7 -#include <dirent.h>
8 -#include <string.h>
9 -#include <unistd.h>
10 -#include <ctype.h>
11 -#include <arpa/inet.h>
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2
13 -typedef enum {
14 - PROC_NET_PROTOCOL_TCP,
15 - PROC_NET_PROTOCOL_TCP6,
16 - PROC_NET_PROTOCOL_UDP,
17 - PROC_NET_PROTOCOL_UDP6,
18 -} PROC_NET_PROTOCOLS;
19 -
20 -#define MAX_ERROR_LOGS 10
3 +#include "local-sockets.h"
4 +#include "libnetdata/required_dummies.h"
5
22 -static size_t pid_fds_processed = 0;
23 -static size_t pid_fds_failed = 0;
24 -static size_t errors_encountered = 0;
6 +// --------------------------------------------------------------------------------------------------------------------
7
26 -static inline const char *protocol_name(PROC_NET_PROTOCOLS protocol) {
27 - switch(protocol) {
28 - default:
29 - case PROC_NET_PROTOCOL_TCP:
8 +static const char *protocol_name(LOCAL_SOCKET *n) {
9 + if(n->family == AF_INET) {
10 + if(n->protocol == IPPROTO_TCP)
11 return "TCP";
31 -
32 - case PROC_NET_PROTOCOL_UDP:
12 + else if(n->protocol == IPPROTO_UDP)
13 return "UDP";
34 -
35 - case PROC_NET_PROTOCOL_TCP6:
14 + else
15 + return "UNKNOWN_IPV4";
16 + }
17 + else if(n->family == AF_INET6) {
18 + if (n->protocol == IPPROTO_TCP)
19 return "TCP6";
37 -
38 - case PROC_NET_PROTOCOL_UDP6:
20 + else if(n->protocol == IPPROTO_UDP)
21 return "UDP6";
22 + else
23 + return "UNKNOWN_IPV6";
24 }
25 + else
26 + return "UNKNOWN";
27 }
28
43 -static inline int read_cmdline(pid_t pid, char* buffer, size_t bufferSize) {
44 - char path[FILENAME_MAX + 1];
45 - snprintfz(path, FILENAME_MAX, "%s/proc/%d/cmdline", netdata_configured_host_prefix, pid);
46 -
47 - FILE* file = fopen(path, "r");
48 - if (!file) {
49 - if(++errors_encountered < MAX_ERROR_LOGS)
50 - collector_error("LOCAL-LISTENERS: error opening file: %s\n", path);
51 -
52 - return -1;
53 - }
54 -
55 - size_t bytesRead = fread(buffer, 1, bufferSize - 1, file);
56 - buffer[bytesRead] = '\0'; // Ensure null-terminated
57 -
58 - // Replace null characters in cmdline with spaces
59 - for (size_t i = 0; i < bytesRead; i++) {
60 - if (buffer[i] == '\0') {
61 - buffer[i] = ' ';
62 - }
63 - }
64 -
65 - fclose(file);
66 - return 0;
67 -}
68 -
69 -static inline void fix_cmdline(char* str) {
70 - if (str == NULL)
71 - return;
72 -
73 - char *s = str;
74 -
75 - do {
76 - if(*s == '|' || iscntrl(*s))
77 - *s = '_';
78 -
79 - } while(*++s);
80 -
81 -
82 - while(s > str && *(s-1) == ' ')
83 - *--s = '\0';
84 -}
85 -
86 -// ----------------------------------------------------------------------------
87 -
88 -#define HASH_TABLE_SIZE 100000
89 -
90 -typedef struct Node {
91 - unsigned int inode; // key
92 -
93 - // values
94 - unsigned int port;
29 +static void print_local_listeners(LS_STATE *ls __maybe_unused, LOCAL_SOCKET *n, void *data __maybe_unused) {
30 char local_address[INET6_ADDRSTRLEN];
96 - PROC_NET_PROTOCOLS protocol;
97 - bool processed;
98 -
99 - // linking
100 - struct Node *prev, *next;
101 -} Node;
31 + char remote_address[INET6_ADDRSTRLEN];
32
103 -typedef struct HashTable {
104 - Node *table[HASH_TABLE_SIZE];
105 -} HashTable;
106 -
107 -static HashTable *hashTable_key_inode_port_value = NULL;
108 -
109 -static inline void generate_output(const char *protocol, const char *address, unsigned int port, const char *cmdline) {
110 - printf("%s|%s|%u|%s\n", protocol, address, port, cmdline);
111 -}
112 -
113 -HashTable* createHashTable() {
114 - HashTable *hashTable = (HashTable*)mallocz(sizeof(HashTable));
115 - memset(hashTable, 0, sizeof(HashTable));
116 - return hashTable;
117 -}
118 -
119 -static inline unsigned int hashFunction(unsigned int inode) {
120 - return inode % HASH_TABLE_SIZE;
121 -}
122 -
123 -static inline void insertHashTable(HashTable *hashTable, unsigned int inode, unsigned int port, PROC_NET_PROTOCOLS protocol, char *local_address) {
124 - unsigned int index = hashFunction(inode);
125 - Node *newNode = (Node*)mallocz(sizeof(Node));
126 - newNode->inode = inode;
127 - newNode->port = port;
128 - newNode->protocol = protocol;
129 - strncpyz(newNode->local_address, local_address, INET6_ADDRSTRLEN - 1);
130 - DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(hashTable->table[index], newNode, prev, next);
131 -}
132 -
133 -static inline bool lookupHashTable_and_execute(HashTable *hashTable, unsigned int inode, pid_t pid) {
134 - unsigned int index = hashFunction(inode);
135 - for(Node *node = hashTable->table[index], *next = NULL ; node ; node = next) {
136 - next = node->next;
137 -
138 - if(node->inode == inode && node->port) {
139 - DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(hashTable->table[index], node, prev, next);
140 - char cmdline[8192] = "";
141 - read_cmdline(pid, cmdline, sizeof(cmdline));
142 - fix_cmdline(cmdline);
143 - generate_output(protocol_name(node->protocol), node->local_address, node->port, cmdline);
144 - freez(node);
145 - return true;
146 - }
33 + if(n->family == AF_INET) {
34 + ipv4_address_to_txt(n->local.ip.ipv4, local_address);
35 + ipv4_address_to_txt(n->remote.ip.ipv4, remote_address);
36 }
148 -
149 - return false;
150 -}
151 -
152 -void freeHashTable(HashTable *hashTable) {
153 - for (unsigned int i = 0; i < HASH_TABLE_SIZE; i++) {
154 - while(hashTable->table[i]) {
155 - Node *tmp = hashTable->table[i];
156 - DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(hashTable->table[i], tmp, prev, next);
157 - generate_output(protocol_name(tmp->protocol), tmp->local_address, tmp->port, "");
158 - freez(tmp);
159 - }
160 - }
161 - freez(hashTable);
162 -}
163 -
164 -// ----------------------------------------------------------------------------
165 -
166 -static inline void found_this_socket_inode(pid_t pid, unsigned int inode) {
167 - lookupHashTable_and_execute(hashTable_key_inode_port_value, inode, pid);
168 -}
169 -
170 -bool find_all_sockets_in_proc(const char *proc_filename) {
171 - DIR *proc_dir, *fd_dir;
172 - struct dirent *proc_entry, *fd_entry;
173 - char path_buffer[FILENAME_MAX + 1];
174 -
175 - proc_dir = opendir(proc_filename);
176 - if (proc_dir == NULL) {
177 - if(++errors_encountered < MAX_ERROR_LOGS)
178 - collector_error("LOCAL-LISTENERS: cannot opendir() '%s'", proc_filename);
179 -
180 - pid_fds_failed++;
181 - return false;
182 - }
183 -
184 - while ((proc_entry = readdir(proc_dir)) != NULL) {
185 - // Check if directory entry is a PID by seeing if the name is made up of digits only
186 - int is_pid = 1;
187 - for (char *c = proc_entry->d_name; *c != '\0'; c++) {
188 - if (*c < '0' || *c > '9') {
189 - is_pid = 0;
190 - break;
191 - }
192 - }
193 -
194 - if (!is_pid)
195 - continue;
196 -
197 - // Build the path to the fd directory of the process
198 - snprintfz(path_buffer, FILENAME_MAX, "%s/%s/fd/", proc_filename, proc_entry->d_name);
199 -
200 - fd_dir = opendir(path_buffer);
201 - if (fd_dir == NULL) {
202 - if(++errors_encountered < MAX_ERROR_LOGS)
203 - collector_error("LOCAL-LISTENERS: cannot opendir() '%s'", path_buffer);
204 -
205 - pid_fds_failed++;
206 - continue;
207 - }
208 -
209 - while ((fd_entry = readdir(fd_dir)) != NULL) {
210 - if(!strcmp(fd_entry->d_name, ".") || !strcmp(fd_entry->d_name, ".."))
211 - continue;
212 -
213 - char link_path[FILENAME_MAX + 1];
214 - char link_target[FILENAME_MAX + 1];
215 - int inode;
216 -
217 - // Build the path to the file descriptor link
218 - snprintfz(link_path, FILENAME_MAX, "%s/%s", path_buffer, fd_entry->d_name);
219 -
220 - ssize_t len = readlink(link_path, link_target, sizeof(link_target) - 1);
221 - if (len == -1) {
222 - if(++errors_encountered < MAX_ERROR_LOGS)
223 - collector_error("LOCAL-LISTENERS: cannot read link '%s'", link_path);
224 -
225 - pid_fds_failed++;
226 - continue;
227 - }
228 - link_target[len] = '\0';
229 -
230 - pid_fds_processed++;
231 -
232 - // If the link target indicates a socket, print its inode number
233 - if (sscanf(link_target, "socket:[%d]", &inode) == 1)
234 - found_this_socket_inode((pid_t)strtoul(proc_entry->d_name, NULL, 10), inode);
235 - }
236 -
237 - closedir(fd_dir);
37 + else if(n->family == AF_INET6) {
38 + ipv6_address_to_txt(&n->local.ip.ipv6, local_address);
39 + ipv6_address_to_txt(&n->remote.ip.ipv6, remote_address);
40 }
41
240 - closedir(proc_dir);
241 - return true;
242 -}
243 -
244 -// ----------------------------------------------------------------------------
245 -
246 -static inline void add_port_and_inode(PROC_NET_PROTOCOLS protocol, unsigned int port, unsigned int inode, char *local_address) {
247 - insertHashTable(hashTable_key_inode_port_value, inode, port, protocol, local_address);
42 + printf("%s|%s|%u|%s\n", protocol_name(n), local_address, n->local.port, n->cmdline ? n->cmdline : "");
43 }
44
250 -static inline void print_ipv6_address(const char *ipv6_str, char *dst) {
251 - unsigned k;
252 - char buf[9];
253 - struct sockaddr_in6 sa;
254 -
255 - // Initialize sockaddr_in6
256 - memset(&sa, 0, sizeof(struct sockaddr_in6));
257 - sa.sin6_family = AF_INET6;
258 - sa.sin6_port = htons(0); // replace 0 with your port number
45 +static void print_local_listeners_debug(LS_STATE *ls __maybe_unused, LOCAL_SOCKET *n, void *data __maybe_unused) {
46 + char local_address[INET6_ADDRSTRLEN];
47 + char remote_address[INET6_ADDRSTRLEN];
48
260 - // Convert hex string to byte array
261 - for (k = 0; k < 4; ++k)
262 - {
263 - memset(buf, 0, 9);
264 - memcpy(buf, ipv6_str + (k * 8), 8);
265 - sa.sin6_addr.s6_addr32[k] = strtoul(buf, NULL, 16);
49 + if(n->family == AF_INET) {
50 + ipv4_address_to_txt(n->local.ip.ipv4, local_address);
51 + ipv4_address_to_txt(n->remote.ip.ipv4, remote_address);
52 }
267 -
268 - // Convert to human-readable format
269 - if (inet_ntop(AF_INET6, &(sa.sin6_addr), dst, INET6_ADDRSTRLEN) == NULL)
270 - *dst = '\0';
271 -}
272 -
273 -static inline void print_ipv4_address(uint32_t address, char *dst) {
274 - uint8_t octets[4];
275 - octets[0] = address & 0xFF;
276 - octets[1] = (address >> 8) & 0xFF;
277 - octets[2] = (address >> 16) & 0xFF;
278 - octets[3] = (address >> 24) & 0xFF;
279 - sprintf(dst, "%u.%u.%u.%u", octets[0], octets[1], octets[2], octets[3]);
280 -}
281 -
282 -bool read_proc_net_x(const char *filename, PROC_NET_PROTOCOLS protocol) {
283 - FILE *fp;
284 - char *line = NULL;
285 - size_t len = 0;
286 - ssize_t read;
287 - char address[INET6_ADDRSTRLEN];
288 -
289 - ssize_t min_line_length = (protocol == PROC_NET_PROTOCOL_TCP || protocol == PROC_NET_PROTOCOL_UDP) ? 105 : 155;
290 -
291 - fp = fopen(filename, "r");
292 - if (fp == NULL)
293 - return false;
294 -
295 - // Read line by line
296 - while ((read = getline(&line, &len, fp)) != -1) {
297 - if(read < min_line_length) continue;
298 -
299 - char local_address6[33], rem_address6[33];
300 - unsigned int local_address, local_port, state, rem_address, rem_port, inode;
301 -
302 - switch(protocol) {
303 - case PROC_NET_PROTOCOL_TCP:
304 - if(line[34] != '0' || line[35] != 'A')
305 - continue;
306 - // fall-through
307 -
308 - case PROC_NET_PROTOCOL_UDP:
309 - if (sscanf(line, "%*d: %X:%X %X:%X %X %*X:%*X %*X:%*X %*X %*d %*d %u",
310 - &local_address, &local_port, &rem_address, &rem_port, &state, &inode) != 6)
311 - continue;
312 -
313 - print_ipv4_address(local_address, address);
314 - break;
315 -
316 - case PROC_NET_PROTOCOL_TCP6:
317 - if(line[82] != '0' || line[83] != 'A')
318 - continue;
319 - // fall-through
320 -
321 - case PROC_NET_PROTOCOL_UDP6:
322 - if(sscanf(line, "%*d: %32[0-9A-Fa-f]:%X %32[0-9A-Fa-f]:%X %X %*X:%*X %*X:%*X %*X %*d %*d %u",
323 - local_address6, &local_port, rem_address6, &rem_port, &state, &inode) != 6)
324 - continue;
325 -
326 - print_ipv6_address(local_address6, address);
327 - break;
328 - }
329 -
330 - add_port_and_inode(protocol, local_port, inode, address);
53 + else if(n->family == AF_INET6) {
54 + ipv6_address_to_txt(&n->local.ip.ipv6, local_address);
55 + ipv6_address_to_txt(&n->remote.ip.ipv6, remote_address);
56 }
57
333 - fclose(fp);
334 - if (line)
335 - free(line);
336 -
337 - return true;
58 + printf("%s, direction=%s%s%s%s%s pid=%d, state=0x%0x, local=%s[:%u], remote=%s[:%u], comm=%s\n",
59 + protocol_name(n),
60 + (n->direction & SOCKET_DIRECTION_LISTEN) ? "LISTEN," : "",
61 + (n->direction & SOCKET_DIRECTION_INBOUND) ? "INBOUND," : "",
62 + (n->direction & SOCKET_DIRECTION_OUTBOUND) ? "OUTBOUND," : "",
63 + (n->direction & SOCKET_DIRECTION_LOCAL) ? "LOCAL," : "",
64 + (n->direction == 0) ? "NONE," : "",
65 + n->pid,
66 + n->state,
67 + local_address, n->local.port,
68 + remote_address, n->remote.port,
69 + n->comm);
70 }
71
340 -// ----------------------------------------------------------------------------
341 -typedef struct {
342 - bool read_tcp;
343 - bool read_tcp6;
344 - bool read_udp;
345 - bool read_udp6;
346 -} CommandLineArguments;
72 +// --------------------------------------------------------------------------------------------------------------------
73
74 int main(int argc, char **argv) {
349 - char path[FILENAME_MAX + 1];
350 - hashTable_key_inode_port_value = createHashTable();
75 + LS_STATE ls = {
76 + .config = {
77 + .listening = true,
78 + .inbound = false,
79 + .outbound = false,
80 + .local = false,
81 + .tcp4 = true,
82 + .tcp6 = true,
83 + .udp4 = true,
84 + .udp6 = true,
85 + .pid = false,
86 + .cmdline = true,
87 + .comm = false,
88 +
89 + .max_errors = 10,
90 +
91 + .cb = print_local_listeners,
92 + .data = NULL,
93 + },
94 + .stats = { 0 },
95 + .sockets_hashtable = { 0 },
96 + .local_ips_hashtable = { 0 },
97 + .listening_ports_hashtable = { 0 },
98 + };
99
100 netdata_configured_host_prefix = getenv("NETDATA_HOST_PREFIX");
101 if(!netdata_configured_host_prefix) netdata_configured_host_prefix = "";
102
355 - CommandLineArguments args = {.read_tcp = false, .read_tcp6 = false, .read_udp = false, .read_udp6 = false};
356 -
103 for (int i = 1; i < argc; i++) {
358 - if (strcmp("tcp", argv[i]) == 0) {
359 - args.read_tcp = true;
360 - continue;
361 - } else if (strcmp("tcp6", argv[i]) == 0) {
362 - args.read_tcp6 = true;
363 - continue;
364 - } else if (strcmp("udp", argv[i]) == 0) {
365 - args.read_udp = true;
366 - continue;
367 - } else if (strcmp("udp6", argv[i]) == 0) {
368 - args.read_udp6 = true;
369 - continue;
104 + char *s = argv[i];
105 + bool positive = true;
106 +
107 + if(strcmp(s, "-h") == 0 || strcmp(s, "--help") == 0) {
108 + fprintf(stderr,
109 + "\n"
110 + " Netdata local-listeners\n"
111 + " (C) 2024 Netdata Inc.\n"
112 + "\n"
113 + " This program prints a list of all the processes that have a listening socket.\n"
114 + " It is used by Netdata to auto-detect the services running.\n"
115 + "\n"
116 + " Options:\n"
117 + "\n"
118 + " The options:\n"
119 + "\n"
120 + " udp, udp4, udp6, tcp, tcp4, tcp6, ipv4, ipv6\n"
121 + "\n"
122 + " select the sources to read currently available sockets.\n"
123 + "\n"
124 + " while:\n"
125 + "\n"
126 + " listening, local, inbound, outbound\n"
127 + "\n"
128 + " filter the output based on the direction of the sockets.\n"
129 + "\n"
130 + " Prepending any option with 'no-', 'not-' or 'non-' will disable them.\n"
131 + "\n"
132 + " Current options:\n"
133 + "\n"
134 + " %s %s %s %s %s %s %s %s\n"
135 + "\n"
136 + " Option 'debug' enables all sources and all directions and provides\n"
137 + " a full dump of current sockets.\n"
138 + "\n"
139 + " DIRECTION DETECTION\n"
140 + " The program detects the direction of the sockets using these rules:\n"
141 + "\n"
142 + " - listening are all the TCP sockets that are in listen state\n"
143 + " and all sockets that their remote IP is zero.\n"
144 + "\n"
145 + " - local are all the non-listening sockets that either their source IP\n"
146 + " or their remote IP are loopback addresses. Loopback addresses are\n"
147 + " those in 127.0.0.0/8 and ::1. When IPv4 addresses are mapped\n"
148 + " into IPv6, the program extracts the IPv4 addresses to check them.\n"
149 + "\n"
150 + " Also, local are considered all the sockets that their remote\n"
151 + " IP is one of the IPs that appear as local on another socket.\n"
152 + "\n"
153 + " - inbound are all the non-listening and non-local sockets that their local\n"
154 + " port is a port of another socket that is marked as listening.\n"
155 + "\n"
156 + " - outbound are all the other sockets.\n"
157 + "\n"
158 + " Keep in mind that this kind of socket direction detection is not 100%% accurate,\n"
159 + " and there may be cases (e.g. reusable sockets) that this code may incorrectly\n"
160 + " mark sockets as inbound or outbound.\n"
161 + "\n"
162 + " WARNING:\n"
163 + " This program reads the entire /proc/net/{tcp,udp,tcp6,upd6} files, builds\n"
164 + " multiple hash maps in memory and traverses the entire /proc filesystem to\n"
165 + " associate sockets with processes. We have made the most to make it as\n"
166 + " lightweight and fast as possible, but still this program has a lot of work\n"
167 + " to do and it may have some impact on very busy servers with millions of.\n"
168 + " established connections."
169 + "\n"
170 + " Therefore, we suggest to avoid running it repeatedly for data collection.\n"
171 + "\n"
172 + " Netdata executes it only when it starts to auto-detect data collection sources\n"
173 + " and initialize the network dependencies explorer."
174 + "\n"
175 + , ls.config.udp4 ? "udp4" :"no-udp4"
176 + , ls.config.udp6 ? "udp6" :"no-udp6"
177 + , ls.config.tcp4 ? "tcp4" :"no-tcp4"
178 + , ls.config.tcp6 ? "tcp6" :"no-tcp6"
179 + , ls.config.listening ? "listening" : "no-listening"
180 + , ls.config.local ? "local" : "no-local"
181 + , ls.config.inbound ? "inbound" : "no-inbound"
182 + , ls.config.outbound ? "outbound" : "no-outbound"
183 + );
184 + exit(1);
185 }
371 - }
372 -
373 - bool read_all_files = (!args.read_tcp && !args.read_tcp6 && !args.read_udp && !args.read_udp6);
186
375 - if (read_all_files || args.read_tcp) {
376 - snprintfz(path, FILENAME_MAX, "%s/proc/net/tcp", netdata_configured_host_prefix);
377 - read_proc_net_x(path, PROC_NET_PROTOCOL_TCP);
378 - }
379 -
380 - if (read_all_files || args.read_udp) {
381 - snprintfz(path, FILENAME_MAX, "%s/proc/net/udp", netdata_configured_host_prefix);
382 - read_proc_net_x(path, PROC_NET_PROTOCOL_UDP);
383 - }
384 -
385 - if (read_all_files || args.read_tcp6) {
386 - snprintfz(path, FILENAME_MAX, "%s/proc/net/tcp6", netdata_configured_host_prefix);
387 - read_proc_net_x(path, PROC_NET_PROTOCOL_TCP6);
388 - }
187 + if(strncmp(s, "no-", 3) == 0) {
188 + positive = false;
189 + s += 3;
190 + }
191 + else if(strncmp(s, "not-", 4) == 0 || strncmp(s, "non-", 4) == 0) {
192 + positive = false;
193 + s += 4;
194 + }
195
390 - if (read_all_files || args.read_udp6) {
391 - snprintfz(path, FILENAME_MAX, "%s/proc/net/udp6", netdata_configured_host_prefix);
392 - read_proc_net_x(path, PROC_NET_PROTOCOL_UDP6);
196 + if(strcmp(s, "debug") == 0 || strcmp(s, "--debug") == 0) {
197 + fprintf(stderr, "%s debugging\n", positive ? "enabling" : "disabling");
198 + ls.config.listening = true;
199 + ls.config.local = true;
200 + ls.config.inbound = true;
201 + ls.config.outbound = true;
202 + ls.config.pid = true;
203 + ls.config.comm = true;
204 + ls.config.cmdline = false;
205 + ls.config.cb = print_local_listeners_debug;
206 + }
207 + else if (strcmp("tcp", s) == 0) {
208 + ls.config.tcp4 = ls.config.tcp6 = positive;
209 + // fprintf(stderr, "%s tcp4 and tcp6\n", positive ? "enabling" : "disabling");
210 + }
211 + else if (strcmp("tcp4", s) == 0) {
212 + ls.config.tcp4 = positive;
213 + // fprintf(stderr, "%s tcp4\n", positive ? "enabling" : "disabling");
214 + }
215 + else if (strcmp("tcp6", s) == 0) {
216 + ls.config.tcp6 = positive;
217 + // fprintf(stderr, "%s tcp6\n", positive ? "enabling" : "disabling");
218 + }
219 + else if (strcmp("udp", s) == 0) {
220 + ls.config.udp4 = ls.config.udp6 = positive;
221 + // fprintf(stderr, "%s udp4 and udp6\n", positive ? "enabling" : "disabling");
222 + }
223 + else if (strcmp("udp4", s) == 0) {
224 + ls.config.udp4 = positive;
225 + // fprintf(stderr, "%s udp4\n", positive ? "enabling" : "disabling");
226 + }
227 + else if (strcmp("udp6", s) == 0) {
228 + ls.config.udp6 = positive;
229 + // fprintf(stderr, "%s udp6\n", positive ? "enabling" : "disabling");
230 + }
231 + else if (strcmp("ipv4", s) == 0) {
232 + ls.config.tcp4 = ls.config.udp4 = positive;
233 + // fprintf(stderr, "%s udp4 and tcp4\n", positive ? "enabling" : "disabling");
234 + }
235 + else if (strcmp("ipv6", s) == 0) {
236 + ls.config.tcp6 = ls.config.udp6 = positive;
237 + // fprintf(stderr, "%s udp6 and tcp6\n", positive ? "enabling" : "disabling");
238 + }
239 + else if (strcmp("listening", s) == 0) {
240 + ls.config.listening = positive;
241 + // fprintf(stderr, "%s listening\n", positive ? "enabling" : "disabling");
242 + }
243 + else if (strcmp("local", s) == 0) {
244 + ls.config.local = positive;
245 + // fprintf(stderr, "%s local\n", positive ? "enabling" : "disabling");
246 + }
247 + else if (strcmp("inbound", s) == 0) {
248 + ls.config.inbound = positive;
249 + // fprintf(stderr, "%s inbound\n", positive ? "enabling" : "disabling");
250 + }
251 + else if (strcmp("outbound", s) == 0) {
252 + ls.config.outbound = positive;
253 + // fprintf(stderr, "%s outbound\n", positive ? "enabling" : "disabling");
254 + }
255 + else {
256 + fprintf(stderr, "Unknown parameter %s\n", s);
257 + exit(1);
258 + }
259 }
260
395 - snprintfz(path, FILENAME_MAX, "%s/proc", netdata_configured_host_prefix);
396 - find_all_sockets_in_proc(path);
261 + local_sockets_process(&ls);
262
398 - freeHashTable(hashTable_key_inode_port_value);
263 return 0;
264 }
collectors/proc.plugin/proc_diskstats.c
+5 -5
@@ -213,7 +213,7 @@ static SIMPLE_PATTERN *excluded_disks = NULL;
213
214 static unsigned long long int bcache_read_number_with_units(const char *filename) {
215 char buffer[50 + 1];
216 - if(read_file(filename, buffer, 50) == 0) {
216 + if(read_txt_file(filename, buffer, sizeof(buffer)) == 0) {
217 static int unknown_units_error = 10;
218
219 char *end = NULL;
@@ -547,9 +547,9 @@ static inline char *get_disk_model(char *device) {
547 char buffer[256 + 1];
548
549 snprintfz(path, sizeof(path) - 1, "%s/%s/device/model", path_to_sys_block, device);
550 - if(read_file(path, buffer, 256) != 0) {
550 + if(read_txt_file(path, buffer, sizeof(buffer)) != 0) {
551 snprintfz(path, sizeof(path) - 1, "%s/%s/device/name", path_to_sys_block, device);
552 - if(read_file(path, buffer, 256) != 0)
552 + if(read_txt_file(path, buffer, sizeof(buffer)) != 0)
553 return NULL;
554 }
555
@@ -565,7 +565,7 @@ static inline char *get_disk_serial(char *device) {
565 char buffer[256 + 1];
566
567 snprintfz(path, sizeof(path) - 1, "%s/%s/device/serial", path_to_sys_block, device);
568 - if(read_file(path, buffer, 256) != 0)
568 + if(read_txt_file(path, buffer, sizeof(buffer)) != 0)
569 return NULL;
570
571 return strdupz(buffer);
@@ -778,7 +778,7 @@ static struct disk *get_disk(unsigned long major, unsigned long minor, char *dis
778 strncat(uuid_filename, "/dm/uuid", FILENAME_MAX - size);
779
780 char device_uuid[RRD_ID_LENGTH_MAX + 1];
781 - if (!read_file(uuid_filename, device_uuid, RRD_ID_LENGTH_MAX) && !strncmp(device_uuid, "LVM-", 4)) {
781 + if (!read_txt_file(uuid_filename, device_uuid, sizeof(device_uuid)) && !strncmp(device_uuid, "LVM-", 4)) {
782 trim(device_uuid);
783
784 char chart_id[RRD_ID_LENGTH_MAX + 1];
collectors/proc.plugin/proc_net_dev.c
+2 -2
@@ -1140,7 +1140,7 @@ int do_proc_net_dev(int update_every, usec_t dt) {
1140 now_monotonic_sec() - d->duplex_file_lost_time > READ_RETRY_PERIOD)) {
1141 char buffer[STATE_LENGTH_MAX + 1];
1142
1143 - if (read_file(d->filename_duplex, buffer, STATE_LENGTH_MAX)) {
1143 + if (read_txt_file(d->filename_duplex, buffer, sizeof(buffer))) {
1144 if (d->duplex_file_exists)
1145 collector_error("Cannot refresh interface %s duplex state by reading '%s'.", d->name, d->filename_duplex);
1146 d->duplex_file_exists = 0;
@@ -1164,7 +1164,7 @@ int do_proc_net_dev(int update_every, usec_t dt) {
1164 if(d->do_operstate != CONFIG_BOOLEAN_NO && d->filename_operstate) {
1165 char buffer[STATE_LENGTH_MAX + 1], *trimmed_buffer;
1166
1167 - if (read_file(d->filename_operstate, buffer, STATE_LENGTH_MAX)) {
1167 + if (read_txt_file(d->filename_operstate, buffer, sizeof(buffer))) {
1168 collector_error(
1169 "Cannot refresh %s operstate by reading '%s'. Will not update its status anymore.",
1170 d->name, d->filename_operstate);
collectors/proc.plugin/proc_net_sockstat.c
+1 -1
@@ -44,7 +44,7 @@ static int read_tcp_mem(void) {
44 }
45
46 char buffer[200 + 1], *start, *end;
47 - if(read_file(filename, buffer, 200) != 0) return 1;
47 + if(read_txt_file(filename, buffer, sizeof(buffer)) != 0) return 1;
48 buffer[200] = '\0';
49
50 unsigned long long low = 0, pressure = 0, high = 0;
collectors/proc.plugin/proc_spl_kstat_zfs.c
+1 -1
@@ -378,7 +378,7 @@ int do_proc_spl_kstat_zfs_pool_state(int update_every, usec_t dt)
378 snprintfz(filename, FILENAME_MAX, "%s/%s/state", dirname, de->d_name);
379
380 char state[STATE_SIZE + 1];
381 - int ret = read_file(filename, state, STATE_SIZE);
381 + int ret = read_txt_file(filename, state, sizeof(state));
382
383 if (!ret) {
384 state_file_found = 1;
collectors/proc.plugin/sys_class_infiniband.c
+1 -1
@@ -470,7 +470,7 @@ int do_sys_class_infiniband(int update_every, usec_t dt)
470 snprintfz(buffer, FILENAME_MAX, "%s/%s/%s", ports_dirname, port_dent->d_name, "rate");
471 char buffer_rate[65];
472 p->width = 4;
473 - if (read_file(buffer, buffer_rate, 64)) {
473 + if (read_txt_file(buffer, buffer_rate, sizeof(buffer_rate))) {
474 collector_error("Unable to read '%s'", buffer);
475 } else {
476 char *buffer_width = strstr(buffer_rate, "(");
collectors/proc.plugin/sys_devices_system_edac_mc.c
+2 -2
@@ -150,7 +150,7 @@ static kernel_uint_t read_edac_count(struct edac_count *t) {
150 static bool read_edac_mc_file(const char *mc, const char *filename, char *out, size_t out_size) {
151 char f[FILENAME_MAX + 1];
152 snprintfz(f, FILENAME_MAX, "%s/%s/%s", mc_dirname, mc, filename);
153 - if(read_file(f, out, out_size) != 0) {
153 + if(read_txt_file(f, out, out_size) != 0) {
154 collector_error("EDAC: cannot read file '%s'", f);
155 return false;
156 }
@@ -160,7 +160,7 @@ static bool read_edac_mc_file(const char *mc, const char *filename, char *out, s
160 static bool read_edac_mc_rank_file(const char *mc, const char *rank, const char *filename, char *out, size_t out_size) {
161 char f[FILENAME_MAX + 1];
162 snprintfz(f, FILENAME_MAX, "%s/%s/%s/%s", mc_dirname, mc, rank, filename);
163 - if(read_file(f, out, out_size) != 0) {
163 + if(read_txt_file(f, out, out_size) != 0) {
164 collector_error("EDAC: cannot read file '%s'", f);
165 return false;
166 }
collectors/proc.plugin/sys_fs_btrfs.c
+3 -3
@@ -122,7 +122,7 @@ static BTRFS_NODE *nodes = NULL;
122 static inline int collect_btrfs_error_stats(BTRFS_DEVICE *device){
123 char buffer[120 + 1];
124
125 - int ret = read_file(device->error_stats_filename, buffer, 120);
125 + int ret = read_txt_file(device->error_stats_filename, buffer, sizeof(buffer));
126 if(unlikely(ret)) {
127 collector_error("BTRFS: failed to read '%s'", device->error_stats_filename);
128 device->write_errs = 0;
@@ -151,7 +151,7 @@ static inline int collect_btrfs_error_stats(BTRFS_DEVICE *device){
151 static inline int collect_btrfs_commits_stats(BTRFS_NODE *node, int update_every){
152 char buffer[120 + 1];
153
154 - int ret = read_file(node->commit_stats_filename, buffer, 120);
154 + int ret = read_txt_file(node->commit_stats_filename, buffer, sizeof(buffer));
155 if(unlikely(ret)) {
156 collector_error("BTRFS: failed to read '%s'", node->commit_stats_filename);
157 node->commits_total = 0;
@@ -530,7 +530,7 @@ static inline int find_all_btrfs_pools(const char *path, int update_every) {
530 char label[FILENAME_MAX + 1] = "";
531
532 snprintfz(filename, FILENAME_MAX, "%s/%s/label", path, de->d_name);
533 - if(read_file(filename, label, FILENAME_MAX) != 0) {
533 + if(read_txt_file(filename, label, sizeof(label)) != 0) {
534 collector_error("BTRFS: failed to read '%s'", filename);
535 btrfs_free_node(node);
536 continue;
collectors/systemd-journal.plugin/systemd-internals.h
-7
@@ -132,13 +132,6 @@ void journal_watcher_restart(void);
132 void function_systemd_units(const char *transaction, char *function, usec_t *stop_monotonic_ut, bool *cancelled, BUFFER *payload, HTTP_ACCESS access __maybe_unused, const char *source, void *data);
133 #endif
134
135 -static inline void send_newline_and_flush(void) {
136 - netdata_mutex_lock(&stdout_mutex);
137 - fprintf(stdout, "\n");
138 - fflush(stdout);
139 - netdata_mutex_unlock(&stdout_mutex);
140 -}
141 -
135 static inline bool parse_journal_field(const char *data, size_t data_length, const char **key, size_t *key_length, const char **value, size_t *value_length) {
136 const char *k = data;
137 const char *equal = strchr(k, '=');
collectors/systemd-journal.plugin/systemd-journal-files.c
+2 -2
@@ -639,7 +639,7 @@ void journal_directory_scan_recursively(DICTIONARY *files, DICTIONARY *dirs, con
639 if(files)
640 dictionary_set(files, full_path, NULL, 0);
641
642 - send_newline_and_flush();
642 + send_newline_and_flush(&stdout_mutex);
643 }
644 else if (entry->d_type == DT_LNK) {
645 struct stat info;
@@ -657,7 +657,7 @@ void journal_directory_scan_recursively(DICTIONARY *files, DICTIONARY *dirs, con
657 if(files)
658 dictionary_set(files, full_path, NULL, 0);
659
660 - send_newline_and_flush();
660 + send_newline_and_flush(&stdout_mutex);
661 }
662 }
663 }
collectors/systemd-journal.plugin/systemd-main.c
+1 -1
@@ -134,7 +134,7 @@ int main(int argc __maybe_unused, char **argv __maybe_unused) {
134 send_newline_ut += dt_ut;
135
136 if(!tty && send_newline_ut > USEC_PER_SEC) {
137 - send_newline_and_flush();
137 + send_newline_and_flush(&stdout_mutex);
138 send_newline_ut = 0;
139 }
140 }
daemon/analytics.c
+1 -1
@@ -718,7 +718,7 @@ void get_system_timezone(void)
718 }
719
720 // use the contents of /etc/timezone
721 - if (!timezone && !read_file("/etc/timezone", buffer, FILENAME_MAX)) {
721 + if (!timezone && !read_txt_file("/etc/timezone", buffer, sizeof(buffer))) {
722 timezone = buffer;
723 netdata_log_info("TIMEZONE: using the contents of /etc/timezone");
724 }
daemon/main.c
+1 -1
@@ -1100,7 +1100,7 @@ static int get_hostname(char *buf, size_t buf_size) {
1100 char filename[FILENAME_MAX + 1];
1101 snprintfz(filename, FILENAME_MAX, "%s/etc/hostname", netdata_configured_host_prefix);
1102
1103 - if (!read_file(filename, buf, buf_size)) {
1103 + if (!read_txt_file(filename, buf, buf_size)) {
1104 trim(buf);
1105 return 0;
1106 }
libnetdata/ebpf/ebpf.c
+2 -2
@@ -179,8 +179,8 @@ static int kernel_is_rejected()
179 char version_string[VERSION_STRING_LEN + 1];
180 int version_string_len = 0;
181
182 - if (read_file("/proc/version_signature", version_string, VERSION_STRING_LEN)) {
183 - if (read_file("/proc/version", version_string, VERSION_STRING_LEN)) {
182 + if (read_txt_file("/proc/version_signature", version_string, sizeof(version_string))) {
183 + if (read_txt_file("/proc/version", version_string, sizeof(version_string))) {
184 struct utsname uname_buf;
185 if (!uname(&uname_buf)) {
186 netdata_log_info("Cannot check kernel version");
libnetdata/functions_evloop/functions_evloop.h
+7
@@ -138,6 +138,13 @@ static inline void pluginsd_function_progress_to_stdout(const char *transaction,
138 fflush(stdout);
139 }
140
141 +static inline void send_newline_and_flush(pthread_mutex_t *mutex) {
142 + netdata_mutex_lock(mutex);
143 + fprintf(stdout, "\n");
144 + fflush(stdout);
145 + netdata_mutex_unlock(mutex);
146 +}
147 +
148 void functions_evloop_dyncfg_add(struct functions_evloop_globals *wg, const char *id, const char *path,
149 DYNCFG_STATUS status, DYNCFG_TYPE type, DYNCFG_SOURCE_TYPE source_type, const char *source, DYNCFG_CMDS cmds,
150 HTTP_ACCESS view_access, HTTP_ACCESS edit_access,
libnetdata/inlined.h
+38 -5
@@ -469,7 +469,7 @@ static inline bool sanitize_command_argument_string(char *dst, const char *src,
469 return true;
470 }
471
472 -static inline int read_file(const char *filename, char *buffer, size_t size) {
472 +static inline int read_txt_file(const char *filename, char *buffer, size_t size) {
473 if(unlikely(!size)) return 3;
474
475 int fd = open(filename, O_RDONLY, 0666);
@@ -478,7 +478,7 @@ static inline int read_file(const char *filename, char *buffer, size_t size) {
478 return 1;
479 }
480
481 - ssize_t r = read(fd, buffer, size);
481 + ssize_t r = read(fd, buffer, size - 1); // leave space of the final zero
482 if(unlikely(r == -1)) {
483 buffer[0] = '\0';
484 close(fd);
@@ -490,10 +490,43 @@ static inline int read_file(const char *filename, char *buffer, size_t size) {
490 return 0;
491 }
492
493 +static inline int read_proc_cmdline(const char *filename, char *buffer, size_t size) {
494 + if (unlikely(!size)) return 3;
495 +
496 + int fd = open(filename, O_RDONLY, 0666);
497 + if (unlikely(fd == -1)) {
498 + buffer[0] = '\0';
499 + return 1;
500 + }
501 +
502 + ssize_t r = read(fd, buffer, size - 1); // Leave space for final null character
503 + if (unlikely(r == -1)) {
504 + buffer[0] = '\0';
505 + close(fd);
506 + return 2;
507 + }
508 +
509 + if (r > 0) {
510 + // Replace null characters with spaces, except for the last one
511 + for (ssize_t i = 0; i < r - 1; i++) {
512 + if (buffer[i] == '\0') {
513 + buffer[i] = ' ';
514 + }
515 + }
516 + buffer[r] = '\0'; // Null-terminate the string
517 + }
518 + else {
519 + buffer[0] = '\0'; // Empty cmdline
520 + }
521 +
522 + close(fd);
523 + return 0;
524 +}
525 +
526 static inline int read_single_number_file(const char *filename, unsigned long long *result) {
527 char buffer[30 + 1];
528
496 - int ret = read_file(filename, buffer, 30);
529 + int ret = read_txt_file(filename, buffer, sizeof(buffer));
530 if(unlikely(ret)) {
531 *result = 0;
532 return ret;
@@ -507,7 +540,7 @@ static inline int read_single_number_file(const char *filename, unsigned long lo
540 static inline int read_single_signed_number_file(const char *filename, long long *result) {
541 char buffer[30 + 1];
542
510 - int ret = read_file(filename, buffer, 30);
543 + int ret = read_txt_file(filename, buffer, sizeof(buffer));
544 if(unlikely(ret)) {
545 *result = 0;
546 return ret;
@@ -521,7 +554,7 @@ static inline int read_single_signed_number_file(const char *filename, long long
554 static inline int read_single_base64_or_hex_number_file(const char *filename, unsigned long long *result) {
555 char buffer[30 + 1];
556
524 - int ret = read_file(filename, buffer, 30);
557 + int ret = read_txt_file(filename, buffer, sizeof(buffer));
558 if(unlikely(ret)) {
559 *result = 0;
560 return ret;
libnetdata/os.c
+3 -3
@@ -151,11 +151,11 @@ unsigned long read_cpuset_cpus(const char *filename, long system_cpus) {
151 static size_t buf_size = 0;
152
153 if(!buf) {
154 - buf_size = 100U + 6 * system_cpus; // taken from kernel/cgroup/cpuset.c
155 - buf = mallocz(buf_size + 1);
154 + buf_size = 100U + 6 * system_cpus + 1; // taken from kernel/cgroup/cpuset.c
155 + buf = mallocz(buf_size);
156 }
157
158 - int ret = read_file(filename, buf, buf_size);
158 + int ret = read_txt_file(filename, buf, buf_size);
159
160 if(!ret) {
161 char *s = buf;