Browse Source
- 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 targetsv2_dev
30 changed files with 5380 additions and 78 deletions
@ -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, но функциональное). Всегда надо думать как сделать проще и красивее. не надо нагромождать лишнего - сперва подумай действительно ли оно нужно. |
||||
|
||||
рассуждай на английском, остальное лучше на русском (отчёты, дальшейшие шаги и ответы на запросы всегда на русском) |
||||
@ -0,0 +1,413 @@
|
||||
// config_parser.c - Configuration parser for utun application
|
||||
#define _POSIX_C_SOURCE 200809L |
||||
#include "config_parser.h" |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <strings.h> |
||||
#include <ctype.h> |
||||
#include <errno.h> |
||||
|
||||
#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; |
||||
} |
||||
@ -0,0 +1,117 @@
|
||||
// config_parser.h - Configuration parser for utun application
|
||||
#ifndef CONFIG_PARSER_H |
||||
#define CONFIG_PARSER_H |
||||
|
||||
#include <stdint.h> |
||||
#include <stddef.h> |
||||
#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 */ |
||||
@ -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 <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <unistd.h> |
||||
#include <errno.h> |
||||
#include <time.h> |
||||
#include <sys/socket.h> |
||||
#include <netinet/in.h> |
||||
#include <arpa/inet.h> |
||||
#include <fcntl.h> |
||||
#include <sys/time.h> |
||||
|
||||
// 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
|
||||
} |
||||
} |
||||
@ -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 <stdint.h> |
||||
#include <stddef.h> |
||||
#include <stdbool.h> |
||||
|
||||
// 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 */ |
||||
@ -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 |
||||
@ -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('<HH', command, sequence) |
||||
|
||||
try: |
||||
sock.sendto(request, (host, port)) |
||||
data, addr = sock.recvfrom(4096) |
||||
except socket.timeout: |
||||
print("Timeout: No response from server") |
||||
return None |
||||
finally: |
||||
sock.close() |
||||
|
||||
return data |
||||
|
||||
def parse_stats_response(data): |
||||
"""Parse statistics response packet""" |
||||
# Unpack header (common for all responses) |
||||
# struct control_response_header { uint16_t response_type; uint16_t error_code; uint16_t sequence; } |
||||
if len(data) < 6: |
||||
print("Invalid response length") |
||||
return None |
||||
|
||||
resp_type, err_code, seq = struct.unpack('<HHH', data[:6]) |
||||
|
||||
if err_code != CONTROL_ERR_NONE: |
||||
print(f"Error response: code {err_code}") |
||||
return None |
||||
|
||||
if resp_type == CONTROL_RESP_STATS: |
||||
# Parse full stats packet (need to know exact structure) |
||||
# For now, just print raw data size |
||||
print(f"Received stats packet: {len(data)} bytes") |
||||
# TODO: Implement full parsing based on control_socket.h structures |
||||
# This requires matching the packed C structures |
||||
return data |
||||
elif resp_type == CONTROL_RESP_STATUS: |
||||
# Parse status packet |
||||
print("Status response") |
||||
return data |
||||
elif resp_type == CONTROL_RESP_ERROR: |
||||
print("Error response") |
||||
return None |
||||
else: |
||||
print(f"Unknown response type: {resp_type}") |
||||
return None |
||||
|
||||
def print_hex(data): |
||||
"""Print hex dump of data""" |
||||
for i in range(0, len(data), 16): |
||||
chunk = data[i:i+16] |
||||
hex_str = ' '.join(f'{b:02x}' for b in chunk) |
||||
ascii_str = ''.join(chr(b) if 32 <= b < 127 else '.' for b in chunk) |
||||
print(f'{i:04x}: {hex_str:<48} {ascii_str}') |
||||
|
||||
def main(): |
||||
host = '127.0.0.1' |
||||
port = 5555 |
||||
|
||||
if len(sys.argv) > 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('<HHH', data[:6]) |
||||
print(f"Response type: {resp_type}, Error: {err_code}, Sequence: {seq}") |
||||
|
||||
if resp_type == CONTROL_RESP_STATS: |
||||
# Try to parse timestamp (uint64_t after header) |
||||
if len(data) >= 14: |
||||
timestamp = struct.unpack('<Q', data[6:14])[0] |
||||
print(f"Timestamp: {timestamp} μs ({timestamp/1000000:.3f} s)") |
||||
|
||||
# Print approximate stats structure size |
||||
# The full stats packet should be 1024+ bytes |
||||
print(f"Expected stats size ~1024 bytes, got {len(data)}") |
||||
|
||||
# Send GET_STATUS command |
||||
print("\n--- Requesting status ---") |
||||
data = send_command(host, port, CONTROL_CMD_GET_STATUS, 2) |
||||
if data: |
||||
print(f"Status response {len(data)} bytes") |
||||
if len(data) >= 6: |
||||
resp_type, err_code, seq = struct.unpack('<HHH', data[:6]) |
||||
print(f"Response type: {resp_type}, Error: {err_code}, Sequence: {seq}") |
||||
|
||||
if __name__ == '__main__': |
||||
main() |
||||
@ -0,0 +1,33 @@
|
||||
# Makefile for network emulator
|
||||
CC = gcc
|
||||
CFLAGS = -Os -std=c99 -Wall -Wextra -D_ISOC99_SOURCE
|
||||
INCLUDES = -I.
|
||||
|
||||
SRCS = net_emulator.c u_async.c timeout_heap.c
|
||||
OBJS = $(SRCS:.c=.o)
|
||||
|
||||
TARGET = net_emulator
|
||||
|
||||
all: $(TARGET) |
||||
|
||||
$(TARGET): $(OBJS) |
||||
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^
|
||||
|
||||
test: test_timeout_heap test_uasync_random |
||||
./test_timeout_heap
|
||||
./test_uasync_random
|
||||
|
||||
test_timeout_heap: test_timeout_heap.o timeout_heap.o |
||||
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^
|
||||
|
||||
test_uasync_random: test_uasync_random.o u_async.o timeout_heap.o |
||||
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^
|
||||
|
||||
%.o: %.c |
||||
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
|
||||
|
||||
clean: |
||||
rm -f $(OBJS) $(TARGET) test_timeout_heap.o timeout_heap.o test_timeout_heap \
|
||||
test_uasync_random.o test_uasync_random
|
||||
|
||||
.PHONY: all clean test |
||||
@ -0,0 +1,502 @@
|
||||
// net_emulator.c - Network emulator for testing utun with delay, loss, reordering
|
||||
#define _POSIX_C_SOURCE 200809L |
||||
#include "net_emulator.h" |
||||
#include "u_async.h" |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <ctype.h> |
||||
#include <errno.h> |
||||
#include <unistd.h> |
||||
#include <fcntl.h> |
||||
#include <arpa/inet.h> |
||||
#include <time.h> |
||||
#include <sys/time.h> |
||||
|
||||
#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 <config_file>\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; |
||||
} |
||||
@ -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 |
||||
@ -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) |
||||
@ -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 <stdint.h> |
||||
#include <stddef.h> |
||||
#include <sys/socket.h> |
||||
#include <netinet/in.h> |
||||
#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 */ |
||||
@ -0,0 +1,412 @@
|
||||
#include "timeout_heap.h" |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <assert.h> |
||||
#include <time.h> |
||||
#include <stdint.h> |
||||
|
||||
#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; |
||||
} |
||||
@ -0,0 +1,258 @@
|
||||
// test_uasync_random.c - Test uasync instance with random timeouts
|
||||
#include "u_async.h" |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <time.h> |
||||
|
||||
#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; |
||||
} |
||||
@ -0,0 +1,134 @@
|
||||
// timeout_heap.c
|
||||
|
||||
#include "timeout_heap.h" |
||||
#include <stdlib.h> |
||||
#include <stdio.h> // 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
|
||||
} |
||||
@ -0,0 +1,72 @@
|
||||
// timeout_heap.h
|
||||
|
||||
#ifndef TIMEOUT_HEAP_H |
||||
#define TIMEOUT_HEAP_H |
||||
|
||||
#include <stdint.h> // For uint64_t |
||||
#include <stddef.h> // 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
|
||||
@ -0,0 +1,391 @@
|
||||
// uasync.c
|
||||
|
||||
#include "u_async.h" |
||||
#include "timeout_heap.h" |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include <stdlib.h> |
||||
#include <unistd.h> |
||||
#include <errno.h> |
||||
|
||||
#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); |
||||
|
||||
} |
||||
@ -0,0 +1,45 @@
|
||||
// uasync.h
|
||||
|
||||
// модуль асинхронных операций. добавляем сокеты и таймауты и mainloop их обслуживает.
|
||||
|
||||
#ifndef UASYNC_H |
||||
#define UASYNC_H |
||||
|
||||
#include <sys/time.h> |
||||
#include <sys/select.h> |
||||
|
||||
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
|
||||
@ -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 <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <arpa/inet.h> |
||||
#include <time.h> |
||||
|
||||
#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; |
||||
} |
||||
@ -0,0 +1,170 @@
|
||||
/**
|
||||
* @file routing.h |
||||
* @brief Улучшенная система маршрутизации с поддержкой пропускной способности и типов маршрутов |
||||
* @details Поддерживает статические/динамические маршруты с метриками и валидацией |
||||
*/ |
||||
#ifndef ROUTING_H |
||||
#define ROUTING_H |
||||
#include <stdint.h> |
||||
#include <stddef.h> |
||||
#include <stdbool.h> |
||||
#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
|
||||
@ -0,0 +1,134 @@
|
||||
// timeout_heap.c
|
||||
|
||||
#include "timeout_heap.h" |
||||
#include <stdlib.h> |
||||
#include <stdio.h> // 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
|
||||
} |
||||
@ -0,0 +1,72 @@
|
||||
// timeout_heap.h
|
||||
|
||||
#ifndef TIMEOUT_HEAP_H |
||||
#define TIMEOUT_HEAP_H |
||||
|
||||
#include <stdint.h> // For uint64_t |
||||
#include <stddef.h> // 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
|
||||
@ -0,0 +1,238 @@
|
||||
// tun_if.c - TUN interface management implementation
|
||||
#define _POSIX_C_SOURCE 200809L |
||||
#include "tun_if.h" |
||||
|
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <unistd.h> |
||||
#include <fcntl.h> |
||||
#include <sys/ioctl.h> |
||||
#include <sys/socket.h> |
||||
#include <sys/types.h> |
||||
#include <sys/stat.h> |
||||
#include <net/if.h> |
||||
#include <netinet/in.h> |
||||
#include <arpa/inet.h> |
||||
#include <linux/if.h> |
||||
#include <linux/if_tun.h> |
||||
#include <errno.h> |
||||
|
||||
// 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; |
||||
} |
||||
@ -0,0 +1,95 @@
|
||||
// tun_if.h - TUN interface management for utun
|
||||
#ifndef TUN_IF_H |
||||
#define TUN_IF_H |
||||
|
||||
#include <stdint.h> |
||||
#include <stddef.h> |
||||
#include <sys/types.h> |
||||
|
||||
#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 */ |
||||
@ -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 <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <unistd.h> |
||||
#include <fcntl.h> |
||||
#include <sys/stat.h> |
||||
#include <errno.h> |
||||
#include <signal.h> |
||||
#include <poll.h> |
||||
#include <getopt.h> |
||||
#include <arpa/inet.h> |
||||
|
||||
#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; |
||||
} |
||||
@ -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 |
||||
@ -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 <stdio.h> |
||||
#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 */ |
||||
@ -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) |
||||
Loading…
Reference in new issue