summaryrefslogtreecommitdiffstats
path: root/src/collectors/network-viewer.plugin/network-viewer.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/collectors/network-viewer.plugin/network-viewer.c')
-rw-r--r--src/collectors/network-viewer.plugin/network-viewer.c803
1 files changed, 803 insertions, 0 deletions
diff --git a/src/collectors/network-viewer.plugin/network-viewer.c b/src/collectors/network-viewer.plugin/network-viewer.c
new file mode 100644
index 000000000..764151f5c
--- /dev/null
+++ b/src/collectors/network-viewer.plugin/network-viewer.c
@@ -0,0 +1,803 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "collectors/all.h"
+#include "libnetdata/libnetdata.h"
+#include "libnetdata/required_dummies.h"
+
+#define ENABLE_DETAILED_VIEW
+
+#define LOCAL_SOCKETS_EXTENDED_MEMBERS struct { \
+ size_t count; \
+ const char *local_address_space; \
+ const char *remote_address_space; \
+ } network_viewer;
+
+#include "libnetdata/maps/local-sockets.h"
+#include "libnetdata/maps/system-users.h"
+
+#define NETWORK_CONNECTIONS_VIEWER_FUNCTION "network-connections"
+#define NETWORK_CONNECTIONS_VIEWER_HELP "Network connections explorer"
+
+#define SIMPLE_HASHTABLE_VALUE_TYPE LOCAL_SOCKET
+#define SIMPLE_HASHTABLE_NAME _AGGREGATED_SOCKETS
+#include "libnetdata/simple_hashtable.h"
+
+netdata_mutex_t stdout_mutex = NETDATA_MUTEX_INITIALIZER;
+static bool plugin_should_exit = false;
+static USERNAMES_CACHE *uc;
+
+ENUM_STR_MAP_DEFINE(SOCKET_DIRECTION) = {
+ { .id = SOCKET_DIRECTION_LISTEN, .name = "listen" },
+ { .id = SOCKET_DIRECTION_LOCAL_INBOUND, .name = "local" },
+ { .id = SOCKET_DIRECTION_LOCAL_OUTBOUND, .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_json_array(BUFFER *wb, LOCAL_SOCKET *n, uint64_t proc_self_net_ns_inode, bool aggregated) {
+ 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 == proc_self_net_ns_inode)
+ type = "system";
+ else if(n->net_ns_inode == 0)
+ type = "[unknown]";
+ 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);
+
+ if(!n->comm[0])
+ buffer_json_add_array_item_string(wb, "[unknown]");
+ else
+ buffer_json_add_array_item_string(wb, n->comm);
+
+ // buffer_json_add_array_item_string(wb, string2str(n->cmdline));
+
+ if(n->uid == UID_UNSET) {
+ // buffer_json_add_array_item_uint64(wb, n->uid);
+ buffer_json_add_array_item_string(wb, "[unknown]");
+ }
+ else {
+ // buffer_json_add_array_item_uint64(wb, n->uid);
+ STRING *u = system_usernames_cache_lookup_uid(uc, n->uid);
+ buffer_json_add_array_item_string(wb, string2str(u));
+ string_freez(u);
+ }
+
+ if(!aggregated) {
+ 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, n->network_viewer.local_address_space);
+
+ if(!aggregated) {
+ 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, n->network_viewer.remote_address_space);
+
+ uint16_t server_port = 0;
+ const char *server_address = NULL;
+ const char *client_address_space = NULL;
+ const char *server_address_space = NULL;
+ switch (n->direction) {
+ case SOCKET_DIRECTION_LISTEN:
+ case SOCKET_DIRECTION_INBOUND:
+ case SOCKET_DIRECTION_LOCAL_INBOUND:
+ server_port = n->local.port;
+ server_address = local_address;
+ server_address_space = n->network_viewer.local_address_space;
+ client_address_space = n->network_viewer.remote_address_space;
+ break;
+
+ case SOCKET_DIRECTION_OUTBOUND:
+ case SOCKET_DIRECTION_LOCAL_OUTBOUND:
+ server_port = n->remote.port;
+ server_address = remote_address;
+ server_address_space = n->network_viewer.remote_address_space;
+ client_address_space = n->network_viewer.local_address_space;
+ break;
+
+ case SOCKET_DIRECTION_NONE:
+ break;
+ }
+ if(aggregated)
+ buffer_json_add_array_item_string(wb, server_address);
+
+ buffer_json_add_array_item_uint64(wb, server_port);
+
+ if(aggregated) {
+ buffer_json_add_array_item_string(wb, client_address_space);
+ buffer_json_add_array_item_string(wb, server_address_space);
+ }
+
+ // 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, n->network_viewer.count);
+ }
+ buffer_json_array_close(wb);
+}
+
+static void local_sockets_cb_to_json(LS_STATE *ls, LOCAL_SOCKET *n, void *data) {
+ n->network_viewer.count = 1;
+ n->network_viewer.local_address_space = local_sockets_address_space(&n->local);
+ n->network_viewer.remote_address_space = local_sockets_address_space(&n->remote);
+ local_socket_to_json_array(data, n, ls->proc_self_net_ns_inode, false);
+}
+
+static void local_sockets_cb_to_aggregation(LS_STATE *ls __maybe_unused, LOCAL_SOCKET *n, void *data) {
+ SIMPLE_HASHTABLE_AGGREGATED_SOCKETS *ht = data;
+ n->network_viewer.count = 1;
+ n->network_viewer.local_address_space = local_sockets_address_space(&n->local);
+ n->network_viewer.remote_address_space = local_sockets_address_space(&n->remote);
+
+ switch(n->direction) {
+ case SOCKET_DIRECTION_INBOUND:
+ case SOCKET_DIRECTION_LOCAL_INBOUND:
+ case SOCKET_DIRECTION_LISTEN:
+ memset(&n->remote.ip, 0, sizeof(n->remote.ip));
+ n->remote.port = 0;
+ break;
+
+ case SOCKET_DIRECTION_OUTBOUND:
+ case SOCKET_DIRECTION_LOCAL_OUTBOUND:
+ memset(&n->local.ip, 0, sizeof(n->local.ip));
+ n->local.port = 0;
+ break;
+
+ case SOCKET_DIRECTION_NONE:
+ return;
+ }
+
+ n->inode = 0;
+ n->local_ip_hash = 0;
+ n->remote_ip_hash = 0;
+ n->local_port_hash = 0;
+ n->timer = 0;
+ n->retransmits = 0;
+ n->expires = 0;
+ n->rqueue = 0;
+ n->wqueue = 0;
+ memset(&n->local_port_key, 0, sizeof(n->local_port_key));
+
+ XXH64_hash_t hash = XXH3_64bits(n, sizeof(*n));
+ SIMPLE_HASHTABLE_SLOT_AGGREGATED_SOCKETS *sl = simple_hashtable_get_slot_AGGREGATED_SOCKETS(ht, hash, n, true);
+ LOCAL_SOCKET *t = SIMPLE_HASHTABLE_SLOT_DATA(sl);
+ if(t) {
+ t->network_viewer.count++;
+ }
+ else {
+ t = mallocz(sizeof(*t));
+ memcpy(t, n, sizeof(*t));
+ t->cmdline = string_dup(t->cmdline);
+ simple_hashtable_set_slot_AGGREGATED_SOCKETS(ht, sl, hash, t);
+ }
+}
+
+static int local_sockets_compar(const void *a, const void *b) {
+ LOCAL_SOCKET *n1 = *(LOCAL_SOCKET **)a, *n2 = *(LOCAL_SOCKET **)b;
+ return strcmp(n1->comm, n2->comm);
+}
+
+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) {
+
+ time_t now_s = now_realtime_sec();
+ bool aggregated = false;
+
+ 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_MINIFY);
+
+ buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
+ buffer_json_member_add_string(wb, "type", "table");
+ buffer_json_member_add_time_t(wb, "update_every", 5);
+ buffer_json_member_add_boolean(wb, "has_history", false);
+ buffer_json_member_add_string(wb, "help", NETWORK_CONNECTIONS_VIEWER_HELP);
+
+#ifdef ENABLE_DETAILED_VIEW
+ buffer_json_member_add_array(wb, "accepted_params");
+ {
+ buffer_json_add_array_item_string(wb, "sockets");
+ }
+ buffer_json_array_close(wb); // accepted_params
+ buffer_json_member_add_array(wb, "required_params");
+ {
+ buffer_json_add_array_item_object(wb);
+ {
+ buffer_json_member_add_string(wb, "id", "sockets");
+ buffer_json_member_add_string(wb, "name", "Sockets");
+ buffer_json_member_add_string(wb, "help", "Select the source type to query");
+ buffer_json_member_add_boolean(wb, "unique_view", true);
+ buffer_json_member_add_string(wb, "type", "select");
+ buffer_json_member_add_array(wb, "options");
+ {
+ buffer_json_add_array_item_object(wb);
+ {
+ buffer_json_member_add_string(wb, "id", "aggregated");
+ buffer_json_member_add_string(wb, "name", "Aggregated view of sockets");
+ }
+ buffer_json_object_close(wb);
+ buffer_json_add_array_item_object(wb);
+ {
+ buffer_json_member_add_string(wb, "id", "detailed");
+ buffer_json_member_add_string(wb, "name", "Detailed view of all sockets");
+ }
+ buffer_json_object_close(wb);
+ }
+ buffer_json_array_close(wb); // options array
+ }
+ buffer_json_object_close(wb);
+ }
+ buffer_json_array_close(wb); // required_params
+#endif
+
+ char function_copy[strlen(function) + 1];
+ memcpy(function_copy, function, sizeof(function_copy));
+ char *words[1024];
+ size_t num_words = quoted_strings_splitter_pluginsd(function_copy, words, 1024);
+ for(size_t i = 1; i < num_words ;i++) {
+ char *param = get_word(words, num_words, i);
+ if(strcmp(param, "sockets:aggregated") == 0) {
+ aggregated = true;
+ }
+ else if(strcmp(param, "sockets:detailed") == 0) {
+ aggregated = false;
+ }
+ else if(strcmp(param, "info") == 0) {
+ goto close_and_send;
+ }
+ }
+
+ if(aggregated) {
+ buffer_json_member_add_object(wb, "aggregated_view");
+ {
+ buffer_json_member_add_string(wb, "column", "Count");
+ buffer_json_member_add_string(wb, "results_label", "unique combinations");
+ buffer_json_member_add_string(wb, "aggregated_label", "sockets");
+ }
+ buffer_json_object_close(wb);
+ }
+
+ {
+ 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,
+ .uid = true,
+ .cmdline = true,
+ .comm = true,
+ .namespaces = true,
+
+ .max_errors = 10,
+ },
+ .stats = { 0 },
+ .sockets_hashtable = { 0 },
+ .local_ips_hashtable = { 0 },
+ .listening_ports_hashtable = { 0 },
+ };
+
+ SIMPLE_HASHTABLE_AGGREGATED_SOCKETS ht = { 0 };
+ if(aggregated) {
+ simple_hashtable_init_AGGREGATED_SOCKETS(&ht, 1024);
+ ls.config.cb = local_sockets_cb_to_aggregation;
+ ls.config.data = &ht;
+ }
+ else {
+ ls.config.cb = local_sockets_cb_to_json;
+ ls.config.data = wb;
+ }
+
+ local_sockets_process(&ls);
+
+ if(aggregated) {
+ LOCAL_SOCKET *array[ht.used];
+ size_t added = 0;
+ uint64_t proc_self_net_ns_inode = ls.proc_self_net_ns_inode;
+ for(SIMPLE_HASHTABLE_SLOT_AGGREGATED_SOCKETS *sl = simple_hashtable_first_read_only_AGGREGATED_SOCKETS(&ht);
+ sl;
+ sl = simple_hashtable_next_read_only_AGGREGATED_SOCKETS(&ht, sl)) {
+ LOCAL_SOCKET *n = SIMPLE_HASHTABLE_SLOT_DATA(sl);
+ if(!n || added >= ht.used) continue;
+
+ array[added++] = n;
+ }
+
+ qsort(array, added, sizeof(LOCAL_SOCKET *), local_sockets_compar);
+
+ for(size_t i = 0; i < added ;i++) {
+ local_socket_to_json_array(wb, array[i], proc_self_net_ns_inode, true);
+ string_freez(array[i]->cmdline);
+ freez(array[i]);
+ }
+
+ simple_hashtable_destroy_AGGREGATED_SOCKETS(&ht);
+ }
+
+ 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|RRDF_FIELD_OPTS_STICKY,
+ 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_NONE,
+ 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);
+
+// // Uid
+// buffer_rrdf_table_add_field(wb, field_id++, "UID", "User 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_NONE,
+// RRDF_FIELD_OPTS_NONE,
+// NULL);
+
+ // Username
+ buffer_rrdf_table_add_field(wb, field_id++, "User", "Username",
+ 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);
+
+ if(!aggregated) {
+ // 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_NONE,
+ 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_NONE,
+ 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);
+
+ if(!aggregated) {
+ // 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_NONE,
+ 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_NONE,
+ 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);
+
+ if(aggregated) {
+ // Server IP
+ buffer_rrdf_table_add_field(wb, field_id++, "ServerIP", "Server 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_NONE,
+ RRDF_FIELD_OPTS_FULL_WIDTH | (aggregated ? RRDF_FIELD_OPTS_VISIBLE : RRDF_FIELD_OPTS_NONE),
+ NULL);
+ }
+
+ // Server Port
+ buffer_rrdf_table_add_field(wb, field_id++, "ServerPort", "Server 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,
+ aggregated ? RRDF_FIELD_OPTS_VISIBLE : RRDF_FIELD_OPTS_NONE,
+ NULL);
+
+ if(aggregated) {
+ // Client Address Space
+ buffer_rrdf_table_add_field(wb, field_id++, "ClientAddressSpace", "Client 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_VISIBLE,
+ NULL);
+
+ // Server Address Space
+ buffer_rrdf_table_add_field(wb, field_id++, "ServerAddressSpace", "Server 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_VISIBLE,
+ 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_NONE,
+// RRDF_FIELD_OPTS_NONE,
+// NULL);
+
+ // Count
+ buffer_rrdf_table_add_field(wb, field_id++, "Count", "Number of sockets like this",
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
+ 0, NULL, NAN, RRDF_FIELD_SORT_DESCENDING, NULL,
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_NONE,
+ aggregated ? (RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_STICKY) : RRDF_FIELD_OPTS_NONE,
+ NULL);
+ }
+ buffer_json_object_close(wb); // columns
+ buffer_json_member_add_string(wb, "default_sort_column", aggregated ? "Count" : "Direction");
+
+ buffer_json_member_add_object(wb, "custom_charts");
+ {
+ buffer_json_member_add_object(wb, "Network Map");
+ {
+ buffer_json_member_add_string(wb, "type", "network-viewer");
+ }
+ buffer_json_object_close(wb);
+ }
+ buffer_json_object_close(wb); // custom_charts
+
+ buffer_json_member_add_object(wb, "charts");
+ {
+ // Data Collection Age chart
+ buffer_json_member_add_object(wb, "Count");
+ {
+ 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, "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, "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);
+
+ if(!aggregated) {
+ 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
+ }
+
+close_and_send:
+ buffer_json_member_add_time_t(wb, "expires", now_s + 1);
+ buffer_json_finalize(wb);
+
+ netdata_mutex_lock(&stdout_mutex);
+ pluginsd_function_result_to_stdout(transaction, HTTP_RESP_OK, "application/json", now_s + 1, wb);
+ netdata_mutex_unlock(&stdout_mutex);
+}
+
+// ----------------------------------------------------------------------------------------------------------------
+// main
+
+int main(int argc __maybe_unused, char **argv __maybe_unused) {
+ clocks_init();
+ nd_thread_tag_set("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);
+
+ uc = system_usernames_cache_init();
+
+ // ----------------------------------------------------------------------------------------------------------------
+
+ if(argc == 2 && strcmp(argv[1], "debug") == 0) {
+ bool cancelled = false;
+ usec_t stop_monotonic_ut = now_monotonic_usec() + 600 * USEC_PER_SEC;
+ char buf[] = "network-connections sockets:aggregated";
+ network_viewer_function("123", buf, &stop_monotonic_ut, &cancelled,
+ NULL, HTTP_ACCESS_ALL, NULL, NULL);
+
+ char buf2[] = "network-connections sockets:detailed";
+ network_viewer_function("123", buf2, &stop_monotonic_ut, &cancelled,
+ NULL, HTTP_ACCESS_ALL, NULL, NULL);
+ exit(1);
+ }
+
+ // ----------------------------------------------------------------------------------------------------------------
+
+ fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"top\" "HTTP_ACCESS_FORMAT" %d\n",
+ NETWORK_CONNECTIONS_VIEWER_FUNCTION, 60,
+ NETWORK_CONNECTIONS_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_CONNECTIONS_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;
+}