From f449f278dd3c70e479a035f50a9bb817a9b433ba Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 17:24:08 +0200 Subject: Adding upstream version 3.2.6. Signed-off-by: Daniel Baumann --- src/knot/zone/contents.c | 609 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 609 insertions(+) create mode 100644 src/knot/zone/contents.c (limited to 'src/knot/zone/contents.c') diff --git a/src/knot/zone/contents.c b/src/knot/zone/contents.c new file mode 100644 index 0000000..cba13e8 --- /dev/null +++ b/src/knot/zone/contents.c @@ -0,0 +1,609 @@ +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include + +#include "libdnssec/error.h" +#include "knot/zone/adds_tree.h" +#include "knot/zone/adjust.h" +#include "knot/zone/contents.h" +#include "knot/common/log.h" +#include "knot/dnssec/zone-nsec.h" +#include "libknot/libknot.h" +#include "contrib/qp-trie/trie.h" + +/*! + * \brief Destroys all RRSets in a node. + * + * \param node Node to destroy RRSets from. + * \param data Unused parameter. + */ +static int destroy_node_rrsets_from_tree(zone_node_t *node, _unused_ void *data) +{ + if (node != NULL) { + binode_unify(node, false, NULL); + node_free_rrsets(node, NULL); + node_free(node, NULL); + } + + return KNOT_EOK; +} + +/*! + * \brief Tries to find the given domain name in the zone tree. + * + * \param zone Zone to search in. + * \param name Domain name to find. + * \param node Found node. + * \param previous Previous node in canonical order (i.e. the one directly + * preceding \a name in canonical order, regardless if the name + * is in the zone or not). + * + * \retval true if the domain name was found. In such case \a node holds the + * zone node with \a name as its owner. \a previous is set + * properly. + * \retval false if the domain name was not found. \a node may hold any (or none) + * node. \a previous is set properly. + */ +static bool find_in_tree(zone_tree_t *tree, const knot_dname_t *name, + zone_node_t **node, zone_node_t **previous) +{ + assert(tree != NULL); + assert(name != NULL); + assert(node != NULL); + assert(previous != NULL); + + zone_node_t *found = NULL, *prev = NULL; + + int match = zone_tree_get_less_or_equal(tree, name, &found, &prev); + if (match < 0) { + assert(0); + return false; + } + + *node = found; + *previous = prev; + + return match > 0; +} + +/*! + * \brief Create a node suitable for inserting into this contents. + */ +static zone_node_t *node_new_for_contents(const knot_dname_t *owner, const zone_contents_t *contents) +{ + assert(contents->nsec3_nodes == NULL || contents->nsec3_nodes->flags == contents->nodes->flags); + return node_new_for_tree(owner, contents->nodes, NULL); +} + +static zone_node_t *get_node(const zone_contents_t *zone, const knot_dname_t *name) +{ + assert(zone); + assert(name); + + return zone_tree_get(zone->nodes, name); +} + +static zone_node_t *get_nsec3_node(const zone_contents_t *zone, + const knot_dname_t *name) +{ + assert(zone); + assert(name); + + return zone_tree_get(zone->nsec3_nodes, name); +} + +static int insert_rr(zone_contents_t *z, const knot_rrset_t *rr, zone_node_t **n) +{ + if (knot_rrset_empty(rr)) { + return KNOT_EINVAL; + } + + if (*n == NULL) { + int ret = zone_tree_add_node(zone_contents_tree_for_rr(z, rr), z->apex, rr->owner, + (zone_tree_new_node_cb_t)node_new_for_contents, z, n); + if (ret != KNOT_EOK) { + return ret; + } + } + + return node_add_rrset(*n, rr, NULL); +} + +static int remove_rr(zone_contents_t *z, const knot_rrset_t *rr, + zone_node_t **n, bool nsec3) +{ + if (knot_rrset_empty(rr)) { + return KNOT_EINVAL; + } + + // check if the RRSet belongs to the zone + if (knot_dname_in_bailiwick(rr->owner, z->apex->owner) < 0) { + return KNOT_EOUTOFZONE; + } + + zone_node_t *node; + if (*n == NULL) { + node = nsec3 ? get_nsec3_node(z, rr->owner) : get_node(z, rr->owner); + if (node == NULL) { + return KNOT_ENONODE; + } + } else { + node = *n; + } + + int ret = node_remove_rrset(node, rr, NULL); + if (ret != KNOT_EOK) { + return ret; + } + + if (node->rrset_count == 0 && node->children == 0 && node != z->apex) { + zone_tree_del_node(nsec3 ? z->nsec3_nodes : z->nodes, node, true); + } + + *n = node; + return KNOT_EOK; +} + +// Public API + +zone_contents_t *zone_contents_new(const knot_dname_t *apex_name, bool use_binodes) +{ + if (apex_name == NULL) { + return NULL; + } + + zone_contents_t *contents = calloc(1, sizeof(*contents)); + if (contents == NULL) { + return NULL; + } + + contents->nodes = zone_tree_create(use_binodes); + if (contents->nodes == NULL) { + goto cleanup; + } + + contents->apex = node_new_for_contents(apex_name, contents); + if (contents->apex == NULL) { + goto cleanup; + } + + if (zone_tree_insert(contents->nodes, &contents->apex) != KNOT_EOK) { + goto cleanup; + } + contents->apex->flags |= NODE_FLAGS_APEX; + contents->max_ttl = UINT32_MAX; + + return contents; + +cleanup: + node_free(contents->apex, NULL); + free(contents->nodes); + free(contents); + return NULL; +} + +zone_tree_t *zone_contents_tree_for_rr(zone_contents_t *contents, const knot_rrset_t *rr) +{ + bool nsec3rel = knot_rrset_is_nsec3rel(rr); + + if (nsec3rel && contents->nsec3_nodes == NULL) { + contents->nsec3_nodes = zone_tree_create((contents->nodes->flags & ZONE_TREE_USE_BINODES)); + if (contents->nsec3_nodes == NULL) { + return NULL; + } + contents->nsec3_nodes->flags = contents->nodes->flags; + } + + return nsec3rel ? contents->nsec3_nodes : contents->nodes; +} + +int zone_contents_add_rr(zone_contents_t *z, const knot_rrset_t *rr, zone_node_t **n) +{ + if (rr == NULL || n == NULL) { + return KNOT_EINVAL; + } + + if (z == NULL) { + return KNOT_EEMPTYZONE; + } + + return insert_rr(z, rr, n); +} + +int zone_contents_remove_rr(zone_contents_t *z, const knot_rrset_t *rr, zone_node_t **n) +{ + if (rr == NULL || n == NULL) { + return KNOT_EINVAL; + } + + if (z == NULL) { + return KNOT_EEMPTYZONE; + } + + return remove_rr(z, rr, n, knot_rrset_is_nsec3rel(rr)); +} + +const zone_node_t *zone_contents_find_node(const zone_contents_t *zone, const knot_dname_t *name) +{ + if (zone == NULL || name == NULL) { + return NULL; + } + + return get_node(zone, name); +} + +const zone_node_t *zone_contents_node_or_nsec3(const zone_contents_t *zone, const knot_dname_t *name) +{ + if (zone == NULL || name == NULL) { + return NULL; + } + + const zone_node_t *node = get_node(zone, name); + if (node == NULL) { + node = get_nsec3_node(zone, name); + } + return node; +} + +zone_node_t *zone_contents_find_node_for_rr(zone_contents_t *contents, const knot_rrset_t *rrset) +{ + if (contents == NULL || rrset == NULL) { + return NULL; + } + + const bool nsec3 = knot_rrset_is_nsec3rel(rrset); + return nsec3 ? get_nsec3_node(contents, rrset->owner) : + get_node(contents, rrset->owner); +} + +int zone_contents_find_dname(const zone_contents_t *zone, + const knot_dname_t *name, + const zone_node_t **match, + const zone_node_t **closest, + const zone_node_t **previous) +{ + if (name == NULL || match == NULL || closest == NULL) { + return KNOT_EINVAL; + } + + if (zone == NULL) { + return KNOT_EEMPTYZONE; + } + + if (knot_dname_in_bailiwick(name, zone->apex->owner) < 0) { + return KNOT_EOUTOFZONE; + } + + zone_node_t *node = NULL; + zone_node_t *prev = NULL; + + int found = zone_tree_get_less_or_equal(zone->nodes, name, &node, &prev); + if (found < 0) { + // error + return found; + } else if (found == 1 && previous != NULL) { + // exact match + + assert(node && prev); + + *match = node; + *closest = node; + *previous = prev; + + return ZONE_NAME_FOUND; + } else if (found == 1 && previous == NULL) { + // exact match, zone not adjusted yet + + assert(node); + *match = node; + *closest = node; + + return ZONE_NAME_FOUND; + } else { + // closest match + + assert(!node && prev); + + node = prev; + size_t matched_labels = knot_dname_matched_labels(node->owner, name); + while (matched_labels < knot_dname_labels(node->owner, NULL)) { + node = node_parent(node); + assert(node); + } + + *match = NULL; + *closest = node; + if (previous != NULL) { + *previous = prev; + } + + return ZONE_NAME_NOT_FOUND; + } +} + +const zone_node_t *zone_contents_find_nsec3_node(const zone_contents_t *zone, + const knot_dname_t *name) +{ + if (zone == NULL || name == NULL) { + return NULL; + } + + return get_nsec3_node(zone, name); +} + +int zone_contents_find_nsec3_for_name(const zone_contents_t *zone, + const knot_dname_t *name, + const zone_node_t **nsec3_node, + const zone_node_t **nsec3_previous) +{ + if (name == NULL || nsec3_node == NULL || nsec3_previous == NULL) { + return KNOT_EINVAL; + } + + if (zone == NULL) { + return KNOT_EEMPTYZONE; + } + + // check if the NSEC3 tree is not empty + if (zone_tree_is_empty(zone->nsec3_nodes)) { + return KNOT_ENSEC3CHAIN; + } + if (!knot_is_nsec3_enabled(zone)) { + return KNOT_ENSEC3PAR; + } + + knot_dname_storage_t nsec3_name; + int ret = knot_create_nsec3_owner(nsec3_name, sizeof(nsec3_name), + name, zone->apex->owner, &zone->nsec3_params); + if (ret != KNOT_EOK) { + return ret; + } + + return zone_contents_find_nsec3(zone, nsec3_name, nsec3_node, nsec3_previous); +} + +int zone_contents_find_nsec3(const zone_contents_t *zone, + const knot_dname_t *nsec3_name, + const zone_node_t **nsec3_node, + const zone_node_t **nsec3_previous) +{ + zone_node_t *found = NULL, *prev = NULL; + bool match = find_in_tree(zone->nsec3_nodes, nsec3_name, &found, &prev); + + *nsec3_node = found; + + if (prev == NULL) { + // either the returned node is the root of the tree, or it is + // the leftmost node in the tree; in both cases node was found + // set the previous node of the found node + assert(match); + assert(*nsec3_node != NULL); + *nsec3_previous = node_prev(*nsec3_node); + assert(*nsec3_previous != NULL); + } else { + *nsec3_previous = prev; + } + + // The previous may be from wrong NSEC3 chain. Search for previous from the right chain. + const zone_node_t *original_prev = *nsec3_previous; + while (!((*nsec3_previous)->flags & NODE_FLAGS_IN_NSEC3_CHAIN)) { + *nsec3_previous = node_prev(*nsec3_previous); + if (*nsec3_previous == original_prev || *nsec3_previous == NULL) { + // cycle + *nsec3_previous = NULL; + break; + } + } + + return (match ? ZONE_NAME_FOUND : ZONE_NAME_NOT_FOUND); +} + +const zone_node_t *zone_contents_find_wildcard_child(const zone_contents_t *contents, + const zone_node_t *parent) +{ + if (contents == NULL || parent == NULL || parent->owner == NULL) { + return NULL; + } + + knot_dname_storage_t wildcard = "\x01""*"; + knot_dname_to_wire(wildcard + 2, parent->owner, sizeof(wildcard) - 2); + + return zone_contents_find_node(contents, wildcard); +} + +bool zone_contents_find_node_or_wildcard(const zone_contents_t *contents, + const knot_dname_t *find, + const zone_node_t **found) +{ + const zone_node_t *encloser = NULL; + zone_contents_find_dname(contents, find, found, &encloser, NULL); + if (*found == NULL && encloser != NULL && (encloser->flags & NODE_FLAGS_WILDCARD_CHILD)) { + *found = zone_contents_find_wildcard_child(contents, encloser); + assert(*found != NULL); + } + return (*found != NULL); +} + +int zone_contents_apply(zone_contents_t *contents, + zone_tree_apply_cb_t function, void *data) +{ + if (contents == NULL) { + return KNOT_EEMPTYZONE; + } + return zone_tree_apply(contents->nodes, function, data); +} + +int zone_contents_nsec3_apply(zone_contents_t *contents, + zone_tree_apply_cb_t function, void *data) +{ + if (contents == NULL) { + return KNOT_EEMPTYZONE; + } + return zone_tree_apply(contents->nsec3_nodes, function, data); +} + +int zone_contents_cow(zone_contents_t *from, zone_contents_t **to) +{ + if (to == NULL) { + return KNOT_EINVAL; + } + + if (from == NULL) { + return KNOT_EEMPTYZONE; + } + + /* Copy to same destination as source. */ + if (from == *to) { + return KNOT_EINVAL; + } + + zone_contents_t *contents = calloc(1, sizeof(zone_contents_t)); + if (contents == NULL) { + return KNOT_ENOMEM; + } + + contents->nodes = zone_tree_cow(from->nodes); + if (contents->nodes == NULL) { + free(contents); + return KNOT_ENOMEM; + } + contents->apex = zone_tree_fix_get(from->apex, contents->nodes); + + if (from->nsec3_nodes) { + contents->nsec3_nodes = zone_tree_cow(from->nsec3_nodes); + if (contents->nsec3_nodes == NULL) { + trie_cow_rollback(contents->nodes->cow, NULL, NULL); + free(contents->nodes); + free(contents); + return KNOT_ENOMEM; + } + } + contents->adds_tree = from->adds_tree; + from->adds_tree = NULL; + contents->size = from->size; + contents->max_ttl = from->max_ttl; + + *to = contents; + return KNOT_EOK; +} + +void zone_contents_free(zone_contents_t *contents) +{ + if (contents == NULL) { + return; + } + + // free the zone tree, but only the structure + zone_tree_free(&contents->nodes); + zone_tree_free(&contents->nsec3_nodes); + + dnssec_nsec3_params_free(&contents->nsec3_params); + additionals_tree_free(contents->adds_tree); + + free(contents); +} + +void zone_contents_deep_free(zone_contents_t *contents) +{ + if (contents == NULL) { + return; + } + + if (contents != NULL) { + // Delete NSEC3 tree. + (void)zone_tree_apply(contents->nsec3_nodes, + destroy_node_rrsets_from_tree, NULL); + + // Delete the normal tree. + (void)zone_tree_apply(contents->nodes, + destroy_node_rrsets_from_tree, NULL); + } + + zone_contents_free(contents); +} + +uint32_t zone_contents_serial(const zone_contents_t *zone) +{ + if (zone == NULL) { + return 0; + } + + const knot_rdataset_t *soa = node_rdataset(zone->apex, KNOT_RRTYPE_SOA); + if (soa == NULL) { + return 0; + } + + return knot_soa_serial(soa->rdata); +} + +void zone_contents_set_soa_serial(zone_contents_t *zone, uint32_t new_serial) +{ + knot_rdataset_t *soa; + if (zone != NULL && (soa = node_rdataset(zone->apex, KNOT_RRTYPE_SOA)) != NULL) { + knot_soa_serial_set(soa->rdata, new_serial); + } +} + +int zone_contents_load_nsec3param(zone_contents_t *contents) +{ + if (contents == NULL) { + return KNOT_EEMPTYZONE; + } + + if (contents->apex == NULL) { + return KNOT_EINVAL; + } + + const knot_rdataset_t *rrs = NULL; + rrs = node_rdataset(contents->apex, KNOT_RRTYPE_NSEC3PARAM); + if (rrs == NULL) { + dnssec_nsec3_params_free(&contents->nsec3_params); + return KNOT_EOK; + } + + if (rrs->count != 1) { + return KNOT_EINVAL; + } + + dnssec_binary_t rdata = { + .size = rrs->rdata->len, + .data = rrs->rdata->data, + }; + + dnssec_nsec3_params_t new_params = { 0 }; + int r = dnssec_nsec3_params_from_rdata(&new_params, &rdata); + if (r != DNSSEC_EOK) { + return KNOT_EMALF; + } + + dnssec_nsec3_params_free(&contents->nsec3_params); + contents->nsec3_params = new_params; + return KNOT_EOK; +} + +bool zone_contents_is_empty(const zone_contents_t *zone) +{ + if (zone == NULL) { + return true; + } + + bool apex_empty = (zone->apex == NULL || zone->apex->rrset_count == 0); + bool no_non_apex = (zone_tree_count(zone->nodes) <= (zone->apex != NULL ? 1 : 0)); + bool no_nsec3 = zone_tree_is_empty(zone->nsec3_nodes); + + return (apex_empty && no_non_apex && no_nsec3); +} -- cgit v1.2.3