summaryrefslogtreecommitdiffstats
path: root/collectors/plugins.d/local_listeners.c
diff options
context:
space:
mode:
Diffstat (limited to 'collectors/plugins.d/local_listeners.c')
-rw-r--r--collectors/plugins.d/local_listeners.c366
1 files changed, 366 insertions, 0 deletions
diff --git a/collectors/plugins.d/local_listeners.c b/collectors/plugins.d/local_listeners.c
new file mode 100644
index 000000000..a39de7974
--- /dev/null
+++ b/collectors/plugins.d/local_listeners.c
@@ -0,0 +1,366 @@
+#include "libnetdata/libnetdata.h"
+#include "libnetdata/required_dummies.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <dirent.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <arpa/inet.h>
+
+typedef enum {
+ PROC_NET_PROTOCOL_TCP,
+ PROC_NET_PROTOCOL_TCP6,
+ PROC_NET_PROTOCOL_UDP,
+ PROC_NET_PROTOCOL_UDP6,
+} PROC_NET_PROTOCOLS;
+
+#define MAX_ERROR_LOGS 10
+
+static size_t pid_fds_processed = 0;
+static size_t pid_fds_failed = 0;
+static size_t errors_encountered = 0;
+
+static inline const char *protocol_name(PROC_NET_PROTOCOLS protocol) {
+ switch(protocol) {
+ default:
+ case PROC_NET_PROTOCOL_TCP:
+ return "TCP";
+
+ case PROC_NET_PROTOCOL_UDP:
+ return "UDP";
+
+ case PROC_NET_PROTOCOL_TCP6:
+ return "TCP6";
+
+ case PROC_NET_PROTOCOL_UDP6:
+ return "UDP6";
+ }
+}
+
+static inline int read_cmdline(pid_t pid, char* buffer, size_t bufferSize) {
+ char path[FILENAME_MAX + 1];
+ snprintfz(path, FILENAME_MAX, "%s/proc/%d/cmdline", netdata_configured_host_prefix, pid);
+
+ FILE* file = fopen(path, "r");
+ if (!file) {
+ if(++errors_encountered < MAX_ERROR_LOGS)
+ collector_error("LOCAL-LISTENERS: error opening file: %s\n", path);
+
+ return -1;
+ }
+
+ size_t bytesRead = fread(buffer, 1, bufferSize - 1, file);
+ buffer[bytesRead] = '\0'; // Ensure null-terminated
+
+ // Replace null characters in cmdline with spaces
+ for (size_t i = 0; i < bytesRead; i++) {
+ if (buffer[i] == '\0') {
+ buffer[i] = ' ';
+ }
+ }
+
+ fclose(file);
+ return 0;
+}
+
+static inline void fix_cmdline(char* str) {
+ if (str == NULL)
+ return;
+
+ char *s = str;
+
+ do {
+ if(*s == '|' || iscntrl(*s))
+ *s = '_';
+
+ } while(*++s);
+
+
+ while(s > str && *(s-1) == ' ')
+ *--s = '\0';
+}
+
+// ----------------------------------------------------------------------------
+
+#define HASH_TABLE_SIZE 100000
+
+typedef struct Node {
+ unsigned int inode; // key
+
+ // values
+ unsigned int port;
+ char local_address[INET6_ADDRSTRLEN];
+ PROC_NET_PROTOCOLS protocol;
+ bool processed;
+
+ // linking
+ struct Node *prev, *next;
+} Node;
+
+typedef struct HashTable {
+ Node *table[HASH_TABLE_SIZE];
+} HashTable;
+
+static HashTable *hashTable_key_inode_port_value = NULL;
+
+static inline void generate_output(const char *protocol, const char *address, unsigned int port, const char *cmdline) {
+ printf("%s|%s|%u|%s\n", protocol, address, port, cmdline);
+}
+
+HashTable* createHashTable() {
+ HashTable *hashTable = (HashTable*)mallocz(sizeof(HashTable));
+ memset(hashTable, 0, sizeof(HashTable));
+ return hashTable;
+}
+
+static inline unsigned int hashFunction(unsigned int inode) {
+ return inode % HASH_TABLE_SIZE;
+}
+
+static inline void insertHashTable(HashTable *hashTable, unsigned int inode, unsigned int port, PROC_NET_PROTOCOLS protocol, char *local_address) {
+ unsigned int index = hashFunction(inode);
+ Node *newNode = (Node*)mallocz(sizeof(Node));
+ newNode->inode = inode;
+ newNode->port = port;
+ newNode->protocol = protocol;
+ strncpyz(newNode->local_address, local_address, INET6_ADDRSTRLEN - 1);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(hashTable->table[index], newNode, prev, next);
+}
+
+static inline bool lookupHashTable_and_execute(HashTable *hashTable, unsigned int inode, pid_t pid) {
+ unsigned int index = hashFunction(inode);
+ for(Node *node = hashTable->table[index], *next = NULL ; node ; node = next) {
+ next = node->next;
+
+ if(node->inode == inode && node->port) {
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(hashTable->table[index], node, prev, next);
+ char cmdline[8192] = "";
+ read_cmdline(pid, cmdline, sizeof(cmdline));
+ fix_cmdline(cmdline);
+ generate_output(protocol_name(node->protocol), node->local_address, node->port, cmdline);
+ freez(node);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void freeHashTable(HashTable *hashTable) {
+ for (unsigned int i = 0; i < HASH_TABLE_SIZE; i++) {
+ while(hashTable->table[i]) {
+ Node *tmp = hashTable->table[i];
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(hashTable->table[i], tmp, prev, next);
+ generate_output(protocol_name(tmp->protocol), tmp->local_address, tmp->port, "");
+ freez(tmp);
+ }
+ }
+ freez(hashTable);
+}
+
+// ----------------------------------------------------------------------------
+
+static inline void found_this_socket_inode(pid_t pid, unsigned int inode) {
+ lookupHashTable_and_execute(hashTable_key_inode_port_value, inode, pid);
+}
+
+bool find_all_sockets_in_proc(const char *proc_filename) {
+ DIR *proc_dir, *fd_dir;
+ struct dirent *proc_entry, *fd_entry;
+ char path_buffer[FILENAME_MAX + 1];
+
+ proc_dir = opendir(proc_filename);
+ if (proc_dir == NULL) {
+ if(++errors_encountered < MAX_ERROR_LOGS)
+ collector_error("LOCAL-LISTENERS: cannot opendir() '%s'", proc_filename);
+
+ pid_fds_failed++;
+ return false;
+ }
+
+ while ((proc_entry = readdir(proc_dir)) != NULL) {
+ // Check if directory entry is a PID by seeing if the name is made up of digits only
+ int is_pid = 1;
+ for (char *c = proc_entry->d_name; *c != '\0'; c++) {
+ if (*c < '0' || *c > '9') {
+ is_pid = 0;
+ break;
+ }
+ }
+
+ if (!is_pid)
+ continue;
+
+ // Build the path to the fd directory of the process
+ snprintfz(path_buffer, FILENAME_MAX, "%s/%s/fd/", proc_filename, proc_entry->d_name);
+
+ fd_dir = opendir(path_buffer);
+ if (fd_dir == NULL) {
+ if(++errors_encountered < MAX_ERROR_LOGS)
+ collector_error("LOCAL-LISTENERS: cannot opendir() '%s'", path_buffer);
+
+ pid_fds_failed++;
+ continue;
+ }
+
+ while ((fd_entry = readdir(fd_dir)) != NULL) {
+ if(!strcmp(fd_entry->d_name, ".") || !strcmp(fd_entry->d_name, ".."))
+ continue;
+
+ char link_path[FILENAME_MAX + 1];
+ char link_target[FILENAME_MAX + 1];
+ int inode;
+
+ // Build the path to the file descriptor link
+ snprintfz(link_path, FILENAME_MAX, "%s/%s", path_buffer, fd_entry->d_name);
+
+ ssize_t len = readlink(link_path, link_target, sizeof(link_target) - 1);
+ if (len == -1) {
+ if(++errors_encountered < MAX_ERROR_LOGS)
+ collector_error("LOCAL-LISTENERS: cannot read link '%s'", link_path);
+
+ pid_fds_failed++;
+ continue;
+ }
+ link_target[len] = '\0';
+
+ pid_fds_processed++;
+
+ // If the link target indicates a socket, print its inode number
+ if (sscanf(link_target, "socket:[%d]", &inode) == 1)
+ found_this_socket_inode((pid_t)strtoul(proc_entry->d_name, NULL, 10), inode);
+ }
+
+ closedir(fd_dir);
+ }
+
+ closedir(proc_dir);
+ return true;
+}
+
+// ----------------------------------------------------------------------------
+
+static inline void add_port_and_inode(PROC_NET_PROTOCOLS protocol, unsigned int port, unsigned int inode, char *local_address) {
+ insertHashTable(hashTable_key_inode_port_value, inode, port, protocol, local_address);
+}
+
+static inline void print_ipv6_address(const char *ipv6_str, char *dst) {
+ unsigned k;
+ char buf[9];
+ struct sockaddr_in6 sa;
+
+ // Initialize sockaddr_in6
+ memset(&sa, 0, sizeof(struct sockaddr_in6));
+ sa.sin6_family = AF_INET6;
+ sa.sin6_port = htons(0); // replace 0 with your port number
+
+ // Convert hex string to byte array
+ for (k = 0; k < 4; ++k)
+ {
+ memset(buf, 0, 9);
+ memcpy(buf, ipv6_str + (k * 8), 8);
+ sa.sin6_addr.s6_addr32[k] = strtoul(buf, NULL, 16);
+ }
+
+ // Convert to human-readable format
+ if (inet_ntop(AF_INET6, &(sa.sin6_addr), dst, INET6_ADDRSTRLEN) == NULL)
+ *dst = '\0';
+}
+
+static inline void print_ipv4_address(uint32_t address, char *dst) {
+ uint8_t octets[4];
+ octets[0] = address & 0xFF;
+ octets[1] = (address >> 8) & 0xFF;
+ octets[2] = (address >> 16) & 0xFF;
+ octets[3] = (address >> 24) & 0xFF;
+ sprintf(dst, "%u.%u.%u.%u", octets[0], octets[1], octets[2], octets[3]);
+}
+
+bool read_proc_net_x(const char *filename, PROC_NET_PROTOCOLS protocol) {
+ FILE *fp;
+ char *line = NULL;
+ size_t len = 0;
+ ssize_t read;
+ char address[INET6_ADDRSTRLEN];
+
+ ssize_t min_line_length = (protocol == PROC_NET_PROTOCOL_TCP || protocol == PROC_NET_PROTOCOL_UDP) ? 105 : 155;
+
+ fp = fopen(filename, "r");
+ if (fp == NULL)
+ return false;
+
+ // Read line by line
+ while ((read = getline(&line, &len, fp)) != -1) {
+ if(read < min_line_length) continue;
+
+ char local_address6[33], rem_address6[33];
+ unsigned int local_address, local_port, state, rem_address, rem_port, inode;
+
+ switch(protocol) {
+ case PROC_NET_PROTOCOL_TCP:
+ if(line[34] != '0' || line[35] != 'A')
+ continue;
+ // fall-through
+
+ case PROC_NET_PROTOCOL_UDP:
+ if (sscanf(line, "%*d: %X:%X %X:%X %X %*X:%*X %*X:%*X %*X %*d %*d %u",
+ &local_address, &local_port, &rem_address, &rem_port, &state, &inode) != 6)
+ continue;
+
+ print_ipv4_address(local_address, address);
+ break;
+
+ case PROC_NET_PROTOCOL_TCP6:
+ if(line[82] != '0' || line[83] != 'A')
+ continue;
+ // fall-through
+
+ case PROC_NET_PROTOCOL_UDP6:
+ if(sscanf(line, "%*d: %32[0-9A-Fa-f]:%X %32[0-9A-Fa-f]:%X %X %*X:%*X %*X:%*X %*X %*d %*d %u",
+ local_address6, &local_port, rem_address6, &rem_port, &state, &inode) != 6)
+ continue;
+
+ print_ipv6_address(local_address6, address);
+ break;
+ }
+
+ add_port_and_inode(protocol, local_port, inode, address);
+ }
+
+ fclose(fp);
+ if (line)
+ free(line);
+
+ return true;
+}
+
+// ----------------------------------------------------------------------------
+
+int main(int argc __maybe_unused, char **argv __maybe_unused) {
+ char path[FILENAME_MAX + 1];
+ hashTable_key_inode_port_value = createHashTable();
+
+ netdata_configured_host_prefix = getenv("NETDATA_HOST_PREFIX");
+ if(!netdata_configured_host_prefix) netdata_configured_host_prefix = "";
+
+ snprintfz(path, FILENAME_MAX, "%s/proc/net/tcp", netdata_configured_host_prefix);
+ read_proc_net_x(path, PROC_NET_PROTOCOL_TCP);
+
+ snprintfz(path, FILENAME_MAX, "%s/proc/net/udp", netdata_configured_host_prefix);
+ read_proc_net_x(path, PROC_NET_PROTOCOL_UDP);
+
+ snprintfz(path, FILENAME_MAX, "%s/proc/net/tcp6", netdata_configured_host_prefix);
+ read_proc_net_x(path, PROC_NET_PROTOCOL_TCP6);
+
+ snprintfz(path, FILENAME_MAX, "%s/proc/net/udp6", netdata_configured_host_prefix);
+ read_proc_net_x(path, PROC_NET_PROTOCOL_UDP6);
+
+ snprintfz(path, FILENAME_MAX, "%s/proc", netdata_configured_host_prefix);
+ find_all_sockets_in_proc(path);
+
+ freeHashTable(hashTable_key_inode_port_value);
+ return 0;
+}