summaryrefslogtreecommitdiffstats
path: root/src/libnetdata/maps
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnetdata/maps')
-rw-r--r--src/libnetdata/maps/local-sockets.h664
-rw-r--r--src/libnetdata/maps/system-services.h92
2 files changed, 492 insertions, 264 deletions
diff --git a/src/libnetdata/maps/local-sockets.h b/src/libnetdata/maps/local-sockets.h
index d407e6be..6f2ffd81 100644
--- a/src/libnetdata/maps/local-sockets.h
+++ b/src/libnetdata/maps/local-sockets.h
@@ -5,10 +5,8 @@
#include "libnetdata/libnetdata.h"
-// disable libmnl for the moment
-#undef HAVE_LIBMNL
-
#ifdef HAVE_LIBMNL
+#include <linux/rtnetlink.h>
#include <linux/inet_diag.h>
#include <linux/sock_diag.h>
#include <linux/unix_diag.h>
@@ -67,30 +65,41 @@ struct local_port;
struct local_socket_state;
typedef void (*local_sockets_cb_t)(struct local_socket_state *state, struct local_socket *n, void *data);
+struct local_sockets_config {
+ bool listening;
+ bool inbound;
+ bool outbound;
+ bool local;
+ bool tcp4;
+ bool tcp6;
+ bool udp4;
+ bool udp6;
+ bool pid;
+ bool cmdline;
+ bool comm;
+ bool uid;
+ bool namespaces;
+ bool tcp_info;
+
+ size_t max_errors;
+ size_t max_concurrent_namespaces;
+
+ local_sockets_cb_t cb;
+ void *data;
+
+ const char *host_prefix;
+
+ // internal use
+ uint64_t net_ns_inode;
+};
+
typedef struct local_socket_state {
- struct {
- bool listening;
- bool inbound;
- bool outbound;
- bool local;
- bool tcp4;
- bool tcp6;
- bool udp4;
- bool udp6;
- bool pid;
- bool cmdline;
- bool comm;
- bool uid;
- bool namespaces;
- size_t max_errors;
-
- local_sockets_cb_t cb;
- void *data;
-
- const char *host_prefix;
- } config;
+ struct local_sockets_config config;
struct {
+ size_t mnl_sends;
+ size_t namespaces_found;
+ size_t tcp_info_received;
size_t pid_fds_processed;
size_t pid_fds_opendir_failed;
size_t pid_fds_readlink_failed;
@@ -98,6 +107,9 @@ typedef struct local_socket_state {
size_t errors_encountered;
} stats;
+ bool spawn_server_is_mine;
+ SPAWN_SERVER *spawn_server;
+
#ifdef HAVE_LIBMNL
bool use_nl;
struct mnl_socket *nl;
@@ -106,6 +118,7 @@ typedef struct local_socket_state {
ARAL *local_socket_aral;
ARAL *pid_socket_aral;
+ SPINLOCK spinlock; // for namespaces
uint64_t proc_self_net_ns_inode;
@@ -181,12 +194,21 @@ typedef struct local_socket {
SOCKET_DIRECTION direction;
uint8_t timer;
- uint8_t retransmits;
+ uint8_t retransmits; // the # of packets currently queued for retransmission (not yet acknowledged)
uint32_t expires;
uint32_t rqueue;
uint32_t wqueue;
uid_t uid;
+ struct {
+ bool checked;
+ bool ipv46;
+ } ipv6ony;
+
+ union {
+ struct tcp_info tcp;
+ } info;
+
char comm[TASK_COMM_LEN];
STRING *cmdline;
@@ -201,16 +223,18 @@ typedef struct local_socket {
#endif
} LOCAL_SOCKET;
+static inline void local_sockets_spawn_server_callback(SPAWN_REQUEST *request);
+
// --------------------------------------------------------------------------------------------------------------------
static inline void local_sockets_log(LS_STATE *ls, const char *format, ...) PRINTFLIKE(2, 3);
static inline void local_sockets_log(LS_STATE *ls, const char *format, ...) {
- if(++ls->stats.errors_encountered == ls->config.max_errors) {
+ if(ls && ++ls->stats.errors_encountered == ls->config.max_errors) {
nd_log(NDLS_COLLECTORS, NDLP_ERR, "LOCAL-SOCKETS: max number of logs reached. Not logging anymore");
return;
}
- if(ls->stats.errors_encountered > ls->config.max_errors)
+ if(ls && ls->stats.errors_encountered > ls->config.max_errors)
return;
char buf[16384];
@@ -224,6 +248,133 @@ static inline void local_sockets_log(LS_STATE *ls, const char *format, ...) {
// --------------------------------------------------------------------------------------------------------------------
+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(struct socket_endpoint *se) {
+ if (se->family == AF_INET) {
+ // For IPv4, loopback addresses are in the 127.0.0.0/8 range
+ 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
+ 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
+ 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
+ return memcmp(&se->ip.ipv6, &in6addr_loopback, sizeof(se->ip.ipv6)) == 0;
+ }
+
+ return false;
+}
+
+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 bool is_local_socket_ipv46(LOCAL_SOCKET *n) {
+ return n->local.family == AF_INET6 &&
+ n->direction == SOCKET_DIRECTION_LISTEN &&
+ local_sockets_is_zero_address(&n->local) &&
+ n->ipv6ony.checked &&
+ n->ipv6ony.ipv46;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
static void local_sockets_foreach_local_socket_call_cb(LS_STATE *ls) {
for(SIMPLE_HASHTABLE_SLOT_LOCAL_SOCKET *sl = simple_hashtable_first_read_only_LOCAL_SOCKET(&ls->sockets_hashtable);
sl;
@@ -425,123 +576,6 @@ static inline bool local_sockets_find_all_sockets_in_proc(LS_STATE *ls, const ch
// --------------------------------------------------------------------------------------------------------------------
-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(struct socket_endpoint *se) {
- if (se->family == AF_INET) {
- // For IPv4, loopback addresses are in the 127.0.0.0/8 range
- 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
- 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
- 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
- return memcmp(&se->ip.ipv6, &in6addr_loopback, sizeof(se->ip.ipv6)) == 0;
- }
-
- return false;
-}
-
-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
@@ -636,28 +670,31 @@ static inline bool local_sockets_add_socket(LS_STATE *ls, LOCAL_SOCKET *tmp) {
#ifdef HAVE_LIBMNL
-static inline void local_sockets_netlink_init(LS_STATE *ls) {
- ls->use_nl = true;
+static inline void local_sockets_libmnl_init(LS_STATE *ls) {
ls->nl = mnl_socket_open(NETLINK_INET_DIAG);
- if (!ls->nl) {
- local_sockets_log(ls, "cannot open netlink socket");
+ if (ls->nl == NULL) {
+ local_sockets_log(ls, "cannot open libmnl netlink socket");
ls->use_nl = false;
}
-
- if (mnl_socket_bind(ls->nl, 0, MNL_SOCKET_AUTOPID) < 0) {
- local_sockets_log(ls, "cannot bind netlink socket");
+ else if (mnl_socket_bind(ls->nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+ local_sockets_log(ls, "cannot bind libmnl netlink socket");
+ mnl_socket_close(ls->nl);
+ ls->nl = NULL;
ls->use_nl = false;
}
+ else
+ ls->use_nl = true;
}
-static inline void local_sockets_netlink_cleanup(LS_STATE *ls) {
+static inline void local_sockets_libmnl_cleanup(LS_STATE *ls) {
if(ls->nl) {
mnl_socket_close(ls->nl);
ls->nl = NULL;
+ ls->use_nl = false;
}
}
-static inline int local_sockets_netlink_cb_data(const struct nlmsghdr *nlh, void *data) {
+static inline int local_sockets_libmnl_cb_data(const struct nlmsghdr *nlh, void *data) {
LS_STATE *ls = data;
struct inet_diag_msg *diag_msg = mnl_nlmsg_get_payload(nlh);
@@ -666,15 +703,19 @@ static inline int local_sockets_netlink_cb_data(const struct nlmsghdr *nlh, void
.inode = diag_msg->idiag_inode,
.direction = SOCKET_DIRECTION_NONE,
.state = diag_msg->idiag_state,
+ .ipv6ony = {
+ .checked = false,
+ .ipv46 = false,
+ },
.local = {
.protocol = ls->tmp_protocol,
.family = diag_msg->idiag_family,
- .port = diag_msg->id.idiag_sport,
+ .port = ntohs(diag_msg->id.idiag_sport),
},
.remote = {
.protocol = ls->tmp_protocol,
.family = diag_msg->idiag_family,
- .port = diag_msg->id.idiag_dport,
+ .port = ntohs(diag_msg->id.idiag_dport),
},
.timer = diag_msg->idiag_timer,
.retransmits = diag_msg->idiag_retrans,
@@ -693,12 +734,37 @@ static inline int local_sockets_netlink_cb_data(const struct nlmsghdr *nlh, void
memcpy(&n.remote.ip.ipv6, diag_msg->id.idiag_dst, sizeof(n.remote.ip.ipv6));
}
+ struct rtattr *attr = (struct rtattr *)(diag_msg + 1);
+ int rtattrlen = nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*diag_msg));
+ for (; !n.ipv6ony.checked && RTA_OK(attr, rtattrlen); attr = RTA_NEXT(attr, rtattrlen)) {
+ switch (attr->rta_type) {
+ case INET_DIAG_INFO: {
+ if(ls->tmp_protocol == IPPROTO_TCP) {
+ struct tcp_info *info = (struct tcp_info *)RTA_DATA(attr);
+ n.info.tcp = *info;
+ ls->stats.tcp_info_received++;
+ }
+ }
+ break;
+
+ case INET_DIAG_SKV6ONLY: {
+ n.ipv6ony.checked = true;
+ int ipv6only = *(int *)RTA_DATA(attr);
+ n.ipv6ony.ipv46 = !ipv6only;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
local_sockets_add_socket(ls, &n);
return MNL_CB_OK;
}
-static inline bool local_sockets_netlink_get_sockets(LS_STATE *ls, uint16_t family, uint16_t protocol) {
+static inline bool local_sockets_libmnl_get_sockets(LS_STATE *ls, uint16_t family, uint16_t protocol) {
ls->tmp_protocol = protocol;
char buf[MNL_SOCKET_BUFFER_SIZE];
@@ -710,14 +776,22 @@ static inline bool local_sockets_netlink_get_sockets(LS_STATE *ls, uint16_t fami
req.sdiag_family = family;
req.sdiag_protocol = protocol;
req.idiag_states = -1;
+ req.idiag_ext = 0;
+
+ if(family == AF_INET6)
+ req.idiag_ext |= 1 << (INET_DIAG_SKV6ONLY - 1);
+
+ if(protocol == IPPROTO_TCP && ls->config.tcp_info)
+ req.idiag_ext |= 1 << (INET_DIAG_INFO - 1);
nlh = mnl_nlmsg_put_header(buf);
nlh->nlmsg_type = SOCK_DIAG_BY_FAMILY;
- nlh->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
+ nlh->nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
nlh->nlmsg_seq = seq = time(NULL);
mnl_nlmsg_put_extra_header(nlh, sizeof(req));
memcpy(mnl_nlmsg_get_payload(nlh), &req, sizeof(req));
+ ls->stats.mnl_sends++;
if (mnl_socket_sendto(ls->nl, nlh, nlh->nlmsg_len) < 0) {
local_sockets_log(ls, "mnl_socket_send failed");
return false;
@@ -725,7 +799,7 @@ static inline bool local_sockets_netlink_get_sockets(LS_STATE *ls, uint16_t fami
ssize_t ret;
while ((ret = mnl_socket_recvfrom(ls->nl, buf, sizeof(buf))) > 0) {
- ret = mnl_cb_run(buf, ret, seq, portid, local_sockets_netlink_cb_data, ls);
+ ret = mnl_cb_run(buf, ret, seq, portid, local_sockets_libmnl_cb_data, ls);
if (ret <= MNL_CB_STOP)
break;
}
@@ -774,6 +848,10 @@ static inline bool local_sockets_read_proc_net_x(LS_STATE *ls, const char *filen
LOCAL_SOCKET n = {
.direction = SOCKET_DIRECTION_NONE,
+ .ipv6ony = {
+ .checked = false,
+ .ipv46 = false,
+ },
.local = {
.family = family,
.protocol = protocol,
@@ -904,6 +982,10 @@ static inline void local_sockets_detect_directions(LS_STATE *ls) {
// --------------------------------------------------------------------------------------------------------------------
static inline void local_sockets_init(LS_STATE *ls) {
+ ls->config.host_prefix = netdata_configured_host_prefix;
+
+ spinlock_init(&ls->spinlock);
+
simple_hashtable_init_NET_NS(&ls->ns_hashtable, 1024);
simple_hashtable_init_PID_SOCKET(&ls->pid_sockets_hashtable, 65535);
simple_hashtable_init_LOCAL_SOCKET(&ls->sockets_hashtable, 65535);
@@ -923,9 +1005,36 @@ static inline void local_sockets_init(LS_STATE *ls) {
65536,
65536,
NULL, NULL, NULL, false, true);
+
+ memset(&ls->stats, 0, sizeof(ls->stats));
+
+#ifdef HAVE_LIBMNL
+ ls->use_nl = false;
+ ls->nl = NULL;
+ ls->tmp_protocol = 0;
+ local_sockets_libmnl_init(ls);
+#endif
+
+ if(ls->config.namespaces && ls->spawn_server == NULL) {
+ ls->spawn_server = spawn_server_create(SPAWN_SERVER_OPTION_CALLBACK, NULL, local_sockets_spawn_server_callback, 0, NULL);
+ ls->spawn_server_is_mine = true;
+ }
+ else
+ ls->spawn_server_is_mine = false;
}
static inline void local_sockets_cleanup(LS_STATE *ls) {
+
+ if(ls->spawn_server_is_mine) {
+ spawn_server_destroy(ls->spawn_server);
+ ls->spawn_server = NULL;
+ ls->spawn_server_is_mine = false;
+ }
+
+#ifdef HAVE_LIBMNL
+ local_sockets_libmnl_cleanup(ls);
+#endif
+
// free the sockets hashtable data
for(SIMPLE_HASHTABLE_SLOT_LOCAL_SOCKET *sl = simple_hashtable_first_read_only_LOCAL_SOCKET(&ls->sockets_hashtable);
sl;
@@ -963,8 +1072,8 @@ static inline void local_sockets_cleanup(LS_STATE *ls) {
static inline void local_sockets_do_family_protocol(LS_STATE *ls, const char *filename, uint16_t family, uint16_t protocol) {
#ifdef HAVE_LIBMNL
- if(ls->use_nl) {
- ls->use_nl = local_sockets_netlink_get_sockets(ls, family, protocol);
+ if(ls->nl && ls->use_nl) {
+ ls->use_nl = local_sockets_libmnl_get_sockets(ls, family, protocol);
if(ls->use_nl)
return;
@@ -974,7 +1083,7 @@ static inline void local_sockets_do_family_protocol(LS_STATE *ls, const char *fi
local_sockets_read_proc_net_x(ls, filename, family, protocol);
}
-static inline void local_sockets_read_sockets_from_proc(LS_STATE *ls) {
+static inline void local_sockets_read_all_system_sockets(LS_STATE *ls) {
char path[FILENAME_MAX + 1];
if(ls->config.namespaces) {
@@ -1036,7 +1145,52 @@ static inline void local_sockets_send_to_parent(struct local_socket_state *ls __
local_sockets_log(ls, "failed to write cmdline to pipe");
}
-static inline bool local_sockets_get_namespace_sockets(LS_STATE *ls, struct pid_socket *ps, pid_t *pid) {
+static inline void local_sockets_spawn_server_callback(SPAWN_REQUEST *request) {
+ LS_STATE ls = { 0 };
+ ls.config = *((struct local_sockets_config *)request->data);
+
+ // we don't need these inside namespaces
+ ls.config.cmdline = false;
+ ls.config.comm = false;
+ ls.config.pid = false;
+ ls.config.namespaces = false;
+
+ // initialize local sockets
+ local_sockets_init(&ls);
+
+ ls.config.host_prefix = ""; // we need the /proc of the container
+
+ struct local_sockets_child_work cw = {
+ .net_ns_inode = ls.proc_self_net_ns_inode,
+ .fd = request->fds[1], // stdout
+ };
+
+ ls.config.cb = local_sockets_send_to_parent;
+ ls.config.data = &cw;
+ ls.proc_self_net_ns_inode = ls.config.net_ns_inode;
+
+ // switch namespace using the custom fd passed via the spawn server
+ if (setns(request->fds[3], CLONE_NEWNET) == -1) {
+ local_sockets_log(&ls, "failed to switch network namespace at child process using fd %d", request->fds[3]);
+ exit(EXIT_FAILURE);
+ }
+
+ // read all sockets from /proc
+ local_sockets_read_all_system_sockets(&ls);
+
+ // send all sockets to parent
+ local_sockets_foreach_local_socket_call_cb(&ls);
+
+ // send the terminating socket
+ struct local_socket zero = {
+ .net_ns_inode = ls.config.net_ns_inode,
+ };
+ local_sockets_send_to_parent(&ls, &zero, &cw);
+
+ exit(EXIT_SUCCESS);
+}
+
+static inline bool local_sockets_get_namespace_sockets_with_pid(LS_STATE *ls, struct pid_socket *ps) {
char filename[1024];
snprintfz(filename, sizeof(filename), "%s/proc/%d/ns/net", ls->config.host_prefix, ps->pid);
@@ -1060,80 +1214,32 @@ static inline bool local_sockets_get_namespace_sockets(LS_STATE *ls, struct pid_
return false;
}
- int pipefd[2];
- if (pipe(pipefd) != 0) {
- local_sockets_log(ls, "cannot create pipe");
+ if(ls->spawn_server == NULL) {
close(fd);
+ local_sockets_log(ls, "spawn server is not available");
return false;
}
- *pid = fork();
- if (*pid == 0) {
- // Child process
- close(pipefd[0]);
-
- // local_sockets_log(ls, "child is here for inode %"PRIu64" and namespace %"PRIu64, ps->inode, ps->net_ns_inode);
-
- struct local_sockets_child_work cw = {
- .net_ns_inode = ps->net_ns_inode,
- .fd = pipefd[1],
- };
-
- ls->config.host_prefix = ""; // we need the /proc of the container
- ls->config.cb = local_sockets_send_to_parent;
- ls->config.data = &cw;
- ls->config.cmdline = false; // we have these already
- ls->config.comm = false; // we have these already
- ls->config.pid = false; // we have these already
- ls->config.namespaces = false;
- ls->proc_self_net_ns_inode = ps->net_ns_inode;
-
-
- // switch namespace
- if (setns(fd, CLONE_NEWNET) == -1) {
- local_sockets_log(ls, "failed to switch network namespace at child process");
- exit(EXIT_FAILURE);
- }
-
-#ifdef HAVE_LIBMNL
- local_sockets_netlink_cleanup(ls);
- local_sockets_netlink_init(ls);
-#endif
-
- // read all sockets from /proc
- local_sockets_read_sockets_from_proc(ls);
-
- // send all sockets to parent
- local_sockets_foreach_local_socket_call_cb(ls);
+ struct local_sockets_config config = ls->config;
+ config.net_ns_inode = ps->net_ns_inode;
+ SPAWN_INSTANCE *si = spawn_server_exec(ls->spawn_server, STDERR_FILENO, fd, NULL, &config, sizeof(config), SPAWN_INSTANCE_TYPE_CALLBACK);
+ close(fd); fd = -1;
- // send the terminating socket
- struct local_socket zero = {
- .net_ns_inode = ps->net_ns_inode,
- };
- local_sockets_send_to_parent(ls, &zero, &cw);
-
-#ifdef HAVE_LIBMNL
- local_sockets_netlink_cleanup(ls);
-#endif
-
- close(pipefd[1]); // Close write end of pipe
- exit(EXIT_SUCCESS);
+ if(si == NULL) {
+ local_sockets_log(ls, "cannot create spawn instance");
+ return false;
}
- // parent
-
- close(fd);
- close(pipefd[1]);
size_t received = 0;
struct local_socket buf;
- while(read(pipefd[0], &buf, sizeof(buf)) == sizeof(buf)) {
+ while(read(spawn_server_instance_read_fd(si), &buf, sizeof(buf)) == sizeof(buf)) {
size_t len = 0;
- if(read(pipefd[0], &len, sizeof(len)) != sizeof(len))
+ if(read(spawn_server_instance_read_fd(si), &len, sizeof(len)) != sizeof(len))
local_sockets_log(ls, "failed to read cmdline length from pipe");
if(len) {
char cmdline[len + 1];
- if(read(pipefd[0], cmdline, len) != (ssize_t)len)
+ if(read(spawn_server_instance_read_fd(si), cmdline, len) != (ssize_t)len)
local_sockets_log(ls, "failed to read cmdline from pipe");
else {
cmdline[len] = '\0';
@@ -1153,15 +1259,15 @@ static inline bool local_sockets_get_namespace_sockets(LS_STATE *ls, struct pid_
break;
}
+ spinlock_lock(&ls->spinlock);
+
SIMPLE_HASHTABLE_SLOT_LOCAL_SOCKET *sl = simple_hashtable_get_slot_LOCAL_SOCKET(&ls->sockets_hashtable, buf.inode, &buf, true);
LOCAL_SOCKET *n = SIMPLE_HASHTABLE_SLOT_DATA(sl);
if(n) {
string_freez(buf.cmdline);
-
// local_sockets_log(ls,
// "ns inode %" PRIu64" (comm: '%s', pid: %u, ns: %"PRIu64") already exists in hashtable (comm: '%s', pid: %u, ns: %"PRIu64") - ignoring duplicate",
// buf.inode, buf.comm, buf.pid, buf.net_ns_inode, n->comm, n->pid, n->net_ns_inode);
- continue;
}
else {
n = aral_mallocz(ls->local_socket_aral);
@@ -1170,75 +1276,109 @@ static inline bool local_sockets_get_namespace_sockets(LS_STATE *ls, struct pid_
local_sockets_index_listening_port(ls, n);
}
- }
- close(pipefd[0]);
+ spinlock_unlock(&ls->spinlock);
+ }
+ spawn_server_exec_kill(ls->spawn_server, si);
return received > 0;
}
-static inline void local_socket_waitpid(LS_STATE *ls, pid_t pid) {
- if(!pid) return;
+struct local_sockets_namespace_worker {
+ LS_STATE *ls;
+ uint64_t inode;
+};
+
+static inline void *local_sockets_get_namespace_sockets(void *arg) {
+ struct local_sockets_namespace_worker *data = arg;
+ LS_STATE *ls = data->ls;
+ const uint64_t inode = data->inode;
+
+ spinlock_lock(&ls->spinlock);
+
+ // find a pid_socket that has this namespace
+ for(SIMPLE_HASHTABLE_SLOT_PID_SOCKET *sl_pid = simple_hashtable_first_read_only_PID_SOCKET(&ls->pid_sockets_hashtable) ;
+ sl_pid ;
+ sl_pid = simple_hashtable_next_read_only_PID_SOCKET(&ls->pid_sockets_hashtable, sl_pid)) {
+ struct pid_socket *ps = SIMPLE_HASHTABLE_SLOT_DATA(sl_pid);
+ if(!ps || ps->net_ns_inode != inode) continue;
- int status;
- waitpid(pid, &status, 0);
+ // now we have a pid that has the same namespace inode
- if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
- local_sockets_log(ls, "Child exited with status %d", WEXITSTATUS(status));
- else if (WIFSIGNALED(status))
- local_sockets_log(ls, "Child terminated by signal %d", WTERMSIG(status));
+ spinlock_unlock(&ls->spinlock);
+ const bool worked = local_sockets_get_namespace_sockets_with_pid(ls, ps);
+ spinlock_lock(&ls->spinlock);
+
+ if(worked)
+ break;
+ }
+
+ spinlock_unlock(&ls->spinlock);
+
+ return NULL;
}
static inline void local_sockets_namespaces(LS_STATE *ls) {
- pid_t children[5] = { 0 };
- size_t last_child = 0;
+ size_t threads = ls->config.max_concurrent_namespaces;
+ if(threads == 0) threads = 5;
+ if(threads > 100) threads = 100;
+
+ size_t last_thread = 0;
+ ND_THREAD *workers[threads];
+ struct local_sockets_namespace_worker workers_data[threads];
+ memset(workers, 0, sizeof(workers));
+ memset(workers_data, 0, sizeof(workers_data));
+
+ spinlock_lock(&ls->spinlock);
for(SIMPLE_HASHTABLE_SLOT_NET_NS *sl = simple_hashtable_first_read_only_NET_NS(&ls->ns_hashtable);
sl;
sl = simple_hashtable_next_read_only_NET_NS(&ls->ns_hashtable, sl)) {
- uint64_t inode = (uint64_t)SIMPLE_HASHTABLE_SLOT_DATA(sl);
+ const uint64_t inode = (uint64_t)SIMPLE_HASHTABLE_SLOT_DATA(sl);
if(inode == ls->proc_self_net_ns_inode)
continue;
- // find a pid_socket that has this namespace
- for(SIMPLE_HASHTABLE_SLOT_PID_SOCKET *sl_pid = simple_hashtable_first_read_only_PID_SOCKET(&ls->pid_sockets_hashtable) ;
- sl_pid ;
- sl_pid = simple_hashtable_next_read_only_PID_SOCKET(&ls->pid_sockets_hashtable, sl_pid)) {
- struct pid_socket *ps = SIMPLE_HASHTABLE_SLOT_DATA(sl_pid);
- if(!ps || ps->net_ns_inode != inode) continue;
+ spinlock_unlock(&ls->spinlock);
- if(++last_child >= 5)
- last_child = 0;
+ ls->stats.namespaces_found++;
- local_socket_waitpid(ls, children[last_child]);
- children[last_child] = 0;
+ if(workers[last_thread] != NULL) {
+ if(++last_thread >= threads)
+ last_thread = 0;
- // now we have a pid that has the same namespace inode
- if(local_sockets_get_namespace_sockets(ls, ps, &children[last_child]))
- break;
+ if(workers[last_thread]) {
+ nd_thread_join(workers[last_thread]);
+ workers[last_thread] = NULL;
+ }
}
+
+ workers_data[last_thread].ls = ls;
+ workers_data[last_thread].inode = inode;
+ workers[last_thread] = nd_thread_create(
+ "local-sockets-worker", NETDATA_THREAD_OPTION_JOINABLE,
+ local_sockets_get_namespace_sockets, &workers_data[last_thread]);
+
+ spinlock_lock(&ls->spinlock);
}
- for(size_t i = 0; i < 5 ;i++)
- local_socket_waitpid(ls, children[i]);
+ spinlock_unlock(&ls->spinlock);
+
+ // wait all the threads running
+ for(size_t i = 0; i < threads ;i++) {
+ if(workers[i])
+ nd_thread_join(workers[i]);
+ }
}
// --------------------------------------------------------------------------------------------------------------------
static inline void local_sockets_process(LS_STATE *ls) {
-
-#ifdef HAVE_LIBMNL
- local_sockets_netlink_init(ls);
-#endif
-
- ls->config.host_prefix = netdata_configured_host_prefix;
-
// initialize our hashtables
local_sockets_init(ls);
// read all sockets from /proc
- local_sockets_read_sockets_from_proc(ls);
+ local_sockets_read_all_system_sockets(ls);
// check all socket namespaces
if(ls->config.namespaces)
@@ -1253,10 +1393,6 @@ static inline void local_sockets_process(LS_STATE *ls) {
// free all memory
local_sockets_cleanup(ls);
-
-#ifdef HAVE_LIBMNL
- local_sockets_netlink_cleanup(ls);
-#endif
}
static inline void ipv6_address_to_txt(struct in6_addr *in6_addr, char *dst) {
diff --git a/src/libnetdata/maps/system-services.h b/src/libnetdata/maps/system-services.h
new file mode 100644
index 00000000..123f4f10
--- /dev/null
+++ b/src/libnetdata/maps/system-services.h
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_SYSTEM_SERVICES_H
+#define NETDATA_SYSTEM_SERVICES_H
+
+#include "libnetdata/libnetdata.h"
+#include <netdb.h>
+
+// --------------------------------------------------------------------------------------------------------------------
+// hashtable for caching port and protocol to service name mappings
+// key is the combination of protocol and port packed into a uint64_t, value is service name (STRING)
+
+#define SIMPLE_HASHTABLE_VALUE_TYPE STRING
+#define SIMPLE_HASHTABLE_NAME _SERVICENAMES_CACHE
+#include "libnetdata/simple_hashtable.h"
+
+typedef struct servicenames_cache {
+ SPINLOCK spinlock;
+ SIMPLE_HASHTABLE_SERVICENAMES_CACHE ht;
+} SERVICENAMES_CACHE;
+
+static inline uint64_t system_servicenames_key(uint16_t port, uint16_t ipproto) {
+ return ((uint64_t)ipproto << 16) | (uint64_t)port;
+}
+
+static inline const char *system_servicenames_ipproto2str(uint16_t ipproto) {
+ return (ipproto == IPPROTO_TCP) ? "tcp" : "udp";
+}
+
+static inline const char *static_portnames(uint16_t port, uint16_t ipproto) {
+ if(port == 19999 && ipproto == IPPROTO_TCP)
+ return "netdata";
+
+ if(port == 8125)
+ return "statsd";
+
+ return NULL;
+}
+
+static inline STRING *system_servicenames_cache_lookup(SERVICENAMES_CACHE *sc, uint16_t port, uint16_t ipproto) {
+ uint64_t key = system_servicenames_key(port, ipproto);
+ spinlock_lock(&sc->spinlock);
+
+ SIMPLE_HASHTABLE_SLOT_SERVICENAMES_CACHE *sl = simple_hashtable_get_slot_SERVICENAMES_CACHE(&sc->ht, key, &key, true);
+ STRING *s = SIMPLE_HASHTABLE_SLOT_DATA(sl);
+ if (!s) {
+ const char *st = static_portnames(port, ipproto);
+ if(st) {
+ s = string_strdupz(st);
+ }
+ else {
+ struct servent *se = getservbyport(htons(port), system_servicenames_ipproto2str(ipproto));
+
+ if (!se || !se->s_name) {
+ char name[50];
+ snprintfz(name, sizeof(name), "%u/%s", port, system_servicenames_ipproto2str(ipproto));
+ s = string_strdupz(name);
+ }
+ else
+ s = string_strdupz(se->s_name);
+ }
+
+ simple_hashtable_set_slot_SERVICENAMES_CACHE(&sc->ht, sl, key, s);
+ }
+
+ s = string_dup(s);
+ spinlock_unlock(&sc->spinlock);
+ return s;
+}
+
+static inline SERVICENAMES_CACHE *system_servicenames_cache_init(void) {
+ SERVICENAMES_CACHE *sc = callocz(1, sizeof(*sc));
+ spinlock_init(&sc->spinlock);
+ simple_hashtable_init_SERVICENAMES_CACHE(&sc->ht, 100);
+ return sc;
+}
+
+static inline void system_servicenames_cache_destroy(SERVICENAMES_CACHE *sc) {
+ spinlock_lock(&sc->spinlock);
+
+ for (SIMPLE_HASHTABLE_SLOT_SERVICENAMES_CACHE *sl = simple_hashtable_first_read_only_SERVICENAMES_CACHE(&sc->ht);
+ sl;
+ sl = simple_hashtable_next_read_only_SERVICENAMES_CACHE(&sc->ht, sl)) {
+ STRING *s = SIMPLE_HASHTABLE_SLOT_DATA(sl);
+ string_freez(s);
+ }
+
+ simple_hashtable_destroy_SERVICENAMES_CACHE(&sc->ht);
+ freez(sc);
+}
+
+#endif //NETDATA_SYSTEM_SERVICES_H