@cryptotaxi247 / netdata-1 / commits / 2edc0c489

local-sockets: use netlink when libmnl is available (#16893)

* enable libmnl when it is available * Link MNL if it's available * working uid from proc too * do not show sockets from unknown pids or uids * fix comparison * enable again unknown uid/pid * updates to network viewer prototype * apps.plugin minify and remove of cloexec workaround --------- Co-authored-by: vkalintiris <vasilis@netdata.cloud>

Costa Tsaousis committed Feb 1, 2024 at 12:14 UTC 2edc0c4890558bea4108ee65c00c1d0da7cb8a99
7 files changed +487 -178
CMakeLists.txt
+25 -3
@@ -1638,6 +1638,12 @@ target_link_libraries(libnetdata PUBLIC ${OPENSSL_LDFLAGS})
1638 # h2o
1639 target_link_libraries(libnetdata PUBLIC "$<$<BOOL:${ENABLE_H2O}>:h2o>")
1640
1641 +# mnl
1642 +pkg_check_modules(MNL libmnl)
1643 +if(MNL_FOUND)
1644 + set(HAVE_LIBMNL True)
1645 +endif()
1646 +
1647 #
1648 # helper function to build protos
1649 #
@@ -1781,8 +1787,11 @@ if(ENABLE_PLUGIN_FREEIPMI)
1787 endif()
1788
1789 if(ENABLE_PLUGIN_NFACCT)
1790 + if (NOT MNL_FOUND)
1791 + message(FATAL_ERROR "Can not build nfacct.plugin because MNL library could not be found.")
1792 + endif()
1793 +
1794 pkg_check_modules(NFACCT REQUIRED libnetfilter_acct)
1785 - pkg_check_modules(MNL REQUIRED libmnl)
1795
1796 set(NFACCT_PLUGIN_FILES collectors/nfacct.plugin/plugin_nfacct.c)
1797
@@ -1979,7 +1988,13 @@ if(ENABLE_PLUGIN_LOCAL_LISTENERS)
1988 )
1989
1990 add_executable(local-listeners ${LOCAL_LISTENERS_FILES})
1982 - target_link_libraries(local-listeners libnetdata)
1991 +
1992 + target_compile_options(local-listeners PRIVATE
1993 + "$<$<BOOL:${MNL_FOUND}>:${MNL_CFLAGS_OTHER}>")
1994 + target_include_directories(local-listeners PRIVATE
1995 + "$<$<BOOL:${MNL_FOUND}>:${MNL_INCLUDE_DIRS}>")
1996 + target_link_libraries(local-listeners libnetdata
1997 + "$<$<BOOL:${MNL_FOUND}>:${MNL_LIBRARIES}>")
1998
1999 install(TARGETS local-listeners
2000 COMPONENT local_listeners
@@ -1993,7 +2008,14 @@ if(ENABLE_PLUGIN_NETWORK_VIEWER)
2008 )
2009
2010 add_executable(network-viewer.plugin ${NETWORK_VIEWER_FILES})
1996 - target_link_libraries(network-viewer.plugin libnetdata)
2011 +
2012 + target_compile_options(network-viewer.plugin PRIVATE
2013 + "$<$<BOOL:${MNL_FOUND}>:${MNL_CFLAGS_OTHER}>")
2014 + target_include_directories(network-viewer.plugin PRIVATE
2015 + "$<$<BOOL:${MNL_FOUND}>:${MNL_INCLUDE_DIRS}>")
2016 + target_link_libraries(network-viewer.plugin libnetdata
2017 + "$<$<BOOL:${MNL_FOUND}>:${MNL_LIBRARIES}>")
2018 +
2019
2020 install(TARGETS network-viewer.plugin
2021 COMPONENT network_viewer_plugin
collectors/apps.plugin/apps_plugin.c
+1 -2
@@ -4483,7 +4483,7 @@ static void function_processes(const char *transaction, char *function __maybe_u
4483 unsigned int io_divisor = 1024 * RATES_DETAIL;
4484
4485 BUFFER *wb = buffer_create(4096, NULL);
4486 - buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_NEWLINE_ON_ARRAY_ITEMS);
4486 + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
4487 buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
4488 buffer_json_member_add_string(wb, "type", "table");
4489 buffer_json_member_add_time_t(wb, "update_every", update_every);
@@ -5264,7 +5264,6 @@ static bool apps_plugin_exit = false;
5264 int main(int argc, char **argv) {
5265 clocks_init();
5266 nd_log_initialize_for_external_plugins("apps.plugin");
5267 - for_each_open_fd(OPEN_FD_ACTION_CLOSE, OPEN_FD_EXCLUDE_STDIN|OPEN_FD_EXCLUDE_STDOUT|OPEN_FD_EXCLUDE_STDERR);
5267
5268 pagesize = (size_t)sysconf(_SC_PAGESIZE);
5269
collectors/network-viewer.plugin/network-viewer.c
+2 -1
@@ -106,7 +106,7 @@ void network_viewer_function(const char *transaction, char *function __maybe_unu
106 CLEAN_BUFFER *wb = buffer_create(0, NULL);
107 buffer_flush(wb);
108 wb->content_type = CT_APPLICATION_JSON;
109 - buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
109 + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
110
111 buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
112 buffer_json_member_add_string(wb, "type", "table");
@@ -125,6 +125,7 @@ void network_viewer_function(const char *transaction, char *function __maybe_unu
125 .udp4 = true,
126 .udp6 = true,
127 .pid = true,
128 + .uid = true,
129 .cmdline = true,
130 .comm = true,
131 .namespaces = true,
collectors/network-viewer.plugin/viewer.html
+150 -71
@@ -209,83 +209,162 @@
209 return `rgba(${r}, ${g}, ${b}, 0.5)`;
210 }
211
212 + function getRgbColor(hex, opacity = 1) {
213 + if (hex.length !== 7 || hex[0] !== "#") throw new Error("Invalid hex color format");
214 +
215 + // Parse the hex color components (red, green, blue)
216 + const r = parseInt(hex.slice(1, 3), 16);
217 + const g = parseInt(hex.slice(3, 5), 16);
218 + const b = parseInt(hex.slice(5, 7), 16);
219 +
220 + // Ensure opacity is within the valid range (0 to 1)
221 + const validOpacity = Math.min(1, Math.max(0, opacity));
222 +
223 + // Return the RGBA color
224 + return `rgba(${r}, ${g}, ${b}, ${validOpacity})`;
225 + }
226 +
227 function drawInitialChart(svg, data, w, h, borderPadding, theme) {
228 const cw = w / 2;
229 const ch = h / 2;
230
231 document.body.style.backgroundColor = theme.backgroundColor;
232
218 - svg.append('rect')
219 - .attr('x', 0)
220 - .attr('y', 0)
221 - .attr('width', '100%')
222 - .attr('height', borderPadding / 2)
223 - .style('fill', hexToHalfOpacityRGBA(theme.clientColor));
224 -
225 - svg.append('text')
226 - .text('Clients')
227 - .attr('x', '50%')
228 - .attr('y', borderPadding / 2 - 4)
229 - .attr('text-anchor', 'middle')
230 - .style('font-family', theme.borderFontFamily)
231 - .style('font-size', theme.borderFontSize)
232 - .style('font-weight', theme.borderFontWeight)
233 - .style('fill', theme.borderFontColor);
234 -
235 - svg.append('rect')
236 - .attr('x', 0)
237 - .attr('y', h - borderPadding / 2)
238 - .attr('width', '100%')
239 - .attr('height', borderPadding / 2)
240 - .style('fill', hexToHalfOpacityRGBA(theme.serverColor));
241 -
242 - svg.append('text')
243 - .text('Servers')
244 - .attr('x', '50%')
245 - .attr('y', h - borderPadding / 2 + 16)
246 - .attr('text-anchor', 'middle')
247 - .style('font-family', theme.borderFontFamily)
248 - .style('font-size', theme.borderFontSize)
249 - .style('font-weight', theme.borderFontWeight)
250 - .style('fill', theme.borderFontColor);
251 -
252 - svg.append('rect')
253 - .attr('x', w - borderPadding / 2)
254 - .attr('y', 0)
255 - .attr('width', borderPadding / 2)
256 - .attr('height', '100%')
257 - .style('fill', hexToHalfOpacityRGBA(theme.publicColor));
258 -
259 - svg.append('text')
260 - .text('Public')
261 - .attr('x', w - (borderPadding / 2))
262 - .attr('y', ch - 10)
263 - .attr('text-anchor', 'middle')
264 - .attr('dominant-baseline', 'middle')
265 - .attr('transform', `rotate(90, ${w - (borderPadding / 2)}, ${ch})`)
266 - .style('font-family', theme.borderFontFamily)
267 - .style('font-size', theme.borderFontSize)
268 - .style('font-weight', theme.borderFontWeight)
269 - .style('fill', theme.borderFontColor);
270 -
271 - svg.append('rect')
272 - .attr('x', 0)
273 - .attr('y', 0)
274 - .attr('width', borderPadding / 2)
275 - .attr('height', '100%')
276 - .style('fill', hexToHalfOpacityRGBA(theme.privateColor));
277 -
278 - svg.append('text')
279 - .text('Private')
280 - .attr('x', borderPadding / 2)
281 - .attr('y', ch)
282 - .attr('text-anchor', 'middle')
283 - .attr('dominant-baseline', 'middle')
284 - .attr('transform', `rotate(-90, ${borderPadding / 2 - 10}, ${ch})`)
285 - .style('font-family', theme.borderFontFamily)
286 - .style('font-size', theme.borderFontSize)
287 - .style('font-weight', theme.borderFontWeight)
288 - .style('fill', theme.borderFontColor);
233 + const clientsGradient = svg.append("defs")
234 + .append("linearGradient")
235 + .attr("id", "clientsGradient")
236 + .attr("x1", "0%")
237 + .attr("y1", "0%")
238 + .attr("x2", "0%")
239 + .attr("y2", "100%");
240 +
241 + clientsGradient.append("stop")
242 + .attr("offset", "0%")
243 + .style("stop-color", getRgbColor(theme.clientColor, 1));
244 +
245 + clientsGradient.append("stop")
246 + .attr("offset", "100%")
247 + .style("stop-color", getRgbColor(theme.clientColor, 0));
248 +
249 + svg.append("rect")
250 + .attr("x", 0)
251 + .attr("y", 0)
252 + .attr("width", "100%")
253 + .attr("height", borderPadding / 2)
254 + .style("fill", "url(#clientsGradient)");
255 +
256 + svg.append("text")
257 + .text("Clients")
258 + .attr("x", "50%")
259 + .attr("y", borderPadding / 2 - 4)
260 + .attr("text-anchor", "middle")
261 + .style("font-family", theme.borderFontFamily)
262 + .style("font-size", theme.borderFontSize)
263 + .style("font-weight", theme.borderFontWeight)
264 + .style("fill", theme.borderFontColor);
265 +
266 + const serversGradient = svg.append("defs")
267 + .append("linearGradient")
268 + .attr("id", "serversGradient")
269 + .attr("x1", "0%")
270 + .attr("y1", "100%") // Start from the bottom
271 + .attr("x2", "0%")
272 + .attr("y2", "0%") // End at the top
273 +
274 + serversGradient.append("stop")
275 + .attr("offset", "0%")
276 + .style("stop-color", getRgbColor(theme.serverColor, 1));
277 +
278 + serversGradient.append("stop")
279 + .attr("offset", "100%")
280 + .style("stop-color", getRgbColor(theme.serverColor, 0));
281 +
282 + svg.append("rect")
283 + .attr("x", 0)
284 + .attr("y", h - borderPadding / 2)
285 + .attr("width", "100%")
286 + .attr("height", borderPadding / 2)
287 + .style("fill", "url(#serversGradient)"); // Use the reversed gradient fill
288 +
289 + svg.append("text")
290 + .text("Servers")
291 + .attr("x", "50%")
292 + .attr("y", h - borderPadding / 2 + 16)
293 + .attr("text-anchor", "middle")
294 + .style("font-family", theme.borderFontFamily)
295 + .style("font-size", theme.borderFontSize)
296 + .style("font-weight", theme.borderFontWeight)
297 + .style("fill", theme.borderFontColor);
298 +
299 + const publicGradient = svg.append("defs")
300 + .append("linearGradient")
301 + .attr("id", "publicGradient")
302 + .attr("x1", "100%") // Start from the right
303 + .attr("y1", "0%")
304 + .attr("x2", "0%") // End at the left
305 + .attr("y2", "0%");
306 +
307 + publicGradient.append("stop")
308 + .attr("offset", "0%")
309 + .style("stop-color", getRgbColor(theme.publicColor, 1));
310 +
311 + publicGradient.append("stop")
312 + .attr("offset", "100%")
313 + .style("stop-color", getRgbColor(theme.publicColor, 0));
314 +
315 + svg.append("rect")
316 + .attr("x", w - borderPadding / 2)
317 + .attr("y", 0)
318 + .attr("width", borderPadding / 2)
319 + .attr("height", "100%")
320 + .style("fill", "url(#publicGradient)");
321 +
322 + svg.append("text")
323 + .text("Public")
324 + .attr("x", w - (borderPadding / 2))
325 + .attr("y", ch - 10)
326 + .attr("text-anchor", "middle")
327 + .attr("dominant-baseline", "middle")
328 + .attr("transform", `rotate(90, ${w - (borderPadding / 2)}, ${ch})`)
329 + .style("font-family", theme.borderFontFamily)
330 + .style("font-size", theme.borderFontSize)
331 + .style("font-weight", theme.borderFontWeight)
332 + .style("fill", theme.borderFontColor);
333 +
334 + const privateGradient = svg.append("defs")
335 + .append("linearGradient")
336 + .attr("id", "privateGradient")
337 + .attr("x1", "0%") // Start from the left
338 + .attr("y1", "0%")
339 + .attr("x2", "100%") // End at the right
340 + .attr("y2", "0%");
341 +
342 + privateGradient.append("stop")
343 + .attr("offset", "0%")
344 + .style("stop-color", getRgbColor(theme.privateColor, 1));
345 +
346 + privateGradient.append("stop")
347 + .attr("offset", "100%")
348 + .style("stop-color", getRgbColor(theme.privateColor, 0));
349 +
350 + svg.append("rect")
351 + .attr("x", 0)
352 + .attr("y", 0)
353 + .attr("width", borderPadding / 2)
354 + .attr("height", "100%")
355 + .style("fill", "url(#privateGradient)");
356 +
357 + svg.append("text")
358 + .text("Private")
359 + .attr("x", borderPadding / 2)
360 + .attr("y", ch)
361 + .attr("text-anchor", "middle")
362 + .attr("dominant-baseline", "middle")
363 + .attr("transform", `rotate(-90, ${borderPadding / 2 - 10}, ${ch})`)
364 + .style("font-family", theme.borderFontFamily)
365 + .style("font-size", theme.borderFontSize)
366 + .style("font-weight", theme.borderFontWeight)
367 + .style("fill", theme.borderFontColor);
368 }
369
370 let positionsMap = new Map();
collectors/plugins.d/local-sockets.h
+289 -100
@@ -5,6 +5,19 @@
5
6 #include "libnetdata/libnetdata.h"
7
8 +// disable libmnl for the moment
9 +#undef HAVE_LIBMNL
10 +
11 +#ifdef HAVE_LIBMNL
12 +#include <linux/inet_diag.h>
13 +#include <linux/sock_diag.h>
14 +#include <linux/unix_diag.h>
15 +#include <linux/netlink.h>
16 +#include <libmnl/libmnl.h>
17 +#endif
18 +
19 +#define UID_UNSET (uid_t)(UINT32_MAX)
20 +
21 // --------------------------------------------------------------------------------------------------------------------
22 // hashtable for keeping the namespaces
23 // key and value is the namespace inode
@@ -67,6 +80,7 @@ typedef struct local_socket_state {
80 bool pid;
81 bool cmdline;
82 bool comm;
83 + bool uid;
84 bool namespaces;
85 size_t max_errors;
86
@@ -84,6 +98,12 @@ typedef struct local_socket_state {
98 size_t errors_encountered;
99 } stats;
100
101 +#ifdef HAVE_LIBMNL
102 + bool use_nl;
103 + struct mnl_socket *nl;
104 + uint16_t tmp_protocol;
105 +#endif
106 +
107 uint64_t proc_self_net_ns_inode;
108
109 SIMPLE_HASHTABLE_NET_NS ns_hashtable;
@@ -110,6 +130,7 @@ typedef enum __attribute__((packed)) {
130 struct pid_socket {
131 uint64_t inode;
132 pid_t pid;
133 + uid_t uid;
134 uint64_t net_ns_inode;
135 char *cmdline;
136 char comm[TASK_COMM_LEN];
@@ -155,6 +176,13 @@ typedef struct local_socket {
176
177 SOCKET_DIRECTION direction;
178
179 + uint8_t timer;
180 + uint8_t retransmits;
181 + uint32_t expires;
182 + uint32_t rqueue;
183 + uint32_t wqueue;
184 + uid_t uid;
185 +
186 char comm[TASK_COMM_LEN];
187 char *cmdline;
188
@@ -305,6 +333,7 @@ static inline bool local_sockets_find_all_sockets_in_proc(LS_STATE *ls, const ch
333 continue;
334 }
335 net_ns_inode = 0;
336 + uid_t uid = UID_UNSET;
337
338 struct dirent *fd_entry;
339 while ((fd_entry = readdir(fd_dir)) != NULL) {
@@ -319,6 +348,22 @@ static inline bool local_sockets_find_all_sockets_in_proc(LS_STATE *ls, const ch
348 SIMPLE_HASHTABLE_SLOT_PID_SOCKET *sl = simple_hashtable_get_slot_PID_SOCKET(&ls->pid_sockets_hashtable, inode, &inode, true);
349 struct pid_socket *ps = SIMPLE_HASHTABLE_SLOT_DATA(sl);
350 if(!ps || (ps->pid == 1 && pid != 1)) {
351 + if(uid == UID_UNSET && ls->config.uid) {
352 + char status_buf[512];
353 + snprintfz(filename, sizeof(filename), "%s/%s/status", proc_filename, proc_entry->d_name);
354 + if (read_txt_file(filename, status_buf, sizeof(status_buf)))
355 + local_sockets_log(ls, "cannot open file: %s\n", filename);
356 + else {
357 + char *u = strstr(status_buf, "Uid:");
358 + if(u) {
359 + u += 4;
360 + while(isspace(*u)) u++; // skip spaces
361 + while(*u >= '0' && *u <= '9') u++; // skip the first number (real uid)
362 + while(isspace(*u)) u++; // skip spaces again
363 + uid = strtol(u, NULL, 10); // parse the 2nd number (effective uid)
364 + }
365 + }
366 + }
367 if(!comm[0] && ls->config.comm) {
368 snprintfz(filename, sizeof(filename), "%s/%s/comm", proc_filename, proc_entry->d_name);
369 if (read_txt_file(filename, comm, sizeof(comm)))
@@ -351,6 +396,7 @@ static inline bool local_sockets_find_all_sockets_in_proc(LS_STATE *ls, const ch
396
397 ps->inode = inode;
398 ps->pid = pid;
399 + ps->uid = uid;
400 ps->net_ns_inode = net_ns_inode;
401 strncpyz(ps->comm, comm, sizeof(ps->comm) - 1);
402
@@ -502,6 +548,193 @@ static inline void local_sockets_index_listening_port(LS_STATE *ls, LOCAL_SOCKET
548 }
549 }
550
551 +static inline bool local_sockets_add_socket(LS_STATE *ls, LOCAL_SOCKET *tmp) {
552 + if(!tmp->inode) return false;
553 +
554 + SIMPLE_HASHTABLE_SLOT_LOCAL_SOCKET *sl = simple_hashtable_get_slot_LOCAL_SOCKET(&ls->sockets_hashtable, tmp->inode, &tmp->inode, true);
555 + LOCAL_SOCKET *n = SIMPLE_HASHTABLE_SLOT_DATA(sl);
556 + if(n) {
557 + local_sockets_log(ls, "inode %" PRIu64" already exists in hashtable - ignoring duplicate", tmp->inode);
558 + return false;
559 + }
560 +
561 + n = (LOCAL_SOCKET *)callocz(1, sizeof(LOCAL_SOCKET));
562 + *n = *tmp; // copy all contents
563 +
564 + // fix the key
565 + n->local_port_key.port = n->local.port;
566 + n->local_port_key.family = n->local.family;
567 + n->local_port_key.protocol = n->local.protocol;
568 + n->local_port_key.net_ns_inode = ls->proc_self_net_ns_inode;
569 +
570 + n->local_ip_hash = XXH3_64bits(&n->local.ip, sizeof(n->local.ip));
571 + n->remote_ip_hash = XXH3_64bits(&n->remote.ip, sizeof(n->remote.ip));
572 + n->local_port_hash = XXH3_64bits(&n->local_port_key, sizeof(n->local_port_key));
573 +
574 + // --- look up a pid for it -----------------------------------------------------------------------------------
575 +
576 + SIMPLE_HASHTABLE_SLOT_PID_SOCKET *sl_pid = simple_hashtable_get_slot_PID_SOCKET(&ls->pid_sockets_hashtable, n->inode, &n->inode, false);
577 + struct pid_socket *ps = SIMPLE_HASHTABLE_SLOT_DATA(sl_pid);
578 + if(ps) {
579 + n->net_ns_inode = ps->net_ns_inode;
580 + n->pid = ps->pid;
581 +
582 + if(ps->uid != UID_UNSET && n->uid == UID_UNSET)
583 + n->uid = ps->uid;
584 +
585 + if(ps->cmdline)
586 + n->cmdline = strdupz(ps->cmdline);
587 + strncpyz(n->comm, ps->comm, sizeof(n->comm) - 1);
588 + }
589 +
590 + // --- index it -----------------------------------------------------------------------------------------------
591 +
592 + simple_hashtable_set_slot_LOCAL_SOCKET(&ls->sockets_hashtable, sl, n->inode, n);
593 +
594 + if(!local_sockets_is_zero_address(&n->local)) {
595 + // put all the local IPs into the local_ips hashtable
596 + // so, we learn all local IPs the system has
597 +
598 + SIMPLE_HASHTABLE_SLOT_LOCAL_IP *sl_ip =
599 + simple_hashtable_get_slot_LOCAL_IP(&ls->local_ips_hashtable, n->local_ip_hash, &n->local.ip, true);
600 +
601 + union ipv46 *ip = SIMPLE_HASHTABLE_SLOT_DATA(sl_ip);
602 + if(!ip)
603 + simple_hashtable_set_slot_LOCAL_IP(&ls->local_ips_hashtable, sl_ip, n->local_ip_hash, &n->local.ip);
604 + }
605 +
606 + // --- 1st phase for direction detection ----------------------------------------------------------------------
607 +
608 + if((n->local.protocol == IPPROTO_TCP && n->state == TCP_LISTEN) ||
609 + local_sockets_is_zero_address(&n->local) ||
610 + local_sockets_is_zero_address(&n->remote)) {
611 + // the socket is either in a TCP LISTEN, or
612 + // the remote address is zero
613 + n->direction |= SOCKET_DIRECTION_LISTEN;
614 + }
615 + else if(
616 + local_sockets_is_loopback_address(&n->local) ||
617 + local_sockets_is_loopback_address(&n->remote)) {
618 + // the local IP address is loopback
619 + n->direction |= SOCKET_DIRECTION_LOCAL;
620 + }
621 + else {
622 + // we can't say yet if it is inbound or outboud
623 + // so, mark it as both inbound and outbound
624 + n->direction |= SOCKET_DIRECTION_INBOUND | SOCKET_DIRECTION_OUTBOUND;
625 + }
626 +
627 + // --- index it in LISTENING_PORT -----------------------------------------------------------------------------
628 +
629 + local_sockets_index_listening_port(ls, n);
630 +
631 + return true;
632 +}
633 +
634 +#ifdef HAVE_LIBMNL
635 +
636 +static inline void local_sockets_netlink_init(LS_STATE *ls) {
637 + ls->use_nl = true;
638 + ls->nl = mnl_socket_open(NETLINK_INET_DIAG);
639 + if (!ls->nl) {
640 + local_sockets_log(ls, "cannot open netlink socket");
641 + ls->use_nl = false;
642 + }
643 +
644 + if (mnl_socket_bind(ls->nl, 0, MNL_SOCKET_AUTOPID) < 0) {
645 + local_sockets_log(ls, "cannot bind netlink socket");
646 + ls->use_nl = false;
647 + }
648 +}
649 +
650 +static inline void local_sockets_netlink_cleanup(LS_STATE *ls) {
651 + if(ls->nl) {
652 + mnl_socket_close(ls->nl);
653 + ls->nl = NULL;
654 + }
655 +}
656 +
657 +static inline int local_sockets_netlink_cb_data(const struct nlmsghdr *nlh, void *data) {
658 + LS_STATE *ls = data;
659 +
660 + struct inet_diag_msg *diag_msg = mnl_nlmsg_get_payload(nlh);
661 +
662 + LOCAL_SOCKET n = {
663 + .inode = diag_msg->idiag_inode,
664 + .direction = SOCKET_DIRECTION_NONE,
665 + .state = diag_msg->idiag_state,
666 + .local = {
667 + .protocol = ls->tmp_protocol,
668 + .family = diag_msg->idiag_family,
669 + .port = diag_msg->id.idiag_sport,
670 + },
671 + .remote = {
672 + .protocol = ls->tmp_protocol,
673 + .family = diag_msg->idiag_family,
674 + .port = diag_msg->id.idiag_dport,
675 + },
676 + .timer = diag_msg->idiag_timer,
677 + .retransmits = diag_msg->idiag_retrans,
678 + .expires = diag_msg->idiag_expires,
679 + .rqueue = diag_msg->idiag_rqueue,
680 + .wqueue = diag_msg->idiag_wqueue,
681 + .uid = diag_msg->idiag_uid,
682 + };
683 +
684 + if (diag_msg->idiag_family == AF_INET) {
685 + memcpy(&n.local.ip.ipv4, diag_msg->id.idiag_src, sizeof(n.local.ip.ipv4));
686 + memcpy(&n.remote.ip.ipv4, diag_msg->id.idiag_dst, sizeof(n.remote.ip.ipv4));
687 + }
688 + else if (diag_msg->idiag_family == AF_INET6) {
689 + memcpy(&n.local.ip.ipv6, diag_msg->id.idiag_src, sizeof(n.local.ip.ipv6));
690 + memcpy(&n.remote.ip.ipv6, diag_msg->id.idiag_dst, sizeof(n.remote.ip.ipv6));
691 + }
692 +
693 + local_sockets_add_socket(ls, &n);
694 +
695 + return MNL_CB_OK;
696 +}
697 +
698 +static inline bool local_sockets_netlink_get_sockets(LS_STATE *ls, uint16_t family, uint16_t protocol) {
699 + ls->tmp_protocol = protocol;
700 +
701 + char buf[MNL_SOCKET_BUFFER_SIZE];
702 + struct nlmsghdr *nlh;
703 + struct inet_diag_req_v2 req;
704 + unsigned int seq, portid = mnl_socket_get_portid(ls->nl);
705 +
706 + memset(&req, 0, sizeof(req));
707 + req.sdiag_family = family;
708 + req.sdiag_protocol = protocol;
709 + req.idiag_states = -1;
710 +
711 + nlh = mnl_nlmsg_put_header(buf);
712 + nlh->nlmsg_type = SOCK_DIAG_BY_FAMILY;
713 + nlh->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
714 + nlh->nlmsg_seq = seq = time(NULL);
715 + mnl_nlmsg_put_extra_header(nlh, sizeof(req));
716 + memcpy(mnl_nlmsg_get_payload(nlh), &req, sizeof(req));
717 +
718 + if (mnl_socket_sendto(ls->nl, nlh, nlh->nlmsg_len) < 0) {
719 + local_sockets_log(ls, "mnl_socket_send failed");
720 + return false;
721 + }
722 +
723 + ssize_t ret;
724 + while ((ret = mnl_socket_recvfrom(ls->nl, buf, sizeof(buf))) > 0) {
725 + ret = mnl_cb_run(buf, ret, seq, portid, local_sockets_netlink_cb_data, ls);
726 + if (ret <= MNL_CB_STOP)
727 + break;
728 + }
729 + if (ret == -1) {
730 + local_sockets_log(ls, "mnl_socket_recvfrom");
731 + return false;
732 + }
733 +
734 + return true;
735 +}
736 +#endif // HAVE_LIBMNL
737 +
738 static inline bool local_sockets_read_proc_net_x(LS_STATE *ls, const char *filename, uint16_t family, uint16_t protocol) {
739 if(family != AF_INET && family != AF_INET6)
740 return false;
@@ -545,109 +778,34 @@ static inline bool local_sockets_read_proc_net_x(LS_STATE *ls, const char *filen
778 continue;
779 }
780 }
548 - if(!inode) continue;
549 -
550 - SIMPLE_HASHTABLE_SLOT_LOCAL_SOCKET *sl = simple_hashtable_get_slot_LOCAL_SOCKET(&ls->sockets_hashtable, inode, &inode, true);
551 - LOCAL_SOCKET *n = SIMPLE_HASHTABLE_SLOT_DATA(sl);
552 - if(n) {
553 - local_sockets_log(
554 - ls,
555 - "inode %" PRIu64
556 - " given on line %zu of filename '%s', already exists in hashtable - ignoring duplicate",
557 - inode,
558 - counter,
559 - filename);
560 - continue;
561 - }
562 -
563 - // allocate a new socket and index it
781
565 - n = (LOCAL_SOCKET *)callocz(1, sizeof(LOCAL_SOCKET));
566 -
567 - // --- initialize it ------------------------------------------------------------------------------------------
782 + LOCAL_SOCKET n = {
783 + .inode = inode,
784 + .direction = SOCKET_DIRECTION_NONE,
785 + .state = (int)state,
786 + .local = {
787 + .family = family,
788 + .protocol = protocol,
789 + .port = local_port,
790 + },
791 + .remote = {
792 + .family = family,
793 + .protocol = protocol,
794 + .port = remote_port,
795 + },
796 + .uid = UID_UNSET,
797 + };
798
799 if(family == AF_INET) {
570 - n->local.ip.ipv4 = local_address;
571 - n->remote.ip.ipv4 = remote_address;
800 + n.local.ip.ipv4 = local_address;
801 + n.remote.ip.ipv4 = remote_address;
802 }
803 else if(family == AF_INET6) {
574 - ipv6_to_in6_addr(local_address6, &n->local.ip.ipv6);
575 - ipv6_to_in6_addr(remote_address6, &n->remote.ip.ipv6);
576 - }
577 -
578 - n->direction = 0;
579 - n->state = (int)state;
580 - n->inode = inode;
581 -
582 - n->local.family = family;
583 - n->local.protocol = protocol;
584 - n->local.port = local_port;
585 -
586 - n->remote.family = family;
587 - n->remote.protocol = protocol;
588 - n->remote.port = remote_port;
589 -
590 - n->local_port_key.port = n->local.port;
591 - n->local_port_key.family = family;
592 - n->local_port_key.protocol = protocol;
593 - n->local_port_key.net_ns_inode = ls->proc_self_net_ns_inode;
594 -
595 - n->local_ip_hash = XXH3_64bits(&n->local.ip, sizeof(n->local.ip));
596 - n->remote_ip_hash = XXH3_64bits(&n->remote.ip, sizeof(n->remote.ip));
597 - n->local_port_hash = XXH3_64bits(&n->local_port_key, sizeof(n->local_port_key));
598 -
599 - // --- look up a pid for it -----------------------------------------------------------------------------------
600 -
601 - SIMPLE_HASHTABLE_SLOT_PID_SOCKET *sl_pid = simple_hashtable_get_slot_PID_SOCKET(&ls->pid_sockets_hashtable, inode, &inode, false);
602 - struct pid_socket *ps = SIMPLE_HASHTABLE_SLOT_DATA(sl_pid);
603 - if(ps) {
604 - n->net_ns_inode = ps->net_ns_inode;
605 - n->pid = ps->pid;
606 - if(ps->cmdline)
607 - n->cmdline = strdupz(ps->cmdline);
608 - strncpyz(n->comm, ps->comm, sizeof(n->comm) - 1);
609 - }
610 -
611 - // --- index it -----------------------------------------------------------------------------------------------
612 -
613 - simple_hashtable_set_slot_LOCAL_SOCKET(&ls->sockets_hashtable, sl, inode, n);
614 -
615 - if(!local_sockets_is_zero_address(&n->local)) {
616 - // put all the local IPs into the local_ips hashtable
617 - // so, we learn all local IPs the system has
618 -
619 - SIMPLE_HASHTABLE_SLOT_LOCAL_IP *sl_ip =
620 - simple_hashtable_get_slot_LOCAL_IP(&ls->local_ips_hashtable, n->local_ip_hash, &n->local.ip, true);
621 -
622 - union ipv46 *ip = SIMPLE_HASHTABLE_SLOT_DATA(sl_ip);
623 - if(!ip)
624 - simple_hashtable_set_slot_LOCAL_IP(&ls->local_ips_hashtable, sl_ip, n->local_ip_hash, &n->local.ip);
625 - }
626 -
627 - // --- 1st phase for direction detection ----------------------------------------------------------------------
628 -
629 - if((n->local.protocol == IPPROTO_TCP && n->state == TCP_LISTEN) ||
630 - local_sockets_is_zero_address(&n->local) ||
631 - local_sockets_is_zero_address(&n->remote)) {
632 - // the socket is either in a TCP LISTEN, or
633 - // the remote address is zero
634 - n->direction |= SOCKET_DIRECTION_LISTEN;
635 - }
636 - else if(
637 - local_sockets_is_loopback_address(&n->local) ||
638 - local_sockets_is_loopback_address(&n->remote)) {
639 - // the local IP address is loopback
640 - n->direction |= SOCKET_DIRECTION_LOCAL;
641 - }
642 - else {
643 - // we can't say yet if it is inbound or outboud
644 - // so, mark it as both inbound and outbound
645 - n->direction |= SOCKET_DIRECTION_INBOUND | SOCKET_DIRECTION_OUTBOUND;
804 + ipv6_to_in6_addr(local_address6, &n.local.ip.ipv6);
805 + ipv6_to_in6_addr(remote_address6, &n.remote.ip.ipv6);
806 }
807
648 - // --- index it in LISTENING_PORT -----------------------------------------------------------------------------
649 -
650 - local_sockets_index_listening_port(ls, n);
808 + local_sockets_add_socket(ls, &n);
809 }
810
811 fclose(fp);
@@ -744,6 +902,19 @@ static inline void local_sockets_cleanup(LS_STATE *ls) {
902
903 // --------------------------------------------------------------------------------------------------------------------
904
905 +static inline void local_sockets_do_family_protocol(LS_STATE *ls, const char *filename, uint16_t family, uint16_t protocol) {
906 +#ifdef HAVE_LIBMNL
907 + if(ls->use_nl) {
908 + ls->use_nl = local_sockets_netlink_get_sockets(ls, family, protocol);
909 +
910 + if(ls->use_nl)
911 + return;
912 + }
913 +#endif
914 +
915 + local_sockets_read_proc_net_x(ls, filename, family, protocol);
916 +}
917 +
918 static inline void local_sockets_read_sockets_from_proc(LS_STATE *ls) {
919 char path[FILENAME_MAX + 1];
920
@@ -759,22 +930,22 @@ static inline void local_sockets_read_sockets_from_proc(LS_STATE *ls) {
930
931 if(ls->config.tcp4) {
932 snprintfz(path, sizeof(path), "%s/proc/net/tcp", ls->config.host_prefix);
762 - local_sockets_read_proc_net_x(ls, path, AF_INET, IPPROTO_TCP);
933 + local_sockets_do_family_protocol(ls, path, AF_INET, IPPROTO_TCP);
934 }
935
936 if(ls->config.udp4) {
937 snprintfz(path, sizeof(path), "%s/proc/net/udp", ls->config.host_prefix);
767 - local_sockets_read_proc_net_x(ls, path, AF_INET, IPPROTO_UDP);
938 + local_sockets_do_family_protocol(ls, path, AF_INET, IPPROTO_UDP);
939 }
940
941 if(ls->config.tcp6) {
942 snprintfz(path, sizeof(path), "%s/proc/net/tcp6", ls->config.host_prefix);
772 - local_sockets_read_proc_net_x(ls, path, AF_INET6, IPPROTO_TCP);
943 + local_sockets_do_family_protocol(ls, path, AF_INET6, IPPROTO_TCP);
944 }
945
946 if(ls->config.udp6) {
947 snprintfz(path, sizeof(path), "%s/proc/net/udp6", ls->config.host_prefix);
777 - local_sockets_read_proc_net_x(ls, path, AF_INET6, IPPROTO_UDP);
948 + local_sockets_do_family_protocol(ls, path, AF_INET6, IPPROTO_UDP);
949 }
950 }
951
@@ -865,6 +1036,11 @@ static inline bool local_sockets_get_namespace_sockets(LS_STATE *ls, struct pid_
1036 exit(EXIT_FAILURE);
1037 }
1038
1039 +#ifdef HAVE_LIBMNL
1040 + local_sockets_netlink_cleanup(ls);
1041 + local_sockets_netlink_init(ls);
1042 +#endif
1043 +
1044 // read all sockets from /proc
1045 local_sockets_read_sockets_from_proc(ls);
1046
@@ -877,6 +1053,10 @@ static inline bool local_sockets_get_namespace_sockets(LS_STATE *ls, struct pid_
1053 };
1054 local_sockets_send_to_parent(ls, &zero, &cw);
1055
1056 +#ifdef HAVE_LIBMNL
1057 + local_sockets_netlink_cleanup(ls);
1058 +#endif
1059 +
1060 close(pipefd[1]); // Close write end of pipe
1061 exit(EXIT_SUCCESS);
1062 }
@@ -985,6 +1165,11 @@ static inline void local_sockets_namespaces(LS_STATE *ls) {
1165 // --------------------------------------------------------------------------------------------------------------------
1166
1167 static inline void local_sockets_process(LS_STATE *ls) {
1168 +
1169 +#ifdef HAVE_LIBMNL
1170 + local_sockets_netlink_init(ls);
1171 +#endif
1172 +
1173 ls->config.host_prefix = netdata_configured_host_prefix;
1174
1175 // initialize our hashtables
@@ -1006,6 +1191,10 @@ static inline void local_sockets_process(LS_STATE *ls) {
1191
1192 // free all memory
1193 local_sockets_cleanup(ls);
1194 +
1195 +#ifdef HAVE_LIBMNL
1196 + local_sockets_netlink_cleanup(ls);
1197 +#endif
1198 }
1199
1200 static inline void ipv6_address_to_txt(struct in6_addr *in6_addr, char *dst) {
collectors/plugins.d/local_listeners.c
+19 -1
@@ -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, ns=%"PRIu64", 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], uid=%u, comm=%s\n",
59 protocol_name(n),
60 (n->direction & SOCKET_DIRECTION_LISTEN) ? "LISTEN," : "",
61 (n->direction & SOCKET_DIRECTION_INBOUND) ? "INBOUND," : "",
@@ -67,12 +67,17 @@ static void print_local_listeners_debug(LS_STATE *ls __maybe_unused, LOCAL_SOCKE
67 n->net_ns_inode,
68 local_address, n->local.port,
69 remote_address, n->remote.port,
70 + n->uid,
71 n->comm);
72 }
73
74 // --------------------------------------------------------------------------------------------------------------------
75
76 int main(int argc, char **argv) {
77 + static struct rusage started, ended;
78 + getrusage(RUSAGE_SELF, &started);
79 + bool debug = false;
80 +
81 LS_STATE ls = {
82 .config = {
83 .listening = true,
@@ -206,8 +211,11 @@ int main(int argc, char **argv) {
211 ls.config.comm = true;
212 ls.config.cmdline = true;
213 ls.config.namespaces = true;
214 + ls.config.uid = true;
215 ls.config.max_errors = SIZE_MAX;
216 ls.config.cb = print_local_listeners_debug;
217 +
218 + debug = true;
219 }
220 else if (strcmp("tcp", s) == 0) {
221 ls.config.tcp4 = ls.config.tcp6 = positive;
@@ -269,5 +277,15 @@ int main(int argc, char **argv) {
277
278 local_sockets_process(&ls);
279
280 + getrusage(RUSAGE_SELF, &ended);
281 +
282 + if(debug) {
283 + unsigned long long user = ended.ru_utime.tv_sec * 1000000ULL + ended.ru_utime.tv_usec - started.ru_utime.tv_sec * 1000000ULL + started.ru_utime.tv_usec;
284 + unsigned long long system = ended.ru_stime.tv_sec * 1000000ULL + ended.ru_stime.tv_usec - started.ru_stime.tv_sec * 1000000ULL + started.ru_stime.tv_usec;
285 + unsigned long long total = user + system;
286 +
287 + fprintf(stderr, "CPU Usage %llu user, %llu system, %llu total\n", user, system, total);
288 + }
289 +
290 return 0;
291 }
config.cmake.h.in
+1
@@ -143,6 +143,7 @@
143 #define ENABLE_JSONC 1
144
145 #cmakedefine HAVE_LIBYAML
146 +#cmakedefine HAVE_LIBMNL
147
148 // /* Enable GNU extensions on systems that have them. */
149 // #ifndef _GNU_SOURCE