// fluxstream.c
// FluxStream by NLD 2026

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <signal.h>
#include <errno.h>
#include <ifaddrs.h>
#include <netdb.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <math.h>

#define PORT 9999  // Zabbix can use it 
#define BUFFER_SIZE 1024
#define MAX_CLIENTS 10
#define VERSION "1.1"
#define UPDATE_INTERVAL 1  // Seconds between stats updates

// UDP broadcast settings
#define UDP_SERVER_IP "<MULTICAST TREE IP>"
#define UDP_SERVER_PORT 9998

// Comment this line to disable debug output and run as daemon
//#define DEBUG

// ================================================
// CONFIGURATION - CHOOSE ONE:
// ================================================

// For ASUS/TrendChip routers with hardware counters:
#define ASUS_HARDWARE_COUNTERS

// For generic Linux routers with standard interfaces:
// #define GENERIC_ROUTER
// #define WAN_INTERFACE "eth0"  // Change to your WAN interface

// ================================================

// Global stats (protected by mutex)
typedef struct {
    int tcp_count;
    int udp_count; 
    int icmp_count;
    unsigned long long rx_bytes;      // Received bytes on WAN
    unsigned long long tx_bytes;      // Transmitted bytes on WAN
    unsigned long long prev_rx_bytes; // Previous reading for delta
    unsigned long long prev_tx_bytes; // Previous reading for delta
    float rx_mbps;                   // RX throughput in Mbps
    float tx_mbps;                   // TX throughput in Mbps
    int data_ready;
} conn_stats_t;

conn_stats_t current_stats = {0, 0, 0, 0, 0, 0, 0, 0.0, 0.0, 0};
pthread_mutex_t stats_mutex = PTHREAD_MUTEX_INITIALIZER;
char router_ip[INET_ADDRSTRLEN] = "";

// Debug output macro
#ifdef DEBUG
    #define DBG_PRINT(...) printf(__VA_ARGS__)
    #define DBG_PERROR(msg) perror(msg)
#else
    #define DBG_PRINT(...) 
    #define DBG_PERROR(msg)
#endif

// Function prototypes
void daemonize();
int get_router_ip();
int is_router_connection(const char *line);
int get_traffic_stats(unsigned long long *rx, unsigned long long *tx);
void calculate_throughput(conn_stats_t *stats);
void send_udp_stats();
void update_connection_stats();
void* stats_collector_thread(void* arg);
void* handle_client(void* client_socket_ptr);
void* server_thread(void* arg);

// ================================================
// ASUS HARDWARE COUNTERS IMPLEMENTATION
// ================================================
#ifdef ASUS_HARDWARE_COUNTERS

// Read TrendChip hardware switch statistics for WAN port (Port 4)
int get_hardware_wan_stats(unsigned long long *rx, unsigned long long *tx) {
    FILE *fp;
    char line[256];
    static int wan_port = 4;  // Port 4 is WAN on ASUS DSL-AC52U
    static unsigned long long prev_rx_total = 0, prev_tx_total = 0;
    static int first_run = 1;
    unsigned long long rx_total = 0, tx_total = 0;
    int current_port = -1;
    int found_port = 0;
    
    fp = fopen("/proc/tc3162/gsw_stats", "r");
    if (fp == NULL) {
        return -1;
    }
    
    while (fgets(line, sizeof(line), fp)) {
        if (strstr(line, "[ Port")) {
            sscanf(line, "[ Port %d ]", &current_port);
        }
        else if (current_port == wan_port && strstr(line, "Tx Bytes") && strstr(line, "Rx Bytes")) {
            char *tx_ptr = strstr(line, "Tx Bytes");
            char *rx_ptr = strstr(line, "Rx Bytes");
            
            if (tx_ptr) sscanf(tx_ptr, "Tx Bytes = 0x%llx", &tx_total);
            if (rx_ptr) sscanf(rx_ptr, "Rx Bytes = 0x%llx", &rx_total);
            
            found_port = 1;
            break;
        }
    }
    
    fclose(fp);
    
    if (!found_port) {
        return -1;
    }
    
    if (first_run) {
        prev_rx_total = rx_total;
        prev_tx_total = tx_total;
        *rx = 0;
        *tx = 0;
        first_run = 0;
        return 0;
    }
    
    // Calculate deltas with wrap-around handling
    if (rx_total >= prev_rx_total) {
        *rx = rx_total - prev_rx_total;
    } else {
        *rx = rx_total + (0xFFFFFFFF - prev_rx_total) + 1;
    }
    
    if (tx_total >= prev_tx_total) {
        *tx = tx_total - prev_tx_total;
    } else {
        *tx = tx_total + (0xFFFFFFFF - prev_tx_total) + 1;
    }
    
    prev_rx_total = rx_total;
    prev_tx_total = tx_total;
    
    return 0;
}

#endif // ASUS_HARDWARE_COUNTERS

// ================================================
// GENERIC LINUX INTERFACE COUNTERS IMPLEMENTATION
// ================================================
#ifdef GENERIC_ROUTER

// Get interface stats from /sys/class/net (most reliable)
int get_interface_stats(const char *iface, unsigned long long *rx, unsigned long long *tx) {
    char rx_path[128], tx_path[128];
    FILE *fp;
    
    // Build paths to /sys statistics
    snprintf(rx_path, sizeof(rx_path), "/sys/class/net/%s/statistics/rx_bytes", iface);
    snprintf(tx_path, sizeof(tx_path), "/sys/class/net/%s/statistics/tx_bytes", iface);
    
    // Read RX bytes
    fp = fopen(rx_path, "r");
    if (fp == NULL) {
        return -1;
    }
    
    if (fscanf(fp, "%llu", rx) != 1) {
        fclose(fp);
        return -1;
    }
    fclose(fp);
    
    // Read TX bytes
    fp = fopen(tx_path, "r");
    if (fp == NULL) {
        return -1;
    }
    
    if (fscanf(fp, "%llu", tx) != 1) {
        fclose(fp);
        return -1;
    }
    fclose(fp);
    
    return 0;
}

// Get generic interface traffic stats
int get_generic_traffic_stats(unsigned long long *rx, unsigned long long *tx) {
    static unsigned long long prev_rx_total = 0, prev_tx_total = 0;
    static int first_run = 1;
    unsigned long long rx_total = 0, tx_total = 0;
    
    if (get_interface_stats(WAN_INTERFACE, &rx_total, &tx_total) != 0) {
        return -1;
    }
    
    if (first_run) {
        prev_rx_total = rx_total;
        prev_tx_total = tx_total;
        *rx = 0;
        *tx = 0;
        first_run = 0;
        return 0;
    }
    
    // Calculate deltas
    *rx = rx_total - prev_rx_total;
    *tx = tx_total - prev_tx_total;
    
    prev_rx_total = rx_total;
    prev_tx_total = tx_total;
    
    return 0;
}

#endif // GENERIC_ROUTER

// ================================================
// COMMON FUNCTIONS
// ================================================

// Function to daemonize the process
void daemonize() {
    pid_t pid;
    
    pid = fork();
    if (pid < 0) exit(EXIT_FAILURE);
    if (pid > 0) exit(EXIT_SUCCESS);
    
    if (setsid() < 0) exit(EXIT_FAILURE);
    
    pid = fork();
    if (pid < 0) exit(EXIT_FAILURE);
    if (pid > 0) exit(EXIT_SUCCESS);
    
    chdir("/");
    umask(0);
    
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);
    
    open("/dev/null", O_RDONLY);
    open("/dev/null", O_WRONLY);  
    open("/dev/null", O_WRONLY);
}

// Function to get router's LAN IP address
int get_router_ip() {
    struct ifaddrs *ifaddr, *ifa;
    int found = 0;
    
    if (getifaddrs(&ifaddr) == -1) {
        DBG_PERROR("getifaddrs");
        return -1;
    }
    
    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
        if (ifa->ifa_addr == NULL) continue;
        
        if (ifa->ifa_addr->sa_family == AF_INET) {
            struct sockaddr_in *addr = (struct sockaddr_in *)ifa->ifa_addr;
            char ip[INET_ADDRSTRLEN];
            
            inet_ntop(AF_INET, &addr->sin_addr, ip, INET_ADDRSTRLEN);
            
            if (strcmp(ifa->ifa_name, "br0") == 0) {
                strncpy(router_ip, ip, INET_ADDRSTRLEN);
                found = 1;
                break;
            }
            else if (strcmp(ip, "127.0.0.1") != 0 && 
                     strncmp(ifa->ifa_name, "ppp", 3) != 0 &&
                     strncmp(ifa->ifa_name, "nas", 3) != 0) {
                strncpy(router_ip, ip, INET_ADDRSTRLEN);
                found = 1;
            }
        }
    }
    
    freeifaddrs(ifaddr);
    return found ? 0 : -1;
}

// Function to check if connection is to router itself
int is_router_connection(const char *line) {
    char search_pattern[64];
    
    if (strlen(router_ip) == 0) return 0;
    
    snprintf(search_pattern, sizeof(search_pattern), "dst=%s", router_ip);
    return strstr(line, search_pattern) != NULL;
}

// Get traffic stats based on configuration
int get_traffic_stats(unsigned long long *rx, unsigned long long *tx) {
#ifdef ASUS_HARDWARE_COUNTERS
    if (get_hardware_wan_stats(rx, tx) == 0) {
        return 0;
    }
#endif

#ifdef GENERIC_ROUTER
    if (get_generic_traffic_stats(rx, tx) == 0) {
        return 0;
    }
#endif

    // No traffic stats available
    *rx = 0;
    *tx = 0;
    return 0;
}

// Function to calculate throughput in Mbps with floating point
void calculate_throughput(conn_stats_t *stats) {
    static struct timespec prev_time = {0, 0};
    struct timespec curr_time;
    double elapsed_seconds;
    
    clock_gettime(CLOCK_MONOTONIC, &curr_time);
    
    if (prev_time.tv_sec > 0) {
        elapsed_seconds = (curr_time.tv_sec - prev_time.tv_sec) +
                         (curr_time.tv_nsec - prev_time.tv_nsec) / 1e9;
        
        if (elapsed_seconds > 0) {
            // Convert bytes/sec to Mbps (1 byte = 8 bits, 1 Mbps = 1,000,000 bits)
            stats->rx_mbps = (stats->rx_bytes * 8.0) / (elapsed_seconds * 1000000.0);
            stats->tx_mbps = (stats->tx_bytes * 8.0) / (elapsed_seconds * 1000000.0);
        }
    }
    
    prev_time = curr_time;
}

// Function to send UDP packet
void send_udp_stats() {
    int sockfd;
    struct sockaddr_in server_addr;
    char buffer[BUFFER_SIZE];
    conn_stats_t stats;
    
    pthread_mutex_lock(&stats_mutex);
    stats = current_stats;
    pthread_mutex_unlock(&stats_mutex);
    
    if (!stats.data_ready) return;
    
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        DBG_PERROR("UDP socket creation failed");
        return;
    }
    
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(UDP_SERVER_PORT);
    
    if (inet_pton(AF_INET, UDP_SERVER_IP, &server_addr.sin_addr) <= 0) {
        DBG_PERROR("Invalid UDP server address");
        close(sockfd);
        return;
    }
    
    // Format: TCP_COUNT,UDP_COUNT,ICMP_COUNT,RX_MBPS,TX_MBPS
    snprintf(buffer, sizeof(buffer), "%d,%d,%d,%.2f,%.2f\n", 
             stats.tcp_count, stats.udp_count, stats.icmp_count,
             stats.rx_mbps, stats.tx_mbps);
    
    sendto(sockfd, buffer, strlen(buffer), 0, 
           (const struct sockaddr *)&server_addr, sizeof(server_addr));
    
    close(sockfd);
}

// Function to read connection stats from /proc/net
void update_connection_stats() {
    FILE *fp;
    char line[1024];
    int tcp = 0, udp = 0, icmp = 0;
    unsigned long long rx_bytes = 0, tx_bytes = 0;
    
    // Read conntrack
    fp = fopen("/proc/net/nf_conntrack", "r");
    if (fp != NULL) {
        while (fgets(line, sizeof(line), fp)) {
            if (is_router_connection(line)) continue;
            
            if (strstr(line, "tcp")) tcp++;
            else if (strstr(line, "udp")) udp++;
            else if (strstr(line, "icmp")) icmp++;
        }
        fclose(fp);
    } else {
        fp = fopen("/proc/net/ip_conntrack", "r");
        if (fp != NULL) {
            while (fgets(line, sizeof(line), fp)) {
                if (is_router_connection(line)) continue;
                
                if (strstr(line, "tcp")) tcp++;
                else if (strstr(line, "udp")) udp++;
                else if (strstr(line, "icmp")) icmp++;
            }
            fclose(fp);
        }
    }
    
    // Get traffic stats
    get_traffic_stats(&rx_bytes, &tx_bytes);
    
    // Update global stats
    pthread_mutex_lock(&stats_mutex);
    current_stats.tcp_count = tcp;
    current_stats.udp_count = udp;
    current_stats.icmp_count = icmp;
    current_stats.rx_bytes = rx_bytes;
    current_stats.tx_bytes = tx_bytes;
    
    calculate_throughput(&current_stats);
    
    current_stats.data_ready = 1;
    pthread_mutex_unlock(&stats_mutex);
    
    DBG_PRINT("Stats: TCP=%d, UDP=%d, ICMP=%d, RX=%.2f Mbps, TX=%.2f Mbps\n", 
              tcp, udp, icmp, current_stats.rx_mbps, current_stats.tx_mbps);
    
    send_udp_stats();
}

// Background thread to periodically update stats
void* stats_collector_thread(void* arg) {
    DBG_PRINT("Stats collector started\n");
    
    while (1) {
        update_connection_stats();
        sleep(UPDATE_INTERVAL);
    }
    return NULL;
}

// Handle individual client connections
void* handle_client(void* client_socket_ptr) {
    int client_socket = *(int*)client_socket_ptr;
    free(client_socket_ptr);
    
    char buffer[BUFFER_SIZE];
    conn_stats_t stats;
    
    char dummy[BUFFER_SIZE];
    recv(client_socket, dummy, sizeof(dummy), MSG_DONTWAIT);
    
    pthread_mutex_lock(&stats_mutex);
    stats = current_stats;
    pthread_mutex_unlock(&stats_mutex);
    
    if (stats.data_ready) {
        snprintf(buffer, sizeof(buffer), "%d,%d,%d,%.2f,%.2f\n", 
                 stats.tcp_count, stats.udp_count, stats.icmp_count,
                 stats.rx_mbps, stats.tx_mbps);
    } else {
        snprintf(buffer, sizeof(buffer), "0,0,0,0.00,0.00\n");
    }
    
    send(client_socket, buffer, strlen(buffer), 0);
    
    shutdown(client_socket, SHUT_WR);
    struct timeval tv = {0, 100000};
    setsockopt(client_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
    recv(client_socket, dummy, sizeof(dummy), 0);
    
    close(client_socket);
    return NULL;
}

// Main server thread
void* server_thread(void* arg) {
    int server_fd, client_socket;
    struct sockaddr_in address;
    int opt = 1;
    socklen_t addrlen = sizeof(address);
    
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        DBG_PERROR("socket failed");
        exit(EXIT_FAILURE);
    }
    
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);
    
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        DBG_PERROR("bind failed");
        exit(EXIT_FAILURE);
    }
    
    if (listen(server_fd, MAX_CLIENTS) < 0) {
        DBG_PERROR("listen");
        exit(EXIT_FAILURE);
    }
    
    DBG_PRINT("FluxStream listening on port %d\n", PORT);
    
    while (1) {
        client_socket = accept(server_fd, (struct sockaddr *)&address, &addrlen);
        if (client_socket < 0) continue;
        
        pthread_t client_thread;
        int *client_sock_ptr = malloc(sizeof(int));
        *client_sock_ptr = client_socket;
        
        if (pthread_create(&client_thread, NULL, handle_client, client_sock_ptr) != 0) {
            close(client_socket);
            free(client_sock_ptr);
        } else {
            pthread_detach(client_thread);
        }
    }
    
    close(server_fd);
    return NULL;
}

int main() {
    pthread_t server_tid, collector_tid;
    
    printf("========================================\n");
    printf("FluxStream by NLD 2026\n");
    printf("Version: %s\n", VERSION);
    
#ifdef ASUS_HARDWARE_COUNTERS
    printf("Mode: ASUS/TrendChip with hardware counters\n");
#endif
    
#ifdef GENERIC_ROUTER
    printf("Mode: Generic Linux router\n");
    printf("WAN Interface: %s\n", WAN_INTERFACE);
#endif
    
#if !defined(ASUS_HARDWARE_COUNTERS) && !defined(GENERIC_ROUTER)
    printf("Mode: Basic (connection stats only)\n");
#endif
    
    printf("========================================\n");
    
#ifndef DEBUG
    daemonize();
#endif
    
    if (get_router_ip() == 0) {
        DBG_PRINT("Router IP: %s\n", router_ip);
    }
    
    update_connection_stats();
    sleep(UPDATE_INTERVAL);
    
    pthread_create(&collector_tid, NULL, stats_collector_thread, NULL);
    pthread_create(&server_tid, NULL, server_thread, NULL);
    
    DBG_PRINT("FluxStream started\n");
    DBG_PRINT("  - Update interval: %d seconds\n", UPDATE_INTERVAL);
    DBG_PRINT("  - TCP port: %d\n", PORT);
    DBG_PRINT("  - UDP target: %s:%d\n", UDP_SERVER_IP, UDP_SERVER_PORT);
    DBG_PRINT("  - Output format: TCP,UDP,ICMP,RX_MBPS,TX_MBPS\\n\n");
    
    pthread_join(collector_tid, NULL);
    pthread_join(server_tid, NULL);
    
    return 0;
}
