Browse Source

fix .gitignore: stop ignoring test_*.c source files; add missing test_ipv6_sockets, test_tcp_proxy, test_radix, test_route6_lib sources

congestion
Evgeny 5 months ago
parent
commit
075d97a459
  1. 6
      .gitignore
  2. 348
      tests/test_ipv6_sockets.c
  3. 523
      tests/test_radix.c
  4. 435
      tests/test_route6_lib.c
  5. 137
      tests/test_tcp_proxy.c

6
.gitignore vendored

@ -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

348
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 <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;
}

523
tests/test_radix.c

@ -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;
}

435
tests/test_route6_lib.c

@ -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;
}

137
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 <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…
Cancel
Save