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.
 
 
 
 
 
 

414 lines
15 KiB

// tests/test_routing_full.c - Comprehensive routing table tests
#include "routing.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <arpa/inet.h>
#include <sys/time.h>
#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;
}