diff --git a/.gitignore b/.gitignore index adf376bc..80246852 100644 --- a/.gitignore +++ b/.gitignore @@ -71,9 +71,13 @@ tests/logs/ # Mass update logs logs/ -# Test binaries (files without extension in tests/) +# Test binaries (executables without extension), but track source/config files tests/test_* +!tests/test_*.c +!tests/test_*.conf +!tests/test_*.json tests/bench_* +!tests/bench_*.c # All log files *.log diff --git a/tests/test_ipv6_sockets.c b/tests/test_ipv6_sockets.c new file mode 100644 index 00000000..7b86f474 --- /dev/null +++ b/tests/test_ipv6_sockets.c @@ -0,0 +1,348 @@ +/** + * @file test_ipv6_sockets.c + * @brief Тест IPv6 сокетов: подключение, nodeinfo, BGP-обмен + * + * Проверяет: + * - Создание и бинд AF_INET6 сокета + * - IPv6 подключение клиент→сервер + * - Заполнение local_v6_sockets/addrs/subnets в nodeinfo + * - Корректность NODEINFO_IPV6_SOCKET_META и NODEINFO_IPV6_ADDR + * - Передачу IPv6 адресов через BGP соседнему узлу + */ + +#include +#include +#include +#include +#include +#include +#include "../lib/platform_compat.h" +#include "test_utils.h" + +#ifdef _WIN32 +#include +#include +#else +#include +#endif + +#include "../src/etcp.h" +#include "../src/etcp_connections.h" +#include "../src/config_parser.h" +#include "../src/utun_instance.h" +#include "../src/routing.h" +#include "../src/route_lib.h" +#include "../src/route_bgp.h" +#include "../src/route_node.h" +#include "../src/tun_if.h" +#include "../src/secure_channel.h" +#include "../lib/u_async.h" +#include "../lib/debug_config.h" + +#define TEST_TIMEOUT_MS 8000 + +static struct UTUN_INSTANCE* server_instance = NULL; +static struct UTUN_INSTANCE* client_instance = NULL; +static struct UASYNC* ua = NULL; +static int test_phase = 0; +static void* monitor_timer = NULL; +static void* timeout_timer = NULL; + +static char temp_dir[] = "/tmp/utun_v6test_XXXXXX"; +static char server_config_path[256]; +static char client_config_path[256]; + +static const char* server_config = + "[global]\n" + "my_node_name=server_v6\n" + "my_node_id=0xAAAA000000000001\n" + "my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n" + "my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n" + "tun_ip=10.99.0.1/24\n" + "tun_ifname=tun99\n" + "tun_test_mode=1\n" + "\n" + "[routing]\n" + "my_subnet=2001:db8:1::/48\n" + "\n" + "[server: v6srv]\n" + "addr=[::1]:40111\n" + "type=public\n" + "\n" + "[allowed_keys]\n" + "allow_all=1\n"; + +static const char* client_config = + "[global]\n" + "my_node_name=client_v6\n" + "my_node_id=0xBBBB000000000002\n" + "my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2\n" + "my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n" + "tun_ip=10.99.0.2/24\n" + "tun_ifname=tun98\n" + "tun_test_mode=1\n" + "\n" + "[server: v6srv]\n" + "addr=[::1]:40112\n" + "type=public\n" + "\n" + "[client: v6client]\n" + "keepalive=1\n" + "peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n" + "link=v6srv:[::1]:40111\n"; + +static int create_temp_configs(void) { + if (test_mkdtemp(temp_dir) != 0) { + fprintf(stderr, "Failed to create temp directory\n"); + return -1; + } + snprintf(server_config_path, sizeof(server_config_path), "%s/server.conf", temp_dir); + snprintf(client_config_path, sizeof(client_config_path), "%s/client.conf", temp_dir); + + FILE* f = fopen(server_config_path, "w"); + if (!f) { fprintf(stderr, "Failed to create server config\n"); return -1; } + fprintf(f, "%s", server_config); + fclose(f); + + f = fopen(client_config_path, "w"); + if (!f) { fprintf(stderr, "Failed to create client config\n"); test_unlink(server_config_path); return -1; } + fprintf(f, "%s", client_config); + fclose(f); + + return 0; +} + +static void cleanup_temp_configs(void) { + if (server_config_path[0]) test_unlink(server_config_path); + if (client_config_path[0]) test_unlink(client_config_path); + if (temp_dir[0]) test_rmdir(temp_dir); +} + +static int is_connection_established(struct UTUN_INSTANCE* inst) { + if (!inst) return 0; + struct ETCP_CONN* conn = inst->connections; + while (conn) { + struct ETCP_LINK* link = conn->links; + while (link) { + if (link->initialized) return 1; + link = link->next; + } + conn = conn->next; + } + return 0; +} + +static int verify_ipv6_local_nodeinfo(const char* name, struct UTUN_INSTANCE* inst, + int expect_socks, int expect_addrs, int expect_subnets) { + if (!inst || !inst->bgp || !inst->bgp->local_node) { + printf("FAIL [%s]: no bgp/local_node\n", name); + return 0; + } + struct NODEINFO* ni = &inst->bgp->local_node->node; + struct ETCP_SOCKET* es = inst->etcp_sockets; + + // Check socket family + int v6_sock_found = 0; + while (es) { + if (es->local_addr.ss_family == AF_INET6) { v6_sock_found = 1; break; } + es = es->next; + } + if (!v6_sock_found) { printf("FAIL [%s]: no AF_INET6 socket\n", name); return 0; } + printf("PASS [%s]: AF_INET6 socket exists\n", name); + + // Check nodeinfo counts + if ((int)ni->local_v6_sockets != expect_socks) { + printf("FAIL [%s]: local_v6_sockets=%d expected=%d\n", name, ni->local_v6_sockets, expect_socks); return 0; + } + printf("PASS [%s]: local_v6_sockets=%d\n", name, ni->local_v6_sockets); + + if ((int)ni->local_v6_addrs != expect_addrs) { + printf("FAIL [%s]: local_v6_addrs=%d expected=%d\n", name, ni->local_v6_addrs, expect_addrs); return 0; + } + printf("PASS [%s]: local_v6_addrs=%d\n", name, ni->local_v6_addrs); + + if ((int)ni->local_v6_subnets != expect_subnets) { + printf("FAIL [%s]: local_v6_subnets=%d expected=%d\n", name, ni->local_v6_subnets, expect_subnets); return 0; + } + printf("PASS [%s]: local_v6_subnets=%d\n", name, ni->local_v6_subnets); + + // Verify socket meta + const struct NODEINFO_IPV6_SOCKET_META* meta; + int mc = get_node_v6_sockets_meta(inst->bgp->local_node, &meta); + if (mc != expect_socks || !meta) { printf("FAIL [%s]: v6 sockets meta retrieval failed\n", name); return 0; } + for (int i = 0; i < mc; i++) { + if (meta[i].type != CFG_SERVER_TYPE_PUBLIC) { + printf("FAIL [%s]: v6 socket meta[%d] type=%d expected=%d\n", name, i, meta[i].type, CFG_SERVER_TYPE_PUBLIC); return 0; + } + printf("PASS [%s]: v6 socket meta[%d] id=%d type=%d\n", name, i, meta[i].id, meta[i].type); + } + + // Verify IPv6 addresses + const struct NODEINFO_IPV6_ADDR* addrs; + int ac = get_node_v6_addrs(inst->bgp->local_node, &addrs); + if (ac != expect_addrs || !addrs) { printf("FAIL [%s]: v6 addrs retrieval failed\n", name); return 0; } + for (int i = 0; i < ac; i++) { + if (addrs[i].port == 0) { printf("FAIL [%s]: v6 addr port=0\n", name); return 0; } + if (addrs[i].type != ADDR_TYPE_INTERFACE) { + printf("FAIL [%s]: v6 addr[%d] type=%d expected=%d(INTERFACE)\n", name, i, addrs[i].type, ADDR_TYPE_INTERFACE); return 0; + } + if (addrs[i].type != ADDR_TYPE_INTERFACE) { + printf("FAIL [%s]: v6 addr[%d] type=%d expected=%d(INTERFACE)\n", name, i, addrs[i].type, ADDR_TYPE_INTERFACE); return 0; + } + printf("PASS [%s]: v6 addr[%d] port=%d type=%d socket_id=%d\n", name, i, addrs[i].port, addrs[i].type, addrs[i].socket_id); + } + + // Verify IPv6 subnets + if (expect_subnets > 0) { + const struct NODEINFO_IPV6_SUBNET* subs; + const uint8_t* dyn_start = (const uint8_t*)ni + sizeof(struct NODEINFO); + const uint8_t* p = dyn_start + ni->node_name_len; + p += ni->local_v4_sockets * sizeof(struct NODEINFO_IPV4_SOCKET_META); + p += ni->local_v4_addrs * sizeof(struct NODEINFO_IPV4_ADDR); + p += ni->local_v6_sockets * sizeof(struct NODEINFO_IPV6_SOCKET_META); + p += ni->local_v6_addrs * sizeof(struct NODEINFO_IPV6_ADDR); + p += ni->local_v4_subnets * sizeof(struct NODEINFO_IPV4_SUBNET); + subs = (const struct NODEINFO_IPV6_SUBNET*)p; + uint8_t expected_sub[16] = {0x20,0x01,0x0d,0xb8,0x00,0x01,0,0,0,0,0,0,0,0,0,0}; + if (memcmp(subs[0].addr, expected_sub, 16) != 0) { + printf("FAIL [%s]: v6 subnet != 2001:db8:1::/48\n", name); return 0; + } + if (subs[0].prefix_length != 48) { + printf("FAIL [%s]: v6 subnet prefix=%d expected=48\n", name, subs[0].prefix_length); return 0; + } + printf("PASS [%s]: v6 subnet 2001:db8:1::/48\n", name); + } + + return 1; +} + +static int verify_remote_v6_nodeinfo(const char* name, struct UTUN_INSTANCE* inst, uint64_t peer_node_id) { + if (!inst || !inst->bgp) { + printf("FAIL [%s]: no bgp\n", name); + return 0; + } + struct NODEINFO_Q* nq = route_bgp_get_node(inst->bgp, peer_node_id); + if (!nq) { + printf("FAIL [%s]: remote node %016llx not found\n", name, (unsigned long long)peer_node_id); + return 0; + } + printf("PASS [%s]: remote node %016llx found\n", name, (unsigned long long)peer_node_id); + + if (nq->node.local_v6_sockets == 0) { + printf("FAIL [%s]: remote node has no v6 sockets\n", name); + return 0; + } + printf("PASS [%s]: remote node v6_sockets=%d\n", name, nq->node.local_v6_sockets); + + if (nq->node.local_v6_addrs == 0) { + printf("FAIL [%s]: remote node has no v6 addrs\n", name); + return 0; + } + printf("PASS [%s]: remote node v6_addrs=%d\n", name, nq->node.local_v6_addrs); + + const struct NODEINFO_IPV6_ADDR* addrs; + int ac = get_node_v6_addrs(nq, &addrs); + if (ac > 0 && addrs) { + uint8_t loopback[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}; + if (memcmp(addrs[0].addr, loopback, 16) != 0) + { printf("FAIL [%s]: remote v6 addr not ::1\n", name); return 0; } + printf("PASS [%s]: remote v6 addr verified ::1 port=%d\n", name, addrs[0].port); + } + return 1; +} + +static void timeout_cb(void* arg) { + (void)arg; + if (test_phase < 3) { + printf("=== TEST TIMEOUT ===\n"); + test_phase = 4; + if (monitor_timer) { uasync_cancel_timeout(ua, monitor_timer); monitor_timer = NULL; } + } +} + +static void monitor(void* arg) { + (void)arg; + if (test_phase >= 3) { monitor_timer = NULL; return; } + + switch (test_phase) { + case 0: { + int srv_ok = is_connection_established(server_instance); + int cli_ok = is_connection_established(client_instance); + if (srv_ok && cli_ok) { + printf("Connection established, verifying nodeinfo...\n"); + test_phase = 1; + monitor_timer = uasync_set_timeout(ua, 500, NULL, monitor, "v6_monitor"); + return; + } + break; + } + case 1: { + int server_ok = verify_ipv6_local_nodeinfo("server_local", server_instance, 1, 1, 1); + int client_ok = verify_ipv6_local_nodeinfo("client_local", client_instance, 1, 1, 0); + if (!server_ok || !client_ok) { test_phase = 4; return; } + + printf("Local nodeinfo verified, waiting for BGP exchange...\n"); + test_phase = 2; + monitor_timer = uasync_set_timeout(ua, 500, NULL, monitor, "v6_monitor"); + return; + } + case 2: { + int ok = verify_remote_v6_nodeinfo("client_remote", client_instance, 0xAAAA000000000001ULL); + if (!ok) { test_phase = 4; return; } + + printf("=== ALL CHECKS PASSED ===\n"); + test_phase = 3; + if (timeout_timer) { uasync_cancel_timeout(ua, timeout_timer); timeout_timer = NULL; } + return; + } + } + + if (test_phase < 3) { + monitor_timer = uasync_set_timeout(ua, 100, NULL, monitor, "v6_monitor"); + } +} + +int main(void) { + printf("=== IPv6 Sockets + Nodeinfo Test ===\n\n"); + + if (create_temp_configs() != 0) { + fprintf(stderr, "Failed to create config files\n"); + return 1; + } + + debug_config_init(); + debug_set_level(DEBUG_LEVEL_ERROR); + utun_instance_set_tun_init_enabled(0); + + ua = uasync_create(); + if (!ua) { fprintf(stderr, "uasync_create failed\n"); return 1; } + + server_instance = utun_instance_create(ua, server_config_path); + if (!server_instance) { fprintf(stderr, "Server instance create failed\n"); return 1; } + if (utun_instance_init(server_instance) < 0) { fprintf(stderr, "Server init failed\n"); return 1; } + + client_instance = utun_instance_create(ua, client_config_path); + if (!client_instance) { fprintf(stderr, "Client instance create failed\n"); return 1; } + if (utun_instance_init(client_instance) < 0) { fprintf(stderr, "Client init failed\n"); return 1; } + + monitor_timer = uasync_set_timeout(ua, 100, NULL, monitor, "v6_monitor"); + timeout_timer = uasync_set_timeout(ua, TEST_TIMEOUT_MS, NULL, timeout_cb, "v6_timeout"); + + while (test_phase < 3) uasync_poll(ua, -1); + + if (timeout_timer) { uasync_cancel_timeout(ua, timeout_timer); timeout_timer = NULL; } + if (monitor_timer) { uasync_cancel_timeout(ua, monitor_timer); monitor_timer = NULL; } + + if (server_instance) { server_instance->running = 0; utun_instance_destroy(server_instance); } + if (client_instance) { client_instance->running = 0; utun_instance_destroy(client_instance); } + if (ua) { uasync_destroy(ua, 0); ua = NULL; } + + cleanup_temp_configs(); + + if (test_phase == 3) { + printf("\n=== TEST PASSED ===\n"); + return 0; + } + printf("\n=== TEST FAILED ===\n"); + return 1; +} diff --git a/tests/test_radix.c b/tests/test_radix.c new file mode 100644 index 00000000..a62c7325 --- /dev/null +++ b/tests/test_radix.c @@ -0,0 +1,523 @@ +#include +#include +#include +#include +#include +#include "../lib/radix.h" +#include "../lib/debug_config.h" +#include "../lib/mem.h" + +#define OFF 1 /* skip first byte (len) in radix comparisons */ +#define KEY_SIZE 5 /* 1 byte len + 4 bytes IPv4 */ +#define STRESS_N_MIN 1 +#define STRESS_N_MAX 300 +#define STRESS_LOOKUPS 100000 +#define STRESS_SEED ((unsigned)time(NULL)) + +static int tests_run = 0; +static int tests_failed = 0; + +#define RUN_TEST(fn) do { \ + tests_run++; \ + fn(); \ +} while(0) + +static void make_key(uint8_t out[KEY_SIZE], uint32_t addr) { + out[0] = KEY_SIZE; + memcpy(out + 1, &addr, 4); +} + +static void make_mask(uint8_t out[KEY_SIZE], int prefix_len) { + out[0] = KEY_SIZE; + uint32_t m = prefix_len == 0 ? 0 : (uint32_t)(0xFFFFFFFFULL << (32 - prefix_len)); + memcpy(out + 1, &m, 4); +} + +static int is_root_or_null(struct radix_node *rn) { + return rn == NULL || (rn->rn_flags & RNF_ROOT); +} + +/* ──── Edge case tests ──── */ + +static void test_init_detach(void) { + void *head = NULL; + struct radix_node_head *rnh; + + assert(rn_inithead(&head, OFF) == 1); + assert(head != NULL); + rnh = (struct radix_node_head *)head; + assert(rnh->rh.rnh_treetop != NULL); + assert(rnh->rh.rnh_masks != NULL); + + assert(rn_detachhead(&head) == 1); + assert(head == NULL); +} + +static void test_empty_lookup(void) { + void *head = NULL; + rn_inithead(&head, OFF); + struct radix_node_head *rnh = (struct radix_node_head *)head; + + uint8_t key[KEY_SIZE]; make_key(key, 0x0A000001); // 10.0.0.1 + uint8_t msk[KEY_SIZE]; make_mask(msk, 24); + + assert(rn_lookup(key, msk, &rnh->rh) == NULL); + assert(rn_match(key, &rnh->rh) == NULL); + + rn_detachhead(&head); +} + +static void test_exact_match(void) { + void *head = NULL; + rn_inithead(&head, OFF); + struct radix_node_head *rnh = (struct radix_node_head *)head; + + uint8_t key[KEY_SIZE]; make_key(key, 0x0A000000); // 10.0.0.0 + uint8_t msk[KEY_SIZE]; make_mask(msk, 24); + struct radix_node nodes[2]; + + struct radix_node *rn = rn_addroute(key, msk, &rnh->rh, nodes); + assert(rn != NULL); + + struct radix_node *found = rn_lookup(key, msk, &rnh->rh); + assert(found != NULL); + assert(memcmp(found->rn_key, key, KEY_SIZE) == 0); + assert(found->rn_mask != NULL); + // stored mask may be trimmed: compare data (skip length byte) + assert(memcmp(found->rn_mask + 1, msk + 1, found->rn_mask[0] - 1) == 0); + + rn_detachhead(&head); +} + +static void test_host_route(void) { + void *head = NULL; + rn_inithead(&head, OFF); + struct radix_node_head *rnh = (struct radix_node_head *)head; + + uint8_t key[KEY_SIZE]; make_key(key, 0x0A000001); // 10.0.0.1 + struct radix_node nodes[2]; + + struct radix_node *rn = rn_addroute(key, NULL, &rnh->rh, nodes); + assert(rn != NULL); + + struct radix_node *found = rn_lookup(key, NULL, &rnh->rh); + assert(found != NULL); + assert(memcmp(found->rn_key, key, KEY_SIZE) == 0); + assert(found->rn_mask == NULL); + + // lookup with a mask should fail for host routes + uint8_t msk[KEY_SIZE]; make_mask(msk, 32); + assert(rn_lookup(key, msk, &rnh->rh) == NULL); + + rn_detachhead(&head); +} + +static void test_longest_prefix(void) { + void *head = NULL; + rn_inithead(&head, OFF); + struct radix_node_head *rnh = (struct radix_node_head *)head; + + uint8_t key24[KEY_SIZE]; make_key(key24, 0x0A000000); + uint8_t msk24[KEY_SIZE]; make_mask(msk24, 24); + uint8_t key32[KEY_SIZE]; make_key(key32, 0x0A000001); + uint8_t msk32[KEY_SIZE]; make_mask(msk32, 32); + struct radix_node n1[2], n2[2]; + + assert(rn_addroute(key24, msk24, &rnh->rh, n1) != NULL); + assert(rn_addroute(key32, msk32, &rnh->rh, n2) != NULL); + + uint8_t lookup_key[KEY_SIZE]; make_key(lookup_key, 0x0A000001); + + struct radix_node *found = rn_match(lookup_key, &rnh->rh); + assert(found != NULL); + assert(found->rn_mask != NULL); + assert(memcmp(found->rn_mask + 1, msk32 + 1, found->rn_mask[0] - 1) == 0); + + // for 10.0.0.2, /32 doesn't match, but /24 does + make_key(lookup_key, 0x0A000002); + found = rn_match(lookup_key, &rnh->rh); + assert(found != NULL); + assert(memcmp(found->rn_mask + 1, msk24 + 1, found->rn_mask[0] - 1) == 0); + + rn_detachhead(&head); +} + +static int walktree_counter; +static int walktree_callback(struct radix_node *rn, void *arg) { + (void)arg; + if (!(rn->rn_flags & RNF_ROOT)) + walktree_counter++; + return 0; +} + +static void test_duplicate_key_walktree(void) { + void *head = NULL; + rn_inithead(&head, OFF); + struct radix_node_head *rnh = (struct radix_node_head *)head; + + uint8_t key[KEY_SIZE]; make_key(key, 0x0A000000); // 10.0.0.0 + uint8_t msk24[KEY_SIZE]; make_mask(msk24, 24); + uint8_t msk25[KEY_SIZE]; make_mask(msk25, 25); + struct radix_node n1[2], n2[2]; + + assert(rn_addroute(key, msk24, &rnh->rh, n1) != NULL); + assert(rn_addroute(key, msk25, &rnh->rh, n2) != NULL); + + walktree_counter = 0; + rn_walktree(&rnh->rh, walktree_callback, NULL); + assert(walktree_counter == 2); + + rn_detachhead(&head); +} + +static void test_delete_simple(void) { + void *head = NULL; + rn_inithead(&head, OFF); + struct radix_node_head *rnh = (struct radix_node_head *)head; + + uint8_t key[KEY_SIZE]; make_key(key, 0x0A000000); + uint8_t msk[KEY_SIZE]; make_mask(msk, 24); + struct radix_node nodes[2]; + + rn_addroute(key, msk, &rnh->rh, nodes); + + struct radix_node *deleted = rn_delete(key, msk, &rnh->rh); + assert(deleted != NULL); + + assert(rn_lookup(key, msk, &rnh->rh) == NULL); + + rn_detachhead(&head); +} + +static void test_delete_chain_middle(void) { + void *head = NULL; + rn_inithead(&head, OFF); + struct radix_node_head *rnh = (struct radix_node_head *)head; + + uint8_t key[KEY_SIZE]; make_key(key, 0x0A000000); + uint8_t msk24[KEY_SIZE]; make_mask(msk24, 24); + uint8_t msk25[KEY_SIZE]; make_mask(msk25, 25); + struct radix_node n1[2], n2[2]; + + rn_addroute(key, msk24, &rnh->rh, n1); + rn_addroute(key, msk25, &rnh->rh, n2); + + // delete /25, /24 should remain + struct radix_node *deleted = rn_delete(key, msk25, &rnh->rh); + assert(deleted != NULL); + + // /25 gone + assert(rn_lookup(key, msk25, &rnh->rh) == NULL); + // /24 still there + assert(rn_lookup(key, msk24, &rnh->rh) != NULL); + + rn_detachhead(&head); +} + +static void test_overlapping_prefixes(void) { + void *head = NULL; + rn_inithead(&head, OFF); + struct radix_node_head *rnh = (struct radix_node_head *)head; + + uint8_t key16[KEY_SIZE]; make_key(key16, 0x0A000000); // 10.0.0.0/16 + uint8_t msk16[KEY_SIZE]; make_mask(msk16, 16); + uint8_t key24[KEY_SIZE]; make_key(key24, 0x0A000100); // 10.0.1.0/24 (within /16) + uint8_t msk24[KEY_SIZE]; make_mask(msk24, 24); + struct radix_node n1[2], n2[2]; + + rn_addroute(key16, msk16, &rnh->rh, n1); + rn_addroute(key24, msk24, &rnh->rh, n2); + + // IP inside both /16 and /24 → returns /24 (more specific) + uint8_t lu[KEY_SIZE]; make_key(lu, 0x0A000105); // 10.0.1.5 + struct radix_node *found = rn_match(lu, &rnh->rh); + assert(found != NULL); + assert(memcmp(found->rn_mask + 1, msk24 + 1, found->rn_mask[0] - 1) == 0); + + // IP inside /16 but outside /24 → returns /16 + make_key(lu, 0x0A000005); // 10.0.0.5 + found = rn_match(lu, &rnh->rh); + assert(found != NULL); + assert(memcmp(found->rn_mask + 1, msk16 + 1, found->rn_mask[0] - 1) == 0); + + rn_detachhead(&head); +} + +static void test_walktree_count(void) { + void *head = NULL; + rn_inithead(&head, OFF); + struct radix_node_head *rnh = (struct radix_node_head *)head; + + uint8_t keys[5][KEY_SIZE]; + uint8_t msks[5][KEY_SIZE]; + struct radix_node nodes[5][2]; + for (int i = 0; i < 5; i++) { + make_key(keys[i], (uint32_t)(0x0A000000 + i * 0x10000)); + make_mask(msks[i], 24); + assert(rn_addroute(keys[i], msks[i], &rnh->rh, nodes[i]) != NULL); + } + + walktree_counter = 0; + rn_walktree(&rnh->rh, walktree_callback, NULL); + assert(walktree_counter == 5); + + rn_detachhead(&head); +} + +static void test_walktree_from_subtree(void) { + void *head = NULL; + rn_inithead(&head, OFF); + struct radix_node_head *rnh = (struct radix_node_head *)head; + + uint8_t key8[KEY_SIZE]; make_key(key8, 0x0A000000); + uint8_t msk8[KEY_SIZE]; make_mask(msk8, 8); + uint8_t key24a[KEY_SIZE]; make_key(key24a, 0x0A000000); + uint8_t msk24a[KEY_SIZE]; make_mask(msk24a, 24); + uint8_t key24b[KEY_SIZE]; make_key(key24b, 0x0A010000); + uint8_t msk24b[KEY_SIZE]; make_mask(msk24b, 24); + struct radix_node n1[2], n2[2], n3[2]; + + rn_addroute(key8, msk8, &rnh->rh, n1); + rn_addroute(key24a, msk24a, &rnh->rh, n2); + rn_addroute(key24b, msk24b, &rnh->rh, n3); + + // rn_walktree_from: with mask containing length byte in byte 0, + // the walk may include a broader range depending on mask bits + uint8_t from_key[KEY_SIZE]; make_key(from_key, 0x0A000000); + uint8_t from_msk[KEY_SIZE]; make_mask(from_msk, 8); + + walktree_counter = 0; + rn_walktree_from(&rnh->rh, from_key, from_msk, walktree_callback, NULL); + assert(walktree_counter > 0); + + rn_detachhead(&head); +} + +static void test_refines(void) { + uint8_t msk24[KEY_SIZE]; make_mask(msk24, 24); + uint8_t msk16[KEY_SIZE]; make_mask(msk16, 16); + uint8_t msk8[KEY_SIZE]; make_mask(msk8, 8); + + // /24 refines /16 (more specific) + assert(rn_refines(msk24, msk16) == 1); + // /16 does not refine /24 + assert(rn_refines(msk16, msk24) == 0); + // equal masks: neither refines the other + assert(rn_refines(msk24, msk24) == 0); + // /8 does not refine /16 + assert(rn_refines(msk8, msk16) == 0); + // /16 refines /8 + assert(rn_refines(msk16, msk8) == 1); +} + +static void test_large_key(void) { + void *head = NULL; + rn_inithead(&head, OFF); + struct radix_node_head *rnh = (struct radix_node_head *)head; + + // key of maximum length (RADIX_MAX_KEY_LEN = 32) + uint8_t key[32]; + uint8_t msk[32]; + key[0] = 32; + msk[0] = 32; + for (int i = 1; i < 32; i++) { + key[i] = (uint8_t)(i * 7); + msk[i] = 0xFF; + } + + struct radix_node nodes[2]; + struct radix_node *rn = rn_addroute(key, msk, &rnh->rh, nodes); + assert(rn != NULL); + + struct radix_node *found = rn_match(key, &rnh->rh); + assert(found != NULL); + assert(memcmp(found->rn_key, key, 32) == 0); + + rn_detachhead(&head); +} + +static void test_delete_nonexistent(void) { + void *head = NULL; + rn_inithead(&head, OFF); + struct radix_node_head *rnh = (struct radix_node_head *)head; + + uint8_t key[KEY_SIZE]; make_key(key, 0x0A000000); + uint8_t msk[KEY_SIZE]; make_mask(msk, 24); + + struct radix_node *deleted = rn_delete(key, msk, &rnh->rh); + assert(deleted == NULL); + + rn_detachhead(&head); +} + +/* ──── Stress test ──── */ + +struct stress_route { + uint8_t key[KEY_SIZE]; + uint8_t mask[KEY_SIZE]; + int plen; /* prefix length 1..32 */ + int valid; /* 1 if successfully added */ +}; + +static int count_ones_32(uint32_t x) { + int n = 0; + while (x) { n++; x &= x - 1; } + return n; +} + +static int brute_match(uint32_t ip, struct stress_route *routes, int n, struct stress_route **out) { + *out = NULL; + int best_plen = -1; + int ambiguous = 0; + + for (int i = 0; i < n; i++) { + if (!routes[i].valid) continue; + uint32_t addr = *(uint32_t *)(routes[i].key + 1); + uint32_t m = *(uint32_t *)(routes[i].mask + 1); + if ((ip & m) == addr) { + int plen = routes[i].plen; + if (plen > best_plen) { + best_plen = plen; + *out = &routes[i]; + ambiguous = 0; + } else if (plen == best_plen) { + ambiguous = 1; + } + } + } + + return ambiguous ? -1 : 0; +} + +static void test_stress(void) { + unsigned seed = STRESS_SEED; + printf(" seed = %u\n", seed); + srand(seed); + + void *head = NULL; + rn_inithead(&head, OFF); + struct radix_node_head *rnh = (struct radix_node_head *)head; + + int N = rand() % (STRESS_N_MAX - STRESS_N_MIN + 1) + STRESS_N_MIN; + + struct radix_node *nodes = u_calloc((size_t)N, 2 * sizeof(struct radix_node)); + struct stress_route *routes = u_calloc((size_t)N, sizeof(struct stress_route)); + int nroutes = 0; + + for (int i = 0; i < N; i++) { + int plen = rand() % 32 + 1; + uint32_t raw_ip = (uint32_t)rand() | ((uint32_t)rand() << 16); + uint32_t m = plen == 0 ? 0 : (uint32_t)(0xFFFFFFFFULL << (32 - plen)); + uint32_t addr = raw_ip & m; + + make_key(routes[i].key, addr); + make_mask(routes[i].mask, plen); + routes[i].plen = plen; + + struct radix_node *rn = rn_addroute(routes[i].key, routes[i].mask, + &rnh->rh, nodes + 2 * i); + if (rn != NULL) { + routes[i].valid = 1; + nroutes++; + } + } + + printf(" routes added: %d (attempted: %d)\n", nroutes, N); + + int skipped = 0; + for (int iter = 0; iter < STRESS_LOOKUPS; iter++) { + uint32_t ip = (uint32_t)rand() | ((uint32_t)rand() << 16); + uint8_t lookup_key[KEY_SIZE]; + make_key(lookup_key, ip); + + struct stress_route *expected = NULL; + if (brute_match(ip, routes, N, &expected) < 0) { + skipped++; + continue; + } + + struct radix_node *rn = rn_match(lookup_key, &rnh->rh); + + if (expected == NULL) { + assert(is_root_or_null(rn)); + } else { + assert(rn != NULL); + assert(!(rn->rn_flags & RNF_ROOT)); + assert(rn->rn_mask != NULL); + uint8_t stored_mlen = rn->rn_mask[0]; + assert(stored_mlen >= 2); + if (memcmp(rn->rn_mask + 1, expected->mask + 1, stored_mlen - 1) != 0) { + fprintf(stderr, "MASK MISMATCH seed=%u ip=%08x plen=%d\n", + STRESS_SEED, ip, expected->plen); + fprintf(stderr, " exp_key=%02x%02x%02x%02x%02x\n", + expected->key[0], expected->key[1], expected->key[2], + expected->key[3], expected->key[4]); + fprintf(stderr, " exp_mask=%02x%02x%02x%02x%02x\n", + expected->mask[0], expected->mask[1], expected->mask[2], + expected->mask[3], expected->mask[4]); + fprintf(stderr, " got_key=%02x%02x%02x%02x%02x\n", + rn->rn_key[0], rn->rn_key[1], rn->rn_key[2], + rn->rn_key[3], rn->rn_key[4]); + fprintf(stderr, " got_mask=%02x%02x%02x%02x%02x mlen=%d\n", + rn->rn_mask[0], rn->rn_mask[1], rn->rn_mask[2], + rn->rn_mask[3], rn->rn_mask[4], stored_mlen); + fprintf(stderr, " all matching routes:\n"); + for (int ri = 0; ri < nroutes; ri++) { + if (!routes[ri].valid) continue; + uint32_t ra = *(uint32_t *)(routes[ri].key + 1); + uint32_t rm = *(uint32_t *)(routes[ri].mask + 1); + if ((ip & rm) == ra) + fprintf(stderr, " r[%d]: key=%02x%02x%02x%02x%02x mask=%02x%02x%02x%02x%02x plen=%d\n", + ri, routes[ri].key[0], routes[ri].key[1], routes[ri].key[2], + routes[ri].key[3], routes[ri].key[4], + routes[ri].mask[0], routes[ri].mask[1], routes[ri].mask[2], + routes[ri].mask[3], routes[ri].mask[4], routes[ri].plen); + } + assert(0); + } + if (memcmp(rn->rn_key, expected->key, KEY_SIZE) != 0) { + fprintf(stderr, + "KEY MISMATCH ip=%08x plen=%d exp_key=%02x%02x%02x%02x%02x " + "got_key=%02x%02x%02x%02x%02x\n", + ip, expected->plen, + expected->key[0], expected->key[1], expected->key[2], + expected->key[3], expected->key[4], + rn->rn_key[0], rn->rn_key[1], rn->rn_key[2], + rn->rn_key[3], rn->rn_key[4]); + assert(0); + } + } + } + + printf(" lookups: %d, skipped (ambiguous): %d, passed: %d\n", + STRESS_LOOKUPS, skipped, STRESS_LOOKUPS - skipped); + + u_free(routes); + u_free(nodes); + rn_detachhead(&head); +} + +int main(void) { + debug_config_init(); + printf("test_radix: seed = %u\n", STRESS_SEED); + + RUN_TEST(test_init_detach); + RUN_TEST(test_empty_lookup); + RUN_TEST(test_exact_match); + RUN_TEST(test_host_route); + RUN_TEST(test_longest_prefix); + RUN_TEST(test_duplicate_key_walktree); + RUN_TEST(test_delete_simple); + RUN_TEST(test_delete_chain_middle); + RUN_TEST(test_overlapping_prefixes); + RUN_TEST(test_walktree_count); + RUN_TEST(test_walktree_from_subtree); + RUN_TEST(test_refines); + RUN_TEST(test_large_key); + RUN_TEST(test_delete_nonexistent); + + RUN_TEST(test_stress); + + printf("ALL RADIX TESTS PASSED (%d tests)\n", tests_run); + return 0; +} diff --git a/tests/test_route6_lib.c b/tests/test_route6_lib.c new file mode 100644 index 00000000..c6e7a5ba --- /dev/null +++ b/tests/test_route6_lib.c @@ -0,0 +1,435 @@ +#include +#include +#include +#include +#include +#include "../src/route_node.h" +#include "../src/route6_lib.h" +#include "../lib/debug_config.h" +#include "../lib/mem.h" +#include "../lib/u_async.h" + +#define STRESS_NODES 30 +#define STRESS_LOOKUPS 50000 +#define STRESS_OPS 300 +#define MAX_SAVED 256 + +static int tests_run = 0; +#define RUN_TEST(fn) do { tests_run++; fn(); } while(0) + +/* ──── helpers ──── */ + +static struct NODEINFO_Q *make_node(uint64_t node_id, + const uint8_t addrs[][16], + const uint8_t *plens, int N) { + size_t dyn = N * sizeof(struct NODEINFO_IPV6_SUBNET); + struct NODEINFO_Q *nq = u_calloc(1, sizeof(*nq) + dyn); + nq->node.node_id = node_id; + nq->node.local_v6_subnets = (uint8_t)N; + struct NODEINFO_IPV6_SUBNET *subs = + (struct NODEINFO_IPV6_SUBNET *)((uint8_t *)&nq->node + sizeof(struct NODEINFO)); + for (int i = 0; i < N; i++) { + memcpy(subs[i].addr, addrs[i], 16); + subs[i].prefix_length = plens[i]; + } + return nq; +} + +static void free_node(struct NODEINFO_Q *nq) { u_free(nq); } + +static void make_key(uint8_t out[17], const uint8_t addr[16]) { + out[0] = 17; memcpy(out + 1, addr, 16); +} + +static int addr_cmp(const uint8_t a[16], const uint8_t b[16]) { + return memcmp(a, b, 16); +} + +/* ──── edge tests ──── */ + +static void test_insert_null_args(void) { + struct UASYNC *ua = uasync_create(); + struct ROUTE6_TABLE *t = route6_table_create(ua); + assert(t != NULL); + assert(route6_insert(NULL, NULL) == false); + assert(route6_insert(t, NULL) == false); + assert(route6_insert(NULL, t ? (struct NODEINFO_Q*)(uintptr_t)t : NULL) == false); + route6_table_destroy(t); + uasync_destroy(ua, 0); +} + +static void test_insert_zero_subnets(void) { + struct UASYNC *ua = uasync_create(); + struct ROUTE6_TABLE *t = route6_table_create(ua); + assert(t != NULL); + + int addr[16] = {0}; + uint8_t zero_plen = 0; + struct NODEINFO_Q *nq = make_node(1, (const uint8_t(*)[16])addr, &zero_plen, 0); + assert(route6_insert(t, nq) == false); + + const uint8_t any[16] = {0}; + assert(route6_lookup(t, any) == NULL); + + free_node(nq); + route6_table_destroy(t); + uasync_destroy(ua, 0); +} + +static void test_insert_duplicate(void) { + struct UASYNC *ua = uasync_create(); + struct ROUTE6_TABLE *t = route6_table_create(ua); + assert(t != NULL); + + uint8_t addr[16] = {0xfd, 0}; + uint8_t plen = 64; + struct NODEINFO_Q *nq = make_node(10, &addr, &plen, 1); + assert(route6_insert(t, nq) == true); + assert(route6_insert(t, nq) == true); // re-insert should not crash + + route6_lookup(t, addr); // route still reachable + + free_node(nq); + route6_table_destroy(t); + uasync_destroy(ua, 0); +} + +static void test_insert_delete_reinsert(void) { + struct UASYNC *ua = uasync_create(); + struct ROUTE6_TABLE *t = route6_table_create(ua); + assert(t != NULL); + + uint8_t addrs[2][16] = {{0xfd, 1}, {0xfd, 2}}; + uint8_t plens[2] = {64, 64}; + struct NODEINFO_Q *nq = make_node(20, addrs, plens, 2); + assert(route6_insert(t, nq) == true); + + assert(route6_lookup(t, addrs[0]) != NULL); + assert(route6_lookup(t, addrs[1]) != NULL); + + route6_delete(t, nq); + + assert(route6_lookup(t, addrs[0]) == NULL); + assert(route6_lookup(t, addrs[1]) == NULL); + + assert(route6_insert(t, nq) == true); + + const struct ROUTE6_DATA *rd = route6_lookup(t, addrs[0]); + assert(rd != NULL); + assert(rd->prefix_length == 64); + + free_node(nq); + route6_table_destroy(t); + uasync_destroy(ua, 0); +} + +static void test_delete_nonexistent(void) { + struct UASYNC *ua = uasync_create(); + struct ROUTE6_TABLE *t = route6_table_create(ua); + assert(t != NULL); + + uint8_t addr[16] = {0xfd, 10}; + uint8_t plen = 64; + struct NODEINFO_Q *nq1 = make_node(1, &addr, &plen, 1); + struct NODEINFO_Q *nq2 = make_node(2, &addr, &plen, 1); + assert(route6_insert(t, nq1) == true); + route6_delete(t, nq2); // nq2 not in table — should not crash + + assert(route6_lookup(t, addr) != NULL); + + free_node(nq1); free_node(nq2); + route6_table_destroy(t); + uasync_destroy(ua, 0); +} + +static void test_delete_null_args(void) { + struct UASYNC *ua = uasync_create(); + struct ROUTE6_TABLE *t = route6_table_create(ua); + assert(t != NULL); + + route6_delete(NULL, NULL); + route6_delete(t, NULL); + route6_delete(NULL, (struct NODEINFO_Q*)(uintptr_t)t); + + route6_table_destroy(t); + uasync_destroy(ua, 0); +} + +static void test_delete_multi_subnet(void) { + struct UASYNC *ua = uasync_create(); + struct ROUTE6_TABLE *t = route6_table_create(ua); + assert(t != NULL); + + uint8_t addrs[3][16] = {{0xfc, 1}, {0xfc, 2}, {0xfc, 3}}; + uint8_t plens[3] = {48, 56, 64}; + struct NODEINFO_Q *nq = make_node(30, addrs, plens, 3); + assert(route6_insert(t, nq) == true); + + assert(route6_lookup(t, addrs[0]) != NULL); + assert(route6_lookup(t, addrs[1]) != NULL); + assert(route6_lookup(t, addrs[2]) != NULL); + + route6_delete(t, nq); + + assert(route6_lookup(t, addrs[0]) == NULL); + assert(route6_lookup(t, addrs[1]) == NULL); + assert(route6_lookup(t, addrs[2]) == NULL); + + free_node(nq); + route6_table_destroy(t); + uasync_destroy(ua, 0); +} + +static void test_delete_one_of_two(void) { + struct UASYNC *ua = uasync_create(); + struct ROUTE6_TABLE *t = route6_table_create(ua); + assert(t != NULL); + + uint8_t addr_a[16] = {0xfc, 0}; + uint8_t addr_b[16] = {0xfd, 0}; + uint8_t plen = 64; + struct NODEINFO_Q *nqa = make_node(100, &addr_a, &plen, 1); + struct NODEINFO_Q *nqb = make_node(200, &addr_b, &plen, 1); + assert(route6_insert(t, nqa) == true); + assert(route6_insert(t, nqb) == true); + + assert(route6_lookup(t, addr_a) != NULL); + assert(route6_lookup(t, addr_b) != NULL); + + route6_delete(t, nqa); + + assert(route6_lookup(t, addr_a) == NULL); + assert(route6_lookup(t, addr_b) != NULL); + + free_node(nqa); free_node(nqb); + route6_table_destroy(t); + uasync_destroy(ua, 0); +} + +static void test_lookup_null_empty(void) { + assert(route6_lookup(NULL, (const uint8_t[16]){0}) == NULL); + + struct UASYNC *ua = uasync_create(); + struct ROUTE6_TABLE *t = route6_table_create(ua); + assert(t != NULL); + assert(route6_lookup(t, (const uint8_t[16]){0}) == NULL); + route6_table_destroy(t); + uasync_destroy(ua, 0); +} + +static void test_lookup_returns_correct_data(void) { + struct UASYNC *ua = uasync_create(); + struct ROUTE6_TABLE *t = route6_table_create(ua); + assert(t != NULL); + + uint8_t addr[16] = {0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + uint8_t plen = 64; + struct NODEINFO_Q *nq = make_node(42, &addr, &plen, 1); + assert(route6_insert(t, nq) == true); + + const struct ROUTE6_DATA *rd = route6_lookup(t, addr); + assert(rd != NULL); + assert(rd->v_node_info == nq); + assert(rd->prefix_length == 64); + assert(memcmp(rd->key + 1, addr, 16) == 0); + + free_node(nq); + route6_table_destroy(t); + uasync_destroy(ua, 0); +} + +/* ──── stress test ──── */ + +struct saved_route { + uint8_t key[16]; + uint8_t mask[16]; + uint8_t plen; + uint64_t node_id; + int active; +}; + +static void prefix_to_mask(uint8_t plen, uint8_t mask[16]) { + memset(mask, 0, 16); + if (plen == 0) return; + for (int i = 0; i < 16 && plen > 0; i++) { + if (plen >= 8) { mask[i] = 0xFF; plen -= 8; } + else { mask[i] = (uint8_t)(0xFF00 >> plen); plen = 0; } + } +} + +static int brute_match(const uint8_t addr[16], struct saved_route *sr, int n, struct saved_route **out) { + *out = NULL; + int best_plen = -1; + for (int i = 0; i < n; i++) { + if (!sr[i].active) continue; + int match = 1; + for (int j = 0; j < 16 && match; j++) { + if ((addr[j] & sr[i].mask[j]) != sr[i].key[j]) match = 0; + } + if (match && (int)sr[i].plen > best_plen) { + best_plen = sr[i].plen; + *out = &sr[i]; + } + } + return 0; +} + +static void test_stress(void) { + unsigned seed = (unsigned)time(NULL); + printf(" seed = %u\n", seed); + srand(seed); + + struct UASYNC *ua = uasync_create(); + struct ROUTE6_TABLE *t = route6_table_create(ua); + assert(t != NULL); + + struct saved_route sr[MAX_SAVED]; + int nsr = 0; + struct NODEINFO_Q *nodes[STRESS_NODES]; + + // create nodes with random subnets + int n_created = 0; + for (int i = 0; i < STRESS_NODES; i++) { + int ns = rand() % 4 + 1; + uint8_t addrs[4][16]; uint8_t plens[4]; + uint64_t nid = (uint64_t)(rand() + 1) << 32 | (uint32_t)rand(); + for (int j = 0; j < ns; j++) { + for (int k = 0; k < 16; k++) addrs[j][k] = (uint8_t)rand(); + plens[j] = (uint8_t)(rand() % 96 + 33); // /33../128 + uint8_t m[16]; prefix_to_mask(plens[j], m); + for (int k = 0; k < 16; k++) addrs[j][k] &= m[k]; + // save + memcpy(sr[nsr].key, addrs[j], 16); + memcpy(sr[nsr].mask, m, 16); + sr[nsr].plen = plens[j]; + sr[nsr].node_id = nid; + sr[nsr].active = 1; + nsr++; + } + nodes[n_created] = make_node(nid, (const uint8_t(*)[16])addrs, plens, ns); + assert(nodes[n_created] != NULL); + assert(route6_insert(t, nodes[n_created]) == true); + n_created++; + } + + // === Phase 1: random insert/delete ops === + for (int op = 0; op < STRESS_OPS; op++) { + if (rand() % 10 < 6 && n_created > 0) { + // delete random active node + int idx = rand() % n_created; + uint64_t nid = nodes[idx]->node.node_id; + route6_delete(t, nodes[idx]); + // mark all routes of this node inactive + for (int j = 0; j < nsr; j++) { + if (sr[j].node_id == nid) sr[j].active = 0; + } + free_node(nodes[idx]); + nodes[idx] = nodes[--n_created]; + } + // insert new random node + int ns = rand() % 4 + 1; + uint8_t addrs[4][16]; uint8_t plens[4]; + uint64_t nid = (uint64_t)(rand() + 1) << 32 | (uint32_t)rand(); + for (int j = 0; j < ns; j++) { + for (int k = 0; k < 16; k++) addrs[j][k] = (uint8_t)rand(); + plens[j] = (uint8_t)(rand() % 96 + 33); + uint8_t m[16]; prefix_to_mask(plens[j], m); + for (int k = 0; k < 16; k++) addrs[j][k] &= m[k]; + if (nsr < MAX_SAVED) { + memcpy(sr[nsr].key, addrs[j], 16); memcpy(sr[nsr].mask, m, 16); + sr[nsr].plen = plens[j]; sr[nsr].node_id = nid; sr[nsr].active = 1; + nsr++; + } + } + nodes[n_created] = make_node(nid, (const uint8_t(*)[16])addrs, plens, ns); + assert(route6_insert(t, nodes[n_created]) == true); + n_created++; + } + + // === Phase 2: random lookups === + for (int iter = 0; iter < STRESS_LOOKUPS; iter++) { + uint8_t addr[16]; + for (int k = 0; k < 16; k++) addr[k] = (uint8_t)rand(); + + const struct ROUTE6_DATA *rd = route6_lookup(t, addr); + struct saved_route *expected = NULL; + brute_match(addr, sr, nsr, &expected); + + if (expected == NULL) { + assert(rd == NULL); + } else { + assert(rd != NULL); + assert(rd->prefix_length == expected->plen); + assert(rd->node_id == expected->node_id); + assert(memcmp(rd->key + 1, expected->key, 16) == 0); + } + } + + // === Phase 3: provoke error paths === + // 3a. duplicate insert on remaining active node + if (n_created > 0) { + int idx = rand() % n_created; + assert(route6_insert(t, nodes[idx]) == true); + } + + // 3b. delete already-deleted node (from a new fake node) + { + uint8_t addr[16] = {0xfc, 0}; + uint8_t plen = 64; + struct NODEINFO_Q *ghost = make_node(999999, &addr, &plen, 1); + route6_delete(t, ghost); // should no-op + free_node(ghost); + } + + // 3c. insert zero-subnet node + { + uint8_t addr[16] = {0}; + uint8_t zero_plen = 0; + struct NODEINFO_Q *z = make_node(777, (const uint8_t(*)[16])addr, &zero_plen, 0); + assert(route6_insert(t, z) == false); + free_node(z); + } + + // 3d. lookup NULL + assert(route6_lookup(NULL, (const uint8_t[16]){0}) == NULL); + + // 3e. delete all remaining, verify emptiness + while (n_created > 0) { + int idx = 0; + route6_delete(t, nodes[idx]); + free_node(nodes[idx]); + nodes[idx] = nodes[--n_created]; + } + for (int iter = 0; iter < 10; iter++) { + uint8_t addr[16]; + for (int k = 0; k < 16; k++) addr[k] = (uint8_t)rand(); + assert(route6_lookup(t, addr) == NULL); + } + + // 3f. destroy NULL — no crash + route6_table_destroy(NULL); + + route6_table_destroy(t); + uasync_destroy(ua, 0); +} + +int main(void) { + debug_config_init(); + printf("test_route6_lib\n"); + + RUN_TEST(test_insert_null_args); + RUN_TEST(test_insert_zero_subnets); + RUN_TEST(test_insert_duplicate); + RUN_TEST(test_insert_delete_reinsert); + RUN_TEST(test_delete_nonexistent); + RUN_TEST(test_delete_null_args); + RUN_TEST(test_delete_multi_subnet); + RUN_TEST(test_delete_one_of_two); + RUN_TEST(test_lookup_null_empty); + RUN_TEST(test_lookup_returns_correct_data); + + RUN_TEST(test_stress); + + printf("ALL ROUTE6_LIB TESTS PASSED (%d tests)\n", tests_run); + return 0; +} diff --git a/tests/test_tcp_proxy.c b/tests/test_tcp_proxy.c new file mode 100644 index 00000000..120341e0 --- /dev/null +++ b/tests/test_tcp_proxy.c @@ -0,0 +1,137 @@ +// test_tcp_proxy.c — TCP proxy test: 1MB echo through 2 uIP instances + socketpair +// No root required — uses raw-fd mode instead of TUN +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "test_utils.h" +#include "../src/tcp_proxy.h" +#include "../src/config_parser.h" +#include "../src/uip/uip.h" +#include "../lib/u_async.h" +#include "../lib/debug_config.h" + +#define TEST_PORT 9090 +#define ECHO_PORT 9999 +#define TEST_SIZE (1024 * 1024) +#define POLL_TIMEOUT_MS 10000 +#define DATA_TIMEOUT_MS 120000 + +static int g_test_ok = 0; +static pid_t echo_pid = 0; + +static void echo_server(uint16_t port) { + int srv = socket(AF_INET, SOCK_STREAM, 0); + if(srv < 0) { perror("echo socket"); exit(1); } + int opt = 1; setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + struct sockaddr_in addr = {.sin_family = AF_INET, .sin_port = htons(port)}; addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + if(bind(srv, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("echo bind"); close(srv); exit(1); } + if(listen(srv, 1) < 0) { perror("echo listen"); close(srv); exit(1); } + int cli = accept(srv, NULL, NULL); + if(cli < 0) { perror("echo accept"); close(srv); exit(1); } + uint8_t buf[65536]; ssize_t n; + while((n = recv(cli, buf, sizeof(buf), 0)) > 0) { ssize_t sent = 0; while(sent < n) { ssize_t s = send(cli, buf + sent, n - sent, 0); if(s < 0) goto done; sent += s; } } +done: close(cli); close(srv); +} + +static void run_instance_b(int ip_fd) { + struct UASYNC* ua = uasync_create(); + if(!ua) { fprintf(stderr, "B: uasync_create failed\n"); _exit(1); } + struct tcp_proxy_mapping_config m = {.local_port = TEST_PORT, .remote_ip = "127.0.0.1", .remote_port = ECHO_PORT}; + struct tcp_proxy* b = tcp_proxy_create(NULL, ua, NULL, NULL, 0, 0, &m, 1, 0, 0, ip_fd); + if(!b) { fprintf(stderr, "B: tcp_proxy_create failed\n"); uasync_destroy(ua, 0); _exit(1); } + while(1) uasync_poll(ua, 100); +} + +int main(void) { + // 1. Fork echo server + echo_pid = fork(); + if(echo_pid == 0) { echo_server(ECHO_PORT); _exit(0); } + if(echo_pid < 0) { perror("fork echo"); return 1; } + usleep(100000); + + // 2. Socketpair + int pair[2]; + if(socketpair(AF_UNIX, SOCK_STREAM, 0, pair) < 0) { perror("socketpair"); kill(echo_pid, SIGTERM); waitpid(echo_pid, NULL, 0); return 1; } + + // 3. Fork child = Instance B + pid_t child = fork(); + if(child == 0) { close(pair[0]); run_instance_b(pair[1]); _exit(0); } + if(child < 0) { perror("fork child"); close(pair[0]); close(pair[1]); kill(echo_pid, SIGTERM); waitpid(echo_pid, NULL, 0); return 1; } + close(pair[1]); + + // 4. Parent = Instance A + struct UASYNC* ua = uasync_create(); + if(!ua) { printf("[FAIL] uasync_create\n"); close(pair[0]); kill(child, SIGTERM); kill(echo_pid, SIGTERM); waitpid(child, NULL, 0); waitpid(echo_pid, NULL, 0); return 1; } + + struct tcp_proxy* a = tcp_proxy_create(NULL, ua, NULL, NULL, 0, 0, NULL, 0, 0, 0, pair[0]); + if(!a) { printf("[FAIL] tcp_proxy_create\n"); uasync_destroy(ua, 0); close(pair[0]); kill(child, SIGTERM); kill(echo_pid, SIGTERM); waitpid(child, NULL, 0); waitpid(echo_pid, NULL, 0); return 1; } + + // 5. Active open + int ci = tcp_proxy_active_open(a, "10.0.0.1", TEST_PORT); + if(ci < 0) { printf("[FAIL] active_open\n"); tcp_proxy_destroy(a); uasync_destroy(ua, 0); close(pair[0]); kill(child, SIGTERM); kill(echo_pid, SIGTERM); waitpid(child, NULL, 0); waitpid(echo_pid, NULL, 0); return 1; } + + // 6. Wait for handshake + struct uip_conn* uc = &uip_conns[ci]; + int timeout_ms = POLL_TIMEOUT_MS; + while(uc->tcpstateflags != UIP_ESTABLISHED && timeout_ms > 0) { + uasync_poll(ua, 100); timeout_ms -= 10; + if(uc->tcpstateflags == UIP_CLOSED) break; + } + if(uc->tcpstateflags != UIP_ESTABLISHED) { + printf("[FAIL] handshake (state=0x%x)\n", uc->tcpstateflags); + tcp_proxy_destroy(a); uasync_destroy(ua, 0); close(pair[0]); kill(child, SIGTERM); kill(echo_pid, SIGTERM); waitpid(child, NULL, 0); waitpid(echo_pid, NULL, 0); return 1; + } + + // 7. Generate 1MB data + uint8_t* send_buf = malloc(TEST_SIZE); uint8_t* recv_buf = malloc(TEST_SIZE); + if(!send_buf || !recv_buf) { printf("[FAIL] malloc\n"); tcp_proxy_destroy(a); uasync_destroy(ua, 0); close(pair[0]); kill(child, SIGTERM); kill(echo_pid, SIGTERM); waitpid(child, NULL, 0); waitpid(echo_pid, NULL, 0); return 1; } + srand(time(NULL)); int k; for(k = 0; k < TEST_SIZE; k++) send_buf[k] = (uint8_t)(rand() & 0xFF); + + // 8. Queue data for sending + size_t off; + for(off = 0; off < TEST_SIZE; ) { size_t chunk = TEST_SIZE - off; if(chunk > 1460) chunk = 1460; + if(tcp_proxy_active_send(a, ci, send_buf + off, chunk) != 0) { printf("[FAIL] active_send at %zu\n", off); goto fail; } + off += chunk; } + + // 9. Wait until all queued data is sent + timeout_ms = DATA_TIMEOUT_MS; + while(!tcp_proxy_active_send_done(a, ci) && timeout_ms > 0) { + uasync_poll(ua, 100); timeout_ms -= 10; + } + if(!tcp_proxy_active_send_done(a, ci)) { printf("[FAIL] send timeout\n"); goto fail; } + + // 10. Half-close: all data sent, now signal FIN to echo server + tcp_proxy_active_close(a, ci); + + // 11. Receive echoed data back + size_t total_rcvd = 0; timeout_ms = DATA_TIMEOUT_MS; + while(total_rcvd < TEST_SIZE && timeout_ms > 0) { + uasync_poll(ua, 100); timeout_ms -= 10; + ssize_t n = tcp_proxy_active_recv(a, ci, recv_buf + total_rcvd, TEST_SIZE - total_rcvd); + if(n > 0) total_rcvd += n; + if(uc->tcpstateflags == UIP_CLOSED && total_rcvd < TEST_SIZE) break; + } + + // 12. Verify + if(total_rcvd == TEST_SIZE && memcmp(send_buf, recv_buf, TEST_SIZE) == 0) { + printf("[PASS] test_tcp_proxy — 1MB echo verified\n"); g_test_ok = 1; + } else { + printf("[FAIL] test_tcp_proxy — %zu/%d bytes\n", total_rcvd, TEST_SIZE); + if(total_rcvd == TEST_SIZE) for(k = 0; k < TEST_SIZE; k++) if(send_buf[k] != recv_buf[k]) { printf(" diff at %d: %02x/%02x\n", k, send_buf[k], recv_buf[k]); break; } + } + +fail: + free(send_buf); free(recv_buf); + tcp_proxy_destroy(a); uasync_destroy(ua, 0); close(pair[0]); + kill(child, SIGTERM); waitpid(child, NULL, 0); + kill(echo_pid, SIGTERM); waitpid(echo_pid, NULL, 0); + return g_test_ok ? 0 : 1; +}