diff --git a/README.rst b/README.rst index 75d84ee..2a19f06 100644 --- a/README.rst +++ b/README.rst @@ -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" diff --git a/radix/__init__.py b/radix/__init__.py index bdfd2a0..6caf635 100644 --- a/radix/__init__.py +++ b/radix/__init__.py @@ -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 diff --git a/radix/_radix.c b/radix/_radix.c index 45e6633..ff147d2 100644 --- a/radix/_radix.c +++ b/radix/_radix.c @@ -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\ @@ -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 */ @@ -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" diff --git a/radix/_radix/radix.c b/radix/_radix/radix.c index fd2f0d9..4dfbcfa 100644 --- a/radix/_radix/radix.c +++ b/radix/_radix/radix.c @@ -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) diff --git a/radix/_radix/radix.h b/radix/_radix/radix.h index 8115a7b..0d70788 100644 --- a/radix/_radix/radix.h +++ b/radix/_radix/radix.h @@ -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 diff --git a/radix/radix.py b/radix/radix.py index 53b99c7..fedab95 100644 --- a/radix/radix.py +++ b/radix/radix.py @@ -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 @@ -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: diff --git a/tests/test_regression.py b/tests/test_regression.py index d2c75d0..b4199f1 100644 --- a/tests/test_regression.py +++ b/tests/test_regression.py @@ -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()