Skip to content

Commit

Permalink
Add search_worst() function, a shortest-matching prefix search
Browse files Browse the repository at this point in the history
search_worst() is useful if you want to use the radix tree to build
aggregated prefix-sets
  • Loading branch information
job committed Sep 6, 2014
1 parent 131049c commit e91b23a
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 0 deletions.
4 changes: 4 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ A simple example that demonstrates most of the features: ::
# that contains the search term (routing-style lookup)
rnode = rtree.search_best("10.123.45.6")

# Worst-search will return the shortest matching prefix
# that contains the search term (inverse routing-style lookup)
rnode = rtree.search_worst("10.123.45.6")

# There are a couple of implicit members of a RadixNode:
print rnode.network # -> "10.0.0.0"
print rnode.prefix # -> "10.0.0.0/8"
Expand Down
1 change: 1 addition & 0 deletions radix/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def __init__(self):
self.delete = self._radix.delete
self.search_exact = self._radix.search_exact
self.search_best = self._radix.search_best
self.search_worst = self._radix.search_worst
self.nodes = self._radix.nodes
self.prefixes = self._radix.prefixes

Expand Down
46 changes: 46 additions & 0 deletions radix/_radix.c
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,47 @@ Radix_search_best(RadixObject *self, PyObject *args, PyObject *kw_args)
return (PyObject *)node_obj;
}

PyDoc_STRVAR(Radix_search_worst_doc,
"Radix.search_worst(network[, masklen][, packed] -> None\n\
\n\
Search for the specified network in the radix tree.\n\
\n\
search_worst will return the worst (shortest) entry that includes the\n\
specified 'prefix', much like opposite of a IP routing table lookup.\n\
\n\
If no match is found, then returns None.");

static PyObject *
Radix_search_worst(RadixObject *self, PyObject *args, PyObject *kw_args)
{
radix_node_t *node;
RadixNodeObject *node_obj;
prefix_t *prefix;
static char *keywords[] = { "network", "masklen", "packed", NULL };

char *addr = NULL, *packed = NULL;
long prefixlen = -1;
int packlen = -1;

if (!PyArg_ParseTupleAndKeywords(args, kw_args, "|sls#:search_worst", keywords,
&addr, &prefixlen, &packed, &packlen))
return NULL;
if ((prefix = args_to_prefix(addr, packed, packlen, prefixlen)) == NULL)
return NULL;

if ((node = radix_search_worst(PICKRT(prefix, self), prefix)) == NULL ||
node->data == NULL) {
Deref_Prefix(prefix);
Py_INCREF(Py_None);
return Py_None;
}
Deref_Prefix(prefix);
node_obj = node->data;
Py_XINCREF(node_obj);
return (PyObject *)node_obj;
}


PyDoc_STRVAR(Radix_nodes_doc,
"Radix.nodes(prefix) -> List of RadixNode\n\
\n\
Expand Down Expand Up @@ -547,6 +588,7 @@ static PyMethodDef Radix_methods[] = {
{"delete", (PyCFunction)Radix_delete, METH_VARARGS|METH_KEYWORDS, Radix_delete_doc },
{"search_exact",(PyCFunction)Radix_search_exact,METH_VARARGS|METH_KEYWORDS, Radix_search_exact_doc },
{"search_best", (PyCFunction)Radix_search_best, METH_VARARGS|METH_KEYWORDS, Radix_search_best_doc },
{"search_worst", (PyCFunction)Radix_search_worst, METH_VARARGS|METH_KEYWORDS, Radix_search_worst_doc },
{"nodes", (PyCFunction)Radix_nodes, METH_VARARGS, Radix_nodes_doc },
{"prefixes", (PyCFunction)Radix_prefixes, METH_VARARGS, Radix_prefixes_doc },
{NULL, NULL} /* sentinel */
Expand Down Expand Up @@ -809,6 +851,10 @@ PyDoc_STRVAR(module_doc,
" # that contains the search term (routing-style lookup)\n"
" rnode = rtree.search_best(\"10.123.45.6\")\n"
"\n"
" # Worst-match search will return the shortest matching prefix\n"
" # that contains the search term (inverse routing-style lookup)\n"
" rnode = rtree.search_worst(\"10.123.45.6\")\n"
"\n"
" # There are a couple of implicit members of a RadixNode:\n"
" print rnode.network # -> \"10.0.0.0\"\n"
" print rnode.prefix # -> \"10.0.0.0/8\"\n"
Expand Down
53 changes: 53 additions & 0 deletions radix/_radix/radix.c
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,59 @@ radix_node_t
return (radix_search_best2(radix, prefix, 1));
}

/* if inclusive != 0, "worst" may be the given prefix itself */
static radix_node_t
*radix_search_worst2(radix_tree_t *radix, prefix_t *prefix, int inclusive)
{
radix_node_t *node;
radix_node_t *stack[RADIX_MAXBITS + 1];
u_char *addr;
u_int bitlen;
int cnt = 0;
int iterator = 0;

if (radix->head == NULL)
return (NULL);

node = radix->head;
addr = prefix_touchar(prefix);
bitlen = prefix->bitlen;

while (node->bit < bitlen) {
if (node->prefix)
stack[cnt++] = node;
if (BIT_TEST(addr[node->bit >> 3], 0x80 >> (node->bit & 0x07)))
node = node->r;
else
node = node->l;

if (node == NULL)
break;
}

if (inclusive && node && node->prefix)
stack[cnt++] = node;


if (cnt <= 0)
return (NULL);

for (iterator; iterator < cnt; ++iterator) {
node = stack[iterator];
if (comp_with_mask(prefix_tochar(node->prefix),
prefix_tochar(prefix), node->prefix->bitlen))
return (node);
}
return (NULL);
}


radix_node_t
*radix_search_worst(radix_tree_t *radix, prefix_t *prefix)
{
return (radix_search_worst2(radix, prefix, 1));
}


radix_node_t
*radix_lookup(radix_tree_t *radix, prefix_t *prefix)
Expand Down
1 change: 1 addition & 0 deletions radix/_radix/radix.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ radix_node_t *radix_lookup(radix_tree_t *radix, prefix_t *prefix);
void radix_remove(radix_tree_t *radix, radix_node_t *node);
radix_node_t *radix_search_exact(radix_tree_t *radix, prefix_t *prefix);
radix_node_t *radix_search_best(radix_tree_t *radix, prefix_t *prefix);
radix_node_t *radix_search_worst(radix_tree_t *radix, prefix_t *prefix);
void radix_process(radix_tree_t *radix, rdx_cb_t func, void *cbctx);

#define RADIX_MAXBITS 128
Expand Down
37 changes: 37 additions & 0 deletions radix/radix.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,32 @@ def search_exact(self, prefix):
return node
return None

def search_worst(self, prefix):
if self.head is None:
return None
node = self.head
addr = prefix.addr
bitlen = prefix.bitlen

stack = []
while node.bitlen < bitlen:
if node._prefix:
stack.append(node)
if self._addr_test(addr, node.bitlen):
node = node.right
else:
node = node.left
if node is None:
break
if node and node._prefix:
stack.append(node)
if len(stack) <= 0:
return None
for node in stack:
if self._prefix_match(node._prefix, prefix, node.bitlen):
return node
return None

def _prefix_match(self, left, right, bitlen):
l = left.addr
r = right.addr
Expand Down Expand Up @@ -411,6 +437,17 @@ def search_best(self, network=None, masklen=None, packed=None):
else:
return None

def search_worst(self, network=None, masklen=None, packed=None):
prefix = RadixPrefix(network, masklen, packed)
if prefix.family == AF_INET:
node = self._tree4.search_worst(prefix)
else:
node = self._tree6.search_worst(prefix)
if node and node.data is not None:
return node
else:
return None

def _iter(self, node):
stack = []
while node is not None:
Expand Down
12 changes: 12 additions & 0 deletions tests/test_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,18 @@ def test_23_add_with_glue(self):
expected = ['1.0.24.0/23', '1.0.26.0/23', '1.0.28.0/22']
self.assertEqual(expected, [n.prefix for n in tree])

def test_24_search_worst(self):
tree = radix.Radix()
tree.add('10.0.0.0/8')
tree.add('10.0.0.0/13')
tree.add('10.0.0.0/16')
self.assertEquals(
tree.search_worst('10.0.0.0/15').prefix,
'10.0.0.0/8')
self.assertEquals(
tree.search_worst('100.0.0.0/15'),
None)


def main():
unittest.main()
Expand Down

0 comments on commit e91b23a

Please sign in to comment.