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;