You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

675 lines
24 KiB

/**
* @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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../lib/platform_compat.h"
#include <time.h>
#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 Парсит строку подсети в сетевой адрес и длину префикса
* @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;
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Cache hit for IP %u", dest_ip);
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 Преобразует 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 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_WARN(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.");
}
}