Skip to content

Commit

Permalink
Network Viewer (local-sockets version) (netdata#16872)
Browse files Browse the repository at this point in the history
* network viewer based on local-sockets

* added more fields for the UI

* added socket state

* added inodes to the lists
  • Loading branch information
ktsaou authored Jan 30, 2024
1 parent 5124aef commit 919dbe1
Show file tree
Hide file tree
Showing 11 changed files with 652 additions and 60 deletions.
21 changes: 19 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand All @@ -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
#
Expand Down
482 changes: 482 additions & 0 deletions collectors/network-viewer.plugin/network-viewer.c

Large diffs are not rendered by default.

159 changes: 119 additions & 40 deletions collectors/plugins.d/local-sockets.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -114,19 +115,21 @@ 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;
uint16_t 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;
};
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -368,50 +369,125 @@ 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
static const unsigned char ipv4_mapped_prefix[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF };
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
Expand Down Expand Up @@ -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));
Expand All @@ -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

Expand All @@ -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;
}
Expand Down
20 changes: 10 additions & 10 deletions collectors/plugins.d/local_listeners.c
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand Down
1 change: 1 addition & 0 deletions contrib/debian/netdata.postinst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions contrib/debian/rules
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 5 additions & 0 deletions netdata-installer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 3 additions & 0 deletions netdata.spec.in
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions packaging/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 && \
Expand Down
Loading

0 comments on commit 919dbe1

Please sign in to comment.