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.
372 lines
13 KiB
372 lines
13 KiB
/** |
|
* @file enhanced_routing.c |
|
* @brief Enhanced routing system with bandwidth support and route types |
|
*/ |
|
|
|
#include "routing.h" |
|
#include "etcp_connections.h" |
|
#include "../lib/debug_config.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 |
|
|
|
// Parse subnet string |
|
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; |
|
} |
|
|
|
|
|
|
|
static int compare_routes(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; |
|
|
|
// First compare by prefix length (longest prefix first) |
|
if (route_b->prefix_length != route_a->prefix_length) { |
|
return route_b->prefix_length - route_a->prefix_length; |
|
} |
|
|
|
// Then by network |
|
if (route_a->network != route_b->network) { |
|
return route_a->network < route_b->network ? -1 : 1; |
|
} |
|
|
|
// 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; |
|
} |
|
|
|
struct routing_table *routing_table_create(void) { |
|
struct routing_table *table = calloc(1, sizeof(struct routing_table)); |
|
if (!table) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "routing_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, "routing_table_create: failed to allocate memory for %d 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, "routing_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 routing_table_destroy(struct routing_table *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(struct routing_table *table, const struct route_entry *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); |
|
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, "routing_table_insert: failed to expand routing table to %zu entries", new_capacity); |
|
return false; |
|
} |
|
|
|
table->entries = new_entries; |
|
table->capacity = new_capacity; |
|
} |
|
|
|
// Set timestamps and ensure route is active |
|
struct route_entry 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 |
|
table->stats.routes_added++; |
|
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; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
bool routing_table_lookup(struct routing_table *table, uint32_t dest_ip, struct route_entry *best_route) { |
|
if (!table || !best_route) { |
|
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "routing_table_lookup: invalid parameters (table=%p, best_route=%p)", table, best_route); |
|
return false; |
|
} |
|
|
|
table->stats.lookup_count++; |
|
|
|
// Find longest prefix match |
|
for (size_t i = 0; i < table->count; i++) { |
|
const struct route_entry *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); |
|
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Found route for %s: %s/%d -> %s", |
|
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++; |
|
table->stats.routes_lookup_hits++; |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
// No route found |
|
char ip_str[16]; |
|
ip_to_string(dest_ip, ip_str); |
|
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "routing_table_lookup: no route found for destination IP %s", ip_str); |
|
|
|
return false; |
|
} |
|
|
|
bool routing_validate_route(struct routing_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: |
|
table->stats.routes_lookup_misses++; |
|
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(struct routing_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 routing_add_dynamic_subnet(struct routing_table *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(struct routing_table *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 struct routing_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 routing_table_print(const struct routing_table *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 struct route_entry *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"; |
|
} |
|
} |
|
|
|
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; |
|
} |