diff options
-rw-r--r-- | CMakeLists.txt | 28 | ||||
-rw-r--r-- | collectors/apps.plugin/apps_plugin.c | 3 | ||||
-rw-r--r-- | collectors/network-viewer.plugin/network-viewer.c | 3 | ||||
-rw-r--r-- | collectors/network-viewer.plugin/viewer.html | 221 | ||||
-rw-r--r-- | collectors/plugins.d/local-sockets.h | 389 | ||||
-rw-r--r-- | collectors/plugins.d/local_listeners.c | 20 | ||||
-rw-r--r-- | config.cmake.h.in | 1 |
7 files changed, 487 insertions, 178 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 378aa5945b..dff002aef6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1638,6 +1638,12 @@ target_link_libraries(libnetdata PUBLIC ${OPENSSL_LDFLAGS}) # h2o target_link_libraries(libnetdata PUBLIC "$<$<BOOL:${ENABLE_H2O}>:h2o>") +# mnl +pkg_check_modules(MNL libmnl) +if(MNL_FOUND) + set(HAVE_LIBMNL True) +endif() + # # helper function to build protos # @@ -1781,8 +1787,11 @@ if(ENABLE_PLUGIN_FREEIPMI) endif() if(ENABLE_PLUGIN_NFACCT) + if (NOT MNL_FOUND) + message(FATAL_ERROR "Can not build nfacct.plugin because MNL library could not be found.") + endif() + pkg_check_modules(NFACCT REQUIRED libnetfilter_acct) - pkg_check_modules(MNL REQUIRED libmnl) set(NFACCT_PLUGIN_FILES collectors/nfacct.plugin/plugin_nfacct.c) @@ -1979,7 +1988,13 @@ if(ENABLE_PLUGIN_LOCAL_LISTENERS) ) add_executable(local-listeners ${LOCAL_LISTENERS_FILES}) - target_link_libraries(local-listeners libnetdata) + + target_compile_options(local-listeners PRIVATE + "$<$<BOOL:${MNL_FOUND}>:${MNL_CFLAGS_OTHER}>") + target_include_directories(local-listeners PRIVATE + "$<$<BOOL:${MNL_FOUND}>:${MNL_INCLUDE_DIRS}>") + target_link_libraries(local-listeners libnetdata + "$<$<BOOL:${MNL_FOUND}>:${MNL_LIBRARIES}>") install(TARGETS local-listeners COMPONENT local_listeners @@ -1993,7 +2008,14 @@ if(ENABLE_PLUGIN_NETWORK_VIEWER) ) add_executable(network-viewer.plugin ${NETWORK_VIEWER_FILES}) - target_link_libraries(network-viewer.plugin libnetdata) + + target_compile_options(network-viewer.plugin PRIVATE + "$<$<BOOL:${MNL_FOUND}>:${MNL_CFLAGS_OTHER}>") + target_include_directories(network-viewer.plugin PRIVATE + "$<$<BOOL:${MNL_FOUND}>:${MNL_INCLUDE_DIRS}>") + target_link_libraries(network-viewer.plugin libnetdata + "$<$<BOOL:${MNL_FOUND}>:${MNL_LIBRARIES}>") + install(TARGETS network-viewer.plugin COMPONENT network_viewer_plugin diff --git a/collectors/apps.plugin/apps_plugin.c b/collectors/apps.plugin/apps_plugin.c index fdaac0a3cf..4144787edc 100644 --- a/collectors/apps.plugin/apps_plugin.c +++ b/collectors/apps.plugin/apps_plugin.c @@ -4483,7 +4483,7 @@ static void function_processes(const char *transaction, char *function __maybe_u unsigned int io_divisor = 1024 * RATES_DETAIL; BUFFER *wb = buffer_create(4096, NULL); - buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_NEWLINE_ON_ARRAY_ITEMS); + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY); buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK); buffer_json_member_add_string(wb, "type", "table"); buffer_json_member_add_time_t(wb, "update_every", update_every); @@ -5264,7 +5264,6 @@ static bool apps_plugin_exit = false; int main(int argc, char **argv) { clocks_init(); nd_log_initialize_for_external_plugins("apps.plugin"); - for_each_open_fd(OPEN_FD_ACTION_CLOSE, OPEN_FD_EXCLUDE_STDIN|OPEN_FD_EXCLUDE_STDOUT|OPEN_FD_EXCLUDE_STDERR); pagesize = (size_t)sysconf(_SC_PAGESIZE); diff --git a/collectors/network-viewer.plugin/network-viewer.c b/collectors/network-viewer.plugin/network-viewer.c index fb82ba0721..39f6205ce7 100644 --- a/collectors/network-viewer.plugin/network-viewer.c +++ b/collectors/network-viewer.plugin/network-viewer.c @@ -106,7 +106,7 @@ void network_viewer_function(const char *transaction, char *function __maybe_unu CLEAN_BUFFER *wb = buffer_create(0, NULL); buffer_flush(wb); wb->content_type = CT_APPLICATION_JSON; - buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT); + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY); buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK); buffer_json_member_add_string(wb, "type", "table"); @@ -125,6 +125,7 @@ void network_viewer_function(const char *transaction, char *function __maybe_unu .udp4 = true, .udp6 = true, .pid = true, + .uid = true, .cmdline = true, .comm = true, .namespaces = true, diff --git a/collectors/network-viewer.plugin/viewer.html b/collectors/network-viewer.plugin/viewer.html index 2ebd4ad4f4..b66f920ae1 100644 --- a/collectors/network-viewer.plugin/viewer.html +++ b/collectors/network-viewer.plugin/viewer.html @@ -209,83 +209,162 @@ return `rgba(${r}, ${g}, ${b}, 0.5)`; } + function getRgbColor(hex, opacity = 1) { + if (hex.length !== 7 || hex[0] !== "#") throw new Error("Invalid hex color format"); + + // Parse the hex color components (red, green, blue) + const r = parseInt(hex.slice(1, 3), 16); + const g = parseInt(hex.slice(3, 5), 16); + const b = parseInt(hex.slice(5, 7), 16); + + // Ensure opacity is within the valid range (0 to 1) + const validOpacity = Math.min(1, Math.max(0, opacity)); + + // Return the RGBA color + return `rgba(${r}, ${g}, ${b}, ${validOpacity})`; + } + function drawInitialChart(svg, data, w, h, borderPadding, theme) { const cw = w / 2; const ch = h / 2; document.body.style.backgroundColor = theme.backgroundColor; - svg.append('rect') - .attr('x', 0) - .attr('y', 0) - .attr('width', '100%') - .attr('height', borderPadding / 2) - .style('fill', hexToHalfOpacityRGBA(theme.clientColor)); - - svg.append('text') - .text('Clients') - .attr('x', '50%') - .attr('y', borderPadding / 2 - 4) - .attr('text-anchor', 'middle') - .style('font-family', theme.borderFontFamily) - .style('font-size', theme.borderFontSize) - .style('font-weight', theme.borderFontWeight) - .style('fill', theme.borderFontColor); - - svg.append('rect') - .attr('x', 0) - .attr('y', h - borderPadding / 2) - .attr('width', '100%') - .attr('height', borderPadding / 2) - .style('fill', hexToHalfOpacityRGBA(theme.serverColor)); - - svg.append('text') - .text('Servers') - .attr('x', '50%') - .attr('y', h - borderPadding / 2 + 16) - .attr('text-anchor', 'middle') - .style('font-family', theme.borderFontFamily) - .style('font-size', theme.borderFontSize) - .style('font-weight', theme.borderFontWeight) - .style('fill', theme.borderFontColor); - - svg.append('rect') - .attr('x', w - borderPadding / 2) - .attr('y', 0) - .attr('width', borderPadding / 2) - .attr('height', '100%') - .style('fill', hexToHalfOpacityRGBA(theme.publicColor)); - - svg.append('text') - .text('Public') - .attr('x', w - (borderPadding / 2)) - .attr('y', ch - 10) - .attr('text-anchor', 'middle') - .attr('dominant-baseline', 'middle') - .attr('transform', `rotate(90, ${w - (borderPadding / 2)}, ${ch})`) - .style('font-family', theme.borderFontFamily) - .style('font-size', theme.borderFontSize) - .style('font-weight', theme.borderFontWeight) - .style('fill', theme.borderFontColor); - - svg.append('rect') - .attr('x', 0) - .attr('y', 0) - .attr('width', borderPadding / 2) - .attr('height', '100%') - .style('fill', hexToHalfOpacityRGBA(theme.privateColor)); - - svg.append('text') - .text('Private') - .attr('x', borderPadding / 2) - .attr('y', ch) - .attr('text-anchor', 'middle') - .attr('dominant-baseline', 'middle') - .attr('transform', `rotate(-90, ${borderPadding / 2 - 10}, ${ch})`) - .style('font-family', theme.borderFontFamily) - .style('font-size', theme.borderFontSize) - .style('font-weight', theme.borderFontWeight) - .style('fill', theme.borderFontColor); + const clientsGradient = svg.append("defs") + .append("linearGradient") + .attr("id", "clientsGradient") + .attr("x1", "0%") + .attr("y1", "0%") + .attr("x2", "0%") + .attr("y2", "100%"); + + clientsGradient.append("stop") + .attr("offset", "0%") + .style("stop-color", getRgbColor(theme.clientColor, 1)); + + clientsGradient.append("stop") + .attr("offset", "100%") + .style("stop-color", getRgbColor(theme.clientColor, 0)); + + svg.append("rect") + .attr("x", 0) + .attr("y", 0) + .attr("width", "100%") + .attr("height", borderPadding / 2) + .style("fill", "url(#clientsGradient)"); + + svg.append("text") + .text("Clients") + .attr("x", "50%") + .attr("y", borderPadding / 2 - 4) + .attr("text-anchor", "middle") + .style("font-family", theme.borderFontFamily) + .style("font-size", theme.borderFontSize) + .style("font-weight", theme.borderFontWeight) + .style("fill", theme.borderFontColor); + + const serversGradient = svg.append("defs") + .append("linearGradient") + .attr("id", "serversGradient") + .attr("x1", "0%") + .attr("y1", "100%") // Start from the bottom + .attr("x2", "0%") + .attr("y2", "0%") // End at the top + + serversGradient.append("stop") + .attr("offset", "0%") + .style("stop-color", getRgbColor(theme.serverColor, 1)); + + serversGradient.append("stop") + .attr("offset", "100%") + .style("stop-color", getRgbColor(theme.serverColor, 0)); + + svg.append("rect") + .attr("x", 0) + .attr("y", h - borderPadding / 2) + .attr("width", "100%") + .attr("height", borderPadding / 2) + .style("fill", "url(#serversGradient)"); // Use the reversed gradient fill + + svg.append("text") + .text("Servers") + .attr("x", "50%") + .attr("y", h - borderPadding / 2 + 16) + .attr("text-anchor", "middle") + .style("font-family", theme.borderFontFamily) + .style("font-size", theme.borderFontSize) + .style("font-weight", theme.borderFontWeight) + .style("fill", theme.borderFontColor); + + const publicGradient = svg.append("defs") + .append("linearGradient") + .attr("id", "publicGradient") + .attr("x1", "100%") // Start from the right + .attr("y1", "0%") + .attr("x2", "0%") // End at the left + .attr("y2", "0%"); + + publicGradient.append("stop") + .attr("offset", "0%") + .style("stop-color", getRgbColor(theme.publicColor, 1)); + + publicGradient.append("stop") + .attr("offset", "100%") + .style("stop-color", getRgbColor(theme.publicColor, 0)); + + svg.append("rect") + .attr("x", w - borderPadding / 2) + .attr("y", 0) + .attr("width", borderPadding / 2) + .attr("height", "100%") + .style("fill", "url(#publicGradient)"); + + svg.append("text") + .text("Public") + .attr("x", w - (borderPadding / 2)) + .attr("y", ch - 10) + .attr("text-anchor", "middle") + .attr("dominant-baseline", "middle") + .attr("transform", `rotate(90, ${w - (borderPadding / 2)}, ${ch})`) + .style("font-family", theme.borderFontFamily) + .style("font-size", theme.borderFontSize) + .style("font-weight", theme.borderFontWeight) + .style("fill", theme.borderFontColor); + + const privateGradient = svg.append("defs") + .append("linearGradient") + .attr("id", "privateGradient") + .attr("x1", "0%") // Start from the left + .attr("y1", "0%") + .attr("x2", "100%") // End at the right + .attr("y2", "0%"); + + privateGradient.append("stop") + .attr("offset", "0%") + .style("stop-color", getRgbColor(theme.privateColor, 1)); + + privateGradient.append("stop") + .attr("offset", "100%") + .style("stop-color", getRgbColor(theme.privateColor, 0)); + + svg.append("rect") + .attr("x", 0) + .attr("y", 0) + .attr("width", borderPadding / 2) + .attr("height", "100%") + .style("fill", "url(#privateGradient)"); + + svg.append("text") + .text("Private") + .attr("x", borderPadding / 2) + .attr("y", ch) + .attr("text-anchor", "middle") + .attr("dominant-baseline", "middle") + .attr("transform", `rotate(-90, ${borderPadding / 2 - 10}, ${ch})`) + .style("font-family", theme.borderFontFamily) + .style("font-size", theme.borderFontSize) + .style("font-weight", theme.borderFontWeight) + .style("fill", theme.borderFontColor); } let positionsMap = new Map(); diff --git a/collectors/plugins.d/local-sockets.h b/collectors/plugins.d/local-sockets.h index 4958594fe5..3a21eef4ac 100644 --- a/collectors/plugins.d/local-sockets.h +++ b/collectors/plugins.d/local-sockets.h @@ -5,6 +5,19 @@ #include "libnetdata/libnetdata.h" +// disable libmnl for the moment +#undef HAVE_LIBMNL + +#ifdef HAVE_LIBMNL +#include <linux/inet_diag.h> +#include <linux/sock_diag.h> +#include <linux/unix_diag.h> +#include <linux/netlink.h> +#include <libmnl/libmnl.h> +#endif + +#define UID_UNSET (uid_t)(UINT32_MAX) + // -------------------------------------------------------------------------------------------------------------------- // hashtable for keeping the namespaces // key and value is the namespace inode @@ -67,6 +80,7 @@ typedef struct local_socket_state { bool pid; bool cmdline; bool comm; + bool uid; bool namespaces; size_t max_errors; @@ -84,6 +98,12 @@ typedef struct local_socket_state { size_t errors_encountered; } stats; +#ifdef HAVE_LIBMNL + bool use_nl; + struct mnl_socket *nl; + uint16_t tmp_protocol; +#endif + uint64_t proc_self_net_ns_inode; SIMPLE_HASHTABLE_NET_NS ns_hashtable; @@ -110,6 +130,7 @@ typedef enum __attribute__((packed)) { struct pid_socket { uint64_t inode; pid_t pid; + uid_t uid; uint64_t net_ns_inode; char *cmdline; char comm[TASK_COMM_LEN]; @@ -155,6 +176,13 @@ typedef struct local_socket { SOCKET_DIRECTION direction; + uint8_t timer; + uint8_t retransmits; + uint32_t expires; + uint32_t rqueue; + uint32_t wqueue; + uid_t uid; + char comm[TASK_COMM_LEN]; char *cmdline; @@ -305,6 +333,7 @@ static inline bool local_sockets_find_all_sockets_in_proc(LS_STATE *ls, const ch continue; } net_ns_inode = 0; + uid_t uid = UID_UNSET; struct dirent *fd_entry; 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 SIMPLE_HASHTABLE_SLOT_PID_SOCKET *sl = simple_hashtable_get_slot_PID_SOCKET(&ls->pid_sockets_hashtable, inode, &inode, true); struct pid_socket *ps = SIMPLE_HASHTABLE_SLOT_DATA(sl); if(!ps || (ps->pid == 1 && pid != 1)) { + if(uid == UID_UNSET && ls->config.uid) { + char status_buf[512]; + snprintfz(filename, sizeof(filename), "%s/%s/status", proc_filename, proc_entry->d_name); + if (read_txt_file(filename, status_buf, sizeof(status_buf))) + local_sockets_log(ls, "cannot open file: %s\n", filename); + else { + char *u = strstr(status_buf, "Uid:"); + if(u) { + u += 4; + while(isspace(*u)) u++; // skip spaces + while(*u >= '0' && *u <= '9') u++; // skip the first number (real uid) + while(isspace(*u)) u++; // skip spaces again + uid = strtol(u, NULL, 10); // parse the 2nd number (effective uid) + } + } + } if(!comm[0] && ls->config.comm) { snprintfz(filename, sizeof(filename), "%s/%s/comm", proc_filename, proc_entry->d_name); 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 ps->inode = inode; ps->pid = pid; + ps->uid = uid; ps->net_ns_inode = net_ns_inode; strncpyz(ps->comm, comm, sizeof(ps->comm) - 1); @@ -502,6 +548,193 @@ static inline void local_sockets_index_listening_port(LS_STATE *ls, LOCAL_SOCKET } } +static inline bool local_sockets_add_socket(LS_STATE *ls, LOCAL_SOCKET *tmp) { + if(!tmp->inode) return false; + + SIMPLE_HASHTABLE_SLOT_LOCAL_SOCKET *sl = simple_hashtable_get_slot_LOCAL_SOCKET(&ls->sockets_hashtable, tmp->inode, &tmp->inode, true); + LOCAL_SOCKET *n = SIMPLE_HASHTABLE_SLOT_DATA(sl); + if(n) { + local_sockets_log(ls, "inode %" PRIu64" already exists in hashtable - ignoring duplicate", tmp->inode); + return false; + } + + n = (LOCAL_SOCKET *)callocz(1, sizeof(LOCAL_SOCKET)); + *n = *tmp; // copy all contents + + // fix the key + n->local_port_key.port = n->local.port; + n->local_port_key.family = n->local.family; + n->local_port_key.protocol = n->local.protocol; + n->local_port_key.net_ns_inode = ls->proc_self_net_ns_inode; + + n->local_ip_hash = XXH3_64bits(&n->local.ip, sizeof(n->local.ip)); + n->remote_ip_hash = XXH3_64bits(&n->remote.ip, sizeof(n->remote.ip)); + n->local_port_hash = XXH3_64bits(&n->local_port_key, sizeof(n->local_port_key)); + + // --- look up a pid for it ----------------------------------------------------------------------------------- + + SIMPLE_HASHTABLE_SLOT_PID_SOCKET *sl_pid = simple_hashtable_get_slot_PID_SOCKET(&ls->pid_sockets_hashtable, n->inode, &n->inode, false); + struct pid_socket *ps = SIMPLE_HASHTABLE_SLOT_DATA(sl_pid); + if(ps) { + n->net_ns_inode = ps->net_ns_inode; + n->pid = ps->pid; + + if(ps->uid != UID_UNSET && n->uid == UID_UNSET) + n->uid = ps->uid; + + if(ps->cmdline) + n->cmdline = strdupz(ps->cmdline); + strncpyz(n->comm, ps->comm, sizeof(n->comm) - 1); + } + + // --- index it ----------------------------------------------------------------------------------------------- + + simple_hashtable_set_slot_LOCAL_SOCKET(&ls->sockets_hashtable, sl, n->inode, n); + + if(!local_sockets_is_zero_address(&n->local)) { + // put all the local IPs into the local_ips hashtable + // so, we learn all local IPs the system has + + SIMPLE_HASHTABLE_SLOT_LOCAL_IP *sl_ip = + simple_hashtable_get_slot_LOCAL_IP(&ls->local_ips_hashtable, n->local_ip_hash, &n->local.ip, true); + + union ipv46 *ip = SIMPLE_HASHTABLE_SLOT_DATA(sl_ip); + if(!ip) + simple_hashtable_set_slot_LOCAL_IP(&ls->local_ips_hashtable, sl_ip, n->local_ip_hash, &n->local.ip); + } + + // --- 1st phase for direction detection ---------------------------------------------------------------------- + + if((n->local.protocol == IPPROTO_TCP && n->state == TCP_LISTEN) || + local_sockets_is_zero_address(&n->local) || + local_sockets_is_zero_address(&n->remote)) { + // the socket is either in a TCP LISTEN, or + // the remote address is zero + n->direction |= SOCKET_DIRECTION_LISTEN; + } + else if( + local_sockets_is_loopback_address(&n->local) || + local_sockets_is_loopback_address(&n->remote)) { + // the local IP address is loopback + n->direction |= SOCKET_DIRECTION_LOCAL; + } + else { + // we can't say yet if it is inbound or outboud + // so, mark it as both inbound and outbound + n->direction |= SOCKET_DIRECTION_INBOUND | SOCKET_DIRECTION_OUTBOUND; + } + + // --- index it in LISTENING_PORT ----------------------------------------------------------------------------- + + local_sockets_index_listening_port(ls, n); + + return true; +} + +#ifdef HAVE_LIBMNL + +static inline void local_sockets_netlink_init(LS_STATE *ls) { + ls->use_nl = true; + ls->nl = mnl_socket_open(NETLINK_INET_DIAG); + if (!ls->nl) { + local_sockets_log(ls, "cannot open netlink socket"); + ls->use_nl = false; + } + + if (mnl_socket_bind(ls->nl, 0, MNL_SOCKET_AUTOPID) < 0) { + local_sockets_log(ls, "cannot bind netlink socket"); + ls->use_nl = false; + } +} + +static inline void local_sockets_netlink_cleanup(LS_STATE *ls) { + if(ls->nl) { + mnl_socket_close(ls->nl); + ls->nl = NULL; + } +} + +static inline int local_sockets_netlink_cb_data(const struct nlmsghdr *nlh, void *data) { + LS_STATE *ls = data; + + struct inet_diag_msg *diag_msg = mnl_nlmsg_get_payload(nlh); + + LOCAL_SOCKET n = { + .inode = diag_msg->idiag_inode, + .direction = SOCKET_DIRECTION_NONE, + .state = diag_msg->idiag_state, + .local = { + .protocol = ls->tmp_protocol, + .family = diag_msg->idiag_family, + .port = diag_msg->id.idiag_sport, + }, + .remote = { + .protocol = ls->tmp_protocol, + .family = diag_msg->idiag_family, + .port = diag_msg->id.idiag_dport, + }, + .timer = diag_msg->idiag_timer, + .retransmits = diag_msg->idiag_retrans, + .expires = diag_msg->idiag_expires, + .rqueue = diag_msg->idiag_rqueue, + .wqueue = diag_msg->idiag_wqueue, + .uid = diag_msg->idiag_uid, + }; + + if (diag_msg->idiag_family == AF_INET) { + memcpy(&n.local.ip.ipv4, diag_msg->id.idiag_src, sizeof(n.local.ip.ipv4)); + memcpy(&n.remote.ip.ipv4, diag_msg->id.idiag_dst, sizeof(n.remote.ip.ipv4)); + } + else if (diag_msg->idiag_family == AF_INET6) { + memcpy(&n.local.ip.ipv6, diag_msg->id.idiag_src, sizeof(n.local.ip.ipv6)); + memcpy(&n.remote.ip.ipv6, diag_msg->id.idiag_dst, sizeof(n.remote.ip.ipv6)); + } + + local_sockets_add_socket(ls, &n); + + return MNL_CB_OK; +} + +static inline bool local_sockets_netlink_get_sockets(LS_STATE *ls, uint16_t family, uint16_t protocol) { + ls->tmp_protocol = protocol; + + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct inet_diag_req_v2 req; + unsigned int seq, portid = mnl_socket_get_portid(ls->nl); + + memset(&req, 0, sizeof(req)); + req.sdiag_family = family; + req.sdiag_protocol = protocol; + req.idiag_states = -1; + + nlh = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = SOCK_DIAG_BY_FAMILY; + nlh->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; + nlh->nlmsg_seq = seq = time(NULL); + mnl_nlmsg_put_extra_header(nlh, sizeof(req)); + memcpy(mnl_nlmsg_get_payload(nlh), &req, sizeof(req)); + + if (mnl_socket_sendto(ls->nl, nlh, nlh->nlmsg_len) < 0) { + local_sockets_log(ls, "mnl_socket_send failed"); + return false; + } + + ssize_t ret; + while ((ret = mnl_socket_recvfrom(ls->nl, buf, sizeof(buf))) > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, local_sockets_netlink_cb_data, ls); + if (ret <= MNL_CB_STOP) + break; + } + if (ret == -1) { + local_sockets_log(ls, "mnl_socket_recvfrom"); + return false; + } + + return true; +} +#endif // HAVE_LIBMNL + static inline bool local_sockets_read_proc_net_x(LS_STATE *ls, const char *filename, uint16_t family, uint16_t protocol) { if(family != AF_INET && family != AF_INET6) return false; @@ -545,109 +778,34 @@ static inline bool local_sockets_read_proc_net_x(LS_STATE *ls, const char *filen continue; } } - if(!inode) continue; - - SIMPLE_HASHTABLE_SLOT_LOCAL_SOCKET *sl = simple_hashtable_get_slot_LOCAL_SOCKET(&ls->sockets_hashtable, inode, &inode, true); - LOCAL_SOCKET *n = SIMPLE_HASHTABLE_SLOT_DATA(sl); - if(n) { - local_sockets_log( - ls, - "inode %" PRIu64 - " given on line %zu of filename '%s', already exists in hashtable - ignoring duplicate", - inode, - counter, - filename); - continue; - } - - // allocate a new socket and index it - n = (LOCAL_SOCKET *)callocz(1, sizeof(LOCAL_SOCKET)); - - // --- initialize it ------------------------------------------------------------------------------------------ + LOCAL_SOCKET n = { + .inode = inode, + .direction = SOCKET_DIRECTION_NONE, + .state = (int)state, + .local = { + .family = family, + .protocol = protocol, + .port = local_port, + }, + .remote = { + .family = family, + .protocol = protocol, + .port = remote_port, + }, + .uid = UID_UNSET, + }; if(family == AF_INET) { - n->local.ip.ipv4 = local_address; - n->remote.ip.ipv4 = remote_address; + n.local.ip.ipv4 = local_address; + n.remote.ip.ipv4 = remote_address; } else if(family == AF_INET6) { - ipv6_to_in6_addr(local_address6, &n->local.ip.ipv6); - ipv6_to_in6_addr(remote_address6, &n->remote.ip.ipv6); - } - - n->direction = 0; - n->state = (int)state; - n->inode = inode; - - n->local.family = family; - n->local.protocol = protocol; - n->local.port = local_port; - - n->remote.family = family; - n->remote.protocol = protocol; - n->remote.port = remote_port; - - n->local_port_key.port = n->local.port; - n->local_port_key.family = family; - n->local_port_key.protocol = protocol; - n->local_port_key.net_ns_inode = ls->proc_self_net_ns_inode; - - n->local_ip_hash = XXH3_64bits(&n->local.ip, sizeof(n->local.ip)); - n->remote_ip_hash = XXH3_64bits(&n->remote.ip, sizeof(n->remote.ip)); - n->local_port_hash = XXH3_64bits(&n->local_port_key, sizeof(n->local_port_key)); - - // --- look up a pid for it ----------------------------------------------------------------------------------- - - SIMPLE_HASHTABLE_SLOT_PID_SOCKET *sl_pid = simple_hashtable_get_slot_PID_SOCKET(&ls->pid_sockets_hashtable, inode, &inode, false); - struct pid_socket *ps = SIMPLE_HASHTABLE_SLOT_DATA(sl_pid); - if(ps) { - n->net_ns_inode = ps->net_ns_inode; - n->pid = ps->pid; - if(ps->cmdline) - n->cmdline = strdupz(ps->cmdline); - strncpyz(n->comm, ps->comm, sizeof(n->comm) - 1); - } - - // --- index it ----------------------------------------------------------------------------------------------- - - simple_hashtable_set_slot_LOCAL_SOCKET(&ls->sockets_hashtable, sl, inode, n); - - if(!local_sockets_is_zero_address(&n->local)) { - // put all the local IPs into the local_ips hashtable - // so, we learn all local IPs the system has - - SIMPLE_HASHTABLE_SLOT_LOCAL_IP *sl_ip = - simple_hashtable_get_slot_LOCAL_IP(&ls->local_ips_hashtable, n->local_ip_hash, &n->local.ip, true); - - union ipv46 *ip = SIMPLE_HASHTABLE_SLOT_DATA(sl_ip); - if(!ip) - simple_hashtable_set_slot_LOCAL_IP(&ls->local_ips_hashtable, sl_ip, n->local_ip_hash, &n->local.ip); - } - - // --- 1st phase for direction detection ---------------------------------------------------------------------- - - if((n->local.protocol == IPPROTO_TCP && n->state == TCP_LISTEN) || - local_sockets_is_zero_address(&n->local) || - local_sockets_is_zero_address(&n->remote)) { - // the socket is either in a TCP LISTEN, or - // the remote address is zero - n->direction |= SOCKET_DIRECTION_LISTEN; - } - else if( - local_sockets_is_loopback_address(&n->local) || - local_sockets_is_loopback_address(&n->remote)) { - // the local IP address is loopback - n->direction |= SOCKET_DIRECTION_LOCAL; - } - else { - // we can't say yet if it is inbound or outboud - // so, mark it as both inbound and outbound - n->direction |= SOCKET_DIRECTION_INBOUND | SOCKET_DIRECTION_OUTBOUND; + ipv6_to_in6_addr(local_address6, &n.local.ip.ipv6); + ipv6_to_in6_addr(remote_address6, &n.remote.ip.ipv6); } - // --- index it in LISTENING_PORT ----------------------------------------------------------------------------- - - local_sockets_index_listening_port(ls, n); + local_sockets_add_socket(ls, &n); } fclose(fp); @@ -744,6 +902,19 @@ static inline void local_sockets_cleanup(LS_STATE *ls) { // -------------------------------------------------------------------------------------------------------------------- +static inline void local_sockets_do_family_protocol(LS_STATE *ls, const char *filename, uint16_t family, uint16_t protocol) { +#ifdef HAVE_LIBMNL + if(ls->use_nl) { + ls->use_nl = local_sockets_netlink_get_sockets(ls, family, protocol); + + if(ls->use_nl) + return; + } +#endif + + local_sockets_read_proc_net_x(ls, filename, family, protocol); +} + static inline void local_sockets_read_sockets_from_proc(LS_STATE *ls) { char path[FILENAME_MAX + 1]; @@ -759,22 +930,22 @@ static inline void local_sockets_read_sockets_from_proc(LS_STATE *ls) { if(ls->config.tcp4) { snprintfz(path, sizeof(path), "%s/proc/net/tcp", ls->config.host_prefix); - local_sockets_read_proc_net_x(ls, path, AF_INET, IPPROTO_TCP); + local_sockets_do_family_protocol(ls, path, AF_INET, IPPROTO_TCP); } if(ls->config.udp4) { snprintfz(path, sizeof(path), "%s/proc/net/udp", ls->config.host_prefix); - local_sockets_read_proc_net_x(ls, path, AF_INET, IPPROTO_UDP); + local_sockets_do_family_protocol(ls, path, AF_INET, IPPROTO_UDP); } if(ls->config.tcp6) { snprintfz(path, sizeof(path), "%s/proc/net/tcp6", ls->config.host_prefix); - local_sockets_read_proc_net_x(ls, path, AF_INET6, IPPROTO_TCP); + local_sockets_do_family_protocol(ls, path, AF_INET6, IPPROTO_TCP); } if(ls->config.udp6) { snprintfz(path, sizeof(path), "%s/proc/net/udp6", ls->config.host_prefix); - local_sockets_read_proc_net_x(ls, path, AF_INET6, IPPROTO_UDP); + local_sockets_do_family_protocol(ls, path, AF_INET6, IPPROTO_UDP); } } @@ -865,6 +1036,11 @@ static inline bool local_sockets_get_namespace_sockets(LS_STATE *ls, struct pid_ exit(EXIT_FAILURE); } +#ifdef HAVE_LIBMNL + local_sockets_netlink_cleanup(ls); + local_sockets_netlink_init(ls); +#endif + // read all sockets from /proc local_sockets_read_sockets_from_proc(ls); @@ -877,6 +1053,10 @@ static inline bool local_sockets_get_namespace_sockets(LS_STATE *ls, struct pid_ }; local_sockets_send_to_parent(ls, &zero, &cw); +#ifdef HAVE_LIBMNL + local_sockets_netlink_cleanup(ls); +#endif + close(pipefd[1]); // Close write end of pipe exit(EXIT_SUCCESS); } @@ -985,6 +1165,11 @@ static inline void local_sockets_namespaces(LS_STATE *ls) { // -------------------------------------------------------------------------------------------------------------------- static inline void local_sockets_process(LS_STATE *ls) { + +#ifdef HAVE_LIBMNL + local_sockets_netlink_init(ls); +#endif + ls->config.host_prefix = netdata_configured_host_prefix; // initialize our hashtables @@ -1006,6 +1191,10 @@ static inline void local_sockets_process(LS_STATE *ls) { // free all memory local_sockets_cleanup(ls); + +#ifdef HAVE_LIBMNL + local_sockets_netlink_cleanup(ls); +#endif } static inline void ipv6_address_to_txt(struct in6_addr *in6_addr, char *dst) { diff --git a/collectors/plugins.d/local_listeners.c b/collectors/plugins.d/local_listeners.c index adebeb86fc..4591b42c5c 100644 --- a/collectors/plugins.d/local_listeners.c +++ b/collectors/plugins.d/local_listeners.c @@ -55,7 +55,7 @@ static void print_local_listeners_debug(LS_STATE *ls __maybe_unused, LOCAL_SOCKE ipv6_address_to_txt(&n->remote.ip.ipv6, remote_address); } - printf("%s, direction=%s%s%s%s%s pid=%d, state=0x%0x, ns=%"PRIu64", local=%s[:%u], remote=%s[:%u], comm=%s\n", + 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", protocol_name(n), (n->direction & SOCKET_DIRECTION_LISTEN) ? "LISTEN," : "", (n->direction & SOCKET_DIRECTION_INBOUND) ? "INBOUND," : "", @@ -67,12 +67,17 @@ static void print_local_listeners_debug(LS_STATE *ls __maybe_unused, LOCAL_SOCKE n->net_ns_inode, local_address, n-&g |