diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2023-07-20 04:49:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2023-07-20 04:49:55 +0000 |
commit | ab1bb5b7f1c3c3a7b240ab7fc8661459ecd7decb (patch) | |
tree | 7a900833aad3ccc685712c6c2a7d87576d54f427 /collectors/plugins.d/local_listeners.c | |
parent | Adding upstream version 1.40.1. (diff) | |
download | netdata-878715cc218c4a71a71c4cb4b4634082aa9627ba.tar.xz netdata-878715cc218c4a71a71c4cb4b4634082aa9627ba.zip |
Adding upstream version 1.41.0.upstream/1.41.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'collectors/plugins.d/local_listeners.c')
-rw-r--r-- | collectors/plugins.d/local_listeners.c | 366 |
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; +} |