]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
add dns_nametree structure for policy match lookups
authorEvan Hunt <each@isc.org>
Wed, 16 Aug 2023 19:08:53 +0000 (12:08 -0700)
committerEvan Hunt <each@isc.org>
Fri, 1 Sep 2023 17:46:48 +0000 (10:46 -0700)
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.

lib/dns/Makefile.am
lib/dns/include/dns/nametree.h [new file with mode: 0644]
lib/dns/include/dns/types.h
lib/dns/nametree.c [new file with mode: 0644]
tests/dns/Makefile.am
tests/dns/nametree_test.c [new file with mode: 0644]

index 865a29f06a64f094b6babe6bf13a3dededd8a90d..b6814bd7ed9a10da801e94fc0c2420ed668f3630 100644 (file)
@@ -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 (file)
index 0000000..be549da
--- /dev/null
@@ -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 <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/refcount.h>
+#include <isc/rwlock.h>
+#include <isc/stdtime.h>
+
+#include <dns/rdatastruct.h>
+#include <dns/types.h>
+
+#include <dst/dst.h>
+
+/* 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
index 70e0b022747fe46e4a7c62d0d21e9730679f5f39..f3fd6f630e3cb50ebd83b17a1f087b957ac45249 100644 (file)
@@ -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 (file)
index 0000000..464690d
--- /dev/null
@@ -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 <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/refcount.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/fixedname.h>
+#include <dns/nametree.h>
+#include <dns/qp.h>
+
+#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);
+}
index 921678ca908424bcb7c853c3f16ceb65c3e74ed7..005a97860bfba70267ffcbe223b3e05bacefe8d9 100644 (file)
@@ -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 (file)
index 0000000..1557d21
--- /dev/null
@@ -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 <inttypes.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/base64.h>
+#include <isc/buffer.h>
+#include <isc/md.h>
+#include <isc/util.h>
+
+#include <dns/fixedname.h>
+#include <dns/name.h>
+#include <dns/nametree.h>
+
+#include <dst/dst.h>
+
+#include <tests/dns.h>
+
+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