]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Implement arbitrary sized bitmap
authorIgor Putovny <igor.putovny@nic.cz>
Fri, 25 Apr 2025 11:50:57 +0000 (13:50 +0200)
committerIgor Putovny <igor.putovny@nic.cz>
Tue, 29 Apr 2025 13:01:15 +0000 (15:01 +0200)
proto/aggregator/aggregator.c
proto/aggregator/aggregator.h
proto/aggregator/trie.c

index 2bfc98d1ba9402efdb404f60bcde900991b318c7..566898467be1c96deb5e3ae8d5d0ddcc1adf7e91 100644 (file)
@@ -101,6 +101,33 @@ aggregator_withdraw_rte(struct aggregator_proto *p)
   lp_flush(p->rte_withdrawal_pool);
 }
 
+/*
+ * Change size of trie node so that @id fits into node's bitmap, allocate
+ * new slab configured with this size and initialize root node there.
+ */
+static void
+aggregator_resize_slab(struct aggregator_proto *p, u32 id)
+{
+  ASSERT_DIE(p->trie_slab != NULL);
+
+  /* Calculate sufficient bitmap size */
+  while (id >= (p->bitmap_size * sizeof(p->root->potential_buckets[0]) * 8))
+    p->bitmap_size *= 2;
+
+  /* Save bucket with default route */
+  struct aggregator_bucket *default_rte_bucket = p->root->original_bucket;
+
+  /* Calculate size of trie node with bigger bitmap */
+  const size_t node_size = sizeof(*p->root) + sizeof(p->root->potential_buckets[0]) * p->bitmap_size;
+
+  /* Free old slab and create new */
+  rfree(p->trie_slab);
+  p->trie_slab = sl_new(p->p.pool, node_size);
+
+  /* Initialize root node in new slab */
+  p->root = aggregator_root_init(default_rte_bucket, p->trie_slab);
+}
+
 static void
 aggregator_aggregate_on_feed_end(struct channel *C)
 {
@@ -649,6 +676,22 @@ aggregator_rt_notify(struct proto *P, struct channel *src_ch, net *net, rte *new
       hmap_set(&p->bucket_id_map, new_bucket->id);
 
       aggregator_add_bucket(p, new_bucket);
+
+      if (p->aggr_mode == PREFIX_AGGR)
+      {
+        /*
+         * If ID of new bucket doesn't fit into bitmap in trie nodes, we have to
+         * create new slab and build whole trie all over again.
+         */
+        if (new_bucket->id >= (p->bitmap_size * sizeof(p->root->potential_buckets[0]) * 8))
+        {
+          aggregator_resize_slab(p, new_bucket->id);
+
+          /* Trie already exists after initial feed, build it again in the new slab */
+          if (!p->initial_feed)
+            aggregator_aggregate(p);
+        }
+      }
     }
 
     /* Store the route attributes */
@@ -888,11 +931,14 @@ aggregator_start(struct proto *P)
   if (p->aggr_mode == PREFIX_AGGR)
   {
     ASSERT_DIE(p->trie_slab == NULL);
-    p->trie_slab = sl_new(P->pool, sizeof(struct trie_node));
-
     ASSERT_DIE(p->bucket_list == NULL);
     ASSERT_DIE(p->bucket_list_size == 0);
 
+    p->bitmap_size = POTENTIAL_BUCKETS_BITMAP_INIT_SIZE;
+
+    const size_t node_size = sizeof(*p->root) + sizeof(p->root->potential_buckets[0]) * p->bitmap_size;
+    p->trie_slab = sl_new(P->pool, node_size);
+
     p->bucket_list_size = BUCKET_LIST_INIT_SIZE;
     p->bucket_list = mb_allocz(P->pool, sizeof(p->bucket_list[0]) * p->bucket_list_size);
 
@@ -936,6 +982,8 @@ aggregator_cleanup(struct proto *P)
   p->bucket_id_map = (struct hmap) { 0 };
 
   p->initial_feed = true;
+
+  p->bitmap_size = 0;
 }
 
 static int
index 758d7d6864d29fe0b8002f2df182f33e802cd1a1..7f3e216f5684bfc624c4f8744993520a7dfa9598 100644 (file)
@@ -15,9 +15,8 @@
 #include "nest/protocol.h"
 #include "lib/hash.h"
 
-#define BUCKET_LIST_INIT_SIZE         16
-#define POTENTIAL_BUCKETS_BITMAP_SIZE 8
-#define MAX_POTENTIAL_BUCKETS_COUNT   ((int)(sizeof(u32) * 8 * POTENTIAL_BUCKETS_BITMAP_SIZE))
+#define BUCKET_LIST_INIT_SIZE              16
+#define POTENTIAL_BUCKETS_BITMAP_INIT_SIZE  8
 
 #define IP4_WITHDRAWAL_MAX_EXPECTED_LIMIT 100
 #define IP6_WITHDRAWAL_MAX_EXPECTED_LIMIT 200
@@ -88,6 +87,7 @@ struct aggregator_proto {
   struct trie_node *root;
   struct slab *trie_slab;
   u32 addr_type;
+  int bitmap_size;
   bool initial_feed;
   bool logging;
 
@@ -144,9 +144,9 @@ struct trie_node {
   struct aggregator_bucket *selected_bucket;
   enum fib_status status;
   enum prefix_origin px_origin;
-  u32 potential_buckets[POTENTIAL_BUCKETS_BITMAP_SIZE];
   int potential_buckets_count;
   int depth;
+  u32 potential_buckets[];
 };
 
 void aggregator_aggregate(struct aggregator_proto *p);
index d332858f116ec1a7eb98dd54dc38faf75973003d..c5508417d2e51d2c59297c53b1bb7f74f0a32d1f 100644 (file)
@@ -216,8 +216,6 @@ aggregator_remove_node(struct trie_node *node)
 static inline void
 aggregator_node_add_potential_bucket(struct trie_node *node, u32 bucket_id)
 {
-  ASSERT_DIE(node->potential_buckets_count < MAX_POTENTIAL_BUCKETS_COUNT);
-
   if (BIT32R_TEST(node->potential_buckets, bucket_id))
     return;
 
@@ -229,11 +227,11 @@ aggregator_node_add_potential_bucket(struct trie_node *node, u32 bucket_id)
  * Check if @bucket is one of potential buckets of @node
  */
 static inline int
-aggregator_is_bucket_potential(const struct trie_node *node, u32 id)
+aggregator_is_bucket_potential(const struct trie_node *node, u32 id, int bitmap_size)
 {
   ASSERT_DIE(node != NULL);
 
-  ASSERT_DIE(id < MAX_POTENTIAL_BUCKETS_COUNT);
+  ASSERT_DIE(id < (sizeof(node->potential_buckets[0]) * bitmap_size * 8));
   return BIT32R_TEST(node->potential_buckets, id);
 }
 
@@ -260,7 +258,7 @@ aggregator_select_lowest_id_bucket(const struct aggregator_proto *p, const struc
   ASSERT_DIE(p != NULL);
   ASSERT_DIE(node != NULL);
 
-  for (int i = 0; i < POTENTIAL_BUCKETS_BITMAP_SIZE; i++)
+  for (int i = 0; i < p->bitmap_size; i++)
   {
     if (node->potential_buckets[i] == 0)
       continue;
@@ -294,7 +292,7 @@ aggregator_select_lowest_id_bucket(const struct aggregator_proto *p, const struc
  * Returns: whether the set of potential buckets in the target node has changed.
  */
 static bool
-aggregator_merge_potential_buckets(struct trie_node *target, const struct trie_node *left, const struct trie_node *right)
+aggregator_merge_potential_buckets(struct trie_node *target, const struct trie_node *left, const struct trie_node *right, int bitmap_size)
 {
   ASSERT_DIE(target != NULL);
   ASSERT_DIE(left != NULL);
@@ -303,12 +301,12 @@ aggregator_merge_potential_buckets(struct trie_node *target, const struct trie_n
   bool has_intersection = false;
   bool has_changed = false;
 
-  u32 before[ARRAY_SIZE(target->potential_buckets)] = { 0 };
+  u32 *before = allocz(sizeof(*before) * bitmap_size);
 
   target->potential_buckets_count = 0;
 
   /* First we try to compute intersection. If it exists, we want to keep it. */
-  for (int i = 0; i < POTENTIAL_BUCKETS_BITMAP_SIZE; i++)
+  for (int i = 0; i < bitmap_size; i++)
   {
     /* Save current bitmap values */
     before[i] = target->potential_buckets[i];
@@ -332,7 +330,7 @@ aggregator_merge_potential_buckets(struct trie_node *target, const struct trie_n
   target->potential_buckets_count = 0;
   has_changed = false;
 
-  for (int i = 0; i < POTENTIAL_BUCKETS_BITMAP_SIZE; i++)
+  for (int i = 0; i < bitmap_size; i++)
   {
     target->potential_buckets[i] = left->potential_buckets[i] | right->potential_buckets[i];
     target->potential_buckets_count += u32_popcount(target->potential_buckets[i]);
@@ -368,7 +366,7 @@ aggregator_trie_dump_helper(const struct aggregator_proto *p, const struct trie_
 
   buffer_print(buf, "{");
 
-  for (int i = 0, j = 0; i < POTENTIAL_BUCKETS_BITMAP_SIZE; i++)
+  for (int i = 0, j = 0; i < p->bitmap_size; i++)
   {
     if (node->potential_buckets[i] == 0)
       continue;
@@ -543,7 +541,7 @@ aggregator_trie_remove_prefix(struct aggregator_proto *p, ip_addr prefix, u32 px
   node->ancestor = NULL;
   node->original_bucket = NULL;
   node->potential_buckets_count = 0;
-  memset(node->potential_buckets, 0, sizeof(node->potential_buckets));
+  memset(node->potential_buckets, 0, sizeof(node->potential_buckets[0]) * p->bitmap_size);
 
   return node;
 }
@@ -623,11 +621,11 @@ aggregator_find_subtree_prefix(const struct trie_node *target, ip_addr *prefix,
  * @node: node from which to descend
  */
 static void
-aggregator_propagate_and_merge(struct trie_node *node)
+aggregator_propagate_and_merge(struct trie_node *node, int bitmap_size)
 {
   ASSERT_DIE(node != NULL);
   ASSERT_DIE(node->status != UNASSIGNED_FIB);
-  ASSERT_DIE(node->potential_buckets_count <= MAX_POTENTIAL_BUCKETS_COUNT);
+  ASSERT_DIE(node->potential_buckets_count <= (int)(bitmap_size * sizeof(node->potential_buckets[0]) * 8));
 
   if (node->px_origin == ORIGINAL)
     ASSERT_DIE(node->original_bucket != NULL);
@@ -654,12 +652,13 @@ aggregator_propagate_and_merge(struct trie_node *node)
   {
     /* Reset the bucket bitmap to cleanup possible old bucket information */
     node->potential_buckets_count = 0;
-    memset(node->potential_buckets, 0, sizeof(node->potential_buckets));
+    memset(node->potential_buckets, 0, sizeof(node->potential_buckets[0]) * bitmap_size);
 
     /*
      * For the leaf node, by definition, the only bucket in the bitmap is the
      * original bucket.
      */
+    ASSERT_DIE(node->original_bucket->id < (bitmap_size * sizeof(node->potential_buckets[0]) * 8));
     aggregator_node_add_potential_bucket(node, node->original_bucket->id);
 
     /* No children, no further work. Done! */
@@ -672,22 +671,24 @@ aggregator_propagate_and_merge(struct trie_node *node)
    * This fixes the (kinda) missing first pass when comparing our algorithm
    * to the original one.
    */
-  struct trie_node imaginary_node = { 0 };
-  aggregator_node_add_potential_bucket(&imaginary_node, node->original_bucket->id);
+  struct trie_node *imaginary_node = allocz(sizeof(*imaginary_node) + sizeof(imaginary_node->potential_buckets[0]) * bitmap_size);
+
+  ASSERT_DIE(node->original_bucket->id < (bitmap_size * sizeof(imaginary_node->potential_buckets[0]) * 8));
+  aggregator_node_add_potential_bucket(imaginary_node, node->original_bucket->id);
 
   /* Process children */
   if (left)
-    aggregator_propagate_and_merge(left);
+    aggregator_propagate_and_merge(left, bitmap_size);
   else
-    left = &imaginary_node;
+    left = imaginary_node;
 
   if (right)
-    aggregator_propagate_and_merge(right);
+    aggregator_propagate_and_merge(right, bitmap_size);
   else
-    right = &imaginary_node;
+    right = imaginary_node;
 
   /* Merge sets of potential buckets */
-  aggregator_merge_potential_buckets(node, left, right);
+  aggregator_merge_potential_buckets(node, left, right, bitmap_size);
 }
 
 /*
@@ -703,12 +704,16 @@ aggregator_propagate_and_merge(struct trie_node *node)
  * the trie.
  */
 static void
-aggregator_process_one_child_nodes(struct trie_node *node, const struct aggregator_bucket *inherited_bucket, struct slab *trie_slab)
+aggregator_process_one_child_nodes(struct trie_node *node, const struct aggregator_bucket *inherited_bucket, struct slab *trie_slab, int bitmap_size)
 {
   ASSERT_DIE(node != NULL);
 
   /* Imaginary node that would have been added during normalization of the trie */
-  struct trie_node imaginary_node = {
+  const size_t node_size = sizeof(*node) + sizeof(node->potential_buckets[0]) * bitmap_size;
+
+  struct trie_node *imaginary_node = allocz(node_size);
+
+  *imaginary_node = (struct trie_node) {
     .parent = node,
     .original_bucket = node->original_bucket,
     .status = NON_FIB,
@@ -717,7 +722,8 @@ aggregator_process_one_child_nodes(struct trie_node *node, const struct aggregat
   };
 
   /* Imaginary node inherits bucket from its parent - current node */
-  aggregator_node_add_potential_bucket(&imaginary_node, node->original_bucket->id);
+  ASSERT_DIE(node->original_bucket->id < (bitmap_size * sizeof(node->potential_buckets[0]) * 8));
+  aggregator_node_add_potential_bucket(imaginary_node, node->original_bucket->id);
 
   /*
    * If the current node (parent of the imaginary node) has a bucket, then
@@ -738,11 +744,11 @@ aggregator_process_one_child_nodes(struct trie_node *node, const struct aggregat
    * imaginary node needs to be added to the trie because it's not covered
    * by its ancestor.
    */
-  if (!aggregator_is_bucket_potential(&imaginary_node, imaginary_node_inherited_bucket->id))
+  if (!aggregator_is_bucket_potential(imaginary_node, imaginary_node_inherited_bucket->id, bitmap_size))
   {
     /* Allocate new node and copy imaginary node into it */
     struct trie_node *new = sl_allocz(trie_slab);
-    *new = imaginary_node;
+    memcpy(new, imaginary_node, node_size);
 
     const struct trie_node * const left  = node->child[0];
     const struct trie_node * const right = node->child[1];
@@ -835,7 +841,7 @@ aggregator_group_prefixes_helper(struct aggregator_proto *p, struct trie_node *n
 {
   ASSERT_DIE(node != NULL);
   ASSERT_DIE(node->status != UNASSIGNED_FIB);
-  ASSERT_DIE(node->potential_buckets_count <= MAX_POTENTIAL_BUCKETS_COUNT);
+  ASSERT_DIE(node->potential_buckets_count <= (int)(p->bitmap_size * sizeof(node->potential_buckets[0]) * 8));
 
   ASSERT_DIE(node->original_bucket != NULL);
   ASSERT_DIE(node->parent->ancestor != NULL);
@@ -849,7 +855,7 @@ aggregator_group_prefixes_helper(struct aggregator_proto *p, struct trie_node *n
    * of the current node, then this node doesn't need a bucket because it
    * inherits one, and its prefix is thus not needed in FIB.
    */
-  if (aggregator_is_bucket_potential(node, inherited_bucket->id))
+  if (aggregator_is_bucket_potential(node, inherited_bucket->id, p->bitmap_size))
     aggregator_remove_node_prefix(p, node, *prefix, pxlen);
   else
     aggregator_export_node_prefix(p, node, *prefix, pxlen);
@@ -864,7 +870,7 @@ aggregator_group_prefixes_helper(struct aggregator_proto *p, struct trie_node *n
 
   /* Process nodes with only one child */
   if ((left && !right) || (!left && right))
-    aggregator_process_one_child_nodes(node, inherited_bucket, p->trie_slab);
+    aggregator_process_one_child_nodes(node, inherited_bucket, p->trie_slab, p->bitmap_size);
 
   /* Preorder traversal */
   if (node->child[0]) /* TODO: nestačí tady left a right? Takhle to vypadá, že se left a right pod rukama můžou přepsat. */
@@ -903,8 +909,8 @@ static void
 aggregator_group_prefixes(struct aggregator_proto *p, struct trie_node *node)
 {
   ASSERT_DIE(node != NULL);
-  ASSERT_DIE(node->potential_buckets_count <= MAX_POTENTIAL_BUCKETS_COUNT);
   ASSERT_DIE(node->potential_buckets_count > 0);
+  ASSERT_DIE(node->potential_buckets_count <= (int)(p->bitmap_size * sizeof(node->potential_buckets[0]) * 8));
 
   ip_addr prefix = (p->addr_type == NET_IP4) ? ipa_from_ip4(IP4_NONE) : ipa_from_ip6(IP6_NONE);
   u32 pxlen = 0;
@@ -973,7 +979,7 @@ check_trie_after_aggregation(const struct trie_node *node)
  * Stop when the node's set doesn't change and return the last updated node.
  */
 static struct trie_node *
-aggregator_merge_buckets_above(struct trie_node *node)
+aggregator_merge_buckets_above(struct trie_node *node, int bitmap_size)
 {
   ASSERT_DIE(node != NULL);
 
@@ -985,19 +991,21 @@ aggregator_merge_buckets_above(struct trie_node *node)
     const struct trie_node *right = parent->child[1];
     ASSERT_DIE(left == node || right == node);
 
-    struct trie_node imaginary_node = { 0 };
-    aggregator_node_add_potential_bucket(&imaginary_node, parent->original_bucket->id);
+    struct trie_node *imaginary_node = allocz(sizeof(*imaginary_node) + sizeof(imaginary_node->potential_buckets[0]) * bitmap_size);
+
+    ASSERT_DIE(parent->original_bucket->id < (bitmap_size * sizeof(imaginary_node->potential_buckets[0]) * 8));
+    aggregator_node_add_potential_bucket(imaginary_node, parent->original_bucket->id);
 
     /* Nodes with only one child */
     if (left && !right)
-      right = &imaginary_node;
+      right = imaginary_node;
     else if (!left && right)
-      left = &imaginary_node;
+      left = imaginary_node;
 
     ASSERT_DIE(left != NULL && right != NULL);
 
     /* The parent's set didn't change by merging, stop here */
-    if (!aggregator_merge_potential_buckets(parent, left, right))
+    if (!aggregator_merge_potential_buckets(parent, left, right, bitmap_size))
       return node;
 
     node = parent;
@@ -1033,7 +1041,7 @@ aggregator_compute_trie(struct aggregator_proto *p)
 {
   ASSERT_DIE(p->addr_type == NET_IP4 || p->addr_type == NET_IP6);
 
-  aggregator_propagate_and_merge(p->root);
+  aggregator_propagate_and_merge(p->root, p->bitmap_size);
   aggregator_group_prefixes(p, p->root);
 
   check_trie_after_aggregation(p->root);
@@ -1099,10 +1107,10 @@ aggregator_recompute(struct aggregator_proto *p, struct aggregator_route *old, s
   ASSERT_DIE(ancestor->status == IN_FIB);
 
   /* Reaggregate trie with incorporated update */
-  aggregator_propagate_and_merge(ancestor);
+  aggregator_propagate_and_merge(ancestor, p->bitmap_size);
 
   /* Merge buckets upwards until they change, return last updated node */
-  struct trie_node *highest_node = aggregator_merge_buckets_above(ancestor);
+  struct trie_node *highest_node = aggregator_merge_buckets_above(ancestor, p->bitmap_size);
   ASSERT_DIE(highest_node != NULL);
 
   aggregator_group_prefixes(p, highest_node);