From 919dbe175e0d811a2091992dfc24d88b8e91a944 Mon Sep 17 00:00:00 2001 From: Costa Tsaousis Date: Tue, 30 Jan 2024 11:28:38 +0200 Subject: [PATCH] Network Viewer (local-sockets version) (#16872) * network viewer based on local-sockets * added more fields for the UI * added socket state * added inodes to the lists --- CMakeLists.txt | 21 +- .../network-viewer.plugin/network-viewer.c | 482 ++++++++++++++++++ collectors/plugins.d/local-sockets.h | 159 ++++-- collectors/plugins.d/local_listeners.c | 20 +- contrib/debian/netdata.postinst | 1 + contrib/debian/rules | 3 + netdata-installer.sh | 5 + netdata.spec.in | 3 + packaging/docker/Dockerfile | 1 + packaging/docker/README.md | 13 +- packaging/makeself/install-or-update.sh | 4 +- 11 files changed, 652 insertions(+), 60 deletions(-) create mode 100644 collectors/network-viewer.plugin/network-viewer.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f72d6e3da4a19..01eff0f8d3627b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,6 +102,7 @@ option(ENABLE_PLUGIN_CUPS "enable cups.plugin" True) option(ENABLE_PLUGIN_CGROUP_NETWORK "enable cgroup-network plugin" True) option(ENABLE_PLUGIN_EBPF "enable ebpf.plugin" True) option(ENABLE_PLUGIN_LOCAL_LISTENERS "enable local-listeners" True) +option(ENABLE_PLUGIN_NETWORK_VIEWER "enable network-viewer" True) option(ENABLE_PLUGIN_SYSTEMD_JOURNAL "enable systemd-journal.plugin" True) option(ENABLE_PLUGIN_LOGS_MANAGEMENT "enable logs-management.plugin" True) @@ -1947,8 +1948,10 @@ if(ENABLE_PLUGIN_EBPF) endif() if(ENABLE_PLUGIN_LOCAL_LISTENERS) - set(LOCAL_LISTENERS_FILES collectors/plugins.d/local_listeners.c - collectors/plugins.d/local-sockets.h) + set(LOCAL_LISTENERS_FILES + collectors/plugins.d/local_listeners.c + collectors/plugins.d/local-sockets.h + ) add_executable(local-listeners ${LOCAL_LISTENERS_FILES}) target_link_libraries(local-listeners libnetdata) @@ -1958,6 +1961,20 @@ if(ENABLE_PLUGIN_LOCAL_LISTENERS) DESTINATION usr/libexec/netdata/plugins.d) endif() +if(ENABLE_PLUGIN_NETWORK_VIEWER) + set(NETWORK_VIEWER_FILES + collectors/plugins.d/local-sockets.h + collectors/network-viewer.plugin/network-viewer.c + ) + + add_executable(network-viewer.plugin ${NETWORK_VIEWER_FILES}) + target_link_libraries(network-viewer.plugin libnetdata) + + install(TARGETS network-viewer.plugin + COMPONENT network_viewer_plugin + DESTINATION usr/libexec/netdata/plugins.d) +endif() + # # exporters # diff --git a/collectors/network-viewer.plugin/network-viewer.c b/collectors/network-viewer.plugin/network-viewer.c new file mode 100644 index 00000000000000..f2f07b552e4844 --- /dev/null +++ b/collectors/network-viewer.plugin/network-viewer.c @@ -0,0 +1,482 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "collectors/all.h" +#include "libnetdata/libnetdata.h" +#include "libnetdata/required_dummies.h" +#include "collectors/plugins.d/local-sockets.h" + +#define NETWORK_VIEWER_FUNCTION "network-viewer" +#define NETWORK_VIEWER_HELP "Network dependencies (outbound connections)" + +netdata_mutex_t stdout_mutex = NETDATA_MUTEX_INITIALIZER; +static bool plugin_should_exit = false; + +ENUM_STR_MAP_DEFINE(SOCKET_DIRECTION) = { + { .id = SOCKET_DIRECTION_LISTEN, .name = "listen" }, + { .id = SOCKET_DIRECTION_LOCAL, .name = "local" }, + { .id = SOCKET_DIRECTION_INBOUND, .name = "inbound" }, + { .id = SOCKET_DIRECTION_OUTBOUND, .name = "outbound" }, + + // terminator + { . id = 0, .name = NULL } +}; +ENUM_STR_DEFINE_FUNCTIONS(SOCKET_DIRECTION, SOCKET_DIRECTION_LISTEN, "unknown"); + +typedef int TCP_STATE; +ENUM_STR_MAP_DEFINE(TCP_STATE) = { + { .id = TCP_ESTABLISHED, .name = "established" }, + { .id = TCP_SYN_SENT, .name = "syn-sent" }, + { .id = TCP_SYN_RECV, .name = "syn-received" }, + { .id = TCP_FIN_WAIT1, .name = "fin1-wait1" }, + { .id = TCP_FIN_WAIT2, .name = "fin1-wait2" }, + { .id = TCP_TIME_WAIT, .name = "time-wait" }, + { .id = TCP_CLOSE, .name = "close" }, + { .id = TCP_CLOSE_WAIT, .name = "close-wait" }, + { .id = TCP_LAST_ACK, .name = "last-ack" }, + { .id = TCP_LISTEN, .name = "listen" }, + { .id = TCP_CLOSING, .name = "closing" }, + + // terminator + { . id = 0, .name = NULL } +}; +ENUM_STR_DEFINE_FUNCTIONS(TCP_STATE, 0, "unknown"); + + +static void local_socket_to_array(struct local_socket_state *ls, struct local_socket *n, void *data) { + BUFFER *wb = data; + + char local_address[INET6_ADDRSTRLEN]; + char remote_address[INET6_ADDRSTRLEN]; + char *protocol; + + if(n->local.family == AF_INET) { + ipv4_address_to_txt(n->local.ip.ipv4, local_address); + ipv4_address_to_txt(n->remote.ip.ipv4, remote_address); + protocol = n->local.protocol == IPPROTO_TCP ? "tcp4" : "udp4"; + } + else if(n->local.family == AF_INET6) { + ipv6_address_to_txt(&n->local.ip.ipv6, local_address); + ipv6_address_to_txt(&n->remote.ip.ipv6, remote_address); + protocol = n->local.protocol == IPPROTO_TCP ? "tcp6" : "udp6"; + } + else + return; + + const char *type; + if(n->net_ns_inode == ls->proc_self_net_ns_inode) + type = "system"; + else + type = "container"; + + buffer_json_add_array_item_array(wb); + { + buffer_json_add_array_item_string(wb, SOCKET_DIRECTION_2str(n->direction)); + buffer_json_add_array_item_string(wb, protocol); + buffer_json_add_array_item_string(wb, type); // system or container + if(n->local.protocol == IPPROTO_TCP) + buffer_json_add_array_item_string(wb, TCP_STATE_2str(n->state)); + else + buffer_json_add_array_item_string(wb, "stateless"); + buffer_json_add_array_item_uint64(wb, n->pid); + buffer_json_add_array_item_string(wb, n->comm); + buffer_json_add_array_item_string(wb, n->cmdline); + buffer_json_add_array_item_string(wb, local_address); + buffer_json_add_array_item_uint64(wb, n->local.port); + buffer_json_add_array_item_string(wb, local_sockets_address_space(&n->local)); + buffer_json_add_array_item_string(wb, remote_address); + buffer_json_add_array_item_uint64(wb, n->remote.port); + buffer_json_add_array_item_string(wb, local_sockets_address_space(&n->remote)); + buffer_json_add_array_item_uint64(wb, n->inode); + buffer_json_add_array_item_uint64(wb, n->net_ns_inode); + buffer_json_add_array_item_uint64(wb, 1); // count + } + buffer_json_array_close(wb); +} + +void network_viewer_function(const char *transaction, char *function __maybe_unused, usec_t *stop_monotonic_ut __maybe_unused, + bool *cancelled __maybe_unused, BUFFER *payload __maybe_unused, HTTP_ACCESS access __maybe_unused, + const char *source __maybe_unused, void *data __maybe_unused) { + + 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_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", 1); + buffer_json_member_add_string(wb, "help", NETWORK_VIEWER_HELP); + buffer_json_member_add_array(wb, "data"); + + LS_STATE ls = { + .config = { + .listening = true, + .inbound = true, + .outbound = true, + .local = true, + .tcp4 = true, + .tcp6 = true, + .udp4 = true, + .udp6 = true, + .pid = true, + .cmdline = true, + .comm = true, + .namespaces = true, + + .max_errors = 10, + + .cb = local_socket_to_array, + .data = wb, + }, + .stats = { 0 }, + .sockets_hashtable = { 0 }, + .local_ips_hashtable = { 0 }, + .listening_ports_hashtable = { 0 }, + }; + + local_sockets_process(&ls); + + buffer_json_array_close(wb); + buffer_json_member_add_object(wb, "columns"); + { + size_t field_id = 0; + + // Direction + buffer_rrdf_table_add_field(wb, field_id++, "Direction", "Socket Direction", + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT, + RRDF_FIELD_OPTS_VISIBLE, + NULL); + + // Protocol + buffer_rrdf_table_add_field(wb, field_id++, "Protocol", "Socket Protocol", + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT, + RRDF_FIELD_OPTS_VISIBLE, + NULL); + + // Type + buffer_rrdf_table_add_field(wb, field_id++, "Namespace", "Namespace", + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT, + RRDF_FIELD_OPTS_VISIBLE, + NULL); + + // State + buffer_rrdf_table_add_field(wb, field_id++, "State", "Socket State", + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT, + RRDF_FIELD_OPTS_VISIBLE, + NULL); + + // Pid + buffer_rrdf_table_add_field(wb, field_id++, "PID", "Process ID", + RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT, + RRDF_FIELD_OPTS_VISIBLE, + NULL); + + // Comm + buffer_rrdf_table_add_field(wb, field_id++, "Process", "Process Name", + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT, + RRDF_FIELD_OPTS_VISIBLE|RRDF_FIELD_OPTS_FULL_WIDTH, + NULL); + + // Cmdline + buffer_rrdf_table_add_field(wb, field_id++, "CommandLine", "Command Line", + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_NONE, + RRDF_FIELD_OPTS_NONE|RRDF_FIELD_OPTS_FULL_WIDTH, + NULL); + + // Local Address + buffer_rrdf_table_add_field(wb, field_id++, "LocalIP", "Local IP Address", + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT, + RRDF_FIELD_OPTS_VISIBLE|RRDF_FIELD_OPTS_FULL_WIDTH, + NULL); + + // Local Port + buffer_rrdf_table_add_field(wb, field_id++, "LocalPort", "Local Port", + RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT, + RRDF_FIELD_OPTS_VISIBLE, + NULL); + + // Local Address Space + buffer_rrdf_table_add_field(wb, field_id++, "LocalAddressSpace", "Local IP Address Space", + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT, + RRDF_FIELD_OPTS_NONE, + NULL); + + // Remote Address + buffer_rrdf_table_add_field(wb, field_id++, "RemoteIP", "Remote IP Address", + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT, + RRDF_FIELD_OPTS_VISIBLE|RRDF_FIELD_OPTS_FULL_WIDTH, + NULL); + + // Remote Port + buffer_rrdf_table_add_field(wb, field_id++, "RemotePort", "Remote Port", + RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT, + RRDF_FIELD_OPTS_VISIBLE, + NULL); + + // Remote Address Space + buffer_rrdf_table_add_field(wb, field_id++, "RemoteAddressSpace", "Remote IP Address Space", + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT, + RRDF_FIELD_OPTS_NONE, + NULL); + + // inode + buffer_rrdf_table_add_field(wb, field_id++, "Inode", "Socket Inode", + RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_NONE, + RRDF_FIELD_OPTS_NONE, + NULL); + + // Namespace inode + buffer_rrdf_table_add_field(wb, field_id++, "Namespace Inode", "Namespace Inode", + RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT, + RRDF_FIELD_OPTS_NONE, + NULL); + + // Count + buffer_rrdf_table_add_field(wb, field_id++, "Count", "Count", + RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_NONE, + RRDF_FIELD_OPTS_NONE, + NULL); + } + buffer_json_object_close(wb); // columns + buffer_json_member_add_string(wb, "default_sort_column", "Direction"); + + buffer_json_member_add_object(wb, "charts"); + { + // Data Collection Age chart + buffer_json_member_add_object(wb, "Count"); + { + buffer_json_member_add_string(wb, "name", "Connections"); + buffer_json_member_add_string(wb, "type", "stacked-bar"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "Direction"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + // Streaming Age chart + buffer_json_member_add_object(wb, "Count"); + { + buffer_json_member_add_string(wb, "name", "Connections"); + buffer_json_member_add_string(wb, "type", "stacked-bar"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "Process"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + // DB Duration + buffer_json_member_add_object(wb, "Count"); + { + buffer_json_member_add_string(wb, "name", "Connections"); + buffer_json_member_add_string(wb, "type", "stacked-bar"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "Protocol"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + } + buffer_json_object_close(wb); // charts + + buffer_json_member_add_array(wb, "default_charts"); + { + buffer_json_add_array_item_array(wb); + buffer_json_add_array_item_string(wb, "Count"); + buffer_json_add_array_item_string(wb, "Direction"); + buffer_json_array_close(wb); + + buffer_json_add_array_item_array(wb); + buffer_json_add_array_item_string(wb, "Count"); + buffer_json_add_array_item_string(wb, "Process"); + buffer_json_array_close(wb); + } + buffer_json_array_close(wb); + + buffer_json_member_add_object(wb, "group_by"); + { + buffer_json_member_add_object(wb, "Direction"); + { + buffer_json_member_add_string(wb, "name", "Direction"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "Direction"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + buffer_json_member_add_object(wb, "Protocol"); + { + buffer_json_member_add_string(wb, "name", "Protocol"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "Protocol"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + buffer_json_member_add_object(wb, "Namespace"); + { + buffer_json_member_add_string(wb, "name", "Namespace"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "Namespace"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + buffer_json_member_add_object(wb, "Process"); + { + buffer_json_member_add_string(wb, "name", "Process"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "Process"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + buffer_json_member_add_object(wb, "LocalIP"); + { + buffer_json_member_add_string(wb, "name", "Local IP"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "LocalIP"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + buffer_json_member_add_object(wb, "LocalPort"); + { + buffer_json_member_add_string(wb, "name", "Local Port"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "LocalPort"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + buffer_json_member_add_object(wb, "RemoteIP"); + { + buffer_json_member_add_string(wb, "name", "Remote IP"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "RemoteIP"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + buffer_json_member_add_object(wb, "RemotePort"); + { + buffer_json_member_add_string(wb, "name", "Remote Port"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "RemotePort"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + } + buffer_json_object_close(wb); // group_by + + + buffer_json_member_add_time_t(wb, "expires", now_realtime_sec() + 1); + buffer_json_finalize(wb); + + netdata_mutex_lock(&stdout_mutex); + pluginsd_function_result_to_stdout(transaction, HTTP_RESP_OK, "application/json", now_realtime_sec(), wb); + netdata_mutex_unlock(&stdout_mutex); +} + +// ---------------------------------------------------------------------------------------------------------------- +// main + +int main(int argc __maybe_unused, char **argv __maybe_unused) { + clocks_init(); + netdata_thread_set_tag("NETWORK-VIEWER"); + nd_log_initialize_for_external_plugins("network-viewer.plugin"); + + netdata_configured_host_prefix = getenv("NETDATA_HOST_PREFIX"); + if(verify_netdata_host_prefix(true) == -1) exit(1); + + // ---------------------------------------------------------------------------------------------------------------- + + fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"top\" "HTTP_ACCESS_FORMAT" %d\n", + NETWORK_VIEWER_FUNCTION, 60, NETWORK_VIEWER_HELP, + (HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA), + RRDFUNCTIONS_PRIORITY_DEFAULT); + + // ---------------------------------------------------------------------------------------------------------------- + + struct functions_evloop_globals *wg = + functions_evloop_init(5, "Network-Viewer", &stdout_mutex, &plugin_should_exit); + + functions_evloop_add_function(wg, + NETWORK_VIEWER_FUNCTION, + network_viewer_function, + PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT, + NULL); + + // ---------------------------------------------------------------------------------------------------------------- + + usec_t step_ut = 100 * USEC_PER_MS; + usec_t send_newline_ut = 0; + bool tty = isatty(fileno(stdout)) == 1; + + heartbeat_t hb; + heartbeat_init(&hb); + while(!plugin_should_exit) { + + usec_t dt_ut = heartbeat_next(&hb, step_ut); + send_newline_ut += dt_ut; + + if(!tty && send_newline_ut > USEC_PER_SEC) { + send_newline_and_flush(&stdout_mutex); + send_newline_ut = 0; + } + } + + return 0; +} diff --git a/collectors/plugins.d/local-sockets.h b/collectors/plugins.d/local-sockets.h index 55f65719e975c6..b51c2ca854a16c 100644 --- a/collectors/plugins.d/local-sockets.h +++ b/collectors/plugins.d/local-sockets.h @@ -96,6 +96,7 @@ typedef struct local_socket_state { // -------------------------------------------------------------------------------------------------------------------- typedef enum __attribute__((packed)) { + SOCKET_DIRECTION_NONE = 0, SOCKET_DIRECTION_LISTEN = (1 << 0), // a listening socket SOCKET_DIRECTION_INBOUND = (1 << 1), // an inbound socket connecting a remote system to a local listening socket SOCKET_DIRECTION_OUTBOUND = (1 << 2), // a socket initiated by this system, connecting to another system @@ -114,11 +115,6 @@ struct pid_socket { char comm[TASK_COMM_LEN]; }; -union ipv46 { - uint32_t ipv4; - struct in6_addr ipv6; -}; - struct local_port { uint16_t protocol; uint16_t family; @@ -126,7 +122,14 @@ struct local_port { uint64_t net_ns_inode; }; +union ipv46 { + uint32_t ipv4; + struct in6_addr ipv6; +}; + struct socket_endpoint { + uint16_t protocol; + uint16_t family; uint16_t port; union ipv46 ip; }; @@ -145,8 +148,6 @@ typedef struct local_socket { uint64_t inode; uint64_t net_ns_inode; - uint16_t protocol; - uint16_t family; int state; struct socket_endpoint local; struct socket_endpoint remote; @@ -218,7 +219,7 @@ static inline void local_sockets_fix_cmdline(char* str) { } } -// ---------------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- static inline bool local_sockets_read_proc_inode_link(LS_STATE *ls, const char *filename, uint64_t *inode, const char *type) { @@ -368,7 +369,7 @@ static inline bool local_sockets_find_all_sockets_in_proc(LS_STATE *ls, const ch return true; } -// ---------------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- static bool local_sockets_is_ipv4_mapped_ipv6_address(const struct in6_addr *addr) { // An IPv4-mapped IPv6 address starts with 80 bits of zeros followed by 16 bits of ones @@ -376,42 +377,117 @@ static bool local_sockets_is_ipv4_mapped_ipv6_address(const struct in6_addr *add return memcmp(addr->s6_addr, ipv4_mapped_prefix, 12) == 0; } -static bool local_sockets_is_loopback_address(const void *ip, uint16_t family) { - if (family == AF_INET) { +static bool local_sockets_is_loopback_address(struct socket_endpoint *se) { + if (se->family == AF_INET) { // For IPv4, loopback addresses are in the 127.0.0.0/8 range - const uint32_t addr = ntohl(*((const uint32_t *)ip)); // Convert to host byte order for comparison - return (addr >> 24) == 127; // Check if the first byte is 127 - } else if (family == AF_INET6) { + return (ntohl(se->ip.ipv4) >> 24) == 127; // Check if the first byte is 127 + } else if (se->family == AF_INET6) { // Check if the address is an IPv4-mapped IPv6 address - const struct in6_addr *ipv6_addr = (const struct in6_addr *)ip; - if (local_sockets_is_ipv4_mapped_ipv6_address(ipv6_addr)) { + if (local_sockets_is_ipv4_mapped_ipv6_address(&se->ip.ipv6)) { // Extract the last 32 bits (IPv4 address) and check if it's in the 127.0.0.0/8 range - const uint32_t ipv4_addr = ntohl(*((const uint32_t *)(ipv6_addr->s6_addr + 12))); - return (ipv4_addr >> 24) == 127; + uint8_t *ip6 = (uint8_t *)&se->ip.ipv6; + const uint32_t ipv4_addr = *((const uint32_t *)(ip6 + 12)); + return (ntohl(ipv4_addr) >> 24) == 127; } // For IPv6, loopback address is ::1 - const struct in6_addr loopback_ipv6 = IN6ADDR_LOOPBACK_INIT; - return memcmp(ipv6_addr, &loopback_ipv6, sizeof(struct in6_addr)) == 0; + return memcmp(&se->ip.ipv6, &in6addr_loopback, sizeof(se->ip.ipv6)) == 0; } return false; } -static bool local_sockets_is_zero_address(const void *ip, uint16_t family) { - if (family == AF_INET) { - // For IPv4, check if the address is not 0.0.0.0 - const uint32_t zero_ipv4 = 0; // Zero address in network byte order - return memcmp(ip, &zero_ipv4, sizeof(uint32_t)) == 0; - } else if (family == AF_INET6) { - // For IPv6, check if the address is not :: - const struct in6_addr zero_ipv6 = IN6ADDR_ANY_INIT; - return memcmp(ip, &zero_ipv6, sizeof(struct in6_addr)) == 0; +static inline bool local_sockets_is_ipv4_reserved_address(uint32_t ip) { + // Check for the reserved address ranges + ip = ntohl(ip); + return ( + (ip >> 24 == 10) || // Private IP range (A class) + (ip >> 20 == (172 << 4) + 1) || // Private IP range (B class) + (ip >> 16 == (192 << 8) + 168) || // Private IP range (C class) + (ip >> 24 == 127) || // Loopback address (127.0.0.0) + (ip >> 24 == 0) || // Reserved (0.0.0.0) + (ip >> 24 == 169 && (ip >> 16) == 254) || // Link-local address (169.254.0.0) + (ip >> 16 == (192 << 8) + 0) // Test-Net (192.0.0.0) + ); +} + +static inline bool local_sockets_is_private_address(struct socket_endpoint *se) { + if (se->family == AF_INET) { + return local_sockets_is_ipv4_reserved_address(se->ip.ipv4); + } + else if (se->family == AF_INET6) { + uint8_t *ip6 = (uint8_t *)&se->ip.ipv6; + + // Check if the address is an IPv4-mapped IPv6 address + if (local_sockets_is_ipv4_mapped_ipv6_address(&se->ip.ipv6)) { + // Extract the last 32 bits (IPv4 address) and check if it's in the 127.0.0.0/8 range + const uint32_t ipv4_addr = *((const uint32_t *)(ip6 + 12)); + return local_sockets_is_ipv4_reserved_address(ipv4_addr); + } + + // Check for link-local addresses (fe80::/10) + if ((ip6[0] == 0xFE) && ((ip6[1] & 0xC0) == 0x80)) + return true; + + // Check for Unique Local Addresses (ULA) (fc00::/7) + if ((ip6[0] & 0xFE) == 0xFC) + return true; + + // Check for multicast addresses (ff00::/8) + if (ip6[0] == 0xFF) + return true; + + // For IPv6, loopback address is :: or ::1 + return memcmp(&se->ip.ipv6, &in6addr_any, sizeof(se->ip.ipv6)) == 0 || + memcmp(&se->ip.ipv6, &in6addr_loopback, sizeof(se->ip.ipv6)) == 0; + } + + return false; +} + +static bool local_sockets_is_multicast_address(struct socket_endpoint *se) { + if (se->family == AF_INET) { + // For IPv4, check if the address is 0.0.0.0 + uint32_t ip = htonl(se->ip.ipv4); + return (ip >= 0xE0000000 && ip <= 0xEFFFFFFF); // Multicast address range (224.0.0.0/4) + } + else if (se->family == AF_INET6) { + // For IPv6, check if the address is ff00::/8 + uint8_t *ip6 = (uint8_t *)&se->ip.ipv6; + return ip6[0] == 0xff; + } + + return false; +} + +static bool local_sockets_is_zero_address(struct socket_endpoint *se) { + if (se->family == AF_INET) { + // For IPv4, check if the address is 0.0.0.0 + return se->ip.ipv4 == 0; + } + else if (se->family == AF_INET6) { + // For IPv6, check if the address is :: + return memcmp(&se->ip.ipv6, &in6addr_any, sizeof(se->ip.ipv6)) == 0; } return false; } +static inline const char *local_sockets_address_space(struct socket_endpoint *se) { + if(local_sockets_is_zero_address(se)) + return "zero"; + else if(local_sockets_is_loopback_address(se)) + return "loopback"; + else if(local_sockets_is_multicast_address(se)) + return "multicast"; + else if(local_sockets_is_private_address(se)) + return "private"; + else + return "public"; +} + +// -------------------------------------------------------------------------------------------------------------------- + static inline void local_sockets_index_listening_port(LS_STATE *ls, LOCAL_SOCKET *n) { if(n->direction & SOCKET_DIRECTION_LISTEN) { // for the listening sockets, keep a hashtable with all the local ports @@ -500,17 +576,20 @@ static inline bool local_sockets_read_proc_net_x(LS_STATE *ls, const char *filen } n->direction = 0; - n->protocol = protocol; - n->family = family; 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->protocol = protocol; n->local_port_key.port = n->local.port; - n->local_port_key.family = n->family; - n->local_port_key.protocol = n->protocol; + 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)); @@ -533,7 +612,7 @@ static inline bool local_sockets_read_proc_net_x(LS_STATE *ls, const char *filen simple_hashtable_set_slot_LOCAL_SOCKET(&ls->sockets_hashtable, sl, inode, n); - if(!local_sockets_is_zero_address(&n->local.ip, n->family)) { + 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 @@ -547,16 +626,16 @@ static inline bool local_sockets_read_proc_net_x(LS_STATE *ls, const char *filen // --- 1st phase for direction detection ---------------------------------------------------------------------- - if((n->protocol == IPPROTO_TCP && n->state == TCP_LISTEN) || - local_sockets_is_zero_address(&n->local.ip, n->family) || - local_sockets_is_zero_address(&n->remote.ip, n->family)) { + 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.ip, n->family) || - local_sockets_is_loopback_address(&n->remote.ip, n->family)) { + 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; } diff --git a/collectors/plugins.d/local_listeners.c b/collectors/plugins.d/local_listeners.c index 8a07eb1a190814..adebeb86fcdb57 100644 --- a/collectors/plugins.d/local_listeners.c +++ b/collectors/plugins.d/local_listeners.c @@ -6,18 +6,18 @@ // -------------------------------------------------------------------------------------------------------------------- static const char *protocol_name(LOCAL_SOCKET *n) { - if(n->family == AF_INET) { - if(n->protocol == IPPROTO_TCP) + if(n->local.family == AF_INET) { + if(n->local.protocol == IPPROTO_TCP) return "TCP"; - else if(n->protocol == IPPROTO_UDP) + else if(n->local.protocol == IPPROTO_UDP) return "UDP"; else return "UNKNOWN_IPV4"; } - else if(n->family == AF_INET6) { - if (n->protocol == IPPROTO_TCP) + else if(n->local.family == AF_INET6) { + if (n->local.protocol == IPPROTO_TCP) return "TCP6"; - else if(n->protocol == IPPROTO_UDP) + else if(n->local.protocol == IPPROTO_UDP) return "UDP6"; else return "UNKNOWN_IPV6"; @@ -30,11 +30,11 @@ static void print_local_listeners(LS_STATE *ls __maybe_unused, LOCAL_SOCKET *n, char local_address[INET6_ADDRSTRLEN]; char remote_address[INET6_ADDRSTRLEN]; - if(n->family == AF_INET) { + if(n->local.family == AF_INET) { ipv4_address_to_txt(n->local.ip.ipv4, local_address); ipv4_address_to_txt(n->remote.ip.ipv4, remote_address); } - else if(n->family == AF_INET6) { + else if(n->local.family == AF_INET6) { ipv6_address_to_txt(&n->local.ip.ipv6, local_address); ipv6_address_to_txt(&n->remote.ip.ipv6, remote_address); } @@ -46,11 +46,11 @@ static void print_local_listeners_debug(LS_STATE *ls __maybe_unused, LOCAL_SOCKE char local_address[INET6_ADDRSTRLEN]; char remote_address[INET6_ADDRSTRLEN]; - if(n->family == AF_INET) { + if(n->local.family == AF_INET) { ipv4_address_to_txt(n->local.ip.ipv4, local_address); ipv4_address_to_txt(n->remote.ip.ipv4, remote_address); } - else if(n->family == AF_INET6) { + else if(n->local.family == AF_INET6) { ipv6_address_to_txt(&n->local.ip.ipv6, local_address); ipv6_address_to_txt(&n->remote.ip.ipv6, remote_address); } diff --git a/contrib/debian/netdata.postinst b/contrib/debian/netdata.postinst index ad4971950c89ab..2913c33543ba06 100644 --- a/contrib/debian/netdata.postinst +++ b/contrib/debian/netdata.postinst @@ -44,6 +44,7 @@ case "$1" in chmod 4750 /usr/libexec/netdata/plugins.d/ndsudo chmod 4750 /usr/libexec/netdata/plugins.d/cgroup-network chmod 4750 /usr/libexec/netdata/plugins.d/local-listeners + chmod 4750 /usr/libexec/netdata/plugins.d/network-viewer.plugin # Workaround for other plugins not installed directly by this package chmod -f 4750 /usr/libexec/netdata/plugins.d/ioping || true diff --git a/contrib/debian/rules b/contrib/debian/rules index a80e774ef3eed1..3468e1a7bf86d4 100755 --- a/contrib/debian/rules +++ b/contrib/debian/rules @@ -274,6 +274,9 @@ override_dh_fixperms: # local-listeners chmod 4750 $(TOP)/usr/libexec/netdata/plugins.d/local-listeners + # network-viewer + chmod 4750 $(TOP)/usr/libexec/netdata/plugins.d/network-viewer.plugin + # systemd-journal chmod 4750 $(TOP)-plugin-systemd-journal/usr/libexec/netdata/plugins.d/systemd-journal.plugin diff --git a/netdata-installer.sh b/netdata-installer.sh index 5bbfafebbb1e17..fceb13f826034d 100755 --- a/netdata-installer.sh +++ b/netdata-installer.sh @@ -1416,6 +1416,11 @@ if [ "$(id -u)" -eq 0 ]; then run chmod 4750 "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/local-listeners" fi + if [ -f "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/network-viewer.plugin" ]; then + run chown "root:${NETDATA_GROUP}" "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/network-viewer.plugin" + run chmod 4750 "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/network-viewer.plugin" + fi + if [ -f "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/ndsudo" ]; then run chown "root:${NETDATA_GROUP}" "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/ndsudo" run chmod 4750 "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/ndsudo" diff --git a/netdata.spec.in b/netdata.spec.in index 82c4eceb46ab8a..e1a2d2acd4b970 100644 --- a/netdata.spec.in +++ b/netdata.spec.in @@ -719,6 +719,9 @@ rm -rf "${RPM_BUILD_ROOT}" # local-listeners detects the local processes that are listening for connections %attr(4750,root,netdata) %{_libexecdir}/%{name}/plugins.d/local-listeners +# network-viewer.plugin, detects all system sockets and classifies them +%attr(4750,root,netdata) %{_libexecdir}/%{name}/plugins.d/network-viewer.plugin + # ndsudo a helper to run privileged commands %attr(4750,root,netdata) %{_libexecdir}/%{name}/plugins.d/ndsudo diff --git a/packaging/docker/Dockerfile b/packaging/docker/Dockerfile index f659dfab66fc55..b12af313d74a32 100644 --- a/packaging/docker/Dockerfile +++ b/packaging/docker/Dockerfile @@ -124,6 +124,7 @@ RUN addgroup --gid ${NETDATA_GID} --system "${DOCKER_GRP}" && \ perf.plugin \ ndsudo \ slabinfo.plugin \ + network-viewer.plugin \ systemd-journal.plugin; do \ [ -f "/usr/libexec/netdata/plugins.d/$name" ] && chmod 4755 "/usr/libexec/netdata/plugins.d/$name"; \ done && \ diff --git a/packaging/docker/README.md b/packaging/docker/README.md index e512856e3bc337..1689cabb530a51 100644 --- a/packaging/docker/README.md +++ b/packaging/docker/README.md @@ -34,12 +34,13 @@ along with their descriptions.
Privileges -| Component | Privileges | Description | -|:---------------:|:-----------------------------:|--------------------------------------------------------------------------------------------------------------------------| -| cgroups.plugin | host PID mode, SYS_ADMIN | Container network interfaces monitoring. Map virtual interfaces in the system namespace to interfaces inside containers. | -| proc.plugin | host network mode | Host system networking stack monitoring. | -| go.d.plugin | host network mode | Monitoring applications running on the host and inside containers. | -| local-listeners | host network mode, SYS_PTRACE | Discovering local services/applications. Map open (listening) ports to running services/applications. | +| Component | Privileges | Description | +|:---------------------:|:-----------------------------:|--------------------------------------------------------------------------------------------------------------------------| +| cgroups.plugin | host PID mode, SYS_ADMIN | Container network interfaces monitoring. Map virtual interfaces in the system namespace to interfaces inside containers. | +| proc.plugin | host network mode | Host system networking stack monitoring. | +| go.d.plugin | host network mode | Monitoring applications running on the host and inside containers. | +| local-listeners | host network mode, SYS_PTRACE | Discovering local services/applications. Map open (listening) ports to running services/applications. | +| network-viewer.plugin | host network mode, SYS_ADMIN | Discovering all current network sockets and building a network-map. |
diff --git a/packaging/makeself/install-or-update.sh b/packaging/makeself/install-or-update.sh index b5b46e2b6145ac..964d2aa5d33c26 100755 --- a/packaging/makeself/install-or-update.sh +++ b/packaging/makeself/install-or-update.sh @@ -172,7 +172,7 @@ fi progress "changing plugins ownership and permissions" -for x in ndsudo apps.plugin perf.plugin slabinfo.plugin debugfs.plugin freeipmi.plugin ioping cgroup-network local-listeners ebpf.plugin nfacct.plugin xenstat.plugin python.d.plugin charts.d.plugin go.d.plugin ioping.plugin cgroup-network-helper.sh; do +for x in ndsudo apps.plugin perf.plugin slabinfo.plugin debugfs.plugin freeipmi.plugin ioping cgroup-network local-listeners network-viewer.plugin ebpf.plugin nfacct.plugin xenstat.plugin python.d.plugin charts.d.plugin go.d.plugin ioping.plugin cgroup-network-helper.sh; do f="usr/libexec/netdata/plugins.d/${x}" if [ -f "${f}" ]; then run chown root:${NETDATA_GROUP} "${f}" @@ -198,7 +198,7 @@ else done fi -for x in freeipmi.plugin ioping cgroup-network local-listeners ebpf.plugin nfacct.plugin xenstat.plugin; do +for x in freeipmi.plugin ioping cgroup-network local-listeners network-viewer.plugin ebpf.plugin nfacct.plugin xenstat.plugin; do f="usr/libexec/netdata/plugins.d/${x}" if [ -f "${f}" ]; then