5 changed files with 1448 additions and 1 deletions
@ -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 <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <time.h> |
||||
#include <sys/stat.h> |
||||
#include <arpa/inet.h> |
||||
#include "../lib/platform_compat.h" |
||||
#include "test_utils.h" |
||||
|
||||
#ifdef _WIN32 |
||||
#include <windows.h> |
||||
#include <direct.h> |
||||
#else |
||||
#include <unistd.h> |
||||
#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; |
||||
} |
||||
@ -0,0 +1,523 @@
|
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <time.h> |
||||
#include <assert.h> |
||||
#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; |
||||
} |
||||
@ -0,0 +1,435 @@
|
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <time.h> |
||||
#include <assert.h> |
||||
#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; |
||||
} |
||||
@ -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 <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <unistd.h> |
||||
#include <signal.h> |
||||
#include <sys/socket.h> |
||||
#include <netinet/in.h> |
||||
#include <arpa/inet.h> |
||||
#include <sys/wait.h> |
||||
#include <errno.h> |
||||
#include <time.h> |
||||
#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; |
||||
} |
||||
Loading…
Reference in new issue