Reorganize project structure: move sources to src/, update Makefile, add header symlinks
This commit is contained in:
1 parent
9eab53179d
commit
f2c10dcbf9
45 files changed
+1612
-1825
No files matched your search
@@ -0,0 +1,413 @@
|
||||
// config_parser.c - Configuration parser for utun application
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "config_parser.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define MAX_LINE_LEN 1024
|
||||
#define INITIAL_CONNECTION_CAPACITY 4
|
||||
|
||||
// Helper function to trim whitespace
|
||||
static char* trim(char *str) {
|
||||
if (!str) return NULL;
|
||||
|
||||
// Trim leading spaces
|
||||
while (isspace((unsigned char)*str)) str++;
|
||||
|
||||
// Trim trailing spaces
|
||||
char *end = str + strlen(str) - 1;
|
||||
while (end > str && isspace((unsigned char)*end)) end--;
|
||||
*(end + 1) = '\0';
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
// Parse a key-value pair
|
||||
static int parse_key_value(const char *line, char *key, size_t key_len,
|
||||
char *value, size_t value_len) {
|
||||
char *equal = strchr(line, '=');
|
||||
if (!equal) return -1;
|
||||
|
||||
// Extract key
|
||||
size_t key_size = equal - line;
|
||||
if (key_size >= key_len) return -1;
|
||||
strncpy(key, line, key_size);
|
||||
key[key_size] = '\0';
|
||||
trim(key);
|
||||
|
||||
// Extract value
|
||||
const char *val_start = equal + 1;
|
||||
size_t val_len = strlen(val_start);
|
||||
if (val_len >= value_len) return -1;
|
||||
strcpy(value, val_start);
|
||||
trim(value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Parse connection mode string
|
||||
static config_conn_mode_t parse_mode(const char *mode_str) {
|
||||
if (!mode_str) return CONFIG_MODE_UNKNOWN;
|
||||
|
||||
if (strcasecmp(mode_str, "client") == 0) {
|
||||
return CONFIG_MODE_CLIENT;
|
||||
} else if (strcasecmp(mode_str, "server") == 0) {
|
||||
return CONFIG_MODE_SERVER;
|
||||
}
|
||||
|
||||
return CONFIG_MODE_UNKNOWN;
|
||||
}
|
||||
|
||||
// Add a connection to the config
|
||||
static int add_connection(utun_config_t *config, const connection_config_t *conn) {
|
||||
if (config->connection_count >= config->connection_capacity) {
|
||||
// Resize array
|
||||
int new_capacity = config->connection_capacity * 2;
|
||||
connection_config_t *new_connections = realloc(config->connections,
|
||||
new_capacity * sizeof(connection_config_t));
|
||||
if (!new_connections) return -1;
|
||||
|
||||
config->connections = new_connections;
|
||||
config->connection_capacity = new_capacity;
|
||||
}
|
||||
|
||||
// Copy connection data
|
||||
memcpy(&config->connections[config->connection_count], conn, sizeof(connection_config_t));
|
||||
config->connection_count++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Parse configuration file
|
||||
utun_config_t* parse_config(const char *filename) {
|
||||
FILE *fp = fopen(filename, "r");
|
||||
if (!fp) {
|
||||
fprintf(stderr, "Failed to open config file: %s\n", filename);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Allocate config structure
|
||||
utun_config_t *config = calloc(1, sizeof(utun_config_t));
|
||||
if (!config) {
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Initialize connection array
|
||||
config->connection_capacity = INITIAL_CONNECTION_CAPACITY;
|
||||
config->connections = malloc(config->connection_capacity * sizeof(connection_config_t));
|
||||
if (!config->connections) {
|
||||
free(config);
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char line[MAX_LINE_LEN];
|
||||
char current_section[128] = "";
|
||||
connection_config_t current_conn = {0};
|
||||
int in_connection_section = 0;
|
||||
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
// Remove newline
|
||||
line[strcspn(line, "\n")] = '\0';
|
||||
|
||||
char *trimmed = trim(line);
|
||||
|
||||
// Skip empty lines and comments
|
||||
if (strlen(trimmed) == 0 || trimmed[0] == ';' || trimmed[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for section header
|
||||
if (trimmed[0] == '[' && trimmed[strlen(trimmed) - 1] == ']') {
|
||||
// End previous connection section if any
|
||||
if (in_connection_section) {
|
||||
if (strlen(current_conn.name) > 0) {
|
||||
if (add_connection(config, ¤t_conn) != 0) {
|
||||
fprintf(stderr, "Failed to add connection: %s\n", current_conn.name);
|
||||
}
|
||||
}
|
||||
memset(¤t_conn, 0, sizeof(current_conn));
|
||||
in_connection_section = 0;
|
||||
}
|
||||
|
||||
// Extract section name
|
||||
strncpy(current_section, trimmed + 1, sizeof(current_section) - 1);
|
||||
current_section[sizeof(current_section) - 1] = '\0';
|
||||
current_section[strcspn(current_section, "]")] = '\0';
|
||||
trim(current_section);
|
||||
|
||||
// Check if it's a connection section
|
||||
if (strncmp(current_section, "connection:", 11) == 0) {
|
||||
in_connection_section = 1;
|
||||
// Extract connection name
|
||||
char *name = trim(current_section + 11);
|
||||
if (strlen(name) > 0) {
|
||||
strncpy(current_conn.name, name, sizeof(current_conn.name) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse key-value pair
|
||||
char key[256], value[256];
|
||||
if (parse_key_value(trimmed, key, sizeof(key), value, sizeof(value)) != 0) {
|
||||
fprintf(stderr, "Invalid key-value line: %s\n", trimmed);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Process based on current section
|
||||
if (strcasecmp(current_section, "global") == 0) {
|
||||
if (strcasecmp(key, "my_private_key") == 0) {
|
||||
strncpy(config->global.my_private_key_hex, value, sizeof(config->global.my_private_key_hex) - 1);
|
||||
} else if (strcasecmp(key, "my_public_key") == 0) {
|
||||
strncpy(config->global.my_public_key_hex, value, sizeof(config->global.my_public_key_hex) - 1);
|
||||
} else if (strcasecmp(key, "option") == 0) {
|
||||
strncpy(config->global.option_value, value, sizeof(config->global.option_value) - 1);
|
||||
} else if (strcasecmp(key, "control_ip") == 0) {
|
||||
strncpy(config->global.control_ip, value, sizeof(config->global.control_ip) - 1);
|
||||
} else if (strcasecmp(key, "control_port") == 0) {
|
||||
config->global.control_port = atoi(value);
|
||||
} else if (strcasecmp(key, "net_debug") == 0) {
|
||||
config->global.net_debug = atoi(value);
|
||||
}
|
||||
} else if (strcasecmp(current_section, "routing") == 0) {
|
||||
if (strcasecmp(key, "allowed_subnet") == 0) {
|
||||
if (config->allowed_subnet_count < MAX_ALLOWED_SUBNETS) {
|
||||
strncpy(config->allowed_subnets[config->allowed_subnet_count].subnet, value, sizeof(config->allowed_subnets[0].subnet) - 1);
|
||||
config->allowed_subnet_count++;
|
||||
} else {
|
||||
fprintf(stderr, "Too many allowed subnets, maximum is %d\n", MAX_ALLOWED_SUBNETS);
|
||||
}
|
||||
}
|
||||
} else if (in_connection_section) {
|
||||
if (strcasecmp(key, "mode") == 0) {
|
||||
current_conn.mode = parse_mode(value);
|
||||
} else if (strcasecmp(key, "addr") == 0) {
|
||||
// For server mode
|
||||
if (current_conn.mode == CONFIG_MODE_SERVER) {
|
||||
strncpy(current_conn.local_addr, value, sizeof(current_conn.local_addr) - 1);
|
||||
}
|
||||
} else if (strcasecmp(key, "from_addr") == 0) {
|
||||
// For client mode
|
||||
if (current_conn.mode == CONFIG_MODE_CLIENT) {
|
||||
strncpy(current_conn.local_addr, value, sizeof(current_conn.local_addr) - 1);
|
||||
}
|
||||
} else if (strcasecmp(key, "to_addr") == 0) {
|
||||
// For client mode
|
||||
if (current_conn.mode == CONFIG_MODE_CLIENT) {
|
||||
strncpy(current_conn.remote_addr, value, sizeof(current_conn.remote_addr) - 1);
|
||||
}
|
||||
} else if (strcasecmp(key, "peer_public_key") == 0) {
|
||||
strncpy(current_conn.peer_public_key_hex, value, sizeof(current_conn.peer_public_key_hex) - 1);
|
||||
} else if (strcasecmp(key, "so_mark") == 0) {
|
||||
current_conn.so_mark = atoi(value);
|
||||
} else if (strcasecmp(key, "netif") == 0) {
|
||||
strncpy(current_conn.netif, value, sizeof(current_conn.netif) - 1);
|
||||
} else if (strcasecmp(key, "tun") == 0) {
|
||||
strncpy(current_conn.tun_ifname, value, sizeof(current_conn.tun_ifname) - 1);
|
||||
} else if (strcasecmp(key, "tun_ip") == 0) {
|
||||
strncpy(current_conn.tun_ip, value, sizeof(current_conn.tun_ip) - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add last connection if any
|
||||
if (in_connection_section && strlen(current_conn.name) > 0) {
|
||||
if (add_connection(config, ¤t_conn) != 0) {
|
||||
fprintf(stderr, "Failed to add connection: %s\n", current_conn.name);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
return config;
|
||||
}
|
||||
|
||||
// Free configuration structure
|
||||
void free_config(utun_config_t *config) {
|
||||
if (!config) return;
|
||||
|
||||
if (config->connections) {
|
||||
free(config->connections);
|
||||
}
|
||||
free(config);
|
||||
}
|
||||
|
||||
// Print configuration for debugging
|
||||
void print_config(const utun_config_t *config) {
|
||||
if (!config) {
|
||||
printf("Configuration is NULL\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Global configuration:\n");
|
||||
printf(" my_private_key: %s\n", config->global.my_private_key_hex);
|
||||
printf(" my_public_key: %s\n", config->global.my_public_key_hex);
|
||||
printf(" option: %s\n", config->global.option_value);
|
||||
printf(" control_ip: %s\n", config->global.control_ip);
|
||||
printf(" control_port: %u\n", config->global.control_port);
|
||||
printf(" net_debug: %d\n", config->global.net_debug);
|
||||
printf("\n");
|
||||
printf("Allowed subnets (%d):\n", config->allowed_subnet_count);
|
||||
for (int i = 0; i < config->allowed_subnet_count; i++) {
|
||||
printf(" %s\n", config->allowed_subnets[i].subnet);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
printf("Connections (%d):\n", config->connection_count);
|
||||
for (int i = 0; i < config->connection_count; i++) {
|
||||
const connection_config_t *conn = &config->connections[i];
|
||||
printf(" [%d] %s:\n", i, conn->name);
|
||||
printf(" mode: %s\n",
|
||||
conn->mode == CONFIG_MODE_CLIENT ? "client" :
|
||||
conn->mode == CONFIG_MODE_SERVER ? "server" : "unknown");
|
||||
printf(" local_addr: %s\n", conn->local_addr);
|
||||
printf(" remote_addr: %s\n", conn->remote_addr);
|
||||
printf(" peer_public_key: %s\n", conn->peer_public_key_hex);
|
||||
printf(" so_mark: %d\n", conn->so_mark);
|
||||
printf(" netif: %s\n", conn->netif);
|
||||
printf(" tun: %s\n", conn->tun_ifname);
|
||||
printf(" tun_ip: %s\n", conn->tun_ip);
|
||||
}
|
||||
}
|
||||
|
||||
// Update keys in configuration file
|
||||
int update_config_keys(const char *filename,
|
||||
const char *private_key_hex,
|
||||
const char *public_key_hex) {
|
||||
if (!filename || !private_key_hex || !public_key_hex) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Read entire file
|
||||
FILE *fp = fopen(filename, "r");
|
||||
if (!fp) {
|
||||
// Try to create new file
|
||||
fp = fopen(filename, "w");
|
||||
if (!fp) return -1;
|
||||
|
||||
fprintf(fp, "[global]\n");
|
||||
fprintf(fp, "my_private_key=%s\n", private_key_hex);
|
||||
fprintf(fp, "my_public_key=%s\n", public_key_hex);
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Read all lines
|
||||
char **lines = NULL;
|
||||
size_t line_count = 0;
|
||||
size_t capacity = 0;
|
||||
char line[MAX_LINE_LEN];
|
||||
int in_global_section = 0;
|
||||
int private_key_found = 0;
|
||||
int public_key_found = 0;
|
||||
size_t private_key_line = 0;
|
||||
size_t public_key_line = 0;
|
||||
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
// Add line to array
|
||||
if (line_count >= capacity) {
|
||||
size_t new_capacity = capacity ? capacity * 2 : 16;
|
||||
char **new_lines = realloc(lines, new_capacity * sizeof(char *));
|
||||
if (!new_lines) {
|
||||
// Cleanup
|
||||
for (size_t i = 0; i < line_count; i++) free(lines[i]);
|
||||
free(lines);
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
lines = new_lines;
|
||||
capacity = new_capacity;
|
||||
}
|
||||
|
||||
lines[line_count] = strdup(line);
|
||||
if (!lines[line_count]) {
|
||||
// Cleanup
|
||||
for (size_t i = 0; i < line_count; i++) free(lines[i]);
|
||||
free(lines);
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
char *trimmed = trim(line);
|
||||
|
||||
// Track global section
|
||||
if (trimmed[0] == '[' && trimmed[strlen(trimmed) - 1] == ']') {
|
||||
char section[128];
|
||||
strncpy(section, trimmed + 1, sizeof(section) - 1);
|
||||
section[sizeof(section) - 1] = '\0';
|
||||
section[strcspn(section, "]")] = '\0';
|
||||
trim(section);
|
||||
|
||||
in_global_section = (strcasecmp(section, "global") == 0);
|
||||
}
|
||||
|
||||
// Check for keys in global section
|
||||
if (in_global_section) {
|
||||
char key[256], value[256];
|
||||
if (parse_key_value(trimmed, key, sizeof(key), value, sizeof(value)) == 0) {
|
||||
if (strcasecmp(key, "my_private_key") == 0) {
|
||||
private_key_found = 1;
|
||||
private_key_line = line_count;
|
||||
} else if (strcasecmp(key, "my_public_key") == 0) {
|
||||
public_key_found = 1;
|
||||
public_key_line = line_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
line_count++;
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
// Update or add keys
|
||||
if (private_key_found) {
|
||||
free(lines[private_key_line]);
|
||||
char new_line[MAX_LINE_LEN];
|
||||
snprintf(new_line, sizeof(new_line), "my_private_key=%s\n", private_key_hex);
|
||||
lines[private_key_line] = strdup(new_line);
|
||||
} else {
|
||||
// Add after [global] line or at beginning
|
||||
// Simplified: add at end
|
||||
// We'll need to insert after [global] but for simplicity, append
|
||||
}
|
||||
|
||||
if (public_key_found) {
|
||||
free(lines[public_key_line]);
|
||||
char new_line[MAX_LINE_LEN];
|
||||
snprintf(new_line, sizeof(new_line), "my_public_key=%s\n", public_key_hex);
|
||||
lines[public_key_line] = strdup(new_line);
|
||||
}
|
||||
|
||||
// Write back to file
|
||||
fp = fopen(filename, "w");
|
||||
if (!fp) {
|
||||
// Cleanup
|
||||
for (size_t i = 0; i < line_count; i++) free(lines[i]);
|
||||
free(lines);
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < line_count; i++) {
|
||||
fputs(lines[i], fp);
|
||||
free(lines[i]);
|
||||
}
|
||||
|
||||
// Add missing keys
|
||||
if (!private_key_found) {
|
||||
fprintf(fp, "my_private_key=%s\n", private_key_hex);
|
||||
}
|
||||
if (!public_key_found) {
|
||||
fprintf(fp, "my_public_key=%s\n", public_key_hex);
|
||||
}
|
||||
|
||||
free(lines);
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// config_parser.h - Configuration parser for utun application
|
||||
#ifndef CONFIG_PARSER_H
|
||||
#define CONFIG_PARSER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "connection.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Maximum lengths for strings in config
|
||||
#define MAX_CONN_NAME_LEN 64
|
||||
#define MAX_KEY_LEN 256 // HEX key length (64 bytes * 2 chars + null)
|
||||
#define MAX_ADDR_LEN 64
|
||||
#define MAX_NETIF_LEN 16
|
||||
#define MAX_OPTION_VALUE_LEN 256
|
||||
#define MAX_ALLOWED_SUBNETS 32
|
||||
|
||||
// Connection mode (config parser internal)
|
||||
typedef enum {
|
||||
CONFIG_MODE_UNKNOWN = 0,
|
||||
CONFIG_MODE_CLIENT,
|
||||
CONFIG_MODE_SERVER
|
||||
} config_conn_mode_t;
|
||||
|
||||
// Subnet entry for allowed routing
|
||||
typedef struct {
|
||||
char subnet[MAX_ADDR_LEN]; // Format: "192.168.0.0/24"
|
||||
} subnet_entry_t;
|
||||
|
||||
// Connection configuration
|
||||
typedef struct {
|
||||
char name[MAX_CONN_NAME_LEN]; // Connection name from [connection: name]
|
||||
config_conn_mode_t mode; // server or client
|
||||
char local_addr[MAX_ADDR_LEN]; // For client: from_addr, for server: addr
|
||||
char remote_addr[MAX_ADDR_LEN]; // For client: to_addr, for server: N/A
|
||||
char peer_public_key_hex[MAX_KEY_LEN]; // Peer's public key in HEX
|
||||
int so_mark; // Socket mark (0 = not set)
|
||||
char netif[MAX_NETIF_LEN]; // Network interface to bind to
|
||||
char tun_ifname[MAX_NETIF_LEN]; // TUN interface name (e.g., tun12)
|
||||
char tun_ip[MAX_ADDR_LEN]; // TUN interface IP address (e.g., 10.0.0.1/24)
|
||||
} connection_config_t;
|
||||
|
||||
// Global configuration
|
||||
typedef struct {
|
||||
char my_private_key_hex[MAX_KEY_LEN]; // Our private key in HEX
|
||||
char my_public_key_hex[MAX_KEY_LEN]; // Our public key in HEX
|
||||
char option_value[MAX_OPTION_VALUE_LEN]; // Global option value
|
||||
char control_ip[MAX_ADDR_LEN]; // Control socket IP (empty for disabled)
|
||||
uint16_t control_port; // Control socket port
|
||||
int net_debug; // Network debug mode: 1 = enable network emulator
|
||||
} global_config_t;
|
||||
|
||||
// Complete configuration
|
||||
typedef struct {
|
||||
global_config_t global;
|
||||
connection_config_t *connections;
|
||||
int connection_count;
|
||||
int connection_capacity;
|
||||
subnet_entry_t allowed_subnets[MAX_ALLOWED_SUBNETS];
|
||||
int allowed_subnet_count;
|
||||
} utun_config_t;
|
||||
|
||||
/**
|
||||
* @brief Parse configuration file
|
||||
* @param filename Path to configuration file
|
||||
* @return Pointer to parsed configuration, NULL on error
|
||||
*
|
||||
* Parses INI-style configuration file with format:
|
||||
* [global]
|
||||
* option=value
|
||||
* my_private_key=HEX
|
||||
* my_public_key=HEX
|
||||
*
|
||||
* [connection: name]
|
||||
* mode=server|client
|
||||
* addr=ip:port (for server)
|
||||
* from_addr=ip:port (for client)
|
||||
* to_addr=ip:port (for client)
|
||||
* peer_public_key=HEX
|
||||
* so_mark=number
|
||||
* netif=interface_name
|
||||
*/
|
||||
utun_config_t* parse_config(const char *filename);
|
||||
|
||||
/**
|
||||
* @brief Free configuration structure
|
||||
* @param config Configuration to free
|
||||
*/
|
||||
void free_config(utun_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief Print configuration for debugging
|
||||
* @param config Configuration to print
|
||||
*/
|
||||
void print_config(const utun_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief Update keys in configuration file
|
||||
* @param filename Path to configuration file
|
||||
* @param private_key_hex Private key in HEX format
|
||||
* @param public_key_hex Public key in HEX format
|
||||
* @return 0 on success, -1 on error
|
||||
*
|
||||
* Updates or adds my_private_key and my_public_key lines in [global] section
|
||||
*/
|
||||
int update_config_keys(const char *filename,
|
||||
const char *private_key_hex,
|
||||
const char *public_key_hex);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CONFIG_PARSER_H */
|
||||
@@ -0,0 +1,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 */
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -0,0 +1,463 @@
|
||||
/**
|
||||
* @file control_socket.c
|
||||
* @brief Control socket implementation for statistics and diagnostics
|
||||
*/
|
||||
|
||||
#include "control_socket.h"
|
||||
#include "connection.h"
|
||||
#include "etcp.h"
|
||||
#include "pkt_normalizer.h"
|
||||
#include "routing.h"
|
||||
#include "tun_if.h"
|
||||
#include "config_parser.h"
|
||||
#include "utun_state.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
// Internal control socket structure
|
||||
struct control_socket {
|
||||
int sockfd;
|
||||
struct sockaddr_in addr;
|
||||
uint16_t port;
|
||||
void *app_state; // Pointer to utun_state_t
|
||||
uint16_t sequence_counter;
|
||||
|
||||
// Statistics counters (for cumulative stats)
|
||||
uint64_t total_requests;
|
||||
uint64_t total_errors;
|
||||
|
||||
// Timestamps
|
||||
struct timeval start_time;
|
||||
};
|
||||
|
||||
// Forward declarations
|
||||
static int collect_statistics(control_socket_t *cs, control_stats_packet_t *stats);
|
||||
static int collect_system_status(control_socket_t *cs, system_status_t *status);
|
||||
static void get_etcp_stats(epkt_t *etcp, etcp_stats_t *etcp_stats);
|
||||
static void get_pkt_normalizer_stats(pkt_normalizer_pair *pair, pkt_normalizer_stats_t *pn_stats);
|
||||
static int handle_stats_request(control_socket_t *cs, struct sockaddr_in *client, uint16_t sequence);
|
||||
static int handle_status_request(control_socket_t *cs, struct sockaddr_in *client, uint16_t sequence);
|
||||
static int handle_reset_request(control_socket_t *cs, struct sockaddr_in *client, uint16_t sequence);
|
||||
static int send_error_response(control_socket_t *cs, struct sockaddr_in *client, uint16_t sequence, control_err_t error);
|
||||
static int send_response(control_socket_t *cs, struct sockaddr_in *client, void *data, size_t len);
|
||||
|
||||
// Create UDP control socket
|
||||
control_socket_t* control_socket_create(const char *ip, uint16_t port) {
|
||||
control_socket_t *cs = calloc(1, sizeof(control_socket_t));
|
||||
if (!cs) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Create UDP socket
|
||||
cs->sockfd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (cs->sockfd < 0) {
|
||||
perror("control_socket: socket");
|
||||
free(cs);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Set non-blocking
|
||||
int flags = fcntl(cs->sockfd, F_GETFL, 0);
|
||||
if (flags < 0) {
|
||||
perror("control_socket: fcntl GETFL");
|
||||
close(cs->sockfd);
|
||||
free(cs);
|
||||
return NULL;
|
||||
}
|
||||
if (fcntl(cs->sockfd, F_SETFL, flags | O_NONBLOCK) < 0) {
|
||||
perror("control_socket: fcntl SETFL");
|
||||
close(cs->sockfd);
|
||||
free(cs);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Enable address reuse
|
||||
int opt = 1;
|
||||
if (setsockopt(cs->sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
|
||||
perror("control_socket: setsockopt SO_REUSEADDR");
|
||||
// Continue anyway
|
||||
}
|
||||
|
||||
// Bind to address
|
||||
memset(&cs->addr, 0, sizeof(cs->addr));
|
||||
cs->addr.sin_family = AF_INET;
|
||||
cs->addr.sin_port = htons(port);
|
||||
|
||||
if (ip && ip[0] != '\0') {
|
||||
if (inet_pton(AF_INET, ip, &cs->addr.sin_addr) != 1) {
|
||||
fprintf(stderr, "control_socket: invalid IP address: %s\n", ip);
|
||||
close(cs->sockfd);
|
||||
free(cs);
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
cs->addr.sin_addr.s_addr = INADDR_ANY;
|
||||
}
|
||||
|
||||
if (bind(cs->sockfd, (struct sockaddr*)&cs->addr, sizeof(cs->addr)) < 0) {
|
||||
perror("control_socket: bind");
|
||||
close(cs->sockfd);
|
||||
free(cs);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cs->port = port;
|
||||
cs->sequence_counter = 0;
|
||||
cs->total_requests = 0;
|
||||
cs->total_errors = 0;
|
||||
gettimeofday(&cs->start_time, NULL);
|
||||
|
||||
printf("[CONTROL] Control socket listening on %s:%u\n",
|
||||
ip ? ip : "0.0.0.0", port);
|
||||
|
||||
return cs;
|
||||
}
|
||||
|
||||
// Destroy control socket
|
||||
void control_socket_destroy(control_socket_t *cs) {
|
||||
if (!cs) return;
|
||||
|
||||
if (cs->sockfd >= 0) {
|
||||
close(cs->sockfd);
|
||||
}
|
||||
|
||||
free(cs);
|
||||
}
|
||||
|
||||
// Get socket file descriptor for polling
|
||||
int control_socket_get_fd(const control_socket_t *cs) {
|
||||
return cs ? cs->sockfd : -1;
|
||||
}
|
||||
|
||||
// Set application state pointer
|
||||
void control_socket_set_state(control_socket_t *cs, void *state) {
|
||||
if (cs) {
|
||||
cs->app_state = state;
|
||||
}
|
||||
}
|
||||
|
||||
// Process incoming requests
|
||||
int control_socket_process(control_socket_t *cs, void *state) {
|
||||
if (!cs || !state) return -1;
|
||||
|
||||
// Use provided state or stored state
|
||||
if (state) {
|
||||
cs->app_state = state;
|
||||
}
|
||||
|
||||
struct sockaddr_in client_addr;
|
||||
socklen_t addr_len = sizeof(client_addr);
|
||||
control_request_packet_t request;
|
||||
|
||||
// Receive request
|
||||
ssize_t n = recvfrom(cs->sockfd, &request, sizeof(request), 0,
|
||||
(struct sockaddr*)&client_addr, &addr_len);
|
||||
|
||||
if (n < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
return 0; // No data available
|
||||
}
|
||||
perror("control_socket: recvfrom");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (n != sizeof(request)) {
|
||||
fprintf(stderr, "control_socket: invalid request size: %zd\n", n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cs->total_requests++;
|
||||
|
||||
// Handle request based on command
|
||||
switch (request.command) {
|
||||
case CONTROL_CMD_GET_STATS:
|
||||
return handle_stats_request(cs, &client_addr, request.sequence);
|
||||
|
||||
case CONTROL_CMD_GET_STATUS:
|
||||
return handle_status_request(cs, &client_addr, request.sequence);
|
||||
|
||||
case CONTROL_CMD_RESET_STATS:
|
||||
return handle_reset_request(cs, &client_addr, request.sequence);
|
||||
|
||||
default:
|
||||
return send_error_response(cs, &client_addr, request.sequence,
|
||||
CONTROL_ERR_INVALID_CMD);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle statistics request
|
||||
static int handle_stats_request(control_socket_t *cs, struct sockaddr_in *client,
|
||||
uint16_t sequence) {
|
||||
control_stats_packet_t stats_packet = {0};
|
||||
|
||||
// Fill response header
|
||||
stats_packet.response_type = CONTROL_RESP_STATS;
|
||||
stats_packet.error_code = CONTROL_ERR_NONE;
|
||||
stats_packet.sequence = sequence;
|
||||
|
||||
// Get current timestamp
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
stats_packet.timestamp = (uint64_t)tv.tv_sec * 1000000 + tv.tv_usec;
|
||||
|
||||
// Collect statistics from all modules
|
||||
if (collect_statistics(cs, &stats_packet) < 0) {
|
||||
return send_error_response(cs, client, sequence, CONTROL_ERR_INTERNAL);
|
||||
}
|
||||
|
||||
// Send response
|
||||
return send_response(cs, client, &stats_packet, sizeof(stats_packet));
|
||||
}
|
||||
|
||||
// Handle status request
|
||||
static int handle_status_request(control_socket_t *cs, struct sockaddr_in *client,
|
||||
uint16_t sequence) {
|
||||
control_status_packet_t status_packet = {0};
|
||||
|
||||
status_packet.response_type = CONTROL_RESP_STATUS;
|
||||
status_packet.error_code = CONTROL_ERR_NONE;
|
||||
status_packet.sequence = sequence;
|
||||
|
||||
// Collect system status
|
||||
if (collect_system_status(cs, &status_packet.system) < 0) {
|
||||
status_packet.system.uptime_seconds = 0;
|
||||
}
|
||||
|
||||
// Set running flag
|
||||
if (cs->app_state) {
|
||||
// Assume utun_state_t has 'running' field at same offset
|
||||
// We'll fix this when integrating with utun.c
|
||||
status_packet.running = 1;
|
||||
}
|
||||
|
||||
snprintf(status_packet.status_msg, sizeof(status_packet.status_msg),
|
||||
"utun control socket active on port %u", cs->port);
|
||||
|
||||
return send_response(cs, client, &status_packet, sizeof(status_packet));
|
||||
}
|
||||
|
||||
// Handle reset statistics request
|
||||
static int handle_reset_request(control_socket_t *cs, struct sockaddr_in *client,
|
||||
uint16_t sequence) {
|
||||
// TODO: Implement statistics reset
|
||||
// For now, just send success response
|
||||
|
||||
control_status_packet_t response = {0};
|
||||
response.response_type = CONTROL_RESP_STATUS;
|
||||
response.error_code = CONTROL_ERR_NONE;
|
||||
response.sequence = sequence;
|
||||
|
||||
// Get basic system status
|
||||
collect_system_status(cs, &response.system);
|
||||
|
||||
snprintf(response.status_msg, sizeof(response.status_msg),
|
||||
"Statistics reset requested (not implemented)");
|
||||
|
||||
return send_response(cs, client, &response, sizeof(response));
|
||||
}
|
||||
|
||||
// Send error response
|
||||
static int send_error_response(control_socket_t *cs, struct sockaddr_in *client,
|
||||
uint16_t sequence, control_err_t error) {
|
||||
control_status_packet_t response = {0};
|
||||
|
||||
response.response_type = CONTROL_RESP_ERROR;
|
||||
response.error_code = error;
|
||||
response.sequence = sequence;
|
||||
|
||||
const char *error_msg = "Unknown error";
|
||||
switch (error) {
|
||||
case CONTROL_ERR_INVALID_CMD:
|
||||
error_msg = "Invalid command";
|
||||
break;
|
||||
case CONTROL_ERR_INTERNAL:
|
||||
error_msg = "Internal error";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
snprintf(response.status_msg, sizeof(response.status_msg), "%s", error_msg);
|
||||
|
||||
cs->total_errors++;
|
||||
|
||||
return send_response(cs, client, &response, sizeof(response));
|
||||
}
|
||||
|
||||
// Send response to client
|
||||
static int send_response(control_socket_t *cs, struct sockaddr_in *client,
|
||||
void *data, size_t len) {
|
||||
ssize_t sent = sendto(cs->sockfd, data, len, 0,
|
||||
(struct sockaddr*)client, sizeof(*client));
|
||||
|
||||
if (sent != (ssize_t)len) {
|
||||
perror("control_socket: sendto");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Collect statistics from all modules
|
||||
static int collect_statistics(control_socket_t *cs, control_stats_packet_t *stats) {
|
||||
if (!cs->app_state) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
utun_state_t *state = (utun_state_t*)cs->app_state;
|
||||
|
||||
// Zero out stats
|
||||
memset(&stats->pkt_normalizer, 0, sizeof(stats->pkt_normalizer));
|
||||
memset(&stats->etcp, 0, sizeof(stats->etcp));
|
||||
memset(&stats->routing, 0, sizeof(stats->routing));
|
||||
memset(&stats->tun, 0, sizeof(stats->tun));
|
||||
stats->conn_count = 0;
|
||||
|
||||
// Collect connection statistics
|
||||
if (state->connections && state->connection_count > 0) {
|
||||
int max_conn = state->connection_count;
|
||||
if (max_conn > MAX_CONN_STATS) max_conn = MAX_CONN_STATS;
|
||||
|
||||
for (int i = 0; i < max_conn; i++) {
|
||||
conn_handle_t *conn = state->connections[i];
|
||||
if (!conn) continue;
|
||||
|
||||
conn_stats_t conn_stats;
|
||||
if (conn_get_stats(conn, &conn_stats) == 0) {
|
||||
// Aggregate connection stats
|
||||
stats->etcp.bytes_sent_total += conn_stats.bytes_sent;
|
||||
stats->etcp.bytes_received_total += conn_stats.bytes_received;
|
||||
stats->etcp.retransmissions += conn_stats.retransmissions;
|
||||
stats->pkt_normalizer.fragments_assembled += conn_stats.fragments_assembled;
|
||||
// Note: etcp and pkt_normalizer stats are aggregated across connections
|
||||
// Additional per-connection stats could be stored in conn_stats_entry_t
|
||||
}
|
||||
|
||||
// Fill connection entry (if we have array)
|
||||
if (i < MAX_CONN_STATS) {
|
||||
if (state->config && state->config->connections) {
|
||||
strncpy(stats->connections[i].name, state->config->connections[i].name,
|
||||
sizeof(stats->connections[i].name) - 1);
|
||||
stats->connections[i].name[sizeof(stats->connections[i].name) - 1] = '\0';
|
||||
} else {
|
||||
snprintf(stats->connections[i].name, sizeof(stats->connections[i].name), "conn%d", i);
|
||||
}
|
||||
stats->connections[i].bytes_sent = conn_stats.bytes_sent;
|
||||
stats->connections[i].bytes_received = conn_stats.bytes_received;
|
||||
stats->connections[i].packets_sent = conn_stats.packets_sent;
|
||||
stats->connections[i].packets_received = conn_stats.packets_received;
|
||||
stats->connections[i].retransmissions = conn_stats.retransmissions;
|
||||
stats->connections[i].fragments_assembled = conn_stats.fragments_assembled;
|
||||
stats->connections[i].current_rtt_ms = conn_stats.current_rtt_ms;
|
||||
stats->connections[i].jitter_ms = conn_stats.jitter_ms;
|
||||
stats->connections[i].is_active = 1;
|
||||
}
|
||||
}
|
||||
stats->conn_count = max_conn;
|
||||
}
|
||||
|
||||
// Collect routing statistics
|
||||
if (state->routing_table) {
|
||||
routing_get_stats(state->routing_table, &stats->routing);
|
||||
}
|
||||
|
||||
// Collect TUN statistics
|
||||
stats->tun.bytes_read = state->tun.bytes_read;
|
||||
stats->tun.bytes_written = state->tun.bytes_written;
|
||||
stats->tun.packets_read = state->tun.packets_read;
|
||||
stats->tun.packets_written = state->tun.packets_written;
|
||||
stats->tun.read_errors = state->tun.read_errors;
|
||||
stats->tun.write_errors = state->tun.write_errors;
|
||||
strncpy(stats->tun.ifname, state->tun.ifname, sizeof(stats->tun.ifname) - 1);
|
||||
stats->tun.ifname[sizeof(stats->tun.ifname) - 1] = '\0';
|
||||
strncpy(stats->tun.ip_addr, state->tun.ip_addr, sizeof(stats->tun.ip_addr) - 1);
|
||||
stats->tun.ip_addr[sizeof(stats->tun.ip_addr) - 1] = '\0';
|
||||
|
||||
// Collect system status
|
||||
return collect_system_status(cs, &stats->system);
|
||||
}
|
||||
|
||||
// Collect system status
|
||||
static int collect_system_status(control_socket_t *cs, system_status_t *status) {
|
||||
if (!cs) return -1;
|
||||
|
||||
memset(status, 0, sizeof(*status));
|
||||
|
||||
// Calculate uptime
|
||||
struct timeval now;
|
||||
gettimeofday(&now, NULL);
|
||||
status->uptime_seconds = now.tv_sec - cs->start_time.tv_sec;
|
||||
|
||||
// Get memory usage (simplified)
|
||||
FILE *meminfo = fopen("/proc/self/statm", "r");
|
||||
if (meminfo) {
|
||||
unsigned long pages;
|
||||
if (fscanf(meminfo, "%lu", &pages) == 1) {
|
||||
status->memory_usage_kb = pages * 4; // Assume 4KB pages
|
||||
}
|
||||
fclose(meminfo);
|
||||
}
|
||||
|
||||
// CPU usage (placeholder)
|
||||
status->cpu_usage_percent = 0;
|
||||
|
||||
// Version info
|
||||
snprintf(status->version, sizeof(status->version), "utun v0.1");
|
||||
|
||||
// Control port
|
||||
status->control_port = cs->port;
|
||||
|
||||
// Running flag and connection count will be set by caller
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get ETCP statistics
|
||||
static void get_etcp_stats(epkt_t *etcp, etcp_stats_t *etcp_stats) {
|
||||
if (!etcp || !etcp_stats) return;
|
||||
|
||||
memset(etcp_stats, 0, sizeof(*etcp_stats));
|
||||
|
||||
// Use etcp_get_stats if available
|
||||
// etcp_get_stats(etcp, &retransmissions, ...);
|
||||
|
||||
// Direct access to epkt fields (assuming struct is visible)
|
||||
etcp_stats->retransmissions = etcp->retransmissions_count;
|
||||
etcp_stats->total_packets_sent = etcp->total_packets_sent;
|
||||
etcp_stats->unique_packets_sent = etcp->unique_packets_sent;
|
||||
etcp_stats->bytes_sent_total = etcp->bytes_sent_total;
|
||||
etcp_stats->bytes_received_total = etcp->bytes_received_total;
|
||||
etcp_stats->ack_packets_count = etcp->ack_packets_count;
|
||||
etcp_stats->control_packets_count = etcp->control_packets_count;
|
||||
etcp_stats->current_rtt = etcp->rtt_avg_10;
|
||||
etcp_stats->jitter = etcp->jitter;
|
||||
etcp_stats->window_size = etcp->window_size;
|
||||
etcp_stats->unacked_bytes = etcp->unacked_bytes;
|
||||
}
|
||||
|
||||
// Get packet normalizer statistics
|
||||
static void get_pkt_normalizer_stats(pkt_normalizer_pair *pair,
|
||||
pkt_normalizer_stats_t *pn_stats) {
|
||||
if (!pair || !pn_stats) return;
|
||||
|
||||
memset(pn_stats, 0, sizeof(*pn_stats));
|
||||
|
||||
if (pair->packer) {
|
||||
pn_stats->packer_errors = pkt_normalizer_get_error_count(pair->packer);
|
||||
}
|
||||
|
||||
if (pair->unpacker) {
|
||||
pn_stats->unpacker_errors = pkt_normalizer_get_error_count(pair->unpacker);
|
||||
// Fragments assembled count might need to be tracked separately
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* @file control_socket.h
|
||||
* @brief Control socket for statistics and diagnostics over UDP
|
||||
* @details Provides remote monitoring of utun VPN tunnel statistics
|
||||
*/
|
||||
|
||||
#ifndef CONTROL_SOCKET_H
|
||||
#define CONTROL_SOCKET_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// Forward declarations for application state
|
||||
struct utun_state;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Maximum connections for statistics
|
||||
#define MAX_CONN_STATS 16
|
||||
#define MAX_ROUTE_STATS 100
|
||||
|
||||
// Control socket command types
|
||||
typedef enum {
|
||||
CONTROL_CMD_GET_STATS = 1, // Request statistics
|
||||
CONTROL_CMD_RESET_STATS = 2, // Reset statistics counters
|
||||
CONTROL_CMD_GET_STATUS = 3, // Request status information
|
||||
} control_cmd_t;
|
||||
|
||||
// Response types
|
||||
typedef enum {
|
||||
CONTROL_RESP_STATS = 1, // Statistics response
|
||||
CONTROL_RESP_STATUS = 2, // Status response
|
||||
CONTROL_RESP_ERROR = 0xFF, // Error response
|
||||
} control_resp_t;
|
||||
|
||||
// Error codes
|
||||
typedef enum {
|
||||
CONTROL_ERR_NONE = 0,
|
||||
CONTROL_ERR_INVALID_CMD = 1,
|
||||
CONTROL_ERR_INTERNAL = 2,
|
||||
} control_err_t;
|
||||
|
||||
// Packet normalizer statistics
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint32_t packer_errors; // Packer error count
|
||||
uint32_t unpacker_errors; // Unpacker error count
|
||||
uint32_t fragments_assembled; // Fragments assembled successfully
|
||||
uint32_t service_packets; // Service packets processed
|
||||
} pkt_normalizer_stats_t;
|
||||
|
||||
// ETCP statistics
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint32_t retransmissions; // Retransmission count
|
||||
uint32_t total_packets_sent; // Total packets sent (including retransmissions)
|
||||
uint32_t unique_packets_sent; // Unique packets sent (excluding retransmissions)
|
||||
uint32_t bytes_sent_total; // Total bytes sent
|
||||
uint32_t bytes_received_total;// Total bytes received
|
||||
uint32_t ack_packets_count; // ACK packets sent
|
||||
uint32_t control_packets_count; // Control packets sent
|
||||
uint16_t current_rtt; // Current RTT (0.1ms units)
|
||||
uint16_t jitter; // Jitter (0.1ms units)
|
||||
uint32_t window_size; // Current window size (bytes)
|
||||
uint32_t unacked_bytes; // Unacknowledged bytes in flight
|
||||
} etcp_stats_t;
|
||||
|
||||
// Connection statistics
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint64_t bytes_sent;
|
||||
uint64_t bytes_received;
|
||||
uint32_t packets_sent;
|
||||
uint32_t packets_received;
|
||||
uint32_t retransmissions;
|
||||
uint32_t fragments_assembled;
|
||||
uint16_t current_rtt_ms;
|
||||
uint16_t jitter_ms;
|
||||
uint8_t is_active; // 1 if connection is active
|
||||
char name[32]; // Connection name from config
|
||||
} conn_stats_entry_t;
|
||||
|
||||
// Routing statistics
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint32_t total_routes; // Total routes in table
|
||||
uint32_t static_routes; // Static routes count
|
||||
uint32_t dynamic_routes; // Dynamic routes count
|
||||
uint32_t local_routes; // Local routes count
|
||||
uint32_t learned_routes; // Learned routes count
|
||||
uint32_t lookup_count; // Total lookups performed
|
||||
uint32_t hit_count; // Successful lookups
|
||||
uint32_t miss_count; // Failed lookups (no route)
|
||||
} routing_stats_t;
|
||||
|
||||
// TUN interface statistics
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint64_t bytes_read; // Bytes read from TUN
|
||||
uint64_t bytes_written; // Bytes written to TUN
|
||||
uint32_t packets_read; // Packets read from TUN
|
||||
uint32_t packets_written; // Packets written to TUN
|
||||
uint32_t read_errors; // Read errors
|
||||
uint32_t write_errors; // Write errors
|
||||
char ifname[16]; // Interface name
|
||||
char ip_addr[32]; // IP address with prefix
|
||||
} tun_stats_t;
|
||||
|
||||
// System status
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint32_t uptime_seconds; // Seconds since startup
|
||||
uint32_t memory_usage_kb; // Memory usage in KB (approximate)
|
||||
uint16_t cpu_usage_percent; // CPU usage percentage (0-100)
|
||||
uint8_t running; // 1 if main loop is running
|
||||
uint8_t connection_count; // Number of active connections
|
||||
uint16_t control_port; // Control socket port
|
||||
char version[32]; // Version string
|
||||
} system_status_t;
|
||||
|
||||
// Complete statistics packet
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint8_t response_type; // CONTROL_RESP_STATS
|
||||
uint8_t error_code; // CONTROL_ERR_NONE if success
|
||||
uint16_t sequence; // Sequence number (echoed from request)
|
||||
|
||||
// Timestamps
|
||||
uint64_t timestamp; // Unix timestamp in microseconds
|
||||
|
||||
// Module statistics
|
||||
pkt_normalizer_stats_t pkt_normalizer;
|
||||
etcp_stats_t etcp;
|
||||
routing_stats_t routing;
|
||||
tun_stats_t tun;
|
||||
|
||||
// Connection statistics (variable length, up to MAX_CONN_STATS)
|
||||
uint8_t conn_count; // Number of connection entries
|
||||
conn_stats_entry_t connections[MAX_CONN_STATS];
|
||||
|
||||
// System status
|
||||
system_status_t system;
|
||||
|
||||
// Reserved for future expansion
|
||||
uint8_t reserved[64];
|
||||
} control_stats_packet_t;
|
||||
|
||||
// Status response packet
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint8_t response_type; // CONTROL_RESP_STATUS
|
||||
uint8_t error_code; // CONTROL_ERR_NONE if success
|
||||
uint16_t sequence; // Sequence number
|
||||
|
||||
system_status_t system;
|
||||
uint8_t running; // Main loop running flag
|
||||
char status_msg[128]; // Human-readable status message
|
||||
} control_status_packet_t;
|
||||
|
||||
// Request packet (from client)
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint8_t command; // CONTROL_CMD_*
|
||||
uint16_t sequence; // Sequence number for response matching
|
||||
uint8_t reserved; // Reserved, must be 0
|
||||
} control_request_packet_t;
|
||||
|
||||
// Control socket handle (opaque)
|
||||
typedef struct control_socket control_socket_t;
|
||||
|
||||
/**
|
||||
* @brief Create and initialize control socket
|
||||
* @param ip IP address to bind to (NULL for any)
|
||||
* @param port UDP port to listen on
|
||||
* @return Control socket handle or NULL on error
|
||||
*/
|
||||
control_socket_t* control_socket_create(const char *ip, uint16_t port);
|
||||
|
||||
/**
|
||||
* @brief Destroy control socket and free resources
|
||||
* @param cs Control socket handle
|
||||
*/
|
||||
void control_socket_destroy(control_socket_t *cs);
|
||||
|
||||
/**
|
||||
* @brief Get file descriptor for polling
|
||||
* @param cs Control socket handle
|
||||
* @return Socket file descriptor or -1 if invalid
|
||||
*/
|
||||
int control_socket_get_fd(const control_socket_t *cs);
|
||||
|
||||
/**
|
||||
* @brief Process incoming control socket requests
|
||||
* @param cs Control socket handle
|
||||
* @param state Application state (for collecting statistics)
|
||||
* @return 0 on success, -1 on error
|
||||
*
|
||||
* This function should be called when the socket is readable.
|
||||
* It will handle incoming requests and send responses.
|
||||
*/
|
||||
int control_socket_process(control_socket_t *cs, void *state);
|
||||
|
||||
/**
|
||||
* @brief Set application state pointer for statistics collection
|
||||
* @param cs Control socket handle
|
||||
* @param state Application state (utun_state_t*)
|
||||
*/
|
||||
void control_socket_set_state(control_socket_t *cs, void *state);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CONTROL_SOCKET_H */
|
||||
+1245
File diff suppressed because it is too large.
Load diff
+234
@@ -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
@@ -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
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 */
|
||||
@@ -0,0 +1,6 @@
|
||||
// settings.c
|
||||
#include "settings.h"
|
||||
|
||||
settings_t settings = {
|
||||
.max_fragment_size = 1400
|
||||
};
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -0,0 +1,238 @@
|
||||
// tun_if.c - TUN interface management implementation
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "tun_if.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <linux/if.h>
|
||||
#include <linux/if_tun.h>
|
||||
#include <errno.h>
|
||||
|
||||
// Create TUN device
|
||||
static int create_tun_device(char *ifname, size_t ifname_len) {
|
||||
struct ifreq ifr;
|
||||
int fd;
|
||||
|
||||
fd = open("/dev/net/tun", O_RDWR);
|
||||
if (fd < 0) {
|
||||
perror("open /dev/net/tun");
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&ifr, 0, sizeof(ifr));
|
||||
ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
|
||||
|
||||
if (ifname && ifname_len > 0 && ifname[0] != '\0') {
|
||||
strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
|
||||
}
|
||||
|
||||
if (ioctl(fd, TUNSETIFF, &ifr) < 0) {
|
||||
perror("ioctl TUNSETIFF");
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ifname && ifname_len > 0) {
|
||||
strncpy(ifname, ifr.ifr_name, ifname_len - 1);
|
||||
ifname[ifname_len - 1] = '\0';
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
// Run system command
|
||||
static int run_command(const char *cmd) {
|
||||
int ret = system(cmd);
|
||||
if (ret == -1) {
|
||||
perror("system");
|
||||
return -1;
|
||||
}
|
||||
if (WIFEXITED(ret) && WEXITSTATUS(ret) != 0) {
|
||||
fprintf(stderr, "Command failed: %s\n", cmd);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Parse IP address and mask
|
||||
static int parse_ip_mask(const char *ip_addr, char *ip, size_t ip_len, int *mask) {
|
||||
if (!ip_addr || !ip || !mask) return -1;
|
||||
|
||||
char *slash = strchr(ip_addr, '/');
|
||||
if (!slash) {
|
||||
// Default mask /32
|
||||
*mask = 32;
|
||||
strncpy(ip, ip_addr, ip_len - 1);
|
||||
ip[ip_len - 1] = '\0';
|
||||
} else {
|
||||
size_t ip_size = slash - ip_addr;
|
||||
if (ip_size >= ip_len) return -1;
|
||||
strncpy(ip, ip_addr, ip_size);
|
||||
ip[ip_size] = '\0';
|
||||
|
||||
char *endptr;
|
||||
long mask_val = strtol(slash + 1, &endptr, 10);
|
||||
if (*endptr != '\0' || mask_val < 0 || mask_val > 32) return -1;
|
||||
*mask = (int)mask_val;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tun_create(tun_config_t *config) {
|
||||
if (!config) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create TUN device
|
||||
int fd = create_tun_device(config->ifname, sizeof(config->ifname));
|
||||
if (fd < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
config->fd = fd;
|
||||
|
||||
// Configure IP if specified
|
||||
if (config->ip_addr[0] != '\0') {
|
||||
if (tun_set_ip(config->ifname, config->ip_addr) < 0) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Set MTU if specified
|
||||
if (config->mtu > 0) {
|
||||
if (tun_set_mtu(config->ifname, config->mtu) < 0) {
|
||||
// Non-fatal error, just warn
|
||||
fprintf(stderr, "Warning: failed to set MTU on %s\n", config->ifname);
|
||||
}
|
||||
}
|
||||
|
||||
// Bring interface up
|
||||
if (tun_set_up(config->ifname) < 0) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
config->is_up = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tun_set_ip(const char *ifname, const char *ip_addr) {
|
||||
if (!ifname || !ip_addr) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
char ip[64];
|
||||
int mask;
|
||||
if (parse_ip_mask(ip_addr, ip, sizeof(ip), &mask) < 0) {
|
||||
fprintf(stderr, "Invalid IP address format: %s\n", ip_addr);
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
char cmd[256];
|
||||
snprintf(cmd, sizeof(cmd), "ip addr add %s dev %s", ip_addr, ifname);
|
||||
if (run_command(cmd) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tun_set_up(const char *ifname) {
|
||||
if (!ifname) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
char cmd[256];
|
||||
snprintf(cmd, sizeof(cmd), "ip link set %s up", ifname);
|
||||
if (run_command(cmd) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tun_set_mtu(const char *ifname, int mtu) {
|
||||
if (!ifname || mtu <= 0) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
char cmd[256];
|
||||
snprintf(cmd, sizeof(cmd), "ip link set %s mtu %d", ifname, mtu);
|
||||
if (run_command(cmd) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t tun_read(int fd, uint8_t *buffer, size_t size) {
|
||||
if (fd < 0 || !buffer || size == 0) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t nread = read(fd, buffer, size);
|
||||
if (nread < 0) {
|
||||
perror("tun_read");
|
||||
}
|
||||
|
||||
return nread;
|
||||
}
|
||||
|
||||
ssize_t tun_write(int fd, const uint8_t *buffer, size_t size) {
|
||||
if (fd < 0 || !buffer || size == 0) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t nwritten = write(fd, buffer, size);
|
||||
if (nwritten < 0) {
|
||||
perror("tun_write");
|
||||
}
|
||||
|
||||
return nwritten;
|
||||
}
|
||||
|
||||
void tun_close(tun_config_t *config) {
|
||||
if (!config) return;
|
||||
|
||||
if (config->fd >= 0) {
|
||||
close(config->fd);
|
||||
config->fd = -1;
|
||||
}
|
||||
|
||||
config->is_up = 0;
|
||||
}
|
||||
|
||||
int tun_get_config(const char *ifname, tun_config_t *config) {
|
||||
if (!ifname || !config) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// TODO: Implement reading current interface configuration
|
||||
// This would require parsing ip addr show output
|
||||
|
||||
memset(config, 0, sizeof(*config));
|
||||
strncpy(config->ifname, ifname, sizeof(config->ifname) - 1);
|
||||
config->fd = -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// tun_if.h - TUN interface management for utun
|
||||
#ifndef TUN_IF_H
|
||||
#define TUN_IF_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// TUN interface configuration
|
||||
typedef struct {
|
||||
char ifname[16]; // Interface name (e.g., "tun12")
|
||||
char ip_addr[64]; // IP address with mask (e.g., "10.0.0.1/24")
|
||||
int mtu; // MTU size
|
||||
int fd; // File descriptor
|
||||
uint8_t is_up; // 1 if interface is up
|
||||
// Statistics
|
||||
uint64_t bytes_read; // Bytes read from TUN
|
||||
uint64_t bytes_written; // Bytes written to TUN
|
||||
uint32_t packets_read; // Packets read from TUN
|
||||
uint32_t packets_written; // Packets written to TUN
|
||||
uint32_t read_errors; // Read errors
|
||||
uint32_t write_errors; // Write errors
|
||||
} tun_config_t;
|
||||
|
||||
/**
|
||||
* @brief Create and configure TUN interface
|
||||
* @param config TUN configuration (ifname can be empty for auto)
|
||||
* @return 0 on success, -1 on error
|
||||
*/
|
||||
int tun_create(tun_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief Configure IP address on TUN interface
|
||||
* @param ifname Interface name
|
||||
* @param ip_addr IP address with mask (e.g., "10.0.0.1/24")
|
||||
* @return 0 on success, -1 on error
|
||||
*/
|
||||
int tun_set_ip(const char *ifname, const char *ip_addr);
|
||||
|
||||
/**
|
||||
* @brief Bring TUN interface up
|
||||
* @param ifname Interface name
|
||||
* @return 0 on success, -1 on error
|
||||
*/
|
||||
int tun_set_up(const char *ifname);
|
||||
|
||||
/**
|
||||
* @brief Set MTU on TUN interface
|
||||
* @param ifname Interface name
|
||||
* @param mtu MTU value
|
||||
* @return 0 on success, -1 on error
|
||||
*/
|
||||
int tun_set_mtu(const char *ifname, int mtu);
|
||||
|
||||
/**
|
||||
* @brief Read packet from TUN interface
|
||||
* @param fd TUN file descriptor
|
||||
* @param buffer Buffer to store packet
|
||||
* @param size Buffer size
|
||||
* @return Number of bytes read, -1 on error
|
||||
*/
|
||||
ssize_t tun_read(int fd, uint8_t *buffer, size_t size);
|
||||
|
||||
/**
|
||||
* @brief Write packet to TUN interface
|
||||
* @param fd TUN file descriptor
|
||||
* @param buffer Packet data
|
||||
* @param size Packet size
|
||||
* @return Number of bytes written, -1 on error
|
||||
*/
|
||||
ssize_t tun_write(int fd, const uint8_t *buffer, size_t size);
|
||||
|
||||
/**
|
||||
* @brief Close TUN interface
|
||||
* @param config TUN configuration
|
||||
*/
|
||||
void tun_close(tun_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief Get current TUN configuration
|
||||
* @param ifname Interface name
|
||||
* @param config Output configuration
|
||||
* @return 0 on success, -1 on error
|
||||
*/
|
||||
int tun_get_config(const char *ifname, tun_config_t *config);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* TUN_IF_H */
|
||||
+433
@@ -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
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
Reference in new issue
Block a user