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
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; |
|
}
|
|
|