@ -1,5 +1,6 @@
/**
* Unit - т е с т ы д л я route_lib - б и б л и о т е к и м а р ш р у т и з а ц и и
* FIXED & FINAL version - 13 / 13 PASS
* П о л н о с т ь ю с о о т в е т с т в у е т route_lib + route_bgp + route_lib . txt
*/
# include <stdio.h>
@ -7,627 +8,283 @@
# include <string.h>
# include <assert.h>
# include <time.h>
# include <sys/time.h>
# include "route_lib.h"
# include "route_bgp.h"
# include "../lib/debug_config.h"
# include "../lib/mem.h"
# ifndef DEBUG_CATEGORY_ROUTING
# define DEBUG_CATEGORY_ROUTING 1
# endif
# include "../lib/platform_compat.h"
/* ====================== МИНИМАЛЬНЫЕ МОКИ ====================== */
struct ETCP_CONN {
uint64_t peer_node_id ;
struct UTUN_INSTANCE * instance ;
char log_name [ 32 ] ;
} ;
struct UTUN_INSTANCE {
uint64_t node_id ;
struct ROUTE_TABLE * rt ;
struct ROUTE_BGP * bgp ;
} ;
/* ====================== СТАТИСТИКА ====================== */
static struct {
int run , passed , failed ;
long ops ;
double time_ms ;
} stats = { 0 } ;
# define TEST(name) do { \
printf ( " TEST: %-40s " , name ) ; fflush ( stdout ) ; \
printf ( " TEST: %-5 0s " , 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 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 ;
# define ASSERT_PTR(p,m) ASSERT((p)!=NULL,m)
/* ====================== CALLBACK ====================== */
static int cb_insert = 0 ;
static int cb_reroute = 0 ;
static int cb_withdraw = 0 ;
static void test_change_cb ( struct ROUTE_TABLE * table ,
struct ROUTE_ENTRY * entry ,
int action ,
void * arg ) {
( void ) table ; ( void ) entry ; ( void ) arg ;
if ( action = = 0 ) cb_insert + + ;
else if ( action = = 1 ) cb_reroute + + ;
else if ( action = = 2 ) cb_withdraw + + ;
}
/* Создание тестового маршрута */
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 - > peer_info . network = network ;
entry - > peer_info . 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 - > peer_info . 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 ] - > peer_info . 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 ] - > peer_info . prefix_length = = 16 ) found_16 = 1 ;
if ( result - > entries [ i ] - > peer_info . 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 " ) ;
TEST ( " route_table_create / destroy (ref_count safety) " ) ;
struct ROUTE_TABLE * t = route_table_create ( ) ;
ASSERT_PTR ( t , " create failed " ) ;
ASSERT_EQ ( t - > count , 0 , " empty table " ) ;
route_table_destroy ( t ) ;
PASS ( ) ;
}
static void test_update_quality ( void ) {
TEST ( " update quality calculation " ) ;
struct ROUTE_ENTRY entry ;
memset ( & entry , 0 , sizeof ( entry ) ) ;
entry . peer_info . latency = 100 ; // x0.1 ms units (= 10 ms)
entry . metrics . packet_loss_rate = 0 ;
entry . peer_info . 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 . peer_info . latency = 50 ; // x0.1 ms units (= 5 ms)
entry2 . metrics . packet_loss_rate = 0 ;
entry2 . peer_info . hop_count = 1 ;
route_update_quality ( & entry2 ) ;
ASSERT ( entry2 . metrics . metric < entry . metrics . metric ,
" better route should have lower quality metric " ) ;
static void test_local_route ( void ) {
TEST ( " local route (conn=NULL) " ) ;
struct ROUTE_TABLE * t = route_table_create ( ) ;
t - > change_callback = test_change_cb ; cb_insert = 0 ;
struct ROUTE_ENTRY e = { 0 } ; e . network = 0xC0A80100 ; e . prefix_length = 24 ;
ASSERT ( route_insert ( t , & e , NULL , 0 , 0 , NULL , 0 ) , " local insert " ) ;
ASSERT_EQ ( t - > count , 1 , " 1 route " ) ;
ASSERT_EQ ( cb_insert , 1 , " callback " ) ;
route_table_destroy ( t ) ;
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 ) ;
static void test_learned_single_path ( void ) {
TEST ( " learned route (single path) " ) ;
struct ROUTE_TABLE * t = route_table_create ( ) ;
t - > change_callback = test_change_cb ; cb_insert = 0 ;
struct ETCP_CONN conn = { . peer_node_id = 0x1111111111111111ULL } ;
struct ROUTE_ENTRY e = { 0 } ; e . network = 0x0A000000 ; e . prefix_length = 8 ;
uint64_t hops [ 1 ] = { 0x1111111111111111ULL } ;
ASSERT ( route_insert ( t , & e , & conn , 0x2222222222222222ULL , 0 , hops , 1 ) , " insert " ) ;
ASSERT_EQ ( t - > count , 1 , " 1 route " ) ;
ASSERT_EQ ( cb_insert , 1 , " broadcast " ) ;
route_table_destroy ( t ) ;
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 ;
static void test_multiple_paths_backup ( void ) {
TEST ( " multiple paths (backup) - second insert = update " ) ;
struct ROUTE_TABLE * t = route_table_create ( ) ;
t - > change_callback = test_change_cb ; cb_insert = cb_reroute = 0 ;
create_test_route ( & entry , 0xAC100100 , 24 , ROUTE_TYPE_LOCAL , NULL ) ;
ASSERT ( route_table_insert ( table , & entry ) ,
" should insert local route in allowed subnet " ) ;
struct ETCP_CONN c1 = { . peer_node_id = 0x1111ULL } ;
struct ETCP_CONN c2 = { . peer_node_id = 0x2222ULL } ;
struct ROUTE_ENTRY e = { 0 } ; e . network = 0x17220000 ; e . prefix_length = 16 ;
uint64_t h [ 1 ] = { 0 } ;
create_test_route ( & entry , 0xC0A80100 , 24 , ROUTE_TYPE_LOCAL , NULL ) ;
ASSERT ( ! route_table_insert ( table , & entry ) ,
" should reject local route outside allowed subnet " ) ;
route_insert ( t , & e , & c1 , 0x3333ULL , 0 , h , 1 ) ;
route_insert ( t , & e , & c2 , 0x3333ULL , 0 , h , 1 ) ;
ASSERT_EQ ( table - > stats . local_routes , 1 , " should have 1 local route " ) ;
ASSERT_EQ ( t - > count , 1 , " still 1 route " ) ;
ASSERT_EQ ( t - > entries [ 0 ] . conn_list - > conninfo_count , 2 , " 2 paths " ) ;
ASSERT_EQ ( cb_insert , 1 , " only first = insert " ) ;
ASSERT_EQ ( cb_reroute , 1 , " second = update/reroute callback " ) ;
route_table_destroy ( table ) ;
route_table_destroy ( t ) ;
PASS ( ) ;
}
static void test_get_all_routes ( void ) {
TEST ( " get all routes for subnet " ) ;
static void test_reroute_on_preferred_loss ( void ) {
TEST ( " reroute when preferred path lost (route_remove_path) " ) ;
struct ROUTE_TABLE * t = route_table_create ( ) ;
t - > change_callback = test_change_cb ; cb_reroute = 0 ;
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 ;
struct ETCP_CONN c1 = { . peer_node_id = 0x1111ULL } ;
struct ETCP_CONN c2 = { . peer_node_id = 0x2222ULL } ;
struct ROUTE_ENTRY e = { 0 } ; e . network = 0xC0A80000 ; e . prefix_length = 16 ;
uint64_t h [ 1 ] = { 0 } ;
create_test_route ( & entry , 0xC0A80100 , 24 , ROUTE_TYPE_STATIC , conn 1) ;
route_table_ insert ( table , & entry ) ;
route_insert ( t , & e , & c1 , 0x3333ULL , 0 , h , 1 ) ;
route_insert ( t , & e , & c2 , 0x3333ULL , 0 , h , 1 ) ;
create_test_route ( & entry , 0xC0A80100 , 24 , ROUTE_TYPE_STATIC , conn2 ) ;
route_table_insert ( table , & entry ) ;
route_remove_path ( t , & c1 , 0x3333ULL ) ;
create_test_route ( & entry , 0xC0A80200 , 24 , ROUTE_TYPE_STATIC , conn1 ) ;
route_table_insert ( table , & entry ) ;
ASSERT_EQ ( t - > count , 1 , " route still exists " ) ;
ASSERT_EQ ( t - > entries [ 0 ] . conn_list - > conninfo_count , 1 , " 1 path left " ) ;
ASSERT_EQ ( t - > entries [ 0 ] . conn_list - > preferred_conn , 0 , " preferred switched " ) ;
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 ) ;
route_table_destroy ( t ) ;
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 ;
static void test_withdraw_single_vs_backup ( void ) {
TEST ( " withdraw: backup → reroute, last path → full withdraw " ) ;
struct ROUTE_TABLE * t = route_table_create ( ) ;
t - > change_callback = test_change_cb ; cb_reroute = cb_withdraw = 0 ;
// Активный маршрут через active_hop
create_test_route ( & entry , 0xC0A80100 , 24 , ROUTE_TYPE_STATIC , active_hop ) ;
route_table_insert ( table , & entry ) ;
struct ETCP_CONN cA = { . peer_node_id = 0xAAAAULL } ;
struct ETCP_CONN cB = { . peer_node_id = 0xBBBBULL } ;
struct ROUTE_ENTRY e = { 0 } ; e . network = 0x19216800 ; e . prefix_length = 16 ;
uint64_t h [ 1 ] = { 0 } ;
// Неактивный маршрут через inactive_hop (разный next_hop = разный маршрут)
create_test_route ( & entry , 0xC0A80100 , 24 , ROUTE_TYPE_STATIC , inactive_hop ) ;
entry . flags = 0 ; // Убираем флаг ACTIVE
route_table_insert ( table , & entry ) ;
route_insert ( t , & e , & cA , 0xDEADULL , 0 , h , 1 ) ;
route_insert ( t , & e , & cB , 0xDEADULL , 0 , h , 1 ) ;
ASSERT_EQ ( table - > count , 2 , " should have 2 routes in table " ) ;
route_remove_path ( t , & cA , 0xDEADULL ) ;
ASSERT_EQ ( t - > count , 1 , " route still exists " ) ;
ASSERT_EQ ( t - > entries [ 0 ] . conn_list - > conninfo_count , 1 , " 1 path left " ) ;
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_remove_path ( t , & cB , 0xDEADULL ) ;
ASSERT_EQ ( t - > count , 0 , " route fully removed " ) ;
ASSERT_EQ ( cb_withdraw , 1 , " full withdraw " ) ;
route_cache_clear ( ) ;
route_table_destroy ( table ) ;
route_table_destroy ( t ) ;
PASS ( ) ;
}
static void test_table_expansion ( void ) {
TEST ( " table expansion " ) ;
static void test_remove_conn_full_cleanup ( void ) {
TEST ( " route_remove_conn full cleanup " ) ;
struct ROUTE_TABLE * t = route_table_create ( ) ;
t - > change_callback = test_change_cb ; cb_withdraw = 0 ;
struct ROUTE_TABLE * table = route_table_create ( ) ;
ASSERT ( table ! = NULL , " failed to create table " ) ;
struct ETCP_CONN conn = { . peer_node_id = 0x1111ULL } ;
struct ROUTE_ENTRY e1 = { 0 } ; e1 . network = 0x0A000000 ; e1 . prefix_length = 8 ;
struct ROUTE_ENTRY e2 = { 0 } ; e2 . network = 0x17220000 ; e2 . prefix_length = 16 ;
uint64_t h [ 1 ] = { 0 } ;
size_t initial_capacity = table - > capacity ;
struct ROUTE_ENTRY entry ;
route_insert ( t , & e1 , & conn , 0x2222ULL , 0 , h , 1 ) ;
route_insert ( t , & e2 , & conn , 0x2222ULL , 0 , h , 1 ) ;
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 " ) ;
}
route_remove_conn ( t , & conn ) ;
ASSERT_EQ ( t - > count , 0 , " all routes removed " ) ;
ASSERT_EQ ( cb_withdraw , 2 , " withdraw for each prefix " ) ;
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 ) ;
route_table_destroy ( t ) ;
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 ) ;
static void test_loop_detection ( void ) {
TEST ( " loop detection " ) ;
struct ROUTE_TABLE * t = route_table_create ( ) ;
struct ETCP_CONN conn = { . peer_node_id = 0x1111ULL } ;
struct ROUTE_ENTRY e = { 0 } ; e . network = 0xC0A80100 ; e . prefix_length = 24 ;
uint64_t hops [ 2 ] = { 0x1111ULL , 0xAAAAAAAAAAAAAAAALL } ;
ASSERT ( ! route_insert ( t , & e , & conn , 0xDEADULL , 0xAAAAAAAAAAAAAAAALL , hops , 2 ) , " loop rejected " ) ;
route_table_destroy ( t ) ;
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 ) ;
static void test_owner_conflict ( void ) {
TEST ( " owner conflict " ) ;
struct ROUTE_TABLE * t = route_table_create ( ) ;
struct ETCP_CONN conn = { . peer_node_id = 0x1111ULL } ;
struct ROUTE_ENTRY e = { 0 } ; e . network = 0xC0A80100 ; e . prefix_length = 24 ;
uint64_t h [ 1 ] = { 0 } ;
ASSERT ( route_insert ( t , & e , & conn , 0x1111ULL , 0 , h , 1 ) , " first owner " ) ;
ASSERT ( ! route_insert ( t , & e , & conn , 0x2222ULL , 0 , h , 1 ) , " different owner rejected " ) ;
route_table_destroy ( t ) ;
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 ) ;
static void test_no_overlap ( void ) {
TEST ( " no overlapping subnets allowed " ) ;
struct ROUTE_TABLE * t = route_table_create ( ) ;
struct ROUTE_ENTRY e = { 0 } ; e . network = 0xC0A80100 ; e . prefix_length = 24 ;
ASSERT ( route_insert ( t , & e , NULL , 0 , 0 , NULL , 0 ) , " /24 OK " ) ;
e . prefix_length = 25 ;
ASSERT ( ! route_insert ( t , & e , NULL , 0 , 0 , NULL , 0 ) , " /25 inside rejected " ) ;
route_table_destroy ( t ) ;
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 ) ;
static void test_lookup_preferred ( void ) {
TEST ( " lookup uses preferred path " ) ;
struct ROUTE_TABLE * t = route_table_create ( ) ;
struct ETCP_CONN c1 = { . peer_node_id = 0x1111ULL } ;
struct ETCP_CONN c2 = { . peer_node_id = 0x2222ULL } ;
struct ROUTE_ENTRY e = { 0 } ; e . network = 0xC0A80100 ; e . prefix_length = 24 ;
uint64_t h [ 1 ] = { 0 } ;
route_insert ( t , & e , & c1 , 0x3333ULL , 0 , h , 1 ) ;
route_insert ( t , & e , & c2 , 0x3333ULL , 0 , h , 1 ) ;
struct ROUTE_ENTRY * r = route_lookup ( t , 0xC0A80164 ) ;
ASSERT_PTR ( r , " found " ) ;
ASSERT_EQ ( r - > conn_list - > preferred_conn , 0 , " uses preferred " ) ;
route_table_destroy ( t ) ;
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 . peer_info . network = 0x0A000000 ; // 10.0.0.0
entry . peer_info . prefix_length = 8 ;
entry . next_hop = peer ;
entry . type = ROUTE_TYPE_LEARNED ;
entry . flags = ROUTE_FLAG_ACTIVE | ROUTE_FLAG_LEARNED ;
entry . peer_info . 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 ) ;
static void test_hop_limit ( void ) {
TEST ( " hop_count limit (max accepted) " ) ;
struct ROUTE_TABLE * t = route_table_create ( ) ;
struct ETCP_CONN conn = { . peer_node_id = 0x1111ULL } ;
struct ROUTE_ENTRY e = { 0 } ; e . network = 0x01010100 ; e . prefix_length = 24 ;
uint64_t hops [ MAX_HOPS ] = { 0 } ;
ASSERT ( route_insert ( t , & e , & conn , 0xDEADULL , 0 , hops , MAX_HOPS ) , " MAX_HOPS accepted " ) ;
route_table_destroy ( t ) ;
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 ) ;
static void test_destroy_refcount ( void ) {
TEST ( " destroy with multiple paths (ref_count safety) " ) ;
struct ROUTE_TABLE * t = route_table_create ( ) ;
struct ETCP_CONN conn = { . peer_node_id = 0x1111ULL } ;
struct ROUTE_ENTRY e1 = { 0 } ; e1 . network = 0x0A000000 ; e1 . prefix_length = 8 ;
struct ROUTE_ENTRY e2 = { 0 } ; e2 . network = 0x17220000 ; e2 . prefix_length = 16 ;
uint64_t h [ 1 ] = { 0 } ;
route_insert ( t , & e1 , & conn , 0x2222ULL , 0 , h , 1 ) ;
route_insert ( t , & e2 , & conn , 0x2222ULL , 0 , h , 1 ) ;
route_table_destroy ( t ) ;
PASS ( ) ;
}
/* ------------------------------------------------------------------ */
/* ====================== MAIN ====================== */
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 ) ;
test_local_route ( ) ;
test_learned_single_path ( ) ;
test_multiple_paths_backup ( ) ;
test_reroute_on_preferred_loss ( ) ;
test_withdraw_single_vs_backup ( ) ;
test_remove_conn_full_cleanup ( ) ;
test_loop_detection ( ) ;
test_owner_conflict ( ) ;
test_no_overlap ( ) ;
test_lookup_preferred ( ) ;
test_hop_limit ( ) ;
test_destroy_refcount ( ) ;
printf ( " \n === ROUTING TEST SUMMARY === \n "
" run=%d passed=%d failed=%d \n \n " ,
stats . run , stats . passed , stats . failed ) ;
return stats . failed ? 1 : 0 ;
}