// tests/test_routing_full.c - Comprehensive routing table tests #include "routing.h" #include #include #include #include #include #include #define TEST_ASSERT(cond, msg) do { \ if (!(cond)) { \ fprintf(stderr, "FAIL: %s\n", msg); \ return -1; \ } \ } while(0) #define TEST_PASS(msg) printf(" ✓ %s\n", msg) // Helper: Convert IP string to uint32_t static uint32_t ip_to_uint32(const char* ip) { struct in_addr addr; inet_pton(AF_INET, ip, &addr); return ntohl(addr.s_addr); } // Helper: Convert uint32_t to IP string static const char* uint32_to_ip(uint32_t ip, char* buf) { struct in_addr addr; addr.s_addr = htonl(ip); return inet_ntop(AF_INET, &addr, buf, INET_ADDRSTRLEN); } // Test 1: Basic create and destroy static int test_routing_table_create_destroy(void) { printf("\n=== Test 1: Create and Destroy ===\n"); struct routing_table* table = routing_table_create(); TEST_ASSERT(table != NULL, "Table creation failed"); TEST_ASSERT(table->entries != NULL, "Entries should be allocated initially"); TEST_ASSERT(table->count == 0, "Count should be 0"); TEST_ASSERT(table->capacity == 100, "Capacity should be 100 (INITIAL_ROUTE_CAPACITY)"); TEST_PASS("Table created successfully"); routing_table_destroy(table); TEST_PASS("Table destroyed successfully"); return 0; } // Test 2: Insert and lookup basic routes static int test_routing_insert_lookup_basic(void) { printf("\n=== Test 2: Insert and Lookup Basic Routes ===\n"); struct routing_table* table = routing_table_create(); TEST_ASSERT(table != NULL, "Table creation failed"); // Insert default route struct route_entry default_route = { .network = 0, // 0.0.0.0 .prefix_length = 0, .next_hop_ip = ip_to_uint32("192.168.1.1"), .type = ROUTE_TYPE_STATIC, .flags = ROUTE_FLAG_ACTIVE }; bool result = routing_table_insert(table, &default_route); TEST_ASSERT(result == true, "Failed to insert default route"); TEST_ASSERT(table->count == 1, "Count should be 1"); TEST_PASS("Default route inserted"); // Insert LAN route struct route_entry lan_route = { .network = ip_to_uint32("192.168.1.0"), .prefix_length = 24, .next_hop_ip = 0, // Directly connected .type = ROUTE_TYPE_LOCAL, .flags = ROUTE_FLAG_ACTIVE }; result = routing_table_insert(table, &lan_route); TEST_ASSERT(result == true, "Failed to insert LAN route"); TEST_ASSERT(table->count == 2, "Count should be 2"); TEST_PASS("LAN route inserted"); // Test lookup for LAN IP struct route_entry found_route; uint32_t test_ip = ip_to_uint32("192.168.1.100"); result = routing_table_lookup(table, test_ip, &found_route); TEST_ASSERT(result == true, "Failed to lookup LAN IP"); TEST_ASSERT(found_route.network == lan_route.network, "Wrong route found"); TEST_PASS("LAN IP lookup correct"); // Test lookup for external IP (should use default) uint32_t external_ip = ip_to_uint32("8.8.8.8"); result = routing_table_lookup(table, external_ip, &found_route); TEST_ASSERT(result == true, "Failed to lookup external IP"); TEST_ASSERT(found_route.network == default_route.network, "Default route not found"); TEST_PASS("Default route lookup correct"); routing_table_destroy(table); TEST_PASS("Table destroyed"); return 0; } // Test 3: Longest Prefix Match static int test_routing_longest_prefix_match(void) { printf("\n=== Test 3: Longest Prefix Match ===\n"); struct routing_table* table = routing_table_create(); TEST_ASSERT(table != NULL, "Table creation failed"); // Insert multiple overlapping routes struct route_entry route_24 = { .network = ip_to_uint32("10.0.0.0"), .prefix_length = 24, .next_hop_ip = ip_to_uint32("192.168.1.1"), .type = ROUTE_TYPE_STATIC }; routing_table_insert(table, &route_24); struct route_entry route_16 = { .network = ip_to_uint32("10.0.0.0"), .prefix_length = 16, .next_hop_ip = ip_to_uint32("192.168.1.2"), .type = ROUTE_TYPE_STATIC }; routing_table_insert(table, &route_16); struct route_entry route_8 = { .network = ip_to_uint32("10.0.0.0"), .prefix_length = 8, .next_hop_ip = ip_to_uint32("192.168.1.3"), .type = ROUTE_TYPE_STATIC }; routing_table_insert(table, &route_8); TEST_ASSERT(table->count == 3, "Should have 3 routes"); TEST_PASS("3 overlapping routes inserted"); // Lookup should find /24 route (longest prefix) struct route_entry found; uint32_t test_ip = ip_to_uint32("10.0.0.50"); bool result = routing_table_lookup(table, test_ip, &found); TEST_ASSERT(result == true, "Lookup failed"); TEST_ASSERT(found.prefix_length == 24, "Should find /24 route"); TEST_ASSERT(found.next_hop_ip == route_24.next_hop_ip, "Wrong next hop"); TEST_PASS("Longest prefix match works"); routing_table_destroy(table); return 0; } // Test 4: Delete routes static int test_routing_delete(void) { printf("\n=== Test 4: Delete Routes ===\n"); struct routing_table* table = routing_table_create(); // Insert 3 routes struct route_entry routes[3] = { {ip_to_uint32("10.0.1.0"), 24, ip_to_uint32("192.168.1.1"), NULL, ROUTE_TYPE_STATIC}, {ip_to_uint32("10.0.2.0"), 24, ip_to_uint32("192.168.1.2"), NULL, ROUTE_TYPE_STATIC}, {ip_to_uint32("10.0.3.0"), 24, ip_to_uint32("192.168.1.3"), NULL, ROUTE_TYPE_STATIC} }; for (int i = 0; i < 3; i++) { routing_table_insert(table, &routes[i]); } TEST_ASSERT(table->count == 3, "Should have 3 routes"); TEST_PASS("3 routes inserted"); // Delete middle route bool result = routing_table_delete(table, routes[1].network, routes[1].prefix_length, routes[1].next_hop_ip); TEST_ASSERT(result == true, "Delete failed"); TEST_ASSERT(table->count == 2, "Count should be 2"); TEST_PASS("Route deleted"); // Verify deleted route not found, but other routes still work struct route_entry found; result = routing_table_lookup(table, ip_to_uint32("10.0.1.50"), &found); TEST_ASSERT(result == true, "Should find remaining route 1"); TEST_ASSERT(found.network == routes[0].network, "Should find route 1"); result = routing_table_lookup(table, ip_to_uint32("10.0.3.50"), &found); TEST_ASSERT(result == true, "Should find remaining route 3"); TEST_ASSERT(found.network == routes[2].network, "Should find route 3"); TEST_PASS("Remaining routes functional"); routing_table_destroy(table); return 0; } // Test 5: Route types and validation static int test_routing_types_validation(void) { printf("\n=== Test 5: Route Types and Validation ===\n"); struct routing_table* table = routing_table_create(); // Insert different route types struct route_entry static_route = { .network = ip_to_uint32("192.168.1.0"), .prefix_length = 24, .next_hop_ip = ip_to_uint32("10.0.0.1"), .next_hop = NULL, .type = ROUTE_TYPE_STATIC }; routing_table_insert(table, &static_route); struct route_entry local_route = { .network = ip_to_uint32("192.168.2.0"), .prefix_length = 24, .next_hop_ip = 0, .next_hop = NULL, .type = ROUTE_TYPE_LOCAL }; routing_table_insert(table, &local_route); struct route_entry dynamic_route = { .network = ip_to_uint32("192.168.3.0"), .prefix_length = 24, .next_hop_ip = ip_to_uint32("10.0.0.2"), .next_hop = NULL, .type = ROUTE_TYPE_DYNAMIC }; routing_table_insert(table, &dynamic_route); TEST_ASSERT(table->count == 3, "Should have 3 routes"); TEST_PASS("Different route types inserted"); // Validate routes bool result = routing_validate_route(table, static_route.network, static_route.prefix_length, ROUTE_TYPE_STATIC); TEST_ASSERT(result == true, "Static route validation failed"); TEST_PASS("Route validation works"); routing_table_destroy(table); return 0; } // Test 6: Statistics static int test_routing_statistics(void) { printf("\n=== Test 6: Statistics ===\n"); struct routing_table* table = routing_table_create(); // Initial stats TEST_ASSERT(table->stats.total_routes == 0, "Initial total_routes should be 0"); TEST_ASSERT(table->stats.routes_added == 0, "Initial routes_added should be 0"); TEST_PASS("Initial stats zero"); // Add routes struct route_entry route = { .network = ip_to_uint32("10.0.0.0"), .prefix_length = 24, .next_hop_ip = ip_to_uint32("192.168.1.1"), .type = ROUTE_TYPE_STATIC }; routing_table_insert(table, &route); TEST_ASSERT(table->stats.total_routes == 1, "total_routes should be 1"); TEST_ASSERT(table->stats.routes_added == 1, "routes_added should be 1"); TEST_ASSERT(table->stats.static_routes == 1, "static_routes should be 1"); TEST_PASS("Stats updated on insert"); // Perform lookups struct route_entry found; for (int i = 0; i < 5; i++) { routing_table_lookup(table, ip_to_uint32("10.0.0.100"), &found); } TEST_ASSERT(table->stats.lookup_count == 5, "lookup_count should be 5"); TEST_ASSERT(table->stats.hit_count == 5, "hit_count should be 5"); TEST_ASSERT(table->stats.routes_lookup_hits == 5, "routes_lookup_hits should be 5"); TEST_PASS("Lookup stats tracked"); routing_table_destroy(table); return 0; } // Test 7: Edge cases static int test_routing_edge_cases(void) { printf("\n=== Test 7: Edge Cases ===\n"); struct routing_table* table = routing_table_create(); // Lookup in empty table struct route_entry found; bool result = routing_table_lookup(table, ip_to_uint32("10.0.0.1"), &found); TEST_ASSERT(result == false, "Lookup in empty table should fail"); TEST_PASS("Empty table lookup handled"); // Delete non-existent route result = routing_table_delete(table, ip_to_uint32("10.0.0.0"), 24, 0); TEST_ASSERT(result == false, "Delete non-existent should fail"); TEST_PASS("Delete non-existent handled"); // Insert with prefix 32 struct route_entry host_route = { .network = ip_to_uint32("10.0.0.100"), .prefix_length = 32, .next_hop_ip = ip_to_uint32("192.168.1.1"), .type = ROUTE_TYPE_STATIC }; result = routing_table_insert(table, &host_route); TEST_ASSERT(result == true, "Failed to insert /32 route"); TEST_ASSERT(table->count == 1, "Should have 1 route"); TEST_PASS("/32 route inserted"); // Insert with prefix 0 (default) struct route_entry default_route = { .network = 0, .prefix_length = 0, .next_hop_ip = ip_to_uint32("192.168.1.1"), .type = ROUTE_TYPE_STATIC }; result = routing_table_insert(table, &default_route); TEST_ASSERT(result == true, "Failed to insert default route"); TEST_ASSERT(table->count == 2, "Should have 2 routes"); TEST_PASS("Default route inserted"); routing_table_destroy(table); return 0; } // Test 8: Performance test (basic) static int test_routing_performance(void) { printf("\n=== Test 8: Performance ===\n"); struct routing_table* table = routing_table_create(); // Insert 1000 routes struct timeval start, end; gettimeofday(&start, NULL); for (int i = 0; i < 1000; i++) { struct route_entry route = { .network = htonl((10 << 24) | i), // 10.0.i.0 .prefix_length = 24, .next_hop_ip = ip_to_uint32("192.168.1.1"), .type = ROUTE_TYPE_STATIC }; routing_table_insert(table, &route); } gettimeofday(&end, NULL); long insert_time_us = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_usec); printf(" Inserted 1000 routes in %ld us (%ld ns/op)\n", insert_time_us, insert_time_us * 1000 / 1000); TEST_PASS("Bulk insert completed"); // Perform 10000 lookups gettimeofday(&start, NULL); struct route_entry found; for (int i = 0; i < 10000; i++) { uint32_t ip = htonl((10 << 24) | (i % 1000)); // Random IPs in 10.0.x.x range routing_table_lookup(table, ip, &found); } gettimeofday(&end, NULL); long lookup_time_us = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_usec); printf(" Performed 10000 lookups in %ld us (%ld ns/op)\n", lookup_time_us, lookup_time_us * 1000 / 10000); TEST_PASS("Bulk lookup completed"); routing_table_destroy(table); TEST_PASS("Performance test passed"); return 0; } // Main test runner int main(void) { printf("╔═══════════════════════════════════════════════════════════════╗\n"); printf("║ uTun Routing Module Comprehensive Tests ║\n"); printf("╚═══════════════════════════════════════════════════════════════╝\n"); int tests_passed = 0; int total_tests = 0; struct test_case { const char* name; int (*func)(void); } test_cases[] = { {"Create/Destroy", test_routing_table_create_destroy}, {"Insert/Lookup Basic", test_routing_insert_lookup_basic}, {"Longest Prefix Match", test_routing_longest_prefix_match}, {"Delete Routes", test_routing_delete}, {"Route Types & Validation", test_routing_types_validation}, {"Statistics", test_routing_statistics}, {"Edge Cases", test_routing_edge_cases}, {"Performance", test_routing_performance}, {NULL, NULL} }; for (int i = 0; test_cases[i].func != NULL; i++) { total_tests++; printf("\n[TEST %d/%d] Running: %s\n", i + 1, 8, test_cases[i].name); if (test_cases[i].func() == 0) { tests_passed++; printf("[TEST %d/%d] ✓ PASSED\n", i + 1, 8); } else { printf("[TEST %d/%d] ✗ FAILED\n", i + 1, 8); } } printf("\n╔═══════════════════════════════════════════════════════════════╗\n"); printf("║ TEST SUMMARY ║\n"); printf("╠═══════════════════════════════════════════════════════════════╣\n"); printf("║ Tests Passed: %-3d / %-3d ║\n", tests_passed, total_tests); printf("║ Coverage: Routing Module Core Functions ║\n"); printf("╚═══════════════════════════════════════════════════════════════╝\n"); return (tests_passed == total_tests) ? 0 : 1; }