summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorAvneesh Sachdev <avneesh@opensourcerouting.org>2012-08-17 08:19:50 -0700
committerDavid Lamparter <equinox@opensourcerouting.org>2012-09-26 21:50:48 +0200
commit28971c8cb1138700e87dc7da673e59b5596bb51b (patch)
tree0e55c3f830681449cd96bb36eb04a6a1293d8b44 /lib
parent67174041d2d9d8908f8b2c915bc0d186d8442c68 (diff)
lib/table: add route_table_get_next() and iterator
* lib/table.[ch] - Add a function (route_table_get_next()) to get the route_node in a tree that succeeds a given prefix in iteration order. This allows one to reliably walk nodes in a tree while allowing modifications, and is useful for achieving scale and performance. Other approaches are also possible -- the main plus point of this one is that it does not require any state about the walk to be maintained in the table data structures. - Add an iterator for walking the nodes in a tree. This introduces a new structure (route_table_iter_t) and the following main functions. route_table_iter_init() route_table_iter_pause() route_table_iter_next() route_table_iter_cleanup() The iterator normally uses node pointers and the existing route_next() function to walk nodes efficiently. When an iteration is 'paused' with route_table_iter_pause(), it stores the last prefix processed. The next call to route_table_iter_next() transparently invokes route_table_get_next() with the prefix to resume iteration. * bgpd/bgp_table.[ch] Add wrappers for the new table features described above. * tests/table_test.c Add tests for the new table code. Signed-off-by: David Lamparter <equinox@opensourcerouting.org>
Diffstat (limited to 'lib')
-rw-r--r--lib/table.c282
-rw-r--r--lib/table.h126
2 files changed, 408 insertions, 0 deletions
diff --git a/lib/table.c b/lib/table.c
index 6bbc9c62..19b5d1b1 100644
--- a/lib/table.c
+++ b/lib/table.c
@@ -528,3 +528,285 @@ route_table_init (void)
{
return route_table_init_with_delegate (&default_delegate);
}
+
+/**
+ * route_table_prefix_iter_cmp
+ *
+ * Compare two prefixes according to the order in which they appear in
+ * an iteration over a tree.
+ *
+ * @return -1 if p1 occurs before p2 (p1 < p2)
+ * 0 if the prefixes are identical (p1 == p2)
+ * +1 if p1 occurs after p2 (p1 > p2)
+ */
+int
+route_table_prefix_iter_cmp (struct prefix *p1, struct prefix *p2)
+{
+ struct prefix common_space;
+ struct prefix *common = &common_space;
+
+ if (p1->prefixlen <= p2->prefixlen)
+ {
+ if (prefix_match (p1, p2))
+ {
+
+ /*
+ * p1 contains p2, or is equal to it.
+ */
+ return (p1->prefixlen == p2->prefixlen) ? 0 : -1;
+ }
+ }
+ else
+ {
+
+ /*
+ * Check if p2 contains p1.
+ */
+ if (prefix_match (p2, p1))
+ return 1;
+ }
+
+ route_common (p1, p2, common);
+ assert (common->prefixlen < p1->prefixlen);
+ assert (common->prefixlen < p2->prefixlen);
+
+ /*
+ * Both prefixes are longer than the common prefix.
+ *
+ * We need to check the bit after the common prefixlen to determine
+ * which one comes later.
+ */
+ if (prefix_bit (&p1->u.prefix, common->prefixlen))
+ {
+
+ /*
+ * We branch to the right to get to p1 from the common prefix.
+ */
+ assert (!prefix_bit (&p2->u.prefix, common->prefixlen));
+ return 1;
+ }
+
+ /*
+ * We branch to the right to get to p2 from the common prefix.
+ */
+ assert (prefix_bit (&p2->u.prefix, common->prefixlen));
+ return -1;
+}
+
+/*
+ * route_get_subtree_next
+ *
+ * Helper function that returns the first node that follows the nodes
+ * in the sub-tree under 'node' in iteration order.
+ */
+static struct route_node *
+route_get_subtree_next (struct route_node *node)
+{
+ while (node->parent)
+ {
+ if (node->parent->l_left == node && node->parent->l_right)
+ return node->parent->l_right;
+
+ node = node->parent;
+ }
+
+ return NULL;
+}
+
+/**
+ * route_table_get_next_internal
+ *
+ * Helper function to find the node that occurs after the given prefix in
+ * order of iteration.
+ *
+ * @see route_table_get_next
+ */
+static struct route_node *
+route_table_get_next_internal (const struct route_table *table,
+ struct prefix *p)
+{
+ struct route_node *node, *tmp_node;
+ u_char prefixlen;
+ int cmp;
+
+ prefixlen = p->prefixlen;
+
+ node = table->top;
+
+ while (node)
+ {
+ int match;
+
+ if (node->p.prefixlen < p->prefixlen)
+ match = prefix_match (&node->p, p);
+ else
+ match = prefix_match (p, &node->p);
+
+ if (match)
+ {
+ if (node->p.prefixlen == p->prefixlen)
+ {
+
+ /*
+ * The prefix p exists in the tree, just return the next
+ * node.
+ */
+ route_lock_node (node);
+ node = route_next (node);
+ if (node)
+ route_unlock_node (node);
+
+ return (node);
+ }
+
+ if (node->p.prefixlen > p->prefixlen)
+ {
+
+ /*
+ * Node is in the subtree of p, and hence greater than p.
+ */
+ return node;
+ }
+
+ /*
+ * p is in the sub-tree under node.
+ */
+ tmp_node = node->link[prefix_bit (&p->u.prefix, node->p.prefixlen)];
+
+ if (tmp_node)
+ {
+ node = tmp_node;
+ continue;
+ }
+
+ /*
+ * There are no nodes in the direction where p should be. If
+ * node has a right child, then it must be greater than p.
+ */
+ if (node->l_right)
+ return node->l_right;
+
+ /*
+ * No more children to follow, go upwards looking for the next
+ * node.
+ */
+ return route_get_subtree_next (node);
+ }
+
+ /*
+ * Neither node prefix nor 'p' contains the other.
+ */
+ cmp = route_table_prefix_iter_cmp (&node->p, p);
+ if (cmp > 0)
+ {
+
+ /*
+ * Node follows p in iteration order. Return it.
+ */
+ return node;
+ }
+
+ assert (cmp < 0);
+
+ /*
+ * Node and the subtree under it come before prefix p in
+ * iteration order. Prefix p and its sub-tree are not present in
+ * the tree. Go upwards and find the first node that follows the
+ * subtree. That node will also succeed p.
+ */
+ return route_get_subtree_next (node);
+ }
+
+ return NULL;
+}
+
+/**
+ * route_table_get_next
+ *
+ * Find the node that occurs after the given prefix in order of
+ * iteration.
+ */
+struct route_node *
+route_table_get_next (const struct route_table *table, struct prefix *p)
+{
+ struct route_node *node;
+
+ node = route_table_get_next_internal (table, p);
+ if (node)
+ {
+ assert (route_table_prefix_iter_cmp (&node->p, p) > 0);
+ route_lock_node (node);
+ }
+ return node;
+}
+
+/*
+ * route_table_iter_init
+ */
+void
+route_table_iter_init (route_table_iter_t * iter, struct route_table *table)
+{
+ memset (iter, 0, sizeof (*iter));
+ iter->state = RT_ITER_STATE_INIT;
+ iter->table = table;
+}
+
+/*
+ * route_table_iter_pause
+ *
+ * Pause an iteration over the table. This allows the iteration to be
+ * resumed point after arbitrary additions/deletions from the table.
+ * An iteration can be resumed by just calling route_table_iter_next()
+ * on the iterator.
+ */
+void
+route_table_iter_pause (route_table_iter_t * iter)
+{
+ switch (iter->state)
+ {
+
+ case RT_ITER_STATE_INIT:
+ case RT_ITER_STATE_PAUSED:
+ case RT_ITER_STATE_DONE:
+ return;
+
+ case RT_ITER_STATE_ITERATING:
+
+ /*
+ * Save the prefix that we are currently at. The next call to
+ * route_table_iter_next() will return the node after this prefix
+ * in the tree.
+ */
+ prefix_copy (&iter->pause_prefix, &iter->current->p);
+ route_unlock_node (iter->current);
+ iter->current = NULL;
+ iter->state = RT_ITER_STATE_PAUSED;
+ return;
+
+ default:
+ assert (0);
+ }
+
+}
+
+/*
+ * route_table_iter_cleanup
+ *
+ * Release any resources held by the iterator.
+ */
+void
+route_table_iter_cleanup (route_table_iter_t * iter)
+{
+ if (iter->state == RT_ITER_STATE_ITERATING)
+ {
+ route_unlock_node (iter->current);
+ iter->current = NULL;
+ }
+ assert (!iter->current);
+
+ /*
+ * Set the state to RT_ITER_STATE_DONE to make any
+ * route_table_iter_next() calls on this iterator return NULL.
+ */
+ iter->state = RT_ITER_STATE_DONE;
+}
diff --git a/lib/table.h b/lib/table.h
index 4d3eddb1..ab357a07 100644
--- a/lib/table.h
+++ b/lib/table.h
@@ -98,6 +98,43 @@ struct route_node
#define l_right link[1]
};
+typedef struct route_table_iter_t_ route_table_iter_t;
+
+typedef enum
+{
+ RT_ITER_STATE_INIT,
+ RT_ITER_STATE_ITERATING,
+ RT_ITER_STATE_PAUSED,
+ RT_ITER_STATE_DONE
+} route_table_iter_state_t;
+
+/*
+ * route_table_iter_t
+ *
+ * Structure that holds state for iterating over a route table.
+ */
+struct route_table_iter_t_
+{
+
+ route_table_iter_state_t state;
+
+ /*
+ * Routing table that we are iterating over. The caller must ensure
+ * that that table outlives the iterator.
+ */
+ struct route_table *table;
+
+ /*
+ * The node that the iterator is currently on.
+ */
+ struct route_node *current;
+
+ /*
+ * The last prefix that the iterator processed before it was paused.
+ */
+ struct prefix pause_prefix;
+};
+
/* Prototypes. */
extern struct route_table *route_table_init (void);
@@ -125,4 +162,93 @@ extern struct route_node *route_node_match_ipv6 (const struct route_table *,
#endif /* HAVE_IPV6 */
extern unsigned long route_table_count (const struct route_table *);
+
+extern struct route_node *
+route_table_get_next (const struct route_table *table, struct prefix *p);
+extern int
+route_table_prefix_iter_cmp (struct prefix *p1, struct prefix *p2);
+
+/*
+ * Iterator functions.
+ */
+extern void route_table_iter_init (route_table_iter_t *iter,
+ struct route_table *table);
+extern void route_table_iter_pause (route_table_iter_t *iter);
+extern void route_table_iter_cleanup (route_table_iter_t *iter);
+
+/*
+ * Inline functions.
+ */
+
+/*
+ * route_table_iter_next
+ *
+ * Get the next node in the tree.
+ */
+static inline struct route_node *
+route_table_iter_next (route_table_iter_t * iter)
+{
+ struct route_node *node;
+
+ switch (iter->state)
+ {
+
+ case RT_ITER_STATE_INIT:
+
+ /*
+ * We're just starting the iteration.
+ */
+ node = route_top (iter->table);
+ break;
+
+ case RT_ITER_STATE_ITERATING:
+ node = route_next (iter->current);
+ break;
+
+ case RT_ITER_STATE_PAUSED:
+
+ /*
+ * Start with the node following pause_prefix.
+ */
+ node = route_table_get_next (iter->table, &iter->pause_prefix);
+ break;
+
+ case RT_ITER_STATE_DONE:
+ return NULL;
+
+ default:
+ assert (0);
+ }
+
+ iter->current = node;
+ if (node)
+ iter->state = RT_ITER_STATE_ITERATING;
+ else
+ iter->state = RT_ITER_STATE_DONE;
+
+ return node;
+}
+
+/*
+ * route_table_iter_is_done
+ *
+ * Returns TRUE if the iteration is complete.
+ */
+static inline int
+route_table_iter_is_done (route_table_iter_t *iter)
+{
+ return iter->state == RT_ITER_STATE_DONE;
+}
+
+/*
+ * route_table_iter_started
+ *
+ * Returns TRUE if this iterator has started iterating over the tree.
+ */
+static inline int
+route_table_iter_started (route_table_iter_t *iter)
+{
+ return iter->state != RT_ITER_STATE_INIT;
+}
+
#endif /* _ZEBRA_TABLE_H */