Файлы: route_node.* route_node_lmdb.* route_bgp.* -> topo_node.* topo_node_lmdb.* topo_bgp.* Структуры: - ROUTE_BGP -> TOPO_BGP, ROUTE_BGP_CONN_ITEM -> TOPO_BGP_CONN_ITEM - NODEINFO -> TOPO_NODE, NODEINFO_Q -> TOPO_NODEQ, NODEINFO_PATH -> TOPO_NODEPATH - NODEINFO_ROUTES -> TOPO_NODESUBNETS, NODE_CONNECTIVITY -> TOPO_CONNECTIVITY - wire: NODEINFO_MSG -> TOPOMSG_NODE, NODEINFO_IPV4_* -> TOPOMSG_*4 и т.д. - memory: NI_IPV4_* -> TOPO_*4, NI_IPV6_* -> TOPO_*6 - ROUTE_SUBCMD_* -> TOPO_SUBCMD_*, ETCP_ID_ROUTE_ENTRY -> ETCP_ID_TOPO_ENTRY Функции: - route_bgp_* -> topo_bgp_* - nodeinfo_* -> topo_node_* - route_node_* -> topo_node_* - route_node_lmdb_* -> topo_node_lmdb_* Смысл: отделение механизма формирования топологии узлов от IP-роутинга.
452 lines
14 KiB
C
452 lines
14 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <assert.h>
|
|
#include "../src/topo_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 TOPO_NODEQ *make_node(uint64_t node_id,
|
|
const uint8_t addrs[][16],
|
|
const uint8_t *plens, int N) {
|
|
struct TOPO_NODEQ *nq = u_calloc(1, sizeof(*nq));
|
|
struct TOPO_NODE *ni = u_calloc(1, sizeof(struct TOPO_NODE));
|
|
if (!nq || !ni) { u_free(nq); u_free(ni); return NULL; }
|
|
ni->ref_count = 1; ni->node_id = node_id;
|
|
nq->node = ni; nq->hash_node_id = node_id;
|
|
if (N > 0) {
|
|
struct TOPO_NODESUBNETS *r = u_calloc(1, sizeof(struct TOPO_NODESUBNETS));
|
|
nq->subnets = r;
|
|
struct TOPO_SUBNET6 *head = NULL;
|
|
for (int i = N-1; i >= 0; i--) {
|
|
struct TOPO_SUBNET6 *s = u_calloc(1, sizeof(struct TOPO_SUBNET6));
|
|
memcpy(s->addr, addrs[i], 16); s->prefix_length = plens[i];
|
|
s->next = head; head = s;
|
|
}
|
|
r->v6_subnets = head;
|
|
}
|
|
return nq;
|
|
}
|
|
|
|
static void free_node(struct TOPO_NODEQ *nq) {
|
|
if (nq) {
|
|
if (nq->subnets) {
|
|
struct TOPO_SUBNET6 *s = nq->subnets->v6_subnets;
|
|
while (s) { struct TOPO_SUBNET6 *next = s->next; u_free(s); s = next; }
|
|
u_free(nq->subnets);
|
|
}
|
|
if (nq->node) u_free(nq->node);
|
|
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 TOPO_NODEQ*)(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 TOPO_NODEQ *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 TOPO_NODEQ *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 TOPO_NODEQ *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 TOPO_NODEQ *nq1 = make_node(1, &addr, &plen, 1);
|
|
struct TOPO_NODEQ *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 TOPO_NODEQ*)(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 TOPO_NODEQ *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 TOPO_NODEQ *nqa = make_node(100, &addr_a, &plen, 1);
|
|
struct TOPO_NODEQ *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 TOPO_NODEQ *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 TOPO_NODEQ *nodes[STRESS_NODES + STRESS_OPS];
|
|
|
|
// 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 TOPO_NODEQ *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 TOPO_NODEQ *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;
|
|
}
|