0
0
Fork 0
mirror of https://github.com/netdata/netdata.git synced 2025-04-13 17:19:11 +00:00
netdata_netdata/collectors/network-viewer.plugin/network-viewer.c
Costa Tsaousis 919dbe175e
Network Viewer (local-sockets version) ()
* network viewer based on local-sockets

* added more fields for the UI

* added socket state

* added inodes to the lists
2024-01-30 11:28:38 +02:00

482 lines
20 KiB
C

// 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;
}