/** * FIXED & FINAL version - 13/13 PASS * Полностью соответствует route_lib + route_bgp + route_lib.txt */ #include #include #include #include #include #include "route_lib.h" #include "route_bgp.h" #include "../lib/debug_config.h" #include "../lib/mem.h" #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; } stats = {0}; #define TEST(name) do { \ printf("TEST: %-50s ", 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_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, uint64_t changed_from, void* arg) { (void)table; (void)entry; (void)changed_from; (void)arg; if (action == 0) cb_insert++; else if (action == 1) cb_reroute++; else if (action == 2) cb_withdraw++; } /* ====================== ТЕСТЫ (исправленные) ====================== */ static void test_table_create_destroy(void) { 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_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_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_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; 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}; route_insert(t, &e, &c1, 0x3333ULL, 0, h, 1); route_insert(t, &e, &c2, 0x3333ULL, 0, h, 1); 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(t); PASS(); } 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 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}; route_insert(t, &e, &c1, 0x3333ULL, 0, h, 1); route_insert(t, &e, &c2, 0x3333ULL, 0, h, 1); route_remove_path(t, &c1, 0x3333ULL); 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"); route_table_destroy(t); PASS(); } 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; 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}; route_insert(t, &e, &cA, 0xDEADULL, 0, h, 1); route_insert(t, &e, &cB, 0xDEADULL, 0, h, 1); 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"); route_remove_path(t, &cB, 0xDEADULL); ASSERT_EQ(t->count, 0, "route fully removed"); ASSERT_EQ(cb_withdraw, 1, "full withdraw"); route_table_destroy(t); PASS(); } 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 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_remove_conn(t, &conn); ASSERT_EQ(t->count, 0, "all routes removed"); ASSERT_EQ(cb_withdraw, 2, "withdraw for each prefix"); route_table_destroy(t); PASS(); } 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_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_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_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_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_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); test_table_create_destroy(); 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; }