524 lines
17 KiB
C
524 lines
17 KiB
C
#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;
|
|
}
|