/** * Debug module */ #include "debug_config.h" #include "platform_compat.h" #include "mem.h" #include #include #include #include #include #include #include void log_dump(const char* prefix, const uint8_t* data, size_t len) { char hex_buf[513] = {'\0'}; size_t hex_len = 0; size_t show_len = (len > 128) ? 128 : len; 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_DUMP, "%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]); // } } ip_str_t ip_to_str(const void *addr, int family) { ip_str_t result; result.str[0] = '\0'; if (!addr) { return result; } if (family == AF_INET) { inet_ntop(AF_INET, addr, result.str, 16); } else if (family == AF_INET6) { inet_ntop(AF_INET6, addr, result.str, 48); } return result; } static const struct { const char* name; debug_category_t category; } g_debug_categories[] = { {"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}, {"normalizer", DEBUG_CATEGORY_NORMALIZER}, {"bgp", DEBUG_CATEGORY_BGP}, {"socket", DEBUG_CATEGORY_SOCKET}, {"control", DEBUG_CATEGORY_CONTROL}, {"dump", DEBUG_CATEGORY_DUMP}, {"traffic", DEBUG_CATEGORY_TRAFFIC}, {"debug", DEBUG_CATEGORY_DEBUG}, {"general", DEBUG_CATEGORY_GENERAL}, {"all", DEBUG_CATEGORY_ALL}, {NULL, DEBUG_CATEGORY_NONE} }; static const struct { const char* name; debug_level_t level; } g_level_table[] = { {"none", DEBUG_LEVEL_NONE}, {"error", DEBUG_LEVEL_ERROR}, {"warn", DEBUG_LEVEL_WARN}, {"info", DEBUG_LEVEL_INFO}, {"debug", DEBUG_LEVEL_DEBUG}, {"trace", DEBUG_LEVEL_TRACE}, {NULL, DEBUG_LEVEL_NONE} }; debug_category_t get_category_by_name(const char* name) { if (!name) return DEBUG_CATEGORY_NONE; for (int i = 0; g_debug_categories[i].name; i++) { if (strcasecmp(g_debug_categories[i].name, name) == 0) { return g_debug_categories[i].category; } } return DEBUG_CATEGORY_NONE; } debug_level_t debug_level_from_name(const char* name) { if (!name) return DEBUG_LEVEL_NONE; for (int i = 0; g_level_table[i].name; i++) { if (strcasecmp(g_level_table[i].name, name) == 0) { return g_level_table[i].level; } } DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid debug level '%s' (valid: none,error,warn,info,debug,trace)", name); return DEBUG_LEVEL_INFO; /* fallback */ } void debug_set_category_level_by_name(const char* category_name, const char* level_name) { if (!category_name || !level_name) return; debug_category_t cat = get_category_by_name(category_name); if (cat == DEBUG_CATEGORY_NONE) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Unknown debug category '%s'", category_name); return; } debug_level_t lvl = debug_level_from_name(level_name); debug_set_category_level(cat, lvl); } int debug_apply_category_config(const char* category_name, const char* level_name) { if (!category_name || !level_name) return -1; debug_category_t cat = get_category_by_name(category_name); if (cat == DEBUG_CATEGORY_NONE) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Unknown debug category '%s'", category_name); return -1; } debug_level_t lvl = debug_level_from_name(level_name); debug_set_category_level(cat, lvl); DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Applied per-category debug: %s=%s", category_name, level_name); return 0; } void debug_apply_global_level(const char* level_name) { if (!level_name || !*level_name) return; debug_level_t level = debug_level_from_name(level_name); debug_set_level(level); DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Applied global debug_level: %s", level_name); } /* Get category name for logging */ const char* debug_get_category_name(debug_category_t category_idx) { switch (category_idx) { case DEBUG_CATEGORY_UASYNC: return "UASYNC"; case DEBUG_CATEGORY_LL_QUEUE: return "LL_QUEUE"; case DEBUG_CATEGORY_CONNECTION: return "CONNECTION"; case DEBUG_CATEGORY_ETCP: return "ETCP"; case DEBUG_CATEGORY_CRYPTO: return "CRYPTO"; case DEBUG_CATEGORY_MEMORY: return "MEMORY"; case DEBUG_CATEGORY_TIMING: return "TIMING"; case DEBUG_CATEGORY_CONFIG: return "CONFIG"; case DEBUG_CATEGORY_TUN: return "TUN"; case DEBUG_CATEGORY_ROUTING: return "ROUTING"; case DEBUG_CATEGORY_TIMERS: return "TIMERS"; case DEBUG_CATEGORY_NORMALIZER: return "NORMALIZER"; case DEBUG_CATEGORY_BGP: return "BGP"; case DEBUG_CATEGORY_SOCKET: return "SOCKET"; case DEBUG_CATEGORY_CONTROL: return "CONTROL"; case DEBUG_CATEGORY_DUMP: return "DUMP"; case DEBUG_CATEGORY_TRAFFIC: return "TRAFFIC"; case DEBUG_CATEGORY_DEBUG: return "DEBUG"; case DEBUG_CATEGORY_GENERAL: return "GENERAL"; case DEBUG_CATEGORY_NONE: return "NONE"; default: return "UNKNOWN"; } } /* 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_ERROR; // Global level: errors only by default for (int i = 0; i < DEBUG_CATEGORY_COUNT; i++) { g_debug_config.category_levels[i] = DEBUG_LEVEL_NONE; // 0 = disabled per category } 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; g_debug_config.console_level = level; } /* Set debug level for specific category (0 = disabled, otherwise uses that level) */ void debug_set_category_level(debug_category_t category_idx, debug_level_t level) { if (category_idx == DEBUG_CATEGORY_NONE || category_idx >= DEBUG_CATEGORY_COUNT) return; g_debug_config.category_levels[category_idx] = level; } /* Enable/disable specific categories (now sets level) */ void debug_enable_category(debug_category_t category_idx) { if (category_idx >= 0 && category_idx < DEBUG_CATEGORY_COUNT) { g_debug_config.category_levels[category_idx] = g_debug_config.level; } } void debug_disable_category(debug_category_t category_idx) { if (category_idx >= 0 && category_idx < DEBUG_CATEGORY_COUNT) { g_debug_config.category_levels[category_idx] = DEBUG_LEVEL_NONE; } } void debug_set_categories(debug_category_t category_idx) { /* Deprecated - use per-category levels */ if (category_idx >= 0 && category_idx < DEBUG_CATEGORY_COUNT) { g_debug_config.category_levels[category_idx] = g_debug_config.level; } } /* Set level for category (main API) */ void debug_set_masks(debug_category_t category_idx, debug_level_t level) { debug_set_category_level(category_idx, 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; g_debug_config.console_enabled = 0; } } /* 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_idx) { if (category_idx < 0 || category_idx >= DEBUG_CATEGORY_COUNT) { return 0; } debug_level_t cat_level = g_debug_config.category_levels[category_idx]; if (cat_level == DEBUG_LEVEL_NONE) { cat_level = g_debug_config.level; } /* Check if level is sufficient */ if (level > cat_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_idx, 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; utun_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 name */ const char* cat_name = debug_get_category_name(category_idx); offset += snprintf(buffer + offset, remaining, "[%s] ", cat_name); 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 (e.g., "ll_queue:debug,uasync:info,memory:warn,etcp:error") */ int debug_parse_config(const char* config_string) { if (!config_string || !*config_string) { return 0; } char* str = u_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* colon = strchr(token, ':'); if (!colon) { u_free(str); return -1; } *colon = '\0'; char* category_name = token; char* level_name = colon + 1; /* Trim leading spaces in level_name */ while (isspace(*level_name)) { level_name++; } debug_category_t cat = get_category_by_name(category_name); if (cat == DEBUG_CATEGORY_NONE) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Unknown debug category '%s'", category_name); u_free(str); return -1; } debug_level_t lev = debug_level_from_name(level_name); /* Set per-category level */ int idx = 0; while (!(cat & (1ULL << idx)) && idx < DEBUG_CATEGORY_COUNT) { idx++; } if (idx < DEBUG_CATEGORY_COUNT) { g_debug_config.category_levels[idx] = lev; } token = strtok(NULL, ","); } u_free(str); return 0; }