@cryptotaxi247 / netdata-1 / commits / e37b12901

Local sockets for network namespaces (#16867)

* local-sockets now reads proc before reading sockets, do detect also the namespaces available * new version of local-sockets that is namespace aware

Costa Tsaousis committed Jan 29, 2024 at 19:20 UTC e37b12901539579aaaabc4e45fe5b0b306db1934
2 files changed +524 -140
collectors/plugins.d/local-sockets.h
+511 -136
@@ -5,21 +5,52 @@
5
6 #include "libnetdata/libnetdata.h"
7
8 +// --------------------------------------------------------------------------------------------------------------------
9 +// hashtable for keeping the namespaces
10 +// key and value is the namespace inode
11 +
12 +#define SIMPLE_HASHTABLE_VALUE_TYPE uint64_t
13 +#define SIMPLE_HASHTABLE_NAME _NET_NS
14 +#include "libnetdata/simple_hashtable.h"
15 +
16 +// --------------------------------------------------------------------------------------------------------------------
17 +// hashtable for keeping the sockets of PIDs
18 +// key is the inode
19 +
20 +struct pid_socket;
21 +#define SIMPLE_HASHTABLE_VALUE_TYPE struct pid_socket
22 +#define SIMPLE_HASHTABLE_NAME _PID_SOCKET
23 +#include "libnetdata/simple_hashtable.h"
24 +
25 +// --------------------------------------------------------------------------------------------------------------------
26 +// hashtable for keeping all the sockets
27 +// key is the inode
28 +
29 struct local_socket;
30 #define SIMPLE_HASHTABLE_VALUE_TYPE struct local_socket
31 #define SIMPLE_HASHTABLE_NAME _LOCAL_SOCKET
32 #include "libnetdata/simple_hashtable.h"
33
34 +// --------------------------------------------------------------------------------------------------------------------
35 +// hashtable for keeping all local IPs
36 +// key is XXH3_64bits hash of the IP
37 +
38 union ipv46;
39 #define SIMPLE_HASHTABLE_VALUE_TYPE union ipv46
40 #define SIMPLE_HASHTABLE_NAME _LOCAL_IP
41 #include "libnetdata/simple_hashtable.h"
42
43 +// --------------------------------------------------------------------------------------------------------------------
44 +// hashtable for keeping all listening ports
45 +// key is XXH3_64bits hash of the family, protocol, port number, namespace
46 +
47 struct local_port;
48 #define SIMPLE_HASHTABLE_VALUE_TYPE struct local_port
20 -#define SIMPLE_HASHTABLE_NAME _LOCAL_PORT
49 +#define SIMPLE_HASHTABLE_NAME _LISTENING_PORT
50 #include "libnetdata/simple_hashtable.h"
51
52 +// --------------------------------------------------------------------------------------------------------------------
53 +
54 struct local_socket_state;
55 typedef void (*local_sockets_cb_t)(struct local_socket_state *state, struct local_socket *n, void *data);
56
@@ -36,21 +67,30 @@ typedef struct local_socket_state {
67 bool pid;
68 bool cmdline;
69 bool comm;
70 + bool namespaces;
71 size_t max_errors;
72
73 local_sockets_cb_t cb;
74 void *data;
75 +
76 + const char *host_prefix;
77 } config;
78
79 struct {
80 size_t pid_fds_processed;
47 - size_t pid_fds_failed;
81 + size_t pid_fds_opendir_failed;
82 + size_t pid_fds_readlink_failed;
83 + size_t pid_fds_parse_failed;
84 size_t errors_encountered;
85 } stats;
86
87 + uint64_t proc_self_net_ns_inode;
88 +
89 + SIMPLE_HASHTABLE_NET_NS ns_hashtable;
90 + SIMPLE_HASHTABLE_PID_SOCKET pid_sockets_hashtable;
91 SIMPLE_HASHTABLE_LOCAL_SOCKET sockets_hashtable;
92 SIMPLE_HASHTABLE_LOCAL_IP local_ips_hashtable;
53 - SIMPLE_HASHTABLE_LOCAL_PORT listening_ports_hashtable;
93 + SIMPLE_HASHTABLE_LISTENING_PORT listening_ports_hashtable;
94 } LS_STATE;
95
96 // --------------------------------------------------------------------------------------------------------------------
@@ -66,6 +106,14 @@ typedef enum __attribute__((packed)) {
106 #define TASK_COMM_LEN 16
107 #endif
108
109 +struct pid_socket {
110 + uint64_t inode;
111 + pid_t pid;
112 + uint64_t net_ns_inode;
113 + char *cmdline;
114 + char comm[TASK_COMM_LEN];
115 +};
116 +
117 union ipv46 {
118 uint32_t ipv4;
119 struct in6_addr ipv6;
@@ -75,6 +123,7 @@ struct local_port {
123 uint16_t protocol;
124 uint16_t family;
125 uint16_t port;
126 + uint64_t net_ns_inode;
127 };
128
129 struct socket_endpoint {
@@ -93,7 +142,8 @@ static inline void ipv6_to_in6_addr(const char *ipv6_str, struct in6_addr *d) {
142 }
143
144 typedef struct local_socket {
96 - unsigned int inode;
145 + uint64_t inode;
146 + uint64_t net_ns_inode;
147
148 uint16_t protocol;
149 uint16_t family;
@@ -116,9 +166,14 @@ typedef struct local_socket {
166
167 // --------------------------------------------------------------------------------------------------------------------
168
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)
169 +static inline void local_sockets_log(LS_STATE *ls, const char *format, ...) __attribute__ ((format(__printf__, 2, 3)));
170 +static inline void local_sockets_log(LS_STATE *ls, const char *format, ...) {
171 + if(++ls->stats.errors_encountered == ls->config.max_errors) {
172 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "LOCAL-LISTENERS: max number of logs reached. Not logging anymore");
173 + return;
174 + }
175 +
176 + if(ls->stats.errors_encountered > ls->config.max_errors)
177 return;
178
179 char buf[16384];
@@ -132,9 +187,10 @@ static inline void ll_log(LS_STATE *ls, const char *format, ...) {
187
188 // --------------------------------------------------------------------------------------------------------------------
189
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];
190 +static void local_sockets_foreach_local_socket_call_cb(LS_STATE *ls) {
191 + for(SIMPLE_HASHTABLE_SLOT_LOCAL_SOCKET *sl = simple_hashtable_first_read_only_LOCAL_SOCKET(&ls->sockets_hashtable);
192 + sl;
193 + sl = simple_hashtable_next_read_only_LOCAL_SOCKET(&ls->sockets_hashtable, sl)) {
194 LOCAL_SOCKET *n = SIMPLE_HASHTABLE_SLOT_DATA(sl);
195 if(!n) continue;
196
@@ -147,15 +203,12 @@ static void foreach_local_socket_call_cb_and_cleanup(LS_STATE *ls) {
203 if (ls->config.cb)
204 ls->config.cb(ls, n, ls->config.data);
205 }
150 -
151 - freez(n->cmdline);
152 - freez(n);
206 }
207 }
208
209 // --------------------------------------------------------------------------------------------------------------------
210
158 -static inline void fix_cmdline(char* str) {
211 +static inline void local_sockets_fix_cmdline(char* str) {
212 char *s = str;
213
214 // map invalid characters to underscores
@@ -165,113 +218,146 @@ static inline void fix_cmdline(char* str) {
218 }
219 }
220
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;
221 +// ----------------------------------------------------------------------------
222
173 - n->pid = pid;
223 +static inline bool
224 +local_sockets_read_proc_inode_link(LS_STATE *ls, const char *filename, uint64_t *inode, const char *type) {
225 + char link_target[FILENAME_MAX + 1];
226
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);
227 + *inode = 0;
228
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);
229 + ssize_t len = readlink(filename, link_target, sizeof(link_target) - 1);
230 + if (len == -1) {
231 + local_sockets_log(ls, "cannot read '%s' link '%s'", type, filename);
232
186 - char *s = trim(cmdline);
233 + ls->stats.pid_fds_readlink_failed++;
234 + return false;
235 + }
236 + link_target[len] = '\0';
237
188 - if(s) {
189 - // replace it
190 - freez(n->cmdline);
191 - n->cmdline = strdupz(s);
192 - }
193 - }
194 - }
238 + len = strlen(type);
239 + if(strncmp(link_target, type, len) == 0 && link_target[len] == ':' && link_target[len + 1] == '[' && isdigit(link_target[len + 2])) {
240 + *inode = strtoull(&link_target[len + 2], NULL, 10);
241 + // ll_log(ls, "read link of type '%s' '%s' from '%s', inode = %"PRIu64, type, link_target, filename, *inode);
242 + return true;
243 + }
244 + else {
245 + // ll_log(ls, "cannot read '%s' link '%s' from '%s'", type, link_target, filename);
246 + ls->stats.pid_fds_processed++;
247 + return false;
248 + }
249 +}
250
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 - }
251 +static inline bool local_sockets_is_path_a_pid(const char *s) {
252 + if(!s || !*s) return false;
253 +
254 + while(*s) {
255 + if(!isdigit(*s++))
256 + return false;
257 }
258
259 return true;
260 }
261
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];
262 +static inline bool local_sockets_find_all_sockets_in_proc(LS_STATE *ls, const char *proc_filename) {
263 + DIR *proc_dir;
264 + struct dirent *proc_entry;
265 + char filename[FILENAME_MAX + 1];
266 + char comm[TASK_COMM_LEN];
267 + char cmdline[8192];
268 + const char *cmdline_trimmed;
269 + uint64_t net_ns_inode;
270
271 proc_dir = opendir(proc_filename);
272 if (proc_dir == NULL) {
221 - ll_log(ls, "cannot opendir() '%s'", proc_filename);
222 - ls->stats.pid_fds_failed++;
273 + local_sockets_log(ls, "cannot opendir() '%s'", proc_filename);
274 + ls->stats.pid_fds_readlink_failed++;
275 return false;
276 }
277
278 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 - }
279 + if(proc_entry->d_type != DT_DIR)
280 + continue;
281
236 - if (!is_pid)
282 + if(!strcmp(proc_entry->d_name, ".") || !strcmp(proc_entry->d_name, ".."))
283 continue;
284
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);
285 + if(!local_sockets_is_path_a_pid(proc_entry->d_name))
286 + continue;
287
242 - fd_dir = opendir(path_buffer);
288 + // Build the path to the fd directory of the process
289 + snprintfz(filename, FILENAME_MAX, "%s/%s/fd/", proc_filename, proc_entry->d_name);
290 + DIR *fd_dir = opendir(filename);
291 if (fd_dir == NULL) {
244 - ll_log(ls, "cannot opendir() '%s'", path_buffer);
292 + local_sockets_log(ls, "cannot opendir() '%s'", filename);
293 + ls->stats.pid_fds_opendir_failed++;
294 + continue;
295 + }
296
246 - ls->stats.pid_fds_failed++;
297 + comm[0] = '\0';
298 + cmdline[0] = '\0';
299 + cmdline_trimmed = NULL;
300 + pid_t pid = (pid_t)strtoul(proc_entry->d_name, NULL, 10);
301 + if(!pid) {
302 + local_sockets_log(ls, "cannot parse pid of '%s'", proc_entry->d_name);
303 continue;
304 }
305 + net_ns_inode = 0;
306
307 + struct dirent *fd_entry;
308 while ((fd_entry = readdir(fd_dir)) != NULL) {
251 - if(!strcmp(fd_entry->d_name, ".") || !strcmp(fd_entry->d_name, ".."))
309 + if(fd_entry->d_type != DT_LNK)
310 continue;
311
254 - char link_path[FILENAME_MAX + 1];
255 - char link_target[FILENAME_MAX + 1];
256 - unsigned inode;
312 + snprintfz(filename, sizeof(filename), "%s/%s/fd/%s", proc_filename, proc_entry->d_name, fd_entry->d_name);
313 + uint64_t inode = 0;
314 + if(!local_sockets_read_proc_inode_link(ls, filename, &inode, "socket"))
315 + continue;
316
258 - // Build the path to the file descriptor link
259 - snprintfz(link_path, FILENAME_MAX, "%s/%s", path_buffer, fd_entry->d_name);
317 + SIMPLE_HASHTABLE_SLOT_PID_SOCKET *sl = simple_hashtable_get_slot_PID_SOCKET(&ls->pid_sockets_hashtable, inode, &inode, true);
318 + struct pid_socket *ps = SIMPLE_HASHTABLE_SLOT_DATA(sl);
319 + if(!ps || (ps->pid == 1 && pid != 1)) {
320 + if(!comm[0] && ls->config.comm) {
321 + snprintfz(filename, sizeof(filename), "%s/%s/comm", proc_filename, proc_entry->d_name);
322 + if (read_txt_file(filename, comm, sizeof(comm)))
323 + local_sockets_log(ls, "cannot open file: %s\n", filename);
324 + else {
325 + size_t clen = strlen(comm);
326 + if(comm[clen - 1] == '\n')
327 + comm[clen - 1] = '\0';
328 + }
329 + }
330 + if(!cmdline[0] && ls->config.cmdline) {
331 + snprintfz(filename, sizeof(filename), "%s/%s/cmdline", proc_filename, proc_entry->d_name);
332 + if (read_proc_cmdline(filename, cmdline, sizeof(cmdline)))
333 + local_sockets_log(ls, "cannot open file: %s\n", filename);
334 + else {
335 + local_sockets_fix_cmdline(cmdline);
336 + cmdline_trimmed = trim(cmdline);
337 + }
338 + }
339 + if(!net_ns_inode && ls->config.namespaces) {
340 + snprintfz(filename, sizeof(filename), "%s/%s/ns/net", proc_filename, proc_entry->d_name);
341 + if(local_sockets_read_proc_inode_link(ls, filename, &net_ns_inode, "net")) {
342 + SIMPLE_HASHTABLE_SLOT_NET_NS *sl_ns = simple_hashtable_get_slot_NET_NS(&ls->ns_hashtable, net_ns_inode, (uint64_t *)net_ns_inode, true);
343 + simple_hashtable_set_slot_NET_NS(&ls->ns_hashtable, sl_ns, net_ns_inode, (uint64_t *)net_ns_inode);
344 + }
345 + }
346
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);
347 + if(!ps)
348 + ps = callocz(1, sizeof(*ps));
349
265 - ls->stats.pid_fds_failed++;
266 - continue;
267 - }
268 - link_target[len] = '\0';
350 + ps->inode = inode;
351 + ps->pid = pid;
352 + ps->net_ns_inode = net_ns_inode;
353 + strncpyz(ps->comm, comm, sizeof(ps->comm) - 1);
354
270 - ls->stats.pid_fds_processed++;
355 + if(ps->cmdline)
356 + freez(ps->cmdline);
357
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));
358 + ps->cmdline = cmdline_trimmed ? strdupz(cmdline_trimmed) : NULL;
359 + simple_hashtable_set_slot_PID_SOCKET(&ls->pid_sockets_hashtable, sl, inode, ps);
360 + }
361 }
362
363 closedir(fd_dir);
@@ -283,13 +369,13 @@ static inline bool find_all_sockets_in_proc(LS_STATE *ls, const char *proc_filen
369
370 // ----------------------------------------------------------------------------
371
286 -static bool is_ipv4_mapped_ipv6_address(const struct in6_addr *addr) {
372 +static bool local_sockets_is_ipv4_mapped_ipv6_address(const struct in6_addr *addr) {
373 // An IPv4-mapped IPv6 address starts with 80 bits of zeros followed by 16 bits of ones
374 static const unsigned char ipv4_mapped_prefix[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF };
375 return memcmp(addr->s6_addr, ipv4_mapped_prefix, 12) == 0;
376 }
377
292 -static bool is_loopback_address(const void *ip, uint16_t family) {
378 +static bool local_sockets_is_loopback_address(const void *ip, uint16_t family) {
379 if (family == AF_INET) {
380 // For IPv4, loopback addresses are in the 127.0.0.0/8 range
381 const uint32_t addr = ntohl(*((const uint32_t *)ip)); // Convert to host byte order for comparison
@@ -297,7 +383,7 @@ static bool is_loopback_address(const void *ip, uint16_t family) {
383 } else if (family == AF_INET6) {
384 // Check if the address is an IPv4-mapped IPv6 address
385 const struct in6_addr *ipv6_addr = (const struct in6_addr *)ip;
300 - if (is_ipv4_mapped_ipv6_address(ipv6_addr)) {
386 + if (local_sockets_is_ipv4_mapped_ipv6_address(ipv6_addr)) {
387 // Extract the last 32 bits (IPv4 address) and check if it's in the 127.0.0.0/8 range
388 const uint32_t ipv4_addr = ntohl(*((const uint32_t *)(ipv6_addr->s6_addr + 12)));
389 return (ipv4_addr >> 24) == 127;
@@ -311,7 +397,7 @@ static bool is_loopback_address(const void *ip, uint16_t family) {
397 return false;
398 }
399
314 -static bool is_zero_address(const void *ip, uint16_t family) {
400 +static bool local_sockets_is_zero_address(const void *ip, uint16_t family) {
401 if (family == AF_INET) {
402 // For IPv4, check if the address is not 0.0.0.0
403 const uint32_t zero_ipv4 = 0; // Zero address in network byte order
@@ -325,7 +411,21 @@ static bool is_zero_address(const void *ip, uint16_t family) {
411 return false;
412 }
413
328 -static inline bool read_proc_net_x(LS_STATE *ls, const char *filename, uint16_t family, uint16_t protocol) {
414 +static inline void local_sockets_index_listening_port(LS_STATE *ls, LOCAL_SOCKET *n) {
415 + if(n->direction & SOCKET_DIRECTION_LISTEN) {
416 + // for the listening sockets, keep a hashtable with all the local ports
417 + // so that we will be able to detect INBOUND sockets
418 +
419 + SIMPLE_HASHTABLE_SLOT_LISTENING_PORT *sl_port =
420 + simple_hashtable_get_slot_LISTENING_PORT(&ls->listening_ports_hashtable, n->local_port_hash, &n->local_port_key, true);
421 +
422 + struct local_port *port = SIMPLE_HASHTABLE_SLOT_DATA(sl_port);
423 + if(!port)
424 + simple_hashtable_set_slot_LISTENING_PORT(&ls->listening_ports_hashtable, sl_port, n->local_port_hash, &n->local_port_key);
425 + }
426 +}
427 +
428 +static inline bool local_sockets_read_proc_net_x(LS_STATE *ls, const char *filename, uint16_t family, uint16_t protocol) {
429 if(family != AF_INET && family != AF_INET6)
430 return false;
431
@@ -346,24 +446,25 @@ static inline bool read_proc_net_x(LS_STATE *ls, const char *filename, uint16_t
446 if(counter++ == 0) continue; // skip the first line
447
448 if(read < min_line_length) {
349 - ll_log(ls, "too small line No %zu of filename '%s': %s", counter, filename, line);
449 + local_sockets_log(ls, "too small line No %zu of filename '%s': %s", counter, filename, line);
450 continue;
451 }
452
353 - unsigned int local_address, local_port, state, remote_address, remote_port, inode = 0;
453 + unsigned int local_address, local_port, state, remote_address, remote_port;
454 + uint64_t inode = 0;
455 char local_address6[33], remote_address6[33];
456
457 if(family == AF_INET) {
357 - if (sscanf(line, "%*d: %X:%X %X:%X %X %*X:%*X %*X:%*X %*X %*d %*d %u",
458 + if (sscanf(line, "%*d: %X:%X %X:%X %X %*X:%*X %*X:%*X %*X %*d %*d %"PRIu64,
459 &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);
460 + local_sockets_log(ls, "cannot parse ipv4 line No %zu of filename '%s': %s", counter, filename, line);
461 continue;
462 }
463 }
464 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",
465 + if(sscanf(line, "%*d: %32[0-9A-Fa-f]:%X %32[0-9A-Fa-f]:%X %X %*X:%*X %*X:%*X %*X %*d %*d %"PRIu64,
466 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);
467 + local_sockets_log(ls, "cannot parse ipv6 line No %zu of filename '%s': %s", counter, filename, line);
468 continue;
469 }
470 }
@@ -372,7 +473,13 @@ static inline bool read_proc_net_x(LS_STATE *ls, const char *filename, uint16_t
473 SIMPLE_HASHTABLE_SLOT_LOCAL_SOCKET *sl = simple_hashtable_get_slot_LOCAL_SOCKET(&ls->sockets_hashtable, inode, &inode, true);
474 LOCAL_SOCKET *n = SIMPLE_HASHTABLE_SLOT_DATA(sl);
475 if(n) {
375 - ll_log(ls, "inode %u given on line %zu of filename '%s', already exists in hashtable - ignoring duplicate", inode, counter, filename);
476 + local_sockets_log(
477 + ls,
478 + "inode %" PRIu64
479 + " given on line %zu of filename '%s', already exists in hashtable - ignoring duplicate",
480 + inode,
481 + counter,
482 + filename);
483 continue;
484 }
485
@@ -380,6 +487,8 @@ static inline bool read_proc_net_x(LS_STATE *ls, const char *filename, uint16_t
487
488 n = (LOCAL_SOCKET *)callocz(1, sizeof(LOCAL_SOCKET));
489
490 + // --- initialize it ------------------------------------------------------------------------------------------
491 +
492 if(family == AF_INET) {
493 n->local.ip.ipv4 = local_address;
494 n->remote.ip.ipv4 = remote_address;
@@ -401,14 +510,29 @@ static inline bool read_proc_net_x(LS_STATE *ls, const char *filename, uint16_t
510 n->local_port_key.port = n->local.port;
511 n->local_port_key.family = n->family;
512 n->local_port_key.protocol = n->protocol;
513 + n->local_port_key.net_ns_inode = ls->proc_self_net_ns_inode;
514
515 n->local_ip_hash = XXH3_64bits(&n->local.ip, sizeof(n->local.ip));
516 n->remote_ip_hash = XXH3_64bits(&n->remote.ip, sizeof(n->remote.ip));
517 n->local_port_hash = XXH3_64bits(&n->local_port_key, sizeof(n->local_port_key));
518
519 + // --- look up a pid for it -----------------------------------------------------------------------------------
520 +
521 + SIMPLE_HASHTABLE_SLOT_PID_SOCKET *sl_pid = simple_hashtable_get_slot_PID_SOCKET(&ls->pid_sockets_hashtable, inode, &inode, false);
522 + struct pid_socket *ps = SIMPLE_HASHTABLE_SLOT_DATA(sl_pid);
523 + if(ps) {
524 + n->net_ns_inode = ps->net_ns_inode;
525 + n->pid = ps->pid;
526 + if(ps->cmdline)
527 + n->cmdline = strdupz(ps->cmdline);
528 + strncpyz(n->comm, ps->comm, sizeof(n->comm) - 1);
529 + }
530 +
531 + // --- index it -----------------------------------------------------------------------------------------------
532 +
533 simple_hashtable_set_slot_LOCAL_SOCKET(&ls->sockets_hashtable, sl, inode, n);
534
411 - if(!is_zero_address(&n->local.ip, n->family)) {
535 + if(!local_sockets_is_zero_address(&n->local.ip, n->family)) {
536 // put all the local IPs into the local_ips hashtable
537 // so, we learn all local IPs the system has
538
@@ -420,12 +544,18 @@ static inline bool read_proc_net_x(LS_STATE *ls, const char *filename, uint16_t
544 simple_hashtable_set_slot_LOCAL_IP(&ls->local_ips_hashtable, sl_ip, n->local_ip_hash, &n->local.ip);
545 }
546
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)) {
547 + // --- 1st phase for direction detection ----------------------------------------------------------------------
548 +
549 + if((n->protocol == IPPROTO_TCP && n->state == TCP_LISTEN) ||
550 + local_sockets_is_zero_address(&n->local.ip, n->family) ||
551 + local_sockets_is_zero_address(&n->remote.ip, n->family)) {
552 // the socket is either in a TCP LISTEN, or
553 // the remote address is zero
554 n->direction |= SOCKET_DIRECTION_LISTEN;
555 }
428 - else if(is_loopback_address(&n->local.ip, n->family) || is_loopback_address(&n->remote.ip, n->family)) {
556 + else if(
557 + local_sockets_is_loopback_address(&n->local.ip, n->family) ||
558 + local_sockets_is_loopback_address(&n->remote.ip, n->family)) {
559 // the local IP address is loopback
560 n->direction |= SOCKET_DIRECTION_LOCAL;
561 }
@@ -435,17 +565,9 @@ static inline bool read_proc_net_x(LS_STATE *ls, const char *filename, uint16_t
565 n->direction |= SOCKET_DIRECTION_INBOUND | SOCKET_DIRECTION_OUTBOUND;
566 }
567
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
568 + // --- index it in LISTENING_PORT -----------------------------------------------------------------------------
569
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 - }
570 + local_sockets_index_listening_port(ls, n);
571 }
572
573 fclose(fp);
@@ -459,8 +581,9 @@ static inline bool read_proc_net_x(LS_STATE *ls, const char *filename, uint16_t
581 // --------------------------------------------------------------------------------------------------------------------
582
583 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];
584 + for(SIMPLE_HASHTABLE_SLOT_LOCAL_SOCKET *sl = simple_hashtable_first_read_only_LOCAL_SOCKET(&ls->sockets_hashtable);
585 + sl ;
586 + sl = simple_hashtable_next_read_only_LOCAL_SOCKET(&ls->sockets_hashtable, sl)) {
587 LOCAL_SOCKET *n = SIMPLE_HASHTABLE_SLOT_DATA(sl);
588 if (!n) continue;
589
@@ -484,8 +607,8 @@ static inline void local_sockets_detect_directions(LS_STATE *ls) {
607
608 // check if the local port is one of our listening ports
609 {
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);
610 + SIMPLE_HASHTABLE_SLOT_LISTENING_PORT *sl_port =
611 + simple_hashtable_get_slot_LISTENING_PORT(&ls->listening_ports_hashtable, n->local_port_hash, &n->local_port_key, false);
612
613 struct local_port *port = SIMPLE_HASHTABLE_SLOT_DATA(sl_port); // do not reference this pointer - is invalid
614 if(port) {
@@ -500,49 +623,301 @@ static inline void local_sockets_detect_directions(LS_STATE *ls) {
623
624 // --------------------------------------------------------------------------------------------------------------------
625
503 -static inline void local_sockets_process(LS_STATE *ls) {
626 +static inline void local_sockets_init(LS_STATE *ls) {
627 + simple_hashtable_init_NET_NS(&ls->ns_hashtable, 1024);
628 + simple_hashtable_init_PID_SOCKET(&ls->pid_sockets_hashtable, 65535);
629 + simple_hashtable_init_LOCAL_SOCKET(&ls->sockets_hashtable, 65535);
630 + simple_hashtable_init_LOCAL_IP(&ls->local_ips_hashtable, 4096);
631 + simple_hashtable_init_LISTENING_PORT(&ls->listening_ports_hashtable, 4096);
632 +}
633 +
634 +static inline void local_sockets_cleanup(LS_STATE *ls) {
635 + // free the sockets hashtable data
636 + for(SIMPLE_HASHTABLE_SLOT_LOCAL_SOCKET *sl = simple_hashtable_first_read_only_LOCAL_SOCKET(&ls->sockets_hashtable);
637 + sl;
638 + sl = simple_hashtable_next_read_only_LOCAL_SOCKET(&ls->sockets_hashtable, sl)) {
639 + LOCAL_SOCKET *n = SIMPLE_HASHTABLE_SLOT_DATA(sl);
640 + if(!n) continue;
641 +
642 + freez(n->cmdline);
643 + freez(n);
644 + }
645 +
646 + // free the pid_socket hashtable data
647 + for(SIMPLE_HASHTABLE_SLOT_PID_SOCKET *sl = simple_hashtable_first_read_only_PID_SOCKET(&ls->pid_sockets_hashtable);
648 + sl;
649 + sl = simple_hashtable_next_read_only_PID_SOCKET(&ls->pid_sockets_hashtable, sl)) {
650 + struct pid_socket *ps = SIMPLE_HASHTABLE_SLOT_DATA(sl);
651 + if(!ps) continue;
652 +
653 + freez(ps->cmdline);
654 + freez(ps);
655 + }
656 +
657 + // free the hashtable
658 + simple_hashtable_destroy_NET_NS(&ls->ns_hashtable);
659 + simple_hashtable_destroy_PID_SOCKET(&ls->pid_sockets_hashtable);
660 + simple_hashtable_destroy_LISTENING_PORT(&ls->listening_ports_hashtable);
661 + simple_hashtable_destroy_LOCAL_IP(&ls->local_ips_hashtable);
662 + simple_hashtable_destroy_LOCAL_SOCKET(&ls->sockets_hashtable);
663 +}
664 +
665 +// --------------------------------------------------------------------------------------------------------------------
666 +
667 +static inline void local_sockets_read_sockets_from_proc(LS_STATE *ls) {
668 char path[FILENAME_MAX + 1];
669
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);
670 + if(ls->config.namespaces) {
671 + snprintfz(path, sizeof(path), "%s/proc/self/ns/net", ls->config.host_prefix);
672 + local_sockets_read_proc_inode_link(ls, path, &ls->proc_self_net_ns_inode, "net");
673 + }
674 +
675 + if(ls->config.cmdline || ls->config.comm || ls->config.pid || ls->config.namespaces) {
676 + snprintfz(path, sizeof(path), "%s/proc", ls->config.host_prefix);
677 + local_sockets_find_all_sockets_in_proc(ls, path);
678 + }
679
680 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);
681 + snprintfz(path, sizeof(path), "%s/proc/net/tcp", ls->config.host_prefix);
682 + local_sockets_read_proc_net_x(ls, path, AF_INET, IPPROTO_TCP);
683 }
684
685 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);
686 + snprintfz(path, sizeof(path), "%s/proc/net/udp", ls->config.host_prefix);
687 + local_sockets_read_proc_net_x(ls, path, AF_INET, IPPROTO_UDP);
688 }
689
690 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);
691 + snprintfz(path, sizeof(path), "%s/proc/net/tcp6", ls->config.host_prefix);
692 + local_sockets_read_proc_net_x(ls, path, AF_INET6, IPPROTO_TCP);
693 }
694
695 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);
696 + snprintfz(path, sizeof(path), "%s/proc/net/udp6", ls->config.host_prefix);
697 + local_sockets_read_proc_net_x(ls, path, AF_INET6, IPPROTO_UDP);
698 + }
699 +}
700 +
701 +// --------------------------------------------------------------------------------------------------------------------
702 +
703 +struct local_sockets_child_work {
704 + int fd;
705 + uint64_t net_ns_inode;
706 +};
707 +
708 +static inline void local_sockets_send_to_parent(struct local_socket_state *ls __maybe_unused, struct local_socket *n, void *data) {
709 + struct local_sockets_child_work *cw = data;
710 + int fd = cw->fd;
711 +
712 + if(n->net_ns_inode != cw->net_ns_inode)
713 + return;
714 +
715 + // local_sockets_log(ls, "child is sending inode %"PRIu64" of namespace %"PRIu64, n->inode, n->net_ns_inode);
716 +
717 + if(write(fd, n, sizeof(*n)) != sizeof(*n))
718 + local_sockets_log(ls, "failed to write local socket to pipe");
719 +
720 + size_t len = n->cmdline ? strlen(n->cmdline) + 1 : 0;
721 + if(write(fd, &len, sizeof(len)) != sizeof(len))
722 + local_sockets_log(ls, "failed to write cmdline length to pipe");
723 +
724 + if(len)
725 + if(write(fd, n->cmdline, len) != (ssize_t)len)
726 + local_sockets_log(ls, "failed to write cmdline to pipe");
727 +}
728 +
729 +static inline bool local_sockets_get_namespace_sockets(LS_STATE *ls, struct pid_socket *ps, pid_t *pid) {
730 + char filename[1024];
731 + snprintfz(filename, sizeof(filename), "%s/proc/%d/ns/net", ls->config.host_prefix, ps->pid);
732 +
733 + // verify the pid is in the target namespace
734 + struct stat statbuf;
735 + if (stat(filename, &statbuf) == -1 || statbuf.st_ino != ps->net_ns_inode) {
736 + local_sockets_log(ls, "pid %d is not in the wanted network namespace", ps->pid);
737 + return false;
738 + }
739 +
740 + int fd = open(filename, O_RDONLY);
741 + if(!fd) {
742 + local_sockets_log(ls, "cannot open file '%s'", filename);
743 + return false;
744 + }
745 +
746 + int pipefd[2];
747 + if (pipe(pipefd) != 0) {
748 + local_sockets_log(ls, "cannot create pipe");
749 + return false;
750 + }
751 +
752 + *pid = fork();
753 + if (*pid == 0) {
754 + // Child process
755 + close(pipefd[0]);
756 +
757 + // local_sockets_log(ls, "child is here for inode %"PRIu64" and namespace %"PRIu64, ps->inode, ps->net_ns_inode);
758 +
759 + struct local_sockets_child_work cw = {
760 + .net_ns_inode = ps->net_ns_inode,
761 + .fd = pipefd[1],
762 + };
763 +
764 + ls->config.host_prefix = ""; // we need the /proc of the container
765 + ls->config.cb = local_sockets_send_to_parent;
766 + ls->config.data = &cw;
767 + ls->config.cmdline = false; // we have these already
768 + ls->config.comm = false; // we have these already
769 + ls->config.pid = false; // we have these already
770 + ls->config.namespaces = false;
771 + ls->proc_self_net_ns_inode = ps->net_ns_inode;
772 +
773 +
774 + // switch namespace
775 + if (setns(fd, CLONE_NEWNET) == -1) {
776 + local_sockets_log(ls, "failed to switch network namespace at child process");
777 + exit(EXIT_FAILURE);
778 + }
779 +
780 + // read all sockets from /proc
781 + local_sockets_read_sockets_from_proc(ls);
782 +
783 + // send all sockets to parent
784 + local_sockets_foreach_local_socket_call_cb(ls);
785 +
786 + // send the terminating socket
787 + struct local_socket zero = {
788 + .net_ns_inode = ps->net_ns_inode,
789 + };
790 + local_sockets_send_to_parent(ls, &zero, &cw);
791 +
792 + close(pipefd[1]); // Close write end of pipe
793 + exit(EXIT_SUCCESS);
794 + }
795 + // parent
796 +
797 + close(fd);
798 + close(pipefd[1]);
799 +
800 + size_t received = 0;
801 + struct local_socket buf;
802 + while(read(pipefd[0], &buf, sizeof(buf)) == sizeof(buf)) {
803 + size_t len = 0;
804 + if(read(pipefd[0], &len, sizeof(len)) != sizeof(len))
805 + local_sockets_log(ls, "failed to read cmdline length from pipe");
806 +
807 + if(len) {
808 + buf.cmdline = mallocz(len);
809 + if(read(pipefd[0], buf.cmdline, len) != (ssize_t)len)
810 + local_sockets_log(ls, "failed to read cmdline from pipe");
811 + }
812 + else
813 + buf.cmdline = NULL;
814 +
815 + received++;
816 +
817 + struct local_socket zero = {
818 + .net_ns_inode = ps->net_ns_inode,
819 + };
820 + if(memcmp(&buf, &zero, sizeof(buf)) == 0) {
821 + // the terminator
822 + break;
823 + }
824 +
825 + SIMPLE_HASHTABLE_SLOT_LOCAL_SOCKET *sl = simple_hashtable_get_slot_LOCAL_SOCKET(&ls->sockets_hashtable, buf.inode, &buf, true);
826 + LOCAL_SOCKET *n = SIMPLE_HASHTABLE_SLOT_DATA(sl);
827 + if(n) {
828 + if(buf.cmdline)
829 + freez(buf.cmdline);
830 +
831 +// local_sockets_log(ls,
832 +// "ns inode %" PRIu64" (comm: '%s', pid: %u, ns: %"PRIu64") already exists in hashtable (comm: '%s', pid: %u, ns: %"PRIu64") - ignoring duplicate",
833 +// buf.inode, buf.comm, buf.pid, buf.net_ns_inode, n->comm, n->pid, n->net_ns_inode);
834 + continue;
835 + }
836 + else {
837 + n = mallocz(sizeof(*n));
838 + memcpy(n, &buf, sizeof(*n));
839 + simple_hashtable_set_slot_LOCAL_SOCKET(&ls->sockets_hashtable, sl, n->inode, n);
840 +
841 + local_sockets_index_listening_port(ls, n);
842 + }
843 }
844
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);
845 + close(pipefd[0]);
846 +
847 + return received > 0;
848 +}
849 +
850 +static inline void local_socket_waitpid(LS_STATE *ls, pid_t pid) {
851 + if(!pid) return;
852 +
853 + int status;
854 + waitpid(pid, &status, 0);
855 +
856 + if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
857 + local_sockets_log(ls, "Child exited with status %d", WEXITSTATUS(status));
858 + else if (WIFSIGNALED(status))
859 + local_sockets_log(ls, "Child terminated by signal %d", WTERMSIG(status));
860 +}
861 +
862 +static inline void local_sockets_namespaces(LS_STATE *ls) {
863 + pid_t children[5] = { 0 };
864 + size_t last_child = 0;
865 +
866 + for(SIMPLE_HASHTABLE_SLOT_NET_NS *sl = simple_hashtable_first_read_only_NET_NS(&ls->ns_hashtable);
867 + sl;
868 + sl = simple_hashtable_next_read_only_NET_NS(&ls->ns_hashtable, sl)) {
869 + uint64_t inode = (uint64_t)SIMPLE_HASHTABLE_SLOT_DATA(sl);
870 +
871 + if(inode == ls->proc_self_net_ns_inode)
872 + continue;
873 +
874 + // find a pid_socket that has this namespace
875 + for(SIMPLE_HASHTABLE_SLOT_PID_SOCKET *sl_pid = simple_hashtable_first_read_only_PID_SOCKET(&ls->pid_sockets_hashtable) ;
876 + sl_pid ;
877 + sl_pid = simple_hashtable_next_read_only_PID_SOCKET(&ls->pid_sockets_hashtable, sl_pid)) {
878 + struct pid_socket *ps = SIMPLE_HASHTABLE_SLOT_DATA(sl_pid);
879 + if(!ps || ps->net_ns_inode != inode) continue;
880 +
881 + if(++last_child >= 5)
882 + last_child = 0;
883 +
884 + local_socket_waitpid(ls, children[last_child]);
885 + children[last_child] = 0;
886 +
887 + // now we have a pid that has the same namespace inode
888 + if(local_sockets_get_namespace_sockets(ls, ps, &children[last_child]))
889 + break;
890 + }
891 }
892
893 + for(size_t i = 0; i < 5 ;i++)
894 + local_socket_waitpid(ls, children[i]);
895 +}
896 +
897 +// --------------------------------------------------------------------------------------------------------------------
898 +
899 +static inline void local_sockets_process(LS_STATE *ls) {
900 + ls->config.host_prefix = netdata_configured_host_prefix;
901 +
902 + // initialize our hashtables
903 + local_sockets_init(ls);
904 +
905 + // read all sockets from /proc
906 + local_sockets_read_sockets_from_proc(ls);
907 +
908 + // check all socket namespaces
909 + if(ls->config.namespaces)
910 + local_sockets_namespaces(ls);
911 +
912 // detect the directions of the sockets
913 if(ls->config.inbound || ls->config.outbound || ls->config.local)
914 local_sockets_detect_directions(ls);
915
539 - // this will call the callback for each socket and free the memory we use
540 - foreach_local_socket_call_cb_and_cleanup(ls);
916 + // call the callback for each socket
917 + local_sockets_foreach_local_socket_call_cb(ls);
918
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);
919 + // free all memory
920 + local_sockets_cleanup(ls);
921 }
922
923 static inline void ipv6_address_to_txt(struct in6_addr *in6_addr, char *dst) {
collectors/plugins.d/local_listeners.c
+13 -4
@@ -55,7 +55,7 @@ static void print_local_listeners_debug(LS_STATE *ls __maybe_unused, LOCAL_SOCKE
55 ipv6_address_to_txt(&n->remote.ip.ipv6, remote_address);
56 }
57
58 - printf("%s, direction=%s%s%s%s%s pid=%d, state=0x%0x, local=%s[:%u], remote=%s[:%u], comm=%s\n",
58 + printf("%s, direction=%s%s%s%s%s pid=%d, state=0x%0x, ns=%"PRIu64", 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," : "",
@@ -64,6 +64,7 @@ static void print_local_listeners_debug(LS_STATE *ls __maybe_unused, LOCAL_SOCKE
64 (n->direction == 0) ? "NONE," : "",
65 n->pid,
66 n->state,
67 + n->net_ns_inode,
68 local_address, n->local.port,
69 remote_address, n->remote.port,
70 n->comm);
@@ -85,6 +86,7 @@ int main(int argc, char **argv) {
86 .pid = false,
87 .cmdline = true,
88 .comm = false,
89 + .namespaces = true,
90
91 .max_errors = 10,
92
@@ -123,7 +125,7 @@ int main(int argc, char **argv) {
125 "\n"
126 " while:\n"
127 "\n"
126 - " listening, local, inbound, outbound\n"
128 + " listening, local, inbound, outbound, namespaces\n"
129 "\n"
130 " filter the output based on the direction of the sockets.\n"
131 "\n"
@@ -131,7 +133,7 @@ int main(int argc, char **argv) {
133 "\n"
134 " Current options:\n"
135 "\n"
134 - " %s %s %s %s %s %s %s %s\n"
136 + " %s %s %s %s %s %s %s %s %s\n"
137 "\n"
138 " Option 'debug' enables all sources and all directions and provides\n"
139 " a full dump of current sockets.\n"
@@ -180,6 +182,7 @@ int main(int argc, char **argv) {
182 , ls.config.local ? "local" : "no-local"
183 , ls.config.inbound ? "inbound" : "no-inbound"
184 , ls.config.outbound ? "outbound" : "no-outbound"
185 + , ls.config.namespaces ? "namespaces" : "no-namespaces"
186 );
187 exit(1);
188 }
@@ -201,7 +204,9 @@ int main(int argc, char **argv) {
204 ls.config.outbound = true;
205 ls.config.pid = true;
206 ls.config.comm = true;
204 - ls.config.cmdline = false;
207 + ls.config.cmdline = true;
208 + ls.config.namespaces = true;
209 + ls.config.max_errors = SIZE_MAX;
210 ls.config.cb = print_local_listeners_debug;
211 }
212 else if (strcmp("tcp", s) == 0) {
@@ -252,6 +257,10 @@ int main(int argc, char **argv) {
257 ls.config.outbound = positive;
258 // fprintf(stderr, "%s outbound\n", positive ? "enabling" : "disabling");
259 }
260 + else if (strcmp("namespaces", s) == 0 || strcmp("ns", s) == 0) {
261 + ls.config.namespaces = positive;
262 + // fprintf(stderr, "%s namespaces\n", positive ? "enabling" : "disabling");
263 + }
264 else {
265 fprintf(stderr, "Unknown parameter %s\n", s);
266 exit(1);