From: Evan Hunt Date: Wed, 16 Aug 2023 19:08:53 +0000 (-0700) Subject: add dns_nametree structure for policy match lookups X-Git-Tag: v9.19.17~16^2~9 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=56114aaa0dba9e64f9fe6998e9c5298be18476e6;p=thirdparty%2Fbind9.git add dns_nametree structure for policy match lookups this is a QP trie of boolean values to indicate whether a name is included in or excluded from some policy. this can be used for synth-from-dnssec, deny-answer-aliases, etc. --- diff --git a/lib/dns/Makefile.am b/lib/dns/Makefile.am index 865a29f06a6..b6814bd7ed9 100644 --- a/lib/dns/Makefile.am +++ b/lib/dns/Makefile.am @@ -91,6 +91,7 @@ libdns_la_HEADERS = \ include/dns/masterdump.h \ include/dns/message.h \ include/dns/name.h \ + include/dns/nametree.h \ include/dns/ncache.h \ include/dns/nsec.h \ include/dns/nsec3.h \ @@ -196,6 +197,7 @@ libdns_la_SOURCES = \ masterdump.c \ message.c \ name.c \ + nametree.c \ ncache.c \ nsec.c \ nsec3.c \ diff --git a/lib/dns/include/dns/nametree.h b/lib/dns/include/dns/nametree.h new file mode 100644 index 00000000000..be549dab016 --- /dev/null +++ b/lib/dns/include/dns/nametree.h @@ -0,0 +1,154 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file + * \brief + * A nametree module is a tree of DNS names containing boolean values + * or bitfields, allowing a quick lookup to see whether a name is included + * in or excluded from some policy. + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include + +/* Define to 1 for detailed reference tracing */ +#undef DNS_NAMETREE_TRACE + +ISC_LANG_BEGINDECLS + +void +dns_nametree_create(isc_mem_t *mctx, 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. + * + * Requires: + * + *\li 'mctx' is a valid memory context. + *\li ntp != NULL && *ntp == NULL + */ + +isc_result_t +dns_nametree_add(dns_nametree_t *nametree, const dns_name_t *name, bool value); +/*%< + * Add a node to 'nametree'. + * + * 'value' is a single boolean value, true or false. If the name already + * exists within the tree, then return ISC_R_EXISTS. + * + * Requires: + * + *\li 'nametree' points to a valid nametree. + * + * Returns: + * + *\li ISC_R_SUCCESS + *\li ISC_R_EXISTS + * + *\li Any other result indicates failure. + */ + +isc_result_t +dns_nametree_delete(dns_nametree_t *nametree, const dns_name_t *name); +/*%< + * Delete 'name' from 'nametree'. + * + * Requires: + * + *\li 'nametree' points to a valid nametree. + *\li 'name' is not NULL + * + * Returns: + * + *\li ISC_R_SUCCESS + * + *\li Any other result indicates failure. + */ + +isc_result_t +dns_nametree_find(dns_nametree_t *nametree, const dns_name_t *name, + dns_ntnode_t **ntp); +/*%< + * Retrieve the node that exactly matches 'name' from 'nametree'. + * + * Requires: + * + *\li 'nametree' is a valid nametree. + * + *\li 'name' is a valid name. + * + *\li ntp != NULL && *ntp == NULL + * + * Returns: + * + *\li ISC_R_SUCCESS + *\li ISC_R_NOTFOUND + * + *\li Any other result indicates an error. + */ + +bool +dns_nametree_covered(dns_nametree_t *nametree, const dns_name_t *name); +/*%< + * Indicates whether a 'name' 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. + * + * Requires: + * + *\li 'nametree' is a valid nametree, or is NULL. + */ + +#if DNS_NAMETREE_TRACE +#define dns_nametree_ref(ptr) \ + dns_nametree__ref(ptr, __func__, __FILE__, __LINE__) +#define dns_nametree_unref(ptr) \ + dns_nametree__unref(ptr, __func__, __FILE__, __LINE__) +#define dns_nametree_attach(ptr, ptrp) \ + dns_nametree__attach(ptr, ptrp, __func__, __FILE__, __LINE__) +#define dns_nametree_detach(ptrp) \ + dns_nametree__detach(ptrp, __func__, __FILE__, __LINE__) +#define dns_ntnode_ref(ptr) dns_ntnode__ref(ptr, __func__, __FILE__, __LINE__) +#define dns_ntnode_unref(ptr) \ + dns_ntnode__unref(ptr, __func__, __FILE__, __LINE__) +#define dns_ntnode_attach(ptr, ptrp) \ + dns_ntnode__attach(ptr, ptrp, __func__, __FILE__, __LINE__) +#define dns_ntnode_detach(ptrp) \ + dns_ntnode__detach(ptrp, __func__, __FILE__, __LINE__) +ISC_REFCOUNT_TRACE_DECL(dns_nametree); +ISC_REFCOUNT_TRACE_DECL(dns_ntnode); +#else +ISC_REFCOUNT_DECL(dns_nametree); +ISC_REFCOUNT_DECL(dns_ntnode); +#endif +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/types.h b/lib/dns/include/dns/types.h index 70e0b022747..f3fd6f630e3 100644 --- a/lib/dns/include/dns/types.h +++ b/lib/dns/include/dns/types.h @@ -116,8 +116,10 @@ typedef struct dns_message dns_message_t; typedef uint16_t dns_messageid_t; typedef isc_region_t dns_label_t; typedef struct dns_name dns_name_t; +typedef struct dns_nametree dns_nametree_t; typedef ISC_LIST(dns_name_t) dns_namelist_t; typedef struct dns_ntatable dns_ntatable_t; +typedef struct dns_ntnode dns_ntnode_t; typedef uint16_t dns_opcode_t; typedef struct dns_order dns_order_t; typedef struct dns_peer dns_peer_t; diff --git a/lib/dns/nametree.c b/lib/dns/nametree.c new file mode 100644 index 00000000000..464690d6c8a --- /dev/null +++ b/lib/dns/nametree.c @@ -0,0 +1,252 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#define NAMETREE_MAGIC ISC_MAGIC('N', 'T', 'r', 'e') +#define VALID_NAMETREE(kt) ISC_MAGIC_VALID(kt, NAMETREE_MAGIC) + +struct dns_nametree { + unsigned int magic; + isc_mem_t *mctx; + isc_refcount_t references; + dns_qpmulti_t *table; + char name[64]; +}; + +struct dns_ntnode { + isc_mem_t *mctx; + isc_refcount_t references; + dns_fixedname_t fn; + dns_name_t *name; + union { + bool value; + unsigned char *bits; + }; +}; + +/* QP trie methods */ +static void +qp_attach(void *uctx, void *pval, uint32_t ival); +static void +qp_detach(void *uctx, void *pval, uint32_t ival); +static size_t +qp_makekey(dns_qpkey_t key, void *uctx, void *pval, uint32_t ival); +static void +qp_triename(void *uctx, char *buf, size_t size); + +static dns_qpmethods_t qpmethods = { + qp_attach, + qp_detach, + qp_makekey, + qp_triename, +}; + +static void +destroy_ntnode(dns_ntnode_t *node) { + isc_refcount_destroy(&node->references); + isc_mem_putanddetach(&node->mctx, node, sizeof(dns_ntnode_t)); +} + +#if DNS_NAMETREE_TRACE +ISC_REFCOUNT_TRACE_IMPL(dns_ntnode, destroy_ntnode); +#else +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_t *nametree = NULL; + + REQUIRE(ntp != NULL && *ntp == NULL); + + nametree = isc_mem_get(mctx, sizeof(*nametree)); + *nametree = (dns_nametree_t){ + .magic = NAMETREE_MAGIC, + }; + isc_mem_attach(mctx, &nametree->mctx); + isc_refcount_init(&nametree->references, 1); + + if (name != NULL) { + strlcpy(nametree->name, name, sizeof(nametree->name)); + } + + dns_qpmulti_create(mctx, &qpmethods, nametree, &nametree->table); + *ntp = nametree; +} + +static void +destroy_nametree(dns_nametree_t *nametree) { + nametree->magic = 0; + + dns_qpmulti_destroy(&nametree->table); + isc_refcount_destroy(&nametree->references); + + isc_mem_putanddetach(&nametree->mctx, nametree, sizeof(*nametree)); +} + +#if DNS_NAMETREE_TRACE +ISC_REFCOUNT_TRACE_IMPL(dns_nametree, destroy_nametree); +#else +ISC_REFCOUNT_IMPL(dns_nametree, destroy_nametree); +#endif + +static dns_ntnode_t * +newnode(isc_mem_t *mctx, const dns_name_t *name) { + dns_ntnode_t *node = isc_mem_get(mctx, sizeof(*node)); + *node = (dns_ntnode_t){ 0 }; + isc_mem_attach(mctx, &node->mctx); + isc_refcount_init(&node->references, 1); + + node->name = dns_fixedname_initname(&node->fn); + dns_name_copy(name, node->name); + + return (node); +} + +isc_result_t +dns_nametree_add(dns_nametree_t *nametree, const dns_name_t *name, bool value) { + isc_result_t result; + dns_qp_t *qp = 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); + + /* + * We detach the node here, so any dns_qp_deletename() will + * destroy the node directly. + */ + dns_ntnode_detach(&node); + } + + dns_qp_compact(qp, DNS_QPGC_MAYBE); + dns_qpmulti_commit(nametree->table, &qp); + return (result); +} + +isc_result_t +dns_nametree_delete(dns_nametree_t *nametree, const dns_name_t *name) { + isc_result_t result; + dns_qp_t *qp = NULL; + void *pval = NULL; + + REQUIRE(VALID_NAMETREE(nametree)); + REQUIRE(name != NULL); + + dns_qpmulti_write(nametree->table, &qp); + result = dns_qp_deletename(qp, name, &pval, NULL); + if (result == ISC_R_SUCCESS) { + dns_ntnode_t *n = pval; + dns_ntnode_detach(&n); + } + dns_qp_compact(qp, DNS_QPGC_MAYBE); + dns_qpmulti_commit(nametree->table, &qp); + + return (result); +} + +isc_result_t +dns_nametree_find(dns_nametree_t *nametree, const dns_name_t *name, + dns_ntnode_t **ntnodep) { + isc_result_t result; + dns_qpread_t qpr; + void *pval = NULL; + + REQUIRE(VALID_NAMETREE(nametree)); + REQUIRE(name != NULL); + REQUIRE(ntnodep != NULL && *ntnodep == NULL); + + dns_qpmulti_query(nametree->table, &qpr); + result = dns_qp_getname(&qpr, name, &pval, NULL); + if (result == ISC_R_SUCCESS) { + dns_ntnode_t *knode = pval; + dns_ntnode_attach(knode, ntnodep); + } + dns_qpread_destroy(nametree->table, &qpr); + + return (result); +} + +bool +dns_nametree_covered(dns_nametree_t *nametree, const dns_name_t *name) { + isc_result_t result; + dns_qpread_t qpr; + dns_ntnode_t *ntnode = NULL; + void *pval = NULL; + bool value = false; + + REQUIRE(nametree == NULL || VALID_NAMETREE(nametree)); + + if (nametree == NULL) { + return (false); + } + + dns_qpmulti_query(nametree->table, &qpr); + result = dns_qp_findname_ancestor(&qpr, name, 0, &pval, NULL); + if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + ntnode = pval; + value = ntnode->value; + } + + dns_qpread_destroy(nametree->table, &qpr); + return (value); +} + +static void +qp_attach(void *uctx ISC_ATTR_UNUSED, void *pval, + uint32_t ival ISC_ATTR_UNUSED) { + dns_ntnode_t *ntnode = pval; + dns_ntnode_ref(ntnode); +} + +static void +qp_detach(void *uctx ISC_ATTR_UNUSED, void *pval, + uint32_t ival ISC_ATTR_UNUSED) { + dns_ntnode_t *ntnode = pval; + dns_ntnode_detach(&ntnode); +} + +static size_t +qp_makekey(dns_qpkey_t key, void *uctx ISC_ATTR_UNUSED, void *pval, + uint32_t ival ISC_ATTR_UNUSED) { + dns_ntnode_t *ntnode = pval; + return (dns_qpkey_fromname(key, ntnode->name)); +} + +static void +qp_triename(void *uctx, char *buf, size_t size) { + dns_nametree_t *nametree = uctx; + snprintf(buf, size, "%s nametree", nametree->name); +} diff --git a/tests/dns/Makefile.am b/tests/dns/Makefile.am index 921678ca908..005a97860bf 100644 --- a/tests/dns/Makefile.am +++ b/tests/dns/Makefile.am @@ -29,6 +29,7 @@ check_PROGRAMS = \ dst_test \ keytable_test \ name_test \ + nametree_test \ nsec3_test \ nsec3param_test \ private_test \ diff --git a/tests/dns/nametree_test.c b/tests/dns/nametree_test.c new file mode 100644 index 00000000000..1557d2184f4 --- /dev/null +++ b/tests/dns/nametree_test.c @@ -0,0 +1,202 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +dns_nametree_t *nametree = NULL; + +/* + * Test utilities. In general, these assume input parameters are valid + * (checking with assert_int_equal, thus aborting if not) and unlikely run time + * errors (such as memory allocation failure) won't happen. This helps keep + * the test code concise. + */ + +/* Common setup: create a nametree 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); + + /* Add a positive node */ + dns_test_namefromstring("example.com.", &fn); + assert_int_equal(dns_nametree_add(nametree, name, true), ISC_R_SUCCESS); + + /* Add a negative node below it */ + dns_test_namefromstring("negative.example.com.", &fn); + assert_int_equal(dns_nametree_add(nametree, 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); +} + +static void +destroy_tables(void) { + if (nametree != NULL) { + dns_nametree_detach(&nametree); + } + rcu_barrier(); +} + +ISC_RUN_TEST_IMPL(add) { + 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(nametree, 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), + 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(nametree, name, true), ISC_R_SUCCESS); + assert_int_equal(dns_nametree_find(nametree, name, &node), + ISC_R_SUCCESS); + dns_ntnode_detach(&node); + + destroy_tables(); +} + +ISC_RUN_TEST_IMPL(delete) { + dns_fixedname_t fn; + dns_name_t *name = dns_fixedname_name(&fn); + + create_tables(); + + /* name doesn't match */ + dns_test_namefromstring("example.org.", &fn); + assert_int_equal(dns_nametree_delete(nametree, 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); + + /* + * delete requires exact match: this should return SUCCESS on + * the first try, then NOTFOUND on the second even though an + * 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); + + 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); + + destroy_tables(); +} + +ISC_RUN_TEST_IMPL(find) { + dns_ntnode_t *node = NULL; + dns_fixedname_t fn; + dns_name_t *name = dns_fixedname_name(&fn); + + create_tables(); + + /* + * dns_nametree_find() requires exact name match. It matches node + * that has a null key, too. + */ + dns_test_namefromstring("example.org.", &fn); + assert_int_equal(dns_nametree_find(nametree, name, &node), + ISC_R_NOTFOUND); + dns_test_namefromstring("sub.example.com.", &fn); + assert_int_equal(dns_nametree_find(nametree, name, &node), + ISC_R_NOTFOUND); + dns_test_namefromstring("example.com.", &fn); + assert_int_equal(dns_nametree_find(nametree, 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(delete) +ISC_TEST_LIST_END + +ISC_TEST_MAIN