]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Extend the trie_walk_init api + test
authorVojtech Vilimek <vojtech.vilimek@nic.cz>
Fri, 7 Apr 2023 15:27:20 +0000 (17:27 +0200)
committerVojtech Vilimek <vojtech.vilimek@nic.cz>
Fri, 7 Apr 2023 15:27:20 +0000 (17:27 +0200)
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.

filter/data.h
filter/trie.c
filter/trie_test.c
nest/rt-table.c

index c1e7c736cc3e5e4eb37d07ebe8d2b121c46e49e5..d145552e9add92f7da43a886d45ee74f312d5ca7 100644 (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_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);
-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_same(const struct f_trie *t1, const struct f_trie *t2);
 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(trie, net, from) ({                          \
+#define TRIE_WALK2(trie, net, from, include) ({                        \
   net_addr net;                                                        \
   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))
 
-#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
index 12ba0b82b10bf4b89c760f8efbe751d12a5bc0dc..3738416c8b8dc853df5e7b095e5260e51a630832 100644 (file)
@@ -790,14 +790,22 @@ done:
  * @s: walk state
  * @t: trie
  * @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
  * 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
- * 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
-trie_walk_init(struct f_trie_walk_state *s, const struct f_trie *t, const net_addr *net)
+int
+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) {
     .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)
-    return;
+    return 1;
 
   /* We want to find node of level at least plen */
   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->stack[0] = n;
-      return;
+      /* Set as root node the only searched subnet */
+      if (!include_successors)
+       s->stack[0] = n;
+      /* 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 */
     n = GET_CHILD(n, v4, GET_NET_BITS(net, v4, nlen, TRIE_STEP));
   }
 
-  s->stack[0] = NULL;
-  return;
+  /* We do not override the trie root in case of inclusive search */
+  if (!include_successors)
+    s->stack[0] = NULL;
+
+  /* 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)))
index dc791280d08871145524c4ef7a3543963f04802b..6e21b0a8954ae315c88eb05dd6c74be4cc3fb532 100644 (file)
@@ -880,6 +880,137 @@ t_trie_walk_to_root(void)
   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
 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_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_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_random, "Benchmark tries from datasets by generated addresses");
index 6a15f474675bfd948a88696f8d261c2557e72144..3837bcee624338acc5244ecf3f3a948801c38c71 100644 (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_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);
        break;
       }