0
0
mirror of https://gitlab.nic.cz/labs/bird.git synced 2024-12-22 09:41:54 +00:00

Extend the trie_walk_init api + test

The trie_walk_init() function now supports also searching whole trie
subnet and all successor subnets (in lexicographic order). This behavior
can be accomplished by setting @net, and @include_successors to subnet,
and non-zero respectivelly.
This commit is contained in:
Vojtech Vilimek 2023-04-07 17:27:20 +02:00
parent 972c717c56
commit 01e16f0e09
4 changed files with 183 additions and 14 deletions

View File

@ -127,7 +127,7 @@ void *trie_add_prefix(struct f_trie *t, const net_addr *n, uint l, uint h);
int trie_match_net(const struct f_trie *t, const net_addr *n); int trie_match_net(const struct f_trie *t, const net_addr *n);
int trie_match_longest_ip4(const struct f_trie *t, const net_addr_ip4 *net, net_addr_ip4 *dst, ip4_addr *found0); int trie_match_longest_ip4(const struct f_trie *t, const net_addr_ip4 *net, net_addr_ip4 *dst, ip4_addr *found0);
int trie_match_longest_ip6(const struct f_trie *t, const net_addr_ip6 *net, net_addr_ip6 *dst, ip6_addr *found0); int trie_match_longest_ip6(const struct f_trie *t, const net_addr_ip6 *net, net_addr_ip6 *dst, ip6_addr *found0);
void trie_walk_init(struct f_trie_walk_state *s, const struct f_trie *t, const net_addr *from); int trie_walk_init(struct f_trie_walk_state *s, const struct f_trie *t, const net_addr *from, u8 include_successors);
int trie_walk_next(struct f_trie_walk_state *s, net_addr *net); int trie_walk_next(struct f_trie_walk_state *s, net_addr *net);
int trie_same(const struct f_trie *t1, const struct f_trie *t2); int trie_same(const struct f_trie *t1, const struct f_trie *t2);
void trie_format(const struct f_trie *t, buffer *buf); void trie_format(const struct f_trie *t, buffer *buf);
@ -179,14 +179,17 @@ trie_match_next_longest_ip6(net_addr_ip6 *n, ip6_addr *found)
#define TRIE_WALK_TO_ROOT_END }) #define TRIE_WALK_TO_ROOT_END })
#define TRIE_WALK2(trie, net, from, include) ({ \
#define TRIE_WALK(trie, net, from) ({ \
net_addr net; \ net_addr net; \
struct f_trie_walk_state tws_; \ struct f_trie_walk_state tws_; \
trie_walk_init(&tws_, trie, from); \ trie_walk_init(&tws_, trie, from, include); \
while (trie_walk_next(&tws_, &net)) while (trie_walk_next(&tws_, &net))
#define TRIE_WALK_END }) #define TRIE_WALK2_END })
#define TRIE_WALK(trie, net, from) TRIE_WALK2(trie, net, from, 0) \
#define TRIE_WALK_END TRIE_WALK2_END
#define F_CMP_ERROR 999 #define F_CMP_ERROR 999

View File

@ -790,14 +790,22 @@ done:
* @s: walk state * @s: walk state
* @t: trie * @t: trie
* @net: optional subnet for walk * @net: optional subnet for walk
* @include_successors: optional flag for continue walking beyond subnet @net
* *
* Initialize walk state for subsequent walk through nodes of the trie @t by * Initialize walk state for subsequent walk through nodes of the trie @t by
* trie_walk_next(). The argument @net allows to restrict walk to given subnet, * trie_walk_next(). The argument @net allows to restrict walk to given subnet,
* otherwise full walk over all nodes is used. This is done by finding node at * otherwise full walk over all nodes is used. This is done by finding node at
* or below @net and starting position in it. * or below @net and starting position in it. The argument @include_successors
* removes the restriction for all subnets lexicographically succeeding the
* @net. In case of @net search fail the walk state starting position points to
* the nearest parent node availible. If you use @net and @include_successors,
* beware that the trie_walk_next() could return a net preceding the one
* specified in @net.
*
* If desired start position node was found in trie, 1 is returned, 0 otherwise.
*/ */
void int
trie_walk_init(struct f_trie_walk_state *s, const struct f_trie *t, const net_addr *net) trie_walk_init(struct f_trie_walk_state *s, const struct f_trie *t, const net_addr *net, u8 include_successors)
{ {
*s = (struct f_trie_walk_state) { *s = (struct f_trie_walk_state) {
.ipv4 = t->ipv4, .ipv4 = t->ipv4,
@ -809,7 +817,7 @@ trie_walk_init(struct f_trie_walk_state *s, const struct f_trie *t, const net_ad
}; };
if (!net) if (!net)
return; return 1;
/* We want to find node of level at least plen */ /* We want to find node of level at least plen */
int plen = ROUND_DOWN_POW2(net->pxlen, TRIE_STEP); int plen = ROUND_DOWN_POW2(net->pxlen, TRIE_STEP);
@ -840,16 +848,42 @@ trie_walk_init(struct f_trie_walk_state *s, const struct f_trie *t, const net_ad
s->accept_length = net->pxlen; s->accept_length = net->pxlen;
} }
/* Set as root node the only searched subnet */
if (!include_successors)
s->stack[0] = n; s->stack[0] = n;
return; /* Save the last node on the stack otherwise */
else
{
/* Found prefect match, no advancing */
s->stack[s->stack_pos] = n;
/* Search whole trie except skipped parts */
s->start_pos = 1;
} }
return 1;
}
/* We store node in stack before moving on */
if (include_successors)
s->stack[s->stack_pos++] = n;
/* Choose child */ /* Choose child */
n = GET_CHILD(n, v4, GET_NET_BITS(net, v4, nlen, TRIE_STEP)); n = GET_CHILD(n, v4, GET_NET_BITS(net, v4, nlen, TRIE_STEP));
} }
/* We do not override the trie root in case of inclusive search */
if (!include_successors)
s->stack[0] = NULL; s->stack[0] = NULL;
return;
/* Be careful about underflow */
else if (s->stack_pos > 0)
s->stack_pos--;
/* Search whole trie except skipped parts */
if (include_successors)
s->start_pos = 1;
return 0;
} }
#define GET_ACCEPT_BIT(N,X,B) ((X) ? ip4_getbit((N)->v4.accept, (B)) : ip6_getbit((N)->v6.accept, (B))) #define GET_ACCEPT_BIT(N,X,B) ((X) ? ip4_getbit((N)->v4.accept, (B)) : ip6_getbit((N)->v6.accept, (B)))

View File

@ -880,6 +880,137 @@ t_trie_walk_to_root(void)
return 1; return 1;
} }
static int
t_trie_walk_inclusive(void)
{
bt_bird_init();
bt_config_parse(BT_CONFIG_SIMPLE);
for (int round = 0; round < TESTS_NUM*8; round++)
{
int level = round / TESTS_NUM;
int v6 = level % 2;
int num = PREFIXES_NUM * (int[]){1, 10, 100, 1000}[level / 2];
int pos = 0, end = 0;
list *prefixes = make_random_prefix_list(num, v6, 1);
struct f_trie *trie = make_trie_from_prefix_list(prefixes);
struct f_prefix *pxset = malloc((num + 1) * sizeof(struct f_prefix));
struct f_prefix_node *n;
WALK_LIST(n, *prefixes)
pxset[pos++] = n->prefix;
memset(&pxset[pos], 0, sizeof (struct f_prefix));
qsort(pxset, num, sizeof(struct f_prefix), compare_prefixes);
/* // print sorted prefixes
bt_debug("sorted prefixes\n");
for (struct f_prefix *px = pxset; px < pxset + num; px++)
{
char buf[64];
bt_format_net(buf, 64, &px->net);
bt_debug("%s{%d,%d}\n", buf, px->lo, px->hi);
}
*/
/* Full walk */
bt_debug("Full walk inclusive (round %d, %d nets)\n", round, num);
pos = 0;
uint pxc = 0;
TRIE_WALK2(trie, net, NULL, 1)
{
log_networks(&net, &pxset[pos].net);
bt_assert(net_equal(&net, &pxset[pos].net));
/* Skip possible duplicates */
while (net_equal(&pxset[pos].net, &pxset[pos + 1].net))
pos++;
pos++;
pxc++;
}
TRIE_WALK2_END;
bt_assert(pos == num);
bt_assert(pxc == trie->prefix_count);
bt_debug("Full walk inclusive done\n");
/* Prepare net for subnet walk - start with random prefix */
pos = bt_random() % num;
end = pos + (int[]){2, 2, 3, 4}[level / 2];
end = MIN(end, num);
struct f_prefix from = pxset[pos];
/* Find a common superprefix to several subsequent prefixes */
for (; pos < end; pos++)
{
if (net_equal(&from.net, &pxset[pos].net))
continue;
int common = !v6 ?
ip4_pxlen(net4_prefix(&from.net), net4_prefix(&pxset[pos].net)) :
ip6_pxlen(net6_prefix(&from.net), net6_prefix(&pxset[pos].net));
from.net.pxlen = MIN(from.net.pxlen, common);
if (!v6)
((net_addr_ip4 *) &from.net)->prefix =
ip4_and(net4_prefix(&from.net), net4_prefix(&pxset[pos].net));
else
((net_addr_ip6 *) &from.net)->prefix =
ip6_and(net6_prefix(&from.net), net6_prefix(&pxset[pos].net));
}
/* Fix irrelevant bits */
if (!v6)
((net_addr_ip4 *) &from.net)->prefix =
ip4_and(net4_prefix(&from.net), ip4_mkmask(net4_pxlen(&from.net)));
else
((net_addr_ip6 *) &from.net)->prefix =
ip6_and(net6_prefix(&from.net), ip6_mkmask(net6_pxlen(&from.net)));
/* Find initial position for final prefix */
for (pos = 0; pos < num; pos++)
if (compare_prefixes(&pxset[pos], &from) >= 0)
break;
/* Account for subnets before searched net from */
for (; pos < num; pos++)
if (net_compare(&pxset[pos].net, &from.net) >= 0)
break;
int p0 = pos;
char buf0[64];
bt_format_net(buf0, 64, &from.net);
bt_debug("Subnet walk inclusive for %s (round %d, %d nets)\n", buf0, round, num);
/* Subnet walk */
TRIE_WALK2(trie, net, &from.net, 1)
{
log_networks(&net, &pxset[pos].net);
bt_assert(net_compare(&net, &pxset[pos].net) >= 0);
/* Skip possible duplicates */
while (net_equal(&pxset[pos].net, &pxset[pos + 1].net))
pos++;
pos++;
}
TRIE_WALK2_END;
bt_assert(pos == num);
bt_debug("Subnet walk done inclusive for %s (found %d nets)\n", buf0, pos - p0);
tmp_flush();
}
bt_bird_cleanup();
return 1;
}
int int
main(int argc, char *argv[]) main(int argc, char *argv[])
{ {
@ -891,6 +1022,7 @@ main(int argc, char *argv[])
bt_test_suite(t_trie_same, "A trie filled forward should be same with a trie filled backward."); bt_test_suite(t_trie_same, "A trie filled forward should be same with a trie filled backward.");
bt_test_suite(t_trie_walk, "Testing TRIE_WALK() on random tries"); bt_test_suite(t_trie_walk, "Testing TRIE_WALK() on random tries");
bt_test_suite(t_trie_walk_to_root, "Testing TRIE_WALK_TO_ROOT() on random tries"); bt_test_suite(t_trie_walk_to_root, "Testing TRIE_WALK_TO_ROOT() on random tries");
bt_test_suite(t_trie_walk_inclusive, "Testing TRIE_WALK2() on random tries");
// bt_test_suite(t_bench_trie_datasets_subset, "Benchmark tries from datasets by random subset of nets"); // bt_test_suite(t_bench_trie_datasets_subset, "Benchmark tries from datasets by random subset of nets");
// bt_test_suite(t_bench_trie_datasets_random, "Benchmark tries from datasets by generated addresses"); // bt_test_suite(t_bench_trie_datasets_random, "Benchmark tries from datasets by generated addresses");

View File

@ -2057,7 +2057,7 @@ rt_table_export_start(struct rt_exporter *re, struct rt_export_request *req)
{ {
hook->walk_state = mb_allocz(p, sizeof (struct f_trie_walk_state)); hook->walk_state = mb_allocz(p, sizeof (struct f_trie_walk_state));
hook->walk_lock = rt_lock_trie(tab); hook->walk_lock = rt_lock_trie(tab);
trie_walk_init(hook->walk_state, tab->trie, req->addr); trie_walk_init(hook->walk_state, tab->trie, req->addr, 0);
hook->event = ev_new_init(p, rt_feed_by_trie, hook); hook->event = ev_new_init(p, rt_feed_by_trie, hook);
break; break;
} }