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