diff options
author | Ondřej Surý <ondrej@sury.org> | 2011-11-02 22:44:12 +0100 |
---|---|---|
committer | Ondřej Surý <ondrej@sury.org> | 2011-11-02 22:44:12 +0100 |
commit | c8d5977bb546dae9ed59d81556639c49badd8121 (patch) | |
tree | 4c86750db26c1c3502b60f2cd78ca9611cfa01d6 /src/libknot | |
download | knot-c8d5977bb546dae9ed59d81556639c49badd8121.tar.gz |
Imported Upstream version 0.8.0~pre1upstream/0.8.0_pre1
Diffstat (limited to 'src/libknot')
60 files changed, 31701 insertions, 0 deletions
diff --git a/src/libknot/common.h b/src/libknot/common.h new file mode 100644 index 0000000..9b2d8ae --- /dev/null +++ b/src/libknot/common.h @@ -0,0 +1,105 @@ +/*! + * \file common.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Common macros, includes and utilities. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#ifdef HAVE_LIBLDNS +#define TEST_WITH_LDNS +#endif + +#ifndef _KNOT_COMMON_H_ +#define _KNOT_COMMON_H_ + +#define KNOT_NAME "lib" PACKAGE_NAME // Project name +#define KNOT_VER PACKAGE_VERSION // 0xMMIIRR (MAJOR,MINOR,REVISION) + +#ifndef UINT_DEFINED +typedef unsigned int uint; /*!< \brief Unsigned. */ +#define UINT_DEFINED +#endif + +/*! \brief If defined, zone structures will use hash table for lookup. */ +#define USE_HASH_TABLE + +/*! \brief Eliminate compiler warning with unused parameters. */ +#define UNUSED(param) (void)(param) + +/*! \brief Type-safe minimum macro. */ +#define MIN(a, b) \ + ({ typeof (a) _a = (a); typeof (b) _b = (b); _a < _b ? _a : _b; }) + +/*! \brief Type-safe maximum macro. */ +#define MAX(a, b) \ + ({ typeof (a) _a = (a); typeof (b) _b = (b); _a > _b ? _a : _b; }) + +/* Optimisation macros. */ +#ifndef likely +/*! \brief Optimize for x to be true value. */ +#define likely(x) __builtin_expect((x),1) +#endif +#ifndef unlikely +/*! \brief Optimize for x to be false value. */ +#define unlikely(x) __builtin_expect((x),0) +#endif + +/* Optimisation macros. */ +#ifndef likely +/*! \brief Optimize for x to be true value. */ +#define likely(x) __builtin_expect((x),1) +#endif +#ifndef unlikely +/*! \brief Optimize for x to be false value. */ +#define unlikely(x) __builtin_expect((x),0) +#endif + +/*! \todo Refactor theese. We should have an allocator function handling this.*/ +#ifndef ERR_ALLOC_FAILED +#define ERR_ALLOC_FAILED fprintf(stderr, "Allocation failed at %s:%d (%s ver.%s)\n", \ + __FILE__, __LINE__, KNOT_NAME, KNOT_VER) +#endif + +#ifndef CHECK_ALLOC_LOG +#define CHECK_ALLOC_LOG(var, ret) \ + do { \ + if ((var) == NULL) { \ + ERR_ALLOC_FAILED; \ + return (ret); \ + } \ + } while (0) +#endif + +#ifndef CHECK_ALLOC +#define CHECK_ALLOC(var, ret) \ + do { \ + if ((var) == NULL) { \ + return (ret); \ + } \ + } while (0) +#endif + +#endif /* _KNOT_COMMON_H_ */ + +/*! @} */ diff --git a/src/libknot/consts.h b/src/libknot/consts.h new file mode 100644 index 0000000..4249763 --- /dev/null +++ b/src/libknot/consts.h @@ -0,0 +1,108 @@ +/*! + * \file consts.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Contains some DNS-related constants. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_CONSTS_H_ +#define _KNOT_CONSTS_H_ + +#include <stdint.h> +#include "util/descriptor.h" + +/* + * OPCODEs + */ +typedef enum knot_opcode { + KNOT_OPCODE_QUERY = 0, /* a standard query (QUERY) */ + KNOT_OPCODE_IQUERY = 1, /* an inverse query (IQUERY) */ + KNOT_OPCODE_STATUS = 2, /* a server status request (STATUS) */ + KNOT_OPCODE_NOTIFY = 4, /* NOTIFY */ + KNOT_OPCODE_UPDATE = 5, /* Dynamic update */ + KNOT_OPCODE_OFFSET = 14 +} knot_opcode_t; + +/*! + * \brief Query types (internal use only). + * + * This type encompasses the different query types distinguished by both the + * OPCODE and the QTYPE. + */ +typedef enum knot_packet_type { + KNOT_QUERY_NORMAL, /*!< Normal query. */ + KNOT_QUERY_AXFR, /*!< Request for AXFR transfer. */ + KNOT_QUERY_IXFR, /*!< Request for IXFR transfer. */ + KNOT_QUERY_NOTIFY, /*!< NOTIFY query. */ + KNOT_QUERY_UPDATE, /*!< Dynamic update. */ + KNOT_RESPONSE_NORMAL, /*!< Normal response. */ + KNOT_RESPONSE_AXFR, /*!< AXFR transfer response. */ + KNOT_RESPONSE_IXFR, /*!< IXFR transfer response. */ + KNOT_RESPONSE_NOTIFY /*!< NOTIFY response. */ +} knot_packet_type_t; + +/* + * RCODEs + */ +typedef enum knot_rcode { + KNOT_RCODE_NOERROR = 0, /* No error condition */ + KNOT_RCODE_FORMERR = 1, /* Format error */ + KNOT_RCODE_SERVFAIL = 2, /* Server failure */ + KNOT_RCODE_NXDOMAIN = 3, /* Name Error */ + KNOT_RCODE_NOTIMPL = 4, /* Not implemented */ + KNOT_RCODE_REFUSED = 5, /* Refused */ + KNOT_RCODE_YXDOMAIN = 6, /* name should not exist */ + KNOT_RCODE_YXRRSET = 7, /* rrset should not exist */ + KNOT_RCODE_NXRRSET = 8, /* rrset does not exist */ + KNOT_RCODE_NOTAUTH = 9, /* server not authoritative */ + KNOT_RCODE_NOTZONE = 10, /* name not inside zone */ +} knot_rcode_t; + +typedef enum knot_tsig_rcode { + KNOT_TSIG_RCODE_BADSIG = 16, + KNOT_TSIG_RCODE_BADKEY = 17, + KNOT_TSIG_RCODE_BADTIME = 18 +} knot_tsig_rcode_t; + +/* + * CLASSes + */ +//typedef enum knot_class { +// KNOT_CLASS_IN = 1, /* Class IN */ +// KNOT_CLASS_CS = 2, /* Class CS */ +// KNOT_CLASS_CH = 3, /* Class CHAOS */ +// KNOT_CLASS_HS = 4, /* Class HS */ +// KNOT_CLASS_NONE = 254, /* Class NONE rfc2136 */ +// KNOT_CLASS_ANY = 255 /* Class ANY */ +//} knot_class_t; + +/* + * Other + */ +typedef enum knot_const { + KNOT_MAX_DNAME_LENGTH = 255, + KNOT_MAX_DNAME_LABELS = 127 // 1-char labels +} knot_const_t; + +#endif /* _KNOT_CONSTS_H_ */ + +/*! @} */ diff --git a/src/libknot/dname.c b/src/libknot/dname.c new file mode 100644 index 0000000..869b342 --- /dev/null +++ b/src/libknot/dname.c @@ -0,0 +1,1070 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdint.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <ctype.h> // tolower() + +#include "common.h" +#include "util/error.h" +#include "dname.h" +#include "consts.h" +#include "util/tolower.h" +#include "util/debug.h" +#include "util/utils.h" +#include "util/wire.h" + +/*! \todo dnames allocated from TLS cache will be discarded after thread + * termination. This shouldn't happpen. + */ +#if 0 +/* + * Memory cache. + */ +#include "common/slab/slab.h" +#include <stdio.h> +#include <pthread.h> + +/*! \brief TLS unique key for each thread cache. */ +static pthread_key_t dname_ckey; +static pthread_once_t dname_once = PTHREAD_ONCE_INIT; + +/*! \brief Destroy thread dname cache (automatically called). */ +static void knot_dname_cache_free(void *ptr) +{ + slab_cache_t* cache = (slab_cache_t*)ptr; + if (cache) { + slab_cache_destroy(cache); + free(cache); + } +} + +/*! \brief Cleanup for main() TLS. */ +static void knot_dname_cache_main_free() +{ + knot_dname_cache_free(pthread_getspecific(dname_ckey)); +} + +static void knot_dname_cache_init() +{ + (void) pthread_key_create(&dname_ckey, knot_dname_cache_free); + atexit(knot_dname_cache_main_free); // Main thread cleanup +} +#endif + +/*! + * \brief Allocate item from thread cache. + * \retval Allocated dname instance on success. + * \retval NULL on error. + */ +static knot_dname_t* knot_dname_alloc() +{ + return malloc(sizeof(knot_dname_t)); + + /*! \todo dnames allocated from TLS cache will be discarded after thread + * termination. This shouldn't happpen. + */ +#if 0 + /* Initialize dname cache TLS key. */ + (void)pthread_once(&dname_once, knot_dname_cache_init); + + /* Create cache if not exists. */ + slab_cache_t* cache = pthread_getspecific(dname_ckey); + if (unlikely(!cache)) { + cache = malloc(sizeof(slab_cache_t)); + if (!cache) { + return 0; + } + + /* Initialize cache. */ + slab_cache_init(cache, sizeof(knot_dname_t)); + (void)pthread_setspecific(dname_ckey, cache); + } + + return slab_cache_alloc(cache); +#endif +} + +/*----------------------------------------------------------------------------*/ +/* Non-API functions */ +/*----------------------------------------------------------------------------*/ + +static int knot_dname_set(knot_dname_t *dname, uint8_t *wire, + short wire_size, const uint8_t *labels, + short label_count) +{ + dname->name = wire; + dname->size = wire_size; + dname->label_count = label_count; + + assert(label_count >= 0); + + dname->labels = (uint8_t *)malloc(dname->label_count * sizeof(uint8_t)); + CHECK_ALLOC_LOG(dname->labels, -1); + memcpy(dname->labels, labels, dname->label_count); + + return 0; +} + +/*! + * \brief Converts domain name from string representation to wire format. + * + * This function also allocates the space for the wire format. + * + * \param name Domain name in string representation (presentation format). + * \param size Size of the given domain name in characters (not counting the + * terminating 0 character. + * \param dname Domain name where to store the wire format. + * + * \return Size of the wire format of the domain name in octets. If 0, no + * space has been allocated. + * + * \todo handle \X and \DDD (RFC 1035 5.1) or it can be handled by the parser? + */ +static int knot_dname_str_to_wire(const char *name, uint size, + knot_dname_t *dname) +{ + if (size > KNOT_MAX_DNAME_LENGTH) { + return -1; + } + + uint wire_size; + int root = (*name == '.' && size == 1); + // root => different size + if (root) { + wire_size = 1; + } else { + wire_size = size + 1; + } + + uint8_t *wire; + uint8_t labels[KNOT_MAX_DNAME_LABELS]; + short label_count = 0; + + // signed / unsigned issues?? + wire = (uint8_t *)malloc(wire_size * sizeof(uint8_t)); + if (wire == NULL) { + return -1; + } + + dbg_dname("Allocated space for wire format of dname: %p\n", + wire); + + if (root) { + *wire = '\0'; + label_count = 0; + return knot_dname_set(dname, wire, wire_size, labels, + label_count); + } + + const uint8_t *ch = (const uint8_t *)name; + uint8_t *label_start = wire; + uint8_t *w = wire + 1; + uint8_t label_length = 0; + + while (ch - (const uint8_t *)name < size) { + assert(w - wire - 1 == ch - (const uint8_t *)name); + + if (*ch == '.') { + dbg_dname("Position %zd (%p): " + "label length: %u\n", + label_start - wire, + label_start, label_length); + *label_start = label_length; + labels[label_count++] = label_start - wire; + label_start = w; + label_length = 0; + } else { + assert(w - wire < wire_size); + dbg_dname("Position %zd (%p): character: %c\n", + w - wire, w, *ch); + *w = *ch; + ++label_length; + } + + ++w; + ++ch; + assert(ch >= (const uint8_t *)name); + } + + --ch; + if (*ch == '.') { // put 0 for root label if the name ended with . + --w; + dbg_dname("Position %zd (%p): character: (null)\n", + w - wire, w); + *w = 0; + } else { // otherwise we did not save the last label length + dbg_dname("Position %zd (%p): " + "label length: %u\n", + label_start - wire, + label_start, label_length); + *label_start = label_length; + labels[label_count++] = label_start - wire; + } + + return knot_dname_set(dname, wire, wire_size, labels, label_count); +} + +/*----------------------------------------------------------------------------*/ + +static inline int knot_dname_tolower(uint8_t c, int cs) +{ + return (cs) ? c : knot_tolower(c); +} + +/*----------------------------------------------------------------------------*/ + +static int knot_dname_compare_labels(const uint8_t *label1, + const uint8_t *label2, int cs) +{ + const uint8_t *pos1 = label1; + const uint8_t *pos2 = label2; + + int label_length = (*pos1 < *pos2) ? *pos1 : *pos2; + int i = 0; + + while (i < label_length + && knot_dname_tolower(*(++pos1), cs) + == knot_dname_tolower(*(++pos2), cs)) { + ++i; + } + + if (i < label_length) { // difference in some octet + return (knot_dname_tolower(*pos1, cs) + - knot_dname_tolower(*pos2, cs)); + } + + return (label1[0] - label2[0]); +} + +/*----------------------------------------------------------------------------*/ + +static int knot_dname_find_labels(knot_dname_t *dname, int alloc) +{ + const uint8_t *name = dname->name; + const uint8_t *pos = name; + const uint size = dname->size; + + uint8_t labels[KNOT_MAX_DNAME_LABELS]; + short label_count = 0; + + while (pos - name < size && *pos != '\0') { + labels[label_count++] = pos - name; + pos += *pos + 1; + } + + // TODO: how to check if the domain name has right format? + if (pos - name > size || *pos != '\0') { + dbg_dname("Wrong wire format of domain name!\n"); + dbg_dname("Position: %d, character: %d, expected" + " size: %d\n", pos - name, *pos, size); + return -1; + } + + if (alloc) { + dname->labels + = (uint8_t *)malloc(label_count * sizeof(uint8_t)); + CHECK_ALLOC_LOG(dname->labels, KNOT_ENOMEM); + } + + memcpy(dname->labels, labels, label_count); + dname->label_count = label_count; + + return 0; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_dname_cmp(const knot_dname_t *d1, const knot_dname_t *d2, + int cs) +{ +dbg_dname_exec( + char *name1 = knot_dname_to_str(d1); + char *name2 = knot_dname_to_str(d2); + + dbg_dname("Comparing dnames %s and %s\n", + name1, name2); + + for (int i = 0; i < strlen(name1); ++i) { + name1[i] = knot_tolower(name1[i]); + } + for (int i = 0; i < strlen(name2); ++i) { + name2[i] = knot_tolower(name2[i]); + } + + dbg_dname("After to lower: %s and %s\n", + name1, name2); + + free(name1); + free(name2); +); + + if (!cs && d1 == d2) { + return 0; + } + + int l1 = d1->label_count; + int l2 = d2->label_count; + dbg_dname("Label counts: %d and %d\n", l1, l2); + assert(l1 >= 0); + assert(l2 >= 0); + + // compare labels from last to first + while (l1 > 0 && l2 > 0) { + dbg_dname("Comparing labels %d and %d\n", + l1 - 1, l2 - 1); + dbg_dname(" at offsets: %d and %d\n", + d1->labels[l1 - 1], d2->labels[l2 - 1]); + int res = knot_dname_compare_labels( + &d1->name[d1->labels[--l1]], + &d2->name[d2->labels[--l2]], + cs); + if (res != 0) { + return res; + } // otherwise the labels are identical, continue with previous + } + + // if all labels matched, the shorter name is first + if (l1 == 0 && l2 > 0) { + return -1; + } + + if (l1 > 0 && l2 == 0) { + return 1; + } + + return 0; +} + +/*! \brief Destructor for reference counter. */ +static void knot_dname_dtor(struct ref_t *p) +{ + knot_dname_t *dname = (knot_dname_t *)p; + knot_dname_free(&dname); +} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +knot_dname_t *knot_dname_new() +{ + knot_dname_t *dname = knot_dname_alloc(); + dname->name = NULL; + dname->size = 0; + dname->node = NULL; + dname->labels = NULL; + dname->label_count = -1; + dname->id = 0; + + /* Initialize reference counting. */ + ref_init(&dname->ref, knot_dname_dtor); + + /* Set reference counter to 1, caller should release it after use. */ + knot_dname_retain(dname); + + return dname; +} + +/*----------------------------------------------------------------------------*/ + +knot_dname_t *knot_dname_new_from_str(const char *name, uint size, + struct knot_node *node) +{ + if (name == NULL || size == 0) { + return NULL; + } + +// knot_dname_t *dname = knot_dname_alloc(); + knot_dname_t *dname = knot_dname_new(); + + if (dname == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + + knot_dname_str_to_wire(name, size, dname); + dbg_dname("Created dname with size: %d\n", dname->size); + dbg_dname("Label offsets: "); + for (int i = 0; i < dname->label_count; ++i) { + dbg_dname("%d, ", dname->labels[i]); + } + dbg_dname("\n"); + + if (dname->size <= 0) { + fprintf(stderr, "Could not parse domain name " + "from string: '%.*s'\n", size, name); + } + assert(dname->name != NULL); + + dname->node = node; + dname->id = 0; + + return dname; +} + +/*----------------------------------------------------------------------------*/ + +//int knot_dname_from_wire(knot_dname_t *dname, const uint8_t *name, +// uint size) +//{ +// int i = 0; +// uint8_t labels[KNOT_MAX_DNAME_LABELS]; +// int label_i = 0; + +// while (name[i] != 0) { +// labels[label_i++] = i; +// uint8_t label_length = name[i]; +// if (i + label_length >= size) { +// return -2; +// } +// for (int j = 1; j <= label_length; ++j) { +// } +// } +//} + +/*----------------------------------------------------------------------------*/ + +knot_dname_t *knot_dname_new_from_wire(const uint8_t *name, uint size, + struct knot_node *node) +{ + if (name == NULL) { /* && size != 0) { !OS: Nerozumjaju */ + dbg_dname("No name given!\n"); + return NULL; + } + + knot_dname_t *dname = knot_dname_new(); + + if (dname == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + + dname->name = (uint8_t *)malloc(size * sizeof(uint8_t)); + if (dname->name == NULL) { + ERR_ALLOC_FAILED; + knot_dname_free(&dname); + return NULL; + } + + memcpy(dname->name, name, size); + dname->size = size; + + if (knot_dname_find_labels(dname, 1) != 0) { + knot_dname_free(&dname); + return NULL; + } + + dname->node = node; + dname->id = 0; + + return dname; +} + +/*----------------------------------------------------------------------------*/ + +knot_dname_t *knot_dname_parse_from_wire(const uint8_t *wire, + size_t *pos, size_t size, + knot_node_t *node) +{ + uint8_t name[KNOT_MAX_DNAME_LENGTH]; + uint8_t labels[KNOT_MAX_DNAME_LABELS]; + + short l = 0; + size_t i = 0, p = *pos; + int pointer_used = 0; + + while (p < size && wire[p] != 0) { + labels[l] = i; + dbg_dname("Next label (%d.) position: %zu\n", l, i); + + if (knot_wire_is_pointer(wire + p)) { + // pointer. +// printf("Pointer.\n"); + p = knot_wire_get_pointer(wire + p); + if (!pointer_used) { + *pos += 2; + pointer_used = 1; + } + if (p >= size) { + return NULL; + } + } else { + // label; first byte is label length + uint8_t length = *(wire + p); +// printf("Label, length: %u.\n", length); + memcpy(name + i, wire + p, length + 1); + p += length + 1; + i += length + 1; + if (!pointer_used) { + *pos += length + 1; + } + ++l; + } + } + if (p >= size) { + return NULL; + } + + name[i] = 0; + if (!pointer_used) { + *pos += 1; + } + + knot_dname_t *dname = knot_dname_new(); + + if (dname == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + + dname->name = (uint8_t *)malloc((i + 1) * sizeof(uint8_t)); + if (dname->name == NULL) { + ERR_ALLOC_FAILED; + knot_dname_free(&dname); + return NULL; + } + + memcpy(dname->name, name, i + 1); + dname->size = i + 1; + + dname->labels = (uint8_t *)malloc((l + 1) * sizeof(uint8_t)); + if (dname->labels == NULL) { + ERR_ALLOC_FAILED; + knot_dname_free(&dname); + return NULL; + } + memcpy(dname->labels, labels, l + 1); + + dname->label_count = l; + + dname->node = node; + + return dname; +} + +/*----------------------------------------------------------------------------*/ + +int knot_dname_from_wire(const uint8_t *name, uint size, + struct knot_node *node, knot_dname_t *target) +{ + if (name == NULL || target == NULL) { + return KNOT_EBADARG; + } + + memcpy(target->name, name, size); + target->size = size; + target->node = node; + target->id = 0; + + return knot_dname_find_labels(target, 0); +} + +/*----------------------------------------------------------------------------*/ + +knot_dname_t *knot_dname_deep_copy(const knot_dname_t *dname) +{ + return knot_dname_new_from_wire(dname->name, dname->size, + dname->node); +} + +/*----------------------------------------------------------------------------*/ + +char *knot_dname_to_str(const knot_dname_t *dname) +{ + if (!dname || dname->size == 0) { + return 0; + } + + char *name; + + // root => special treatment + if (dname->size == 1) { + assert(dname->name[0] == 0); + name = (char *)malloc(2 * sizeof(char)); + name[0] = '.'; + name[1] = '\0'; + return name; + } + + name = (char *)malloc(dname->size * sizeof(char)); + if (name == NULL) { + return NULL; + } + + uint8_t *w = dname->name; + char *ch = name; + int i = 0; + + do { + assert(*w != 0); + int label_size = *(w++); + // copy the label + memcpy(ch, w, label_size); + i += label_size; + ch += label_size; + w += label_size; + if (w - dname->name < dname->size) { // another label following + *(ch++) = '.'; + ++i; + } + } while (i < dname->size - 1); + + *ch = 0; + assert(ch - name == dname->size - 1); + + return name; +} + +/*----------------------------------------------------------------------------*/ + +int knot_dname_to_lower(knot_dname_t *dname) +{ + if (dname == NULL) { + return KNOT_EBADARG; + } + + for (int i = 0; i < dname->size; ++i) { + dname->name[i] = knot_tolower(dname->name[i]); + } + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_dname_to_lower_copy(const knot_dname_t *dname, char *name, + size_t size) +{ + if (dname == NULL || name == NULL || size < dname->size) { + return KNOT_EBADARG; + } + + for (int i = 0; i < dname->size; ++i) { + name[i] = knot_tolower(dname->name[i]); + } + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +const uint8_t *knot_dname_name(const knot_dname_t *dname) +{ + return dname->name; +} + +/*----------------------------------------------------------------------------*/ + +uint knot_dname_size(const knot_dname_t *dname) +{ + return dname->size; +} + +/*----------------------------------------------------------------------------*/ + +unsigned int knot_dname_id(const knot_dname_t *dname) +{ + return dname->id; +} + +/*----------------------------------------------------------------------------*/ + +uint8_t knot_dname_size_part(const knot_dname_t *dname, int labels) +{ + assert(labels < dname->label_count); + assert(dname->labels != NULL); + return (dname->labels[labels]); +} + +/*----------------------------------------------------------------------------*/ + +const struct knot_node *knot_dname_node(const knot_dname_t *dname, + int check_version) + +{ + if (check_version) { + return knot_node_current(dname->node); + } else { + return dname->node; + } +} + +/*----------------------------------------------------------------------------*/ + +struct knot_node *knot_dname_get_node(knot_dname_t *dname, + int check_version) +{ + if (check_version) { + return knot_node_get_current(dname->node); + } else { + return dname->node; + } +} + +/*----------------------------------------------------------------------------*/ + +void knot_dname_set_node(knot_dname_t *dname, knot_node_t *node) +{ + dname->node = node; +} + +/*----------------------------------------------------------------------------*/ + +void knot_dname_update_node(knot_dname_t *dname) +{ + knot_node_update_ref(&dname->node); +} + +/*----------------------------------------------------------------------------*/ + +int knot_dname_is_fqdn(const knot_dname_t *dname) +{ + return (dname->name[dname->size - 1] == '\0'); +} + +/*----------------------------------------------------------------------------*/ + +knot_dname_t *knot_dname_left_chop(const knot_dname_t *dname) +{ + knot_dname_t *parent = knot_dname_new(); + if (parent == NULL) { + return NULL; + } + + parent->size = dname->size - dname->name[0] - 1; + parent->name = (uint8_t *)malloc(parent->size); + if (parent->name == NULL) { + ERR_ALLOC_FAILED; + knot_dname_free(&parent); + return NULL; + } + + parent->labels = (uint8_t *)malloc(dname->label_count - 1); + if (parent->labels == NULL) { + ERR_ALLOC_FAILED; + free(parent->name); + knot_dname_free(&parent); + return NULL; + } + + memcpy(parent->name, &dname->name[dname->name[0] + 1], parent->size); + + short first_label_length = dname->labels[1]; + + for (int i = 0; i < dname->label_count - 1; ++i) { + parent->labels[i] = dname->labels[i + 1] - first_label_length; + } + parent->label_count = dname->label_count - 1; + + return parent; +} + +/*----------------------------------------------------------------------------*/ + +void knot_dname_left_chop_no_copy(knot_dname_t *dname) +{ + // copy the name + short first_label_length = dname->labels[1]; + + if (dname->label_count > 1) { + memmove(dname->name, &dname->name[dname->labels[1]], + dname->size - first_label_length); + // adjust labels + for (int i = 0; i < dname->label_count - 1; ++i) { + dname->labels[i] = dname->labels[i + 1] + - first_label_length; + } + dname->label_count = dname->label_count - 1; + dname->size -= first_label_length; + } else { + dname->name[0] = '\0'; + dname->size = 1; + dname->label_count = 0; + } +} + +/*----------------------------------------------------------------------------*/ + +int knot_dname_is_subdomain(const knot_dname_t *sub, + const knot_dname_t *domain) +{ +dbg_dname_exec( + char *name1 = knot_dname_to_str(sub); + char *name2 = knot_dname_to_str(domain); + + dbg_dname("Checking if %s is subdomain of %s\n", + name1, name2); + free(name1); + free(name2); +); + + if (sub == domain) { + return 0; + } + + // if one of the names is fqdn and the other is not + if ((sub->name[sub->size - 1] == '\0' + && domain->name[domain->size - 1] != '\0') + || (sub->name[sub->size - 1] != '\0' + && domain->name[domain->size - 1] == '\0')) { + return 0; + } + + int l1 = sub->label_count; + int l2 = domain->label_count; + + dbg_dname("Label counts: %d and %d\n", l1, l2); + + if (l1 <= l2) { // if sub does not have more labes than domain + return 0; // it is not its subdomain + } + + // compare labels from last to first + while (l1 > 0 && l2 > 0) { + dbg_dname("Comparing labels %d and %d\n", + l1 - 1, l2 - 1); + dbg_dname(" at offsets: %d and %d\n", + sub->labels[l1 - 1], domain->labels[l2 - 1]); + // if some labels do not match + if (knot_dname_compare_labels(&sub->name[sub->labels[--l1]], + &domain->name[domain->labels[--l2]], 0) + != 0) { + return 0; // sub is not a subdomain of domain + } // otherwise the labels are identical, continue with previous + } + + // if all labels matched, it should be subdomain (more labels) + assert(l1 > l2); + + return 1; +} + +/*----------------------------------------------------------------------------*/ + +int knot_dname_is_wildcard(const knot_dname_t *dname) +{ + return (dname->size >= 2 + && dname->name[0] == 1 + && dname->name[1] == '*'); +} + +/*----------------------------------------------------------------------------*/ + +int knot_dname_matched_labels(const knot_dname_t *dname1, + const knot_dname_t *dname2) +{ + int l1 = dname1->label_count; + int l2 = dname2->label_count; + + // compare labels from last to first + int matched = 0; + while (l1 > 0 && l2 > 0) { + int res = knot_dname_compare_labels( + &dname1->name[dname1->labels[--l1]], + &dname2->name[dname2->labels[--l2]], 0); + if (res == 0) { + ++matched; + } else { + break; + } + } + + return matched; +} + +/*----------------------------------------------------------------------------*/ + +int knot_dname_label_count(const knot_dname_t *dname) +{ + return dname->label_count; +} + +/*----------------------------------------------------------------------------*/ + +uint8_t knot_dname_label_size(const knot_dname_t *dname, int i) +{ +// printf("Returning size of %d. label starting on %d\n", +// i, dname->labels[i]); +// printf("Label count: %d, size of %d. label: %d, size of %d.label: %d\n", +// dname->label_count, i, dname->labels[i], i + 1, +// dname->labels[i+1]); +// printf("Size from the name: %u\n", dname->name[dname->labels[i]]); +// printf("Size from label offsets: %u\n", +// dname->labels[i + 1] - dname->labels[i]); + + assert(i >= 0); + assert(dname->size == 1 || i + 1 == dname->label_count + || dname->labels[i + 1] - dname->labels[i] - 1 + == dname->name[dname->labels[i]]); + return dname->name[dname->labels[i]]; +} + +/*----------------------------------------------------------------------------*/ + +knot_dname_t *knot_dname_replace_suffix(const knot_dname_t *dname, + int size, + const knot_dname_t *suffix) +{ +dbg_dname_exec( + char *name = knot_dname_to_str(dname); + dbg_dname("Replacing suffix of name %s, size %d with ", name, + size); + free(name); + name = knot_dname_to_str(suffix); + dbg_dname("%s (size %d)\n", name, suffix->size); + free(name); +); + knot_dname_t *res = knot_dname_new(); + CHECK_ALLOC(res, NULL); + + res->size = dname->size - size + suffix->size; + + dbg_dname("Allocating %d bytes...\n", res->size); + res->name = (uint8_t *)malloc(res->size); + if (res->name == NULL) { + knot_dname_free(&res); + return NULL; + } + + dbg_dname_hex((char *)res->name, res->size); + + dbg_dname("Copying %d bytes from the original name.\n", + dname->size - size); + memcpy(res->name, dname->name, dname->size - size); + dbg_dname_hex((char *)res->name, res->size); + + dbg_dname("Copying %d bytes from the suffix.\n", suffix->size); + memcpy(res->name + dname->size - size, suffix->name, suffix->size); + + dbg_dname_hex((char *)res->name, res->size); + + knot_dname_find_labels(res, 1); + + return res; +} + +/*----------------------------------------------------------------------------*/ + +void knot_dname_free(knot_dname_t **dname) +{ + if (dname == NULL || *dname == NULL) { + return; + } + +// char *name = knot_dname_to_str((*dname)); + +// printf("freeing in dname: %s %p\n", name, *dname); + +// free(name); + + + if ((*dname)->name != NULL) { + free((*dname)->name); + } + + if((*dname)->labels != NULL) { + free((*dname)->labels); + } + + +// slab_free(*dname); + free(*dname); + *dname = NULL; +} + +/*----------------------------------------------------------------------------*/ + +int knot_dname_compare(const knot_dname_t *d1, const knot_dname_t *d2) +{ + return knot_dname_cmp(d1, d2, 0); +} + +/*----------------------------------------------------------------------------*/ + +int knot_dname_compare_cs(const knot_dname_t *d1, const knot_dname_t *d2) +{ + return knot_dname_cmp(d1, d2, 1); +} + +/*----------------------------------------------------------------------------*/ + +knot_dname_t *knot_dname_cat(knot_dname_t *d1, const knot_dname_t *d2) +{ + if (d2->size == 0) { + return d1; + } + + if (knot_dname_is_fqdn(d1)) { + return NULL; + } + + // allocate new space + uint8_t *new_dname = (uint8_t *)malloc(d1->size + d2->size); + CHECK_ALLOC_LOG(new_dname, NULL); + + uint8_t *new_labels = (uint8_t *)malloc(d1->label_count + + d2->label_count); + if (new_labels == NULL) { + ERR_ALLOC_FAILED; + free(new_dname); + return NULL; + } + + dbg_dname("1: copying %d bytes from adress %p to %p\n", + d1->size, d1->name, new_dname); + + memcpy(new_dname, d1->name, d1->size); + + dbg_dname("2: copying %d bytes from adress %p to %p\n", + d2->size, d2->name, new_dname + d1->size); + + memcpy(new_dname + d1->size, d2->name, d2->size); + + // update labels + memcpy(new_labels, d1->labels, d1->label_count); + for (int i = 0; i < d2->label_count; ++i) { + new_labels[d1->label_count + i] = d2->labels[i] + d1->size; + } + + uint8_t *old_labels = d1->labels; + d1->labels = new_labels; + free(old_labels); + d1->label_count += d2->label_count; + + uint8_t *old_name = d1->name; + d1->name = new_dname; + free(old_name); + + d1->size += d2->size; + + return d1; +} + +void knot_dname_set_id(knot_dname_t *dname, unsigned int id) +{ + dname->id = id; +} + +unsigned int knot_dname_get_id(const knot_dname_t *dname) +{ + if (dname != NULL) { + return dname->id; + } else { + return 0; /* 0 should never be used and is reserved for err. */ + } +} diff --git a/src/libknot/dname.h b/src/libknot/dname.h new file mode 100644 index 0000000..c0e3f35 --- /dev/null +++ b/src/libknot/dname.h @@ -0,0 +1,428 @@ +/*! + * \file dname.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Domain name structure and API for manipulating it. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_DNAME_H_ +#define _KNOT_DNAME_H_ + +#include <stdint.h> +#include <string.h> +#include "common/ref.h" + +struct knot_node; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Structure for representing a domain name. + * + * Stores the domain name in wire format. + * + * \todo Consider restricting to FQDN only (see knot_dname_new_from_str()). + */ +struct knot_dname { + ref_t ref; /*!< Reference counting. */ + uint8_t *name; /*!< Wire format of the domain name. */ + /*! + * \brief Size of the domain name in octets. + * \todo Is this needed? Every dname should end with \0 or pointer. + */ + unsigned int size; + uint8_t *labels; + unsigned short label_count; + struct knot_node *node; /*!< Zone node the domain name belongs to. */ + unsigned int id; /*!< ID of domain name used in zone dumping. */ +}; + +typedef struct knot_dname knot_dname_t; + +/*----------------------------------------------------------------------------*/ + +/*! + * \brief Creates empty dname structure (no name, no owner node). + * + * \note Newly created dname is referenced, caller is responsible for releasing + * it after use. + * + * \return Newly allocated and initialized dname structure. + * + * \todo Possibly useless. + */ +knot_dname_t *knot_dname_new(); + +/*! + * \brief Creates a dname structure from domain name given in presentation + * format. + * + * The resulting domain name is stored in wire format, but it may not end with + * root label (0). + * + * \note Newly created dname is referenced, caller is responsible for releasing + * it after use. + * + * \param name Domain name in presentation format (labels separated by dots). + * \param size Size of the domain name (count of characters with all dots). + * \param node Zone node the domain name belongs to. Set to NULL if not + * applicable. + * + * \return Newly allocated and initialized dname structure representing the + * given domain name. + */ +knot_dname_t *knot_dname_new_from_str(const char *name, unsigned int size, + struct knot_node *node); + +/*! + * \brief Creates a dname structure from domain name given in wire format. + * + * \note The name is copied into the structure. + * \note If the given name is not a FQDN, the result will be neither. + * \note Newly created dname is referenced, caller is responsible for releasing + * it after use. + * + * \param name Domain name in wire format. + * \param size Size of the domain name in octets. + * \param node Zone node the domain name belongs to. Set to NULL if not + * applicable. + * + * \return Newly allocated and initialized dname structure representing the + * given domain name. + * + * \todo This function does not check if the given data is in correct wire + * format at all. It thus creates a invalid domain name, which if passed + * e.g. to knot_dname_to_str() may result in crash. Decide whether it + * is OK to retain this and check the data in other functions before + * calling this one, or if it should verify the given data. + */ +knot_dname_t *knot_dname_new_from_wire(const uint8_t *name, + unsigned int size, + struct knot_node *node); + +knot_dname_t *knot_dname_parse_from_wire(const uint8_t *wire, + size_t *pos, size_t size, + struct knot_node *node); + +/*! + * \brief Initializes domain name by the name given in wire format. + * + * \note The name is copied into the structure. + * \note If there is any name in the structure, it will be replaced. + * \note If the given name is not a FQDN, the result will be neither. + * + * \param name Domain name in wire format. + * \param size Size of the domain name in octets. + * \param node Zone node the domain name belongs to. Set to NULL if not + * applicable. + * \param target Domain name structure to initialize. + * + * \retval KNOT_EOK on success. + * \retval KNOT_ENOMEM if allocation of labels info failed. + * \retval KNOT_EBADARG if name or target is null. + * + * \todo This function does not check if the given data is in correct wire + * format at all. It thus creates a invalid domain name, which if passed + * e.g. to knot_dname_to_str() may result in crash. Decide whether it + * is OK to retain this and check the data in other functions before + * calling this one, or if it should verify the given data. + */ +int knot_dname_from_wire(const uint8_t *name, unsigned int size, + struct knot_node *node, knot_dname_t *target); + +/*! + * \brief Duplicates the given domain name. + * + * \note Copied dname referense count is reset to 1, caller is responsible + * for releasing it after use. + * + * \param dname Domain name to be copied. + * + * \return New domain name which is an exact copy of \a dname. + */ +knot_dname_t *knot_dname_deep_copy(const knot_dname_t *dname); + +/*! + * \brief Converts the given domain name to string representation. + * + * \note Allocates new memory, remember to free it. + * + * \param dname Domain name to be converted. + * + * \return 0-terminated string representing the given domain name in + * presentation format. + */ +char *knot_dname_to_str(const knot_dname_t *dname); + +int knot_dname_to_lower(knot_dname_t *dname); + +int knot_dname_to_lower_copy(const knot_dname_t *dname, char *name, + size_t size); + +/*! + * \brief Returns the domain name in wire format. + * + * \param dname Domain name. + * + * \return Wire format of the domain name. + */ +const uint8_t *knot_dname_name(const knot_dname_t *dname); + +/*! + * \brief Returns size of the given domain name. + * + * \param dname Domain name to get the size of. + * + * \return Size of the domain name in wire format in octets. + */ +unsigned int knot_dname_size(const knot_dname_t *dname); + +unsigned int knot_dname_id(const knot_dname_t *dname); + +/*! + * \brief Returns size of a part of domain name. + * + * \param dname Domain name. + * \param labels Count of labels to get the size of (counted from left). + * + * \return Size of first \a labels labels of \a dname, counted from left. + */ +uint8_t knot_dname_size_part(const knot_dname_t *dname, int labels); + +/*! + * \brief Returns the zone node the domain name belongs to. + * + * \param dname Domain name to get the zone node of. + * + * \return Zone node the domain name belongs to or NULL if none. + */ +const struct knot_node *knot_dname_node(const knot_dname_t *dname, + int check_version); + +struct knot_node *knot_dname_get_node(knot_dname_t *dname, + int check_version); + +void knot_dname_set_node(knot_dname_t *dname, struct knot_node *node); + +void knot_dname_update_node(knot_dname_t *dname); + +void knot_dname_set_node(knot_dname_t *dname, struct knot_node *node); + +/*! + * \brief Checks if the given domain name is a fully-qualified domain name. + * + * \param dname Domain name to check. + * + * \retval <> 0 if \a dname is a FQDN. + * \retval 0 otherwise. + */ +int knot_dname_is_fqdn(const knot_dname_t *dname); + +/*! + * \brief Creates new domain name by removing leftmost label from \a dname. + * + * \note Newly created dname reference count is set to 1, caller is responsible + * for releasing it after use. + * + * \param dname Domain name to remove the first label from. + * + * \return New domain name with the same labels as \a dname, except for the + * leftmost label, which is removed. + */ +knot_dname_t *knot_dname_left_chop(const knot_dname_t *dname); + +/*! + * \brief Removes leftmost label from \a dname. + * + * \param dname Domain name to remove the first label from. + */ +void knot_dname_left_chop_no_copy(knot_dname_t *dname); + +/*! + * \brief Checks if one domain name is a subdomain of other. + * + * \param sub Domain name to be the possible subdomain. + * \param domain Domain name to be the possible parent domain. + * + * \retval <> 0 if \a sub is a subdomain of \a domain. + * \retval 0 otherwise. + */ +int knot_dname_is_subdomain(const knot_dname_t *sub, + const knot_dname_t *domain); + +/*! + * \brief Checks if the domain name is a wildcard. + * + * \param dname Domain name to check. + * + * \retval <> 0 if \a dname is a wildcard domain name. + * \retval 0 otherwise. + */ +int knot_dname_is_wildcard(const knot_dname_t *dname); + +/*! + * \brief Returns the number of labels common for the two domain names (counted + * from the rightmost label. + * + * \param dname1 First domain name. + * \param dname2 Second domain name. + * + * \return Number of labels common for the two domain names. + */ +int knot_dname_matched_labels(const knot_dname_t *dname1, + const knot_dname_t *dname2); + +/*! + * \brief Returns the number of labels in the domain name. + * + * \param dname Domain name to get the label count of. + * + * \return Number of labels in \a dname. + * + * \todo Find out if this counts the root label also. + */ +int knot_dname_label_count(const knot_dname_t *dname); + +/*! + * \brief Returns the size of the requested label in the domain name. + * + * \param dname Domain name to get the label size from. + * \param i Index of the label (0 is the leftmost label). + * + * \return Size of \a i-th label in \a dname (counted from left). + */ +uint8_t knot_dname_label_size(const knot_dname_t *dname, int i); + +/*! + * \brief Replaces the suffix of given size in one domain name with other domain + * name. + * + * \param dname Domain name where to replace the suffix. + * \param size Size of the suffix to be replaced. + * \param suffix New suffix to be used as a replacement. + * + * \return New domain name created by replacing suffix of \a dname of size + * \a size with \a suffix. + */ +knot_dname_t *knot_dname_replace_suffix(const knot_dname_t *dname, + int size, + const knot_dname_t *suffix); + +/*! + * \brief Destroys the given domain name. + * + * Frees also the data within the struct. This is somewhat different behaviour + * than that of RDATA and RRSet structures which do not deallocate their + * contents. + * + * Sets the given pointer to NULL. + * + * \param dname Domain name to be destroyed. + */ +void knot_dname_free(knot_dname_t **dname); + +/*! + * \brief Compares two domain names (case insensitive). + * + * \param d1 First domain name. + * \param d2 Second domain name. + * + * \retval < 0 if \a d1 goes before \a d2 in canonical order. + * \retval > 0 if \a d1 goes after \a d2 in canonical order. + * \retval 0 if the domain names are identical. + */ +int knot_dname_compare(const knot_dname_t *d1, const knot_dname_t *d2); + +/*! + * \brief Compares two domain names (case sensitive). + * + * \param d1 First domain name. + * \param d2 Second domain name. + * + * \retval < 0 if \a d1 goes before \a d2 in canonical order. + * \retval > 0 if \a d1 goes after \a d2 in canonical order. + * \retval 0 if the domain names are identical. + */ +int knot_dname_compare_cs(const knot_dname_t *d1, const knot_dname_t *d2); + +/*! + * \brief Concatenates two domain names. + * + * \note Member \a node is ignored, i.e. preserved. + * + * \param d1 First domain name (will be modified). + * \param d2 Second domain name (will not be modified). + * + * \return The concatenated domain name (i.e. modified \a d1) or NULL if + * the operation is not valid (e.g. \a d1 is a FQDN). + */ +knot_dname_t *knot_dname_cat(knot_dname_t *d1, const knot_dname_t *d2); + +void knot_dname_set_id(knot_dname_t *dname, unsigned int id); + +unsigned int knot_dname_get_id(const knot_dname_t *dname); + +/*! + * \brief Increment reference counter for dname. + * + * Function makes shallow copy (reference). + * + * \param dname Referenced dname. + */ +static inline void knot_dname_retain(knot_dname_t *dname) { + if (dname) { + ref_retain(&dname->ref); +// char *name = knot_dname_to_str(dname); +// printf("retain: %s %p %d\n", name, dname, dname->ref.count); +// free(name); + + } +} + +/*#define knot_dname_retain(d) \ + knot_dname_retain_((d));\ + if ((d))\ + printf("dname_retain: %s() at %s:%d, %p refcount=%zu\n",\ + __func__, __FILE__, __LINE__, d, (d)->ref.count) */ + +/*! + * \brief Decrement reference counter for dname. + * + * \param dname Referenced dname. + */ +static inline void knot_dname_release(knot_dname_t *dname) { + if (dname) { +// char *name = knot_dname_to_str(dname); +// printf("releasing: %p %s %d\n", dname, name, dname->ref.count - 1); +// free(name); + ref_release(&dname->ref); + } +} + +/*#define knot_dname_release(d) \ + if ((d))\ + printf("dname_release: %s() at %s:%d, %p refcount=%zu\n",\ + __func__, __FILE__, __LINE__, d, (d)->ref.count-1);\ + knot_dname_release_((d)) */ + +#endif /* _KNOT_DNAME_H_ */ + +/*! @} */ diff --git a/src/libknot/edns.c b/src/libknot/edns.c new file mode 100644 index 0000000..05ebd7b --- /dev/null +++ b/src/libknot/edns.c @@ -0,0 +1,416 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdint.h> +#include <stdlib.h> +#include <assert.h> + +#include "edns.h" +#include "common.h" +#include "util/descriptor.h" +#include "util/debug.h" +#include "util/error.h" + +/*! \brief Various EDNS constatns. */ +enum knot_edns_consts { + /*! \brief Mask for the DO bit. */ + KNOT_EDNS_DO_MASK = (uint16_t)0x8000, + /*! \brief Step for allocation of space for option entries. */ + KNOT_EDNS_OPTION_STEP = 1 +}; + +/*! \brief Minimum size of EDNS OPT RR in wire format. */ +static const short KNOT_EDNS_MIN_SIZE = 11; + +/*----------------------------------------------------------------------------*/ + +knot_opt_rr_t *knot_edns_new() +{ + knot_opt_rr_t *opt_rr = (knot_opt_rr_t *)malloc( + sizeof(knot_opt_rr_t)); + CHECK_ALLOC_LOG(opt_rr, NULL); + opt_rr->size = KNOT_EDNS_MIN_SIZE; + opt_rr->option_count = 0; + opt_rr->options_max = 0; + + opt_rr->ext_rcode = 0; + opt_rr->flags = 0; + opt_rr->version = 0; + + return opt_rr; +} + +/*----------------------------------------------------------------------------*/ + +int knot_edns_new_from_wire(knot_opt_rr_t *opt_rr, const uint8_t *wire, + size_t max_size) +{ + const uint8_t *pos = wire; + int parsed = 0; + + if (pos == NULL || max_size == 0 || opt_rr == NULL) { + return KNOT_EBADARG; + } + + if (max_size < KNOT_EDNS_MIN_SIZE) { + dbg_edns("Not enough data to parse OPT RR header.\n"); + return KNOT_EFEWDATA; + } + + // owner of EDNS OPT RR must be root (0) + if (*pos != 0) { + dbg_edns("EDNS packet malformed (expected root " + "domain as owner).\n"); + return KNOT_EMALF; + } + pos += 1; + + // check the type of the record (must be OPT) + if (knot_wire_read_u16(pos) != KNOT_RRTYPE_OPT) { + dbg_edns("EDNS packet malformed (expected OPT type" + ".\n"); + return KNOT_EMALF; + } + pos += 2; + + opt_rr->payload = knot_wire_read_u16(pos); + dbg_edns("Parsed payload: %u\n", opt_rr->payload); + + pos += 2; + opt_rr->ext_rcode = *(pos++); + opt_rr->version = *(pos++); + opt_rr->flags = knot_wire_read_u16(pos); + pos += 2; + + parsed = KNOT_EDNS_MIN_SIZE; + + // ignore RDATA, but move pos behind them + uint16_t rdlength = knot_wire_read_u16(pos); + pos += 2; + + if (max_size - parsed < rdlength) { + dbg_edns("Not enough data to parse OPT RR.\n"); + return KNOT_EFEWDATA; + } + + while (parsed < rdlength + KNOT_EDNS_MIN_SIZE) { + if (max_size - parsed < 4) { + dbg_edns("Not enough data to parse OPT RR" + " OPTION header.\n"); + return KNOT_EFEWDATA; + } + uint16_t code = knot_wire_read_u16(pos); + pos += 2; + uint16_t length = knot_wire_read_u16(pos); + pos += 2; + dbg_edns("EDNS OPTION: Code: %u, Length: %u\n", + code, length); + if (max_size - parsed - 4 < length) { + dbg_edns("Not enough data to parse OPT RR" + " OPTION data.\n"); + return KNOT_EFEWDATA; + } + int ret; + if ((ret = + knot_edns_add_option(opt_rr, code, length, pos)) != 0) { + dbg_edns("Error parsing OPT option field.\n"); + return ret; + } + pos += length; + parsed += length + 4; + } + + return parsed; +} + +/*----------------------------------------------------------------------------*/ + +int knot_edns_new_from_rr(knot_opt_rr_t *opt_rr, + const knot_rrset_t *rrset) +{ + if (opt_rr == NULL || rrset == NULL + || knot_rrset_type(rrset) != KNOT_RRTYPE_OPT) { + return KNOT_EBADARG; + } + + dbg_edns("Parsing payload.\n"); + opt_rr->payload = knot_rrset_class(rrset); + + // the TTL has switched bytes + uint32_t ttl; + dbg_edns("TTL: %u\n", knot_rrset_ttl(rrset)); + knot_wire_write_u32((uint8_t *)&ttl, knot_rrset_ttl(rrset)); + // first byte of TTL is extended RCODE + dbg_edns("TTL: %u\n", ttl); + memcpy(&opt_rr->ext_rcode, &ttl, 1); + dbg_edns("Parsed extended RCODE: %u.\n", opt_rr->ext_rcode); + // second is the version + memcpy(&opt_rr->version, (const uint8_t *)(&ttl) + 1, 1); + dbg_edns("Parsed version: %u.\n", opt_rr->version); + // third and fourth are flags + opt_rr->flags = knot_wire_read_u16((const uint8_t *)(&ttl) + 2); + dbg_edns("Parsed flags: %u.\n", opt_rr->flags); + // size of the header, options are counted elsewhere + opt_rr->size = 11; + + int rc = 0; + dbg_edns("Parsing options.\n"); + const knot_rdata_t *rdata = knot_rrset_rdata(rrset); + + // in OPT RR, all RDATA are in one RDATA item stored as BINARY data, + // i.e. preceded by their length + if (rdata != NULL) { + assert(knot_rdata_item_count(rdata) == 1); + const uint8_t *raw = (const uint8_t *) + knot_rdata_item(rdata, 0)->raw_data; + uint16_t size = knot_wire_read_u16(raw); + int pos = 2; + assert(size > 0); + while (pos - 2 < size) { + // ensure there is enough data to parse the OPTION CODE + // and OPTION LENGTH + if (size - pos + 2 < 4) { + return KNOT_EMALF; + } + uint16_t opt_code = knot_wire_read_u16(raw + pos); + uint16_t opt_size = knot_wire_read_u16(raw + pos + 2); + + // there should be enough data for parsing the OPTION + // data + if (size - pos - 2 < opt_size) { + return KNOT_EMALF; + } + rc = knot_edns_add_option(opt_rr, opt_code, opt_size, + raw + pos + 4); + if (rc != KNOT_EOK) { + return rc; + } + pos += 4 + opt_size; + } + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +uint16_t knot_edns_get_payload(const knot_opt_rr_t *opt_rr) +{ + assert(opt_rr != NULL); + return opt_rr->payload; +} + +/*----------------------------------------------------------------------------*/ + +void knot_edns_set_payload(knot_opt_rr_t *opt_rr, + uint16_t payload) +{ + assert(opt_rr != NULL); + opt_rr->payload = payload; +} + +/*----------------------------------------------------------------------------*/ + +uint8_t knot_edns_get_ext_rcode(const knot_opt_rr_t *opt_rr) +{ + return opt_rr->ext_rcode; +} + +/*----------------------------------------------------------------------------*/ + +void knot_edns_set_ext_rcode(knot_opt_rr_t *opt_rr, + uint8_t ext_rcode) +{ + assert(opt_rr != NULL); + opt_rr->ext_rcode = ext_rcode; +} + +/*----------------------------------------------------------------------------*/ + +uint8_t knot_edns_get_version(const knot_opt_rr_t *opt_rr) +{ + assert(opt_rr != NULL); + return opt_rr->version; +} + +/*----------------------------------------------------------------------------*/ + +void knot_edns_set_version(knot_opt_rr_t *opt_rr, + uint8_t version) +{ + assert(opt_rr != NULL); + opt_rr->version = version; +} + +/*----------------------------------------------------------------------------*/ + +uint16_t knot_edns_get_flags(const knot_opt_rr_t *opt_rr) +{ + assert(opt_rr != NULL); + return opt_rr->flags; +} + +/*----------------------------------------------------------------------------*/ + +int knot_edns_do(const knot_opt_rr_t *opt_rr) +{ + if (opt_rr == NULL) { + return KNOT_EBADARG; + } + + dbg_edns("Flags: %u\n", opt_rr->flags); + return (opt_rr->flags & KNOT_EDNS_DO_MASK); +} + +/*----------------------------------------------------------------------------*/ + +void knot_edns_set_do(knot_opt_rr_t *opt_rr) +{ + if (opt_rr == NULL) { + return; + } + + opt_rr->flags |= KNOT_EDNS_DO_MASK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_edns_add_option(knot_opt_rr_t *opt_rr, uint16_t code, + uint16_t length, const uint8_t *data) +{ + if (opt_rr == NULL) { + return KNOT_EBADARG; + } + + if (opt_rr->option_count == opt_rr->options_max) { + knot_opt_option_t *options_new = + (knot_opt_option_t *)calloc( + (opt_rr->options_max + KNOT_EDNS_OPTION_STEP), + sizeof(knot_opt_option_t)); + CHECK_ALLOC_LOG(options_new, KNOT_ENOMEM); + memcpy(options_new, opt_rr->options, opt_rr->option_count); + opt_rr->options = options_new; + opt_rr->options_max += KNOT_EDNS_OPTION_STEP; + } + + dbg_edns("Adding option.\n"); + dbg_edns("Code: %u.\n", code); + dbg_edns("Length: %u.\n", length); + dbg_edns("Data: %p.\n", data); + + opt_rr->options[opt_rr->option_count].data = (uint8_t *)malloc(length); + CHECK_ALLOC_LOG(opt_rr->options[opt_rr->option_count].data, KNOT_ENOMEM); + memcpy(opt_rr->options[opt_rr->option_count].data, data, length); + + opt_rr->options[opt_rr->option_count].code = code; + opt_rr->options[opt_rr->option_count].length = length; + + ++opt_rr->option_count; + opt_rr->size += 4 + length; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_edns_has_option(const knot_opt_rr_t *opt_rr, uint16_t code) +{ + if (opt_rr == NULL) { + return KNOT_EBADARG; + } + + int i = 0; + while (i < opt_rr->option_count && opt_rr->options[i].code != code) { + ++i; + } + + assert(i >= opt_rr->option_count || opt_rr->options[i].code == code); + + return (i < opt_rr->option_count); +} + +/*----------------------------------------------------------------------------*/ + +short knot_edns_to_wire(const knot_opt_rr_t *opt_rr, uint8_t *wire, + size_t max_size) +{ + if (opt_rr == NULL) { + return KNOT_EBADARG; + } + + assert(KNOT_EDNS_MIN_SIZE <= max_size); + + if (max_size < opt_rr->size) { + dbg_edns("Not enough place for OPT RR wire format.\n"); + return KNOT_ESPACE; + } + + uint8_t *pos = wire; + *(pos++) = 0; + knot_wire_write_u16(pos, KNOT_RRTYPE_OPT); + pos += 2; + knot_wire_write_u16(pos, opt_rr->payload); + pos += 2; + *(pos++) = opt_rr->ext_rcode; + *(pos++) = opt_rr->version; + knot_wire_write_u16(pos, opt_rr->flags); + pos += 2; + + uint8_t *rdlen = pos; + uint16_t len = 0; + pos += 2; + + // OPTIONs + for (int i = 0; i < opt_rr->option_count; ++i) { + knot_wire_write_u16(pos, opt_rr->options[i].code); + pos += 2; + knot_wire_write_u16(pos, opt_rr->options[i].length); + pos += 2; + memcpy(pos, opt_rr->options[i].data, opt_rr->options[i].length); + pos += opt_rr->options[i].length; + len += 4 + opt_rr->options[i].length; + } + + knot_wire_write_u16(rdlen, len); + + return opt_rr->size; +} + +/*----------------------------------------------------------------------------*/ + +short knot_edns_size(knot_opt_rr_t *opt_rr) +{ + if (opt_rr == NULL) { + return KNOT_EBADARG; + } + + return opt_rr->size; +} + +/*----------------------------------------------------------------------------*/ + +void knot_edns_free(knot_opt_rr_t **opt_rr) +{ + if (opt_rr == NULL || *opt_rr == NULL) { + return; + } + + if ((*opt_rr)->option_count > 0) { + free((*opt_rr)->options); + } + free(*opt_rr); + *opt_rr = NULL; +} diff --git a/src/libknot/edns.h b/src/libknot/edns.h new file mode 100644 index 0000000..010d155 --- /dev/null +++ b/src/libknot/edns.h @@ -0,0 +1,273 @@ +/*! + * \file edns.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Functions for manipulating and parsing EDNS OPT pseudo-RR. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_EDNS_H_ +#define _KNOT_EDNS_H_ + +#include <stdint.h> + +#include "util/utils.h" +#include "rrset.h" + +/*----------------------------------------------------------------------------*/ +/*! \brief Structure representing one OPT RR Option. */ +struct knot_opt_option { + uint16_t code; + uint16_t length; + uint8_t *data; +}; + +/*! \brief Structure representing one OPT RR Option. */ +typedef struct knot_opt_option knot_opt_option_t; + +/*! + * \brief Structure for holding EDNS parameters. + * + * \todo NSID + */ +struct knot_opt_rr { + uint16_t payload; /*!< UDP payload. */ + uint8_t ext_rcode; /*!< Extended RCODE. */ + + /*! + * \brief Supported version of EDNS. + * + * Set to EDNS_NOT_SUPPORTED if not supported. + */ + uint8_t version; + + uint16_t flags; /*!< EDNS flags. */ + knot_opt_option_t *options; /*!< EDNS options. */ + short option_count; /*!< Count of EDNS options in this OPT RR.*/ + short options_max; /*!< Maximum count of options. */ + short size; /*!< Total size of the OPT RR in wire format. */ +}; + +/*! \brief Structure for holding EDNS parameters. */ +typedef struct knot_opt_rr knot_opt_rr_t; + +/*----------------------------------------------------------------------------*/ +/*! \brief Constants for supported versions of EDNS. */ +enum knot_edns_versions { + EDNS_VERSION_0 = (uint8_t)0, /*!< EDNS version 0. */ + EDNS_NOT_SUPPORTED = (uint8_t)255 /*!< EDNS not supported. */ +}; + +/*! \brief Constants for EDNS option codes. */ +enum knot_edns_option_codes { + EDNS_OPTION_NSID = (uint16_t)3 /*!< NSID option code. */ +}; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates new empty OPT RR structure for holding EDNS parameters. + * + * \return New empty knot_opt_rr_t structure, or NULL if not successful. + */ +knot_opt_rr_t *knot_edns_new(); + +/*! + * \brief Initializes OPT RR structure from given OPT RR in wire format. + * + * \param opt_rr OPT RR structure to initialize. + * \param wire Wire format of the OPT RR to parse. + * \param max_size Maximum size of the wire format in bytes (may be more + * than acutal size of the OPT RR). + * + * \return Size of the parserd OPT RR in bytes if successful (always > 0). + * \retval KNOT_EBADARG + * \retval KNOT_EFEWDATA + * \retval KNOT_EMALF + * \retval KNOT_ENOMEM + */ +int knot_edns_new_from_wire(knot_opt_rr_t *opt_rr, const uint8_t *wire, + size_t max_size); + +int knot_edns_new_from_rr(knot_opt_rr_t *opt_rr, + const knot_rrset_t *rrset); + +/*! + * \brief Returns the UDP payload stored in the OPT RR. + * + * \warning This function does not check the parameter, so ensure to check it + * before calling the function. It must not be NULL. + * \note There is an assert() for debug checking of the parameter. + * + * \param opt_rr OPT RR structure to get the payload from. + * + * \return UDP payload in bytes. + */ +uint16_t knot_edns_get_payload(const knot_opt_rr_t *opt_rr); + +/*! + * \brief Sets the UDP payload field in the OPT RR. + * + * \warning This function does not check the parameter, so ensure to check it + * before calling the function. It must not be NULL. + * \note There is an assert() for debug checking of the parameter. + * + * \param opt_rr OPT RR structure to set the payload to. + * \param payload UDP payload in bytes. + */ +void knot_edns_set_payload(knot_opt_rr_t *opt_rr, uint16_t payload); + +/*! + * \brief Returns the Extended RCODE stored in the OPT RR. + * + * \warning This function does not check the parameter, so ensure to check it + * before calling the function. It must not be NULL. + * \note There is an assert() for debug checking of the parameter. + * + * \param opt_rr OPT RR structure to get the Extended RCODE from. + * + * \return Extended RCODE. + */ +uint8_t knot_edns_get_ext_rcode(const knot_opt_rr_t *opt_rr); + +/*! + * \brief Sets the Extended RCODE field in the OPT RR. + * + * \warning This function does not check the parameter, so ensure to check it + * before calling the function. It must not be NULL. + * \note There is an assert() for debug checking of the parameter. + * + * \param opt_rr OPT RR structure to set the Extended RCODE to. + * \param ext_rcode Extended RCODE to set. + */ +void knot_edns_set_ext_rcode(knot_opt_rr_t *opt_rr, uint8_t ext_rcode); + +/*! + * \brief Returns the EDNS version stored in the OPT RR. + * + * \warning This function does not check the parameter, so ensure to check it + * before calling the function. It must not be NULL. + * \note There is an assert() for debug checking of the parameter. + * + * \param opt_rr OPT RR structure to get the EDNS version from. + * + * \return EDNS version. + */ +uint8_t knot_edns_get_version(const knot_opt_rr_t *opt_rr); + +/*! + * \brief Sets the EDNS version field in the OPT RR. + * + * \warning This function does not check the parameter, so ensure to check it + * before calling the function. It must not be NULL. + * \note There is an assert() for debug checking of the parameter. + * + * \param opt_rr OPT RR structure to set the EDNS version to. + * \param version EDNS version to set. + */ +void knot_edns_set_version(knot_opt_rr_t *opt_rr, uint8_t version); + +/*! + * \brief Returns the flags stored in the OPT RR. + * + * \warning This function does not check the parameter, so ensure to check it + * before calling the function. It must not be NULL. + * \note There is an assert() for debug checking of the parameter. + * + * \param opt_rr OPT RR structure to get the flags from. + * + * \return EDNS flags. + */ +uint16_t knot_edns_get_flags(const knot_opt_rr_t *opt_rr); + +/*! + * \brief Returns the state of the DO bit in the OPT RR flags. + * + * \param opt_rr OPT RR structure to get the DO bit from. + * + * \return <> 0 if the DO bit is set. + * \return 0 if the DO bit is not set. + */ +int knot_edns_do(const knot_opt_rr_t *opt_rr); + +/*! + * \brief Sets the DO bit in the OPT RR. + * + * \param opt_rr OPT RR structure to set the DO bit in. + */ +void knot_edns_set_do(knot_opt_rr_t *opt_rr); + +/*! + * \brief Adds EDNS Option to the OPT RR. + * + * \param opt_rr OPT RR structure to add the Option to. + * \param code Option code. + * \param length Option data length in bytes. + * \param data Option data. + * + * \retval KNOT_EOK + * \retval KNOT_ENOMEM + */ +int knot_edns_add_option(knot_opt_rr_t *opt_rr, uint16_t code, + uint16_t length, const uint8_t *data); + +/*! + * \brief Checks if the OPT RR contains Option with the specified code. + * + * \param opt_rr OPT RR structure to check for the Option in. + * \param code Option code to check for. + * + * \retval <> 0 if the OPT RR contains Option with Option code \a code. + * \retval 0 otherwise. + */ +int knot_edns_has_option(const knot_opt_rr_t *opt_rr, uint16_t code); + +/*! + * \brief Converts the given OPT RR into wire format. + * + * \param opt_rr OPT RR structure to convert into wire format. + * \param wire Place to put the wire format to. + * \param max_size Maximum space available for the wire format in bytes. + * + * \return Size of the wire format in bytes if successful. + * \retval KNOT_ESPACE + */ +short knot_edns_to_wire(const knot_opt_rr_t *opt_rr, uint8_t *wire, + size_t max_size); + +/*! + * \brief Returns size of the OPT RR in wire format. + * + * \param opt_rr OPT RR to get the size of. + * + * \return Size of the OPT RR in bytes. + */ +short knot_edns_size(knot_opt_rr_t *opt_rr); + +/*! + * \brief Properly destroys the OPT RR structure. + * + * \note Also sets the given pointer to NULL. + */ +void knot_edns_free(knot_opt_rr_t **opt_rr); + +#endif /* _KNOT_EDNS_H_ */ + +/*! @} */ diff --git a/src/libknot/hash/cuckoo-hash-table.c b/src/libknot/hash/cuckoo-hash-table.c new file mode 100644 index 0000000..c5d1c4f --- /dev/null +++ b/src/libknot/hash/cuckoo-hash-table.c @@ -0,0 +1,1688 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> /* defines uint32_t etc */ +#include <assert.h> +#include <pthread.h> +#include <math.h> + +#include <urcu.h> + +#include "util/utils.h" +#include "common.h" +#include "util/debug.h" +#include "hash/cuckoo-hash-table.h" +#include "hash/hash-functions.h" +#include "common/dynamic-array.h" + +/*----------------------------------------------------------------------------*/ +/* Macros and inline functions */ +/*----------------------------------------------------------------------------*/ + +/*! + * \brief Default size table holding information about used hash table cells + * when hashing. + */ +#define RELOCATIONS_DEFAULT 200 + +/*! + * \brief Maximum size table holding information about used hash table cells + * when hashing (just for debug issues). + */ +#define RELOCATIONS_MAX 1000 + +/*! + * \brief Macro for hashing the given key using the universal system. + * + * \param system Universal system to use for the hashing. + * \param key Key to hash. + * \param length Size of the key in bytes. + * \param exp Exponent of the hash table size (the size is a power of 2). + * \param table Hash table index. + * \param gen Universal system generation. + * + * \return Hashed key. + */ +#define HASH(system, key, length, exp, gen, table) \ + us_hash(system, fnv_32_buf(key, length, FNV1_32_INIT), exp, table, gen) + +/*! + * \brief Approximate ratio of hash table size to number of hashed items when 2 + * tables are used. + */ +static const float SIZE_RATIO_2 = 2; + +/*! + * \brief Approximate ratio of hash table size to number of hashed items when 3 + * tables are used. + */ +static const float SIZE_RATIO_3 = 1.15; + +/*! + * \brief Approximate ratio of hash table size to number of hashed items when 4 + * tables are used. + */ +static const float SIZE_RATIO_4 = 1.08; + +/*----------------------------------------------------------------------------*/ + +/*! \brief Flag marking the generation of hash table or its item to be 1. */ +static const uint8_t FLAG_GENERATION1 = 0x1; // 00000001 +/*! \brief Flag marking the generation of hash table or its item to be 2. */ +static const uint8_t FLAG_GENERATION2 = 0x2; // 00000010 +/*! \brief Flag marking both generations. */ +static const uint8_t FLAG_GENERATION_BOTH = 0x3; // 00000011 + +/*! \brief Flag used to mark the table when it's being rehashed. */ +static const uint8_t FLAG_REHASH = 0x4; // 00000100 + +/*----------------------------------------------------------------------------*/ +/*! \brief Clears the table / item flags. */ +static inline void CLEAR_FLAGS(uint8_t *flags) +{ + *flags = (uint8_t)0x0; +} + +/*! \brief Returns the generation stored in the flags. */ +static inline uint8_t GET_GENERATION(uint8_t flags) +{ + return (flags & FLAG_GENERATION_BOTH); +} + +/*! \brief Checks if the generation stored in both flags are the same. */ +static inline int EQUAL_GENERATIONS(uint8_t flags1, uint8_t flags2) +{ + return (GET_GENERATION(flags1) == GET_GENERATION(flags2)); +} + +/*! \brief Checks if the generation stored in the flags is 1. */ +static inline int IS_GENERATION1(uint8_t flags) +{ + return ((flags & FLAG_GENERATION1) != 0); +} + +/*! \brief Sets the generation stored in the flags to 1. */ +static inline void SET_GENERATION1(uint8_t *flags) +{ + *flags = ((*flags) & ~FLAG_GENERATION2) | FLAG_GENERATION1; +} + +/*! \brief Checks if the generation stored in the flags is 2. */ +static inline int IS_GENERATION2(uint8_t flags) +{ + return ((flags & FLAG_GENERATION2) != 0); +} + +/*! \brief Sets the generation stored in the flags to 2. */ +static inline void SET_GENERATION2(uint8_t *flags) +{ + *flags = ((*flags) & ~FLAG_GENERATION1) | FLAG_GENERATION2; +} + +/*! \brief Sets the generation stored in the flags to the given generation. */ +static inline void SET_GENERATION(uint8_t *flags, uint8_t generation) +{ + *flags = ((*flags) & ~FLAG_GENERATION_BOTH) | generation; +} + +/*! \brief Sets the generation stored in the flags to the next one (cyclic). */ +static inline uint8_t SET_NEXT_GENERATION(uint8_t *flags) +{ + return ((*flags) ^= FLAG_GENERATION_BOTH); +} + +/*! \brief Returns the next generation to the one stored in flags (cyclic). */ +static inline uint8_t NEXT_GENERATION(uint8_t flags) +{ + return ((flags & FLAG_GENERATION_BOTH) ^ FLAG_GENERATION_BOTH); +} + +/*! \brief Sets the rehashing flag to the flags. */ +static inline void SET_REHASHING_ON(uint8_t *flags) +{ + *flags = (*flags | FLAG_REHASH); +} + +/*! \brief Removes the rehashing flag from the flags. */ +static inline void SET_REHASHING_OFF(uint8_t *flags) +{ + *flags = (*flags & ~FLAG_REHASH); +} + +/*! \brief Checks if the rehashing flag is set in the flags. */ +static inline int IS_REHASHING(uint8_t flags) +{ + return ((flags & FLAG_REHASH) != 0); +} + +/*----------------------------------------------------------------------------*/ +/* Private functions */ +/*----------------------------------------------------------------------------*/ +/*! + * \brief Returns the exponent of the nearest larger power of two. + */ +static uint get_larger_exp(uint n) +{ + uint res = 0; + while (hashsize(++res) < n) {} + + return res; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Counts the ideal table count and the exponent of those tables' sizes. + * + * Only 3 or 4 hash tables are considered. The setup in which less items are + * wasted is recommended. + * + * \param items Number of items to hash. + * \param table_count Recommended number of tables will be saved here. + * + * \return Exponent of the tables' sizes. + */ +static uint get_table_exp_and_count(uint items, uint *table_count) +{ + // considering only 3 or 4 tables + int exp3 = get_larger_exp((items * SIZE_RATIO_3) / 3); + int exp4 = get_larger_exp(items * SIZE_RATIO_4) - 2; + + if (exp4 < 0) { + exp4 = 1; + } + + dbg_ck("Determining ideal table size...\n"); + dbg_ck("\tNumber of items: %u\n", items); + dbg_ck("\tThree tables: size of one table: %u, total size: %u\n", + hashsize(exp3), 3 * hashsize(exp3)); + dbg_ck("\tFour tables: size of one table: %u, total size: %u\n", + hashsize(exp4), 4 * hashsize(exp4)); + + // we need exponent at least 1 (this is quite ugly..) + if (exp3 == 0) { + exp3 = 1; + } + if (exp4 == 0) { + exp4 = 1; + } + + if (exp3 >= 32 || exp4 >= 32) { + return 0; + } + + if (((hashsize(exp3) * 3) - (items)) < ((hashsize(exp4) * 4) - items)) { + *table_count = 3; + return exp3; + } else { + *table_count = 4; + return exp4; + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Counts the maximum effective item count based on size of the tables. + * + * For 3 tables, the effective utilization should be around 91%. + * For 4 tables it is 97%. + * + * See Fotakis, Dimitris, et al. - Space Efficient Hash Tables with Worst Case + * Constant Access Time. CiteSeerX. 2003 + * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.14.5337 + */ +static uint get_max_table_items(uint table_count, int table_exponent) +{ + assert(table_count == 3 || table_count == 4); + + float coef; + + if (table_count == 3) { + coef = 0.91; + } else { + coef = 0.97; + } + + return (uint)floor((table_count * hashsize(table_exponent)) * coef); +} + +/*----------------------------------------------------------------------------*/ + +static int ck_is_full(const ck_hash_table_t *table) +{ + return (table->items >= get_max_table_items(table->table_count, + table->table_size_exp)); +} + +/*----------------------------------------------------------------------------*/ + +static int ck_stash_is_full(const ck_hash_table_t *table) +{ + return (table->items_in_stash >= STASH_SIZE_MAX); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Clears the given item by assigning a NULL pointer to it. + */ +static inline void ck_clear_item(ck_hash_table_item_t **item) +{ + *item = NULL; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Insert given contents to the hash table item. + */ +static void ck_fill_item(const char *key, size_t key_length, void *value, + uint generation, ck_hash_table_item_t *item) +{ + // must allocate new space for key and value, otherwise it will be lost! + item->key = key; + item->key_length = key_length; + item->value = value; + CLEAR_FLAGS(&item->timestamp); + item->timestamp = generation; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Swaps two hash table items. + */ +static inline void ck_swap_items(ck_hash_table_item_t **item1, + ck_hash_table_item_t **item2) +{ + ck_hash_table_item_t *tmp = *item1; + *item1 = *item2; + *item2 = tmp; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Sets the \a item pointer to the \a to pointer. + */ +static inline void ck_put_item(ck_hash_table_item_t **to, + ck_hash_table_item_t *item) +{ + *to = item; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Checks if the hash was already used twice. + * + * If yes, it means we entered a loop in the hashing process, so we must stop. + * Otherwise it remembers that we used the hash. + * + * \note According to Kirsch, et al. a check that at most one hash was used + * twice should be sufficient. We will retain our version for now. + * + * \param used Array of used table indices (hashes). + * \param hash Hash to check. + * + * \retval -1 if the hash was already used twice. + * \retval -2 if an error occured. + * \retval 0 if the hash was not used twice yet. + */ +static uint ck_check_used_twice(da_array_t *used, uint32_t hash) +{ + uint i = 0, found = 0; + while (i <= da_get_count(used) && found < 2) { + ++i; + if (((uint *)(da_get_items(used)))[i] == hash) { + ++found; + } + } + + if (i <= da_get_count(used) && found == 2) { + dbg_ck_hash("Hashing entered infinite loop.\n"); + return -1; + } else { + if (da_reserve(used, 1) < 0) { + ERR_ALLOC_FAILED; + return -2; + } + ((uint *)da_get_items(used))[da_get_count(used)] = hash; + da_occupy(used, 1); + assert(da_get_count(used) < RELOCATIONS_MAX); + return 0; + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Compares the key of item with the given key. + * + * \param item Item to compare with. + * \param key Key to compare. + * \param length Size of the key in bytes. + * + * \return <> 0 if the keys match. + * \return 0 if they don't. + */ +static inline uint ck_items_match(const ck_hash_table_item_t *item, + const char *key, size_t length) +{ + return (length == item->key_length + && (strncmp(item->key, key, length) == 0)); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Switches the given table number to a randomly chosen other table + * number. + */ +static inline void ck_next_table(uint *table, uint table_count) +{ + uint next; + while ((*table) == (next = knot_quick_rand() % table_count)) {} + *table = next; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Tries to find the given key in the hash table's stash. + * + * \param table Hash table to search in. + * \param key Key to find. + * \param length Size of the key in bytes. + * + * \return Hash table item matching the key or NULL if not found in the stash. + */ +static ck_hash_table_item_t **ck_find_in_stash(const ck_hash_table_t *table, + const char *key, uint length) +{ + ck_stash_item_t *item = table->stash; + while (item != NULL) { + dbg_ck("Comparing item in stash (key: %.*s (size %zu))" + "with searched item (key %.*s (size %u)).\n", + (int)item->item->key_length, item->item->key, + item->item->key_length, (int)length, key, length); + if (ck_items_match(item->item, key, length)) { + return &item->item; + } + item = item->next; + } + + return NULL; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Tries to find item with given key using hash functions from the given + * generation. + * + * \param table Hash table to search in. + * \param key Key to find. + * \param length Size of the key in bytes. + * \param generation Generation of items (table) to use. Items having other + * generation are ignored. + */ +static ck_hash_table_item_t **ck_find_gen(const ck_hash_table_t *table, + const char *key, + size_t length, uint8_t generation) +{ + uint32_t hash; + dbg_ck("Finding item in generation: %u\n", generation); + + // check hash tables + for (uint t = 0; t < table->table_count; ++t) { + hash = HASH(&table->hash_system, key, length, + table->table_size_exp, generation, t); + + dbg_ck("Hash: %u, key: %.*s\n", hash, (int)length, key); + dbg_ck("Table %d, hash: %u, item: %p\n", t + 1, hash, + table->tables[t][hash]); + if (table->tables[t][hash] != NULL) { + dbg_ck("Table %u, key: %.*s, value: %p, key " + "length: %zu\n", + t + 1, (int)table->tables[t][hash]->key_length, + table->tables[t][hash]->key, + table->tables[t][hash]->value, + table->tables[t][hash]->key_length); + } + + if (table->tables[t][hash] && + ck_items_match(table->tables[t][hash], key, length)) { + // found + return &table->tables[t][hash]; + } + } + + // try to find in stash + dbg_ck("Searching in stash...\n"); + + ck_hash_table_item_t **found = + ck_find_in_stash(table, key, length); + + dbg_ck("Found pointer: %p\n", found); + if (found != NULL) { + dbg_ck("Stash, key: %.*s, value: %p, key length: %zu\n", + (int)(*found)->key_length, (*found)->key, + (*found)->value, (*found)->key_length); + } + + // ck_find_in_buffer returns NULL if not found, otherwise pointer to + // item + return found; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Finds item with given key and returns non-constant pointer to pointer + * to the appropriate hash table item. + * + * \param table Hash table to search in. + * \param key Key to find. + * \param length Size of the key in bytes. + */ +static ck_hash_table_item_t **ck_find_item_nc(const ck_hash_table_t *table, + const char *key, size_t length) +{ + // get the generation of the table so that we use the same value + uint8_t generation = table->generation; + + // find item using the table generation's hash functions + ck_hash_table_item_t **found = ck_find_gen(table, key, length, + GET_GENERATION(generation)); + // if rehashing is in progress, try the next generation's functions + if (!found && IS_REHASHING(generation)) { + found = ck_find_gen(table, key, length, + NEXT_GENERATION(generation)); + } + + return found; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Hashes the given item using the given generation. + * + * \param table Hash table where to put the item. + * \param to_hash In: Item to hash. Out: NULL if successful, item that failed + * to hash if not. + * \param free Free place where to put the last moved item when the hasing + * is unsuccessful. + * \param generation Generation of items (table) to be used for hashing. + * + * \retval 0 if successful and no loop occured. + * \retval 1 if a loop occured and the item was inserted to the \a free place. + */ +static int ck_hash_item(ck_hash_table_t *table, ck_hash_table_item_t **to_hash, + ck_hash_table_item_t **free, uint8_t generation) +{ + da_array_t used[table->table_count]; + for (uint i = 0; i < table->table_count; ++i) { + da_initialize(&used[i], RELOCATIONS_DEFAULT, sizeof(uint)); + } + + // hash until empty cell is encountered or until loop appears + + dbg_ck_hash("Hashing key: %.*s of size %zu.\n", + (int)(*to_hash)->key_length, (*to_hash)->key, + (*to_hash)->key_length); + + uint next_table = 0; + + uint32_t hash = HASH(&table->hash_system, (*to_hash)->key, + (*to_hash)->key_length, table->table_size_exp, + generation, next_table); + + dbg_ck_hash("New hash: %u.\n", hash); + assert(hash < hashsize(table->table_size_exp)); + + ((uint *)da_get_items(&used[next_table])) + [da_get_count(&used[next_table])] = hash; + ck_hash_table_item_t **next = &table->tables[next_table][hash]; + dbg_ck_hash("Item to be moved: %p, place in table: %p\n", + *next, next); + ck_hash_table_item_t **moving = to_hash; + + int loop = 0; + + while (*next != NULL) { + dbg_ck_hash("Swapping items to hash: %p and Moving: %p\n", + to_hash, moving); + ck_swap_items(to_hash, moving); // first time it's unnecessary + + // set the generation of the inserted item to the next + SET_GENERATION(&(*moving)->timestamp, generation); + + moving = next; + + dbg_ck_hash("Moving item from table %u, key: %.*s, hash %u ", + next_table + 1, (int)(*moving)->key_length, + (*moving)->key, hash); + + // if rehashing and the 'next' item is from the old generation, + // start from table 1 + if (generation != table->generation && + EQUAL_GENERATIONS((*next)->timestamp, table->generation)) { + next_table = 0; + } else { + ck_next_table(&next_table, table->table_count); + } + + hash = HASH(&table->hash_system, (*next)->key, + (*next)->key_length, table->table_size_exp, + generation, next_table); + + next = &table->tables[next_table][hash]; + + dbg_ck_hash("to table %u, hash %u, item: %p, place: %p\n", + next_table + 1, hash, *next, next); + + if ((*next) != NULL) { + dbg_ck_hash("Table %u, hash: %u, key: %.*s\n", + next_table + 1, hash, + (int)(*next)->key_length, (*next)->key); + } + + // check if this cell wasn't already used in this item's hashing + if (ck_check_used_twice(&used[next_table], hash) != 0) { + next = free; + loop = -1; + break; + } + } + + dbg_ck_hash("Putting pointer %p (*moving) to item %p (next).\n", + *moving, next); + + ck_put_item(next, *moving); + // set the new generation for the inserted item + SET_GENERATION(&(*next)->timestamp, generation); + dbg_ck_hash("Putting pointer %p (*old) to item %p (moving).\n", + *to_hash, moving); + + ck_put_item(moving, *to_hash); + + // set the new generation for the inserted item + SET_GENERATION(&(*moving)->timestamp, generation); + *to_hash = NULL; + + for (uint i = 0; i < table->table_count; ++i) { + da_destroy(&used[i]); + } + + return loop; +} + +/*----------------------------------------------------------------------------*/ + +static void ck_rollback_rehash(ck_hash_table_t *table) +{ + // set old generation in tables + for (int i = 0; i < hashsize(table->table_size_exp); ++i) { + // no need for locking - timestamp is not used in lookup + // and two paralel insertions (and thus rehashings) are + // impossible + for (uint t = 0; t < table->table_count; ++t) { + if (table->tables[t][i] != NULL) { + SET_GENERATION(&table->tables[t][i]->timestamp, + table->generation); + } + } + } + + // set old generation in stash + ck_stash_item_t *item = table->stash; + while (item != NULL) { + assert(item->item != NULL); + SET_GENERATION(&item->item->timestamp, table->generation); + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adds the given item to the hash table's stash. + * + * \param table Hash table to add the item to. + * \param item Item to add. + * + * \retval 0 if successful. + * \retval -1 if an error occured. + */ +int ck_add_to_stash(ck_hash_table_t *table, ck_hash_table_item_t *item) +{ + ck_stash_item_t *new_item + = (ck_stash_item_t *)malloc(sizeof(ck_stash_item_t)); + if (new_item == NULL) { + ERR_ALLOC_FAILED; + return -1; + } + + new_item->item = item; + new_item->next = table->stash; + table->stash = new_item; + + dbg_ck_hash("First item in stash (now inserted): key: %.*s (size %zu)" + ", value: %p\n", (int)table->stash->item->key_length, + table->stash->item->key, table->stash->item->key_length, + table->stash->item->value); + + // increase count of items in stash + ++table->items_in_stash; + + return 0; +} + +/*----------------------------------------------------------------------------*/ + +static int ck_new_table(ck_hash_table_item_t ***table, int exp) +{ + *table = (ck_hash_table_item_t **) + malloc(hashsize(exp) * sizeof(ck_hash_table_item_t *)); + if (*table == NULL) { + ERR_ALLOC_FAILED; + return -1; + } + + // set to 0 + memset(*table, 0, hashsize(exp) * sizeof(ck_hash_table_item_t *)); + + return 0; +} + +/*----------------------------------------------------------------------------*/ +/* Public functions */ +/*----------------------------------------------------------------------------*/ + +ck_hash_table_t *ck_create_table(uint items) +{ + ck_hash_table_t *table = + (ck_hash_table_t *)malloc(sizeof(ck_hash_table_t)); + + if (table == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + + memset(table, 0, sizeof(ck_hash_table_t)); + + // determine ideal size of one table in powers of 2 and save the + // exponent + table->table_size_exp = get_table_exp_and_count(items, + &table->table_count); + assert(table->table_size_exp <= 32); + + if (table->table_size_exp == 0) { + dbg_ck("Failed to count exponent of the hash table.\n"); + return NULL; + } + + dbg_ck("Creating hash table for %u items.\n", items); + dbg_ck("Exponent: %u, number of tables: %u\n ", + table->table_size_exp, table->table_count); + dbg_ck("Table size: %u items, each %zu bytes, total %zu bytes\n", + hashsize(table->table_size_exp), + sizeof(ck_hash_table_item_t *), + hashsize(table->table_size_exp) + * sizeof(ck_hash_table_item_t *)); + + // create tables + for (uint t = 0; t < table->table_count; ++t) { + dbg_ck("Creating table %u...\n", t); + if (ck_new_table(&table->tables[t], table->table_size_exp) + != 0) { + for (uint i = 0; i < t; ++i) { + free(table->tables[i]); + } + free(table); + return NULL; + } + } + + assert(table->stash == NULL); + assert(table->hashed == NULL); + assert(table->items == 0); + assert(table->items_in_stash == 0); + assert(table->table_count == MAX_TABLES + || table->tables[table->table_count] == NULL); + + // initialize rehash/insert mutex + pthread_mutex_init(&table->mtx_table, NULL); + + // set the generation to 1 and initialize the universal system + CLEAR_FLAGS(&table->generation); + SET_GENERATION1(&table->generation); + + us_initialize(&table->hash_system); + + return table; +} + +/*----------------------------------------------------------------------------*/ + +void ck_destroy_table(ck_hash_table_t **table, void (*dtor_value)(void *value), + int delete_key) +{ + assert(table); + assert(*table); + pthread_mutex_lock(&(*table)->mtx_table); + + // destroy items in tables + for (uint i = 0; i < hashsize((*table)->table_size_exp); ++i) { + for (uint t = 0; t < (*table)->table_count; ++t) { + if ((*table)->tables[t][i] != NULL) { + if (dtor_value) { + dtor_value( + (*table)->tables[t][i]->value); + } + if (delete_key != 0) { + free( + (void *)(*table)->tables[t][i]->key); + } + free((void *)(*table)->tables[t][i]); + } + } + } + + // destroy items in stash +// ck_hash_table_item_t **stash = +// ((ck_hash_table_item_t **)(da_get_items(&(*table)->stash))); +// for (uint i = 0; i < da_get_count(&(*table)->stash); ++i) { +// assert(stash[i] != NULL); +// if (dtor_value) { +// dtor_value(stash[i]->value); +// } +// if (delete_key != 0) { +// free((void *)stash[i]->key); +// } +// free((void *)stash[i]); +// } + ck_stash_item_t *item = (*table)->stash; + while (item != NULL) { + // disconnect the item + (*table)->stash = item->next; + /*! \todo Investigate this. */ + assert(item->item != NULL); + + if (dtor_value) { + dtor_value(item->item->value); + } + if (delete_key) { + free((void *)item->item->key); + } + + free((void *)item->item); + free(item); + item = (*table)->stash; + } + + // deallocate tables + for (uint t = 0; t < (*table)->table_count; ++t) { + free((*table)->tables[t]); + } + // destroy stash +// da_destroy(&(*table)->stash); + + pthread_mutex_unlock(&(*table)->mtx_table); + // destroy mutex, assuming that here noone will lock the mutex again + pthread_mutex_destroy(&(*table)->mtx_table); + + free(*table); + (*table) = NULL; +} + +void ck_table_free(ck_hash_table_t **table) +{ + if (table == NULL || *table == NULL) { + return; + } + + pthread_mutex_lock(&(*table)->mtx_table); + + ck_stash_item_t *item = (*table)->stash; + while (item != NULL) { + // disconnect the item + (*table)->stash = item->next; + free(item); + item = (*table)->stash; + } + + // deallocate tables + for (uint t = 0; t < (*table)->table_count; ++t) { + free((*table)->tables[t]); + } + + pthread_mutex_unlock(&(*table)->mtx_table); + pthread_mutex_destroy(&(*table)->mtx_table); + + free(*table); + (*table) = NULL; +} + +int ck_resize_table(ck_hash_table_t *table) +{ + dbg_ck("Resizing hash table.\n"); + + /* + * Easiest is just to increment the exponent, resulting in doubling + * the table sizes. This is not very memory-effective, but should do + * the job. + */ + + if (table->table_size_exp == 31) { + dbg_ck("Hash tables achieved max size (exponent 31).\n"); + return -1; + } + + ck_hash_table_item_t **tables_new[MAX_TABLES]; + ck_hash_table_item_t **tables_old[MAX_TABLES]; + int exp_new = table->table_size_exp + 1; + + dbg_ck("New tables exponent: %d\n", exp_new); + + for (int t = 0; t < table->table_count; ++t) { + if (ck_new_table(&tables_new[t], exp_new) != 0) { + dbg_ck("Failed to create new table.\n"); + for (int i = 0; i < t; ++i) { + free(tables_new[i]); + } + return -1; + } + } + + dbg_ck("Created new tables, copying data to them.\n"); + + for (int t = 0; t < table->table_count; ++t) { + size_t old_size = hashsize(table->table_size_exp) + * sizeof(ck_hash_table_item_t *); + + // copy the old table items + dbg_ck("Copying to: %p, from %p, size: %zu\n", + tables_new[t], table->tables[t], old_size); + memcpy(tables_new[t], table->tables[t], old_size); + // set the rest to 0 + dbg_ck("Setting to 0 from %p, size %zu\n", + tables_new[t] + hashsize(table->table_size_exp), + (hashsize(exp_new) * sizeof(ck_hash_table_item_t *)) + - old_size); + memset(tables_new[t] + hashsize(table->table_size_exp), 0, + (hashsize(exp_new) * sizeof(ck_hash_table_item_t *)) + - old_size); + } + + dbg_ck("Done, switching the tables and running rehash.\n"); + + + memcpy(tables_old, table->tables, + MAX_TABLES * sizeof(ck_hash_table_item_t **)); + memcpy(table->tables, tables_new, + MAX_TABLES * sizeof(ck_hash_table_item_t **)); + + table->table_size_exp = exp_new; + + // delete the old tables + for (int t = 0; t < table->table_count; ++t) { + free(tables_old[t]); + } + + return ck_rehash(table); + //return 0; +} + +int ck_insert_item(ck_hash_table_t *table, const char *key, + size_t length, void *value) +{ + // lock mutex to avoid write conflicts + pthread_mutex_lock(&table->mtx_table); + + assert(value != NULL); + + dbg_ck_hash("Inserting item with key: %.*s.\n", (int)length, key); + dbg_ck_hash_hex(key, length); + dbg_ck_hash("\n"); + + // create item structure and fill in the given data, key won't be copied + ck_hash_table_item_t *new_item = + (ck_hash_table_item_t *)malloc((sizeof(ck_hash_table_item_t))); + ck_fill_item(key, length, value, GET_GENERATION(table->generation), + new_item); + + // check if the table is not full; if yes, resize and rehash! + if (ck_is_full(table)) { + dbg_ck("Table is full, resize needed.\n"); + if (ck_resize_table(table) != 0) { + dbg_ck("Failed to resize hash table!\n"); + return -1; + } + } + + // there should be at least 2 free places + //assert(da_try_reserve(&table->stash, 2) == 0); + //da_reserve(&table->stash, 1); + ck_hash_table_item_t *free_place = NULL; + if (ck_hash_item(table, &new_item, &free_place, + table->generation) != 0) { + + dbg_ck("Adding item with key %.*s to stash.\n", + (int)free_place->key_length, free_place->key); + + // maybe some limit on the stash and rehash if full + if (ck_add_to_stash(table, free_place) != 0) { + dbg_ck_hash("Could not add item to stash!!\n"); + assert(0); + } + + if (ck_stash_is_full(table)) { + dbg_ck("Stash is full, resize needed.\n"); + if (ck_resize_table(table) != 0) { + dbg_ck("Failed to resize hash table!\n"); + return -1; + } + } + } + + ++table->items; + pthread_mutex_unlock(&table->mtx_table); + return 0; +} + +/*----------------------------------------------------------------------------*/ + +const ck_hash_table_item_t *ck_find_item(const ck_hash_table_t *table, + const char *key, size_t length) +{ + dbg_ck("ck_find_item(), key: %.*s, size: %zu\n", + (int)length, key, length); + + ck_hash_table_item_t **found = ck_find_item_nc(table, key, length); + + return (found == NULL) ? NULL : rcu_dereference(*found); +} + +/*----------------------------------------------------------------------------*/ + +int ck_update_item(const ck_hash_table_t *table, const char *key, size_t length, + void *new_value, void (*dtor_value)(void *value)) +{ + rcu_read_lock(); // is needed? + + assert(new_value != NULL); + + ck_hash_table_item_t **item = ck_find_item_nc(table, key, length); + + if (item == NULL || (*item) == NULL) { + return -1; + } + + void *old = rcu_xchg_pointer(&(*item)->value, new_value); + rcu_read_unlock(); + + synchronize_rcu(); + if (dtor_value) { + dtor_value(old); + } + + return 0; +} + +/*----------------------------------------------------------------------------*/ + +int ck_delete_item(const ck_hash_table_t *table, const char *key, size_t length, + void (*dtor_value)(void *value), int delete_key) +{ + rcu_read_lock(); // is needed? + ck_hash_table_item_t **place = ck_find_item_nc(table, key, length); + + if (place == NULL) { + return -1; + } + + ck_hash_table_item_t *item = *place; + + assert(item != NULL); + + ck_put_item(place, NULL); + rcu_read_unlock(); + + synchronize_rcu(); + if (dtor_value) { + dtor_value(item->value); + } + item->value = NULL; + if (delete_key != 0) { + free((void *)item->key); + } + free(item); + + return 0; +} + +/*----------------------------------------------------------------------------*/ + +ck_hash_table_item_t *ck_remove_item(ck_hash_table_t *table, const char *key, + size_t length) +{ + ck_hash_table_item_t **place = ck_find_item_nc(table, key, length); + if (place == NULL) { + return NULL; + } + + ck_hash_table_item_t *item = *place; + *place = NULL; + return item; +} + +/*----------------------------------------------------------------------------*/ + +int ck_shallow_copy(const ck_hash_table_t *from, ck_hash_table_t **to) +{ + if (from == NULL || to == NULL) { + return -1; + } + + *to = (ck_hash_table_t *)malloc(sizeof(ck_hash_table_t)); + + if (*to == NULL) { + ERR_ALLOC_FAILED; + return -2; + } + memset(*to, 0, sizeof(ck_hash_table_t)); + + // copy table count and table size exponent + (*to)->table_size_exp = from->table_size_exp; + (*to)->table_count = from->table_count; + assert((*to)->table_size_exp <= 32); + + dbg_ck("Creating hash table for %u items.\n", from->table_count); + dbg_ck("Exponent: %u, number of tables: %u\n ", + (*to)->table_size_exp, (*to)->table_count); + dbg_ck("Table size: %u items, each %zu bytes, total %zu bytes\n", + hashsize((*to)->table_size_exp), + sizeof(ck_hash_table_item_t *), + hashsize((*to)->table_size_exp) + * sizeof(ck_hash_table_item_t *)); + + // create tables + for (uint t = 0; t < (*to)->table_count; ++t) { + dbg_ck("Creating table %u...\n", t); + (*to)->tables[t] = (ck_hash_table_item_t **)malloc( + hashsize((*to)->table_size_exp) + * sizeof(ck_hash_table_item_t *)); + if ((*to)->tables[t] == NULL) { + ERR_ALLOC_FAILED; + for (uint i = 0; i < t; ++i) { + free((*to)->tables[i]); + } + free(*to); + return -2; + } + + // copy the table + memcpy((*to)->tables[t], from->tables[t], + hashsize((*to)->table_size_exp) + * sizeof(ck_hash_table_item_t *)); + } + + // copy the stash - we must explicitly copy each stash item, but do not + // copy the ck_hash_table_item_t within them. + ck_stash_item_t *si = from->stash; + ck_stash_item_t **pos = &(*to)->stash; + dbg_ck_verb(stderr, "Copying hash table stash.\n"); + while (si != NULL) { + ck_stash_item_t *si_new = (ck_stash_item_t *) + malloc(sizeof(ck_stash_item_t)); + if (si_new == NULL) { + ERR_ALLOC_FAILED; + // delete tables + for (uint i = 0; i < (*to)->table_count; ++i) { + free((*to)->tables[i]); + } + // delete created stash items + si_new = (*to)->stash; + while (si_new != NULL) { + ck_stash_item_t *prev = si_new; + si_new = si_new->next; + free(prev); + } + free(*to); + return -2; + } + + dbg_ck("Copying stash item: %p with item %p, ", si, si->item); + dbg_ck("key: %.*s\n", (int)si->item->key_length, si->item->key); + + si_new->item = si->item; + *pos = si_new; + pos = &si_new->next; + si = si->next; + + + dbg_ck("Old stash item: %p with item %p, ", si, + ((si == NULL) ? NULL : si->item)); + if (si != NULL) { + dbg_ck("key: %.*s\n", (int)si->item->key_length, si->item->key); + } else { + dbg_ck("\n"); + } + dbg_ck("New stash item: %p with item %p, ", si_new, + si_new->item); + dbg_ck("key: %.*s\n", (int)si_new->item->key_length, + si_new->item->key); + } + + *pos = NULL; + + // there should be no item being hashed right now + /*! \todo This operation should not be done while inserting / rehashing. + */ + assert(from->hashed == NULL); + (*to)->hashed = NULL; + + // initialize rehash/insert mutex + pthread_mutex_init(&(*to)->mtx_table, NULL); + + // copy the generation + (*to)->generation = from->generation; + + // copy the hash functions + memcpy(&(*to)->hash_system, &from->hash_system, sizeof(us_system_t)); + + return 0; +} + +/*----------------------------------------------------------------------------*/ + +int ck_apply(ck_hash_table_t *table, + void (*function)(ck_hash_table_item_t *item, void *data), + void *data) +{ + if (table == NULL || function == NULL) { + return -1; + } + + /*! \todo Ensure that no insertion nor rehash is made during applying.*/ + + // apply the function to all items in all tables + for (int t = 0; t < table->table_count; ++t) { + for (int i = 0; i < hashsize(table->table_size_exp); ++i) { + function(table->tables[t][i], data); + } + } + + // apply the function to the stash items + ck_stash_item_t *si = table->stash; + while (si != NULL) { + function(si->item, data); + si = si->next; + } + + return 0; +} + +/*----------------------------------------------------------------------------*/ + +int ck_rehash(ck_hash_table_t *table) +{ + dbg_ck_hash("Rehashing items in table.\n"); + SET_REHASHING_ON(&table->generation); + + ck_stash_item_t *free_stash_items = NULL; + + do { + // 1) Rehash items from stash + dbg_ck_rehash("Rehashing items from stash.\n"); + ck_stash_item_t *item = table->stash; + ck_stash_item_t **item_place = &table->stash; + // terminate when at the end; this way the newly added items + // (added to the beginning) will be properly ignored + while (item != NULL) { + dbg_ck_rehash("Rehashing item with " + "key (length %zu): %.*s, generation: %hu, " + "table generation: %hu.\n", item->item->key_length, + (int)item->item->key_length, item->item->key, + GET_GENERATION( + item->item->timestamp), + GET_GENERATION(table->generation)); + + // put the hashed item to the prepared space + table->hashed = item->item; + item->item = NULL; + // we may use the place in the stash item as the free + // place for rehashing + if (ck_hash_item(table, &table->hashed, &item->item, + NEXT_GENERATION(table->generation)) != 0) { + // the free place was used + assert(item->item != NULL); + // we may leave the item there (in the stash) + assert(EQUAL_GENERATIONS(item->item->timestamp, + NEXT_GENERATION(table->generation))); + //assert(item->item == table->hashed); + + item_place = &item->next; + item = item->next; + } else { + // the free place should be free + assert(item->item == NULL); + // and the item should be hashed too +// assert(table->hashed == NULL); + + // fix the pointer from the previous hash item + *item_place = item->next; + // and do not change the item place pointer + + // put the stash item into list of free stash + // items + item->next = free_stash_items; + free_stash_items = item; + + item = *item_place; + } + } + + // 2) Rehash items from tables + + // in case of failure, save the item in a temp variable + // which will be put to the stash + ck_hash_table_item_t *free = NULL; + assert(table->hashed == NULL); +// ck_hash_table_item_t *old = table->hashed; + + for (uint t = 0; t < table->table_count; ++t) { + uint rehashed = 0; + + dbg_ck_rehash("Rehashing table %d.\n", t); + + while (rehashed < hashsize(table->table_size_exp)) { + + // if item's generation is the new generation, + // skip + if (table->tables[t][rehashed] == NULL + || !(EQUAL_GENERATIONS( + table->tables[t][rehashed]->timestamp, + table->generation))) { + dbg_ck_rehash("Skipping item.\n"); + ++rehashed; + continue; + } + + dbg_ck_rehash("Rehashing item with hash %u, " + "key (length %zu): %.*s, generation: %hu, " + "table generation: %hu.\n", rehashed, + table->tables[t][rehashed]->key_length, + (int)(table->tables[t][rehashed]->key_length), + table->tables[t][rehashed]->key, + GET_GENERATION( + table->tables[t][rehashed]->timestamp), + GET_GENERATION(table->generation)); + + // otherwise copy the item for rehashing + ck_put_item(&table->hashed, table->tables[t][rehashed]); + // clear the place so that this item will not + // get rehashed again + ck_clear_item(&table->tables[t][rehashed]); + + dbg_ck_rehash("Table generation: %hu, next " + "generation: %hu.\n", + GET_GENERATION(table->generation), + NEXT_GENERATION(table->generation)); + + if (ck_hash_item(table, &table->hashed, &free, + NEXT_GENERATION(table->generation)) != 0) { + // loop occured + dbg_ck_hash("Hashing entered a loop." + "\n"); + dbg_ck_rehash("Item with key %.*s " + "inserted into the free slot.\n", + free->key_length, free->key); + + //assert(old == free); + + // put the item into the stash, but + // try the free stash items first + if (free_stash_items != NULL) { + // take first + ck_stash_item_t *item = + free_stash_items; + free_stash_items = item->next; + + item->item = free; + item->next = table->stash; + table->stash = item; + } else { + if (ck_add_to_stash(table, free) + != 0) { + ck_rollback_rehash( + table); + } + } + + free = NULL; + table->hashed = NULL; + } + ++rehashed; + } + } + + dbg_ck_rehash("Old table generation: %u\n", + GET_GENERATION(table->generation)); + // rehashing completed, switch generation of the table + SET_NEXT_GENERATION(&table->generation); + dbg_ck_rehash("New table generation: %u\n", + GET_GENERATION(table->generation)); + // generate new hash functions for the old generation + dbg_ck_rehash("Generating coeficients for generation: %u\n", + NEXT_GENERATION(table->generation)); + us_next(&table->hash_system, + NEXT_GENERATION(table->generation)); + + } while (false /*! \todo Add proper condition!! */); + + SET_REHASHING_OFF(&table->generation); + + assert(table->hashed == NULL); + + + while (free_stash_items != NULL) { + ck_stash_item_t *item = free_stash_items; + free_stash_items = item->next; + assert(item->item == NULL); + free(item); + } + + return 0; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Rehashes the whole table. + * + * \param table Hash table to be rehashed. + * + * \note While rehashing no item should be inserted as it will result in a + * deadlock. + * + * \retval 0 No error. + * \retval -1 Rehashing failed. Some items may have been already moved and the + * rehashing flag remains set. + * + * \todo What if the stash is reallocated during ck_hash_item()? We'd be using + * the old stash for saving items! The old stash would not get deallocated + * (due to RCU - maybe put some rcu_read_lock() here), but the item + * would not be saved into the new stash! + * Maybe add a function for getting a pointer to particular item from + * the dynamic array and protect it using rcu_read_lock(). + * Other option: Do not use pointer to an item in stash in the call to + * ck_hash_item(). Use some new place & put the item to the stash + * afterwards, protecting it using rcu_read_lock() and rcu_assign_pointer. + */ +//int ck_rehash(ck_hash_table_t *table) +//{ +// dbg_ck_rehash("Rehashing items in table.\n"); +// SET_REHASHING_ON(&table->generation); + +// // we already have functions for the next generation, begin rehashing +// // we wil use the last item in the buffer as free cell for hashing +// assert(da_try_reserve(&table->stash, 1) == 0); +// ck_hash_table_item_t *old = (ck_hash_table_item_t *) +// (malloc(sizeof(ck_hash_table_item_t))); + +// do { +// dbg_ck_hash("Rehash!\n"); + +// if (da_get_count(&table->stash) > STASH_SIZE) { +// dbg_ck_hash("STASH RESIZED!!! (new stash size: %d)\n", +// da_get_count(&table->stash)); +// } + +// // rehash items from stash, starting from the last old item +// int stash_i = da_get_count(&table->stash) - 1; +// while (stash_i >= 0) { +// // if item's generation is the new generation, skip +// if (STASH_ITEMS(&table->stash)[stash_i] == NULL +// || !(EQUAL_GENERATIONS(STASH_ITEMS(&table->stash) +// [stash_i]->timestamp, +// table->generation))) { +// dbg_ck_rehash("Skipping item.\n"); +// --stash_i; +// continue; +// } + +// dbg_ck_rehash("Rehashing item from buffer position %u" +// ", key (length %u): %.*s, generation: " +// "%hu, table generation: %hu.\n", +// stash_i, +// STASH_ITEMS(&table->stash)[stash_i]->key_length, +// (int)STASH_ITEMS(&table->stash)[stash_i]->key_length, +// STASH_ITEMS(&table->stash)[stash_i]->key, +// GET_GENERATION( +// STASH_ITEMS(&table->stash)[stash_i]->timestamp), +// GET_GENERATION(table->generation)); + +// // otherwise copy the item for rehashing +// ck_put_item(&old, STASH_ITEMS(&table->stash)[stash_i]); +// // clear the place so that this item will not get +// // rehashed again +// ck_clear_item(&STASH_ITEMS(&table->stash)[stash_i]); +// da_release(&table->stash, 1); + +// // there should be at least one place in the stash +// assert(da_try_reserve(&table->stash, 1) == 0); +// da_reserve(&table->stash, 1); + +// assert(STASH_ITEMS(&table->stash)[stash_i] == NULL); + +// // and start rehashing +// if (ck_hash_item(table, &old, +// &STASH_ITEMS(&table->stash)[stash_i], +// NEXT_GENERATION(table->generation)) != 0) { +// // loop occured +// dbg_ck_hash("Hashing entered a loop.\n"); + +// dbg_ck_rehash("Item with key %.*s inserted " +// "into the stash on position %d.\n", +// STASH_ITEMS(&table->stash) +// [stash_i]->key_length, +// STASH_ITEMS(&table->stash) +// [stash_i]->key, +// da_get_count(&table->stash)); + +// // hashing unsuccessful, the item was inserted +// // into the stash +// da_occupy(&table->stash, 1); +// assert(STASH_ITEMS(&table->stash)[stash_i] +// != NULL); + +// // if only one place left, resize the stash +// // TODO: Why??? +// if (da_reserve(&table->stash, 2) < 0) { +// // stash could not be resized => !!! +// dbg_ck_hash("Failed to rehash items " +// "from " +// "table, no other rehash possible!\n"); +// // so rollback +// ck_rollback_rehash(table); +// // clear the 'old' item +// ck_clear_item(&old); +// return -1; +// } +// } + +// // clear the 'old' item +// ck_clear_item(&old); +// // decrement the index +// --stash_i; +// } + +// uint i = 0; +// while (i < da_get_count(&table->stash)) { +// assert(STASH_ITEMS(&table->stash)[i] != NULL); +// ++i; +// } +// dbg_ck_hash("OK\n"); +// assert(da_try_reserve(&table->stash, 1) == 0); +// assert(STASH_ITEMS(&table->stash)[da_get_count(&table->stash)] +// == NULL); + +// // rehash items from hash tables +// for (uint t = TABLE_FIRST; +// t <= TABLE_LAST(table->table_count); ++t) { +// dbg_ck_rehash("Rehashing items from table %d.\n", +// t + 1); +// uint rehashed = 0; + +// while (rehashed < hashsize(table->table_size_exp)) { + +// // if item's generation is the new generation, +// // skip +// if (table->tables[t][rehashed] == NULL +// || !(EQUAL_GENERATIONS( +// table->tables[t][rehashed]->timestamp, +// table->generation))) { +// dbg_ck_rehash("Skipping item.\n"); +// ++rehashed; +// continue; +// } + +// dbg_ck_rehash("Rehashing item with hash %u, " +// "key (length %u): %.*s, generation: %hu, " +// "table generation: %hu.\n", rehashed, +// table->tables[t][rehashed]->key_length, +// (int)(table->tables[t][rehashed]->key_length), +// table->tables[t][rehashed]->key, +// GET_GENERATION( +// table->tables[t][rehashed]->timestamp), +// GET_GENERATION(table->generation)); + +// // otherwise copy the item for rehashing +// ck_put_item(&old, table->tables[t][rehashed]); +// // clear the place so that this item will not +// // get rehashed again +// ck_clear_item(&table->tables[t][rehashed]); + +// dbg_ck_rehash("Table generation: %hu, next " +// "generation: %hu.\n", +// GET_GENERATION(table->generation), +// NEXT_GENERATION(table->generation)); + +// // and start rehashing +// assert(&old != &STASH_ITEMS(&table->stash)[ +// da_get_count(&table->stash)]); +// assert(da_try_reserve(&table->stash, 1) == 0); +// da_reserve(&table->stash, 1); + +// if (ck_hash_item(table, &old, +// &STASH_ITEMS(&table->stash)[ +// da_get_count(&table->stash)], +// NEXT_GENERATION(table->generation)) != 0) { +// // loop occured +// dbg_ck_hash("Hashing entered a loop." +// "\n"); +// dbg_ck_rehash("Item with key %.*s " +// "inserted into the stash on position " +// "%d.\n", STASH_ITEMS(&table->stash)[ +// da_get_count(&table->stash)] +// ->key_length, +// STASH_ITEMS(&table->stash)[ +// da_get_count(&table->stash)]->key, +// da_get_count(&table->stash)); + +// assert(STASH_ITEMS(&table->stash)[ +// da_get_count(&table->stash)] != NULL); +// // loop occured, the item is already at +// // its new place in the buffer, so just +// // increment the index +// da_occupy(&table->stash, 1); + +// // if only one place left, resize the +// // stash TODO: Why? +// if (da_reserve(&table->stash, 2) < 0) { +// // stash could not be resized +// dbg_ck_hash("Failed to rehash" +// " items from table, no other " +// "rehash possible!\n"); +// // so rollback +// ck_rollback_rehash(table); +// // clear the 'old' item +// ck_clear_item(&old); +// return -1; +// } +// } +// ++rehashed; +// } +// } + +// dbg_ck_rehash("Old table generation: %u\n", +// GET_GENERATION(table->generation)); +// // rehashing completed, switch generation of the table +// SET_NEXT_GENERATION(&table->generation); +// dbg_ck_rehash("New table generation: %u\n", +// GET_GENERATION(table->generation)); +// // generate new hash functions for the old generation +// dbg_ck_rehash("Generating coeficients for generation: %u\n", +// NEXT_GENERATION(table->generation)); +// us_next(NEXT_GENERATION(table->generation)); + +// // repeat rehashing while there are more items in the stash than +// // its initial size +// if (da_get_count(&table->stash) > STASH_SIZE) { +// dbg_ck_rehash("Rehashing again!\n"); +// } +// } while (da_get_count(&table->stash) > STASH_SIZE); + +// SET_REHASHING_OFF(&table->generation); + +// return 0; +//} + +/*----------------------------------------------------------------------------*/ + +void ck_dump_table(const ck_hash_table_t *table) +{ +#ifdef CUCKOO_DEBUG + uint i = 0; + dbg_ck("----------------------------------------------\n"); + dbg_ck("Hash table dump:\n\n"); + dbg_ck("Size of each table: %u\n\n", hashsize(table->table_size_exp)); + + for (uint t = 0; t < table->table_count; ++t) { + dbg_ck("Table %d:\n", t + 1); + + for (i = 0; i < hashsize(table->table_size_exp); i++) { + dbg_ck("Hash: %u, Key: %.*s, Value: %p.\n", i, + (int)(table->tables[t])[i]->key_length, + (table->tables[t])[i]->key, + (table->tables[t])[i]->value); + } + } + + dbg_ck("Stash:\n"); +// for (i = 0; i < da_get_count(&table->stash); ++i) { +// dbg_ck("Index: %u, Key: %.*s Value: %p.\n", i, +// ((ck_hash_table_item_t **) +// da_get_items(&table->stash))[i]->key_length, +// ((ck_hash_table_item_t **) +// da_get_items(&table->stash))[i]->key, +// ((ck_hash_table_item_t **) +// da_get_items(&table->stash))[i]->value); +// } + ck_stash_item_t *item = table->stash; + while (item != NULL) { + dbg_ck("Hash: %u, Key: %.*s, Value: %p.\n", i, + (int)item->item->key_length, item->item->key, + item->item->value); + item = item->next; + } + + dbg_ck("\n"); +#endif +} diff --git a/src/libknot/hash/cuckoo-hash-table.h b/src/libknot/hash/cuckoo-hash-table.h new file mode 100644 index 0000000..dd78294 --- /dev/null +++ b/src/libknot/hash/cuckoo-hash-table.h @@ -0,0 +1,333 @@ +/*! + * \file cuckoo-hash-table.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Implementation of Cuckoo hashing scheme. + * + * Uses d-ary Cuckoo hashing with stash. + * + * \todo Maybe provide some way to resize the whole table if the number of items + * grows too much. + * \todo Check size of integers, the table size may be larger than unsigned int. + * \todo Maybe do not return ck_hash_table_item from ck_find_item(), but only + * its value. + * \todo When hashing an item, only the first table is tried for this item. + * We may try all tables. (But it is not neccessary.) + * + * \addtogroup hashing + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_CUCKOO_HASH_TABLE_H_ +#define _KNOT_CUCKOO_HASH_TABLE_H_ + +#include <stdint.h> /* uint32_t */ +#include <stdlib.h> /* size_t */ +#include <pthread.h> + +#include "hash/universal-system.h" +#include "common/dynamic-array.h" + +/*----------------------------------------------------------------------------*/ + +/*! \brief Macro for getting one hash table size. */ +#define hashsize(n) ((uint32_t)1 << (n)) + +/*! + * \brief Max number of hash tables - must be the same as number of the hash + * functions in each generation of the universal system. + */ +#define MAX_TABLES US_FNC_COUNT + +/*! \brief Default stash size. */ +static const uint STASH_SIZE = 10; + +/*! \brief Maximum stash size. When achieved, rehashing is needed. */ +static const uint STASH_SIZE_MAX = 30; + +/*----------------------------------------------------------------------------*/ +/* Public structures */ +/*----------------------------------------------------------------------------*/ +/*! + * \brief Structure for storing the hashed data. + */ +struct ck_hash_table_item { + const char *key; /*!< Key of the item, used for hashing. */ + + size_t key_length; /*!< Length of the key in octets. */ + + void *value; /*!< The actual item stored in the table. */ + + /*! + * \brief Flags. Currently used for keeping the generation of the item, + * i.e. the generation of the functions used for hashing this + * item. + * + * Form: 000000xy; + * xy - generation; may be 01 (1) or 10 (2). + */ + uint8_t timestamp; +}; + +typedef struct ck_hash_table_item ck_hash_table_item_t; + +struct ck_stash_item { + ck_hash_table_item_t *item; + struct ck_stash_item *next; +}; + +typedef struct ck_stash_item ck_stash_item_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Hash table structure which uses cuckoo hashing. + * + * Keys are expected to be strings of characters (char *), not necesarily + * null-terminated. It uses the Fowler/Noll/Vo (FNV) hash function to + * obtain a 32bit unsigned integer from the character data and a function + * randomly chosen from an universal system (see universal-system.h) to obtain + * the final hash. The FNV hash was taken from + * http://home.comcast.net/~bretm/hash/6.html and the universal system is + * constructed according to Katajainen J., Lykke M., Experiments with universal + * hashing (obtained from + * http://www.diku.dk/OLD/publikationer/tekniske.rapporter/rapporter/96-08.pdf). + * + * The table uses either 3-ary or 4-ary cuckoo hashing (and thus 3 or 4 tables) + * with stash, according to the number of items provided to ck_create_table() + * function. The number of table pointers is however set to be the larger value + * (4) always, so the \a tables array may be statically allocated. Size of one + * table is always a power of 2 (due to the character of the hash function). + * The stash has a default size STASH_SIZE, but can be resized if needed. + * However, the resizing is only done in rehashing process, if the items do not + * fit into the table and the original stash. + * + * Rehashing is done when the stash gets full (actually, last item is always + * free and is used in the rehashing process as a temporary variable). + */ +struct ck_hash_table { + uint table_count; /*!< Actual number of hash tables used. */ + + /*! + * \brief Exponent of one table's size (2^table_size_exp is table size). + */ + int table_size_exp; + + ck_hash_table_item_t **tables[MAX_TABLES]; /*!< Array of hash tables. */ + + //da_array_t stash; /*!< Stash implemented as a dynamic array. */ + ck_stash_item_t *stash; + + /*! \brief Temporary storage for item being hashed. */ + ck_hash_table_item_t *hashed; + + /*! \brief Mutex for avoiding multiple insertions / rehashes at once. */ + pthread_mutex_t mtx_table; + + /*! + * \brief Flags used for determining which hash functions are currently + * used + * + * Form: 00000xyz. + * x - rehash flag (1 if rehashing is in progress) + * yz - generation (may be 10 = 2, or 01 = 1) + * + * There are always two sets of hash functions available via the + * us_hash() function (see universal-hashing.h). Normally all items in + * the table are hashed using one set of functions. However, during + * rehash, the other set is used for rehashing. In this case the rehash + * flag (x) is set, so the lookup function (ck_find_item()) tries to use + * both sets of functions when searching for item. + */ + uint8_t generation; + + us_system_t hash_system; /*!< Universal system of hash functions. */ + + size_t items; + size_t items_in_stash; +}; + +typedef struct ck_hash_table ck_hash_table_t; + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates and initializes the hash table structure. + * + * All hash tables are allocated and their items initialized to 0 (NULL). + * A stash of default size is also created. The \a generation flags are set to + * 0. + * + * \param items Number of items to be hashed to the table. This number + * determines the size of the hash table that will be created. + * + * + * \return Pointer to the initialized hash table. + */ +ck_hash_table_t *ck_create_table(uint items); + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Destroys the whole hash table together with the saved values. + * + * \param table Pointer to pointer to the hash table. + * \param dtor_value Destructor function for the values that are be stored in + * the hash table. Set to NULL if you do not want the values + * to be deleted. + * \param delete_key Set to 0 if you do not want the function to delete the + * key of the item (e.g. when used elsewhere). Set to any + * other value otherwise. + * + * \note Make sure the table and its items are not used anymore when calling + * this function. + */ +void ck_destroy_table(ck_hash_table_t **table, + void (*dtor_value)(void *value), int delete_key); + +/*! + * \brief Destroys the table structures, but does not remove the individual + * hash table items. + */ +void ck_table_free(ck_hash_table_t **table); + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Inserts item into the hash table. + * + * Insertion starts always by trying to hash the item into the first table. The + * possible displaced item is then hashed into randomly chosen other table, + * etc., until a free place is found or a loop occured. A loop occurs when one + * position in one table is tried more than twice. + * + * \param table Hash table the item should be inserted into. + * \param key Item's key. It can be any string of octets. The key is not copied + * by the function. + * \param length Length of the key in bytes (octets). + * \param value Pointer to the actual item to be inserted into the hash table. + * + * \note This function does not copy the key. + * \note This function may trigger rehash of the whole table in case the stash + * gets full. + * + * \retval 0 No error. + * \retval -1 Insertion failed. This may occur only when the rehashing fails. + * In this case it is necessary to somehow manually force another + * rehash as no other rehash would be possible. + */ +int ck_insert_item(ck_hash_table_t *table, const char *key, size_t length, + void *value); + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Finds item in table. + * + * \param table Hash table to search in. + * \param key Key of the item. It can be an arbitrary string of octets. + * \param length Length of the key in bytes (octets). + * + * \return Pointer to the item if found. NULL otherwise. + */ +const ck_hash_table_item_t *ck_find_item(const ck_hash_table_t *table, + const char *key, size_t length); + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Updates item with the given key by replacing its value. + * + * The update process is synchronized using RCU mechanism, so the old item's + * value will not be deleted while some thread is using it. + * + * \param table Hash table where to search for the item. + * \param key Key of the item to be updated. It can be an arbitrary string of + * octets. + * \param length Length of the key in bytes (octets). + * \param new_value New value for the item with key \a key. + * \param dtor_value Destructor function for the values that are be stored in + * the hash table. Set to NULL if you do not want the values + * to be deleted. + * + * \retval 0 If successful. + * \retval -1 If the item was not found in the table. No changes are made. + */ +int ck_update_item(const ck_hash_table_t *table, const char *key, size_t length, + void *new_value, void (*dtor_value)(void *value)); + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Removes item with the given key from table. + * + * The deletion process is synchronized using RCU mechanism, so the old item + * will not be deleted while some thread is using it. + * + * \param table Hash table where to search for the item. + * \param key Key of the item to be removed. It can be an arbitrary string of + * octets. + * \param length Length of the key in bytes (octets). + * \param dtor_value Destructor function for the values that are be stored in + * the hash table. Set to NULL if you do not want the values + * to be deleted. + * \param delete_key Set to 0 if you do not want the function to delete the + * key of the item (e.g. when used elsewhere). Set to any + * other value otherwise. + * + * \retval 0 If successful. + * \retval -1 If the item was not found in the table. + */ +int ck_delete_item(const ck_hash_table_t *table, const char *key, size_t length, + void (*dtor_value)(void *value), int delete_key); + +ck_hash_table_item_t *ck_remove_item(ck_hash_table_t *table, const char *key, + size_t length); + +/*! + * \brief Creates a shallow copy of the cuckoo hash table. + * + * This function creates just the ck_hash_table_t structure and its tables and + * stash. It does not copy individual ck_hash_table_item_t structures. + * + * \param from Table to copy. + * \param to The new copy will be stored here. + * + * \retval 0 if successful. + * \retval + */ +int ck_shallow_copy(const ck_hash_table_t *from, ck_hash_table_t **to); + +int ck_apply(ck_hash_table_t *table, + void (*function)(ck_hash_table_item_t *item, void *data), + void *data); + +/*----------------------------------------------------------------------------*/ + +int ck_rehash(ck_hash_table_t *table); + +// for testing purposes only +int ck_resize_table(ck_hash_table_t *table); + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Dumps the whole hash table to the standard output. + */ +void ck_dump_table(const ck_hash_table_t *table); + +/*----------------------------------------------------------------------------*/ + +#endif /* _KNOT_CUCKOO_HASH_TABLE_H_ */ + +/*! @} */ diff --git a/src/libknot/hash/hash-functions.c b/src/libknot/hash/hash-functions.c new file mode 100644 index 0000000..a33dd6b --- /dev/null +++ b/src/libknot/hash/hash-functions.c @@ -0,0 +1,241 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include "hash-functions.h" + +/*--------------------------------- FNV HASH ---------------------------------*/ + +unsigned long int fnv_hash(const char *data, int size, int bits) +{ + int shift, i; + unsigned long int mask; + unsigned long int hash = 2166136261; + + if (bits == -1) { + shift = 0; + mask = 0xFFFFFFFF; + } else { + shift = 32 - bits; + mask = (1U << shift) - 1U; + } + + for (i = 0; i < size; i++) { + hash = (hash * 16777619) ^ data[i]; + } + + if (shift == 0) { + return hash; + } + + return (hash ^(hash >> shift)) & mask; +} + +/*------------------------------- JENKINS HASH -------------------------------*/ + +/* The mixing step */ +/* +#define mix(a,b,c) \ + { \ + a=a-b; a=a-c; a=a^(c>>13); \ + b=b-c; b=b-a; b=b^(a<<8); \ + c=c-a; c=c-b; c=c^(b>>13); \ + a=a-b; a=a-c; a=a^(c>>12); \ + b=b-c; b=b-a; b=b^(a<<16); \ + c=c-a; c=c-b; c=c^(b>>5); \ + a=a-b; a=a-c; a=a^(c>>3); \ + b=b-c; b=b-a; b=b^(a<<10); \ + c=c-a; c=c-b; c=c^(b>>15); \ + } +*/ + +///* The whole new hash function */ +//u4 jhash(register u1 *k, u4 length, u4 initval) +//{ +// register u4 a, b, c; /* the internal state */ +// u4 len; /* how many key bytes still need mixing */ + +// /* Set up the internal state */ +// len = length; +// a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */ +// c = initval; /* variable initialization of internal state */ + +// /*---------------------------------------- handle most of the key */ +// while (len >= 12) { +// a = a + (k[0] + ((u4)k[1] << 8) +// + ((u4)k[2] << 16) + ((u4)k[3] << 24)); +// b = b + (k[4] + ((u4)k[5] << 8) +// + ((u4)k[6] << 16) + ((u4)k[7] << 24)); +// c = c + (k[8] + ((u4)k[9] << 8) +// + ((u4)k[10] << 16) + ((u4)k[11] << 24)); +// mix(a, b, c); +// k = k + 12; +// len = len - 12; +// } + +// /*------------------------------------- handle the last 11 bytes */ +// c = c + length; +// switch (len) { /* all the case statements fall through */ +// case 11: +// c = c + ((u4)k[10] << 24); +// case 10: +// c = c + ((u4)k[9] << 16); +// case 9 : +// c = c + ((u4)k[8] << 8); +// /* the first byte of c is reserved for the length */ +// case 8 : +// b = b + ((u4)k[7] << 24); +// case 7 : +// b = b + ((u4)k[6] << 16); +// case 6 : +// b = b + ((u4)k[5] << 8); +// case 5 : +// b = b + k[4]; +// case 4 : +// a = a + ((u4)k[3] << 24); +// case 3 : +// a = a + ((u4)k[2] << 16); +// case 2 : +// a = a + ((u4)k[1] << 8); +// case 1 : +// a = a + k[0]; +// /* case 0: nothing left to add */ +// } +// mix(a, b, c); +// /*-------------------------------------------- report the result */ +// return c; +//} + + + +#define hashsize(n) ((ub4)1<<(n)) +#define hashmask(n) (hashsize(n)-1) + +/* +-------------------------------------------------------------------- +mix -- mix 3 32-bit values reversibly. +For every delta with one or two bits set, and the deltas of all three + high bits or all three low bits, whether the original value of a,b,c + is almost all zero or is uniformly distributed, +* If mix() is run forward or backward, at least 32 bits in a,b,c + have at least 1/4 probability of changing. +* If mix() is run forward, every bit of c will change between 1/3 and + 2/3 of the time. (Well, 22/100 and 78/100 for some 2-bit deltas.) +mix() was built out of 36 single-cycle latency instructions in a + structure that could supported 2x parallelism, like so: + a -= b; + a -= c; x = (c>>13); + b -= c; a ^= x; + b -= a; x = (a<<8); + c -= a; b ^= x; + c -= b; x = (b>>13); + ... + Unfortunately, superscalar Pentiums and Sparcs can't take advantage + of that parallelism. They've also turned some of those single-cycle + latency instructions into multi-cycle latency instructions. Still, + this is the fastest good hash I could find. There were about 2^^68 + to choose from. I only looked at a billion or so. +-------------------------------------------------------------------- +*/ +#define mix(a,b,c) \ +{ \ + a -= b; a -= c; a ^= (c>>13); \ + b -= c; b -= a; b ^= (a<<8); \ + c -= a; c -= b; c ^= (b>>13); \ + a -= b; a -= c; a ^= (c>>12); \ + b -= c; b -= a; b ^= (a<<16); \ + c -= a; c -= b; c ^= (b>>5); \ + a -= b; a -= c; a ^= (c>>3); \ + b -= c; b -= a; b ^= (a<<10); \ + c -= a; c -= b; c ^= (b>>15); \ +} + +/* +-------------------------------------------------------------------- +hash() -- hash a variable-length key into a 32-bit value + k : the key (the unaligned variable-length array of bytes) + len : the length of the key, counting by bytes + initval : can be any 4-byte value +Returns a 32-bit value. Every bit of the key affects every bit of +the return value. Every 1-bit and 2-bit delta achieves avalanche. +About 6*len+35 instructions. + +The best hash table sizes are powers of 2. There is no need to do +mod a prime (mod is sooo slow!). If you need less than 32 bits, +use a bitmask. For example, if you need only 10 bits, do + h = (h & hashmask(10)); +In which case, the hash table should have hashsize(10) elements. + +If you are hashing n strings (ub1 **)k, do it like this: + for (i=0, h=0; i<n; ++i) h = hash( k[i], len[i], h); + +By Bob Jenkins, 1996. bob_jenkins@burtleburtle.net. You may use this +code any way you wish, private, educational, or commercial. It's free. + +See http://burtleburtle.net/bob/hash/evahash.html +Use for hash table lookup, or anything where one collision in 2^^32 is +acceptable. Do NOT use for cryptographic purposes. +-------------------------------------------------------------------- +*/ + +ub4 jhash(k, length, initval) +register ub1 *k; /* the key */ +register ub4 length; /* the length of the key */ +register ub4 initval; /* the previous hash, or an arbitrary value */ +{ + register ub4 a,b,c,len; + + /* Set up the internal state */ + len = length; + a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */ + c = initval; /* the previous hash value */ + + /*---------------------------------------- handle most of the key */ + while (len >= 12) + { + a += (k[0] +((ub4)k[1]<<8) +((ub4)k[2]<<16) +((ub4)k[3]<<24)); + b += (k[4] +((ub4)k[5]<<8) +((ub4)k[6]<<16) +((ub4)k[7]<<24)); + c += (k[8] +((ub4)k[9]<<8) +((ub4)k[10]<<16)+((ub4)k[11]<<24)); + mix(a,b,c); + k += 12; len -= 12; + } + + /*------------------------------------- handle the last 11 bytes */ + c += length; + switch(len) /* all the case statements fall through */ + { + case 11: c+=((ub4)k[10]<<24); + case 10: c+=((ub4)k[9]<<16); + case 9 : c+=((ub4)k[8]<<8); + /* the first byte of c is reserved for the length */ + case 8 : b+=((ub4)k[7]<<24); + case 7 : b+=((ub4)k[6]<<16); + case 6 : b+=((ub4)k[5]<<8); + case 5 : b+=k[4]; + case 4 : a+=((ub4)k[3]<<24); + case 3 : a+=((ub4)k[2]<<16); + case 2 : a+=((ub4)k[1]<<8); + case 1 : a+=k[0]; + /* case 0: nothing left to add */ + } + mix(a,b,c); + /*-------------------------------------------- report the result */ + return c; +} + +#undef hashsize +#undef hashmask + diff --git a/src/libknot/hash/hash-functions.h b/src/libknot/hash/hash-functions.h new file mode 100644 index 0000000..f23730b --- /dev/null +++ b/src/libknot/hash/hash-functions.h @@ -0,0 +1,85 @@ +/*! + * \file hash-functions.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Various hash functions. + * + * All of the hash functions are downloaded from various sources. + * + * \todo Add references to sources. + * + * \addtogroup hashing + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_HASH_FUNCTIONS_H_ +#define _KNOT_HASH_FUNCTIONS_H_ + +#include <stdint.h> +#include <string.h> + +/* + * Fowler / Noll / Vo Hash (FNV Hash) + * http://www.isthe.com/chongo/tech/comp/fnv/ + * + * This is an implementation of the algorithms posted above. + * This file is placed in the public domain by Peter Wemm. + * + * $FreeBSD: src/sys/sys/fnv_hash.h,v 1.2.2.1 2001/03/21 10:50:59 peter Exp $ + */ + +typedef uint32_t Fnv32_t; + +#define FNV1_32_INIT ((Fnv32_t) 33554467UL) + +#define FNV_32_PRIME ((Fnv32_t) 0x01000193UL) + +static __inline Fnv32_t +fnv_32_buf(const void *buf, size_t len, Fnv32_t hval) +{ + const uint8_t *s = (const uint8_t *)buf; + + while (len-- != 0) { + hval *= FNV_32_PRIME; + hval ^= *s++; + } + return hval; +} + +/*! + * \brief Jenkins hash function. + * + * Downloaded from http://burtleburtle.net/bob/hash/evahash.html + * + * \param k Data to hash + * \param length Size of the data in bytes. + * \param initval The previous hash or an arbitrary value. + * + * \return Hash of the data. + * + * \todo Add source. + */ +typedef unsigned long int ub4; /* unsigned 4-byte quantities */ +typedef unsigned char ub1; /* unsigned 1-byte quantities */ + +ub4 jhash(register ub1 *k, register ub4 length, register ub4 initval); + +#endif /* _KNOT_HASH_FUNCTIONS_H_ */ + +/*! @} */ diff --git a/src/libknot/hash/universal-system.c b/src/libknot/hash/universal-system.c new file mode 100644 index 0000000..096974c --- /dev/null +++ b/src/libknot/hash/universal-system.c @@ -0,0 +1,116 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <limits.h> +#include <stdint.h> +#include <time.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +#include "universal-system.h" +#include "common.h" +#include "util/utils.h" + +/*----------------------------------------------------------------------------*/ + +const uint MAX_UINT_EXP = 32; +const unsigned long MAX_UINT_MY = UINT32_MAX; /* 4294967295 */ + +/*----------------------------------------------------------------------------*/ +/* Private functions */ +/*----------------------------------------------------------------------------*/ +/*! + * \brief Generates new set of coeficients. + * + * \param system Universal system to generate the coeficients for. + * \param from First coeficient to be replaced. + * \param to Up to this the coeficients will be replaced. + */ +static void us_generate_coefs(us_system_t *system, uint from, uint to) +{ + assert(system != NULL); + + for (uint i = from; i < to; ++i) { + int used = 0; + + do { + // generate random odd number + system->coefs[i] = knot_quick_rand() % MAX_UINT_MY; + if (system->coefs[i] % 2 == 0) { + system->coefs[i] = (system->coefs[i] == 0) + ? 1 + : system->coefs[i] - 1; + } + // check if this coeficient is already used + uint j = from; + while (used == 0 && j < i) { + if (system->coefs[j++] == system->coefs[i]) { + used = 1; + } + } + // if already used, generate again + } while (used != 0); + } +} + +/*----------------------------------------------------------------------------*/ +/* Public functions */ +/*----------------------------------------------------------------------------*/ + +void us_initialize(us_system_t *system) +{ + assert(system != NULL); + assert(UINT_MAX == MAX_UINT_MY); + + // Initialize both generations of functions by generating random odd + // numbers + us_generate_coefs(system, 0, US_FNC_COUNT * GEN_COUNT); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \note \a generation starts from 1 + */ +int us_next(us_system_t *system, uint generation) +{ + assert(system != NULL); + // generate new coeficients for the new generation + us_generate_coefs(system, (generation - 1) * US_FNC_COUNT, + generation * US_FNC_COUNT); + return 0; +} + +/*----------------------------------------------------------------------------*/ + +uint32_t us_hash(const us_system_t *system, uint32_t value, uint table_exp, + uint fnc, uint generation) +{ + /* + * multiplication should overflow if larger than MAX_UINT + * this is the same as (coef * value) mod MAX_UINT + * + * TODO: maybe we should not rely on this + */ + assert(system != NULL); + assert(table_exp <= 32); + assert(fnc < US_FNC_COUNT); + assert(generation <= GEN_COUNT); + + return ((system->coefs[((generation - 1) * US_FNC_COUNT) + fnc] * value) + >> (MAX_UINT_EXP - table_exp)); +} diff --git a/src/libknot/hash/universal-system.h b/src/libknot/hash/universal-system.h new file mode 100644 index 0000000..25330de --- /dev/null +++ b/src/libknot/hash/universal-system.h @@ -0,0 +1,109 @@ +/*! + * \file universal-system.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * This file provides interface to a 2-universal system of hash functions that + * hash from 32-bit unsigned integer to a 32-bit unsigned integer within a given + * range. The range is always a power of two and is given by the exponent (see + * function us_hash(). + * + * Before using the system, it must be initialized by calling us_initialize(). + * The system stores 2 sets (generations), each of US_FNC_COUNT functions. + * For generating a new set of coeficients (i.e. hash functions) use the + * us_next() function. + * + * For hashing use the us_hash() function. + * + * \todo What if all numbers are tried and still need rehash? + * (that means 2mld rehashes - we can probably live with that ;) + * \todo Consider counting generations from 0, will be easier! + * \todo Check out some better random number generator. + * + * \addtogroup hashing + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_UNIVERSAL_SYSTEM_H_ +#define _KNOT_UNIVERSAL_SYSTEM_H_ + +#include <stdint.h> +#include "common.h" + + +enum { US_FNC_COUNT = 4 /*!< Number of functions for one generation. */ }; + +enum { GEN_COUNT = 2 /*!< Number of generations. */ }; + +/*----------------------------------------------------------------------------*/ +/*! \brief Analytically defined universal system of hashing functions. */ +struct us_system { + /*! \brief Coeficients for the functions */ + uint coefs[US_FNC_COUNT * GEN_COUNT]; +}; + +typedef struct us_system us_system_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Initializes the universal system by generating coeficients for all + * hash functions and all generations. + * + * \param system Universal system to be used. + */ +void us_initialize(us_system_t *system); + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Generates new hash functions' coeficients for the given \a generation. + * + * \param system Universal system to be used. + * \param generation Generation for which to generate the new coeficients. + * + * \return 0 + */ +int us_next(us_system_t *system, uint generation); + +/*----------------------------------------------------------------------------*/ + +/*! + * \brief Hashes the \a value using the given \a exponent and function. + * + * The actual formula of the hash is: + * h = ((coef * value) mod 2^32) / 2^(32 - table_exp) + * where \a coef is the proper coeficient. + * + * \param system Universal system to be used. + * \param value Value to be hashed. + * \param table_exp Determines the upper bound for the result - the hash will + * be between 0 and 2^(32 - table_exp). + * \param fnc Which function from the set should be used. + * \param generation Which set (generation) of functions should be used. + * + * \todo Make inline? + * + * \return Hash value (32bit unsigned). + */ +uint32_t us_hash(const us_system_t *system, uint32_t value, uint table_exp, + uint fnc, uint generation); + +/*----------------------------------------------------------------------------*/ + +#endif /* _KNOT_UNIVERSAL_SYSTEM_H_ */ + +/*! @} */ diff --git a/src/libknot/libknot.h b/src/libknot/libknot.h new file mode 100644 index 0000000..a401be7 --- /dev/null +++ b/src/libknot/libknot.h @@ -0,0 +1,48 @@ +/*! + * \file libknot.h + * + * \author Jan Kadlec <jan.kadlec@nic.cz> + * + * \brief Convenience header for including whole library. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_LIBKNOT_H_ +#define _KNOT_LIBKNOT_H_ + +#include "consts.h" +#include "util/descriptor.h" +#include "dname.h" +#include "edns.h" +#include "zone/node.h" +#include "nsec3.h" +#include "util/wire.h" +#include "rdata.h" +#include "packet/response.h" +#include "rrset.h" +#include "util/tolower.h" +#include "util/utils.h" +#include "zone/zone.h" +#include "zone/zonedb.h" +#include "util/error.h" + +#endif + +/*! @} */ diff --git a/src/libknot/nameserver/name-server.c b/src/libknot/nameserver/name-server.c new file mode 100644 index 0000000..f88f802 --- /dev/null +++ b/src/libknot/nameserver/name-server.c @@ -0,0 +1,3663 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <assert.h> +#include <sys/time.h> + +#include <urcu.h> + +#include "nameserver/name-server.h" +#include "updates/xfr-in.h" + +#include "util/error.h" +#include "libknot.h" +#include "util/debug.h" +#include "packet/packet.h" +#include "packet/response.h" +#include "packet/query.h" +#include "consts.h" +#include "updates/changesets.h" +#include "updates/ddns.h" +#include "tsig-op.h" + +/*----------------------------------------------------------------------------*/ + +/*! \brief Maximum UDP payload with EDNS enabled. */ +static const uint16_t MAX_UDP_PAYLOAD_EDNS = 4096; +/*! \brief Maximum UDP payload with EDNS disabled. */ +static const uint16_t MAX_UDP_PAYLOAD = 504; // 512 - 8B header +/*! \brief Maximum size of one AXFR response packet. */ +static const uint16_t MAX_AXFR_PAYLOAD = 65535; +/*! \brief Supported EDNS version. */ +static const uint8_t EDNS_VERSION = 0; +/*! \brief Determines whether EDNS is enabled. */ +static const int EDNS_ENABLED = 1; + +/*! \brief TTL of a CNAME synthetized from a DNAME. */ +static const uint32_t SYNTH_CNAME_TTL = 0; + +/*! \brief Determines whether DNSSEC is enabled. */ +static const int DNSSEC_ENABLED = 1; + +/*! \brief Determines whether NSID is enabled. */ +static const int NSID_ENABLED = 1; + +/*! \brief Length of NSID option data. */ +static const uint16_t NSID_LENGTH = 6; +/*! \brief NSID option data. */ +static const uint8_t NSID_DATA[6] = {0x46, 0x6f, 0x6f, 0x42, 0x61, 0x72}; + +/*! \brief Internal error code to propagate need for SERVFAIL response. */ +static const int NS_ERR_SERVFAIL = -999; + +/*----------------------------------------------------------------------------*/ +/* Private functions */ +/*----------------------------------------------------------------------------*/ +/*! + * \brief Finds zone where to search for the QNAME. + * + * \note As QTYPE DS requires special handling, this function finds a zone for + * a direct predecessor of QNAME in such case. + * + * \param zdb Zone database where to search for the proper zone. + * \param qname QNAME. + * \param qtype QTYPE. + * + * \return Zone to which QNAME belongs (according to QTYPE), or NULL if no such + * zone was found. + */ +static const knot_zone_t *ns_get_zone_for_qname(knot_zonedb_t *zdb, + const knot_dname_t *qname, + uint16_t qtype) +{ + const knot_zone_t *zone; + /* + * Find a zone in which to search. + * + * In case of DS query, we strip the leftmost label when searching for + * the zone (but use whole qname in search for the record), as the DS + * records are only present in a parent zone. + */ + if (qtype == KNOT_RRTYPE_DS) { + /*! \todo Optimize, do not deep copy dname. */ + knot_dname_t *name = knot_dname_left_chop(qname); + zone = knot_zonedb_find_zone_for_name(zdb, name); + /* Directly discard. */ + knot_dname_free(&name); + } else { + zone = knot_zonedb_find_zone_for_name(zdb, qname); + } + + return zone; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Synthetizes RRSet from a wildcard RRSet using the given QNAME. + * + * The synthetized RRSet is identical to the wildcard RRSets, except that the + * owner name is replaced by \a qname. + * + * \param wildcard_rrset Wildcard RRSet to synthetize from. + * \param qname Domain name to be used as the owner of the synthetized RRset. + * + * \return The synthetized RRSet (this is a newly created RRSet, remember to + * free it). + */ +static knot_rrset_t *ns_synth_from_wildcard( + const knot_rrset_t *wildcard_rrset, const knot_dname_t *qname) +{ + dbg_ns("Synthetizing RRSet from wildcard...\n"); + + knot_dname_t *owner = knot_dname_deep_copy(qname); +// printf("Copied owner ptr: %p\n", owner); + + knot_rrset_t *synth_rrset = knot_rrset_new( + owner, knot_rrset_type(wildcard_rrset), + knot_rrset_class(wildcard_rrset), + knot_rrset_ttl(wildcard_rrset)); + + /* Release owner, as it's retained in rrset. */ + knot_dname_release(owner); + + if (synth_rrset == NULL) { + return NULL; + } + + dbg_ns("Created RRSet header:\n"); + knot_rrset_dump(synth_rrset, 1); + + // copy all RDATA + const knot_rdata_t *rdata = knot_rrset_rdata(wildcard_rrset); + while (rdata != NULL) { + // we could use the RDATA from the wildcard rrset + // but there is no way to distinguish it when deleting + // temporary RRSets + knot_rdata_t *rdata_copy = knot_rdata_deep_copy(rdata, + knot_rrset_type(synth_rrset)); + if (rdata_copy == NULL) { + knot_rrset_deep_free(&synth_rrset, 1, 1, 0); + return NULL; + } + + dbg_ns("Copied RDATA:\n"); + knot_rdata_dump(rdata_copy, + knot_rrset_type(synth_rrset), 1); + + knot_rrset_add_rdata(synth_rrset, rdata_copy); + rdata = knot_rrset_rdata_next(wildcard_rrset, rdata); + } + +// printf("Synthetized RRSet pointer: %p\n", synth_rrset); + return synth_rrset; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Checks if the given RRSet is a wildcard RRSet and replaces it with + * a synthetized RRSet if required. + * + * \param name Domain name to be used as the owner of the possibly synthetized + * RRSet + * \param resp Response to which the synthetized RRSet should be stored (as a + * temporary RRSet). + * \param rrset RRSet to check (and possibly replace). + */ +static void ns_check_wildcard(const knot_dname_t *name, knot_packet_t *resp, + const knot_rrset_t **rrset) +{ + assert(name != NULL); + assert(resp != NULL); + assert(rrset != NULL); + assert(*rrset != NULL); + + if (knot_dname_is_wildcard((*rrset)->owner)) { + knot_rrset_t *synth_rrset = + ns_synth_from_wildcard(*rrset, name); + dbg_ns("Synthetized RRSet:\n"); + knot_rrset_dump(synth_rrset, 1); + knot_packet_add_tmp_rrset(resp, synth_rrset); + *rrset = synth_rrset; + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adds signatures (RRSIGs) for the given RRSet to the response. + * + * This function first checks if DNSSEC is enabled and if it was requested in + * the response (DO bit set). If not, it does nothing and returns 0. If yes, + * it retrieves RRSIGs stored in the RRSet, deals with possible wildcard owner + * and adds the RRSIGs to response using the given function (that determines + * to which section of the response they will be added). + * + * \param rrset RRSet to get the RRSIGs from. + * \param resp Response where to add the RRSIGs. + * \param name Actual name to be used as owner in case of wildcard RRSet. + * \param add_rrset_to_resp Function for adding the RRSIG RRset to the response. + * \param tc Set to 1 if omitting the RRSIG RRSet should result in setting the + * TC bit in the response. + * + * \return KNOT_EOK + * \return KNOT_ENOMEM + * \return KNOT_ESPACE + */ +static int ns_add_rrsigs(const knot_rrset_t *rrset, knot_packet_t *resp, + const knot_dname_t *name, + int (*add_rrset_to_resp)(knot_packet_t *, + const knot_rrset_t *, + int, int, int), + int tc) +{ + const knot_rrset_t *rrsigs; + + dbg_ns("Adding RRSIGs for RRSet, type: %s.\n", + knot_rrtype_to_string(knot_rrset_type(rrset))); + + assert(resp != NULL); + assert(add_rrset_to_resp != NULL); + + dbg_ns("DNSSEC requested: %d\n", + knot_query_dnssec_requested(knot_packet_query(resp))); + dbg_ns("RRSIGS: %p\n", knot_rrset_rrsigs(rrset)); + + if (DNSSEC_ENABLED + && knot_query_dnssec_requested(knot_packet_query(resp)) + && (rrsigs = knot_rrset_rrsigs(rrset)) != NULL) { + if (name != NULL) { + ns_check_wildcard(name, resp, &rrsigs); + } + return add_rrset_to_resp(resp, rrsigs, tc, 0, 0); + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Resolves CNAME chain starting in \a node, stores all the CNAMEs in the + * response and updates \a node and \a qname to the last node in the + * chain. + * + * \param node Node (possibly) containing a CNAME RR. + * \param qname Searched name. Will be updated to the canonical name. + * \param resp Response where to add the CNAME RRs. + * \param add_rrset_to_resp Function for adding the CNAME RRs to the response. + * \param tc Set to 1 if omitting the RRSIG RRSet should result in setting the + * TC bit in the response. + */ +static void ns_follow_cname(const knot_node_t **node, + const knot_dname_t **qname, + knot_packet_t *resp, + int (*add_rrset_to_resp)(knot_packet_t *, + const knot_rrset_t *, + int, int, int), + int tc) +{ + dbg_ns("Resolving CNAME chain...\n"); + const knot_rrset_t *cname_rrset; + + while (*node != NULL + && (cname_rrset = knot_node_rrset(*node, KNOT_RRTYPE_CNAME)) + != NULL) { + /* put the CNAME record to answer, but replace the possible + wildcard name with qname */ + + assert(cname_rrset != NULL); + + dbg_ns("CNAME RRSet: %p, owner: %p\n", cname_rrset, + cname_rrset->owner); + + const knot_rrset_t *rrset = cname_rrset; + + // ignoring other than the first record + if (knot_dname_is_wildcard(knot_node_owner(*node))) { + /* if wildcard node, we must copy the RRSet and + replace its owner */ + rrset = ns_synth_from_wildcard(cname_rrset, *qname); + knot_packet_add_tmp_rrset(resp, (knot_rrset_t *)rrset); + add_rrset_to_resp(resp, rrset, tc, 0, 0); + ns_add_rrsigs(cname_rrset, resp, *qname, + add_rrset_to_resp, tc); + } else { + add_rrset_to_resp(resp, rrset, tc, 0, 0); + ns_add_rrsigs(rrset, resp, *qname, add_rrset_to_resp, + tc); + } + + dbg_ns("Using RRSet: %p, owner: %p\n", rrset, rrset->owner); + +dbg_ns_exec( + char *name = knot_dname_to_str(knot_rrset_owner(rrset)); + dbg_ns("CNAME record for owner %s put to response.\n", name); + free(name); +); + + // get the name from the CNAME RDATA + const knot_dname_t *cname = knot_rdata_cname_name( + knot_rrset_rdata(cname_rrset)); + dbg_ns("CNAME name from RDATA: %p\n", cname); + // change the node to the node of that name + *node = knot_dname_node(cname, 1); + dbg_ns("This name's node: %p\n", *node); +// // it is not an old node and if yes, skip it +// if (knot_node_is_old(*node)) { +// *node = knot_node_new_node(*node); +// } + + // save the new name which should be used for replacing wildcard + *qname = cname; + }; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Retrieves RRSet(s) of given type from the given node and adds them to + * the response's Answer section. + * + * \param node Node where to take the RRSet from. + * \param name Actual searched name (used in case of wildcard RRSet(s)). + * \param type Type of the RRSet(s). If set to KNOT_RRTYPE_ANY, all RRSets + * from the node will be added to the answer. + * \param resp Response where to add the RRSets. + * + * \return Number of RRSets added. + */ +static int ns_put_answer(const knot_node_t *node, const knot_dname_t *name, + uint16_t type, knot_packet_t *resp) +{ + int added = 0; +dbg_ns_exec( + char *name_str = knot_dname_to_str(node->owner); + dbg_ns("Putting answers from node %s.\n", name_str); + free(name_str); +); + + switch (type) { + case KNOT_RRTYPE_ANY: { + dbg_ns("Returning all RRTYPES.\n"); + const knot_rrset_t **rrsets = knot_node_rrsets(node); + if (rrsets == NULL) { + break; + } + int i = 0; + int ret = 0; + const knot_rrset_t *rrset; + while (i < knot_node_rrset_count(node)) { + assert(rrsets[i] != NULL); + rrset = rrsets[i]; + + dbg_ns(" Type: %s\n", + knot_rrtype_to_string(knot_rrset_type(rrset))); + + ns_check_wildcard(name, resp, &rrset); + ret = knot_response_add_rrset_answer(resp, rrset, 1, + 0, 0); + if (ret >= 0 && (added += 1) + && (ret = ns_add_rrsigs(rrset, resp, name, + knot_response_add_rrset_answer, 1)) + >=0 ) { + added += 1; + } else { + free(rrsets); + rrsets = NULL; + break; + } + + ++i; + } + if (rrsets != NULL) { + free(rrsets); + } + break; + } + case KNOT_RRTYPE_RRSIG: { + dbg_ns("Returning all RRSIGs.\n"); + const knot_rrset_t **rrsets = knot_node_rrsets(node); + if (rrsets == NULL) { + break; + } + int i = 0; + int ret = 0; + const knot_rrset_t *rrset; + while (i < knot_node_rrset_count(node)) { + assert(rrsets[i] != NULL); + rrset = knot_rrset_rrsigs(rrsets[i]); + + if (rrset == NULL) { + ++i; + continue; + } + + ns_check_wildcard(name, resp, &rrset); + ret = knot_response_add_rrset_answer(resp, rrset, 1, + 0, 0); + + if (ret < 0) { + break; + } + + added += 1; + ++i; + } + free(rrsets); + break; + } + default: { + int ret = 0; + const knot_rrset_t *rrset = knot_node_rrset(node, type); + const knot_rrset_t *rrset2 = rrset; + if (rrset != NULL) { + dbg_ns("Found RRSet of type %s\n", + knot_rrtype_to_string(type)); + ns_check_wildcard(name, resp, &rrset2); + ret = knot_response_add_rrset_answer(resp, rrset2, 1, + 0, 0); + if (ret >= 0 && (added += 1) + && (ret = ns_add_rrsigs(rrset, resp, name, + knot_response_add_rrset_answer, 1)) > 0) { + added += 1; + } + } + } + } + + knot_response_set_rcode(resp, KNOT_RCODE_NOERROR); + return added; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adds RRSets to Additional section of the response. + * + * This function uses knot_rdata_get_name() to get the domain name from the + * RDATA of the RRSet according to its type. It also does not search for the + * retrieved domain name, but just uses its node field. Thus to work correctly, + * the zone where the RRSet is from should be adjusted using + * knot_zone_adjust_dnames(). + * + * A and AAAA RRSets (and possible CNAMEs) for the found domain names are added. + * + * \warning Use this function only with types containing some domain name, + * otherwise it will crash (or behave strangely). + * + * \param resp Response where to add the Additional data. + * \param rrset RRSet to get the Additional data for. + */ +static void ns_put_additional_for_rrset(knot_packet_t *resp, + const knot_rrset_t *rrset) +{ + const knot_node_t *node = NULL; + const knot_rdata_t *rdata = NULL; + const knot_dname_t *dname = NULL; + + // for all RRs in the RRset + rdata = knot_rrset_rdata(rrset); + while (rdata != NULL) { + dbg_ns("Getting name from RDATA, type %s..\n", + knot_rrtype_to_string(knot_rrset_type(rrset))); + dname = knot_rdata_get_name(rdata, + knot_rrset_type(rrset)); + assert(dname != NULL); + node = knot_dname_node(dname, 1); +// // check if the node is not old and if yes, take the new one +// if (knot_node_is_old(node)) { +// node = knot_node_new_node(node); +// } + + dbg_ns_detail("Node saved in RDATA dname: %p\n", node); + + if (node != NULL && node->owner != dname) { + // the stored node should be the closest encloser + assert(knot_dname_is_subdomain(dname, node->owner)); + // try the wildcard child, if any + node = knot_node_wildcard_child(node, 1); +// // this should not be old node!! +// assert(!knot_node_is_old(node)); + } + + const knot_rrset_t *rrset_add; + + if (node != NULL) { +dbg_ns_exec( + char *name = knot_dname_to_str(node->owner); + dbg_ns("Putting additional from node %s\n", name); + free(name); +); + dbg_ns("Checking CNAMEs...\n"); + if (knot_node_rrset(node, KNOT_RRTYPE_CNAME) + != NULL) { + dbg_ns("Found CNAME in node, following...\n"); + const knot_dname_t *dname + = knot_node_owner(node); + ns_follow_cname(&node, &dname, resp, + knot_response_add_rrset_additional, 0); + } + + // A RRSet + dbg_ns("A RRSets...\n"); + rrset_add = knot_node_rrset(node, KNOT_RRTYPE_A); + if (rrset_add != NULL) { + dbg_ns("Found A RRsets.\n"); + const knot_rrset_t *rrset_add2 = rrset_add; + ns_check_wildcard(dname, resp, &rrset_add2); + knot_response_add_rrset_additional( + resp, rrset_add2, 0, 1, 0); + ns_add_rrsigs(rrset_add, resp, dname, + knot_response_add_rrset_additional, 0); + } + + // AAAA RRSet + dbg_ns("AAAA RRSets...\n"); + rrset_add = knot_node_rrset(node, KNOT_RRTYPE_AAAA); + if (rrset_add != NULL) { + dbg_ns("Found AAAA RRsets.\n"); + const knot_rrset_t *rrset_add2 = rrset_add; + ns_check_wildcard(dname, resp, &rrset_add2); + knot_response_add_rrset_additional( + resp, rrset_add2, 0, 1, 0); + ns_add_rrsigs(rrset_add, resp, dname, + knot_response_add_rrset_additional, 0); + } + } + + assert(rrset != NULL); + assert(rdata != NULL); + rdata = knot_rrset_rdata_next(rrset, rdata); + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Checks whether the given type requires additional processing. + * + * Only MX, NS and SRV types require additional processing. + * + * \param qtype Type to check. + * + * \retval <> 0 if additional processing is needed for \a qtype. + * \retval 0 otherwise. + */ +static int ns_additional_needed(uint16_t qtype) +{ + return (qtype == KNOT_RRTYPE_MX || + qtype == KNOT_RRTYPE_NS || + qtype == KNOT_RRTYPE_SRV); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adds whatever Additional RRSets are required for the response. + * + * For each RRSet in Answer and Authority sections this function checks if + * additional processing is needed and if yes, it puts any Additional RRSets + * available to the Additional section of the response. + * + * \param resp Response to process. + */ +static void ns_put_additional(knot_packet_t *resp) +{ + dbg_ns("ADDITIONAL SECTION PROCESSING\n"); + + const knot_rrset_t *rrset = NULL; + + for (int i = 0; i < knot_packet_answer_rrset_count(resp); ++i) { + rrset = knot_packet_answer_rrset(resp, i); + assert(rrset != NULL); + if (ns_additional_needed(knot_rrset_type(rrset))) { + ns_put_additional_for_rrset(resp, rrset); + } + } + + for (int i = 0; i < knot_packet_authority_rrset_count(resp); ++i) { + rrset = knot_packet_authority_rrset(resp, i); + if (ns_additional_needed(knot_rrset_type(rrset))) { + ns_put_additional_for_rrset(resp, rrset); + } + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Puts authority NS RRSet to the Auhority section of the response. + * + * \param zone Zone to take the authority NS RRSet from. + * \param resp Response where to add the RRSet. + */ +static void ns_put_authority_ns(const knot_zone_contents_t *zone, + knot_packet_t *resp) +{ + const knot_rrset_t *ns_rrset = knot_node_rrset( + knot_zone_contents_apex(zone), KNOT_RRTYPE_NS); + + if (ns_rrset != NULL) { + knot_response_add_rrset_authority(resp, ns_rrset, 0, 1, 0); + ns_add_rrsigs(ns_rrset, resp, knot_node_owner( + knot_zone_contents_apex(zone)), + knot_response_add_rrset_authority, 1); + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Puts SOA RRSet to the Auhority section of the response. + * + * \param zone Zone to take the SOA RRSet from. + * \param resp Response where to add the RRSet. + */ +static void ns_put_authority_soa(const knot_zone_contents_t *zone, + knot_packet_t *resp) +{ + const knot_rrset_t *soa_rrset = knot_node_rrset( + knot_zone_contents_apex(zone), KNOT_RRTYPE_SOA); + assert(soa_rrset != NULL); + + knot_response_add_rrset_authority(resp, soa_rrset, 0, 0, 0); + ns_add_rrsigs(soa_rrset, resp, + knot_node_owner(knot_zone_contents_apex(zone)), + knot_response_add_rrset_authority, 1); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates a 'next closer name' to the given domain name. + * + * For definition of 'next closer name', see RFC5155, Page 6. + * + * \param closest_encloser Closest encloser of \a name. + * \param name Domain name to create the 'next closer' name to. + * + * \return 'Next closer name' to the given domain name or NULL if an error + * occured. + */ +static knot_dname_t *ns_next_closer(const knot_dname_t *closest_encloser, + const knot_dname_t *name) +{ + int ce_labels = knot_dname_label_count(closest_encloser); + int qname_labels = knot_dname_label_count(name); + + assert(ce_labels < qname_labels); + + // the common labels should match + assert(knot_dname_matched_labels(closest_encloser, name) + == ce_labels); + + // chop some labels from the qname + knot_dname_t *next_closer = knot_dname_deep_copy(name); + if (next_closer == NULL) { + return NULL; + } + + for (int i = 0; i < (qname_labels - ce_labels - 1); ++i) { + knot_dname_left_chop_no_copy(next_closer); + } + + return next_closer; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adds NSEC3 RRSet (together with corresponding RRSIGs) from the given + * node into the response. + * + * \param node Node to get the NSEC3 RRSet from. + * \param resp Response where to add the RRSets. + */ +static void ns_put_nsec3_from_node(const knot_node_t *node, + knot_packet_t *resp) +{ + assert(DNSSEC_ENABLED + && knot_query_dnssec_requested(knot_packet_query(resp))); + + const knot_rrset_t *rrset = knot_node_rrset(node, + KNOT_RRTYPE_NSEC3); + assert(rrset != NULL); + + int res = knot_response_add_rrset_authority(resp, rrset, 1, 1, 0); + // add RRSIG for the RRSet + if (res == 0 && (rrset = knot_rrset_rrsigs(rrset)) != NULL) { + knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Finds and adds NSEC3 covering the given domain name (and their + * associated RRSIGs) to the response. + * + * \param zone Zone used for answering. + * \param name Domain name to cover. + * \param resp Response where to add the RRSets. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL if a runtime collision occured. The server should + * respond with SERVFAIL in such case. + */ +static int ns_put_covering_nsec3(const knot_zone_contents_t *zone, + const knot_dname_t *name, + knot_packet_t *resp) +{ + const knot_node_t *prev, *node; + /*! \todo Check version. */ + int match = knot_zone_contents_find_nsec3_for_name(zone, name, + &node, &prev, 1); + assert(match >= 0); + node = knot_node_current(node); + prev = knot_node_current(prev); + + if (match == KNOT_ZONE_NAME_FOUND){ + // run-time collision => SERVFAIL + return KNOT_EOK; + } + +// // check if the prev node is not old and if yes, take the new one +// if (knot_node_is_old(prev)) { +// prev = knot_node_new_node(prev); +// assert(prev != NULL); +// } + +dbg_ns_exec( + char *name = knot_dname_to_str(prev->owner); + dbg_ns("Covering NSEC3 node: %s\n", name); + free(name); +); + + ns_put_nsec3_from_node(prev, resp); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adds NSEC3s comprising the 'closest encloser proof' for the given + * (non-existent) domain name (and their associated RRSIGs) to the + * response. + * + * For definition of 'closest encloser proof', see RFC5155, section 7.2.1, + * Page 18. + * + * \note This function does not check if DNSSEC is enabled, nor if it is + * requested by the query. + * + * \param zone Zone used for answering. + * \param closest_encloser Closest encloser of \a qname in the zone. + * \param qname Searched (non-existent) name. + * \param resp Response where to add the NSEC3s. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_put_nsec3_closest_encloser_proof( + const knot_zone_contents_t *zone, + const knot_node_t **closest_encloser, + const knot_dname_t *qname, + knot_packet_t *resp) +{ + assert(zone != NULL); + assert(closest_encloser != NULL); + assert(*closest_encloser != NULL); + assert(qname != NULL); + assert(resp != NULL); + + if (knot_zone_contents_nsec3params(zone) == NULL) { +dbg_ns_exec( + char *name = knot_dname_to_str(knot_node_owner( + knot_zone_contents_apex(zone))); + dbg_ns("No NSEC3PARAM found in zone %s.\n", name); + free(name); +); + return KNOT_EOK; + } + +dbg_ns_exec( + char *name = knot_dname_to_str(knot_node_owner(*closest_encloser)); + dbg_ns("Closest encloser: %s\n", name); + free(name); +); + + /* + * 1) NSEC3 that matches closest provable encloser. + */ + const knot_node_t *nsec3_node = NULL; + const knot_dname_t *next_closer = NULL; + while ((nsec3_node = knot_node_nsec3_node((*closest_encloser), 1)) + == NULL) { + next_closer = knot_node_owner((*closest_encloser)); + *closest_encloser = knot_node_parent(*closest_encloser, 1); + if (*closest_encloser == NULL) { + // there are no NSEC3s to add + return KNOT_EOK; + } + } + + assert(nsec3_node != NULL); + +dbg_ns_exec( + char *name = knot_dname_to_str(nsec3_node->owner); + dbg_ns("NSEC3 node: %s\n", name); + free(name); + name = knot_dname_to_str((*closest_encloser)->owner); + dbg_ns("Closest provable encloser: %s\n", name); + free(name); + if (next_closer != NULL) { + name = knot_dname_to_str(next_closer); + dbg_ns("Next closer name: %s\n", name); + free(name); + } else { + dbg_ns("Next closer name: none\n"); + } +); + + ns_put_nsec3_from_node(nsec3_node, resp); + + /* + * 2) NSEC3 that covers the "next closer" name. + */ + int ret = 0; + if (next_closer == NULL) { + // create the "next closer" name by appending from qname + next_closer = ns_next_closer( + knot_node_owner(*closest_encloser), qname); + + if (next_closer == NULL) { + return NS_ERR_SERVFAIL; + } +dbg_ns_exec( + char *name = knot_dname_to_str(next_closer); + dbg_ns("Next closer name: %s\n", name); + free(name); +); + ret = ns_put_covering_nsec3(zone, next_closer, resp); + + // the cast is ugly, but no better way around it + knot_dname_release((knot_dname_t *)next_closer); + } else { + ret = ns_put_covering_nsec3(zone, next_closer, resp); + } + + return ret; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates a name of a wildcard child of \a name. + * + * \param name Domain name to get the wildcard child name of. + * + * \return Wildcard child name or NULL if an error occured. + */ +static knot_dname_t *ns_wildcard_child_name(const knot_dname_t *name) +{ + assert(name != NULL); + + knot_dname_t *wildcard = knot_dname_new_from_str("*", 1, NULL); + if (wildcard == NULL) { + return NULL; + } + + if (knot_dname_cat(wildcard, name) == NULL) { + /* Directly discard dname. */ + knot_dname_free(&wildcard); + return NULL; + } + +dbg_ns_exec( + char *name = knot_dname_to_str(wildcard); + dbg_ns("Wildcard: %s\n", name); + free(name); +); + return wildcard; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Puts NSEC3s covering the non-existent wildcard child of a node + * (and their associated RRSIGs) into the response. + * + * \note This function does not check if DNSSEC is enabled, nor if it is + * requested by the query. + * + * \param zone Zone used for answering. + * \param node Node whose non-existent wildcard child should be covered. + * \param resp Response where to add the NSEC3s. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_put_nsec3_no_wildcard_child(const knot_zone_contents_t *zone, + const knot_node_t *node, + knot_packet_t *resp) +{ + assert(node != NULL); + assert(resp != NULL); + assert(node->owner != NULL); + + int ret = 0; + knot_dname_t *wildcard = ns_wildcard_child_name(node->owner); + if (wildcard == NULL) { + ret = NS_ERR_SERVFAIL; + } else { + ret = ns_put_covering_nsec3(zone, wildcard, resp); + + /* Directly discard wildcard. */ + knot_dname_free(&wildcard); + } + + return ret; +} +/*----------------------------------------------------------------------------*/ +/*! + * \brief Puts NSECs or NSEC3s for NODATA error (and their associated RRSIGs) + * to the response. + * + * \note This function first checks if DNSSEC is enabled and requested by the + * query. + * \note Note that for each zone there are either NSEC or NSEC3 records used. + * + * \param node Node which generated the NODATA response (i.e. not containing + * RRSets of the requested type). + * \param resp Response where to add the NSECs or NSEC3s. + */ +static void ns_put_nsec_nsec3_nodata(const knot_node_t *node, + knot_packet_t *resp) +{ + if (!DNSSEC_ENABLED || + !knot_query_dnssec_requested(knot_packet_query(resp))) { + return; + } + + const knot_node_t *nsec3_node = knot_node_nsec3_node(node, 1); + const knot_rrset_t *rrset = NULL; + if ((rrset = knot_node_rrset(node, KNOT_RRTYPE_NSEC)) != NULL + || (nsec3_node != NULL && (rrset = + knot_node_rrset(nsec3_node, KNOT_RRTYPE_NSEC3)) != NULL)) { + knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); + // add RRSIG for the RRSet + if ((rrset = knot_rrset_rrsigs(rrset)) != NULL) { + knot_response_add_rrset_authority(resp, rrset, 1, + 0, 0); + } + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Puts NSECs for NXDOMAIN error to the response. + * + * \note This function does not check if DNSSEC is enabled, nor if it is + * requested by the query. + * + * \param qname QNAME which generated the NXDOMAIN error (i.e. not found in the + * zone). + * \param zone Zone used for answering. + * \param previous Previous node to \a qname in the zone. May also be NULL. In + * such case the function finds the previous node in the zone. + * \param closest_encloser Closest encloser of \a qname. Must not be NULL. + * \param resp Response where to put the NSECs. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_put_nsec_nxdomain(const knot_dname_t *qname, + const knot_zone_contents_t *zone, + const knot_node_t *previous, + const knot_node_t *closest_encloser, + knot_packet_t *resp) +{ + const knot_rrset_t *rrset = NULL; + + // check if we have previous; if not, find one using the tree + if (previous == NULL) { + /*! \todo Check version. */ + previous = knot_zone_contents_find_previous(zone, qname); + + while (!knot_node_is_auth(previous)) { + previous = knot_node_previous(previous, 1); + } + + previous = knot_node_current(previous); + assert(previous != NULL); + } + + char *name = knot_dname_to_str(previous->owner); + dbg_ns("Previous node: %s\n", name); + free(name); + + // 1) NSEC proving that there is no node with the searched name + rrset = knot_node_rrset(previous, KNOT_RRTYPE_NSEC); + if (rrset == NULL) { + // no NSEC records + //return NS_ERR_SERVFAIL; + return KNOT_EOK; + + } + + knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); + rrset = knot_rrset_rrsigs(rrset); + assert(rrset != NULL); + knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); + + // 2) NSEC proving that there is no wildcard covering the name + // this is only different from 1) if the wildcard would be + // before 'previous' in canonical order, i.e. we can + // search for previous until we find name lesser than wildcard + assert(closest_encloser != NULL); + + knot_dname_t *wildcard = + ns_wildcard_child_name(closest_encloser->owner); + if (wildcard == NULL) { + return NS_ERR_SERVFAIL; + } + + const knot_node_t *prev_new = previous; + + while (knot_dname_compare(knot_node_owner(prev_new), + wildcard) > 0) { + dbg_ns("Previous node: %s\n", + knot_dname_to_str(knot_node_owner(prev_new))); + assert(prev_new != knot_zone_contents_apex(zone)); + prev_new = knot_node_previous(prev_new, 1); + } + assert(knot_dname_compare(knot_node_owner(prev_new), + wildcard) < 0); + + dbg_ns("Previous node: %s\n", + knot_dname_to_str(knot_node_owner(prev_new))); + + /* Directly discard dname. */ + knot_dname_free(&wildcard); + + if (prev_new != previous) { + rrset = knot_node_rrset(prev_new, KNOT_RRTYPE_NSEC); + assert(rrset != NULL); + knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); + rrset = knot_rrset_rrsigs(rrset); + assert(rrset != NULL); + knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Puts NSEC3s for NXDOMAIN error to the response. + * + * \note This function does not check if DNSSEC is enabled, nor if it is + * requested by the query. + * + * \param zone Zone used for answering. + * \param closest_encloser Closest encloser of \a qname. + * \param qname Domain name which generated the NXDOMAIN error (i.e. not found + * in the zone. + * \param resp Response where to put the NSEC3s. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_put_nsec3_nxdomain(const knot_zone_contents_t *zone, + const knot_node_t *closest_encloser, + const knot_dname_t *qname, + knot_packet_t *resp) +{ + // 1) Closest encloser proof + dbg_ns("Putting closest encloser proof.\n"); + int ret = ns_put_nsec3_closest_encloser_proof(zone, &closest_encloser, + qname, resp); + // 2) NSEC3 covering non-existent wildcard + if (ret == KNOT_EOK && closest_encloser != NULL) { + dbg_ns("Putting NSEC3 for no wildcard child of closest " + "encloser.\n"); + ret = ns_put_nsec3_no_wildcard_child(zone, closest_encloser, + resp); + } + + return ret; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Puts NSECs or NSEC3s for the NXDOMAIN error to the response. + * + * \note This function first checks if DNSSEC is enabled and requested by the + * query. + * \note Note that for each zone there are either NSEC or NSEC3 records used. + * + * \param zone Zone used for answering. + * \param previous Previous node to \a qname in the zone. May also be NULL. In + * such case the function finds the previous node in the zone. + * \param closest_encloser Closest encloser of \a qname. Must not be NULL. + * \param qname QNAME which generated the NXDOMAIN error (i.e. not found in the + * zone). + * \param resp Response where to put the NSECs. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_put_nsec_nsec3_nxdomain(const knot_zone_contents_t *zone, + const knot_node_t *previous, + const knot_node_t *closest_encloser, + const knot_dname_t *qname, + knot_packet_t *resp) +{ + int ret = 0; + if (DNSSEC_ENABLED + && knot_query_dnssec_requested(knot_packet_query(resp))) { + if (knot_zone_contents_nsec3_enabled(zone)) { + ret = ns_put_nsec3_nxdomain(zone, closest_encloser, + qname, resp); + } else { + ret = ns_put_nsec_nxdomain(qname, zone, previous, + closest_encloser, resp); + } + } + return ret; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Puts NSEC3s for wildcard answer into the response. + * + * \note This function does not check if DNSSEC is enabled, nor if it is + * requested by the query. + * + * \param zone Zone used for answering. + * \param closest_encloser Closest encloser of \a qname in the zone. In this + * case it is the parent of the source of synthesis. + * \param qname Domain name covered by the wildcard used for answering the + * query. + * \param resp Response to put the NSEC3s into. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_put_nsec3_wildcard(const knot_zone_contents_t *zone, + const knot_node_t *closest_encloser, + const knot_dname_t *qname, + knot_packet_t *resp) +{ + assert(closest_encloser != NULL); + assert(qname != NULL); + assert(resp != NULL); + assert(DNSSEC_ENABLED + && knot_query_dnssec_requested(knot_packet_query(resp))); + + if (!knot_zone_contents_nsec3_enabled(zone)) { + return KNOT_EOK; + } + + /* + * NSEC3 that covers the "next closer" name. + */ + // create the "next closer" name by appending from qname + dbg_ns("Finding next closer name for wildcard NSEC3.\n"); + knot_dname_t *next_closer = + ns_next_closer(closest_encloser->owner, qname); + + if (next_closer == NULL) { + return NS_ERR_SERVFAIL; + } +dbg_ns_exec( + char *name = knot_dname_to_str(next_closer); + dbg_ns("Next closer name: %s\n", name); + free(name); +); + int ret = ns_put_covering_nsec3(zone, next_closer, resp); + + + /* Duplicate from ns_next_close(), safe to discard. */ + knot_dname_release(next_closer); + + return ret; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Puts NSECs for wildcard answer into the response. + * + * \note This function does not check if DNSSEC is enabled, nor if it is + * requested by the query. + * + * \param zone Zone used for answering. + * \param qname Domain name covered by the wildcard used for answering the + * query. + * \param previous Previous node of \a qname in canonical order. + * \param resp Response to put the NSEC3s into. + */ +static void ns_put_nsec_wildcard(const knot_zone_contents_t *zone, + const knot_dname_t *qname, + const knot_node_t *previous, + knot_packet_t *resp) +{ + assert(DNSSEC_ENABLED + && knot_query_dnssec_requested(knot_packet_query(resp))); + + // check if we have previous; if not, find one using the tree + if (previous == NULL) { + previous = knot_zone_contents_find_previous(zone, qname); + + while (!knot_node_is_auth(previous)) { + previous = knot_node_previous(previous, 1); + } + + previous = knot_node_current(previous); + assert(previous != NULL); + } + + const knot_rrset_t *rrset = + knot_node_rrset(previous, KNOT_RRTYPE_NSEC); + if (rrset != NULL) { + // NSEC proving that there is no node with the searched name + knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); + rrset = knot_rrset_rrsigs(rrset); + assert(rrset != NULL); + knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Puts NSECs or NSEC3s for wildcard NODATA answer into the response. + * + * \note This function first checks if DNSSEC is enabled and requested by the + * query. + * + * \param node Node used for answering. + * \param closest_encloser Closest encloser of \a qname in the zone. + * \param previous Previous node of \a qname in canonical order. + * \param zone Zone used for answering. + * \param qname Actual searched domain name. + * \param resp Response where to put the NSECs and NSEC3s. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_put_nsec_nsec3_wildcard_nodata(const knot_node_t *node, + const knot_node_t *closest_encloser, + const knot_node_t *previous, + const knot_zone_contents_t *zone, + const knot_dname_t *qname, + knot_packet_t *resp) +{ + int ret = KNOT_EOK; + if (DNSSEC_ENABLED + && knot_query_dnssec_requested(knot_packet_query(resp))) { + if (knot_zone_contents_nsec3_enabled(zone)) { + ret = ns_put_nsec3_closest_encloser_proof(zone, + &closest_encloser, + qname, resp); + + const knot_node_t *nsec3_node; + if (ret == KNOT_EOK + && (nsec3_node = knot_node_nsec3_node(node, 1)) + != NULL) { + ns_put_nsec3_from_node(nsec3_node, resp); + } + } else { + ns_put_nsec_wildcard(zone, qname, previous, resp); + } + } + return ret; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Puts NSECs or NSEC3s for wildcard answer into the response. + * + * \note This function first checks if DNSSEC is enabled and requested by the + * query and if the node's owner is a wildcard. + * + * \param node Node used for answering. + * \param closest_encloser Closest encloser of \a qname in the zone. + * \param previous Previous node of \a qname in canonical order. + * \param zone Zone used for answering. + * \param qname Actual searched domain name. + * \param resp Response where to put the NSECs and NSEC3s. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_put_nsec_nsec3_wildcard_answer(const knot_node_t *node, + const knot_node_t *closest_encloser, + const knot_node_t *previous, + const knot_zone_contents_t *zone, + const knot_dname_t *qname, + knot_packet_t *resp) +{ + int r = KNOT_EOK; + if (DNSSEC_ENABLED + && knot_query_dnssec_requested(knot_packet_query(resp)) + && knot_dname_is_wildcard(knot_node_owner(node))) { + if (knot_zone_contents_nsec3_enabled(zone)) { + r = ns_put_nsec3_wildcard(zone, closest_encloser, qname, + resp); + } else { + ns_put_nsec_wildcard(zone, qname, previous, resp); + } + } + return r; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates a referral response. + * + * This function puts the delegation NS RRSet to the Authority section of the + * response, possibly adds DS and their associated RRSIGs (if DNSSEC is enabled + * and requested by the query) and adds any available additional data (A and + * AAAA RRSets for the names in the NS RRs) with their associated RRSIGs + * to the Additional section. + * + * \param node Delegation point node. + * \param zone Parent zone (the one from which the response is generated). + * \param qname Searched name (which caused the referral). + * \param resp Response. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static inline int ns_referral(const knot_node_t *node, + const knot_zone_contents_t *zone, + const knot_dname_t *qname, + knot_packet_t *resp) +{ + dbg_ns("Referral response.\n"); + + while (!knot_node_is_deleg_point(node)) { + assert(knot_node_parent(node, 1) != NULL); + node = knot_node_parent(node, 1); + } + + const knot_rrset_t *rrset = knot_node_rrset(node, KNOT_RRTYPE_NS); + assert(rrset != NULL); + + // TODO: wildcards?? + //ns_check_wildcard(name, resp, &rrset); + + knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); + ns_add_rrsigs(rrset, resp, node->owner, + knot_response_add_rrset_authority, 1); + + int ret = KNOT_EOK; + // add DS records + dbg_ns("DNSSEC requested: %d\n", + knot_query_dnssec_requested(knot_packet_query(resp))); + dbg_ns("DS records: %p\n", knot_node_rrset(node, KNOT_RRTYPE_DS)); + if (DNSSEC_ENABLED + && knot_query_dnssec_requested(knot_packet_query(resp))) { + rrset = knot_node_rrset(node, KNOT_RRTYPE_DS); + if (rrset != NULL) { + knot_response_add_rrset_authority(resp, rrset, 1, 0, + 0); + ns_add_rrsigs(rrset, resp, node->owner, + knot_response_add_rrset_authority, 1); + } else { + // no DS, add NSEC3 or NSEC + // if NSEC3 enabled, search for NSEC3 + if (knot_zone_contents_nsec3_enabled(zone)) { + const knot_node_t *nsec3_node = + knot_node_nsec3_node(node, 1); + dbg_ns("There is no DS, putting NSEC3s...\n"); + if (nsec3_node != NULL) { + dbg_ns("Putting NSEC3s from the node.\n"); + ns_put_nsec3_from_node(nsec3_node, resp); + } else { + dbg_ns("Putting Opt-Out NSEC3s.\n"); + // no NSEC3 (probably Opt-Out) + // TODO: check if the zone is Opt-Out + ret = ns_put_nsec3_closest_encloser_proof(zone, + &node, qname, resp); + } + } else { + const knot_rrset_t *nsec = knot_node_rrset( + node, KNOT_RRTYPE_NSEC); + if (nsec) { + /*! \todo Check return value? */ + knot_response_add_rrset_authority( + resp, nsec, 1, 1, 0); + if ((nsec = knot_rrset_rrsigs(nsec)) != NULL) { + knot_response_add_rrset_authority(resp, nsec, 1, + 1, 0); + } + } + } + } + } + + if (ret == KNOT_EOK) { + ns_put_additional(resp); + knot_response_set_rcode(resp, KNOT_RCODE_NOERROR); + } + return ret; +} + +/*----------------------------------------------------------------------------*/ + +/*! + * \brief Tries to answer the query from the given node. + * + * Tries to put RRSets of requested type (\a qtype) to the Answer section of the + * response. If successful, it also adds authority NS RRSet to the Authority + * section and it may add NSEC or NSEC3s in case of a wildcard answer (\a node + * is a wildcard node). If not successful (there are no such RRSets), it adds + * the SOA record to the Authority section and may add NSEC or NSEC3s according + * to the type of the response (NXDOMAIN if \a node is an empty non-terminal, + * NODATA if it is a regular node). It also adds any additional data that may + * be required. + * + * \param node Node to answer from. + * \param closest_encloser Closest encloser of \a qname in the zone. + * \param previous Previous domain name of \a qname in canonical order. + * \param zone Zone used for answering. + * \param qname Searched domain name. + * \param qtype Searched RR type. + * \param resp Response. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_answer_from_node(const knot_node_t *node, + const knot_node_t *closest_encloser, + const knot_node_t *previous, + const knot_zone_contents_t *zone, + const knot_dname_t *qname, uint16_t qtype, + knot_packet_t *resp) +{ + dbg_ns("Putting answers from found node to the response...\n"); + int answers = ns_put_answer(node, qname, qtype, resp); + + int ret = KNOT_EOK; + if (answers == 0) { // if NODATA response, put SOA + if (knot_node_rrset_count(node) == 0 + && !knot_zone_contents_nsec3_enabled(zone)) { + // node is an empty non-terminal => NSEC for NXDOMAIN + //assert(knot_node_rrset_count(closest_encloser) > 0); + dbg_ns("Adding NSEC/NSEC3 for NXDOMAIN.\n"); + ret = ns_put_nsec_nsec3_nxdomain(zone, + knot_node_previous(node, 1), closest_encloser, + qname, resp); + } else { + dbg_ns("Adding NSEC/NSEC3 for NODATA.\n"); + ns_put_nsec_nsec3_nodata(node, resp); + if (knot_dname_is_wildcard(node->owner)) { + dbg_ns("Putting NSEC/NSEC3 for wildcard" + " NODATA\n"); + ret = ns_put_nsec_nsec3_wildcard_nodata(node, + closest_encloser, previous, zone, qname, + resp); + } + } + ns_put_authority_soa(zone, resp); + } else { // else put authority NS + // if wildcard answer, add NSEC / NSEC3 + dbg_ns("Adding NSEC/NSEC3 for wildcard answer.\n"); + ret = ns_put_nsec_nsec3_wildcard_answer(node, closest_encloser, + previous, zone, qname, resp); + ns_put_authority_ns(zone, resp); + } + + if (ret == KNOT_EOK) { + ns_put_additional(resp); + } + return ret; +} + +/*----------------------------------------------------------------------------*/ + +/*! + * \brief Synthetizes a CNAME RR from a DNAME. + * + * \param dname_rrset DNAME RRSet to synthetize from (only the first RR is + * used). + * \param qname Name to be used as the owner name of the synthetized CNAME. + * + * \return Synthetized CNAME RRset (this is a newly created RRSet, remember to + * free it). + */ +static knot_rrset_t *ns_cname_from_dname(const knot_rrset_t *dname_rrset, + const knot_dname_t *qname) +{ + dbg_ns("Synthetizing CNAME from DNAME...\n"); + + // create new CNAME RRSet + + knot_dname_t *owner = knot_dname_deep_copy(qname); + if (owner == NULL) { + return NULL; + } + + knot_rrset_t *cname_rrset = knot_rrset_new( + owner, KNOT_RRTYPE_CNAME, KNOT_CLASS_IN, SYNTH_CNAME_TTL); + + /* Release owner, as it's retained in rrset. */ + knot_dname_release(owner); + + if (cname_rrset == NULL) { + return NULL; + } + + // replace last labels of qname with DNAME + knot_dname_t *cname = knot_dname_replace_suffix(qname, + knot_dname_size(knot_rrset_owner(dname_rrset)), + knot_rdata_get_item(knot_rrset_rdata(dname_rrset), 0)->dname); +dbg_ns_exec( + char *name = knot_dname_to_str(cname); + dbg_ns("CNAME canonical name: %s.\n", name); + free(name); +); + knot_rdata_t *cname_rdata = knot_rdata_new(); + knot_rdata_item_t cname_rdata_item; + cname_rdata_item.dname = cname; + knot_rdata_set_items(cname_rdata, &cname_rdata_item, 1); + + knot_rrset_add_rdata(cname_rrset, cname_rdata); + + return cname_rrset; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Checks if the name created by replacing the owner of \a dname_rrset + * in the \a qname by the DNAME's target would be longer than allowed. + * + * \param dname_rrset DNAME RRSet to be used for the check. + * \param qname Name whose part is to be replaced. + * + * \retval <>0 if the created domain name would be too long. + * \retval 0 otherwise. + */ +static int ns_dname_is_too_long(const knot_rrset_t *dname_rrset, + const knot_dname_t *qname) +{ + // TODO: add function for getting DNAME target + if (knot_dname_label_count(qname) + - knot_dname_label_count(knot_rrset_owner(dname_rrset)) + + knot_dname_label_count(knot_rdata_get_item( + knot_rrset_rdata(dname_rrset), 0)->dname) + > KNOT_MAX_DNAME_LENGTH) { + return 1; + } else { + return 0; + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief DNAME processing. + * + * This function adds the DNAME RRSet (and possibly its associated RRSIGs to the + * Answer section of the response, synthetizes CNAME record from the DNAME and + * adds it there too. It also stores the synthetized CNAME in the temporary + * RRSets of the response. + * + * \param dname_rrset DNAME RRSet to use. + * \param qname Searched name. + * \param resp Response. + */ +static void ns_process_dname(const knot_rrset_t *dname_rrset, + const knot_dname_t *qname, + knot_packet_t *resp) +{ +dbg_ns_exec( + char *name = knot_dname_to_str(knot_rrset_owner(dname_rrset)); + dbg_ns("Processing DNAME for owner %s...\n", name); + free(name); +); + // TODO: check the number of RRs in the RRSet?? + + // put the DNAME RRSet into the answer + knot_response_add_rrset_answer(resp, dname_rrset, 1, 0, 0); + ns_add_rrsigs(dname_rrset, resp, qname, + knot_response_add_rrset_answer, 1); + + if (ns_dname_is_too_long(dname_rrset, qname)) { + knot_response_set_rcode(resp, KNOT_RCODE_YXDOMAIN); + return; + } + + // synthetize CNAME (no way to tell that client supports DNAME) + knot_rrset_t *synth_cname = ns_cname_from_dname(dname_rrset, qname); + // add the synthetized RRSet to the Answer + knot_response_add_rrset_answer(resp, synth_cname, 1, 0, 0); + + // no RRSIGs for this RRSet + + // add the synthetized RRSet into list of temporary RRSets of response + knot_packet_add_tmp_rrset(resp, synth_cname); + + // do not search for the name in new zone (out-of-bailiwick) +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adds DNSKEY RRSet from the apex of a zone to the response. + * + * \param apex Zone apex node. + * \param resp Response. + */ +static void ns_add_dnskey(const knot_node_t *apex, knot_packet_t *resp) +{ + const knot_rrset_t *rrset = + knot_node_rrset(apex, KNOT_RRTYPE_DNSKEY); + if (rrset != NULL) { + knot_response_add_rrset_additional(resp, rrset, 0, 0, 0); + ns_add_rrsigs(rrset, resp, apex->owner, + knot_response_add_rrset_additional, 0); + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Answers the query from the given zone. + * + * This function performs the actual answering logic. + * + * \param zone Zone to use for answering. + * \param qname QNAME from the query. + * \param qtype QTYPE from the query. + * \param resp Response to fill in. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + * + * \todo Describe the answering logic in detail. + */ +static int ns_answer_from_zone(const knot_zone_contents_t *zone, + const knot_dname_t *qname, uint16_t qtype, + knot_packet_t *resp) +{ + const knot_node_t *node = NULL, *closest_encloser = NULL, + *previous = NULL; + int cname = 0, auth_soa = 0, ret = 0, find_ret = 0; + +search: +#ifdef USE_HASH_TABLE + /*! \todo Check version. */ + find_ret = knot_zone_contents_find_dname_hash(zone, qname, &node, + &closest_encloser); +// node = knot_node_current(node); +// closest_encloser = knot_node_current(closest_encloser); +#else + /*! \todo Check version. */ + find_ret = knot_zone_contents_find_dname(zone, qname, &node, + &closest_encloser, &previous); + node = knot_node_current(node); + closest_encloser = knot_node_current(closest_encloser); + previous = knot_node_current(previous); +#endif + if (find_ret == KNOT_EBADARG) { + return NS_ERR_SERVFAIL; + } + +dbg_ns_exec( + char *name; + if (node) { + name = knot_dname_to_str(node->owner); + dbg_ns("zone_find_dname() returned node %s ", name); + free(name); + } else { + dbg_ns("zone_find_dname() returned no node,"); + } + + if (closest_encloser != NULL) { + name = knot_dname_to_str(closest_encloser->owner); + dbg_ns(" closest encloser %s.\n", name); + free(name); + } else { + dbg_ns(" closest encloser (nil).\n"); + } + if (previous != NULL) { + name = knot_dname_to_str(previous->owner); + dbg_ns(" and previous node: %s.\n", name); + free(name); + } else { + dbg_ns(" and previous node: (nil).\n"); + } +); + if (find_ret == KNOT_EBADZONE) { + // possible only if we followed cname + assert(cname != 0); + knot_response_set_rcode(resp, KNOT_RCODE_NOERROR); + auth_soa = 1; + knot_response_set_aa(resp); + goto finalize; + } + +have_node: + dbg_ns("Closest encloser is deleg. point? %s\n", + (knot_node_is_deleg_point(closest_encloser)) ? "yes" : "no"); + + dbg_ns("Closest encloser is non authoritative? %s\n", + (knot_node_is_non_auth(closest_encloser)) ? "yes" : "no"); + + if (knot_node_is_deleg_point(closest_encloser) + || knot_node_is_non_auth(closest_encloser)) { + ret = ns_referral(closest_encloser, zone, qname, resp); + goto finalize; + } + + if (find_ret == KNOT_ZONE_NAME_NOT_FOUND) { + // DNAME? + const knot_rrset_t *dname_rrset = knot_node_rrset( + closest_encloser, KNOT_RRTYPE_DNAME); + if (dname_rrset != NULL) { + ns_process_dname(dname_rrset, qname, resp); + auth_soa = 1; + knot_response_set_aa(resp); + goto finalize; + } + // else check for a wildcard child + const knot_node_t *wildcard_node = + knot_node_wildcard_child(closest_encloser, 1); + + if (wildcard_node == NULL) { + dbg_ns("No wildcard node. (cname: %d)\n", + cname); + auth_soa = 1; + if (cname == 0) { + dbg_ns("Setting NXDOMAIN RCODE.\n"); + // return NXDOMAIN + knot_response_set_rcode(resp, + KNOT_RCODE_NXDOMAIN); + if (ns_put_nsec_nsec3_nxdomain(zone, previous, + closest_encloser, qname, resp) != 0) { + return NS_ERR_SERVFAIL; + } + } else { + knot_response_set_rcode(resp, + KNOT_RCODE_NOERROR); + } + knot_response_set_aa(resp); + goto finalize; + } + // else set the node from which to take the answers to wild.node + node = wildcard_node; + } + + // now we have the node for answering + if (knot_node_is_deleg_point(node) || knot_node_is_non_auth(node)) { + ret = ns_referral(node, zone, qname, resp); + goto finalize; + } + + if (knot_node_rrset(node, KNOT_RRTYPE_CNAME) != NULL) { +dbg_ns_exec( + char *name = knot_dname_to_str(node->owner); + dbg_ns("Node %s has CNAME record, resolving...\n", + name); + free(name); +); + const knot_dname_t *act_name = qname; + ns_follow_cname(&node, &act_name, resp, + knot_response_add_rrset_answer, 1); +dbg_ns_exec( + char *name = (node != NULL) ? knot_dname_to_str(node->owner) + : "(nil)"; + char *name2 = knot_dname_to_str(act_name); + dbg_ns("Canonical name: %s (%p), node found: %p\n", + name2, act_name, node); + dbg_ns("The node's owner: %s (%p)\n", name, (node != NULL) + ? node->owner : NULL); + if (node != NULL) { + free(name); + } + free(name2); +); + qname = act_name; + cname = 1; + + // otherwise search for the new name + if (node == NULL) { + goto search; + } else if (node->owner != act_name) { + // the stored node is closest encloser + find_ret = KNOT_ZONE_NAME_NOT_FOUND; + closest_encloser = node; + node = NULL; + goto have_node; + } // else do nothing, just continue + } + + ret = ns_answer_from_node(node, closest_encloser, previous, zone, qname, + qtype, resp); + if (ret == NS_ERR_SERVFAIL) { + // in this case we should drop the response and send an error + // for now, just send the error code with a non-complete answer +// knot_response_set_rcode(resp, KNOT_RCODE_SERVFAIL); +// goto finalize; + return ret; + } else if (ret != KNOT_EOK) { + /*! \todo Handle RCODE return values!!! */ + goto finalize; + } + knot_response_set_aa(resp); + knot_response_set_rcode(resp, KNOT_RCODE_NOERROR); + + // this is the only case when the servers answers from + // particular node, i.e. the only case when it may return SOA + // or NS records in Answer section + if (DNSSEC_ENABLED + && knot_query_dnssec_requested(knot_packet_query(resp)) + && node == knot_zone_contents_apex(zone) + && (qtype == KNOT_RRTYPE_SOA || qtype == KNOT_RRTYPE_NS)) { + ns_add_dnskey(node, resp); + } + +finalize: + if (ret == KNOT_EOK && auth_soa) { + ns_put_authority_soa(zone, resp); + } + + return ret; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Answers the query from the given zone database. + * + * First it searches for a zone to answer from. If there is none, it sets + * RCODE REFUSED to the response and ends. Otherwise it tries to answer the + * query using the found zone (see ns_answer_from_zone()). + * + * \param db Zone database to use for answering. + * \param resp Response that holds the parsed query. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_answer(knot_zonedb_t *db, knot_packet_t *resp) +{ + const knot_dname_t *qname = knot_packet_qname(resp); + assert(qname != NULL); + + uint16_t qtype = knot_packet_qtype(resp); +dbg_ns_exec( + char *name_str = knot_dname_to_str(qname); + dbg_ns("Trying to find zone for QNAME %s\n", name_str); + free(name_str); +); + // find zone in which to search for the name + const knot_zone_t *zone = + ns_get_zone_for_qname(db, qname, qtype); + const knot_zone_contents_t *contents = knot_zone_contents(zone); + + // if no zone found, return REFUSED + if (zone == NULL) { + dbg_ns("No zone found.\n"); + knot_response_set_rcode(resp, KNOT_RCODE_REFUSED); + //knot_dname_free(&qname); + return KNOT_EOK; + } else if (contents == NULL) { + dbg_ns("Zone expired or not bootstrapped. Reply SERVFAIL.\n"); + knot_response_set_rcode(resp, KNOT_RCODE_SERVFAIL); + return KNOT_EOK; + } + +dbg_ns_exec( + char *name_str2 = knot_dname_to_str(zone->contents->apex->owner); + dbg_ns("Found zone for QNAME %s\n", name_str2); + free(name_str2); +); + + // take the zone contents and use only them for answering + + return ns_answer_from_zone(contents, qname, qtype, resp); + + //knot_dname_free(&qname); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Converts the response to wire format. + * + * \param resp Response to convert. + * \param wire Place for the wire format of the response. + * \param wire_size In: space available for the wire format in bytes. + * Out: actual size of the wire format in bytes. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_response_to_wire(knot_packet_t *resp, uint8_t *wire, + size_t *wire_size) +{ + uint8_t *rwire = NULL; + size_t rsize = 0; + int ret = 0; + + if ((ret = knot_packet_to_wire(resp, &rwire, &rsize)) + != KNOT_EOK) { + dbg_ns("Error converting response packet " + "to wire format (error %d).\n", ret); + return NS_ERR_SERVFAIL; + } + + if (rsize > *wire_size) { + dbg_ns("Reponse size (%zu) larger than allowed wire size " + "(%zu).\n", rsize, *wire_size); + return NS_ERR_SERVFAIL; + } + + if (rwire != wire) { + dbg_ns("Wire format reallocated, copying to place for " + "wire.\n"); + memcpy(wire, rwire, rsize); + } else { + dbg_ns("Using the same space or wire format.\n"); + } + + *wire_size = rsize; + //free(rwire); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates a wire format of an error response from partially created + * response. + * + * \param resp Response to use. + * \param wire Place for the wire format of the response. + * \param wire_size In: space available for the wire format in bytes. + * Out: actual size of the wire format in bytes. + * + * \retval KNOT_EOK + * \retval NS_ERR_SERVFAIL + */ +static int ns_error_response_to_wire(knot_packet_t *resp, uint8_t *wire, + size_t *wire_size) +{ + /* Do not call the packet conversion function + * wire format is assembled, but COUNTs in header are not set. + * This is ideal, we just truncate the packet after the question. + */ + dbg_ns("Creating error response.\n"); + + size_t rsize = knot_packet_question_size(knot_packet_query(resp)); + dbg_ns("Error response (~ query) size: %zu\n", rsize); + + // take 'qsize' from the current wireformat of the response + // it is already assembled - Header and Question section are copied + const uint8_t *rwire = knot_packet_wireformat(resp); + if (rsize > *wire_size) { + dbg_ns("Reponse size (%zu) larger than allowed wire size" + " (%zu).\n", rsize, *wire_size); + return NS_ERR_SERVFAIL; + } + + assert(rwire != wire); + + /*! \todo Why is this copied?? Why we cannot use resp->wireformat?? */ + memcpy(wire, rwire, rsize); + + *wire_size = rsize; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +typedef struct ns_axfr_params { + knot_ns_xfr_t *xfr; + int ret; +} ns_axfr_params_t; + +/*----------------------------------------------------------------------------*/ + +int knot_ns_tsig_required(int packet_nr) +{ + dbg_ns_detail("ns_tsig_required(%d): %d\n", packet_nr, + (packet_nr % KNOT_NS_TSIG_FREQ == 0)); + return (packet_nr % KNOT_NS_TSIG_FREQ == 0); +} + +/*----------------------------------------------------------------------------*/ + +static int ns_xfr_send_and_clear(knot_ns_xfr_t *xfr, int add_tsig) +{ + assert(xfr != NULL); + assert(xfr->query != NULL); + assert(xfr->response != NULL); + assert(xfr->wire != NULL); + assert(xfr->send != NULL); + + // Transform the packet into wire format + dbg_ns("Converting response to wire format..\n"); + size_t real_size = xfr->wire_size; + if (ns_response_to_wire(xfr->response, xfr->wire, &real_size) != 0) { + return NS_ERR_SERVFAIL; +// // send back SERVFAIL (as this is our problem) +// ns_error_response(nameserver, +// knot_wire_get_id(query_wire), +// KNOT_RCODE_SERVFAIL, response_wire, +// rsize); + } + + int res = 0; + + size_t digest_real_size = xfr->digest_max_size; + + dbg_ns_detail("xfr->tsig_key=%p\n", xfr->tsig_key); + /*! \note [TSIG] Generate TSIG if required (during XFR/IN). */ + if (xfr->tsig_key && add_tsig) { + if (xfr->packet_nr == 0) { + /* Add key, digest and digest length. */ + dbg_ns_detail("Calling tsig_sign(): %p, %zu, %zu, " + "%p, %zu, %p, %zu, %p\n", + xfr->wire, real_size, xfr->wire_size, + xfr->digest, xfr->digest_size, xfr->digest, + digest_real_size, xfr->tsig_key); + res = knot_tsig_sign(xfr->wire, &real_size, + xfr->wire_size, xfr->digest, + xfr->digest_size, xfr->digest, + &digest_real_size, + xfr->tsig_key); + } else { + /* Add key, digest and digest length. */ + dbg_ns_detail("Calling tsig_sign_next()\n"); + res = knot_tsig_sign_next(xfr->wire, &real_size, + xfr->wire_size, + xfr->digest, + xfr->digest_size, + xfr->digest, + &digest_real_size, + xfr->tsig_key); + } + + dbg_ns_detail("Sign function returned: %s\n", + knot_strerror(res)); + dbg_ns_detail("Real size of digest: %zu\n", digest_real_size); + + if (res != KNOT_EOK) { + return res; + } + + assert(digest_real_size > 0); + // save the new previous digest size + xfr->digest_size = digest_real_size; + } + + // Send the response + dbg_ns("Sending response (size %zu)..\n", real_size); + //dbg_ns_hex((const char *)xfr->wire, real_size); + res = xfr->send(xfr->session, &xfr->addr, xfr->wire, real_size); + if (res < 0) { + dbg_ns("Send returned %d\n", res); + return res; + } else if (res != real_size) { + dbg_ns("AXFR did not send right amount of bytes." + " Transfer size: %zu, sent: %d\n", + real_size, res); + } + + // Clean the response structure + dbg_ns("Clearing response structure..\n"); + knot_response_clear(xfr->response, 0); + + // increment the packet number + ++xfr->packet_nr; + if (xfr->tsig_key && add_tsig) { + knot_packet_set_tsig_size(xfr->response, xfr->tsig_size); + } else { + knot_packet_set_tsig_size(xfr->response, 0); + } + + dbg_ns("Response structure after clearing:\n"); + knot_packet_dump(xfr->response); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static void ns_axfr_from_node(knot_node_t *node, void *data) +{ + assert(node != NULL); + assert(data != NULL); + + ns_axfr_params_t *params = (ns_axfr_params_t *)data; + + if (params->ret != KNOT_EOK) { + // just skip (will be called on next node with the same params + dbg_ns("Params contain error: %s, skipping node...\n", + knot_strerror(params->ret)); + return; + } + + dbg_ns("Params OK, answering AXFR from node %p.\n", node); +dbg_ns_exec( + char *name = knot_dname_to_str(knot_node_owner(node)); + dbg_ns("Node ownerr: %s\n", name); + free(name); +); + + if (knot_node_rrset_count(node) == 0) { + return; + } + + const knot_rrset_t **rrsets = knot_node_rrsets(node); + if (rrsets == NULL) { + params->ret = KNOT_ENOMEM; + return; + } + + int i = 0; + int ret = 0; + const knot_rrset_t *rrset = NULL; + while (i < knot_node_rrset_count(node)) { + assert(rrsets[i] != NULL); + rrset = rrsets[i]; +rrset: + dbg_ns(" Type: %s\n", + knot_rrtype_to_string(knot_rrset_type(rrset))); + + // do not add SOA + if (knot_rrset_type(rrset) == KNOT_RRTYPE_SOA) { + ++i; + continue; + } + + ret = knot_response_add_rrset_answer(params->xfr->response, + rrset, 0, 0, 1); + + if (ret == KNOT_ESPACE) { + // TODO: send the packet and clean the structure + dbg_ns("Packet full, sending..\n"); + ret = ns_xfr_send_and_clear(params->xfr, + knot_ns_tsig_required(params->xfr->packet_nr)); + if (ret != KNOT_EOK) { + // some wierd problem, we should end + params->ret = KNOT_ERROR; + break; + } + // otherwise try once more with the same RRSet + goto rrset; + } else if (ret != KNOT_EOK) { + // some wierd problem, we should end + params->ret = KNOT_ERROR; + break; + } + + // we can send the RRSets in any order, so add the RRSIGs now + rrset = knot_rrset_rrsigs(rrset); +rrsigs: + if (rrset == NULL) { + ++i; + continue; + } + + ret = knot_response_add_rrset_answer(params->xfr->response, + rrset, 0, 0, 1); + + if (ret == KNOT_ESPACE) { + // TODO: send the packet and clean the structure + dbg_ns("Packet full, sending..\n"); + ret = ns_xfr_send_and_clear(params->xfr, + knot_ns_tsig_required(params->xfr->packet_nr)); + if (ret != KNOT_EOK) { + // some wierd problem, we should end + params->ret = KNOT_ERROR; + break; + } + // otherwise try once more with the same RRSet + goto rrsigs; + } else if (ret != KNOT_EOK) { + // some wierd problem, we should end + params->ret = KNOT_ERROR; + break; + } + + // this way only whole RRSets are always sent + // we guess it will not create too much overhead + + ++i; + } + if (rrsets != NULL) { + free(rrsets); + } + + /*! \todo maybe distinguish some error codes. */ + //params->ret = (ret == 0) ? KNOT_EOK : KNOT_ERROR; +} + +/*----------------------------------------------------------------------------*/ + +static int ns_axfr_from_zone(knot_zone_contents_t *zone, knot_ns_xfr_t *xfr) +{ + assert(xfr != NULL); + assert(xfr->query != NULL); + assert(xfr->response != NULL); + assert(xfr->wire != NULL); + assert(xfr->send != NULL); + + ns_axfr_params_t params; + memset(¶ms, 0, sizeof(ns_axfr_params_t)); + params.xfr = xfr; + params.ret = KNOT_EOK; + + xfr->packet_nr = 0; + + /* + * First SOA + */ + + // retrieve SOA - must be send as first and last RR + const knot_rrset_t *soa_rrset = knot_node_rrset( + knot_zone_contents_apex(zone), KNOT_RRTYPE_SOA); + if (soa_rrset == NULL) { + // some really serious error + return KNOT_ERROR; + } + + int ret; + + // add SOA RR to the response + ret = knot_response_add_rrset_answer(xfr->response, soa_rrset, 0, 0, 1); + if (ret != KNOT_EOK) { + // something is really wrong + return KNOT_ERROR; + } + + // add the SOA's RRSIG + const knot_rrset_t *rrset = knot_rrset_rrsigs(soa_rrset); + if (rrset != NULL + && (ret = knot_response_add_rrset_answer(xfr->response, rrset, + 0, 0, 1)) != KNOT_EOK) { + // something is really wrong, these should definitely fit in + return KNOT_ERROR; + } + + knot_zone_contents_tree_apply_inorder(zone, ns_axfr_from_node, + ¶ms); + + if (params.ret != KNOT_EOK) { + return KNOT_ERROR; // maybe do something with the code + } + + knot_zone_contents_nsec3_apply_inorder(zone, ns_axfr_from_node, + ¶ms); + + if (params.ret != KNOT_EOK) { + return KNOT_ERROR; // maybe do something with the code + } + + /* + * Last SOA + */ + + // try to add the SOA to the response again (last RR) + ret = knot_response_add_rrset_answer(xfr->response, soa_rrset, 0, 0, 1); + if (ret == KNOT_ESPACE) { + + // if there is not enough space, send the response and + // add the SOA record to a new packet + dbg_ns("Packet full, sending..\n"); + ret = ns_xfr_send_and_clear(xfr, + knot_ns_tsig_required(xfr->packet_nr)); + if (ret != KNOT_EOK) { + return ret; + } + + ret = knot_response_add_rrset_answer(xfr->response, + soa_rrset, 0, 0, 1); + if (ret != KNOT_EOK) { + return KNOT_ERROR; + } + + } else if (ret != KNOT_EOK) { + // something is really wrong + return KNOT_ERROR; + } + + dbg_ns("Sending packet...\n"); + return ns_xfr_send_and_clear(xfr, 1); +} + +/*----------------------------------------------------------------------------*/ + +static int ns_ixfr_put_rrset(knot_ns_xfr_t *xfr, const knot_rrset_t *rrset) +{ + int res = knot_response_add_rrset_answer(xfr->response, rrset, + 0, 0, 0); + if (res == KNOT_ESPACE) { + knot_response_set_rcode(xfr->response, KNOT_RCODE_NOERROR); + /*! \todo Probably rename the function. */ + ns_xfr_send_and_clear(xfr, knot_ns_tsig_required(xfr->packet_nr)); + + res = knot_response_add_rrset_answer(xfr->response, + rrset, 0, 0, 0); + } + + if (res != KNOT_EOK) { + dbg_ns("Error putting origin SOA to IXFR reply: %s\n", + knot_strerror(res)); + /*! \todo Probably send back AXFR instead. */ + knot_response_set_rcode(xfr->response, + KNOT_RCODE_SERVFAIL); + /*! \todo Probably rename the function. */ + ns_xfr_send_and_clear(xfr, 1); + //socket_close(xfr->session); /*! \todo Remove for UDP.*/ + return KNOT_ERROR; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int ns_ixfr_put_changeset(knot_ns_xfr_t *xfr, const knot_changeset_t *chgset) +{ + // 1) put origin SOA + int res = ns_ixfr_put_rrset(xfr, chgset->soa_from); + if (res != KNOT_EOK) { + return res; + } + + // 2) put remove RRSets + for (int i = 0; i < chgset->remove_count; ++i) { + res = ns_ixfr_put_rrset(xfr, chgset->remove[i]); + if (res != KNOT_EOK) { + return res; + } + } + + // 1) put target SOA + res = ns_ixfr_put_rrset(xfr, chgset->soa_to); + if (res != KNOT_EOK) { + return res; + } + + // 2) put remove RRSets + for (int i = 0; i < chgset->add_count; ++i) { + res = ns_ixfr_put_rrset(xfr, chgset->add[i]); + if (res != KNOT_EOK) { + return res; + } + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int ns_ixfr_from_zone(knot_ns_xfr_t *xfr) +{ + assert(xfr != NULL); + assert(xfr->zone != NULL); + assert(xfr->query != NULL); + assert(xfr->response != NULL); + assert(knot_packet_authority_rrset_count(xfr->query) > 0); + assert(xfr->data != NULL); + + /*! \todo REMOVE start */ +// const knot_rrset_t *zone_soa = +// knot_node_rrset(knot_zone_contents_apex( +// knot_zone_contents(xfr->zone)), +// KNOT_RRTYPE_SOA); +// // retrieve origin (xfr) serial and target (zone) serial +// uint32_t zone_serial = knot_rdata_soa_serial( +// knot_rrset_rdata(zone_soa)); +// uint32_t xfr_serial = knot_rdata_soa_serial(knot_rrset_rdata( +// knot_packet_authority_rrset(xfr->query, 0))); + +// // 3) load changesets from journal +// knot_changesets_t *chgsets = (knot_changesets_t *) +// calloc(1, sizeof(knot_changesets_t)); +// int res = xfr_load_changesets(xfr->zone, chgsets, xfr_serial, +// zone_serial); +// if (res != KNOT_EOK) { +// dbg_ns("IXFR query cannot be answered: %s.\n", +// knot_strerror(res)); +// /*! \todo Probably send back AXFR instead. */ +// knot_response_set_rcode(xfr->response, KNOT_RCODE_SERVFAIL); +// /*! \todo Probably rename the function. */ +// ns_axfr_send_and_clear(xfr); +// //socket_close(xfr->session); /*! \todo Remove for UDP. */ +// return 1; +// } + + /*! \todo REMOVE end */ + + knot_changesets_t *chgsets = (knot_changesets_t *)xfr->data; + knot_zone_contents_t* contents = knot_zone_get_contents(xfr->zone); + assert(contents); + const knot_rrset_t *zone_soa = + knot_node_rrset(knot_zone_contents_apex(contents), + KNOT_RRTYPE_SOA); + + // 4) put the zone SOA as the first Answer RR + int res = knot_response_add_rrset_answer(xfr->response, zone_soa, 0, + 0, 0); + if (res != KNOT_EOK) { + dbg_ns("IXFR query cannot be answered: %s.\n", + knot_strerror(res)); + knot_response_set_rcode(xfr->response, + KNOT_RCODE_SERVFAIL); + /*! \todo Probably rename the function. */ + ns_xfr_send_and_clear(xfr, 1); +// socket_close(xfr->session); /*! \todo Remove for UDP.*/ + return 1; + } + + // 5) put the changesets into the response while they fit in + for (int i = 0; i < chgsets->count; ++i) { + res = ns_ixfr_put_changeset(xfr, &chgsets->sets[i]); + if (res != KNOT_EOK) { + // answer is sent, socket is closed + return KNOT_EOK; + } + } + + if (chgsets->count > 0) { + res = ns_ixfr_put_rrset(xfr, zone_soa); + } + + if (res == KNOT_EOK) { + /*! \todo Probably rename the function. */ + ns_xfr_send_and_clear(xfr, 1); + //socket_close(xfr->session); /*! \todo Remove for UDP.*/ + return 1; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int ns_ixfr(knot_ns_xfr_t *xfr) +{ + assert(xfr != NULL); + assert(xfr->query != NULL); + assert(xfr->response != NULL); + assert(knot_packet_qtype(xfr->response) == KNOT_RRTYPE_IXFR); + + // check if there is the required authority record + if ((knot_packet_authority_rrset_count(xfr->query) <= 0)) { + // malformed packet + dbg_ns("IXFR query does not contain authority record.\n"); + knot_response_set_rcode(xfr->response, KNOT_RCODE_FORMERR); + /*! \todo Probably rename the function. */ + ns_xfr_send_and_clear(xfr, 1); + //socket_close(xfr->session); + return 1; + } + + const knot_rrset_t *soa = knot_packet_authority_rrset(xfr->query, 0); + const knot_dname_t *qname = knot_packet_qname(xfr->response); + + // check if XFR QNAME and SOA correspond + if (knot_packet_qtype(xfr->query) != KNOT_RRTYPE_IXFR + || knot_rrset_type(soa) != KNOT_RRTYPE_SOA + || knot_dname_compare(qname, knot_rrset_owner(soa)) != 0) { + // malformed packet + dbg_ns("IXFR query is malformed.\n"); + knot_response_set_rcode(xfr->response, KNOT_RCODE_FORMERR); + /*! \todo Probably rename the function. */ + ns_xfr_send_and_clear(xfr, 1); + //socket_close(xfr->session); /*! \todo Remove for UDP. */ + return 1; + } + + return ns_ixfr_from_zone(xfr); +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ns_prepare_response(knot_nameserver_t *nameserver, + knot_packet_t *query, knot_packet_t **resp, + size_t max_size) +{ + assert(max_size >= 500); + + // initialize response packet structure + *resp = knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); + if (*resp == NULL) { + dbg_ns("Failed to create packet structure.\n"); + return KNOT_ENOMEM; + } + + int ret = knot_packet_set_max_size(*resp, max_size); + //(*resp)->wireformat = response_wire;; + //(*resp)->max_size = max_size; + + if (ret != KNOT_EOK) { + dbg_ns("Failed to init response structure.\n"); + knot_packet_free(resp); + return ret; + } + + ret = knot_response_init_from_query(*resp, query); + + if (ret != KNOT_EOK) { + dbg_ns("Failed to init response structure.\n"); + knot_packet_free(resp); + return ret; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int32_t ns_serial_difference(uint32_t s1, uint32_t s2) +{ + return (((int64_t)s1 - s2) % ((int64_t)1 << 32)); +} + +/*----------------------------------------------------------------------------*/ +/* Public functions */ +/*----------------------------------------------------------------------------*/ + +knot_nameserver_t *knot_ns_create() +{ + knot_nameserver_t *ns = malloc(sizeof(knot_nameserver_t)); + if (ns == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + ns->data = 0; + + // Create zone database structure + dbg_ns("Creating Zone Database structure...\n"); + ns->zone_db = knot_zonedb_new(); + if (ns->zone_db == NULL) { + ERR_ALLOC_FAILED; + free(ns); + return NULL; + } + + // prepare empty response with SERVFAIL error + knot_packet_t *err = knot_packet_new(KNOT_PACKET_PREALLOC_NONE); + if (err == NULL) { + ERR_ALLOC_FAILED; + free(ns); + return NULL; + } + + dbg_ns("Created default empty response...\n"); + + int rc = knot_packet_set_max_size(err, KNOT_WIRE_HEADER_SIZE); + if (rc != KNOT_EOK) { + dbg_ns("Error creating default error response: %s.\n", + knot_strerror(rc)); + free(ns); + knot_packet_free(&err); + return NULL; + } + + rc = knot_response_init(err); + if (rc != KNOT_EOK) { + dbg_ns("Error initializing default error response:" + " %s.\n", knot_strerror(rc)); + free(ns); + knot_packet_free(&err); + return NULL; + } + + knot_response_set_rcode(err, KNOT_RCODE_SERVFAIL); + ns->err_resp_size = 0; + + dbg_ns("Converting default empty response to wire format...\n"); + + uint8_t *error_wire = NULL; + + if (knot_packet_to_wire(err, &error_wire, &ns->err_resp_size) != 0) { + dbg_ns("Error while converting " + "default error response to " + "wire format \n"); + knot_packet_free(&err); + free(ns); + return NULL; + } + + ns->err_response = (uint8_t *)malloc(ns->err_resp_size); + if (ns->err_response == NULL) { + dbg_ns("Error while converting default " + "error response to wire format \n"); + knot_packet_free(&err); + free(ns); + return NULL; + } + + memcpy(ns->err_response, error_wire, ns->err_resp_size); + + dbg_ns("Done..\n"); + + knot_packet_free(&err); + + if (EDNS_ENABLED) { + ns->opt_rr = knot_edns_new(); + if (ns->opt_rr == NULL) { + dbg_ns("Error while preparing OPT RR of the" + " server.\n"); + knot_packet_free(&err); + free(ns); + return NULL; + } + knot_edns_set_version(ns->opt_rr, EDNS_VERSION); + knot_edns_set_payload(ns->opt_rr, MAX_UDP_PAYLOAD_EDNS); + } else { + ns->opt_rr = NULL; + } + + knot_packet_free(&err); + + return ns; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ns_parse_packet(const uint8_t *query_wire, size_t qsize, + knot_packet_t *packet, knot_packet_type_t *type) +{ + if (packet == NULL || query_wire == NULL || type == NULL) { + dbg_ns("Missing parameter to query parsing.\n"); + return KNOT_EBADARG; + } + + dbg_ns("ns_parse_packet() called with query size %zu.\n", qsize); + //dbg_ns_hex((char *)query_wire, qsize); + + if (qsize < 2) { + return KNOT_EMALF; + } + + // 1) create empty response + dbg_ns("Parsing packet...\n"); + //parsed = knot_response_new_empty(NULL); + + int ret = 0; + + if ((ret = knot_packet_parse_from_wire(packet, query_wire, + qsize, 1)) != 0) { + dbg_ns("Error while parsing packet, " + "libknot error '%s'.\n", knot_strerror(ret)); +// knot_response_free(&parsed); + return KNOT_RCODE_FORMERR; + } + + dbg_ns("Parsed packet header and Question:\n"); + knot_packet_dump(packet); + + // 3) determine the query type + switch (knot_packet_opcode(packet)) { + case KNOT_OPCODE_QUERY: + switch (knot_packet_qtype(packet)) { + case KNOT_RRTYPE_AXFR: + *type = (knot_packet_is_query(packet)) + ? KNOT_QUERY_AXFR : KNOT_RESPONSE_AXFR; + break; + case KNOT_RRTYPE_IXFR: + *type = (knot_packet_is_query(packet)) + ? KNOT_QUERY_IXFR : KNOT_RESPONSE_IXFR; + break; + default: + *type = (knot_packet_is_query(packet)) + ? KNOT_QUERY_NORMAL : KNOT_RESPONSE_NORMAL; + } + + break; + case KNOT_OPCODE_NOTIFY: + *type = (knot_packet_is_query(packet)) + ? KNOT_QUERY_NOTIFY : KNOT_RESPONSE_NOTIFY; + break; + case KNOT_OPCODE_UPDATE: + if(knot_packet_is_query(packet)) { + *type = KNOT_QUERY_UPDATE; + } else { + return KNOT_RCODE_FORMERR; + } + break; + default: + return KNOT_RCODE_NOTIMPL; + } + +// knot_packet_free(&packet); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +void knot_ns_error_response(const knot_nameserver_t *nameserver, uint16_t query_id, + uint8_t rcode, uint8_t *response_wire, size_t *rsize) +{ + //dbg_ns("Error response: \n"); + //dbg_ns_hex((const char *)nameserver->err_response, + // nameserver->err_resp_size); + + memcpy(response_wire, nameserver->err_response, + nameserver->err_resp_size); + // copy ID of the query + knot_wire_set_id(response_wire, query_id); + // set the RCODE + knot_wire_set_rcode(response_wire, rcode); + *rsize = nameserver->err_resp_size; +} + +/*----------------------------------------------------------------------------*/ + +void knot_ns_error_response_full(knot_nameserver_t *nameserver, + knot_packet_t *response, uint8_t rcode, + uint8_t *response_wire, size_t *rsize) +{ + knot_response_set_rcode(response, rcode); + + if (ns_error_response_to_wire(response, response_wire, rsize) != 0) { + knot_ns_error_response(nameserver, knot_packet_id( + knot_packet_query(response)), + KNOT_RCODE_SERVFAIL, response_wire, + rsize); + } +} + +/*----------------------------------------------------------------------------*/ + +int knot_ns_answer_normal(knot_nameserver_t *nameserver, knot_packet_t *query, + uint8_t *response_wire, size_t *rsize) +{ + dbg_ns("ns_answer_normal()\n"); + + // first, parse the rest of the packet + assert(knot_packet_is_query(query)); + dbg_ns("Query - parsed: %zu, total wire size: %zu\n", + knot_packet_parsed(query), knot_packet_size(query)); + int ret; + + ret = knot_packet_parse_rest(query); + if (ret != KNOT_EOK) { + dbg_ns("Failed to parse rest of the query: " + "%s.\n", knot_strerror(ret)); + knot_ns_error_response(nameserver, knot_packet_id(query), + (ret == KNOT_EMALF) + ? KNOT_RCODE_FORMERR + : KNOT_RCODE_SERVFAIL, response_wire, + rsize); + return KNOT_EOK; + } + + /* + * Semantic checks - if ANCOUNT > 0 or NSCOUNT > 0, return FORMERR. + * + * If any xxCOUNT is less or more than actual RR count + * the previously called knot_packet_parse_rest() will recognize this. + * + * Check the QDCOUNT and in case of anything but 1 send back + * FORMERR + */ + if (knot_packet_ancount(query) > 0 + || knot_packet_nscount(query) > 0 + || knot_packet_qdcount(query) != 1) { + dbg_ns("ANCOUNT or NSCOUNT not 0 in query, reply FORMERR.\n"); + knot_ns_error_response(nameserver, knot_packet_id(query), + KNOT_RCODE_FORMERR, response_wire, + rsize); + return KNOT_EOK; + } + + size_t resp_max_size = 0; + + assert(*rsize >= MAX_UDP_PAYLOAD); + + knot_packet_dump(query); + + if (knot_query_edns_supported(query)) { + if (knot_edns_get_payload(&query->opt_rr) < + knot_edns_get_payload(nameserver->opt_rr)) { + resp_max_size = knot_edns_get_payload(&query->opt_rr); + } else { + resp_max_size = knot_edns_get_payload( + nameserver->opt_rr); + } + } + + if (resp_max_size < MAX_UDP_PAYLOAD) { + resp_max_size = MAX_UDP_PAYLOAD; + } + + knot_packet_t *response; + ret = knot_ns_prepare_response(nameserver, query, &response, + resp_max_size); + if (ret != KNOT_EOK) { + knot_ns_error_response(nameserver, knot_packet_id(query), + KNOT_RCODE_SERVFAIL, response_wire, + rsize); + return KNOT_EOK; + } + + dbg_ns("Query - parsed: %zu, total wire size: %zu\n", + query->parsed, query->size); + dbg_ns("Opt RR: version: %d, payload: %d\n", + query->opt_rr.version, query->opt_rr.payload); + + // get the answer for the query + rcu_read_lock(); + knot_zonedb_t *zonedb = rcu_dereference(nameserver->zone_db); + + dbg_ns("EDNS supported in query: %d\n", + knot_query_edns_supported(query)); + + // set the OPT RR to the response + if (knot_query_edns_supported(query)) { + /*! \todo API. */ +// if (knot_edns_get_payload(&query->opt_rr) > MAX_UDP_PAYLOAD) { +// ret = knot_packet_set_max_size(response, +// knot_edns_get_payload(&query->opt_rr)); +// } else { +// ret = knot_packet_set_max_size(response, +// MAX_UDP_PAYLOAD); +// } + +// if (ret != KNOT_EOK) { +// dbg_ns("Failed to set max size.\n"); +// knot_ns_error_response_full(nameserver, response, +// KNOT_RCODE_SERVFAIL, +// response_wire, rsize); +// return KNOT_EOK; +// } + + ret = knot_response_add_opt(response, nameserver->opt_rr, 1); + if (ret != KNOT_EOK) { + dbg_ns("Failed to set OPT RR to the response" + ": %s\n",knot_strerror(ret)); + } else { + // copy the DO bit from the query + if (knot_query_dnssec_requested(query)) { + /*! \todo API for this. */ + knot_edns_set_do(&response->opt_rr); + } + } + }/* else { + dbg_ns("Setting max size to %u.\n", MAX_UDP_PAYLOAD); + ret = knot_packet_set_max_size(response, MAX_UDP_PAYLOAD); + if (ret != KNOT_EOK) { + dbg_ns("Failed to set max size to %u\n", + MAX_UDP_PAYLOAD); + knot_ns_error_response_full(nameserver, response, + KNOT_RCODE_SERVFAIL, + response_wire, rsize); + return KNOT_EOK; + } + }*/ + + dbg_ns("Response max size: %zu\n", response->max_size); + + ret = ns_answer(zonedb, response); + if (ret != 0) { + // now only one type of error (SERVFAIL), later maybe more + knot_ns_error_response_full(nameserver, response, + KNOT_RCODE_SERVFAIL, + response_wire, rsize); + } else { + dbg_ns("Created response packet.\n"); + //knot_response_dump(resp); + knot_packet_dump(response); + + // 4) Transform the packet into wire format + if (ns_response_to_wire(response, response_wire, rsize) != 0) { + // send back SERVFAIL (as this is our problem) + knot_ns_error_response_full(nameserver, response, + KNOT_RCODE_SERVFAIL, + response_wire, rsize); + } + } + + rcu_read_unlock(); + knot_packet_free(&response); + + dbg_ns("Returning response with wire size %zu\n", *rsize); + //dbg_ns_hex((char *)response_wire, *rsize); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ns_init_xfr(knot_nameserver_t *nameserver, knot_ns_xfr_t *xfr) +{ + dbg_ns("knot_ns_init_xfr()\n"); + + if (nameserver == NULL || xfr == NULL) { + return KNOT_EBADARG; + } + + // no need to parse rest of the packet + /*! \todo Parse rest of packet because of EDNS. */ + int ret = knot_packet_parse_rest(xfr->query); + if (ret != KNOT_EOK) { + dbg_ns("Failed to parse rest of the query: %s\n", + knot_strerror(ret)); + knot_ns_error_response(nameserver, xfr->query->header.id, + (ret == KNOT_EMALF) ? KNOT_RCODE_FORMERR + : KNOT_RCODE_SERVFAIL, + xfr->wire, &xfr->wire_size); + ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, + xfr->wire_size); + return ret; + } + + dbg_packet("Parsed XFR query:\n"); + knot_packet_dump(xfr->query); + + // initialize response packet structure + knot_packet_t *response = knot_packet_new( + KNOT_PACKET_PREALLOC_RESPONSE); + if (response == NULL) { + dbg_ns("Failed to create packet structure.\n"); + /*! \todo xfr->wire is not NULL, will fail on assert! */ + knot_ns_error_response(nameserver, xfr->query->header.id, + KNOT_RCODE_SERVFAIL, xfr->wire, + &xfr->wire_size); + ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, + xfr->wire_size); + knot_packet_free(&response); + return ret; + } + + //int ret = knot_packet_set_max_size(response, xfr->wire_size); + response->wireformat = xfr->wire; + response->max_size = xfr->wire_size; + +// if (ret != KNOT_EOK) { +// dbg_ns("Failed to init response structure.\n"); +// /*! \todo xfr->wire is not NULL, will fail on assert! */ +// knot_ns_error_response(nameserver, xfr->query->header.id, +// KNOT_RCODE_SERVFAIL, xfr->wire, +// &xfr->wire_size); +// int res = xfr->send(xfr->session, &xfr->addr, xfr->wire, +// xfr->wire_size); +// knot_packet_free(&response); +// return res; +// } + + ret = knot_response_init_from_query(response, xfr->query); + + if (ret != KNOT_EOK) { + dbg_ns("Failed to init response structure.\n"); + /*! \todo xfr->wire is not NULL, will fail on assert! */ + knot_ns_error_response(nameserver, xfr->query->header.id, + KNOT_RCODE_SERVFAIL, xfr->wire, + &xfr->wire_size); + int res = xfr->send(xfr->session, &xfr->addr, xfr->wire, + xfr->wire_size); + knot_packet_free(&response); + return res; + } + + xfr->response = response; + + knot_zonedb_t *zonedb = rcu_dereference(nameserver->zone_db); + + const knot_dname_t *qname = knot_packet_qname(xfr->response); + + assert(knot_packet_qtype(xfr->response) == KNOT_RRTYPE_AXFR || + knot_packet_qtype(xfr->response) == KNOT_RRTYPE_IXFR); + +dbg_ns_exec( + char *name_str = knot_dname_to_str(qname); + dbg_ns("Trying to find zone with name %s\n", name_str); + free(name_str); +); + // find zone in which to search for the name + knot_zone_t *zone = knot_zonedb_find_zone(zonedb, qname); + + // if no zone found, return NotAuth + if (zone == NULL) { + dbg_ns("No zone found.\n"); + knot_response_set_rcode(xfr->response, KNOT_RCODE_NOTAUTH); + ns_xfr_send_and_clear(xfr, 1); + return KNOT_ENOZONE; + } + +dbg_ns_exec( + char *name2_str = knot_dname_to_str(qname); + dbg_ns("Found zone for name %s\n", name2_str); + free(name2_str); +); + xfr->zone = zone; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int ns_serial_compare(uint32_t s1, uint32_t s2) +{ + int32_t diff = ns_serial_difference(s1, s2); + return (s1 == s2) /* s1 equal to s2 */ + ? 0 + :((diff >= 1 && diff < ((uint32_t)1 << 31)) + ? 1 /* s1 larger than s2 */ + : -1); /* s1 less than s2 */ +} + +/*----------------------------------------------------------------------------*/ + +int ns_ixfr_load_serials(const knot_ns_xfr_t *xfr, uint32_t *serial_from, + uint32_t *serial_to) +{ + if (xfr == NULL || xfr->zone == NULL || serial_from == NULL + || serial_to == NULL) { + dbg_ns_detail("Wrong parameters: xfr=%p," + " xfr->zone = %p\n", xfr, xfr->zone); + return KNOT_EBADARG; + } + + const knot_zone_t *zone = xfr->zone; + const knot_zone_contents_t *contents = knot_zone_contents(zone); + if (!contents) { + dbg_ns_detail("Missing contents\n"); + return KNOT_EBADARG; + } + + if (knot_zone_contents_apex(contents) == NULL) { + dbg_ns_detail("No apex.\n"); + return KNOT_EBADARG; + } + + const knot_rrset_t *zone_soa = + knot_node_rrset(knot_zone_contents_apex(contents), + KNOT_RRTYPE_SOA); + if (zone_soa == NULL) { + dbg_ns_verb("No SOA.\n"); + return KNOT_EBADARG; + } + + if (knot_packet_nscount(xfr->query) < 1) { + dbg_ns_verb("No Authority record.\n"); + return KNOT_EMALF; + } + + if (knot_packet_authority_rrset(xfr->query, 0) == NULL) { + dbg_ns_verb("Authority record missing.\n"); + return KNOT_ERROR; + } + + // retrieve origin (xfr) serial and target (zone) serial + *serial_to = knot_rdata_soa_serial(knot_rrset_rdata(zone_soa)); + *serial_from = knot_rdata_soa_serial(knot_rrset_rdata( + knot_packet_authority_rrset(xfr->query, 0))); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ns_xfr_send_error(const knot_nameserver_t *nameserver, + knot_ns_xfr_t *xfr, knot_rcode_t rcode) +{ + /*! \todo Handle TSIG errors differently. */ + knot_response_set_rcode(xfr->response, rcode); + + /*! \todo Probably rename the function. */ + int ret = 0; + if ((ret = ns_xfr_send_and_clear(xfr, 1)) != KNOT_EOK) { + size_t size = 0; + knot_ns_error_response(nameserver, xfr->query->header.id, + KNOT_RCODE_SERVFAIL, xfr->wire, &size); + ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, size); + } + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ns_answer_axfr(knot_nameserver_t *nameserver, knot_ns_xfr_t *xfr) +{ + if (xfr == NULL || nameserver == NULL || xfr->zone == NULL) { + return KNOT_EBADARG; + } + + rcu_read_lock(); + + // take the contents and answer from them + int ret = 0; + knot_zone_contents_t *contents = knot_zone_get_contents(xfr->zone); + if (!contents) { + dbg_ns("AXFR failed on stub zone\n"); + /*! \todo replace with knot_ns_xfr_send_error() */ + knot_ns_error_response(nameserver, xfr->query->header.id, + KNOT_RCODE_SERVFAIL, xfr->wire, + &xfr->wire_size); + ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, + xfr->wire_size); + rcu_read_unlock(); + knot_packet_free(&xfr->response); + return KNOT_EOK; + } + + /*! + * \todo [TSIG] The TSIG data should already be stored in 'xfr'. + * Now just count the expected size of the TSIG RR and save it + * to the response structure. + */ + + /*! \todo [TSIG] Get the TSIG size from some API function. */ + if (xfr->tsig_size > 0) { + dbg_ns_detail("Setting TSIG size in packet: %zu\n", + xfr->tsig_size); + knot_packet_set_tsig_size(xfr->response, xfr->tsig_size); + } + + ret = ns_axfr_from_zone(contents, xfr); + + /*! \todo Somehow distinguish when it makes sense to send the SERVFAIL + * and when it does not. E.g. if there was problem in sending + * packet, it will probably fail when sending the SERVFAIL also. + */ + if (ret < 0) { + dbg_ns("AXFR failed, sending SERVFAIL.\n"); + // now only one type of error (SERVFAIL), later maybe more + /*! \todo xfr->wire is not NULL, will fail on assert! */ + /*! \todo replace with knot_ns_xfr_send_error() */ + knot_ns_error_response(nameserver, xfr->query->header.id, + KNOT_RCODE_SERVFAIL, xfr->wire, + &xfr->wire_size); + ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, + xfr->wire_size); + } else if (ret > 0) { + ret = KNOT_ERROR; + } + + rcu_read_unlock(); + + knot_packet_free(&xfr->response); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ns_answer_ixfr(knot_nameserver_t *nameserver, knot_ns_xfr_t *xfr) +{ + if (nameserver == NULL || xfr == NULL || xfr->zone == NULL + || xfr->response == NULL) { + return KNOT_EBADARG; + } + + //uint8_t *wire = NULL; + //size_t size = xfr->wire_size; + + // parse rest of the packet (we need the Authority record) + int ret = knot_packet_parse_rest(xfr->query); + if (ret != KNOT_EOK) { + dbg_ns("Failed to parse rest of the packet. Reply FORMERR.\n"); +// knot_ns_error_response_full(nameserver, xfr->response, +// KNOT_RCODE_FORMERR, xfr->wire, +// &size); + knot_ns_xfr_send_error(nameserver, xfr, KNOT_RCODE_FORMERR); + + //ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, size); + knot_packet_free(&xfr->response); + return ret; + } + + // check if the zone has contents + if (knot_zone_contents(xfr->zone) == NULL) { + dbg_ns("Zone expired or not bootstrapped. Reply SERVFAIL.\n"); + ret = knot_ns_xfr_send_error(nameserver, xfr, KNOT_RCODE_SERVFAIL); +// knot_ns_error_response_full(nameserver, xfr->response, +// KNOT_RCODE_SERVFAIL, xfr->wire, +// &size); + +// ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, size); + knot_packet_free(&xfr->response); + return ret; + } + + /*! + * \todo [TSIG] The TSIG data should already be stored in 'xfr'. + * Now just count the expected size of the TSIG RR and save it + * to the response structure. This should be optional, only if + * the request contained TSIG, i.e. if there is the data in 'xfr'. + */ + + /*! \todo [TSIG] Get the TSIG size from some API function. */ + if (xfr->tsig_size > 0) { + knot_packet_set_tsig_size(xfr->response, xfr->tsig_size); + } + + ret = ns_ixfr(xfr); + + /*! \todo Somehow distinguish when it makes sense to send the SERVFAIL + * and when it does not. E.g. if there was problem in sending + * packet, it will probably fail when sending the SERVFAIL also. + */ + if (ret < 0) { + dbg_ns("IXFR failed, sending SERVFAIL.\n"); + // now only one type of error (SERVFAIL), later maybe more + + /*! \todo Extract this to some function. */ +// knot_response_set_rcode(xfr->response, KNOT_RCODE_SERVFAIL); +// uint8_t *wire = NULL; +// ret = knot_packet_to_wire(xfr->response, &wire, &size); +// if (ret != KNOT_EOK) { +//// knot_ns_error_response(nameserver, +//// xfr->query->header.id, +//// KNOT_RCODE_SERVFAIL, xfr->wire, +//// &size); +//// ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, +//// size); +// knot_ns_xfr_send_error(xfr, KNOT_RCODE_SERVFAIL); +// knot_packet_free(&xfr->response); +// return ret; +// } else { +// ret = xfr->send(xfr->session, &xfr->addr, wire, size); +// } + knot_ns_xfr_send_error(nameserver, xfr, KNOT_RCODE_SERVFAIL); + } /*else if (ret > 0) { + ret = KNOT_ERROR; + }*/ + + knot_packet_free(&xfr->response); + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ns_process_axfrin(knot_nameserver_t *nameserver, knot_ns_xfr_t *xfr) +{ + /*! + * \todo [TSIG] Here we assume that 'xfr' contains TSIG information + * and the digest of the query sent to the master or the previous + * digest. + */ + + dbg_ns("ns_process_axfrin: incoming packet, wire size: %zu\n", + xfr->wire_size); + + int ret = xfrin_process_axfr_packet(/*xfr->wire, xfr->wire_size,*/ + /*(xfrin_constructed_zone_t **)(&xfr->data)*/ + xfr); + + if (ret > 0) { // transfer finished + dbg_ns("ns_process_axfrin: AXFR finished, zone created.\n"); + /* + * Adjust zone so that node count is set properly and nodes are + * marked authoritative / delegation point. + */ + xfrin_constructed_zone_t *constr_zone = + (xfrin_constructed_zone_t *)xfr->data; + knot_zone_contents_t *zone = constr_zone->contents; + assert(zone != NULL); + + dbg_ns("ns_process_axfrin: adjusting zone.\n"); + knot_zone_contents_adjust(zone, 0); + + /* Create and fill hash table */ + dbg_ns("ns_process_axfrin: filling hash table.\n"); + int rc = knot_zone_contents_create_and_fill_hash_table(zone); + if (rc != KNOT_EOK) { + return KNOT_ERROR; // TODO: change error code + } + + // save the zone contents to the xfr->data + xfr->data = zone; + + // free the structure used for processing XFR + assert(constr_zone->rrsigs == NULL); + free(constr_zone); + + //knot_zone_contents_dump(zone, 0); + } + + /*! + * \todo In case of error, shouldn't the zone be destroyed here? + */ + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ns_switch_zone(knot_nameserver_t *nameserver, + knot_ns_xfr_t *xfr) +{ + if (xfr == NULL || nameserver == NULL || xfr->data == NULL) { + return KNOT_EBADARG; + } + + knot_zone_contents_t *zone = (knot_zone_contents_t *)xfr->data; + + dbg_ns("Replacing zone by new one: %p\n", zone); + + // find the zone in the zone db + knot_zone_t *z = knot_zonedb_find_zone(nameserver->zone_db, + knot_node_owner(knot_zone_contents_apex(zone))); + if (z == NULL) { + char *name = knot_dname_to_str(knot_node_owner( + knot_zone_contents_apex(zone))); + dbg_ns("Failed to replace zone %s, old zone " + "not found\n", name); + free(name); + } else { + zone->zone = z; + } + + knot_zone_contents_t *old = rcu_xchg_pointer(&z->contents, zone); + +// knot_zone_t *old = knot_zonedb_replace_zone(nameserver->zone_db, +// zone); + dbg_ns("Old zone: %p\n", old); +// if (old == NULL) { +// char *name = knot_dname_to_str( +// knot_node_owner(knot_zone_apex(zone))); +// dbg_ns("Failed to replace zone %s\n", name); +// free(name); +// } + + // wait for readers to finish + dbg_ns("Waiting for readers to finish...\n"); + synchronize_rcu(); + // destroy the old zone + dbg_ns("Freeing old zone: %p\n", old); + knot_zone_contents_deep_free(&old, 0); + +dbg_ns_exec( + dbg_ns("Zone db contents: (zone count: %zu)\n", + nameserver->zone_db->zone_count); + + const knot_zone_t **zones = knot_zonedb_zones(nameserver->zone_db); + for (int i = 0; i < knot_zonedb_zone_count + (nameserver->zone_db); i++) { + dbg_ns("%d. zone: %p", i, zones[i]); + char *name = knot_dname_to_str(zones[i]->name); + dbg_ns(" zone name: %s\n", name); + free(name); + } + free(zones); +); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! \todo In this function, xfr->zone is properly set. If this is so, we do not + * have to search for the zone after the transfer has finished. + */ +int knot_ns_process_ixfrin(knot_nameserver_t *nameserver, + knot_ns_xfr_t *xfr) +{ + dbg_ns("ns_process_ixfrin: incoming packet\n"); + + /*! + * \todo [TSIG] Here we assume that 'xfr' contains TSIG information + * and the digest of the query sent to the master or the previous + * digest. + */ + + int ret = xfrin_process_ixfr_packet(xfr/*xfr->wire, xfr->wire_size, + (knot_changesets_t **)(&xfr->data)*/); + + if (ret == XFRIN_RES_FALLBACK) { + dbg_ns("ns_process_ixfrin: Fallback to AXFR.\n"); + assert(xfr->data == NULL); +// dbg_ns("xfr->zone = %p\n", xfr->zone); +// dbg_ns("Zone name: %.*s\n", +// xfr->zone->name->size, xfr->zone->name->name); +// assert(xfr->zone == NULL); + knot_packet_free(&xfr->query); + return KNOT_ENOIXFR; + } + + if (ret > 0) { + dbg_ns("ns_process_ixfrin: IXFR finished\n"); + + knot_changesets_t *chgsets = (knot_changesets_t *)xfr->data; + if (chgsets == NULL || chgsets->first_soa == NULL) { + // nothing to be done?? + dbg_ns("No changesets created for incoming IXFR!\n"); + return ret; + } + + // find zone associated with the changesets + knot_zone_t *zone = knot_zonedb_find_zone( + nameserver->zone_db, + knot_rrset_owner(chgsets->first_soa)); + if (zone == NULL) { + dbg_ns("No zone found for incoming IXFR!\n"); + knot_free_changesets( + (knot_changesets_t **)(&xfr->data)); + return KNOT_ENOZONE; /*! \todo Other error code? */ + } + + switch (ret) { + case XFRIN_RES_COMPLETE: + xfr->zone = zone; + break; + case XFRIN_RES_SOA_ONLY: { + // compare the SERIAL from the changeset with the zone's + // serial + const knot_node_t *apex = knot_zone_contents_apex( + knot_zone_contents(zone)); + if (apex == NULL) { + return KNOT_ERROR; + } + + const knot_rrset_t *zone_soa = knot_node_rrset( + apex, KNOT_RRTYPE_SOA); + if (zone_soa == NULL) { + return KNOT_ERROR; + } + + if (knot_rdata_soa_serial(knot_rrset_rdata( + chgsets->first_soa)) + != knot_rdata_soa_serial(knot_rrset_rdata( + zone_soa))) { + dbg_ns("Update did not fit.\n"); + return KNOT_EAGAIN; + } else { + // free changesets + dbg_ns("No update needed.\n"); + knot_free_changesets( + (knot_changesets_t **)(&xfr->data)); + return KNOT_ENOXFR; + } + } break; + } + } + + /*! + * \todo In case of error, shouldn't the zone be destroyed here? + */ + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ns_process_update(knot_nameserver_t *nameserver, knot_packet_t *query, + uint8_t *response_wire, size_t *rsize, + knot_zone_t **zone, knot_changeset_t **changeset) +{ + // 1) Parse the rest of the packet + assert(knot_packet_is_query(query)); + + knot_packet_t *response; + assert(*rsize >= MAX_UDP_PAYLOAD); + int ret = knot_ns_prepare_response(nameserver, query, &response, + MAX_UDP_PAYLOAD); + if (ret != KNOT_EOK) { + knot_ns_error_response(nameserver, knot_packet_id(query), + KNOT_RCODE_SERVFAIL, response_wire, + rsize); + return KNOT_EOK; + } + + assert(response != NULL); + + dbg_ns("Query - parsed: %zu, total wire size: %zu\n", + query->parsed, query->size); + + if (knot_packet_parsed(query) < knot_packet_size(query)) { + ret = knot_packet_parse_rest(query); + if (ret != KNOT_EOK) { + dbg_ns("Failed to parse rest of the query: " + "%s.\n", knot_strerror(ret)); + knot_ns_error_response_full(nameserver, response, + (ret == KNOT_EMALF) + ? KNOT_RCODE_FORMERR + : KNOT_RCODE_SERVFAIL, + response_wire, rsize); + knot_packet_free(&response); + return KNOT_EOK; + } + } + + dbg_ns("Query - parsed: %zu, total wire size: %zu\n", + knot_packet_parsed(query), knot_packet_size(query)); + + /*! \todo API for EDNS values. */ + dbg_ns("Opt RR: version: %d, payload: %d\n", + query->opt_rr.version, query->opt_rr.payload); + + // 2) Find zone for the query + // we do not check if there is only one entry in the Question section + // because the packet structure does not allow it + /*! \todo Check number of Question entries while parsing. */ + if (knot_packet_qtype(query) != KNOT_RRTYPE_SOA) { + dbg_ns("Question is not of type SOA.\n"); + knot_ns_error_response_full(nameserver, response, + KNOT_RCODE_FORMERR, + response_wire, rsize); + knot_packet_free(&response); + return KNOT_EOK; + } + + *zone = knot_zonedb_find_zone(nameserver->zone_db, + knot_packet_qname(query)); + if (*zone == NULL) { + dbg_ns("Zone not found for the update.\n"); + knot_ns_error_response_full(nameserver, response, + KNOT_RCODE_NOTAUTH, + response_wire, rsize); + knot_packet_free(&response); + return KNOT_EOK; + } + + uint8_t rcode = 0; + // 3) Check zone + ret = knot_ddns_check_zone(*zone, query, &rcode); + if (ret == KNOT_EBADZONE) { + // zone is slave, forward the request + /*! \todo Implement forwarding. */ + return KNOT_EBADZONE; + } else if (ret != KNOT_EOK) { + dbg_ns("Failed to check zone for update: " + "%s.\n", knot_strerror(ret)); + knot_ns_error_response_full(nameserver, response, rcode, + response_wire, rsize); + knot_packet_free(&response); + return KNOT_EOK; + } + + // 4) Convert prerequisities + knot_ddns_prereq_t *prereqs = NULL; + ret = knot_ddns_process_prereqs(query, &prereqs, &rcode); + if (ret != KNOT_EOK) { + dbg_ns("Failed to check zone for update: " + "%s.\n", knot_strerror(ret)); + knot_ns_error_response_full(nameserver, response, rcode, + response_wire, rsize); + knot_packet_free(&response); + return KNOT_EOK; + } + + assert(prereqs != NULL); + + // 5) Check prerequisities + /*! \todo Somehow ensure the zone will not be changed until the update + * is finished. + */ + ret = knot_ddns_check_prereqs(knot_zone_contents(*zone), &prereqs, + &rcode); + if (ret != KNOT_EOK) { + dbg_ns("Failed to check zone for update: " + "%s.\n", knot_strerror(ret)); + knot_ns_error_response_full(nameserver, response, rcode, + response_wire, rsize); + knot_ddns_prereqs_free(&prereqs); + knot_packet_free(&response); + return KNOT_EOK; + } + + // 6) Convert update to changeset + ret = knot_ddns_process_update(query, changeset, &rcode); + if (ret != KNOT_EOK) { + dbg_ns("Failed to check zone for update: " + "%s.\n", knot_strerror(ret)); + knot_ns_error_response_full(nameserver, response, rcode, + response_wire, rsize); + knot_ddns_prereqs_free(&prereqs); + knot_packet_free(&response); + return KNOT_EOK; + } + + assert(changeset != NULL); + + // 7) Create response + dbg_ns("Update converted successfuly.\n"); + + /*! \todo No response yet. Distinguish somehow in the caller. + * Maybe only this case will be EOK, other cases some error. + */ + + knot_packet_free(&response); + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ns_create_forward_query(const knot_packet_t *query, + uint8_t *query_wire, size_t *size) +{ + // just copy the wireformat of the query and set a new random ID to it + if (knot_packet_size(query) > *size) { + return KNOT_ESPACE; + } + + memcpy(query_wire, knot_packet_wireformat(query), + knot_packet_size(query)); + *size = knot_packet_size(query); + + knot_wire_set_id(query_wire, knot_random_id()); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ns_process_forward_response(const knot_packet_t *response, + uint16_t original_id, + uint8_t *response_wire, size_t *size) +{ + // just copy the wireformat of the response and set the original ID + + if (knot_packet_size(response) > *size) { + return KNOT_ESPACE; + } + + memcpy(response_wire, knot_packet_wireformat(response), + knot_packet_size(response)); + *size = knot_packet_size(response); + + knot_wire_set_id(response_wire, original_id); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +void *knot_ns_data(knot_nameserver_t *nameserver) +{ + return nameserver->data; +} + +/*----------------------------------------------------------------------------*/ + +void *knot_ns_get_data(knot_nameserver_t *nameserver) +{ + return nameserver->data; +} + +/*----------------------------------------------------------------------------*/ + +void knot_ns_set_data(knot_nameserver_t *nameserver, void *data) +{ + nameserver->data = data; +} + +/*----------------------------------------------------------------------------*/ + +void knot_ns_destroy(knot_nameserver_t **nameserver) +{ + synchronize_rcu(); + + free((*nameserver)->err_response); + if ((*nameserver)->opt_rr != NULL) { + knot_edns_free(&(*nameserver)->opt_rr); + } + + // destroy the zone db + knot_zonedb_deep_free(&(*nameserver)->zone_db); + + free(*nameserver); + *nameserver = NULL; +} diff --git a/src/libknot/nameserver/name-server.h b/src/libknot/nameserver/name-server.h new file mode 100644 index 0000000..0d93df6 --- /dev/null +++ b/src/libknot/nameserver/name-server.h @@ -0,0 +1,358 @@ +/*! + * \file name-server.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * Contains the "name server" structure and interface for the main DNS + * functions. Currently only supports answering simple queries, without any + * extensions. + * + * \todo Consider saving pointer to the zdb_find_name() function in the + * nameserver structure. Probably not needed, these modules can be + * inter-connected. + * \todo Provide interface for other DNS functions - zone transfers, dynamic + * updates, etc. + * + * \addtogroup query_processing + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_NAME_SERVER_H_ +#define _KNOT_NAME_SERVER_H_ + +#include <stdint.h> +#include <string.h> + +#include "zone/zonedb.h" +#include "edns.h" +#include "consts.h" +#include "tsig.h" +#include "packet/packet.h" +#include "common/sockaddr.h" +#include "updates/changesets.h" + +struct conf_t; +struct server_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Name server structure. Holds all important data needed for the + * supported DNS functions. + * + * Currently only holds pointer to the zone database for answering queries. + */ +typedef struct knot_nameserver { + /*! + * \brief Pointer to the zone database structure used for answering + * queries. + */ + knot_zonedb_t *zone_db; + uint8_t *err_response; /*!< Prepared generic error response. */ + size_t err_resp_size; /*!< Size of the prepared error response. */ + knot_opt_rr_t *opt_rr; /*!< OPT RR with the server's EDNS0 info. */ + + void *data; +} knot_nameserver_t; + +/*! \brief Callback for sending one packet back through a TCP connection. */ +typedef int (*xfr_callback_t)(int session, sockaddr_t *addr, + uint8_t *packet, size_t size); + +/*! + * \brief Single XFR operation structure. + * + * Used for communication with XFR handler. + */ +typedef struct knot_ns_xfr { + int type; + int flags; + sockaddr_t addr; + knot_packet_t *query; + knot_packet_t *response; + xfr_callback_t send; + int session; + + /*! + * XFR-out: Output buffer. + * XFR-in: Buffer for query or incoming packet. + */ + uint8_t *wire; + + /*! + * XFR-out: Size of the output buffer. + * XFR-in: Size of the current packet. + */ + size_t wire_size; + void *data; + knot_zone_t *zone; + void *owner; + + /*! \note [TSIG] TSIG fields */ + /*! \brief Message(s) to sign in wireformat. + * + * This field should be allocated at the start of transfer and + * freed at the end. During the transfer it is only rewritten. + */ + uint8_t *tsig_data; + size_t tsig_data_size; /*!< Size of the message(s) in bytes */ +// const knot_rrset_t *tsig; /*!< Response TSIG. +// \todo [TSIG] Replace with separate data. */ + size_t tsig_size; /*!< Size of the TSIG RR wireformat in bytes.*/ + knot_key_t *tsig_key; /*!< Associated TSIG key for signing. */ + + uint8_t *digest; /*!< Buffer for counting digest. */ + size_t digest_size; /*!< Size of the digest. */ + size_t digest_max_size; /*!< Size of the buffer. */ + + /*! \brief Previous digest or request digest. + * + * Should be allocated before the transfer (known size). + */ +// uint8_t *prev_digest; +// size_t prev_digest_size; /*!< Size of previous digest in bytes. */ + + /*! + * \brief Number of the packet currently assembled. + * + * In case of XFR-in, this is not the overall number of packet, just + * number counted from last TSIG check. + */ + int packet_nr; +} knot_ns_xfr_t; + + +static const int KNOT_NS_TSIG_FREQ = 100; + +static const size_t KNOT_NS_TSIG_DATA_MAX_SIZE = 100 * 64 * 1024; + +/*! + * \brief XFR request flags. + */ +enum knot_ns_xfr_flag_t { + XFR_FLAG_TCP = 1 << 0, /*!< XFR request is on TCP. */ + XFR_FLAG_UDP = 1 << 1 /*!< XFR request is on UDP. */ +}; + +/*! + * \brief XFR request types. + */ +typedef enum knot_ns_xfr_type_t { + /* Special events. */ + XFR_TYPE_CLOSE = -1, /*!< Close connection event. */ + + /* DNS events. */ + XFR_TYPE_AIN = 0, /*!< AXFR-IN request (start transfer). */ + XFR_TYPE_AOUT, /*!< AXFR-OUT request (incoming transfer). */ + XFR_TYPE_IIN, /*!< IXFR-IN request (start transfer). */ + XFR_TYPE_IOUT, /*!< IXFR-OUT request (incoming transfer). */ + XFR_TYPE_SOA, /*!< Pending SOA request. */ + XFR_TYPE_NOTIFY /*!< Pending NOTIFY query. */ +} knot_ns_xfr_type_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Allocates and initializes the name server structure. + * + * \return Pointer to the name server structure. + */ +knot_nameserver_t *knot_ns_create(); + +/*! + * \brief Parses the given query into the response structure and recognizes + * type of the query. + * + * Some query types are distinguished by OPCODE (NOTIFY, UPDATE, etc.), some + * by QTYPE (AXFR, IXFR). As these information are needed on the same layer + * to decide what to do with the query, the knot_query_t type is used for this + * purpose. + * + * \param query_wire Wire format of the query. + * \param qsize Size of the query in octets. + * \param packet Packet structure to be filled with the parsed query. + * \param type Type of the query. + * + * \retval KNOT_EOK + * \retval KNOT_EMALF if the query is totally unusable. Such query must be + * ignored. + * \retval KNOT_RCODE_SERVFAIL if there was some internal error. Call + * ns_error_response() with \a rcode set to this + * value to get proper error response. + * \retval KNOT_RCODE_FORMERR if the query was malformed, but can be used to + * construct an error response. Call + * ns_error_response() with \a rcode set to this + * value to get proper error response. + * \retval KNOT_RCODE_NOTIMPL if the query has an unsupported type. Call + * ns_error_response() with \a rcode set to this + * value to get proper error response. + */ +int knot_ns_parse_packet(const uint8_t *query_wire, size_t qsize, + knot_packet_t *packet, knot_packet_type_t *type); + +/*! + * \brief Prepares wire format of an error response using generic error template + * stored in the nameserver structure. + * + * The error response will not contain the Question section from the query, just + * a header with ID copied from the query and the given RCODE. + * + * \param nameserver Nameserver structure containing the error template. + * \param query_id ID of the query. + * \param rcode RCODE to set in the response. + * \param response_wire Place for wire format of the response. + * \param rsize Size of the error response will be stored here. + */ +void knot_ns_error_response(const knot_nameserver_t *nameserver, uint16_t query_id, + uint8_t rcode, uint8_t *response_wire, size_t *rsize); + +/*! + * \brief Creates a response for the given normal query using the data of the + * nameserver. + * + * \param nameserver Name server structure to provide the needed data. + * \param resp Response structure with parsed query. + * \param response_wire Place for the response in wire format. + * \param rsize Input: maximum acceptable size of the response. Output: real + * size of the response. + * + * \retval KNOT_EOK if a valid response was created. + * \retval KNOT_EMALF if an error occured and the response is not valid. + */ +int knot_ns_answer_normal(knot_nameserver_t *nameserver, knot_packet_t *query, + uint8_t *response_wire, size_t *rsize); + +int knot_ns_init_xfr(knot_nameserver_t *nameserver, knot_ns_xfr_t *xfr); + +/*! + * \brief Compares two zone serials. + * + * \retval < 0 if s1 is less than s2. + * \retval > 0 if s1 is larger than s2. + * \retval == 0 if s1 is equal to s2. + */ +int ns_serial_compare(uint32_t s1, uint32_t s2); + +int ns_ixfr_load_serials(const knot_ns_xfr_t *xfr, uint32_t *serial_from, + uint32_t *serial_to); + +int knot_ns_xfr_send_error(const knot_nameserver_t *nameserver, + knot_ns_xfr_t *xfr, knot_rcode_t rcode); + +/*! + * \brief Processes an AXFR query. + * + * This function sequentially creates DNS packets to be sent as a response + * to the AXFR query and sends each packet using the given callback (\a + * send_packet). + * + * \param nameserver Name server structure to provide the data for answering. + * \param xfr Persistent transfer-specific data. + * + * \note Currently only a stub which sends one error response using the given + * callback. + * + * \retval KNOT_EOK + * \retval KNOT_EINVAL + * \retval KNOT_ENOMEM + * \retval KNOT_ERROR + * + * \todo Maybe the place for the wire format should be passed in as in + * the ns_answer_request() function...? + */ +int knot_ns_answer_axfr(knot_nameserver_t *nameserver, knot_ns_xfr_t *xfr); + +/*! + * \brief Processes an IXFR query. + * + * \param nameserver Name server structure to provide the data for answering. + * \param xfr Persistent transfer-specific data. + * + * \todo Document properly. + */ +int knot_ns_answer_ixfr(knot_nameserver_t *nameserver, knot_ns_xfr_t *xfr); + +/*! + * \brief Processes an AXFR-IN packet. + * + * \param nameserver Name server structure to provide the data for answering. + * \param xfr Persistent transfer-specific data. + * + * \todo Document me. + */ +int knot_ns_process_axfrin(knot_nameserver_t *nameserver, + knot_ns_xfr_t *xfr); + +int knot_ns_switch_zone(knot_nameserver_t *nameserver, + knot_ns_xfr_t *xfr); + +/*! + * \brief Processes an IXFR-IN packet. + * + * \param nameserver Name server structure to provide the data for answering. + * \param xfr Persistent transfer-specific data. + * + * \retval KNOT_EOK If this packet was processed successfuly and another packet + * is expected. (RFC1995bis, case c) + * \retval KNOT_ENOXFR If the transfer is not taking place because server's + * SERIAL is the same as this client's SERIAL. The client + * should close the connection and do no further processing. + * (RFC1995bis case a). + * \retval KNOT_EAGAIN If the server could not fit the transfer into the packet. + * This should happen only if UDP was used. In this case + * the client should retry the request via TCP. If UDP was + * not used, it should be considered that the transfer was + * malformed and the connection should be closed. + * (RFC1995bis case b). + * \retval >0 Transfer successully finished. Changesets are created and furter + * processing is needed. + * \retval Other If any other error occured. The connection should be closed. + * + * \todo Document me. + */ +int knot_ns_process_ixfrin(knot_nameserver_t *nameserver, + knot_ns_xfr_t *xfr); + +int knot_ns_process_update(knot_nameserver_t *nameserver, knot_packet_t *query, + uint8_t *response_wire, size_t *rsize, + knot_zone_t **zone, knot_changeset_t **changeset); + +int knot_ns_create_forward_query(const knot_packet_t *query, + uint8_t *query_wire, size_t *size); + +int knot_ns_process_forward_response(const knot_packet_t *response, + uint16_t original_id, + uint8_t *response_wire, size_t *size); + +void *knot_ns_data(knot_nameserver_t *nameserver); + +void *knot_ns_get_data(knot_nameserver_t *nameserver); + +void knot_ns_set_data(knot_nameserver_t *nameserver, void *data); + +int knot_ns_tsig_required(int packet_nr); + +/*! + * \brief Properly destroys the name server structure. + * + * \param nameserver Nameserver to destroy. + */ +void knot_ns_destroy(knot_nameserver_t **nameserver); + + +#endif /* _KNOTNAME_SERVER_H_ */ + +/*! @} */ diff --git a/src/libknot/nsec3.c b/src/libknot/nsec3.c new file mode 100644 index 0000000..303d2e6 --- /dev/null +++ b/src/libknot/nsec3.c @@ -0,0 +1,265 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdint.h> +#include <assert.h> +#include <stdlib.h> +#include <sys/time.h> + +#include <openssl/evp.h> +#include <openssl/sha.h> + +#include "nsec3.h" +#include "common.h" +#include "util/descriptor.h" +#include "util/utils.h" +#include "util/tolower.h" +#include "util/error.h" +#include "util/debug.h" + +/*----------------------------------------------------------------------------*/ + +int knot_nsec3_params_from_wire(knot_nsec3_params_t *params, + const knot_rrset_t *nsec3param) +{ + if (params == NULL || nsec3param == NULL) { + return KNOT_EBADARG; + } + + assert(knot_rrset_type(nsec3param) == KNOT_RRTYPE_NSEC3PARAM); + const knot_rdata_t *rdata = knot_rrset_rdata(nsec3param); + + assert(rdata->count == 4); + + params->algorithm = *(uint8_t *) + (&knot_rdata_item(rdata, 0)->raw_data[1]); + params->flags = *(uint8_t *) + (&knot_rdata_item(rdata, 1)->raw_data[1]); + params->iterations = knot_wire_read_u16( + (uint8_t *)(knot_rdata_item(rdata, 2)->raw_data + 1)); + + params->salt_length = + ((uint8_t *)knot_rdata_item(rdata, 3)->raw_data)[2]; + + if (params->salt_length > 0) { + params->salt = (uint8_t *)malloc(params->salt_length); + CHECK_ALLOC_LOG(params->salt, -1); + memcpy(params->salt, + (uint8_t *)knot_rdata_item(rdata, 3)->raw_data + 3, + params->salt_length); + } else { + params->salt = NULL; + } + + dbg_nsec3("Parsed NSEC3PARAM:\n"); + dbg_nsec3("Algorithm: %hu\n", params->algorithm); + dbg_nsec3("Flags: %hu\n", params->flags); + dbg_nsec3("Iterations: %hu\n", params->iterations); + dbg_nsec3("Salt length: %hu\n", params->salt_length); + dbg_nsec3("Salt: "); + if (params->salt != NULL) { + dbg_nsec3_hex((char *)params->salt, + params->salt_length); + dbg_nsec3("\n"); + } else { + dbg_nsec3("none\n"); + } + + return KNOT_EOK; +} + +static uint8_t *knot_nsec3_to_lowercase(const uint8_t *data, size_t size) +{ + uint8_t *out = (uint8_t *)malloc(size); + CHECK_ALLOC_LOG(out, NULL); + + for (int i = 0; i < size; ++i) { + out[i] = knot_tolower(data[i]); + } + + return out; +} + +/*----------------------------------------------------------------------------*/ +#if KNOT_NSEC3_SHA_USE_EVP +int knot_nsec3_sha1(const knot_nsec3_params_t *params, + const uint8_t *data, size_t size, uint8_t **digest, + size_t *digest_size) +{ + if (digest == NULL || digest_size == NULL || data == NULL) { + return KNOT_EBADARG; + } + + uint8_t *salt = params->salt; + uint8_t salt_length = params->salt_length; + uint16_t iterations = params->iterations; + + EVP_MD_CTX mdctx; + EVP_MD_CTX_init(&mdctx); + + *digest = (uint8_t *)malloc(EVP_MD_size(EVP_sha1())); + if (*digest == NULL) { + ERR_ALLOC_FAILED; + return -1; + } + + uint8_t *data_low = knot_nsec3_to_lowercase(data, size); + if (data_low == NULL) { + free(*digest); + return -1; + } + + const uint8_t *in = data_low; + unsigned in_size = size; + + int res = 0; + +#ifdef KNOT_NSEC3_DEBUG + unsigned long long total_time = 0; + unsigned long calls = 0; + long time = 0; +#endif + + for (int i = 0; i <= iterations; ++i) { +#ifdef KNOT_NSEC3_DEBUG + perf_begin(); +#endif + + EVP_DigestInit_ex(&mdctx, EVP_sha1(), NULL); + + res = EVP_DigestUpdate(&mdctx, in, in_size); + + if (salt_length > 0) { + res = EVP_DigestUpdate(&mdctx, salt, salt_length); + } + + EVP_DigestFinal_ex(&mdctx, *digest, digest_size); + in = *digest; + in_size = *digest_size; + +#ifdef KNOT_NSEC3_DEBUG + perf_end(time); + total_time += time; + ++calls; +#endif + + if (res != 1) { + dbg_nsec3("Error calculating SHA-1 hash.\n"); + free(data_low); + free(*digest); + return -2; + } + } + + EVP_MD_CTX_cleanup(&mdctx); + + dbg_nsec3("NSEC3 hashing: calls: %lu, avg time per call: %f." + "\n", calls, (double)(total_time) / calls); + + free(data_low); + return 0; +} + +/*----------------------------------------------------------------------------*/ +#else + +int knot_nsec3_sha1(const knot_nsec3_params_t *params, + const uint8_t *data, size_t size, uint8_t **digest, + size_t *digest_size) +{ + if (params == NULL || digest == NULL || digest_size == NULL + || data == NULL) { + return KNOT_EBADARG; + } + + uint8_t *salt = params->salt; + uint8_t salt_length = params->salt_length; + uint16_t iterations = params->iterations; + + dbg_nsec3("Hashing: \n"); + dbg_nsec3(" Data: %.*s \n", size, data); + dbg_nsec3_hex((const char *)data, size); + dbg_nsec3(" (size %d)\n Iterations: %u\n", (int)size, iterations); + dbg_nsec3(" Salt length: %u\n", salt_length); + dbg_nsec3(" Salt: "); + if (salt_length > 0) { + dbg_nsec3_hex((char *)salt, salt_length); + dbg_nsec3("\n"); + } else { + dbg_nsec3("none\n"); + } + + SHA_CTX ctx; + + *digest = (uint8_t *)malloc(SHA_DIGEST_LENGTH); + if (*digest == NULL) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + uint8_t *data_low = knot_nsec3_to_lowercase(data, size); + if (data_low == NULL) { + free(*digest); + return KNOT_ENOMEM; + } + + const uint8_t *in = data_low; + unsigned in_size = size; + + int res = 0; + + // other iterations + for (int i = 0; i <= iterations; ++i) { + SHA1_Init(&ctx); + + res = SHA1_Update(&ctx, in, in_size); + + if (salt_length > 0) { + res = SHA1_Update(&ctx, salt, salt_length); + } + + SHA1_Final(*digest, &ctx); + + in = *digest; + in_size = SHA_DIGEST_LENGTH; + + if (res != 1) { + dbg_nsec3("Error calculating SHA-1 hash.\n"); + free(data_low); + free(*digest); + return KNOT_ECRYPTO; + } + } + + *digest_size = SHA_DIGEST_LENGTH; + + dbg_nsec3("Hash: %.*s\n", *digest_size, *digest); + dbg_nsec3_hex((const char *)*digest, *digest_size); + dbg_nsec3("\n"); + + free(data_low); + return KNOT_EOK; +} +#endif + +/*----------------------------------------------------------------------------*/ + +void knot_nsec3_params_free(knot_nsec3_params_t *params) +{ + if (params->salt != NULL) { + free(params->salt); + } +} diff --git a/src/libknot/nsec3.h b/src/libknot/nsec3.h new file mode 100644 index 0000000..0ce6899 --- /dev/null +++ b/src/libknot/nsec3.h @@ -0,0 +1,92 @@ +/*! + * \file nsec3.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Functions for calcularing NSEC3 hashes. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_NSEC3_H_ +#define _KNOT_NSEC3_H_ + +#include <stdint.h> +#include <string.h> + +#include "rrset.h" + +#define KNOT_NSEC3_SHA_USE_EVP 0 + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Structure representing the NSEC3PARAM resource record. + */ +struct knot_nsec3_params { + uint8_t algorithm; /*!< Hash algorithm. */ + uint8_t flags; /*!< Flags. */ + uint16_t iterations; /*!< Additional iterations of the hash function.*/ + uint8_t salt_length; /*!< Length of the salt field in bytes. */ + uint8_t *salt; /*!< Salt used in hashing. */ +}; + +typedef struct knot_nsec3_params knot_nsec3_params_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Initializes the NSEC3PARAM structure. + * + * \param params NSEC3PARAM structure to initialize. + * \param nsec3param The NSEC3PARAM RRset. + * + * \retval KNOT_EOK on success (always). + */ +int knot_nsec3_params_from_wire(knot_nsec3_params_t *params, + const knot_rrset_t *nsec3param); + +/*! + * \brief Hashes the given data using the SHA1 hash and the given parameters. + * + * \param[in] params NSEC3PARAM structure with the required parameters for + * hashing. + * \param[in] data Data to hash. + * \param[in] size Size of the data in bytes. + * \param[out] digest Result will be store here. + * \param[out] digest_size Size of the result in octets will be stored here. + * + * \retval KNOT_EOK if successful. + * \retval KNOT_ENOMEM + * \retval KNOT_EBADARG + * \retval KNOT_ECRYPTO + */ +int knot_nsec3_sha1(const knot_nsec3_params_t *params, const uint8_t *data, + size_t size, uint8_t **digest, size_t *digest_size); + +/*! + * \brief Properly cleans up (but does not deallocate) the NSEC3PARAM structure. + * + * \param params NSEC3PARAMS structure to clean up. + */ +void knot_nsec3_params_free(knot_nsec3_params_t *params); + +/*----------------------------------------------------------------------------*/ + +#endif /* _KNOT_NSEC3_H_ */ + +/*! @} */ diff --git a/src/libknot/packet/packet.c b/src/libknot/packet/packet.c new file mode 100644 index 0000000..82e818f --- /dev/null +++ b/src/libknot/packet/packet.c @@ -0,0 +1,1532 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> + +#include "packet/packet.h" +#include "util/error.h" +#include "util/debug.h" +#include "common.h" +#include "util/descriptor.h" +#include "util/wire.h" + +/*----------------------------------------------------------------------------*/ + +#define DEFAULT_RRCOUNT_QUERY(type) DEFAULT_##type##COUNT_QUERY +#define DEFAULT_RRCOUNT(type) DEFAULT_##type##COUNT + +#define DEFAULT_RRSET_COUNT(type, packet) \ + ((packet->prealloc_type == KNOT_PACKET_PREALLOC_NONE) \ + ? 0 \ + : (packet->prealloc_type == KNOT_PACKET_PREALLOC_QUERY) \ + ? DEFAULT_##type##_QUERY \ + : DEFAULT_##type) + + + +typedef enum { + KNOT_PACKET_DUPL_IGNORE, + KNOT_PACKET_DUPL_SKIP, + KNOT_PACKET_DUPL_MERGE +} knot_packet_duplicate_handling_t; +/*----------------------------------------------------------------------------*/ +/* Non-API functions */ +/*----------------------------------------------------------------------------*/ +/*! + * \brief Sets all the pointers in the packet structure to the respective + * parts of the pre-allocated space. + */ +static void knot_packet_init_pointers_response(knot_packet_t *pkt) +{ + dbg_packet("Packet pointer: %p\n", pkt); + + char *pos = (char *)pkt + PREALLOC_PACKET; + + // put QNAME directly after the structure + pkt->question.qname = (knot_dname_t *)pos; + pos += PREALLOC_QNAME_DNAME; + + dbg_packet("QNAME: %p\n", pkt->question.qname); + + pkt->question.qname->name = (uint8_t *)pos; + pos += PREALLOC_QNAME_NAME; + pkt->question.qname->labels = (uint8_t *)pos; + pos += PREALLOC_QNAME_LABELS; + + pkt->owner_tmp = (uint8_t *)pos; + dbg_packet("Tmp owner: %p\n", pkt->owner_tmp); + pos += PREALLOC_RR_OWNER; + + // then answer, authority and additional sections + if (DEFAULT_ANCOUNT == 0) { + pkt->answer = NULL; + } else { + pkt->answer = (const knot_rrset_t **)pos; + pos += DEFAULT_ANCOUNT * sizeof(const knot_rrset_t *); + } + + if (DEFAULT_NSCOUNT == 0) { + pkt->authority = NULL; + } else { + pkt->authority = (const knot_rrset_t **)pos; + pos += DEFAULT_NSCOUNT * sizeof(const knot_rrset_t *); + } + + if (DEFAULT_ARCOUNT == 0) { + pkt->additional = NULL; + } else { + pkt->additional = (const knot_rrset_t **)pos; + pos += DEFAULT_ARCOUNT * sizeof(const knot_rrset_t *); + } + + dbg_packet("Answer section: %p\n", pkt->answer); + dbg_packet("Authority section: %p\n", pkt->authority); + dbg_packet("Additional section: %p\n", pkt->additional); + + pkt->max_an_rrsets = DEFAULT_ANCOUNT; + pkt->max_ns_rrsets = DEFAULT_NSCOUNT; + pkt->max_ar_rrsets = DEFAULT_ARCOUNT; + + // then domain names for compression and offsets + pkt->compression.dnames = (const knot_dname_t **)pos; + pos += DEFAULT_DOMAINS_IN_RESPONSE * sizeof(const knot_dname_t *); + pkt->compression.offsets = (size_t *)pos; + pos += DEFAULT_DOMAINS_IN_RESPONSE * sizeof(size_t); + + dbg_packet("Compression dnames: %p\n", pkt->compression.dnames); + dbg_packet("Compression offsets: %p\n", pkt->compression.offsets); + + pkt->compression.max = DEFAULT_DOMAINS_IN_RESPONSE; + + pkt->tmp_rrsets = (const knot_rrset_t **)pos; + pos += DEFAULT_TMP_RRSETS * sizeof(const knot_rrset_t *); + + dbg_packet("Tmp rrsets: %p\n", pkt->tmp_rrsets); + + pkt->tmp_rrsets_max = DEFAULT_TMP_RRSETS; + + assert((char *)pos == (char *)pkt + PREALLOC_RESPONSE); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Sets all the pointers in the packet structure to the respective + * parts of the pre-allocated space. + */ +static void knot_packet_init_pointers_query(knot_packet_t *pkt) +{ + dbg_packet("Packet pointer: %p\n", pkt); + + char *pos = (char *)pkt + PREALLOC_PACKET; + + // put QNAME directly after the structure + pkt->question.qname = (knot_dname_t *)pos; + pos += PREALLOC_QNAME_DNAME; + + dbg_packet("QNAME: %p (%zu after start of packet)\n", + pkt->question.qname, + (void *)pkt->question.qname - (void *)pkt); + + pkt->question.qname->name = (uint8_t *)pos; + pos += PREALLOC_QNAME_NAME; + pkt->question.qname->labels = (uint8_t *)pos; + pos += PREALLOC_QNAME_LABELS; + +// pkt->owner_tmp = (uint8_t *)((char *)pkt->question.qname->labels +// + PREALLOC_QNAME_LABELS); + + // then answer, authority and additional sections + if (DEFAULT_ANCOUNT_QUERY == 0) { + pkt->answer = NULL; + } else { + pkt->answer = (const knot_rrset_t **)pos; + pos += DEFAULT_ANCOUNT_QUERY * sizeof(const knot_rrset_t *); + } + + if (DEFAULT_NSCOUNT_QUERY == 0) { + pkt->authority = NULL; + } else { + pkt->authority = (const knot_rrset_t **)pos; + pos += DEFAULT_NSCOUNT_QUERY * sizeof(const knot_rrset_t *); + } + + if (DEFAULT_ARCOUNT_QUERY == 0) { + pkt->additional = NULL; + } else { + pkt->additional = (const knot_rrset_t **)pos; + pos += DEFAULT_ARCOUNT_QUERY * sizeof(const knot_rrset_t *); + } + + dbg_packet("Answer section: %p\n", pkt->answer); + dbg_packet("Authority section: %p\n", pkt->authority); + dbg_packet("Additional section: %p\n", pkt->additional); + + pkt->max_an_rrsets = DEFAULT_ANCOUNT_QUERY; + pkt->max_ns_rrsets = DEFAULT_NSCOUNT_QUERY; + pkt->max_ar_rrsets = DEFAULT_ARCOUNT_QUERY; + + pkt->tmp_rrsets = (const knot_rrset_t **)pos; + pos += DEFAULT_TMP_RRSETS_QUERY * sizeof(const knot_rrset_t *); + + dbg_packet("Tmp rrsets: %p\n", pkt->tmp_rrsets); + + pkt->tmp_rrsets_max = DEFAULT_TMP_RRSETS_QUERY; + +// dbg_packet("End of data: %p (%zu after start of packet)\n", +// pkt->tmp_rrsets + DEFAULT_TMP_RRSETS_QUERY, +// (void *)(pkt->tmp_rrsets + DEFAULT_TMP_RRSETS_QUERY) +// - (void *)pkt); + dbg_packet("Allocated total: %u\n", PREALLOC_QUERY); + + assert(pos == (char *)pkt + PREALLOC_QUERY); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Parses DNS header from the wire format. + * + * \note This function also adjusts the position (\a pos) and size of remaining + * bytes in the wire format (\a remaining) according to what was parsed + * (though it actually always parses the 12 bytes of the header). + * + * \param[in,out] pos Wire format to parse the header from. + * \param[in,out] remaining Remaining size of the wire format. + * \param[out] header Header structure to fill in. + * + * \retval KNOT_EOK + * \retval KNOT_EFEWDATA + */ +static int knot_packet_parse_header(const uint8_t *wire, size_t *pos, + size_t size, knot_header_t *header) +{ + assert(wire != NULL); + assert(pos != NULL); + assert(header != NULL); + + if (size - *pos < KNOT_WIRE_HEADER_SIZE) { + dbg_response("Not enough data to parse header.\n"); + return KNOT_EFEWDATA; + } + + header->id = knot_wire_get_id(wire); + // copy some of the flags: OPCODE and RD + // do this by copying flags1 and setting QR to 1, AA to 0 and TC to 0 + header->flags1 = knot_wire_get_flags1(wire); +// knot_wire_flags_set_qr(&header->flags1); +// knot_wire_flags_clear_aa(&header->flags1); +// knot_wire_flags_clear_tc(&header->flags1); + // do not copy flags2 (all set by server) + header->qdcount = knot_wire_get_qdcount(wire); + header->ancount = knot_wire_get_ancount(wire); + header->nscount = knot_wire_get_nscount(wire); + header->arcount = knot_wire_get_arcount(wire); + + *pos += KNOT_WIRE_HEADER_SIZE; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Parses DNS Question entry from the wire format. + * + * \note This function also adjusts the position (\a pos) and size of remaining + * bytes in the wire format (\a remaining) according to what was parsed. + * + * \param[in,out] pos Wire format to parse the Question from. + * \param[in,out] remaining Remaining size of the wire format. + * \param[out] question DNS Question structure to be filled. + * + * \retval KNOT_EOK + * \retval KNOT_EFEWDATA + * \retval KNOT_ENOMEM + */ +static int knot_packet_parse_question(const uint8_t *wire, size_t *pos, + size_t size, + knot_question_t *question, int alloc) +{ + assert(pos != NULL); + assert(wire != NULL); + assert(question != NULL); + + if (size - *pos < KNOT_WIRE_QUESTION_MIN_SIZE) { + dbg_response("Not enough data to parse question.\n"); + return KNOT_EFEWDATA; // malformed + } + + dbg_response("Parsing Question starting on position %zu.\n", + *pos); + + // domain name must end with 0, so just search for 0 + int i = *pos; + while (i < size && wire[i] != 0) { + ++i; + } + + if (size - i - 1 < 4) { + dbg_response("Not enough data to parse question.\n"); + return KNOT_EFEWDATA; // no 0 found or not enough data left + } + + dbg_response("Parsing dname starting on position %zu and " + "%zu bytes long.\n", *pos, i - *pos + 1); + dbg_response("Alloc: %d\n", alloc); + if (alloc) { + question->qname = knot_dname_new_from_wire( + wire + *pos, i - *pos + 1, NULL); + if (question->qname == NULL) { + return KNOT_ENOMEM; + } + } else { + int res = knot_dname_from_wire(wire + *pos, i - *pos + 1, + NULL, question->qname); + if (res != KNOT_EOK) { + assert(res != KNOT_EBADARG); + return res; + } + } + + *pos = i + 1; + question->qtype = knot_wire_read_u16(wire + i + 1); + //*pos += 2; + question->qclass = knot_wire_read_u16(wire + i + 3); + //*pos += 2; + + *pos += 4; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Reallocate space for RRSets. + * + * \param rrsets Space for RRSets. + * \param max_count Size of the space available for the RRSets. + * \param default_max_count Size of the space pre-allocated for the RRSets when + * the response structure was initialized. + * \param step How much the space should be increased. + * + * \retval KNOT_EOK + * \retval KNOT_ENOMEM + */ +static int knot_packet_realloc_rrsets(const knot_rrset_t ***rrsets, + short *max_count, + short default_max_count, short step) +{ + dbg_packet("Max count: %d, default max count: %d\n", + *max_count, default_max_count); + int free_old = (*max_count) != default_max_count; + const knot_rrset_t **old = *rrsets; + + short new_max_count = *max_count + step; + const knot_rrset_t **new_rrsets = (const knot_rrset_t **)malloc( + new_max_count * sizeof(knot_rrset_t *)); + CHECK_ALLOC_LOG(new_rrsets, KNOT_ENOMEM); + + memcpy(new_rrsets, *rrsets, (*max_count) * sizeof(knot_rrset_t *)); + + *rrsets = new_rrsets; + *max_count = new_max_count; + + if (free_old) { + free(old); + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static knot_rdata_t *knot_packet_parse_rdata(const uint8_t *wire, + size_t *pos, size_t total_size, size_t rdlength, + const knot_rrtype_descriptor_t *desc) +{ +// if (desc->type == 0) { +// dbg_packet("Unknown RR type.\n"); +// return NULL; +// } + + knot_rdata_t *rdata = knot_rdata_new(); + if (rdata == NULL) { + return NULL; + } + + int rc = knot_rdata_from_wire(rdata, wire, pos, total_size, rdlength, + desc); + + if (rc != KNOT_EOK) { + dbg_packet("rdata_from_wire() returned: %s\n", + knot_strerror(rc)); + knot_rdata_free(&rdata); + return NULL; + } + + return rdata; +} + +/*----------------------------------------------------------------------------*/ + +static knot_rrset_t *knot_packet_parse_rr(const uint8_t *wire, size_t *pos, + size_t size) +{ +// knot_rrset_t *rrset = +// (knot_rrset_t *)malloc(sizeof(knot_rrset_t)); +// CHECK_ALLOC_LOG(rrset, NULL); + + dbg_packet("Parsing RR from position: %zu, total size: %zu\n", + *pos, size); + + knot_dname_t *owner = knot_dname_parse_from_wire(wire, pos, size, + NULL); + dbg_packet("Created owner: %p, actual position: %zu\n", owner, + *pos); + if (owner == NULL) { + return NULL; + } + +dbg_packet_exec( + char *name = knot_dname_to_str(owner); + dbg_packet("Parsed name: %s\n", name); + free(name); +); + + //*remaining -= knot_dname_size(rrset->owner); + + /*! @todo Get rid of the numerical constant. */ + if (size - *pos < 10) { + dbg_packet("Malformed RR: Not enough data to parse RR" + " header.\n"); + knot_dname_release(owner); + return NULL; + } + + dbg_packet("Reading type from position %zu\n", *pos); + + uint16_t type = knot_wire_read_u16(wire + *pos); + uint16_t rclass = knot_wire_read_u16(wire + *pos + 2); + uint32_t ttl = knot_wire_read_u32(wire + *pos + 4); + + knot_rrset_t *rrset = knot_rrset_new(owner, type, rclass, ttl); + + /* Owner is either referenced in rrset or rrset creation failed. */ + knot_dname_release(owner); + + /* Check rrset allocation. */ + if (rrset == NULL) { + return NULL; + } + + uint16_t rdlength = knot_wire_read_u16(wire + *pos + 8); + + dbg_packet("Read RR header: type %u, class %u, ttl %u, " + "rdlength %u\n", rrset->type, rrset->rclass, + rrset->ttl, rdlength); + + *pos += 10; + + if (size - *pos < rdlength) { + dbg_packet("Malformed RR: Not enough data to parse RR" + " RDATA (size: %zu, position: %zu).\n", + size, *pos); + knot_rrset_deep_free(&rrset, 1, 1, 0); +// free(rrset); + return NULL; + } + + rrset->rrsigs = NULL; + + if (rdlength == 0) { + return rrset; + } + + // parse RDATA + knot_rdata_t *rdata = knot_packet_parse_rdata(wire, pos, size, + rdlength, + knot_rrtype_descriptor_by_type(rrset->type)); + if (rdata == NULL) { + dbg_packet("Malformed RR: Could not parse RDATA.\n"); + knot_rrset_deep_free(&rrset, 1, 1, 0); +// free(rrset); + return NULL; + } + + if (knot_rrset_add_rdata(rrset, rdata) != KNOT_EOK) { + dbg_packet("Malformed RR: Could not add RDATA to RRSet" + ".\n"); + knot_rdata_free(&rdata); + knot_rrset_deep_free(&rrset, 1, 1, 0); +// free(rrset); + return NULL; + } + + return rrset; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_packet_add_rrset(knot_rrset_t *rrset, + const knot_rrset_t ***rrsets, + short *rrset_count, + short *max_rrsets, + short default_rrsets, + const knot_packet_t *packet, + knot_packet_duplicate_handling_t dupl) +{ + + assert(rrset != NULL); + assert(rrsets != NULL); + assert(rrset_count != NULL); + assert(max_rrsets != NULL); + +dbg_packet_exec( + char *name = knot_dname_to_str(rrset->owner); + dbg_packet("packet_add_rrset(), owner: %s, type: %s\n", + name, knot_rrtype_to_string(rrset->type)); + free(name); +); + + if (*rrset_count == *max_rrsets + && knot_packet_realloc_rrsets(rrsets, max_rrsets, default_rrsets, + STEP_ANCOUNT) != KNOT_EOK) { + return KNOT_ENOMEM; + } + + if (dupl == KNOT_PACKET_DUPL_SKIP && + knot_packet_contains(packet, rrset, KNOT_RRSET_COMPARE_PTR)) { + /*! \todo This should also return > 0, as it means that the + RRSet was not used actually. */ + return KNOT_EOK; + } + + if (dupl == KNOT_PACKET_DUPL_MERGE) { + // try to find the RRSet in this array of RRSets + for (int i = 0; i < *rrset_count; ++i) { + +dbg_packet_exec( + char *name = knot_dname_to_str((*rrsets)[i]->owner); + dbg_packet("Comparing to RRSet: owner: %s, " + "type: %s\n", name, + knot_rrtype_to_string( + (*rrsets)[i]->type)); + free(name); +); + + if (knot_rrset_compare((*rrsets)[i], rrset, + KNOT_RRSET_COMPARE_HEADER)) { + //const knot_rrset_t *r = (*rrsets) + /*! \todo Test this!!! */ + int rc = knot_rrset_merge( + (void **)((*rrsets) + i), (void **)&rrset); + if (rc != KNOT_EOK) { + return rc; + } + return 1; + } + } + } + + (*rrsets)[*rrset_count] = rrset; + ++(*rrset_count); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_packet_parse_rrs(const uint8_t *wire, size_t *pos, + size_t size, uint16_t rr_count, + const knot_rrset_t ***rrsets, + short *rrset_count, short *max_rrsets, + short default_rrsets, + knot_packet_t *packet) +{ + assert(pos != NULL); + assert(wire != NULL); + assert(rrsets != NULL); + assert(rrset_count != NULL); + assert(max_rrsets != NULL); + assert(packet != NULL); + + dbg_packet("Parsing RRSets starting on position: %zu\n", + *pos); + +// if (*rrsets == NULL) { +// knot_packet_realloc_rrsets(rrsets, max_rrsets, 0, 1); +// } + + /* + * The RRs from one RRSet may be scattered in the current section. + * We must parse all RRs separately and try to add them to already + * parsed RRSets. + */ + int err = KNOT_EOK; + knot_rrset_t *rrset = NULL; + + for (int i = 0; i < rr_count; ++i) { + rrset = knot_packet_parse_rr(wire, pos, size); + if (rrset == NULL) { + dbg_packet("Failed to parse RR!\n"); + err = KNOT_EMALF; + break; + } + + err = knot_packet_add_rrset(rrset, rrsets, rrset_count, + max_rrsets, default_rrsets, packet, + KNOT_PACKET_DUPL_MERGE); + if (err < 0) { + break; + } else if (err > 0) { // merged + dbg_packet("RRSet merged, freeing.\n"); + knot_rrset_deep_free(&rrset, 1, 0, 0); // TODO: ok?? + continue; + } + + err = knot_packet_add_tmp_rrset(packet, rrset); + if (err != KNOT_EOK) { + // remove the last RRSet from the list of RRSets + // - just decrement the count + --(*rrset_count); + knot_rrset_deep_free(&rrset, 1, 1, 1); + break; + } + } + + return (err < 0) ? err : KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Deallocates all space which was allocated additionally to the + * pre-allocated space of the response structure. + * + * \param resp Response structure that holds pointers to the allocated space. + */ +static void knot_packet_free_allocated_space(knot_packet_t *pkt) +{ + dbg_packet("Freeing additional space in packet.\n"); + if (pkt->prealloc_type == KNOT_PACKET_PREALLOC_NONE) { + dbg_packet("Freeing QNAME.\n"); + knot_dname_release(pkt->question.qname); + } + + if (pkt->max_an_rrsets > DEFAULT_RRSET_COUNT(ANCOUNT, pkt)) { + free(pkt->answer); + } + if (pkt->max_ns_rrsets > DEFAULT_RRSET_COUNT(NSCOUNT, pkt)) { + free(pkt->authority); + } + if (pkt->max_ar_rrsets > DEFAULT_RRSET_COUNT(ARCOUNT, pkt)) { + free(pkt->additional); + } + + if (pkt->compression.max > DEFAULT_DOMAINS_IN_RESPONSE) { + free(pkt->compression.dnames); + free(pkt->compression.offsets); + } + + if (pkt->tmp_rrsets_max > DEFAULT_RRSET_COUNT(TMP_RRSETS, pkt)) { + free(pkt->tmp_rrsets); + } +} + +/*----------------------------------------------------------------------------*/ + +static int knot_packet_parse_rr_sections(knot_packet_t *packet, + size_t *pos) +{ + assert(packet != NULL); + assert(packet->wireformat != NULL); + assert(packet->size > 0); + assert(pos != NULL); + assert(*pos > 0); + + int err; + + dbg_packet("Parsing Answer RRs...\n"); + if ((err = knot_packet_parse_rrs(packet->wireformat, pos, + packet->size, packet->header.ancount, &packet->answer, + &packet->an_rrsets, &packet->max_an_rrsets, + DEFAULT_RRSET_COUNT(ANCOUNT, packet), packet)) != KNOT_EOK) { + return err; + } + + dbg_packet("Parsing Authority RRs...\n"); + if ((err = knot_packet_parse_rrs(packet->wireformat, pos, + packet->size, packet->header.nscount, &packet->authority, + &packet->ns_rrsets, &packet->max_ns_rrsets, + DEFAULT_RRSET_COUNT(NSCOUNT, packet), packet)) != KNOT_EOK) { + return err; + } + + dbg_packet("Parsing Additional RRs...\n"); + if ((err = knot_packet_parse_rrs(packet->wireformat, pos, + packet->size, packet->header.arcount, &packet->additional, + &packet->ar_rrsets, &packet->max_ar_rrsets, + DEFAULT_RRSET_COUNT(ARCOUNT, packet), packet)) != KNOT_EOK) { + return err; + } + + dbg_packet("Trying to find OPT RR in the packet.\n"); + + for (int i = 0; i < packet->ar_rrsets; ++i) { + assert(packet->additional[i] != NULL); + if (knot_rrset_type(packet->additional[i]) + == KNOT_RRTYPE_OPT) { + dbg_packet("Found OPT RR, filling.\n"); + err = knot_edns_new_from_rr(&packet->opt_rr, + packet->additional[i]); + if (err != KNOT_EOK) { + return err; + } + break; + } + } + + packet->parsed = *pos; + + if (*pos < packet->size) { + // some trailing garbage; ignore, but log + dbg_response("Packet: %zu bytes of trailing garbage " + "in packet.\n", packet->size - (*pos)); + return KNOT_EMALF; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +knot_packet_t *knot_packet_new(knot_packet_prealloc_type_t prealloc) +{ + knot_packet_t *pkt; + void (*init_pointers)(knot_packet_t *pkt) = NULL; + size_t size = 0; + + switch (prealloc) { + case KNOT_PACKET_PREALLOC_NONE: + size = sizeof(knot_packet_t); + break; + case KNOT_PACKET_PREALLOC_QUERY: + size = PREALLOC_QUERY; + init_pointers = knot_packet_init_pointers_query; + break; + case KNOT_PACKET_PREALLOC_RESPONSE: + size = PREALLOC_RESPONSE; + init_pointers = knot_packet_init_pointers_response; + break; + } + + pkt = (knot_packet_t *)malloc(size); + CHECK_ALLOC_LOG(pkt, NULL); + memset(pkt, 0, size); + if (init_pointers != NULL) { + init_pointers(pkt); + } + + pkt->prealloc_type = prealloc; + + // set EDNS version to not supported + pkt->opt_rr.version = EDNS_NOT_SUPPORTED; + + return pkt; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_parse_from_wire(knot_packet_t *packet, + const uint8_t *wireformat, size_t size, + int question_only) +{ + if (packet == NULL || wireformat == NULL) { + return KNOT_EBADARG; + } + + int err; + + // save the wireformat in the packet + // TODO: can we just save the pointer, or we have to copy the data?? + assert(packet->wireformat == NULL); + packet->wireformat = (uint8_t*)wireformat; + packet->size = size; + packet->free_wireformat = 0; + + //uint8_t *pos = wireformat; + size_t pos = 0; + //size_t remaining = size; + + dbg_packet("Parsing wire format of packet (size %zu).\nHeader\n", + size); + if ((err = knot_packet_parse_header(wireformat, &pos, size, + &packet->header)) != KNOT_EOK) { + return err; + } + + packet->parsed = pos; + + dbg_packet("Question (prealloc type: %d)...\n", packet->prealloc_type); + + if (packet->header.qdcount > 1) { + dbg_packet("QDCOUNT larger than 1, FORMERR.\n"); + return KNOT_EMALF; + } + + knot_packet_dump(packet); + + if (packet->header.qdcount == 1) { + if ((err = knot_packet_parse_question(wireformat, &pos, size, + &packet->question, packet->prealloc_type + == KNOT_PACKET_PREALLOC_NONE) + ) != KNOT_EOK) { + return err; + } + packet->parsed = pos; + } + + knot_packet_dump(packet); + + if (question_only) { + return KNOT_EOK; + } + + /*! \todo Replace by call to parse_rest()? */ + err = knot_packet_parse_rr_sections(packet, &pos); + +#ifdef KNOT_PACKET_DEBUG + knot_packet_dump(packet); +#endif /* KNOT_RESPONSE_DEBUG */ + + return err; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_parse_rest(knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + +// if (packet->parsed >= packet->size) { +// return KNOT_EOK; +// } + + if (packet->parsed == packet->size) { + return KNOT_EOK; + } + + size_t pos = packet->parsed; + + return knot_packet_parse_rr_sections(packet, &pos); +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_parse_next_rr_answer(knot_packet_t *packet, + knot_rrset_t **rr) +{ + if (packet == NULL || rr == NULL) { + return KNOT_EBADARG; + } + + *rr = NULL; + + if (packet->parsed >= packet->size) { + assert(packet->an_rrsets <= packet->header.ancount); + if (packet->an_rrsets != packet->header.ancount) { + dbg_packet("Parsed less RRs than expected.\n"); + return KNOT_EMALF; + } else { + dbg_packet("Whole packet parsed\n"); + return KNOT_EOK; + } + } + + if (packet->an_rrsets == packet->header.ancount) { + assert(packet->parsed < packet->size); + //dbg_packet("Trailing garbage, ignoring...\n"); + // there may be other data in the packet + // (authority or additional). + return KNOT_EOK; + } + + size_t pos = packet->parsed; + + dbg_packet("Parsing next Answer RR (pos: %zu)...\n", pos); + *rr = knot_packet_parse_rr(packet->wireformat, &pos, packet->size); + if (*rr == NULL) { + dbg_packet("Failed to parse RR!\n"); + return KNOT_EMALF; + } + + dbg_packet("Parsed. Pos: %zu.\n", pos); + + packet->parsed = pos; + // increment the number of answer RRSets, though there are no saved + // in the packet; it is OK, because packet->answer is NULL + ++packet->an_rrsets; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_parse_next_rr_additional(knot_packet_t *packet, + knot_rrset_t **rr) +{ + /*! \todo Implement. */ + if (packet == NULL || rr == NULL) { + return KNOT_EBADARG; + } + + *rr = NULL; + + if (packet->parsed >= packet->size) { + assert(packet->ar_rrsets <= packet->header.arcount); + if (packet->ar_rrsets != packet->header.arcount) { + dbg_packet("Parsed less RRs than expected.\n"); + return KNOT_EMALF; + } else { + dbg_packet("Whole packet parsed\n"); + return KNOT_EOK; + } + } + + if (packet->ar_rrsets == packet->header.arcount) { + assert(packet->parsed < packet->size); + dbg_packet("Trailing garbage, ignoring...\n"); + /*! \todo Do not ignore. */ + return KNOT_EOK; + } + + size_t pos = packet->parsed; + + dbg_packet("Parsing next Additional RR (pos: %zu)...\n", pos); + *rr = knot_packet_parse_rr(packet->wireformat, &pos, packet->size); + if (*rr == NULL) { + dbg_packet("Failed to parse RR!\n"); + return KNOT_EMALF; + } + + dbg_packet("Parsed. Pos: %zu.\n", pos); + + packet->parsed = pos; + // increment the number of answer RRSets, though there are no saved + // in the packet; it is OK, because packet->answer is NULL + ++packet->ar_rrsets; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +size_t knot_packet_size(const knot_packet_t *packet) +{ + return packet->size; +} + +/*----------------------------------------------------------------------------*/ + +size_t knot_packet_question_size(const knot_packet_t *packet) +{ + return (KNOT_WIRE_HEADER_SIZE + 4 + + knot_dname_size(packet->question.qname)); +} + +/*----------------------------------------------------------------------------*/ + +size_t knot_packet_parsed(const knot_packet_t *packet) +{ + return packet->parsed; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_set_max_size(knot_packet_t *packet, int max_size) +{ + if (packet == NULL || max_size <= 0) { + return KNOT_EBADARG; + } + + if (packet->max_size < max_size) { + // reallocate space for the wire format (and copy anything + // that might have been there before + uint8_t *wire_new = (uint8_t *)malloc(max_size); + if (wire_new == NULL) { + return KNOT_ENOMEM; + } + + uint8_t *wire_old = packet->wireformat; + + memcpy(wire_new, packet->wireformat, packet->max_size); + packet->wireformat = wire_new; + + if (packet->max_size > 0 && packet->free_wireformat) { + free(wire_old); + } + + packet->free_wireformat = 1; + } + + // set max size + packet->max_size = max_size; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +uint16_t knot_packet_id(const knot_packet_t *packet) +{ + assert(packet != NULL); + return packet->header.id; +} + +/*----------------------------------------------------------------------------*/ + +void knot_packet_set_id(knot_packet_t *packet, uint16_t id) +{ + if (packet == NULL) { + return; + } + + packet->header.id = id; +} + +/*----------------------------------------------------------------------------*/ + +void knot_packet_set_random_id(knot_packet_t *packet) +{ + if (packet == NULL) { + return; + } + + packet->header.id = knot_random_id(); +} + +/*----------------------------------------------------------------------------*/ + +uint8_t knot_packet_opcode(const knot_packet_t *packet) +{ + assert(packet != NULL); + return knot_wire_flags_get_opcode(packet->header.flags1); +} + +/*----------------------------------------------------------------------------*/ + +const knot_dname_t *knot_packet_qname(const knot_packet_t *packet) +{ + if (packet == NULL) { + return NULL; + } + + return packet->question.qname; +} + +/*----------------------------------------------------------------------------*/ + +uint16_t knot_packet_qtype(const knot_packet_t *packet) +{ + assert(packet != NULL); + return packet->question.qtype; +} + +/*----------------------------------------------------------------------------*/ + +void knot_packet_set_qtype(knot_packet_t *packet, knot_rr_type_t qtype) +{ + assert(packet != NULL); + packet->question.qtype = qtype; +} + +/*----------------------------------------------------------------------------*/ + +uint16_t knot_packet_qclass(const knot_packet_t *packet) +{ + assert(packet != NULL); + return packet->question.qclass; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_is_query(const knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + return (knot_wire_flags_get_qr(packet->header.flags1) == 0); +} + +/*----------------------------------------------------------------------------*/ + +const knot_packet_t *knot_packet_query(const knot_packet_t *packet) +{ + if (packet == NULL) { + return NULL; + } + + return packet->query; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_rcode(const knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + return knot_wire_flags_get_rcode(packet->header.flags2); +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_tc(const knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + return knot_wire_flags_get_tc(packet->header.flags1); +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_qdcount(const knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + return packet->header.qdcount; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_ancount(const knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + return packet->header.ancount; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_nscount(const knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + return packet->header.nscount; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_arcount(const knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + return packet->header.arcount; +} + +/*----------------------------------------------------------------------------*/ + +void knot_packet_set_tsig_size(knot_packet_t *packet, size_t tsig_size) +{ + packet->tsig_size = tsig_size; +} + +/*----------------------------------------------------------------------------*/ + +short knot_packet_answer_rrset_count(const knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + return packet->an_rrsets; +} + +/*----------------------------------------------------------------------------*/ + +short knot_packet_authority_rrset_count(const knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + return packet->ns_rrsets; +} + +/*----------------------------------------------------------------------------*/ + +short knot_packet_additional_rrset_count(const knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + return packet->ar_rrsets; +} + +/*----------------------------------------------------------------------------*/ + +const knot_rrset_t *knot_packet_answer_rrset( + const knot_packet_t *packet, short pos) +{ + if (packet == NULL || pos > packet->an_rrsets) { + return NULL; + } + + return packet->answer[pos]; +} + +/*----------------------------------------------------------------------------*/ + +const knot_rrset_t *knot_packet_authority_rrset( + knot_packet_t *packet, short pos) +{ + if (packet == NULL || pos > packet->ns_rrsets) { + return NULL; + } + + return packet->authority[pos]; +} + +/*----------------------------------------------------------------------------*/ + +const knot_rrset_t *knot_packet_additional_rrset( + knot_packet_t *packet, short pos) +{ + if (packet == NULL || pos > packet->ar_rrsets) { + return NULL; + } + + return packet->additional[pos]; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_contains(const knot_packet_t *packet, + const knot_rrset_t *rrset, + knot_rrset_compare_type_t cmp) +{ + if (packet == NULL || rrset == NULL) { + return KNOT_EBADARG; + } + + for (int i = 0; i < packet->header.ancount; ++i) { + if (knot_rrset_compare(packet->answer[i], rrset, cmp)) { + return 1; + } + } + + for (int i = 0; i < packet->header.nscount; ++i) { + if (knot_rrset_compare(packet->authority[i], rrset, cmp)) { + return 1; + } + } + + for (int i = 0; i < packet->header.arcount; ++i) { + if (knot_rrset_compare(packet->additional[i], rrset, cmp)) { + return 1; + } + } + + return 0; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_add_tmp_rrset(knot_packet_t *packet, + knot_rrset_t *tmp_rrset) +{ + if (packet == NULL || tmp_rrset == NULL) { + return KNOT_EBADARG; + } + + if (packet->tmp_rrsets_count == packet->tmp_rrsets_max + && knot_packet_realloc_rrsets(&packet->tmp_rrsets, + &packet->tmp_rrsets_max, + DEFAULT_RRSET_COUNT(TMP_RRSETS, packet), + STEP_TMP_RRSETS) != KNOT_EOK) { + return KNOT_ENOMEM; + } + + packet->tmp_rrsets[packet->tmp_rrsets_count++] = tmp_rrset; + dbg_packet("Current tmp RRSets count: %d, max count: %d\n", + packet->tmp_rrsets_count, packet->tmp_rrsets_max); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Frees all temporary RRSets stored in the response structure. + * + * \param resp Response structure to free the temporary RRSets from. + */ +void knot_packet_free_tmp_rrsets(knot_packet_t *pkt) +{ + if (pkt == NULL) { + return; + } + + for (int i = 0; i < pkt->tmp_rrsets_count; ++i) { +dbg_packet_exec( + char *name = knot_dname_to_str( + (((knot_rrset_t **)(pkt->tmp_rrsets))[i])->owner); + dbg_packet("Freeing tmp RRSet on ptr: %p (ptr to ptr:" + " %p, type: %s, owner: %s)\n", + (((knot_rrset_t **)(pkt->tmp_rrsets))[i]), + &(((knot_rrset_t **)(pkt->tmp_rrsets))[i]), + knot_rrtype_to_string( + (((knot_rrset_t **)(pkt->tmp_rrsets))[i])->type), + name); + free(name); +); + // TODO: this is quite ugly, but better than copying whole + // function (for reallocating rrset array) + knot_rrset_deep_free( + &(((knot_rrset_t **)(pkt->tmp_rrsets))[i]), 1, 1, 1); + } +} + +/*----------------------------------------------------------------------------*/ + +void knot_packet_header_to_wire(const knot_header_t *header, + uint8_t **pos, size_t *size) +{ + if (header == NULL || pos == NULL || *pos == NULL || size == NULL) { + return; + } + + knot_wire_set_id(*pos, header->id); + knot_wire_set_flags1(*pos, header->flags1); + knot_wire_set_flags2(*pos, header->flags2); + knot_wire_set_qdcount(*pos, header->qdcount); + knot_wire_set_ancount(*pos, header->ancount); + knot_wire_set_nscount(*pos, header->nscount); + knot_wire_set_arcount(*pos, header->arcount); + + *pos += KNOT_WIRE_HEADER_SIZE; + *size += KNOT_WIRE_HEADER_SIZE; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_question_to_wire(knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + if (packet->size > KNOT_WIRE_HEADER_SIZE) { + return KNOT_ERROR; + } + + // TODO: get rid of the numeric constants + size_t qsize = 4 + knot_dname_size(packet->question.qname); + if (qsize > packet->max_size - KNOT_WIRE_HEADER_SIZE) { + return KNOT_ESPACE; + } + + // create the wireformat of Question + uint8_t *pos = packet->wireformat + KNOT_WIRE_HEADER_SIZE; + memcpy(pos, knot_dname_name(packet->question.qname), + knot_dname_size(packet->question.qname)); + + pos += knot_dname_size(packet->question.qname); + knot_wire_write_u16(pos, packet->question.qtype); + pos += 2; + knot_wire_write_u16(pos, packet->question.qclass); + +// int err = 0; + // TODO: put the qname into the compression table +// // TODO: get rid of the numeric constants +// if ((err = knot_response_store_dname_pos(&packet->compression, +// packet->question.qname,0, 12, 12)) != KNOT_EOK) { +// return err; +// } + + packet->size += knot_dname_size(packet->question.qname) + 4; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_edns_to_wire(knot_packet_t *packet) +{ + if (packet == NULL) { + return KNOT_EBADARG; + } + + packet->size += knot_edns_to_wire(&packet->opt_rr, + packet->wireformat + packet->size, + packet->max_size - packet->size); + + packet->header.arcount += 1; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_packet_to_wire(knot_packet_t *packet, + uint8_t **wire, size_t *wire_size) +{ + if (packet == NULL || wire == NULL || wire_size == NULL + || *wire != NULL) { + return KNOT_EBADARG; + } + + assert(packet->size <= packet->max_size); + + // if there are no additional RRSets, add EDNS OPT RR + if (packet->header.arcount == 0 + && packet->opt_rr.version != EDNS_NOT_SUPPORTED) { + knot_packet_edns_to_wire(packet); + } + + // set QDCOUNT (in response it is already set, in query it is needed) + knot_wire_set_qdcount(packet->wireformat, packet->header.qdcount); + // set ANCOUNT to the packet + knot_wire_set_ancount(packet->wireformat, packet->header.ancount); + // set NSCOUNT to the packet + knot_wire_set_nscount(packet->wireformat, packet->header.nscount); + // set ARCOUNT to the packet + knot_wire_set_arcount(packet->wireformat, packet->header.arcount); + + //assert(response->size == size); + *wire = packet->wireformat; + *wire_size = packet->size; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +const uint8_t *knot_packet_wireformat(const knot_packet_t *packet) +{ + return packet->wireformat; +} + +/*----------------------------------------------------------------------------*/ + +void knot_packet_free(knot_packet_t **packet) +{ + if (packet == NULL || *packet == NULL) { + return; + } + + // free temporary domain names + dbg_packet("Freeing tmp RRSets...\n"); + knot_packet_free_tmp_rrsets(*packet); + + // check if some additional space was allocated for the packet + dbg_packet("Freeing additional allocated space...\n"); + knot_packet_free_allocated_space(*packet); + + // free the space for wireformat +// assert((*packet)->wireformat != NULL); +// free((*packet)->wireformat); + if ((*packet)->wireformat != NULL && (*packet)->free_wireformat) { + free((*packet)->wireformat); + } + + dbg_packet("Freeing packet structure\n"); + free(*packet); + *packet = NULL; +} + +/*----------------------------------------------------------------------------*/ +#ifdef KNOT_PACKET_DEBUG +static void knot_packet_dump_rrsets(const knot_rrset_t **rrsets, + int count) +{ + assert((rrsets != NULL && *rrsets != NULL) || count < 1); + + for (int i = 0; i < count; ++i) { + knot_rrset_dump(rrsets[i], 0); + } +} +#endif +/*----------------------------------------------------------------------------*/ + +void knot_packet_dump(const knot_packet_t *packet) +{ + if (packet == NULL) { + return; + } + +#ifdef KNOT_PACKET_DEBUG + dbg_packet("DNS packet:\n-----------------------------\n"); + + dbg_packet("\nHeader:\n"); + dbg_packet(" ID: %u", packet->header.id); + dbg_packet(" FLAGS: %s %s %s %s %s %s %s\n", + knot_wire_flags_get_qr(packet->header.flags1) ? "qr" : "", + knot_wire_flags_get_aa(packet->header.flags1) ? "aa" : "", + knot_wire_flags_get_tc(packet->header.flags1) ? "tc" : "", + knot_wire_flags_get_rd(packet->header.flags1) ? "rd" : "", + knot_wire_flags_get_ra(packet->header.flags2) ? "ra" : "", + knot_wire_flags_get_ad(packet->header.flags2) ? "ad" : "", + knot_wire_flags_get_cd(packet->header.flags2) ? "cd" : ""); + dbg_packet(" QDCOUNT: %u\n", packet->header.qdcount); + dbg_packet(" ANCOUNT: %u\n", packet->header.ancount); + dbg_packet(" NSCOUNT: %u\n", packet->header.nscount); + dbg_packet(" ARCOUNT: %u\n", packet->header.arcount); + + if (knot_packet_qdcount(packet) > 0) { + dbg_packet("\nQuestion:\n"); + char *qname = knot_dname_to_str(packet->question.qname); + dbg_packet(" QNAME: %s\n", qname); + free(qname); + dbg_packet(" QTYPE: %u (%s)\n", packet->question.qtype, + knot_rrtype_to_string(packet->question.qtype)); + dbg_packet(" QCLASS: %u (%s)\n", packet->question.qclass, + knot_rrclass_to_string(packet->question.qclass)); + } + + dbg_packet("\nAnswer RRSets:\n"); + knot_packet_dump_rrsets(packet->answer, packet->an_rrsets); + dbg_packet("\nAuthority RRSets:\n"); + knot_packet_dump_rrsets(packet->authority, packet->ns_rrsets); + dbg_packet("\nAdditional RRSets:\n"); + knot_packet_dump_rrsets(packet->additional, packet->ar_rrsets); + + /*! \todo Dumping of Answer, Authority and Additional sections. */ + + dbg_packet("\nEDNS:\n"); + dbg_packet(" Version: %u\n", packet->opt_rr.version); + dbg_packet(" Payload: %u\n", packet->opt_rr.payload); + dbg_packet(" Extended RCODE: %u\n", + packet->opt_rr.ext_rcode); + + dbg_packet("\nPacket size: %zu\n", packet->size); + dbg_packet("\n-----------------------------\n"); +#endif +} + diff --git a/src/libknot/packet/packet.h b/src/libknot/packet/packet.h new file mode 100644 index 0000000..1bf74a9 --- /dev/null +++ b/src/libknot/packet/packet.h @@ -0,0 +1,538 @@ +/*! + * \file packet.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Structure for holding DNS packet data and metadata. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_PACKET_H_ +#define _KNOT_PACKET_H_ + +#include <stdint.h> +#include <string.h> + +#include "dname.h" +#include "rrset.h" +#include "edns.h" + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Structure for holding information needed for compressing domain names. + * + * It's a simple table of domain names and their offsets in wire format of the + * packet. + * + * \todo Consider using some better lookup structure, such as skip-list. + */ +struct knot_compressed_dnames { + const knot_dname_t **dnames; /*!< Domain names present in packet. */ + size_t *offsets; /*!< Offsets of domain names in the packet. */ + short count; /*!< Count of items in the previous arrays. */ + short max; /*!< Capacity of the structure (allocated). */ +}; + +typedef struct knot_compressed_dnames knot_compressed_dnames_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Structure representing the DNS packet header. + */ +struct knot_header { + uint16_t id; /*!< ID stored in host byte order. */ + uint8_t flags1; /*!< First octet of header flags. */ + uint8_t flags2; /*!< Second octet of header flags. */ + uint16_t qdcount; /*!< Number of Question RRs, in host byte order. */ + uint16_t ancount; /*!< Number of Answer RRs, in host byte order. */ + uint16_t nscount; /*!< Number of Authority RRs, in host byte order. */ + uint16_t arcount; /*!< Number of Additional RRs, in host byte order. */ +}; + +typedef struct knot_header knot_header_t; + +/*! + * \brief Structure representing one Question entry in the DNS packet. + */ +struct knot_question { + knot_dname_t *qname; /*!< Question domain name. */ + uint16_t qtype; /*!< Question TYPE. */ + uint16_t qclass; /*!< Question CLASS. */ +}; + +typedef struct knot_question knot_question_t; + +enum knot_packet_prealloc_type { + KNOT_PACKET_PREALLOC_NONE, + KNOT_PACKET_PREALLOC_QUERY, + KNOT_PACKET_PREALLOC_RESPONSE +}; + +typedef enum knot_packet_prealloc_type knot_packet_prealloc_type_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Structure representing a DNS packet. + * + * \note QNAME, Answer, Authority and Additonal sections are by default put to + * preallocated space after the structure with default sizes. If the + * space is not enough, more space is allocated dynamically. + */ +struct knot_packet { + /*! \brief DNS header. */ + knot_header_t header; + + /*! + * \brief Question section. + * + * \note Only one Question is supported! + */ + knot_question_t question; + + uint8_t *owner_tmp; /*!< Allocated space for RRSet owner wire format.*/ + + const knot_rrset_t **answer; /*!< Answer RRSets. */ + const knot_rrset_t **authority; /*!< Authority RRSets. */ + const knot_rrset_t **additional; /*!< Additional RRSets. */ + + short an_rrsets; /*!< Count of Answer RRSets in the response. */ + short ns_rrsets; /*!< Count of Authority RRSets in the response. */ + short ar_rrsets; /*!< Count of Additional RRSets in the response. */ + + short max_an_rrsets; /*!< Allocated space for Answer RRsets. */ + short max_ns_rrsets; /*!< Allocated space for Authority RRsets. */ + short max_ar_rrsets; /*!< Allocated space for Additional RRsets. */ + + knot_opt_rr_t opt_rr; /*!< OPT RR included in the packet. */ + + uint8_t *wireformat; /*!< Wire format of the packet. */ + + short free_wireformat; + size_t parsed; + + size_t size; /*!< Current wire size of the packet. */ + size_t max_size; /*!< Maximum allowed size of the packet. */ + + /*! \brief Information needed for compressing domain names in packet. */ + knot_compressed_dnames_t compression; + + /*! \brief RRSets to be destroyed with the packet structure. */ + const knot_rrset_t **tmp_rrsets; + short tmp_rrsets_count; /*!< Count of temporary RRSets. */ + short tmp_rrsets_max; /*!< Allocated space for temporary RRSets. */ + + struct knot_packet *query; /*!< Associated query. */ + + knot_packet_prealloc_type_t prealloc_type; + + size_t tsig_size; /*!< Space to reserve for the TSIG RR. */ +}; + +typedef struct knot_packet knot_packet_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Default sizes for response structure parts and steps for increasing + * them. + */ +enum { + DEFAULT_ANCOUNT = 6, /*!< Default count of Answer RRSets. */ + DEFAULT_NSCOUNT = 8, /*!< Default count of Authority RRSets. */ + DEFAULT_ARCOUNT = 28, /*!< Default count of Additional RRSets. */ + + DEFAULT_ANCOUNT_QUERY = 1, /*!< Default count of Answer RRSets. */ + DEFAULT_NSCOUNT_QUERY = 0, /*!< Default count of Authority RRSets. */ + DEFAULT_ARCOUNT_QUERY = 1, /*!< Default count of Additional RRSets. */ + /*! + * \brief Default count of all domain names in response. + * + * Used for compression table. + */ + DEFAULT_DOMAINS_IN_RESPONSE = 22, + + /*! \brief Default count of temporary RRSets stored in response. */ + DEFAULT_TMP_RRSETS = 5, + + /*! \brief Default count of temporary RRSets stored in query. */ + DEFAULT_TMP_RRSETS_QUERY = 2, + + STEP_ANCOUNT = 6, /*!< Step for increasing space for Answer RRSets. */ + STEP_NSCOUNT = 8, /*!< Step for increasing space for Authority RRSets.*/ + STEP_ARCOUNT = 8,/*!< Step for increasing space for Additional RRSets.*/ + STEP_DOMAINS = 10, /*!< Step for resizing compression table. */ + STEP_TMP_RRSETS = 5 /*!< Step for increasing temorary RRSets count. */ +}; + +/*----------------------------------------------------------------------------*/ +#define PREALLOC_RRSETS(count) (count * sizeof(knot_rrset_t *)) + +/*! \brief Sizes for preallocated space in the response structure. */ +enum { + /*! \brief Size of the response structure itself. */ + PREALLOC_PACKET = sizeof(knot_packet_t), + /*! \brief Space for QNAME dname structure. */ + PREALLOC_QNAME_DNAME = sizeof(knot_dname_t), + /*! \brief Space for QNAME name (maximum domain name size). */ + PREALLOC_QNAME_NAME = 256, + /*! \brief Space for QNAME labels (maximum label count). */ + PREALLOC_QNAME_LABELS = 127, + /*! \brief Total space for QNAME. */ + PREALLOC_QNAME = PREALLOC_QNAME_DNAME + + PREALLOC_QNAME_NAME + + PREALLOC_QNAME_LABELS, + /*! + * \brief Space for RR owner wire format. + * + * Temporary buffer, used when putting RRSets to the response. + */ + PREALLOC_RR_OWNER = 256, + +// /*! \brief Space for Answer RRSets. */ +// PREALLOC_ANSWER = DEFAULT_ANCOUNT * sizeof(knot_dname_t *), +// /*! \brief Space for Authority RRSets. */ +// PREALLOC_AUTHORITY = DEFAULT_NSCOUNT * sizeof(knot_dname_t *), +// /*! \brief Space for Additional RRSets. */ +// PREALLOC_ADDITIONAL = DEFAULT_ARCOUNT * sizeof(knot_dname_t *), +// /*! \brief Total size for Answer, Authority and Additional RRSets. */ +// PREALLOC_RRSETS = PREALLOC_ANSWER +// + PREALLOC_AUTHORITY +// + PREALLOC_ADDITIONAL, + /*! \brief Space for one part of the compression table (domain names).*/ + PREALLOC_DOMAINS = + DEFAULT_DOMAINS_IN_RESPONSE * sizeof(knot_dname_t *), + /*! \brief Space for other part of the compression table (offsets). */ + PREALLOC_OFFSETS = + DEFAULT_DOMAINS_IN_RESPONSE * sizeof(size_t), + PREALLOC_COMPRESSION = PREALLOC_DOMAINS + PREALLOC_OFFSETS, + +// /*! \brief Space for temporary RRSets. */ +// PREALLOC_TMP_RRSETS = +// DEFAULT_TMP_RRSETS * sizeof(knot_rrset_t *), + + PREALLOC_QUERY = PREALLOC_PACKET + + PREALLOC_QNAME + + PREALLOC_RRSETS(DEFAULT_ANCOUNT_QUERY) + + PREALLOC_RRSETS(DEFAULT_NSCOUNT_QUERY) + + PREALLOC_RRSETS(DEFAULT_ARCOUNT_QUERY) + + PREALLOC_RRSETS(DEFAULT_TMP_RRSETS_QUERY), + + /*! \brief Total preallocated size for the response. */ + PREALLOC_RESPONSE = PREALLOC_PACKET + + PREALLOC_QNAME + + PREALLOC_RR_OWNER + + PREALLOC_RRSETS(DEFAULT_ANCOUNT) + + PREALLOC_RRSETS(DEFAULT_NSCOUNT) + + PREALLOC_RRSETS(DEFAULT_ARCOUNT) + + PREALLOC_COMPRESSION + + PREALLOC_RRSETS(DEFAULT_TMP_RRSETS) +}; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates new empty packet structure. + * + * \param prealloc What space should be preallocated in the structure. + * + * \return New packet structure or NULL if an error occured. + */ +knot_packet_t *knot_packet_new(knot_packet_prealloc_type_t prealloc); + +/*! + * \brief Parses the DNS packet from wire format. + * + * \param packet Packet structure to parse into. + * \param wireformat Wire format of the DNS packet. + * \param size Size of the wire format in bytes. + * \param question_only Set to <> 0 if you do not want to parse the whole + * packet. In such case the parsing will end after the + * Question section. Set to 0 to parse the whole packet. + * + * \retval KNOT_EOK + */ +int knot_packet_parse_from_wire(knot_packet_t *packet, + const uint8_t *wireformat, size_t size, + int question_only); + +int knot_packet_parse_rest(knot_packet_t *packet); + +int knot_packet_parse_next_rr_answer(knot_packet_t *packet, + knot_rrset_t **rr); + +int knot_packet_parse_next_rr_additional(knot_packet_t *packet, + knot_rrset_t **rr); + +size_t knot_packet_size(const knot_packet_t *packet); + +/*! \brief Returns size of the wireformat of Header and Question sections. */ +size_t knot_packet_question_size(const knot_packet_t *packet); + +size_t knot_packet_parsed(const knot_packet_t *packet); + +/*! + * \brief Sets the maximum size of the packet and allocates space for wire + * format (if needed). + * + * This function also allocates space for the wireformat of the packet, if + * the given max size is larger than the current maximum size of the packet + * and copies the current wireformat over to the new space. + * + * \warning Do not call this function if you are not completely sure that the + * current wire format of the packet fits into the new space. + * It does not update the current size of the wire format, so the + * produced packet may be larger than the given max size. + * + * \param packet Packet to set the maximum size of. + * \param max_size Maximum size of the packet in bytes. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + * \retval KNOT_ENOMEM + * + * \todo Needs test. + */ +int knot_packet_set_max_size(knot_packet_t *packet, int max_size); + +uint16_t knot_packet_id(const knot_packet_t *packet); + +void knot_packet_set_id(knot_packet_t *packet, uint16_t id); + +void knot_packet_set_random_id(knot_packet_t *packet); + +/*! + * \brief Returns the OPCODE of the packet. + * + * \param packet Packet (with parsed query) to get the OPCODE from. + * + * \return OPCODE stored in the packet. + */ +uint8_t knot_packet_opcode(const knot_packet_t *packet); + +/*! + * \brief Returns the QNAME from the packet. + * + * \param packet Packet (with parsed query) to get the QNAME from. + * + * \return QNAME stored in the packet. + */ +const knot_dname_t *knot_packet_qname(const knot_packet_t *packet); + +/*! + * \brief Returns the QTYPE from the packet. + * + * \param packet Packet (with parsed query) to get the QTYPE from. + * + * \return QTYPE stored in the packet. + */ +uint16_t knot_packet_qtype(const knot_packet_t *packet); + +/*! + * \brief Set the QTYPE of the packet. + * + * \param packet Packet containing question. + * \param qtype New QTYPE for question. + */ +void knot_packet_set_qtype(knot_packet_t *packet, knot_rr_type_t qtype); + + +/*! + * \brief Returns the QCLASS from the packet. + * + * \param response Packet (with parsed query) to get the QCLASS from. + * + * \return QCLASS stored in the packet. + */ +uint16_t knot_packet_qclass(const knot_packet_t *packet); + +int knot_packet_is_query(const knot_packet_t *packet); + +const knot_packet_t *knot_packet_query(const knot_packet_t *packet); + +int knot_packet_rcode(const knot_packet_t *packet); + +int knot_packet_tc(const knot_packet_t *packet); + +int knot_packet_qdcount(const knot_packet_t *packet); + +int knot_packet_ancount(const knot_packet_t *packet); + +int knot_packet_nscount(const knot_packet_t *packet); + +int knot_packet_arcount(const knot_packet_t *packet); + +void knot_packet_set_tsig_size(knot_packet_t *packet, size_t tsig_size); + +/*! + * \brief Returns number of RRSets in Answer section of the packet. + * + * \param response Packet to get the Answer RRSet count from. + */ +short knot_packet_answer_rrset_count(const knot_packet_t *packet); + +/*! + * \brief Returns number of RRSets in Authority section of the packet. + * + * \param response Packet to get the Authority RRSet count from. + */ +short knot_packet_authority_rrset_count(const knot_packet_t *packet); + +/*! + * \brief Returns number of RRSets in Additional section of the packet. + * + * \param response Packet to get the Additional RRSet count from. + */ +short knot_packet_additional_rrset_count(const knot_packet_t *packet); + +/*! + * \brief Returns the requested Answer RRset. + * + * \param packet Packet to get the RRSet from. + * \param pos Position of the RRSet in the Answer section (RRSets are stored + * in the order they were added to the response or parsed from the + * query). + * + * \return The RRSet on position \a pos in the Answer section of \a packet + * or NULL if there is no such RRSet. + */ +const knot_rrset_t *knot_packet_answer_rrset( + const knot_packet_t *packet, short pos); + +/*! + * \brief Returns the requested Authority RRset. + * + * \param packet Packet to get the RRSet from. + * \param pos Position of the RRSet in the Authority section (RRSets are stored + * in the order they were added to the response or parsed from the + * query). + * + * \return The RRSet on position \a pos in the Authority section of \a packet + * or NULL if there is no such RRSet. + */ +const knot_rrset_t *knot_packet_authority_rrset( + knot_packet_t *packet, short pos); + +/*! + * \brief Returns the requested Additional RRset. + * + * \param packet Packet to get the RRSet from. + * \param pos Position of the RRSet in the Additional section (RRSets are stored + * in the order they were added to the response or parsed from the + * query). + * + * \return The RRSet on position \a pos in the Additional section of \a packet + * or NULL if there is no such RRSet. + */ +const knot_rrset_t *knot_packet_additional_rrset( + knot_packet_t *packet, short pos); + +/*! + * \brief Checks if the packet already contains the given RRSet. + * + * It searches for the RRSet in the three lists of RRSets corresponding to + * Answer, Authority and Additional sections of the packet. + * + * \note Only pointers are compared, i.e. two instances of knot_rrset_t with + * the same data will be considered different. + * + * \param packet Packet to look for the RRSet in. + * \param rrset RRSet to look for. + * + * \retval 0 if \a resp does not contain \a rrset. + * \retval <> 0 if \a resp does contain \a rrset. + */ +int knot_packet_contains(const knot_packet_t *packet, + const knot_rrset_t *rrset, + knot_rrset_compare_type_t cmp); + +/*! + * \brief Adds RRSet to the list of temporary RRSets. + * + * Temporary RRSets are fully freed when the response structure is destroyed. + * + * \param response Response to which the temporary RRSet should be added. + * \param tmp_rrset Temporary RRSet to be stored in the response. + * + * \retval KNOT_EOK + * \retval KNOT_ENOMEM + */ +int knot_packet_add_tmp_rrset(knot_packet_t *response, + knot_rrset_t *tmp_rrset); + +void knot_packet_free_tmp_rrsets(knot_packet_t *pkt); + +/*! + * \brief Converts the header structure to wire format. + * + * \note This function also adjusts the position (\a pos) according to + * the size of the converted wire format. + * + * \param[in] header DNS header structure to convert. + * \param[out] pos Position where to put the converted header. The space has + * to be allocated before calling this function. + * \param[out] size Size of the wire format of the header in bytes. + */ +void knot_packet_header_to_wire(const knot_header_t *header, + uint8_t **pos, size_t *size); + +int knot_packet_question_to_wire(knot_packet_t *packet); + +/*! + * \brief Converts the stored response OPT RR to wire format and adds it to + * the response wire format. + * + * \param resp Response structure. + */ +int knot_packet_edns_to_wire(knot_packet_t *packet); + +/*! + * \brief Converts the packet to wire format. + * + * \param packet Packet to be converted to wire format. + * \param wire Here the wire format of the packet will be stored. + * Space for the packet will be allocated. *resp_wire must + * be set to NULL (to avoid leaks). + * \param wire_size The size of the packet in wire format will be stored here. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + */ +int knot_packet_to_wire(knot_packet_t *packet, uint8_t **wire, + size_t *wire_size); + +const uint8_t *knot_packet_wireformat(const knot_packet_t *packet); + +/*! + * \brief Properly destroys the packet structure. + * + * \param response Packet to be destroyed. + */ +void knot_packet_free(knot_packet_t **packet); + +/*! + * \brief Dumps the whole packet in human-readable form. + * + * \note This function is empty unless KNOT_PACKET_DEBUG is defined. + * + * \param resp Packet to dump. + */ +void knot_packet_dump(const knot_packet_t *packet); + +#endif /* _KNOT_PACKET_H_ */ + +/*! @} */ diff --git a/src/libknot/packet/query.c b/src/libknot/packet/query.c new file mode 100644 index 0000000..63e902a --- /dev/null +++ b/src/libknot/packet/query.c @@ -0,0 +1,228 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include "packet/query.h" + +#include "util/error.h" +#include "util/wire.h" + +/*----------------------------------------------------------------------------*/ + +int knot_query_rr_to_wire(const knot_rrset_t *rrset, const knot_rdata_t *rdata, + uint8_t **wire, uint8_t *endp) +{ + /* Store owner. */ + knot_dname_t *owner = rrset->owner; + if (*wire + owner->size > endp) { + return KNOT_ENOMEM; + } + memcpy(*wire, owner->name, owner->size); + *wire += owner->size; + + if (*wire + 10 > endp) { + return KNOT_ENOMEM; + } + + /* Write RR header. */ + knot_wire_write_u16(*wire, rrset->type); *wire += 2; + knot_wire_write_u16(*wire, rrset->rclass); *wire += 2; + knot_wire_write_u32(*wire, rrset->ttl); *wire += 4; + knot_wire_write_u16(*wire, 0); *wire += 2; /* RDLENGTH reserve. */ + uint8_t *rdlength_p = *wire - 2; + uint16_t rdlength = 0; + + /* Write data. */ + knot_dname_t *dname = 0; + uint16_t *raw_data = 0; + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(rrset->type); + + for (int i = 0; i < rdata->count; ++i) { + switch (desc->wireformat[i]) { + case KNOT_RDATA_WF_COMPRESSED_DNAME: + case KNOT_RDATA_WF_UNCOMPRESSED_DNAME: + case KNOT_RDATA_WF_LITERAL_DNAME: + + /* Check space for dname. */ + dname = knot_rdata_item(rdata, i)->dname; + if (*wire + dname->size > endp) { + return KNOT_ESPACE; + } + + /* Save domain name. */ + memcpy(*wire, dname->name, dname->size); + *wire += dname->size; + rdlength += dname->size; + break; + default: + raw_data = knot_rdata_item(rdata, i)->raw_data; + if (*wire + raw_data[0] > endp) { + return KNOT_ESPACE; + } + + /* Copy data. */ + memcpy(*wire, raw_data + 1, raw_data[0]); + *wire += raw_data[0]; + rdlength += raw_data[0]; + break; + + } + } + + /* Store rdlength. */ + knot_wire_write_u16(rdlength_p, rdlength); + + return KNOT_EOK; +} +/*----------------------------------------------------------------------------*/ + +int knot_query_dnssec_requested(const knot_packet_t *query) +{ + if (query == NULL) { + return KNOT_EBADARG; + } + + return ((knot_edns_get_version(&query->opt_rr) != EDNS_NOT_SUPPORTED) + && knot_edns_do(&query->opt_rr)); +} + +/*----------------------------------------------------------------------------*/ + +int knot_query_nsid_requested(const knot_packet_t *query) +{ + if (query == NULL) { + return KNOT_EBADARG; + } + + return ((knot_edns_get_version(&query->opt_rr) != EDNS_NOT_SUPPORTED) + && knot_edns_has_option(&query->opt_rr, EDNS_OPTION_NSID)); +} + +/*----------------------------------------------------------------------------*/ + +int knot_query_edns_supported(const knot_packet_t *query) +{ + if (query == NULL) { + return KNOT_EBADARG; + } + + return (knot_edns_get_version(&query->opt_rr) != EDNS_NOT_SUPPORTED); +} + +/*----------------------------------------------------------------------------*/ + +int knot_query_init(knot_packet_t *query) +{ + if (query == NULL) { + return KNOT_EBADARG; + } + // set the qr bit to 0 + knot_wire_flags_clear_qr(&query->header.flags1); + + uint8_t *pos = query->wireformat; + knot_packet_header_to_wire(&query->header, &pos, &query->size); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_query_set_question(knot_packet_t *query, + const knot_question_t *question) +{ + if (query == NULL || question == NULL) { + return KNOT_EBADARG; + } + + query->question.qname = question->qname; + query->question.qclass = question->qclass; + query->question.qtype = question->qtype; + query->header.qdcount = 1; + + // convert the Question to wire format right away + knot_packet_question_to_wire(query); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_query_set_opcode(knot_packet_t *query, uint8_t opcode) +{ + if (query == NULL) { + return KNOT_EBADARG; + } + // set the OPCODE in the structure + knot_wire_flags_set_opcode(&query->header.flags1, opcode); + // set the OPCODE in the wire format + knot_wire_set_opcode(query->wireformat, opcode); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_query_add_rrset_authority(knot_packet_t *query, + const knot_rrset_t *rrset) +{ + if (query == NULL || rrset == NULL) { + return KNOT_EBADARG; + } + + if (query->ns_rrsets == query->max_ns_rrsets) { + size_t oldsize = query->max_ns_rrsets * sizeof(knot_rrset_t *); + ++query->max_ns_rrsets; + size_t newsize = query->max_ns_rrsets * sizeof(knot_rrset_t *); + const knot_rrset_t ** na = malloc(newsize); + if (na == 0) { + query->max_ns_rrsets = 0; + return KNOT_ENOMEM; + } else { + memcpy(na, query->authority, oldsize); + free(query->authority); + query->authority = na; + } + } + + /* Append to packet. */ + query->authority[query->ns_rrsets] = rrset; + + /* Write to wire. */ + uint8_t *startp = query->wireformat + query->size; + uint8_t *endp = query->wireformat + query->max_size; + + assert(endp - startp > query->opt_rr.size + query->tsig_size); + // reserve space for OPT RR + endp -= query->opt_rr.size; + /*! \note [TSIG] reserve space for TSIG RR */ + endp -= query->tsig_size; + + uint8_t *pos = startp; + + const knot_rdata_t *rdata = 0; + while ((rdata = knot_rrset_rdata_next(rrset, rdata))) { + knot_query_rr_to_wire(rrset, rdata, &pos, endp); + } + + size_t written = (pos - startp); + query->size += written; + ++query->ns_rrsets; + ++query->header.nscount; + + return KNOT_EOK; +} + diff --git a/src/libknot/packet/query.h b/src/libknot/packet/query.h new file mode 100644 index 0000000..a979641 --- /dev/null +++ b/src/libknot/packet/query.h @@ -0,0 +1,93 @@ +/*! + * \file query.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief API for manipulating queries. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_QUERY_H_ +#define _KNOT_QUERY_H_ + +#include <stdint.h> +#include <string.h> + +#include "packet/packet.h" +#include "dname.h" +#include "rrset.h" +#include "edns.h" + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Checks if DNSSEC was requested in the query (i.e. the DO bit was set). + * + * \param query Packet where the parsed query is stored. + * + * \retval 0 if the DO bit was not set in the query, or the query is not yet + * parsed. + * \retval > 0 if DO bit was set in the query. + */ +int knot_query_dnssec_requested(const knot_packet_t *query); + +/*! + * \brief Checks if NSID was requested in the query (i.e. the NSID option was + * present in the query OPT RR). + * + * \param query Packet where the parsed query is stored. + * + * \retval 0 if the NSID option was not present in the query, or the query is + * not yet parsed. + * \retval > 0 if the NSID option was present in the query. + */ +int knot_query_nsid_requested(const knot_packet_t *query); + +int knot_query_edns_supported(const knot_packet_t *query); + +//int knot_query_set_qname(knot_packet_t *query, const knot_dname_t *qname); + +//int knot_query_set_qtype(knot_packet_t *query, uint16_t qtype); + +//int knot_query_set_qclass(knot_packet_t *query, uint16_t qclass); + +int knot_query_init(knot_packet_t *query); + +int knot_query_set_question(knot_packet_t *query, + const knot_question_t *question); + +int knot_query_set_opcode(knot_packet_t *query, uint8_t opcode); + +/*! + * \brief Adds a RRSet to the Authority section of the query. + * + * \param query Query to add the RRSet into. + * \param rrset RRSet to be added. + * + * \retval KNOT_EOK if successful, or the RRSet was already in the query. + * \retval KNOT_ENOMEM + * \retval KNOT_ESPACE + */ +int knot_query_add_rrset_authority(knot_packet_t *query, + const knot_rrset_t *rrset); + + +#endif /* _KNOT_QUERY_H_ */ + +/*! @} */ diff --git a/src/libknot/packet/response.c b/src/libknot/packet/response.c new file mode 100644 index 0000000..e6f89d0 --- /dev/null +++ b/src/libknot/packet/response.c @@ -0,0 +1,1170 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> + +#include "packet/response.h" +#include "util/wire.h" +#include "util/descriptor.h" +#include "common.h" +#include "util/error.h" +#include "util/debug.h" +#include "packet/packet.h" + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Holds information about compressed domain name. + * + * Used only to pass information between functions. + * + * \todo This description should be revised and clarified. + */ +struct knot_compr_owner { + /*! + * \brief Place where the name is stored in the wire format of the + * packet. + */ + uint8_t *wire; + short size; /*!< Size of the domain name in bytes. */ + /*! \brief Position of the name relative to the start of the packet. */ + size_t pos; +}; + +typedef struct knot_compr_owner knot_compr_owner_t; + +/*! + * \brief Holds information about compressed domain names in packet. + * + * Used only to pass information between functions. + * + * \todo This description should be revised and clarified. + */ +struct knot_compr { + knot_compressed_dnames_t *table; /*!< Compression table. */ + size_t wire_pos; /*!< Current position in the wire format. */ + knot_compr_owner_t owner; /*!< Information about the current name. */ +}; + +typedef struct knot_compr knot_compr_t; + +static const size_t KNOT_RESPONSE_MAX_PTR = 16383; + +/*----------------------------------------------------------------------------*/ +/* Non-API functions */ +/*----------------------------------------------------------------------------*/ +/*! + * \brief Reallocates space for compression table. + * + * \param table Compression table to reallocate space for. + * + * \retval KNOT_EOK + * \retval KNOT_ENOMEM + */ +static int knot_response_realloc_compr(knot_compressed_dnames_t *table) +{ + int free_old = table->max != DEFAULT_DOMAINS_IN_RESPONSE; + size_t *old_offsets = table->offsets; + const knot_dname_t **old_dnames = table->dnames; + + short new_max_count = table->max + STEP_DOMAINS; + + size_t *new_offsets = (size_t *)malloc(new_max_count * sizeof(size_t)); + CHECK_ALLOC_LOG(new_offsets, -1); + + const knot_dname_t **new_dnames = (const knot_dname_t **)malloc( + new_max_count * sizeof(knot_dname_t *)); + if (new_dnames == NULL) { + ERR_ALLOC_FAILED; + free(new_offsets); + return KNOT_ENOMEM; + } + + memcpy(new_offsets, table->offsets, table->max * sizeof(size_t)); + memcpy(new_dnames, table->dnames, + table->max * sizeof(knot_dname_t *)); + + table->offsets = new_offsets; + table->dnames = new_dnames; + table->max = new_max_count; + + if (free_old) { + free(old_offsets); + free(old_dnames); + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Stores new mapping between domain name and offset in the compression + * table. + * + * If the domain name is already present in the table, it is not inserted again. + * + * \param table Compression table to save the mapping into. + * \param dname Domain name to insert. + * \param pos Position of the domain name in the packet's wire format. + */ +static void knot_response_compr_save(knot_compressed_dnames_t *table, + const knot_dname_t *dname, size_t pos) +{ + assert(table->count < table->max); + + for (int i = 0; i < table->count; ++i) { + if (table->dnames[i] == dname) { + dbg_response("Already present, skipping..\n"); + return; + } + } + + table->dnames[table->count] = dname; + table->offsets[table->count] = pos; + ++table->count; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Stores domain name position and positions of its parent domain names + * to the compression table. + * + * If part of the domain name (\a dname) was not found previously in the + * compression table, this part and all its parent domains is stored also, to + * maximize compression potential. + * + * \param table Compression table to save the information into. + * \param dname Domain name to save. + * \param not_matched Count of labels not matched when previously searching in + * the compression table for \a dname. + * \param pos Position of the domain name in the wire format of the packet. + * \param unmatched_offset Position of the unmatched parent domain of \a dname. + * + * \retval KNOT_EOK + * \retval KNOT_ENOMEM + */ +static int knot_response_store_dname_pos(knot_compressed_dnames_t *table, + const knot_dname_t *dname, + int not_matched, size_t pos, + size_t unmatched_offset, + int compr_cs) +{ +dbg_response_exec( + char *name = knot_dname_to_str(dname); + dbg_response("Putting dname %s into compression table." + " Labels not matched: %d, position: %zu," + ", pointer: %p, unmatched off: %zu\n", name, + not_matched, pos, dname, unmatched_offset); + free(name); +); + if (pos > KNOT_RESPONSE_MAX_PTR) { + dbg_response("Pointer larger than it can be, not" + " saving\n"); + return KNOT_EDNAMEPTR; + } + + if (table->count == table->max && + knot_response_realloc_compr(table) != 0) { + return KNOT_ENOMEM; + } + + // store the position of the name +// table->dnames[table->count] = dname; +// table->offsets[table->count] = pos; +// ++table->count; + + /* + * Store positions of ancestors if more than 1 label was not matched. + * + * In case the name is not in the zone, the counting to not_matched + * may be limiting, because the search stopped before after the first + * label (i.e. not_matched == 1). So we do not store the parents in + * this case. However, storing them will require creating those domain + * names, as they do not exist. + * + * The same problem is with domain names synthetized from wildcards. + * These also do not have any node to follow. + * + * We accept this as performance has higher + * priority than the best possible compression. + */ + const knot_dname_t *to_save = dname; + size_t parent_pos = pos; + int i = 0; + + while (to_save != NULL && i < knot_dname_label_count(dname)) { + if (i == not_matched) { + parent_pos = unmatched_offset; + } + +dbg_response_exec( + char *name = knot_dname_to_str(to_save); + dbg_response("Putting dname %s into compression table." + " Position: %zu, pointer: %p\n", + name, parent_pos, to_save); + free(name); +); + + if (table->count == table->max && + knot_response_realloc_compr(table) != 0) { + dbg_response("Unable to realloc.\n"); + return KNOT_ENOMEM; + } + +// dbg_response("Saving..\n"); + knot_response_compr_save(table, to_save, parent_pos); + + /*! \todo Remove '!compr_cs'. */ + // This is a temporary hack to avoid the wrong behaviour + // when the wrong not_matched count is used to compare with i + // and resulting in using the 0 offset. + // If case-sensitive search is in place, we should not save the + // node's parent's positions. + + to_save = !compr_cs && (knot_dname_node(to_save, 1) != NULL + && knot_node_parent(knot_dname_node(to_save, 1), 1) + != NULL) ? knot_node_owner(knot_node_parent( + knot_dname_node(to_save, 1), 1)) + : NULL; + + dbg_response("i: %d\n", i); + parent_pos += knot_dname_label_size(dname, i) + 1; +// parent_pos += (i > 0) +// ? knot_dname_label_size(dname, i - 1) + 1 : 0; + ++i; + } + + return KNOT_EOK; +} + +/*---------------------------------------------------------------------------*/ +/*! + * \brief Tries to find offset of domain name in the compression table. + * + * \param table Compression table to search in. + * \param dname Domain name to search for. + * \param compr_cs Set to <> 0 if dname compression should use case sensitive + * comparation. Set to 0 otherwise. + * + * \return Offset of \a dname stored in the compression table or -1 if the name + * was not found in the table. + */ +static size_t knot_response_find_dname_pos( + const knot_compressed_dnames_t *table, + const knot_dname_t *dname, int compr_cs) +{ + for (int i = 0; i < table->count; ++i) { +// dbg_response("Comparing dnames %p and %p\n", +// dname, table->dnames[i]); +//dbg_response_exec( +// char *name = knot_dname_to_str(dname); +// dbg_response("(%s and ", name); +// name = knot_dname_to_str(table->dnames[i]); +// dbg_response("%s)\n", name); +// free(name); +//); + //if (table->dnames[i] == dname) { + int ret = (compr_cs) + ? knot_dname_compare_cs(table->dnames[i], dname) + : knot_dname_compare(table->dnames[i], dname); + if (ret == 0) { + dbg_response("Found offset: %zu\n", + table->offsets[i]); + return table->offsets[i]; + } + } + return 0; +} + +/*---------------------------------------------------------------------------*/ +/*! + * \brief Put a compressed domain name to the wire format of the packet. + * + * Puts the not matched part of the domain name to the wire format and puts + * a pointer to the rest of the name after that. + * + * \param dname Domain name to put to the wire format. + * \param not_matched Size of the part of domain name that cannot be compressed. + * \param offset Position of the rest of the domain name in the packet's wire + * format. + * \param wire Place where to put the wire format of the name. + * \param max Maximum available size of the place for the wire format. + * + * \return Size of the compressed domain name put into the wire format or + * KNOT_ESPACE if it did not fit. + */ +static int knot_response_put_dname_ptr(const knot_dname_t *dname, + int not_matched, size_t offset, + uint8_t *wire, size_t max) +{ + // put the not matched labels + short size = knot_dname_size_part(dname, not_matched); + if (size + 2 > max) { + return KNOT_ESPACE; + } + + memcpy(wire, knot_dname_name(dname), size); + knot_wire_put_pointer(wire + size, offset); + + dbg_response("Size of the dname with ptr: %d\n", size + 2); + + return size + 2; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Tries to compress domain name and creates its wire format. + * + * \param dname Domain name to convert and compress. + * \param compr Compression table holding information about offsets of domain + * names in the packet. + * \param dname_wire Place where to put the wire format of the name. + * \param max Maximum available size of the place for the wire format. + * \param compr_cs Set to <> 0 if dname compression should use case sensitive + * comparation. Set to 0 otherwise. + * + * \return Size of the domain name's wire format or KNOT_ESPACE if it did not + * fit into the provided space. + */ +static int knot_response_compress_dname(const knot_dname_t *dname, + knot_compr_t *compr, uint8_t *dname_wire, size_t max, int compr_cs) +{ + int size = 0; + /*! + * \todo Compress!! + * + * if pos == 0, do not store the position! + */ + + // try to find the name or one of its ancestors in the compr. table +#ifdef COMPRESSION_PEDANTIC + //knot_dname_t *to_find = knot_dname_copy(dname); + knot_dname_t *to_find = (knot_dname_t *)dname; + int copied = 0; +#else + const knot_dname_t *to_find = dname; +#endif + size_t offset = 0; + int not_matched = 0; + + while (to_find != NULL && knot_dname_label_count(to_find) != 0) { +dbg_response_exec( + char *name = knot_dname_to_str(to_find); + dbg_response("Searching for name %s in the compression" + " table, not matched labels: %d\n", name, + not_matched); + free(name); +); + offset = knot_response_find_dname_pos(compr->table, to_find, + compr_cs); + if (offset == 0) { + ++not_matched; + } else { + break; + } +#ifdef COMPRESSION_PEDANTIC + if (compr_cs || to_find->node == NULL + || to_find->node->owner != to_find + || to_find->node->parent == NULL) { + if (!copied) { + to_find = knot_dname_left_chop(to_find); + copied = 1; + } else { + knot_dname_left_chop_no_copy(to_find); + } + } else { + assert(to_find->node != to_find->node->parent); + assert(to_find != to_find->node->parent->owner); + to_find = to_find->node->parent->owner; + } +#else + // if case-sensitive comparation, we cannot just take the parent + if (compr_cs || knot_dname_node(to_find, 1) == NULL + || knot_node_owner(knot_dname_node(to_find, 1)) != to_find + || knot_node_parent(knot_dname_node(to_find, 1), 1) + == NULL) { + dbg_response("compr_cs: %d\n", compr_cs); + dbg_response("knot_dname_node(to_find, 1) == %p" + "\n", knot_dname_node(to_find, 1)); + + if (knot_dname_node(to_find, 1) != NULL) { + dbg_response("knot_node_owner(knot_dname_node(" + "to_find, 1)) = %p, to_find = %p\n", + knot_node_owner(knot_dname_node(to_find, 1)), + to_find); + dbg_response("knot_node_parent(knot_dname_node(" + "to_find, 1), 1) = %p\n", + knot_node_parent(knot_dname_node(to_find, 1), 1)); + } + break; + } else { + assert(knot_dname_node(to_find, 1) != + knot_node_parent(knot_dname_node(to_find, 1), 1)); + assert(to_find != knot_node_owner( + knot_node_parent(knot_dname_node(to_find, 1), 1))); + to_find = knot_node_owner( + knot_node_parent(knot_dname_node(to_find, 1), 1)); + dbg_response("New to_find: %p\n", to_find); + } +#endif + } + +#ifdef COMPRESSION_PEDANTIC + if (copied) { + knot_dname_free(&to_find); + } +#endif + + dbg_response("Max size available for domain name: %zu\n", max); + + if (offset > 0) { // found such dname somewhere in the packet + dbg_response("Found name in the compression table.\n"); + assert(offset >= KNOT_WIRE_HEADER_SIZE); + size = knot_response_put_dname_ptr(dname, not_matched, offset, + dname_wire, max); + if (size <= 0) { + return KNOT_ESPACE; + } + } else { + dbg_response("Not found, putting whole name.\n"); + // now just copy the dname without compressing + if (dname->size > max) { + return KNOT_ESPACE; + } + + memcpy(dname_wire, dname->name, dname->size); + size = dname->size; + } + + // in either way, put info into the compression table + /*! \todo This is useless if the name was already in the table. + * It is meaningful only if the found name is the one from QNAME + * and thus its parents are not stored yet. + */ + assert(compr->wire_pos >= 0); + + if (knot_response_store_dname_pos(compr->table, dname, not_matched, + compr->wire_pos, offset, compr_cs) + != 0) { + dbg_response("Compression info could not be stored." + "\n"); + } + + return size; +} + +/*---------------------------------------------------------------------------*/ +/*! + * \brief Convert one RR into wire format. + * + * \param[in] rrset RRSet to which the RR belongs. + * \param[in] rdata The actual RDATA of this RR. + * \param[in] compr Information about compressed domain names in the packet. + * \param[out] rrset_wire Place to put the wire format of the RR into. + * \param[in] max_size Size of space available for the wire format. + * \param[in] compr_cs Set to <> 0 if dname compression should use case + * sensitive comparation. Set to 0 otherwise. + * + * \return Size of the RR's wire format or KNOT_ESPACE if it did not fit into + * the provided space. + */ +static int knot_response_rr_to_wire(const knot_rrset_t *rrset, + const knot_rdata_t *rdata, + knot_compr_t *compr, + uint8_t **rrset_wire, size_t max_size, + int compr_cs) +{ + int size = 0; + + dbg_response("Max size: %zu, owner pos: %zu, owner size: %d\n", + max_size, compr->owner.pos, compr->owner.size); + + if (size + ((compr->owner.pos == 0 + || compr->owner.pos > KNOT_RESPONSE_MAX_PTR) + ? compr->owner.size : 2) + 10 + > max_size) { + return KNOT_ESPACE; + } + + dbg_response("Owner position: %zu\n", compr->owner.pos); + + // put owner if needed (already compressed) + if (compr->owner.pos == 0 || compr->owner.pos > KNOT_RESPONSE_MAX_PTR) { + memcpy(*rrset_wire, compr->owner.wire, compr->owner.size); + compr->owner.pos = compr->wire_pos; + *rrset_wire += compr->owner.size; + size += compr->owner.size; + } else { + dbg_response("Putting pointer: %zu\n", + compr->owner.pos); + knot_wire_put_pointer(*rrset_wire, compr->owner.pos); + *rrset_wire += 2; + size += 2; + } + + dbg_response("Max size: %zu, size: %d\n", max_size, size); + + dbg_response("Wire format:\n"); + + // put rest of RR 'header' + knot_wire_write_u16(*rrset_wire, rrset->type); + dbg_response(" Type: %u\n", rrset->type); + dbg_response(" Type in wire: "); + dbg_response_hex((char *)*rrset_wire, 2); + *rrset_wire += 2; + + knot_wire_write_u16(*rrset_wire, rrset->rclass); + dbg_response(" Class: %u\n", rrset->rclass); + dbg_response(" Class in wire: "); + dbg_response_hex((char *)*rrset_wire, 2); + *rrset_wire += 2; + + knot_wire_write_u32(*rrset_wire, rrset->ttl); + dbg_response(" TTL: %u\n", rrset->ttl); + dbg_response(" TTL in wire: "); + dbg_response_hex((char *)*rrset_wire, 4); + *rrset_wire += 4; + + // save space for RDLENGTH + uint8_t *rdlength_pos = *rrset_wire; + *rrset_wire += 2; + + size += 10; + compr->wire_pos += size; + + dbg_response("Max size: %zu, size: %d\n", max_size, size); + + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(rrset->type); + + uint16_t rdlength = 0; + + for (int i = 0; i < rdata->count; ++i) { + if (max_size < size + rdlength) { + return KNOT_ESPACE; + } + + switch (desc->wireformat[i]) { + case KNOT_RDATA_WF_COMPRESSED_DNAME: { + int ret = knot_response_compress_dname( + knot_rdata_item(rdata, i)->dname, + compr, *rrset_wire, max_size - size - rdlength, + compr_cs); + + if (ret < 0) { + return KNOT_ESPACE; + } + + dbg_response("Compressed dname size: %d\n", + ret); + *rrset_wire += ret; + rdlength += ret; + compr->wire_pos += ret; + // TODO: compress domain name + break; + } + case KNOT_RDATA_WF_UNCOMPRESSED_DNAME: + case KNOT_RDATA_WF_LITERAL_DNAME: { + knot_dname_t *dname = + knot_rdata_item(rdata, i)->dname; + if (size + rdlength + dname->size > max_size) { + return KNOT_ESPACE; + } + + // save whole domain name + memcpy(*rrset_wire, dname->name, dname->size); + dbg_response("Uncompressed dname size: %d\n", + dname->size); + *rrset_wire += dname->size; + rdlength += dname->size; + compr->wire_pos += dname->size; + break; + } +// case KNOT_RDATA_WF_BINARYWITHLENGTH: { +// uint16_t *raw_data = +// knot_rdata_item(rdata, i)->raw_data; + +// if (size + raw_data[0] + 1 > max_size) { +// return KNOT_ESPACE; +// } + +// // copy also the rdata item size +// assert(raw_data[0] < 256); +// **rrset_wire = raw_data[0]; +// *rrset_wire += 1; +// memcpy(*rrset_wire, raw_data + 1, raw_data[0]); +// dbg_response("Raw data size: %d\n", +// raw_data[0] + 1); +// *rrset_wire += raw_data[0]; +// rdlength += raw_data[0] + 1; +// compr->wire_pos += raw_data[0] + 1; +// break; +// } + default: { + uint16_t *raw_data = + knot_rdata_item(rdata, i)->raw_data; + + if (size + rdlength + raw_data[0] > max_size) { + return KNOT_ESPACE; + } + + // copy just the rdata item data (without size) + memcpy(*rrset_wire, raw_data + 1, raw_data[0]); + dbg_response("Raw data size: %d\n", + raw_data[0]); + *rrset_wire += raw_data[0]; + rdlength += raw_data[0]; + compr->wire_pos += raw_data[0]; + break; + } + } + } + + dbg_response("Max size: %zu, size: %d\n", max_size, size); + + assert(size + rdlength <= max_size); + size += rdlength; + knot_wire_write_u16(rdlength_pos, rdlength); + + return size; +} + +/*---------------------------------------------------------------------------*/ +/*! + * \brief Convert whole RRSet into wire format. + * + * \param[in] rrset RRSet to convert + * \param[out] pos Place where to put the wire format. + * \param[out] size Size of the converted wire format. + * \param[in] max_size Maximum available space for the wire format. + * \param wire_pos Current position in the wire format of the whole packet. + * \param owner_tmp Wire format of the RRSet's owner, possibly compressed. + * \param compr Information about compressed domain names in the packet. + * \param compr_cs Set to <> 0 if dname compression should use case sensitive + * comparation. Set to 0 otherwise. + * + * \return Size of the RRSet's wire format or KNOT_ESPACE if it did not fit + * into the provided space. + */ +static int knot_response_rrset_to_wire(const knot_rrset_t *rrset, + uint8_t **pos, size_t *size, + size_t max_size, size_t wire_pos, + uint8_t *owner_tmp, + knot_compressed_dnames_t *compr, + int compr_cs) +{ +dbg_response_exec( + char *name = knot_dname_to_str(rrset->owner); + dbg_response("Converting RRSet with owner %s, type %s\n", + name, knot_rrtype_to_string(rrset->type)); + free(name); + dbg_response(" Size before: %zu\n", *size); +); + + // if no RDATA in RRSet, return + if (rrset->rdata == NULL) { + return KNOT_EOK; + } + + //uint8_t *rrset_wire = (uint8_t *)malloc(PREALLOC_RRSET_WIRE); + //short rrset_size = 0; + + //uint8_t *owner_wire = (uint8_t *)malloc(rrset->owner->size); + /* + * We may pass the current position to the compression function + * because if the owner will be put somewhere, it will be on the + * current position (first item of a RR). If it will not be put into + * the wireformat, we may remove the dname (and possibly its parents) + * from the compression table. + */ + + knot_compr_t compr_info; + //compr_info.new_entries = 0; + compr_info.table = compr; + compr_info.wire_pos = wire_pos; + compr_info.owner.pos = 0; + compr_info.owner.wire = owner_tmp; + compr_info.owner.size = + knot_response_compress_dname(rrset->owner, &compr_info, + owner_tmp, max_size, compr_cs); + + dbg_response(" Owner size: %d, position: %zu\n", + compr_info.owner.size, compr_info.owner.pos); + if (compr_info.owner.size < 0) { + return KNOT_ESPACE; + } + + int rrs = 0; + short rrset_size = 0; + + const knot_rdata_t *rdata = rrset->rdata; + do { + int ret = knot_response_rr_to_wire(rrset, rdata, &compr_info, + pos, max_size - rrset_size, + compr_cs); + + assert(ret != 0); + + if (ret < 0) { + // some RR didn't fit in, so no RRs should be used + // TODO: remove last entries from compression table + dbg_response("Some RR didn't fit in.\n"); + return KNOT_ESPACE; + } + + dbg_response("RR of size %d added.\n", ret); + rrset_size += ret; + ++rrs; + } while ((rdata = knot_rrset_rdata_next(rrset, rdata)) != NULL); + + //memcpy(*pos, rrset_wire, rrset_size); + //*size += rrset_size; + //*pos += rrset_size; + + // the whole RRSet did fit in + assert (rrset_size <= max_size); + *size += rrset_size; + + dbg_response(" Size after: %zu\n", *size); + + return rrs; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Tries to add RRSet to the response. + * + * This function tries to convert the RRSet to wire format and add it to the + * wire format of the response and if successful, adds the RRSet to the given + * list (and updates its size). If the RRSet did not fit into the available + * space (\a max_size), it is omitted as a whole and the TC bit may be set + * (according to \a tc). + * + * \param rrsets Lists of RRSets to which this RRSet should be added. + * \param rrset_count Number of RRSets in the list. + * \param resp Response structure where the RRSet should be added. + * \param max_size Maximum available space in wire format of the response. + * \param rrset RRSet to add. + * \param tc Set to <> 0 if omitting the RRSet should cause the TC bit to be + * set in the response. + * \param compr_cs Set to <> 0 if dname compression should use case sensitive + * comparation. Set to 0 otherwise. + * + * \return Count of RRs added to the response or KNOT_ESPACE if the RRSet did + * not fit in the available space. + */ +static int knot_response_try_add_rrset(const knot_rrset_t **rrsets, + short *rrset_count, + knot_packet_t *resp, + size_t max_size, + const knot_rrset_t *rrset, int tc, + int compr_cs) +{ + //short size = knot_response_rrset_size(rrset, &resp->compression); + +dbg_response_exec( + char *name = knot_dname_to_str(rrset->owner); + dbg_response("\nAdding RRSet with owner %s and type %s: \n", + name, knot_rrtype_to_string(rrset->type)); + free(name); +); + + uint8_t *pos = resp->wireformat + resp->size; + size_t size = 0; + int rrs = knot_response_rrset_to_wire(rrset, &pos, &size, max_size, + resp->size, resp->owner_tmp, + &resp->compression, compr_cs); + + if (rrs >= 0) { + rrsets[(*rrset_count)++] = rrset; + resp->size += size; + dbg_response("RRset added, size: %zu, RRs: %d, total " + "size of response: %zu\n\n", size, rrs, + resp->size); + } else if (tc) { + dbg_response("Setting TC bit.\n"); + knot_wire_flags_set_tc(&resp->header.flags1); + knot_wire_set_tc(resp->wireformat); + } + + return rrs; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Reallocate space for RRSets. + * + * \param rrsets Space for RRSets. + * \param max_count Size of the space available for the RRSets. + * \param default_max_count Size of the space pre-allocated for the RRSets when + * the response structure was initialized. + * \param step How much the space should be increased. + * + * \retval KNOT_EOK + * \retval KNOT_ENOMEM + */ +static int knot_response_realloc_rrsets(const knot_rrset_t ***rrsets, + short *max_count, + short default_max_count, short step) +{ + int free_old = (*max_count) != default_max_count; + const knot_rrset_t **old = *rrsets; + + short new_max_count = *max_count + step; + const knot_rrset_t **new_rrsets = (const knot_rrset_t **)malloc( + new_max_count * sizeof(knot_rrset_t *)); + CHECK_ALLOC_LOG(new_rrsets, KNOT_ENOMEM); + + memcpy(new_rrsets, *rrsets, (*max_count) * sizeof(knot_rrset_t *)); + + *rrsets = new_rrsets; + *max_count = new_max_count; + + if (free_old) { + free(old); + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +int knot_response_init(knot_packet_t *response) +{ + if (response == NULL) { + return KNOT_EBADARG; + } + + if (response->max_size < KNOT_WIRE_HEADER_SIZE) { + return KNOT_ESPACE; + } + + // set the qr bit to 1 + knot_wire_flags_set_qr(&response->header.flags1); + + uint8_t *pos = response->wireformat; + knot_packet_header_to_wire(&response->header, &pos, + &response->size); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_response_init_from_query(knot_packet_t *response, + knot_packet_t *query) +{ + + if (response == NULL || query == NULL) { + return KNOT_EBADARG; + } + + // copy the header from the query + memcpy(&response->header, &query->header, sizeof(knot_header_t)); +// memmove(&response->header, &query->header, sizeof(knot_header_t)); + + // copy the Question section (but do not copy the QNAME) + memcpy(&response->question, &query->question, + sizeof(knot_question_t)); +// memmove(&response->question, &query->question, +// sizeof(knot_question_t)); + + int err = 0; + // put the qname into the compression table + // TODO: get rid of the numeric constants + if ((err = knot_response_store_dname_pos(&response->compression, + response->question.qname, 0, 12, 12, 0)) != KNOT_EOK) { + return err; + } + + // copy the wireformat of Header and Question from the query + // TODO: get rid of the numeric constants + size_t to_copy = 12 + 4 + knot_dname_size(response->question.qname); + + assert(response->max_size >= to_copy); +// printf("Resp init from query: Copying from: %p to: %p size: %d\n", +// response->wireformat, query->wireformat, +// to_copy); +// printf("Resp init from query: Question name size: %d Query name size: %d\n", +// knot_dname_size(response->question.qname), +// knot_dname_size(query->question.qname)); + memcpy(response->wireformat, query->wireformat, to_copy); + response->size = to_copy; + + // set the qr bit to 1 + knot_wire_flags_set_qr(&response->header.flags1); + knot_wire_set_qr(response->wireformat); + + // clear AD flag + knot_wire_flags_clear_ad(&response->header.flags2); + knot_wire_clear_ad(response->wireformat); + + // clear RA flag + knot_wire_flags_clear_ra(&response->header.flags2); + knot_wire_clear_ad(response->wireformat); + + // set counts to 0 + response->header.ancount = 0; + response->header.nscount = 0; + response->header.arcount = 0; + + response->query = query; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +void knot_response_clear(knot_packet_t *resp, int clear_question) +{ + if (resp == NULL) { + return; + } + + resp->size = (clear_question) ? KNOT_WIRE_HEADER_SIZE + : KNOT_WIRE_HEADER_SIZE + 4 + + knot_dname_size(resp->question.qname); + resp->an_rrsets = 0; + resp->ns_rrsets = 0; + resp->ar_rrsets = 0; + resp->compression.count = 0; + knot_packet_free_tmp_rrsets(resp); + resp->tmp_rrsets_count = 0; + resp->header.ancount = 0; + resp->header.nscount = 0; + resp->header.arcount = 0; +} + +/*----------------------------------------------------------------------------*/ + +int knot_response_add_opt(knot_packet_t *resp, + const knot_opt_rr_t *opt_rr, + int override_max_size) +{ + if (resp == NULL || opt_rr == NULL) { + return KNOT_EBADARG; + } + + // copy the OPT RR + resp->opt_rr.version = opt_rr->version; + resp->opt_rr.ext_rcode = opt_rr->ext_rcode; + resp->opt_rr.payload = opt_rr->payload; + resp->opt_rr.size = opt_rr->size; + + // if max size is set, it means there is some reason to be that way, + // so we can't just set it to higher value + + if (override_max_size && resp->max_size > 0 + && resp->max_size < opt_rr->payload) { + return KNOT_EPAYLOAD; + } + + // set max size (less is OK) + if (override_max_size) { + dbg_response("Overriding max size to: %u\n", + resp->opt_rr.payload); + return knot_packet_set_max_size(resp, resp->opt_rr.payload); + //resp->max_size = resp->opt_rr.payload; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_response_add_rrset_answer(knot_packet_t *response, + const knot_rrset_t *rrset, int tc, + int check_duplicates, int compr_cs) +{ + if (response == NULL || rrset == NULL) { + return KNOT_EBADARG; + } + + dbg_response("add_rrset_answer()\n"); + assert(response->header.arcount == 0); + assert(response->header.nscount == 0); + + if (response->an_rrsets == response->max_an_rrsets + && knot_response_realloc_rrsets(&response->answer, + &response->max_an_rrsets, DEFAULT_ANCOUNT, STEP_ANCOUNT) + != KNOT_EOK) { + return KNOT_ENOMEM; + } + + if (check_duplicates && knot_packet_contains(response, rrset, + KNOT_RRSET_COMPARE_PTR)) { + return KNOT_EOK; + } + + dbg_response("Trying to add RRSet to Answer section.\n"); + dbg_response("RRset: %p\n", rrset); + dbg_response("Owner: %p\n", rrset->owner); + + int rrs = knot_response_try_add_rrset(response->answer, + &response->an_rrsets, response, + response->max_size + - response->size + - response->opt_rr.size + - response->tsig_size, + rrset, tc, compr_cs); + + if (rrs >= 0) { + response->header.ancount += rrs; + return KNOT_EOK; + } + + return KNOT_ESPACE; +} + +/*----------------------------------------------------------------------------*/ + +int knot_response_add_rrset_authority(knot_packet_t *response, + const knot_rrset_t *rrset, int tc, + int check_duplicates, int compr_cs) +{ + if (response == NULL || rrset == NULL) { + return KNOT_EBADARG; + } + + assert(response->header.arcount == 0); + + if (response->ns_rrsets == response->max_ns_rrsets + && knot_response_realloc_rrsets(&response->authority, + &response->max_ns_rrsets, DEFAULT_NSCOUNT, STEP_NSCOUNT) + != 0) { + return KNOT_ENOMEM; + } + + if (check_duplicates && knot_packet_contains(response, rrset, + KNOT_RRSET_COMPARE_PTR)) { + return KNOT_EOK; + } + + dbg_response("Trying to add RRSet to Authority section.\n"); + + int rrs = knot_response_try_add_rrset(response->authority, + &response->ns_rrsets, response, + response->max_size + - response->size + - response->opt_rr.size + - response->tsig_size, + rrset, tc, compr_cs); + + if (rrs >= 0) { + response->header.nscount += rrs; + return KNOT_EOK; + } + + return KNOT_ESPACE; +} + +/*----------------------------------------------------------------------------*/ + +int knot_response_add_rrset_additional(knot_packet_t *response, + const knot_rrset_t *rrset, int tc, + int check_duplicates, int compr_cs) +{ + if (response == NULL || rrset == NULL) { + return KNOT_EBADARG; + } + + int ret; + + // if this is the first additional RRSet, add EDNS OPT RR first + if (response->header.arcount == 0 + && response->opt_rr.version != EDNS_NOT_SUPPORTED + && (ret = knot_packet_edns_to_wire(response)) != KNOT_EOK) { + return ret; + } + + if (response->ar_rrsets == response->max_ar_rrsets + && knot_response_realloc_rrsets(&response->additional, + &response->max_ar_rrsets, DEFAULT_ARCOUNT, STEP_ARCOUNT) + != 0) { + return KNOT_ENOMEM; + } + + if (check_duplicates && knot_packet_contains(response, rrset, + KNOT_RRSET_COMPARE_PTR)) { + return KNOT_EOK; + } + + dbg_response("Trying to add RRSet to Additional section.\n"); + + int rrs = knot_response_try_add_rrset(response->additional, + &response->ar_rrsets, response, + response->max_size + - response->size + - response->tsig_size, rrset, + tc, compr_cs); + + if (rrs >= 0) { + response->header.arcount += rrs; + return KNOT_EOK; + } + + return KNOT_ESPACE; +} + +/*----------------------------------------------------------------------------*/ + +void knot_response_set_rcode(knot_packet_t *response, short rcode) +{ + if (response == NULL) { + return; + } + + knot_wire_flags_set_rcode(&response->header.flags2, rcode); + knot_wire_set_rcode(response->wireformat, rcode); +} + +/*----------------------------------------------------------------------------*/ + +void knot_response_set_aa(knot_packet_t *response) +{ + if (response == NULL) { + return; + } + + knot_wire_flags_set_aa(&response->header.flags1); + knot_wire_set_aa(response->wireformat); +} + +/*----------------------------------------------------------------------------*/ + +void knot_response_set_tc(knot_packet_t *response) +{ + if (response == NULL) { + return; + } + + knot_wire_flags_set_tc(&response->header.flags1); + knot_wire_set_tc(response->wireformat); +} + +/*----------------------------------------------------------------------------*/ + +int knot_response_add_nsid(knot_packet_t *response, const uint8_t *data, + uint16_t length) +{ + if (response == NULL) { + return KNOT_EBADARG; + } + + return knot_edns_add_option(&response->opt_rr, + EDNS_OPTION_NSID, length, data); +} diff --git a/src/libknot/packet/response.h b/src/libknot/packet/response.h new file mode 100644 index 0000000..38bd9a8 --- /dev/null +++ b/src/libknot/packet/response.h @@ -0,0 +1,198 @@ +/*! + * \file response.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief API for response manipulation. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_response_H_ +#define _KNOT_response_H_ + +#include <stdint.h> +#include <string.h> + +#include "packet/packet.h" + +#include "dname.h" +#include "rrset.h" +#include "edns.h" + +/*! + * \brief Default maximum DNS response size + * + * This size must be supported by all servers and clients. + */ +static const short KNOT_MAX_RESPONSE_SIZE = 512; + +/*----------------------------------------------------------------------------*/ +int knot_response_init(knot_packet_t *response); + +/*! + * \brief Initializes response from the given query. + * + * Copies the header, Changes QR bit to 1, copies the Question section and + * stores pointer to the query packet structure in the response packet + * structure. + * + * \warning Never free the query packet structure after calling this function, + * it will be freed when the response structure is freed. + * + * \param response Packet structure representing the response. + * \param query Packet structure representing the query. + * + * \retval KNOT_EOK + */ +int knot_response_init_from_query(knot_packet_t *response, + knot_packet_t *query); + +/*! + * \brief Clears the response structure for reuse. + * + * After call to this function, the response will be in the same state as if + * knot_response_new() was called. The maximum wire size is retained. + * + * \param response Response structure to clear. + * + * \todo Replace the use of this function with something else maybe? + */ +void knot_response_clear(knot_packet_t *resp, int clear_question); + +/*! + * \brief Sets the OPT RR of the response. + * + * This function also allocates space for the wireformat of the response, if + * the payload in the OPT RR is larger than the current maximum size of the + * response and copies the current wireformat over to the new space. + * + * \note The contents of the OPT RR are copied. + * + * \param resp Response to set the OPT RR to. + * \param opt_rr OPT RR to set. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + * \retval KNOT_ENOMEM + * + * \todo Needs test. + */ +int knot_response_add_opt(knot_packet_t *resp, + const knot_opt_rr_t *opt_rr, + int override_max_size); + +/*! + * \brief Adds a RRSet to the Answer section of the response. + * + * \param response Response to add the RRSet into. + * \param rrset RRSet to be added. + * \param tc Set to <> 0 if omitting this RRSet should result in the TC bit set. + * Otherwise set to 0. + * \param check_duplicates Set to <> 0 if the RRSet should not be added to the + * response in case it is already there. + * \param compr_cs Set to <> 0 if dname compression should use case sensitive + * comparation. Set to 0 otherwise. + * + * \retval KNOT_EOK if successful, or the RRSet was already in the answer. + * \retval KNOT_ENOMEM + * \retval KNOT_ESPACE + */ +int knot_response_add_rrset_answer(knot_packet_t *response, + const knot_rrset_t *rrset, int tc, + int check_duplicates, int compr_cs); + +/*! + * \brief Adds a RRSet to the Authority section of the response. + * + * \param response Response to add the RRSet into. + * \param rrset RRSet to be added. + * \param tc Set to <> 0 if omitting this RRSet should result in the TC bit set. + * Otherwise set to 0. + * \param check_duplicates Set to <> 0 if the RRSet should not be added to the + * response in case it is already there. + * \param compr_cs Set to <> 0 if dname compression should use case sensitive + * comparation. Set to 0 otherwise. + * + * \retval KNOT_EOK if successful, or the RRSet was already in the answer. + * \retval KNOT_ENOMEM + * \retval KNOT_ESPACE + */ +int knot_response_add_rrset_authority(knot_packet_t *response, + const knot_rrset_t *rrset, int tc, + int check_duplicates, int compr_cs); + +/*! + * \brief Adds a RRSet to the Additional section of the response. + * + * \param response Response to add the RRSet into. + * \param rrset RRSet to be added. + * \param tc Set to <> 0 if omitting this RRSet should result in the TC bit set. + * Otherwise set to 0. + * \param check_duplicates Set to <> 0 if the RRSet should not be added to the + * response in case it is already there. + * \param compr_cs Set to <> 0 if dname compression should use case sensitive + * comparation. Set to 0 otherwise. + * + * \retval KNOT_EOK if successful, or the RRSet was already in the answer. + * \retval KNOT_ENOMEM + * \retval KNOT_ESPACE + */ +int knot_response_add_rrset_additional(knot_packet_t *response, + const knot_rrset_t *rrset, int tc, + int check_duplicates, int compr_cs); + +/*! + * \brief Sets the RCODE of the response. + * + * \param response Response to set the RCODE in. + * \param rcode RCODE to set. + */ +void knot_response_set_rcode(knot_packet_t *response, short rcode); + +/*! + * \brief Sets the AA bit of the response to 1. + * + * \param response Response in which the AA bit should be set. + */ +void knot_response_set_aa(knot_packet_t *response); + +/*! + * \brief Sets the TC bit of the response to 1. + * + * \param response Response in which the TC bit should be set. + */ +void knot_response_set_tc(knot_packet_t *response); + +/*! + * \brief Adds NSID option to the response. + * + * \param response Response to add the NSID option into. + * \param data NSID data. + * \param length Size of NSID data in bytes. + * + * \retval KNOT_EOK + * \retval KNOT_ENOMEM + */ +int knot_response_add_nsid(knot_packet_t *response, const uint8_t *data, + uint16_t length); + +#endif /* _KNOT_response_H_ */ + +/*! @} */ diff --git a/src/libknot/rdata.c b/src/libknot/rdata.c new file mode 100644 index 0000000..0c51f5b --- /dev/null +++ b/src/libknot/rdata.c @@ -0,0 +1,838 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdint.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <stdio.h> + +#include "common.h" +#include "rdata.h" +#include "util/descriptor.h" +#include "dname.h" +#include "util/error.h" +#include "zone/node.h" +#include "util/utils.h" +#include "util/debug.h" + +/*----------------------------------------------------------------------------*/ +/* Non-API functions */ +/*----------------------------------------------------------------------------*/ +/*! + * \brief Compares two RDATA items as binary data. + * + * \param d1 First item. + * \param d2 Second item. + * \param count1 Size of the first item in bytes. If set to < 0, the size will + * be taken from the first two bytes of \a d1. + * \param count2 Size of the second item in bytes. If set to < 0, the size will + * be taken from the first two bytes of \a d2. + * + * \retval 0 if the items are identical. + * \retval < 0 if \a d1 goes before \a d2 in canonical order. + * \retval > 0 if \a d1 goes after \a d2 in canonical order. + */ +static int knot_rdata_compare_binary(const uint8_t *d1, const uint8_t *d2, + int count1, int count2) +{ + int i1 = 0, i2 = 0; + + // length stored in the first octet + if (count1 < 0) { + // take count from the first two bytes + count1 = (int)(*(uint16_t *)d1); + // and start from the third byte + i1 = 2; + } + if (count2 < 0) { // dtto + count2 = (int)(*(uint16_t *)d2); + i2 = 2; + } + + + while (i1 < count1 && i2 < count2 && d1[i1] == d2[i2]) { + ++i1; + ++i2; + } + + if (i1 == count1 && i2 == count2) { + return 0; + } + + if (i1 == count1 && i2 < count2) { + return -1; + } else if (i2 == count2 && i1 < count1) { + return 1; + } else { + assert(i1 < count1 && i2 < count2); + return (d1[i1] < d2[i2]) ? -1 : 1; + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Retrieves the domain name from MX RDATA. + * + * \note This is only convenience function. It does not (and cannot) check if + * the given RDATA is of the right type, so it always returns the second + * RDATA item, even if it is not a domain name. + * + * \param rdata RDATA to get the MX domain name from. + * + * \return MX domain name stored in \a rdata or NULL if \a rdata has less than 2 + * items. + */ +static const knot_dname_t *knot_rdata_mx_name(const knot_rdata_t *rdata) +{ + if (rdata->count < 2) { + return NULL; + } + return rdata->items[1].dname; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Retrieves the domain name from NS RDATA. + * + * \note This is only convenience function. It does not (and cannot) check if + * the given RDATA is of the right type, so it always returns the first + * RDATA item, even if it is not a domain name. + * + * \param rdata RDATA to get the NS domain name from. + * + * \return NS domain name stored in \a rdata or NULL if \a rdata has no items. + */ +static const knot_dname_t *knot_rdata_ns_name(const knot_rdata_t *rdata) +{ + if (rdata->count < 1) { + return NULL; + } + return rdata->items[0].dname; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Retrieves the domain name from SRV RDATA. + * + * \note This is only convenience function. It does not (and cannot) check if + * the given RDATA is of the right type, so it always returns the fourth + * RDATA item, even if it is not a domain name. + * + * \param rdata RDATA to get the SRV domain name from. + * + * \return SRV domain name stored in \a rdata or NULL if \a rdata has less than + * 4 items. + */ +static const knot_dname_t *knot_rdata_srv_name(const knot_rdata_t *rdata) +{ + if (rdata->count < 4) { + return NULL; + } + return rdata->items[3].dname; +} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +knot_rdata_t *knot_rdata_new() +{ + knot_rdata_t *rdata = + (knot_rdata_t *)malloc(sizeof(knot_rdata_t)); + if (rdata == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + + rdata->items = NULL; + rdata->count = 0; + rdata->next = NULL; + + return rdata; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rdata_from_wire(knot_rdata_t *rdata, const uint8_t *wire, + size_t *pos, size_t total_size, size_t rdlength, + const knot_rrtype_descriptor_t *desc) +{ + int i = 0; + uint8_t item_type; + size_t parsed = 0; + + if (rdlength == 0) { + rdata->items = NULL; + return KNOT_EOK; + } + + knot_rdata_item_t *items = (knot_rdata_item_t *)malloc( + desc->length * sizeof(knot_rdata_item_t)); + CHECK_ALLOC_LOG(items, KNOT_ENOMEM); + + size_t item_size = 0; + uint8_t gateway_type = 0; // only to handle IPSECKEY record + knot_dname_t *dname = NULL; + + while (i < desc->length && (desc->fixed_items || parsed < rdlength)) { + + item_type = desc->wireformat[i]; + item_size = 0; + + size_t pos2; + + switch (item_type) { + case KNOT_RDATA_WF_COMPRESSED_DNAME: + case KNOT_RDATA_WF_UNCOMPRESSED_DNAME: + case KNOT_RDATA_WF_LITERAL_DNAME: + pos2 = *pos; + dname = knot_dname_parse_from_wire( + wire, &pos2, total_size, NULL); + if (dname == NULL) { + free(items); + return KNOT_ERROR; + } + items[i].dname = dname; + //*pos += dname->size; + parsed += pos2 - *pos; + *pos = pos2; + dname = 0; + break; + case KNOT_RDATA_WF_BYTE: + if (desc->type == KNOT_RRTYPE_IPSECKEY && i == 1) { + gateway_type = *(wire + *pos); + } + item_size = 1; + break; + case KNOT_RDATA_WF_SHORT: + item_size = 2; + break; + case KNOT_RDATA_WF_LONG: + item_size = 4; + break; + case KNOT_RDATA_WF_UINT48: + item_size = 6; + break; + case KNOT_RDATA_WF_TEXT: + item_size = rdlength - parsed; + break; + case KNOT_RDATA_WF_TEXT_SINGLE: + item_size = *(wire + *pos) + 1; + break; + case KNOT_RDATA_WF_A: + item_size = 4; + break; + case KNOT_RDATA_WF_AAAA: + item_size = 16; + break; + case KNOT_RDATA_WF_BINARY: + item_size = rdlength - parsed; + break; + case KNOT_RDATA_WF_BINARYWITHLENGTH: + item_size = *(wire + *pos) + 1; + break; + case KNOT_RDATA_WF_BINARYWITHSHORT: + item_size = knot_wire_read_u16(wire + *pos) + 2; + break; + case KNOT_RDATA_WF_APL: + // WTF? what to do with this?? + // Same as TXT, I guess. + item_size = rdlength - parsed; + break; + case KNOT_RDATA_WF_IPSECGATEWAY: + // determine size based on the 'gateway type' field + switch (gateway_type) { + case 0: + item_size = 0; + break; + case 1: + item_size = 4; + break; + case 2: + item_size = 16; + break; + case 3: + pos2 = *pos; + fprintf(stderr, "reading dname from pos: %zu\n", pos2); + dname = + knot_dname_parse_from_wire( + wire, &pos2, total_size, NULL); + if (dname == NULL) { + return KNOT_ERROR; + } + items[i].dname = dname; + //*pos += dname->size; + parsed += pos2 - *pos; + + fprintf(stderr, "read %zu bytes.\n", parsed); + *pos = pos2; + dname = 0; + break; + default: + assert(0); + } + + break; + default: + return KNOT_EMALF; + + } + + if (item_size != 0) { + if (parsed + item_size > rdlength) { + free(items); + return KNOT_EFEWDATA; + } + + items[i].raw_data = (uint16_t *)malloc(item_size + 2); + if (items[i].raw_data == NULL) { + free(items); + return KNOT_ENOMEM; + } + memcpy(items[i].raw_data, &item_size, 2); + memcpy(items[i].raw_data + 1, wire + *pos, item_size); + *pos += item_size; + parsed += item_size; + } else if (item_type == KNOT_RDATA_WF_BINARY + || item_type == KNOT_RDATA_WF_IPSECGATEWAY) { + fprintf(stderr, "item_size was 0, creating empty rdata item.\n"); + // in this case we are at the end of the RDATA + // and should create an empty RDATA item + items[i].raw_data = (uint16_t *)malloc(2); + if (items[i].raw_data == NULL) { + free(items); + return KNOT_ENOMEM; + } + memcpy(items[i].raw_data, &item_size, 2); + } else if (item_type != KNOT_RDATA_WF_COMPRESSED_DNAME + && item_type != KNOT_RDATA_WF_UNCOMPRESSED_DNAME + && item_type != KNOT_RDATA_WF_LITERAL_DNAME) { + fprintf(stderr, "RDATA item not set (i: %d), type: %u" + " RDATA item type: %d\n", i, desc->type ,item_type); + assert(0); + } + + ++i; + } + + assert(!desc->fixed_items || i == desc->length); + + // all items are parsed, insert into the RDATA + int rc; + rc = knot_rdata_set_items(rdata, items, i); + + for (int j = 0; j < i; ++j) { + assert(rdata->items[j].raw_data != NULL); + } + + free(items); + return rc; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rdata_set_item(knot_rdata_t *rdata, uint pos, + knot_rdata_item_t item) +{ + if (pos >= rdata->count) { + return KNOT_EBADARG; + } + + /*! \todo As in set_items() we should increment refcounter for dnames, + * but we don't know the item type. + */ + + rdata->items[pos] = item; // this should copy the union; or use memcpy? + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +unsigned int knot_rdata_item_count(const knot_rdata_t *rdata) +{ + return rdata->count; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rdata_set_items(knot_rdata_t *rdata, + const knot_rdata_item_t *items, uint count) +{ + if (rdata == NULL || items == NULL || count == 0 || + rdata->items != NULL) { + return KNOT_EBADARG; + } + + assert(rdata->count == 0); + if ((rdata->items = (knot_rdata_item_t *)malloc( + count * sizeof(knot_rdata_item_t))) == NULL) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + memcpy(rdata->items, items, count * sizeof(knot_rdata_item_t)); + rdata->count = count; + + /*! \todo Cannot determine items type, so the dname + * refcounters should be increased in caller. + */ + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +const knot_rdata_item_t *knot_rdata_item(const knot_rdata_t *rdata, + uint pos) +{ + if (pos >= rdata->count) { + return NULL; + } else { + return &rdata->items[pos]; + } +} + +/*----------------------------------------------------------------------------*/ + +knot_rdata_item_t *knot_rdata_get_item(const knot_rdata_t *rdata, + uint pos) +{ + if (pos >= rdata->count) { + return NULL; + } else { + return &rdata->items[pos]; + } +} + +/*----------------------------------------------------------------------------*/ + +int knot_rdata_item_set_dname(knot_rdata_t *rdata, uint pos, + knot_dname_t *dname) +{ + if (pos >= rdata->count) { + return KNOT_EBADARG; + } + + /* Retain dname. */ + knot_dname_retain(dname); + + rdata->items[pos].dname = dname; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rdata_item_set_raw_data(knot_rdata_t *rdata, uint pos, + uint16_t *raw_data) +{ + if (pos >= rdata->count) { + return KNOT_EBADARG; + } + + rdata->items[pos].raw_data = raw_data; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +void knot_rdata_free(knot_rdata_t **rdata) +{ + if (rdata == NULL || *rdata == NULL) { + return; + } + + if ((*rdata)->items) { + free((*rdata)->items); + } + free(*rdata); + *rdata = NULL; +} + +/*----------------------------------------------------------------------------*/ + +void knot_rdata_deep_free(knot_rdata_t **rdata, uint type, + int free_all_dnames) +{ + if (rdata == NULL || *rdata == NULL) { + return; + } + + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(type); + assert(desc != NULL); + + assert((*rdata)->count <= desc->length); + + for (int i = 0; i < (*rdata)->count; i++) { + if (&((*rdata)->items[i]) == NULL) { + continue; + } + if (desc->wireformat[i] == KNOT_RDATA_WF_COMPRESSED_DNAME + || desc->wireformat[i] == KNOT_RDATA_WF_UNCOMPRESSED_DNAME + || desc->wireformat[i] == KNOT_RDATA_WF_LITERAL_DNAME ) { + if (((*rdata)->items[i].dname != NULL)) { + /*! \todo This is hack to prevent memory errors, + * as the rdata_set_items() cannot determine + * items type and so cannot increment + * reference count in case of dname type. + * Free would then release dnames that + * aren't referenced by the rdata. + */ + if (free_all_dnames) { + knot_dname_release((*rdata)->items[i].dname); + } + } + } else { + free((*rdata)->items[i].raw_data); + } + } + + if ((*rdata)->items) { + free((*rdata)->items); + } + free(*rdata); + *rdata = NULL; +} + +/*----------------------------------------------------------------------------*/ +/* CLEANUP */ +//uint knot_rdata_wire_size(const knot_rdata_t *rdata, +// const uint8_t *format) +//{ +// uint size = 0; + +// for (int i = 0; i < rdata->count; ++i) { +// switch (format[i]) { +// case KNOT_RDATA_WF_COMPRESSED_DNAME: +// case KNOT_RDATA_WF_UNCOMPRESSED_DNAME: +// case KNOT_RDATA_WF_LITERAL_DNAME: +// size += knot_dname_size(rdata->items[i].dname); +// break; +// case KNOT_RDATA_WF_BYTE: +// size += 1; +// break; +// case KNOT_RDATA_WF_SHORT: +// size += 2; +// break; +// case KNOT_RDATA_WF_LONG: +// size += 4; +// break; +// case KNOT_RDATA_WF_A: +// size += 4; +// break; +// case KNOT_RDATA_WF_AAAA: +// size += 16; +// break; +// case KNOT_RDATA_WF_BINARY: +// case KNOT_RDATA_WF_APL: // saved as binary +// case KNOT_RDATA_WF_IPSECGATEWAY: // saved as binary +// size += rdata->items[i].raw_data[0]; +// break; +// case KNOT_RDATA_WF_TEXT: +// case KNOT_RDATA_WF_BINARYWITHLENGTH: +// size += rdata->items[i].raw_data[0] + 1; +// break; +// default: +// assert(0); +// } +// } +// return size; +//} + +/*----------------------------------------------------------------------------*/ + +//int knot_rdata_to_wire(const knot_rdata_t *rdata, const uint8_t *format, +// uint8_t *buffer, uint buf_size) +//{ +// uint copied = 0; +// uint8_t tmp[KNOT_MAX_RDATA_WIRE_SIZE]; +// uint8_t *to = tmp; + +// for (int i = 0; i < rdata->count; ++i) { +// assert(copied < KNOT_MAX_RDATA_WIRE_SIZE); + +// const uint8_t *from = (uint8_t *)rdata->items[i].raw_data; +// uint size = 0; + +// switch (format[i]) { +// case KNOT_RDATA_WF_COMPRESSED_DNAME: +// case KNOT_RDATA_WF_UNCOMPRESSED_DNAME: +// case KNOT_RDATA_WF_LITERAL_DNAME: +// size = knot_dname_size(rdata->items[i].dname); +// from = knot_dname_name(rdata->items[i].dname); + +// break; +// case KNOT_RDATA_WF_BYTE: +// size = 1; +// break; +// case KNOT_RDATA_WF_SHORT: +// size = 2; +// break; +// case KNOT_RDATA_WF_LONG: +// size = 4; +// break; +// case KNOT_RDATA_WF_A: +// size = 4; +// break; +// case KNOT_RDATA_WF_AAAA: +// size = 16; +// break; +// case KNOT_RDATA_WF_TEXT: +// case KNOT_RDATA_WF_BINARYWITHLENGTH: +// // size stored in the first two bytes, but in little +// // endian and we need only the lower byte from it +// *to = *from; // lower byte is the first in little endian +// to += 1; +// case KNOT_RDATA_WF_BINARY: +// case KNOT_RDATA_WF_APL: // saved as binary +// case KNOT_RDATA_WF_IPSECGATEWAY: // saved as binary +// // size stored in the first two bytes, those bytes +// // must not be copied +// size = rdata->items[i].raw_data[0]; +// from += 2; // skip the first two bytes +// break; +// default: +// assert(0); +// } + +// assert(size != 0); +// assert(copied + size < KNOT_MAX_RDATA_WIRE_SIZE); + +// memcpy(to, from, size); +// to += size; +// copied += size; +// } + +// if (copied > buf_size) { +// dbg_rdata("Not enough place allocated for function " +// "knot_rdata_to_wire(). Allocated %u, need %u\n", +// buf_size, copied); +// return -1; +// } + +// memcpy(buffer, tmp, copied); +// return 0; +//} + +/*----------------------------------------------------------------------------*/ + +knot_rdata_t *knot_rdata_deep_copy(const knot_rdata_t *rdata, + uint16_t type) +{ + knot_rdata_t *copy = knot_rdata_new(); + CHECK_ALLOC_LOG(copy, NULL); + + + if ((copy->items = (knot_rdata_item_t *)malloc( + rdata->count * sizeof(knot_rdata_item_t))) == NULL) { + knot_rdata_free(©); + ERR_ALLOC_FAILED; + return NULL; + } + + copy->count = rdata->count; + + knot_rrtype_descriptor_t *d = knot_rrtype_descriptor_by_type(type); + + assert(copy->count <= d->length); + + // copy all items one by one + for (int i = 0; i < copy->count; ++i) { + if (d->wireformat[i] == KNOT_RDATA_WF_COMPRESSED_DNAME + || d->wireformat[i] == KNOT_RDATA_WF_UNCOMPRESSED_DNAME + || d->wireformat[i] == KNOT_RDATA_WF_LITERAL_DNAME) { + copy->items[i].dname = + knot_dname_deep_copy(rdata->items[i].dname); + } else { + copy->items[i].raw_data = (uint16_t *)malloc( + rdata->items[i].raw_data[0] + 2); + if (copy->items[i].raw_data == NULL) { + knot_rdata_deep_free(©, type, 1); + return NULL; + } + memcpy(copy->items[i].raw_data, + rdata->items[i].raw_data, + rdata->items[i].raw_data[0] + 2); + } + } + + return copy; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rdata_compare(const knot_rdata_t *r1, const knot_rdata_t *r2, + const uint8_t *format) +{ + uint count = (r1->count < r2->count) ? r1->count : r2->count; + + int cmp = 0; + + for (int i = 0; i < count; ++i) { + /* CLEANUP */ +// const uint8_t *data1, *data2; +// int size1, size2; + + if (format[i] == KNOT_RDATA_WF_COMPRESSED_DNAME || + format[i] == KNOT_RDATA_WF_UNCOMPRESSED_DNAME || + format[i] == KNOT_RDATA_WF_LITERAL_DNAME) { + cmp = knot_dname_compare(r1->items[i].dname, + r2->items[i].dname); +// data1 = knot_dname_name(r1->items[i].dname); +// data2 = knot_dname_name(r2->items[i].dname); +// size1 = knot_dname_size(r2->items[i].dname); +// size2 = knot_dname_size(r2->items[i].dname); + } else { + cmp = knot_rdata_compare_binary( + (uint8_t *)(r1->items[i].raw_data + 1), + (uint8_t *)(r2->items[i].raw_data + 1), + r1->items[i].raw_data[0], + r1->items[i].raw_data[0]); +// data1 = (uint8_t *)(r1->items[i].raw_data + 1); +// data2 = (uint8_t *)(r2->items[i].raw_data + 1); +// size1 = r1->items[i].raw_data[0]; +// size2 = r1->items[i].raw_data[0]; + } + +// cmp = + + if (cmp != 0) { + return cmp; + } + } + + assert(cmp == 0); + return 0; +} + +/*----------------------------------------------------------------------------*/ + +const knot_dname_t *knot_rdata_cname_name(const knot_rdata_t *rdata) +{ + if (rdata->count < 1) { + return NULL; + } + return rdata->items[0].dname; +} + +/*----------------------------------------------------------------------------*/ + +const knot_dname_t *knot_rdata_dname_target(const knot_rdata_t *rdata) +{ + if (rdata->count < 1) { + return NULL; + } + return rdata->items[0].dname; +} + +/*---------------------------------------------------------------------------*/ + +const knot_dname_t *knot_rdata_get_name(const knot_rdata_t *rdata, + uint16_t type) +{ + // iterate over the rdata items or act as if we knew where the name is? + + switch (type) { + case KNOT_RRTYPE_NS: + return knot_rdata_ns_name(rdata); + case KNOT_RRTYPE_MX: + return knot_rdata_mx_name(rdata); + case KNOT_RRTYPE_SRV: + return knot_rdata_srv_name(rdata); + case KNOT_RRTYPE_CNAME: + return knot_rdata_cname_name(rdata); + } + + return NULL; +} + +/*---------------------------------------------------------------------------*/ +int64_t knot_rdata_soa_serial(const knot_rdata_t *rdata) +{ + if (!rdata) { + return -1; + } + + if (rdata->count < 3) { + return -1; + } + + // the number is in network byte order, transform it + return knot_wire_read_u32((uint8_t *)(rdata->items[2].raw_data + 1)); +} + +/*---------------------------------------------------------------------------*/ + +uint32_t knot_rdata_soa_refresh(const knot_rdata_t *rdata) +{ + if (!rdata) { + return 0; + } + + if (rdata->count < 4) { + return 0; /*! \todo Some other error value. */ + } + + // the number is in network byte order, transform it + return knot_wire_read_u32((uint8_t *)(rdata->items[3].raw_data + 1)); +} + +/*---------------------------------------------------------------------------*/ + +uint32_t knot_rdata_soa_retry(const knot_rdata_t *rdata) +{ + if (!rdata) { + return 0; + } + + if (rdata->count < 5) { + return 0; /*! \todo Some other error value. */ + } + + // the number is in network byte order, transform it + return knot_wire_read_u32((uint8_t *)(rdata->items[4].raw_data + 1)); +} + +/*---------------------------------------------------------------------------*/ + +uint32_t knot_rdata_soa_expire(const knot_rdata_t *rdata) +{ + if (!rdata) { + return -1; + } + + if (rdata->count < 6) { + return 0; /*! \todo Some other error value. */ + } + + // the number is in network byte order, transform it + return knot_wire_read_u32((uint8_t *)(rdata->items[5].raw_data + 1)); +} + +/*---------------------------------------------------------------------------*/ + +uint16_t knot_rdata_rrsig_type_covered(const knot_rdata_t *rdata) +{ + if (rdata->count < 1) { + return 0; + } + + return knot_wire_read_u16((uint8_t *)(rdata->items[0].raw_data + 1)); +} diff --git a/src/libknot/rdata.h b/src/libknot/rdata.h new file mode 100644 index 0000000..5d328c9 --- /dev/null +++ b/src/libknot/rdata.h @@ -0,0 +1,339 @@ +/*! + * \file rdata.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Structures representing RDATA and its items and API for manipulating + * both. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_RDATA_H_ +#define _KNOT_RDATA_H_ + +#include <stdint.h> +#include <string.h> + +#include "dname.h" +#include "util/descriptor.h" + +/*----------------------------------------------------------------------------*/ +/*! + * \brief RDATA item structure. + * + * Each RDATA may be logically divided into items, each of possible different + * type. This structure distinguishes between general data (\a raw_data) + * represented as an array of octets, and domain name (\a dname) as domain names + * require special treatment within some RDATA (e.g. compressing in packets). + */ +union knot_rdata_item { + knot_dname_t *dname; /*!< RDATA item represented as a domain name. */ + + /*! + * \brief RDATA item represented as raw array of octets. + * + * The first two bytes hold the length of the item in bytes. The length + * is stored in little endian. + * + * In some cases the stored length is also used in the wire format of + * RDATA (e.g. character-data as defined in RFC1035). In such case, + * the length should be less than 256, so that it fits into one byte + * in the wireformat. + * + * \todo Store the length in system byte order. + */ + uint16_t *raw_data; +}; + +typedef union knot_rdata_item knot_rdata_item_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief RDATA structure. + * + * Each RDATA may be logically divided into items, each of possible different + * type (see knot_rdata_item). This structure stores an array of such items. + * It is not dynamic, so any RDATA structure may hold either 0 or one specified + * number of items which cannot be changed later. However, the number of items + * may be different for each RDATA structure. The number of items should be + * given by descriptors for each RR type. Some types may have variable number + * of items. In such cases, the last item in the array will be set tu NULL + * to distinguish the actual count of items. + * + * This structure does not hold information about the RDATA items, such as + * what type is which item or how long are they. This information should be + * stored elsewhere (in descriptors) as it is RR-specific and given for each + * RR type. + * + * \todo Find out whether NULL is appropriate value. If it is a possible + * value for some of the items, we must find some other way to deal with + * this. + * \todo Add some function for freeing particular item? Or a non-const getter? + */ +struct knot_rdata { + knot_rdata_item_t *items; /*!< RDATA items comprising this RDATA. */ + unsigned int count; /*! < Count of RDATA items in this RDATA. */ + struct knot_rdata *next; /*!< Next RDATA item in a linked list. */ +}; + +typedef struct knot_rdata knot_rdata_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates an empty RDATA structure. + * + * \return Pointer to the new RDATA structure or NULL if an error occured. + */ +knot_rdata_t *knot_rdata_new(); + +/*! + * \brief Parses RDATA from the given data in wire format. + * + * \param rdata RDATA to fill. + * \param wire Wire format of the whole data in which the RDATA are present. + * \param pos Position in \a wire where to start parsing. + * \param total_size Size of the whole data. + * \param rdlength Size of the RDATA to parse in bytes. + * \param desc RR type descriptor for the RDATA type. + * + * \retval KNOT_ENOMEM + * \retval KNOT_EFEWDATA + * \retval KNOT_EMALF + * \retval KNOT_ERROR + * \retval KNOT_EOK + */ +int knot_rdata_from_wire(knot_rdata_t *rdata, const uint8_t *wire, + size_t *pos, size_t total_size, size_t rdlength, + const knot_rrtype_descriptor_t *desc); + +/*! + * \brief Sets the RDATA item on position \a pos. + * + * \param rdata RDATA structure in which the item should be set. + * \param pos Position of the RDATA item to be set. + * \param item RDATA item value to be set. + * + * \retval KNOT_EOK if successful. + * \retval KNOT_EBADARG if \a pos is not a valid position. + * + * \todo Use the union or a pointer to it as parameter? IMHO there is always + * only one pointer that is copied, so it doesn't matter. + */ +int knot_rdata_set_item(knot_rdata_t *rdata, unsigned int pos, + knot_rdata_item_t item); + +/*! + * \brief Sets all RDATA items within the given RDATA structure. + * + * \a rdata must be empty so far (\a rdata->count == 0). The necessary space + * is allocated. + * + * This function copies the array of RDATA items from \a items to \a rdata. + * + * \param rdata RDATA structure to store the items in. + * \param items An array of RDATA items to be stored in this RDATA structure. + * \param count Count of RDATA items to be stored. + * + * \retval 0 if successful. + * \retval KNOT_EBADARG + * \retval KNOT_ENOMEM + */ +int knot_rdata_set_items(knot_rdata_t *rdata, + const knot_rdata_item_t *items, + unsigned int count); + +unsigned int knot_rdata_item_count(const knot_rdata_t *rdata); + +/*! + * \brief Returns the RDATA item on position \a pos. + * + * \note Although returning union would be OK (no overhead), we need to be able + * to distinguish errors (in this case by returning NULL). + * + * \param rdata RDATA structure to get the item from. + * \param pos Position of the item to retrieve. + * + * \return The RDATA item on position \a pos, or NULL if such position does not + * exist within the given RDATA structure. + */ +knot_rdata_item_t *knot_rdata_get_item(const knot_rdata_t *rdata, + unsigned int pos); + +/*! + * \brief Returns the RDATA item on position \a pos. + * + * \note Although returning union would be OK (no overhead), we need to be able + * to distinguish errors (in this case by returning NULL). + * \note This function is identical to knot_rdata_get_item(), only it returns + * constant data. + * + * \param rdata RDATA structure to get the item from. + * \param pos Position of the item to retrieve. + * + * \return The RDATA item on position \a pos, or NULL if such position does not + * exist within the given RDATA structure. + */ +const knot_rdata_item_t *knot_rdata_item(const knot_rdata_t *rdata, + unsigned int pos); + +/*! + * \brief Sets the given domain name as a value of RDATA item on position + * \a pos. + * + * \param rdata RDATA structure to set the item in. + * \param pos Position of the RDATA item to set. + * \param dname Domain name to set to the item. + * + * \retval KNOT_EOK if successful. + * \retval KNOT_EBADARG + */ +int knot_rdata_item_set_dname(knot_rdata_t *rdata, unsigned int pos, + knot_dname_t *dname); + +/*! + * \brief Sets the given raw data as a value of RDATA item on position \a pos. + * + * \param rdata RDATA structure to set the item in. + * \param pos Position of the RDATA item to set. + * \param raw_data Raw data to set to the item. + * + * \retval KNOT_EOK if successful. + * \retval KNOT_EBADARG + */ +int knot_rdata_item_set_raw_data(knot_rdata_t *rdata, unsigned int pos, + uint16_t *raw_data); + +/*! + * \brief Copies the given RDATA. + * + * \param rdata RDATA to copy. + * \param type RR type of the RDATA. + * + * \return Copy of \a rdata. + */ +knot_rdata_t *knot_rdata_deep_copy(const knot_rdata_t *rdata, + uint16_t type); + +/*! + * \brief Destroys the RDATA structure without deleting RDATA items. + * + * Also sets the given pointer to NULL. + * + * \param rdata RDATA structure to be destroyed. + */ +void knot_rdata_free(knot_rdata_t **rdata); + +/*! + * \brief Destroys the RDATA structure and all its RDATA items. + * + * RDATA items are deleted according to the given RR Type. In case of domain + * name, it is deallocated only if either the free_all_dnames parameter is set + * to <> 0 or the name does not contain reference to a node (i.e. it is not an + * owner of some node) or if it does contain a reference to a node, but is + * not equal to its owner. (If free_all_dnames is set to <> 0, no other + * condition is evaluated.) + * + * Also sets the given pointer to NULL. + * + * \param rdata RDATA structure to be destroyed. + * \param type RR Type of the RDATA. + * \param free_all_dnames Set to <> 0 if you want to delete ALL domain names + * from the RDATA. Set to 0 otherwise. + */ +void knot_rdata_deep_free(knot_rdata_t **rdata, unsigned int type, + int free_all_dnames); + +/*! + * \brief Compares two RDATAs of the same type. + * + * \note Compares domain names normally (dname_compare()), i.e. + * case-insensitive. + * + * \param r1 First RDATA. + * \param r2 Second RDATA. + * \param format Descriptor of the RDATA format. + * + * \retval 0 if RDATAs are equal. + * \retval < 0 if \a r1 goes before \a r2 in canonical order. + * \retval > 0 if \a r1 goes after \a r2 in canonical order. + */ +int knot_rdata_compare(const knot_rdata_t *r1, const knot_rdata_t *r2, + const uint8_t *format); + +/*! + * \brief Retrieves the domain name from CNAME RDATA. + * + * \note This is only convenience function. It does not (and cannot) check if + * the given RDATA is of the right type, so it always returns the first + * RDATA item, even if it is not a domain name. + * + * \param rdata RDATA to get the CNAME domain name from. + * + * \return Canonical name stored in \a rdata or NULL if \a rdata has no items. + */ +const knot_dname_t *knot_rdata_cname_name(const knot_rdata_t *rdata); + +/*! + * \brief Retrieves the domain name from DNAME RDATA. + * + * \note This is only convenience function. It does not (and cannot) check if + * the given RDATA is of the right type, so it always returns the first + * RDATA item, even if it is not a domain name. + * + * \param rdata RDATA to get the DNAME domain name from. + * + * \return Target domain name stored in \a rdata or NULL if \a rdata has no + * items. + */ +const knot_dname_t *knot_rdata_dname_target(const knot_rdata_t *rdata); + +/*! + * \brief Retrieves the domain name from RDATA of given type. + * + * Supported types: + * - KNOT_RRTYPE_NS + * - KNOT_RRTYPE_MX + * - KNOT_RRTYPE_SRV + * - KNOT_RRTYPE_CNAME + * + * \note This is only convenience function. It does not (and cannot) check if + * the given RDATA is of the right type, so it always returns the RDATA + * item according to the given type, even if it is not a domain name. + * + * \param rdata RDATA to get the domain name from. + * \param type RR type of the RDATA. + * + * \return Domain name stored in \a rdata or NULL if \a rdata has not enough + * items. + */ +const knot_dname_t *knot_rdata_get_name(const knot_rdata_t *rdata, + uint16_t type); + +int64_t knot_rdata_soa_serial(const knot_rdata_t *rdata); + +uint32_t knot_rdata_soa_refresh(const knot_rdata_t *rdata); +uint32_t knot_rdata_soa_retry(const knot_rdata_t *rdata); +uint32_t knot_rdata_soa_expire(const knot_rdata_t *rdata); + +uint16_t knot_rdata_rrsig_type_covered(const knot_rdata_t *rdata); + +#endif /* _KNOT_RDATA_H */ + +/*! @} */ diff --git a/src/libknot/rrset.c b/src/libknot/rrset.c new file mode 100644 index 0000000..6083f77 --- /dev/null +++ b/src/libknot/rrset.c @@ -0,0 +1,719 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdint.h> +#include <stdlib.h> +#include <assert.h> +#include <stdio.h> + +#include "common.h" +#include "rrset.h" +#include "util/descriptor.h" +#include "util/error.h" +#include "util/utils.h" + +/*----------------------------------------------------------------------------*/ +/* Non-API functions */ +/*----------------------------------------------------------------------------*/ + +static void knot_rrset_disconnect_rdata(knot_rrset_t *rrset, + knot_rdata_t *prev, knot_rdata_t *rdata) +{ + if (prev == NULL) { + // find the previous RDATA in the series, as its pointer must + // be changed + prev = rdata->next; + while (prev->next != rdata) { + prev = prev->next; + } + } + + assert(prev); + assert(prev->next == rdata); + + prev->next = rdata->next; + + if (rrset->rdata == rdata) { + if (rdata->next == rdata) { + rrset->rdata = NULL; + } else { + rrset->rdata = rdata->next; + } + } +} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +knot_rrset_t *knot_rrset_new(knot_dname_t *owner, uint16_t type, + uint16_t rclass, uint32_t ttl) +{ + knot_rrset_t *ret = malloc(sizeof(knot_rrset_t)); + if (ret == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + + ret->rdata = NULL; + + /* Retain reference to owner. */ + knot_dname_retain(owner); + + ret->owner = owner; + ret->type = type; + ret->rclass = rclass; + ret->ttl = ttl; + ret->rrsigs = NULL; + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rrset_add_rdata(knot_rrset_t *rrset, knot_rdata_t *rdata) +{ + if (rrset == NULL || rdata == NULL) { + return KNOT_EBADARG; + } + + if (rrset->rdata == NULL) { + rrset->rdata = rdata; + rrset->rdata->next = rrset->rdata; + } else { + knot_rdata_t *tmp; + + tmp = rrset->rdata; + + while (tmp->next != rrset->rdata) { + tmp = tmp->next; + } + rdata->next = tmp->next; + tmp->next = rdata; + } + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +knot_rdata_t *knot_rrset_remove_rdata(knot_rrset_t *rrset, + const knot_rdata_t *rdata) +{ + if (rrset == NULL || rdata == NULL) { + return NULL; + } + + knot_rdata_t *prev = NULL; + knot_rdata_t *rr = rrset->rdata; + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(rrset->type); + + if (desc == NULL) { + return NULL; + } + + while (rr != NULL) { + /*! \todo maybe the dnames should be compared case-insensitive*/ + if (knot_rdata_compare(rr, rdata, desc->wireformat) == 0) { + knot_rrset_disconnect_rdata(rrset, prev, rr); + return rr; + } + prev = rr; + rr = knot_rrset_rdata_get_next(rrset, rr); + } + + return NULL; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rrset_set_rrsigs(knot_rrset_t *rrset, knot_rrset_t *rrsigs) +{ + if (rrset == NULL) { + return KNOT_EBADARG; + } + + rrset->rrsigs = rrsigs; + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rrset_add_rrsigs(knot_rrset_t *rrset, knot_rrset_t *rrsigs, + knot_rrset_dupl_handling_t dupl) +{ + if (rrset == NULL || rrsigs == NULL + || knot_dname_compare(rrset->owner, rrsigs->owner) != 0) { + return KNOT_EBADARG; + } + + int rc; + if (rrset->rrsigs != NULL) { + if (dupl == KNOT_RRSET_DUPL_MERGE) { + rc = knot_rrset_merge((void **)&rrset->rrsigs, + (void **)&rrsigs); + if (rc != KNOT_EOK) { + return rc; + } else { + return 1; + } + } else if (dupl == KNOT_RRSET_DUPL_SKIP) { + return 2; + } else if (dupl == KNOT_RRSET_DUPL_REPLACE) { + rrset->rrsigs = rrsigs; + } + } else { + rrset->rrsigs = rrsigs; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +const knot_dname_t *knot_rrset_owner(const knot_rrset_t *rrset) +{ + return rrset->owner; +} + +/*----------------------------------------------------------------------------*/ + +knot_dname_t *knot_rrset_get_owner(const knot_rrset_t *rrset) +{ + return rrset->owner; +} + +/*----------------------------------------------------------------------------*/ + +void knot_rrset_set_owner(knot_rrset_t *rrset, knot_dname_t* owner) +{ + if (rrset) { + /* Retain new owner and release old owner. */ + knot_dname_retain(owner); + knot_dname_release(rrset->owner); + rrset->owner = owner; + } +} + +/*----------------------------------------------------------------------------*/ + +uint16_t knot_rrset_type(const knot_rrset_t *rrset) +{ + return rrset->type; +} + +/*----------------------------------------------------------------------------*/ + +uint16_t knot_rrset_class(const knot_rrset_t *rrset) +{ + return rrset->rclass; +} + +/*----------------------------------------------------------------------------*/ + +uint32_t knot_rrset_ttl(const knot_rrset_t *rrset) +{ + return rrset->ttl; +} + +/*----------------------------------------------------------------------------*/ + +const knot_rdata_t *knot_rrset_rdata(const knot_rrset_t *rrset) +{ + return rrset->rdata; +} + +/*----------------------------------------------------------------------------*/ + +const knot_rdata_t *knot_rrset_rdata_next(const knot_rrset_t *rrset, + const knot_rdata_t *rdata) +{ + if (rdata == NULL) { + return rrset->rdata; + } + if (rdata->next == rrset->rdata) { + return NULL; + } else { + return rdata->next; + } +} + +/*----------------------------------------------------------------------------*/ + +knot_rdata_t *knot_rrset_get_rdata(knot_rrset_t *rrset) +{ + if (rrset == NULL) { + return NULL; + } else { + return rrset->rdata; + } +} + +/*----------------------------------------------------------------------------*/ + +knot_rdata_t *knot_rrset_rdata_get_next(knot_rrset_t *rrset, + knot_rdata_t *rdata) +{ + if (rdata->next == rrset->rdata) { + return NULL; + } else { + return rdata->next; + } +} + +/*----------------------------------------------------------------------------*/ + +int knot_rrset_rdata_rr_count(const knot_rrset_t *rrset) +{ + int count = 0; + const knot_rdata_t *rdata = rrset->rdata; + + while (rdata != NULL) { + ++count; + rdata = knot_rrset_rdata_next(rrset, rdata); + } + + return count; +} + +/*----------------------------------------------------------------------------*/ + +const knot_rrset_t *knot_rrset_rrsigs(const knot_rrset_t *rrset) +{ + if (rrset == NULL) { + assert(0); + return NULL; + } else { + return rrset->rrsigs; + } +} + +/*----------------------------------------------------------------------------*/ + +knot_rrset_t *knot_rrset_get_rrsigs(knot_rrset_t *rrset) +{ + if (rrset == NULL) { + assert(0); + return NULL; + } else { + return rrset->rrsigs; + } +} + +/*----------------------------------------------------------------------------*/ + +int knot_rrset_compare_rdata(const knot_rrset_t *r1, const knot_rrset_t *r2) +{ + if (r1 == NULL || r2 == NULL) { + return KNOT_EBADARG; + } + + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(r1->type); + if (desc == NULL) { + return KNOT_EBADARG; + } + + // compare RDATA sets (order is not significant) + const knot_rdata_t *rdata1= knot_rrset_rdata(r1); + const knot_rdata_t *rdata2; + + // find all RDATA from r1 in r2 + while (rdata1 != NULL) { + rdata2 = knot_rrset_rdata(r2); + while (rdata2 != NULL && knot_rdata_compare(rdata1, rdata2, + desc->wireformat)) { + rdata2 = knot_rrset_rdata_next(r2, rdata2); + } + + if (rdata2 == NULL) { + // RDATA from r1 not found in r2 + return 0; + } + + // otherwise it was found, continue with next r1 RDATA + rdata1 = knot_rrset_rdata_next(r1, rdata1); + } + + // find all RDATA from r2 in r1 (this can be done in a better way) + rdata2 = knot_rrset_rdata(r2); + while (rdata2 != NULL) { + rdata1 = knot_rrset_rdata(r1); + while (rdata2 != NULL && knot_rdata_compare(rdata1, rdata2, + desc->wireformat)) { + rdata1 = knot_rrset_rdata_next(r1, rdata1); + } + + if (rdata1 == NULL) { + // RDATA from r1 not found in r2 + return 0; + } + + // otherwise it was found, continue with next r1 RDATA + rdata2 = knot_rrset_rdata_next(r2, rdata2); + } + + // all RDATA found + return 1; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_rrset_rr_to_wire(const knot_rrset_t *rrset, + const knot_rdata_t *rdata, uint8_t **pos, + size_t max_size) +{ + int size = 0; + + assert(rrset != NULL); + assert(rrset->owner != NULL); + assert(rdata != NULL); + assert(pos != NULL); + assert(*pos != NULL); + + fprintf(stderr, "Max size: %zu, owner: %p, owner size: %d\n", + max_size, rrset->owner, rrset->owner->size); + + // check if owner fits + if (size + knot_dname_size(rrset->owner) + 10 > max_size) { + return KNOT_ESPACE; + } + + memcpy(*pos, knot_dname_name(rrset->owner), + knot_dname_size(rrset->owner)); + *pos += knot_dname_size(rrset->owner); + size += knot_dname_size(rrset->owner); + + fprintf(stderr, "Max size: %zu, size: %d\n", max_size, size); + + fprintf(stderr, "Wire format:\n"); + + // put rest of RR 'header' + knot_wire_write_u16(*pos, rrset->type); + fprintf(stderr, " Type: %u\n", rrset->type); + *pos += 2; + + knot_wire_write_u16(*pos, rrset->rclass); + fprintf(stderr, " Class: %u\n", rrset->rclass); + *pos += 2; + + knot_wire_write_u32(*pos, rrset->ttl); + fprintf(stderr, " TTL: %u\n", rrset->ttl); + *pos += 4; + + // save space for RDLENGTH + uint8_t *rdlength_pos = *pos; + *pos += 2; + + size += 10; +// compr->wire_pos += size; + + fprintf(stderr, "Max size: %zu, size: %d\n", max_size, size); + + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(rrset->type); + + uint16_t rdlength = 0; + + for (int i = 0; i < rdata->count; ++i) { + if (max_size < size + rdlength) { + return KNOT_ESPACE; + } + + switch (desc->wireformat[i]) { + case KNOT_RDATA_WF_COMPRESSED_DNAME: + case KNOT_RDATA_WF_UNCOMPRESSED_DNAME: + case KNOT_RDATA_WF_LITERAL_DNAME: { + knot_dname_t *dname = + knot_rdata_item(rdata, i)->dname; + if (size + rdlength + dname->size > max_size) { + return KNOT_ESPACE; + } + + // save whole domain name + memcpy(*pos, knot_dname_name(dname), + knot_dname_size(dname)); + fprintf(stderr, "Uncompressed dname size: %d\n", + knot_dname_size(dname)); + *pos += knot_dname_size(dname); + rdlength += knot_dname_size(dname); +// compr->wire_pos += dname->size; + break; + } + default: { + uint16_t *raw_data = + knot_rdata_item(rdata, i)->raw_data; + + if (size + rdlength + raw_data[0] > max_size) { + return KNOT_ESPACE; + } + + // copy just the rdata item data (without size) + memcpy(*pos, raw_data + 1, raw_data[0]); + fprintf(stderr, "Raw data size: %d\n", raw_data[0]); + *pos += raw_data[0]; + rdlength += raw_data[0]; +// compr->wire_pos += raw_data[0]; + break; + } + } + } + + fprintf(stderr, "Max size: %zu, size: %d\n", max_size, size); + + assert(size + rdlength <= max_size); + size += rdlength; + knot_wire_write_u16(rdlength_pos, rdlength); + + return size; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rrset_to_wire(const knot_rrset_t *rrset, uint8_t *wire, size_t *size, + int *rr_count) +{ + // if no RDATA in RRSet, return + if (rrset->rdata == NULL) { + *size = 0; + *rr_count = 0; + return KNOT_EOK; + } + + + uint8_t *pos = wire; + int rrs = 0; + short rrset_size = 0; + + const knot_rdata_t *rdata = rrset->rdata; + do { + int ret = knot_rrset_rr_to_wire(rrset, rdata, &pos, + *size - rrset_size); + + assert(ret != 0); + + if (ret < 0) { + // some RR didn't fit in, so no RRs should be used + // TODO: remove last entries from compression table + fprintf(stderr, "Some RR didn't fit in.\n"); + return KNOT_ESPACE; + } + + fprintf(stderr, "RR of size %d added.\n", ret); + rrset_size += ret; + ++rrs; + } while ((rdata = knot_rrset_rdata_next(rrset, rdata)) != NULL); + + // the whole RRSet did fit in + assert(rrset_size <= *size); + assert(pos - wire == rrset_size); + *size = rrset_size; + + fprintf(stderr, " Size after: %zu\n", *size); + + *rr_count = rrs; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rrset_compare(const knot_rrset_t *r1, + const knot_rrset_t *r2, + knot_rrset_compare_type_t cmp) +{ + if (cmp == KNOT_RRSET_COMPARE_PTR) { + return (r1 == r2); + } + + int res = ((r1->rclass == r2->rclass) + && (r1->type == r2->type) + && (r1->ttl == r2->ttl) + && knot_dname_compare(r1->owner, r2->owner) == 0); + + if (cmp == KNOT_RRSET_COMPARE_WHOLE && res) { + res = knot_rrset_compare_rdata(r1, r2); + if (res < 0) { + return 0; + } + } + + return res; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rrset_deep_copy(const knot_rrset_t *from, knot_rrset_t **to) +{ + if (from == NULL || to == NULL) { + return KNOT_EBADARG; + } + + int ret; + + *to = (knot_rrset_t *)calloc(1, sizeof(knot_rrset_t)); + CHECK_ALLOC_LOG(*to, KNOT_ENOMEM); + + (*to)->owner = knot_dname_deep_copy(from->owner); + (*to)->rclass = from->rclass; + (*to)->ttl = from->ttl; + (*to)->type = from->type; + if (from->rrsigs != NULL) { + ret = knot_rrset_deep_copy(from->rrsigs, &(*to)->rrsigs); + if (ret != KNOT_EOK) { + knot_rrset_deep_free(to, 1, 0, 0); + return ret; + } + } + assert((*to)->rrsigs == NULL || from->rrsigs != NULL); + + const knot_rdata_t *rdata = knot_rrset_rdata(from); + + /*! \note Order of RDATA will be reversed. */ + while (rdata != NULL) { + ret = knot_rrset_add_rdata(*to, knot_rdata_deep_copy(rdata, + knot_rrset_type(from))); + if (ret != KNOT_EOK) { + knot_rrset_deep_free(to, 1, 1, 1); + return ret; + } + rdata = knot_rrset_rdata_next(from, rdata); + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rrset_shallow_copy(const knot_rrset_t *from, knot_rrset_t **to) +{ + *to = (knot_rrset_t *)malloc(sizeof(knot_rrset_t)); + CHECK_ALLOC_LOG(*to, KNOT_ENOMEM); + + memcpy(*to, from, sizeof(knot_rrset_t)); + + /* Retain owner. */ + knot_dname_retain((*to)->owner); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +void knot_rrset_free(knot_rrset_t **rrset) +{ + if (rrset == NULL || *rrset == NULL) { + return; + } + + /*! \todo Shouldn't we always release owner reference? */ + knot_dname_release((*rrset)->owner); + + free(*rrset); + *rrset = NULL; +} + +/*----------------------------------------------------------------------------*/ + +void knot_rrset_deep_free(knot_rrset_t **rrset, int free_owner, + int free_rdata, int free_rdata_dnames) +{ + if (rrset == NULL || *rrset == NULL) { + return; + } + + if (free_rdata) { + knot_rdata_t *tmp_rdata; + knot_rdata_t *next_rdata; + tmp_rdata = (*rrset)->rdata; + + while ((tmp_rdata != NULL) + && (tmp_rdata->next != (*rrset)->rdata) + && (tmp_rdata->next != NULL)) { + next_rdata = tmp_rdata->next; + knot_rdata_deep_free(&tmp_rdata, (*rrset)->type, + free_rdata_dnames); + tmp_rdata = next_rdata; + } + + assert(tmp_rdata == NULL + || tmp_rdata->next == (*rrset)->rdata); + + knot_rdata_deep_free(&tmp_rdata, (*rrset)->type, + free_rdata_dnames); + } + + // RRSIGs should have the same owner as this RRSet, so do not delete it + if ((*rrset)->rrsigs != NULL) { + knot_rrset_deep_free(&(*rrset)->rrsigs, 0, 1, + free_rdata_dnames); + } + + /*! \todo Release owner every time? */ + //if (free_owner) { + knot_dname_release((*rrset)->owner); + //} + + free(*rrset); + *rrset = NULL; +} + +/*----------------------------------------------------------------------------*/ + +int knot_rrset_merge(void **r1, void **r2) +{ + knot_rrset_t *rrset1 = (knot_rrset_t *)(*r1); + knot_rrset_t *rrset2 = (knot_rrset_t *)(*r2); + + if ((knot_dname_compare(rrset1->owner, rrset2->owner) != 0) + || rrset1->rclass != rrset2->rclass + || rrset1->type != rrset2->type + || rrset1->ttl != rrset2->ttl) { + return KNOT_EBADARG; + } + + // add all RDATAs from rrset2 to rrset1 (i.e. concatenate linked lists) + + // no RDATA in RRSet 1 + assert(rrset1 && rrset2); + if (rrset1->rdata == NULL) { + rrset1->rdata = rrset2->rdata; + return KNOT_EOK; + } + + knot_rdata_t *tmp_rdata = rrset1->rdata; + + if (!tmp_rdata) { + return KNOT_EOK; + } + + while (tmp_rdata->next != rrset1->rdata) { + tmp_rdata = tmp_rdata->next; + } + + tmp_rdata->next = rrset2->rdata; + + tmp_rdata = rrset2->rdata; //maybe unnecessary, but is clearer + + while (tmp_rdata->next != rrset2->rdata) { + tmp_rdata = tmp_rdata->next; + } + + tmp_rdata->next = rrset1->rdata; + + return KNOT_EOK; +} diff --git a/src/libknot/rrset.h b/src/libknot/rrset.h new file mode 100644 index 0000000..7754c7f --- /dev/null +++ b/src/libknot/rrset.h @@ -0,0 +1,306 @@ +/*! + * \file rrset.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief RRSet structure and API for manipulating it. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_RRSET_H_ +#define _KNOT_RRSET_H_ + +#include <stdint.h> + +#include "dname.h" +#include "rdata.h" + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Structure for representing an RRSet. + * + * For definition of a RRSet see RFC2181, Section 5. + * + * As all RRs within a RRSet share the same OWNER, TYPE, CLASS and TTL (see + * Section 5.2 of RFC2181), there is no need to duplicate these data in the + * program. Distinct Resource Records are thus represented only as distinct + * RDATA sections of corresponding RRs. + */ +struct knot_rrset { + /*! \brief Domain name being the owner of the RRSet. */ + knot_dname_t *owner; + uint16_t type; /*!< TYPE of the RRset. */ + uint16_t rclass; /*!< CLASS of the RRSet. */ + uint32_t ttl; /*!< TTL of the RRSet. */ + /*! + * \brief First item in an ordered cyclic list of RDATA items. + * + * \note The fact that the list is cyclic will easily allow for + * possible round-robin rotation of RRSets. + */ + knot_rdata_t *rdata; + struct knot_rrset *rrsigs; /*!< Set of RRSIGs covering this RRSet. */ +}; + +typedef struct knot_rrset knot_rrset_t; + +/*----------------------------------------------------------------------------*/ + +typedef enum { + KNOT_RRSET_COMPARE_PTR, + KNOT_RRSET_COMPARE_HEADER, + KNOT_RRSET_COMPARE_WHOLE +} knot_rrset_compare_type_t; + +typedef enum { + KNOT_RRSET_DUPL_MERGE, + KNOT_RRSET_DUPL_REPLACE, + KNOT_RRSET_DUPL_SKIP +} knot_rrset_dupl_handling_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates a new RRSet with the given properties. + * + * The created RRSet contains no RDATAs (i.e. is actually empty). + * + * \param owner OWNER of the RRSet. + * \param type TYPE of the RRSet. + * \param rclass CLASS of the RRSet. + * \param ttl TTL of the RRset. + * + * \return New RRSet structure with the given OWNER, TYPE, CLASS and TTL or NULL + * if an error occured. + */ +knot_rrset_t *knot_rrset_new(knot_dname_t *owner, uint16_t type, + uint16_t rclass, uint32_t ttl); + +/*! + * \brief Adds the given RDATA to the RRSet. + * + * \param rrset RRSet to add the RDATA to. + * \param rdata RDATA to add to the RRSet. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + * + * \todo Provide some function for comparing RDATAs. + */ +int knot_rrset_add_rdata(knot_rrset_t *rrset, knot_rdata_t *rdata); + +knot_rdata_t * knot_rrset_remove_rdata(knot_rrset_t *rrset, + const knot_rdata_t *rdata); + +/*! + * \brief Adds RRSIG signatures to this RRSet. + * + * \param rrset RRSet to add the signatures into. + * \param rrsigs Set of RRSIGs covering this RRSet. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + */ +int knot_rrset_set_rrsigs(knot_rrset_t *rrset, knot_rrset_t *rrsigs); + +int knot_rrset_add_rrsigs(knot_rrset_t *rrset, knot_rrset_t *rrsigs, + knot_rrset_dupl_handling_t dupl); + +/*! + * \brief Returns the Owner of the RRSet. + * + * \param rrset RRSet to get the Owner of. + * + * \return Owner of the given RRSet. + */ +const knot_dname_t *knot_rrset_owner(const knot_rrset_t *rrset); + +/*! + * \todo Document me. + */ +knot_dname_t *knot_rrset_get_owner(const knot_rrset_t *rrset); + +/*! + * \brief Set rrset owner to specified dname. + * + * Previous owner will be replaced if exist. + * + * \param rrset Specified RRSet. + * \param owner New owner dname. + */ +void knot_rrset_set_owner(knot_rrset_t *rrset, knot_dname_t* owner); + +/*! + * \brief Returns the TYPE of the RRSet. + * + * \param rrset RRSet to get the TYPE of. + * + * \return TYPE of the given RRSet. + */ +uint16_t knot_rrset_type(const knot_rrset_t *rrset); + +/*! + * \brief Returns the CLASS of the RRSet. + * + * \param rrset RRSet to get the CLASS of. + * + * \return CLASS of the given RRSet. + */ +uint16_t knot_rrset_class(const knot_rrset_t *rrset); + +/*! + * \brief Returns the TTL of the RRSet. + * + * \param rrset RRSet to get the TTL of. + * + * \return TTL of the given RRSet. + */ +uint32_t knot_rrset_ttl(const knot_rrset_t *rrset); + +/*! + * \brief Returns the first RDATA in the RRSet. + * + * RDATAs in a RRSet are stored in a ordered cyclic list. + * + * \note If later a round-robin rotation of RRSets is employed, the RDATA + * returned by this function may not be the first RDATA in canonical + * order. + * + * \param rrset RRSet to get the RDATA from. + * + * \return First RDATA in the given RRSet. + */ +const knot_rdata_t *knot_rrset_rdata(const knot_rrset_t *rrset); + +const knot_rdata_t *knot_rrset_rdata_next(const knot_rrset_t *rrset, + const knot_rdata_t *rdata); + +/*! + * \brief Returns the first RDATA in the RRSet (non-const version). + * + * RDATAs in a RRSet are stored in a ordered cyclic list. + * + * \note If later a round-robin rotation of RRSets is employed, the RDATA + * returned by this function may not be the first RDATA in canonical + * order. + * + * \param rrset RRSet to get the RDATA from. + * + * \return First RDATA in the given RRSet or NULL if there is none or if no + * rrset was provided (\a rrset is NULL). + */ +knot_rdata_t *knot_rrset_get_rdata(knot_rrset_t *rrset); + +knot_rdata_t *knot_rrset_rdata_get_next(knot_rrset_t *rrset, + knot_rdata_t *rdata); + +int knot_rrset_rdata_rr_count(const knot_rrset_t *rrset); + +/*! + * \brief Returns the set of RRSIGs covering the given RRSet. + * + * \param rrset RRSet to get the signatures for. + * + * \return Set of RRSIGs which cover the given RRSet or NULL if there is none or + * if no rrset was provided (\a rrset is NULL). + */ +const knot_rrset_t *knot_rrset_rrsigs(const knot_rrset_t *rrset); + +knot_rrset_t *knot_rrset_get_rrsigs(knot_rrset_t *rrset); + +int knot_rrset_compare_rdata(const knot_rrset_t *r1, const knot_rrset_t *r2); + +int knot_rrset_to_wire(const knot_rrset_t *rrset, uint8_t *wire, size_t *size, + int *rr_count); + +/*! + * \brief Compares two RRSets. + * + * \note This function does not return 'standard' compare return values, because + * there is no way to define which RRSet is 'larger'. + * + * \param r1 First RRSet. + * \param r2 Second RRSet. + * \param cmp Type of comparison to perform. + * + * \retval <> 0 If RRSets are equal. + * \retval 0 if RRSets are not equal. + */ +int knot_rrset_compare(const knot_rrset_t *r1, + const knot_rrset_t *r2, + knot_rrset_compare_type_t cmp); + +/*! \todo Add unit test. */ +int knot_rrset_deep_copy(const knot_rrset_t *from, knot_rrset_t **to); + +/*! \todo Add unit test. */ +int knot_rrset_shallow_copy(const knot_rrset_t *from, knot_rrset_t **to); + +/*! + * \brief Destroys the RRSet structure. + * + * Does not destroy the OWNER domain name structure, nor the signatures, as + * these may be used elsewhere. + * + * Does not destroy RDATA structures neither, as they need special processing. + * + * Also sets the given pointer to NULL. + * + * \param rrset RRset to be destroyed. + */ +void knot_rrset_free(knot_rrset_t **rrset); + +/*! + * \brief Destroys the RRSet structure and all its substructures. + * + * Also sets the given pointer to NULL. + * + * \param rrset RRset to be destroyed. + * \param free_owner Set to 0 if you do not want the owner domain name to be + * destroyed also. Set to <> 0 otherwise. + * \param free_rdata ***\todo DOCUMENT ME*** + * \param free_rdata_dnames Set to <> 0 if you want to delete ALL domain names + * present in RDATA. Set to 0 otherwise. (See + * knot_rdata_deep_free().) + */ +void knot_rrset_deep_free(knot_rrset_t **rrset, int free_owner, + int free_rdata, int free_rdata_dnames); + +/*! + * \brief Merges two RRSets. + * + * Merges \a r1 into \a r2 by concatenating the list of RDATAs in \a r2 after + * the list of RDATAs in \a r1. \a r2 is unaffected by this, though you must not + * destroy the RDATAs in \a r2 as they are now also in \a r1. (You may use + * function knot_rrset_free() though, as it does not touch RDATAs). + * + * \note Member \a rrsigs is preserved from the first RRSet. + * + * \param r1 Pointer to RRSet to be merged into. + * \param r2 Poitner to RRSet to be merged. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG if the RRSets could not be merged, because their + * Owner, Type, Class or TTL does not match. + */ +int knot_rrset_merge(void **r1, void **r2); + +#endif /* _KNOT_RRSET_H_ */ + +/*! @} */ diff --git a/src/libknot/tsig-op.c b/src/libknot/tsig-op.c new file mode 100644 index 0000000..3178a23 --- /dev/null +++ b/src/libknot/tsig-op.c @@ -0,0 +1,1089 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stdint.h> +#include <openssl/hmac.h> +#include <openssl/evp.h> +#include <time.h> +#include <ctype.h> + +#include "common.h" +#include "tsig.h" +#include "tsig-op.h" +#include "util/wire.h" +#include "util/error.h" +#include "util/debug.h" + + +static const char Base64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char Pad64 = '='; + + +static int b64rmap_initialized = 0; +static uint8_t b64rmap[256]; + +static const uint8_t b64rmap_special = 0xf0; +static const uint8_t b64rmap_end = 0xfd; +static const uint8_t b64rmap_space = 0xfe; +static const uint8_t b64rmap_invalid = 0xff; + +/** + * Initializing the reverse map is not thread safe. + * Which is fine for NSD. For now... + **/ +void b64_initialize_rmap() +{ + int i; + char ch; + + /* Null: end of string, stop parsing */ + b64rmap[0] = b64rmap_end; + + for (i = 1; i < 256; ++i) { + ch = (char)i; + /* Whitespaces */ + if (isspace(ch)) { + b64rmap[i] = b64rmap_space; + } + /* Padding: stop parsing */ + else if (ch == Pad64) { + b64rmap[i] = b64rmap_end; + } + /* Non-base64 char */ + else { + b64rmap[i] = b64rmap_invalid; + } + } + + /* Fill reverse mapping for base64 chars */ + for (i = 0; Base64[i] != '\0'; ++i) { + b64rmap[(uint8_t)Base64[i]] = i; + } + + b64rmap_initialized = 1; +} + +int b64_pton_do(char const *src, uint8_t *target, size_t targsize) +{ + int tarindex, state, ch; + uint8_t ofs; + + state = 0; + tarindex = 0; + + while (1) { + ch = *src++; + ofs = b64rmap[ch]; + + if (ofs >= b64rmap_special) { + /* Ignore whitespaces */ + if (ofs == b64rmap_space) { + continue; + } + /* End of base64 characters */ + if (ofs == b64rmap_end) { + break; + } + /* A non-base64 character. */ + return (-1); + } + + switch (state) { + case 0: + if ((size_t)tarindex >= targsize) { + return (-1); + } + target[tarindex] = ofs << 2; + state = 1; + break; + case 1: + if ((size_t)tarindex + 1 >= targsize) { + return (-1); + } + target[tarindex] |= ofs >> 4; + target[tarindex+1] = (ofs & 0x0f) + << 4 ; + tarindex++; + state = 2; + break; + case 2: + if ((size_t)tarindex + 1 >= targsize) { + return (-1); + } + target[tarindex] |= ofs >> 2; + target[tarindex+1] = (ofs & 0x03) + << 6; + tarindex++; + state = 3; + break; + case 3: + if ((size_t)tarindex >= targsize) { + return (-1); + } + target[tarindex] |= ofs; + tarindex++; + state = 0; + break; + default: + abort(); + } + } + + /* + * We are done decoding Base-64 chars. Let's see if we ended + * on a byte boundary, and/or with erroneous trailing characters. + */ + + if (ch == Pad64) { /* We got a pad char. */ + ch = *src++; /* Skip it, get next. */ + switch (state) { + case 0: /* Invalid = in first position */ + case 1: /* Invalid = in second position */ + return (-1); + + case 2: /* Valid, means one byte of info */ + /* Skip any number of spaces. */ + for ((void)NULL; ch != '\0'; ch = *src++) + if (b64rmap[ch] != b64rmap_space) { + break; + } + /* Make sure there is another trailing = sign. */ + if (ch != Pad64) { + return (-1); + } + ch = *src++; /* Skip the = */ + /* Fall through to "single trailing =" case. */ + /* FALLTHROUGH */ + + case 3: /* Valid, means two bytes of info */ + /* + * We know this char is an =. Is there anything but + * whitespace after it? + */ + for ((void)NULL; ch != '\0'; ch = *src++) + if (b64rmap[ch] != b64rmap_space) { + return (-1); + } + + /* + * Now make sure for cases 2 and 3 that the "extra" + * bits that slopped past the last full byte were + * zeros. If we don't check them, they become a + * subliminal channel. + */ + if (target[tarindex] != 0) { + return (-1); + } + } + } else { + /* + * We ended by seeing the end of the string. Make sure we + * have no partial bytes lying around. + */ + if (state != 0) { + return (-1); + } + } + + return (tarindex); +} + + +int b64_pton_len(char const *src) +{ + int tarindex, state, ch; + uint8_t ofs; + + state = 0; + tarindex = 0; + + while (1) { + ch = *src++; + ofs = b64rmap[ch]; + + if (ofs >= b64rmap_special) { + /* Ignore whitespaces */ + if (ofs == b64rmap_space) { + continue; + } + /* End of base64 characters */ + if (ofs == b64rmap_end) { + break; + } + /* A non-base64 character. */ + return (-1); + } + + switch (state) { + case 0: + state = 1; + break; + case 1: + tarindex++; + state = 2; + break; + case 2: + tarindex++; + state = 3; + break; + case 3: + tarindex++; + state = 0; + break; + default: + abort(); + } + } + + /* + * We are done decoding Base-64 chars. Let's see if we ended + * on a byte boundary, and/or with erroneous trailing characters. + */ + + if (ch == Pad64) { /* We got a pad char. */ + ch = *src++; /* Skip it, get next. */ + switch (state) { + case 0: /* Invalid = in first position */ + case 1: /* Invalid = in second position */ + return (-1); + + case 2: /* Valid, means one byte of info */ + /* Skip any number of spaces. */ + for ((void)NULL; ch != '\0'; ch = *src++) + if (b64rmap[ch] != b64rmap_space) { + break; + } + /* Make sure there is another trailing = sign. */ + if (ch != Pad64) { + return (-1); + } + ch = *src++; /* Skip the = */ + /* Fall through to "single trailing =" case. */ + /* FALLTHROUGH */ + + case 3: /* Valid, means two bytes of info */ + /* + * We know this char is an =. Is there anything but + * whitespace after it? + */ + for ((void)NULL; ch != '\0'; ch = *src++) + if (b64rmap[ch] != b64rmap_space) { + return (-1); + } + + } + } else { + /* + * We ended by seeing the end of the string. Make sure we + * have no partial bytes lying around. + */ + if (state != 0) { + return (-1); + } + } + + return (tarindex); +} + +int b64_pton(char const *src, uint8_t *target, size_t targsize) +{ + if (!b64rmap_initialized) { + b64_initialize_rmap(); + } + + if (target) { + return b64_pton_do(src, target, targsize); + } else { + return b64_pton_len(src); + } +} + +#define B64BUFSIZE 65535 /*!< Buffer size for b64 conversion. */ + + + + + + + + + + + + +const int KNOT_TSIG_MAX_DIGEST_SIZE = 64; // size of HMAC-SHA512 digest + + +static int knot_tsig_check_algorithm(const knot_rrset_t *tsig_rr) +{ + const knot_dname_t *alg_name = tsig_rdata_alg_name(tsig_rr); + if (!alg_name) { + return KNOT_EMALF; + } + + tsig_algorithm_t alg = tsig_alg_from_name(alg_name); + if (alg == 0) { + /*!< \todo is this error OK? */ + dbg_tsig("TSIG: unknown algorithm.\n"); + return KNOT_TSIG_EBADSIG; + } + + return KNOT_EOK; +} + +static int knot_tsig_check_key(const knot_rrset_t *tsig_rr, + const knot_key_t *tsig_key) +{ + const knot_dname_t *tsig_name = knot_rrset_owner(tsig_rr); + if (!tsig_name) { + return KNOT_EMALF; + } + + const char *name = knot_dname_to_str(tsig_name); + if (!name) { + return KNOT_EMALF; + } + + if (knot_dname_compare(tsig_name, tsig_key->name) != 0) { + /*!< \todo which error. */ + dbg_tsig("TSIG: unknown key: %s\n", name); + return KNOT_TSIG_EBADKEY; + } + + return KNOT_EOK; +} + +static int knot_tsig_compute_digest(const uint8_t *wire, size_t wire_len, + uint8_t *digest, size_t *digest_len, + const knot_key_t *key) +{ + if (!wire || !digest || !digest_len || !key) { + dbg_tsig("TSIG: digest: bad args.\n"); + return KNOT_EBADARG; + } + + if (!key->name) { + dbg_tsig("TSIG: digest: no algorithm\n"); + return KNOT_EMALF; + } + + tsig_algorithm_t tsig_alg = key->algorithm; + if (tsig_alg == 0) { + dbg_tsig("TSIG: digest: unknown algorithm\n"); + return KNOT_TSIG_EBADSIG; + } + + /* Create digest, using length of the algorithm. */ +// *digest = malloc(sizeof(uint8_t) * tsig_alg_digest_length(tsig_alg)); +// if (!digest) { +// ERR_ALLOC_FAILED; +// return KNOT_ENOMEM; +// } + + /* Decode key from Base64. */ + char decoded_key[B64BUFSIZE]; + + int decoded_key_size = b64_pton(key->secret, (uint8_t *)decoded_key, + B64BUFSIZE); + if (decoded_key_size < 0) { + dbg_tsig("TSIG: Could not decode Base64\n"); + return KNOT_EMALF; + } + + dbg_tsig("TSIG: decoded key size: %d\n", decoded_key_size); + dbg_tsig("TSIG: decoded key: '%*s'\n", decoded_key_size, decoded_key); + + dbg_tsig("TSIG: using this wire for digest calculation\n"); + + //dbg_tsig_hex(wire, wire_len); + + /* Compute digest. */ + HMAC_CTX ctx; + + switch (tsig_alg) { + case KNOT_TSIG_ALG_HMAC_MD5: + HMAC_Init(&ctx, decoded_key, + decoded_key_size, EVP_md5()); + break; + default: + return KNOT_ENOTSUP; + } /* switch */ + + unsigned tmp_dig_len = *digest_len; + HMAC_Update(&ctx, (const unsigned char *)wire, wire_len); + HMAC_Final(&ctx, digest, &tmp_dig_len); + *digest_len = tmp_dig_len; + + return KNOT_EOK; +} + +static int knot_tsig_check_time_signed(const knot_rrset_t *tsig_rr) +{ + if (!tsig_rr) { + return KNOT_EBADARG; + } + + /* Get the time signed and fudge values. */ + uint64_t time_signed = tsig_rdata_time_signed(tsig_rr); + if (time_signed == 0) { + return KNOT_TSIG_EBADTIME; + } + uint16_t fudge = tsig_rdata_fudge(tsig_rr); + if (fudge == 0) { + return KNOT_TSIG_EBADTIME; + } + + /* Get the current time. */ + time_t curr_time = time(NULL); + + /*!< \todo bleeding eyes. */ + if (difftime(curr_time, (time_t)time_signed) > fudge) { + return KNOT_TSIG_EBADTIME; + } + + return KNOT_EOK; +} + +static int knot_tsig_write_tsig_timers(uint8_t *wire, + const knot_rrset_t *tsig_rr) +{ + // put time signed + knot_wire_write_u48(wire, tsig_rdata_time_signed(tsig_rr)); + + // put fudge + knot_wire_write_u16(wire + 6, tsig_rdata_fudge(tsig_rr)); + + return KNOT_EOK; +} + +static int knot_tsig_write_tsig_variables(uint8_t *wire, + const knot_rrset_t *tsig_rr) +{ + /* Copy TSIG variables - starting with key name. */ + const knot_dname_t *tsig_owner = knot_rrset_owner(tsig_rr); + if (!tsig_owner) { + dbg_tsig("TSIG: write variables: no owner.\n"); + return KNOT_EBADARG; + } + + int offset = 0; + + memcpy(wire + offset, knot_dname_name(tsig_owner), + sizeof(uint8_t) * knot_dname_size(tsig_owner)); + dbg_tsig("TSIG: write variables: written owner (tsig alg): \n"); + /*knot_rrset_class(tsig_rr));*/ + dbg_tsig_hex_detail(wire + offset, knot_dname_size(tsig_owner)); + offset += knot_dname_size(tsig_owner); + + /*!< \todo which order? */ + + /* Copy class. */ + knot_wire_write_u16(wire + offset, knot_rrset_class(tsig_rr)); + dbg_tsig("TSIG: write variables: written CLASS: %u - ", + knot_rrset_class(tsig_rr)); + dbg_tsig_hex_detail(wire + offset, sizeof(uint16_t)); + offset += sizeof(uint16_t); + + /* Copy TTL - always 0. */ + knot_wire_write_u32(wire + offset, knot_rrset_ttl(tsig_rr)); + dbg_tsig("TSIG: write variables: written TTL: %u - ", + knot_rrset_ttl(tsig_rr)); + dbg_tsig_hex_detail(wire + offset, sizeof(uint32_t)); + offset += sizeof(uint32_t); + + /* Copy alg name. */ + const knot_dname_t *alg_name = tsig_rdata_alg_name(tsig_rr); + if (!alg_name) { + dbg_tsig("TSIG: write variables: no algorithm name.\n"); + return KNOT_EBADARG; + } +// alg_name = knot_dname_new_from_str("HMAC-MD5.SIG-ALG.REG.INT.", + //strlen("HMAC-MD5.SIG-ALG.REG.INT."), + //NULL); + + memcpy(wire + offset, knot_dname_name(alg_name), + sizeof(uint8_t) * knot_dname_size(alg_name)); + offset += knot_dname_size(alg_name); + dbg_tsig_detail("TSIG: write variables: written alg name: %s\n", + knot_dname_to_str(alg_name)); + + /* Following data are written in network order. */ + /* Time signed. */ + knot_wire_write_u48(wire + offset, tsig_rdata_time_signed(tsig_rr)); + offset += 6; + dbg_tsig_detail("TSIG: write variables: time signed: %llu - ", + tsig_rdata_time_signed(tsig_rr)); + dbg_tsig_hex_detail(wire + offset - 6, 6); + /* Fudge. */ + knot_wire_write_u16(wire + offset, tsig_rdata_fudge(tsig_rr)); + offset += sizeof(uint16_t); + dbg_tsig_detail("TSIG: write variables: fudge: %hu\n", + tsig_rdata_fudge(tsig_rr)); + /* TSIG error. */ + knot_wire_write_u16(wire + offset, tsig_rdata_error(tsig_rr)); + offset += sizeof(uint16_t); + /* Get other data length. */ + uint16_t other_data_length = tsig_rdata_other_data_length(tsig_rr); + /* Get other data. */ + const uint8_t *other_data = tsig_rdata_other_data(tsig_rr); + if (!other_data) { + dbg_tsig("TSIG: write variables: no other data.\n"); + return KNOT_EBADARG; + } + + /* + * We cannot write the whole other_data, as it contains its length in + * machine order. + */ + knot_wire_write_u16(wire + offset, other_data_length); + offset += sizeof(uint16_t); + + /* Skip the length. */ + dbg_tsig_detail("Copying other data.\n"); + memcpy(wire + offset, other_data, other_data_length); + + return KNOT_EOK; +} + +static int knot_tsig_wire_write_timers(uint8_t *wire, + const knot_rrset_t *tsig_rr) +{ + knot_wire_write_u48(wire, tsig_rdata_time_signed(tsig_rr)); + knot_wire_write_u16(wire + 6, tsig_rdata_fudge(tsig_rr)); + + return KNOT_EOK; +} + +int knot_tsig_create_sign_wire(const uint8_t *msg, size_t msg_len, + /*size_t msg_max_len, */const uint8_t *request_mac, + size_t request_mac_len, + uint8_t *digest, size_t *digest_len, + const knot_rrset_t *tmp_tsig, + const knot_key_t *key) +{ + if (!msg || !key || digest_len == NULL) { + dbg_tsig("TSIG: create wire: bad args.\n"); + return KNOT_EBADARG; + } + + /* Create tmp TSIG. */ + int ret = KNOT_EOK; +// knot_rrset_t *tmp_tsig = +// knot_rrset_new(key->name, KNOT_RRTYPE_TSIG, KNOT_CLASS_ANY, 0); +// if (!tmp_tsig) { +// return KNOT_ENOMEM; +// } + +// tsig_rdata_store_current_time(tmp_tsig); + + /* + * Create tmp wire, it should contain message + * plus request mac plus tsig varibles. + */ + dbg_tsig("Counting wire size: %zu, %zu, %zu.\n", + msg_len, request_mac_len, + tsig_rdata_tsig_variables_length(tmp_tsig)); + size_t wire_len = sizeof(uint8_t) * + (msg_len + request_mac_len + ((request_mac_len > 0) + ? 2 : 0) + + tsig_rdata_tsig_variables_length(tmp_tsig)); + uint8_t *wire = malloc(wire_len); + if (!wire) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + memset(wire, 0, wire_len); + + uint8_t *pos = wire; + + /* Copy the request MAC - should work even if NULL. */ + if (request_mac_len > 0) { + dbg_tsig_detail("Copying request MAC size\n"); + knot_wire_write_u16(pos, request_mac_len); + pos += 2; + } + dbg_tsig("Copying request mac.\n"); + memcpy(pos, request_mac, sizeof(uint8_t) * request_mac_len); + dbg_tsig_detail("TSIG: create wire: request mac: "); + dbg_tsig_hex_detail(pos, request_mac_len); + pos += request_mac_len; + /* Copy the original message. */ + dbg_tsig("Copying original message.\n"); + memcpy(pos, msg, msg_len); + dbg_tsig_detail("TSIG: create wire: original message: \n"); + //dbg_tsig_hex_detail(pos, msg_len); + pos += msg_len; + /* Copy TSIG variables. */ + dbg_tsig("Writing TSIG variables.\n"); + ret = knot_tsig_write_tsig_variables(pos, tmp_tsig); + if (ret != KNOT_EOK) { + dbg_tsig("TSIG: create wire: failed to write TSIG " + "variables: %s\n", knot_strerror(ret)); + return ret; + } + + /* Compute digest. */ + ret = knot_tsig_compute_digest(wire, wire_len, + digest, digest_len, key); + if (ret != KNOT_EOK) { + dbg_tsig("TSIG: create wire: failed to compute digest: %s\n", + knot_strerror(ret)); + *digest_len = 0; + return ret; + } + +// assert(digest_tmp_len > 0); + free(wire); + +// if (digest_tmp_len > *digest_len) { +// *digest_len = 0; +// return KNOT_ESPACE; +// } + +// knot_rrset_deep_free(&tmp_tsig, 1, 1, 1); + + // everything went ok, save the digest to the output parameter +// memcpy(digest, digest_tmp, digest_tmp_len); +// *digest_len = digest_tmp_len; + + return KNOT_EOK; +} + +static int knot_tsig_create_sign_wire_next(const uint8_t *msg, size_t msg_len, + const uint8_t *prev_mac, + size_t prev_mac_len, + uint8_t *digest, size_t *digest_len, + const knot_rrset_t *tmp_tsig, + const knot_key_t *key) +{ + if (!msg || !key || digest_len == NULL) { + dbg_tsig("TSIG: create wire: bad args.\n"); + return KNOT_EBADARG; + } + + /* Create tmp TSIG. */ + int ret = KNOT_EOK; + + /* + * Create tmp wire, it should contain message + * plus request mac plus tsig varibles. + */ + dbg_tsig("Counting wire size: %zu, %zu, %zu.\n", + msg_len, prev_mac_len, + tsig_rdata_tsig_timers_length()); + size_t wire_len = sizeof(uint8_t) * + (msg_len + prev_mac_len + + tsig_rdata_tsig_timers_length()); + uint8_t *wire = malloc(wire_len); + if (!wire) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + memset(wire, 0, wire_len); + + /* Copy the request MAC - should work even if NULL. */ + dbg_tsig("Copying request mac.\n"); + memcpy(wire, prev_mac, sizeof(uint8_t) * prev_mac_len); + dbg_tsig_detail("TSIG: create wire: request mac: "); + dbg_tsig_hex_detail(wire, prev_mac_len); + /* Copy the original message. */ + dbg_tsig("Copying original message.\n"); + memcpy(wire + prev_mac_len, msg, msg_len); + dbg_tsig_detail("TSIG: create wire: original message: \n"); + //dbg_tsig_hex_detail(wire + prev_mac_len, msg_len); + /* Copy TSIG variables. */ + + dbg_tsig("Writing TSIG timers.\n"); + ret = knot_tsig_write_tsig_timers(wire + prev_mac_len + msg_len, + tmp_tsig); +// ret = knot_tsig_write_tsig_variables(wire + prev_mac_len + msg_len, +// tmp_tsig); + if (ret != KNOT_EOK) { + dbg_tsig("TSIG: create wire: failed to write TSIG " + "timers: %s\n", knot_strerror(ret)); + return ret; + } + + /* Compute digest. */ + ret = knot_tsig_compute_digest(wire, wire_len, + digest, digest_len, key); + if (ret != KNOT_EOK) { + dbg_tsig("TSIG: create wire: failed to compute digest: %s\n", + knot_strerror(ret)); + *digest_len = 0; + return ret; + } + + free(wire); + + return KNOT_EOK; +} + +int knot_tsig_sign(uint8_t *msg, size_t *msg_len, + size_t msg_max_len, const uint8_t *request_mac, + size_t request_mac_len, + uint8_t *digest, size_t *digest_len, + const knot_key_t *key) +{ + if (!msg || !msg_len || !key || digest == NULL || digest_len == NULL) { + return KNOT_EBADARG; + } + + knot_dname_t *key_name_copy = knot_dname_deep_copy(key->name); + if (!key_name_copy) { + dbg_tsig_detail("TSIG: key_name_copy = NULL\n"); + return KNOT_ENOMEM; + } + + knot_rrset_t *tmp_tsig = + knot_rrset_new(key_name_copy, + KNOT_RRTYPE_TSIG, KNOT_CLASS_ANY, 0); + if (!tmp_tsig) { + dbg_tsig_detail("TSIG: tmp_tsig = NULL\n"); + return KNOT_ENOMEM; + } + + /* Create rdata for TSIG RR. */ + knot_rdata_t *rdata = knot_rdata_new(); + if (!rdata) { + dbg_tsig_detail("TSIG: rdata = NULL\n"); + return KNOT_ENOMEM; + } + + knot_rrset_add_rdata(tmp_tsig, rdata); + + /* Create items for TSIG RR. */ + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(KNOT_RRTYPE_TSIG); + assert(desc); + + knot_rdata_item_t *items = + malloc(sizeof(knot_rdata_item_t) * desc->length); + if (!items) { + dbg_tsig_detail("TSIG: items = NULL\n"); + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + memset(items, 0, sizeof(knot_rdata_item_t) * desc->length); + + int ret = knot_rdata_set_items(rdata, items, desc->length); + if (ret != KNOT_EOK) { + dbg_tsig_detail("TSIG: rdata_set_items returned %s\n", knot_strerror(ret)); + return ret; + } + free(items); + + tsig_rdata_set_alg(tmp_tsig, key->algorithm); + tsig_rdata_store_current_time(tmp_tsig); + tsig_rdata_set_fudge(tmp_tsig, 300); + + /* Set original ID */ + tsig_rdata_set_orig_id(tmp_tsig, knot_wire_get_id(msg)); + + /* Set error */ + /*! \todo [TSIG] Set error and other data if appropriate. */ + tsig_rdata_set_tsig_error(tmp_tsig, 0); + + /* Set other len. */ + tsig_rdata_set_other_data(tmp_tsig, 0, 0); + + uint8_t digest_tmp[KNOT_TSIG_MAX_DIGEST_SIZE]; + size_t digest_tmp_len = 0; + + dbg_tsig_detail("tmp_tsig before sign_wire():\n"); + knot_rrset_dump(tmp_tsig, 0); + + ret = knot_tsig_create_sign_wire(msg, *msg_len, /*msg_max_len,*/ + request_mac, request_mac_len, + digest_tmp, &digest_tmp_len, + tmp_tsig, key); + if (ret != KNOT_EOK) { + dbg_tsig("TSIG: could not create wire or sign wire: %s\n", + knot_strerror(ret)); + return ret; + } + + /* Set the digest. */ + size_t tsig_wire_len = msg_max_len - *msg_len; + int rr_count = 0; + tsig_rdata_set_mac(tmp_tsig, digest_tmp_len, digest_tmp); + + //knot_rrset_dump(tmp_tsig, 1); + + /* Write RRSet to wire */ + ret = knot_rrset_to_wire(tmp_tsig, msg + *msg_len, + &tsig_wire_len, &rr_count); + if (ret != KNOT_EOK) { + dbg_tsig_detail("TSIG: rrset_to_wire = %s\n", knot_strerror(ret)); + *digest_len = 0; + return ret; + } + + knot_rrset_deep_free(&tmp_tsig, 1, 1, 1); + + *msg_len += tsig_wire_len; + + uint16_t arcount = knot_wire_get_arcount(msg); + knot_wire_set_arcount(msg, ++arcount); + + // everything went ok, save the digest to the output parameter + memcpy(digest, digest_tmp, digest_tmp_len); + *digest_len = digest_tmp_len; + + return KNOT_EOK; +} + +int knot_tsig_sign_next(uint8_t *msg, size_t *msg_len, size_t msg_max_len, + const uint8_t *prev_digest, size_t prev_digest_len, + uint8_t *digest, size_t *digest_len, + const knot_key_t *key) +{ + if (!msg || !msg_len || !key || !key || !digest || !digest_len) { + return KNOT_EBADARG; + } + + uint8_t digest_tmp[KNOT_TSIG_MAX_DIGEST_SIZE]; + size_t digest_tmp_len = 0; + + /* Create tmp TSIG. */ + knot_rrset_t *tmp_tsig = + knot_rrset_new(key->name, KNOT_RRTYPE_TSIG, KNOT_CLASS_ANY, 0); + if (!tmp_tsig) { + return KNOT_ENOMEM; + } + + tsig_rdata_store_current_time(tmp_tsig); + + /* Create wire to be signed. */ + size_t wire_len = prev_digest_len + *msg_len + KNOT_TSIG_TIMERS_LENGTH; + uint8_t *wire = malloc(wire_len); + if (!wire) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + memset(wire, 0, wire_len); + + /* Write previous digest. */ + memcpy(wire, prev_digest, sizeof(uint8_t) * prev_digest_len); + /* Write original message. */ + memcpy(msg + prev_digest_len, msg, *msg_len); + /* Write timers. */ + knot_tsig_wire_write_timers(msg + prev_digest_len + *msg_len, tmp_tsig); + + int ret = 0; + ret = knot_tsig_compute_digest(wire, wire_len, + digest_tmp, &digest_tmp_len, key); + if (ret != KNOT_EOK) { + *digest_len = 0; + return ret; + } + + if (digest_tmp_len > *digest_len) { + *digest_len = 0; + return KNOT_ESPACE; + } + + free(wire); + + /* Set the MAC. */ + tsig_rdata_set_mac(tmp_tsig, *digest_len, digest); + + size_t tsig_wire_size = msg_max_len - *msg_len; + int rr_count = 0; + ret = knot_rrset_to_wire(tmp_tsig, msg + *msg_len, + &tsig_wire_size, &rr_count); + if (ret != KNOT_EOK) { + *digest_len = 0; + return ret; + } + + knot_rrset_deep_free(&tmp_tsig, 1, 1, 1); + + *msg_len += tsig_wire_size; + uint16_t arcount = knot_wire_get_arcount(msg); + knot_wire_set_arcount(msg, ++arcount); + + memcpy(digest, digest_tmp, digest_tmp_len); + *digest_len = digest_tmp_len; + + return KNOT_EOK; +} + +static int knot_tsig_check_digest(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const uint8_t *request_mac, + size_t request_mac_len, + const knot_key_t *tsig_key, + int use_times) +{ + if (!tsig_rr || !wire || !tsig_key) { + return KNOT_EBADARG; + } + + /* Check time signed. */ + int ret = knot_tsig_check_time_signed(tsig_rr); + if (ret != KNOT_EOK) { + return ret; + } + + dbg_tsig("TSIG: time checked.\n"); + + /* Check that libknot knows the algorithm. */ + ret = knot_tsig_check_algorithm(tsig_rr); + if (ret != KNOT_EOK) { + return ret; + } + + dbg_tsig("TSIG: algorithm checked.\n"); + + /* Check that key is valid, ie. the same as given in args. */ + ret = knot_tsig_check_key(tsig_rr, tsig_key); + if (ret != KNOT_EOK) { + return ret; + } + + dbg_tsig("TSIG: key validity checked.\n"); + + /* Time OK algorithm OK, key name OK - do digest. */ + /* Calculate the size of TSIG RR. */ + size_t tsig_len = tsig_wire_actsize(tsig_rr); + + dbg_tsig_detail("TSIG: check digest: wire before strip: \n"); + //dbg_tsig_hex_detail(wire, size); + + /* Strip the TSIG. */ + size -= tsig_len; + + dbg_tsig_detail("TSIG: check digest: wire after strip (stripped %zu):\n", + tsig_len); + //dbg_tsig_hex_detail(wire, size); + + uint8_t *wire_to_sign = malloc(sizeof(uint8_t) * size); + if (!wire_to_sign) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + memset(wire_to_sign, 0, sizeof(uint8_t) * size); + memcpy(wire_to_sign, wire, size); + + /* Decrease arcount. */ + knot_wire_set_arcount(wire_to_sign, + knot_wire_get_arcount(wire_to_sign) - 1); + + uint8_t digest_tmp[KNOT_TSIG_MAX_DIGEST_SIZE]; + size_t digest_tmp_len = 0; + assert(tsig_rr->rdata); + + if (use_times) { + ret = knot_tsig_create_sign_wire_next(wire_to_sign, size, + request_mac, request_mac_len, + digest_tmp, &digest_tmp_len, + tsig_rr, tsig_key); + } else { + ret = knot_tsig_create_sign_wire(wire_to_sign, size, + request_mac, request_mac_len, + digest_tmp, &digest_tmp_len, + tsig_rr, tsig_key); + } + + assert(tsig_rr->rdata); + free(wire_to_sign); + + if (ret != KNOT_EOK) { + dbg_tsig("Failed to create wire format for checking: %s.\n", + knot_strerror(ret)); + return ret; + } + +// uint8_t digest_tmp[KNOT_TSIG_MAX_DIGEST_SIZE]; +// size_t digest_tmp_len = 0; +// ret = knot_tsig_compute_digest(wire, size, digest_tmp, +// &digest_tmp_len, tsig_key); +// if (ret != KNOT_EOK) { +// dbg_tsig("TSIG: digest could not be calculated\n"); +// return ret; +// } + + dbg_tsig("TSIG: digest calculated\n"); + + /* Compare MAC from TSIG RR RDATA with just computed digest. */ + + /*!< \todo move to function. */ + const knot_dname_t *alg_name = tsig_rdata_alg_name(tsig_rr); + tsig_algorithm_t alg = tsig_alg_from_name(alg_name); + + /*! \todo [TSIG] TRUNCATION */ + uint16_t mac_length = tsig_rdata_mac_length(tsig_rr); + const uint8_t *tsig_mac = tsig_rdata_mac(tsig_rr); + + if (mac_length != tsig_alg_digest_length(alg)) { + dbg_tsig("TSIG: calculated digest length and given length do not match!\n"); + return KNOT_TSIG_EBADSIG; + } + +// assert(tsig_alg_digest_length(alg) == mac_length); + + dbg_tsig("TSIG: calc digest : "); + dbg_tsig_hex(digest_tmp, digest_tmp_len); + + dbg_tsig("TSIG: given digest: "); + dbg_tsig_hex(tsig_mac, mac_length); + + if (strncasecmp((char *)(tsig_mac), (char *)digest_tmp, + mac_length) != 0) { + return KNOT_TSIG_EBADSIG; + } + + return KNOT_EOK; +} + +int knot_tsig_server_check(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const knot_key_t *tsig_key) +{ + dbg_tsig_verb("tsig_server_check()\n"); + return knot_tsig_check_digest(tsig_rr, wire, size, NULL, 0, tsig_key, 0); +} + +int knot_tsig_client_check(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const uint8_t *request_mac, size_t request_mac_len, + const knot_key_t *tsig_key) +{ + dbg_tsig_verb("tsig_client_check()\n"); + return knot_tsig_check_digest(tsig_rr, wire, size, request_mac, + request_mac_len, tsig_key, 0); +} + +int knot_tsig_client_check_next(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const uint8_t *prev_digest, + size_t prev_digest_len, + const knot_key_t *tsig_key) +{ +// return knot_tsig_client_check(tsig_rr, wire, size, prev_digest, +// prev_digest_len, tsig_key); + dbg_tsig_verb("tsig_client_check_next()\n"); + return knot_tsig_check_digest(tsig_rr, wire, size, prev_digest, + prev_digest_len, tsig_key, 1); + return KNOT_ENOTSUP; +} diff --git a/src/libknot/tsig-op.h b/src/libknot/tsig-op.h new file mode 100644 index 0000000..b206dc7 --- /dev/null +++ b/src/libknot/tsig-op.h @@ -0,0 +1,161 @@ +/*! + * \file tsig-op.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief TSIG signing and validating. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_TSIG_OP_H_ +#define _KNOT_TSIG_OP_H_ + +#include <stdint.h> + +#include "tsig.h" +#include "rrset.h" + +/*! + * \brief Generate TSIG signature of a message. + * + * This function generates TSIG digest of the given message prepended with the + * given Request MAC (if any) and appended with TSIG Variables. It also appends + * the resulting TSIG RR to the message wire format and accordingly adjusts + * the message size. + * + * \note This function does not save the new digest to the 'digest' parameter + * unless everything went OK. This allows to sent the same buffer to + * the 'request_mac' and 'digest' parameters. + * + * \param msg Message to be signed. + * \param msg_len Size of the message in bytes. + * \param msg_max_len Maximum size of the message in bytes. + * \param request_mac Request MAC. (may be NULL). + * \param request_mac_len Size of the request MAC in bytes. + * \param digest Buffer to save the digest in. + * \param digest_len In: size of the buffer. Out: real size of the digest saved. + * \param tsig_rr RRSet containing the TSIG RR to be used. Data from the RR are + * appended to the signed message. + * + * \retval KNOT_EOK if everything went OK. + * \retval TODO + * + * \todo This function should return TSIG errors by their codes which are + * positive values - this will be recognized by the caller. + */ +int knot_tsig_sign(uint8_t *msg, size_t *msg_len, size_t msg_max_len, + const uint8_t *request_mac, size_t request_mac_len, + uint8_t *digest, size_t *digest_len, + const knot_key_t *key); + +/*! + * \brief Generate TSIG signature of a 2nd or later message in a TCP session. + * + * This function generates TSIG digest of the given message prepended with the + * given Request MAC (if any) and appended with TSIG Variables. It also appends + * the resulting TSIG RR to the message wire format and accordingly adjusts + * the message size. + * + * \note This function does not save the new digest to the 'digest' parameter + * unless everything went OK. This allows to sent the same buffer to + * the 'request_mac' and 'digest' parameters. + * + * \param msg Message to be signed. + * \param msg_len Size of the message in bytes. + * \param msg_max_len Maximum size of the message in bytes. + * \param prev_digest Previous digest sent by the server in the session. + * \param prev_digest_len Size of the previous digest in bytes. + * \param digest Buffer to save the digest in. + * \param digest_len In: size of the buffer. Out: real size of the digest saved. + * \param tsig_rr RRSet containing the TSIG RR to be used. Data from the RR are + * appended to the signed message. + * + * \retval KNOT_EOK if successful. + * \retval TODO + * + * \todo This function should return TSIG errors by their codes which are + * positive values - this will be recognized by the caller. + */ +int knot_tsig_sign_next(uint8_t *msg, size_t *msg_len, size_t msg_max_len, + const uint8_t *prev_digest, size_t prev_digest_len, + uint8_t *digest, size_t *digest_len, + const knot_key_t *key); + +/*! + * \brief Checks incoming request. + * + * \param tsig_rr TSIG extracted from the packet. + * \param wire Wire format of the packet (including the TSIG RR). + * \param size Size of the wire format of packet in bytes. + * + * \retval KNOT_EOK If the signature is valid. + * \retval TODO + * + * \todo This function should return TSIG errors by their codes which are + * positive values - this will be recognized by the caller. + */ +int knot_tsig_server_check(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const knot_key_t *tsig_key); + +/*! + * \brief Checks incoming response. + * + * \param tsig_rr TSIG extracted from the packet. + * \param wire Wire format of the packet (including the TSIG RR). + * \param size Size of the wire format of packet in bytes. + * \param request_mac Request MAC. (may be NULL). + * \param request_mac_len Size of the request MAC in bytes. + * + * \retval KNOT_EOK If the signature is valid. + * \retval TODO + * + * \todo This function should return TSIG errors by their codes which are + * positive values - this will be recognized by the caller. + */ +int knot_tsig_client_check(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const uint8_t *request_mac, size_t request_mac_len, + const knot_key_t *key); + +/*! + * \brief Checks signature of 2nd or next packet in a TCP session. + * + * \param tsig_rr TSIG extracted from the packet. + * \param wire Wire format of the packet (including the TSIG RR). + * \param size Size of the wire format of packet in bytes. + * \param prev_digest Previous digest sent by the server in the session. + * \param prev_digest_len Size of the previous digest in bytes. + * + * \retval KNOT_EOK If the signature is valid. + * \retval TODO + * + * \todo This function should return TSIG errors by their codes which are + * positive values - this will be recognized by the caller. + */ +int knot_tsig_client_check_next(const knot_rrset_t *tsig_rr, + const uint8_t *wire, size_t size, + const uint8_t *prev_digest, + size_t prev_digest_len, + const knot_key_t *key); + +#endif /* _KNOT_TSIG_H_ */ + +/*! @} */ diff --git a/src/libknot/tsig.c b/src/libknot/tsig.c new file mode 100644 index 0000000..432539f --- /dev/null +++ b/src/libknot/tsig.c @@ -0,0 +1,618 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <stdlib.h> +#include <stdint.h> +#include <assert.h> +#include <time.h> + +#include "tsig.h" +#include "util/error.h" +#include "util/debug.h" +#include "common.h" +#include "util/utils.h" +#include "rrset.h" +#include "rdata.h" +#include "dname.h" + +/*! \brief TSIG algorithms table. */ +#define TSIG_ALG_TABLE_SIZE 8 +static knot_lookup_table_t tsig_alg_table[TSIG_ALG_TABLE_SIZE] = { + { KNOT_TSIG_ALG_GSS_TSIG, "gss-tsig." }, + { KNOT_TSIG_ALG_HMAC_MD5, "hmac-md5.sig-alg.reg.int." }, + { KNOT_TSIG_ALG_HMAC_SHA1, "hmac-sha1." }, + { KNOT_TSIG_ALG_HMAC_SHA224, "hmac-sha224." }, + { KNOT_TSIG_ALG_HMAC_SHA256, "hmac-sha256." }, + { KNOT_TSIG_ALG_HMAC_SHA384, "hmac-sha384." }, + { KNOT_TSIG_ALG_HMAC_SHA512, "hmac-sha512." }, + { KNOT_TSIG_ALG_NULL, NULL } +}; + +int tsig_rdata_init(knot_rrset_t *tsig) +{ + if (!tsig) { + return KNOT_EBADARG; + } + + /* Initializes rdata. */ + tsig->rdata = knot_rdata_new(); + if (!tsig->rdata) { + return KNOT_ENOMEM; + } + + tsig->rdata->items = + malloc(sizeof(knot_rdata_item_t) * KNOT_TSIG_ITEM_COUNT); + if (!tsig->rdata->items) { + return KNOT_ENOMEM; + } + + memset(tsig->rdata->items, 0, + sizeof(knot_rdata_item_t) * KNOT_TSIG_ITEM_COUNT); + + return KNOT_EOK; +} + +int tsig_rdata_set_alg_name(knot_rrset_t *tsig, knot_dname_t *alg_name) +{ + if (!tsig) { + return KNOT_EBADARG; + } + + knot_rdata_t *rdata = knot_rrset_get_rdata(tsig); + if (!rdata) { + return KNOT_EBADARG; + } + assert(knot_rdata_item_count(rdata) >= 1); + + knot_dname_t *alg_name_copy = knot_dname_deep_copy(alg_name); + if (!alg_name_copy) { + return KNOT_ENOMEM; + } + + knot_rdata_item_set_dname(rdata, 0, alg_name_copy); + + return KNOT_EOK; +} + +int tsig_rdata_set_alg(knot_rrset_t *tsig, tsig_algorithm_t alg) +{ + if (!tsig) { + return KNOT_EBADARG; + } + + knot_rdata_t *rdata = knot_rrset_get_rdata(tsig); + if (!rdata) { + return KNOT_EBADARG; + } + assert(knot_rdata_item_count(rdata) >= 1); + + const char *alg_str = tsig_alg_to_str(alg); + knot_dname_t *alg_name_copy = knot_dname_new_from_str(alg_str, + strlen(alg_str), + 0); + if (!alg_name_copy) { + return KNOT_ENOMEM; + } + + knot_rdata_item_set_dname(rdata, 0, alg_name_copy); + + return KNOT_EOK; +} + +int tsig_rdata_set_time_signed(knot_rrset_t *tsig, uint64_t time) +{ + if (!tsig) { + return KNOT_EBADARG; + } + + knot_rdata_t *rdata = knot_rrset_get_rdata(tsig); + if (!rdata) { + return KNOT_EBADARG; + } + assert(knot_rdata_item_count(rdata) >= 2); + + /* Create the wire format. */ + uint16_t *wire = malloc(sizeof(uint8_t) * 6 + sizeof(uint16_t)); + if (!wire) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + /* Write the length - 6. */ + wire[0] = 6; + knot_wire_write_u48((uint8_t *)(wire + 1), time); + + knot_rdata_item_set_raw_data(rdata, 1, wire); + + return KNOT_EOK; +} + +int tsig_rdata_set_fudge(knot_rrset_t *tsig, uint16_t fudge) +{ + if (!tsig) { + return KNOT_EBADARG; + } + + knot_rdata_t *rdata = knot_rrset_get_rdata(tsig); + if (!rdata) { + return KNOT_EBADARG; + } + assert(knot_rdata_item_count(rdata) >= 3); + + /* Create the wire format. */ + uint16_t *wire = malloc(sizeof(uint8_t) * 2 + sizeof(uint16_t)); + if (!wire) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + /* Write the length - 2. */ + wire[0] = sizeof(uint16_t); + knot_wire_write_u16((uint8_t *)(wire + 1), fudge); + + knot_rdata_item_set_raw_data(rdata, 2, wire); + + return KNOT_EOK; +} + +int tsig_rdata_set_mac(knot_rrset_t *tsig, uint16_t length, const uint8_t *mac) +{ + if (!tsig) { + return KNOT_EBADARG; + } + + knot_rdata_t *rdata = knot_rrset_get_rdata(tsig); + if (!rdata) { + return KNOT_EBADARG; + } + assert(knot_rdata_item_count(rdata) >= 4); + + /* Create the wire format. */ + uint16_t *wire = malloc(sizeof(uint8_t) * length + 2 * sizeof(uint16_t)); + if (!wire) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + /* Write the length. */ + wire[0] = length + sizeof(uint16_t); + knot_wire_write_u16((uint8_t *)(wire + 1), length); + /* Copy the actual MAC. */ + memcpy((uint8_t *)(wire + 2), mac, sizeof(uint8_t) * length); + knot_rdata_item_set_raw_data(rdata, 3, wire); + + return KNOT_EOK; +} + +int tsig_rdata_set_orig_id(knot_rrset_t *tsig, uint16_t id) +{ + if (!tsig) { + return KNOT_EBADARG; + } + + knot_rdata_t *rdata = knot_rrset_get_rdata(tsig); + if (!rdata) { + return KNOT_EBADARG; + } + assert(knot_rdata_item_count(rdata) >= 5); + + /* Create the wire format. */ + uint16_t *wire = malloc(sizeof(uint8_t) * 2 + sizeof(uint16_t)); + if (!wire) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + /* Write the length - 2. */ + wire[0] = sizeof(uint16_t); + knot_wire_write_u16((uint8_t *)(wire + 1), id); + + knot_rdata_item_set_raw_data(rdata, 4, wire); + + return KNOT_EOK; +} + +int tsig_rdata_set_tsig_error(knot_rrset_t *tsig, uint16_t tsig_error) +{ + if (!tsig) { + return KNOT_EBADARG; + } + + knot_rdata_t *rdata = knot_rrset_get_rdata(tsig); + if (!rdata) { + return KNOT_EBADARG; + } + assert(knot_rdata_item_count(rdata) >= 6); + + /* Create the wire format. */ + uint16_t *wire = malloc(sizeof(uint8_t) * 2 + sizeof(uint16_t)); + if (!wire) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + /* Write the length - 2. */ + wire[0] = sizeof(uint16_t); + knot_wire_write_u16((uint8_t *)(wire + 1), tsig_error); + + knot_rdata_item_set_raw_data(rdata, 5, wire); + + return KNOT_EOK; +} + +int tsig_rdata_set_other_data(knot_rrset_t *tsig, uint16_t length, + const uint8_t *other_data) +{ + if (!tsig) { + return KNOT_EBADARG; + } + + knot_rdata_t *rdata = knot_rrset_get_rdata(tsig); + if (!rdata) { + return KNOT_EBADARG; + } + assert(knot_rdata_item_count(rdata) >= 6); + + /* Create the wire format. */ + uint16_t *wire = malloc(sizeof(uint8_t) * length + 2 * sizeof(uint16_t)); + if (!wire) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + /* Write the length. */ + wire[0] = length + 2; + knot_wire_write_u16((uint8_t *)(wire + 1), length); + /* Copy the actual data. */ + memcpy(wire + 2, other_data, sizeof(uint8_t) * length); + knot_rdata_item_set_raw_data(rdata, 6, wire); + + return KNOT_EOK; +} + +const knot_dname_t *tsig_rdata_alg_name(const knot_rrset_t *tsig) +{ + if (!tsig) { + return NULL; + } + + const knot_rdata_t *rdata = knot_rrset_rdata(tsig); + if (!rdata) { + dbg_tsig("TSIG: rdata: alg name: no rdata.\n"); + return NULL; + } + + if (knot_rdata_item_count(rdata) < 1) { + dbg_tsig("TSIG: rdata: alg name: not enough items.\n"); + return NULL; + } + + return knot_rdata_item(rdata, 0)->dname; +} + +tsig_algorithm_t tsig_rdata_alg(const knot_rrset_t *tsig) +{ + /*! \todo [TSIG] Implement me. */ + return KNOT_TSIG_ALG_HMAC_MD5; +} + +uint64_t tsig_rdata_time_signed(const knot_rrset_t *tsig) +{ + /*!< \note How about assert. Or maybe change API??? */ + if (!tsig) { + return 0; + } + + const knot_rdata_t *rdata = knot_rrset_rdata(tsig); + if (!rdata) { + return 0; + } + + if (knot_rdata_item_count(rdata) < 2) { + return 0; + } + + uint16_t *wire = knot_rdata_item(rdata, 1)->raw_data; + assert(wire[0] == 6); + /* Skip the size. */ + wire++; + + return knot_wire_read_u48((uint8_t *)wire); +} + +uint16_t tsig_rdata_fudge(const knot_rrset_t *tsig) +{ + /*!< \note How about assert. Or maybe change API??? */ + if (!tsig) { + return 0; + } + + const knot_rdata_t *rdata = knot_rrset_rdata(tsig); + if (!rdata) { + return 0; + } + + if (knot_rdata_item_count(rdata) < 3) { + return 0; + } + + uint16_t *wire = knot_rdata_item(rdata, 2)->raw_data; + assert(wire[0] == 2); + /* Skip the size. */ + wire++; + + return knot_wire_read_u16((uint8_t *)wire); +} + +const uint8_t *tsig_rdata_mac(const knot_rrset_t *tsig) +{ + /*!< \note How about assert. Or maybe change API??? */ + if (!tsig) { + return 0; + } + + const knot_rdata_t *rdata = knot_rrset_rdata(tsig); + if (!rdata) { + return 0; + } + + if (knot_rdata_item_count(rdata) < 4) { + return 0; + } + + return (uint8_t*)(knot_rdata_item(rdata, 3)->raw_data + 2); +} + +size_t tsig_rdata_mac_length(const knot_rrset_t *tsig) +{ + if (!tsig) { + return 0; + } + + const knot_rdata_t *rdata = knot_rrset_rdata(tsig); + if (!rdata || knot_rdata_item_count(rdata) < 4) { + return 0; + } + + return knot_wire_read_u16( + (uint8_t *)(knot_rdata_item(rdata, 3)->raw_data + 1)); +} + +uint16_t tsig_rdata_orig_id(const knot_rrset_t *tsig) +{ + /*!< \note How about assert. Or maybe change API??? */ + if (!tsig) { + return 0; + } + + const knot_rdata_t *rdata = knot_rrset_rdata(tsig); + if (!rdata) { + return 0; + } + + if (knot_rdata_item_count(rdata) < 5) { + return 0; + } + + uint16_t *wire = knot_rdata_item(rdata, 4)->raw_data; + assert(wire[0] == 2); + /* Skip the size. */ + wire++; + + return knot_wire_read_u16((uint8_t *)wire); +} + +uint16_t tsig_rdata_error(const knot_rrset_t *tsig) +{ + /*!< \note How about assert. Or maybe change API??? */ + if (!tsig) { + return 0; + } + + const knot_rdata_t *rdata = knot_rrset_rdata(tsig); + if (!rdata) { + return 0; + } + + if (knot_rdata_item_count(rdata) < 6) { + return 0; + } + + uint16_t *wire = knot_rdata_item(rdata, 5)->raw_data; + assert(wire[0] == 2); + /* Skip the size. */ + wire++; + + return knot_wire_read_u16((uint8_t *)wire); +} + +const uint8_t *tsig_rdata_other_data(const knot_rrset_t *tsig) +{ + /*!< \note How about assert. Or maybe change API??? */ + if (!tsig) { + return 0; + } + + const knot_rdata_t *rdata = knot_rrset_rdata(tsig); + if (!rdata) { + return 0; + } + + if (knot_rdata_item_count(rdata) < 7) { + return 0; + } + + return (uint8_t *)(knot_rdata_item(rdata, 6)->raw_data + 2); +} + +uint16_t tsig_rdata_other_data_length(const knot_rrset_t *tsig) +{ + /*!< \note How about assert. Or maybe change API??? */ + if (!tsig) { + return 0; + } + + const knot_rdata_t *rdata = knot_rrset_rdata(tsig); + if (!rdata) { + return 0; + } + + if (knot_rdata_item_count(rdata) < 7) { + return 0; + } + + return knot_wire_read_u16((uint8_t *) + (knot_rdata_item(rdata, 6)->raw_data + 1)); +} + +int tsig_alg_from_name(const knot_dname_t *alg_name) +{ + if (!alg_name) { + return 0; + } + + char *name = knot_dname_to_str(alg_name); + if (!name) { + return 0; + } + + knot_lookup_table_t *found = + knot_lookup_by_name(tsig_alg_table, name); + + if (!found) { + dbg_tsig("Unknown algorithm: %s \n", name); + free(name); + return 0; + } + + free(name); + + return found->id; +} + +uint16_t tsig_alg_digest_length(tsig_algorithm_t alg) +{ + switch (alg) { + case KNOT_TSIG_ALG_GSS_TSIG: + return KNOT_TSIG_ALG_DIG_LENGTH_GSS_TSIG; + case KNOT_TSIG_ALG_HMAC_MD5: + return KNOT_TSIG_ALG_DIG_LENGTH_HMAC_MD5; + case KNOT_TSIG_ALG_HMAC_SHA1: + return KNOT_TSIG_ALG_DIG_LENGTH_SHA1; + case KNOT_TSIG_ALG_HMAC_SHA224: + return KNOT_TSIG_ALG_DIG_LENGTH_SHA224; + case KNOT_TSIG_ALG_HMAC_SHA256: + return KNOT_TSIG_ALG_DIG_LENGTH_SHA384; + case KNOT_TSIG_ALG_HMAC_SHA512: + return KNOT_TSIG_ALG_DIG_LENGTH_SHA512; + default: + return 0; + } /* switch(alg) */ +} + +size_t tsig_rdata_tsig_variables_length(const knot_rrset_t *tsig) +{ + /* Key name, Algorithm name and Other data have variable lengths. */ + const knot_dname_t *key_name = knot_rrset_owner(tsig); + if (!key_name) { + return 0; + } + + const knot_dname_t *alg_name = tsig_rdata_alg_name(tsig); + if (!alg_name) { + return 0; + } + +// dbg_tsig_detail("key_name: %.*s (size: %u) alg_name: %.*s (size: %u)\n", knot_dname_size(key_name), +// key_name->name, alg_name->size, alg_name->name, +// key_name->size, alg_name->size); + +// dbg_tsig_hex_detail(key_name->name, key_name->size); +// dbg_tsig_hex_detail(alg_name->name, alg_name->size); + + uint16_t other_data_length = tsig_rdata_other_data_length(tsig); + + return knot_dname_size(key_name) + knot_dname_size(alg_name) + + other_data_length + KNOT_TSIG_VARIABLES_LENGTH; +} + +size_t tsig_rdata_tsig_timers_length() +{ + return KNOT_TSIG_TIMERS_LENGTH; +} + + +int tsig_rdata_store_current_time(knot_rrset_t *tsig) +{ + if (!tsig) { + return KNOT_EBADARG; + } + time_t curr_time = time(NULL); + /*!< \todo bleeding eyes. */ + tsig_rdata_set_time_signed(tsig, (uint64_t)curr_time); + return KNOT_EOK; +} + +const char* tsig_alg_to_str(tsig_algorithm_t alg) +{ + for (unsigned i = 0; i < TSIG_ALG_TABLE_SIZE; ++i) { + if (tsig_alg_table[i].id == alg) { + return tsig_alg_table[i].name; + } + } + + return ""; +} + +size_t tsig_wire_maxsize(const knot_key_t* key) +{ + size_t alg_name_size = strlen(tsig_alg_to_str(key->algorithm)) + 1; + + return knot_dname_size(key->name) + + sizeof(uint16_t) + /* TYPE */ + sizeof(uint16_t) + /* CLASS */ + sizeof(uint32_t) + /* TTL */ + sizeof(uint16_t) + /* RDLENGTH */ + alg_name_size + /* Alg. name */ + 6 * sizeof(uint8_t) + /* Time signed */ + sizeof(uint16_t) + /* Fudge */ + sizeof(uint16_t) + /* MAC size */ + tsig_alg_digest_length(key->algorithm) + /* MAC */ + sizeof(uint16_t) + /* Original ID */ + sizeof(uint16_t) + /* Error */ + sizeof(uint16_t) + /* Other len */ + 6* sizeof(uint8_t); /* uint48_t in case of BADTIME RCODE */ +} + +size_t tsig_wire_actsize(const knot_rrset_t *tsig) +{ + return knot_dname_size(knot_rrset_owner(tsig)) + + sizeof(uint16_t) + /* TYPE */ + sizeof(uint16_t) + /* CLASS */ + sizeof(uint32_t) + /* TTL */ + sizeof(uint16_t) + /* RDLENGTH */ + knot_dname_size(tsig_rdata_alg_name(tsig)) + + 6 * sizeof(uint8_t) + /* Time signed */ + sizeof(uint16_t) + /* Fudge */ + sizeof(uint16_t) + /* MAC size */ + tsig_rdata_mac_length(tsig) + + sizeof(uint16_t) + /* Original ID */ + sizeof(uint16_t) + /* Error */ + sizeof(uint16_t) + /* Other len */ + tsig_rdata_other_data_length(tsig); +} + diff --git a/src/libknot/tsig.h b/src/libknot/tsig.h new file mode 100644 index 0000000..eafcfab --- /dev/null +++ b/src/libknot/tsig.h @@ -0,0 +1,145 @@ +/*! + * \file tsig.h + * + * \author Jan Kadlec <jan.kadlec@nic.cz> + * + * \brief TSIG manipulation. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_TSIG_H_ +#define _KNOT_TSIG_H_ + +#include <stdint.h> + +#include "rrset.h" +#include "util/utils.h" + +/* The assigned numbers should not begin with 0 - reserved for error. */ +enum tsig_algorithm { + KNOT_TSIG_ALG_NULL = 0, + KNOT_TSIG_ALG_GSS_TSIG = 128, /*!< \brief gss-tsig. */ + KNOT_TSIG_ALG_HMAC_MD5, /*!< \brief HMAC-MD5.SIG-ALG.REG.INT. */ + KNOT_TSIG_ALG_HMAC_SHA1, /*!< \brief hmac-sha1. */ + KNOT_TSIG_ALG_HMAC_SHA224, /*!< \brief hmac-sha224. */ + KNOT_TSIG_ALG_HMAC_SHA256, /*!< \brief hmac-sha256. */ + KNOT_TSIG_ALG_HMAC_SHA384, /*!< \brief hmac-sha384. */ + KNOT_TSIG_ALG_HMAC_SHA512 /*!< \brief hmac-sha512. */ +}; + +typedef enum tsig_algorithm tsig_algorithm_t; + +struct knot_key { + knot_dname_t *name; /*!< Key name. */ + tsig_algorithm_t algorithm; /*!< Key algorithm. */ + char *secret; /*!< Key data. */ + size_t secret_size; /*!< Key length. */ +}; + +typedef struct knot_key knot_key_t; + +/*!< \todo FIND ALG LENGTHS */ +enum tsig_algorithm_digest_length { + KNOT_TSIG_ALG_DIG_LENGTH_GSS_TSIG = 0, + KNOT_TSIG_ALG_DIG_LENGTH_HMAC_MD5 = 16, + KNOT_TSIG_ALG_DIG_LENGTH_SHA1 = 0, + KNOT_TSIG_ALG_DIG_LENGTH_SHA224 = 0, + KNOT_TSIG_ALG_DIG_LENGTH_SHA256 = 0, + KNOT_TSIG_ALG_DIG_LENGTH_SHA384 = 0, + KNOT_TSIG_ALG_DIG_LENGTH_SHA512 = 0 +}; + +enum tsig_consts { + KNOT_TSIG_ITEM_COUNT = 7, + KNOT_TSIG_VARIABLES_LENGTH = sizeof(uint16_t) // class + + sizeof(uint32_t) // ttl + + 6 // time signed + + sizeof(uint16_t) // fudge + + sizeof(uint16_t) // error + + sizeof(uint16_t),// other data length + KNOT_TSIG_TIMERS_LENGTH = sizeof(uint16_t) //fugde + + 6 // time signed +}; + +/*! TSIG errors are defined in util/error.h + * and present negative value of the TSIG error to + * comply with other parts of the library. + * + * KNOT_TSIG_EBADSIG = -16 + * KNOT_TSIG_EBADKEY = -17 + * KNOT_TSIG_EBADTIME = -18 + */ + +/*! + * \note Uses the given domain name, do not deallocate it! + */ +int tsig_rdata_set_alg_name(knot_rrset_t *tsig, knot_dname_t *alg_name); +int tsig_rdata_set_alg(knot_rrset_t *tsig, tsig_algorithm_t alg); +int tsig_rdata_set_time_signed(knot_rrset_t *tsig, uint64_t time); +int tsig_rdata_store_current_time(knot_rrset_t *tsig); +int tsig_rdata_set_fudge(knot_rrset_t *tsig, uint16_t fudge); +int tsig_rdata_set_mac(knot_rrset_t *tsig, uint16_t length, + const uint8_t *mac); +int tsig_rdata_set_orig_id(knot_rrset_t *tsig, uint16_t id); +int tsig_rdata_set_tsig_error(knot_rrset_t *tsig, uint16_t tsig_error); +int tsig_rdata_set_other_data(knot_rrset_t *tsig, uint16_t length, + const uint8_t *other_data); + +const knot_dname_t *tsig_rdata_alg_name(const knot_rrset_t *tsig); +tsig_algorithm_t tsig_rdata_alg(const knot_rrset_t *tsig); +uint64_t tsig_rdata_time_signed(const knot_rrset_t *tsig); +uint16_t tsig_rdata_fudge(const knot_rrset_t *tsig); +const uint8_t *tsig_rdata_mac(const knot_rrset_t *tsig); +size_t tsig_rdata_mac_length(const knot_rrset_t *tsig); +uint16_t tsig_rdata_orig_id(const knot_rrset_t *tsig); +uint16_t tsig_rdata_error(const knot_rrset_t *tsig); +const uint8_t *tsig_rdata_other_data(const knot_rrset_t *tsig); +uint16_t tsig_rdata_other_data_length(const knot_rrset_t *tsig); +size_t tsig_rdata_tsig_variables_length(const knot_rrset_t *tsig); + +size_t tsig_rdata_tsig_timers_length(); + +int tsig_alg_from_name(const knot_dname_t *name); + +/*! + * \brief Convert TSIG algorithm identifier to name. + * + * \param alg TSIG algorithm identifier. + * + * \retval TSIG algorithm string name. + * \retval Empty string if undefined. + */ +const char* tsig_alg_to_str(tsig_algorithm_t alg); + +uint16_t tsig_alg_digest_length(tsig_algorithm_t alg); + +/*! + * \brief Return TSIG RRSET maximum wire size for given algorithm. + * + * \param key Signing key descriptor. + * + * \return RRSET wire size. + */ +size_t tsig_wire_maxsize(const knot_key_t *key); +size_t tsig_wire_actsize(const knot_rrset_t *tsig); + +#endif /* _KNOT_TSIG_H_ */ + +/*! @} */ diff --git a/src/libknot/updates/changesets.c b/src/libknot/updates/changesets.c new file mode 100644 index 0000000..cf9e6a0 --- /dev/null +++ b/src/libknot/updates/changesets.c @@ -0,0 +1,296 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <stdlib.h> +#include <assert.h> + +#include "updates/changesets.h" + +#include "rrset.h" +#include "util/error.h" + +static const size_t KNOT_CHANGESET_COUNT = 5; +static const size_t KNOT_CHANGESET_STEP = 5; +static const size_t KNOT_CHANGESET_RRSET_COUNT = 5; +static const size_t KNOT_CHANGESET_RRSET_STEP = 5; + +/*----------------------------------------------------------------------------*/ + +static int knot_changeset_check_count(knot_rrset_t ***rrsets, size_t count, + size_t *allocated) +{ + /* Check if allocated is sufficient. */ + if (count <= *allocated) { + return KNOT_EOK; + } + + /* How many steps is needed to content count? */ + size_t extra = (count - *allocated) % KNOT_CHANGESET_RRSET_STEP; + extra = (extra + 1) * KNOT_CHANGESET_RRSET_STEP; + + /* Allocate new memory block. */ + const size_t item_len = sizeof(knot_rrset_t *); + const size_t new_count = *allocated + extra; + knot_rrset_t **rrsets_new = malloc(new_count * item_len); + if (rrsets_new == NULL) { + return KNOT_ENOMEM; + } + + /* Clear old memory block and copy old data. */ + memset(rrsets_new, 0, new_count * item_len); + memcpy(rrsets_new, *rrsets, (*allocated) * item_len); + + /* Replace old rrsets. */ + free(*rrsets); + *rrsets = rrsets_new; + *allocated = new_count; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_changeset_rrsets_match(const knot_rrset_t *rrset1, + const knot_rrset_t *rrset2) +{ + return knot_rrset_compare(rrset1, rrset2, KNOT_RRSET_COMPARE_HEADER) + && (knot_rrset_type(rrset1) != KNOT_RRTYPE_RRSIG + || knot_rdata_rrsig_type_covered( + knot_rrset_rdata(rrset1)) + == knot_rdata_rrsig_type_covered( + knot_rrset_rdata(rrset2))); +} + +/*----------------------------------------------------------------------------*/ + +int knot_changeset_allocate(knot_changesets_t **changesets) +{ + // create new changesets + *changesets = (knot_changesets_t *)(malloc(sizeof(knot_changesets_t))); + if (*changesets == NULL) { + return KNOT_ENOMEM; + } + + memset(*changesets, 0, sizeof(knot_changesets_t)); + + return knot_changesets_check_size(*changesets); +} + +/*----------------------------------------------------------------------------*/ + +int knot_changeset_add_rrset(knot_rrset_t ***rrsets, + size_t *count, size_t *allocated, + knot_rrset_t *rrset) +{ + int ret = knot_changeset_check_count(rrsets, *count + 1, allocated); + if (ret != KNOT_EOK) { + return ret; + } + + (*rrsets)[*count] = rrset; + *count = *count + 1; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_changeset_add_rr(knot_rrset_t ***rrsets, size_t *count, + size_t *allocated, knot_rrset_t *rr) +{ + // try to find the RRSet in the list of RRSets, but search backwards + // as it is probable that the last RRSet is the one to which the RR + // belongs + int i = *count - 1; + + while (i >= 0 && !knot_changeset_rrsets_match((*rrsets)[i], rr)) { + --i; + } + + if (i >= 0) { + // found RRSet to merge the new one into + if (knot_rrset_merge((void **)&(*rrsets)[i], + (void **)&rr) != KNOT_EOK) { + return KNOT_ERROR; + } + + // remove the RR + /*! \todo does this make sense? */ + knot_rrset_free(&rr); // used to be deep free with all 1's + + return KNOT_EOK; + } else { + return knot_changeset_add_rrset(rrsets, count, allocated, rr); + } +} + +/*----------------------------------------------------------------------------*/ + +int knot_changeset_add_new_rr(knot_changeset_t *changeset, + knot_rrset_t *rrset, + xfrin_changeset_part_t part) +{ + knot_rrset_t ***rrsets = NULL; + size_t *count = NULL; + size_t *allocated = NULL; + + switch (part) { + case XFRIN_CHANGESET_ADD: + rrsets = &changeset->add; + count = &changeset->add_count; + allocated = &changeset->add_allocated; + break; + case XFRIN_CHANGESET_REMOVE: + rrsets = &changeset->remove; + count = &changeset->remove_count; + allocated = &changeset->remove_allocated; + break; + default: + assert(0); + } + + assert(rrsets != NULL); + assert(count != NULL); + assert(allocated != NULL); + + int ret = knot_changeset_add_rr(rrsets, count, allocated, rrset); + if (ret != KNOT_EOK) { + return ret; + } + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +void knot_changeset_store_soa(knot_rrset_t **chg_soa, + uint32_t *chg_serial, knot_rrset_t *soa) +{ + *chg_soa = soa; + *chg_serial = knot_rdata_soa_serial(knot_rrset_rdata(soa)); +} + +/*----------------------------------------------------------------------------*/ + +int knot_changeset_add_soa(knot_changeset_t *changeset, knot_rrset_t *soa, + xfrin_changeset_part_t part) +{ + switch (part) { + case XFRIN_CHANGESET_ADD: + knot_changeset_store_soa(&changeset->soa_to, + &changeset->serial_to, soa); + break; + case XFRIN_CHANGESET_REMOVE: + knot_changeset_store_soa(&changeset->soa_from, + &changeset->serial_from, soa); + break; + default: + assert(0); + } + + /*! \todo Remove return value? */ + return KNOT_EOK; +} + +/*---------------------------------------------------------------------------*/ + +int knot_changesets_check_size(knot_changesets_t *changesets) +{ + /* Check if allocated is sufficient. */ + if (changesets->count <= changesets->allocated) { + return KNOT_EOK; + } + + /* How many steps is needed to content count? */ + size_t extra = (changesets->count - changesets->allocated) % KNOT_CHANGESET_STEP; + extra = (extra + 1) * KNOT_CHANGESET_STEP; + + /* Allocate new memory block. */ + const size_t item_len = sizeof(knot_changeset_t); + size_t new_count = (changesets->allocated + extra); + knot_changeset_t *sets = malloc(new_count * item_len); + if (sets == NULL) { + return KNOT_ENOMEM; + } + + /* Clear old memory block and copy old data. */ + memset(sets, 0, new_count * item_len); + memcpy(sets, changesets->sets, changesets->allocated * item_len); + + /* Replace old changesets. */ + free(changesets->sets); + changesets->sets = sets; + changesets->allocated = new_count; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +void knot_free_changeset(knot_changeset_t **changeset) +{ + /* XXX XXX investigate wrong frees. */ + assert((*changeset)->add_allocated >= (*changeset)->add_count); + assert((*changeset)->remove_allocated >= (*changeset)->remove_count); + assert((*changeset)->allocated >= (*changeset)->size); + + int j; + for (j = 0; j < (*changeset)->add_count; ++j) { + knot_rrset_deep_free(&(*changeset)->add[j], 1, 1, 1); + } + free((*changeset)->add); + + for (j = 0; j < (*changeset)->remove_count; ++j) { + knot_rrset_deep_free(&(*changeset)->remove[j], 1, 1, 1); + } + free((*changeset)->remove); + + knot_rrset_deep_free(&(*changeset)->soa_from, 1, 1, 1); + knot_rrset_deep_free(&(*changeset)->soa_to, 1, 1, 1); + + free((*changeset)->data); + + + *changeset = NULL; +} + +/*----------------------------------------------------------------------------*/ + +void knot_free_changesets(knot_changesets_t **changesets) +{ + if (changesets == NULL || *changesets == NULL) { + return; + } + + assert((*changesets)->allocated >= (*changesets)->count); + + for (int i = 0; i < (*changesets)->count; ++i) { + knot_changeset_t *ch = &(*changesets)->sets[i]; + knot_free_changeset(&ch); + } + + free((*changesets)->sets); + + knot_rrset_deep_free(&(*changesets)->first_soa, 1, 1, 1); + + free(*changesets); + *changesets = NULL; +} + +/*---------------------------------------------------------------------------*/ + + diff --git a/src/libknot/updates/changesets.h b/src/libknot/updates/changesets.h new file mode 100644 index 0000000..e8d5e39 --- /dev/null +++ b/src/libknot/updates/changesets.h @@ -0,0 +1,102 @@ +/*! + * \file changesets.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Structure for representing IXFR/DDNS changeset and its API. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_CHANGESETS_H_ +#define _KNOT_CHANGESETS_H_ + +#include "rrset.h" + +/*! \todo Changeset must be serializable/deserializable, so + * all data and pointers have to be changeset-exclusive, + * or more advanced structure serialization scheme has to be + * implemented. + * + * \todo Preallocation of space for changeset. + */ +typedef struct { + knot_rrset_t *soa_from; + knot_rrset_t **remove; + size_t remove_count; + size_t remove_allocated; + + knot_rrset_t *soa_to; + knot_rrset_t **add; + size_t add_count; + size_t add_allocated; + + uint8_t *data; + size_t size; + size_t allocated; + uint32_t serial_from; + uint32_t serial_to; +} knot_changeset_t; + +/*----------------------------------------------------------------------------*/ + +typedef struct { + knot_changeset_t *sets; + size_t count; + size_t allocated; + knot_rrset_t *first_soa; +} knot_changesets_t; + +/*----------------------------------------------------------------------------*/ + +typedef enum { + XFRIN_CHANGESET_ADD, + XFRIN_CHANGESET_REMOVE +} xfrin_changeset_part_t; + +/*----------------------------------------------------------------------------*/ + +int knot_changeset_allocate(knot_changesets_t **changesets); + +int knot_changeset_add_rrset(knot_rrset_t ***rrsets, + size_t *count, size_t *allocated, + knot_rrset_t *rrset); + +int knot_changeset_add_rr(knot_rrset_t ***rrsets, size_t *count, + size_t *allocated, knot_rrset_t *rr); + +int knot_changeset_add_new_rr(knot_changeset_t *changeset, + knot_rrset_t *rrset, + xfrin_changeset_part_t part); + +void knot_changeset_store_soa(knot_rrset_t **chg_soa, + uint32_t *chg_serial, knot_rrset_t *soa); + +int knot_changeset_add_soa(knot_changeset_t *changeset, knot_rrset_t *soa, + xfrin_changeset_part_t part); + +int knot_changesets_check_size(knot_changesets_t *changesets); + +void knot_free_changeset(knot_changeset_t **changeset); + +void knot_free_changesets(knot_changesets_t **changesets); + +#endif /* _KNOT_CHANGESETS_H_ */ + +/*! @} */ diff --git a/src/libknot/updates/ddns.c b/src/libknot/updates/ddns.c new file mode 100644 index 0000000..4c6ab7b --- /dev/null +++ b/src/libknot/updates/ddns.c @@ -0,0 +1,638 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> + +#include "updates/ddns.h" +#include "updates/changesets.h" +#include "util/debug.h" +#include "packet/packet.h" +#include "util/error.h" +#include "consts.h" + +/*----------------------------------------------------------------------------*/ +// Copied from XFR - maybe extract somewhere else +static int knot_ddns_prereq_check_rrsets(knot_rrset_t ***rrsets, + size_t *count, size_t *allocated) +{ + int new_count = 0; + if (*count == *allocated) { + new_count = *allocated * 2; + } + + knot_rrset_t **rrsets_new = + (knot_rrset_t **)calloc(new_count, sizeof(knot_rrset_t *)); + if (rrsets_new == NULL) { + return KNOT_ENOMEM; + } + + memcpy(rrsets_new, *rrsets, *count); + *rrsets = rrsets_new; + *allocated = new_count; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_prereq_check_dnames(knot_dname_t ***dnames, + size_t *count, size_t *allocated) +{ + int new_count = 0; + if (*count == *allocated) { + new_count = *allocated * 2; + } + + knot_dname_t **dnames_new = + (knot_dname_t **)calloc(new_count, sizeof(knot_dname_t *)); + if (dnames_new == NULL) { + return KNOT_ENOMEM; + } + + memcpy(dnames_new, *dnames, *count); + *dnames = dnames_new; + *allocated = new_count; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_add_prereq_rrset(const knot_rrset_t *rrset, + knot_rrset_t ***rrsets, + size_t *count, size_t *allocd) +{ + // check if such RRSet is not already there and merge if needed + int ret; + for (int i = 0; i < *count; ++i) { + if (knot_rrset_compare(rrset, (*rrsets)[i], + KNOT_RRSET_COMPARE_HEADER) == 0) { + ret = knot_rrset_merge((void **)&((*rrsets)[i]), + (void **)&rrset); + if (ret != KNOT_EOK) { + return ret; + } else { + return KNOT_EOK; + } + } + } + + // if we are here, the RRSet was not found + ret = knot_ddns_prereq_check_rrsets(rrsets, count, allocd); + if (ret != KNOT_EOK) { + return ret; + } + + knot_rrset_t *new_rrset = NULL; + ret = knot_rrset_deep_copy(rrset, &new_rrset); + if (ret != KNOT_EOK) { + return ret; + } + + (*rrsets)[(*count)++] = new_rrset; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_add_prereq_dname(const knot_dname_t *dname, + knot_dname_t ***dnames, + size_t *count, size_t *allocd) +{ + // we do not have to check if the name is not already there + // if it is, we will just check it twice in the zone + + int ret = knot_ddns_prereq_check_dnames(dnames, count, allocd); + if (ret != KNOT_EOK) { + return ret; + } + + knot_dname_t *dname_new = knot_dname_deep_copy(dname); + if (dname_new == NULL) { + return KNOT_ENOMEM; + } + + (*dnames)[(*count)++] = dname_new; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_add_prereq(knot_ddns_prereq_t *prereqs, + const knot_rrset_t *rrset, uint16_t qclass) +{ + assert(prereqs != NULL); + assert(rrset != NULL); + + if (knot_rrset_ttl(rrset) != 0) { + return KNOT_EMALF; + } + + int ret; + + if (knot_rrset_class(rrset) == KNOT_CLASS_ANY) { + if (knot_rrset_rdata(rrset) != NULL) { + return KNOT_EMALF; + } + if (knot_rrset_type(rrset) == KNOT_RRTYPE_ANY) { + ret = knot_ddns_add_prereq_dname( + knot_rrset_owner(rrset), &prereqs->in_use, + &prereqs->in_use_count, + &prereqs->in_use_allocd); + } else { + ret = knot_ddns_add_prereq_rrset(rrset, + &prereqs->exist, + &prereqs->exist_count, + &prereqs->exist_allocd); + } + } else if (knot_rrset_class(rrset) == KNOT_CLASS_NONE) { + if (knot_rrset_rdata(rrset) != NULL) { + return KNOT_EMALF; + } + if (knot_rrset_type(rrset) == KNOT_RRTYPE_ANY) { + ret = knot_ddns_add_prereq_dname( + knot_rrset_owner(rrset), &prereqs->not_in_use, + &prereqs->not_in_use_count, + &prereqs->not_in_use_allocd); + } else { + ret = knot_ddns_add_prereq_rrset(rrset, + &prereqs->not_exist, + &prereqs->not_exist_count, + &prereqs->not_exist_allocd); + } + } else if (knot_rrset_class(rrset) == qclass) { + ret = knot_ddns_add_prereq_rrset(rrset, + &prereqs->exist_full, + &prereqs->exist_full_count, + &prereqs->exist_full_allocd); + } else { + return KNOT_EMALF; + } + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_add_update(knot_changeset_t *changeset, + const knot_rrset_t *rrset, uint16_t qclass) +{ + assert(changeset != NULL); + assert(rrset != NULL); + + int ret; + + // create a copy of the RRSet + /*! \todo If the packet was not parsed all at once, we could save this + * copy. + */ + knot_rrset_t *rrset_copy; + ret = knot_rrset_deep_copy(rrset, &rrset_copy); + if (ret != KNOT_EOK) { + return ret; + } + + /*! \todo What about the SOAs? */ + + if (knot_rrset_class(rrset) == qclass) { + // this RRSet should be added to the zone + ret = knot_changeset_add_rr(&changeset->add, + &changeset->add_count, + &changeset->add_allocated, + rrset_copy); + } else { + // this RRSet marks removal of something from zone + // what should be removed is distinguished when applying + ret = knot_changeset_add_rr(&changeset->remove, + &changeset->remove_count, + &changeset->remove_allocated, + rrset_copy); + } + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_check_exist(const knot_zone_contents_t *zone, + const knot_rrset_t *rrset, uint8_t *rcode) +{ + assert(zone != NULL); + assert(rrset != NULL); + assert(rcode != NULL); + assert(knot_rrset_rdata(rrset) == NULL); + assert(knot_rrset_type(rrset) != KNOT_RRTYPE_ANY); + assert(knot_rrset_ttl(rrset) == 0); + assert(knot_rrset_class(rrset) == KNOT_CLASS_ANY); + + if (!knot_dname_is_subdomain(knot_rrset_owner(rrset), + knot_node_owner(knot_zone_contents_apex(zone)))) { + *rcode = KNOT_RCODE_NOTZONE; + return KNOT_EBADZONE; + } + + const knot_node_t *node; + node = knot_zone_contents_find_node(zone, knot_rrset_owner(rrset)); + if (node == NULL) { + *rcode = KNOT_RCODE_NXRRSET; + return KNOT_ENONODE; + } else if (knot_node_rrset(node, knot_rrset_type(rrset)) == NULL) { + *rcode = KNOT_RCODE_NXRRSET; + return KNOT_ENORRSET; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_check_exist_full(const knot_zone_contents_t *zone, + const knot_rrset_t *rrset, uint8_t *rcode) +{ + assert(zone != NULL); + assert(rrset != NULL); + assert(rcode != NULL); + assert(knot_rrset_rdata(rrset) == NULL); + assert(knot_rrset_type(rrset) != KNOT_RRTYPE_ANY); + assert(knot_rrset_ttl(rrset) == 0); + assert(knot_rrset_class(rrset) == KNOT_CLASS_ANY); + + if (!knot_dname_is_subdomain(knot_rrset_owner(rrset), + knot_node_owner(knot_zone_contents_apex(zone)))) { + *rcode = KNOT_RCODE_NOTZONE; + return KNOT_EBADZONE; + } + + const knot_node_t *node; + const knot_rrset_t *found; + + node = knot_zone_contents_find_node(zone, knot_rrset_owner(rrset)); + if (node == NULL) { + *rcode = KNOT_RCODE_NXRRSET; + return KNOT_EPREREQ; + } else if ((found = knot_node_rrset(node, knot_rrset_type(rrset))) + == NULL) { + *rcode = KNOT_RCODE_NXRRSET; + return KNOT_EPREREQ; + } else { + // do not have to compare the header, it is already done + assert(knot_rrset_type(found) == knot_rrset_type(rrset)); + assert(knot_dname_compare(knot_rrset_owner(found), + knot_rrset_owner(rrset)) == 0); + if (knot_rrset_compare_rdata(found, rrset) <= 0) { + *rcode = KNOT_RCODE_NXRRSET; + return KNOT_EPREREQ; + } + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_check_not_exist(const knot_zone_contents_t *zone, + const knot_rrset_t *rrset, uint8_t *rcode) +{ + assert(zone != NULL); + assert(rrset != NULL); + assert(rcode != NULL); + assert(knot_rrset_rdata(rrset) == NULL); + assert(knot_rrset_type(rrset) != KNOT_RRTYPE_ANY); + assert(knot_rrset_ttl(rrset) == 0); + assert(knot_rrset_class(rrset) == KNOT_CLASS_NONE); + + if (!knot_dname_is_subdomain(knot_rrset_owner(rrset), + knot_node_owner(knot_zone_contents_apex(zone)))) { + *rcode = KNOT_RCODE_NOTZONE; + return KNOT_EBADZONE; + } + + const knot_node_t *node; + const knot_rrset_t *found; + + node = knot_zone_contents_find_node(zone, knot_rrset_owner(rrset)); + if (node == NULL) { + return KNOT_EOK; + } else if ((found = knot_node_rrset(node, knot_rrset_type(rrset))) + == NULL) { + return KNOT_EOK; + } else { + // do not have to compare the header, it is already done + assert(knot_rrset_type(found) == knot_rrset_type(rrset)); + assert(knot_dname_compare(knot_rrset_owner(found), + knot_rrset_owner(rrset)) == 0); + if (knot_rrset_compare_rdata(found, rrset) <= 0) { + return KNOT_EOK; + } + } + + *rcode = KNOT_RCODE_YXRRSET; + return KNOT_EPREREQ; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_check_in_use(const knot_zone_contents_t *zone, + const knot_dname_t *dname, uint8_t *rcode) +{ + assert(zone != NULL); + assert(dname != NULL); + assert(rcode != NULL); + + if (!knot_dname_is_subdomain(dname, + knot_node_owner(knot_zone_contents_apex(zone)))) { + *rcode = KNOT_RCODE_NOTZONE; + return KNOT_EBADZONE; + } + + const knot_node_t *node; + + node = knot_zone_contents_find_node(zone, dname); + if (node == NULL) { + *rcode = KNOT_RCODE_NXDOMAIN; + return KNOT_EPREREQ; + } else if (knot_node_rrset_count(node) == 0) { + *rcode = KNOT_RCODE_NXDOMAIN; + return KNOT_EPREREQ; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_check_not_in_use(const knot_zone_contents_t *zone, + const knot_dname_t *dname, uint8_t *rcode) +{ + assert(zone != NULL); + assert(dname != NULL); + assert(rcode != NULL); + + if (!knot_dname_is_subdomain(dname, + knot_node_owner(knot_zone_contents_apex(zone)))) { + *rcode = KNOT_RCODE_NOTZONE; + return KNOT_EBADZONE; + } + + const knot_node_t *node; + + node = knot_zone_contents_find_node(zone, dname); + if (node == NULL) { + return KNOT_EOK; + } else if (knot_node_rrset_count(node) == 0) { + return KNOT_EOK; + } + + *rcode = KNOT_RCODE_YXDOMAIN; + return KNOT_EPREREQ; +} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +int knot_ddns_check_zone(const knot_zone_t *zone, knot_packet_t *query, + uint8_t *rcode) +{ + if (zone == NULL || query == NULL || rcode == NULL) { + return KNOT_EBADARG; + } + + if (knot_packet_qtype(query) != KNOT_RRTYPE_SOA) { + *rcode = KNOT_RCODE_FORMERR; + return KNOT_EMALF; + } + + if(!knot_zone_contents(zone)) { + *rcode = KNOT_RCODE_REFUSED; + return KNOT_ENOZONE; + } + + // 1) check if the zone is master or slave + if (!knot_zone_is_master(zone)) { + return KNOT_EBADZONE; + } + + // 2) check zone CLASS + if (knot_zone_contents_class(knot_zone_contents(zone)) != + knot_packet_qclass(query)) { + *rcode = KNOT_RCODE_NOTAUTH; + return KNOT_ENOZONE; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ddns_process_prereqs(knot_packet_t *query, + knot_ddns_prereq_t **prereqs, uint8_t *rcode) +{ + /*! \todo Consider not parsing the whole packet at once, but + * parsing one RR at a time - could save some memory and time. + */ + + if (query == NULL || prereqs == NULL || rcode == NULL) { + return KNOT_EBADARG; + } + + // allocate space for the prerequisities + *prereqs = (knot_ddns_prereq_t *)calloc(1, sizeof(knot_ddns_prereq_t)); + CHECK_ALLOC_LOG(*prereqs, KNOT_ENOMEM); + + int ret; + + for (int i = 0; i < knot_packet_answer_rrset_count(query); ++i) { + // we must copy the RRSets, because all those stored in the + // packet will be destroyed + ret = knot_ddns_add_prereq(*prereqs, + knot_packet_answer_rrset(query, i), + knot_packet_qclass(query)); + if (ret != KNOT_EOK) { + dbg_ddns("Failed to add prerequisity RRSet:%s\n", + knot_strerror(ret)); + *rcode = (ret == KNOT_EMALF) ? KNOT_RCODE_FORMERR + : KNOT_RCODE_SERVFAIL; + knot_ddns_prereqs_free(prereqs); + return ret; + } + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ddns_check_prereqs(const knot_zone_contents_t *zone, + knot_ddns_prereq_t **prereqs, uint8_t *rcode) +{ + int i, ret; + + for (i = 0; i < (*prereqs)->exist_count; ++i) { + ret = knot_ddns_check_exist(zone, (*prereqs)->exist[i], rcode); + if (ret != KNOT_EOK) { + return ret; + } + } + + for (i = 0; i < (*prereqs)->exist_full_count; ++i) { + ret = knot_ddns_check_exist_full(zone, + (*prereqs)->exist_full[i], + rcode); + if (ret != KNOT_EOK) { + return ret; + } + } + + for (i = 0; i < (*prereqs)->not_exist_count; ++i) { + ret = knot_ddns_check_not_exist(zone, (*prereqs)->not_exist[i], + rcode); + if (ret != KNOT_EOK) { + return ret; + } + } + + for (i = 0; i < (*prereqs)->in_use_count; ++i) { + ret = knot_ddns_check_in_use(zone, (*prereqs)->in_use[i], + rcode); + if (ret != KNOT_EOK) { + return ret; + } + } + + for (i = 0; i < (*prereqs)->not_in_use_count; ++i) { + ret = knot_ddns_check_not_in_use(zone, + (*prereqs)->not_in_use[i], + rcode); + if (ret != KNOT_EOK) { + return ret; + } + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_ddns_check_update(const knot_rrset_t *rrset, + const knot_packet_t *query, uint8_t *rcode) +{ + if (!knot_dname_is_subdomain(knot_rrset_owner(rrset), + knot_packet_qname(query))) { + *rcode = KNOT_RCODE_NOTZONE; + return KNOT_EBADZONE; + } + + if (knot_rrset_class(rrset) == knot_packet_qclass(query)) { + if (knot_rrtype_is_metatype(knot_rrset_type(rrset))) { + *rcode = KNOT_RCODE_FORMERR; + return KNOT_EMALF; + } + } else if (knot_rrset_class(rrset) == KNOT_CLASS_ANY) { + if (knot_rrset_rdata(rrset) != NULL + || (knot_rrtype_is_metatype(knot_rrset_type(rrset)) + && knot_rrset_type(rrset) != KNOT_RRTYPE_ANY)) { + *rcode = KNOT_RCODE_FORMERR; + return KNOT_EMALF; + } + } else if (knot_rrset_class(rrset) == KNOT_CLASS_NONE) { + if (knot_rrset_ttl(rrset) != 0 + || knot_rrtype_is_metatype(knot_rrset_type(rrset))) { + *rcode = KNOT_RCODE_FORMERR; + return KNOT_EMALF; + } + } else { + *rcode = KNOT_RCODE_FORMERR; + return KNOT_EMALF; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_ddns_process_update(knot_packet_t *query, + knot_changeset_t **changeset, uint8_t *rcode) +{ + // just put all RRSets from query's Authority section + // it will be distinguished when applying to the zone + + if (query == NULL || changeset == NULL || rcode == NULL) { + return KNOT_EBADARG; + } + + *changeset = (knot_changeset_t *)calloc(1, sizeof(knot_changeset_t)); + CHECK_ALLOC_LOG(*changeset, KNOT_ENOMEM); + + int ret; + + for (int i = 0; i < knot_packet_authority_rrset_count(query); ++i) { + + const knot_rrset_t *rrset = + knot_packet_authority_rrset(query, i); + + ret = knot_ddns_check_update(rrset, query, rcode); + if (ret != KNOT_EOK) { + return ret; + } + + ret = knot_ddns_add_update(*changeset, rrset, + knot_packet_qclass(query)); + + if (ret != KNOT_EOK) { + dbg_ddns("Failed to add update RRSet:%s\n", + knot_strerror(ret)); + *rcode = (ret == KNOT_EMALF) ? KNOT_RCODE_FORMERR + : KNOT_RCODE_SERVFAIL; + knot_free_changeset(changeset); + return ret; + } + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +void knot_ddns_prereqs_free(knot_ddns_prereq_t **prereq) +{ + int i; + + for (i = 0; i < (*prereq)->exist_count; ++i) { + knot_rrset_deep_free(&(*prereq)->exist[i], 1, 1, 1); + } + + for (i = 0; i < (*prereq)->exist_full_count; ++i) { + knot_rrset_deep_free(&(*prereq)->exist_full[i], 1, 1, 1); + } + + for (i = 0; i < (*prereq)->not_exist_count; ++i) { + knot_rrset_deep_free(&(*prereq)->not_exist[i], 1, 1, 1); + } + + for (i = 0; i < (*prereq)->in_use_count; ++i) { + knot_dname_free(&(*prereq)->in_use[i]); + } + + for (i = 0; i < (*prereq)->not_in_use_count; ++i) { + knot_dname_free(&(*prereq)->not_in_use[i]); + } + + free(*prereq); + *prereq = NULL; +} diff --git a/src/libknot/updates/ddns.h b/src/libknot/updates/ddns.h new file mode 100644 index 0000000..dceebed --- /dev/null +++ b/src/libknot/updates/ddns.h @@ -0,0 +1,74 @@ +/*! + * \file ddns.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Dynamic updates processing. + * + * \addtogroup query_processing + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_DDNS_H_ +#define _KNOT_DDNS_H_ + +#include "updates/changesets.h" +#include "zone/zone.h" +#include "packet/packet.h" +#include "rrset.h" +#include "dname.h" + +typedef struct knot_ddns_prereq_t { + knot_rrset_t **exist; + size_t exist_count; + size_t exist_allocd; + + knot_rrset_t **exist_full; + size_t exist_full_count; + size_t exist_full_allocd; + + knot_rrset_t **not_exist; + size_t not_exist_count; + size_t not_exist_allocd; + + knot_dname_t **in_use; + size_t in_use_count; + size_t in_use_allocd; + + knot_dname_t **not_in_use; + size_t not_in_use_count; + size_t not_in_use_allocd; +} knot_ddns_prereq_t; + +int knot_ddns_check_zone(const knot_zone_t *zone, knot_packet_t *query, + uint8_t *rcode); + +int knot_ddns_process_prereqs(knot_packet_t *query, + knot_ddns_prereq_t **prereqs, uint8_t *rcode); + +int knot_ddns_check_prereqs(const knot_zone_contents_t *zone, + knot_ddns_prereq_t **prereqs, uint8_t *rcode); + +int knot_ddns_process_update(knot_packet_t *query, + knot_changeset_t **changeset, uint8_t *rcode); + +void knot_ddns_prereqs_free(knot_ddns_prereq_t **prereq); + +#endif /* _KNOT_DDNS_H_ */ + +/*! @} */ diff --git a/src/libknot/updates/xfr-in.c b/src/libknot/updates/xfr-in.c new file mode 100644 index 0000000..51be430 --- /dev/null +++ b/src/libknot/updates/xfr-in.c @@ -0,0 +1,3013 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <urcu.h> + +#include "updates/xfr-in.h" + +#include "nameserver/name-server.h" +#include "util/wire.h" +#include "util/debug.h" +// #include "knot/zone/zone-dump.h" +// #include "knot/zone/zone-load.h" +#include "packet/packet.h" +#include "dname.h" +#include "zone/zone.h" +#include "packet/query.h" +#include "packet/response.h" +#include "util/error.h" +#include "updates/changesets.h" +#include "tsig.h" +#include "tsig-op.h" + +/*----------------------------------------------------------------------------*/ +/* Non-API functions */ +/*----------------------------------------------------------------------------*/ + +static int xfrin_create_query(knot_dname_t *qname, uint16_t qtype, + uint16_t qclass, knot_ns_xfr_t *xfr, size_t *size, + const knot_rrset_t *soa, int use_tsig) +{ + knot_packet_t *pkt = knot_packet_new(KNOT_PACKET_PREALLOC_QUERY); + CHECK_ALLOC_LOG(pkt, KNOT_ENOMEM); + + /*! \todo Get rid of the numeric constant. */ + int rc = knot_packet_set_max_size(pkt, 512); + if (rc != KNOT_EOK) { + knot_packet_free(&pkt); + return KNOT_ERROR; + } + + rc = knot_query_init(pkt); + if (rc != KNOT_EOK) { + knot_packet_free(&pkt); + return KNOT_ERROR; + } + + knot_question_t question; + + /* Retain qname until the question is freed. */ + knot_dname_retain(qname); + + /* Set random query ID. */ + knot_packet_set_random_id(pkt); + knot_wire_set_id(pkt->wireformat, pkt->header.id); + + // this is ugly!! + question.qname = (knot_dname_t *)qname; + question.qtype = qtype; + question.qclass = qclass; + + rc = knot_query_set_question(pkt, &question); + if (rc != KNOT_EOK) { + knot_dname_release(question.qname); + knot_packet_free(&pkt); + return KNOT_ERROR; + } + + /* Reserve space for TSIG. */ + if (use_tsig && xfr->tsig_key) { + dbg_xfrin_detail("xfrin: setting packet TSIG size to %zu\n", + xfr->tsig_size); + knot_packet_set_tsig_size(pkt, xfr->tsig_size); + } + + /* Add SOA RR to authority section for IXFR. */ + if (qtype == KNOT_RRTYPE_IXFR && soa) { + knot_query_add_rrset_authority(pkt, soa); + } + + /*! \todo OPT RR ?? */ + + uint8_t *wire = NULL; + size_t wire_size = 0; + rc = knot_packet_to_wire(pkt, &wire, &wire_size); + if (rc != KNOT_EOK) { + dbg_xfrin("Failed to write packet to wire.\n"); + knot_dname_release(question.qname); + knot_packet_free(&pkt); + return KNOT_ERROR; + } + + if (wire_size > *size) { + dbg_xfrin("Not enough space provided for the wire " + "format of the query.\n"); + knot_packet_free(&pkt); + return KNOT_ESPACE; + } + + // wire format created, sign it with TSIG if required + if (use_tsig && xfr->tsig_key) { + char *name = knot_dname_to_str(xfr->tsig_key->name); + dbg_xfrin_detail("Signing XFR query with key (name %s): \n", + name); + free(name); + dbg_xfrin_hex_detail(xfr->tsig_key->secret, + xfr->tsig_key->secret_size); + + xfr->digest_size = xfr->digest_max_size; + rc = knot_tsig_sign(wire, &wire_size, *size, NULL, 0, + xfr->digest, &xfr->digest_size, xfr->tsig_key); + if (rc != KNOT_EOK) { + /*! \todo [TSIG] Handle TSIG errors. */ + knot_packet_free(&pkt); + return rc; + } + + dbg_xfrin_detail("Signed XFR query, new wire size: %zu, digest:" + "\n", wire_size); + dbg_xfrin_hex_detail((const char*)xfr->digest, xfr->digest_size); + } + + memcpy(xfr->wire, wire, wire_size); + *size = wire_size; + + dbg_xfrin("Created query of size %zu.\n", *size); + knot_packet_dump(pkt); + + knot_packet_free(&pkt); + + /* Release qname. */ + knot_dname_release(question.qname); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +int xfrin_create_soa_query(knot_dname_t *owner, knot_ns_xfr_t *xfr, + size_t *size) +{ + /*! \todo [TSIG] Should TSIG apply for SOA query too? */ + return xfrin_create_query(owner, KNOT_RRTYPE_SOA, + KNOT_CLASS_IN, xfr, size, 0, 0); +} + +/*----------------------------------------------------------------------------*/ + +int xfrin_transfer_needed(const knot_zone_contents_t *zone, + knot_packet_t *soa_response) +{ + // first, parse the rest of the packet + assert(!knot_packet_is_query(soa_response)); + dbg_xfrin("Response - parsed: %zu, total wire size: %zu\n", + soa_response->parsed, soa_response->size); + int ret; + + if (soa_response->parsed < soa_response->size) { + ret = knot_packet_parse_rest(soa_response); + if (ret != KNOT_EOK) { + return KNOT_EMALF; + } + } + + /* + * Retrieve the local Serial + */ + const knot_rrset_t *soa_rrset = + knot_node_rrset(knot_zone_contents_apex(zone), + KNOT_RRTYPE_SOA); + if (soa_rrset == NULL) { + char *name = knot_dname_to_str(knot_node_owner( + knot_zone_contents_apex(zone))); + dbg_xfrin("SOA RRSet missing in the zone %s!\n", name); + free(name); + return KNOT_ERROR; + } + + int64_t local_serial = knot_rdata_soa_serial( + knot_rrset_rdata(soa_rrset)); + if (local_serial < 0) { +dbg_xfrin_exec( + char *name = knot_dname_to_str(knot_rrset_owner(soa_rrset)); + dbg_xfrin("Malformed data in SOA of zone %s\n", name); + free(name); +); + return KNOT_EMALF; // maybe some other error + } + + /* + * Retrieve the remote Serial + */ + // the SOA should be the first (and only) RRSet in the response + soa_rrset = knot_packet_answer_rrset(soa_response, 0); + if (soa_rrset == NULL + || knot_rrset_type(soa_rrset) != KNOT_RRTYPE_SOA) { + return KNOT_EMALF; + } + + int64_t remote_serial = knot_rdata_soa_serial( + knot_rrset_rdata(soa_rrset)); + if (remote_serial < 0) { + return KNOT_EMALF; // maybe some other error + } + + return (ns_serial_compare(local_serial, remote_serial) < 0); +} + +/*----------------------------------------------------------------------------*/ + +int xfrin_create_axfr_query(knot_dname_t *owner, knot_ns_xfr_t *xfr, + size_t *size, int use_tsig) +{ + return xfrin_create_query(owner, KNOT_RRTYPE_AXFR, + KNOT_CLASS_IN, xfr, size, 0, use_tsig); +} + +/*----------------------------------------------------------------------------*/ + +int xfrin_create_ixfr_query(const knot_zone_contents_t *zone, + knot_ns_xfr_t *xfr, size_t *size, int use_tsig) +{ + /*! + * \todo Implement properly. + */ + knot_node_t *apex = knot_zone_contents_get_apex(zone); + const knot_rrset_t *soa = knot_node_rrset(apex, KNOT_RRTYPE_SOA); + + return xfrin_create_query(knot_node_get_owner(apex), KNOT_RRTYPE_IXFR, + KNOT_CLASS_IN, xfr, size, soa, use_tsig); +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_add_orphan_rrsig(xfrin_orphan_rrsig_t *rrsigs, + knot_rrset_t *rr) +{ + // try to find similar RRSIGs (check owner and type covered) in the list + assert(knot_rrset_type(rr) == KNOT_RRTYPE_RRSIG); + + int ret = 0; + xfrin_orphan_rrsig_t **last = &rrsigs; + while (*last != NULL) { + // check if the RRSIG is not similar to the one we want to add + assert((*last)->rrsig != NULL); + if (knot_rrset_compare((*last)->rrsig, rr, + KNOT_RRSET_COMPARE_HEADER) == 1 + && knot_rdata_rrsig_type_covered(knot_rrset_rdata( + (*last)->rrsig)) + == knot_rdata_rrsig_type_covered(knot_rrset_rdata(rr))) { + ret = knot_rrset_merge((void **)&(*last)->rrsig, + (void **)&rr); + if (ret != KNOT_EOK) { + return ret; + } else { + return 1; + } + } + last = &((*last)->next); + } + + assert(*last == NULL); + // we did not find the right RRSIGs, add to the end + *last = (xfrin_orphan_rrsig_t *)malloc(sizeof(xfrin_orphan_rrsig_t)); + CHECK_ALLOC_LOG(*last, KNOT_ENOMEM); + + (*last)->rrsig = rr; + (*last)->next = NULL; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_process_orphan_rrsigs(knot_zone_contents_t *zone, + xfrin_orphan_rrsig_t *rrsigs) +{ + xfrin_orphan_rrsig_t **last = &rrsigs; + int ret = 0; + while (*last != NULL) { + knot_rrset_t *rrset = NULL; + knot_node_t *node = NULL; + ret = knot_zone_contents_add_rrsigs(zone, (*last)->rrsig, + &rrset, &node, + KNOT_RRSET_DUPL_MERGE, 1); + if (ret > 0) { + knot_rrset_free(&(*last)->rrsig); + } else if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add orphan RRSIG to zone.\n"); + return ret; + } else { + (*last)->rrsig = NULL; + } + + last = &((*last)->next); + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static void xfrin_free_orphan_rrsigs(xfrin_orphan_rrsig_t **rrsigs) +{ + xfrin_orphan_rrsig_t *r = *rrsigs; + while (r != NULL) { + xfrin_orphan_rrsig_t *prev = r; + r = r->next; + free(prev); + } + + *rrsigs = NULL; +} + +/*----------------------------------------------------------------------------*/ +/*! \note [TSIG] */ +static int xfrin_check_tsig(knot_packet_t *packet, knot_ns_xfr_t *xfr, + int tsig_req) +{ + assert(packet != NULL); + assert(xfr != NULL); + + dbg_xfrin_verb("xfrin_check_tsig(): packet nr: %d, required: %d\n", + xfr->packet_nr, tsig_req); + + /* + * If we are expecting it (i.e. xfr->prev_digest_size > 0) + * a) it should be there (first, last or each 100th packet) and it + * is not + * Then we should discard the changes and close the connection. + * b) it should be there and it is or it may not be there (other + * packets) and it is + * We validate the TSIG and reset packet number counting and + * data aggregation. + * + * If we are not expecting it (i.e. xfr->prev_digest_size <= 0) and + * it is there => it should probably be considered an error + */ + knot_rrset_t *tsig = NULL; + int ret = knot_packet_parse_next_rr_additional(packet, &tsig); + if (ret != KNOT_EOK) { + return ret; + } + + if (xfr->tsig_key) { + if (tsig_req && tsig == NULL) { + // TSIG missing!! + return KNOT_EMALF; + } else if (tsig != NULL) { + // TSIG there, either required or not, process + if (xfr->packet_nr == 0) { + ret = knot_tsig_client_check(tsig, + xfr->wire, xfr->wire_size, + xfr->digest, xfr->digest_size, + xfr->tsig_key); + } else { + ret = knot_tsig_client_check_next(tsig, + xfr->wire, xfr->wire_size, + xfr->digest, xfr->digest_size, + xfr->tsig_key); + } + + if (ret != KNOT_EOK) { + /*! \note [TSIG] No need to check TSIG error + * here, propagate and check elsewhere.*/ + return ret; + } + + // and reset the data storage + //xfr->packet_nr = 1; + xfr->tsig_data_size = 0; + + // Extract the digest from the TSIG RDATA and store it. + if (xfr->digest_max_size < tsig_rdata_mac_length(tsig)) { + return KNOT_ESPACE; + } + memcpy(xfr->digest, tsig_rdata_mac(tsig), + tsig_rdata_mac_length(tsig)); + xfr->digest_size = tsig_rdata_mac_length(tsig); + + } else { // TSIG not required and not there + // just append the wireformat to the TSIG data + assert(KNOT_NS_TSIG_DATA_MAX_SIZE - xfr->tsig_data_size + >= xfr->wire_size); + memcpy(xfr->tsig_data + xfr->tsig_data_size, + xfr->wire, xfr->wire_size); + xfr->tsig_data_size += xfr->wire_size; + } + } else if (tsig != NULL) { + // TSIG where it should not be + return KNOT_EMALF; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int xfrin_process_axfr_packet(/*const uint8_t *pkt, size_t size, + xfrin_constructed_zone_t **constr*/ + knot_ns_xfr_t *xfr) +{ + const uint8_t *pkt = xfr->wire; + size_t size = xfr->wire_size; + xfrin_constructed_zone_t **constr = + (xfrin_constructed_zone_t **)(&xfr->data); + + if (pkt == NULL || constr == NULL) { + dbg_xfrin("Wrong parameters supported.\n"); + return KNOT_EBADARG; + } + + dbg_xfrin("Processing AXFR packet of size %zu.\n", size); + + // check if the response is OK + if (knot_wire_get_rcode(pkt) != KNOT_RCODE_NOERROR) { + return KNOT_EXFRREFUSED; + } + + /*! \todo Should TC bit be checked? */ + + knot_packet_t *packet = + knot_packet_new(KNOT_PACKET_PREALLOC_NONE); + if (packet == NULL) { + dbg_xfrin("Could not create packet structure.\n"); + return KNOT_ENOMEM; + } + + int ret = knot_packet_parse_from_wire(packet, pkt, size, 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Could not parse packet: %s.\n", + knot_strerror(ret)); + knot_packet_free(&packet); + /*! \todo Cleanup. */ + return KNOT_EMALF; + } + + /*! \todo [TSIG] If packet RCODE is NOTAUTH(9), process as TSIG error. */ + + knot_rrset_t *rr = NULL; + ret = knot_packet_parse_next_rr_answer(packet, &rr); + + if (ret != KNOT_EOK) { + dbg_xfrin("Could not parse first Answer RR: %s.\n", + knot_strerror(ret)); + knot_packet_free(&packet); + /*! \todo Cleanup. */ + return KNOT_EMALF; + } + + if (rr == NULL) { + dbg_xfrin("No RRs in the packet.\n"); + knot_packet_free(&packet); + /*! \todo Cleanup. */ + return KNOT_EMALF; + } + + /*! \todo We should probably test whether the Question of the first + * message corresponds to the SOA RR. + */ + + knot_node_t *node = NULL; + int in_zone = 0; + knot_zone_contents_t *zone = NULL; + + if (*constr == NULL) { + // this should be the first packet + /*! \note [TSIG] Packet number for checking TSIG validation. */ + xfr->packet_nr = 0; + /*! \note [TSIG] Storing total size of data for TSIG digest. */ + xfr->tsig_data_size = 0; + + // create new zone + /*! \todo Ensure that the packet is the first one. */ + if (knot_rrset_type(rr) != KNOT_RRTYPE_SOA) { + dbg_xfrin("No zone created, but the first RR in " + "Answer is not a SOA RR.\n"); + knot_packet_free(&packet); + knot_node_free(&node, 0, 0); + knot_rrset_deep_free(&rr, 1, 1, 1); + /*! \todo Cleanup. */ + return KNOT_EMALF; + } + + if (knot_dname_compare(knot_rrset_owner(rr), + knot_packet_qname(packet)) != 0) { +dbg_xfrin_exec( + char *rr_owner = + knot_dname_to_str(knot_rrset_owner(rr)); + char *qname = knot_dname_to_str( + knot_packet_qname(packet)); + + dbg_xfrin("Owner of the first SOA RR (%s) does not" + " match QNAME (%s).\n", rr_owner, qname); + + free(rr_owner); + free(qname); +); + /*! \todo Cleanup. */ + knot_packet_free(&packet); + knot_node_free(&node, 0, 0); + knot_rrset_deep_free(&rr, 1, 1, 1); + return KNOT_EMALF; + } + + node = knot_node_new(rr->owner, NULL, 0); + if (node == NULL) { + dbg_xfrin("Failed to create new node.\n"); + knot_packet_free(&packet); + knot_rrset_deep_free(&rr, 1, 1, 1); + return KNOT_ENOMEM; + } + + // the first RR is SOA and its owner and QNAME are the same + // create the zone + + *constr = (xfrin_constructed_zone_t *)malloc( + sizeof(xfrin_constructed_zone_t)); + if (*constr == NULL) { + dbg_xfrin("Failed to create new constr. zone.\n"); + knot_packet_free(&packet); + knot_node_free(&node, 0, 0); + knot_rrset_deep_free(&rr, 1, 1, 1); + return KNOT_ENOMEM; + } + + memset(*constr, 0, sizeof(xfrin_constructed_zone_t)); + + (*constr)->contents = knot_zone_contents_new(node, 0, 1, NULL); +// assert(0); + if ((*constr)->contents== NULL) { + dbg_xfrin("Failed to create new zone.\n"); + knot_packet_free(&packet); + knot_node_free(&node, 0, 0); + knot_rrset_deep_free(&rr, 1, 1, 1); + /*! \todo Cleanup. */ + return KNOT_ENOMEM; + } + + in_zone = 1; + assert(node->owner == rr->owner); + zone = (*constr)->contents; + assert(zone->apex == node); + assert(zone->apex->owner == rr->owner); + // add the RRSet to the node + //ret = knot_node_add_rrset(node, rr, 0); + ret = knot_zone_contents_add_rrset(zone, rr, &node, + KNOT_RRSET_DUPL_MERGE, 1); + if (ret < 0) { + dbg_xfrin("Failed to add RRSet to zone node: %s.\n", + knot_strerror(ret)); + knot_packet_free(&packet); + knot_node_free(&node, 0, 0); + knot_rrset_deep_free(&rr, 1, 1, 1); + /*! \todo Cleanup. */ + return KNOT_ERROR; + } else if (ret > 0) { + dbg_xfrin("Merged SOA RRSet.\n"); + // merged, free the RRSet + //knot_rrset_deep_free(&rr, 1, 0, 0); + knot_rrset_free(&rr); + } + + // take next RR + ret = knot_packet_parse_next_rr_answer(packet, &rr); + } else { + zone = (*constr)->contents; + ++xfr->packet_nr; + } + + /*! \note [TSIG] add the packet wire size to the data to be verified by + * TSIG + */ + if (xfr->tsig_key) { + dbg_xfrin("Adding packet wire to TSIG data (size till now: %zu," + " adding: %zu).\n", xfr->tsig_data_size, + xfr->wire_size); + assert(KNOT_NS_TSIG_DATA_MAX_SIZE - xfr->tsig_data_size + >= xfr->wire_size); + memcpy(xfr->tsig_data + xfr->tsig_data_size, xfr->wire, + xfr->wire_size); + xfr->tsig_data_size += xfr->wire_size; + } + + assert(zone != NULL); + + while (ret == KNOT_EOK && rr != NULL) { + // process the parsed RR + + dbg_xfrin("\nNext RR:\n\n"); + knot_rrset_dump(rr, 0); + + if (node != NULL + && knot_dname_compare(rr->owner, node->owner) != 0) { +dbg_xfrin_exec( + char *name = knot_dname_to_str(node->owner); + dbg_xfrin("Node owner: %s\n", name); + free(name); +); + if (!in_zone) { + // this should not happen + assert(0); + // the node is not in the zone and the RR has + // other owner, so a new node must be created + // insert the old node to the zone + } + + node = NULL; + } + + if (knot_rrset_type(rr) == KNOT_RRTYPE_SOA) { + // this must be the last SOA, do not do anything more + // discard the RR + assert(knot_zone_contents_apex((zone)) != NULL); + assert(knot_node_rrset(knot_zone_contents_apex((zone)), + KNOT_RRTYPE_SOA) != NULL); + dbg_xfrin("Found last SOA, transfer finished.\n"); + + dbg_xfrin("Verifying TSIG...\n"); + /*! \note [TSIG] Now check if there is not a TSIG record + * at the end of the packet. + */ + ret = xfrin_check_tsig(packet, xfr, 1); + + dbg_xfrin_detail("xfrin_check_tsig() returned %d\n", + ret); + + knot_packet_free(&packet); + knot_rrset_deep_free(&rr, 1, 1, 1); + + if (ret != KNOT_EOK) { + /*! \todo [TSIG] Handle TSIG errors. */ + return ret; + } + + // we must now find place for all orphan RRSIGs + ret = xfrin_process_orphan_rrsigs(zone, + (*constr)->rrsigs); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to process orphan " + "RRSIGs\n"); + /*! \todo Cleanup?? */ + return ret; + } + + xfrin_free_orphan_rrsigs(&(*constr)->rrsigs); + + return 1; + } + + if (knot_rrset_type(rr) == KNOT_RRTYPE_RRSIG) { + // RRSIGs require special handling, as there are no + // nodes for them + knot_rrset_t *tmp_rrset = NULL; + ret = knot_zone_contents_add_rrsigs(zone, rr, + &tmp_rrset, &node, KNOT_RRSET_DUPL_MERGE, 1); + if (ret == KNOT_ENONODE || ret == KNOT_ENORRSET) { + dbg_xfrin("No node or RRSet for RRSIGs\n"); + dbg_xfrin("Saving for later insertion.\n"); + ret = xfrin_add_orphan_rrsig((*constr)->rrsigs, + rr); + if (ret > 0) { + dbg_xfrin("Merged RRSIGs.\n"); + knot_rrset_free(&rr); + } else if (ret != KNOT_EOK) { + dbg_xfrin("Failed to save orphan" + " RRSIGs.\n"); + knot_packet_free(&packet); + knot_node_free(&node, 1, 0); // ??? + knot_rrset_deep_free(&rr, 1, 1, 1); + return ret; + } + } else if (ret < 0) { + dbg_xfrin("Failed to add RRSIGs (%s).\n", + knot_strerror(ret)); + knot_packet_free(&packet); + knot_node_free(&node, 1, 0); // ??? + knot_rrset_deep_free(&rr, 1, 1, 1); + return KNOT_ERROR; /*! \todo Other error code. */ + } else if (ret == 1) { + assert(node != NULL); +dbg_xfrin_exec( + char *name = knot_dname_to_str(node->owner); + dbg_xfrin("Found node for the record in " + "zone: %s.\n", name); + free(name); +); + in_zone = 1; + knot_rrset_deep_free(&rr, 1, 0, 0); + } else if (ret == 2) { + // should not happen + assert(0); +// knot_rrset_deep_free(&rr, 1, 1, 1); + } else { + assert(node != NULL); +dbg_xfrin_exec( + char *name = knot_dname_to_str(node->owner); + dbg_xfrin("Found node for the record in " + "zone: %s.\n", name); + free(name); +); + in_zone = 1; + assert(tmp_rrset->rrsigs == rr); + } + + // parse next RR + ret = knot_packet_parse_next_rr_answer(packet, &rr); + + continue; + } + + /*! \note [TSIG] TSIG where it should not be - in Answer section.*/ + if (knot_rrset_type(rr) == KNOT_RRTYPE_TSIG) { + // not allowed here + dbg_xfrin(" in Answer section.\n"); + knot_packet_free(&packet); + knot_node_free(&node, 1, 0); // ??? + knot_rrset_deep_free(&rr, 1, 1, 1); + return KNOT_EMALF; + } + + knot_node_t *(*get_node)(const knot_zone_contents_t *, + const knot_dname_t *) = NULL; + int (*add_node)(knot_zone_contents_t *, knot_node_t *, int, + uint8_t, int) = NULL; + + if (knot_rrset_type(rr) == KNOT_RRTYPE_NSEC3) { + get_node = knot_zone_contents_get_nsec3_node; + add_node = knot_zone_contents_add_nsec3_node; + } else { + get_node = knot_zone_contents_get_node; + add_node = knot_zone_contents_add_node; + } + + if (node == NULL && (node = get_node(zone, + knot_rrset_owner(rr))) != NULL) { + // the node for this RR was found in the zone + dbg_xfrin("Found node for the record in zone.\n"); + in_zone = 1; + } + + if (node == NULL) { + // a new node for the RR is required but it is not + // in the zone + node = knot_node_new(rr->owner, NULL, 0); + if (node == NULL) { + dbg_xfrin("Failed to create new node.\n"); + knot_packet_free(&packet); + knot_rrset_deep_free(&rr, 1, 1, 1); + return KNOT_ENOMEM; + } + dbg_xfrin("Created new node for the record.\n"); + + // insert the RRSet to the node + ret = knot_node_add_rrset(node, rr, 1); + if (ret < 0) { + dbg_xfrin("Failed to add RRSet to node (%s" + ")\n", knot_strerror(ret)); + knot_packet_free(&packet); + knot_node_free(&node, 1, 0); // ??? + knot_rrset_deep_free(&rr, 1, 1, 1); + return KNOT_ERROR; + } else if (ret > 0) { + // should not happen, this is new node + assert(0); +// knot_rrset_deep_free(&rr, 1, 0, 0); + } + + // insert the node into the zone + ret = add_node(zone, node, 1, 0, 1); + assert(node != NULL); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add node to zone (%s)" + ".\n", knot_strerror(ret)); + knot_packet_free(&packet); + knot_node_free(&node, 1, 0); // ??? + knot_rrset_deep_free(&rr, 1, 1, 1); + return KNOT_ERROR; + } + + in_zone = 1; + } else { + assert(in_zone); + + ret = knot_zone_contents_add_rrset(zone, rr, &node, + KNOT_RRSET_DUPL_MERGE, 1); + if (ret < 0) { + dbg_xfrin("Failed to add RRSet to zone:" + "%s.\n", knot_strerror(ret)); + return KNOT_ERROR; + } else if (ret > 0) { + // merged, free the RRSet +// knot_rrset_deep_free(&rr, 1, 0, 0); + knot_rrset_free(&rr); + } + + } + + rr = NULL; + + // parse next RR + ret = knot_packet_parse_next_rr_answer(packet, &rr); + } + + assert(ret != KNOT_EOK || rr == NULL); + + if (ret < 0) { + // some error in parsing + dbg_xfrin("Could not parse next RR: %s.\n", + knot_strerror(ret)); + knot_packet_free(&packet); + knot_node_free(&node, 0, 0); + knot_rrset_deep_free(&rr, 1, 1, 1); + /*! \todo Cleanup. */ + return KNOT_EMALF; + } + + assert(ret == KNOT_EOK); + assert(rr == NULL); + + // if the last node is not yet in the zone, insert + if (!in_zone) { + assert(node != NULL); + ret = knot_zone_contents_add_node(zone, node, 1, 0, 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add last node into zone (%s)" + ".\n", knot_strerror(ret)); + knot_packet_free(&packet); + knot_node_free(&node, 1, 0); + return KNOT_ERROR; /*! \todo Other error */ + } + } + + /*! \note [TSIG] Now check if there is not a TSIG record at the end of + * the packet. + */ + ret = xfrin_check_tsig(packet, xfr, + knot_ns_tsig_required(xfr->packet_nr)); + ++xfr->packet_nr; + + knot_packet_free(&packet); + dbg_xfrin("Processed one AXFR packet successfully.\n"); + + /*! \note [TSIG] TSIG errors are propagated and reported in a standard + * manner, as we're in response processing, no further error response + * should be sent. + */ + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_parse_first_rr(knot_packet_t **packet, const uint8_t *pkt, + size_t size, knot_rrset_t **rr) +{ + *packet = knot_packet_new(KNOT_PACKET_PREALLOC_NONE); + if (packet == NULL) { + dbg_xfrin("Could not create packet structure.\n"); + return KNOT_ENOMEM; + } + + int ret = knot_packet_parse_from_wire(*packet, pkt, size, 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Could not parse packet: %s.\n", + knot_strerror(ret)); + knot_packet_free(packet); + return KNOT_EMALF; + } + + // check if the TC bit is set (it must not be) + if (knot_wire_get_tc(pkt)) { + dbg_xfrin("IXFR response has TC bit set.\n"); + knot_packet_free(packet); + return KNOT_EMALF; + } + + ret = knot_packet_parse_next_rr_answer(*packet, rr); + + if (ret != KNOT_EOK) { + dbg_xfrin("Could not parse first Answer RR: %s.\n", + knot_strerror(ret)); + knot_packet_free(packet); + return KNOT_EMALF; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int xfrin_process_ixfr_packet(/*const uint8_t *pkt, size_t size, + knot_changesets_t **chs*/knot_ns_xfr_t *xfr) +{ + size_t size = xfr->wire_size; + const uint8_t *pkt = xfr->wire; + knot_changesets_t **chs = (knot_changesets_t **)(&xfr->data); + + if (pkt == NULL || chs == NULL) { + dbg_xfrin("Wrong parameters supported.\n"); + return KNOT_EBADARG; + } + + // check if the response is OK + if (knot_wire_get_rcode(pkt) != KNOT_RCODE_NOERROR) { + return KNOT_EXFRREFUSED; + } + + knot_packet_t *packet = NULL; +// knot_rrset_t *soa1 = NULL; +// knot_rrset_t *soa2 = NULL; + knot_rrset_t *rr = NULL; + + int ret; + + if ((ret = xfrin_parse_first_rr(&packet, pkt, size, &rr)) != KNOT_EOK) { + return ret; + } + + assert(packet != NULL); + + // state of the transfer + // -1 .. a SOA is expected to create a new changeset + int state = 0; + + if (rr == NULL) { + dbg_xfrin("No RRs in the packet.\n"); + knot_packet_free(&packet); + /*! \todo Some other action??? */ + return KNOT_EMALF; + } + + if (*chs == NULL) { + dbg_xfrin("Changesets empty, creating new.\n"); + + ret = knot_changeset_allocate(chs); + if (ret != KNOT_EOK) { + knot_rrset_deep_free(&rr, 1, 1, 1); + knot_packet_free(&packet); + return ret; + } + + // the first RR must be a SOA + if (knot_rrset_type(rr) != KNOT_RRTYPE_SOA) { + dbg_xfrin("First RR is not a SOA RR!\n"); + knot_rrset_deep_free(&rr, 1, 1, 1); + ret = KNOT_EMALF; + goto cleanup; + } + + // just store the first SOA for later use + (*chs)->first_soa = rr; + state = -1; + + dbg_xfrin("First SOA of IXFR saved, state set to -1.\n"); + + // parse the next one + ret = knot_packet_parse_next_rr_answer(packet, &rr); + if (ret != KNOT_EOK) { + return ret; + } + + /* + * If there is no other records in the response than the SOA, it + * means one of these two cases: + * + * 1) The server does not have newer zone than ours. + * This is indicated by serial equal to the one of our zone. + * 2) The server wants to send the transfer but is unable to fit + * it in the packet. This is indicated by serial different + * (newer) from the one of our zone. + * + * The serials must be compared in other parts of the server, so + * just indicate that the answer contains only one SOA. + */ + if (rr == NULL) { + dbg_xfrin("Response containing only SOA,\n"); + knot_packet_free(&packet); + return XFRIN_RES_SOA_ONLY; + } else if (knot_rrset_type(rr) != KNOT_RRTYPE_SOA) { + knot_rrset_deep_free(&rr, 1, 1, 1); + dbg_xfrin("Fallback to AXFR.\n"); + ret = XFRIN_RES_FALLBACK; + knot_free_changesets(chs); + xfr->data = 0; + return ret; + } + } else { + if ((*chs)->first_soa == NULL) { + dbg_xfrin("Changesets don't contain frist SOA!\n"); + ret = KNOT_EBADARG; + goto cleanup; + } + dbg_xfrin("Changesets present.\n"); + } + + /* + * Process the next RR. Different requirements are in place in + * different cases: + * + * 1) Last changeset has both soa_from and soa_to. + * a) The next RR is a SOA. + * i) The next RR is equal to the first_soa saved in changesets. + * This denotes the end of the transfer. It may be dropped and + * the end should be signalised by returning positive value. + * + * ii) The next RR is some other SOA. + * This means a start of new changeset - create it and add it + * to the list. + * + * b) The next RR is not a SOA. + * Put the RR into the ADD part of the last changeset as this is + * not finished yet. Continue while SOA is not encountered. Then + * jump to 1-a. + * + * 2) Last changeset has only the soa_from and does not have soa_to. + * a) The next RR is a SOA. + * This means start of the ADD section. Put the SOA to the + * changeset. Continue adding RRs to the ADD section while SOA + * is not encountered. This is identical to 1-b. + * + * b) The next RR is not a SOA. + * This means the REMOVE part is not finished yet. Add the RR to + * the REMOVE part. Continue adding next RRs until a SOA is + * encountered. Then jump to 2-a. + */ + + // first, find out in what state we are + /*! \todo It would be more elegant to store the state in the + * changesets structure, or in some place persistent between + * calls to this function. + */ + if (state != -1) { + dbg_xfrin("State is not -1, deciding...\n"); + // there should be at least one started changeset right now + if ((*chs)->count <= 0) { + knot_rrset_deep_free(&rr, 1, 1, 1); + ret = KNOT_EMALF; + goto cleanup; + } + + // a changeset should be created only when there is a SOA + assert((*chs)->sets[(*chs)->count - 1].soa_from != NULL); + + if ((*chs)->sets[(*chs)->count - 1].soa_to == NULL) { + state = XFRIN_CHANGESET_REMOVE; + } else { + state = XFRIN_CHANGESET_ADD; + } + } + + dbg_xfrin("State before the loop: %d\n", state); + + /*! \todo This may be implemented with much less IFs! */ + + while (ret == KNOT_EOK && rr != NULL) { +dbg_xfrin_exec( + dbg_xfrin("Next loop, state: %d\n", state); + char *name = knot_dname_to_str(knot_rrset_owner(rr)); + dbg_xfrin("Actual RR: %s, type %s.\n", name, + knot_rrtype_to_string(knot_rrset_type(rr))); + free(name); +); + switch (state) { + case -1: + // a SOA is expected + // this may be either a start of a changeset or the + // last SOA (in case the transfer was empty, but that + // is quite weird in fact + if (knot_rrset_type(rr) != KNOT_RRTYPE_SOA) { + dbg_xfrin("First RR is not a SOA RR!\n"); + dbg_xfrin("RR type: %s\n", + knot_rrtype_to_string(knot_rrset_type(rr))); + ret = KNOT_EMALF; + knot_rrset_deep_free(&rr, 1, 1, 1); + goto cleanup; + } + + if (knot_rdata_soa_serial(knot_rrset_rdata(rr)) + == knot_rdata_soa_serial( + knot_rrset_rdata((*chs)->first_soa))) { + + /*! \note [TSIG] Check TSIG, we're at the end of + * transfer. + */ + ret = xfrin_check_tsig(packet, xfr, 1); + + // last SOA, discard and end + knot_rrset_deep_free(&rr, 1, 1, 1); + knot_packet_free(&packet); + + /*! \note [TSIG] If TSIG validates, consider + * transfer complete. */ + if (ret == KNOT_EOK) { + ret = XFRIN_RES_COMPLETE; + } + + return ret; + } else { + // normal SOA, start new changeset + (*chs)->count++; + if ((ret = knot_changesets_check_size(*chs)) + != KNOT_EOK) { + (*chs)->count--; + knot_rrset_deep_free(&rr, 1, 1, 1); + goto cleanup; + } + + ret = knot_changeset_add_soa( + &(*chs)->sets[(*chs)->count - 1], rr, + XFRIN_CHANGESET_REMOVE); + if (ret != KNOT_EOK) { + knot_rrset_deep_free(&rr, 1, 1, 1); + goto cleanup; + } + + // change state to REMOVE + state = XFRIN_CHANGESET_REMOVE; + } + break; + case XFRIN_CHANGESET_REMOVE: + // if the next RR is SOA, store it and change state to + // ADD + if (knot_rrset_type(rr) == KNOT_RRTYPE_SOA) { + // we should not be here if soa_from is not set + assert((*chs)->sets[(*chs)->count - 1].soa_from + != NULL); + + ret = knot_changeset_add_soa( + &(*chs)->sets[(*chs)->count - 1], rr, + XFRIN_CHANGESET_ADD); + if (ret != KNOT_EOK) { + knot_rrset_deep_free(&rr, 1, 1, 1); + goto cleanup; + } + + state = XFRIN_CHANGESET_ADD; + } else { + // just add the RR to the REMOVE part and + // continue + if ((ret = knot_changeset_add_new_rr( + &(*chs)->sets[(*chs)->count - 1], rr, + XFRIN_CHANGESET_REMOVE)) != KNOT_EOK) { + knot_rrset_deep_free(&rr, 1, 1, 1); + goto cleanup; + } + } + break; + case XFRIN_CHANGESET_ADD: + // if the next RR is SOA change to state -1 and do not + // parse next RR + if (knot_rrset_type(rr) == KNOT_RRTYPE_SOA) { + state = -1; + continue; + } else { + + // just add the RR to the ADD part and continue + if ((ret = knot_changeset_add_new_rr( + &(*chs)->sets[(*chs)->count - 1], rr, + XFRIN_CHANGESET_ADD)) != KNOT_EOK) { + knot_rrset_deep_free(&rr, 1, 1, 1); + goto cleanup; + } + } + break; + default: + assert(0); + } + + // parse the next RR + dbg_xfrin("Parsing next RR..\n"); + ret = knot_packet_parse_next_rr_answer(packet, &rr); + dbg_xfrin("Returned %d, %p.\n", ret, rr); + } + + /*! \note Check TSIG, we're at the end of packet. It may not be + * required. + */ + ret = xfrin_check_tsig(packet, xfr, + knot_ns_tsig_required(xfr->packet_nr)); + dbg_xfrin_detail("xfrin_check_tsig() returned %d\n", ret); + ++xfr->packet_nr; + + /*! \note [TSIG] Cleanup and propagate error if TSIG validation fails.*/ + if (ret != KNOT_EOK) { + goto cleanup; + } + + // here no RRs remain in the packet but the transfer is not finished + // yet, return EOK + knot_packet_free(&packet); + return KNOT_EOK; + +cleanup: + /* We should go here only if some error occured. */ + assert(ret < 0); + + dbg_xfrin("Cleanup after processing IXFR/IN packet.\n"); + knot_free_changesets(chs); + knot_packet_free(&packet); + xfr->data = 0; + return ret; +} + +/*----------------------------------------------------------------------------*/ +/* Applying changesets to zone */ +/*----------------------------------------------------------------------------*/ + +typedef struct { + /*! + * Deleted (without owners and RDATA) after successful update. + */ + knot_rrset_t **old_rrsets; + int old_rrsets_count; + int old_rrsets_allocated; + + /*! + * Deleted after successful update. + */ + knot_rdata_t **old_rdata; + uint *old_rdata_types; + int old_rdata_count; + int old_rdata_allocated; + + /*! + * \brief Copied RRSets (i.e. modified by the update). + * + * Deleted (without owners and RDATA) after failed update. + */ + knot_rrset_t **new_rrsets; + int new_rrsets_count; + int new_rrsets_allocated; + + /*! + * Deleted (without contents) after successful update. + */ + knot_node_t **old_nodes; + int old_nodes_count; + int old_nodes_allocated; + + /*! + * Deleted (without contents) after failed update. + */ + knot_node_t **new_nodes; + int new_nodes_count; + int new_nodes_allocated; + + ck_hash_table_item_t **old_hash_items; + int old_hash_items_count; + int old_hash_items_allocated; +} xfrin_changes_t; + +/*----------------------------------------------------------------------------*/ + +static void xfrin_changes_free(xfrin_changes_t **changes) +{ + free((*changes)->old_nodes); + free((*changes)->old_rrsets); + free((*changes)->old_rdata); + free((*changes)->old_rdata_types); + free((*changes)->new_rrsets); + free((*changes)->new_nodes); + free((*changes)->old_hash_items); +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_changes_check_rrsets(knot_rrset_t ***rrsets, + int *count, int *allocated, int to_add) +{ + /* Ensure at least requested size is allocated. */ + int new_count = (*count + to_add); + assert(new_count >= 0); + if (new_count <= *allocated) { + return KNOT_EOK; + } + + /* Allocate new memory block. */ + knot_rrset_t **rrsets_new = malloc(new_count * sizeof(knot_rrset_t *)); + if (rrsets_new == NULL) { + return KNOT_ENOMEM; + } + + /* Initialize new memory and copy old data. */ + memset(rrsets_new, 0, new_count * sizeof(knot_rrset_t *)); + memcpy(rrsets_new, *rrsets, (*allocated) * sizeof(knot_rrset_t *)); + + /* Free old nodes and switch pointers. */ + free(*rrsets); + *rrsets = rrsets_new; + *allocated = new_count; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_changes_check_nodes(knot_node_t ***nodes, + int *count, int *allocated) +{ + assert(nodes != NULL); + assert(count != NULL); + assert(allocated != 0); + + /* Ensure at least count and some reserve is allocated. */ + int new_count = *count + 2; + if (new_count <= *allocated) { + return KNOT_EOK; + } + + /* Allocate new memory block. */ + const size_t node_len = sizeof(knot_node_t *); + knot_node_t **nodes_new = malloc(new_count * node_len); + if (nodes_new == NULL) { + return KNOT_ENOMEM; + } + + /* Clear memory block and copy old data. */ + memset(nodes_new, 0, new_count * node_len); + memcpy(nodes_new, *nodes, (*allocated) * node_len); + + /* Free old nodes and switch pointers. */ + free(*nodes); + *nodes = nodes_new; + *allocated = new_count; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_changes_check_rdata(knot_rdata_t ***rdatas, uint **types, + int count, int *allocated, int to_add) +{ + /* Ensure at least requested size is allocated. */ + int new_count = (count + to_add); + assert(new_count >= 0); + if (new_count <= *allocated) { + return KNOT_EOK; + } + + /* Allocate new memory block. */ + knot_rdata_t **rdatas_new = malloc(new_count * sizeof(knot_rdata_t *)); + if (rdatas_new == NULL) { + return KNOT_ENOMEM; + } + + uint *types_new = malloc(new_count * sizeof(uint)); + if (types_new == NULL) { + return KNOT_ENOMEM; + } + + /* Initialize new memory and copy old data. */ + memset(rdatas_new, 0, new_count * sizeof(knot_rdata_t *)); + memcpy(rdatas_new, *rdatas, (*allocated) * sizeof(knot_rdata_t *)); + + memset(types_new, 0, new_count * sizeof(uint)); + memcpy(types_new, *types, (*allocated) * sizeof(uint)); + + /* Free old rdatas and switch pointers. */ + free(*rdatas); + free(*types); + *rdatas = rdatas_new; + *types = types_new; + *allocated = new_count; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_changes_check_hash_items(ck_hash_table_item_t ***items, + int *count, int *allocated) +{ + /* Prevent infinite loop in case of allocated = 0. */ + int new_count = 0; + if (*allocated == 0) { + new_count = *count + 1; + } else { + if (*count == *allocated) { + new_count = *allocated * 2; + } + } + + const size_t item_len = sizeof(ck_hash_table_item_t *); + ck_hash_table_item_t **items_new = malloc(new_count * item_len); + if (items_new == NULL) { + return KNOT_ENOMEM; + } + + memset(items_new, 0, new_count * item_len); + memcpy(items_new, *items, (*count) * item_len); + free(*items); + *items = items_new; + *allocated = new_count; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static void xfrin_zone_contents_free(knot_zone_contents_t **contents) +{ + /*! \todo This should be all in some API!! */ + + if ((*contents)->table != NULL) { +// ck_destroy_table(&(*contents)->table, NULL, 0); + ck_table_free(&(*contents)->table); + } + + // free the zone tree, but only the structure + // (nodes are already destroyed) + dbg_zone("Destroying zone tree.\n"); + knot_zone_tree_free(&(*contents)->nodes); + dbg_zone("Destroying NSEC3 zone tree.\n"); + knot_zone_tree_free(&(*contents)->nsec3_nodes); + + knot_nsec3_params_free(&(*contents)->nsec3_params); + + knot_dname_table_deep_free(&(*contents)->dname_table); + + free(*contents); + *contents = NULL; +} + +/*----------------------------------------------------------------------------*/ + +static void xfrin_rollback_update(knot_zone_contents_t *contents, + xfrin_changes_t *changes) +{ + /* + * This function is called only when no references were actually set to + * the new nodes, just the new nodes reference other. + * We thus do not need to fix any references, just from the old nodes + * to the new ones. + */ + + // discard new nodes, but do not remove RRSets from them + for (int i = 0; i < changes->new_nodes_count; ++i) { + knot_node_free(&changes->new_nodes[i], 0, 0); + } + + // set references from old nodes to new nodes to NULL and remove the + // old flag + for (int i = 0; i < changes->old_nodes_count; ++i) { + knot_node_set_new_node(changes->old_nodes[i], NULL); + knot_node_clear_old(changes->old_nodes[i]); + } + + // discard new RRSets + for (int i = 0; i < changes->old_rrsets_count; ++i) { + knot_rrset_deep_free(&changes->new_rrsets[i], 0, 1, 0); + } + + // destroy the shallow copy of zone + xfrin_zone_contents_free(&contents); +} + +/*----------------------------------------------------------------------------*/ + +static knot_rdata_t *xfrin_remove_rdata(knot_rrset_t *from, + const knot_rrset_t *what) +{ + knot_rdata_t *old = NULL; + knot_rdata_t *old_actual = NULL; + + const knot_rdata_t *rdata = knot_rrset_rdata(what); + + while (rdata != NULL) { + old_actual = knot_rrset_remove_rdata(from, rdata); + if (old_actual != NULL) { + old_actual->next = old; + old = old_actual; + } + rdata = knot_rrset_rdata_next(what, rdata); + } + + return old; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_get_node_copy(knot_node_t **node, xfrin_changes_t *changes) +{ + knot_node_t *new_node = + knot_node_get_new_node(*node); + if (new_node == NULL) { + dbg_xfrin("Creating copy of node.\n"); + int ret = knot_node_shallow_copy(*node, &new_node); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to create node copy.\n"); + return KNOT_ENOMEM; + } + + dbg_xfrin_detail("Created copy of old node %p to new node %p\n", + *node, new_node); + + assert(changes); + +// changes->new_nodes_allocated = 0; + + // save the copy of the node + ret = xfrin_changes_check_nodes( + &changes->new_nodes, + &changes->new_nodes_count, + &changes->new_nodes_allocated); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add new node to list.\n"); + knot_node_free(&new_node, 0, 0); + return ret; + } + +// changes->old_nodes_allocated = 0; + + // save the old node to list of old nodes + ret = xfrin_changes_check_nodes( + &changes->old_nodes, + &changes->old_nodes_count, + &changes->old_nodes_allocated); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add old node to list.\n"); + knot_node_free(&new_node, 0, 0); + return ret; + } + + assert(changes->new_nodes); + assert(changes->old_nodes); + + changes->new_nodes[changes->new_nodes_count++] = new_node; + changes->old_nodes[changes->old_nodes_count++] = *node; + + // mark the old node as old + knot_node_set_old(*node); + + knot_node_set_new(new_node); + knot_node_set_new_node(*node, new_node); + } + + *node = new_node; + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_copy_old_rrset(knot_rrset_t *old, knot_rrset_t **copy, + xfrin_changes_t *changes) +{ + // create new RRSet by copying the old one + int ret = knot_rrset_shallow_copy(old, copy); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to create RRSet copy.\n"); + return KNOT_ENOMEM; + } + + // add the RRSet to the list of new RRSets + ret = xfrin_changes_check_rrsets(&changes->new_rrsets, + &changes->new_rrsets_count, + &changes->new_rrsets_allocated, 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add new RRSet to list.\n"); + knot_rrset_free(copy); + return ret; + } + + changes->new_rrsets[changes->new_rrsets_count++] = *copy; + + // add the old RRSet to the list of old RRSets + ret = xfrin_changes_check_rrsets(&changes->old_rrsets, + &changes->old_rrsets_count, + &changes->old_rrsets_allocated, 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add old RRSet to list.\n"); + return ret; + } + + changes->old_rrsets[changes->old_rrsets_count++] = old; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_copy_rrset(knot_node_t *node, knot_rr_type_t type, + knot_rrset_t **rrset, xfrin_changes_t *changes) +{ + knot_rrset_t *old = knot_node_remove_rrset(node, type); + + if (old == NULL) { + dbg_xfrin("RRSet not found for RR to be removed.\n"); + return 1; + } + + int ret = xfrin_copy_old_rrset(old, rrset, changes); + if (ret != KNOT_EOK) { + return ret; + } + + dbg_xfrin_detail("Copied old rrset %p to new %p.\n", + old, *rrset); + + // replace the RRSet in the node copy by the new one + ret = knot_node_add_rrset(node, *rrset, 0); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add RRSet copy to node\n"); + return KNOT_ERROR; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_apply_remove_rrsigs(xfrin_changes_t *changes, + const knot_rrset_t *remove, + knot_node_t *node, + knot_rrset_t **rrset) +{ + assert(changes != NULL); + assert(remove != NULL); + assert(node != NULL); + assert(rrset != NULL); + assert(knot_rrset_type(remove) == KNOT_RRTYPE_RRSIG); + + /*! \todo These optimalizations may be useless as there may be only + * one RRSet of each type and owner in the changeset. + */ + + int ret; + + if (!*rrset + || knot_dname_compare(knot_rrset_owner(*rrset), + knot_node_owner(node)) != 0 + || knot_rrset_type(*rrset) != knot_rdata_rrsig_type_covered( + knot_rrset_rdata(remove))) { + // find RRSet based on the Type Covered + knot_rr_type_t type = knot_rdata_rrsig_type_covered( + knot_rrset_rdata(remove)); + + // copy the rrset + ret = xfrin_copy_rrset(node, type, rrset, changes); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to copy rrset from changeset.\n"); + return ret; + } + } else { + // we should have the right RRSIG RRSet in *rrset + assert(knot_rrset_type(*rrset) + == knot_rdata_rrsig_type_covered( + knot_rrset_rdata(remove))); + // this RRSet should be the already copied RRSet so we may + // update it right away + } + + // get the old rrsigs + knot_rrset_t *old = knot_rrset_get_rrsigs(*rrset); + if (old == NULL) { + return 1; + } + + // copy the RRSIGs + /*! \todo This may be done unnecessarily more times. */ + knot_rrset_t *rrsigs; + ret = xfrin_copy_old_rrset(old, &rrsigs, changes); + if (ret != KNOT_EOK) { + return ret; + } + + // set the RRSIGs to the new RRSet copy + if (knot_rrset_set_rrsigs(*rrset, rrsigs) != KNOT_EOK) { + dbg_xfrin("Failed to set rrsigs.\n"); + return KNOT_ERROR; + } + + + + // now in '*rrset' we have a copy of the RRSet which holds the RRSIGs + // and in 'rrsigs' we have the copy of the RRSIGs + + knot_rdata_t *rdata = xfrin_remove_rdata(rrsigs, remove); + if (rdata == NULL) { + dbg_xfrin("Failed to remove RDATA from RRSet: %s.\n", + knot_strerror(ret)); + return 1; + } + + // if the RRSet is empty, remove from node and add to old RRSets + // check if there is no RRSIGs; if there are, leave the RRSet + // there; it may be eventually removed when the RRSIGs are removed + if (knot_rrset_rdata(rrsigs) == NULL) { + // remove the RRSIGs from the RRSet + knot_rrset_set_rrsigs(*rrset, NULL); + + ret = xfrin_changes_check_rrsets(&changes->old_rrsets, + &changes->old_rrsets_count, + &changes->old_rrsets_allocated, + 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add empty RRSet to the " + "list of old RRSets."); + // delete the RRSet right away + knot_rrset_free(&rrsigs); + return ret; + } + + changes->old_rrsets[changes->old_rrsets_count++] = rrsigs; + + // now check if the RRSet is not totally empty + if (knot_rrset_rdata(*rrset) == NULL) { + assert(knot_rrset_rrsigs(*rrset) == NULL); + + // remove the whole RRSet from the node + knot_rrset_t *tmp = knot_node_remove_rrset(node, + knot_rrset_type(*rrset)); + assert(tmp == *rrset); + + ret = xfrin_changes_check_rrsets(&changes->old_rrsets, + &changes->old_rrsets_count, + &changes->old_rrsets_allocated, + 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add empty RRSet to " + "the list of old RRSets."); + // delete the RRSet right away + knot_rrset_free(rrset); + return ret; + } + + changes->old_rrsets[changes->old_rrsets_count++] = + *rrset; + } + } + + // connect the RDATA to the list of old RDATA + ret = xfrin_changes_check_rdata(&changes->old_rdata, + &changes->old_rdata_types, + changes->old_rdata_count, + &changes->old_rdata_allocated, 1); + if (ret != KNOT_EOK) { + return ret; + } + + changes->old_rdata[changes->old_rdata_count] = rdata; + changes->old_rdata_types[changes->old_rdata_count] = + knot_rrset_type(remove); + ++changes->old_rdata_count; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_apply_remove_normal(xfrin_changes_t *changes, + const knot_rrset_t *remove, + knot_node_t *node, + knot_rrset_t **rrset) +{ + assert(changes != NULL); + assert(remove != NULL); + assert(node != NULL); + assert(rrset != NULL); + + int ret; + + dbg_xfrin_detail("Removing RRSet: \n"); + knot_rrset_dump(remove, 0); + + // now we have the copy of the node, so lets get the right RRSet + // check if we do not already have it + if (!*rrset + || knot_dname_compare(knot_rrset_owner(*rrset), + knot_node_owner(node)) != 0 + || knot_rrset_type(*rrset) + != knot_rrset_type(remove)) { + /*! + * \todo This may happen also with already + * copied RRSet. In that case it would be + * an unnecesary overhead but will + * probably not cause problems. TEST!! + */ + ret = xfrin_copy_rrset(node, + knot_rrset_type(remove), rrset, changes); + dbg_xfrin_detail("Copied RRSet:\n"); + knot_rrset_dump(*rrset, 0); + if (ret != KNOT_EOK) { + return ret; + } + } + + if (*rrset == NULL) { + dbg_xfrin("RRSet not found for RR to be removed.\n"); + return 1; + } + +dbg_xfrin_exec( + char *name = knot_dname_to_str(knot_rrset_owner(*rrset)); + dbg_xfrin("Updating RRSet with owner %s, type %s\n", name, + knot_rrtype_to_string(knot_rrset_type(*rrset))); + free(name); +); + + // remove the specified RRs from the RRSet (de facto difference of + // sets) + knot_rdata_t *rdata = xfrin_remove_rdata(*rrset, remove); + if (rdata == NULL) { + dbg_xfrin("Failed to remove RDATA from RRSet: %s.\n", + knot_strerror(ret)); + return 1; + } + +dbg_xfrin_exec_detail( + dbg_xfrin_detail("Removed rdata: \n"); + knot_rdata_t *r = rdata; + if (r != NULL) { + do { + dbg_xfrin_detail("pointer: %p\n", r); + knot_rdata_dump(r, knot_rrset_type(remove), 0); + r = r->next; + } while (r != NULL && r != rdata); + } +); + + // if the RRSet is empty, remove from node and add to old RRSets + // check if there is no RRSIGs; if there are, leave the RRSet + // there; it may be eventually removed when the RRSIGs are removed + if (knot_rrset_rdata(*rrset) == NULL + && knot_rrset_rrsigs(*rrset) == NULL) { + + knot_rrset_t *tmp = knot_node_remove_rrset(node, + knot_rrset_type(*rrset)); + dbg_xfrin_detail("Removed whole RRSet (%p).\n", tmp); + + // add the removed RRSet to list of old RRSets + + assert(tmp == *rrset); + ret = xfrin_changes_check_rrsets(&changes->old_rrsets, + &changes->old_rrsets_count, + &changes->old_rrsets_allocated, + 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add empty RRSet to the " + "list of old RRSets."); + // delete the RRSet right away + knot_rrset_free(rrset); + return ret; + } + + changes->old_rrsets[changes->old_rrsets_count++] = *rrset; + } + + // connect the RDATA to the list of old RDATA + ret = xfrin_changes_check_rdata(&changes->old_rdata, + &changes->old_rdata_types, + changes->old_rdata_count, + &changes->old_rdata_allocated, 1); + if (ret != KNOT_EOK) { + return ret; + } + + changes->old_rdata[changes->old_rdata_count] = rdata; + changes->old_rdata_types[changes->old_rdata_count] = + knot_rrset_type(remove); + ++changes->old_rdata_count; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_apply_remove_all_rrsets(xfrin_changes_t *changes, + knot_node_t *node, uint16_t type) +{ + /*! \todo Implement. */ + int ret; + + if (type == KNOT_RRTYPE_ANY) { + // put all the RRSets to the changes structure + ret = xfrin_changes_check_rrsets(&changes->old_rrsets, + &changes->old_rrsets_count, + &changes->old_rrsets_allocated, + knot_node_rrset_count(node)); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to check changeset rrsets.\n"); + return ret; + } + + knot_rrset_t **rrsets = knot_node_get_rrsets(node); + knot_rrset_t **place = changes->old_rrsets + + changes->old_rrsets_count; + /*! \todo Test this!!! */ + memcpy(place, rrsets, knot_node_rrset_count(node) * sizeof(knot_rrset_t *)); + + // remove all RRSets from the node + knot_node_remove_all_rrsets(node); + } else { + ret = xfrin_changes_check_rrsets(&changes->old_rrsets, + &changes->old_rrsets_count, + &changes->old_rrsets_allocated, + 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to check changeset rrsets.\n"); + return ret; + } + // remove only RRSet with the given type + knot_rrset_t *rrset = knot_node_remove_rrset(node, type); + changes->old_rrsets[changes->old_rrsets_count++] = rrset; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_apply_remove(knot_zone_contents_t *contents, + knot_changeset_t *chset, + xfrin_changes_t *changes) +{ + /* + * Iterate over removed RRSets, copy appropriate nodes and remove + * the rrsets from them. By default, the RRSet should be copied so that + * RDATA may be removed from it. + */ + int ret = 0; + knot_node_t *node = NULL; + knot_rrset_t *rrset = NULL; + + for (int i = 0; i < chset->remove_count; ++i) { + // check if the old node is not the one we should use + if (!node || knot_rrset_owner(chset->remove[i]) + != knot_node_owner(node)) { + node = knot_zone_contents_get_node(contents, + knot_rrset_owner(chset->remove[i])); + if (node == NULL) { + dbg_xfrin("Node not found for RR to be removed" + "!\n"); + continue; + } + } + + // create a copy of the node if not already created + if (!knot_node_is_new(node)) { + ret = xfrin_get_node_copy(&node, changes); + if (ret != KNOT_EOK) { + return ret; + } + } + + assert(node != NULL); + assert(knot_node_is_new(node)); + + // first check if all RRSets should be removed + if (knot_rrset_class(chset->remove[i]) == KNOT_CLASS_ANY) { + ret = xfrin_apply_remove_all_rrsets( + changes, node, + knot_rrset_type(chset->remove[i])); + } else if (knot_rrset_type(chset->remove[i]) + == KNOT_RRTYPE_RRSIG) { + // this should work also for UPDATE + ret = xfrin_apply_remove_rrsigs(changes, + chset->remove[i], + node, &rrset); + } else { + // this should work also for UPDATE + ret = xfrin_apply_remove_normal(changes, + chset->remove[i], + node, &rrset); + } + + dbg_xfrin("xfrin_apply_remove() ret = %d\n", ret); + + if (ret > 0) { + continue; + } else if (ret != KNOT_EOK) { + return ret; + } + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static knot_node_t *xfrin_add_new_node(knot_zone_contents_t *contents, + knot_rrset_t *rrset) +{ + /*! \todo Why is the function disabled? */ + //return NULL; + + knot_node_t *node = knot_node_new(knot_rrset_get_owner(rrset), + NULL, KNOT_NODE_FLAGS_NEW); + if (node == NULL) { + dbg_xfrin("Failed to create a new node.\n"); + return NULL; + } + + int ret = 0; + + // insert the node into zone structures and create parents if + // necessary +// dbg_xfrin("Adding new node to zone. From owner: %s type %s\n", +// knot_dname_to_str(node->owner), +// knot_rrtype_to_string(rrset->type)); +// getchar(); + if (knot_rrset_type(rrset) == KNOT_RRTYPE_NSEC3) { + ret = knot_zone_contents_add_nsec3_node(contents, node, 1, 0, + 1); + } else { + ret = knot_zone_contents_add_node(contents, node, 1, + KNOT_NODE_FLAGS_NEW, 1); + } + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add new node to zone contents.\n"); + return NULL; + } + + // find previous node and connect the new one to it + knot_node_t *prev = NULL; + if (knot_rrset_type(rrset) == KNOT_RRTYPE_NSEC3) { + prev = knot_zone_contents_get_previous_nsec3(contents, + knot_rrset_owner(rrset)); + } else { + prev = knot_zone_contents_get_previous(contents, + knot_rrset_owner(rrset)); + } + + // fix prev and next pointers + if (prev != NULL) { + knot_node_set_previous(node, prev); + } + +// printf("contents owned by: %s (%p)\n", +// knot_dname_to_str(contents->apex->owner), +// contents); + assert(contents->zone != NULL); + knot_node_set_zone(node, contents->zone); + + return node; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_apply_add_normal(xfrin_changes_t *changes, + knot_rrset_t *add, + knot_node_t *node, + knot_rrset_t **rrset) +{ + assert(changes != NULL); + assert(add != NULL); + assert(node != NULL); + assert(rrset != NULL); + + int ret; + + dbg_xfrin("applying rrset:\n"); + knot_rrset_dump(add, 0); +// getchar(); + + if (!*rrset + || knot_dname_compare(knot_rrset_owner(*rrset), + knot_node_owner(node)) != 0 + || knot_rrset_type(*rrset) + != knot_rrset_type(add)) { + dbg_xfrin("Removing rrset!\n"); + *rrset = knot_node_remove_rrset(node, knot_rrset_type(add)); + } + + dbg_xfrin("Removed RRSet: \n"); + knot_rrset_dump(*rrset, 1); + + if (*rrset == NULL) { +dbg_xfrin_exec_verb( + char *name = knot_dname_to_str(add->owner); + dbg_xfrin_verb("RRSet to be added not found in zone.\n"); + dbg_xfrin_verb("owner: %s type: %s\n", name, + knot_rrtype_to_string(add->type)); + free(name); +); +// getchar(); + // add the RRSet from the changeset to the node + /*! \todo What about domain names?? Shouldn't we use the + * zone-contents' version of this function?? + */ + ret = knot_node_add_rrset(node, add, 0); +// ret = knot_zone_contents_add_rrset(node->zone->contents, +// rrset, node, +// KNOT_RRSET_DUPL_MERGE, +// 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add RRSet to node.\n"); + return KNOT_ERROR; + } + return 1; // return 1 to indicate the add RRSet was used + } + + knot_rrset_t *old = *rrset; + +dbg_xfrin_exec( + char *name = knot_dname_to_str(knot_rrset_owner(*rrset)); + dbg_xfrin("Found RRSet with owner %s, type %s\n", name, + knot_rrtype_to_string(knot_rrset_type(*rrset))); + free(name); +); + knot_rrset_dump(*rrset, 1); + ret = xfrin_copy_old_rrset(old, rrset, changes); + if (ret != KNOT_EOK) { + assert(0); + return ret; + } + +// dbg_xfrin("After copy: Found RRSet with owner %s, type %s\n", +// knot_dname_to_str((*rrset)->owner), +// knot_rrtype_to_string(knot_rrset_type(*rrset))); + + // merge the changeset RRSet to the copy + /* What if the update fails? + * The changesets will be destroyed - that will destroy 'add', + * and the copied RRSet will be destroyed because it is in the new + * rrsets list. + * + * If the update is successfull, the old RRSet will be destroyed, + * but the one from the changeset will be not!! + * + * TODO: add the 'add' rrset to list of old RRSets? + */ + dbg_xfrin("Merging RRSets with owners: %s %s types: %d %d\n", + (*rrset)->owner->name, add->owner->name, (*rrset)->type, + add->type); + dbg_xfrin_detail("RDATA in RRSet1: %p, RDATA in RRSet2: %p\n", + (*rrset)->rdata, add->rdata); + ret = knot_rrset_merge((void **)rrset, (void **)&add); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to merge changeset RRSet to copy.\n"); + return KNOT_ERROR; + } + dbg_xfrin("Merge returned: %d\n", ret); + knot_rrset_dump(*rrset, 1); + ret = knot_node_add_rrset(node, *rrset, 0); + + // return 2 so that the add RRSet is removed from + // the changeset (and thus not deleted) + // and put to list of new RRSets (is this ok?) + // and deleted + return 2; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_apply_add_rrsig(xfrin_changes_t *changes, + knot_rrset_t *add, + knot_node_t *node, + knot_rrset_t **rrset) +{ + assert(changes != NULL); + assert(add != NULL); + assert(node != NULL); + assert(rrset != NULL); + assert(knot_rrset_type(add) == KNOT_RRTYPE_RRSIG); + + int ret; + + knot_rr_type_t type = knot_rdata_rrsig_type_covered( + knot_rrset_rdata(add)); + + if (!*rrset + || knot_dname_compare(knot_rrset_owner(*rrset), + knot_node_owner(node)) != 0 + || knot_rrset_type(*rrset) != knot_rdata_rrsig_type_covered( + knot_rrset_rdata(add))) { + // copy the rrset + ret = xfrin_copy_rrset(node, type, rrset, changes); + if (ret < 0) { + return ret; + } + } else { + // we should have the right RRSIG RRSet in *rrset + assert(knot_rrset_type(*rrset) == type); + // this RRSet should be the already copied RRSet so we may + // update it right away + } + + if (*rrset == NULL) { + dbg_xfrin("RRSet to be added not found in zone.\n"); + + // create a new RRSet to add the RRSIGs into + *rrset = knot_rrset_new(knot_node_get_owner(node), type, + knot_rrset_class(add), + knot_rrset_ttl(add)); + if (*rrset == NULL) { + dbg_xfrin("Failed to create new RRSet for RRSIGs.\n"); + return KNOT_ENOMEM; + } + + // add the RRSet from the changeset to the node + ret = knot_node_add_rrset(node, *rrset, 0); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add RRSet to node.\n"); + return KNOT_ERROR; + } + } + +dbg_xfrin_exec( + char *name = knot_dname_to_str(knot_rrset_owner(*rrset)); + dbg_xfrin("Found RRSet with owner %s, type %s\n", name, + knot_rrtype_to_string(knot_rrset_type(*rrset))); + free(name); +); + + if (knot_rrset_rrsigs(*rrset) == NULL) { + ret = knot_rrset_set_rrsigs(*rrset, add); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add RRSIGs to the RRSet.\n"); + return KNOT_ERROR; + } + + return 1; + } else { + knot_rrset_t *old = knot_rrset_get_rrsigs(*rrset); + assert(old != NULL); + knot_rrset_t *rrsig; + + ret = xfrin_copy_old_rrset(old, &rrsig, changes); + if (ret != KNOT_EOK) { + return ret; + } + + // replace the old RRSIGs with the new ones + knot_rrset_set_rrsigs(*rrset, rrsig); + + // merge the changeset RRSet to the copy + /*! \todo What if the update fails? + * + */ + ret = knot_rrset_merge((void **)&rrsig, (void **)&add); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to merge changeset RRSet to copy.\n"); + return KNOT_ERROR; + } + + return 2; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_apply_add(knot_zone_contents_t *contents, + knot_changeset_t *chset, + xfrin_changes_t *changes) +{ + // iterate over removed RRSets, copy appropriate nodes and remove + // the rrsets from them + int ret = 0; + knot_node_t *node = NULL; + knot_rrset_t *rrset = NULL; + + for (int i = 0; i < chset->add_count; ++i) { + dbg_xfrin_detail("Adding RRSet:\n"); + knot_rrset_dump(chset->add[i], 0); + // check if the old node is not the one we should use + if (!node || knot_rrset_owner(chset->add[i]) + != knot_node_owner(node)) { + node = knot_zone_contents_get_node(contents, + knot_rrset_owner(chset->add[i])); + if (node == NULL) { + // create new node, connect it properly to the + // zone nodes + dbg_xfrin("Creating new node from.\n"); + node = xfrin_add_new_node(contents, + chset->add[i]); + if (node == NULL) { + dbg_xfrin("Failed to create new node " + "in zone.\n"); + return KNOT_ERROR; + } +// continue; // continue with another RRSet + } + } + + // create a copy of the node if not already created + if (!knot_node_is_new(node)) { + xfrin_get_node_copy(&node, changes); + } + + assert(node != NULL); + assert(knot_node_is_new(node)); + + if (knot_rrset_type(chset->add[i]) == KNOT_RRTYPE_RRSIG) { + ret = xfrin_apply_add_rrsig(changes, chset->add[i], + node, &rrset); + } else { + ret = xfrin_apply_add_normal(changes, chset->add[i], + node, &rrset); + } + + dbg_xfrin("xfrin_apply_..() returned %d, rrset: %p\n", ret, + rrset); + + if (ret == 1) { + // the ADD RRSet was used, i.e. it should be removed + // from the changeset and saved in the list of new + // RRSets + ret = xfrin_changes_check_rrsets( + &changes->new_rrsets, + &changes->new_rrsets_count, + &changes->new_rrsets_allocated, 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add old RRSet to list.\n"); + return ret; + } + + changes->new_rrsets[changes->new_rrsets_count++] = + chset->add[i]; + + chset->add[i] = NULL; + } else if (ret == 2) { + // the copy of the RRSet was used, but it was already + // stored in the new RRSets list + // just delete the add RRSet, but without RDATA + // as these were merged to the copied RRSet + knot_rrset_free(&chset->add[i]); + } else if (ret != KNOT_EOK) { + + return ret; + } + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \todo This must be tested!! Simulate failure somehow. + */ +static void xfrin_clean_changes_after_fail(xfrin_changes_t *changes) +{ + /* 1) Delete copies of RRSets created because they were updated. + * Do not delete their RDATA or owners. + */ + for (int i = 0; i < changes->new_rrsets_count; ++i) { + knot_rrset_free(&changes->new_rrsets[i]); + } + + /* 2) Delete copies of nodes created because they were updated. + * Do not delete their RRSets. + */ + for (int i = 0; i < changes->new_nodes_count; ++i) { + knot_node_free(&changes->new_nodes[i], 0, 1); + } + + // changesets will be deleted elsewhere + // so just delete the changes structure + xfrin_changes_free(&changes); +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_apply_replace_soa(knot_zone_contents_t *contents, + xfrin_changes_t *changes, + knot_changeset_t *chset) +{ + knot_node_t *node = knot_zone_contents_get_apex(contents); + assert(node != NULL); + + int ret = 0; + + // create a copy of the node if not already created + if (!knot_node_is_new(node)) { + ret = xfrin_get_node_copy(&node, changes); + if (ret != KNOT_EOK) { + return ret; + } + } + + assert(knot_node_is_new(node)); + + // set the node copy as the apex of the contents + contents->apex = node; + + // remove the SOA RRSet from the apex + knot_rrset_t *rrset = knot_node_remove_rrset(node, KNOT_RRTYPE_SOA); + assert(rrset != NULL); + + // add the old RRSet to the list of old RRSets + ret = xfrin_changes_check_rrsets(&changes->old_rrsets, + &changes->old_rrsets_count, + &changes->old_rrsets_allocated, 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add old RRSet to list.\n"); + return ret; + } + + // save also the SOA RDATA, because RDATA are not deleted with the + // RRSet + ret = xfrin_changes_check_rdata(&changes->old_rdata, + &changes->old_rdata_types, + changes->old_rdata_count, + &changes->old_rdata_allocated, 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add old RDATA to list.\n"); + return ret; + } + + // save the SOA to the new RRSet, so that it is deleted if the + // apply fails + ret = xfrin_changes_check_rrsets(&changes->new_rrsets, + &changes->new_rrsets_count, + &changes->new_rrsets_allocated, 1); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add old RRSet to list.\n"); + return ret; + } + + changes->old_rrsets[changes->old_rrsets_count++] = rrset; + + /*! \todo Maybe check if the SOA does not have more RDATA? */ + changes->old_rdata[changes->old_rdata_count] = rrset->rdata; + changes->old_rdata_types[changes->old_rdata_count] = KNOT_RRTYPE_SOA; + ++changes->old_rdata_count; + + // insert the new SOA RRSet to the node + dbg_xfrin_verb("Adding SOA.\n"); + ret = knot_node_add_rrset(node, chset->soa_to, 0); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to add RRSet to node.\n"); + return KNOT_ERROR; + } + + changes->new_rrsets[changes->new_rrsets_count++] = chset->soa_to; + + // remove the SOA from the changeset, so it will not be deleted after + // successful apply + chset->soa_to = NULL; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_apply_changeset(knot_zone_contents_t *contents, + xfrin_changes_t *changes, + knot_changeset_t *chset) +{ + /* + * Applies one changeset to the zone. Checks if the changeset may be + * applied (i.e. the origin SOA (soa_from) has the same serial as + * SOA in the zone apex. + */ + + // check if serial matches + /*! \todo Only if SOA is present? */ + const knot_rrset_t *soa = knot_node_rrset(contents->apex, + KNOT_RRTYPE_SOA); + if (soa == NULL || knot_rdata_soa_serial(knot_rrset_rdata(soa)) + != chset->serial_from) { + dbg_xfrin("SOA serials do not match!!\n"); + return KNOT_ERROR; + } + + int ret = xfrin_apply_remove(contents, chset, changes); + if (ret != KNOT_EOK) { + xfrin_clean_changes_after_fail(changes); + return ret; + } + + ret = xfrin_apply_add(contents, chset, changes); + if (ret != KNOT_EOK) { + xfrin_clean_changes_after_fail(changes); + return ret; + } + + /*! \todo Only if SOA is present? */ + return xfrin_apply_replace_soa(contents, changes, chset); +} + +/*----------------------------------------------------------------------------*/ + +static void xfrin_check_node_in_tree(knot_zone_tree_node_t *tnode, void *data) +{ + assert(tnode != NULL); + assert(data != NULL); + assert(tnode->node != NULL); + + xfrin_changes_t *changes = (xfrin_changes_t *)data; + + knot_node_t *node = knot_node_get_new_node(tnode->node); + + if (node == NULL) { + // no RRSets were removed from this node, thus it cannot be + // empty + return; + } + + dbg_xfrin("xfrin_check_node_in_tree: children of old node: %u, " + "children of new node: %u.\n", + knot_node_children(node), + knot_node_children(tnode->node)); + + + // check if the node is empty and has no children + // to be sure, check also the count of children of the old node + if (knot_node_rrset_count(node) == 0 + && knot_node_children(node) == 0 + && knot_node_children(tnode->node) == 0) { + // in this case the new node copy should be removed + // but it cannot be deleted because if a rollback happens, + // the node must be in the new nodes list + // just add it to the old nodes list so that it is deleted + // after successful update + + // set the new node of the old node to NULL + knot_node_set_new_node(tnode->node, NULL); + + // if the parent has a new copy, decrease the number of + // children of that copy + if (knot_node_new_node(knot_node_parent(node, 0))) { + /*! \todo Replace by some API. */ + --node->parent->new_node->children; + } + + // put the new node to te list of old nodes + if (xfrin_changes_check_nodes(&changes->old_nodes, + &changes->old_nodes_count, + &changes->old_nodes_allocated) + != KNOT_EOK) { + /*! \todo Notify about the error!!! */ + return; + } + + changes->old_nodes[changes->old_nodes_count++] = node; + + // leave the old node in the old node list, we will delete + // it later + } +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_finalize_remove_nodes(knot_zone_contents_t *contents, + xfrin_changes_t *changes) +{ + assert(contents != NULL); + assert(changes != NULL); + + knot_node_t *node; + knot_zone_tree_node_t *removed; + ck_hash_table_item_t *rem_hash; + int ret; + + for (int i = 0; i < changes->old_nodes_count; ++i) { + node = changes->old_nodes[i]; + + // if the node is marked as old and has no new node copy + // remove it from the zone structure but do not delete it + // that may be done only after the grace period + if (knot_node_is_old(node) + && knot_node_new_node(node) == NULL) { + + if (knot_node_rrset(node, KNOT_RRTYPE_NSEC3) + != NULL) { + ret = knot_zone_contents_remove_nsec3_node( + contents, node, &removed); + } else { + ret = knot_zone_contents_remove_node( + contents, node, &removed, &rem_hash); + } + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to remove node from zone" + "!\n"); + return KNOT_ENONODE; + } + + assert(removed != NULL); + assert(removed->node == node); + // delete the tree node (not needed) + free(removed); + + if (rem_hash != NULL) { + // save the removed hash table item + ret = xfrin_changes_check_hash_items( + &changes->old_hash_items, + &changes->old_hash_items_count, + &changes->old_hash_items_allocated); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to save the hash" + " table item to list of " + "old items.\n"); + return ret; + } + changes->old_hash_items[ + changes->old_hash_items_count++] + = rem_hash; + } + } + } + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_finalize_contents(knot_zone_contents_t *contents, + xfrin_changes_t *changes) +{ + // don't know what should have been done here, except for one thing: + // walk through the zone and remove empty nodes (save them in the + // old nodes list). But only those having no children!!! + + /* + * Walk through the zone and remove empty nodes. + * We must walk backwards, so that children are processed before + * their parents. This will allow to remove chain of parent-children + * nodes. + * We cannot remove the nodes right away as it would modify the very + * structure used for walking through the zone. Just put the nodes + * to the list of old nodes to be removed. + * We must also decrease the node's parent's children count now + * and not when deleting the node, so that the chain of parent-child + * nodes may be removed. + */ + knot_zone_tree_t *t = knot_zone_contents_get_nodes(contents); + assert(t != NULL); + + // walk through the zone and select nodes to be removed + knot_zone_tree_reverse_apply_postorder(t, xfrin_check_node_in_tree, + (void *)changes); + + // Do the same with NSEC3 nodes. + t = knot_zone_contents_get_nsec3_nodes(contents); + assert(t != NULL); + + knot_zone_tree_reverse_apply_postorder(t, xfrin_check_node_in_tree, + (void *)changes); + + // remove the nodes one by one + return xfrin_finalize_remove_nodes(contents, changes); +} + +/*----------------------------------------------------------------------------*/ + +static void xfrin_fix_refs_in_node(knot_zone_tree_node_t *tnode, void *data) +{ + /*! \todo Passed data is always seto to NULL. */ + assert(tnode != NULL); + //assert(data != NULL); + + //xfrin_changes_t *changes = (xfrin_changes_t *)data; + + // 1) Fix the reference to the node to the new one if there is some + knot_node_t *node = tnode->node; + + knot_node_t *new_node = knot_node_get_new_node(node); + if (new_node != NULL) { + //assert(knot_node_rrset_count(new_node) > 0); + node = new_node; + tnode->node = new_node; + } + + // 2) fix references from the node remaining in the zone + knot_node_update_refs(node); +} + +/*----------------------------------------------------------------------------*/ + +static void xfrin_fix_gen_in_node(knot_zone_tree_node_t *tnode, void *data) +{ + /*! \todo Passed data is always seto to NULL. */ + assert(tnode != NULL); + + knot_node_t *node = tnode->node; + + knot_node_set_old(node); +} + +/*----------------------------------------------------------------------------*/ + +static void xfrin_fix_hash_refs(ck_hash_table_item_t *item, void *data) +{ + if (item == NULL) { + return; + } + + knot_node_t *new_node = knot_node_get_new_node( + (knot_node_t *)item->value); + if (new_node != NULL) { + assert(item->key_length + == knot_dname_size(knot_node_owner(new_node))); + assert(strncmp(item->key, (const char *)knot_dname_name( + knot_node_owner(new_node)), item->key_length) == 0); + item->value = (void *)new_node; + item->key = (const char *)knot_dname_name( + knot_node_owner(new_node)); + } +} + +/*----------------------------------------------------------------------------*/ + +static void xfrin_fix_dname_refs(knot_dname_t *dname, void *data) +{ + UNUSED(data); + knot_dname_update_node(dname); +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_fix_references(knot_zone_contents_t *contents) +{ + /*! \todo This function must not fail!! */ + + /* + * Now the contents are already switched, and we should update all + * references not updated yet, so that the old contents may be removed. + * + * Walk through the zone tree, so that each node will be checked + * and updated. + */ + // fix references in normal nodes + knot_zone_tree_t *tree = knot_zone_contents_get_nodes(contents); + knot_zone_tree_forward_apply_inorder(tree, xfrin_fix_refs_in_node, + NULL); + + // fix refereces in NSEC3 nodes + tree = knot_zone_contents_get_nsec3_nodes(contents); + knot_zone_tree_forward_apply_inorder(tree, xfrin_fix_refs_in_node, + NULL); + + // fix references in hash table + ck_hash_table_t *table = knot_zone_contents_get_hash_table(contents); + ck_apply(table, xfrin_fix_hash_refs, NULL); + + // fix references dname table + int ret = knot_zone_contents_dname_table_apply(contents, + xfrin_fix_dname_refs, NULL); + assert(ret == KNOT_EOK); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int xfrin_fix_generation(knot_zone_contents_t *contents) +{ + assert(contents != NULL); + + knot_zone_tree_t *tree = knot_zone_contents_get_nodes(contents); + knot_zone_tree_forward_apply_inorder(tree, xfrin_fix_gen_in_node, + NULL); + + tree = knot_zone_contents_get_nsec3_nodes(contents); + knot_zone_tree_forward_apply_inorder(tree, xfrin_fix_gen_in_node, + NULL); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static void xfrin_cleanup_update(xfrin_changes_t *changes) +{ + // free old nodes but do not destroy their RRSets + // remove owners also, because of reference counting + for (int i = 0; i < changes->old_nodes_count; ++i) { + dbg_xfrin_detail("Deleting old node: %p\n", changes->old_nodes[i]); + knot_node_dump(changes->old_nodes[i], 0); + knot_node_free(&changes->old_nodes[i], 1, 0); + } + + // free old RRSets, and destroy also domain names in them + // because of reference counting + + // check if there are not some duplicate RRSets +// for (int i = 0; i < changes->old_rrsets_count; ++i) { +// for (int j = i + 1; j < changes->old_rrsets_count; ++j) { +// if (changes->old_rrsets[i] == changes->old_rrsets[j]) { +// assert(0); +// } +// if (changes->old_rrsets[i]->rdata != NULL +// && changes->old_rrsets[i]->rdata +// == changes->old_rrsets[j]->rdata) { +// assert(0); +// } +// } +// } + + for (int i = 0; i < changes->old_rrsets_count; ++i) { +// knot_rrset_deep_free(&changes->old_rrsets[i], 1, 1, 1); + dbg_xfrin_detail("Deleting old RRSet: %p\n", changes->old_rrsets[i]); + knot_rrset_dump(changes->old_rrsets[i], 0); + knot_rrset_free(&changes->old_rrsets[i]); + } + + // delete old RDATA + for (int i = 0; i < changes->old_rdata_count; ++i) { + dbg_xfrin_detail("Deleting old RDATA: %p, type: %s\n", + changes->old_rdata[i], + knot_rrtype_to_string(changes->old_rdata_types[i])); + knot_rdata_dump(changes->old_rdata[i], changes->old_rdata_types[i], 0); + knot_rdata_t *rdata = changes->old_rdata[i]; + assert(rdata != NULL); + do { + knot_rdata_t *tmp = rdata->next; + knot_rdata_deep_free(&rdata, + changes->old_rdata_types[i], 1); + rdata = tmp; + } while (rdata != NULL && rdata != changes->old_rdata[i]); + changes->old_rdata[i] = NULL; + } + + // free old hash table items, but do not touch their contents + for (int i = 0; i < changes->old_hash_items_count; ++i) { + free(changes->old_hash_items[i]); + } + free(changes->old_hash_items); + + // free allocated arrays of nodes and rrsets + free(changes->new_nodes); + free(changes->old_nodes); + free(changes->new_rrsets); + free(changes->old_rrsets); + free(changes->old_rdata); + free(changes->old_rdata_types); +} + +/*----------------------------------------------------------------------------*/ + +int xfrin_apply_changesets_to_zone(knot_zone_t *zone, + knot_changesets_t *chsets) +{ + if (zone == NULL || chsets == NULL || chsets->count == 0) { + return KNOT_EBADARG; + } + + knot_zone_contents_t *old_contents = knot_zone_get_contents(zone); + if (!old_contents) { + return KNOT_EBADARG; + } + +// dbg_xfrin("\nOLD ZONE CONTENTS:\n\n"); +// knot_zone_contents_dump(old_contents, 1); + + /* + * Ensure that the zone generation is set to 0. + */ + if (!knot_zone_contents_gen_is_old(old_contents)) { + // this would mean that a previous update was not completed + // abort + dbg_zone("Trying to apply changesets to zone that is " + "being updated. Aborting.\n"); + return KNOT_EAGAIN; + } + + /* + * Create a shallow copy of the zone, so that the structures may be + * updated. + * + * This will create new zone contents structures (normal nodes' tree, + * NSEC3 tree, hash table, domain name table), but fill them with the + * data from the old contents. + */ + knot_zone_contents_t *contents_copy = NULL; + + int ret = knot_zone_contents_shallow_copy(old_contents, + &contents_copy); + if (ret != KNOT_EOK) { + dbg_xfrin("Failed to create shallow copy of zone: %s\n", + knot_strerror(ret)); + return ret; + } + + /* + * Now, apply one changeset after another until all are applied. + * Changesets may be either from IXFR or from a dynamic update. + * Dynamic updates use special TYPE and CLASS values to distinguish + * requests, such as "remove all RRSets from a node", "remove all RRs + * with the specified type from a node", etc. + * + * When updating anything within some node (removing RR, adding RR), + * the node structure is copied, but the RRSets within are not. + * + * 1) When removing RRs from node, The affected RRSet is copied. This + * it also a 'shallow copy', i.e. the RDATA remain the exact same. + * The specified RRs (i.e. RDATA) are then removed from the copied + * RRSet. + * 2) When adding RRs to node, there are two cases: + * a) If there is a RRSet that should contain these RRs + * this RRSet is copied (shallow copy) and the RRs are added to + * it (rrset_merge()). + * b) If there is not such a RRSet, the whole RRSet from the + * changeset is added to the new node (thus this RRSet must not + * be deleted afterwards). + * + * A special case are RRSIG RRs. These functions assume that they + * are grouped together in knot_rrset_t structures according to + * their header (owner, type, class) AND their 'type covered', i.e. + * there may be more RRSIG RRSets in one changeset (while there + * should not be more RRSets of any other type). + * 3) When removing RRSIG RRs from node, the appropriate RRSet holding + * them must be found (according to the 'type covered' field). This + * RRSet is then copied (shallow copy), Its RRSIGs are also copied + * and the RRSIG RRs are added to the RRSIG copy. + * 4) When adding RRSIG RRs to node, the same process is done - the + * proper RRSet holding them is found, copied, its RRSIGs are + * copied (if there are some) and the RRs are added to the copy. + * + * When a node is copied, reference to the copy is stored within the + * old node (node_t.old_node). This is important, because when the + * zone contents are switched to the new ones, references from old nodes + * that should point to new nodes are not yet set (it would influence + * replying from the old zone contents). While all these references + * (such as node_t.prev, node_t.next, node_t.parent, etc.) are properly + * modified, the search functions use old or new nodes accordingly + * (old nodes while old contents are used, new nodes when new contents + * are used). The 'check_version' parameter turns on this behaviour in + * search functions. + * + * In case of error, we must remove all data created by the update, i.e. + * - new nodes, + * - new RRSets, + * and remove the references to the new nodes from old nodes. + * + * In case of success, the RRSet structures from the changeset + * structures must not be deleted, as they are either already used by + * the server (stored within the new zone contents) or deleted when + * cleaning up the temporary 'changes' structure. + */ + xfrin_changes_t changes; + memset(&changes, 0, sizeof(xfrin_changes_t)); + + for (int i = 0; i < chsets->count; ++i) { + if ((ret = xfrin_apply_changeset(contents_copy, &changes, + &chsets->sets[i])) != KNOT_EOK) { + xfrin_rollback_update(contents_copy, &changes); + dbg_xfrin("Failed to apply changesets to zone: " + "%s\n", knot_strerror(ret)); + return ret; + } + } + + /* + * When all changesets are applied, set generation 1 to the copy of + * the zone so that new nodes are used instead of old ones. + */ +// knot_zone_contents_switch_generation(contents_copy); + //contents_copy->generation = 1; + knot_zone_contents_set_gen_new(contents_copy); + + /* + * Finalize the zone contents. + */ + ret = xfrin_finalize_contents(contents_copy, &changes); + if (ret != KNOT_EOK) { + xfrin_rollback_update(contents_copy, &changes); + dbg_xfrin("Failed to finalize new zone contents: %s\n", + knot_strerror(ret)); + return ret; + } + + /* + * Switch the zone contents + */ + knot_zone_contents_t *old = + knot_zone_switch_contents(zone, contents_copy); + assert(old == old_contents); + + /* + * From now on, the new contents of the zone are being used. + * References to nodes may be updated in the meantime. However, we must + * traverse the zone and fix all references that were not. + */ + /*! \todo This operation must not fail!!! .*/ + ret = xfrin_fix_references(contents_copy); + assert(ret == KNOT_EOK); + + // set generation to finished + knot_zone_contents_set_gen_new_finished(contents_copy); + + // set generation of all nodes to the old one + // now it is safe (no old nodes should be referenced) + ret = xfrin_fix_generation(contents_copy); + assert(ret == KNOT_EOK); + + /* + * Now we may also set the generation back to 0 so that another + * update is possible. + */ + knot_zone_contents_set_gen_old(contents_copy); + + /* + * Wait until all readers finish reading + */ + synchronize_rcu(); + + /* + * Delete all old and unused data. + */ + xfrin_zone_contents_free(&old_contents); + xfrin_cleanup_update(&changes); + + return KNOT_EOK; +} diff --git a/src/libknot/updates/xfr-in.h b/src/libknot/updates/xfr-in.h new file mode 100644 index 0000000..8a7c64b --- /dev/null +++ b/src/libknot/updates/xfr-in.h @@ -0,0 +1,184 @@ +/*! + * \file xfr-in.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief XFR client API. + * + * \addtogroup query_processing + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_XFR_IN_H_ +#define _KNOT_XFR_IN_H_ + +#include <stdint.h> +#include <string.h> + +#include "dname.h" +#include "zone/zone.h" +#include "packet/packet.h" +#include "nameserver/name-server.h" +#include "updates/changesets.h" + +/*----------------------------------------------------------------------------*/ + +typedef struct xfrin_orphan_rrsig { + knot_rrset_t *rrsig; + struct xfrin_orphan_rrsig *next; +} xfrin_orphan_rrsig_t; + +typedef struct xfrin_constructed_zone { + knot_zone_contents_t *contents; + xfrin_orphan_rrsig_t *rrsigs; +} xfrin_constructed_zone_t; + +typedef enum xfrin_transfer_result { + XFRIN_RES_COMPLETE = 1, + XFRIN_RES_SOA_ONLY = 2, + XFRIN_RES_FALLBACK = 3 +} xfrin_transfer_result_t; + +/*----------------------------------------------------------------------------*/ + +/*! + * \brief Creates normal query for the given zone name and the SOA type. + * + * \param owner Zone owner. + * \param buffer Buffer to fill the message in. + * \param size In: available space in the buffer. Out: actual size of the + * message in bytes. + * + * \retval KNOT_EOK + * \retval KNOT_ESPACE + * \retval KNOT_ERROR + */ +int xfrin_create_soa_query(knot_dname_t *owner, knot_ns_xfr_t *xfr, + size_t *size); + +/*! + * \brief Checks if a zone transfer is required by comparing the zone's SOA with + * the one received from master server. + * + * \param zone Zone to check. + * \param soa_response Response to SOA query received from master server. + * + * \retval < 0 if an error occured. + * \retval 1 if the transfer is needed. + * \retval 0 if the transfer is not needed. + */ +int xfrin_transfer_needed(const knot_zone_contents_t *zone, + knot_packet_t *soa_response); + +/*! + * \brief Creates normal query for the given zone name and the AXFR type. + * + * \param owner Zone owner. + * \param xfr Data structure holding important data for the query, namely + * pointer to the buffer for wireformat and TSIG data. + * \param size In: available space in the buffer. Out: actual size of the + * message in bytes. + * \param use_tsig If TSIG should be used. + * + * \todo Parameter use_tsig probably not needed. + * + * \retval KNOT_EOK + * \retval KNOT_ESPACE + * \retval KNOT_ERROR + */ +int xfrin_create_axfr_query(knot_dname_t *owner, knot_ns_xfr_t *xfr, + size_t *size, int use_tsig); + +/*! + * \brief Creates normal query for the given zone name and the IXFR type. + * + * \param zone Zone contents. + * \param buffer Buffer to fill the message in. + * \param size In: available space in the buffer. Out: actual size of the + * message in bytes. + * \param use_tsig If TSIG should be used. + * + * \todo Parameter use_tsig probably not needed. + * + * \retval KNOT_EOK + * \retval KNOT_ESPACE + * \retval KNOT_ERROR + */ +int xfrin_create_ixfr_query(const knot_zone_contents_t *zone, + knot_ns_xfr_t *xfr, size_t *size, int use_tsig); + +/*! + * \brief Processes the newly created transferred zone. + * + * \param nameserver Name server to update. + * \param zone Zone build from transfer. + * + * \retval KNOT_ENOTSUP + */ +int xfrin_zone_transferred(knot_nameserver_t *nameserver, + knot_zone_contents_t *zone); + +/*! + * \brief Processes one incoming packet of AXFR transfer by updating the given + * zone. + * + * \param pkt Incoming packet in wire format. + * \param size Size of the packet in bytes. + * \param zone Zone being built. If there is no such zone (i.e. this is the + * first packet, \a *zone may be set to NULL, in which case a new + * zone structure is created). + * + * \retval KNOT_EOK + * + * \todo Refactor!!! + */ +int xfrin_process_axfr_packet(/*const uint8_t *pkt, size_t size, + xfrin_constructed_zone_t **zone*/ + knot_ns_xfr_t *xfr); + +/*! + * \brief Destroys the whole changesets structure. + * + * Frees all RRSets present in the changesets and all their data. Also frees + * the changesets structure and sets the parameter to NULL. + * + * \param changesets Changesets to destroy. + */ +void xfrin_free_changesets(knot_changesets_t **changesets); + +/*! + * \brief Parses IXFR reply packet and fills in the changesets structure. + * + * \param pkt Packet containing the IXFR reply in wire format. + * \param size Size of the packet in bytes. + * \param changesets Changesets to be filled in. + * + * \retval KNOT_EOK + * \retval KNOT_EINVAL + * \retval KNOT_EMALF + * \retval KNOT_ENOMEM + */ +int xfrin_process_ixfr_packet(knot_ns_xfr_t *xfr/*const uint8_t *pkt, size_t size, + knot_changesets_t **changesets*/); + +int xfrin_apply_changesets_to_zone(knot_zone_t *zone, + knot_changesets_t *chsets); + +#endif /* _KNOTXFR_IN_H_ */ + +/*! @} */ diff --git a/src/libknot/util/debug.c b/src/libknot/util/debug.c new file mode 100644 index 0000000..0ca67c9 --- /dev/null +++ b/src/libknot/util/debug.c @@ -0,0 +1,233 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdint.h> +#include <assert.h> +#include <stdlib.h> + +#include "util/utils.h" +#include "util/debug.h" +#include "libknot.h" +#include "common/print.h" + +void knot_rdata_dump(knot_rdata_t *rdata, uint32_t type, char loaded_zone) +{ +#if defined(KNOT_ZONE_DEBUG) || defined(KNOT_RDATA_DEBUG) + fprintf(stderr, " ------- RDATA -------\n"); + if (rdata == NULL) { + fprintf(stderr, " There are no rdata in this RRset!\n"); + fprintf(stderr, " ------- RDATA -------\n"); + return; + } + knot_rrtype_descriptor_t *desc = knot_rrtype_descriptor_by_type(type); + assert(desc != NULL); + char *name; + + for (int i = 0; i < rdata->count; i++) { + if (rdata->items[i].raw_data == NULL) { + continue; + } + if (desc->wireformat[i] == KNOT_RDATA_WF_COMPRESSED_DNAME || + desc->wireformat[i] == KNOT_RDATA_WF_UNCOMPRESSED_DNAME || + desc->wireformat[i] == KNOT_RDATA_WF_LITERAL_DNAME ) { + assert(rdata->items[i].dname != NULL); + name = knot_dname_to_str(rdata->items[i].dname); + fprintf(stderr, " DNAME: %d: %s\n", + i, name); + free(name); + if (loaded_zone) { + if (rdata->items[i].dname->node) { + name = + knot_dname_to_str(rdata->items[i].dname->node->owner); + fprintf(stderr, " Has node owner: %s\n", name); + free(name); + } else { + fprintf(stderr, " No node set\n"); + } + } + fprintf(stderr, " labels: "); + hex_print((char *)rdata->items[i].dname->labels, + rdata->items[i].dname->label_count); + + } else { + assert(rdata->items[i].raw_data != NULL); + fprintf(stderr, " %d: raw_data: length: %d\n", i, + *(rdata->items[i].raw_data)); + fprintf(stderr, " "); + hex_print(((char *)( + rdata->items[i].raw_data + 1)), + rdata->items[i].raw_data[0]); + } + } + fprintf(stderr, " ------- RDATA -------\n"); +#endif +} + +void knot_rrset_dump(const knot_rrset_t *rrset, char loaded_zone) +{ +#if defined(KNOT_ZONE_DEBUG) || defined(KNOT_RRSET_DEBUG) + fprintf(stderr, " ------- RRSET -------\n"); + fprintf(stderr, " %p\n", rrset); + if (!rrset) { + return; + } + char *name = knot_dname_to_str(rrset->owner); + fprintf(stderr, " owner: %s\n", name); + free(name); + fprintf(stderr, " type: %s\n", knot_rrtype_to_string(rrset->type)); + fprintf(stderr, " class: %d\n", rrset->rclass); + fprintf(stderr, " ttl: %d\n", rrset->ttl); + + fprintf(stderr, " RRSIGs:\n"); + if (rrset->rrsigs != NULL) { + knot_rrset_dump(rrset->rrsigs, loaded_zone); + } else { + fprintf(stderr, " none\n"); + } + + if (rrset->rdata == NULL) { + fprintf(stderr, " NO RDATA!\n"); + fprintf(stderr, " ------- RRSET -------\n"); + return; + } + + fprintf(stderr, " rdata count: %d\n", rrset->rdata->count); + knot_rdata_t *tmp = rrset->rdata; + + while (tmp->next != rrset->rdata && tmp->next != NULL) { + knot_rdata_dump(tmp, rrset->type, loaded_zone); + tmp = tmp->next; + } + + knot_rdata_dump(tmp, rrset->type, loaded_zone); + + fprintf(stderr, " ------- RRSET -------\n"); +#endif +} + +void knot_node_dump(knot_node_t *node, void *loaded_zone) +{ +#if defined(KNOT_ZONE_DEBUG) || defined(KNOT_NODE_DEBUG) + //char loaded_zone = *((char*) data); + char *name; + + fprintf(stderr, "------- NODE --------\n"); + name = knot_dname_to_str(node->owner); + fprintf(stderr, "owner: %s\n", name); + free(name); + fprintf(stderr, "labels: "); + hex_print((char *)node->owner->labels, node->owner->label_count); + fprintf(stderr, "node: %p\n", node); + fprintf(stderr, "node (in node's owner): %p\n", node->owner->node); + if (loaded_zone && node->prev != NULL) { + name = knot_dname_to_str(node->prev->owner); + fprintf(stderr, "previous node: %s\n", name); + free(name); + } + + if (knot_node_is_deleg_point(node)) { + fprintf(stderr, "delegation point\n"); + } + + if (knot_node_is_non_auth(node)) { + fprintf(stderr, "non-authoritative node\n"); + } + + if (node->parent != NULL) { + /*! \todo This causes segfault when parent was free'd, + * e.g. when applying changesets. + */ + name = knot_dname_to_str(node->parent->owner); + fprintf(stderr, "parent: %s\n", name); + free(name); + } else { + fprintf(stderr, "no parent\n"); + } + + if (node->prev != NULL) { + fprintf(stderr, "previous node: %p\n", node->prev); + /*! \todo This causes segfault when prev was free'd, + * e.g. when applying changesets. + */ + name = knot_dname_to_str(node->prev->owner); + fprintf(stderr, "previous node: %s\n", name); + free(name); + } else { + fprintf(stderr, "previous node: none\n"); + } + + knot_rrset_t **rrsets = knot_node_get_rrsets(node); + + fprintf(stderr, "Wildcard child: "); + + if (node->wildcard_child != NULL) { + /*! \todo This causes segfault when wildcard child was free'd, + * e.g. when applying changesets. + */ + name = knot_dname_to_str(node->wildcard_child->owner); + fprintf(stderr, "%s\n", name); + free(name); + } else { + fprintf(stderr, "none\n"); + } + + fprintf(stderr, "NSEC3 node: "); + + if (node->nsec3_node != NULL) { + /*! \todo This causes segfault when nsec3_node was free'd, + * e.g. when applying changesets. + */ + name = knot_dname_to_str(node->nsec3_node->owner); + fprintf(stderr, "%s\n", name); + free(name); + } else { + fprintf(stderr, "none\n"); + } + + fprintf(stderr, "RRSet count: %d\n", node->rrset_count); + + for (int i = 0; i < node->rrset_count; i++) { + knot_rrset_dump(rrsets[i], (int) loaded_zone); + } + free(rrsets); + //assert(node->owner->node == node); + fprintf(stderr, "------- NODE --------\n"); +#endif +} + +void knot_zone_contents_dump(knot_zone_contents_t *zone, char loaded_zone) +{ +#if defined(KNOT_ZONE_DEBUG) + if (!zone) { + fprintf(stderr, "------- STUB ZONE --------\n"); + return; + } + + fprintf(stderr, "------- ZONE --------\n"); + + knot_zone_contents_tree_apply_inorder(zone, knot_node_dump, (void *)&loaded_zone); + + fprintf(stderr, "------- ZONE --------\n"); + + fprintf(stderr, "------- NSEC 3 tree -\n"); + + knot_zone_contents_nsec3_apply_inorder(zone, knot_node_dump, (void *)&loaded_zone); + + fprintf(stderr, "------- NSEC 3 tree -\n"); +#endif +} diff --git a/src/libknot/util/debug.h b/src/libknot/util/debug.h new file mode 100644 index 0000000..2f9f5fd --- /dev/null +++ b/src/libknot/util/debug.h @@ -0,0 +1,755 @@ +/*! + * \file debug.h + * + * \author Jan Kadlec <jan.kadlec.@nic.cz> + * \author Lubos Slovak <lubos.slovak@nic.cz> + * \author Marek Vavrusa <marek.vavrusa@nic.cz> + * + * \brief Functions for debug output of structures. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_DEBUG_H_ +#define _KNOT_DEBUG_H_ + +#include <stdint.h> +#include <stdio.h> + +#include "config.h" /* autoconf generated */ + +#include "rdata.h" +#include "rrset.h" +#include "zone/node.h" +#include "zone/zone.h" +#include "util/utils.h" +#include "common/print.h" + +/* + * Debug macros + */ +/*! \todo Set these during configure. */ +//#define KNOT_ZONE_DEBUG +//#define KNOT_RESPONSE_DEBUG +//#define KNOT_ZONEDB_DEBUG +//#define KNOT_DNAME_DEBUG +//#define KNOT_NODE_DEBUG +//#define KNOT_PACKET_DEBUG +//#define KNOT_EDNS_DEBUG +//#define KNOT_RRSET_DEBUG +//#define KNOT_RDATA_DEBUG +//#define KNOT_NSEC3_DEBUG +//#define CUCKOO_DEBUG +//#define CUCKOO_DEBUG_HASH +//#define KNOT_NS_DEBUG +//#define KNOT_XFRIN_DEBUG +//#define KNOT_DDNS_DEBUG +//#define KNOT_TSIG_DEBUG + +/*! + * \brief Dumps RDATA of the given type. + * + * This function is empty if neither KNOT_ZONE_DEBUG nor KNOT_RDATA_DEBUG + * is defined. + * + * \param rdata RDATA to dump. + * \param type Type of the RDATA (needed to properly parse the RDATA). + * \param loaded_zone Set to <> 0 if the RDATA is part of a zone loaded into + * the server. Set to 0 otherwise. + */ +void knot_rdata_dump(knot_rdata_t *rdata, uint32_t type, char loaded_zone); + +/*! + * \brief Dumps RRSet. + * + * This function is empty if neither KNOT_ZONE_DEBUG nor KNOT_RRSET_DEBUG + * is defined. + * + * \param rrset RRSet to dump. + * \param loaded_zone Set to <> 0 if the RRSet is part of a zone loaded into + * the server. Set to 0 otherwise. + */ +void knot_rrset_dump(const knot_rrset_t *rrset, char loaded_zone); + +/*! + * \brief Dumps zone node. + * + * This function is empty if neither KNOT_ZONE_DEBUG nor KNOT_NODE_DEBUG + * is defined. + * + * \param node Node to dump. + * \param loaded_zone Set to <> 0 if the node is part of a zone loaded into + * the server. Set to 0 otherwise. + */ +void knot_node_dump(knot_node_t *node, void *loaded_zone); + +/*! + * \brief Dumps the whole zone. + * + * This function is empty if KNOT_ZONE_DEBUG is not defined. + * + * \param zone Zone to dump. + * \param loaded_zone Set to <> 0 if the node is part of a zone loaded into + * the server. Set to 0 otherwise. + */ +void knot_zone_contents_dump(knot_zone_contents_t *zone, char loaded_zone); + +/******************************************************************************/ + +#ifdef KNOT_NS_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_ns(msg...) fprintf(stderr, msg) +#define dbg_ns_hex(data, len) hex_print((data), (len)) +#define dbg_ns_exec(cmds) do { cmds } while (0) +#else +#define dbg_ns(msg...) +#define dbg_ns_hex(data, len) +#define dbg_ns_exec(cmds) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_ns_verb(msg...) fprintf(stderr, msg) +#define dbg_ns_hex_verb(data, len) hex_print((data), (len)) +#define dbg_ns_exec_verb(cmds) do { cmds } while (0) +#else +#define dbg_ns_verb(msg...) +#define dbg_ns_hex_verb(data, len) +#define dbg_ns_exec_verb(cmds) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_ns_detail(msg...) fprintf(stderr, msg) +#define dbg_ns_hex_detail(data, len) hex_print((data), (len)) +#define dbg_ns_exec_detail(cmds) do { cmds } while (0) +#else +#define dbg_ns_detail(msg...) +#define dbg_ns_hex_detail(data, len) +#define dbg_ns_exec_detail(cmds) +#endif + +/* No messages. */ +#else +#define dbg_ns(msg...) +#define dbg_ns_hex(data, len) +#define dbg_ns_exec(cmds) +#define dbg_ns_verb(msg...) +#define dbg_ns_hex_verb(data, len) +#define dbg_ns_exec_verb(cmds) +#define dbg_ns_detail(msg...) +#define dbg_ns_hex_detail(data, len) +#define dbg_ns_exec_detail(cmds) +#endif + +/******************************************************************************/ + +#ifdef KNOT_DNAME_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_dname(msg...) fprintf(stderr, msg) +#define dbg_dname_hex(data, len) hex_print((data), (len)) +#define dbg_dname_exec(cmds) do { cmds } while (0) +#else +#define dbg_dname(msg...) +#define dbg_dname_hex(data, len) +#define dbg_dname_exec(cmds) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_dname_verb(msg...) fprintf(stderr, msg) +#define dbg_dname_hex_verb(data, len) hex_print((data), (len)) +#define dbg_dname_exec_verb(cmds) do { cmds } while (0) +#else +#define dbg_dname_verb(msg...) +#define dbg_dname_hex_verb(data, len) +#define dbg_dname_exec_verb(cmds) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_dname_detail(msg...) fprintf(stderr, msg) +#define dbg_dname_hex_detail(data, len) hex_print((data), (len)) +#define dbg_dname_exec_detail(cmds) do { cmds } while (0) +#else +#define dbg_dname_detail(msg...) +#define dbg_dname_hex_detail(data, len) +#define dbg_dname_exec_detail(cmds) +#endif + +/* No messages. */ +#else +#define dbg_dname(msg...) +#define dbg_dname_hex(data, len) +#define dbg_dname_exec(cmds) +#define dbg_dname_verb(msg...) +#define dbg_dname_hex_verb(data, len) +#define dbg_dname_exec_verb(cmds) +#define dbg_dname_detail(msg...) +#define dbg_dname_hex_detail(data, len) +#define dbg_dname_exec_detail(cmds) +#endif + +/******************************************************************************/ + +#ifdef KNOT_NODE_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_node(msg...) fprintf(stderr, msg) +#define dbg_node_hex(data, len) hex_print((data), (len)) +#else +#define dbg_node(msg...) +#define dbg_node_hex(data, len) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_node_verb(msg...) fprintf(stderr, msg) +#define dbg_node_hex_verb(data, len) hex_print((data), (len)) +#else +#define dbg_node_verb(msg...) +#define dbg_node_hex_verb(data, len) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_node_detail(msg...) fprintf(stderr, msg) +#define dbg_node_hex_detail(data, len) hex_print((data), (len)) +#else +#define dbg_node_detail(msg...) +#define dbg_node_hex_detail(data, len) +#endif + +/* No messages. */ +#else +#define dbg_node(msg...) +#define dbg_node_hex(data, len) +#define dbg_node_verb(msg...) +#define dbg_node_hex_verb(data, len) +#define dbg_node_detail(msg...) +#define dbg_node_hex_detail(data, len) +#endif + +/******************************************************************************/ + +#ifdef KNOT_ZONE_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_zone(msg...) fprintf(stderr, msg) +#define dbg_zone_hex(data, len) hex_print((data), (len)) +#define dbg_zone_exec(cmds) do { cmds } while (0) +#else +#define dbg_zone(msg...) +#define dbg_zone_hex(data, len) +#define dbg_zone_exec(cmds) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_zone_verb(msg...) fprintf(stderr, msg) +#define dbg_zone_hex_verb(data, len) hex_print((data), (len)) +#define dbg_zone_exec_verb(cmds) do { cmds } while (0) +#else +#define dbg_zone_verb(msg...) +#define dbg_zone_hex_verb(data, len) +#define dbg_zone_exec_verb(cmds) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_zone_detail(msg...) fprintf(stderr, msg) +#define dbg_zone_hex_detail(data, len) hex_print((data), (len)) +#define dbg_zone_exec_detail(cmds) do { cmds } while (0) +#else +#define dbg_zone_detail(msg...) +#define dbg_zone_hex_detail(data, len) +#define dbg_zone_exec_detail(cmds) +#endif + +/* No messages. */ +#else +#define dbg_zone(msg...) +#define dbg_zone_hex(data, len) +#define dbg_zone_exec(cmds) +#define dbg_zone_verb(msg...) +#define dbg_zone_hex_verb(data, len) +#define dbg_zone_exec_verb(cmds) +#define dbg_zone_detail(msg...) +#define dbg_zone_hex_detail(data, len) +#define dbg_zone_exec_detail(cmds) +#endif + +/******************************************************************************/ + +#ifdef KNOT_ZONEDB_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_zonedb(msg...) fprintf(stderr, msg) +#define dbg_zonedb_hex(data, len) hex_print((data), (len)) +#define dbg_zonedb_exec(cmds) do { cmds } while (0) +#else +#define dbg_zonedb(msg...) +#define dbg_zonedb_hex(data, len) +#define dbg_zonedb_exec(cmds) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_zonedb_verb(msg...) fprintf(stderr, msg) +#define dbg_zonedb_hex_verb(data, len) hex_print((data), (len)) +#define dbg_zonedb_exec_verb(cmds) do { cmds } while (0) +#else +#define dbg_zonedb_verb(msg...) +#define dbg_zonedb_hex_verb(data, len) +#define dbg_zonedb_exec_verb(cmds) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_zonedb_detail(msg...) fprintf(stderr, msg) +#define dbg_zonedb_hex_detail(data, len) hex_print((data), (len)) +#define dbg_zonedb_exec_detail(cmds) do { cmds } while (0) +#else +#define dbg_zonedb_detail(msg...) +#define dbg_zonedb_hex_detail(data, len) +#define dbg_zonedb_exec_detail(cmds) +#endif + +/* No messages. */ +#else +#define dbg_zonedb(msg...) +#define dbg_zonedb_hex(data, len) +#define dbg_zonedb_exec(cmds) +#define dbg_zonedb_verb(msg...) +#define dbg_zonedb_hex_verb(data, len) +#define dbg_zonedb_exec_verb(cmds) +#define dbg_zonedb_detail(msg...) +#define dbg_zonedb_hex_detail(data, len) +#define dbg_zonedb_exec_detail(cmds) +#endif + +/******************************************************************************/ + +#ifdef KNOT_RESPONSE_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_response(msg...) fprintf(stderr, msg) +#define dbg_response_hex(data, len) hex_print((data), (len)) +#define dbg_response_exec(cmds) do { cmds } while (0) +#else +#define dbg_response(msg...) +#define dbg_response_hex(data, len) +#define dbg_response_exec(cmds) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_response_verb(msg...) fprintf(stderr, msg) +#define dbg_response_hex_verb(data, len) hex_print((data), (len)) +#define dbg_response_exec_verb(cmds) do { cmds } while (0) +#else +#define dbg_response_verb(msg...) +#define dbg_response_hex_verb(data, len) +#define dbg_response_exec_verb(cmds) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_response_detail(msg...) fprintf(stderr, msg) +#define dbg_response_hex_detail(data, len) hex_print((data), (len)) +#define dbg_response_exec_detail(cmds) do { cmds } while (0) +#else +#define dbg_response_detail(msg...) +#define dbg_response_hex_detail(data, len) +#define dbg_response_exec_detail(cmds) +#endif + +/* No messages. */ +#else +#define dbg_response(msg...) +#define dbg_response_hex(data, len) +#define dbg_response_exec(cmds) +#define dbg_response_verb(msg...) +#define dbg_response_hex_verb(data, len) +#define dbg_response_exec_verb(cmds) +#define dbg_response_detail(msg...) +#define dbg_response_hex_detail(data, len) +#define dbg_response_exec_detail(cmds) +#endif + +/******************************************************************************/ + +#ifdef KNOT_PACKET_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_packet(msg...) fprintf(stderr, msg) +#define dbg_packet_hex(data, len) hex_print((data), (len)) +#define dbg_packet_exec(cmds) do { cmds } while (0) +#else +#define dbg_packet(msg...) +#define dbg_packet_hex(data, len) +#define dbg_packet_exec(cmds) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_packet_verb(msg...) fprintf(stderr, msg) +#define dbg_packet_hex_verb(data, len) hex_print((data), (len)) +#define dbg_packet_exec_verb(cmds) do { cmds } while (0) +#else +#define dbg_packet_verb(msg...) +#define dbg_packet_hex_verb(data, len) +#define dbg_packet_exec_verb(cmds) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_packet_detail(msg...) fprintf(stderr, msg) +#define dbg_packet_hex_detail(data, len) hex_print((data), (len)) +#define dbg_packet_exec_detail(cmds) do { cmds } while (0) +#else +#define dbg_packet_detail(msg...) +#define dbg_packet_hex_detail(data, len) +#define dbg_packet_exec_detail(cmds) +#endif + +/* No messages. */ +#else +#define dbg_packet(msg...) +#define dbg_packet_hex(data, len) +#define dbg_packet_exec(cmds) +#define dbg_packet_verb(msg...) +#define dbg_packet_hex_verb(data, len) +#define dbg_packet_exec_verb(cmds) +#define dbg_packet_detail(msg...) +#define dbg_packet_hex_detail(data, len) +#define dbg_packet_exec_detail(cmds) +#endif + +/******************************************************************************/ + +#ifdef KNOT_EDNS_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_edns(msg...) fprintf(stderr, msg) +#define dbg_edns_hex(data, len) hex_print((data), (len)) +#else +#define dbg_edns(msg...) +#define dbg_edns_hex(data, len) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_edns_verb(msg...) fprintf(stderr, msg) +#define dbg_edns_hex_verb(data, len) hex_print((data), (len)) +#else +#define dbg_edns_verb(msg...) +#define dbg_edns_hex_verb(data, len) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_edns_detail(msg...) fprintf(stderr, msg) +#define dbg_edns_hex_detail(data, len) hex_print((data), (len)) +#else +#define dbg_edns_detail(msg...) +#define dbg_edns_hex_detail(data, len) +#endif + +/* No messages. */ +#else +#define dbg_edns(msg...) +#define dbg_edns_hex(data, len) +#define dbg_edns_verb(msg...) +#define dbg_edns_hex_verb(data, len) +#define dbg_edns_detail(msg...) +#define dbg_edns_hex_detail(data, len) +#endif + +/******************************************************************************/ + +#ifdef KNOT_NSEC3_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_nsec3(msg...) fprintf(stderr, msg) +#define dbg_nsec3_hex(data, len) hex_print((data), (len)) +#else +#define dbg_nsec3(msg...) +#define dbg_nsec3_hex(data, len) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_nsec3_verb(msg...) fprintf(stderr, msg) +#define dbg_nsec3_hex_verb(data, len) hex_print((data), (len)) +#else +#define dbg_nsec3_verb(msg...) +#define dbg_nsec3_hex_verb(data, len) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_nsec3_detail(msg...) fprintf(stderr, msg) +#define dbg_nsec3_hex_detail(data, len) hex_print((data), (len)) +#else +#define dbg_nsec3_detail(msg...) +#define dbg_nsec3_hex_detail(data, len) +#endif + +/* No messages. */ +#else +#define dbg_nsec3(msg...) +#define dbg_nsec3_hex(data, len) +#define dbg_nsec3_verb(msg...) +#define dbg_nsec3_hex_verb(data, len) +#define dbg_nsec3_detail(msg...) +#define dbg_nsec3_hex_detail(data, len) +#endif + +/******************************************************************************/ + +#ifdef CUCKOO_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_ck(msg...) fprintf(stderr, msg) +#define dbg_ck_hex(data, len) hex_print((data), (len)) +#else +#define dbg_ck(msg...) +#define dbg_ck_hex(data, len) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_ck_verb(msg...) fprintf(stderr, msg) +#define dbg_ck_hex_verb(data, len) hex_print((data), (len)) +#else +#define dbg_ck_verb(msg...) +#define dbg_ck_hex_verb(data, len) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_ck_detail(msg...) fprintf(stderr, msg) +#define dbg_ck_hex_detail(data, len) hex_print((data), (len)) +#else +#define dbg_ck_detail(msg...) +#define dbg_ck_hex_detail(data, len) +#endif + +/* No messages. */ +#else +#define dbg_ck(msg...) +#define dbg_ck_hex(data, len) +#define dbg_ck_verb(msg...) +#define dbg_ck_hex_verb(data, len) +#define dbg_ck_detail(msg...) +#define dbg_ck_hex_detail(data, len) +#endif + +/******************************************************************************/ + +#ifdef CUCKOO_DEBUG_HASH + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_ck_hash(msg...) fprintf(stderr, msg) +#define dbg_ck_rehash(msg...) fprintf(stderr, msg) +#define dbg_ck_hash_hex(data, len) hex_print((data), (len)) +#else +#define dbg_ck_hash(msg...) +#define dbg_ck_rehash(msg...) +#define dbg_ck_hash_hex(data, len) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_ck_hash_verb(msg...) fprintf(stderr, msg) +#define dbg_ck_hash_hex_verb(data, len) hex_print((data), (len)) +#else +#define dbg_ck_hash_verb(msg...) +#define dbg_ck_hash_hex_verb(data, len) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_ck_hash_detail(msg...) fprintf(stderr, msg) +#define dbg_ck_hash_hex_detail(data, len) hex_print((data), (len)) +#else +#define dbg_ck_hash_detail(msg...) +#define dbg_ck_hash_hex_detail(data, len) +#endif + +/* No messages. */ +#else +#define dbg_ck_hash(msg...) +#define dbg_ck_rehash(msg...) +#define dbg_ck_hash_hex(data, len) +#define dbg_ck_hash_verb(msg...) +#define dbg_ck_hash_hex_verb(data, len) +#define dbg_ck_hash_detail(msg...) +#define dbg_ck_hash_hex_detail(data, len) +#endif + +/******************************************************************************/ + +#ifdef KNOT_XFRIN_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_xfrin(msg...) fprintf(stderr, msg) +#define dbg_xfrin_hex(data, len) hex_print((data), (len)) +#define dbg_xfrin_exec(cmds) do { cmds } while (0) +#else +#define dbg_xfrin(msg...) +#define dbg_xfrin_hex(data, len) +#define dbg_xfrin_exec(cmds) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_xfrin_verb(msg...) fprintf(stderr, msg) +#define dbg_xfrin_hex_verb(data, len) hex_print((data), (len)) +#define dbg_xfrin_exec_verb(cmds) do { cmds } while (0) +#else +#define dbg_xfrin_verb(msg...) +#define dbg_xfrin_hex_verb(data, len) +#define dbg_xfrin_exec_verb(cmds) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_xfrin_detail(msg...) fprintf(stderr, msg) +#define dbg_xfrin_hex_detail(data, len) hex_print((data), (len)) +#define dbg_xfrin_exec_detail(cmds) do { cmds } while (0) +#else +#define dbg_xfrin_detail(msg...) +#define dbg_xfrin_hex_detail(data, len) +#define dbg_xfrin_exec_detail(cmds) +#endif + +/* No messages. */ +#else +#define dbg_xfrin(msg...) +#define dbg_xfrin_hex(data, len) +#define dbg_xfrin_exec(cmds) +#define dbg_xfrin_verb(msg...) +#define dbg_xfrin_hex_verb(data, len) +#define dbg_xfrin_exec_verb(cmds) +#define dbg_xfrin_detail(msg...) +#define dbg_xfrin_hex_detail(data, len) +#define dbg_xfrin_exec_detail(cmds) +#endif + +/******************************************************************************/ + +#ifdef KNOT_DDNS_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_ddns(msg...) fprintf(stderr, msg) +#define dbg_ddns_hex(data, len) hex_print((data), (len)) +#else +#define dbg_ddns(msg...) +#define dbg_ddns_hex(data, len) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_ddns_verb(msg...) fprintf(stderr, msg) +#define dbg_ddns_hex_verb(data, len) hex_print((data), (len)) +#else +#define dbg_ddns_verb(msg...) +#define dbg_ddns_hex_verb(data, len) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_ddns_detail(msg...) fprintf(stderr, msg) +#define dbg_ddns_hex_detail(data, len) hex_print((data), (len)) +#else +#define dbg_ddns_detail(msg...) +#define dbg_ddns_hex_detail(data, len) +#endif + +/* No messages. */ +#else +#define dbg_ddns(msg...) +#define dbg_ddns_hex(data, len) +#define dbg_ddns_verb(msg...) +#define dbg_ddns_hex_verb(data, len) +#define dbg_ddns_detail(msg...) +#define dbg_ddns_hex_detail(data, len) +#endif + +#ifdef KNOT_TSIG_DEBUG + +/* Brief messages. */ +#ifdef DEBUG_ENABLE_BRIEF +#define dbg_tsig(msg...) fprintf(stderr, msg) +#define dbg_tsig_hex(data, len) hex_print((const char*)(data), (len)) +#else +#define dbg_tsig(msg...) +#define dbg_tsig_hex(data, len) +#endif + +/* Verbose messages. */ +#ifdef DEBUG_ENABLE_VERBOSE +#define dbg_tsig_verb(msg...) fprintf(stderr, msg) +#define dbg_tsig_hex_verb(data, len) hex_print((const char*)(data), (len)) +#else +#define dbg_tsig_verb(msg...) +#define dbg_tsig_hex_verb(data, len) +#endif + +/* Detail messages. */ +#ifdef DEBUG_ENABLE_DETAILS +#define dbg_tsig_detail(msg...) fprintf(stderr, msg) +#define dbg_tsig_hex_detail(data, len) hex_print((const char*)(data), (len)) +#else +#define dbg_tsig_detail(msg...) +#define dbg_tsig_hex_detail(data, len) +#endif + +/* No messages. */ +#else +#define dbg_tsig(msg...) +#define dbg_tsig_hex(data, len) +#define dbg_tsig_verb(msg...) +#define dbg_tsig_hex_verb(data, len) +#define dbg_tsig_detail(msg...) +#define dbg_tsig_hex_detail(data, len) +#endif + +/******************************************************************************/ + +#endif /* _KNOT_DEBUG_H_ */ + +/*! @} */ diff --git a/src/libknot/util/descriptor.c b/src/libknot/util/descriptor.c new file mode 100644 index 0000000..fa94c24 --- /dev/null +++ b/src/libknot/util/descriptor.c @@ -0,0 +1,501 @@ +/*! + * \file descriptor.c + * + * \author Modifications by Jan Kadlec <jan.kadlec@nic.cz>, + * most of the work by NLnet labs. + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * + * \note Most of the constants and functions were taken from NSD's dns.c. + * + * \addtogroup libknot + * @{ + */ + +/* + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <config.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> +#include <assert.h> +#include <string.h> +#include <sys/types.h> + +#include "libknot.h" + +enum desclen { KNOT_RRTYPE_DESCRIPTORS_LENGTH = 32770 }; // used to be 101 + +/*! + * \brief Table for linking RR class constants to their textual representation. + */ +static knot_lookup_table_t dns_rrclasses[] = { + { KNOT_CLASS_IN, "IN" }, /* the Internet */ + { KNOT_CLASS_CS, "CS" }, /* the CSNET class (Obsolete) */ + { KNOT_CLASS_CH, "CH" }, /* the CHAOS class */ + { KNOT_CLASS_HS, "HS" }, /* Hesiod */ + { 0, NULL } +}; + +/*! \brief RR type descriptors. */ +static knot_rrtype_descriptor_t + knot_rrtype_descriptors[KNOT_RRTYPE_DESCRIPTORS_LENGTH] = { + /* 0 */ + { 0, NULL, 1, { KNOT_RDATA_WF_BINARY }, { KNOT_RDATA_ZF_UNKNOWN }, true }, + /* 1 */ + { KNOT_RRTYPE_A, "A", 1, { KNOT_RDATA_WF_A }, { KNOT_RDATA_ZF_A }, true }, + /* 2 */ + { KNOT_RRTYPE_NS, "NS", 1, + { KNOT_RDATA_WF_COMPRESSED_DNAME }, { KNOT_RDATA_ZF_DNAME }, true }, + /* 3 */ + { KNOT_RRTYPE_MD, "MD", 1, + { KNOT_RDATA_WF_UNCOMPRESSED_DNAME }, { KNOT_RDATA_ZF_DNAME }, true }, + /* 4 */ + { KNOT_RRTYPE_MF, "MF", 1, + { KNOT_RDATA_WF_UNCOMPRESSED_DNAME }, { KNOT_RDATA_ZF_DNAME }, true }, + /* 5 */ + { KNOT_RRTYPE_CNAME, "CNAME", 1, + { KNOT_RDATA_WF_COMPRESSED_DNAME }, { KNOT_RDATA_ZF_DNAME }, true }, + /* 6 */ + { KNOT_RRTYPE_SOA, "SOA", 7, + { KNOT_RDATA_WF_COMPRESSED_DNAME, KNOT_RDATA_WF_COMPRESSED_DNAME, + KNOT_RDATA_WF_LONG, KNOT_RDATA_WF_LONG, KNOT_RDATA_WF_LONG, + KNOT_RDATA_WF_LONG, KNOT_RDATA_WF_LONG }, + { KNOT_RDATA_ZF_DNAME, KNOT_RDATA_ZF_DNAME, KNOT_RDATA_ZF_PERIOD, KNOT_RDATA_ZF_PERIOD, + KNOT_RDATA_ZF_PERIOD, KNOT_RDATA_ZF_PERIOD, KNOT_RDATA_ZF_PERIOD }, + true }, + /* 7 */ + { KNOT_RRTYPE_MB, "MB", 1, + { KNOT_RDATA_WF_COMPRESSED_DNAME }, { KNOT_RDATA_ZF_DNAME }, true }, + /* 8 */ + { KNOT_RRTYPE_MG, "MG", 1, + { KNOT_RDATA_WF_COMPRESSED_DNAME }, { KNOT_RDATA_ZF_DNAME }, true }, + /* 9 */ + { KNOT_RRTYPE_MR, "MR", 1, + { KNOT_RDATA_WF_COMPRESSED_DNAME }, { KNOT_RDATA_ZF_DNAME }, true }, + /* 10 */ + { KNOT_RRTYPE_NULL, NULL, 1, + { KNOT_RDATA_WF_BINARY }, { KNOT_RDATA_ZF_UNKNOWN }, true }, + /* 11 */ + { KNOT_RRTYPE_WKS, "WKS", 2, + { KNOT_RDATA_WF_A, KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_A, KNOT_RDATA_ZF_SERVICES }, true }, + /* 12 */ + { KNOT_RRTYPE_PTR, "PTR", 1, + { KNOT_RDATA_WF_COMPRESSED_DNAME }, + { KNOT_RDATA_ZF_DNAME }, true }, + /* 13 */ + { KNOT_RRTYPE_HINFO, "HINFO", 2, + { KNOT_RDATA_WF_TEXT_SINGLE, KNOT_RDATA_WF_TEXT_SINGLE }, + { KNOT_RDATA_ZF_TEXT, KNOT_RDATA_ZF_TEXT }, true }, + /* 14 */ + { KNOT_RRTYPE_MINFO, "MINFO", 2, + { KNOT_RDATA_WF_COMPRESSED_DNAME, + KNOT_RDATA_WF_COMPRESSED_DNAME }, + { KNOT_RDATA_ZF_DNAME, KNOT_RDATA_ZF_DNAME }, true }, + /* 15 */ + { KNOT_RRTYPE_MX, "MX", 2, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_COMPRESSED_DNAME }, + { KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_DNAME }, true }, + /* 16 */ /* This is obscure, but I guess there's no other way */ + { KNOT_RRTYPE_TXT, "TXT", 1, + { KNOT_RDATA_WF_TEXT }, + { KNOT_RDATA_ZF_TEXT }, + false }, + /* 17 */ + { KNOT_RRTYPE_RP, "RP", 2, + { KNOT_RDATA_WF_COMPRESSED_DNAME, + KNOT_RDATA_WF_COMPRESSED_DNAME }, + { KNOT_RDATA_ZF_DNAME, KNOT_RDATA_ZF_DNAME }, true }, + /* 18 */ + { KNOT_RRTYPE_AFSDB, "AFSDB", 2, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_COMPRESSED_DNAME }, + { KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_DNAME }, true }, + /* 19 */ + { KNOT_RRTYPE_X25, "X25", 1, + { KNOT_RDATA_WF_TEXT_SINGLE }, + { KNOT_RDATA_ZF_TEXT }, true }, + /* 20 */ + { KNOT_RRTYPE_ISDN, "ISDN", 2, + { KNOT_RDATA_WF_TEXT_SINGLE, KNOT_RDATA_WF_TEXT_SINGLE }, + { KNOT_RDATA_ZF_TEXT, KNOT_RDATA_ZF_TEXT }, false }, + /* 21 */ + { KNOT_RRTYPE_RT, "RT", 2, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_COMPRESSED_DNAME }, + { KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_DNAME }, true }, + /* 22 */ + { KNOT_RRTYPE_NSAP, "NSAP", 1, + { KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_NSAP }, true }, + /* 23 */ + { 23, NULL, 1, { KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_UNKNOWN }, true }, + /* 24 */ + { KNOT_RRTYPE_SIG, "SIG", 9, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_BYTE, KNOT_RDATA_WF_BYTE, + KNOT_RDATA_WF_LONG, KNOT_RDATA_WF_LONG, KNOT_RDATA_WF_LONG, + KNOT_RDATA_WF_SHORT,KNOT_RDATA_WF_UNCOMPRESSED_DNAME, + KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_RRTYPE, KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_PERIOD, + KNOT_RDATA_ZF_TIME, KNOT_RDATA_ZF_TIME, KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_DNAME, + KNOT_RDATA_ZF_BASE64 }, + true }, + /* 25 */ + { KNOT_RRTYPE_KEY, "KEY", 4, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_BYTE, + KNOT_RDATA_WF_BYTE, KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_ALGORITHM, + KNOT_RDATA_ZF_BASE64 }, true }, + /* 26 */ + { KNOT_RRTYPE_PX, "PX", 3, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_UNCOMPRESSED_DNAME, + KNOT_RDATA_WF_UNCOMPRESSED_DNAME }, + { KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_DNAME, KNOT_RDATA_ZF_DNAME }, true }, + /* 27 */ + { 27, NULL, 1, { KNOT_RDATA_WF_BINARY }, { KNOT_RDATA_ZF_UNKNOWN }, true }, + /* 28 */ + { KNOT_RRTYPE_AAAA, "AAAA", 1, + { KNOT_RDATA_WF_AAAA }, + { KNOT_RDATA_ZF_AAAA }, true }, + /* 29 */ + { KNOT_RRTYPE_LOC, "LOC", 1, + { KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_LOC }, true }, + /* 30 */ + { KNOT_RRTYPE_NXT, "NXT", 2, + { KNOT_RDATA_WF_UNCOMPRESSED_DNAME, + KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_DNAME, KNOT_RDATA_ZF_NXT }, true }, + /* 31 */ + { 31, NULL, 1, { KNOT_RDATA_WF_BINARY }, { KNOT_RDATA_ZF_UNKNOWN }, true }, + /* 32 */ + { 32, NULL, 1, { KNOT_RDATA_WF_BINARY }, { KNOT_RDATA_ZF_UNKNOWN }, true }, + /* 33 */ + { KNOT_RRTYPE_SRV, "SRV", 4, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_SHORT, + KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_UNCOMPRESSED_DNAME }, + { KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_DNAME }, + true }, + /* 34 */ + { 34, NULL, 1, { KNOT_RDATA_WF_BINARY }, { KNOT_RDATA_ZF_UNKNOWN }, true }, + /* 35 */ + { KNOT_RRTYPE_NAPTR, "NAPTR", 6, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_TEXT_SINGLE, + KNOT_RDATA_WF_TEXT_SINGLE, KNOT_RDATA_WF_TEXT_SINGLE, + KNOT_RDATA_WF_UNCOMPRESSED_DNAME }, + { KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_TEXT, KNOT_RDATA_ZF_TEXT, + KNOT_RDATA_ZF_TEXT, KNOT_RDATA_ZF_DNAME }, true }, + /* 36 */ + { KNOT_RRTYPE_KX, "KX", 2, + { KNOT_RDATA_WF_SHORT, + KNOT_RDATA_WF_UNCOMPRESSED_DNAME }, + { KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_DNAME }, true }, + /* 37 */ + { KNOT_RRTYPE_CERT, "CERT", 4, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_SHORT, + KNOT_RDATA_WF_BYTE, KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_CERTIFICATE_TYPE, KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_ALGORITHM, + KNOT_RDATA_ZF_BASE64 }, true }, + /* 38 */ + { KNOT_RRTYPE_A6, NULL, 1, { KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_UNKNOWN }, true }, + /* 39 */ + { KNOT_RRTYPE_DNAME, "DNAME", 1, + { KNOT_RDATA_WF_UNCOMPRESSED_DNAME }, + { KNOT_RDATA_ZF_DNAME }, true }, + /* 40 */ + { 40, NULL, 1, { KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_UNKNOWN }, true }, + /* 41 */ + /* OPT has its parser token, but should never be in zone file... */ + { KNOT_RRTYPE_OPT, "OPT", 1, + { KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_UNKNOWN }, true }, + /* 42 */ + { KNOT_RRTYPE_APL, "APL", 1, + { KNOT_RDATA_WF_APL }, + { KNOT_RDATA_ZF_APL }, false }, + /* 43 */ + { KNOT_RRTYPE_DS, "DS", 4, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_BYTE, + KNOT_RDATA_WF_BYTE, KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_ALGORITHM, KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_HEX }, true }, + /* 44 */ + { KNOT_RRTYPE_SSHFP, "SSHFP", 3, + { KNOT_RDATA_WF_BYTE, KNOT_RDATA_WF_BYTE, + KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_HEX }, + true }, + /* 45 */ + { KNOT_RRTYPE_IPSECKEY, "IPSECKEY", 5, + { KNOT_RDATA_WF_BYTE, KNOT_RDATA_WF_BYTE, + KNOT_RDATA_WF_BYTE, KNOT_RDATA_WF_IPSECGATEWAY, + KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_IPSECGATEWAY, + KNOT_RDATA_ZF_BASE64 }, false }, + /* 46 */ + { KNOT_RRTYPE_RRSIG, "RRSIG", 9, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_BYTE, + KNOT_RDATA_WF_BYTE, KNOT_RDATA_WF_LONG, + KNOT_RDATA_WF_LONG, KNOT_RDATA_WF_LONG, + KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_LITERAL_DNAME, + KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_RRTYPE, KNOT_RDATA_ZF_ALGORITHM, + KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_PERIOD, + KNOT_RDATA_ZF_TIME, KNOT_RDATA_ZF_TIME, + KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_LITERAL_DNAME, + KNOT_RDATA_ZF_BASE64 }, true }, + /* 47 */ + { KNOT_RRTYPE_NSEC, "NSEC", 2, + { KNOT_RDATA_WF_LITERAL_DNAME, KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_LITERAL_DNAME, KNOT_RDATA_ZF_NSEC }, + true }, + /* 48 */ + { KNOT_RRTYPE_DNSKEY, "DNSKEY", 4, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_BYTE, + KNOT_RDATA_WF_BYTE, KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_BYTE, + KNOT_RDATA_ZF_ALGORITHM, KNOT_RDATA_ZF_BASE64 }, true }, + /* 49 */ + { KNOT_RRTYPE_DHCID, "DHCID", 1, { KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_BASE64 }, true }, + /* 50 */ + { KNOT_RRTYPE_NSEC3, "NSEC3", 6, + { KNOT_RDATA_WF_BYTE, /* hash type */ + KNOT_RDATA_WF_BYTE, /* flags */ + KNOT_RDATA_WF_SHORT, /* iterations */ + KNOT_RDATA_WF_BINARYWITHLENGTH, /* salt */ + KNOT_RDATA_WF_BINARYWITHLENGTH, /* next hashed name */ + KNOT_RDATA_WF_BINARY /* type bitmap */ }, + { KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_HEX_LEN, + KNOT_RDATA_ZF_BASE32, KNOT_RDATA_ZF_NSEC }, + true }, + /* 51 */ + { KNOT_RRTYPE_NSEC3PARAM, "NSEC3PARAM", 4, + { KNOT_RDATA_WF_BYTE, /* hash type */ + KNOT_RDATA_WF_BYTE, /* flags */ + KNOT_RDATA_WF_SHORT, /* iterations */ + KNOT_RDATA_WF_BINARYWITHLENGTH /* salt */ }, + { KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_BYTE, + KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_HEX_LEN }, true }, + /* 52 */ + + + /* In NSD they have indices between 52 and 99 filled with + unknown types. TODO add here if it's really needed? */ + /* it is indeed needed, in rrtype_from_string */ + + /* There's a GNU extension that works like this: [first ... last] = value */ + + /* 99 */ + [99] = { KNOT_RRTYPE_SPF, "SPF", 1, + { KNOT_RDATA_WF_TEXT }, + { KNOT_RDATA_ZF_TEXT }, false }, + /* TSIG pseudo RR. */ + [250] = { KNOT_RRTYPE_TSIG, "TSIG", 7, + { KNOT_RDATA_WF_UNCOMPRESSED_DNAME, KNOT_RDATA_WF_UINT48, + KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_BINARYWITHSHORT, + KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_SHORT, + KNOT_RDATA_WF_BINARYWITHSHORT }, + /* Zoneformat not needed. */ + {0, 0, 0, 0, 0}, true }, + /* 32769 */ + [32769] = { KNOT_RRTYPE_DLV, "DLV", 4, + { KNOT_RDATA_WF_SHORT, KNOT_RDATA_WF_BYTE, + KNOT_RDATA_WF_BYTE, KNOT_RDATA_WF_BINARY }, + { KNOT_RDATA_ZF_SHORT, KNOT_RDATA_ZF_ALGORITHM, KNOT_RDATA_ZF_BYTE, KNOT_RDATA_ZF_HEX }, + true }, +}; + +knot_rrtype_descriptor_t *knot_rrtype_descriptor_by_type(uint16_t type) +{ + if (type < KNOT_RRTYPE_LAST + 1) { + return &knot_rrtype_descriptors[type]; + } else if (type == KNOT_RRTYPE_DLV) { + return &knot_rrtype_descriptors[KNOT_RRTYPE_DLV]; + } + return &knot_rrtype_descriptors[0]; +} + +/* I see a lot of potential here to speed up zone parsing - this is O(n) * + * could be better */ +knot_rrtype_descriptor_t *knot_rrtype_descriptor_by_name(const char *name) +{ + int i; + + for (i = 0; i < KNOT_RRTYPE_DLV + 1; ++i) { + if (knot_rrtype_descriptors[i].name && + strcasecmp(knot_rrtype_descriptors[i].name, name) == 0) { + return &knot_rrtype_descriptors[i]; + } + } + + if (knot_rrtype_descriptors[KNOT_RRTYPE_DLV].name && + strcasecmp(knot_rrtype_descriptors[KNOT_RRTYPE_DLV].name, + name) == 0) { + return &knot_rrtype_descriptors[KNOT_RRTYPE_DLV]; + } + + return NULL; +} + +const char *knot_rrtype_to_string(uint16_t rrtype) +{ + static char buf[20]; + knot_rrtype_descriptor_t *descriptor = + knot_rrtype_descriptor_by_type(rrtype); + if (descriptor->name) { + return descriptor->name; + } else { + snprintf(buf, sizeof(buf), "TYPE%d", (int) rrtype); + return buf; + } +} + +uint16_t knot_rrtype_from_string(const char *name) +{ + char *end; + long rrtype; + knot_rrtype_descriptor_t *entry; + + entry = knot_rrtype_descriptor_by_name(name); + if (entry) { + return entry->type; + } + + if (strlen(name) < 5) { + return 0; + } + + if (strncasecmp(name, "TYPE", 4) != 0) { + return 0; + } + + if (!isdigit((int)name[4])) { + return 0; + } + + /* The rest from the string must be a number. */ + rrtype = strtol(name + 4, &end, 10); + if (*end != '\0') { + return 0; + } + if (rrtype < 0 || rrtype > 65535L) { + return 0; + } + + return (uint16_t) rrtype; +} + +const char *knot_rrclass_to_string(uint16_t rrclass) +{ + static char buf[20]; + knot_lookup_table_t *entry = knot_lookup_by_id(dns_rrclasses, + rrclass); + if (entry) { + assert(strlen(entry->name) < sizeof(buf)); + knot_strlcpy(buf, entry->name, sizeof(buf)); + } else { + snprintf(buf, sizeof(buf), "CLASS%d", (int) rrclass); + } + return buf; +} + +uint16_t knot_rrclass_from_string(const char *name) +{ + char *end; + long rrclass; + knot_lookup_table_t *entry; + + entry = knot_lookup_by_name(dns_rrclasses, name); + if (entry) { + return (uint16_t) entry->id; + } + + if (strlen(name) < 6) { + return 0; + } + + if (strncasecmp(name, "CLASS", 5) != 0) { + return 0; + } + + if (!isdigit((int)name[5])) { + return 0; + } + + // The rest from the string must be a number. + rrclass = strtol(name + 5, &end, 10); + if (*end != '\0') { + return 0; + } + if (rrclass < 0 || rrclass > 65535L) { + return 0; + } + + return (uint16_t) rrclass; +} + +size_t knot_wireformat_size(unsigned int wire_type) +{ + switch(wire_type) { + case KNOT_RDATA_WF_BYTE: + return 1; + break; + case KNOT_RDATA_WF_SHORT: + return 2; + break; + case KNOT_RDATA_WF_LONG: + return 4; + break; + case KNOT_RDATA_WF_A: + return 4; + break; + default: /* unknown size */ + return 0; + break; + } /* switch */ +} + +int knot_rrtype_is_metatype(uint16_t type) +{ + /*! \todo Check if there are some other metatypes. */ + return (type == KNOT_RRTYPE_ANY + || type == KNOT_RRTYPE_AXFR + || type == KNOT_RRTYPE_IXFR + || type == KNOT_RRTYPE_MAILA + || type == KNOT_RRTYPE_MAILB + || type == KNOT_RRTYPE_OPT); +} + diff --git a/src/libknot/util/descriptor.h b/src/libknot/util/descriptor.h new file mode 100644 index 0000000..10d3d20 --- /dev/null +++ b/src/libknot/util/descriptor.h @@ -0,0 +1,332 @@ +/*! + * \file descriptor.h + * + * \author Modifications by Jan Kadlec <jan.kadlec@nic.cz>, + * most of the work by NLnet Labs. + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * + * \note Most of the constants and functions were taken from NSD's dns.h. + * + * \addtogroup libknot + * @{ + */ + +/* + * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _KNOT_DESCRIPTOR_H_ +#define _KNOT_DESCRIPTOR_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <string.h> + +enum knot_mxrdtln { + /*! \brief Maximum items in RDATA wireformat. */ + KNOT_MAX_RDATA_ITEMS = 64, + /*! \brief Maximum size of one item in RDATA wireformat. */ + KNOT_MAX_RDATA_ITEM_SIZE = 65534, + /*! \brief Maximum wire size of one RDATA. */ + KNOT_MAX_RDATA_WIRE_SIZE = + KNOT_MAX_RDATA_ITEMS * KNOT_MAX_RDATA_ITEM_SIZE +}; + +typedef enum knot_mxrdtln knot_mxrdtln_t; +//#define MAXRDATALEN 64 + +/* 64 is in NSD. Seems a little too much, but I'd say it's not a real issue. */ + +/*! + * \brief Resource record class codes. + */ +enum knot_rr_class { + KNOT_CLASS_IN = 1, + KNOT_CLASS_CS, + KNOT_CLASS_CH, + KNOT_CLASS_HS, + KNOT_CLASS_NONE = 254, + KNOT_CLASS_ANY = 255 +}; + +typedef enum knot_rr_class knot_rr_class_t; + +/*! + * \brief Resource record type constants. + * \todo Not all indices can be used for indexing. + */ +enum knot_rr_type { + KNOT_RRTYPE_UNKNOWN, /*!< 0 - an unknown type */ + KNOT_RRTYPE_A, /*!< 1 - a host address */ + KNOT_RRTYPE_NS, /*!< 2 - an authoritative name server */ + KNOT_RRTYPE_MD, /*!< 3 - a mail destination (Obsolete - use MX) */ + KNOT_RRTYPE_MF, /*!< 4 - a mail forwarder (Obsolete - use MX) */ + KNOT_RRTYPE_CNAME, /*!< 5 - the canonical name for an alias */ + KNOT_RRTYPE_SOA, /*!< 6 - marks the start of a zone of authority */ + KNOT_RRTYPE_MB, /*!< 7 - a mailbox domain name (EXPERIMENTAL) */ + KNOT_RRTYPE_MG, /*!< 8 - a mail group member (EXPERIMENTAL) */ + KNOT_RRTYPE_MR, /*!< 9 - a mail rename domain name (EXPERIMENTAL) */ + KNOT_RRTYPE_NULL, /*!< 10 - a null RR (EXPERIMENTAL) */ + KNOT_RRTYPE_WKS, /*!< 11 - a well known service description */ + KNOT_RRTYPE_PTR, /*!< 12 - a domain name pointer */ + KNOT_RRTYPE_HINFO, /*!< 13 - host information */ + KNOT_RRTYPE_MINFO, /*!< 14 - mailbox or mail list information */ + KNOT_RRTYPE_MX, /*!< 15 - mail exchange */ + KNOT_RRTYPE_TXT, /*!< 16 - text strings */ + KNOT_RRTYPE_RP, /*!< 17 - RFC1183 */ + KNOT_RRTYPE_AFSDB, /*!< 18 - RFC1183 */ + KNOT_RRTYPE_X25, /*!< 19 - RFC1183 */ + KNOT_RRTYPE_ISDN, /*!< 20 - RFC1183 */ + KNOT_RRTYPE_RT, /*!< 21 - RFC1183 */ + KNOT_RRTYPE_NSAP, /*!< 22 - RFC1706 */ + + KNOT_RRTYPE_SIG = 24, /*!< 24 - 2535typecode */ + KNOT_RRTYPE_KEY, /*!< 25 - 2535typecode */ + KNOT_RRTYPE_PX, /*!< 26 - RFC2163 */ + + KNOT_RRTYPE_AAAA = 28, /*!< 28 - ipv6 address */ + KNOT_RRTYPE_LOC, /*!< 29 - LOC record RFC1876 */ + KNOT_RRTYPE_NXT, /*!< 30 - 2535typecode */ + + KNOT_RRTYPE_SRV = 33, /*!< 33 - SRV record RFC2782 */ + + KNOT_RRTYPE_NAPTR = 35, /*!< 35 - RFC2915 */ + KNOT_RRTYPE_KX, /*!< 36 - RFC2230 Key Exchange Delegation Record */ + KNOT_RRTYPE_CERT, /*!< 37 - RFC2538 */ + KNOT_RRTYPE_A6, /*!< 38 - RFC2874 */ + KNOT_RRTYPE_DNAME, /*!< 39 - RFC2672 */ + + KNOT_RRTYPE_OPT = 41, /*!< 41 - Pseudo OPT record... */ + KNOT_RRTYPE_APL, /*!< 42 - RFC3123 */ + KNOT_RRTYPE_DS, /*!< 43 - RFC 4033, 4034, and 4035 */ + KNOT_RRTYPE_SSHFP, /*!< 44 - SSH Key Fingerprint */ + KNOT_RRTYPE_IPSECKEY, /*!< 45 - public key for ipsec use. RFC 4025 */ + KNOT_RRTYPE_RRSIG, /*!< 46 - RFC 4033, 4034, and 4035 */ + KNOT_RRTYPE_NSEC, /*!< 47 - RFC 4033, 4034, and 4035 */ + KNOT_RRTYPE_DNSKEY, /*!< 48 - RFC 4033, 4034, and 4035 */ + KNOT_RRTYPE_DHCID, /*!< 49 - RFC4701 DHCP information */ + /*! + * \brief 50 - NSEC3, secure denial, prevents zonewalking + */ + KNOT_RRTYPE_NSEC3, + /*! + * \brief 51 - NSEC3PARAM at zone apex nsec3 parameters + */ + KNOT_RRTYPE_NSEC3PARAM, + + /* TODO consider some better way of doing this, indices too high */ + + KNOT_RRTYPE_SPF = 99, /*!< RFC 4408 */ + + // not designating any RRs + KNOT_RRTYPE_TSIG = 250, /*!< TSIG - RFC2845. */ + KNOT_RRTYPE_IXFR = 251, /*!< IXFR (not an actual RR). */ + KNOT_RRTYPE_AXFR = 252, /*!< AXFR (not an actual RR). */ + /*! + * \brief A request for mailbox-related records (MB, MG or MR) + */ + KNOT_RRTYPE_MAILB = 253, + /*! + * \brief A request for mail agent RRs (Obsolete - see MX) + */ + KNOT_RRTYPE_MAILA = 254, + KNOT_RRTYPE_ANY = 255, /*!< any type (wildcard) */ + + // totally weird numbers (cannot use for indexing) + KNOT_RRTYPE_TA = 32768, /*!< DNSSEC Trust Authorities */ + KNOT_RRTYPE_DLV = 32769, /*!< RFC 4431 */ + + /*! \brief Last normal RR type. */ + KNOT_RRTYPE_LAST = KNOT_RRTYPE_TSIG + /*! \todo [TSIG] Is it allright to include all <= RR TSIG? + * Because TSIG is normal RR type. */ +}; + +typedef enum knot_rr_type knot_rr_type_t; + +/*! \brief Constants characterising the wire format of RDATA items. */ +enum knot_rdata_wireformat { + /*! + * \brief Possibly compressed domain name. + */ + KNOT_RDATA_WF_COMPRESSED_DNAME = 50, + KNOT_RDATA_WF_UNCOMPRESSED_DNAME = 51, /*!< Uncompressed domain name. */ + KNOT_RDATA_WF_LITERAL_DNAME = 52, /*!< Literal (not downcased) dname. */ + KNOT_RDATA_WF_BYTE = 1, /*!< 8-bit integer. */ + KNOT_RDATA_WF_SHORT = 2, /*!< 16-bit integer. */ + KNOT_RDATA_WF_LONG = 4, /*!< 32-bit integer. */ + KNOT_RDATA_WF_UINT48 = 8, /*!< 48-bit integer. */ + KNOT_RDATA_WF_TEXT = 53, /*!< Text string. */ + KNOT_RDATA_WF_A = 58, /*!< 32-bit IPv4 address. */ + KNOT_RDATA_WF_AAAA = 16, /*!< 128-bit IPv6 address. */ + KNOT_RDATA_WF_BINARY = 54, /*!< Binary data (unknown length). */ + /*! + * \brief Binary data preceded by 1 byte length + */ + KNOT_RDATA_WF_BINARYWITHLENGTH = 55, + KNOT_RDATA_WF_APL = 56, /*!< APL data. */ + KNOT_RDATA_WF_IPSECGATEWAY = 57, /*!< IPSECKEY gateway ip4, ip6 or dname. */ + KNOT_RDATA_WF_BINARYWITHSHORT = 59, + KNOT_RDATA_WF_TEXT_SINGLE = 60 /*!< Text string. */ +}; + +/*! \brief Constants characterising the format of RDATA items in zone file. */ +enum knot_rdata_zoneformat +{ + KNOT_RDATA_ZF_DNAME, /* Domain name. */ + KNOT_RDATA_ZF_LITERAL_DNAME, /* DNS name (not lowercased domain name). */ + KNOT_RDATA_ZF_TEXT, /* Text string. */ + KNOT_RDATA_ZF_BYTE, /* 8-bit integer. */ + KNOT_RDATA_ZF_SHORT, /* 16-bit integer. */ + KNOT_RDATA_ZF_LONG, /* 32-bit integer. */ + KNOT_RDATA_ZF_A, /* 32-bit IPv4 address. */ + KNOT_RDATA_ZF_AAAA, /* 128-bit IPv6 address. */ + KNOT_RDATA_ZF_RRTYPE, /* RR type. */ + KNOT_RDATA_ZF_ALGORITHM, /* Cryptographic algorithm. */ + KNOT_RDATA_ZF_CERTIFICATE_TYPE, + KNOT_RDATA_ZF_PERIOD, /* Time period. */ + KNOT_RDATA_ZF_TIME, + KNOT_RDATA_ZF_BASE64, /* Base-64 binary data. */ + KNOT_RDATA_ZF_BASE32, /* Base-32 binary data. */ + KNOT_RDATA_ZF_HEX, /* Hexadecimal binary data. */ + KNOT_RDATA_ZF_HEX_LEN, /* Hexadecimal binary data. Skip initial length byte. */ + KNOT_RDATA_ZF_NSAP, /* NSAP. */ + KNOT_RDATA_ZF_APL, /* APL. */ + KNOT_RDATA_ZF_IPSECGATEWAY, /* IPSECKEY gateway ip4, ip6 or dname. */ + KNOT_RDATA_ZF_SERVICES, /* Protocol and port number bitmap. */ + KNOT_RDATA_ZF_NXT, /* NXT type bitmap. */ + KNOT_RDATA_ZF_NSEC, /* NSEC type bitmap. */ + KNOT_RDATA_ZF_LOC, /* Location data. */ + KNOT_RDATA_ZF_UNKNOWN /* Unknown data. */ +}; + +/*! \brief Constants characterising the wire format of RDATA items. */ +typedef enum knot_rdata_zoneformat knot_rdata_zoneformat_t; + +/*! \brief Enum containing wireformat codes. */ +typedef enum knot_rdatawireformat knot_rdata_wireformat_t; + +/*! \brief Structure holding RR descriptor. */ +struct knot_rrtype_descriptor { + uint16_t type; /*!< RR type */ + const char *name; /*!< Textual name. */ + uint8_t length; /*!< Maximum number of RDATA items. */ + + /*! \brief Wire format specification for the RDATA. */ + uint8_t wireformat[KNOT_MAX_RDATA_ITEMS]; + + /*! \brief Zone file format specification for the RDATA. */ + uint8_t zoneformat[KNOT_MAX_RDATA_ITEMS]; + + bool fixed_items; /*!< Has fixed number of RDATA items? */ +}; + +/*! \brief Structure holding RR descriptor. */ +typedef struct knot_rrtype_descriptor knot_rrtype_descriptor_t; + +/*! + * \brief Gets RR descriptor for given RR type. + * + * \param type Code of RR type whose descriptor should be returned. + * + * \return RR descriptor for given type code, NULL descriptor if + * unknown type. + * + * \todo Change return value to const. + */ +knot_rrtype_descriptor_t *knot_rrtype_descriptor_by_type(uint16_t type); + +/*! + * \brief Gets RR descriptor for given RR name. + * + * \param name Mnemonic of RR type whose descriptor should be returned. + * + * \return RR descriptor for given name, NULL descriptor if + * unknown type. + * + * \todo Change return value to const. + */ +knot_rrtype_descriptor_t *knot_rrtype_descriptor_by_name(const char *name); + +/*! + * \brief Converts numeric type representation to mnemonic string. + * + * \param rrtype Type RR type code to be converted. + * + * \return Mnemonic string if found, str(TYPE[rrtype]) otherwise. + */ +const char *knot_rrtype_to_string(uint16_t rrtype); + +/*! + * \brief Converts mnemonic string representation of a type to numeric one. + * + * \param name Mnemonic string to be converted. + * + * \return Correct code if found, 0 otherwise. + */ +uint16_t knot_rrtype_from_string(const char *name); + +/*! + * \brief Converts numeric class representation to string one. + * + * \param rrclass Class code to be converted. + * + * \return String represenation of class if found, + * str(CLASS[rrclass]) otherwise. + */ +const char *knot_rrclass_to_string(uint16_t rrclass); + +/*! + * \brief Converts string representation of a class to numeric one. + * + * \param name Class string to be converted. + * + * \return Correct code if found, 0 otherwise. + */ +uint16_t knot_rrclass_from_string(const char *name); + +/*! + * \brief Returns size of wireformat type in bytes. + * + * \param wire_type Wireformat type. + * + * \retval Size of given type on success. + * \retval 0 on unknown type or type that has no length. + */ +size_t knot_wireformat_size(unsigned int wire_type); + +int knot_rrtype_is_metatype(uint16_t type); + +#endif /* _KNOT_DESCRIPTOR_H_ */ + +/*! @} */ + diff --git a/src/libknot/util/error.h b/src/libknot/util/error.h new file mode 100644 index 0000000..da45151 --- /dev/null +++ b/src/libknot/util/error.h @@ -0,0 +1,87 @@ +/*! + * \file error.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Error codes and function for getting error message. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_ERROR_H_ +#define _KNOT_ERROR_H_ + +#include "common/errors.h" + +/*! \brief Error codes used in the library. */ +enum knot_error { + KNOT_EOK = 0, /*!< OK */ + + /* TSIG errors. */ + KNOT_TSIG_EBADSIG = -16, /*!< Failed to verify TSIG MAC. */ + KNOT_TSIG_EBADKEY = -17, /*!< TSIG key not recognized or invalid. */ + KNOT_TSIG_EBADTIME = -18,/*!< TSIG signing time out of range. */ + + /* General errors. */ + KNOT_ERROR = -10000, /*!< General error. */ + KNOT_ENOMEM, /*!< Not enough memory. */ + KNOT_ENOTSUP, /*!< Operation not supported. */ + KNOT_EAGAIN, /*!< OS lacked necessary resources. */ + KNOT_ERANGE, /*!< Value is out of range. */ + KNOT_EBADARG, /*!< Wrong argument supported. */ + KNOT_EFEWDATA, /*!< Not enough data to parse. */ + KNOT_ESPACE, /*!< Not enough space provided. */ + KNOT_EMALF, /*!< Malformed data. */ + KNOT_ECRYPTO, /*!< Error in crypto library. */ + KNOT_ENSEC3PAR, /*!< Missing or wrong NSEC3PARAM record. */ + KNOT_EBADZONE, /*!< Domain name does not belong to the zone. */ + KNOT_EHASH, /*!< Error in hash table. */ + KNOT_EZONEIN, /*!< Error inserting zone. */ + KNOT_ENOZONE, /*!< No such zone found. */ + KNOT_ENONODE, /*!< No such node in zone found. */ + KNOT_ENORRSET, /*!< No such RRSet found. */ + KNOT_EDNAMEPTR, /*!< Domain name pointer larger than allowed. */ + KNOT_EPAYLOAD, /*!< Payload in OPT RR larger than max wire size. */ + KNOT_ECRC, /*!< Wrong dump CRC. */ + KNOT_EPREREQ, /*!< UPDATE prerequisity not met. */ + KNOT_ENOXFR, /*!< Transfer was not sent. */ + KNOT_ENOIXFR, /*!< Transfer is not IXFR (is in AXFR format). */ + KNOT_EXFRREFUSED, /*!< Zone transfer refused by the server. */ + KNOT_ECONN, /*!< Connection reset. */ + KNOT_ERROR_COUNT = 30 +}; + +/*! \brief Table linking error messages to error codes. */ +extern const error_table_t knot_error_msgs[KNOT_ERROR_COUNT]; + +/*! + * \brief Returns error message for the given error code. + * + * \param code Error code. + * + * \return String containing the error message. + */ +static inline const char *knot_strerror(int code) +{ + return error_to_str((const error_table_t*)knot_error_msgs, code); +} + +#endif /* _KNOT_ERROR_H_ */ + +/*! @} */ diff --git a/src/libknot/util/libknot_error.c b/src/libknot/util/libknot_error.c new file mode 100644 index 0000000..bc2bed2 --- /dev/null +++ b/src/libknot/util/libknot_error.c @@ -0,0 +1,53 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include "util/error.h" +#include "util/utils.h" + +#include "common/errors.h" + +const error_table_t knot_error_msgs[KNOT_ERROR_COUNT] = { + {KNOT_EOK, "OK"}, + {KNOT_ERROR, "General error."}, + {KNOT_ENOMEM, "Not enough memory."}, + {KNOT_ENOTSUP, "Operation not supported."}, + {KNOT_EAGAIN, "OS lacked necessary resources."}, + {KNOT_ERANGE, "Value is out of range."}, + {KNOT_EBADARG, "Wrong argument supported."}, + {KNOT_EFEWDATA, "Not enough data to parse."}, + {KNOT_ESPACE, "Not enough space provided."}, + {KNOT_EMALF, "Malformed data."}, + {KNOT_ECRYPTO, "Error in crypto library."}, + {KNOT_ENSEC3PAR, "Missing or wrong NSEC3PARAM record."}, + {KNOT_EBADZONE, "Domain name does not belong to the given zone."}, + {KNOT_EHASH, "Error in hash table."}, + {KNOT_EZONEIN, "Error inserting zone."}, + {KNOT_ENOZONE, "No such zone found."}, + {KNOT_ENONODE, "No such node in zone found."}, + {KNOT_ENORRSET, "No such RRSet found."}, + {KNOT_EDNAMEPTR, "Domain name pointer larger than allowed."}, + {KNOT_EPAYLOAD, "Payload in OPT RR larger than max wire size."}, + {KNOT_ECRC, "CRC check failed."}, + {KNOT_EPREREQ, "UPDATE prerequisity not met."}, + {KNOT_ENOXFR, "Transfer was not sent."}, + {KNOT_ENOIXFR, "Transfer is not IXFR (is in AXFR format)."}, + {KNOT_EXFRREFUSED, "Zone transfer refused by the server."}, + {KNOT_TSIG_EBADSIG, "Failed to verify TSIG MAC." }, + {KNOT_TSIG_EBADKEY, "TSIG key not recognized or invalid." }, + {KNOT_TSIG_EBADTIME, "TSIG signing time out of range." }, + {KNOT_ECONN, "Connection reset."}, + {KNOT_ERROR, 0} +}; diff --git a/src/libknot/util/tolower.c b/src/libknot/util/tolower.c new file mode 100644 index 0000000..d71c467 --- /dev/null +++ b/src/libknot/util/tolower.c @@ -0,0 +1,276 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include "util/tolower.h" + +const uint8_t char_table[CHAR_TABLE_SIZE] = { + '\x00', + '\x01', + '\x02', + '\x03', + '\x04', + '\x05', + '\x06', + '\x07', + '\x08', + '\x09', + '\x0A', + '\x0B', + '\x0C', + '\x0D', + '\x0E', + '\x0F', + '\x10', + '\x11', + '\x12', + '\x13', + '\x14', + '\x15', + '\x16', + '\x17', + '\x18', + '\x19', + '\x1A', + '\x1B', + '\x1C', + '\x1D', + '\x1E', + '\x1F', + '\x20', + '\x21', /* ! */ + '\x22', /* " */ + '\x23', /* # */ + '\x24', /* $ */ + '\x25', /* % */ + '\x26', /* & */ + '\x27', /* ' */ + '\x28', /* ( */ + '\x29', /* ) */ + '\x2A', /* * */ + '\x2B', /* + */ + '\x2C', /* , */ + '\x2D', /* - */ + '\x2E', /* . */ + '\x2F', /* / */ + '\x30', /* 0 */ + '\x31', /* 1 */ + '\x32', /* 2 */ + '\x33', /* 3 */ + '\x34', /* 4 */ + '\x35', /* 5 */ + '\x36', /* 6 */ + '\x37', /* 7 */ + '\x38', /* 8 */ + '\x39', /* 9 */ + '\x3A', /* : */ + '\x3B', /* ; */ + '\x3C', /* < */ + '\x3D', /* = */ + '\x3E', /* > */ + '\x3F', /* ? */ + '\x40', /* @ */ + '\x61', /* A */ + '\x62', /* B */ + '\x63', /* C */ + '\x64', /* D */ + '\x65', /* E */ + '\x66', /* F */ + '\x67', /* G */ + '\x68', /* H */ + '\x69', /* I */ + '\x6A', /* J */ + '\x6B', /* K */ + '\x6C', /* L */ + '\x6D', /* M */ + '\x6E', /* N */ + '\x6F', /* O */ + '\x70', /* P */ + '\x71', /* Q */ + '\x72', /* R */ + '\x73', /* S */ + '\x74', /* T */ + '\x75', /* U */ + '\x76', /* V */ + '\x77', /* W */ + '\x78', /* X */ + '\x79', /* Y */ + '\x7A', /* Z */ + '\x5B', /* [ */ + '\x5C', /* \ */ + '\x5D', /* ] */ + '\x5E', /* ^ */ + '\x5F', /* _ */ + '\x60', /* ` */ + '\x61', /* a */ + '\x62', /* b */ + '\x63', /* c */ + '\x64', /* d */ + '\x65', /* e */ + '\x66', /* f */ + '\x67', /* g */ + '\x68', /* h */ + '\x69', /* i */ + '\x6A', /* j */ + '\x6B', /* k */ + '\x6C', /* l */ + '\x6D', /* m */ + '\x6E', /* n */ + '\x6F', /* o */ + '\x70', /* p */ + '\x71', /* q */ + '\x72', /* r */ + '\x73', /* s */ + '\x74', /* t */ + '\x75', /* u */ + '\x76', /* v */ + '\x77', /* w */ + '\x78', /* x */ + '\x79', /* y */ + '\x7A', /* z */ + '\x7B', /* { */ + '\x7C', /* | */ + '\x7D', /* } */ + '\x7E', /* ~ */ + '\x7F', + '\x80', + '\x81', + '\x82', + '\x83', + '\x84', + '\x85', + '\x86', + '\x87', + '\x88', + '\x89', + '\x8A', + '\x8B', + '\x8C', + '\x8D', + '\x8E', + '\x8F', + '\x90', + '\x91', + '\x92', + '\x93', + '\x94', + '\x95', + '\x96', + '\x97', + '\x98', + '\x99', + '\x9A', + '\x9B', + '\x9C', + '\x9D', + '\x9E', + '\x9F', + '\xA0', + '\xA1', + '\xA2', + '\xA3', + '\xA4', + '\xA5', + '\xA6', + '\xA7', + '\xA8', + '\xA9', + '\xAA', + '\xAB', + '\xAC', + '\xAD', + '\xAE', + '\xAF', + '\xB0', + '\xB1', + '\xB2', + '\xB3', + '\xB4', + '\xB5', + '\xB6', + '\xB7', + '\xB8', + '\xB9', + '\xBA', + '\xBB', + '\xBC', + '\xBD', + '\xBE', + '\xBF', + '\xC0', + '\xC1', + '\xC2', + '\xC3', + '\xC4', + '\xC5', + '\xC6', + '\xC7', + '\xC8', + '\xC9', + '\xCA', + '\xCB', + '\xCC', + '\xCD', + '\xCE', + '\xCF', + '\xD0', + '\xD1', + '\xD2', + '\xD3', + '\xD4', + '\xD5', + '\xD6', + '\xD7', + '\xD8', + '\xD9', + '\xDA', + '\xDB', + '\xDC', + '\xDD', + '\xDE', + '\xDF', + '\xE0', + '\xE1', + '\xE2', + '\xE3', + '\xE4', + '\xE5', + '\xE6', + '\xE7', + '\xE8', + '\xE9', + '\xEA', + '\xEB', + '\xEC', + '\xED', + '\xEE', + '\xEF', + '\xF0', + '\xF1', + '\xF2', + '\xF3', + '\xF4', + '\xF5', + '\xF6', + '\xF7', + '\xF8', + '\xF9', + '\xFA', + '\xFB', + '\xFC', + '\xFD', + '\xFE', + '\xFF', +}; diff --git a/src/libknot/util/tolower.h b/src/libknot/util/tolower.h new file mode 100644 index 0000000..6b9e98c --- /dev/null +++ b/src/libknot/util/tolower.h @@ -0,0 +1,57 @@ +/*! + * \file tolower.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Table for converting ASCII characters to lowercase. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_TOLOWER_H_ +#define _KNOT_TOLOWER_H_ + +#include <stdint.h> + +/*! \brief Size of the character conversion table. */ +#define KNOT_CHAR_TABLE_SIZE 256 + +enum { + /*! \brief Size of the character conversion table. */ + CHAR_TABLE_SIZE = KNOT_CHAR_TABLE_SIZE +}; + +/*! \brief Character table mapping uppercase letters to lowercase. */ +extern const uint8_t char_table[CHAR_TABLE_SIZE]; + +/*! + * \brief Converts ASCII character to lowercase. + * + * \param c ASCII character code. + * + * \return \a c converted to lowercase (or \a c if not applicable). + */ +static inline uint8_t knot_tolower(uint8_t c) { +#if KNOT_CHAR_TABLE_SIZE < 256 + assert(c < CHAR_TABLE_SIZE); +#endif + return char_table[c]; +} + +#endif /* _KNOT_TOLOWER_H_ */ diff --git a/src/libknot/util/utils.c b/src/libknot/util/utils.c new file mode 100644 index 0000000..17b33a7 --- /dev/null +++ b/src/libknot/util/utils.c @@ -0,0 +1,127 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <string.h> +#include <pthread.h> +#include <fcntl.h> +#include <unistd.h> + +#include "common.h" +#include "util/utils.h" +#include "common/WELL1024a.h" + +/*----------------------------------------------------------------------------*/ + +knot_lookup_table_t *knot_lookup_by_name(knot_lookup_table_t *table, + const char *name) +{ + while (table->name != NULL) { + if (strcasecmp(name, table->name) == 0) { + return table; + } + table++; + } + + return NULL; +} + +/*----------------------------------------------------------------------------*/ + +knot_lookup_table_t *knot_lookup_by_id(knot_lookup_table_t *table, + int id) +{ + while (table->name != NULL) { + if (table->id == id) { + return table; + } + table++; + } + + return NULL; +} + +/*----------------------------------------------------------------------------*/ + +size_t knot_strlcpy(char *dst, const char *src, size_t size) +{ + char *d = dst; + const char *s = src; + size_t n = size; + + /* Copy as many bytes as will fit */ + if (n != 0 && --n != 0) { + do { + if ((*d++ = *s++) == 0) { + break; + } + } while (--n != 0); + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (size != 0) { + *d = '\0'; /* NUL-terminate dst */ + } + while (*s++) + ; + } + + return(s - src - 1); /* count does not include NUL */ +} + +/*! \brief TLS key for rand seed. */ +static pthread_key_t _qr_key; +static pthread_once_t _qr_once = PTHREAD_ONCE_INIT; + +/*! \brief TLS key initializer. */ +static void _qr_init() +{ + (void) pthread_key_create(&_qr_key, NULL); + (void) pthread_setspecific(_qr_key, (void*)time(0)); +} + +size_t knot_quick_rand() +{ + (void) pthread_once(&_qr_once, _qr_init); + size_t x = (size_t)pthread_getspecific(_qr_key); + + /* Numerical Recipes in C. + * The Art of Scientific Computing, 2nd Edition, + * 1992, ISBN 0-521-43108-5. + * Page 284. + */ + x = 1664525L * x + 1013904223L; + (void) pthread_setspecific(_qr_key, (void*)x); + return x; +} + +uint16_t knot_random_id() +{ + return (uint16_t)(tls_rand() * ((uint16_t)~0)); +} + +struct flock* knot_file_lock(short type, short whence) +{ + static struct flock ret; + ret.l_type = type; + ret.l_start = 0; + ret.l_whence = whence; + ret.l_len = 0; + ret.l_pid = getpid(); + return &ret; +} + diff --git a/src/libknot/util/utils.h b/src/libknot/util/utils.h new file mode 100644 index 0000000..f43b8f0 --- /dev/null +++ b/src/libknot/util/utils.h @@ -0,0 +1,196 @@ +/*! + * \file utils.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Various utilities. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_UTILS_H_ +#define _KNOT_UTILS_H_ + +#include <string.h> +#include <stdint.h> +#include <stdio.h> + +/*! + * \brief A general purpose lookup table. + * + * \note Taken from NSD. + */ +struct knot_lookup_table { + int id; + const char *name; +}; + +typedef struct knot_lookup_table knot_lookup_table_t; + +/*! + * \brief Looks up the given name in the lookup table. + * + * \param table Lookup table. + * \param name Name to look up. + * + * \return Item in the lookup table with the given name or NULL if no such is + * present. + */ +knot_lookup_table_t *knot_lookup_by_name(knot_lookup_table_t *table, + const char *name); + +/*! + * \brief Looks up the given id in the lookup table. + * + * \param table Lookup table. + * \param id ID to look up. + * + * \return Item in the lookup table with the given id or NULL if no such is + * present. + */ +knot_lookup_table_t *knot_lookup_by_id(knot_lookup_table_t *table, + int id); + +/*! + * \brief Strlcpy - safe string copy function, based on FreeBSD implementation. + * + * http://www.openbsd.org/cgi-bin/cvsweb/src/lib/libc/string/ + * + * \param dst Destination string. + * \param src Source string. + * \param size How many characters to copy - 1. + * + * \return strlen(src), if retval >= siz, truncation occurred. + */ +size_t knot_strlcpy(char *dst, const char *src, size_t size); + +/* + * Writing / reading arbitrary data to / from wireformat. + */ + +/*! + * \brief Reads 2 bytes from the wireformat data. + * + * \param pos Data to read the 2 bytes from. + * + * \return The 2 bytes read, in inverse endian. + */ +static inline uint16_t knot_wire_read_u16(const uint8_t *pos) +{ + return (pos[0] << 8) | pos[1]; +} + +/*! + * \brief Reads 4 bytes from the wireformat data. + * + * \param pos Data to read the 4 bytes from. + * + * \return The 4 bytes read, in inverse endian. + */ +static inline uint32_t knot_wire_read_u32(const uint8_t *pos) +{ + return (pos[0] << 24) | (pos[1] << 16) | (pos[2] << 8) | pos[3]; +} + +/*! + * \brief Reads 6 bytes from the wireformat data. + * + * \param pos Data to read the 6 bytes from. + * + * \return The 6 bytes read, in inverse endian. + */ +static inline uint64_t knot_wire_read_u48(const uint8_t *pos) +{ + return ((uint64_t)(pos[0]) << 40) | ((uint64_t)(pos[1]) << 32) | (pos[2] << 24) | + (pos[3] << 16) | (pos[4] << 8) | pos[5]; +} + +/*! + * \brief Writes 2 bytes in wireformat. + * + * The endian of the data is inverted. + * + * \param pos Position where to put the 2 bytes. + * \param data Data to put. + */ +static inline void knot_wire_write_u16(uint8_t *pos, uint16_t data) +{ + pos[0] = (uint8_t)((data >> 8) & 0xff); + pos[1] = (uint8_t)(data & 0xff); +} + +/*! + * \brief Writes 4 bytes in wireformat. + * + * The endian of the data is inverted. + * + * \param pos Position where to put the 4 bytes. + * \param data Data to put. + */ +static inline void knot_wire_write_u32(uint8_t *pos, uint32_t data) +{ + pos[0] = (uint8_t)((data >> 24) & 0xff); + pos[1] = (uint8_t)((data >> 16) & 0xff); + pos[2] = (uint8_t)((data >> 8) & 0xff); + pos[3] = (uint8_t)(data & 0xff); +} + +/*! + * \brief Writes 6 bytes in wireformat. + * + * The endian of the data is inverted. + * + * \param pos Position where to put the 4 bytes. + * \param data Data to put. + */ +static inline void knot_wire_write_u48(uint8_t *pos, uint64_t data) +{ + pos[0] = (uint8_t)((data >> 40) & 0xff); + pos[1] = (uint8_t)((data >> 32) & 0xff); + pos[2] = (uint8_t)((data >> 24) & 0xff); + pos[3] = (uint8_t)((data >> 16) & 0xff); + pos[4] = (uint8_t)((data >> 8) & 0xff); + pos[5] = (uint8_t)(data & 0xff); +} + +/*! + * \brief Linear congruential generator. + * + * Simple pseudorandom generator for general purpose. + * \warning Do not use for cryptography. + * \return Random number <0, (size_t)~0> + */ +size_t knot_quick_rand(); + +uint16_t knot_random_id(); + +/*! + * \brief Helper function for simple locking. + * + * \param type Type of lock. + * \param type Starting position of lock. + * + * \return Locking structure. + */ +struct flock* knot_file_lock(short type, short whence); + +#endif /* _KNOT_UTILS_H_ */ + +/*! @} */ + diff --git a/src/libknot/util/wire.h b/src/libknot/util/wire.h new file mode 100644 index 0000000..0a24ff1 --- /dev/null +++ b/src/libknot/util/wire.h @@ -0,0 +1,926 @@ +/*! + * \file wire.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Functions for manipulating and parsing raw data in DNS packets. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_WIRE_H_ +#define _KNOT_WIRE_H_ + +#include <stdint.h> +#include <assert.h> + +#include "util/utils.h" + +/*! \brief Offset of DNS header fields in wireformat. */ +enum knot_wire_offsets { + KNOT_WIRE_OFFSET_ID = 0, + KNOT_WIRE_OFFSET_FLAGS1 = 2, + KNOT_WIRE_OFFSET_FLAGS2 = 3, + KNOT_WIRE_OFFSET_QDCOUNT = 4, + KNOT_WIRE_OFFSET_ANCOUNT = 6, + KNOT_WIRE_OFFSET_NSCOUNT = 8, + KNOT_WIRE_OFFSET_ARCOUNT = 10 +}; + +/*! \brief Minimum size for some parts of the DNS packet. */ +enum knot_wire_sizes { + KNOT_WIRE_HEADER_SIZE = 12, + KNOT_WIRE_QUESTION_MIN_SIZE = 5, + KNOT_WIRE_RR_MIN_SIZE = 11 +}; + +/* + * Packet header manipulation functions. + */ + +/*! + * \brief Returns the ID from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return DNS packet ID. + */ +static inline uint16_t knot_wire_get_id(const uint8_t *packet) +{ + return knot_wire_read_u16(packet + KNOT_WIRE_OFFSET_ID); +} + +/*! + * \brief Sets the ID to the wire format of the packet. + * + * \param packet Wire format of the packet. + * \param id DNS packet ID. + */ +static inline void knot_wire_set_id(uint8_t *packet, uint16_t id) +{ + knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_ID, id); +} + +/*! + * \brief Returns the first byte of flags from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return First byte of DNS flags. + */ +static inline uint8_t knot_wire_get_flags1(const uint8_t *packet) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS1); +} + +/*! + * \brief Sets the first byte of flags to the wire format of the packet. + * + * \param packet Wire format of the packet. + * \param flags1 First byte of the DNS flags. + */ +static inline uint8_t knot_wire_set_flags1(uint8_t *packet, uint8_t flags1) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS1) = flags1; +} + +/*! + * \brief Returns the second byte of flags from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Second byte of DNS flags. + */ +static inline uint8_t knot_wire_get_flags2(const uint8_t *packet) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS2); +} + +/*! + * \brief Sets the second byte of flags to the wire format of the packet. + * + * \param packet Wire format of the packet. + * \param flags2 Second byte of the DNS flags. + */ +static inline uint8_t knot_wire_set_flags2(uint8_t *packet, uint8_t flags2) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS2) = flags2; +} + +/*! + * \brief Returns the QDCOUNT (count of Question entries) from wire format of + * the packet. + * + * \param packet Wire format of the packet. + * + * \return QDCOUNT (count of Question entries in the packet). + */ +static inline uint16_t knot_wire_get_qdcount(const uint8_t *packet) +{ + return knot_wire_read_u16(packet + KNOT_WIRE_OFFSET_QDCOUNT); +} + +/*! + * \brief Sets the QDCOUNT (count of Question entries) to wire format of the + * packet. + * + * \param packet Wire format of the packet. + * \param qdcount QDCOUNT (count of Question entries in the packet). + */ +static inline void knot_wire_set_qdcount(uint8_t *packet, uint16_t qdcount) +{ + knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_QDCOUNT, qdcount); +} + +/*! + * \brief Returns the ANCOUNT (count of Answer entries) from wire format of + * the packet. + * + * \param packet Wire format of the packet. + * + * \return ANCOUNT (count of Answer entries in the packet). + */ +static inline uint16_t knot_wire_get_ancount(const uint8_t *packet) +{ + return knot_wire_read_u16(packet + KNOT_WIRE_OFFSET_ANCOUNT); +} + +/*! + * \brief Sets the ANCOUNT (count of Answer entries) to wire format of the + * packet. + * + * \param packet Wire format of the packet. + * \param ancount ANCOUNT (count of Answer entries in the packet). + */ +static inline void knot_wire_set_ancount(uint8_t *packet, uint16_t ancount) +{ + knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_ANCOUNT, ancount); +} + +/*! + * \brief Returns the NSCOUNT (count of Authority entries) from wire format of + * the packet. + * + * \param packet Wire format of the packet. + * + * \return NSCOUNT (count of Authority entries in the packet). + */ +static inline uint16_t knot_wire_get_nscount(const uint8_t *packet) +{ + return knot_wire_read_u16(packet + KNOT_WIRE_OFFSET_NSCOUNT); +} + +/*! + * \brief Sets the NSCOUNT (count of Authority entries) to wire format of the + * packet. + * + * \param packet Wire format of the packet. + * \param nscount NSCOUNT (count of Authority entries in the packet). + */ +static inline void knot_wire_set_nscount(uint8_t *packet, uint16_t nscount) +{ + knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_NSCOUNT, nscount); +} + +/*! + * \brief Returns the ARCOUNT (count of Additional entries) from wire format of + * the packet. + * + * \param packet Wire format of the packet. + * + * \return ARCOUNT (count of Additional entries in the packet). + */ +static inline uint16_t knot_wire_get_arcount(const uint8_t *packet) +{ + return knot_wire_read_u16(packet + KNOT_WIRE_OFFSET_ARCOUNT); +} + +/*! + * \brief Sets the ARCOUNT (count of Additional entries) to wire format of the + * packet. + * + * \param packet Wire format of the packet. + * \param arcount ARCOUNT (count of Additional entries in the packet). + */ +static inline void knot_wire_set_arcount(uint8_t *packet, uint16_t arcount) +{ + knot_wire_write_u16(packet + KNOT_WIRE_OFFSET_ARCOUNT, arcount); +} + +/* + * Packet header flags manipulation functions. + */ +/*! \brief Constants for DNS header flags in the first flags byte. */ +enum knot_wire_flags1_consts { + KNOT_WIRE_RD_MASK = (uint8_t)0x01U, /*!< RD bit mask. */ + KNOT_WIRE_RD_SHIFT = 0, /*!< RD bit shift. */ + KNOT_WIRE_TC_MASK = (uint8_t)0x02U, /*!< TC bit mask. */ + KNOT_WIRE_TC_SHIFT = 1, /*!< TC bit shift. */ + KNOT_WIRE_AA_MASK = (uint8_t)0x04U, /*!< AA bit mask. */ + KNOT_WIRE_AA_SHIFT = 2, /*!< AA bit shift. */ + KNOT_WIRE_OPCODE_MASK = (uint8_t)0x78U, /*!< OPCODE mask. */ + KNOT_WIRE_OPCODE_SHIFT = 3, /*!< OPCODE shift. */ + KNOT_WIRE_QR_MASK = (uint8_t)0x80U, /*!< QR bit mask. */ + KNOT_WIRE_QR_SHIFT = 7 /*!< QR bit shift. */ +}; + +/*! \brief Constants for DNS header flags in the second flags byte. */ +enum knot_wire_flags2_consts { + KNOT_WIRE_RCODE_MASK = (uint8_t)0x0fU, /*!< RCODE mask. */ + KNOT_WIRE_RCODE_SHIFT = 0, /*!< RCODE shift. */ + KNOT_WIRE_CD_MASK = (uint8_t)0x10U, /*!< CD bit mask. */ + KNOT_WIRE_CD_SHIFT = 4, /*!< CD bit shift. */ + KNOT_WIRE_AD_MASK = (uint8_t)0x20U, /*!< AD bit mask. */ + KNOT_WIRE_AD_SHIFT = 5, /*!< AD bit shift. */ + KNOT_WIRE_Z_MASK = (uint8_t)0x40U, /*!< Zero bit mask. */ + KNOT_WIRE_Z_SHIFT = 6, /*!< Zero bit shift. */ + KNOT_WIRE_RA_MASK = (uint8_t)0x80U, /*!< RA bit mask. */ + KNOT_WIRE_RA_SHIFT = 7 /*!< RA bit shift. */ +}; + +/* + * Functions for getting / setting / clearing flags and codes directly in packet + */ + +/*! + * \brief Returns the RD bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Flags with only the RD bit according to its setting in the packet. + */ +static inline uint8_t knot_wire_get_rd(const uint8_t *packet) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS1) & KNOT_WIRE_RD_MASK; +} + +/*! + * \brief Sets the RD bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_rd(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS1) |= KNOT_WIRE_RD_MASK; +} + +/*! + * \brief Clears the RD bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_flags_clear_rd(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS1) &= ~KNOT_WIRE_RD_MASK; +} + +/*! + * \brief Returns the TC bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Flags with only the TC bit according to its setting in the packet. + */ +static inline uint8_t knot_wire_get_tc(const uint8_t *packet) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS1) & KNOT_WIRE_TC_MASK; +} + +/*! + * \brief Sets the TC bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_tc(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS1) |= KNOT_WIRE_TC_MASK; +} + +/*! + * \brief Clears the TC bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_clear_tc(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS1) &= ~KNOT_WIRE_TC_MASK; +} + +/*! + * \brief Returns the AA bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Flags with only the AA bit according to its setting in the packet. + */ +static inline uint8_t knot_wire_get_aa(const uint8_t *packet) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS1) & KNOT_WIRE_AA_MASK; +} + +/*! + * \brief Sets the AA bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_aa(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS1) |= KNOT_WIRE_AA_MASK; +} + +/*! + * \brief Clears the AA bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_clear_aa(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS1) &= ~KNOT_WIRE_AA_MASK; +} + +/*! + * \brief Returns the OPCODE from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return OPCODE of the packet. + */ +static inline uint8_t knot_wire_get_opcode(const uint8_t *packet) +{ + return (*(packet + KNOT_WIRE_OFFSET_FLAGS1) + & KNOT_WIRE_OPCODE_MASK) >> KNOT_WIRE_OPCODE_SHIFT; +} + +/*! + * \brief Sets the OPCODE in the wire format of the packet. + * + * \param packet Wire format of the packet. + * \param opcode OPCODE to set. + */ +static inline void knot_wire_set_opcode(uint8_t *packet, short opcode) +{ + uint8_t *flags1 = packet + KNOT_WIRE_OFFSET_FLAGS1; + *flags1 = (*flags1 & ~KNOT_WIRE_OPCODE_MASK) + | ((opcode) << KNOT_WIRE_OPCODE_SHIFT); +} + +/*! + * \brief Returns the QR bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Flags with only the QR bit according to its setting in the packet. + */ +static inline uint8_t knot_wire_get_qr(const uint8_t *packet) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS1) & KNOT_WIRE_QR_MASK; +} + +/*! + * \brief Sets the QR bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_qr(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS1) |= KNOT_WIRE_QR_MASK; +} + +/*! + * \brief Clears the QR bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_clear_qr(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS1) &= ~KNOT_WIRE_QR_MASK; +} + +/*! + * \brief Returns the RCODE from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return RCODE of the packet. + */ +static inline uint8_t knot_wire_get_rcode(const uint8_t *packet) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS2) + & KNOT_WIRE_RCODE_MASK; +} + +/*! + * \brief Sets the RCODE in the wire format of the packet. + * + * \param packet Wire format of the packet. + * \param rcode RCODE to set. + */ +static inline void knot_wire_set_rcode(uint8_t *packet, short rcode) +{ + uint8_t *flags2 = packet + KNOT_WIRE_OFFSET_FLAGS2; + *flags2 = (*flags2 & ~KNOT_WIRE_RCODE_MASK) | (rcode); +} + +/*! + * \brief Returns the CD bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Flags with only the CD bit according to its setting in the packet. + */ +static inline uint8_t knot_wire_get_cd(const uint8_t *packet) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS2) & KNOT_WIRE_CD_MASK; +} + +/*! + * \brief Sets the CD bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_cd(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS2) |= KNOT_WIRE_CD_MASK; +} + +/*! + * \brief Clears the CD bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_clear_cd(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS2) &= ~KNOT_WIRE_CD_MASK; +} + +/*! + * \brief Returns the AD bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Flags with only the AD bit according to its setting in the packet. + */ +static inline uint8_t knot_wire_get_ad(const uint8_t *packet) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS2) & KNOT_WIRE_AD_MASK; +} + +/*! + * \brief Sets the AD bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_ad(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS2) |= KNOT_WIRE_AD_MASK; +} + +/*! + * \brief Clears the AD bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_clear_ad(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS2) &= ~KNOT_WIRE_AD_MASK; +} + +/*! + * \brief Returns the Zero bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Flags with only the Zero bit according to its setting in the packet. + */ +static inline uint8_t knot_wire_get_z(const uint8_t *packet) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS2) & KNOT_WIRE_Z_MASK; +} + +/*! + * \brief Sets the Zero bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_z(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS2) |= KNOT_WIRE_Z_MASK; +} + +/*! + * \brief Clears the Zero bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_clear_z(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS2) &= ~KNOT_WIRE_Z_MASK; +} + +/*! + * \brief Returns the RA bit from wire format of the packet. + * + * \param packet Wire format of the packet. + * + * \return Flags with only the RA bit according to its setting in the packet. + */ +static inline uint8_t knot_wire_get_ra(const uint8_t *packet) +{ + return *(packet + KNOT_WIRE_OFFSET_FLAGS2) & KNOT_WIRE_RA_MASK; +} + +/*! + * \brief Sets the RA bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_set_ra(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS2) |= KNOT_WIRE_RA_MASK; +} + +/*! + * \brief Clears the RA bit in the wire format of the packet. + * + * \param packet Wire format of the packet. + */ +static inline void knot_wire_clear_ra(uint8_t *packet) +{ + *(packet + KNOT_WIRE_OFFSET_FLAGS2) &= ~KNOT_WIRE_RA_MASK; +} + +/* + * Functions for getting / setting / clearing flags in flags variable + */ + +/*! + * \brief Returns the RD bit from the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + * + * \return Flags byte with only the RD bit according to its setting in + * \a flags1. + */ +static inline uint8_t knot_wire_flags_get_rd(uint8_t flags1) +{ + return flags1 & KNOT_WIRE_RD_MASK; +} + +/*! + * \brief Sets the RD bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_set_rd(uint8_t *flags1) +{ + *flags1 |= KNOT_WIRE_RD_MASK; +} + +/*! + * \brief Clears the RD bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_flags_clear_rd(uint8_t *flags1) +{ + *flags1 &= ~KNOT_WIRE_RD_MASK; +} + +/*! + * \brief Returns the TC bit from the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + * + * \return Flags byte with only the TC bit according to its setting in + * \a flags1. + */ +static inline uint8_t knot_wire_flags_get_tc(uint8_t flags1) +{ + return flags1 & KNOT_WIRE_TC_MASK; +} + +/*! + * \brief Sets the TC bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_set_tc(uint8_t *flags1) +{ + *flags1 |= KNOT_WIRE_TC_MASK; +} + +/*! + * \brief Clears the TC bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_clear_tc(uint8_t *flags1) +{ + *flags1 &= ~KNOT_WIRE_TC_MASK; +} + +/*! + * \brief Returns the AA bit from the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + * + * \return Flags byte with only the AA bit according to its setting in + * \a flags1. + */ +static inline uint8_t knot_wire_flags_get_aa(uint8_t flags1) +{ + return flags1 & KNOT_WIRE_AA_MASK; +} + +/*! + * \brief Sets the AA bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_set_aa(uint8_t *flags1) +{ + *flags1 |= KNOT_WIRE_AA_MASK; +} + +/*! + * \brief Clears the AA bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_clear_aa(uint8_t *flags1) +{ + *flags1 &= ~KNOT_WIRE_AA_MASK; +} + +/*! + * \brief Returns the OPCODE from the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + * + * \return OPCODE + */ +static inline uint8_t knot_wire_flags_get_opcode(uint8_t flags1) +{ + return (flags1 & KNOT_WIRE_OPCODE_MASK) + >> KNOT_WIRE_OPCODE_SHIFT; +} + +/*! + * \brief Sets the OPCODE in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + * \param opcode OPCODE to set. + */ +static inline void knot_wire_flags_set_opcode(uint8_t *flags1, short opcode) +{ + *flags1 = (*flags1 & ~KNOT_WIRE_OPCODE_MASK) + | ((opcode) << KNOT_WIRE_OPCODE_SHIFT); +} + +/*! + * \brief Returns the QR bit from the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + * + * \return Flags byte with only the QR bit according to its setting in + * \a flags1. + */ +static inline uint8_t knot_wire_flags_get_qr(uint8_t flags1) +{ + return flags1 & KNOT_WIRE_QR_MASK; +} + +/*! + * \brief Sets the QR bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_set_qr(uint8_t *flags1) +{ + *flags1 |= KNOT_WIRE_QR_MASK; +} + +/*! + * \brief Clears the QR bit in the first byte of flags. + * + * \param flags1 First byte of DNS header flags. + */ +static inline void knot_wire_flags_clear_qr(uint8_t *flags1) +{ + *flags1 &= ~KNOT_WIRE_QR_MASK; +} + +/*! + * \brief Returns the RCODE from the second byte of flags. + * + * \param flags2 First byte of DNS header flags. + * + * \return RCODE + */ +static inline uint8_t knot_wire_flags_get_rcode(uint8_t flags2) +{ + return flags2 & KNOT_WIRE_RCODE_MASK; +} + +/*! + * \brief Sets the RCODE in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + * \param rcode RCODE to set. + */ +static inline void knot_wire_flags_set_rcode(uint8_t *flags2, short rcode) +{ + *flags2 = (*flags2 & ~KNOT_WIRE_RCODE_MASK) | (rcode); +} + +/*! + * \brief Returns the CD bit from the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + * + * \return Flags byte with only the CD bit according to its setting in + * \a flags2. + */ +static inline uint8_t knot_wire_flags_get_cd(uint8_t flags2) +{ + return flags2 & KNOT_WIRE_CD_MASK; +} + +/*! + * \brief Sets the CD bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_set_cd(uint8_t *flags2) +{ + *flags2 |= KNOT_WIRE_CD_MASK; +} + +/*! + * \brief Clears the CD bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_clear_cd(uint8_t *flags2) +{ + *flags2 &= ~KNOT_WIRE_CD_MASK; +} + +/*! + * \brief Returns the AD bit from the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + * + * \return Flags byte with only the AD bit according to its setting in + * \a flags2. + */ +static inline uint8_t knot_wire_flags_get_ad(uint8_t flags2) +{ + return flags2 & KNOT_WIRE_AD_MASK; +} + +/*! + * \brief Sets the AD bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_set_ad(uint8_t *flags2) +{ + *flags2 |= KNOT_WIRE_AD_MASK; +} + +/*! + * \brief Clears the AD bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_clear_ad(uint8_t *flags2) +{ + *flags2 &= ~KNOT_WIRE_AD_MASK; +} + +/*! + * \brief Returns the Zero bit from the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + * + * \return Flags byte with only the Zero bit according to its setting in + * \a flags2. + */ +static inline uint8_t knot_wire_flags_get_z(uint8_t flags2) +{ + return flags2 & KNOT_WIRE_Z_MASK; +} + +/*! + * \brief Sets the Zero bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_set_z(uint8_t *flags2) +{ + *flags2 |= KNOT_WIRE_Z_MASK; +} + +/*! + * \brief Clears the Zero bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_clear_z(uint8_t *flags2) +{ + *flags2 &= ~KNOT_WIRE_Z_MASK; +} + +/*! + * \brief Returns the RA bit from the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + * + * \return Flags byte with only the RA bit according to its setting in + * \a flags2. + */ +static inline uint8_t knot_wire_flags_get_ra(uint8_t flags2) +{ + return flags2 & KNOT_WIRE_RA_MASK; +} + +/*! + * \brief Sets the RA bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_set_ra(uint8_t *flags2) +{ + *flags2 |= KNOT_WIRE_RA_MASK; +} + +/*! + * \brief Clears the RA bit in the second byte of flags. + * + * \param flags2 Second byte of DNS header flags. + */ +static inline void knot_wire_flags_clear_ra(uint8_t *flags2) +{ + *flags2 &= ~KNOT_WIRE_RA_MASK; +} + +/* + * Pointer manipulation + */ + +enum knot_wire_pointer_consts { + /*! \brief DNS packet pointer designation (first two bits set to 1). */ + KNOT_WIRE_PTR = (uint8_t)0xc0U +}; + +/*! + * \brief Creates a DNS packet pointer and stores it in wire format. + * + * \param pos Position where tu put the pointer. + * \param ptr Relative position of the item to which the pointer should point in + * the wire format of the packet. + */ +static inline void knot_wire_put_pointer(uint8_t *pos, size_t ptr) +{ + uint16_t p = ptr; + knot_wire_write_u16(pos, p); + assert((pos[0] & KNOT_WIRE_PTR) == 0); + pos[0] |= KNOT_WIRE_PTR; +} + +static inline int knot_wire_is_pointer(const uint8_t *pos) +{ + return ((pos[0] & KNOT_WIRE_PTR) != 0); +} + +static inline size_t knot_wire_get_pointer(const uint8_t *pos) +{ + /*! \todo memcpy() is not needed, may be directly assigned. */ + uint16_t p = 0; + memcpy(&p, pos, 2); + p &= ~KNOT_WIRE_PTR; + + uint16_t p2 = knot_wire_read_u16((uint8_t *)&p); + return p2; +} + +#endif /* _KNOT_WIRE_H_ */ + +/*! @} */ diff --git a/src/libknot/zone/dname-table.c b/src/libknot/zone/dname-table.c new file mode 100644 index 0000000..c41b4bd --- /dev/null +++ b/src/libknot/zone/dname-table.c @@ -0,0 +1,310 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <stddef.h> + +#include "zone/dname-table.h" +#include "util/error.h" + +/*!< Tree functions. */ +TREE_DEFINE(dname_table_node, avl); + +struct knot_dname_table_fnc_data { + void (*func)(knot_dname_t *dname, void *data); + void *data; +}; + +static void knot_dname_table_apply(struct dname_table_node *node, void *data) +{ + assert(data != NULL); + assert(node != NULL); + struct knot_dname_table_fnc_data *d = + (struct knot_dname_table_fnc_data *)data; + d->func(node->dname, d->data); +} + +/*! + * \brief Comparison function to be used with tree. + * + * \param n1 First dname to be compared. + * \param n2 Second dname to be compared. + * + * \return strncmp of dname's wireformats. + */ +static int compare_dname_table_nodes(struct dname_table_node *n1, + struct dname_table_node *n2) +{ + assert(n1 && n2); + return (strncmp((char *)n1->dname->name, (char *)n2->dname->name, + (n1->dname->size < n2->dname->size) ? + (n1->dname->size):(n2->dname->size))); +} + +/*! + * \brief Deletes tree node along with its domain name. + * + * \param node Node to be deleted. + * \param data If <> 0, dname in the node will be freed as well. + */ +static void delete_dname_table_node(struct dname_table_node *node, void *data) +{ + if ((ssize_t)data == 1) { + knot_dname_release(node->dname); + } else if ((ssize_t)data == 2) { + knot_dname_free(&node->dname); + } + + /*!< \todo it would be nice to set pointers to NULL. */ + free(node); +} + +static void knot_dname_table_delete_subtree(struct dname_table_node *root) +{ + if (root == NULL) { + return; + } + + knot_dname_table_delete_subtree(root->avl.avl_left); + knot_dname_table_delete_subtree(root->avl.avl_right); + free(root); +} + +static int knot_dname_table_copy_node(const struct dname_table_node *from, + struct dname_table_node **to) +{ + if (from == NULL) { + return KNOT_EOK; + } + + *to = (struct dname_table_node *) + malloc(sizeof(struct dname_table_node)); + if (*to == NULL) { + return KNOT_ENOMEM; + } + memset(*to, 0, sizeof(struct dname_table_node)); + + (*to)->dname = from->dname; + knot_dname_retain((*to)->dname); + (*to)->avl.avl_height = from->avl.avl_height; + + int ret = knot_dname_table_copy_node(from->avl.avl_left, + &(*to)->avl.avl_left); + if (ret != KNOT_EOK) { + return ret; + } + + ret = knot_dname_table_copy_node(from->avl.avl_right, + &(*to)->avl.avl_right); + if (ret != KNOT_EOK) { + knot_dname_table_delete_subtree((*to)->avl.avl_left); + (*to)->avl.avl_left = NULL; + return ret; + } + + return KNOT_EOK; +} + +knot_dname_table_t *knot_dname_table_new() +{ + knot_dname_table_t *ret = malloc(sizeof(knot_dname_table_t)); + CHECK_ALLOC_LOG(ret, NULL); + + ret->tree = malloc(sizeof(table_tree_t)); + if (ret->tree == NULL) { + ERR_ALLOC_FAILED; + free(ret); + return NULL; + } + + TREE_INIT(ret->tree, compare_dname_table_nodes); + + ret->id_counter = 1; + + return ret; +} + +knot_dname_t *knot_dname_table_find_dname(const knot_dname_table_t *table, + knot_dname_t *dname) +{ + if (table == NULL || dname == NULL) { + return NULL; + } + + struct dname_table_node *node = NULL; + struct dname_table_node sought; + sought.dname = dname; + + node = TREE_FIND(table->tree, dname_table_node, avl, &sought); + + if (node == NULL) { + return NULL; + } else { + /* Increase reference counter. */ + knot_dname_retain(node->dname); + + return node->dname; + } +} + +int knot_dname_table_add_dname(knot_dname_table_t *table, + knot_dname_t *dname) +{ + if (dname == NULL || table == NULL) { + return KNOT_EBADARG; + } + + /* Node for insertion has to be created */ + struct dname_table_node *node = + malloc(sizeof(struct dname_table_node)); + CHECK_ALLOC_LOG(node, KNOT_ENOMEM); + + // convert the dname to lowercase + knot_dname_to_lower(dname); + + node->dname = dname; + node->avl.avl_height = 0; + node->avl.avl_left = NULL; + node->avl.avl_right = NULL; + + node->dname->id = table->id_counter++; + assert(node->dname->id != 0); + + /* Increase reference counter. */ + knot_dname_retain(dname); + + TREE_INSERT(table->tree, dname_table_node, avl, node); + return KNOT_EOK; +} + +int knot_dname_table_add_dname_check(knot_dname_table_t *table, + knot_dname_t **dname) +{ + knot_dname_t *found_dname = NULL; + + if (table == NULL || dname == NULL || *dname == NULL) { + return KNOT_EBADARG; + } + + /* Fetch dname, need to release it later. */ + found_dname = knot_dname_table_find_dname(table ,*dname); + + if (!found_dname) { + /* Store reference in table. */ + return knot_dname_table_add_dname(table, *dname); + } else { + /*! \todo Remove the check for equality. */ + if (found_dname != *dname) { + /* Replace dname with found. */ + knot_dname_release(*dname); + *dname = found_dname; + return 1; /*! \todo Error code? */ + + } else { + + /* If the dname is already in the table, there is already + * a reference to it. + */ + knot_dname_release(found_dname); + } + } + + return KNOT_EOK; +} + +int knot_dname_table_shallow_copy(knot_dname_table_t *from, + knot_dname_table_t *to) +{ + to->id_counter = from->id_counter; + + if (to->tree == NULL) { + to->tree = malloc(sizeof(table_tree_t)); + if (to->tree == NULL) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + TREE_INIT(to->tree, compare_dname_table_nodes); + } + + return knot_dname_table_copy_node(from->tree->th_root, + &to->tree->th_root); +} + +void knot_dname_table_free(knot_dname_table_t **table) +{ + if (table == NULL || *table == NULL) { + return; + } + + /* Walk the tree and free each node, but not the dnames. */ + TREE_POST_ORDER_APPLY((*table)->tree, dname_table_node, avl, + delete_dname_table_node, 0); + + free((*table)->tree); + + free(*table); + *table = NULL; +} + +void knot_dname_table_deep_free(knot_dname_table_t **table) +{ + if (table == NULL || *table == NULL) { + return; + } + + /* Walk the tree and free each node, but free the dnames. */ + TREE_POST_ORDER_APPLY((*table)->tree, dname_table_node, avl, + delete_dname_table_node, (void *) 1); + + free((*table)->tree); + + free(*table); + *table = NULL; +} + +void knot_dname_table_destroy(knot_dname_table_t **table) +{ + if (table == NULL || *table == NULL) { + return; + } + + /* Walk the tree and free each node, but free the dnames. */ + TREE_POST_ORDER_APPLY((*table)->tree, dname_table_node, avl, + delete_dname_table_node, (void *) 2); + + free((*table)->tree); + + free(*table); + *table = NULL; +} + +void knot_dname_table_tree_inorder_apply(const knot_dname_table_t *table, + void (*applied_function)(knot_dname_t *node, + void *data), + void *data) +{ + struct knot_dname_table_fnc_data d; + d.data = data; + d.func = applied_function; + + TREE_FORWARD_APPLY(table->tree, dname_table_node, avl, + knot_dname_table_apply, &d); +} + diff --git a/src/libknot/zone/dname-table.h b/src/libknot/zone/dname-table.h new file mode 100644 index 0000000..dd86eaf --- /dev/null +++ b/src/libknot/zone/dname-table.h @@ -0,0 +1,168 @@ +/*! + * \file dname-table.h + * + * \author Jan Kadlec <jan.kadlec.@nic.cz> + * + * \brief Structures representing dname table and functions for + * manipulating these structures. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_DNAME_TABLE_H_ +#define _KNOT_DNAME_TABLE_H_ + +#include <config.h> + +#include "common/tree.h" + +#include "dname.h" +#include "common.h" + + +/*! + * \brief Structure encapsulating + */ +struct dname_table_node { + knot_dname_t *dname; /*!< Dname stored in node. */ + TREE_ENTRY(dname_table_node) avl; /*!< Tree variables. */ +}; + +/*! + * \brief Tree structure. + */ +typedef TREE_HEAD(avl, dname_table_node) table_tree_t; + +/*! + * \brief Structure holding tree together with dname ID counter. + */ +struct knot_dname_table { + unsigned int id_counter; /*!< ID counter (starts from 1) */ + table_tree_t *tree; /*!< AVL tree */ +}; + +typedef struct knot_dname_table knot_dname_table_t; + +/*! + * \brief Creates new empty domain name table. + * + * \retval Created table on success. + * \retval NULL on memory error. + */ +knot_dname_table_t *knot_dname_table_new(); + +/*! + * \brief Finds name in the domain name table. + * + * \note Reference count to dname will be incremented, caller is responsible + * for releasing it. + * + * \param table Domain name table to be searched. + * \param dname Dname to be searched. + * + * \retval Pointer to found dname when dname is present in the table. + * \retval NULL when dname is not present. + */ +knot_dname_t *knot_dname_table_find_dname(const knot_dname_table_t *table, + knot_dname_t *dname); + +/*! + * \brief Adds domain name to domain name table. + * + * \param table Domain name table to be added to. + * \param dname Domain name to be added. + * + * \warning Function does not check for duplicates! + * + * \note This function encapsulates dname in a structure and saves it to a tree. + * + * \retval KNOT_EOK on success. + * \retval KNOT_ENOMEM when memory runs out. + */ +int knot_dname_table_add_dname(knot_dname_table_t *table, + knot_dname_t *dname); + +/*! + * \brief Adds domain name to domain name table and checks for duplicates. + * + * \param table Domain name table to be added to. + * \param dname Domain name to be added. + * + * \note This function encapsulates dname in a structure and saves it to a tree. + * \note If a duplicate is found, \a dname is replaced by the name from table. + * + * \retval KNOT_EOK on success. + * \retval KNOT_ENOMEM when memory runs out. + */ +int knot_dname_table_add_dname_check(knot_dname_table_t *table, + knot_dname_t **dname); + +/*! + * \brief Creates a shallow copy of the domain name table. + * + * Expects an existing knot_dname_table_t structure to be passed via \a to, + * and fills it with the same data (domain names) as the original. Actual + * tree nodes are created, but domain names are not copied (just referenced). + * + * \param from Original domain name table. + * \param to Copy of the domain name table. + */ +int knot_dname_table_shallow_copy(knot_dname_table_t *from, + knot_dname_table_t *to); + +/*! + * \brief Frees dname table without its nodes. Sets pointer to NULL. + * + * \param table Table to be freed. + */ +void knot_dname_table_free(knot_dname_table_t **table); + +/*! + * \brief Frees dname table and all its nodes (and release dnames in the nodes) + * Sets pointer to NULL. + * + * \param table Table to be freed. + */ +void knot_dname_table_deep_free(knot_dname_table_t **table); + +/*! + * \brief Frees dname table and all its nodes (including dnames in the nodes) + * Sets pointer to NULL. + * + * \param table Table to be freed. + */ +void knot_dname_table_destroy(knot_dname_table_t **table); + +/*! + * \brief Encapsulation of domain name table tree traversal function. + * + * \param table Table containing tree to be traversed. + * \param applied_function Function to be used to process nodes. + * \param data Data to be passed to processing function. + */ +void knot_dname_table_tree_inorder_apply(const knot_dname_table_t *table, + void (*applied_function)(knot_dname_t *dname, + void *data), + void *data); + + +#endif // _KNOT_DNAME_TABLE_H_ + +/*! @} */ + diff --git a/src/libknot/zone/node.c b/src/libknot/zone/node.c new file mode 100644 index 0000000..1d2f938 --- /dev/null +++ b/src/libknot/zone/node.c @@ -0,0 +1,906 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdlib.h> +#include <assert.h> +#include <stdio.h> + +#include <urcu.h> + +#include "common.h" +#include "zone/node.h" +#include "rrset.h" +#include "util/error.h" +#include "common/skip-list.h" +#include "common/tree.h" +#include "util/debug.h" + +/*----------------------------------------------------------------------------*/ +/* Non-API functions */ +/*----------------------------------------------------------------------------*/ +/*! + * \brief Returns the delegation point flag + * + * \param flags Flags to retrieve the flag from. + * + * \return A byte with only the delegation point flag set if it was set in + * \a flags. + */ +static inline uint8_t knot_node_flags_get_deleg(uint8_t flags) +{ + return flags & KNOT_NODE_FLAGS_DELEG; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Sets the delegation point flag. + * + * \param flags Flags to set the flag in. + */ +static inline void knot_node_flags_set_deleg(uint8_t *flags) +{ + *flags |= KNOT_NODE_FLAGS_DELEG; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Returns the non-authoritative node flag + * + * \param flags Flags to retrieve the flag from. + * + * \return A byte with only the non-authoritative node flag set if it was set in + * \a flags. + */ +static inline uint8_t knot_node_flags_get_nonauth(uint8_t flags) +{ + return flags & KNOT_NODE_FLAGS_NONAUTH; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Sets the non-authoritative node flag. + * + * \param flags Flags to set the flag in. + */ +static inline void knot_node_flags_set_nonauth(uint8_t *flags) +{ + *flags |= KNOT_NODE_FLAGS_NONAUTH; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Returns the old node flag + * + * \param flags Flags to retrieve the flag from. + * + * \return A byte with only the old node flag set if it was set in \a flags. + */ +static inline uint8_t knot_node_flags_get_old(uint8_t flags) +{ + return flags & KNOT_NODE_FLAGS_OLD; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Sets the old node flag. + * + * \param flags Flags to set the flag in. + */ +static inline void knot_node_flags_set_new(uint8_t *flags) +{ + *flags |= KNOT_NODE_FLAGS_NEW; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Returns the new node flag + * + * \param flags Flags to retrieve the flag from. + * + * \return A byte with only the new node flag set if it was set in \a flags. + */ +static inline uint8_t knot_node_flags_get_new(uint8_t flags) +{ + return flags & KNOT_NODE_FLAGS_NEW; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Sets the new node flag. + * + * \param flags Flags to set the flag in. + */ +static inline void knot_node_flags_set_old(uint8_t *flags) +{ + *flags |= KNOT_NODE_FLAGS_OLD; +} + +/*----------------------------------------------------------------------------*/ + +static inline void knot_node_flags_clear_new(uint8_t *flags) +{ + *flags &= ~KNOT_NODE_FLAGS_NEW; +} + +/*----------------------------------------------------------------------------*/ + +static inline void knot_node_flags_clear_old(uint8_t *flags) +{ + *flags &= ~KNOT_NODE_FLAGS_OLD; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Compares the two keys as RR types. + * + * \note This function may be used in data structures requiring generic + * comparation function. + * + * \param key1 First RR type. + * \param key2 Second RR type. + * + * \retval 0 if \a key1 is equal to \a key2. + * \retval < 0 if \a key1 is lower than \a key2. + * \retval > 0 if \a key1 is higher than \a key2. + */ +static int compare_rrset_types(void *rr1, void *rr2) +{ + knot_rrset_t *rrset1 = (knot_rrset_t *)rr1; + knot_rrset_t *rrset2 = (knot_rrset_t *)rr2; + return ((rrset1->type > rrset2->type) ? 1 : + (rrset1->type == rrset2->type) ? 0 : -1); +} + +/*----------------------------------------------------------------------------*/ + +static int knot_node_zone_gen_is_new(const knot_node_t *node) +{ + assert(node->zone != NULL); + knot_zone_contents_t *cont = rcu_dereference(node->zone->contents); + assert(cont != NULL); + return knot_zone_contents_gen_is_new(cont); +} + +/*----------------------------------------------------------------------------*/ + +static int knot_node_zone_gen_is_old(const knot_node_t *node) +{ + assert(node->zone != NULL); + knot_zone_contents_t *cont = rcu_dereference(node->zone->contents); + assert(cont != NULL); + return knot_zone_contents_gen_is_old(cont); +} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +knot_node_t *knot_node_new(knot_dname_t *owner, knot_node_t *parent, + uint8_t flags) +{ + knot_node_t *ret = (knot_node_t *)calloc(1, sizeof(knot_node_t)); + if (ret == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + + /* Store reference to owner. */ + knot_dname_retain(owner); + ret->owner = owner; + knot_node_set_parent(ret, parent); + ret->rrset_tree = gen_tree_new(compare_rrset_types); + ret->flags = flags; + + assert(ret->children == 0); + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_node_add_rrset(knot_node_t *node, knot_rrset_t *rrset, + int merge) +{ + int ret = 0; + + if ((ret = (gen_tree_add(node->rrset_tree, rrset, + (merge) ? knot_rrset_merge : NULL))) < 0) { + dbg_node("Failed to add rrset to node->rrset_tree.\n"); + return KNOT_ERROR; + } + + if (ret >= 0) { + node->rrset_count += (ret > 0 ? 0 : 1); + return ret; + } else { + return KNOT_ERROR; + } +} + +/*----------------------------------------------------------------------------*/ + +const knot_rrset_t *knot_node_rrset(const knot_node_t *node, + uint16_t type) +{ + assert(node != NULL); + assert(node->rrset_tree != NULL); + knot_rrset_t rrset; + rrset.type = type; + return (const knot_rrset_t *)gen_tree_find(node->rrset_tree, &rrset); +} + +/*----------------------------------------------------------------------------*/ + +knot_rrset_t *knot_node_get_rrset(knot_node_t *node, uint16_t type) +{ + knot_rrset_t rrset; + rrset.type = type; + return (knot_rrset_t *)gen_tree_find(node->rrset_tree, &rrset); +} + +/*----------------------------------------------------------------------------*/ + +knot_rrset_t *knot_node_remove_rrset(knot_node_t *node, uint16_t type) +{ + knot_rrset_t dummy_rrset; + dummy_rrset.type = type; + knot_rrset_t *rrset = + (knot_rrset_t *)gen_tree_find(node->rrset_tree, &dummy_rrset); + if (rrset != NULL) { + gen_tree_remove(node->rrset_tree, rrset); + node->rrset_count--; + } + return rrset; +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_remove_all_rrsets(knot_node_t *node) +{ + // remove RRSets but do not delete them + gen_tree_clear(node->rrset_tree); + node->rrset_count = 0; + +} + +/*----------------------------------------------------------------------------*/ + +short knot_node_rrset_count(const knot_node_t *node) +{ + return node->rrset_count; +} + +/*----------------------------------------------------------------------------*/ + +struct knot_node_save_rrset_arg { + knot_rrset_t **array; + size_t count; +}; + +static void save_rrset_to_array(void *node, void *data) +{ + knot_rrset_t *rrset = (knot_rrset_t *)node; + struct knot_node_save_rrset_arg *args = + (struct knot_node_save_rrset_arg *)data; + args->array[args->count++] = rrset; +} + +knot_rrset_t **knot_node_get_rrsets(const knot_node_t *node) +{ +// knot_node_dump(node, 1); + if (node->rrset_count == 0) { + return NULL; + } + knot_rrset_t **rrsets = (knot_rrset_t **)malloc( + node->rrset_count * sizeof(knot_rrset_t *)); + CHECK_ALLOC_LOG(rrsets, NULL); + struct knot_node_save_rrset_arg args; + args.array = rrsets; + args.count = 0; + + gen_tree_apply_inorder(node->rrset_tree, save_rrset_to_array, + &args); + + assert(args.count == node->rrset_count); + + return rrsets; +} + +/*----------------------------------------------------------------------------*/ + +const knot_rrset_t **knot_node_rrsets(const knot_node_t *node) +{ + //knot_node_dump((knot_node_t *)node, (void*)1); + if (node->rrset_count == 0) { + return NULL; + } + + knot_rrset_t **rrsets = (knot_rrset_t **)malloc( + node->rrset_count * sizeof(knot_rrset_t *)); + CHECK_ALLOC_LOG(rrsets, NULL); + struct knot_node_save_rrset_arg args; + args.array = rrsets; + args.count = 0; + + gen_tree_apply_inorder(node->rrset_tree, save_rrset_to_array, + &args); + + assert(args.count == node->rrset_count); + assert(args.count); + + return (const knot_rrset_t **)rrsets; + +} + +/*----------------------------------------------------------------------------*/ + +const knot_node_t *knot_node_parent(const knot_node_t *node, + int check_version) +{ +// assert(!check_version +// || (node->zone != NULL && node->zone->contents != NULL)); + + knot_node_t *parent = node->parent; + + if (check_version && node->zone != NULL) { + int new_gen = knot_node_zone_gen_is_new(node); +// short ver = knot_node_zone_generation(node); + + /*! \todo Remove, this will not be true during the reference + * fixing. + */ +// assert(new_gen || parent == NULL +// || !knot_node_is_new(parent)); + + if (new_gen && parent != NULL) { + // we want the new node + assert(node->parent->new_node != NULL); + parent = parent->new_node; + } + } + + return parent; +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_set_parent(knot_node_t *node, knot_node_t *parent) +{ + // decrease number of children of previous parent + if (node->parent != NULL) { + --parent->children; + } + // set the parent + node->parent = parent; + + // increase the count of children of the new parent + if (parent != NULL) { + ++parent->children; + } +} + +/*----------------------------------------------------------------------------*/ + +unsigned int knot_node_children(const knot_node_t *node) +{ + return node->children; +} + +/*----------------------------------------------------------------------------*/ + +const knot_node_t *knot_node_previous(const knot_node_t *node, + int check_version) +{ + return knot_node_get_previous(node, check_version); +} + +/*----------------------------------------------------------------------------*/ + +knot_node_t *knot_node_get_previous(const knot_node_t *node, + int check_version) +{ + assert(!check_version + || (node->zone != NULL && node->zone->contents != NULL)); + + knot_node_t *prev = node->prev; + + if (check_version && prev != NULL) { + int new_gen = knot_node_zone_gen_is_new(node); + int old_gen = knot_node_zone_gen_is_old(node); +// short ver = knot_node_zone_generation(node); + + if (old_gen) { // we want old node + while (knot_node_is_new(prev)) { + prev = prev->prev; + } + assert(!knot_node_is_new(prev)); + } else if (new_gen) { // we want new node + while (knot_node_is_old(prev)) { + if (prev->new_node) { + prev = prev->new_node; + } else { + prev = prev; + } + } + assert(knot_node_is_new(prev)); + } + } + + return prev; +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_set_previous(knot_node_t *node, knot_node_t *prev) +{ + node->prev = prev; + if (prev != NULL) { + // set the prev pointer of the next node to the given node + if (prev->next != NULL) { + assert(prev->next->prev == prev); + prev->next->prev = node; + } + node->next = prev->next; + prev->next = node; + } +} + +/*----------------------------------------------------------------------------*/ + +const knot_node_t *knot_node_nsec3_node(const knot_node_t *node, + int check_version) +{ + knot_node_t *nsec3_node = node->nsec3_node; + if (nsec3_node == NULL) { + return NULL; + } + + if (check_version) { + int new_gen = knot_node_zone_gen_is_new(node); + int old_gen = knot_node_zone_gen_is_old(node); +// short ver = knot_node_zone_generation(node); + assert(new_gen || !knot_node_is_new(nsec3_node)); + if (old_gen && knot_node_is_new(nsec3_node)) { + return NULL; + } else if (new_gen && knot_node_is_old(nsec3_node)) { + nsec3_node = nsec3_node->new_node; + } + } + + return nsec3_node; +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_set_nsec3_node(knot_node_t *node, knot_node_t *nsec3_node) +{ + node->nsec3_node = nsec3_node; + if (nsec3_node != NULL) { + nsec3_node->nsec3_referer = node; + } +} + +/*----------------------------------------------------------------------------*/ + +const knot_dname_t *knot_node_owner(const knot_node_t *node) +{ + return node->owner; +} + +/*----------------------------------------------------------------------------*/ + +knot_dname_t *knot_node_get_owner(const knot_node_t *node) +{ + return node->owner; +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_set_owner(knot_node_t *node, knot_dname_t* owner) +{ + if (node) { + /* Retain new owner and release old owner. */ + knot_dname_retain(owner); + knot_dname_release(node->owner); + node->owner = owner; + } +} + +/*----------------------------------------------------------------------------*/ + +const knot_node_t *knot_node_wildcard_child(const knot_node_t *node, + int check_version) +{ + knot_node_t *w = node->wildcard_child; + + if (check_version && w != 0) { + int new_gen = knot_node_zone_gen_is_new(node); + int old_gen = knot_node_zone_gen_is_old(node); +// short ver = knot_node_zone_generation(node); + + if (old_gen && knot_node_is_new(w)) { + return NULL; + } else if (new_gen && knot_node_is_old(w)) { + assert(w->new_node != NULL); + w = w->new_node; + } + } + + return w; +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_set_wildcard_child(knot_node_t *node, + knot_node_t *wildcard_child) +{ + node->wildcard_child = wildcard_child; +// assert(wildcard_child->parent == node); +} + +/*----------------------------------------------------------------------------*/ + +const knot_node_t *knot_node_current(const knot_node_t *node) +{ + if (node == NULL || node->zone == NULL + || knot_zone_contents(node->zone) == NULL) { + return node; + } + + int new_gen = knot_node_zone_gen_is_new(node); + int old_gen = knot_node_zone_gen_is_old(node); +// short ver = knot_node_zone_generation(node); + + if (old_gen && knot_node_is_new(node)) { + return NULL; + } else if (new_gen && knot_node_is_old(node)) { + assert(node->new_node != NULL); + return node->new_node; + } + return node; +} + +/*----------------------------------------------------------------------------*/ + +knot_node_t *knot_node_get_current(knot_node_t *node) +{ + if (node == NULL || node->zone == NULL + || knot_zone_contents(node->zone) == NULL) { + return node; + } + + int new_gen = knot_node_zone_gen_is_new(node); + int old_gen = knot_node_zone_gen_is_old(node); +// short ver = knot_node_zone_generation(node); + + if (old_gen && knot_node_is_new(node)) { + return NULL; + } else if (new_gen && knot_node_is_old(node)) { + assert(node->new_node != NULL); + return node->new_node; + } + + assert((old_gen && knot_node_is_old(node)) + || (new_gen && knot_node_is_new(node)) + || (!old_gen && !new_gen)); + + return node; +} + +/*----------------------------------------------------------------------------*/ + +const knot_node_t *knot_node_new_node(const knot_node_t *node) +{ + return node->new_node; +} + +/*----------------------------------------------------------------------------*/ + +knot_node_t *knot_node_get_new_node(const knot_node_t *node) +{ + return node->new_node; +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_set_new_node(knot_node_t *node, + knot_node_t *new_node) +{ + node->new_node = new_node; +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_set_zone(knot_node_t *node, knot_zone_t *zone) +{ + node->zone = zone; +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_update_ref(knot_node_t **ref) +{ + if (*ref != NULL && knot_node_is_old(*ref)) { + *ref = (*ref)->new_node; + } +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_update_refs(knot_node_t *node) +{ + /* CLEANUP */ + /* OMG! */ + // reference to previous node + knot_node_update_ref(&node->prev); +// if (node->prev && knot_node_is_old(node->prev)) { +// assert(node->prev->new_node != NULL); +// node->prev = node->prev->new_node; +// } + + // reference to next node + knot_node_update_ref(&node->next); +// if (node->next && knot_node_is_old(node->next)) { +// assert(node->next->new_node != NULL); +// node->next = node->next->new_node; +// } + + // reference to parent +// if (node->parent && knot_node_is_old(node->parent)) { +// assert(node->parent->new_node != NULL); +// // do not use the API function to set parent, so that children count +// // is not changed +// //knot_node_set_parent(node, node->parent->new_node); +// node->parent = node->parent->new_node; +// } + knot_node_update_ref(&node->parent); + + // reference to wildcard child + knot_node_update_ref(&node->wildcard_child); +// if (node->wildcard_child && knot_node_is_old(node->wildcard_child)) { +// assert(node->wildcard_child->new_node != NULL); +// node->wildcard_child = node->wildcard_child->new_node; +// } + + // reference to NSEC3 node + knot_node_update_ref(&node->nsec3_node); +// if (node->nsec3_node && knot_node_is_old(node->nsec3_node)) { +// assert(node->nsec3_node->new_node != NULL); +// node->nsec3_node = node->nsec3_node->new_node; +// } + + // reference to NSEC3 referrer + knot_node_update_ref(&node->nsec3_referer); +// if (node->nsec3_referer && knot_node_is_old(node->nsec3_referer)) { +// assert(node->nsec3_referer->new_node != NULL); +// node->nsec3_referer = node->nsec3_referer->new_node; +// } +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_set_deleg_point(knot_node_t *node) +{ + knot_node_flags_set_deleg(&node->flags); +} + +/*----------------------------------------------------------------------------*/ + +int knot_node_is_deleg_point(const knot_node_t *node) +{ + return knot_node_flags_get_deleg(node->flags); +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_set_non_auth(knot_node_t *node) +{ + knot_node_flags_set_nonauth(&node->flags); +} + +/*----------------------------------------------------------------------------*/ + +int knot_node_is_non_auth(const knot_node_t *node) +{ + return knot_node_flags_get_nonauth(node->flags); +} + +/*----------------------------------------------------------------------------*/ + +int knot_node_is_auth(const knot_node_t *node) +{ + return (node->flags == 0); +} + +/*----------------------------------------------------------------------------*/ + +int knot_node_is_new(const knot_node_t *node) +{ + return knot_node_flags_get_new(node->flags); +} + +/*----------------------------------------------------------------------------*/ + +int knot_node_is_old(const knot_node_t *node) +{ + return knot_node_flags_get_old(node->flags); +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_set_new(knot_node_t *node) +{ + knot_node_flags_set_new(&node->flags); +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_set_old(knot_node_t *node) +{ + knot_node_flags_set_old(&node->flags); +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_clear_new(knot_node_t *node) +{ + knot_node_flags_clear_new(&node->flags); +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_clear_old(knot_node_t *node) +{ + knot_node_flags_clear_old(&node->flags); +} + +/*----------------------------------------------------------------------------*/ + +static void knot_node_free_rrsets_from_tree(void *item, void *data) +{ + if (item == NULL) { + return; + } + + knot_rrset_t *rrset = (knot_rrset_t *)(item); + knot_rrset_deep_free(&rrset, 0, 1, *((int *)data)); +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_free_rrsets(knot_node_t *node, int free_rdata_dnames) +{ + /* CLEANUP */ +// knot_rrset_t **rrsets = knot_node_get_rrsets(node); +// for (int i = 0; i < node->rrset_count; i++) { +// knot_rrset_deep_free(&(rrsets[i]), 0, 1, free_rdata_dnames); +// } + +// free(rrsets); + + char *name = knot_dname_to_str(node->owner); + free(name); + + gen_tree_destroy(&node->rrset_tree, knot_node_free_rrsets_from_tree, + (void *)&free_rdata_dnames); +} + +/*----------------------------------------------------------------------------*/ + +void knot_node_free(knot_node_t **node, int free_owner, int fix_refs) +{ + if (node == NULL || *node == NULL) { + return; + } + + dbg_node("Freeing node.\n"); + if ((*node)->rrset_tree != NULL) { + dbg_node("Freeing RRSets.\n"); + gen_tree_destroy(&(*node)->rrset_tree, NULL, NULL); + } + + /*! \todo Always release owner? */ + //if (free_owner) { + dbg_node("Releasing owner.\n"); + knot_dname_release((*node)->owner); + //} + + // check nodes referencing this node and fix the references + + if (fix_refs) { + // previous node + dbg_node("Checking previous.\n"); + if ((*node)->prev && (*node)->prev->next == (*node)) { + (*node)->prev->next = (*node)->next; + } + + dbg_node("Checking next.\n"); + if ((*node)->next && (*node)->next->prev == (*node)) { + (*node)->next->prev = (*node)->prev; + } + + // NSEC3 node + dbg_node("Checking NSEC3.\n"); + if ((*node)->nsec3_node + && (*node)->nsec3_node->nsec3_referer == (*node)) { + (*node)->nsec3_node->nsec3_referer = NULL; + } + + dbg_node("Checking NSEC3 ref.\n"); + if ((*node)->nsec3_referer + && (*node)->nsec3_referer->nsec3_node == (*node)) { + (*node)->nsec3_referer->nsec3_node = NULL; + } + + // wildcard child node + dbg_node("Checking parent's wildcard child.\n"); + if ((*node)->parent + && (*node)->parent->wildcard_child == (*node)) { + (*node)->parent->wildcard_child = NULL; + } + + // fix parent's children count + if ((*node)->parent) { + --(*node)->parent->children; + } + } + + free(*node); + *node = NULL; + + dbg_node("Done.\n"); +} + +/*----------------------------------------------------------------------------*/ + +int knot_node_compare(knot_node_t *node1, knot_node_t *node2) +{ + return knot_dname_compare(node1->owner, node2->owner); +} + +/*----------------------------------------------------------------------------*/ + +int knot_node_shallow_copy(const knot_node_t *from, knot_node_t **to) +{ + // create new node + *to = knot_node_new(from->owner, from->parent, from->flags); + if (*to == NULL) { + return KNOT_ENOMEM; + } + + /* Free old rrset_tree, as it will be replaced by shallow copy. */ + gen_tree_destroy(&(*to)->rrset_tree, 0, 0); + + // copy references + // do not use the API function to set parent, so that children count + // is not changed + memcpy(*to, from, sizeof(knot_node_t)); + + // copy RRSets + // copy the skip list with the old references + /* CLEANUP */ + (*to)->rrset_tree = gen_tree_shallow_copy(from->rrset_tree); +// assert((*to)->rrset_tree != from->rrset_tree); +// (*to)->rrsets = skip_copy_list(from->rrsets); + if ((*to)->rrset_tree == NULL) { + free(*to); + *to = NULL; + return KNOT_ENOMEM; + } + + return KNOT_EOK; +} diff --git a/src/libknot/zone/node.h b/src/libknot/zone/node.h new file mode 100644 index 0000000..fcb612d --- /dev/null +++ b/src/libknot/zone/node.h @@ -0,0 +1,436 @@ +/*! + * \file node.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Structure representing one node in domain name tree and API for + * manipulating it. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_NODE_H_ +#define _KNOT_NODE_H_ + +#include "dname.h" +#include "common/skip-list.h" +#include "rrset.h" +#include "common/tree.h" +#include "common/general-tree.h" + +struct knot_zone; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Structure representing one node in a domain name tree, i.e. one domain + * name in a zone. + * + * RRSets are ordered by type and stored in a skip-list to allow fast lookup. + */ +struct knot_node { + knot_dname_t *owner; /*!< Domain name being the owner of this node. */ + struct knot_node *parent; /*!< Parent node in the name hierarchy. */ + + /*! \brief Type-ordered list of RRSets belonging to this node. */ + general_tree_t *rrset_tree; + + unsigned short rrset_count; /*!< Number of RRSets stored in the node. */ + + /*! \brief Wildcard node being the direct descendant of this node. */ + struct knot_node *wildcard_child; + + /*! + * \brief Previous node in canonical order. + * + * Only authoritative nodes or delegation points are referenced by this, + * as only they may contain NSEC records needed for authenticating + * negative answers. + */ + struct knot_node *prev; + + struct knot_node *next; + + /*! + * \brief NSEC3 node corresponding to this node. + * + * Such NSEC3 node has owner in form of the hashed domain name of this + * node prepended as a single label to the zone name. + */ + struct knot_node *nsec3_node; + + struct knot_node *nsec3_referer; + + /*! + * \brief Various flags. + * + * Currently only two: + * 0x01 - node is a delegation point + * 0x02 - node is non-authoritative (under a delegation point) + * 0x80 - node is old and will be removed (during update) + * 0x40 - node is new, should not be used while zone is old + */ + uint8_t flags; + + struct knot_node *new_node; + + unsigned int children; + + /*! + * \brief Generation of node to be used. + * + * If set to 0, the old node will be used. Otherwise new nodes will + * be used. This applies when getting some referenced node. + + */ +// short **generation; + + struct knot_zone *zone; +}; + +typedef struct knot_node knot_node_t; + +/*----------------------------------------------------------------------------*/ +/*! \brief Flags used to mark nodes with some property. */ +typedef enum { + /*! \brief Node is a delegation point (i.e. marking a zone cut). */ + KNOT_NODE_FLAGS_DELEG = (uint8_t)0x01, + /*! \brief Node is not authoritative (i.e. below a zone cut). */ + KNOT_NODE_FLAGS_NONAUTH = (uint8_t)0x02, + /*! \brief Node is old and will be removed (during update). */ + KNOT_NODE_FLAGS_OLD = (uint8_t)0x80, + /*! \brief Node is new and should not be used while zoen is old. */ + KNOT_NODE_FLAGS_NEW = (uint8_t)0x40 +} knot_node_flags_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates and initializes new node structure. + * + * \todo Owner reference counter will be increased. + * + * \param owner Owner of the created node. + * \param parent Parent of the created node. + * \param flags Document me. + * + * \todo Document missing parameters. + * + * \return Newly created node or NULL if an error occured. + */ +knot_node_t *knot_node_new(knot_dname_t *owner, knot_node_t *parent, + uint8_t flags); + +/*! + * \brief Adds an RRSet to the node. + * + * \param node Node to add the RRSet to. + * \param rrset RRSet to add. + * + * \retval KNOT_EOK on success. + * \retval KNOT_ERROR if the RRSet could not be inserted. + */ +int knot_node_add_rrset(knot_node_t *node, knot_rrset_t *rrset, + int merge); + +/*! + * \brief Returns the RRSet of the given type from the node. + * + * \param node Node to get the RRSet from. + * \param type Type of the RRSet to retrieve. + * + * \return RRSet from node \a node having type \a type, or NULL if no such + * RRSet exists in this node. + */ +const knot_rrset_t *knot_node_rrset(const knot_node_t *node, + uint16_t type); + +/*! + * \brief Returns the RRSet of the given type from the node (non-const version). + * + * \param node Node to get the RRSet from. + * \param type Type of the RRSet to retrieve. + * + * \return RRSet from node \a node having type \a type, or NULL if no such + * RRSet exists in this node. + */ +knot_rrset_t *knot_node_get_rrset(knot_node_t *node, uint16_t type); + +knot_rrset_t *knot_node_remove_rrset(knot_node_t *node, uint16_t type); + +void knot_node_remove_all_rrsets(knot_node_t *node); + +/*! + * \brief Returns number of RRSets in the node. + * + * \param node Node to get the RRSet count from. + * + * \return Number of RRSets in \a node. + */ +short knot_node_rrset_count(const knot_node_t *node); + +/*! + * \brief Returns all RRSets from the node. + * + * \param node Node to get the RRSets from. + * + * \return Newly allocated array of RRSets or NULL if an error occured. + */ +knot_rrset_t **knot_node_get_rrsets(const knot_node_t *node); + +/*! + * \brief Returns all RRSets from the node. + * + * \note This function is identical to knot_node_get_rrsets(), only it returns + * non-modifiable data. + * + * \param node Node to get the RRSets from. + * + * \return Newly allocated array of RRSets or NULL if an error occured. + */ +const knot_rrset_t **knot_node_rrsets(const knot_node_t *node); + +/*! + * \brief Returns the parent of the node. + * + * \param node Node to get the parent of. + * + * \return Parent node of the given node or NULL if no parent has been set (e.g. + * node in a zone apex has no parent). + */ +const knot_node_t *knot_node_parent(const knot_node_t *node, + int check_version); + +/*! + * \brief Sets the parent of the node. + * + * \param node Node to set the parent of. + * \param parent Parent to set to the node. + */ +void knot_node_set_parent(knot_node_t *node, knot_node_t *parent); + +unsigned int knot_node_children(const knot_node_t *node); + +/*! + * \brief Returns the previous authoritative node or delegation point in + * canonical order or the first node in zone. + * + * \param node Node to get the previous node of. + * + * \return Previous authoritative node or delegation point in canonical order or + * the first node in zone if \a node is the last node in zone. + * \retval NULL if previous node is not set. + */ +const knot_node_t *knot_node_previous(const knot_node_t *node, + int check_version); + +/*! + * \brief Returns the previous authoritative node or delegation point in + * canonical order or the first node in zone. + * + * \note This function is identical to knot_node_previous() except that it + * returns non-const node. + * + * \param node Node to get the previous node of. + * + * \return Previous authoritative node or delegation point in canonical order or + * the first node in zone if \a node is the last node in zone. + * \retval NULL if previous node is not set. + */ +knot_node_t *knot_node_get_previous(const knot_node_t *node, + int check_version); + +/*! + * \brief Sets the previous node of the given node. + * + * \param node Node to set the previous node to. + * \param prev Previous node to set. + */ +void knot_node_set_previous(knot_node_t *node, knot_node_t *prev); + +/*! + * \brief Returns the NSEC3 node corresponding to the given node. + * + * \param node Node to get the NSEC3 node for. + * + * \return NSEC3 node corresponding to \a node (i.e. node with owner name + * created by concatenating the hash of owner domain name of \a node + * and the name of the zone \a node belongs to). + * \retval NULL if the NSEC3 node is not set. + */ +const knot_node_t *knot_node_nsec3_node(const knot_node_t *node, + int check_version); + +/*! + * \brief Sets the corresponding NSEC3 node of the given node. + * + * \param node Node to set the NSEC3 node to. + * \param nsec3_node NSEC3 node to set. + */ +void knot_node_set_nsec3_node(knot_node_t *node, knot_node_t *nsec3_node); + +/*! + * \brief Returns the owner of the node. + * + * \param node Node to get the owner of. + * + * \return Owner of the given node. + */ +const knot_dname_t *knot_node_owner(const knot_node_t *node); + +/*! + * \todo Document me. + */ +knot_dname_t *knot_node_get_owner(const knot_node_t *node); + +/*! + * \brief Set node owner to specified dname. + * + * Previous owner will be replaced if exist. + * + * \param node Specified node. + * \param owner New owner dname. + */ +void knot_node_set_owner(knot_node_t *node, knot_dname_t* owner); + +/*! + * \brief Returns the wildcard child of the node. + * + * \param node Node to get the owner of. + * + * \return Wildcard child of the given node or NULL if it has none. + */ +const knot_node_t *knot_node_wildcard_child(const knot_node_t *node, + int check_version); + +/*! + * \brief Sets the wildcard child of the node. + * + * \param node Node to set the wildcard child of. + * \param wildcard_child Wildcard child of the node. + */ +void knot_node_set_wildcard_child(knot_node_t *node, + knot_node_t *wildcard_child); + +const knot_node_t *knot_node_current(const knot_node_t *node); + +knot_node_t *knot_node_get_current(knot_node_t *node); + +const knot_node_t *knot_node_new_node(const knot_node_t *node); + +knot_node_t *knot_node_get_new_node(const knot_node_t *node); + +void knot_node_set_new_node(knot_node_t *node, + knot_node_t *new_node); + +void knot_node_set_zone(knot_node_t *node, struct knot_zone *zone); + +void knot_node_update_ref(knot_node_t **ref); + +void knot_node_update_refs(knot_node_t *node); + +/*! + * \brief Mark the node as a delegation point. + * + * \param node Node to mark as a delegation point. + */ +void knot_node_set_deleg_point(knot_node_t *node); + +/*! + * \brief Checks if the node is a delegation point. + * + * \param node Node to check. + * + * \retval <> 0 if \a node is marked as delegation point. + * \retval 0 otherwise. + */ +int knot_node_is_deleg_point(const knot_node_t *node); + +/*! + * \brief Mark the node as non-authoritative. + * + * \param node Node to mark as non-authoritative. + */ +void knot_node_set_non_auth(knot_node_t *node); + +/*! + * \brief Checks if the node is non-authoritative. + * + * \param node Node to check. + * + * \retval <> 0 if \a node is marked as non-authoritative. + * \retval 0 otherwise. + */ +int knot_node_is_non_auth(const knot_node_t *node); + +int knot_node_is_auth(const knot_node_t *node); + +int knot_node_is_new(const knot_node_t *node); + +int knot_node_is_old(const knot_node_t *node); + +void knot_node_set_new(knot_node_t *node); + +void knot_node_set_old(knot_node_t *node); + +void knot_node_clear_new(knot_node_t *node); + +void knot_node_clear_old(knot_node_t *node); + +/*! + * \brief Destroys the RRSets within the node structure. + * + * \param node Node to be destroyed. + * \param free_rdata_dnames Set to <> 0 if you want to delete ALL domain names + * present in RDATA. Set to 0 otherwise. (See + * knot_rdata_deep_free().) + */ +void knot_node_free_rrsets(knot_node_t *node, int free_rdata_dnames); + +/*! + * \brief Destroys the node structure. + * + * Does not destroy the RRSets within the node. + * Also sets the given pointer to NULL. + * + * \param node Node to be destroyed. + * \param free_owner Set to 0 if you do not want the owner domain name to be + * destroyed also. Set to <> 0 otherwise. + * \param fix_refs + * + * \todo Document missing parameters. + */ +void knot_node_free(knot_node_t **node, int free_owner, int fix_refs); + +/*! + * \brief Compares two nodes according to their owner. + * + * \param node1 First node. + * \param node2 Second node. + * + * \retval < 0 if \a node1 goes before \a node2 according to canonical order + * of their owner names. + * \retval 0 if they are equal. + * \retval > 0 if \a node1 goes after \a node2. + */ +int knot_node_compare(knot_node_t *node1, knot_node_t *node2); + +int knot_node_shallow_copy(const knot_node_t *from, knot_node_t **to); + +#endif /* _KNOT_NODE_H_ */ + +/*! @} */ diff --git a/src/libknot/zone/zone-contents.c b/src/libknot/zone/zone-contents.c new file mode 100644 index 0000000..d550728 --- /dev/null +++ b/src/libknot/zone/zone-contents.c @@ -0,0 +1,2396 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> + +#include "zone/zone-contents.h" +#include "util/error.h" +#include "util/debug.h" +#include "common/base32hex.h" +#include "consts.h" + +/*----------------------------------------------------------------------------*/ +/* Non-API functions */ +/*----------------------------------------------------------------------------*/ + +typedef struct { + void (*func)(knot_node_t *, void *); + void *data; +} knot_zone_tree_func_t; + +typedef struct { + knot_node_t *first_node; + knot_zone_contents_t *zone; + knot_node_t *previous_node; + int check_ver; +} knot_zone_adjust_arg_t; + +/*----------------------------------------------------------------------------*/ + +static void knot_zone_tree_apply(knot_zone_tree_node_t *node, + void *data) +{ + if (node == NULL || data == NULL) { + return; + } + + knot_zone_tree_func_t *f = (knot_zone_tree_func_t *)data; + f->func(node->node, f->data); +} + +/*----------------------------------------------------------------------------*/ + +/*! + * \brief Checks if the given node can be inserted into the given zone. + * + * Checks if both the arguments are non-NULL and if the owner of the node + * belongs to the zone (i.e. is a subdomain of the zone apex). + * + * \param zone Zone to which the node is going to be inserted. + * \param node Node to check. + * + * \retval KNOT_EOK if both arguments are non-NULL and the node belongs to the + * zone. + * \retval KNOT_EBADARG if either of the arguments is NULL. + * \retval KNOT_EBADZONE if the node does not belong to the zone. + */ +static int knot_zone_contents_check_node( + const knot_zone_contents_t *contents, const knot_node_t *node) +{ + if (contents == NULL || node == NULL) { + return KNOT_EBADARG; + } + + // assert or just check?? + assert(contents->apex != NULL); + + if (!knot_dname_is_subdomain(node->owner, + knot_node_owner(contents->apex))) { +dbg_zone_exec( + char *node_owner = knot_dname_to_str(knot_node_owner(node)); + char *apex_owner = knot_dname_to_str(contents->apex->owner); + dbg_zone("zone: Trying to insert foreign node to a " + "zone. Node owner: %s, zone apex: %s\n", + node_owner, apex_owner); + free(node_owner); + free(apex_owner); +); + return KNOT_EBADZONE; + } + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Destroys all RRSets in a node. + * + * This function is designed to be used in the tree-iterating functions. + * + * \param node Node to destroy RRSets from. + * \param data Unused parameter. + */ +static void knot_zone_contents_destroy_node_rrsets_from_tree( + knot_zone_tree_node_t *tnode, void *data) +{ + assert(tnode != NULL); + assert(tnode->node != NULL); + + int free_rdata_dnames = (int)((intptr_t)data); + knot_node_free_rrsets(tnode->node, free_rdata_dnames); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Destroys node owner. + * + * This function is designed to be used in the tree-iterating functions. + * + * \param node Node to destroy the owner of. + * \param data Unused parameter. + */ +static void knot_zone_contents_destroy_node_owner_from_tree( + knot_zone_tree_node_t *tnode, void *data) +{ + assert(tnode != NULL); + assert(tnode->node != NULL); + + UNUSED(data); + /*!< \todo change completely! */ + knot_node_free(&tnode->node, 0, 0); +} + +/*! + * \brief Finds and sets wildcard child for given node's owner. + * + * \param zone Current zone. + * \param node Node to be used. + */ +static void find_and_set_wildcard_child(knot_zone_contents_t *zone, + knot_node_t *node) +{ + knot_dname_t *chopped = knot_dname_left_chop(node->owner); + assert(chopped); + knot_node_t *wildcard_parent; + wildcard_parent = + knot_zone_contents_get_node(zone, chopped); + + knot_dname_free(&chopped); + + assert(wildcard_parent); /* it *has* to be there */ + + knot_node_set_wildcard_child(wildcard_parent, node); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adjusts one RDATA item by replacing domain name by one present in the + * zone. + * + * This function tries to find the domain name in the zone. If the name is not + * in the zone, it does nothing. If it is there, it destroys the domain name + * stored in the RDATA item and replaces it by pointer to the domain name from + * the zone. + * + * \warning Call this function only with RDATA items which store domain names, + * otherwise the behaviour is undefined. + * + * \param rdata RDATA where the item is located. + * \param zone Zone to which the RDATA belongs. + * \param pos Position of the RDATA item in the RDATA. + */ +static void knot_zone_contents_adjust_rdata_item(knot_rdata_t *rdata, + knot_zone_contents_t *zone, + knot_node_t *node, + int pos) +{ + return; + const knot_rdata_item_t *dname_item + = knot_rdata_item(rdata, pos); + + assert(dname_item); + + if (dname_item != NULL) { + knot_dname_t *dname = dname_item->dname; + const knot_node_t *n = NULL; + const knot_node_t *closest_encloser = NULL; + const knot_node_t *prev = NULL; + + if (knot_dname_is_wildcard(dname)) { + find_and_set_wildcard_child(zone, node); + } + + int ret = knot_zone_contents_find_dname(zone, dname, &n, + &closest_encloser, &prev); + + // n = knot_zone_find_node(zone, dname); + + if (ret == KNOT_EBADARG || ret == KNOT_EBADZONE) { + // TODO: do some cleanup if needed + return; + } + + assert(ret != KNOT_ZONE_NAME_FOUND + || n == closest_encloser); + + if (ret != KNOT_ZONE_NAME_FOUND + && (closest_encloser != NULL)) { + dbg_zone("Saving closest encloser to RDATA.\n"); + // save pointer to the closest encloser + knot_rdata_item_t *item = + knot_rdata_get_item(rdata, pos); + assert(item->dname != NULL); + assert(item->dname->node == NULL); + //skip_insert(list, (void *)item->dname, + // (void *)closest_encloser->owner, NULL); + item->dname->node = closest_encloser->owner->node; + } + } +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adjusts all RDATA in the given RRSet by replacing domain names by ones + * present in the zone. + * + * This function selects the RDATA items containing a domain name (according to + * RR type descriptor of the RRSet's type and adjusts the item using + * knot_zone_adjust_rdata_item(). + * + * \param rrset RRSet to adjust RDATA in. + * \param zone Zone to which the RRSet belongs. + */ +static void knot_zone_contents_adjust_rdata_in_rrset(knot_rrset_t *rrset, + knot_zone_contents_t *zone, + knot_node_t *node) +{ + uint16_t type = knot_rrset_type(rrset); + + knot_rrtype_descriptor_t *desc = + knot_rrtype_descriptor_by_type(type); + assert(desc); + + knot_rdata_t *rdata_first = knot_rrset_get_rdata(rrset); + knot_rdata_t *rdata = rdata_first; + + if (rdata == NULL) { + return; + } + + while (rdata->next != rdata_first) { + for (int i = 0; i < rdata->count; ++i) { + if (desc->wireformat[i] + == KNOT_RDATA_WF_COMPRESSED_DNAME + || desc->wireformat[i] + == KNOT_RDATA_WF_UNCOMPRESSED_DNAME + || desc->wireformat[i] + == KNOT_RDATA_WF_LITERAL_DNAME) { + dbg_zone("Adjusting domain name at " + "position %d of RDATA of record with owner " + "%s and type %s.\n", + i, rrset->owner->name, + knot_rrtype_to_string(type)); + + knot_zone_contents_adjust_rdata_item(rdata, + zone, + node, + i); + } + } + rdata = rdata->next; + } + + for (int i = 0; i < rdata->count; ++i) { + if (desc->wireformat[i] + == KNOT_RDATA_WF_COMPRESSED_DNAME + || desc->wireformat[i] + == KNOT_RDATA_WF_UNCOMPRESSED_DNAME + || desc->wireformat[i] + == KNOT_RDATA_WF_LITERAL_DNAME) { + dbg_zone("Adjusting domain name at " + "position %d of RDATA of record with owner " + "%s and type %s.\n", + i, rrset->owner->name, + knot_rrtype_to_string(type)); + + knot_zone_contents_adjust_rdata_item(rdata, zone, + node, i); + } + } + +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adjusts all RRSets in the given node by replacing domain names in + * RDATA by ones present in the zone. + * + * This function just calls knot_zone_adjust_rdata_in_rrset() for all RRSets + * in the node (including all RRSIG RRSets). + * + * \param node Zone node to adjust the RRSets in. + * \param zone Zone to which the node belongs. + */ +static void knot_zone_contents_adjust_rrsets(knot_node_t *node, + knot_zone_contents_t *zone) +{ + //return; + knot_rrset_t **rrsets = knot_node_get_rrsets(node); + short count = knot_node_rrset_count(node); + + assert(count == 0 || rrsets != NULL); + + for (int r = 0; r < count; ++r) { + assert(rrsets[r] != NULL); + dbg_zone("Adjusting next RRSet.\n"); + knot_zone_contents_adjust_rdata_in_rrset(rrsets[r], zone, + node); + knot_rrset_t *rrsigs = rrsets[r]->rrsigs; + if (rrsigs != NULL) { + dbg_zone("Adjusting next RRSIGs.\n"); + knot_zone_contents_adjust_rdata_in_rrset(rrsigs, + zone, + node); + } + } + + free(rrsets); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adjusts zone node for faster query processing. + * + * - Adjusts RRSets in the node (see knot_zone_adjust_rrsets()). + * - Marks the node as delegation point or non-authoritative (below a zone cut) + * if applicable. + * - Stores reference to corresponding NSEC3 node if applicable. + * + * \param node Zone node to adjust. + * \param zone Zone the node belongs to. + */ +static void knot_zone_contents_adjust_node(knot_node_t *node, + knot_zone_contents_t *zone, + int check_ver) +{ + +dbg_zone_exec( + char *name = knot_dname_to_str(node->owner); + dbg_zone("----- Adjusting node %s -----\n", name); + free(name); +); + + // adjust domain names in RDATA + knot_zone_contents_adjust_rrsets(node, zone); + +dbg_zone_exec( + if (knot_node_parent(node, 1)) { + char *name = knot_dname_to_str(knot_node_owner( + knot_node_parent(node, check_ver))); + dbg_zone("Parent: %s\n", name); + dbg_zone("Parent is delegation point: %s\n", + knot_node_is_deleg_point(knot_node_parent(node, check_ver)) + ? "yes" : "no"); + dbg_zone("Parent is non-authoritative: %s\n", + knot_node_is_non_auth(knot_node_parent(node, check_ver)) + ? "yes" : "no"); + free(name); + } else { + dbg_zone("No parent!\n"); + } +); + // delegation point / non-authoritative node + if (knot_node_parent(node, check_ver) + && (knot_node_is_deleg_point(knot_node_parent(node, check_ver)) + || knot_node_is_non_auth(knot_node_parent(node, check_ver)))) { + knot_node_set_non_auth(node); + } else if (knot_node_rrset(node, KNOT_RRTYPE_NS) != NULL + && node != zone->apex) { + knot_node_set_deleg_point(node); + } + + // authorative node? +// if (!knot_node_is_non_auth(node)) { + zone->node_count++; +// } + + // assure that owner has proper node + if (knot_dname_node(knot_node_owner(node), 0) == NULL) { + knot_dname_set_node(knot_node_get_owner(node), node); + knot_dname_set_node(knot_node_get_owner(node), node); + } + + // NSEC3 node (only if NSEC3 tree is not empty) + const knot_node_t *prev; + const knot_node_t *nsec3; + int match = knot_zone_contents_find_nsec3_for_name(zone, + knot_node_owner(node), + &nsec3, &prev, check_ver); + if (match != KNOT_ZONE_NAME_FOUND) { + nsec3 = NULL; + } + + knot_node_set_nsec3_node(node, (knot_node_t *)nsec3); + + dbg_zone("Set flags to the node: \n"); + dbg_zone("Delegation point: %s\n", + knot_node_is_deleg_point(node) ? "yes" : "no"); + dbg_zone("Non-authoritative: %s\n", + knot_node_is_non_auth(node) ? "yes" : "no"); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adjusts zone node for faster query processing. + * + * This function is just a wrapper over knot_zone_adjust_node() to be used + * in tree-traversing functions. + * + * \param node Zone node to adjust. + * \param data Zone the node belongs to. + */ +static void knot_zone_contents_adjust_node_in_tree( + knot_zone_tree_node_t *tnode, void *data) +{ + assert(data != NULL); + assert(tnode != NULL); + assert(tnode->node != NULL); + + knot_zone_adjust_arg_t *args = (knot_zone_adjust_arg_t *)data; + knot_node_t *node = tnode->node; + knot_node_set_previous(node, args->previous_node); + args->previous_node = node; + if (args->first_node == NULL) { + args->first_node = node; + } + knot_zone_contents_t *zone = args->zone; + + knot_zone_contents_adjust_node(node, zone, args->check_ver); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Adjusts NSEC3 node for faster query processing. + * + * This function is just a wrapper over knot_zone_adjust_nsec3_node() to be + * used in tree-traversing functions. + * + * \param node Zone node to adjust. + * \param data Zone the node belongs to. + */ +static void knot_zone_contents_adjust_nsec3_node_in_tree( + knot_zone_tree_node_t *tnode, void *data) +{ + assert(data != NULL); + assert(tnode != NULL); + assert(tnode->node != NULL); + + knot_zone_adjust_arg_t *args = (knot_zone_adjust_arg_t *)data; + knot_node_t *node = tnode->node; + knot_node_set_previous(node, args->previous_node); + args->previous_node = node; + if (args->first_node == NULL) { + args->first_node = node; + } + + /* Not needed anymore. */ +// knot_zone_contents_t *zone = args->zone; +// knot_zone_contents_adjust_nsec3_node(node, zone, 1); +} + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Creates a NSEC3 hashed name for the given domain name. + * + * \note The zone's NSEC3PARAM record must be parsed prior to calling this + * function (see knot_zone_load_nsec3param()). + * + * \param zone Zone from which to take the NSEC3 parameters. + * \param name Domain name to hash. + * \param nsec3_name Hashed name. + * + * \retval KNOT_EOK + * \retval KNOT_ENSEC3PAR + * \retval KNOT_ECRYPTO + * \retval KNOT_ERROR if an error occured while creating a new domain name + * from the hash or concatenating it with the zone name. + */ +static int knot_zone_contents_nsec3_name(const knot_zone_contents_t *zone, + const knot_dname_t *name, + knot_dname_t **nsec3_name) +{ + assert(nsec3_name != NULL); + + *nsec3_name = NULL; + + const knot_nsec3_params_t *nsec3_params = + knot_zone_contents_nsec3params(zone); + + if (nsec3_params == NULL) { +dbg_zone_exec( + char *n = knot_dname_to_str(zone->apex->owner); + dbg_zone("No NSEC3PARAM for zone %s.\n", n); + free(n); +); + return KNOT_ENSEC3PAR; + } + + uint8_t *hashed_name = NULL; + size_t hash_size = 0; + +dbg_zone_exec( + char *n = knot_dname_to_str(name); + dbg_zone("Hashing name %s.\n", n); + free(n); +); + + int res = knot_nsec3_sha1(nsec3_params, knot_dname_name(name), + knot_dname_size(name), &hashed_name, + &hash_size); + + if (res != 0) { + char *n = knot_dname_to_str(name); + dbg_zone("Error while hashing name %s.\n", n); + free(n); + return KNOT_ECRYPTO; + } + + dbg_zone("Hash: "); + dbg_zone_hex((char *)hashed_name, hash_size); + dbg_zone("\n"); + + char *name_b32 = NULL; + size_t size = base32hex_encode_alloc((char *)hashed_name, hash_size, + &name_b32); + + if (size == 0) { + char *n = knot_dname_to_str(name); + dbg_zone("Error while encoding hashed name %s to " + "base32.\n", n); + free(n); + if (name_b32 != NULL) { + free(name_b32); + } + return KNOT_ECRYPTO; + } + + assert(name_b32 != NULL); + free(hashed_name); + + dbg_zone("Base32-encoded hash: %s\n", name_b32); + + /* Will be returned to caller, make sure it is released after use. */ + *nsec3_name = knot_dname_new_from_str(name_b32, size, NULL); + + free(name_b32); + + if (*nsec3_name == NULL) { + dbg_zone("Error while creating domain name for hashed" + " name.\n"); + return KNOT_ERROR; + } + + assert(zone->apex->owner != NULL); + knot_dname_t *ret = knot_dname_cat(*nsec3_name, zone->apex->owner); + + if (ret == NULL) { + dbg_zone("Error while creating NSEC3 domain name for " + "hashed name.\n"); + knot_dname_release(*nsec3_name); + return KNOT_ERROR; + } + + assert(ret == *nsec3_name); + + 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 <> 0 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 0 if the domain name was not found. \a node may hold any (or none) + * node. \a previous is set properly. + */ +static int knot_zone_contents_find_in_tree(knot_zone_tree_t *tree, + const knot_dname_t *name, + knot_node_t **node, + knot_node_t **previous) +{ + assert(tree != NULL); + assert(name != NULL); + assert(node != NULL); + assert(previous != NULL); + + knot_node_t *found = NULL, *prev = NULL; +// knot_node_t *found2 = NULL, *prev2 = NULL; + + int exact_match = knot_zone_tree_get_less_or_equal( + tree, name, &found, &prev, 1); + +// assert(prev != NULL); + assert(exact_match >= 0); + *node = found; + *previous = prev; + +// 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(exact_match); +// assert(found != NULL); +// *previous = knot_node_get_previous(found, 1); +// } else { +// // otherwise check if the previous node is not an empty +// // non-terminal +// *previous = (knot_node_rrset_count(prev) == 0) +// ? knot_node_get_previous(prev, 1) +// : prev; +// } + + return exact_match; +} + +/*----------------------------------------------------------------------------*/ + +static void knot_zone_contents_node_to_hash(knot_zone_tree_node_t *tnode, + void *data) +{ + assert(tnode != NULL && tnode->node != NULL + && tnode->node->owner != NULL && data != NULL); + + knot_node_t *node = tnode->node; + + knot_zone_contents_t *zone = (knot_zone_contents_t *)data; + /* + * By the original approach, only authoritative nodes and delegation + * points should be added to the hash table, but currently, all nodes + * are being added when the zone is created (don't know why actually:), + * so we will do no distinction here neither. + */ + +#ifdef USE_HASH_TABLE +//dbg_zone_exec( +// char *name = knot_dname_to_str(node->owner); +// dbg_zone("Adding node with owner %s to hash table.\n", name); +// free(name); +//); + //assert(zone->table != NULL); + // add the node also to the hash table if authoritative, or deleg. point + if (zone->table != NULL + && ck_insert_item(zone->table, + (const char *)node->owner->name, + node->owner->size, (void *)node) != 0) { + dbg_zone("Error inserting node into hash table!\n"); + } +#endif +} + +/*----------------------------------------------------------------------------*/ + +static int knot_zone_contents_dnames_from_rdata_to_table( + knot_dname_table_t *table, knot_rdata_t *rdata, + knot_rrtype_descriptor_t *d) +{ + unsigned int count = knot_rdata_item_count(rdata); + int rc = 0; + assert(count <= d->length); + // for each RDATA item + for (unsigned int j = 0; j < count; ++j) { + if (d->wireformat[j] + == KNOT_RDATA_WF_COMPRESSED_DNAME + || d->wireformat[j] + == KNOT_RDATA_WF_UNCOMPRESSED_DNAME + || d->wireformat[j] + == KNOT_RDATA_WF_LITERAL_DNAME) { + dbg_zone("Saving dname from " + "rdata to dname table" + ".\n"); + rc = knot_dname_table_add_dname_check(table, + &knot_rdata_get_item(rdata, j)->dname); + if (rc < 0) { + dbg_zone("Error: %s\n", + knot_strerror(rc)); + return rc; + } + } + } + + dbg_zone("RDATA OK.\n"); + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_zone_contents_dnames_from_rrset_to_table( + knot_dname_table_t *table, knot_rrset_t *rrset, int replace_owner, + knot_dname_t *owner) +{ + assert(table != NULL && rrset != NULL && owner != NULL); + + if (replace_owner) { + // discard the old owner and replace it with the new + knot_rrset_set_owner(rrset, owner); + } + dbg_zone("RRSet owner: %p\n", rrset->owner); + + knot_rrtype_descriptor_t *desc = knot_rrtype_descriptor_by_type( + knot_rrset_type(rrset)); + if (desc == NULL) { + // not recognized RR type, ignore + dbg_zone("RRSet type not recognized.\n"); + return KNOT_EOK; + } + // for each RDATA in RRSet + knot_rdata_t *rdata = knot_rrset_get_rdata(rrset); + while (rdata != NULL) { + int rc = knot_zone_contents_dnames_from_rdata_to_table(table, + rdata, desc); + if (rc != KNOT_EOK) { + return rc; + } + + rdata = knot_rrset_rdata_get_next(rrset, rdata); + } + + dbg_zone("RRSet OK.\n"); + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static int knot_zone_contents_dnames_from_node_to_table( + knot_dname_table_t *table, knot_node_t *node) +{ + /* + * Assuming that all the RRSets have the same owner as the node. + */ + + // insert owner + char *name = knot_dname_to_str(node->owner); + dbg_zone("Node owner before inserting to dname table: %p.\n", + node->owner); + dbg_zone("Node owner before inserting to dname table: %s.\n", + name); + free(name); + //knot_dname_t *old_owner = node->owner; + int rc = knot_dname_table_add_dname_check(table, &node->owner); + if (rc < 0) { + dbg_zone("Failed to add dname to dname table.\n"); + return rc; + } + int replace_owner = (rc > 0); + dbg_zone("Node owner after inserting to dname table: %p.\n", + node->owner); + name = knot_dname_to_str(node->owner); + dbg_zone("Node owner after inserting to dname table: %s.\n", + name); + free(name); + + knot_rrset_t **rrsets = knot_node_get_rrsets(node); + // for each RRSet + for (int i = 0; i < knot_node_rrset_count(node); ++i) { + dbg_zone("Inserting RRSets from node to table.\n"); + rc = knot_zone_contents_dnames_from_rrset_to_table(table, + rrsets[i], replace_owner, node->owner); + if (rc != KNOT_EOK) { + return rc; + } + } + + free(rrsets); + + dbg_zone("Node OK\n"); + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +knot_zone_contents_t *knot_zone_contents_new(knot_node_t *apex, + uint node_count, + int use_domain_table, + struct knot_zone *zone) +{ + knot_zone_contents_t *contents = (knot_zone_contents_t *) + calloc(1, sizeof(knot_zone_contents_t)); + if (contents == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + +// printf("created cont: %p (%s)\n", +// contents, knot_dname_to_str(apex->owner)); + + contents->apex = apex; + contents->zone = zone; + knot_node_set_zone(apex, zone); + + dbg_zone("Creating tree for normal nodes.\n"); + contents->nodes = malloc(sizeof(knot_zone_tree_t)); + if (contents->nodes == NULL) { + ERR_ALLOC_FAILED; + goto cleanup; + } + + dbg_zone("Creating tree for NSEC3 nodes.\n"); + contents->nsec3_nodes = malloc(sizeof(knot_zone_tree_t)); + if (contents->nsec3_nodes == NULL) { + ERR_ALLOC_FAILED; + goto cleanup; + } + + if (use_domain_table) { + dbg_zone("Creating domain name table.\n"); + contents->dname_table = knot_dname_table_new(); + if (contents->dname_table == NULL) { + ERR_ALLOC_FAILED; + goto cleanup; + } + } else { + contents->dname_table = NULL; + } + + contents->node_count = node_count; + + /* Initialize NSEC3 params */ + dbg_zone("Initializing NSEC3 parameters.\n"); + contents->nsec3_params.algorithm = 0; + contents->nsec3_params.flags = 0; + contents->nsec3_params.iterations = 0; + contents->nsec3_params.salt_length = 0; + contents->nsec3_params.salt = NULL; + + dbg_zone("Initializing zone trees.\n"); + if (knot_zone_tree_init(contents->nodes) != KNOT_EOK + || knot_zone_tree_init(contents->nsec3_nodes) != KNOT_EOK) { + goto cleanup; + } + + dbg_zone("Inserting apex into the zone tree.\n"); + if (knot_zone_tree_insert(contents->nodes, apex) != KNOT_EOK) { + dbg_zone("Failed to insert apex to the zone tree.\n"); + goto cleanup; + } + +#ifdef USE_HASH_TABLE + if (contents->node_count > 0) { + dbg_zone("Creating hash table.\n"); + contents->table = ck_create_table(contents->node_count); + if (contents->table == NULL) { + goto cleanup; + } + + // insert the apex into the hash table + dbg_zone("Inserting apex into the hash table.\n"); + if (ck_insert_item(contents->table, + (const char *)knot_dname_name( + knot_node_owner(apex)), + knot_dname_size(knot_node_owner(apex)), + (void *)apex) != 0) { + ck_destroy_table(&contents->table, NULL, 0); + goto cleanup; + } + } else { + contents->table = NULL; + } +#endif + + // insert names from the apex to the domain table + if (use_domain_table) { + dbg_zone("Inserting names from apex to table.\n"); + int rc = knot_zone_contents_dnames_from_node_to_table( + contents->dname_table, apex); + if (rc != KNOT_EOK) { + ck_destroy_table(&contents->table, NULL, 0); + goto cleanup; + } + } + + return contents; + +cleanup: + dbg_zone("Cleaning up.\n"); + free(contents->dname_table); + free(contents->nodes); + free(contents->nsec3_nodes); + free(contents); + return NULL; +} + +/*----------------------------------------------------------------------------*/ + +//short knot_zone_contents_generation(const knot_zone_contents_t *zone) +//{ +// return zone->generation; +//} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_gen_is_old(const knot_zone_contents_t *contents) +{ + return (contents->generation == 0); +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_gen_is_new(const knot_zone_contents_t *contents) +{ + return (contents->generation == 1); +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_gen_is_finished(const knot_zone_contents_t *contents) +{ + return (contents->generation == -1); +} + +/*----------------------------------------------------------------------------*/ + +//void knot_zone_contents_switch_generation(knot_zone_contents_t *zone) +//{ +// zone->generation = 1 - zone->generation; +//} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_contents_set_gen_old(knot_zone_contents_t *contents) +{ + contents->generation = 0; +} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_contents_set_gen_new(knot_zone_contents_t *contents) +{ + contents->generation = 1; +} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_contents_set_gen_new_finished(knot_zone_contents_t *contents) +{ + contents->generation = -1; +} + +/*----------------------------------------------------------------------------*/ + +uint16_t knot_zone_contents_class(const knot_zone_contents_t *contents) +{ + if (contents == NULL || contents->apex == NULL + || knot_node_rrset(contents->apex, KNOT_RRTYPE_SOA) == NULL) { + return KNOT_EBADARG; + } + + return knot_rrset_class(knot_node_rrset(contents->apex, + KNOT_RRTYPE_SOA)); +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_add_node(knot_zone_contents_t *zone, + knot_node_t *node, int create_parents, + uint8_t flags, int use_domain_table) +{ + if (zone == NULL || node == NULL) { + return KNOT_EBADARG; + } + + int ret = 0; + if ((ret = knot_zone_contents_check_node(zone, node)) != 0) { + return ret; + } + + ret = knot_zone_tree_insert(zone->nodes, node); + if (ret != KNOT_EOK) { + dbg_zone("Failed to insert node into zone tree.\n"); + return ret; + } + +#ifdef USE_HASH_TABLE + char *name = knot_dname_to_str(node->owner); +// dbg_zone("Adding node with owner %s to hash table.\n", name); + free(name); + //assert(zone->table != NULL); + // add the node also to the hash table if authoritative, or deleg. point + if (zone->table != NULL + && ck_insert_item(zone->table, + (const char *)node->owner->name, + node->owner->size, (void *)node) != 0) { + dbg_zone("Error inserting node into hash table!\n"); + /*! \todo Remove the node from the tree. */ + return KNOT_EHASH; + } +#endif + assert(knot_zone_contents_find_node(zone, node->owner)); + + if (use_domain_table) { + ret = knot_zone_contents_dnames_from_node_to_table( + zone->dname_table, node); + if (ret != KNOT_EOK) { + /*! \todo Remove node from the tree and hash table.*/ + dbg_zone("Failed to add dnames into table.\n"); + return ret; + } + } + + knot_node_set_zone(node, zone->zone); + + if (!create_parents) { + return KNOT_EOK; + } + + dbg_zone("Creating parents of the node.\n"); + + knot_dname_t *chopped = + knot_dname_left_chop(knot_node_owner(node)); + if (knot_dname_compare(knot_node_owner(zone->apex), chopped) == 0) { + dbg_zone("Zone apex is the parent.\n"); + knot_node_set_parent(node, zone->apex); + } else { + knot_node_t *next_node; + while ((next_node + = knot_zone_contents_get_node(zone, chopped)) == NULL) { + /* Adding new dname to zone + add to table. */ + dbg_zone("Creating new node.\n"); + next_node = knot_node_new(chopped, NULL, flags); + if (next_node == NULL) { + /* Directly discard. */ + knot_dname_free(&chopped); + return KNOT_ENOMEM; + } + if (use_domain_table) { + ret = + knot_zone_contents_dnames_from_node_to_table( + zone->dname_table, next_node); + if (ret != KNOT_EOK) { + /*! \todo Will next_node leak? */ + knot_dname_release(chopped); + return ret; + } + } + + if (next_node->owner != chopped) { + /* Node owner was in RDATA */ + chopped = next_node->owner; + } + + assert(knot_zone_contents_find_node(zone, chopped) + == NULL); + assert(knot_node_owner(next_node) == chopped); + + dbg_zone("Inserting new node to zone tree.\n"); +// TREE_INSERT(zone->tree, knot_node, avl, next_node); + + ret = knot_zone_tree_insert(zone->nodes, + next_node); + if (ret != KNOT_EOK) { + dbg_zone("Failed to insert new node " + "to zone tree.\n"); + /*! \todo Delete the node?? */ + /* Directly discard. */ + knot_dname_release(chopped); + return ret; + } + +#ifdef USE_HASH_TABLE +dbg_zone_exec( + char *name = knot_dname_to_str( + knot_node_owner(next_node)); + dbg_zone("Adding new node with owner %s to " + "hash table.\n", name); + free(name); +); + + if (zone->table != NULL + && ck_insert_item(zone->table, + (const char *)knot_dname_name( + knot_node_owner(next_node)), + knot_dname_size(knot_node_owner(next_node)), + (void *)next_node) != 0) { + dbg_zone("Error inserting node into " + "hash table!\n"); + /*! \todo Delete the node?? */ + /* Directly discard. */ + knot_dname_release(chopped); + return KNOT_EHASH; + } + + // set parent + knot_node_set_parent(node, next_node); + + // set zone + knot_node_set_zone(next_node, zone->zone); + + // check if the node is not wildcard child of the parent + if (knot_dname_is_wildcard( + knot_node_owner(node))) { + knot_node_set_wildcard_child(next_node, node); + } +#endif + dbg_zone("Next parent.\n"); + node = next_node; + knot_dname_t *chopped_last = chopped; + chopped = knot_dname_left_chop(chopped); + + /* Release last chop, reference is already stored + * in next_node. + */ + knot_dname_release(chopped_last); + + } + // set the found parent (in the zone) as the parent of the last + // inserted node + assert(knot_node_parent(node, 0) == NULL); + knot_node_set_parent(node, next_node); + + dbg_zone("Created all parents.\n"); + } + + /* Directly discard. */ + /*! \todo This may be double-release. */ + knot_dname_release(chopped); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_add_rrset(knot_zone_contents_t *zone, + knot_rrset_t *rrset, knot_node_t **node, + knot_rrset_dupl_handling_t dupl, + int use_domain_table) +{ + if (zone == NULL || rrset == NULL || zone->apex == NULL + || zone->apex->owner == NULL || node == NULL) { + return KNOT_EBADARG; + } + + // check if the RRSet belongs to the zone + if (knot_dname_compare(knot_rrset_owner(rrset), + zone->apex->owner) != 0 + && !knot_dname_is_subdomain(knot_rrset_owner(rrset), + zone->apex->owner)) { + return KNOT_EBADZONE; + } + + if ((*node) == NULL + && (*node = knot_zone_contents_get_node(zone, + knot_rrset_owner(rrset))) == NULL) { + return KNOT_ENONODE; + } + + assert(*node != NULL); + + // add all domain names from the RRSet to domain name table + int rc; + + /*! \todo REMOVE RRSET */ + rc = knot_node_add_rrset(*node, rrset, + dupl == KNOT_RRSET_DUPL_MERGE); + if (rc < 0) { + dbg_zone("Failed to add RRSet to node.\n"); + return rc; + } + + int ret = rc; + + if (use_domain_table) { + dbg_zone("Saving RRSet to table.\n"); + rc = knot_zone_contents_dnames_from_rrset_to_table( + zone->dname_table, rrset, 0, (*node)->owner); + if (rc != KNOT_EOK) { + dbg_zone("Error saving domain names from " + "RRSIGs to the domain name table.\n " + "The zone may be in an inconsistent " + "state.\n"); + // WARNING: the zone is not in consistent state now - + // there may be domain names in it that are not inserted + // into the domain table + return rc; + } + } + + // replace RRSet's owner with the node's owner (that is already in the + // table) + /*! \todo Do even if domain table is not used?? */ + if (ret == KNOT_EOK && rrset->owner != (*node)->owner) { + knot_rrset_set_owner(rrset, (*node)->owner); + } + + dbg_zone("RRSet OK.\n"); + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_add_rrsigs(knot_zone_contents_t *zone, + knot_rrset_t *rrsigs, + knot_rrset_t **rrset, + knot_node_t **node, + knot_rrset_dupl_handling_t dupl, + int use_domain_table) +{ + if (zone == NULL || rrsigs == NULL || rrset == NULL || node == NULL + || zone->apex == NULL || zone->apex->owner == NULL) { +dbg_zone_exec( + dbg_zone("Parameters: zone=%p, rrsigs=%p, rrset=%p, " + "node=%p\n", zone, rrsigs, rrset, node); + if (zone != NULL) { + dbg_zone("zone->apex=%p\n", zone->apex); + if (zone->apex != NULL) { + dbg_zone("zone->apex->owner=%p\n", + zone->apex->owner); + } + } +); + return KNOT_EBADARG; + } + + // check if the RRSet belongs to the zone + if (*rrset != NULL + && knot_dname_compare(knot_rrset_owner(*rrset), + zone->apex->owner) != 0 + && !knot_dname_is_subdomain(knot_rrset_owner(*rrset), + zone->apex->owner)) { + return KNOT_EBADZONE; + } + + // check if the RRSIGs belong to the RRSet + if (*rrset != NULL + && (knot_dname_compare(knot_rrset_owner(rrsigs), + knot_rrset_owner(*rrset)) != 0)) { + dbg_zone("RRSIGs does not belong to the given RRSet.\n"); + return KNOT_EBADARG; + } + + // if no RRSet given, try to find the right RRSet + if (*rrset == NULL) { + // even no node given + // find proper node + knot_node_t *(*get_node)(const knot_zone_contents_t *, + const knot_dname_t *) + = (knot_rdata_rrsig_type_covered( + knot_rrset_rdata(rrsigs)) == KNOT_RRTYPE_NSEC3) + ? knot_zone_contents_get_nsec3_node + : knot_zone_contents_get_node; + + if (*node == NULL + && (*node = get_node( + zone, knot_rrset_owner(rrsigs))) == NULL) { + dbg_zone("Failed to find node for RRSIGs.\n"); + return KNOT_ENONODE; + } + + assert(*node != NULL); + + // find the RRSet in the node + // take only the first RDATA from the RRSIGs + dbg_zone("Finding RRSet for type %s\n", + knot_rrtype_to_string( + knot_rdata_rrsig_type_covered( + knot_rrset_rdata(rrsigs)))); + *rrset = knot_node_get_rrset( + *node, knot_rdata_rrsig_type_covered( + knot_rrset_rdata(rrsigs))); + if (*rrset == NULL) { + dbg_zone("Failed to find RRSet for RRSIGs.\n"); + return KNOT_ENORRSET; + } + } + + assert(*rrset != NULL); + + // add all domain names from the RRSet to domain name table + int rc; + int ret = KNOT_EOK; + + rc = knot_rrset_add_rrsigs(*rrset, rrsigs, dupl); + if (rc < 0) { + dbg_dname("Failed to add RRSIGs to RRSet.\n"); + return rc; + } else if (rc > 0) { + assert(dupl == KNOT_RRSET_DUPL_MERGE); + ret = 1; + } + + if (use_domain_table) { + dbg_zone("Saving RRSIG RRSet to table.\n"); + rc = knot_zone_contents_dnames_from_rrset_to_table( + zone->dname_table, rrsigs, 0, (*rrset)->owner); + if (rc != KNOT_EOK) { + dbg_zone("Error saving domain names from " + "RRSIGs to the domain name table.\n " + "The zone may be in an inconsistent " + "state.\n"); + // WARNING: the zone is not in consistent state now - + // there may be domain names in it that are not inserted + // into the domain table + return rc; + } + } + + // replace RRSet's owner with the node's owner (that is already in the + // table) + if ((*rrset)->owner != (*rrset)->rrsigs->owner) { + knot_rrset_set_owner((*rrset)->rrsigs, (*rrset)->owner); + } + + dbg_zone("RRSIGs OK\n"); + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_add_nsec3_node(knot_zone_contents_t *zone, + knot_node_t *node, int create_parents, + uint8_t flags, int use_domain_table) +{ + UNUSED(create_parents); + UNUSED(flags); + + if (zone == NULL || node == NULL) { + return KNOT_EBADARG; + } + + int ret = 0; + if ((ret = knot_zone_contents_check_node(zone, node)) != 0) { + return ret; + } + + // how to know if this is successfull?? +// TREE_INSERT(zone->nsec3_nodes, knot_node, avl, node); + knot_zone_tree_insert(zone->nsec3_nodes, node); + + if (use_domain_table) { + ret = knot_zone_contents_dnames_from_node_to_table( + zone->dname_table, node); + if (ret != KNOT_EOK) { + /*! \todo Remove the node from the tree. */ + dbg_zone("Failed to add dnames into table.\n"); + return ret; + } + } + + // no parents to be created, the only parent is the zone apex + // set the apex as the parent of the node + knot_node_set_parent(node, zone->apex); + + // set the zone to the node + knot_node_set_zone(node, zone->zone); + + // cannot be wildcard child, so nothing to be done + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_add_nsec3_rrset(knot_zone_contents_t *zone, + knot_rrset_t *rrset, + knot_node_t **node, + knot_rrset_dupl_handling_t dupl, + int use_domain_table) +{ + if (zone == NULL || rrset == NULL || zone->apex == NULL + || zone->apex->owner == NULL || node == NULL) { + return KNOT_EBADARG; + } + + // check if the RRSet belongs to the zone + if (knot_dname_compare(knot_rrset_owner(rrset), + zone->apex->owner) != 0 + && !knot_dname_is_subdomain(knot_rrset_owner(rrset), + zone->apex->owner)) { + return KNOT_EBADZONE; + } + + if ((*node) == NULL + && (*node = knot_zone_contents_get_nsec3_node( + zone, knot_rrset_owner(rrset))) == NULL) { + return KNOT_ENONODE; + } + + assert(*node != NULL); + + // add all domain names from the RRSet to domain name table + int rc; + + /*! \todo REMOVE RRSET */ + rc = knot_node_add_rrset(*node, rrset, + dupl == KNOT_RRSET_DUPL_MERGE); + if (rc < 0) { + return rc; + } + + int ret = rc; + + if (use_domain_table) { + dbg_zone("Saving NSEC3 RRSet to table.\n"); + rc = knot_zone_contents_dnames_from_rrset_to_table( + zone->dname_table, rrset, 0, (*node)->owner); + if (rc != KNOT_EOK) { + dbg_zone("Error saving domain names from " + "RRSIGs to the domain name table.\n " + "The zone may be in an inconsistent " + "state.\n"); + // WARNING: the zone is not in consistent state now - + // there may be domain names in it that are not inserted + // into the domain table + return rc; + } + } + + // replace RRSet's owner with the node's owner (that is already in the + // table) + /*! \todo Do even if domain table is not used? */ + if (rrset->owner != (*node)->owner) { + knot_rrset_set_owner(rrset, (*node)->owner); + } + + dbg_zone("NSEC3 OK\n"); + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_remove_node(knot_zone_contents_t *contents, + const knot_node_t *node, knot_zone_tree_node_t **removed_tree, + ck_hash_table_item_t **removed_hash) +{ + if (contents == NULL || node == NULL) { + return KNOT_EBADARG; + } + + const knot_dname_t *owner = knot_node_owner(node); + + // 1) remove the node from hash table + *removed_hash = NULL; + *removed_hash = ck_remove_item(contents->table, + (const char *)knot_dname_name(owner), + knot_dname_size(owner)); +// int ret = ck_detete_item(contents->table, +// (const char *)knot_dname_name(owner), +// knot_dname_size(owner), NULL, 0); + if (*removed_hash == NULL) { + return KNOT_ENONODE; + } + + // 2) remove the node from the zone tree + *removed_tree = NULL; + int ret = knot_zone_tree_remove(contents->nodes, owner, removed_tree); + if (ret != KNOT_EOK) { + return KNOT_ENONODE; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_remove_nsec3_node(knot_zone_contents_t *contents, + const knot_node_t *node, knot_zone_tree_node_t **removed) +{ + if (contents == NULL || node == NULL) { + return KNOT_EBADARG; + } + + const knot_dname_t *owner = knot_node_owner(node); + + // remove the node from the zone tree + *removed = NULL; + int ret = knot_zone_tree_remove(contents->nsec3_nodes, owner, removed); + if (ret != KNOT_EOK) { + return KNOT_ENONODE; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_create_and_fill_hash_table( + knot_zone_contents_t *zone) +{ + if (zone == NULL || zone->apex == NULL || zone->apex->owner == NULL) { + return KNOT_EBADARG; + } + /* + * 1) Create hash table. + */ +#ifdef USE_HASH_TABLE + if (zone->node_count > 0) { + zone->table = ck_create_table(zone->node_count); + if (zone->table == NULL) { + return KNOT_ENOMEM; + } + + // insert the apex into the hash table + if (ck_insert_item(zone->table, + (const char *)zone->apex->owner->name, + zone->apex->owner->size, + (void *)zone->apex) != 0) { + return KNOT_EHASH; + } + } else { + zone->table = NULL; + return KNOT_EOK; // OK? + } + + /* + * 2) Fill in the hash table. + * + * In this point, the nodes in the zone must be adjusted, so that only + * relevant nodes (authoritative and delegation points are inserted. + * + * TODO: how to know if this was successful?? + */ + /*! \todo Replace by zone tree. */ + int ret = knot_zone_tree_forward_apply_inorder(zone->nodes, + knot_zone_contents_node_to_hash, zone); + if (ret != KNOT_EOK) { + dbg_zone("Failed to insert nodes to hash table.\n"); + return ret; + } + +#endif + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +knot_node_t *knot_zone_contents_get_node(const knot_zone_contents_t *zone, + const knot_dname_t *name) +{ + if (zone == NULL || name == NULL) { + return NULL; + } + + // create dummy node to use for lookup +// knot_node_t *tmp = knot_node_new((knot_dname_t *)name, NULL); +// knot_node_t *n = TREE_FIND(zone->tree, knot_node, avl, tmp); +// knot_node_free(&tmp, 0); + + knot_node_t *n; + int ret = knot_zone_tree_get(zone->nodes, name, &n); + if (ret != KNOT_EOK) { + dbg_zone("Failed to find name in the zone tree.\n"); + return NULL; + } + + return n; +} + +/*----------------------------------------------------------------------------*/ + +knot_node_t *knot_zone_contents_get_nsec3_node( + const knot_zone_contents_t *zone, const knot_dname_t *name) +{ + if (zone == NULL || name == NULL) { + return NULL; + } + + // create dummy node to use for lookup +// knot_node_t *tmp = knot_node_new((knot_dname_t *)name, NULL); +// knot_node_t *n = TREE_FIND(zone->nsec3_nodes, knot_node, avl, tmp); +// knot_node_free(&tmp, 0); + knot_node_t *n; + int ret = knot_zone_tree_get(zone->nsec3_nodes, name, &n); + + if (ret != KNOT_EOK) { + dbg_zone("Failed to find NSEC3 name in the zone tree." + "\n"); + return NULL; + } + + return n; +} + +/*----------------------------------------------------------------------------*/ + +const knot_node_t *knot_zone_contents_find_node( + const knot_zone_contents_t *zone,const knot_dname_t *name) +{ + return knot_zone_contents_get_node(zone, name); +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_find_dname(const knot_zone_contents_t *zone, + const knot_dname_t *name, + const knot_node_t **node, + const knot_node_t **closest_encloser, + const knot_node_t **previous) +{ + if (zone == NULL || name == NULL || node == NULL + || closest_encloser == NULL || previous == NULL + || zone->apex == NULL || zone->apex->owner == NULL) { + return KNOT_EBADARG; + } + +dbg_zone_exec( + char *name_str = knot_dname_to_str(name); + char *zone_str = knot_dname_to_str(zone->apex->owner); + dbg_zone("Searching for name %s in zone %s...\n", + name_str, zone_str); + free(name_str); + free(zone_str); +); + + if (knot_dname_compare(name, zone->apex->owner) == 0) { + *node = zone->apex; + *closest_encloser = *node; + return KNOT_ZONE_NAME_FOUND; + } + + if (!knot_dname_is_subdomain(name, zone->apex->owner)) { + *node = NULL; + *closest_encloser = NULL; + return KNOT_EBADZONE; + } + + knot_node_t *found = NULL, *prev = NULL; + + int exact_match = knot_zone_contents_find_in_tree(zone->nodes, name, + &found, &prev); + assert(exact_match >= 0); + *node = found; + *previous = prev; + +dbg_zone_exec( + char *name_str = (*node) ? knot_dname_to_str((*node)->owner) + : "(nil)"; + char *name_str2 = (*previous != NULL) + ? knot_dname_to_str((*previous)->owner) + : "(nil)"; + dbg_zone("Search function returned %d, node %s and prev: %s\n", + exact_match, name_str, name_str2); + + if (*node) { + free(name_str); + } + if (*previous != NULL) { + free(name_str2); + } +); + + *closest_encloser = *node; + + // there must be at least one node with domain name less or equal to + // the searched name if the name belongs to the zone (the root) + if (*node == NULL) { + return KNOT_EBADZONE; + } + + // TODO: this could be replaced by saving pointer to closest encloser + // in node + + if (!exact_match) { + int matched_labels = knot_dname_matched_labels( + knot_node_owner((*closest_encloser)), name); + while (matched_labels < knot_dname_label_count( + knot_node_owner((*closest_encloser)))) { + (*closest_encloser) = + knot_node_parent((*closest_encloser), 1); + assert(*closest_encloser); + } + } +dbg_zone_exec( + char *n = knot_dname_to_str(knot_node_owner((*closest_encloser))); + dbg_zone("Closest encloser: %s\n", n); + free(n); +); + + dbg_zone("find_dname() returning %d\n", exact_match); + + return (exact_match) + ? KNOT_ZONE_NAME_FOUND + : KNOT_ZONE_NAME_NOT_FOUND; +} + +/*----------------------------------------------------------------------------*/ + +knot_node_t *knot_zone_contents_get_previous( + const knot_zone_contents_t *zone, const knot_dname_t *name) +{ + if (zone == NULL || name == NULL) { + return NULL; + } + + knot_node_t *found = NULL, *prev = NULL; + + int exact_match = knot_zone_contents_find_in_tree(zone->nodes, name, + &found, &prev); + assert(exact_match >= 0); + assert(prev != NULL); + + return prev; +} + +/*----------------------------------------------------------------------------*/ + +const knot_node_t *knot_zone_contents_find_previous( + const knot_zone_contents_t *zone, const knot_dname_t *name) +{ + return knot_zone_contents_get_previous(zone, name); +} + +/*----------------------------------------------------------------------------*/ + +knot_node_t *knot_zone_contents_get_previous_nsec3( + const knot_zone_contents_t *zone, const knot_dname_t *name) +{ + if (zone == NULL || name == NULL) { + return NULL; + } + + knot_node_t *found = NULL, *prev = NULL; + + int exact_match = knot_zone_contents_find_in_tree(zone->nsec3_nodes, + name, &found, &prev); + assert(exact_match >= 0); + assert(prev != NULL); + + return prev; +} + +/*----------------------------------------------------------------------------*/ + +const knot_node_t *knot_zone_contents_find_previous_nsec3( + const knot_zone_contents_t *zone, const knot_dname_t *name) +{ + return knot_zone_contents_get_previous(zone, name); +} + +/*----------------------------------------------------------------------------*/ + +static void knot_zone_contents_left_chop(char *name, size_t *size) +{ + short label_size = name[0]; + + memmove(name, name + label_size + 1, *size -label_size - 1); + *size = *size - label_size - 1; +} + +/*----------------------------------------------------------------------------*/ +#ifdef USE_HASH_TABLE +int knot_zone_contents_find_dname_hash(const knot_zone_contents_t *zone, + const knot_dname_t *name, + const knot_node_t **node, + const knot_node_t **closest_encloser) +{ + if (zone == NULL || name == NULL || node == NULL + || closest_encloser == NULL) { + return KNOT_EBADARG; + } + +dbg_zone_exec( + char *name_str = knot_dname_to_str(name); + char *zone_str = knot_dname_to_str(zone->apex->owner); + dbg_zone("Searching for name %s in zone %s...\n", + name_str, zone_str); + free(name_str); + free(zone_str); +); + + if (knot_dname_compare(name, zone->apex->owner) == 0) { + *node = zone->apex; + *closest_encloser = *node; + return KNOT_ZONE_NAME_FOUND; + } + + if (!knot_dname_is_subdomain(name, zone->apex->owner)) { + *node = NULL; + *closest_encloser = NULL; + return KNOT_EBADZONE; + } + + // temporary name used for hashing + char name_tmp[KNOT_MAX_DNAME_LENGTH]; + size_t name_size = name->size; + if (knot_dname_to_lower_copy(name, name_tmp, KNOT_MAX_DNAME_LENGTH) + != KNOT_EOK) { + return KNOT_ERROR; + } + + const ck_hash_table_item_t *item = ck_find_item(zone->table, + name_tmp, name_size); + + if (item != NULL) { + *node = (const knot_node_t *)item->value; + *closest_encloser = *node; + + dbg_zone("Found node in hash table: %p (owner %p, " + "labels: %d)\n", *node, (*node)->owner, + knot_dname_label_count((*node)->owner)); + assert(*node != NULL); + assert(*closest_encloser != NULL); + return KNOT_ZONE_NAME_FOUND; + } + + *node = NULL; + + // chop leftmost labels until some node is found + // copy the name for chopping + /* Local allocation, will be discarded. */ + //knot_dname_t *name_copy = knot_dname_deep_copy(name); +dbg_zone_exec( + //char *n = knot_dname_to_str(name_copy); + dbg_zone("Finding closest encloser..\nStarting with: %.*s\n", + (int)name_size, name_tmp); + //free(n); +); + + while (item == NULL) { + //knot_dname_left_chop_no_copy(name_copy); + knot_zone_contents_left_chop(name_tmp, &name_size); +dbg_zone_exec( + //char *n = knot_dname_to_str(name_copy); + dbg_zone("Chopped leftmost label: %.*s\n", + (int)name_size, name_tmp); + //free(n); +); + // not satisfied in root zone!! + //assert(name_copy->label_count > 0); + assert(name_size > 0); + + item = ck_find_item(zone->table, name_tmp, name_size); + } + + /* Directly discard. */ + //knot_dname_free(&name_copy); + + assert(item != NULL); + *closest_encloser = (const knot_node_t *)item->value; + + return KNOT_ZONE_NAME_NOT_FOUND; +} +#endif +/*----------------------------------------------------------------------------*/ + +const knot_node_t *knot_zone_contents_find_nsec3_node( + const knot_zone_contents_t *zone, const knot_dname_t *name) +{ + return knot_zone_contents_get_nsec3_node(zone, name); +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_find_nsec3_for_name(const knot_zone_contents_t *zone, + const knot_dname_t *name, + const knot_node_t **nsec3_node, + const knot_node_t **nsec3_previous, + int check_ver) +{ + if (zone == NULL || name == NULL + || nsec3_node == NULL || nsec3_previous == NULL) { + return KNOT_EBADARG; + } + + knot_dname_t *nsec3_name = NULL; + int ret = knot_zone_contents_nsec3_name(zone, name, &nsec3_name); + + if (ret != KNOT_EOK) { + return ret; + } + +dbg_zone_exec( + char *n = knot_dname_to_str(nsec3_name); + dbg_zone("NSEC3 node name: %s.\n", n); + free(n); +); + + const knot_node_t *found = NULL, *prev = NULL; + + // create dummy node to use for lookup + int exact_match = knot_zone_tree_find_less_or_equal( + zone->nsec3_nodes, nsec3_name, &found, &prev, check_ver); + assert(exact_match >= 0); + + knot_dname_release(nsec3_name); + +dbg_zone_exec( + if (found) { + char *n = knot_dname_to_str(found->owner); + dbg_zone("Found NSEC3 node: %s.\n", n); + free(n); + } else { + dbg_zone("Found no NSEC3 node.\n"); + } + + if (prev) { + assert(prev->owner); + char *n = knot_dname_to_str(prev->owner); + dbg_zone("Found previous NSEC3 node: %s.\n", n); + free(n); + } else { + dbg_zone("Found no previous NSEC3 node.\n"); + } +); + *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(exact_match); + assert(*nsec3_node != NULL); + *nsec3_previous = knot_node_previous(*nsec3_node, check_ver); + } else { + *nsec3_previous = prev; + } + + dbg_zone("find_nsec3_for_name() returning %d\n", exact_match); + + return (exact_match) + ? KNOT_ZONE_NAME_FOUND + : KNOT_ZONE_NAME_NOT_FOUND; +} + +/*----------------------------------------------------------------------------*/ + +const knot_node_t *knot_zone_contents_apex( + const knot_zone_contents_t *zone) +{ + if (zone == NULL) { + return NULL; + } + + return zone->apex; +} + +/*----------------------------------------------------------------------------*/ + +knot_node_t *knot_zone_contents_get_apex(const knot_zone_contents_t *zone) +{ + if (zone == NULL) { + return NULL; + } + + return zone->apex; +} + +/*----------------------------------------------------------------------------*/ + +//knot_dname_t *knot_zone_contents_name(const knot_zone_contents_t *zone) +//{ +// if (zone == NULL) { +// return NULL; +// } + +// return zone->name; +//} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_adjust(knot_zone_contents_t *zone, int check_ver) +{ + if (zone == NULL) { + return KNOT_EBADARG; + } + + // load NSEC3PARAM (needed on adjusting function) + knot_zone_contents_load_nsec3param(zone); + + knot_zone_adjust_arg_t adjust_arg; + adjust_arg.zone = zone; + adjust_arg.first_node = NULL; + adjust_arg.previous_node = NULL; + adjust_arg.check_ver = check_ver; + + dbg_zone("Adjusting normal nodes.\n"); + int ret = knot_zone_tree_forward_apply_inorder(zone->nodes, + knot_zone_contents_adjust_node_in_tree, + &adjust_arg); + if (ret != KNOT_EOK) { + return ret; + } + dbg_zone("Done.\n"); + + assert(zone->apex == adjust_arg.first_node); + knot_node_set_previous(zone->apex, adjust_arg.previous_node); + + adjust_arg.first_node = NULL; + adjust_arg.previous_node = NULL; + + dbg_zone("Adjusting NSEC3 nodes.\n"); + ret = knot_zone_tree_forward_apply_inorder( + zone->nsec3_nodes, + knot_zone_contents_adjust_nsec3_node_in_tree, + &adjust_arg); + + dbg_zone("Done.\n"); + if (adjust_arg.first_node) { + knot_node_set_previous(adjust_arg.first_node, + adjust_arg.previous_node); + } + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_load_nsec3param(knot_zone_contents_t *zone) +{ + if (zone == NULL || zone->apex == NULL) { + return KNOT_EBADARG; + } + + const knot_rrset_t *rrset = knot_node_rrset(zone->apex, + KNOT_RRTYPE_NSEC3PARAM); + + if (rrset != NULL) { + knot_nsec3_params_from_wire(&zone->nsec3_params, rrset); + } else { + memset(&zone->nsec3_params, 0, sizeof(knot_nsec3_params_t)); + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_nsec3_enabled(const knot_zone_contents_t *zone) +{ + if (zone == NULL) { + return KNOT_EBADARG; + } + + //return (zone->nsec3_params.algorithm != 0); + return (zone->nsec3_nodes->th_root != NULL); +} + +/*----------------------------------------------------------------------------*/ + +const knot_nsec3_params_t *knot_zone_contents_nsec3params( + const knot_zone_contents_t *zone) +{ + if (zone == NULL) { + return NULL; + } + + if (knot_zone_contents_nsec3_enabled(zone)) { + return &zone->nsec3_params; + } else { + return NULL; + } +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_tree_apply_postorder(knot_zone_contents_t *zone, + void (*function)(knot_node_t *node, void *data), + void *data) +{ + if (zone == NULL) { + return KNOT_EBADARG; + } + + knot_zone_tree_func_t f; + f.func = function; + f.data = data; + + return knot_zone_tree_forward_apply_postorder(zone->nodes, + knot_zone_tree_apply, &f); +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_tree_apply_inorder(knot_zone_contents_t *zone, + void (*function)(knot_node_t *node, void *data), + void *data) +{ + if (zone == NULL) { + return KNOT_EBADARG; + } + + knot_zone_tree_func_t f; + f.func = function; + f.data = data; + + return knot_zone_tree_forward_apply_inorder(zone->nodes, + knot_zone_tree_apply, &f); +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_tree_apply_inorder_reverse( + knot_zone_contents_t *zone, + void (*function)(knot_node_t *node, void *data), void *data) +{ + if (zone == NULL) { + return KNOT_EBADARG; + } + + knot_zone_tree_func_t f; + f.func = function; + f.data = data; + + return knot_zone_tree_reverse_apply_inorder(zone->nodes, + knot_zone_tree_apply, &f); +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_nsec3_apply_postorder(knot_zone_contents_t *zone, + void (*function)(knot_node_t *node, void *data), + void *data) +{ + if (zone == NULL) { + return KNOT_EBADARG; + } + + knot_zone_tree_func_t f; + f.func = function; + f.data = data; + + return knot_zone_tree_forward_apply_postorder( + zone->nsec3_nodes, knot_zone_tree_apply, &f); +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_nsec3_apply_inorder(knot_zone_contents_t *zone, + void (*function)(knot_node_t *node, void *data), + void *data) +{ + if (zone == NULL) { + return KNOT_EBADARG; + } + + knot_zone_tree_func_t f; + f.func = function; + f.data = data; + + return knot_zone_tree_forward_apply_inorder( + zone->nsec3_nodes, knot_zone_tree_apply, &f); +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_nsec3_apply_inorder_reverse( + knot_zone_contents_t *zone, + void (*function)(knot_node_t *node, void *data), void *data) +{ + if (zone == NULL) { + return KNOT_EBADARG; + } + + knot_zone_tree_func_t f; + f.func = function; + f.data = data; + + return knot_zone_tree_reverse_apply_inorder( + zone->nsec3_nodes, knot_zone_tree_apply, &f); +} + +/*----------------------------------------------------------------------------*/ + +knot_zone_tree_t *knot_zone_contents_get_nodes( + knot_zone_contents_t *contents) +{ + return contents->nodes; +} + +/*----------------------------------------------------------------------------*/ + +knot_zone_tree_t *knot_zone_contents_get_nsec3_nodes( + knot_zone_contents_t *contents) +{ + return contents->nsec3_nodes; +} + +/*----------------------------------------------------------------------------*/ + +ck_hash_table_t *knot_zone_contents_get_hash_table( + knot_zone_contents_t *contents) +{ + return contents->table; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_dname_table_apply(knot_zone_contents_t *contents, + void (*function)(knot_dname_t *, + void *), + void *data) +{ + if (contents == NULL || function == NULL) { + return KNOT_EBADARG; + } + + knot_dname_table_tree_inorder_apply(contents->dname_table, + function, data); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_contents_shallow_copy(const knot_zone_contents_t *from, + knot_zone_contents_t **to) +{ + if (from == NULL || to == NULL) { + return KNOT_EBADARG; + } + + /* Copy to same destination as source. */ + if (from == *to) { + return KNOT_EBADARG; + } + + int ret = KNOT_EOK; + + knot_zone_contents_t *contents = (knot_zone_contents_t *)calloc( + 1, sizeof(knot_zone_contents_t)); + if (contents == NULL) { + ERR_ALLOC_FAILED; + return KNOT_ENOMEM; + } + + contents->apex = from->apex; + + contents->nodes = malloc(sizeof(knot_zone_tree_t)); + if (contents->nodes == NULL) { + ERR_ALLOC_FAILED; + ret = KNOT_ENOMEM; + goto cleanup; + } + + contents->nsec3_nodes = malloc(sizeof(knot_zone_tree_t)); + if (contents->nsec3_nodes == NULL) { + ERR_ALLOC_FAILED; + ret = KNOT_ENOMEM; + goto cleanup; + } + + if (from->dname_table != NULL) { + contents->dname_table = knot_dname_table_new(); + if (contents->dname_table == NULL) { + ERR_ALLOC_FAILED; + ret = KNOT_ENOMEM; + goto cleanup; + } + if ((ret = knot_dname_table_shallow_copy(from->dname_table, + contents->dname_table)) != KNOT_EOK) { + goto cleanup; + } + } else { + contents->dname_table = NULL; + } + + contents->node_count = from->node_count; + contents->generation = from->generation; + + contents->zone = from->zone; + + /* Initialize NSEC3 params */ + memcpy(&contents->nsec3_params, &from->nsec3_params, + sizeof(knot_nsec3_params_t)); + + if ((ret = knot_zone_tree_shallow_copy(from->nodes, + contents->nodes)) != KNOT_EOK + || (ret = knot_zone_tree_shallow_copy(from->nsec3_nodes, + contents->nsec3_nodes)) != KNOT_EOK) { + goto cleanup; + } + +#ifdef USE_HASH_TABLE + if (from->table != NULL) { +// ret = ck_copy_table(from->table, &contents->table); + ret = ck_shallow_copy(from->table, &contents->table); + if (ret != 0) { + dbg_zone("knot_zone_contents_shallow_copy: " + "hash table copied\n"); + ret = KNOT_ERROR; + goto cleanup; + } + } +#endif + + dbg_zone("knot_zone_contents_shallow_copy: " + "finished OK\n"); + + *to = contents; + return KNOT_EOK; + +cleanup: + knot_zone_tree_free(&contents->nodes); + knot_zone_tree_free(&contents->nsec3_nodes); + free(contents->dname_table); + free(contents); + return ret; +} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_contents_free(knot_zone_contents_t **contents) +{ + if (contents == NULL || *contents == NULL) { + return; + } + + // free the zone tree, but only the structure + knot_zone_tree_free(&(*contents)->nodes); + knot_zone_tree_free(&(*contents)->nsec3_nodes); + +#ifdef USE_HASH_TABLE + if ((*contents)->table != NULL) { + ck_destroy_table(&(*contents)->table, NULL, 0); + } +#endif + knot_nsec3_params_free(&(*contents)->nsec3_params); + + knot_dname_table_free(&(*contents)->dname_table); + + free(*contents); + *contents = NULL; +} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_contents_deep_free(knot_zone_contents_t **contents, + int destroy_dname_table) +{ + if (contents == NULL || *contents == NULL) { + return; + } + + if ((*contents) != NULL) { + +#ifdef USE_HASH_TABLE + if ((*contents)->table != NULL) { + ck_destroy_table(&(*contents)->table, NULL, 0); + } +#endif + /* has to go through zone twice, rdata may contain references to + node owners earlier in the zone which may be already freed */ + /* NSEC3 tree is deleted first as it may contain references to + the normal tree. */ + + knot_zone_tree_reverse_apply_postorder( + (*contents)->nsec3_nodes, + knot_zone_contents_destroy_node_rrsets_from_tree, + (void*)1); + + knot_zone_tree_reverse_apply_postorder( + (*contents)->nsec3_nodes, + knot_zone_contents_destroy_node_owner_from_tree, 0); + + knot_zone_tree_reverse_apply_postorder( + (*contents)->nodes, + knot_zone_contents_destroy_node_rrsets_from_tree, + (void*)1); + + knot_zone_tree_reverse_apply_postorder( + (*contents)->nodes, + knot_zone_contents_destroy_node_owner_from_tree, 0); + + // free the zone tree, but only the structure + // (nodes are already destroyed) + dbg_zone("Destroying zone tree.\n"); + knot_zone_tree_free(&(*contents)->nodes); + dbg_zone("Destroying NSEC3 zone tree.\n"); + knot_zone_tree_free(&(*contents)->nsec3_nodes); + + knot_nsec3_params_free(&(*contents)->nsec3_params); + + if (destroy_dname_table) { + /* + * Hack, used in zcompile - destroys the table using + * dname_free() instead of dname_retain(). + */ + knot_dname_table_destroy(&(*contents)->dname_table); + } else { + knot_dname_table_deep_free(&(*contents)->dname_table); + } + } + + free((*contents)); + *contents = NULL; +} + diff --git a/src/libknot/zone/zone-contents.h b/src/libknot/zone/zone-contents.h new file mode 100644 index 0000000..2856f76 --- /dev/null +++ b/src/libknot/zone/zone-contents.h @@ -0,0 +1,556 @@ +/*! + * \file zone-contents.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Zone contents structure and API for manipulating it. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_ZONE_CONTENTS_H_ +#define _KNOT_ZONE_CONTENTS_H_ + +//#include <time.h> + +#include "zone/node.h" +#include "dname.h" +#include "nsec3.h" +#include "zone/dname-table.h" +#include "common/tree.h" +#include "hash/cuckoo-hash-table.h" + +#include "zone-tree.h" + +struct knot_zone; + +/*----------------------------------------------------------------------------*/ + +typedef struct knot_zone_contents_t { + knot_node_t *apex; /*!< Apex node of the zone (holding SOA) */ + + ck_hash_table_t *table; /*!< Hash table for holding zone nodes. */ + knot_zone_tree_t *nodes; + knot_zone_tree_t *nsec3_nodes; + + /*! + * \todo Unify the use of this field - authoritative nodes vs. all. + */ + uint node_count; + + knot_dname_table_t *dname_table; + + knot_nsec3_params_t nsec3_params; + + /*! \brief Generation of the zone during update. + * + * Possible values: + * - 0 - Original version of the zone. Old nodes should be used. + * - 1 - New (updated) zone. New nodes should be used. + * - -1 - New (updated) zone, but exactly the stored nodes should be + * used, no matter their generation. + */ + short generation; + + struct knot_zone *zone; +} knot_zone_contents_t; + +/*----------------------------------------------------------------------------*/ + +knot_zone_contents_t *knot_zone_contents_new(knot_node_t *apex, + uint node_count, + int use_domain_table, + struct knot_zone *zone); + +time_t knot_zone_contents_version(const knot_zone_contents_t *contents); + +void knot_zone_contents_set_version(knot_zone_contents_t *contents, + time_t version); + +//short knot_zone_contents_generation(const knot_zone_contents_t *contents); + +int knot_zone_contents_gen_is_old(const knot_zone_contents_t *contents); +int knot_zone_contents_gen_is_new(const knot_zone_contents_t *contents); +int knot_zone_contents_gen_is_finished(const knot_zone_contents_t *contents); + +//void knot_zone_contents_switch_generation(knot_zone_contents_t *contents); + +void knot_zone_contents_set_gen_old(knot_zone_contents_t *contents); +void knot_zone_contents_set_gen_new(knot_zone_contents_t *contents); +void knot_zone_contents_set_gen_new_finished(knot_zone_contents_t *contents); + +uint16_t knot_zone_contents_class(const knot_zone_contents_t *contents); + +/*! + * \brief Adds a node to the given zone. + * + * Checks if the node belongs to the zone, i.e. if its owner is a subdomain of + * the zone's apex. It thus also forbids adding node with the same name as the + * zone apex. + * + * \warning This function may destroy domain names saved in the node, that + * are already present in the zone. + * + * \param zone Zone to add the node into. + * \param node Node to add into the zone. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + * \retval KNOT_EBADZONE + * \retval KNOT_EHASH + */ +int knot_zone_contents_add_node(knot_zone_contents_t *contents, + knot_node_t *node, int create_parents, + uint8_t flags, int use_domain_table); + +/*! + * \brief Adds a RRSet to the given zone. + * + * Checks if the RRSet belongs to the zone, i.e. if its owner is a subdomain of + * the zone's apex. The RRSet is inserted only if the node is given, or if + * a node where the RRSet should belong is found in the zone. + * + * \warning The function does not check if the node is already inserted in the + * zone, just assumes that it is. + * \warning This function may destroy domain names saved in the RRSet, that + * are already present in the zone. + * + * \param zone Zone to add the node into. + * \param rrset RRSet to add into the zone. + * \param node Node the RRSet should be inserted into. (Should be a node of the + * given zone.) If set to NULL, the function will find proper node + * and set it to this parameter. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + * \retval KNOT_EBADZONE + */ +int knot_zone_contents_add_rrset(knot_zone_contents_t *contents, + knot_rrset_t *rrset, + knot_node_t **node, + knot_rrset_dupl_handling_t dupl, + int use_domain_table); + +int knot_zone_contents_add_rrsigs(knot_zone_contents_t *contents, + knot_rrset_t *rrsigs, + knot_rrset_t **rrset, knot_node_t **node, + knot_rrset_dupl_handling_t dupl, + int use_domain_table); + +/*! + * \brief Adds a node holding NSEC3 records to the given zone. + * + * Checks if the node belongs to the zone, i.e. if its owner is a subdomain of + * the zone's apex. It does not check if the node really contains any NSEC3 + * records, nor if the name is a hash (as there is actually no way of + * determining this). + * + * \param zone Zone to add the node into. + * \param node Node to add into the zone. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + * \retval KNOT_EBADZONE + */ +int knot_zone_contents_add_nsec3_node(knot_zone_contents_t *contents, + knot_node_t *node, int create_parents, + uint8_t flags, int use_domain_table); + +int knot_zone_contents_add_nsec3_rrset(knot_zone_contents_t *contents, + knot_rrset_t *rrset, + knot_node_t **node, + knot_rrset_dupl_handling_t dupl, + int use_domain_table); + +int knot_zone_contents_remove_node(knot_zone_contents_t *contents, + const knot_node_t *node, knot_zone_tree_node_t **removed_tree, + ck_hash_table_item_t **removed_hash); + +//knot_zone_tree_node_t *knot_zone_contents_remove_node( +// knot_zone_contents_t *contents, const knot_node_t *node); + +int knot_zone_contents_remove_nsec3_node(knot_zone_contents_t *contents, + const knot_node_t *node, knot_zone_tree_node_t **removed); + +/*! + * \warning Always call knot_zone_adjust_dnames() prior to calling this + * function. Otherwise the node count would not be set. + * + * \note Currently, all nodes (even non-authoritative) are inserted into the + * hash table. + */ +int knot_zone_contents_create_and_fill_hash_table( + knot_zone_contents_t *contents); + +/*! + * \brief Tries to find a node with the specified name in the zone. + * + * \param zone Zone where the name should be searched for. + * \param name Name to find. + * + * \return Corresponding node if found, NULL otherwise. + */ +knot_node_t *knot_zone_contents_get_node( + const knot_zone_contents_t *contents, const knot_dname_t *name); + +/*! + * \brief Tries to find a node with the specified name among the NSEC3 nodes + * of the zone. + * + * \param zone Zone where the name should be searched for. + * \param name Name to find. + * + * \return Corresponding node if found, NULL otherwise. + */ +knot_node_t *knot_zone_contents_get_nsec3_node( + const knot_zone_contents_t *contents, const knot_dname_t *name); + +/*! + * \brief Tries to find a node with the specified name in the zone. + * + * \note This function is identical to knot_zone_contents_get_node(), only it returns + * constant reference. + * + * \param zone Zone where the name should be searched for. + * \param name Name to find. + * + * \return Corresponding node if found, NULL otherwise. + */ +const knot_node_t *knot_zone_contents_find_node( + const knot_zone_contents_t *contents, const knot_dname_t *name); + +/*! + * \brief Tries to find domain name in the given zone using AVL tree. + * + * \param[in] zone Zone to search for the name. + * \param[in] name Domain name to search for. + * \param[out] node The found node (if it was found, otherwise it may contain + * arbitrary node). + * \param[out] closest_encloser Closest encloser of the given name in the zone. + * \param[out] previous Previous domain name in canonical order. + * + * \retval KNOT_ZONE_NAME_FOUND if node with owner \a name was found. + * \retval KNOT_ZONE_NAME_NOT_FOUND if it was not found. + * \retval KNOT_EBADARG + * \retval KNOT_EBADZONE + */ +int knot_zone_contents_find_dname(const knot_zone_contents_t *contents, + const knot_dname_t *name, + const knot_node_t **node, + const knot_node_t **closest_encloser, + const knot_node_t **previous); + +/*! + * \brief Finds previous name in canonical order to the given name in the zone. + * + * \param zone Zone to search for the name. + * \param name Domain name to find the previous domain name of. + * + * \return Previous node in canonical order, or NULL if some parameter is wrong. + */ +const knot_node_t *knot_zone_contents_find_previous( + const knot_zone_contents_t *contents, const knot_dname_t *name); + +knot_node_t *knot_zone_contents_get_previous( + const knot_zone_contents_t *contents, const knot_dname_t *name); + +const knot_node_t *knot_zone_contents_find_previous_nsec3( + const knot_zone_contents_t *contents, const knot_dname_t *name); + +knot_node_t *knot_zone_contents_get_previous_nsec3( + const knot_zone_contents_t *contents, const knot_dname_t *name); + +#ifdef USE_HASH_TABLE +/*! + * \brief Tries to find domain name in the given zone using the hash table. + * + * \param[in] zone Zone to search for the name. + * \param[in] name Domain name to search for. + * \param[out] node The found node (if it was found, otherwise it may contain + * arbitrary node). + * \param[out] closest_encloser Closest encloser of the given name in the zone. + * \param[out] previous Previous domain name in canonical order. + * + * \retval KNOT_ZONE_NAME_FOUND if node with owner \a name was found. + * \retval KNOT_ZONE_NAME_NOT_FOUND if it was not found. + * \retval KNOT_EBADARG + * \retval KNOT_EBADZONE + */ +int knot_zone_contents_find_dname_hash(const knot_zone_contents_t *contents, + const knot_dname_t *name, + const knot_node_t **node, + const knot_node_t **closest_encloser); +#endif + +/*! + * \brief Tries to find a node with the specified name among the NSEC3 nodes + * of the zone. + * + * \note This function is identical to knot_zone_contents_get_nsec3_node(), only it + * returns constant reference. + * + * \param zone Zone where the name should be searched for. + * \param name Name to find. + * + * \return Corresponding node if found, NULL otherwise. + */ +const knot_node_t *knot_zone_contents_find_nsec3_node( + const knot_zone_contents_t *contents, const knot_dname_t *name); + +/*! + * \brief Finds NSEC3 node and previous NSEC3 node in canonical order, + * corresponding to the given domain name. + * + * This functions creates a NSEC3 hash of \a name and tries to find NSEC3 node + * with the hashed domain name as owner. + * + * \param[in] zone Zone to search in. + * \param[in] name Domain name to get the corresponding NSEC3 nodes for. + * \param[out] nsec3_node NSEC3 node corresponding to \a name (if found, + * otherwise this may be an arbitrary NSEC3 node). + * \param[out] nsec3_previous The NSEC3 node immediately preceding hashed domain + * name corresponding to \a name in canonical order. + * + * \retval KNOT_ZONE_NAME_FOUND if the corresponding NSEC3 node was found. + * \retval KNOT_ZONE_NAME_NOT_FOUND if it was not found. + * \retval KNOT_EBADARG + * \retval KNOT_ENSEC3PAR + * \retval KNOT_ECRYPTO + * \retval KNOT_ERROR + */ +int knot_zone_contents_find_nsec3_for_name( + const knot_zone_contents_t *contents, + const knot_dname_t *name, + const knot_node_t **nsec3_node, + const knot_node_t **nsec3_previous, + int check_ver); +/*! + * \brief Returns the apex node of the zone. + * + * \param zone Zone to get the apex of. + * + * \return Zone apex node. + */ +const knot_node_t *knot_zone_contents_apex( + const knot_zone_contents_t *contents); + +knot_node_t *knot_zone_contents_get_apex( + const knot_zone_contents_t *contents); + +//knot_dname_t *knot_zone_contents_name( +// const knot_zone_contents_t *contents); + +/*! + * \brief Optimizes zone by replacing domain names in RDATA with references to + * domain names present in zone (as node owners). + * + * \param zone Zone to adjust domain names in. + */ +int knot_zone_contents_adjust(knot_zone_contents_t *contents, int check_ver); + +/*! + * \brief Parses the NSEC3PARAM record stored in the zone. + * + * This function properly fills in the nsec3_params field of the zone structure + * according to data stored in the NSEC3PARAM record. This is necessary to do + * before any NSEC3 operations on the zone are requested, otherwise they will + * fail (error KNOT_ENSEC3PAR). + * + * \note If there is no NSEC3PARAM record in the zone, this function clears + * the nsec3_params field of the zone structure (fills it with zeros). + * + * \param zone Zone to get the NSEC3PARAM record from. + */ +int knot_zone_contents_load_nsec3param(knot_zone_contents_t *contents); + +/*! + * \brief Checks if the zone uses NSEC3. + * + * This function will return 0 if the NSEC3PARAM record was not parse prior to + * calling it. + * + * \param zone Zone to check. + * + * \retval <> 0 if the zone uses NSEC3. + * \retval 0 if it does not. + * + * \see knot_zone_contents_load_nsec3param() + */ +int knot_zone_contents_nsec3_enabled(const knot_zone_contents_t *contents); + +/*! + * \brief Returns the parsed NSEC3PARAM record of the zone. + * + * \note You must parse the NSEC3PARAM record prior to calling this function + * (knot_zone_contents_load_nsec3param()). + * + * \param zone Zone to get the NSEC3PARAM record from. + * + * \return Parsed NSEC3PARAM from the zone or NULL if the zone does not use + * NSEC3 or the record was not parsed before. + * + * \see knot_zone_contents_load_nsec3param() + */ +const knot_nsec3_params_t *knot_zone_contents_nsec3params( + const knot_zone_contents_t *contents); + +/*! + * \brief Applies the given function to each regular node in the zone. + * + * This function uses post-order depth-first forward traversal, i.e. the + * function is first recursively applied to subtrees and then to the root. + * + * \param zone Nodes of this zone will be used as parameters for the function. + * \param function Function to be applied to each node of the zone. + * \param data Arbitrary data to be passed to the function. + */ +int knot_zone_contents_tree_apply_postorder(knot_zone_contents_t *contents, + void (*function)(knot_node_t *node, void *data), + void *data); + +/*! + * \brief Applies the given function to each regular node in the zone. + * + * This function uses in-order depth-first forward traversal, i.e. the function + * is first recursively applied to left subtree, then to the root and then to + * the right subtree. + * + * \note This implies that the zone is stored in a binary tree. Is there a way + * to make this traversal independent on the underlying structure? + * + * \param zone Nodes of this zone will be used as parameters for the function. + * \param function Function to be applied to each node of the zone. + * \param data Arbitrary data to be passed to the function. + */ +int knot_zone_contents_tree_apply_inorder(knot_zone_contents_t *contents, + void (*function)(knot_node_t *node, void *data), + void *data); + +/*! + * \brief Applies the given function to each regular node in the zone. + * + * This function uses in-order depth-first reverse traversal, i.e. the function + * is first recursively applied to right subtree, then to the root and then to + * the left subtree. + * + * \note This implies that the zone is stored in a binary tree. Is there a way + * to make this traversal independent on the underlying structure? + * + * \param zone Nodes of this zone will be used as parameters for the function. + * \param function Function to be applied to each node of the zone. + * \param data Arbitrary data to be passed to the function. + */ +int knot_zone_contents_tree_apply_inorder_reverse( + knot_zone_contents_t *contents, + void (*function)(knot_node_t *node, void *data), void *data); + +/*! + * \brief Applies the given function to each NSEC3 node in the zone. + * + * This function uses post-order depth-first forward traversal, i.e. the + * function is first recursively applied to subtrees and then to the root. + * + * \param zone NSEC3 nodes of this zone will be used as parameters for the + * function. + * \param function Function to be applied to each node of the zone. + * \param data Arbitrary data to be passed to the function. + */ +int knot_zone_contents_nsec3_apply_postorder(knot_zone_contents_t *contents, + void (*function)(knot_node_t *node, void *data), + void *data); + +/*! + * \brief Applies the given function to each NSEC3 node in the zone. + * + * This function uses in-order depth-first forward traversal, i.e. the function + * is first recursively applied to left subtree, then to the root and then to + * the right subtree. + * + * \note This implies that the zone is stored in a binary tree. Is there a way + * to make this traversal independent on the underlying structure? + * + * \param zone NSEC3 nodes of this zone will be used as parameters for the + * function. + * \param function Function to be applied to each node of the zone. + * \param data Arbitrary data to be passed to the function. + */ +int knot_zone_contents_nsec3_apply_inorder(knot_zone_contents_t *contents, + void (*function)(knot_node_t *node, void *data), + void *data); + +/*! + * \brief Applies the given function to each NSEC3 node in the zone. + * + * This function uses in-order depth-first reverse traversal, i.e. the function + * is first recursively applied to right subtree, then to the root and then to + * the left subtree. + * + * \note This implies that the zone is stored in a binary tree. Is there a way + * to make this traversal independent on the underlying structure? + * + * \param zone NSEC3 nodes of this zone will be used as parameters for the + * function. + * \param function Function to be applied to each node of the zone. + * \param data Arbitrary data to be passed to the function. + */ +int knot_zone_contents_nsec3_apply_inorder_reverse( + knot_zone_contents_t *contents, + void (*function)(knot_node_t *node, void *data), void *data); + +knot_zone_tree_t *knot_zone_contents_get_nodes( + knot_zone_contents_t *contents); + +knot_zone_tree_t *knot_zone_contents_get_nsec3_nodes( + knot_zone_contents_t *contents); + +ck_hash_table_t *knot_zone_contents_get_hash_table( + knot_zone_contents_t *contents); + +int knot_zone_contents_dname_table_apply(knot_zone_contents_t *contents, + void (*function)(knot_dname_t *, + void *), + void *data); + +/*! + * \brief Creates a shallow copy of the zone (no stored data are copied). + * + * This function creates a new zone structure in \a to, creates new trees for + * regular nodes and for NSEC3 nodes, creates new hash table and a new domain + * table. It also fills these structures with the exact same data as the + * original zone is - no copying of stored data is done, just pointers are + * copied. + * + * \param from Original zone. + * \param to Copy of the zone. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + * \retval KNOT_ENOMEM + */ +int knot_zone_contents_shallow_copy(const knot_zone_contents_t *from, + knot_zone_contents_t **to); + +void knot_zone_contents_free(knot_zone_contents_t **contents); + +void knot_zone_contents_deep_free(knot_zone_contents_t **contents, + int destroy_dname_table); + +#endif + +/*! @} */ diff --git a/src/libknot/zone/zone-tree.c b/src/libknot/zone/zone-tree.c new file mode 100644 index 0000000..cdf128e --- /dev/null +++ b/src/libknot/zone/zone-tree.c @@ -0,0 +1,470 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> + +#include "zone-tree.h" +#include "zone/node.h" +#include "util/error.h" + +/*----------------------------------------------------------------------------*/ +/* Non-API functions */ +/*----------------------------------------------------------------------------*/ + +// AVL tree functions +TREE_DEFINE(knot_zone_tree_node, avl); + +/*----------------------------------------------------------------------------*/ + +static int knot_zone_tree_node_compare(knot_zone_tree_node_t *node1, + knot_zone_tree_node_t *node2) +{ + assert(node1 != NULL); + assert(node2 != NULL); + assert(node1->node != NULL); + assert(node2->node != NULL); + assert(knot_node_owner(node1->node) != NULL); + assert(knot_node_owner(node2->node) != NULL); + + return knot_node_compare(node1->node, node2->node); +} + +/*----------------------------------------------------------------------------*/ + +static void knot_zone_tree_delete_subtree(knot_zone_tree_node_t *root) +{ + if (root == NULL) { + return; + } + + knot_zone_tree_delete_subtree(root->avl.avl_left); + knot_zone_tree_delete_subtree(root->avl.avl_right); + free(root); +} + +/*----------------------------------------------------------------------------*/ + +static int knot_zone_tree_copy_node(knot_zone_tree_node_t *from, + knot_zone_tree_node_t **to) +{ + if (from == NULL) { + *to = NULL; + return KNOT_EOK; + } + + *to = (knot_zone_tree_node_t *) + malloc(sizeof(knot_zone_tree_node_t)); + if (*to == NULL) { + return KNOT_ENOMEM; + } + + (*to)->node = from->node; + (*to)->avl.avl_height = from->avl.avl_height; + + int ret = knot_zone_tree_copy_node(from->avl.avl_left, + &(*to)->avl.avl_left); + if (ret != KNOT_EOK) { + return ret; + } + + ret = knot_zone_tree_copy_node(from->avl.avl_right, + &(*to)->avl.avl_right); + if (ret != KNOT_EOK) { + knot_zone_tree_delete_subtree((*to)->avl.avl_left); + (*to)->avl.avl_left = NULL; + return ret; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +static void knot_zone_tree_free_node(knot_zone_tree_node_t *node, + int free_data, int free_owner) +{ + if (node == NULL) { + return; + } + + knot_zone_tree_free_node(node->avl.avl_left, free_data, free_owner); + + knot_zone_tree_free_node(node->avl.avl_right, free_data, free_owner); + + if (free_data) { + knot_node_free(&node->node, free_owner, 0); + } + + free(node); +} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_init(knot_zone_tree_t *tree) +{ + if (tree == NULL) { + return KNOT_EBADARG; + } + + TREE_INIT(tree, knot_zone_tree_node_compare); + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_insert(knot_zone_tree_t *tree, knot_node_t *node) +{ + if (tree == NULL || node == NULL) { + return KNOT_EBADARG; + } + + knot_zone_tree_node_t *znode = (knot_zone_tree_node_t *)malloc( + sizeof(knot_zone_tree_node_t)); + if (znode == NULL) { + return KNOT_ENOMEM; + } + + znode->node = node; + znode->avl.avl_left = NULL; + znode->avl.avl_right = NULL; + znode->avl.avl_height = 0; + + /*! \todo How to know if this was successful? */ + TREE_INSERT(tree, knot_zone_tree_node, avl, znode); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_find(knot_zone_tree_t *tree, const knot_dname_t *owner, + const knot_node_t **found) +{ + if (tree == NULL || owner == NULL || found == NULL) { + return KNOT_EBADARG; + } + + knot_node_t *f = NULL; + int ret = knot_zone_tree_get(tree, owner, &f); + *found = f; + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_get(knot_zone_tree_t *tree, const knot_dname_t *owner, + knot_node_t **found) +{ + if (tree == NULL || owner == NULL) { + return KNOT_EBADARG; + } + + *found = NULL; + + // create dummy node to use for lookup + knot_zone_tree_node_t *tmp = (knot_zone_tree_node_t *)malloc( + sizeof(knot_zone_tree_node_t)); + if (tmp == NULL) { + return KNOT_ENOMEM; + } + + // create dummy data node to use for lookup + knot_node_t *tmp_data = knot_node_new( + (knot_dname_t *)owner, NULL, 0); + if (tmp_data == NULL) { + free(tmp); + return KNOT_ENOMEM; + } + tmp->node = tmp_data; + + knot_zone_tree_node_t *n = TREE_FIND(tree, knot_zone_tree_node, avl, + tmp); + + knot_node_free(&tmp_data, 0, 0); + free(tmp); + + if (n != NULL) { + *found = n->node; + } + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_find_less_or_equal(knot_zone_tree_t *tree, + const knot_dname_t *owner, + const knot_node_t **found, + const knot_node_t **previous, + int check_version) +{ + if (tree == NULL || owner == NULL || found == NULL || previous == NULL) { + return KNOT_EBADARG; + } + + knot_node_t *f, *p; + int ret = knot_zone_tree_get_less_or_equal(tree, owner, &f, &p, check_version); + + *found = f; + *previous = p; + + return ret; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_get_less_or_equal(knot_zone_tree_t *tree, + const knot_dname_t *owner, + knot_node_t **found, + knot_node_t **previous, + int check_version) +{ + if (tree == NULL || owner == NULL || found == NULL + || previous == NULL) { + return KNOT_EBADARG; + } + + knot_zone_tree_node_t *f = NULL, *prev = NULL; + + // create dummy node to use for lookup + knot_zone_tree_node_t *tmp = (knot_zone_tree_node_t *)malloc( + sizeof(knot_zone_tree_node_t)); + if (tmp == NULL) { + return KNOT_ENOMEM; + } + + // create dummy data node to use for lookup + knot_node_t *tmp_data = knot_node_new( + (knot_dname_t *)owner, NULL, 0); + if (tmp_data == NULL) { + free(tmp); + return KNOT_ENOMEM; + } + tmp->node = tmp_data; + + int exact_match = TREE_FIND_LESS_EQUAL( + tree, knot_zone_tree_node, avl, tmp, &f, &prev); + + knot_node_free(&tmp_data, 0, 0); + free(tmp); + + *found = (exact_match > 0) ? f->node : NULL; + + if (exact_match < 0) { + // previous is not really previous but should be the leftmost + // node in the tree; take it's previous + assert(prev != NULL); + *previous = knot_node_get_previous(prev->node, check_version); + exact_match = 0; + } else if (prev == NULL) { + if (!exact_match) { + printf("Searched for owner %s in zone tree.\n", + knot_dname_to_str(owner)); + printf("Exact match: %d\n", exact_match); + printf("Found node: %p: %s.\n", f, (f) + ? knot_dname_to_str(knot_node_owner(f->node)) + : "none"); + printf("Previous node: %p: %s.\n", prev, (prev) + ? knot_dname_to_str(knot_node_owner(prev->node)) + : "none"); + } + + // 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(exact_match > 0); + assert(f != NULL); + *previous = knot_node_get_previous(f->node, check_version); + } else { + // otherwise check if the previous node is not an empty + // non-terminal + /*! \todo Here we assume that the 'prev' pointer always points + * to an empty non-terminal. + */ + *previous = (knot_node_rrset_count(prev->node) == 0) + ? knot_node_get_previous(prev->node, check_version) + : prev->node; + } + + assert(exact_match >= 0); + + return exact_match; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_remove(knot_zone_tree_t *tree, + const knot_dname_t *owner, + knot_zone_tree_node_t **removed) +{ + if (tree == NULL || owner == NULL || removed == NULL) { + return KNOT_EBADARG; + } + + // create dummy node to use for lookup + knot_zone_tree_node_t *tmp = (knot_zone_tree_node_t *)malloc( + sizeof(knot_zone_tree_node_t)); + if (tmp == NULL) { + return KNOT_ENOMEM; + } + + // create dummy data node to use for lookup + knot_node_t *tmp_data = knot_node_new( + (knot_dname_t *)owner, NULL, 0); + if (tmp_data == NULL) { + free(tmp); + return KNOT_ENOMEM; + } + tmp->node = tmp_data; + + // we must first find the node, so that it may be destroyed + knot_zone_tree_node_t *n = TREE_FIND(tree, knot_zone_tree_node, avl, + tmp); + + /*! \todo How to know if this was successful? */ + TREE_REMOVE(tree, knot_zone_tree_node, avl, tmp); + + knot_node_free(&tmp_data, 0, 0); + free(tmp); + +// *removed = (n) ? n->node : NULL; +// free(n); + *removed = n; + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_forward_apply_inorder(knot_zone_tree_t *tree, + void (*function)( + knot_zone_tree_node_t *node, + void *data), + void *data) +{ + if (tree == NULL || function == NULL) { + return KNOT_EBADARG; + } + + TREE_FORWARD_APPLY(tree, knot_zone_tree_node, avl, + function, data); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_forward_apply_postorder(knot_zone_tree_t *tree, + void (*function)( + knot_zone_tree_node_t *node, + void *data), + void *data) +{ + if (tree == NULL || function == NULL) { + return KNOT_EBADARG; + } + + TREE_POST_ORDER_APPLY(tree, knot_zone_tree_node, avl, + function, data); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_reverse_apply_inorder(knot_zone_tree_t *tree, + void (*function)( + knot_zone_tree_node_t *node, + void *data), + void *data) +{ + if (tree == NULL || function == NULL) { + return KNOT_EBADARG; + } + + TREE_REVERSE_APPLY(tree, knot_zone_tree_node, avl, + function, data); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_reverse_apply_postorder(knot_zone_tree_t *tree, + void (*function)( + knot_zone_tree_node_t *node, + void *data), + void *data) +{ + if (tree == NULL || function == NULL) { + return KNOT_EBADARG; + } + + TREE_REVERSE_APPLY_POST(tree, knot_zone_tree_node, avl, + function, data); + + return KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zone_tree_shallow_copy(knot_zone_tree_t *from, + knot_zone_tree_t *to) +{ + if (to == NULL || from == NULL) { + return KNOT_EBADARG; + } + /* + * This function will copy the tree by hand, so that the nodes + * do not have to be inserted the normal way. It should be substantially + * faster. + */ + + to->th_cmp = from->th_cmp; + + return knot_zone_tree_copy_node(from->th_root, &to->th_root); +} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_tree_free(knot_zone_tree_t **tree) +{ + if (tree == NULL || *tree == NULL) { + return; + } + knot_zone_tree_free_node((*tree)->th_root, 0, 0); + free(*tree); + *tree = NULL; +} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_tree_deep_free(knot_zone_tree_t **tree, int free_owners) +{ + if (tree == NULL || *tree == NULL) { + return; + } + knot_zone_tree_free_node((*tree)->th_root, 1, free_owners); + free(*tree); + *tree = NULL; +} + +/*----------------------------------------------------------------------------*/ diff --git a/src/libknot/zone/zone-tree.h b/src/libknot/zone/zone-tree.h new file mode 100644 index 0000000..0971749 --- /dev/null +++ b/src/libknot/zone/zone-tree.h @@ -0,0 +1,300 @@ +/*! + * \file zone-tree.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Zone tree structure and API for manipulating it. + * + * Implemented as AVL tree. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_ZONE_TREE_H_ +#define _KNOT_ZONE_TREE_H_ + +#include "common/tree.h" +#include "zone/node.h" + +/*----------------------------------------------------------------------------*/ + +typedef struct knot_zone_tree_node { + /*! \brief Structure for connecting this node to an AVL tree. */ + TREE_ENTRY(knot_zone_tree_node) avl; + /*! \brief Zone tree data. */ + knot_node_t *node; + /*! \brief Owner of the node. */ +// knot_dname_t *owner; +} knot_zone_tree_node_t; + +/*----------------------------------------------------------------------------*/ + +typedef TREE_HEAD(knot_zone_tree, knot_zone_tree_node) knot_zone_tree_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Initializes the zone tree. + * + * Does not allocate the structure. Must be called before any use of the tree. + * + * \param tree Zone tree structure to initialize. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + */ +int knot_zone_tree_init(knot_zone_tree_t *tree); + +/*! + * \brief Inserts the given node into the zone tree. + * + * \param tree Zone tree to insert the node into. + * \param node Node to insert. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + * \retval KNOT_ENOMEM + */ +int knot_zone_tree_insert(knot_zone_tree_t *tree, knot_node_t *node); + +/*! + * \brief Finds node with the given owner in the zone tree. + * + * \param tree Zone tree to search in. + * \param owner Owner of the node to find. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + * \retval KNOT_ENOMEM + */ +int knot_zone_tree_find(knot_zone_tree_t *tree, + const knot_dname_t *owner, + const knot_node_t **found); + +/*! + * \brief Finds node with the given owner in the zone tree. + * + * \note This function is identical to knot_zone_tree_find() except that it + * returns non-const node. + * + * \param tree Zone tree to search in. + * \param owner Owner of the node to find. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + * \retval KNOT_ENOMEM + */ +int knot_zone_tree_get(knot_zone_tree_t *tree, + const knot_dname_t *owner, + knot_node_t **found); + +/*! + * \brief Tries to find the given domain name in the zone tree and returns the + * associated node and previous node in canonical order. + * + * \param zone Zone to search in. + * \param owner Owner of the node to find. + * \param found Found node. + * \param previous Previous node in canonical order (i.e. the one directly + * preceding \a owner in canonical order, regardless if the name + * is in the zone or not). + * + * \retval > 0 if the domain name was found. In such case \a found holds the + * zone node with \a owner as its owner. + * \a previous is set properly. + * \retval 0 if the domain name was not found. \a found may hold any (or none) + * node. \a previous is set properly. + * \retval KNOT_EBADARG + * \retval KNOT_ENOMEM + */ +int knot_zone_tree_find_less_or_equal(knot_zone_tree_t *tree, + const knot_dname_t *owner, + const knot_node_t **found, + const knot_node_t **previous, + int check_version); + +/*! + * \brief Tries to find the given domain name in the zone tree and returns the + * associated node and previous node in canonical order. + * + * \note This function is identical to knot_zone_tree_find_less_or_equal() + * except that it returns non-const nodes. + * + * \param zone Zone to search in. + * \param owner Owner of the node to find. + * \param found Found node. + * \param previous Previous node in canonical order (i.e. the one directly + * preceding \a owner in canonical order, regardless if the name + * is in the zone or not). + * + * \retval > 0 if the domain name was found. In such case \a found holds the + * zone node with \a owner as its owner. + * \a previous is set properly. + * \retval 0 if the domain name was not found. \a found may hold any (or none) + * node. \a previous is set properly. + * \retval KNOT_EBADARG + * \retval KNOT_ENOMEM + */ +int knot_zone_tree_get_less_or_equal(knot_zone_tree_t *tree, + const knot_dname_t *owner, + knot_node_t **found, + knot_node_t **previous, + int check_version); + +/*! + * \brief Removes node with the given owner from the zone tree and returns it. + * + * \param tree Zone tree to remove the node from. + * \param owner Owner of the node to find. + * \param removed The removed node. + * + * \retval The removed node. + */ +int knot_zone_tree_remove(knot_zone_tree_t *tree, + const knot_dname_t *owner, + knot_zone_tree_node_t **removed); + +/*! + * \brief Applies the given function to each node in the zone. + * + * This function uses in-order depth-first forward traversal, i.e. the function + * is first recursively applied to left subtree, then to the root and then to + * the right subtree. + * + * \note This implies that the zone is stored in a binary tree. Is there a way + * to make this traversal independent on the underlying structure? + * + * \param tree Zone tree to apply the function to. + * \param function Function to be applied to each node of the zone. + * \param data Arbitrary data to be passed to the function. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + */ +int knot_zone_tree_forward_apply_inorder(knot_zone_tree_t *tree, + void (*function)( + knot_zone_tree_node_t *node, + void *data), + void *data); + +/*! + * \brief Applies the given function to each node in the zone. + * + * This function uses post-order depth-first forward traversal, i.e. the + * function is first recursively applied to subtrees and then to the root. + * + * \note This implies that the zone is stored in a binary tree. Is there a way + * to make this traversal independent on the underlying structure? + * + * \param tree Zone tree to apply the function to. + * \param function Function to be applied to each node of the zone. + * \param data Arbitrary data to be passed to the function. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + */ +int knot_zone_tree_forward_apply_postorder(knot_zone_tree_t *tree, + void (*function)( + knot_zone_tree_node_t *node, + void *data), + void *data); + +/*! + * \brief Applies the given function to each node in the zone. + * + * This function uses in-order depth-first reverse traversal, i.e. the function + * is first recursively applied to right subtree, then to the root and then to + * the left subtree. + * + * \note This implies that the zone is stored in a binary tree. Is there a way + * to make this traversal independent on the underlying structure? + * + * \param tree Zone tree to apply the function to. + * \param function Function to be applied to each node of the zone. + * \param data Arbitrary data to be passed to the function. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + */ +int knot_zone_tree_reverse_apply_inorder(knot_zone_tree_t *tree, + void (*function)( + knot_zone_tree_node_t *node, + void *data), + void *data); + +/*! + * \brief Applies the given function to each node in the zone. + * + * This function uses post-order depth-first reverse traversal, i.e. the + * function is first recursively applied to right subtree, then to the + * left subtree and then to the root. + * + * \note This implies that the zone is stored in a binary tree. Is there a way + * to make this traversal independent on the underlying structure? + * + * \param tree Zone tree to apply the function to. + * \param function Function to be applied to each node of the zone. + * \param data Arbitrary data to be passed to the function. + * + * \retval KNOT_EOK + * \retval KNOT_EBADARG + */ +int knot_zone_tree_reverse_apply_postorder(knot_zone_tree_t *tree, + void (*function)( + knot_zone_tree_node_t *node, + void *data), + void *data); + +/*! + * \brief Copies the whole zone tree structure (but not the data contained + * within). + * + * \warning This function does not check if the target zone tree is empty, + * it just replaces the root pointer. + * + * \param from Original zone tree. + * \param to Zone tree to copy the original one into. + * + * \retval KNOT_EOK + * \retval KNOT_ENOMEM + */ +int knot_zone_tree_shallow_copy(knot_zone_tree_t *from, + knot_zone_tree_t *to); + +/*! + * \brief Destroys the zone tree, not touching the saved data. + * + * \param tree Zone tree to be destroyed. + */ +void knot_zone_tree_free(knot_zone_tree_t **tree); + +/*! + * \brief Destroys the zone tree, together with the saved data. + * + * \param tree Zone tree to be destroyed. + * \param free_owners Set to <> 0 if owners of the nodes should be destroyed + * as well. Set to 0 otherwise. + */ +void knot_zone_tree_deep_free(knot_zone_tree_t **tree, int free_owners); + +/*----------------------------------------------------------------------------*/ + +#endif // _KNOT_ZONE_TREE_H_ + +/*! @} */ + diff --git a/src/libknot/zone/zone.c b/src/libknot/zone/zone.c new file mode 100644 index 0000000..9de1a53 --- /dev/null +++ b/src/libknot/zone/zone.c @@ -0,0 +1,246 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> + +#include <urcu.h> + +#include "common.h" +#include "zone/zone.h" +#include "zone/node.h" +#include "dname.h" +#include "consts.h" +#include "util/descriptor.h" +#include "nsec3.h" +#include "util/error.h" +#include "util/debug.h" +#include "util/utils.h" +#include "common/tree.h" +#include "common/base32hex.h" +#include "hash/cuckoo-hash-table.h" +#include "zone/zone-contents.h" + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +knot_zone_t *knot_zone_new_empty(knot_dname_t *name) +{ + if (!name) { + return 0; + } + + dbg_zone("Creating new zone!\n"); + + knot_zone_t *zone = malloc(sizeof(knot_zone_t)); + if (zone == NULL) { + ERR_ALLOC_FAILED; + return NULL; + } + memset(zone, 0, sizeof(knot_zone_t)); + + // save the zone name + dbg_zone("Setting zone name.\n"); + zone->name = name; + return zone; +} + +/*----------------------------------------------------------------------------*/ + +knot_zone_t *knot_zone_new(knot_node_t *apex, uint node_count, + int use_domain_table) +{ + knot_zone_t *zone = knot_zone_new_empty( + knot_dname_deep_copy(knot_node_owner(apex))); + if (zone == NULL) { + return NULL; + } + + dbg_zone("Creating zone contents.\n"); + zone->contents = knot_zone_contents_new(apex, node_count, + use_domain_table, zone); + if (zone->contents == NULL) { + knot_dname_release(zone->name); + free(zone); + return NULL; + } + + zone->contents->zone = zone; + + return zone; +} + +/*----------------------------------------------------------------------------*/ + +knot_zone_contents_t *knot_zone_get_contents( + const knot_zone_t *zone) +{ + if (zone == NULL) { + return NULL; + } + + return rcu_dereference(zone->contents); +} + +/*----------------------------------------------------------------------------*/ + +const knot_zone_contents_t *knot_zone_contents( + const knot_zone_t *zone) +{ + if (zone == NULL) { + return NULL; + } + + return rcu_dereference(zone->contents); +} + +/*----------------------------------------------------------------------------*/ + +time_t knot_zone_version(const knot_zone_t *zone) +{ + if (zone == NULL) { + return KNOT_EBADARG; + } + + return zone->version; +} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_set_version(knot_zone_t *zone, time_t version) +{ + if (zone == NULL) { + return; + } + + zone->version = version; +} + +/*----------------------------------------------------------------------------*/ + +short knot_zone_is_master(const knot_zone_t *zone) +{ + return zone->master; +} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_set_master(knot_zone_t *zone, short master) +{ + zone->master = master; +} + +/*----------------------------------------------------------------------------*/ + +const void *knot_zone_data(const knot_zone_t *zone) +{ + return zone->data; +} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_set_data(knot_zone_t *zone, void *data) +{ + zone->data = data; +} + +/*----------------------------------------------------------------------------*/ + +const knot_dname_t *knot_zone_name(const knot_zone_t *zone) +{ + return zone->name; +} + +/*----------------------------------------------------------------------------*/ + +knot_zone_contents_t *knot_zone_switch_contents(knot_zone_t *zone, + knot_zone_contents_t *new_contents) +{ + if (zone == NULL) { + return NULL; + } + + knot_zone_contents_t *old_contents = + rcu_xchg_pointer(&zone->contents, new_contents); + return old_contents; +} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_free(knot_zone_t **zone) +{ + if (zone == NULL || *zone == NULL) { + return; + } + + dbg_zone("zone_free().\n"); + + if ((*zone)->contents + && !knot_zone_contents_gen_is_old((*zone)->contents)) { + // zone is in the middle of an update, report + dbg_zone("Destroying zone that is in the middle of an " + "update.\n"); + } + + knot_dname_release((*zone)->name); + + /* Call zone data destructor if exists. */ + if ((*zone)->dtor) { + (*zone)->dtor(*zone); + } + + knot_zone_contents_free(&(*zone)->contents); + free(*zone); + *zone = NULL; + + dbg_zone("Done.\n"); +} + +/*----------------------------------------------------------------------------*/ + +void knot_zone_deep_free(knot_zone_t **zone, int destroy_dname_table) +{ + if (zone == NULL || *zone == NULL) { + return; + } + + if ((*zone)->contents + && !knot_zone_contents_gen_is_old((*zone)->contents)) { + // zone is in the middle of an update, report + dbg_zone("Destroying zone that is in the middle of an " + "update.\n"); + } + +dbg_zone_exec( + char *name = knot_dname_to_str((*zone)->name); + dbg_zone("Destroying zone %p, name: %s.\n", *zone, name); + free(name); +); + + knot_dname_release((*zone)->name); + + /* Call zone data destructor if exists. */ + if ((*zone)->dtor) { + (*zone)->dtor(*zone); + } + + knot_zone_contents_deep_free(&(*zone)->contents, destroy_dname_table); + free(*zone); + *zone = NULL; +} diff --git a/src/libknot/zone/zone.h b/src/libknot/zone/zone.h new file mode 100644 index 0000000..331ef1f --- /dev/null +++ b/src/libknot/zone/zone.h @@ -0,0 +1,157 @@ +/*! + * \file zone.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Zone structure and API for manipulating it. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_ZONE_H_ +#define _KNOT_ZONE_H_ + +#include <time.h> + +#include "zone/node.h" +#include "dname.h" +#include "nsec3.h" +#include "zone/dname-table.h" +#include "common/tree.h" +#include "hash/cuckoo-hash-table.h" + +#include "zone-tree.h" + +#include "zone/zone-contents.h" + +/*----------------------------------------------------------------------------*/ + +//typedef TREE_HEAD(avl_tree, knot_node) avl_tree_t; +//struct event_t; + +/*----------------------------------------------------------------------------*/ +/*! + * \brief Return values for search functions. + * + * Used in knot_zone_find_dname() and knot_zone_find_dname_hash(). + */ +enum knot_zone_retvals { + KNOT_ZONE_NAME_FOUND = 1, + KNOT_ZONE_NAME_NOT_FOUND = 0 +}; + +typedef enum knot_zone_retvals knot_zone_retvals_t; + +/*----------------------------------------------------------------------------*/ + +/*! + * \brief Structure for holding DNS zone. + * + * \warning Make sure not to insert the same nodes using both the normal and + * NSEC3 functions. Although this will be successfull, it will produce + * double-free errors when destroying the zone. + */ +struct knot_zone { + knot_dname_t *name; + + knot_zone_contents_t *contents; + + time_t version; + + /*! \todo Set when loading zone. */ + short master; + + void *data; /*!< Pointer to generic zone-related data. */ + int (*dtor)(struct knot_zone *); /*!< Data destructor. */ +}; + +typedef struct knot_zone knot_zone_t; + +/*----------------------------------------------------------------------------*/ + +/*! + * \brief Creates new empty DNS zone. + * + * \notice Zone will be created without contents. + * + * \param name Zone owner. + * + * \return The initialized zone structure or NULL if an error occured. + */ +knot_zone_t *knot_zone_new_empty(knot_dname_t *name); + +/*! + * \brief Creates new DNS zone. + * + * \param apex Node representing the zone apex. + * \param node_count Number of authorative nodes in the zone. + * + * \return The initialized zone structure or NULL if an error occured. + */ +knot_zone_t *knot_zone_new(knot_node_t *apex, uint node_count, + int use_domain_table); + +knot_zone_contents_t *knot_zone_get_contents( + const knot_zone_t *zone); + +const knot_zone_contents_t *knot_zone_contents( + const knot_zone_t *zone); + + +time_t knot_zone_version(const knot_zone_t *zone); + +void knot_zone_set_version(knot_zone_t *zone, time_t version); + +short knot_zone_is_master(const knot_zone_t *zone); + +void knot_zone_set_master(knot_zone_t *zone, short master); + +const void *knot_zone_data(const knot_zone_t *zone); + +void knot_zone_set_data(knot_zone_t *zone, void *data); + +const knot_dname_t *knot_zone_name(const knot_zone_t *zone); + +knot_zone_contents_t *knot_zone_switch_contents(knot_zone_t *zone, + knot_zone_contents_t *new_contents); + +/*! + * \brief Correctly deallocates the zone structure, without deleting its nodes. + * + * Also sets the given pointer to NULL. + * + * \param zone Zone to be freed. + */ +void knot_zone_free(knot_zone_t **zone); + +/*! + * \brief Correctly deallocates the zone structure and all nodes within. + * + * Also sets the given pointer to NULL. + * + * \param zone Zone to be freed. + * \param free_rdata_dnames Set to <> 0 if you want to delete ALL domain names + * present in RDATA. Set to 0 otherwise. (See + * knot_rdata_deep_free().) + */ +void knot_zone_deep_free(knot_zone_t **zone, int destroy_dname_table); + +#endif + +/*! @} */ diff --git a/src/libknot/zone/zonedb.c b/src/libknot/zone/zonedb.c new file mode 100644 index 0000000..8f07d45 --- /dev/null +++ b/src/libknot/zone/zonedb.c @@ -0,0 +1,389 @@ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdlib.h> +#include <assert.h> + +#include <urcu.h> + +#include "common.h" +#include "zone/zone.h" +#include "zone/zonedb.h" +#include "dname.h" +#include "zone/node.h" +#include "util/error.h" +#include "util/debug.h" +#include "common/general-tree.h" + +/*----------------------------------------------------------------------------*/ +/* Non-API functions */ +/*----------------------------------------------------------------------------*/ +/*! + * \brief Compares the two arguments interpreted as zone names (domain names). + * + * Use this function with generic data structures (such as the skip list). + * + * \param d1 First zone name. + * \param d2 Second zone name. + * + * \retval 0 if the two zone names are equal. + * \retval < 0 if \a d1 is before \a d2 in canonical order. + * \retval > 0 if \a d1 is after \a d2 in canonical order. + */ +static int knot_zonedb_compare_zone_names(void *p1, void *p2) +{ + const knot_zone_t *zone1 = (const knot_zone_t *)p1; + const knot_zone_t *zone2 = (const knot_zone_t *)p2; + + int ret = knot_dname_compare(zone1->name, zone2->name); + +dbg_zonedb_exec( + char *name1 = knot_dname_to_str(zone1->name); + char *name2 = knot_dname_to_str(zone2->name); + dbg_zonedb("Compared names %s and %s, result: %d.\n", + name1, name2, ret); + free(name1); + free(name2); +); + + return (ret); +} + +/*----------------------------------------------------------------------------*/ + +//static int knot_zonedb_replace_zone_in_list(void **list_item, void **new_zone) +//{ +// assert(list_item != NULL); +// assert(*list_item != NULL); +// assert(new_zone != NULL); +// assert(*new_zone != NULL); + +// dbg_zonedb("Replacing list item %p with new zone %p\n", +// *list_item, *new_zone); + +// *list_item = *new_zone; + +// return 0; +//} + +/*----------------------------------------------------------------------------*/ +/* API functions */ +/*----------------------------------------------------------------------------*/ + +knot_zonedb_t *knot_zonedb_new() +{ + knot_zonedb_t *db = + (knot_zonedb_t *)malloc(sizeof(knot_zonedb_t)); + CHECK_ALLOC_LOG(db, NULL); + + db->zone_tree = gen_tree_new(knot_zonedb_compare_zone_names); + if (db->zone_tree == NULL) { + free(db); + return NULL; + } + + db->zone_count = 0; + + return db; +} + +/*----------------------------------------------------------------------------*/ + +int knot_zonedb_add_zone(knot_zonedb_t *db, knot_zone_t *zone) +{ + if (db == NULL || zone == NULL) { + return KNOT_EBADARG; + } +dbg_zonedb_exec( + char *name = knot_dname_to_str(zone->name); + dbg_zonedb("Inserting zone %s into zone db.\n", name); + free(name); +); + + int ret = KNOT_EOK; + if (knot_zone_contents(zone)) { + ret = knot_zone_contents_load_nsec3param( + knot_zone_get_contents(zone)); + if (ret != KNOT_EOK) { + return ret; + } + } + + ret = gen_tree_add(db->zone_tree, zone, NULL); + + if (ret == 0) { + db->zone_count++; + } + + return (ret != 0) ? KNOT_EZONEIN : KNOT_EOK; +} + +/*----------------------------------------------------------------------------*/ + +knot_zone_t *knot_zonedb_remove_zone(knot_zonedb_t *db, + const knot_dname_t *zone_name) +{ + knot_zone_t dummy_zone; + memset(&dummy_zone, 0, sizeof(knot_zone_t)); + dummy_zone.name = (knot_dname_t *)zone_name; + + // add some lock to avoid multiple removals + knot_zone_t *z = (knot_zone_t *)gen_tree_find(db->zone_tree, + &dummy_zone); + + if (z == NULL) { + return NULL; + } + + // remove the zone from the skip list, but do not destroy it + gen_tree_remove(db->zone_tree, &dummy_zone); + +// if (destroy_zone) { +// // properly destroy the zone and all its contents +// knot_zone_deep_free(&z, 0); +// } + + db->zone_count--; + + //return KNOT_EOK; + return z; +} + +/*----------------------------------------------------------------------------*/ + +//knot_zone_t *knot_zonedb_replace_zone(knot_zonedb_t *db, +// knot_zone_t *zone) +//{ +// knot_zone_t *z = knot_zonedb_find_zone(db, +// knot_node_owner(knot_zone_apex(zone))); +// if (z == NULL) { +// return NULL; +// } + +// /*! \todo The replace should be atomic!!! */ + +// dbg_zonedb("Found zone: %p\n", z); + +// int ret = skip_remove(db->zones, +// (void *)knot_node_owner(knot_zone_apex(zone)), +// NULL, NULL); +// if (ret != 0) { +// return NULL; +// } + +// dbg_zonedb("Removed zone, return value: %d\n", ret); +// dbg_zonedb("Old zone: %p\n", z); + +// ret = skip_insert(db->zones, +// (void *)knot_node_owner(knot_zone_apex(zone)), +// (void *)zone, NULL); + +// dbg_zonedb("Inserted zone, return value: %d\n", ret); + +// if (ret != 0) { +// // return the removed zone back +// skip_insert(db->zones, +// (void *)knot_node_owner(knot_zone_apex(z)), +// (void *)z, NULL); +// /*! \todo There may be problems and the zone may remain +// removed. */ +// return NULL; +// } + +// return z; +//} + +/*----------------------------------------------------------------------------*/ + +knot_zone_t *knot_zonedb_find_zone(const knot_zonedb_t *db, + const knot_dname_t *zone_name) +{ + knot_zone_t dummy_zone; + dummy_zone.name = (knot_dname_t *)zone_name; + return (knot_zone_t *)gen_tree_find(db->zone_tree, &dummy_zone); +} + +/*----------------------------------------------------------------------------*/ + +const knot_zone_t *knot_zonedb_find_zone_for_name(knot_zonedb_t *db, + const knot_dname_t *dname) +{ + if (db == NULL || dname == NULL) { + return NULL; + } + + knot_zone_t dummy_zone; + dummy_zone.name = (knot_dname_t *)dname; + void *found = NULL; + int exact_match = gen_tree_find_less_or_equal(db->zone_tree, + &dummy_zone, + &found); + UNUSED(exact_match); + + knot_zone_t *zone = (found) ? (knot_zone_t *)found : NULL; + +dbg_zonedb_exec( + char *name = knot_dname_to_str(dname); + dbg_zonedb("Found zone for name %s: %p\n", name, zone); + free(name); +); + if (zone != NULL && zone->contents != NULL + && knot_dname_compare(zone->contents->apex->owner, dname) != 0 + && !knot_dname_is_subdomain(dname, zone->contents->apex->owner)) { + zone = NULL; + } + + return zone; +} + +/*----------------------------------------------------------------------------*/ + +knot_zone_contents_t *knot_zonedb_expire_zone(knot_zonedb_t *db, + const knot_dname_t *zone_name) +{ + if (db == NULL || zone_name == NULL) { + return NULL; + } + + // Remove the contents from the zone, but keep the zone in the zonedb. + + knot_zone_t *zone = knot_zonedb_find_zone(db, zone_name); + if (zone == NULL) { + return NULL; + } + + return knot_zone_switch_contents(zone, NULL); +} + +/*----------------------------------------------------------------------------*/ + +knot_zonedb_t *knot_zonedb_copy(const knot_zonedb_t *db) +{ + knot_zonedb_t *db_new = + (knot_zonedb_t *)malloc(sizeof(knot_zonedb_t)); + CHECK_ALLOC_LOG(db_new, NULL); + + db_new->zone_tree = gen_tree_shallow_copy(db->zone_tree); + if (db_new->zone_tree == NULL) { + free(db_new); + return NULL; + } + + return db_new; +} + +size_t knot_zonedb_zone_count(const knot_zonedb_t *db) +{ + return db->zone_count; +} + +struct knot_zone_db_tree_arg { + const knot_zone_t **zones; + size_t count; +}; + +static void save_zone_to_array(void *node, void *data) +{ + knot_zone_t *zone = (knot_zone_t *)node; + struct knot_zone_db_tree_arg *args = + (struct knot_zone_db_tree_arg *)data; + assert(data); + args->zones[args->count++] = zone; +} + +const knot_zone_t **knot_zonedb_zones(const knot_zonedb_t *db) +{ + struct knot_zone_db_tree_arg args; + args.zones = malloc(sizeof(knot_zone_t) * db->zone_count); + args.count = 0; + CHECK_ALLOC_LOG(args.zones, NULL); + + gen_tree_apply_inorder(db->zone_tree, save_zone_to_array, + &args); + assert(db->zone_count == args.count); + + return args.zones; +} + +/*----------------------------------------------------------------------------*/ + +void knot_zonedb_free(knot_zonedb_t **db) +{ + gen_tree_destroy(&((*db)->zone_tree), NULL ,NULL); + free(*db); + *db = NULL; +} + +/*----------------------------------------------------------------------------*/ + +static void delete_zone_from_db(void *node, void *data) +{ + UNUSED(data); + knot_zone_t *zone = (knot_zone_t *)node; + assert(zone); + synchronize_rcu(); + knot_zone_deep_free(&zone, 0); +} + +void knot_zonedb_deep_free(knot_zonedb_t **db) +{ + dbg_zonedb("Deleting zone db (%p).\n", *db); +// dbg_zonedb("Is it empty (%p)? %s\n", +// (*db)->zones, skip_is_empty((*db)->zones) ? "yes" : "no"); + +//dbg_zonedb_exec( +// int i = 1; +// char *name = NULL; +// while (zn != NULL) { +// dbg_zonedb("%d. zone: %p, key: %p\n", i, zn->value, +// zn->key); +// assert(zn->key == ((knot_zone_t *)zn->value)->apex->owner); +// name = knot_dname_to_str((knot_dname_t *)zn->key); +// dbg_zonedb(" zone name: %s\n", name); +// free(name); + +// zn = skip_next(zn); +// } + +// zn = skip_first((*db)->zones); +//); + +// while (zn != NULL) { +// zone = (knot_zone_t *)zn->value; +// assert(zone != NULL); + +// // remove the zone from the database +// skip_remove((*db)->zones, zn->key, NULL, NULL); +// // wait for all readers to finish +// synchronize_rcu; +// // destroy the zone +// knot_zone_deep_free(&zone, 0); + +// zn = skip_first((*db)->zones); +// } + +// assert(skip_is_empty((*db)->zones)); + +// skip_destroy_list(&(*db)->zones, NULL, NULL); + gen_tree_destroy(&((*db)->zone_tree), delete_zone_from_db, NULL); + assert((*db)->zone_tree == NULL); + free(*db); + *db = NULL; +} + +/*----------------------------------------------------------------------------*/ + diff --git a/src/libknot/zone/zonedb.h b/src/libknot/zone/zonedb.h new file mode 100644 index 0000000..d5a4992 --- /dev/null +++ b/src/libknot/zone/zonedb.h @@ -0,0 +1,145 @@ +/*! + * \file zonedb.h + * + * \author Lubos Slovak <lubos.slovak@nic.cz> + * + * \brief Zone database structure and API for manipulating it. + * + * Zone database groups several zones and provides functions for finding + * suitable zone for a domain name, for searching in a particular zone, etc. + * + * \addtogroup libknot + * @{ + */ +/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _KNOT_ZONEDB_H_ +#define _KNOT_ZONEDB_H_ + +#include "common/general-tree.h" +#include "zone/zone.h" +#include "zone/node.h" +#include "dname.h" + +/*! + * \brief Zone database structure. Contains all zones managed by the server. + */ +struct knot_zonedb { + general_tree_t *zone_tree; /*!< AVL tree of zones. */ + size_t zone_count; +}; + +typedef struct knot_zonedb knot_zonedb_t; + +/*----------------------------------------------------------------------------*/ + +/*! + * \brief Allocates and initializes the zone database structure. + * + * \return Pointer to the created zone database structure or NULL if an error + * occured. + */ +knot_zonedb_t *knot_zonedb_new(); + +/*! + * \brief Adds new zone to the database. + * + * \param db Zone database to store the zone. + * \param zone Parsed zone. + * + * \retval KNOT_EOK + * \retval KNOT_EZONEIN + */ +int knot_zonedb_add_zone(knot_zonedb_t *db, knot_zone_t *zone); + +/*! + * \brief Removes the given zone from the database if it exists. + * + * \note Assumes that the zone was adjusted using knot_zone_adjust_dnames(). + * If it was not, it may leak some memory due to checks used in + * knot_rdata_deep_free(). + * + * \param db Zone database to remove from. + * \param zone_name Name of the zone to be removed. + * \param destroy_zone Set to <> 0 if you do want the function to destroy the + * zone after removing from zone database. Set to 0 + * otherwise. + * + * \retval KNOT_EOK + * \retval KNOT_ENOZONE + */ +knot_zone_t * knot_zonedb_remove_zone(knot_zonedb_t *db, + const knot_dname_t *zone_name); + +//knot_zone_t *knot_zonedb_replace_zone(knot_zonedb_t *db, +// knot_zone_t *zone); + +/*! + * \brief Finds zone exactly matching the given zone name. + * + * \param db Zone database to search in. + * \param zone_name Domain name representing the zone name. + * + * \return Zone with \a zone_name being the owner of the zone apex or NULL if + * not found. + */ +knot_zone_t *knot_zonedb_find_zone(const knot_zonedb_t *db, + const knot_dname_t *zone_name); + + +/*! + * \brief Finds zone the given domain name should belong to. + * + * \param db Zone database to search in. + * \param dname Domain name to find zone for. + * + * \retval Zone in which the domain name should be present or NULL if no such + * zone is found. + */ +const knot_zone_t *knot_zonedb_find_zone_for_name(knot_zonedb_t *db, + const knot_dname_t *dname); + +knot_zone_contents_t *knot_zonedb_expire_zone(knot_zonedb_t *db, + const knot_dname_t *zone_name); + +size_t knot_zonedb_zone_count(const knot_zonedb_t *db); +const knot_zone_t **knot_zonedb_zones(const knot_zonedb_t *db); + +/*! + * \brief Destroys and deallocates the zone database structure (but not the + * zones within). + * + * \param db Zone database to be destroyed. + */ +void knot_zonedb_free(knot_zonedb_t **db); + +/*! + * \brief Destroys and deallocates the whole zone database including the zones. + * + * \note Assumes that the zone was adjusted using knot_zone_adjust_dnames(). + * If it was not, it may leak some memory due to checks used in + * knot_rdata_deep_free(). + * + * \param db Zone database to be destroyed. + */ +void knot_zonedb_deep_free(knot_zonedb_t **db); + +/*----------------------------------------------------------------------------*/ + +#endif /* _KNOT_ZONEDB_H_ */ + +/*! @} */ |