Browse Source

Add utun VPN implementation and enhance async infrastructure

- Add utun VPN daemon with config parsing, control socket, routing, and TUN interface
- Refactor net_emulator/u_async to support multiple independent instances
- Replace linked-list timeout management with heap-based implementation
- Fix timeout processing bug (process timeouts after select)
- Add comprehensive random timeout and instance isolation tests
- Update Makefile to build utun and new test targets
v2_dev
jek 9 months ago
parent
commit
70b8caadbf
  1. 18
      AGENTS.md
  2. 17
      Makefile
  3. 413
      config_parser.c
  4. 117
      config_parser.h
  5. 463
      control_socket.c
  6. 208
      control_socket.h
  7. 29
      example_control.conf
  8. 132
      monitor.py
  9. 33
      net_emulator/Makefile
  10. 502
      net_emulator/net_emulator.c
  11. 23
      net_emulator/net_emulator.conf
  12. 12
      net_emulator/net_emulator.conf.example
  13. 90
      net_emulator/net_emulator.h
  14. 412
      net_emulator/test_timeout_heap.c
  15. 258
      net_emulator/test_uasync_random.c
  16. 134
      net_emulator/timeout_heap.c
  17. 72
      net_emulator/timeout_heap.h
  18. 391
      net_emulator/u_async.c
  19. 45
      net_emulator/u_async.h
  20. 405
      routing.c
  21. 170
      routing.h
  22. 134
      timeout_heap.c
  23. 72
      timeout_heap.h
  24. 238
      tun_if.c
  25. 95
      tun_if.h
  26. 159
      u_async.c
  27. 735
      utun.c
  28. 31
      utun.conf.example
  29. 29
      utun_state.h
  30. 21
      utun_test.cfg

18
AGENTS.md

@ -0,0 +1,18 @@
# AGENTS.md - uTun Development Guide
This document provides essential information for agentic coding assistants working on the uTun VPN tunnel project.
Совместимость со станым при доработках сохранять не надо. вместо этого надо доработать остальной код чтобы работало
Если не работает - добавляй отладочную информацию чтобы быстрее найти проблемное место и не рушить работающий код.
если отладочная информация будет флудить сделай ее отключаемой или подумай как ограничить ее вывод по возможности сохраняя информативность.
Добавляй в код структуры для статистики (например суммируй число ошибок, вызовов и других потенциально нужных для анализа при ошибках метрик).
добавляй в тест подсчет времени сколько каждфй этап длился для лучшего понимания оптимизации
веди changelog.txt: дата время: что поменялось
## Code Style Guidelines
комменты в коде на русском
Приоритет - простое (логически понятное, минималистичное api, но функциональное). Всегда надо думать как сделать проще и красивее. не надо нагромождать лишнего - сперва подумай действительно ли оно нужно.
рассуждай на английском, остальное лучше на русском (отчёты, дальшейшие шаги и ответы на запросы всегда на русском)

17
Makefile

@ -16,12 +16,14 @@ TINYCRYPT_SRCS := \
TINYCRYPT_OBJS := $(TINYCRYPT_SRCS:.c=.o)
SC_LIB_OBJS := sc_lib.o
UASYNC_OBJS := u_async.o
UASYNC_OBJS := u_async.o timeout_heap.o
PN_OBJS := pkt_normalizer.o settings.o
LL_QUEUE_OBJS := ll_queue.o
ETCP_OBJS := etcp.o
CONFIG_PARSER_OBJS := config_parser.o
TUN_IF_OBJS := tun_if.o
all: $(TEST_DIR)/test_ecc_encrypt $(TEST_DIR)/test_sc_lib $(TEST_DIR)/test_udp_secure $(TEST_DIR)/test_pkt_normalizer $(TEST_DIR)/test_etcp $(TEST_DIR)/test_etcp_stress $(TEST_DIR)/test_etcp_simple $(TEST_DIR)/test_connection $(TEST_DIR)/test_connection_stress $(TEST_DIR)/test_new_features
all: utun $(TEST_DIR)/test_ecc_encrypt $(TEST_DIR)/test_sc_lib $(TEST_DIR)/test_udp_secure $(TEST_DIR)/test_pkt_normalizer $(TEST_DIR)/test_etcp $(TEST_DIR)/test_etcp_stress $(TEST_DIR)/test_etcp_simple $(TEST_DIR)/test_connection $(TEST_DIR)/test_connection_stress $(TEST_DIR)/test_new_features $(TEST_DIR)/test_utun_integration $(TEST_DIR)/test_utun_fork
$(TEST_DIR)/test_pkt_normalizer: $(TEST_DIR)/test_pkt_normalizer.o $(PN_OBJS) $(LL_QUEUE_OBJS) $(UASYNC_OBJS)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^
@ -44,12 +46,21 @@ $(TEST_DIR)/test_connection_stress: $(TEST_DIR)/test_connection_stress.o connect
$(TEST_DIR)/test_new_features: $(TEST_DIR)/test_new_features.o $(ETCP_OBJS) $(PN_OBJS) $(LL_QUEUE_OBJS) $(UASYNC_OBJS) connection.o $(SC_LIB_OBJS) $(TINYCRYPT_OBJS)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^
utun: utun.o $(CONFIG_PARSER_OBJS) $(TUN_IF_OBJS) connection.o routing.o control_socket.o $(ETCP_OBJS) $(PN_OBJS) $(LL_QUEUE_OBJS) $(UASYNC_OBJS) $(SC_LIB_OBJS) $(TINYCRYPT_OBJS)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^
$(TEST_DIR)/test_sc_lib: $(TEST_DIR)/test_sc_lib.o $(SC_LIB_OBJS) $(TINYCRYPT_OBJS)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^
$(TEST_DIR)/test_udp_secure: $(TEST_DIR)/test_udp_secure.o $(SC_LIB_OBJS) $(UASYNC_OBJS) $(TINYCRYPT_OBJS)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^
$(TEST_DIR)/test_utun_integration: $(TEST_DIR)/test_utun_integration.o $(CONFIG_PARSER_OBJS) $(TUN_IF_OBJS) connection.o $(ETCP_OBJS) $(PN_OBJS) $(LL_QUEUE_OBJS) $(UASYNC_OBJS) $(SC_LIB_OBJS) $(TINYCRYPT_OBJS)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^
$(TEST_DIR)/test_utun_fork: $(TEST_DIR)/test_utun_fork.o $(SC_LIB_OBJS) $(TINYCRYPT_OBJS)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^
$(TEST_DIR)/test_pkt_normalizer: $(TEST_DIR)/test_pkt_normalizer.o $(PN_OBJS) $(LL_QUEUE_OBJS) $(UASYNC_OBJS)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^
@ -60,7 +71,7 @@ $(TEST_DIR)/test_etcp: $(TEST_DIR)/test_etcp.o $(ETCP_OBJS) $(LL_QUEUE_OBJS) $(U
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
clean:
rm -f $(TEST_DIR)/test_ecc_encrypt $(TEST_DIR)/test_sc_lib $(TEST_DIR)/test_udp_secure $(TEST_DIR)/test_pkt_normalizer $(TEST_DIR)/test_etcp $(TEST_DIR)/test_etcp_stress $(TEST_DIR)/test_etcp_simple $(TEST_DIR)/test_connection $(TEST_DIR)/test_connection_stress $(TEST_DIR)/test_new_features \
rm -f utun $(TEST_DIR)/test_ecc_encrypt $(TEST_DIR)/test_sc_lib $(TEST_DIR)/test_udp_secure $(TEST_DIR)/test_pkt_normalizer $(TEST_DIR)/test_etcp $(TEST_DIR)/test_etcp_stress $(TEST_DIR)/test_etcp_simple $(TEST_DIR)/test_connection $(TEST_DIR)/test_connection_stress $(TEST_DIR)/test_new_features $(TEST_DIR)/test_utun_integration $(TEST_DIR)/test_utun_fork \
*.o tinycrypt/lib/source/*.o $(TEST_DIR)/*.o
.PHONY: all clean

413
config_parser.c

@ -0,0 +1,413 @@
// config_parser.c - Configuration parser for utun application
#define _POSIX_C_SOURCE 200809L
#include "config_parser.h"
#include <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, &current_conn) != 0) {
fprintf(stderr, "Failed to add connection: %s\n", current_conn.name);
}
}
memset(&current_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, &current_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;
}

117
config_parser.h

@ -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 */

463
control_socket.c

@ -0,0 +1,463 @@
/**
* @file control_socket.c
* @brief Control socket implementation for statistics and diagnostics
*/
#include "control_socket.h"
#include "connection.h"
#include "etcp.h"
#include "pkt_normalizer.h"
#include "routing.h"
#include "tun_if.h"
#include "config_parser.h"
#include "utun_state.h"
#include <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
}
}

208
control_socket.h

@ -0,0 +1,208 @@
/**
* @file control_socket.h
* @brief Control socket for statistics and diagnostics over UDP
* @details Provides remote monitoring of utun VPN tunnel statistics
*/
#ifndef CONTROL_SOCKET_H
#define CONTROL_SOCKET_H
#include <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 */

29
example_control.conf

@ -0,0 +1,29 @@
# Example configuration with control socket enabled
[global]
my_private_key=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
my_public_key=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
control_ip=127.0.0.1 # Listen on localhost only
control_port=5555 # UDP port for monitoring
# TUN interface configuration
[tun]
ifname=tun12
ip_addr=10.0.0.1/24
mtu=1400
# Routing configuration
[routing]
allowed_subnet=10.0.0.0/8
allowed_subnet=172.16.0.0/12
allowed_subnet=192.168.0.0/16
# Connection configuration (example)
[[connection]]
name=node1
remote_ip=192.168.1.100
remote_port=12345
local_ip=0.0.0.0
local_port=0
mode=client
# Add more connections as needed

132
monitor.py

@ -0,0 +1,132 @@
#!/usr/bin/env python3
"""
utun control socket monitor client
Usage: python3 monitor.py [host] [port]
Default: localhost:5555
"""
import socket
import struct
import sys
import json
import time
# Command types from control_socket.h
CONTROL_CMD_GET_STATS = 1
CONTROL_CMD_RESET_STATS = 2
CONTROL_CMD_GET_STATUS = 3
# Response types
CONTROL_RESP_STATS = 1
CONTROL_RESP_STATUS = 2
CONTROL_RESP_ERROR = 3
# Error codes
CONTROL_ERR_NONE = 0
CONTROL_ERR_INVALID_CMD = 1
CONTROL_ERR_INTERNAL = 2
def send_command(host='127.0.0.1', port=5555, command=CONTROL_CMD_GET_STATS, sequence=1):
"""Send a control command and receive response"""
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(2.0)
# Build request packet (4 bytes: command + sequence)
# struct control_request_packet_t { uint16_t command; uint16_t sequence; }
request = struct.pack('<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()

33
net_emulator/Makefile

@ -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

502
net_emulator/net_emulator.c

@ -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, &current_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 = &current_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;
}

23
net_emulator/net_emulator.conf

@ -0,0 +1,23 @@
# Network emulator configuration for net_debug mode
# Format: [ip:port] (listening address)
# Rules: prob=percent,delay=min-max (in milliseconds)
# If sum of probabilities < 100%, remaining packets are dropped
# Endpoint 1: Forward packets from port 20000 to 10000 (port - 10000)
[127.0.0.1:20000]
prob=40,delay=100-130 (adds 100-130ms delay)
prob=30,delay=10-20 (adds 10-20ms delay)
# Total probability = 70%, remaining 30% packets are dropped
# Endpoint 2: Forward packets from port 20001 to 10001
[127.0.0.1:20001]
prob=10,delay=0-0 (no delay, 10% probability)
prob=80,delay=10-13 (adds 10-13ms delay, 80% probability)
# Total probability = 90%, remaining 10% packets are dropped
# Endpoint 3: Server response path (from port 10001 back to client)
# Note: In net_debug mode, server responses come to original client port
# which is also +10000. This endpoint handles responses from server to client.
[127.0.0.1:10001]
prob=80,delay=5-10
# Total probability = 80%, 20% dropped

12
net_emulator/net_emulator.conf.example

@ -0,0 +1,12 @@
[127.0.0.1:20000] (порт 20000 форвардится на 10000)
prob=40,delay=100-130 (добавляет 100-130ms задержки)
(в остальных случаях drop)
[127.0.0.1:20001]
prob=10,delay=0-0 (отправляет без задержки в 10% случаев)
prob=80,delay=10-13 (добавляет 10-13ms задержки 80% вероятность)
(в остальных случаях drop)
[127.0.0.1:20001] (здесь вываливается с ошибкой - ip:порт уже занят)
prob=50,delay=0-0 (отправляет без задержки в 50% случаев)
prob=80,delay=10-13 (здесь он должен вывалиться с ошибкой парсинга - prob суммарный >100)

90
net_emulator/net_emulator.h

@ -0,0 +1,90 @@
// net_emulator.h - Network emulator for testing utun with delay, loss, reordering
#ifndef NET_EMULATOR_H
#define NET_EMULATOR_H
#include <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 */

412
net_emulator/test_timeout_heap.c

@ -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;
}

258
net_emulator/test_uasync_random.c

@ -0,0 +1,258 @@
// test_uasync_random.c - Test uasync instance with random timeouts
#include "u_async.h"
#include <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;
}

134
net_emulator/timeout_heap.c

@ -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
}

72
net_emulator/timeout_heap.h

@ -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

391
net_emulator/u_async.c

@ -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);
}

45
net_emulator/u_async.h

@ -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

405
routing.c

@ -0,0 +1,405 @@
/**
* @file enhanced_routing.c
* @brief Enhanced routing system with bandwidth support and route types
*/
#include "routing.h"
#include "connection.h"
#include <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;
}

170
routing.h

@ -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

134
timeout_heap.c

@ -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
}

72
timeout_heap.h

@ -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

238
tun_if.c

@ -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;
}

95
tun_if.h

@ -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 */

159
u_async.c

@ -1,6 +1,7 @@
// uasync.c
#include "u_async.h"
#include "timeout_heap.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
@ -13,11 +14,9 @@
// Timeout node
struct timeout_node {
struct timeval expire_time;
void* arg;
timeout_callback_t callback;
struct timeout_node* next;
struct timeout_node* prev; // Doubly linked for easier removal
uint64_t expiration_ms; // absolute expiration time in milliseconds
};
// Socket node
@ -31,7 +30,7 @@ struct socket_node {
};
// Global state
static struct timeout_node* timeout_head = NULL; // Sorted by expire_time, earliest first
static TimeoutHeap* timeout_heap = NULL; // Heap for timeout management
static struct socket_node* socket_head = NULL;
static int max_fd = -1;
@ -48,14 +47,7 @@ static void get_current_time(struct timeval* tv) {
gettimeofday(tv, NULL);
}
// Helper to compare times: returns 1 if a > b, 0 if equal, -1 if a < b
static int timeval_cmp(const struct timeval* a, const struct timeval* b) {
if (a->tv_sec > b->tv_sec) return 1;
if (a->tv_sec < b->tv_sec) return -1;
if (a->tv_usec > b->tv_usec) return 1;
if (a->tv_usec < b->tv_usec) return -1;
return 0;
}
// Helper to add timeval: tv += dt (timebase units)
static void timeval_add_tb(struct timeval* tv, int dt) {
@ -64,66 +56,67 @@ static void timeval_add_tb(struct timeval* tv, int dt) {
tv->tv_usec %= 1000000;
}
// Insert timeout into sorted list
static struct timeout_node* insert_timeout(struct timeout_node* new_node) {
if (!timeout_head || timeval_cmp(&new_node->expire_time, &timeout_head->expire_time) < 0) {
new_node->next = timeout_head;
new_node->prev = NULL;
if (timeout_head) timeout_head->prev = new_node;
timeout_head = new_node;
return new_node;
}
struct timeout_node* cur = timeout_head;
while (cur->next && timeval_cmp(&new_node->expire_time, &cur->next->expire_time) >= 0) {
cur = cur->next;
}
new_node->next = cur->next;
new_node->prev = cur;
if (cur->next) cur->next->prev = new_node;
cur->next = new_node;
return new_node;
// Convert timeval to milliseconds (uint64_t)
static uint64_t timeval_to_ms(const struct timeval* tv) {
return (uint64_t)tv->tv_sec * 1000ULL + (uint64_t)tv->tv_usec / 1000ULL;
}
// Process expired timeouts
static void process_timeouts() {
struct timeval now;
get_current_time(&now);
while (timeout_head && timeval_cmp(&timeout_head->expire_time, &now) <= 0) {
struct timeout_node* node = timeout_head;
timeout_head = node->next;
if (timeout_head) timeout_head->prev = NULL;
if (node->callback) node->callback(node->arg);
// Process expired timeouts
static void process_timeouts() {
if (!timeout_heap) return;
struct timeval now_tv;
get_current_time(&now_tv);
uint64_t now_ms = timeval_to_ms(&now_tv);
while (1) {
TimeoutEntry entry;
if (timeout_heap_peek(timeout_heap, &entry) != 0) break;
if (entry.expiration > now_ms) break;
// Pop the expired timeout
timeout_heap_pop(timeout_heap, &entry);
struct timeout_node* node = (struct timeout_node*)entry.data;
if (node && node->callback) {
node->callback(node->arg);
}
free(node);
}
}
// Compute time to next timeout
static void get_next_timeout(struct timeval* tv) {
struct timeval now;
get_current_time(&now);
if (!timeout_head) {
if (!timeout_heap) {
tv->tv_sec = 0;
tv->tv_usec = 0; // No timeout, but select can handle NULL
tv->tv_usec = 0;
return;
}
*tv = timeout_head->expire_time;
if (timeval_cmp(tv, &now) <= 0) {
TimeoutEntry entry;
if (timeout_heap_peek(timeout_heap, &entry) != 0) {
tv->tv_sec = 0;
tv->tv_usec = 0;
return;
}
tv->tv_sec -= now.tv_sec;
tv->tv_usec -= now.tv_usec;
if (tv->tv_usec < 0) {
tv->tv_sec--;
tv->tv_usec += 1000000;
struct timeval now_tv;
get_current_time(&now_tv);
uint64_t now_ms = timeval_to_ms(&now_tv);
if (entry.expiration <= now_ms) {
tv->tv_sec = 0;
tv->tv_usec = 0;
return;
}
uint64_t delta_ms = entry.expiration - now_ms;
if (delta_ms > 86400000) { // Cap at 1 day to avoid overflow
delta_ms = 86400000;
}
tv->tv_sec = delta_ms / 1000;
tv->tv_usec = (delta_ms % 1000) * 1000;
}
void uasync_init(void) {
@ -131,38 +124,51 @@ void uasync_init(void) {
FD_ZERO(&master_writefds);
FD_ZERO(&master_exceptfds);
memset(fd_to_node, 0, sizeof(fd_to_node)); // Init map to NULL
if (!timeout_heap) {
timeout_heap = timeout_heap_create(16); // initial capacity 16
}
}
void* uasync_set_timeout(int timeout, void* arg, timeout_callback_t callback) {
if (timeout < 0 || !callback) return NULL;
void* uasync_set_timeout(int timeout_tb, void* arg, timeout_callback_t callback) {
if (timeout_tb < 0 || !callback) return NULL;
if (!timeout_heap) return NULL;
struct timeout_node* node = malloc(sizeof(struct timeout_node));
if (!node) return NULL;
get_current_time(&node->expire_time);
timeval_add_tb(&node->expire_time, timeout);
node->arg = arg;
node->callback = callback;
node->next = NULL;
node->prev = NULL;
return insert_timeout(node);
// Calculate expiration time in milliseconds
struct timeval now;
get_current_time(&now);
timeval_add_tb(&now, timeout_tb);
node->expiration_ms = timeval_to_ms(&now);
// Insert into heap
if (timeout_heap_push(timeout_heap, node->expiration_ms, node) != 0) {
free(node);
return NULL;
}
return node;
}
err_t uasync_cancel_timeout(void* t_id) {
if (!t_id) return ERR_FAIL;
if (!t_id || !timeout_heap) return ERR_FAIL;
struct timeout_node* node = (struct timeout_node*)t_id;
if (node->prev) {
node->prev->next = node->next;
} else {
timeout_head = node->next;
}
if (node->next) {
node->next->prev = node->prev;
// Try to cancel from heap
if (timeout_heap_cancel(timeout_heap, node->expiration_ms, node) == 0) {
free(node);
return ERR_OK;
}
// If not found in heap (maybe already expired and removed), still free
free(node);
return ERR_OK;
return ERR_FAIL;
}
void* uasync_add_socket(int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_data) {
@ -257,7 +263,7 @@ void uasync_poll(int timeout_tb) {
user_tv.tv_usec = (timeout_tb % 10000) * 100;
/* If no internal timer or user timeout is smaller */
if (tv.tv_sec == 0 && tv.tv_usec == 0 && !timeout_head) {
if (tv.tv_sec == 0 && tv.tv_usec == 0 && (!timeout_heap || timeout_heap->size == 0)) {
tv = user_tv;
} else if (user_tv.tv_sec < tv.tv_sec ||
(user_tv.tv_sec == tv.tv_sec && user_tv.tv_usec < tv.tv_usec)) {
@ -265,7 +271,7 @@ void uasync_poll(int timeout_tb) {
}
}
struct timeval* ptv = (tv.tv_sec == 0 && tv.tv_usec == 0 && !timeout_head) ? NULL : &tv;
struct timeval* ptv = (tv.tv_sec == 0 && tv.tv_usec == 0 && (!timeout_heap || timeout_heap->size == 0)) ? NULL : &tv;
int nfds = select(max_fd + 1, &readfds, &writefds, &exceptfds, ptv);
if (nfds < 0) {
@ -274,6 +280,9 @@ void uasync_poll(int timeout_tb) {
return;
}
/* Process timeouts that may have expired during select */
process_timeouts();
/* Process sockets with faster dispatch */
for (int fd = 0; nfds > 0 && fd <= max_fd; fd++) {
struct socket_node* node = fd_to_node[fd];

735
utun.c

@ -0,0 +1,735 @@
// utun.c - Main application for utun VPN tunnel
#define _DEFAULT_SOURCE
#define _POSIX_C_SOURCE 200809L
#include "config_parser.h"
#include "connection.h"
#include "tun_if.h"
#include "sc_lib.h"
#include "routing.h"
#include "control_socket.h"
#include "utun_state.h"
#include <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;
}

31
utun.conf.example

@ -0,0 +1,31 @@
# utun configuration file example
# Global settings
[global]
# Generate keys using: openssl ecparam -name prime256v1 -genkey -noout | openssl ec -text -noout
# Private key (32 bytes in hex)
my_private_key=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
# Public key (64 bytes in hex)
my_public_key=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
# Global option (optional)
option=value
# Server connection (listening for incoming connections)
[connection: server1]
mode=server
addr=0.0.0.0:51820 # Listen on all interfaces, port 51820
peer_public_key=00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
so_mark=0 # Socket mark (0 = disabled)
netif=eth0 # Bind to specific network interface
tun=tun0 # TUN interface name
tun_ip=10.0.0.1/24 # TUN interface IP address
# Client connection (initiate connection to server)
[connection: client1]
mode=client
from_addr=0.0.0.0:51821 # Local address to bind (0.0.0.0 = any)
to_addr=192.168.1.100:51820 # Remote server address
peer_public_key=00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
so_mark=0
netif=eth0
tun=tun1
tun_ip=10.0.0.2/24

29
utun_state.h

@ -0,0 +1,29 @@
/**
* @file utun_state.h
* @brief Main application state structure
* @details Contains all module instances and configuration
*/
#ifndef UTUN_STATE_H
#define UTUN_STATE_H
#include <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 */

21
utun_test.cfg

@ -0,0 +1,21 @@
[global]
option=value1,value2
my_private_key=1F1C8A.... (HEX)
my_public_key=1F1C8A.... (HEX)
tun_ip=1.2.3.4
# remote connections
[connection: srv_conn1]
mode=server
addr=127.0.0.1:1234
peer_public_key=1F1C8A.... (HEX)
[connection: conn2_blablabla] (это имя подключения и опции надо запомнить в структуре подключения)
mode=client
from_addr=127.0.0.1:1234
to_addr=127.0.0.1:1234
so_mark=100 (надо установить для этого подключения)
netif=lo0 (биндим к конкретному интерфейсу если это можно)
peer_public_key=1F1C8A.... (HEX)
(подключений может быть много, надо все распарсить и открыть. имя должно загружаться в структуру connection)
Loading…
Cancel
Save