/** * @file route_lib.c * @brief Enhanced routing system with bandwidth support and route types */ #include "route_lib.h" #include "etcp_connections.h" #include "../lib/debug_config.h" #include #include #include #include "../lib/platform_compat.h" #include #define INITIAL_ROUTE_CAPACITY 100 #define ROUTE_EXPANSION_FACTOR 2 #define MAX_SUBNET_VALIDATION_RANGES 50 #define ROUTE_CACHE_SIZE 10 /** * @brief Структура кеша маршрутов (LRU) */ struct ROUTE_CACHE { struct ROUTE_ARRAY *entries[ROUTE_CACHE_SIZE]; /**< Массив кешированных результатов */ uint32_t keys[ROUTE_CACHE_SIZE]; /**< IP-адреса (ключи) */ uint64_t access_time[ROUTE_CACHE_SIZE]; /**< Время последнего доступа */ int count; /**< Текущее количество записей */ }; static struct ROUTE_CACHE g_route_cache = {0}; /** * @brief Преобразует IP-адрес в строку * @param ip IP-адрес (в host byte order) * @param buffer Буфер для строки (минимум 16 байт) * @return Указатель на buffer или NULL */ static char *ip_to_string(uint32_t ip, char *buffer) { if (!buffer) return NULL; struct in_addr addr; addr.s_addr = htonl(ip); strncpy(buffer, inet_ntoa(addr), 15); buffer[15] = '\0'; return buffer; } /** * @brief Парсит строку подсети в сетевой адрес и длину префикса * @param subnet_str Строка подсети (например, "192.168.1.0/24") * @param network Указатель для сохранения сетевого адреса * @param prefix_length Указатель для сохранения длины префикса * @return 0 при успехе, -1 при ошибке */ 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 = ntohl(addr.s_addr); *prefix_length = (uint8_t)prefix; return 0; } /** * @brief Создает массив маршрутов * @param capacity Максимальное количество маршрутов * @return Указатель на созданный массив или NULL */ static struct ROUTE_ARRAY* route_array_create(uint32_t capacity) { struct ROUTE_ARRAY *array = calloc(1, sizeof(struct ROUTE_ARRAY) + capacity * sizeof(struct ROUTE_ENTRY*)); if (!array) { DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "route_array_create: failed to allocate memory"); return NULL; } array->capacity = capacity; array->routes = 0; array->ip = 0; return array; } /** * @brief Освобождает память массива маршрутов * @param array Указатель на массив */ static void route_array_destroy(struct ROUTE_ARRAY *array) { if (!array) return; // entries[] содержит только указатели на ROUTE_ENTRY из таблицы, // их освобождать не нужно - они принадлежат таблице free(array); } /** * @brief Добавляет маршрут в массив * @param array Массив маршрутов * @param entry Указатель на запись маршрута * @return true при успехе, false если массив полон */ static bool route_array_add(struct ROUTE_ARRAY *array, struct ROUTE_ENTRY *entry) { if (!array || !entry) return false; if (array->routes >= array->capacity) return false; array->entries[array->routes++] = entry; return true; } /** * @brief Пересчитывает метрику quality на основе других метрик * @param entry Указатель на запись маршрута * @return true при успехе, false при ошибке */ bool route_update_quality(struct ROUTE_ENTRY *entry) { if (!entry) return false; // Простая формула: latency * 2 + loss * 10 + hops * 50 // Меньше значение = лучше маршрут entry->metrics.metric = entry->metrics.latency_ms * 2 + entry->metrics.packet_loss_rate * 10 + entry->metrics.hop_count * 50; return true; } /** * @brief Сравнивает два маршрута для сортировки (лучший маршрут - меньшее значение) */ static int compare_routes_by_quality(const void *a, const void *b) { const struct ROUTE_ENTRY *route_a = *(const struct ROUTE_ENTRY **)a; const struct ROUTE_ENTRY *route_b = *(const struct ROUTE_ENTRY **)b; // Сначала сравниваем по quality (меньше - лучше) if (route_a->metrics.metric != route_b->metrics.metric) { return (int)(route_a->metrics.metric - route_b->metrics.metric); } // Затем по длине префикса (больше - лучше) if (route_b->prefix_length != route_a->prefix_length) { return route_b->prefix_length - route_a->prefix_length; } return 0; } /** * @brief Ищет запись в кеше по IP */ static struct ROUTE_ARRAY* cache_lookup(uint32_t dest_ip) { uint64_t current_time = (uint64_t)time(NULL) * 1000000; for (int i = 0; i < g_route_cache.count; i++) { if (g_route_cache.keys[i] == dest_ip && g_route_cache.entries[i]) { g_route_cache.access_time[i] = current_time; char ip_str[16]; ip_to_string(dest_ip, ip_str); DEBUG_DEBUG(DEBUG_CATEGORY_ROUTING, "Cache hit for IP %s", ip_str); return g_route_cache.entries[i]; } } return NULL; } /** * @brief Добавляет результат в кеш (LRU замещение) */ static void cache_insert(uint32_t dest_ip, struct ROUTE_ARRAY *result) { uint64_t current_time = (uint64_t)time(NULL) * 1000000; int insert_idx = -1; // Ищем пустое место или самую старую запись if (g_route_cache.count < ROUTE_CACHE_SIZE) { insert_idx = g_route_cache.count++; } else { // LRU: находим запись с самым старым access_time uint64_t oldest_time = g_route_cache.access_time[0]; insert_idx = 0; for (int i = 1; i < ROUTE_CACHE_SIZE; i++) { if (g_route_cache.access_time[i] < oldest_time) { oldest_time = g_route_cache.access_time[i]; insert_idx = i; } } // Освобождаем старую запись if (g_route_cache.entries[insert_idx]) { route_array_destroy(g_route_cache.entries[insert_idx]); } } g_route_cache.keys[insert_idx] = dest_ip; g_route_cache.entries[insert_idx] = result; g_route_cache.access_time[insert_idx] = current_time; DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Cache insert for IP %u at slot %d", dest_ip, insert_idx); } /** * @brief Преобразует тип маршрута в строку * @param type Тип маршрута * @return Строковое представление типа */ static 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"; } } struct ROUTE_TABLE *route_table_create(void) { struct ROUTE_TABLE *table = calloc(1, sizeof(struct ROUTE_TABLE)); if (!table) { DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "route_table_create: failed to allocate memory for routing table"); return NULL; } table->capacity = INITIAL_ROUTE_CAPACITY; table->entries = calloc(table->capacity, sizeof(struct ROUTE_ENTRY)); if (!table->entries) { DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "route_table_create: failed to allocate memory for %zu route entries", table->capacity); 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) { DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "route_table_create: failed to allocate memory for subnet validation arrays"); free(table->entries); free(table->dynamic_subnets); free(table->local_subnets); free(table); return NULL; } return table; } void route_table_destroy(struct ROUTE_TABLE *table) { if (!table) return; // Free route entries free(table->entries); // Free validation ranges free(table->dynamic_subnets); free(table->local_subnets); free(table); } /** * @brief Очищает глобальный кеш маршрутов */ void route_cache_clear(void) { for (int i = 0; i < g_route_cache.count; i++) { if (g_route_cache.entries[i]) { route_array_destroy(g_route_cache.entries[i]); g_route_cache.entries[i] = NULL; } } g_route_cache.count = 0; } /** * @brief Валидирует маршрут на соответствие разрешенным подсетям */ static bool route_validate_route(struct ROUTE_TABLE *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: 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 route_table_insert(struct ROUTE_TABLE *table, const struct ROUTE_ENTRY *entry) { if (!table || !entry) return false; // Validate the route if (!route_validate_route(table, entry->network, entry->prefix_length, entry->type)) { char ip_str[16]; ip_to_string(entry->network, ip_str); DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "Ignoring invalid route: %s/%d (type: %s)", 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; struct ROUTE_ENTRY *new_entries = realloc(table->entries, new_capacity * sizeof(struct ROUTE_ENTRY)); if (!new_entries) { DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "route_table_insert: failed to expand routing table to %zu entries", new_capacity); return false; } // Zero new memory memset(new_entries + table->capacity, 0, (new_capacity - table->capacity) * sizeof(struct ROUTE_ENTRY)); table->entries = new_entries; table->capacity = new_capacity; } // Проверяем, не существует ли уже такой маршрут (по network + prefix_length + next_hop) for (size_t i = 0; i < table->count; i++) { if (table->entries[i].network == entry->network && table->entries[i].prefix_length == entry->prefix_length && table->entries[i].next_hop == entry->next_hop) { // Обновляем существующий маршрут table->entries[i] = *entry; table->entries[i].last_update = (uint64_t)time(NULL) * 1000000; // Вызываем callback (action=1 - update) if (table->change_callback) { table->change_callback(table, &table->entries[i], 1, table->change_callback_arg); } return true; } } // Set timestamps struct ROUTE_ENTRY new_entry = *entry; new_entry.created_time = new_entry.last_update = (uint64_t)time(NULL) * 1000000; // Insert at the end (order doesn't matter, lookup will sort) table->entries[table->count++] = new_entry; // Вызываем callback (action=0 - insert) if (table->change_callback) { table->change_callback(table, &table->entries[table->count - 1], 0, table->change_callback_arg); } // Update stats if (new_entry.type == ROUTE_TYPE_LOCAL) { table->stats.local_routes++; } else if (new_entry.type == ROUTE_TYPE_LEARNED) { table->stats.learned_routes++; } char ip_str[16]; ip_to_string(new_entry.network, ip_str); DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Route inserted: %s/%d (%s)", ip_str, new_entry.prefix_length, route_type_to_string(new_entry.type)); return true; } void route_table_delete(struct ROUTE_TABLE *table, struct ETCP_CONN* conn) { if (!table || !conn) return; size_t removed = 0; size_t write_idx = 0; for (size_t i = 0; i < table->count; i++) { if (table->entries[i].next_hop == conn) { // Обновляем статистику if (table->entries[i].type == ROUTE_TYPE_LOCAL) { table->stats.local_routes--; } else if (table->entries[i].type == ROUTE_TYPE_LEARNED) { table->stats.learned_routes--; } removed++; } else { // Сохраняем запись if (write_idx != i) { table->entries[write_idx] = table->entries[i]; } write_idx++; } } table->count = write_idx; // Очищаем освободившиеся записи for (size_t i = write_idx; i < write_idx + removed && i < table->capacity; i++) { memset(&table->entries[i], 0, sizeof(struct ROUTE_ENTRY)); } // Инвалидируем кеш for (int i = 0; i < g_route_cache.count; i++) { if (g_route_cache.entries[i]) { route_array_destroy(g_route_cache.entries[i]); g_route_cache.entries[i] = NULL; } } g_route_cache.count = 0; DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Removed %zu routes for connection %p", removed, (void*)conn); } bool route_table_delete_entry(struct ROUTE_TABLE *table, uint32_t network, uint8_t prefix_length, struct ETCP_CONN* next_hop) { if (!table) return false; int found_idx = -1; // Ищем маршрут for (size_t i = 0; i < table->count; i++) { if (table->entries[i].network == network && table->entries[i].prefix_length == prefix_length) { // Если указан next_hop - проверяем его тоже if (next_hop == NULL || table->entries[i].next_hop == next_hop) { found_idx = (int)i; break; } } } if (found_idx == -1) { // Маршрут не найден char ip_str[16]; ip_to_string(network, ip_str); DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "Route to delete not found: %s/%d", ip_str, prefix_length); return false; } // Вызываем callback перед удалением (action=2 - delete) if (table->change_callback) { table->change_callback(table, &table->entries[found_idx], 2, table->change_callback_arg); } // Обновляем статистику if (table->entries[found_idx].type == ROUTE_TYPE_LOCAL) { table->stats.local_routes--; } else if (table->entries[found_idx].type == ROUTE_TYPE_LEARNED) { table->stats.learned_routes--; } // Сдвигаем все записи после удаляемой for (size_t i = found_idx; i < table->count - 1; i++) { table->entries[i] = table->entries[i + 1]; } // Очищаем последнюю запись memset(&table->entries[table->count - 1], 0, sizeof(struct ROUTE_ENTRY)); table->count--; // Инвалидируем кеш for (int i = 0; i < g_route_cache.count; i++) { if (g_route_cache.entries[i]) { route_array_destroy(g_route_cache.entries[i]); g_route_cache.entries[i] = NULL; } } g_route_cache.count = 0; char ip_str[16]; ip_to_string(network, ip_str); DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Deleted route: %s/%d", ip_str, prefix_length); return true; } struct ROUTE_ARRAY* route_table_lookup(struct ROUTE_TABLE *table, uint32_t dest_ip) { if (!table) return NULL; table->stats.routes_lookup_hits++; // Сначала проверяем кеш struct ROUTE_ARRAY *cached = cache_lookup(dest_ip); if (cached) { return cached; } // Создаем новый результат struct ROUTE_ARRAY *result = route_array_create(table->count); if (!result) { return NULL; } result->ip = dest_ip; // Ищем все подходящие маршруты (longest prefix match) for (size_t i = 0; i < table->count; i++) { struct ROUTE_ENTRY *entry = &table->entries[i]; // Проверяем что маршрут активен if (!(entry->flags & ROUTE_FLAG_ACTIVE)) { continue; } // Проверяем совпадение по подсети uint32_t mask = (entry->prefix_length == 0) ? 0 : (0xFFFFFFFFU << (32 - entry->prefix_length)); if ((dest_ip & mask) == (entry->network & mask)) { // Добавляем в результат if (!route_array_add(result, entry)) { DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "Route array full, skipping additional routes"); break; } } } if (result->routes == 0) { // Нет маршрутов table->stats.routes_lookup_misses++; route_array_destroy(result); char ip_str[16]; ip_to_string(dest_ip, ip_str); DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "No route found for destination IP %s", ip_str); return NULL; } // Обновляем quality для всех найденных маршрутов for (uint32_t i = 0; i < result->routes; i++) { route_update_quality(result->entries[i]); } // Сортируем по quality (лучший первый) qsort(result->entries, result->routes, sizeof(struct ROUTE_ENTRY*), compare_routes_by_quality); // Сохраняем в кеш cache_insert(dest_ip, result); char ip_str[16]; ip_to_string(dest_ip, ip_str); DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Found %u routes for %s (best quality: %u)", result->routes, ip_str, result->entries[0]->metrics.metric); return result; } static bool enhanced_route_add_subnet_range(struct ROUTE_TABLE *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); DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Added subnet validation range: %s/%d", ip_str, prefix_length); return true; } bool route_add_dynamic_subnet(struct ROUTE_TABLE *table, uint32_t network, uint8_t prefix_length) { return enhanced_route_add_subnet_range(table, network, prefix_length, &table->dynamic_subnets, &table->dynamic_subnet_count); } bool route_add_local_subnet(struct ROUTE_TABLE *table, uint32_t network, uint8_t prefix_length) { return enhanced_route_add_subnet_range(table, network, prefix_length, &table->local_subnets, &table->local_subnet_count); } bool route_get_all_routes(const struct ROUTE_TABLE *table, uint32_t network, uint8_t prefix_length, struct ROUTE_ENTRY **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(struct ROUTE_ENTRY)); 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 route_table_print(const struct ROUTE_TABLE *table) { if (!table) return; DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "=== Routing Table ==="); DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Total routes: %zu", table->count); DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Dynamic subnets: %zu, Local subnets: %zu", table->dynamic_subnet_count / 2, table->local_subnet_count / 2); DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Local routes: %lu, Learned routes: %lu", table->stats.local_routes, table->stats.learned_routes); DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Cache hits: %lu, Misses: %lu", table->stats.routes_lookup_hits - table->stats.routes_lookup_misses, table->stats.routes_lookup_misses); if (table->count > 0) { DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Routes:"); for (size_t i = 0; i < table->count; i++) { const struct ROUTE_ENTRY *entry = &table->entries[i]; char network_str[16]; ip_to_string(entry->network, network_str); DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " %zu: %s/%d -> %p [%s] (quality: %u)", i + 1, network_str, entry->prefix_length, (void*)entry->next_hop, route_type_to_string(entry->type), entry->metrics.metric); DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " Bandwidth: %uK, Loss: %.2f%%, Latency: %ums, Hops: %d", entry->metrics.bandwidth_kbps, entry->metrics.packet_loss_rate / 100.0, entry->metrics.latency_ms, entry->metrics.hop_count); DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " Flags: %s%s%s%s", (entry->flags & ROUTE_FLAG_ACTIVE) ? "ACTIVE " : "", (entry->flags & ROUTE_FLAG_VALIDATED) ? "VALIDATED " : "", (entry->flags & ROUTE_FLAG_ADVERTISED) ? "ADVERTISED " : "", (entry->flags & ROUTE_FLAG_LEARNED) ? "LEARNED" : ""); } } else { DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "No routes configured."); } }