summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCosta Tsaousis <costa@netdata.cloud>2024-01-30 11:28:38 +0200
committerGitHub <noreply@github.com>2024-01-30 11:28:38 +0200
commit919dbe175e0d811a2091992dfc24d88b8e91a944 (patch)
treea99f81fe7c4c6f75aa3ad56b63540c0bc34e17bb
parent5124aefc2b78ddc227c9117f092e4ec1a805ca9b (diff)
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
-rw-r--r--CMakeLists.txt21
-rw-r--r--collectors/network-viewer.plugin/network-viewer.c482
-rw-r--r--collectors/plugins.d/local-sockets.h159
-rw-r--r--collectors/plugins.d/local_listeners.c20
-rw-r--r--contrib/debian/netdata.postinst1
-rwxr-xr-xcontrib/debian/rules3
-rwxr-xr-xnetdata-installer.sh5
-rw-r--r--netdata.spec.in3
-rw-r--r--packaging/docker/Dockerfile1
-rw-r--r--packaging/docker/README.md13
-rwxr-xr-xpackaging/makeself/install-or-update.sh4
11 files changed, 652 insertions, 60 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7f72d6e3da..01eff0f8d3 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 0000000000..f2f07b552e
--- /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 55f65719e9..b51c2ca854 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 ==