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