From 70b8caadbfef016780e0a6254b6a5c6d6c84c058 Mon Sep 17 00:00:00 2001 From: jek Date: Thu, 15 Jan 2026 12:16:39 +0400 Subject: [PATCH] Add utun VPN implementation and enhance async infrastructure - Add utun VPN daemon with config parsing, control socket, routing, and TUN interface - Refactor net_emulator/u_async to support multiple independent instances - Replace linked-list timeout management with heap-based implementation - Fix timeout processing bug (process timeouts after select) - Add comprehensive random timeout and instance isolation tests - Update Makefile to build utun and new test targets --- AGENTS.md | 18 + Makefile | 17 +- config_parser.c | 413 ++++++++++++++ config_parser.h | 117 ++++ control_socket.c | 463 ++++++++++++++++ control_socket.h | 208 +++++++ example_control.conf | 29 + monitor.py | 132 +++++ net_emulator/Makefile | 33 ++ net_emulator/net_emulator.c | 502 +++++++++++++++++ net_emulator/net_emulator.conf | 23 + net_emulator/net_emulator.conf.example | 12 + net_emulator/net_emulator.h | 90 +++ net_emulator/test_timeout_heap.c | 412 ++++++++++++++ net_emulator/test_uasync_random.c | 258 +++++++++ net_emulator/timeout_heap.c | 134 +++++ net_emulator/timeout_heap.h | 72 +++ net_emulator/u_async.c | 391 +++++++++++++ net_emulator/u_async.h | 45 ++ routing.c | 405 ++++++++++++++ routing.h | 170 ++++++ timeout_heap.c | 134 +++++ timeout_heap.h | 72 +++ tun_if.c | 238 ++++++++ tun_if.h | 95 ++++ u_async.c | 159 +++--- utun.c | 735 +++++++++++++++++++++++++ utun.conf.example | 31 ++ utun_state.h | 29 + utun_test.cfg | 21 + 30 files changed, 5380 insertions(+), 78 deletions(-) create mode 100755 AGENTS.md create mode 100644 config_parser.c create mode 100644 config_parser.h create mode 100644 control_socket.c create mode 100644 control_socket.h create mode 100644 example_control.conf create mode 100644 monitor.py create mode 100644 net_emulator/Makefile create mode 100644 net_emulator/net_emulator.c create mode 100644 net_emulator/net_emulator.conf create mode 100644 net_emulator/net_emulator.conf.example create mode 100644 net_emulator/net_emulator.h create mode 100644 net_emulator/test_timeout_heap.c create mode 100644 net_emulator/test_uasync_random.c create mode 100644 net_emulator/timeout_heap.c create mode 100644 net_emulator/timeout_heap.h create mode 100644 net_emulator/u_async.c create mode 100644 net_emulator/u_async.h create mode 100644 routing.c create mode 100644 routing.h create mode 100644 timeout_heap.c create mode 100644 timeout_heap.h create mode 100644 tun_if.c create mode 100644 tun_if.h create mode 100644 utun.c create mode 100644 utun.conf.example create mode 100644 utun_state.h create mode 100755 utun_test.cfg diff --git a/AGENTS.md b/AGENTS.md new file mode 100755 index 00000000..75b5885d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,18 @@ +# AGENTS.md - uTun Development Guide + +This document provides essential information for agentic coding assistants working on the uTun VPN tunnel project. +Совместимость со станым при доработках сохранять не надо. вместо этого надо доработать остальной код чтобы работало + +Если не работает - добавляй отладочную информацию чтобы быстрее найти проблемное место и не рушить работающий код. +если отладочная информация будет флудить сделай ее отключаемой или подумай как ограничить ее вывод по возможности сохраняя информативность. +Добавляй в код структуры для статистики (например суммируй число ошибок, вызовов и других потенциально нужных для анализа при ошибках метрик). +добавляй в тест подсчет времени сколько каждфй этап длился для лучшего понимания оптимизации + +веди changelog.txt: дата время: что поменялось + +## Code Style Guidelines + +комменты в коде на русском +Приоритет - простое (логически понятное, минималистичное api, но функциональное). Всегда надо думать как сделать проще и красивее. не надо нагромождать лишнего - сперва подумай действительно ли оно нужно. + +рассуждай на английском, остальное лучше на русском (отчёты, дальшейшие шаги и ответы на запросы всегда на русском) diff --git a/Makefile b/Makefile index f600dc17..acfdf01d 100644 --- a/Makefile +++ b/Makefile @@ -16,12 +16,14 @@ TINYCRYPT_SRCS := \ TINYCRYPT_OBJS := $(TINYCRYPT_SRCS:.c=.o) SC_LIB_OBJS := sc_lib.o -UASYNC_OBJS := u_async.o +UASYNC_OBJS := u_async.o timeout_heap.o PN_OBJS := pkt_normalizer.o settings.o LL_QUEUE_OBJS := ll_queue.o ETCP_OBJS := etcp.o +CONFIG_PARSER_OBJS := config_parser.o +TUN_IF_OBJS := tun_if.o -all: $(TEST_DIR)/test_ecc_encrypt $(TEST_DIR)/test_sc_lib $(TEST_DIR)/test_udp_secure $(TEST_DIR)/test_pkt_normalizer $(TEST_DIR)/test_etcp $(TEST_DIR)/test_etcp_stress $(TEST_DIR)/test_etcp_simple $(TEST_DIR)/test_connection $(TEST_DIR)/test_connection_stress $(TEST_DIR)/test_new_features +all: utun $(TEST_DIR)/test_ecc_encrypt $(TEST_DIR)/test_sc_lib $(TEST_DIR)/test_udp_secure $(TEST_DIR)/test_pkt_normalizer $(TEST_DIR)/test_etcp $(TEST_DIR)/test_etcp_stress $(TEST_DIR)/test_etcp_simple $(TEST_DIR)/test_connection $(TEST_DIR)/test_connection_stress $(TEST_DIR)/test_new_features $(TEST_DIR)/test_utun_integration $(TEST_DIR)/test_utun_fork $(TEST_DIR)/test_pkt_normalizer: $(TEST_DIR)/test_pkt_normalizer.o $(PN_OBJS) $(LL_QUEUE_OBJS) $(UASYNC_OBJS) $(CC) $(CFLAGS) $(INCLUDES) -o $@ $^ @@ -44,12 +46,21 @@ $(TEST_DIR)/test_connection_stress: $(TEST_DIR)/test_connection_stress.o connect $(TEST_DIR)/test_new_features: $(TEST_DIR)/test_new_features.o $(ETCP_OBJS) $(PN_OBJS) $(LL_QUEUE_OBJS) $(UASYNC_OBJS) connection.o $(SC_LIB_OBJS) $(TINYCRYPT_OBJS) $(CC) $(CFLAGS) $(INCLUDES) -o $@ $^ +utun: utun.o $(CONFIG_PARSER_OBJS) $(TUN_IF_OBJS) connection.o routing.o control_socket.o $(ETCP_OBJS) $(PN_OBJS) $(LL_QUEUE_OBJS) $(UASYNC_OBJS) $(SC_LIB_OBJS) $(TINYCRYPT_OBJS) + $(CC) $(CFLAGS) $(INCLUDES) -o $@ $^ + $(TEST_DIR)/test_sc_lib: $(TEST_DIR)/test_sc_lib.o $(SC_LIB_OBJS) $(TINYCRYPT_OBJS) $(CC) $(CFLAGS) $(INCLUDES) -o $@ $^ $(TEST_DIR)/test_udp_secure: $(TEST_DIR)/test_udp_secure.o $(SC_LIB_OBJS) $(UASYNC_OBJS) $(TINYCRYPT_OBJS) $(CC) $(CFLAGS) $(INCLUDES) -o $@ $^ +$(TEST_DIR)/test_utun_integration: $(TEST_DIR)/test_utun_integration.o $(CONFIG_PARSER_OBJS) $(TUN_IF_OBJS) connection.o $(ETCP_OBJS) $(PN_OBJS) $(LL_QUEUE_OBJS) $(UASYNC_OBJS) $(SC_LIB_OBJS) $(TINYCRYPT_OBJS) + $(CC) $(CFLAGS) $(INCLUDES) -o $@ $^ + +$(TEST_DIR)/test_utun_fork: $(TEST_DIR)/test_utun_fork.o $(SC_LIB_OBJS) $(TINYCRYPT_OBJS) + $(CC) $(CFLAGS) $(INCLUDES) -o $@ $^ + $(TEST_DIR)/test_pkt_normalizer: $(TEST_DIR)/test_pkt_normalizer.o $(PN_OBJS) $(LL_QUEUE_OBJS) $(UASYNC_OBJS) $(CC) $(CFLAGS) $(INCLUDES) -o $@ $^ @@ -60,7 +71,7 @@ $(TEST_DIR)/test_etcp: $(TEST_DIR)/test_etcp.o $(ETCP_OBJS) $(LL_QUEUE_OBJS) $(U $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@ clean: - rm -f $(TEST_DIR)/test_ecc_encrypt $(TEST_DIR)/test_sc_lib $(TEST_DIR)/test_udp_secure $(TEST_DIR)/test_pkt_normalizer $(TEST_DIR)/test_etcp $(TEST_DIR)/test_etcp_stress $(TEST_DIR)/test_etcp_simple $(TEST_DIR)/test_connection $(TEST_DIR)/test_connection_stress $(TEST_DIR)/test_new_features \ + rm -f utun $(TEST_DIR)/test_ecc_encrypt $(TEST_DIR)/test_sc_lib $(TEST_DIR)/test_udp_secure $(TEST_DIR)/test_pkt_normalizer $(TEST_DIR)/test_etcp $(TEST_DIR)/test_etcp_stress $(TEST_DIR)/test_etcp_simple $(TEST_DIR)/test_connection $(TEST_DIR)/test_connection_stress $(TEST_DIR)/test_new_features $(TEST_DIR)/test_utun_integration $(TEST_DIR)/test_utun_fork \ *.o tinycrypt/lib/source/*.o $(TEST_DIR)/*.o .PHONY: all clean \ No newline at end of file diff --git a/config_parser.c b/config_parser.c new file mode 100644 index 00000000..8b277aec --- /dev/null +++ b/config_parser.c @@ -0,0 +1,413 @@ +// config_parser.c - Configuration parser for utun application +#define _POSIX_C_SOURCE 200809L +#include "config_parser.h" +#include +#include +#include +#include +#include +#include + +#define MAX_LINE_LEN 1024 +#define INITIAL_CONNECTION_CAPACITY 4 + +// Helper function to trim whitespace +static char* trim(char *str) { + if (!str) return NULL; + + // Trim leading spaces + while (isspace((unsigned char)*str)) str++; + + // Trim trailing spaces + char *end = str + strlen(str) - 1; + while (end > str && isspace((unsigned char)*end)) end--; + *(end + 1) = '\0'; + + return str; +} + +// Parse a key-value pair +static int parse_key_value(const char *line, char *key, size_t key_len, + char *value, size_t value_len) { + char *equal = strchr(line, '='); + if (!equal) return -1; + + // Extract key + size_t key_size = equal - line; + if (key_size >= key_len) return -1; + strncpy(key, line, key_size); + key[key_size] = '\0'; + trim(key); + + // Extract value + const char *val_start = equal + 1; + size_t val_len = strlen(val_start); + if (val_len >= value_len) return -1; + strcpy(value, val_start); + trim(value); + + return 0; +} + +// Parse connection mode string +static config_conn_mode_t parse_mode(const char *mode_str) { + if (!mode_str) return CONFIG_MODE_UNKNOWN; + + if (strcasecmp(mode_str, "client") == 0) { + return CONFIG_MODE_CLIENT; + } else if (strcasecmp(mode_str, "server") == 0) { + return CONFIG_MODE_SERVER; + } + + return CONFIG_MODE_UNKNOWN; +} + +// Add a connection to the config +static int add_connection(utun_config_t *config, const connection_config_t *conn) { + if (config->connection_count >= config->connection_capacity) { + // Resize array + int new_capacity = config->connection_capacity * 2; + connection_config_t *new_connections = realloc(config->connections, + new_capacity * sizeof(connection_config_t)); + if (!new_connections) return -1; + + config->connections = new_connections; + config->connection_capacity = new_capacity; + } + + // Copy connection data + memcpy(&config->connections[config->connection_count], conn, sizeof(connection_config_t)); + config->connection_count++; + + return 0; +} + +// Parse configuration file +utun_config_t* parse_config(const char *filename) { + FILE *fp = fopen(filename, "r"); + if (!fp) { + fprintf(stderr, "Failed to open config file: %s\n", filename); + return NULL; + } + + // Allocate config structure + utun_config_t *config = calloc(1, sizeof(utun_config_t)); + if (!config) { + fclose(fp); + return NULL; + } + + // Initialize connection array + config->connection_capacity = INITIAL_CONNECTION_CAPACITY; + config->connections = malloc(config->connection_capacity * sizeof(connection_config_t)); + if (!config->connections) { + free(config); + fclose(fp); + return NULL; + } + + char line[MAX_LINE_LEN]; + char current_section[128] = ""; + connection_config_t current_conn = {0}; + int in_connection_section = 0; + + while (fgets(line, sizeof(line), fp)) { + // Remove newline + line[strcspn(line, "\n")] = '\0'; + + char *trimmed = trim(line); + + // Skip empty lines and comments + if (strlen(trimmed) == 0 || trimmed[0] == ';' || trimmed[0] == '#') { + continue; + } + + // Check for section header + if (trimmed[0] == '[' && trimmed[strlen(trimmed) - 1] == ']') { + // End previous connection section if any + if (in_connection_section) { + if (strlen(current_conn.name) > 0) { + if (add_connection(config, ¤t_conn) != 0) { + fprintf(stderr, "Failed to add connection: %s\n", current_conn.name); + } + } + memset(¤t_conn, 0, sizeof(current_conn)); + in_connection_section = 0; + } + + // Extract section name + strncpy(current_section, trimmed + 1, sizeof(current_section) - 1); + current_section[sizeof(current_section) - 1] = '\0'; + current_section[strcspn(current_section, "]")] = '\0'; + trim(current_section); + + // Check if it's a connection section + if (strncmp(current_section, "connection:", 11) == 0) { + in_connection_section = 1; + // Extract connection name + char *name = trim(current_section + 11); + if (strlen(name) > 0) { + strncpy(current_conn.name, name, sizeof(current_conn.name) - 1); + } + } + + continue; + } + + // Parse key-value pair + char key[256], value[256]; + if (parse_key_value(trimmed, key, sizeof(key), value, sizeof(value)) != 0) { + fprintf(stderr, "Invalid key-value line: %s\n", trimmed); + continue; + } + + // Process based on current section + if (strcasecmp(current_section, "global") == 0) { + if (strcasecmp(key, "my_private_key") == 0) { + strncpy(config->global.my_private_key_hex, value, sizeof(config->global.my_private_key_hex) - 1); + } else if (strcasecmp(key, "my_public_key") == 0) { + strncpy(config->global.my_public_key_hex, value, sizeof(config->global.my_public_key_hex) - 1); + } else if (strcasecmp(key, "option") == 0) { + strncpy(config->global.option_value, value, sizeof(config->global.option_value) - 1); + } else if (strcasecmp(key, "control_ip") == 0) { + strncpy(config->global.control_ip, value, sizeof(config->global.control_ip) - 1); + } else if (strcasecmp(key, "control_port") == 0) { + config->global.control_port = atoi(value); + } else if (strcasecmp(key, "net_debug") == 0) { + config->global.net_debug = atoi(value); + } + } else if (strcasecmp(current_section, "routing") == 0) { + if (strcasecmp(key, "allowed_subnet") == 0) { + if (config->allowed_subnet_count < MAX_ALLOWED_SUBNETS) { + strncpy(config->allowed_subnets[config->allowed_subnet_count].subnet, value, sizeof(config->allowed_subnets[0].subnet) - 1); + config->allowed_subnet_count++; + } else { + fprintf(stderr, "Too many allowed subnets, maximum is %d\n", MAX_ALLOWED_SUBNETS); + } + } + } else if (in_connection_section) { + if (strcasecmp(key, "mode") == 0) { + current_conn.mode = parse_mode(value); + } else if (strcasecmp(key, "addr") == 0) { + // For server mode + if (current_conn.mode == CONFIG_MODE_SERVER) { + strncpy(current_conn.local_addr, value, sizeof(current_conn.local_addr) - 1); + } + } else if (strcasecmp(key, "from_addr") == 0) { + // For client mode + if (current_conn.mode == CONFIG_MODE_CLIENT) { + strncpy(current_conn.local_addr, value, sizeof(current_conn.local_addr) - 1); + } + } else if (strcasecmp(key, "to_addr") == 0) { + // For client mode + if (current_conn.mode == CONFIG_MODE_CLIENT) { + strncpy(current_conn.remote_addr, value, sizeof(current_conn.remote_addr) - 1); + } + } else if (strcasecmp(key, "peer_public_key") == 0) { + strncpy(current_conn.peer_public_key_hex, value, sizeof(current_conn.peer_public_key_hex) - 1); + } else if (strcasecmp(key, "so_mark") == 0) { + current_conn.so_mark = atoi(value); + } else if (strcasecmp(key, "netif") == 0) { + strncpy(current_conn.netif, value, sizeof(current_conn.netif) - 1); + } else if (strcasecmp(key, "tun") == 0) { + strncpy(current_conn.tun_ifname, value, sizeof(current_conn.tun_ifname) - 1); + } else if (strcasecmp(key, "tun_ip") == 0) { + strncpy(current_conn.tun_ip, value, sizeof(current_conn.tun_ip) - 1); + } + } + } + + // Add last connection if any + if (in_connection_section && strlen(current_conn.name) > 0) { + if (add_connection(config, ¤t_conn) != 0) { + fprintf(stderr, "Failed to add connection: %s\n", current_conn.name); + } + } + + fclose(fp); + return config; +} + +// Free configuration structure +void free_config(utun_config_t *config) { + if (!config) return; + + if (config->connections) { + free(config->connections); + } + free(config); +} + +// Print configuration for debugging +void print_config(const utun_config_t *config) { + if (!config) { + printf("Configuration is NULL\n"); + return; + } + + printf("Global configuration:\n"); + printf(" my_private_key: %s\n", config->global.my_private_key_hex); + printf(" my_public_key: %s\n", config->global.my_public_key_hex); + printf(" option: %s\n", config->global.option_value); + printf(" control_ip: %s\n", config->global.control_ip); + printf(" control_port: %u\n", config->global.control_port); + printf(" net_debug: %d\n", config->global.net_debug); + printf("\n"); + printf("Allowed subnets (%d):\n", config->allowed_subnet_count); + for (int i = 0; i < config->allowed_subnet_count; i++) { + printf(" %s\n", config->allowed_subnets[i].subnet); + } + printf("\n"); + + printf("Connections (%d):\n", config->connection_count); + for (int i = 0; i < config->connection_count; i++) { + const connection_config_t *conn = &config->connections[i]; + printf(" [%d] %s:\n", i, conn->name); + printf(" mode: %s\n", + conn->mode == CONFIG_MODE_CLIENT ? "client" : + conn->mode == CONFIG_MODE_SERVER ? "server" : "unknown"); + printf(" local_addr: %s\n", conn->local_addr); + printf(" remote_addr: %s\n", conn->remote_addr); + printf(" peer_public_key: %s\n", conn->peer_public_key_hex); + printf(" so_mark: %d\n", conn->so_mark); + printf(" netif: %s\n", conn->netif); + printf(" tun: %s\n", conn->tun_ifname); + printf(" tun_ip: %s\n", conn->tun_ip); + } +} + +// Update keys in configuration file +int update_config_keys(const char *filename, + const char *private_key_hex, + const char *public_key_hex) { + if (!filename || !private_key_hex || !public_key_hex) { + errno = EINVAL; + return -1; + } + + // Read entire file + FILE *fp = fopen(filename, "r"); + if (!fp) { + // Try to create new file + fp = fopen(filename, "w"); + if (!fp) return -1; + + fprintf(fp, "[global]\n"); + fprintf(fp, "my_private_key=%s\n", private_key_hex); + fprintf(fp, "my_public_key=%s\n", public_key_hex); + fclose(fp); + return 0; + } + + // Read all lines + char **lines = NULL; + size_t line_count = 0; + size_t capacity = 0; + char line[MAX_LINE_LEN]; + int in_global_section = 0; + int private_key_found = 0; + int public_key_found = 0; + size_t private_key_line = 0; + size_t public_key_line = 0; + + while (fgets(line, sizeof(line), fp)) { + // Add line to array + if (line_count >= capacity) { + size_t new_capacity = capacity ? capacity * 2 : 16; + char **new_lines = realloc(lines, new_capacity * sizeof(char *)); + if (!new_lines) { + // Cleanup + for (size_t i = 0; i < line_count; i++) free(lines[i]); + free(lines); + fclose(fp); + return -1; + } + lines = new_lines; + capacity = new_capacity; + } + + lines[line_count] = strdup(line); + if (!lines[line_count]) { + // Cleanup + for (size_t i = 0; i < line_count; i++) free(lines[i]); + free(lines); + fclose(fp); + return -1; + } + + char *trimmed = trim(line); + + // Track global section + if (trimmed[0] == '[' && trimmed[strlen(trimmed) - 1] == ']') { + char section[128]; + strncpy(section, trimmed + 1, sizeof(section) - 1); + section[sizeof(section) - 1] = '\0'; + section[strcspn(section, "]")] = '\0'; + trim(section); + + in_global_section = (strcasecmp(section, "global") == 0); + } + + // Check for keys in global section + if (in_global_section) { + char key[256], value[256]; + if (parse_key_value(trimmed, key, sizeof(key), value, sizeof(value)) == 0) { + if (strcasecmp(key, "my_private_key") == 0) { + private_key_found = 1; + private_key_line = line_count; + } else if (strcasecmp(key, "my_public_key") == 0) { + public_key_found = 1; + public_key_line = line_count; + } + } + } + + line_count++; + } + fclose(fp); + + // Update or add keys + if (private_key_found) { + free(lines[private_key_line]); + char new_line[MAX_LINE_LEN]; + snprintf(new_line, sizeof(new_line), "my_private_key=%s\n", private_key_hex); + lines[private_key_line] = strdup(new_line); + } else { + // Add after [global] line or at beginning + // Simplified: add at end + // We'll need to insert after [global] but for simplicity, append + } + + if (public_key_found) { + free(lines[public_key_line]); + char new_line[MAX_LINE_LEN]; + snprintf(new_line, sizeof(new_line), "my_public_key=%s\n", public_key_hex); + lines[public_key_line] = strdup(new_line); + } + + // Write back to file + fp = fopen(filename, "w"); + if (!fp) { + // Cleanup + for (size_t i = 0; i < line_count; i++) free(lines[i]); + free(lines); + return -1; + } + + for (size_t i = 0; i < line_count; i++) { + fputs(lines[i], fp); + free(lines[i]); + } + + // Add missing keys + if (!private_key_found) { + fprintf(fp, "my_private_key=%s\n", private_key_hex); + } + if (!public_key_found) { + fprintf(fp, "my_public_key=%s\n", public_key_hex); + } + + free(lines); + fclose(fp); + return 0; +} \ No newline at end of file diff --git a/config_parser.h b/config_parser.h new file mode 100644 index 00000000..6f776b30 --- /dev/null +++ b/config_parser.h @@ -0,0 +1,117 @@ +// config_parser.h - Configuration parser for utun application +#ifndef CONFIG_PARSER_H +#define CONFIG_PARSER_H + +#include +#include +#include "connection.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Maximum lengths for strings in config +#define MAX_CONN_NAME_LEN 64 +#define MAX_KEY_LEN 256 // HEX key length (64 bytes * 2 chars + null) +#define MAX_ADDR_LEN 64 +#define MAX_NETIF_LEN 16 +#define MAX_OPTION_VALUE_LEN 256 +#define MAX_ALLOWED_SUBNETS 32 + +// Connection mode (config parser internal) +typedef enum { + CONFIG_MODE_UNKNOWN = 0, + CONFIG_MODE_CLIENT, + CONFIG_MODE_SERVER +} config_conn_mode_t; + +// Subnet entry for allowed routing +typedef struct { + char subnet[MAX_ADDR_LEN]; // Format: "192.168.0.0/24" +} subnet_entry_t; + +// Connection configuration +typedef struct { + char name[MAX_CONN_NAME_LEN]; // Connection name from [connection: name] + config_conn_mode_t mode; // server or client + char local_addr[MAX_ADDR_LEN]; // For client: from_addr, for server: addr + char remote_addr[MAX_ADDR_LEN]; // For client: to_addr, for server: N/A + char peer_public_key_hex[MAX_KEY_LEN]; // Peer's public key in HEX + int so_mark; // Socket mark (0 = not set) + char netif[MAX_NETIF_LEN]; // Network interface to bind to + char tun_ifname[MAX_NETIF_LEN]; // TUN interface name (e.g., tun12) + char tun_ip[MAX_ADDR_LEN]; // TUN interface IP address (e.g., 10.0.0.1/24) +} connection_config_t; + +// Global configuration +typedef struct { + char my_private_key_hex[MAX_KEY_LEN]; // Our private key in HEX + char my_public_key_hex[MAX_KEY_LEN]; // Our public key in HEX + char option_value[MAX_OPTION_VALUE_LEN]; // Global option value + char control_ip[MAX_ADDR_LEN]; // Control socket IP (empty for disabled) + uint16_t control_port; // Control socket port + int net_debug; // Network debug mode: 1 = enable network emulator +} global_config_t; + +// Complete configuration +typedef struct { + global_config_t global; + connection_config_t *connections; + int connection_count; + int connection_capacity; + subnet_entry_t allowed_subnets[MAX_ALLOWED_SUBNETS]; + int allowed_subnet_count; +} utun_config_t; + +/** + * @brief Parse configuration file + * @param filename Path to configuration file + * @return Pointer to parsed configuration, NULL on error + * + * Parses INI-style configuration file with format: + * [global] + * option=value + * my_private_key=HEX + * my_public_key=HEX + * + * [connection: name] + * mode=server|client + * addr=ip:port (for server) + * from_addr=ip:port (for client) + * to_addr=ip:port (for client) + * peer_public_key=HEX + * so_mark=number + * netif=interface_name + */ +utun_config_t* parse_config(const char *filename); + +/** + * @brief Free configuration structure + * @param config Configuration to free + */ +void free_config(utun_config_t *config); + +/** + * @brief Print configuration for debugging + * @param config Configuration to print + */ +void print_config(const utun_config_t *config); + +/** + * @brief Update keys in configuration file + * @param filename Path to configuration file + * @param private_key_hex Private key in HEX format + * @param public_key_hex Public key in HEX format + * @return 0 on success, -1 on error + * + * Updates or adds my_private_key and my_public_key lines in [global] section + */ +int update_config_keys(const char *filename, + const char *private_key_hex, + const char *public_key_hex); + +#ifdef __cplusplus +} +#endif + +#endif /* CONFIG_PARSER_H */ \ No newline at end of file diff --git a/control_socket.c b/control_socket.c new file mode 100644 index 00000000..f3af348d --- /dev/null +++ b/control_socket.c @@ -0,0 +1,463 @@ +/** + * @file control_socket.c + * @brief Control socket implementation for statistics and diagnostics + */ + +#include "control_socket.h" +#include "connection.h" +#include "etcp.h" +#include "pkt_normalizer.h" +#include "routing.h" +#include "tun_if.h" +#include "config_parser.h" +#include "utun_state.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Internal control socket structure +struct control_socket { + int sockfd; + struct sockaddr_in addr; + uint16_t port; + void *app_state; // Pointer to utun_state_t + uint16_t sequence_counter; + + // Statistics counters (for cumulative stats) + uint64_t total_requests; + uint64_t total_errors; + + // Timestamps + struct timeval start_time; +}; + +// Forward declarations +static int collect_statistics(control_socket_t *cs, control_stats_packet_t *stats); +static int collect_system_status(control_socket_t *cs, system_status_t *status); +static void get_etcp_stats(epkt_t *etcp, etcp_stats_t *etcp_stats); +static void get_pkt_normalizer_stats(pkt_normalizer_pair *pair, pkt_normalizer_stats_t *pn_stats); +static int handle_stats_request(control_socket_t *cs, struct sockaddr_in *client, uint16_t sequence); +static int handle_status_request(control_socket_t *cs, struct sockaddr_in *client, uint16_t sequence); +static int handle_reset_request(control_socket_t *cs, struct sockaddr_in *client, uint16_t sequence); +static int send_error_response(control_socket_t *cs, struct sockaddr_in *client, uint16_t sequence, control_err_t error); +static int send_response(control_socket_t *cs, struct sockaddr_in *client, void *data, size_t len); + +// Create UDP control socket +control_socket_t* control_socket_create(const char *ip, uint16_t port) { + control_socket_t *cs = calloc(1, sizeof(control_socket_t)); + if (!cs) { + return NULL; + } + + // Create UDP socket + cs->sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (cs->sockfd < 0) { + perror("control_socket: socket"); + free(cs); + return NULL; + } + + // Set non-blocking + int flags = fcntl(cs->sockfd, F_GETFL, 0); + if (flags < 0) { + perror("control_socket: fcntl GETFL"); + close(cs->sockfd); + free(cs); + return NULL; + } + if (fcntl(cs->sockfd, F_SETFL, flags | O_NONBLOCK) < 0) { + perror("control_socket: fcntl SETFL"); + close(cs->sockfd); + free(cs); + return NULL; + } + + // Enable address reuse + int opt = 1; + if (setsockopt(cs->sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { + perror("control_socket: setsockopt SO_REUSEADDR"); + // Continue anyway + } + + // Bind to address + memset(&cs->addr, 0, sizeof(cs->addr)); + cs->addr.sin_family = AF_INET; + cs->addr.sin_port = htons(port); + + if (ip && ip[0] != '\0') { + if (inet_pton(AF_INET, ip, &cs->addr.sin_addr) != 1) { + fprintf(stderr, "control_socket: invalid IP address: %s\n", ip); + close(cs->sockfd); + free(cs); + return NULL; + } + } else { + cs->addr.sin_addr.s_addr = INADDR_ANY; + } + + if (bind(cs->sockfd, (struct sockaddr*)&cs->addr, sizeof(cs->addr)) < 0) { + perror("control_socket: bind"); + close(cs->sockfd); + free(cs); + return NULL; + } + + cs->port = port; + cs->sequence_counter = 0; + cs->total_requests = 0; + cs->total_errors = 0; + gettimeofday(&cs->start_time, NULL); + + printf("[CONTROL] Control socket listening on %s:%u\n", + ip ? ip : "0.0.0.0", port); + + return cs; +} + +// Destroy control socket +void control_socket_destroy(control_socket_t *cs) { + if (!cs) return; + + if (cs->sockfd >= 0) { + close(cs->sockfd); + } + + free(cs); +} + +// Get socket file descriptor for polling +int control_socket_get_fd(const control_socket_t *cs) { + return cs ? cs->sockfd : -1; +} + +// Set application state pointer +void control_socket_set_state(control_socket_t *cs, void *state) { + if (cs) { + cs->app_state = state; + } +} + +// Process incoming requests +int control_socket_process(control_socket_t *cs, void *state) { + if (!cs || !state) return -1; + + // Use provided state or stored state + if (state) { + cs->app_state = state; + } + + struct sockaddr_in client_addr; + socklen_t addr_len = sizeof(client_addr); + control_request_packet_t request; + + // Receive request + ssize_t n = recvfrom(cs->sockfd, &request, sizeof(request), 0, + (struct sockaddr*)&client_addr, &addr_len); + + if (n < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return 0; // No data available + } + perror("control_socket: recvfrom"); + return -1; + } + + if (n != sizeof(request)) { + fprintf(stderr, "control_socket: invalid request size: %zd\n", n); + return 0; + } + + cs->total_requests++; + + // Handle request based on command + switch (request.command) { + case CONTROL_CMD_GET_STATS: + return handle_stats_request(cs, &client_addr, request.sequence); + + case CONTROL_CMD_GET_STATUS: + return handle_status_request(cs, &client_addr, request.sequence); + + case CONTROL_CMD_RESET_STATS: + return handle_reset_request(cs, &client_addr, request.sequence); + + default: + return send_error_response(cs, &client_addr, request.sequence, + CONTROL_ERR_INVALID_CMD); + } +} + +// Handle statistics request +static int handle_stats_request(control_socket_t *cs, struct sockaddr_in *client, + uint16_t sequence) { + control_stats_packet_t stats_packet = {0}; + + // Fill response header + stats_packet.response_type = CONTROL_RESP_STATS; + stats_packet.error_code = CONTROL_ERR_NONE; + stats_packet.sequence = sequence; + + // Get current timestamp + struct timeval tv; + gettimeofday(&tv, NULL); + stats_packet.timestamp = (uint64_t)tv.tv_sec * 1000000 + tv.tv_usec; + + // Collect statistics from all modules + if (collect_statistics(cs, &stats_packet) < 0) { + return send_error_response(cs, client, sequence, CONTROL_ERR_INTERNAL); + } + + // Send response + return send_response(cs, client, &stats_packet, sizeof(stats_packet)); +} + +// Handle status request +static int handle_status_request(control_socket_t *cs, struct sockaddr_in *client, + uint16_t sequence) { + control_status_packet_t status_packet = {0}; + + status_packet.response_type = CONTROL_RESP_STATUS; + status_packet.error_code = CONTROL_ERR_NONE; + status_packet.sequence = sequence; + + // Collect system status + if (collect_system_status(cs, &status_packet.system) < 0) { + status_packet.system.uptime_seconds = 0; + } + + // Set running flag + if (cs->app_state) { + // Assume utun_state_t has 'running' field at same offset + // We'll fix this when integrating with utun.c + status_packet.running = 1; + } + + snprintf(status_packet.status_msg, sizeof(status_packet.status_msg), + "utun control socket active on port %u", cs->port); + + return send_response(cs, client, &status_packet, sizeof(status_packet)); +} + +// Handle reset statistics request +static int handle_reset_request(control_socket_t *cs, struct sockaddr_in *client, + uint16_t sequence) { + // TODO: Implement statistics reset + // For now, just send success response + + control_status_packet_t response = {0}; + response.response_type = CONTROL_RESP_STATUS; + response.error_code = CONTROL_ERR_NONE; + response.sequence = sequence; + + // Get basic system status + collect_system_status(cs, &response.system); + + snprintf(response.status_msg, sizeof(response.status_msg), + "Statistics reset requested (not implemented)"); + + return send_response(cs, client, &response, sizeof(response)); +} + +// Send error response +static int send_error_response(control_socket_t *cs, struct sockaddr_in *client, + uint16_t sequence, control_err_t error) { + control_status_packet_t response = {0}; + + response.response_type = CONTROL_RESP_ERROR; + response.error_code = error; + response.sequence = sequence; + + const char *error_msg = "Unknown error"; + switch (error) { + case CONTROL_ERR_INVALID_CMD: + error_msg = "Invalid command"; + break; + case CONTROL_ERR_INTERNAL: + error_msg = "Internal error"; + break; + default: + break; + } + + snprintf(response.status_msg, sizeof(response.status_msg), "%s", error_msg); + + cs->total_errors++; + + return send_response(cs, client, &response, sizeof(response)); +} + +// Send response to client +static int send_response(control_socket_t *cs, struct sockaddr_in *client, + void *data, size_t len) { + ssize_t sent = sendto(cs->sockfd, data, len, 0, + (struct sockaddr*)client, sizeof(*client)); + + if (sent != (ssize_t)len) { + perror("control_socket: sendto"); + return -1; + } + + return 0; +} + +// Collect statistics from all modules +static int collect_statistics(control_socket_t *cs, control_stats_packet_t *stats) { + if (!cs->app_state) { + return -1; + } + + utun_state_t *state = (utun_state_t*)cs->app_state; + + // Zero out stats + memset(&stats->pkt_normalizer, 0, sizeof(stats->pkt_normalizer)); + memset(&stats->etcp, 0, sizeof(stats->etcp)); + memset(&stats->routing, 0, sizeof(stats->routing)); + memset(&stats->tun, 0, sizeof(stats->tun)); + stats->conn_count = 0; + + // Collect connection statistics + if (state->connections && state->connection_count > 0) { + int max_conn = state->connection_count; + if (max_conn > MAX_CONN_STATS) max_conn = MAX_CONN_STATS; + + for (int i = 0; i < max_conn; i++) { + conn_handle_t *conn = state->connections[i]; + if (!conn) continue; + + conn_stats_t conn_stats; + if (conn_get_stats(conn, &conn_stats) == 0) { + // Aggregate connection stats + stats->etcp.bytes_sent_total += conn_stats.bytes_sent; + stats->etcp.bytes_received_total += conn_stats.bytes_received; + stats->etcp.retransmissions += conn_stats.retransmissions; + stats->pkt_normalizer.fragments_assembled += conn_stats.fragments_assembled; + // Note: etcp and pkt_normalizer stats are aggregated across connections + // Additional per-connection stats could be stored in conn_stats_entry_t + } + + // Fill connection entry (if we have array) + if (i < MAX_CONN_STATS) { + if (state->config && state->config->connections) { + strncpy(stats->connections[i].name, state->config->connections[i].name, + sizeof(stats->connections[i].name) - 1); + stats->connections[i].name[sizeof(stats->connections[i].name) - 1] = '\0'; + } else { + snprintf(stats->connections[i].name, sizeof(stats->connections[i].name), "conn%d", i); + } + stats->connections[i].bytes_sent = conn_stats.bytes_sent; + stats->connections[i].bytes_received = conn_stats.bytes_received; + stats->connections[i].packets_sent = conn_stats.packets_sent; + stats->connections[i].packets_received = conn_stats.packets_received; + stats->connections[i].retransmissions = conn_stats.retransmissions; + stats->connections[i].fragments_assembled = conn_stats.fragments_assembled; + stats->connections[i].current_rtt_ms = conn_stats.current_rtt_ms; + stats->connections[i].jitter_ms = conn_stats.jitter_ms; + stats->connections[i].is_active = 1; + } + } + stats->conn_count = max_conn; + } + + // Collect routing statistics + if (state->routing_table) { + routing_get_stats(state->routing_table, &stats->routing); + } + + // Collect TUN statistics + stats->tun.bytes_read = state->tun.bytes_read; + stats->tun.bytes_written = state->tun.bytes_written; + stats->tun.packets_read = state->tun.packets_read; + stats->tun.packets_written = state->tun.packets_written; + stats->tun.read_errors = state->tun.read_errors; + stats->tun.write_errors = state->tun.write_errors; + strncpy(stats->tun.ifname, state->tun.ifname, sizeof(stats->tun.ifname) - 1); + stats->tun.ifname[sizeof(stats->tun.ifname) - 1] = '\0'; + strncpy(stats->tun.ip_addr, state->tun.ip_addr, sizeof(stats->tun.ip_addr) - 1); + stats->tun.ip_addr[sizeof(stats->tun.ip_addr) - 1] = '\0'; + + // Collect system status + return collect_system_status(cs, &stats->system); +} + +// Collect system status +static int collect_system_status(control_socket_t *cs, system_status_t *status) { + if (!cs) return -1; + + memset(status, 0, sizeof(*status)); + + // Calculate uptime + struct timeval now; + gettimeofday(&now, NULL); + status->uptime_seconds = now.tv_sec - cs->start_time.tv_sec; + + // Get memory usage (simplified) + FILE *meminfo = fopen("/proc/self/statm", "r"); + if (meminfo) { + unsigned long pages; + if (fscanf(meminfo, "%lu", &pages) == 1) { + status->memory_usage_kb = pages * 4; // Assume 4KB pages + } + fclose(meminfo); + } + + // CPU usage (placeholder) + status->cpu_usage_percent = 0; + + // Version info + snprintf(status->version, sizeof(status->version), "utun v0.1"); + + // Control port + status->control_port = cs->port; + + // Running flag and connection count will be set by caller + + return 0; +} + +// Get ETCP statistics +static void get_etcp_stats(epkt_t *etcp, etcp_stats_t *etcp_stats) { + if (!etcp || !etcp_stats) return; + + memset(etcp_stats, 0, sizeof(*etcp_stats)); + + // Use etcp_get_stats if available + // etcp_get_stats(etcp, &retransmissions, ...); + + // Direct access to epkt fields (assuming struct is visible) + etcp_stats->retransmissions = etcp->retransmissions_count; + etcp_stats->total_packets_sent = etcp->total_packets_sent; + etcp_stats->unique_packets_sent = etcp->unique_packets_sent; + etcp_stats->bytes_sent_total = etcp->bytes_sent_total; + etcp_stats->bytes_received_total = etcp->bytes_received_total; + etcp_stats->ack_packets_count = etcp->ack_packets_count; + etcp_stats->control_packets_count = etcp->control_packets_count; + etcp_stats->current_rtt = etcp->rtt_avg_10; + etcp_stats->jitter = etcp->jitter; + etcp_stats->window_size = etcp->window_size; + etcp_stats->unacked_bytes = etcp->unacked_bytes; +} + +// Get packet normalizer statistics +static void get_pkt_normalizer_stats(pkt_normalizer_pair *pair, + pkt_normalizer_stats_t *pn_stats) { + if (!pair || !pn_stats) return; + + memset(pn_stats, 0, sizeof(*pn_stats)); + + if (pair->packer) { + pn_stats->packer_errors = pkt_normalizer_get_error_count(pair->packer); + } + + if (pair->unpacker) { + pn_stats->unpacker_errors = pkt_normalizer_get_error_count(pair->unpacker); + // Fragments assembled count might need to be tracked separately + } +} \ No newline at end of file diff --git a/control_socket.h b/control_socket.h new file mode 100644 index 00000000..4a0e2ad6 --- /dev/null +++ b/control_socket.h @@ -0,0 +1,208 @@ +/** + * @file control_socket.h + * @brief Control socket for statistics and diagnostics over UDP + * @details Provides remote monitoring of utun VPN tunnel statistics + */ + +#ifndef CONTROL_SOCKET_H +#define CONTROL_SOCKET_H + +#include +#include +#include + +// Forward declarations for application state +struct utun_state; + +#ifdef __cplusplus +extern "C" { +#endif + +// Maximum connections for statistics +#define MAX_CONN_STATS 16 +#define MAX_ROUTE_STATS 100 + +// Control socket command types +typedef enum { + CONTROL_CMD_GET_STATS = 1, // Request statistics + CONTROL_CMD_RESET_STATS = 2, // Reset statistics counters + CONTROL_CMD_GET_STATUS = 3, // Request status information +} control_cmd_t; + +// Response types +typedef enum { + CONTROL_RESP_STATS = 1, // Statistics response + CONTROL_RESP_STATUS = 2, // Status response + CONTROL_RESP_ERROR = 0xFF, // Error response +} control_resp_t; + +// Error codes +typedef enum { + CONTROL_ERR_NONE = 0, + CONTROL_ERR_INVALID_CMD = 1, + CONTROL_ERR_INTERNAL = 2, +} control_err_t; + +// Packet normalizer statistics +typedef struct __attribute__((packed)) { + uint32_t packer_errors; // Packer error count + uint32_t unpacker_errors; // Unpacker error count + uint32_t fragments_assembled; // Fragments assembled successfully + uint32_t service_packets; // Service packets processed +} pkt_normalizer_stats_t; + +// ETCP statistics +typedef struct __attribute__((packed)) { + uint32_t retransmissions; // Retransmission count + uint32_t total_packets_sent; // Total packets sent (including retransmissions) + uint32_t unique_packets_sent; // Unique packets sent (excluding retransmissions) + uint32_t bytes_sent_total; // Total bytes sent + uint32_t bytes_received_total;// Total bytes received + uint32_t ack_packets_count; // ACK packets sent + uint32_t control_packets_count; // Control packets sent + uint16_t current_rtt; // Current RTT (0.1ms units) + uint16_t jitter; // Jitter (0.1ms units) + uint32_t window_size; // Current window size (bytes) + uint32_t unacked_bytes; // Unacknowledged bytes in flight +} etcp_stats_t; + +// Connection statistics +typedef struct __attribute__((packed)) { + uint64_t bytes_sent; + uint64_t bytes_received; + uint32_t packets_sent; + uint32_t packets_received; + uint32_t retransmissions; + uint32_t fragments_assembled; + uint16_t current_rtt_ms; + uint16_t jitter_ms; + uint8_t is_active; // 1 if connection is active + char name[32]; // Connection name from config +} conn_stats_entry_t; + +// Routing statistics +typedef struct __attribute__((packed)) { + uint32_t total_routes; // Total routes in table + uint32_t static_routes; // Static routes count + uint32_t dynamic_routes; // Dynamic routes count + uint32_t local_routes; // Local routes count + uint32_t learned_routes; // Learned routes count + uint32_t lookup_count; // Total lookups performed + uint32_t hit_count; // Successful lookups + uint32_t miss_count; // Failed lookups (no route) +} routing_stats_t; + +// TUN interface statistics +typedef struct __attribute__((packed)) { + uint64_t bytes_read; // Bytes read from TUN + uint64_t bytes_written; // Bytes written to TUN + uint32_t packets_read; // Packets read from TUN + uint32_t packets_written; // Packets written to TUN + uint32_t read_errors; // Read errors + uint32_t write_errors; // Write errors + char ifname[16]; // Interface name + char ip_addr[32]; // IP address with prefix +} tun_stats_t; + +// System status +typedef struct __attribute__((packed)) { + uint32_t uptime_seconds; // Seconds since startup + uint32_t memory_usage_kb; // Memory usage in KB (approximate) + uint16_t cpu_usage_percent; // CPU usage percentage (0-100) + uint8_t running; // 1 if main loop is running + uint8_t connection_count; // Number of active connections + uint16_t control_port; // Control socket port + char version[32]; // Version string +} system_status_t; + +// Complete statistics packet +typedef struct __attribute__((packed)) { + uint8_t response_type; // CONTROL_RESP_STATS + uint8_t error_code; // CONTROL_ERR_NONE if success + uint16_t sequence; // Sequence number (echoed from request) + + // Timestamps + uint64_t timestamp; // Unix timestamp in microseconds + + // Module statistics + pkt_normalizer_stats_t pkt_normalizer; + etcp_stats_t etcp; + routing_stats_t routing; + tun_stats_t tun; + + // Connection statistics (variable length, up to MAX_CONN_STATS) + uint8_t conn_count; // Number of connection entries + conn_stats_entry_t connections[MAX_CONN_STATS]; + + // System status + system_status_t system; + + // Reserved for future expansion + uint8_t reserved[64]; +} control_stats_packet_t; + +// Status response packet +typedef struct __attribute__((packed)) { + uint8_t response_type; // CONTROL_RESP_STATUS + uint8_t error_code; // CONTROL_ERR_NONE if success + uint16_t sequence; // Sequence number + + system_status_t system; + uint8_t running; // Main loop running flag + char status_msg[128]; // Human-readable status message +} control_status_packet_t; + +// Request packet (from client) +typedef struct __attribute__((packed)) { + uint8_t command; // CONTROL_CMD_* + uint16_t sequence; // Sequence number for response matching + uint8_t reserved; // Reserved, must be 0 +} control_request_packet_t; + +// Control socket handle (opaque) +typedef struct control_socket control_socket_t; + +/** + * @brief Create and initialize control socket + * @param ip IP address to bind to (NULL for any) + * @param port UDP port to listen on + * @return Control socket handle or NULL on error + */ +control_socket_t* control_socket_create(const char *ip, uint16_t port); + +/** + * @brief Destroy control socket and free resources + * @param cs Control socket handle + */ +void control_socket_destroy(control_socket_t *cs); + +/** + * @brief Get file descriptor for polling + * @param cs Control socket handle + * @return Socket file descriptor or -1 if invalid + */ +int control_socket_get_fd(const control_socket_t *cs); + +/** + * @brief Process incoming control socket requests + * @param cs Control socket handle + * @param state Application state (for collecting statistics) + * @return 0 on success, -1 on error + * + * This function should be called when the socket is readable. + * It will handle incoming requests and send responses. + */ +int control_socket_process(control_socket_t *cs, void *state); + +/** + * @brief Set application state pointer for statistics collection + * @param cs Control socket handle + * @param state Application state (utun_state_t*) + */ +void control_socket_set_state(control_socket_t *cs, void *state); + +#ifdef __cplusplus +} +#endif + +#endif /* CONTROL_SOCKET_H */ \ No newline at end of file diff --git a/example_control.conf b/example_control.conf new file mode 100644 index 00000000..ec908188 --- /dev/null +++ b/example_control.conf @@ -0,0 +1,29 @@ +# Example configuration with control socket enabled +[global] +my_private_key=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef +my_public_key=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef +control_ip=127.0.0.1 # Listen on localhost only +control_port=5555 # UDP port for monitoring + +# TUN interface configuration +[tun] +ifname=tun12 +ip_addr=10.0.0.1/24 +mtu=1400 + +# Routing configuration +[routing] +allowed_subnet=10.0.0.0/8 +allowed_subnet=172.16.0.0/12 +allowed_subnet=192.168.0.0/16 + +# Connection configuration (example) +[[connection]] +name=node1 +remote_ip=192.168.1.100 +remote_port=12345 +local_ip=0.0.0.0 +local_port=0 +mode=client + +# Add more connections as needed \ No newline at end of file diff --git a/monitor.py b/monitor.py new file mode 100644 index 00000000..f48eae70 --- /dev/null +++ b/monitor.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +""" +utun control socket monitor client +Usage: python3 monitor.py [host] [port] +Default: localhost:5555 +""" +import socket +import struct +import sys +import json +import time + +# Command types from control_socket.h +CONTROL_CMD_GET_STATS = 1 +CONTROL_CMD_RESET_STATS = 2 +CONTROL_CMD_GET_STATUS = 3 + +# Response types +CONTROL_RESP_STATS = 1 +CONTROL_RESP_STATUS = 2 +CONTROL_RESP_ERROR = 3 + +# Error codes +CONTROL_ERR_NONE = 0 +CONTROL_ERR_INVALID_CMD = 1 +CONTROL_ERR_INTERNAL = 2 + +def send_command(host='127.0.0.1', port=5555, command=CONTROL_CMD_GET_STATS, sequence=1): + """Send a control command and receive response""" + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.settimeout(2.0) + + # Build request packet (4 bytes: command + sequence) + # struct control_request_packet_t { uint16_t command; uint16_t sequence; } + request = struct.pack(' 1: + host = sys.argv[1] + if len(sys.argv) > 2: + port = int(sys.argv[2]) + + print(f"Connecting to {host}:{port}") + + # Send GET_STATS command + data = send_command(host, port, CONTROL_CMD_GET_STATS, 1) + if data is None: + return + + print(f"Received {len(data)} bytes") + print_hex(data) + + # Attempt to parse basic info + if len(data) >= 6: + resp_type, err_code, seq = struct.unpack('= 14: + timestamp = struct.unpack('= 6: + resp_type, err_code, seq = struct.unpack(' +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_LINE_LEN 1024 +#define TIME_BASE_MS 0.1 // u_async timebase is 0.1ms + +// Packet buffer with timestamp for delayed sending +typedef struct { + uint8_t data[MAX_PACKET_SIZE]; + size_t len; + struct sockaddr_in from_addr; + struct sockaddr_in to_addr; + endpoint_t *endpoint; + struct timeval send_time; // When to send this packet +} delayed_packet_t; + +// Global emulator state +static net_emulator_t g_emulator = {0}; +static int g_packet_counter = 0; + +// Helper function to trim whitespace +static char* trim(char *str) { + if (!str) return NULL; + + // Trim leading spaces + while (isspace((unsigned char)*str)) str++; + + // Trim trailing spaces + char *end = str + strlen(str) - 1; + while (end > str && isspace((unsigned char)*end)) end--; + *(end + 1) = '\0'; + + return str; +} + +// Parse IP:port from string like "127.0.0.1:20000" +static int parse_ip_port(const char *str, struct sockaddr_in *addr) { + if (!str || !addr) return -1; + + char ip[64]; + int port; + + // Find colon + const char *colon = strchr(str, ':'); + if (!colon) return -1; + + // Extract IP + size_t ip_len = colon - str; + if (ip_len >= sizeof(ip)) return -1; + strncpy(ip, str, ip_len); + ip[ip_len] = '\0'; + + // Parse port + port = atoi(colon + 1); + if (port <= 0 || port > 65535) return -1; + + // Convert IP + if (inet_pton(AF_INET, ip, &addr->sin_addr) != 1) return -1; + addr->sin_family = AF_INET; + addr->sin_port = htons(port); + + return 0; +} + +// Parse delay range like "100-130" +static int parse_delay_range(const char *str, delay_range_t *range) { + if (!str || !range) return -1; + + char *dash = strchr(str, '-'); + if (!dash) { + // Single value + range->min_ms = atoi(str); + range->max_ms = range->min_ms; + } else { + char min_str[32], max_str[32]; + size_t min_len = dash - str; + strncpy(min_str, str, min_len); + min_str[min_len] = '\0'; + strncpy(max_str, dash + 1, sizeof(max_str) - 1); max_str[sizeof(max_str) - 1] = '\0'; + + range->min_ms = atoi(min_str); + range->max_ms = atoi(max_str); + } + + if (range->min_ms < 0 || range->max_ms < range->min_ms) return -1; + return 0; +} + +// Parse probability and delay from "prob=40,delay=100-130" +static int parse_rule(const char *line, rule_t *rule) { + if (!line || !rule) return -1; + + char *prob_start = strstr(line, "prob="); + if (!prob_start) return -1; + + char *comma = strchr(prob_start, ','); + if (!comma) return -1; + + // Parse probability + char prob_str[32]; + strncpy(prob_str, prob_start + 5, comma - (prob_start + 5)); + prob_str[comma - (prob_start + 5)] = '\0'; + rule->probability_percent = atoi(prob_str); + if (rule->probability_percent < 0 || rule->probability_percent > 100) return -1; + + // Parse delay + char *delay_start = strstr(comma, "delay="); + if (!delay_start) return -1; + + char delay_str[64]; + strncpy(delay_str, delay_start + 6, sizeof(delay_str) - 1); delay_str[sizeof(delay_str) - 1] = '\0'; + trim(delay_str); + + // Remove trailing ) if present (from comment) + char *paren = strchr(delay_str, ')'); + if (paren) *paren = '\0'; + + if (parse_delay_range(delay_str, &rule->delay) < 0) return -1; + + return 0; +} + +// Parse configuration file +int parse_emulator_config(const char *filename, net_emulator_t *emulator) { + if (!filename || !emulator) return -1; + + FILE *fp = fopen(filename, "r"); + if (!fp) { + fprintf(stderr, "Failed to open config file: %s\n", filename); + return -1; + } + + char line[MAX_LINE_LEN]; + endpoint_t *current_endpoint = NULL; + + while (fgets(line, sizeof(line), fp)) { + // Remove newline + line[strcspn(line, "\n")] = '\0'; + char *trimmed = trim(line); + + // Skip empty lines and comments starting with ; + if (strlen(trimmed) == 0 || trimmed[0] == ';') { + continue; + } + + // Check for endpoint section [ip:port] + if (trimmed[0] == '[' && trimmed[strlen(trimmed) - 1] == ']') { + // Extract ip:port inside brackets + char endpoint_str[128]; + strncpy(endpoint_str, trimmed + 1, sizeof(endpoint_str) - 1); + endpoint_str[sizeof(endpoint_str) - 1] = '\0'; + endpoint_str[strcspn(endpoint_str, "]")] = '\0'; + trim(endpoint_str); + + // Remove any comments in parentheses + char *paren = strchr(endpoint_str, '('); + if (paren) *paren = '\0'; + trim(endpoint_str); + + // Check if we have space for more endpoints + if (emulator->endpoint_count >= MAX_ENDPOINTS) { + fprintf(stderr, "Too many endpoints, maximum is %d\n", MAX_ENDPOINTS); + fclose(fp); + return -1; + } + + current_endpoint = &emulator->endpoints[emulator->endpoint_count]; + memset(current_endpoint, 0, sizeof(endpoint_t)); + + // Parse listen address + if (parse_ip_port(endpoint_str, ¤t_endpoint->listen_addr) < 0) { + fprintf(stderr, "Invalid endpoint format: %s\n", endpoint_str); + fclose(fp); + return -1; + } + + // Calculate target address (port - 10000 for net_debug mode) + uint16_t listen_port = ntohs(current_endpoint->listen_addr.sin_port); + uint16_t target_port = listen_port; + if (listen_port >= 10000) { + target_port = listen_port - 10000; + printf("[CONFIG] Forwarding %s:%u -> %s:%u (net_debug mode)\n", + inet_ntoa(current_endpoint->listen_addr.sin_addr), listen_port, + inet_ntoa(current_endpoint->listen_addr.sin_addr), target_port); + } else { + printf("[CONFIG] Forwarding %s:%u -> %s:%u (direct)\n", + inet_ntoa(current_endpoint->listen_addr.sin_addr), listen_port, + inet_ntoa(current_endpoint->listen_addr.sin_addr), target_port); + } + + current_endpoint->target_addr = current_endpoint->listen_addr; + current_endpoint->target_addr.sin_port = htons(target_port); + + emulator->endpoint_count++; + continue; + } + + // Parse rule if we're in an endpoint section + if (current_endpoint) { + // Check if line contains prob= (it's a rule) + if (strstr(trimmed, "prob=")) { + if (current_endpoint->rule_count >= MAX_RULES_PER_ENDPOINT) { + fprintf(stderr, "Too many rules for endpoint, maximum is %d\n", + MAX_RULES_PER_ENDPOINT); + fclose(fp); + return -1; + } + + rule_t *rule = ¤t_endpoint->rules[current_endpoint->rule_count]; + if (parse_rule(trimmed, rule) == 0) { + current_endpoint->total_probability += rule->probability_percent; + current_endpoint->rule_count++; + + if (current_endpoint->total_probability > 100) { + fprintf(stderr, "Total probability exceeds 100%% for endpoint %s:%u\n", + inet_ntoa(current_endpoint->listen_addr.sin_addr), + ntohs(current_endpoint->listen_addr.sin_port)); + fclose(fp); + return -1; + } + } else { + fprintf(stderr, "Invalid rule format: %s\n", trimmed); + } + } + // Ignore lines in parentheses (comments) + } + } + + fclose(fp); + + // Validate configuration + for (int i = 0; i < emulator->endpoint_count; i++) { + endpoint_t *ep = &emulator->endpoints[i]; + printf("[CONFIG] Endpoint %s:%u has %d rules (total prob=%d%%)\n", + inet_ntoa(ep->listen_addr.sin_addr), ntohs(ep->listen_addr.sin_port), + ep->rule_count, ep->total_probability); + } + + return 0; +} + +// Create UDP socket for endpoint +static int create_endpoint_socket(endpoint_t *ep) { + int sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) { + perror("socket"); + return -1; + } + + // Enable address reuse + int opt = 1; + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { + perror("setsockopt SO_REUSEADDR"); + close(sockfd); + return -1; + } + + // Bind to listen address + if (bind(sockfd, (struct sockaddr*)&ep->listen_addr, sizeof(ep->listen_addr)) < 0) { + perror("bind"); + close(sockfd); + return -1; + } + + // Set non-blocking + int flags = fcntl(sockfd, F_GETFL, 0); + if (flags < 0) { + perror("fcntl GETFL"); + close(sockfd); + return -1; + } + if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) < 0) { + perror("fcntl SETFL"); + close(sockfd); + return -1; + } + + ep->sockfd = sockfd; + return 0; +} + +// Forward declaration +static void send_delayed_packet(void *user_arg); + +// Socket callback for incoming packets +static void socket_callback(int fd, void *user_arg) { + endpoint_t *ep = (endpoint_t*)user_arg; + if (!ep) return; + + uint8_t buffer[MAX_PACKET_SIZE]; + struct sockaddr_in from_addr; + socklen_t addr_len = sizeof(from_addr); + + ssize_t nread = recvfrom(fd, buffer, sizeof(buffer), 0, + (struct sockaddr*)&from_addr, &addr_len); + if (nread <= 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + perror("recvfrom"); + } + return; + } + + g_packet_counter++; + printf("[PACKET #%d] Received %zd bytes from %s:%u -> %s:%u\n", + g_packet_counter, nread, + inet_ntoa(from_addr.sin_addr), ntohs(from_addr.sin_port), + inet_ntoa(ep->target_addr.sin_addr), ntohs(ep->target_addr.sin_port)); + + // Apply rules to decide fate of packet + int random_val = rand() % 100; + int cumulative_prob = 0; + rule_t *selected_rule = NULL; + + for (int i = 0; i < ep->rule_count; i++) { + cumulative_prob += ep->rules[i].probability_percent; + if (random_val < cumulative_prob) { + selected_rule = &ep->rules[i]; + break; + } + } + + // If no rule selected (random_val >= total_probability), packet is dropped + if (!selected_rule) { + printf("[PACKET #%d] DROPPED (no matching rule)\n", g_packet_counter); + return; + } + + // Calculate delay + int delay_ms = selected_rule->delay.min_ms; + if (selected_rule->delay.max_ms > selected_rule->delay.min_ms) { + delay_ms += rand() % (selected_rule->delay.max_ms - selected_rule->delay.min_ms + 1); + } + + printf("[PACKET #%d] Applying delay: %d ms (rule prob=%d%%)\n", + g_packet_counter, delay_ms, selected_rule->probability_percent); + + // Allocate packet buffer for delayed sending + delayed_packet_t *packet = malloc(sizeof(delayed_packet_t)); + if (!packet) { + fprintf(stderr, "Failed to allocate packet buffer\n"); + return; + } + + memcpy(packet->data, buffer, nread); + packet->len = nread; + packet->from_addr = from_addr; + packet->to_addr = ep->target_addr; + packet->endpoint = ep; + + // Calculate send time + gettimeofday(&packet->send_time, NULL); + packet->send_time.tv_sec += delay_ms / 1000; + packet->send_time.tv_usec += (delay_ms % 1000) * 1000; + if (packet->send_time.tv_usec >= 1000000) { + packet->send_time.tv_sec++; + packet->send_time.tv_usec -= 1000000; + } + + // Create timeout for delayed sending + // Convert delay to u_async timebase (0.1ms) + int delay_tb = delay_ms * 10; + if (!ep->emulator || !ep->emulator->ua) { + free(packet); + return; + } + uasync_set_timeout(ep->emulator->ua, delay_tb, packet, (timeout_callback_t)send_delayed_packet); +} + +// Callback for sending delayed packet +static void send_delayed_packet(void *user_arg) { + delayed_packet_t *packet = (delayed_packet_t*)user_arg; + if (!packet) return; + + ssize_t nsent = sendto(packet->endpoint->sockfd, packet->data, packet->len, 0, + (struct sockaddr*)&packet->to_addr, sizeof(packet->to_addr)); + if (nsent != (ssize_t)packet->len) { + perror("sendto"); + printf("[PACKET] Failed to forward packet\n"); + } else { + printf("[PACKET] Forwarded %zd bytes to %s:%u\n", + nsent, inet_ntoa(packet->to_addr.sin_addr), ntohs(packet->to_addr.sin_port)); + } + + free(packet); +} + +// Initialize network emulator +int net_emulator_init(net_emulator_t *emulator) { + if (!emulator) return -1; + + // Initialize random seed + srand(time(NULL)); + + // Initialize u_async instance + emulator->ua = uasync_create(); + if (!emulator->ua) { + fprintf(stderr, "Failed to create uasync instance\n"); + return -1; + } + + // Create sockets for all endpoints + for (int i = 0; i < emulator->endpoint_count; i++) { + endpoint_t *ep = &emulator->endpoints[i]; + if (create_endpoint_socket(ep) < 0) { + fprintf(stderr, "Failed to create socket for endpoint %s:%u\n", + inet_ntoa(ep->listen_addr.sin_addr), ntohs(ep->listen_addr.sin_port)); + uasync_destroy(emulator->ua); + emulator->ua = NULL; + return -1; + } + + ep->emulator = emulator; + + // Add socket to u_async + uasync_add_socket(emulator->ua, ep->sockfd, socket_callback, NULL, NULL, ep); + printf("[INIT] Listening on %s:%u\n", + inet_ntoa(ep->listen_addr.sin_addr), ntohs(ep->listen_addr.sin_port)); + } + + emulator->running = 1; + return 0; +} + +// Run network emulator main loop +void net_emulator_run(net_emulator_t *emulator) { + if (!emulator || !emulator->running || !emulator->ua) return; + + printf("[EMULATOR] Starting main loop. Press Ctrl+C to stop.\n"); + uasync_mainloop(emulator->ua); // This never returns +} + +// Clean up emulator resources +void net_emulator_cleanup(net_emulator_t *emulator) { + if (!emulator) return; + + for (int i = 0; i < emulator->endpoint_count; i++) { + endpoint_t *ep = &emulator->endpoints[i]; + if (ep->sockfd >= 0) { + close(ep->sockfd); + ep->sockfd = -1; + } + } + + if (emulator->ua) { + uasync_destroy(emulator->ua); + emulator->ua = NULL; + } + + emulator->running = 0; +} + +// Main function +int main(int argc, char *argv[]) { + setbuf(stdout, NULL); + setbuf(stderr, NULL); + + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + fprintf(stderr, "Example: %s net_emulator.conf\n", argv[0]); + return 1; + } + + printf("Network Emulator for utun net_debug mode\n"); + printf("=========================================\n"); + + // Parse configuration + if (parse_emulator_config(argv[1], &g_emulator) < 0) { + fprintf(stderr, "Failed to parse configuration\n"); + return 1; + } + + if (g_emulator.endpoint_count == 0) { + fprintf(stderr, "No endpoints configured\n"); + return 1; + } + + // Initialize emulator + if (net_emulator_init(&g_emulator) < 0) { + fprintf(stderr, "Failed to initialize emulator\n"); + net_emulator_cleanup(&g_emulator); + return 1; + } + + // Run main loop + net_emulator_run(&g_emulator); + + // Cleanup (never reached) + net_emulator_cleanup(&g_emulator); + return 0; +} \ No newline at end of file diff --git a/net_emulator/net_emulator.conf b/net_emulator/net_emulator.conf new file mode 100644 index 00000000..115894b1 --- /dev/null +++ b/net_emulator/net_emulator.conf @@ -0,0 +1,23 @@ +# Network emulator configuration for net_debug mode +# Format: [ip:port] (listening address) +# Rules: prob=percent,delay=min-max (in milliseconds) +# If sum of probabilities < 100%, remaining packets are dropped + +# Endpoint 1: Forward packets from port 20000 to 10000 (port - 10000) +[127.0.0.1:20000] +prob=40,delay=100-130 (adds 100-130ms delay) +prob=30,delay=10-20 (adds 10-20ms delay) +# Total probability = 70%, remaining 30% packets are dropped + +# Endpoint 2: Forward packets from port 20001 to 10001 +[127.0.0.1:20001] +prob=10,delay=0-0 (no delay, 10% probability) +prob=80,delay=10-13 (adds 10-13ms delay, 80% probability) +# Total probability = 90%, remaining 10% packets are dropped + +# Endpoint 3: Server response path (from port 10001 back to client) +# Note: In net_debug mode, server responses come to original client port +# which is also +10000. This endpoint handles responses from server to client. +[127.0.0.1:10001] +prob=80,delay=5-10 +# Total probability = 80%, 20% dropped \ No newline at end of file diff --git a/net_emulator/net_emulator.conf.example b/net_emulator/net_emulator.conf.example new file mode 100644 index 00000000..d62a0c1c --- /dev/null +++ b/net_emulator/net_emulator.conf.example @@ -0,0 +1,12 @@ +[127.0.0.1:20000] (порт 20000 форвардится на 10000) +prob=40,delay=100-130 (добавляет 100-130ms задержки) +(в остальных случаях drop) + +[127.0.0.1:20001] +prob=10,delay=0-0 (отправляет без задержки в 10% случаев) +prob=80,delay=10-13 (добавляет 10-13ms задержки 80% вероятность) +(в остальных случаях drop) + +[127.0.0.1:20001] (здесь вываливается с ошибкой - ip:порт уже занят) +prob=50,delay=0-0 (отправляет без задержки в 50% случаев) +prob=80,delay=10-13 (здесь он должен вывалиться с ошибкой парсинга - prob суммарный >100) diff --git a/net_emulator/net_emulator.h b/net_emulator/net_emulator.h new file mode 100644 index 00000000..7afd1a34 --- /dev/null +++ b/net_emulator/net_emulator.h @@ -0,0 +1,90 @@ +// net_emulator.h - Network emulator for testing utun with delay, loss, reordering +#ifndef NET_EMULATOR_H +#define NET_EMULATOR_H + +#include +#include +#include +#include +#include "u_async.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Maximum number of rules per endpoint +#define MAX_RULES_PER_ENDPOINT 10 +#define MAX_ENDPOINTS 100 +#define MAX_PACKET_SIZE 65536 + +// Forward declaration +typedef struct net_emulator_s net_emulator_t; + +// Delay range (milliseconds) +typedef struct { + int min_ms; + int max_ms; +} delay_range_t; + +// Rule: probability and delay +typedef struct { + int probability_percent; // 0-100 + delay_range_t delay; +} rule_t; + +// Network endpoint configuration +typedef struct { + struct sockaddr_in listen_addr; // Address to listen on + struct sockaddr_in target_addr; // Address to forward to (port - 10000) + rule_t rules[MAX_RULES_PER_ENDPOINT]; + int rule_count; + int total_probability; // Sum of probabilities (should be <= 100) + + // For reordering simulation + int reorder_probability; // Probability to reorder with next packet (0-100) + + // Socket descriptor + int sockfd; + net_emulator_t* emulator; // Parent emulator instance +} endpoint_t; + +// Main emulator state +struct net_emulator_s { + endpoint_t endpoints[MAX_ENDPOINTS]; + int endpoint_count; + int running; + uasync_t* ua; // uasync instance +}; + +/** + * @brief Parse configuration file + * @param filename Configuration file path + * @param emulator Emulator state to fill + * @return 0 on success, -1 on error + */ +int parse_emulator_config(const char *filename, net_emulator_t *emulator); + +/** + * @brief Initialize network emulator + * @param emulator Emulator state + * @return 0 on success, -1 on error + */ +int net_emulator_init(net_emulator_t *emulator); + +/** + * @brief Run network emulator main loop + * @param emulator Emulator state + */ +void net_emulator_run(net_emulator_t *emulator); + +/** + * @brief Clean up emulator resources + * @param emulator Emulator state + */ +void net_emulator_cleanup(net_emulator_t *emulator); + +#ifdef __cplusplus +} +#endif + +#endif /* NET_EMULATOR_H */ \ No newline at end of file diff --git a/net_emulator/test_timeout_heap.c b/net_emulator/test_timeout_heap.c new file mode 100644 index 00000000..0929348e --- /dev/null +++ b/net_emulator/test_timeout_heap.c @@ -0,0 +1,412 @@ +#include "timeout_heap.h" +#include +#include +#include +#include +#include + +#define TEST_ASSERT(cond, msg) do { \ + if (!(cond)) { \ + fprintf(stderr, "FAIL: %s:%d: %s\n", __FILE__, __LINE__, (msg)); \ + exit(1); \ + } \ +} while (0) + +// Element structure for large random test +typedef struct { + uint64_t expiration; + void *data; + int cancelled; +} HeapElement; + +// Helper: compare two entries (for qsort) +static int compare_entries(const void *a, const void *b) { + const TimeoutEntry *ea = (const TimeoutEntry *)a; + const TimeoutEntry *eb = (const TimeoutEntry *)b; + if (ea->expiration < eb->expiration) return -1; + if (ea->expiration > eb->expiration) return 1; + return 0; +} + +// Test 1: basic operations +static void test_basic(void) { + printf("Test 1: basic operations...\n"); + TimeoutHeap *h = timeout_heap_create(10); + TEST_ASSERT(h != NULL, "heap creation"); + TEST_ASSERT(h->size == 0, "initial size zero"); + + // Push one element + int data1 = 42; + int ret = timeout_heap_push(h, 100, &data1); + TEST_ASSERT(ret == 0, "push success"); + TEST_ASSERT(h->size == 1, "size after push"); + + // Peek + TimeoutEntry entry; + ret = timeout_heap_peek(h, &entry); + TEST_ASSERT(ret == 0, "peek success"); + TEST_ASSERT(entry.expiration == 100, "peek expiration"); + TEST_ASSERT(entry.data == &data1, "peek data"); + TEST_ASSERT(entry.deleted == 0, "peek deleted flag"); + + // Pop + ret = timeout_heap_pop(h, &entry); + TEST_ASSERT(ret == 0, "pop success"); + TEST_ASSERT(entry.expiration == 100, "pop expiration"); + TEST_ASSERT(entry.data == &data1, "pop data"); + TEST_ASSERT(h->size == 0, "size after pop"); + + // Pop empty + ret = timeout_heap_pop(h, &entry); + TEST_ASSERT(ret == -1, "pop empty returns -1"); + + timeout_heap_destroy(h); + printf(" Passed\n"); +} + +// Test 2: ordering with random values +static void test_ordering(size_t num_elements) { + printf("Test 2: ordering with %zu elements...\n", num_elements); + TimeoutHeap *h = timeout_heap_create(4); // small initial capacity + TEST_ASSERT(h != NULL, "heap creation"); + + // Generate random expirations and store them + TimeoutEntry *expected = malloc(num_elements * sizeof(TimeoutEntry)); + TEST_ASSERT(expected != NULL, "alloc expected array"); + + srand((unsigned int)time(NULL)); + for (size_t i = 0; i < num_elements; i++) { + // Generate unique expiration to avoid ties + uint64_t exp = i * 1000000ULL + (rand() % 1000000); + int *data = malloc(sizeof(int)); + *data = (int)i; + int ret = timeout_heap_push(h, exp, data); + TEST_ASSERT(ret == 0, "push success"); + expected[i].expiration = exp; + expected[i].data = data; + expected[i].deleted = 0; + } + + // Sort expected by expiration + qsort(expected, num_elements, sizeof(TimeoutEntry), compare_entries); + + // Pop and verify order + for (size_t i = 0; i < num_elements; i++) { + TimeoutEntry entry; + int ret = timeout_heap_pop(h, &entry); + TEST_ASSERT(ret == 0, "pop success"); + TEST_ASSERT(entry.expiration == expected[i].expiration, "expiration order"); + TEST_ASSERT(entry.data == expected[i].data, "data matches"); + TEST_ASSERT(entry.deleted == 0, "not deleted"); + free(entry.data); // cleanup + } + + // Heap should be empty + TimeoutEntry entry; + int ret = timeout_heap_pop(h, &entry); + TEST_ASSERT(ret == -1, "heap empty after all pops"); + + free(expected); + timeout_heap_destroy(h); + printf(" Passed\n"); +} + +// Test 3: cancel operation +static void test_cancel(void) { + printf("Test 3: cancel operation...\n"); + TimeoutHeap *h = timeout_heap_create(10); + TEST_ASSERT(h != NULL, "heap creation"); + + int data1 = 1, data2 = 2, data3 = 3; + timeout_heap_push(h, 100, &data1); + timeout_heap_push(h, 200, &data2); + timeout_heap_push(h, 300, &data3); + + // Cancel middle element + int ret = timeout_heap_cancel(h, 200, &data2); + TEST_ASSERT(ret == 0, "cancel success"); + + // Pop and verify we get data1 then data3, not data2 + TimeoutEntry entry; + ret = timeout_heap_pop(h, &entry); + TEST_ASSERT(ret == 0, "pop 1 success"); + TEST_ASSERT(entry.expiration == 100, "first expiration"); + TEST_ASSERT(entry.data == &data1, "first data"); + + ret = timeout_heap_pop(h, &entry); + TEST_ASSERT(ret == 0, "pop 2 success"); + TEST_ASSERT(entry.expiration == 300, "second expiration"); + TEST_ASSERT(entry.data == &data3, "second data"); + + // Heap empty + ret = timeout_heap_pop(h, &entry); + TEST_ASSERT(ret == -1, "heap empty"); + + timeout_heap_destroy(h); + printf(" Passed\n"); +} + +// Test 4: duplicate expiration times +static void test_duplicate_expirations(void) { + printf("Test 4: duplicate expirations...\n"); + TimeoutHeap *h = timeout_heap_create(10); + TEST_ASSERT(h != NULL, "heap creation"); + + int data1 = 1, data2 = 2, data3 = 3; + // Push same expiration for data1 and data2 + timeout_heap_push(h, 100, &data1); + timeout_heap_push(h, 100, &data2); + timeout_heap_push(h, 200, &data3); + + // Pop should give both items with expiration 100 (order not guaranteed) + TimeoutEntry entry; + int ret = timeout_heap_pop(h, &entry); + TEST_ASSERT(ret == 0, "first pop success"); + TEST_ASSERT(entry.expiration == 100, "first pop expiration"); + // Record which data pointer we got + void *first_data = entry.data; + TEST_ASSERT(first_data == &data1 || first_data == &data2, "first pop data matches one of duplicates"); + + ret = timeout_heap_pop(h, &entry); + TEST_ASSERT(ret == 0, "second pop success"); + TEST_ASSERT(entry.expiration == 100, "second pop expiration"); + void *second_data = entry.data; + TEST_ASSERT(second_data == &data1 || second_data == &data2, "second pop data matches one of duplicates"); + TEST_ASSERT(first_data != second_data, "two different data pointers"); + + // Third pop should give expiration 200 + ret = timeout_heap_pop(h, &entry); + TEST_ASSERT(ret == 0, "third pop success"); + TEST_ASSERT(entry.expiration == 200, "third pop expiration"); + TEST_ASSERT(entry.data == &data3, "third pop data"); + + // Heap empty + ret = timeout_heap_pop(h, &entry); + TEST_ASSERT(ret == -1, "heap empty"); + + timeout_heap_destroy(h); + printf(" Passed\n"); +} + +// Test 5: deleted flag handling (multiple deleted at root) +static void test_deleted_root(void) { + printf("Test 5: deleted root handling...\n"); + TimeoutHeap *h = timeout_heap_create(10); + TEST_ASSERT(h != NULL, "heap creation"); + + int data1 = 1, data2 = 2, data3 = 3, data4 = 4; + timeout_heap_push(h, 100, &data1); + timeout_heap_push(h, 50, &data2); // earliest + timeout_heap_push(h, 150, &data3); + timeout_heap_push(h, 75, &data4); + + // Cancel the earliest (root) + timeout_heap_cancel(h, 50, &data2); + + // Peek should skip deleted root and give next earliest (75) + TimeoutEntry entry; + int ret = timeout_heap_peek(h, &entry); + TEST_ASSERT(ret == 0, "peek success after root deleted"); + TEST_ASSERT(entry.expiration == 75, "peek expiration after skip"); + TEST_ASSERT(entry.data == &data4, "peek data after skip"); + + // Pop should also skip deleted root + ret = timeout_heap_pop(h, &entry); + TEST_ASSERT(ret == 0, "pop success after root deleted"); + TEST_ASSERT(entry.expiration == 75, "pop expiration after skip"); + + // Next pop should be 100 + ret = timeout_heap_pop(h, &entry); + TEST_ASSERT(ret == 0, "pop second"); + TEST_ASSERT(entry.expiration == 100, "second pop expiration"); + + // Cancel 150, then push another earlier + timeout_heap_cancel(h, 150, &data3); + int data5 = 5; + timeout_heap_push(h, 60, &data5); // earlier than 150 but after 100 + + // Pop should give 60 + ret = timeout_heap_pop(h, &entry); + TEST_ASSERT(ret == 0, "pop after new push"); + TEST_ASSERT(entry.expiration == 60, "expiration of newly pushed"); + + // Heap empty (150 deleted) + ret = timeout_heap_pop(h, &entry); + TEST_ASSERT(ret == -1, "heap empty after deleted remaining"); + + timeout_heap_destroy(h); + printf(" Passed\n"); +} + +// Test 6: capacity growth +static void test_growth(void) { + printf("Test 6: capacity growth...\n"); + TimeoutHeap *h = timeout_heap_create(2); // tiny capacity + TEST_ASSERT(h != NULL, "heap creation"); + TEST_ASSERT(h->capacity == 2, "initial capacity"); + + // Push 10 elements + for (int i = 0; i < 10; i++) { + int ret = timeout_heap_push(h, i * 10, NULL); + TEST_ASSERT(ret == 0, "push success"); + } + TEST_ASSERT(h->size == 10, "size after pushes"); + TEST_ASSERT(h->capacity >= 10, "capacity grown"); + + // Pop all, verify order + uint64_t prev = 0; + for (int i = 0; i < 10; i++) { + TimeoutEntry entry; + int ret = timeout_heap_pop(h, &entry); + TEST_ASSERT(ret == 0, "pop success"); + TEST_ASSERT(entry.expiration == prev, "order after growth"); + prev = entry.expiration + 10; + } + + timeout_heap_destroy(h); + printf(" Passed\n"); +} + +// Comparison function for HeapElement +static int compare_elements(const void *a, const void *b) { + const HeapElement *ea = (const HeapElement *)a; + const HeapElement *eb = (const HeapElement *)b; + if (ea->expiration < eb->expiration) return -1; + if (ea->expiration > eb->expiration) return 1; + return 0; +} + +// Test 8: large number of elements, random order, with some cancellations +static void test_large_random(size_t num_elements, size_t cancel_percent) { + printf("Test 8: large random test (%zu elements, cancel %zu%%)...\n", + num_elements, cancel_percent); + TimeoutHeap *h = timeout_heap_create(100); + TEST_ASSERT(h != NULL, "heap creation"); + + // Generate elements with unique expiration values + HeapElement *elems = malloc(num_elements * sizeof(HeapElement)); + TEST_ASSERT(elems != NULL, "alloc elems"); + + // Create array of unique expiration values + uint64_t *expirations = malloc(num_elements * sizeof(uint64_t)); + TEST_ASSERT(expirations != NULL, "alloc expirations"); + for (size_t i = 0; i < num_elements; i++) { + expirations[i] = i; // base unique value + } + // Shuffle using Fisher-Yates + srand((unsigned int)time(NULL) ^ 0x1234); + for (size_t i = num_elements - 1; i > 0; i--) { + size_t j = rand() % (i + 1); + uint64_t tmp = expirations[i]; + expirations[i] = expirations[j]; + expirations[j] = tmp; + } + + for (size_t i = 0; i < num_elements; i++) { + elems[i].expiration = expirations[i]; + elems[i].data = malloc(1); // unique pointer + elems[i].cancelled = 0; + int ret = timeout_heap_push(h, elems[i].expiration, elems[i].data); + TEST_ASSERT(ret == 0, "push success"); + } + free(expirations); + + // Cancel some random elements + size_t cancel_count = num_elements * cancel_percent / 100; + for (size_t i = 0; i < cancel_count; i++) { + size_t idx = rand() % num_elements; + if (!elems[idx].cancelled) { + int ret = timeout_heap_cancel(h, elems[idx].expiration, elems[idx].data); + TEST_ASSERT(ret == 0, "cancel success"); + elems[idx].cancelled = 1; + } + } + + // Build array of non-cancelled elements, sort by expiration + HeapElement *remaining = malloc(num_elements * sizeof(HeapElement)); + TEST_ASSERT(remaining != NULL, "alloc remaining"); + size_t remain_count = 0; + for (size_t i = 0; i < num_elements; i++) { + if (!elems[i].cancelled) { + remaining[remain_count++] = elems[i]; + } + } + qsort(remaining, remain_count, sizeof(HeapElement), compare_elements); + + // Pop and verify order + uint64_t last_expiration = 0; + for (size_t i = 0; i < remain_count; i++) { + TimeoutEntry entry; + int ret = timeout_heap_pop(h, &entry); + TEST_ASSERT(ret == 0, "pop success"); + TEST_ASSERT(entry.expiration == remaining[i].expiration, "expiration order"); + TEST_ASSERT(entry.data == remaining[i].data, "data matches"); + TEST_ASSERT(entry.expiration >= last_expiration, "non-decreasing"); + last_expiration = entry.expiration; + free(entry.data); + } + + // Heap empty + TimeoutEntry entry; + int ret = timeout_heap_pop(h, &entry); + TEST_ASSERT(ret == -1, "heap empty after all pops"); + + // Free cancelled elements data + for (size_t i = 0; i < num_elements; i++) { + if (elems[i].cancelled) { + free(elems[i].data); + } + } + + free(elems); + free(remaining); + timeout_heap_destroy(h); + printf(" Passed\n"); +} + +// Test 7: peek should not remove +static void test_peek_no_remove(void) { + printf("Test 7: peek does not remove...\n"); + TimeoutHeap *h = timeout_heap_create(10); + TEST_ASSERT(h != NULL, "heap creation"); + + int data1 = 1, data2 = 2; + timeout_heap_push(h, 100, &data1); + timeout_heap_push(h, 200, &data2); + + TimeoutEntry entry; + int ret = timeout_heap_peek(h, &entry); + TEST_ASSERT(ret == 0, "peek success"); + TEST_ASSERT(entry.expiration == 100, "peek expiration"); + TEST_ASSERT(h->size == 2, "size unchanged after peek"); + + ret = timeout_heap_peek(h, &entry); + TEST_ASSERT(ret == 0, "second peek success"); + TEST_ASSERT(entry.expiration == 100, "peek same element"); + + // Pop should still give same element + ret = timeout_heap_pop(h, &entry); + TEST_ASSERT(ret == 0, "pop after peek"); + TEST_ASSERT(entry.expiration == 100, "pop expiration"); + + timeout_heap_destroy(h); + printf(" Passed\n"); +} + +int main(void) { + printf("=== Timeout Heap Tests ===\n"); + + test_basic(); + test_ordering(100); // moderate number + test_ordering(1000); // more elements + test_cancel(); + test_duplicate_expirations(); + test_deleted_root(); + test_growth(); + test_peek_no_remove(); + test_large_random(5000, 10); // 5000 elements, 10% cancelled + test_large_random(10000, 5); // 10000 elements, 5% cancelled + + printf("\n=== All tests passed! ===\n"); + return 0; +} \ No newline at end of file diff --git a/net_emulator/test_uasync_random.c b/net_emulator/test_uasync_random.c new file mode 100644 index 00000000..85145b00 --- /dev/null +++ b/net_emulator/test_uasync_random.c @@ -0,0 +1,258 @@ +// test_uasync_random.c - Test uasync instance with random timeouts +#include "u_async.h" +#include +#include +#include +#include + +#define MAX_TIMEOUTS 1000 +#define MAX_TIMEOUT_TB 10000 // 1 second in timebase units (0.1 ms) +#define CANCEL_PROBABILITY 20 // 20% chance to cancel a timeout +#define NUM_ITERATIONS 50000 + +typedef struct { + int id; + int fired; + int cancelled; + uasync_t* ua; +} test_context_t; + +static int g_fired_count = 0; +static int g_cancelled_count = 0; +static int g_expected_fired = 0; + +static void timeout_callback(void* arg) { + test_context_t* ctx = (test_context_t*)arg; + if (!ctx) return; + + if (ctx->cancelled) { + printf("ERROR: Cancelled timeout %d fired!\n", ctx->id); + exit(1); + } + + ctx->fired = 1; + g_fired_count++; + + // Verify the timeout was removed from heap + // (we can't directly check, but we trust the implementation) +} + +static void run_random_test(void) { + printf("=== Random uasync timeout test ===\n"); + + // Create uasync instance + uasync_t* ua = uasync_create(); + if (!ua) { + printf("FAIL: uasync_create returned NULL\n"); + exit(1); + } + + printf("Created uasync instance\n"); + + // Allocate test contexts + test_context_t* contexts = malloc(MAX_TIMEOUTS * sizeof(test_context_t)); + if (!contexts) { + printf("FAIL: malloc failed\n"); + uasync_destroy(ua); + exit(1); + } + + memset(contexts, 0, MAX_TIMEOUTS * sizeof(test_context_t)); + + srand(time(NULL)); + + // Set random timeouts + int active_timeouts = 0; + for (int i = 0; i < MAX_TIMEOUTS; i++) { + contexts[i].id = i; + contexts[i].ua = ua; + + // Random timeout between 1 and MAX_TIMEOUT_TB + int timeout_tb = (rand() % MAX_TIMEOUT_TB) + 1; + + void* handle = uasync_set_timeout(ua, timeout_tb, &contexts[i], timeout_callback); + if (!handle) { + printf("WARN: uasync_set_timeout failed for timeout %d\n", i); + continue; + } + + // Store handle in context (we could store it, but we'll just track) + contexts[i].fired = 0; + contexts[i].cancelled = 0; + active_timeouts++; + + // Randomly cancel some timeouts + if (rand() % 100 < CANCEL_PROBABILITY) { + if (uasync_cancel_timeout(ua, handle) == ERR_OK) { + contexts[i].cancelled = 1; + g_cancelled_count++; + active_timeouts--; + } else { + printf("WARN: Failed to cancel timeout %d\n", i); + } + } + } + + g_expected_fired = active_timeouts; + printf("Set %d timeouts, cancelled %d, expecting %d to fire\n", + MAX_TIMEOUTS, g_cancelled_count, g_expected_fired); + + // Run event loop for enough time to fire all timeouts + // Calculate maximum timeout in milliseconds plus some margin + int max_timeout_ms = (MAX_TIMEOUT_TB + 1000) / 10; // convert to ms with margin + int iterations = 0; + + while (g_fired_count < g_expected_fired && iterations < NUM_ITERATIONS) { + // Poll with short timeout (10ms = 100 timebase units) + uasync_poll(ua, 100); + iterations++; + + // Check if we've waited long enough + if (iterations * 10 > max_timeout_ms * 5) { + // We've waited five times the max timeout, something might be wrong + if (g_fired_count < g_expected_fired) { + printf("WARN: Only %d of %d timeouts fired after %d ms\n", + g_fired_count, g_expected_fired, iterations * 10); + break; + } + } + } + + // Verify all non-cancelled timeouts fired + int failed_fires = 0; + for (int i = 0; i < MAX_TIMEOUTS; i++) { + if (!contexts[i].cancelled && !contexts[i].fired) { + failed_fires++; + if (failed_fires < 10) { // Limit output + printf("ERROR: Timeout %d didn't fire (not cancelled)\n", i); + } + } + + if (contexts[i].cancelled && contexts[i].fired) { + printf("ERROR: Cancelled timeout %d fired\n", i); + failed_fires++; + } + } + + if (failed_fires > 0) { + printf("FAIL: %d timeout failures\n", failed_fires); + } else { + printf("PASS: All %d expected timeouts fired correctly\n", g_fired_count); + } + + // Verify that no cancelled timeouts fired (already checked above) + printf("Cancelled timeouts: %d (none should fire)\n", g_cancelled_count); + + // Cleanup + free(contexts); + uasync_destroy(ua); + + if (failed_fires == 0) { + printf("=== Test PASSED ===\n"); + } else { + printf("=== Test FAILED ===\n"); + exit(1); + } +} + +static void simple_callback(void* arg) { + int* flag = (int*)arg; + *flag = 1; +} + +static void free_callback(void* arg) { + free(arg); +} + +static void test_instance_isolation(void) { + printf("\n=== Instance isolation test ===\n"); + + // Create two independent instances + uasync_t* ua1 = uasync_create(); + uasync_t* ua2 = uasync_create(); + if (!ua1 || !ua2) { + printf("FAIL: Failed to create instances\n"); + exit(1); + } + + int fired1 = 0; + int fired2 = 0; + + void* timeout1 = uasync_set_timeout(ua1, 10, &fired1, simple_callback); + void* timeout2 = uasync_set_timeout(ua2, 20, &fired2, simple_callback); + + if (!timeout1 || !timeout2) { + printf("FAIL: Failed to set timeouts\n"); + exit(1); + } + + // Run first instance - should fire timeout1 only + uasync_poll(ua1, 50); // 5ms wait + if (fired1 != 1) { + printf("FAIL: Instance 1 timeout didn't fire\n"); + exit(1); + } + if (fired2 != 0) { + printf("FAIL: Instance 2 timeout fired incorrectly\n"); + exit(1); + } + + // Cancel timeout2 + if (uasync_cancel_timeout(ua2, timeout2) != ERR_OK) { + printf("FAIL: Failed to cancel timeout2\n"); + exit(1); + } + + // Run second instance - nothing should fire + fired1 = 0; + uasync_poll(ua2, 50); + if (fired1 != 0) { + printf("FAIL: Instance 1 timeout fired from instance 2\n"); + exit(1); + } + if (fired2 != 0) { + printf("FAIL: Cancelled timeout fired\n"); + exit(1); + } + + uasync_destroy(ua1); + uasync_destroy(ua2); + + printf("PASS: Instance isolation works correctly\n"); +} + +static void test_memory_cleanup(void) { + printf("\n=== Memory cleanup test ===\n"); + + uasync_t* ua = uasync_create(); + if (!ua) { + printf("FAIL: uasync_create failed\n"); + exit(1); + } + + // Set many timeouts + for (int i = 0; i < 100; i++) { + int* counter = malloc(sizeof(int)); + *counter = i; + uasync_set_timeout(ua, 1000 + i, counter, free_callback); + } + + // Destroy instance without waiting for timeouts + // This should free all pending timeout nodes + uasync_destroy(ua); + + printf("PASS: Memory cleanup completed\n"); +} + +int main(void) { + printf("Starting uasync random timeout tests\n"); + printf("====================================\n"); + + run_random_test(); + test_instance_isolation(); + test_memory_cleanup(); + + printf("\n====================================\n"); + printf("All tests PASSED!\n"); + return 0; +} \ No newline at end of file diff --git a/net_emulator/timeout_heap.c b/net_emulator/timeout_heap.c new file mode 100644 index 00000000..7d61f0fb --- /dev/null +++ b/net_emulator/timeout_heap.c @@ -0,0 +1,134 @@ +// timeout_heap.c + +#include "timeout_heap.h" +#include +#include // For potential error printing, optional + +// Helper macros for 1-based indices +#define PARENT(i) ((i) / 2) +#define LEFT_CHILD(i) (2 * (i)) +#define RIGHT_CHILD(i) (2 * (i) + 1) + +TimeoutHeap *timeout_heap_create(size_t initial_capacity) { + TimeoutHeap *h = malloc(sizeof(TimeoutHeap)); + if (!h) return NULL; + h->heap = malloc(sizeof(TimeoutEntry) * initial_capacity); + if (!h->heap) { + free(h); + return NULL; + } + h->size = 0; + h->capacity = initial_capacity; + return h; +} + +void timeout_heap_destroy(TimeoutHeap *h) { + if (h) { + free(h->heap); + free(h); + } +} + +static void bubble_up(TimeoutHeap *h, size_t i) { + // i is 1-based + while (i > 1 && h->heap[PARENT(i) - 1].expiration > h->heap[i - 1].expiration) { + // Swap with parent + TimeoutEntry temp = h->heap[PARENT(i) - 1]; + h->heap[PARENT(i) - 1] = h->heap[i - 1]; + h->heap[i - 1] = temp; + i = PARENT(i); + } +} + +int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data) { + if (h->size == h->capacity) { + size_t new_cap = h->capacity ? h->capacity * 2 : 1; + TimeoutEntry *new_heap = realloc(h->heap, sizeof(TimeoutEntry) * new_cap); + if (!new_heap) return -1; // Allocation failed + h->heap = new_heap; + h->capacity = new_cap; + } + + // Insert at end (0-based) + size_t idx = h->size++; + h->heap[idx].expiration = expiration; + h->heap[idx].data = data; + h->heap[idx].deleted = 0; + + // Bubble up (1-based) + bubble_up(h, idx + 1); + return 0; +} + +static void heapify_down(TimeoutHeap *h, size_t i) { + // i is 1-based + while (1) { + size_t smallest = i; + size_t left = LEFT_CHILD(i); + size_t right = RIGHT_CHILD(i); + + if (left <= h->size && h->heap[left - 1].expiration < h->heap[smallest - 1].expiration) { + smallest = left; + } + if (right <= h->size && h->heap[right - 1].expiration < h->heap[smallest - 1].expiration) { + smallest = right; + } + if (smallest == i) break; + + // Swap + TimeoutEntry temp = h->heap[smallest - 1]; + h->heap[smallest - 1] = h->heap[i - 1]; + h->heap[i - 1] = temp; + i = smallest; + } +} + +static void remove_root(TimeoutHeap *h) { + if (h->size == 0) return; + + // Move last to root + h->heap[0] = h->heap[--h->size]; + + // Heapify down (1-based) + if (h->size > 0) { + heapify_down(h, 1); + } +} + +int timeout_heap_peek(TimeoutHeap *h, TimeoutEntry *out) { + if (h->size == 0) return -1; + + // Skip deleted + size_t i = 0; + while (i < h->size && h->heap[0].deleted) { + remove_root(h); + } + if (h->size == 0) return -1; + + *out = h->heap[0]; + return 0; +} + +int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out) { + if (h->size == 0) return -1; + + // Skip deleted + while (h->size > 0 && h->heap[0].deleted) { + remove_root(h); + } + if (h->size == 0) return -1; + + *out = h->heap[0]; + remove_root(h); + return 0; +} + +int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data) { + for (size_t i = 0; i < h->size; ++i) { + if (h->heap[i].expiration == expiration && h->heap[i].data == data) { + h->heap[i].deleted = 1; + return 0; + } + } + return -1; // Not found +} diff --git a/net_emulator/timeout_heap.h b/net_emulator/timeout_heap.h new file mode 100644 index 00000000..15e4ad1e --- /dev/null +++ b/net_emulator/timeout_heap.h @@ -0,0 +1,72 @@ +// timeout_heap.h + +#ifndef TIMEOUT_HEAP_H +#define TIMEOUT_HEAP_H + +#include // For uint64_t +#include // For size_t + +typedef uint64_t TimeoutTime; // e.g., milliseconds since epoch or from now + +typedef struct { + TimeoutTime expiration; // Sort key (smaller = earlier) + void *data; // User data (e.g., callback or ID) + int deleted; // 0 = active, 1 = deleted +} TimeoutEntry; + +typedef struct { + TimeoutEntry *heap; // Dynamic array + size_t size; // Current number of elements + size_t capacity; // Allocated size +} TimeoutHeap; + +/** + * Create a new timeout heap with initial capacity. + * @param initial_capacity Starting capacity (will grow as needed). + * @return Pointer to the heap, or NULL on failure. + */ +TimeoutHeap *timeout_heap_create(size_t initial_capacity); + +/** + * Destroy the timeout heap and free resources. + * @param h The heap to destroy. + */ +void timeout_heap_destroy(TimeoutHeap *h); + +/** + * Insert a new timeout into the heap. + * @param h The heap. + * @param expiration The expiration time. + * @param data User data associated with the timeout. + * @return 0 on success, -1 on allocation failure. + */ +int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data); + +/** + * Peek at the earliest non-deleted timeout without removing it. + * @param h The heap. + * @param out Where to store the entry. + * @return 0 on success, -1 if empty. + */ +int timeout_heap_peek(TimeoutHeap *h, TimeoutEntry *out); + +/** + * Pop the earliest non-deleted timeout from the heap. + * @param h The heap. + * @param out Where to store the entry. + * @return 0 on success, -1 if empty. + */ +int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out); + +/** + * Cancel a timeout by matching expiration and data. + * Scans the heap linearly, so O(n) time. + * Assumes combinations are unique; cancels the first match. + * @param h The heap. + * @param expiration The expiration time to match. + * @param data The data to match. + * @return 0 if found and canceled, -1 if not found. + */ +int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data); + +#endif // TIMEOUT_HEAP_H diff --git a/net_emulator/u_async.c b/net_emulator/u_async.c new file mode 100644 index 00000000..27e4daea --- /dev/null +++ b/net_emulator/u_async.c @@ -0,0 +1,391 @@ +// uasync.c + +#include "u_async.h" +#include "timeout_heap.h" +#include +#include +#include +#include +#include + +#ifndef FD_SETSIZE +#define FD_SETSIZE 1024 // Assume standard size; adjust if needed for your platform +#endif + +// Timeout node +struct timeout_node { + void* arg; + timeout_callback_t callback; + uint64_t expiration_ms; // absolute expiration time in milliseconds +}; + +// Socket node +struct socket_node { + int fd; + socket_callback_t read_cbk; + socket_callback_t write_cbk; + socket_callback_t except_cbk; + void* user_data; + struct socket_node* next; +}; + +// Uasync instance structure +struct uasync_s { + TimeoutHeap* timeout_heap; // Heap for timeout management + struct socket_node* socket_head; + int max_fd; + fd_set master_readfds; + fd_set master_writefds; + fd_set master_exceptfds; + struct socket_node* fd_to_node[FD_SETSIZE]; +}; + + + +// Helper to get current time +static void get_current_time(struct timeval* tv) { + gettimeofday(tv, NULL); +} + + + +// Helper to add timeval: tv += dt (timebase units) +static void timeval_add_tb(struct timeval* tv, int dt) { + tv->tv_usec += (dt % 10000) * 100; + tv->tv_sec += dt / 10000 + tv->tv_usec / 1000000; + tv->tv_usec %= 1000000; +} + +// Convert timeval to milliseconds (uint64_t) +static uint64_t timeval_to_ms(const struct timeval* tv) { + return (uint64_t)tv->tv_sec * 1000ULL + (uint64_t)tv->tv_usec / 1000ULL; +} + +// Instance management +uasync_t* uasync_create(void) { + uasync_t* ua = malloc(sizeof(uasync_t)); + if (!ua) return NULL; + + memset(ua, 0, sizeof(uasync_t)); + ua->max_fd = -1; + FD_ZERO(&ua->master_readfds); + FD_ZERO(&ua->master_writefds); + FD_ZERO(&ua->master_exceptfds); + memset(ua->fd_to_node, 0, sizeof(ua->fd_to_node)); + + ua->timeout_heap = timeout_heap_create(16); + if (!ua->timeout_heap) { + free(ua); + return NULL; + } + + return ua; +} + +void uasync_destroy(uasync_t* ua) { + if (!ua) return; + + // Free all remaining timeouts + if (ua->timeout_heap) { + while (1) { + TimeoutEntry entry; + if (timeout_heap_pop(ua->timeout_heap, &entry) != 0) break; + struct timeout_node* node = (struct timeout_node*)entry.data; + free(node); + } + timeout_heap_destroy(ua->timeout_heap); + } + + // Free all socket nodes + struct socket_node* cur = ua->socket_head; + while (cur) { + struct socket_node* next = cur->next; + free(cur); + cur = next; + } + + free(ua); +} + +void uasync_init(uasync_t* ua) { + if (!ua) return; + + ua->max_fd = -1; + FD_ZERO(&ua->master_readfds); + FD_ZERO(&ua->master_writefds); + FD_ZERO(&ua->master_exceptfds); + memset(ua->fd_to_node, 0, sizeof(ua->fd_to_node)); + + if (!ua->timeout_heap) { + ua->timeout_heap = timeout_heap_create(16); + } +} + +// Process expired timeouts +static void process_timeouts(uasync_t* ua) { + if (!ua || !ua->timeout_heap) return; + + struct timeval now_tv; + get_current_time(&now_tv); + uint64_t now_ms = timeval_to_ms(&now_tv); + + int processed = 0; + while (1) { + TimeoutEntry entry; + if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) break; + if (entry.expiration > now_ms) break; + + // DEBUG + // printf("process_timeouts: expiration=%lu now=%lu diff=%ld heap_size=%zu\n", + // entry.expiration, now_ms, (long)(now_ms - entry.expiration), + // ua->timeout_heap->size); + + // Pop the expired timeout + timeout_heap_pop(ua->timeout_heap, &entry); + struct timeout_node* node = (struct timeout_node*)entry.data; + if (node && node->callback) { + node->callback(node->arg); + processed++; + } else { + printf("WARN: timeout node missing callback\n"); + } + free(node); + } + if (processed > 0) { + // printf("process_timeouts: processed %d timeouts, now_ms=%lu heap_size=%zu\n", + // processed, now_ms, ua->timeout_heap->size); + } +} + +// Compute time to next timeout +static void get_next_timeout(uasync_t* ua, struct timeval* tv) { + if (!ua || !ua->timeout_heap) { + tv->tv_sec = 0; + tv->tv_usec = 0; + return; + } + + TimeoutEntry entry; + if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) { + tv->tv_sec = 0; + tv->tv_usec = 0; + return; + } + + struct timeval now_tv; + get_current_time(&now_tv); + uint64_t now_ms = timeval_to_ms(&now_tv); + + if (entry.expiration <= now_ms) { + tv->tv_sec = 0; + tv->tv_usec = 0; + return; + } + + uint64_t delta_ms = entry.expiration - now_ms; + if (delta_ms > 86400000) { // Cap at 1 day to avoid overflow + delta_ms = 86400000; + } + tv->tv_sec = delta_ms / 1000; + tv->tv_usec = (delta_ms % 1000) * 1000; + + // DEBUG + // printf("get_next_timeout: expiration=%lu now=%lu delta=%lu sec=%ld usec=%ld\n", + // entry.expiration, now_ms, delta_ms, (long)tv->tv_sec, (long)tv->tv_usec); +} + + + +void* uasync_set_timeout(uasync_t* ua, int timeout_tb, void* arg, timeout_callback_t callback) { + // printf("SETTIMEOUT called\n"); + // fflush(stdout); + if (!ua || timeout_tb < 0 || !callback) return NULL; + if (!ua->timeout_heap) return NULL; + + struct timeout_node* node = malloc(sizeof(struct timeout_node)); + if (!node) return NULL; + + node->arg = arg; + node->callback = callback; + + // Calculate expiration time in milliseconds + struct timeval now; + get_current_time(&now); + uint64_t now_ms = timeval_to_ms(&now); + timeval_add_tb(&now, timeout_tb); + node->expiration_ms = timeval_to_ms(&now); + + // DEBUG + // printf("set_timeout: tb=%d (%.2fms), now=%lu, expiration=%lu, delta=%ldms\n", + // timeout_tb, timeout_tb/10.0, now_ms, node->expiration_ms, + // (long)(node->expiration_ms - now_ms)); + + // Insert into heap + if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node) != 0) { + free(node); + return NULL; + } + + return node; +} + + + +err_t uasync_cancel_timeout(uasync_t* ua, void* t_id) { + if (!ua || !t_id || !ua->timeout_heap) return ERR_FAIL; + + struct timeout_node* node = (struct timeout_node*)t_id; + + // Try to cancel from heap + if (timeout_heap_cancel(ua->timeout_heap, node->expiration_ms, node) == 0) { + free(node); + return ERR_OK; + } + + // If not found in heap (maybe already expired and removed), still free + free(node); + return ERR_FAIL; +} + + + +void* uasync_add_socket(uasync_t* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) { + if (!ua || fd < 0 || fd >= FD_SETSIZE) return NULL; // Add bounds check for map + + struct socket_node* node = malloc(sizeof(struct socket_node)); + if (!node) return NULL; + + node->fd = fd; + node->read_cbk = read_cbk; + node->write_cbk = write_cbk; + node->except_cbk = except_cbk; + node->user_data = user_data; + node->next = ua->socket_head; + ua->socket_head = node; + + // Update masters (point 1) + if (read_cbk) FD_SET(fd, &ua->master_readfds); + if (write_cbk) FD_SET(fd, &ua->master_writefds); + if (except_cbk) FD_SET(fd, &ua->master_exceptfds); + + // Update map (point 2) + ua->fd_to_node[fd] = node; + + if (fd > ua->max_fd) ua->max_fd = fd; + + return node; +} + + + +err_t uasync_remove_socket(uasync_t* ua, void* s_id) { + if (!ua || !s_id) return ERR_FAIL; + + struct socket_node* node = (struct socket_node*)s_id; + struct socket_node* cur = ua->socket_head; + struct socket_node* prev = NULL; + + while (cur) { + if (cur == node) { + if (prev) { + prev->next = cur->next; + } else { + ua->socket_head = cur->next; + } + + // Update masters (point 1) + if (node->read_cbk) FD_CLR(node->fd, &ua->master_readfds); + if (node->write_cbk) FD_CLR(node->fd, &ua->master_writefds); + if (node->except_cbk) FD_CLR(node->fd, &ua->master_exceptfds); + + // Update map (point 2) + ua->fd_to_node[node->fd] = NULL; + + free(cur); + + // Update max_fd (simple rescan; optimize if needed by checking if removed == max_fd) + ua->max_fd = -1; + cur = ua->socket_head; + while (cur) { + if (cur->fd > ua->max_fd) ua->max_fd = cur->fd; + cur = cur->next; + } + return ERR_OK; + } + prev = cur; + cur = cur->next; + } + return ERR_FAIL; +} + + + +void uasync_mainloop(uasync_t* ua) { + while (1) { + uasync_poll(ua, -1); /* infinite timeout */ + } +} + + + +void uasync_poll(uasync_t* ua, int timeout_tb) { + if (!ua) return; + + /* Process expired timeouts */ + process_timeouts(ua); + + /* Prepare select with copies of masters */ + fd_set readfds = ua->master_readfds; + fd_set writefds = ua->master_writefds; + fd_set exceptfds = ua->master_exceptfds; + + struct timeval tv; + get_next_timeout(ua, &tv); + + /* If timeout_tb >= 0, compute timeout as min(timeout_tb, existing timer) */ + if (timeout_tb >= 0) { + struct timeval user_tv; + user_tv.tv_sec = timeout_tb / 10000; + user_tv.tv_usec = (timeout_tb % 10000) * 100; + + /* If no internal timer or user timeout is smaller */ + if (tv.tv_sec == 0 && tv.tv_usec == 0 && (!ua->timeout_heap || ua->timeout_heap->size == 0)) { + tv = user_tv; + } else if (user_tv.tv_sec < tv.tv_sec || + (user_tv.tv_sec == tv.tv_sec && user_tv.tv_usec < tv.tv_usec)) { + tv = user_tv; + } + } + + struct timeval* ptv = (tv.tv_sec == 0 && tv.tv_usec == 0 && (!ua->timeout_heap || ua->timeout_heap->size == 0)) ? NULL : &tv; + + int nfds = select(ua->max_fd + 1, &readfds, &writefds, &exceptfds, ptv); + if (nfds < 0) { + if (errno == EINTR) return; + perror("select"); + return; + } + + /* Process sockets with faster dispatch */ + for (int fd = 0; nfds > 0 && fd <= ua->max_fd; fd++) { + struct socket_node* node = ua->fd_to_node[fd]; + if (!node) continue; + + if (node->except_cbk && FD_ISSET(fd, &exceptfds)) { + node->except_cbk(fd, node->user_data); + nfds--; + } + if (node->read_cbk && FD_ISSET(fd, &readfds)) { + node->read_cbk(fd, node->user_data); + nfds--; + } + if (node->write_cbk && FD_ISSET(fd, &writefds)) { + node->write_cbk(fd, node->user_data); + nfds--; + } + } + + /* Process timeouts that may have expired during select. тайм-ауты после данных. */ + process_timeouts(ua); + +} diff --git a/net_emulator/u_async.h b/net_emulator/u_async.h new file mode 100644 index 00000000..43cf234d --- /dev/null +++ b/net_emulator/u_async.h @@ -0,0 +1,45 @@ +// uasync.h + +// модуль асинхронных операций. добавляем сокеты и таймауты и mainloop их обслуживает. + +#ifndef UASYNC_H +#define UASYNC_H + +#include +#include + +typedef void (*timeout_callback_t)(void* user_arg);// передаёт user_arg из uasync_set_timeout +typedef void (*socket_callback_t)(int fd, void* user_arg);// передаёт user_arg из uasync_add_socket +// user_arg полезен если нужно передать управляющую структуру. Ее можно выделить в памяти и в ней хранить всё что надо. т.е. при set_timeout передаём и получаем ее в callback-е + + +// Error type +typedef int err_t; +#define ERR_OK 0 +#define ERR_FAIL -1 + +// Opaque uasync instance handle +typedef struct uasync_s uasync_t; + +// Instance management +uasync_t* uasync_create(void); +void uasync_destroy(uasync_t* ua); +void uasync_init(uasync_t* ua); // Alternative: initialize existing instance + +// Main loop +void uasync_mainloop(uasync_t* ua);// бесконечный цикл, __noreturn + +// Timeouts, timebase = 0.1 mS +void* uasync_set_timeout(uasync_t* ua, int timeout_tb, void* user_arg, timeout_callback_t callback); +err_t uasync_cancel_timeout(uasync_t* ua, void* t_id); + +// Sockets +void* uasync_add_socket(uasync_t* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_arg); +err_t uasync_remove_socket(uasync_t* ua, void* s_id); + +// Single iteration of event loop with timeout (timebase units) +void uasync_poll(uasync_t* ua, int timeout_tb); + + + +#endif // UASYNC_H diff --git a/routing.c b/routing.c new file mode 100644 index 00000000..b75874fe --- /dev/null +++ b/routing.c @@ -0,0 +1,405 @@ +/** + * @file enhanced_routing.c + * @brief Enhanced routing system with bandwidth support and route types + */ + +#include "routing.h" +#include "connection.h" +#include +#include +#include +#include +#include + +#define INITIAL_ROUTE_CAPACITY 100 +#define ROUTE_EXPANSION_FACTOR 2 +#define MAX_SUBNET_VALIDATION_RANGES 50 + +static int compare_routes(const void *a, const void *b) { + const route_entry_t *route_a = (const route_entry_t *)a; + const route_entry_t *route_b = (const route_entry_t *)b; + + // First compare by network + if (route_a->network != route_b->network) { + return route_a->network < route_b->network ? -1 : 1; + } + + // Then by prefix length (longest prefix first) + if (route_b->prefix_length != route_a->prefix_length) { + return route_b->prefix_length - route_a->prefix_length; + } + + // Finally by metrics (best route first) + if (route_a->metrics.bandwidth_kbps != route_b->metrics.bandwidth_kbps) { + return route_b->metrics.bandwidth_kbps - route_a->metrics.bandwidth_kbps; + } + + if (route_a->metrics.latency_ms != route_b->metrics.latency_ms) { + return route_a->metrics.latency_ms - route_b->metrics.latency_ms; + } + + if (route_a->metrics.packet_loss_rate != route_b->metrics.packet_loss_rate) { + return route_a->metrics.packet_loss_rate - route_b->metrics.packet_loss_rate; + } + + return route_a->metrics.hop_count - route_b->metrics.hop_count; +} + +routing_table_t *routing_table_create(void) { + routing_table_t *table = calloc(1, sizeof(routing_table_t)); + if (!table) return NULL; + + table->capacity = INITIAL_ROUTE_CAPACITY; + table->entries = calloc(table->capacity, sizeof(route_entry_t)); + if (!table->entries) { + free(table); + return NULL; + } + + // Initialize subnet validation arrays + table->dynamic_subnets = calloc(MAX_SUBNET_VALIDATION_RANGES, sizeof(uint32_t) * 2); + table->local_subnets = calloc(MAX_SUBNET_VALIDATION_RANGES, sizeof(uint32_t) * 2); + + if (!table->dynamic_subnets || !table->local_subnets) { + free(table->entries); + free(table->dynamic_subnets); + free(table->local_subnets); + free(table); + return NULL; + } + + return table; +} + +void routing_table_destroy(routing_table_t *table) { + if (!table) return; + + // Free route entries + free(table->entries); + + // Free validation ranges + free(table->dynamic_subnets); + free(table->local_subnets); + + free(table); +} + +bool routing_table_insert(routing_table_t *table, const route_entry_t *entry) { + if (!table || !entry) return false; + + // Validate the route + if (!routing_validate_route(table, entry->network, entry->prefix_length, entry->type)) { + char ip_str[16]; + ip_to_string(entry->network, ip_str); + printf("[ROUTING] Ignoring invalid route: %s/%d (type: %s)\n", + ip_str, entry->prefix_length, route_type_to_string(entry->type)); + + return false; + } + + + // Check if we need to expand the table + if (table->count >= table->capacity) { + size_t new_capacity = table->capacity * ROUTE_EXPANSION_FACTOR; + route_entry_t *new_entries = realloc(table->entries, new_capacity * sizeof(route_entry_t)); + if (!new_entries) return false; + + table->entries = new_entries; + table->capacity = new_capacity; + } + + // Set timestamps and ensure route is active + route_entry_t new_entry = *entry; + new_entry.created_time = new_entry.last_update = (uint64_t)time(NULL) * 1000000; // Convert to microseconds + new_entry.last_used = 0; + + // Ensure route is active by default + if (!(new_entry.flags & ROUTE_FLAG_ACTIVE)) { + new_entry.flags |= ROUTE_FLAG_ACTIVE; + } + + + + // Insert and maintain sorted order + size_t insert_pos = table->count; + for (size_t i = 0; i < table->count; i++) { + if (compare_routes(&new_entry, &table->entries[i]) < 0) { + insert_pos = i; + break; + } + } + + // Shift entries to make room + if (insert_pos < table->count) { + memmove(&table->entries[insert_pos + 1], &table->entries[insert_pos], + (table->count - insert_pos) * sizeof(route_entry_t)); + } + + table->entries[insert_pos] = new_entry; + table->count++; + table->stats.total_routes++; + switch (new_entry.type) { + case ROUTE_TYPE_STATIC: table->stats.static_routes++; break; + case ROUTE_TYPE_DYNAMIC: table->stats.dynamic_routes++; break; + case ROUTE_TYPE_LOCAL: table->stats.local_routes++; break; + case ROUTE_TYPE_LEARNED: table->stats.learned_routes++; break; + } + + char ip_str[16]; + ip_to_string(entry->network, ip_str); + printf("[ROUTING] Added route: %s/%d -> %s (type: %s, bandwidth: %uK)\n", + ip_str, entry->prefix_length, + ip_to_string(entry->next_hop_ip, ip_str), + route_type_to_string(entry->type), entry->metrics.bandwidth_kbps); + + return true; +} + +bool routing_table_delete(routing_table_t *table, uint32_t network, uint8_t prefix_length, uint32_t source_node_id) { + if (!table) return false; + + for (size_t i = 0; i < table->count; i++) { + route_entry_t *entry = &table->entries[i]; + + if (entry->network == network && + entry->prefix_length == prefix_length && + entry->next_hop_ip == source_node_id) { + + route_type_t route_type = entry->type; + + // Shift remaining entries + if (i < table->count - 1) { + memmove(entry, &table->entries[i + 1], + (table->count - i - 1) * sizeof(route_entry_t)); + } + + table->count--; + table->stats.total_routes--; + switch (route_type) { + case ROUTE_TYPE_STATIC: table->stats.static_routes--; break; + case ROUTE_TYPE_DYNAMIC: table->stats.dynamic_routes--; break; + case ROUTE_TYPE_LOCAL: table->stats.local_routes--; break; + case ROUTE_TYPE_LEARNED: table->stats.learned_routes--; break; + } + + char ip_str[16]; + ip_to_string(network, ip_str); + printf("[ROUTING] Deleted route: %s/%d (source: %u)\n", + ip_str, prefix_length, source_node_id); + + + return true; + } + } + + return false; +} + +bool routing_table_lookup(routing_table_t *table, uint32_t dest_ip, route_entry_t *best_route) { + if (!table || !best_route) return false; + + table->stats.lookup_count++; + + // Find longest prefix match + for (size_t i = 0; i < table->count; i++) { + const route_entry_t *entry = &table->entries[i]; + + // Check if route is valid and matches destination + if (entry->flags & ROUTE_FLAG_ACTIVE) { + uint32_t mask = (entry->prefix_length == 0) ? 0 : (0xFFFFFFFFU << (32 - entry->prefix_length)); + if ((dest_ip & mask) == (entry->network & mask)) { + *best_route = *entry; + best_route->last_update = (uint64_t)time(NULL) * 1000000; + + char ip_str[16]; + ip_to_string(dest_ip, ip_str); + printf("[ROUTING] Found route for %s: %s/%d -> %s\n", + ip_str, ip_to_string(entry->network, ip_str), entry->prefix_length, + ip_to_string(entry->next_hop_ip, ip_str)); + + table->stats.hit_count++; + return true; + } + } + } + + return false; +} + +bool routing_validate_route(routing_table_t *table, uint32_t network, uint8_t prefix_length, route_type_t route_type) { + if (!table) return false; + + uint32_t *validation_ranges = NULL; + size_t range_count = 0; + + switch (route_type) { + case ROUTE_TYPE_STATIC: + case ROUTE_TYPE_LEARNED: + return true; // No validation for static or learned routes + case ROUTE_TYPE_DYNAMIC: + validation_ranges = table->dynamic_subnets; + range_count = table->dynamic_subnet_count; + break; + case ROUTE_TYPE_LOCAL: + validation_ranges = table->local_subnets; + range_count = table->local_subnet_count; + break; + default: + table->stats.miss_count++; + return false; + } + + if (range_count == 0) return true; // No validation ranges configured + + // Check if network falls within any validation range + for (size_t i = 0; i < range_count; i += 2) { + uint32_t range_network = validation_ranges[i]; + uint8_t range_prefix = (uint8_t)validation_ranges[i + 1]; + + uint32_t mask = (range_prefix == 0) ? 0 : (0xFFFFFFFFU << (32 - range_prefix)); + if ((network & mask) == (range_network & mask)) { + // Check if this route is more specific than or equal to the validation range + if (prefix_length >= range_prefix) { + return true; + } + } + } + + return false; +} + +bool enhanced_routing_add_subnet_range(routing_table_t *table, uint32_t network, uint8_t prefix_length, uint32_t **ranges, size_t *count) { + if (!table || !ranges || !count) return false; + + if (*count >= MAX_SUBNET_VALIDATION_RANGES - 2) return false; + + // Expand array if needed + if (*ranges == NULL) { + *ranges = calloc(MAX_SUBNET_VALIDATION_RANGES, sizeof(uint32_t)); + if (!*ranges) return false; + *count = 0; + } + + // Add network and prefix length as a pair + (*ranges)[*count] = network; + (*ranges)[*count + 1] = prefix_length; + *count += 2; + + char ip_str[16]; + ip_to_string(network, ip_str); + printf("[ROUTING] Added subnet validation range: %s/%d\n", ip_str, prefix_length); + + return true; +} + + + +bool routing_add_dynamic_subnet(routing_table_t *table, uint32_t network, uint8_t prefix_length) { + return enhanced_routing_add_subnet_range(table, network, prefix_length, &table->dynamic_subnets, &table->dynamic_subnet_count); +} + +bool routing_add_local_subnet(routing_table_t *table, uint32_t network, uint8_t prefix_length) { + return enhanced_routing_add_subnet_range(table, network, prefix_length, &table->local_subnets, &table->local_subnet_count); +} + +bool routing_get_all_routes(const routing_table_t *table, uint32_t network, uint8_t prefix_length, + route_entry_t **routes, size_t *count) { + if (!table || !routes || !count) return false; + + *routes = NULL; + *count = 0; + + // Count matching routes + for (size_t i = 0; i < table->count; i++) { + if (table->entries[i].network == network && table->entries[i].prefix_length == prefix_length) { + (*count)++; + } + } + + if (*count == 0) return true; // No routes found, but not an error + + // Allocate result array + *routes = calloc(*count, sizeof(route_entry_t)); + if (!*routes) { + *count = 0; + return false; + } + + // Copy matching routes + size_t result_index = 0; + for (size_t i = 0; i < table->count; i++) { + if (table->entries[i].network == network && table->entries[i].prefix_length == prefix_length) { + (*routes)[result_index] = table->entries[i]; + + result_index++; + } + } + + return true; +} + +void routing_table_print(const routing_table_t *table) { + if (!table) return; + + printf("\n=== Routing Table ===\n"); + printf("Total routes: %zu\n", table->count); + printf("Dynamic subnets: %zu, Local subnets: %zu\n", + table->dynamic_subnet_count / 2, table->local_subnet_count / 2); + + if (table->count > 0) { + printf("\nRoutes:\n"); + for (size_t i = 0; i < table->count; i++) { + const route_entry_t *entry = &table->entries[i]; + char network_str[16], next_hop_str[16]; + + ip_to_string(entry->network, network_str); + ip_to_string(entry->next_hop_ip, next_hop_str); + + printf(" %zu: %s/%d -> %s [%s]\n", + i + 1, network_str, entry->prefix_length, next_hop_str, + route_type_to_string(entry->type)); + + printf(" Bandwidth: %uK, Loss: %.2f%%, Latency: %ums, Hops: %d\n", + entry->metrics.bandwidth_kbps, + entry->metrics.packet_loss_rate / 100.0, + entry->metrics.latency_ms, + entry->metrics.hop_count); + + + + printf(" Flags: %s%s%s%s\n", + (entry->flags & ROUTE_FLAG_ACTIVE) ? "ACTIVE " : "", + (entry->flags & ROUTE_FLAG_VALIDATED) ? "VALIDATED " : "", + (entry->flags & ROUTE_FLAG_ADVERTISED) ? "ADVERTISED " : "", + (entry->flags & ROUTE_FLAG_LEARNED) ? "LEARNED" : ""); + printf("\n"); + } + } else { + printf("\nNo routes configured.\n"); + } +} + +const char *route_type_to_string(route_type_t type) { + switch (type) { + case ROUTE_TYPE_STATIC: return "STATIC"; + case ROUTE_TYPE_DYNAMIC: return "DYNAMIC"; + case ROUTE_TYPE_LOCAL: return "LOCAL"; + case ROUTE_TYPE_LEARNED: return "LEARNED"; + default: return "UNKNOWN"; + } +} + +void routing_get_stats(const routing_table_t *table, routing_stats_t *stats) { + if (!table || !stats) return; + *stats = table->stats; +} + +char *ip_to_string(uint32_t ip, char *buffer) { + if (!buffer) return NULL; + + struct in_addr addr; + addr.s_addr = ip; + strncpy(buffer, inet_ntoa(addr), 15); + buffer[15] = '\0'; + return buffer; +} \ No newline at end of file diff --git a/routing.h b/routing.h new file mode 100644 index 00000000..a8d7dc8b --- /dev/null +++ b/routing.h @@ -0,0 +1,170 @@ +/** + * @file routing.h + * @brief Улучшенная система маршрутизации с поддержкой пропускной способности и типов маршрутов + * @details Поддерживает статические/динамические маршруты с метриками и валидацией + */ +#ifndef ROUTING_H +#define ROUTING_H +#include +#include +#include +#include "connection.h" +#include "control_socket.h" + +// Типы маршрутов +typedef enum { + ROUTE_TYPE_STATIC = 0, // Статические маршруты, загружаемые при старте + ROUTE_TYPE_DYNAMIC = 1, // Динамические маршруты, полученные от соседей + ROUTE_TYPE_LOCAL = 2, // Локальные подсети + ROUTE_TYPE_LEARNED = 3 // Маршруты, полученные от других узлов +} route_type_t; + +// Флаги маршрута +typedef enum { + ROUTE_FLAG_ACTIVE = (1 << 0), // Маршрут активен (лучший путь) + ROUTE_FLAG_VALIDATED = (1 << 1), // Маршрут прошёл валидацию (по разрешенным подсетям в конфиге) + ROUTE_FLAG_ADVERTISED = (1 << 2), // Маршрут анонсируется другим узлам + ROUTE_FLAG_LEARNED = (1 << 3) // Маршрут получен от соседа +} route_flags_t; + +// Расширенные метрики маршрута +typedef struct { + uint32_t bandwidth_kbps; // Доступная (минимальная по всем узлам) пропускная способность до узла по этому маршруту, кбит/с + uint16_t packet_loss_rate; // Процент потерь пакетов (0–10000 = 0.00–100.00%) + uint16_t latency_ms; // Задержка в миллисекундах (суммарная до узла, не вулючает задержку до next hop) + uint8_t hop_count; // Количество хопов до цели (не включая next hop) + uint64_t last_updated; // Время последнего обновления (в timebase 0.1ms) +} route_metrics_t; + +// Расширенная запись маршрута +typedef struct { + uint32_t network; // Адрес сети (в сетевом порядке байтов) + uint8_t prefix_length; // Длина префикса (0–32) + uint32_t next_hop_ip; // IP-адрес следующего хопа (узла который анонсировал маршрут) + conn_handle_t* next_hop; // Указатель на соединение со следующим хопом + + route_type_t type; // Тип маршрута + uint8_t flags; // Флаги маршрута + + route_metrics_t metrics; // Метрики маршрута + uint64_t created_time; // Время создания записи + uint64_t last_update; // Время последнего обновления + uint64_t last_used; // Время последнего использования + +} route_entry_t; + + + +// Расширенная таблица маршрутизации (элементы сортированы по IP), может содержать дублирующие маршруты по альтернативным каналам. +typedef struct { + route_entry_t *entries; // Массив записей маршрутов + size_t count; // Количество активных записей + size_t capacity; // Выделенная ёмкость массива (расширение x2 при нехватке места) + + // Диапазоны для валидации подсетей + uint32_t *dynamic_subnets; // Разрешённые диапазоны динамических маршрутов (анонс не принимается если вне этой таблицы) + size_t dynamic_subnet_count; + + // Локальные подсети, которые будут анонсироваться + uint32_t *local_subnets; + size_t local_subnet_count; + routing_stats_t stats; // Statistics counters +} routing_table_t; + +/** + * @brief Создаёт улучшенную таблицу маршрутизации + * @return указатель на новую таблицу или NULL при ошибке + */ +routing_table_t *routing_table_create(void); + +/** + * @brief Уничтожает таблицу маршрутизации + */ +void routing_table_destroy(routing_table_t *table); + +/** + * @brief Получить статистику таблицы маршрутизации + * @param table таблица маршрутизации + * @param stats указатель на структуру для заполнения статистики + */ +void routing_get_stats(const routing_table_t *table, routing_stats_t *stats); + +/** + * @brief Добавляет маршрут в таблицу + * + * @param table таблица маршрутизации + * @param entry структура маршрута для вставки + * @return true при успехе, false при ошибке + */ +bool routing_table_insert(routing_table_t *table, const route_entry_t *entry); + +/** + * @brief Удаляет маршрут из таблицы + * + * @param table таблица маршрутизации + * @param network адрес сети + * @param prefix_length длина префикса + * @param source_node_id идентификатор узла-источника + * @return true — маршрут удалён, false — не найден + */ +bool routing_table_delete(routing_table_t *table, uint32_t network, uint8_t prefix_length, uint32_t source_node_id); + +/** + * @brief Ищет лучший маршрут до указанного адреса + * + * @param table таблица маршрутизации + * @param dest_ip целевой IP-адрес + * @param best_route [out] сюда будет записан лучший маршрут + * @return true — маршрут найден, false — нет подходящего маршрута + */ +bool routing_table_lookup(routing_table_t *table, uint32_t dest_ip, route_entry_t *best_route); + +/** + * @brief Проверяет маршрут на соответствие разрешённым подсетям + * + * @param table таблица с диапазонами валидации + * @param network адрес сети + * @param prefix_length длина префикса + * @param route_type ожидаемый тип маршрута + * @return true — маршрут разрешён, false — запрещён + */ +bool routing_validate_route(routing_table_t *table, uint32_t network, uint8_t prefix_length, route_type_t route_type); + +/** + * @brief Добавляет диапазон подсети для динамической валидации + */ +bool routing_add_dynamic_subnet(routing_table_t *table, uint32_t network, uint8_t prefix_length); + +/** + * @brief Добавляет локальную подсеть, которая будет анонсироваться соседям + */ +bool routing_add_local_subnet(routing_table_t *table, uint32_t network, uint8_t prefix_length); + +/** + * @brief Возвращает все маршруты для указанной сети/префикса + * + * @param routes [out] массив указателей (выделяется внутри, нужно освободить) + */ +bool routing_get_all_routes(const routing_table_t *table, uint32_t network, uint8_t prefix_length, + route_entry_t **routes, size_t *count); + +/** + * @brief Выводит содержимое таблицы маршрутизации (для отладки) + */ +void routing_table_print(const routing_table_t *table); + +/** + * @brief Преобразует тип маршрута в строку + * @return текстовое представление типа + */ +const char *route_type_to_string(route_type_t type); + +/** + * @brief Преобразует IP-адрес в строку (для отладки) + * + * @param ip адрес в сетевом порядке байтов + * @param buffer буфер минимум 16 байт + * @return указатель на заполненный буфер + */ +char *ip_to_string(uint32_t ip, char *buffer); +#endif // ROUTING_H \ No newline at end of file diff --git a/timeout_heap.c b/timeout_heap.c new file mode 100644 index 00000000..7d61f0fb --- /dev/null +++ b/timeout_heap.c @@ -0,0 +1,134 @@ +// timeout_heap.c + +#include "timeout_heap.h" +#include +#include // For potential error printing, optional + +// Helper macros for 1-based indices +#define PARENT(i) ((i) / 2) +#define LEFT_CHILD(i) (2 * (i)) +#define RIGHT_CHILD(i) (2 * (i) + 1) + +TimeoutHeap *timeout_heap_create(size_t initial_capacity) { + TimeoutHeap *h = malloc(sizeof(TimeoutHeap)); + if (!h) return NULL; + h->heap = malloc(sizeof(TimeoutEntry) * initial_capacity); + if (!h->heap) { + free(h); + return NULL; + } + h->size = 0; + h->capacity = initial_capacity; + return h; +} + +void timeout_heap_destroy(TimeoutHeap *h) { + if (h) { + free(h->heap); + free(h); + } +} + +static void bubble_up(TimeoutHeap *h, size_t i) { + // i is 1-based + while (i > 1 && h->heap[PARENT(i) - 1].expiration > h->heap[i - 1].expiration) { + // Swap with parent + TimeoutEntry temp = h->heap[PARENT(i) - 1]; + h->heap[PARENT(i) - 1] = h->heap[i - 1]; + h->heap[i - 1] = temp; + i = PARENT(i); + } +} + +int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data) { + if (h->size == h->capacity) { + size_t new_cap = h->capacity ? h->capacity * 2 : 1; + TimeoutEntry *new_heap = realloc(h->heap, sizeof(TimeoutEntry) * new_cap); + if (!new_heap) return -1; // Allocation failed + h->heap = new_heap; + h->capacity = new_cap; + } + + // Insert at end (0-based) + size_t idx = h->size++; + h->heap[idx].expiration = expiration; + h->heap[idx].data = data; + h->heap[idx].deleted = 0; + + // Bubble up (1-based) + bubble_up(h, idx + 1); + return 0; +} + +static void heapify_down(TimeoutHeap *h, size_t i) { + // i is 1-based + while (1) { + size_t smallest = i; + size_t left = LEFT_CHILD(i); + size_t right = RIGHT_CHILD(i); + + if (left <= h->size && h->heap[left - 1].expiration < h->heap[smallest - 1].expiration) { + smallest = left; + } + if (right <= h->size && h->heap[right - 1].expiration < h->heap[smallest - 1].expiration) { + smallest = right; + } + if (smallest == i) break; + + // Swap + TimeoutEntry temp = h->heap[smallest - 1]; + h->heap[smallest - 1] = h->heap[i - 1]; + h->heap[i - 1] = temp; + i = smallest; + } +} + +static void remove_root(TimeoutHeap *h) { + if (h->size == 0) return; + + // Move last to root + h->heap[0] = h->heap[--h->size]; + + // Heapify down (1-based) + if (h->size > 0) { + heapify_down(h, 1); + } +} + +int timeout_heap_peek(TimeoutHeap *h, TimeoutEntry *out) { + if (h->size == 0) return -1; + + // Skip deleted + size_t i = 0; + while (i < h->size && h->heap[0].deleted) { + remove_root(h); + } + if (h->size == 0) return -1; + + *out = h->heap[0]; + return 0; +} + +int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out) { + if (h->size == 0) return -1; + + // Skip deleted + while (h->size > 0 && h->heap[0].deleted) { + remove_root(h); + } + if (h->size == 0) return -1; + + *out = h->heap[0]; + remove_root(h); + return 0; +} + +int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data) { + for (size_t i = 0; i < h->size; ++i) { + if (h->heap[i].expiration == expiration && h->heap[i].data == data) { + h->heap[i].deleted = 1; + return 0; + } + } + return -1; // Not found +} diff --git a/timeout_heap.h b/timeout_heap.h new file mode 100644 index 00000000..15e4ad1e --- /dev/null +++ b/timeout_heap.h @@ -0,0 +1,72 @@ +// timeout_heap.h + +#ifndef TIMEOUT_HEAP_H +#define TIMEOUT_HEAP_H + +#include // For uint64_t +#include // For size_t + +typedef uint64_t TimeoutTime; // e.g., milliseconds since epoch or from now + +typedef struct { + TimeoutTime expiration; // Sort key (smaller = earlier) + void *data; // User data (e.g., callback or ID) + int deleted; // 0 = active, 1 = deleted +} TimeoutEntry; + +typedef struct { + TimeoutEntry *heap; // Dynamic array + size_t size; // Current number of elements + size_t capacity; // Allocated size +} TimeoutHeap; + +/** + * Create a new timeout heap with initial capacity. + * @param initial_capacity Starting capacity (will grow as needed). + * @return Pointer to the heap, or NULL on failure. + */ +TimeoutHeap *timeout_heap_create(size_t initial_capacity); + +/** + * Destroy the timeout heap and free resources. + * @param h The heap to destroy. + */ +void timeout_heap_destroy(TimeoutHeap *h); + +/** + * Insert a new timeout into the heap. + * @param h The heap. + * @param expiration The expiration time. + * @param data User data associated with the timeout. + * @return 0 on success, -1 on allocation failure. + */ +int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data); + +/** + * Peek at the earliest non-deleted timeout without removing it. + * @param h The heap. + * @param out Where to store the entry. + * @return 0 on success, -1 if empty. + */ +int timeout_heap_peek(TimeoutHeap *h, TimeoutEntry *out); + +/** + * Pop the earliest non-deleted timeout from the heap. + * @param h The heap. + * @param out Where to store the entry. + * @return 0 on success, -1 if empty. + */ +int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out); + +/** + * Cancel a timeout by matching expiration and data. + * Scans the heap linearly, so O(n) time. + * Assumes combinations are unique; cancels the first match. + * @param h The heap. + * @param expiration The expiration time to match. + * @param data The data to match. + * @return 0 if found and canceled, -1 if not found. + */ +int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data); + +#endif // TIMEOUT_HEAP_H diff --git a/tun_if.c b/tun_if.c new file mode 100644 index 00000000..63b11482 --- /dev/null +++ b/tun_if.c @@ -0,0 +1,238 @@ +// tun_if.c - TUN interface management implementation +#define _POSIX_C_SOURCE 200809L +#include "tun_if.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Create TUN device +static int create_tun_device(char *ifname, size_t ifname_len) { + struct ifreq ifr; + int fd; + + fd = open("/dev/net/tun", O_RDWR); + if (fd < 0) { + perror("open /dev/net/tun"); + return -1; + } + + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TUN | IFF_NO_PI; + + if (ifname && ifname_len > 0 && ifname[0] != '\0') { + strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); + } + + if (ioctl(fd, TUNSETIFF, &ifr) < 0) { + perror("ioctl TUNSETIFF"); + close(fd); + return -1; + } + + if (ifname && ifname_len > 0) { + strncpy(ifname, ifr.ifr_name, ifname_len - 1); + ifname[ifname_len - 1] = '\0'; + } + + return fd; +} + +// Run system command +static int run_command(const char *cmd) { + int ret = system(cmd); + if (ret == -1) { + perror("system"); + return -1; + } + if (WIFEXITED(ret) && WEXITSTATUS(ret) != 0) { + fprintf(stderr, "Command failed: %s\n", cmd); + return -1; + } + return 0; +} + +// Parse IP address and mask +static int parse_ip_mask(const char *ip_addr, char *ip, size_t ip_len, int *mask) { + if (!ip_addr || !ip || !mask) return -1; + + char *slash = strchr(ip_addr, '/'); + if (!slash) { + // Default mask /32 + *mask = 32; + strncpy(ip, ip_addr, ip_len - 1); + ip[ip_len - 1] = '\0'; + } else { + size_t ip_size = slash - ip_addr; + if (ip_size >= ip_len) return -1; + strncpy(ip, ip_addr, ip_size); + ip[ip_size] = '\0'; + + char *endptr; + long mask_val = strtol(slash + 1, &endptr, 10); + if (*endptr != '\0' || mask_val < 0 || mask_val > 32) return -1; + *mask = (int)mask_val; + } + + return 0; +} + +int tun_create(tun_config_t *config) { + if (!config) { + errno = EINVAL; + return -1; + } + + // Create TUN device + int fd = create_tun_device(config->ifname, sizeof(config->ifname)); + if (fd < 0) { + return -1; + } + + config->fd = fd; + + // Configure IP if specified + if (config->ip_addr[0] != '\0') { + if (tun_set_ip(config->ifname, config->ip_addr) < 0) { + close(fd); + return -1; + } + } + + // Set MTU if specified + if (config->mtu > 0) { + if (tun_set_mtu(config->ifname, config->mtu) < 0) { + // Non-fatal error, just warn + fprintf(stderr, "Warning: failed to set MTU on %s\n", config->ifname); + } + } + + // Bring interface up + if (tun_set_up(config->ifname) < 0) { + close(fd); + return -1; + } + + config->is_up = 1; + return 0; +} + +int tun_set_ip(const char *ifname, const char *ip_addr) { + if (!ifname || !ip_addr) { + errno = EINVAL; + return -1; + } + + char ip[64]; + int mask; + if (parse_ip_mask(ip_addr, ip, sizeof(ip), &mask) < 0) { + fprintf(stderr, "Invalid IP address format: %s\n", ip_addr); + errno = EINVAL; + return -1; + } + + char cmd[256]; + snprintf(cmd, sizeof(cmd), "ip addr add %s dev %s", ip_addr, ifname); + if (run_command(cmd) < 0) { + return -1; + } + + return 0; +} + +int tun_set_up(const char *ifname) { + if (!ifname) { + errno = EINVAL; + return -1; + } + + char cmd[256]; + snprintf(cmd, sizeof(cmd), "ip link set %s up", ifname); + if (run_command(cmd) < 0) { + return -1; + } + + return 0; +} + +int tun_set_mtu(const char *ifname, int mtu) { + if (!ifname || mtu <= 0) { + errno = EINVAL; + return -1; + } + + char cmd[256]; + snprintf(cmd, sizeof(cmd), "ip link set %s mtu %d", ifname, mtu); + if (run_command(cmd) < 0) { + return -1; + } + + return 0; +} + +ssize_t tun_read(int fd, uint8_t *buffer, size_t size) { + if (fd < 0 || !buffer || size == 0) { + errno = EINVAL; + return -1; + } + + ssize_t nread = read(fd, buffer, size); + if (nread < 0) { + perror("tun_read"); + } + + return nread; +} + +ssize_t tun_write(int fd, const uint8_t *buffer, size_t size) { + if (fd < 0 || !buffer || size == 0) { + errno = EINVAL; + return -1; + } + + ssize_t nwritten = write(fd, buffer, size); + if (nwritten < 0) { + perror("tun_write"); + } + + return nwritten; +} + +void tun_close(tun_config_t *config) { + if (!config) return; + + if (config->fd >= 0) { + close(config->fd); + config->fd = -1; + } + + config->is_up = 0; +} + +int tun_get_config(const char *ifname, tun_config_t *config) { + if (!ifname || !config) { + errno = EINVAL; + return -1; + } + + // TODO: Implement reading current interface configuration + // This would require parsing ip addr show output + + memset(config, 0, sizeof(*config)); + strncpy(config->ifname, ifname, sizeof(config->ifname) - 1); + config->fd = -1; + + return 0; +} \ No newline at end of file diff --git a/tun_if.h b/tun_if.h new file mode 100644 index 00000000..7b04d3b6 --- /dev/null +++ b/tun_if.h @@ -0,0 +1,95 @@ +// tun_if.h - TUN interface management for utun +#ifndef TUN_IF_H +#define TUN_IF_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// TUN interface configuration +typedef struct { + char ifname[16]; // Interface name (e.g., "tun12") + char ip_addr[64]; // IP address with mask (e.g., "10.0.0.1/24") + int mtu; // MTU size + int fd; // File descriptor + uint8_t is_up; // 1 if interface is up + // Statistics + uint64_t bytes_read; // Bytes read from TUN + uint64_t bytes_written; // Bytes written to TUN + uint32_t packets_read; // Packets read from TUN + uint32_t packets_written; // Packets written to TUN + uint32_t read_errors; // Read errors + uint32_t write_errors; // Write errors +} tun_config_t; + +/** + * @brief Create and configure TUN interface + * @param config TUN configuration (ifname can be empty for auto) + * @return 0 on success, -1 on error + */ +int tun_create(tun_config_t *config); + +/** + * @brief Configure IP address on TUN interface + * @param ifname Interface name + * @param ip_addr IP address with mask (e.g., "10.0.0.1/24") + * @return 0 on success, -1 on error + */ +int tun_set_ip(const char *ifname, const char *ip_addr); + +/** + * @brief Bring TUN interface up + * @param ifname Interface name + * @return 0 on success, -1 on error + */ +int tun_set_up(const char *ifname); + +/** + * @brief Set MTU on TUN interface + * @param ifname Interface name + * @param mtu MTU value + * @return 0 on success, -1 on error + */ +int tun_set_mtu(const char *ifname, int mtu); + +/** + * @brief Read packet from TUN interface + * @param fd TUN file descriptor + * @param buffer Buffer to store packet + * @param size Buffer size + * @return Number of bytes read, -1 on error + */ +ssize_t tun_read(int fd, uint8_t *buffer, size_t size); + +/** + * @brief Write packet to TUN interface + * @param fd TUN file descriptor + * @param buffer Packet data + * @param size Packet size + * @return Number of bytes written, -1 on error + */ +ssize_t tun_write(int fd, const uint8_t *buffer, size_t size); + +/** + * @brief Close TUN interface + * @param config TUN configuration + */ +void tun_close(tun_config_t *config); + +/** + * @brief Get current TUN configuration + * @param ifname Interface name + * @param config Output configuration + * @return 0 on success, -1 on error + */ +int tun_get_config(const char *ifname, tun_config_t *config); + +#ifdef __cplusplus +} +#endif + +#endif /* TUN_IF_H */ \ No newline at end of file diff --git a/u_async.c b/u_async.c index e72e83be..37647a8c 100644 --- a/u_async.c +++ b/u_async.c @@ -1,6 +1,7 @@ // uasync.c #include "u_async.h" +#include "timeout_heap.h" #include #include #include @@ -13,11 +14,9 @@ // Timeout node struct timeout_node { - struct timeval expire_time; void* arg; timeout_callback_t callback; - struct timeout_node* next; - struct timeout_node* prev; // Doubly linked for easier removal + uint64_t expiration_ms; // absolute expiration time in milliseconds }; // Socket node @@ -31,7 +30,7 @@ struct socket_node { }; // Global state -static struct timeout_node* timeout_head = NULL; // Sorted by expire_time, earliest first +static TimeoutHeap* timeout_heap = NULL; // Heap for timeout management static struct socket_node* socket_head = NULL; static int max_fd = -1; @@ -48,14 +47,7 @@ static void get_current_time(struct timeval* tv) { gettimeofday(tv, NULL); } -// Helper to compare times: returns 1 if a > b, 0 if equal, -1 if a < b -static int timeval_cmp(const struct timeval* a, const struct timeval* b) { - if (a->tv_sec > b->tv_sec) return 1; - if (a->tv_sec < b->tv_sec) return -1; - if (a->tv_usec > b->tv_usec) return 1; - if (a->tv_usec < b->tv_usec) return -1; - return 0; -} + // Helper to add timeval: tv += dt (timebase units) static void timeval_add_tb(struct timeval* tv, int dt) { @@ -64,66 +56,67 @@ static void timeval_add_tb(struct timeval* tv, int dt) { tv->tv_usec %= 1000000; } -// Insert timeout into sorted list -static struct timeout_node* insert_timeout(struct timeout_node* new_node) { - if (!timeout_head || timeval_cmp(&new_node->expire_time, &timeout_head->expire_time) < 0) { - new_node->next = timeout_head; - new_node->prev = NULL; - if (timeout_head) timeout_head->prev = new_node; - timeout_head = new_node; - return new_node; - } - - struct timeout_node* cur = timeout_head; - while (cur->next && timeval_cmp(&new_node->expire_time, &cur->next->expire_time) >= 0) { - cur = cur->next; - } - new_node->next = cur->next; - new_node->prev = cur; - if (cur->next) cur->next->prev = new_node; - cur->next = new_node; - return new_node; +// Convert timeval to milliseconds (uint64_t) +static uint64_t timeval_to_ms(const struct timeval* tv) { + return (uint64_t)tv->tv_sec * 1000ULL + (uint64_t)tv->tv_usec / 1000ULL; } -// Process expired timeouts -static void process_timeouts() { - struct timeval now; - get_current_time(&now); - while (timeout_head && timeval_cmp(&timeout_head->expire_time, &now) <= 0) { - struct timeout_node* node = timeout_head; - timeout_head = node->next; - if (timeout_head) timeout_head->prev = NULL; - if (node->callback) node->callback(node->arg); +// Process expired timeouts +static void process_timeouts() { + if (!timeout_heap) return; + + struct timeval now_tv; + get_current_time(&now_tv); + uint64_t now_ms = timeval_to_ms(&now_tv); + + while (1) { + TimeoutEntry entry; + if (timeout_heap_peek(timeout_heap, &entry) != 0) break; + if (entry.expiration > now_ms) break; + + // Pop the expired timeout + timeout_heap_pop(timeout_heap, &entry); + struct timeout_node* node = (struct timeout_node*)entry.data; + if (node && node->callback) { + node->callback(node->arg); + } free(node); } } // Compute time to next timeout static void get_next_timeout(struct timeval* tv) { - struct timeval now; - get_current_time(&now); - - if (!timeout_head) { + if (!timeout_heap) { tv->tv_sec = 0; - tv->tv_usec = 0; // No timeout, but select can handle NULL + tv->tv_usec = 0; return; } - - *tv = timeout_head->expire_time; - if (timeval_cmp(tv, &now) <= 0) { + + TimeoutEntry entry; + if (timeout_heap_peek(timeout_heap, &entry) != 0) { tv->tv_sec = 0; tv->tv_usec = 0; return; } - - tv->tv_sec -= now.tv_sec; - tv->tv_usec -= now.tv_usec; - if (tv->tv_usec < 0) { - tv->tv_sec--; - tv->tv_usec += 1000000; + + struct timeval now_tv; + get_current_time(&now_tv); + uint64_t now_ms = timeval_to_ms(&now_tv); + + if (entry.expiration <= now_ms) { + tv->tv_sec = 0; + tv->tv_usec = 0; + return; + } + + uint64_t delta_ms = entry.expiration - now_ms; + if (delta_ms > 86400000) { // Cap at 1 day to avoid overflow + delta_ms = 86400000; } + tv->tv_sec = delta_ms / 1000; + tv->tv_usec = (delta_ms % 1000) * 1000; } void uasync_init(void) { @@ -131,38 +124,51 @@ void uasync_init(void) { FD_ZERO(&master_writefds); FD_ZERO(&master_exceptfds); memset(fd_to_node, 0, sizeof(fd_to_node)); // Init map to NULL + + if (!timeout_heap) { + timeout_heap = timeout_heap_create(16); // initial capacity 16 + } } -void* uasync_set_timeout(int timeout, void* arg, timeout_callback_t callback) { - if (timeout < 0 || !callback) return NULL; - +void* uasync_set_timeout(int timeout_tb, void* arg, timeout_callback_t callback) { + if (timeout_tb < 0 || !callback) return NULL; + if (!timeout_heap) return NULL; + struct timeout_node* node = malloc(sizeof(struct timeout_node)); if (!node) return NULL; - - get_current_time(&node->expire_time); - timeval_add_tb(&node->expire_time, timeout); + node->arg = arg; node->callback = callback; - node->next = NULL; - node->prev = NULL; - - return insert_timeout(node); + + // Calculate expiration time in milliseconds + struct timeval now; + get_current_time(&now); + timeval_add_tb(&now, timeout_tb); + node->expiration_ms = timeval_to_ms(&now); + + // Insert into heap + if (timeout_heap_push(timeout_heap, node->expiration_ms, node) != 0) { + free(node); + return NULL; + } + + return node; } err_t uasync_cancel_timeout(void* t_id) { - if (!t_id) return ERR_FAIL; + if (!t_id || !timeout_heap) return ERR_FAIL; struct timeout_node* node = (struct timeout_node*)t_id; - if (node->prev) { - node->prev->next = node->next; - } else { - timeout_head = node->next; - } - if (node->next) { - node->next->prev = node->prev; + + // Try to cancel from heap + if (timeout_heap_cancel(timeout_heap, node->expiration_ms, node) == 0) { + free(node); + return ERR_OK; } + + // If not found in heap (maybe already expired and removed), still free free(node); - return ERR_OK; + return ERR_FAIL; } void* uasync_add_socket(int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) { @@ -257,7 +263,7 @@ void uasync_poll(int timeout_tb) { user_tv.tv_usec = (timeout_tb % 10000) * 100; /* If no internal timer or user timeout is smaller */ - if (tv.tv_sec == 0 && tv.tv_usec == 0 && !timeout_head) { + if (tv.tv_sec == 0 && tv.tv_usec == 0 && (!timeout_heap || timeout_heap->size == 0)) { tv = user_tv; } else if (user_tv.tv_sec < tv.tv_sec || (user_tv.tv_sec == tv.tv_sec && user_tv.tv_usec < tv.tv_usec)) { @@ -265,7 +271,7 @@ void uasync_poll(int timeout_tb) { } } - struct timeval* ptv = (tv.tv_sec == 0 && tv.tv_usec == 0 && !timeout_head) ? NULL : &tv; + struct timeval* ptv = (tv.tv_sec == 0 && tv.tv_usec == 0 && (!timeout_heap || timeout_heap->size == 0)) ? NULL : &tv; int nfds = select(max_fd + 1, &readfds, &writefds, &exceptfds, ptv); if (nfds < 0) { @@ -274,6 +280,9 @@ void uasync_poll(int timeout_tb) { return; } + /* Process timeouts that may have expired during select */ + process_timeouts(); + /* Process sockets with faster dispatch */ for (int fd = 0; nfds > 0 && fd <= max_fd; fd++) { struct socket_node* node = fd_to_node[fd]; diff --git a/utun.c b/utun.c new file mode 100644 index 00000000..f14a7179 --- /dev/null +++ b/utun.c @@ -0,0 +1,735 @@ +// utun.c - Main application for utun VPN tunnel +#define _DEFAULT_SOURCE +#define _POSIX_C_SOURCE 200809L +#include "config_parser.h" +#include "connection.h" +#include "tun_if.h" +#include "sc_lib.h" +#include "routing.h" +#include "control_socket.h" +#include "utun_state.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_PACKET_SIZE 2048 +#define DEFAULT_CONFIG "utun.conf" +#define DEFAULT_PIDFILE "/var/run/utun.pid" + +// Command line arguments +typedef struct { + char *config_file; + char *pid_file; + char *log_file; + char *tun_ifname; + char *tun_ip; + int foreground; + int help; +} cmd_args_t; + +// Global state + +// Hex string to binary conversion +static int hex_to_bin(const char *hex, uint8_t *bin, size_t bin_len) { + if (!hex || !bin) return -1; + + size_t hex_len = strlen(hex); + if (hex_len % 2 != 0 || hex_len / 2 > bin_len) return -1; + + for (size_t i = 0; i < hex_len; i += 2) { + char byte_str[3] = {hex[i], hex[i + 1], '\0'}; + char *endptr; + long byte = strtol(byte_str, &endptr, 16); + if (*endptr != '\0') return -1; + bin[i / 2] = (uint8_t)byte; + } + + return hex_len / 2; +} + +// Parse IP:port string +static int parse_addr(const char *addr_str, char *ip, size_t ip_len, uint16_t *port) { + if (!addr_str || !ip || !port) return -1; + + char *colon = strchr(addr_str, ':'); + if (!colon) return -1; + + size_t ip_size = colon - addr_str; + if (ip_size >= ip_len) return -1; + + strncpy(ip, addr_str, ip_size); + ip[ip_size] = '\0'; + + char *endptr; + long port_num = strtol(colon + 1, &endptr, 10); + if (*endptr != '\0' || port_num < 1 || port_num > 65535) return -1; + + *port = (uint16_t)port_num; + return 0; +} + +// Parse subnet string +static int parse_subnet(const char *subnet_str, uint32_t *network, uint8_t *prefix_length) { + if (!subnet_str || !network || !prefix_length) return -1; + char ip[64]; + int prefix; + if (sscanf(subnet_str, "%[^/]/%d", ip, &prefix) != 2) return -1; + if (prefix < 0 || prefix > 32) return -1; + struct in_addr addr; + if (inet_pton(AF_INET, ip, &addr) != 1) return -1; + *network = addr.s_addr; + *prefix_length = (uint8_t)prefix; + return 0; +} + +// Extract destination IPv4 address from packet +static uint32_t get_dest_ip(const uint8_t *packet, size_t len) { + if (len < 20) return 0; // Minimum IPv4 header size + // Check IP version (first nibble) + uint8_t version = (packet[0] >> 4) & 0x0F; + if (version != 4) return 0; + // Destination IP is at offset 16 + uint32_t dest_ip; + memcpy(&dest_ip, packet + 16, 4); + return dest_ip; +} + +// Initialize connection from configuration +static conn_handle_t* init_connection(const connection_config_t *conn_cfg, + const global_config_t *global_cfg) { + if (!conn_cfg || !global_cfg) return NULL; + + // Create connection + conn_handle_t *conn = conn_create(); + if (!conn) { + fprintf(stderr, "Failed to create connection\n"); + return NULL; + } + + // Convert keys from hex to binary + uint8_t my_priv_key[32] = {0}; + uint8_t my_pub_key[64] = {0}; + uint8_t peer_pub_key[64] = {0}; + + if (strlen(global_cfg->my_private_key_hex) > 0) { + if (hex_to_bin(global_cfg->my_private_key_hex, my_priv_key, sizeof(my_priv_key)) < 0) { + fprintf(stderr, "Invalid private key format\n"); + conn_destroy(conn); + return NULL; + } + } + + if (strlen(global_cfg->my_public_key_hex) > 0) { + if (hex_to_bin(global_cfg->my_public_key_hex, my_pub_key, sizeof(my_pub_key)) < 0) { + fprintf(stderr, "Invalid public key format\n"); + conn_destroy(conn); + return NULL; + } + } + + if (strlen(conn_cfg->peer_public_key_hex) > 0) { + if (hex_to_bin(conn_cfg->peer_public_key_hex, peer_pub_key, sizeof(peer_pub_key)) < 0) { + fprintf(stderr, "Invalid peer public key format\n"); + conn_destroy(conn); + return NULL; + } + } + + // Set keys + if (conn_set_keys(conn, my_pub_key, my_priv_key, peer_pub_key) < 0) { + fprintf(stderr, "Failed to set keys\n"); + conn_destroy(conn); + return NULL; + } + + // Parse addresses + char local_ip[64] = ""; + uint16_t local_port = 0; + char remote_ip[64] = ""; + uint16_t remote_port = 0; + + if (strlen(conn_cfg->local_addr) > 0) { + if (parse_addr(conn_cfg->local_addr, local_ip, sizeof(local_ip), &local_port) < 0) { + fprintf(stderr, "Invalid local address format: %s\n", conn_cfg->local_addr); + conn_destroy(conn); + return NULL; + } + } + + if (conn_cfg->mode == CONFIG_MODE_CLIENT && strlen(conn_cfg->remote_addr) > 0) { + if (parse_addr(conn_cfg->remote_addr, remote_ip, sizeof(remote_ip), &remote_port) < 0) { + fprintf(stderr, "Invalid remote address format: %s\n", conn_cfg->remote_addr); + conn_destroy(conn); + return NULL; + } + + // Apply net_debug mode: redirect traffic to emulator port (+10000) + if (global_cfg->net_debug && remote_port > 0) { + uint16_t original_port = remote_port; + remote_port += 10000; + if (remote_port < original_port) { // Overflow check + remote_port = 65535; + } + printf("[NET_DEBUG] Redirecting connection %s:%u -> %s:%u (emulator port %u)\n", + conn_cfg->name, original_port, remote_ip, remote_port - 10000, remote_port); + } + } + + // Connect + conn_mode_t mode = (conn_cfg->mode == CONFIG_MODE_CLIENT) ? CONN_MODE_CLIENT : CONN_MODE_SERVER; + + if (conn_connect(conn, + strlen(local_ip) > 0 ? local_ip : NULL, + local_port, + strlen(remote_ip) > 0 ? remote_ip : NULL, + remote_port, + mode) < 0) { + fprintf(stderr, "Failed to connect\n"); + conn_destroy(conn); + return NULL; + } + + return conn; +} + +// Callback for received data from connection +static void connection_recv_callback(conn_handle_t* conn, + const uint8_t* data, + size_t len, + void* user_data) { + (void)conn; // unused + utun_state_t *state = (utun_state_t*)user_data; + if (!state || !data || len == 0) return; + + // Write to TUN device + ssize_t written = tun_write(state->tun.fd, data, len); + if (written > 0) { + state->tun.bytes_written += written; + state->tun.packets_written++; + } else if (written < 0) { + state->tun.write_errors++; + fprintf(stderr, "Failed to write to TUN device: %s\n", strerror(errno)); + } else if (written != (ssize_t)len) { + fprintf(stderr, "Partial write to TUN device: %zd/%zu\n", written, len); + } +} + +// Initialize all connections +static int init_connections(utun_state_t *state) { + if (!state || !state->config) return -1; + + state->connection_count = state->config->connection_count; + state->connections = calloc(state->connection_count, sizeof(conn_handle_t*)); + if (!state->connections) return -1; + + for (int i = 0; i < state->connection_count; i++) { + state->connections[i] = init_connection(&state->config->connections[i], + &state->config->global); + if (!state->connections[i]) { + fprintf(stderr, "Failed to initialize connection %d\n", i); + // Cleanup already created connections + for (int j = 0; j < i; j++) { + conn_destroy(state->connections[j]); + } + free(state->connections); + state->connections = NULL; + state->connection_count = 0; + return -1; + } + + // Set receive callback + conn_set_recv_callback(state->connections[i], + connection_recv_callback, + state); + } + + return 0; +} + +// Add default route via first connection +static int add_default_route(utun_state_t *state) { + if (!state || !state->routing_table || state->connection_count == 0) return -1; + + route_entry_t default_route = {0}; + default_route.network = 0; // 0.0.0.0 + default_route.prefix_length = 0; + default_route.next_hop_ip = 0; // Unknown + default_route.next_hop = state->connections[0]; + default_route.type = ROUTE_TYPE_STATIC; + default_route.flags = ROUTE_FLAG_ACTIVE; + default_route.created_time = 0; // Not used + default_route.last_update = 0; + default_route.last_used = 0; + + if (!routing_table_insert(state->routing_table, &default_route)) { + fprintf(stderr, "Failed to add default route\n"); + return -1; + } + + printf("Added default route via first connection\n"); + return 0; +} + +// Parse command line arguments +static void parse_args(int argc, char *argv[], cmd_args_t *args) { + memset(args, 0, sizeof(*args)); + args->config_file = DEFAULT_CONFIG; + args->pid_file = DEFAULT_PIDFILE; + args->foreground = 0; + args->help = 0; + + static struct option long_options[] = { + {"config", required_argument, 0, 'c'}, + {"pidfile", required_argument, 0, 'p'}, + {"log", required_argument, 0, 'l'}, + {"tun", required_argument, 0, 't'}, + {"tun-ip", required_argument, 0, 'i'}, + {"foreground", no_argument, 0, 'f'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + int opt; + int option_index = 0; + + while ((opt = getopt_long(argc, argv, "c:p:l:t:i:fh", + long_options, &option_index)) != -1) { + switch (opt) { + case 'c': + args->config_file = optarg; + break; + case 'p': + args->pid_file = optarg; + break; + case 'l': + args->log_file = optarg; + break; + case 't': + args->tun_ifname = optarg; + break; + case 'i': + args->tun_ip = optarg; + break; + case 'f': + args->foreground = 1; + break; + case 'h': + args->help = 1; + break; + default: + fprintf(stderr, "Unknown option: %c\n", opt); + exit(1); + } + } +} + +// Print usage +static void print_usage(const char *progname) { + printf("Usage: %s [OPTIONS]\n", progname); + printf("Secure VPN tunnel over UDP with TUN interface\n\n"); + printf("Options:\n"); + printf(" -c, --config FILE Configuration file (default: %s)\n", DEFAULT_CONFIG); + printf(" -p, --pidfile FILE PID file (default: %s)\n", DEFAULT_PIDFILE); + printf(" -l, --log FILE Log file (default: stderr)\n"); + printf(" -t, --tun IFNAME TUN interface name (e.g., tun12)\n"); + printf(" -i, --tun-ip ADDR TUN interface IP address (e.g., 10.0.0.1/24)\n"); + printf(" -f, --foreground Run in foreground (don't daemonize)\n"); + printf(" -h, --help Show this help\n"); + printf("\nExamples:\n"); + printf(" %s -c myconfig.conf -t tun0 -i 10.0.0.1/24\n", progname); + printf(" %s --config server.conf --pidfile /var/run/utun.pid\n", progname); +} + +// Write PID file +static int write_pidfile(const char *pidfile) { + if (!pidfile) return -1; + + FILE *fp = fopen(pidfile, "w"); + if (!fp) { + perror("fopen pidfile"); + return -1; + } + + fprintf(fp, "%d\n", getpid()); + fclose(fp); + return 0; +} + +// Remove PID file +static void remove_pidfile(const char *pidfile) { + if (pidfile) { + unlink(pidfile); + } +} + +// Daemonize process +static int daemonize(void) { + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + return -1; + } + + if (pid > 0) { + // Parent exits + exit(0); + } + + // Child becomes session leader + if (setsid() < 0) { + perror("setsid"); + return -1; + } + + // Close standard file descriptors + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + + // Redirect to /dev/null + int fd = open("/dev/null", O_RDWR); + if (fd >= 0) { + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + if (fd > 2) close(fd); + } + + return 0; +} + +// Open log file +static FILE* open_logfile(const char *logfile) { + if (!logfile) return stderr; + + FILE *fp = fopen(logfile, "a"); + if (!fp) { + perror("fopen logfile"); + return stderr; + } + + // Set line buffering + setlinebuf(fp); + return fp; +} + +// Cleanup function +static void cleanup(utun_state_t *state, const char *pidfile) { + if (!state) return; + + state->running = 0; + + // Destroy connections + if (state->connections) { + for (int i = 0; i < state->connection_count; i++) { + if (state->connections[i]) { + conn_destroy(state->connections[i]); + } + } + free(state->connections); + state->connections = NULL; + } + + // Destroy routing table + if (state->routing_table) { + routing_table_destroy(state->routing_table); + state->routing_table = NULL; + } + + // Destroy control socket + if (state->control_socket) { + control_socket_destroy(state->control_socket); + state->control_socket = NULL; + } + + // Close TUN device + tun_close(&state->tun); + + // Free config + if (state->config) { + free_config(state->config); + state->config = NULL; + } + + // Close log file if not stderr + if (state->log_fp && state->log_fp != stderr) { + fclose(state->log_fp); + state->log_fp = NULL; + } + + // Remove PID file + remove_pidfile(pidfile); +} + +// Signal handler +static volatile sig_atomic_t got_signal = 0; +static void signal_handler(int sig) { + (void)sig; // unused + got_signal = 1; +} + +// Setup signal handlers +static void setup_signals(void) { + struct sigaction sa; + sa.sa_handler = signal_handler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGHUP, &sa, NULL); +} + +// Main event loop +static int event_loop(utun_state_t *state) { + // Determine number of file descriptors to poll + int num_fds = 1; // TUN device always + int ctrl_fd_idx = -1; + + if (state->control_socket) { + num_fds = 2; + ctrl_fd_idx = 1; + } + + struct pollfd fds[2]; + uint8_t buffer[MAX_PACKET_SIZE]; + + // Setup poll for TUN device + fds[0].fd = state->tun.fd; + fds[0].events = POLLIN; + + // Setup poll for control socket if exists + if (state->control_socket) { + fds[ctrl_fd_idx].fd = control_socket_get_fd(state->control_socket); + fds[ctrl_fd_idx].events = POLLIN; + } + + while (state->running && !got_signal) { + // Poll all file descriptors with timeout + int ret = poll(fds, num_fds, 100); // 100ms timeout + if (ret < 0) { + if (errno == EINTR) continue; + perror("poll"); + break; + } + + if (ret == 0) { + // Timeout - continue + continue; + } + + // Check control socket first + if (state->control_socket && (fds[ctrl_fd_idx].revents & POLLIN)) { + if (control_socket_process(state->control_socket, state) < 0) { + fprintf(stderr, "Error processing control socket request\n"); + } + } + + // Check TUN device + if (fds[0].revents & POLLIN) { + // Read from TUN device + ssize_t nread = tun_read(state->tun.fd, buffer, sizeof(buffer)); + if (nread < 0) { + if (errno == EINTR) continue; + perror("read from TUN"); + state->tun.read_errors++; + break; + } + + if (nread > 0) { + state->tun.bytes_read += nread; + state->tun.packets_read++; + // Route packet based on destination IP + uint32_t dest_ip = get_dest_ip(buffer, nread); + route_entry_t route; + if (dest_ip != 0 && routing_table_lookup(state->routing_table, dest_ip, &route)) { + // Found route, send to next hop connection + if (route.next_hop) { + if (conn_send(route.next_hop, buffer, nread) < 0) { + fprintf(stderr, "Failed to send packet via route\n"); + } + } else { + // Local route, no forwarding needed + } + } else { + // No route found, drop packet + char ip_str[16]; + ip_to_string(dest_ip, ip_str); + fprintf(stderr, "No route for destination IP %s\n", ip_str); + } + } + } + } + + return 0; +} + +// Main function +int main(int argc, char *argv[]) { + cmd_args_t args; + parse_args(argc, argv, &args); + + if (args.help) { + print_usage(argv[0]); + return 0; + } + + // Parse configuration + utun_config_t *config = parse_config(args.config_file); + if (!config) { + fprintf(stderr, "Failed to parse configuration file: %s\n", args.config_file); + return 1; + } + + // Print config for debugging + if (args.foreground) { + print_config(config); + } + + // Log net_debug mode + if (config->global.net_debug) { + printf("[NET_DEBUG] Network debug mode ENABLED\n"); + printf("[NET_DEBUG] Outgoing client connections will use port +10000\n"); + } + + // Initialize state + utun_state_t state = {0}; + state.config = config; + state.running = 1; + state.tun.fd = -1; + state.routing_table = routing_table_create(); + if (!state.routing_table) { + fprintf(stderr, "Failed to create routing table\n"); + cleanup(&state, args.pid_file); + return 1; + } + + // Setup TUN configuration from command line or config + if (args.tun_ifname) { + strncpy(state.tun.ifname, args.tun_ifname, sizeof(state.tun.ifname) - 1); + } + if (args.tun_ip) { + strncpy(state.tun.ip_addr, args.tun_ip, sizeof(state.tun.ip_addr) - 1); + } + + // If not set by command line, try to get from first connection config + if (state.tun.ifname[0] == '\0' && config->connection_count > 0) { + if (config->connections[0].tun_ifname[0] != '\0') { + strncpy(state.tun.ifname, config->connections[0].tun_ifname, sizeof(state.tun.ifname) - 1); + } + } + if (state.tun.ip_addr[0] == '\0' && config->connection_count > 0) { + if (config->connections[0].tun_ip[0] != '\0') { + strncpy(state.tun.ip_addr, config->connections[0].tun_ip, sizeof(state.tun.ip_addr) - 1); + } + } + + state.tun.mtu = 1500; // Default MTU + + // Load allowed subnets into routing table + for (int i = 0; i < config->allowed_subnet_count; i++) { + uint32_t network; + uint8_t prefix_len; + if (parse_subnet(config->allowed_subnets[i].subnet, &network, &prefix_len) == 0) { + // Add as dynamic subnet (allowed for validation) + if (!routing_add_dynamic_subnet(state.routing_table, network, prefix_len)) { + fprintf(stderr, "Failed to add allowed subnet: %s\n", config->allowed_subnets[i].subnet); + } else { + printf("Added allowed subnet: %s\n", config->allowed_subnets[i].subnet); + } + } else { + fprintf(stderr, "Invalid subnet format: %s\n", config->allowed_subnets[i].subnet); + } + } + + // Add TUN interface subnet as local subnet + if (state.tun.ip_addr[0] != '\0') { + uint32_t network; + uint8_t prefix_len; + if (parse_subnet(state.tun.ip_addr, &network, &prefix_len) == 0) { + if (!routing_add_local_subnet(state.routing_table, network, prefix_len)) { + fprintf(stderr, "Failed to add TUN subnet as local: %s\n", state.tun.ip_addr); + } else { + printf("Added TUN subnet as local: %s\n", state.tun.ip_addr); + } + } else { + fprintf(stderr, "Invalid TUN IP format: %s\n", state.tun.ip_addr); + } + } + + // Initialize control socket if configured + if (config->global.control_port != 0) { + const char *control_ip = config->global.control_ip[0] != '\0' ? config->global.control_ip : NULL; + state.control_socket = control_socket_create(control_ip, config->global.control_port); + if (!state.control_socket) { + fprintf(stderr, "Failed to create control socket on %s:%u\n", + control_ip ? control_ip : "0.0.0.0", config->global.control_port); + cleanup(&state, args.pid_file); + return 1; + } + control_socket_set_state(state.control_socket, &state); + printf("Control socket listening on %s:%u\n", + control_ip ? control_ip : "0.0.0.0", config->global.control_port); + } + + // Open log file + state.log_fp = open_logfile(args.log_file); + + // Daemonize if not in foreground + if (!args.foreground) { + if (daemonize() < 0) { + fprintf(stderr, "Failed to daemonize\n"); + cleanup(&state, args.pid_file); + return 1; + } + } + + // Write PID file + if (write_pidfile(args.pid_file) < 0) { + cleanup(&state, args.pid_file); + return 1; + } + + // Setup signal handlers + setup_signals(); + + // Create TUN device + if (tun_create(&state.tun) < 0) { + fprintf(stderr, "Failed to create TUN device\n"); + cleanup(&state, args.pid_file); + return 1; + } + + fprintf(state.log_fp, "Created TUN device: %s\n", state.tun.ifname); + + // Initialize connections + if (init_connections(&state) < 0) { + fprintf(stderr, "Failed to initialize connections\n"); + cleanup(&state, args.pid_file); + return 1; + } + + fprintf(state.log_fp, "Initialized %d connection(s)\n", state.connection_count); + + // Add default route via first connection + add_default_route(&state); + + // Run event loop + fprintf(state.log_fp, "Starting event loop...\n"); + event_loop(&state); + + // Cleanup + fprintf(state.log_fp, "Shutting down...\n"); + cleanup(&state, args.pid_file); + + return 0; +} \ No newline at end of file diff --git a/utun.conf.example b/utun.conf.example new file mode 100644 index 00000000..a4d81ace --- /dev/null +++ b/utun.conf.example @@ -0,0 +1,31 @@ +# utun configuration file example +# Global settings +[global] +# Generate keys using: openssl ecparam -name prime256v1 -genkey -noout | openssl ec -text -noout +# Private key (32 bytes in hex) +my_private_key=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef +# Public key (64 bytes in hex) +my_public_key=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef +# Global option (optional) +option=value + +# Server connection (listening for incoming connections) +[connection: server1] +mode=server +addr=0.0.0.0:51820 # Listen on all interfaces, port 51820 +peer_public_key=00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +so_mark=0 # Socket mark (0 = disabled) +netif=eth0 # Bind to specific network interface +tun=tun0 # TUN interface name +tun_ip=10.0.0.1/24 # TUN interface IP address + +# Client connection (initiate connection to server) +[connection: client1] +mode=client +from_addr=0.0.0.0:51821 # Local address to bind (0.0.0.0 = any) +to_addr=192.168.1.100:51820 # Remote server address +peer_public_key=00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +so_mark=0 +netif=eth0 +tun=tun1 +tun_ip=10.0.0.2/24 \ No newline at end of file diff --git a/utun_state.h b/utun_state.h new file mode 100644 index 00000000..38f87d0e --- /dev/null +++ b/utun_state.h @@ -0,0 +1,29 @@ +/** + * @file utun_state.h + * @brief Main application state structure + * @details Contains all module instances and configuration + */ + +#ifndef UTUN_STATE_H +#define UTUN_STATE_H + +#include +#include "tun_if.h" +#include "connection.h" +#include "routing.h" +#include "control_socket.h" +#include "config_parser.h" + +// Main application state +typedef struct utun_state { + tun_config_t tun; + conn_handle_t **connections; + int connection_count; + routing_table_t *routing_table; + control_socket_t *control_socket; + int running; + utun_config_t *config; + FILE *log_fp; +} utun_state_t; + +#endif /* UTUN_STATE_H */ \ No newline at end of file diff --git a/utun_test.cfg b/utun_test.cfg new file mode 100755 index 00000000..d7d7f215 --- /dev/null +++ b/utun_test.cfg @@ -0,0 +1,21 @@ +[global] +option=value1,value2 +my_private_key=1F1C8A.... (HEX) +my_public_key=1F1C8A.... (HEX) +tun_ip=1.2.3.4 + +# remote connections +[connection: srv_conn1] +mode=server +addr=127.0.0.1:1234 +peer_public_key=1F1C8A.... (HEX) + +[connection: conn2_blablabla] (это имя подключения и опции надо запомнить в структуре подключения) +mode=client +from_addr=127.0.0.1:1234 +to_addr=127.0.0.1:1234 +so_mark=100 (надо установить для этого подключения) +netif=lo0 (биндим к конкретному интерфейсу если это можно) +peer_public_key=1F1C8A.... (HEX) + +(подключений может быть много, надо все распарсить и открыть. имя должно загружаться в структуру connection)