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 == 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 8a07eb1a19..adebeb86fc 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 ad4971950c..2913c33543 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 a80e774ef3..3468e1a7bf 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 5bbfafebbb..fceb13f826 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 82c4eceb46..e1a2d2acd4 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 f659dfab66..b12af313d7 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 e512856e3b..1689cabb53 100644
--- a/packaging/docker/README.md
+++ b/packaging/docker/README.md
@@ -34,12 +34,13 @@ along with their descriptions.
 <details open>
 <summary>Privileges</summary>
 
-|    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.                                                      |
 
 </details>
 
diff --git a/packaging/makeself/install-or-update.sh b/packaging/makeself/install-or-update.sh
index b5b46e2b61..964d2aa5d3 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