Reorganize project structure: move sources to src/, update Makefile, add header symlinks

This commit is contained in:
jek committed 2026-01-15 17:13:15 +03:00
1 parent 9eab53179d
commit f2c10dcbf9
45 files changed
+1612 -1825

No files matched your search

+413
View File
@@ -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
View File
@@ -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 */
+743
View File
@@ -0,0 +1,743 @@
// connection.c - Реализация минималистичного API для защищенных UDP подключений
#include "connection.h"
#include "sc_lib.h"
#include "pkt_normalizer.h"
#include "etcp.h"
#include "u_async.h"
#include "ll_queue.h"
#include "settings.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
/* Сетевые заголовки */
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
/* Внутренняя структура подключения */
struct conn_handle {
/* Сетевая часть */
int sockfd;
struct sockaddr_in local_addr;
struct sockaddr_in remote_addr;
conn_mode_t mode;
uint8_t remote_defined; /* 1 если удаленный адрес определен */
uint8_t socket_connected; /* 1 если сокет создан и настроен */
/* Криптография */
sc_context_t crypto_ctx;
uint8_t keys_set; /* 1 если ключи установлены */
uint8_t crypto_ready; /* 1 если сессионный ключ вычислен */
/* Обработка данных */
epkt_t* etcp;
pkt_normalizer_pair* normalizer;
ll_queue_t* app_input_queue; /* Данные от приложения для отправки */
ll_queue_t* app_output_queue; /* Собранные данные для приложения */
/* Callback'и */
conn_recv_callback_t recv_callback;
void* recv_callback_user;
/* Состояние */
uint8_t is_closing;
uint8_t is_destroying;
conn_stats_t stats; /* Статистика подключения */
/* Таймеры */
void* socket_read_timer;
/* Async instance */
uasync_t* ua;
};
/* Внутренние функции */
static int create_udp_socket(const char* ip, uint16_t port, struct sockaddr_in* addr);
static void socket_read_callback(void* arg);
static void etcp_tx_callback(epkt_t* epkt, uint8_t* data, uint16_t len, void* arg);
static void app_output_callback(ll_queue_t* q, ll_entry_t* entry, void* arg);
static void process_received_packet(conn_handle_t* conn, uint8_t* data, size_t len);
static void update_stats_from_etcp(conn_handle_t* conn);
/* Bridge functions for data flow */
static void app_input_bridge(ll_queue_t* q, ll_entry_t* entry, void* arg);
static void packer_output_bridge(ll_queue_t* q, ll_entry_t* entry, void* arg);
static void etcp_output_bridge(ll_queue_t* q, ll_entry_t* entry, void* arg);
/* ==================== Публичные функции ==================== */
conn_handle_t* conn_create(uasync_t* ua)
{
/* Выделение памяти */
conn_handle_t* conn = calloc(1, sizeof(conn_handle_t));
if (!conn) {
return NULL;
}
/* Установка инстанса uasync */
conn->ua = ua;
/* Инициализация очередей приложения */
conn->app_input_queue = queue_new(ua);
conn->app_output_queue = queue_new(ua);
if (!conn->app_input_queue || !conn->app_output_queue) {
if (conn->app_input_queue) queue_free(conn->app_input_queue);
if (conn->app_output_queue) queue_free(conn->app_output_queue);
free(conn);
return NULL;
}
/* Настройка callback для выходной очереди */
queue_set_callback(conn->app_output_queue, app_output_callback, conn);
/* Инициализация статистики */
memset(&conn->stats, 0, sizeof(conn->stats));
/* Начальное состояние */
conn->sockfd = -1;
conn->mode = CONN_MODE_CLIENT; /* по умолчанию клиент */
conn->is_closing = 0;
conn->is_destroying = 0;
return conn;
}
int conn_set_keys(conn_handle_t* conn,
const uint8_t* my_pub_key,
const uint8_t* my_priv_key,
const uint8_t* peer_pub_key)
{
if (!conn || conn->socket_connected) {
return -1; /* Ключи нужно устанавливать до подключения */
}
sc_status_t status;
if (my_pub_key && my_priv_key) {
/* Использование предоставленных ключей */
status = sc_init_local_keys(&conn->crypto_ctx, my_pub_key, my_priv_key);
if (status != SC_OK) {
return -1;
}
} else {
/* Автоматическая генерация ключей */
status = sc_generate_keypair(&conn->crypto_ctx);
if (status != SC_OK) {
return -1;
}
}
/* Установка ключа пира, если предоставлен */
if (peer_pub_key) {
status = sc_set_peer_public_key(&conn->crypto_ctx, peer_pub_key);
if (status != SC_OK) {
return -1;
}
conn->crypto_ready = 1;
} else {
/* Для сервера ключ пира будет получен из первого пакета */
conn->crypto_ready = 0;
}
conn->keys_set = 1;
return 0;
}
int conn_connect(conn_handle_t* conn,
const char* local_ip,
uint16_t local_port,
const char* remote_ip,
uint16_t remote_port,
conn_mode_t mode)
{
if (!conn || conn->socket_connected) {
return -1; /* Уже подключен */
}
/* Проверка ключей */
if (!conn->keys_set) {
/* Автоматическая установка ключей по умолчанию */
if (conn_set_keys(conn, NULL, NULL, NULL) != 0) {
return -1;
}
}
/* Создание UDP сокета */
conn->sockfd = create_udp_socket(local_ip, local_port, &conn->local_addr);
if (conn->sockfd < 0) {
return -1;
}
/* Сохранение режима */
conn->mode = mode;
/* Настройка удаленного адреса для клиента */
if (mode == CONN_MODE_CLIENT && remote_ip) {
memset(&conn->remote_addr, 0, sizeof(conn->remote_addr));
conn->remote_addr.sin_family = AF_INET;
conn->remote_addr.sin_port = htons(remote_port);
if (inet_pton(AF_INET, remote_ip, &conn->remote_addr.sin_addr) != 1) {
close(conn->sockfd);
conn->sockfd = -1;
return -1;
}
conn->remote_defined = 1;
} else {
/* Сервер или клиент без указанного адреса */
conn->remote_defined = 0;
}
/* Инициализация нормализатора пакетов */
conn->normalizer = pkt_normalizer_pair_init(conn->ua);
if (!conn->normalizer) {
close(conn->sockfd);
conn->sockfd = -1;
return -1;
}
/* Инициализация ETCP */
conn->etcp = etcp_init(conn->ua);
if (!conn->etcp) {
pkt_normalizer_pair_deinit(conn->normalizer);
close(conn->sockfd);
conn->sockfd = -1;
return -1;
}
/* Настройка callback'ов ETCP */
etcp_set_callback(conn->etcp, etcp_tx_callback, conn);
/* Начальная полоса пропускания: максимальная для uint16_t ≈ 6553 байт/мс */
etcp_set_bandwidth(conn->etcp, 65535); /* в единицах 0.1мс: 6553.5 * 10 */
/* Связывание нормализатора с ETCP */
/* Packer: app_input_queue -> normalizer -> etcp_tx_put */
/* Unpacker: etcp_output_queue -> normalizer -> app_output_queue */
/* Set up bridge callbacks */
/* 1. App input -> packer input */
queue_set_callback(conn->app_input_queue, app_input_bridge, conn);
/* 2. Packer output -> ETCP tx */
if (conn->normalizer->packer && conn->normalizer->packer->output) {
queue_set_callback(conn->normalizer->packer->output, packer_output_bridge, conn);
}
/* 3. ETCP output -> unpacker input */
ll_queue_t* etcp_output_q = etcp_get_output_queue(conn->etcp);
if (etcp_output_q) {
queue_set_callback(etcp_output_q, etcp_output_bridge, conn);
}
/* 4. Unpacker output -> app output queue */
if (conn->normalizer->unpacker && conn->normalizer->unpacker->output) {
queue_set_callback(conn->normalizer->unpacker->output, app_output_callback, conn);
}
/* Запуск таймера чтения сокета */
conn->socket_read_timer = uasync_set_timeout(conn->ua, 1, conn, socket_read_callback);
conn->socket_connected = 1;
return 0;
}
void conn_set_recv_callback(conn_handle_t* conn,
conn_recv_callback_t callback,
void* user_data)
{
if (!conn) return;
conn->recv_callback = callback;
conn->recv_callback_user = user_data;
}
int conn_send(conn_handle_t* conn, const uint8_t* data, size_t len)
{
if (!conn || !data || len == 0 || !conn->socket_connected || conn->is_closing) {
return -1;
}
/* Проверка готовности криптографии (для клиента) */
/* TODO: implement proper key exchange */
/* if (conn->mode == CONN_MODE_CLIENT && !conn->crypto_ready) {
return -1;
} */
/* Создание записи очереди */
ll_entry_t* entry = queue_entry_new(len);
if (!entry) {
return -1;
}
/* Копирование данных */
memcpy(ll_entry_data(entry), data, len);
/* Добавление в очередь ввода приложения */
int result = queue_entry_put(conn->app_input_queue, entry);
if (result != 0) {
queue_entry_free(entry);
return -1;
}
/* Обновление статистики */
conn->stats.bytes_sent += len;
/* Активация обработки (через callback очереди) */
queue_resume_callback(conn->app_input_queue);
return 0;
}
void conn_close(conn_handle_t* conn)
{
if (!conn || conn->is_closing) {
return;
}
conn->is_closing = 1;
/* Отмена таймеров */
if (conn->socket_read_timer) {
uasync_cancel_timeout(conn->ua, conn->socket_read_timer);
conn->socket_read_timer = NULL;
}
/* Закрытие сокета */
if (conn->sockfd >= 0) {
close(conn->sockfd);
conn->sockfd = -1;
}
conn->socket_connected = 0;
}
void conn_reset(conn_handle_t* conn)
{
if (!conn || conn->is_closing) {
return;
}
ETCP_LOG("Connection reset initiated\n");
/* Send reset service packet via etcp */
if (conn->etcp) {
etcp_reset_connection(conn->etcp);
}
/* Reset pkt_normalizer state */
if (conn->normalizer) {
pkt_normalizer_reset_state(conn->normalizer->packer);
pkt_normalizer_reset_state(conn->normalizer->unpacker);
}
/* Note: Does not close socket or stop communication */
/* Reset crypto session? Probably not needed for testing */
}
void conn_destroy(conn_handle_t* conn)
{
if (!conn) return;
conn->is_destroying = 1;
/* Закрытие подключения если активно */
conn_close(conn);
/* Освобождение ETCP */
if (conn->etcp) {
etcp_free(conn->etcp);
conn->etcp = NULL;
}
/* Освобождение нормализатора */
if (conn->normalizer) {
pkt_normalizer_pair_deinit(conn->normalizer);
conn->normalizer = NULL;
}
/* Освобождение очередей */
if (conn->app_input_queue) {
queue_free(conn->app_input_queue);
conn->app_input_queue = NULL;
}
if (conn->app_output_queue) {
queue_free(conn->app_output_queue);
conn->app_output_queue = NULL;
}
/* Очистка криптографического контекста */
memset(&conn->crypto_ctx, 0, sizeof(conn->crypto_ctx));
/* Освобождение дескриптора */
free(conn);
}
int conn_get_stats(conn_handle_t* conn, conn_stats_t* stats)
{
if (!conn || !stats) {
return -1;
}
/* Обновление статистики из ETCP */
update_stats_from_etcp(conn);
/* Копирование статистики */
memcpy(stats, &conn->stats, sizeof(conn_stats_t));
return 0;
}
/* ==================== Внутренние функции ==================== */
/* Bridge functions implementation */
static void app_input_bridge(ll_queue_t* q, ll_entry_t* entry, void* arg)
{
(void)entry; /* unused parameter - we'll get entry from queue */
conn_handle_t* conn = (conn_handle_t*)arg;
if (!conn || !q || !conn->normalizer || !conn->normalizer->packer) {
return;
}
/* Remove entry from app_input_queue */
ll_entry_t* entry_to_forward = queue_entry_get(q);
if (!entry_to_forward) {
queue_resume_callback(q);
return;
}
size_t len = ll_entry_size(entry_to_forward);
printf("[CONN DEBUG] app_input_bridge: forwarding packet len=%zu to packer\n", len);
/* Forward entry from app_input_queue to packer's input queue */
queue_entry_put(conn->normalizer->packer->input, entry_to_forward);
queue_resume_callback(q);
}
static void packer_output_bridge(ll_queue_t* q, ll_entry_t* entry, void* arg)
{
(void)entry; /* unused parameter - we'll get entry from queue */
printf("[CONN DEBUG] packer_output_bridge ENTER, queue count=%d\n", queue_entry_count(q));
conn_handle_t* conn = (conn_handle_t*)arg;
if (!conn || !q || !conn->etcp) {
queue_resume_callback(q);
return;
}
/* Remove entry from queue */
ll_entry_t* entry_to_process = queue_entry_get(q);
if (!entry_to_process) {
queue_resume_callback(q);
return;
}
/* Extract data from entry */
uint8_t* data = ll_entry_data(entry_to_process);
size_t len = ll_entry_size(entry_to_process);
printf("[CONN DEBUG] packer_output_bridge: processing packet len=%zu\n", len);
if (len > 65535) {
/* Too large for ETCP (max 64KB) */
queue_entry_free(entry_to_process);
queue_resume_callback(q);
return;
}
uint8_t* data_to_send = data;
uint16_t data_len = (uint16_t)len;
/* Encrypt if crypto is ready - TEMPORARILY DISABLED */
if (conn->crypto_ready) {
/* Allocate buffer for ciphertext + tag */
uint8_t* ciphertext = malloc(len);
uint8_t tag[SC_TAG_SIZE];
sc_status_t status = sc_encrypt(&conn->crypto_ctx, data, len, ciphertext, tag);
if (status == SC_OK) {
/* Combine ciphertext and tag into single buffer */
uint8_t* encrypted_data = malloc(len + SC_TAG_SIZE);
if (encrypted_data) {
memcpy(encrypted_data, ciphertext, len);
memcpy(encrypted_data + len, tag, SC_TAG_SIZE);
data_to_send = encrypted_data;
data_len = (uint16_t)(len + SC_TAG_SIZE);
}
/* Free ciphertext buffer */
free(ciphertext);
}
/* If encryption failed, fall back to plaintext */
}
/* Note: etcp_tx_put copies data, we can free entry after call */
int result = etcp_tx_put(conn->etcp, data_to_send, data_len);
/* Free encrypted buffer if it was allocated */
if (conn->crypto_ready && data_to_send != data) {
free(data_to_send);
}
queue_entry_free(entry_to_process);
(void)result; /* Ignore result for now */
queue_resume_callback(q);
}
static void etcp_output_bridge(ll_queue_t* q, ll_entry_t* entry, void* arg)
{
(void)entry; /* unused parameter - we'll get entry from queue */
conn_handle_t* conn = (conn_handle_t*)arg;
if (!conn || !q || !conn->normalizer || !conn->normalizer->unpacker) {
return;
}
/* Remove entry from ETCP output queue */
ll_entry_t* entry_to_forward = queue_entry_get(q);
if (!entry_to_forward) {
queue_resume_callback(q);
return;
}
/* Extract data from entry */
uint8_t* data = ll_entry_data(entry_to_forward);
size_t len = ll_entry_size(entry_to_forward);
ll_entry_t* new_entry = NULL;
/* Decrypt if crypto is ready - TEMPORARILY DISABLED */
if (conn->crypto_ready) {
size_t ciphertext_len = len - SC_TAG_SIZE;
uint8_t* ciphertext = data;
uint8_t* tag = data + ciphertext_len;
/* Allocate buffer for plaintext */
uint8_t* plaintext = malloc(ciphertext_len);
if (plaintext) {
sc_status_t status = sc_decrypt(&conn->crypto_ctx, ciphertext, ciphertext_len, tag, plaintext);
if (status == SC_OK) {
/* Create new entry with plaintext */
new_entry = queue_entry_new(ciphertext_len);
if (new_entry) {
memcpy(ll_entry_data(new_entry), plaintext, ciphertext_len);
}
}
free(plaintext);
}
}
/* If we created a new entry, replace the old one */
if (new_entry) {
queue_entry_free(entry_to_forward);
entry_to_forward = new_entry;
}
/* Forward entry to unpacker's input queue */
queue_entry_put(conn->normalizer->unpacker->input, entry_to_forward);
queue_resume_callback(q);
}
static int create_udp_socket(const char* ip, uint16_t port, struct sockaddr_in* addr)
{
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
return -1;
}
/* Установка non-blocking режима */
int flags = fcntl(sock, F_GETFL, 0);
if (flags < 0) {
close(sock);
return -1;
}
if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
close(sock);
return -1;
}
/* Настройка адреса */
memset(addr, 0, sizeof(*addr));
addr->sin_family = AF_INET;
addr->sin_port = htons(port);
if (ip && ip[0] != '\0') {
if (inet_pton(AF_INET, ip, &addr->sin_addr) != 1) {
close(sock);
return -1;
}
} else {
addr->sin_addr.s_addr = INADDR_ANY;
}
/* Bind к адресу */
if (bind(sock, (struct sockaddr*)addr, sizeof(*addr)) < 0) {
close(sock);
return -1;
}
return sock;
}
static void socket_read_callback(void* arg)
{
conn_handle_t* conn = (conn_handle_t*)arg;
if (!conn || conn->is_closing || conn->sockfd < 0) {
return;
}
uint8_t buffer[2048]; /* Максимальный размер UDP пакета */
struct sockaddr_in from_addr;
socklen_t from_len = sizeof(from_addr);
/* Чтение всех доступных пакетов */
while (1) {
ssize_t received = recvfrom(conn->sockfd, buffer, sizeof(buffer), 0,
(struct sockaddr*)&from_addr, &from_len);
if (received <= 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
/* Нет больше данных */
break;
}
/* Ошибка чтения */
break;
}
/* Обновление статистики */
conn->stats.bytes_received += received;
conn->stats.packets_received++;
printf("[CONN DEBUG] socket_read_callback: received packet len=%zd, total_received=%u\n", received, conn->stats.packets_received);
/* Для сервера: установка удаленного адреса при первом пакете */
if (!conn->remote_defined && conn->mode == CONN_MODE_SERVER) {
memcpy(&conn->remote_addr, &from_addr, sizeof(conn->remote_addr));
conn->remote_defined = 1;
}
/* Обработка полученного пакета */
process_received_packet(conn, buffer, received);
}
/* Перезапуск таймера чтения */
if (!conn->is_closing) {
conn->socket_read_timer = uasync_set_timeout(conn->ua, 1, conn, socket_read_callback);
}
}
static void etcp_tx_callback(epkt_t* epkt, uint8_t* data, uint16_t len, void* arg)
{
(void)epkt; /* unused parameter */
conn_handle_t* conn = (conn_handle_t*)arg;
if (!conn || conn->is_closing || conn->sockfd < 0) {
free(data); /* Данные были выделены в etcp.c */
return;
}
/* Шифрование данных если криптография готова */
uint8_t* packet_to_send = data;
uint16_t packet_len = len;
if (conn->crypto_ready) {
/* TODO: реализовать шифрование через sc_encrypt */
/* Временно пропускаем шифрование для тестирования */
}
/* Отправка через UDP сокет */
if (conn->remote_defined) {
printf("[CONN DEBUG] etcp_tx_callback: sending packet len=%u, stats.packets_sent=%u\n", packet_len, conn->stats.packets_sent);
ssize_t sent = sendto(conn->sockfd, packet_to_send, packet_len, 0,
(struct sockaddr*)&conn->remote_addr,
sizeof(conn->remote_addr));
if (sent == (ssize_t)packet_len) {
conn->stats.packets_sent++;
printf("[CONN DEBUG] etcp_tx_callback: sent successfully, new count=%u\n", conn->stats.packets_sent);
} else {
printf("[CONN DEBUG] etcp_tx_callback: send failed, sent=%zd, errno=%d\n", sent, errno);
}
}
/* Освобождение данных (выделены в etcp.c) - packet is stored in ETCP sent_list for retransmission */
/* DO NOT free here */
(void)packet_to_send; /* unused */
}
static void app_output_callback(ll_queue_t* q, ll_entry_t* entry, void* arg)
{
(void)entry; /* unused parameter - we'll get entry from queue */
conn_handle_t* conn = (conn_handle_t*)arg;
if (!conn || !q || conn->is_closing) {
return;
}
/* Remove entry from unpacker output queue */
ll_entry_t* entry_to_process = queue_entry_get(q);
if (!entry_to_process) {
queue_resume_callback(q);
return;
}
/* Получение данных из записи */
uint8_t* data = ll_entry_data(entry_to_process);
size_t len = ll_entry_size(entry_to_process);
/* Вызов пользовательского callback'а если установлен */
if (conn->recv_callback) {
conn->recv_callback(conn, data, len, conn->recv_callback_user);
}
/* Освобождение записи */
queue_entry_free(entry_to_process);
/* Обновление статистики */
conn->stats.fragments_assembled++;
queue_resume_callback(q);
}
static void process_received_packet(conn_handle_t* conn, uint8_t* data, size_t len)
{
if (!conn || !data || len == 0) {
return;
}
/* Расшифрование если криптография готова */
uint8_t* packet_to_process = data;
uint16_t packet_len = len;
if (conn->crypto_ready) {
/* TODO: реализовать расшифрование через sc_decrypt */
/* Временно пропускаем для тестирования */
} else {
/* Криптография не готова, но ключи должны быть установлены заранее */
printf("[CONN] WARNING: Received packet but crypto not ready (keys should be pre-shared)\n");
/* Все равно передаем пакет в ETCP для обработки */
/* В реальном использовании это ошибка конфигурации */
}
/* Передача пакета в ETCP для обработки */
printf("[CONN DEBUG] process_received_packet: forwarding to ETCP, len=%u\n", packet_len);
etcp_rx_input(conn->etcp, packet_to_process, packet_len);
}
static void update_stats_from_etcp(conn_handle_t* conn)
{
if (!conn || !conn->etcp) {
return;
}
/* Получение метрик из ETCP */
conn->stats.current_rtt_ms = etcp_get_rtt(conn->etcp) / 10; /* 0.1ms -> ms */
conn->stats.jitter_ms = etcp_get_jitter(conn->etcp) / 10;
/* TODO: получение счетчика ретрансмиссий из ETCP */
}
+129
View File
@@ -0,0 +1,129 @@
// connection.h - Минималистичный API для защищенных UDP подключений
#ifndef CONNECTION_H
#define CONNECTION_H
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Непрозрачный дескриптор подключения */
typedef struct conn_handle conn_handle_t;
/* Forward declaration для uasync */
typedef struct uasync_s uasync_t;
/* Режим подключения */
typedef enum {
CONN_MODE_CLIENT, /* Инициируем подключение к указанному удаленному адресу */
CONN_MODE_SERVER /* Ожидаем входящие подключения */
} conn_mode_t;
/* Callback для входящих данных */
typedef void (*conn_recv_callback_t)(conn_handle_t* conn,
const uint8_t* data,
size_t len,
void* user_data);
/*
* Создание дескриптора подключения (только выделение памяти).
* ua - экземпляр uasync для таймеров (обязательный параметр)
* Возвращает NULL при ошибке.
*/
conn_handle_t* conn_create(uasync_t* ua);
/*
* Установка криптографических ключей.
* Должна быть вызвана до conn_connect().
*
* @param conn Дескриптор подключения
* @param my_pub_key Публичный ключ (64 байта для secp256r1), NULL для авто-генерации
* @param my_priv_key Приватный ключ (32 байта), NULL для авто-генерации
* @param peer_pub_key Публичный ключ пира (64 байта), NULL для сервера (получит из первого пакета)
*
* @return 0 при успехе, -1 при ошибке
*/
int conn_set_keys(conn_handle_t* conn,
const uint8_t* my_pub_key,
const uint8_t* my_priv_key,
const uint8_t* peer_pub_key);
/*
* Подключение к удаленному узлу или начало ожидания входящих подключений.
*
* @param conn Дескриптор подключения
* @param local_ip Локальный IP для bind (NULL для "0.0.0.0")
* @param local_port Локальный порт (0 для авто-выбора)
* @param remote_ip Удаленный IP (NULL для серверного режима)
* @param remote_port Удаленный порт (игнорируется если remote_ip NULL)
* @param mode Режим подключения (CONN_MODE_CLIENT/SERVER)
*
* @return 0 при успехе, -1 при ошибке
*/
int conn_connect(conn_handle_t* conn,
const char* local_ip,
uint16_t local_port,
const char* remote_ip,
uint16_t remote_port,
conn_mode_t mode);
/*
* Установка callback'а для входящих данных.
* Callback будет вызываться при получении полных собранных пакетов.
*/
void conn_set_recv_callback(conn_handle_t* conn,
conn_recv_callback_t callback,
void* user_data);
/*
* Отправка данных.
* Данные будут автоматически фрагментированы, зашифрованы и отправлены.
*
* @return 0 при успехе, -1 при ошибке
*/
int conn_send(conn_handle_t* conn, const uint8_t* data, size_t len);
/*
* Закрытие подключения (немедленное, без протокола завершения).
* После вызова дескриптор можно уничтожить через conn_destroy().
*/
void conn_close(conn_handle_t* conn);
/*
* Сброс соединения с использованием служебных пакетов.
* Отправляет пакет сброса через ETCP и сбрасывает состояние всех компонентов.
* Предназначено для тестирования и восстановления после сбоев.
*/
void conn_reset(conn_handle_t* conn);
/*
* Полное уничтожение дескриптора подключения и освобождение всех ресурсов.
* Автоматически вызывает conn_close() если подключение активно.
*/
void conn_destroy(conn_handle_t* conn);
/*
* Получение статистики подключения (опционально).
*
* @return 0 при успехе, -1 при ошибке
*/
typedef struct {
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; /* Текущее RTT в миллисекундах */
uint16_t jitter_ms; /* Джиттер в миллисекундах */
} conn_stats_t;
int conn_get_stats(conn_handle_t* conn, conn_stats_t* stats);
#ifdef __cplusplus
}
#endif
#endif /* CONNECTION_H */
+463
View File
@@ -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
View File
@@ -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 */
+1245
View File
File diff suppressed because it is too large. Load diff
+234
View File
@@ -0,0 +1,234 @@
// etcp.h - Расширенный протокол управления передачей (Extended Transmission Control Protocol)
#ifndef ETCP_H
#define ETCP_H
#include <stdint.h>
#include <stddef.h>
#include "ll_queue.h"
#include "u_async.h"
// Отладочное логирование
#ifdef ETCP_DEBUG
#include <stdio.h>
#define ETCP_LOG(fmt, ...) printf("[ETCP] " fmt, ##__VA_ARGS__)
#ifdef ETCP_DEBUG_EXT
#define ETCP_DEBUG_LOG(fmt, ...) printf("[ETCP_DEBUG] " fmt, ##__VA_ARGS__)
#else
#define ETCP_DEBUG_LOG(fmt, ...) ((void)0)
#endif
#else
#define ETCP_LOG(fmt, ...) ((void)0)
#define ETCP_DEBUG_LOG(fmt, ...) ((void)0)
#endif
#ifdef __cplusplus
extern "C" {
#endif
// Предварительные объявления
typedef struct epkt epkt_t;
// Тип обратного вызова для отправки пакетов через UDP
typedef void (*etcp_tx_callback_t)(epkt_t* epkt, uint8_t* pkt, uint16_t len, void* arg);
// Основная структура ETCP
struct epkt {
// Очереди
ll_queue_t* tx_queue; // Очередь данных для отправки
ll_queue_t* output_queue; // Выходная очередь (собранные данные)
// Список полученных пакетов (отсортированный связанный список)
struct rx_packet* rx_list;
// Отправленные пакеты (для повторной передачи)
struct sent_packet* sent_list;
// Метрики
uint16_t rtt_last; // Последнее RTT (в единицах времени 0.1 мкс)
uint16_t rtt_avg_10; // Среднее RTT за последние 10 пакетов
uint16_t rtt_avg_100; // Среднее RTT за последние 100 пакетов
uint16_t jitter; // Джиттер (усредненный)
uint16_t bandwidth; // Текущая пропускная способность (байты за единицу времени)
uint32_t bytes_sent_total; // Общее количество отправленных байт
uint16_t last_sent_timestamp; // Временная метка последнего отправленного пакета
uint32_t bytes_allowed; // Рассчитанное количество разрешенных к отправке байт
// Статистика
uint32_t retransmissions_count; // Количество ретрансмиссий
uint32_t ack_packets_count; // Количество отправленных пакетов подтверждения
uint32_t control_packets_count; // Количество отправленных управляющих пакетов (ACK + запросы ретрансмиссии)
uint32_t total_packets_sent; // Общее количество отправленных пакетов (включая ретрансмиссии)
uint32_t unique_packets_sent; // Количество уникальных отправленных пакетов (без ретрансмиссий)
uint32_t bytes_received_total; // Общее количество полученных байт
// Состояние
uint16_t next_tx_id; // Следующий ID для передачи
uint16_t last_sent_id; // Последний отправленный ID (для ретрансмиссии самого нового пакета)
uint16_t last_rx_id; // Последний полученный ID (для подтверждения)
uint16_t last_delivered_id; // Последний ID, переданный в output_queue
// Таймеры
void* next_tx_timer; // Таймер для следующей передачи
void* retransmit_timer; // Таймер для повторных передач
uasync_t* ua; // Экземпляр uasync для таймеров
// Обратный вызов
etcp_tx_callback_t tx_callback;
void* tx_callback_arg;
// История RTT для усреднения
uint16_t rtt_history[100];
uint8_t rtt_history_idx;
uint8_t rtt_history_count;
// Ожидающие подтверждения
uint16_t pending_ack_ids[32];
uint16_t pending_ack_timestamps[32];
uint8_t pending_ack_count;
// Ожидающие запросы на повторную передачу
uint16_t pending_retransmit_ids[32];
uint8_t pending_retransmit_count;
// Управление окном
uint32_t unacked_bytes; // Количество байт, отправленных но еще не подтвержденных
uint32_t window_size; // Текущий размер окна в байтах (рассчитывается)
uint16_t last_acked_id; // Последний подтвержденный ID пакета
uint16_t last_rx_ack_id; // Последний полученный ID подтверждения от получателя
uint16_t retrans_timer_period; // Текущий период таймера повторной передачи (в единицах времени)
uint16_t next_retrans_time; // Время следующей проверки повторной передачи
uint8_t window_blocked; // Флаг: передача заблокирована из-за ограничения окна
// Forward progress tracking
uint16_t oldest_missing_id; // Oldest missing packet ID
uint16_t missing_since_time; // Time when oldest missing packet was first detected
// Reset state
uint8_t reset_pending; // Reset packet sent, waiting for ACK
uint8_t reset_ack_received; // Reset ACK received
void* reset_timer; // Timer for reset retransmission
uint16_t reset_retry_count; // Number of reset retries
};
// Функции API
/**
* @brief Инициализировать новый экземпляр ETCP
* @param ua Экземпляр uasync для таймеров (обязательный параметр)
* @return Указатель на новый экземпляр или NULL в случае ошибки
*/
epkt_t* etcp_init(uasync_t* ua);
/**
* @brief Освободить экземпляр ETCP и все связанные ресурсы
* @param epkt Экземпляр для освобождения
*/
void etcp_free(epkt_t* epkt);
/**
* @brief Установить обратный вызов для отправки пакетов через UDP
* @param epkt Экземпляр ETCP
* @param cb Функция обратного вызова
* @param arg Пользовательский аргумент, передаваемый в обратный вызов
*/
void etcp_set_callback(epkt_t* epkt, etcp_tx_callback_t cb, void* arg);
/**
* @brief Обработать полученный UDP пакет
* @param epkt Экземпляр ETCP
* @param pkt Данные пакета
* @param len Длина пакета
* @return 0 при успехе, -1 при ошибке
*/
int etcp_rx_input(epkt_t* epkt, uint8_t* pkt, uint16_t len);
/**
* @brief Получить общее количество пакетов, ожидающих в очередях передачи
* @param epkt Экземпляр ETCP
* @return Количество пакетов
*/
int etcp_tx_queue_size(epkt_t* epkt);
/**
* @brief Поместить данные в очередь передачи
* @param epkt Экземпляр ETCP
* @param data Данные для отправки
* @param len Длина данных
* @return 0 при успехе, -1 при ошибке
*/
int etcp_tx_put(epkt_t* epkt, uint8_t* data, uint16_t len);
/**
* @brief Получить выходную очередь для чтения полученных данных
* @param epkt Экземпляр ETCP
* @return Указатель на выходную очередь (ll_queue_t*)
*/
ll_queue_t* etcp_get_output_queue(epkt_t* epkt);
/**
* @brief Установить ограничение пропускной способности
* @param epkt Экземпляр ETCP
* @param bandwidth Байты за единицу времени (0.1 мкс)
*/
void etcp_set_bandwidth(epkt_t* epkt, uint16_t bandwidth);
/**
* @brief Обновить размер окна на основе текущего RTT и пропускной способности
* @param epkt Экземпляр ETCP
* Размер окна = RTT * пропускная способность * 2 (байт в пути)
*/
void etcp_update_window(epkt_t* epkt);
/**
* @brief Получить текущее RTT
* @param epkt Экземпляр ETCP
* @return RTT в единицах времени
*/
uint16_t etcp_get_rtt(epkt_t* epkt);
/**
* @brief Получить текущий джиттер
* @param epkt Экземпляр ETCP
* @return Джиттер в единицах времени
*/
uint16_t etcp_get_jitter(epkt_t* epkt);
/**
* @brief Сбросить состояние соединения (очистить очереди, метрики, таймеры)
* @param epkt Экземпляр ETCP
* Примечание: Сохраняет настройки пропускной способности и обратного вызова
*/
void etcp_reset(epkt_t* epkt);
/**
* @brief Инициировать сброс соединения через служебные пакеты
* @param epkt Экземпляр ETCP
* Отправляет пакет сброса (0x02) и ждет подтверждения (0x03) с повторными попытками каждые 100мс
*/
void etcp_reset_connection(epkt_t* epkt);
/**
* @brief Получить статистику ETCP
* @param epkt Экземпляр ETCP
* @param retransmissions Указатель для возврата количества ретрансмиссий
* @param total_packets_sent Указатель для возврата общего количества отправленных пакетов
* @param unique_packets_sent Указатель для возврата количества уникальных отправленных пакетов
* @param bytes_sent_total Указатель для возврата общего количества отправленных байт
* @param bytes_received_total Указатель для возврата общего количества полученных байт
* @param ack_packets_count Указатель для возврата количества отправленных пакетов подтверждения
* @param control_packets_count Указатель для возврата количества отправленных управляющих пакетов
*/
void etcp_get_stats(epkt_t* epkt,
uint32_t* retransmissions,
uint32_t* total_packets_sent,
uint32_t* unique_packets_sent,
uint32_t* bytes_sent_total,
uint32_t* bytes_received_total,
uint32_t* ack_packets_count,
uint32_t* control_packets_count);
#ifdef __cplusplus
}
#endif
#endif // ETCP_H
+285
View File
@@ -0,0 +1,285 @@
#include "ll_queue.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include "u_async.h"
// Предварительное объявление для отложенного возобновления
static void queue_resume_timeout_cb(void* arg);
// Проверить и запустить ожидающие коллбэки
static void check_waiters(ll_queue_t* q) {
if (!q || !q->waiters) return;
queue_waiter_t** pprev = &q->waiters;
queue_waiter_t* waiter = q->waiters;
while (waiter) {
queue_waiter_t* next = waiter->next;
// Проверить условие: не больше max_packets и не больше max_bytes
if (q->count <= waiter->max_packets && q->total_bytes <= waiter->max_bytes) {
// Условие выполнено - вызвать коллбэк
waiter->callback(q, waiter->callback_arg);
// Удалить waiter из списка
*pprev = next;
free(waiter);
// pprev уже указывает на правильный следующий элемент
} else {
// Условие не выполнено - оставить в списке
pprev = &waiter->next;
}
waiter = next;
}
}
// ==================== Управление очередью ====================
ll_queue_t* queue_new(uasync_t* ua) {
ll_queue_t* q = calloc(1, sizeof(ll_queue_t));
if (!q) return NULL;
q->head = NULL;
q->tail = NULL;
q->count = 0;
q->total_bytes = 0;
q->size_limit = -1; // По умолчанию без ограничения
q->callback = NULL;
q->callback_arg = NULL;
q->callback_suspended = 0; // Коллбэки разрешены изначально
q->resume_timeout_id = NULL;
q->ua = ua;
q->waiters = NULL;
return q;
}
void queue_free(ll_queue_t* q) {
if (!q) return;
// Освободить все элементы
ll_entry_t* entry = q->head;
while (entry) {
ll_entry_t* next = entry->next;
free(entry);
entry = next;
}
// Освободить все ожидающие коллбэки
queue_waiter_t* waiter = q->waiters;
while (waiter) {
queue_waiter_t* next = waiter->next;
free(waiter);
waiter = next;
}
// Отменить отложенное возобновление если запланировано
if (q->resume_timeout_id) {
uasync_cancel_timeout(q->ua, q->resume_timeout_id);
}
free(q);
}
// ==================== Конфигурация очереди ====================
void queue_set_callback(ll_queue_t* q, queue_callback_t cbk_fn, void* arg) {
if (!q) return;
q->callback = cbk_fn;
q->callback_arg = arg;
}
static void queue_resume_timeout_cb(void* arg) {
ll_queue_t* q = (ll_queue_t*)arg;
if (!q || !q->callback) return;
// Очистить ID таймаута (таймаут сработал)
q->resume_timeout_id = NULL;
// Разрешить коллбэки
q->callback_suspended = 0;
// Если в очереди есть элементы, вызвать коллбэк с первым элементом
// Обработчик должен извлечь этот элемент вызовом queue_entry_get()
if (q->head) {
q->callback(q, q->head, q->callback_arg);
}
}
void queue_resume_callback(ll_queue_t* q) {
if (!q || !q->callback) return;
// Если уже есть отложенное возобновление, ничего не делать
if (q->resume_timeout_id) {
return;
}
// Запланировать отложенное возобновление через uasync
q->resume_timeout_id = uasync_set_timeout(q->ua, 0, q, queue_resume_timeout_cb);
}
void queue_set_size_limit(ll_queue_t* q, int lim) {
if (!q) return;
q->size_limit = lim;
}
// ==================== Управление элементами ====================
ll_entry_t* queue_entry_new(size_t data_size) {
// Выделить память под структуру + область данных
ll_entry_t* entry = malloc(sizeof(ll_entry_t) + data_size);
if (!entry) return NULL;
entry->next = NULL;
entry->size = data_size;
// Область данных оставить неинициализированной для производительности
return entry;
}
void queue_entry_free(ll_entry_t* entry) {
free(entry);
}
// ==================== Операции с очередью ====================
int queue_entry_put(ll_queue_t* q, ll_entry_t* entry) {
if (!q || !entry) return -1;
// Проверить лимит размера
if (q->size_limit >= 0 && q->count >= q->size_limit) {
queue_entry_free(entry);
return -1;
}
// Добавить в хвост (FIFO)
entry->next = NULL;
if (q->tail) {
q->tail->next = entry;
} else {
q->head = entry;
}
q->tail = entry;
q->count++;
q->total_bytes += entry->size;
// Если коллбэки разрешены - вызвать коллбэк
// Это запускает автоматическую обработку очереди
if (!q->callback_suspended && q->callback) {
printf("[LL_QUEUE DEBUG] queue_entry_put: calling callback, count=%d, suspended=%d\n", q->count, q->callback_suspended);
q->callback(q, entry, q->callback_arg);
}
// Проверить ожидающие коллбэки
check_waiters(q);
return 0;
}
int queue_entry_put_first(ll_queue_t* q, ll_entry_t* entry) {
if (!q || !entry) return -1;
// Проверить лимит размера
if (q->size_limit >= 0 && q->count >= q->size_limit) {
queue_entry_free(entry);
return -1;
}
// Добавить в голову (LIFO, высокий приоритет)
entry->next = q->head;
q->head = entry;
if (!q->tail) {
q->tail = entry;
}
q->count++;
q->total_bytes += entry->size;
// Если коллбэки разрешены - вызвать коллбэк
if (!q->callback_suspended && q->callback) {
printf("[LL_QUEUE DEBUG] queue_entry_put_first: calling callback, count=%d, suspended=%d\n", q->count, q->callback_suspended);
q->callback(q, entry, q->callback_arg);
}
// Проверить ожидающие коллбэки
check_waiters(q);
return 0;
}
ll_entry_t* queue_entry_get(ll_queue_t* q) {
if (!q || !q->head) return NULL;
ll_entry_t* entry = q->head;
q->head = entry->next;
if (!q->head) {
q->tail = NULL;
}
q->count--;
q->total_bytes -= entry->size;
entry->next = NULL; // Отсоединить от очереди
// При извлечении элемента приостанавливаем коллбэки
// Это предотвращает рекурсию если во время обработки добавляются новые элементы
q->callback_suspended = 1;
// Проверить ожидающие коллбэки
check_waiters(q);
return entry;
}
int queue_entry_count(ll_queue_t* q) {
if (!q) return 0;
return q->count;
}
// ==================== Асинхронное ожидание ====================
queue_waiter_t* queue_wait_threshold(ll_queue_t* q, int max_packets, size_t max_bytes,
queue_threshold_callback_t callback, void* arg) {
if (!q || !callback) return NULL;
// Создать новый waiter
queue_waiter_t* waiter = malloc(sizeof(queue_waiter_t));
if (!waiter) return NULL;
waiter->max_packets = max_packets;
waiter->max_bytes = max_bytes;
waiter->callback = callback;
waiter->callback_arg = arg;
waiter->next = NULL;
// Проверить условие немедленно
if (q->count <= max_packets && q->total_bytes <= max_bytes) {
// Условие уже выполнено - вызвать коллбэк и освободить waiter
callback(q, arg);
free(waiter);
return NULL;
}
// Добавить в список ожидающих
waiter->next = q->waiters;
q->waiters = waiter;
return waiter;
}
void queue_cancel_wait(ll_queue_t* q, queue_waiter_t* waiter) {
if (!q || !waiter) return;
// Найти и удалить waiter из списка
queue_waiter_t** pprev = &q->waiters;
queue_waiter_t* w = q->waiters;
while (w) {
if (w == waiter) {
*pprev = w->next;
free(w);
return;
}
pprev = &w->next;
w = w->next;
}
}
+141
View File
@@ -0,0 +1,141 @@
#ifndef LL_QUEUE_H
#define LL_QUEUE_H
#include <stddef.h> // для size_t
// Предварительные объявления
typedef struct ll_queue ll_queue_t;
typedef struct uasync_s uasync_t;
typedef struct ll_entry ll_entry_t;
// Тип коллбэка: вызывается при добавлении элемента в пустую очередь или для продолжения обработки
// Параметры: указатель на очередь, указатель на элемент (первый в очереди), пользовательский аргумент
typedef void (*queue_callback_t)(ll_queue_t* q, ll_entry_t* entry, void* arg);
// Структура элемента - переменный размер, данные расположены сразу после структуры
struct ll_entry {
struct ll_entry* next; // Указатель на следующий элемент в очереди
size_t size; // Размер данных элемента (байт)
};
// Структура условия ожидания (waiter)
struct queue_waiter {
int max_packets; // Максимальное количество пакетов
size_t max_bytes; // Максимальное количество байт
void (*callback)(ll_queue_t* q, void* arg); // Коллбэк для вызова
void* callback_arg; // Аргумент коллбэка
struct queue_waiter* next; // Следующий ожидающий в списке
};
typedef struct queue_waiter queue_waiter_t;
typedef void (*queue_threshold_callback_t)(ll_queue_t* q, void* arg);
// Структура очереди
struct ll_queue {
ll_entry_t* head; // Первый элемент (извлекается отсюда)
ll_entry_t* tail; // Последний элемент (добавляется сюда)
int count; // Текущее количество элементов
size_t total_bytes; // Общий размер данных всех элементов (байт)
int size_limit; // Максимальное количество (-1 = без ограничения)
queue_callback_t callback; // Функция коллбэка
void* callback_arg; // Пользовательский аргумент для коллбэка
int callback_suspended; // 1 если коллбэки приостановлены (во время обработки)
void* resume_timeout_id; // ID таймаута uasync для отложенного возобновления
uasync_t* ua; // Экземпляр uasync для таймеров
queue_waiter_t* waiters; // Список ожидающих коллбэков
};
// ==================== Управление очередью ====================
// Создать новую пустую очередь
// ua - экземпляр uasync для таймеров (обязательный параметр)
// Возвращает: указатель на очередь или NULL при ошибке выделения памяти
ll_queue_t* queue_new(uasync_t* ua);
// Освободить очередь и все её элементы
// Также отменяет отложенное возобновление если оно запланировано
void queue_free(ll_queue_t* q);
// ==================== Конфигурация очереди ====================
// Установить функцию и аргумент коллбэка для очереди
// Коллбэк вызывается при добавлении элемента в пустую очередь (разрешенные коллбэки)
// обработчик должен обработать этот пакет и когда будет готов к приёму следующего - вызывает resume_callback. обработка строго по одному пакету.
void queue_set_callback(ll_queue_t* q, queue_callback_t cbk_fn, void* arg);
// Возобновить коллбэки после обработки элемента переданного в коллбэке (тянуть дополнительные элементы из очереди не предусмотернные api нельзя).
// эта функция должна вызываться всегда после того как cbk_fn обработала пакет (можно с ожиданием через async), иначе очередь застрянет.
// Если в очереди остались элементы, запланирует вызов коллбэка через uasync_set_timeout(0)
// Это предотвращает накопление рекурсии в стеке вызовов
void queue_resume_callback(ll_queue_t* q);
// Установить максимальное количество элементов в очереди
// При превышении лимита новый элемент автоматически освобождается
void queue_set_size_limit(ll_queue_t* q, int lim);
// ==================== Управление элементами ====================
// Создать новый элемент с областью данных указанного размера
// Память выделяется одним блоком: [ll_entry_t][область данных data_size байт]
// Возвращает: указатель на элемент или NULL при ошибке выделения памяти
ll_entry_t* queue_entry_new(size_t data_size);
// Освободить элемент (не влияет на связи в очереди)
void queue_entry_free(ll_entry_t* entry);
// ==================== Операции с очередью ====================
// Добавить элемент в конец очереди (FIFO)
// Если очередь была пустой и коллбэки разрешены - вызывает коллбэк
// Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден)
int queue_entry_put(ll_queue_t* q, ll_entry_t* entry);
// Добавить элемент в начало очереди (LIFO, высокий приоритет)
// Если очередь была пустой и коллбэки разрешены - вызывает коллбэк
// Возвращает: 0 при успехе, -1 если превышен лимит размера (элемент освобожден)
int queue_entry_put_first(ll_queue_t* q, ll_entry_t* entry);
// Извлечь элемент из начала очереди
// При извлечении приостанавливает коллбэки (callback_suspended = 1) чтобы предотвратить рекурсию
// Возвращает: указатель на элемент или NULL если очередь пуста
ll_entry_t* queue_entry_get(ll_queue_t* q);
// Получить текущее количество элементов в очереди
int queue_entry_count(ll_queue_t* q);
// ==================== Вспомогательные функции ====================
// Получить указатель на область данных элемента
// Данные расположены сразу после структуры ll_entry_t
static inline void* ll_entry_data(ll_entry_t* entry) {
return (void*)(entry + 1);
}
// Получить размер данных элемента
static inline size_t ll_entry_size(ll_entry_t* entry) {
return entry->size;
}
// ==================== Асинхронное ожидание ====================
// Зарегистрировать коллбэк, который будет вызван когда очередь будет иметь
// не более max_packets пакетов и не более max_bytes байт.
// Если условие уже выполнено, коллбэк вызывается немедленно.
// Можно зарегистрировать несколько ожиданий на одной очереди.
// Возвращает указатель на waiter для возможной отмены через queue_cancel_wait
queue_waiter_t* queue_wait_threshold(ll_queue_t* q, int max_packets, size_t max_bytes,
queue_threshold_callback_t callback, void* arg);
// Отменить ожидание (удалить waiter из списка)
void queue_cancel_wait(ll_queue_t* q, queue_waiter_t* waiter);
// Получить общий размер данных в очереди (байт)
static inline size_t queue_total_bytes(ll_queue_t* q) {
if (!q) return 0;
return q->total_bytes;
}
#endif // LL_QUEUE_H
+670
View File
@@ -0,0 +1,670 @@
#include "pkt_normalizer.h"
#include "u_async.h"
#include "settings.h"
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdio.h>
static void packer_handler(ll_queue_t* q, ll_entry_t* unused, void* arg);
static void unpacker_handler(ll_queue_t* q, ll_entry_t* unused, void* arg);
static void send_buf(pn_struct* pn);
static int get_header(uint8_t* header, size_t L);
/* Calculate maximum regular block size that fits in max_fragment_size */
pn_struct* pkt_normalizer_init(uasync_t* ua, int is_packer) {
pn_struct* pn = malloc(sizeof(pn_struct));
if (!pn) return NULL;
pn->ua = ua;
pn->input = queue_new(ua);
if (!pn->input) {
free(pn);
return NULL;
}
pn->output = queue_new(ua);
if (!pn->output) {
queue_free(pn->input);
free(pn);
return NULL;
}
pn->is_packer = is_packer;
if (is_packer) {
pn->u.packer.cap = settings.max_fragment_size;
pn->u.packer.buf = malloc(pn->u.packer.cap);
if (!pn->u.packer.buf) {
queue_free(pn->input);
queue_free(pn->output);
free(pn);
return NULL;
}
pn->u.packer.len = 0;
pn->u.packer.error_count = 0;
queue_set_callback(pn->input, packer_handler, pn);
} else {
pn->u.unpacker.buf = NULL;
pn->u.unpacker.len = 0;
pn->u.unpacker.total_len = 0;
pn->u.unpacker.cap = 0;
pn->u.unpacker.error_count = 0;
pn->u.unpacker.in_fragment = 0;
pn->u.unpacker.service_buf = NULL;
pn->u.unpacker.service_len = 0;
pn->u.unpacker.service_cap = 0;
pn->u.unpacker.service_type = 0;
pn->u.unpacker.in_service = 0;
queue_set_callback(pn->input, unpacker_handler, pn);
}
return pn;
}
void pkt_normalizer_deinit(pn_struct* pn) {
if (!pn) return;
queue_free(pn->input);
queue_free(pn->output);
if (pn->is_packer) {
free(pn->u.packer.buf);
} else {
free(pn->u.unpacker.buf);
free(pn->u.unpacker.service_buf);
}
free(pn);
}
pkt_normalizer_pair* pkt_normalizer_pair_init(uasync_t* ua) {
pkt_normalizer_pair* pair = malloc(sizeof(pkt_normalizer_pair));
if (!pair) return NULL;
pair->packer = pkt_normalizer_init(ua, 1);
if (!pair->packer) {
free(pair);
return NULL;
}
pair->unpacker = pkt_normalizer_init(ua, 0);
if (!pair->unpacker) {
pkt_normalizer_deinit(pair->packer);
free(pair);
return NULL;
}
return pair;
}
void pkt_normalizer_pair_deinit(pkt_normalizer_pair* pair) {
if (!pair) return;
pkt_normalizer_deinit(pair->packer);
pkt_normalizer_deinit(pair->unpacker);
free(pair);
}
static int get_header(uint8_t* header, size_t L) {
if (L > 1535) return -1;
if (L <= 239) {
header[0] = (uint8_t)L;
return 1;
} else {
uint8_t high = (uint8_t)(L >> 8);
if (high > 5) return -1;
header[0] = 0xF0 + high;
header[1] = (uint8_t)(L & 0xFF);
return 2;
}
}
/* Сбросить состояние сборки фрагментов */
static void reset_fragment_state(pn_struct* pn) {
if (!pn->is_packer) {
pn->u.unpacker.len = 0;
pn->u.unpacker.total_len = 0;
pn->u.unpacker.in_fragment = 0;
}
}
/* Таймаут для сборки фрагментов */
static void send_buf(pn_struct* pn) {
if (pn->u.packer.len == 0) return;
size_t payload_len = pn->u.packer.len;
ll_entry_t* out = queue_entry_new(2 + payload_len);
if (!out) return;
uint8_t* d = ll_entry_data(out);
*(uint16_t*)d = (uint16_t)payload_len;
memcpy(d + 2, pn->u.packer.buf, payload_len);
queue_entry_put(pn->output, out);
pn->u.packer.len = 0;
}
static void packer_handler(ll_queue_t* q, ll_entry_t* unused, void* arg) {
(void)unused;
pn_struct* pn = arg;
size_t max = (size_t)settings.max_fragment_size;
ll_entry_t* entry = queue_entry_get(q);
if (!entry) {
queue_resume_callback(q);
return;
}
size_t L = ll_entry_size(entry);
uint8_t* data = ll_entry_data(entry);
uint8_t header[2];
int hsize = get_header(header, L);
size_t needed = (size_t)hsize + L;
if (hsize < 0 || needed > max) {
// Fragment
if (pn->u.packer.len > 0) {
send_buf(pn);
}
size_t remaining = L;
size_t pos = 0;
int fragment_count = 0;
while (remaining > 0) {
size_t chunk;
size_t payload_len;
ll_entry_t* fout;
uint8_t* fd;
uint8_t frag_header[2];
int frag_hsize;
if (fragment_count == 0) {
// Первый фрагмент: FF + общая длина (2 байта)
chunk = remaining > (max - 5) ? (max - 5) : remaining; // 2+1+2+chunk <= max
payload_len = 1 + 2 + chunk; // FF + total_len + data
fout = queue_entry_new(2 + payload_len);
if (!fout) {
break;
}
fd = ll_entry_data(fout);
*(uint16_t*)fd = (uint16_t)payload_len;
fd += 2;
*fd++ = 0xFF;
*fd++ = (uint8_t)(L >> 8); // старший байт общей длины
*fd++ = (uint8_t)(L & 0xFF); // младший байт общей длины
} else {
// Не первый фрагмент
if (remaining <= max - 3) {
// Это последний возможный фрагмент (помещается в один пакет с префиксом FE)
// Пытаемся отправить как обычный блок
frag_hsize = get_header(frag_header, remaining);
if (frag_hsize > 0 && (size_t)frag_hsize + remaining + 2 <= max) {
// Успешно: обычный блок
payload_len = frag_hsize + remaining;
chunk = remaining;
fout = queue_entry_new(2 + payload_len);
if (!fout) {
break;
}
fd = ll_entry_data(fout);
*(uint16_t*)fd = (uint16_t)payload_len;
fd += 2;
memcpy(fd, frag_header, frag_hsize);
fd += frag_hsize;
} else {
// Не удалось отправить как обычный блок - разбиваем на 2 фрагмента
// 1. FE фрагмент с частью данных
// 2. Обычный блок с оставшимися данными
// Находим максимальный размер для FE фрагмента
size_t max_fe_data = max - 3; // 2 байта длины + 0xFE
if (max_fe_data > remaining) {
max_fe_data = remaining;
}
// Пробуем различные размеры, начиная с максимального
size_t fe_data_size = 0;
for (size_t try_fe = max_fe_data; try_fe > 0; try_fe--) {
size_t try_regular = remaining - try_fe;
if (try_regular == 0) continue; // Нужно отправить что-то как обычный блок
uint8_t test_header[2];
int hsize = get_header(test_header, try_regular);
if (hsize <= 0) continue;
if ((size_t)hsize + try_regular + 2 <= max) {
fe_data_size = try_fe;
break;
}
}
if (fe_data_size == 0) {
// Не удалось найти разбиение - ошибка
pn->u.packer.error_count++;
// Отправляем как FE (нарушение спецификации, но это крайний случай)
chunk = remaining > (max - 3) ? (max - 3) : remaining;
payload_len = 1 + chunk; // FE + data
fout = queue_entry_new(2 + payload_len);
if (!fout) break;
fd = ll_entry_data(fout);
*(uint16_t*)fd = (uint16_t)payload_len;
fd += 2;
*fd++ = 0xFE;
} else {
// Отправляем FE фрагмент
chunk = fe_data_size;
payload_len = 1 + chunk; // FE + data
fout = queue_entry_new(2 + payload_len);
if (!fout) break;
fd = ll_entry_data(fout);
*(uint16_t*)fd = (uint16_t)payload_len;
fd += 2;
*fd++ = 0xFE;
memcpy(fd, data + pos, chunk);
queue_entry_put(pn->output, fout);
pos += chunk;
remaining -= chunk;
fragment_count++;
// Обновляем оставшиеся данные для обычного блока
// (цикл продолжит обработку на следующей итерации)
continue;
}
}
} else {
// Промежуточный фрагмент, отправляем как FE
chunk = remaining > (max - 3) ? (max - 3) : remaining;
payload_len = 1 + chunk; // FE + data
fout = queue_entry_new(2 + payload_len);
if (!fout) {
break;
}
fd = ll_entry_data(fout);
*(uint16_t*)fd = (uint16_t)payload_len;
fd += 2;
*fd++ = 0xFE;
}
}
memcpy(fd, data + pos, chunk);
queue_entry_put(pn->output, fout);
pos += chunk;
remaining -= chunk;
fragment_count++;
}
} else {
if (pn->u.packer.len + needed > max) {
send_buf(pn);
}
// Add to buffer
uint8_t* p = pn->u.packer.buf + pn->u.packer.len;
memcpy(p, header, (size_t)hsize);
memcpy(p + hsize, data, L);
pn->u.packer.len += needed;
}
queue_entry_free(entry);
if (pn->u.packer.len > 0) {
send_buf(pn);
}
queue_resume_callback(q);
}
void pkt_normalizer_set_service_callback(pn_struct* pn, pkt_normalizer_service_callback_t callback, void* user_data) {
if (!pn) return;
pn->service_callback = callback;
pn->service_callback_user_data = user_data;
}
void pkt_normalizer_reset_service_state(pn_struct* pn) {
if (!pn || pn->is_packer) return;
if (pn->u.unpacker.in_service) {
// Deliver pending service packet
if (pn->service_callback) {
pn->service_callback(pn->service_callback_user_data,
pn->u.unpacker.service_type,
pn->u.unpacker.service_buf,
pn->u.unpacker.service_len);
}
free(pn->u.unpacker.service_buf);
pn->u.unpacker.service_buf = NULL;
pn->u.unpacker.service_len = 0;
pn->u.unpacker.service_cap = 0;
pn->u.unpacker.in_service = 0;
}
}
void pkt_normalizer_reset_state(pn_struct* pn) {
if (!pn) return;
if (pn->is_packer) {
// Flush packer buffer
if (pn->u.packer.len > 0) {
send_buf(pn);
}
} else {
// Reset unpacker fragment state
reset_fragment_state(pn);
// Reset service state
pkt_normalizer_reset_service_state(pn);
}
}
int pkt_normalizer_send_service(pn_struct* pn, uint8_t type, const void* data, size_t len) {
if (!pn || !pn->is_packer) return -1;
// Service packet ограничен 256 байтами всего
if (len > 256 - 2) return -1; // 2 байта на заголовок (0xFC + тип)
size_t max = (size_t)settings.max_fragment_size;
if (max < 3) return -1;
// Размер сервисного пакета: 2 байта длины + 1 байт 0xFC + 1 байт тип + данные
// Если не помещается в один фрагмент - используем продолжение 0xFD
size_t total_service_len = 1 + 1 + len; // 0xFC + type + data
size_t pos = 0;
while (total_service_len > 0) {
// Определяем размер куска для этого пакета
size_t chunk;
uint8_t service_header;
if (pos == 0) {
// Первый пакет: 0xFC + тип + часть данных
// Максимум данных в первом пакете: max - 2 (длина) - 2 (0xFC+тип)
size_t max_first_data = max - 4;
if (max_first_data > len) max_first_data = len;
chunk = max_first_data;
service_header = 0xFC;
} else {
// Продолжение: 0xFD + данные
// Максимум данных: max - 2 (длина) - 1 (0xFD)
size_t max_cont_data = max - 3;
size_t remaining = len - pos;
if (max_cont_data > remaining) max_cont_data = remaining;
chunk = max_cont_data;
service_header = 0xFD;
}
if (chunk == 0) break;
size_t payload_len = 1 + chunk + (pos == 0 ? 1 : 0); // +1 байт типа для первого пакета
ll_entry_t* entry = queue_entry_new(2 + payload_len);
if (!entry) return -1;
uint8_t* d = ll_entry_data(entry);
*(uint16_t*)d = (uint16_t)payload_len;
d += 2;
*d++ = service_header;
if (pos == 0) {
*d++ = type;
}
memcpy(d, (const uint8_t*)data + pos, chunk);
queue_entry_put(pn->output, entry);
pos += chunk;
total_service_len -= chunk + (pos == chunk ? 2 : 1); // корректно вычитаем заголовки
}
return 0;
}
int pkt_normalizer_get_error_count(const pn_struct* pn) {
if (!pn) return 0;
if (pn->is_packer) {
return pn->u.packer.error_count;
}
return pn->u.unpacker.error_count;
}
void pkt_normalizer_reset_error_count(pn_struct* pn) {
if (!pn) return;
if (pn->is_packer) {
pn->u.packer.error_count = 0;
} else {
pn->u.unpacker.error_count = 0;
}
}
void pkt_normalizer_flush(pn_struct* pn) {
if (!pn || !pn->is_packer) return;
if (pn->u.packer.len > 0) {
send_buf(pn);
}
}
static void unpacker_handler(ll_queue_t* q, ll_entry_t* unused, void* arg) {
(void)unused;
pn_struct* pn = arg;
while (queue_entry_count(q) > 0) {
ll_entry_t* entry = queue_entry_get(q);
uint8_t* data = ll_entry_data(entry);
size_t total = ll_entry_size(entry);
uint16_t payload_len = *(uint16_t*)data;
if (total != 2 + (size_t)payload_len) {
queue_entry_free(entry);
continue;
}
uint8_t* cg = data + 2;
size_t cg_pos = 0;
size_t cg_len = (size_t)payload_len;
while (cg_pos < cg_len) {
uint8_t byte = cg[cg_pos++];
if (byte == 0xFF) {
/* Начало нового фрагментированного пакета */
if (pn->u.unpacker.in_fragment) {
/* Не завершен предыдущий фрагмент - ошибка */
pn->u.unpacker.error_count++;
reset_fragment_state(pn);
}
/* Проверить, что есть 2 байта для общей длины */
if (cg_pos + 2 > cg_len) {
pn->u.unpacker.error_count++;
goto err;
}
/* Прочитать общую длину */
uint16_t total_len = ((uint16_t)cg[cg_pos] << 8) | cg[cg_pos + 1];
cg_pos += 2;
size_t chunk_len = cg_len - cg_pos;
/* Выделить буфер при необходимости */
size_t new_len = chunk_len;
if (new_len > pn->u.unpacker.cap) {
size_t new_cap = pn->u.unpacker.cap ? pn->u.unpacker.cap * 2 : 4096;
if (new_cap < new_len) new_cap = new_len;
pn->u.unpacker.buf = realloc(pn->u.unpacker.buf, new_cap);
pn->u.unpacker.cap = new_cap;
}
memcpy(pn->u.unpacker.buf, cg + cg_pos, chunk_len);
pn->u.unpacker.len = chunk_len;
pn->u.unpacker.total_len = total_len;
pn->u.unpacker.in_fragment = 1;
cg_pos += chunk_len;
/* Проверить, не собрали ли уже весь пакет */
if (pn->u.unpacker.len >= pn->u.unpacker.total_len) {
if (pn->u.unpacker.len == pn->u.unpacker.total_len) {
ll_entry_t* out = queue_entry_new(pn->u.unpacker.total_len);
if (out) {
memcpy(ll_entry_data(out), pn->u.unpacker.buf, pn->u.unpacker.total_len);
queue_entry_put(pn->output, out);
}
} else {
/* Слишком много данных - ошибка */
pn->u.unpacker.error_count++;
}
reset_fragment_state(pn);
}
continue;
}
if (byte == 0xFE) {
/* Продолжение фрагментированного пакета */
if (!pn->u.unpacker.in_fragment) {
/* Не было начала фрагмента - ошибка */
pn->u.unpacker.error_count++;
goto err;
}
size_t chunk_len = cg_len - cg_pos;
size_t new_len = pn->u.unpacker.len + chunk_len;
if (new_len > pn->u.unpacker.cap) {
size_t new_cap = pn->u.unpacker.cap ? pn->u.unpacker.cap * 2 : 4096;
if (new_cap < new_len) new_cap = new_len;
pn->u.unpacker.buf = realloc(pn->u.unpacker.buf, new_cap);
pn->u.unpacker.cap = new_cap;
}
memcpy(pn->u.unpacker.buf + pn->u.unpacker.len, cg + cg_pos, chunk_len);
pn->u.unpacker.len = new_len;
cg_pos += chunk_len;
/* Проверить, не собрали ли уже весь пакет */
if (pn->u.unpacker.len >= pn->u.unpacker.total_len) {
if (pn->u.unpacker.len == pn->u.unpacker.total_len) {
ll_entry_t* out = queue_entry_new(pn->u.unpacker.total_len);
if (out) {
memcpy(ll_entry_data(out), pn->u.unpacker.buf, pn->u.unpacker.total_len);
queue_entry_put(pn->output, out);
}
} else {
/* Слишком много данных - ошибка */
pn->u.unpacker.error_count++;
}
reset_fragment_state(pn);
}
continue;
}
if (byte == 0xFC || byte == 0xFD) {
/* Service packet */
if (byte == 0xFC) {
/* Start of service packet */
if (pn->u.unpacker.in_service) {
/* Previous service packet finished - deliver it */
if (pn->service_callback) {
pn->service_callback(pn->service_callback_user_data,
pn->u.unpacker.service_type,
pn->u.unpacker.service_buf,
pn->u.unpacker.service_len);
}
free(pn->u.unpacker.service_buf);
pn->u.unpacker.service_buf = NULL;
pn->u.unpacker.service_len = 0;
pn->u.unpacker.service_cap = 0;
pn->u.unpacker.in_service = 0;
}
/* Read service type */
if (cg_pos >= cg_len) goto err;
uint8_t service_type = cg[cg_pos++];
pn->u.unpacker.service_type = service_type;
pn->u.unpacker.in_service = 1;
pn->u.unpacker.service_len = 0;
} else {
/* 0xFD - continuation */
if (!pn->u.unpacker.in_service) {
/* No service packet started - error */
pn->u.unpacker.error_count++;
goto err;
}
}
/* Read data */
size_t data_len = cg_len - cg_pos;
if (data_len > 0) {
size_t new_len = pn->u.unpacker.service_len + data_len;
if (new_len > 256) {
/* Service packet too long - error */
pn->u.unpacker.error_count++;
free(pn->u.unpacker.service_buf);
pn->u.unpacker.service_buf = NULL;
pn->u.unpacker.service_len = 0;
pn->u.unpacker.service_cap = 0;
pn->u.unpacker.in_service = 0;
goto err;
}
if (new_len > pn->u.unpacker.service_cap) {
size_t new_cap = pn->u.unpacker.service_cap ? pn->u.unpacker.service_cap * 2 : 256;
if (new_cap < new_len) new_cap = new_len;
if (new_cap > 256) new_cap = 256;
uint8_t* new_buf = realloc(pn->u.unpacker.service_buf, new_cap);
if (!new_buf) {
pn->u.unpacker.error_count++;
goto err;
}
pn->u.unpacker.service_buf = new_buf;
pn->u.unpacker.service_cap = new_cap;
}
memcpy(pn->u.unpacker.service_buf + pn->u.unpacker.service_len, cg + cg_pos, data_len);
pn->u.unpacker.service_len = new_len;
cg_pos += data_len;
}
/* Check if this is the end of service packet (end of payload) */
if (cg_pos >= cg_len) {
/* End of current payload, but service packet may continue in next transport packet */
continue;
} else {
/* There is more data in this payload after service packet - error */
pn->u.unpacker.error_count++;
free(pn->u.unpacker.service_buf);
pn->u.unpacker.service_buf = NULL;
pn->u.unpacker.service_len = 0;
pn->u.unpacker.service_cap = 0;
pn->u.unpacker.in_service = 0;
goto err;
}
}
/* Обычная запись (не фрагмент) */
size_t L;
if (byte <= 0xEF) {
L = byte;
} else if (byte >= 0xF0 && byte <= 0xF5) {
if (cg_pos >= cg_len) goto err;
uint8_t ext = cg[cg_pos++];
L = ((size_t)(byte - 0xF0) << 8) | ext;
} else {
/* Недопустимый байт */
goto err;
}
if (cg_pos + L > cg_len) goto err;
if (pn->u.unpacker.in_fragment) {
/* Это последний фрагмент в виде обычной записи */
size_t new_len = pn->u.unpacker.len + L;
if (new_len > pn->u.unpacker.cap) {
size_t new_cap = pn->u.unpacker.cap ? pn->u.unpacker.cap * 2 : 4096;
if (new_cap < new_len) new_cap = new_len;
pn->u.unpacker.buf = realloc(pn->u.unpacker.buf, new_cap);
pn->u.unpacker.cap = new_cap;
}
memcpy(pn->u.unpacker.buf + pn->u.unpacker.len, cg + cg_pos, L);
pn->u.unpacker.len = new_len;
cg_pos += L;
/* Проверить, собрали ли весь пакет */
if (pn->u.unpacker.len >= pn->u.unpacker.total_len) {
if (pn->u.unpacker.len == pn->u.unpacker.total_len) {
ll_entry_t* out = queue_entry_new(pn->u.unpacker.total_len);
if (out) {
memcpy(ll_entry_data(out), pn->u.unpacker.buf, pn->u.unpacker.total_len);
queue_entry_put(pn->output, out);
}
} else {
/* Слишком много данных - ошибка */
pn->u.unpacker.error_count++;
}
reset_fragment_state(pn);
}
} else {
/* Обычная запись (не часть фрагмента) */
ll_entry_t* out = queue_entry_new(L);
if (out) {
memcpy(ll_entry_data(out), cg + cg_pos, L);
queue_entry_put(pn->output, out);
}
cg_pos += L;
}
}
err:
queue_entry_free(entry);
}
queue_resume_callback(q);
}
+75
View File
@@ -0,0 +1,75 @@
// pkt_normalizer.h
#ifndef PKT_NORMALIZER_H
#define PKT_NORMALIZER_H
#include "ll_queue.h"
#include "u_async.h"
#include <stdint.h>
/* Default fragment reassembly timeout in uasync timebase units (0.1 ms) */
#ifndef PKT_NORMALIZER_FRAGMENT_TIMEOUT
#define PKT_NORMALIZER_FRAGMENT_TIMEOUT 5000 /* 500 ms */
#endif
typedef struct pn_struct pn_struct;
typedef struct pkt_normalizer_pair pkt_normalizer_pair;
/* Service packet callback type */
typedef void (*pkt_normalizer_service_callback_t)(void* user_data, uint8_t type, const uint8_t* data, size_t len);
struct pn_struct {
ll_queue_t* input;
ll_queue_t* output;
uasync_t* ua;
int is_packer;
union {
struct {
uint8_t* buf;
size_t len;
size_t cap;
int error_count;
} packer;
struct {
uint8_t* buf; /* буфер для сборки фрагментов */
size_t len; /* текущая накопленная длина */
size_t total_len; /* ожидаемая общая длина из первого фрагмента */
size_t cap; /* ёмкость буфера */
int error_count; /* счетчик ошибок сборки */
int in_fragment; /* флаг: идет сборка фрагментов (1) или нет (0) */
/* Service packet reassembly */
uint8_t* service_buf; /* буфер для сборки сервисных пакетов */
size_t service_len; /* текущая накопленная длина сервисного пакета */
size_t service_cap; /* ёмкость буфера сервисного пакета */
uint8_t service_type; /* тип сервисного пакета */
int in_service; /* флаг: идет сборка сервисного пакета (1) или нет (0) */
} unpacker;
} u;
/* Service packet callback */
pkt_normalizer_service_callback_t service_callback;
void* service_callback_user_data;
};
pn_struct* pkt_normalizer_init(uasync_t* ua, int is_packer); // 1 for packer, 0 for unpacker
void pkt_normalizer_deinit(pn_struct* pn);
pkt_normalizer_pair* pkt_normalizer_pair_init(uasync_t* ua);
void pkt_normalizer_pair_deinit(pkt_normalizer_pair* pair);
/* Error handling */
int pkt_normalizer_get_error_count(const pn_struct* pn);
void pkt_normalizer_reset_error_count(pn_struct* pn);
/* Flush internal buffer (packer only) */
void pkt_normalizer_flush(pn_struct* pn);
int pkt_normalizer_send_service(pn_struct* pn, uint8_t type, const void* data, size_t len);
void pkt_normalizer_set_service_callback(pn_struct* pn, pkt_normalizer_service_callback_t callback, void* user_data);
void pkt_normalizer_reset_service_state(pn_struct* pn);
void pkt_normalizer_reset_state(pn_struct* pn);
struct pkt_normalizer_pair {
pn_struct* packer;
pn_struct* unpacker;
};
#endif // PKT_NORMALIZER_H
+405
View File
@@ -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
View File
@@ -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
+265
View File
@@ -0,0 +1,265 @@
/* sc_lib.c - Secure Channel library implementation using TinyCrypt */
#include "sc_lib.h"
#include <tinycrypt/ecc.h>
#include <tinycrypt/ecc_dh.h>
#include <tinycrypt/aes.h>
#include <tinycrypt/ccm_mode.h>
#include <tinycrypt/constants.h>
#include <tinycrypt/ecc_platform_specific.h>
#include <string.h>
#include <stddef.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/time.h>
#include <fcntl.h>
static const struct uECC_Curve_t *curve = NULL;
static int sc_rng(uint8_t *dest, unsigned size)
{
int fd = open("/dev/urandom", O_RDONLY);
if (fd < 0) {
return 0;
}
ssize_t ret = read(fd, dest, size);
close(fd);
if (ret != size) {
return 0;
}
/* Mix in PID and microtime for additional entropy */
pid_t pid = getpid();
struct timeval tv;
gettimeofday(&tv, NULL);
for (unsigned i = 0; i < size; i++) {
dest[i] ^= ((pid >> (i % (sizeof(pid) * 8))) & 0xFF);
dest[i] ^= ((tv.tv_sec >> (i % (sizeof(tv.tv_sec) * 8))) & 0xFF);
dest[i] ^= ((tv.tv_usec >> (i % (sizeof(tv.tv_usec) * 8))) & 0xFF);
}
return 1;
}
static int sc_validate_key(const uint8_t *public_key)
{
if (!curve) {
curve = uECC_secp256r1();
}
return uECC_valid_public_key(public_key, curve);
}
sc_status_t sc_generate_keypair(sc_context_t *ctx)
{
if (!ctx) {
return SC_ERR_INVALID_ARG;
}
if (!curve) {
curve = uECC_secp256r1();
}
/* Set custom RNG function */
uECC_set_rng(sc_rng);
if (!uECC_make_key(ctx->public_key, ctx->private_key, curve)) {
return SC_ERR_CRYPTO;
}
ctx->initialized = 1;
ctx->peer_key_set = 0;
ctx->session_ready = 0;
ctx->tx_counter = 0;
ctx->rx_counter = 0;
return SC_OK;
}
sc_status_t sc_init_local_keys(sc_context_t *ctx,
const uint8_t *public_key,
const uint8_t *private_key)
{
if (!ctx || !public_key || !private_key) {
return SC_ERR_INVALID_ARG;
}
if (!curve) {
curve = uECC_secp256r1();
}
/* Validate public key */
if (sc_validate_key(public_key) != 0) {
return SC_ERR_INVALID_ARG;
}
memcpy(ctx->public_key, public_key, SC_PUBKEY_SIZE);
memcpy(ctx->private_key, private_key, SC_PRIVKEY_SIZE);
ctx->initialized = 1;
ctx->peer_key_set = 0;
ctx->session_ready = 0;
ctx->tx_counter = 0;
ctx->rx_counter = 0;
return SC_OK;
}
sc_status_t sc_set_peer_public_key(sc_context_t *ctx,
const uint8_t *peer_public_key)
{
uint8_t shared_secret[SC_SHARED_SECRET_SIZE];
if (!ctx || !peer_public_key) {
return SC_ERR_INVALID_ARG;
}
if (!ctx->initialized) {
return SC_ERR_NOT_INITIALIZED;
}
if (!curve) {
curve = uECC_secp256r1();
}
/* Validate peer public key */
if (sc_validate_key(peer_public_key) != 0) {
return SC_ERR_INVALID_ARG;
}
/* Compute shared secret using ECDH */
if (!uECC_shared_secret(peer_public_key, ctx->private_key,
shared_secret, curve)) {
return SC_ERR_CRYPTO;
}
/* Derive session key from shared secret (simple copy for demo) */
memcpy(ctx->session_key, shared_secret, SC_SESSION_KEY_SIZE);
/* Store peer public key */
memcpy(ctx->peer_public_key, peer_public_key, SC_PUBKEY_SIZE);
ctx->peer_key_set = 1;
ctx->session_ready = 1;
return SC_OK;
}
static void sc_build_nonce(uint32_t counter, uint8_t *nonce_out)
{
/* Simple nonce construction: counter in little-endian + zeros */
memset(nonce_out, 0, SC_NONCE_SIZE);
nonce_out[0] = (counter >> 0) & 0xFF;
nonce_out[1] = (counter >> 8) & 0xFF;
nonce_out[2] = (counter >> 16) & 0xFF;
nonce_out[3] = (counter >> 24) & 0xFF;
}
sc_status_t sc_encrypt(sc_context_t *ctx,
const uint8_t *plaintext,
size_t plaintext_len,
uint8_t *ciphertext,
uint8_t *tag)
{
uint8_t nonce[SC_NONCE_SIZE];
struct tc_aes_key_sched_struct sched;
struct tc_ccm_mode_struct ccm_state;
TCCcmMode_t c = &ccm_state;
uint8_t combined_output[plaintext_len + SC_TAG_SIZE];
if (!ctx || !plaintext || !ciphertext || !tag) {
return SC_ERR_INVALID_ARG;
}
if (!ctx->session_ready) {
return SC_ERR_NOT_INITIALIZED;
}
if (plaintext_len == 0) {
return SC_ERR_INVALID_ARG;
}
/* Initialize AES key schedule */
if (tc_aes128_set_encrypt_key(&sched, ctx->session_key) != TC_CRYPTO_SUCCESS) {
return SC_ERR_CRYPTO;
}
/* Build nonce from counter */
sc_build_nonce(ctx->tx_counter, nonce);
/* Configure CCM mode */
if (tc_ccm_config(c, &sched, nonce, SC_NONCE_SIZE, SC_TAG_SIZE) != TC_CRYPTO_SUCCESS) {
return SC_ERR_CRYPTO;
}
/* Encrypt and generate tag */
if (tc_ccm_generation_encryption(combined_output, sizeof(combined_output),
NULL, 0, /* no associated data */
plaintext, plaintext_len,
c) != TC_CRYPTO_SUCCESS) {
return SC_ERR_CRYPTO;
}
/* Split combined output into ciphertext and tag */
memcpy(ciphertext, combined_output, plaintext_len);
memcpy(tag, combined_output + plaintext_len, SC_TAG_SIZE);
ctx->tx_counter++;
return SC_OK;
}
sc_status_t sc_decrypt(sc_context_t *ctx,
const uint8_t *ciphertext,
size_t ciphertext_len,
const uint8_t *tag,
uint8_t *plaintext)
{
uint8_t nonce[SC_NONCE_SIZE];
struct tc_aes_key_sched_struct sched;
struct tc_ccm_mode_struct ccm_state;
TCCcmMode_t c = &ccm_state;
uint8_t combined_input[ciphertext_len + SC_TAG_SIZE];
if (!ctx || !ciphertext || !tag || !plaintext) {
return SC_ERR_INVALID_ARG;
}
if (!ctx->session_ready) {
return SC_ERR_NOT_INITIALIZED;
}
if (ciphertext_len == 0) {
return SC_ERR_INVALID_ARG;
}
/* Combine ciphertext and tag for CCM input */
memcpy(combined_input, ciphertext, ciphertext_len);
memcpy(combined_input + ciphertext_len, tag, SC_TAG_SIZE);
/* Initialize AES key schedule */
if (tc_aes128_set_encrypt_key(&sched, ctx->session_key) != TC_CRYPTO_SUCCESS) {
return SC_ERR_CRYPTO;
}
/* Build nonce from counter */
sc_build_nonce(ctx->rx_counter, nonce);
/* Configure CCM mode */
if (tc_ccm_config(c, &sched, nonce, SC_NONCE_SIZE, SC_TAG_SIZE) != TC_CRYPTO_SUCCESS) {
return SC_ERR_CRYPTO;
}
/* Decrypt and verify tag */
if (tc_ccm_decryption_verification(plaintext, ciphertext_len,
NULL, 0, /* no associated data */
combined_input, ciphertext_len + SC_TAG_SIZE,
c) != TC_CRYPTO_SUCCESS) {
return SC_ERR_AUTH_FAILED;
}
ctx->rx_counter++;
return SC_OK;
}
+119
View File
@@ -0,0 +1,119 @@
#ifndef SECURE_CHANNEL_H
#define SECURE_CHANNEL_H
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ===== Константы ===== */
#define SC_PUBKEY_SIZE 64 /* secp256r1: X || Y */
#define SC_PRIVKEY_SIZE 32
#define SC_SHARED_SECRET_SIZE 32
#define SC_SESSION_KEY_SIZE 16 /* AES-128 key size */
#define SC_NONCE_SIZE 13
#define SC_TAG_SIZE 16
/* ===== Коды ошибок ===== */
typedef enum {
SC_OK = 0,
SC_ERR_INVALID_ARG,
SC_ERR_CRYPTO,
SC_ERR_NOT_INITIALIZED,
SC_ERR_AUTH_FAILED
} sc_status_t;
/* ===== Контекст защищённого канала ===== */
typedef struct
{
/* Локальные ключи */
uint8_t private_key[SC_PRIVKEY_SIZE];
uint8_t public_key[SC_PUBKEY_SIZE];
/* Ключ пира */
uint8_t peer_public_key[SC_PUBKEY_SIZE];
uint8_t peer_key_set;
/* Производные ключи */
uint8_t session_key[SC_SESSION_KEY_SIZE];
/* Nonce / counters */
uint32_t tx_counter;
uint32_t rx_counter;
/* Флаги состояния */
uint8_t initialized;
uint8_t session_ready;
} sc_context_t;
/* ===== API ===== */
/**
* @brief Сгенерировать пару ECDH ключей
*
* @param ctx Контекст
* @return SC_OK или ошибка
*/
sc_status_t sc_generate_keypair(sc_context_t *ctx);
/**
* @brief Инициализация контекста с уже существующими ключами
*
* @param ctx Контекст
* @param public_key Указатель на публичный ключ (64 байта)
* @param private_key Указатель на приватный ключ (32 байта)
*/
sc_status_t sc_init_local_keys(sc_context_t *ctx,
const uint8_t *public_key,
const uint8_t *private_key);
/**
* @brief Установить публичный ключ пира и вычислить session key
*
* @param ctx Контекст
* @param peer_public_key Публичный ключ пира (64 байта)
*/
sc_status_t sc_set_peer_public_key(sc_context_t *ctx,
const uint8_t *peer_public_key);
/**
* @brief Зашифровать сообщение
*
* @param ctx Контекст
* @param plaintext Входные данные
* @param plaintext_len Длина
* @param ciphertext Выход (может совпадать с plaintext)
* @param tag MAC-тег (16 байт)
*/
sc_status_t sc_encrypt(sc_context_t *ctx,
const uint8_t *plaintext,
size_t plaintext_len,
uint8_t *ciphertext,
uint8_t *tag);
/**
* @brief Расшифровать и проверить сообщение
*
* @param ctx Контекст
* @param ciphertext Зашифрованные данные
* @param ciphertext_len Длина
* @param tag MAC-тег
* @param plaintext Выход
*/
sc_status_t sc_decrypt(sc_context_t *ctx,
const uint8_t *ciphertext,
size_t ciphertext_len,
const uint8_t *tag,
uint8_t *plaintext);
#ifdef __cplusplus
}
#endif
#endif /* SECURE_CHANNEL_H */
+6
View File
@@ -0,0 +1,6 @@
// settings.c
#include "settings.h"
settings_t settings = {
.max_fragment_size = 1400
};
+11
View File
@@ -0,0 +1,11 @@
// settings.h
#ifndef SETTINGS_H
#define SETTINGS_H
typedef struct {
int max_fragment_size;
} settings_t;
extern settings_t settings;
#endif // SETTINGS_H
+150
View File
@@ -0,0 +1,150 @@
// 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;
h->freed_count = 0;
h->user_data = NULL;
h->free_callback = NULL;
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 and free their data
while (h->size > 0 && h->heap[0].deleted) {
void *data_to_free = h->heap[0].data;
remove_root(h);
if (h->free_callback) {
h->free_callback(h->user_data, data_to_free);
} else {
free(data_to_free);
}
h->freed_count++;
}
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
}
void timeout_heap_set_free_callback(TimeoutHeap *h, void* user_data, void (*callback)(void* user_data, void* data)) {
if (!h) return;
h->user_data = user_data;
h->free_callback = callback;
}
+92
View File
@@ -0,0 +1,92 @@
// 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 TimeoutHeap TimeoutHeap;
struct TimeoutHeap {
TimeoutEntry *heap; // Dynamic array
size_t size; // Current number of elements
size_t capacity; // Allocated size
size_t freed_count; // Number of freed timer nodes
void* user_data; // User data for free callback
void (*free_callback)(void* user_data, void* data); // Callback to free data
};
/**
* 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);
/**
* Set a callback function to free data when deleted nodes are removed.
* @param h The heap.
* @param user_data User data passed to callback.
* @param callback Callback function (if NULL, data is freed with free()).
*/
void timeout_heap_set_free_callback(TimeoutHeap *h, void* user_data, void (*callback)(void* user_data, void* data));
/**
* 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);
/**
* Get the number of freed timer nodes.
* @param h The heap.
* @return Count of freed timer nodes.
*/
size_t timeout_heap_get_freed_count(TimeoutHeap *h);
#endif // TIMEOUT_HEAP_H
+238
View File
@@ -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
View File
@@ -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 */
+433
View File
@@ -0,0 +1,433 @@
// 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
uasync_t* ua; // Pointer back to uasync instance for counter updates
};
// 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];
// Debug counters for memory allocation tracking
size_t timer_alloc_count;
size_t timer_free_count;
size_t socket_alloc_count;
size_t socket_free_count;
};
// No global instance - each module must use its own uasync_t instance
// Callback to free timeout node and update counters
static void timeout_node_free_callback(void* user_data, void* data) {
uasync_t* ua = (uasync_t*)user_data;
struct timeout_node* node = (struct timeout_node*)data;
(void)node; // Not used directly, but keep for consistency
ua->timer_free_count++;
free(data);
}
// 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;
}
// Process expired timeouts
static void process_timeouts(struct uasync_s* 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);
while (1) {
TimeoutEntry entry;
if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) break;
if (entry.expiration > now_ms) break;
// 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);
}
ua->timer_free_count++;
free(node);
}
}
// Compute time to next timeout
static void get_next_timeout(struct uasync_s* 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;
}
// Instance version
void* uasync_set_timeout(uasync_t* ua, int timeout_tb, void* arg, timeout_callback_t callback) {
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;
ua->timer_alloc_count++;
node->arg = arg;
node->callback = callback;
node->ua = ua;
// 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(ua->timeout_heap, node->expiration_ms, node) != 0) {
ua->timer_free_count++;
free(node);
return NULL;
}
return node;
}
// Instance version
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) {
// Mark as cancelled by clearing callback - memory will be freed later
node->callback = NULL;
return ERR_OK;
}
// If not found in heap (maybe already expired and removed), do NOT free
// because node was already freed in process_timeouts
return ERR_FAIL;
}
// Instance version
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;
ua->socket_alloc_count++;
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;
}
// Instance version
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;
ua->socket_free_count++;
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 */
}
}
// Instance version
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 timeouts that may have expired during select */
process_timeouts(ua);
/* 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--;
}
}
}
// ========== Instance management functions ==========
uasync_t* uasync_create(void) {
uasync_t* ua = malloc(sizeof(struct uasync_s));
if (!ua) return NULL;
memset(ua, 0, sizeof(struct uasync_s));
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;
}
// Set callback to free timeout nodes and update counters
timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback);
return ua;
}
void uasync_destroy(uasync_t* ua) {
if (!ua) return;
// Check for potential memory leaks
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) {
fprintf(stderr, "[UASYNC FATAL] Memory leaks detected before cleanup: timers %zu/%zu, sockets %zu/%zu\n",
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count);
// Continue cleanup, will abort after if leaks remain
}
// Free all remaining timeouts
if (ua->timeout_heap) {
size_t freed_count = 0;
while (1) {
TimeoutEntry entry;
if (timeout_heap_pop(ua->timeout_heap, &entry) != 0) break;
struct timeout_node* node = (struct timeout_node*)entry.data;
ua->timer_free_count++;
freed_count++;
free(node);
}
printf("[UASYNC_DEBUG] Freed %zu timer nodes in destroy, heap freed_count = %zu\n",
freed_count, ua->timeout_heap->freed_count);
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;
ua->socket_free_count++;
free(cur);
cur = next;
}
// Final leak check
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) {
fprintf(stderr, "[UASYNC FATAL] Memory leaks detected after cleanup: timers %zu/%zu, sockets %zu/%zu\n",
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count);
abort();
}
free(ua);
}
void uasync_init_instance(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);
if (ua->timeout_heap) {
timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback);
}
}
}
// Debug statistics
void uasync_get_stats(uasync_t* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free) {
if (!ua) return;
if (timer_alloc) *timer_alloc = ua->timer_alloc_count;
if (timer_free) *timer_free = ua->timer_free_count;
if (socket_alloc) *socket_alloc = ua->socket_alloc_count;
if (socket_free) *socket_free = ua->socket_free_count;
}
// Get global instance for backward compatibility
+47
View File
@@ -0,0 +1,47 @@
// uasync.h
// модуль асинхронных операций. добавляем сокеты и таймауты и mainloop их обслуживает.
#ifndef UASYNC_H
#define UASYNC_H
#include <sys/time.h>
#include <sys/select.h>
#include <stddef.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 API - основной API для работы с uasync
uasync_t* uasync_create(void);
void uasync_destroy(uasync_t* ua);
void uasync_init_instance(uasync_t* ua);
// 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);
// Mainloop (бесконечный цикл, __noreturn)
void uasync_mainloop(uasync_t* ua);
// Debug statistics
void uasync_get_stats(uasync_t* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free);
#endif // UASYNC_H
+756
View File
@@ -0,0 +1,756 @@
// 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 "u_async.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(uasync_t *ua,
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(ua);
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->ua,
&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;
}
// Destroy uasync instance
if (state->ua) {
uasync_destroy(state->ua);
state->ua = 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) {
// Process async events (timers, sockets)
if (state->ua) {
uasync_poll(state->ua, 0);
}
// 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;
}
state.ua = uasync_create();
if (!state.ua) {
fprintf(stderr, "Failed to create uasync instance\n");
cleanup(&state, args.pid_file);
return 1;
}
uasync_init_instance(state.ua);
// 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;
}
+30
View File
@@ -0,0 +1,30 @@
/**
* @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;
uasync_t *ua;
utun_config_t *config;
FILE *log_fp;
} utun_state_t;
#endif /* UTUN_STATE_H */