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.
 
 
 
 
 
 

633 lines
22 KiB

/**
* Unit-тесты для route_lib - библиотеки маршрутизации
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include <sys/time.h>
#include "route_lib.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#ifndef DEBUG_CATEGORY_ROUTING
#define DEBUG_CATEGORY_ROUTING 1
#endif
static struct {
int run, passed, failed;
long ops;
double time_ms;
} stats = {0};
#define TEST(name) do { \
printf("TEST: %-40s ", name); fflush(stdout); \
stats.run++; \
} while(0)
#define PASS() do { puts("PASS"); stats.passed++; } while(0)
#define FAIL(msg) do { printf("FAIL: %s\n", msg); stats.failed++; } while(0)
#define ASSERT(c, m) do { if (!(c)) { FAIL(m); return; } } while(0)
#define ASSERT_EQ(a,b,m) ASSERT((a)==(b),m)
#define ASSERT_NE(a,b,m) ASSERT((a)!=(b),m)
/* ------------------------------------------------------------------ */
static double now_ms(void) {
struct timeval tv; gettimeofday(&tv, NULL);
return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
}
/* Создание тестового маршрута */
static void create_test_route(struct ROUTE_ENTRY *entry, uint32_t network,
uint8_t prefix_len, route_type_t type,
struct ETCP_CONN *next_hop) {
memset(entry, 0, sizeof(*entry));
entry->network = network;
entry->prefix_length = prefix_len;
entry->type = type;
entry->next_hop = next_hop;
entry->flags = ROUTE_FLAG_ACTIVE;
entry->metrics.latency_ms = 10 + (rand() % 100);
entry->metrics.packet_loss_rate = rand() % 50;
entry->hop_count = 1 + (rand() % 5);
entry->metrics.bandwidth_kbps = 1000 + (rand() % 9000);
route_update_quality(entry);
}
/* ------------------------------------------------------------------ */
static void test_table_create_destroy(void) {
TEST("table create/destroy");
struct ROUTE_TABLE *table = route_table_create();
ASSERT(table != NULL, "failed to create table");
ASSERT_EQ(table->count, 0, "new table should be empty");
ASSERT(table->capacity > 0, "table should have capacity");
ASSERT(table->entries != NULL, "table should have entries array");
route_table_destroy(table);
PASS();
}
static void test_route_insert(void) {
TEST("route insert");
struct ROUTE_TABLE *table = route_table_create();
ASSERT(table != NULL, "failed to create table");
struct ROUTE_ENTRY entry;
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, NULL);
ASSERT(route_table_insert(table, &entry), "failed to insert static route");
ASSERT_EQ(table->count, 1, "table should have 1 route");
create_test_route(&entry, 0xC0A80200, 24, ROUTE_TYPE_STATIC, NULL);
ASSERT(route_table_insert(table, &entry), "failed to insert second route");
ASSERT_EQ(table->count, 2, "table should have 2 routes");
route_table_destroy(table);
PASS();
}
static void test_route_update_existing(void) {
TEST("route update existing");
struct ROUTE_TABLE *table = route_table_create();
struct ROUTE_ENTRY entry;
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, NULL);
entry.metrics.latency_ms = 50;
ASSERT(route_table_insert(table, &entry), "failed to insert route");
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, NULL);
entry.metrics.latency_ms = 100;
ASSERT(route_table_insert(table, &entry), "failed to update route");
ASSERT_EQ(table->count, 1, "should still have 1 route after update");
route_table_destroy(table);
PASS();
}
static void test_route_lookup_basic(void) {
TEST("route lookup basic");
struct ROUTE_TABLE *table = route_table_create();
struct ROUTE_ENTRY entry;
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, NULL);
route_table_insert(table, &entry);
struct ROUTE_ARRAY *result = route_table_lookup(table, 0xC0A80164);
ASSERT(result != NULL, "should find route for 192.168.1.100");
ASSERT_EQ(result->routes, 1, "should find exactly 1 route");
ASSERT_EQ(result->entries[0]->network, 0xC0A80100, "wrong network");
route_cache_clear();
route_table_destroy(table);
PASS();
}
static void test_route_lookup_miss(void) {
TEST("route lookup miss");
struct ROUTE_TABLE *table = route_table_create();
struct ROUTE_ENTRY entry;
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, NULL);
route_table_insert(table, &entry);
struct ROUTE_ARRAY *result = route_table_lookup(table, 0x0A000001);
ASSERT(result == NULL, "should not find route for 10.0.0.1");
route_table_destroy(table);
PASS();
}
static void test_longest_prefix_match(void) {
TEST("longest prefix match");
struct ROUTE_TABLE *table = route_table_create();
struct ROUTE_ENTRY entry;
// Используем фиктивные указатели для next_hop
struct ETCP_CONN *dummy_conn1 = (struct ETCP_CONN *)0x1000;
struct ETCP_CONN *dummy_conn2 = (struct ETCP_CONN *)0x2000;
create_test_route(&entry, 0xC0A80000, 16, ROUTE_TYPE_STATIC, dummy_conn1);
route_table_insert(table, &entry);
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, dummy_conn2);
route_table_insert(table, &entry);
// Используем IP 192.168.1.200 (0xC0A801C8) - отличается от предыдущего теста
// чтобы избежать попадания в кеш с предыдущего теста
struct ROUTE_ARRAY *result = route_table_lookup(table, 0xC0A801C8);
ASSERT(result != NULL, "should find routes");
ASSERT_EQ(result->routes, 2, "should find 2 routes");
int found_16 = 0, found_24 = 0;
for (uint32_t i = 0; i < result->routes; i++) {
if (result->entries[i]->prefix_length == 16) found_16 = 1;
if (result->entries[i]->prefix_length == 24) found_24 = 1;
}
ASSERT(found_16 && found_24, "should find both prefix lengths");
route_cache_clear();
route_table_destroy(table);
PASS();
}
static void test_route_delete_by_conn(void) {
TEST("route delete by connection");
struct ROUTE_TABLE *table = route_table_create();
struct ROUTE_ENTRY entry;
struct ETCP_CONN *conn1 = (struct ETCP_CONN *)0x1000;
struct ETCP_CONN *conn2 = (struct ETCP_CONN *)0x2000;
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, conn1);
route_table_insert(table, &entry);
create_test_route(&entry, 0xC0A80200, 24, ROUTE_TYPE_STATIC, conn1);
route_table_insert(table, &entry);
create_test_route(&entry, 0xC0A80300, 24, ROUTE_TYPE_STATIC, conn2);
route_table_insert(table, &entry);
ASSERT_EQ(table->count, 3, "should have 3 routes");
route_table_delete(table, conn1);
ASSERT_EQ(table->count, 1, "should have 1 route after deletion");
struct ROUTE_ARRAY *result = route_table_lookup(table, 0xC0A80301);
ASSERT(result != NULL, "should still find route via conn2");
ASSERT_EQ(result->entries[0]->next_hop, conn2, "wrong next_hop");
route_cache_clear();
route_table_destroy(table);
PASS();
}
static void test_parse_subnet(void) {
TEST("parse subnet");
uint32_t network;
uint8_t prefix_len;
ASSERT_EQ(parse_subnet("192.168.1.0/24", &network, &prefix_len), 0,
"failed to parse 192.168.1.0/24");
ASSERT_EQ(network, 0xC0A80100, "wrong network for 192.168.1.0/24");
ASSERT_EQ(prefix_len, 24, "wrong prefix length");
ASSERT_EQ(parse_subnet("10.0.0.0/8", &network, &prefix_len), 0,
"failed to parse 10.0.0.0/8");
ASSERT_EQ(network, 0x0A000000, "wrong network for 10.0.0.0/8");
ASSERT_EQ(prefix_len, 8, "wrong prefix length");
ASSERT_EQ(parse_subnet("0.0.0.0/0", &network, &prefix_len), 0,
"failed to parse 0.0.0.0/0");
ASSERT_EQ(network, 0, "wrong network for 0.0.0.0/0");
ASSERT_EQ(prefix_len, 0, "wrong prefix length");
ASSERT_EQ(parse_subnet("invalid", &network, &prefix_len), -1,
"should fail on invalid string");
ASSERT_EQ(parse_subnet("192.168.1.0/33", &network, &prefix_len), -1,
"should fail on prefix > 32");
ASSERT_EQ(parse_subnet("192.168.1.0", &network, &prefix_len), -1,
"should fail without prefix");
ASSERT_EQ(parse_subnet("256.0.0.0/8", &network, &prefix_len), -1,
"should fail on invalid IP");
ASSERT_EQ(parse_subnet(NULL, &network, &prefix_len), -1,
"should fail on NULL string");
PASS();
}
static void test_update_quality(void) {
TEST("update quality calculation");
struct ROUTE_ENTRY entry;
memset(&entry, 0, sizeof(entry));
entry.latency = 100; // x0.1 ms units (= 10 ms)
entry.metrics.packet_loss_rate = 0;
entry.hop_count = 1;
ASSERT(route_update_quality(&entry), "failed to update quality");
ASSERT_EQ(entry.metrics.metric, 70, "wrong quality calculation");
struct ROUTE_ENTRY entry2;
memset(&entry2, 0, sizeof(entry2));
entry2.latency = 50; // x0.1 ms units (= 5 ms)
entry2.metrics.packet_loss_rate = 0;
entry2.hop_count = 1;
route_update_quality(&entry2);
ASSERT(entry2.metrics.metric < entry.metrics.metric,
"better route should have lower quality metric");
PASS();
}
static void test_dynamic_subnet_validation(void) {
TEST("dynamic subnet validation");
struct ROUTE_TABLE *table = route_table_create();
ASSERT(table != NULL, "failed to create table");
ASSERT(route_add_dynamic_subnet(table, 0x0A000000, 8),
"failed to add dynamic subnet");
struct ROUTE_ENTRY entry;
create_test_route(&entry, 0x0A000100, 24, ROUTE_TYPE_DYNAMIC, NULL);
ASSERT(route_table_insert(table, &entry),
"should insert route in allowed subnet");
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_DYNAMIC, NULL);
ASSERT(!route_table_insert(table, &entry),
"should reject route outside allowed subnet");
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, NULL);
ASSERT(route_table_insert(table, &entry),
"should accept static route regardless of validation");
route_table_destroy(table);
PASS();
}
static void test_local_subnet_validation(void) {
TEST("local subnet validation");
struct ROUTE_TABLE *table = route_table_create();
ASSERT(route_add_local_subnet(table, 0xAC100000, 12),
"failed to add local subnet");
struct ROUTE_ENTRY entry;
create_test_route(&entry, 0xAC100100, 24, ROUTE_TYPE_LOCAL, NULL);
ASSERT(route_table_insert(table, &entry),
"should insert local route in allowed subnet");
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_LOCAL, NULL);
ASSERT(!route_table_insert(table, &entry),
"should reject local route outside allowed subnet");
ASSERT_EQ(table->stats.local_routes, 1, "should have 1 local route");
route_table_destroy(table);
PASS();
}
static void test_get_all_routes(void) {
TEST("get all routes for subnet");
struct ROUTE_TABLE *table = route_table_create();
struct ROUTE_ENTRY entry;
struct ETCP_CONN *conn1 = (struct ETCP_CONN *)0x1000;
struct ETCP_CONN *conn2 = (struct ETCP_CONN *)0x2000;
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, conn1);
route_table_insert(table, &entry);
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, conn2);
route_table_insert(table, &entry);
create_test_route(&entry, 0xC0A80200, 24, ROUTE_TYPE_STATIC, conn1);
route_table_insert(table, &entry);
struct ROUTE_ENTRY *routes = NULL;
size_t count = 0;
ASSERT(route_get_all_routes(table, 0xC0A80100, 24, &routes, &count),
"failed to get routes");
ASSERT_EQ(count, 2, "should find 2 routes for 192.168.1.0/24");
ASSERT(routes != NULL, "routes should not be NULL");
u_free(routes);
route_table_destroy(table);
PASS();
}
static void test_inactive_route(void) {
TEST("inactive route not returned");
struct ROUTE_TABLE *table = route_table_create();
struct ROUTE_ENTRY entry;
struct ETCP_CONN *active_hop = (struct ETCP_CONN *)0x1000;
struct ETCP_CONN *inactive_hop = (struct ETCP_CONN *)0x2000;
// Активный маршрут через active_hop
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, active_hop);
route_table_insert(table, &entry);
// Неактивный маршрут через inactive_hop (разный next_hop = разный маршрут)
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, inactive_hop);
entry.flags = 0; // Убираем флаг ACTIVE
route_table_insert(table, &entry);
ASSERT_EQ(table->count, 2, "should have 2 routes in table");
struct ROUTE_ARRAY *result = route_table_lookup(table, 0xC0A80101);
ASSERT(result != NULL, "should find route");
ASSERT_EQ(result->routes, 1, "should find only 1 active route");
ASSERT(result->entries[0]->flags & ROUTE_FLAG_ACTIVE,
"returned route should be active");
route_cache_clear();
route_table_destroy(table);
PASS();
}
static void test_table_expansion(void) {
TEST("table expansion");
struct ROUTE_TABLE *table = route_table_create();
ASSERT(table != NULL, "failed to create table");
size_t initial_capacity = table->capacity;
struct ROUTE_ENTRY entry;
for (int i = 0; i < (int)initial_capacity + 10; i++) {
create_test_route(&entry, 0xC0A80000 + (i << 8), 24,
ROUTE_TYPE_STATIC, NULL);
ASSERT(route_table_insert(table, &entry), "failed to insert route");
}
ASSERT(table->capacity > initial_capacity, "table should have expanded");
ASSERT_EQ(table->count, (size_t)(initial_capacity + 10),
"should have all routes");
route_table_destroy(table);
PASS();
}
static void test_cache_functionality(void) {
TEST("route cache functionality");
struct ROUTE_TABLE *table = route_table_create();
struct ROUTE_ENTRY entry;
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, NULL);
route_table_insert(table, &entry);
uint64_t hits_before = table->stats.routes_lookup_hits;
struct ROUTE_ARRAY *result1 = route_table_lookup(table, 0xC0A80101);
ASSERT(result1 != NULL, "first lookup should succeed");
struct ROUTE_ARRAY *result2 = route_table_lookup(table, 0xC0A80101);
ASSERT(result2 != NULL, "second lookup should succeed");
ASSERT(table->stats.routes_lookup_hits > hits_before,
"should increment lookup hits");
route_cache_clear();
route_table_destroy(table);
PASS();
}
static void test_route_types(void) {
TEST("all route types");
struct ROUTE_TABLE *table = route_table_create();
struct ROUTE_ENTRY entry;
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, NULL);
ASSERT(route_table_insert(table, &entry),
"failed to insert static route");
route_add_dynamic_subnet(table, 0x0A000000, 8);
create_test_route(&entry, 0x0A000100, 24, ROUTE_TYPE_DYNAMIC, NULL);
ASSERT(route_table_insert(table, &entry),
"failed to insert dynamic route");
route_add_local_subnet(table, 0xAC100000, 12);
create_test_route(&entry, 0xAC100100, 24, ROUTE_TYPE_LOCAL, NULL);
ASSERT(route_table_insert(table, &entry),
"failed to insert local route");
create_test_route(&entry, 0xC0A80200, 24, ROUTE_TYPE_LEARNED, NULL);
ASSERT(route_table_insert(table, &entry),
"failed to insert learned route");
ASSERT_EQ(table->count, 4, "should have 4 routes of different types");
ASSERT_EQ(table->stats.local_routes, 1, "should have 1 local route");
ASSERT_EQ(table->stats.learned_routes, 1, "should have 1 learned route");
route_table_destroy(table);
PASS();
}
static void test_route_delete_entry(void) {
TEST("route delete entry");
struct ROUTE_TABLE *table = route_table_create();
struct ROUTE_ENTRY entry;
struct ETCP_CONN *conn1 = (struct ETCP_CONN *)0x1000;
// Вставляем несколько маршрутов
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, conn1);
route_table_insert(table, &entry);
create_test_route(&entry, 0xC0A80200, 24, ROUTE_TYPE_STATIC, conn1);
route_table_insert(table, &entry);
create_test_route(&entry, 0xC0A80300, 24, ROUTE_TYPE_STATIC, conn1);
route_table_insert(table, &entry);
ASSERT_EQ(table->count, 3, "should have 3 routes");
// Удаляем маршрут по network/prefix (next_hop = NULL - любой)
ASSERT(route_table_delete_entry(table, 0xC0A80200, 24, NULL),
"should delete route 192.168.2.0/24");
ASSERT_EQ(table->count, 2, "should have 2 routes after deletion");
// Проверяем что правильный маршрут удалён
struct ROUTE_ARRAY *result = route_table_lookup(table, 0xC0A80201);
ASSERT(result == NULL, "should not find deleted route");
result = route_table_lookup(table, 0xC0A80101);
ASSERT(result != NULL, "should still find 192.168.1.x route");
route_cache_clear();
// Пытаемся удалить несуществующий маршрут
ASSERT(!route_table_delete_entry(table, 0xC0A80500, 24, NULL),
"should fail to delete non-existent route");
ASSERT_EQ(table->count, 2, "should still have 2 routes");
route_table_destroy(table);
PASS();
}
static void test_route_delete_entry_by_nexthop(void) {
TEST("route delete entry by next_hop");
struct ROUTE_TABLE *table = route_table_create();
struct ROUTE_ENTRY entry;
struct ETCP_CONN *conn1 = (struct ETCP_CONN *)0x1000;
struct ETCP_CONN *conn2 = (struct ETCP_CONN *)0x2000;
// Одинаковый network/prefix, разные next_hop
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, conn1);
route_table_insert(table, &entry);
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, conn2);
route_table_insert(table, &entry);
ASSERT_EQ(table->count, 2, "should have 2 routes");
// Удаляем только маршрут через conn1
ASSERT(route_table_delete_entry(table, 0xC0A80100, 24, conn1),
"should delete route via conn1");
ASSERT_EQ(table->count, 1, "should have 1 route after deletion");
// Проверяем что остался маршрут через conn2
struct ROUTE_ARRAY *result = route_table_lookup(table, 0xC0A80101);
ASSERT(result != NULL, "should still find route via conn2");
ASSERT_EQ(result->entries[0]->next_hop, conn2, "wrong next_hop remaining");
route_cache_clear();
route_table_destroy(table);
PASS();
}
static void test_route_withdraw_scenario(void) {
TEST("BGP withdraw scenario");
struct ROUTE_TABLE *table = route_table_create();
struct ROUTE_ENTRY entry;
struct ETCP_CONN *peer = (struct ETCP_CONN *)0x1000;
// Имитируем получение маршрута от пира (learned route)
memset(&entry, 0, sizeof(entry));
entry.network = 0x0A000000; // 10.0.0.0
entry.prefix_length = 8;
entry.next_hop = peer;
entry.type = ROUTE_TYPE_LEARNED;
entry.flags = ROUTE_FLAG_ACTIVE | ROUTE_FLAG_LEARNED;
entry.node_id = 0x1234567890ABCDEFULL;
ASSERT(route_table_insert(table, &entry), "failed to insert learned route");
ASSERT_EQ(table->stats.learned_routes, 1, "should have 1 learned route");
// Имитируем получение withdraw
ASSERT(route_table_delete_entry(table, 0x0A000000, 8, peer),
"should withdraw route from peer");
ASSERT_EQ(table->count, 0, "should have 0 routes after withdraw");
ASSERT_EQ(table->stats.learned_routes, 0, "should have 0 learned routes");
route_table_destroy(table);
PASS();
}
static void test_stress(void) {
TEST("stress 1000 routes");
double start = now_ms();
struct ROUTE_TABLE *table = route_table_create();
struct ROUTE_ENTRY entry;
for (int i = 0; i < 1000; i++) {
uint32_t network = 0xC0000000 | ((i & 0xFF) << 8) | ((i >> 8) & 0xFF);
create_test_route(&entry, network, 24, ROUTE_TYPE_STATIC, NULL);
ASSERT(route_table_insert(table, &entry),
"failed to insert route in stress test");
stats.ops++;
}
for (int i = 0; i < 1000; i++) {
uint32_t ip = 0xC0000000 | ((i & 0xFF) << 8) | ((i >> 8) & 0xFF) | 1;
struct ROUTE_ARRAY *result = route_table_lookup(table, ip);
// result находится в кеше, не освобождаем явно
(void)result;
stats.ops++;
}
stats.time_ms += now_ms() - start;
ASSERT_EQ(table->count, 1000, "should have 1000 routes");
route_cache_clear();
route_table_destroy(table);
PASS();
}
/* ------------------------------------------------------------------ */
int main(void) {
debug_config_init();
debug_set_level(DEBUG_LEVEL_INFO);
debug_set_categories(DEBUG_CATEGORY_ROUTING);
srand(time(NULL));
double t0 = now_ms();
test_table_create_destroy();
test_route_insert();
test_route_update_existing();
test_route_lookup_basic();
test_route_lookup_miss();
test_longest_prefix_match();
test_route_delete_by_conn();
test_parse_subnet();
test_update_quality();
test_dynamic_subnet_validation();
test_local_subnet_validation();
test_get_all_routes();
test_inactive_route();
test_table_expansion();
test_cache_functionality();
test_route_types();
test_route_delete_entry();
test_route_delete_entry_by_nexthop();
test_route_withdraw_scenario();
test_stress();
printf("\n=== SUMMARY ===\n"
"run=%d passed=%d failed=%d\n"
"ops=%ld time=%.2f ms\n",
stats.run, stats.passed, stats.failed,
stats.ops, now_ms() - t0);
return stats.failed ? 1 : 0;
}