mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-08-29 05:28:00 +00:00
Support for finding the longest parent domain in a qp-trie
This is the first of the "fancy" searches that know how the DNS namespace maps on to the structure of a qp-trie. For example, it will find the closest enclosing zone in the zone tree.
This commit is contained in:
parent
8a3a216f40
commit
fa1b57ee6e
@ -19,7 +19,11 @@
|
||||
*
|
||||
* Keys are `dns_qpkey_t`, which is a string-like thing, usually created
|
||||
* from a DNS name. You can use both relative and absolute DNS names as
|
||||
* keys.
|
||||
* keys, even in the same trie, except for one caveat: if a trie contains
|
||||
* names relative to the zone apex, the natural way to represent the apex
|
||||
* itself (spelled `@` in zone files) is a zero-length name; but a
|
||||
* zero-length name has the same qpkey representation as the root zone
|
||||
* (apart from its length), so they collide.
|
||||
*
|
||||
* Leaf values are a pair of a `void *` pointer and a `uint32_t`
|
||||
* (because that is what fits inside an internal qp-trie leaf node).
|
||||
@ -259,6 +263,13 @@ typedef enum dns_qpgc {
|
||||
DNS_QPGC_ALL,
|
||||
} dns_qpgc_t;
|
||||
|
||||
/*%
|
||||
* Options for fancy searches such as `dns_qp_findname_parent()`
|
||||
*/
|
||||
typedef enum dns_qpfind {
|
||||
DNS_QPFIND_NOEXACT = 1 << 0,
|
||||
} dns_qpfind_t;
|
||||
|
||||
/***********************************************************************
|
||||
*
|
||||
* functions - create, destory, enquire
|
||||
@ -395,8 +406,8 @@ dns_qpmulti_memusage(dns_qpmulti_t *multi);
|
||||
/*
|
||||
* XXXFANF todo, based on what we discover BIND needs
|
||||
*
|
||||
* fancy searches: longest match, lexicographic predecessor
|
||||
* (for NSEC), successor (for modification-safe iteration), etc.
|
||||
* more fancy searches: lexicographic predecessor (for NSEC),
|
||||
* successor (for modification-safe iteration), etc.
|
||||
*
|
||||
* do we need specific lookup functions to find out if the
|
||||
* returned value is readonly or mutable?
|
||||
@ -457,6 +468,30 @@ dns_qp_getname(dns_qpreadable_t qpr, const dns_name_t *name, void **pval_r,
|
||||
* \li ISC_R_SUCCESS if the leaf was found
|
||||
*/
|
||||
|
||||
isc_result_t
|
||||
dns_qp_findname_parent(dns_qpreadable_t qpr, const dns_name_t *name,
|
||||
dns_qpfind_t options, void **pval_r, uint32_t *ival_r);
|
||||
/*%<
|
||||
* Find a leaf in a qp-trie that is a parent domain of or equal to the
|
||||
* given DNS name.
|
||||
*
|
||||
* If the DNS_QPFIND_NOEXACT option is set, find a strict parent
|
||||
* domain not equal to the search name.
|
||||
*
|
||||
* The leaf values are assigned to `*pval_r` and `*ival_r`
|
||||
*
|
||||
* Requires:
|
||||
* \li `qpr` is a pointer to a readable qp-trie
|
||||
* \li `name` is a pointer to a valid `dns_name_t`
|
||||
* \li `pval_r != NULL`
|
||||
* \li `ival_r != NULL`
|
||||
*
|
||||
* Returns:
|
||||
* \li ISC_R_SUCCESS if an exact match was found
|
||||
* \li ISC_R_PARTIALMATCH if a parent domain was found
|
||||
* \li ISC_R_NOTFOUND if no match was found
|
||||
*/
|
||||
|
||||
isc_result_t
|
||||
dns_qp_insert(dns_qp_t *qp, void *pval, uint32_t ival);
|
||||
/*%<
|
||||
|
116
lib/dns/qp.c
116
lib/dns/qp.c
@ -278,6 +278,25 @@ qpkey_compare(const dns_qpkey_t key_a, const size_t keylen_a,
|
||||
return (QPKEY_EQUAL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a key constructed by dns_qpkey_fromname(), trim it down to the last
|
||||
* label boundary before the `max` length.
|
||||
*
|
||||
* This is used when searching a trie for the best match for a name.
|
||||
*/
|
||||
static size_t
|
||||
qpkey_trim_label(dns_qpkey_t key, size_t len, size_t max) {
|
||||
size_t stop = 0;
|
||||
for (size_t offset = 0; offset < max; offset++) {
|
||||
if (qpkey_bit(key, len, offset) == SHIFT_NOBYTE &&
|
||||
qpkey_bit(key, len, offset + 1) != SHIFT_NOBYTE)
|
||||
{
|
||||
stop = offset + 1;
|
||||
}
|
||||
}
|
||||
return (stop);
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
*
|
||||
* allocator wrappers
|
||||
@ -1800,4 +1819,101 @@ dns_qp_getname(dns_qpreadable_t qpr, const dns_name_t *name, void **pval_r,
|
||||
return (dns_qp_getkey(qpr, key, keylen, pval_r, ival_r));
|
||||
}
|
||||
|
||||
isc_result_t
|
||||
dns_qp_findname_parent(dns_qpreadable_t qpr, const dns_name_t *name,
|
||||
dns_qpfind_t options, void **pval_r, uint32_t *ival_r) {
|
||||
dns_qpreader_t *qp = dns_qpreader(qpr);
|
||||
dns_qpkey_t search, found;
|
||||
size_t searchlen, foundlen;
|
||||
size_t offset;
|
||||
qp_shift_t bit;
|
||||
qp_node_t *n, *twigs;
|
||||
isc_result_t result;
|
||||
unsigned int labels = 0;
|
||||
struct offref {
|
||||
uint32_t off;
|
||||
qp_ref_t ref;
|
||||
} label[DNS_NAME_MAXLABELS];
|
||||
|
||||
REQUIRE(QP_VALID(qp));
|
||||
REQUIRE(pval_r != NULL);
|
||||
REQUIRE(ival_r != NULL);
|
||||
|
||||
searchlen = dns_qpkey_fromname(search, name);
|
||||
if ((options & DNS_QPFIND_NOEXACT) != 0) {
|
||||
searchlen = qpkey_trim_label(search, searchlen, searchlen);
|
||||
result = DNS_R_PARTIALMATCH;
|
||||
} else {
|
||||
result = ISC_R_SUCCESS;
|
||||
}
|
||||
|
||||
n = get_root(qp);
|
||||
if (n == NULL) {
|
||||
return (ISC_R_NOTFOUND);
|
||||
}
|
||||
|
||||
/*
|
||||
* Like `dns_qp_insert()`, we must find a leaf. However, we don't make a
|
||||
* second pass: instead, we keep track of any leaves with shorter keys
|
||||
* that we discover along the way. (In general, qp-trie searches can be
|
||||
* one-pass, by recording their traversal, or two-pass, for less stack
|
||||
* memory usage.)
|
||||
*
|
||||
* A shorter key that can be a parent domain always has a leaf node at
|
||||
* SHIFT_NOBYTE (indicating end of its key) where our search key has a
|
||||
* normal character immediately after a label separator. Note 1: It is
|
||||
* OK if `offset - 1` underflows: it will become SIZE_MAX, which is
|
||||
* greater than `searchlen`, so `qpkey_bit()` will return SHIFT_NOBYTE,
|
||||
* which is what we want when `offset == 0`. Note 2: Any SHIFT_NOBYTE
|
||||
* twig is always `twigs[0]`.
|
||||
*/
|
||||
while (is_branch(n)) {
|
||||
prefetch_twigs(qp, n);
|
||||
twigs = branch_twigs_vector(qp, n);
|
||||
offset = branch_key_offset(n);
|
||||
bit = qpkey_bit(search, searchlen, offset);
|
||||
if (bit != SHIFT_NOBYTE && branch_has_twig(n, SHIFT_NOBYTE) &&
|
||||
qpkey_bit(search, searchlen, offset - 1) == SHIFT_NOBYTE &&
|
||||
!is_branch(&twigs[0]))
|
||||
{
|
||||
label[labels].off = offset;
|
||||
label[labels].ref = branch_twigs_ref(n);
|
||||
labels++;
|
||||
INSIST(labels <= DNS_NAME_MAXLABELS);
|
||||
}
|
||||
if (branch_has_twig(n, bit)) {
|
||||
n = branch_twig_ptr(qp, n, bit);
|
||||
} else if (labels == 0) {
|
||||
/* any twig will do */
|
||||
n = &twigs[0];
|
||||
} else {
|
||||
n = ref_ptr(qp, label[labels - 1].ref);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* do the keys differ, and if so, where? */
|
||||
foundlen = leaf_qpkey(qp, n, found);
|
||||
offset = qpkey_compare(search, searchlen, found, foundlen);
|
||||
|
||||
if (offset == QPKEY_EQUAL || offset == foundlen) {
|
||||
*pval_r = leaf_pval(n);
|
||||
*ival_r = leaf_ival(n);
|
||||
if (offset == QPKEY_EQUAL) {
|
||||
return (result);
|
||||
} else {
|
||||
return (DNS_R_PARTIALMATCH);
|
||||
}
|
||||
}
|
||||
while (labels-- > 0) {
|
||||
if (offset > label[labels].off) {
|
||||
n = ref_ptr(qp, label[labels].ref);
|
||||
*pval_r = leaf_pval(n);
|
||||
*ival_r = leaf_ival(n);
|
||||
return (DNS_R_PARTIALMATCH);
|
||||
}
|
||||
}
|
||||
return (ISC_R_NOTFOUND);
|
||||
}
|
||||
|
||||
/**********************************************************************/
|
||||
|
@ -216,10 +216,150 @@ ISC_RUN_TEST_IMPL(qpiter) {
|
||||
dns_qp_destroy(&qp);
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
no_op(void *uctx, void *pval, uint32_t ival) {
|
||||
UNUSED(uctx);
|
||||
UNUSED(pval);
|
||||
UNUSED(ival);
|
||||
return (1);
|
||||
}
|
||||
|
||||
static size_t
|
||||
qpkey_fromstring(dns_qpkey_t key, void *uctx, void *pval, uint32_t ival) {
|
||||
dns_fixedname_t fixed;
|
||||
|
||||
UNUSED(uctx);
|
||||
UNUSED(ival);
|
||||
if (*(char *)pval == '\0') {
|
||||
return (0);
|
||||
}
|
||||
dns_test_namefromstring(pval, &fixed);
|
||||
return (dns_qpkey_fromname(key, dns_fixedname_name(&fixed)));
|
||||
}
|
||||
|
||||
const struct dns_qpmethods string_methods = {
|
||||
no_op,
|
||||
no_op,
|
||||
qpkey_fromstring,
|
||||
getname,
|
||||
};
|
||||
|
||||
struct check_partialmatch {
|
||||
const char *query;
|
||||
dns_qpfind_t options;
|
||||
isc_result_t result;
|
||||
const char *found;
|
||||
};
|
||||
|
||||
static void
|
||||
check_partialmatch(dns_qp_t *qp, struct check_partialmatch check[]) {
|
||||
for (int i = 0; check[i].query != NULL; i++) {
|
||||
isc_result_t result;
|
||||
dns_fixedname_t fixed;
|
||||
dns_name_t *name = dns_fixedname_name(&fixed);
|
||||
void *pval = NULL;
|
||||
uint32_t ival;
|
||||
|
||||
#if 0
|
||||
fprintf(stderr, "%s %u %s %s\n", check[i].query,
|
||||
check[i].options, isc_result_totext(check[i].result),
|
||||
check[i].found);
|
||||
#endif
|
||||
dns_test_namefromstring(check[i].query, &fixed);
|
||||
result = dns_qp_findname_parent(qp, name, check[i].options,
|
||||
&pval, &ival);
|
||||
assert_int_equal(result, check[i].result);
|
||||
if (check[i].found == NULL) {
|
||||
assert_null(pval);
|
||||
} else {
|
||||
assert_string_equal(pval, check[i].found);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
insert_str(dns_qp_t *qp, const char *str) {
|
||||
isc_result_t result;
|
||||
uintptr_t pval = (uintptr_t)str;
|
||||
INSIST((pval & 3) == 0);
|
||||
result = dns_qp_insert(qp, (void *)pval, 0);
|
||||
assert_int_equal(result, ISC_R_SUCCESS);
|
||||
}
|
||||
|
||||
ISC_RUN_TEST_IMPL(partialmatch) {
|
||||
isc_result_t result;
|
||||
dns_qp_t *qp = NULL;
|
||||
|
||||
dns_qp_create(mctx, &string_methods, NULL, &qp);
|
||||
|
||||
/*
|
||||
* Fixed size strings [16] should ensure leaf-compatible alignment.
|
||||
*/
|
||||
const char insert[][16] = {
|
||||
"a.b.", "b.", "fo.bar.", "foo.bar.",
|
||||
"fooo.bar.", "web.foo.bar.", ".", "",
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
while (insert[i][0] != '.') {
|
||||
insert_str(qp, insert[i++]);
|
||||
}
|
||||
|
||||
static struct check_partialmatch check1[] = {
|
||||
{ "a.b.", 0, ISC_R_SUCCESS, "a.b." },
|
||||
{ "a.b.", DNS_QPFIND_NOEXACT, DNS_R_PARTIALMATCH, "b." },
|
||||
{ "b.c.", DNS_QPFIND_NOEXACT, ISC_R_NOTFOUND, NULL },
|
||||
{ "bar.", 0, ISC_R_NOTFOUND, NULL },
|
||||
{ "f.bar.", 0, ISC_R_NOTFOUND, NULL },
|
||||
{ "foo.bar.", 0, ISC_R_SUCCESS, "foo.bar." },
|
||||
{ "foo.bar.", DNS_QPFIND_NOEXACT, ISC_R_NOTFOUND, NULL },
|
||||
{ "foooo.bar.", 0, ISC_R_NOTFOUND, NULL },
|
||||
{ "w.foo.bar.", 0, DNS_R_PARTIALMATCH, "foo.bar." },
|
||||
{ "www.foo.bar.", 0, DNS_R_PARTIALMATCH, "foo.bar." },
|
||||
{ "web.foo.bar.", 0, ISC_R_SUCCESS, "web.foo.bar." },
|
||||
{ "webby.foo.bar.", 0, DNS_R_PARTIALMATCH, "foo.bar." },
|
||||
{ "my.web.foo.bar.", 0, DNS_R_PARTIALMATCH, "web.foo.bar." },
|
||||
{ "web.foo.bar.", DNS_QPFIND_NOEXACT, DNS_R_PARTIALMATCH,
|
||||
"foo.bar." },
|
||||
{ "my.web.foo.bar.", DNS_QPFIND_NOEXACT, DNS_R_PARTIALMATCH,
|
||||
"web.foo.bar." },
|
||||
{ NULL, 0, 0, NULL },
|
||||
};
|
||||
check_partialmatch(qp, check1);
|
||||
|
||||
/* what if the trie contains the root? */
|
||||
INSIST(insert[i][0] == '.');
|
||||
insert_str(qp, insert[i++]);
|
||||
|
||||
static struct check_partialmatch check2[] = {
|
||||
{ "b.c.", DNS_QPFIND_NOEXACT, DNS_R_PARTIALMATCH, "." },
|
||||
{ "bar.", 0, DNS_R_PARTIALMATCH, "." },
|
||||
{ "foo.bar.", 0, ISC_R_SUCCESS, "foo.bar." },
|
||||
{ "foo.bar.", DNS_QPFIND_NOEXACT, DNS_R_PARTIALMATCH, "." },
|
||||
{ NULL, 0, 0, NULL },
|
||||
};
|
||||
check_partialmatch(qp, check2);
|
||||
|
||||
/* what if entries in the trie are relative to the zone apex? */
|
||||
dns_qpkey_t rootkey = { SHIFT_NOBYTE };
|
||||
result = dns_qp_deletekey(qp, rootkey, 1);
|
||||
assert_int_equal(result, ISC_R_SUCCESS);
|
||||
INSIST(insert[i][0] == '\0');
|
||||
insert_str(qp, insert[i++]);
|
||||
check_partialmatch(qp, (struct check_partialmatch[]){
|
||||
{ "bar", 0, DNS_R_PARTIALMATCH, "" },
|
||||
{ "bar.", 0, DNS_R_PARTIALMATCH, "" },
|
||||
{ NULL, 0, 0, NULL },
|
||||
});
|
||||
|
||||
dns_qp_destroy(&qp);
|
||||
}
|
||||
|
||||
ISC_TEST_LIST_START
|
||||
ISC_TEST_ENTRY(qpkey_name)
|
||||
ISC_TEST_ENTRY(qpkey_sort)
|
||||
ISC_TEST_ENTRY(qpiter)
|
||||
ISC_TEST_ENTRY(partialmatch)
|
||||
ISC_TEST_LIST_END
|
||||
|
||||
ISC_TEST_MAIN
|
||||
|
Loading…
x
Reference in New Issue
Block a user