- Добавлен platform_compat.c с кроссплатформенной генерацией случайных чисел - Исправлена генерация ключей и node_id на Windows (вместо /dev/urandom) - PID файл отключен по умолчанию на Windows - Добавлено двойное логирование: файл utun.log + консоль - Добавлен манифест Windows для запроса прав администратора - Исправлено завершение программы при ошибках отправки (Network unreachable) - TUN инициализация включена по умолчанию - Исправлен main loop (instance->running = 1)
390 lines
11 KiB
C
390 lines
11 KiB
C
/**
|
|
* Debug module
|
|
*/
|
|
|
|
#include "debug_config.h"
|
|
#include "platform_compat.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <ctype.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <time.h>
|
|
|
|
void log_dump(const char* prefix, const uint8_t* data, size_t len) {
|
|
// Build packet data as hex string for single-line output
|
|
char hex_buf[513]; // 256 bytes * 2 chars + 1 for null terminator
|
|
size_t hex_len = 0;
|
|
size_t show_len = (len > 128) ? 128 : len; // Show max 128 bytes
|
|
|
|
for (size_t i = 0; i < show_len && hex_len < 512 - 3; i++) {
|
|
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "%02x", data[i]);
|
|
if (i < show_len - 1 && (i + 1) % 32 == 0) { // Add space every 32 bytes
|
|
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, " ");
|
|
}
|
|
}
|
|
|
|
if (len > 128) {
|
|
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "...");
|
|
}
|
|
|
|
// Single-line debug output with packet info
|
|
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "%s: len=%zu hex=%s", prefix, len, hex_buf);
|
|
|
|
// Additional debug info for first few bytes
|
|
// if (len >= 2) {
|
|
// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "%s: first_bytes=%02x%02x last_bytes=%02x%02x",
|
|
// prefix, data[0], data[1], data[len-2], data[len-1]);
|
|
// }
|
|
}
|
|
|
|
|
|
size_t addr_to_string(const struct sockaddr_storage *addr, char *buf, size_t buflen) {
|
|
if (!buf || buflen == 0)
|
|
return 0;
|
|
|
|
buf[0] = '\0'; // всегда валидная строка
|
|
|
|
char ip[INET6_ADDRSTRLEN];
|
|
uint16_t port;
|
|
int n;
|
|
|
|
if (addr->ss_family == AF_INET) {
|
|
const struct sockaddr_in *a = (const struct sockaddr_in *)addr;
|
|
inet_ntop(AF_INET, &a->sin_addr, ip, sizeof(ip));
|
|
port = ntohs(a->sin_port);
|
|
|
|
n = snprintf(buf, buflen, "%s:%u", ip, port);
|
|
|
|
} else if (addr->ss_family == AF_INET6) {
|
|
const struct sockaddr_in6 *a = (const struct sockaddr_in6 *)addr;
|
|
inet_ntop(AF_INET6, &a->sin6_addr, ip, sizeof(ip));
|
|
port = ntohs(a->sin6_port);
|
|
|
|
n = snprintf(buf, buflen, "[%s]:%u", ip, port);
|
|
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
// snprintf гарантирует '\0', но на всякий случай:
|
|
buf[buflen - 1] = '\0';
|
|
|
|
// Возвращаем фактическую длину (как snprintf)
|
|
return (n < 0) ? 0 : (size_t)n;
|
|
}
|
|
|
|
|
|
/* Get category by name */
|
|
static debug_category_t get_category_by_name(const char* name) {
|
|
struct {
|
|
const char* n;
|
|
debug_category_t c;
|
|
} map[] = {
|
|
{"none", DEBUG_CATEGORY_NONE},
|
|
{"uasync", DEBUG_CATEGORY_UASYNC},
|
|
{"ll_queue", DEBUG_CATEGORY_LL_QUEUE},
|
|
{"connection", DEBUG_CATEGORY_CONNECTION},
|
|
{"etcp", DEBUG_CATEGORY_ETCP},
|
|
{"crypto", DEBUG_CATEGORY_CRYPTO},
|
|
{"memory", DEBUG_CATEGORY_MEMORY},
|
|
{"timing", DEBUG_CATEGORY_TIMING},
|
|
{"config", DEBUG_CATEGORY_CONFIG},
|
|
{"tun", DEBUG_CATEGORY_TUN},
|
|
{"routing", DEBUG_CATEGORY_ROUTING},
|
|
{"timers", DEBUG_CATEGORY_TIMERS},
|
|
{"all", DEBUG_CATEGORY_ALL},
|
|
{NULL, 0}
|
|
};
|
|
|
|
for (int i = 0; map[i].n; i++) {
|
|
if (strcasecmp(map[i].n, name) == 0) {
|
|
return map[i].c;
|
|
}
|
|
}
|
|
return DEBUG_CATEGORY_NONE;
|
|
}
|
|
|
|
/* Global debug configuration */
|
|
debug_config_t g_debug_config;
|
|
|
|
/* Initialize debug system with default settings */
|
|
void debug_config_init(void) {
|
|
g_debug_config.level = DEBUG_LEVEL_TRACE; // Global level
|
|
g_debug_config.categories = DEBUG_CATEGORY_ALL;
|
|
g_debug_config.timestamp_enabled = 1;
|
|
g_debug_config.function_name_enabled = 1;
|
|
g_debug_config.file_line_enabled = 1;
|
|
g_debug_config.output_file = NULL;
|
|
|
|
// Dual output defaults
|
|
g_debug_config.file_output = NULL; // No file by default
|
|
g_debug_config.file_level = DEBUG_LEVEL_TRACE; // File gets everything
|
|
g_debug_config.console_level = DEBUG_LEVEL_INFO; // Console gets INFO+
|
|
g_debug_config.console_enabled = 1; // Console enabled
|
|
}
|
|
|
|
/* Set debug level */
|
|
void debug_set_level(debug_level_t level) {
|
|
g_debug_config.level = level;
|
|
}
|
|
|
|
/* Enable/disable specific categories */
|
|
void debug_enable_category(debug_category_t category) {
|
|
g_debug_config.categories |= category;
|
|
}
|
|
|
|
void debug_disable_category(debug_category_t category) {
|
|
g_debug_config.categories &= ~category;
|
|
}
|
|
|
|
void debug_set_categories(debug_category_t categories) {
|
|
g_debug_config.categories = categories;
|
|
}
|
|
|
|
/* Set masks directly */
|
|
void debug_set_masks(debug_category_t categories, debug_level_t level) {
|
|
g_debug_config.categories = categories;
|
|
g_debug_config.level = level;
|
|
}
|
|
|
|
void debug_enable_function_name(int enable) {
|
|
g_debug_config.function_name_enabled = enable;
|
|
}
|
|
|
|
void debug_enable_file_line(int enable) {
|
|
g_debug_config.file_line_enabled = enable;
|
|
}
|
|
|
|
void debug_set_output_file(const char* file_path) {
|
|
if (g_debug_config.file_output) return;// уже установлен, кто первый того и тапки
|
|
FILE* new_file = fopen(file_path, "a");
|
|
if (new_file) {
|
|
g_debug_config.file_output = new_file;
|
|
g_debug_config.output_file = file_path;
|
|
}
|
|
}
|
|
|
|
/* Set console log level */
|
|
void debug_set_console_level(debug_level_t level) {
|
|
g_debug_config.console_level = level;
|
|
}
|
|
|
|
/* Set file log level */
|
|
void debug_set_file_level(debug_level_t level) {
|
|
g_debug_config.file_level = level;
|
|
}
|
|
|
|
/* Enable/disable console output */
|
|
void debug_enable_console(int enable) {
|
|
g_debug_config.console_enabled = enable;
|
|
}
|
|
|
|
/* Enable file output with optional truncation */
|
|
void debug_enable_file_output(const char* file_path, int truncate) {
|
|
if (!file_path) return;
|
|
|
|
// Close existing file if open
|
|
if (g_debug_config.file_output) {
|
|
fclose(g_debug_config.file_output);
|
|
g_debug_config.file_output = NULL;
|
|
}
|
|
|
|
// Open new file
|
|
const char* mode = truncate ? "w" : "a";
|
|
FILE* new_file = fopen(file_path, mode);
|
|
if (new_file) {
|
|
g_debug_config.file_output = new_file;
|
|
g_debug_config.output_file = file_path;
|
|
}
|
|
}
|
|
|
|
/* Disable file output */
|
|
void debug_disable_file_output(void) {
|
|
if (g_debug_config.file_output) {
|
|
fclose(g_debug_config.file_output);
|
|
g_debug_config.file_output = NULL;
|
|
g_debug_config.output_file = NULL;
|
|
}
|
|
}
|
|
|
|
/* Check if debug output should be shown for given level and category */
|
|
int debug_should_output(debug_level_t level, debug_category_t category) {
|
|
|
|
/* Check if category is enabled */
|
|
if (!(g_debug_config.categories & category)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Check if level is sufficient */
|
|
if (level > g_debug_config.level) {
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Get current debug level */
|
|
debug_level_t debug_get_level(void) {
|
|
return g_debug_config.level;
|
|
}
|
|
|
|
/* Get level name */
|
|
static const char* get_level_name(debug_level_t level) {
|
|
switch (level) {
|
|
case DEBUG_LEVEL_ERROR: return "ERROR";
|
|
case DEBUG_LEVEL_WARN: return "WARN";
|
|
case DEBUG_LEVEL_INFO: return "INFO";
|
|
case DEBUG_LEVEL_DEBUG: return "DEBUG";
|
|
case DEBUG_LEVEL_TRACE: return "TRACE";
|
|
default: return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
/* Format and output debug message */
|
|
void debug_output(debug_level_t level, debug_category_t category,
|
|
const char* function, const char* file, int line,
|
|
const char* format, ...) {
|
|
|
|
#define BUFFER_SIZE 4096
|
|
char buffer[BUFFER_SIZE];
|
|
int offset = 0;
|
|
size_t remaining = BUFFER_SIZE;
|
|
|
|
va_list args;
|
|
va_start(args, format);
|
|
|
|
/* Add timestamp with microseconds: hh:mm:ss-xxx.yyy */
|
|
struct timeval tv;
|
|
gettimeofday(&tv, NULL);
|
|
time_t tv_sec = (time_t)tv.tv_sec;
|
|
struct tm* tm_info = localtime(&tv_sec);
|
|
char time_str[32];
|
|
strftime(time_str, sizeof(time_str), "%H:%M:%S", tm_info);
|
|
offset += snprintf(buffer + offset, remaining, "[%s-%03ld.%03ld] ", time_str, tv.tv_usec / 1000, tv.tv_usec % 1000);
|
|
remaining = BUFFER_SIZE - offset;
|
|
|
|
/* Add level */
|
|
const char* level_name = get_level_name(level);
|
|
offset += snprintf(buffer + offset, remaining, "[%s] ", level_name);
|
|
remaining = BUFFER_SIZE - offset;
|
|
|
|
/* Add category */
|
|
offset += snprintf(buffer + offset, remaining, "[%llu] ", (unsigned long long)category);
|
|
remaining = BUFFER_SIZE - offset;
|
|
|
|
/* Add file:line if enabled */
|
|
if (g_debug_config.file_line_enabled && file) {
|
|
offset += snprintf(buffer + offset, remaining, "(%s:%d) ", file, line);
|
|
remaining = BUFFER_SIZE - offset;
|
|
}
|
|
|
|
/* Add function name if enabled */
|
|
if (g_debug_config.function_name_enabled && function) {
|
|
offset += snprintf(buffer + offset, remaining, "%s() ", function);
|
|
remaining = BUFFER_SIZE - offset;
|
|
}
|
|
|
|
/* Add the actual message */
|
|
offset += vsnprintf(buffer + offset, remaining, format, args);
|
|
remaining = BUFFER_SIZE - offset;
|
|
|
|
/* Add newline */
|
|
if (remaining > 1) {
|
|
buffer[offset] = '\n';
|
|
buffer[offset + 1] = '\0';
|
|
} else {
|
|
buffer[BUFFER_SIZE - 2] = '\n';
|
|
buffer[BUFFER_SIZE - 1] = '\0';
|
|
}
|
|
|
|
va_end(args);
|
|
|
|
/* Output to console if enabled and level is sufficient */
|
|
if (g_debug_config.console_enabled && level <= g_debug_config.console_level) {
|
|
fprintf(stdout, "%s", buffer);
|
|
fflush(stdout);
|
|
}
|
|
|
|
/* Output to file if open and level is sufficient */
|
|
if (g_debug_config.file_output && level <= g_debug_config.file_level) {
|
|
fprintf(g_debug_config.file_output, "%s", buffer);
|
|
fflush(g_debug_config.file_output);
|
|
}
|
|
#undef BUFFER_SIZE
|
|
}
|
|
|
|
/* Parse debug configuration from string */
|
|
int debug_parse_config(const char* config_string) {
|
|
if (!config_string || !*config_string) {
|
|
return 0;
|
|
}
|
|
|
|
char* str = strdup(config_string);
|
|
if (!str) {
|
|
return -1;
|
|
}
|
|
|
|
debug_category_t categories = 0;
|
|
debug_level_t level = DEBUG_LEVEL_NONE;
|
|
|
|
char* token = strtok(str, ";");
|
|
while (token) {
|
|
/* Trim leading spaces */
|
|
while (isspace(*token)) {
|
|
token++;
|
|
}
|
|
|
|
char* eq = strchr(token, '=');
|
|
if (!eq) {
|
|
free(str);
|
|
return -1;
|
|
}
|
|
|
|
*eq = '\0';
|
|
char* key = token;
|
|
char* value = eq + 1;
|
|
|
|
/* Trim leading spaces in value */
|
|
while (isspace(*value)) {
|
|
value++;
|
|
}
|
|
|
|
if (strcasecmp(key, "modules") == 0) {
|
|
char* mod_token = strtok(value, ",");
|
|
while (mod_token) {
|
|
/* Trim leading spaces */
|
|
while (isspace(*mod_token)) {
|
|
mod_token++;
|
|
}
|
|
|
|
debug_category_t cat = get_category_by_name(mod_token);
|
|
if (cat == DEBUG_CATEGORY_NONE) {
|
|
free(str);
|
|
return -1;
|
|
}
|
|
categories |= cat;
|
|
mod_token = strtok(NULL, ",");
|
|
}
|
|
} else if (strcasecmp(key, "level") == 0) {
|
|
int lev_num = atoi(value);
|
|
if (lev_num < DEBUG_LEVEL_NONE || lev_num > DEBUG_LEVEL_TRACE) {
|
|
free(str);
|
|
return -1;
|
|
}
|
|
level = (debug_level_t)lev_num;
|
|
} else {
|
|
free(str);
|
|
return -1;
|
|
}
|
|
|
|
token = strtok(NULL, ";");
|
|
}
|
|
|
|
debug_set_masks(categories, level);
|
|
|
|
free(str);
|
|
return 0;
|
|
}
|