You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

523 lines
17 KiB

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