add semantics to dns_nametree to support bitfields

name trees can now hold either boolean values or bit fields. the
type is selected when the name tree is created.

the behavior of dns_nametree_add() differs slightly beteween the types:
in a boolean tree adding an existing name will return ISC_R_EXISTS,
but in a bitfield tree it simply sets the specified bit in the bitfield
and returns ISC_R_SUCCESS.
This commit is contained in:
Evan Hunt 2023-08-16 19:59:50 -07:00 committed by Ondřej Surý
parent 54fc02410e
commit 9ed1dba976
No known key found for this signature in database
GPG key ID: 2820F37E873DEA41
5 changed files with 273 additions and 96 deletions

View file

@ -645,7 +645,7 @@ configure_view_nametable(const cfg_obj_t *vconfig, const cfg_obj_t *config,
}
}
dns_nametree_create(mctx, confname, ntp);
dns_nametree_create(mctx, DNS_NAMETREE_BOOL, confname, ntp);
name = dns_fixedname_initname(&fixed);
for (element = cfg_list_first(obj); element != NULL;

View file

@ -40,16 +40,22 @@
/* Define to 1 for detailed reference tracing */
#undef DNS_NAMETREE_TRACE
typedef enum { DNS_NAMETREE_BOOL, DNS_NAMETREE_BITS } dns_nametree_type_t;
ISC_LANG_BEGINDECLS
void
dns_nametree_create(isc_mem_t *mctx, const char *name, dns_nametree_t **ntp);
dns_nametree_create(isc_mem_t *mctx, dns_nametree_type_t type, const char *name,
dns_nametree_t **ntp);
/*%<
* Create a nametree.
*
* If 'name' is not NULL, it will be saved as the name of the QP trie
* for debugging purposes.
*
* 'type' indicates whether the tree will be used for storing boolean
* values (DNS_NAMETREE_BOOL) or bitfields (DNS_NAMETREE_BITS).
*
* Requires:
*
*\li 'mctx' is a valid memory context.
@ -57,13 +63,22 @@ dns_nametree_create(isc_mem_t *mctx, const char *name, dns_nametree_t **ntp);
*/
isc_result_t
dns_nametree_add(dns_nametree_t *nametree, const dns_name_t *name, bool value);
dns_nametree_add(dns_nametree_t *nametree, const dns_name_t *name,
uint32_t value);
/*%<
* Add a node to 'nametree'.
*
* 'value' is a single boolean value, true or false. If the name already
* If the nametree type was set to DNS_NAMETREE_BOOL, then 'value'
* represents a single boolean value, true or false. If the name already
* exists within the tree, then return ISC_R_EXISTS.
*
* If the nametree type was set to DNS_NAMETREE_BITS, then 'value' is
* a bit number within a bit field, which is sized to accomodate at least
* 'value' bits. If the name already exists, then that bit will be set
* in the bitfield, other bits will be retained, and ISC_R_SUCCESS will be
* returned. If 'value' excees the number of bits in the existing bit
* field, the field will be expanded.
*
* Requires:
*
*\li 'nametree' points to a valid nametree.
@ -116,13 +131,21 @@ dns_nametree_find(dns_nametree_t *nametree, const dns_name_t *name,
*/
bool
dns_nametree_covered(dns_nametree_t *nametree, const dns_name_t *name);
dns_nametree_covered(dns_nametree_t *nametree, const dns_name_t *name,
uint32_t bit);
/*%<
* Indicates whether a 'name' is covered by 'nametree'.
* Indicates whether a 'name' (with optional 'bit' value) is covered by
* 'nametree'.
*
* This returns true if 'name' has a match or a closest ancestor in
* 'nametree' with its value set to 'true'. If a name is not found, or if
* 'nametree' is NULL, the default return value is false.
* In DNS_NAMETREE_BOOL nametrees, this returns true if 'name' has a match
* or a closest ancestor in 'nametree' with its value set to 'true'.
* 'bit' is ignored.
*
* In DNS_NAMETREE_BITS trees, this returns true if 'name' has a match or
* a closest ancestor in 'nametree' with the 'bit' set in its bitfield.
*
* If a name is not found, or if 'nametree' is NULL, the default return
* value is false.
*
* Requires:
*

View file

@ -32,6 +32,7 @@ struct dns_nametree {
unsigned int magic;
isc_mem_t *mctx;
isc_refcount_t references;
dns_nametree_type_t type;
dns_qpmulti_t *table;
char name[64];
};
@ -41,10 +42,8 @@ struct dns_ntnode {
isc_refcount_t references;
dns_fixedname_t fn;
dns_name_t *name;
union {
bool value;
unsigned char *bits;
};
bool set;
uint8_t *bits;
};
/* QP trie methods */
@ -67,6 +66,9 @@ static dns_qpmethods_t qpmethods = {
static void
destroy_ntnode(dns_ntnode_t *node) {
isc_refcount_destroy(&node->references);
if (node->bits != NULL) {
isc_mem_cput(node->mctx, node->bits, 8, sizeof(uint32_t));
}
isc_mem_putanddetach(&node->mctx, node, sizeof(dns_ntnode_t));
}
@ -77,7 +79,8 @@ ISC_REFCOUNT_IMPL(dns_ntnode, destroy_ntnode);
#endif
void
dns_nametree_create(isc_mem_t *mctx, const char *name, dns_nametree_t **ntp) {
dns_nametree_create(isc_mem_t *mctx, dns_nametree_type_t type, const char *name,
dns_nametree_t **ntp) {
dns_nametree_t *nametree = NULL;
REQUIRE(ntp != NULL && *ntp == NULL);
@ -85,6 +88,7 @@ dns_nametree_create(isc_mem_t *mctx, const char *name, dns_nametree_t **ntp) {
nametree = isc_mem_get(mctx, sizeof(*nametree));
*nametree = (dns_nametree_t){
.magic = NAMETREE_MAGIC,
.type = type,
};
isc_mem_attach(mctx, &nametree->mctx);
isc_refcount_init(&nametree->references, 1);
@ -126,31 +130,69 @@ newnode(isc_mem_t *mctx, const dns_name_t *name) {
return (node);
}
static bool
matchbit(unsigned char *bits, uint32_t val) {
unsigned int len = val / 8;
unsigned int mask = 1 << (val % 8);
if ((bits[len] & mask) != 0) {
return (true);
}
return (false);
}
isc_result_t
dns_nametree_add(dns_nametree_t *nametree, const dns_name_t *name, bool value) {
dns_nametree_add(dns_nametree_t *nametree, const dns_name_t *name,
uint32_t value) {
isc_result_t result;
dns_qp_t *qp = NULL;
unsigned int len, mask;
dns_ntnode_t *old = NULL, *new = NULL;
REQUIRE(VALID_NAMETREE(nametree));
REQUIRE(name != NULL);
dns_qpmulti_write(nametree->table, &qp);
result = dns_qp_getname(qp, name, NULL, NULL);
if (result == ISC_R_SUCCESS) {
result = ISC_R_EXISTS;
} else {
dns_ntnode_t *node = newnode(nametree->mctx, name);
node->value = value;
result = dns_qp_insert(qp, node, 0);
switch (nametree->type) {
case DNS_NAMETREE_BOOL:
new = newnode(nametree->mctx, name);
new->set = value;
break;
/*
* We detach the node here, so any dns_qp_deletename() will
* destroy the node directly.
*/
dns_ntnode_detach(&node);
case DNS_NAMETREE_BITS:
result = dns_qp_getname(qp, name, (void **)&old, NULL);
if (result == ISC_R_SUCCESS && matchbit(old->bits, value)) {
goto out;
}
len = value / 8;
mask = 1 << (value % 8);
new = newnode(nametree->mctx, name);
new->bits = isc_mem_cget(nametree->mctx, 8, sizeof(value));
if (result == ISC_R_SUCCESS) {
INSIST(old != NULL);
memmove(new->bits, old->bits, old->bits[0]);
result = dns_qp_deletename(qp, name, NULL, NULL);
INSIST(result == ISC_R_SUCCESS);
}
new->bits[len] |= mask;
break;
default:
UNREACHABLE();
}
result = dns_qp_insert(qp, new, 0);
/*
* We detach the node here, so any dns_qp_deletename() will
* destroy the node directly.
*/
dns_ntnode_detach(&new);
out:
dns_qp_compact(qp, DNS_QPGC_MAYBE);
dns_qpmulti_commit(nametree->table, &qp);
return (result);
@ -200,12 +242,12 @@ dns_nametree_find(dns_nametree_t *nametree, const dns_name_t *name,
}
bool
dns_nametree_covered(dns_nametree_t *nametree, const dns_name_t *name) {
dns_nametree_covered(dns_nametree_t *nametree, const dns_name_t *name,
uint32_t bit) {
isc_result_t result;
dns_qpread_t qpr;
dns_ntnode_t *ntnode = NULL;
void *pval = NULL;
bool value = false;
dns_ntnode_t *node = NULL;
bool ret = false;
REQUIRE(nametree == NULL || VALID_NAMETREE(nametree));
@ -214,14 +256,17 @@ dns_nametree_covered(dns_nametree_t *nametree, const dns_name_t *name) {
}
dns_qpmulti_query(nametree->table, &qpr);
result = dns_qp_findname_ancestor(&qpr, name, 0, &pval, NULL);
result = dns_qp_findname_ancestor(&qpr, name, 0, (void **)&node, NULL);
if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
ntnode = pval;
value = ntnode->value;
if (nametree->type == DNS_NAMETREE_BOOL) {
ret = node->set;
} else {
ret = matchbit(node->bits, bit);
}
}
dns_qpread_destroy(nametree->table, &qpr);
return (value);
return (ret);
}
static void

View file

@ -6752,7 +6752,7 @@ is_answeraddress_allowed(dns_view_t *view, dns_name_t *name,
* If the owner name matches one in the exclusion list, either
* exactly or partially, allow it.
*/
if (dns_nametree_covered(view->answeracl_exclude, name)) {
if (dns_nametree_covered(view->answeracl_exclude, name, 0)) {
return (true);
}
@ -6865,7 +6865,7 @@ is_answertarget_allowed(fetchctx_t *fctx, dns_name_t *qname, dns_name_t *rname,
* If the owner name matches one in the exclusion list, either
* exactly or partially, allow it.
*/
if (dns_nametree_covered(view->answernames_exclude, qname)) {
if (dns_nametree_covered(view->answernames_exclude, qname, 0)) {
return (true);
}
@ -6885,7 +6885,7 @@ is_answertarget_allowed(fetchctx_t *fctx, dns_name_t *qname, dns_name_t *rname,
/*
* Otherwise, apply filters.
*/
if (dns_nametree_covered(view->denyanswernames, tname)) {
if (dns_nametree_covered(view->denyanswernames, tname, 0)) {
char qnamebuf[DNS_NAME_FORMATSIZE];
char tnamebuf[DNS_NAME_FORMATSIZE];
char classbuf[64];
@ -10887,7 +10887,8 @@ dns_resolver_setmustbesecure(dns_resolver_t *resolver, const dns_name_t *name,
REQUIRE(VALID_RESOLVER(resolver));
if (resolver->mustbesecure == NULL) {
dns_nametree_create(resolver->mctx, "dnssec-must-be-secure",
dns_nametree_create(resolver->mctx, DNS_NAMETREE_BOOL,
"dnssec-must-be-secure",
&resolver->mustbesecure);
}
@ -10899,7 +10900,7 @@ bool
dns_resolver_getmustbesecure(dns_resolver_t *resolver, const dns_name_t *name) {
REQUIRE(VALID_RESOLVER(resolver));
return (dns_nametree_covered(resolver->mustbesecure, name));
return (dns_nametree_covered(resolver->mustbesecure, name, 0));
}
void

View file

@ -37,7 +37,8 @@
#include <tests/dns.h>
dns_nametree_t *nametree = NULL;
dns_nametree_t *booltree = NULL;
dns_nametree_t *bitstree = NULL;
/*
* Test utilities. In general, these assume input parameters are valid
@ -46,38 +47,49 @@ dns_nametree_t *nametree = NULL;
* the test code concise.
*/
/* Common setup: create a nametree to test with a few keys */
/* Common setup: create a booltree to test with a few keys */
static void
create_tables(void) {
dns_fixedname_t fn;
dns_name_t *name = dns_fixedname_name(&fn);
dns_nametree_create(mctx, "test", &nametree);
dns_nametree_create(mctx, DNS_NAMETREE_BOOL, "bool test", &booltree);
dns_nametree_create(mctx, DNS_NAMETREE_BITS, "bits test", &bitstree);
/* Add a positive node */
/* Add a positive boolean node */
dns_test_namefromstring("example.com.", &fn);
assert_int_equal(dns_nametree_add(nametree, name, true), ISC_R_SUCCESS);
assert_int_equal(dns_nametree_add(booltree, name, true), ISC_R_SUCCESS);
/* Add a negative node below it */
/* Add assorted bits to a bitfield node */
assert_int_equal(dns_nametree_add(bitstree, name, 1), ISC_R_SUCCESS);
assert_int_equal(dns_nametree_add(bitstree, name, 9), ISC_R_SUCCESS);
assert_int_equal(dns_nametree_add(bitstree, name, 53), ISC_R_SUCCESS);
/* Add negative boolean nodes with and without parents */
dns_test_namefromstring("negative.example.com.", &fn);
assert_int_equal(dns_nametree_add(nametree, name, false),
assert_int_equal(dns_nametree_add(booltree, name, false),
ISC_R_SUCCESS);
dns_test_namefromstring("negative.example.org.", &fn);
assert_int_equal(dns_nametree_add(booltree, name, false),
ISC_R_SUCCESS);
/* Add a negative node with no parent */
dns_test_namefromstring("negative.example.org.", &fn);
assert_int_equal(dns_nametree_add(nametree, name, false),
ISC_R_SUCCESS);
/* Add a bitfield nodes under a parent */
dns_test_namefromstring("sub.example.com.", &fn);
assert_int_equal(dns_nametree_add(bitstree, name, 2), ISC_R_SUCCESS);
}
static void
destroy_tables(void) {
if (nametree != NULL) {
dns_nametree_detach(&nametree);
if (booltree != NULL) {
dns_nametree_detach(&booltree);
}
if (bitstree != NULL) {
dns_nametree_detach(&bitstree);
}
rcu_barrier();
}
ISC_RUN_TEST_IMPL(add) {
ISC_RUN_TEST_IMPL(add_bool) {
dns_ntnode_t *node = NULL;
dns_fixedname_t fn;
dns_name_t *name = dns_fixedname_name(&fn);
@ -88,15 +100,15 @@ ISC_RUN_TEST_IMPL(add) {
* Getting the node for example.com should succeed.
*/
dns_test_namefromstring("example.com.", &fn);
assert_int_equal(dns_nametree_find(nametree, name, &node),
assert_int_equal(dns_nametree_find(booltree, name, &node),
ISC_R_SUCCESS);
dns_ntnode_detach(&node);
/*
* Try to add the same name. This should fail.
*/
assert_int_equal(dns_nametree_add(nametree, name, false), ISC_R_EXISTS);
assert_int_equal(dns_nametree_find(nametree, name, &node),
assert_int_equal(dns_nametree_add(booltree, name, false), ISC_R_EXISTS);
assert_int_equal(dns_nametree_find(booltree, name, &node),
ISC_R_SUCCESS);
dns_ntnode_detach(&node);
@ -104,14 +116,133 @@ ISC_RUN_TEST_IMPL(add) {
* Try to add a new name.
*/
dns_test_namefromstring("newname.com.", &fn);
assert_int_equal(dns_nametree_add(nametree, name, true), ISC_R_SUCCESS);
assert_int_equal(dns_nametree_find(nametree, name, &node),
assert_int_equal(dns_nametree_add(booltree, name, true), ISC_R_SUCCESS);
assert_int_equal(dns_nametree_find(booltree, name, &node),
ISC_R_SUCCESS);
dns_ntnode_detach(&node);
destroy_tables();
}
ISC_RUN_TEST_IMPL(add_bits) {
dns_ntnode_t *node = NULL;
dns_fixedname_t fn;
dns_name_t *name = dns_fixedname_name(&fn);
create_tables();
/*
* Getting the node for example.com should succeed.
*/
dns_test_namefromstring("example.com.", &fn);
assert_int_equal(dns_nametree_find(booltree, name, &node),
ISC_R_SUCCESS);
dns_ntnode_detach(&node);
/*
* Try to add the same name. This should succeed.
*/
assert_int_equal(dns_nametree_add(bitstree, name, 1), ISC_R_SUCCESS);
assert_int_equal(dns_nametree_add(bitstree, name, 2), ISC_R_SUCCESS);
assert_int_equal(dns_nametree_add(bitstree, name, 3), ISC_R_SUCCESS);
assert_int_equal(dns_nametree_find(booltree, name, &node),
ISC_R_SUCCESS);
dns_ntnode_detach(&node);
/*
* Try to add a new name.
*/
dns_test_namefromstring("newname.com.", &fn);
assert_int_equal(dns_nametree_add(booltree, name, true), ISC_R_SUCCESS);
assert_int_equal(dns_nametree_find(booltree, name, &node),
ISC_R_SUCCESS);
dns_ntnode_detach(&node);
destroy_tables();
}
ISC_RUN_TEST_IMPL(covered_bool) {
dns_fixedname_t fn;
dns_name_t *name = dns_fixedname_name(&fn);
const char *yesnames[] = { "example.com.", "sub.example.com.", NULL };
const char *nonames[] = { "whatever.com.", "negative.example.com.",
"example.org.", "negative.example.org.",
NULL };
create_tables();
for (const char **n = yesnames; *n != NULL; n++) {
dns_test_namefromstring(*n, &fn);
assert_true(dns_nametree_covered(booltree, name, 0));
}
for (const char **n = nonames; *n != NULL; n++) {
dns_test_namefromstring(*n, &fn);
assert_false(dns_nametree_covered(booltree, name, 0));
}
/* If the nametree is NULL, dns_nametree_covered() returns false. */
dns_test_namefromstring("anyname.example.", &fn);
assert_false(dns_nametree_covered(NULL, name, 0));
destroy_tables();
}
ISC_RUN_TEST_IMPL(covered_bits) {
dns_fixedname_t fn;
dns_name_t *name = dns_fixedname_name(&fn);
create_tables();
/* check existing bit values */
dns_test_namefromstring("example.com.", &fn);
assert_false(dns_nametree_covered(bitstree, name, 0));
assert_true(dns_nametree_covered(bitstree, name, 1));
assert_false(dns_nametree_covered(bitstree, name, 2));
assert_false(dns_nametree_covered(bitstree, name, 3));
assert_true(dns_nametree_covered(bitstree, name, 9));
assert_true(dns_nametree_covered(bitstree, name, 53));
assert_false(dns_nametree_covered(bitstree, name, 288));
/* add a small bit value, test again */
assert_int_equal(dns_nametree_add(bitstree, name, 3), ISC_R_SUCCESS);
assert_true(dns_nametree_covered(bitstree, name, 3));
/* add a large bit value, test again */
assert_int_equal(dns_nametree_add(bitstree, name, 615), ISC_R_SUCCESS);
assert_true(dns_nametree_covered(bitstree, name, 615));
/* check existing bit values for subdomain */
dns_test_namefromstring("sub.example.com.", &fn);
assert_false(dns_nametree_covered(bitstree, name, 0));
assert_false(dns_nametree_covered(bitstree, name, 1));
assert_true(dns_nametree_covered(bitstree, name, 2));
assert_false(dns_nametree_covered(bitstree, name, 3));
assert_false(dns_nametree_covered(bitstree, name, 9));
assert_false(dns_nametree_covered(bitstree, name, 53));
assert_false(dns_nametree_covered(bitstree, name, 288));
/* check nonexistent subdomain is all false */
dns_test_namefromstring("other.example.com", &fn);
assert_false(dns_nametree_covered(bitstree, name, 0));
assert_false(dns_nametree_covered(bitstree, name, 1));
assert_false(dns_nametree_covered(bitstree, name, 2));
assert_false(dns_nametree_covered(bitstree, name, 3));
assert_false(dns_nametree_covered(bitstree, name, 9));
assert_false(dns_nametree_covered(bitstree, name, 53));
assert_false(dns_nametree_covered(bitstree, name, 288));
/* check nonexistent domain is all false */
dns_test_namefromstring("anyname.", &fn);
assert_false(dns_nametree_covered(bitstree, name, 0));
assert_false(dns_nametree_covered(bitstree, name, 1));
assert_false(dns_nametree_covered(bitstree, name, 2));
assert_false(dns_nametree_covered(bitstree, name, 3));
assert_false(dns_nametree_covered(bitstree, name, 9));
assert_false(dns_nametree_covered(bitstree, name, 53));
assert_false(dns_nametree_covered(bitstree, name, 288));
destroy_tables();
}
ISC_RUN_TEST_IMPL(delete) {
dns_fixedname_t fn;
dns_name_t *name = dns_fixedname_name(&fn);
@ -120,11 +251,11 @@ ISC_RUN_TEST_IMPL(delete) {
/* name doesn't match */
dns_test_namefromstring("example.org.", &fn);
assert_int_equal(dns_nametree_delete(nametree, name), ISC_R_NOTFOUND);
assert_int_equal(dns_nametree_delete(booltree, name), ISC_R_NOTFOUND);
/* subdomain match is the same as no match */
dns_test_namefromstring("sub.example.org.", &fn);
assert_int_equal(dns_nametree_delete(nametree, name), ISC_R_NOTFOUND);
assert_int_equal(dns_nametree_delete(booltree, name), ISC_R_NOTFOUND);
/*
* delete requires exact match: this should return SUCCESS on
@ -132,12 +263,12 @@ ISC_RUN_TEST_IMPL(delete) {
* ancestor does exist.
*/
dns_test_namefromstring("negative.example.com.", &fn);
assert_int_equal(dns_nametree_delete(nametree, name), ISC_R_SUCCESS);
assert_int_equal(dns_nametree_delete(nametree, name), ISC_R_NOTFOUND);
assert_int_equal(dns_nametree_delete(booltree, name), ISC_R_SUCCESS);
assert_int_equal(dns_nametree_delete(booltree, name), ISC_R_NOTFOUND);
dns_test_namefromstring("negative.example.org.", &fn);
assert_int_equal(dns_nametree_delete(nametree, name), ISC_R_SUCCESS);
assert_int_equal(dns_nametree_delete(nametree, name), ISC_R_NOTFOUND);
assert_int_equal(dns_nametree_delete(booltree, name), ISC_R_SUCCESS);
assert_int_equal(dns_nametree_delete(booltree, name), ISC_R_NOTFOUND);
destroy_tables();
}
@ -154,49 +285,26 @@ ISC_RUN_TEST_IMPL(find) {
* that has a null key, too.
*/
dns_test_namefromstring("example.org.", &fn);
assert_int_equal(dns_nametree_find(nametree, name, &node),
assert_int_equal(dns_nametree_find(booltree, name, &node),
ISC_R_NOTFOUND);
dns_test_namefromstring("sub.example.com.", &fn);
assert_int_equal(dns_nametree_find(nametree, name, &node),
assert_int_equal(dns_nametree_find(booltree, name, &node),
ISC_R_NOTFOUND);
dns_test_namefromstring("example.com.", &fn);
assert_int_equal(dns_nametree_find(nametree, name, &node),
assert_int_equal(dns_nametree_find(booltree, name, &node),
ISC_R_SUCCESS);
dns_ntnode_detach(&node);
destroy_tables();
}
ISC_RUN_TEST_IMPL(covered) {
dns_fixedname_t fn;
dns_name_t *name = dns_fixedname_name(&fn);
const char *yesnames[] = { "example.com.", "sub.example.com.", NULL };
const char *nonames[] = { "whatever.com.", "negative.example.com.",
"example.org.", "negative.example.org.",
NULL };
create_tables();
for (const char **n = yesnames; *n != NULL; n++) {
dns_test_namefromstring(*n, &fn);
assert_true(dns_nametree_covered(nametree, name));
}
for (const char **n = nonames; *n != NULL; n++) {
dns_test_namefromstring(*n, &fn);
assert_false(dns_nametree_covered(nametree, name));
}
/* If nametree is NULL, dns_nametree_covered() returns false. */
dns_test_namefromstring("anyname.example.", &fn);
assert_false(dns_nametree_covered(NULL, name));
destroy_tables();
}
ISC_TEST_LIST_START
ISC_TEST_ENTRY(add)
ISC_TEST_ENTRY(covered)
ISC_TEST_ENTRY(find)
ISC_TEST_ENTRY(add_bool)
ISC_TEST_ENTRY(add_bits)
ISC_TEST_ENTRY(covered_bool)
ISC_TEST_ENTRY(covered_bits)
ISC_TEST_ENTRY(delete)
ISC_TEST_ENTRY(find)
ISC_TEST_LIST_END
ISC_TEST_MAIN