diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:26:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:26:00 +0000 |
commit | 830407e88f9d40d954356c3754f2647f91d5c06a (patch) | |
tree | d6a0ece6feea91f3c656166dbaa884ef8a29740e /lib | |
parent | Initial commit. (diff) | |
download | knot-resolver-upstream/5.6.0.tar.xz knot-resolver-upstream/5.6.0.zip |
Adding upstream version 5.6.0.upstream/5.6.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib')
96 files changed, 28524 insertions, 0 deletions
diff --git a/lib/README.rst b/lib/README.rst new file mode 100644 index 0000000..1c8cf7b --- /dev/null +++ b/lib/README.rst @@ -0,0 +1,313 @@ +.. SPDX-License-Identifier: GPL-3.0-or-later + +********************* +Knot Resolver library +********************* + +Requirements +============ + +* libknot_ 2.0 (Knot DNS high-performance DNS library.) + +For users +========= + +The library as described provides basic services for name resolution, which should cover the usage, +examples are in the :ref:`resolve API <lib_api_rplan>` documentation. + +.. tip:: If you're migrating from ``getaddrinfo()``, see *"synchronous"* API, but the library offers iterative API as well to plug it into your event loop for example. + +.. _lib-layers: + +For developers +============== + +The resolution process starts with the functions in :ref:`resolve.c <lib_api_rplan>`, they are responsible for: + +* reacting to state machine state (i.e. calling consume layers if we have an answer ready) +* interacting with the library user (i.e. asking caller for I/O, accepting queries) +* fetching assets needed by layers (i.e. zone cut) + +This is the *driver*. The driver is not meant to know *"how"* the query resolves, but rather *"when"* to execute *"what"*. + +.. image:: ../doc/resolution.png + :align: center + +On the other side are *layers*. They are responsible for dissecting the packets and informing the driver about the results. For example, a *produce* layer generates query, a *consume* layer validates answer. + +.. tip:: Layers are executed asynchronously by the driver. If you need some asset beforehand, you can signalize the driver using returning state or current query flags. For example, setting a flag ``AWAIT_CUT`` forces driver to fetch zone cut information before the packet is consumed; setting a ``RESOLVED`` flag makes it pop a query after the current set of layers is finished; returning ``FAIL`` state makes it fail current query. + +Layers can also change course of resolution, for example by appending additional queries. + +.. code-block:: lua + + consume = function (state, req, answer) + if answer:qtype() == kres.type.NS then + local qry = req:push(answer:qname(), kres.type.SOA, kres.class.IN) + qry.flags.AWAIT_CUT = true + end + return state + end + +This **doesn't** block currently processed query, and the newly created sub-request will start as soon as driver finishes processing current. In some cases you might need to issue sub-request and process it **before** continuing with the current, i.e. validator may need a DNSKEY before it can validate signatures. In this case, layers can yield and resume afterwards. + +.. code-block:: lua + + consume = function (state, req, answer) + if state == kres.YIELD then + print('continuing yielded layer') + return kres.DONE + else + if answer:qtype() == kres.type.NS then + local qry = req:push(answer:qname(), kres.type.SOA, kres.class.IN) + qry.flags.AWAIT_CUT = true + print('planned SOA query, yielding') + return kres.YIELD + end + return state + end + end + +The ``YIELD`` state is a bit special. When a layer returns it, it interrupts current walk through the layers. When the layer receives it, +it means that it yielded before and now it is resumed. This is useful in a situation where you need a sub-request to determine whether current answer is valid or not. + +Writing layers +============== + +.. warning:: FIXME: this dev-docs section is outdated! Better see comments in files instead, for now. + +The resolver :ref:`library <lib_index>` leverages the processing API from the libknot to separate packet processing code into layers. + +.. note:: This is only crash-course in the library internals, see the resolver :ref:`library <lib_index>` documentation for the complete overview of the services. + +The library offers following services: + +- :ref:`Cache <lib_api_cache>` - MVCC cache interface for retrieving/storing resource records. +- :ref:`Resolution plan <lib_api_rplan>` - Query resolution plan, a list of partial queries (with hierarchy) sent in order to satisfy original query. This contains information about the queries, nameserver choice, timing information, answer and its class. +- :ref:`Nameservers <lib_api_nameservers>` - Reputation database of nameservers, this serves as an aid for nameserver choice. + +A processing layer is going to be called by the query resolution driver for each query, +so you're going to work with :ref:`struct kr_request <lib_api_rplan>` as your per-query context. +This structure contains pointers to resolution context, resolution plan and also the final answer. + +.. code-block:: c + + int consume(kr_layer_t *ctx, knot_pkt_t *pkt) + { + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + } + +This is only passive processing of the incoming answer. If you want to change the course of resolution, say satisfy a query from a local cache before the library issues a query to the nameserver, you can use states (see the :ref:`Static hints <mod-hints>` for example). + +.. code-block:: c + + int produce(kr_layer_t *ctx, knot_pkt_t *pkt) + { + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + + /* Query can be satisfied locally. */ + if (can_satisfy(qry)) { + /* This flag makes the resolver move the query + * to the "resolved" list. */ + qry->flags.RESOLVED = true; + return KR_STATE_DONE; + } + + /* Pass-through. */ + return ctx->state; + } + +It is possible to not only act during the query resolution, but also to view the complete resolution plan afterwards. This is useful for analysis-type tasks, or *"per answer"* hooks. + +.. code-block:: c + + int finish(kr_layer_t *ctx) + { + struct kr_request *req = ctx->req; + struct kr_rplan *rplan = req->rplan; + + /* Print the query sequence with start time. */ + char qname_str[KNOT_DNAME_MAXLEN]; + struct kr_query *qry = NULL + WALK_LIST(qry, rplan->resolved) { + knot_dname_to_str(qname_str, qry->sname, sizeof(qname_str)); + printf("%s at %u\n", qname_str, qry->timestamp); + } + + return ctx->state; + } + +APIs in Lua +=========== + +The APIs in Lua world try to mirror the C APIs using LuaJIT FFI, with several differences and enhancements. +There is not comprehensive guide on the API yet, but you can have a look at the bindings_ file. + +Elementary types and constants +------------------------------ + +* States are directly in ``kres`` table, e.g. ``kres.YIELD, kres.CONSUME, kres.PRODUCE, kres.DONE, kres.FAIL``. +* DNS classes are in ``kres.class`` table, e.g. ``kres.class.IN`` for Internet class. +* DNS types are in ``kres.type`` table, e.g. ``kres.type.AAAA`` for AAAA type. +* DNS rcodes types are in ``kres.rcode`` table, e.g. ``kres.rcode.NOERROR``. +* Extended DNS error codes are in ``kres.extended_error`` table, e.g. ``kres.extended_error.BLOCKED``. +* Packet sections (QUESTION, ANSWER, AUTHORITY, ADDITIONAL) are in the ``kres.section`` table. + +Working with domain names +------------------------- + +The internal API usually works with domain names in label format, you can convert between text and wire freely. + +.. code-block:: lua + + local dname = kres.str2dname('business.se') + local strname = kres.dname2str(dname) + +Working with resource records +----------------------------- + +Resource records are stored as tables. + +.. code-block:: lua + + local rr = { owner = kres.str2dname('owner'), + ttl = 0, + class = kres.class.IN, + type = kres.type.CNAME, + rdata = kres.str2dname('someplace') } + print(kres.rr2str(rr)) + +RRSets in packet can be accessed using FFI, you can easily fetch single records. + +.. code-block:: lua + + local rrset = { ... } + local rr = rrset:get(0) -- Return first RR + print(kres.dname2str(rr:owner())) + print(rr:ttl()) + print(kres.rr2str(rr)) + +Working with packets +-------------------- + +Packet is the data structure that you're going to see in layers very often. They consists of a header, and four sections: QUESTION, ANSWER, AUTHORITY, ADDITIONAL. The first section is special, as it contains the query name, type, and class; the rest of the sections contain RRSets. + +First you need to convert it to a type known to FFI and check basic properties. Let's start with a snippet of a *consume* layer. + +.. code-block:: lua + + consume = function (state, req, pkt) + print('rcode:', pkt:rcode()) + print('query:', kres.dname2str(pkt:qname()), pkt:qclass(), pkt:qtype()) + if pkt:rcode() ~= kres.rcode.NOERROR then + print('error response') + end + end + +You can enumerate records in the sections. + +.. code-block:: lua + + local records = pkt:section(kres.section.ANSWER) + for i = 1, #records do + local rr = records[i] + if rr.type == kres.type.AAAA then + print(kres.rr2str(rr)) + end + end + +During *produce* or *begin*, you might want to want to write to packet. Keep in mind that you have to write packet sections in sequence, +e.g. you can't write to ANSWER after writing AUTHORITY, it's like stages where you can't go back. + +.. code-block:: lua + + pkt:rcode(kres.rcode.NXDOMAIN) + -- Clear answer and write QUESTION + pkt:recycle() + pkt:question('\7blocked', kres.class.IN, kres.type.SOA) + -- Start writing data + pkt:begin(kres.section.ANSWER) + -- Nothing in answer + pkt:begin(kres.section.AUTHORITY) + local soa = { owner = '\7blocked', ttl = 900, class = kres.class.IN, type = kres.type.SOA, rdata = '...' } + pkt:put(soa.owner, soa.ttl, soa.class, soa.type, soa.rdata) + +Working with requests +--------------------- + +The request holds information about currently processed query, enabled options, cache, and other extra data. +You primarily need to retrieve currently processed query. + +.. code-block:: lua + + consume = function (state, req, pkt) + print(req.options) + print(req.state) + + -- Print information about current query + local current = req:current() + print(kres.dname2str(current.owner)) + print(current.stype, current.sclass, current.id, current.flags) + end + +In layers that either begin or finalize, you can walk the list of resolved queries. + +.. code-block:: lua + + local last = req:resolved() + print(last.stype) + +As described in the layers, you can not only retrieve information about current query, but also push new ones or pop old ones. + +.. code-block:: lua + + -- Push new query + local qry = req:push(pkt:qname(), kres.type.SOA, kres.class.IN) + qry.flags.AWAIT_CUT = true + + -- Pop the query, this will erase it from resolution plan + req:pop(qry) + + +.. _libknot: https://gitlab.nic.cz/knot/knot-dns/tree/master/src/libknot +.. _bindings: https://gitlab.nic.cz/knot/knot-resolver/blob/master/daemon/lua/kres.lua + + +.. _significant-lua-changes: + +Significant Lua API changes +--------------------------- + +Incompatible changes since 3.0.0 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In the main ``kres.*`` lua binding, there was only change in struct knot_rrset_t: + +- constructor now accepts TTL as additional parameter (defaulting to zero) +- add_rdata() doesn't accept TTL anymore (and will throw an error if passed) + +In case you used knot_* functions and structures bound to lua: + +- knot_dname_is_sub(a, b): knot_dname_in_bailiwick(a, b) > 0 +- knot_rdata_rdlen(): knot_rdataset_at().len +- knot_rdata_data(): knot_rdataset_at().data +- knot_rdata_array_size(): offsetof(struct knot_data_t, data) + knot_rdataset_at().len +- struct knot_rdataset: field names were renamed to .count and .rdata +- some functions got inlined from headers, but you can use their kr_* clones: + kr_rrsig_sig_inception(), kr_rrsig_sig_expiration(), kr_rrsig_type_covered(). + Note that these functions now accept knot_rdata_t* instead of a pair + knot_rdataset_t* and size_t - you can use knot_rdataset_at() for that. + +- knot_rrset_add_rdata() doesn't take TTL parameter anymore +- knot_rrset_init_empty() was inlined, but in lua you can use the constructor +- knot_rrset_ttl() was inlined, but in lua you can use :ttl() method instead + +- knot_pkt_qname(), _qtype(), _qclass(), _rr(), _section() were inlined, + but in lua you can use methods instead, e.g. myPacket:qname() +- knot_pkt_free() takes knot_pkt_t* instead of knot_pkt_t**, but from lua + you probably didn't want to use that; constructor ensures garbage collection. + + +.. |---| unicode:: U+02014 .. em dash diff --git a/lib/cache/README.rst b/lib/cache/README.rst new file mode 100644 index 0000000..767c4c0 --- /dev/null +++ b/lib/cache/README.rst @@ -0,0 +1,69 @@ +.. SPDX-License-Identifier: GPL-3.0-or-later + +.. _cache_sizing: + +Cache sizing +------------ + +For personal use-cases and small deployments cache size around 100 MB is more than enough. + +For large deployments we recommend to run Knot Resolver on a dedicated machine, and to allocate 90% of machine's free memory for resolver's cache. + +For example, imagine you have a machine with 16 GB of memory. +After machine restart you use command ``free -m`` to determine amount of free memory (without swap): + +.. code-block:: bash + + $ free -m + total used free + Mem: 15907 979 14928 + +Now you can configure cache size to be 90% of the free memory 14 928 MB, i.e. 13 453 MB: + +.. code-block:: lua + + -- 90 % of free memory after machine restart + cache.size = 13453 * MB + +.. _cache_persistence: + +Cache persistence +----------------- +.. tip:: Using tmpfs for cache improves performance and reduces disk I/O. + +By default the cache is saved on a persistent storage device +so the content of the cache is persisted during system reboot. +This usually leads to smaller latency after restart etc., +however in certain situations a non-persistent cache storage might be preferred, e.g.: + + - Resolver handles high volume of queries and I/O performance to disk is too low. + - Threat model includes attacker getting access to disk content in power-off state. + - Disk has limited number of writes (e.g. flash memory in routers). + +If non-persistent cache is desired configure cache directory to be on +tmpfs_ filesystem, a temporary in-memory file storage. +The cache content will be saved in memory, and thus have faster access +and will be lost on power-off or reboot. + + +.. note:: In most of the Unix-like systems ``/tmp`` and ``/var/run`` are commonly mounted to tmpfs. + While it is technically possible to move the cache to an existing + tmpfs filesystem, it is *not recommended*: The path to cache is specified in + multiple systemd units, and a shared tmpfs space could be used up by other + applications, leading to ``SIGBUS`` errors during runtime. + +Mounting the cache directory as tmpfs_ is recommended approach. +Make sure to use appropriate ``size=`` option and don't forget to adjust the +size in the config file as well. + +.. code-block:: + + # /etc/fstab + tmpfs /var/cache/knot-resolver tmpfs rw,size=2G,uid=knot-resolver,gid=knot-resolver,nosuid,nodev,noexec,mode=0700 0 0 + +.. code-block:: lua + + # /etc/knot-resolver/config + cache.size = 2 * GB + +.. _tmpfs: https://en.wikipedia.org/wiki/Tmpfs diff --git a/lib/cache/api.c b/lib/cache/api.c new file mode 100644 index 0000000..116d775 --- /dev/null +++ b/lib/cache/api.c @@ -0,0 +1,1029 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <errno.h> +#include <limits.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <time.h> +#include <unistd.h> + +#include <libknot/descriptor.h> +#include <libknot/dname.h> +#include <libknot/errcode.h> +#include <libknot/rrtype/rrsig.h> + +#include <uv.h> + +#include "contrib/base32hex.h" +#include "contrib/cleanup.h" +#include "contrib/ucw/lib.h" +#include "lib/cache/api.h" +#include "lib/cache/cdb_lmdb.h" +#include "lib/defines.h" +#include "lib/dnssec/nsec3.h" +#include "lib/generic/trie.h" +#include "lib/resolve.h" +#include "lib/rplan.h" +#include "lib/utils.h" + +#include "lib/cache/impl.h" + +/* TODO: + * - Reconsider when RRSIGs are put in and retrieved from the cache. + * Currently it's always done, which _might_ be spurious, depending + * on how kresd will use the returned result. + * There's also the "problem" that kresd ATM does _not_ ask upstream + * with DO bit in some cases. + */ + + +/** Cache version */ +static const uint16_t CACHE_VERSION = 6; +/** Key size */ +#define KEY_HSIZE (sizeof(uint8_t) + sizeof(uint16_t)) +#define KEY_SIZE (KEY_HSIZE + KNOT_DNAME_MAXLEN) + + +/** @internal Forward declarations of the implementation details + * \param needs_pkt[out] optionally set *needs_pkt = true; + * We do that when some RRset wasn't stashed to aggressive cache, + * even though it might have taken part in a successful DNSSEC proof: + * 1. any opt-out NSEC3, as they typically aren't much use aggressively anyway + * 2. some kinds of minimal NSEC* ranges, as they'd seem more trouble than worth: + * - extremely short range of covered names limits the benefits severely + * - the type-set is often a lie, either a working lie, e.g. CloudFlare's + * black lies, or even a non-working lie, e.g. DVE-2018-0003 + * 3. some kinds of "weird" RRsets, to get at least some caching on them + */ +static ssize_t stash_rrset(struct kr_cache *cache, const struct kr_query *qry, + const knot_rrset_t *rr, const knot_rrset_t *rr_sigs, uint32_t timestamp, + uint8_t rank, trie_t *nsec_pmap, knot_mm_t *pool, bool *needs_pkt); +/** Preliminary checks before stash_rrset(). Don't call if returns <= 0. */ +static int stash_rrset_precond(const knot_rrset_t *rr, const struct kr_query *qry/*logs*/); + +/** @internal Ensure the cache version is right, possibly by clearing it. */ +static int assert_right_version(struct kr_cache *cache) +{ + /* Check cache ABI version. */ + /* CACHE_KEY_DEF: to avoid collisions with kr_cache_match(). */ + uint8_t key_str[4] = "VERS"; + knot_db_val_t key = { .data = key_str, .len = sizeof(key_str) }; + knot_db_val_t val = { NULL, 0 }; + int ret = cache_op(cache, read, &key, &val, 1); + if (ret == 0 && val.len == sizeof(CACHE_VERSION) + && memcmp(val.data, &CACHE_VERSION, sizeof(CACHE_VERSION)) == 0) { + ret = kr_ok(); + } else { + int oldret = ret; + /* Version doesn't match or we were unable to read it, possibly because DB is empty. + * Recreate cache and write version key. */ + ret = cache_op(cache, count); + if (ret != 0) { /* Log for non-empty cache to limit noise on fresh start. */ + kr_log_info(CACHE, "incompatible cache database detected, purging\n"); + if (oldret) { + kr_log_debug(CACHE, "reading version returned: %d\n", oldret); + } else if (val.len != sizeof(CACHE_VERSION)) { + kr_log_debug(CACHE, "version has bad length: %d\n", (int)val.len); + } else { + uint16_t ver; + memcpy(&ver, val.data, sizeof(ver)); + kr_log_debug(CACHE, "version has bad value: %d instead of %d\n", + (int)ver, (int)CACHE_VERSION); + } + } + ret = cache_op(cache, clear); + } + /* Rewrite the entry even if it isn't needed. Because of cache-size-changing + * possibility it's good to always perform some write during opening of cache. */ + if (ret == 0) { + /* Key/Val is invalidated by cache purge, recreate it */ + val.data = /*const-cast*/(void *)&CACHE_VERSION; + val.len = sizeof(CACHE_VERSION); + ret = cache_op(cache, write, &key, &val, 1); + } + kr_cache_commit(cache); + return ret; +} + +int kr_cache_open(struct kr_cache *cache, const struct kr_cdb_api *api, struct kr_cdb_opts *opts, knot_mm_t *mm) +{ + if (kr_fails_assert(cache)) + return kr_error(EINVAL); + memset(cache, 0, sizeof(*cache)); + /* Open cache */ + if (!api) + api = kr_cdb_lmdb(); + cache->api = api; + int ret = cache->api->open(&cache->db, &cache->stats, opts, mm); + if (ret == 0) { + ret = assert_right_version(cache); + // The included write also committed maxsize increase to the file. + } + if (ret == 0 && opts->maxsize) { + /* If some maxsize is requested and it's smaller than in-file maxsize, + * LMDB only restricts our env without changing the in-file maxsize. + * That is worked around by reopening (found no other reliable way). */ + cache->api->close(cache->db, &cache->stats); + struct kr_cdb_opts opts2; + memcpy(&opts2, opts, sizeof(opts2)); + opts2.maxsize = 0; + ret = cache->api->open(&cache->db, &cache->stats, &opts2, mm); + } + + char *fpath = kr_absolutize_path(opts->path, "data.mdb"); + if (kr_fails_assert(fpath)) { + /* non-critical, but still */ + fpath = "<ENOMEM>"; + } else { + kr_cache_emergency_file_to_remove = fpath; + } + + if (ret == 0 && opts->maxsize) { + size_t maxsize = cache->api->get_maxsize(cache->db); + if (maxsize > opts->maxsize) kr_log_warning(CACHE, + "Warning: real cache size is %zu instead of the requested %zu bytes." + " To reduce the size you need to remove the file '%s' by hand.\n", + maxsize, opts->maxsize, fpath); + } + if (ret != 0) + return ret; + cache->ttl_min = KR_CACHE_DEFAULT_TTL_MIN; + cache->ttl_max = KR_CACHE_DEFAULT_TTL_MAX; + kr_cache_make_checkpoint(cache); + return 0; +} + +const char *kr_cache_emergency_file_to_remove = NULL; + + +#define cache_isvalid(cache) ((cache) && (cache)->api && (cache)->db) + +void kr_cache_close(struct kr_cache *cache) +{ + kr_cache_check_health(cache, -1); + if (cache_isvalid(cache)) { + cache_op(cache, close); + cache->db = NULL; + } + free(/*const-cast*/(char*)kr_cache_emergency_file_to_remove); + kr_cache_emergency_file_to_remove = NULL; +} + +int kr_cache_commit(struct kr_cache *cache) +{ + if (!cache_isvalid(cache)) { + return kr_error(EINVAL); + } + if (cache->api->commit) { + return cache_op(cache, commit); + } + return kr_ok(); +} + +int kr_cache_clear(struct kr_cache *cache) +{ + if (!cache_isvalid(cache)) { + return kr_error(EINVAL); + } + int ret = cache_op(cache, clear); + if (ret == 0) { + kr_cache_make_checkpoint(cache); + ret = assert_right_version(cache); + } + return ret; +} + +/* When going stricter, BEWARE of breaking entry_h_consistent_NSEC() */ +struct entry_h * entry_h_consistent_E(knot_db_val_t data, uint16_t type) +{ + (void) type; /* unused, for now */ + if (!data.data) return NULL; + /* Length checks. */ + if (data.len < offsetof(struct entry_h, data)) + return NULL; + const struct entry_h *eh = data.data; + if (eh->is_packet) { + uint16_t pkt_len; + if (data.len < offsetof(struct entry_h, data) + sizeof(pkt_len)) { + return NULL; + } + memcpy(&pkt_len, eh->data, sizeof(pkt_len)); + if (data.len < offsetof(struct entry_h, data) + sizeof(pkt_len) + + pkt_len) { + return NULL; + } + } + + bool ok = true; + ok = ok && kr_rank_check(eh->rank); + ok = ok && (!kr_rank_test(eh->rank, KR_RANK_BOGUS) + || eh->is_packet); + ok = ok && (eh->is_packet || !eh->has_optout); + + return ok ? /*const-cast*/(struct entry_h *)eh : NULL; +} + +int32_t get_new_ttl(const struct entry_h *entry, const struct kr_query *qry, + const knot_dname_t *owner, uint16_t type, uint32_t now) +{ + int32_t diff = now - entry->time; + if (diff < 0) { + /* We may have obtained the record *after* the request started. */ + diff = 0; + } + int32_t res = entry->ttl - diff; + if (res < 0 && owner && qry && qry->stale_cb) { + /* Stale-serving decision, delegated to a callback. */ + int res_stale = qry->stale_cb(res, owner, type, qry); + if (res_stale >= 0) { + VERBOSE_MSG(qry, "responding with stale answer\n"); + /* LATER: Perhaps we could use a more specific Stale + * NXDOMAIN Answer code for applicable responses. */ + kr_request_set_extended_error(qry->request, KNOT_EDNS_EDE_STALE, "6Q6X"); + return res_stale; + } + } + return res; +} + +int32_t kr_cache_ttl(const struct kr_cache_p *peek, const struct kr_query *qry, + const knot_dname_t *name, uint16_t type) +{ + const struct entry_h *eh = peek->raw_data; + return get_new_ttl(eh, qry, name, type, qry->timestamp.tv_sec); +} + +/** Check that no label contains a zero character, incl. a log trace. + * + * We refuse to work with those, as LF and our cache keys might become ambiguous. + * Assuming uncompressed name, as usual. + * CACHE_KEY_DEF + */ +static bool check_dname_for_lf(const knot_dname_t *n, const struct kr_query *qry/*logging*/) +{ + const bool ret = knot_dname_size(n) == strlen((const char *)n) + 1; + if (!ret && kr_log_is_debug_qry(CACHE, qry)) { + auto_free char *n_str = kr_dname_text(n); + VERBOSE_MSG(qry, "=> skipping zero-containing name %s\n", n_str); + } + return ret; +} + +/** Return false on types to be ignored. Meant both for sname and direct cache requests. */ +static bool check_rrtype(uint16_t type, const struct kr_query *qry/*logging*/) +{ + const bool ret = !knot_rrtype_is_metatype(type) + && type != KNOT_RRTYPE_RRSIG; + if (!ret && kr_log_is_debug_qry(CACHE, qry)) { + auto_free char *type_str = kr_rrtype_text(type); + VERBOSE_MSG(qry, "=> skipping RR type %s\n", type_str); + } + return ret; +} + +/** Like key_exact_type() but omits a couple checks not holding for pkt cache. */ +knot_db_val_t key_exact_type_maypkt(struct key *k, uint16_t type) +{ + if (kr_fails_assert(check_rrtype(type, NULL))) + return (knot_db_val_t){ NULL, 0 }; + switch (type) { + case KNOT_RRTYPE_RRSIG: /* no RRSIG query caching, at least for now */ + kr_assert(false); + return (knot_db_val_t){ NULL, 0 }; + /* xNAME lumped into NS. */ + case KNOT_RRTYPE_CNAME: + case KNOT_RRTYPE_DNAME: + type = KNOT_RRTYPE_NS; + default: + break; + } + + int name_len = k->buf[0]; + k->buf[name_len + 1] = 0; /* make sure different names can never match */ + k->buf[name_len + 2] = 'E'; /* tag for exact name+type matches */ + memcpy(k->buf + name_len + 3, &type, 2); + k->type = type; + /* CACHE_KEY_DEF: key == dname_lf + '\0' + 'E' + RRTYPE */ + return (knot_db_val_t){ k->buf + 1, name_len + 4 }; +} + + +/** The inside for cache_peek(); implementation separated to ./peek.c */ +int peek_nosync(kr_layer_t *ctx, knot_pkt_t *pkt); +/** function for .produce phase */ +int cache_peek(kr_layer_t *ctx, knot_pkt_t *pkt) +{ + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + /* We first check various exit-conditions and then call the _real function. */ + + if (!kr_cache_is_open(&req->ctx->cache) + || ctx->state & (KR_STATE_FAIL|KR_STATE_DONE) || qry->flags.NO_CACHE + || (qry->flags.CACHE_TRIED && !qry->stale_cb) + || !check_rrtype(qry->stype, qry) /* LATER: some other behavior for some of these? */ + || qry->sclass != KNOT_CLASS_IN) { + return ctx->state; /* Already resolved/failed or already tried, etc. */ + } + /* ATM cache only peeks for qry->sname and that would be useless + * to repeat on every iteration, so disable it from now on. + * LATER(optim.): assist with more precise QNAME minimization. */ + qry->flags.CACHE_TRIED = true; + + if (qry->stype == KNOT_RRTYPE_NSEC) { + VERBOSE_MSG(qry, "=> skipping stype NSEC\n"); + return ctx->state; + } + if (!check_dname_for_lf(qry->sname, qry)) { + return ctx->state; + } + + int ret = peek_nosync(ctx, pkt); + kr_cache_commit(&req->ctx->cache); + return ret; +} + + + +/** It's simply inside of cycle taken out to decrease indentation. \return error code. */ +static int stash_rrarray_entry(ranked_rr_array_t *arr, int arr_i, + const struct kr_query *qry, struct kr_cache *cache, + int *unauth_cnt, trie_t *nsec_pmap, bool *needs_pkt); +/** Stash a single nsec_p. \return 0 (errors are ignored). */ +static int stash_nsec_p(const knot_dname_t *dname, const char *nsec_p_v, + struct kr_cache *cache, uint32_t timestamp, knot_mm_t *pool, + const struct kr_query *qry/*logging*/); + +/** The whole .consume phase for the cache module. */ +int cache_stash(kr_layer_t *ctx, knot_pkt_t *pkt) +{ + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + struct kr_cache *cache = &req->ctx->cache; + + /* Note: we cache even in KR_STATE_FAIL. For example, + * BOGUS answer can go to +cd cache even without +cd request. */ + if (!kr_cache_is_open(cache) || !qry + || qry->flags.CACHED || !check_rrtype(knot_pkt_qtype(pkt), qry) + || qry->sclass != KNOT_CLASS_IN) { + return ctx->state; + } + /* Do not cache truncated answers, at least for now. LATER */ + if (knot_wire_get_tc(pkt->wire)) { + return ctx->state; + } + int unauth_cnt = 0; + bool needs_pkt = false; + if (qry->flags.STUB) { + needs_pkt = true; + goto stash_packet; + } + + /* Stash individual records. */ + ranked_rr_array_t *selected[] = kr_request_selected(req); + trie_t *nsec_pmap = trie_create(&req->pool); + if (kr_fails_assert(nsec_pmap)) + goto finally; + for (int psec = KNOT_ANSWER; psec <= KNOT_ADDITIONAL; ++psec) { + ranked_rr_array_t *arr = selected[psec]; + /* uncached entries are located at the end */ + for (ssize_t i = arr->len - 1; i >= 0; --i) { + ranked_rr_array_entry_t *entry = arr->at[i]; + if (entry->qry_uid != qry->uid || entry->dont_cache) { + continue; + /* TODO: probably safe to break on uid mismatch but maybe not worth it */ + } + int ret = stash_rrarray_entry( + arr, i, qry, cache, &unauth_cnt, nsec_pmap, + /* ADDITIONAL RRs are considered non-essential + * in our (resolver) answers */ + (psec == KNOT_ADDITIONAL ? NULL : &needs_pkt)); + if (ret) { + VERBOSE_MSG(qry, "=> stashing RRs errored out\n"); + goto finally; + } + /* LATER(optim.): maybe filter out some type-rank combinations + * that won't be useful as separate RRsets. */ + } + } + + trie_it_t *it; + for (it = trie_it_begin(nsec_pmap); !trie_it_finished(it); trie_it_next(it)) { + stash_nsec_p((const knot_dname_t *)trie_it_key(it, NULL), + (const char *)*trie_it_val(it), + cache, qry->timestamp.tv_sec, &req->pool, req->current_query); + } + trie_it_free(it); + /* LATER(optim.): typically we also have corresponding NS record in the list, + * so we might save a cache operation. */ +stash_packet: + if (qry->flags.PKT_IS_SANE && check_dname_for_lf(knot_pkt_qname(pkt), qry)) { + stash_pkt(pkt, qry, req, needs_pkt); + } + +finally: + if (unauth_cnt) { + VERBOSE_MSG(qry, "=> stashed also %d nonauth RRsets\n", unauth_cnt); + }; + kr_cache_commit(cache); + return ctx->state; /* we ignore cache-stashing errors */ +} + +/** Preliminary checks before stash_rrset(). Don't call if returns <= 0. */ +static int stash_rrset_precond(const knot_rrset_t *rr, const struct kr_query *qry/*logs*/) +{ + if (kr_fails_assert(rr && rr->rclass == KNOT_CLASS_IN)) + return kr_error(EINVAL); + if (!check_rrtype(rr->type, qry)) + return kr_ok(); + if (!check_dname_for_lf(rr->owner, qry)) + return kr_ok(); + return 1/*proceed*/; +} + +/** Return true on some cases of NSEC* RRsets covering minimal ranges. + * Also include some abnormal RR cases; qry is just for logging. */ +static bool rrset_has_min_range_or_weird(const knot_rrset_t *rr, const struct kr_query *qry) +{ + if (rr->rrs.count != 1) { + kr_assert(rr->rrs.count > 0); + if (rr->type == KNOT_RRTYPE_NSEC || rr->type == KNOT_RRTYPE_NSEC3 + || rr->rrs.count == 0) { + return true; /*< weird */ + } + } + bool ret; /**< NOT used for the weird cases */ + if (rr->type == KNOT_RRTYPE_NSEC) { + if (!check_dname_for_lf(rr->owner, qry)) + return true; /*< weird, probably filtered even before this point */ + ret = !check_dname_for_lf(knot_nsec_next(rr->rrs.rdata), qry); + /* ^^ Zero inside the next-name label means it's probably a minimal range, + * and anyway it's problematic for our aggressive cache (comparisons). + * Real-life examples covered: + * NSEC: name -> \000.name (e.g. typical foobar.CloudFlare.net) + * NSEC: name -> name\000 (CloudFlare on delegations) + */ + } else if (rr->type == KNOT_RRTYPE_NSEC3) { + if (knot_nsec3_next_len(rr->rrs.rdata) != NSEC3_HASH_LEN + || *rr->owner != NSEC3_HASH_TXT_LEN) { + return true; /*< weird */ + } + /* Let's work on the binary hashes. Find if they "differ by one", + * by constructing the owner hash incremented by one and comparing. */ + uint8_t owner_hash[NSEC3_HASH_LEN]; + if (base32hex_decode(rr->owner + 1, NSEC3_HASH_TXT_LEN, + owner_hash, NSEC3_HASH_LEN) != NSEC3_HASH_LEN) { + return true; /*< weird */ + } + for (int i = NSEC3_HASH_LEN - 1; i >= 0; --i) { + if (++owner_hash[i] != 0) break; + } + const uint8_t *next_hash = knot_nsec3_next(rr->rrs.rdata); + ret = memcmp(owner_hash, next_hash, NSEC3_HASH_LEN) == 0; + } else { + return false; + } + if (ret) VERBOSE_MSG(qry, "=> minimized NSEC* range detected\n"); + return ret; +} + +static ssize_t stash_rrset(struct kr_cache *cache, const struct kr_query *qry, + const knot_rrset_t *rr, const knot_rrset_t *rr_sigs, uint32_t timestamp, + uint8_t rank, trie_t *nsec_pmap, knot_mm_t *pool, bool *needs_pkt) +{ + if (kr_rank_test(rank, KR_RANK_BOGUS)) { + WITH_VERBOSE(qry) { + auto_free char *type_str = kr_rrtype_text(rr->type); + VERBOSE_MSG(qry, "=> skipping bogus RR set %s\n", type_str); + } + return kr_ok(); + } + if (rr->type == KNOT_RRTYPE_NSEC3 && rr->rrs.count + && knot_nsec3_iters(rr->rrs.rdata) > KR_NSEC3_MAX_ITERATIONS) { + /* This shouldn't happen often, thanks to downgrades during validation. */ + VERBOSE_MSG(qry, "=> skipping NSEC3 with too many iterations\n"); + return kr_ok(); + } + if (kr_fails_assert(cache && stash_rrset_precond(rr, qry) > 0)) + return kr_error(EINVAL); + + int ret = kr_ok(); + if (rrset_has_min_range_or_weird(rr, qry)) + goto return_needs_pkt; + const int wild_labels = rr_sigs == NULL ? 0 : + knot_dname_labels(rr->owner, NULL) - knot_rrsig_labels(rr_sigs->rrs.rdata); + if (wild_labels < 0) + goto return_needs_pkt; + const knot_dname_t *encloser = rr->owner; /**< the closest encloser name */ + for (int i = 0; i < wild_labels; ++i) { + encloser = knot_wire_next_label(encloser, NULL); + } + + /* Construct the key under which RRs will be stored, + * and add corresponding nsec_pmap item (if necessary). */ + struct key k_storage, *k = &k_storage; + knot_db_val_t key; + switch (rr->type) { + case KNOT_RRTYPE_NSEC3: + /* Skip opt-out NSEC3 sets. */ + if (KNOT_NSEC3_FLAG_OPT_OUT & knot_nsec3_flags(rr->rrs.rdata)) + goto return_needs_pkt; + /* fall through */ + case KNOT_RRTYPE_NSEC: + /* Skip any NSEC*s that aren't validated or are suspicious. */ + if (!kr_rank_test(rank, KR_RANK_SECURE) || rr->rrs.count != 1) + goto return_needs_pkt; + if (kr_fails_assert(rr_sigs && rr_sigs->rrs.count && rr_sigs->rrs.rdata)) { + ret = kr_error(EINVAL); + goto return_needs_pkt; + } + const knot_dname_t *signer = knot_rrsig_signer_name(rr_sigs->rrs.rdata); + const int signer_size = knot_dname_size(signer); + k->zlf_len = signer_size - 1; + + void **npp = NULL; + if (nsec_pmap) { + npp = trie_get_ins(nsec_pmap, (const char *)signer, signer_size); + if (kr_fails_assert(npp)) + return kr_error(ENOMEM); + } + if (rr->type == KNOT_RRTYPE_NSEC) { + key = key_NSEC1(k, encloser, wild_labels); + break; + } + + kr_require(rr->type == KNOT_RRTYPE_NSEC3); + const knot_rdata_t * const rdata = rr->rrs.rdata; + if (rdata->len <= 4) { + ret = kr_error(EILSEQ); /*< data from outside; less trust */ + goto return_needs_pkt; + } + const int np_dlen = nsec_p_rdlen(rdata->data); + if (np_dlen > rdata->len) { + ret = kr_error(EILSEQ); + goto return_needs_pkt; + } + key = key_NSEC3(k, encloser, nsec_p_mkHash(rdata->data)); + if (npp && !*npp) { + *npp = mm_alloc(pool, np_dlen); + if (kr_fails_assert(*npp)) + break; + memcpy(*npp, rdata->data, np_dlen); + } + break; + default: + ret = kr_dname_lf(k->buf, encloser, wild_labels); + if (kr_fails_assert(ret == 0)) + goto return_needs_pkt; + key = key_exact_type(k, rr->type); + } + + /* Compute in-cache size for the new data. */ + const knot_rdataset_t *rds_sigs = rr_sigs ? &rr_sigs->rrs : NULL; + const int rr_ssize = rdataset_dematerialize_size(&rr->rrs); + if (kr_fails_assert(rr_ssize == to_even(rr_ssize))) + return kr_error(EINVAL); + knot_db_val_t val_new_entry = { + .data = NULL, + .len = offsetof(struct entry_h, data) + rr_ssize + + rdataset_dematerialize_size(rds_sigs), + }; + + /* Prepare raw memory for the new entry. */ + ret = entry_h_splice(&val_new_entry, rank, key, k->type, rr->type, + rr->owner, qry, cache, timestamp); + if (ret) return kr_ok(); /* some aren't really errors */ + if (kr_fails_assert(val_new_entry.data)) + return kr_error(EFAULT); + + /* Write the entry itself. */ + struct entry_h *eh = val_new_entry.data; + memset(eh, 0, offsetof(struct entry_h, data)); + eh->time = timestamp; + eh->ttl = rr->ttl; + eh->rank = rank; + rdataset_dematerialize(&rr->rrs, eh->data); + rdataset_dematerialize(rds_sigs, eh->data + rr_ssize); + if (kr_fails_assert(entry_h_consistent_E(val_new_entry, rr->type))) + return kr_error(EINVAL); + + #if 0 /* Occasionally useful when debugging some kinds of changes. */ + { + kr_cache_commit(cache); + knot_db_val_t val = { NULL, 0 }; + ret = cache_op(cache, read, &key, &val, 1); + if (ret != kr_error(ENOENT)) { // ENOENT might happen in some edge case, I guess + kr_assert(!ret); + entry_list_t el; + entry_list_parse(val, el); + } + } + #endif + + /* Verbose-log some not-too-common cases. */ + WITH_VERBOSE(qry) { if (kr_rank_test(rank, KR_RANK_AUTH) + || rr->type == KNOT_RRTYPE_NS) { + auto_free char *type_str = kr_rrtype_text(rr->type), + *encl_str = kr_dname_text(encloser); + VERBOSE_MSG(qry, "=> stashed %s%s %s, rank 0%.2o, " + "%d B total, incl. %d RRSIGs\n", + (wild_labels ? "*." : ""), encl_str, type_str, rank, + (int)val_new_entry.len, (rr_sigs ? rr_sigs->rrs.count : 0) + ); + } } + + return (ssize_t) val_new_entry.len; +return_needs_pkt: + if (needs_pkt) *needs_pkt = true; + return ret; +} + +static int stash_rrarray_entry(ranked_rr_array_t *arr, int arr_i, + const struct kr_query *qry, struct kr_cache *cache, + int *unauth_cnt, trie_t *nsec_pmap, bool *needs_pkt) +{ + ranked_rr_array_entry_t *entry = arr->at[arr_i]; + if (entry->cached) { + return kr_ok(); + } + const knot_rrset_t *rr = entry->rr; + if (rr->type == KNOT_RRTYPE_RRSIG) { + return kr_ok(); /* reduce verbose logging from the following call */ + } + int ret = stash_rrset_precond(rr, qry); + if (ret <= 0) { + return ret; + } + + /* Try to find corresponding signatures, always. LATER(optim.): speed. */ + ranked_rr_array_entry_t *entry_rrsigs = NULL; + const knot_rrset_t *rr_sigs = NULL; + for (ssize_t j = arr->len - 1; j >= 0; --j) { + /* TODO: ATM we assume that some properties are the same + * for all RRSIGs in the set (esp. label count). */ + ranked_rr_array_entry_t *e = arr->at[j]; + if (kr_fails_assert(!e->in_progress)) + return kr_error(EINVAL); + bool ok = e->qry_uid == qry->uid && !e->cached + && e->rr->type == KNOT_RRTYPE_RRSIG + && knot_rrsig_type_covered(e->rr->rrs.rdata) == rr->type + && knot_dname_is_equal(rr->owner, e->rr->owner); + if (!ok) continue; + entry_rrsigs = e; + rr_sigs = e->rr; + break; + } + + ssize_t written = stash_rrset(cache, qry, rr, rr_sigs, qry->timestamp.tv_sec, + entry->rank, nsec_pmap, &qry->request->pool, needs_pkt); + if (written < 0) { + kr_log_error(CACHE, "[%05u.%02u] stash failed, ret = %d\n", qry->request->uid, + qry->uid, ret); + return (int) written; + } + + if (written > 0) { + /* Mark entry as cached for the rest of the query processing */ + entry->cached = true; + if (entry_rrsigs) { + entry_rrsigs->cached = true; + } + if (!kr_rank_test(entry->rank, KR_RANK_AUTH) && rr->type != KNOT_RRTYPE_NS) { + *unauth_cnt += 1; + } + } + + return kr_ok(); +} + +static int stash_nsec_p(const knot_dname_t *dname, const char *nsec_p_v, + struct kr_cache *cache, uint32_t timestamp, knot_mm_t *pool, + const struct kr_query *qry/*logging*/) +{ + uint32_t valid_until = timestamp + cache->ttl_max; + /* LATER(optim.): be more precise here ^^ and reduce calls. */ + static const int32_t ttl_margin = 3600; + const uint8_t *nsec_p = (const uint8_t *)nsec_p_v; + int data_stride = sizeof(valid_until) + nsec_p_rdlen(nsec_p); + + unsigned int log_hash = 0xFeeeFeee; /* this type is simpler for printf args */ + auto_free char *log_dname = NULL; + WITH_VERBOSE(qry) { + log_hash = nsec_p_v ? nsec_p_mkHash((const uint8_t *)nsec_p_v) : 0; + log_dname = kr_dname_text(dname); + } + /* Find what's in the cache. */ + struct key k_storage, *k = &k_storage; + int ret = kr_dname_lf(k->buf, dname, false); + if (ret) return kr_error(ret); + knot_db_val_t key = key_exact_type(k, KNOT_RRTYPE_NS); + knot_db_val_t val_orig = { NULL, 0 }; + ret = cache_op(cache, read, &key, &val_orig, 1); + if (ret && ret != -ABS(ENOENT)) { + VERBOSE_MSG(qry, "=> EL read failed (ret: %d)\n", ret); + return kr_ok(); + } + /* Prepare new entry_list_t so we can just write at el[0]. */ + entry_list_t el; + int log_refresh_by = 0; + if (ret == -ABS(ENOENT)) { + memset(el, 0, sizeof(el)); + } else { + ret = entry_list_parse(val_orig, el); + if (ret) { + VERBOSE_MSG(qry, "=> EL parse failed (ret: %d)\n", ret); + return kr_error(0); + } + /* Find the index to replace. */ + int i_replace = ENTRY_APEX_NSECS_CNT - 1; + for (int i = 0; i < ENTRY_APEX_NSECS_CNT; ++i) { + if (el[i].len != data_stride) continue; + if (nsec_p && memcmp(nsec_p, (uint8_t *)el[i].data + sizeof(uint32_t), + data_stride - sizeof(uint32_t)) != 0) { + continue; + } + /* Save a cache operation if TTL extended only a little. */ + uint32_t valid_orig; + memcpy(&valid_orig, el[i].data, sizeof(valid_orig)); + const int32_t ttl_extended_by = valid_until - valid_orig; + if (ttl_extended_by < ttl_margin) { + VERBOSE_MSG(qry, + "=> nsec_p stash for %s skipped (extra TTL: %d, hash: %x)\n", + log_dname, ttl_extended_by, log_hash); + return kr_ok(); + } + i_replace = i; + log_refresh_by = ttl_extended_by; + break; + } + /* Shift the other indices: move the first `i_replace` blocks + * by one position. */ + if (i_replace) { + memmove(&el[1], &el[0], sizeof(el[0]) * i_replace); + } + } + /* Prepare old data into a buffer. See entry_h_splice() for why. LATER(optim.) */ + el[0].len = data_stride; + el[0].data = NULL; + knot_db_val_t val; + val.len = entry_list_serial_size(el), + val.data = mm_alloc(pool, val.len), + entry_list_memcpy(val.data, el); + /* Prepare the new data chunk */ + memcpy(el[0].data, &valid_until, sizeof(valid_until)); + if (nsec_p) { + memcpy((uint8_t *)el[0].data + sizeof(valid_until), nsec_p, + data_stride - sizeof(valid_until)); + } + /* Write it all to the cache */ + ret = cache_op(cache, write, &key, &val, 1); + mm_free(pool, val.data); + if (ret || !val.data) { + VERBOSE_MSG(qry, "=> EL write failed (ret: %d)\n", ret); + return kr_ok(); + } + if (log_refresh_by) { + VERBOSE_MSG(qry, "=> nsec_p stashed for %s (refresh by %d, hash: %x)\n", + log_dname, log_refresh_by, log_hash); + } else { + VERBOSE_MSG(qry, "=> nsec_p stashed for %s (new, hash: %x)\n", + log_dname, log_hash); + } + return kr_ok(); +} + +int kr_cache_insert_rr(struct kr_cache *cache, + const knot_rrset_t *rr, const knot_rrset_t *rrsig, + uint8_t rank, uint32_t timestamp, bool ins_nsec_p) +{ + int err = stash_rrset_precond(rr, NULL); + if (err <= 0) { + return kr_ok(); + } + + trie_t *nsec_pmap = NULL; + knot_mm_t *pool = NULL; + if (ins_nsec_p && (rr->type == KNOT_RRTYPE_NSEC || rr->type == KNOT_RRTYPE_NSEC3)) { + pool = mm_ctx_mempool2(4096); + nsec_pmap = trie_create(pool); + kr_assert(pool && nsec_pmap); + } + + ssize_t written = stash_rrset(cache, NULL, rr, rrsig, timestamp, rank, + nsec_pmap, pool, NULL); + + if (nsec_pmap) { + trie_it_t *it; + for (it = trie_it_begin(nsec_pmap); !trie_it_finished(it); trie_it_next(it)) { + stash_nsec_p((const knot_dname_t *)trie_it_key(it, NULL), + (const char *)*trie_it_val(it), + cache, timestamp, pool, NULL); + } + trie_it_free(it); + mm_ctx_delete(pool); + } + + if (written >= 0) { + return kr_ok(); + } + + return (int) written; +} + +static int peek_exact_real(struct kr_cache *cache, const knot_dname_t *name, uint16_t type, + struct kr_cache_p *peek) +{ + if (!check_rrtype(type, NULL) || !check_dname_for_lf(name, NULL)) { + return kr_error(ENOTSUP); + } + struct key k_storage, *k = &k_storage; + + int ret = kr_dname_lf(k->buf, name, false); + if (ret) return kr_error(ret); + + knot_db_val_t key = key_exact_type(k, type); + knot_db_val_t val = { NULL, 0 }; + ret = cache_op(cache, read, &key, &val, 1); + if (!ret) ret = entry_h_seek(&val, type); + if (ret) return kr_error(ret); + + const struct entry_h *eh = entry_h_consistent_E(val, type); + if (!eh || eh->is_packet) { + // TODO: no packets, but better get rid of whole kr_cache_peek_exact(). + return kr_error(ENOENT); + } + *peek = (struct kr_cache_p){ + .time = eh->time, + .ttl = eh->ttl, + .rank = eh->rank, + .raw_data = val.data, + .raw_bound = knot_db_val_bound(val), + }; + return kr_ok(); +} +int kr_cache_peek_exact(struct kr_cache *cache, const knot_dname_t *name, uint16_t type, + struct kr_cache_p *peek) +{ /* Just wrap with extra verbose logging. */ + const int ret = peek_exact_real(cache, name, type, peek); + if (false && kr_log_is_debug(CACHE, NULL)) { /* too noisy for usual --verbose */ + auto_free char *type_str = kr_rrtype_text(type), + *name_str = kr_dname_text(name); + const char *result_str = (ret == kr_ok() ? "hit" : + (ret == kr_error(ENOENT) ? "miss" : "error")); + VERBOSE_MSG(NULL, "_peek_exact: %s %s %s (ret: %d)", + type_str, name_str, result_str, ret); + } + return ret; +} + +int kr_cache_remove(struct kr_cache *cache, const knot_dname_t *name, uint16_t type) +{ + if (!cache_isvalid(cache)) { + return kr_error(EINVAL); + } + if (!cache->api->remove) { + return kr_error(ENOSYS); + } + struct key k_storage, *k = &k_storage; + int ret = kr_dname_lf(k->buf, name, false); + if (ret) return kr_error(ret); + + knot_db_val_t key = key_exact_type(k, type); + return cache_op(cache, remove, &key, 1); +} + +int kr_cache_match(struct kr_cache *cache, const knot_dname_t *name, + bool exact_name, knot_db_val_t keyval[][2], int maxcount) +{ + if (!cache_isvalid(cache)) { + return kr_error(EINVAL); + } + if (!cache->api->match) { + return kr_error(ENOSYS); + } + + struct key k_storage, *k = &k_storage; + + int ret = kr_dname_lf(k->buf, name, false); + if (ret) return kr_error(ret); + + // use a mock type + knot_db_val_t key = key_exact_type(k, KNOT_RRTYPE_A); + /* CACHE_KEY_DEF */ + key.len -= sizeof(uint16_t); /* the type */ + if (!exact_name) { + key.len -= 2; /* '\0' 'E' */ + if (name[0] == '\0') ++key.len; /* the root name is special ATM */ + } + return cache_op(cache, match, &key, keyval, maxcount); +} + +int kr_unpack_cache_key(knot_db_val_t key, knot_dname_t *buf, uint16_t *type) +{ + if (key.data == NULL || buf == NULL || type == NULL) { + return kr_error(EINVAL); + } + + int len = -1; + const char *tag, *key_data = key.data; + for (tag = key_data + 1; tag < key_data + key.len; ++tag) { + /* CACHE_KEY_DEF */ + if (tag[-1] == '\0' && (tag == key_data + 1 || tag[-2] == '\0')) { + if (tag[0] != 'E') return kr_error(EINVAL); + len = tag - 1 - key_data; + break; + } + } + + if (len == -1 || len > KNOT_DNAME_MAXLEN) { + return kr_error(EINVAL); + } + + int ret = knot_dname_lf2wire(buf, len, key.data); + if (ret < 0) { + return kr_error(ret); + } + + /* CACHE_KEY_DEF: jump over "\0 E/1" */ + memcpy(type, tag + 1, sizeof(uint16_t)); + + return kr_ok(); +} + + +int kr_cache_remove_subtree(struct kr_cache *cache, const knot_dname_t *name, + bool exact_name, int maxcount) +{ + if (!cache_isvalid(cache)) { + return kr_error(EINVAL); + } + + knot_db_val_t keyval[maxcount][2], keys[maxcount]; + int ret = kr_cache_match(cache, name, exact_name, keyval, maxcount); + if (ret <= 0) { /* ENOENT -> nothing to remove */ + return (ret == KNOT_ENOENT) ? 0 : ret; + } + const int count = ret; + /* Duplicate the key strings, as deletion may invalidate the pointers. */ + int i; + for (i = 0; i < count; ++i) { + keys[i].len = keyval[i][0].len; + keys[i].data = malloc(keys[i].len); + if (!keys[i].data) { + ret = kr_error(ENOMEM); + goto cleanup; + } + memcpy(keys[i].data, keyval[i][0].data, keys[i].len); + } + ret = cache_op(cache, remove, keys, count); +cleanup: + kr_cache_commit(cache); /* Sync even after just kr_cache_match(). */ + /* Free keys */ + while (--i >= 0) { + free(keys[i].data); + } + return ret; +} + +static void health_timer_cb(uv_timer_t *health_timer) +{ + struct kr_cache *cache = health_timer->data; + if (cache) + cache_op(cache, check_health); + /* We don't do anything with the return code. For example, in some situations + * the file may not exist (temporarily), and we just expect to be more lucky + * when the timer fires again. */ +} + +int kr_cache_check_health(struct kr_cache *cache, int interval) +{ + if (interval == 0) + return cache_op(cache, check_health); + if (interval < 0) { + if (!cache->health_timer) + return kr_ok(); // tolerate stopping a "stopped" timer + uv_close((uv_handle_t *)cache->health_timer, (uv_close_cb)free); + cache->health_timer->data = NULL; + cache->health_timer = NULL; + return kr_ok(); + } + + if (!cache->health_timer) { + /* We avoid depending on daemon's symbols by using uv_default_loop. */ + cache->health_timer = malloc(sizeof(*cache->health_timer)); + if (!cache->health_timer) return kr_error(ENOMEM); + uv_loop_t *loop = uv_default_loop(); + kr_require(loop); + int ret = uv_timer_init(loop, cache->health_timer); + if (ret) { + free(cache->health_timer); + cache->health_timer = NULL; + return kr_error(ret); + } + cache->health_timer->data = cache; + } + kr_assert(cache->health_timer->data); + return kr_error(uv_timer_start(cache->health_timer, health_timer_cb, interval, interval)); +} + diff --git a/lib/cache/api.h b/lib/cache/api.h new file mode 100644 index 0000000..0abe920 --- /dev/null +++ b/lib/cache/api.h @@ -0,0 +1,194 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <libknot/consts.h> +#include <libknot/rrset.h> +#include <sys/time.h> +#include "lib/cache/cdb_api.h" +#include "lib/defines.h" +#include "contrib/ucw/config.h" /*uint*/ + +#include "lib/module.h" +/* Prototypes for the 'cache' module implementation. */ +int cache_peek(kr_layer_t *ctx, knot_pkt_t *pkt); +int cache_stash(kr_layer_t *ctx, knot_pkt_t *pkt); + + +/** + * Cache structure, keeps API, instance and metadata. + */ +struct kr_cache +{ + kr_cdb_pt db; /**< Storage instance */ + const struct kr_cdb_api *api; /**< Storage engine */ + struct kr_cdb_stats stats; + uint32_t ttl_min, ttl_max; /**< TTL limits; enforced primarily in iterator actually. */ + + /* A pair of stamps for detection of real-time shifts during runtime. */ + struct timeval checkpoint_walltime; /**< Wall time on the last check-point. */ + uint64_t checkpoint_monotime; /**< Monotonic milliseconds on the last check-point. */ + + uv_timer_t *health_timer; /**< Timer used for kr_cache_check_health() */ +}; +// https://datatracker.ietf.org/doc/html/rfc2181#section-8 +#define TTL_MAX_MAX ((1u << 31) - 1) + +/** + * Open/create cache with provided storage options. + * @param cache cache structure to be initialized + * @param api storage engine API + * @param opts storage-specific options (may be NULL for default) + * @param mm memory context. + * @return 0 or an error code + */ +KR_EXPORT +int kr_cache_open(struct kr_cache *cache, const struct kr_cdb_api *api, struct kr_cdb_opts *opts, knot_mm_t *mm); + +/** + * Path to cache file to remove on critical out-of-space error. (do NOT modify it) + */ +KR_EXPORT extern +const char *kr_cache_emergency_file_to_remove; + +/** + * Close persistent cache. + * @note This doesn't clear the data, just closes the connection to the database. + * @param cache structure + */ +KR_EXPORT +void kr_cache_close(struct kr_cache *cache); + +/** Run after a row of operations to release transaction/lock if needed. */ +KR_EXPORT +int kr_cache_commit(struct kr_cache *cache); + +/** + * Return true if cache is open and enabled. + */ +static inline bool kr_cache_is_open(struct kr_cache *cache) +{ + return cache->db != NULL; +} + +/** (Re)set the time pair to the current values. */ +static inline void kr_cache_make_checkpoint(struct kr_cache *cache) +{ + cache->checkpoint_monotime = kr_now(); + gettimeofday(&cache->checkpoint_walltime, NULL); +} + +/** + * Insert RRSet into cache, replacing any existing data. + * @param cache cache structure + * @param rr inserted RRSet + * @param rrsig RRSIG for inserted RRSet (optional) + * @param rank rank of the data + * @param timestamp current time (as-if; if the RR are older, their timestamp is appropriate) + * @param ins_nsec_p update NSEC* parameters if applicable + * @return 0 or an errcode + */ +KR_EXPORT +int kr_cache_insert_rr(struct kr_cache *cache, + const knot_rrset_t *rr, const knot_rrset_t *rrsig, + uint8_t rank, uint32_t timestamp, bool ins_nsec_p); + +/** + * Clear all items from the cache. + * @param cache cache structure + * @return if nonzero is returned, there's a big problem - you probably want to abort(), + * perhaps except for kr_error(EAGAIN) which probably indicates transient errors. + */ +KR_EXPORT +int kr_cache_clear(struct kr_cache *cache); + + +/* ** This interface is temporary. ** */ + +struct kr_cache_p { + uint32_t time; /**< The time of inception. */ + uint32_t ttl; /**< TTL at inception moment. Assuming it fits into int32_t ATM. */ + uint8_t rank; /**< See enum kr_rank */ + struct { + /* internal: pointer to eh struct */ + void *raw_data, *raw_bound; + }; +}; +KR_EXPORT +int kr_cache_peek_exact(struct kr_cache *cache, const knot_dname_t *name, uint16_t type, + struct kr_cache_p *peek); +/* Parameters (qry, name, type) are used for timestamp and stale-serving decisions. */ +KR_EXPORT +int32_t kr_cache_ttl(const struct kr_cache_p *peek, const struct kr_query *qry, + const knot_dname_t *name, uint16_t type); + +KR_EXPORT +int kr_cache_materialize(knot_rdataset_t *dst, const struct kr_cache_p *ref, + knot_mm_t *pool); + + +/** + * Remove an entry from cache. + * @param cache cache structure + * @param name dname + * @param type rr type + * @return number of deleted records, or negative error code + * @note only "exact hits" are considered ATM, and + * some other information may be removed alongside. + */ +KR_EXPORT +int kr_cache_remove(struct kr_cache *cache, const knot_dname_t *name, uint16_t type); + +/** + * Get keys matching a dname lf prefix + * @param cache cache structure + * @param name dname + * @param exact_name whether to only consider exact name matches + * @param keyval matched key-value pairs + * @param maxcount limit on the number of returned key-value pairs + * @return result count or an errcode + * @note the cache keys are matched by prefix, i.e. it very much depends + * on their structure; CACHE_KEY_DEF. + */ +KR_EXPORT +int kr_cache_match(struct kr_cache *cache, const knot_dname_t *name, + bool exact_name, knot_db_val_t keyval[][2], int maxcount); + +/** + * Remove a subtree in cache. It's like _match but removing them instead of returning. + * @return number of deleted entries or an errcode + */ +KR_EXPORT +int kr_cache_remove_subtree(struct kr_cache *cache, const knot_dname_t *name, + bool exact_name, int maxcount); + +/** + * Find the closest cached zone apex for a name (in cache). + * @param is_DS start searching one name higher + * @return the number of labels to remove from the name, or negative error code + * @note timestamp is found by a syscall, and stale-serving is not considered + */ +KR_EXPORT +int kr_cache_closest_apex(struct kr_cache *cache, const knot_dname_t *name, bool is_DS, + knot_dname_t **apex); + +/** + * Unpack dname and type from db key + * @param key db key representation + * @param buf output buffer of domain name in dname format + * @param type output for type + * @return length of dname or an errcode + * @note only "exact hits" are considered ATM, moreover xNAME records + * are "hidden" as NS. (see comments in struct entry_h) + */ +KR_EXPORT +int kr_unpack_cache_key(knot_db_val_t key, knot_dname_t *buf, uint16_t *type); + +/** Periodic kr_cdb_api::check_health(). + * @param interval in milliseconds. 0 for one-time check, -1 to stop the checks. + * @return see check_health() for one-time check; otherwise normal kr_error() code. */ +KR_EXPORT +int kr_cache_check_health(struct kr_cache *cache, int interval); + diff --git a/lib/cache/cdb_api.h b/lib/cache/cdb_api.h new file mode 100644 index 0000000..fcca8a9 --- /dev/null +++ b/lib/cache/cdb_api.h @@ -0,0 +1,97 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later +*/ + +#pragma once + +#include <stdint.h> + +#include <libknot/db/db.h> + +/* Cache options. */ +struct kr_cdb_opts { + const char *path; /*!< Cache URI path. */ + size_t maxsize; /*!< Suggested cache size in bytes; pass 0 to keep unchanged/default. */ +}; + +struct kr_cdb_stats { + uint64_t open; + uint64_t close; + uint64_t count; + uint64_t count_entries; + uint64_t clear; + uint64_t commit; + uint64_t read; + uint64_t read_miss; + uint64_t write; + uint64_t remove; + uint64_t remove_miss; + uint64_t match; + uint64_t match_miss; + uint64_t read_leq; + uint64_t read_leq_miss; + double usage_percent; +}; + +/*! Pointer to a cache structure. + * + * This struct is opaque and never defined; the purpose is to get better + * type safety than with void *. + */ +typedef struct kr_cdb *kr_cdb_pt; + +/*! Cache database API. + * This is a simplified version of generic DB API from libknot, + * that is tailored to caching purposes. + */ +struct kr_cdb_api { + const char *name; + + /* Context operations */ + + int (*open)(kr_cdb_pt *db, struct kr_cdb_stats *stat, struct kr_cdb_opts *opts, knot_mm_t *mm); + void (*close)(kr_cdb_pt db, struct kr_cdb_stats *stat); + int (*count)(kr_cdb_pt db, struct kr_cdb_stats *stat); + int (*clear)(kr_cdb_pt db, struct kr_cdb_stats *stat); + + /** Run after a row of operations to release transaction/lock if needed. */ + int (*commit)(kr_cdb_pt db, struct kr_cdb_stats *stat); + + /* Data access */ + + int (*read)(kr_cdb_pt db, struct kr_cdb_stats *stat, + const knot_db_val_t *key, knot_db_val_t *val, int maxcount); + int (*write)(kr_cdb_pt db, struct kr_cdb_stats *stat, const knot_db_val_t *key, + knot_db_val_t *val, int maxcount); + + /** Remove maxcount keys. + * \returns the number of successfully removed keys or the first error code + * It returns on first error, but ENOENT is not considered an error. */ + int (*remove)(kr_cdb_pt db, struct kr_cdb_stats *stat, + knot_db_val_t keys[], int maxcount); + + /* Specialised operations */ + + /** Find key-value pairs that are prefixed by the given key, limited by maxcount. + * \return the number of pairs or negative error. */ + int (*match)(kr_cdb_pt db, struct kr_cdb_stats *stat, + knot_db_val_t *key, knot_db_val_t keyval[][2], int maxcount); + + /** Less-or-equal search (lexicographic ordering). + * On successful return, key->data and val->data point to DB-owned data. + * return: 0 for equality, > 0 for less, < 0 kr_error */ + int (*read_leq)(kr_cdb_pt db, struct kr_cdb_stats *stat, + knot_db_val_t *key, knot_db_val_t *val); + + /** Return estimated space usage (0--100). */ + double (*usage_percent)(kr_cdb_pt db); + + /** Return the current cache size limit in bytes; could be cached by check_health(). */ + size_t (*get_maxsize)(kr_cdb_pt db); + + /** Perform maintenance. + * In LMDB case it checks whether data.mdb is still the same + * and reopens it if it isn't; it errors out if the file doesn't exist anymore. + * \return 0 if OK, 1 if reopened OK, < 0 kr_error */ + int (*check_health)(kr_cdb_pt db, struct kr_cdb_stats *stat); +}; diff --git a/lib/cache/cdb_lmdb.c b/lib/cache/cdb_lmdb.c new file mode 100644 index 0000000..80c7372 --- /dev/null +++ b/lib/cache/cdb_lmdb.c @@ -0,0 +1,868 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later +*/ + +#include <fcntl.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <lmdb.h> + +#include "contrib/cleanup.h" +#include "contrib/ucw/lib.h" +#include "lib/cache/cdb_lmdb.h" +#include "lib/cache/cdb_api.h" +#include "lib/utils.h" + + +/* Defines */ +#define LMDB_DIR_MODE 0770 +#define LMDB_FILE_MODE 0660 + +/* TODO: we rely on mirrors of these two structs not changing layout + * in libknot and knot resolver! */ +struct lmdb_env +{ + size_t mapsize; + MDB_dbi dbi; + MDB_env *env; + + /** Cached transactions + * + * - only one of (ro,rw) may be active at once + * - non-NULL .ro may be active or reset + * - non-NULL .rw is always active + */ + struct { + bool ro_active, ro_curs_active; + MDB_txn *ro, *rw; + MDB_cursor *ro_curs; + } txn; + + /* Cached part of struct stat for data.mdb. */ + dev_t st_dev; + ino_t st_ino; + off_t st_size; + const char *mdb_data_path; /**< path to data.mdb, for convenience */ +}; + +struct libknot_lmdb_env { + bool shared; + unsigned dbi; + void *env; + knot_mm_t *pool; +}; + +/** Type-safe conversion helper. + * + * We keep lmdb_env as a separate type from kr_db_pt, as different implementation of API + * would need to define the contents differently. + */ +static inline struct lmdb_env * db2env(kr_cdb_pt db) +{ + return (struct lmdb_env *)db; +} +static inline kr_cdb_pt env2db(struct lmdb_env *env) +{ + return (kr_cdb_pt)env; +} + +static int cdb_commit(kr_cdb_pt db, struct kr_cdb_stats *stats); + +/** @brief Convert LMDB error code. */ +static int lmdb_error(int error) +{ + switch (error) { + case MDB_SUCCESS: + return kr_ok(); + case MDB_NOTFOUND: + return kr_error(ENOENT); + case ENOSPC: + case MDB_MAP_FULL: + case MDB_TXN_FULL: + return kr_error(ENOSPC); + default: + kr_log_error(CACHE, "LMDB error: %s\n", mdb_strerror(error)); + return kr_error(error); + } +} + +/** Conversion between knot and lmdb structs for values. */ +static inline knot_db_val_t val_mdb2knot(MDB_val v) +{ + return (knot_db_val_t){ .len = v.mv_size, .data = v.mv_data }; +} +static inline MDB_val val_knot2mdb(knot_db_val_t v) +{ + return (MDB_val){ .mv_size = v.len, .mv_data = v.data }; +} + +/** Refresh mapsize value from file, including env->mapsize. + * It's much lighter than reopen_env(). */ +static int refresh_mapsize(struct lmdb_env *env) +{ + int ret = cdb_commit(env2db(env), NULL); + if (!ret) ret = lmdb_error(mdb_env_set_mapsize(env->env, 0)); + if (ret) return ret; + + MDB_envinfo info; + ret = lmdb_error(mdb_env_info(env->env, &info)); + if (ret) return ret; + + env->mapsize = info.me_mapsize; + if (env->mapsize != env->st_size) { + kr_log_info(CACHE, "suspicious size of cache file '%s'" + ": file size %zu != LMDB map size %zu\n", + env->mdb_data_path, (size_t)env->st_size, env->mapsize); + } + return kr_ok(); +} + +static void clear_stale_readers(struct lmdb_env *env) +{ + int cleared; + int ret = mdb_reader_check(env->env, &cleared); + if (ret != MDB_SUCCESS) { + kr_log_error(CACHE, "failed to clear stale reader locks: " + "LMDB error %d %s\n", ret, mdb_strerror(ret)); + } else if (cleared != 0) { + kr_log_info(CACHE, "cleared %d stale reader locks\n", cleared); + } +} + +#define FLAG_RENEW (2*MDB_RDONLY) +/** mdb_txn_begin or _renew + handle retries in some situations + * + * The retrying logic is so ugly that it has its own function. + * \note this assumes no transactions are active + * \return MDB_ errcode, not usual kr_error(...) + */ +static int txn_get_noresize(struct lmdb_env *env, unsigned int flag, MDB_txn **txn) +{ + if (kr_fails_assert(!env->txn.rw && (!env->txn.ro || !env->txn.ro_active))) + return kr_error(1); + int attempts = 0; + int ret; +retry: + /* Do a few attempts in case we encounter multiple issues at once. */ + if (++attempts > 2) + return kr_error(1); + + if (flag == FLAG_RENEW) { + ret = mdb_txn_renew(*txn); + } else { + ret = mdb_txn_begin(env->env, NULL, flag, txn); + } + + if (unlikely(ret == MDB_MAP_RESIZED)) { + kr_log_info(CACHE, "detected size increased by another process\n"); + ret = refresh_mapsize(env); + if (ret == 0) + goto retry; + } else if (unlikely(ret == MDB_READERS_FULL)) { + clear_stale_readers(env); + goto retry; + } + return ret; +} + +/** Obtain a transaction. (they're cached in env->txn) */ +static int txn_get(struct lmdb_env *env, MDB_txn **txn, bool rdonly) +{ + if (kr_fails_assert(env && txn)) + return kr_error(EINVAL); + if (env->txn.rw) { + /* Reuse the *open* RW txn even if only reading is requested. + * We leave the management of this to the cdb_commit command. + * The user may e.g. want to do some reads between the writes. */ + *txn = env->txn.rw; + return kr_ok(); + } + + if (!rdonly) { + /* avoid two active transactions */ + if (env->txn.ro && env->txn.ro_active) { + mdb_txn_reset(env->txn.ro); + env->txn.ro_active = false; + env->txn.ro_curs_active = false; + } + int ret = txn_get_noresize(env, 0/*RW*/, &env->txn.rw); + if (ret == MDB_SUCCESS) { + *txn = env->txn.rw; + kr_assert(*txn); + } + return lmdb_error(ret); + } + + /* Get an active RO txn and return it. */ + int ret = MDB_SUCCESS; + if (!env->txn.ro) { //:unlikely + ret = txn_get_noresize(env, MDB_RDONLY, &env->txn.ro); + } else if (!env->txn.ro_active) { + ret = txn_get_noresize(env, FLAG_RENEW, &env->txn.ro); + } + if (ret != MDB_SUCCESS) { + return lmdb_error(ret); + } + env->txn.ro_active = true; + *txn = env->txn.ro; + kr_assert(*txn); + return kr_ok(); +} + +static int cdb_commit(kr_cdb_pt db, struct kr_cdb_stats *stats) +{ + struct lmdb_env *env = db2env(db); + int ret = kr_ok(); + if (env->txn.rw) { + if (stats) stats->commit++; + ret = lmdb_error(mdb_txn_commit(env->txn.rw)); + env->txn.rw = NULL; /* the transaction got freed even in case of errors */ + } else if (env->txn.ro && env->txn.ro_active) { + mdb_txn_reset(env->txn.ro); + env->txn.ro_active = false; + env->txn.ro_curs_active = false; + } + return ret; +} + +/** Obtain a read-only cursor (and a read-only transaction). */ +static int txn_curs_get(struct lmdb_env *env, MDB_cursor **curs, struct kr_cdb_stats *stats) +{ + if (kr_fails_assert(env && curs)) + return kr_error(EINVAL); + if (env->txn.ro_curs_active) + goto success; + /* Only in a read-only txn; TODO: it's a bit messy/coupled */ + if (env->txn.rw) { + int ret = cdb_commit(env2db(env), stats); + if (ret) return ret; + } + MDB_txn *txn = NULL; + int ret = txn_get(env, &txn, true); + if (ret) return ret; + + if (env->txn.ro_curs) { + ret = mdb_cursor_renew(txn, env->txn.ro_curs); + } else { + ret = mdb_cursor_open(txn, env->dbi, &env->txn.ro_curs); + } + if (ret) return lmdb_error(ret); + env->txn.ro_curs_active = true; +success: + kr_assert(env->txn.ro_curs_active && env->txn.ro && env->txn.ro_active + && !env->txn.rw); + *curs = env->txn.ro_curs; + kr_assert(*curs); + return kr_ok(); +} + +static void txn_free_ro(struct lmdb_env *env) +{ + if (env->txn.ro_curs) { + mdb_cursor_close(env->txn.ro_curs); + env->txn.ro_curs = NULL; + } + if (env->txn.ro) { + mdb_txn_abort(env->txn.ro); + env->txn.ro = NULL; + } +} + +/** Abort all transactions. + * + * This is useful after an error happens, as those (always?) require abortion. + * It's possible that _reset() would suffice and marking cursor inactive, + * but these errors should be rare so let's close them completely. */ +static void txn_abort(struct lmdb_env *env) +{ + txn_free_ro(env); + if (env->txn.rw) { + mdb_txn_abort(env->txn.rw); + env->txn.rw = NULL; /* the transaction got freed even in case of errors */ + } +} + +/*! \brief Close the database. */ +static void cdb_close_env(struct lmdb_env *env, struct kr_cdb_stats *stats) +{ + if (kr_fails_assert(env && env->env)) + return; + + /* Get rid of any transactions. */ + txn_free_ro(env); + cdb_commit(env2db(env), stats); + + mdb_env_sync(env->env, 1); + stats->close++; + mdb_dbi_close(env->env, env->dbi); + mdb_env_close(env->env); + free_const(env->mdb_data_path); + memset(env, 0, sizeof(*env)); +} + +/** We assume that *env is zeroed and we return it zeroed on errors. */ +static int cdb_open_env(struct lmdb_env *env, const char *path, const size_t mapsize, + struct kr_cdb_stats *stats) +{ + int ret = mkdir(path, LMDB_DIR_MODE); + if (ret && errno != EEXIST) return kr_error(errno); + + stats->open++; + ret = mdb_env_create(&env->env); + if (ret != MDB_SUCCESS) return lmdb_error(ret); + + env->mdb_data_path = kr_absolutize_path(path, "data.mdb"); + if (!env->mdb_data_path) { + ret = ENOMEM; + goto error_sys; + } + + /* Set map size, rounded to page size. */ + errno = 0; + const long pagesize = sysconf(_SC_PAGESIZE); + if (errno) { + ret = errno; + goto error_sys; + } + + const bool size_requested = mapsize; + if (size_requested) { + env->mapsize = (mapsize / pagesize) * pagesize; + ret = mdb_env_set_mapsize(env->env, env->mapsize); + if (ret != MDB_SUCCESS) goto error_mdb; + } + + /* Cache doesn't require durability, we can be + * loose with the requirements as a tradeoff for speed. */ + const unsigned flags = MDB_WRITEMAP | MDB_MAPASYNC | MDB_NOTLS; + ret = mdb_env_open(env->env, path, flags, LMDB_FILE_MODE); + if (ret != MDB_SUCCESS) goto error_mdb; + + mdb_filehandle_t fd = -1; + ret = mdb_env_get_fd(env->env, &fd); + if (ret != MDB_SUCCESS) goto error_mdb; + + struct stat st; + if (fstat(fd, &st)) { + ret = errno; + goto error_sys; + } + env->st_dev = st.st_dev; + env->st_ino = st.st_ino; + env->st_size = st.st_size; + + /* Get the real mapsize. Shrinking can be restricted, etc. + * Unfortunately this is only reliable when not setting the size explicitly. */ + if (!size_requested) { + ret = refresh_mapsize(env); + if (ret) goto error_sys; + } + + /* Open the database. */ + MDB_txn *txn = NULL; + ret = mdb_txn_begin(env->env, NULL, 0, &txn); + if (ret != MDB_SUCCESS) goto error_mdb; + + ret = mdb_dbi_open(txn, NULL, 0, &env->dbi); + if (ret != MDB_SUCCESS) { + mdb_txn_abort(txn); + goto error_mdb; + } + +#if !defined(__MACOSX__) && !(defined(__APPLE__) && defined(__MACH__)) + if (size_requested) { + ret = posix_fallocate(fd, 0, MAX(env->mapsize, env->st_size)); + } else { + ret = 0; + } + if (ret == EINVAL || ret == EOPNOTSUPP) { + /* POSIX says this can happen when the feature isn't supported by the FS. + * We haven't seen this happen on Linux+glibc but it was reported on + * Linux+musl and FreeBSD. */ + kr_log_info(CACHE, "space pre-allocation failed and ignored; " + "your (file)system probably doesn't support it.\n"); + } else if (ret != 0) { + mdb_txn_abort(txn); + goto error_sys; + } +#endif + + stats->commit++; + ret = mdb_txn_commit(txn); + if (ret != MDB_SUCCESS) goto error_mdb; + + /* Stale RO transactions could have been left behind by a cashing process + * (e.g. one whose termination lead to spawning the current one). + * According to docs they might hold onto some space until we clear them. */ + clear_stale_readers(env); + + return kr_ok(); + +error_mdb: + ret = lmdb_error(ret); +error_sys: + free_const(env->mdb_data_path); + stats->close++; + mdb_env_close(env->env); + memset(env, 0, sizeof(*env)); + return kr_error(ret); +} + +static int cdb_init(kr_cdb_pt *db, struct kr_cdb_stats *stats, + struct kr_cdb_opts *opts, knot_mm_t *pool) +{ + if (!db || !stats || !opts) { + return kr_error(EINVAL); + } + + /* Open the database. */ + struct lmdb_env *env = calloc(1, sizeof(*env)); + if (!env) { + return kr_error(ENOMEM); + } + int ret = cdb_open_env(env, opts->path, opts->maxsize, stats); + if (ret != 0) { + free(env); + return ret; + } + + *db = env2db(env); + return 0; +} + +static void cdb_deinit(kr_cdb_pt db, struct kr_cdb_stats *stats) +{ + cdb_close_env(db2env(db), stats); + free(db); +} + +static int cdb_count(kr_cdb_pt db, struct kr_cdb_stats *stats) +{ + struct lmdb_env *env = db2env(db); + MDB_txn *txn = NULL; + int ret = txn_get(env, &txn, true); + if (ret != 0) { + return ret; + } + + MDB_stat stat; + stats->count++; + ret = mdb_stat(txn, env->dbi, &stat); + + if (ret == MDB_SUCCESS) { + return stat.ms_entries; + } else { + txn_abort(env); + return lmdb_error(ret); + } +} + +static int reopen_env(struct lmdb_env *env, struct kr_cdb_stats *stats, const size_t mapsize) +{ + /* Keep copy as it points to current handle internals. */ + const char *path; + int ret = mdb_env_get_path(env->env, &path); + if (ret != MDB_SUCCESS) { + return lmdb_error(ret); + } + auto_free char *path_copy = strdup(path); + cdb_close_env(env, stats); + return cdb_open_env(env, path_copy, mapsize, stats); +} + +static int cdb_check_health(kr_cdb_pt db, struct kr_cdb_stats *stats) +{ + struct lmdb_env *env = db2env(db); + + struct stat st; + if (stat(env->mdb_data_path, &st)) { + int ret = errno; + return kr_error(ret); + } + + if (st.st_dev != env->st_dev || st.st_ino != env->st_ino) { + kr_log_debug(CACHE, "cache file has been replaced, reopening\n"); + int ret = reopen_env(env, stats, 0); // we accept mapsize from the new file + return ret == 0 ? 1 : ret; + } + + /* Cache check through file size works OK without reopening, + * contrary to methods based on mdb_env_info(). */ + if (st.st_size == env->st_size) + return kr_ok(); + kr_log_info(CACHE, "detected size change (by another instance?) of file '%s'" + ": file size %zu -> file size %zu\n", + env->mdb_data_path, (size_t)env->st_size, (size_t)st.st_size); + env->st_size = st.st_size; // avoid retrying in cycle even if we fail + return refresh_mapsize(env); +} + +/** Obtain exclusive (advisory) lock by creating a file, returning FD or negative kr_error(). + * The lock is auto-released by OS in case the process finishes in any way (file remains). */ +static int lockfile_get(const char *path) +{ + if (kr_fails_assert(path)) + return kr_error(EINVAL); + const int fd = open(path, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP); + if (fd < 0) + return kr_error(errno); + + struct flock lock_info; + memset(&lock_info, 0, sizeof(lock_info)); + lock_info.l_type = F_WRLCK; + lock_info.l_whence = SEEK_SET; + lock_info.l_start = 0; + lock_info.l_len = 1; // it's OK for locks to extend beyond the end of the file + int err; + do { + err = fcntl(fd, F_SETLK, &lock_info); + } while (err == -1 && errno == EINTR); + if (err) { + close(fd); + return kr_error(errno); + } + return fd; +} + +/** Release and remove lockfile created by lockfile_get(). Return kr_error(). */ +static int lockfile_release(int fd) +{ + if (kr_fails_assert(fd > 0)) // fd == 0 is surely a mistake, in our case at least + return kr_error(EINVAL); + if (close(fd)) { + return kr_error(errno); + } else { + return kr_ok(); + } +} + +static int cdb_clear(kr_cdb_pt db, struct kr_cdb_stats *stats) +{ + struct lmdb_env *env = db2env(db); + stats->clear++; + /* First try mdb_drop() to clear the DB; this may fail with ENOSPC. */ + { + MDB_txn *txn = NULL; + int ret = txn_get(env, &txn, false); + if (ret == kr_ok()) { + ret = lmdb_error(mdb_drop(txn, env->dbi, 0)); + if (ret == kr_ok()) { + ret = cdb_commit(db, stats); + } + if (ret == kr_ok()) { + return ret; + } + } + kr_log_info(CACHE, "clearing error, falling back\n"); + } + /* Fallback: we'll remove the database files and reopen. + * Other instances can continue to use the removed lmdb, + * though it's best for them to reopen soon. */ + + /* We are about to switch to a different file, so end all txns, to be sure. */ + txn_free_ro(env); + (void) cdb_commit(db, stats); + + const char *path = NULL; + int ret = mdb_env_get_path(env->env, &path); + if (ret != MDB_SUCCESS) { + return lmdb_error(ret); + } + auto_free char *mdb_lockfile = kr_strcatdup(2, path, "/lock.mdb"); + auto_free char *lockfile = kr_strcatdup(2, path, "/krcachelock"); + if (!mdb_lockfile || !lockfile) { + return kr_error(ENOMEM); + } + + /* Find if we get a lock on lockfile. */ + const int lockfile_fd = lockfile_get(lockfile); + if (lockfile_fd < 0) { + kr_log_error(CACHE, "clearing failed to get ./krcachelock (%s); retry later\n", + kr_strerror(lockfile_fd)); + /* As we're out of space (almost certainly - mdb_drop didn't work), + * we will retry on the next failing write operation. */ + return kr_error(EAGAIN); + } + + /* We acquired lockfile. Now find whether *.mdb are what we have open now. + * If they are not we don't want to remove them; most likely they have been + * cleaned by another instance. */ + ret = cdb_check_health(db, stats); + if (ret != 0) { + if (ret == 1) // file changed and reopened successfully + ret = kr_ok(); + // else pass some other error + } else { + kr_log_debug(CACHE, "clear: identical files, unlinking\n"); + // coverity[toctou] + unlink(env->mdb_data_path); + unlink(mdb_lockfile); + ret = reopen_env(env, stats, env->mapsize); + } + + /* Environment updated, release lockfile. */ + int lrerr = lockfile_release(lockfile_fd); + if (lrerr) { + kr_log_error(CACHE, "failed to release ./krcachelock: %s\n", + kr_strerror(lrerr)); + } + return ret; +} + +static int cdb_readv(kr_cdb_pt db, struct kr_cdb_stats *stats, + const knot_db_val_t *key, knot_db_val_t *val, int maxcount) +{ + struct lmdb_env *env = db2env(db); + MDB_txn *txn = NULL; + int ret = txn_get(env, &txn, true); + if (ret) { + return ret; + } + + for (int i = 0; i < maxcount; ++i) { + /* Convert key structs */ + MDB_val _key = val_knot2mdb(key[i]); + MDB_val _val = val_knot2mdb(val[i]); + stats->read++; + ret = mdb_get(txn, env->dbi, &_key, &_val); + if (ret != MDB_SUCCESS) { + if (ret == MDB_NOTFOUND) { + stats->read_miss++; + } else { + txn_abort(env); + } + ret = lmdb_error(ret); + if (ret == kr_error(ENOSPC)) { + /* we're likely to be forced to cache clear anyway */ + ret = kr_error(ENOENT); + } + return ret; + } + /* Update the result. */ + val[i] = val_mdb2knot(_val); + } + return kr_ok(); +} + +static int cdb_write(struct lmdb_env *env, MDB_txn **txn, const knot_db_val_t *key, + knot_db_val_t *val, unsigned flags, + struct kr_cdb_stats *stats) +{ + /* Convert key structs and write */ + MDB_val _key = val_knot2mdb(*key); + MDB_val _val = val_knot2mdb(*val); + stats->write++; + int ret = mdb_put(*txn, env->dbi, &_key, &_val, flags); + + /* We don't try to recover from MDB_TXN_FULL. */ + if (ret != MDB_SUCCESS) { + txn_abort(env); + return lmdb_error(ret); + } + + /* Update the result. */ + val->data = _val.mv_data; + val->len = _val.mv_size; + return kr_ok(); +} + +static int cdb_writev(kr_cdb_pt db, struct kr_cdb_stats *stats, + const knot_db_val_t *key, knot_db_val_t *val, int maxcount) +{ + struct lmdb_env *env = db2env(db); + MDB_txn *txn = NULL; + int ret = txn_get(env, &txn, false); + + for (int i = 0; ret == kr_ok() && i < maxcount; ++i) { + /* This is LMDB specific optimisation, + * if caller specifies value with NULL data and non-zero length, + * LMDB will preallocate the entry for caller and leave write + * transaction open, caller is responsible for syncing thus committing transaction. + */ + unsigned mdb_flags = 0; + if (val[i].len > 0 && val[i].data == NULL) { + mdb_flags |= MDB_RESERVE; + } + ret = cdb_write(env, &txn, &key[i], &val[i], mdb_flags, stats); + } + + return ret; +} + +static int cdb_remove(kr_cdb_pt db, struct kr_cdb_stats *stats, + knot_db_val_t keys[], int maxcount) +{ + struct lmdb_env *env = db2env(db); + MDB_txn *txn = NULL; + int ret = txn_get(env, &txn, false); + int deleted = 0; + + for (int i = 0; ret == kr_ok() && i < maxcount; ++i) { + MDB_val _key = val_knot2mdb(keys[i]); + MDB_val val = { 0, NULL }; + stats->remove++; + ret = lmdb_error(mdb_del(txn, env->dbi, &_key, &val)); + if (ret == kr_ok()) + deleted++; + else if (ret == KNOT_ENOENT) { + stats->remove_miss++; + ret = kr_ok(); /* skip over non-existing entries */ + } else { + txn_abort(env); + break; + } + } + + return ret < 0 ? ret : deleted; +} + +static int cdb_match(kr_cdb_pt db, struct kr_cdb_stats *stats, + knot_db_val_t *key, knot_db_val_t keyval[][2], int maxcount) +{ + struct lmdb_env *env = db2env(db); + MDB_txn *txn = NULL; + int ret = txn_get(env, &txn, true); + if (ret != 0) { + return ret; + } + + /* LATER(optim.): use txn_curs_get() instead, to save resources. */ + MDB_cursor *cur = NULL; + ret = mdb_cursor_open(txn, env->dbi, &cur); + if (ret != 0) { + txn_abort(env); + return lmdb_error(ret); + } + + MDB_val cur_key = val_knot2mdb(*key); + MDB_val cur_val = { 0, NULL }; + stats->match++; + ret = mdb_cursor_get(cur, &cur_key, &cur_val, MDB_SET_RANGE); + if (ret != MDB_SUCCESS) { + mdb_cursor_close(cur); + if (ret != MDB_NOTFOUND) { + txn_abort(env); + } + return lmdb_error(ret); + } + + int results = 0; + while (ret == MDB_SUCCESS) { + /* Retrieve current key and compare with prefix */ + if (cur_key.mv_size < key->len || memcmp(cur_key.mv_data, key->data, key->len) != 0) { + break; + } + /* Add to result set */ + if (results < maxcount) { + keyval[results][0] = val_mdb2knot(cur_key); + keyval[results][1] = val_mdb2knot(cur_val); + ++results; + } else { + break; + } + stats->match++; + ret = mdb_cursor_get(cur, &cur_key, &cur_val, MDB_NEXT); + } + + mdb_cursor_close(cur); + if (ret != MDB_SUCCESS && ret != MDB_NOTFOUND) { + txn_abort(env); + return lmdb_error(ret); + } else if (results == 0) { + stats->match_miss++; + } + return results; +} + + +static int cdb_read_leq(kr_cdb_pt db, struct kr_cdb_stats *stats, + knot_db_val_t *key, knot_db_val_t *val) +{ + if (kr_fails_assert(db && key && key->data && val)) + return kr_error(EINVAL); + struct lmdb_env *env = db2env(db); + MDB_cursor *curs = NULL; + int ret = txn_curs_get(env, &curs, stats); + if (ret) return ret; + + MDB_val key2_m = val_knot2mdb(*key); + MDB_val val2_m = { 0, NULL }; + stats->read_leq++; + ret = mdb_cursor_get(curs, &key2_m, &val2_m, MDB_SET_RANGE); + if (ret) goto failure; + /* test for equality //:unlikely */ + if (key2_m.mv_size == key->len + && memcmp(key2_m.mv_data, key->data, key->len) == 0) { + ret = 0; /* equality */ + goto success; + } + stats->read_leq_miss++; + + /* we must be greater than key; do one step to smaller */ + stats->read_leq++; + ret = mdb_cursor_get(curs, &key2_m, &val2_m, MDB_PREV); + if (ret) goto failure; + ret = 1; +success: + /* finalize the output */ + *key = val_mdb2knot(key2_m); + *val = val_mdb2knot(val2_m); + return ret; +failure: + if (ret == MDB_NOTFOUND) { + stats->read_leq_miss++; + } else { + txn_abort(env); + } + return lmdb_error(ret); +} + +static double cdb_usage_percent(kr_cdb_pt db) +{ + knot_db_t *kdb = kr_cdb_pt2knot_db_t(db); + const size_t db_size = knot_db_lmdb_get_mapsize(kdb); + const size_t db_usage_abs = knot_db_lmdb_get_usage(kdb); + const double db_usage = (double)db_usage_abs / db_size * 100.0; + free(kdb); + return db_usage; +} + +static size_t cdb_get_maxsize(kr_cdb_pt db) +{ + return db2env(db)->mapsize; +} + +/** Conversion between knot and lmdb structs. */ +knot_db_t *kr_cdb_pt2knot_db_t(kr_cdb_pt db) +{ + /* this is struct lmdb_env as in resolver/cdb_lmdb.c */ + const struct lmdb_env *kres_db = db2env(db); + struct libknot_lmdb_env *libknot_db = malloc(sizeof(*libknot_db)); + if (libknot_db != NULL) { + libknot_db->shared = false; + libknot_db->pool = NULL; + libknot_db->env = kres_db->env; + libknot_db->dbi = kres_db->dbi; + } + return libknot_db; +} + +const struct kr_cdb_api *kr_cdb_lmdb(void) +{ + static const struct kr_cdb_api api = { + "lmdb", + cdb_init, cdb_deinit, cdb_count, cdb_clear, cdb_commit, + cdb_readv, cdb_writev, cdb_remove, + cdb_match, + cdb_read_leq, + cdb_usage_percent, + cdb_get_maxsize, + cdb_check_health, + }; + + return &api; +} diff --git a/lib/cache/cdb_lmdb.h b/lib/cache/cdb_lmdb.h new file mode 100644 index 0000000..988fccf --- /dev/null +++ b/lib/cache/cdb_lmdb.h @@ -0,0 +1,16 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later +*/ + +#pragma once + +#include "lib/cache/cdb_api.h" +#include "lib/defines.h" + +KR_EXPORT KR_CONST +const struct kr_cdb_api *kr_cdb_lmdb(void); + +/** Create a pointer for knot_db_lmdb_api. You free() it to release it. */ +KR_EXPORT +knot_db_t *kr_cdb_pt2knot_db_t(kr_cdb_pt db); + diff --git a/lib/cache/entry_list.c b/lib/cache/entry_list.c new file mode 100644 index 0000000..4dced2f --- /dev/null +++ b/lib/cache/entry_list.c @@ -0,0 +1,301 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/** @file + * Implementation of chaining in struct entry_h. Prototypes in ./impl.h + */ + +#include "lib/cache/impl.h" +#include "lib/utils.h" + + +static int entry_h_len(knot_db_val_t val); + + +void entry_list_memcpy(struct entry_apex *ea, entry_list_t list) +{ + if (kr_fails_assert(ea)) + return; + memset(ea, 0, offsetof(struct entry_apex, data)); + ea->has_ns = list[EL_NS ].len; + ea->has_cname = list[EL_CNAME ].len; + ea->has_dname = list[EL_DNAME ].len; + for (int i = 0; i < ENTRY_APEX_NSECS_CNT; ++i) { + ea->nsecs[i] = list[i].len == 0 ? 0 : + (list[i].len == 4 ? 1 : 3); + } + uint8_t *it = ea->data; + for (int i = 0; i < EL_LENGTH; ++i) { + if (list[i].data) { + memcpy(it, list[i].data, list[i].len); + /* LATER(optim.): coalesce consecutive writes? */ + } else { + list[i].data = it; + } + it += to_even(list[i].len); + } +} + +int entry_list_parse(const knot_db_val_t val, entry_list_t list) +{ + if (kr_fails_assert(val.data && val.len && list)) + return kr_error(EINVAL); + /* Parse the apex itself (nsec parameters). */ + const struct entry_apex *ea = entry_apex_consistent(val); + if (!ea) { + return kr_error(EILSEQ); + } + const uint8_t *it = ea->data, + *it_bound = knot_db_val_bound(val); + for (int i = 0; i < ENTRY_APEX_NSECS_CNT; ++i) { + if (it > it_bound) { + return kr_error(EILSEQ); + } + list[i].data = (void *)it; + switch (ea->nsecs[i]) { + case 0: + list[i].len = 0; + break; + case 1: + list[i].len = sizeof(uint32_t); /* just timestamp */ + break; + case 3: { /* timestamp + NSEC3PARAM wire */ + if (it + sizeof(uint32_t) + 4 > it_bound) { + return kr_error(EILSEQ); + } + list[i].len = sizeof(uint32_t) + + nsec_p_rdlen(it + sizeof(uint32_t)); + break; + } + default: + return kr_error(EILSEQ); + }; + it += to_even(list[i].len); + } + /* Parse every entry_h. */ + for (int i = ENTRY_APEX_NSECS_CNT; i < EL_LENGTH; ++i) { + list[i].data = (void *)it; + bool has_type; + switch (i) { + case EL_NS: has_type = ea->has_ns; break; + case EL_CNAME: has_type = ea->has_cname; break; + case EL_DNAME: has_type = ea->has_dname; break; + default: + kr_assert(!EINVAL); + return kr_error(EINVAL); /* something very bad */ + } + if (!has_type) { + list[i].len = 0; + continue; + } + if (kr_fails_assert(it < it_bound)) + return kr_error(EILSEQ); + const int len = entry_h_len( + (knot_db_val_t){ .data = (void *)it, .len = it_bound - it }); + if (kr_fails_assert(len >= 0)) + return kr_error(len); + list[i].len = len; + it += to_even(len); + } + if (kr_fails_assert(it == it_bound)) /* better not use it; might be "damaged" */ + return kr_error(EILSEQ); + return kr_ok(); +} + +/** Given a valid entry header, find its length (i.e. offset of the next entry). + * \param val The beginning of the data and the bound (read only). + */ +static int entry_h_len(const knot_db_val_t val) +{ + const bool ok = val.data && ((ssize_t)val.len) > 0; + if (!ok) return kr_error(EINVAL); + const struct entry_h *eh = val.data; + const uint8_t *d = eh->data; /* iterates over the data in entry */ + const uint8_t *data_bound = knot_db_val_bound(val); + if (d >= data_bound) return kr_error(EILSEQ); + if (!eh->is_packet) { /* Positive RRset + its RRsig set (may be empty). */ + int sets = 2; + while (sets-- > 0) { + d += KR_CACHE_RR_COUNT_SIZE + rdataset_dematerialized_size(d, NULL); + if (kr_fails_assert(d <= data_bound)) + return kr_error(EILSEQ); + } + } else { /* A "packet" (opaque ATM). */ + uint16_t len; + if (d + sizeof(len) > data_bound) return kr_error(EILSEQ); + memcpy(&len, d, sizeof(len)); + d += 2 + to_even(len); + } + if (kr_fails_assert(d <= data_bound)) + return kr_error(EILSEQ); + return d - (uint8_t *)val.data; +} + +struct entry_apex * entry_apex_consistent(knot_db_val_t val) +{ + //XXX: check lengths, etc. + return val.data; +} + +/* See the header file. */ +int entry_h_seek(knot_db_val_t *val, uint16_t type) +{ + int i = -1; + switch (type) { + case KNOT_RRTYPE_NS: i = EL_NS; break; + case KNOT_RRTYPE_CNAME: i = EL_CNAME; break; + case KNOT_RRTYPE_DNAME: i = EL_DNAME; break; + default: return kr_ok(); + } + + entry_list_t el; + int ret = entry_list_parse(*val, el); + if (ret) return ret; + *val = el[i]; + return val->len ? kr_ok() : kr_error(ENOENT); +} + +static int cache_write_or_clear(struct kr_cache *cache, const knot_db_val_t *key, + knot_db_val_t *val, const struct kr_query *qry) +{ + static uint64_t ignoring_errors_until = 0; /// zero or a timestamp + int ret = cache_op(cache, write, key, val, 1); + if (!ret) { + ignoring_errors_until = 0; + return kr_ok(); + } + VERBOSE_MSG(qry, "=> failed backend write, ret = %d\n", ret); + + if (ret == kr_error(ENOSPC) && cache->api->usage_percent(cache->db) > 90) { + // Cache seems overfull. Maybe kres-cache-gc service doesn't work. + goto recovery; + } + + /* If we get ENOSPC with usage < 90% (especially just above 80% when GC fires), + * it most likely isn't real overfull state but some LMDB bug related + * to transactions. Upstream seems unlikely to address it: + https://lists.openldap.org/hyperkitty/list/openldap-technical@openldap.org/thread/QHOTE2Y3WZ6E7J27OOKI44P344ETUOSF/ + * + * In real life we see all processes getting a LMDB failure + * but it should recover after the transactions get reopened. + * + * Fortunately the kresd cache can afford to be slightly lossy, + * so we ignore this and other errors for a short while. + */ + const uint64_t now = kr_now(); + if (!ignoring_errors_until) { // First error after a success. + kr_log_info(CACHE, "LMDB refusing writes (ignored for 5-9s): %s\n", + kr_strerror(ret)); + ignoring_errors_until = now + 5000 + kr_rand_bytes(2)/16; + return kr_error(ret); + } + if (now < ignoring_errors_until) + return kr_error(ret); + // We've lost patience with cache writes not working continuously. + +recovery: // Try to recover by clearing cache. + ret = kr_cache_clear(cache); + switch (ret) { + default: + kr_log_crit(CACHE, "CRITICAL: clearing cache failed: %s; fatal error, aborting\n", + kr_strerror(ret)); + abort(); + case 0: + kr_log_info(CACHE, "stuck cache cleared\n"); + ignoring_errors_until = 0; + case -EAGAIN: // fall-through; krcachelock race -> retry later + return kr_error(ENOSPC); + } +} + + +/* See the header file. */ +int entry_h_splice( + knot_db_val_t *val_new_entry, uint8_t rank, + const knot_db_val_t key, const uint16_t ktype, const uint16_t type, + const knot_dname_t *owner/*log only*/, + const struct kr_query *qry, struct kr_cache *cache, uint32_t timestamp) +{ + //TODO: another review, perhaps including the API + if (kr_fails_assert(val_new_entry && val_new_entry->len > 0)) + return kr_error(EINVAL); + + int i_type; + switch (type) { + case KNOT_RRTYPE_NS: i_type = EL_NS; break; + case KNOT_RRTYPE_CNAME: i_type = EL_CNAME; break; + case KNOT_RRTYPE_DNAME: i_type = EL_DNAME; break; + default: i_type = 0; + } + + /* Get eh_orig (original entry), and also el list if multi-entry case. */ + const struct entry_h *eh_orig = NULL; + entry_list_t el; + int ret = -1; + if (!kr_rank_test(rank, KR_RANK_SECURE) || ktype == KNOT_RRTYPE_NS) { + knot_db_val_t val; + ret = cache_op(cache, read, &key, &val, 1); + if (i_type) { + if (!ret) ret = entry_list_parse(val, el); + if (ret) memset(el, 0, sizeof(el)); + val = el[i_type]; + } + /* val is on the entry, in either case (or error) */ + if (!ret) { + eh_orig = entry_h_consistent_E(val, type); + } + } else { + /* We want to fully overwrite the entry, so don't even read it. */ + memset(el, 0, sizeof(el)); + } + + if (!kr_rank_test(rank, KR_RANK_SECURE) && eh_orig) { + /* If equal rank was accepted, spoofing a *single* answer would be + * enough to e.g. override NS record in AUTHORITY section. + * This way they would have to hit the first answer + * (whenever TTL nears expiration). + * Stale-serving is NOT considered, but TTL 1 would be considered + * as expiring anyway, ... */ + int32_t old_ttl = get_new_ttl(eh_orig, qry, NULL, 0, timestamp); + if (old_ttl > 0 && !is_expiring(eh_orig->ttl, old_ttl) + && rank <= eh_orig->rank) { + WITH_VERBOSE(qry) { + auto_free char *type_str = kr_rrtype_text(type), + *owner_str = kr_dname_text(owner); + VERBOSE_MSG(qry, "=> not overwriting %s %s\n", + type_str, owner_str); + } + return kr_error(EEXIST); + } + } + + if (!i_type) { + /* The non-list types are trivial now. */ + return cache_write_or_clear(cache, &key, val_new_entry, qry); + } + /* Now we're in trouble. In some cases, parts of data to be written + * is an lmdb entry that may be invalidated by our write request. + * (lmdb does even in-place updates!) Therefore we copy all into a buffer. + * LATER(optim.): do this only when necessary, or perhaps another approach. + * This is also complicated by the fact that the val_new_entry part + * is to be written *afterwards* by the caller. + */ + el[i_type] = (knot_db_val_t){ + .len = val_new_entry->len, + .data = NULL, /* perhaps unclear in the entry_h_splice() API */ + }; + knot_db_val_t val = { + .len = entry_list_serial_size(el), + .data = NULL, + }; + uint8_t buf[val.len]; + entry_list_memcpy((struct entry_apex *)buf, el); + ret = cache_write_or_clear(cache, &key, &val, qry); + if (ret) return kr_error(ret); + memcpy(val.data, buf, val.len); /* we also copy the "empty" space, but well... */ + val_new_entry->data = (uint8_t *)val.data + + ((uint8_t *)el[i_type].data - buf); + return kr_ok(); +} + diff --git a/lib/cache/entry_pkt.c b/lib/cache/entry_pkt.c new file mode 100644 index 0000000..884bfaa --- /dev/null +++ b/lib/cache/entry_pkt.c @@ -0,0 +1,206 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/** @file + * Implementation of packet-caching. Prototypes in ./impl.h + * + * The packet is stashed in entry_h::data as uint16_t length + full packet wire format. + */ + +#include "lib/utils.h" +#include "lib/layer/iterate.h" /* kr_response_classify */ +#include "lib/cache/impl.h" + + +/** Compute TTL for a packet. It's minimum TTL or zero. (You can apply limits.) */ +KR_EXPORT +uint32_t packet_ttl(const knot_pkt_t *pkt) +{ + bool has_ttl = false; + uint32_t ttl = TTL_MAX_MAX; + for (knot_section_t i = KNOT_ANSWER; i <= KNOT_ADDITIONAL; ++i) { + const knot_pktsection_t *sec = knot_pkt_section(pkt, i); + for (unsigned k = 0; k < sec->count; ++k) { + const knot_rrset_t *rr = knot_pkt_rr(sec, k); + ttl = MIN(ttl, rr->ttl); + has_ttl = true; + } + } + return has_ttl ? ttl : 0; +} + + +void stash_pkt(const knot_pkt_t *pkt, const struct kr_query *qry, + const struct kr_request *req, const bool needs_pkt) +{ + /* In some cases, stash also the packet. */ + const bool is_negative = kr_response_classify(pkt) + & (PKT_NODATA|PKT_NXDOMAIN); + const struct kr_qflags * const qf = &qry->flags; + const bool want_negative = qf->DNSSEC_INSECURE || !qf->DNSSEC_WANT; + const bool want_pkt = qf->DNSSEC_BOGUS /*< useful for +cd answers */ + || (is_negative && want_negative) || needs_pkt; + + if (!want_pkt || !knot_wire_get_aa(pkt->wire) + || pkt->parsed != pkt->size /*< malformed packet; still can't detect KNOT_EFEWDATA */ + ) { + return; + } + + /* Compute rank. If cd bit is set or we got answer via non-validated + * forwarding, make the rank bad; otherwise it depends on flags. + * TODO: probably make validator attempt validation even with +cd. */ + uint8_t rank = KR_RANK_AUTH; + const bool risky_vldr = is_negative && qf->FORWARD && qf->CNAME; + /* ^^ CNAME'ed NXDOMAIN answer in forwarding mode can contain + * unvalidated records; original commit: d6e22f476. */ + if (knot_wire_get_cd(req->qsource.packet->wire) || qf->STUB || risky_vldr) { + kr_rank_set(&rank, KR_RANK_OMIT); + } else { + if (qf->DNSSEC_BOGUS) { + kr_rank_set(&rank, KR_RANK_BOGUS); + } else if (qf->DNSSEC_INSECURE) { + kr_rank_set(&rank, KR_RANK_INSECURE); + } else if (!qf->DNSSEC_WANT) { + /* no TAs at all, leave _RANK_AUTH */ + } else if (needs_pkt) { + /* All bad cases should be filtered above, + * at least the same way as pktcache in kresd 1.5.x. */ + kr_rank_set(&rank, KR_RANK_SECURE); + } else kr_assert(false); + } + + const uint16_t pkt_type = knot_pkt_qtype(pkt); + const knot_dname_t *owner = knot_pkt_qname(pkt); /* qname can't be compressed */ + + // LATER: nothing exists under NXDOMAIN. Implement that (optionally)? +#if 0 + if (knot_wire_get_rcode(pkt->wire) == KNOT_RCODE_NXDOMAIN + /* && !qf->DNSSEC_INSECURE */ ) { + pkt_type = KNOT_RRTYPE_NS; + } +#endif + + /* Construct the key under which the pkt will be stored. */ + struct key k_storage, *k = &k_storage; + knot_db_val_t key; + int ret = kr_dname_lf(k->buf, owner, false); + if (ret) { + /* A server might (incorrectly) reply with QDCOUNT=0. */ + kr_assert(owner == NULL); + return; + } + key = key_exact_type_maypkt(k, pkt_type); + + /* For now we stash the full packet byte-exactly as it came from upstream. */ + const uint16_t pkt_size = pkt->size; + knot_db_val_t val_new_entry = { + .data = NULL, + .len = offsetof(struct entry_h, data) + sizeof(pkt_size) + pkt->size, + }; + /* Prepare raw memory for the new entry and fill it. */ + struct kr_cache *cache = &req->ctx->cache; + ret = entry_h_splice(&val_new_entry, rank, key, k->type, pkt_type, + owner, qry, cache, qry->timestamp.tv_sec); + if (ret || kr_fails_assert(val_new_entry.data)) return; /* some aren't really errors */ + struct entry_h *eh = val_new_entry.data; + memset(eh, 0, offsetof(struct entry_h, data)); + eh->time = qry->timestamp.tv_sec; + eh->ttl = MAX(MIN(packet_ttl(pkt), cache->ttl_max), cache->ttl_min); + eh->rank = rank; + eh->is_packet = true; + eh->has_optout = qf->DNSSEC_OPTOUT; + memcpy(eh->data, &pkt_size, sizeof(pkt_size)); + memcpy(eh->data + sizeof(pkt_size), pkt->wire, pkt_size); + + WITH_VERBOSE(qry) { + auto_free char *type_str = kr_rrtype_text(pkt_type), + *owner_str = kr_dname_text(owner); + VERBOSE_MSG(qry, "=> stashed packet: rank 0%.2o, TTL %d, " + "%s %s (%d B)\n", + eh->rank, eh->ttl, + type_str, owner_str, (int)val_new_entry.len); + } +} + + +int answer_from_pkt(kr_layer_t *ctx, knot_pkt_t *pkt, uint16_t type, + const struct entry_h *eh, const void *eh_bound, uint32_t new_ttl) +{ + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + + const uint16_t msgid = knot_wire_get_id(pkt->wire); + + /* Ensure the wire buffer is large enough. Strategy: fit and at least double. */ + uint16_t pkt_len; + memcpy(&pkt_len, eh->data, sizeof(pkt_len)); + if (pkt_len > pkt->max_size) { + pkt->max_size = MIN(KNOT_WIRE_MAX_PKTSIZE, + MAX(pkt->max_size * 2, pkt_len)); + mm_free(&ctx->req->pool, pkt->wire); /* no-op, but... */ + pkt->wire = mm_alloc(&ctx->req->pool, pkt->max_size); + pkt->compr.wire = pkt->wire; + /* TODO: ^^ nicer way how to replace knot_pkt_t::wire ? */ + } + kr_require(pkt->max_size >= pkt_len); + + /* Copy answer and reparse it, but keep the original message id. */ + knot_pkt_clear(pkt); + memcpy(pkt->wire, eh->data + 2, pkt_len); + pkt->size = pkt_len; + int ret = knot_pkt_parse(pkt, 0); + if (ret == KNOT_EFEWDATA || ret == KNOT_EMALF) { + return kr_error(ENOENT); + /* LATER(opt): try harder to avoid stashing such packets */ + } + if (kr_fails_assert(ret == KNOT_EOK)) + return kr_error(ret); + knot_wire_set_id(pkt->wire, msgid); + + /* Add rank into the additional field. */ + for (size_t i = 0; i < pkt->rrset_count; ++i) { + kr_assert(!pkt->rr[i].additional); + uint8_t *rr_rank = mm_alloc(&pkt->mm, sizeof(*rr_rank)); + if (!rr_rank) { + return kr_error(ENOMEM); + } + *rr_rank = eh->rank; + pkt->rr[i].additional = rr_rank; + } + + /* Adjust TTL in each record. */ + const uint32_t drift = eh->ttl - new_ttl; + for (knot_section_t i = KNOT_ANSWER; i <= KNOT_ADDITIONAL; ++i) { + const knot_pktsection_t *sec = knot_pkt_section(pkt, i); + for (unsigned k = 0; k < sec->count; ++k) { + knot_rrset_t *rrs = // vv FIXME?? + /*const-cast*/(knot_rrset_t *)knot_pkt_rr(sec, k); + /* We need to be careful: due to enforcing minimum TTL + * on packet, some records may be below that value. + * We keep those records at TTL 0. */ + if (rrs->ttl >= drift) { + rrs->ttl -= drift; + } else { + rrs->ttl = 0; + } + } + } + + /* Finishing touches. TODO: perhaps factor out */ + struct kr_qflags * const qf = &qry->flags; + qf->EXPIRING = is_expiring(eh->ttl, new_ttl); + qf->CACHED = true; + qf->NO_MINIMIZE = true; + qf->DNSSEC_INSECURE = kr_rank_test(eh->rank, KR_RANK_INSECURE); + qf->DNSSEC_BOGUS = kr_rank_test(eh->rank, KR_RANK_BOGUS); + if (qf->DNSSEC_INSECURE || qf->DNSSEC_BOGUS) { + qf->DNSSEC_WANT = false; + } + qf->DNSSEC_OPTOUT = eh->has_optout; + VERBOSE_MSG(qry, "=> satisfied by exact packet: rank 0%.2o, new TTL %d\n", + eh->rank, new_ttl); + return kr_ok(); +} + diff --git a/lib/cache/entry_rr.c b/lib/cache/entry_rr.c new file mode 100644 index 0000000..3239e7e --- /dev/null +++ b/lib/cache/entry_rr.c @@ -0,0 +1,115 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/** @file + * Implementation of RRset (de)materialization, i.e. (de)serialization to storage + * format used in cache (some repeated fields are omitted). Prototypes in ./impl.h + */ + +#include "lib/cache/impl.h" + +void rdataset_dematerialize(const knot_rdataset_t *rds, uint8_t * restrict data) +{ + /* FIXME: either give up on even alignment and thus direct usability + * of rdatasets as they are in lmdb, or align inside cdb_* functions + * (request sizes one byte longer and shift iff on an odd address). */ + //if ((size_t)data & 1) VERBOSE_MSG(NULL, "dematerialize: odd address\n"); + //const uint8_t *data0 = data; + kr_require(data); + const uint16_t rr_count = rds ? rds->count : 0; + memcpy(data, &rr_count, sizeof(rr_count)); + data += sizeof(rr_count); + if (rr_count) { + memcpy(data, rds->rdata, rds->size); + data += rds->size; + } + //VERBOSE_MSG(NULL, "dematerialized to %d B\n", (int)(data - data0)); + (void)data; // silence analyzers +} + +/** Materialize a knot_rdataset_t from cache with given TTL. + * Return the number of bytes consumed or an error code. + */ +static int rdataset_materialize(knot_rdataset_t * restrict rds, const uint8_t * const data, + const uint8_t *data_bound, knot_mm_t *pool) +{ + if (kr_fails_assert(rds && data && data_bound && data_bound > data && !rds->rdata + /*&& !((size_t)data & 1)*/)) + return kr_error(EINVAL); + kr_assert(pool); /* not required, but that's our current usage; guard leaks */ + const uint8_t *d = data; /* iterates over the cache data */ + /* First sum up the sizes for wire format length. */ + /* TODO: we might overrun here already, but we need to trust cache anyway...*/ + rds->size = rdataset_dematerialized_size(d, &rds->count); + d += KR_CACHE_RR_COUNT_SIZE; + if (d + rds->size > data_bound) { + VERBOSE_MSG(NULL, "materialize: EILSEQ!\n"); + return kr_error(EILSEQ); + } + if (!rds->count) { /* avoid mm_alloc(pool, 0); etc. */ + rds->rdata = NULL; + return d - data; + } + rds->rdata = mm_alloc(pool, rds->size); + if (!rds->rdata) { + return kr_error(ENOMEM); + } + memcpy(rds->rdata, d, rds->size); + d += rds->size; + //VERBOSE_MSG(NULL, "materialized from %d B\n", (int)(d - data)); + return d - data; +} + +int kr_cache_materialize(knot_rdataset_t *dst, const struct kr_cache_p *ref, + knot_mm_t *pool) +{ + struct entry_h *eh = ref->raw_data; + return rdataset_materialize(dst, eh->data, ref->raw_bound, pool); +} + + +int entry2answer(struct answer *ans, int id, + const struct entry_h *eh, const uint8_t *eh_bound, + const knot_dname_t *owner, uint16_t type, uint32_t new_ttl) +{ + /* We assume it's zeroed. Do basic sanity check. */ + const bool not_ok = ans->rrsets[id].set.rr || ans->rrsets[id].sig_rds.rdata + || (type == KNOT_RRTYPE_NSEC && ans->nsec_p.raw) + || (type == KNOT_RRTYPE_NSEC3 && !ans->nsec_p.raw); + if (kr_fails_assert(!not_ok)) + return kr_error(EINVAL); + /* Materialize the base RRset. */ + knot_rrset_t *rr = ans->rrsets[id].set.rr + = knot_rrset_new(owner, type, KNOT_CLASS_IN, new_ttl, ans->mm); + if (kr_fails_assert(rr)) + return kr_error(ENOMEM); + int ret = rdataset_materialize(&rr->rrs, eh->data, eh_bound, ans->mm); + if (kr_fails_assert(ret >= 0)) goto fail; + size_t data_off = ret; + ans->rrsets[id].set.rank = eh->rank; + ans->rrsets[id].set.expiring = is_expiring(eh->ttl, new_ttl); + /* Materialize the RRSIG RRset for the answer in (pseudo-)packet. */ + bool want_rrsigs = true; /* LATER(optim.): might be omitted in some cases. */ + if (want_rrsigs) { + ret = rdataset_materialize(&ans->rrsets[id].sig_rds, eh->data + data_off, + eh_bound, ans->mm); + if (kr_fails_assert(ret >= 0)) goto fail; + /* Sanity check: we consumed exactly all data. */ + int unused_bytes = eh_bound - (uint8_t *)eh->data - data_off - ret; + if (kr_fails_assert(unused_bytes == 0)) { + kr_log_error(CACHE, "entry2answer ERROR: unused bytes: %d\n", + unused_bytes); + ret = kr_error(EILSEQ); + goto fail; /* to be on the safe side */ + } + } + return kr_ok(); +fail: + /* Cleanup the item that we might've (partially) written to. */ + knot_rrset_free(ans->rrsets[id].set.rr, ans->mm); + knot_rdataset_clear(&ans->rrsets[id].sig_rds, ans->mm); + memset(&ans->rrsets[id], 0, sizeof(ans->rrsets[id])); + return kr_error(ret); +} + diff --git a/lib/cache/impl.h b/lib/cache/impl.h new file mode 100644 index 0000000..305f36e --- /dev/null +++ b/lib/cache/impl.h @@ -0,0 +1,439 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/** @file + * Header internal for cache implementation(s). + * Only LMDB works for now. + */ +#pragma once + +#include <stdbool.h> +#include <stdint.h> + +#include <libdnssec/error.h> +#include <libdnssec/nsec.h> +#include <libknot/consts.h> +#include <libknot/db/db.h> +#include <libknot/dname.h> + +#include "contrib/cleanup.h" +#include "contrib/murmurhash3/murmurhash3.h" /* hash() for nsec_p_hash() */ +#include "lib/cache/cdb_api.h" +#include "lib/resolve.h" + +/* Cache entry values - binary layout. + * + * It depends on type which is recognizable by the key. + * Code depending on the contents of the key is marked by CACHE_KEY_DEF. + * + * 'E' entry (exact hit): + * - ktype == NS: struct entry_apex - multiple types inside (NS and xNAME); + * - ktype != NS: struct entry_h + * * is_packet: uint16_t length, the rest is opaque and handled by ./entry_pkt.c + * * otherwise RRset + its RRSIG set (possibly empty). + * '1' or '3' entry (NSEC or NSEC3) + * - struct entry_h, contents is the same as for exact hit + * - flags don't make sense there + */ + +struct entry_h { + uint32_t time; /**< The time of inception. */ + uint32_t ttl; /**< TTL at inception moment. Assuming it fits into int32_t ATM. */ + uint8_t rank : 6; /**< See enum kr_rank */ + bool is_packet : 1; /**< Negative-answer packet for insecure/bogus name. */ + bool has_optout : 1; /**< Only for packets; persisted DNSSEC_OPTOUT. */ + uint8_t _pad; /**< We need even alignment for data now. */ + uint8_t data[]; +/* Well, we don't really need packing or alignment changes, + * but due to LMDB the whole structure may not be stored at an aligned address, + * and we need compilers (for non-x86) to know it to avoid SIGBUS (test: UBSAN). */ +} __attribute__ ((packed,aligned(1))); +struct entry_apex; + +/** Check basic consistency of entry_h for 'E' entries, not looking into ->data. + * (for is_packet the length of data is checked) + */ +KR_EXPORT +struct entry_h * entry_h_consistent_E(knot_db_val_t data, uint16_t type); + +struct entry_apex * entry_apex_consistent(knot_db_val_t val); + +/** Consistency check, ATM common for NSEC and NSEC3. */ +static inline struct entry_h * entry_h_consistent_NSEC(knot_db_val_t data) +{ + /* ATM it's enough to just extend the checks for exact entries. */ + const struct entry_h *eh = entry_h_consistent_E(data, KNOT_RRTYPE_NSEC); + bool ok = eh != NULL; + ok = ok && !eh->is_packet && !eh->has_optout; + return ok ? /*const-cast*/(struct entry_h *)eh : NULL; +} + +static inline struct entry_h * entry_h_consistent(knot_db_val_t data, uint16_t type) +{ + switch (type) { + case KNOT_RRTYPE_NSEC: + case KNOT_RRTYPE_NSEC3: + return entry_h_consistent_NSEC(data); + default: + return entry_h_consistent_E(data, type); + } +} + +/* nsec_p* - NSEC* chain parameters */ + +static inline int nsec_p_rdlen(const uint8_t *rdata) +{ + //TODO: do we really need the zero case? + return rdata ? 5 + rdata[4] : 0; /* rfc5155 4.2 and 3.2. */ +} +static const int NSEC_P_MAXLEN = sizeof(uint32_t) + 5 + 255; // TODO: remove?? + +/** Hash of NSEC3 parameters, used as a tag to separate different chains for same zone. */ +typedef uint32_t nsec_p_hash_t; +static inline nsec_p_hash_t nsec_p_mkHash(const uint8_t *nsec_p) +{ + kr_require(nsec_p && !(KNOT_NSEC3_FLAG_OPT_OUT & nsec_p[1])); + return hash((const char *)nsec_p, nsec_p_rdlen(nsec_p)); +} + +/** NSEC* parameters for the chain. */ +struct nsec_p { + const uint8_t *raw; /**< Pointer to raw NSEC3 parameters; NULL for NSEC. */ + nsec_p_hash_t hash; /**< Hash of `raw`, used for cache keys. */ + dnssec_nsec3_params_t libknot; /**< Format for libknot; owns malloced memory! */ +}; + + + +/** LATER(optim.): this is overshot, but struct key usage should be cheap ATM. */ +#define KR_CACHE_KEY_MAXLEN (KNOT_DNAME_MAXLEN + 100) /* CACHE_KEY_DEF */ + +struct key { + const knot_dname_t *zname; /**< current zone name (points within qry->sname) */ + uint8_t zlf_len; /**< length of current zone's lookup format */ + + /** Corresponding key type; e.g. NS for CNAME. + * Note: NSEC type is ambiguous (exact and range key). */ + uint16_t type; + /** The key data start at buf+1, and buf[0] contains some length. + * For details see key_exact* and key_NSEC* functions. */ + uint8_t buf[KR_CACHE_KEY_MAXLEN]; + /* LATER(opt.): ^^ probably change the anchoring, so that kr_dname_lf() + * doesn't need to move data after knot_dname_lf(). */ +}; + +static inline size_t key_nwz_off(const struct key *k) +{ + /* CACHE_KEY_DEF: zone name lf + 0 ('1' or '3'). + * NSEC '1' case continues just with the name within zone. */ + return k->zlf_len + 2; +} +static inline size_t key_nsec3_hash_off(const struct key *k) +{ + /* CACHE_KEY_DEF NSEC3: tag (nsec_p_hash_t) + 20 bytes NSEC3 name hash) */ + return key_nwz_off(k) + sizeof(nsec_p_hash_t); +} +/** Hash is always SHA1; I see no plans to standardize anything else. + * https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml#dnssec-nsec3-parameters-3 + */ +static const int NSEC3_HASH_LEN = 20, + NSEC3_HASH_TXT_LEN = 32; + +/** Finish constructing string key for for exact search. + * It's assumed that kr_dname_lf(k->buf, owner, *) had been ran. + */ +knot_db_val_t key_exact_type_maypkt(struct key *k, uint16_t type); + +/** Like key_exact_type_maypkt but with extra checks if used for RRs only. */ +static inline knot_db_val_t key_exact_type(struct key *k, uint16_t type) +{ + switch (type) { + /* Sanity check: forbidden types represented in other way(s). */ + case KNOT_RRTYPE_NSEC: + case KNOT_RRTYPE_NSEC3: + kr_assert(false); + return (knot_db_val_t){ NULL, 0 }; + } + return key_exact_type_maypkt(k, type); +} + + +/* entry_h chaining; implementation in ./entry_list.c */ + +enum { ENTRY_APEX_NSECS_CNT = 2 }; + +/** Header of 'E' entry with ktype == NS. Inside is private to ./entry_list.c + * + * We store xNAME at NS type to lower the number of searches in closest_NS(). + * CNAME is only considered for equal name, of course. + * We also store NSEC* parameters at NS type. + */ +struct entry_apex { + /* ENTRY_H_FLAGS */ + bool has_ns : 1; + bool has_cname : 1; + bool has_dname : 1; + + uint8_t pad_; /**< 1 byte + 2 bytes + x bytes would be weird; let's do 2+2+x. */ + + /** We have two slots for NSEC* parameters. + * + * This array describes how they're filled; + * values: 0: none, 1: NSEC, 3: NSEC3. + * + * Two slots are a compromise to smoothly handle normal rollovers + * (either changing NSEC3 parameters or between NSEC and NSEC3). */ + int8_t nsecs[ENTRY_APEX_NSECS_CNT]; + uint8_t data[]; + /* XXX: if not first, stamp of last being the first? + * Purpose: save cache operations if rolled the algo/params long ago. */ +}; + +/** Indices for decompressed entry_list_t. */ +enum EL { + EL_NS = ENTRY_APEX_NSECS_CNT, + EL_CNAME, + EL_DNAME, + EL_LENGTH +}; +/** Decompressed entry_apex. It's an array of unparsed entry_h references. + * Note: arrays are passed "by reference" to functions (in C99). */ +typedef knot_db_val_t entry_list_t[EL_LENGTH]; + +static inline uint16_t EL2RRTYPE(enum EL i) +{ + switch (i) { + case EL_NS: return KNOT_RRTYPE_NS; + case EL_CNAME: return KNOT_RRTYPE_CNAME; + case EL_DNAME: return KNOT_RRTYPE_DNAME; + default: kr_assert(false); return 0; + } +} + +/** There may be multiple entries within, so rewind `val` to the one we want. + * + * ATM there are multiple types only for the NS ktype - it also accommodates xNAMEs. + * \note `val->len` represents the bound of the whole list, not of a single entry. + * \note in case of ENOENT, `val` is still rewound to the beginning of the next entry. + * \return error code + * TODO: maybe get rid of this API? + */ +int entry_h_seek(knot_db_val_t *val, uint16_t type); + +/** Prepare space to insert an entry. + * + * Some checks are performed (rank, TTL), the current entry in cache is copied + * with a hole ready for the new entry (old one of the same type is cut out). + * + * \param val_new_entry The only changing parameter; ->len is read, ->data written. + * \return error code + */ +int entry_h_splice( + knot_db_val_t *val_new_entry, uint8_t rank, + const knot_db_val_t key, const uint16_t ktype, const uint16_t type, + const knot_dname_t *owner/*log only*/, + const struct kr_query *qry, struct kr_cache *cache, uint32_t timestamp); + +/** Parse an entry_apex into individual items. @return error code. */ +KR_EXPORT int entry_list_parse(const knot_db_val_t val, entry_list_t list); + +static inline size_t to_even(size_t n) +{ + return n + (n & 1); +} + +static inline int entry_list_serial_size(const entry_list_t list) +{ + int size = offsetof(struct entry_apex, data); + for (int i = 0; i < EL_LENGTH; ++i) { + size += to_even(list[i].len); + } + return size; +} + +/** Fill contents of an entry_apex. + * + * @note NULL pointers are overwritten - caller may like to fill the space later. + */ +void entry_list_memcpy(struct entry_apex *ea, entry_list_t list); + + + +/* Packet caching; implementation in ./entry_pkt.c */ + +/** Stash the packet into cache (if suitable, etc.) + * \param needs_pkt we need the packet due to not stashing some RRs; + * see stash_rrset() for details + * It assumes check_dname_for_lf(). */ +void stash_pkt(const knot_pkt_t *pkt, const struct kr_query *qry, + const struct kr_request *req, bool needs_pkt); + +/** Try answering from packet cache, given an entry_h. + * + * This assumes the TTL is OK and entry_h_consistent, but it may still return error. + * On success it handles all the rest, incl. qry->flags. + */ +int answer_from_pkt(kr_layer_t *ctx, knot_pkt_t *pkt, uint16_t type, + const struct entry_h *eh, const void *eh_bound, uint32_t new_ttl); + + +/** Record is expiring if it has less than 1% TTL (or less than 5s) */ +static inline bool is_expiring(uint32_t orig_ttl, uint32_t new_ttl) +{ + int64_t nttl = new_ttl; /* avoid potential over/under-flow */ + return 100 * (nttl - 5) < orig_ttl; +} + +/** Returns signed result so you can inspect how much stale the RR is. + * + * @param owner name for stale-serving decisions. You may pass NULL to disable stale. + * @note: NSEC* uses zone name ATM; for NSEC3 the owner may not even be knowable. + * @param type for stale-serving. + */ +int32_t get_new_ttl(const struct entry_h *entry, const struct kr_query *qry, + const knot_dname_t *owner, uint16_t type, uint32_t now); + + +/* RRset (de)materialization; implementation in ./entry_rr.c */ + +/** Size of the RR count field */ +#define KR_CACHE_RR_COUNT_SIZE sizeof(uint16_t) + +/** Compute size of serialized rdataset. NULL is accepted as empty set. */ +static inline int rdataset_dematerialize_size(const knot_rdataset_t *rds) +{ + return KR_CACHE_RR_COUNT_SIZE + (rds == NULL ? 0 : rds->size); +} + +/** Analyze the length of a dematerialized rdataset. + * Note that in the data it's KR_CACHE_RR_COUNT_SIZE and then this returned size. */ +static inline int rdataset_dematerialized_size(const uint8_t *data, uint16_t *rdataset_count) +{ + uint16_t count; + static_assert(sizeof(count) == KR_CACHE_RR_COUNT_SIZE, + "Unexpected KR_CACHE_RR_COUNT_SIZE."); + memcpy(&count, data, sizeof(count)); + const uint8_t *rdata = data + sizeof(count); + if (rdataset_count) // memcpy is safe for unaligned case (on non-x86) + memcpy(rdataset_count, &count, sizeof(count)); + for (int i = 0; i < count; ++i) { + __typeof__(((knot_rdata_t *)NULL)->len) len; // memcpy as above + memcpy(&len, rdata + offsetof(knot_rdata_t, len), sizeof(len)); + rdata += knot_rdata_size(len); + } + return rdata - (data + sizeof(count)); +} + +/** Serialize an rdataset. It may be NULL as short-hand for empty. */ +void rdataset_dematerialize(const knot_rdataset_t *rds, uint8_t * restrict data); + + +/** Partially constructed answer when gathering RRsets from cache. */ +struct answer { + int rcode; /**< PKT_NODATA, etc. */ + struct nsec_p nsec_p; /**< Don't mix different NSEC* parameters in one answer. */ + knot_mm_t *mm; /**< Allocator for rrsets */ + struct answer_rrset { + ranked_rr_array_entry_t set; /**< set+rank for the main data */ + knot_rdataset_t sig_rds; /**< RRSIG data, if any */ + } rrsets[1+1+3]; /**< see AR_ANSWER and friends; only required records are filled */ +}; +enum { + AR_ANSWER = 0, /**< Positive answer record. It might be wildcard-expanded. */ + AR_SOA, /**< SOA record. */ + AR_NSEC, /**< NSEC* covering or matching the SNAME (next closer name in NSEC3 case). */ + AR_WILD, /**< NSEC* covering or matching the source of synthesis. */ + AR_CPE, /**< NSEC3 matching the closest provable encloser. */ +}; + +/** Materialize RRset + RRSIGs into ans->rrsets[id]. + * LATER(optim.): it's slightly wasteful that we allocate knot_rrset_t for the packet + * + * \return error code. They are all bad conditions and "guarded" by kresd's assertions. + */ +int entry2answer(struct answer *ans, int id, + const struct entry_h *eh, const uint8_t *eh_bound, + const knot_dname_t *owner, uint16_t type, uint32_t new_ttl); + + +/* Preparing knot_pkt_t for cache answer from RRs; implementation in ./knot_pkt.c */ + +/** Prepare answer packet to be filled by RRs (without RR data in wire). */ +int pkt_renew(knot_pkt_t *pkt, const knot_dname_t *name, uint16_t type); + +/** Append RRset + its RRSIGs into the current section (*shallow* copy), with given rank. + * + * \note it works with empty set as well (skipped) + * \note pkt->wire is not updated in any way + * \note KNOT_CLASS_IN is assumed + * \note Whole RRsets are put into the pseudo-packet; + * normal parsed packets would only contain single-RR sets. + */ +int pkt_append(knot_pkt_t *pkt, const struct answer_rrset *rrset, uint8_t rank); + + + +/* NSEC (1) stuff. Implementation in ./nsec1.c */ + +/** Construct a string key for for NSEC (1) predecessor-search. + * \param add_wildcard Act as if the name was extended by "*." + * \note k->zlf_len is assumed to have been correctly set */ +knot_db_val_t key_NSEC1(struct key *k, const knot_dname_t *name, bool add_wildcard); + +/** Closest encloser check for NSEC (1). + * To understand the interface, see the call point. + * \param k space to store key + input: zname and zlf_len + * \return 0: success; >0: try other (NSEC3); <0: exit cache immediately. */ +int nsec1_encloser(struct key *k, struct answer *ans, + const int sname_labels, int *clencl_labels, + knot_db_val_t *cover_low_kwz, knot_db_val_t *cover_hi_kwz, + const struct kr_query *qry, struct kr_cache *cache); + +/** Source of synthesis (SS) check for NSEC (1). + * To understand the interface, see the call point. + * \return 0: continue; <0: exit cache immediately; + * AR_SOA: skip to adding SOA (SS was covered or matched for NODATA). */ +int nsec1_src_synth(struct key *k, struct answer *ans, const knot_dname_t *clencl_name, + knot_db_val_t cover_low_kwz, knot_db_val_t cover_hi_kwz, + const struct kr_query *qry, struct kr_cache *cache); + + +/* NSEC3 stuff. Implementation in ./nsec3.c */ + +/** Construct a string key for for NSEC3 predecessor-search, from an NSEC3 name. + * \note k->zlf_len is assumed to have been correctly set */ +knot_db_val_t key_NSEC3(struct key *k, const knot_dname_t *nsec3_name, + const nsec_p_hash_t nsec_p_hash); + +/** TODO. See nsec1_encloser(...) */ +int nsec3_encloser(struct key *k, struct answer *ans, + const int sname_labels, int *clencl_labels, + const struct kr_query *qry, struct kr_cache *cache); + +/** TODO. See nsec1_src_synth(...) */ +int nsec3_src_synth(struct key *k, struct answer *ans, const knot_dname_t *clencl_name, + const struct kr_query *qry, struct kr_cache *cache); + + + +#define VERBOSE_MSG(qry, ...) kr_log_q((qry), CACHE, ## __VA_ARGS__) +#define WITH_VERBOSE(qry) if (kr_log_is_debug_qry(CACHE, (qry))) + +/** Shorthand for operations on cache backend */ +#define cache_op(cache, op, ...) (cache)->api->op((cache)->db, &(cache)->stats, ## __VA_ARGS__) + + +static inline uint16_t get_uint16(const void *address) +{ + uint16_t tmp; + memcpy(&tmp, address, sizeof(tmp)); + return tmp; +} + +/** Useful pattern, especially as void-pointer arithmetic isn't standard-compliant. */ +static inline uint8_t * knot_db_val_bound(knot_db_val_t val) +{ + return (uint8_t *)val.data + val.len; +} + diff --git a/lib/cache/knot_pkt.c b/lib/cache/knot_pkt.c new file mode 100644 index 0000000..31fa7e9 --- /dev/null +++ b/lib/cache/knot_pkt.c @@ -0,0 +1,94 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/** @file + * Implementation of preparing knot_pkt_t for filling with RRs. + * Prototypes in ./impl.h + */ + +#include "lib/cache/impl.h" + +int pkt_renew(knot_pkt_t *pkt, const knot_dname_t *name, uint16_t type) +{ + /* Update packet question if needed. */ + if (!knot_dname_is_equal(knot_pkt_qname(pkt), name) + || knot_pkt_qtype(pkt) != type || knot_pkt_qclass(pkt) != KNOT_CLASS_IN) { + int ret = kr_pkt_recycle(pkt); + if (ret) return kr_error(ret); + ret = knot_pkt_put_question(pkt, name, KNOT_CLASS_IN, type); + if (ret) return kr_error(ret); + } + + pkt->parsed = pkt->size = KR_PKT_SIZE_NOWIRE; + knot_wire_set_qr(pkt->wire); + knot_wire_set_aa(pkt->wire); + return kr_ok(); +} + +/** Reserve space for additional `count` RRsets. + * \note pkt->rr_info gets correct length but is always zeroed + */ +static int pkt_alloc_space(knot_pkt_t *pkt, int count) +{ + size_t allocd_orig = pkt->rrset_allocd; + if (pkt->rrset_count + count <= allocd_orig) { + return kr_ok(); + } + /* A simple growth strategy, amortized O(count). */ + pkt->rrset_allocd = MAX( + pkt->rrset_count + count, + pkt->rrset_count + allocd_orig); + + pkt->rr = mm_realloc(&pkt->mm, pkt->rr, + sizeof(pkt->rr[0]) * pkt->rrset_allocd, + sizeof(pkt->rr[0]) * allocd_orig); + if (!pkt->rr) { + return kr_error(ENOMEM); + } + /* Allocate pkt->rr_info to be certain, but just leave it zeroed. */ + mm_free(&pkt->mm, pkt->rr_info); + pkt->rr_info = mm_calloc(&pkt->mm, pkt->rrset_allocd, sizeof(pkt->rr_info[0])); + if (!pkt->rr_info) { + return kr_error(ENOMEM); + } + return kr_ok(); +} + +int pkt_append(knot_pkt_t *pkt, const struct answer_rrset *rrset, uint8_t rank) +{ + /* allocate space, to be sure */ + int rrset_cnt = (rrset->set.rr->rrs.count > 0) + (rrset->sig_rds.count > 0); + int ret = pkt_alloc_space(pkt, rrset_cnt); + if (ret) return kr_error(ret); + /* write both sets */ + const knot_rdataset_t *rdss[2] = { &rrset->set.rr->rrs, &rrset->sig_rds }; + for (int i = 0; i < rrset_cnt; ++i) { + if (kr_fails_assert(rdss[i]->count)) + return kr_error(EINVAL); + /* allocate rank */ + uint8_t *rr_rank = mm_alloc(&pkt->mm, sizeof(*rr_rank)); + if (!rr_rank) return kr_error(ENOMEM); + *rr_rank = (i == 0) ? rank : (KR_RANK_OMIT | KR_RANK_AUTH); + /* rank for RRSIGs isn't really useful: ^^ */ + if (i == 0) { + pkt->rr[pkt->rrset_count] = *rrset->set.rr; + pkt->rr[pkt->rrset_count].additional = rr_rank; + } else { + /* append the RR array */ + pkt->rr[pkt->rrset_count] = (knot_rrset_t){ + .owner = knot_dname_copy(rrset->set.rr->owner, &pkt->mm), + /* ^^ well, another copy isn't really needed */ + .ttl = rrset->set.rr->ttl, + .type = KNOT_RRTYPE_RRSIG, + .rclass = KNOT_CLASS_IN, + .rrs = *rdss[i], + .additional = rr_rank, + }; + } + ++pkt->rrset_count; + ++(pkt->sections[pkt->current].count); + } + return kr_ok(); +} + diff --git a/lib/cache/nsec1.c b/lib/cache/nsec1.c new file mode 100644 index 0000000..4554303 --- /dev/null +++ b/lib/cache/nsec1.c @@ -0,0 +1,488 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/** @file + * Implementation of NSEC (1) handling. Prototypes in ./impl.h + */ + +#include "lib/cache/impl.h" +#include "lib/dnssec/nsec.h" +#include "lib/layer/iterate.h" + + +/** Reconstruct a name into a buffer (assuming length at least KNOT_DNAME_MAXLEN). + * \return kr_ok() or error code (<0). */ +static int dname_wire_reconstruct(knot_dname_t *buf, const struct key *k, + knot_db_val_t kwz) +{ + /* Reconstruct from key: first the ending, then zone name. */ + int ret = knot_dname_lf2wire(buf, kwz.len, kwz.data); + if (kr_fails_assert(ret >= 0)) { + VERBOSE_MSG(NULL, "=> NSEC: LF2wire ret = %d\n", ret); + return ret; + } + /* The last written byte is the zero label for root -> overwrite. */ + knot_dname_t *zone_start = buf + ret - 1; + if (kr_fails_assert(*zone_start == '\0')) + return kr_error(EFAULT); + ret = knot_dname_to_wire(zone_start, k->zname, KNOT_DNAME_MAXLEN - kwz.len); + if (kr_fails_assert(ret == k->zlf_len + 1)) + return ret < 0 ? ret : kr_error(EILSEQ); + return kr_ok(); +} + + +knot_db_val_t key_NSEC1(struct key *k, const knot_dname_t *name, bool add_wildcard) +{ + /* we basically need dname_lf with two bytes added + * on a correct place within the name (the cut) */ + int ret; + const bool ok = k && name + && !(ret = kr_dname_lf(k->buf, name, add_wildcard)); + if (kr_fails_assert(ok)) + return (knot_db_val_t){ NULL, 0 }; + + uint8_t *begin = k->buf + 1 + k->zlf_len; /* one byte after zone's zero */ + uint8_t *end = k->buf + 1 + k->buf[0]; /* we don't use the final zero in key, + * but move it anyway */ + if (kr_fails_assert(end >= begin)) + return (knot_db_val_t){ NULL, 0 }; + int key_len; + if (end > begin) { + memmove(begin + 2, begin, end - begin); + key_len = k->buf[0] + 1; + } else { + key_len = k->buf[0] + 2; + } + /* CACHE_KEY_DEF: key == zone's dname_lf + '\0' + '1' + dname_lf + * of the name within the zone without the final 0. Iff the latter is empty, + * there's no zero to cut and thus the key_len difference. + */ + begin[0] = 0; + begin[1] = '1'; /* tag for NSEC1 */ + k->type = KNOT_RRTYPE_NSEC; + + /* + VERBOSE_MSG(NULL, "<> key_NSEC1; name: "); + kr_dname_print(name, add_wildcard ? "*." : "" , " "); + kr_log_debug(CACHE, "(zone name LF length: %d; total key length: %d)\n", + k->zlf_len, key_len); + */ + + return (knot_db_val_t){ k->buf + 1, key_len }; +} + + +/** Assuming that k1 < k4, find where k2 is. (Considers DNS wrap-around.) + * + * \return Intuition: position of k2 among kX. + * 0: k2 < k1; 1: k1 == k2; 2: k1 is a prefix of k2 < k4; + * 3: k1 < k2 < k4 (and not 2); 4: k2 == k4; 5: k2 > k4 + * \note k1.data may be NULL, meaning assumption that k1 < k2 and not a prefix + * (i.e. return code will be > 2) + */ +static int kwz_between(knot_db_val_t k1, knot_db_val_t k2, knot_db_val_t k4) +{ + kr_require(k2.data && k4.data); + /* CACHE_KEY_DEF; we need to beware of one key being a prefix of another */ + int ret_maybe; /**< result, assuming we confirm k2 < k4 */ + if (k1.data) { + const int cmp12 = memcmp(k1.data, k2.data, MIN(k1.len, k2.len)); + if (cmp12 == 0 && k1.len == k2.len) /* iff k1 == k2 */ + return 1; + if (cmp12 > 0 || (cmp12 == 0 && k1.len > k2.len)) /* iff k1 > k2 */ + return 0; + ret_maybe = cmp12 == 0 ? 2 : 3; + } else { + ret_maybe = 3; + } + if (k4.len == 0) { /* wrap-around */ + return k2.len > 0 ? ret_maybe : 4; + } else { + const int cmp24 = memcmp(k2.data, k4.data, MIN(k2.len, k4.len)); + if (cmp24 == 0 && k2.len == k4.len) /* iff k2 == k4 */ + return 4; + if (cmp24 > 0 || (cmp24 == 0 && k2.len > k4.len)) /* iff k2 > k4 */ + return 5; + return ret_maybe; + } +} + + +/** NSEC1 range search. + * + * \param key Pass output of key_NSEC1(k, ...) + * \param value[out] The raw data of the NSEC cache record (optional; consistency checked). + * \param exact_match[out] Whether the key was matched exactly or just covered (optional). + * \param kwz_low[out] Output the low end of covering NSEC, pointing within DB (optional). + * \param kwz_high[in,out] Storage for the high end of covering NSEC (optional). + * It's only set if !exact_match. + * \param new_ttl[out] New TTL of the NSEC (optional). + * \return Error message or NULL. + * \note The function itself does *no* bitmap checks, e.g. RFC 6840 sec. 4. + */ +static const char * find_leq_NSEC1(struct kr_cache *cache, const struct kr_query *qry, + const knot_db_val_t key, const struct key *k, knot_db_val_t *value, + bool *exact_match, knot_db_val_t *kwz_low, knot_db_val_t *kwz_high, + uint32_t *new_ttl) +{ + /* Do the cache operation. */ + const size_t nwz_off = key_nwz_off(k); + if (kr_fails_assert(key.data && key.len >= nwz_off)) + return "range search ERROR"; + knot_db_val_t key_nsec = key; + knot_db_val_t val = { NULL, 0 }; + int ret = cache_op(cache, read_leq, &key_nsec, &val); + if (ret < 0) { + if (kr_fails_assert(ret == kr_error(ENOENT))) { + return "range search ERROR"; + } else { + return "range search miss"; + } + } + if (value) { + *value = val; + } + /* Check consistency, TTL, rank. */ + const bool is_exact = (ret == 0); + if (exact_match) { + *exact_match = is_exact; + } + const struct entry_h *eh = entry_h_consistent_NSEC(val); + if (!eh) { + /* This might be just finding something else than NSEC1 entry, + * in case we searched before the very first one in the zone. */ + return "range search found inconsistent entry"; + } + /* Passing just zone name instead of owner, as we don't + * have it reconstructed at this point. */ + int32_t new_ttl_ = get_new_ttl(eh, qry, k->zname, KNOT_RRTYPE_NSEC, + qry->timestamp.tv_sec); + if (new_ttl_ < 0 || !kr_rank_test(eh->rank, KR_RANK_SECURE)) { + return "range search found stale or insecure entry"; + /* TODO: remove the stale record *and* retry, + * in case we haven't run off. Perhaps start by in_zone check. */ + } + if (new_ttl) { + *new_ttl = new_ttl_; + } + if (kwz_low) { + *kwz_low = (knot_db_val_t){ + .data = (uint8_t *)key_nsec.data + nwz_off, + .len = key_nsec.len - nwz_off, + }; /* CACHE_KEY_DEF */ + } + if (is_exact) { + /* Nothing else to do. */ + return NULL; + } + /* The NSEC starts strictly before our target name; + * now check that it still belongs into that zone. */ + const bool nsec_in_zone = key_nsec.len >= nwz_off + /* CACHE_KEY_DEF */ + && memcmp(key.data, key_nsec.data, nwz_off) == 0; + if (!nsec_in_zone) { + return "range search miss (!nsec_in_zone)"; + } + /* We know it starts before sname, so let's check the other end. + * 1. construct the key for the next name - kwz_hi. */ + /* it's *full* name ATM */ + /* Technical complication: memcpy is safe for unaligned case (on non-x86) */ + __typeof__(((knot_rdata_t *)NULL)->len) next_len; + const uint8_t *next_data; + { /* next points to knot_rdata_t but possibly unaligned */ + const uint8_t *next = eh->data + KR_CACHE_RR_COUNT_SIZE; + memcpy(&next_len, next + offsetof(knot_rdata_t, len), sizeof(next_len)); + next_data = next + offsetof(knot_rdata_t, data); + } + if (kr_fails_assert(KR_CACHE_RR_COUNT_SIZE == 2 && get_uint16(eh->data) != 0)) { + return "ERROR"; /* TODO: more checks? */ + } + /* + WITH_VERBOSE { + VERBOSE_MSG(qry, "=> NSEC: next name: "); + kr_dname_print(next, "", "\n"); + } + */ + knot_dname_t ch_buf[KNOT_DNAME_MAXLEN]; + knot_dname_t *chs = kwz_high ? kwz_high->data : ch_buf; + if (kr_fails_assert(chs)) + return "EINVAL"; + + { + /* Lower-case chs; see also RFC 6840 5.1. + * LATER(optim.): we do lots of copying etc. */ + knot_dname_t lower_buf[KNOT_DNAME_MAXLEN]; + ret = knot_dname_to_wire(lower_buf, next_data, + MIN(next_len, KNOT_DNAME_MAXLEN)); + if (ret < 0) { /* _ESPACE */ + return "range search found record with incorrect contents"; + } + knot_dname_to_lower(lower_buf); + ret = kr_dname_lf(chs, lower_buf, false); + } + + if (kr_fails_assert(ret == 0)) + return "ERROR"; + knot_db_val_t kwz_hi = { /* skip the zone name */ + .data = chs + 1 + k->zlf_len, + .len = chs[0] - k->zlf_len, + }; + if (kr_fails_assert((ssize_t)(kwz_hi.len) >= 0)) + return "ERROR"; + /* 2. do the actual range check. */ + const knot_db_val_t kwz_sname = { + .data = (void *)/*const-cast*/(k->buf + 1 + nwz_off), + .len = k->buf[0] - k->zlf_len, + }; + if (kr_fails_assert((ssize_t)(kwz_sname.len) >= 0)) + return "ERROR"; + bool covers = /* we know for sure that the low end is before kwz_sname */ + 3 == kwz_between((knot_db_val_t){ NULL, 0 }, kwz_sname, kwz_hi); + if (!covers) { + return "range search miss (!covers)"; + } + if (kwz_high) { + *kwz_high = kwz_hi; + } + return NULL; +} + + +int nsec1_encloser(struct key *k, struct answer *ans, + const int sname_labels, int *clencl_labels, + knot_db_val_t *cover_low_kwz, knot_db_val_t *cover_hi_kwz, + const struct kr_query *qry, struct kr_cache *cache) +{ + static const int ESKIP = ABS(ENOENT); + /* Basic sanity check. */ + const bool ok = k && ans && clencl_labels && cover_low_kwz && cover_hi_kwz + && qry && cache; + if (kr_fails_assert(ok)) + return kr_error(EINVAL); + + /* Find a previous-or-equal name+NSEC in cache covering the QNAME, + * checking TTL etc. */ + knot_db_val_t key = key_NSEC1(k, qry->sname, false); + knot_db_val_t val = { NULL, 0 }; + bool exact_match; + uint32_t new_ttl; + const char *err = find_leq_NSEC1(cache, qry, key, k, &val, + &exact_match, cover_low_kwz, cover_hi_kwz, &new_ttl); + if (err) { + VERBOSE_MSG(qry, "=> NSEC sname: %s\n", err); + return ESKIP; + } + + /* Get owner name of the record. */ + const knot_dname_t *owner; + knot_dname_t owner_buf[KNOT_DNAME_MAXLEN]; + if (exact_match) { + owner = qry->sname; + } else { + int ret = dname_wire_reconstruct(owner_buf, k, *cover_low_kwz); + if (unlikely(ret)) return ESKIP; + owner = owner_buf; + } + /* Basic checks OK -> materialize data. */ + { + const struct entry_h *nsec_eh = val.data; + int ret = entry2answer(ans, AR_NSEC, nsec_eh, knot_db_val_bound(val), + owner, KNOT_RRTYPE_NSEC, new_ttl); + if (ret) return kr_error(ret); + } + + /* Final checks, split for matching vs. covering our sname. */ + const knot_rrset_t *nsec_rr = ans->rrsets[AR_NSEC].set.rr; + const uint8_t *bm = knot_nsec_bitmap(nsec_rr->rrs.rdata); + uint16_t bm_size = knot_nsec_bitmap_len(nsec_rr->rrs.rdata); + if (kr_fails_assert(bm)) + return kr_error(EFAULT); + + if (exact_match) { + if (kr_nsec_bitmap_nodata_check(bm, bm_size, qry->stype, nsec_rr->owner) != 0) { + VERBOSE_MSG(qry, + "=> NSEC sname: match but failed type check\n"); + return ESKIP; + } + /* NODATA proven; just need to add SOA+RRSIG later */ + VERBOSE_MSG(qry, "=> NSEC sname: match proved NODATA, new TTL %d\n", + new_ttl); + ans->rcode = PKT_NODATA; + return kr_ok(); + } /* else */ + + /* Inexact match. First check if sname is delegated by that NSEC. */ + const int nsec_matched = knot_dname_matched_labels(nsec_rr->owner, qry->sname); + const bool is_sub = nsec_matched == knot_dname_labels(nsec_rr->owner, NULL); + if (is_sub && kr_nsec_children_in_zone_check(bm, bm_size) != 0) { + VERBOSE_MSG(qry, "=> NSEC sname: covered but delegated (or error)\n"); + return ESKIP; + } + /* NXDOMAIN proven *except* for wildcards. */ + WITH_VERBOSE(qry) { + auto_free char *owner_str = kr_dname_text(nsec_rr->owner), + *next_str = kr_dname_text(knot_nsec_next(nsec_rr->rrs.rdata)); + VERBOSE_MSG(qry, "=> NSEC sname: covered by: %s -> %s, new TTL %d\n", + owner_str, next_str, new_ttl); + } + + /* Find label count of the closest encloser. + * Both endpoints in an NSEC do exist (though possibly in a child zone) + * and any prefixes of those names as well (empty non-terminals), + * but nothing else exists inside this "triangle". + * + * Note that we have to lower-case the next name for comparison, + * even though we have canonicalized NSEC already; see RFC 6840 5.1. + * LATER(optim.): it might be faster to use the LFs we already have. + */ + knot_dname_t next[KNOT_DNAME_MAXLEN]; + int ret = knot_dname_to_wire(next, knot_nsec_next(nsec_rr->rrs.rdata), sizeof(next)); + if (kr_fails_assert(ret >= 0)) + return kr_error(ret); + knot_dname_to_lower(next); + *clencl_labels = MAX( + nsec_matched, + knot_dname_matched_labels(qry->sname, next) + ); + + /* Empty non-terminals don't need to have + * a matching NSEC record. */ + if (sname_labels == *clencl_labels) { + ans->rcode = PKT_NODATA; + VERBOSE_MSG(qry, + "=> NSEC sname: empty non-terminal by the same RR\n"); + } else { + ans->rcode = PKT_NXDOMAIN; + } + return kr_ok(); +} + +/** Verify non-existence after kwz_between() call. */ +static bool nonexistence_ok(int cmp, const knot_rrset_t *rrs) +{ + if (cmp == 3) { + return true; + } + if (cmp != 2) { + return false; + } + const uint8_t *bm = knot_nsec_bitmap(rrs->rrs.rdata); + uint16_t bm_size = knot_nsec_bitmap_len(rrs->rrs.rdata); + return kr_nsec_children_in_zone_check(bm, bm_size) != 0; +} + +int nsec1_src_synth(struct key *k, struct answer *ans, const knot_dname_t *clencl_name, + knot_db_val_t cover_low_kwz, knot_db_val_t cover_hi_kwz, + const struct kr_query *qry, struct kr_cache *cache) +{ + /* Construct key for the source of synthesis. */ + knot_db_val_t key = key_NSEC1(k, clencl_name, true); + const size_t nwz_off = key_nwz_off(k); + if (kr_fails_assert(key.data && key.len >= nwz_off)) + return kr_error(1); + /* Check if our sname-covering NSEC also covers/matches SS. */ + knot_db_val_t kwz = { + .data = (uint8_t *)key.data + nwz_off, + .len = key.len - nwz_off, + }; + if (kr_fails_assert((ssize_t)(kwz.len) >= 0)) + return kr_error(EINVAL); + const int cmp = kwz_between(cover_low_kwz, kwz, cover_hi_kwz); + if (nonexistence_ok(cmp, ans->rrsets[AR_NSEC].set.rr)) { + VERBOSE_MSG(qry, "=> NSEC wildcard: covered by the same RR\n"); + return AR_SOA; + } + const knot_rrset_t *nsec_rr = NULL; /**< the wildcard proof NSEC */ + bool exact_match; /**< whether it matches the source of synthesis */ + if (cmp == 1) { + exact_match = true; + nsec_rr = ans->rrsets[AR_NSEC].set.rr; + } else { + /* Try to find the NSEC for SS. */ + knot_db_val_t val = { NULL, 0 }; + knot_db_val_t wild_low_kwz = { NULL, 0 }; + uint32_t new_ttl; + const char *err = find_leq_NSEC1(cache, qry, key, k, &val, + &exact_match, &wild_low_kwz, NULL, &new_ttl); + if (err) { + VERBOSE_MSG(qry, "=> NSEC wildcard: %s\n", err); + return kr_ok(); + } + /* Materialize the record into answer (speculatively). */ + knot_dname_t owner[KNOT_DNAME_MAXLEN]; + int ret = dname_wire_reconstruct(owner, k, wild_low_kwz); + if (ret) return kr_error(ret); + const struct entry_h *nsec_eh = val.data; + ret = entry2answer(ans, AR_WILD, nsec_eh, knot_db_val_bound(val), + owner, KNOT_RRTYPE_NSEC, new_ttl); + if (ret) return kr_error(ret); + nsec_rr = ans->rrsets[AR_WILD].set.rr; + } + + if (kr_fails_assert(nsec_rr)) + return kr_error(EFAULT); + const uint8_t *bm = knot_nsec_bitmap(nsec_rr->rrs.rdata); + uint16_t bm_size = knot_nsec_bitmap_len(nsec_rr->rrs.rdata); + int ret; + struct answer_rrset * const arw = &ans->rrsets[AR_WILD]; + if (kr_fails_assert(bm)) { + ret = kr_error(EFAULT); + goto clean_wild; + } + if (!exact_match) { + /* Finish verification that the source of synthesis doesn't exist. */ + const int nsec_matched = + knot_dname_matched_labels(nsec_rr->owner, clencl_name); + /* we don't need to use the full source of synthesis ^ */ + const bool is_sub = + nsec_matched == knot_dname_labels(nsec_rr->owner, NULL); + if (is_sub && kr_nsec_children_in_zone_check(bm, bm_size) != 0) { + VERBOSE_MSG(qry, + "=> NSEC wildcard: covered but delegated (or error)\n"); + ret = kr_ok(); + goto clean_wild; + } + /* We have a record proving wildcard non-existence. */ + WITH_VERBOSE(qry) { + auto_free char *owner_str = kr_dname_text(nsec_rr->owner), + *next_str = kr_dname_text(knot_nsec_next(nsec_rr->rrs.rdata)); + VERBOSE_MSG(qry, "=> NSEC wildcard: covered by: %s -> %s, new TTL %d\n", + owner_str, next_str, nsec_rr->ttl); + } + return AR_SOA; + } + + /* The wildcard exists. Find if it's NODATA - check type bitmap. */ + if (kr_nsec_bitmap_nodata_check(bm, bm_size, qry->stype, nsec_rr->owner) == 0) { + /* NODATA proven; just need to add SOA+RRSIG later */ + WITH_VERBOSE(qry) { + const char *msg_start = "=> NSEC wildcard: match proved NODATA"; + if (arw->set.rr) { + auto_free char *owner_str = kr_dname_text(nsec_rr->owner); + VERBOSE_MSG(qry, "%s: %s, new TTL %d\n", + msg_start, owner_str, nsec_rr->ttl); + } else { + /* don't repeat the RR if it's the same */ + VERBOSE_MSG(qry, "%s, by the same RR\n", msg_start); + } + } + ans->rcode = PKT_NODATA; + return AR_SOA; + + } /* else */ + /* The data probably exists -> don't add this NSEC + * and (later) try to find the real wildcard data */ + VERBOSE_MSG(qry, "=> NSEC wildcard: should exist (or error)\n"); + ans->rcode = PKT_NOERROR; + ret = kr_ok(); +clean_wild: + if (arw->set.rr) { /* we may have matched AR_NSEC */ + knot_rrset_free(arw->set.rr, ans->mm); + arw->set.rr = NULL; + knot_rdataset_clear(&arw->sig_rds, ans->mm); + } + return ret; +} + diff --git a/lib/cache/nsec3.c b/lib/cache/nsec3.c new file mode 100644 index 0000000..0b70775 --- /dev/null +++ b/lib/cache/nsec3.c @@ -0,0 +1,481 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/** @file + * Implementation of NSEC3 handling. Prototypes in ./impl.h + */ + +#include "lib/cache/impl.h" + +#include "contrib/base32hex.h" +#include "lib/dnssec/nsec.h" +#include "lib/dnssec/nsec3.h" +#include "lib/layer/iterate.h" + +#include <libknot/rrtype/nsec3.h> + +static const knot_db_val_t VAL_EMPTY = { NULL, 0 }; + +/** Common part: write all but the NSEC3 hash. */ +static knot_db_val_t key_NSEC3_common(struct key *k, const knot_dname_t *zname, + const nsec_p_hash_t nsec_p_hash) +{ + if (kr_fails_assert(k && zname && !kr_dname_lf(k->buf, zname, false))) + return VAL_EMPTY; + + /* CACHE_KEY_DEF: key == zone's dname_lf + '\0' + '3' + nsec_p hash (4B) + * + NSEC3 hash (20B == NSEC3_HASH_LEN binary!) + * LATER(optim.) nsec_p hash: perhaps 2B would give a sufficient probability + * of avoiding collisions. + */ + uint8_t *begin = k->buf + 1 + k->zlf_len; /* one byte after zone's zero */ + begin[0] = 0; + begin[1] = '3'; /* tag for NSEC3 */ + k->type = KNOT_RRTYPE_NSEC3; + memcpy(begin + 2, &nsec_p_hash, sizeof(nsec_p_hash)); + return (knot_db_val_t){ + .data = k->buf + 1, + .len = begin + 2 + sizeof(nsec_p_hash) - (k->buf + 1), + }; +} + +knot_db_val_t key_NSEC3(struct key *k, const knot_dname_t *nsec3_name, + const nsec_p_hash_t nsec_p_hash) +{ + knot_db_val_t val = key_NSEC3_common(k, nsec3_name /*only zname required*/, + nsec_p_hash); + if (!val.data) return val; + int len = base32hex_decode(nsec3_name + 1, nsec3_name[0], + knot_db_val_bound(val), KR_CACHE_KEY_MAXLEN - val.len); + if (len != NSEC3_HASH_LEN) { + return VAL_EMPTY; + } + val.len += len; + return val; +} + +/** Construct a string key for for NSEC3 predecessor-search, from an non-NSEC3 name. + * \note k->zlf_len and k->zname are assumed to have been correctly set */ +static knot_db_val_t key_NSEC3_name(struct key *k, const knot_dname_t *name, + const bool add_wildcard, const struct nsec_p *nsec_p) +{ + bool ok = k && name && nsec_p && nsec_p->raw; + if (!ok) return VAL_EMPTY; + knot_db_val_t val = key_NSEC3_common(k, k->zname, nsec_p->hash); + if (!val.data) return val; + + /* Make `name` point to correctly wildcarded owner name. */ + uint8_t buf[KNOT_DNAME_MAXLEN]; + int name_len; + if (add_wildcard) { + buf[0] = '\1'; + buf[1] = '*'; + name_len = knot_dname_to_wire(buf + 2, name, sizeof(buf) - 2); + if (name_len < 0) return VAL_EMPTY; /* wants wildcard but doesn't fit */ + name = buf; + name_len += 2; + } else { + name_len = knot_dname_size(name); + } + /* Append the NSEC3 hash. */ + const dnssec_binary_t dname = { + .size = name_len, + .data = (uint8_t *)/*const-cast*/name, + }; + + if (kr_fails_assert(nsec_p->libknot.iterations <= KR_NSEC3_MAX_ITERATIONS)) { + /* This is mainly defensive; it shouldn't happen thanks to downgrades. */ + return VAL_EMPTY; + } + #if 0 // LATER(optim.): this requires a patched libdnssec - tries to realloc() + dnssec_binary_t hash = { + .size = KR_CACHE_KEY_MAXLEN - val.len, + .data = val.data + val.len, + }; + int ret = dnssec_nsec3_hash(&dname, &nsec_p->libknot, &hash); + if (ret != DNSSEC_EOK) return VAL_EMPTY; + if (kr_fails_assert(hash.size == NSEC3_HASH_LEN)) + return VAL_EMPTY; + + #else + dnssec_binary_t hash = { .size = 0, .data = NULL }; + int ret = dnssec_nsec3_hash(&dname, &nsec_p->libknot, &hash); + if (ret != DNSSEC_EOK) return VAL_EMPTY; + if (kr_fails_assert(hash.size == NSEC3_HASH_LEN && hash.data)) + return VAL_EMPTY; + memcpy(knot_db_val_bound(val), hash.data, NSEC3_HASH_LEN); + free(hash.data); + #endif + + val.len += hash.size; + return val; +} + +/** Return h1 < h2, semantically on NSEC3 hashes. */ +static inline bool nsec3_hash_ordered(const uint8_t *h1, const uint8_t *h2) +{ + return memcmp(h1, h2, NSEC3_HASH_LEN) < 0; +} + +/** NSEC3 range search. + * + * \param key Pass output of key_NSEC3(k, ...) + * \param nsec_p Restrict to this NSEC3 parameter-set. + * \param value[out] The raw data of the NSEC3 cache record (optional; consistency checked). + * \param exact_match[out] Whether the key was matched exactly or just covered (optional). + * \param hash_low[out] Output the low end hash of covering NSEC3, pointing within DB (optional). + * \param new_ttl[out] New TTL of the NSEC3 (optional). + * \return Error message or NULL. + * \note The function itself does *no* bitmap checks, e.g. RFC 6840 sec. 4. + */ +static const char * find_leq_NSEC3(struct kr_cache *cache, const struct kr_query *qry, + const knot_db_val_t key, const struct key *k, const struct nsec_p *nsec_p, + knot_db_val_t *value, bool *exact_match, const uint8_t **hash_low, + uint32_t *new_ttl) +{ + /* Do the cache operation. */ + const size_t hash_off = key_nsec3_hash_off(k); + if (kr_fails_assert(key.data && key.len >= hash_off)) + return "range search ERROR"; + knot_db_val_t key_found = key; + knot_db_val_t val = { NULL, 0 }; + int ret = cache_op(cache, read_leq, &key_found, &val); + /* ^^ LATER(optim.): incrementing key and doing less-than search + * would probably be slightly more efficient with LMDB, + * but the code complexity would grow considerably. */ + if (ret < 0) { + if (kr_fails_assert(ret == kr_error(ENOENT))) { + return "range search ERROR"; + } else { + return "range search miss"; + } + } + if (value) { + *value = val; + } + /* Check consistency, TTL, rank. */ + const bool is_exact = (ret == 0); + if (exact_match) { + *exact_match = is_exact; + } + const struct entry_h *eh = entry_h_consistent_NSEC(val); + if (!eh) { + /* This might be just finding something else than NSEC3 entry, + * in case we searched before the very first one in the zone. */ + return "range search found inconsistent entry"; + } + /* Passing just zone name instead of owner. */ + int32_t new_ttl_ = get_new_ttl(eh, qry, k->zname, KNOT_RRTYPE_NSEC3, + qry->timestamp.tv_sec); + if (new_ttl_ < 0 || !kr_rank_test(eh->rank, KR_RANK_SECURE)) { + return "range search found stale or insecure entry"; + /* TODO: remove the stale record *and* retry, + * in case we haven't run off. Perhaps start by in_zone check. */ + } + if (new_ttl) { + *new_ttl = new_ttl_; + } + if (hash_low) { + *hash_low = (uint8_t *)key_found.data + hash_off; + } + if (is_exact) { + /* Nothing else to do. */ + return NULL; + } + /* The NSEC3 starts strictly before our target name; + * now check that it still belongs into that zone and chain. */ + const uint8_t *nsec_p_raw = eh->data + KR_CACHE_RR_COUNT_SIZE + + 2 /* RDLENGTH from rfc1034 */; + const int nsec_p_len = nsec_p_rdlen(nsec_p_raw); + const bool same_chain = key_found.len == hash_off + NSEC3_HASH_LEN + /* CACHE_KEY_DEF */ + && memcmp(key.data, key_found.data, hash_off) == 0 + /* exact comparison of NSEC3 parameters */ + && nsec_p_len == nsec_p_rdlen(nsec_p->raw) + && memcmp(nsec_p_raw, nsec_p->raw, nsec_p_len) == 0; + if (!same_chain) { + return "range search miss (!same_chain)"; + } + /* We know it starts before sname, so let's check the other end. + * A. find the next hash and check its length. */ + if (kr_fails_assert(KR_CACHE_RR_COUNT_SIZE == 2 && get_uint16(eh->data) != 0)) + return "ERROR"; /* TODO: more checks? Also, `next` computation is kinda messy. */ + const uint8_t *hash_next = nsec_p_raw + nsec_p_len + + sizeof(uint8_t) /* hash length from rfc5155 */; + if (hash_next[-1] != NSEC3_HASH_LEN) { + return "unexpected next hash length"; + } + /* B. do the actual range check. */ + const uint8_t * const hash_searched = (uint8_t *)key.data + hash_off; + bool covers = /* we know for sure that the low end is before the searched name */ + nsec3_hash_ordered(hash_searched, hash_next) + /* and the wrap-around case */ + || nsec3_hash_ordered(hash_next, (const uint8_t *)key_found.data + hash_off); + if (!covers) { + return "range search miss (!covers)"; + } + return NULL; +} + +/** Extract textual representation of NSEC3 hash from a cache key. + * \param text must have length at least NSEC3_HASH_TXT_LEN+1 (will get 0-terminated). */ +static void key_NSEC3_hash2text(const knot_db_val_t key, char *text) +{ + kr_require(key.data && key.len > NSEC3_HASH_LEN); + const uint8_t *hash_raw = knot_db_val_bound(key) - NSEC3_HASH_LEN; + /* CACHE_KEY_DEF ^^ */ + int len = base32hex_encode(hash_raw, NSEC3_HASH_LEN, (uint8_t *)text, + NSEC3_HASH_TXT_LEN); + kr_assert(len == NSEC3_HASH_TXT_LEN); + text[NSEC3_HASH_TXT_LEN] = '\0'; +} + +/** Reconstruct a name into a buffer (assuming length at least KNOT_DNAME_MAXLEN). + * \return kr_ok() or error code (<0). */ +static int dname_wire_reconstruct(knot_dname_t *buf, const knot_dname_t *zname, + const uint8_t *hash_raw) +{ + int len = base32hex_encode(hash_raw, NSEC3_HASH_LEN, buf + 1, NSEC3_HASH_TXT_LEN); + if (kr_fails_assert(len == NSEC3_HASH_TXT_LEN)) + return kr_error(EINVAL); + buf[0] = len; + int ret = knot_dname_to_wire(buf + 1 + len, zname, KNOT_DNAME_MAXLEN - 1 - len); + return ret < 0 ? kr_error(ret) : kr_ok(); +} + +static void nsec3_hash2text(const knot_dname_t *owner, char *text) +{ + kr_require(owner[0] == NSEC3_HASH_TXT_LEN); + memcpy(text, owner + 1, MIN(owner[0], NSEC3_HASH_TXT_LEN)); + text[NSEC3_HASH_TXT_LEN] = '\0'; +} + +int nsec3_encloser(struct key *k, struct answer *ans, + const int sname_labels, int *clencl_labels, + const struct kr_query *qry, struct kr_cache *cache) +{ + static const int ESKIP = ABS(ENOENT); + /* Basic sanity check. */ + const bool ok = k && k->zname && ans && clencl_labels + && qry && cache; + if (kr_fails_assert(ok)) + return kr_error(EINVAL); + + /*** Find the closest encloser - cycle: name starting at sname, + * proceeding while longer than zname, shortening by one label on step. + * We need a pair where a name doesn't exist *and* its parent does. */ + /* LATER(optim.): perhaps iterate in the other order - that + * should help significantly against deep queries where we have + * a shallow proof in the cache. We can also optimize by using + * only exact search unless we had a match in the previous iteration. */ + const int zname_labels = knot_dname_labels(k->zname, NULL); + int last_nxproven_labels = -1; + const knot_dname_t *name = qry->sname; + for (int name_labels = sname_labels; name_labels >= zname_labels; + --name_labels, name += 1 + name[0]) { + /* Find a previous-or-equal NSEC3 in cache covering the name, + * checking TTL etc. */ + const knot_db_val_t key = key_NSEC3_name(k, name, false, &ans->nsec_p); + if (!key.data) continue; + WITH_VERBOSE(qry) { + char hash_txt[NSEC3_HASH_TXT_LEN + 1]; + key_NSEC3_hash2text(key, hash_txt); + VERBOSE_MSG(qry, "=> NSEC3 depth %d: hash %s\n", + name_labels - zname_labels, hash_txt); + } + knot_db_val_t val = { NULL, 0 }; + bool exact_match; + uint32_t new_ttl; + const uint8_t *hash_low; + const char *err = find_leq_NSEC3(cache, qry, key, k, &ans->nsec_p, &val, + &exact_match, &hash_low, &new_ttl); + if (err) { + WITH_VERBOSE(qry) { + auto_free char *name_str = kr_dname_text(name); + VERBOSE_MSG(qry, "=> NSEC3 encloser error for %s: %s\n", + name_str, err); + } + continue; + } + if (exact_match && name_labels != sname_labels + && name_labels + 1 != last_nxproven_labels) { + /* This name exists (checked rank and TTL), and it's + * neither of the two interesting cases, so we do not + * keep searching for non-existence above this name. */ + VERBOSE_MSG(qry, + "=> NSEC3 encloser: only found existence of an ancestor\n"); + return ESKIP; + } + /* Optimization: avoid the rest of the last iteration if pointless. */ + if (!exact_match && name_labels == zname_labels + && last_nxproven_labels != name_labels + 1) { + break; + } + + /* Basic checks OK -> materialize data, cleaning any previous + * records on that answer index (unsuccessful attempts). */ + knot_dname_t owner[KNOT_DNAME_MAXLEN]; + { + int ret = dname_wire_reconstruct(owner, k->zname, hash_low); + if (unlikely(ret)) continue; + } + const int ans_id = (exact_match && name_labels + 1 == last_nxproven_labels) + ? AR_CPE : AR_NSEC; + { + const struct entry_h *nsec_eh = val.data; + memset(&ans->rrsets[ans_id], 0, sizeof(ans->rrsets[ans_id])); + int ret = entry2answer(ans, ans_id, nsec_eh, knot_db_val_bound(val), + owner, KNOT_RRTYPE_NSEC3, new_ttl); + if (ret) return kr_error(ret); + } + + if (!exact_match) { + /* Non-existence proven, but we don't know if `name` + * is the next closer name. + * Note: we don't need to check for the sname being + * delegated away by this record, as with NSEC3 only + * *exact* match on an ancestor could do that. */ + last_nxproven_labels = name_labels; + WITH_VERBOSE(qry) { + char hash_low_txt[NSEC3_HASH_TXT_LEN + 1]; + nsec3_hash2text(owner, hash_low_txt); + VERBOSE_MSG(qry, + "=> NSEC3 depth %d: covered by %s -> TODO, new TTL %d\n", + name_labels - zname_labels, hash_low_txt, new_ttl); + } + continue; + } + + /* Exactly matched NSEC3: two cases, one after another. */ + const knot_rrset_t *nsec_rr = ans->rrsets[ans_id].set.rr; + const uint8_t *bm = knot_nsec3_bitmap(nsec_rr->rrs.rdata); + uint16_t bm_size = knot_nsec3_bitmap_len(nsec_rr->rrs.rdata); + if (kr_fails_assert(bm)) + return kr_error(EFAULT); + if (name_labels == sname_labels) { + if (kr_nsec_bitmap_nodata_check(bm, bm_size, qry->stype, + nsec_rr->owner) != 0) { + VERBOSE_MSG(qry, + "=> NSEC3 sname: match but failed type check\n"); + return ESKIP; + } + /* NODATA proven; just need to add SOA+RRSIG later */ + VERBOSE_MSG(qry, + "=> NSEC3 sname: match proved NODATA, new TTL %d\n", + new_ttl); + ans->rcode = PKT_NODATA; + return kr_ok(); + + } /* else */ + + if (kr_fails_assert(name_labels + 1 == last_nxproven_labels)) + return kr_error(EINVAL); + if (kr_nsec_children_in_zone_check(bm, bm_size) != 0) { + VERBOSE_MSG(qry, + "=> NSEC3 encloser: found but delegated (or error)\n"); + return ESKIP; + } + /* NXDOMAIN proven *except* for wildcards. */ + WITH_VERBOSE(qry) { + auto_free char *name_str = kr_dname_text(name); + VERBOSE_MSG(qry, + "=> NSEC3 encloser: confirmed as %s, new TTL %d\n", + name_str, new_ttl); + } + *clencl_labels = name_labels; + ans->rcode = PKT_NXDOMAIN; + /* Avoid repeated NSEC3 - remove either if the hashes match. + * This is very unlikely in larger zones: 1/size (per attempt). + * Well, deduplication would happen anyway when the answer + * from cache is read by kresd (internally). */ + if (unlikely(0 == memcmp(ans->rrsets[AR_NSEC].set.rr->owner + 1, + ans->rrsets[AR_CPE ].set.rr->owner + 1, + NSEC3_HASH_LEN))) { + memset(&ans->rrsets[AR_CPE], 0, sizeof(ans->rrsets[AR_CPE])); + /* LATER(optim.): perhaps check this earlier and avoid some work? */ + } + return kr_ok(); + } + + /* We've ran out of options. */ + if (last_nxproven_labels > 0) { + /* We didn't manage to prove existence of the closest encloser, + * meaning the only chance left is a *positive* wildcard record. */ + *clencl_labels = last_nxproven_labels - 1; + ans->rcode = PKT_NXDOMAIN; + /* FIXME: review */ + } + return ESKIP; +} + +int nsec3_src_synth(struct key *k, struct answer *ans, const knot_dname_t *clencl_name, + const struct kr_query *qry, struct kr_cache *cache) +{ + /* Find a previous-or-equal NSEC3 in cache covering or matching + * the source of synthesis, checking TTL etc. */ + const knot_db_val_t key = key_NSEC3_name(k, clencl_name, true, &ans->nsec_p); + if (!key.data) return kr_error(1); + WITH_VERBOSE(qry) { + char hash_txt[NSEC3_HASH_TXT_LEN + 1]; + key_NSEC3_hash2text(key, hash_txt); + VERBOSE_MSG(qry, "=> NSEC3 wildcard: hash %s\n", hash_txt); + } + knot_db_val_t val = { NULL, 0 }; + bool exact_match; + uint32_t new_ttl; + const uint8_t *hash_low; + const char *err = find_leq_NSEC3(cache, qry, key, k, &ans->nsec_p, &val, + &exact_match, &hash_low, &new_ttl); + if (err) { + VERBOSE_MSG(qry, "=> NSEC3 wildcard: %s\n", err); + return kr_ok(); + } + + /* LATER(optim.): avoid duplicities in answer. */ + + /* Basic checks OK -> materialize the data (speculatively). */ + knot_dname_t owner[KNOT_DNAME_MAXLEN]; + { + int ret = dname_wire_reconstruct(owner, k->zname, hash_low); + if (unlikely(ret)) return kr_ok(); + const struct entry_h *nsec_eh = val.data; + ret = entry2answer(ans, AR_WILD, nsec_eh, knot_db_val_bound(val), + owner, KNOT_RRTYPE_NSEC3, new_ttl); + if (ret) return kr_error(ret); + } + const knot_rrset_t *nsec_rr = ans->rrsets[AR_WILD].set.rr; + + if (!exact_match) { + /* The record proves wildcard non-existence. */ + WITH_VERBOSE(qry) { + char hash_low_txt[NSEC3_HASH_TXT_LEN + 1]; + nsec3_hash2text(owner, hash_low_txt); + VERBOSE_MSG(qry, + "=> NSEC3 wildcard: covered by %s -> TODO, new TTL %d\n", + hash_low_txt, new_ttl); + } + return AR_SOA; + } + + /* The wildcard exists. Find if it's NODATA - check type bitmap. */ + const uint8_t *bm = knot_nsec3_bitmap(nsec_rr->rrs.rdata); + uint16_t bm_size = knot_nsec3_bitmap_len(nsec_rr->rrs.rdata); + if (kr_fails_assert(bm)) + return kr_error(EFAULT); + if (kr_nsec_bitmap_nodata_check(bm, bm_size, qry->stype, nsec_rr->owner) == 0) { + /* NODATA proven; just need to add SOA+RRSIG later */ + VERBOSE_MSG(qry, "=> NSEC3 wildcard: match proved NODATA, new TTL %d\n", + new_ttl); + ans->rcode = PKT_NODATA; + return AR_SOA; + + } /* else */ + /* The data probably exists -> don't add this NSEC3 + * and (later) try to find the real wildcard data */ + VERBOSE_MSG(qry, "=> NSEC3 wildcard: should exist (or error)\n"); + ans->rcode = PKT_NOERROR; + memset(&ans->rrsets[AR_WILD], 0, sizeof(ans->rrsets[AR_WILD])); + return kr_ok(); +} + diff --git a/lib/cache/overflow.test.integr/deckard.yaml b/lib/cache/overflow.test.integr/deckard.yaml new file mode 100644 index 0000000..61032fb --- /dev/null +++ b/lib/cache/overflow.test.integr/deckard.yaml @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +programs: +- name: kresd1 + binary: kresd + additional: + - -n + templates: + - lib/cache/overflow.test.integr/kresd_config.j2 + - tests/config/test_dns_generators.lua + configs: + - config + - dns_gen.lua +- name: kresd2 + binary: kresd + additional: + - -n + templates: + - lib/cache/overflow.test.integr/kresd_config.j2 + - tests/config/test_dns_generators.lua + configs: + - config + - dns_gen.lua diff --git a/lib/cache/overflow.test.integr/kresd_config.j2 b/lib/cache/overflow.test.integr/kresd_config.j2 new file mode 100644 index 0000000..63841ff --- /dev/null +++ b/lib/cache/overflow.test.integr/kresd_config.j2 @@ -0,0 +1,91 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later + +trust_anchors.remove('.') +{% for TAF in TRUST_ANCHOR_FILES %} +trust_anchors.add_file('{{TAF}}') +{% endfor %} + +modules.load("hints") +hints.root({['{{ROOT_NAME}}'] = '{{ROOT_ADDR}}'}) + +{% raw %} +-- Disable RFC5011 TA update +if ta_update then + modules.unload('ta_update') +end + +-- Disable RFC8145 signaling, scenario doesn't provide expected answers +if ta_signal_query then + modules.unload('ta_signal_query') +end + +-- Disable RFC8109 priming, scenario doesn't provide expected answers +if priming then + modules.unload('priming') +end + +-- Disable this module because it make one priming query +if detect_time_skew then + modules.unload('detect_time_skew') +end + +log_level('debug') +policy.add(policy.all(policy.DEBUG_ALWAYS)) + +cache.open(1*MB) + +{% endraw %} + +{% if DO_IP6 == "true" %} +net.ipv6 = true +{% else %} +net.ipv6 = false +{% endif %} + +{% if DO_IP4 == "true" %} +net.ipv4 = true +{% else %} +net.ipv4 = false +{% endif %} + +-- both instances listen on both addresses +-- so queries get distributed between them randomly +net.listen('{{programs[0]["address"]}}') +net.listen('{{programs[1]["address"]}}') + +{% raw %} +-- Self-checks on globals +assert(help() ~= nil) +assert(worker.id ~= nil) +-- Self-checks on facilities +assert(cache.stats() ~= nil) +assert(cache.backends() ~= nil) +assert(worker.stats() ~= nil) +assert(net.interfaces() ~= nil) +-- Self-checks on loaded stuff +{% endraw %} + +assert(net.list()[1].transport.ip == '{{programs[0]["address"]}}') + +{% raw %} +assert(#modules.list() > 0) +-- Self-check timers +ev = event.recurrent(1 * sec, function (ev) return 1 end) +event.cancel(ev) +ev = event.after(0, function (ev) return 1 end) + +local ffi = require('ffi') +local kr_cach = kres.context().cache + +-- canary for cache overflow +local kr_rrset = kres.rrset( + todname('www.example.com'), + kres.type.A, + kres.class.IN, + 604800) +assert(kr_rrset:add_rdata('\192\000\002\001', 4)) +assert(kr_cach:insert(kr_rrset, nil, ffi.C.KR_RANK_SECURE)) + +local generators = dofile('./dns_gen.lua') +event.after(0, generators.gen_batch) +{% endraw %} diff --git a/lib/cache/overflow.test.integr/world_cz_vutbr_www.rpl b/lib/cache/overflow.test.integr/world_cz_vutbr_www.rpl new file mode 100644 index 0000000..eddfbd0 --- /dev/null +++ b/lib/cache/overflow.test.integr/world_cz_vutbr_www.rpl @@ -0,0 +1,298 @@ +do-ip4: no + +; test with real world Internet data +; attempt to resolve www.vutbr.cz. A leads to CNAME piranha.ro.vutbr.cz. +; sub-trees vutbr.cz and ro.vutbr.cz. are in separate zones +; hosted on the same servers with different DNSKEYs + +val-override-date: 20170124180319 +trust-anchor: ". 172800 IN DS 19036 8 2 49aac11d7b6f6446702e54a1607371607a1a41855200fd2ce1cdde32f24e8fb5" +stub-addr: 2001:dc3::35 +CONFIG_END + +SCENARIO_BEGIN www.vutbr.cz. CNAME kresd issue #130 + +; DNS root ; M.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 2001:dc3::35 + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA DO NOERROR +SECTION QUESTION +. IN DNSKEY +SECTION ANSWER +. 16567 IN DNSKEY 257 3 8 AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjF FVQUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoX bfDaUeVPQuYEhg37NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaD X6RS6CXpoY68LsvPVjR0ZSwzz1apAzvN9dlzEheX7ICJBBtuA6G3LQpz W5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7OyQdXfZ57relS Qageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulq QxA+Uk1ihz0= +. 16567 IN DNSKEY 256 3 8 AwEAAYvgWbYkpeGgdPKaKTJU3Us4YSTRgy7+dzvfArIhi2tKoZ/WR1Df w883SOU6Uw7tpVRkLarN0oIMK/xbOBD1DcXnyfElBwKsz4sVVWmfyr/x +igD/UjrcJ5zEBUrUmVtHyjar7ccaVc1/3ntkhZjI1hcungAlOhPhHlk MeX+5Azx6GdX//An5OgrdyH3o/JmOPMDX1mt806JI/hf0EwAp1pBwo5e 8SrSuR1tD3sgNjr6IzCdrKSgqi92z49zcdis3EaY199WFW60DCS7ydu+ +T5Xa+GyOw1quagwf/JUC/mEpeBQYWrnpkBbpDB3sy4+P2i8iCvavehb RyVm9U0MlIc= +. 16567 IN RRSIG DNSKEY 8 0 172800 20170201000000 20170111000000 19036 . Sh+EpofvZgk3J9szMD2B94FxFgyIUKz3hkbCjgWSTqPQyhqNgqVU8QlS EtOo8YLmS4AX98eit5Gmmb2ObpkGoXBmAzu5w/Qt5WsGsWzLQhYrsy9s lDmFQ2JKUoCyfdwqhlJ8VxjzdFdMUiVl+/GPnv4yjxjM8Ke3VAtBkn6n BO7JkcxxOfcgZdZ4MuvSr40K/SenZE+JlLLL1LF4TMCGqaZTTdOx6kFF KSSgy2AS884htWcK0tnwRc630g6nAI2wdvjlRLBeisbfXanI4v8iiPyT FnMmnV7wJGWJ4gtRJ0UH3u5RWXUPZ+s1tKytk3slXbLyQ9xkEDveuD+h b659gQ== +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR DO NOERROR +SECTION QUESTION +cz. IN NS +SECTION AUTHORITY +cz. 172800 IN NS d.ns.nic.cz. +cz. 172800 IN NS a.ns.nic.cz. +cz. 172800 IN NS c.ns.nic.cz. +cz. 172800 IN NS b.ns.nic.cz. +cz. 86400 IN DS 54576 10 2 397E50C85EDE9CDE33F363A9E66FD1B216D788F8DD438A57A423A386 869C8F06 +cz. 86400 IN RRSIG DS 8 1 86400 20170202170000 20170120160000 61045 . ig2BBmA1kOuTqhVogqLciH40Ina7BCrG/fcaNARSWoaFHGOcC/7KsBZO uMttn/hKDJkH3RPsed2Oswl9bXZ+zrhjeXluUqC0zmsUJDBkS+AkiFJL HCpMSIZaXu/w1ZMADGfyQXl7XWCRbl+eyXi2eTG0SdLtRHNhm3CGJP3C xjzVuOTr9oPEyL0U81jhhlJPCFe8xDD441wLLzpEuVX8VP9N2S1QnIjO BhCEE9OTkPgpS7fMPEl0Yq2gfpRl+DCw1Dd0VB3Hh5M3hmrXuFqNYZQK b0JqDFGYhzvcpUs3EiB9IG7rJt51n6pxCTek1M2w+s6mLYzawVfq+b1Q uQD98A== +SECTION ADDITIONAL +a.ns.nic.cz. 172800 IN A 194.0.12.1 +b.ns.nic.cz. 172800 IN A 194.0.13.1 +c.ns.nic.cz. 172800 IN A 194.0.14.1 +d.ns.nic.cz. 172800 IN A 193.29.206.1 +a.ns.nic.cz. 172800 IN AAAA 2001:678:f::1 +b.ns.nic.cz. 172800 IN AAAA 2001:678:10::1 +c.ns.nic.cz. 172800 IN AAAA 2001:678:11::1 +d.ns.nic.cz. 172800 IN AAAA 2001:678:1::1 +ENTRY_END +; end of M.ROOT-SERVERS.NET. +RANGE_END + + +; domains: cz. ; ?.ns.nic.cz. +RANGE_BEGIN 0 100 + ADDRESS 194.0.12.1 + ADDRESS 194.0.13.1 + ADDRESS 194.0.14.1 + ADDRESS 193.29.206.1 + ADDRESS 2001:678:f::1 + ADDRESS 2001:678:10::1 + ADDRESS 2001:678:11::1 + ADDRESS 2001:678:1::1 + + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +cz. IN DNSKEY +SECTION ANSWER +cz. 18000 IN DNSKEY 256 3 10 AwEAAc9e2YFnG56xtTXu42GLGAkwsrFOBBwOZphNat7HQdBmfi0CbmDf oywCUsaSkObNmm+Zu9MYLNJDHsD+vxsZbtHClpYaSEhMEmHrbnj0BMPV A6hwY6YDGFhKudJ62RmB/rmhQ3iwmICsEdRn2w5fu1rHZv8UJOUMkeWd 6GA48mW3 +cz. 18000 IN DNSKEY 256 3 10 AwEAAdWL2Br92Vx0dLEOOB8y02ss8LtKIyGlLJ2ymJ02WqR3AAEEZN0f NPKF77kdKsjlG8DlzmSIOR12aa9EhpXqyHOwWI0kHOMJVnn6ZKFIAl71 JP/dYIcshYUxKZZMe+zEAUrVtzlLVDtM6cDOPDuBNa1ujYec3eJl9Ipq eUEG6gAH +cz. 18000 IN DNSKEY 257 3 10 AwEAAay0hi4HN2r/BqMQTpIPIVDyjmyF+9ZWvr5Lewx+q+947o/GrRv4 FGFfkZxf9CFfYVUf0jG5Yq4i06pGVNwJl81HS9Ux2oeHRXUvgtLnl5He RVLL+zgI5byx9HSNr4bPO8ZEn5OjoayhkNyGSFr4VWrzQk/K02vLP4d1 cCEzUQy30eyZto2/tG5ZwCU/iRkS1PJOcOW98hiFIfFDZv1XjbEpqEYh T2PATs6rt+BKwSHKGISmg1PNdg+y0rItemYMWr1f9BGAdtTWoPCPCYPj OZMPoIyA4tMscD+ww54Jf/QNoHccY4hO1yHiuAXG7SUn8jo0IKQ9W7JJ xES0aqFCX/0= +cz. 18000 IN RRSIG DNSKEY 10 1 18000 20170127000000 20170120000000 54576 cz. Fdl//hMdLoZq8//gLt/+3a7LfWqB5/psW9YR3AWNPQGfvrEAcKRBcah+ ikbSCmpAZ6j834xZP1zPd5xMoN33PGXf23iqcgjHvUn50Uq48KRBVYwU H885xNJBl/Po0N8STeG0WNZz2mbUbBbPCGN7CI5yl08usvqOvf2fV8+D 0m//+Fa1cWaqMXpHc6OnhWZ+BN4VdcxxwNbGhH2TZxyiGEMMscEGoIxn yL1pVY8T93LOMwQmuFJ71f8Scij3vYouW/mNuEma/UUZM1bEn8vR1UrP /6JTGPGTG+snHvCxiVtAxCNnqoIJDD+xuonpZLeKN5XU7UDMZPDTtSgX vtzjww== +cz. 18000 IN RRSIG DNSKEY 10 1 18000 20170205002523 20170123080953 58211 cz. MZ6KTtQisTde4iOBH6oasl7bVrRM5ly7Yxdv2l+2gk1YYk4zX6L3m6oB P26SKi+fj8pM77775bRK7uCI9FlyqXa3MJclLU/GmnRANm6T4sSdz0zs F3FK4UfUmHnzdnWXWTnueDfIZr44yF1y1+4I3E96/9/nEYGO+xsifvIj iks= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR DO NOERROR +SECTION QUESTION +vutbr.cz. IN NS +SECTION AUTHORITY +vutbr.cz. 18000 IN NS pipit.cis.vutbr.cz. +vutbr.cz. 18000 IN NS rhino.cis.vutbr.cz. +vutbr.cz. 18000 IN DS 5512 5 2 78510F9433A4D536A5B9099193E9D58EE5B5CF71F14D983B4DA2EB16 29CFA1E9 +vutbr.cz. 18000 IN RRSIG DS 10 2 18000 20170204213601 20170123080953 58211 cz. lXNBswz/r/1NY7VQq+BlisC+1yqFmUBIaF30L8XDAbiHLcj/AIj0dEy6 PlBlkEeDAi4W9DvR0jo9LjHvFFJLs54cuEEd3pHTdlw8x0dLd1X7Zkh7 cezfAt2EEqdux/ce/sc86lUKOpLnDtry2piWwVf2EqFg9NlW4cHTm78U gsY= +SECTION ADDITIONAL +pipit.cis.vutbr.cz. 18000 IN A 77.93.219.110 +rhino.cis.vutbr.cz. 18000 IN A 147.229.3.10 +pipit.cis.vutbr.cz. 18000 IN AAAA 2a01:430:120::4d5d:db6e +rhino.cis.vutbr.cz. 18000 IN AAAA 2001:67c:1220:e000::93e5:30a +ENTRY_END + +; end of domain cz.: servers ?.ns.nic.cz. +RANGE_END + + +; domains: vutbr.cz. + ro.vutbr.cz. +; servers: pipit.cis.vutbr.cz. + rhino.cis.vutbr.cz. + shark.ro.vutbr.cz. +; shark.ro.vutbr.cz. in fact serves both domains but is listed only in ro.vutbr.cz NS +RANGE_BEGIN 0 100 + ADDRESS 77.93.219.110 + ADDRESS 147.229.3.10 + ADDRESS 147.229.2.59 + ADDRESS 2a01:430:120::4d5d:db6e + ADDRESS 2001:67c:1220:e000::93e5:30a + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA DO +SECTION QUESTION +vutbr.cz. IN NS +SECTION ANSWER +vutbr.cz. 28800 IN NS rhino.cis.vutbr.cz. +vutbr.cz. 28800 IN NS pipit.cis.vutbr.cz. +vutbr.cz. 28800 IN RRSIG NS 5 2 28800 20170216060902 20170117060902 39756 vutbr.cz. y6Jj5vfvdlLeecB/++/qyhjCzfnFJyY1sX1Ja+wV0ulq3laeCVV7ICXh PKG+CjHUu/nDOrzT9QJP4qxYDCANneI0yxI82XKhhoTN5O/TxyWH/DyT k8JarRoMooHv2RwKd8jtLIxvj1SaJ+AvlP0pOPraaVgbHtn1SJ4ubxQD cFc= +SECTION ADDITIONAL +pipit.cis.vutbr.cz. 86400 IN A 77.93.219.110 +pipit.cis.vutbr.cz. 86400 IN AAAA 2a01:430:120::4d5d:db6e +rhino.cis.vutbr.cz. 86400 IN A 147.229.3.10 +rhino.cis.vutbr.cz. 86400 IN AAAA 2001:67c:1220:e000::93e5:30a +pipit.cis.vutbr.cz. 86400 IN RRSIG A 5 4 86400 20170204080646 20170105080646 28257 cis.vutbr.cz. Cz9etHnEOQTzu+6rYJEqx/SQ1tQgPOCyf8HSj4KOsx89jtgiHNC6pep6 ZE0SphMGAs3jC/uGIhlaFNZ3i38OQIMuqwacbz+XZyW5bByvV3QZrhqh dFxMDfmPuNiCAT3crFpUkvVW1OE3YfGHzZGXX7JP5wb1b8A3X6Qih7fV +nQ= +pipit.cis.vutbr.cz. 86400 IN RRSIG AAAA 5 4 86400 20170204080646 20170105080646 28257 cis.vutbr.cz. piafjh6my2fooZRrzwCu9RQ95gYaMQkhIkDaGX/fT6wXzSdmgFZkS1Nl EMIKdDCQaPrLGMG3p32ptMkAm4esPekeyNtLSMBtXwZyUkgEGn6h1QM2 Yr3TOo8cixfk5nmRRdlYadf5krLb8yI9exiqeymgEQLa1YNRz/bWArlX bn8= +rhino.cis.vutbr.cz. 86400 IN RRSIG A 5 4 86400 20170204080646 20170105080646 28257 cis.vutbr.cz. X/tDf8e3JEV0LxiItfpQnBzeaRIq693VG8d30iCH4/1I0uqyCfxboWmm /CBpn9A8MCJu9NEEv+4+povNlfUfqi2yjsqJEVj8ztHxD4g9cc284Cv6 ySjxrSZ9axVqoaopEXujiTwwWJUFcgF6pxqyXVksW7sgKJrboM4VSlQD +Sw= +rhino.cis.vutbr.cz. 86400 IN RRSIG AAAA 5 4 86400 20170204080646 20170105080646 28257 cis.vutbr.cz. T3Yf5PAkSeJtoOH90ea9zZBG9FC3iFhiCSerDn6d9up8GRfzxDsavYJC zQu+3vnOySySn+3TMzQSSFcWdJC2iO7ulaDGr177Gof9QJbKSVSMW7jt YDE2f4/R4Go3NZVwjk/HfpCInoR6pHNA1s/9hMnWtiVopmBdfzyd3/sW YOU= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA DO +SECTION QUESTION +vutbr.cz. IN DNSKEY +SECTION ANSWER +vutbr.cz. 28800 IN DNSKEY 256 3 5 AwEAAfwRRuGjpt9v4fzuIWFA9MtGfxDrIKhoFA7DNq6B+iCOoQb6t0HZ I9lGDUSR5DRswDGP569NJ/uVD4tJxGnaK2SBQVxIu+bEP1Ouzk+O43iO 8odw50NBWetljjNDP32B3zHpgJRpxyEqzDQaQ6B4Zer6sDZm9wo5SVJe r9LjJV9p +vutbr.cz. 28800 IN DNSKEY 257 3 5 AwEAAfhR+s/4SLZZNA+kD2u1UgYBUu+X3Avi60QCaE1o2STterM405s8 mWMWJOlZGtjjIky3TEMxQ0+ZtMbEeJu2wNDLdV/XglX+pJAjyy728WJH 4u2/gJR8ZWsEIc0Jwb4FjwmBiF2Koz0SGVvrzEZ9T1H7dHq2X6f8KzYB otJyrAIWr9tZi/9tHrngZJ5wXELmMPWCfEFapdQMoKWoNvzrMYFli17R Mz7gJzCmNxMRV8/WkjsNPgYsTKpsAT8qEsXiTN9987AIKPHvc5j+/njq +fTXdOqGVpIgSiso+qJMddEMBcu/MBBYVFOwRQe1ez2tMwIX7y5mwDvK 0wsmyRvHugfFuxSnfiJvQr05kSnj0wxD9s9LNhrF4PocrcYqnBN/lBx9 D6633jJ3zT3T5Foe/Vj9A/X7F2oN6FOkdwO+YSEUot980pJQut6DR22U P4bLakyDMiTdOQ31c/dRIoTsccxw+838pXFyEPgiqOHRSeN/w9km6BID cl+32Xq97kXSMQH6AxOUsx9/Mxdj7ISwbS4utaAWoP460+TMcnfJfWfB NEWhuFvnfB9l63ZjZToB2PUVhrTxRwKUlfMLegSJKoZfiae82kK1pN4x FYyquKSykm/oXsM2w4OQvpqGcTwAXzZ5s95J45f7PsCap0bscGKumxsH cDswWpUz/UVosIrr +vutbr.cz. 28800 IN RRSIG DNSKEY 5 2 28800 20170216060902 20170117060902 5512 vutbr.cz. QHw07MAjA4NFi3On8zaMw/q4IuADXVp4TODfK5PHb8OUIX2Yy+bKLrSX /Cc9ClWUpE69x80F9dFEeRZGJiYOwstNQGQVeq/EKNytm1XmhS8cp3SW CYHBpLjZGPrlhvqPhWd0S4vqPNiD8hDzgFAgaCNfwXDDKXhF2/qtpQ0V pDnytMP6pNPLPMpF2hzaLfCMzABShxcEOAr7+KTbxbffOik4YneG8seu XDtBvCVjP8lJcSU+q+UbotLnjyOgn8vV8pliTNqcvRsJTdtvTlJKHu8B iLkFeCE1DpRhyrVT5zC9NSOcoIv7tau2NE2oUPgtRzK76el6i9L9LcSs G+59j02AINefpAtc6W2khmTnGthibeOy/F9FuFkXUy6AmqIdNszMAj++ 8Mzv3A1OHfsfpIS3tLmC4drhdSHr2ab0Pe0lYQq2a9FSeQzSk6s9gwwZ gMVPVQHbouyvn6BCHaRVDjTV8GPKlk3C8GNaHcHb1hAGSPpw3kqL41dd K92Un4tLIoOYomxUYoyMtyxxwddXyR7ivToUHF7e/yv8MACMEo72N9sf y4zLEqkL1mJ1pCp3csI1bKaaA/c7sqb7PX93iqvoY06k55Pd7kT+lAF1 7QvXGg4U1kDrwytQPyocN8wmsX3//CpWUD07v8fCUqKOcIrVNGnoPmPC PpNe3AtpJoE= +vutbr.cz. 28800 IN RRSIG DNSKEY 5 2 28800 20170216060902 20170117060902 39756 vutbr.cz. CNDE7Ht7xm8Jo9tuOlJ8N9+vI/Htfpk53MI0HG7B1EZJws/yEV7YFOOL SIAt3rzu1OHjaxr4CG/baqGRPtsaWSBHuLSdSduivxXw8xiQcMKzP6Cz 7xhJkQZxzDJ4oO5L2K2zWHcAJ8lfP1/3NHHoH1p2RATLN5sI7ofQE//W +ck= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA DO +SECTION QUESTION +www.vutbr.cz. IN A +SECTION ANSWER +www.vutbr.cz. 300 IN CNAME piranha.ro.vutbr.cz. +www.vutbr.cz. 300 IN RRSIG CNAME 5 3 300 20170216060902 20170117060902 39756 vutbr.cz. 9B3UC5SOEw1+yKlYlOTINEuNq0Kdglywc5IYJwzeSzQ3ykptzZo3ABSy bYhTqImVkhm/4NFM9/4HWMHPDzTmrWS0mCI/ljCd/oe/PxW/uESvo4P5 EQzlcuH6xBzc1KdEFAJOSmRzFjj3vyK1QN3k/c+1y2oMFOYOR2oOzCw+ MIE= +piranha.ro.vutbr.cz. 3600 IN A 147.229.2.90 +piranha.ro.vutbr.cz. 3600 IN RRSIG A 5 4 3600 20170222120032 20170123120032 12150 ro.vutbr.cz. Jz8bcAADQjCKTCcF70IK1aHGQlM4ukyN0myABlxoPaqid1mHX5jwR91b kdQmUAh2xDitlgRLbFjbUUgmjSPzQ5Qt7GAFUsVmqxvjbOLZjqHER1dh zmiWO0fDvvP647Osv3RiAP822rNUJcJrUBZU9LmeP05gwIHcpJrhdVBT b7I= +SECTION AUTHORITY +ro.vutbr.cz. 86400 IN NS shark.ro.vutbr.cz. +ro.vutbr.cz. 86400 IN NS rhino.cis.vutbr.cz. +ro.vutbr.cz. 86400 IN NS pipit.cis.vutbr.cz. +ro.vutbr.cz. 86400 IN RRSIG NS 5 3 86400 20170222120032 20170123120032 12150 ro.vutbr.cz. HAQ8A+QNsS1WIXdW/fbT3jP+IxObBBvgUmvzsmJBXo8HMtnMAcuCQGmB 2JBQsQethQXsdyLnMK8to/5A9VRkqkAa7edxUoy7SdDi/mzGeLAVhF+5 kXSPD6t1vjiNdnIYAMpiOQbodCGxAnq6jnNyrjEzffdq3qw+5IkFNdG4 7Pw= +SECTION ADDITIONAL +rhino.cis.vutbr.cz. 83217 IN A 147.229.3.10 +rhino.cis.vutbr.cz. 83217 IN AAAA 2001:67c:1220:e000::93e5:30a +shark.ro.vutbr.cz. 3600 IN A 147.229.2.59 +pipit.cis.vutbr.cz. 14794 IN A 77.93.219.110 +pipit.cis.vutbr.cz. 14794 IN AAAA 2a01:430:120::4d5d:db6e +rhino.cis.vutbr.cz. 83217 IN RRSIG A 5 4 86400 20170204080646 20170105080646 28257 cis.vutbr.cz. X/tDf8e3JEV0LxiItfpQnBzeaRIq693VG8d30iCH4/1I0uqyCfxboWmm /CBpn9A8MCJu9NEEv+4+povNlfUfqi2yjsqJEVj8ztHxD4g9cc284Cv6 ySjxrSZ9axVqoaopEXujiTwwWJUFcgF6pxqyXVksW7sgKJrboM4VSlQD +Sw= +rhino.cis.vutbr.cz. 83217 IN RRSIG AAAA 5 4 86400 20170204080646 20170105080646 28257 cis.vutbr.cz. T3Yf5PAkSeJtoOH90ea9zZBG9FC3iFhiCSerDn6d9up8GRfzxDsavYJC zQu+3vnOySySn+3TMzQSSFcWdJC2iO7ulaDGr177Gof9QJbKSVSMW7jt YDE2f4/R4Go3NZVwjk/HfpCInoR6pHNA1s/9hMnWtiVopmBdfzyd3/sW YOU= +shark.ro.vutbr.cz. 3600 IN RRSIG A 5 4 3600 20170222120032 20170123120032 12150 ro.vutbr.cz. SmhgyF48yX/6yH7AdSmGX60NL/xaiKH/oAzB0rnPfQZ6j+UfV57ginVV lj798K9A8jjucUpqE8ua2mZ6/aOhpqlV2iI0CZXG44zOupsCY1/OXBDx YNetBcjoXDQCBQRLLLEUL5FerDVxqT74ngdLdKubwRdrB0TLQlvpBr+F Tc8= +pipit.cis.vutbr.cz. 85923 IN RRSIG A 5 4 86400 20170204080646 20170105080646 28257 cis.vutbr.cz. Cz9etHnEOQTzu+6rYJEqx/SQ1tQgPOCyf8HSj4KOsx89jtgiHNC6pep6 ZE0SphMGAs3jC/uGIhlaFNZ3i38OQIMuqwacbz+XZyW5bByvV3QZrhqh dFxMDfmPuNiCAT3crFpUkvVW1OE3YfGHzZGXX7JP5wb1b8A3X6Qih7fV +nQ= +pipit.cis.vutbr.cz. 85923 IN RRSIG AAAA 5 4 86400 20170204080646 20170105080646 28257 cis.vutbr.cz. piafjh6my2fooZRrzwCu9RQ95gYaMQkhIkDaGX/fT6wXzSdmgFZkS1Nl EMIKdDCQaPrLGMG3p32ptMkAm4esPekeyNtLSMBtXwZyUkgEGn6h1QM2 Yr3TOo8cixfk5nmRRdlYadf5krLb8yI9exiqeymgEQLa1YNRz/bWArlX bn8= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA DO +SECTION QUESTION +ro.vutbr.cz. IN NS +SECTION ANSWER +ro.vutbr.cz. 86400 IN NS pipit.cis.vutbr.cz. +ro.vutbr.cz. 86400 IN NS rhino.cis.vutbr.cz. +ro.vutbr.cz. 86400 IN NS shark.ro.vutbr.cz. +ro.vutbr.cz. 86400 IN RRSIG NS 5 3 86400 20170222120032 20170123120032 12150 ro.vutbr.cz. HAQ8A+QNsS1WIXdW/fbT3jP+IxObBBvgUmvzsmJBXo8HMtnMAcuCQGmB 2JBQsQethQXsdyLnMK8to/5A9VRkqkAa7edxUoy7SdDi/mzGeLAVhF+5 kXSPD6t1vjiNdnIYAMpiOQbodCGxAnq6jnNyrjEzffdq3qw+5IkFNdG4 7Pw= +SECTION ADDITIONAL +rhino.cis.vutbr.cz. 86400 IN A 147.229.3.10 +rhino.cis.vutbr.cz. 86400 IN AAAA 2001:67c:1220:e000::93e5:30a +shark.ro.vutbr.cz. 3600 IN A 147.229.2.59 +pipit.cis.vutbr.cz. 86400 IN A 77.93.219.110 +pipit.cis.vutbr.cz. 86400 IN AAAA 2a01:430:120::4d5d:db6e +rhino.cis.vutbr.cz. 86400 IN RRSIG A 5 4 86400 20170204080646 20170105080646 28257 cis.vutbr.cz. X/tDf8e3JEV0LxiItfpQnBzeaRIq693VG8d30iCH4/1I0uqyCfxboWmm /CBpn9A8MCJu9NEEv+4+povNlfUfqi2yjsqJEVj8ztHxD4g9cc284Cv6 ySjxrSZ9axVqoaopEXujiTwwWJUFcgF6pxqyXVksW7sgKJrboM4VSlQD +Sw= +rhino.cis.vutbr.cz. 86400 IN RRSIG AAAA 5 4 86400 20170204080646 20170105080646 28257 cis.vutbr.cz. T3Yf5PAkSeJtoOH90ea9zZBG9FC3iFhiCSerDn6d9up8GRfzxDsavYJC zQu+3vnOySySn+3TMzQSSFcWdJC2iO7ulaDGr177Gof9QJbKSVSMW7jt YDE2f4/R4Go3NZVwjk/HfpCInoR6pHNA1s/9hMnWtiVopmBdfzyd3/sW YOU= +shark.ro.vutbr.cz. 3600 IN RRSIG A 5 4 3600 20170222120032 20170123120032 12150 ro.vutbr.cz. SmhgyF48yX/6yH7AdSmGX60NL/xaiKH/oAzB0rnPfQZ6j+UfV57ginVV lj798K9A8jjucUpqE8ua2mZ6/aOhpqlV2iI0CZXG44zOupsCY1/OXBDx YNetBcjoXDQCBQRLLLEUL5FerDVxqT74ngdLdKubwRdrB0TLQlvpBr+F Tc8= +pipit.cis.vutbr.cz. 86400 IN RRSIG A 5 4 86400 20170204080646 20170105080646 28257 cis.vutbr.cz. Cz9etHnEOQTzu+6rYJEqx/SQ1tQgPOCyf8HSj4KOsx89jtgiHNC6pep6 ZE0SphMGAs3jC/uGIhlaFNZ3i38OQIMuqwacbz+XZyW5bByvV3QZrhqh dFxMDfmPuNiCAT3crFpUkvVW1OE3YfGHzZGXX7JP5wb1b8A3X6Qih7fV +nQ= +pipit.cis.vutbr.cz. 86400 IN RRSIG AAAA 5 4 86400 20170204080646 20170105080646 28257 cis.vutbr.cz. piafjh6my2fooZRrzwCu9RQ95gYaMQkhIkDaGX/fT6wXzSdmgFZkS1Nl EMIKdDCQaPrLGMG3p32ptMkAm4esPekeyNtLSMBtXwZyUkgEGn6h1QM2 Yr3TOo8cixfk5nmRRdlYadf5krLb8yI9exiqeymgEQLa1YNRz/bWArlX bn8= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA DO +SECTION QUESTION +ro.vutbr.cz. IN DS +SECTION ANSWER +ro.vutbr.cz. 28800 IN DS 16627 5 2 1AEE56EAF9D01A51C8C524E55A7FAE0E27207911F0FA6126052CE5B3 39335FC8 +ro.vutbr.cz. 28800 IN DS 16627 5 1 BFDFD0FB1EDFCEBFB9ECB13C93F9CA65755217BA +ro.vutbr.cz. 28800 IN RRSIG DS 5 3 28800 20170216060902 20170117060902 39756 vutbr.cz. OOJfGI14bRHqeWhRLMOa75pfHo+clR4rMJpvO3PPjmheownqy2awA7u3 xR5FJko7A6e+difoJdAWCMzN7x1qcrd1htOOKOc7wtcb+QC2JH8B/e0G 0gNPw2UKsFL1Qw9HQkSqxyIaCGg3nMLO1hh3AVccZadw2f/jLpAzw5/1 pLA= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA DO +SECTION QUESTION +ro.vutbr.cz. IN DNSKEY +SECTION ANSWER +ro.vutbr.cz. 3600 IN DNSKEY 256 3 5 AwEAAb4tyN4pqltB48s1xQS3ZPXnTZJMvgXxiouXU9xtzj4wnhjhZp45 H7ozslWuksrwQWhZ8AASAD5kPFbQRbwpQ7xbEb3xdKHaWCFpyRCTkrqa ZQZQy4gaVqO+oRW42dIQ9K08A/WfvRuRDtw3VWDATp9pUkgpvb1n6+lp 71YK19RX +ro.vutbr.cz. 3600 IN DNSKEY 257 3 5 AwEAAef6bqTAl94KddNHvit41gw6QBKkiYjUeS+UP58VHybV29RC7sSE +rYmkXabaMOLmoqMQRMepBEaUdM5OoZBWibHrPAbG0Wf+vlMOoWD5+EC 2mCxrUntIlOuS4XpMTh22+l0k1xSPiMGKjY0BDR95Iu3dDezCVl9PkPp tHj/rAnRTH7Q0fH9Mip8sigosd/CmsoY03I0AcZT4z1+XpGsq5Npxwtj 7cz0SRTI/eV5nynNYK+vr6kOfU1fw7p8/wxIfXkks0Xy8ktXa26DFdw1 RoqVlTS1s1diFyF5niCOT6Ei2kAlf0fggZJBypwoK+6J42wwD2OhORX+ lKrhooaN4TU9AcHwgv25XTXhUq4tYh+veazdXNWDjEb3ZyLM8fKERCa9 YtDBFoHM7yFOHbsOhHKMn8F6T2Boi73hU/+wspjL/n8taKevyyygGg+U g4ugo2pTIouAs5DNnv+nUrpctcKZ5nMEUVl+3XBsXplIyz9QEKHWdFzL gyfIZEok8WdYHebcIy1vJrxzqCNw9ixnTn+OK1lwlMToVH1AGpvRKRPo wGSFaIrDyXxKul34j2jEhP9TWRcJqncy166Ueu3c0BKmclM29N8jeWbP 3TqRJ3RRxNj/vk6c/UGmmrHEz8YdNp2L0hv3JgItr2GujCvPApUvLNPW C7DSErQ3JsjV3gah +ro.vutbr.cz. 3600 IN RRSIG DNSKEY 5 3 3600 20170222120032 20170123120032 12150 ro.vutbr.cz. pN+8YElj24dhtnOQ20sjWxJTjx+FLTMrPms1lWIJKZtp2evQBG5AnAep 6w0QeMUTIh9ter58Dh6wu2IN4uA1h3ThxnSgwLraOChUFBtPTO8h5y8J mAq4KXSfqbEcHzZO/nBAtxSUk7aUz7yWf09xE+iozW3ORRWIXovMYci5 eEw= +ro.vutbr.cz. 3600 IN RRSIG DNSKEY 5 3 3600 20170222120032 20170123120032 16627 ro.vutbr.cz. DSaIAl+iyToM8+ai9xuRVcRshYyI66XHWkOz0XEbIAwbc8aEMEeFCA91 1vpuBb6H92MXvM8hYsBhZHNIA0ApoIE4bdyEGZY05XN3GYgJ4BEhXJVM RR+inJf+vGGqdlRP6F2sPO+rCqfxWBvSoUFU7DpCpkl7hz2Ex0Clm9C9 YnWgL+tGmAH33s2Y8lTA3hG/0W0NxD5zy1LiyDa8Ls3vV4MC6gVxyloT Capd8FkDL9PmgW0gMRNtIWmc5Hw+j/HRMoy+oRCe8PIfUL/Dpx3iTAH8 iN3wV8apV2uPa0L8QgpixK4Tc87aSainCopVY+NOc5t0HErUzj8i7qA9 J/cRtQvlUzln5vBsrQsVIzIeNV4o8/cM3zFyfdKkHh1tWYKLJKkjfXc5 +7VMvF8PnoHceT/Zr2gCc8tnygRobypzgqy3p69bRJqiT0/eCAgpGusV 1DCOJY0sdiGDZEtpqeINbAgGKAMmmNwjIwYSFowRzdawip1wNd+90RhI +8hvx8Sc5+K5Mom2BF2wGHf/2Kv/ArzyXxqqcNozM61L1AjxIsBHjnLZ TzPlLntmiHUVaqET9Yc3G0K/RdsIpqz4M79N0BX66a58x2a3fLqQdrEC QshZPNxk2S4eCsrVRjHvU4a7e74Rbf/zXp89Y+jmwBbDMdnp+2/h9s6U J0sEBCYyo9M= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA DO +SECTION QUESTION +piranha.ro.vutbr.cz. IN A +SECTION ANSWER +piranha.ro.vutbr.cz. 3600 IN A 147.229.2.90 +piranha.ro.vutbr.cz. 3600 IN RRSIG A 5 4 3600 20170222120032 20170123120032 12150 ro.vutbr.cz. Jz8bcAADQjCKTCcF70IK1aHGQlM4ukyN0myABlxoPaqid1mHX5jwR91b kdQmUAh2xDitlgRLbFjbUUgmjSPzQ5Qt7GAFUsVmqxvjbOLZjqHER1dh zmiWO0fDvvP647Osv3RiAP822rNUJcJrUBZU9LmeP05gwIHcpJrhdVBT b7I= +SECTION AUTHORITY +ro.vutbr.cz. 86400 IN NS shark.ro.vutbr.cz. +ro.vutbr.cz. 86400 IN NS rhino.cis.vutbr.cz. +ro.vutbr.cz. 86400 IN NS pipit.cis.vutbr.cz. +ro.vutbr.cz. 86400 IN RRSIG NS 5 3 86400 20170222120032 20170123120032 12150 ro.vutbr.cz. HAQ8A+QNsS1WIXdW/fbT3jP+IxObBBvgUmvzsmJBXo8HMtnMAcuCQGmB 2JBQsQethQXsdyLnMK8to/5A9VRkqkAa7edxUoy7SdDi/mzGeLAVhF+5 kXSPD6t1vjiNdnIYAMpiOQbodCGxAnq6jnNyrjEzffdq3qw+5IkFNdG4 7Pw= +SECTION ADDITIONAL +rhino.cis.vutbr.cz. 86400 IN A 147.229.3.10 +rhino.cis.vutbr.cz. 86400 IN AAAA 2001:67c:1220:e000::93e5:30a +shark.ro.vutbr.cz. 3600 IN A 147.229.2.59 +pipit.cis.vutbr.cz. 86400 IN A 77.93.219.110 +pipit.cis.vutbr.cz. 86400 IN AAAA 2a01:430:120::4d5d:db6e +rhino.cis.vutbr.cz. 86400 IN RRSIG A 5 4 86400 20170204080646 20170105080646 28257 cis.vutbr.cz. X/tDf8e3JEV0LxiItfpQnBzeaRIq693VG8d30iCH4/1I0uqyCfxboWmm /CBpn9A8MCJu9NEEv+4+povNlfUfqi2yjsqJEVj8ztHxD4g9cc284Cv6 ySjxrSZ9axVqoaopEXujiTwwWJUFcgF6pxqyXVksW7sgKJrboM4VSlQD +Sw= +rhino.cis.vutbr.cz. 86400 IN RRSIG AAAA 5 4 86400 20170204080646 20170105080646 28257 cis.vutbr.cz. T3Yf5PAkSeJtoOH90ea9zZBG9FC3iFhiCSerDn6d9up8GRfzxDsavYJC zQu+3vnOySySn+3TMzQSSFcWdJC2iO7ulaDGr177Gof9QJbKSVSMW7jt YDE2f4/R4Go3NZVwjk/HfpCInoR6pHNA1s/9hMnWtiVopmBdfzyd3/sW YOU= +shark.ro.vutbr.cz. 3600 IN RRSIG A 5 4 3600 20170222120032 20170123120032 12150 ro.vutbr.cz. SmhgyF48yX/6yH7AdSmGX60NL/xaiKH/oAzB0rnPfQZ6j+UfV57ginVV lj798K9A8jjucUpqE8ua2mZ6/aOhpqlV2iI0CZXG44zOupsCY1/OXBDx YNetBcjoXDQCBQRLLLEUL5FerDVxqT74ngdLdKubwRdrB0TLQlvpBr+F Tc8= +pipit.cis.vutbr.cz. 86400 IN RRSIG A 5 4 86400 20170204080646 20170105080646 28257 cis.vutbr.cz. Cz9etHnEOQTzu+6rYJEqx/SQ1tQgPOCyf8HSj4KOsx89jtgiHNC6pep6 ZE0SphMGAs3jC/uGIhlaFNZ3i38OQIMuqwacbz+XZyW5bByvV3QZrhqh dFxMDfmPuNiCAT3crFpUkvVW1OE3YfGHzZGXX7JP5wb1b8A3X6Qih7fV +nQ= +pipit.cis.vutbr.cz. 86400 IN RRSIG AAAA 5 4 86400 20170204080646 20170105080646 28257 cis.vutbr.cz. piafjh6my2fooZRrzwCu9RQ95gYaMQkhIkDaGX/fT6wXzSdmgFZkS1Nl EMIKdDCQaPrLGMG3p32ptMkAm4esPekeyNtLSMBtXwZyUkgEGn6h1QM2 Yr3TOo8cixfk5nmRRdlYadf5krLb8yI9exiqeymgEQLa1YNRz/bWArlX bn8= +ENTRY_END + +; end of pipit.cis.vutbr.cz. & rhino.cis.vutbr.cz. +RANGE_END + + +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.vutbr.cz. IN A +ENTRY_END + +; recursion happens here. +STEP 10 CHECK_ANSWER +ENTRY_BEGIN +MATCH flags rcode question answer +REPLY QR RD RA NOERROR +SECTION QUESTION +www.vutbr.cz. IN A +SECTION ANSWER +www.vutbr.cz. IN CNAME piranha.ro.vutbr.cz. +piranha.ro.vutbr.cz. IN A 147.229.2.90 +ENTRY_END + +STEP 20 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.vutbr.cz. IN A +ENTRY_END + +STEP 21 CHECK_ANSWER +ENTRY_BEGIN +MATCH flags rcode question answer +REPLY QR RD RA AD NOERROR +SECTION QUESTION +www.vutbr.cz. IN A +SECTION ANSWER +www.vutbr.cz. IN CNAME piranha.ro.vutbr.cz. +www.vutbr.cz. IN RRSIG CNAME 5 3 300 20170216060902 20170117060902 39756 vutbr.cz. 9B3UC5SOEw1+yKlYlOTINEuNq0Kdglywc5IYJwzeSzQ3ykptzZo3ABSy bYhTqImVkhm/4NFM9/4HWMHPDzTmrWS0mCI/ljCd/oe/PxW/uESvo4P5 EQzlcuH6xBzc1KdEFAJOSmRzFjj3vyK1QN3k/c+1y2oMFOYOR2oOzCw+ MIE= +piranha.ro.vutbr.cz. IN A 147.229.2.90 +piranha.ro.vutbr.cz. 3600 IN RRSIG A 5 4 3600 20170222120032 20170123120032 12150 ro.vutbr.cz. Jz8bcAADQjCKTCcF70IK1aHGQlM4ukyN0myABlxoPaqid1mHX5jwR91b kdQmUAh2xDitlgRLbFjbUUgmjSPzQ5Qt7GAFUsVmqxvjbOLZjqHER1dh zmiWO0fDvvP647Osv3RiAP822rNUJcJrUBZU9LmeP05gwIHcpJrhdVBT b7I= +ENTRY_END + +SCENARIO_END diff --git a/lib/cache/peek.c b/lib/cache/peek.c new file mode 100644 index 0000000..e1901ac --- /dev/null +++ b/lib/cache/peek.c @@ -0,0 +1,774 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "lib/cache/impl.h" + +#include "lib/dnssec/ta.h" +#include "lib/layer/iterate.h" + +/* The whole file only exports peek_nosync(). + * Forwards for larger chunks of code: */ + +static int found_exact_hit(kr_layer_t *ctx, knot_pkt_t *pkt, knot_db_val_t val, + uint8_t lowest_rank); +static int closest_NS(struct kr_cache *cache, struct key *k, entry_list_t el, + struct kr_query *qry, bool only_NS, bool is_DS); +static int answer_simple_hit(kr_layer_t *ctx, knot_pkt_t *pkt, uint16_t type, + const struct entry_h *eh, const void *eh_bound, uint32_t new_ttl); +static int answer_dname_hit(kr_layer_t *ctx, knot_pkt_t *pkt, const knot_dname_t *dname_owner, + const struct entry_h *eh, const void *eh_bound, uint32_t new_ttl); +static int try_wild(struct key *k, struct answer *ans, const knot_dname_t *clencl_name, + uint16_t type, uint8_t lowest_rank, + const struct kr_query *qry, struct kr_cache *cache); + +static int peek_encloser( + struct key *k, struct answer *ans, int sname_labels, + uint8_t lowest_rank, const struct kr_query *qry, struct kr_cache *cache); + + +static int nsec_p_init(struct nsec_p *nsec_p, knot_db_val_t nsec_p_entry, bool with_knot) +{ + const size_t stamp_len = sizeof(uint32_t); + if (nsec_p_entry.len <= stamp_len) { /* plain NSEC if equal */ + nsec_p->raw = NULL; + nsec_p->hash = 0; + return kr_ok(); + } + nsec_p->raw = (uint8_t *)nsec_p_entry.data + stamp_len; + nsec_p->hash = nsec_p_mkHash(nsec_p->raw); + if (!with_knot) return kr_ok(); + /* Convert NSEC3 params to another format. */ + const dnssec_binary_t rdata = { + .size = nsec_p_rdlen(nsec_p->raw), + .data = (uint8_t *)/*const-cast*/nsec_p->raw, + }; + int ret = dnssec_nsec3_params_from_rdata(&nsec_p->libknot, &rdata); + return ret == DNSSEC_EOK ? kr_ok() : kr_error(ret); +} + +static void nsec_p_cleanup(struct nsec_p *nsec_p) +{ + dnssec_binary_free(&nsec_p->libknot.salt); + /* We don't really need to clear it, but it's not large. (`salt` zeroed above) */ + memset(nsec_p, 0, sizeof(*nsec_p)); +} + +/** Compute new TTL for nsec_p entry, using SOA serial arith. + * \param new_ttl (optionally) write the new TTL (even if negative) + * \return error code, e.g. kr_error(ESTALE) */ +static int nsec_p_ttl(knot_db_val_t entry, const uint32_t timestamp, int32_t *new_ttl) +{ + if (kr_fails_assert(entry.data)) + return kr_error(EINVAL); + uint32_t stamp; + if (!entry.len) + return kr_error(ENOENT); + if (kr_fails_assert(entry.len >= sizeof(stamp))) + return kr_error(EILSEQ); + memcpy(&stamp, entry.data, sizeof(stamp)); + int32_t newttl = stamp - timestamp; + if (new_ttl) *new_ttl = newttl; + return newttl < 0 ? kr_error(ESTALE) : kr_ok(); +} + +static uint8_t get_lowest_rank(const struct kr_query *qry, const knot_dname_t *name, const uint16_t type) +{ + /* Shut up linters. */ + kr_require(qry && qry->request); + /* TODO: move rank handling into the iterator (DNSSEC_* flags)? */ + const bool allow_unverified = + knot_wire_get_cd(qry->request->qsource.packet->wire) || qry->flags.STUB; + /* in stub mode we don't trust RRs anyway ^^ */ + if (qry->flags.NONAUTH) { + return KR_RANK_INITIAL; + /* Note: there's little sense in validation status for non-auth records. + * In case of using NONAUTH to get NS IPs, knowing that you ask correct + * IP doesn't matter much for security; it matters whether you can + * validate the answers from the NS. + */ + } else if (!allow_unverified) { + /* Records not present under any TA don't have their security + * verified at all, so we also accept low ranks in that case. */ + const bool ta_covers = kr_ta_closest(qry->request->ctx, name, type); + /* ^ TODO: performance? TODO: stype - call sites */ + if (ta_covers) { + return KR_RANK_INSECURE | KR_RANK_AUTH; + } /* else fallthrough */ + } + return KR_RANK_INITIAL | KR_RANK_AUTH; +} + + +/** Almost whole .produce phase for the cache module. + * \note we don't transition to KR_STATE_FAIL even in case of "unexpected errors". + */ +int peek_nosync(kr_layer_t *ctx, knot_pkt_t *pkt) +{ + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + struct kr_cache *cache = &req->ctx->cache; + + struct key k_storage, *k = &k_storage; + int ret = kr_dname_lf(k->buf, qry->sname, false); + if (kr_fails_assert(ret == 0)) + return ctx->state; + + const uint8_t lowest_rank = get_lowest_rank(qry, qry->sname, qry->stype); + + /**** 1. find the name or the closest (available) zone, not considering wildcards + **** 1a. exact name+type match (can be negative, mainly in insecure zones) */ + { + knot_db_val_t key = key_exact_type_maypkt(k, qry->stype); + knot_db_val_t val = { NULL, 0 }; + ret = cache_op(cache, read, &key, &val, 1); + if (!ret) { + /* found an entry: test conditions, materialize into pkt, etc. */ + ret = found_exact_hit(ctx, pkt, val, lowest_rank); + } + } + if (!ret) { + return KR_STATE_DONE; + } else if (kr_fails_assert(ret == kr_error(ENOENT))) { + VERBOSE_MSG(qry, "=> exact hit error: %d %s\n", ret, kr_strerror(ret)); + return ctx->state; + } + + /* Avoid aggressive answers in STUB mode. + * As STUB mode doesn't validate, it wouldn't save the necessary records. + * Moreover, this special case avoids unintentional NXDOMAIN on grafted subtrees. */ + if (qry->flags.STUB) + return ctx->state; + + /**** 1b. otherwise, find the longest prefix zone/xNAME (with OK time+rank). [...] */ + k->zname = qry->sname; + ret = kr_dname_lf(k->buf, k->zname, false); /* LATER(optim.): probably remove */ + if (kr_fails_assert(ret == 0)) + return ctx->state; + entry_list_t el; + ret = closest_NS(cache, k, el, qry, false, qry->stype == KNOT_RRTYPE_DS); + if (ret) { + if (kr_fails_assert(ret == kr_error(ENOENT)) || !el[0].len) { + return ctx->state; + } + } + switch (k->type) { + case KNOT_RRTYPE_CNAME: { + const knot_db_val_t v = el[EL_CNAME]; + if (kr_fails_assert(v.data && v.len)) + return ctx->state; + const int32_t new_ttl = get_new_ttl(v.data, qry, qry->sname, + KNOT_RRTYPE_CNAME, qry->timestamp.tv_sec); + ret = answer_simple_hit(ctx, pkt, KNOT_RRTYPE_CNAME, v.data, + knot_db_val_bound(v), new_ttl); + return ret == kr_ok() ? KR_STATE_DONE : ctx->state; + } + case KNOT_RRTYPE_DNAME: { + const knot_db_val_t v = el[EL_DNAME]; + if (kr_fails_assert(v.data && v.len)) + return ctx->state; + /* TTL: for simplicity, we just ask for TTL of the generated CNAME. */ + const int32_t new_ttl = get_new_ttl(v.data, qry, qry->sname, + KNOT_RRTYPE_CNAME, qry->timestamp.tv_sec); + ret = answer_dname_hit(ctx, pkt, k->zname, v.data, + knot_db_val_bound(v), new_ttl); + return ret == kr_ok() ? KR_STATE_DONE : ctx->state; + } + } + + /* We have to try proving from NSEC*. */ + auto_free char *log_zname = NULL; + WITH_VERBOSE(qry) { + log_zname = kr_dname_text(k->zname); + if (!el[0].len) { + VERBOSE_MSG(qry, "=> no NSEC* cached for zone: %s\n", log_zname); + } + } + +#if 0 + if (!eh) { /* fall back to root hints? */ + ret = kr_zonecut_set_sbelt(req->ctx, &qry->zone_cut); + if (ret) return ctx->state; + kr_assert(!qry->zone_cut.parent); + + //VERBOSE_MSG(qry, "=> using root hints\n"); + //qry->flags.AWAIT_CUT = false; + return ctx->state; + } + + /* Now `eh` points to the closest NS record that we've found, + * and that's the only place to start - we may either find + * a negative proof or we may query upstream from that point. */ + kr_zonecut_set(&qry->zone_cut, k->zname); + ret = kr_make_query(qry, pkt); // TODO: probably not yet - qname minimization + if (ret) return ctx->state; +#endif + + /** Structure for collecting multiple NSEC* + RRSIG records, + * in preparation for the answer, and for tracking the progress. */ + struct answer ans; + memset(&ans, 0, sizeof(ans)); + ans.mm = &pkt->mm; + const int sname_labels = knot_dname_labels(qry->sname, NULL); + + /* Try the NSEC* parameters in order, until success. + * Let's not mix different parameters for NSEC* RRs in a single proof. */ + for (int i = 0; ;) { + int32_t log_new_ttl = -123456789; /* visually recognizable value */ + ret = nsec_p_ttl(el[i], qry->timestamp.tv_sec, &log_new_ttl); + if (!ret || kr_log_is_debug_qry(CACHE, qry)) { + nsec_p_init(&ans.nsec_p, el[i], !ret); + } + if (ret) { + VERBOSE_MSG(qry, "=> skipping zone: %s, %s, hash %x;" + "new TTL %d, ret %d\n", + log_zname, (ans.nsec_p.raw ? "NSEC3" : "NSEC"), + (unsigned)ans.nsec_p.hash, (int)log_new_ttl, ret); + /* no need for nsec_p_cleanup() in this case */ + goto cont; + } + VERBOSE_MSG(qry, "=> trying zone: %s, %s, hash %x\n", + log_zname, (ans.nsec_p.raw ? "NSEC3" : "NSEC"), + (unsigned)ans.nsec_p.hash); + /**** 2. and 3. inside */ + ret = peek_encloser(k, &ans, sname_labels, + lowest_rank, qry, cache); + nsec_p_cleanup(&ans.nsec_p); + if (!ret) break; + if (ret < 0) return ctx->state; + cont: + /* Otherwise we try another nsec_p, if available. */ + if (++i == ENTRY_APEX_NSECS_CNT) return ctx->state; + /* clear possible partial answers in `ans` (no need to deallocate) */ + ans.rcode = 0; + memset(&ans.rrsets, 0, sizeof(ans.rrsets)); + } + + /**** 4. add SOA iff needed */ + if (ans.rcode != PKT_NOERROR) { + /* Assuming k->buf still starts with zone's prefix, + * look up the SOA in cache. */ + k->buf[0] = k->zlf_len; + knot_db_val_t key = key_exact_type(k, KNOT_RRTYPE_SOA); + knot_db_val_t val = { NULL, 0 }; + ret = cache_op(cache, read, &key, &val, 1); + const struct entry_h *eh; + if (ret || !(eh = entry_h_consistent_E(val, KNOT_RRTYPE_SOA))) { + kr_assert(ret); /* only want to catch `eh` failures */ + VERBOSE_MSG(qry, "=> SOA missed\n"); + return ctx->state; + } + /* Check if the record is OK. */ + int32_t new_ttl = get_new_ttl(eh, qry, k->zname, KNOT_RRTYPE_SOA, + qry->timestamp.tv_sec); + if (new_ttl < 0 || eh->rank < lowest_rank || eh->is_packet) { + VERBOSE_MSG(qry, "=> SOA unfit %s: rank 0%.2o, new TTL %d\n", + (eh->is_packet ? "packet" : "RR"), + eh->rank, new_ttl); + return ctx->state; + } + /* Add the SOA into the answer. */ + ret = entry2answer(&ans, AR_SOA, eh, knot_db_val_bound(val), + k->zname, KNOT_RRTYPE_SOA, new_ttl); + if (ret) return ctx->state; + } + + /* Find our target RCODE. */ + int real_rcode; + switch (ans.rcode) { + case PKT_NODATA: + case PKT_NOERROR: /* positive wildcarded response */ + real_rcode = KNOT_RCODE_NOERROR; + break; + case PKT_NXDOMAIN: + real_rcode = KNOT_RCODE_NXDOMAIN; + break; + default: + kr_assert(false); + case 0: /* i.e. nothing was found */ + /* LATER(optim.): zone cut? */ + VERBOSE_MSG(qry, "=> cache miss\n"); + return ctx->state; + } + + if (pkt_renew(pkt, qry->sname, qry->stype) + || knot_pkt_begin(pkt, KNOT_ANSWER) + ) { + kr_assert(false); + return ctx->state; + } + knot_wire_set_rcode(pkt->wire, real_rcode); + + bool expiring = false; // TODO + for (int i = 0; i < sizeof(ans.rrsets) / sizeof(ans.rrsets[0]); ++i) { + if (i == 1) knot_pkt_begin(pkt, KNOT_AUTHORITY); + if (!ans.rrsets[i].set.rr) continue; + expiring = expiring || ans.rrsets[i].set.expiring; + ret = pkt_append(pkt, &ans.rrsets[i], ans.rrsets[i].set.rank); + if (kr_fails_assert(ret == 0)) + return ctx->state; + } + + /* Finishing touches. */ + struct kr_qflags * const qf = &qry->flags; + qf->EXPIRING = expiring; + qf->CACHED = true; + qf->NO_MINIMIZE = true; + + return KR_STATE_DONE; +} + +/** + * This is where the high-level "business logic" of aggressive cache is. + * \return 0: success (may need SOA); >0: try other nsec_p; <0: exit cache immediately. + */ +static int peek_encloser( + struct key *k, struct answer *ans, const int sname_labels, + uint8_t lowest_rank, const struct kr_query *qry, struct kr_cache *cache) +{ + /** Start of NSEC* covering the sname; + * it's part of key - the one within zone (read only) */ + knot_db_val_t cover_low_kwz = { NULL, 0 }; + knot_dname_t cover_hi_storage[KNOT_DNAME_MAXLEN]; + /** End of NSEC* covering the sname. */ + knot_db_val_t cover_hi_kwz = { + .data = cover_hi_storage, + .len = sizeof(cover_hi_storage), + }; + + /**** 2. Find a closest (provable) encloser (of sname). */ + int clencl_labels = -1; + bool clencl_is_tentative = false; + if (!ans->nsec_p.raw) { /* NSEC */ + int ret = nsec1_encloser(k, ans, sname_labels, &clencl_labels, + &cover_low_kwz, &cover_hi_kwz, qry, cache); + if (ret) return ret; + } else { + int ret = nsec3_encloser(k, ans, sname_labels, &clencl_labels, + qry, cache); + clencl_is_tentative = ret == ABS(ENOENT) && clencl_labels >= 0; + /* ^^ Last chance: *positive* wildcard record under this clencl. */ + if (ret && !clencl_is_tentative) return ret; + } + + /* We should have either a match or a cover at this point. */ + if (kr_fails_assert(ans->rcode == PKT_NODATA || ans->rcode == PKT_NXDOMAIN)) + return kr_error(EINVAL); + const bool ncloser_covered = ans->rcode == PKT_NXDOMAIN; + + /** Name of the closest (provable) encloser. */ + const knot_dname_t *clencl_name = qry->sname; + for (int l = sname_labels; l > clencl_labels; --l) + clencl_name = knot_wire_next_label(clencl_name, NULL); + + /**** 3. source of synthesis checks, in case the next closer name was covered. + **** 3a. We want to query for NSEC* of source of synthesis (SS) or its + * predecessor, providing us with a proof of its existence or non-existence. */ + if (ncloser_covered && !ans->nsec_p.raw) { + int ret = nsec1_src_synth(k, ans, clencl_name, + cover_low_kwz, cover_hi_kwz, qry, cache); + if (ret == AR_SOA) return 0; + kr_assert(ret <= 0); + if (ret) return ret; + + } else if (ncloser_covered && ans->nsec_p.raw && !clencl_is_tentative) { + int ret = nsec3_src_synth(k, ans, clencl_name, qry, cache); + if (ret == AR_SOA) return 0; + kr_assert(ret <= 0); + if (ret) return ret; + + } /* else (!ncloser_covered) so no wildcard checks needed, + * as we proved that sname exists. */ + + /**** 3b. find wildcarded answer, if next closer name was covered + * and we don't have a full proof yet. (common for NSEC*) */ + if (!ncloser_covered) + return kr_ok(); /* decrease indentation */ + /* Construct key for exact qry->stype + source of synthesis. */ + int ret = kr_dname_lf(k->buf, clencl_name, true); + if (kr_fails_assert(ret == 0)) + return kr_error(ret); + const uint16_t types[] = { qry->stype, KNOT_RRTYPE_CNAME }; + for (int i = 0; i < (2 - (qry->stype == KNOT_RRTYPE_CNAME)); ++i) { + ret = try_wild(k, ans, clencl_name, types[i], + lowest_rank, qry, cache); + if (ret == kr_ok()) { + return kr_ok(); + } else if (kr_fails_assert(ret == kr_error(ENOENT) || ret == kr_error(ESTALE))) { + return kr_error(ret); + } + /* else continue */ + } + /* Neither attempt succeeded, but the NSEC* proofs were found, + * so skip trying other parameters, as it seems very unlikely + * to turn out differently than by the same wildcard search. */ + return kr_error(ENOENT); +} + +static void answer_simple_qflags(struct kr_qflags *qf, const struct entry_h *eh, + uint32_t new_ttl) +{ + /* Finishing touches. */ + qf->EXPIRING = is_expiring(eh->ttl, new_ttl); + qf->CACHED = true; + qf->NO_MINIMIZE = true; + qf->DNSSEC_INSECURE = kr_rank_test(eh->rank, KR_RANK_INSECURE); + if (qf->DNSSEC_INSECURE) { + qf->DNSSEC_WANT = false; + } +} + +#define CHECK_RET(ret) do { \ + if (kr_fails_assert((ret) >= 0)) return kr_error((ret)); \ +} while (false) + +static int answer_simple_hit(kr_layer_t *ctx, knot_pkt_t *pkt, uint16_t type, + const struct entry_h *eh, const void *eh_bound, uint32_t new_ttl) +{ + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + + /* All OK, so start constructing the (pseudo-)packet. */ + int ret = pkt_renew(pkt, qry->sname, qry->stype); + CHECK_RET(ret); + + /* Materialize the sets for the answer in (pseudo-)packet. */ + struct answer ans; + memset(&ans, 0, sizeof(ans)); + ans.mm = &pkt->mm; + ret = entry2answer(&ans, AR_ANSWER, eh, eh_bound, + qry->sname, type, new_ttl); + CHECK_RET(ret); + /* Put links to the materialized data into the pkt. */ + ret = pkt_append(pkt, &ans.rrsets[AR_ANSWER], eh->rank); + CHECK_RET(ret); + + answer_simple_qflags(&qry->flags, eh, new_ttl); + + VERBOSE_MSG(qry, "=> satisfied by exact %s: rank 0%.2o, new TTL %d\n", + (type == KNOT_RRTYPE_CNAME ? "CNAME" : "RRset"), + eh->rank, new_ttl); + return kr_ok(); +} + +static int answer_dname_hit(kr_layer_t *ctx, knot_pkt_t *pkt, const knot_dname_t *dname_owner, + const struct entry_h *eh, const void *eh_bound, uint32_t new_ttl) +{ + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + + /* All OK, so start constructing the (pseudo-)packet. */ + int ret = pkt_renew(pkt, qry->sname, qry->stype); + CHECK_RET(ret); + + /* Materialize the DNAME for the answer in (pseudo-)packet. */ + struct answer ans; + memset(&ans, 0, sizeof(ans)); + ans.mm = &pkt->mm; + ret = entry2answer(&ans, AR_ANSWER, eh, eh_bound, + dname_owner, KNOT_RRTYPE_DNAME, new_ttl); + CHECK_RET(ret); + /* Put link to the RRset into the pkt. */ + ret = pkt_append(pkt, &ans.rrsets[AR_ANSWER], eh->rank); + CHECK_RET(ret); + const knot_dname_t *dname_target = + knot_dname_target(ans.rrsets[AR_ANSWER].set.rr->rrs.rdata); + + /* Generate CNAME RRset for the answer in (pseudo-)packet. */ + const int AR_CNAME = AR_SOA; + knot_rrset_t *rr = ans.rrsets[AR_CNAME].set.rr + = knot_rrset_new(qry->sname, KNOT_RRTYPE_CNAME, KNOT_CLASS_IN, + new_ttl, ans.mm); + CHECK_RET(rr ? kr_ok() : -ENOMEM); + const knot_dname_t *cname_target = knot_dname_replace_suffix(qry->sname, + knot_dname_labels(dname_owner, NULL), dname_target, ans.mm); + CHECK_RET(cname_target ? kr_ok() : -ENOMEM); + const int rdata_len = knot_dname_size(cname_target); + + if (rdata_len <= KNOT_DNAME_MAXLEN + && knot_dname_labels(cname_target, NULL) <= KNOT_DNAME_MAXLABELS) { + /* Normal case: the target name fits. */ + rr->rrs.count = 1; + rr->rrs.size = knot_rdata_size(rdata_len); + rr->rrs.rdata = mm_alloc(ans.mm, rr->rrs.size); + CHECK_RET(rr->rrs.rdata ? kr_ok() : -ENOMEM); + knot_rdata_init(rr->rrs.rdata, rdata_len, cname_target); + /* Put link to the RRset into the pkt. */ + ret = pkt_append(pkt, &ans.rrsets[AR_CNAME], eh->rank); + CHECK_RET(ret); + } else { + /* Note that it's basically a successful answer; name just doesn't fit. */ + knot_wire_set_rcode(pkt->wire, KNOT_RCODE_YXDOMAIN); + } + + answer_simple_qflags(&qry->flags, eh, new_ttl); + VERBOSE_MSG(qry, "=> satisfied by DNAME+CNAME: rank 0%.2o, new TTL %d\n", + eh->rank, new_ttl); + return kr_ok(); +} + +#undef CHECK_RET + +/** TODO: description; see the single call site for now. */ +static int found_exact_hit(kr_layer_t *ctx, knot_pkt_t *pkt, knot_db_val_t val, + uint8_t lowest_rank) +{ + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + + int ret = entry_h_seek(&val, qry->stype); + if (ret) return ret; + const struct entry_h *eh = entry_h_consistent_E(val, qry->stype); + if (kr_fails_assert(eh)) + return kr_error(ENOENT); + // LATER: recovery in case of error, perhaps via removing the entry? + // LATER(optim): perhaps optimize the zone cut search + + int32_t new_ttl = get_new_ttl(eh, qry, qry->sname, qry->stype, + qry->timestamp.tv_sec); + if (new_ttl < 0 || eh->rank < lowest_rank) { + /* Positive record with stale TTL or bad rank. + * LATER(optim.): It's unlikely that we find a negative one, + * so we might theoretically skip all the cache code. */ + + VERBOSE_MSG(qry, "=> skipping exact %s: rank 0%.2o (min. 0%.2o), new TTL %d\n", + eh->is_packet ? "packet" : "RR", eh->rank, lowest_rank, new_ttl); + return kr_error(ENOENT); + } + + const uint8_t *eh_bound = knot_db_val_bound(val); + if (eh->is_packet) { + /* Note: we answer here immediately, even if it's (theoretically) + * possible that we could generate a higher-security negative proof. + * Rank is high-enough so we take it to save time searching; + * in practice this also helps in some incorrect zones (live-signed). */ + return answer_from_pkt (ctx, pkt, qry->stype, eh, eh_bound, new_ttl); + } else { + return answer_simple_hit(ctx, pkt, qry->stype, eh, eh_bound, new_ttl); + } +} + + +/** Try to satisfy via wildcard (positively). See the single call site. */ +static int try_wild(struct key *k, struct answer *ans, const knot_dname_t *clencl_name, + const uint16_t type, const uint8_t lowest_rank, + const struct kr_query *qry, struct kr_cache *cache) +{ + knot_db_val_t key = key_exact_type(k, type); + /* Find the record. */ + knot_db_val_t val = { NULL, 0 }; + int ret = cache_op(cache, read, &key, &val, 1); + if (!ret) { + ret = entry_h_seek(&val, type); + } + if (ret) { + if (kr_fails_assert(ret == kr_error(ENOENT))) + VERBOSE_MSG(qry, "=> wildcard: hit error %d %s\n", + ret, strerror(abs(ret))); + WITH_VERBOSE(qry) { + auto_free char *clencl_str = kr_dname_text(clencl_name), + *type_str = kr_rrtype_text(type); + VERBOSE_MSG(qry, "=> wildcard: not found: *.%s %s\n", + clencl_str, type_str); + } + return ret; + } + /* Check if the record is OK. */ + const struct entry_h *eh = entry_h_consistent_E(val, type); + if (kr_fails_assert(eh)) + return kr_error(ret); + // LATER: recovery in case of error, perhaps via removing the entry? + int32_t new_ttl = get_new_ttl(eh, qry, qry->sname, type, qry->timestamp.tv_sec); + /* ^^ here we use the *expanded* wildcard name */ + if (new_ttl < 0 || eh->rank < lowest_rank || eh->is_packet) { + /* Wildcard record with stale TTL, bad rank or packet. */ + VERBOSE_MSG(qry, "=> wildcard: skipping %s, rank 0%.2o, new TTL %d\n", + eh->is_packet ? "packet" : "RR", eh->rank, new_ttl); + return kr_error(ESTALE); + } + /* Add the RR into the answer. */ + ret = entry2answer(ans, AR_ANSWER, eh, knot_db_val_bound(val), + qry->sname, type, new_ttl); + VERBOSE_MSG(qry, "=> wildcard: answer expanded, ret = %d, new TTL %d\n", + ret, (int)new_ttl); + if (ret) return kr_error(ret); + ans->rcode = PKT_NOERROR; + return kr_ok(); +} + +int kr_cache_closest_apex(struct kr_cache *cache, const knot_dname_t *name, bool is_DS, + knot_dname_t ** apex) +{ + if (kr_fails_assert(cache && cache->db && name && apex && *apex == NULL)) + return kr_error(EINVAL); + struct key k_storage, *k = &k_storage; + int ret = kr_dname_lf(k->buf, name, false); + if (ret) + return kr_error(ret); + entry_list_t el_; + k->zname = name; + ret = closest_NS(cache, k, el_, NULL, true, is_DS); + if (ret && ret != -abs(ENOENT)) + return ret; + *apex = knot_dname_copy(k->zname, NULL); + if (!*apex) + return kr_error(ENOMEM); + return kr_ok(); +} + +/** \internal for closest_NS. Check suitability of a single entry, setting k->type if OK. + * \return error code, negative iff whole list should be skipped. + */ +static int check_NS_entry(struct key *k, knot_db_val_t entry, int i, + bool exact_match, bool is_DS, + const struct kr_query *qry, uint32_t timestamp); + +/** + * Find the longest prefix zone/xNAME (with OK time+rank), starting at k->*. + * + * The found type is returned via k->type; the values are returned in el. + * \note we use k->type = KNOT_RRTYPE_NS also for the nsec_p result. + * \param qry can be NULL (-> gettimeofday(), but you lose the stale-serve hook) + * \param only_NS don't consider xNAMEs + * \return error code + */ +static int closest_NS(struct kr_cache *cache, struct key *k, entry_list_t el, + struct kr_query *qry, const bool only_NS, const bool is_DS) +{ + /* get the current timestamp */ + uint32_t timestamp; + if (qry) { + timestamp = qry->timestamp.tv_sec; + } else { + struct timeval tv; + if (gettimeofday(&tv, NULL)) return kr_error(errno); + timestamp = tv.tv_sec; + } + + int zlf_len = k->buf[0]; + + // LATER(optim): if stype is NS, we check the same value again + bool exact_match = true; + bool need_zero = true; + /* Inspect the NS/xNAME entries, shortening by a label on each iteration. */ + do { + k->buf[0] = zlf_len; + knot_db_val_t key = key_exact_type(k, KNOT_RRTYPE_NS); + knot_db_val_t val; + int ret = cache_op(cache, read, &key, &val, 1); + if (ret == kr_error(ENOENT)) goto next_label; + if (kr_fails_assert(ret == 0)) { + if (need_zero) memset(el, 0, sizeof(entry_list_t)); + return kr_error(ret); + } + + /* Check consistency, find any type; + * using `goto` for shortening by another label. */ + ret = entry_list_parse(val, el); + if (kr_fails_assert(ret == 0)) // do something about it? + goto next_label; + need_zero = false; + /* More types are possible; try in order. + * For non-fatal failures just "continue;" to try the next type. */ + /* Now a complication - we need to try EL_DNAME before NSEC* + * (Unfortunately that's not easy to write very nicely.) */ + if (!only_NS) { + const int i = EL_DNAME; + ret = check_NS_entry(k, el[i], i, exact_match, is_DS, + qry, timestamp); + if (ret < 0) goto next_label; else + if (!ret) { + /* We found our match. */ + k->zlf_len = zlf_len; + return kr_ok(); + } + } + const int el_count = only_NS ? EL_NS + 1 : EL_LENGTH; + for (int i = 0; i < el_count; ++i) { + if (i == EL_DNAME) continue; + ret = check_NS_entry(k, el[i], i, exact_match, is_DS, + qry, timestamp); + if (ret < 0) goto next_label; else + if (!ret) { + /* We found our match. */ + k->zlf_len = zlf_len; + return kr_ok(); + } + } + + next_label: + /* remove one more label */ + exact_match = false; + if (k->zname[0] == 0) { + /* We miss root NS in cache, but let's at least assume it exists. */ + k->type = KNOT_RRTYPE_NS; + k->zlf_len = zlf_len; + kr_assert(zlf_len == 0); + if (need_zero) memset(el, 0, sizeof(entry_list_t)); + return kr_error(ENOENT); + } + zlf_len -= (k->zname[0] + 1); + k->zname += (k->zname[0] + 1); + k->buf[zlf_len + 1] = 0; + } while (true); +} + +static int check_NS_entry(struct key *k, const knot_db_val_t entry, const int i, + const bool exact_match, const bool is_DS, + const struct kr_query *qry, uint32_t timestamp) +{ + const int ESKIP = ABS(ENOENT); + if (!entry.len + /* On a zone cut we want DS from the parent zone. */ + || (exact_match && is_DS) + /* CNAME is interesting only if we + * directly hit the name that was asked. + * Note that we want it even in the DS case. */ + || (i == EL_CNAME && !exact_match) + /* DNAME is interesting only if we did NOT + * directly hit the name that was asked. */ + || (i == EL_DNAME && exact_match) + ) { + return ESKIP; + } + + uint16_t type; + if (i < ENTRY_APEX_NSECS_CNT) { + type = KNOT_RRTYPE_NS; + int32_t log_new_ttl = -123456789; /* visually recognizable value */ + const int err = nsec_p_ttl(entry, timestamp, &log_new_ttl); + if (err) { + VERBOSE_MSG(qry, + "=> skipping unfit nsec_p: new TTL %d, error %d\n", + (int)log_new_ttl, err); + return ESKIP; + } + } else { + type = EL2RRTYPE(i); + /* Find the entry for the type, check positivity, TTL */ + const struct entry_h *eh = entry_h_consistent_E(entry, type); + if (kr_fails_assert(eh)) { + VERBOSE_MSG(qry, "=> EH not consistent\n"); + return kr_error(EILSEQ); + } + const int32_t log_new_ttl = get_new_ttl(eh, qry, k->zname, type, timestamp); + + const bool ok = /* Not interested in negative bogus or outdated RRs. */ + !eh->is_packet && log_new_ttl >= 0 + /* For NS any kr_rank is accepted, as insecure or even nonauth is OK */ + && (type == KNOT_RRTYPE_NS + || eh->rank >= get_lowest_rank(qry, k->zname, type)); + + WITH_VERBOSE(qry) { if (!ok) { + auto_free char *type_str = kr_rrtype_text(type); + const char *packet_str = eh->is_packet ? "packet" : "RR"; + VERBOSE_MSG(qry, + "=> skipping unfit %s %s: rank 0%.2o, new TTL %d\n", + type_str, packet_str, eh->rank, (int)log_new_ttl); + } } + if (!ok) return ESKIP; + } + k->type = type; + return kr_ok(); +} + diff --git a/lib/cache/test.integr/cache_minimal_nsec3.rpl b/lib/cache/test.integr/cache_minimal_nsec3.rpl new file mode 100644 index 0000000..7c4a5cf --- /dev/null +++ b/lib/cache/test.integr/cache_minimal_nsec3.rpl @@ -0,0 +1,4120 @@ +; SPDX-License-Identifier: GPL-3.0-or-later + trust-anchor: ". IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D" + val-override-date: 20190625160934 + stub-addr: 2001:503:ba3e::2:30 +CONFIG_END + +SCENARIO_BEGIN Test that minimal NSEC3 range does not trigger agressive cache (workaround for buggy auths, optimization to improve cache hit rate on correct auths using black lies) + +; Group's zones: +; . +; root-servers.net. +; Server names: +; f.root-servers.net. +; a.root-servers.net. +; j.root-servers.net. +; e.root-servers.net. +; i.root-servers.net. +; d.root-servers.net. +; m.root-servers.net. +; h.root-servers.net. +; c.root-servers.net. +; l.root-servers.net. +; g.root-servers.net. +; b.root-servers.net. +; k.root-servers.net. +RANGE_BEGIN 0 1000 + ADDRESS 2001:500:2f::f + ADDRESS 2001:503:ba3e::2:30 + ADDRESS 2001:500:12::d0d + ADDRESS 202.12.27.33 + ADDRESS 2001:7fd::1 + ADDRESS 2001:503:c27::2:30 + ADDRESS 2001:500:200::b + ADDRESS 198.97.190.53 + ADDRESS 192.58.128.30 + ADDRESS 198.41.0.4 + ADDRESS 199.7.91.13 + ADDRESS 2001:dc3::35 + ADDRESS 192.36.148.17 + ADDRESS 192.203.230.10 + ADDRESS 192.5.5.241 + ADDRESS 199.9.14.201 + ADDRESS 193.0.14.129 + ADDRESS 2001:7fe::53 + ADDRESS 2001:500:9f::42 + ADDRESS 2001:500:a8::e + ADDRESS 2001:500:2d::d + ADDRESS 192.112.36.4 + ADDRESS 2001:500:2::c + ADDRESS 192.33.4.12 + ADDRESS 199.7.83.42 + ADDRESS 2001:500:1::53 + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +i.root-servers.net. IN AAAA +SECTION ANSWER +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +g.root-servers.net. IN NS +SECTION AUTHORITY +root-servers.net. 3600000 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2019061700 14400 7200 1209600 3600000 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +e.root-servers.net. IN A +SECTION ANSWER +e.root-servers.net. 3600000 IN A 192.203.230.10 +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +f.root-servers.net. IN NS +SECTION AUTHORITY +root-servers.net. 3600000 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2019061700 14400 7200 1209600 3600000 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +i.root-servers.net. IN A +SECTION ANSWER +i.root-servers.net. 3600000 IN A 192.36.148.17 +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +c.root-servers.net. IN NS +SECTION AUTHORITY +root-servers.net. 3600000 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2019061700 14400 7200 1209600 3600000 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +root-servers.net. IN DS +SECTION AUTHORITY +root-servers.net. 3600000 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2019061700 14400 7200 1209600 3600000 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +c.root-servers.net. IN A +SECTION ANSWER +c.root-servers.net. 3600000 IN A 192.33.4.12 +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +d.root-servers.net. IN A +SECTION ANSWER +d.root-servers.net. 3600000 IN A 199.7.91.13 +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +f.root-servers.net. IN AAAA +SECTION ANSWER +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. 518400 IN NS a.root-servers.net. +. 518400 IN NS b.root-servers.net. +. 518400 IN NS c.root-servers.net. +. 518400 IN NS d.root-servers.net. +. 518400 IN NS e.root-servers.net. +. 518400 IN NS f.root-servers.net. +. 518400 IN NS g.root-servers.net. +. 518400 IN NS h.root-servers.net. +. 518400 IN NS i.root-servers.net. +. 518400 IN NS j.root-servers.net. +. 518400 IN NS k.root-servers.net. +. 518400 IN NS l.root-servers.net. +. 518400 IN NS m.root-servers.net. +. 518400 IN RRSIG NS 8 0 518400 20190708050000 20190625040000 25266 . IwbmJVqFUjJz5WwRbYqOWejRck85QWW8 eIGID2J+Qhw89iUDDz2lgvysed4WQfks 8Y2XZu79T0+RJF+mj1UUiE+Y6RdOmFDU Qx3ovGkYwOXcr1anreBD+Wn5tv1WW6El NbKf40pXdtDX6Ad1qx6hCHHR4hieQPww psNHmrGDg+Eog+VqYjwwRj9EaYEms5dU PRJmiHiACe85DZMCjxl6f+kp7ZXyFD/L coLi7QzXiRWYOPHhWKk3pqYGD1j7I7YB Oq7UujK+jPscWCDuArGmZwlhAtAPaPLe 5TZHIGE39c6eYpuXwSXZ1EPM545/9WsI HihzUQ75ltuiPXwjv0OpQg== +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +j.root-servers.net. IN NS +SECTION AUTHORITY +root-servers.net. 3600000 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2019061700 14400 7200 1209600 3600000 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +a.root-servers.net. IN A +SECTION ANSWER +a.root-servers.net. 3600000 IN A 198.41.0.4 +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +h.root-servers.net. IN NS +SECTION AUTHORITY +root-servers.net. 3600000 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2019061700 14400 7200 1209600 3600000 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +g.root-servers.net. IN A +SECTION ANSWER +g.root-servers.net. 3600000 IN A 192.112.36.4 +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +k.root-servers.net. IN A +SECTION ANSWER +k.root-servers.net. 3600000 IN A 193.0.14.129 +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +m.root-servers.net. IN AAAA +SECTION ANSWER +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +. IN DNSKEY +SECTION ANSWER +. 172800 IN DNSKEY 256 3 8 AwEAAcTQyaIe6nt3xSPOG2L/YfwBkOVT JN6mlnZ249O5Rtt3ZSRQHxQSW61AODYw 6bvgxrrGq8eeOuenFjcSYgNAMcBYoEYY mKDW6e9EryW4ZaT/MCq+8Am06oR40xAA 3fClOM6QjRcT85tP41Go946AicBGP8XO P/Aj1aI/oPRGzRnboUPUok/AzTNnW5np BU69+BuiIwYE7mQOiNBFePyvjQBdoiuY bmuD3Py0IyjlBxzZUXbqLsRL9gYFkCqe TY29Ik7usuzMTa+JRSLz6KGS5RSJ7CTS MjZg8aNaUbN2dvGhakJPh92HnLvMA3Te fFgbKJphFNPA3BWSKLZ02cRWXqM= +. 172800 IN DNSKEY 256 3 8 AwEAAeVDC34GZILwsQJy97K2Fst4P3XY ZrXLyrkausYzSqEjSUulgh+iLgHg0y7F IF890+sIjXsk7KLJUmCOWfYWPorNKEOK Lk5Zx/4M6D3IHZE3O3m/Eahrc28qQzmT LxiMZAW65MvR2UO3LxVtYOPBEBiDgAQD 47x2JLsJYtavCzNL5WiUk59OgvHmDqmc C7VXYBhK8V8Tic089XJgExGeplKWUt9y yc31ra1swJX51XsOaQz17+vyLVH8AZP2 6KvKFiZeoRbaq6vl+hc8HQnI2ug5rA2z oz3MsSQBvP1f/HvqsWxLqwXXKyDD1QM6 39U+XzVB8CYigyscRP22QCnwKIU= +. 172800 IN DNSKEY 257 3 8 AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexT BAvkMgJzkKTOiW1vkIbzxeF3+/4RgWOq 7HrxRixHlFlExOLAJr5emLvN7SWXgnLh 4+B5xQlNVz8Og8kvArMtNROxVQuCaSnI DdD5LKyWbRd2n9WGe2R8PzgCmr3EgVLr jyBxWezF0jLHwVN8efS3rCj/EWgvIWgb 9tarpVUDK/b58Da+sqqls3eNbuv7pr+e oZG+SrDK6nWeL3c6H5Apxz7LjVc1uTId sIXxuOLYA4/ilBmSVIzuDWfdRUfhHdY6 +cn8HFRm+2hM8AnXGXws9555KrUB5qih ylGa8subX2Nn6UwNR1AkUTV74bU= +. 172800 IN RRSIG DNSKEY 8 0 172800 20190711000000 20190620000000 20326 . LeNUOIxGGe+xAKxr13YIXNqMAxVH7RuD XQyVclUuxA9aENp0+yYkeIL+/lkTteEh dHXNVZqYch8QrvcsuCpDN2gKx5D5M04g KAjR5ywgvEdsZHr9DhjCZ3uvXKvbPsi6 14QpjYCxxvtq/ZZE6dhm59K3N3T8Mhm5 l36b6w+fR1F3Kc+eeJqy2ZjVxNe9CClE 4Qy6q78Yu6rS1vZkuzG1l2AT9Gko72St WbdsU2Ry9fBk+uCJOLxej37z5Rfi5EAz FcnfwQYryqCRt2go9PuD0/AulqG8wmTV z23tnwnaxowkYKxFH2yE0d7pDiFjvOyU HdGPXYwl/+GDmjrQsN6JPQ== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +b.root-servers.net. IN NS +SECTION AUTHORITY +root-servers.net. 3600000 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2019061700 14400 7200 1209600 3600000 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +k.root-servers.net. IN NS +SECTION AUTHORITY +root-servers.net. 3600000 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2019061700 14400 7200 1209600 3600000 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +. IN DS +SECTION AUTHORITY +. 86400 IN NSEC aaa. NS SOA RRSIG NSEC DNSKEY +. 86400 IN RRSIG NSEC 8 0 86400 20190708050000 20190625040000 25266 . fAYegvNXOW8j+x++y5EWtNQlie56/WO+ w6PlDWJ87oZPgUJuYPxqCIpoKJttfUBR o1nOzZaUlxQeOE0Daep/fnlo8OtAfauK w5J+l5rZqcaM6C9MA7cB7ZswPVd1p609 rPqgGoxSvuNuX/iFPBqPQhRw5JyiVuwF Q4PSB3Etq85BXUEhlpfcQAt6z3scHlLa ARoZoea3/u9z/xuB+296IgZHOQkp3WJL zJivnrQFisO7vQ7o01t0x6WxtskBwbf3 GXwHg/2DbHY/7QJ4hVWO0/L+tdeJciTM 92+RC/U7GkUPDb0rvfOfntB2MKz0rZhU g5m+qJOJYpG8HGN69f4dyQ== +. 86400 IN RRSIG SOA 8 0 86400 20190708050000 20190625040000 25266 . kYJsFlFDsY3FO94BM5DRROo34/8EZODz iOXejYh397rIJqr5bjx315WJabGoAf3p gtQ5U6QXDJnrKnuZYK/4b+mYyef722if vfNdKclJxKp5vwdDGKLEu7Z2Ey09K4WD MPeyemaIIlbDw3F7lYzz0ZiZubagtrZu OeD2CUOJO4qauzUpGtXf4cx0r+aQJkPq 4eXQyBQ4gg6Mdh4iBNgjGhYB9SLFNMtb eRMNpJG2ifhjP+pNWd27+TGHOhHTu082 osz2lNYKibuMoEfQeHNlINGIU+8oTa/K 3O7IlOAp1APvDmbKnLgy9FFf+6yCMo5y r+A0RVMZheQA3iEXBGug+g== +. 86400 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2019062500 1800 900 604800 86400 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +c.root-servers.net. IN AAAA +SECTION ANSWER +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +l.root-servers.net. IN NS +SECTION AUTHORITY +root-servers.net. 3600000 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2019061700 14400 7200 1209600 3600000 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +b.root-servers.net. IN A +SECTION ANSWER +b.root-servers.net. 3600000 IN A 199.9.14.201 +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +m.root-servers.net. IN NS +SECTION AUTHORITY +root-servers.net. 3600000 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2019061700 14400 7200 1209600 3600000 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD NOERROR +SECTION QUESTION +csas.cz. IN DS +SECTION AUTHORITY +cz. 172800 IN NS a.ns.nic.cz. +cz. 172800 IN NS b.ns.nic.cz. +cz. 172800 IN NS c.ns.nic.cz. +cz. 172800 IN NS d.ns.nic.cz. +cz. 86400 IN DS 20237 13 2 cff0f3ecdbc529c1f0031ba1840bfb835853b9209ed1e508fff48451d7b778e2 +cz. 86400 IN RRSIG DS 8 1 86400 20190708050000 20190625040000 25266 . r7Lxqu4C80oW46tP7BNfJWrXs2K5YEqn IkEKfzXSFqur0cSfnj630EPQfokTYO2Q RCOT/JaNpbUi69MS+d81xFZb5efzzoYE tL3tCE8axm39kzuHKhk2EOSIN8sQPCTC isLppgCbLRPbzKima8Jk5kGs7pV+FK9K vCExdukQ4aB7MYvIZaKzHKP8NAOgKdVk x/BrrGl8IV0T+YvUDo9e8gpdcbhLFXoN w+qZ9xVNIvhSsjzzL1fxrkjJIEdTPzrS HXdUjK8v56KppQJ0pr+XSq2CicRbcn5b ur5HQz4yKfIr2q7aH9CMGuwbMLNDWnjO G+iHdA/ekKLYQ5afWOxaqw== +SECTION ADDITIONAL +a.ns.nic.cz. 172800 IN A 194.0.12.1 +a.ns.nic.cz. 172800 IN AAAA 2001:678:f::1 +b.ns.nic.cz. 172800 IN A 194.0.13.1 +b.ns.nic.cz. 172800 IN AAAA 2001:678:10::1 +c.ns.nic.cz. 172800 IN A 194.0.14.1 +c.ns.nic.cz. 172800 IN AAAA 2001:678:11::1 +d.ns.nic.cz. 172800 IN A 193.29.206.1 +d.ns.nic.cz. 172800 IN AAAA 2001:678:1::1 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +a.root-servers.net. IN NS +SECTION AUTHORITY +root-servers.net. 3600000 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2019061700 14400 7200 1209600 3600000 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +g.root-servers.net. IN AAAA +SECTION ANSWER +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +cz. IN DS +SECTION ANSWER +cz. 86400 IN DS 20237 13 2 cff0f3ecdbc529c1f0031ba1840bfb835853b9209ed1e508fff48451d7b778e2 +cz. 86400 IN RRSIG DS 8 1 86400 20190708050000 20190625040000 25266 . r7Lxqu4C80oW46tP7BNfJWrXs2K5YEqn IkEKfzXSFqur0cSfnj630EPQfokTYO2Q RCOT/JaNpbUi69MS+d81xFZb5efzzoYE tL3tCE8axm39kzuHKhk2EOSIN8sQPCTC isLppgCbLRPbzKima8Jk5kGs7pV+FK9K vCExdukQ4aB7MYvIZaKzHKP8NAOgKdVk x/BrrGl8IV0T+YvUDo9e8gpdcbhLFXoN w+qZ9xVNIvhSsjzzL1fxrkjJIEdTPzrS HXdUjK8v56KppQJ0pr+XSq2CicRbcn5b ur5HQz4yKfIr2q7aH9CMGuwbMLNDWnjO G+iHdA/ekKLYQ5afWOxaqw== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +j.root-servers.net. IN AAAA +SECTION ANSWER +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +d.root-servers.net. IN AAAA +SECTION ANSWER +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +l.root-servers.net. IN AAAA +SECTION ANSWER +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +a.root-servers.net. IN AAAA +SECTION ANSWER +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +e.root-servers.net. IN AAAA +SECTION ANSWER +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +j.root-servers.net. IN A +SECTION ANSWER +j.root-servers.net. 3600000 IN A 192.58.128.30 +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +i.root-servers.net. IN NS +SECTION AUTHORITY +root-servers.net. 3600000 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2019061700 14400 7200 1209600 3600000 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +k.root-servers.net. IN AAAA +SECTION ANSWER +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +net. IN DS +SECTION ANSWER +net. 86400 IN DS 35886 8 2 7862b27f5f516ebe19680444d4ce5e762981931842c465f00236401d8bd973ee +net. 86400 IN RRSIG DS 8 1 86400 20190708050000 20190625040000 25266 . EK4wI33V5RQyA5SAxBfX1oTiDeGfOzXr u0OA6KaXALQOMELkugzUD3NipvVpzO7B 0ZYSsnNn+Kk1h0qJBE/ZWpHHZyvZCg1o zo+kq1Z7gGJvlV4Y9XfuwIGPZKL0tlkm LBVSBd36yQy/x4gFyBKvRgIDd1IyKrjT xJYENyNwvtj3MkrT+Njsg1NWXP5ORRx1 r0zVlq2snbJsp8ze+sLYrSqVXbihg4mq JoMe7NB4M9EYEMfBOUcWo8Wrj73jiYRx 0uJ3HfvOHBqBgVFyhMcr4FeCiN9F9V6C xTQBQnL3lQF1TnOhN//Z7h7TvLulxHRu DeKEsZDcoC4el8u1Fx7t/w== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +f.root-servers.net. IN A +SECTION ANSWER +f.root-servers.net. 3600000 IN A 192.5.5.241 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +f.root-servers.net. IN A +SECTION ANSWER +f.root-servers.net. 3600000 IN A 192.5.5.241 +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +f.root-servers.net. IN AAAA +SECTION ANSWER +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +h.root-servers.net. IN AAAA +SECTION ANSWER +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +e.root-servers.net. IN NS +SECTION AUTHORITY +root-servers.net. 3600000 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2019061700 14400 7200 1209600 3600000 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +b.root-servers.net. IN AAAA +SECTION ANSWER +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD NOERROR +SECTION QUESTION +gtld-servers.net. IN DS +SECTION AUTHORITY +net. 172800 IN NS a.gtld-servers.net. +net. 172800 IN NS b.gtld-servers.net. +net. 172800 IN NS c.gtld-servers.net. +net. 172800 IN NS d.gtld-servers.net. +net. 172800 IN NS e.gtld-servers.net. +net. 172800 IN NS f.gtld-servers.net. +net. 172800 IN NS g.gtld-servers.net. +net. 172800 IN NS h.gtld-servers.net. +net. 172800 IN NS i.gtld-servers.net. +net. 172800 IN NS j.gtld-servers.net. +net. 172800 IN NS k.gtld-servers.net. +net. 172800 IN NS l.gtld-servers.net. +net. 172800 IN NS m.gtld-servers.net. +net. 86400 IN DS 35886 8 2 7862b27f5f516ebe19680444d4ce5e762981931842c465f00236401d8bd973ee +net. 86400 IN RRSIG DS 8 1 86400 20190708050000 20190625040000 25266 . EK4wI33V5RQyA5SAxBfX1oTiDeGfOzXr u0OA6KaXALQOMELkugzUD3NipvVpzO7B 0ZYSsnNn+Kk1h0qJBE/ZWpHHZyvZCg1o zo+kq1Z7gGJvlV4Y9XfuwIGPZKL0tlkm LBVSBd36yQy/x4gFyBKvRgIDd1IyKrjT xJYENyNwvtj3MkrT+Njsg1NWXP5ORRx1 r0zVlq2snbJsp8ze+sLYrSqVXbihg4mq JoMe7NB4M9EYEMfBOUcWo8Wrj73jiYRx 0uJ3HfvOHBqBgVFyhMcr4FeCiN9F9V6C xTQBQnL3lQF1TnOhN//Z7h7TvLulxHRu DeKEsZDcoC4el8u1Fx7t/w== +SECTION ADDITIONAL +a.gtld-servers.net. 172800 IN A 192.5.6.30 +a.gtld-servers.net. 172800 IN AAAA 2001:503:a83e::2:30 +b.gtld-servers.net. 172800 IN A 192.33.14.30 +b.gtld-servers.net. 172800 IN AAAA 2001:503:231d::2:30 +c.gtld-servers.net. 172800 IN A 192.26.92.30 +c.gtld-servers.net. 172800 IN AAAA 2001:503:83eb::30 +d.gtld-servers.net. 172800 IN A 192.31.80.30 +d.gtld-servers.net. 172800 IN AAAA 2001:500:856e::30 +e.gtld-servers.net. 172800 IN A 192.12.94.30 +e.gtld-servers.net. 172800 IN AAAA 2001:502:1ca1::30 +f.gtld-servers.net. 172800 IN A 192.35.51.30 +f.gtld-servers.net. 172800 IN AAAA 2001:503:d414::30 +g.gtld-servers.net. 172800 IN A 192.42.93.30 +g.gtld-servers.net. 172800 IN AAAA 2001:503:eea3::30 +h.gtld-servers.net. 172800 IN A 192.54.112.30 +h.gtld-servers.net. 172800 IN AAAA 2001:502:8cc::30 +i.gtld-servers.net. 172800 IN A 192.43.172.30 +i.gtld-servers.net. 172800 IN AAAA 2001:503:39c1::30 +j.gtld-servers.net. 172800 IN A 192.48.79.30 +j.gtld-servers.net. 172800 IN AAAA 2001:502:7094::30 +k.gtld-servers.net. 172800 IN A 192.52.178.30 +k.gtld-servers.net. 172800 IN AAAA 2001:503:d2d::30 +l.gtld-servers.net. 172800 IN A 192.41.162.30 +l.gtld-servers.net. 172800 IN AAAA 2001:500:d937::30 +m.gtld-servers.net. 172800 IN A 192.55.83.30 +m.gtld-servers.net. 172800 IN AAAA 2001:501:b1f9::30 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD NOERROR +SECTION QUESTION +nstld.com. IN DS +SECTION AUTHORITY +com. 172800 IN NS a.gtld-servers.net. +com. 172800 IN NS b.gtld-servers.net. +com. 172800 IN NS c.gtld-servers.net. +com. 172800 IN NS d.gtld-servers.net. +com. 172800 IN NS e.gtld-servers.net. +com. 172800 IN NS f.gtld-servers.net. +com. 172800 IN NS g.gtld-servers.net. +com. 172800 IN NS h.gtld-servers.net. +com. 172800 IN NS i.gtld-servers.net. +com. 172800 IN NS j.gtld-servers.net. +com. 172800 IN NS k.gtld-servers.net. +com. 172800 IN NS l.gtld-servers.net. +com. 172800 IN NS m.gtld-servers.net. +com. 86400 IN DS 30909 8 2 e2d3c916f6deeac73294e8268fb5885044a833fc5459588f4a9184cfc41a5766 +com. 86400 IN RRSIG DS 8 1 86400 20190708050000 20190625040000 25266 . h8CYVMqouUO2IAPlG4Iqf06ykpl07wny KuM2dRGhrfx5hQbF0CpzGRwT2B6i2drI td9i7BSA4GVKLlTYr9n3Xd+BcAHKwywv 44A2WmTAo3xWMv4THwowwu29B4bAKe0V WQKDfmZ92m1yn8T3MCNZWtuGGaLcY6+g fKgyuHu5fEakVn2GFMdAMayBBFTF0bp4 hVFuNSJBe/1EnFZMcxU9aNuCyC8xup25 7K3x1rcM0hthHr8o0Vevpima1YXsWDGb RDIkDyStPDIQ1c0C9LHMaaGR+MA+fxoL 2x4w2lwOptCK//zpfyPvj11oIyouwgdh Fe3PCf9hS03Y1FsiY+mtWw== +SECTION ADDITIONAL +a.gtld-servers.net. 172800 IN A 192.5.6.30 +a.gtld-servers.net. 172800 IN AAAA 2001:503:a83e::2:30 +b.gtld-servers.net. 172800 IN A 192.33.14.30 +b.gtld-servers.net. 172800 IN AAAA 2001:503:231d::2:30 +c.gtld-servers.net. 172800 IN A 192.26.92.30 +c.gtld-servers.net. 172800 IN AAAA 2001:503:83eb::30 +d.gtld-servers.net. 172800 IN A 192.31.80.30 +d.gtld-servers.net. 172800 IN AAAA 2001:500:856e::30 +e.gtld-servers.net. 172800 IN A 192.12.94.30 +e.gtld-servers.net. 172800 IN AAAA 2001:502:1ca1::30 +f.gtld-servers.net. 172800 IN A 192.35.51.30 +f.gtld-servers.net. 172800 IN AAAA 2001:503:d414::30 +g.gtld-servers.net. 172800 IN A 192.42.93.30 +g.gtld-servers.net. 172800 IN AAAA 2001:503:eea3::30 +h.gtld-servers.net. 172800 IN A 192.54.112.30 +h.gtld-servers.net. 172800 IN AAAA 2001:502:8cc::30 +i.gtld-servers.net. 172800 IN A 192.43.172.30 +i.gtld-servers.net. 172800 IN AAAA 2001:503:39c1::30 +j.gtld-servers.net. 172800 IN A 192.48.79.30 +j.gtld-servers.net. 172800 IN AAAA 2001:502:7094::30 +k.gtld-servers.net. 172800 IN A 192.52.178.30 +k.gtld-servers.net. 172800 IN AAAA 2001:503:d2d::30 +l.gtld-servers.net. 172800 IN A 192.41.162.30 +l.gtld-servers.net. 172800 IN AAAA 2001:500:d937::30 +m.gtld-servers.net. 172800 IN A 192.55.83.30 +m.gtld-servers.net. 172800 IN AAAA 2001:501:b1f9::30 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +m.root-servers.net. IN A +SECTION ANSWER +m.root-servers.net. 3600000 IN A 202.12.27.33 +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +h.root-servers.net. IN A +SECTION ANSWER +h.root-servers.net. 3600000 IN A 198.97.190.53 +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +com. IN DS +SECTION ANSWER +com. 86400 IN DS 30909 8 2 e2d3c916f6deeac73294e8268fb5885044a833fc5459588f4a9184cfc41a5766 +com. 86400 IN RRSIG DS 8 1 86400 20190708050000 20190625040000 25266 . h8CYVMqouUO2IAPlG4Iqf06ykpl07wny KuM2dRGhrfx5hQbF0CpzGRwT2B6i2drI td9i7BSA4GVKLlTYr9n3Xd+BcAHKwywv 44A2WmTAo3xWMv4THwowwu29B4bAKe0V WQKDfmZ92m1yn8T3MCNZWtuGGaLcY6+g fKgyuHu5fEakVn2GFMdAMayBBFTF0bp4 hVFuNSJBe/1EnFZMcxU9aNuCyC8xup25 7K3x1rcM0hthHr8o0Vevpima1YXsWDGb RDIkDyStPDIQ1c0C9LHMaaGR+MA+fxoL 2x4w2lwOptCK//zpfyPvj11oIyouwgdh Fe3PCf9hS03Y1FsiY+mtWw== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD NOERROR +SECTION QUESTION +nic.cz. IN DS +SECTION AUTHORITY +cz. 172800 IN NS a.ns.nic.cz. +cz. 172800 IN NS b.ns.nic.cz. +cz. 172800 IN NS c.ns.nic.cz. +cz. 172800 IN NS d.ns.nic.cz. +cz. 86400 IN DS 20237 13 2 cff0f3ecdbc529c1f0031ba1840bfb835853b9209ed1e508fff48451d7b778e2 +cz. 86400 IN RRSIG DS 8 1 86400 20190708050000 20190625040000 25266 . r7Lxqu4C80oW46tP7BNfJWrXs2K5YEqn IkEKfzXSFqur0cSfnj630EPQfokTYO2Q RCOT/JaNpbUi69MS+d81xFZb5efzzoYE tL3tCE8axm39kzuHKhk2EOSIN8sQPCTC isLppgCbLRPbzKima8Jk5kGs7pV+FK9K vCExdukQ4aB7MYvIZaKzHKP8NAOgKdVk x/BrrGl8IV0T+YvUDo9e8gpdcbhLFXoN w+qZ9xVNIvhSsjzzL1fxrkjJIEdTPzrS HXdUjK8v56KppQJ0pr+XSq2CicRbcn5b ur5HQz4yKfIr2q7aH9CMGuwbMLNDWnjO G+iHdA/ekKLYQ5afWOxaqw== +SECTION ADDITIONAL +a.ns.nic.cz. 172800 IN A 194.0.12.1 +a.ns.nic.cz. 172800 IN AAAA 2001:678:f::1 +b.ns.nic.cz. 172800 IN A 194.0.13.1 +b.ns.nic.cz. 172800 IN AAAA 2001:678:10::1 +c.ns.nic.cz. 172800 IN A 194.0.14.1 +c.ns.nic.cz. 172800 IN AAAA 2001:678:11::1 +d.ns.nic.cz. 172800 IN A 193.29.206.1 +d.ns.nic.cz. 172800 IN AAAA 2001:678:1::1 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +l.root-servers.net. IN A +SECTION ANSWER +l.root-servers.net. 3600000 IN A 199.7.83.42 +SECTION AUTHORITY +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +d.root-servers.net. IN NS +SECTION AUTHORITY +root-servers.net. 3600000 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2019061700 14400 7200 1209600 3600000 +ENTRY_END + + +ENTRY_BEGIN +MATCH subdomain +ADJUST copy_id copy_query +REPLY QR AA RD NOERROR +SECTION QUESTION +root-servers.net. IN NS +SECTION ANSWER +root-servers.net. 3600000 IN NS a.root-servers.net. +root-servers.net. 3600000 IN NS b.root-servers.net. +root-servers.net. 3600000 IN NS c.root-servers.net. +root-servers.net. 3600000 IN NS d.root-servers.net. +root-servers.net. 3600000 IN NS e.root-servers.net. +root-servers.net. 3600000 IN NS f.root-servers.net. +root-servers.net. 3600000 IN NS g.root-servers.net. +root-servers.net. 3600000 IN NS h.root-servers.net. +root-servers.net. 3600000 IN NS i.root-servers.net. +root-servers.net. 3600000 IN NS j.root-servers.net. +root-servers.net. 3600000 IN NS k.root-servers.net. +root-servers.net. 3600000 IN NS l.root-servers.net. +root-servers.net. 3600000 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 3600000 IN A 198.41.0.4 +a.root-servers.net. 3600000 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 3600000 IN A 199.9.14.201 +b.root-servers.net. 3600000 IN AAAA 2001:500:200::b +c.root-servers.net. 3600000 IN A 192.33.4.12 +c.root-servers.net. 3600000 IN AAAA 2001:500:2::c +d.root-servers.net. 3600000 IN A 199.7.91.13 +d.root-servers.net. 3600000 IN AAAA 2001:500:2d::d +e.root-servers.net. 3600000 IN A 192.203.230.10 +e.root-servers.net. 3600000 IN AAAA 2001:500:a8::e +f.root-servers.net. 3600000 IN A 192.5.5.241 +f.root-servers.net. 3600000 IN AAAA 2001:500:2f::f +g.root-servers.net. 3600000 IN A 192.112.36.4 +g.root-servers.net. 3600000 IN AAAA 2001:500:12::d0d +h.root-servers.net. 3600000 IN A 198.97.190.53 +h.root-servers.net. 3600000 IN AAAA 2001:500:1::53 +i.root-servers.net. 3600000 IN A 192.36.148.17 +i.root-servers.net. 3600000 IN AAAA 2001:7fe::53 +j.root-servers.net. 3600000 IN A 192.58.128.30 +j.root-servers.net. 3600000 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 3600000 IN A 193.0.14.129 +k.root-servers.net. 3600000 IN AAAA 2001:7fd::1 +l.root-servers.net. 3600000 IN A 199.7.83.42 +l.root-servers.net. 3600000 IN AAAA 2001:500:9f::42 +m.root-servers.net. 3600000 IN A 202.12.27.33 +m.root-servers.net. 3600000 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH subdomain +ADJUST copy_id copy_query +REPLY QR RD NOERROR +SECTION QUESTION +cz. IN NS +SECTION AUTHORITY +cz. 172800 IN NS a.ns.nic.cz. +cz. 172800 IN NS b.ns.nic.cz. +cz. 172800 IN NS c.ns.nic.cz. +cz. 172800 IN NS d.ns.nic.cz. +cz. 86400 IN DS 20237 13 2 cff0f3ecdbc529c1f0031ba1840bfb835853b9209ed1e508fff48451d7b778e2 +cz. 86400 IN RRSIG DS 8 1 86400 20190708050000 20190625040000 25266 . r7Lxqu4C80oW46tP7BNfJWrXs2K5YEqn IkEKfzXSFqur0cSfnj630EPQfokTYO2Q RCOT/JaNpbUi69MS+d81xFZb5efzzoYE tL3tCE8axm39kzuHKhk2EOSIN8sQPCTC isLppgCbLRPbzKima8Jk5kGs7pV+FK9K vCExdukQ4aB7MYvIZaKzHKP8NAOgKdVk x/BrrGl8IV0T+YvUDo9e8gpdcbhLFXoN w+qZ9xVNIvhSsjzzL1fxrkjJIEdTPzrS HXdUjK8v56KppQJ0pr+XSq2CicRbcn5b ur5HQz4yKfIr2q7aH9CMGuwbMLNDWnjO G+iHdA/ekKLYQ5afWOxaqw== +SECTION ADDITIONAL +a.ns.nic.cz. 172800 IN A 194.0.12.1 +a.ns.nic.cz. 172800 IN AAAA 2001:678:f::1 +b.ns.nic.cz. 172800 IN A 194.0.13.1 +b.ns.nic.cz. 172800 IN AAAA 2001:678:10::1 +c.ns.nic.cz. 172800 IN A 194.0.14.1 +c.ns.nic.cz. 172800 IN AAAA 2001:678:11::1 +d.ns.nic.cz. 172800 IN A 193.29.206.1 +d.ns.nic.cz. 172800 IN AAAA 2001:678:1::1 +ENTRY_END + + +ENTRY_BEGIN +MATCH subdomain +ADJUST copy_id copy_query +REPLY QR RD NOERROR +SECTION QUESTION +net. IN NS +SECTION AUTHORITY +net. 172800 IN NS a.gtld-servers.net. +net. 172800 IN NS b.gtld-servers.net. +net. 172800 IN NS c.gtld-servers.net. +net. 172800 IN NS d.gtld-servers.net. +net. 172800 IN NS e.gtld-servers.net. +net. 172800 IN NS f.gtld-servers.net. +net. 172800 IN NS g.gtld-servers.net. +net. 172800 IN NS h.gtld-servers.net. +net. 172800 IN NS i.gtld-servers.net. +net. 172800 IN NS j.gtld-servers.net. +net. 172800 IN NS k.gtld-servers.net. +net. 172800 IN NS l.gtld-servers.net. +net. 172800 IN NS m.gtld-servers.net. +net. 86400 IN DS 35886 8 2 7862b27f5f516ebe19680444d4ce5e762981931842c465f00236401d8bd973ee +net. 86400 IN RRSIG DS 8 1 86400 20190708050000 20190625040000 25266 . EK4wI33V5RQyA5SAxBfX1oTiDeGfOzXr u0OA6KaXALQOMELkugzUD3NipvVpzO7B 0ZYSsnNn+Kk1h0qJBE/ZWpHHZyvZCg1o zo+kq1Z7gGJvlV4Y9XfuwIGPZKL0tlkm LBVSBd36yQy/x4gFyBKvRgIDd1IyKrjT xJYENyNwvtj3MkrT+Njsg1NWXP5ORRx1 r0zVlq2snbJsp8ze+sLYrSqVXbihg4mq JoMe7NB4M9EYEMfBOUcWo8Wrj73jiYRx 0uJ3HfvOHBqBgVFyhMcr4FeCiN9F9V6C xTQBQnL3lQF1TnOhN//Z7h7TvLulxHRu DeKEsZDcoC4el8u1Fx7t/w== +SECTION ADDITIONAL +a.gtld-servers.net. 172800 IN A 192.5.6.30 +a.gtld-servers.net. 172800 IN AAAA 2001:503:a83e::2:30 +b.gtld-servers.net. 172800 IN A 192.33.14.30 +b.gtld-servers.net. 172800 IN AAAA 2001:503:231d::2:30 +c.gtld-servers.net. 172800 IN A 192.26.92.30 +c.gtld-servers.net. 172800 IN AAAA 2001:503:83eb::30 +d.gtld-servers.net. 172800 IN A 192.31.80.30 +d.gtld-servers.net. 172800 IN AAAA 2001:500:856e::30 +e.gtld-servers.net. 172800 IN A 192.12.94.30 +e.gtld-servers.net. 172800 IN AAAA 2001:502:1ca1::30 +f.gtld-servers.net. 172800 IN A 192.35.51.30 +f.gtld-servers.net. 172800 IN AAAA 2001:503:d414::30 +g.gtld-servers.net. 172800 IN A 192.42.93.30 +g.gtld-servers.net. 172800 IN AAAA 2001:503:eea3::30 +h.gtld-servers.net. 172800 IN A 192.54.112.30 +h.gtld-servers.net. 172800 IN AAAA 2001:502:8cc::30 +i.gtld-servers.net. 172800 IN A 192.43.172.30 +i.gtld-servers.net. 172800 IN AAAA 2001:503:39c1::30 +j.gtld-servers.net. 172800 IN A 192.48.79.30 +j.gtld-servers.net. 172800 IN AAAA 2001:502:7094::30 +k.gtld-servers.net. 172800 IN A 192.52.178.30 +k.gtld-servers.net. 172800 IN AAAA 2001:503:d2d::30 +l.gtld-servers.net. 172800 IN A 192.41.162.30 +l.gtld-servers.net. 172800 IN AAAA 2001:500:d937::30 +m.gtld-servers.net. 172800 IN A 192.55.83.30 +m.gtld-servers.net. 172800 IN AAAA 2001:501:b1f9::30 +ENTRY_END + + +ENTRY_BEGIN +MATCH subdomain +ADJUST copy_id copy_query +REPLY QR RD NOERROR +SECTION QUESTION +com. IN NS +SECTION AUTHORITY +com. 172800 IN NS a.gtld-servers.net. +com. 172800 IN NS b.gtld-servers.net. +com. 172800 IN NS c.gtld-servers.net. +com. 172800 IN NS d.gtld-servers.net. +com. 172800 IN NS e.gtld-servers.net. +com. 172800 IN NS f.gtld-servers.net. +com. 172800 IN NS g.gtld-servers.net. +com. 172800 IN NS h.gtld-servers.net. +com. 172800 IN NS i.gtld-servers.net. +com. 172800 IN NS j.gtld-servers.net. +com. 172800 IN NS k.gtld-servers.net. +com. 172800 IN NS l.gtld-servers.net. +com. 172800 IN NS m.gtld-servers.net. +com. 86400 IN DS 30909 8 2 e2d3c916f6deeac73294e8268fb5885044a833fc5459588f4a9184cfc41a5766 +com. 86400 IN RRSIG DS 8 1 86400 20190708050000 20190625040000 25266 . h8CYVMqouUO2IAPlG4Iqf06ykpl07wny KuM2dRGhrfx5hQbF0CpzGRwT2B6i2drI td9i7BSA4GVKLlTYr9n3Xd+BcAHKwywv 44A2WmTAo3xWMv4THwowwu29B4bAKe0V WQKDfmZ92m1yn8T3MCNZWtuGGaLcY6+g fKgyuHu5fEakVn2GFMdAMayBBFTF0bp4 hVFuNSJBe/1EnFZMcxU9aNuCyC8xup25 7K3x1rcM0hthHr8o0Vevpima1YXsWDGb RDIkDyStPDIQ1c0C9LHMaaGR+MA+fxoL 2x4w2lwOptCK//zpfyPvj11oIyouwgdh Fe3PCf9hS03Y1FsiY+mtWw== +SECTION ADDITIONAL +a.gtld-servers.net. 172800 IN A 192.5.6.30 +a.gtld-servers.net. 172800 IN AAAA 2001:503:a83e::2:30 +b.gtld-servers.net. 172800 IN A 192.33.14.30 +b.gtld-servers.net. 172800 IN AAAA 2001:503:231d::2:30 +c.gtld-servers.net. 172800 IN A 192.26.92.30 +c.gtld-servers.net. 172800 IN AAAA 2001:503:83eb::30 +d.gtld-servers.net. 172800 IN A 192.31.80.30 +d.gtld-servers.net. 172800 IN AAAA 2001:500:856e::30 +e.gtld-servers.net. 172800 IN A 192.12.94.30 +e.gtld-servers.net. 172800 IN AAAA 2001:502:1ca1::30 +f.gtld-servers.net. 172800 IN A 192.35.51.30 +f.gtld-servers.net. 172800 IN AAAA 2001:503:d414::30 +g.gtld-servers.net. 172800 IN A 192.42.93.30 +g.gtld-servers.net. 172800 IN AAAA 2001:503:eea3::30 +h.gtld-servers.net. 172800 IN A 192.54.112.30 +h.gtld-servers.net. 172800 IN AAAA 2001:502:8cc::30 +i.gtld-servers.net. 172800 IN A 192.43.172.30 +i.gtld-servers.net. 172800 IN AAAA 2001:503:39c1::30 +j.gtld-servers.net. 172800 IN A 192.48.79.30 +j.gtld-servers.net. 172800 IN AAAA 2001:502:7094::30 +k.gtld-servers.net. 172800 IN A 192.52.178.30 +k.gtld-servers.net. 172800 IN AAAA 2001:503:d2d::30 +l.gtld-servers.net. 172800 IN A 192.41.162.30 +l.gtld-servers.net. 172800 IN AAAA 2001:500:d937::30 +m.gtld-servers.net. 172800 IN A 192.55.83.30 +m.gtld-servers.net. 172800 IN AAAA 2001:501:b1f9::30 +ENTRY_END + + + + +RANGE_END + + + +; Group's zones: +; cz. +; nic.cz. +; Server names: +; a.ns.nic.cz. +; b.ns.nic.cz. +; c.ns.nic.cz. +; d.ns.nic.cz. +RANGE_BEGIN 0 1000 + ADDRESS 194.0.14.1 + ADDRESS 194.0.13.1 + ADDRESS 193.29.206.1 + ADDRESS 2001:678:1::1 + ADDRESS 2001:678:11::1 + ADDRESS 2001:678:10::1 + ADDRESS 2001:678:f::1 + ADDRESS 194.0.12.1 + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +ns.nic.cz. IN NS +SECTION AUTHORITY +jnp2uc34hha9de64l3rjf6ulp4pra74n.nic.cz. 7200 IN NSEC3 1 0 10 879ec89c91d874a8 jsdlj2k5hipr7eb12ne8bads7lshvo1k +jnp2uc34hha9de64l3rjf6ulp4pra74n.nic.cz. 7200 IN RRSIG NSEC3 13 3 7200 20190707164247 20190624124001 10486 nic.cz. lrFJPPGpHyoC5l4uM8l94ye/HG1kTVw7 dR98um06iG+2XK82Dib+wnzqoNNIqbaA FeaAkjfqgCHA8kDySAP+5g== +nic.cz. 1800 IN RRSIG SOA 13 2 1800 20190708100906 20190624124001 10486 nic.cz. 1mDJpihqjobv42BXF8H1dT0/vLtPTpb/ k7flS9wr8tEPe57o6GrkimcSWZlS/lm5 OyhvjIpvU9Da8n2ezGiGHw== +nic.cz. 1800 IN SOA a.ns.nic.cz. hostmaster.nic.cz. 1561383601 10800 3600 1209600 7200 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +b.ns.nic.cz. IN A +SECTION ANSWER +b.ns.nic.cz. 1800 IN A 194.0.13.1 +b.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708030702 20190624124001 10486 nic.cz. bV+9iqB6KDNUCZrcoo9fXj3X1BHhCpgh MGSXx8q4JWJ9mm9Hz6h63UfXTWPthvJy +J18PantZQVPScwMoXVzuw== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +nic.cz. IN DNSKEY +SECTION ANSWER +nic.cz. 1800 IN DNSKEY 256 3 13 SmMYG4VjCgj4rAxB4sqgvIzcGtESoX1H m7Dsoekap6HJwj8WOEiFciSg537caOPl 4+7Dyp/b5JwkBemxQQRL9Q== +nic.cz. 1800 IN DNSKEY 257 3 13 LM4zvjUgZi2XZKsYooDE0HFYGfWp242f KB+O8sLsuox8S6MJTowY8lBDjZD7JKbm aNot3+1H8zU9TrDzWmmHwQ== +nic.cz. 1800 IN RRSIG DNSKEY 13 2 1800 20190707163411 20190624124001 61281 nic.cz. ggHlmuzLOTZOCYcbZ8TrNoTXOAg7xJ9N B+QLdmZYyny53ODMkRfDv28SSMkwtuc1 rZXfC+/c7oArzsBbbncTRA== +nic.cz. 1800 IN RRSIG DNSKEY 13 2 1800 20190708092836 20190624124001 10486 nic.cz. lFYLLcm5ICS0BSdB0+dA8m7XxdRbB49+ 5N1w8AHOaPNDTWp9GlXA935IUk18C2to 1ghYmP2RZaNOTchSVRgWzA== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +b.ns.nic.cz. IN A +SECTION ANSWER +b.ns.nic.cz. 1800 IN A 194.0.13.1 +b.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708030702 20190624124001 10486 nic.cz. bV+9iqB6KDNUCZrcoo9fXj3X1BHhCpgh MGSXx8q4JWJ9mm9Hz6h63UfXTWPthvJy +J18PantZQVPScwMoXVzuw== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +b.ns.nic.cz. IN AAAA +SECTION ANSWER +b.ns.nic.cz. 1800 IN AAAA 2001:678:10::1 +b.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20190707133636 20190624124001 10486 nic.cz. OuH2sPy1tH6CENbjioxaaYzCB8yxB1sP FyQXAcY4VpNmLnqthfGKTn5dONZuG4UD I9ihrEaPsV3RsDse0GpMqg== +SECTION AUTHORITY +nic.cz. 1800 IN NS a.ns.nic.cz. +nic.cz. 1800 IN NS b.ns.nic.cz. +nic.cz. 1800 IN NS d.ns.nic.cz. +nic.cz. 1800 IN RRSIG NS 13 2 1800 20190707221726 20190624124001 10486 nic.cz. JhCrQB0nVFkti/j3weaalBPxqDG7PyiC KLV7hj61SLdRGcue9/fI9IN7lIanFWhL A1b7/L5DYejIY7WpHVU3Jg== +SECTION ADDITIONAL +a.ns.nic.cz. 1800 IN A 194.0.12.1 +a.ns.nic.cz. 1800 IN AAAA 2001:678:f::1 +a.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708050633 20190624124001 10486 nic.cz. 18u1P3Wvg0xoz3fTRtqVNlTHiZPOuGW7 C8nsMIBTCTT9mPYN0z+CcBDfRwVLNnfJ eNTfHXA9zERxbAT3WCHEXg== +a.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20190707232622 20190624124001 10486 nic.cz. qY1Rm171qERHJwykmMYDKXlIGUnHdMhX gYlvOZPGONNuapbsqzTQos9Vd7v4IQfp k6j8sVTjSId1/q/75D4vNQ== +b.ns.nic.cz. 1800 IN A 194.0.13.1 +b.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708030702 20190624124001 10486 nic.cz. bV+9iqB6KDNUCZrcoo9fXj3X1BHhCpgh MGSXx8q4JWJ9mm9Hz6h63UfXTWPthvJy +J18PantZQVPScwMoXVzuw== +d.ns.nic.cz. 1800 IN A 193.29.206.1 +d.ns.nic.cz. 1800 IN AAAA 2001:678:1::1 +d.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708114823 20190624124001 10486 nic.cz. p2nhRTWBo56GXRdL19wT+y/XyNb6wjrz Oy3AndSR2/L9BDZrX/mkGYh20x5KpdUV +r+DPy9XFXEmvGrwAD5meA== +d.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20190708121635 20190624124001 10486 nic.cz. QeqTWoaOuL+L+QdiGOIne/WCi+D0V6EH 0h96aDuMs2eySLXSWPC54ICz28gwudmh wX4oEdQf1nYVneO7iEDFew== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +c.ns.nic.cz. IN A +SECTION ANSWER +c.ns.nic.cz. 1800 IN A 194.0.14.1 +c.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708123232 20190624124001 10486 nic.cz. ln7Q9H73Ba6dEbIrhA6QrK3OMMEIu4QA h4fJ3xeUIW4US+XU21wylj09Zaf6ALE+ V3E9jTWdPfo1UTGnuW1VUw== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +d.ns.nic.cz. IN AAAA +SECTION ANSWER +d.ns.nic.cz. 1800 IN AAAA 2001:678:1::1 +d.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20190708121635 20190624124001 10486 nic.cz. QeqTWoaOuL+L+QdiGOIne/WCi+D0V6EH 0h96aDuMs2eySLXSWPC54ICz28gwudmh wX4oEdQf1nYVneO7iEDFew== +SECTION AUTHORITY +nic.cz. 1800 IN NS a.ns.nic.cz. +nic.cz. 1800 IN NS b.ns.nic.cz. +nic.cz. 1800 IN NS d.ns.nic.cz. +nic.cz. 1800 IN RRSIG NS 13 2 1800 20190707221726 20190624124001 10486 nic.cz. JhCrQB0nVFkti/j3weaalBPxqDG7PyiC KLV7hj61SLdRGcue9/fI9IN7lIanFWhL A1b7/L5DYejIY7WpHVU3Jg== +SECTION ADDITIONAL +a.ns.nic.cz. 1800 IN A 194.0.12.1 +a.ns.nic.cz. 1800 IN AAAA 2001:678:f::1 +a.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708050633 20190624124001 10486 nic.cz. 18u1P3Wvg0xoz3fTRtqVNlTHiZPOuGW7 C8nsMIBTCTT9mPYN0z+CcBDfRwVLNnfJ eNTfHXA9zERxbAT3WCHEXg== +a.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20190707232622 20190624124001 10486 nic.cz. qY1Rm171qERHJwykmMYDKXlIGUnHdMhX gYlvOZPGONNuapbsqzTQos9Vd7v4IQfp k6j8sVTjSId1/q/75D4vNQ== +b.ns.nic.cz. 1800 IN A 194.0.13.1 +b.ns.nic.cz. 1800 IN AAAA 2001:678:10::1 +b.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708030702 20190624124001 10486 nic.cz. bV+9iqB6KDNUCZrcoo9fXj3X1BHhCpgh MGSXx8q4JWJ9mm9Hz6h63UfXTWPthvJy +J18PantZQVPScwMoXVzuw== +b.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20190707133636 20190624124001 10486 nic.cz. OuH2sPy1tH6CENbjioxaaYzCB8yxB1sP FyQXAcY4VpNmLnqthfGKTn5dONZuG4UD I9ihrEaPsV3RsDse0GpMqg== +d.ns.nic.cz. 1800 IN A 193.29.206.1 +d.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708114823 20190624124001 10486 nic.cz. p2nhRTWBo56GXRdL19wT+y/XyNb6wjrz Oy3AndSR2/L9BDZrX/mkGYh20x5KpdUV +r+DPy9XFXEmvGrwAD5meA== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +b.ns.nic.cz. IN AAAA +SECTION ANSWER +b.ns.nic.cz. 1800 IN AAAA 2001:678:10::1 +b.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20190707133636 20190624124001 10486 nic.cz. OuH2sPy1tH6CENbjioxaaYzCB8yxB1sP FyQXAcY4VpNmLnqthfGKTn5dONZuG4UD I9ihrEaPsV3RsDse0GpMqg== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +a.ns.nic.cz. IN NS +SECTION AUTHORITY +51npp2qit2otcucer39ql8d4q4h9mr5d.nic.cz. 7200 IN NSEC3 1 0 10 879ec89c91d874a8 52jm0s7pifluesur7b9p840f8fkcpcsi A AAAA RRSIG +51npp2qit2otcucer39ql8d4q4h9mr5d.nic.cz. 7200 IN RRSIG NSEC3 13 3 7200 20190708102945 20190624124001 10486 nic.cz. ScbUA0IlzL0o1h34t007ViOD53YFHe+V zn2ge8gqiNeT29FW/sCwiyVsrUpMZ7nW O9gmXfdNjjtmyTA7iWbVTA== +nic.cz. 1800 IN RRSIG SOA 13 2 1800 20190708100906 20190624124001 10486 nic.cz. 1mDJpihqjobv42BXF8H1dT0/vLtPTpb/ k7flS9wr8tEPe57o6GrkimcSWZlS/lm5 OyhvjIpvU9Da8n2ezGiGHw== +nic.cz. 1800 IN SOA a.ns.nic.cz. hostmaster.nic.cz. 1561383601 10800 3600 1209600 7200 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +c.ns.nic.cz. IN A +SECTION ANSWER +c.ns.nic.cz. 1800 IN A 194.0.14.1 +c.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708123232 20190624124001 10486 nic.cz. ln7Q9H73Ba6dEbIrhA6QrK3OMMEIu4QA h4fJ3xeUIW4US+XU21wylj09Zaf6ALE+ V3E9jTWdPfo1UTGnuW1VUw== +SECTION AUTHORITY +nic.cz. 1800 IN NS a.ns.nic.cz. +nic.cz. 1800 IN NS b.ns.nic.cz. +nic.cz. 1800 IN NS d.ns.nic.cz. +nic.cz. 1800 IN RRSIG NS 13 2 1800 20190707221726 20190624124001 10486 nic.cz. JhCrQB0nVFkti/j3weaalBPxqDG7PyiC KLV7hj61SLdRGcue9/fI9IN7lIanFWhL A1b7/L5DYejIY7WpHVU3Jg== +SECTION ADDITIONAL +a.ns.nic.cz. 1800 IN A 194.0.12.1 +a.ns.nic.cz. 1800 IN AAAA 2001:678:f::1 +a.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708050633 20190624124001 10486 nic.cz. 18u1P3Wvg0xoz3fTRtqVNlTHiZPOuGW7 C8nsMIBTCTT9mPYN0z+CcBDfRwVLNnfJ eNTfHXA9zERxbAT3WCHEXg== +a.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20190707232622 20190624124001 10486 nic.cz. qY1Rm171qERHJwykmMYDKXlIGUnHdMhX gYlvOZPGONNuapbsqzTQos9Vd7v4IQfp k6j8sVTjSId1/q/75D4vNQ== +b.ns.nic.cz. 1800 IN A 194.0.13.1 +b.ns.nic.cz. 1800 IN AAAA 2001:678:10::1 +b.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708030702 20190624124001 10486 nic.cz. bV+9iqB6KDNUCZrcoo9fXj3X1BHhCpgh MGSXx8q4JWJ9mm9Hz6h63UfXTWPthvJy +J18PantZQVPScwMoXVzuw== +b.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20190707133636 20190624124001 10486 nic.cz. OuH2sPy1tH6CENbjioxaaYzCB8yxB1sP FyQXAcY4VpNmLnqthfGKTn5dONZuG4UD I9ihrEaPsV3RsDse0GpMqg== +d.ns.nic.cz. 1800 IN A 193.29.206.1 +d.ns.nic.cz. 1800 IN AAAA 2001:678:1::1 +d.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708114823 20190624124001 10486 nic.cz. p2nhRTWBo56GXRdL19wT+y/XyNb6wjrz Oy3AndSR2/L9BDZrX/mkGYh20x5KpdUV +r+DPy9XFXEmvGrwAD5meA== +d.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20190708121635 20190624124001 10486 nic.cz. QeqTWoaOuL+L+QdiGOIne/WCi+D0V6EH 0h96aDuMs2eySLXSWPC54ICz28gwudmh wX4oEdQf1nYVneO7iEDFew== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +nic.cz. IN DS +SECTION ANSWER +nic.cz. 3600 IN DS 61281 13 2 4104d40c8fe2030bf7a09a199fcf37b36f7ec8ddd16f5a84f2e61c248d3afd0f +nic.cz. 3600 IN RRSIG DS 13 2 3600 20190705064642 20190622210528 6318 cz. h4tSy9MopxkbCg2mPu0s/CoE+DtoKUTL 5iw1cpQZOF1MJMZhTYo3yZjYiIkIrih8 xigA0UNFXtZEAxzsqI6omA== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +c.ns.nic.cz. IN AAAA +SECTION ANSWER +c.ns.nic.cz. 1800 IN AAAA 2001:678:11::1 +c.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20190707153808 20190624124001 10486 nic.cz. 2n+SYd+Eh6pFujzSb5u/ZFbJkfHGB3aB wo5vSKAp+s8RgEtwMawcs54psA6LWKc5 swrxP1C1xVyMLQ6L7eifGA== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +a.ns.nic.cz. IN AAAA +SECTION ANSWER +a.ns.nic.cz. 1800 IN AAAA 2001:678:f::1 +a.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20190707232622 20190624124001 10486 nic.cz. qY1Rm171qERHJwykmMYDKXlIGUnHdMhX gYlvOZPGONNuapbsqzTQos9Vd7v4IQfp k6j8sVTjSId1/q/75D4vNQ== +SECTION AUTHORITY +nic.cz. 1800 IN NS a.ns.nic.cz. +nic.cz. 1800 IN NS b.ns.nic.cz. +nic.cz. 1800 IN NS d.ns.nic.cz. +nic.cz. 1800 IN RRSIG NS 13 2 1800 20190707221726 20190624124001 10486 nic.cz. JhCrQB0nVFkti/j3weaalBPxqDG7PyiC KLV7hj61SLdRGcue9/fI9IN7lIanFWhL A1b7/L5DYejIY7WpHVU3Jg== +SECTION ADDITIONAL +a.ns.nic.cz. 1800 IN A 194.0.12.1 +a.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708050633 20190624124001 10486 nic.cz. 18u1P3Wvg0xoz3fTRtqVNlTHiZPOuGW7 C8nsMIBTCTT9mPYN0z+CcBDfRwVLNnfJ eNTfHXA9zERxbAT3WCHEXg== +b.ns.nic.cz. 1800 IN A 194.0.13.1 +b.ns.nic.cz. 1800 IN AAAA 2001:678:10::1 +b.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708030702 20190624124001 10486 nic.cz. bV+9iqB6KDNUCZrcoo9fXj3X1BHhCpgh MGSXx8q4JWJ9mm9Hz6h63UfXTWPthvJy +J18PantZQVPScwMoXVzuw== +b.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20190707133636 20190624124001 10486 nic.cz. OuH2sPy1tH6CENbjioxaaYzCB8yxB1sP FyQXAcY4VpNmLnqthfGKTn5dONZuG4UD I9ihrEaPsV3RsDse0GpMqg== +d.ns.nic.cz. 1800 IN A 193.29.206.1 +d.ns.nic.cz. 1800 IN AAAA 2001:678:1::1 +d.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708114823 20190624124001 10486 nic.cz. p2nhRTWBo56GXRdL19wT+y/XyNb6wjrz Oy3AndSR2/L9BDZrX/mkGYh20x5KpdUV +r+DPy9XFXEmvGrwAD5meA== +d.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20190708121635 20190624124001 10486 nic.cz. QeqTWoaOuL+L+QdiGOIne/WCi+D0V6EH 0h96aDuMs2eySLXSWPC54ICz28gwudmh wX4oEdQf1nYVneO7iEDFew== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +c.ns.nic.cz. IN NS +SECTION AUTHORITY +CNTVMLLA2O27TCBC4MEP7IV9P54L7FLE.nic.cz. 7200 IN NSEC3 1 0 10 879ec89c91d874a8 co07u5iiiqhl96t3hlmgkchj3203eqqj A AAAA RRSIG +CNTVMLLA2O27TCBC4MEP7IV9P54L7FLE.nic.cz. 7200 IN RRSIG NSEC3 13 3 7200 20190707155010 20190624124001 10486 nic.cz. osfB/CgwWbwa/BX/fYo+gzrImuam0bhT bD9xmAH6w+acJ8GrCNHNcCgFdimdacEA rSx2ztVVWCiALLlqzFP1WQ== +nic.cz. 1800 IN RRSIG SOA 13 2 1800 20190708100906 20190624124001 10486 nic.cz. 1mDJpihqjobv42BXF8H1dT0/vLtPTpb/ k7flS9wr8tEPe57o6GrkimcSWZlS/lm5 OyhvjIpvU9Da8n2ezGiGHw== +nic.cz. 1800 IN SOA a.ns.nic.cz. hostmaster.nic.cz. 1561383601 10800 3600 1209600 7200 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +d.ns.nic.cz. IN A +SECTION ANSWER +d.ns.nic.cz. 1800 IN A 193.29.206.1 +d.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708114823 20190624124001 10486 nic.cz. p2nhRTWBo56GXRdL19wT+y/XyNb6wjrz Oy3AndSR2/L9BDZrX/mkGYh20x5KpdUV +r+DPy9XFXEmvGrwAD5meA== +SECTION AUTHORITY +nic.cz. 1800 IN NS a.ns.nic.cz. +nic.cz. 1800 IN NS b.ns.nic.cz. +nic.cz. 1800 IN NS d.ns.nic.cz. +nic.cz. 1800 IN RRSIG NS 13 2 1800 20190707221726 20190624124001 10486 nic.cz. JhCrQB0nVFkti/j3weaalBPxqDG7PyiC KLV7hj61SLdRGcue9/fI9IN7lIanFWhL A1b7/L5DYejIY7WpHVU3Jg== +SECTION ADDITIONAL +a.ns.nic.cz. 1800 IN A 194.0.12.1 +a.ns.nic.cz. 1800 IN AAAA 2001:678:f::1 +a.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708050633 20190624124001 10486 nic.cz. 18u1P3Wvg0xoz3fTRtqVNlTHiZPOuGW7 C8nsMIBTCTT9mPYN0z+CcBDfRwVLNnfJ eNTfHXA9zERxbAT3WCHEXg== +a.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20190707232622 20190624124001 10486 nic.cz. qY1Rm171qERHJwykmMYDKXlIGUnHdMhX gYlvOZPGONNuapbsqzTQos9Vd7v4IQfp k6j8sVTjSId1/q/75D4vNQ== +b.ns.nic.cz. 1800 IN A 194.0.13.1 +b.ns.nic.cz. 1800 IN AAAA 2001:678:10::1 +b.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708030702 20190624124001 10486 nic.cz. bV+9iqB6KDNUCZrcoo9fXj3X1BHhCpgh MGSXx8q4JWJ9mm9Hz6h63UfXTWPthvJy +J18PantZQVPScwMoXVzuw== +b.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20190707133636 20190624124001 10486 nic.cz. OuH2sPy1tH6CENbjioxaaYzCB8yxB1sP FyQXAcY4VpNmLnqthfGKTn5dONZuG4UD I9ihrEaPsV3RsDse0GpMqg== +d.ns.nic.cz. 1800 IN AAAA 2001:678:1::1 +d.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20190708121635 20190624124001 10486 nic.cz. QeqTWoaOuL+L+QdiGOIne/WCi+D0V6EH 0h96aDuMs2eySLXSWPC54ICz28gwudmh wX4oEdQf1nYVneO7iEDFew== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +nic.cz. IN DNSKEY +SECTION ANSWER +nic.cz. 1800 IN DNSKEY 256 3 13 SmMYG4VjCgj4rAxB4sqgvIzcGtESoX1H m7Dsoekap6HJwj8WOEiFciSg537caOPl 4+7Dyp/b5JwkBemxQQRL9Q== +nic.cz. 1800 IN DNSKEY 257 3 13 LM4zvjUgZi2XZKsYooDE0HFYGfWp242f KB+O8sLsuox8S6MJTowY8lBDjZD7JKbm aNot3+1H8zU9TrDzWmmHwQ== +nic.cz. 1800 IN RRSIG DNSKEY 13 2 1800 20190707163411 20190624124001 61281 nic.cz. ggHlmuzLOTZOCYcbZ8TrNoTXOAg7xJ9N B+QLdmZYyny53ODMkRfDv28SSMkwtuc1 rZXfC+/c7oArzsBbbncTRA== +nic.cz. 1800 IN RRSIG DNSKEY 13 2 1800 20190708092836 20190624124001 10486 nic.cz. lFYLLcm5ICS0BSdB0+dA8m7XxdRbB49+ 5N1w8AHOaPNDTWp9GlXA935IUk18C2to 1ghYmP2RZaNOTchSVRgWzA== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +c.ns.nic.cz. IN NS +SECTION AUTHORITY +cntvmlla2o27tcbc4mep7iv9p54l7fle.nic.cz. 7200 IN NSEC3 1 0 10 879ec89c91d874a8 co07u5iiiqhl96t3hlmgkchj3203eqqj A AAAA RRSIG +cntvmlla2o27tcbc4mep7iv9p54l7fle.nic.cz. 7200 IN RRSIG NSEC3 13 3 7200 20190707155010 20190624124001 10486 nic.cz. osfB/CgwWbwa/BX/fYo+gzrImuam0bhT bD9xmAH6w+acJ8GrCNHNcCgFdimdacEA rSx2ztVVWCiALLlqzFP1WQ== +nic.cz. 1800 IN RRSIG SOA 13 2 1800 20190708100906 20190624124001 10486 nic.cz. 1mDJpihqjobv42BXF8H1dT0/vLtPTpb/ k7flS9wr8tEPe57o6GrkimcSWZlS/lm5 OyhvjIpvU9Da8n2ezGiGHw== +nic.cz. 1800 IN SOA a.ns.nic.cz. hostmaster.nic.cz. 1561383601 10800 3600 1209600 7200 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +b.ns.nic.cz. IN NS +SECTION AUTHORITY +cu82t3qracdj063olk907dv20ine9dea.nic.cz. 7200 IN NSEC3 1 0 10 879ec89c91d874a8 d2749lueihj9moq02lt0k9i8tnbgest1 A AAAA RRSIG +cu82t3qracdj063olk907dv20ine9dea.nic.cz. 7200 IN RRSIG NSEC3 13 3 7200 20190708122905 20190624124001 10486 nic.cz. fg5ypFDXqZlnzSbndkPm2VjZieL0t5NT zKu0cfcWHu4TlWB/OxxuaD4C2oGx5nki Pchj+FQNobNZJfCXKOa4ug== +nic.cz. 1800 IN RRSIG SOA 13 2 1800 20190708100906 20190624124001 10486 nic.cz. 1mDJpihqjobv42BXF8H1dT0/vLtPTpb/ k7flS9wr8tEPe57o6GrkimcSWZlS/lm5 OyhvjIpvU9Da8n2ezGiGHw== +nic.cz. 1800 IN SOA a.ns.nic.cz. hostmaster.nic.cz. 1561383601 10800 3600 1209600 7200 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +csas.cz. IN DS +SECTION ANSWER +csas.cz. 3600 IN DS 8196 7 2 c5c7974e1dc3bf036f8744a4d919e03ef01d8d6167db6201940ca87059558ee3 +csas.cz. 3600 IN RRSIG DS 13 2 3600 20190706080140 20190622110535 6318 cz. 5V0ZvjpT8zWMsVwPpyhsMc9DRFVMQtP2 D2o7S/dLbZ7YL4Mqd8P9n+o+4NQgaqQw 7VPBHnN3VzVDYvWjmr+rfw== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +ns.nic.cz. IN NS +SECTION AUTHORITY +JNP2UC34HHA9DE64L3RJF6ULP4PRA74N.nic.cz. 7200 IN NSEC3 1 0 10 879ec89c91d874a8 jsdlj2k5hipr7eb12ne8bads7lshvo1k +JNP2UC34HHA9DE64L3RJF6ULP4PRA74N.nic.cz. 7200 IN RRSIG NSEC3 13 3 7200 20190707164247 20190624124001 10486 nic.cz. lrFJPPGpHyoC5l4uM8l94ye/HG1kTVw7 dR98um06iG+2XK82Dib+wnzqoNNIqbaA FeaAkjfqgCHA8kDySAP+5g== +nic.cz. 1800 IN RRSIG SOA 13 2 1800 20190708100906 20190624124001 10486 nic.cz. 1mDJpihqjobv42BXF8H1dT0/vLtPTpb/ k7flS9wr8tEPe57o6GrkimcSWZlS/lm5 OyhvjIpvU9Da8n2ezGiGHw== +nic.cz. 1800 IN SOA a.ns.nic.cz. hostmaster.nic.cz. 1561383601 10800 3600 1209600 7200 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +d.ns.nic.cz. IN NS +SECTION AUTHORITY +nic.cz. 1800 IN RRSIG SOA 13 2 1800 20190708100906 20190624124001 10486 nic.cz. 1mDJpihqjobv42BXF8H1dT0/vLtPTpb/ k7flS9wr8tEPe57o6GrkimcSWZlS/lm5 OyhvjIpvU9Da8n2ezGiGHw== +nic.cz. 1800 IN SOA a.ns.nic.cz. hostmaster.nic.cz. 1561383601 10800 3600 1209600 7200 +on20qsre8qs9a2asfdatp5cs6g2i5ssq.nic.cz. 7200 IN NSEC3 1 0 10 879ec89c91d874a8 onv9qnjm1krm9qdk0n6o6o23doo5jfug A AAAA RRSIG +on20qsre8qs9a2asfdatp5cs6g2i5ssq.nic.cz. 7200 IN RRSIG NSEC3 13 3 7200 20190708041440 20190624124001 10486 nic.cz. WkMEKZUDwPTv5Y3KG/C9LTVTynACPb16 1OuoxTij1reZR4LzHVoGQKDf0Dbj2Rau 9LrBW5PI5PSDQ6BmUKRj6A== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +a.ns.nic.cz. IN AAAA +SECTION ANSWER +a.ns.nic.cz. 1800 IN AAAA 2001:678:f::1 +a.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20190707232622 20190624124001 10486 nic.cz. qY1Rm171qERHJwykmMYDKXlIGUnHdMhX gYlvOZPGONNuapbsqzTQos9Vd7v4IQfp k6j8sVTjSId1/q/75D4vNQ== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +a.ns.nic.cz. IN A +SECTION ANSWER +a.ns.nic.cz. 1800 IN A 194.0.12.1 +a.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708050633 20190624124001 10486 nic.cz. 18u1P3Wvg0xoz3fTRtqVNlTHiZPOuGW7 C8nsMIBTCTT9mPYN0z+CcBDfRwVLNnfJ eNTfHXA9zERxbAT3WCHEXg== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +cz. IN DS +SECTION AUTHORITY +cz. 3600 IN RRSIG SOA 13 1 3600 20190707192652 20190625123528 6318 cz. BYXtAj/BIXIb5xlT6TBQYaFhbloeuo0H RqJTdPu9gVVyuBKbvV6YmQbS4BBMM4qA Cp2Vw7yBK9+dONHp9JAmuA== +cz. 3600 IN SOA a.ns.nic.cz. hostmaster.nic.cz. 1561469728 900 300 604800 900 +fu65o5n6kmh8a04mps1e4a73s37jr72u.cz. 900 IN NSEC3 1 0 10 357dda080afe0ef8 fu677p5qqp6ihbeeloacm3sr4ieklu7m NS SOA RRSIG DNSKEY NSEC3PARAM +fu65o5n6kmh8a04mps1e4a73s37jr72u.cz. 900 IN RRSIG NSEC3 13 2 900 20190706134746 20190622170523 6318 cz. EsElag8LQ5TcunX40efTh35IN3GoQnPq XAZZ0cSPHT6GOohzp8iD1LSLLbwZUiNE yB4hCp1yqEi0pINRoHglNA== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +cz. IN DNSKEY +SECTION ANSWER +cz. 18000 IN DNSKEY 256 3 13 7t+ZoZGIrV27M/PEAH8OKSjEXVAJhV6O o5ESS+Vry5ZhfoWogIKAXzvda/qY/WTA L09BEk+ko16oGRRktvzWEw== +cz. 18000 IN DNSKEY 257 3 13 nqzH7xP1QU5UOVy/VvxFSlrB/XgX9JDJ zj51PzIj35TXjZTyalTlAT/f7PAfaSD5 mEG1N8Vk9NmI2nxgQqhzDQ== +cz. 18000 IN RRSIG DNSKEY 13 1 18000 20190705000000 20190621000000 20237 cz. kJ1zRR76FpPS4SLyFuLbrQBvVnq6GY+x 5sOV2ayq6rt4D1sjpxROufFfBfi/6wAv qo3VZ0iUJSB03djeX8gk5A== +cz. 18000 IN RRSIG DNSKEY 13 1 18000 20190709092856 20190625123528 6318 cz. Lpz5UZi8lVH1TCdz6DyjWvoUciOVqA4Z d+3TVxNF2GsLzM15nwc34FnaQF3dxhJZ mdf6dET7iOUmy2SN3majVg== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +c.ns.nic.cz. IN AAAA +SECTION ANSWER +c.ns.nic.cz. 1800 IN AAAA 2001:678:11::1 +c.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20190707153808 20190624124001 10486 nic.cz. 2n+SYd+Eh6pFujzSb5u/ZFbJkfHGB3aB wo5vSKAp+s8RgEtwMawcs54psA6LWKc5 swrxP1C1xVyMLQ6L7eifGA== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +d.ns.nic.cz. IN A +SECTION ANSWER +d.ns.nic.cz. 1800 IN A 193.29.206.1 +d.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708114823 20190624124001 10486 nic.cz. p2nhRTWBo56GXRdL19wT+y/XyNb6wjrz Oy3AndSR2/L9BDZrX/mkGYh20x5KpdUV +r+DPy9XFXEmvGrwAD5meA== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +d.ns.nic.cz. IN AAAA +SECTION ANSWER +d.ns.nic.cz. 1800 IN AAAA 2001:678:1::1 +d.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20190708121635 20190624124001 10486 nic.cz. QeqTWoaOuL+L+QdiGOIne/WCi+D0V6EH 0h96aDuMs2eySLXSWPC54ICz28gwudmh wX4oEdQf1nYVneO7iEDFew== +ENTRY_END + + +ENTRY_BEGIN +MATCH subdomain +ADJUST copy_id copy_query +REPLY QR RD NOERROR +SECTION QUESTION +csas.cz. IN NS +SECTION AUTHORITY +csas.cz. 3600 IN DS 8196 7 2 c5c7974e1dc3bf036f8744a4d919e03ef01d8d6167db6201940ca87059558ee3 +csas.cz. 3600 IN NS ddnsa.csas.cz. +csas.cz. 3600 IN NS ddnsb.csas.cz. +csas.cz. 3600 IN RRSIG DS 13 2 3600 20190706080140 20190622110535 6318 cz. 5V0ZvjpT8zWMsVwPpyhsMc9DRFVMQtP2 D2o7S/dLbZ7YL4Mqd8P9n+o+4NQgaqQw 7VPBHnN3VzVDYvWjmr+rfw== +SECTION ADDITIONAL +ddnsa.csas.cz. 3600 IN A 194.50.240.64 +ddnsb.csas.cz. 3600 IN A 194.50.240.192 +ENTRY_END + + +ENTRY_BEGIN +MATCH subdomain +ADJUST copy_id copy_query +REPLY QR AA RD NOERROR +SECTION QUESTION +nic.cz. IN NS +SECTION ANSWER +nic.cz. 1800 IN NS a.ns.nic.cz. +nic.cz. 1800 IN NS b.ns.nic.cz. +nic.cz. 1800 IN NS d.ns.nic.cz. +nic.cz. 1800 IN RRSIG NS 13 2 1800 20190707221726 20190624124001 10486 nic.cz. JhCrQB0nVFkti/j3weaalBPxqDG7PyiC KLV7hj61SLdRGcue9/fI9IN7lIanFWhL A1b7/L5DYejIY7WpHVU3Jg== +SECTION ADDITIONAL +a.ns.nic.cz. 1800 IN A 194.0.12.1 +a.ns.nic.cz. 1800 IN AAAA 2001:678:f::1 +a.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708050633 20190624124001 10486 nic.cz. 18u1P3Wvg0xoz3fTRtqVNlTHiZPOuGW7 C8nsMIBTCTT9mPYN0z+CcBDfRwVLNnfJ eNTfHXA9zERxbAT3WCHEXg== +a.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20190707232622 20190624124001 10486 nic.cz. qY1Rm171qERHJwykmMYDKXlIGUnHdMhX gYlvOZPGONNuapbsqzTQos9Vd7v4IQfp k6j8sVTjSId1/q/75D4vNQ== +b.ns.nic.cz. 1800 IN A 194.0.13.1 +b.ns.nic.cz. 1800 IN AAAA 2001:678:10::1 +b.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708030702 20190624124001 10486 nic.cz. bV+9iqB6KDNUCZrcoo9fXj3X1BHhCpgh MGSXx8q4JWJ9mm9Hz6h63UfXTWPthvJy +J18PantZQVPScwMoXVzuw== +b.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20190707133636 20190624124001 10486 nic.cz. OuH2sPy1tH6CENbjioxaaYzCB8yxB1sP FyQXAcY4VpNmLnqthfGKTn5dONZuG4UD I9ihrEaPsV3RsDse0GpMqg== +d.ns.nic.cz. 1800 IN A 193.29.206.1 +d.ns.nic.cz. 1800 IN AAAA 2001:678:1::1 +d.ns.nic.cz. 1800 IN RRSIG A 13 4 1800 20190708114823 20190624124001 10486 nic.cz. p2nhRTWBo56GXRdL19wT+y/XyNb6wjrz Oy3AndSR2/L9BDZrX/mkGYh20x5KpdUV +r+DPy9XFXEmvGrwAD5meA== +d.ns.nic.cz. 1800 IN RRSIG AAAA 13 4 1800 20190708121635 20190624124001 10486 nic.cz. QeqTWoaOuL+L+QdiGOIne/WCi+D0V6EH 0h96aDuMs2eySLXSWPC54ICz28gwudmh wX4oEdQf1nYVneO7iEDFew== +ENTRY_END + + +ENTRY_BEGIN +MATCH subdomain +ADJUST copy_id copy_query +REPLY QR AA RD NOERROR +SECTION QUESTION +cz. IN NS +SECTION ANSWER +cz. 3600 IN NS a.ns.nic.cz. +cz. 3600 IN NS b.ns.nic.cz. +cz. 3600 IN NS c.ns.nic.cz. +cz. 3600 IN NS d.ns.nic.cz. +cz. 3600 IN RRSIG NS 13 1 3600 20190705184721 20190622030526 6318 cz. SzhZGIFskUTnlxipSleB+IghUpyhS1eV PubDzCU/CC0LmDBoAVwfiY8zKWdPSu0X 5544N2KzZ5JrPoasQkOLzg== +SECTION ADDITIONAL +a.ns.nic.cz. 3600 IN A 194.0.12.1 +a.ns.nic.cz. 3600 IN AAAA 2001:678:f::1 +b.ns.nic.cz. 3600 IN A 194.0.13.1 +b.ns.nic.cz. 3600 IN AAAA 2001:678:10::1 +d.ns.nic.cz. 3600 IN A 193.29.206.1 +d.ns.nic.cz. 3600 IN AAAA 2001:678:1::1 +ENTRY_END + + +ENTRY_BEGIN +MATCH subdomain +ADJUST copy_id copy_query +REPLY QR AA RD NOERROR +SECTION QUESTION +cz. IN NS +SECTION ANSWER +cz. 3600 IN NS a.ns.nic.cz. +cz. 3600 IN NS b.ns.nic.cz. +cz. 3600 IN NS c.ns.nic.cz. +cz. 3600 IN NS d.ns.nic.cz. +cz. 3600 IN RRSIG NS 13 1 3600 20190705184721 20190622030526 6318 cz. SzhZGIFskUTnlxipSleB+IghUpyhS1eV PubDzCU/CC0LmDBoAVwfiY8zKWdPSu0X 5544N2KzZ5JrPoasQkOLzg== +ENTRY_END + + + + +RANGE_END + + + +; Group's zones: +; csas.cz. +; Server names: +; ddnsa.csas.cz. +; ddnsb.csas.cz. +; ddnsc.csas.cz. +; ddnsd.csas.cz. +RANGE_BEGIN 0 1000 + ADDRESS 194.50.240.64 + ADDRESS 194.50.240.192 + ADDRESS 194.50.240.194 + ADDRESS 194.50.240.66 + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +ddnsd.csas.cz. IN NS +SECTION AUTHORITY +csas.cz. 120 IN RRSIG SOA 7 2 120 20190630054550 20190623054550 26663 csas.cz. c/sxvp5WLJOsDGgnhxPOk9oBwXRtsUXa enyWg5rd8yBcquHqfFKQu1v8wEPjJjAH B6joG1tJXq99nxtCN4jlDipPKLIzWxba aYIxE8Pab199qiHrR5vxO4KgPxRXbg5d gzIVrrhB+h23DZDacQ+l+kQWSAcycjPh xFvNjAZrH4obU/F+jHt4ix5XqEVuDrPQ YTGBGEUpl7YbcyaROzVP3dtCQZamkfaB CQYoNAJlNw6YFG1qulPm3pn3jJHhu88f dFgCisWfp7TjMYn7Id7aYJtliagE47xu ASnhOtaeXAVxDGUIkOI5u8uUCC/jAiHr MgARE5rcqh2AIZeSv0BiMQ== +csas.cz. 120 IN SOA ddnsa.csas.cz. domainservices.csas.cz. 2019061320 28800 1800 2592000 120 +gfr9qt699hmcloa9radenpk7pqeikiu8.csas.cz. 120 IN NSEC3 1 0 1 31245099125aedf7 gfr9qt699hmcloa9radenpk7pqeikiu9 TXT +gfr9qt699hmcloa9radenpk7pqeikiu8.csas.cz. 120 IN RRSIG NSEC3 7 3 120 20190702130338 20190625130338 26663 csas.cz. OWtiEsmO7VnUV2MQp3gZJmOAQw4n81DH 9c5FFJW6MBE3afaIqGTvWYCgRipEEvZF qPBVxGpptVsMbuELagewwZYtR+EEh2A/ qMf/wVC//t2c67YFIxMILi9ijleYS5y8 m5Mu+logobk2Resxs0kC47kwNP9KSh7s gm3SFjK1LXRXg19UmiabMIINoOoAE1M0 XePGhiaqhFwros32p++2JrCdaYrPXLWy VuENN0chZ/hE55ic4M7KpxZ12+oPzigk tIeh9sqTaKpGJBZk4vlJQLq2zfL/gnKZ CRRN8bJQ/ZsSZG//rL0nqZcgyxQqXI9w As8TBPtQ9IQWq2K5MxjKHg== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +csas.cz. IN AAAA +SECTION AUTHORITY +8b6896cv5m6qpdd29hb4s12lagep7utq.csas.cz. 120 IN NSEC3 1 0 1 31245099125aedf7 8b6896cv5m6qpdd29hb4s12lagep7utr NS SOA RRSIG DNSKEY NSEC3PARAM +8b6896cv5m6qpdd29hb4s12lagep7utq.csas.cz. 120 IN RRSIG NSEC3 7 3 120 20190702130338 20190625130338 26663 csas.cz. SNkUAfA3do4t3pyNw1I0zfdiGnWufjIW S4DnHV7Sz+LNOfbe36ipmpUevus+12DG qgG+fQNzxAbCByk7e5fqieyXLVyGfRla 1H9UwCj42/TWATKqDQUFADVEaZ4nHrnN E2yZWkaNNW3jBX7LTIvgwHHVXuM5HQwd Q3/be9B6oz92QLs7ds2zhtjB1NYCUeFD OwYOxSekxZV/ePEfytrXbw1NPcji9GsO v6+HLgYLLy2qIQKMS5xavb4+4/eDMsq4 vJ+uvrmc66JHL7rFyrwd/x1NNsHXwqac czV+GifY3m8svOydXETd9tTC8sTsiU9x ZFTAOcTlxlo211UwuHnwkA== +csas.cz. 120 IN RRSIG SOA 7 2 120 20190630054550 20190623054550 26663 csas.cz. c/sxvp5WLJOsDGgnhxPOk9oBwXRtsUXa enyWg5rd8yBcquHqfFKQu1v8wEPjJjAH B6joG1tJXq99nxtCN4jlDipPKLIzWxba aYIxE8Pab199qiHrR5vxO4KgPxRXbg5d gzIVrrhB+h23DZDacQ+l+kQWSAcycjPh xFvNjAZrH4obU/F+jHt4ix5XqEVuDrPQ YTGBGEUpl7YbcyaROzVP3dtCQZamkfaB CQYoNAJlNw6YFG1qulPm3pn3jJHhu88f dFgCisWfp7TjMYn7Id7aYJtliagE47xu ASnhOtaeXAVxDGUIkOI5u8uUCC/jAiHr MgARE5rcqh2AIZeSv0BiMQ== +csas.cz. 120 IN SOA ddnsa.csas.cz. domainservices.csas.cz. 2019061320 28800 1800 2592000 120 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +ddnsc.csas.cz. IN NS +SECTION AUTHORITY +c1c03am0n36kncp4iqb4buoqo1ectql4.csas.cz. 120 IN NSEC3 1 0 1 31245099125aedf7 c1c03am0n36kncp4iqb4buoqo1ectql5 TXT +c1c03am0n36kncp4iqb4buoqo1ectql4.csas.cz. 120 IN RRSIG NSEC3 7 3 120 20190702130338 20190625130338 26663 csas.cz. FKjWbcw5aP2HzHFy2t+M462dxM1ph0FW 02Tohh/aJQOp1iKQOi6u1V2tI3H9CMXW xeIAktZ+xiUDRYLX50fNuJZMaSsn7r5A LYjVAKGzwMPncIf6IlEDYkj1I1rdp6z8 LvWsWNO1wfIPq1Dw2geN60bcz3D7r/sY WfjU/7jO9f0nekZZXI2qFsMSYd9Dkaq7 mlpP0NrWnUbCjL+BfuLFNebh8g3D8f5w Tq3R9czwW5YTVXUquBIrjiZ0Ko9INgQ/ 4pvJqJ1bsTpmzdODfAssEtpu+es4wLwt ZTe8Ll9VeDB20RqEnAyHZwQRXXVF2t+F vdJU7Desbpu6rElA7IXwRw== +csas.cz. 120 IN RRSIG SOA 7 2 120 20190630054550 20190623054550 26663 csas.cz. c/sxvp5WLJOsDGgnhxPOk9oBwXRtsUXa enyWg5rd8yBcquHqfFKQu1v8wEPjJjAH B6joG1tJXq99nxtCN4jlDipPKLIzWxba aYIxE8Pab199qiHrR5vxO4KgPxRXbg5d gzIVrrhB+h23DZDacQ+l+kQWSAcycjPh xFvNjAZrH4obU/F+jHt4ix5XqEVuDrPQ YTGBGEUpl7YbcyaROzVP3dtCQZamkfaB CQYoNAJlNw6YFG1qulPm3pn3jJHhu88f dFgCisWfp7TjMYn7Id7aYJtliagE47xu ASnhOtaeXAVxDGUIkOI5u8uUCC/jAiHr MgARE5rcqh2AIZeSv0BiMQ== +csas.cz. 120 IN SOA ddnsa.csas.cz. domainservices.csas.cz. 2019061320 28800 1800 2592000 120 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +ddnsd.csas.cz. IN AAAA +SECTION AUTHORITY +csas.cz. 120 IN RRSIG SOA 7 2 120 20190630054550 20190623054550 26663 csas.cz. c/sxvp5WLJOsDGgnhxPOk9oBwXRtsUXa enyWg5rd8yBcquHqfFKQu1v8wEPjJjAH B6joG1tJXq99nxtCN4jlDipPKLIzWxba aYIxE8Pab199qiHrR5vxO4KgPxRXbg5d gzIVrrhB+h23DZDacQ+l+kQWSAcycjPh xFvNjAZrH4obU/F+jHt4ix5XqEVuDrPQ YTGBGEUpl7YbcyaROzVP3dtCQZamkfaB CQYoNAJlNw6YFG1qulPm3pn3jJHhu88f dFgCisWfp7TjMYn7Id7aYJtliagE47xu ASnhOtaeXAVxDGUIkOI5u8uUCC/jAiHr MgARE5rcqh2AIZeSv0BiMQ== +csas.cz. 120 IN SOA ddnsa.csas.cz. domainservices.csas.cz. 2019061320 28800 1800 2592000 120 +gfr9qt699hmcloa9radenpk7pqeikiu8.csas.cz. 120 IN NSEC3 1 0 1 31245099125aedf7 gfr9qt699hmcloa9radenpk7pqeikiu9 TXT +gfr9qt699hmcloa9radenpk7pqeikiu8.csas.cz. 120 IN RRSIG NSEC3 7 3 120 20190702130338 20190625130338 26663 csas.cz. OWtiEsmO7VnUV2MQp3gZJmOAQw4n81DH 9c5FFJW6MBE3afaIqGTvWYCgRipEEvZF qPBVxGpptVsMbuELagewwZYtR+EEh2A/ qMf/wVC//t2c67YFIxMILi9ijleYS5y8 m5Mu+logobk2Resxs0kC47kwNP9KSh7s gm3SFjK1LXRXg19UmiabMIINoOoAE1M0 XePGhiaqhFwros32p++2JrCdaYrPXLWy VuENN0chZ/hE55ic4M7KpxZ12+oPzigk tIeh9sqTaKpGJBZk4vlJQLq2zfL/gnKZ CRRN8bJQ/ZsSZG//rL0nqZcgyxQqXI9w As8TBPtQ9IQWq2K5MxjKHg== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +csas.cz. IN DNSKEY +SECTION ANSWER +csas.cz. 7200 IN DNSKEY 256 3 7 AwEAAaNjFdWEB091Zv/uWznXJXbFaDYD cG4GvutyTPMM1nWDLu1ZTR/ujYhwEoWM 1DYyq8QkIFJS7PVnu312O8pkiWcvV9Fl hVv9AYJ8OCbixXjSrwXL1yfBhhzJxxjA RXi49Xn/NTPNiTeK6lVed2QMLbwucpTy 4GEduAcXWDi7/OnDD3UmaFtxXQ2LKc7d +of4okGQWYdka9b+KeyJtXwhDuuzGf/v m8E3Tdjt5ldgOpofh/BO5lifl6mAXgnW E6z+p0fb1ksyq1hGu9ssizxr8A10amUa repR+zcOFoDJNv5IxGWi5rriAvBW4NAl U1wy1P6BSDgMZuc8JBj3VikbI6U= +csas.cz. 7200 IN DNSKEY 256 3 7 AwEAAbk21ZXai9JsSCmuW8InRPINGI+o 2vxokpQ0imk6iDPHqF3QhJSs5QcFG+Hq yZ3mWLkb5cT/u0zKZ+Onri0s2zSm4ASk /A86BQSEaPXG90GiD+waH7qF5vorF5Gx 1BvzdtLz9vtbsQ0G1GfdbvlouvlU05t2 GlSuAXqsj6RI5WaWHejKJ1fcOtR7hQTt tH5+oir5t1eJ1riQsOyPzC2y9V+Q28Tf cD5F6oxghhn9+YieAIqMMp5r7txouxp0 DFG+oVMbTmt07B9MIClXOMMTOYDTOj0c fpdx5/WHoigkgNdcUh5ulsva9OK9EWZX WkicdtzfYfe7fi3LqFxWc0DMiY0= +csas.cz. 7200 IN DNSKEY 257 3 7 AwEAAcMXtJYlvIshn+O541gOW48wNlgh szwYVFeSUd+qB+BfejKueV+4vo9IY1A8 cKxfLcOwT0F/XyEZXhpigZPXf2OQrLWi R6aK0cDErhLUp30AWdEwB/T8QnfePzM1 LX30rgAP5JhGLckrfoVbSZi28S6vvuxA GbFmgEpGkKVgMg9pKAcjrRUd/bmyNaQR yECq40cwgHFaDm9SPeAavgMxn1l88dAv qIwKDvpN2Er+K/Gl5GRApDFxSt9x+7rt NIrpFV4Apl/j81Rcxyj7v7HXEu3P8BNQ 6s0cVg8RmtY+R0J59sTsZyJGI2krq5Ap k2K1S2RA7QZxHQCMKx+caRyLwk6KqjmF EYMILxpYWER+iPddpeo5w0AYJNxDYYeE 2cUYuBXNi7SM4UZGDbMZ+j2xlzGNyAsE mWXlrR4qCgYKO7JxK0Yw3otZzNyrcJhS fT1E0tNvDv3zH/j8riqIyW36EK1yjVav dcI3a+GGSh5oylb/7SuLFNKEW0g2fo5q W+rITI77k+MRR798XNXqSKTT19LNiqWc xhlA4aGzHzKZieEWR4d6FHhOKzNoo+Ce j7b00rg5v2hwSAEEHcC3lwuStya1J5IX g6sfdNxWqJ0yydI/PGOn0ZgakX2oEE4b zGJchx/aHKogs2L+bZJfi7N1OmU7AU0D mY6kTxEYC1lBFb1h +csas.cz. 7200 IN DNSKEY 257 3 7 AwEAAcoflVKUA6KreqNnC7qYr0B+UiYS Fqoja50or7jBmrrmxok5mR9Ea+L3D0PK ynffMxFMMQm1JekaJYH66eUGgWWqTHRQ nAKVQchHCv37Bf0nFEetKo/qf4KJbdDD Ar3sXjfkClaQQlqMPDeCQQbfxSWpdvQo ip7pX35KsatTObINwqnMsl3tsvsugybw 6+xujlYWOpmJcjHri7cdZA5NSx3k3Sdb 16/GEpcwFe0Z8Oi4TgbcVEaAziyOm0zC to1SW1L4VovdZkadAa3RMQ7QpyR/wIOr TjqS/weDkOyKgGNnbVL5l8dUAD8rk/Do cXhQuXxNqHB9PVNxBCsfnVVj6fbk09eo 6stXAdJbwoa1NoPBlHsCV5+EN65XXsGO eLZneUt213RDGkphcKTWaF3dvIRXtWbt Rz2bCrxs12ADhhXisyH2EGntyI3LX3zY ISbKaZnRzbU1nKIdcNzFJ1p8aoUPgzX7 YhpXVkhfcaB8aAG38Ys6EXvcACFdoqma V7b0VnvU1tPsI9bAesU4d5K4EjCNrana yfvFgakYdmoi3GrpWeHim7CDK9s5yrh3 Dou59pU7a/1xugGvuCkDllo8Z70hWzJq tVNFrSv7jwLA7+77pRKBF7Vl88Y0i/6h zUkllh7mBCMilyU50AX9bGJsd6PFJn6P iZ8x+XoCel0oZqz3 +csas.cz. 7200 IN RRSIG DNSKEY 7 2 7200 20190630181024 20190623181024 26663 csas.cz. cPZVp/k5lPxsjnRhCc8zXkKGf2VQjg93 Rwyrwjm6wPg/B7n3iTzLA+TUROwMgcAB rlEru/TNaySpWR36rDQdg/w8JZsB17Wf HIO0t1GxjQdEjPYD9T4x0b5u/Ra/ienZ XgHwuto1tQCghttwv7ioECzf1+52asf+ oxe8il1W4HdMH8/wiEyOyPm8n9ou6siX tcVjqbRTy0xLcPoFm/ATvUBMGBrbMedy hGWn88cLoIQA4IPm4zAIAWunPV8sFNKY l12HDNzQlfrO6ukJqtFTjQREeTWFC0qP KXWSPJeX9fTfrT6zHfyV/YQd3tGRP0J8 KTzXmUMYi2vrVbeagR00Vg== +csas.cz. 7200 IN RRSIG DNSKEY 7 2 7200 20190630181024 20190623181024 8196 csas.cz. t8HaIHf6FCi91a3ppWC4bAiN+WvZPdbf ESinQ+iYFSWk7NlCw45eT/0SpRWNQ5jQ fvWkkvBDDuHDv9FVdmqkvAuMCf0/GgnM 2bdOQt/q2Hp3E91eFpHW1fx7YtQabj+r 9Ww30ewUplDE2v6JlHnJzm53mYsbUUum l+rAtq+QJnb8sjjn2JceDtQLBkZaLsLe vUuFL/Hz/WvvMgjYUyuxbK4xxJByiymo hZIDxA8TWcaoFFyiP+Rv4leqaxVLXun4 VsWsCE2YYtOqrYRaq2UJvbSIFAPdix5Q EL44TXYlJ00NbngD480tJ8TwyNTm4CLp 8Z85xtmpYfKO3VFAycL0zV/7fOJRkoPQ LrBxClIbeQJlQapm1FMQQK/FFfHKSVRq 659VLfDbxfbTU/yWOmlUldaB8r7y6BhD iq4u/IZqIU5eTIbeoMd3HBerKREo0vOy UPCoW4OJ3Q9/WQ18cPsVW+BKjkKRrV4T VQKc52jwkPufUq8fNuVeuZ+8cvgIaUfD 8Xie+N2Trlh5tfaAAg9etgu0EmoEAP8V 1mTzT54losWySfSh5JNx5fDtUaxZI/q3 2+mbgnO4QlGB9eAhoA6JRHByWf+Gf9rR hEktuZqg2Uw4ifOebJsQE4atFr8Tr910 xihtrgtx/y6HwpB9nIr7V91ewn6lKMEz d4ordmuS+1Q= +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +ddnsa.csas.cz. IN A +SECTION ANSWER +ddnsa.csas.cz. 7200 IN A 194.50.240.64 +ddnsa.csas.cz. 7200 IN RRSIG A 7 3 7200 20190630180929 20190623180929 26663 csas.cz. QJCNptf+bgoFFzW6vh6lMnevBqaHq1Qk p7UB7hJZfqewhVJsf4/aZlUJg9Ulq990 xe1K+Vw2H7zFKRmS+BhLL0NVS6F/uo3g VWU7UkiLCUdICMNmBlUfv6dTJmovHydF DeCtS8eunKTYKnvSklmr/aLKnFFEaeoi SUF8hkHM2fADZekxqTEMS2ATjlY0rbTb biDd4c1tZdSDeUV7z4B4pHVdgVFBOi6+ kL4MtMM2AY4o9h31jOUGFnfA0cJPmxNN Cx+qgBAl0zKL9+uFrBqB6nYgmd0a1pRa UEQCcVVKkSjDX3gWHtO/nqflFmyDNV4Y OlVLcHVCDNqRtwIRwucUOg== +SECTION AUTHORITY +csas.cz. 7200 IN NS ddnsa.csas.cz. +csas.cz. 7200 IN NS ddnsb.csas.cz. +csas.cz. 7200 IN NS ddnsc.csas.cz. +csas.cz. 7200 IN NS ddnsd.csas.cz. +csas.cz. 7200 IN RRSIG NS 7 2 7200 20190630180929 20190623180929 26663 csas.cz. PLqM9UHNFbb8GmE+SlzSCjAEKYsklbgt CnWf9G26HkwtacOyJkIHe+T0m94rJZTd 6Mnn32Sw/at6idSvVNGQfE6DIKr2rtK6 lJNe0HPMxYWC9Tr5zpzW1/SjLVooMw6n m8K+18RBI33PBI69UgXyuePnWefdTYOJ g4SK3rR2wUlFxnELEFGfYkytZ6tLi1Cd bTgch93lDnXNBRZ2t/mu5jPyv4NTdKCo WgIMJJcuFdPr/YRVRaemkVEup7qVYImk pa4n+7MMERTL/dw+GxtnZ2YKqyxcepYo rPib3FSTaOFZjEPqNPDNfXXYW+bPmWFP 9ESqUtgmvZ1rMDZbr8lbgQ== +SECTION ADDITIONAL +ddnsb.csas.cz. 7200 IN A 194.50.240.192 +ddnsb.csas.cz. 7200 IN RRSIG A 7 3 7200 20190630180929 20190623180929 26663 csas.cz. CXelkXBmt3nR60+gTwRcTXTVH1OBpfAE McYGfcEmxk8t+wFwLrCfM+6mJUWbV3a8 cIggoBHU8gJxarnIX4K5bXNel2QRzlTa +qTTPPTxH5LwHayDwoo5edk6c2HkCrY3 qChg5SyjuNxxsF4ybqvqIU0d/kM6wqG7 pKPmplD9BNJmzVIVQ8aTBMhdrvIcqK/f 81TGgNxMqJxfpTYKmnA8vVqpJ3C1kbfn no+ux39NY7Wj58NwI+UIcDlmBj414MSz Z4wEfYSgtVnEA8fO+ufZ3jaxjk6NClz+ XMP1gYj/y08bj0h5ACIKJqci2uqPujoi TNBYzQWn6LVPqNIM8O7eSg== +ddnsc.csas.cz. 7200 IN A 194.50.240.66 +ddnsc.csas.cz. 7200 IN RRSIG A 7 3 7200 20190630180929 20190623180929 26663 csas.cz. AHQJp3i/UzKmjm/hhmEM1mhV7gPAq2rX 0z4ZOF+ZDyL57nXSHnzGqz4sdd3Q0E1H pmGENdgiOR1Grabo6X3QIp7mZbuAR14T 5TQYHSDoTZ+aTOkqvxCxfUCpEYLVHlbP whE+ntazt0gmkko3eGUBMefMv+JVmuwy tirS7pxlNL1VRnKtbfDsWJ2zmJuur/WK wGzHCMybt4fITB0Og/I9pnwMwykdxhpF bo/MQAa35oTefzvWsD3uFKOhrAc7CP2N MyqDFzv0QDN00s0yFXHHxNg3QFXLAg16 4khZaKT076k+F5N/ufsoIeU/AbByx82T PxpLg8NCh9nNvzgqoCEccw== +ddnsd.csas.cz. 7200 IN A 194.50.240.194 +ddnsd.csas.cz. 7200 IN RRSIG A 7 3 7200 20190630180929 20190623180929 26663 csas.cz. nsWIVa/SusXOatYzHtX8IPkETOBo90yd Zz8ce1PQhUawbQamihm1di90qJSQJ/i4 QCdL5ulxKJdMv8jLBjIBGe1H08d43luD OmLwkiBNoROZ1apKKabSQAmJTuwIxZCD sqYti2+rEbmOaRC1n96fIH2HWL9pYAnn C0GrvoWEDeVkSHI70Ei9CUCpUNOrPzhS BZc3PrB0DhojkzlXj359e4OhO1gH2QDp 2Uj05suhlIEzQf3lsvvIRUOrMPG2i6Fh j3yThN21+kuVyasrsN5Pv6Dp52U1k60l Y7AuwQwMUXrsdrK3/15DyxSS+pHenpsq cBI3Rb8P4Z6uVxZv9sQ4rQ== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +ddnsb.csas.cz. IN AAAA +SECTION AUTHORITY +csas.cz. 120 IN RRSIG SOA 7 2 120 20190630054550 20190623054550 26663 csas.cz. c/sxvp5WLJOsDGgnhxPOk9oBwXRtsUXa enyWg5rd8yBcquHqfFKQu1v8wEPjJjAH B6joG1tJXq99nxtCN4jlDipPKLIzWxba aYIxE8Pab199qiHrR5vxO4KgPxRXbg5d gzIVrrhB+h23DZDacQ+l+kQWSAcycjPh xFvNjAZrH4obU/F+jHt4ix5XqEVuDrPQ YTGBGEUpl7YbcyaROzVP3dtCQZamkfaB CQYoNAJlNw6YFG1qulPm3pn3jJHhu88f dFgCisWfp7TjMYn7Id7aYJtliagE47xu ASnhOtaeXAVxDGUIkOI5u8uUCC/jAiHr MgARE5rcqh2AIZeSv0BiMQ== +csas.cz. 120 IN SOA ddnsa.csas.cz. domainservices.csas.cz. 2019061320 28800 1800 2592000 120 +kl8ko9vcu5os0r0marmli019btprhvdr.csas.cz. 120 IN NSEC3 1 0 1 31245099125aedf7 kl8ko9vcu5os0r0marmli019btprhvds TXT +kl8ko9vcu5os0r0marmli019btprhvdr.csas.cz. 120 IN RRSIG NSEC3 7 3 120 20190702130338 20190625130338 26663 csas.cz. ebbykapJt+h+uDJlz84Yu4rpx7spx8aY bbR/BCMIVJ6HjziYnwwbBQuyyd6t281+ pdn+2Dxo5tUJ0Kw4oatMPVhID8Psasd3 2Za8bZrouTk8RcQislJCj1jPi2HMIQUI dvJ1aCMC6vn1vI++ZMvcxfptxgmWyL46 i4P3xZAwtrB6hCVrctQ0dbbp1nswh1go YFIf3Hzxu2NHfXZYMdXXb1cLuOZ5L17g UZlqqgcrudLORc2cHj0NcjVT2PscMeoy a/jFNJwCHmkfI3nCAeXmemz/txQUY01u SxBNjkd/y2DVSIgvDPY/Zz0dW4gYdcMG 4T4D43oBTG6w06CgBg1QQw== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +csas.cz. IN A +SECTION ANSWER +csas.cz. 120 IN A 194.50.240.198 +csas.cz. 120 IN A 194.50.240.70 +csas.cz. 120 IN RRSIG A 7 2 120 20190701063211 20190624063211 26663 csas.cz. UXxlRUL6nBVmcrcP4kkryr7lfVxnQHs4 z2WahLZEIaQdf56Iw/+bxddThPD9l3a0 rBCwURI8QMspgPks+7UprWN5TESIifiP W9qJLDuvtD+sVoLgunBAwgi1sE8KfRas asbiISaRpot+3IqJYzYi9X9O5JMdYCuS H9nfayDebVlbC4ChAo5Rcdpkzfb7DwFp gDFKdn+Lmp7IdImSlvJQQT2Mqxpew+gd vMBr5ZN82G8zlsrrHz4n2/kF/SqCYTCG q1WLZUsMvLsP+ha3TAR1t/iG9ayw5Mll 8N2c+AejB2lMwTIlBX+zLlJYT0MoNCyF cMNoCxwjENtDMbq9QZKowA== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +ddnsc.csas.cz. IN A +SECTION ANSWER +ddnsc.csas.cz. 7200 IN A 194.50.240.66 +ddnsc.csas.cz. 7200 IN RRSIG A 7 3 7200 20190630180929 20190623180929 26663 csas.cz. AHQJp3i/UzKmjm/hhmEM1mhV7gPAq2rX 0z4ZOF+ZDyL57nXSHnzGqz4sdd3Q0E1H pmGENdgiOR1Grabo6X3QIp7mZbuAR14T 5TQYHSDoTZ+aTOkqvxCxfUCpEYLVHlbP whE+ntazt0gmkko3eGUBMefMv+JVmuwy tirS7pxlNL1VRnKtbfDsWJ2zmJuur/WK wGzHCMybt4fITB0Og/I9pnwMwykdxhpF bo/MQAa35oTefzvWsD3uFKOhrAc7CP2N MyqDFzv0QDN00s0yFXHHxNg3QFXLAg16 4khZaKT076k+F5N/ufsoIeU/AbByx82T PxpLg8NCh9nNvzgqoCEccw== +SECTION AUTHORITY +csas.cz. 7200 IN NS ddnsa.csas.cz. +csas.cz. 7200 IN NS ddnsb.csas.cz. +csas.cz. 7200 IN NS ddnsc.csas.cz. +csas.cz. 7200 IN NS ddnsd.csas.cz. +csas.cz. 7200 IN RRSIG NS 7 2 7200 20190630180929 20190623180929 26663 csas.cz. PLqM9UHNFbb8GmE+SlzSCjAEKYsklbgt CnWf9G26HkwtacOyJkIHe+T0m94rJZTd 6Mnn32Sw/at6idSvVNGQfE6DIKr2rtK6 lJNe0HPMxYWC9Tr5zpzW1/SjLVooMw6n m8K+18RBI33PBI69UgXyuePnWefdTYOJ g4SK3rR2wUlFxnELEFGfYkytZ6tLi1Cd bTgch93lDnXNBRZ2t/mu5jPyv4NTdKCo WgIMJJcuFdPr/YRVRaemkVEup7qVYImk pa4n+7MMERTL/dw+GxtnZ2YKqyxcepYo rPib3FSTaOFZjEPqNPDNfXXYW+bPmWFP 9ESqUtgmvZ1rMDZbr8lbgQ== +SECTION ADDITIONAL +ddnsa.csas.cz. 7200 IN A 194.50.240.64 +ddnsa.csas.cz. 7200 IN RRSIG A 7 3 7200 20190630180929 20190623180929 26663 csas.cz. QJCNptf+bgoFFzW6vh6lMnevBqaHq1Qk p7UB7hJZfqewhVJsf4/aZlUJg9Ulq990 xe1K+Vw2H7zFKRmS+BhLL0NVS6F/uo3g VWU7UkiLCUdICMNmBlUfv6dTJmovHydF DeCtS8eunKTYKnvSklmr/aLKnFFEaeoi SUF8hkHM2fADZekxqTEMS2ATjlY0rbTb biDd4c1tZdSDeUV7z4B4pHVdgVFBOi6+ kL4MtMM2AY4o9h31jOUGFnfA0cJPmxNN Cx+qgBAl0zKL9+uFrBqB6nYgmd0a1pRa UEQCcVVKkSjDX3gWHtO/nqflFmyDNV4Y OlVLcHVCDNqRtwIRwucUOg== +ddnsb.csas.cz. 7200 IN A 194.50.240.192 +ddnsb.csas.cz. 7200 IN RRSIG A 7 3 7200 20190630180929 20190623180929 26663 csas.cz. CXelkXBmt3nR60+gTwRcTXTVH1OBpfAE McYGfcEmxk8t+wFwLrCfM+6mJUWbV3a8 cIggoBHU8gJxarnIX4K5bXNel2QRzlTa +qTTPPTxH5LwHayDwoo5edk6c2HkCrY3 qChg5SyjuNxxsF4ybqvqIU0d/kM6wqG7 pKPmplD9BNJmzVIVQ8aTBMhdrvIcqK/f 81TGgNxMqJxfpTYKmnA8vVqpJ3C1kbfn no+ux39NY7Wj58NwI+UIcDlmBj414MSz Z4wEfYSgtVnEA8fO+ufZ3jaxjk6NClz+ XMP1gYj/y08bj0h5ACIKJqci2uqPujoi TNBYzQWn6LVPqNIM8O7eSg== +ddnsd.csas.cz. 7200 IN A 194.50.240.194 +ddnsd.csas.cz. 7200 IN RRSIG A 7 3 7200 20190630180929 20190623180929 26663 csas.cz. nsWIVa/SusXOatYzHtX8IPkETOBo90yd Zz8ce1PQhUawbQamihm1di90qJSQJ/i4 QCdL5ulxKJdMv8jLBjIBGe1H08d43luD OmLwkiBNoROZ1apKKabSQAmJTuwIxZCD sqYti2+rEbmOaRC1n96fIH2HWL9pYAnn C0GrvoWEDeVkSHI70Ei9CUCpUNOrPzhS BZc3PrB0DhojkzlXj359e4OhO1gH2QDp 2Uj05suhlIEzQf3lsvvIRUOrMPG2i6Fh j3yThN21+kuVyasrsN5Pv6Dp52U1k60l Y7AuwQwMUXrsdrK3/15DyxSS+pHenpsq cBI3Rb8P4Z6uVxZv9sQ4rQ== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NXDOMAIN +SECTION QUESTION +csas.cz. IN DS +SECTION AUTHORITY +cz. 60 IN SOA cecf53-ant-MS2A-1.csin.cz. hostmaster.cecf53-ant-MS2A-1.csin.cz. 2019061307 10800 3600 604800 60 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +ddnsb.csas.cz. IN NS +SECTION AUTHORITY +csas.cz. 120 IN RRSIG SOA 7 2 120 20190630054550 20190623054550 26663 csas.cz. c/sxvp5WLJOsDGgnhxPOk9oBwXRtsUXa enyWg5rd8yBcquHqfFKQu1v8wEPjJjAH B6joG1tJXq99nxtCN4jlDipPKLIzWxba aYIxE8Pab199qiHrR5vxO4KgPxRXbg5d gzIVrrhB+h23DZDacQ+l+kQWSAcycjPh xFvNjAZrH4obU/F+jHt4ix5XqEVuDrPQ YTGBGEUpl7YbcyaROzVP3dtCQZamkfaB CQYoNAJlNw6YFG1qulPm3pn3jJHhu88f dFgCisWfp7TjMYn7Id7aYJtliagE47xu ASnhOtaeXAVxDGUIkOI5u8uUCC/jAiHr MgARE5rcqh2AIZeSv0BiMQ== +csas.cz. 120 IN SOA ddnsa.csas.cz. domainservices.csas.cz. 2019061320 28800 1800 2592000 120 +kl8ko9vcu5os0r0marmli019btprhvdr.csas.cz. 120 IN NSEC3 1 0 1 31245099125aedf7 kl8ko9vcu5os0r0marmli019btprhvds TXT +kl8ko9vcu5os0r0marmli019btprhvdr.csas.cz. 120 IN RRSIG NSEC3 7 3 120 20190702130338 20190625130338 26663 csas.cz. ebbykapJt+h+uDJlz84Yu4rpx7spx8aY bbR/BCMIVJ6HjziYnwwbBQuyyd6t281+ pdn+2Dxo5tUJ0Kw4oatMPVhID8Psasd3 2Za8bZrouTk8RcQislJCj1jPi2HMIQUI dvJ1aCMC6vn1vI++ZMvcxfptxgmWyL46 i4P3xZAwtrB6hCVrctQ0dbbp1nswh1go YFIf3Hzxu2NHfXZYMdXXb1cLuOZ5L17g UZlqqgcrudLORc2cHj0NcjVT2PscMeoy a/jFNJwCHmkfI3nCAeXmemz/txQUY01u SxBNjkd/y2DVSIgvDPY/Zz0dW4gYdcMG 4T4D43oBTG6w06CgBg1QQw== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +ddnsb.csas.cz. IN A +SECTION ANSWER +ddnsb.csas.cz. 7200 IN A 194.50.240.192 +ddnsb.csas.cz. 7200 IN RRSIG A 7 3 7200 20190630180929 20190623180929 26663 csas.cz. CXelkXBmt3nR60+gTwRcTXTVH1OBpfAE McYGfcEmxk8t+wFwLrCfM+6mJUWbV3a8 cIggoBHU8gJxarnIX4K5bXNel2QRzlTa +qTTPPTxH5LwHayDwoo5edk6c2HkCrY3 qChg5SyjuNxxsF4ybqvqIU0d/kM6wqG7 pKPmplD9BNJmzVIVQ8aTBMhdrvIcqK/f 81TGgNxMqJxfpTYKmnA8vVqpJ3C1kbfn no+ux39NY7Wj58NwI+UIcDlmBj414MSz Z4wEfYSgtVnEA8fO+ufZ3jaxjk6NClz+ XMP1gYj/y08bj0h5ACIKJqci2uqPujoi TNBYzQWn6LVPqNIM8O7eSg== +SECTION AUTHORITY +csas.cz. 7200 IN NS ddnsa.csas.cz. +csas.cz. 7200 IN NS ddnsb.csas.cz. +csas.cz. 7200 IN NS ddnsc.csas.cz. +csas.cz. 7200 IN NS ddnsd.csas.cz. +csas.cz. 7200 IN RRSIG NS 7 2 7200 20190630180929 20190623180929 26663 csas.cz. PLqM9UHNFbb8GmE+SlzSCjAEKYsklbgt CnWf9G26HkwtacOyJkIHe+T0m94rJZTd 6Mnn32Sw/at6idSvVNGQfE6DIKr2rtK6 lJNe0HPMxYWC9Tr5zpzW1/SjLVooMw6n m8K+18RBI33PBI69UgXyuePnWefdTYOJ g4SK3rR2wUlFxnELEFGfYkytZ6tLi1Cd bTgch93lDnXNBRZ2t/mu5jPyv4NTdKCo WgIMJJcuFdPr/YRVRaemkVEup7qVYImk pa4n+7MMERTL/dw+GxtnZ2YKqyxcepYo rPib3FSTaOFZjEPqNPDNfXXYW+bPmWFP 9ESqUtgmvZ1rMDZbr8lbgQ== +SECTION ADDITIONAL +ddnsa.csas.cz. 7200 IN A 194.50.240.64 +ddnsa.csas.cz. 7200 IN RRSIG A 7 3 7200 20190630180929 20190623180929 26663 csas.cz. QJCNptf+bgoFFzW6vh6lMnevBqaHq1Qk p7UB7hJZfqewhVJsf4/aZlUJg9Ulq990 xe1K+Vw2H7zFKRmS+BhLL0NVS6F/uo3g VWU7UkiLCUdICMNmBlUfv6dTJmovHydF DeCtS8eunKTYKnvSklmr/aLKnFFEaeoi SUF8hkHM2fADZekxqTEMS2ATjlY0rbTb biDd4c1tZdSDeUV7z4B4pHVdgVFBOi6+ kL4MtMM2AY4o9h31jOUGFnfA0cJPmxNN Cx+qgBAl0zKL9+uFrBqB6nYgmd0a1pRa UEQCcVVKkSjDX3gWHtO/nqflFmyDNV4Y OlVLcHVCDNqRtwIRwucUOg== +ddnsc.csas.cz. 7200 IN A 194.50.240.66 +ddnsc.csas.cz. 7200 IN RRSIG A 7 3 7200 20190630180929 20190623180929 26663 csas.cz. AHQJp3i/UzKmjm/hhmEM1mhV7gPAq2rX 0z4ZOF+ZDyL57nXSHnzGqz4sdd3Q0E1H pmGENdgiOR1Grabo6X3QIp7mZbuAR14T 5TQYHSDoTZ+aTOkqvxCxfUCpEYLVHlbP whE+ntazt0gmkko3eGUBMefMv+JVmuwy tirS7pxlNL1VRnKtbfDsWJ2zmJuur/WK wGzHCMybt4fITB0Og/I9pnwMwykdxhpF bo/MQAa35oTefzvWsD3uFKOhrAc7CP2N MyqDFzv0QDN00s0yFXHHxNg3QFXLAg16 4khZaKT076k+F5N/ufsoIeU/AbByx82T PxpLg8NCh9nNvzgqoCEccw== +ddnsd.csas.cz. 7200 IN A 194.50.240.194 +ddnsd.csas.cz. 7200 IN RRSIG A 7 3 7200 20190630180929 20190623180929 26663 csas.cz. nsWIVa/SusXOatYzHtX8IPkETOBo90yd Zz8ce1PQhUawbQamihm1di90qJSQJ/i4 QCdL5ulxKJdMv8jLBjIBGe1H08d43luD OmLwkiBNoROZ1apKKabSQAmJTuwIxZCD sqYti2+rEbmOaRC1n96fIH2HWL9pYAnn C0GrvoWEDeVkSHI70Ei9CUCpUNOrPzhS BZc3PrB0DhojkzlXj359e4OhO1gH2QDp 2Uj05suhlIEzQf3lsvvIRUOrMPG2i6Fh j3yThN21+kuVyasrsN5Pv6Dp52U1k60l Y7AuwQwMUXrsdrK3/15DyxSS+pHenpsq cBI3Rb8P4Z6uVxZv9sQ4rQ== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +ddnsd.csas.cz. IN A +SECTION ANSWER +ddnsd.csas.cz. 7200 IN A 194.50.240.194 +ddnsd.csas.cz. 7200 IN RRSIG A 7 3 7200 20190630180929 20190623180929 26663 csas.cz. nsWIVa/SusXOatYzHtX8IPkETOBo90yd Zz8ce1PQhUawbQamihm1di90qJSQJ/i4 QCdL5ulxKJdMv8jLBjIBGe1H08d43luD OmLwkiBNoROZ1apKKabSQAmJTuwIxZCD sqYti2+rEbmOaRC1n96fIH2HWL9pYAnn C0GrvoWEDeVkSHI70Ei9CUCpUNOrPzhS BZc3PrB0DhojkzlXj359e4OhO1gH2QDp 2Uj05suhlIEzQf3lsvvIRUOrMPG2i6Fh j3yThN21+kuVyasrsN5Pv6Dp52U1k60l Y7AuwQwMUXrsdrK3/15DyxSS+pHenpsq cBI3Rb8P4Z6uVxZv9sQ4rQ== +SECTION AUTHORITY +csas.cz. 7200 IN NS ddnsa.csas.cz. +csas.cz. 7200 IN NS ddnsb.csas.cz. +csas.cz. 7200 IN NS ddnsc.csas.cz. +csas.cz. 7200 IN NS ddnsd.csas.cz. +csas.cz. 7200 IN RRSIG NS 7 2 7200 20190630180929 20190623180929 26663 csas.cz. PLqM9UHNFbb8GmE+SlzSCjAEKYsklbgt CnWf9G26HkwtacOyJkIHe+T0m94rJZTd 6Mnn32Sw/at6idSvVNGQfE6DIKr2rtK6 lJNe0HPMxYWC9Tr5zpzW1/SjLVooMw6n m8K+18RBI33PBI69UgXyuePnWefdTYOJ g4SK3rR2wUlFxnELEFGfYkytZ6tLi1Cd bTgch93lDnXNBRZ2t/mu5jPyv4NTdKCo WgIMJJcuFdPr/YRVRaemkVEup7qVYImk pa4n+7MMERTL/dw+GxtnZ2YKqyxcepYo rPib3FSTaOFZjEPqNPDNfXXYW+bPmWFP 9ESqUtgmvZ1rMDZbr8lbgQ== +SECTION ADDITIONAL +ddnsa.csas.cz. 7200 IN A 194.50.240.64 +ddnsa.csas.cz. 7200 IN RRSIG A 7 3 7200 20190630180929 20190623180929 26663 csas.cz. QJCNptf+bgoFFzW6vh6lMnevBqaHq1Qk p7UB7hJZfqewhVJsf4/aZlUJg9Ulq990 xe1K+Vw2H7zFKRmS+BhLL0NVS6F/uo3g VWU7UkiLCUdICMNmBlUfv6dTJmovHydF DeCtS8eunKTYKnvSklmr/aLKnFFEaeoi SUF8hkHM2fADZekxqTEMS2ATjlY0rbTb biDd4c1tZdSDeUV7z4B4pHVdgVFBOi6+ kL4MtMM2AY4o9h31jOUGFnfA0cJPmxNN Cx+qgBAl0zKL9+uFrBqB6nYgmd0a1pRa UEQCcVVKkSjDX3gWHtO/nqflFmyDNV4Y OlVLcHVCDNqRtwIRwucUOg== +ddnsb.csas.cz. 7200 IN A 194.50.240.192 +ddnsb.csas.cz. 7200 IN RRSIG A 7 3 7200 20190630180929 20190623180929 26663 csas.cz. CXelkXBmt3nR60+gTwRcTXTVH1OBpfAE McYGfcEmxk8t+wFwLrCfM+6mJUWbV3a8 cIggoBHU8gJxarnIX4K5bXNel2QRzlTa +qTTPPTxH5LwHayDwoo5edk6c2HkCrY3 qChg5SyjuNxxsF4ybqvqIU0d/kM6wqG7 pKPmplD9BNJmzVIVQ8aTBMhdrvIcqK/f 81TGgNxMqJxfpTYKmnA8vVqpJ3C1kbfn no+ux39NY7Wj58NwI+UIcDlmBj414MSz Z4wEfYSgtVnEA8fO+ufZ3jaxjk6NClz+ XMP1gYj/y08bj0h5ACIKJqci2uqPujoi TNBYzQWn6LVPqNIM8O7eSg== +ddnsc.csas.cz. 7200 IN A 194.50.240.66 +ddnsc.csas.cz. 7200 IN RRSIG A 7 3 7200 20190630180929 20190623180929 26663 csas.cz. AHQJp3i/UzKmjm/hhmEM1mhV7gPAq2rX 0z4ZOF+ZDyL57nXSHnzGqz4sdd3Q0E1H pmGENdgiOR1Grabo6X3QIp7mZbuAR14T 5TQYHSDoTZ+aTOkqvxCxfUCpEYLVHlbP whE+ntazt0gmkko3eGUBMefMv+JVmuwy tirS7pxlNL1VRnKtbfDsWJ2zmJuur/WK wGzHCMybt4fITB0Og/I9pnwMwykdxhpF bo/MQAa35oTefzvWsD3uFKOhrAc7CP2N MyqDFzv0QDN00s0yFXHHxNg3QFXLAg16 4khZaKT076k+F5N/ufsoIeU/AbByx82T PxpLg8NCh9nNvzgqoCEccw== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +ddnsa.csas.cz. IN AAAA +SECTION AUTHORITY +csas.cz. 120 IN RRSIG SOA 7 2 120 20190630054550 20190623054550 26663 csas.cz. c/sxvp5WLJOsDGgnhxPOk9oBwXRtsUXa enyWg5rd8yBcquHqfFKQu1v8wEPjJjAH B6joG1tJXq99nxtCN4jlDipPKLIzWxba aYIxE8Pab199qiHrR5vxO4KgPxRXbg5d gzIVrrhB+h23DZDacQ+l+kQWSAcycjPh xFvNjAZrH4obU/F+jHt4ix5XqEVuDrPQ YTGBGEUpl7YbcyaROzVP3dtCQZamkfaB CQYoNAJlNw6YFG1qulPm3pn3jJHhu88f dFgCisWfp7TjMYn7Id7aYJtliagE47xu ASnhOtaeXAVxDGUIkOI5u8uUCC/jAiHr MgARE5rcqh2AIZeSv0BiMQ== +csas.cz. 120 IN SOA ddnsa.csas.cz. domainservices.csas.cz. 2019061320 28800 1800 2592000 120 +j0hp68elhot3j046r6mr0tnqlns1dbc7.csas.cz. 120 IN NSEC3 1 0 1 31245099125aedf7 j0hp68elhot3j046r6mr0tnqlns1dbc8 TXT +j0hp68elhot3j046r6mr0tnqlns1dbc7.csas.cz. 120 IN RRSIG NSEC3 7 3 120 20190702130338 20190625130338 26663 csas.cz. gUyltGyzdD2L6M29xgqWXMO834aavtFc Si2oAbET0rEJZ5w9ewhEJdVT65MMUUlK wvVX239N7PtsyL+fQvxu5TZ8du0ML8Oi KBp3gao/stajg/lZFd6zVvDWliC3+psL 7PFmD5au5qcrYc6HLc8m8D4hHFPREyd6 1tLGA98RevbQGfrDYODXi6G28vwbxoYU POn4mo82MnVVTZhh58cu7nKRRvKAtay6 VZVWZBOuxy8u+LNsrj3vpDbaOIiWzHd0 pRACsv3LqGpxQNH8avSalWLBvLWchAHj 0T2M+doO8UVaCsyFo/3wF2TXdgFcOAr3 xDIdhHCCoanRVFZ+gFAwIw== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +ddnsa.csas.cz. IN NS +SECTION AUTHORITY +csas.cz. 120 IN RRSIG SOA 7 2 120 20190630054550 20190623054550 26663 csas.cz. c/sxvp5WLJOsDGgnhxPOk9oBwXRtsUXa enyWg5rd8yBcquHqfFKQu1v8wEPjJjAH B6joG1tJXq99nxtCN4jlDipPKLIzWxba aYIxE8Pab199qiHrR5vxO4KgPxRXbg5d gzIVrrhB+h23DZDacQ+l+kQWSAcycjPh xFvNjAZrH4obU/F+jHt4ix5XqEVuDrPQ YTGBGEUpl7YbcyaROzVP3dtCQZamkfaB CQYoNAJlNw6YFG1qulPm3pn3jJHhu88f dFgCisWfp7TjMYn7Id7aYJtliagE47xu ASnhOtaeXAVxDGUIkOI5u8uUCC/jAiHr MgARE5rcqh2AIZeSv0BiMQ== +csas.cz. 120 IN SOA ddnsa.csas.cz. domainservices.csas.cz. 2019061320 28800 1800 2592000 120 +j0hp68elhot3j046r6mr0tnqlns1dbc7.csas.cz. 120 IN NSEC3 1 0 1 31245099125aedf7 j0hp68elhot3j046r6mr0tnqlns1dbc8 TXT +j0hp68elhot3j046r6mr0tnqlns1dbc7.csas.cz. 120 IN RRSIG NSEC3 7 3 120 20190702130338 20190625130338 26663 csas.cz. gUyltGyzdD2L6M29xgqWXMO834aavtFc Si2oAbET0rEJZ5w9ewhEJdVT65MMUUlK wvVX239N7PtsyL+fQvxu5TZ8du0ML8Oi KBp3gao/stajg/lZFd6zVvDWliC3+psL 7PFmD5au5qcrYc6HLc8m8D4hHFPREyd6 1tLGA98RevbQGfrDYODXi6G28vwbxoYU POn4mo82MnVVTZhh58cu7nKRRvKAtay6 VZVWZBOuxy8u+LNsrj3vpDbaOIiWzHd0 pRACsv3LqGpxQNH8avSalWLBvLWchAHj 0T2M+doO8UVaCsyFo/3wF2TXdgFcOAr3 xDIdhHCCoanRVFZ+gFAwIw== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +ddnsc.csas.cz. IN AAAA +SECTION AUTHORITY +c1c03am0n36kncp4iqb4buoqo1ectql4.csas.cz. 120 IN NSEC3 1 0 1 31245099125aedf7 c1c03am0n36kncp4iqb4buoqo1ectql5 TXT +c1c03am0n36kncp4iqb4buoqo1ectql4.csas.cz. 120 IN RRSIG NSEC3 7 3 120 20190702130338 20190625130338 26663 csas.cz. FKjWbcw5aP2HzHFy2t+M462dxM1ph0FW 02Tohh/aJQOp1iKQOi6u1V2tI3H9CMXW xeIAktZ+xiUDRYLX50fNuJZMaSsn7r5A LYjVAKGzwMPncIf6IlEDYkj1I1rdp6z8 LvWsWNO1wfIPq1Dw2geN60bcz3D7r/sY WfjU/7jO9f0nekZZXI2qFsMSYd9Dkaq7 mlpP0NrWnUbCjL+BfuLFNebh8g3D8f5w Tq3R9czwW5YTVXUquBIrjiZ0Ko9INgQ/ 4pvJqJ1bsTpmzdODfAssEtpu+es4wLwt ZTe8Ll9VeDB20RqEnAyHZwQRXXVF2t+F vdJU7Desbpu6rElA7IXwRw== +csas.cz. 120 IN RRSIG SOA 7 2 120 20190630054550 20190623054550 26663 csas.cz. c/sxvp5WLJOsDGgnhxPOk9oBwXRtsUXa enyWg5rd8yBcquHqfFKQu1v8wEPjJjAH B6joG1tJXq99nxtCN4jlDipPKLIzWxba aYIxE8Pab199qiHrR5vxO4KgPxRXbg5d gzIVrrhB+h23DZDacQ+l+kQWSAcycjPh xFvNjAZrH4obU/F+jHt4ix5XqEVuDrPQ YTGBGEUpl7YbcyaROzVP3dtCQZamkfaB CQYoNAJlNw6YFG1qulPm3pn3jJHhu88f dFgCisWfp7TjMYn7Id7aYJtliagE47xu ASnhOtaeXAVxDGUIkOI5u8uUCC/jAiHr MgARE5rcqh2AIZeSv0BiMQ== +csas.cz. 120 IN SOA ddnsa.csas.cz. domainservices.csas.cz. 2019061320 28800 1800 2592000 120 +ENTRY_END + + +ENTRY_BEGIN +MATCH subdomain +ADJUST copy_id copy_query +REPLY QR AA RD NOERROR +SECTION QUESTION +csas.cz. IN NS +SECTION ANSWER +csas.cz. 7200 IN NS ddnsa.csas.cz. +csas.cz. 7200 IN NS ddnsb.csas.cz. +csas.cz. 7200 IN NS ddnsc.csas.cz. +csas.cz. 7200 IN NS ddnsd.csas.cz. +csas.cz. 7200 IN RRSIG NS 7 2 7200 20190630180929 20190623180929 26663 csas.cz. PLqM9UHNFbb8GmE+SlzSCjAEKYsklbgt CnWf9G26HkwtacOyJkIHe+T0m94rJZTd 6Mnn32Sw/at6idSvVNGQfE6DIKr2rtK6 lJNe0HPMxYWC9Tr5zpzW1/SjLVooMw6n m8K+18RBI33PBI69UgXyuePnWefdTYOJ g4SK3rR2wUlFxnELEFGfYkytZ6tLi1Cd bTgch93lDnXNBRZ2t/mu5jPyv4NTdKCo WgIMJJcuFdPr/YRVRaemkVEup7qVYImk pa4n+7MMERTL/dw+GxtnZ2YKqyxcepYo rPib3FSTaOFZjEPqNPDNfXXYW+bPmWFP 9ESqUtgmvZ1rMDZbr8lbgQ== +SECTION ADDITIONAL +ddnsa.csas.cz. 7200 IN A 194.50.240.64 +ddnsa.csas.cz. 7200 IN RRSIG A 7 3 7200 20190630180929 20190623180929 26663 csas.cz. QJCNptf+bgoFFzW6vh6lMnevBqaHq1Qk p7UB7hJZfqewhVJsf4/aZlUJg9Ulq990 xe1K+Vw2H7zFKRmS+BhLL0NVS6F/uo3g VWU7UkiLCUdICMNmBlUfv6dTJmovHydF DeCtS8eunKTYKnvSklmr/aLKnFFEaeoi SUF8hkHM2fADZekxqTEMS2ATjlY0rbTb biDd4c1tZdSDeUV7z4B4pHVdgVFBOi6+ kL4MtMM2AY4o9h31jOUGFnfA0cJPmxNN Cx+qgBAl0zKL9+uFrBqB6nYgmd0a1pRa UEQCcVVKkSjDX3gWHtO/nqflFmyDNV4Y OlVLcHVCDNqRtwIRwucUOg== +ddnsb.csas.cz. 7200 IN A 194.50.240.192 +ddnsb.csas.cz. 7200 IN RRSIG A 7 3 7200 20190630180929 20190623180929 26663 csas.cz. CXelkXBmt3nR60+gTwRcTXTVH1OBpfAE McYGfcEmxk8t+wFwLrCfM+6mJUWbV3a8 cIggoBHU8gJxarnIX4K5bXNel2QRzlTa +qTTPPTxH5LwHayDwoo5edk6c2HkCrY3 qChg5SyjuNxxsF4ybqvqIU0d/kM6wqG7 pKPmplD9BNJmzVIVQ8aTBMhdrvIcqK/f 81TGgNxMqJxfpTYKmnA8vVqpJ3C1kbfn no+ux39NY7Wj58NwI+UIcDlmBj414MSz Z4wEfYSgtVnEA8fO+ufZ3jaxjk6NClz+ XMP1gYj/y08bj0h5ACIKJqci2uqPujoi TNBYzQWn6LVPqNIM8O7eSg== +ddnsc.csas.cz. 7200 IN A 194.50.240.66 +ddnsc.csas.cz. 7200 IN RRSIG A 7 3 7200 20190630180929 20190623180929 26663 csas.cz. AHQJp3i/UzKmjm/hhmEM1mhV7gPAq2rX 0z4ZOF+ZDyL57nXSHnzGqz4sdd3Q0E1H pmGENdgiOR1Grabo6X3QIp7mZbuAR14T 5TQYHSDoTZ+aTOkqvxCxfUCpEYLVHlbP whE+ntazt0gmkko3eGUBMefMv+JVmuwy tirS7pxlNL1VRnKtbfDsWJ2zmJuur/WK wGzHCMybt4fITB0Og/I9pnwMwykdxhpF bo/MQAa35oTefzvWsD3uFKOhrAc7CP2N MyqDFzv0QDN00s0yFXHHxNg3QFXLAg16 4khZaKT076k+F5N/ufsoIeU/AbByx82T PxpLg8NCh9nNvzgqoCEccw== +ddnsd.csas.cz. 7200 IN A 194.50.240.194 +ddnsd.csas.cz. 7200 IN RRSIG A 7 3 7200 20190630180929 20190623180929 26663 csas.cz. nsWIVa/SusXOatYzHtX8IPkETOBo90yd Zz8ce1PQhUawbQamihm1di90qJSQJ/i4 QCdL5ulxKJdMv8jLBjIBGe1H08d43luD OmLwkiBNoROZ1apKKabSQAmJTuwIxZCD sqYti2+rEbmOaRC1n96fIH2HWL9pYAnn C0GrvoWEDeVkSHI70Ei9CUCpUNOrPzhS BZc3PrB0DhojkzlXj359e4OhO1gH2QDp 2Uj05suhlIEzQf3lsvvIRUOrMPG2i6Fh j3yThN21+kuVyasrsN5Pv6Dp52U1k60l Y7AuwQwMUXrsdrK3/15DyxSS+pHenpsq cBI3Rb8P4Z6uVxZv9sQ4rQ== +ENTRY_END + + + + +RANGE_END + + + +; Group's zones: +; com. +; net. +; Server names: +; a.gtld-servers.net. +; j.gtld-servers.net. +; e.gtld-servers.net. +; i.gtld-servers.net. +; d.gtld-servers.net. +; m.gtld-servers.net. +; h.gtld-servers.net. +; c.gtld-servers.net. +; l.gtld-servers.net. +; g.gtld-servers.net. +; b.gtld-servers.net. +; k.gtld-servers.net. +; f.gtld-servers.net. +RANGE_BEGIN 0 1000 + ADDRESS 2001:502:8cc::30 + ADDRESS 2001:503:eea3::30 + ADDRESS 192.5.6.30 + ADDRESS 192.33.14.30 + ADDRESS 192.43.172.30 + ADDRESS 192.12.94.30 + ADDRESS 192.48.79.30 + ADDRESS 2001:502:1ca1::30 + ADDRESS 192.54.112.30 + ADDRESS 2001:500:856e::30 + ADDRESS 2001:503:a83e::2:30 + ADDRESS 192.52.178.30 + ADDRESS 2001:503:d2d::30 + ADDRESS 192.26.92.30 + ADDRESS 192.41.162.30 + ADDRESS 192.31.80.30 + ADDRESS 2001:503:83eb::30 + ADDRESS 192.42.93.30 + ADDRESS 2001:503:39c1::30 + ADDRESS 2001:503:231d::2:30 + ADDRESS 192.35.51.30 + ADDRESS 2001:500:d937::30 + ADDRESS 2001:503:d414::30 + ADDRESS 2001:502:7094::30 + ADDRESS 2001:501:b1f9::30 + ADDRESS 192.55.83.30 + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +nstld.com. IN DS +SECTION AUTHORITY +5V12UURISSGGLPAS52GE1V3R0V7KR5BS.com. 86400 IN NSEC3 1 1 0 - 5v13q049b9ittui4fbdtm34dtev47bgj NS DS RRSIG +5V12UURISSGGLPAS52GE1V3R0V7KR5BS.com. 86400 IN RRSIG NSEC3 8 2 86400 20190630044250 20190623033250 3800 com. NlBOkX84YgGooJJ2vp+Z/O16Vkz6z1gC Jy1Va3fJyEXfZr/GIm8J3KJ6Eq+xxp1A x3ZWxwUjNH3TGUEme7pDrmusqRpx/TZF Za3dVAYIvsaVgIWtDCn4BLBPrg0HXcon HE60KLgWGQmezzFKrNsy5Tzz9TajUSgQ uOTQHB7TFDg= +CK0POJMG874LJREF7EFN8430QVIT8BSM.com. 86400 IN NSEC3 1 1 0 - ck0q1gin43n1arrc9osm6qpqr81h5m9a NS SOA RRSIG DNSKEY NSEC3PARAM +CK0POJMG874LJREF7EFN8430QVIT8BSM.com. 86400 IN RRSIG NSEC3 8 2 86400 20190702044530 20190625033530 3800 com. BMWhk6USUrb7TNJ3dSObn8QEPWvpzedW kP9i4RRPz7V9c2yVPmHSPt29QKsHOYTg iF4uyHt+K8RK8DBIziB19nrOjbb8iRDY 7m5koEj/OrS2peMuF6rqRRmkI6SY/ACy XHb/hRGE4tZd2gThNb7evZkk02IVuhEo njO3NBjCrsw= +com. 900 IN RRSIG SOA 8 1 900 20190702140912 20190625125912 3800 com. c6kEiaL24fAOXZf4AexmGfQejNxB+Na5 Omftkar0u1SFkd7I03ieGTsJt/aiFVND ucQklNtxUqTC4SQpGLmNYlzIDRvb2vYC GL7tSomPB/Tt4xYczPvNIOJKQOtFAjoL hJn5hj1jCo56iBD5OmbLwQcq8XI/W7xs yvTMfjKpeEQ= +com. 900 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1561471752 1800 900 604800 86400 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +root-servers.net. IN DS +SECTION AUTHORITY +A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN NSEC3 1 1 0 - a1ruuffjkct2q54p78f8ejgj8jbk7i8b NS SOA RRSIG DNSKEY NSEC3PARAM +A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN RRSIG NSEC3 8 2 86400 20190702055539 20190625044539 2129 net. KgEWjLXuYdPBhlNh2epfnKU879hg4GGf F4m4Fo7ZW+S3uhNqq8pF7sMJCvSs8ZZA Ctcm88XThCeIAxDxLZ5moOlMIWaweVX3 7B7s3mw+3K0cnkYF6J4FqBO8o7x6J3i2 c6kxI3q0DKub23GcXpWZu3x2yIkWuN0I ZgceikGAPpM= +T2UF21DR03E0BNPB42UQMVUF38P2TA8D.net. 86400 IN NSEC3 1 1 0 - t2ukct9k5i0uhv7b3m3na6jaigdjm0gr NS DS RRSIG +T2UF21DR03E0BNPB42UQMVUF38P2TA8D.net. 86400 IN RRSIG NSEC3 8 2 86400 20190630054523 20190623043523 2129 net. adpeQf+K6Hr5amhmF9DNK56LVWWnfGHK Dz9t9e7swG/P+4clc4yMKmUvK0OF9aTA kBzDwnzeB+FT+nsg1cWdibs4559AKQCL RtNErPhZRMojUFo6TLM9lQoXhv9i5rZB AJGmirHgSwiAvaUK7tgxX3nr1Atahyew iwHDQIVPhw4= +net. 900 IN RRSIG SOA 8 1 900 20190702140916 20190625125916 2129 net. cMaAZOeBq2v9urE/L5RZEBRSTmY4/MMJ ADm+GUhaqP3FQ0RD3fO6IU7qdP3jCmlZ QmCl68ZVaMyZvVIcNcv7fokWbqTm9r4j 2NB0W9j/B/ZIOzCV4myDqaylU62tNTUA okGZMzclPCSd2n8ZDHZ3RysudhiZ/8WG FVdsHHtp67c= +net. 900 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1561471756 1800 900 604800 86400 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +net. IN DS +SECTION AUTHORITY +A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN NSEC3 1 1 0 - a1ruuffjkct2q54p78f8ejgj8jbk7i8b NS SOA RRSIG DNSKEY NSEC3PARAM +A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN RRSIG NSEC3 8 2 86400 20190702055539 20190625044539 2129 net. KgEWjLXuYdPBhlNh2epfnKU879hg4GGf F4m4Fo7ZW+S3uhNqq8pF7sMJCvSs8ZZA Ctcm88XThCeIAxDxLZ5moOlMIWaweVX3 7B7s3mw+3K0cnkYF6J4FqBO8o7x6J3i2 c6kxI3q0DKub23GcXpWZu3x2yIkWuN0I ZgceikGAPpM= +net. 900 IN RRSIG SOA 8 1 900 20190702140916 20190625125916 2129 net. cMaAZOeBq2v9urE/L5RZEBRSTmY4/MMJ ADm+GUhaqP3FQ0RD3fO6IU7qdP3jCmlZ QmCl68ZVaMyZvVIcNcv7fokWbqTm9r4j 2NB0W9j/B/ZIOzCV4myDqaylU62tNTUA okGZMzclPCSd2n8ZDHZ3RysudhiZ/8WG FVdsHHtp67c= +net. 900 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1561471756 1800 900 604800 86400 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +com. IN DS +SECTION AUTHORITY +CK0POJMG874LJREF7EFN8430QVIT8BSM.com. 86400 IN NSEC3 1 1 0 - ck0q1gin43n1arrc9osm6qpqr81h5m9a NS SOA RRSIG DNSKEY NSEC3PARAM +CK0POJMG874LJREF7EFN8430QVIT8BSM.com. 86400 IN RRSIG NSEC3 8 2 86400 20190702044530 20190625033530 3800 com. BMWhk6USUrb7TNJ3dSObn8QEPWvpzedW kP9i4RRPz7V9c2yVPmHSPt29QKsHOYTg iF4uyHt+K8RK8DBIziB19nrOjbb8iRDY 7m5koEj/OrS2peMuF6rqRRmkI6SY/ACy XHb/hRGE4tZd2gThNb7evZkk02IVuhEo njO3NBjCrsw= +com. 900 IN RRSIG SOA 8 1 900 20190702140912 20190625125912 3800 com. c6kEiaL24fAOXZf4AexmGfQejNxB+Na5 Omftkar0u1SFkd7I03ieGTsJt/aiFVND ucQklNtxUqTC4SQpGLmNYlzIDRvb2vYC GL7tSomPB/Tt4xYczPvNIOJKQOtFAjoL hJn5hj1jCo56iBD5OmbLwQcq8XI/W7xs yvTMfjKpeEQ= +com. 900 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1561471752 1800 900 604800 86400 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +net. IN DNSKEY +SECTION ANSWER +net. 86400 IN DNSKEY 256 3 8 AQPB6mADOfWU6hTegl+pTl1wtjTll+Zn WIuRb8+cU9XNWNPpc3mX+rUqPeiw0RpK kD4QkTqTefWjMqjXMvYn2TxX4V4FMQXY MJr0bgiZXGosA9C9Aa7RgYyt4GJCToXq lM+mTqBjXDkh7yBa8I2X3p6Tt/PoKhiq CtmNl6sggTanJw== +net. 86400 IN DNSKEY 257 3 8 AQOYBnzqWXIEj6mlgXg4LWC0HP2n8eK8 XqgHlmJ/69iuIHsa1TrHDG6TcOra/pye GKwH0nKZhTmXSuUFGh9BCNiwVDuyyb6O BGy2Nte9Kr8NwWg4q+zhSoOf4D+gC9dE zg0yFdwT0DKEvmNPt0K4jbQDS4Yimb+u PKuF6yieWWrPYYCrv8C9KC8JMze2uT6N uWBfsl2fDUoV4l65qMww06D7n+p7Rbdw WkAZ0fA63mXVXBZF6kpDtsYD7SUB9jhh fLQE/r85bvg3FaSs5Wi2BaqN06SzGWI1 DHu7axthIOeHwg00zxlhTpoYCH0ldoQz +S65zWYi/fRJiyLSBb6JZOvn +net. 86400 IN RRSIG DNSKEY 8 1 86400 20190704153857 20190619153357 35886 net. T0Dcg9EVsoYzu+hywAW4mW11X/+oW/zt 0tQ8TdsKgY/5+/RL0tZCoU1EqbkBfM5h PxZgupQ0SQ//haveGagTgXOAOpa2nmUa o26dJC0xyZRTHuViy5708DONc88JZt+U pAgFcmJYjTXLu2QNywtyCXhcjnCuYAVG lTrarCn0rzW7PZp94xyGTCegkK9TysCt aqnIS0z/LVA2PjF+5etoEl7nWxCa1iUY 7as3zQ8CYlSDLwf7Sx4pUA/4hLZftwdO gNvb/pUdy7Zy/+6SGS3K4CPFkWG9kfs/ /76iMG6GkV2loGJApIFtYlui5iFaNfIM YBTD29dqC2I9ofe4WJnmpg== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +com. IN DNSKEY +SECTION ANSWER +com. 86400 IN DNSKEY 256 3 8 AQOcWJruwENIaapq3h3lKJlflLx6rWJz 2aKRBV6CBJRTxZu4ge2iDX164dQ7Hpc9 b8C9z2gqWyx1XyjvbQisgSid24voeUHQ gD0weedVDQjD6B9By7zMI6Yz+cM/w6H8 zB8ZRn6qZuWZAxFOjKvzoz+3vf32tyZE 7QmLFnXDgQpFxw== +com. 86400 IN DNSKEY 257 3 8 AQPDzldNmMvZFX4NcNJ0uEnKDg7tmv/F 3MyQR0lpBmVcNcsIszxNFxsBfKNW9JYC Yqpik8366LE7VbIcNRzfp2h9OO8HRl+H +E08zauK8k7evWEmu/6od+2boggPoiEf GNyvNPaSI7FOIroDsnw/taggzHRX1Z7S OiOiPWPNIwSUyWOZ79VmcQ1GLkC6NlYv G3HwYmynQv6oFwGv/KELSw7ZSdrbTQ0H XvZbqMUI7BaMskmvgm1G7oKZ1YiF7O9i oVNc0+7ASbqmZN7Z98EGU/Qh2K/BgUe8 Hs0XVcdPKrtyYnoQHd2ynKPcMMlTEih2 /2HDHjRPJ2aywIpKNnv4oPo/ +com. 86400 IN RRSIG DNSKEY 8 1 86400 20190706182533 20190621182033 30909 com. v4xbl08Oyis8hHQ/7k9DuSLa75uMshqc V4783gXEEq1+dbgwBct8I2Td4+9mjIql fD1UO6Wu6VSYUtef39VQe4DuIWqtRrh/ WA/EOrrEY5Qieem9LTPL/zHWGQiILaHz CUHk1iUGFyZy4IRnZPkigTYKm1M7ZRXv qTTYSLemVZsna2/siDVYAdAhXWvJHQGl DkT3WDAvryFPpuEOrhUWkfhWAGy/ZsmD 87OiTX50TG/3ISEqFyzow/GZjViZnG5C tBmwdX/E8CIN6lVJV3LU3bMRYmuAHYEE lM+lVKAEyooxuRBNOdtZHD9tM8V6cfyd t89aE9LaiSn1DsFjUqFu/A== +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +gtld-servers.net. IN DS +SECTION AUTHORITY +5QDO29Q2PS5ARBKHB6R0BRN6NDSTSN90.net. 86400 IN NSEC3 1 1 0 - 5qdppotuk27kkp9ligtrb0k1cbvm9cim NS DS RRSIG +5QDO29Q2PS5ARBKHB6R0BRN6NDSTSN90.net. 86400 IN RRSIG NSEC3 8 2 86400 20190629054715 20190622043715 2129 net. fvRKJkn9D2H3DFGE0zpk/tAkyjaoEZY4 4TkN6XtCAibjzQS8R0SCaXmZlQQujnjG wp0Rjhy0gMQaFUrJ8QuUbJSDZQaicAl7 pCESVKd95GZOSgi07BXSKwdUMAPROSjz gDkYGzHcUB5Kfe23gkL2eQ05YVigHYhI l0YCHui1wHE= +A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN NSEC3 1 1 0 - a1ruuffjkct2q54p78f8ejgj8jbk7i8b NS SOA RRSIG DNSKEY NSEC3PARAM +A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN RRSIG NSEC3 8 2 86400 20190702055539 20190625044539 2129 net. KgEWjLXuYdPBhlNh2epfnKU879hg4GGf F4m4Fo7ZW+S3uhNqq8pF7sMJCvSs8ZZA Ctcm88XThCeIAxDxLZ5moOlMIWaweVX3 7B7s3mw+3K0cnkYF6J4FqBO8o7x6J3i2 c6kxI3q0DKub23GcXpWZu3x2yIkWuN0I ZgceikGAPpM= +net. 900 IN RRSIG SOA 8 1 900 20190702140916 20190625125916 2129 net. cMaAZOeBq2v9urE/L5RZEBRSTmY4/MMJ ADm+GUhaqP3FQ0RD3fO6IU7qdP3jCmlZ QmCl68ZVaMyZvVIcNcv7fokWbqTm9r4j 2NB0W9j/B/ZIOzCV4myDqaylU62tNTUA okGZMzclPCSd2n8ZDHZ3RysudhiZ/8WG FVdsHHtp67c= +net. 900 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1561471756 1800 900 604800 86400 +ENTRY_END + + +ENTRY_BEGIN +MATCH subdomain +ADJUST copy_id copy_query +REPLY QR RD NOERROR +SECTION QUESTION +gtld-servers.net. IN NS +SECTION AUTHORITY +5QDO29Q2PS5ARBKHB6R0BRN6NDSTSN90.net. 86400 IN NSEC3 1 1 0 - 5qdppotuk27kkp9ligtrb0k1cbvm9cim NS DS RRSIG +5QDO29Q2PS5ARBKHB6R0BRN6NDSTSN90.net. 86400 IN RRSIG NSEC3 8 2 86400 20190629054715 20190622043715 2129 net. fvRKJkn9D2H3DFGE0zpk/tAkyjaoEZY4 4TkN6XtCAibjzQS8R0SCaXmZlQQujnjG wp0Rjhy0gMQaFUrJ8QuUbJSDZQaicAl7 pCESVKd95GZOSgi07BXSKwdUMAPROSjz gDkYGzHcUB5Kfe23gkL2eQ05YVigHYhI l0YCHui1wHE= +A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN NSEC3 1 1 0 - a1ruuffjkct2q54p78f8ejgj8jbk7i8b NS SOA RRSIG DNSKEY NSEC3PARAM +A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN RRSIG NSEC3 8 2 86400 20190702055539 20190625044539 2129 net. KgEWjLXuYdPBhlNh2epfnKU879hg4GGf F4m4Fo7ZW+S3uhNqq8pF7sMJCvSs8ZZA Ctcm88XThCeIAxDxLZ5moOlMIWaweVX3 7B7s3mw+3K0cnkYF6J4FqBO8o7x6J3i2 c6kxI3q0DKub23GcXpWZu3x2yIkWuN0I ZgceikGAPpM= +gtld-servers.net. 172800 IN NS av1.nstld.com. +gtld-servers.net. 172800 IN NS av2.nstld.com. +gtld-servers.net. 172800 IN NS av3.nstld.com. +gtld-servers.net. 172800 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH subdomain +ADJUST copy_id copy_query +REPLY QR RD NOERROR +SECTION QUESTION +root-servers.net. IN NS +SECTION AUTHORITY +A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN NSEC3 1 1 0 - a1ruuffjkct2q54p78f8ejgj8jbk7i8b NS SOA RRSIG DNSKEY NSEC3PARAM +A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN RRSIG NSEC3 8 2 86400 20190702055539 20190625044539 2129 net. KgEWjLXuYdPBhlNh2epfnKU879hg4GGf F4m4Fo7ZW+S3uhNqq8pF7sMJCvSs8ZZA Ctcm88XThCeIAxDxLZ5moOlMIWaweVX3 7B7s3mw+3K0cnkYF6J4FqBO8o7x6J3i2 c6kxI3q0DKub23GcXpWZu3x2yIkWuN0I ZgceikGAPpM= +T2UF21DR03E0BNPB42UQMVUF38P2TA8D.net. 86400 IN NSEC3 1 1 0 - t2ukct9k5i0uhv7b3m3na6jaigdjm0gr NS DS RRSIG +T2UF21DR03E0BNPB42UQMVUF38P2TA8D.net. 86400 IN RRSIG NSEC3 8 2 86400 20190630054523 20190623043523 2129 net. adpeQf+K6Hr5amhmF9DNK56LVWWnfGHK Dz9t9e7swG/P+4clc4yMKmUvK0OF9aTA kBzDwnzeB+FT+nsg1cWdibs4559AKQCL RtNErPhZRMojUFo6TLM9lQoXhv9i5rZB AJGmirHgSwiAvaUK7tgxX3nr1Atahyew iwHDQIVPhw4= +root-servers.net. 172800 IN NS a.root-servers.net. +root-servers.net. 172800 IN NS b.root-servers.net. +root-servers.net. 172800 IN NS c.root-servers.net. +root-servers.net. 172800 IN NS d.root-servers.net. +root-servers.net. 172800 IN NS e.root-servers.net. +root-servers.net. 172800 IN NS f.root-servers.net. +root-servers.net. 172800 IN NS g.root-servers.net. +root-servers.net. 172800 IN NS h.root-servers.net. +root-servers.net. 172800 IN NS i.root-servers.net. +root-servers.net. 172800 IN NS j.root-servers.net. +root-servers.net. 172800 IN NS k.root-servers.net. +root-servers.net. 172800 IN NS l.root-servers.net. +root-servers.net. 172800 IN NS m.root-servers.net. +SECTION ADDITIONAL +a.root-servers.net. 172800 IN A 198.41.0.4 +a.root-servers.net. 172800 IN AAAA 2001:503:ba3e::2:30 +b.root-servers.net. 172800 IN A 199.9.14.201 +b.root-servers.net. 172800 IN AAAA 2001:500:200::b +c.root-servers.net. 172800 IN A 192.33.4.12 +c.root-servers.net. 172800 IN AAAA 2001:500:2::c +d.root-servers.net. 172800 IN A 199.7.91.13 +d.root-servers.net. 172800 IN AAAA 2001:500:2d::d +e.root-servers.net. 172800 IN A 192.203.230.10 +e.root-servers.net. 172800 IN AAAA 2001:500:a8::e +f.root-servers.net. 172800 IN A 192.5.5.241 +f.root-servers.net. 172800 IN AAAA 2001:500:2f::f +g.root-servers.net. 172800 IN A 192.112.36.4 +g.root-servers.net. 172800 IN AAAA 2001:500:12::d0d +h.root-servers.net. 172800 IN A 198.97.190.53 +h.root-servers.net. 172800 IN AAAA 2001:500:1::53 +i.root-servers.net. 172800 IN A 192.36.148.17 +i.root-servers.net. 172800 IN AAAA 2001:7fe::53 +j.root-servers.net. 172800 IN A 192.58.128.30 +j.root-servers.net. 172800 IN AAAA 2001:503:c27::2:30 +k.root-servers.net. 172800 IN A 193.0.14.129 +k.root-servers.net. 172800 IN AAAA 2001:7fd::1 +l.root-servers.net. 172800 IN A 199.7.83.42 +l.root-servers.net. 172800 IN AAAA 2001:500:9f::42 +m.root-servers.net. 172800 IN A 202.12.27.33 +m.root-servers.net. 172800 IN AAAA 2001:dc3::35 +ENTRY_END + + +ENTRY_BEGIN +MATCH subdomain +ADJUST copy_id copy_query +REPLY QR RD NOERROR +SECTION QUESTION +nstld.com. IN NS +SECTION AUTHORITY +5V12UURISSGGLPAS52GE1V3R0V7KR5BS.com. 86400 IN NSEC3 1 1 0 - 5v13q049b9ittui4fbdtm34dtev47bgj NS DS RRSIG +5V12UURISSGGLPAS52GE1V3R0V7KR5BS.com. 86400 IN RRSIG NSEC3 8 2 86400 20190630044250 20190623033250 3800 com. NlBOkX84YgGooJJ2vp+Z/O16Vkz6z1gC Jy1Va3fJyEXfZr/GIm8J3KJ6Eq+xxp1A x3ZWxwUjNH3TGUEme7pDrmusqRpx/TZF Za3dVAYIvsaVgIWtDCn4BLBPrg0HXcon HE60KLgWGQmezzFKrNsy5Tzz9TajUSgQ uOTQHB7TFDg= +CK0POJMG874LJREF7EFN8430QVIT8BSM.com. 86400 IN NSEC3 1 1 0 - ck0q1gin43n1arrc9osm6qpqr81h5m9a NS SOA RRSIG DNSKEY NSEC3PARAM +CK0POJMG874LJREF7EFN8430QVIT8BSM.com. 86400 IN RRSIG NSEC3 8 2 86400 20190702044530 20190625033530 3800 com. BMWhk6USUrb7TNJ3dSObn8QEPWvpzedW kP9i4RRPz7V9c2yVPmHSPt29QKsHOYTg iF4uyHt+K8RK8DBIziB19nrOjbb8iRDY 7m5koEj/OrS2peMuF6rqRRmkI6SY/ACy XHb/hRGE4tZd2gThNb7evZkk02IVuhEo njO3NBjCrsw= +nstld.com. 172800 IN NS av1.nstld.com. +nstld.com. 172800 IN NS av2.nstld.com. +nstld.com. 172800 IN NS av3.nstld.com. +nstld.com. 172800 IN NS av4.nstld.com. +SECTION ADDITIONAL +av1.nstld.com. 172800 IN A 192.42.177.30 +av1.nstld.com. 172800 IN AAAA 2001:500:124::30 +av2.nstld.com. 172800 IN A 192.42.178.30 +av2.nstld.com. 172800 IN AAAA 2001:500:125::30 +av3.nstld.com. 172800 IN A 192.82.133.30 +av3.nstld.com. 172800 IN AAAA 2001:500:126::30 +av4.nstld.com. 172800 IN A 192.82.134.30 +av4.nstld.com. 172800 IN AAAA 2001:500:127::30 +ENTRY_END + + +ENTRY_BEGIN +MATCH subdomain +ADJUST copy_id copy_query +REPLY QR AA RD NOERROR +SECTION QUESTION +net. IN NS +SECTION ANSWER +net. 172800 IN NS a.gtld-servers.net. +net. 172800 IN NS b.gtld-servers.net. +net. 172800 IN NS c.gtld-servers.net. +net. 172800 IN NS d.gtld-servers.net. +net. 172800 IN NS e.gtld-servers.net. +net. 172800 IN NS f.gtld-servers.net. +net. 172800 IN NS g.gtld-servers.net. +net. 172800 IN NS h.gtld-servers.net. +net. 172800 IN NS i.gtld-servers.net. +net. 172800 IN NS j.gtld-servers.net. +net. 172800 IN NS k.gtld-servers.net. +net. 172800 IN NS l.gtld-servers.net. +net. 172800 IN NS m.gtld-servers.net. +net. 172800 IN RRSIG NS 8 1 172800 20190630055417 20190623044417 2129 net. WUAzfzoslC7YpzfY7qJ+vPaYpL/TN1fq Ak97qaEsQbPEka9AfUyL/ZKgGucOrDmB e0GK55jGT1B1XXiQasdlB8/SThSPm+Oc V/aQ8zUopPJ6gzCCRfEZOWCRvRbXa3am f6apMdig+NSxXgYQpVjZmka8XX8xDJar 3c4G5gZS9rA= +SECTION ADDITIONAL +a.gtld-servers.net. 172800 IN A 192.5.6.30 +a.gtld-servers.net. 172800 IN AAAA 2001:503:a83e::2:30 +b.gtld-servers.net. 172800 IN A 192.33.14.30 +b.gtld-servers.net. 172800 IN AAAA 2001:503:231d::2:30 +c.gtld-servers.net. 172800 IN A 192.26.92.30 +c.gtld-servers.net. 172800 IN AAAA 2001:503:83eb::30 +d.gtld-servers.net. 172800 IN A 192.31.80.30 +d.gtld-servers.net. 172800 IN AAAA 2001:500:856e::30 +e.gtld-servers.net. 172800 IN A 192.12.94.30 +e.gtld-servers.net. 172800 IN AAAA 2001:502:1ca1::30 +f.gtld-servers.net. 172800 IN A 192.35.51.30 +f.gtld-servers.net. 172800 IN AAAA 2001:503:d414::30 +g.gtld-servers.net. 172800 IN A 192.42.93.30 +g.gtld-servers.net. 172800 IN AAAA 2001:503:eea3::30 +h.gtld-servers.net. 172800 IN A 192.54.112.30 +h.gtld-servers.net. 172800 IN AAAA 2001:502:8cc::30 +i.gtld-servers.net. 172800 IN A 192.43.172.30 +i.gtld-servers.net. 172800 IN AAAA 2001:503:39c1::30 +j.gtld-servers.net. 172800 IN A 192.48.79.30 +j.gtld-servers.net. 172800 IN AAAA 2001:502:7094::30 +k.gtld-servers.net. 172800 IN A 192.52.178.30 +k.gtld-servers.net. 172800 IN AAAA 2001:503:d2d::30 +l.gtld-servers.net. 172800 IN A 192.41.162.30 +l.gtld-servers.net. 172800 IN AAAA 2001:500:d937::30 +m.gtld-servers.net. 172800 IN A 192.55.83.30 +m.gtld-servers.net. 172800 IN AAAA 2001:501:b1f9::30 +ENTRY_END + + +ENTRY_BEGIN +MATCH subdomain +ADJUST copy_id copy_query +REPLY QR AA RD NOERROR +SECTION QUESTION +com. IN NS +SECTION ANSWER +com. 172800 IN NS a.gtld-servers.net. +com. 172800 IN NS b.gtld-servers.net. +com. 172800 IN NS c.gtld-servers.net. +com. 172800 IN NS d.gtld-servers.net. +com. 172800 IN NS e.gtld-servers.net. +com. 172800 IN NS f.gtld-servers.net. +com. 172800 IN NS g.gtld-servers.net. +com. 172800 IN NS h.gtld-servers.net. +com. 172800 IN NS i.gtld-servers.net. +com. 172800 IN NS j.gtld-servers.net. +com. 172800 IN NS k.gtld-servers.net. +com. 172800 IN NS l.gtld-servers.net. +com. 172800 IN NS m.gtld-servers.net. +com. 172800 IN RRSIG NS 8 1 172800 20190702044530 20190625033530 3800 com. F4MEGl13z9h8iv22sko8S8/JjSskipqt COrmfpx4p5XeRutUOpr/6wAEdSXWJ7yu 5Z/PMTBDjTo37WxfEhyvvYyiOLTzYOf+ AvFeeGDAxdOAlfjQ+6etggDPz1bsqIhE 2FX5i7dhfk0WYshBIUqCJmmPMdnVgLNN Bh4zYoT5irQ= +SECTION ADDITIONAL +a.gtld-servers.net. 172800 IN A 192.5.6.30 +a.gtld-servers.net. 172800 IN AAAA 2001:503:a83e::2:30 +b.gtld-servers.net. 172800 IN A 192.33.14.30 +b.gtld-servers.net. 172800 IN AAAA 2001:503:231d::2:30 +c.gtld-servers.net. 172800 IN A 192.26.92.30 +c.gtld-servers.net. 172800 IN AAAA 2001:503:83eb::30 +d.gtld-servers.net. 172800 IN A 192.31.80.30 +d.gtld-servers.net. 172800 IN AAAA 2001:500:856e::30 +e.gtld-servers.net. 172800 IN A 192.12.94.30 +e.gtld-servers.net. 172800 IN AAAA 2001:502:1ca1::30 +f.gtld-servers.net. 172800 IN A 192.35.51.30 +f.gtld-servers.net. 172800 IN AAAA 2001:503:d414::30 +g.gtld-servers.net. 172800 IN A 192.42.93.30 +g.gtld-servers.net. 172800 IN AAAA 2001:503:eea3::30 +h.gtld-servers.net. 172800 IN A 192.54.112.30 +h.gtld-servers.net. 172800 IN AAAA 2001:502:8cc::30 +i.gtld-servers.net. 172800 IN A 192.43.172.30 +i.gtld-servers.net. 172800 IN AAAA 2001:503:39c1::30 +j.gtld-servers.net. 172800 IN A 192.48.79.30 +j.gtld-servers.net. 172800 IN AAAA 2001:502:7094::30 +k.gtld-servers.net. 172800 IN A 192.52.178.30 +k.gtld-servers.net. 172800 IN AAAA 2001:503:d2d::30 +l.gtld-servers.net. 172800 IN A 192.41.162.30 +l.gtld-servers.net. 172800 IN AAAA 2001:500:d937::30 +m.gtld-servers.net. 172800 IN A 192.55.83.30 +m.gtld-servers.net. 172800 IN AAAA 2001:501:b1f9::30 +ENTRY_END + + + + +RANGE_END + + + +; Group's zones: +; gtld-servers.net. +; nstld.com. +; Server names: +; av1.nstld.com. +; av2.nstld.com. +; av3.nstld.com. +; av4.nstld.com. +RANGE_BEGIN 0 1000 + ADDRESS 192.42.178.30 + ADDRESS 2001:500:125::30 + ADDRESS 192.82.133.30 + ADDRESS 192.42.177.30 + ADDRESS 2001:500:124::30 + ADDRESS 192.82.134.30 + ADDRESS 2001:500:126::30 + ADDRESS 2001:500:127::30 + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +h.gtld-servers.net. IN A +SECTION ANSWER +h.gtld-servers.net. 86400 IN A 192.54.112.30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +i.gtld-servers.net. IN AAAA +SECTION ANSWER +i.gtld-servers.net. 86400 IN AAAA 2001:503:39c1::30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +av3.nstld.com. IN A +SECTION ANSWER +av3.nstld.com. 300 IN A 192.82.133.30 +SECTION AUTHORITY +nstld.com. 86400 IN NS av1.nstld.com. +nstld.com. 86400 IN NS av2.nstld.com. +nstld.com. 86400 IN NS av3.nstld.com. +nstld.com. 86400 IN NS av4.nstld.com. +SECTION ADDITIONAL +av1.nstld.com. 300 IN A 192.42.177.30 +av1.nstld.com. 300 IN AAAA 2001:500:124::30 +av2.nstld.com. 300 IN A 192.42.178.30 +av2.nstld.com. 300 IN AAAA 2001:500:125::30 +av3.nstld.com. 300 IN AAAA 2001:500:126::30 +av4.nstld.com. 300 IN A 192.82.134.30 +av4.nstld.com. 300 IN AAAA 2001:500:127::30 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +av1.nstld.com. IN A +SECTION ANSWER +av1.nstld.com. 300 IN A 192.42.177.30 +SECTION AUTHORITY +nstld.com. 86400 IN NS av1.nstld.com. +nstld.com. 86400 IN NS av2.nstld.com. +nstld.com. 86400 IN NS av3.nstld.com. +nstld.com. 86400 IN NS av4.nstld.com. +SECTION ADDITIONAL +av1.nstld.com. 300 IN AAAA 2001:500:124::30 +av2.nstld.com. 300 IN A 192.42.178.30 +av2.nstld.com. 300 IN AAAA 2001:500:125::30 +av3.nstld.com. 300 IN A 192.82.133.30 +av3.nstld.com. 300 IN AAAA 2001:500:126::30 +av4.nstld.com. 300 IN A 192.82.134.30 +av4.nstld.com. 300 IN AAAA 2001:500:127::30 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +j.gtld-servers.net. IN AAAA +SECTION ANSWER +j.gtld-servers.net. 86400 IN AAAA 2001:502:7094::30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +av2.nstld.com. IN AAAA +SECTION ANSWER +av2.nstld.com. 300 IN AAAA 2001:500:125::30 +SECTION AUTHORITY +nstld.com. 86400 IN NS av1.nstld.com. +nstld.com. 86400 IN NS av2.nstld.com. +nstld.com. 86400 IN NS av3.nstld.com. +nstld.com. 86400 IN NS av4.nstld.com. +SECTION ADDITIONAL +av1.nstld.com. 300 IN A 192.42.177.30 +av1.nstld.com. 300 IN AAAA 2001:500:124::30 +av2.nstld.com. 300 IN A 192.42.178.30 +av3.nstld.com. 300 IN A 192.82.133.30 +av3.nstld.com. 300 IN AAAA 2001:500:126::30 +av4.nstld.com. 300 IN A 192.82.134.30 +av4.nstld.com. 300 IN AAAA 2001:500:127::30 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +k.gtld-servers.net. IN AAAA +SECTION ANSWER +k.gtld-servers.net. 86400 IN AAAA 2001:503:d2d::30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +av1.nstld.com. IN NS +SECTION AUTHORITY +nstld.com. 86400 IN SOA av4.nstld.com. nstld.verisign-grs.com. 2018073100 7200 900 1209600 86400 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +f.gtld-servers.net. IN AAAA +SECTION ANSWER +f.gtld-servers.net. 86400 IN AAAA 2001:503:d414::30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +j.gtld-servers.net. IN NS +SECTION AUTHORITY +gtld-servers.net. 86400 IN SOA av4.nstld.com. nstld.verisign-grs.com. 2017061500 3600 900 1209600 86400 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +c.gtld-servers.net. IN NS +SECTION AUTHORITY +gtld-servers.net. 86400 IN SOA av4.nstld.com. nstld.verisign-grs.com. 2017061500 3600 900 1209600 86400 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +l.gtld-servers.net. IN AAAA +SECTION ANSWER +l.gtld-servers.net. 86400 IN AAAA 2001:500:d937::30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +b.gtld-servers.net. IN NS +SECTION AUTHORITY +gtld-servers.net. 86400 IN SOA av4.nstld.com. nstld.verisign-grs.com. 2017061500 3600 900 1209600 86400 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +f.gtld-servers.net. IN A +SECTION ANSWER +f.gtld-servers.net. 86400 IN A 192.35.51.30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +e.gtld-servers.net. IN NS +SECTION AUTHORITY +gtld-servers.net. 86400 IN SOA av4.nstld.com. nstld.verisign-grs.com. 2017061500 3600 900 1209600 86400 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +i.gtld-servers.net. IN A +SECTION ANSWER +i.gtld-servers.net. 86400 IN A 192.43.172.30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +d.gtld-servers.net. IN AAAA +SECTION ANSWER +d.gtld-servers.net. 86400 IN AAAA 2001:500:856e::30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +m.gtld-servers.net. IN NS +SECTION AUTHORITY +gtld-servers.net. 86400 IN SOA av4.nstld.com. nstld.verisign-grs.com. 2017061500 3600 900 1209600 86400 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +av3.nstld.com. IN NS +SECTION AUTHORITY +nstld.com. 86400 IN SOA av4.nstld.com. nstld.verisign-grs.com. 2018073100 7200 900 1209600 86400 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +g.gtld-servers.net. IN A +SECTION ANSWER +g.gtld-servers.net. 86400 IN A 192.42.93.30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +j.gtld-servers.net. IN A +SECTION ANSWER +j.gtld-servers.net. 86400 IN A 192.48.79.30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +f.gtld-servers.net. IN NS +SECTION AUTHORITY +gtld-servers.net. 86400 IN SOA av4.nstld.com. nstld.verisign-grs.com. 2017061500 3600 900 1209600 86400 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +av2.nstld.com. IN NS +SECTION AUTHORITY +nstld.com. 86400 IN SOA av4.nstld.com. nstld.verisign-grs.com. 2018073100 7200 900 1209600 86400 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +h.gtld-servers.net. IN NS +SECTION AUTHORITY +gtld-servers.net. 86400 IN SOA av4.nstld.com. nstld.verisign-grs.com. 2017061500 3600 900 1209600 86400 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +d.gtld-servers.net. IN NS +SECTION AUTHORITY +gtld-servers.net. 86400 IN SOA av4.nstld.com. nstld.verisign-grs.com. 2017061500 3600 900 1209600 86400 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +av1.nstld.com. IN AAAA +SECTION ANSWER +av1.nstld.com. 300 IN AAAA 2001:500:124::30 +SECTION AUTHORITY +nstld.com. 86400 IN NS av1.nstld.com. +nstld.com. 86400 IN NS av2.nstld.com. +nstld.com. 86400 IN NS av3.nstld.com. +nstld.com. 86400 IN NS av4.nstld.com. +SECTION ADDITIONAL +av1.nstld.com. 300 IN A 192.42.177.30 +av2.nstld.com. 300 IN A 192.42.178.30 +av2.nstld.com. 300 IN AAAA 2001:500:125::30 +av3.nstld.com. 300 IN A 192.82.133.30 +av3.nstld.com. 300 IN AAAA 2001:500:126::30 +av4.nstld.com. 300 IN A 192.82.134.30 +av4.nstld.com. 300 IN AAAA 2001:500:127::30 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +k.gtld-servers.net. IN A +SECTION ANSWER +k.gtld-servers.net. 86400 IN A 192.52.178.30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +h.gtld-servers.net. IN AAAA +SECTION ANSWER +h.gtld-servers.net. 86400 IN AAAA 2001:502:8cc::30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +a.gtld-servers.net. IN AAAA +SECTION ANSWER +a.gtld-servers.net. 86400 IN AAAA 2001:503:a83e::2:30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +l.gtld-servers.net. IN NS +SECTION AUTHORITY +gtld-servers.net. 86400 IN SOA av4.nstld.com. nstld.verisign-grs.com. 2017061500 3600 900 1209600 86400 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +b.gtld-servers.net. IN AAAA +SECTION ANSWER +b.gtld-servers.net. 86400 IN AAAA 2001:503:231d::2:30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +l.gtld-servers.net. IN A +SECTION ANSWER +l.gtld-servers.net. 86400 IN A 192.41.162.30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +d.gtld-servers.net. IN A +SECTION ANSWER +d.gtld-servers.net. 86400 IN A 192.31.80.30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +e.gtld-servers.net. IN AAAA +SECTION ANSWER +e.gtld-servers.net. 86400 IN AAAA 2001:502:1ca1::30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +k.gtld-servers.net. IN NS +SECTION AUTHORITY +gtld-servers.net. 86400 IN SOA av4.nstld.com. nstld.verisign-grs.com. 2017061500 3600 900 1209600 86400 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +av3.nstld.com. IN AAAA +SECTION ANSWER +av3.nstld.com. 300 IN AAAA 2001:500:126::30 +SECTION AUTHORITY +nstld.com. 86400 IN NS av1.nstld.com. +nstld.com. 86400 IN NS av2.nstld.com. +nstld.com. 86400 IN NS av3.nstld.com. +nstld.com. 86400 IN NS av4.nstld.com. +SECTION ADDITIONAL +av1.nstld.com. 300 IN A 192.42.177.30 +av1.nstld.com. 300 IN AAAA 2001:500:124::30 +av2.nstld.com. 300 IN A 192.42.178.30 +av2.nstld.com. 300 IN AAAA 2001:500:125::30 +av3.nstld.com. 300 IN A 192.82.133.30 +av4.nstld.com. 300 IN A 192.82.134.30 +av4.nstld.com. 300 IN AAAA 2001:500:127::30 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +a.gtld-servers.net. IN A +SECTION ANSWER +a.gtld-servers.net. 86400 IN A 192.5.6.30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +av4.nstld.com. IN AAAA +SECTION ANSWER +av4.nstld.com. 300 IN AAAA 2001:500:127::30 +SECTION AUTHORITY +nstld.com. 86400 IN NS av1.nstld.com. +nstld.com. 86400 IN NS av2.nstld.com. +nstld.com. 86400 IN NS av3.nstld.com. +nstld.com. 86400 IN NS av4.nstld.com. +SECTION ADDITIONAL +av1.nstld.com. 300 IN A 192.42.177.30 +av1.nstld.com. 300 IN AAAA 2001:500:124::30 +av2.nstld.com. 300 IN A 192.42.178.30 +av2.nstld.com. 300 IN AAAA 2001:500:125::30 +av3.nstld.com. 300 IN A 192.82.133.30 +av3.nstld.com. 300 IN AAAA 2001:500:126::30 +av4.nstld.com. 300 IN A 192.82.134.30 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +g.gtld-servers.net. IN AAAA +SECTION ANSWER +g.gtld-servers.net. 86400 IN AAAA 2001:503:eea3::30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +c.gtld-servers.net. IN A +SECTION ANSWER +c.gtld-servers.net. 86400 IN A 192.26.92.30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +av4.nstld.com. IN NS +SECTION AUTHORITY +nstld.com. 86400 IN SOA av4.nstld.com. nstld.verisign-grs.com. 2018073100 7200 900 1209600 86400 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +a.gtld-servers.net. IN NS +SECTION AUTHORITY +gtld-servers.net. 86400 IN SOA av4.nstld.com. nstld.verisign-grs.com. 2017061500 3600 900 1209600 86400 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +m.gtld-servers.net. IN AAAA +SECTION ANSWER +m.gtld-servers.net. 86400 IN AAAA 2001:501:b1f9::30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +m.gtld-servers.net. IN A +SECTION ANSWER +m.gtld-servers.net. 86400 IN A 192.55.83.30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +i.gtld-servers.net. IN NS +SECTION AUTHORITY +gtld-servers.net. 86400 IN SOA av4.nstld.com. nstld.verisign-grs.com. 2017061500 3600 900 1209600 86400 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +g.gtld-servers.net. IN NS +SECTION AUTHORITY +gtld-servers.net. 86400 IN SOA av4.nstld.com. nstld.verisign-grs.com. 2017061500 3600 900 1209600 86400 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +c.gtld-servers.net. IN AAAA +SECTION ANSWER +c.gtld-servers.net. 86400 IN AAAA 2001:503:83eb::30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +av2.nstld.com. IN A +SECTION ANSWER +av2.nstld.com. 300 IN A 192.42.178.30 +SECTION AUTHORITY +nstld.com. 86400 IN NS av1.nstld.com. +nstld.com. 86400 IN NS av2.nstld.com. +nstld.com. 86400 IN NS av3.nstld.com. +nstld.com. 86400 IN NS av4.nstld.com. +SECTION ADDITIONAL +av1.nstld.com. 300 IN A 192.42.177.30 +av1.nstld.com. 300 IN AAAA 2001:500:124::30 +av2.nstld.com. 300 IN AAAA 2001:500:125::30 +av3.nstld.com. 300 IN A 192.82.133.30 +av3.nstld.com. 300 IN AAAA 2001:500:126::30 +av4.nstld.com. 300 IN A 192.82.134.30 +av4.nstld.com. 300 IN AAAA 2001:500:127::30 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +av4.nstld.com. IN A +SECTION ANSWER +av4.nstld.com. 300 IN A 192.82.134.30 +SECTION AUTHORITY +nstld.com. 86400 IN NS av1.nstld.com. +nstld.com. 86400 IN NS av2.nstld.com. +nstld.com. 86400 IN NS av3.nstld.com. +nstld.com. 86400 IN NS av4.nstld.com. +SECTION ADDITIONAL +av1.nstld.com. 300 IN A 192.42.177.30 +av1.nstld.com. 300 IN AAAA 2001:500:124::30 +av2.nstld.com. 300 IN A 192.42.178.30 +av2.nstld.com. 300 IN AAAA 2001:500:125::30 +av3.nstld.com. 300 IN A 192.82.133.30 +av3.nstld.com. 300 IN AAAA 2001:500:126::30 +av4.nstld.com. 300 IN AAAA 2001:500:127::30 +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +b.gtld-servers.net. IN A +SECTION ANSWER +b.gtld-servers.net. 86400 IN A 192.33.14.30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR AA RD NOERROR +SECTION QUESTION +e.gtld-servers.net. IN A +SECTION ANSWER +e.gtld-servers.net. 86400 IN A 192.12.94.30 +SECTION AUTHORITY +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH subdomain +ADJUST copy_id copy_query +REPLY QR AA RD NOERROR +SECTION QUESTION +gtld-servers.net. IN NS +SECTION ANSWER +gtld-servers.net. 86400 IN NS av1.nstld.com. +gtld-servers.net. 86400 IN NS av2.nstld.com. +gtld-servers.net. 86400 IN NS av3.nstld.com. +gtld-servers.net. 86400 IN NS av4.nstld.com. +ENTRY_END + + +ENTRY_BEGIN +MATCH subdomain +ADJUST copy_id copy_query +REPLY QR AA RD NOERROR +SECTION QUESTION +nstld.com. IN NS +SECTION ANSWER +nstld.com. 86400 IN NS av1.nstld.com. +nstld.com. 86400 IN NS av2.nstld.com. +nstld.com. 86400 IN NS av3.nstld.com. +nstld.com. 86400 IN NS av4.nstld.com. +SECTION ADDITIONAL +av1.nstld.com. 300 IN A 192.42.177.30 +av1.nstld.com. 300 IN AAAA 2001:500:124::30 +av2.nstld.com. 300 IN A 192.42.178.30 +av2.nstld.com. 300 IN AAAA 2001:500:125::30 +av3.nstld.com. 300 IN A 192.82.133.30 +av3.nstld.com. 300 IN AAAA 2001:500:126::30 +av4.nstld.com. 300 IN A 192.82.134.30 +av4.nstld.com. 300 IN AAAA 2001:500:127::30 +ENTRY_END + + + + +RANGE_END + + + + + +; Sequence of queries made by browser + +; 1st query for AAAA +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD AD +SECTION QUESTION +csas.cz. IN AAAA +ENTRY_END + +; answer for AAAA contains minimally covering NSEC3 answer with incorrect bitmap +; it claims that csas.cz A RR is not present +STEP 11 CHECK_ANSWER +ENTRY_BEGIN +MATCH opcode flags rcode question answer +REPLY QR RD RA AD NOERROR +SECTION QUESTION +csas.cz. IN AAAA +SECTION AUTHORITY +csas.cz. 119 IN SOA ddnsa.csas.cz. domainservices.csas.cz. 2019061320 28800 1800 2592000 120 +ENTRY_END + +; 2nd query for A +STEP 21 QUERY +ENTRY_BEGIN +REPLY RD AD +SECTION QUESTION +csas.cz. IN A +ENTRY_END + +; check that A query gets an IP address +; this answer would be empty +; if minimally covering NSEC3 was not exempt from aggressive caching +STEP 22 CHECK_ANSWER +ENTRY_BEGIN +MATCH opcode flags rcode question answer +REPLY QR RD RA AD NOERROR +SECTION QUESTION +csas.cz. IN A +SECTION ANSWER +csas.cz. 120 IN A 194.50.240.198 +csas.cz. 120 IN A 194.50.240.70 +ENTRY_END + +SCENARIO_END diff --git a/lib/cache/test.integr/deckard.yaml b/lib/cache/test.integr/deckard.yaml new file mode 100644 index 0000000..df88f83 --- /dev/null +++ b/lib/cache/test.integr/deckard.yaml @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +programs: +- name: kresd + binary: kresd + additional: + - --noninteractive + templates: + - lib/cache/test.integr/kresd_config.j2 + - tests/integration/hints_zone.j2 + configs: + - config + - hints +noclean: True diff --git a/lib/cache/test.integr/kresd_config.j2 b/lib/cache/test.integr/kresd_config.j2 new file mode 100644 index 0000000..c4c286f --- /dev/null +++ b/lib/cache/test.integr/kresd_config.j2 @@ -0,0 +1,69 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +{% for TAF in TRUST_ANCHOR_FILES %} +-- trust_anchors.add_file('{{TAF}}') +{% endfor %} + +{% raw %} +-- Disable RFC5011 TA update +if ta_update then + modules.unload('ta_update') +end + +-- Disable RFC8145 signaling, scenario doesn't provide expected answers +if ta_signal_query then + modules.unload('ta_signal_query') +end + +-- Disable RFC8109 priming, scenario doesn't provide expected answers +if priming then + modules.unload('priming') +end + +-- Disable this module because it make one priming query +if detect_time_skew then + modules.unload('detect_time_skew') +end + +_hint_root_file('hints') +cache.size = 2*MB +log_level('debug') +policy.add(policy.all(policy.DEBUG_ALWAYS)) +{% endraw %} + +net = { '{{SELF_ADDR}}' } + +{% if DO_IP6 == "true" %} +net.ipv6 = true +{% else %} +net.ipv6 = false +{% endif %} + +{% if DO_IP4 == "true" %} +net.ipv4 = true +{% else %} +net.ipv4 = false +{% endif %} + +{% if QMIN == "false" %} +option('NO_MINIMIZE', true) +{% else %} +option('NO_MINIMIZE', false) +{% endif %} + + +-- Self-checks on globals +assert(help() ~= nil) +assert(worker.id ~= nil) +-- Self-checks on facilities +assert(cache.count() == 0) +assert(cache.stats() ~= nil) +assert(cache.backends() ~= nil) +assert(worker.stats() ~= nil) +assert(net.interfaces() ~= nil) +-- Self-checks on loaded stuff +assert(net.list()[1].transport.ip == '{{SELF_ADDR}}') +assert(#modules.list() > 0) +-- Self-check timers +ev = event.recurrent(1 * sec, function (ev) return 1 end) +event.cancel(ev) +ev = event.after(0, function (ev) return 1 end) diff --git a/lib/cache/util.h b/lib/cache/util.h new file mode 100644 index 0000000..3f81830 --- /dev/null +++ b/lib/cache/util.h @@ -0,0 +1,4 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +#include <libknot/packet/pkt.h> + +uint32_t packet_ttl(const knot_pkt_t *pkt); diff --git a/lib/cookies/alg_containers.c b/lib/cookies/alg_containers.c new file mode 100644 index 0000000..1da0bda --- /dev/null +++ b/lib/cookies/alg_containers.c @@ -0,0 +1,59 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <stdint.h> +#include <stdlib.h> + +#include <libknot/cookies/alg-fnv64.h> + +#include "lib/cookies/alg_containers.h" +#include "lib/cookies/alg_sha.h" + +const struct knot_cc_alg *kr_cc_alg_get(int id) +{ + /* + * Client algorithm identifiers are used to index this array of + * pointers. + */ + static const struct knot_cc_alg *const cc_algs[] = { + /* 0 */ &knot_cc_alg_fnv64, + /* 1 */ &knot_cc_alg_hmac_sha256_64 + }; + + if (id >= 0 && id < 2) { + return cc_algs[id]; + } + + return NULL; +} + +const knot_lookup_t kr_cc_alg_names[] = { + { 0, "FNV-64" }, + { 1, "HMAC-SHA256-64" }, + { -1, NULL } +}; + +const struct knot_sc_alg *kr_sc_alg_get(int id) +{ + /* + * Server algorithm identifiers are used to index this array of + * pointers. + */ + static const struct knot_sc_alg *const sc_algs[] = { + /* 0 */ &knot_sc_alg_fnv64, + /* 1 */ &knot_sc_alg_hmac_sha256_64 + }; + + if (id >= 0 && id < 2) { + return sc_algs[id]; + } + + return NULL; +} + +const knot_lookup_t kr_sc_alg_names[] = { + { 0, "FNV-64" }, + { 1, "HMAC-SHA256-64" }, + { -1, NULL } +}; diff --git a/lib/cookies/alg_containers.h b/lib/cookies/alg_containers.h new file mode 100644 index 0000000..5764c28 --- /dev/null +++ b/lib/cookies/alg_containers.h @@ -0,0 +1,37 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <libknot/cookies/client.h> +#include <libknot/cookies/server.h> +#include <libknot/lookup.h> + +#include "lib/defines.h" + +/** + * @brief Returns pointer to client cookie algorithm. + * + * @param id algorithm identifier as defined by lookup table + * @return pointer to algorithm structure with given id or NULL on error + */ +KR_EXPORT +const struct knot_cc_alg *kr_cc_alg_get(int id); + +/** Binds client algorithm identifiers onto names. */ +KR_EXPORT +extern const knot_lookup_t kr_cc_alg_names[]; + +/** + * @brief Returns pointer to server cookie algorithm. + * + * @param id algorithm identifier as defined by lookup table + * @return pointer to algorithm structure with given id or NULL on error + */ +KR_EXPORT +const struct knot_sc_alg *kr_sc_alg_get(int id); + +/** Binds server algorithm identifiers onto names. */ +KR_EXPORT +extern const knot_lookup_t kr_sc_alg_names[]; diff --git a/lib/cookies/alg_sha.c b/lib/cookies/alg_sha.c new file mode 100644 index 0000000..34e79c3 --- /dev/null +++ b/lib/cookies/alg_sha.c @@ -0,0 +1,110 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <nettle/hmac.h> +#include <stdint.h> +#include <stdlib.h> + +#include <libknot/errcode.h> +#include <libknot/rrtype/opt-cookie.h> + +#include "lib/cookies/alg_sha.h" +#include "lib/utils.h" + +/** + * @brief Update hash value. + * + * @param ctx HMAC-SHA256 context to be updated. + * @param sa Socket address. + */ +static inline void update_hash(struct hmac_sha256_ctx *ctx, + const struct sockaddr *sa) +{ + if (kr_fails_assert(ctx && sa)) + return; + + int addr_len = kr_inaddr_len(sa); + const uint8_t *addr = (uint8_t *)kr_inaddr(sa); + + if (addr && addr_len > 0) { + hmac_sha256_update(ctx, addr_len, addr); + } +} + +/** + * @brief Compute client cookie using HMAC-SHA256-64. + * @note At least one of the arguments must be non-null. + * @param input input parameters + * @param cc_out buffer for computed client cookie + * @param cc_len buffer size + * @return Non-zero size of written data on success, 0 in case of a failure. + */ +static uint16_t cc_gen_hmac_sha256_64(const struct knot_cc_input *input, + uint8_t *cc_out, uint16_t cc_len) +{ + if (!knot_cc_input_is_valid(input) || + !cc_out || cc_len < KNOT_OPT_COOKIE_CLNT) { + return 0; + } + + struct hmac_sha256_ctx ctx; + hmac_sha256_set_key(&ctx, input->secret_len, input->secret_data); + + if (input->clnt_sockaddr) { + update_hash(&ctx, input->clnt_sockaddr); + } + + if (input->srvr_sockaddr) { + update_hash(&ctx, input->srvr_sockaddr); + } + + /* KNOT_OPT_COOKIE_CLNT <= SHA256_DIGEST_SIZE */ + + hmac_sha256_digest(&ctx, KNOT_OPT_COOKIE_CLNT, cc_out); + + return KNOT_OPT_COOKIE_CLNT; +} + +#define SRVR_HMAC_SHA256_64_HASH_SIZE 8 + +/** + * @brief Compute server cookie hash using HMAC-SHA256-64). + * @note Server cookie = nonce | time | HMAC-SHA256-64( server secret, client cookie | nonce| time | client IP ) + * @param input data to compute cookie from + * @param hash_out hash output buffer + * @param hash_len buffer size + * @return Non-zero size of written data on success, 0 in case of a failure. + */ +static uint16_t sc_gen_hmac_sha256_64(const struct knot_sc_input *input, + uint8_t *hash_out, uint16_t hash_len) +{ + if (!knot_sc_input_is_valid(input) || + !hash_out || hash_len < SRVR_HMAC_SHA256_64_HASH_SIZE) { + return 0; + } + + struct hmac_sha256_ctx ctx; + hmac_sha256_set_key(&ctx, input->srvr_data->secret_len, + input->srvr_data->secret_data); + + hmac_sha256_update(&ctx, input->cc_len, input->cc); + + if (input->nonce && input->nonce_len) { + hmac_sha256_update(&ctx, input->nonce_len, input->nonce); + } + + if (input->srvr_data->clnt_sockaddr) { + update_hash(&ctx, input->srvr_data->clnt_sockaddr); + } + + /* SRVR_HMAC_SHA256_64_HASH_SIZE < SHA256_DIGEST_SIZE */ + + hmac_sha256_digest(&ctx, SRVR_HMAC_SHA256_64_HASH_SIZE, hash_out); + + return SRVR_HMAC_SHA256_64_HASH_SIZE; +} + +const struct knot_cc_alg knot_cc_alg_hmac_sha256_64 = { KNOT_OPT_COOKIE_CLNT, cc_gen_hmac_sha256_64 }; + +const struct knot_sc_alg knot_sc_alg_hmac_sha256_64 = { SRVR_HMAC_SHA256_64_HASH_SIZE, sc_gen_hmac_sha256_64 }; diff --git a/lib/cookies/alg_sha.h b/lib/cookies/alg_sha.h new file mode 100644 index 0000000..e97972a --- /dev/null +++ b/lib/cookies/alg_sha.h @@ -0,0 +1,18 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <libknot/cookies/client.h> +#include <libknot/cookies/server.h> + +#include "lib/defines.h" + +/* These structures are not meant to be part of public interface. */ + +/** HMAC-SHA256-64 client cookie algorithm. */ +extern const struct knot_cc_alg knot_cc_alg_hmac_sha256_64; + +/** HMAC-SHA256-64 server cookie algorithm. */ +extern const struct knot_sc_alg knot_sc_alg_hmac_sha256_64; diff --git a/lib/cookies/control.h b/lib/cookies/control.h new file mode 100644 index 0000000..475b3fd --- /dev/null +++ b/lib/cookies/control.h @@ -0,0 +1,37 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +#include "lib/defines.h" + +/** Holds secret quantity. */ +struct kr_cookie_secret { + size_t size; /*!< Secret quantity size. */ + uint8_t data[]; /*!< Secret quantity data. */ +}; + +/** Holds settings that have direct influence on cookie values computation. */ +struct kr_cookie_comp { + struct kr_cookie_secret *secr; /*!< Secret data. */ + int alg_id; /*!< Cookie algorithm identifier. */ +}; + +/** Holds settings that control client/server cookie behaviour. */ +struct kr_cookie_settings { + bool enabled; /**< Enable/disables DNS cookies functionality. */ + + struct kr_cookie_comp current; /**< Current cookie settings. */ + struct kr_cookie_comp recent; /**< Recent cookie settings. */ +}; + +/** DNS cookies controlling structure. */ +struct kr_cookie_ctx { + struct kr_cookie_settings clnt; /**< Client settings. */ + struct kr_cookie_settings srvr; /**< Server settings. */ +}; diff --git a/lib/cookies/helper.c b/lib/cookies/helper.c new file mode 100644 index 0000000..4867817 --- /dev/null +++ b/lib/cookies/helper.c @@ -0,0 +1,268 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <libknot/rrtype/opt.h> +#include <libknot/rrtype/opt-cookie.h> + +#include "lib/cookies/helper.h" +#include "lib/defines.h" + +/** + * @brief Check whether there is a cached cookie that matches the current + * client cookie. + */ +static const uint8_t *peek_and_check_cc(kr_cookie_lru_t *cache, const void *sa, + const uint8_t *cc, uint16_t cc_len) +{ + if (kr_fails_assert(cache && sa && cc && cc_len)) + return NULL; + + const uint8_t *cached_opt = kr_cookie_lru_get(cache, sa); + if (!cached_opt) + return NULL; + + const uint8_t *cached_cc = knot_edns_opt_get_data((uint8_t *) cached_opt); + + if (cc_len == KNOT_OPT_COOKIE_CLNT && + 0 == memcmp(cc, cached_cc, cc_len)) { + return cached_opt; + } + + return NULL; +} + +/** + * @brief Put a client cookie into the RR Set. + */ +static int opt_rr_put_cookie(knot_rrset_t *opt_rr, uint8_t *data, + uint16_t data_len, knot_mm_t *mm) +{ + if (kr_fails_assert(opt_rr && data && data_len > 0)) + return kr_error(EINVAL); + + const uint8_t *cc = NULL, *sc = NULL; + uint16_t cc_len = 0, sc_len = 0; + + int ret = knot_edns_opt_cookie_parse(data, data_len, &cc, &cc_len, + &sc, &sc_len); + if (ret != KNOT_EOK) + return kr_error(EINVAL); + if (kr_fails_assert(data_len == cc_len + sc_len)) + return kr_error(EINVAL); + + uint16_t cookies_size = data_len; + uint8_t *cookies_data = NULL; + + ret = knot_edns_reserve_unique_option(opt_rr, KNOT_EDNS_OPTION_COOKIE, + cookies_size, &cookies_data, mm); + if (ret != KNOT_EOK) + return kr_error(EINVAL); + if (kr_fails_assert(cookies_data)) + return kr_error(EINVAL); + + cookies_size = knot_edns_opt_cookie_write(cc, cc_len, sc, sc_len, + cookies_data, cookies_size); + if (cookies_size == 0) + return kr_error(EINVAL); + if (kr_fails_assert(cookies_size == data_len)) + return kr_error(EINVAL); + + return kr_ok(); +} + +/** + * @brief Puts entire EDNS option into the RR Set. + */ +static int opt_rr_put_cookie_opt(knot_rrset_t *opt_rr, uint8_t *option, knot_mm_t *mm) +{ + if (kr_fails_assert(opt_rr && option)) + return kr_error(EINVAL); + + uint16_t opt_code = knot_edns_opt_get_code(option); + if (opt_code != KNOT_EDNS_OPTION_COOKIE) + return kr_error(EINVAL); + + uint16_t opt_len = knot_edns_opt_get_length(option); + uint8_t *opt_data = knot_edns_opt_get_data(option); + if (!opt_data || opt_len == 0) + return kr_error(EINVAL); + + return opt_rr_put_cookie(opt_rr, opt_data, opt_len, mm); +} + +int kr_request_put_cookie(const struct kr_cookie_comp *clnt_comp, + kr_cookie_lru_t *cookie_cache, + const struct sockaddr *clnt_sa, + const struct sockaddr *srvr_sa, + struct kr_request *req) +{ + if (!clnt_comp || !req) + return kr_error(EINVAL); + + if (!req->ctx->opt_rr) + return kr_ok(); + + if (!clnt_comp->secr || (clnt_comp->alg_id < 0) || !cookie_cache) + return kr_error(EINVAL); + + /* + * Generate client cookie from client address, server address and + * secret quantity. + */ + struct knot_cc_input input = { + .clnt_sockaddr = clnt_sa, + .srvr_sockaddr = srvr_sa, + .secret_data = clnt_comp->secr->data, + .secret_len = clnt_comp->secr->size + }; + uint8_t cc[KNOT_OPT_COOKIE_CLNT]; + uint16_t cc_len = KNOT_OPT_COOKIE_CLNT; + const struct knot_cc_alg *cc_alg = kr_cc_alg_get(clnt_comp->alg_id); + if (!cc_alg) + return kr_error(EINVAL); + if (kr_fails_assert(cc_alg->gen_func)) + return kr_error(EINVAL); + cc_len = cc_alg->gen_func(&input, cc, cc_len); + if (cc_len != KNOT_OPT_COOKIE_CLNT) + return kr_error(EINVAL); + + const uint8_t *cached_cookie = peek_and_check_cc(cookie_cache, + srvr_sa, cc, cc_len); + + /* Add cookie option. */ + int ret; + if (cached_cookie) { + ret = opt_rr_put_cookie_opt(req->ctx->opt_rr, + (uint8_t *)cached_cookie, + req->ctx->pool); + } else { + ret = opt_rr_put_cookie(req->ctx->opt_rr, cc, cc_len, + req->ctx->pool); + } + + return ret; +} + +int kr_answer_write_cookie(struct knot_sc_input *sc_input, + const struct kr_nonce_input *nonce, + const struct knot_sc_alg *alg, knot_pkt_t *pkt) +{ + if (!sc_input || !sc_input->cc || sc_input->cc_len == 0) + return kr_error(EINVAL); + + if (!sc_input->srvr_data || !sc_input->srvr_data->clnt_sockaddr || + !sc_input->srvr_data->secret_data || + !sc_input->srvr_data->secret_len) { + return kr_error(EINVAL); + } + + if (!nonce) + return kr_error(EINVAL); + + if (!alg || !alg->hash_size || !alg->hash_func) + return kr_error(EINVAL); + + if (!pkt || !pkt->opt_rr) + return kr_error(EINVAL); + + uint16_t nonce_len = KR_NONCE_LEN; + uint16_t hash_len = alg->hash_size; + + /* + * Space for cookie is reserved inside the EDNS OPT RR of + * the answer packet. + */ + uint8_t *cookie = NULL; + uint16_t cookie_len = knot_edns_opt_cookie_data_len(sc_input->cc_len, + nonce_len + hash_len); + if (cookie_len == 0) + return kr_error(EINVAL); + + int ret = knot_edns_reserve_unique_option(pkt->opt_rr, + KNOT_EDNS_OPTION_COOKIE, + cookie_len, &cookie, + &pkt->mm); + if (ret != KNOT_EOK) + return kr_error(ENOMEM); + if (kr_fails_assert(cookie)) + return kr_error(EFAULT); + + /* + * Function knot_edns_opt_cookie_data_len() returns the sum of its + * parameters or zero. Anyway, let's check again. + */ + if (cookie_len < (sc_input->cc_len + nonce_len + hash_len)) + return kr_error(EINVAL); + + /* Copy client cookie data portion. */ + memcpy(cookie, sc_input->cc, sc_input->cc_len); + + if (nonce_len) { + /* Write nonce data portion. */ + kr_nonce_write_wire(cookie + sc_input->cc_len, nonce_len, + nonce); + /* Adjust input for written nonce value. */ + sc_input->nonce = cookie + sc_input->cc_len; + sc_input->nonce_len = nonce_len; + } + + hash_len = alg->hash_func(sc_input, + cookie + sc_input->cc_len + nonce_len, + hash_len); + /* Zero nonce values. */ + sc_input->nonce = NULL; + sc_input->nonce_len = 0; + + return (hash_len != 0) ? kr_ok() : kr_error(EINVAL); +} + +int kr_pkt_set_ext_rcode(knot_pkt_t *pkt, uint16_t whole_rcode) +{ + /* + * RFC6891 6.1.3 -- extended RCODE forms the upper 8 bits of whole + * 12-bit RCODE (together with the 4 bits of 'normal' RCODE). + * + * | 11 10 09 08 07 06 05 04 | 03 02 01 00 | + * | 12-bit whole RCODE | + * | 8-bit extended RCODE | 4-bit RCODE | + */ + + if (!pkt || !knot_pkt_has_edns(pkt)) + return kr_error(EINVAL); + + uint8_t rcode = whole_rcode & 0x0f; + uint8_t ext_rcode = whole_rcode >> 4; + knot_wire_set_rcode(pkt->wire, rcode); + knot_edns_set_ext_rcode(pkt->opt_rr, ext_rcode); + + return kr_ok(); +} + +uint8_t *kr_no_question_cookie_query(const knot_pkt_t *pkt) +{ + if (!pkt || knot_wire_get_qdcount(pkt->wire) > 0) + return false; + + if (knot_wire_get_qr(pkt->wire) != 0 || !pkt->opt_rr) + return false; + + return knot_edns_get_option(pkt->opt_rr, KNOT_EDNS_OPTION_COOKIE); +} + +int kr_parse_cookie_opt(uint8_t *cookie_opt, struct knot_dns_cookies *cookies) +{ + if (!cookie_opt || !cookies) + return kr_error(EINVAL); + + const uint8_t *cookie_data = knot_edns_opt_get_data(cookie_opt); + uint16_t cookie_len = knot_edns_opt_get_length(cookie_opt); + if (!cookie_data || cookie_len == 0) + return kr_error(EINVAL); + + int ret = knot_edns_opt_cookie_parse(cookie_data, cookie_len, + &cookies->cc, &cookies->cc_len, + &cookies->sc, &cookies->sc_len); + + return (ret == KNOT_EOK) ? kr_ok() : kr_error(EINVAL); +} diff --git a/lib/cookies/helper.h b/lib/cookies/helper.h new file mode 100644 index 0000000..dfde90e --- /dev/null +++ b/lib/cookies/helper.h @@ -0,0 +1,74 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <libknot/packet/pkt.h> + +#include "lib/cookies/alg_containers.h" +#include "lib/cookies/control.h" +#include "lib/cookies/lru_cache.h" +#include "lib/cookies/nonce.h" +#include "lib/defines.h" +#include "lib/resolve.h" + +/** + * @brief Updates DNS cookie in the request EDNS options. + * @note This function must be called before the request packet is finalised. + * @param clnt_comp client cookie control structure + * @param cookie_cache cookie cache + * @param clnt_sa client socket address + * @param srvr_sa server socket address + * @param req name resolution request + * @return kr_ok() or error code + */ +KR_EXPORT +int kr_request_put_cookie(const struct kr_cookie_comp *clnt_comp, + kr_cookie_lru_t *cookie_cache, + const struct sockaddr *clnt_sa, + const struct sockaddr *srvr_sa, + struct kr_request *req); + +/** + * @brief Inserts a cookie option into the OPT RR. It does not write any + * wire data. + * @note The content of @a sc_input is modified. Any pre-set nonce value is + * ignored. After retuning its nonce value will be null. + * @param sc_input data needed to compute server cookie, nonce is ignored + * @param nonce nonce value that is actually used + * @param alg hash algorithm + * @param pkt DNS response packet + */ +KR_EXPORT +int kr_answer_write_cookie(struct knot_sc_input *sc_input, + const struct kr_nonce_input *nonce, + const struct knot_sc_alg *alg, knot_pkt_t *pkt); + +/** + * @brief Set RCODE and extended RCODE. + * @param pkt DNS packet + * @param whole_rcode RCODE value + * @return kr_ok() or error code + */ +KR_EXPORT +int kr_pkt_set_ext_rcode(knot_pkt_t *pkt, uint16_t whole_rcode); + +/** + * @brief Check whether packet is a server cookie request according to + * RFC7873 5.4. + * @param pkt Packet to be examined. + * @return Pointer to entire cookie option if is a server cookie query, NULL on + * errors or if packet doesn't contain cookies or if QDCOUNT > 0. + */ +KR_EXPORT +uint8_t *kr_no_question_cookie_query(const knot_pkt_t *pkt); + +/** + * @brief Parse cookies from cookie option. + * @param cookie_opt Cookie option. + * @param cookies Cookie structure to be set. + * @return kr_ok() on success, error if cookies are malformed. + */ +KR_EXPORT +int kr_parse_cookie_opt(uint8_t *cookie_opt, struct knot_dns_cookies *cookies); diff --git a/lib/cookies/lru_cache.c b/lib/cookies/lru_cache.c new file mode 100644 index 0000000..245d1c3 --- /dev/null +++ b/lib/cookies/lru_cache.c @@ -0,0 +1,58 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <libknot/rrtype/opt.h> +#include <string.h> + +#include "lib/cookies/lru_cache.h" +#include "lib/utils.h" + +const uint8_t *kr_cookie_lru_get(kr_cookie_lru_t *cache, + const struct sockaddr *sa) +{ + if (!cache || !sa) { + return NULL; + } + + int addr_len = kr_inaddr_len(sa); + const char *addr = kr_inaddr(sa); + if (!addr || addr_len <= 0) { + return NULL; + } + + struct cookie_opt_data *cached = lru_get_try(cache, addr, addr_len); + return cached ? cached->opt_data : NULL; +} + +int kr_cookie_lru_set(kr_cookie_lru_t *cache, const struct sockaddr *sa, + uint8_t *opt) +{ + if (!cache || !sa) { + return kr_error(EINVAL); + } + + if (!opt) { + return kr_ok(); + } + + int addr_len = kr_inaddr_len(sa); + const char *addr = kr_inaddr(sa); + if (!addr || addr_len <= 0) { + return kr_error(EINVAL); + } + + uint16_t opt_size = KNOT_EDNS_OPTION_HDRLEN + + knot_edns_opt_get_length(opt); + + if (opt_size > KR_COOKIE_OPT_MAX_LEN) { + return kr_error(EINVAL); + } + + struct cookie_opt_data *cached = lru_get_new(cache, addr, addr_len, NULL); + if (cached) { + memcpy(cached->opt_data, opt, opt_size); + } + + return kr_ok(); +} diff --git a/lib/cookies/lru_cache.h b/lib/cookies/lru_cache.h new file mode 100644 index 0000000..a0f6cab --- /dev/null +++ b/lib/cookies/lru_cache.h @@ -0,0 +1,57 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <netinet/in.h> +#include <stdint.h> + +#if ENABLE_COOKIES +#include <libknot/rrtype/opt.h> +#include <libknot/rrtype/opt-cookie.h> +#else +#define KNOT_OPT_COOKIE_CLNT 8 +#define KNOT_OPT_COOKIE_SRVR_MAX 32 +#endif /* ENABLE_COOKIES */ + +#include "lib/defines.h" +#include "lib/generic/lru.h" + +/** Maximal size of a cookie option. */ +#define KR_COOKIE_OPT_MAX_LEN (KNOT_EDNS_OPTION_HDRLEN + KNOT_OPT_COOKIE_CLNT + KNOT_OPT_COOKIE_SRVR_MAX) + +/** + * Cookie option entry. + */ +struct cookie_opt_data { + uint8_t opt_data[KR_COOKIE_OPT_MAX_LEN]; +}; + +/** + * DNS cookies tracking. + */ +typedef lru_t(struct cookie_opt_data) kr_cookie_lru_t; + +/** + * @brief Obtain LRU cache entry. + * + * @param cache cookie LRU cache + * @param sa socket address serving as key + * @return pointer to cached option or NULL if not found or error occurred + */ +KR_EXPORT +const uint8_t *kr_cookie_lru_get(kr_cookie_lru_t *cache, + const struct sockaddr *sa); + +/** + * @brief Stores cookie option into LRU cache. + * + * @param cache cookie LRU cache + * @param sa socket address serving as key + * @param opt cookie option to be stored + * @return kr_ok() or error code + */ +KR_EXPORT +int kr_cookie_lru_set(kr_cookie_lru_t *cache, const struct sockaddr *sa, + uint8_t *opt); diff --git a/lib/cookies/nonce.c b/lib/cookies/nonce.c new file mode 100644 index 0000000..1b50d87 --- /dev/null +++ b/lib/cookies/nonce.c @@ -0,0 +1,20 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <libknot/wire.h> +#include "lib/cookies/nonce.h" + +uint16_t kr_nonce_write_wire(uint8_t *buf, uint16_t buf_len, + const struct kr_nonce_input *input) +{ + if (!buf || buf_len < KR_NONCE_LEN || !input) { + return 0; + } + + knot_wire_write_u32(buf, input->rand); + knot_wire_write_u32(buf + sizeof(uint32_t), input->time); + buf_len = 2 * sizeof(uint32_t); + + return buf_len; +} diff --git a/lib/cookies/nonce.h b/lib/cookies/nonce.h new file mode 100644 index 0000000..6c2970f --- /dev/null +++ b/lib/cookies/nonce.h @@ -0,0 +1,31 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "lib/defines.h" + +/* RFC7873 Appendix B.2 mentions an algorithm using two values before the + * actual server cookie hash. */ + +/** Nonce value length. */ +#define KR_NONCE_LEN 8 + +/** Input data to generate nonce from. */ +struct kr_nonce_input { + uint32_t rand; /**< some random value */ + uint32_t time; /**< time stamp */ +}; + +/** + * @brief Writes server cookie nonce value into given buffer. + * + * @param buf buffer to write nonce data in wire format into + * @param buf_len buffer size + * @param input data to generate wire data from + * @return non-zero size of written data on success, 0 on failure + */ +KR_EXPORT +uint16_t kr_nonce_write_wire(uint8_t *buf, uint16_t buf_len, + const struct kr_nonce_input *input); diff --git a/lib/defines.h b/lib/defines.h new file mode 100644 index 0000000..6b6dac5 --- /dev/null +++ b/lib/defines.h @@ -0,0 +1,106 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <errno.h> +#include <libknot/errcode.h> +#include <libknot/dname.h> +#include <libknot/rrset.h> +#include <libknot/version.h> + +/* Function attributes */ +#if __GNUC__ >= 4 +#define KR_EXPORT __attribute__ ((visibility ("default"))) +#define KR_CONST __attribute__((__const__)) +#define KR_PURE __attribute__((__pure__)) +#define KR_NORETURN __attribute__((__noreturn__)) +#define KR_COLD __attribute__((__cold__)) +#define KR_PRINTF(n) __attribute__((format (printf, n, (n+1)))) +#else +#define KR_EXPORT +#define KR_CONST +#define KR_PURE +#define KR_NORETURN +#define KR_COLD +#define KR_PRINTF(n) +#endif + +typedef unsigned int uint; + +/* + * Error codes. + */ +#define kr_ok() 0 +/* Mark as cold to mark all branches as unlikely. */ +static inline int KR_COLD kr_error(int x) { + return x <= 0 ? x : -x; +} +#define kr_strerror(x) strerror(abs(x)) + +/* We require C11 but want to avoid including the standard assertion header + * so we alias it ourselves. */ +#define static_assert _Static_assert + +/* + * Connection limits. + * @cond internal + */ +#define KR_CONN_RTT_MAX 2000 /* Timeout for network activity */ +#define KR_CONN_RETRY 200 /* Retry interval for network activity */ +#define KR_ITER_LIMIT 100 /* Built-in iterator limit */ +#define KR_RESOLVE_TIME_LIMIT 10000 /* Upper limit for resolution time of single query, ms */ +#define KR_CNAME_CHAIN_LIMIT 13 /* Built-in maximum CNAME chain length */ +#define KR_TIMEOUT_LIMIT 10 /* Maximum number of retries after timeout. */ +#define KR_QUERY_NSRETRY_LIMIT 4 /* Maximum number of retries per query. */ +#define KR_COUNT_NO_NSADDR_LIMIT 5 +#define KR_CONSUME_FAIL_ROW_LIMIT 3 /* Maximum number of KR_STATE_FAIL in a row. */ + +/* + * Defines. + */ +#define KR_DNS_PORT 53 +#define KR_DNS_DOH_PORT 443 +#define KR_DNS_TLS_PORT 853 +#define KR_EDNS_VERSION 0 +#define KR_EDNS_PAYLOAD 1232 /* Default UDP payload; see https://www.dnsflagday.net/2020/ */ +#define KR_CACHE_DEFAULT_TTL_MIN (5) /* avoid bursts of queries */ +#define KR_CACHE_DEFAULT_TTL_MAX (1 * 24 * 3600) /* one day seems enough; fits prefill module */ + +#define KR_DNAME_STR_MAXLEN (KNOT_DNAME_TXT_MAXLEN + 1) +#define KR_RRTYPE_STR_MAXLEN (16 + 1) + +/* Compatibility with libknot<3.1.0 */ +#if KNOT_VERSION_HEX < 0x030100 +#define KNOT_EDNS_EDE_NONE (-1) +#endif + +/* + * Address sanitizer hints. + */ +#if !defined(__SANITIZE_ADDRESS__) && defined(__has_feature) +# if __has_feature(address_sanitizer) +# define __SANITIZE_ADDRESS__ 1 +# endif +#endif +#if defined(__SANITIZE_ADDRESS__) +void __asan_poison_memory_region(void const volatile *addr, size_t size); +void __asan_unpoison_memory_region(void const volatile *addr, size_t size); +#define kr_asan_poison(addr, size) __asan_poison_memory_region((addr), (size)) +#define kr_asan_unpoison(addr, size) __asan_unpoison_memory_region((addr), (size)) +#define kr_asan_custom_poison(fn, addr) fn ##_poison((addr)) +#define kr_asan_custom_unpoison(fn, addr) fn ##_unpoison((addr)) +#else +#define kr_asan_poison(addr, size) +#define kr_asan_unpoison(addr, size) +#define kr_asan_custom_poison(fn, addr) +#define kr_asan_custom_unpoison(fn, addr) +#endif + +#if defined(__SANITIZE_ADDRESS__) && defined(_FORTIFY_SOURCE) + #error "You can't use address sanitizer with _FORTIFY_SOURCE" + // https://github.com/google/sanitizers/issues/247 +#endif + +/* @endcond */ diff --git a/lib/dnssec.c b/lib/dnssec.c new file mode 100644 index 0000000..d6ae3cc --- /dev/null +++ b/lib/dnssec.c @@ -0,0 +1,601 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <libdnssec/binary.h> +#include <libdnssec/crypto.h> +#include <libdnssec/error.h> +#include <libdnssec/key.h> +#include <libdnssec/sign.h> +#include <libknot/descriptor.h> +#include <libknot/packet/wire.h> +#include <libknot/rdataset.h> +#include <libknot/rrset.h> +#include <libknot/rrtype/dnskey.h> +#include <libknot/rrtype/nsec.h> +#include <libknot/rrtype/rrsig.h> + +#include "contrib/cleanup.h" +#include "lib/defines.h" +#include "lib/dnssec/nsec.h" +#include "lib/dnssec/nsec3.h" +#include "lib/dnssec/signature.h" +#include "lib/dnssec.h" +#include "lib/resolve.h" + +/* forward */ +static int kr_rrset_validate_with_key(kr_rrset_validation_ctx_t *vctx, + knot_rrset_t *covered, size_t key_pos, const struct dnssec_key *key); + +void kr_crypto_init(void) +{ + dnssec_crypto_init(); +} + +void kr_crypto_cleanup(void) +{ + dnssec_crypto_cleanup(); +} + +void kr_crypto_reinit(void) +{ + dnssec_crypto_reinit(); +} + +#define FLG_WILDCARD_EXPANSION 0x01 /**< Possibly generated by using wildcard expansion. */ + +/** + * Check the RRSIG RR validity according to RFC4035 5.3.1 . + * @param flags The flags are going to be set according to validation result. + * @param cov_labels Covered RRSet owner label count. + * @param rrsigs rdata containing the signatures. + * @param key_alg DNSKEY's algorithm. + * @param keytag Used key tag. + * @param vctx->zone_name The name of the zone cut (and the DNSKEY). + * @param vctx->timestamp Validation time. + */ +static int validate_rrsig_rr(int *flags, int cov_labels, + const knot_rdata_t *rrsigs, + uint8_t key_alg, + uint16_t keytag, + kr_rrset_validation_ctx_t *vctx) +{ + if (kr_fails_assert(flags && rrsigs && vctx && vctx->zone_name)) { + return kr_error(EINVAL); + } + /* bullet 5 */ + if (knot_rrsig_sig_expiration(rrsigs) < vctx->timestamp) { + vctx->rrs_counters.expired++; + return kr_error(EINVAL); + } + /* bullet 6 */ + if (knot_rrsig_sig_inception(rrsigs) > vctx->timestamp) { + vctx->rrs_counters.notyet++; + return kr_error(EINVAL); + } + /* bullet 2 */ + const knot_dname_t *signer_name = knot_rrsig_signer_name(rrsigs); + if (!signer_name || !knot_dname_is_equal(signer_name, vctx->zone_name)) { + vctx->rrs_counters.signer_invalid++; + return kr_error(EAGAIN); + } + /* bullet 4 */ + { + int rrsig_labels = knot_rrsig_labels(rrsigs); + if (rrsig_labels > cov_labels) { + vctx->rrs_counters.labels_invalid++; + return kr_error(EINVAL); + } + if (rrsig_labels < cov_labels) { + *flags |= FLG_WILDCARD_EXPANSION; + } + } + + /* bullet 7 + * Part checked elsewhere: key owner matching the zone_name. */ + if (key_alg != knot_rrsig_alg(rrsigs) || keytag != knot_rrsig_key_tag(rrsigs)) { + vctx->rrs_counters.key_invalid++; + return kr_error(EINVAL); + } + /* bullet 8 */ + /* Checked somewhere else. */ + /* bullet 9 and 10 */ + /* One of the requirements should be always fulfilled. */ + + return kr_ok(); +} + +/** + * Returns the number of labels that have been added by wildcard expansion. + * @param expanded Expanded wildcard. + * @param rrsigs RRSet containing the signatures. + * @param sig_pos Specifies the signature within the RRSIG RRSet. + * @return Number of added labels, -1 on error. + */ +static inline int wildcard_radix_len_diff(const knot_dname_t *expanded, + const knot_rdata_t *rrsig) +{ + if (!expanded || !rrsig) { + return -1; + } + + return knot_dname_labels(expanded, NULL) - knot_rrsig_labels(rrsig); +} + +int kr_rrset_validate(kr_rrset_validation_ctx_t *vctx, knot_rrset_t *covered) +{ + if (!vctx) { + return kr_error(EINVAL); + } + if (!vctx->pkt || !covered || !vctx->keys || !vctx->zone_name) { + return kr_error(EINVAL); + } + + memset(&vctx->rrs_counters, 0, sizeof(vctx->rrs_counters)); + for (unsigned i = 0; i < vctx->keys->rrs.count; ++i) { + int ret = kr_rrset_validate_with_key(vctx, covered, i, NULL); + if (ret == 0) { + return ret; + } + } + + return kr_error(ENOENT); +} + +/** Assuming `rrs` was validated with `sig`, trim its TTL in case it's over-extended. */ +static bool trim_ttl(knot_rrset_t *rrs, const knot_rdata_t *sig, + const kr_rrset_validation_ctx_t *vctx) +{ + /* The trimming logic is a bit complicated. + * + * We respect configured ttl_min over the (signed) original TTL, + * but we very much want to avoid TTLs over signature expiration, + * as that could cause serious issues with downstream validators. + */ + const uint32_t ttl_max = MIN( + MAX(knot_rrsig_original_ttl(sig), vctx->ttl_min), + knot_rrsig_sig_expiration(sig) - vctx->timestamp + ); + if (likely(rrs->ttl <= ttl_max)) + return false; + if (kr_log_is_debug_qry(VALIDATOR, vctx->log_qry)) { + auto_free char *name_str = kr_dname_text(rrs->owner), + *type_str = kr_rrtype_text(rrs->type); + kr_log_q(vctx->log_qry, VALIDATOR, "trimming TTL of %s %s: %d -> %d\n", + name_str, type_str, (int)rrs->ttl, (int)ttl_max); + } + rrs->ttl = ttl_max; + return true; +} + + +typedef struct { + struct dnssec_key *key; + uint8_t alg; + uint16_t tag; +} kr_svldr_key_t; + +struct kr_svldr_ctx { + kr_rrset_validation_ctx_t vctx; + array_t(kr_svldr_key_t) keys; // owned(malloc), also insides via svldr_key_* +}; + +static int svldr_key_new(const knot_rdata_t *rdata, const knot_dname_t *owner, + kr_svldr_key_t *result) +{ + result->alg = knot_dnskey_alg(rdata); + result->key = NULL; // just silence analyzers + int ret = kr_dnssec_key_from_rdata(&result->key, owner, rdata->data, rdata->len); + if (likely(ret == 0)) + result->tag = dnssec_key_get_keytag(result->key); + return ret; +} +static inline void svldr_key_del(kr_svldr_key_t *skey) +{ + kr_dnssec_key_free(&skey->key); +} + +void kr_svldr_free_ctx(struct kr_svldr_ctx *ctx) +{ + if (!ctx) return; + for (ssize_t i = 0; i < ctx->keys.len; ++i) + svldr_key_del(&ctx->keys.at[i]); + array_clear(ctx->keys); + free_const(ctx->vctx.zone_name); + free(ctx); +} +struct kr_svldr_ctx * kr_svldr_new_ctx(const knot_rrset_t *ds, knot_rrset_t *dnskey, + const knot_rdataset_t *dnskey_sigs, uint32_t timestamp, + kr_rrset_validation_ctx_t *err_ctx) +{ + // Basic init. + struct kr_svldr_ctx *ctx = calloc(1, sizeof(*ctx)); + if (unlikely(!ctx)) + return NULL; + ctx->vctx.timestamp = timestamp; // .ttl_min is implicitly zero + ctx->vctx.zone_name = knot_dname_copy(ds->owner, NULL); + if (unlikely(!ctx->vctx.zone_name)) + goto fail; + // Validate the DNSKEY set. + ctx->vctx.keys = dnskey; + if (kr_dnskeys_trusted(&ctx->vctx, dnskey_sigs, ds) != 0) + goto fail; + // Put usable DNSKEYs into ctx->keys. (Some duplication of work happens, but OK.) + array_init(ctx->keys); + array_reserve(ctx->keys, dnskey->rrs.count); + knot_rdata_t *krr = dnskey->rrs.rdata; + for (int i = 0; i < dnskey->rrs.count; ++i, krr = knot_rdataset_next(krr)) { + if (!kr_dnssec_key_zsk(krr->data) || kr_dnssec_key_revoked(krr->data)) + continue; // key not usable for this + kr_svldr_key_t key; + if (unlikely(svldr_key_new(krr, NULL/*seems OK here*/, &key) != 0)) + goto fail; + array_push(ctx->keys, key); + } + return ctx; +fail: + if (err_ctx) + memcpy(err_ctx, &ctx->vctx, sizeof(*err_ctx)); + kr_svldr_free_ctx(ctx); + return NULL; +} + +static int kr_svldr_rrset_with_key(knot_rrset_t *rrs, const knot_rdataset_t *rrsigs, + kr_rrset_validation_ctx_t *vctx, const kr_svldr_key_t *key) +{ + const int covered_labels = knot_dname_labels(rrs->owner, NULL) + - knot_dname_is_wildcard(rrs->owner); + knot_rdata_t *rdata_j = rrsigs->rdata; + for (uint16_t j = 0; j < rrsigs->count; ++j, rdata_j = knot_rdataset_next(rdata_j)) { + if (kr_fails_assert(knot_rrsig_type_covered(rdata_j) == rrs->type)) + continue; //^^ not a problem but no reason to allow them in the API + int val_flgs = 0; + int retv = validate_rrsig_rr(&val_flgs, covered_labels, rdata_j, + key->alg, key->tag, vctx); + if (retv == kr_error(EAGAIN)) { + vctx->result = retv; + return vctx->result; + } else if (retv != 0) { + continue; + } + // We only expect non-expanded wildcard records in input; + // that also means we don't need to perform non-existence proofs. + const int trim_labels = (val_flgs & FLG_WILDCARD_EXPANSION) ? 1 : 0; + if (kr_check_signature(rdata_j, key->key, rrs, trim_labels) == 0) { + trim_ttl(rrs, rdata_j, vctx); + vctx->result = kr_ok(); + return vctx->result; + } else { + vctx->rrs_counters.crypto_invalid++; + } + } + vctx->result = kr_error(ENOENT); + return vctx->result; +} +/* The implementation basically performs "parts of" kr_rrset_validate(). */ +int kr_svldr_rrset(knot_rrset_t *rrs, const knot_rdataset_t *rrsigs, + struct kr_svldr_ctx *ctx) +{ + if (knot_dname_in_bailiwick(rrs->owner, ctx->vctx.zone_name) < 0) { + ctx->vctx.result = kr_error(EAGAIN); + return ctx->vctx.result; + } + for (ssize_t i = 0; i < ctx->keys.len; ++i) { + kr_svldr_rrset_with_key(rrs, rrsigs, &ctx->vctx, &ctx->keys.at[i]); + if (ctx->vctx.result == 0) + break; + } + return ctx->vctx.result; +} + + +/** + * Validate RRSet using a specific key. + * @param vctx Pointer to validation context. + * @param covered RRSet covered by a signature. It must be in canonical format. + * TTL may get lowered. + * @param key_pos Position of the key to be validated with. + * @param key Key to be used to validate. + * If NULL, then key from DNSKEY RRSet is used. + * @return 0 or error code, same as vctx->result. + */ +static int kr_rrset_validate_with_key(kr_rrset_validation_ctx_t *vctx, + knot_rrset_t *covered, + size_t key_pos, const struct dnssec_key *key) +{ + const knot_pkt_t *pkt = vctx->pkt; + const knot_rrset_t *keys = vctx->keys; + const knot_dname_t *zone_name = vctx->zone_name; + bool has_nsec3 = vctx->has_nsec3; + struct dnssec_key *created_key = NULL; + + if (!knot_dname_is_equal(keys->owner, zone_name) + /* It's just caller's approximation that the RR is in that particular zone, + * so we verify that in the following condition. + * We MUST guard against attempts of zones signing out-of-bailiwick records. */ + || knot_dname_in_bailiwick(covered->owner, zone_name) < 0) { + vctx->result = kr_error(ENOENT); + return vctx->result; + } + + const knot_rdata_t *key_rdata = knot_rdataset_at(&keys->rrs, key_pos); + if (key == NULL) { + int ret = kr_dnssec_key_from_rdata(&created_key, keys->owner, + key_rdata->data, key_rdata->len); + if (ret != 0) { + vctx->result = ret; + return vctx->result; + } + key = created_key; + } + uint16_t keytag = dnssec_key_get_keytag(key); + const uint8_t key_alg = knot_dnskey_alg(key_rdata); + /* The asterisk does not count, RFC4034 3.1.3, paragraph 3. */ + const int covered_labels = knot_dname_labels(covered->owner, NULL) + - knot_dname_is_wildcard(covered->owner); + + for (uint16_t i = 0; i < vctx->rrs->len; ++i) { + /* Consider every RRSIG that matches and comes from the same query. */ + const knot_rrset_t *rrsig = vctx->rrs->at[i]->rr; + const bool ok = vctx->rrs->at[i]->qry_uid == vctx->qry_uid + && rrsig->type == KNOT_RRTYPE_RRSIG + && rrsig->rclass == covered->rclass + && knot_dname_is_equal(rrsig->owner, covered->owner); + if (!ok) + continue; + + knot_rdata_t *rdata_j = rrsig->rrs.rdata; + for (uint16_t j = 0; j < rrsig->rrs.count; ++j, rdata_j = knot_rdataset_next(rdata_j)) { + int val_flgs = 0; + int trim_labels = 0; + if (knot_rrsig_type_covered(rdata_j) != covered->type) { + continue; + } + kr_rank_set(&vctx->rrs->at[i]->rank, KR_RANK_BOGUS); /* defensive style */ + vctx->rrs_counters.matching_name_type++; + int retv = validate_rrsig_rr(&val_flgs, covered_labels, rdata_j, + key_alg, keytag, vctx); + if (retv == kr_error(EAGAIN)) { + kr_dnssec_key_free(&created_key); + vctx->result = retv; + return retv; + } else if (retv != 0) { + continue; + } + if (val_flgs & FLG_WILDCARD_EXPANSION) { + trim_labels = wildcard_radix_len_diff(covered->owner, rdata_j); + if (trim_labels < 0) { + break; + } + } + if (kr_check_signature(rdata_j, key, covered, trim_labels) != 0) { + vctx->rrs_counters.crypto_invalid++; + continue; + } + if (val_flgs & FLG_WILDCARD_EXPANSION) { + int ret = 0; + if (!has_nsec3) { + ret = kr_nsec_wildcard_answer_response_check(pkt, KNOT_AUTHORITY, covered->owner); + } else { + ret = kr_nsec3_wildcard_answer_response_check(pkt, KNOT_AUTHORITY, covered->owner, trim_labels - 1); + if (ret == kr_error(KNOT_ERANGE)) { + ret = 0; + vctx->flags |= KR_DNSSEC_VFLG_OPTOUT; + } + } + if (ret != 0) { + vctx->rrs_counters.nsec_invalid++; + continue; + } + vctx->flags |= KR_DNSSEC_VFLG_WEXPAND; + } + + trim_ttl(covered, rdata_j, vctx); + + kr_dnssec_key_free(&created_key); + vctx->result = kr_ok(); + kr_rank_set(&vctx->rrs->at[i]->rank, KR_RANK_SECURE); /* upgrade from bogus */ + return vctx->result; + } + } + /* No applicable key found, cannot be validated. */ + kr_dnssec_key_free(&created_key); + vctx->result = kr_error(ENOENT); + return vctx->result; +} + +bool kr_ds_algo_support(const knot_rrset_t *ta) +{ + if (kr_fails_assert(ta && ta->type == KNOT_RRTYPE_DS && ta->rclass == KNOT_CLASS_IN)) + return false; + /* Check if at least one DS has a usable algorithm pair. */ + knot_rdata_t *rdata_i = ta->rrs.rdata; + for (uint16_t i = 0; i < ta->rrs.count; + ++i, rdata_i = knot_rdataset_next(rdata_i)) { + if (dnssec_algorithm_digest_support(knot_ds_digest_type(rdata_i)) + && dnssec_algorithm_key_support(knot_ds_alg(rdata_i))) { + return true; + } + } + return false; +} + +int kr_dnskeys_trusted(kr_rrset_validation_ctx_t *vctx, const knot_rdataset_t *sigs, + const knot_rrset_t *ta) +{ + knot_rrset_t *keys = vctx->keys; + const bool ok = keys && ta && ta->rrs.count && ta->rrs.rdata + && ta->type == KNOT_RRTYPE_DS + && knot_dname_is_equal(ta->owner, keys->owner); + if (kr_fails_assert(ok)) + return kr_error(EINVAL); + + /* RFC4035 5.2, bullet 1 + * The supplied DS record has been authenticated. + * It has been validated or is part of a configured trust anchor. + */ + knot_rdata_t *krr = keys->rrs.rdata; + for (int i = 0; i < keys->rrs.count; ++i, krr = knot_rdataset_next(krr)) { + /* RFC4035 5.3.1, bullet 8 */ /* ZSK */ + if (!kr_dnssec_key_zsk(krr->data) || kr_dnssec_key_revoked(krr->data)) + continue; + + kr_svldr_key_t key; + if (svldr_key_new(krr, keys->owner, &key) != 0) + continue; // it might e.g. be malformed + + int ret = kr_authenticate_referral(ta, key.key); + if (ret == 0) + ret = kr_svldr_rrset_with_key(keys, sigs, vctx, &key); + svldr_key_del(&key); + if (ret == 0) { + kr_assert(vctx->result == 0); + return vctx->result; + } + } + + /* No useable key found */ + vctx->result = kr_error(ENOENT); + return vctx->result; +} + +bool kr_dnssec_key_zsk(const uint8_t *dnskey_rdata) +{ + return knot_wire_read_u16(dnskey_rdata) & 0x0100; +} + +bool kr_dnssec_key_ksk(const uint8_t *dnskey_rdata) +{ + return knot_wire_read_u16(dnskey_rdata) & 0x0001; +} + +/** Return true if the DNSKEY is revoked. */ +bool kr_dnssec_key_revoked(const uint8_t *dnskey_rdata) +{ + return knot_wire_read_u16(dnskey_rdata) & 0x0080; +} + +int kr_dnssec_key_tag(uint16_t rrtype, const uint8_t *rdata, size_t rdlen) +{ + if (!rdata || rdlen == 0 || (rrtype != KNOT_RRTYPE_DS && rrtype != KNOT_RRTYPE_DNSKEY)) { + return kr_error(EINVAL); + } + if (rrtype == KNOT_RRTYPE_DS) { + return knot_wire_read_u16(rdata); + } else if (rrtype == KNOT_RRTYPE_DNSKEY) { + struct dnssec_key *key = NULL; + int ret = kr_dnssec_key_from_rdata(&key, NULL, rdata, rdlen); + if (ret != 0) { + return ret; + } + uint16_t keytag = dnssec_key_get_keytag(key); + kr_dnssec_key_free(&key); + return keytag; + } else { + return kr_error(EINVAL); + } +} + +int kr_dnssec_key_match(const uint8_t *key_a_rdata, size_t key_a_rdlen, + const uint8_t *key_b_rdata, size_t key_b_rdlen) +{ + dnssec_key_t *key_a = NULL, *key_b = NULL; + int ret = kr_dnssec_key_from_rdata(&key_a, NULL, key_a_rdata, key_a_rdlen); + if (ret != 0) { + return ret; + } + ret = kr_dnssec_key_from_rdata(&key_b, NULL, key_b_rdata, key_b_rdlen); + if (ret != 0) { + dnssec_key_free(key_a); + return ret; + } + /* If the algorithm and the public key match, we can be sure + * that they are the same key. */ + ret = kr_error(ENOENT); + dnssec_binary_t pk_a, pk_b; + if (dnssec_key_get_algorithm(key_a) == dnssec_key_get_algorithm(key_b) && + dnssec_key_get_pubkey(key_a, &pk_a) == DNSSEC_EOK && + dnssec_key_get_pubkey(key_b, &pk_b) == DNSSEC_EOK) { + if (pk_a.size == pk_b.size && memcmp(pk_a.data, pk_b.data, pk_a.size) == 0) { + ret = 0; + } + } + dnssec_key_free(key_a); + dnssec_key_free(key_b); + return ret; +} + +int kr_dnssec_key_from_rdata(struct dnssec_key **key, const knot_dname_t *kown, const uint8_t *rdata, size_t rdlen) +{ + if (!key || !rdata || rdlen == 0) { + return kr_error(EINVAL); + } + + dnssec_key_t *new_key = NULL; + const dnssec_binary_t binary_key = { + .size = rdlen, + .data = (uint8_t *)rdata + }; + + int ret = dnssec_key_new(&new_key); + if (ret != DNSSEC_EOK) { + return kr_error(ENOMEM); + } + ret = dnssec_key_set_rdata(new_key, &binary_key); + if (ret != DNSSEC_EOK) { + dnssec_key_free(new_key); + return kr_error(ret); + } + if (kown) { + ret = dnssec_key_set_dname(new_key, kown); + if (ret != DNSSEC_EOK) { + dnssec_key_free(new_key); + return kr_error(ENOMEM); + } + } + + *key = new_key; + return kr_ok(); +} + +void kr_dnssec_key_free(struct dnssec_key **key) +{ + if (kr_fails_assert(key)) + return; + + dnssec_key_free(*key); + *key = NULL; +} + +int kr_dnssec_matches_name_and_type(const ranked_rr_array_t *rrs, uint32_t qry_uid, + const knot_dname_t *name, uint16_t type) +{ + int ret = kr_error(ENOENT); + for (size_t i = 0; i < rrs->len; ++i) { + const ranked_rr_array_entry_t *entry = rrs->at[i]; + if (kr_fails_assert(!entry->in_progress)) + return kr_error(EINVAL); + const knot_rrset_t *nsec = entry->rr; + if (entry->qry_uid != qry_uid || entry->yielded) { + continue; + } + if (nsec->type != KNOT_RRTYPE_NSEC && + nsec->type != KNOT_RRTYPE_NSEC3) { + continue; + } + if (!kr_rank_test(entry->rank, KR_RANK_SECURE)) { + continue; + } + if (nsec->type == KNOT_RRTYPE_NSEC) { + ret = kr_nsec_matches_name_and_type(nsec, name, type); + } else { + ret = kr_nsec3_matches_name_and_type(nsec, name, type); + } + if (ret == kr_ok()) { + return kr_ok(); + } else if (ret != kr_error(ENOENT)) { + return ret; + } + } + return ret; +} diff --git a/lib/dnssec.h b/lib/dnssec.h new file mode 100644 index 0000000..0fbd47c --- /dev/null +++ b/lib/dnssec.h @@ -0,0 +1,191 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "lib/defines.h" +#include "lib/utils.h" +#include <libknot/packet/pkt.h> + +/** + * Initialise cryptographic back-end. + */ +KR_EXPORT +void kr_crypto_init(void); + +/** + * De-initialise cryptographic back-end. + */ +KR_EXPORT +void kr_crypto_cleanup(void); + +/** + * Re-initialise cryptographic back-end. + * @note Must be called after fork() in the child. + */ +KR_EXPORT +void kr_crypto_reinit(void); + +#define KR_DNSSEC_VFLG_WEXPAND 0x01 +#define KR_DNSSEC_VFLG_OPTOUT 0x02 + +/** DNSSEC validation context. */ +struct kr_rrset_validation_ctx { + const knot_pkt_t *pkt; /*!< Packet to be validated. */ + ranked_rr_array_t *rrs; /*!< List of preselected RRs to be validated. */ + knot_section_t section_id; /*!< Section to work with. */ + knot_rrset_t *keys; /*!< DNSKEY RRSet; TTLs may get lowered when validating this set. */ + const knot_dname_t *zone_name; /*!< Name of the zone containing the RRSIG RRSet. */ + uint32_t timestamp; /*!< Validation time. */ + uint32_t ttl_min; /*!< See trim_ttl() for details. */ + bool has_nsec3; /*!< Whether to use NSEC3 validation. */ + uint32_t qry_uid; /*!< Current query uid. */ + uint32_t flags; /*!< Output - Flags. */ + uint32_t err_cnt; /*!< Output - Number of validation failures. */ + uint32_t cname_norrsig_cnt; /*!< Output - Number of CNAMEs missing RRSIGs. */ + + /** Validation result: kr_error() code. + * + * ENOENT: the usual, no suitable signature found + * EAGAIN: encountered a different signer name + * +others + */ + int result; + const struct kr_query *log_qry; /*!< The query; just for logging purposes. */ + struct { + unsigned int matching_name_type; /*!< Name + type matches */ + unsigned int expired; + unsigned int notyet; + unsigned int signer_invalid; /*!< Signer is not zone apex */ + unsigned int labels_invalid; /*!< Number of labels in RRSIG */ + unsigned int key_invalid; /*!< Algorithm/keytag/key owner */ + unsigned int crypto_invalid; + unsigned int nsec_invalid; + } rrs_counters; /*!< Error counters for single RRset validation. */ +}; + +typedef struct kr_rrset_validation_ctx kr_rrset_validation_ctx_t; + +/** + * Validate RRSet. + * @param vctx Pointer to validation context. + * @param covered RRSet covered by a signature. It must be in canonical format. + * Its TTL may get lowered. + * @return 0 or kr_error() code, same as vctx->result (see its docs). + */ +int kr_rrset_validate(kr_rrset_validation_ctx_t *vctx, knot_rrset_t *covered); + +/** + * Return true iff the RRset contains at least one usable DS. See RFC6840 5.2. + */ +KR_EXPORT KR_PURE +bool kr_ds_algo_support(const knot_rrset_t *ta); + +/** + * Check whether the DNSKEY rrset matches the supplied trust anchor RRSet. + * + * @param vctx Pointer to validation context. Note that TTL of vctx->keys may get lowered. + * @param sigs RRSIGs for this DNSKEY set + * @param ta Trusted DS RRSet against which to validate the DNSKEY RRSet. + * @return 0 or error code, same as vctx->result. + */ +int kr_dnskeys_trusted(kr_rrset_validation_ctx_t *vctx, const knot_rdataset_t *sigs, + const knot_rrset_t *ta); + +/** Return true if the DNSKEY can be used as a ZSK. */ +KR_EXPORT KR_PURE +bool kr_dnssec_key_zsk(const uint8_t *dnskey_rdata); + +/** Return true if the DNSKEY indicates being KSK (=> has SEP). */ +KR_EXPORT KR_PURE +bool kr_dnssec_key_ksk(const uint8_t *dnskey_rdata); + +/** Return true if the DNSKEY is revoked. */ +KR_EXPORT KR_PURE +bool kr_dnssec_key_revoked(const uint8_t *dnskey_rdata); + +/** Return DNSKEY tag. + * @param rrtype RR type (either DS or DNSKEY are supported) + * @param rdata Key/digest RDATA. + * @param rdlen RDATA length. + * @return Key tag (positive number), or an error code + */ +KR_EXPORT KR_PURE +int kr_dnssec_key_tag(uint16_t rrtype, const uint8_t *rdata, size_t rdlen); + +/** Return 0 if the two keys are identical. + * @note This compares RDATA only, algorithm and public key must match. + * @param key_a_rdata First key RDATA + * @param key_a_rdlen First key RDATA length + * @param key_b_rdata Second key RDATA + * @param key_b_rdlen Second key RDATA length + * @return 0 if they match or an error code + */ +KR_EXPORT KR_PURE +int kr_dnssec_key_match(const uint8_t *key_a_rdata, size_t key_a_rdlen, + const uint8_t *key_b_rdata, size_t key_b_rdlen); + +/* Opaque DNSSEC key struct; forward declaration from libdnssec. */ +struct dnssec_key; + +/** + * Construct a DNSSEC key. + * @param key Pointer to be set to newly created DNSSEC key. + * @param kown DNSKEY owner name. + * @param rdata DNSKEY RDATA + * @param rdlen DNSKEY RDATA length + * @return 0 or error code; in particular: DNSSEC_INVALID_KEY_ALGORITHM + */ +int kr_dnssec_key_from_rdata(struct dnssec_key **key, const knot_dname_t *kown, const uint8_t *rdata, size_t rdlen); + +/** + * Frees the DNSSEC key. + * @param key Pointer to freed key. + */ +void kr_dnssec_key_free(struct dnssec_key **key); + +/** + * Checks whether NSEC/NSEC3 RR selected by iterator matches the supplied name and type. + * @param rrs Records selected by iterator. + * @param qry_uid Query unique identifier where NSEC/NSEC3 belongs to. + * @param name Name to be checked. + * @param type Type to be checked. + * @return 0 or error code. + */ +int kr_dnssec_matches_name_and_type(const ranked_rr_array_t *rrs, uint32_t qry_uid, + const knot_dname_t *name, uint16_t type); + + +/* Simple validator API. Main use case: prefill module, i.e. RRs from a zone file. */ + +/** Opaque context for simple validator. */ +struct kr_svldr_ctx; +/** + * Create new context for validating within a given zone. + * + * - `ds` is assumed to be trusted, and it's used to validate `dnskey+dnskey_sigs`. + * - The TTL of `dnskey` may get trimmed. + * - The insides are placed on malloc heap (use _free_ctx). + * - `err_ctx` is optional, for use when error happens (but avoid the inside pointers) + */ +KR_EXPORT +struct kr_svldr_ctx * kr_svldr_new_ctx(const knot_rrset_t *ds, knot_rrset_t *dnskey, + const knot_rdataset_t *dnskey_sigs, uint32_t timestamp, + kr_rrset_validation_ctx_t *err_ctx); +/** Free the context. Passing NULL is OK. */ +KR_EXPORT +void kr_svldr_free_ctx(struct kr_svldr_ctx *ctx); +/** + * Validate an RRset with the associated signatures; assume no wildcard expansions. + * + * - It's caller's responsibility that rrsigs have matching owner, class and type. + * - The TTL of `rrs` may get trimmed. + * - If it's a wildcard other than in its simple `*.` form, it may fail to validate. + * - More generally, non-existence proofs are not supported. + * @return 0 or kr_error() code, same as kr_rrset_validation_ctx::result (see its docs). + */ +KR_EXPORT +int kr_svldr_rrset(knot_rrset_t *rrs, const knot_rdataset_t *rrsigs, + struct kr_svldr_ctx *ctx); + diff --git a/lib/dnssec/nsec.c b/lib/dnssec/nsec.c new file mode 100644 index 0000000..8b17247 --- /dev/null +++ b/lib/dnssec/nsec.c @@ -0,0 +1,315 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <stdlib.h> + +#include <libknot/descriptor.h> +#include <libknot/dname.h> +#include <libknot/packet/wire.h> +#include <libknot/rrset.h> +#include <libknot/rrtype/nsec.h> +#include <libknot/rrtype/rrsig.h> +#include <libdnssec/error.h> +#include <libdnssec/nsec.h> + +#include "lib/defines.h" +#include "lib/dnssec/nsec.h" +#include "lib/utils.h" +#include "resolve.h" + +int kr_nsec_children_in_zone_check(const uint8_t *bm, uint16_t bm_size) +{ + if (kr_fails_assert(bm)) + return kr_error(EINVAL); + const bool parent_side = + dnssec_nsec_bitmap_contains(bm, bm_size, KNOT_RRTYPE_DNAME) + || (dnssec_nsec_bitmap_contains(bm, bm_size, KNOT_RRTYPE_NS) + && !dnssec_nsec_bitmap_contains(bm, bm_size, KNOT_RRTYPE_SOA) + ); + return parent_side ? abs(ENOENT) : kr_ok(); + /* LATER: after refactoring, probably also check if signer name equals owner, + * but even without that it's not possible to attack *correctly* signed zones. + */ +} + +/* This block of functions implements a "safe" version of knot_dname_cmp(), + * until that one handles in-label zero bytes correctly. */ +static int lf_cmp(const uint8_t *lf1, const uint8_t *lf2) +{ + /* Compare common part. */ + uint8_t common = lf1[0]; + if (common > lf2[0]) { + common = lf2[0]; + } + int ret = memcmp(lf1 + 1, lf2 + 1, common); + if (ret != 0) { + return ret; + } + + /* If they match, compare lengths. */ + if (lf1[0] < lf2[0]) { + return -1; + } else if (lf1[0] > lf2[0]) { + return 1; + } else { + return 0; + } +} +static void dname_reverse(const knot_dname_t *src, size_t src_len, knot_dname_t *dst) +{ + knot_dname_t *idx = dst + src_len - 1; + kr_require(src[src_len - 1] == '\0'); + *idx = '\0'; + + while (*src) { + uint16_t len = *src + 1; + idx -= len; + memcpy(idx, src, len); + src += len; + } + kr_require(idx == dst); +} +static int dname_cmp(const knot_dname_t *d1, const knot_dname_t *d2) +{ + size_t d1_len = knot_dname_size(d1); + size_t d2_len = knot_dname_size(d2); + + knot_dname_t d1_rev_arr[d1_len], d2_rev_arr[d2_len]; + const knot_dname_t *d1_rev = d1_rev_arr, *d2_rev = d2_rev_arr; + + dname_reverse(d1, d1_len, d1_rev_arr); + dname_reverse(d2, d2_len, d2_rev_arr); + + int res = 0; + while (res == 0 && d1_rev != NULL) { + res = lf_cmp(d1_rev, d2_rev); + d1_rev = knot_wire_next_label(d1_rev, NULL); + d2_rev = knot_wire_next_label(d2_rev, NULL); + } + + kr_require(res != 0 || d2_rev == NULL); + return res; +} + + +/** + * Check whether this nsec proves that there is no closer match for sname. + * + * @param nsec NSEC RRSet. + * @param sname Searched name. + * @return 0 if proves, >0 if not (abs(ENOENT) or abs(EEXIST)), or error code (<0). + */ +static int nsec_covers(const knot_rrset_t *nsec, const knot_dname_t *sname) +{ + if (kr_fails_assert(nsec && sname)) + return kr_error(EINVAL); + const int cmp = dname_cmp(sname, nsec->owner); + if (cmp < 0) return abs(ENOENT); /* 'sname' before 'owner', so can't be covered */ + if (cmp == 0) return abs(EEXIST); /* matched, not covered */ + + /* We have to lower-case 'next' with libknot >= 2.7; see also RFC 6840 5.1. */ + knot_dname_t next[KNOT_DNAME_MAXLEN]; + int ret = knot_dname_to_wire(next, knot_nsec_next(nsec->rrs.rdata), sizeof(next)); + if (kr_fails_assert(ret >= 0)) + return kr_error(ret); + knot_dname_to_lower(next); + + /* If NSEC 'owner' >= 'next', it means that there is nothing after 'owner' */ + const bool is_last_nsec = dname_cmp(nsec->owner, next) >= 0; + const bool in_range = is_last_nsec || dname_cmp(sname, next) < 0; + if (!in_range) + return abs(ENOENT); + /* Before returning kr_ok(), we have to check a special case: + * sname might be under delegation from owner and thus + * not in the zone of this NSEC at all. + */ + if (knot_dname_in_bailiwick(sname, nsec->owner) <= 0) + return kr_ok(); + const uint8_t *bm = knot_nsec_bitmap(nsec->rrs.rdata); + uint16_t bm_size = knot_nsec_bitmap_len(nsec->rrs.rdata); + + return kr_nsec_children_in_zone_check(bm, bm_size); +} + +int kr_nsec_bitmap_nodata_check(const uint8_t *bm, uint16_t bm_size, uint16_t type, const knot_dname_t *owner) +{ + const int NO_PROOF = abs(ENOENT); + if (!bm || !owner) + return kr_error(EINVAL); + if (dnssec_nsec_bitmap_contains(bm, bm_size, type)) + return NO_PROOF; + + if (type != KNOT_RRTYPE_CNAME + && dnssec_nsec_bitmap_contains(bm, bm_size, KNOT_RRTYPE_CNAME)) { + return NO_PROOF; + } + /* Special behavior around zone cuts. */ + switch (type) { + case KNOT_RRTYPE_DS: + /* Security feature: in case of DS also check for SOA + * non-existence to be more certain that we don't hold + * a child-side NSEC by some mistake (e.g. when forwarding). + * See RFC4035 5.2, next-to-last paragraph. + * This doesn't apply for root DS as it doesn't exist in DNS hierarchy. + */ + if (owner[0] != '\0' && dnssec_nsec_bitmap_contains(bm, bm_size, KNOT_RRTYPE_SOA)) + return NO_PROOF; + break; + case KNOT_RRTYPE_CNAME: + /* Exception from the `default` rule. It's perhaps disputable, + * but existence of CNAME at zone apex is not allowed, so we + * consider a parent-side record to be enough to prove non-existence. */ + break; + default: + /* Parent-side delegation record isn't authoritative for non-DS; + * see RFC6840 4.1. */ + if (dnssec_nsec_bitmap_contains(bm, bm_size, KNOT_RRTYPE_NS) + && !dnssec_nsec_bitmap_contains(bm, bm_size, KNOT_RRTYPE_SOA)) { + return NO_PROOF; + } + /* LATER(opt): perhaps short-circuit test if we repeat it here. */ + } + + return kr_ok(); +} + +/// Convenience wrapper for kr_nsec_bitmap_nodata_check() +static int no_data_response_check_rrtype(const knot_rrset_t *nsec, uint16_t type) +{ + if (kr_fails_assert(nsec && nsec->type == KNOT_RRTYPE_NSEC)) + return kr_error(EINVAL); + const uint8_t *bm = knot_nsec_bitmap(nsec->rrs.rdata); + uint16_t bm_size = knot_nsec_bitmap_len(nsec->rrs.rdata); + return kr_nsec_bitmap_nodata_check(bm, bm_size, type, nsec->owner); +} + +int kr_nsec_wildcard_answer_response_check(const knot_pkt_t *pkt, knot_section_t section_id, + const knot_dname_t *sname) +{ + const knot_pktsection_t *sec = knot_pkt_section(pkt, section_id); + if (!sec || !sname) + return kr_error(EINVAL); + + for (unsigned i = 0; i < sec->count; ++i) { + const knot_rrset_t *rrset = knot_pkt_rr(sec, i); + if (rrset->type != KNOT_RRTYPE_NSEC) + continue; + if (nsec_covers(rrset, sname) == 0) + return kr_ok(); + } + + return kr_error(ENOENT); +} + +int kr_nsec_negative(const ranked_rr_array_t *rrrs, uint32_t qry_uid, + const knot_dname_t *sname, uint16_t stype) +{ + // We really only consider the (canonically) first NSEC in each RRset. + // Using same owner with differing content probably isn't useful for NSECs anyway. + // Many other parts of code do the same, too. + if (kr_fails_assert(rrrs && sname)) + return kr_error(EINVAL); + + // Terminology: https://datatracker.ietf.org/doc/html/rfc4592#section-3.3.1 + int clencl_labels = -1; // the label count of the closest encloser of sname + for (int i = rrrs->len - 1; i >= 0; --i) { // NSECs near the end typically + const knot_rrset_t *nsec = rrrs->at[i]->rr; + bool ok = rrrs->at[i]->qry_uid == qry_uid + && nsec->type == KNOT_RRTYPE_NSEC + && kr_rank_test(rrrs->at[i]->rank, KR_RANK_SECURE); + if (!ok) continue; + const int covers = nsec_covers(nsec, sname); + if (covers == abs(EEXIST) + && no_data_response_check_rrtype(nsec, stype) == 0) { + return PKT_NODATA; // proven NODATA by matching NSEC + } + if (covers != 0) continue; + + // We have to lower-case 'next' with libknot >= 2.7; see also RFC 6840 5.1. + // LATER(optim.): it's duplicate work with the nsec_covers() call. + knot_dname_t next[KNOT_DNAME_MAXLEN]; + int ret = knot_dname_to_wire(next, knot_nsec_next(nsec->rrs.rdata), sizeof(next)); + if (kr_fails_assert(ret >= 0)) + return kr_error(ret); + knot_dname_to_lower(next); + + clencl_labels = MAX(knot_dname_matched_labels(nsec->owner, sname), + knot_dname_matched_labels(sname, next)); + break; // reduce indentation again + } + + if (clencl_labels < 0) + return kr_error(ENOENT); + const int sname_labels = knot_dname_labels(sname, NULL); + if (sname_labels == clencl_labels) + return PKT_NODATA; // proven NODATA; sname is an empty non-terminal + + // Explicitly construct name for the corresponding source of synthesis. + knot_dname_t ssynth[KNOT_DNAME_MAXLEN + 2]; + ssynth[0] = 1; + ssynth[1] = '*'; + const knot_dname_t *clencl = sname; + for (int l = sname_labels; l > clencl_labels; --l) + clencl = knot_wire_next_label(clencl, NULL); + (void)!!knot_dname_store(&ssynth[2], clencl); + + // Try to (dis)prove the source of synthesis by a covering or matching NSEC. + for (int i = rrrs->len - 1; i >= 0; --i) { // NSECs near the end typically + const knot_rrset_t *nsec = rrrs->at[i]->rr; + bool ok = rrrs->at[i]->qry_uid == qry_uid + && nsec->type == KNOT_RRTYPE_NSEC + && kr_rank_test(rrrs->at[i]->rank, KR_RANK_SECURE); + if (!ok) continue; + const int covers = nsec_covers(nsec, ssynth); + if (covers == abs(EEXIST)) { + int ret = no_data_response_check_rrtype(nsec, stype); + if (ret == 0) return PKT_NODATA; // proven NODATA by wildcard NSEC + // TODO: also try expansion? Or at least a different return code? + } else if (covers == 0) { + return PKT_NXDOMAIN | PKT_NODATA; + } + } + return kr_error(ENOENT); +} + +int kr_nsec_ref_to_unsigned(const ranked_rr_array_t *rrrs, uint32_t qry_uid, + const knot_dname_t *sname) +{ + for (int i = rrrs->len - 1; i >= 0; --i) { // NSECs near the end typically + const knot_rrset_t *nsec = rrrs->at[i]->rr; + bool ok = rrrs->at[i]->qry_uid == qry_uid + && nsec->type == KNOT_RRTYPE_NSEC + && kr_rank_test(rrrs->at[i]->rank, KR_RANK_SECURE) + // avoid any possibility of getting tricked in deeper zones + && knot_dname_in_bailiwick(sname, nsec->owner) >= 0; + if (!ok) continue; + + kr_assert(nsec->rrs.rdata); + const uint8_t *bm = knot_nsec_bitmap(nsec->rrs.rdata); + uint16_t bm_size = knot_nsec_bitmap_len(nsec->rrs.rdata); + ok = ok && dnssec_nsec_bitmap_contains(bm, bm_size, KNOT_RRTYPE_NS) + && !dnssec_nsec_bitmap_contains(bm, bm_size, KNOT_RRTYPE_DS) + && !dnssec_nsec_bitmap_contains(bm, bm_size, KNOT_RRTYPE_SOA); + if (ok) return kr_ok(); + } + return kr_error(DNSSEC_NOT_FOUND); +} + +int kr_nsec_matches_name_and_type(const knot_rrset_t *nsec, + const knot_dname_t *name, uint16_t type) +{ + /* It's not secure enough to just check a single bit for (some) other types, + * but we (currently) only use this API for NS. See RFC 6840 sec. 4. */ + if (kr_fails_assert(type == KNOT_RRTYPE_NS && nsec && nsec->rrs.rdata && name)) + return kr_error(EINVAL); + if (!knot_dname_is_equal(nsec->owner, name)) + return kr_error(ENOENT); + const uint8_t *bm = knot_nsec_bitmap(nsec->rrs.rdata); + uint16_t bm_size = knot_nsec_bitmap_len(nsec->rrs.rdata); + if (dnssec_nsec_bitmap_contains(bm, bm_size, type)) { + return kr_ok(); + } else { + return kr_error(ENOENT); + } +} diff --git a/lib/dnssec/nsec.h b/lib/dnssec/nsec.h new file mode 100644 index 0000000..a173fa5 --- /dev/null +++ b/lib/dnssec/nsec.h @@ -0,0 +1,69 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <libknot/packet/pkt.h> + +#include "lib/layer/iterate.h" + +/** + * Check bitmap that child names are contained in the same zone. + * @note see RFC6840 4.1. + * @param bm Bitmap from NSEC or NSEC3. + * @param bm_size Bitmap size. + * @return 0 if they are, >0 if not (abs(ENOENT)), <0 on error. + */ +int kr_nsec_children_in_zone_check(const uint8_t *bm, uint16_t bm_size); + +/** + * Check an NSEC or NSEC3 bitmap for NODATA for a type. + * @param bm Bitmap. + * @param bm_size Bitmap size. + * @param type RR type to check. + * @param owner NSEC record owner. + * @note This includes special checks for zone cuts, e.g. from RFC 6840 sec. 4. + * @return 0, abs(ENOENT) (no proof), kr_error(EINVAL) + */ +int kr_nsec_bitmap_nodata_check(const uint8_t *bm, uint16_t bm_size, uint16_t type, const knot_dname_t *owner); + +/** + * Wildcard answer response check (RFC4035 3.1.3.3). + * @param pkt Packet structure to be processed. + * @param section_id Packet section to be processed. + * @param sname Name to be checked. + * @return 0 or error code. + */ +int kr_nsec_wildcard_answer_response_check(const knot_pkt_t *pkt, knot_section_t section_id, + const knot_dname_t *sname); + +/** + * Search for a negative proof for sname+stype among validated NSECs. + * + * @param rrrs list of RRs to search; typically kr_request::auth_selected + * @param qry_uid only consider NSECs from this packet, for better efficiency + * @return negative error code, or PKT_NXDOMAIN | PKT_NODATA (both for NXDOMAIN) + */ +int kr_nsec_negative(const ranked_rr_array_t *rrrs, uint32_t qry_uid, + const knot_dname_t *sname, uint16_t stype); + +/** + * Referral to unsigned subzone check (RFC4035 5.2). + * + * @param rrrs list of RRs to search; typically kr_request::auth_selected + * @param qry_uid only consider NSECs from this packet, for better efficiency + * @return 0 or negative error code, in particular DNSSEC_NOT_FOUND + */ +int kr_nsec_ref_to_unsigned(const ranked_rr_array_t *rrrs, uint32_t qry_uid, + const knot_dname_t *sname); + +/** + * Checks whether supplied NSEC RR matches the supplied name and type. + * @param nsec NSEC RR. + * @param name Name to be checked. + * @param type Type to be checked. Only use with NS! TODO (+copy&paste NSEC3) + * @return 0 or error code. + */ +int kr_nsec_matches_name_and_type(const knot_rrset_t *nsec, + const knot_dname_t *name, uint16_t type); diff --git a/lib/dnssec/nsec3.c b/lib/dnssec/nsec3.c new file mode 100644 index 0000000..037d5bd --- /dev/null +++ b/lib/dnssec/nsec3.c @@ -0,0 +1,722 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <string.h> + +#include <libdnssec/binary.h> +#include <libdnssec/error.h> +#include <libdnssec/nsec.h> +#include <libknot/descriptor.h> +#include <contrib/base32hex.h> +#include <libknot/rrset.h> +#include <libknot/rrtype/nsec3.h> + +#include "lib/defines.h" +#include "lib/dnssec/nsec.h" +#include "lib/dnssec/nsec3.h" +#include "lib/utils.h" + +#define OPT_OUT_BIT 0x01 + +//#define FLG_CLOSEST_ENCLOSER (1 << 0) +#define FLG_CLOSEST_PROVABLE_ENCLOSER (1 << 1) +#define FLG_NAME_COVERED (1 << 2) +#define FLG_NAME_MATCHED (1 << 3) +#define FLG_TYPE_BIT_MISSING (1 << 4) +#define FLG_CNAME_BIT_MISSING (1 << 5) + +/** + * Obtains NSEC3 parameters from RR. + * @param params NSEC3 parameters structure to be set. + * @param nsec3 NSEC3 RR containing the parameters. + * @return 0 or error code. + */ +static int nsec3_parameters(dnssec_nsec3_params_t *params, const knot_rrset_t *nsec3) +{ + if (kr_fails_assert(params && nsec3)) + return kr_error(EINVAL); + + const knot_rdata_t *rr = knot_rdataset_at(&nsec3->rrs, 0); + if (kr_fails_assert(rr)) + return kr_error(EINVAL); + + /* Every NSEC3 RR contains data from NSEC3PARAMS. */ + const size_t SALT_OFFSET = 5; /* First 5 octets contain { Alg, Flags, Iterations, Salt length } */ + dnssec_binary_t rdata = { + .size = SALT_OFFSET + (size_t)knot_nsec3_salt_len(nsec3->rrs.rdata), + .data = /*const-cast*/(uint8_t *)rr->data, + }; + if (rdata.size > rr->len) + return kr_error(EMSGSIZE); + + int ret = dnssec_nsec3_params_from_rdata(params, &rdata); + if (ret != DNSSEC_EOK) + return kr_error(EINVAL); + + return kr_ok(); +} + +/** + * Computes a hash of a given domain name. + * @param hash Resulting hash, must be freed. + * @param params NSEC3 parameters. + * @param name Domain name to be hashed. + * @return 0 or error code. + */ +static int hash_name(dnssec_binary_t *hash, const dnssec_nsec3_params_t *params, + const knot_dname_t *name) +{ + if (kr_fails_assert(hash && params)) + return kr_error(EINVAL); + if (!name) + return kr_error(EINVAL); + if (kr_fails_assert(params->iterations <= KR_NSEC3_MAX_ITERATIONS)) { + /* This if is mainly defensive; it shouldn't happen. */ + return kr_error(EINVAL); + } + + dnssec_binary_t dname = { + .size = knot_dname_size(name), + .data = (uint8_t *) name, + }; + + int ret = dnssec_nsec3_hash(&dname, params, hash); + if (ret != DNSSEC_EOK) { + return kr_error(EINVAL); + } + + return kr_ok(); +} + +/** + * Read hash from NSEC3 owner name and store its binary form. + * @param hash Buffer to be written. + * @param max_hash_size Maximal has size. + * @param nsec3 NSEC3 RR. + * @return 0 or error code. + */ +static int read_owner_hash(dnssec_binary_t *hash, size_t max_hash_size, const knot_rrset_t *nsec3) +{ + if (kr_fails_assert(hash && nsec3 && hash->data)) + return kr_error(EINVAL); + + int32_t ret = base32hex_decode(nsec3->owner + 1, nsec3->owner[0], hash->data, max_hash_size); + if (ret < 0) + return kr_error(EILSEQ); + hash->size = ret; + + return kr_ok(); +} + +#define MAX_HASH_BYTES 64 +/** + * Closest (provable) encloser match (RFC5155 7.2.1, bullet 1). + * @param flags Flags to be set according to check outcome. + * @param nsec3 NSEC3 RR. + * @param name Name to be checked. + * @param skipped Number of skipped labels to find closest (provable) match. + * @return 0 or error code. + */ +static int closest_encloser_match(int *flags, const knot_rrset_t *nsec3, + const knot_dname_t *name, unsigned *skipped) +{ + if (kr_fails_assert(flags && nsec3 && name && skipped)) + return kr_error(EINVAL); + + uint8_t hash_data[MAX_HASH_BYTES] = {0, }; + dnssec_binary_t owner_hash = { 0, hash_data }; + dnssec_nsec3_params_t params = { 0, }; + dnssec_binary_t name_hash = { 0, }; + + int ret = read_owner_hash(&owner_hash, MAX_HASH_BYTES, nsec3); + if (ret != 0) + goto fail; + + ret = nsec3_parameters(¶ms, nsec3); + if (ret != 0) + goto fail; + + /* Root label has no encloser */ + if (!name[0]) { + ret = kr_error(ENOENT); + goto fail; + } + + const knot_dname_t *encloser = knot_wire_next_label(name, NULL); + *skipped = 1; + + while(encloser) { + ret = hash_name(&name_hash, ¶ms, encloser); + if (ret != 0) + goto fail; + + if ((owner_hash.size == name_hash.size) && + (memcmp(owner_hash.data, name_hash.data, owner_hash.size) == 0)) { + dnssec_binary_free(&name_hash); + *flags |= FLG_CLOSEST_PROVABLE_ENCLOSER; + break; + } + + dnssec_binary_free(&name_hash); + + if (!encloser[0]) + break; + encloser = knot_wire_next_label(encloser, NULL); + ++(*skipped); + } + + ret = kr_ok(); + +fail: + if (params.salt.data) + dnssec_nsec3_params_free(¶ms); + if (name_hash.data) + dnssec_binary_free(&name_hash); + return ret; +} + +/** + * Checks whether NSEC3 RR covers the supplied name (RFC5155 7.2.1, bullet 2). + * @param flags Flags to be set according to check outcome. + * @param nsec3 NSEC3 RR. + * @param name Name to be checked. + * @return 0 or error code. + */ +static int covers_name(int *flags, const knot_rrset_t *nsec3, const knot_dname_t *name) +{ + if (kr_fails_assert(flags && nsec3 && name)) + return kr_error(EINVAL); + + uint8_t hash_data[MAX_HASH_BYTES] = { 0, }; + dnssec_binary_t owner_hash = { 0, hash_data }; + dnssec_nsec3_params_t params = { 0, }; + dnssec_binary_t name_hash = { 0, }; + + int ret = read_owner_hash(&owner_hash, MAX_HASH_BYTES, nsec3); + if (ret != 0) + goto fail; + + ret = nsec3_parameters(¶ms, nsec3); + if (ret != 0) + goto fail; + + ret = hash_name(&name_hash, ¶ms, name); + if (ret != 0) + goto fail; + + uint8_t next_size = knot_nsec3_next_len(nsec3->rrs.rdata); + const uint8_t *next_hash = knot_nsec3_next(nsec3->rrs.rdata); + + if ((next_size > 0) && (owner_hash.size == next_size) && (name_hash.size == next_size)) { + /* All hash lengths must be same. */ + const uint8_t *ownerd = owner_hash.data; + const uint8_t *nextd = next_hash; + int covered = 0; + int greater_then_owner = (memcmp(ownerd, name_hash.data, next_size) < 0); + int less_then_next = (memcmp(name_hash.data, nextd, next_size) < 0); + if (memcmp(ownerd, nextd, next_size) < 0) { + /* + * 0 (...) owner ... next (...) MAX + * ^ + * name + * ==> + * (owner < name) && (name < next) + */ + covered = ((greater_then_owner) && (less_then_next)); + } else { + /* + * owner ... MAX, 0 ... next + * ^ ^ ^ + * name name name + * => + * (owner < name) || (name < next) + */ + covered = ((greater_then_owner) || (less_then_next)); + } + + if (covered) { + *flags |= FLG_NAME_COVERED; + + uint8_t nsec3_flags = knot_nsec3_flags(nsec3->rrs.rdata); + if (nsec3_flags & ~OPT_OUT_BIT) { + /* RFC5155 3.1.2 */ + ret = kr_error(EINVAL); + } else { + ret = kr_ok(); + } + } + } + +fail: + if (params.salt.data) + dnssec_nsec3_params_free(¶ms); + if (name_hash.data) + dnssec_binary_free(&name_hash); + return ret; +} + +/** + * Checks whether NSEC3 RR has the opt-out bit set. + * @param flags Flags to be set according to check outcome. + * @param nsec3 NSEC3 RR. + * @param name Name to be checked. + * @return 0 or error code. + */ +static bool has_optout(const knot_rrset_t *nsec3) +{ + if (!nsec3) + return false; + + uint8_t nsec3_flags = knot_nsec3_flags(nsec3->rrs.rdata); + if (nsec3_flags & ~OPT_OUT_BIT) { + /* RFC5155 3.1.2 */ + return false; + } + + return nsec3_flags & OPT_OUT_BIT; +} + +/** + * Checks whether NSEC3 RR matches the supplied name. + * @param flags Flags to be set according to check outcome. + * @param nsec3 NSEC3 RR. + * @param name Name to be checked. + * @return 0 if matching, >0 if not (abs(ENOENT)), or error code (<0). + */ +static int matches_name(const knot_rrset_t *nsec3, const knot_dname_t *name) +{ + if (kr_fails_assert(nsec3 && name)) + return kr_error(EINVAL); + + uint8_t hash_data[MAX_HASH_BYTES] = { 0, }; + dnssec_binary_t owner_hash = { 0, hash_data }; + dnssec_nsec3_params_t params = { 0, }; + dnssec_binary_t name_hash = { 0, }; + + int ret = read_owner_hash(&owner_hash, MAX_HASH_BYTES, nsec3); + if (ret != 0) + goto fail; + + ret = nsec3_parameters(¶ms, nsec3); + if (ret != 0) + goto fail; + + ret = hash_name(&name_hash, ¶ms, name); + if (ret != 0) + goto fail; + + if ((owner_hash.size == name_hash.size) && + (memcmp(owner_hash.data, name_hash.data, owner_hash.size) == 0)) { + ret = kr_ok(); + } else { + ret = abs(ENOENT); + } + +fail: + if (params.salt.data) + dnssec_nsec3_params_free(¶ms); + if (name_hash.data) + dnssec_binary_free(&name_hash); + return ret; +} +#undef MAX_HASH_BYTES + +/** + * Prepends an asterisk label to given name. + * + * @param tgt Target buffer to write domain name into. + * @param name Name to be added to the asterisk. + * @return Size of the resulting name or error code. + */ +static int prepend_asterisk(uint8_t *tgt, size_t maxlen, const knot_dname_t *name) +{ + if (kr_fails_assert(maxlen >= 3)) + return kr_error(EINVAL); + memcpy(tgt, "\1*", 3); + return knot_dname_to_wire(tgt + 2, name, maxlen - 2); +} + +/** + * Closest encloser proof (RFC5155 7.2.1). + * @note No RRSIGs are validated. + * @param pkt Packet structure to be processed. + * @param section_id Packet section to be processed. + * @param sname Name to be checked. + * @param encloser_name Returned matching encloser name, if found. + * @param matching_encloser_nsec3 Pointer to matching encloser NSEC RRSet. + * @param covering_next_nsec3 Pointer to covering next closer NSEC3 RRSet. + * @return 0 or error code. + */ +static int closest_encloser_proof(const knot_pkt_t *pkt, + knot_section_t section_id, + const knot_dname_t *sname, + const knot_dname_t **encloser_name, + const knot_rrset_t **matching_encloser_nsec3, + const knot_rrset_t **covering_next_nsec3) +{ + const knot_pktsection_t *sec = knot_pkt_section(pkt, section_id); + if (!sec || !sname) + return kr_error(EINVAL); + + const knot_rrset_t *matching = NULL; + const knot_rrset_t *covering = NULL; + + int flags = 0; + const knot_dname_t *next_closer = NULL; + for (unsigned i = 0; i < sec->count; ++i) { + const knot_rrset_t *rrset = knot_pkt_rr(sec, i); + if (rrset->type != KNOT_RRTYPE_NSEC3) + continue; + /* Also skip the NSEC3-to-match an ancestor of sname if it's + * a parent-side delegation, as that would mean the owner + * does not really exist (authoritatively in this zone, + * even in case of opt-out). + */ + const uint8_t *bm = knot_nsec3_bitmap(rrset->rrs.rdata); + uint16_t bm_size = knot_nsec3_bitmap_len(rrset->rrs.rdata); + if (kr_nsec_children_in_zone_check(bm, bm_size) != 0) + continue; /* no fatal errors from bad RRs */ + /* Match the NSEC3 to sname or one of its ancestors. */ + unsigned skipped = 0; + flags = 0; + int ret = closest_encloser_match(&flags, rrset, sname, &skipped); + if (ret != 0) + return ret; + if (!(flags & FLG_CLOSEST_PROVABLE_ENCLOSER)) + continue; + matching = rrset; + /* Construct the next closer name and try to cover it. */ + --skipped; + next_closer = sname; + for (unsigned j = 0; j < skipped; ++j) { + if (kr_fails_assert(next_closer[0])) + return kr_error(EINVAL); + next_closer = knot_wire_next_label(next_closer, NULL); + } + for (unsigned j = 0; j < sec->count; ++j) { + const knot_rrset_t *rrset_j = knot_pkt_rr(sec, j); + if (rrset_j->type != KNOT_RRTYPE_NSEC3) + continue; + ret = covers_name(&flags, rrset_j, next_closer); + if (ret != 0) + return ret; + if (flags & FLG_NAME_COVERED) { + covering = rrset_j; + break; + } + } + if (flags & FLG_NAME_COVERED) + break; + flags = 0; // + } + + if ((flags & FLG_CLOSEST_PROVABLE_ENCLOSER) && (flags & FLG_NAME_COVERED) && next_closer) { + if (encloser_name && next_closer[0]) + *encloser_name = knot_wire_next_label(next_closer, NULL); + if (matching_encloser_nsec3) + *matching_encloser_nsec3 = matching; + if (covering_next_nsec3) + *covering_next_nsec3 = covering; + return kr_ok(); + } + + return kr_error(ENOENT); +} + +/** + * Check whether any NSEC3 RR covers a wildcard RR at the closer encloser. + * @param pkt Packet structure to be processed. + * @param section_id Packet section to be processed. + * @param encloser Closest (provable) encloser domain name. + * @return 0 or error code: + * KNOT_ERANGE - NSEC3 RR (that covers a wildcard) + * has been found, but has opt-out flag set; + * otherwise - error. + */ +static int covers_closest_encloser_wildcard(const knot_pkt_t *pkt, knot_section_t section_id, + const knot_dname_t *encloser) +{ + const knot_pktsection_t *sec = knot_pkt_section(pkt, section_id); + if (!sec || !encloser) + return kr_error(EINVAL); + + uint8_t wildcard[KNOT_DNAME_MAXLEN]; + wildcard[0] = 1; + wildcard[1] = '*'; + int encloser_len = knot_dname_size(encloser); + if (encloser_len < 0) + return encloser_len; + memcpy(wildcard + 2, encloser, encloser_len); + + int flags = 0; + for (unsigned i = 0; i < sec->count; ++i) { + const knot_rrset_t *rrset = knot_pkt_rr(sec, i); + if (rrset->type != KNOT_RRTYPE_NSEC3) + continue; + int ret = covers_name(&flags, rrset, wildcard); + if (ret != 0) + return ret; + if (flags & FLG_NAME_COVERED) { + return has_optout(rrset) ? + kr_error(KNOT_ERANGE) : kr_ok(); + } + } + + return kr_error(ENOENT); +} + +int kr_nsec3_name_error_response_check(const knot_pkt_t *pkt, knot_section_t section_id, + const knot_dname_t *sname) +{ + const knot_dname_t *encloser = NULL; + const knot_rrset_t *covering_next_nsec3 = NULL; + int ret = closest_encloser_proof(pkt, section_id, sname, + &encloser, NULL, &covering_next_nsec3); + if (ret != 0) + return ret; + ret = covers_closest_encloser_wildcard(pkt, section_id, encloser); + if (ret != 0) { + /* OK, but NSEC3 for wildcard at encloser has opt-out; + * or error */ + return ret; + } + /* Closest encloser proof is OK and + * NSEC3 for wildcard has been found and optout flag is not set. + * Now check if NSEC3 that covers next closer name has opt-out. */ + return has_optout(covering_next_nsec3) ? + kr_error(KNOT_ERANGE) : kr_ok(); +} + +/** + * Search the packet section for a matching NSEC3 with nodata-proving bitmap. + * @param pkt Packet structure to be processed. + * @param section_id Packet section to be processed. + * @param sname Name to be checked. + * @param stype Type to be checked. + * @return 0 or error code. + * @note This does NOT check the opt-out case if type is DS; + * see RFC 5155 8.6. + */ +static int nodata_find(const knot_pkt_t *pkt, knot_section_t section_id, + const knot_dname_t *name, const uint16_t type) +{ + const knot_pktsection_t *sec = knot_pkt_section(pkt, section_id); + if (!sec || !name) + return kr_error(EINVAL); + + for (unsigned i = 0; i < sec->count; ++i) { + const knot_rrset_t *nsec3 = knot_pkt_rr(sec, i); + /* Records causing any errors are simply skipped. */ + if (nsec3->type != KNOT_RRTYPE_NSEC3 + || matches_name(nsec3, name) != kr_ok()) { + continue; + /* LATER(optim.): we repeatedly recompute the hash of `name` */ + } + + const uint8_t *bm = knot_nsec3_bitmap(nsec3->rrs.rdata); + uint16_t bm_size = knot_nsec3_bitmap_len(nsec3->rrs.rdata); + if (kr_nsec_bitmap_nodata_check(bm, bm_size, type, nsec3->owner) == kr_ok()) + return kr_ok(); + } + + return kr_error(ENOENT); +} + +/** + * Check whether NSEC3 RR matches a wildcard at the closest encloser and has given type bit missing. + * @param pkt Packet structure to be processed. + * @param section_id Packet section to be processed. + * @param encloser Closest (provable) encloser domain name. + * @param stype Type to be checked. + * @return 0 or error code. + */ +static int matches_closest_encloser_wildcard(const knot_pkt_t *pkt, knot_section_t section_id, + const knot_dname_t *encloser, uint16_t stype) +{ + const knot_pktsection_t *sec = knot_pkt_section(pkt, section_id); + if (!sec || !encloser) + return kr_error(EINVAL); + + uint8_t wildcard[KNOT_DNAME_MAXLEN]; /**< the source of synthesis */ + int ret = prepend_asterisk(wildcard, sizeof(wildcard), encloser); + if (ret < 0) + return ret; + kr_require(ret >= 3); + return nodata_find(pkt, section_id, wildcard, stype); +} + +int kr_nsec3_wildcard_answer_response_check(const knot_pkt_t *pkt, knot_section_t section_id, + const knot_dname_t *sname, int trim_to_next) +{ + const knot_pktsection_t *sec = knot_pkt_section(pkt, section_id); + if (!sec || !sname) + return kr_error(EINVAL); + + /* Compute the next closer name. */ + for (int i = 0; i < trim_to_next; ++i) { + if (kr_fails_assert(sname[0])) + return kr_error(EINVAL); + sname = knot_wire_next_label(sname, NULL); + } + + int flags = 0; + for (unsigned i = 0; i < sec->count; ++i) { + const knot_rrset_t *rrset = knot_pkt_rr(sec, i); + if (rrset->type != KNOT_RRTYPE_NSEC3) + continue; + if (knot_nsec3_iters(rrset->rrs.rdata) > KR_NSEC3_MAX_ITERATIONS) { + /* Avoid hashing with too many iterations. + * If we get here, the `sname` wildcard probably ends up bogus, + * but it gets downgraded to KR_RANK_INSECURE when validator + * gets to verifying one of these over-limit NSEC3 RRs. */ + continue; + } + int ret = covers_name(&flags, rrset, sname); + if (ret != 0) + return ret; + if (flags & FLG_NAME_COVERED) { + return has_optout(rrset) ? + kr_error(KNOT_ERANGE) : kr_ok(); + } + } + + return kr_error(ENOENT); +} + + +int kr_nsec3_no_data(const knot_pkt_t *pkt, knot_section_t section_id, + const knot_dname_t *sname, uint16_t stype) +{ + /* DS record may be also matched by an existing NSEC3 RR. */ + int ret = nodata_find(pkt, section_id, sname, stype); + if (ret == 0) { + /* Satisfies RFC5155 8.5 and 8.6, both first paragraph. */ + return ret; + } + + /* Find closest provable encloser. */ + const knot_dname_t *encloser_name = NULL; + const knot_rrset_t *covering_next_nsec3 = NULL; + ret = closest_encloser_proof(pkt, section_id, sname, &encloser_name, + NULL, &covering_next_nsec3); + if (ret != 0) + return ret; + + if (kr_fails_assert(encloser_name && covering_next_nsec3)) + return kr_error(EFAULT); + ret = matches_closest_encloser_wildcard(pkt, section_id, + encloser_name, stype); + if (ret == 0) { + /* Satisfies RFC5155 8.7 */ + if (has_optout(covering_next_nsec3)) { + /* Opt-out is detected. + * Despite the fact that all records + * in the packet can be properly signed, + * AD bit must not be set due to rfc5155 9.2. + * Return appropriate code to the caller */ + ret = kr_error(KNOT_ERANGE); + } + return ret; + } + + if (!has_optout(covering_next_nsec3)) { + /* Bogus */ + ret = kr_error(ENOENT); + } else { + /* + * Satisfies RFC5155 8.6 (QTYPE == DS), 2nd paragraph. + * Also satisfies ERRATA 3441 8.5 (QTYPE != DS), 3rd paragraph. + * - (wildcard) empty nonterminal + * derived from insecure delegation. + * Denial of existence can not be proven. + * Set error code to proceed insecure. + */ + ret = kr_error(KNOT_ERANGE); + } + + return ret; +} + +int kr_nsec3_ref_to_unsigned(const knot_pkt_t *pkt) +{ + const knot_pktsection_t *sec = knot_pkt_section(pkt, KNOT_AUTHORITY); + if (!sec) + return kr_error(EINVAL); + for (unsigned i = 0; i < sec->count; ++i) { + const knot_rrset_t *ns = knot_pkt_rr(sec, i); + if (ns->type == KNOT_RRTYPE_DS) + return kr_error(EEXIST); + if (ns->type != KNOT_RRTYPE_NS) + continue; + + bool nsec3_found = false; + for (unsigned j = 0; j < sec->count; ++j) { + const knot_rrset_t *nsec3 = knot_pkt_rr(sec, j); + if (nsec3->type == KNOT_RRTYPE_DS) + return kr_error(EEXIST); + if (nsec3->type != KNOT_RRTYPE_NSEC3) + continue; + nsec3_found = true; + /* nsec3 found, check if owner name matches the delegation name. + * Just skip in case of *any* errors. */ + if (matches_name(nsec3, ns->owner) != kr_ok()) + continue; + + const uint8_t *bm = knot_nsec3_bitmap(nsec3->rrs.rdata); + uint16_t bm_size = knot_nsec3_bitmap_len(nsec3->rrs.rdata); + if (!bm) + return kr_error(EINVAL); + if (dnssec_nsec_bitmap_contains(bm, bm_size, + KNOT_RRTYPE_NS) && + !dnssec_nsec_bitmap_contains(bm, bm_size, + KNOT_RRTYPE_DS) && + !dnssec_nsec_bitmap_contains(bm, bm_size, + KNOT_RRTYPE_SOA)) { + /* Satisfies rfc5155, 8.9. paragraph 2 */ + return kr_ok(); + } + } + if (!nsec3_found) + return kr_error(DNSSEC_NOT_FOUND); + /* nsec3 that matches the delegation was not found. + * Check rfc5155, 8.9. paragraph 4. + * Find closest provable encloser. + */ + const knot_dname_t *encloser_name = NULL; + const knot_rrset_t *covering_next_nsec3 = NULL; + int ret = closest_encloser_proof(pkt, KNOT_AUTHORITY, ns->owner, + &encloser_name, NULL, &covering_next_nsec3); + if (ret != 0) + return kr_error(EINVAL); + + if (has_optout(covering_next_nsec3)) { + return kr_error(KNOT_ERANGE); + } else { + return kr_error(EINVAL); + } + } + return kr_error(EINVAL); +} + +int kr_nsec3_matches_name_and_type(const knot_rrset_t *nsec3, + const knot_dname_t *name, uint16_t type) +{ + /* It's not secure enough to just check a single bit for (some) other types, + * but we don't (currently) only use this API for NS. See RFC 6840 sec. 4. + */ + if (kr_fails_assert(type == KNOT_RRTYPE_NS)) + return kr_error(EINVAL); + int ret = matches_name(nsec3, name); + if (ret) + return kr_error(ret); + const uint8_t *bm = knot_nsec3_bitmap(nsec3->rrs.rdata); + uint16_t bm_size = knot_nsec3_bitmap_len(nsec3->rrs.rdata); + if (!bm) + return kr_error(EINVAL); + if (dnssec_nsec_bitmap_contains(bm, bm_size, type)) { + return kr_ok(); + } else { + return kr_error(ENOENT); + } +} diff --git a/lib/dnssec/nsec3.h b/lib/dnssec/nsec3.h new file mode 100644 index 0000000..eb0bd39 --- /dev/null +++ b/lib/dnssec/nsec3.h @@ -0,0 +1,83 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <libknot/packet/pkt.h> + +/** High numbers in NSEC3 iterations don't really help security + * + * ...so we avoid doing all the work. The value is a current compromise; + * zones shooting over get downgraded to insecure status. + * + * Original restriction wasn't that strict: + https://datatracker.ietf.org/doc/html/rfc5155#section-10.3 + * but there is discussion about officially lowering the limits: + https://tools.ietf.org/id/draft-hardaker-dnsop-nsec3-guidance-02.html#section-2.3 + */ +#define KR_NSEC3_MAX_ITERATIONS 150 + +/** + * Name error response check (RFC5155 7.2.2). + * @note No RRSIGs are validated. + * @param pkt Packet structure to be processed. + * @param section_id Packet section to be processed. + * @param sname Name to be checked. + * @return 0 or error code. + */ +int kr_nsec3_name_error_response_check(const knot_pkt_t *pkt, knot_section_t section_id, + const knot_dname_t *sname); + +/** + * Wildcard answer response check (RFC5155 7.2.6). + * @param pkt Packet structure to be processed. + * @param section_id Packet section to be processed. + * @param sname Name to be checked. + * @param trim_to_next Number of labels to remove to obtain next closer name. + * @return 0 or error code: + * KNOT_ERANGE - NSEC3 RR that covers a wildcard + * has been found, but has opt-out flag set; + * otherwise - error. + * Records over KR_NSEC3_MAX_ITERATIONS are skipped, so you probably get kr_error(ENOENT). + */ +int kr_nsec3_wildcard_answer_response_check(const knot_pkt_t *pkt, knot_section_t section_id, + const knot_dname_t *sname, int trim_to_next); + +/** + * Authenticated denial of existence according to RFC5155 8.5 and 8.7. + * @note No RRSIGs are validated. + * @param pkt Packet structure to be processed. + * @param section_id Packet section to be processed. + * @param sname Queried domain name. + * @param stype Queried type. + * @return 0 or error code: + * DNSSEC_NOT_FOUND - neither ds nor nsec records + * were not found. + * KNOT_ERANGE - denial of existence can't be proven + * due to opt-out, otherwise - bogus. + */ +int kr_nsec3_no_data(const knot_pkt_t *pkt, knot_section_t section_id, + const knot_dname_t *sname, uint16_t stype); + +/** + * Referral to unsigned subzone check (RFC5155 8.9). + * @note No RRSIGs are validated. + * @param pkt Packet structure to be processed. + * @return 0 or error code: + * KNOT_ERANGE - denial of existence can't be proven + * due to opt-out. + * EEXIST - ds record was found. + * EINVAL - bogus. + */ +int kr_nsec3_ref_to_unsigned(const knot_pkt_t *pkt); + +/** + * Checks whether supplied NSEC3 RR matches the supplied name and NS type. + * @param nsec3 NSEC3 RR. + * @param name Name to be checked. + * @param type Type to be checked. Only use with NS! TODO + * @return 0 or error code. + */ +int kr_nsec3_matches_name_and_type(const knot_rrset_t *nsec3, + const knot_dname_t *name, uint16_t type); diff --git a/lib/dnssec/signature.c b/lib/dnssec/signature.c new file mode 100644 index 0000000..aadb5cb --- /dev/null +++ b/lib/dnssec/signature.c @@ -0,0 +1,304 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <arpa/inet.h> +#include <string.h> + +#include <libdnssec/error.h> +#include <libdnssec/key.h> +#include <libdnssec/sign.h> +#include <libknot/descriptor.h> +#include <libknot/packet/rrset-wire.h> +#include <libknot/packet/wire.h> +#include <libknot/rrset.h> +#include <libknot/rrtype/rrsig.h> +#include <libknot/rrtype/ds.h> +#include <libknot/wire.h> + +#include "lib/defines.h" +#include "lib/utils.h" +#include "lib/dnssec/signature.h" + +static int authenticate_ds(const dnssec_key_t *key, dnssec_binary_t *ds_rdata, uint8_t digest_type) +{ + /* Compute DS RDATA from the DNSKEY. */ + dnssec_binary_t computed_ds = { 0, }; + int ret = dnssec_key_create_ds(key, digest_type, &computed_ds); + if (ret != DNSSEC_EOK) + goto fail; + + /* DS records contain algorithm, key tag and the digest. + * Therefore the comparison of the two DS is sufficient. + */ + ret = (ds_rdata->size == computed_ds.size) && + (memcmp(ds_rdata->data, computed_ds.data, ds_rdata->size) == 0); + ret = ret ? kr_ok() : kr_error(ENOENT); + +fail: + dnssec_binary_free(&computed_ds); + return kr_error(ret); +} + +int kr_authenticate_referral(const knot_rrset_t *ref, const dnssec_key_t *key) +{ + if (kr_fails_assert(ref && key)) + return kr_error(EINVAL); + if (ref->type != KNOT_RRTYPE_DS) + return kr_error(EINVAL); + + /* Determine whether to ignore SHA1 digests, because: + https://datatracker.ietf.org/doc/html/rfc4509#section-3 + * Now, the RFCs seem to only mention SHA1 and SHA256 (e.g. no SHA384), + * but the most natural extension is to make any other algorithm trump SHA1. + * (Note that the old GOST version is already unsupported by libdnssec.) */ + bool skip_sha1 = false; + knot_rdata_t *rd = ref->rrs.rdata; + for (int i = 0; i < ref->rrs.count; ++i, rd = knot_rdataset_next(rd)) { + const uint8_t algo = knot_ds_digest_type(rd); + if (algo != DNSSEC_KEY_DIGEST_SHA1 && dnssec_algorithm_digest_support(algo)) { + skip_sha1 = true; + break; + } + } + /* But otherwise try all possible DS records. */ + int ret = 0; + rd = ref->rrs.rdata; + for (int i = 0; i < ref->rrs.count; ++i, rd = knot_rdataset_next(rd)) { + const uint8_t algo = knot_ds_digest_type(rd); + if (skip_sha1 && algo == DNSSEC_KEY_DIGEST_SHA1) + continue; + dnssec_binary_t ds_rdata = { + .size = rd->len, + .data = rd->data + }; + ret = authenticate_ds(key, &ds_rdata, algo); + if (ret == 0) /* Found a good DS */ + return kr_ok(); + } + + return kr_error(ret); +} + +/** + * Adjust TTL in wire format. + * @param wire RR Set in wire format. + * @param wire_size Size of the wire data portion. + * @param new_ttl TTL value to be set for all RRs. + * @return 0 or error code. + */ +static int adjust_wire_ttl(uint8_t *wire, size_t wire_size, uint32_t new_ttl) +{ + if (kr_fails_assert(wire)) + return kr_error(EINVAL); + static_assert(sizeof(uint16_t) == 2, "uint16_t must be exactly 2 bytes"); + static_assert(sizeof(uint32_t) == 4, "uint32_t must be exactly 4 bytes"); + uint16_t rdlen; + + int ret; + + new_ttl = htonl(new_ttl); + + size_t i = 0; + /* RR wire format in RFC1035 3.2.1 */ + while(i < wire_size) { + ret = knot_dname_size(wire + i); + if (ret < 0) + return ret; + i += ret + 4; + memcpy(wire + i, &new_ttl, sizeof(uint32_t)); + i += sizeof(uint32_t); + + memcpy(&rdlen, wire + i, sizeof(uint16_t)); + rdlen = ntohs(rdlen); + i += sizeof(uint16_t) + rdlen; + + if (kr_fails_assert(i <= wire_size)) + return kr_error(EINVAL); + } + + return kr_ok(); +} + +/*! + * \brief Add RRSIG RDATA without signature to signing context. + * + * Requires signer name in RDATA in canonical form. + * + * \param ctx Signing context. + * \param rdata Pointer to RRSIG RDATA. + * + * \return Error code, KNOT_EOK if successful. + */ +#define RRSIG_RDATA_SIGNER_OFFSET 18 +static int sign_ctx_add_self(dnssec_sign_ctx_t *ctx, const uint8_t *rdata) +{ + if (kr_fails_assert(ctx && rdata)) + return kr_error(EINVAL); + + int result; + + // static header + + dnssec_binary_t header = { + .data = (uint8_t *)rdata, + .size = RRSIG_RDATA_SIGNER_OFFSET, + }; + + result = dnssec_sign_add(ctx, &header); + if (result != DNSSEC_EOK) + return result; + + // signer name + + const uint8_t *rdata_signer = rdata + RRSIG_RDATA_SIGNER_OFFSET; + dnssec_binary_t signer = { 0 }; + signer.data = knot_dname_copy(rdata_signer, NULL); + signer.size = knot_dname_size(signer.data); + + result = dnssec_sign_add(ctx, &signer); + free(signer.data); + + return result; +} +#undef RRSIG_RDATA_SIGNER_OFFSET + +/*! + * \brief Add covered RRs to signing context. + * + * Requires all DNAMEs in canonical form and all RRs ordered canonically. + * + * \param ctx Signing context. + * \param covered Covered RRs. + * + * \return Error code, KNOT_EOK if successful. + */ +static int sign_ctx_add_records(dnssec_sign_ctx_t *ctx, const knot_rrset_t *covered, + uint32_t orig_ttl, int trim_labels) +{ + if (!ctx || !covered || trim_labels < 0) + return kr_error(EINVAL); + + // huge block of rrsets can be optionally created + static uint8_t wire_buffer[KNOT_WIRE_MAX_PKTSIZE]; + int written = knot_rrset_to_wire(covered, wire_buffer, sizeof(wire_buffer), NULL); + if (written < 0) + return written; + + /* Set original ttl. */ + int ret = adjust_wire_ttl(wire_buffer, written, orig_ttl); + if (ret != 0) + return ret; + + if (!trim_labels) { + const dnssec_binary_t wire_binary = { + .size = written, + .data = wire_buffer + }; + return dnssec_sign_add(ctx, &wire_binary); + } + + /* RFC4035 5.3.2 + * Remove leftmost labels and replace them with '*.' + * for each RR in covered. + */ + uint8_t *beginp = wire_buffer; + for (uint16_t i = 0; i < covered->rrs.count; ++i) { + /* RR(i) = name | type | class | OrigTTL | RDATA length | RDATA */ + for (int j = 0; j < trim_labels; ++j) { + if (kr_fails_assert(beginp[0])) + return kr_error(EINVAL); + beginp = (uint8_t *) knot_wire_next_label(beginp, NULL); + if (kr_fails_assert(beginp)) + return kr_error(EFAULT); + } + *(--beginp) = '*'; + *(--beginp) = 1; + const size_t rdatalen_offset = knot_dname_size(beginp) + /* name */ + sizeof(uint16_t) + /* type */ + sizeof(uint16_t) + /* class */ + sizeof(uint32_t); /* OrigTTL */ + const uint8_t *rdatalen_ptr = beginp + rdatalen_offset; + const uint16_t rdata_size = knot_wire_read_u16(rdatalen_ptr); + const size_t rr_size = rdatalen_offset + + sizeof(uint16_t) + /* RDATA length */ + rdata_size; /* RDATA */ + const dnssec_binary_t wire_binary = { + .size = rr_size, + .data = beginp + }; + ret = dnssec_sign_add(ctx, &wire_binary); + if (ret != 0) + break; + beginp += rr_size; + } + return ret; +} + +/*! + * \brief Add all data covered by signature into signing context. + * + * RFC 4034: The signature covers RRSIG RDATA field (excluding the signature) + * and all matching RR records, which are ordered canonically. + * + * Requires all DNAMEs in canonical form and all RRs ordered canonically. + * + * \param ctx Signing context. + * \param rrsig_rdata RRSIG RDATA with populated fields except signature. + * \param covered Covered RRs. + * + * \return Error code, KNOT_EOK if successful. + */ +/* TODO -- Taken from knot/src/knot/dnssec/rrset-sign.c. Re-write for better fit needed. */ +static int sign_ctx_add_data(dnssec_sign_ctx_t *ctx, const uint8_t *rrsig_rdata, + const knot_rrset_t *covered, uint32_t orig_ttl, int trim_labels) +{ + int result = sign_ctx_add_self(ctx, rrsig_rdata); + if (result != KNOT_EOK) + return result; + + return sign_ctx_add_records(ctx, covered, orig_ttl, trim_labels); +} + +int kr_check_signature(const knot_rdata_t *rrsig, + const dnssec_key_t *key, const knot_rrset_t *covered, + int trim_labels) +{ + if (!rrsig || !key || !dnssec_key_can_verify(key)) + return kr_error(EINVAL); + + int ret = 0; + dnssec_sign_ctx_t *sign_ctx = NULL; + dnssec_binary_t signature = { + .data = /*const-cast*/(uint8_t*)knot_rrsig_signature(rrsig), + .size = knot_rrsig_signature_len(rrsig), + }; + if (!signature.data || !signature.size) { + ret = kr_error(EINVAL); + goto fail; + } + + if (dnssec_sign_new(&sign_ctx, key) != 0) { + ret = kr_error(ENOMEM); + goto fail; + } + + uint32_t orig_ttl = knot_rrsig_original_ttl(rrsig); + + if (sign_ctx_add_data(sign_ctx, rrsig->data, covered, orig_ttl, trim_labels) != 0) { + ret = kr_error(ENOMEM); + goto fail; + } + + ret = dnssec_sign_verify(sign_ctx, false, &signature); + if (ret != 0) { + ret = kr_error(EBADMSG); + goto fail; + } + + ret = kr_ok(); + +fail: + dnssec_sign_free(sign_ctx); + return ret; +} diff --git a/lib/dnssec/signature.h b/lib/dnssec/signature.h new file mode 100644 index 0000000..1cc6c8f --- /dev/null +++ b/lib/dnssec/signature.h @@ -0,0 +1,29 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <libdnssec/key.h> +#include <libknot/rrset.h> + +/** + * Performs referral authentication according to RFC4035 5.2, bullet 2 + * @param ref Referral RRSet. Currently only DS can be used. + * @param key Already parsed key. + * @return 0 or error code. In particular: DNSSEC_INVALID_DS_ALGORITHM + * in case *all* DSs in ref use an unimplemented algorithm. + */ +int kr_authenticate_referral(const knot_rrset_t *ref, const dnssec_key_t *key); + +/** + * Check the signature of the supplied RRSet. + * @param rrsig A single signature. + * @param key Key to be used to validate the signature. + * @param covered The covered RRSet. + * @param trim_labels Number of the leftmost labels to be removed and replaced with '*.'. + * @return 0 if signature valid, error code else. + */ +int kr_check_signature(const knot_rdata_t *rrsig, + const dnssec_key_t *key, const knot_rrset_t *covered, + int trim_labels); diff --git a/lib/dnssec/ta.c b/lib/dnssec/ta.c new file mode 100644 index 0000000..becf7d8 --- /dev/null +++ b/lib/dnssec/ta.c @@ -0,0 +1,154 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <contrib/cleanup.h> +#include <libknot/descriptor.h> +#include <libknot/rdataset.h> +#include <libknot/rrset.h> +#include <libknot/packet/wire.h> +#include <libdnssec/key.h> +#include <libdnssec/error.h> + +#include "lib/defines.h" +#include "lib/dnssec.h" +#include "lib/dnssec/ta.h" +#include "lib/resolve.h" +#include "lib/utils.h" + +knot_rrset_t *kr_ta_get(trie_t *trust_anchors, const knot_dname_t *name) +{ + trie_val_t *val = trie_get_try(trust_anchors, (const char *)name, strlen((const char *)name)); + return (val) ? *val : NULL; +} + +const knot_dname_t * kr_ta_closest(const struct kr_context *ctx, const knot_dname_t *name, + const uint16_t type) +{ + kr_require(ctx && name); + if (type == KNOT_RRTYPE_DS && name[0] != '\0') { + /* DS is parent-side record, so the parent name needs to be covered. */ + name = knot_wire_next_label(name, NULL); + } + while (name) { + struct kr_context *ctx_nc = (struct kr_context *)/*const-cast*/ctx; + if (kr_ta_get(ctx_nc->trust_anchors, name)) { + return name; + } + if (kr_ta_get(ctx_nc->negative_anchors, name)) { + return NULL; + } + name = knot_wire_next_label(name, NULL); + } + return NULL; +} + +/* @internal Create DS from DNSKEY, caller MUST free dst if successful. */ +static int dnskey2ds(dnssec_binary_t *dst, const knot_dname_t *owner, const uint8_t *rdata, uint16_t rdlen) +{ + dnssec_key_t *key = NULL; + int ret = dnssec_key_new(&key); + if (ret) goto cleanup; + /* Create DS from DNSKEY and reinsert */ + const dnssec_binary_t key_data = { .size = rdlen, .data = (uint8_t *)rdata }; + ret = dnssec_key_set_rdata(key, &key_data); + if (ret) goto cleanup; + /* Accept only keys with Zone and SEP flags that aren't revoked, + * as a precaution. RFC 5011 also utilizes these flags. + * TODO: kr_dnssec_key_* names are confusing. */ + const bool flags_ok = kr_dnssec_key_zsk(rdata) && !kr_dnssec_key_revoked(rdata); + if (!flags_ok) { + auto_free char *owner_str = kr_dname_text(owner); + kr_log_error(TA, "refusing to trust %s DNSKEY because of flags %d\n", + owner_str, dnssec_key_get_flags(key)); + ret = kr_error(EILSEQ); + goto cleanup; + } else if (!kr_dnssec_key_ksk(rdata)) { + auto_free char *owner_str = kr_dname_text(owner); + int flags = dnssec_key_get_flags(key); + kr_log_warning(TA, "warning: %s DNSKEY is missing the SEP bit; " + "flags %d instead of %d\n", + owner_str, flags, flags + 1/*a little ugly*/); + } + ret = dnssec_key_set_dname(key, owner); + if (ret) goto cleanup; + ret = dnssec_key_create_ds(key, DNSSEC_KEY_DIGEST_SHA256, dst); +cleanup: + dnssec_key_free(key); + return kr_error(ret); +} + +/* @internal Insert new TA to trust anchor set, rdata MUST be of DS type. */ +static int insert_ta(trie_t *trust_anchors, const knot_dname_t *name, + uint32_t ttl, const uint8_t *rdata, uint16_t rdlen) +{ + bool is_new_key = false; + knot_rrset_t *ta_rr = kr_ta_get(trust_anchors, name); + if (!ta_rr) { + ta_rr = knot_rrset_new(name, KNOT_RRTYPE_DS, KNOT_CLASS_IN, ttl, NULL); + is_new_key = true; + } + /* Merge-in new key data */ + if (!ta_rr || (rdlen > 0 && knot_rrset_add_rdata(ta_rr, rdata, rdlen, NULL) != 0)) { + knot_rrset_free(ta_rr, NULL); + return kr_error(ENOMEM); + } + if (is_new_key) { + trie_val_t *val = trie_get_ins(trust_anchors, (const char *)name, strlen((const char *)name)); + if (kr_fails_assert(val)) + return kr_error(EINVAL); + *val = ta_rr; + } + return kr_ok(); +} + +int kr_ta_add(trie_t *trust_anchors, const knot_dname_t *name, uint16_t type, + uint32_t ttl, const uint8_t *rdata, uint16_t rdlen) +{ + if (!trust_anchors || !name) { + return kr_error(EINVAL); + } + + /* DS/DNSKEY types are accepted, for DNSKEY we + * need to compute a DS digest. */ + if (type == KNOT_RRTYPE_DS) { + return insert_ta(trust_anchors, name, ttl, rdata, rdlen); + } else if (type == KNOT_RRTYPE_DNSKEY) { + dnssec_binary_t ds_rdata = { 0, }; + int ret = dnskey2ds(&ds_rdata, name, rdata, rdlen); + if (ret != 0) { + return ret; + } + ret = insert_ta(trust_anchors, name, ttl, ds_rdata.data, ds_rdata.size); + dnssec_binary_free(&ds_rdata); + return ret; + } else { /* Invalid type for TA */ + return kr_error(EINVAL); + } +} + +/* Delete record data */ +static int del_record(trie_val_t *v, void *ext) +{ + knot_rrset_t *ta_rr = *v; + if (ta_rr) { + knot_rrset_free(ta_rr, NULL); + } + return 0; +} + +int kr_ta_del(trie_t *trust_anchors, const knot_dname_t *name) +{ + knot_rrset_t *ta_rr; + int ret = trie_del(trust_anchors, (const char *)name, strlen((const char *)name), + (trie_val_t *) &ta_rr); + if (ret == KNOT_EOK && ta_rr) + knot_rrset_free(ta_rr, NULL); + return kr_ok(); +} + +void kr_ta_clear(trie_t *trust_anchors) +{ + trie_apply(trust_anchors, del_record, NULL); + trie_clear(trust_anchors); +} diff --git a/lib/dnssec/ta.h b/lib/dnssec/ta.h new file mode 100644 index 0000000..1eb1dd9 --- /dev/null +++ b/lib/dnssec/ta.h @@ -0,0 +1,61 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "lib/defines.h" +#include "lib/generic/trie.h" +#include <libknot/rrset.h> + +/** + * Find TA RRSet by name. + * @param trust_anchors trust store + * @param name name of the TA + * @return non-empty RRSet or NULL + */ +KR_EXPORT +knot_rrset_t *kr_ta_get(trie_t *trust_anchors, const knot_dname_t *name); + +/** + * Add TA to trust store. DS or DNSKEY types are supported. + * @param trust_anchors trust store + * @param name name of the TA + * @param type RR type of the TA (DS or DNSKEY) + * @param ttl + * @param rdata + * @param rdlen + * @return 0 or an error + */ +KR_EXPORT +int kr_ta_add(trie_t *trust_anchors, const knot_dname_t *name, uint16_t type, + uint32_t ttl, const uint8_t *rdata, uint16_t rdlen); + +struct kr_context; + +/** + * Return pointer to the name of the closest positive trust anchor or NULL. + * + * "Closest" means on path towards root. Closer negative anchor results into NULL. + * @param type serves as a shorthand because DS needs to start one level higher. + */ +KR_EXPORT KR_PURE +const knot_dname_t * kr_ta_closest(const struct kr_context *ctx, const knot_dname_t *name, + const uint16_t type); + +/** + * Remove TA from trust store. + * @param trust_anchors trust store + * @param name name of the TA + * @return 0 or an error + */ +KR_EXPORT +int kr_ta_del(trie_t *trust_anchors, const knot_dname_t *name); + +/** + * Clear trust store. + * @param trust_anchors trust store + */ +KR_EXPORT +void kr_ta_clear(trie_t *trust_anchors); + diff --git a/lib/generic/README.rst b/lib/generic/README.rst new file mode 100644 index 0000000..dae0b7e --- /dev/null +++ b/lib/generic/README.rst @@ -0,0 +1,48 @@ +.. SPDX-License-Identifier: GPL-3.0-or-later + +Generics library +---------------- + +This small collection of "generics" was born out of frustration that I couldn't find no +such thing for C. It's either bloated, has poor interface, null-checking is absent or +doesn't allow custom allocation scheme. BSD-licensed (or compatible) code is allowed here, +as long as it comes with a test case in `tests/test_generics.c`. + +* array_ - a set of simple macros to make working with dynamic arrays easier. +* queue_ - a FIFO + LIFO queue. +* pack_ - length-prefixed list of objects (i.e. array-list). +* lru_ - LRU-like hash table +* trie_ - a trie-based key-value map, taken from knot-dns + +array +~~~~~ + +.. doxygenfile:: array.h + :project: libkres + +queue +~~~~~ + +.. doxygenfile:: queue.h + :project: libkres + +pack +~~~~ + +.. doxygenfile:: pack.h + :project: libkres + +lru +~~~ + +.. doxygenfile:: lru.h + :project: libkres + +trie +~~~~ + +.. doxygenfile:: trie.h + :project: libkres + + +.. _`Crit-bit tree`: https://cr.yp.to/critbit.html diff --git a/lib/generic/array.h b/lib/generic/array.h new file mode 100644 index 0000000..9f35118 --- /dev/null +++ b/lib/generic/array.h @@ -0,0 +1,157 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/** + * + * @file array.h + * @brief A set of simple macros to make working with dynamic arrays easier. + * + * @note The C has no generics, so it is implemented mostly using macros. + * Be aware of that, as direct usage of the macros in the evaluating macros + * may lead to different expectations: + * + * @code{.c} + * MIN(array_push(arr, val), other) + * @endcode + * + * May evaluate the code twice, leading to unexpected behaviour. + * This is a price to pay for the absence of proper generics. + * + * # Example usage: + * + * @code{.c} + * array_t(const char*) arr; + * array_init(arr); + * + * // Reserve memory in advance + * if (array_reserve(arr, 2) < 0) { + * return ENOMEM; + * } + * + * // Already reserved, cannot fail + * array_push(arr, "princess"); + * array_push(arr, "leia"); + * + * // Not reserved, may fail + * if (array_push(arr, "han") < 0) { + * return ENOMEM; + * } + * + * // It does not hide what it really is + * for (size_t i = 0; i < arr.len; ++i) { + * printf("%s\n", arr.at[i]); + * } + * + * // Random delete + * array_del(arr, 0); + * @endcode + * \addtogroup generics + * @{ + */ + +#pragma once +#include <stdlib.h> + +/** Choose array length when it overflows. */ +static inline size_t array_next_count(size_t elm_size, size_t want, size_t have) +{ + if (want >= have * 2) // We amortized enough and maybe more won't be needed. + return want; + const size_t want_b = want * elm_size; + if (want_b < 64) // Short arrays are cheap to copy; get just one extra. + return want + 1; + if (want_b < 1024) // 50% growth amortizes to roughly 3 copies per element. + return want + want / 2; + return want * 2; // Doubling growth amortizes to roughly 2 copies per element. +} + +/** @internal Incremental memory reservation */ +static inline int array_std_reserve(void *baton, void **mem, size_t elm_size, size_t want, size_t *have) +{ + if (*have >= want) { + return 0; + } + /* Simplified Qt containers growth strategy */ + size_t next_size = array_next_count(elm_size, want, *have); + void *mem_new = realloc(*mem, next_size * elm_size); + if (mem_new != NULL) { + *mem = mem_new; + *have = next_size; + return 0; + } + return -1; +} + +/** @internal Wrapper for stdlib free. */ +static inline void array_std_free(void *baton, void *p) +{ + free(p); +} + +/** Declare an array structure. */ +#define array_t(type) struct {type * at; size_t len; size_t cap; } + +/** Zero-initialize the array. */ +#define array_init(array) ((array).at = NULL, (array).len = (array).cap = 0) + +/** Free and zero-initialize the array (plain malloc/free). */ +#define array_clear(array) \ + array_clear_mm(array, array_std_free, NULL) + +/** Make the array empty and free pointed-to memory. + * Mempool usage: pass mm_free and a knot_mm_t* . */ +#define array_clear_mm(array, free, baton) \ + (free)((baton), (array).at), array_init(array) + +/** Reserve capacity for at least n elements. + * @return 0 if success, <0 on failure */ +#define array_reserve(array, n) \ + array_reserve_mm(array, n, array_std_reserve, NULL) + +/** Reserve capacity for at least n elements. + * Mempool usage: pass kr_memreserve and a knot_mm_t* . + * @return 0 if success, <0 on failure */ +#define array_reserve_mm(array, n, reserve, baton) \ + (reserve)((baton), (void **) &(array).at, sizeof((array).at[0]), (n), &(array).cap) + +/** + * Push value at the end of the array, resize it if necessary. + * Mempool usage: pass kr_memreserve and a knot_mm_t* . + * @note May fail if the capacity is not reserved. + * @return element index on success, <0 on failure + */ +#define array_push_mm(array, val, reserve, baton) \ + (int)((array).len < (array).cap ? ((array).at[(array).len] = val, (array).len++) \ + : (array_reserve_mm(array, ((array).cap + 1), reserve, baton) < 0 ? -1 \ + : ((array).at[(array).len] = val, (array).len++))) + +/** + * Push value at the end of the array, resize it if necessary (plain malloc/free). + * @note May fail if the capacity is not reserved. + * @return element index on success, <0 on failure + */ +#define array_push(array, val) \ + array_push_mm(array, val, array_std_reserve, NULL) + +/** + * Pop value from the end of the array. + */ +#define array_pop(array) \ + (array).len -= 1 + +/** + * Remove value at given index. + * @return 0 on success, <0 on failure + */ +#define array_del(array, i) \ + (int)((i) < (array).len ? ((array).len -= 1,(array).at[i] = (array).at[(array).len], 0) : -1) + +/** + * Return last element of the array. + * @warning Undefined if the array is empty. + */ +#define array_tail(array) \ + (array).at[(array).len - 1] + +/** @} */ diff --git a/lib/generic/lru.c b/lib/generic/lru.c new file mode 100644 index 0000000..857b20b --- /dev/null +++ b/lib/generic/lru.c @@ -0,0 +1,249 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "lib/generic/lru.h" +#include "contrib/murmurhash3/murmurhash3.h" +#include "contrib/ucw/mempool.h" + +typedef struct lru_group lru_group_t; + +struct lru_item { + uint16_t key_len, val_len; /**< Two bytes should be enough for our purposes. */ + char data[]; + /**< Place for both key and value. + * + * We use "char" to satisfy the C99+ aliasing rules. + * See C99 section 6.5 Expressions, paragraph 7. + * Any type can be accessed through char-pointer, + * so we can use a common struct definition + * for all types being held. + * + * The value address is restricted by val_alignment. + * Approach: require slightly larger sizes from the allocator + * and shift value on the closest address with val_alignment. + */ +}; + +/** @brief Round the value up to a multiple of mul (a power of two). */ +static inline size_t round_power(size_t size, size_t mult) +{ + kr_require(__builtin_popcount(mult) == 1); + size_t res = ((size - 1) & ~(mult - 1)) + mult; + kr_require(__builtin_ctz(res) >= __builtin_ctz(mult)); + kr_require(size <= res && res < size + mult); + return res; +} + +/** @internal Compute the allocation size for an lru_item. */ +static uint item_size(const struct lru *lru, uint key_len, uint val_len) +{ + uint key_end = offsetof(struct lru_item, data) + key_len; + return key_end + (lru->val_alignment - 1) + val_len; + /* ^^^ worst-case padding length + * Well, we might compute the bound a bit more precisely, + * as we know that lru_item will get alignment at least + * some sizeof(void*) and we know all the lengths, + * but let's not complicate it, as the gain would be small anyway. */ +} + +/** @internal Return pointer to value in an lru_item. */ +static void * item_val(const struct lru *lru, struct lru_item *it) +{ + size_t key_end = it->data + it->key_len - (char *)NULL; + size_t val_begin = round_power(key_end, lru->val_alignment); + return (char *)NULL + val_begin; +} + +/** @internal Free each item. */ +KR_EXPORT void lru_free_items_impl(struct lru *lru) +{ + if (kr_fails_assert(lru)) + return; + for (size_t i = 0; i < (1 << (size_t)lru->log_groups); ++i) { + lru_group_t *g = &lru->groups[i]; + for (int j = 0; j < LRU_ASSOC; ++j) + mm_free(lru->mm, g->items[j]); + } +} + +/** @internal See lru_apply. */ +KR_EXPORT void lru_apply_impl(struct lru *lru, lru_apply_fun f, void *baton) +{ + if (kr_fails_assert(lru && f)) + return; + for (size_t i = 0; i < (1 << (size_t)lru->log_groups); ++i) { + lru_group_t *g = &lru->groups[i]; + for (uint j = 0; j < LRU_ASSOC; ++j) { + struct lru_item *it = g->items[j]; + if (!it) + continue; + enum lru_apply_do ret = + f(it->data, it->key_len, item_val(lru, it), baton); + switch(ret) { + case LRU_APPLY_DO_EVICT: // evict + mm_free(lru->mm, it); + g->items[j] = NULL; + g->counts[j] = 0; + g->hashes[j] = 0; + break; + default: + kr_assert(ret == LRU_APPLY_DO_NOTHING); + } + } + } +} + +/** @internal See lru_create. */ +KR_EXPORT struct lru * lru_create_impl(uint max_slots, uint val_alignment, + knot_mm_t *mm_array, knot_mm_t *mm) +{ + if (kr_fails_assert(max_slots && __builtin_popcount(val_alignment) == 1)) + return NULL; + // let lru->log_groups = ceil(log2(max_slots / (float) assoc)) + // without trying for efficiency + uint group_count = (max_slots - 1) / LRU_ASSOC + 1; + uint log_groups = 0; + for (uint s = group_count - 1; s; s /= 2) + ++log_groups; + group_count = 1 << log_groups; + if (kr_fails_assert(max_slots <= group_count * LRU_ASSOC && group_count * LRU_ASSOC < 2 * max_slots)) + return NULL; + + /* Get a sufficiently aligning mm_array if NULL is passed. */ + if (!mm_array) { + static knot_mm_t mm_array_default = { 0 }; + if (!mm_array_default.ctx) + mm_ctx_init_aligned(&mm_array_default, alignof(struct lru)); + mm_array = &mm_array_default; + } + if (kr_fails_assert(mm_array->alloc && mm_array->alloc != (knot_mm_alloc_t)mp_alloc)) + return NULL; + + size_t size = offsetof(struct lru, groups[group_count]); + struct lru *lru = mm_alloc(mm_array, size); + if (unlikely(lru == NULL)) + return NULL; + *lru = (struct lru){ + .mm = mm, + .mm_array = mm_array, + .log_groups = log_groups, + .val_alignment = val_alignment, + }; + // zeros are a good init + memset(lru->groups, 0, size - offsetof(struct lru, groups)); + return lru; +} + +/** @internal Decrement all counters within a group. */ +static void group_dec_counts(lru_group_t *g) { + g->counts[LRU_TRACKED] = LRU_TRACKED; + for (uint i = 0; i < LRU_TRACKED + 1; ++i) + if (likely(g->counts[i])) + --g->counts[i]; +} + +/** @internal Increment a counter within a group. */ +static void group_inc_count(lru_group_t *g, int i) { + if (likely(++(g->counts[i]))) + return; + g->counts[i] = -1; + // We could've decreased or halved all of them, but let's keep the max. +} + +/** @internal Implementation of both getting and insertion. + * Note: val_len is only meaningful if do_insert. + * *is_new is only meaningful when return value isn't NULL, contains + * true when returned lru entry has been allocated right now + * if return value is NULL, *is_new remains untouched. + */ +KR_EXPORT void * lru_get_impl(struct lru *lru, const char *key, uint key_len, + uint val_len, bool do_insert, bool *is_new) +{ + bool ok = lru && (key || !key_len) && key_len <= UINT16_MAX + && (!do_insert || val_len <= UINT16_MAX); + if (kr_fails_assert(ok)) + return NULL; // reasonable fallback when not debugging + bool is_new_entry = false; + // find the right group + uint32_t khash = hash(key, key_len); + uint16_t khash_top = khash >> 16; + lru_group_t *g = &lru->groups[khash & ((1 << lru->log_groups) - 1)]; + struct lru_item *it = NULL; + uint i; + // scan the *stored* elements in the group + for (i = 0; i < LRU_ASSOC; ++i) { + if (g->hashes[i] == khash_top) { + it = g->items[i]; + if (likely(it && it->key_len == key_len + && (key_len == 0 || memcmp(it->data, key, key_len) == 0))) { + /* Found a key, but trying to insert a value larger than available + * space in the allocated slot, so the entry must be resized to fit. */ + if (unlikely(do_insert && val_len > it->val_len)) { + goto insert; + } else { + goto found; // to reduce huge nesting depth + } + } + } + } + // key not found; first try an empty/counted-out place to insert + if (do_insert) + for (i = 0; i < LRU_ASSOC; ++i) + if (g->items[i] == NULL || g->counts[i] == 0) + goto insert; + // check if we track key's count at least + for (i = LRU_ASSOC; i < LRU_TRACKED; ++i) { + if (g->hashes[i] == khash_top) { + group_inc_count(g, i); + if (!do_insert) + return NULL; + // check if we trumped some stored key + for (uint j = 0; j < LRU_ASSOC; ++j) + if (unlikely(g->counts[i] > g->counts[j])) { + // evict key j, i.e. swap with i + --g->counts[i]; // we increment it below + SWAP(g->counts[i], g->counts[j]); + SWAP(g->hashes[i], g->hashes[j]); + i = j; + goto insert; + } + return NULL; + } + } + // not found at all: decrement all counts but only on every LRU_TRACKED occasion + if (g->counts[LRU_TRACKED]) + --g->counts[LRU_TRACKED]; + else + group_dec_counts(g); + return NULL; +insert: // insert into position i (incl. key) + if (kr_fails_assert(i < LRU_ASSOC)) + return NULL; + g->hashes[i] = khash_top; + it = g->items[i]; + uint new_size = item_size(lru, key_len, val_len); + if (it == NULL || new_size != item_size(lru, it->key_len, it->val_len)) { + // (re)allocate + mm_free(lru->mm, it); + it = g->items[i] = mm_alloc(lru->mm, new_size); + if (it == NULL) + return NULL; + } + it->key_len = key_len; + it->val_len = val_len; + if (key_len > 0) { + memcpy(it->data, key, key_len); + } + memset(item_val(lru, it), 0, val_len); // clear the value + is_new_entry = true; +found: // key and hash OK on g->items[i]; now update stamps + if (kr_fails_assert(i < LRU_ASSOC)) + return NULL; + group_inc_count(g, i); + if (is_new) { + *is_new = is_new_entry; + } + return item_val(lru, g->items[i]); +} + diff --git a/lib/generic/lru.h b/lib/generic/lru.h new file mode 100644 index 0000000..448c1b9 --- /dev/null +++ b/lib/generic/lru.h @@ -0,0 +1,240 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ +/** + * @file lru.h + * @brief A lossy cache. + * + * @note The implementation tries to keep frequent keys and avoid others, + * even if "used recently", so it may refuse to store it on lru_get_new(). + * It uses hashing to split the problem pseudo-randomly into smaller groups, + * and within each it tries to approximate relative usage counts of several + * most frequent keys/hashes. This tracking is done for *more* keys than + * those that are actually stored. + * + * Example usage: + * @code{.c} + * // Define new LRU type + * typedef lru_t(int) lru_int_t; + * + * // Create LRU + * lru_int_t *lru; + * lru_create(&lru, 5, NULL, NULL); + * + * // Insert some values + * int *pi = lru_get_new(lru, "luke", strlen("luke"), NULL); + * if (pi) + * *pi = 42; + * pi = lru_get_new(lru, "leia", strlen("leia"), NULL); + * if (pi) + * *pi = 24; + * + * // Retrieve values + * int *ret = lru_get_try(lru, "luke", strlen("luke"), NULL); + * if (!ret) printf("luke dropped out!\n"); + * else printf("luke's number is %d\n", *ret); + * + * char *enemies[] = {"goro", "raiden", "subzero", "scorpion"}; + * for (int i = 0; i < 4; ++i) { + * int *val = lru_get_new(lru, enemies[i], strlen(enemies[i]), NULL); + * if (val) + * *val = i; + * } + * + * // We're done + * lru_free(lru); + * @endcode + * + * \addtogroup generics + * @{ + */ + +#pragma once + +#include <stdalign.h> +#include <stddef.h> +#include <stdint.h> + +#include "contrib/ucw/lib.h" +#include "lib/utils.h" +#include "libknot/mm_ctx.h" + +/* ================================ Interface ================================ */ + +/** @brief The type for LRU, parametrized by value type. */ +#define lru_t(type) \ + union { \ + type *pdata_t; /* only the *type* information is used */ \ + struct lru lru; \ + } + +/** + * @brief Allocate and initialize an LRU with default associativity. + * + * The real limit on the number of slots can be a bit larger but less than double. + * + * @param ptable pointer to a pointer to the LRU + * @param max_slots number of slots + * @param mm_ctx_array memory context to use for the huge array, NULL for default + * If you pass your own, it needs to produce CACHE_ALIGNED allocations (ubsan). + * @param mm_ctx memory context to use for individual key-value pairs, NULL for default + * + * @note The pointers to memory contexts need to remain valid + * during the whole life of the structure (or be NULL). + */ +/* Pragmas: C11 only standardizes alignof on type names, not on expressions. + * That's a GNU extension; in clang it's supported but may generate warnings. + * It seems hard to disable warnings that are only supported by some compilers. */ +#define lru_create(ptable, max_slots, mm_ctx_array, mm_ctx) do { \ + (void)(((__typeof__((*(ptable))->pdata_t))0) == (void *)0); /* typecheck lru_t */ \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wpragmas\"") \ + _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") \ + _Pragma("GCC diagnostic ignored \"-Wgnu-alignof-expression\"") \ + *(ptable) = (__typeof__(*(ptable))) \ + lru_create_impl((max_slots), alignof(*( (*(ptable))->pdata_t )), \ + (mm_ctx_array), (mm_ctx)); \ + _Pragma("GCC diagnostic pop") \ + } while (false) + +/** @brief Free an LRU created by lru_create (it can be NULL). */ +#define lru_free(table) \ + lru_free_impl(&(table)->lru) + +/** @brief Reset an LRU to the empty state (but preserve any settings). */ +#define lru_reset(table) \ + lru_reset_impl(&(table)->lru) + +/** + * @brief Find key in the LRU and return pointer to the corresponding value. + * + * @param table pointer to LRU + * @param key_ lookup key + * @param len_ key length + * @return pointer to data or NULL if not found + */ +#define lru_get_try(table, key_, len_) \ + (__typeof__((table)->pdata_t)) \ + lru_get_impl(&(table)->lru, (key_), (len_), -1, false, NULL) + +/** + * @brief Return pointer to value, inserting if needed (zeroed). + * + * @param table pointer to LRU + * @param key_ lookup key + * @param len_ key lengthkeys + * @param is_new pointer to bool to store result of operation + * (true if entry is newly added, false otherwise; can be NULL). + * @return pointer to data or NULL (can be even if memory could be allocated!) + */ +#define lru_get_new(table, key_, len_, is_new) \ + (__typeof__((table)->pdata_t)) \ + lru_get_impl(&(table)->lru, (key_), (len_), \ + sizeof(*(table)->pdata_t), true, is_new) + +/** + * @brief Apply a function to every item in LRU. + * + * @param table pointer to LRU + * @param function enum lru_apply_do (*function)(const char *key, uint len, val_type *val, void *baton) + * See enum lru_apply_do for the return type meanings. + * @param baton extra pointer passed to each function invocation + */ +#define lru_apply(table, function, baton) do { \ + lru_apply_fun_g(fun_dummy, __typeof__(*(table)->pdata_t)) = 0; \ + (void)(fun_dummy == (function)); /* produce a warning with incompatible function type */ \ + lru_apply_impl(&(table)->lru, (lru_apply_fun)(function), (baton)); \ + } while (false) + +/** @brief Possible actions to do with an element. */ +enum lru_apply_do { + LRU_APPLY_DO_NOTHING, + LRU_APPLY_DO_EVICT, + /* maybe more in future*/ +}; + +/** + * @brief Return the real capacity - maximum number of keys holdable within. + * + * @param table pointer to LRU + */ +#define lru_capacity(table) lru_capacity_impl(&(table)->lru) + + + +/* ======================== Inlined part of implementation ======================== */ +/** @cond internal */ + +#define lru_apply_fun_g(name, val_type) \ + enum lru_apply_do (*(name))(const char *key, uint len, val_type *val, void *baton) +typedef lru_apply_fun_g(lru_apply_fun, void); + +#if __GNUC__ >= 4 + #define CACHE_ALIGNED __attribute__((aligned(64))) +#else + #define CACHE_ALIGNED +#endif + +struct lru; +void lru_free_items_impl(struct lru *lru); +struct lru * lru_create_impl(uint max_slots, uint val_alignment, + knot_mm_t *mm_array, knot_mm_t *mm); +void * lru_get_impl(struct lru *lru, const char *key, uint key_len, + uint val_len, bool do_insert, bool *is_new); +void lru_apply_impl(struct lru *lru, lru_apply_fun f, void *baton); + +struct lru_item; + +#if SIZE_MAX > (1 << 32) + /** @internal The number of keys stored within each group. */ + #define LRU_ASSOC 3 +#else + #define LRU_ASSOC 4 +#endif +/** @internal The number of hashes tracked within each group: 10-1 or 12-1. */ +#define LRU_TRACKED ((64 - sizeof(size_t) * LRU_ASSOC) / 4 - 1) + +struct lru_group { + uint16_t counts[LRU_TRACKED+1]; /*!< Occurrence counters; the last one is special. */ + uint16_t hashes[LRU_TRACKED+1]; /*!< Top halves of hashes; the last one is unused. */ + struct lru_item *items[LRU_ASSOC]; /*!< The full items. */ +} CACHE_ALIGNED; + +/* The sizes are chosen so lru_group just fits into a single x86 cache line. */ +static_assert(64 == sizeof(struct lru_group) + && 64 == LRU_ASSOC * sizeof(void*) + (LRU_TRACKED+1) * 4, + "bad sizing for your sizeof(void*)"); + +struct lru { + struct knot_mm *mm, /**< Memory context to use for keys. */ + *mm_array; /**< Memory context to use for this structure itself. */ + uint log_groups; /**< Logarithm of the number of LRU groups. */ + uint val_alignment; /**< Alignment for the values. */ + struct lru_group groups[] CACHE_ALIGNED; /**< The groups of items. */ +}; + +/** @internal See lru_free. */ +static inline void lru_free_impl(struct lru *lru) +{ + if (!lru) + return; + lru_free_items_impl(lru); + mm_free(lru->mm_array, lru); +} + +/** @internal See lru_reset. */ +static inline void lru_reset_impl(struct lru *lru) +{ + lru_free_items_impl(lru); + memset(lru->groups, 0, sizeof(lru->groups[0]) * (1 << lru->log_groups)); +} + +/** @internal See lru_capacity. */ +static inline uint lru_capacity_impl(struct lru *lru) +{ + kr_require(lru); + return (1 << lru->log_groups) * LRU_ASSOC; +} + +/** @endcond */ +/** @} (addtogroup generics) */ diff --git a/lib/generic/pack.h b/lib/generic/pack.h new file mode 100644 index 0000000..18d57db --- /dev/null +++ b/lib/generic/pack.h @@ -0,0 +1,221 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/** + * @file pack.h + * @brief A length-prefixed list of objects, also an array list. + * + * Each object is prefixed by item length, unlike array this structure + * permits variable-length data. It is also equivalent to forward-only list + * backed by an array. + * + * @note Maximum object size is 2^16 bytes, see ::pack_objlen_t + * @todo If some mistake happens somewhere, the access may end up in an infinite loop. + * (equality comparison on pointers) + * + * # Example usage: + * + * @code{.c} + * pack_t pack; + * pack_init(pack); + * + * // Reserve 2 objects, 6 bytes total + * pack_reserve(pack, 2, 4 + 2); + * + * // Push 2 objects + * pack_obj_push(pack, U8("jedi"), 4) + * pack_obj_push(pack, U8("\xbe\xef"), 2); + * + * // Iterate length-value pairs + * uint8_t *it = pack_head(pack); + * while (it != pack_tail(pack)) { + * uint8_t *val = pack_obj_val(it); + * it = pack_obj_next(it); + * } + * + * // Remove object + * pack_obj_del(pack, U8("jedi"), 4); + * + * pack_clear(pack); + * @endcode + * + * \addtogroup generics + * @{ + */ + +#pragma once + +#include <stdint.h> +#include <string.h> +#include "array.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Packed object length type. */ +typedef uint16_t pack_objlen_t; + +/** Pack is defined as an array of bytes */ +typedef array_t(uint8_t) pack_t; + +/** Zero-initialize the pack. */ +#define pack_init(pack) \ + array_init(pack) + +/** Make the pack empty and free pointed-to memory (plain malloc/free). */ +#define pack_clear(pack) \ + array_clear(pack) + +/** Make the pack empty and free pointed-to memory. + * Mempool usage: pass mm_free and a knot_mm_t* . */ +#define pack_clear_mm(pack, free, baton) \ + array_clear_mm((pack), (free), (baton)) + +/** Reserve space for *additional* objects in the pack (plain malloc/free). + * @return 0 if success, <0 on failure */ +#define pack_reserve(pack, objs_count, objs_len) \ + pack_reserve_mm((pack), (objs_count), (objs_len), array_std_reserve, NULL) + +/** Reserve space for *additional* objects in the pack. + * Mempool usage: pass kr_memreserve and a knot_mm_t* . + * @return 0 if success, <0 on failure */ +#define pack_reserve_mm(pack, objs_count, objs_len, reserve, baton) \ + array_reserve_mm((pack), (pack).len + (sizeof(pack_objlen_t)*(objs_count) + (objs_len)), (reserve), (baton)) + +/** Return pointer to first packed object. + * + * Recommended way to iterate: + * for (uint8_t *it = pack_head(pack); it != pack_tail(pack); it = pack_obj_next(it)) + */ +#define pack_head(pack) \ + ((pack).len > 0 ? &((pack).at[0]) : NULL) + +/** Return pack end pointer. */ +#define pack_tail(pack) \ + ((pack).len > 0 ? &((pack).at[(pack).len]) : NULL) + +/** Return packed object length. */ +static inline pack_objlen_t pack_obj_len(uint8_t *it) +{ + pack_objlen_t len = 0; + if (it != NULL) + memcpy(&len, it, sizeof(len)); + return len; +} + +/** Return packed object value. */ +static inline uint8_t *pack_obj_val(uint8_t *it) +{ + if (kr_fails_assert(it)) + return NULL; + return it + sizeof(pack_objlen_t); +} + +/** Return pointer to next packed object. */ +static inline uint8_t *pack_obj_next(uint8_t *it) +{ + if (kr_fails_assert(it)) + return NULL; + return pack_obj_val(it) + pack_obj_len(it); +} + +/** Return pointer to the last packed object. */ +static inline uint8_t *pack_last(pack_t pack) +{ + if (pack.len == 0) + return NULL; + uint8_t *it = pack_head(pack); + uint8_t *tail = pack_tail(pack); + while (true) { + uint8_t *next = pack_obj_next(it); + if (next == tail) + return it; + it = next; + } +} + +/** Push object to the end of the pack + * @return 0 on success, negative number on failure + */ +static inline int pack_obj_push(pack_t *pack, const uint8_t *obj, pack_objlen_t len) +{ + if (kr_fails_assert(pack && obj)) + return kr_error(EINVAL); + size_t packed_len = len + sizeof(len); + if (pack->len + packed_len > pack->cap) + return kr_error(ENOSPC); + + uint8_t *endp = pack->at + pack->len; + memcpy(endp, (char *)&len, sizeof(len)); + memcpy(endp + sizeof(len), obj, len); + pack->len += packed_len; + return 0; +} + +/** Returns a pointer to packed object. + * @return pointer to packed object or NULL + */ +static inline uint8_t *pack_obj_find(pack_t *pack, const uint8_t *obj, pack_objlen_t len) +{ + if (!pack || kr_fails_assert(obj)) + return NULL; + uint8_t *endp = pack_tail(*pack); + uint8_t *it = pack_head(*pack); + while (it != endp) { + uint8_t *val = pack_obj_val(it); + if (pack_obj_len(it) == len && memcmp(obj, val, len) == 0) + return it; + it = pack_obj_next(it); + } + return NULL; +} + +/** Delete object from the pack + * @return 0 on success, negative number on failure + */ +static inline int pack_obj_del(pack_t *pack, const uint8_t *obj, pack_objlen_t len) +{ + if (!pack || kr_fails_assert(obj)) + return kr_error(EINVAL); + uint8_t *endp = pack_tail(*pack); + uint8_t *it = pack_obj_find(pack, obj, len); + if (it) { + size_t packed_len = len + sizeof(len); + memmove(it, it + packed_len, endp - it - packed_len); + pack->len -= packed_len; + return 0; + } + return -1; +} + +/** Clone a pack, replacing destination pack; (*dst == NULL) is valid input. + * @return kr_error(ENOMEM) on allocation failure. */ +static inline int pack_clone(pack_t **dst, const pack_t *src, knot_mm_t *pool) +{ + if (kr_fails_assert(dst && src)) + return kr_error(EINVAL); + /* Get a valid pack_t. */ + if (!*dst) { + *dst = mm_alloc(pool, sizeof(pack_t)); + if (!*dst) return kr_error(ENOMEM); + pack_init(**dst); + /* Clone data only if needed */ + if (src->len == 0) return kr_ok(); + } + /* Replace the contents of the pack_t. */ + int ret = array_reserve_mm(**dst, src->len, kr_memreserve, pool); + if (ret < 0) { + return kr_error(ENOMEM); + } + memcpy((*dst)->at, src->at, src->len); + (*dst)->len = src->len; + return kr_ok(); +} + +#ifdef __cplusplus +} +#endif + +/** @} */ diff --git a/lib/generic/queue.c b/lib/generic/queue.c new file mode 100644 index 0000000..5bed153 --- /dev/null +++ b/lib/generic/queue.c @@ -0,0 +1,140 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "lib/generic/queue.h" +#include <string.h> + +extern inline void * queue_head_impl(const struct queue *q); + +void queue_init_impl(struct queue *q, size_t item_size) +{ + q->len = 0; + q->item_size = item_size; + q->head = q->tail = NULL; + /* Take 128 B (two x86 cache lines), except a small margin + * that the allocator can use for its overhead. + * Normally (64-bit pointers) this means 16 B header + 13*8 B data. */ + q->chunk_cap = (128 - offsetof(struct queue_chunk, data) + - sizeof(size_t) + ) / item_size; + if (!q->chunk_cap) q->chunk_cap = 1; /* item_size big enough by itself */ +} + +void queue_deinit_impl(struct queue *q) +{ + if (kr_fails_assert(q)) + return; + struct queue_chunk *p = q->head; + while (p != NULL) { + struct queue_chunk *pf = p; + p = p->next; + free(pf); + } +#ifndef NDEBUG + memset(q, 0, sizeof(*q)); +#endif +} + +static struct queue_chunk * queue_chunk_new(const struct queue *q) +{ + /* size_t cast is to avoid unintended sign-extension */ + struct queue_chunk *c = malloc(offsetof(struct queue_chunk, data) + + (size_t) q->chunk_cap * (size_t) q->item_size); + if (unlikely(!c)) abort(); // simplify stuff + memset(c, 0, offsetof(struct queue_chunk, data)); + c->cap = q->chunk_cap; + /* ->begin and ->end are zero, i.e. we optimize for _push + * and not _push_head, by default. */ + return c; +} + +/* Return pointer to the space for the new element. */ +void * queue_push_impl(struct queue *q) +{ + kr_require(q); + struct queue_chunk *t = q->tail; // shorthand + if (unlikely(!t)) { + kr_require(!q->head && !q->len); + q->head = q->tail = t = queue_chunk_new(q); + } else + if (t->end == t->cap) { + if (t->begin * 2 >= t->cap) { + /* Utilization is below 50%, so let's shift (no overlap). + * (size_t cast is to avoid unintended sign-extension) */ + memcpy(t->data, t->data + t->begin * q->item_size, + (size_t) (t->end - t->begin) * (size_t) q->item_size); + t->end -= t->begin; + t->begin = 0; + } else { + /* Let's grow the tail by another chunk. */ + kr_require(!t->next); + t->next = queue_chunk_new(q); + t = q->tail = t->next; + } + } + kr_require(t->end < t->cap); + ++(q->len); + ++(t->end); + return t->data + q->item_size * (t->end - 1); +} + +/* Return pointer to the space for the new element. */ +void * queue_push_head_impl(struct queue *q) +{ + /* When we have choice, we optimize for further _push_head, + * i.e. when shifting or allocating a chunk, + * we store items on the tail-end of the chunk. */ + kr_require(q); + struct queue_chunk *h = q->head; // shorthand + if (unlikely(!h)) { + kr_require(!q->tail && !q->len); + h = q->head = q->tail = queue_chunk_new(q); + h->begin = h->end = h->cap; + } else + if (h->begin == 0) { + if (h->end * 2 <= h->cap) { + /* Utilization is below 50%, so let's shift (no overlap). + * Computations here are simplified due to h->begin == 0. + * (size_t cast is to avoid unintended sign-extension) */ + const int cnt = h->end; + memcpy(h->data + (h->cap - cnt) * q->item_size, h->data, + (size_t) cnt * (size_t) q->item_size); + h->begin = h->cap - cnt; + h->end = h->cap; + } else { + /* Let's grow the head by another chunk. */ + h = queue_chunk_new(q); + h->next = q->head; + q->head = h; + h->begin = h->end = h->cap; + } + } + kr_require(h->begin > 0); + --(h->begin); + ++(q->len); + return h->data + q->item_size * h->begin; +} + +void queue_pop_impl(struct queue *q) +{ + kr_require(q); + struct queue_chunk *h = q->head; + kr_require(h && h->end > h->begin); + if (h->end - h->begin == 1) { + /* removing the last element in the chunk */ + kr_require((q->len == 1) == (q->head == q->tail)); + if (q->len == 1) { + q->tail = NULL; + kr_require(!h->next); + } else { + kr_require(h->next); + } + q->head = h->next; + free(h); + } else { + ++(h->begin); + } + --(q->len); +} + diff --git a/lib/generic/queue.h b/lib/generic/queue.h new file mode 100644 index 0000000..3fa52ce --- /dev/null +++ b/lib/generic/queue.h @@ -0,0 +1,230 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ +/** + * @file queue.h + * @brief A queue, usable for FIFO and LIFO simultaneously. + * + * Both the head and tail of the queue can be accessed and pushed to, + * but only the head can be popped from. + * + * @note The implementation uses a singly linked list of blocks ("chunks") + * where each block stores an array of values (for better efficiency). + * + * Example usage: + * @code{.c} + // define new queue type, and init a new queue instance + typedef queue_t(int) queue_int_t; + queue_int_t q; + queue_init(q); + // do some operations + queue_push(q, 1); + queue_push(q, 2); + queue_push(q, 3); + queue_push(q, 4); + queue_pop(q); + kr_require(queue_head(q) == 2); + kr_require(queue_tail(q) == 4); + + // you may iterate + typedef queue_it_t(int) queue_it_int_t; + for (queue_it_int_t it = queue_it_begin(q); !queue_it_finished(it); + queue_it_next(it)) { + ++queue_it_val(it); + } + kr_require(queue_tail(q) == 5); + + queue_push_head(q, 0); + ++queue_tail(q); + kr_require(queue_tail(q) == 6); + // free it up + queue_deinit(q); + + // you may use dynamic allocation for the type itself + queue_int_t *qm = malloc(sizeof(queue_int_t)); + queue_init(*qm); + queue_deinit(*qm); + free(qm); + * @endcode + * + * \addtogroup generics + * @{ + */ + +#pragma once + +#include "lib/defines.h" +#include "lib/utils.h" +#include "contrib/ucw/lib.h" +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> + +/** @brief The type for queue, parametrized by value type. */ +#define queue_t(type) \ + union { \ + type *pdata_t; /* only the *type* information is used */ \ + struct queue queue; \ + } + +/** @brief Initialize a queue. You can malloc() it the usual way. */ +#define queue_init(q) do { \ + (void)(((__typeof__(((q).pdata_t)))0) == (void *)0); /* typecheck queue_t */ \ + queue_init_impl(&(q).queue, sizeof(*(q).pdata_t)); \ + } while (false) + +/** @brief De-initialize a queue: make it invalid and free any inner allocations. */ +#define queue_deinit(q) \ + queue_deinit_impl(&(q).queue) + +/** @brief Push data to queue's tail. (Type-safe version; use _impl() otherwise.) */ +#define queue_push(q, data) \ + *((__typeof__((q).pdata_t)) queue_push_impl(&(q).queue)) = data + +/** @brief Push data to queue's head. (Type-safe version; use _impl() otherwise.) */ +#define queue_push_head(q, data) \ + *((__typeof__((q).pdata_t)) queue_push_head_impl(&(q).queue)) = data + +/** @brief Remove the element at the head. + * The queue must not be empty. */ +#define queue_pop(q) \ + queue_pop_impl(&(q).queue) + +/** @brief Return a "reference" to the element at the head (it's an L-value). + * The queue must not be empty. */ +#define queue_head(q) \ + ( *(__typeof__((q).pdata_t)) queue_head_impl(&(q).queue) ) + +/** @brief Return a "reference" to the element at the tail (it's an L-value). + * The queue must not be empty. */ +#define queue_tail(q) \ + ( *(__typeof__((q).pdata_t)) queue_tail_impl(&(q).queue) ) + +/** @brief Return the number of elements in the queue (very efficient). */ +#define queue_len(q) \ + ((const size_t)(q).queue.len) + + +/** @brief Type for queue iterator, parametrized by value type. + * It's a simple structure that owns no other resources. + * You may NOT use it after doing any push or pop (without _begin again). */ +#define queue_it_t(type) \ + union { \ + type *pdata_t; /* only the *type* information is used */ \ + struct queue_it iter; \ + } + +/** @brief Initialize a queue iterator at the head of the queue. + * If you use this in assignment (instead of initialization), + * you will unfortunately need to add corresponding type-cast in front. + * Beware: there's no type-check between queue and iterator! */ +#define queue_it_begin(q) \ + { .iter = queue_it_begin_impl(&(q).queue) } + +/** @brief Return a "reference" to the current element (it's an L-value) . */ +#define queue_it_val(it) \ + ( *(__typeof__((it).pdata_t)) queue_it_val_impl(&(it).iter) ) + +/** @brief Test if the iterator has gone past the last element. + * If it has, you may not use _val or _next. */ +#define queue_it_finished(it) \ + queue_it_finished_impl(&(it).iter) + +/** @brief Advance the iterator to the next element. */ +#define queue_it_next(it) \ + queue_it_next_impl(&(it).iter) + + + +/* ====================== Internal for the implementation ================== */ +/** @cond internal */ + +struct queue; +/* Non-inline functions are exported to be usable from daemon. */ +KR_EXPORT void queue_init_impl(struct queue *q, size_t item_size); +KR_EXPORT void queue_deinit_impl(struct queue *q); +KR_EXPORT void * queue_push_impl(struct queue *q); +KR_EXPORT void * queue_push_head_impl(struct queue *q); +KR_EXPORT void queue_pop_impl(struct queue *q); + +struct queue_chunk; +struct queue { + size_t len; /**< the current number of items in queue */ + uint16_t chunk_cap; /**< max. number of items in each chunk */ + uint16_t item_size; /**< sizeof() each item */ + struct queue_chunk *head, *tail; /*< first and last chunk (or NULLs) */ +}; + +struct queue_chunk { + struct queue_chunk *next; /*< *head -> ... -> *tail; each is non-empty */ + int16_t begin, end, cap, pad_; /*< indices: zero is closest to head */ + /*< We could fit into uint8_t for example, but the choice of (3+1)*2 bytes + * is a compromise between wasting space and getting a good alignment. + * In particular, queue_t(type*) will store the pointers on addresses + * aligned to the pointer size, on both 64-bit and 32-bit platforms. + */ + char data[]; + /**< The item data. We use "char" to satisfy the C99+ aliasing rules. + * See C99 section 6.5 Expressions, paragraph 7. + * Any type can be accessed through char-pointer, + * so we can use a common struct definition + * for all types being held. + */ +}; + +KR_EXPORT inline void * queue_head_impl(const struct queue *q) +{ + kr_require(q); + struct queue_chunk *h = q->head; + kr_require(h && h->end > h->begin); + return h->data + h->begin * q->item_size; +} + +static inline void * queue_tail_impl(const struct queue *q) +{ + kr_require(q); + struct queue_chunk *t = q->tail; + kr_require(t && t->end > t->begin); + return t->data + (t->end - 1) * q->item_size; +} + +struct queue_it { + struct queue_chunk *chunk; + int16_t pos, item_size; +}; + +static inline struct queue_it queue_it_begin_impl(struct queue *q) +{ + kr_require(q); + return (struct queue_it){ + .chunk = q->head, + .pos = q->head ? q->head->begin : -1, + .item_size = q->item_size, + }; +} + +static inline bool queue_it_finished_impl(struct queue_it *it) +{ + return it->chunk == NULL || it->pos >= it->chunk->end; +} + +static inline void * queue_it_val_impl(struct queue_it *it) +{ + kr_require(!queue_it_finished_impl(it)); + return it->chunk->data + it->pos * it->item_size; +} + +static inline void queue_it_next_impl(struct queue_it *it) +{ + kr_require(!queue_it_finished_impl(it)); + ++(it->pos); + if (it->pos < it->chunk->end) + return; + it->chunk = it->chunk->next; + it->pos = it->chunk ? it->chunk->begin : -1; +} + +/** @endcond (internal) */ +/** @} (addtogroup generics) */ + diff --git a/lib/generic/test_array.c b/lib/generic/test_array.c new file mode 100644 index 0000000..3e95b49 --- /dev/null +++ b/lib/generic/test_array.c @@ -0,0 +1,99 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "tests/unit/test.h" +#include "lib/generic/array.h" + +knot_mm_t global_mm; + +static void test_array(void **state) +{ + int ret = 0; + array_t(int) arr; + array_init(arr); + + /* Basic access */ + assert_int_equal(arr.len, 0); + assert_int_equal(array_push(arr, 5), 0); + assert_int_equal(arr.at[0], 5); + assert_int_equal(array_tail(arr), 5); + array_clear(arr); + + /* Reserve capacity and fill. */ + assert_true(array_reserve(arr, 5) >= 0); + for (unsigned i = 0; i < 100; ++i) { + ret = array_push(arr, i); + assert_true(ret >= 0); + } + + /* Make sure reservation holds. */ + assert_true(array_reserve(arr, 5) >= 0); + + /* Delete elements. */ + array_del(arr, 0); + while (arr.len > 0) { + array_pop(arr); + } + + /* Overfill. */ + for (unsigned i = 0; i < 4096; ++i) { + ret = array_push(arr, i); + assert_true(ret >= 0); + } + + array_clear(arr); +} + +/** Reservation through tracked memory allocator. */ +static int test_reserve(void *baton, void **mem, size_t elm_size, size_t want, size_t *have) +{ + if (want > *have) { + void *new_mem = mm_alloc(baton, elm_size * want); + if (*mem != NULL) { + memcpy(new_mem, *mem, (*have) * elm_size); + mm_free(baton, *mem); + } + *mem = new_mem; + *have = want; + } + + return 0; +} + +/** Reservation through fake memory allocator. */ +static int fake_reserve(void *baton, void **mem, size_t elm_size, size_t want, size_t *have) +{ + return -1; +} + +static void test_array_mm(void **state) +{ + array_t(int) arr; + array_init(arr); + + /* Reserve using fake memory allocator. */ + assert_false(array_reserve_mm(arr, 5, fake_reserve, NULL) >= 0); + + /* Reserve capacity and fill. */ + assert_true(array_reserve_mm(arr, 100, test_reserve, &global_mm) >= 0); + for (unsigned i = 0; i < 100; ++i) { + int ret = array_push(arr, i); + assert_true(ret >= 0); + } + + array_clear_mm(arr, mm_free, &global_mm); + +} + +int main(void) +{ + test_mm_ctx_init(&global_mm); + + const UnitTest tests[] = { + unit_test(test_array), + unit_test(test_array_mm) + }; + + return run_tests(tests); +} diff --git a/lib/generic/test_lru.c b/lib/generic/test_lru.c new file mode 100644 index 0000000..7c2f11f --- /dev/null +++ b/lib/generic/test_lru.c @@ -0,0 +1,111 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "tests/unit/test.h" +#include "lib/generic/lru.h" + +typedef lru_t(int) lru_int_t; +#define HASH_SIZE 1024 +#define KEY_LEN(x) (strlen(x) + 1) + +/* + * Sample dictionary + */ +static const char *dict[] = { + "catagmatic", "prevaricator", "statoscope", "workhand", "benzamide", + "alluvia", "fanciful", "bladish", "Tarsius", "unfast", "appropriative", + "seraphically", "monkeypod", "deflectometer", "tanglesome", "zodiacal", + "physiologically", "economizer", "forcepslike", "betrumpet", + "Danization", "broadthroat", "randir", "usherette", "nephropyosis", + "hematocyanin", "chrysohermidin", "uncave", "mirksome", "podophyllum", + "siphonognathous", "indoor", "featheriness", "forwardation", + "archruler", "soricoid", "Dailamite", "carmoisin", "controllability", + "unpragmatical", "childless", "transumpt", "productive", + "thyreotoxicosis", "oversorrow", "disshadow", "osse", "roar", + "pantomnesia", "talcer", "hydrorrhoea", "Satyridae", "undetesting", + "smoothbored", "widower", "sivathere", "pendle", "saltation", + "autopelagic", "campfight", "unexplained", "Macrorhamphosus", + "absconsa", "counterflory", "interdependent", "triact", "reconcentration", + "oversharpness", "sarcoenchondroma", "superstimulate", "assessory", + "pseudepiscopacy", "telescopically", "ventriloque", "politicaster", + "Caesalpiniaceae", "inopportunity", "Helion", "uncompatible", + "cephaloclasia", "oversearch", "Mahayanistic", "quarterspace", + "bacillogenic", "hamartite", "polytheistical", "unescapableness", + "Pterophorus", "cradlemaking", "Hippoboscidae", "overindustrialize", + "perishless", "cupidity", "semilichen", "gadge", "detrimental", + "misencourage", "toparchia", "lurchingly", "apocatastasis" +}; + +static void test_insert(void **state) +{ + lru_int_t *lru = *state; + int dict_size = sizeof(dict) / sizeof(const char *); + int i; + + for (i = 0; i < dict_size; i++) { + int *data = lru_get_new(lru, dict[i], KEY_LEN(dict[i]), NULL); + if (!data) { + continue; + } + *data = i; + assert_true(*lru_get_try(lru, dict[i], KEY_LEN(dict[i])) == i); + } +} + +static void test_missing(void **state) +{ + lru_int_t *lru = *state; + const char *notin = "not in lru"; + assert_true(lru_get_try(lru, notin, KEY_LEN(notin)) == NULL); +} + +static void test_eviction(void **state) +{ + lru_int_t *lru = *state; + char key[16]; + for (unsigned i = 0; i < HASH_SIZE; ++i) { + test_randstr(key, sizeof(key)); + int *data = lru_get_new(lru, key, sizeof(key), NULL); + if (!data) { + continue; + } + *data = i; + if (*lru_get_try(lru, key, sizeof(key)) != i) { + assert_true(0); + } + } +} + +static void test_init(void **state) +{ + lru_int_t *lru; + lru_create(&lru, HASH_SIZE, NULL, NULL); + assert_non_null(lru); + *state = lru; +} + +static void test_deinit(void **state) +{ + lru_int_t *lru = *state; + lru_free(lru); +} + +/* Program entry point */ +int main(int argc, char **argv) +{ + const UnitTest tests[] = { + group_test_setup(test_init), + unit_test(test_insert), + unit_test(test_missing), + unit_test(test_eviction), + group_test_teardown(test_deinit) + }; + + return run_group_tests(tests); +} diff --git a/lib/generic/test_pack.c b/lib/generic/test_pack.c new file mode 100644 index 0000000..e1c1ab5 --- /dev/null +++ b/lib/generic/test_pack.c @@ -0,0 +1,68 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "tests/unit/test.h" +#include "lib/generic/pack.h" + +#define U8(x) (const uint8_t *)(x) +knot_mm_t global_mm; + +static void test_pack_std(void **state) +{ + int ret = 0; + pack_t pack; + pack_init(pack); + assert_int_equal(pack.len, 0); + + /* Test that iterator on empty pack works */ + assert_null(pack_head(pack)); + assert_null(pack_tail(pack)); + assert_null(pack_obj_find(&pack, U8(""), 1)); + assert_int_equal(pack_obj_len(pack_head(pack)), 0); + assert_int_equal(pack_obj_del(&pack, U8(""), 1), -1); + + /* Push/delete without reservation. */ + assert_int_not_equal(pack_obj_push(&pack, U8(""), 1), 0); + assert_int_not_equal(pack_obj_del(&pack, U8(""), 1), 0); + + /* Reserve capacity and fill. */ + assert_true(pack_reserve(pack, 10, 10 * 2) >= 0); + for (unsigned i = 0; i < 10; ++i) { + ret = pack_obj_push(&pack, U8("de"), 2); + assert_true(ret >= 0); + } + + /* Iterate */ + uint8_t *it = pack_head(pack); + assert_non_null(it); + while (it != pack_tail(pack)) { + assert_int_equal(pack_obj_len(it), 2); + assert_true(memcmp(pack_obj_val(it), "de", 2) == 0); + it = pack_obj_next(it); + } + + /* Find */ + it = pack_obj_find(&pack, U8("de"), 2); + assert_non_null(it); + it = pack_obj_find(&pack, U8("ed"), 2); + assert_null(it); + + /* Delete */ + assert_int_not_equal(pack_obj_del(&pack, U8("be"), 2), 0); + assert_int_equal(pack_obj_del(&pack, U8("de"), 2), 0); + assert_int_equal(pack.len, 9*(2+2)); /* 9 objects, length=2 */ + + pack_clear(pack); +} + +int main(void) +{ + test_mm_ctx_init(&global_mm); + + const UnitTest tests[] = { + unit_test(test_pack_std), + }; + + return run_tests(tests); +} diff --git a/lib/generic/test_queue.c b/lib/generic/test_queue.c new file mode 100644 index 0000000..eb26b01 --- /dev/null +++ b/lib/generic/test_queue.c @@ -0,0 +1,71 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "tests/unit/test.h" +#include "lib/generic/queue.h" + +/* The main intention is to use queues with pointers, so we test the same-sized int. */ +typedef queue_t(ptrdiff_t) queue_int_t; +typedef queue_it_t(int) queue_int_it_t; + +static void test_int(void **state_) +{ + queue_int_t q; + queue_init(q); + + /* Case of emptying the queue (and using again) has been broken for a long time. */ + queue_push(q, 2); + queue_pop(q); + queue_push(q, 4); + queue_pop(q); + + queue_push_head(q, 2); + queue_push_head(q, 1); + queue_push_head(q, 0); + for (int i = 0; i < 100; ++i) { + assert_int_equal(queue_head(q), i); + queue_push(q, i + 3); + queue_pop(q); + } + assert_int_equal(queue_len(q), 3); + for (int i = 99; i > 0; --i) { + assert_int_equal(queue_head(q), i + 1); + queue_push_head(q, i); + } + assert_int_equal(queue_len(q), 3 + 99); + + /* Basic iterator test. */ + { + int i = 0; + for (queue_int_it_t it = queue_it_begin(q); !queue_it_finished(it); + queue_it_next(it)) { + ++queue_it_val(it); + ++i; + } + assert_int_equal(queue_len(q), i); + } + + queue_deinit(q); + queue_init(q); + + for (int i = 0; i < 100; ++i) { + queue_push(q, 2*i); + queue_push(q, 2*i + 1); + assert_int_equal(queue_head(q), i); + queue_pop(q); + } + + queue_deinit(q); +} + + +int main(void) +{ + const UnitTest tests[] = { + unit_test(test_int), + }; + + return run_tests(tests); +} + diff --git a/lib/generic/test_trie.c b/lib/generic/test_trie.c new file mode 100644 index 0000000..9ecd67c --- /dev/null +++ b/lib/generic/test_trie.c @@ -0,0 +1,154 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "lib/generic/trie.h" +#include "tests/unit/test.h" + +static const char *dict[] = { + "catagmatic", "prevaricator", "statoscope", "workhand", "benzamide", + "work", "workhands", // have some keys that are prefixes of each other + "alluvia", "fanciful", "bladish", "Tarsius", "unfast", "appropriative", + "seraphically", "monkeypod", "deflectometer", "tanglesome", "zodiacal", + "physiologically", "economizer", "forcepslike", "betrumpet", + "Danization", "broadthroat", "randir", "usherette", "nephropyosis", + "hematocyanin", "chrysohermidin", "uncave", "mirksome", "podophyllum", + "siphonognathous", "indoor", "featheriness", "forwardation", + "archruler", "soricoid", "Dailamite", "carmoisin", "controllability", + "unpragmatical", "childless", "transumpt", "productive", + "thyreotoxicosis", "oversorrow", "disshadow", "osse", "roar", + "pantomnesia", "talcer", "hydrorrhoea", "Satyridae", "undetesting", + "smoothbored", "widower", "sivathere", "pendle", "saltation", + "autopelagic", "campfight", "unexplained", "Macrorhamphosus", + "absconsa", "counterflory", "interdependent", "triact", "reconcentration", + "oversharpness", "sarcoenchondroma", "superstimulate", "assessory", + "pseudepiscopacy", "telescopically", "ventriloque", "politicaster", + "Caesalpiniaceae", "inopportunity", "Helion", "uncompatible", + "cephaloclasia", "oversearch", "Mahayanistic", "quarterspace", + "bacillogenic", "hamartite", "polytheistical", "unescapableness", + "Pterophorus", "cradlemaking", "Hippoboscidae", "overindustrialize", + "perishless", "cupidity", "semilichen", "gadge", "detrimental", + "misencourage", "toparchia", "lurchingly", "apocatastasis" +}; +#define KEY_LEN(x) (strlen(x) + 1) +static const int dict_size = sizeof(dict) / sizeof(const char *); + +static void test_init(void **state) +{ + trie_t *t = trie_create(NULL); + assert_non_null(t); + *state = t; +} + +static void test_insert(void **state) +{ + trie_t *t = *state; + + for (int i = 0; i < dict_size; ++i) { + trie_val_t *data = trie_get_ins(t, dict[i], KEY_LEN(dict[i])); + assert_non_null(data); + assert_null(*data); + *data = (char *)NULL + i; // yes, ugly + assert_ptr_equal(trie_get_try(t, dict[i], KEY_LEN(dict[i])), data); + } + assert_int_equal(trie_weight(t), dict_size); +} + +static void test_missing(void **state) +{ + trie_t *t = *state; + const char *notin = "p"; + assert_null(trie_get_try(t, notin, KEY_LEN(notin))); +} + +static int cmpstringp(const void *p1, const void *p2) +{ + return strcmp(* (char * const *) p1, * (char * const *) p2); +} + +static void test_iter(void **state) +{ + // prepare sorted dictionary + char *dict_sorted[dict_size]; + memcpy(dict_sorted, dict, sizeof(dict)); + qsort(dict_sorted, dict_size, sizeof(dict[0]), cmpstringp); + + // iterate and check the order is consistent + trie_t *t = *state; + trie_it_t *it = trie_it_begin(t); + for (int i = 0; i < dict_size; ++i, trie_it_next(it)) { + assert_false(trie_it_finished(it)); + size_t len; + const char *key = trie_it_key(it, &len); + assert_int_equal(KEY_LEN(key), len); + assert_string_equal(key, dict_sorted[i]); + assert_ptr_equal(dict[(char *)*trie_it_val(it) - (char *)NULL], + dict_sorted[i]); + } + assert_true(trie_it_finished(it)); + trie_it_free(it); +} + +static void test_queue(void **state) +{ + trie_t *t = *state; + // remove all the elements in ascending order + for (int i = 0; i < dict_size; ++i) { + char *key; + uint32_t len; + trie_val_t *data = trie_get_first(t, &key, &len); + assert_non_null(key); + assert_int_equal(len, KEY_LEN(key)); + assert_non_null(data); + ptrdiff_t key_i = (char *)*data - (char *)NULL; + assert_string_equal(key, dict[key_i]); + + len = 30; + char key_buf[len]; + ptrdiff_t key_i_new; + int ret = trie_del_first(t, key_buf, &len, (trie_val_t *)&key_i_new); + assert_int_equal(ret, KNOT_EOK); + assert_int_equal(KEY_LEN(key_buf), len); + assert_int_equal(key_i, key_i_new); + assert_string_equal(dict[key_i], key_buf); + } +} + +static void test_leq_bug(void **state) +{ + /* We use different contents of the trie, + * so that the particular bug would've been triggered. */ + trie_t *t = trie_create(NULL); + char key = 'a'; + trie_get_ins(t, &key, sizeof(key)); + + key = (char)0xff; + trie_val_t *val; + int ret = trie_get_leq(t, &key, sizeof(key), &val); + assert_int_equal(ret, 1); + trie_free(t); +} + +static void test_deinit(void **state) +{ + trie_t *t = *state; + trie_free(t); + *state = NULL; +} + +/* Program entry point */ +int main(int argc, char **argv) +{ + const UnitTest tests[] = { + group_test_setup(test_init), + unit_test(test_insert), + unit_test(test_leq_bug), + unit_test(test_missing), + unit_test(test_iter), + unit_test(test_queue), + group_test_teardown(test_deinit) + }; + + return run_group_tests(tests); +} + diff --git a/lib/generic/trie.c b/lib/generic/trie.c new file mode 100644 index 0000000..f9aceda --- /dev/null +++ b/lib/generic/trie.c @@ -0,0 +1,923 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + + The code originated from https://github.com/fanf2/qp/blob/master/qp.c + at revision 5f6d93753. + */ + +#include <stdlib.h> +#include <string.h> + +#include "lib/generic/trie.h" +#include "lib/utils.h" +#include "contrib/ucw/lib.h" + +#if defined(__i386) || defined(__x86_64) || defined(_M_IX86) \ + || (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN) \ + && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + + /*! + * \brief Use a pointer alignment hack to save memory. + * + * When on, isbranch() relies on the fact that in leaf_t the first pointer + * is aligned on multiple of 4 bytes and that the flags bitfield is + * overlaid over the lowest two bits of that pointer. + * Neither is really guaranteed by the C standards; the second part should + * be OK with x86_64 ABI and most likely any other little-endian platform. + * It would be possible to manipulate the right bits portably, but it would + * complicate the code nontrivially. C++ doesn't even guarantee type-punning. + * In debug mode we check this works OK when creating a new trie instance. + */ + #define FLAGS_HACK 1 +#else + #define FLAGS_HACK 0 +#endif + +typedef unsigned char byte; +#ifndef uint +typedef unsigned int uint; +#define uint uint +#endif +typedef uint bitmap_t; /*! Bit-maps, using the range of 1<<0 to 1<<16 (inclusive). */ + +typedef struct { + uint32_t len; // 32 bits are enough for key lengths; probably even 16 bits would be. + char chars[]; +} tkey_t; + +/*! \brief Leaf of trie. */ +typedef struct { + #if !FLAGS_HACK + byte flags; + #endif + tkey_t *key; /*!< The pointer must be aligned to 4-byte multiples! */ + trie_val_t val; +} leaf_t; + +/*! \brief A trie node is either leaf_t or branch_t. */ +typedef union node node_t; + +/*! + * \brief Branch node of trie. + * + * - The flags distinguish whether the node is a leaf_t (0), or a branch + * testing the more-important nibble (1) or the less-important one (2). + * - It stores the index of the byte that the node tests. The combined + * value (index*4 + flags) increases in branch nodes as you go deeper + * into the trie. All the keys below a branch are identical up to the + * nibble identified by the branch. Indices have to be stored because + * we skip any branch nodes that would have a single child. + * (Consequently, the skipped parts of key have to be validated in a leaf.) + * - The bitmap indicates which subtries are present. The present child nodes + * are stored in the twigs array (with no holes between them). + * - To simplify storing keys that are prefixes of each other, the end-of-string + * position is treated as another nibble value, ordered before all others. + * That affects the bitmap and twigs fields. + * + * \note The branch nodes are never allocated individually, but they are + * always part of either the root node or the twigs array of the parent. + */ +typedef struct { + #if FLAGS_HACK + uint32_t flags : 2, + bitmap : 17; /*!< The first bitmap bit is for end-of-string child. */ + #else + byte flags; + uint32_t bitmap; + #endif + uint32_t index; + node_t *twigs; +} branch_t; + +union node { + leaf_t leaf; + branch_t branch; +}; + +struct trie { + node_t root; // undefined when weight == 0, see empty_root() + size_t weight; + knot_mm_t mm; +}; + +/*! \brief Make the root node empty (debug-only). */ +static inline void empty_root(node_t *root) { +#ifndef NDEBUG + *root = (node_t){ .branch = { + .flags = 3, // invalid value that fits + .bitmap = 0, + .index = -1, + .twigs = NULL + } }; +#endif +} + +/*! \brief Check that unportable code works OK (debug-only). */ +static void assert_portability(void) { +#if FLAGS_HACK + kr_require(((union node){ .leaf = { + .key = (tkey_t *)(((uint8_t *)NULL) + 1), + .val = NULL + } }).branch.flags == 1); +#endif +} + +/*! \brief Propagate error codes. */ +#define ERR_RETURN(x) \ + do { \ + int err_code_ = x; \ + if (unlikely(err_code_ != KNOT_EOK)) \ + return err_code_; \ + } while (false) + +/*! + * \brief Count the number of set bits. + * + * \TODO This implementation may be relatively slow on some HW. + */ +static uint bitmap_weight(bitmap_t w) +{ + kr_require((w & ~((1 << 17) - 1)) == 0); // using the least-important 17 bits + return __builtin_popcount(w); +} + +/*! \brief Only keep the lowest bit in the bitmap (least significant -> twigs[0]). */ +static bitmap_t bitmap_lowest_bit(bitmap_t w) +{ + kr_require((w & ~((1 << 17) - 1)) == 0); // using the least-important 17 bits + return 1 << __builtin_ctz(w); +} + +/*! \brief Test flags to determine type of this node. */ +static bool isbranch(const node_t *t) +{ + uint f = t->branch.flags; + kr_require(f <= 2); + return f != 0; +} + +/*! \brief Make a bitmask for testing a branch bitmap. */ +static bitmap_t nibbit(byte k, uint flags) +{ + uint shift = (2 - flags) << 2; + uint nibble = (k >> shift) & 0xf; + return 1 << (nibble + 1/*because of prefix keys*/); +} + +/*! \brief Extract a nibble from a key and turn it into a bitmask. */ +static bitmap_t twigbit(const node_t *t, const char *key, uint32_t len) +{ + kr_require(isbranch(t)); + uint i = t->branch.index; + + if (i >= len) + return 1 << 0; // leaf position + + return nibbit((byte)key[i], t->branch.flags); +} + +/*! \brief Test if a branch node has a child indicated by a bitmask. */ +static bool hastwig(const node_t *t, bitmap_t bit) +{ + kr_require(isbranch(t)); + return t->branch.bitmap & bit; +} + +/*! \brief Compute offset of an existing child in a branch node. */ +static uint twigoff(const node_t *t, bitmap_t b) +{ + kr_require(isbranch(t)); + return bitmap_weight(t->branch.bitmap & (b - 1)); +} + +/*! \brief Get pointer to a particular child of a branch node. */ +static node_t* twig(node_t *t, uint i) +{ + kr_require(isbranch(t)); + return &t->branch.twigs[i]; +} + +/*! + * \brief For a branch nod, compute offset of a child and child count. + * + * Having this separate might be meaningful for performance optimization. + */ +#define TWIGOFFMAX(off, max, t, b) do { \ + (off) = twigoff((t), (b)); \ + (max) = bitmap_weight((t)->branch.bitmap);\ + } while(0) + +/*! \brief Simple string comparator. */ +static int key_cmp(const char *k1, uint32_t k1_len, const char *k2, uint32_t k2_len) +{ + int ret = memcmp(k1, k2, MIN(k1_len, k2_len)); + if (ret != 0) { + return ret; + } + + /* Key string is equal, compare lengths. */ + if (k1_len == k2_len) { + return 0; + } else if (k1_len < k2_len) { + return -1; + } else { + return 1; + } +} + +trie_t* trie_create(knot_mm_t *mm) +{ + assert_portability(); + trie_t *trie = mm_alloc(mm, sizeof(trie_t)); + if (trie != NULL) { + empty_root(&trie->root); + trie->weight = 0; + if (mm != NULL) + trie->mm = *mm; + else + mm_ctx_init(&trie->mm); + } + return trie; +} + +/*! \brief Free anything under the trie node, except for the passed pointer itself. */ +static void clear_trie(node_t *trie, knot_mm_t *mm) +{ + if (!isbranch(trie)) { + mm_free(mm, trie->leaf.key); + } else { + branch_t *b = &trie->branch; + int len = bitmap_weight(b->bitmap); + for (int i = 0; i < len; ++i) + clear_trie(b->twigs + i, mm); + mm_free(mm, b->twigs); + } +} + +void trie_free(trie_t *tbl) +{ + if (tbl == NULL) + return; + if (tbl->weight) + clear_trie(&tbl->root, &tbl->mm); + mm_free(&tbl->mm, tbl); +} + +void trie_clear(trie_t *tbl) +{ + if (kr_fails_assert(tbl)) + return; + if (!tbl->weight) + return; + clear_trie(&tbl->root, &tbl->mm); + empty_root(&tbl->root); + tbl->weight = 0; +} + +size_t trie_weight(const trie_t *tbl) +{ + kr_require(tbl); + return tbl->weight; +} + +struct found { + leaf_t *l; /**< the found leaf (NULL if not found) */ + branch_t *p; /**< the leaf's parent (if exists) */ + bitmap_t b; /**< bit-mask with a single bit marking l under p */ +}; +/** Search trie for an item with the given key (equality only). */ +static struct found find_equal(trie_t *tbl, const char *key, uint32_t len) +{ + kr_require(tbl); + struct found ret0; + memset(&ret0, 0, sizeof(ret0)); + if (!tbl->weight) + return ret0; + /* Current node and parent while descending (returned values basically). */ + node_t *t = &tbl->root; + branch_t *p = NULL; + bitmap_t b = 0; + while (isbranch(t)) { + __builtin_prefetch(t->branch.twigs); + b = twigbit(t, key, len); + if (!hastwig(t, b)) + return ret0; + p = &t->branch; + t = twig(t, twigoff(t, b)); + } + if (key_cmp(key, len, t->leaf.key->chars, t->leaf.key->len) != 0) + return ret0; + return (struct found) { + .l = &t->leaf, + .p = p, + .b = b, + }; +} +/** Find item with the first key (lexicographical order). */ +static struct found find_first(trie_t *tbl) +{ + kr_require(tbl); + if (!tbl->weight) { + struct found ret0; + memset(&ret0, 0, sizeof(ret0)); + return ret0; + } + /* Current node and parent while descending (returned values basically). */ + node_t *t = &tbl->root; + branch_t *p = NULL; + while (isbranch(t)) { + p = &t->branch; + t = &p->twigs[0]; + } + return (struct found) { + .l = &t->leaf, + .p = p, + .b = p ? bitmap_lowest_bit(p->bitmap) : 0, + }; +} + +trie_val_t* trie_get_try(trie_t *tbl, const char *key, uint32_t len) +{ + struct found found = find_equal(tbl, key, len); + return found.l ? &found.l->val : NULL; +} + +trie_val_t* trie_get_first(trie_t *tbl, char **key, uint32_t *len) +{ + struct found found = find_first(tbl); + if (!found.l) + return NULL; + if (key) + *key = found.l->key->chars; + if (len) + *len = found.l->key->len; + return &found.l->val; +} + +/** Delete the found element (if any) and return value (unless NULL is passed) */ +static int del_found(trie_t *tbl, struct found found, trie_val_t *val) +{ + if (!found.l) + return KNOT_ENOENT; + mm_free(&tbl->mm, found.l->key); + if (val != NULL) + *val = found.l->val; // we return trie_val_t directly when deleting + --tbl->weight; + branch_t * const p = found.p; // short-hand + if (unlikely(!p)) { // whole trie was a single leaf + kr_require(tbl->weight == 0); + empty_root(&tbl->root); + return KNOT_EOK; + } + // remove leaf t as child of p; get child index via pointer arithmetic + int ci = ((union node *)found.l) - p->twigs, + cc = bitmap_weight(p->bitmap); // child count + kr_require(ci >= 0 && ci < cc); + + if (cc == 2) { // collapse binary node p: move the other child to this node + node_t *twigs = p->twigs; + (*(union node *)p) = twigs[1 - ci]; // it might be a leaf or branch + mm_free(&tbl->mm, twigs); + return KNOT_EOK; + } + memmove(p->twigs + ci, p->twigs + ci + 1, sizeof(node_t) * (cc - ci - 1)); + p->bitmap &= ~found.b; + node_t *twigs = mm_realloc(&tbl->mm, p->twigs, sizeof(node_t) * (cc - 1), + sizeof(node_t) * cc); + if (likely(twigs != NULL)) + p->twigs = twigs; + /* We can ignore mm_realloc failure, only beware that next time + * the prev_size passed to it wouldn't be correct; TODO? */ + return KNOT_EOK; +} + +int trie_del(trie_t *tbl, const char *key, uint32_t len, trie_val_t *val) +{ + struct found found = find_equal(tbl, key, len); + return del_found(tbl, found, val); +} + +int trie_del_first(trie_t *tbl, char *key, uint32_t *len, trie_val_t *val) +{ + struct found found = find_first(tbl); + if (!found.l) + return KNOT_ENOENT; + if (key) { + if (!len) + return KNOT_EINVAL; + if (*len < found.l->key->len) + return kr_error(ENOSPC); + memcpy(key, found.l->key->chars, found.l->key->len); + } + if (len) { // makes sense even with key == NULL + *len = found.l->key->len; + } + return del_found(tbl, found, val); +} + +/*! + * \brief Stack of nodes, storing a path down a trie. + * + * The structure also serves directly as the public trie_it_t type, + * in which case it always points to the current leaf, unless we've finished + * (i.e. it->len == 0). + */ +typedef struct trie_it { + node_t* *stack; /*!< The stack; malloc is used directly instead of mm. */ + uint32_t len; /*!< Current length of the stack. */ + uint32_t alen; /*!< Allocated/available length of the stack. */ + /*! \brief Initial storage for \a stack; it should fit in many use cases. */ + node_t* stack_init[60]; +} nstack_t; + +/*! \brief Create a node stack containing just the root (or empty). */ +static void ns_init(nstack_t *ns, trie_t *tbl) +{ + kr_require(tbl); + ns->stack = ns->stack_init; + ns->alen = sizeof(ns->stack_init) / sizeof(ns->stack_init[0]); + if (tbl->weight) { + ns->len = 1; + ns->stack[0] = &tbl->root; + } else { + ns->len = 0; + } +} + +/*! \brief Free inside of the stack, i.e. not the passed pointer itself. */ +static void ns_cleanup(nstack_t *ns) +{ + if (kr_fails_assert(ns && ns->stack)) + return; + if (likely(ns->stack == ns->stack_init)) + return; + free(ns->stack); + #ifndef NDEBUG + ns->stack = NULL; + ns->alen = 0; + #endif +} + +/*! \brief Allocate more space for the stack. */ +static int ns_longer_alloc(nstack_t *ns) +{ + ns->alen *= 2; + size_t new_size = sizeof(nstack_t) + ns->alen * sizeof(node_t *); + node_t **st; + if (ns->stack == ns->stack_init) { + st = malloc(new_size); + if (st != NULL) + memcpy(st, ns->stack, ns->len * sizeof(node_t *)); + } else { + st = realloc(ns->stack, new_size); + } + if (st == NULL) + return KNOT_ENOMEM; + ns->stack = st; + return KNOT_EOK; +} + +/*! \brief Ensure the node stack can be extended by one. */ +static inline int ns_longer(nstack_t *ns) +{ + // get a longer stack if needed + if (likely(ns->len < ns->alen)) + return KNOT_EOK; + return ns_longer_alloc(ns); // hand-split the part suitable for inlining +} + +/*! + * \brief Find the "branching point" as if searching for a key. + * + * The whole path to the point is kept on the passed stack; + * always at least the root will remain on the top of it. + * Beware: the precise semantics of this function is rather tricky. + * The top of the stack will contain: the corresponding leaf if exact match is found; + * or the immediate node below a branching-point-on-edge or the branching-point itself. + * + * \param info Set position of the point of first mismatch (in index and flags). + * \param first Set the value of the first non-matching character (from trie), + * optionally; end-of-string character has value -256 (that's why it's int). + * Note: the character is converted to *unsigned* char (i.e. 0..255), + * as that's the ordering used in the trie. + * + * \return KNOT_EOK or KNOT_ENOMEM. + */ +static int ns_find_branch(nstack_t *ns, const char *key, uint32_t len, + branch_t *info, int *first) +{ + kr_require(ns && ns->len && info); + // First find some leaf with longest matching prefix. + while (isbranch(ns->stack[ns->len - 1])) { + ERR_RETURN(ns_longer(ns)); + node_t *t = ns->stack[ns->len - 1]; + __builtin_prefetch(t->branch.twigs); + bitmap_t b = twigbit(t, key, len); + // Even if our key is missing from this branch we need to + // keep iterating down to a leaf. It doesn't matter which + // twig we choose since the keys are all the same up to this + // index. Note that blindly using twigoff(t, b) can cause + // an out-of-bounds index if it equals twigmax(t). + uint i = hastwig(t, b) ? twigoff(t, b) : 0; + ns->stack[ns->len++] = twig(t, i); + } + tkey_t *lkey = ns->stack[ns->len-1]->leaf.key; + // Find index of the first char that differs. + uint32_t index = 0; + while (index < MIN(len,lkey->len)) { + if (key[index] != lkey->chars[index]) + break; + else + ++index; + } + info->index = index; + if (first) + *first = lkey->len > index ? (unsigned char)lkey->chars[index] : -256; + // Find flags: which half-byte has matched. + uint flags; + if (index == len && len == lkey->len) { // found equivalent key + info->flags = flags = 0; + goto success; + } + if (likely(index < MIN(len,lkey->len))) { + byte k2 = (byte)lkey->chars[index]; + byte k1 = (byte)key[index]; + flags = ((k1 ^ k2) & 0xf0) ? 1 : 2; + } else { // one is prefix of another + flags = 1; + } + info->flags = flags; + // now go up the trie from the current leaf + branch_t *t; + do { + if (unlikely(ns->len == 1)) + goto success; // only the root stays on the stack + t = (branch_t*)ns->stack[ns->len - 2]; + if (t->index < index || (t->index == index && t->flags < flags)) + goto success; + --ns->len; + } while (true); +success: + #ifndef NDEBUG // invariants on successful return + kr_require(ns->len); + if (isbranch(ns->stack[ns->len - 1])) { + t = &ns->stack[ns->len - 1]->branch; + kr_require(t->index > index || (t->index == index && t->flags >= flags)); + } + if (ns->len > 1) { + t = &ns->stack[ns->len - 2]->branch; + kr_require(t->index < index || (t->index == index + && (t->flags < flags || (t->flags == 1 && flags == 0)))); + } + #endif + return KNOT_EOK; +} + +/*! + * \brief Advance the node stack to the last leaf in the subtree. + * + * \return KNOT_EOK or KNOT_ENOMEM. + */ +static int ns_last_leaf(nstack_t *ns) +{ + kr_require(ns); + do { + ERR_RETURN(ns_longer(ns)); + node_t *t = ns->stack[ns->len - 1]; + if (!isbranch(t)) + return KNOT_EOK; + int lasti = bitmap_weight(t->branch.bitmap) - 1; + kr_require(lasti >= 0); + ns->stack[ns->len++] = twig(t, lasti); + } while (true); +} + +/*! + * \brief Advance the node stack to the first leaf in the subtree. + * + * \return KNOT_EOK or KNOT_ENOMEM. + */ +static int ns_first_leaf(nstack_t *ns) +{ + kr_require(ns && ns->len); + do { + ERR_RETURN(ns_longer(ns)); + node_t *t = ns->stack[ns->len - 1]; + if (!isbranch(t)) + return KNOT_EOK; + ns->stack[ns->len++] = twig(t, 0); + } while (true); +} + +/*! + * \brief Advance the node stack to the leaf that is previous to the current node. + * + * \note Prefix leaf under the current node DOES count (if present; perhaps questionable). + * \return KNOT_EOK on success, KNOT_ENOENT on not-found, or possibly KNOT_ENOMEM. + */ +static int ns_prev_leaf(nstack_t *ns) +{ + kr_require(ns && ns->len > 0); + + node_t *t = ns->stack[ns->len - 1]; + if (hastwig(t, 1 << 0)) { // the prefix leaf + t = twig(t, 0); + ERR_RETURN(ns_longer(ns)); + ns->stack[ns->len++] = t; + return KNOT_EOK; + } + + do { + if (ns->len < 2) + return KNOT_ENOENT; // root without empty key has no previous leaf + t = ns->stack[ns->len - 1]; + node_t *p = ns->stack[ns->len - 2]; + int pindex = t - p->branch.twigs; // index in parent via pointer arithmetic + kr_require(pindex >= 0 && pindex <= 16); + if (pindex > 0) { // t isn't the first child -> go down the previous one + ns->stack[ns->len - 1] = twig(p, pindex - 1); + return ns_last_leaf(ns); + } + // we've got to go up again + --ns->len; + } while (true); +} + +/*! + * \brief Advance the node stack to the leaf that is successor to the current node. + * + * \note Prefix leaf or anything else under the current node DOES count. + * \return KNOT_EOK on success, KNOT_ENOENT on not-found, or possibly KNOT_ENOMEM. + */ +static int ns_next_leaf(nstack_t *ns) +{ + kr_require(ns && ns->len > 0); + + node_t *t = ns->stack[ns->len - 1]; + if (isbranch(t)) + return ns_first_leaf(ns); + do { + if (ns->len < 2) + return KNOT_ENOENT; // not found, as no more parent is available + t = ns->stack[ns->len - 1]; + node_t *p = ns->stack[ns->len - 2]; + int pindex = t - p->branch.twigs; // index in parent via pointer arithmetic + kr_require(pindex >= 0 && pindex <= 16); + int pcount = bitmap_weight(p->branch.bitmap); + if (pindex + 1 < pcount) { // t isn't the last child -> go down the next one + ns->stack[ns->len - 1] = twig(p, pindex + 1); + return ns_first_leaf(ns); + } + // we've got to go up again + --ns->len; + } while (true); +} + +int trie_get_leq(trie_t *tbl, const char *key, uint32_t len, trie_val_t **val) +{ + kr_require(tbl && val); + *val = NULL; // so on failure we can just return; + if (tbl->weight == 0) + return KNOT_ENOENT; + { // Intentionally un-indented; until end of function, to bound cleanup attr. + // First find a key with longest-matching prefix + __attribute__((cleanup(ns_cleanup))) + nstack_t ns_local; + ns_init(&ns_local, tbl); + nstack_t *ns = &ns_local; + branch_t bp; + int un_leaf; // first unmatched character in the leaf + ERR_RETURN(ns_find_branch(ns, key, len, &bp, &un_leaf)); + int un_key = bp.index < len ? (unsigned char)key[bp.index] : -256; + node_t *t = ns->stack[ns->len - 1]; + if (bp.flags == 0) { // found exact match + *val = &t->leaf.val; + return KNOT_EOK; + } + // Get t: the last node on matching path + if (isbranch(t) && t->branch.index == bp.index && t->branch.flags == bp.flags) { + // t is OK + } else { + // the top of the stack was the first unmatched node -> step up + if (ns->len == 1) { + // root was unmatched already + if (un_key < un_leaf) + return KNOT_ENOENT; + ERR_RETURN(ns_last_leaf(ns)); + goto success; + } + --ns->len; + t = ns->stack[ns->len - 1]; + } + // Now we re-do the first "non-matching" step in the trie + // but try the previous child if key was less (it may not exist) + bitmap_t b = twigbit(t, key, len); + int i = hastwig(t, b) + ? twigoff(t, b) - (un_key < un_leaf) + : twigoff(t, b) - 1 /*twigoff returns successor when !hastwig*/; + if (i >= 0) { + ERR_RETURN(ns_longer(ns)); + ns->stack[ns->len++] = twig(t, i); + ERR_RETURN(ns_last_leaf(ns)); + } else { + ERR_RETURN(ns_prev_leaf(ns)); + } +success: + kr_require(!isbranch(ns->stack[ns->len - 1])); + *val = &ns->stack[ns->len - 1]->leaf.val; + return 1; + } +} + +/*! \brief Initialize a new leaf, copying the key, and returning failure code. */ +static int mk_leaf(node_t *leaf, const char *key, uint32_t len, knot_mm_t *mm) +{ + tkey_t *k = mm_alloc(mm, sizeof(tkey_t) + len); + #if FLAGS_HACK + kr_require(((uintptr_t)k) % 4 == 0); // we need an aligned pointer + #endif + if (unlikely(!k)) + return KNOT_ENOMEM; + k->len = len; + memcpy(k->chars, key, len); + leaf->leaf = (leaf_t){ + #if !FLAGS_HACK + .flags = 0, + #endif + .val = NULL, + .key = k + }; + return KNOT_EOK; +} + +trie_val_t* trie_get_ins(trie_t *tbl, const char *key, uint32_t len) +{ + if (kr_fails_assert(tbl)) + return NULL; + // First leaf in an empty tbl? + if (unlikely(!tbl->weight)) { + if (unlikely(mk_leaf(&tbl->root, key, len, &tbl->mm))) + return NULL; + ++tbl->weight; + return &tbl->root.leaf.val; + } + { // Intentionally un-indented; until end of function, to bound cleanup attr. + // Find the branching-point + __attribute__((cleanup(ns_cleanup))) + nstack_t ns_local; + ns_init(&ns_local, tbl); + nstack_t *ns = &ns_local; + branch_t bp; // branch-point: index and flags signifying the longest common prefix + int k2; // the first unmatched character in the leaf + if (unlikely(ns_find_branch(ns, key, len, &bp, &k2))) + return NULL; + node_t *t = ns->stack[ns->len - 1]; + if (bp.flags == 0) // the same key was already present + return &t->leaf.val; + node_t leaf; + if (unlikely(mk_leaf(&leaf, key, len, &tbl->mm))) + return NULL; + + if (isbranch(t) && bp.index == t->branch.index && bp.flags == t->branch.flags) { + // The node t needs a new leaf child. + bitmap_t b1 = twigbit(t, key, len); + kr_require(!hastwig(t, b1)); + uint s, m; TWIGOFFMAX(s, m, t, b1); // new child position and original child count + node_t *twigs = mm_realloc(&tbl->mm, t->branch.twigs, + sizeof(node_t) * (m + 1), sizeof(node_t) * m); + if (unlikely(!twigs)) + goto err_leaf; + memmove(twigs + s + 1, twigs + s, sizeof(node_t) * (m - s)); + twigs[s] = leaf; + t->branch.twigs = twigs; + t->branch.bitmap |= b1; + ++tbl->weight; + return &twigs[s].leaf.val; + } else { + // We need to insert a new binary branch with leaf at *t. + // Note: it works the same for the case where we insert above root t. + #ifndef NDEBUG + if (ns->len > 1) { + node_t *pt = ns->stack[ns->len - 2]; + kr_require(hastwig(pt, twigbit(pt, key, len))); + } + #endif + node_t *twigs = mm_alloc(&tbl->mm, sizeof(node_t) * 2); + if (unlikely(!twigs)) + goto err_leaf; + node_t t2 = *t; // Save before overwriting t. + t->branch.flags = bp.flags; + t->branch.index = bp.index; + t->branch.twigs = twigs; + bitmap_t b1 = twigbit(t, key, len); + bitmap_t b2 = unlikely(k2 == -256) ? (1 << 0) : nibbit(k2, bp.flags); + t->branch.bitmap = b1 | b2; + *twig(t, twigoff(t, b1)) = leaf; + *twig(t, twigoff(t, b2)) = t2; + ++tbl->weight; + return &twig(t, twigoff(t, b1))->leaf.val; + }; +err_leaf: + mm_free(&tbl->mm, leaf.leaf.key); + return NULL; + } +} + +/*! \brief Apply a function to every trie_val_t*, in order; a recursive solution. */ +static int apply_trie(node_t *t, int (*f)(trie_val_t *, void *), void *d) +{ + kr_require(t); + if (!isbranch(t)) + return f(&t->leaf.val, d); + int child_count = bitmap_weight(t->branch.bitmap); + for (int i = 0; i < child_count; ++i) + ERR_RETURN(apply_trie(twig(t, i), f, d)); + return KNOT_EOK; +} + +int trie_apply(trie_t *tbl, int (*f)(trie_val_t *, void *), void *d) +{ + kr_require(tbl && f); + if (!tbl->weight) + return KNOT_EOK; + return apply_trie(&tbl->root, f, d); +} + +/*! \brief Apply a function to every key + trie_val_t*, in order; a recursive solution. */ +static int apply_trie_with_key(node_t *t, int (*f)(const char *, uint32_t, trie_val_t *, void *), void *d) +{ + kr_require(t); + if (!isbranch(t)) + return f(t->leaf.key->chars, t->leaf.key->len, &t->leaf.val, d); + int child_count = bitmap_weight(t->branch.bitmap); + for (int i = 0; i < child_count; ++i) + ERR_RETURN(apply_trie_with_key(twig(t, i), f, d)); + return KNOT_EOK; +} + +int trie_apply_with_key(trie_t *tbl, int (*f)(const char *, uint32_t, trie_val_t *, void *), void *d) +{ + kr_require(tbl && f); + if (!tbl->weight) + return KNOT_EOK; + return apply_trie_with_key(&tbl->root, f, d); +} + +/* These are all thin wrappers around static Tns* functions. */ +trie_it_t* trie_it_begin(trie_t *tbl) +{ + if (kr_fails_assert(tbl)) + return NULL; + trie_it_t *it = malloc(sizeof(nstack_t)); + if (!it) + return NULL; + ns_init(it, tbl); + if (it->len == 0) // empty tbl + return it; + if (ns_first_leaf(it)) { + ns_cleanup(it); + free(it); + return NULL; + } + return it; +} + +void trie_it_next(trie_it_t *it) +{ + kr_require(it && it->len); + if (ns_next_leaf(it) != KNOT_EOK) + it->len = 0; +} + +bool trie_it_finished(trie_it_t *it) +{ + kr_require(it); + return it->len == 0; +} + +void trie_it_free(trie_it_t *it) +{ + if (!it) + return; + ns_cleanup(it); + free(it); +} + +const char* trie_it_key(trie_it_t *it, size_t *len) +{ + kr_require(it && it->len); + node_t *t = it->stack[it->len - 1]; + kr_require(!isbranch(t)); + tkey_t *key = t->leaf.key; + if (len) + *len = key->len; + return key->chars; +} + +trie_val_t* trie_it_val(trie_it_t *it) +{ + kr_require(it && it->len); + node_t *t = it->stack[it->len - 1]; + kr_require(!isbranch(t)); + return &t->leaf.val; +} diff --git a/lib/generic/trie.h b/lib/generic/trie.h new file mode 100644 index 0000000..a5f0347 --- /dev/null +++ b/lib/generic/trie.h @@ -0,0 +1,150 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <stdbool.h> +#include <stdint.h> + +#include <libknot/mm_ctx.h> +#include "lib/defines.h" + +/*! + * \brief Native API of QP-tries: + * + * - keys are char strings, not necessarily zero-terminated, + * the structure copies the contents of the passed keys + * - values are void* pointers, typically you get an ephemeral pointer to it + * - key lengths are limited by 2^32-1 ATM + * + * XXX EDITORS: trie.{h,c} are synced from + * https://gitlab.nic.cz/knot/knot-dns/tree/68352fc969/src/contrib/qp-trie + * only with simple adjustments, mostly include lines, KR_EXPORT and assertions. + */ + +/*! \brief Element value. */ +typedef void* trie_val_t; + +/*! \brief Opaque structure holding a QP-trie. */ +typedef struct trie trie_t; + +/*! \brief Opaque type for holding a QP-trie iterator. */ +typedef struct trie_it trie_it_t; + +/*! \brief Create a trie instance. Pass NULL to use malloc+free. */ +KR_EXPORT +trie_t* trie_create(knot_mm_t *mm); + +/*! \brief Free a trie instance. */ +KR_EXPORT +void trie_free(trie_t *tbl); + +/*! \brief Clear a trie instance (make it empty). */ +KR_EXPORT +void trie_clear(trie_t *tbl); + +/*! \brief Return the number of keys in the trie. */ +KR_EXPORT +size_t trie_weight(const trie_t *tbl); + +/*! \brief Search the trie, returning NULL on failure. */ +KR_EXPORT +trie_val_t* trie_get_try(trie_t *tbl, const char *key, uint32_t len); + +/*! + * \brief Return pointer to the minimum. Optionally with key and its length. */ +KR_EXPORT +trie_val_t* trie_get_first(trie_t *tbl, char **key, uint32_t *len); + +/*! \brief Search the trie, inserting NULL trie_val_t on failure. */ +KR_EXPORT +trie_val_t* trie_get_ins(trie_t *tbl, const char *key, uint32_t len); + +/*! + * \brief Search for less-or-equal element. + * + * \param tbl Trie. + * \param key Searched key. + * \param len Key length. + * \param val Must be valid; it will be set to NULL if not found or errored. + * \return KNOT_EOK for exact match, 1 for previous, KNOT_ENOENT for not-found, + * or KNOT_E*. + */ +KR_EXPORT +int trie_get_leq(trie_t *tbl, const char *key, uint32_t len, trie_val_t **val); + +/*! + * \brief Apply a function to every trie_val_t, in order. + * + * \param d Parameter passed as the second argument to f(). + * \return First nonzero from f() or zero (i.e. KNOT_EOK). + */ +KR_EXPORT +int trie_apply(trie_t *tbl, int (*f)(trie_val_t *, void *), void *d); + +/*! + * \brief Apply a function to every trie_val_t, in order. + * + * It's like trie_apply() but additionally passes keys and their lengths. + * + * \param d Parameter passed as the second argument to f(). + * \return First nonzero from f() or zero (i.e. KNOT_EOK). + */ +KR_EXPORT +int trie_apply_with_key(trie_t *tbl, int (*f)(const char *, uint32_t, trie_val_t *, void *), void *d); + +/*! + * \brief Remove an item, returning KNOT_EOK if succeeded or KNOT_ENOENT if not found. + * + * If val!=NULL and deletion succeeded, the deleted value is set. + */ +KR_EXPORT +int trie_del(trie_t *tbl, const char *key, uint32_t len, trie_val_t *val); + +/*! + * \brief Remove the first item, returning KNOT_EOK on success. + * + * You may optionally get the key and/or value. + * The key is copied, so you need to pass sufficient len, + * otherwise kr_error(ENOSPC) is returned. + */ +KR_EXPORT +int trie_del_first(trie_t *tbl, char *key, uint32_t *len, trie_val_t *val); + +/*! \brief Create a new iterator pointing to the first element (if any). */ +KR_EXPORT +trie_it_t* trie_it_begin(trie_t *tbl); + +/*! + * \brief Advance the iterator to the next element. + * + * Iteration is in ascending lexicographical order. + * In particular, the empty string would be considered as the very first. + * + * \note You may not use this function if the trie's key-set has been modified + * during the lifetime of the iterator (modifying values only is OK). + */ +KR_EXPORT +void trie_it_next(trie_it_t *it); + +/*! \brief Test if the iterator has gone past the last element. */ +KR_EXPORT +bool trie_it_finished(trie_it_t *it); + +/*! \brief Free any resources of the iterator. It's OK to call it on NULL. */ +KR_EXPORT +void trie_it_free(trie_it_t *it); + +/*! + * \brief Return pointer to the key of the current element. + * + * \note The optional len is uint32_t internally but size_t is better for our usage, + * as it is without an additional type conversion. + */ +KR_EXPORT +const char* trie_it_key(trie_it_t *it, size_t *len); + +/*! \brief Return pointer to the value of the current element (writable). */ +KR_EXPORT +trie_val_t* trie_it_val(trie_it_t *it); diff --git a/lib/generic/trie.spdx b/lib/generic/trie.spdx new file mode 100644 index 0000000..e8d52f2 --- /dev/null +++ b/lib/generic/trie.spdx @@ -0,0 +1,10 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +SPDXID: SPDXRef-DOCUMENT +DocumentName: knotdns-trie +DocumentNamespace: http://spdx.org/spdxdocs/spdx-v2.1-f99c0e11-6afb-46ce-af96-0955a83957bb + +PackageName: knotdns-trie +PackageDownloadLocation: git+https://gitlab.nic.cz/knot/knot-dns.git@68352fc969bc04aa4aa8203e113ce747d887f410#src/contrib/qp-trie/trie.c +PackageOriginator: Organization: Knot DNS contributors +PackageLicenseDeclared: GPL-3.0-or-later diff --git a/lib/layer.h b/lib/layer.h new file mode 100644 index 0000000..7721560 --- /dev/null +++ b/lib/layer.h @@ -0,0 +1,107 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "kresconfig.h" +#include "lib/defines.h" +#include "lib/utils.h" + +/** Layer processing states. Only one value at a time (but see TODO). + * + * Each state represents the state machine transition, + * and determines readiness for the next action. + * See struct kr_layer_api for the actions. + * + * TODO: the cookie module sometimes sets (_FAIL | _DONE) on purpose (!) + */ +enum kr_layer_state { + KR_STATE_CONSUME = 1 << 0, /*!< Consume data. */ + KR_STATE_PRODUCE = 1 << 1, /*!< Produce data. */ + + /*! Finished successfully or a special case: in CONSUME phase this can + * be used (by iterator) to do a transition to PRODUCE phase again, + * in which case the packet wasn't accepted for some reason. */ + KR_STATE_DONE = 1 << 2, + + KR_STATE_FAIL = 1 << 3, /*!< Error. */ + KR_STATE_YIELD = 1 << 4, /*!< Paused, waiting for a sub-query. */ +}; + +/** Check that a kr_layer_state makes sense. We're not very strict ATM. */ +static inline bool kr_state_consistent(enum kr_layer_state s) +{ +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare" +#pragma clang diagnostic ignored "-Wtautological-unsigned-enum-zero-compare" +#endif + return s >= 0 && s < (1 << 5); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +} + +/* Forward declarations. */ +struct kr_layer_api; + +/** Packet processing context. */ +typedef struct kr_layer { + int state; /*!< The current state; bitmap of enum kr_layer_state. */ + struct kr_request *req; /*!< The corresponding request. */ + const struct kr_layer_api *api; + knot_pkt_t *pkt; /*!< In glue for lua kr_layer_api it's used to pass the parameter. */ + struct sockaddr *dst; /*!< In glue for checkout layer it's used to pass the parameter. */ + bool is_stream; /*!< In glue for checkout layer it's used to pass the parameter. */ +} kr_layer_t; + +/** Packet processing module API. All functions return the new kr_layer_state. + * + * Lua modules are allowed to return nil/nothing, meaning the state shall not change. + */ +struct kr_layer_api { + /** Start of processing the DNS request. */ + int (*begin)(kr_layer_t *ctx); + + int (*reset)(kr_layer_t *ctx); + + /** Paired to begin, called both on successes and failures. */ + int (*finish)(kr_layer_t *ctx); + + /** Process an answer from upstream or from cache. + * Lua API: call is omitted iff (state & KR_STATE_FAIL). */ + int (*consume)(kr_layer_t *ctx, knot_pkt_t *pkt); + + /** Produce either an answer to the request or a query for upstream (or fail). + * Lua API: call is omitted iff (state & KR_STATE_FAIL). */ + int (*produce)(kr_layer_t *ctx, knot_pkt_t *pkt); + + /** Finalises the outbound query packet with the knowledge of the IP addresses. + * The checkout layer doesn't persist the state, so canceled subrequests + * don't affect the resolution or rest of the processing. + * Lua API: call is omitted iff (state & KR_STATE_FAIL). */ + int (*checkout)(kr_layer_t *ctx, knot_pkt_t *packet, struct sockaddr *dst, int type); + + /** Finalises the answer. + * Last chance to affect what will get into the answer, including EDNS. + * Not called if the packet is being dropped. */ + int (*answer_finalize)(kr_layer_t *ctx); + + /** The C module can store anything in here. */ + void *data; + + /** Internal to ./daemon/ffimodule.c. */ + int cb_slots[]; +}; + +typedef struct kr_layer_api kr_layer_api_t; + +/** Pickled layer state (api, input, state). */ +struct kr_layer_pickle { + struct kr_layer_pickle *next; + const struct kr_layer_api *api; + knot_pkt_t *pkt; + unsigned state; +}; + diff --git a/lib/layer/cache.c b/lib/layer/cache.c new file mode 100644 index 0000000..2f1ba60 --- /dev/null +++ b/lib/layer/cache.c @@ -0,0 +1,20 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "lib/module.h" +#include "lib/cache/api.h" + +/** Module implementation. */ +int cache_init(struct kr_module *self) +{ + static const kr_layer_api_t layer = { + .produce = &cache_peek, + .consume = &cache_stash, + }; + self->layer = &layer; + return kr_ok(); +} + +KR_MODULE_EXPORT(cache) /* useless for builtin module, but let's be consistent */ + diff --git a/lib/layer/iterate.c b/lib/layer/iterate.c new file mode 100644 index 0000000..edc666e --- /dev/null +++ b/lib/layer/iterate.c @@ -0,0 +1,1235 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/** @file iterate.c + * + * This builtin module is mainly active in the consume phase. + * Primary responsibilities: + * - Classify the packet as auth/nonauth and change its AA flag accordingly. + * - Pick interesting RRs to kr_request::answ_selected and ::auth_selected, + * NEW: and classify their rank, except for validation status. + * - Update kr_query::zone_cut (in case of referral). + * - Interpret CNAMEs. + * - Prepare the followup query - either inline or as another kr_query + * (CNAME jumps create a new "sibling" query). + */ + +#include <sys/time.h> +#include <arpa/inet.h> + +#include <contrib/cleanup.h> +#include <libknot/descriptor.h> +#include <libknot/rrtype/rdname.h> +#include <libknot/rrtype/rrsig.h> + +#include "lib/layer/iterate.h" +#include "lib/resolve.h" +#include "lib/rplan.h" +#include "lib/defines.h" +#include "lib/selection.h" +#include "lib/module.h" +#include "lib/dnssec/ta.h" + +#define VERBOSE_MSG(...) kr_log_q(req->current_query, ITERATOR, __VA_ARGS__) +#define QVERBOSE_MSG(qry, ...) kr_log_q(qry, ITERATOR, __VA_ARGS__) +#define WITH_VERBOSE(qry) if (kr_log_is_debug_qry(ITERATOR, (qry))) + +/* Iterator often walks through packet section, this is an abstraction. */ +typedef int (*rr_callback_t)(const knot_rrset_t *, unsigned, struct kr_request *); + +/** Return minimized QNAME/QTYPE for current zone cut. */ +static const knot_dname_t *minimized_qname(struct kr_query *query, uint16_t *qtype) +{ + /* Minimization disabled. */ + const knot_dname_t *qname = query->sname; + if (qname[0] == '\0' || query->flags.NO_MINIMIZE || query->flags.STUB) { + return qname; + } + + /* Minimize name to contain current zone cut + 1 label. */ + int cut_labels = knot_dname_labels(query->zone_cut.name, NULL); + int qname_labels = knot_dname_labels(qname, NULL); + while(qname[0] && qname_labels > cut_labels + 1) { + qname = knot_wire_next_label(qname, NULL); + qname_labels -= 1; + } + + /* Hide QTYPE if minimized. */ + if (qname != query->sname) { + *qtype = KNOT_RRTYPE_NS; + } + + return qname; +} + +/** Answer is paired to query. */ +static bool is_paired_to_query(const knot_pkt_t *answer, struct kr_query *query) +{ + uint16_t qtype = query->stype; + const knot_dname_t *qname = minimized_qname(query, &qtype); + + /* ID should already match, thanks to session_tasklist_del_msgid() + * in worker_submit(), but it won't hurt to check again. */ + return query->id == knot_wire_get_id(answer->wire) && + knot_wire_get_qdcount(answer->wire) == 1 && + query->sclass == knot_pkt_qclass(answer) && + qtype == knot_pkt_qtype(answer) && + /* qry->secret had been xor-applied to answer already, + * so this also checks for correctness of case randomization */ + knot_dname_is_equal(qname, kr_pkt_qname_raw(answer)); +} + +/** Relaxed rule for AA, either AA=1 or SOA matching zone cut is required. */ +static bool is_authoritative(const knot_pkt_t *answer, struct kr_query *query) +{ + if (knot_wire_get_aa(answer->wire)) { + return true; + } + + const knot_pktsection_t *ns = knot_pkt_section(answer, KNOT_AUTHORITY); + for (unsigned i = 0; i < ns->count; ++i) { + const knot_rrset_t *rr = knot_pkt_rr(ns, i); + if (rr->type == KNOT_RRTYPE_SOA + && knot_dname_in_bailiwick(rr->owner, query->zone_cut.name) >= 0) { + return true; + } + } + +#ifndef STRICT_MODE + /* Last resort to work around broken auths, if the zone cut is at the QNAME. */ + if (knot_dname_is_equal(query->zone_cut.name, knot_pkt_qname(answer))) { + return true; + } +#endif + + /* Some authoritative servers are hopelessly broken, allow lame answers in permissive mode. */ + if (query->flags.PERMISSIVE) { + return true; + } + + return false; +} + +int kr_response_classify(const knot_pkt_t *pkt) +{ + const knot_pktsection_t *an = knot_pkt_section(pkt, KNOT_ANSWER); + switch (knot_wire_get_rcode(pkt->wire)) { + case KNOT_RCODE_NOERROR: + return (an->count == 0) ? PKT_NODATA : PKT_NOERROR; + case KNOT_RCODE_NXDOMAIN: + return PKT_NXDOMAIN; + case KNOT_RCODE_REFUSED: + return PKT_REFUSED; + default: + return PKT_ERROR; + } +} + +/** @internal Filter ANY or loopback addresses. */ +static bool is_valid_addr(const uint8_t *addr, size_t len) +{ + if (len == sizeof(struct in_addr)) { + /* Filter ANY and 127.0.0.0/8 */ + uint32_t ip_host; /* Memcpy is safe for unaligned case (on non-x86) */ + memcpy(&ip_host, addr, sizeof(ip_host)); + ip_host = ntohl(ip_host); + if (ip_host == 0 || (ip_host & 0xff000000) == 0x7f000000) { + return false; + } + } else if (len == sizeof(struct in6_addr)) { + struct in6_addr ip6_mask; + memset(&ip6_mask, 0, sizeof(ip6_mask)); + /* All except last byte are zeroed, last byte defines ANY/::1 */ + if (memcmp(addr, ip6_mask.s6_addr, sizeof(ip6_mask.s6_addr) - 1) == 0) { + return (addr[len - 1] > 1); + } + } + return true; +} + +/** @internal Update NS address from record \a rr. Return _FAIL on error. */ +static int update_nsaddr(const knot_rrset_t *rr, struct kr_query *query, int *glue_cnt) +{ + if (rr->type == KNOT_RRTYPE_A || rr->type == KNOT_RRTYPE_AAAA) { + const knot_rdata_t *rdata = rr->rrs.rdata; + const int a_len = rr->type == KNOT_RRTYPE_A + ? sizeof(struct in_addr) : sizeof(struct in6_addr); + if (a_len != rdata->len) { + QVERBOSE_MSG(query, "<= ignoring invalid glue, length %d != %d\n", + (int)rdata->len, a_len); + return KR_STATE_FAIL; + } + char name_str[KR_DNAME_STR_MAXLEN]; + char addr_str[INET6_ADDRSTRLEN]; + WITH_VERBOSE(query) { + const int af = (rr->type == KNOT_RRTYPE_A) ? AF_INET : AF_INET6; + knot_dname_to_str(name_str, rr->owner, sizeof(name_str)); + name_str[sizeof(name_str) - 1] = 0; + inet_ntop(af, rdata->data, addr_str, sizeof(addr_str)); + } + if (!(query->flags.ALLOW_LOCAL) && + !is_valid_addr(rdata->data, rdata->len)) { + QVERBOSE_MSG(query, "<= ignoring invalid glue for " + "'%s': '%s'\n", name_str, addr_str); + return KR_STATE_CONSUME; /* Ignore invalid addresses */ + } + int ret = kr_zonecut_add(&query->zone_cut, rr->owner, rdata->data, rdata->len); + if (ret != 0) { + return KR_STATE_FAIL; + } + + ++*glue_cnt; /* reduced verbosity */ + /* QVERBOSE_MSG(query, "<= using glue for " + "'%s': '%s'\n", name_str, addr_str); + */ + } + return KR_STATE_CONSUME; +} + +enum { GLUE_COUNT_THROTTLE = 26 }; + +/** @internal From \a pkt, fetch glue records for name \a ns, and update the cut etc. + * + * \param glue_cnt the number of accepted addresses (to be incremented) + */ +static void fetch_glue(knot_pkt_t *pkt, const knot_dname_t *ns, bool in_bailiwick, + struct kr_request *req, const struct kr_query *qry, int *glue_cnt) +{ + ranked_rr_array_t *selected[] = kr_request_selected(req); + for (knot_section_t i = KNOT_ANSWER; i <= KNOT_ADDITIONAL; ++i) { + const knot_pktsection_t *sec = knot_pkt_section(pkt, i); + for (unsigned k = 0; k < sec->count; ++k) { + const knot_rrset_t *rr = knot_pkt_rr(sec, k); + if (!knot_dname_is_equal(ns, rr->owner)) { + continue; + } + if ((rr->type != KNOT_RRTYPE_A) && + (rr->type != KNOT_RRTYPE_AAAA)) { + continue; + } + + uint8_t rank = (in_bailiwick && i == KNOT_ANSWER) + ? (KR_RANK_INITIAL | KR_RANK_AUTH) : KR_RANK_OMIT; + (void) kr_ranked_rrarray_add(selected[i], rr, rank, + false, qry->uid, &req->pool); + + if ((rr->type == KNOT_RRTYPE_A) && + (req->ctx->options.NO_IPV4)) { + QVERBOSE_MSG(qry, "<= skipping IPv4 glue due to network settings\n"); + continue; + } + if ((rr->type == KNOT_RRTYPE_AAAA) && + (req->ctx->options.NO_IPV6)) { + QVERBOSE_MSG(qry, "<= skipping IPv6 glue due to network settings\n"); + continue; + } + (void) update_nsaddr(rr, req->current_query, glue_cnt); + /* If we reach limit on total glue addresses, + * we only load the first one per NS name (the one just above). */ + if (*glue_cnt > GLUE_COUNT_THROTTLE) + break; + } + } +} + +/** Attempt to find glue for given nameserver name (best effort). */ +static bool has_glue(knot_pkt_t *pkt, const knot_dname_t *ns) +{ + for (knot_section_t i = KNOT_ANSWER; i <= KNOT_ADDITIONAL; ++i) { + const knot_pktsection_t *sec = knot_pkt_section(pkt, i); + for (unsigned k = 0; k < sec->count; ++k) { + const knot_rrset_t *rr = knot_pkt_rr(sec, k); + if (knot_dname_is_equal(ns, rr->owner) && + (rr->type == KNOT_RRTYPE_A || rr->type == KNOT_RRTYPE_AAAA)) { + return true; + } + } + } + return false; +} + +/** @internal Update the cut with another NS(+glue) record. + * @param current_cut is cut name before this packet. + * @return _DONE if cut->name changes, _FAIL on error, and _CONSUME otherwise. */ +static int update_cut(knot_pkt_t *pkt, const knot_rrset_t *rr, + struct kr_request *req, const knot_dname_t *current_cut, + int *glue_cnt) +{ + struct kr_query *qry = req->current_query; + struct kr_zonecut *cut = &qry->zone_cut; + int state = KR_STATE_CONSUME; + + /* New authority MUST be at/below the authority of the current cut; + * also qname must be below new authority; + * otherwise it's a possible cache injection attempt. */ + const bool ok = knot_dname_in_bailiwick(rr->owner, current_cut) >= 0 + && knot_dname_in_bailiwick(qry->sname, rr->owner) >= 0; + if (!ok) { + VERBOSE_MSG("<= authority: ns outside bailiwick\n"); + qry->server_selection.error(qry, req->upstream.transport, KR_SELECTION_LAME_DELEGATION); +#ifdef STRICT_MODE + return KR_STATE_FAIL; +#else + /* Workaround: ignore out-of-bailiwick NSs for authoritative answers, + * but fail for referrals. This is important to detect lame answers. */ + if (knot_pkt_section(pkt, KNOT_ANSWER)->count == 0) { + state = KR_STATE_FAIL; + } + return state; +#endif + } + + /* Update zone cut name */ + if (!knot_dname_is_equal(rr->owner, cut->name)) { + /* Remember parent cut and descend to new (keep keys and TA). */ + struct kr_zonecut *parent = mm_alloc(&req->pool, sizeof(*parent)); + if (parent) { + memcpy(parent, cut, sizeof(*parent)); + kr_zonecut_init(cut, rr->owner, &req->pool); + cut->key = parent->key; + cut->trust_anchor = parent->trust_anchor; + cut->parent = parent; + } else { + kr_zonecut_set(cut, rr->owner); + } + state = KR_STATE_DONE; + } + + /* Fetch glue for each NS */ + knot_rdata_t *rdata_i = rr->rrs.rdata; + for (unsigned i = 0; i < rr->rrs.count; + ++i, rdata_i = knot_rdataset_next(rdata_i)) { + const knot_dname_t *ns_name = knot_ns_name(rdata_i); + /* Glue is mandatory for NS below zone */ + if (knot_dname_in_bailiwick(ns_name, rr->owner) >= 0 + && !has_glue(pkt, ns_name)) { + const char *msg = + "<= authority: missing mandatory glue, skipping NS"; + WITH_VERBOSE(qry) { + auto_free char *ns_str = kr_dname_text(ns_name); + VERBOSE_MSG("%s %s\n", msg, ns_str); + } + continue; + } + int ret = kr_zonecut_add(cut, ns_name, NULL, 0); + kr_assert(!ret); + + /* Choose when to use glue records. */ + const bool in_bailiwick = + knot_dname_in_bailiwick(ns_name, current_cut) >= 0; + bool do_fetch; + if (qry->flags.PERMISSIVE) { + do_fetch = true; + } else if (qry->flags.STRICT) { + /* Strict mode uses only mandatory glue. */ + do_fetch = knot_dname_in_bailiwick(ns_name, cut->name) >= 0; + } else { + /* Normal mode uses in-bailiwick glue. */ + do_fetch = in_bailiwick; + } + if (do_fetch) { + fetch_glue(pkt, ns_name, in_bailiwick, req, qry, glue_cnt); + } + } + + return state; +} + +/** Compute rank appropriate for RRs present in the packet. + * @param answer whether the RR is from answer or authority section + * @param is_nonauth: from referral or forwarding (etc.) */ +static uint8_t get_initial_rank(const knot_rrset_t *rr, const struct kr_query *qry, + const bool answer, const bool is_nonauth) +{ + /* For RRSIGs, ensure the KR_RANK_AUTH flag corresponds to the signed RR. */ + uint16_t type = kr_rrset_type_maysig(rr); + + if (qry->flags.CACHED) { + return rr->additional ? *(uint8_t *)rr->additional : KR_RANK_OMIT; + /* ^^ Current use case for "cached" RRs without rank: hints module. */ + } + if (answer || type == KNOT_RRTYPE_DS + || type == KNOT_RRTYPE_SOA /* needed for aggressive negative caching */ + || type == KNOT_RRTYPE_NSEC || type == KNOT_RRTYPE_NSEC3) { + /* We almost always want these validated, and it should be possible. */ + return KR_RANK_INITIAL | KR_RANK_AUTH; + } + /* Be aggressive: try to validate anything else (almost never extra latency). */ + return KR_RANK_TRY; + /* TODO: this classifier of authoritativity may not be perfect yet. */ +} + +static int pick_authority(knot_pkt_t *pkt, struct kr_request *req, bool to_wire) +{ + struct kr_query *qry = req->current_query; + const knot_pktsection_t *ns = knot_pkt_section(pkt, KNOT_AUTHORITY); + + const knot_dname_t *zonecut_name = qry->zone_cut.name; + bool referral = !knot_wire_get_aa(pkt->wire); + if (referral) { + /* zone cut already updated by process_authority() + * use parent zonecut name */ + zonecut_name = qry->zone_cut.parent ? qry->zone_cut.parent->name : qry->zone_cut.name; + to_wire = false; + } + + for (unsigned i = 0; i < ns->count; ++i) { + const knot_rrset_t *rr = knot_pkt_rr(ns, i); + if (rr->rclass != KNOT_CLASS_IN + || knot_dname_in_bailiwick(rr->owner, zonecut_name) < 0) { + continue; + } + uint8_t rank = get_initial_rank(rr, qry, false, + qry->flags.FORWARD || referral); + int ret = kr_ranked_rrarray_add(&req->auth_selected, rr, + rank, to_wire, qry->uid, &req->pool); + if (ret < 0) { + return ret; + } + } + + return kr_ok(); +} + +static int process_authority(knot_pkt_t *pkt, struct kr_request *req) +{ + struct kr_query *qry = req->current_query; + if (kr_fails_assert(!qry->flags.STUB)) + return KR_STATE_FAIL; + + int result = KR_STATE_CONSUME; + if (qry->flags.FORWARD) { + return result; + } + + const knot_pktsection_t *ns = knot_pkt_section(pkt, KNOT_AUTHORITY); + const knot_pktsection_t *an = knot_pkt_section(pkt, KNOT_ANSWER); + +#ifdef STRICT_MODE + /* AA, terminate resolution chain. */ + if (knot_wire_get_aa(pkt->wire)) { + return KR_STATE_CONSUME; + } +#else + /* Work around servers sending back CNAME with different delegation and no AA. */ + if (an->count > 0 && ns->count > 0) { + const knot_rrset_t *rr = knot_pkt_rr(an, 0); + if (rr->type == KNOT_RRTYPE_CNAME) { + return KR_STATE_CONSUME; + } + /* Work around for these NSs which are authoritative both for + * parent and child and mixes data from both zones in single answer */ + if (knot_wire_get_aa(pkt->wire) && + (rr->type == qry->stype) && + (knot_dname_is_equal(rr->owner, qry->sname))) { + return KR_STATE_CONSUME; + } + } +#endif + /* Remember current bailiwick for NS processing. */ + const knot_dname_t *current_zone_cut = qry->zone_cut.name; + bool ns_record_exists = false; + int glue_cnt = 0; + int ns_count = 0; + /* Update zone cut information. */ + for (unsigned i = 0; i < ns->count; ++i) { + const knot_rrset_t *rr = knot_pkt_rr(ns, i); + if (rr->type == KNOT_RRTYPE_NS) { + ns_record_exists = true; + int state = update_cut(pkt, rr, req, current_zone_cut, &glue_cnt); + switch(state) { + case KR_STATE_DONE: result = state; break; + case KR_STATE_FAIL: return state; break; + default: /* continue */ break; + } + + if (++ns_count >= 13) { + VERBOSE_MSG("<= authority: many glue NSs, skipping the rest\n"); + break; + } + } else if (rr->type == KNOT_RRTYPE_SOA + && knot_dname_in_bailiwick(rr->owner, qry->zone_cut.name) > 0) { + /* SOA below cut in authority indicates different authority, + * but same NS set. */ + qry->zone_cut.name = knot_dname_copy(rr->owner, &req->pool); + } + } + + /* Nameserver is authoritative for both parent side and the child side of the + * delegation may respond with an NS record in the answer section, and still update + * the zone cut (this e.g. happens on the `nrl.navy.mil.` zone cut). + * By updating the zone cut, we can continue with QNAME minimization, + * as the current code is only able to minimize one label below a zone cut. */ + if (!ns_record_exists && knot_wire_get_aa(pkt->wire)) { + for (unsigned i = 0; i < an->count; ++i) { + const knot_rrset_t *rr = knot_pkt_rr(an, i); + if (rr->type == KNOT_RRTYPE_NS + && knot_dname_in_bailiwick(rr->owner, qry->zone_cut.name) > 0 + /* "confusing" NS records can happen e.g. on a CNAME chain */ + && knot_dname_in_bailiwick(qry->sname, rr->owner) >= 0) { + /* NS below cut in authority indicates different authority, + * but same NS set. */ + qry->zone_cut.name = knot_dname_copy(rr->owner, &req->pool); + } + } + } + + if (glue_cnt) { + VERBOSE_MSG("<= loaded %d glue addresses\n", glue_cnt); + } + if (glue_cnt > GLUE_COUNT_THROTTLE) { + VERBOSE_MSG("<= (some may have been omitted due to being too many)\n"); + } + + + if ((qry->flags.DNSSEC_WANT) && (result == KR_STATE_CONSUME)) { + if (knot_wire_get_aa(pkt->wire) == 0 && + knot_wire_get_ancount(pkt->wire) == 0 && + ns_record_exists) { + /* Unhelpful referral + Prevent from validating as an authoritative answer */ + result = KR_STATE_DONE; + } + } + + /* CONSUME => Unhelpful referral. + * DONE => Zone cut updated. */ + return result; +} + +static int finalize_answer(knot_pkt_t *pkt, struct kr_request *req) +{ + /* Finalize header */ + knot_pkt_t *answer = kr_request_ensure_answer(req); + if (answer) { + knot_wire_set_rcode(answer->wire, knot_wire_get_rcode(pkt->wire)); + req->state = KR_STATE_DONE; + } + return req->state; +} + +static int unroll_cname(knot_pkt_t *pkt, struct kr_request *req, bool referral, const knot_dname_t **cname_ret) +{ + struct kr_query *query = req->current_query; + if (kr_fails_assert(!query->flags.STUB)) + return KR_STATE_FAIL; + /* Process answer type */ + const knot_pktsection_t *an = knot_pkt_section(pkt, KNOT_ANSWER); + const knot_dname_t *cname = NULL; + const knot_dname_t *pending_cname = query->sname; + bool is_final = (query->parent == NULL); + bool strict_mode = (query->flags.STRICT); + + query->cname_depth = query->cname_parent ? query->cname_parent->cname_depth : 1; + + do { + /* CNAME was found at previous iteration, but records may not follow the correct order. + * Try to find records for pending_cname owner from section start. */ + cname = pending_cname; + size_t cname_answ_selected_i = -1; + bool cname_is_occluded = false; /* whether `cname` is in a DNAME's bailiwick */ + pending_cname = NULL; + const int cname_labels = knot_dname_labels(cname, NULL); + for (unsigned i = 0; i < an->count; ++i) { + const knot_rrset_t *rr = knot_pkt_rr(an, i); + + /* Skip the RR if its owner+type doesn't interest us. */ + const uint16_t type = kr_rrset_type_maysig(rr); + const bool type_OK = rr->type == query->stype || type == query->stype + || type == KNOT_RRTYPE_CNAME; + if (rr->rclass != KNOT_CLASS_IN + || knot_dname_in_bailiwick(rr->owner, query->zone_cut.name) < 0) { + continue; + } + const bool all_OK = type_OK && knot_dname_is_equal(rr->owner, cname); + + const bool to_wire = is_final && !referral; + + if (!all_OK && type == KNOT_RRTYPE_DNAME + && knot_dname_in_bailiwick(cname, rr->owner) >= 1) { + /* This DNAME (or RRSIGs) cover the current target (`cname`), + * so it is interesting and will occlude its CNAME. + * We rely on CNAME being sent along with DNAME + * (mandatory unless YXDOMAIN). */ + cname_is_occluded = true; + uint8_t rank = get_initial_rank(rr, query, true, + query->flags.FORWARD || referral); + int ret = kr_ranked_rrarray_add(&req->answ_selected, rr, + rank, to_wire, query->uid, &req->pool); + if (ret < 0) { + return KR_STATE_FAIL; + } + } + if (!all_OK) { + continue; + } + + if (rr->type == KNOT_RRTYPE_RRSIG) { + int rrsig_labels = knot_rrsig_labels(rr->rrs.rdata); + if (rrsig_labels > cname_labels) { + /* clearly wrong RRSIG, don't pick it. + * don't fail immediately, + * let validator work. */ + continue; + } + if (rrsig_labels < cname_labels) { + query->flags.DNSSEC_WEXPAND = true; + } + } + + /* Process records matching current SNAME */ + if (!is_final) { + int cnt_ = 0; + int state = update_nsaddr(rr, query->parent, &cnt_); + if (state & KR_STATE_FAIL) { + return state; + } + } + uint8_t rank = get_initial_rank(rr, query, true, + query->flags.FORWARD || referral); + int ret = kr_ranked_rrarray_add(&req->answ_selected, rr, + rank, to_wire, query->uid, &req->pool); + if (ret < 0) { + return KR_STATE_FAIL; + } + cname_answ_selected_i = ret; + + /* Select the next CNAME target, but don't jump immediately. + * There can be records for "old" cname (RRSIGs are interesting); + * more importantly there might be a DNAME for `cname_is_occluded`. */ + if (query->stype != KNOT_RRTYPE_CNAME && rr->type == KNOT_RRTYPE_CNAME) { + pending_cname = knot_cname_name(rr->rrs.rdata); + if (!pending_cname) { + break; + } + } + } + if (!pending_cname) { + break; + } + if (cname_is_occluded) { + req->answ_selected.at[cname_answ_selected_i]->dont_cache = true; + } + if (++(query->cname_depth) > KR_CNAME_CHAIN_LIMIT) { + VERBOSE_MSG("<= error: CNAME chain exceeded max length %d\n", + /* people count objects from 0, no CNAME = 0 */ + (int)KR_CNAME_CHAIN_LIMIT - 1); + return KR_STATE_FAIL; + } + + if (knot_dname_is_equal(cname, pending_cname)) { + VERBOSE_MSG("<= error: CNAME chain loop detected\n"); + return KR_STATE_FAIL; + } + /* In strict mode, explicitly fetch each CNAME target. */ + if (strict_mode) { + cname = pending_cname; + break; + } + /* Information outside bailiwick is not trusted. */ + if (knot_dname_in_bailiwick(pending_cname, query->zone_cut.name) < 0) { + cname = pending_cname; + break; + } + /* The validator still can't handle multiple zones in one answer, + * so we only follow if a single label is replaced. + * Forwarding appears to be even more sensitive to this. + * TODO: iteration can probably handle the remaining cases, + * but overall it would be better to have a smarter validator + * (and thus save roundtrips).*/ + const int pending_labels = knot_dname_labels(pending_cname, NULL); + if (pending_labels != cname_labels) { + cname = pending_cname; + break; + } + if (knot_dname_matched_labels(pending_cname, cname) != cname_labels - 1 + || query->flags.FORWARD) { + cname = pending_cname; + break; + } + } while (true); + *cname_ret = cname; + return kr_ok(); +} + +static int process_referral_answer(knot_pkt_t *pkt, struct kr_request *req) +{ + const knot_dname_t *cname = NULL; + int state = unroll_cname(pkt, req, true, &cname); + struct kr_query *query = req->current_query; + if (state != kr_ok()) { + query->server_selection.error(query, req->upstream.transport, KR_SELECTION_BAD_CNAME); + return KR_STATE_FAIL; + } + if (!(query->flags.CACHED)) { + /* If not cached (i.e. got from upstream) + * make sure that this is not an authoritative answer + * (even with AA=1) for other layers. + * There can be answers with AA=1, + * empty answer section and NS in authority. + * Clearing of AA prevents them from + * caching in the packet cache. + * If packet already cached, don't touch him. */ + knot_wire_clear_aa(pkt->wire); + } + state = pick_authority(pkt, req, false); + return state == kr_ok() ? KR_STATE_DONE : KR_STATE_FAIL; +} + +static int process_final(knot_pkt_t *pkt, struct kr_request *req, + const knot_dname_t *cname) +{ + const int pkt_class = kr_response_classify(pkt); + struct kr_query *query = req->current_query; + ranked_rr_array_t *array = &req->answ_selected; + for (size_t i = 0; i < array->len; ++i) { + const knot_rrset_t *rr = array->at[i]->rr; + if (!knot_dname_is_equal(rr->owner, cname)) { + continue; + } + if ((rr->rclass != query->sclass) || + (rr->type != query->stype)) { + continue; + } + const bool to_wire = ((pkt_class & (PKT_NXDOMAIN|PKT_NODATA)) != 0); + const int state = pick_authority(pkt, req, to_wire); + if (state != kr_ok()) { + return KR_STATE_FAIL; + } + if (!array->at[i]->to_wire) { + const size_t last_idx = array->len - 1; + size_t j = i; + ranked_rr_array_entry_t *entry = array->at[i]; + /* Relocate record to the end, after current cname */ + while (j < last_idx) { + array->at[j] = array->at[j + 1]; + ++j; + } + array->at[last_idx] = entry; + entry->to_wire = true; + } + return finalize_answer(pkt, req); + } + return kr_ok(); +} + +static int process_answer(knot_pkt_t *pkt, struct kr_request *req) +{ + struct kr_query *query = req->current_query; + + /* Response for minimized QNAME. Note that current iterator's minimization + * is only able ask one label below a zone cut. + * NODATA => may be empty non-terminal, retry (found zone cut) + * NOERROR => found zone cut, retry, except the case described below + * NXDOMAIN => parent is zone cut, retry as a workaround for bad authoritatives + */ + const bool is_final = (query->parent == NULL); + const int pkt_class = kr_response_classify(pkt); + const knot_dname_t * pkt_qname = knot_pkt_qname(pkt); + if (!knot_dname_is_equal(pkt_qname, query->sname) && + (pkt_class & (PKT_NOERROR|PKT_NXDOMAIN|PKT_REFUSED|PKT_NODATA))) { + /* Check for parent server that is authoritative for child zone, + * several CCTLDs where the SLD and TLD have the same name servers */ + const knot_pktsection_t *ans = knot_pkt_section(pkt, KNOT_ANSWER); + if ((pkt_class & (PKT_NOERROR)) && ans->count > 0 && + knot_dname_is_equal(pkt_qname, query->zone_cut.name)) { + VERBOSE_MSG("<= continuing with qname minimization\n"); + } else { + /* fall back to disabling minimization */ + VERBOSE_MSG("<= retrying with non-minimized name\n"); + query->flags.NO_MINIMIZE = true; + } + return KR_STATE_CONSUME; + } + + /* This answer didn't improve resolution chain, therefore must be authoritative (relaxed to negative). */ + if (!is_authoritative(pkt, query)) { + if (!(query->flags.FORWARD) && + pkt_class & (PKT_NXDOMAIN|PKT_NODATA)) { + query->server_selection.error(query, req->upstream.transport, KR_SELECTION_LAME_DELEGATION); + VERBOSE_MSG("<= lame response: non-auth sent negative response\n"); + return KR_STATE_FAIL; + } + } + + const knot_dname_t *cname = NULL; + /* Process answer type */ + int state = unroll_cname(pkt, req, false, &cname); + if (state != kr_ok()) { + query->server_selection.error(query, req->upstream.transport, KR_SELECTION_BAD_CNAME); + return state; + } + /* Make sure that this is an authoritative answer (even with AA=0) for other layers */ + knot_wire_set_aa(pkt->wire); + /* Either way it resolves current query. */ + query->flags.RESOLVED = true; + /* Follow canonical name as next SNAME. */ + if (!knot_dname_is_equal(cname, query->sname)) { + /* Check if target record has been already copied */ + query->flags.CNAME = true; + if (is_final) { + state = process_final(pkt, req, cname); + if (state != kr_ok()) { + return state; + } + } else if ((query->flags.FORWARD) && + ((query->stype == KNOT_RRTYPE_DS) || + (query->stype == KNOT_RRTYPE_NS))) { + /* CNAME'ed answer for DS or NS subquery. + * Treat it as proof of zonecut nonexistence. */ + return KR_STATE_DONE; + } + VERBOSE_MSG("<= cname chain, following\n"); + /* Check if the same query was followed in the same CNAME chain. */ + for (const struct kr_query *q = query->cname_parent; q != NULL; + q = q->cname_parent) { + if (q->sclass == query->sclass && + q->stype == query->stype && + knot_dname_is_equal(q->sname, cname)) { + VERBOSE_MSG("<= cname chain loop\n"); + query->server_selection.error(query, req->upstream.transport, KR_SELECTION_BAD_CNAME); + return KR_STATE_FAIL; + } + } + struct kr_query *next = kr_rplan_push(&req->rplan, query->parent, cname, query->sclass, query->stype); + if (!next) { + return KR_STATE_FAIL; + } + next->flags.AWAIT_CUT = true; + + /* Copy transitive flags from original query to CNAME followup. */ + next->flags.TRACE = query->flags.TRACE; + next->flags.ALWAYS_CUT = query->flags.ALWAYS_CUT; + + /* Original query might have turned minimization off, revert. */ + next->flags.NO_MINIMIZE = req->options.NO_MINIMIZE; + + if (query->flags.FORWARD) { + next->forward_flags.CNAME = true; + } + next->cname_parent = query; + /* Want DNSSEC if and only if it's possible to secure + * this name (i.e. iff it is covered by a TA) */ + if (kr_ta_closest(req->ctx, cname, query->stype)) { + next->flags.DNSSEC_WANT = true; + } else { + next->flags.DNSSEC_WANT = false; + } + if (!(query->flags.FORWARD) || + (query->flags.DNSSEC_WEXPAND)) { + state = pick_authority(pkt, req, false); + if (state != kr_ok()) { + return KR_STATE_FAIL; + } + } + } else if (!query->parent) { + /* Answer for initial query */ + const bool to_wire = ((pkt_class & (PKT_NXDOMAIN|PKT_NODATA)) != 0); + state = pick_authority(pkt, req, to_wire); + if (state != kr_ok()) { + return KR_STATE_FAIL; + } + return finalize_answer(pkt, req); + } else { + /* Answer for sub-query; DS, IP for NS etc. + * It may contains NSEC \ NSEC3 records for + * data non-existence or wc expansion proving. + * If yes, they must be validated by validator. + * If no, authority section is unuseful. + * dnssec\nsec.c & dnssec\nsec3.c use + * rrsets from incoming packet. + * validator uses answer_selected & auth_selected. + * So, if nsec\nsec3 records are present in authority, + * pick_authority() must be called. + * TODO refactor nsec\nsec3 modules to work with + * answer_selected & auth_selected instead of incoming pkt. */ + bool auth_is_unuseful = true; + const knot_pktsection_t *ns = knot_pkt_section(pkt, KNOT_AUTHORITY); + for (unsigned i = 0; i < ns->count; ++i) { + const knot_rrset_t *rr = knot_pkt_rr(ns, i); + if (rr->type == KNOT_RRTYPE_NSEC || + rr->type == KNOT_RRTYPE_NSEC3) { + auth_is_unuseful = false; + break; + } + } + if (!auth_is_unuseful) { + state = pick_authority(pkt, req, false); + if (state != kr_ok()) { + return KR_STATE_FAIL; + } + } + } + return KR_STATE_DONE; +} + +/** @internal like process_answer() but for the STUB mode. */ +static int process_stub(knot_pkt_t *pkt, struct kr_request *req) +{ + struct kr_query *query = req->current_query; + if (kr_fails_assert(query->flags.STUB)) + return KR_STATE_FAIL; + /* Pick all answer RRs. */ + const knot_pktsection_t *an = knot_pkt_section(pkt, KNOT_ANSWER); + for (unsigned i = 0; i < an->count; ++i) { + const knot_rrset_t *rr = knot_pkt_rr(an, i); + int err = kr_ranked_rrarray_add(&req->answ_selected, rr, + KR_RANK_OMIT | KR_RANK_AUTH, true, query->uid, &req->pool); + /* KR_RANK_AUTH: we don't have the records directly from + * an authoritative source, but we do trust the server and it's + * supposed to only send us authoritative records. */ + if (err < 0) { + return KR_STATE_FAIL; + } + } + + knot_wire_set_aa(pkt->wire); + query->flags.RESOLVED = true; + /* Pick authority RRs. */ + int pkt_class = kr_response_classify(pkt); + const bool to_wire = ((pkt_class & (PKT_NXDOMAIN|PKT_NODATA)) != 0); + int err = pick_authority(pkt, req, to_wire); + if (err != kr_ok()) { + return KR_STATE_FAIL; + } + + return finalize_answer(pkt, req); +} + +/* State-less single resolution iteration step, not needed. */ +static int reset(kr_layer_t *ctx) { return KR_STATE_PRODUCE; } + +/* Set resolution context and parameters. */ +static int begin(kr_layer_t *ctx) +{ + if (ctx->state & (KR_STATE_DONE|KR_STATE_FAIL)) { + return ctx->state; + } + /* + * RFC7873 5.4 extends the QUERY operation code behaviour in order to + * be able to generate requests for server cookies. Such requests have + * QDCOUNT equal to zero and must contain a cookie option. + * Server cookie queries must be handled by the cookie module/layer + * before this layer. + */ + const knot_pkt_t *pkt = ctx->req->qsource.packet; + if (!pkt || knot_wire_get_qdcount(pkt->wire) == 0) { + return KR_STATE_FAIL; + } + + struct kr_query *qry = ctx->req->current_query; + /* Avoid any other classes, and avoid any meta-types ~~except for ANY~~. */ + if (qry->sclass != KNOT_CLASS_IN + || (knot_rrtype_is_metatype(qry->stype) + /* && qry->stype != KNOT_RRTYPE_ANY hmm ANY seems broken ATM */)) { + knot_pkt_t *ans = kr_request_ensure_answer(ctx->req); + if (!ans) return ctx->req->state; + knot_wire_set_rcode(ans->wire, KNOT_RCODE_NOTIMPL); + return KR_STATE_FAIL; + } + + return reset(ctx); +} + +int kr_make_query(struct kr_query *query, knot_pkt_t *pkt) +{ + /* Minimize QNAME (if possible). */ + uint16_t qtype = query->stype; + const knot_dname_t *qname = minimized_qname(query, &qtype); + + /* Form a query for the authoritative. */ + knot_pkt_clear(pkt); + int ret = knot_pkt_put_question(pkt, qname, query->sclass, qtype); + if (ret != KNOT_EOK) { + return ret; + } + + /* Query built, expect answer. */ + query->id = kr_rand_bytes(2); + /* We must respect https://tools.ietf.org/html/rfc7766#section-6.2.1 + * - When sending multiple queries over a TCP connection, clients MUST NOT + * reuse the DNS Message ID of an in-flight query on that connection. + * + * So, if query is going to be sent over TCP connection + * this id can be changed to avoid duplication with query that already was sent + * but didn't receive answer yet. + */ + knot_wire_set_id(pkt->wire, query->id); + pkt->parsed = pkt->size; + + return kr_ok(); +} + +static int prepare_query(kr_layer_t *ctx, knot_pkt_t *pkt) +{ + if (kr_fails_assert(pkt && ctx)) + return KR_STATE_FAIL; + struct kr_request *req = ctx->req; + struct kr_query *query = req->current_query; + if (!query || ctx->state & (KR_STATE_DONE|KR_STATE_FAIL)) { + return ctx->state; + } + + /* Make query */ + int ret = kr_make_query(query, pkt); + if (ret != 0) { + return KR_STATE_FAIL; + } + + WITH_VERBOSE(query) { + KR_DNAME_GET_STR(name_str, query->sname); + KR_RRTYPE_GET_STR(type_str, query->stype); + QVERBOSE_MSG(query, "'%s' type '%s' new uid was assigned .%02u, parent uid .%02u\n", + name_str, type_str, req->rplan.next_uid, + query->parent ? query->parent->uid : 0); + } + + query->uid = req->rplan.next_uid; + req->rplan.next_uid += 1; + query->flags.CACHED = false; // in case it got left from earlier (unknown edge case) + + return KR_STATE_CONSUME; +} + +static bool satisfied_by_additional(const struct kr_query *qry) +{ + const bool prereq = !qry->flags.STUB && !qry->flags.FORWARD && qry->flags.NONAUTH; + if (!prereq) + return false; + const struct kr_request *req = qry->request; + for (ssize_t i = req->add_selected.len - 1; i >= 0; --i) { + ranked_rr_array_entry_t *entry = req->add_selected.at[i]; + if (entry->qry_uid != qry->uid) + break; + if (entry->rr->type == qry->stype + && knot_dname_is_equal(entry->rr->owner, qry->sname)) { + return true; + } + } + return false; +} + +/** Restrict all RRset TTLs to the specified bounds (if matching qry_uid). */ +static void bound_ttls(ranked_rr_array_t *array, uint32_t qry_uid, + uint32_t ttl_min, uint32_t ttl_max) +{ + for (ssize_t i = 0; i < array->len; ++i) { + if (array->at[i]->qry_uid != qry_uid) + continue; + uint32_t *ttl = &array->at[i]->rr->ttl; + if (*ttl < ttl_min) { + *ttl = ttl_min; + } else if (*ttl > ttl_max) { + *ttl = ttl_max; + } + } +} + +/** Resolve input query or continue resolution with followups. + * + * This roughly corresponds to RFC1034, 5.3.3 4a-d. + */ +static int resolve(kr_layer_t *ctx, knot_pkt_t *pkt) +{ + if (kr_fails_assert(pkt && ctx)) + return KR_STATE_FAIL; + struct kr_request *req = ctx->req; + struct kr_query *query = req->current_query; + if (!query) { + return ctx->state; + } + query->flags.PKT_IS_SANE = false; + + WITH_VERBOSE(query) { + if (query->flags.TRACE) { + auto_free char *pkt_text = kr_pkt_text(pkt); + VERBOSE_MSG("<= answer received:\n%s\n", pkt_text); + } + } + + if (query->flags.RESOLVED || query->flags.BADCOOKIE_AGAIN) { + return ctx->state; + } + + /* Check for packet processing errors first. + * Note - we *MUST* check if it has at least a QUESTION, + * otherwise it would crash on accessing QNAME. */ + /* TODO: some of these erros are probably unreachable + * thanks to getting caught earlier, in particular in worker_submit() */ + if (pkt->parsed <= KNOT_WIRE_HEADER_SIZE) { + if (pkt->parsed == KNOT_WIRE_HEADER_SIZE && knot_wire_get_rcode(pkt->wire) == KNOT_RCODE_FORMERR) { + /* This is a special case where we get valid header with FORMERR and nothing else. + * This happens on some authoritatives which don't support EDNS and don't + * bother copying the SECTION QUESTION. */ + query->server_selection.error(query, req->upstream.transport, KR_SELECTION_FORMERR); + return KR_STATE_FAIL; + } + VERBOSE_MSG("<= malformed response (parsed %d)\n", (int)pkt->parsed); + query->server_selection.error(query, req->upstream.transport, KR_SELECTION_MALFORMED); + return KR_STATE_FAIL; + } else if (!is_paired_to_query(pkt, query)) { + WITH_VERBOSE(query) { + const char *ns_str = + req->upstream.transport ? kr_straddr(&req->upstream.transport->address.ip) : "(internal)"; + VERBOSE_MSG("<= ignoring mismatching response from %s\n", + ns_str ? ns_str : "(kr_straddr failed)"); + } + query->server_selection.error(query, req->upstream.transport, KR_SELECTION_MISMATCHED); + return KR_STATE_FAIL; + } else if (knot_wire_get_tc(pkt->wire)) { + VERBOSE_MSG("<= truncated response, failover to TCP\n"); + if (query) { + /* Fail if already on TCP. */ + if (req->upstream.transport->protocol != KR_TRANSPORT_UDP) { + VERBOSE_MSG("<= TC=1 with TCP, bailing out\n"); + query->server_selection.error(query, req->upstream.transport, KR_SELECTION_TRUNCATED); + return KR_STATE_FAIL; + } + query->server_selection.error(query, req->upstream.transport, KR_SELECTION_TRUNCATED); + } + return KR_STATE_CONSUME; + } + + /* If exiting above here, there's no sense to put it into packet cache. + * Having "extra bytes" at the end of DNS message is considered SANE here. + * The most important part is to check for spoofing: is_paired_to_query() */ + query->flags.PKT_IS_SANE = true; + + const knot_lookup_t *rcode = // just for logging but cheaper than a condition + knot_lookup_by_id(knot_rcode_names, knot_wire_get_rcode(pkt->wire)); + + // We can't return directly from the switch because we have to give feedback to server selection first + int ret = 0; + int selection_error = KR_SELECTION_OK; + + /* Check response code. */ + switch(knot_wire_get_rcode(pkt->wire)) { + case KNOT_RCODE_NOERROR: + case KNOT_RCODE_NXDOMAIN: + break; /* OK */ + case KNOT_RCODE_YXDOMAIN: /* Basically a successful answer; name just doesn't fit. */ + if (!kr_request_ensure_answer(req)) { + ret = req->state; + } + knot_wire_set_rcode(req->answer->wire, KNOT_RCODE_YXDOMAIN); + break; + case KNOT_RCODE_REFUSED: + if (query->flags.STUB) { + /* just pass answer through if in stub mode */ + break; + } + ret = KR_STATE_FAIL; + selection_error = KR_SELECTION_REFUSED; + break; + case KNOT_RCODE_SERVFAIL: + if (query->flags.STUB) { + /* just pass answer through if in stub mode */ + break; + } + ret = KR_STATE_FAIL; + selection_error = KR_SELECTION_SERVFAIL; + break; + case KNOT_RCODE_FORMERR: + ret = KR_STATE_FAIL; + if (knot_pkt_has_edns(pkt)) { + selection_error = KR_SELECTION_FORMERR_EDNS; + } else { + selection_error = KR_SELECTION_FORMERR; + } + break; + case KNOT_RCODE_NOTIMPL: + ret = KR_STATE_FAIL; + selection_error = KR_SELECTION_NOTIMPL; + break; + default: + ret = KR_STATE_FAIL; + selection_error = KR_SELECTION_OTHER_RCODE; + break; + } + + /* Check for "extra bytes" is deferred, so that RCODE-based failures take priority. */ + if (ret != KR_STATE_FAIL && pkt->parsed < pkt->size) { + VERBOSE_MSG("<= malformed response with %zu extra bytes\n", + pkt->size - pkt->parsed); + ret = KR_STATE_FAIL; + if (selection_error == KR_SELECTION_OK) + selection_error = KR_SELECTION_MALFORMED; + } + + if (query->server_selection.initialized) { + query->server_selection.error(query, req->upstream.transport, selection_error); + } + + if (ret) { + VERBOSE_MSG("<= rcode: %s\n", rcode ? rcode->name : "??"); + return ret; + } + + int state; + /* Forwarding/stub mode is special. */ + if (query->flags.STUB) { + state = process_stub(pkt, req); + goto rrarray_finalize; + } + + /* Resolve authority to see if it's referral or authoritative. */ + state = process_authority(pkt, req); + switch(state) { + case KR_STATE_CONSUME: /* Not referral, process answer. */ + VERBOSE_MSG("<= rcode: %s\n", rcode ? rcode->name : "??"); + state = process_answer(pkt, req); + break; + case KR_STATE_DONE: /* Referral */ + state = process_referral_answer(pkt,req); + if (satisfied_by_additional(query)) { /* This is a little hacky. + * We found sufficient information in ADDITIONAL section + * and it was selected for caching in this CONSUME round. + * To make iterator accept the record in a simple way, + * we trigger another cache *reading* attempt + * for the subsequent PRODUCE round. + */ + kr_assert(query->flags.NONAUTH); + query->flags.CACHE_TRIED = false; + VERBOSE_MSG("<= referral response, but cache should stop us short now\n"); + } else { + VERBOSE_MSG("<= referral response, follow\n"); + } + break; + default: + break; + } + +rrarray_finalize: + /* Finish construction of libknot-format RRsets. + * We do this even if dropping the answer, though it's probably useless. */ + (void)0; + const struct kr_cache *cache = &req->ctx->cache; + ranked_rr_array_t *selected[] = kr_request_selected(req); + for (knot_section_t i = KNOT_ANSWER; i <= KNOT_ADDITIONAL; ++i) { + ret = kr_ranked_rrarray_finalize(selected[i], query->uid, &req->pool); + if (unlikely(ret)) + return KR_STATE_FAIL; + if (!query->flags.CACHED) + bound_ttls(selected[i], query->uid, cache->ttl_min, cache->ttl_max); + } + + return state; +} + +/** Module implementation. */ +int iterate_init(struct kr_module *self) +{ + static const kr_layer_api_t layer = { + .begin = &begin, + .reset = &reset, + .consume = &resolve, + .produce = &prepare_query + }; + self->layer = &layer; + return kr_ok(); +} + +KR_MODULE_EXPORT(iterate) /* useless for builtin module, but let's be consistent */ + +#undef VERBOSE_MSG diff --git a/lib/layer/iterate.h b/lib/layer/iterate.h new file mode 100644 index 0000000..4ea4351 --- /dev/null +++ b/lib/layer/iterate.h @@ -0,0 +1,25 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "lib/layer.h" +#include "lib/rplan.h" + +/* Packet classification. */ +enum { + PKT_NOERROR = 1 << 0, /* Positive response */ + PKT_NODATA = 1 << 1, /* No data response */ + PKT_NXDOMAIN = 1 << 2, /* Negative response */ + PKT_REFUSED = 1 << 3, /* Refused response */ + PKT_ERROR = 1 << 4 /* Bad message */ +}; + +/** Classify response by type. */ +KR_EXPORT +int kr_response_classify(const knot_pkt_t *pkt); + +/** Make next iterative query. */ +KR_EXPORT +int kr_make_query(struct kr_query *query, knot_pkt_t *pkt); diff --git a/lib/layer/mode.rst b/lib/layer/mode.rst new file mode 100644 index 0000000..d64257e --- /dev/null +++ b/lib/layer/mode.rst @@ -0,0 +1,26 @@ +.. SPDX-License-Identifier: GPL-3.0-or-later + +.. function:: mode(['strict' | 'normal' | 'permissive']) + + :param: New checking level specified as string (*optional*). + :return: Current checking level. + + Get or change resolver strictness checking level. + + By default, resolver runs in *normal* mode. There are possibly many small adjustments + hidden behind the mode settings, but the main idea is that in *permissive* mode, the resolver + tries to resolve a name with as few lookups as possible, while in *strict* mode it spends much + more effort resolving and checking referral path. However, if majority of the traffic is covered + by DNSSEC, some of the strict checking actions are counter-productive. + + .. csv-table:: + :header: "Glue type", "Modes when it is accepted", "Example glue [#example_glue]_" + + "mandatory glue", "strict, normal, permissive", "ns1.example.org" + "in-bailiwick glue", "normal, permissive", "ns1.example2.org" + "any glue records", "permissive", "ns1.example3.net" + + .. [#example_glue] The examples show glue records acceptable from servers + authoritative for `org` zone when delegating to `example.org` zone. + Unacceptable or missing glue records trigger resolution of names listed + in NS records before following respective delegation. diff --git a/lib/layer/test.integr/deckard.yaml b/lib/layer/test.integr/deckard.yaml new file mode 100644 index 0000000..d2d62d0 --- /dev/null +++ b/lib/layer/test.integr/deckard.yaml @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +programs: +- name: kresd + binary: kresd + additional: + - --noninteractive + templates: + - lib/layer/test.integr/kresd_config.j2 + - tests/integration/hints_zone.j2 + configs: + - config + - hints +noclean: True diff --git a/lib/layer/test.integr/iter_cname_length.rpl b/lib/layer/test.integr/iter_cname_length.rpl new file mode 100644 index 0000000..39f48a8 --- /dev/null +++ b/lib/layer/test.integr/iter_cname_length.rpl @@ -0,0 +1,226 @@ +do-ip6: no +; config options +; SPDX-License-Identifier: GPL-3.0-or-later + stub-addr: 193.0.14.129 # k.root-servers.net. +CONFIG_END + +SCENARIO_BEGIN Test restriction on CNAME chain length. + + +; k.root-servers.net. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +n1.tld. IN NS +SECTION ANSWER +n1.tld. IN CNAME n2.tld. +n2.tld. IN CNAME n3.tld. +n3.tld. IN CNAME n4.tld. +n4.tld. IN CNAME n5.tld. +n5.tld. IN CNAME n6.tld. +n6.tld. IN CNAME n7.sub. +SECTION AUTHORITY +sub. IN NS ns.sub. +SECTION ADDITIONAL +ns.sub. IN A 194.0.14.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +n2.tld. IN NS +SECTION ANSWER +n2.tld. IN CNAME n3.tld. +n3.tld. IN CNAME n4.tld. +n4.tld. IN CNAME n5.tld. +n5.tld. IN CNAME n6.tld. +n6.tld. IN CNAME n7.sub. +SECTION AUTHORITY +sub. IN NS ns.sub. +SECTION ADDITIONAL +ns.sub. IN A 194.0.14.1 +ENTRY_END + + +; empty non-terminal for query name minimization +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +tld. IN NS +SECTION ANSWER +ENTRY_END + + + +; sub. subdomains +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +sub. IN NS +SECTION AUTHORITY +sub. IN NS ns.sub. +SECTION ADDITIONAL +ns.sub. IN A 194.0.14.1 +ENTRY_END + +RANGE_END + + +; ns.sub. +RANGE_BEGIN 0 100 + ADDRESS 194.0.14.1 + +ENTRY_BEGIN +MATCH opcode qname qtype +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.sub. IN A +SECTION ANSWER +ns.sub. IN A 194.0.14.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname qtype +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.sub. IN AAAA +SECTION ANSWER +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname qtype +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +n7.sub. IN A +SECTION ANSWER +n7.sub. IN CNAME n8.sub. +n8.sub. IN CNAME n9.sub. +n9.sub. IN CNAME n10.sub. +n10.sub. IN CNAME n11.sub. +n11.sub. IN CNAME n12.sub. +n12.sub. IN CNAME n13.sub. +n13.sub. IN CNAME n14.sub. +n14.sub. IN A 198.18.0.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname qtype +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +loop7.sub. IN A +SECTION ANSWER +loop7.sub. IN CNAME loop8.sub. +loop8.sub. IN CNAME loop9.sub. +loop9.sub. IN CNAME loop10.sub. +loop10.sub. IN CNAME loop11.sub. +; loop11 -> loop7 -> ... -> loop11 +loop11.sub. IN CNAME loop7.sub. +loop12.sub. IN CNAME loop13.sub. +loop13.sub. IN CNAME loop14.sub. +loop14.sub. IN A 198.18.0.1 +ENTRY_END + +RANGE_END + +; maximum allowed chain length +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +n2.tld. IN A +ENTRY_END + +STEP 11 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +n2.tld. IN A +SECTION ANSWER +n2.tld. IN CNAME n3.tld. +n3.tld. IN CNAME n4.tld. +n4.tld. IN CNAME n5.tld. +n5.tld. IN CNAME n6.tld. +n6.tld. IN CNAME n7.sub. +n7.sub. IN CNAME n8.sub. +n8.sub. IN CNAME n9.sub. +n9.sub. IN CNAME n10.sub. +n10.sub. IN CNAME n11.sub. +n11.sub. IN CNAME n12.sub. +n12.sub. IN CNAME n13.sub. +n13.sub. IN CNAME n14.sub. +n14.sub. IN A 198.18.0.1 +ENTRY_END + + +; too long CNAME chain across two zones +STEP 20 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +n1.tld. IN A +ENTRY_END + +STEP 21 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA SERVFAIL +SECTION QUESTION +n1.tld. IN A +SECTION ANSWER +n1.tld. IN CNAME n2.tld. +n2.tld. IN CNAME n3.tld. +n3.tld. IN CNAME n4.tld. +n4.tld. IN CNAME n5.tld. +n5.tld. IN CNAME n6.tld. +n6.tld. IN CNAME n7.sub. +n7.sub. IN CNAME n8.sub. +n8.sub. IN CNAME n9.sub. +n9.sub. IN CNAME n10.sub. +n10.sub. IN CNAME n11.sub. +n11.sub. IN CNAME n12.sub. +n12.sub. IN CNAME n13.sub. +n13.sub. IN CNAME n14.sub. +; This chain is too long (> 13): +; n14.sub. IN A 198.18.0.1 +ENTRY_END + + +; CNAME loop detection +STEP 30 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +loop7.sub. IN A +ENTRY_END + +STEP 31 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA SERVFAIL +SECTION QUESTION +loop7.sub. IN A +SECTION ANSWER +loop7.sub. IN CNAME loop8.sub. +loop8.sub. IN CNAME loop9.sub. +loop9.sub. IN CNAME loop10.sub. +loop10.sub. IN CNAME loop11.sub. +loop11.sub. IN CNAME loop7.sub. +ENTRY_END + +SCENARIO_END diff --git a/lib/layer/test.integr/iter_limit_bad_glueless.rpl b/lib/layer/test.integr/iter_limit_bad_glueless.rpl new file mode 100644 index 0000000..73d4627 --- /dev/null +++ b/lib/layer/test.integr/iter_limit_bad_glueless.rpl @@ -0,0 +1,220 @@ +do-ip6: no +; config options +; target-fetch-policy: "0 0 0 0 0" +; name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test resolution with lame reply looks like nodata with noSOA + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +; com +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +com. IN NS +SECTION AUTHORITY +com. IN NS a.gtld-servers.net. +SECTION ADDITIONAL +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END + +; net +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +net. IN NS +SECTION AUTHORITY +net. IN NS e.gtld-servers.net. +SECTION ADDITIONAL +e.gtld-servers.net. IN A 192.12.94.30 +ENTRY_END + +RANGE_END + +; a.gtld-servers.net. - com +RANGE_BEGIN 0 100 + ADDRESS 192.5.6.30 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +com. IN NS +SECTION ANSWER +com. IN NS a.gtld-servers.net. +SECTION ADDITIONAL +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +victim.com. IN NS +SECTION AUTHORITY +victim.com. IN NS ns.victim.com. +SECTION ADDITIONAL +ns.victim.com. IN A 1.2.3.55 +ENTRY_END +RANGE_END + +; e.gtld-servers.net. - net +RANGE_BEGIN 0 100 + ADDRESS 192.12.94.30 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +net. IN NS +SECTION ANSWER +net. IN NS e.gtld-servers.net. +SECTION ADDITIONAL +e.gtld-servers.net. IN A 192.12.94.30 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +attacker.net. IN NS +SECTION AUTHORITY +attacker.net. IN NS ns.attacker.net. +SECTION ADDITIONAL +ns.attacker.net. IN A 1.2.3.44 +ENTRY_END +RANGE_END + +; ns.attacker.net. +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.44 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +attacker.net. IN NS +SECTION ANSWER +attacker.net. IN NS ns.attacker.net. +SECTION ADDITIONAL +ns.attacker.net. IN A 1.2.3.44 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +ns.attacker.net. IN A +SECTION ANSWER +ns.attacker.net. IN A 1.2.3.44 +SECTION AUTHORITY +attacker.net. IN NS ns.attacker.net. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +ns.attacker.net. IN AAAA +SECTION AUTHORITY +SECTION ADDITIONAL +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +sub.attacker.net. IN NS +SECTION AUTHORITY +sub.attacker.net. IN NS ns1.victim.com. +sub.attacker.net. IN NS ns2.victim.com. +sub.attacker.net. IN NS ns3.victim.com. +sub.attacker.net. IN NS ns4.victim.com. +sub.attacker.net. IN NS ns5.victim.com. +sub.attacker.net. IN NS ns6.victim.com. +sub.attacker.net. IN NS ns7.victim.com. +sub.attacker.net. IN NS ns8.victim.com. +sub.attacker.net. IN NS ns9.victim.com. +ENTRY_END +RANGE_END + +; ns.victim.com. +; returns NXDOMAIN for all queries (attacker generated NS names are not present) +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.55 +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NXDOMAIN +SECTION QUESTION +victim.com. IN NS +SECTION AUTHORITY +victim.com. 0 IN SOA . . 1 1 1 1 1 +SECTION ADDITIONAL +ENTRY_END +RANGE_END + + +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.sub.attacker.net. IN A +ENTRY_END + +; in any case we must get SERVFAIL, no deleation works +STEP 11 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA SERVFAIL +SECTION QUESTION +www.sub.attacker.net. IN A +SECTION ANSWER +ENTRY_END + +; recursion happens here +STEP 20 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +glueless.trigger.check.max.number.of.upstream.queries. IN TXT +ENTRY_END + +STEP 21 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA RD RA NOERROR +SECTION QUESTION +glueless.trigger.check.max.number.of.upstream.queries. IN TXT +SECTION ANSWER +glueless.trigger.check.max.number.of.upstream.queries. IN TXT "pass" +SECTION AUTHORITY +SECTION ADDITIONAL +ENTRY_END + + +SCENARIO_END diff --git a/lib/layer/test.integr/iter_limit_refuse.rpl b/lib/layer/test.integr/iter_limit_refuse.rpl new file mode 100644 index 0000000..285b5af --- /dev/null +++ b/lib/layer/test.integr/iter_limit_refuse.rpl @@ -0,0 +1,150 @@ +do-ip6: no +; config options +;server: + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Outrageous number of auth servers return REFUSED. Simulates NXNSAttack misusing wildcard which points to victim's DNS server. Lua config checks if number of outgoing queries is within limits. + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +com. IN A +SECTION AUTHORITY +com. IN NS a.gtld-servers.net. +SECTION ADDITIONAL +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END +RANGE_END + +; a.gtld-servers.net. +RANGE_BEGIN 0 100 + ADDRESS 192.5.6.30 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +com. IN NS +SECTION ANSWER +com. IN NS a.gtld-servers.net. +SECTION ADDITIONAL +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +example.com. IN A +SECTION AUTHORITY +example.com. IN NS ns10.example.com. +example.com. IN NS ns11.example.com. +example.com. IN NS ns12.example.com. +example.com. IN NS ns13.example.com. +example.com. IN NS ns14.example.com. +example.com. IN NS ns15.example.com. +example.com. IN NS ns16.example.com. +example.com. IN NS ns17.example.com. +example.com. IN NS ns18.example.com. +example.com. IN NS ns19.example.com. +SECTION ADDITIONAL +ns10.example.com. IN A 1.2.3.10 +ns11.example.com. IN A 1.2.3.11 +ns12.example.com. IN A 1.2.3.12 +ns13.example.com. IN A 1.2.3.13 +ns14.example.com. IN A 1.2.3.14 +ns15.example.com. IN A 1.2.3.15 +ns16.example.com. IN A 1.2.3.16 +ns17.example.com. IN A 1.2.3.17 +ns18.example.com. IN A 1.2.3.18 +ns19.example.com. IN A 1.2.3.19 + +ENTRY_END +RANGE_END + +; ns1.example.com. +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.10 + ADDRESS 1.2.3.11 + ADDRESS 1.2.3.12 + ADDRESS 1.2.3.13 + ADDRESS 1.2.3.14 + ADDRESS 1.2.3.15 + ADDRESS 1.2.3.16 + ADDRESS 1.2.3.17 + ADDRESS 1.2.3.18 + ADDRESS 1.2.3.19 +ENTRY_BEGIN +MANDATORY +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA REFUSED +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +SECTION AUTHORITY +SECTION ADDITIONAL +ENTRY_END +RANGE_END + + +; recursion happens here +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +; in any case we must get SERVFAIL, no auth works +STEP 11 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA DO SERVFAIL +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +SECTION AUTHORITY +SECTION ADDITIONAL +ENTRY_END + +; recursion happens here +STEP 20 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +refused.trigger.check.max.number.of.upstream.queries. IN TXT +ENTRY_END + +STEP 21 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA RD RA NOERROR +SECTION QUESTION +refused.trigger.check.max.number.of.upstream.queries. IN TXT +SECTION ANSWER +refused.trigger.check.max.number.of.upstream.queries. IN TXT "pass" +SECTION AUTHORITY +SECTION ADDITIONAL +ENTRY_END + +SCENARIO_END diff --git a/lib/layer/test.integr/kresd_config.j2 b/lib/layer/test.integr/kresd_config.j2 new file mode 100644 index 0000000..dc18a1b --- /dev/null +++ b/lib/layer/test.integr/kresd_config.j2 @@ -0,0 +1,107 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later + +local ffi = require('ffi') + +-- hook for iter_refuse_toomany.rpl +local function check_max_number_of_upstream_queries(maxcnt) + return function (state, req) + local vals = worker.stats() + local upstream_packets = vals.ipv4 + vals.ipv6 + log_info(ffi.C.LOG_GRP_TESTS, '%d packets sent to upstream', upstream_packets) + local answ_f + if upstream_packets > maxcnt then -- . + com. + ???? + answ_f = policy.ANSWER( + { [kres.type.TXT] = { ttl=300, rdata='\4fail' } }) + + else + answ_f = policy.ANSWER( + { [kres.type.TXT] = { ttl=300, rdata='\4pass' } }) + end + return answ_f(state, req) + end +end + +policy.add( + policy.suffix(check_max_number_of_upstream_queries(8), + policy.todnames({'refused.trigger.check.max.number.of.upstream.queries.'}) + ) +) +policy.add( + policy.suffix(check_max_number_of_upstream_queries(16), + policy.todnames({'glueless.trigger.check.max.number.of.upstream.queries.'}) + ) +) + +-- hook end iter_refuse_toomany.rpl + + +trust_anchors.remove('.') +{% for TAF in TRUST_ANCHOR_FILES %} +-- trust_anchors.add_file('{{TAF}}') +{% endfor %} + +{% raw %} +-- Disable RFC5011 TA update +if ta_update then + modules.unload('ta_update') +end + +-- Disable RFC8145 signaling, scenario doesn't provide expected answers +if ta_signal_query then + modules.unload('ta_signal_query') +end + +-- Disable RFC8109 priming, scenario doesn't provide expected answers +if priming then + modules.unload('priming') +end + +-- Disable this module because it make one priming query +if detect_time_skew then + modules.unload('detect_time_skew') +end + +_hint_root_file('hints') +cache.size = 2*MB +log_level('debug') +policy.add(policy.all(policy.DEBUG_ALWAYS)) +{% endraw %} + +net = { '{{SELF_ADDR}}' } + +{% if DO_IP6 == "true" %} +net.ipv6 = true +{% else %} +net.ipv6 = false +{% endif %} + +{% if DO_IP4 == "true" %} +net.ipv4 = true +{% else %} +net.ipv4 = false +{% endif %} + + +{% if QMIN == "false" %} +option('NO_MINIMIZE', true) +{% else %} +option('NO_MINIMIZE', false) +{% endif %} + + +-- Self-checks on globals +assert(help() ~= nil) +assert(worker.id ~= nil) +-- Self-checks on facilities +assert(cache.count() == 0) +assert(cache.stats() ~= nil) +assert(cache.backends() ~= nil) +assert(worker.stats() ~= nil) +assert(net.interfaces() ~= nil) +-- Self-checks on loaded stuff +assert(net.list()[1].transport.ip == '{{SELF_ADDR}}') +assert(#modules.list() > 0) +-- Self-check timers +ev = event.recurrent(1 * sec, function (ev) return 1 end) +event.cancel(ev) +ev = event.after(0, function (ev) return 1 end) diff --git a/lib/layer/validate.c b/lib/layer/validate.c new file mode 100644 index 0000000..93f1d4f --- /dev/null +++ b/lib/layer/validate.c @@ -0,0 +1,1366 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <errno.h> +#include <sys/time.h> +#include <stdio.h> +#include <string.h> + +#include <contrib/cleanup.h> +#include <libknot/packet/wire.h> +#include <libknot/rrtype/rdname.h> +#include <libknot/rrtype/rrsig.h> +#include <libdnssec/error.h> + +#include "lib/dnssec/nsec.h" +#include "lib/dnssec/nsec3.h" +#include "lib/dnssec/ta.h" +#include "lib/dnssec.h" +#include "lib/layer.h" +#include "lib/resolve.h" +#include "lib/rplan.h" +#include "lib/utils.h" +#include "lib/defines.h" +#include "lib/module.h" +#include "lib/selection.h" + +#define VERBOSE_MSG(qry, ...) kr_log_q(qry, VALIDATOR, __VA_ARGS__) + +#define MAX_REVALIDATION_CNT 2 + +/** + * Search in section for given type. + * @param sec Packet section. + * @param type Type to search for. + * @return True if found. + */ +static bool section_has_type(const knot_pktsection_t *sec, uint16_t type) +{ + if (!sec) { + return false; + } + + for (unsigned i = 0; i < sec->count; ++i) { + const knot_rrset_t *rr = knot_pkt_rr(sec, i); + if (rr->type == type) { + return true; + } + } + + return false; +} + +static bool pkt_has_type(const knot_pkt_t *pkt, uint16_t type) +{ + if (!pkt) { + return false; + } + + if (section_has_type(knot_pkt_section(pkt, KNOT_ANSWER), type)) { + return true; + } + if (section_has_type(knot_pkt_section(pkt, KNOT_AUTHORITY), type)) { + return true; + } + return section_has_type(knot_pkt_section(pkt, KNOT_ADDITIONAL), type); +} + +static void log_bogus_rrsig(kr_rrset_validation_ctx_t *vctx, + const knot_rrset_t *rr, const char *msg) { + if (kr_log_is_debug_qry(VALIDATOR, vctx->log_qry)) { + auto_free char *name_text = kr_dname_text(rr->owner); + auto_free char *type_text = kr_rrtype_text(rr->type); + VERBOSE_MSG(vctx->log_qry, ">< %s: %s %s " + "(%u matching RRSIGs, %u expired, %u not yet valid, " + "%u invalid signer, %u invalid label count, %u invalid key, " + "%u invalid crypto, %u invalid NSEC)\n", + msg, name_text, type_text, vctx->rrs_counters.matching_name_type, + vctx->rrs_counters.expired, vctx->rrs_counters.notyet, + vctx->rrs_counters.signer_invalid, vctx->rrs_counters.labels_invalid, + vctx->rrs_counters.key_invalid, vctx->rrs_counters.crypto_invalid, + vctx->rrs_counters.nsec_invalid); + } +} + +/** Check that given CNAME could be generated by given DNAME (no DNSSEC validation). */ +static bool cname_matches_dname(const knot_rrset_t *rr_cn, const knot_rrset_t *rr_dn) +{ + if (kr_fails_assert(rr_cn->type == KNOT_RRTYPE_CNAME && rr_dn->type == KNOT_RRTYPE_DNAME)) + return false; + /* When DNAME substitution happens, let's consider the "prefix" + * that is carried over and the "suffix" that is replaced. + * (Here we consider the label order used in wire and presentation.) */ + const int prefix_labels = knot_dname_in_bailiwick(rr_cn->owner, rr_dn->owner); + if (prefix_labels < 1) + return false; + const knot_dname_t *cn_target = knot_cname_name(rr_cn->rrs.rdata); + const knot_dname_t *dn_target = knot_dname_target(rr_dn->rrs.rdata); + /* ^ We silently use the first RR in each RRset. Could be e.g. logged. */ + /* Check that the suffixes are correct - and even prefix label counts. */ + if (knot_dname_in_bailiwick(cn_target, dn_target) != prefix_labels) + return false; + /* Check that prefixes match. Find end of the first one and compare. */ + const knot_dname_t *cn_se = rr_cn->owner; + for (int i = 0; i < prefix_labels; ++i) + cn_se += 1 + *cn_se; + return strncmp((const char *)rr_cn->owner, (const char *)cn_target, + cn_se - rr_cn->owner) == 0; + /* ^ We use the fact that dnames are always zero-terminated + * to avoid any possible over-read in cn_target. */ +} + +static void mark_insecure_parents(const struct kr_query *qry); +static void rank_records(struct kr_query *qry, bool any_rank, enum kr_rank rank_to_set, + const knot_dname_t *bailiwick); + +static bool maybe_downgrade_nsec3(const ranked_rr_array_entry_t *e, struct kr_query *qry, + const kr_rrset_validation_ctx_t *vctx) +{ + bool required_conditions = + e->rr->type == KNOT_RRTYPE_NSEC3 + && kr_rank_test(e->rank, KR_RANK_SECURE) + // extra careful: avoid downgrade if SNAME isn't in bailiwick of signer + && knot_dname_in_bailiwick(qry->sname, vctx->zone_name) >= 0; + if (!required_conditions) + return false; + + const knot_rdataset_t *rrs = &e->rr->rrs; + knot_rdata_t *rd = rrs->rdata; + for (int j = 0; j < rrs->count; ++j, rd = knot_rdataset_next(rd)) { + if (knot_nsec3_iters(rd) > KR_NSEC3_MAX_ITERATIONS) + goto do_downgrade; + } + return false; + +do_downgrade: // we do this deep inside calls because of having signer name available + VERBOSE_MSG(qry, "<= DNSSEC downgraded due to NSEC3 iterations %d > %d\n", + (int)knot_nsec3_iters(rd), (int)KR_NSEC3_MAX_ITERATIONS); + qry->flags.DNSSEC_WANT = false; + qry->flags.DNSSEC_INSECURE = true; + rank_records(qry, true, KR_RANK_INSECURE, vctx->zone_name); + mark_insecure_parents(qry); + return true; +} + +#define KNOT_EDOWNGRADED (KNOT_ERROR_MIN - 1) + +static int validate_section(kr_rrset_validation_ctx_t *vctx, struct kr_query *qry, + knot_mm_t *pool) +{ + struct kr_request *req = qry->request; + if (!vctx) { + return kr_error(EINVAL); + } + + /* Can't use qry->zone_cut.name directly, as this name can + * change when updating cut information before validation. + */ + vctx->zone_name = vctx->keys ? vctx->keys->owner : NULL; + + for (ssize_t i = 0; i < vctx->rrs->len; ++i) { + ranked_rr_array_entry_t *entry = vctx->rrs->at[i]; + knot_rrset_t * const rr = entry->rr; + + if (entry->yielded || vctx->qry_uid != entry->qry_uid) { + continue; + } + + if (kr_rank_test(entry->rank, KR_RANK_OMIT) + || kr_rank_test(entry->rank, KR_RANK_SECURE)) { + continue; /* these are already OK */ + } + + if (!knot_dname_is_equal(qry->zone_cut.name, rr->owner)/*optim.*/ + && !kr_ta_closest(qry->request->ctx, rr->owner, rr->type)) { + /* We have NTA "between" our (perceived) zone cut and the RR. */ + kr_rank_set(&entry->rank, KR_RANK_INSECURE); + continue; + } + + if (rr->type == KNOT_RRTYPE_RRSIG) { + const knot_dname_t *signer_name = knot_rrsig_signer_name(rr->rrs.rdata); + if (!knot_dname_is_equal(vctx->zone_name, signer_name)) { + kr_rank_set(&entry->rank, KR_RANK_MISMATCH); + vctx->err_cnt += 1; + break; + } + if (!kr_rank_test(entry->rank, KR_RANK_BOGUS)) + kr_rank_set(&entry->rank, KR_RANK_OMIT); + continue; + } + + uint8_t rank_orig = entry->rank; + int validation_result = kr_rrset_validate(vctx, rr); + + /* Handle the case of CNAMEs synthesized from DNAMEs (they don't have RRSIGs). */ + if (rr->type == KNOT_RRTYPE_CNAME && validation_result == kr_error(ENOENT)) { + for (ssize_t j = 0; j < vctx->rrs->len; ++j) { + ranked_rr_array_entry_t *e_dname = vctx->rrs->at[j]; + if ((e_dname->rr->type == KNOT_RRTYPE_DNAME) + /* If the order is wrong, we will need two passes. */ + && kr_rank_test(e_dname->rank, KR_RANK_SECURE) + && cname_matches_dname(rr, e_dname->rr)) { + /* Now we believe the CNAME is OK. */ + validation_result = kr_ok(); + break; + } + } + if (validation_result != kr_ok()) { + vctx->cname_norrsig_cnt += 1; + } + } + + if (validation_result == kr_ok()) { + kr_rank_set(&entry->rank, KR_RANK_SECURE); + + /* Downgrade zone to insecure if certain NSEC3 record occurs. */ + if (unlikely(maybe_downgrade_nsec3(entry, qry, vctx))) + return kr_error(KNOT_EDOWNGRADED); + + } else if (kr_rank_test(rank_orig, KR_RANK_TRY)) { + /* RFC 4035 section 2.2: + * NS RRsets that appear at delegation points (...) + * MUST NOT be signed */ + if (vctx->rrs_counters.matching_name_type > 0) + log_bogus_rrsig(vctx, rr, + "found unexpected signatures for non-authoritative data which failed to validate, continuing"); + vctx->result = kr_ok(); + kr_rank_set(&entry->rank, KR_RANK_TRY); + /* ^^ BOGUS would be more accurate, but it might change + * to MISMATCH on revalidation, e.g. in test val_referral_nods :-/ + */ + + } else if (validation_result == kr_error(ENOENT) + && vctx->rrs_counters.matching_name_type == 0) { + /* no RRSIGs found */ + kr_rank_set(&entry->rank, KR_RANK_MISSING); + vctx->err_cnt += 1; + kr_request_set_extended_error(req, KNOT_EDNS_EDE_RRSIG_MISS, "JZAJ"); + log_bogus_rrsig(vctx, rr, "no valid RRSIGs found"); + } else { + kr_rank_set(&entry->rank, KR_RANK_BOGUS); + vctx->err_cnt += 1; + if (vctx->rrs_counters.expired > 0) + kr_request_set_extended_error(req, KNOT_EDNS_EDE_SIG_EXPIRED, "YFJ2"); + else if (vctx->rrs_counters.notyet > 0) + kr_request_set_extended_error(req, KNOT_EDNS_EDE_SIG_NOTYET, "UBBS"); + else + kr_request_set_extended_error(req, KNOT_EDNS_EDE_BOGUS, "I74V"); + log_bogus_rrsig(vctx, rr, "bogus signatures"); + } + } + return kr_ok(); +} + +static int validate_records(struct kr_request *req, knot_pkt_t *answer, knot_mm_t *pool, bool has_nsec3) +{ + struct kr_query *qry = req->current_query; + if (!qry->zone_cut.key) { + VERBOSE_MSG(qry, "<= no DNSKEY, can't validate\n"); + return kr_error(EBADMSG); + } + + kr_rrset_validation_ctx_t vctx = { + .pkt = answer, + .rrs = &req->answ_selected, + .section_id = KNOT_ANSWER, + .keys = qry->zone_cut.key, + .zone_name = qry->zone_cut.name, + .timestamp = qry->timestamp.tv_sec, + .ttl_min = req->ctx->cache.ttl_min, + .qry_uid = qry->uid, + .has_nsec3 = has_nsec3, + .flags = 0, + .err_cnt = 0, + .cname_norrsig_cnt = 0, + .result = 0, + .log_qry = qry, + }; + + int ret = validate_section(&vctx, qry, pool); + if (vctx.err_cnt && vctx.err_cnt == vctx.cname_norrsig_cnt) { + VERBOSE_MSG(qry, ">< all validation errors are missing RRSIGs on CNAMES, trying again in hope for DNAMEs\n"); + vctx.err_cnt = vctx.cname_norrsig_cnt = vctx.result = 0; + ret = validate_section(&vctx, qry, pool); + } + req->answ_validated = (vctx.err_cnt == 0); + if (ret != kr_ok()) { + return ret; + } + + uint32_t an_flags = vctx.flags; + vctx.rrs = &req->auth_selected; + vctx.section_id = KNOT_AUTHORITY; + vctx.flags = 0; + vctx.err_cnt = 0; + vctx.result = 0; + + ret = validate_section(&vctx, qry, pool); + req->auth_validated = (vctx.err_cnt == 0); + if (ret != kr_ok()) { + return ret; + } + + /* Records were validated. + * If there is wildcard expansion in answer, + * or optout - flag the query. + */ + if (an_flags & KR_DNSSEC_VFLG_WEXPAND) { + qry->flags.DNSSEC_WEXPAND = true; + } + if (an_flags & KR_DNSSEC_VFLG_OPTOUT) { + qry->flags.DNSSEC_OPTOUT = true; + } + + return ret; +} + +static int validate_keyset(struct kr_request *req, knot_pkt_t *answer, bool has_nsec3) +{ + /* Merge DNSKEY records from answer that are below/at current cut. */ + struct kr_query *qry = req->current_query; + bool updated_key = false; + const knot_pktsection_t *an = knot_pkt_section(answer, KNOT_ANSWER); + for (unsigned i = 0; i < an->count; ++i) { + const knot_rrset_t *rr = knot_pkt_rr(an, i); + if (rr->type != KNOT_RRTYPE_DNSKEY + || knot_dname_in_bailiwick(rr->owner, qry->zone_cut.name) < 0) { + continue; + } + /* Merge with zone cut (or replace ancestor key). */ + if (!qry->zone_cut.key || !knot_dname_is_equal(qry->zone_cut.key->owner, rr->owner)) { + qry->zone_cut.key = knot_rrset_copy(rr, qry->zone_cut.pool); + if (!qry->zone_cut.key) { + return kr_error(ENOMEM); + } + updated_key = true; + } else { + int ret = knot_rdataset_merge(&qry->zone_cut.key->rrs, + &rr->rrs, qry->zone_cut.pool); + if (ret != 0) { + knot_rrset_free(qry->zone_cut.key, qry->zone_cut.pool); + qry->zone_cut.key = NULL; + return ret; + } + updated_key = true; + } + } + + /* Check if there's a key for current TA. */ + if (updated_key && !(qry->flags.CACHED)) { + /* Find signatures for the DNSKEY; selected by iterator from ANSWER. */ + int sig_index = -1; + for (int i = req->answ_selected.len - 1; i >= 0; --i) { + const knot_rrset_t *rrsig = req->answ_selected.at[i]->rr; + const bool ok = req->answ_selected.at[i]->qry_uid == qry->uid + && rrsig->type == KNOT_RRTYPE_RRSIG + && knot_rrsig_type_covered(rrsig->rrs.rdata) + == KNOT_RRTYPE_DNSKEY + && rrsig->rclass == KNOT_CLASS_IN + && knot_dname_is_equal(rrsig->owner, + qry->zone_cut.key->owner); + if (ok) { + sig_index = i; + break; + } + } + if (sig_index < 0) { + kr_request_set_extended_error(req, KNOT_EDNS_EDE_RRSIG_MISS, "EZDC"); + return kr_error(ENOENT); + } + const knot_rdataset_t *sig_rds = &req->answ_selected.at[sig_index]->rr->rrs; + + kr_rrset_validation_ctx_t vctx = { + .pkt = answer, + .rrs = &req->answ_selected, + .section_id = KNOT_ANSWER, + .keys = qry->zone_cut.key, + .zone_name = qry->zone_cut.name, + .timestamp = qry->timestamp.tv_sec, + .ttl_min = req->ctx->cache.ttl_min, + .qry_uid = qry->uid, + .has_nsec3 = has_nsec3, + .flags = 0, + .result = 0, + .log_qry = qry, + }; + int ret = kr_dnskeys_trusted(&vctx, sig_rds, qry->zone_cut.trust_anchor); + /* Set rank of the RRSIG. This may be needed, but I don't know why. + * In particular, black_ent.rpl may get broken otherwise. */ + kr_rank_set(&req->answ_selected.at[sig_index]->rank, + ret == 0 ? KR_RANK_SECURE : KR_RANK_BOGUS); + + if (ret != 0) { + log_bogus_rrsig(&vctx, qry->zone_cut.key, "bogus key"); + knot_rrset_free(qry->zone_cut.key, qry->zone_cut.pool); + qry->zone_cut.key = NULL; + if (vctx.rrs_counters.expired > 0) + kr_request_set_extended_error(req, KNOT_EDNS_EDE_SIG_EXPIRED, "6GJV"); + else if (vctx.rrs_counters.notyet > 0) + kr_request_set_extended_error(req, KNOT_EDNS_EDE_SIG_NOTYET, "4DJQ"); + else + kr_request_set_extended_error(req, KNOT_EDNS_EDE_BOGUS, "EXRU"); + return ret; + } + + if (vctx.flags & KR_DNSSEC_VFLG_WEXPAND) { + qry->flags.DNSSEC_WEXPAND = true; + } + if (vctx.flags & KR_DNSSEC_VFLG_OPTOUT) { + qry->flags.DNSSEC_OPTOUT = true; + } + + } + return kr_ok(); +} + +static knot_rrset_t *update_ds(struct kr_zonecut *cut, const knot_pktsection_t *sec) +{ + /* Aggregate DS records (if using multiple keys) */ + knot_rrset_t *new_ds = NULL; + for (unsigned i = 0; i < sec->count; ++i) { + const knot_rrset_t *rr = knot_pkt_rr(sec, i); + if (rr->type != KNOT_RRTYPE_DS) { + continue; + } + int ret = 0; + if (new_ds) { + ret = knot_rdataset_merge(&new_ds->rrs, &rr->rrs, cut->pool); + } else { + new_ds = knot_rrset_copy(rr, cut->pool); + if (!new_ds) { + return NULL; + } + } + if (ret != 0) { + knot_rrset_free(new_ds, cut->pool); + return NULL; + } + } + return new_ds; +} + +static void mark_insecure_parents(const struct kr_query *qry) +{ + /* If there is a chain of DS queries mark all of them, + * then mark first non-DS parent. + * Stop if parent is waiting for ns address. + * NS can be located at unsigned zone, but still will return + * valid DNSSEC records for initial query. */ + struct kr_query *parent = qry->parent; + while (parent && !parent->flags.AWAIT_IPV4 && !parent->flags.AWAIT_IPV6) { + parent->flags.DNSSEC_WANT = false; + parent->flags.DNSSEC_INSECURE = true; + if (parent->stype != KNOT_RRTYPE_DS && + parent->stype != KNOT_RRTYPE_RRSIG) { + break; + } + parent = parent->parent; + } +} + +static int update_parent_keys(struct kr_request *req, uint16_t answer_type) +{ + struct kr_query *qry = req->current_query; + struct kr_query *parent = qry->parent; + if (kr_fails_assert(parent)) + return KR_STATE_FAIL; + switch(answer_type) { + case KNOT_RRTYPE_DNSKEY: + VERBOSE_MSG(qry, "<= parent: updating DNSKEY\n"); + parent->zone_cut.key = knot_rrset_copy(qry->zone_cut.key, parent->zone_cut.pool); + if (!parent->zone_cut.key) { + return KR_STATE_FAIL; + } + break; + case KNOT_RRTYPE_DS: + VERBOSE_MSG(qry, "<= parent: updating DS\n"); + if (qry->flags.DNSSEC_INSECURE) { /* DS non-existence proven. */ + mark_insecure_parents(qry); + } else if (qry->flags.DNSSEC_NODS && !qry->flags.FORWARD) { + if (qry->flags.DNSSEC_OPTOUT) { + mark_insecure_parents(qry); + } else { + int ret = kr_dnssec_matches_name_and_type(&req->auth_selected, qry->uid, + qry->sname, KNOT_RRTYPE_NS); + if (ret == kr_ok()) { + mark_insecure_parents(qry); + } + } + } else if (qry->flags.DNSSEC_NODS && qry->flags.FORWARD) { + int ret = kr_dnssec_matches_name_and_type(&req->auth_selected, qry->uid, + qry->sname, KNOT_RRTYPE_NS); + if (ret == kr_ok()) { + mark_insecure_parents(qry); + } + } else { /* DS existence proven. */ + parent->zone_cut.trust_anchor = knot_rrset_copy(qry->zone_cut.trust_anchor, parent->zone_cut.pool); + if (!parent->zone_cut.trust_anchor) { + return KR_STATE_FAIL; + } + } + break; + default: break; + } + return kr_ok(); +} + +static int update_delegation(struct kr_request *req, struct kr_query *qry, knot_pkt_t *answer, bool has_nsec3) +{ + struct kr_zonecut *cut = &qry->zone_cut; + + /* RFC4035 3.1.4. authoritative must send either DS or proof of non-existence. + * If it contains neither, resolver must query the parent for the DS (RFC4035 5.2.). + * If DS exists, the referral is OK, + * otherwise referral is bogus (or an attempted downgrade attack). + */ + + + unsigned section = KNOT_ANSWER; + const bool referral = !knot_wire_get_aa(answer->wire); + if (referral) { + section = KNOT_AUTHORITY; + } else if (knot_pkt_qtype(answer) == KNOT_RRTYPE_DS && + !(qry->flags.CNAME) && + (knot_wire_get_rcode(answer->wire) != KNOT_RCODE_NXDOMAIN)) { + section = KNOT_ANSWER; + } else { /* N/A */ + return kr_ok(); + } + + int ret = 0; + const knot_dname_t *proved_name = knot_pkt_qname(answer); + /* Aggregate DS records (if using multiple keys) */ + knot_rrset_t *new_ds = update_ds(cut, knot_pkt_section(answer, section)); + if (!new_ds) { + /* No DS provided, check for proof of non-existence. */ + if (!has_nsec3) { + if (referral) { + /* Check if it is referral to unsigned, rfc4035 5.2 */ + ret = kr_nsec_ref_to_unsigned(&req->auth_selected, + qry->uid, proved_name); + } else { + /* No-data answer */ + ret = kr_nsec_negative(&req->auth_selected, qry->uid, + proved_name, KNOT_RRTYPE_DS); + if (ret >= 0) { + if (ret == PKT_NODATA) { + ret = kr_ok(); + } else { + ret = kr_error(ENOENT); // suspicious + } + } + } + } else { + if (referral) { + /* Check if it is referral to unsigned, rfc5155 8.9 */ + ret = kr_nsec3_ref_to_unsigned(answer); + } else { + /* No-data answer, QTYPE is DS, rfc5155 8.6 */ + ret = kr_nsec3_no_data(answer, KNOT_AUTHORITY, proved_name, KNOT_RRTYPE_DS); + } + if (ret == kr_error(KNOT_ERANGE)) { + /* Not bogus, going insecure due to optout */ + ret = 0; + } + } + + if (referral && qry->stype != KNOT_RRTYPE_DS && + ret == DNSSEC_NOT_FOUND) { + /* referral, + * qtype is not KNOT_RRTYPE_DS, NSEC\NSEC3 were not found. + * Check if DS already was fetched. */ + knot_rrset_t *ta = cut->trust_anchor; + if (knot_dname_is_equal(cut->name, ta->owner)) { + /* DS is OK */ + ret = 0; + } + } else if (ret != 0) { + VERBOSE_MSG(qry, "<= bogus proof of DS non-existence\n"); + kr_request_set_extended_error(req, KNOT_EDNS_EDE_BOGUS, "Z4I6"); + qry->flags.DNSSEC_BOGUS = true; + } else if (proved_name[0] != '\0') { /* don't go to insecure for . DS */ + qry->flags.DNSSEC_NODS = true; + /* Rank the corresponding nonauth NS as insecure. */ + for (int i = 0; i < req->auth_selected.len; ++i) { + ranked_rr_array_entry_t *ns = req->auth_selected.at[i]; + if (ns->qry_uid != qry->uid + || !ns->rr + || ns->rr->type != KNOT_RRTYPE_NS) { + continue; + } + if (!referral && !knot_dname_is_equal(qry->sname, ns->rr->owner)) { + continue; + } + /* Found the record. Note: this is slightly fragile + * in case there were more NS records in the packet. + * As it is now for referrals, kr_nsec*_ref_to_unsigned consider + * (only) the first NS record in the packet. */ + if (!kr_rank_test(ns->rank, KR_RANK_AUTH)) { /* sanity */ + ns->rank = KR_RANK_INSECURE; + } + break; + } + } + return ret; + } else if (qry->flags.FORWARD && qry->parent) { + struct kr_query *parent = qry->parent; + parent->zone_cut.name = knot_dname_copy(qry->sname, parent->zone_cut.pool); + } + + /* Extend trust anchor */ + VERBOSE_MSG(qry, "<= DS: OK\n"); + cut->trust_anchor = new_ds; + return ret; +} + +static const knot_dname_t *find_first_signer(ranked_rr_array_t *arr, struct kr_query *qry) +{ + for (size_t i = 0; i < arr->len; ++i) { + ranked_rr_array_entry_t *entry = arr->at[i]; + const knot_rrset_t *rr = entry->rr; + if (entry->yielded || + (!kr_rank_test(entry->rank, KR_RANK_INITIAL) && + !kr_rank_test(entry->rank, KR_RANK_TRY) && + !kr_rank_test(entry->rank, KR_RANK_MISMATCH))) { + continue; + } + if (rr->type != KNOT_RRTYPE_RRSIG) { + continue; + } + const knot_dname_t *signame = knot_rrsig_signer_name(rr->rrs.rdata); + if (knot_dname_in_bailiwick(rr->owner, signame) >= 0) { + return signame; + } else { + /* otherwise it's some nonsense, so we skip it */ + kr_log_q(qry, VALIDATOR, "protocol violation: " + "out-of-bailiwick RRSIG signer, skipping\n"); + } + } + return NULL; +} + +static const knot_dname_t *signature_authority(struct kr_request *req) +{ + const knot_dname_t *signer_name = find_first_signer(&req->answ_selected, req->current_query); + if (!signer_name) { + signer_name = find_first_signer(&req->auth_selected, req->current_query); + } + return signer_name; +} + +static int rrsig_not_found(const kr_layer_t * const ctx, const knot_pkt_t * const pkt, + const knot_rrset_t * const rr) +{ + /* Signatures are missing. There might be a zone cut that we've skipped + * and transitions to insecure. That can commonly happen when iterating + * and both sides of that cut are served by the same IP address(es). + * We'll try proving that the name truly is insecure - by spawning + * a DS sub-query on a suitable QNAME. + */ + struct kr_request * const req = ctx->req; + struct kr_query * const qry = req->current_query; + + if (qry->flags.FORWARD || qry->flags.STUB) { + /* Undiscovered signed cuts can't happen in the current forwarding + * algorithm, so this function shouldn't be able to help. */ + return KR_STATE_FAIL; + } + + /* Find cut_next: the name at which to try finding the "missing" zone cut. */ + const knot_dname_t * const cut_top = qry->zone_cut.name; + const int next_depth = knot_dname_in_bailiwick(rr->owner, cut_top); + if (next_depth <= 0) { + return KR_STATE_FAIL; // shouldn't happen, I think + } + /* Add one extra label to cur_top, i.e. descend one level below current zone cut */ + const knot_dname_t * const cut_next = rr->owner + + knot_dname_prefixlen(rr->owner, next_depth - 1, NULL); + + /* Spawn that DS sub-query. */ + struct kr_query * const next = kr_rplan_push(&req->rplan, qry, cut_next, + rr->rclass, KNOT_RRTYPE_DS); + if (!next) { + return KR_STATE_FAIL; + } + kr_zonecut_init(&next->zone_cut, qry->zone_cut.name, &req->pool); + kr_zonecut_copy(&next->zone_cut, &qry->zone_cut); + kr_zonecut_copy_trust(&next->zone_cut, &qry->zone_cut); + next->flags.DNSSEC_WANT = true; + return KR_STATE_YIELD; +} + +static int check_validation_result(kr_layer_t *ctx, const knot_pkt_t *pkt, ranked_rr_array_t *arr) +{ + int ret = KR_STATE_DONE; + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + ranked_rr_array_entry_t *invalid_entry = NULL; + for (size_t i = 0; i < arr->len; ++i) { + ranked_rr_array_entry_t *entry = arr->at[i]; + if (entry->yielded || entry->qry_uid != qry->uid) { + continue; + } + if (kr_rank_test(entry->rank, KR_RANK_MISMATCH)) { + invalid_entry = entry; + break; + } else if (kr_rank_test(entry->rank, KR_RANK_MISSING) && + !invalid_entry) { + invalid_entry = entry; + } else if (kr_rank_test(entry->rank, KR_RANK_OMIT)) { + continue; + } else if (!kr_rank_test(entry->rank, KR_RANK_SECURE) && + !invalid_entry) { + invalid_entry = entry; + } + } + + if (!invalid_entry) { + return ret; + } + + if (!kr_rank_test(invalid_entry->rank, KR_RANK_SECURE) && + (++(invalid_entry->revalidation_cnt) > MAX_REVALIDATION_CNT)) { + VERBOSE_MSG(qry, "<= continuous revalidation, fails\n"); + kr_request_set_extended_error(req, KNOT_EDNS_EDE_OTHER, + "4T4L: continuous revalidation"); + qry->flags.DNSSEC_BOGUS = true; + return KR_STATE_FAIL; + } + + const knot_rrset_t *rr = invalid_entry->rr; + if (kr_rank_test(invalid_entry->rank, KR_RANK_MISMATCH)) { + const knot_dname_t *signer_name = knot_rrsig_signer_name(rr->rrs.rdata); + if (knot_dname_in_bailiwick(signer_name, qry->zone_cut.name) > 0) { + qry->zone_cut.name = knot_dname_copy(signer_name, &req->pool); + qry->flags.AWAIT_CUT = true; + } else if (!knot_dname_is_equal(signer_name, qry->zone_cut.name)) { + if (qry->zone_cut.parent) { + memcpy(&qry->zone_cut, qry->zone_cut.parent, sizeof(qry->zone_cut)); + } else { + qry->flags.AWAIT_CUT = true; + } + qry->zone_cut.name = knot_dname_copy(signer_name, &req->pool); + } + VERBOSE_MSG(qry, ">< cut changed (new signer), needs revalidation\n"); + ret = KR_STATE_YIELD; + } else if (kr_rank_test(invalid_entry->rank, KR_RANK_MISSING)) { + ret = rrsig_not_found(ctx, pkt, rr); + } else if (!kr_rank_test(invalid_entry->rank, KR_RANK_SECURE)) { + kr_request_set_extended_error(req, KNOT_EDNS_EDE_BOGUS, "NXJA"); + qry->flags.DNSSEC_BOGUS = true; + ret = KR_STATE_FAIL; + } + + return ret; +} + +static bool check_empty_answer(kr_layer_t *ctx, knot_pkt_t *pkt) +{ + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + ranked_rr_array_t *arr = &req->answ_selected; + size_t num_entries = 0; + for (size_t i = 0; i < arr->len; ++i) { + ranked_rr_array_entry_t *entry = arr->at[i]; + const knot_rrset_t *rr = entry->rr; + if (rr->type == KNOT_RRTYPE_RRSIG && qry->stype != KNOT_RRTYPE_RRSIG) { + continue; + } + if (entry->qry_uid == qry->uid) { + ++num_entries; + } + } + const knot_pktsection_t *an = knot_pkt_section(pkt, KNOT_ANSWER); + return ((an->count != 0) && (num_entries == 0)) ? false : true; +} + +static int unsigned_forward(kr_layer_t *ctx, knot_pkt_t *pkt) +{ + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + const uint16_t qtype = knot_pkt_qtype(pkt); + const uint8_t pkt_rcode = knot_wire_get_rcode(pkt->wire); + bool nods = false; + bool ns_exist = true; + for (int i = 0; i < req->rplan.resolved.len; ++i) { + struct kr_query *q = req->rplan.resolved.at[i]; + if (q->sclass == qry->sclass && + q->stype == KNOT_RRTYPE_DS && + knot_dname_is_equal(q->sname, qry->sname)) { + nods = true; + if (!(q->flags.DNSSEC_OPTOUT)) { + int ret = kr_dnssec_matches_name_and_type(&req->auth_selected, q->uid, + qry->sname, KNOT_RRTYPE_NS); + ns_exist = (ret == kr_ok()); + } + } + } + + if (nods && ns_exist && qtype == KNOT_RRTYPE_NS) { + qry->flags.DNSSEC_WANT = false; + qry->flags.DNSSEC_INSECURE = true; + if (qry->forward_flags.CNAME) { + if (kr_fails_assert(qry->cname_parent)) + return KR_STATE_FAIL; + qry->cname_parent->flags.DNSSEC_WANT = false; + qry->cname_parent->flags.DNSSEC_INSECURE = true; + } else if (pkt_rcode == KNOT_RCODE_NOERROR && qry->parent != NULL) { + const knot_pktsection_t *sec = knot_pkt_section(pkt, KNOT_ANSWER); + const knot_rrset_t *rr = knot_pkt_rr(sec, 0); + if (rr->type == KNOT_RRTYPE_NS) { + qry->parent->zone_cut.name = knot_dname_copy(rr->owner, &req->pool); + qry->parent->flags.DNSSEC_WANT = false; + qry->parent->flags.DNSSEC_INSECURE = true; + } + } + while (qry->parent) { + qry = qry->parent; + qry->flags.DNSSEC_WANT = false; + qry->flags.DNSSEC_INSECURE = true; + if (qry->forward_flags.CNAME) { + if (kr_fails_assert(qry->cname_parent)) + return KR_STATE_FAIL; + qry->cname_parent->flags.DNSSEC_WANT = false; + qry->cname_parent->flags.DNSSEC_INSECURE = true; + } + } + return KR_STATE_DONE; + } + + if (ctx->state == KR_STATE_YIELD) { + return KR_STATE_DONE; + } + + if (!nods && qtype != KNOT_RRTYPE_DS) { + struct kr_rplan *rplan = &req->rplan; + struct kr_query *next = kr_rplan_push(rplan, qry, qry->sname, qry->sclass, KNOT_RRTYPE_DS); + if (!next) { + return KR_STATE_FAIL; + } + kr_zonecut_set(&next->zone_cut, qry->zone_cut.name); + kr_zonecut_copy_trust(&next->zone_cut, &qry->zone_cut); + next->flags.DNSSEC_WANT = true; + } + + return KR_STATE_YIELD; +} + +static int check_signer(kr_layer_t *ctx, knot_pkt_t *pkt) +{ + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + const knot_dname_t *ta_name = qry->zone_cut.trust_anchor ? qry->zone_cut.trust_anchor->owner : NULL; + const knot_dname_t *signer = signature_authority(req); + if (ta_name && (!signer || !knot_dname_is_equal(ta_name, signer))) { + /* check all newly added RRSIGs */ + if (!signer) { + if (qry->flags.FORWARD) { + return unsigned_forward(ctx, pkt); + } + /* Not a DNSSEC-signed response. */ + if (ctx->state == KR_STATE_YIELD) { + /* Already yielded for revalidation. + * It means that trust chain is OK and + * transition to INSECURE hasn't occurred. + * Let the validation logic ask about RRSIG. */ + return KR_STATE_DONE; + } + /* Ask parent for DS + * to prove transition to INSECURE. */ + const uint16_t qtype = knot_pkt_qtype(pkt); + const knot_dname_t *qname = knot_pkt_qname(pkt); + if (qtype == KNOT_RRTYPE_NS && + knot_dname_in_bailiwick(qname, qry->zone_cut.name) > 0) { + /* Server is authoritative + * for both parent and child, + * and child zone is not signed. */ + qry->zone_cut.name = knot_dname_copy(qname, &req->pool); + } + } else if (knot_dname_in_bailiwick(signer, qry->zone_cut.name) > 0) { + if (!(qry->flags.FORWARD)) { + /* Key signer is below current cut, advance and refetch keys. */ + qry->zone_cut.name = knot_dname_copy(signer, &req->pool); + } else { + /* Check if DS does not exist. */ + struct kr_query *q = kr_rplan_find_resolved(&req->rplan, NULL, + signer, qry->sclass, KNOT_RRTYPE_DS); + if (q && q->flags.DNSSEC_NODS) { + qry->flags.DNSSEC_WANT = false; + qry->flags.DNSSEC_INSECURE = true; + if (qry->parent) { + qry->parent->flags.DNSSEC_WANT = false; + qry->parent->flags.DNSSEC_INSECURE = true; + } + } else if (qry->stype != KNOT_RRTYPE_DS) { + struct kr_rplan *rplan = &req->rplan; + struct kr_query *next = kr_rplan_push(rplan, qry, qry->sname, + qry->sclass, KNOT_RRTYPE_DS); + if (!next) { + return KR_STATE_FAIL; + } + kr_zonecut_set(&next->zone_cut, qry->zone_cut.name); + kr_zonecut_copy_trust(&next->zone_cut, &qry->zone_cut); + next->flags.DNSSEC_WANT = true; + } + } + } else if (!knot_dname_is_equal(signer, qry->zone_cut.name)) { + /* Key signer is above the current cut, so we can't validate it. This happens when + a server is authoritative for both grandparent, parent and child zone. + Ascend to parent cut, and refetch authority for signer. */ + if (qry->zone_cut.parent) { + memcpy(&qry->zone_cut, qry->zone_cut.parent, sizeof(qry->zone_cut)); + } else { + qry->flags.AWAIT_CUT = true; + } + qry->zone_cut.name = knot_dname_copy(signer, &req->pool); + } + + /* zone cut matches, but DS/DNSKEY doesn't => refetch. */ + VERBOSE_MSG(qry, ">< cut changed, needs revalidation\n"); + if ((qry->flags.FORWARD) && qry->stype != KNOT_RRTYPE_DS) { + struct kr_rplan *rplan = &req->rplan; + struct kr_query *next = kr_rplan_push(rplan, qry, signer, + qry->sclass, KNOT_RRTYPE_DS); + if (!next) { + return KR_STATE_FAIL; + } + kr_zonecut_set(&next->zone_cut, qry->zone_cut.name); + kr_zonecut_copy_trust(&next->zone_cut, &qry->zone_cut); + next->flags.DNSSEC_WANT = true; + return KR_STATE_YIELD; + } + if (!(qry->flags.FORWARD)) { + return KR_STATE_YIELD; + } + } + return KR_STATE_DONE; +} + +/** Change ranks of RRs from this single iteration: + * _INITIAL or _TRY or _MISSING -> rank_to_set. Or any rank, if any_rank == true. + * + * Optionally do this only in a `bailiwick` (if not NULL). + * Iterator shouldn't have selected such records, but we check to be sure. */ +static void rank_records(struct kr_query *qry, bool any_rank, enum kr_rank rank_to_set, + const knot_dname_t *bailiwick) +{ + struct kr_request *req = qry->request; + ranked_rr_array_t *ptrs[2] = { &req->answ_selected, &req->auth_selected }; + for (size_t i = 0; i < 2; ++i) { + ranked_rr_array_t *arr = ptrs[i]; + for (size_t j = 0; j < arr->len; ++j) { + ranked_rr_array_entry_t *entry = arr->at[j]; + if (entry->qry_uid != qry->uid) { + continue; + } + if (bailiwick && knot_dname_in_bailiwick(entry->rr->owner, + bailiwick) < 0) { + continue; + } + if (any_rank + || kr_rank_test(entry->rank, KR_RANK_INITIAL) + || kr_rank_test(entry->rank, KR_RANK_TRY) + || kr_rank_test(entry->rank, KR_RANK_MISSING)) { + kr_rank_set(&entry->rank, rank_to_set); + } + } + } +} + +static void check_wildcard(kr_layer_t *ctx) +{ + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + ranked_rr_array_t *ptrs[2] = { &req->answ_selected, &req->auth_selected }; + + for (int i = 0; i < 2; ++i) { + ranked_rr_array_t *arr = ptrs[i]; + for (ssize_t j = 0; j < arr->len; ++j) { + ranked_rr_array_entry_t *entry = arr->at[j]; + const knot_rrset_t *rrsigs = entry->rr; + + if (qry->uid != entry->qry_uid) { + continue; + } + + if (rrsigs->type != KNOT_RRTYPE_RRSIG) { + continue; + } + + int owner_labels = knot_dname_labels(rrsigs->owner, NULL); + + knot_rdata_t *rdata_k = rrsigs->rrs.rdata; + for (int k = 0; k < rrsigs->rrs.count; + ++k, rdata_k = knot_rdataset_next(rdata_k)) { + if (knot_rrsig_labels(rdata_k) != owner_labels) { + qry->flags.DNSSEC_WEXPAND = true; + } + } + } + } +} + +/** Just for wildcard_adjust_to_wire() */ +static bool rr_is_for_wildcard(const ranked_rr_array_entry_t *entry) +{ + switch (kr_rrset_type_maysig(entry->rr)) { + case KNOT_RRTYPE_NSEC: + case KNOT_RRTYPE_NSEC3: + return true; + default: + return false; + } +} +/** In case of wildcard expansion, mark required authority RRs by to_wire. */ +static int wildcard_adjust_to_wire(struct kr_request *req, const struct kr_query *qry) +{ + if (!qry->parent && qry->flags.DNSSEC_WEXPAND) { + return kr_ranked_rrarray_set_wire(&req->auth_selected, true, + qry->uid, true, &rr_is_for_wildcard); + } + return kr_ok(); +} + +static int validate(kr_layer_t *ctx, knot_pkt_t *pkt) +{ + int ret = 0; + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + + /* Ignore faulty or unprocessed responses. */ + if (ctx->state & (KR_STATE_FAIL|KR_STATE_CONSUME)) { + return ctx->state; + } + + /* Pass-through if user doesn't want secure answer or stub. */ + if (qry->flags.STUB) { + rank_records(qry, false, KR_RANK_OMIT, NULL); + return ctx->state; + } + uint8_t pkt_rcode = knot_wire_get_rcode(pkt->wire); + if ((qry->flags.FORWARD) && + pkt_rcode != KNOT_RCODE_NOERROR && + pkt_rcode != KNOT_RCODE_NXDOMAIN) { + do { + qry->flags.DNSSEC_BOGUS = true; + if (qry->cname_parent) { + qry->cname_parent->flags.DNSSEC_BOGUS = true; + } + qry = qry->parent; + } while (qry); + ctx->state = KR_STATE_DONE; + return ctx->state; + } + + if (!(qry->flags.DNSSEC_WANT)) { + const bool is_insec = qry->flags.CACHED && qry->flags.DNSSEC_INSECURE; + if ((qry->flags.DNSSEC_INSECURE)) { + rank_records(qry, true, KR_RANK_INSECURE, qry->zone_cut.name); + } + if (is_insec && qry->parent != NULL) { + /* We have got insecure answer from cache. + * Mark parent(s) as insecure. */ + mark_insecure_parents(qry); + VERBOSE_MSG(qry, "<= cached insecure response, going insecure\n"); + ctx->state = KR_STATE_DONE; + } else if (ctx->state == KR_STATE_YIELD) { + /* Transition to insecure state + occurred during revalidation. + if state remains YIELD, answer will not be cached. + Let cache layers to work. */ + ctx->state = KR_STATE_DONE; + } + return ctx->state; + } + + /* Pass-through if CD bit is set. */ + if (knot_wire_get_cd(req->qsource.packet->wire)) { + check_wildcard(ctx); + wildcard_adjust_to_wire(req, qry); + rank_records(qry, false, KR_RANK_OMIT, NULL); + return ctx->state; + } + /* Answer for RRSIG may not set DO=1, but all records MUST still validate. */ + bool use_signatures = (knot_pkt_qtype(pkt) != KNOT_RRTYPE_RRSIG); + if (!(qry->flags.CACHED) && !knot_pkt_has_dnssec(pkt) && !use_signatures) { + VERBOSE_MSG(qry, "<= got insecure response\n"); + kr_request_set_extended_error(req, KNOT_EDNS_EDE_BOGUS, "MISQ"); + qry->flags.DNSSEC_BOGUS = true; + return KR_STATE_FAIL; + } + + /* Check if this is a DNSKEY answer, check trust chain and store. */ + uint16_t qtype = knot_pkt_qtype(pkt); + bool has_nsec3 = pkt_has_type(pkt, KNOT_RRTYPE_NSEC3); + const knot_pktsection_t *an = knot_pkt_section(pkt, KNOT_ANSWER); + const bool referral = (an->count == 0 && !knot_wire_get_aa(pkt->wire)); + + if (!(qry->flags.CACHED) && knot_wire_get_aa(pkt->wire)) { + /* Check if answer if not empty, + * but iterator has not selected any records. */ + if (!check_empty_answer(ctx, pkt)) { + VERBOSE_MSG(qry, "<= no useful RR in authoritative answer\n"); + kr_request_set_extended_error(req, KNOT_EDNS_EDE_BOGUS, "MJX6"); + qry->flags.DNSSEC_BOGUS = true; + return KR_STATE_FAIL; + } + /* Track difference between current TA and signer name. + * This indicates that the NS is auth for both parent-child, + * and we must update DS/DNSKEY to validate it. + */ + ret = check_signer(ctx, pkt); + if (ret != KR_STATE_DONE) { + return ret; + } + if (qry->flags.FORWARD && qry->flags.DNSSEC_INSECURE) { + return KR_STATE_DONE; + } + } + + if (knot_wire_get_aa(pkt->wire) && qtype == KNOT_RRTYPE_DNSKEY) { + const knot_rrset_t *ds = qry->zone_cut.trust_anchor; + if (ds && !kr_ds_algo_support(ds)) { + VERBOSE_MSG(qry, ">< all DS entries use unsupported algorithm pairs, going insecure\n"); + /* ^ the message is a bit imprecise to avoid being too verbose */ + kr_request_set_extended_error(req, KNOT_EDNS_EDE_OTHER, "LSLC: unsupported digest/key"); + qry->flags.DNSSEC_WANT = false; + qry->flags.DNSSEC_INSECURE = true; + rank_records(qry, true, KR_RANK_INSECURE, qry->zone_cut.name); + mark_insecure_parents(qry); + return KR_STATE_DONE; + } + + ret = validate_keyset(req, pkt, has_nsec3); + if (ret == kr_error(EAGAIN)) { + VERBOSE_MSG(qry, ">< cut changed, needs revalidation\n"); + return KR_STATE_YIELD; + } else if (ret != 0) { + VERBOSE_MSG(qry, "<= bad keys, broken trust chain\n"); + /* EDE code already set in validate_keyset() */ + qry->flags.DNSSEC_BOGUS = true; + return KR_STATE_FAIL; + } + } + + /* Validate all records, fail as bogus if it doesn't match. + * Do not revalidate data from cache, as it's already trusted. + * TTLs of RRsets may get lowered. */ + if (!(qry->flags.CACHED)) { + ret = validate_records(req, pkt, req->rplan.pool, has_nsec3); + if (ret == KNOT_EDOWNGRADED) { + return KR_STATE_DONE; + } else if (ret != 0) { + /* something exceptional - no DNS key, empty pointers etc + * normally it shouldn't happen */ + VERBOSE_MSG(qry, "<= couldn't validate RRSIGs\n"); + kr_request_set_extended_error(req, KNOT_EDNS_EDE_OTHER, + "O4TP: couldn't validate RRSIGs"); + qry->flags.DNSSEC_BOGUS = true; + return KR_STATE_FAIL; + } + /* check validation state and spawn subrequests */ + if (!req->answ_validated) { + ret = check_validation_result(ctx, pkt, &req->answ_selected); + if (ret != KR_STATE_DONE) { + return ret; + } + } + if (!req->auth_validated) { + ret = check_validation_result(ctx, pkt, &req->auth_selected); + if (ret != KR_STATE_DONE) { + return ret; + } + } + } + + /* Validate non-existence proof if not positive answer. + * In case of CNAME, iterator scheduled a sibling query for the target, + * so we just drop the negative piece of information and don't try to prove it. + * TODO: not ideal; with aggressive cache we'll at least avoid the extra packet. */ + if (!qry->flags.CACHED && pkt_rcode == KNOT_RCODE_NXDOMAIN && !qry->flags.CNAME) { + /* @todo If knot_pkt_qname(pkt) is used instead of qry->sname then the tests crash. */ + if (!has_nsec3) { + ret = kr_nsec_negative(&req->auth_selected, qry->uid, + qry->sname, KNOT_RRTYPE_NULL); + if (ret >= 0) { + if (ret & PKT_NXDOMAIN) { + ret = kr_ok(); + } else { + ret = kr_error(ENOENT); // probably proved NODATA + } + } + } else { + ret = kr_nsec3_name_error_response_check(pkt, KNOT_AUTHORITY, qry->sname); + } + if (has_nsec3 && (ret == kr_error(KNOT_ERANGE))) { + /* NXDOMAIN proof is OK, + * but NSEC3 that covers next closer name + * (or wildcard at next closer name) has opt-out flag. + * RFC5155 9.2; AD flag can not be set */ + qry->flags.DNSSEC_OPTOUT = true; + VERBOSE_MSG(qry, "<= can't prove NXDOMAIN due to optout, going insecure\n"); + } else if (ret != 0) { + VERBOSE_MSG(qry, "<= bad NXDOMAIN proof\n"); + kr_request_set_extended_error(req, KNOT_EDNS_EDE_NSEC_MISS, "3WKM"); + qry->flags.DNSSEC_BOGUS = true; + return KR_STATE_FAIL; + } + } + + /* @todo WTH, this needs API that just tries to find a proof and the caller + * doesn't have to worry about NSEC/NSEC3 + * @todo rework this + * CNAME: same as the NXDOMAIN case above */ + if (!qry->flags.CACHED && pkt_rcode == KNOT_RCODE_NOERROR && !qry->flags.CNAME) { + bool no_data = (an->count == 0 && knot_wire_get_aa(pkt->wire)); + if (no_data) { + /* @todo + * ? quick mechanism to determine which check to preform first + * ? merge the functionality together to share code/resources + */ + if (!has_nsec3) { + ret = kr_nsec_negative(&req->auth_selected, qry->uid, + knot_pkt_qname(pkt), knot_pkt_qtype(pkt)); + if (ret >= 0) { + if (ret == PKT_NODATA) { + ret = kr_ok(); + } else { + ret = kr_error(ENOENT); // suspicious + } + } + } else { + ret = kr_nsec3_no_data(pkt, KNOT_AUTHORITY, knot_pkt_qname(pkt), knot_pkt_qtype(pkt)); + } + if (ret != 0) { + if (has_nsec3 && (ret == kr_error(KNOT_ERANGE))) { + VERBOSE_MSG(qry, "<= can't prove NODATA due to optout, going insecure\n"); + qry->flags.DNSSEC_OPTOUT = true; + /* Could not return from here, + * we must continue, validate NSEC\NSEC3 and + * call update_parent_keys() to mark + * parent queries as insecure */ + } else { + VERBOSE_MSG(qry, "<= bad NODATA proof\n"); + kr_request_set_extended_error(req, KNOT_EDNS_EDE_NSEC_MISS, "AHXI"); + qry->flags.DNSSEC_BOGUS = true; + return KR_STATE_FAIL; + } + } + } + } + + wildcard_adjust_to_wire(req, qry); + + /* Check and update current delegation point security status. */ + ret = update_delegation(req, qry, pkt, has_nsec3); + if (ret == DNSSEC_NOT_FOUND && qry->stype != KNOT_RRTYPE_DS) { + if (ctx->state == KR_STATE_YIELD) { + VERBOSE_MSG(qry, "<= can't validate referral\n"); + kr_request_set_extended_error(req, KNOT_EDNS_EDE_BOGUS, "XLE4"); + qry->flags.DNSSEC_BOGUS = true; + return KR_STATE_FAIL; + } else { + /* Check the trust chain and query DS\DNSKEY if needed. */ + VERBOSE_MSG(qry, "<= DS\\NSEC was not found, querying for DS\n"); + return KR_STATE_YIELD; + } + } else if (ret != 0) { + return KR_STATE_FAIL; + } else if (pkt_rcode == KNOT_RCODE_NOERROR && + referral && + ((!qry->flags.DNSSEC_WANT && qry->flags.DNSSEC_INSECURE) || + (qry->flags.DNSSEC_NODS))) { + /* referral with proven DS non-existence */ + qtype = KNOT_RRTYPE_DS; + } + /* Update parent query zone cut */ + if (qry->parent) { + if (update_parent_keys(req, qtype) != 0) { + return KR_STATE_FAIL; + } + } + + if (qry->flags.FORWARD && qry->parent) { + if (pkt_rcode == KNOT_RCODE_NXDOMAIN) { + qry->parent->forward_flags.NO_MINIMIZE = true; + } + } + VERBOSE_MSG(qry, "<= answer valid, OK\n"); + return KR_STATE_DONE; +} + +/** Hide RRsets which did not validate from clients. */ +static int hide_bogus(kr_layer_t *ctx) { + if (knot_wire_get_cd(ctx->req->qsource.packet->wire)) { + return ctx->state; + } + /* We don't want to send bogus answers to clients, not even in SERVFAIL + * answers, but we cannot drop whole sections. If a CNAME chain + * SERVFAILs somewhere, the steps that were OK should be put into + * answer. + * + * There is one specific issue: currently we follow CNAME *before* + * we validate it, because... iterator comes before validator. + * Therefore some rrsets might be added into req->*_selected before + * we detected failure in validator. + * TODO: better approach, probably during work on parallel queries. + */ + const ranked_rr_array_t *sel[] = kr_request_selected(ctx->req); + for (knot_section_t sect = KNOT_ANSWER; sect <= KNOT_ADDITIONAL; ++sect) { + for (size_t i = 0; i < sel[sect]->len; ++i) { + ranked_rr_array_entry_t *e = sel[sect]->at[i]; + e->to_wire = e->to_wire + && !kr_rank_test(e->rank, KR_RANK_INDET) + && !kr_rank_test(e->rank, KR_RANK_BOGUS) + && !kr_rank_test(e->rank, KR_RANK_MISMATCH) + && !kr_rank_test(e->rank, KR_RANK_MISSING); + } + } + return ctx->state; +} + +static int validate_wrapper(kr_layer_t *ctx, knot_pkt_t *pkt) { + // Wrapper for now. + int ret = validate(ctx, pkt); + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + if (ret & KR_STATE_FAIL && qry->flags.DNSSEC_BOGUS) + qry->server_selection.error(qry, req->upstream.transport, KR_SELECTION_DNSSEC_ERROR); + if (ret & KR_STATE_DONE && !qry->flags.DNSSEC_BOGUS) { + /* Don't report extended DNS errors related to validation + * when it managed to succeed (e.g. by trying different auth). */ + switch (req->extended_error.info_code) { + case KNOT_EDNS_EDE_BOGUS: + case KNOT_EDNS_EDE_NSEC_MISS: + case KNOT_EDNS_EDE_RRSIG_MISS: + case KNOT_EDNS_EDE_SIG_EXPIRED: + case KNOT_EDNS_EDE_SIG_NOTYET: + kr_request_set_extended_error(req, KNOT_EDNS_EDE_NONE, NULL); + break; + case KNOT_EDNS_EDE_DNSKEY_MISS: + case KNOT_EDNS_EDE_DNSKEY_BIT: + kr_assert(false); /* These EDE codes aren't used. */ + break; + default: break; /* Remaining codes don't indicate hard DNSSEC failure. */ + } + } + return ret; +} + + +/** Module implementation. */ +int validate_init(struct kr_module *self) +{ + static const kr_layer_api_t layer = { + .consume = &validate_wrapper, + .answer_finalize = &hide_bogus, + }; + self->layer = &layer; + return kr_ok(); +} + +KR_MODULE_EXPORT(validate) /* useless for builtin module, but let's be consistent */ + +#undef VERBOSE_MSG diff --git a/lib/layer/validate.test.integr/deckard.yaml b/lib/layer/validate.test.integr/deckard.yaml new file mode 100644 index 0000000..aac7b20 --- /dev/null +++ b/lib/layer/validate.test.integr/deckard.yaml @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +programs: +- name: kresd + binary: kresd + additional: + - -n + templates: + - lib/layer/validate.test.integr/kresd_config.j2 + configs: + - config diff --git a/lib/layer/validate.test.integr/fwd_insecure_but_rrsig_signer_invalid.rpl b/lib/layer/validate.test.integr/fwd_insecure_but_rrsig_signer_invalid.rpl new file mode 100644 index 0000000..3cc0968 --- /dev/null +++ b/lib/layer/validate.test.integr/fwd_insecure_but_rrsig_signer_invalid.rpl @@ -0,0 +1,294 @@ +; SPDX-License-Identifier: GPL-3.0-or-later + val-override-date: "20200722144207" + trust-anchor: ". IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D" +CONFIG_END + +SCENARIO_BEGIN Forwarding: forwarder sent RRSIGs with invalid signed for an insecure zone, it should not cause SERVFAIL because the zone is insecure + + +; forwarder +RANGE_BEGIN 0 100 + ADDRESS 8.8.8.8 + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA AD CD NOERROR +SECTION QUESTION +. IN DNSKEY +SECTION ANSWER +. 86913 IN DNSKEY 256 3 8 AwEAAdauOGxLhfAKFTTZwGhBXbk793QK dWIQRjiSftWdusCwkPhNyJrIjwtNffCW XGLlZAbpcs414RE3oS1qVwV+AdXsO92S Bu5haGlxMUk0NqZO7Xlf84/wrzGZVRRo uPo5pNX/CKS8Mv9UOi0olKGCu31dNfh8 qCszWZcloLDgeLzSnQSkvFoGe69vNCfh 7feESKedkBC2qRz0BZv9+oJI0IY/3D7W EnV0NOlf8gSHozhfJFJ/ZAKtvw/Q3ogr VJFk0LyVaU/NVtVA5FM4pVMIRID7pfrP i78aAzG7b/Wh/Pce4jPAIpS3dApq25Yk vMuPvfB91NMf9FemKwlp78PBVcM= +. 86913 IN DNSKEY 257 3 8 AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexT BAvkMgJzkKTOiW1vkIbzxeF3+/4RgWOq 7HrxRixHlFlExOLAJr5emLvN7SWXgnLh 4+B5xQlNVz8Og8kvArMtNROxVQuCaSnI DdD5LKyWbRd2n9WGe2R8PzgCmr3EgVLr jyBxWezF0jLHwVN8efS3rCj/EWgvIWgb 9tarpVUDK/b58Da+sqqls3eNbuv7pr+e oZG+SrDK6nWeL3c6H5Apxz7LjVc1uTId sIXxuOLYA4/ilBmSVIzuDWfdRUfhHdY6 +cn8HFRm+2hM8AnXGXws9555KrUB5qih ylGa8subX2Nn6UwNR1AkUTV74bU= +. 86913 IN RRSIG DNSKEY 8 0 172800 20200811000000 20200721000000 20326 . IcMH/yNRoNKkCPmOo8MDcMEZO4sF8p0A 8xgASRnD1c0t+VSU5NRzh05eME7RJrRP 31T/E4eUh+jyI18Gz/O5Lg02Zu1wmcOy Mnkr+bfU+Al7pCztj+6aGTUl34HFyWtM cChKkeJQDeJoBtyVDVa4oL1FQs4Ml6HC OOjzoOKIHakrfCLyaktN82G+uNFXt0CB SGR2xQSWDKnzqSJqCep9X8NtNjjAFus2 g8weAXomG2+gRlrNfQAFqcGPjLHeVtZv yco3u0u8ZOp+8PC8fnlLhtpJ3DBXgwFp wY3V7uM7Zfabcio64st79wu4zNwZR5uf IEpKciMtUh8J8LfVWFM62w== +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA AD CD NXDOMAIN +SECTION QUESTION +_TA-4f66. IN NULL +SECTION AUTHORITY +. 86161 IN NSEC aaa. NS SOA RRSIG NSEC DNSKEY +. 86161 IN RRSIG NSEC 8 0 86400 20200804050000 20200722040000 46594 . zNoDySatAPoT5YV6XmQcz3qs60+GxuEo yITrzrWMEU1keQNU+6663BM6N/F6G2nd b5Bj5cY5m7MN7iZnBFN/cXQd7EWjdLTC Fw7FxBPc3J51vqShIaM2xxpm9RRdpaB/ vfei+x2e+4YRCJJdw81qmrUBBohANyNW 119JAEufoTgAVY9jPd8699lJ4svMY10E avFAL+PTfC/la9KKNPlOgDgbVRjpUZcU wLeooyqggterm4kXcnWahk3PtG3tmB7A zz1qccY6rOeQALloUQ3Im1Wl9s6pbExZ XI/6qBXYetdexB6DpebnsAk1P4wTprhQ iIxvZpHCppz/jMAx/KDRjQ== +. 86161 IN RRSIG SOA 8 0 86400 20200804050000 20200722040000 46594 . qI98OcNtjSzHbTbiNg6MZRopwcTAW0Cm JiHKdcEOzGY+Tabxyl76YDeVWEZuKrYG pzeqFLKC05W+4nQrUKTmCoI89YHFNdAc f8uO7zbSEM6dFlS4ksCZFkZZHb2hjp4g KI1MEkI56EsojYqEDa1fXakpytEucvKO 2qJgcqPVkb29lk2lePIicO4YIddI38M4 BNdKsCnEhdMYC76lKls7EYMfJeHAr+FX c3SZIAG/r3ov2KR0u/CNqDDlKcfsGCVt wRCWE/EYUMuZbCNW7/cZ8fCWps1Xn7fu I8N7YLgMTqAsAmXx4I86zf8TAUTslgYN 5IR69n16/l2h6QP9vJPS3g== +. 86161 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2020072200 1800 900 604800 86400 +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA AD CD NOERROR +SECTION QUESTION +fi. IN DS +SECTION ANSWER +fi. 48962 IN DS 44855 8 2 80cd184a31c50d5dc44b4f98811f298315986b44edcc0666455c6aef7dbd997e +fi. 48962 IN RRSIG DS 8 1 86400 20200803200000 20200721190000 46594 . HhKwynZK3BYIdOYlwFp6wLacu8uci+ig va3NggxT/LdJqCXlIpuBV5EBGFnEowA9 dYJMdJkkbizYmqkTLbAqaomS5nH3Juz4 5rvjtqi2/viyZjy9VfIPtWx5T5+xfSHH tLac4rJk8ieKSxhpjcc9tITGFT0cnU7U kpWD8OO/0toJF94diLleqS7M/uCDIYyj zgvwIuvx0WxwgxG6bG45EvwfWQbIMRTi R2XSzKtOTRSBbXtI0XKoGrTblOkVxCei uQka3Jqpm/HvmFa9rsspylXTC/6VkKwC u+/TogoJ9vobAr9vXpLx0LiYTkh3GKzZ QL0uw/36jcqplPHlddICPg== +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA AD CD NOERROR +SECTION QUESTION +fi. IN DNSKEY +SECTION ANSWER +fi. 793 IN DNSKEY 256 3 8 AwEAAbfXUBTwb2oWNVvv3kCwgaER6vZ6 76rm5DsLmQjOuClDEHXRtH4JmX+nbjmX yFfeNRBasI0jr2sLIYSPB2TF7ctF/FLB a4zgEKIOTiwM2Q58bBxzUhXtU2KAgqEo V5X7kHYau899uJSW+CUJhKAPYpb/sb41 VqSUrsxkACf5ocZ4k4K57H81B2OKZSHC u6C6h4h7D59l8fwYg7F+RAr2vJENfbSM EjHfIWt54x7WfDuCKGDYvpoKmdgg/ACg 6CyLMttbml5dCZ9XJyWyFbs7eJZYGOgN Uw/3ezxmouChVSqqOeVzZAwfsUAsjb1l 1GSSj3YSiRVY4FWewrdw7XL09v0= +fi. 793 IN DNSKEY 257 3 8 AwEAAb9AMR4NV2YxZH9E6ELMFY5DOszk dTd5AxhSg1YZWi8B9cruHXjghFCmApd1 VfUyQ4MX3DZbskfML/ToxumeSv9OhfA4 I6Fao9bN9UxsBbFlkqwqhAGmuJapSgNu yMWArSoUG4XB/dykPNdyFt+3t1dEH/S3 hS5JmZccSI6YAjnUfG+Pd45R8ljO8ERI 1wSa0IJjDArkuFaLcGrtjR9GJluVmM15 0gWVUIiUkBfDUz8pFjqAWewk0QY9TX22 Z8gfl3yKhO9TYPVHN1oMHTydVqjQKbdb s/BvGXx/GPh63OOxE2ICmxXcz2Ma+082 eA0DfwKLo86PFOre8YK7pEWuw4U= +fi. 793 IN RRSIG DNSKEY 8 1 900 20200805163544 20200722030600 44855 fi. p9isNtIsiD4yhCQkiVeyfPva4iXV8+oE hhU/SEec2ykGjs22yiscFKppV/4s738y Tl58F682IXAq5jolf/6ShAMOUqxzMD1j 5nphLZ62O5B/r6Ah9JiJf9l748mXHCAW kOnwep4IpkEJkiS1lOHgcxd0hw/rsRNZ QAAgiDhYBGcTIWLw6FFsWL/sMJTEnMim 4QzkDLrF4MlFRduQSffC2N7mBbtQpwYt BFS38tZHQZ7w48or3GGJw7ejwR7IQQQJ VYO7yWr13eLZiGPnne51tvltO7LVeDbl cskxbGWYdtTaB+klcukrVlApqUopqjbT mgzA60/IMKe4PXk+QnzZeA== +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +dIGiTrAnSIT.FI. IN DS +SECTION AUTHORITY +FI. 1334 IN SOA a.FI. fi-domain-tech.ficora.FI. 2020072239 3605 1800 2419200 86400 +FI. 21134 IN RRSIG SOA 8 1 86400 20200805082122 20200722074635 20436 fi. mXyNMPJRTLMLOKDjZ2ET/z1SOO9g0a8o o1dsHchts2X0uPvYu6Hc8quBCmUbmjhe qu9dRDiDtjCyddFScwn9FTI9CRMJKytB 1LVcrlBrbuWFlmDilRLkhrK2gmm6MOYU /VGgj+kgEtIQE7NSGF35NQHTJfDw1Jmo FxGAo7I//QBAHJemx5Q4YmbVMDiyTBjz FCLSdAjCGouJTRU9jFgweoEivGk+yD+H vsaaG7NTumzK4miU3SL4sMPb9NYr+HIR 2j+6pUjD05x9UOKAfIQzFnRcToDB1KUo ZZr8teWnLH3fbHCq9JGfz338Eju0wdfe 0E2CGL6Cc77+VThotyTrAg== +es7p5jquq6ng6hd9vn5q3el38s2vuoqh.FI. 21134 IN NSEC3 1 1 5 7298d6895c6c0415 escp8o5mqifge1u5itme7ju27k5hf19a NS DS RRSIG +es7p5jquq6ng6hd9vn5q3el38s2vuoqh.FI. 21134 IN RRSIG NSEC3 8 2 86400 20200805124944 20200722030600 20436 fi. S/DGd4jI4zndv50oMGp2BmB/aH9/M3AX /My9hwZ4zi2r88DrYiNyd2ghyuUvZO9a lvY2NcB9cX9sjAdQAM/xmoHsoraB5YWV v53YBTJDF+kY7BzO5mqImNWmkpe/Kcxt Mpp9Gz02ySpPp37dot4FbdK9A0RWERVw XkoEaFvLkHRf3QGMJUBnCjKJ7r448axs OAKdHIIQb6aG6OATML6mJx6xHOeI8CFl giTrgixGOR/qGP92i9ErcP9iQ2lvHlfK A7SuOLi6uSPBYWIgZzkqGJOpJ0o+cPHV 69QX/SP7m0qr00shV+rxfyppHCLUpKBx iXacRghILlVV5mUgV/25lw== +ml0llbvj7rbbtgbp31q2j3d3qv89643r.FI. 21134 IN NSEC3 1 1 5 7298d6895c6c0415 ml0mjk4qs3b070682obd52l1p9v07cl6 NS SOA TXT RRSIG DNSKEY NSEC3PARAM +ml0llbvj7rbbtgbp31q2j3d3qv89643r.FI. 21134 IN RRSIG NSEC3 8 2 86400 20200804210456 20200722030600 20436 fi. bLLvaTmn2WXk8RxKz7Kb28FoDSgNuIoj ZPrpnwyYVRltfYvOe8wOtzzPQtDYj9F8 bqZgPmZFIAQfsKJk86NMsEWvArGYhX8z 1w6z4qkfpgXpLGeV6fNjLMi/YHZRHQJn cELLNcxh/U9e+xuURCr55XzgzpVVnXpk fm2848LbLO/9p2ZltOGd+GWcyQxxt/aK FlSHUHz2Zp27/9wNCxRyQ8EKtR4eIic8 T9p4kgu5w6302GPSAlfbFCX8Yf+ikMvE Fy7XdbLiE+Uyt0PjEnayT51kqxL2DJFe vtY6Y+MSKazC5xBJudtB6S3owmCDd4PL OhOtxu8lwuuU/FVLteZwvg== +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +diGITrANsiT.fI. IN NS +SECTION ANSWER +diGITrANsiT.fI. 3134 IN NS dns1.ficora.fI. +diGITrANsiT.fI. 3134 IN NS dns2.ficora.fI. +diGITrANsiT.fI. 3134 IN NS ns-secondary.funet.fI. +diGITrANsiT.fI. 3134 IN NS ns1.z.fI. +diGITrANsiT.fI. 3134 IN NS ns2.z.fI. +; following RRSIG signer is out-of-bailiwick, i.e. nonsense +diGITrANsiT.fI. 3134 IN RRSIG NS 10 2 3600 20190517083644 20190117083646 28100 droneinfo.fi. XFNrVGseG3exPEFC58o3tgRckNghA9+G Psc8w8lUgJW7NGPuWHM4aSuhgMWL3nxk kCqTYI60RerzNV1PNxFLlfyBzuwi6rqe dOjpud7Nr9giKQc2r2YsOSLrSfcRN4Wq KGllqTVXZAYIbF5+QYA+2x3sY1StATQS mb2qqYPPB6iR/EPuHOn/1DA9gzJKXdQS wWwRWSuzBtO89q/e/zhSlCsWhk96POR7 du1KpJb58wPdNm6+Jznwj7E7KphZaTID mexL5S9Sf6VwDNDHkSOGT9x182tjIXTs kZcpFZgJTDgrR+vwVwjXmvAs/CKQmMax D0ZuRbMjvgvaMkCsBRjtDQ== +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +Api.dIGiTRaNsit.Fi. IN A +SECTION ANSWER +Api.dIGiTRaNsit.Fi. 3357 IN CNAME digitransit-prod.trafficmanager.net. +digitransit-aks.westeurope.cloudapp.azure.com. 9 IN A 40.119.148.209 +digitransit-prod.trafficmanager.net. 299 IN CNAME digitransit-aks.westeurope.cloudapp.azure.com. +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA AD CD NOERROR +SECTION QUESTION +nET. IN DS +SECTION ANSWER +nET. 56393 IN DS 35886 8 2 7862b27f5f516ebe19680444d4ce5e762981931842c465f00236401d8bd973ee +nET. 56393 IN RRSIG DS 8 1 86400 20200803200000 20200721190000 46594 . PtZ4PuUSHtwvVksquoCtgL5ylNkYaBJk uXVY0Xx4FOyJ8U5kJwlQzScXS8/W7/4m NMLRJWvDIfEMTwpRtFpgd71THg5w3M+O He4GoQ5dGaaSuREvpYCHY+O6aeO/t3DX P3mTcps+CJlIOJckiRirvv3V1u7jmTGB t4jZ6Gn27CX9lPXGUkhWrDx9EOW1p7ky ZYtFGtkVxGmqnMqoNMSz+JaFmN43uaJU grJt6B8aKFIw3MR1Z4xCX3oYzd5jDdQt sS1h8frflyyN/dF/aIl5BW58sc8qkgGS kv8iUgHW1/E24chcMQFngnOuAAF6hjaA K4SJCdrCXUNiduL8H9Q5MQ== +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA AD CD NOERROR +SECTION QUESTION +nEt. IN DNSKEY +SECTION ANSWER +nEt. 21146 IN DNSKEY 256 3 8 AQPeYYme8NvhAl+0XjyGqHVep4Y1T2Or RmO+L3QGULBlOe571PnxI+gRyXCQmtN7 WpoJxzALFSVBPsggqwOP+wnmCx8DZ49N HrfS7WbMtoYHTtiaIvHTjZZ88leuCtNL qfIH8N1Ax68Xnf4uKobYFgZXj0M2Zi7Y I84iFkCpIyZk6VIiJpvpNgyCK5mWetPF 2zmO2jXC8M045JIPam38reXD +nEt. 21146 IN DNSKEY 257 3 8 AQOYBnzqWXIEj6mlgXg4LWC0HP2n8eK8 XqgHlmJ/69iuIHsa1TrHDG6TcOra/pye GKwH0nKZhTmXSuUFGh9BCNiwVDuyyb6O BGy2Nte9Kr8NwWg4q+zhSoOf4D+gC9dE zg0yFdwT0DKEvmNPt0K4jbQDS4Yimb+u PKuF6yieWWrPYYCrv8C9KC8JMze2uT6N uWBfsl2fDUoV4l65qMww06D7n+p7Rbdw WkAZ0fA63mXVXBZF6kpDtsYD7SUB9jhh fLQE/r85bvg3FaSs5Wi2BaqN06SzGWI1 DHu7axthIOeHwg00zxlhTpoYCH0ldoQz +S65zWYi/fRJiyLSBb6JZOvn +nEt. 21146 IN RRSIG DNSKEY 8 1 86400 20200728162830 20200713162330 35886 net. BsxHqTUrVNqYdQdv5uriiUd/p+Dh5F12 /01oniA3F1keMZU1V+pbELcih+1gfLs3 i+f88p/9r3kM8gghxQtInzyJl3lPdeBM 7LjWuonQR5CzvfnM4WAgkVZZxmFB7l6b bj+ey9mwocAMR1ht9502MgB4eQLOHVve 3mdCXYuilxwQ9vOrVsFDLiELVoCDVtQi csatJy3Z/LU31IWtR8c6Ta/ItApgqsWg fU8Br3uyHesiDbA2FSfBb9qWFfGcNrDJ ZGF9dLCxvMeHSU5AKpEwX8flOKWKRVwM nB/+LB1owMmcyao82yJlL7y7vxfrQDb0 V0uJaQMEiQl7XGBsT+8Hgg== +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +TRAFficManaGer.NEt. IN DS +SECTION AUTHORITY +A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.NEt. 21540 IN NSEC3 1 1 0 - a1ruuffjkct2q54p78f8ejgj8jbk7i8b NS SOA RRSIG DNSKEY NSEC3PARAM +A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.NEt. 21540 IN RRSIG NSEC3 8 2 86400 20200728064417 20200721053417 56519 net. B4N1ZLUcMCAkfgGNrmJfXylkUAbPnqxO 31ZQ5ZwU0AecyChOkGGTelH/87eM7RtG M7mqrM6zU9CnGwsgheqg2HCbpX7n5Fvl 1AclnJZBuFlF0hvejSO99rrLrLRaTWQt LyJwVQJWkMfgztCO5Mh2ngJfK6ZB1PLS xOFqVz0j5MTYYp9QwdL1PvLIaBAw0kTg cm3o476wB0glMo6yzDbK2A== +CS431SS8CTI7M6JHMN592GRLI9T17OK7.NEt. 21540 IN NSEC3 1 1 0 - cs49egn3atpo6m7fblhased3j00k92nb NS DS RRSIG +CS431SS8CTI7M6JHMN592GRLI9T17OK7.NEt. 21540 IN RRSIG NSEC3 8 2 86400 20200727063544 20200720052544 56519 net. FB73aPYNlR3FqN1gmxYeIccL+ybiv+8A ymQkJDevhRITdI76YqbobqbvAKQg9Knm lSe5OtWZEmI6h+qbYXtXfnAGl+GayzbL LsSyUABJdWp8ZuGooatayzGqjWUpIGZr qgAZIK+twkCKUicgi2XhCztnVr1wVf2z L0tRuctiZQRdoDlcRfFzXBsg21Kn3eVy ivjnZUzp93vL1ZrBhhZfMg== +NEt. 840 IN RRSIG SOA 8 1 900 20200729095953 20200722084953 56519 net. GEY1baT1zShB6uxFAyHlg6EPfEbCxp3E 3C2l8OeVCBwgzBdP9TwDeXH+//RcfT9w Q++Wan2q/0W5I9fJRWZKGDPkdmhOcvh9 VjJRlb3DwZdJEeDc3Rj3dip3MvL7OyZt 8lfv42/WoBYHXeduQwGknz0KbNpeSqU0 BjV4C9O/w5rUSjvk3z6Qka7jz3/0sz7H 4FfXqv5CxkUCKYyl9PzxuA== +NEt. 840 IN SOA a.gtld-servers.NEt. nstld.verisign-grs.com. 1595411993 1800 900 604800 86400 +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +TrAfFicMANAger.net. IN NS +SECTION ANSWER +TrAfFicMANAger.net. 21041 IN NS tm1.dns-tm.com. +TrAfFicMANAger.net. 21041 IN NS tm1.edgedns-tm.info. +TrAfFicMANAger.net. 21041 IN NS tm2.dns-tm.com. +TrAfFicMANAger.net. 21041 IN NS tm2.edgedns-tm.info. +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +Api.dIGiTRaNsit.Fi. IN A +SECTION ANSWER +Api.dIGiTRaNsit.Fi. 3585 IN CNAME digitransit-prod.trafficmanager.net. +digitransit-aks.westeurope.cloudapp.azure.com. 9 IN A 40.119.148.209 +digitransit-prod.trafficmanager.net. 285 IN CNAME digitransit-aks.westeurope.cloudapp.azure.com. +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +DigiTranSiT-pROd.tRaffiCmanAgeR.Net. IN A +SECTION ANSWER +DigiTranSiT-pROd.tRaffiCmanAgeR.Net. 299 IN CNAME digitransit-aks.westeurope.cloudapp.azure.com. +digitransit-aks.westeurope.cloudapp.azure.com. 9 IN A 40.119.148.209 +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +DigiTranSiT-pROd.tRaffiCmanAgeR.Net. IN A +SECTION ANSWER +DigiTranSiT-pROd.tRaffiCmanAgeR.Net. 299 IN CNAME digitransit-aks.westeurope.cloudapp.azure.com. +digitransit-aks.westeurope.cloudapp.azure.com. 9 IN A 40.119.148.209 +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA AD CD NOERROR +SECTION QUESTION +coM. IN DS +SECTION ANSWER +coM. 86251 IN DS 30909 8 2 e2d3c916f6deeac73294e8268fb5885044a833fc5459588f4a9184cfc41a5766 +coM. 86251 IN RRSIG DS 8 1 86400 20200803200000 20200721190000 46594 . F3sngBM8xYQ10Z3iIVWUqlMFLvecizXH 2d5kM4iguQL088Cv6xz1Ep3d4wUVEzlL YBBrsCQB627WztctcVPFiXUn22cWwzky 7yxjII8YY72V2x2/758hmZMCSHdSzJph By9Wv5Av5O8qqLt1QyYq8r6cZK7352Vk ICENa/OhKWX3dUvQ+EQe+3JUN5q/Xfeb WnCiuG2LgaWIgl/f+yjpMEkg808EUCCz 41Dbe81gwIadN+vvpjvpb3j5cBfozwqk thyCWlXt2+ZUUH4aSr5q5WsS8nD4gb70 6YC+A08woNmeYms8t40irruVMdlUzrXC Fz7+azz0zCEAc046A9v5fQ== +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA AD CD NOERROR +SECTION QUESTION +coM. IN DNSKEY +SECTION ANSWER +coM. 20944 IN DNSKEY 256 3 8 AwEAAdVECVAFFKnwlH7lDYpsSvv50Z7E JP518luWdiN7X5igYJo6dLij/noOhYO0 ppmTghphtSqHn75/qMmETK9NiUfLW4M9 X8j/IvIr1xrTPEb6+dipDE9xKjhMGFUu fOeXHiBoMQiKLNzlssYuz90oQrEwCKpa 5R4cYYFiZaoeezi2NQeIAY82dh/8auvF zqCOewWx/J2zVh8YHqfkGeXyzsM= +coM. 20944 IN DNSKEY 257 3 8 AQPDzldNmMvZFX4NcNJ0uEnKDg7tmv/F 3MyQR0lpBmVcNcsIszxNFxsBfKNW9JYC Yqpik8366LE7VbIcNRzfp2h9OO8HRl+H +E08zauK8k7evWEmu/6od+2boggPoiEf GNyvNPaSI7FOIroDsnw/taggzHRX1Z7S OiOiPWPNIwSUyWOZ79VmcQ1GLkC6NlYv G3HwYmynQv6oFwGv/KELSw7ZSdrbTQ0H XvZbqMUI7BaMskmvgm1G7oKZ1YiF7O9i oVNc0+7ASbqmZN7Z98EGU/Qh2K/BgUe8 Hs0XVcdPKrtyYnoQHd2ynKPcMMlTEih2 /2HDHjRPJ2aywIpKNnv4oPo/ +coM. 20944 IN RRSIG DNSKEY 8 1 86400 20200729182421 20200714181921 30909 com. kWt6r1qzHb1LABToPGz61aVgRBHMIkS+ x1FbuHxh+Ha9nYtlnl1AOju4CjMC6gje qBXYhhwpZWC4VFSE+hVXuI2NNEPvtcD1 Om9eu69KEK8d1rXmho+PBzJyzXSSUpM6 KtapTL0NDdjg+uCt6YmWNli/e3QdRAoI u5eNnmFBK5viaGcnIP5l8/QdXH+dBmfi qrrs+z0oJv89euCCjH0UeMfVJUetHTox MLiB4GlMyQnPWsNXNZPzQEWk8CeLEhVu e8QVvMQmq+GfcvRF/jFnTsL9ILTMYiJR 8gYp5YnFtBgtrqPoekmAaJ0dig2bXFVV /x8RzR/wFd8yUHTaT+AWkA== +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +AZURe.com. IN DS +SECTION AUTHORITY +2VPE4QITM4PLV486G56AB7OUDVIO1U3D.com. 21574 IN NSEC3 1 1 0 - 2vpf134m9p90k8k9jmtdjemi4anqnsaf NS DS RRSIG +2VPE4QITM4PLV486G56AB7OUDVIO1U3D.com. 21574 IN RRSIG NSEC3 8 2 86400 20200726043358 20200719032358 24966 com. SpLKFkZ534rLzf6WTHf+JdBCanfiUhX1 BbmjpFQRI+l1qRN9po9mtA95jfw8AGjw S0n36LK9MBNF4pRo4zcmxny3/K7lfDwf 6HhDlNMp3KQGtLMGcSCqw7StxA3b3qsg 0NhxPtHl818RsBKueIR88LPX88x5jkzC Gr11WJoVlq682pCJlmkVOze2JCmRvjYY m/mUHv4y5+mh3h2AbHc+zA== +CK0POJMG874LJREF7EFN8430QVIT8BSM.com. 21574 IN NSEC3 1 1 0 - ck0q1gin43n1arrc9osm6qpqr81h5m9a NS SOA RRSIG DNSKEY NSEC3PARAM +CK0POJMG874LJREF7EFN8430QVIT8BSM.com. 21574 IN RRSIG NSEC3 8 2 86400 20200728044213 20200721033213 24966 com. qoAss2VVxsBWG9+MAQ3giAmCnzf4dz7G ppZsQfkt3b7cFIVgO3TiWPWkusb3BEbp sma2Q+x/TBLeeHYm6l1HWXMIl7zcYLsA XY0ZzoWaqNPTPH6YNDAreZYP21UekBL7 g710cndRk4oaJUNz5t8sGi3JaOJF046Q cUz6gGg7NLMvyGlJWzmftGbxgp9ovdOg wmirddESGOj33kuCfSJvWA== +com. 874 IN RRSIG SOA 8 1 900 20200729100807 20200722085807 24966 com. kDvHo8x7ut5Lu66MSUOUTPvxfYJLXMcu aKPCu9I+jTYZOzAH4KquPm+765a/gnp5 2okPhEUJTO2JYumhfkjyG04kE4HCnqfR bWtjjfIyqXo34km+CR8rG8RGZ6QilLWZ 0yxux5+izvuji4L4KLeTxPwUJQFgAmVA 59unj2IqysGWRc2ETSofHOPFrydduuyc DJdQLN6Dq1fMFh849Qr6nw== +com. 874 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1595412487 1800 900 604800 86400 +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +AZURe.COM. IN NS +SECTION ANSWER +AZURe.COM. 21462 IN NS ns1-205.azure-dns.COM. +AZURe.COM. 21462 IN NS ns2-205.azure-dns.net. +AZURe.COM. 21462 IN NS ns3-205.azure-dns.org. +AZURe.COM. 21462 IN NS ns4-205.azure-dns.info. +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +DigitRaNsit-aKS.WESTEuROPe.CloUDApp.aZuRe.cOm. IN A +SECTION ANSWER +DigitRaNsit-aKS.WESTEuROPe.CloUDApp.aZuRe.cOm. 9 IN A 40.119.148.209 +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA NOERROR +SECTION QUESTION +api.digitransit.fi. IN A +SECTION ANSWER +api.digitransit.fi. 3357 IN CNAME digitransit-prod.trafficmanager.net. +digitransit-aks.westeurope.cloudapp.azure.com. 9 IN A 40.119.148.209 +digitransit-prod.trafficmanager.net. 299 IN CNAME digitransit-aks.westeurope.cloudapp.azure.com. +ENTRY_END + +ENTRY_BEGIN +MATCH qname qtype +ADJUST copy_id +REPLY QR RD RA CD NOERROR +SECTION QUESTION +DigitRaNsit-aKS.WESTEuROPe.CloUDApp.aZuRe.cOm. IN A +SECTION ANSWER +DigitRaNsit-aKS.WESTEuROPe.CloUDApp.aZuRe.cOm. 9 IN A 40.119.148.209 +ENTRY_END + +RANGE_END + + +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +api.digitransit.fi. IN A +ENTRY_END + +STEP 11 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +api.digitransit.fi. IN A +SECTION ANSWER +api.digitransit.fi. IN CNAME digitransit-prod.trafficmanager.net. +digitransit-prod.trafficmanager.net. IN CNAME digitransit-aks.westeurope.cloudapp.azure.com. +digitransit-aks.westeurope.cloudapp.azure.com. IN A 40.119.148.209 +ENTRY_END + +SCENARIO_END diff --git a/lib/layer/validate.test.integr/kresd_config.j2 b/lib/layer/validate.test.integr/kresd_config.j2 new file mode 100644 index 0000000..cc0dbd5 --- /dev/null +++ b/lib/layer/validate.test.integr/kresd_config.j2 @@ -0,0 +1,52 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later + +trust_anchors.remove('.') +{% for TAF in TRUST_ANCHOR_FILES %} +trust_anchors.add_file('{{TAF}}') +{% endfor %} + +{% raw %} +-- Disable RFC5011 TA update +if ta_update then + modules.unload('ta_update') +end + +-- Disable RFC8145 signaling, scenario doesn't provide expected answers +if ta_signal_query then + modules.unload('ta_signal_query') +end + +-- Disable RFC8109 priming, scenario doesn't provide expected answers +if priming then + modules.unload('priming') +end + +-- Disable this module because it make one priming query +if detect_time_skew then + modules.unload('detect_time_skew') +end + +cache.size = 2*MB +log_level('debug') +policy.add(policy.all(policy.DEBUG_ALWAYS)) +policy.add(policy.all(policy.FORWARD('8.8.8.8'))) +{% endraw %} + +net = { '{{SELF_ADDR}}' } + +-- Self-checks on globals +assert(help() ~= nil) +assert(worker.id ~= nil) +-- Self-checks on facilities +assert(cache.count() == 0) +assert(cache.stats() ~= nil) +assert(cache.backends() ~= nil) +assert(worker.stats() ~= nil) +assert(net.interfaces() ~= nil) +-- Self-checks on loaded stuff +assert(net.list()[1].transport.ip == '{{SELF_ADDR}}') +assert(#modules.list() > 0) +-- Self-check timers +ev = event.recurrent(1 * sec, function (ev) return 1 end) +event.cancel(ev) +ev = event.after(0, function (ev) return 1 end) diff --git a/lib/log.c b/lib/log.c new file mode 100644 index 0000000..1a3d715 --- /dev/null +++ b/lib/log.c @@ -0,0 +1,328 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "kresconfig.h" + +#include <stdarg.h> +#include <stdio.h> +#include <gnutls/gnutls.h> +#include "contrib/ucw/mempool.h" +#include "lib/log.h" +#include "lib/resolve.h" + +#if ENABLE_LIBSYSTEMD +#include <stdlib.h> +#include <systemd/sd-daemon.h> +#include <systemd/sd-journal.h> +bool use_journal = false; +#else +#define use_journal false +#endif + +kr_log_level_t kr_log_level = LOG_DEFAULT_LEVEL; +kr_log_target_t kr_log_target = LOG_TARGET_DEFAULT; + +/** Set of log-groups that are on debug level. It's a bitmap over 1 << enum kr_log_group. */ +static uint64_t kr_log_groups = 0; + +typedef struct { + const char *g_name; + enum kr_log_group g_val; +} log_group_names_t; + +#define GRP_NAME_ITEM(grp) { grp ## _TAG, grp } + +const log_group_names_t log_group_names[] = { + GRP_NAME_ITEM(LOG_GRP_SYSTEM), + GRP_NAME_ITEM(LOG_GRP_CACHE), + GRP_NAME_ITEM(LOG_GRP_IO), + GRP_NAME_ITEM(LOG_GRP_NETWORK), + GRP_NAME_ITEM(LOG_GRP_TA), + GRP_NAME_ITEM(LOG_GRP_TLS), + GRP_NAME_ITEM(LOG_GRP_GNUTLS), + GRP_NAME_ITEM(LOG_GRP_TLSCLIENT), + GRP_NAME_ITEM(LOG_GRP_XDP), + GRP_NAME_ITEM(LOG_GRP_DOH), + GRP_NAME_ITEM(LOG_GRP_DNSSEC), + GRP_NAME_ITEM(LOG_GRP_HINT), + GRP_NAME_ITEM(LOG_GRP_PLAN), + GRP_NAME_ITEM(LOG_GRP_ITERATOR), + GRP_NAME_ITEM(LOG_GRP_VALIDATOR), + GRP_NAME_ITEM(LOG_GRP_RESOLVER), + GRP_NAME_ITEM(LOG_GRP_SELECTION), + GRP_NAME_ITEM(LOG_GRP_ZCUT), + GRP_NAME_ITEM(LOG_GRP_COOKIES), + GRP_NAME_ITEM(LOG_GRP_STATISTICS), + GRP_NAME_ITEM(LOG_GRP_REBIND), + GRP_NAME_ITEM(LOG_GRP_WORKER), + GRP_NAME_ITEM(LOG_GRP_POLICY), + GRP_NAME_ITEM(LOG_GRP_TASENTINEL), + GRP_NAME_ITEM(LOG_GRP_TASIGNALING), + GRP_NAME_ITEM(LOG_GRP_TAUPDATE), + GRP_NAME_ITEM(LOG_GRP_DAF), + GRP_NAME_ITEM(LOG_GRP_DETECTTIMEJUMP), + GRP_NAME_ITEM(LOG_GRP_DETECTTIMESKEW), + GRP_NAME_ITEM(LOG_GRP_GRAPHITE), + GRP_NAME_ITEM(LOG_GRP_PREFILL), + GRP_NAME_ITEM(LOG_GRP_PRIMING), + GRP_NAME_ITEM(LOG_GRP_SRVSTALE), + GRP_NAME_ITEM(LOG_GRP_WATCHDOG), + GRP_NAME_ITEM(LOG_GRP_NSID), + GRP_NAME_ITEM(LOG_GRP_DNSTAP), + GRP_NAME_ITEM(LOG_GRP_TESTS), + GRP_NAME_ITEM(LOG_GRP_DOTAUTH), + GRP_NAME_ITEM(LOG_GRP_HTTP), + GRP_NAME_ITEM(LOG_GRP_CONTROL), + GRP_NAME_ITEM(LOG_GRP_MODULE), + GRP_NAME_ITEM(LOG_GRP_DEVEL), + GRP_NAME_ITEM(LOG_GRP_RENUMBER), + GRP_NAME_ITEM(LOG_GRP_EDE), + GRP_NAME_ITEM(LOG_GRP_REQDBG), + { NULL, LOG_GRP_UNKNOWN }, +}; +static_assert(LOG_GRP_REQDBG <= 8 * sizeof(kr_log_groups), "Too many log groups."); + +bool kr_log_group_is_set(enum kr_log_group group) +{ + if (kr_fails_assert(group >= 0)) + return false; + + return kr_log_groups & (1ULL << group); +} + +void kr_log_fmt(enum kr_log_group group, kr_log_level_t level, const char *file, + const char *line, const char *func, const char *fmt, ...) +{ + va_list args; + + if (!(KR_LOG_LEVEL_IS(level) || kr_log_group_is_set(group))) + return; + + if (kr_log_target == LOG_TARGET_SYSLOG) { + if (kr_log_group_is_set(group)) + setlogmask(LOG_UPTO(LOG_DEBUG)); + + va_start(args, fmt); + if (use_journal) { + #if ENABLE_LIBSYSTEMD + sd_journal_printv_with_location(level, file, line, func, fmt, args); + #endif + } else { + vsyslog(level, fmt, args); + } + va_end(args); + + if (kr_log_group_is_set(group)) + setlogmask(LOG_UPTO(kr_log_level)); + } else { + FILE *stream; + switch(kr_log_target) { + case LOG_TARGET_STDOUT: stream = stdout; break; + default: kr_assert(false); // fall through + case LOG_TARGET_STDERR: stream = stderr; break; + } + + va_start(args, fmt); + vfprintf(stream, fmt, args); + va_end(args); + } +} + +static void kres_gnutls_log(int level, const char *message) +{ + kr_log_debug(GNUTLS, "(%d) %s", level, message); +} + + +struct log_level_name { + const char *name; + kr_log_level_t level; +}; +const struct log_level_name level_names[] = { + { "alert", LOG_ALERT }, + { "crit", LOG_CRIT }, + { "debug", LOG_DEBUG }, + { "emerg", LOG_EMERG }, + { "err", LOG_ERR }, + { "info", LOG_INFO }, + { "notice", LOG_NOTICE }, + { "warning", LOG_WARNING }, + { NULL, -1 }, +}; + +const char *kr_log_level2name(kr_log_level_t level) +{ + for (int i = 0; level_names[i].name; ++i) + { + if (level_names[i].level == level) + return level_names[i].name; + } + + return NULL; +} + +kr_log_level_t kr_log_name2level(const char *name) +{ + if (kr_fails_assert(name)) + return LOG_UNKNOWN_LEVEL; + + for (int i = 0; level_names[i].name; ++i) + { + if (strcmp(level_names[i].name, name) == 0) + return level_names[i].level; + } + + return LOG_UNKNOWN_LEVEL; +} + +const char *kr_log_grp2name(enum kr_log_group group) +{ + for (int i = 0; log_group_names[i].g_name; ++i) + { + if (log_group_names[i].g_val == group) + return log_group_names[i].g_name; + } + + return NULL; +} + +enum kr_log_group kr_log_name2grp(const char *name) +{ + if (kr_fails_assert(name)) + return LOG_GRP_UNKNOWN; + + for (int i = 0; log_group_names[i].g_name; ++i) + { + if (strcmp(log_group_names[i].g_name, name) == 0) + return log_group_names[i].g_val; + } + + return LOG_GRP_UNKNOWN; +} + + + +static void kr_gnutls_log_level_set() +{ + /* gnutls logs messages related to our TLS and also libdnssec, + * and the logging is set up in a global way only */ + if (KR_LOG_LEVEL_IS(LOG_DEBUG) || kr_log_group_is_set(LOG_GRP_GNUTLS)) { + gnutls_global_set_log_function(kres_gnutls_log); + gnutls_global_set_log_level(LOG_GNUTLS_LEVEL); + } else { + gnutls_global_set_log_level(0); + } +} + +void kr_log_level_set(kr_log_level_t level) +{ + if (level < LOG_CRIT || level > LOG_DEBUG) { + kr_log_warning(SYSTEM, "invalid log level\n"); + return; + } + + kr_log_level = level; + setlogmask(LOG_UPTO(kr_log_level)); + + kr_gnutls_log_level_set(); +} + +void kr_log_group_add(enum kr_log_group group) +{ + if (kr_fails_assert(group >= 0)) + return; + + kr_log_groups |= (1ULL << group); + if (group == LOG_GRP_GNUTLS) + kr_gnutls_log_level_set(); +} + +void kr_log_group_reset() +{ + bool had_gnutls = kr_log_group_is_set(LOG_GRP_GNUTLS); + kr_log_groups = 0; + kr_log_group_add(LOG_GRP_REQDBG); + if (had_gnutls) + kr_gnutls_log_level_set(); +} + +void kr_log_target_set(kr_log_target_t target) +{ + kr_log_target = target; + if (target != LOG_TARGET_SYSLOG) + return; + + int ret = 0; +#if ENABLE_LIBSYSTEMD + ret = sd_booted(); + use_journal = ret > 0; +#endif + if (!use_journal) + openlog(NULL, LOG_PID, LOG_DAEMON); + if (ret < 0) + kr_log_error(SYSTEM, "failed test for systemd presence: %s\n", + strerror(abs(ret))); +} + +static inline bool req_has_trace_log(const struct kr_request *req) +{ + return unlikely(req && req->trace_log); +} + +static void kr_vlog_req( + const struct kr_request * const req, uint32_t qry_uid, + const unsigned int indent, enum kr_log_group group, const char *tag, const char *fmt, + va_list args) +{ + struct mempool *mp = mp_new(512); + + const uint32_t req_uid = req ? req->uid : 0; + char *msg = mp_printf(mp, "[%-6s][%05u.%02u] %*s", + tag, req_uid, qry_uid, indent, ""); + + msg = mp_vprintf_append(mp, msg, fmt, args); + + if (req_has_trace_log(req)) + req->trace_log(req, msg); + + kr_log_fmt(group, LOG_DEBUG, SD_JOURNAL_METADATA, "%s", msg); + + mp_delete(mp); +} + +void kr_log_req1(const struct kr_request * const req, uint32_t qry_uid, + const unsigned int indent, enum kr_log_group group, const char *tag, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + kr_vlog_req(req, qry_uid, indent, group, tag, fmt, args); + va_end(args); +} + +bool kr_log_is_debug_fun(enum kr_log_group group, const struct kr_request *req) +{ + return req_has_trace_log(req) + || kr_log_group_is_set(group) + || KR_LOG_LEVEL_IS(LOG_DEBUG); +} + +void kr_log_q1(const struct kr_query * const qry, + enum kr_log_group group, const char *tag, const char *fmt, ...) +{ + // Optimize: this is probably quite a hot path. + const struct kr_request *req = likely(qry != NULL) ? qry->request : NULL; + if (likely(!kr_log_is_debug_fun(group, req))) + return; + + unsigned ind = 0; + for (const struct kr_query *q = qry; q; q = q->parent) + ind += 2; + const uint32_t qry_uid = qry ? qry->uid : 0; + + va_list args; + va_start(args, fmt); + kr_vlog_req(req, qry_uid, ind, group, tag, fmt, args); + va_end(args); +} + diff --git a/lib/log.h b/lib/log.h new file mode 100644 index 0000000..1a0237a --- /dev/null +++ b/lib/log.h @@ -0,0 +1,278 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <syslog.h> +#include "lib/defines.h" + +#define LOG_UNKNOWN_LEVEL -1 /**< Negative error value. */ +#define LOG_GNUTLS_LEVEL 5 /**< GnuTLS level is 5. */ + +/* Targets */ + +typedef enum { + LOG_TARGET_SYSLOG = 0, + LOG_TARGET_STDERR = 1, + LOG_TARGET_STDOUT = 2, + /* The default also applies *before* configuration changes it. */ + LOG_TARGET_DEFAULT = LOG_TARGET_STDERR, +} kr_log_target_t; + +/** Current logging target. Read only, please. */ +KR_EXPORT extern +kr_log_target_t kr_log_target; + +/** Set the current logging target. */ +KR_EXPORT +void kr_log_target_set(kr_log_target_t target); + + +/* Groups */ + +/* Don't forget add *_TAG below, log_group_names[] item (log.c) and generate + * new kres-gen.lua */ +enum kr_log_group { + LOG_GRP_UNKNOWN = -1, + LOG_GRP_SYSTEM = 1, /* Must be first in enum. */ + LOG_GRP_CACHE, + LOG_GRP_IO, + LOG_GRP_NETWORK, + LOG_GRP_TA, + LOG_GRP_TLS, + LOG_GRP_GNUTLS, + LOG_GRP_TLSCLIENT, + LOG_GRP_XDP, + LOG_GRP_DOH, + LOG_GRP_DNSSEC, + LOG_GRP_HINT, + LOG_GRP_PLAN, + LOG_GRP_ITERATOR, + LOG_GRP_VALIDATOR, + LOG_GRP_RESOLVER, + LOG_GRP_SELECTION, + LOG_GRP_ZCUT, + LOG_GRP_COOKIES, + LOG_GRP_STATISTICS, + LOG_GRP_REBIND, + LOG_GRP_WORKER, + LOG_GRP_POLICY, + LOG_GRP_TASENTINEL, + LOG_GRP_TASIGNALING, + LOG_GRP_TAUPDATE, + LOG_GRP_DAF, + LOG_GRP_DETECTTIMEJUMP, + LOG_GRP_DETECTTIMESKEW, + LOG_GRP_GRAPHITE, + LOG_GRP_PREFILL, + LOG_GRP_PRIMING, + LOG_GRP_SRVSTALE, + LOG_GRP_WATCHDOG, + LOG_GRP_NSID, + LOG_GRP_DNSTAP, + LOG_GRP_TESTS, + LOG_GRP_DOTAUTH, + LOG_GRP_HTTP, + LOG_GRP_CONTROL, + LOG_GRP_MODULE, + LOG_GRP_DEVEL, + LOG_GRP_RENUMBER, + LOG_GRP_EDE, + /* ^^ Add new log groups above ^^. */ + LOG_GRP_REQDBG, /* Must be first non-displayed entry in enum! */ +}; + +/** + * @name Group names + */ +///@{ +#define LOG_GRP_SYSTEM_TAG "system" /**< ``system``: catch-all log for generic messages*/ +#define LOG_GRP_CACHE_TAG "cache" /**< ``cache``: operations related to cache */ +#define LOG_GRP_IO_TAG "io" /**< ``io``: input/output operations */ +#define LOG_GRP_NETWORK_TAG "net" /**< ``net``: network configuration and operation */ +#define LOG_GRP_TA_TAG "ta" /**< ``ta``: basic log for trust anchors (TA) */ +#define LOG_GRP_TASENTINEL_TAG "tasent" /**< ``tasent``: TA sentinel */ +#define LOG_GRP_TASIGNALING_TAG "tasign" /**< ``tasign``: TA signal query */ +#define LOG_GRP_TAUPDATE_TAG "taupd" /**< ``taupd``: TA update */ +#define LOG_GRP_TLS_TAG "tls" /**< ``tls``: TLS encryption layer */ +#define LOG_GRP_GNUTLS_TAG "gnutls" /**< ``gnutls``: low-level logs from GnuTLS */ +#define LOG_GRP_TLSCLIENT_TAG "tls_cl" /**< ``tls_cl``: TLS client messages (used for TLS forwarding) */ +#define LOG_GRP_XDP_TAG "xdp" /**< ``xdp``: operations related to XDP */ +#define LOG_GRP_DOH_TAG "doh" /**< ``doh``: DNS-over-HTTPS logger (doh2 implementation) */ +#define LOG_GRP_DNSSEC_TAG "dnssec" /**< ``dnssec``: operations related to DNSSEC */ +#define LOG_GRP_HINT_TAG "hint" /**< ``hint``: operations related to static hints */ +#define LOG_GRP_PLAN_TAG "plan" /**< ``plan``: operations related to resolution plan */ +#define LOG_GRP_ITERATOR_TAG "iterat" /**< ``iterat``: operations related to iterate layer */ +#define LOG_GRP_VALIDATOR_TAG "valdtr" /**< ``valdtr``: operations related to validate layer */ +#define LOG_GRP_RESOLVER_TAG "resolv" /**< ``resolv``: operations related to resolving */ +#define LOG_GRP_SELECTION_TAG "select" /**< ``select``: operations related to server selection */ +#define LOG_GRP_ZCUT_TAG "zoncut" /**< ``zonecut``: operations related to zone cut */ +#define LOG_GRP_COOKIES_TAG "cookie" /**< ``cookie``: operations related to cookies */ +#define LOG_GRP_STATISTICS_TAG "statis" /**< ``statis``: operations related to statistics */ +#define LOG_GRP_REBIND_TAG "rebind" /**< ``rebind``: operations related to rebinding */ +#define LOG_GRP_WORKER_TAG "worker" /**< ``worker``: operations related to worker layer */ +#define LOG_GRP_POLICY_TAG "policy" /**< ``policy``: operations related to policy */ +#define LOG_GRP_DAF_TAG "daf" /**< ``daf``: operations related to DAF module */ +#define LOG_GRP_DETECTTIMEJUMP_TAG "timejm" /**< ``timejm``: operations related to time jump */ +#define LOG_GRP_DETECTTIMESKEW_TAG "timesk" /**< ``timesk``: operations related to time skew */ +#define LOG_GRP_GRAPHITE_TAG "graphi" /**< ``graphi``: operations related to graphite */ +#define LOG_GRP_PREFILL_TAG "prefil" /**< ``prefil``: operations related to prefill */ +#define LOG_GRP_PRIMING_TAG "primin" /**< ``primin``: operations related to priming */ +#define LOG_GRP_SRVSTALE_TAG "srvstl" /**< ``srvstl``: operations related to serve stale */ +#define LOG_GRP_WATCHDOG_TAG "wtchdg" /**< ``wtchdg``: operations related to watchdog */ +#define LOG_GRP_NSID_TAG "nsid" /**< ``nsid``: operations related to NSID */ +#define LOG_GRP_DNSTAP_TAG "dnstap" /**< ``dnstap``: operations related to dnstap */ +#define LOG_GRP_TESTS_TAG "tests" /**< ``tests``: operations related to tests */ +#define LOG_GRP_DOTAUTH_TAG "dotaut" /**< ``dotaut``: DNS-over-TLS against authoritative servers */ +#define LOG_GRP_HTTP_TAG "http" /**< ``http``: http module, its web interface and legacy DNS-over-HTTPS */ +#define LOG_GRP_CONTROL_TAG "contrl" /**< ``contrl``: TTY control sockets*/ +#define LOG_GRP_MODULE_TAG "module" /**< ``module``: suitable for user-defined modules */ +#define LOG_GRP_DEVEL_TAG "devel" /**< ``devel``: for development purposes */ +#define LOG_GRP_RENUMBER_TAG "renum" /**< ``renum``: operation related to renumber */ +#define LOG_GRP_EDE_TAG "exterr" /**< ``exterr``: extended error module */ +#define LOG_GRP_REQDBG_TAG "reqdbg" /**< ``reqdbg``: debug logs enabled by policy actions */ +///@} + +KR_EXPORT +bool kr_log_group_is_set(enum kr_log_group group); +KR_EXPORT +void kr_log_group_add(enum kr_log_group group); +KR_EXPORT +void kr_log_group_reset(); +KR_EXPORT +const char *kr_log_grp2name(enum kr_log_group group); +KR_EXPORT +enum kr_log_group kr_log_name2grp(const char *name); + + +/* Levels */ + +typedef int kr_log_level_t; + +/** Current logging level. Read only, please. */ +KR_EXPORT extern +kr_log_level_t kr_log_level; + +/** Set the current logging level. */ +KR_EXPORT +void kr_log_level_set(kr_log_level_t level); + +KR_EXPORT +const char *kr_log_level2name(kr_log_level_t level); + +/** Return negative on error. */ +KR_EXPORT +kr_log_level_t kr_log_name2level(const char *name); + +#define KR_LOG_LEVEL_IS(exp) ((kr_log_level >= (exp)) ? true : false) + +/** + * @name Logging levels + * + * We stick very close to POSIX syslog.h + */ +/// @{ + +/** Debugging message. Can be very verbose. + * The level is most often used through VERBOSE_MSG. */ +#define kr_log_debug(grp, fmt, ...) \ + kr_log_fmt(LOG_GRP_ ## grp, LOG_DEBUG, SD_JOURNAL_METADATA, \ + "[%-6s] " fmt, LOG_GRP_ ## grp ## _TAG, ## __VA_ARGS__) + +#define kr_log_info(grp, fmt, ...) \ + kr_log_fmt(LOG_GRP_ ## grp, LOG_INFO, SD_JOURNAL_METADATA, \ + "[%-6s] " fmt, LOG_GRP_ ## grp ## _TAG, ## __VA_ARGS__) + +#define kr_log_notice(grp, fmt, ...) \ + kr_log_fmt(LOG_GRP_ ## grp, LOG_NOTICE, SD_JOURNAL_METADATA, \ + "[%-6s] " fmt, LOG_GRP_ ## grp ## _TAG, ## __VA_ARGS__) + +/** Levels less severe than ``notice`` are not logged by default. */ +#define LOG_DEFAULT_LEVEL LOG_NOTICE + +#define kr_log_warning(grp, fmt, ...) \ + kr_log_fmt(LOG_GRP_ ## grp, LOG_WARNING, SD_JOURNAL_METADATA, \ + "[%-6s] " fmt, LOG_GRP_ ## grp ## _TAG, ## __VA_ARGS__) + +/** Significant error. The process continues, except for configuration errors during startup. */ +#define kr_log_error(grp, fmt, ...) \ + kr_log_fmt(LOG_GRP_ ## grp, LOG_ERR, SD_JOURNAL_METADATA, \ + "[%-6s] " fmt, LOG_GRP_ ## grp ## _TAG, ## __VA_ARGS__) + +/** Critical condition. The process dies. Bad configuration should not cause this. */ +#define kr_log_crit(grp, fmt, ...) \ + kr_log_fmt(LOG_GRP_ ## grp, LOG_CRIT, SD_JOURNAL_METADATA, \ + "[%-6s] " fmt, LOG_GRP_ ## grp ## _TAG, ## __VA_ARGS__) + +#define kr_log_deprecate(grp, fmt, ...) \ + kr_log_fmt(LOG_GRP_ ## grp, LOG_WARNING, SD_JOURNAL_METADATA, \ + "[%-6s] deprecation WARNING: " fmt, LOG_GRP_ ## grp ## _TAG, ## __VA_ARGS__) + +/** + * Logging function for user modules. Uses group LOG_GRP_MODULE and ``info`` level. + * @param fmt Format string + */ +#define kr_log(fmt, ...) \ + kr_log_fmt(LOG_GRP_MODULE, LOG_INFO, SD_JOURNAL_METADATA, \ + "[%-6s] " fmt, LOG_GRP_MODULE_TAG, ## __VA_ARGS__) + +/// @} + +struct kr_request; +struct kr_query; + +/** + * Log a debug-level message from a kr_request. Typically we call kr_log_q() instead. + * + * @param qry_uid query ID to append to request ID, 0 means "no query" + * @param indent level of indentation between [group ][req.qry] and message + * @param grp GROUP_NAME (without the LOG_GRP_ prefix) + * @param fmt printf-like format string + */ +#define kr_log_req(req, qry_id, indent, grp, fmt, ...) \ + kr_log_req1(req, qry_id, indent, LOG_GRP_ ## grp, LOG_GRP_ ## grp ## _TAG, fmt, ## __VA_ARGS__) +KR_EXPORT KR_PRINTF(6) +void kr_log_req1(const struct kr_request * const req, uint32_t qry_uid, + const unsigned int indent, enum kr_log_group group, const char *tag, const char *fmt, ...); + +/** + * Log a debug-level message from a kr_query. + * + * @param qry current query + * @param grp GROUP_NAME (without the LOG_GRP_ prefix) + * @param fmt printf-like format string + */ +#define kr_log_q(qry, grp, fmt, ...) kr_log_q1(qry, LOG_GRP_ ## grp, LOG_GRP_ ## grp ## _TAG, fmt, ## __VA_ARGS__) +KR_EXPORT KR_PRINTF(4) +void kr_log_q1(const struct kr_query *qry, enum kr_log_group group, const char *tag, const char *fmt, ...); + +/** + * Return whether a particular log group in a request is in debug/verbose mode. + * + * Typically you use this as condition to compute some data to be logged, + * in case that's considered too expensive to do unless it really gets logged. + * + * The request can be NULL, and there's a _qry() shorthand to specify query instead. + */ +#define kr_log_is_debug(grp, req) \ + __builtin_expect(kr_log_is_debug_fun(LOG_GRP_ ## grp, (req)), false) +#define kr_log_is_debug_qry(grp, qry) kr_log_is_debug(grp, (qry) ? (qry)->request : NULL) +KR_EXPORT +bool kr_log_is_debug_fun(enum kr_log_group group, const struct kr_request *req); + + +/* Helpers "internal" to log.* */ + +/** @internal + * + * If you don't have location, pass ("CODE_FILE=", "CODE_LINE=", "CODE_FUNC=") + * Others than systemd don't utilize these metadata. + */ +KR_EXPORT KR_PRINTF(6) +void kr_log_fmt(enum kr_log_group group, kr_log_level_t level, const char *file, const char *line, + const char *func, const char *fmt, ...); + +#define KR_LOG_SJM_STR(x) #x +#define SD_JOURNAL_METADATA "CODE_FILE=" __FILE__, "CODE_LINE=" KR_LOG_SJM_STR(__LINE__), "" + diff --git a/lib/meson.build b/lib/meson.build new file mode 100644 index 0000000..ec11da9 --- /dev/null +++ b/lib/meson.build @@ -0,0 +1,121 @@ +# libkres +# SPDX-License-Identifier: GPL-3.0-or-later + +libkres_src = files([ + 'cache/api.c', + 'cache/cdb_lmdb.c', + 'cache/entry_list.c', + 'cache/entry_pkt.c', + 'cache/entry_rr.c', + 'cache/knot_pkt.c', + 'cache/nsec1.c', + 'cache/nsec3.c', + 'cache/peek.c', + 'dnssec.c', + 'dnssec/nsec.c', + 'dnssec/nsec3.c', + 'dnssec/signature.c', + 'dnssec/ta.c', + 'generic/lru.c', + 'generic/queue.c', + 'generic/trie.c', + 'layer/cache.c', + 'layer/iterate.c', + 'layer/validate.c', + 'log.c', + 'module.c', + 'resolve.c', + 'rplan.c', + 'selection.c', + 'selection_forward.c', + 'selection_iter.c', + 'utils.c', + 'zonecut.c', +]) +c_src_lint += libkres_src + +libkres_headers = files([ + 'cache/api.h', + 'cache/cdb_api.h', + 'cache/cdb_lmdb.h', + 'cache/impl.h', + 'defines.h', + 'dnssec.h', + 'dnssec/nsec.h', + 'dnssec/nsec3.h', + 'dnssec/signature.h', + 'dnssec/ta.h', + 'generic/array.h', + 'generic/lru.h', + 'generic/pack.h', + 'generic/queue.h', + 'generic/trie.h', + 'layer.h', + 'layer/iterate.h', + 'log.h', + 'module.h', + 'resolve.h', + 'rplan.h', + 'selection.h', + 'selection_forward.h', + 'selection_iter.h', + 'utils.h', + 'zonecut.h', +]) + +unit_tests += [ + ['array', files('generic/test_array.c')], + ['lru', files('generic/test_lru.c')], + ['pack', files('generic/test_pack.c')], + ['queue', files('generic/test_queue.c')], + ['trie', files('generic/test_trie.c')], + ['module', files('test_module.c')], + ['rplan', files('test_rplan.c')], + ['utils', files('test_utils.c')], + ['zonecut', files('test_zonecut.c')], +] + +integr_tests += [ + ['cache_overflow', meson.current_source_dir() / 'cache' / 'overflow.test.integr'], + ['cache_minimal_nsec', meson.current_source_dir() / 'cache' / 'test.integr'], + ['iter_limits' , meson.current_source_dir() / 'layer' / 'test.integr'], + ['validate' , meson.current_source_dir() / 'layer' / 'validate.test.integr'], +] + +libkres_inc = include_directories('..') + +libkres_lib = library('kres', + libkres_src, + soversion: libkres_soversion, + include_directories: libkres_inc, + dependencies: [ + contrib_dep, + kresconfig_dep, + libuv, + lmdb, + libknot, + libdnssec, + gnutls, + luajit, + libsystemd, + ], + install: true, +) + +libkres_dep = declare_dependency( + include_directories: libkres_inc, + link_with: libkres_lib +) + +install_headers( + libkres_headers, + subdir: 'libkres', +) + +pkgconfig = import('pkgconfig') +pkgconfig.generate( + name: 'libkres', + description: 'Knot Resolver library', + url: 'https://knot-resolver.cz/', + libraries: [libkres_lib], +) diff --git a/lib/module.c b/lib/module.c new file mode 100644 index 0000000..83ae773 --- /dev/null +++ b/lib/module.c @@ -0,0 +1,148 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <stdlib.h> +#include <dlfcn.h> +#include <contrib/cleanup.h> + +#include "kresconfig.h" +#include "lib/defines.h" +#include "lib/utils.h" +#include "lib/module.h" + + +/* List of embedded modules. These aren't (un)loaded. */ +int iterate_init(struct kr_module *self); +int validate_init(struct kr_module *self); +int cache_init(struct kr_module *self); +kr_module_init_cb kr_module_get_embedded(const char *name) +{ + if (strcmp(name, "iterate") == 0) + return iterate_init; + if (strcmp(name, "validate") == 0) + return validate_init; + if (strcmp(name, "cache") == 0) + return cache_init; + return NULL; +} + +/** Load prefixed symbol. */ +static void *load_symbol(void *lib, const char *prefix, const char *name) +{ + auto_free char *symbol = kr_strcatdup(2, prefix, name); + return dlsym(lib, symbol); +} + +static int load_library(struct kr_module *module, const char *name, const char *path) +{ + if (kr_fails_assert(module && name && path)) + return kr_error(EINVAL); + /* Absolute or relative path (then only library search path is used). */ + auto_free char *lib_path = kr_strcatdup(4, path, "/", name, LIBEXT); + if (lib_path == NULL) { + return kr_error(ENOMEM); + } + + /* Workaround for buggy _fini/__attribute__((destructor)) and dlclose(), + * this keeps the library mapped until the program finishes though. */ + module->lib = dlopen(lib_path, RTLD_NOW | RTLD_NODELETE); + if (module->lib) { + return kr_ok(); + } + + return kr_error(ENOENT); +} + +/** Load C module symbols. */ +static int load_sym_c(struct kr_module *module, uint32_t api_required) +{ + module->init = kr_module_get_embedded(module->name); + if (module->init) { + return kr_ok(); + } + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wpedantic" /* casts after load_symbol() */ + /* Check if it's embedded first */ + /* Load dynamic library module */ + auto_free char *m_prefix = kr_strcatdup(2, module->name, "_"); + + /* Check ABI version, return error on mismatch. */ + module_api_cb *api = load_symbol(module->lib, m_prefix, "api"); + if (api == NULL) { + return kr_error(ENOENT); + } + if (api() != api_required) { + return kr_error(ENOTSUP); + } + + /* Load ABI by symbol names. */ + #define ML(symname) module->symname = \ + load_symbol(module->lib, m_prefix, #symname) + ML(init); + ML(deinit); + ML(config); + #undef ML + if (load_symbol(module->lib, m_prefix, "layer") + || load_symbol(module->lib, m_prefix, "props")) { + /* In case someone re-compiled against new kresd + * but haven't actually changed the symbols. */ + kr_log_error(SYSTEM, "module %s requires upgrade. Please refer to " + "https://knot-resolver.readthedocs.io/en/stable/upgrading.html", + module->name); + return kr_error(ENOTSUP); + } + + return kr_ok(); + #pragma GCC diagnostic pop +} + +int kr_module_load(struct kr_module *module, const char *name, const char *path) +{ + if (module == NULL || name == NULL) { + return kr_error(EINVAL); + } + + /* Initialize, keep userdata */ + void *data = module->data; + memset(module, 0, sizeof(struct kr_module)); + module->data = data; + module->name = strdup(name); + if (module->name == NULL) { + return kr_error(ENOMEM); + } + + /* Search for module library. */ + if (!path || load_library(module, name, path) != 0) { + module->lib = RTLD_DEFAULT; + } + + /* Try to load module ABI. */ + int ret = load_sym_c(module, KR_MODULE_API); + if (ret == 0 && module->init) { + ret = module->init(module); + } + if (ret != 0) { + kr_module_unload(module); + } + + return ret; +} + +void kr_module_unload(struct kr_module *module) +{ + if (module == NULL) { + return; + } + + if (module->deinit) { + module->deinit(module); + } + + if (module->lib && module->lib != RTLD_DEFAULT) { + dlclose(module->lib); + } + + free(module->name); + memset(module, 0, sizeof(struct kr_module)); +} diff --git a/lib/module.h b/lib/module.h new file mode 100644 index 0000000..7548803 --- /dev/null +++ b/lib/module.h @@ -0,0 +1,112 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/** @file + * Module API definition and functions for (un)loading modules. + */ + +#pragma once + +#include "lib/defines.h" +#include "lib/utils.h" +#include "lib/layer.h" + +struct kr_module; +struct kr_prop; + + +/** + * Export module API version (place this at the end of your module). + * + * @param module module name (e.g. policy) + */ +#define KR_MODULE_EXPORT(module) \ + KR_EXPORT uint32_t module ## _api() { return KR_MODULE_API; } +#define KR_MODULE_API ((uint32_t) 0x20210125) + +typedef uint32_t (module_api_cb)(void); + + +/** + * Module representation. + * + * The five symbols (init, ...) may be defined by the module as name_init(), etc; + * all are optional and missing symbols are represented as NULLs; + */ +struct kr_module { + char *name; + + /** Constructor. Called after loading the module. @return error code. + * Lua modules: not populated, called via lua directly. */ + int (*init)(struct kr_module *self); + + /** Destructor. Called before unloading the module. @return error code. */ + int (*deinit)(struct kr_module *self); + + /** Configure with encoded JSON (NULL if missing). @return error code. + * Lua modules: not used and not useful from C. + * When called from lua, input is JSON, like for kr_prop_cb. */ + int (*config)(struct kr_module *self, const char *input); + + /** Packet processing API specs. May be NULL. See docs on that type. + * Owned by the module code. */ + const kr_layer_api_t *layer; + + /** List of properties. May be NULL. Terminated by { NULL, NULL, NULL }. + * Lua modules: not used and not useful. */ + const struct kr_prop *props; + + /** dlopen() handle; RTLD_DEFAULT for embedded modules; NULL for lua modules. */ + void *lib; + void *data; /**< Custom data context. */ +}; + +/** + * Module property callback. Input and output is passed via a JSON encoded in a string. + * + * @param env pointer to the lua engine, i.e. struct engine *env (TODO: explicit type) + * @param input parameter (NULL if missing/nil on lua level) + * @return a free-form JSON output (malloc-ated) + * @note see modules_create_table_for_c() implementation for details + * about the input/output conversion. + */ +typedef char *(kr_prop_cb)(void *env, struct kr_module *self, const char *input); + +/** + * Module property (named callable). + */ +struct kr_prop { + kr_prop_cb *cb; + const char *name; + const char *info; +}; + + +/** + * Load a C module instance into memory. And call its init(). + * + * @param module module structure. Will be overwritten except for ->data on success. + * @param name module name + * @param path module search path + * @return 0 or an error + */ +KR_EXPORT +int kr_module_load(struct kr_module *module, const char *name, const char *path); + +/** + * Unload module instance. + * + * @param module module structure + * @note currently used even for lua modules + */ +KR_EXPORT +void kr_module_unload(struct kr_module *module); + +typedef int (*kr_module_init_cb)(struct kr_module *); +/** + * Get embedded module's init function by name (or NULL). + */ +KR_EXPORT +kr_module_init_cb kr_module_get_embedded(const char *name); + diff --git a/lib/resolve.c b/lib/resolve.c new file mode 100644 index 0000000..aa3d521 --- /dev/null +++ b/lib/resolve.c @@ -0,0 +1,1695 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <ctype.h> +#include <inttypes.h> +#include <stdio.h> +#include <fcntl.h> +#include <arpa/inet.h> +#include <libknot/rrtype/rdname.h> +#include <libknot/descriptor.h> +#include <ucw/mempool.h> +#include <sys/socket.h> +#include "lib/resolve.h" +#include "lib/layer.h" +#include "lib/rplan.h" +#include "lib/layer/iterate.h" +#include "lib/dnssec/ta.h" +#include "lib/dnssec.h" +#if ENABLE_COOKIES +#include "lib/cookies/control.h" +#include "lib/cookies/helper.h" +#include "lib/cookies/nonce.h" +#else /* Define compatibility macros */ +#define KNOT_EDNS_OPTION_COOKIE 10 +#endif /* ENABLE_COOKIES */ + +#define VERBOSE_MSG(qry, ...) kr_log_q((qry), RESOLVER, __VA_ARGS__) + +bool kr_rank_check(uint8_t rank) +{ + switch (rank & ~KR_RANK_AUTH) { + case KR_RANK_INITIAL: + case KR_RANK_OMIT: + case KR_RANK_TRY: + case KR_RANK_INDET: + case KR_RANK_BOGUS: + case KR_RANK_MISMATCH: + case KR_RANK_MISSING: + case KR_RANK_INSECURE: + case KR_RANK_SECURE: + return true; + default: + return false; + } +} + +bool kr_rank_test(uint8_t rank, uint8_t kr_flag) +{ + if (kr_fails_assert(kr_rank_check(rank) && kr_rank_check(kr_flag))) + return false; + if (kr_flag == KR_RANK_AUTH) { + return rank & KR_RANK_AUTH; + } + if (kr_fails_assert(!(kr_flag & KR_RANK_AUTH))) + return false; + /* The rest are exclusive values - exactly one has to be set. */ + return (rank & ~KR_RANK_AUTH) == kr_flag; +} + +/** @internal Set @a yielded to all RRs with matching @a qry_uid. */ +static void set_yield(ranked_rr_array_t *array, const uint32_t qry_uid, const bool yielded) +{ + for (unsigned i = 0; i < array->len; ++i) { + ranked_rr_array_entry_t *entry = array->at[i]; + if (entry->qry_uid == qry_uid) { + entry->yielded = yielded; + } + } +} + +/** + * @internal Defer execution of current query. + * The current layer state and input will be pushed to a stack and resumed on next iteration. + */ +static int consume_yield(kr_layer_t *ctx, knot_pkt_t *pkt) +{ + struct kr_request *req = ctx->req; + size_t pkt_size = pkt->size; + if (knot_pkt_has_tsig(pkt)) { + pkt_size += pkt->tsig_wire.len; + } + knot_pkt_t *pkt_copy = knot_pkt_new(NULL, pkt_size, &req->pool); + struct kr_layer_pickle *pickle = mm_alloc(&req->pool, sizeof(*pickle)); + if (pickle && pkt_copy && knot_pkt_copy(pkt_copy, pkt) == 0) { + struct kr_query *qry = req->current_query; + pickle->api = ctx->api; + pickle->state = ctx->state; + pickle->pkt = pkt_copy; + pickle->next = qry->deferred; + qry->deferred = pickle; + set_yield(&req->answ_selected, qry->uid, true); + set_yield(&req->auth_selected, qry->uid, true); + return kr_ok(); + } + return kr_error(ENOMEM); +} +static int begin_yield(kr_layer_t *ctx) { return kr_ok(); } +static int reset_yield(kr_layer_t *ctx) { return kr_ok(); } +static int finish_yield(kr_layer_t *ctx) { return kr_ok(); } +static int produce_yield(kr_layer_t *ctx, knot_pkt_t *pkt) { return kr_ok(); } +static int checkout_yield(kr_layer_t *ctx, knot_pkt_t *packet, struct sockaddr *dst, int type) { return kr_ok(); } +static int answer_finalize_yield(kr_layer_t *ctx) { return kr_ok(); } + +/** @internal Macro for iterating module layers. */ +#define RESUME_LAYERS(from, r, qry, func, ...) \ + (r)->current_query = (qry); \ + for (size_t i = (from); i < (r)->ctx->modules->len; ++i) { \ + struct kr_module *mod = (r)->ctx->modules->at[i]; \ + if (mod->layer) { \ + struct kr_layer layer = {.state = (r)->state, .api = mod->layer, .req = (r)}; \ + if (layer.api && layer.api->func) { \ + (r)->state = layer.api->func(&layer, ##__VA_ARGS__); \ + /* It's an easy mistake to return error code, for example. */ \ + /* (though we could allow such an overload later) */ \ + if (kr_fails_assert(kr_state_consistent((r)->state))) { \ + (r)->state = KR_STATE_FAIL; \ + } else \ + if ((r)->state == KR_STATE_YIELD) { \ + func ## _yield(&layer, ##__VA_ARGS__); \ + break; \ + } \ + } \ + } \ + } /* Invalidate current query. */ \ + (r)->current_query = NULL + +/** @internal Macro for starting module iteration. */ +#define ITERATE_LAYERS(req, qry, func, ...) RESUME_LAYERS(0, req, qry, func, ##__VA_ARGS__) + +/** @internal Find layer id matching API. */ +static inline size_t layer_id(struct kr_request *req, const struct kr_layer_api *api) { + module_array_t *modules = req->ctx->modules; + for (size_t i = 0; i < modules->len; ++i) { + if (modules->at[i]->layer == api) { + return i; + } + } + return 0; /* Not found, try all. */ +} + +/* @internal We don't need to deal with locale here */ +KR_CONST static inline bool isletter(unsigned chr) +{ return (chr | 0x20 /* tolower */) - 'a' <= 'z' - 'a'; } + +/* Randomize QNAME letter case. + * This adds 32 bits of randomness at maximum, but that's more than an average domain name length. + * https://tools.ietf.org/html/draft-vixie-dnsext-dns0x20-00 + */ +static void randomized_qname_case(knot_dname_t * restrict qname, uint32_t secret) +{ + if (secret == 0) + return; + if (kr_fails_assert(qname)) + return; + const int len = knot_dname_size(qname) - 2; /* Skip first, last label. First is length, last is always root */ + for (int i = 0; i < len; ++i) { + /* Note: this relies on the fact that correct label lengths + * can't pass the isletter() test (by "luck"). */ + if (isletter(*++qname)) { + *qname ^= ((secret >> (i & 31)) & 1) * 0x20; + } + } +} + +/** This turns of QNAME minimisation if there is a non-terminal between current zone cut, and name target. + * It save several minimization steps, as the zone cut is likely final one. + */ +static void check_empty_nonterms(struct kr_query *qry, knot_pkt_t *pkt, struct kr_cache *cache, uint32_t timestamp) +{ + // FIXME cleanup, etc. +#if 0 + if (qry->flags.NO_MINIMIZE) { + return; + } + + const knot_dname_t *target = qry->sname; + const knot_dname_t *cut_name = qry->zone_cut.name; + if (!target || !cut_name) + return; + + struct kr_cache_entry *entry = NULL; + /* @note: The non-terminal must be direct child of zone cut (e.g. label distance <= 2), + * otherwise this would risk leaking information to parent if the NODATA TTD > zone cut TTD. */ + int labels = knot_dname_labels(target, NULL) - knot_dname_labels(cut_name, NULL); + while (target[0] && labels > 2) { + target = knot_wire_next_label(target, NULL); + --labels; + } + for (int i = 0; i < labels; ++i) { + int ret = kr_cache_peek(cache, KR_CACHE_PKT, target, KNOT_RRTYPE_NS, &entry, ×tamp); + if (ret == 0) { /* Either NXDOMAIN or NODATA, start here. */ + /* @todo We could stop resolution here for NXDOMAIN, but we can't because of broken CDNs */ + qry->flags.NO_MINIMIZE = true; + kr_make_query(qry, pkt); + break; + } + kr_assert(target[0]); + target = knot_wire_next_label(target, NULL); + } + kr_cache_commit(cache); +#endif +} + +static int ns_fetch_cut(struct kr_query *qry, const knot_dname_t *requested_name, + struct kr_request *req, knot_pkt_t *pkt) +{ + /* It can occur that here parent query already have + * provably insecure zonecut which not in the cache yet. */ + struct kr_qflags pflags; + if (qry->parent) { + pflags = qry->parent->flags; + } + const bool is_insecure = qry->parent != NULL + && !(pflags.AWAIT_IPV4 || pflags.AWAIT_IPV6) + && (pflags.DNSSEC_INSECURE || pflags.DNSSEC_NODS); + + /* Want DNSSEC if it's possible to secure this name + * (e.g. is covered by any TA) */ + if (is_insecure) { + /* If parent is insecure we don't want DNSSEC + * even if cut name is covered by TA. */ + qry->flags.DNSSEC_WANT = false; + qry->flags.DNSSEC_INSECURE = true; + VERBOSE_MSG(qry, "=> going insecure because parent query is insecure\n"); + } else if (kr_ta_closest(req->ctx, qry->zone_cut.name, KNOT_RRTYPE_NS)) { + qry->flags.DNSSEC_WANT = true; + } else { + qry->flags.DNSSEC_WANT = false; + VERBOSE_MSG(qry, "=> going insecure because there's no covering TA\n"); + } + + struct kr_zonecut cut_found; + kr_zonecut_init(&cut_found, requested_name, req->rplan.pool); + /* Cut that has been found can differs from cut that has been requested. + * So if not already insecure, + * try to fetch ta & keys even if initial cut name not covered by TA */ + bool secure = !is_insecure; + int ret = kr_zonecut_find_cached(req->ctx, &cut_found, requested_name, + qry, &secure); + if (ret == kr_error(ENOENT)) { + /* No cached cut found, start from SBELT + * and issue priming query. */ + kr_zonecut_deinit(&cut_found); + ret = kr_zonecut_set_sbelt(req->ctx, &qry->zone_cut); + if (ret != 0) { + return KR_STATE_FAIL; + } + VERBOSE_MSG(qry, "=> using root hints\n"); + qry->flags.AWAIT_CUT = false; + return KR_STATE_DONE; + } else if (ret != kr_ok()) { + kr_zonecut_deinit(&cut_found); + return KR_STATE_FAIL; + } + + /* Find out security status. + * Go insecure if the zone cut is provably insecure */ + if ((qry->flags.DNSSEC_WANT) && !secure) { + VERBOSE_MSG(qry, "=> NS is provably without DS, going insecure\n"); + qry->flags.DNSSEC_WANT = false; + qry->flags.DNSSEC_INSECURE = true; + } + /* Zonecut name can change, check it again + * to prevent unnecessary DS & DNSKEY queries */ + if (!(qry->flags.DNSSEC_INSECURE) && + kr_ta_closest(req->ctx, cut_found.name, KNOT_RRTYPE_NS)) { + qry->flags.DNSSEC_WANT = true; + } else { + qry->flags.DNSSEC_WANT = false; + } + /* Check if any DNSKEY found for cached cut */ + if (qry->flags.DNSSEC_WANT && cut_found.key == NULL && + kr_zonecut_is_empty(&cut_found)) { + /* Cut found and there are no proofs of zone insecurity. + * But no DNSKEY found and no glue fetched. + * We have got circular dependency - must fetch A\AAAA + * from authoritative, but we have no key to verify it. */ + kr_zonecut_deinit(&cut_found); + if (requested_name[0] != '\0' ) { + /* If not root - try next label */ + return KR_STATE_CONSUME; + } + /* No cached cut & keys found, start from SBELT */ + ret = kr_zonecut_set_sbelt(req->ctx, &qry->zone_cut); + if (ret != 0) { + return KR_STATE_FAIL; + } + VERBOSE_MSG(qry, "=> using root hints\n"); + qry->flags.AWAIT_CUT = false; + return KR_STATE_DONE; + } + /* Use the found zone cut. */ + kr_zonecut_move(&qry->zone_cut, &cut_found); + /* Check if there's a non-terminal between target and current cut. */ + struct kr_cache *cache = &req->ctx->cache; + check_empty_nonterms(qry, pkt, cache, qry->timestamp.tv_sec); + /* Cut found */ + return KR_STATE_PRODUCE; +} + +static int edns_put(knot_pkt_t *pkt, bool reclaim) +{ + if (!pkt->opt_rr) { + return kr_ok(); + } + if (reclaim) { + /* Reclaim reserved size. */ + int ret = knot_pkt_reclaim(pkt, knot_edns_wire_size(pkt->opt_rr)); + if (ret != 0) { + return ret; + } + } + /* Write to packet. */ + if (kr_fails_assert(pkt->current == KNOT_ADDITIONAL)) + return kr_error(EINVAL); + return knot_pkt_put(pkt, KNOT_COMPR_HINT_NONE, pkt->opt_rr, KNOT_PF_FREE); +} + +/** Removes last EDNS OPT RR written to the packet. */ +static int edns_erase_and_reserve(knot_pkt_t *pkt) +{ + /* Nothing to be done. */ + if (!pkt || !pkt->opt_rr) { + return 0; + } + + /* Fail if the data are located elsewhere than at the end of packet. */ + if (pkt->current != KNOT_ADDITIONAL || + pkt->opt_rr != &pkt->rr[pkt->rrset_count - 1]) { + return -1; + } + + size_t len = knot_rrset_size(pkt->opt_rr); + int16_t rr_removed = pkt->opt_rr->rrs.count; + /* Decrease rrset counters. */ + pkt->rrset_count -= 1; + pkt->sections[pkt->current].count -= 1; + pkt->size -= len; + knot_wire_add_arcount(pkt->wire, -rr_removed); /* ADDITIONAL */ + + pkt->opt_rr = NULL; + + /* Reserve the freed space. */ + return knot_pkt_reserve(pkt, len); +} + +static inline size_t edns_padding_option_size(int32_t tls_padding) +{ + if (tls_padding == -1) + /* FIXME: we do not know how to reserve space for the + * default padding policy, since we can't predict what + * it will select. So i'm just guessing :/ */ + return KNOT_EDNS_OPTION_HDRLEN + 512; + if (tls_padding >= 2) + return KNOT_EDNS_OPTION_HDRLEN + tls_padding; + + return 0; +} + +static int edns_create(knot_pkt_t *pkt, const struct kr_request *req) +{ + pkt->opt_rr = knot_rrset_copy(req->ctx->upstream_opt_rr, &pkt->mm); + size_t wire_size = knot_edns_wire_size(pkt->opt_rr); +#if ENABLE_COOKIES + if (req->ctx->cookie_ctx.clnt.enabled || + req->ctx->cookie_ctx.srvr.enabled) { + wire_size += KR_COOKIE_OPT_MAX_LEN; + } +#endif /* ENABLE_COOKIES */ + if (req->qsource.flags.tls || req->qsource.comm_flags.tls) { + wire_size += edns_padding_option_size(req->ctx->tls_padding); + } + return knot_pkt_reserve(pkt, wire_size); +} + +/** + * @param all_secure optionally &&-combine security of written RRs into its value. + * (i.e. if you pass a pointer to false, it will always remain) + * @param all_cname optionally output if all written RRs are CNAMEs and RRSIGs of CNAMEs + * @return error code, ignoring if forced to truncate the packet. + */ +static int write_extra_ranked_records(const ranked_rr_array_t *arr, uint16_t reorder, + knot_pkt_t *answer, bool *all_secure, bool *all_cname) +{ + const bool has_dnssec = knot_pkt_has_dnssec(answer); + bool all_sec = true; + bool all_cn = (all_cname != NULL); /* optim.: init as false if not needed */ + int err = kr_ok(); + + for (size_t i = 0; i < arr->len; ++i) { + ranked_rr_array_entry_t * entry = arr->at[i]; + kr_assert(!entry->in_progress); + if (!entry->to_wire) { + continue; + } + knot_rrset_t *rr = entry->rr; + if (!has_dnssec) { + if (rr->type != knot_pkt_qtype(answer) && knot_rrtype_is_dnssec(rr->type)) { + continue; + } + } + err = knot_pkt_put_rotate(answer, 0, rr, reorder, 0); + if (err != KNOT_EOK) { + if (err == KNOT_ESPACE) { + err = kr_ok(); + } + break; + } + + if (rr->type != KNOT_RRTYPE_RRSIG) { + all_sec = all_sec && kr_rank_test(entry->rank, KR_RANK_SECURE); + } + all_cn = all_cn && kr_rrset_type_maysig(entry->rr) == KNOT_RRTYPE_CNAME; + } + + if (all_secure) { + *all_secure = *all_secure && all_sec; + } + if (all_cname) { + *all_cname = all_cn; + } + return err; +} + +static int pkt_padding(knot_pkt_t *packet, int32_t padding) +{ + knot_rrset_t *opt_rr = packet->opt_rr; + int32_t pad_bytes = -1; + + if (padding == -1) { /* use the default padding policy from libknot */ + pad_bytes = knot_pkt_default_padding_size(packet, opt_rr); + } + if (padding >= 2) { + int32_t max_pad_bytes = knot_edns_get_payload(opt_rr) - (packet->size + knot_rrset_size(opt_rr)); + pad_bytes = MIN(knot_edns_alignment_size(packet->size, knot_rrset_size(opt_rr), padding), + max_pad_bytes); + } + + if (pad_bytes >= 0) { + uint8_t zeros[MAX(1, pad_bytes)]; + memset(zeros, 0, sizeof(zeros)); + int r = knot_edns_add_option(opt_rr, KNOT_EDNS_OPTION_PADDING, + pad_bytes, zeros, &packet->mm); + if (r != KNOT_EOK) { + knot_rrset_clear(opt_rr, &packet->mm); + return kr_error(r); + } + } + return kr_ok(); +} + +/** @internal Add an EDNS padding RR into the answer if requested and required. */ +static int answer_padding(struct kr_request *request) +{ + if (kr_fails_assert(request && request->answer && request->ctx)) + return kr_error(EINVAL); + if (!request->qsource.flags.tls && !request->qsource.comm_flags.tls) { + /* Not meaningful to pad without encryption. */ + return kr_ok(); + } + return pkt_padding(request->answer, request->ctx->tls_padding); +} + +/* Make a clean SERVFAIL answer. */ +static void answer_fail(struct kr_request *request) +{ + /* Note: OPT in SERVFAIL response is still useful for cookies/additional info. */ + if (kr_log_is_debug(RESOLVER, request)) /* logging optimization */ + kr_log_req(request, 0, 0, RESOLVER, + "request failed, answering with empty SERVFAIL\n"); + knot_pkt_t *answer = request->answer; + knot_rrset_t *opt_rr = answer->opt_rr; /* it gets NULLed below */ + int ret = kr_pkt_clear_payload(answer); + knot_wire_clear_ad(answer->wire); + knot_wire_clear_aa(answer->wire); + knot_wire_set_rcode(answer->wire, KNOT_RCODE_SERVFAIL); + if (ret == 0 && opt_rr) { + knot_pkt_begin(answer, KNOT_ADDITIONAL); + answer->opt_rr = opt_rr; + answer_padding(request); /* Ignore failed padding in SERVFAIL answer. */ + edns_put(answer, false); + } +} + +/* Append EDNS records into the answer. */ +static int answer_append_edns(struct kr_request *request) +{ + knot_pkt_t *answer = request->answer; + if (!answer->opt_rr) + return kr_ok(); + int ret = answer_padding(request); + if (!ret) ret = knot_pkt_begin(answer, KNOT_ADDITIONAL); + if (!ret) ret = knot_pkt_put(answer, KNOT_COMPR_HINT_NONE, + answer->opt_rr, KNOT_PF_FREE); + return ret; +} + +static void answer_finalize(struct kr_request *request) +{ + struct kr_rplan *rplan = &request->rplan; + knot_pkt_t *answer = request->answer; + const uint8_t *q_wire = request->qsource.packet->wire; + + if (answer->rrset_count != 0) { + /* Non-standard: we assume the answer had been constructed. + * Let's check we don't have a "collision". */ + const ranked_rr_array_t *selected[] = kr_request_selected(request); + for (int psec = KNOT_ANSWER; psec <= KNOT_ADDITIONAL; ++psec) { + const ranked_rr_array_t *arr = selected[psec]; + for (ssize_t i = 0; i < arr->len; ++i) { + if (kr_fails_assert(!arr->at[i]->to_wire)) { + answer_fail(request); + return; + } + } + } + /* We only add EDNS, and we even assume AD bit was correct. */ + if (answer_append_edns(request)) { + answer_fail(request); + return; + } + return; + } + + struct kr_query *const last = + rplan->resolved.len > 0 ? array_tail(rplan->resolved) : NULL; + /* TODO ^^^^ this is slightly fragile */ + + if (!last) { + /* Suspicious: no kr_query got resolved (not even from cache), + * so let's (defensively) SERVFAIL the request. + * ATM many checks below depend on `last` anyway, + * so this helps to avoid surprises. */ + answer_fail(request); + return; + } + /* TODO: clean this up in !660 or followup, and it isn't foolproof anyway. */ + if (last->flags.DNSSEC_BOGUS + || (rplan->pending.len > 0 && array_tail(rplan->pending)->flags.DNSSEC_BOGUS)) { + if (!knot_wire_get_cd(q_wire)) { + answer_fail(request); + return; + } + } + + /* AD flag. We can only change `secure` from true to false. + * Be conservative. Primary approach: check ranks of all RRs in wire. + * Only "negative answers" need special handling. */ + bool secure = request->state == KR_STATE_DONE /*< suspicious otherwise */ + && knot_pkt_qtype(answer) != KNOT_RRTYPE_RRSIG; + if (last->flags.STUB) { + secure = false; /* don't trust forwarding for now */ + } + if (last->flags.DNSSEC_OPTOUT) { + VERBOSE_MSG(last, "insecure because of opt-out\n"); + secure = false; /* the last answer is insecure due to opt-out */ + } + + /* Write all RRsets meant for the answer. */ + bool answ_all_cnames = false/*arbitrary*/; + if (knot_pkt_begin(answer, KNOT_ANSWER) + || write_extra_ranked_records(&request->answ_selected, last->reorder, + answer, &secure, &answ_all_cnames) + || knot_pkt_begin(answer, KNOT_AUTHORITY) + || write_extra_ranked_records(&request->auth_selected, last->reorder, + answer, &secure, NULL) + || knot_pkt_begin(answer, KNOT_ADDITIONAL) + || write_extra_ranked_records(&request->add_selected, last->reorder, + answer, NULL/*not relevant to AD*/, NULL) + || answer_append_edns(request) + ) + { + answer_fail(request); + return; + } + + /* AD: "negative answers" need more handling. */ + if (kr_response_classify(answer) != PKT_NOERROR + /* Additionally check for CNAME chains that "end in NODATA", + * as those would also be PKT_NOERROR. */ + || (answ_all_cnames && knot_pkt_qtype(answer) != KNOT_RRTYPE_CNAME)) { + + secure = secure && last->flags.DNSSEC_WANT + && !last->flags.DNSSEC_BOGUS && !last->flags.DNSSEC_INSECURE; + } + + if (secure) { + struct kr_query *cname_parent = last->cname_parent; + while (cname_parent != NULL) { + if (cname_parent->flags.DNSSEC_OPTOUT) { + secure = false; + break; + } + cname_parent = cname_parent->cname_parent; + } + } + + /* No detailed analysis ATM, just _SECURE or not. + * LATER: request->rank might better be computed in validator's finish phase. */ + VERBOSE_MSG(last, "AD: request%s classified as SECURE\n", secure ? "" : " NOT"); + request->rank = secure ? KR_RANK_SECURE : KR_RANK_INITIAL; + + /* Set AD if secure and AD bit "was requested". */ + if (secure && !knot_wire_get_cd(q_wire) + && (knot_pkt_has_dnssec(answer) || knot_wire_get_ad(q_wire))) { + knot_wire_set_ad(answer->wire); + } +} + +static int query_finalize(struct kr_request *request, struct kr_query *qry, knot_pkt_t *pkt) +{ + knot_pkt_begin(pkt, KNOT_ADDITIONAL); + if (qry->flags.NO_EDNS) + return kr_ok(); + /* Remove any EDNS records from any previous iteration. */ + int ret = edns_erase_and_reserve(pkt); + if (ret) return ret; + ret = edns_create(pkt, request); + if (ret) return ret; + if (qry->flags.STUB) { + /* Stub resolution */ + knot_wire_set_rd(pkt->wire); + if (knot_wire_get_cd(request->qsource.packet->wire)) { + knot_wire_set_cd(pkt->wire); + } + } else { + /* Full resolution (ask for +cd and +do) */ + knot_edns_set_do(pkt->opt_rr); + knot_wire_set_cd(pkt->wire); + if (qry->flags.FORWARD) { + knot_wire_set_rd(pkt->wire); + } + } + return kr_ok(); +} + +int kr_resolve_begin(struct kr_request *request, struct kr_context *ctx) +{ + /* Initialize request */ + request->ctx = ctx; + request->answer = NULL; + request->options = ctx->options; + request->state = KR_STATE_CONSUME; + request->current_query = NULL; + array_init(request->answ_selected); + array_init(request->auth_selected); + array_init(request->add_selected); + request->answ_validated = false; + request->auth_validated = false; + request->rank = KR_RANK_INITIAL; + request->trace_log = NULL; + request->trace_finish = NULL; + + /* Expect first query */ + kr_rplan_init(&request->rplan, request, &request->pool); + return KR_STATE_CONSUME; +} + +static int resolve_query(struct kr_request *request, const knot_pkt_t *packet) +{ + struct kr_rplan *rplan = &request->rplan; + const knot_dname_t *qname = knot_pkt_qname(packet); + uint16_t qclass = knot_pkt_qclass(packet); + uint16_t qtype = knot_pkt_qtype(packet); + struct kr_query *qry = NULL; + struct kr_context *ctx = request->ctx; + struct kr_cookie_ctx *cookie_ctx = ctx ? &ctx->cookie_ctx : NULL; + + if (qname != NULL) { + qry = kr_rplan_push(rplan, NULL, qname, qclass, qtype); + } else if (cookie_ctx && cookie_ctx->srvr.enabled && + knot_wire_get_qdcount(packet->wire) == 0 && + knot_pkt_has_edns(packet) && + knot_pkt_edns_option(packet, KNOT_EDNS_OPTION_COOKIE)) { + /* Plan empty query only for cookies. */ + qry = kr_rplan_push_empty(rplan, NULL); + } + if (!qry) { + return KR_STATE_FAIL; + } + + if (qname != NULL) { + /* Deferred zone cut lookup for this query. */ + qry->flags.AWAIT_CUT = true; + /* Want DNSSEC if it's possible to secure this name (e.g. is covered by any TA) */ + if ((knot_wire_get_ad(packet->wire) || knot_pkt_has_dnssec(packet)) && + kr_ta_closest(request->ctx, qry->sname, qtype)) { + qry->flags.DNSSEC_WANT = true; + } + } + + /* Expect answer, pop if satisfied immediately */ + ITERATE_LAYERS(request, qry, begin); + if ((request->state & KR_STATE_DONE) != 0) { + kr_rplan_pop(rplan, qry); + } else if (qname == NULL) { + /* it is an empty query which must be resolved by + `begin` layer of cookie module. + If query isn't resolved, fail. */ + request->state = KR_STATE_FAIL; + } + return request->state; +} + +knot_rrset_t* kr_request_ensure_edns(struct kr_request *request) +{ + kr_require(request && request->answer && request->qsource.packet && request->ctx); + knot_pkt_t* answer = request->answer; + bool want_edns = knot_pkt_has_edns(request->qsource.packet); + if (!want_edns) { + kr_assert(!answer->opt_rr); + return answer->opt_rr; + } else if (answer->opt_rr) { + return answer->opt_rr; + } + + kr_assert(request->ctx->downstream_opt_rr); + answer->opt_rr = knot_rrset_copy(request->ctx->downstream_opt_rr, &answer->mm); + if (!answer->opt_rr) + return NULL; + if (knot_pkt_has_dnssec(request->qsource.packet)) + knot_edns_set_do(answer->opt_rr); + return answer->opt_rr; +} + +knot_pkt_t *kr_request_ensure_answer(struct kr_request *request) +{ + if (request->options.NO_ANSWER) { + kr_assert(request->state & KR_STATE_FAIL); + return NULL; + } + if (request->answer) + return request->answer; + + const knot_pkt_t *qs_pkt = request->qsource.packet; + if (kr_fails_assert(qs_pkt)) + goto fail; + // Find answer_max: limit on DNS wire length. + uint16_t answer_max; + const struct kr_request_qsource_flags *qs_flags = &request->qsource.flags; + const struct kr_request_qsource_flags *qs_cflags = &request->qsource.comm_flags; + if (kr_fails_assert(!(qs_flags->tls || qs_cflags->tls || qs_cflags->http) || qs_flags->tcp)) + goto fail; + if (!request->qsource.addr || qs_flags->tcp || qs_cflags->tcp) { + // not on UDP + answer_max = KNOT_WIRE_MAX_PKTSIZE; + } else if (knot_pkt_has_edns(qs_pkt)) { + // UDP with EDNS + answer_max = MIN(knot_edns_get_payload(qs_pkt->opt_rr), + knot_edns_get_payload(request->ctx->downstream_opt_rr)); + answer_max = MAX(answer_max, KNOT_WIRE_MIN_PKTSIZE); + } else { + // UDP without EDNS + answer_max = KNOT_WIRE_MIN_PKTSIZE; + } + + // Allocate the packet. + uint8_t *wire = NULL; + if (request->alloc_wire_cb) { + wire = request->alloc_wire_cb(request, &answer_max); + if (!wire) + goto enomem; + } + knot_pkt_t *answer = request->answer = + knot_pkt_new(wire, answer_max, &request->pool); + if (!answer || knot_pkt_init_response(answer, qs_pkt) != 0) { + kr_assert(!answer); // otherwise we messed something up + goto enomem; + } + if (!wire) + wire = answer->wire; + + // Much was done by knot_pkt_init_response() + knot_wire_set_ra(wire); + knot_wire_set_rcode(wire, KNOT_RCODE_NOERROR); + if (knot_wire_get_cd(qs_pkt->wire)) { + knot_wire_set_cd(wire); + } + + // Prepare EDNS if required. + if (knot_pkt_has_edns(qs_pkt) && kr_fails_assert(kr_request_ensure_edns(request))) + goto enomem; // answer is on mempool, so "leak" is OK + + return request->answer; +enomem: +fail: + request->state = KR_STATE_FAIL; // TODO: really combine with another flag? + return request->answer = NULL; +} + +int kr_resolve_consume(struct kr_request *request, struct kr_transport **transport, knot_pkt_t *packet) +{ + struct kr_rplan *rplan = &request->rplan; + + /* Empty resolution plan, push packet as the new query */ + if (packet && kr_rplan_empty(rplan)) { + return resolve_query(request, packet); + } + + /* Different processing for network error */ + struct kr_query *qry = array_tail(rplan->pending); + /* Check overall resolution time */ + if (kr_now() - qry->creation_time_mono >= KR_RESOLVE_TIME_LIMIT) { + kr_query_inform_timeout(request, qry); + return KR_STATE_FAIL; + } + bool tried_tcp = (qry->flags.TCP); + if (!packet || packet->size == 0) + return KR_STATE_PRODUCE; + + /* Packet cleared, derandomize QNAME. */ + knot_dname_t *qname_raw = kr_pkt_qname_raw(packet); + if (qname_raw && qry->secret != 0) { + randomized_qname_case(qname_raw, qry->secret); + } + request->state = KR_STATE_CONSUME; + if (qry->flags.CACHED) { + ITERATE_LAYERS(request, qry, consume, packet); + } else { + /* Fill in source and latency information. */ + request->upstream.rtt = kr_now() - qry->timestamp_mono; + request->upstream.transport = transport ? *transport : NULL; + ITERATE_LAYERS(request, qry, consume, packet); + /* Clear temporary information */ + request->upstream.transport = NULL; + request->upstream.rtt = 0; + } + + if (transport && !qry->flags.CACHED) { + if (!(request->state & KR_STATE_FAIL)) { + /* Do not complete NS address resolution on soft-fail. */ + const int rcode = knot_wire_get_rcode(packet->wire); + if (rcode != KNOT_RCODE_SERVFAIL && rcode != KNOT_RCODE_REFUSED) { + qry->flags.AWAIT_IPV6 = false; + qry->flags.AWAIT_IPV4 = false; + } + } + } + + if (request->state & KR_STATE_FAIL) { + qry->flags.RESOLVED = false; + } + + if (!qry->flags.CACHED) { + if (request->state & KR_STATE_FAIL) { + if (++request->count_fail_row > KR_CONSUME_FAIL_ROW_LIMIT) { + if (kr_log_is_debug(RESOLVER, request)) { /* logging optimization */ + kr_log_req(request, 0, 2, RESOLVER, + "=> too many failures in a row, " + "bail out (mitigation for NXNSAttack " + "CVE-2020-12667)\n"); + } + if (!qry->flags.NO_NS_FOUND) { + qry->flags.NO_NS_FOUND = true; + return KR_STATE_PRODUCE; + } + return KR_STATE_FAIL; + } + } else { + request->count_fail_row = 0; + } + } + + /* Pop query if resolved. */ + if (request->state == KR_STATE_YIELD) { + return KR_STATE_PRODUCE; /* Requery */ + } else if (qry->flags.RESOLVED) { + kr_rplan_pop(rplan, qry); + } else if (!tried_tcp && (qry->flags.TCP)) { + return KR_STATE_PRODUCE; /* Requery over TCP */ + } else { /* Clear query flags for next attempt */ + qry->flags.CACHED = false; + if (!request->options.TCP) { + qry->flags.TCP = false; + } + } + + ITERATE_LAYERS(request, qry, reset); + + /* Do not finish with bogus answer. */ + if (qry->flags.DNSSEC_BOGUS) { + if (qry->flags.FORWARD || qry->flags.STUB) { + return KR_STATE_FAIL; + } + /* Other servers might not have broken DNSSEC. */ + qry->flags.DNSSEC_BOGUS = false; + return KR_STATE_PRODUCE; + } + + return kr_rplan_empty(&request->rplan) ? KR_STATE_DONE : KR_STATE_PRODUCE; +} + +/** @internal Spawn subrequest in current zone cut (no minimization or lookup). */ +static struct kr_query *zone_cut_subreq(struct kr_rplan *rplan, struct kr_query *parent, + const knot_dname_t *qname, uint16_t qtype) +{ + struct kr_query *next = kr_rplan_push(rplan, parent, qname, parent->sclass, qtype); + if (!next) { + return NULL; + } + kr_zonecut_set(&next->zone_cut, parent->zone_cut.name); + if (kr_zonecut_copy(&next->zone_cut, &parent->zone_cut) != 0 || + kr_zonecut_copy_trust(&next->zone_cut, &parent->zone_cut) != 0) { + return NULL; + } + next->flags.NO_MINIMIZE = true; + if (parent->flags.DNSSEC_WANT) { + next->flags.DNSSEC_WANT = true; + } + return next; +} + +static int forward_trust_chain_check(struct kr_request *request, struct kr_query *qry, bool resume) +{ + struct kr_rplan *rplan = &request->rplan; + trie_t *trust_anchors = request->ctx->trust_anchors; + trie_t *negative_anchors = request->ctx->negative_anchors; + + if (qry->parent != NULL && + !(qry->forward_flags.CNAME) && + !(qry->flags.DNS64_MARK) && + knot_dname_in_bailiwick(qry->zone_cut.name, qry->parent->zone_cut.name) >= 0) { + return KR_STATE_PRODUCE; + } + + if (kr_fails_assert(qry->flags.FORWARD)) + return KR_STATE_FAIL; + + if (!trust_anchors) { + qry->flags.AWAIT_CUT = false; + return KR_STATE_PRODUCE; + } + + if (qry->flags.DNSSEC_INSECURE) { + qry->flags.AWAIT_CUT = false; + return KR_STATE_PRODUCE; + } + + if (qry->forward_flags.NO_MINIMIZE) { + qry->flags.AWAIT_CUT = false; + return KR_STATE_PRODUCE; + } + + const knot_dname_t *start_name = qry->sname; + if ((qry->flags.AWAIT_CUT) && !resume) { + qry->flags.AWAIT_CUT = false; + const knot_dname_t *longest_ta = kr_ta_closest(request->ctx, qry->sname, qry->stype); + if (longest_ta) { + start_name = longest_ta; + qry->zone_cut.name = knot_dname_copy(start_name, qry->zone_cut.pool); + qry->flags.DNSSEC_WANT = true; + } else { + qry->flags.DNSSEC_WANT = false; + return KR_STATE_PRODUCE; + } + } + + bool has_ta = (qry->zone_cut.trust_anchor != NULL); + knot_dname_t *ta_name = (has_ta ? qry->zone_cut.trust_anchor->owner : NULL); + bool refetch_ta = (!has_ta || !knot_dname_is_equal(qry->zone_cut.name, ta_name)); + bool is_dnskey_subreq = kr_rplan_satisfies(qry, ta_name, KNOT_CLASS_IN, KNOT_RRTYPE_DNSKEY); + bool refetch_key = has_ta && (!qry->zone_cut.key || !knot_dname_is_equal(ta_name, qry->zone_cut.key->owner)); + if (refetch_key && !is_dnskey_subreq) { + struct kr_query *next = zone_cut_subreq(rplan, qry, ta_name, KNOT_RRTYPE_DNSKEY); + if (!next) { + return KR_STATE_FAIL; + } + return KR_STATE_DONE; + } + + int name_offset = 1; + const knot_dname_t *wanted_name; + bool nods, ds_req, ns_req, minimized, ns_exist; + do { + wanted_name = start_name; + ds_req = false; + ns_req = false; + ns_exist = true; + + int cut_labels = knot_dname_labels(qry->zone_cut.name, NULL); + int wanted_name_labels = knot_dname_labels(wanted_name, NULL); + while (wanted_name[0] && wanted_name_labels > cut_labels + name_offset) { + wanted_name = knot_wire_next_label(wanted_name, NULL); + wanted_name_labels -= 1; + } + minimized = (wanted_name != qry->sname); + + for (int i = 0; i < request->rplan.resolved.len; ++i) { + struct kr_query *q = request->rplan.resolved.at[i]; + if (q->parent == qry && + q->sclass == qry->sclass && + (q->stype == KNOT_RRTYPE_DS || q->stype == KNOT_RRTYPE_NS) && + knot_dname_is_equal(q->sname, wanted_name)) { + if (q->stype == KNOT_RRTYPE_DS) { + ds_req = true; + if (q->flags.CNAME) { + ns_exist = false; + } else if (!(q->flags.DNSSEC_OPTOUT)) { + int ret = kr_dnssec_matches_name_and_type(&request->auth_selected, q->uid, + wanted_name, KNOT_RRTYPE_NS); + ns_exist = (ret == kr_ok()); + } + } else { + if (q->flags.CNAME) { + ns_exist = false; + } + ns_req = true; + } + } + } + + if (ds_req && ns_exist && !ns_req && (minimized || resume)) { + struct kr_query *next = zone_cut_subreq(rplan, qry, wanted_name, + KNOT_RRTYPE_NS); + if (!next) { + return KR_STATE_FAIL; + } + return KR_STATE_DONE; + } + + if (qry->parent == NULL && (qry->flags.CNAME) && + ds_req && ns_req) { + return KR_STATE_PRODUCE; + } + + /* set `nods` */ + if ((qry->stype == KNOT_RRTYPE_DS) && + knot_dname_is_equal(wanted_name, qry->sname)) { + nods = true; + } else if (resume && !ds_req) { + nods = false; + } else if (!minimized && qry->stype != KNOT_RRTYPE_DNSKEY) { + nods = true; + } else { + nods = ds_req; + } + name_offset += 1; + } while (ds_req && (ns_req || !ns_exist) && minimized); + + /* Disable DNSSEC if it enters NTA. */ + if (kr_ta_get(negative_anchors, wanted_name)){ + VERBOSE_MSG(qry, ">< negative TA, going insecure\n"); + qry->flags.DNSSEC_WANT = false; + } + + /* Enable DNSSEC if enters a new island of trust. */ + bool want_secure = (qry->flags.DNSSEC_WANT) && + !knot_wire_get_cd(request->qsource.packet->wire); + if (!(qry->flags.DNSSEC_WANT) && + !knot_wire_get_cd(request->qsource.packet->wire) && + kr_ta_get(trust_anchors, wanted_name)) { + qry->flags.DNSSEC_WANT = true; + want_secure = true; + if (kr_log_is_debug_qry(RESOLVER, qry)) { + KR_DNAME_GET_STR(qname_str, wanted_name); + VERBOSE_MSG(qry, ">< TA: '%s'\n", qname_str); + } + } + + if (want_secure && !qry->zone_cut.trust_anchor) { + knot_rrset_t *ta_rr = kr_ta_get(trust_anchors, wanted_name); + if (!ta_rr) { + char name[] = "\0"; + ta_rr = kr_ta_get(trust_anchors, (knot_dname_t*)name); + } + if (ta_rr) { + qry->zone_cut.trust_anchor = knot_rrset_copy(ta_rr, qry->zone_cut.pool); + } + } + + has_ta = (qry->zone_cut.trust_anchor != NULL); + ta_name = (has_ta ? qry->zone_cut.trust_anchor->owner : NULL); + refetch_ta = (!has_ta || !knot_dname_is_equal(wanted_name, ta_name)); + if (!nods && want_secure && refetch_ta) { + struct kr_query *next = zone_cut_subreq(rplan, qry, wanted_name, + KNOT_RRTYPE_DS); + if (!next) { + return KR_STATE_FAIL; + } + return KR_STATE_DONE; + } + + /* Try to fetch missing DNSKEY. + * Do not fetch if this is a DNSKEY subrequest to avoid circular dependency. */ + is_dnskey_subreq = kr_rplan_satisfies(qry, ta_name, KNOT_CLASS_IN, KNOT_RRTYPE_DNSKEY); + refetch_key = has_ta && (!qry->zone_cut.key || !knot_dname_is_equal(ta_name, qry->zone_cut.key->owner)); + if (want_secure && refetch_key && !is_dnskey_subreq) { + struct kr_query *next = zone_cut_subreq(rplan, qry, ta_name, KNOT_RRTYPE_DNSKEY); + if (!next) { + return KR_STATE_FAIL; + } + return KR_STATE_DONE; + } + + return KR_STATE_PRODUCE; +} + +/* @todo: Validator refactoring, keep this in driver for now. */ +static int trust_chain_check(struct kr_request *request, struct kr_query *qry) +{ + struct kr_rplan *rplan = &request->rplan; + trie_t *trust_anchors = request->ctx->trust_anchors; + trie_t *negative_anchors = request->ctx->negative_anchors; + + /* Disable DNSSEC if it enters NTA. */ + if (kr_ta_get(negative_anchors, qry->zone_cut.name)){ + VERBOSE_MSG(qry, ">< negative TA, going insecure\n"); + qry->flags.DNSSEC_WANT = false; + qry->flags.DNSSEC_INSECURE = true; + } + if (qry->flags.DNSSEC_NODS) { + /* This is the next query iteration with minimized qname. + * At previous iteration DS non-existence has been proven */ + VERBOSE_MSG(qry, "<= DS doesn't exist, going insecure\n"); + qry->flags.DNSSEC_NODS = false; + qry->flags.DNSSEC_WANT = false; + qry->flags.DNSSEC_INSECURE = true; + } + /* Enable DNSSEC if entering a new (or different) island of trust, + * and update the TA RRset if required. */ + const bool has_cd = knot_wire_get_cd(request->qsource.packet->wire); + knot_rrset_t *ta_rr = kr_ta_get(trust_anchors, qry->zone_cut.name); + if (!has_cd && ta_rr) { + qry->flags.DNSSEC_WANT = true; + if (qry->zone_cut.trust_anchor == NULL + || !knot_dname_is_equal(qry->zone_cut.trust_anchor->owner, qry->zone_cut.name)) { + mm_free(qry->zone_cut.pool, qry->zone_cut.trust_anchor); + qry->zone_cut.trust_anchor = knot_rrset_copy(ta_rr, qry->zone_cut.pool); + + if (kr_log_is_debug_qry(RESOLVER, qry)) { + KR_DNAME_GET_STR(qname_str, ta_rr->owner); + VERBOSE_MSG(qry, ">< TA: '%s'\n", qname_str); + } + } + } + + /* Try to fetch missing DS (from above the cut). */ + const bool has_ta = (qry->zone_cut.trust_anchor != NULL); + const knot_dname_t *ta_name = (has_ta ? qry->zone_cut.trust_anchor->owner : NULL); + const bool refetch_ta = !has_ta || !knot_dname_is_equal(qry->zone_cut.name, ta_name); + const bool want_secure = qry->flags.DNSSEC_WANT && !has_cd; + if (want_secure && refetch_ta) { + /* @todo we could fetch the information from the parent cut, but we don't remember that now */ + struct kr_query *next = kr_rplan_push(rplan, qry, qry->zone_cut.name, qry->sclass, KNOT_RRTYPE_DS); + if (!next) { + return KR_STATE_FAIL; + } + next->flags.AWAIT_CUT = true; + next->flags.DNSSEC_WANT = true; + return KR_STATE_DONE; + } + /* Try to fetch missing DNSKEY (either missing or above current cut). + * Do not fetch if this is a DNSKEY subrequest to avoid circular dependency. */ + const bool is_dnskey_subreq = kr_rplan_satisfies(qry, ta_name, KNOT_CLASS_IN, KNOT_RRTYPE_DNSKEY); + const bool refetch_key = has_ta && (!qry->zone_cut.key || !knot_dname_is_equal(ta_name, qry->zone_cut.key->owner)); + if (want_secure && refetch_key && !is_dnskey_subreq) { + struct kr_query *next = zone_cut_subreq(rplan, qry, ta_name, KNOT_RRTYPE_DNSKEY); + if (!next) { + return KR_STATE_FAIL; + } + return KR_STATE_DONE; + } + + return KR_STATE_PRODUCE; +} + +/** @internal Check current zone cut status and credibility, spawn subrequests if needed. */ +static int zone_cut_check(struct kr_request *request, struct kr_query *qry, knot_pkt_t *packet) +/* TODO: using cache on this point in this way just isn't nice; remove in time */ +{ + /* Stub mode, just forward and do not solve cut. */ + if (qry->flags.STUB) { + return KR_STATE_PRODUCE; + } + + /* Forwarding to upstream resolver mode. + * Since forwarding targets already are in qry->ns - + * cut fetching is not needed. */ + if (qry->flags.FORWARD) { + return forward_trust_chain_check(request, qry, false); + } + if (!(qry->flags.AWAIT_CUT)) { + /* The query was resolved from cache. + * Spawn DS \ DNSKEY requests if needed and exit */ + return trust_chain_check(request, qry); + } + + /* The query wasn't resolved from cache, + * now it's the time to look up closest zone cut from cache. */ + struct kr_cache *cache = &request->ctx->cache; + if (!kr_cache_is_open(cache)) { + int ret = kr_zonecut_set_sbelt(request->ctx, &qry->zone_cut); + if (ret != 0) { + return KR_STATE_FAIL; + } + VERBOSE_MSG(qry, "=> no cache open, using root hints\n"); + qry->flags.AWAIT_CUT = false; + return KR_STATE_DONE; + } + + const knot_dname_t *requested_name = qry->sname; + /* If at/subdomain of parent zone cut, start from its encloser. + * This is for case when we get to a dead end + * (and need glue from parent), or DS refetch. */ + if (qry->parent) { + const knot_dname_t *parent = qry->parent->zone_cut.name; + if (parent[0] != '\0' + && knot_dname_in_bailiwick(qry->sname, parent) >= 0) { + requested_name = knot_wire_next_label(parent, NULL); + } + } else if ((qry->stype == KNOT_RRTYPE_DS) && (qry->sname[0] != '\0')) { + /* If this is explicit DS query, start from encloser too. */ + requested_name = knot_wire_next_label(requested_name, NULL); + } + + int state = KR_STATE_FAIL; + do { + state = ns_fetch_cut(qry, requested_name, request, packet); + if (state == KR_STATE_DONE || (state & KR_STATE_FAIL)) { + return state; + } else if (state == KR_STATE_CONSUME) { + requested_name = knot_wire_next_label(requested_name, NULL); + } + } while (state == KR_STATE_CONSUME); + + /* Update minimized QNAME if zone cut changed */ + if (qry->zone_cut.name && qry->zone_cut.name[0] != '\0' && !(qry->flags.NO_MINIMIZE)) { + if (kr_make_query(qry, packet) != 0) { + return KR_STATE_FAIL; + } + } + qry->flags.AWAIT_CUT = false; + + /* Check trust chain */ + return trust_chain_check(request, qry); +} + + +static int ns_resolve_addr(struct kr_query *qry, struct kr_request *param, struct kr_transport *transport, uint16_t next_type) +{ + struct kr_rplan *rplan = ¶m->rplan; + struct kr_context *ctx = param->ctx; + + + /* Start NS queries from root, to avoid certain cases + * where a NS drops out of cache and the rest is unavailable, + * this would lead to dependency loop in current zone cut. + */ + + /* Bail out if the query is already pending or dependency loop. */ + if (!next_type || kr_rplan_satisfies(qry->parent, transport->ns_name, KNOT_CLASS_IN, next_type)) { + /* Fall back to SBELT if root server query fails. */ + if (!next_type && qry->zone_cut.name[0] == '\0') { + VERBOSE_MSG(qry, "=> fallback to root hints\n"); + kr_zonecut_set_sbelt(ctx, &qry->zone_cut); + return kr_error(EAGAIN); + } + /* No IPv4 nor IPv6, flag server as unusable. */ + VERBOSE_MSG(qry, "=> unresolvable NS address, bailing out\n"); + kr_zonecut_del_all(&qry->zone_cut, transport->ns_name); + return kr_error(EHOSTUNREACH); + } + /* Push new query to the resolution plan */ + struct kr_query *next = + kr_rplan_push(rplan, qry, transport->ns_name, KNOT_CLASS_IN, next_type); + if (!next) { + return kr_error(ENOMEM); + } + next->flags.NONAUTH = true; + + /* At the root level with no NS addresses, add SBELT subrequest. */ + int ret = 0; + if (qry->zone_cut.name[0] == '\0') { + ret = kr_zonecut_set_sbelt(ctx, &next->zone_cut); + if (ret == 0) { /* Copy TA and key since it's the same cut to avoid lookup. */ + kr_zonecut_copy_trust(&next->zone_cut, &qry->zone_cut); + kr_zonecut_set_sbelt(ctx, &qry->zone_cut); /* Add SBELT to parent in case query fails. */ + } + } else { + next->flags.AWAIT_CUT = true; + } + + if (ret == 0) { + if (next_type == KNOT_RRTYPE_AAAA) { + qry->flags.AWAIT_IPV6 = true; + } else { + qry->flags.AWAIT_IPV4 = true; + } + } + + return ret; +} + +int kr_resolve_produce(struct kr_request *request, struct kr_transport **transport, knot_pkt_t *packet) +{ + struct kr_rplan *rplan = &request->rplan; + + /* No query left for resolution */ + if (kr_rplan_empty(rplan)) { + return KR_STATE_FAIL; + } + + struct kr_query *qry = array_tail(rplan->pending); + + /* Initialize server selection */ + if (!qry->server_selection.initialized) { + kr_server_selection_init(qry); + } + + /* If we have deferred answers, resume them. */ + if (qry->deferred != NULL) { + /* @todo: Refactoring validator, check trust chain before resuming. */ + int state = 0; + if (((qry->flags.FORWARD) == 0) || + ((qry->stype == KNOT_RRTYPE_DS) && (qry->flags.CNAME))) { + state = trust_chain_check(request, qry); + } else { + state = forward_trust_chain_check(request, qry, true); + } + + switch(state) { + case KR_STATE_FAIL: return KR_STATE_FAIL; + case KR_STATE_DONE: return KR_STATE_PRODUCE; + default: break; + } + VERBOSE_MSG(qry, "=> resuming yielded answer\n"); + struct kr_layer_pickle *pickle = qry->deferred; + request->state = KR_STATE_YIELD; + set_yield(&request->answ_selected, qry->uid, false); + set_yield(&request->auth_selected, qry->uid, false); + RESUME_LAYERS(layer_id(request, pickle->api), request, qry, consume, pickle->pkt); + if (request->state != KR_STATE_YIELD) { + /* No new deferred answers, take the next */ + qry->deferred = pickle->next; + } + } else { + /* Caller is interested in always tracking a zone cut, even if the answer is cached + * this is normally not required, and incurs another cache lookups for cached answer. */ + if (qry->flags.ALWAYS_CUT) { + if (!(qry->flags.STUB)) { + switch(zone_cut_check(request, qry, packet)) { + case KR_STATE_FAIL: return KR_STATE_FAIL; + case KR_STATE_DONE: return KR_STATE_PRODUCE; + default: break; + } + } + } + /* Resolve current query and produce dependent or finish */ + request->state = KR_STATE_PRODUCE; + ITERATE_LAYERS(request, qry, produce, packet); + if (!(request->state & KR_STATE_FAIL) && knot_wire_get_qr(packet->wire)) { + /* Produced an answer from cache, consume it. */ + qry->secret = 0; + request->state = KR_STATE_CONSUME; + ITERATE_LAYERS(request, qry, consume, packet); + } + } + switch(request->state) { + case KR_STATE_FAIL: return request->state; + case KR_STATE_CONSUME: break; + case KR_STATE_DONE: + default: /* Current query is done */ + if (qry->flags.RESOLVED && request->state != KR_STATE_YIELD) { + kr_rplan_pop(rplan, qry); + } + ITERATE_LAYERS(request, qry, reset); + return kr_rplan_empty(rplan) ? KR_STATE_DONE : KR_STATE_PRODUCE; + } + + + /* This query has RD=0 or is ANY, stop here. */ + if (qry->stype == KNOT_RRTYPE_ANY || + !knot_wire_get_rd(request->qsource.packet->wire)) { + VERBOSE_MSG(qry, "=> qtype is ANY or RD=0, bail out\n"); + return KR_STATE_FAIL; + } + + /* Update zone cut, spawn new subrequests. */ + if (!(qry->flags.STUB)) { + int state = zone_cut_check(request, qry, packet); + switch(state) { + case KR_STATE_FAIL: return KR_STATE_FAIL; + case KR_STATE_DONE: return KR_STATE_PRODUCE; + default: break; + } + } + + + const struct kr_qflags qflg = qry->flags; + const bool retry = qflg.TCP || qflg.BADCOOKIE_AGAIN; + if (!qflg.FORWARD && !qflg.STUB && !retry) { /* Keep NS when requerying/stub/badcookie. */ + /* Root DNSKEY must be fetched from the hints to avoid chicken and egg problem. */ + if (qry->sname[0] == '\0' && qry->stype == KNOT_RRTYPE_DNSKEY) { + kr_zonecut_set_sbelt(request->ctx, &qry->zone_cut); + } + } + + qry->server_selection.choose_transport(qry, transport); + + if (*transport == NULL) { + /* Properly signal to serve_stale module. */ + if (qry->flags.NO_NS_FOUND) { + ITERATE_LAYERS(request, qry, reset); + kr_rplan_pop(rplan, qry); + return KR_STATE_FAIL; + } else { + /* FIXME: This is probably quite inefficient: + * we go through the whole qr_task_step loop just because of the serve_stale + * module which might not even be loaded. */ + qry->flags.NO_NS_FOUND = true; + return KR_STATE_PRODUCE; + } + } + + if ((*transport)->protocol == KR_TRANSPORT_RESOLVE_A || (*transport)->protocol == KR_TRANSPORT_RESOLVE_AAAA) { + uint16_t type = (*transport)->protocol == KR_TRANSPORT_RESOLVE_A ? KNOT_RRTYPE_A : KNOT_RRTYPE_AAAA; + ns_resolve_addr(qry, qry->request, *transport, type); + ITERATE_LAYERS(request, qry, reset); + return KR_STATE_PRODUCE; + } + + /* Randomize query case (if not in not turned off) */ + qry->secret = qry->flags.NO_0X20 ? 0 : kr_rand_bytes(sizeof(qry->secret)); + knot_dname_t *qname_raw = kr_pkt_qname_raw(packet); + randomized_qname_case(qname_raw, qry->secret); + + /* + * Additional query is going to be finalized when calling + * kr_resolve_checkout(). + */ + qry->timestamp_mono = kr_now(); + return request->state; +} + +#if ENABLE_COOKIES +/** Update DNS cookie data in packet. */ +static bool outbound_request_update_cookies(struct kr_request *req, + const struct sockaddr *src, + const struct sockaddr *dst) +{ + if (kr_fails_assert(req)) + return false; + + /* RFC7873 4.1 strongly requires server address. */ + if (!dst) + return false; + + struct kr_cookie_settings *clnt_sett = &req->ctx->cookie_ctx.clnt; + + /* Cookies disabled or packet has no EDNS section. */ + if (!clnt_sett->enabled) + return true; + + /* + * RFC7873 4.1 recommends using also the client address. The matter is + * also discussed in section 6. + */ + + kr_request_put_cookie(&clnt_sett->current, req->ctx->cache_cookie, + src, dst, req); + + return true; +} +#endif /* ENABLE_COOKIES */ + +int kr_resolve_checkout(struct kr_request *request, const struct sockaddr *src, + struct kr_transport *transport, knot_pkt_t *packet) +{ + /* @todo: Update documentation if this function becomes approved. */ + + struct kr_rplan *rplan = &request->rplan; + + if (knot_wire_get_qr(packet->wire) != 0) { + return kr_ok(); + } + + /* No query left for resolution */ + if (kr_rplan_empty(rplan)) { + return kr_error(EINVAL); + } + struct kr_query *qry = array_tail(rplan->pending); + +#if ENABLE_COOKIES + /* Update DNS cookies in request. */ + if (type == SOCK_DGRAM) { /* @todo: Add cookies also over TCP? */ + /* + * The actual server IP address is needed before generating the + * actual cookie. If we don't know the server address then we + * also don't know the actual cookie size. + */ + if (!outbound_request_update_cookies(request, src, &transport->address.ip)) { + return kr_error(EINVAL); + } + } +#endif /* ENABLE_COOKIES */ + + int ret = query_finalize(request, qry, packet); + if (ret != 0) { + return kr_error(EINVAL); + } + + /* Track changes in minimization secret to enable/disable minimization */ + uint32_t old_minimization_secret = qry->secret; + + /* Run the checkout layers and cancel on failure. + * The checkout layer doesn't persist the state, so canceled subrequests + * don't affect the resolution or rest of the processing. */ + int type = -1; + switch(transport->protocol) { + case KR_TRANSPORT_UDP: + type = SOCK_DGRAM; + break; + case KR_TRANSPORT_TCP: + case KR_TRANSPORT_TLS: + type = SOCK_STREAM; + break; + default: + kr_assert(false); + } + int state = request->state; + ITERATE_LAYERS(request, qry, checkout, packet, &transport->address.ip, type); + if (request->state & KR_STATE_FAIL) { + request->state = state; /* Restore */ + return kr_error(ECANCELED); + } + + /* Randomize query case (if secret changed) */ + knot_dname_t *qname_raw = kr_pkt_qname_raw(packet); + if (qry->secret != old_minimization_secret) { + randomized_qname_case(qname_raw, qry->secret); + } + + /* Write down OPT unless in safemode */ + if (!(qry->flags.NO_EDNS)) { + /* TLS padding */ + if (transport->protocol == KR_TRANSPORT_TLS) { + size_t padding_size = edns_padding_option_size(request->ctx->tls_padding); + ret = knot_pkt_reserve(packet, padding_size); + if (ret) + return kr_error(EINVAL); + ret = pkt_padding(packet, request->ctx->tls_padding); + if (ret) + return kr_error(EINVAL); + } + + ret = edns_put(packet, true); + if (ret != 0) { + return kr_error(EINVAL); + } + } + + if (kr_log_is_debug_qry(RESOLVER, qry)) { + KR_DNAME_GET_STR(qname_str, knot_pkt_qname(packet)); + KR_DNAME_GET_STR(ns_name, transport->ns_name); + KR_DNAME_GET_STR(zonecut_str, qry->zone_cut.name); + KR_RRTYPE_GET_STR(type_str, knot_pkt_qtype(packet)); + const char *ns_str = kr_straddr(&transport->address.ip); + + VERBOSE_MSG(qry, + "=> id: '%05u' querying: '%s'@'%s' zone cut: '%s' " + "qname: '%s' qtype: '%s' proto: '%s'\n", + qry->id, ns_name, ns_str ? ns_str : "", zonecut_str, + qname_str, type_str, (qry->flags.TCP) ? "tcp" : "udp"); + } + + return kr_ok(); +} + +int kr_resolve_finish(struct kr_request *request, int state) +{ + request->state = state; + /* Finalize answer and construct whole wire-format (unless dropping). */ + knot_pkt_t *answer = kr_request_ensure_answer(request); + if (answer) { + ITERATE_LAYERS(request, NULL, answer_finalize); + answer_finalize(request); + + /* Defensive style, in case someone has forgotten. + * Beware: non-empty answers do make sense even with SERVFAIL case, etc. */ + if (request->state != KR_STATE_DONE) { + uint8_t *wire = answer->wire; + switch (knot_wire_get_rcode(wire)) { + case KNOT_RCODE_NOERROR: + case KNOT_RCODE_NXDOMAIN: + knot_wire_clear_ad(wire); + knot_wire_clear_aa(wire); + knot_wire_set_rcode(wire, KNOT_RCODE_SERVFAIL); + } + } + } + + ITERATE_LAYERS(request, NULL, finish); + + struct kr_rplan *rplan = &request->rplan; + struct kr_query *last = kr_rplan_last(rplan); + VERBOSE_MSG(last, "finished in state: %d, queries: %zu, mempool: %zu B\n", + request->state, rplan->resolved.len, (size_t) mp_total_size(request->pool.ctx)); + + /* Trace request finish */ + if (request->trace_finish) { + request->trace_finish(request); + } + + /* Uninstall all tracepoints */ + request->trace_finish = NULL; + request->trace_log = NULL; + + return KR_STATE_DONE; +} + +struct kr_rplan *kr_resolve_plan(struct kr_request *request) +{ + if (request) { + return &request->rplan; + } + return NULL; +} + +knot_mm_t *kr_resolve_pool(struct kr_request *request) +{ + if (request) { + return &request->pool; + } + return NULL; +} + +static int ede_priority(int info_code) +{ + switch(info_code) { + case KNOT_EDNS_EDE_DNSKEY_BIT: + case KNOT_EDNS_EDE_DNSKEY_MISS: + case KNOT_EDNS_EDE_SIG_EXPIRED: + case KNOT_EDNS_EDE_SIG_NOTYET: + case KNOT_EDNS_EDE_RRSIG_MISS: + case KNOT_EDNS_EDE_NSEC_MISS: + return 900; /* Specific DNSSEC failures */ + case KNOT_EDNS_EDE_BOGUS: + return 800; /* Generic DNSSEC failure */ + case KNOT_EDNS_EDE_FORGED: + case KNOT_EDNS_EDE_FILTERED: + return 700; /* Considered hard fail by firefox */ + case KNOT_EDNS_EDE_PROHIBITED: + case KNOT_EDNS_EDE_BLOCKED: + case KNOT_EDNS_EDE_CENSORED: + return 600; /* Policy related */ + case KNOT_EDNS_EDE_DNSKEY_ALG: + case KNOT_EDNS_EDE_DS_DIGEST: + return 500; /* Non-critical DNSSEC issues */ + case KNOT_EDNS_EDE_STALE: + case KNOT_EDNS_EDE_STALE_NXD: + return 300; /* Serve-stale answers. */ + case KNOT_EDNS_EDE_INDETERMINATE: + case KNOT_EDNS_EDE_CACHED_ERR: + case KNOT_EDNS_EDE_NOT_READY: + case KNOT_EDNS_EDE_NOTAUTH: + case KNOT_EDNS_EDE_NOTSUP: + case KNOT_EDNS_EDE_NREACH_AUTH: + case KNOT_EDNS_EDE_NETWORK: + case KNOT_EDNS_EDE_INV_DATA: + return 200; /* Assorted codes */ + case KNOT_EDNS_EDE_OTHER: + return 100; /* Most generic catch-all error */ + case KNOT_EDNS_EDE_NONE: + return 0; /* No error - allow overriding */ + default: + kr_assert(false); /* Unknown info_code */ + return 50; + } +} + +int kr_request_set_extended_error(struct kr_request *request, int info_code, const char *extra_text) +{ + if (kr_fails_assert(request)) + return KNOT_EDNS_EDE_NONE; + + struct kr_extended_error *ede = &request->extended_error; + + /* Clear any previously set error. */ + if (info_code == KNOT_EDNS_EDE_NONE) { + kr_assert(extra_text == NULL); + ede->info_code = KNOT_EDNS_EDE_NONE; + ede->extra_text = NULL; + return KNOT_EDNS_EDE_NONE; + } + + if (ede_priority(info_code) >= ede_priority(ede->info_code)) { + ede->info_code = info_code; + ede->extra_text = extra_text; + } + + return ede->info_code; +} + +#undef VERBOSE_MSG diff --git a/lib/resolve.h b/lib/resolve.h new file mode 100644 index 0000000..97ba07b --- /dev/null +++ b/lib/resolve.h @@ -0,0 +1,420 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <netinet/in.h> +#include <sys/socket.h> +#include <libknot/packet/pkt.h> + +#include "lib/cookies/control.h" +#include "lib/cookies/lru_cache.h" +#include "lib/layer.h" +#include "lib/generic/array.h" +#include "lib/selection.h" +#include "lib/rplan.h" +#include "lib/module.h" +#include "lib/cache/api.h" + +/** + * @file resolve.h + * @brief The API provides an API providing a "consumer-producer"-like interface to enable + * user to plug it into existing event loop or I/O code. + * + * # Example usage of the iterative API: + * + * @code{.c} + * + * // Create request and its memory pool + * struct kr_request req = { + * .pool = { + * .ctx = mp_new (4096), + * .alloc = (mm_alloc_t) mp_alloc + * } + * }; + * + * // Setup and provide input query + * int state = kr_resolve_begin(&req, ctx); + * state = kr_resolve_consume(&req, query); + * + * // Generate answer + * while (state == KR_STATE_PRODUCE) { + * + * // Additional query generate, do the I/O and pass back answer + * state = kr_resolve_produce(&req, &addr, &type, query); + * while (state == KR_STATE_CONSUME) { + * int ret = sendrecv(addr, proto, query, resp); + * + * // If I/O fails, make "resp" empty + * state = kr_resolve_consume(&request, addr, resp); + * knot_pkt_clear(resp); + * } + * knot_pkt_clear(query); + * } + * + * // "state" is either DONE or FAIL + * kr_resolve_finish(&request, state); + * + * @endcode + */ + + +struct kr_request; +/** Allocate buffer for answer's wire (*maxlen may get lowered). + * + * Motivation: XDP wire allocation is an overlap of library and daemon: + * - it needs to be called from the library + * - it needs to rely on some daemon's internals + * - the library (currently) isn't allowed to directly use symbols from daemon + * (contrary to modules), e.g. some of our lib-using tests run without daemon + * + * Note: after we obtain the wire, we're obliged to send it out. + * (So far there's no use case to allow cancelling at that point.) + */ +typedef uint8_t * (*alloc_wire_f)(struct kr_request *req, uint16_t *maxlen); + +/** + * RRset rank - for cache and ranked_rr_*. + * + * The rank meaning consists of one independent flag - KR_RANK_AUTH, + * and the rest have meaning of values where only one can hold at any time. + * You can use one of the enums as a safe initial value, optionally | KR_RANK_AUTH; + * otherwise it's best to manipulate ranks via the kr_rank_* functions. + * + * @note The representation is complicated by restrictions on integer comparison: + * - AUTH must be > than !AUTH + * - AUTH INSECURE must be > than AUTH (because it attempted validation) + * - !AUTH SECURE must be > than AUTH (because it's valid) + * + * See also: + * https://tools.ietf.org/html/rfc2181#section-5.4.1 + * https://tools.ietf.org/html/rfc4035#section-4.3 + */ +enum kr_rank { + /* Initial-like states. No validation has been attempted (yet). */ + KR_RANK_INITIAL = 0, /**< Did not attempt to validate. It's assumed + compulsory to validate (or prove insecure). */ + KR_RANK_OMIT, /**< Do not attempt to validate. + (And don't consider it a validation failure.) */ + KR_RANK_TRY, /**< Attempt to validate, but failures are non-fatal. */ + + /* Failure states. These have higher value because they have more information. */ + KR_RANK_INDET = 4, /**< Unable to determine whether it should be secure. */ + KR_RANK_BOGUS, /**< Ought to be secure but isn't. */ + KR_RANK_MISMATCH, + KR_RANK_MISSING, /**< No RRSIG found for that owner+type combination. */ + + /** Proven to be insecure, i.e. we have a chain of trust from TAs + * that cryptographically denies the possibility of existence + * of a positive chain of trust from the TAs to the record. + * Or it may be covered by a closer negative TA. */ + KR_RANK_INSECURE = 8, + + /** Authoritative data flag; the chain of authority was "verified". + * Even if not set, only in-bailiwick stuff is acceptable, + * i.e. almost authoritative (example: mandatory glue and its NS RR). */ + KR_RANK_AUTH = 16, + + KR_RANK_SECURE = 32, /**< Verified whole chain of trust from the closest TA. */ + /* @note Rank must not exceed 6 bits */ +}; + +/** Check that a rank value is valid. Meant for assertions. */ +bool kr_rank_check(uint8_t rank) KR_PURE; + +/** Test the presence of any flag/state in a rank, i.e. including KR_RANK_AUTH. */ +bool kr_rank_test(uint8_t rank, uint8_t kr_flag) KR_PURE KR_EXPORT; + +/** Set the rank state. The _AUTH flag is kept as it was. */ +static inline void kr_rank_set(uint8_t *rank, uint8_t kr_flag) +{ + if (kr_fails_assert(rank && kr_rank_check(*rank))) + return; + if (kr_fails_assert(kr_rank_check(kr_flag) && !(kr_flag & KR_RANK_AUTH))) + return; + *rank = kr_flag | (*rank & KR_RANK_AUTH); +} + + +/** @cond internal Array of modules. */ +typedef array_t(struct kr_module *) module_array_t; +/* @endcond */ + +/** + * Name resolution context. + * + * Resolution context provides basic services like cache, configuration and options. + * + * @note This structure is persistent between name resolutions and may + * be shared between threads. + */ +struct kr_context +{ + /** Default kr_request flags. For startup defaults see init_resolver() */ + struct kr_qflags options; + + /** Default EDNS towards *both* clients and upstream. + * LATER: consider splitting the two, e.g. allow separately + * configured limits for UDP packet size (say, LAN is under control). */ + knot_rrset_t *downstream_opt_rr; + knot_rrset_t *upstream_opt_rr; + + trie_t *trust_anchors; + trie_t *negative_anchors; + struct kr_zonecut root_hints; + struct kr_cache cache; + unsigned cache_rtt_tout_retry_interval; + module_array_t *modules; + /* The cookie context structure should not be held within the cookies + * module because of better access. */ + struct kr_cookie_ctx cookie_ctx; + kr_cookie_lru_t *cache_cookie; + int32_t tls_padding; /**< See net.tls_padding in ../daemon/README.rst -- -1 is "true" (default policy), 0 is "false" (no padding) */ + knot_mm_t *pool; +}; + +/* Kept outside, because kres-gen.lua can't handle this depth + * (and lines here were too long anyway). */ +struct kr_request_qsource_flags { + bool tcp:1; /**< true if the request is not on UDP; only meaningful if (dst_addr). */ + bool tls:1; /**< true if the request is encrypted; only meaningful if (dst_addr). */ + bool http:1; /**< true if the request is on HTTP; only meaningful if (dst_addr). */ + bool xdp:1; /**< true if the request is on AF_XDP; only meaningful if (dst_addr). */ +}; + +/* Extended DNS Errors, RFC 8914 */ +struct kr_extended_error { + int32_t info_code; /**< May contain -1 (KNOT_EDNS_EDE_NONE); filter before converting to uint16_t. */ + const char *extra_text; /**< Can be NULL. Allocated on the kr_request::pool or static. */ +}; + + +typedef bool (*addr_info_f)(struct sockaddr*); +typedef void (*async_resolution_f)(knot_dname_t*, enum knot_rr_type); +typedef array_t(union kr_sockaddr) kr_sockaddr_array_t; + +/** + * Name resolution request. + * + * Keeps information about current query processing between calls to + * processing APIs, i.e. current resolved query, resolution plan, ... + * Use this instead of the simple interface if you want to implement + * multiplexing or custom I/O. + * + * @note All data for this request must be allocated from the given pool. + */ +struct kr_request { + struct kr_context *ctx; + knot_pkt_t *answer; /**< See kr_request_ensure_answer() */ + struct kr_query *current_query; /**< Current evaluated query. */ + struct { + /** Address that originated the request. May be that of a client + * behind a proxy, if PROXYv2 is used. Otherwise, it will be + * the same as `comm_addr`. `NULL` for internal origin. */ + const struct sockaddr *addr; + /** Address that communicated the request. This may be the address + * of a proxy. It is the same as `addr` if no proxy is used. + * `NULL` for internal origin. */ + const struct sockaddr *comm_addr; + /** Address that accepted the request. `NULL` for internal origin. + * Beware: in case of UDP on wildcard address it will be wildcard; + * closely related: issue #173. */ + const struct sockaddr *dst_addr; + const knot_pkt_t *packet; + /** Request flags from the point of view of the original client. + * This client may be behind a proxy. */ + struct kr_request_qsource_flags flags; + /** Request flags from the point of view of the client actually + * communicating with the resolver. When PROXYv2 protocol is used, + * this describes the request from the proxy. When there is no + * proxy, this will have exactly the same value as `flags`. */ + struct kr_request_qsource_flags comm_flags; + size_t size; /**< query packet size */ + int32_t stream_id; /**< HTTP/2 stream ID for DoH requests */ + kr_http_header_array_t headers; /**< HTTP/2 headers for DoH requests */ + } qsource; + struct { + unsigned rtt; /**< Current upstream RTT */ + const struct kr_transport *transport; /**< Current upstream transport */ + } upstream; /**< Upstream information, valid only in consume() phase */ + struct kr_qflags options; + int state; + ranked_rr_array_t answ_selected; + ranked_rr_array_t auth_selected; + ranked_rr_array_t add_selected; + bool answ_validated; /**< internal to validator; beware of caching, etc. */ + bool auth_validated; /**< see answ_validated ^^ ; TODO */ + + /** Overall rank for the request. + * + * Values from kr_rank, currently just KR_RANK_SECURE and _INITIAL. + * Only read this in finish phase and after validator, please. + * Meaning of _SECURE: all RRs in answer+authority are _SECURE, + * including any negative results implied (NXDOMAIN, NODATA). + */ + uint8_t rank; + + struct kr_rplan rplan; + trace_log_f trace_log; /**< Logging tracepoint */ + trace_callback_f trace_finish; /**< Request finish tracepoint */ + int vars_ref; /**< Reference to per-request variable table. LUA_NOREF if not set. */ + knot_mm_t pool; + unsigned int uid; /**< for logging purposes only */ + struct { + addr_info_f is_tls_capable; + addr_info_f is_tcp_connected; + addr_info_f is_tcp_waiting; + kr_sockaddr_array_t forwarding_targets; /**< When forwarding, possible targets are put here */ + } selection_context; + unsigned int count_no_nsaddr; + unsigned int count_fail_row; + alloc_wire_f alloc_wire_cb; /**< CB to allocate answer wire (can be NULL). */ + struct kr_extended_error extended_error; /**< EDE info; don't modify directly, use kr_request_set_extended_error() */ +}; + +/** Initializer for an array of *_selected. */ +#define kr_request_selected(req) { \ + [KNOT_ANSWER] = &(req)->answ_selected, \ + [KNOT_AUTHORITY] = &(req)->auth_selected, \ + [KNOT_ADDITIONAL] = &(req)->add_selected, \ + } + +/** + * Begin name resolution. + * + * @note Expects a request to have an initialized mempool. + * + * @param request request state with initialized mempool + * @param ctx resolution context + * @return CONSUME (expecting query) + */ +KR_EXPORT +int kr_resolve_begin(struct kr_request *request, struct kr_context *ctx); + +/** + * Ensure that request->answer->opt_rr is present if query has EDNS. + * + * This function should be used after clearing a response packet to ensure its + * opt_rr is properly set. Returns the opt_rr (for convenience) or NULL. + */ +KR_EXPORT +knot_rrset_t * kr_request_ensure_edns(struct kr_request *request); + +/** + * Ensure that request->answer is usable, and return it (for convenience). + * + * It may return NULL, in which case it marks ->state with _FAIL and no answer will be sent. + * Only use this when it's guaranteed that there will be no delay before sending it. + * You don't need to call this in places where "resolver knows" that there will be no delay, + * but even there you need to check if the ->answer is NULL (unless you check for _FAIL anyway). + */ +KR_EXPORT +knot_pkt_t * kr_request_ensure_answer(struct kr_request *request); + +/** + * Consume input packet (may be either first query or answer to query originated from kr_resolve_produce()) + * + * @note If the I/O fails, provide an empty or NULL packet, this will make iterator recognize nameserver failure. + * + * @param request request state (awaiting input) + * @param src [in] packet source address + * @param packet [in] input packet + * @return any state + */ +KR_EXPORT +int kr_resolve_consume(struct kr_request *request, struct kr_transport **transport, knot_pkt_t *packet); + +/** + * Produce either next additional query or finish. + * + * If the CONSUME is returned then dst, type and packet will be filled with + * appropriate values and caller is responsible to send them and receive answer. + * If it returns any other state, then content of the variables is undefined. + * + * @param request request state (in PRODUCE state) + * @param dst [out] possible address of the next nameserver + * @param type [out] possible used socket type (SOCK_STREAM, SOCK_DGRAM) + * @param packet [out] packet to be filled with additional query + * @return any state + */ +KR_EXPORT +int kr_resolve_produce(struct kr_request *request, struct kr_transport **transport, knot_pkt_t *packet); + +/** + * Finalises the outbound query packet with the knowledge of the IP addresses. + * + * @note The function must be called before actual sending of the request packet. + * + * @param request request state (in PRODUCE state) + * @param src address from which the query is going to be sent + * @param dst address of the name server + * @param type used socket type (SOCK_STREAM, SOCK_DGRAM) + * @param packet [in,out] query packet to be finalised + * @return kr_ok() or error code + */ +KR_EXPORT +int kr_resolve_checkout(struct kr_request *request, const struct sockaddr *src, + struct kr_transport *transport, knot_pkt_t *packet); + +/** + * Finish resolution and commit results if the state is DONE. + * + * @note The structures will be deinitialized, but the assigned memory pool is not going to + * be destroyed, as it's owned by caller. + * + * @param request request state + * @param state either DONE or FAIL state (to be assigned to request->state) + * @return DONE + */ +KR_EXPORT +int kr_resolve_finish(struct kr_request *request, int state); + +/** + * Return resolution plan. + * @param request request state + * @return pointer to rplan + */ +KR_EXPORT KR_PURE +struct kr_rplan *kr_resolve_plan(struct kr_request *request); + +/** + * Return memory pool associated with request. + * @param request request state + * @return mempool + */ +KR_EXPORT KR_PURE +knot_mm_t *kr_resolve_pool(struct kr_request *request); + +/** + * Set the extended DNS error for request. + * + * The error is set only if it has a higher or the same priority as the one + * already assigned. The provided extra_text may be NULL, or a string that is + * allocated either statically, or on the request's mempool. To clear any + * error, call it with KNOT_EDNS_EDE_NONE and NULL as extra_text. + * + * To facilitate debugging, we include a unique base32 identifier at the start + * of the extra_text field for every call of this function. To generate such an + * identifier, you can use the command: + * $ base32 /dev/random | head -c 4 + * + * @param request request state + * @param info_code extended DNS error code + * @param extra_text optional string with additional information + * @return info_code that is set after the call + */ +KR_EXPORT +int kr_request_set_extended_error(struct kr_request *request, int info_code, const char *extra_text); + +static inline void kr_query_inform_timeout(struct kr_request *req, const struct kr_query *qry) +{ + kr_request_set_extended_error(req, KNOT_EDNS_EDE_NREACH_AUTH, "RRPF"); + + unsigned ind = 0; + for (const struct kr_query *q = qry; q; q = q->parent) + ind += 2; + const uint32_t qid = qry ? qry->uid : 0; + + kr_log_req(req, qid, ind, WORKER, "internal timeout for resolving the request has expired\n"); +} diff --git a/lib/rplan.c b/lib/rplan.c new file mode 100644 index 0000000..0bedd8a --- /dev/null +++ b/lib/rplan.c @@ -0,0 +1,291 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <libknot/descriptor.h> +#include <libknot/errcode.h> + +#include "lib/rplan.h" +#include "lib/resolve.h" + +#define VERBOSE_MSG(qry, ...) kr_log_q(qry, PLAN, __VA_ARGS__) + +inline static unsigned char chars_or(const unsigned char a, const unsigned char b) +{ + return a | b; +} + +/** Bits set to 1 in variable b will be set to zero in variable a. */ +inline static unsigned char chars_mask(const unsigned char a, const unsigned char b) +{ + return a & ~b; +} + +/** Apply mod(a, b) to every byte a, b from fl1, fl2 and return result in fl1. */ +inline static void kr_qflags_mod(struct kr_qflags *fl1, struct kr_qflags fl2, + unsigned char mod(const unsigned char a, const unsigned char b)) +{ + kr_require(fl1); + union { + struct kr_qflags flags; + /* C99 section 6.5.3.4: sizeof(char) == 1 */ + unsigned char chars[sizeof(struct kr_qflags)]; + } tmp1, tmp2; + /* The compiler should be able to optimize all this into simple ORs. */ + tmp1.flags = *fl1; + tmp2.flags = fl2; + for (size_t i = 0; i < sizeof(struct kr_qflags); ++i) { + tmp1.chars[i] = mod(tmp1.chars[i], tmp2.chars[i]); + } + *fl1 = tmp1.flags; +} + +/** + * Set bits from variable fl2 in variable fl1. + * Bits which are not set in fl2 are not modified in fl1. + * + * @param[in,out] fl1 + * @param[in] fl2 + */ +void kr_qflags_set(struct kr_qflags *fl1, struct kr_qflags fl2) +{ + kr_qflags_mod(fl1, fl2, chars_or); +} + +/** + * Clear bits from variable fl2 in variable fl1. + * Bits which are not set in fl2 are not modified in fl1. + * + * @param[in,out] fl1 + * @param[in] fl2 + */ +void kr_qflags_clear(struct kr_qflags *fl1, struct kr_qflags fl2) +{ + kr_qflags_mod(fl1, fl2, chars_mask); +} + +static struct kr_query *query_create(knot_mm_t *pool, const knot_dname_t *name, uint32_t uid) +{ + struct kr_query *qry = mm_calloc(pool, 1, sizeof(*qry)); + if (qry == NULL) { + return NULL; + } + + if (name != NULL) { + qry->sname = knot_dname_copy(name, pool); + if (qry->sname == NULL) { + mm_free(pool, qry); + return NULL; + } + } + + knot_dname_to_lower(qry->sname); + qry->uid = uid; + return qry; +} + +static void query_free(knot_mm_t *pool, struct kr_query *qry) +{ + kr_zonecut_deinit(&qry->zone_cut); + mm_free(pool, qry->sname); + mm_free(pool, qry); +} + +int kr_rplan_init(struct kr_rplan *rplan, struct kr_request *request, knot_mm_t *pool) +{ + if (rplan == NULL) { + return KNOT_EINVAL; + } + + memset(rplan, 0, sizeof(struct kr_rplan)); + + rplan->pool = pool; + rplan->request = request; + array_init(rplan->pending); + array_init(rplan->resolved); + rplan->next_uid = 0; + return KNOT_EOK; +} + +void kr_rplan_deinit(struct kr_rplan *rplan) +{ + if (rplan == NULL) { + return; + } + + for (size_t i = 0; i < rplan->pending.len; ++i) { + query_free(rplan->pool, rplan->pending.at[i]); + } + for (size_t i = 0; i < rplan->resolved.len; ++i) { + query_free(rplan->pool, rplan->resolved.at[i]); + } + array_clear_mm(rplan->pending, mm_free, rplan->pool); + array_clear_mm(rplan->resolved, mm_free, rplan->pool); +} + +bool kr_rplan_empty(struct kr_rplan *rplan) +{ + if (rplan == NULL) { + return true; + } + + return rplan->pending.len == 0; +} + +static struct kr_query *kr_rplan_push_query(struct kr_rplan *rplan, + struct kr_query *parent, + const knot_dname_t *name) +{ + if (rplan == NULL) { + return NULL; + } + + /* Make sure there's enough space */ + int ret = array_reserve_mm(rplan->pending, rplan->pending.len + 1, kr_memreserve, rplan->pool); + if (ret != 0) { + return NULL; + } + + struct kr_query *qry = query_create(rplan->pool, name, rplan->next_uid); + if (qry == NULL) { + return NULL; + } + rplan->next_uid += 1; + /* Class and type must be set outside this function. */ + qry->flags = rplan->request->options; + qry->parent = parent; + qry->request = rplan->request; + + gettimeofday(&qry->timestamp, NULL); + qry->timestamp_mono = kr_now(); + qry->creation_time_mono = parent ? parent->creation_time_mono : qry->timestamp_mono; + kr_zonecut_init(&qry->zone_cut, (const uint8_t *)"", rplan->pool); + qry->reorder = qry->flags.REORDER_RR ? kr_rand_bytes(sizeof(qry->reorder)) : 0; + + + kr_assert((rplan->pending.len == 0 && rplan->resolved.len == 0) + == (rplan->initial == NULL)); + if (rplan->initial == NULL) { + rplan->initial = qry; + } + + array_push(rplan->pending, qry); + + return qry; +} + +struct kr_query *kr_rplan_push_empty(struct kr_rplan *rplan, struct kr_query *parent) +{ + if (rplan == NULL) { + return NULL; + } + + struct kr_query *qry = kr_rplan_push_query(rplan, parent, NULL); + if (qry == NULL) { + return NULL; + } + + VERBOSE_MSG(qry, "plan '%s' type '%s' uid [%05u.%02u]\n", "", "", + qry->request ? qry->request->uid : 0, qry->uid); + return qry; +} + +struct kr_query *kr_rplan_push(struct kr_rplan *rplan, struct kr_query *parent, + const knot_dname_t *name, uint16_t cls, uint16_t type) +{ + if (rplan == NULL || name == NULL) { + return NULL; + } + + struct kr_query *qry = kr_rplan_push_query(rplan, parent, name); + if (qry == NULL) { + return NULL; + } + + qry->sclass = cls; + qry->stype = type; + + if (kr_log_is_debug_qry(PLAN, qry)) { + KR_DNAME_GET_STR(name_str, name); + KR_RRTYPE_GET_STR(type_str, type); + VERBOSE_MSG(parent, "plan '%s' type '%s' uid [%05u.%02u]\n", + name_str, type_str, + qry->request ? qry->request->uid : 0, qry->uid); + } + return qry; +} + +int kr_rplan_pop(struct kr_rplan *rplan, struct kr_query *qry) +{ + if (rplan == NULL || qry == NULL) { + return KNOT_EINVAL; + } + + /* Make sure there's enough space */ + int ret = array_reserve_mm(rplan->resolved, rplan->resolved.len + 1, kr_memreserve, rplan->pool); + if (ret != 0) { + return ret; + } + + /* Find the query, it will likely be on top */ + for (size_t i = rplan->pending.len; i > 0; i--) { + if (rplan->pending.at[i - 1] == qry) { + /* Delete i-1 element by *sliding* the rest, + * contrary to array_del() */ + for (size_t j = i; j < rplan->pending.len; ++j) + rplan->pending.at[j - 1] = rplan->pending.at[j]; + array_pop(rplan->pending); + + array_push(rplan->resolved, qry); + break; + } + } + return KNOT_EOK; +} + +bool kr_rplan_satisfies(struct kr_query *closure, const knot_dname_t *name, uint16_t cls, uint16_t type) +{ + while (name && closure) { + if (closure->sclass == cls && closure->stype == type + && knot_dname_is_equal(closure->sname, name)) { + return true; + } + closure = closure->parent; + } + return false; +} + +struct kr_query *kr_rplan_resolved(struct kr_rplan *rplan) +{ + if (rplan->resolved.len == 0) { + return NULL; + } + return array_tail(rplan->resolved); +} + +struct kr_query *kr_rplan_last(struct kr_rplan *rplan) +{ + if (!kr_rplan_empty(rplan)) { + return array_tail(rplan->pending); + } + + return kr_rplan_resolved(rplan); +} + +struct kr_query *kr_rplan_find_resolved(struct kr_rplan *rplan, struct kr_query *parent, + const knot_dname_t *name, uint16_t cls, uint16_t type) +{ + struct kr_query *ret = NULL; + for (int i = 0; i < rplan->resolved.len; ++i) { + struct kr_query *q = rplan->resolved.at[i]; + if (q->stype == type && q->sclass == cls && + (parent == NULL || q->parent == parent) && + knot_dname_is_equal(q->sname, name)) { + ret = q; + break; + } + } + return ret; +} + +#undef VERBOSE_MSG diff --git a/lib/rplan.h b/lib/rplan.h new file mode 100644 index 0000000..891781f --- /dev/null +++ b/lib/rplan.h @@ -0,0 +1,221 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <sys/time.h> +#include <libknot/dname.h> +#include <libknot/codes.h> + +#include "lib/selection.h" +#include "lib/zonecut.h" + +/** Query flags */ +struct kr_qflags { + bool NO_MINIMIZE : 1; /**< Don't minimize QNAME. */ + bool NO_IPV6 : 1; /**< Disable IPv6 */ + bool NO_IPV4 : 1; /**< Disable IPv4 */ + bool TCP : 1; /**< Use TCP (or TLS) for this query. */ + bool NO_ANSWER : 1; /**< Do not send any answer to the client. + * Request state should be set to `KR_STATE_FAIL` + * when this flag is set. */ + bool RESOLVED : 1; /**< Query is resolved. Note that kr_query gets + * RESOLVED before following a CNAME chain; see .CNAME. */ + bool AWAIT_IPV4 : 1; /**< Query is waiting for A address. */ + bool AWAIT_IPV6 : 1; /**< Query is waiting for AAAA address. */ + bool AWAIT_CUT : 1; /**< Query is waiting for zone cut lookup */ + bool NO_EDNS : 1; /**< Don't use EDNS. */ + bool CACHED : 1; /**< Query response is cached. */ + bool NO_CACHE : 1; /**< No cache for lookup; exception: finding NSs and subqueries. */ + bool EXPIRING : 1; /**< Query response is cached but expiring. See is_expiring(). */ + bool ALLOW_LOCAL : 1; /**< Allow queries to local or private address ranges. */ + bool DNSSEC_WANT : 1; /**< Want DNSSEC secured answer; exception: +cd, + * i.e. knot_wire_get_cd(request->qsource.packet->wire) */ + bool DNSSEC_BOGUS : 1; /**< Query response is DNSSEC bogus. */ + bool DNSSEC_INSECURE : 1;/**< Query response is DNSSEC insecure. */ + bool DNSSEC_CD : 1; /**< Instruction to set CD bit in request. */ + bool STUB : 1; /**< Stub resolution, accept received answer as solved. */ + bool ALWAYS_CUT : 1; /**< Always recover zone cut (even if cached). */ + bool DNSSEC_WEXPAND : 1; /**< Query response has wildcard expansion. */ + bool PERMISSIVE : 1; /**< Permissive resolver mode. */ + bool STRICT : 1; /**< Strict resolver mode. */ + bool BADCOOKIE_AGAIN : 1;/**< Query again because bad cookie returned. */ + bool CNAME : 1; /**< Query response contains CNAME in answer section. */ + bool REORDER_RR : 1; /**< Reorder cached RRs. */ + bool TRACE : 1; /**< Also log answers on debug level. */ + bool NO_0X20 : 1; /**< Disable query case randomization . */ + bool DNSSEC_NODS : 1; /**< DS non-existence is proven */ + bool DNSSEC_OPTOUT : 1; /**< Closest encloser proof has optout */ + bool NONAUTH : 1; /**< Non-authoritative in-bailiwick records are enough. + * TODO: utilize this also outside cache. */ + bool FORWARD : 1; /**< Forward all queries to upstream; validate answers. */ + bool DNS64_MARK : 1; /**< Internal mark for dns64 module. */ + bool CACHE_TRIED : 1; /**< Internal to cache module. */ + bool NO_NS_FOUND : 1; /**< No valid NS found during last PRODUCE stage. */ + bool PKT_IS_SANE : 1; /**< Set by iterator in consume phase to indicate whether + * some basic aspects of the packet are OK, e.g. QNAME. */ + bool DNS64_DISABLE : 1; /**< Don't do any DNS64 stuff (meant for view:addr). */ +}; + +/** Combine flags together. This means set union for simple flags. */ +KR_EXPORT +void kr_qflags_set(struct kr_qflags *fl1, struct kr_qflags fl2); + +/** Remove flags. This means set-theoretic difference. */ +KR_EXPORT +void kr_qflags_clear(struct kr_qflags *fl1, struct kr_qflags fl2); + +/** Callback for serve-stale decisions. + * @param ttl the expired TTL (i.e. it's < 0) + * @return the adjusted TTL (typically 1) or < 0. + */ +typedef int32_t (*kr_stale_cb)(int32_t ttl, const knot_dname_t *owner, uint16_t type, + const struct kr_query *qry); + +/** + * Single query representation. + */ +struct kr_query { + struct kr_query *parent; + knot_dname_t *sname; /**< The name to resolve - lower-cased, uncompressed. */ + uint16_t stype; + uint16_t sclass; + uint16_t id; + uint16_t reorder; /**< Seed to reorder (cached) RRs in answer or zero. */ + struct kr_qflags flags; + struct kr_qflags forward_flags; + uint32_t secret; + uint32_t uid; /**< Query iteration number, unique within the kr_rplan. */ + uint64_t creation_time_mono; /* The time of query's creation (milliseconds). + * Or time of creation of an oldest + * ancestor if it is a subquery. */ + uint64_t timestamp_mono; /**< Time of query created or time of + * query to upstream resolver (milliseconds). */ + struct timeval timestamp; /**< Real time for TTL+DNSSEC checks (.tv_sec only). */ + struct kr_zonecut zone_cut; + struct kr_layer_pickle *deferred; + + /** Current xNAME depth, set by iterator. 0 = uninitialized, 1 = no CNAME, ... + * See also KR_CNAME_CHAIN_LIMIT. */ + int8_t cname_depth; + /** Pointer to the query that originated this one because of following a CNAME (or NULL). */ + struct kr_query *cname_parent; + struct kr_request *request; /**< Parent resolution request. */ + kr_stale_cb stale_cb; /**< See the type */ + struct kr_server_selection server_selection; +}; + +/** @cond internal Array of queries. */ +typedef array_t(struct kr_query *) kr_qarray_t; +/* @endcond */ + +/** + * Query resolution plan structure. + * + * The structure most importantly holds the original query, answer and the + * list of pending queries required to resolve the original query. + * It also keeps a notion of current zone cut. + */ +struct kr_rplan { + kr_qarray_t pending; /**< List of pending queries. + Beware: order is significant ATM, + as the last is the next one to solve, + and they may be inter-dependent. */ + kr_qarray_t resolved; /**< List of resolved queries. */ + struct kr_query *initial; /**< The initial query (also in pending or resolved). */ + + struct kr_request *request; /**< Parent resolution request. */ + knot_mm_t *pool; /**< Temporary memory pool. */ + uint32_t next_uid; /**< Next value for kr_query::uid (incremental). */ +}; + +/** + * Initialize resolution plan (empty). + * @param rplan plan instance + * @param request resolution request + * @param pool ephemeral memory pool for whole resolution + */ +KR_EXPORT +int kr_rplan_init(struct kr_rplan *rplan, struct kr_request *request, knot_mm_t *pool); + +/** + * Deinitialize resolution plan, aborting any uncommitted transactions. + * @param rplan plan instance + */ +KR_EXPORT +void kr_rplan_deinit(struct kr_rplan *rplan); + +/** + * Return true if the resolution plan is empty (i.e. finished or initialized) + * @param rplan plan instance + * @return true or false + */ +KR_EXPORT KR_PURE +bool kr_rplan_empty(struct kr_rplan *rplan); + +/** + * Push empty query to the top of the resolution plan. + * @note This query serves as a cookie query only. + * @param rplan plan instance + * @param parent query parent (or NULL) + * @return query instance or NULL + */ +KR_EXPORT +struct kr_query *kr_rplan_push_empty(struct kr_rplan *rplan, + struct kr_query *parent); + +/** + * Push a query to the top of the resolution plan. + * @note This means that this query takes precedence before all pending queries. + * @param rplan plan instance + * @param parent query parent (or NULL) + * @param name resolved name + * @param cls resolved class + * @param type resolved type + * @return query instance or NULL + */ +KR_EXPORT +struct kr_query *kr_rplan_push(struct kr_rplan *rplan, struct kr_query *parent, + const knot_dname_t *name, uint16_t cls, uint16_t type); + +/** + * Pop existing query from the resolution plan. + * @note Popped queries are not discarded, but moved to the resolved list. + * @param rplan plan instance + * @param qry resolved query + * @return 0 or an error + */ +KR_EXPORT +int kr_rplan_pop(struct kr_rplan *rplan, struct kr_query *qry); + +/** + * Return true if resolution chain satisfies given query. + */ +KR_EXPORT KR_PURE +bool kr_rplan_satisfies(struct kr_query *closure, const knot_dname_t *name, uint16_t cls, uint16_t type); + +/** Return last resolved query. */ +KR_EXPORT KR_PURE +struct kr_query *kr_rplan_resolved(struct kr_rplan *rplan); + +/** + * Return last query (either currently being solved or last resolved). + * This is necessary to retrieve the last query in case of resolution failures (e.g. time limit reached). + */ +KR_EXPORT KR_PURE +struct kr_query *kr_rplan_last(struct kr_rplan *rplan); + + +/** + * Check if a given query already resolved. + * @param rplan plan instance + * @param parent query parent (or NULL) + * @param name resolved name + * @param cls resolved class + * @param type resolved type + * @return query instance or NULL + */ +KR_EXPORT KR_PURE +struct kr_query *kr_rplan_find_resolved(struct kr_rplan *rplan, struct kr_query *parent, + const knot_dname_t *name, uint16_t cls, uint16_t type); diff --git a/lib/selection.c b/lib/selection.c new file mode 100644 index 0000000..5aa2992 --- /dev/null +++ b/lib/selection.c @@ -0,0 +1,795 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <libknot/dname.h> + +#include "lib/selection.h" +#include "lib/selection_forward.h" +#include "lib/selection_iter.h" +#include "lib/rplan.h" +#include "lib/cache/api.h" +#include "lib/resolve.h" + +#include "lib/utils.h" + +#define VERBOSE_MSG(qry, ...) kr_log_q((qry), SELECTION, __VA_ARGS__) + +#define DEFAULT_TIMEOUT 400 +#define MAX_TIMEOUT 10000 +#define EXPLORE_TIMEOUT_COEFFICIENT 2 +#define MAX_BACKOFF 8 +#define MINIMAL_TIMEOUT_ADDITION 20 + +/* After TCP_TIMEOUT_THRESHOLD timeouts one transport, we'll switch to TCP. */ +#define TCP_TIMEOUT_THRESHOLD 2 +/* If the expected RTT is over TCP_RTT_THRESHOLD we switch to TCP instead. */ +#define TCP_RTT_THRESHOLD 2000 + +/* Define ε for ε-greedy algorithm (see select_transport) + * as ε=EPSILON_NOMIN/EPSILON_DENOM */ +#define EPSILON_NOMIN 1 +#define EPSILON_DENOM 20 + +static const char *kr_selection_error_str(enum kr_selection_error err) { + switch (err) { + #define X(ENAME) case KR_SELECTION_ ## ENAME: return #ENAME + X(OK); + X(QUERY_TIMEOUT); + X(TLS_HANDSHAKE_FAILED); + X(TCP_CONNECT_FAILED); + X(TCP_CONNECT_TIMEOUT); + X(REFUSED); + X(SERVFAIL); + X(FORMERR); + X(FORMERR_EDNS); + X(NOTIMPL); + X(OTHER_RCODE); + X(MALFORMED); + X(MISMATCHED); + X(TRUNCATED); + X(DNSSEC_ERROR); + X(LAME_DELEGATION); + X(BAD_CNAME); + case KR_SELECTION_NUMBER_OF_ERRORS: break; // not a valid code + #undef X + } + kr_assert(false); // we want to define all; compiler helps by -Wswitch (no default:) + return NULL; +} + + +/* Simple detection of IPv6 being broken. + * + * We follow all IPv6 timeouts and successes. Consider it broken iff we've had + * timeouts on several different IPv6 prefixes since the last IPv6 success. + * Note: unlike the rtt_state, this happens only per-process (for simplicity). + * + * ## NO6_PREFIX_* choice + * For our practical use we choose primarily based on root and typical TLD servers. + * Looking at *.{root,gtld}-servers.net, we have 7/26 AAAAs in 2001:500:00**:: + * but adding one more byte makes these completely unique, so we choose /48. + * As distribution to ASs seems to be on shorter prefixes (RIPE: /32 -- /24?), + * we wait for several distinct prefixes. + */ + +#define NO6_PREFIX_COUNT 6 +#define NO6_PREFIX_BYTES (48/8) +static struct { + int len_used; + uint8_t addr_prefixes[NO6_PREFIX_COUNT][NO6_PREFIX_BYTES]; +} no6_est = { .len_used = 0 }; + +bool no6_is_bad(void) +{ + return no6_est.len_used == NO6_PREFIX_COUNT; +} + +static void no6_timed_out(const struct kr_query *qry, const uint8_t *addr) +{ + if (no6_is_bad()) { // we can't get worse + VERBOSE_MSG(qry, "NO6: timed out, but bad already\n"); + return; + } + // If we have the address already, do nothing. + for (int i = 0; i < no6_est.len_used; ++i) { + if (memcmp(addr, no6_est.addr_prefixes[i], NO6_PREFIX_BYTES) == 0) { + VERBOSE_MSG(qry, "NO6: timed out, repeated prefix, timeouts %d/%d\n", + no6_est.len_used, (int)NO6_PREFIX_COUNT); + return; + } + } + // Append! + memcpy(no6_est.addr_prefixes[no6_est.len_used++], addr, NO6_PREFIX_BYTES); + VERBOSE_MSG(qry, "NO6: timed out, appended, timeouts %d/%d\n", + no6_est.len_used, (int)NO6_PREFIX_COUNT); +} + +static inline void no6_success(const struct kr_query *qry) +{ + if (no6_est.len_used) { + VERBOSE_MSG(qry, "NO6: success, zeroing %d/%d\n", + no6_est.len_used, (int)NO6_PREFIX_COUNT); + } + no6_est.len_used = 0; +} + + +/* Simple cache interface follows */ + +static knot_db_val_t cache_key(const uint8_t *ip, size_t len) +{ + // CACHE_KEY_DEF: '\0' + 'S' + raw IP + const size_t key_len = len + 2; + uint8_t *key_data = malloc(key_len); + key_data[0] = '\0'; + key_data[1] = 'S'; + memcpy(key_data + 2, ip, len); + knot_db_val_t key = { + .len = key_len, + .data = key_data, + }; + return key; +} + +/* First value of timeout will be calculated as SRTT+4*VARIANCE + * by calc_timeout(), so it'll be equal to DEFAULT_TIMEOUT. */ +static const struct rtt_state default_rtt_state = { .srtt = 0, + .variance = DEFAULT_TIMEOUT / 4, + .consecutive_timeouts = 0, + .dead_since = 0 }; + +struct rtt_state get_rtt_state(const uint8_t *ip, size_t len, + struct kr_cache *cache) +{ + struct rtt_state state; + knot_db_val_t value; + knot_db_t *db = cache->db; + struct kr_cdb_stats *stats = &cache->stats; + + knot_db_val_t key = cache_key(ip, len); + + if (cache->api->read(db, stats, &key, &value, 1)) { + state = default_rtt_state; + } else if (kr_fails_assert(value.len == sizeof(struct rtt_state))) { + // shouldn't happen but let's be more robust + state = default_rtt_state; + } else { // memcpy is safe for unaligned case (on non-x86) + memcpy(&state, value.data, sizeof(state)); + } + + free(key.data); + return state; +} + +int put_rtt_state(const uint8_t *ip, size_t len, struct rtt_state state, + struct kr_cache *cache) +{ + knot_db_t *db = cache->db; + struct kr_cdb_stats *stats = &cache->stats; + + knot_db_val_t key = cache_key(ip, len); + knot_db_val_t value = { .len = sizeof(struct rtt_state), + .data = &state }; + + int ret = cache->api->write(db, stats, &key, &value, 1); + cache->api->commit(db, stats); + + free(key.data); + return ret; +} + +void bytes_to_ip(uint8_t *bytes, size_t len, uint16_t port, union kr_sockaddr *dst) +{ + switch (len) { + case sizeof(struct in_addr): + dst->ip4.sin_family = AF_INET; + memcpy(&dst->ip4.sin_addr, bytes, len); + dst->ip4.sin_port = htons(port); + break; + case sizeof(struct in6_addr): + memset(&dst->ip6, 0, sizeof(dst->ip6)); // avoid uninit surprises + dst->ip6.sin6_family = AF_INET6; + memcpy(&dst->ip6.sin6_addr, bytes, len); + dst->ip6.sin6_port = htons(port); + break; + default: + kr_assert(false); + } +} + +uint8_t *ip_to_bytes(const union kr_sockaddr *src, size_t len) +{ + switch (len) { + case sizeof(struct in_addr): + return (uint8_t *)&src->ip4.sin_addr; + case sizeof(struct in6_addr): + return (uint8_t *)&src->ip6.sin6_addr; + default: + kr_assert(false); + return NULL; + } +} + +static bool no_rtt_info(struct rtt_state s) +{ + return s.srtt == 0 && s.consecutive_timeouts == 0; +} + +static unsigned back_off_timeout(uint32_t to, int pow) +{ + pow = MIN(pow, MAX_BACKOFF); + to <<= pow; + return MIN(to, MAX_TIMEOUT); +} + +/* This is verbatim (minus the default timeout value and minimal variance) + * RFC6298, sec. 2. */ +static unsigned calc_timeout(struct rtt_state state) +{ + int32_t timeout = state.srtt + MAX(4 * state.variance, MINIMAL_TIMEOUT_ADDITION); + return back_off_timeout(timeout, state.consecutive_timeouts); +} + +/* This is verbatim RFC6298, sec. 2. */ +static struct rtt_state calc_rtt_state(struct rtt_state old, unsigned new_rtt) +{ + if (no_rtt_info(old)) { + return (struct rtt_state){ new_rtt, new_rtt / 2, 0 }; + } + + struct rtt_state ret = { 0 }; + ret.variance = (3 * old.variance + abs(old.srtt - (int32_t)new_rtt) + + 2/*rounding*/) / 4; + ret.srtt = (7 * old.srtt + new_rtt + 4/*rounding*/) / 8; + + return ret; +} + +/** + * @internal Invalidate addresses which should be considered dead + */ +static void invalidate_dead_upstream(struct address_state *state, + unsigned int retry_timeout) +{ + struct rtt_state *rs = &state->rtt_state; + if (rs->dead_since) { + uint64_t now = kr_now(); + if (now < rs->dead_since) { + // broken continuity of timestamp (reboot, different machine, etc.) + *rs = default_rtt_state; + } else if (now < rs->dead_since + retry_timeout) { + // period when we don't want to use the address + state->generation = -1; + } else { + kr_assert(now >= rs->dead_since + retry_timeout); + // we allow to retry the server now + // TODO: perhaps tweak *rs? + } + } +} + +/** + * @internal Check if IP address is TLS capable. + * + * @p req has to have the selection_context properly initialized. + */ +static void check_tls_capable(struct address_state *address_state, + struct kr_request *req, struct sockaddr *address) +{ + address_state->tls_capable = + req->selection_context.is_tls_capable ? + req->selection_context.is_tls_capable(address) : + false; +} + +#if 0 +/* TODO: uncomment these once we actually use the information it collects. */ +/** + * Check if there is a existing TCP connection to this address. + * + * @p req has to have the selection_context properly initialized. + */ +void check_tcp_connections(struct address_state *address_state, struct kr_request *req, struct sockaddr *address) { + address_state->tcp_connected = req->selection_context.is_tcp_connected ? req->selection_context.is_tcp_connected(address) : false; + address_state->tcp_waiting = req->selection_context.is_tcp_waiting ? req->selection_context.is_tcp_waiting(address) : false; +} +#endif + +/** + * @internal Invalidate address if the respective IP version is disabled. + */ +static void check_network_settings(struct address_state *address_state, + size_t address_len, bool no_ipv4, bool no_ipv6) +{ + if (no_ipv4 && address_len == sizeof(struct in_addr)) { + address_state->generation = -1; + } + if (no_ipv6 && address_len == sizeof(struct in6_addr)) { + address_state->generation = -1; + } +} + +void update_address_state(struct address_state *state, union kr_sockaddr *address, + size_t address_len, struct kr_query *qry) +{ + check_tls_capable(state, qry->request, &address->ip); + /* TODO: uncomment this once we actually use the information it collects + check_tcp_connections(address_state, qry->request, &address->ip); + */ + check_network_settings(state, address_len, qry->flags.NO_IPV4, + qry->flags.NO_IPV6); + state->rtt_state = + get_rtt_state(ip_to_bytes(address, address_len), + address_len, &qry->request->ctx->cache); + invalidate_dead_upstream( + state, qry->request->ctx->cache_rtt_tout_retry_interval); +#ifdef SELECTION_CHOICE_LOGGING + // This is sometimes useful for debugging, but usually too verbose + if (kr_log_is_debug_qry(SELECTION, qry)) { + const char *ns_str = kr_straddr(&address->ip); + VERBOSE_MSG(qry, "rtt of %s is %d, variance is %d\n", ns_str, + state->rtt_state.srtt, state->rtt_state.variance); + } +#endif +} + +static int cmp_choices(const struct choice *a_, const struct choice *b_) +{ + int diff; + /* Prefer IPv4 if IPv6 appears to be generally broken. */ + diff = (int)a_->address_len - (int)b_->address_len; + if (diff && no6_is_bad()) { + return diff; + } + /* Address with no RTT information is better than address + * with some information. */ + if ((diff = no_rtt_info(b_->address_state->rtt_state) - + no_rtt_info(a_->address_state->rtt_state))) { + return diff; + } + /* Address with less errors is better. */ + if ((diff = a_->address_state->error_count - + b_->address_state->error_count)) { + return diff; + } + /* Address with smaller expected timeout is better. */ + if ((diff = calc_timeout(a_->address_state->rtt_state) - + calc_timeout(b_->address_state->rtt_state))) { + return diff; + } + return 0; +} +/** Select the best entry from choices[] according to cmp_choices() comparator. + * + * Ties are decided in an (almost) uniformly random fashion. + */ +static const struct choice * select_best(const struct choice choices[], int choices_len) +{ + /* Deciding ties: it's as-if each index carries one byte of randomness. + * Ties get decided by comparing that byte, and the byte itself + * is computed lazily (negative until computed). + */ + int best_i = 0; + int best_rnd = -1; + for (int i = 1; i < choices_len; ++i) { + int diff = cmp_choices(&choices[i], &choices[best_i]); + if (diff > 0) + continue; + if (diff < 0) { + best_i = i; + best_rnd = -1; + continue; + } + if (best_rnd < 0) + best_rnd = kr_rand_bytes(1); + int new_rnd = kr_rand_bytes(1); + if (new_rnd < best_rnd) { + best_i = i; + best_rnd = new_rnd; + } + } + return &choices[best_i]; +} + +/* Adjust choice from `unresolved` in case of NO6 (broken IPv6). */ +static struct kr_transport unresolved_adjust(const struct to_resolve unresolved[], + int unresolved_len, int index) +{ + if (unresolved[index].type != KR_TRANSPORT_RESOLVE_AAAA || !no6_is_bad()) + goto finish; + /* AAAA is detected as bad; let's choose randomly from others, if there are any. */ + int aaaa_count = 0; + for (int i = 0; i < unresolved_len; ++i) + aaaa_count += (unresolved[i].type == KR_TRANSPORT_RESOLVE_AAAA); + if (aaaa_count == unresolved_len) + goto finish; + /* Chosen index within non-AAAA items. */ + int i_no6 = kr_rand_bytes(1) % (unresolved_len - aaaa_count); + for (int i = 0; i < unresolved_len; ++i) { + if (unresolved[i].type == KR_TRANSPORT_RESOLVE_AAAA) { + //continue + } else if (i_no6 == 0) { + index = i; + break; + } else { + --i_no6; + } + } +finish: + return (struct kr_transport){ + .protocol = unresolved[index].type, + .ns_name = unresolved[index].name + }; +} + +/* Performs the actual selection (currently variation on epsilon-greedy). */ +struct kr_transport *select_transport(const struct choice choices[], int choices_len, + const struct to_resolve unresolved[], + int unresolved_len, int timeouts, + struct knot_mm *mempool, bool tcp, + size_t *choice_index) +{ + if (!choices_len && !unresolved_len) { + /* There is nothing to choose from */ + return NULL; + } + + struct kr_transport *transport = mm_calloc(mempool, 1, sizeof(*transport)); + + /* If there are some addresses with no rtt_info we try them + * first (see cmp_choices). So unknown servers are chosen + * *before* the best know server. This ensures that every option + * is tried before going back to some that was tried before. */ + const struct choice *best = select_best(choices, choices_len); + const struct choice *chosen; + + const bool explore = choices_len == 0 || kr_rand_coin(EPSILON_NOMIN, EPSILON_DENOM) + /* We may need to explore to get at least one A record. */ + || (no6_is_bad() && best->address.ip.sa_family == AF_INET6); + if (explore) { + /* "EXPLORE": + * randomly choose some option + * (including resolution of some new name). */ + int index = kr_rand_bytes(1) % (choices_len + unresolved_len); + if (index < unresolved_len) { + // We will resolve a new NS name + *transport = unresolved_adjust(unresolved, unresolved_len, index); + return transport; + } else { + chosen = &choices[index - unresolved_len]; + } + } else { + /* "EXPLOIT": + * choose a resolved address which seems best right now. */ + chosen = best; + } + + /* Don't try the same server again when there are other choices to be explored */ + if (chosen->address_state->error_count && unresolved_len) { + int index = kr_rand_bytes(1) % unresolved_len; + *transport = unresolved_adjust(unresolved, unresolved_len, index); + return transport; + } + + unsigned timeout; + if (no_rtt_info(chosen->address_state->rtt_state)) { + /* Exponential back-off when retrying after timeout and choosing + * an unknown server. */ + timeout = back_off_timeout(DEFAULT_TIMEOUT, timeouts); + } else { + timeout = calc_timeout(chosen->address_state->rtt_state); + if (explore) { + /* When trying a random server, we cap the timeout to EXPLORE_TIMEOUT_COEFFICIENT + * times the timeout for the best server. This is done so we don't spend + * unreasonable amounts of time probing really bad servers while still + * checking once in a while for e.g. big network change etc. + * We also note this capping was done and don't punish the bad server + * further if it fails to answer in the capped timeout. */ + unsigned best_timeout = calc_timeout(best->address_state->rtt_state); + if (timeout > best_timeout * EXPLORE_TIMEOUT_COEFFICIENT) { + timeout = best_timeout * EXPLORE_TIMEOUT_COEFFICIENT; + transport->timeout_capped = true; + } + } + } + + enum kr_transport_protocol protocol; + if (chosen->address_state->tls_capable) { + protocol = KR_TRANSPORT_TLS; + } else if (tcp || + chosen->address_state->errors[KR_SELECTION_QUERY_TIMEOUT] >= TCP_TIMEOUT_THRESHOLD || + timeout > TCP_RTT_THRESHOLD) { + protocol = KR_TRANSPORT_TCP; + } else { + protocol = KR_TRANSPORT_UDP; + } + + *transport = (struct kr_transport){ + .ns_name = chosen->address_state->ns_name, + .protocol = protocol, + .timeout = timeout, + }; + + int port = chosen->port; + if (!port) { + switch (transport->protocol) { + case KR_TRANSPORT_TLS: + port = KR_DNS_TLS_PORT; + break; + case KR_TRANSPORT_UDP: + case KR_TRANSPORT_TCP: + port = KR_DNS_PORT; + break; + default: + kr_assert(false); + return NULL; + } + } + + switch (chosen->address_len) + { + case sizeof(struct in_addr): + transport->address.ip4 = chosen->address.ip4; + transport->address.ip4.sin_port = htons(port); + break; + case sizeof(struct in6_addr): + transport->address.ip6 = chosen->address.ip6; + transport->address.ip6.sin6_port = htons(port); + break; + default: + kr_assert(false); + return NULL; + } + + transport->address_len = chosen->address_len; + + if (choice_index) { + *choice_index = chosen->address_state->choice_array_index; + } + + return transport; +} + +void update_rtt(struct kr_query *qry, struct address_state *addr_state, + const struct kr_transport *transport, unsigned rtt) +{ + if (!transport || !addr_state) { + /* Answers from cache have NULL transport, ignore them. */ + return; + } + + struct kr_cache *cache = &qry->request->ctx->cache; + + uint8_t *address = ip_to_bytes(&transport->address, transport->address_len); + /* This construct is a bit racy since the global state may change + * between calls to `get_rtt_state` and `put_rtt_state` but we don't + * care that much since it is rare and we only risk slightly suboptimal + * transport choice. */ + struct rtt_state cur_rtt_state = + get_rtt_state(address, transport->address_len, cache); + struct rtt_state new_rtt_state = calc_rtt_state(cur_rtt_state, rtt); + put_rtt_state(address, transport->address_len, new_rtt_state, cache); + + if (transport->address_len == sizeof(struct in6_addr)) + no6_success(qry); + + if (kr_log_is_debug_qry(SELECTION, qry)) { + KR_DNAME_GET_STR(ns_name, transport->ns_name); + KR_DNAME_GET_STR(zonecut_str, qry->zone_cut.name); + const char *ns_str = kr_straddr(&transport->address.ip); + + VERBOSE_MSG( + qry, + "=> id: '%05u' updating: '%s'@'%s' zone cut: '%s'" + " with rtt %u to srtt: %d and variance: %d \n", + qry->id, ns_name, ns_str ? ns_str : "", zonecut_str, + rtt, new_rtt_state.srtt, new_rtt_state.variance); + } +} + +/// Update rtt_state (including caching) after a server timed out. +static void server_timeout(const struct kr_query *qry, const struct kr_transport *transport, + struct address_state *addr_state, struct kr_cache *cache) +{ + // Make sure that the timeout wasn't capped; see kr_transport::timeout_capped + if (transport->timeout_capped) + return; + + const uint8_t *address = ip_to_bytes(&transport->address, transport->address_len); + if (transport->address_len == sizeof(struct in6_addr)) + no6_timed_out(qry, address); + + struct rtt_state *state = &addr_state->rtt_state; + // While we were waiting for timeout, the stats might have changed considerably, + // so let's overwrite what we had by fresh cache contents. + // This is useful when the address is busy (we query it concurrently). + *state = get_rtt_state(address, transport->address_len, cache); + + ++state->consecutive_timeouts; + // Avoid overflow; we don't utilize very high values anyway (arbitrary limit). + state->consecutive_timeouts = MIN(64, state->consecutive_timeouts); + if (state->consecutive_timeouts >= KR_NS_TIMEOUT_ROW_DEAD) { + // Only mark as dead if we waited long enough, + // so that many (concurrent) short attempts can't cause the dead state. + if (transport->timeout >= KR_NS_TIMEOUT_MIN_DEAD_TIMEOUT) + state->dead_since = kr_now(); + } + + // If transport was chosen by a different query, that one will cache it. + if (!transport->deduplicated) { + put_rtt_state(address, transport->address_len, *state, cache); + } else { + kr_cache_commit(cache); // Avoid any risk of long transaction. + } +} +// Not everything can be checked in nice ways like static_assert() +static __attribute__((constructor)) void test_RTT_consts(void) +{ + // See KR_NS_TIMEOUT_MIN_DEAD_TIMEOUT above. + kr_require( + calc_timeout((struct rtt_state){ .consecutive_timeouts = MAX_BACKOFF, }) + >= KR_NS_TIMEOUT_MIN_DEAD_TIMEOUT + ); +} + +void error(struct kr_query *qry, struct address_state *addr_state, + const struct kr_transport *transport, + enum kr_selection_error sel_error) +{ + if (!transport || !addr_state) { + /* Answers from cache have NULL transport, ignore them. */ + return; + } + + switch (sel_error) { + case KR_SELECTION_OK: + return; + case KR_SELECTION_TCP_CONNECT_FAILED: + case KR_SELECTION_TCP_CONNECT_TIMEOUT: + qry->server_selection.local_state->force_udp = true; + qry->flags.NO_0X20 = false; + /* Connection and handshake failures have properties similar + * to UDP timeouts, so we handle them (almost) the same way. */ + /* fall-through */ + case KR_SELECTION_TLS_HANDSHAKE_FAILED: + case KR_SELECTION_QUERY_TIMEOUT: + qry->server_selection.local_state->timeouts++; + server_timeout(qry, transport, addr_state, &qry->request->ctx->cache); + break; + case KR_SELECTION_FORMERR: + if (qry->flags.NO_EDNS) { + addr_state->broken = true; + } else { + qry->flags.NO_EDNS = true; + } + break; + case KR_SELECTION_FORMERR_EDNS: + addr_state->broken = true; + break; + case KR_SELECTION_MISMATCHED: + if (qry->flags.NO_0X20 && qry->flags.TCP) { + addr_state->broken = true; + } else { + qry->flags.TCP = true; + qry->flags.NO_0X20 = true; + } + break; + case KR_SELECTION_TRUNCATED: + if (transport->protocol == KR_TRANSPORT_UDP) { + qry->server_selection.local_state->truncated = true; + /* TC=1 over UDP is not an error, so we compensate. */ + addr_state->error_count--; + } else { + addr_state->broken = true; + } + break; + case KR_SELECTION_REFUSED: + case KR_SELECTION_SERVFAIL: + if (qry->flags.NO_MINIMIZE && qry->flags.NO_0X20 && qry->flags.TCP) { + addr_state->broken = true; + } else if (qry->flags.NO_MINIMIZE) { + qry->flags.NO_0X20 = true; + qry->flags.TCP = true; + } else { + qry->flags.NO_MINIMIZE = true; + } + break; + case KR_SELECTION_LAME_DELEGATION: + if (qry->flags.NO_MINIMIZE) { + /* Lame delegations are weird, they breed more lame delegations on broken + * zones since trying another server from the same set usually doesn't help. + * We force resolution of another NS name in hope of getting somewhere. */ + qry->server_selection.local_state->force_resolve = true; + addr_state->broken = true; + } else { + qry->flags.NO_MINIMIZE = true; + } + break; + case KR_SELECTION_NOTIMPL: + case KR_SELECTION_OTHER_RCODE: + case KR_SELECTION_DNSSEC_ERROR: + case KR_SELECTION_BAD_CNAME: + case KR_SELECTION_MALFORMED: + /* These errors are fatal, no point in trying this server again. */ + addr_state->broken = true; + break; + default: + kr_assert(false); + return; + } + + addr_state->error_count++; + addr_state->errors[sel_error]++; + + if (kr_log_is_debug_qry(SELECTION, qry)) { + KR_DNAME_GET_STR(ns_name, transport->ns_name); + KR_DNAME_GET_STR(zonecut_str, qry->zone_cut.name); + const char *ns_str = kr_straddr(&transport->address.ip); + const char *err_str = kr_selection_error_str(sel_error); + + VERBOSE_MSG( + qry, + "=> id: '%05u' noting selection error: '%s'@'%s'" + " zone cut: '%s' error: %d %s\n", + qry->id, ns_name, ns_str ? ns_str : "", + zonecut_str, sel_error, err_str ? err_str : "??"); + } +} + +void kr_server_selection_init(struct kr_query *qry) +{ + struct knot_mm *mempool = &qry->request->pool; + struct local_state *local_state = mm_calloc(mempool, 1, sizeof(*local_state)); + + if (qry->flags.FORWARD || qry->flags.STUB) { + qry->server_selection = (struct kr_server_selection){ + .initialized = true, + .choose_transport = forward_choose_transport, + .update_rtt = forward_update_rtt, + .error = forward_error, + .local_state = local_state, + }; + forward_local_state_alloc( + mempool, &qry->server_selection.local_state->private, + qry->request); + } else { + qry->server_selection = (struct kr_server_selection){ + .initialized = true, + .choose_transport = iter_choose_transport, + .update_rtt = iter_update_rtt, + .error = iter_error, + .local_state = local_state, + }; + iter_local_state_alloc( + mempool, &qry->server_selection.local_state->private); + } +} + +int kr_forward_add_target(struct kr_request *req, const struct sockaddr *sock) +{ + if (!req->selection_context.forwarding_targets.at) { + return kr_error(EINVAL); + } + + union kr_sockaddr address; + + switch (sock->sa_family) { + case AF_INET: + if (req->options.NO_IPV4) + return kr_error(EINVAL); + address.ip4 = *(const struct sockaddr_in *)sock; + break; + case AF_INET6: + if (req->options.NO_IPV6) + return kr_error(EINVAL); + address.ip6 = *(const struct sockaddr_in6 *)sock; + break; + default: + return kr_error(EINVAL); + } + + array_push_mm(req->selection_context.forwarding_targets, address, + kr_memreserve, &req->pool); + return kr_ok(); +} diff --git a/lib/selection.h b/lib/selection.h new file mode 100644 index 0000000..34cc69c --- /dev/null +++ b/lib/selection.h @@ -0,0 +1,269 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +/** + * @file selection.h + * Provides server selection API (see `kr_server_selection`) + * and functions common to both implementations. + */ + +#include "lib/cache/api.h" + +/* After KR_NS_TIMEOUT_ROW_DEAD consecutive timeouts + * where at least one was over KR_NS_TIMEOUT_MIN_DEAD_TIMEOUT ms, + * we consider the upstream IP dead for KR_NS_TIMEOUT_RETRY_INTERVAL ms */ +#define KR_NS_TIMEOUT_ROW_DEAD 4 +#define KR_NS_TIMEOUT_MIN_DEAD_TIMEOUT 800 /* == DEFAULT_TIMEOUT * 2 */ +#define KR_NS_TIMEOUT_RETRY_INTERVAL 1000 + +/** + * These errors are to be reported as feedback to server selection. + * See `kr_server_selection::error` for more details. + */ +enum kr_selection_error { + KR_SELECTION_OK = 0, + + // Network errors + KR_SELECTION_QUERY_TIMEOUT, + KR_SELECTION_TLS_HANDSHAKE_FAILED, + KR_SELECTION_TCP_CONNECT_FAILED, + KR_SELECTION_TCP_CONNECT_TIMEOUT, + + // RCODEs + KR_SELECTION_REFUSED, + KR_SELECTION_SERVFAIL, + KR_SELECTION_FORMERR, /// inside an answer without an OPT record + KR_SELECTION_FORMERR_EDNS, /// with an OPT record + KR_SELECTION_NOTIMPL, + KR_SELECTION_OTHER_RCODE, + + // DNS errors + KR_SELECTION_MALFORMED, + /** Name or type mismatch. */ + KR_SELECTION_MISMATCHED, + KR_SELECTION_TRUNCATED, + KR_SELECTION_DNSSEC_ERROR, + KR_SELECTION_LAME_DELEGATION, + /** Too long chain, or a cycle. */ + KR_SELECTION_BAD_CNAME, + + /** Leave this last, as it is used as array size. */ + KR_SELECTION_NUMBER_OF_ERRORS +}; + +enum kr_transport_protocol { + /** Selected name with no IPv4 address, it has to be resolved first. */ + KR_TRANSPORT_RESOLVE_A, + /** Selected name with no IPv6 address, it has to be resolved first. */ + KR_TRANSPORT_RESOLVE_AAAA, + KR_TRANSPORT_UDP, + KR_TRANSPORT_TCP, + KR_TRANSPORT_TLS, +}; + +/** + * Output of the selection algorithm. + */ +struct kr_transport { + knot_dname_t *ns_name; /**< Set to "." for forwarding targets.*/ + union kr_sockaddr address; + size_t address_len; + enum kr_transport_protocol protocol; + unsigned timeout; /**< Timeout in ms to be set for UDP transmission. */ + /** Timeout was capped to a maximum value based on the other candidates + * when choosing this transport. The timeout therefore can be much lower + * than what we expect it to be. We basically probe the server for a sudden + * network change but we expect it to timeout in most cases. We have to keep + * this in mind when noting the timeout in cache. */ + bool timeout_capped; + /** True iff transport was set in worker.c:subreq_finalize, + * that means it may be different from the one originally chosen one.*/ + bool deduplicated; +}; + +struct local_state { + int timeouts; /**< Number of timeouts that occurred resolving this query.*/ + bool truncated; /**< Query was truncated, switch to TCP. */ + /** Force resolution of a new NS name (if possible) + * Done by selection.c:error in some cases. */ + bool force_resolve; + /** Used to work around auths with broken TCP. */ + bool force_udp; + void *private; /**< Inner state of the implementation.*/ +}; + +/** + * Specifies a API for selecting transports and giving feedback on the choices. + * + * The function pointers are to be used throughout resolver when some information about + * the transport is obtained. E.g. RTT in `worker.c` or RCODE in `iterate.c`,… + */ +struct kr_server_selection { + bool initialized; + /** + * Puts a pointer to next transport of @p qry to @p transport . + * + * Allocates new kr_transport in request's mempool, chooses transport to be used for this query. + * Selection may fail, so @p transport can be set to NULL. + * + * @param transport to be filled with pointer to the chosen transport or NULL on failure + */ + void (*choose_transport)(struct kr_query *qry, + struct kr_transport **transport); + /** Report back the RTT of network operation for transport in ms. */ + void (*update_rtt)(struct kr_query *qry, + const struct kr_transport *transport, unsigned rtt); + /** Report back error encountered with the chosen transport. See `enum kr_selection` */ + void (*error)(struct kr_query *qry, + const struct kr_transport *transport, + enum kr_selection_error error); + + struct local_state *local_state; +}; + +/** + * @brief Initialize the server selection API for @p qry. + * + * The implementation is to be chosen based on qry->flags. + */ +KR_EXPORT +void kr_server_selection_init(struct kr_query *qry); + +/** + * @brief Add forwarding target to request. + * + * This is exposed to Lua in order to add forwarding targets to request. + * These are then shared by all the queries in said request. + */ +KR_EXPORT +int kr_forward_add_target(struct kr_request *req, const struct sockaddr *sock); + + + + + +/* Below are internal parts shared by ./selection_{forward,iter}.c */ + +/** + * To be held per IP address in the global LMDB cache + */ +struct rtt_state { + int32_t srtt; /**< Smoothed RTT, i.e. an estimate of round-trip time. */ + int32_t variance; /**< An estimate of RTT's standard derivation (not variance). */ + /** Note: some TCP and TLS failures are also considered as timeouts. */ + int32_t consecutive_timeouts; + /** Timestamp of pronouncing this IP bad based on KR_NS_TIMEOUT_ROW_DEAD */ + uint64_t dead_since; +}; + +/** + * @brief To be held per IP address and locally "inside" query. + */ +struct address_state { + /** Used to distinguish old and valid records in local_state; -1 means unusable IP. */ + unsigned int generation; + struct rtt_state rtt_state; + knot_dname_t *ns_name; + bool tls_capable : 1; + /* TODO: uncomment these once we actually use this information in selection + bool tcp_waiting : 1; + bool tcp_connected : 1; + */ + int choice_array_index; + int error_count; + bool broken; + int errors[KR_SELECTION_NUMBER_OF_ERRORS]; +}; + +/** + * @brief Array of these is one of inputs for the actual selection algorithm (`select_transport`) + */ +struct choice { + union kr_sockaddr address; + size_t address_len; + struct address_state *address_state; + /** used to overwrite the port number; + * if zero, `select_transport` determines it. */ + uint16_t port; +}; + +/** + * @brief Array of these is description of names to be resolved (i.e. name without some address) + */ +struct to_resolve { + knot_dname_t *name; + /** Either KR_TRANSPORT_RESOLVE_A or KR_TRANSPORT_RESOLVE_AAAA is valid here. */ + enum kr_transport_protocol type; +}; + +/** + * @brief Based on passed choices, choose the next transport. + * + * Common function to both implementations (iteration and forwarding). + * The `*_choose_transport` functions from `selection_*.h` preprocess the input for this one. + * + * @param choices Options to choose from, see struct above + * @param unresolved Array of names that can be resolved (i.e. no A/AAAA record) + * @param timeouts Number of timeouts that occurred in this query (used for exponential backoff) + * @param mempool Memory context of current request + * @param tcp Force TCP as transport protocol + * @param[out] choice_index Optionally index of the chosen transport in the @p choices array. + * @return Chosen transport (on mempool) or NULL when no choice is viable + */ +struct kr_transport *select_transport(const struct choice choices[], int choices_len, + const struct to_resolve unresolved[], + int unresolved_len, int timeouts, + struct knot_mm *mempool, bool tcp, + size_t *choice_index); + +/** + * Common part of RTT feedback mechanism. Notes RTT to global cache. + */ +void update_rtt(struct kr_query *qry, struct address_state *addr_state, + const struct kr_transport *transport, unsigned rtt); + +/** + * Common part of error feedback mechanism. + */ +void error(struct kr_query *qry, struct address_state *addr_state, + const struct kr_transport *transport, + enum kr_selection_error sel_error); + +/** + * Get RTT state from cache. Returns `default_rtt_state` on unknown addresses. + * + * Note that this opens a cache transaction which is usually closed by calling + * `put_rtt_state`, i.e. callee is responsible for its closing + * (e.g. calling kr_cache_commit). + */ +struct rtt_state get_rtt_state(const uint8_t *ip, size_t len, + struct kr_cache *cache); + +int put_rtt_state(const uint8_t *ip, size_t len, struct rtt_state state, + struct kr_cache *cache); + +/** + * @internal Helper function for conversion between different IP representations. + */ +void bytes_to_ip(uint8_t *bytes, size_t len, uint16_t port, union kr_sockaddr *dst); + +/** + * @internal Helper function for conversion between different IP representations. + */ +uint8_t *ip_to_bytes(const union kr_sockaddr *src, size_t len); + +/** + * @internal Fetch per-address information from various sources. + * + * Note that this opens a RO cache transaction; the callee is responsible + * for its closing not too long afterwards (e.g. calling kr_cache_commit). + */ +void update_address_state(struct address_state *state, union kr_sockaddr *address, + size_t address_len, struct kr_query *qry); + +/** @internal Return whether IPv6 is considered to be broken. */ +bool no6_is_bad(void); + diff --git a/lib/selection_forward.c b/lib/selection_forward.c new file mode 100644 index 0000000..54f9a12 --- /dev/null +++ b/lib/selection_forward.c @@ -0,0 +1,136 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "lib/selection_forward.h" +#include "lib/resolve.h" + +#define VERBOSE_MSG(qry, ...) kr_log_q((qry), SELECTION, __VA_ARGS__) + +#define FORWARDING_TIMEOUT 2000 +/* TODO: well, this is a bit hard; maybe we'd better have a different approach + * for estimating DEAD-ness for forwarding. + * Even ACKs on connections might be useful here. */ +static_assert(FORWARDING_TIMEOUT >= KR_NS_TIMEOUT_MIN_DEAD_TIMEOUT, + "Bad combination of NS selection limits."); + +struct forward_local_state { + kr_sockaddr_array_t *targets; + struct address_state *addr_states; + /** Index of last choice in the targets array, used for error reporting. */ + size_t last_choice_index; +}; + +void forward_local_state_alloc(struct knot_mm *mm, void **local_state, + struct kr_request *req) +{ + kr_require(req->selection_context.forwarding_targets.at); + *local_state = mm_calloc(mm, 1, sizeof(struct forward_local_state)); + + struct forward_local_state *forward_state = *local_state; + forward_state->targets = &req->selection_context.forwarding_targets; + + forward_state->addr_states = mm_calloc(mm, forward_state->targets->len, + sizeof(struct address_state)); +} + +void forward_choose_transport(struct kr_query *qry, + struct kr_transport **transport) +{ + struct forward_local_state *local_state = + qry->server_selection.local_state->private; + struct choice choices[local_state->targets->len]; + int valid = 0; + + for (int i = 0; i < local_state->targets->len; i++) { + union kr_sockaddr *address = &local_state->targets->at[i]; + size_t addr_len; + uint16_t port; + switch (address->ip.sa_family) { + case AF_INET: + port = ntohs(address->ip4.sin_port); + addr_len = sizeof(struct in_addr); + break; + case AF_INET6: + port = ntohs(address->ip6.sin6_port); + addr_len = sizeof(struct in6_addr); + break; + default: + kr_assert(false); + *transport = NULL; + goto cleanup; + } + + struct address_state *addr_state = &local_state->addr_states[i]; + addr_state->ns_name = (knot_dname_t *)""; + + update_address_state(addr_state, address, addr_len, qry); + + if (addr_state->generation == -1) { + continue; + } + addr_state->choice_array_index = i; + + choices[valid++] = (struct choice){ + .address = *address, + .address_len = addr_len, + .address_state = addr_state, + .port = port, + }; + } + + bool tcp = qry->flags.TCP || qry->server_selection.local_state->truncated; + *transport = + select_transport(choices, valid, NULL, 0, + qry->server_selection.local_state->timeouts, + &qry->request->pool, tcp, + &local_state->last_choice_index); + if (*transport) { + /* Set static timeout for forwarding; there is no point in this + * being dynamic since the RTT of a packet to forwarding target + * says nothing about the network RTT of said target, since + * it is doing resolution upstream. */ + (*transport)->timeout = FORWARDING_TIMEOUT; + /* Try to avoid TCP in STUB case. It seems better for common use cases. */ + if (qry->flags.STUB && !tcp && (*transport)->protocol == KR_TRANSPORT_TCP) + (*transport)->protocol = KR_TRANSPORT_UDP; + /* We need to propagate this to flags since it's used in other + * parts of the resolver (e.g. logging and stats). */ + qry->flags.TCP = (*transport)->protocol == KR_TRANSPORT_TCP + || (*transport)->protocol == KR_TRANSPORT_TLS; + } +cleanup: + kr_cache_commit(&qry->request->ctx->cache); +} + +void forward_error(struct kr_query *qry, const struct kr_transport *transport, + enum kr_selection_error sel_error) +{ + if (!qry->server_selection.initialized) { + return; + } + struct forward_local_state *local_state = + qry->server_selection.local_state->private; + struct address_state *addr_state = + &local_state->addr_states[local_state->last_choice_index]; + error(qry, addr_state, transport, sel_error); +} + +void forward_update_rtt(struct kr_query *qry, + const struct kr_transport *transport, unsigned rtt) +{ + if (!qry->server_selection.initialized) { + return; + } + + if (!transport) { + return; + } + + struct forward_local_state *local_state = + qry->server_selection.local_state->private; + struct address_state *addr_state = + &local_state->addr_states[local_state->last_choice_index]; + + update_rtt(qry, addr_state, transport, rtt); +} diff --git a/lib/selection_forward.h b/lib/selection_forward.h new file mode 100644 index 0000000..0c48c40 --- /dev/null +++ b/lib/selection_forward.h @@ -0,0 +1,17 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "lib/selection.h" +#include "lib/resolve.h" + +void forward_local_state_alloc(struct knot_mm *mm, void **local_state, + struct kr_request *req); +void forward_choose_transport(struct kr_query *qry, + struct kr_transport **transport); +void forward_error(struct kr_query *qry, const struct kr_transport *transport, + enum kr_selection_error sel_error); +void forward_update_rtt(struct kr_query *qry, + const struct kr_transport *transport, unsigned rtt); diff --git a/lib/selection_iter.c b/lib/selection_iter.c new file mode 100644 index 0000000..5978278 --- /dev/null +++ b/lib/selection_iter.c @@ -0,0 +1,378 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "lib/selection_iter.h" +#include "lib/selection.h" + +#include "lib/generic/trie.h" +#include "lib/generic/pack.h" +#include "lib/zonecut.h" +#include "lib/resolve.h" + +#define VERBOSE_MSG(qry, ...) kr_log_q((qry), SELECTION, __VA_ARGS__) + +/// To be held per query and locally. Allocations are in the kr_request's mempool. +struct iter_local_state { + trie_t *names; /// knot_dname_t -> struct iter_name_state * + trie_t *addresses; /// IP address -> struct address_state * + knot_dname_t *zonecut; + /** Used to distinguish old and valid records in tries. */ + unsigned int generation; + enum kr_selection_error last_error; + unsigned int no_ns_addr_count; +}; + +enum record_state { RECORD_UNKNOWN, RECORD_RESOLVED, RECORD_TRIED }; + +// To be held per NS name and locally +struct iter_name_state { + unsigned int generation; + enum record_state a_state; + enum record_state aaaa_state; +}; + +void iter_local_state_alloc(struct knot_mm *mm, void **local_state) +{ + *local_state = mm_calloc(mm, 1, sizeof(struct iter_local_state)); +} + +static struct address_state *get_address_state(struct iter_local_state *local_state, + const struct kr_transport *transport) +{ + if (!transport) { + return NULL; + } + + uint8_t *address = ip_to_bytes(&transport->address, transport->address_len); + trie_val_t *address_state = trie_get_try(local_state->addresses, (char *)address, + transport->address_len); + if (!address_state) { + kr_assert(transport->deduplicated); + /* Transport was chosen by a different query. */ + return NULL; + } + return *address_state; +} + +static void unpack_state_from_zonecut(struct iter_local_state *local_state, + struct kr_query *qry) +{ + struct kr_zonecut *zonecut = &qry->zone_cut; + struct knot_mm *mm = &qry->request->pool; + + bool zcut_changed = false; + if (local_state->names == NULL || local_state->addresses == NULL) { + /* Local state initialization. */ + memset(local_state, 0, sizeof(struct iter_local_state)); + local_state->names = trie_create(mm); + local_state->addresses = trie_create(mm); + } else { + zcut_changed = !knot_dname_is_equal(zonecut->name, local_state->zonecut); + } + local_state->zonecut = zonecut->name; + local_state->generation++; + + if (zcut_changed) { + local_state->no_ns_addr_count = 0; + } + + trie_it_t *it; + const unsigned int current_generation = local_state->generation; + + for (it = trie_it_begin(zonecut->nsset); !trie_it_finished(it); trie_it_next(it)) { + knot_dname_t *dname = (knot_dname_t *)trie_it_key(it, NULL); + pack_t *addresses = *trie_it_val(it); + + trie_val_t *val = trie_get_ins(local_state->names, (char *)dname, + knot_dname_size(dname)); + if (!*val) { + /* We encountered this name for the first time. */ + *val = mm_calloc(mm, 1, sizeof(struct iter_name_state)); + } + struct iter_name_state *name_state = *val; + name_state->generation = current_generation; + + if (zcut_changed) { + /* Set name as unresolved as they might have fallen out + * of cache (TTL expired). */ + name_state->a_state = RECORD_UNKNOWN; + name_state->aaaa_state = RECORD_UNKNOWN; + } + + /* Iterate over all addresses of this NS (if any). */ + for (uint8_t *obj = pack_head(*addresses); obj != pack_tail(*addresses); + obj = pack_obj_next(obj)) { + uint8_t *address = pack_obj_val(obj); + size_t address_len = pack_obj_len(obj); + trie_val_t *tval = trie_get_ins(local_state->addresses, + (char *)address, + address_len); + if (!*tval) { + /* We have have not seen this address before. */ + *tval = mm_calloc(mm, 1, sizeof(struct address_state)); + } + struct address_state *address_state = *tval; + address_state->generation = current_generation; + address_state->ns_name = dname; + + if (address_len == sizeof(struct in_addr)) { + name_state->a_state = RECORD_RESOLVED; + } else if (address_len == sizeof(struct in6_addr)) { + name_state->aaaa_state = RECORD_RESOLVED; + } + union kr_sockaddr tmp_address; + bytes_to_ip(address, address_len, 0, &tmp_address); + update_address_state(address_state, &tmp_address, address_len, qry); + } + } + trie_it_free(it); + kr_cache_commit(&qry->request->ctx->cache); +} + +static int get_valid_addresses(struct iter_local_state *local_state, + struct choice choices[]) +{ + unsigned count = 0; + trie_it_t *it; + for (it = trie_it_begin(local_state->addresses); !trie_it_finished(it); + trie_it_next(it)) { + size_t address_len; + uint8_t *address = (uint8_t *)trie_it_key(it, &address_len); + struct address_state *address_state = *trie_it_val(it); + if (address_state->generation == local_state->generation && + !address_state->broken) { + choices[count] = (struct choice){ + .address_len = address_len, + .address_state = address_state, + }; + bytes_to_ip(address, address_len, 0, &choices[count].address); + count++; + } + } + trie_it_free(it); + return count; +} + +static int get_resolvable_names(struct iter_local_state *local_state, + struct to_resolve resolvable[], struct kr_query *qry) +{ + /* Further resolution is not possible until we get `. DNSKEY` record; + * we have to choose one of the known addresses here. */ + if (qry->sname[0] == '\0' && qry->stype == KNOT_RRTYPE_DNSKEY) { + return 0; + } + + unsigned count = 0; + trie_it_t *it; + for (it = trie_it_begin(local_state->names); !trie_it_finished(it); + trie_it_next(it)) { + struct iter_name_state *name_state = *trie_it_val(it); + if (name_state->generation != local_state->generation) + continue; + + knot_dname_t *name = (knot_dname_t *)trie_it_key(it, NULL); + if (qry->stype == KNOT_RRTYPE_DNSKEY && + knot_dname_in_bailiwick(name, qry->sname) > 0) { + /* Resolving `domain. DNSKEY` can't trigger the + * resolution of `sub.domain. A/AAAA` since it + * will cause a cycle. */ + continue; + } + + /* FIXME: kr_rplan_satisfies(qry,…) should have been here, but this leads to failures on + * iter_ns_badip.rpl, this is because the test requires the resolver to switch to parent + * side after a record in cache expires. Only way to do this in the current zonecut setup is + * to requery the same query twice in the row. So we have to allow that and only check the + * rplan from parent upwards. + */ + bool a_in_rplan = kr_rplan_satisfies(qry->parent, name, + KNOT_CLASS_IN, KNOT_RRTYPE_A); + bool aaaa_in_rplan = kr_rplan_satisfies(qry->parent, name, + KNOT_CLASS_IN, KNOT_RRTYPE_AAAA); + + if (name_state->a_state == RECORD_UNKNOWN && + !qry->flags.NO_IPV4 && !a_in_rplan) { + resolvable[count++] = (struct to_resolve){ + name, KR_TRANSPORT_RESOLVE_A + }; + } + + if (name_state->aaaa_state == RECORD_UNKNOWN && + !qry->flags.NO_IPV6 && !aaaa_in_rplan) { + resolvable[count++] = (struct to_resolve){ + name, KR_TRANSPORT_RESOLVE_AAAA + }; + } + } + trie_it_free(it); + return count; +} + +static void update_name_state(knot_dname_t *name, enum kr_transport_protocol type, + trie_t *names) +{ + size_t name_len = knot_dname_size(name); + trie_val_t *val = trie_get_try(names, (char *)name, name_len); + + if (!val) { + return; + } + + struct iter_name_state *name_state = (struct iter_name_state *)*val; + switch (type) { + case KR_TRANSPORT_RESOLVE_A: + name_state->a_state = RECORD_TRIED; + break; + case KR_TRANSPORT_RESOLVE_AAAA: + name_state->aaaa_state = RECORD_TRIED; + break; + default: + kr_assert(false); + } +} + +void iter_choose_transport(struct kr_query *qry, struct kr_transport **transport) +{ + struct knot_mm *mempool = &qry->request->pool; + struct iter_local_state *local_state = + (struct iter_local_state *) + qry->server_selection.local_state->private; + + unpack_state_from_zonecut(local_state, qry); + + struct choice choices[trie_weight(local_state->addresses) + 1/*avoid 0*/]; + /* We may try to resolve A and AAAA record for each name, so therefore + * 2*trie_weight(…) is here. */ + struct to_resolve resolvable[2 * trie_weight(local_state->names)]; + + // Filter valid addresses and names from the tries + int choices_len = get_valid_addresses(local_state, choices); + int resolvable_len = get_resolvable_names(local_state, resolvable, qry); + bool * const force_resolve_p = &qry->server_selection.local_state->force_resolve; + + // Print some stats into debug logs. + if (kr_log_is_debug_qry(SELECTION, qry)) { + int v4_choices = 0; + for (int i = 0; i < choices_len; ++i) + if (choices[i].address.ip.sa_family == AF_INET) + ++v4_choices; + int v4_resolvable = 0; + for (int i = 0; i < resolvable_len; ++i) + if (resolvable[i].type == KR_TRANSPORT_RESOLVE_A) + ++v4_resolvable; + VERBOSE_MSG(qry, "=> id: '%05u' choosing from addresses: %d v4 + %d v6; " + "names to resolve: %d v4 + %d v6; " + "force_resolve: %d; NO6: IPv6 is %s\n", + qry->id, v4_choices, choices_len - v4_choices, + v4_resolvable, resolvable_len - v4_resolvable, + (int)*force_resolve_p, no6_is_bad() ? "KO" : "OK"); + } + + if (*force_resolve_p && resolvable_len) { + choices_len = 0; + *force_resolve_p = false; + } + + bool tcp = qry->flags.TCP || qry->server_selection.local_state->truncated; + *transport = select_transport(choices, choices_len, resolvable, resolvable_len, + qry->server_selection.local_state->timeouts, + mempool, tcp, NULL); + bool nxnsattack_mitigation = false; + + if (*transport) { + switch ((*transport)->protocol) { + case KR_TRANSPORT_RESOLVE_A: + case KR_TRANSPORT_RESOLVE_AAAA: + if (++local_state->no_ns_addr_count > KR_COUNT_NO_NSADDR_LIMIT) { + *transport = NULL; + nxnsattack_mitigation = true; + break; + } + /* Note that we tried resolving this name to not try it again. */ + update_name_state((*transport)->ns_name, (*transport)->protocol, local_state->names); + break; + case KR_TRANSPORT_TLS: + case KR_TRANSPORT_TCP: + /* We need to propagate this to flags since it's used in + * other parts of the resolver. */ + qry->flags.TCP = true; + case KR_TRANSPORT_UDP: /* fall through */ + local_state->no_ns_addr_count = 0; + break; + default: + kr_assert(false); + break; + } + + if (*transport && + (*transport)->protocol == KR_TRANSPORT_TCP && + !qry->server_selection.local_state->truncated && + qry->server_selection.local_state->force_udp) { + // Last chance on broken TCP. + (*transport)->protocol = KR_TRANSPORT_UDP; + qry->flags.TCP = false; + } + } + + if (*transport == NULL && local_state->last_error == KR_SELECTION_DNSSEC_ERROR) { + /* Last selected server had broken DNSSEC and now we have no more + * servers to ask. We signal this to the rest of resolver by + * setting DNSSEC_BOGUS flag. */ + qry->flags.DNSSEC_BOGUS = true; + } + + if (kr_log_is_debug_qry(SELECTION, qry)) + { + KR_DNAME_GET_STR(zonecut_str, qry->zone_cut.name); + if (*transport) { + KR_DNAME_GET_STR(ns_name, (*transport)->ns_name); + const enum kr_transport_protocol proto = *transport ? (*transport)->protocol : -1; + const char *ns_str = kr_straddr(&(*transport)->address.ip); + const char *ip_version; + switch (proto) + { + case KR_TRANSPORT_RESOLVE_A: + case KR_TRANSPORT_RESOLVE_AAAA: + ip_version = (proto == KR_TRANSPORT_RESOLVE_A) ? "A" : "AAAA"; + VERBOSE_MSG(qry, "=> id: '%05u' choosing to resolve %s: '%s' zone cut: '%s'\n", + qry->id, ip_version, ns_name, zonecut_str); + break; + default: + VERBOSE_MSG(qry, "=> id: '%05u' choosing: '%s'@'%s'" + " with timeout %u ms zone cut: '%s'\n", + qry->id, ns_name, ns_str ? ns_str : "", + (*transport)->timeout, zonecut_str); + break; + } + } else { + const char *nxns_msg = nxnsattack_mitigation + ? " (stopped due to mitigation for NXNSAttack CVE-2020-12667)" : ""; + VERBOSE_MSG(qry, "=> id: '%05u' no suitable transport, zone cut: '%s'%s\n", + qry->id, zonecut_str, nxns_msg ); + } + } +} + +void iter_error(struct kr_query *qry, const struct kr_transport *transport, + enum kr_selection_error sel_error) +{ + if (!qry->server_selection.initialized) { + return; + } + struct iter_local_state *local_state = qry->server_selection.local_state->private; + struct address_state *addr_state = get_address_state(local_state, transport); + local_state->last_error = sel_error; + error(qry, addr_state, transport, sel_error); +} + +void iter_update_rtt(struct kr_query *qry, const struct kr_transport *transport, + unsigned rtt) +{ + if (!qry->server_selection.initialized) { + return; + } + struct iter_local_state *local_state = qry->server_selection.local_state->private; + struct address_state *addr_state = get_address_state(local_state, transport); + update_rtt(qry, addr_state, transport, rtt); +} diff --git a/lib/selection_iter.h b/lib/selection_iter.h new file mode 100644 index 0000000..692463c --- /dev/null +++ b/lib/selection_iter.h @@ -0,0 +1,14 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "lib/selection.h" + +void iter_local_state_alloc(struct knot_mm *mm, void **local_state); +void iter_choose_transport(struct kr_query *qry, struct kr_transport **transport); +void iter_error(struct kr_query *qry, const struct kr_transport *transport, + enum kr_selection_error sel_error); +void iter_update_rtt(struct kr_query *qry, const struct kr_transport *transport, + unsigned rtt); diff --git a/lib/test_module.c b/lib/test_module.c new file mode 100644 index 0000000..d7124c1 --- /dev/null +++ b/lib/test_module.c @@ -0,0 +1,39 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "tests/unit/test.h" +#include "lib/module.h" + +static void test_module_params(void **state) +{ + struct kr_module module = { 0 }; + assert_int_equal(kr_module_load(NULL, NULL, NULL), kr_error(EINVAL)); + assert_int_equal(kr_module_load(&module, NULL, NULL), kr_error(EINVAL)); + kr_module_unload(NULL); +} + +static void test_module_builtin(void **state) +{ + struct kr_module module = { 0 }; + assert_int_equal(kr_module_load(&module, "iterate", NULL), 0); + kr_module_unload(&module); +} + +static void test_module_c(void **state) +{ + struct kr_module module = { 0 }; + assert_int_equal(kr_module_load(&module, "mock_cmodule", "tests/unit"), 0); + kr_module_unload(&module); +} + +int main(void) +{ + const UnitTest tests[] = { + unit_test(test_module_params), + unit_test(test_module_builtin), + unit_test(test_module_c), + }; + + return run_tests(tests); +} diff --git a/lib/test_rplan.c b/lib/test_rplan.c new file mode 100644 index 0000000..12f4cc4 --- /dev/null +++ b/lib/test_rplan.c @@ -0,0 +1,75 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "tests/unit/test.h" +#include "lib/resolve.h" +#include "lib/rplan.h" + +static void test_rplan_params(void **state) +{ + /* NULL rplan */ + + assert_int_equal(kr_rplan_init(NULL, NULL, NULL), KNOT_EINVAL); + assert_null((void *)kr_rplan_push(NULL, NULL, NULL, 0, 0)); + assert_int_equal(kr_rplan_pop(NULL, NULL), KNOT_EINVAL); + assert_true(kr_rplan_empty(NULL) == true); + kr_rplan_deinit(NULL); + + /* NULL mandatory parameters */ + + struct kr_rplan rplan; + assert_int_equal(kr_rplan_init(&rplan, NULL, NULL), KNOT_EOK); + assert_null((void *)kr_rplan_push(&rplan, NULL, NULL, 0, 0)); + assert_int_equal(kr_rplan_pop(&rplan, NULL), KNOT_EINVAL); + assert_true(kr_rplan_empty(&rplan) == true); + kr_rplan_deinit(&rplan); +} + +static void test_rplan_push(void **state) +{ + knot_mm_t mm = { 0 }; + test_mm_ctx_init(&mm); + struct kr_request request = { + .pool = mm, + .options = { 0 }, + }; + + struct kr_rplan rplan; + kr_rplan_init(&rplan, &request, &mm); + + /* Push query. */ + assert_non_null((void *)kr_rplan_push(&rplan, NULL, (knot_dname_t *)"", 0, 0)); + + kr_rplan_deinit(&rplan); +} + +/** + * Set and clear must not omit any bit, especially around byte boundaries. + */ +static void test_rplan_flags(void **state) +{ + static struct kr_qflags f1, f2, ones, zeros; /* static => initialized to zeroes */ + assert_true(memcmp(&f1, &f2, sizeof(f1)) == 0); /* sanity check */ + memset(&ones, 0xff, sizeof(ones)); /* all ones */ + + /* test set */ + kr_qflags_set(&f1, ones); + assert_true(memcmp(&f1, &ones, sizeof(f1)) == 0); /* 1 == 1 */ + + /* test clear */ + memset(&f2, 0xff, sizeof(f2)); /* all ones */ + kr_qflags_clear(&f2, ones); + assert_true(memcmp(&f2, &zeros, sizeof(f1)) == 0); /* 0 == 0 */ +} + +int main(void) +{ + const UnitTest tests[] = { + unit_test(test_rplan_params), + unit_test(test_rplan_push), + unit_test(test_rplan_flags) + }; + + return run_tests(tests); +} diff --git a/lib/test_utils.c b/lib/test_utils.c new file mode 100644 index 0000000..22f2483 --- /dev/null +++ b/lib/test_utils.c @@ -0,0 +1,147 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <sys/socket.h> +#include <stdio.h> +#include <contrib/cleanup.h> + +#include "tests/unit/test.h" +#include "lib/utils.h" + +static void test_strcatdup(void **state) +{ + auto_free char *empty_res = kr_strcatdup(0); + assert_null(empty_res); + + auto_free char *null_res = kr_strcatdup(1, NULL); + assert_null(null_res); + + auto_free char *nullcat_res = kr_strcatdup(2, NULL, "beef"); + assert_string_equal(nullcat_res, "beef"); + + auto_free char *multi_res = kr_strcatdup(3, "need", "beef", "dead"); + assert_string_equal(multi_res, "needbeefdead"); + + /* Test fails if this leaks. */ + auto_fclose FILE* null_file = fopen("/dev/null", "r"); + (void)(null_file); + + /* Test fails if this leaks. */ + auto_close int null_sock = socket(AF_INET, SOCK_DGRAM, 0); + (void)(null_sock); +} + +static inline int test_bitcmp(const char *subnet, const char *str_addr, size_t len) +{ + char addr_buf[16] = {'\0'}; + kr_straddr_subnet(addr_buf, str_addr); + return kr_bitcmp(subnet, addr_buf, len); +} + +static void test_straddr(void **state) +{ + const char *ip4_ok = "1.2.3.0/30"; + const char *ip4_bad = "1.2.3.0/33"; + const char *ip4_in = "1.2.3.1"; + const char *ip4_out = "1.2.3.5"; + const char *ip6_ok = "7caa::/4"; + const char *ip6_bad = "7caa::/129"; + const char *ip6_in = "7caa::aa7c"; + const char *ip6_out = "8caa::aa7c"; + /* Parsing family */ + assert_int_equal(kr_straddr_family(ip4_ok), AF_INET); + assert_int_equal(kr_straddr_family(ip4_in), AF_INET); + assert_int_equal(kr_straddr_family(ip6_ok), AF_INET6); + assert_int_equal(kr_straddr_family(ip6_in), AF_INET6); + /* Parsing subnet */ + char ip4_sub[4], ip6_sub[16]; + assert_true(kr_straddr_subnet(ip4_sub, ip4_bad) < 0); + assert_int_equal(kr_straddr_subnet(ip4_sub, ip4_ok), 30); + assert_true(kr_straddr_subnet(ip6_sub, ip6_bad) < 0); + assert_int_equal(kr_straddr_subnet(ip6_sub, ip6_ok), 4); + /* Matching subnet */ + assert_int_equal(test_bitcmp(ip4_sub, ip4_in, 30), 0); + assert_int_not_equal(test_bitcmp(ip4_sub, ip4_out, 30), 0); + assert_int_equal(test_bitcmp(ip6_sub, ip6_in, 4), 0); + assert_int_not_equal(test_bitcmp(ip6_sub, ip6_out, 4), 0); +} + +static inline int assert_bitmask(const char *addr, const char *exp_masked) +{ + unsigned char addr_buf[16]; + unsigned char exp_masked_buf[16]; + + int bits = kr_straddr_subnet(addr_buf, addr); + size_t addr_len = (kr_straddr_family(addr) == AF_INET6) ? 16 : 4; + int exp_masked_bits = kr_straddr_subnet(exp_masked_buf, exp_masked); + size_t exp_masked_len = (kr_straddr_family(exp_masked) == AF_INET6) ? 16 : 4; + + /* sanity checks */ + assert_true(bits >= 0); + assert_int_equal(addr_len, exp_masked_len); + assert_int_equal(exp_masked_bits, exp_masked_len * 8); + + kr_bitmask(addr_buf, addr_len, bits); + return memcmp(addr_buf, exp_masked_buf, addr_len); +} + +static void test_bitmask(void **state) +{ + assert_int_equal(assert_bitmask("10.0.1.5/32", "10.0.1.5"), 0); + assert_int_equal(assert_bitmask("10.0.1.5", "10.0.1.5"), 0); + assert_int_equal(assert_bitmask("10.0.1.5/24", "10.0.1.0"), 0); + assert_int_equal(assert_bitmask("128.30.1.16/16", "128.30.0.0"), 0); + assert_int_equal(assert_bitmask("255.255.255.255/20", "255.255.240.0"), 0); + assert_int_equal(assert_bitmask("255.255.255.255/22", "255.255.252.0"), 0); + assert_int_equal(assert_bitmask("192.168.0.1/0", "0.0.0.0"), 0); + assert_int_equal(assert_bitmask("7caa::/4", "7000::"), 0); + assert_int_equal(assert_bitmask("dead:beef::/16", "dead::"), 0); + assert_int_equal(assert_bitmask("dead:beef::/20", "dead:b000::"), 0); + assert_int_equal(assert_bitmask("dead:beef::/0", "::"), 0); + assert_int_equal(assert_bitmask("64aa:22fa:1378:aaaa:bbbb::/36", "64aa:22fa:1000::"), 0); +} + +static void test_strptime_diff(void **state) +{ + char *format = "%Y-%m-%dT%H:%M:%S"; + const char *errmsg = NULL; + double output; + + errmsg = kr_strptime_diff(format, + "2019-01-09T12:06:04", + "2019-01-09T12:06:04", &output); + assert_true(errmsg == NULL); + /* double type -> equality is not reliable */ + assert_true(output > -0.01 && output < 0.01); + + errmsg = kr_strptime_diff(format, + "2019-01-09T12:06:04", + "2019-01-09T11:06:04", &output); + assert_true(errmsg == NULL); + /* double type -> equality is not reliable */ + assert_true(output > -3600.01 && output < 3600.01); + + /* invalid inputs */ + errmsg = kr_strptime_diff(format, + "2019-01-09T25:06:04", + "2019-01-09T11:06:04", &output); + assert_true(errmsg != NULL); + + errmsg = kr_strptime_diff("fail", + "2019-01-09T23:06:04", + "2019-01-09T11:06:04", &output); + assert_true(errmsg != NULL); +} + +int main(void) +{ + const UnitTest tests[] = { + unit_test(test_strcatdup), + unit_test(test_straddr), + unit_test(test_bitmask), + unit_test(test_strptime_diff) + }; + + return run_tests(tests); +} diff --git a/lib/test_zonecut.c b/lib/test_zonecut.c new file mode 100644 index 0000000..c039963 --- /dev/null +++ b/lib/test_zonecut.c @@ -0,0 +1,58 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <netinet/in.h> + +#include "tests/unit/test.h" +#include "lib/zonecut.h" + +static void test_zonecut_params(void **state) +{ + /* NULL args */ + struct kr_zonecut cut; + assert_int_not_equal(kr_zonecut_init(NULL, NULL, NULL), 0); + assert_int_not_equal(kr_zonecut_init(&cut, NULL, NULL), 0); + kr_zonecut_deinit(NULL); + kr_zonecut_set(NULL, NULL); + kr_zonecut_set(&cut, NULL); + /* TODO triggering inner assertion: + assert_int_not_equal(kr_zonecut_add(NULL, NULL, NULL, 0), 0); + */ + assert_null((void *)kr_zonecut_find(NULL, NULL)); + assert_null((void *)kr_zonecut_find(&cut, NULL)); + assert_int_not_equal(kr_zonecut_set_sbelt(NULL, NULL), 0); + assert_int_not_equal(kr_zonecut_find_cached(NULL, NULL, NULL, 0, 0), 0); +} + +static void test_zonecut_copy(void **state) +{ + const knot_dname_t *n_root = (const uint8_t *)""; + struct kr_zonecut cut1, cut2; + kr_zonecut_init(&cut1, n_root, NULL); + kr_zonecut_init(&cut2, n_root, NULL); + /* Insert some values */ + const knot_dname_t + *n_1 = (const uint8_t *)"\4dead", + *n_2 = (const uint8_t *)"\3bee\1f"; + assert_int_equal(kr_zonecut_add(&cut1, n_1, NULL, 0), 0); + assert_int_equal(kr_zonecut_add(&cut1, n_2, NULL, 0), 0); + /* Copy */ + assert_int_equal(kr_zonecut_copy(&cut2, &cut1), 0); + /* Check if exist */ + assert_non_null(kr_zonecut_find(&cut2, n_1)); + assert_non_null(kr_zonecut_find(&cut2, n_2)); + assert_null(kr_zonecut_find(&cut2, (const uint8_t *)"\5death")); + kr_zonecut_deinit(&cut1); + kr_zonecut_deinit(&cut2); +} + +int main(void) +{ + const UnitTest tests[] = { + unit_test(test_zonecut_params), + unit_test(test_zonecut_copy) + }; + + return run_tests(tests); +} diff --git a/lib/utils.c b/lib/utils.c new file mode 100644 index 0000000..c1b25db --- /dev/null +++ b/lib/utils.c @@ -0,0 +1,1393 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "lib/utils.h" + +#include "contrib/cleanup.h" +#include "contrib/ucw/mempool.h" +#include "kresconfig.h" +#include "lib/defines.h" +#include "lib/generic/array.h" +#include "lib/module.h" +#include "lib/resolve.h" + +#include <libknot/descriptor.h> +#include <libknot/dname.h> +#include <libknot/rrset-dump.h> +#include <libknot/rrtype/rrsig.h> +#include <libknot/version.h> +#include <uv.h> + +#include <arpa/inet.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/statvfs.h> +#include <sys/un.h> + +struct __attribute__((packed)) kr_sockaddr_key { + int family; +}; + +struct __attribute__((packed)) kr_sockaddr_in_key { + int family; + char address[sizeof(((struct sockaddr_in *) NULL)->sin_addr)]; + uint16_t port; +}; + +struct __attribute__((packed)) kr_sockaddr_in6_key { + int family; + char address[sizeof(((struct sockaddr_in6 *) NULL)->sin6_addr)]; + uint32_t scope; + uint16_t port; +}; + +struct __attribute((packed)) kr_sockaddr_un_key { + int family; + char path[sizeof(((struct sockaddr_un *) NULL)->sun_path)]; +}; + +extern inline uint64_t kr_rand_bytes(unsigned int size); + +/* Logging & debugging */ +bool kr_dbg_assertion_abort = DBG_ASSERTION_ABORT; +int kr_dbg_assertion_fork = DBG_ASSERTION_FORK; + +void kr_fail(bool is_fatal, const char *expr, const char *func, const char *file, int line) +{ + const int errno_orig = errno; + if (is_fatal) + kr_log_crit(SYSTEM, "requirement \"%s\" failed in %s@%s:%d\n", expr, func, file, line); + else + kr_log_error(SYSTEM, "assertion \"%s\" failed in %s@%s:%d\n", expr, func, file, line); + + if (is_fatal || (kr_dbg_assertion_abort && !kr_dbg_assertion_fork)) + abort(); + else if (!kr_dbg_assertion_abort || !kr_dbg_assertion_fork) + goto recover; + // We want to fork and abort the child, unless rate-limited. + static uint64_t limited_until = 0; + const uint64_t now = kr_now(); + if (now < limited_until) + goto recover; + if (kr_dbg_assertion_fork > 0) { + // Add jitter +- 25%; in other words: 75% + uniform(0,50%). + // Motivation: if a persistent problem starts happening, desynchronize + // coredumps from different instances as they're not cheap. + limited_until = now + kr_dbg_assertion_fork * 3 / 4 + + kr_dbg_assertion_fork * kr_rand_bytes(1) / 256 / 2; + } + if (fork() == 0) + abort(); +recover: + errno = errno_orig; +} + +/* + * Macros. + */ +#define strlen_safe(x) ((x) ? strlen(x) : 0) + +/** + * @internal Convert 16bit unsigned to string, keeps leading spaces. + * @note Always fills dst length = 5 + * Credit: http://computer-programming-forum.com/46-asm/7aa4b50bce8dd985.htm + */ +static inline int u16tostr(uint8_t *dst, uint16_t num) +{ + uint32_t tmp = num * (((1 << 28) / 10000) + 1) - (num / 4); + for(size_t i = 0; i < 5; i++) { + dst[i] = '0' + (char) (tmp >> 28); + tmp = (tmp & 0x0fffffff) * 10; + } + return 5; +} + +char* kr_strcatdup(unsigned n, ...) +{ + if (n < 1) { + return NULL; + } + + /* Calculate total length */ + size_t total_len = 0; + va_list vl; + va_start(vl, n); + for (unsigned i = 0; i < n; ++i) { + char *item = va_arg(vl, char *); + const size_t new_len = total_len + strlen_safe(item); + if (unlikely(new_len < total_len)) { + va_end(vl); + return NULL; + } + total_len = new_len; + } + va_end(vl); + + /* Allocate result and fill */ + char *result = NULL; + if (total_len > 0) { + if (unlikely(total_len == SIZE_MAX)) return NULL; + result = malloc(total_len + 1); + } + if (result) { + char *stream = result; + va_start(vl, n); + for (unsigned i = 0; i < n; ++i) { + char *item = va_arg(vl, char *); + if (item) { + size_t len = strlen(item); + memcpy(stream, item, len + 1); + stream += len; + } + } + va_end(vl); + } + + return result; +} + +char * kr_absolutize_path(const char *dirname, const char *fname) +{ + if (kr_fails_assert(dirname && fname)) { + errno = EINVAL; + return NULL; + } + char *result; + int aret; + if (dirname[0] == '/') { // absolute path is easier + aret = asprintf(&result, "%s/%s", dirname, fname); + } else { // relative path, but don't resolve symlinks + char buf[PATH_MAX]; + const char *cwd = getcwd(buf, sizeof(buf)); + if (!cwd) + return NULL; // errno has been set already + if (strcmp(dirname, ".") == 0) { + // get rid of one common case of extraneous "./" + aret = asprintf(&result, "%s/%s", cwd, fname); + } else { + aret = asprintf(&result, "%s/%s/%s", cwd, dirname, fname); + } + } + if (aret > 0) + return result; + errno = -aret; + return NULL; +} + +int kr_memreserve(void *baton, void **mem, size_t elm_size, size_t want, size_t *have) +{ + if (*have >= want) { + return 0; + } else { + knot_mm_t *pool = baton; + size_t next_size = array_next_count(elm_size, want, *have); + void *mem_new = mm_alloc(pool, next_size * elm_size); + if (mem_new != NULL) { + if (*mem) { /* 0-length memcpy from NULL isn't technically OK */ + memcpy(mem_new, *mem, (*have)*(elm_size)); + mm_free(pool, *mem); + } + *mem = mem_new; + *have = next_size; + return 0; + } + } + return -1; +} + +static int pkt_recycle(knot_pkt_t *pkt, bool keep_question) +{ + /* The maximum size of a header + query name + (class, type) */ + uint8_t buf[KNOT_WIRE_HEADER_SIZE + KNOT_DNAME_MAXLEN + 2 * sizeof(uint16_t)]; + + /* Save header and the question section */ + size_t base_size = KNOT_WIRE_HEADER_SIZE; + if (keep_question) { + base_size += knot_pkt_question_size(pkt); + } + if (kr_fails_assert(base_size <= sizeof(buf))) return kr_error(EINVAL); + memcpy(buf, pkt->wire, base_size); + + /* Clear the packet and its auxiliary structures */ + knot_pkt_clear(pkt); + + /* Restore header and question section and clear counters */ + pkt->size = base_size; + memcpy(pkt->wire, buf, base_size); + knot_wire_set_qdcount(pkt->wire, keep_question); + knot_wire_set_ancount(pkt->wire, 0); + knot_wire_set_nscount(pkt->wire, 0); + knot_wire_set_arcount(pkt->wire, 0); + + /* Reparse question */ + knot_pkt_begin(pkt, KNOT_ANSWER); + return knot_pkt_parse_question(pkt); +} + +int kr_pkt_recycle(knot_pkt_t *pkt) +{ + return pkt_recycle(pkt, false); +} + +int kr_pkt_clear_payload(knot_pkt_t *pkt) +{ + return pkt_recycle(pkt, knot_wire_get_qdcount(pkt->wire)); +} + +int kr_pkt_put(knot_pkt_t *pkt, const knot_dname_t *name, uint32_t ttl, + uint16_t rclass, uint16_t rtype, const uint8_t *rdata, uint16_t rdlen) +{ + /* LATER(opt.): there's relatively lots of copying, but ATM kr_pkt_put() + * isn't considered to be used in any performance-critical parts (just lua). */ + if (!pkt || !name) { + return kr_error(EINVAL); + } + /* Create empty RR */ + knot_rrset_t rr; + knot_rrset_init(&rr, knot_dname_copy(name, &pkt->mm), rtype, rclass, ttl); + /* Create RDATA */ + knot_rdata_t *rdata_tmp = mm_alloc(&pkt->mm, offsetof(knot_rdata_t, data) + rdlen); + knot_rdata_init(rdata_tmp, rdlen, rdata); + knot_rdataset_add(&rr.rrs, rdata_tmp, &pkt->mm); + mm_free(&pkt->mm, rdata_tmp); /* we're always on mempool for now, but whatever */ + /* Append RR */ + return knot_pkt_put(pkt, 0, &rr, KNOT_PF_FREE); +} + +void kr_pkt_make_auth_header(knot_pkt_t *pkt) +{ + if (kr_fails_assert(pkt && pkt->wire)) return; + knot_wire_clear_ad(pkt->wire); + knot_wire_set_aa(pkt->wire); +} + +const char *kr_inaddr(const struct sockaddr *addr) +{ + if (!addr) { + return NULL; + } + switch (addr->sa_family) { + case AF_INET: return (const char *)&(((const struct sockaddr_in *)addr)->sin_addr); + case AF_INET6: return (const char *)&(((const struct sockaddr_in6 *)addr)->sin6_addr); + default: return NULL; + } +} + +int kr_inaddr_family(const struct sockaddr *addr) +{ + if (!addr) + return AF_UNSPEC; + return addr->sa_family; +} + +int kr_inaddr_len(const struct sockaddr *addr) +{ + if (!addr) { + return kr_error(EINVAL); + } + return kr_family_len(addr->sa_family); +} + +int kr_sockaddr_len(const struct sockaddr *addr) +{ + if (!addr) { + return kr_error(EINVAL); + } + switch (addr->sa_family) { + case AF_INET: return sizeof(struct sockaddr_in); + case AF_INET6: return sizeof(struct sockaddr_in6); + case AF_UNIX: return sizeof(struct sockaddr_un); + default: return kr_error(EINVAL); + } +} + +ssize_t kr_sockaddr_key(struct kr_sockaddr_key_storage *dst, + const struct sockaddr *addr) +{ + kr_require(addr); + + switch (addr->sa_family) { + case AF_INET:; + const struct sockaddr_in *addr_in = (const struct sockaddr_in *) addr; + struct kr_sockaddr_in_key *inkey = (struct kr_sockaddr_in_key *) dst; + inkey->family = AF_INET; + memcpy(&inkey->address, &addr_in->sin_addr, sizeof(inkey->address)); + memcpy(&inkey->port, &addr_in->sin_port, sizeof(inkey->port)); + return sizeof(*inkey); + + case AF_INET6:; + const struct sockaddr_in6 *addr_in6 = (const struct sockaddr_in6 *) addr; + struct kr_sockaddr_in6_key *in6key = (struct kr_sockaddr_in6_key *) dst; + in6key->family = AF_INET6; + memcpy(&in6key->address, &addr_in6->sin6_addr, sizeof(in6key->address)); + memcpy(&in6key->port, &addr_in6->sin6_port, sizeof(in6key->port)); + if (kr_sockaddr_link_local(addr)) + memcpy(&in6key->scope, &addr_in6->sin6_scope_id, sizeof(in6key->scope)); + else + in6key->scope = 0; + return sizeof(*in6key); + + case AF_UNIX:; + const struct sockaddr_un *addr_un = (const struct sockaddr_un *) addr; + struct kr_sockaddr_un_key *unkey = (struct kr_sockaddr_un_key *) dst; + unkey->family = AF_UNIX; + size_t pathlen = strnlen(addr_un->sun_path, sizeof(unkey->path)); + if (pathlen == 0 || pathlen >= sizeof(unkey->path)) { + /* Abstract sockets are not supported - we would need + * to also supply a length value for the abstract + * pathname. + * + * UNIX socket path should be null-terminated. + * + * See unix(7). */ + return kr_error(EINVAL); + } + + pathlen += 1; /* Include null-terminator */ + strncpy(unkey->path, addr_un->sun_path, pathlen); + return offsetof(struct kr_sockaddr_un_key, path) + pathlen; + + default: + return kr_error(EAFNOSUPPORT); + } +} + +struct sockaddr *kr_sockaddr_from_key(struct sockaddr_storage *dst, + const char *key) +{ + kr_require(key); + + switch (((struct kr_sockaddr_key *) key)->family) { + case AF_INET:; + const struct kr_sockaddr_in_key *inkey = (struct kr_sockaddr_in_key *) key; + struct sockaddr_in *addr_in = (struct sockaddr_in *) dst; + addr_in->sin_family = AF_INET; + memcpy(&addr_in->sin_addr, &inkey->address, sizeof(inkey->address)); + memcpy(&addr_in->sin_port, &inkey->port, sizeof(inkey->port)); + return (struct sockaddr *) addr_in; + + case AF_INET6:; + const struct kr_sockaddr_in6_key *in6key = (struct kr_sockaddr_in6_key *) key; + struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *) dst; + addr_in6->sin6_family = AF_INET6; + memcpy(&addr_in6->sin6_addr, &in6key->address, sizeof(in6key->address)); + memcpy(&addr_in6->sin6_port, &in6key->port, sizeof(in6key->port)); + memcpy(&addr_in6->sin6_scope_id, &in6key->scope, sizeof(in6key->scope)); + return (struct sockaddr *) addr_in6; + + case AF_UNIX:; + const struct kr_sockaddr_un_key *unkey = (struct kr_sockaddr_un_key *) key; + struct sockaddr_un *addr_un = (struct sockaddr_un *) dst; + addr_un->sun_family = AF_UNIX; + strncpy(addr_un->sun_path, unkey->path, sizeof(unkey->path)); + return (struct sockaddr *) addr_un; + + default: + kr_assert(false); + return NULL; + } +} + +bool kr_sockaddr_key_same_addr(const char *key_a, const char *key_b) +{ + const struct kr_sockaddr_in6_key *kkey_a = (struct kr_sockaddr_in6_key *) key_a; + const struct kr_sockaddr_in6_key *kkey_b = (struct kr_sockaddr_in6_key *) key_b; + + if (kkey_a->family != kkey_b->family) + return false; + + ptrdiff_t offset; + switch (kkey_a->family) { + case AF_INET: + offset = offsetof(struct kr_sockaddr_in_key, address); + break; + case AF_INET6: + if (unlikely(kkey_a->scope != kkey_b->scope)) + return false; + offset = offsetof(struct kr_sockaddr_in6_key, address); + break; + + case AF_UNIX:; + const struct kr_sockaddr_un_key *unkey_a = + (struct kr_sockaddr_un_key *) key_a; + const struct kr_sockaddr_un_key *unkey_b = + (struct kr_sockaddr_un_key *) key_b; + + return strncmp(unkey_a->path, unkey_b->path, + sizeof(unkey_a->path)) == 0; + + default: + kr_assert(false); + return false; + } + + size_t len = kr_family_len(kkey_a->family); + return memcmp(key_a + offset, key_b + offset, len) == 0; +} + +int kr_sockaddr_cmp(const struct sockaddr *left, const struct sockaddr *right) +{ + if (!left || !right) { + return kr_error(EINVAL); + } + if (left->sa_family != right->sa_family) { + return kr_error(EFAULT); + } + if (left->sa_family == AF_INET) { + struct sockaddr_in *left_in = (struct sockaddr_in *)left; + struct sockaddr_in *right_in = (struct sockaddr_in *)right; + if (left_in->sin_addr.s_addr != right_in->sin_addr.s_addr) { + return kr_error(EFAULT); + } + if (left_in->sin_port != right_in->sin_port) { + return kr_error(EFAULT); + } + } else if (left->sa_family == AF_INET6) { + struct sockaddr_in6 *left_in6 = (struct sockaddr_in6 *)left; + struct sockaddr_in6 *right_in6 = (struct sockaddr_in6 *)right; + if (memcmp(&left_in6->sin6_addr, &right_in6->sin6_addr, + sizeof(struct in6_addr)) != 0) { + return kr_error(EFAULT); + } + if (left_in6->sin6_port != right_in6->sin6_port) { + return kr_error(EFAULT); + } + } else { + return kr_error(ENOENT); + } + return kr_ok(); +} + +uint16_t kr_inaddr_port(const struct sockaddr *addr) +{ + if (!addr) { + return 0; + } + switch (addr->sa_family) { + case AF_INET: return ntohs(((const struct sockaddr_in *)addr)->sin_port); + case AF_INET6: return ntohs(((const struct sockaddr_in6 *)addr)->sin6_port); + default: return 0; + } +} + +void kr_inaddr_set_port(struct sockaddr *addr, uint16_t port) +{ + if (!addr) { + return; + } + switch (addr->sa_family) { + case AF_INET: + ((struct sockaddr_in *)addr)->sin_port = htons(port); + break; + case AF_INET6: + ((struct sockaddr_in6 *)addr)->sin6_port = htons(port); + break; + default: + break; + } +} + +int kr_inaddr_str(const struct sockaddr *addr, char *buf, size_t *buflen) +{ + if (!addr) { + return kr_error(EINVAL); + } + return kr_ntop_str(addr->sa_family, kr_inaddr(addr), kr_inaddr_port(addr), + buf, buflen); +} + +int kr_ntop_str(int family, const void *src, uint16_t port, char *buf, size_t *buflen) +{ + if (!src || !buf || !buflen) { + return kr_error(EINVAL); + } + + if (!inet_ntop(family, src, buf, *buflen)) { + return kr_error(errno); + } + const int len = strlen(buf); + const int len_need = len + 1 + 5 + 1; + if (len_need > *buflen) { + *buflen = len_need; + return kr_error(ENOSPC); + } + *buflen = len_need; + buf[len] = '#'; + u16tostr((uint8_t *)&buf[len + 1], port); + buf[len_need - 1] = 0; + return kr_ok(); +} + +char *kr_straddr(const struct sockaddr *addr) +{ + if (kr_fails_assert(addr)) return NULL; + static char str[KR_STRADDR_MAXLEN + 1] = {0}; + if (addr->sa_family == AF_UNIX) { + strncpy(str, ((struct sockaddr_un *)addr)->sun_path, sizeof(str) - 1); + return str; + } + size_t len = KR_STRADDR_MAXLEN; + int ret = kr_inaddr_str(addr, str, &len); + return ret != kr_ok() || len == 0 ? NULL : str; +} + +int kr_straddr_family(const char *addr) +{ + if (!addr) { + return kr_error(EINVAL); + } + if (addr[0] == '/') { + return AF_UNIX; + } + if (strchr(addr, ':')) { + return AF_INET6; + } + if (strchr(addr, '.')) { + return AF_INET; + } + return kr_error(EINVAL); +} + +int kr_family_len(int family) +{ + switch (family) { + case AF_INET: return sizeof(struct in_addr); + case AF_INET6: return sizeof(struct in6_addr); + default: return kr_error(EINVAL); + } +} + +struct sockaddr * kr_straddr_socket(const char *addr, int port, knot_mm_t *pool) +{ + switch (kr_straddr_family(addr)) { + case AF_INET: { + struct sockaddr_in *res = mm_alloc(pool, sizeof(*res)); + if (uv_ip4_addr(addr, port, res) >= 0) { + return (struct sockaddr *)res; + } else { + mm_free(pool, res); + return NULL; + } + } + case AF_INET6: { + struct sockaddr_in6 *res = mm_alloc(pool, sizeof(*res)); + if (uv_ip6_addr(addr, port, res) >= 0) { + return (struct sockaddr *)res; + } else { + mm_free(pool, res); + return NULL; + } + } + case AF_UNIX: { + struct sockaddr_un *res; + const size_t alen = strlen(addr) + 1; + if (alen > sizeof(res->sun_path)) { + return NULL; + } + res = mm_alloc(pool, sizeof(*res)); + res->sun_family = AF_UNIX; + memcpy(res->sun_path, addr, alen); + return (struct sockaddr *)res; + } + default: + return NULL; + } +} + +int kr_straddr_subnet(void *dst, const char *addr) +{ + if (!dst || !addr) { + return kr_error(EINVAL); + } + /* Parse subnet */ + int bit_len = 0; + int family = kr_straddr_family(addr); + if (family != AF_INET && family != AF_INET6) + return kr_error(EINVAL); + const int max_len = (family == AF_INET6) ? 128 : 32; + auto_free char *addr_str = strdup(addr); + char *subnet = strchr(addr_str, '/'); + if (subnet) { + *subnet = '\0'; + subnet += 1; + bit_len = strtol(subnet, NULL, 10); + /* Check client subnet length */ + if (bit_len < 0 || bit_len > max_len) { + return kr_error(ERANGE); + } + } else { + /* No subnet, use maximal subnet length. */ + bit_len = max_len; + } + /* Parse address */ + int ret = inet_pton(family, addr_str, dst); + if (ret != 1) { + return kr_error(EILSEQ); + } + + return bit_len; +} + +int kr_straddr_split(const char *instr, char ipaddr[static restrict (INET6_ADDRSTRLEN + 1)], + uint16_t *port) +{ + if (kr_fails_assert(instr && ipaddr && port)) return kr_error(EINVAL); + /* Find where port number starts. */ + const char *p_start = strchr(instr, '@'); + if (!p_start) + p_start = strchr(instr, '#'); + if (p_start) { /* Get and check the port number. */ + if (p_start[1] == '\0') /* Don't accept empty port string. */ + return kr_error(EILSEQ); + char *p_end; + long p = strtol(p_start + 1, &p_end, 10); + if (*p_end != '\0' || p <= 0 || p > UINT16_MAX) + return kr_error(EILSEQ); + *port = p; + } + /* Copy the address. */ + const size_t addrlen = p_start ? p_start - instr : strlen(instr); + if (addrlen > INET6_ADDRSTRLEN) + return kr_error(EILSEQ); + memcpy(ipaddr, instr, addrlen); + ipaddr[addrlen] = '\0'; + return kr_ok(); +} + +int kr_straddr_join(const char *addr, uint16_t port, char *buf, size_t *buflen) +{ + if (!addr || !buf || !buflen) { + return kr_error(EINVAL); + } + + struct sockaddr_storage ss; + int family = kr_straddr_family(addr); + if (family == kr_error(EINVAL) || inet_pton(family, addr, &ss) != 1) { + return kr_error(EINVAL); + } + + int len = strlen(addr); + if (len + 6 >= *buflen) { + return kr_error(ENOSPC); + } + + memcpy(buf, addr, len + 1); + buf[len] = '#'; + u16tostr((uint8_t *)&buf[len + 1], port); + len += 6; + buf[len] = 0; + *buflen = len; + + return kr_ok(); +} + +int kr_bitcmp(const char *a, const char *b, int bits) +{ + /* We're using the function from lua directly, so at least for now + * we avoid crashing on bogus inputs. Meaning: NULL is ordered before + * anything else, and negative length is the same as zero. + * TODO: review the call sites and probably remove the checks. */ + if (bits <= 0 || (!a && !b)) { + return 0; + } else if (!a) { + return -1; + } else if (!b) { + return 1; + } + + kr_require((a && b && bits >= 0) || bits == 0); + /* Compare part byte-divisible part. */ + const size_t chunk = bits / 8; + int ret = memcmp(a, b, chunk); + if (ret != 0) { + return ret; + } + a += chunk; + b += chunk; + bits -= chunk * 8; + /* Compare last partial byte address block. */ + if (bits > 0) { + const size_t shift = (8 - bits); + ret = ((uint8_t)(*a >> shift) - (uint8_t)(*b >> shift)); + } + return ret; +} + +void kr_bitmask(unsigned char *a, size_t a_len, int bits) +{ + if (bits < 0 || !a || !a_len) { + return; + } + + size_t i = bits / 8; + const size_t mid_bits = 8 - (bits % 8); + const unsigned char mask = 0xFF << mid_bits; + if (i < a_len) + a[i] &= mask; + + for (++i; i < a_len; ++i) + a[i] = 0; +} + +int kr_rrkey(char *key, uint16_t class, const knot_dname_t *owner, + uint16_t type, uint16_t additional) +{ + if (!key || !owner) { + return kr_error(EINVAL); + } + uint8_t *key_buf = (uint8_t *)key; + int ret = u16tostr(key_buf, class); + if (ret <= 0) { + return ret; + } + key_buf += ret; + ret = knot_dname_to_wire(key_buf, owner, KNOT_DNAME_MAXLEN); + if (ret <= 0) { + return ret; + } + knot_dname_to_lower(key_buf); + key_buf += ret - 1; + ret = u16tostr(key_buf, type); + if (ret <= 0) { + return ret; + } + key_buf += ret; + ret = u16tostr(key_buf, additional); + if (ret <= 0) { + return ret; + } + key_buf[ret] = '\0'; + return (char *)&key_buf[ret] - key; +} + +/** Return whether two RRsets match, i.e. would form the same set; see ranked_rr_array_t */ +static inline bool rrsets_match(const knot_rrset_t *rr1, const knot_rrset_t *rr2) +{ + bool match = rr1->type == rr2->type && rr1->rclass == rr2->rclass; + if (match && rr2->type == KNOT_RRTYPE_RRSIG) { + match = match && knot_rrsig_type_covered(rr1->rrs.rdata) + == knot_rrsig_type_covered(rr2->rrs.rdata); + } + match = match && knot_dname_is_equal(rr1->owner, rr2->owner); + return match; +} + +/** Ensure that an index in a ranked array won't cause "duplicate" RRsets on wire. + * + * Other entries that would form the same RRset get to_wire = false. + * See also rrsets_match. + */ +static int to_wire_ensure_unique(ranked_rr_array_t *array, size_t index) +{ + if (kr_fails_assert(array && index < array->len)) return kr_error(EINVAL); + + const struct ranked_rr_array_entry *e0 = array->at[index]; + if (!e0->to_wire) { + return kr_ok(); + } + + for (ssize_t i = array->len - 1; i >= 0; --i) { + /* ^ iterate backwards, as the end is more likely in CPU caches */ + struct ranked_rr_array_entry *ei = array->at[i]; + if (ei->qry_uid == e0->qry_uid /* assumption: no duplicates within qry */ + || !ei->to_wire /* no use for complex comparison if @to_wire */ + ) { + continue; + } + if (rrsets_match(ei->rr, e0->rr)) { + ei->to_wire = false; + } + } + return kr_ok(); +} + +/* Implementation overview of _add() and _finalize(): + * - for rdata we just maintain a list of pointers (in knot_rrset_t::additional) + * - we only construct the final rdataset at the end (and thus more efficiently) + */ +typedef array_t(knot_rdata_t *) rdata_array_t; +int kr_ranked_rrarray_add(ranked_rr_array_t *array, const knot_rrset_t *rr, + uint8_t rank, bool to_wire, uint32_t qry_uid, knot_mm_t *pool) +{ + /* From normal packet parser we always get RRs one by one, + * but cache and prefil modules (also) feed us larger RRsets. */ + kr_assert(rr->rrs.count >= 1); + /* Check if another rrset with the same + * rclass/type/owner combination exists within current query + * and merge if needed */ + for (ssize_t i = array->len - 1; i >= 0; --i) { + ranked_rr_array_entry_t *stashed = array->at[i]; + if (stashed->yielded) { + break; + } + if (stashed->qry_uid != qry_uid) { + break; + /* We do not guarantee merging RRs "across" any point that switched + * to processing a different upstream packet (i.e. qry_uid). + * In particular, iterator never returns KR_STATE_YIELD. */ + } + if (!rrsets_match(stashed->rr, rr)) { + continue; + } + /* Found the entry to merge with. Check consistency and merge. */ + if (kr_fails_assert(stashed->rank == rank && !stashed->cached && stashed->in_progress)) + return kr_error(EEXIST); + + /* It may happen that an RRset is first considered useful + * (to_wire = false, e.g. due to being part of glue), + * and later we may find we also want it in the answer. */ + stashed->to_wire = stashed->to_wire || to_wire; + + /* We just add the reference into this in_progress RRset. */ + rdata_array_t *ra = stashed->rr->additional; + if (ra == NULL) { + /* RRset not in array format yet -> convert it. */ + ra = stashed->rr->additional = mm_alloc(pool, sizeof(*ra)); + if (!ra) { + return kr_error(ENOMEM); + } + array_init(*ra); + int ret = array_reserve_mm(*ra, stashed->rr->rrs.count + rr->rrs.count, + kr_memreserve, pool); + if (ret) { + return kr_error(ret); + } + knot_rdata_t *r_it = stashed->rr->rrs.rdata; + for (int ri = 0; ri < stashed->rr->rrs.count; + ++ri, r_it = knot_rdataset_next(r_it)) { + kr_require(array_push(*ra, r_it) >= 0); + } + } else { + int ret = array_reserve_mm(*ra, ra->len + rr->rrs.count, + kr_memreserve, pool); + if (ret) { + return kr_error(ret); + } + } + /* Append to the array. */ + knot_rdata_t *r_it = rr->rrs.rdata; + for (int ri = 0; ri < rr->rrs.count; + ++ri, r_it = knot_rdataset_next(r_it)) { + kr_require(array_push(*ra, r_it) >= 0); + } + return i; + } + + /* No stashed rrset found, add */ + int ret = array_reserve_mm(*array, array->len + 1, kr_memreserve, pool); + if (ret) { + return kr_error(ret); + } + + ranked_rr_array_entry_t *entry = mm_calloc(pool, 1, sizeof(*entry)); + if (!entry) { + return kr_error(ENOMEM); + } + + knot_rrset_t *rr_new = knot_rrset_new(rr->owner, rr->type, rr->rclass, rr->ttl, pool); + if (!rr_new) { + mm_free(pool, entry); + return kr_error(ENOMEM); + } + rr_new->rrs = rr->rrs; + if (kr_fails_assert(rr_new->additional == NULL)) { + mm_free(pool, entry); + return kr_error(EINVAL); + } + + entry->qry_uid = qry_uid; + entry->rr = rr_new; + entry->rank = rank; + entry->to_wire = to_wire; + entry->in_progress = true; + if (array_push(*array, entry) < 0) { + /* Silence coverity. It shouldn't be possible to happen, + * due to the array_reserve_mm call above. */ + mm_free(pool, entry); + return kr_error(ENOMEM); + } + + ret = to_wire_ensure_unique(array, array->len - 1); + if (ret < 0) return ret; + return array->len - 1; +} + +/** Comparator for qsort() on an array of knot_data_t pointers. */ +static int rdata_p_cmp(const void *rp1, const void *rp2) +{ + /* Just correct types of the parameters and pass them dereferenced. */ + const knot_rdata_t + *const *r1 = rp1, + *const *r2 = rp2; + return knot_rdata_cmp(*r1, *r2); +} +int kr_ranked_rrarray_finalize(ranked_rr_array_t *array, uint32_t qry_uid, knot_mm_t *pool) +{ + for (ssize_t array_i = array->len - 1; array_i >= 0; --array_i) { + ranked_rr_array_entry_t *stashed = array->at[array_i]; + if (stashed->qry_uid != qry_uid) { + continue; /* We apparently can't always short-cut the cycle. */ + } + if (!stashed->in_progress) { + continue; + } + rdata_array_t *ra = stashed->rr->additional; + if (!ra) { + /* No array, so we just need to copy the rdataset. */ + knot_rdataset_t *rds = &stashed->rr->rrs; + knot_rdataset_t tmp = *rds; + int ret = knot_rdataset_copy(rds, &tmp, pool); + if (ret) { + return kr_error(ret); + } + } else { + /* Multiple RRs; first: sort the array. */ + stashed->rr->additional = NULL; + qsort(ra->at, ra->len, sizeof(ra->at[0]), rdata_p_cmp); + /* Prune duplicates: NULL all except the last instance. */ + int dup_count = 0; + for (int i = 0; i + 1 < ra->len; ++i) { + if (knot_rdata_cmp(ra->at[i], ra->at[i + 1]) == 0) { + ra->at[i] = NULL; + ++dup_count; + kr_log_q(NULL, ITERATOR, "deleted duplicate RR\n"); + } + } + /* Prepare rdataset, except rdata contents. */ + knot_rdataset_t *rds = &stashed->rr->rrs; + rds->size = 0; + for (int i = 0; i < ra->len; ++i) { + if (ra->at[i]) { + rds->size += knot_rdata_size(ra->at[i]->len); + } + } + rds->count = ra->len - dup_count; + if (rds->size) { + rds->rdata = mm_alloc(pool, rds->size); + if (!rds->rdata) { + return kr_error(ENOMEM); + } + } else { + rds->rdata = NULL; + } + /* Everything is ready; now just copy all the rdata. */ + uint8_t *raw_it = (uint8_t *)rds->rdata; + for (int i = 0; i < ra->len; ++i) { + if (ra->at[i] && rds->size/*linters*/) { + const int size = knot_rdata_size(ra->at[i]->len); + memcpy(raw_it, ra->at[i], size); + raw_it += size; + } + } + if (kr_fails_assert(raw_it == (uint8_t *)rds->rdata + rds->size)) + return kr_error(EINVAL); + } + stashed->in_progress = false; + } + return kr_ok(); +} + + +int kr_ranked_rrarray_set_wire(ranked_rr_array_t *array, bool to_wire, + uint32_t qry_uid, bool check_dups, + bool (*extraCheck)(const ranked_rr_array_entry_t *)) +{ + for (size_t i = 0; i < array->len; ++i) { + ranked_rr_array_entry_t *entry = array->at[i]; + if (entry->qry_uid != qry_uid) { + continue; + } + if (extraCheck != NULL && !extraCheck(entry)) { + continue; + } + entry->to_wire = to_wire; + if (check_dups) { + int ret = to_wire_ensure_unique(array, i); + if (ret) return ret; + } + } + return kr_ok(); +} + + +static char *callprop(struct kr_module *module, const char *prop, const char *input, void *env) +{ + if (!module || !module->props || !prop) { + return NULL; + } + for (const struct kr_prop *p = module->props; p && p->name; ++p) { + if (p->cb != NULL && strcmp(p->name, prop) == 0) { + return p->cb(env, module, input); + } + } + return NULL; +} + +char *kr_module_call(struct kr_context *ctx, const char *module, const char *prop, const char *input) +{ + if (!ctx || !ctx->modules || !module || !prop) { + return NULL; + } + module_array_t *mod_list = ctx->modules; + for (size_t i = 0; i < mod_list->len; ++i) { + struct kr_module *mod = mod_list->at[i]; + if (strcmp(mod->name, module) == 0) { + return callprop(mod, prop, input, ctx); + } + } + return NULL; +} + +static void flags_to_str(char *dst, const knot_pkt_t *pkt, size_t maxlen) +{ + int offset = 0; + int ret = 0; + struct { + uint8_t (*get) (const uint8_t *packet); + char name[3]; + } flag[7] = { + {knot_wire_get_qr, "qr"}, + {knot_wire_get_aa, "aa"}, + {knot_wire_get_rd, "rd"}, + {knot_wire_get_ra, "ra"}, + {knot_wire_get_tc, "tc"}, + {knot_wire_get_ad, "ad"}, + {knot_wire_get_cd, "cd"} + }; + for (int i = 0; i < 7; ++i) { + if (!flag[i].get(pkt->wire)) { + continue; + } + ret = snprintf(dst + offset, maxlen, "%s ", flag[i].name); + if (ret <= 0 || ret >= maxlen) { + dst[0] = 0; + return; + } + offset += ret; + maxlen -= ret; + } + dst[offset] = 0; +} + +static char *print_section_opt(struct mempool *mp, char *endp, const knot_rrset_t *rr, const uint8_t rcode) +{ + uint8_t errcode = knot_edns_get_ext_rcode(rr); + uint16_t ext_rcode_id = knot_edns_whole_rcode(errcode, rcode); + const char *ext_rcode_str = "Unused"; + const knot_lookup_t *ext_rcode; + + if (errcode > 0) { + ext_rcode = knot_lookup_by_id(knot_rcode_names, ext_rcode_id); + if (ext_rcode != NULL) { + ext_rcode_str = ext_rcode->name; + } else { + ext_rcode_str = "Unknown"; + } + } + + return mp_printf_append(mp, endp, + ";; EDNS PSEUDOSECTION:\n;; " + "Version: %u; flags: %s; UDP size: %u B; ext-rcode: %s\n\n", + knot_edns_get_version(rr), + (knot_edns_do(rr) != 0) ? "do" : "", + knot_edns_get_payload(rr), + ext_rcode_str); + +} + +/** + * Detect if qname contains an uppercase letter. + */ +static bool qname_has_uppercase(const knot_dname_t *qname) { + const int len = knot_dname_size(qname) - 1; /* skip root label at the end */ + for (int i = 1; i < len; ++i) { /* skip first length byte */ + /* Note: this relies on the fact that correct label lengths + * can't pass this test by "luck" and that correctness + * is checked earlier by packet parser. */ + if (qname[i] >= 'A' && qname[i] <= 'Z') + return true; + } + return false; +} + +char *kr_pkt_text(const knot_pkt_t *pkt) +{ + if (!pkt) { + return NULL; + } + + struct mempool *mp = mp_new(512); + + static const char * snames[] = { + ";; ANSWER SECTION", ";; AUTHORITY SECTION", ";; ADDITIONAL SECTION" + }; + char flags[32]; + uint8_t pkt_rcode = knot_wire_get_rcode(pkt->wire); + uint8_t pkt_opcode = knot_wire_get_opcode(pkt->wire); + const char *rcode_str = "Unknown"; + const char *opcode_str = "Unknown"; + const knot_lookup_t *rcode = knot_lookup_by_id(knot_rcode_names, pkt_rcode); + const knot_lookup_t *opcode = knot_lookup_by_id(knot_opcode_names, pkt_opcode); + uint16_t qry_id = knot_wire_get_id(pkt->wire); + uint16_t qdcount = knot_wire_get_qdcount(pkt->wire); + + if (rcode != NULL) { + rcode_str = rcode->name; + } + if (opcode != NULL) { + opcode_str = opcode->name; + } + flags_to_str(flags, pkt, sizeof(flags)); + + char *ptr = mp_printf(mp, + ";; ->>HEADER<<- opcode: %s; status: %s; id: %hu\n" + ";; Flags: %s QUERY: %hu; ANSWER: %hu; " + "AUTHORITY: %hu; ADDITIONAL: %hu\n\n", + opcode_str, rcode_str, qry_id, + flags, + qdcount, + knot_wire_get_ancount(pkt->wire), + knot_wire_get_nscount(pkt->wire), + knot_wire_get_arcount(pkt->wire)); + + if (knot_pkt_has_edns(pkt)) { + ptr = print_section_opt(mp, ptr, pkt->opt_rr, knot_wire_get_rcode(pkt->wire)); + } + + if (qdcount == 1) { + KR_DNAME_GET_STR(qname, knot_pkt_qname(pkt)); + KR_RRTYPE_GET_STR(rrtype, knot_pkt_qtype(pkt)); + const char *qnwarn; + if (qname_has_uppercase(knot_pkt_qname(pkt))) + qnwarn = \ +"; WARNING! Uppercase letters indicate positions with letter case mismatches!\n" +"; Normally you should see all-lowercase qname here.\n"; + else + qnwarn = ""; + ptr = mp_printf_append(mp, ptr, ";; QUESTION SECTION\n%s%s\t\t%s\n", qnwarn, qname, rrtype); + } else if (qdcount > 1) { + ptr = mp_printf_append(mp, ptr, ";; Warning: unsupported QDCOUNT %hu\n", qdcount); + } + + for (knot_section_t i = KNOT_ANSWER; i <= KNOT_ADDITIONAL; ++i) { + const knot_pktsection_t *sec = knot_pkt_section(pkt, i); + if (sec->count == 0) { + continue; + } + + ptr = mp_printf_append(mp, ptr, "\n%s\n", snames[i - KNOT_ANSWER]); + for (unsigned k = 0; k < sec->count; ++k) { + const knot_rrset_t *rr = knot_pkt_rr(sec, k); + if (rr->type == KNOT_RRTYPE_OPT) { + continue; + } + auto_free char *rr_text = kr_rrset_text(rr); + ptr = mp_printf_append(mp, ptr, "%s", rr_text); + } + } + + /* Close growing buffer and duplicate result before deleting */ + char *result = strdup(ptr); + mp_delete(mp); + return result; +} + +const knot_dump_style_t KR_DUMP_STYLE_DEFAULT = { /* almost all = false, */ + .show_ttl = true, +}; + +char *kr_rrset_text(const knot_rrset_t *rr) +{ + if (!rr) { + return NULL; + } + + /* Note: knot_rrset_txt_dump will double the size until the rrset fits */ + size_t bufsize = 128; + char *buf = malloc(bufsize); + int ret = knot_rrset_txt_dump(rr, &buf, &bufsize, &KR_DUMP_STYLE_DEFAULT); + if (ret < 0) { + free(buf); + return NULL; + } + + return buf; +} + +uint64_t kr_now() +{ + return uv_now(uv_default_loop()); +} + +void kr_uv_free_cb(uv_handle_t* handle) +{ + free(handle->data); +} + +const char *kr_strptime_diff(const char *format, const char *time1_str, + const char *time0_str, double *diff) { + if (kr_fails_assert(format && time1_str && time0_str && diff)) return NULL; + + struct tm time1_tm; + time_t time1_u; + struct tm time0_tm; + time_t time0_u; + + char *err = strptime(time1_str, format, &time1_tm); + if (err == NULL || err != time1_str + strlen(time1_str)) + return "strptime failed for time1"; + time1_tm.tm_isdst = -1; /* determine if DST is active or not */ + time1_u = mktime(&time1_tm); + if (time1_u == (time_t)-1) + return "mktime failed for time1"; + + err = strptime(time0_str, format, &time0_tm); + if (err == NULL || err != time0_str + strlen(time0_str)) + return "strptime failed for time0"; + time0_tm.tm_isdst = -1; /* determine if DST is active or not */ + time0_u = mktime(&time0_tm); + if (time0_u == (time_t)-1) + return "mktime failed for time0"; + *diff = difftime(time1_u, time0_u); + + return NULL; +} + +int knot_dname_lf2wire(knot_dname_t * const dst, uint8_t len, const uint8_t *lf) +{ + knot_dname_t *d = dst; /* moving "cursor" as we write it out */ + if (kr_fails_assert(d && (len == 0 || lf))) return kr_error(EINVAL); + /* we allow the final zero byte to be omitted */ + if (!len) { + goto finish; + } + if (lf[len - 1]) { + ++len; + } + /* convert the name, one label at a time */ + int label_end = len - 1; /* index of the zero byte after the current label */ + while (label_end >= 0) { + /* find label_start */ + int i = label_end - 1; + while (i >= 0 && lf[i]) + --i; + const int label_start = i + 1; /* index of the first byte of the current label */ + const int label_len = label_end - label_start; + kr_assert(label_len >= 0); + if (label_len > 63 || label_len <= 0) + return kr_error(EILSEQ); + /* write the label */ + *d = label_len; + ++d; + memcpy(d, lf + label_start, label_len); + d += label_len; + /* next label */ + label_end = label_start - 1; + } +finish: + *d = 0; /* the final zero */ + ++d; + return d - dst; +} + +static void rnd_noerror(void *data, uint size) +{ + int ret = gnutls_rnd(GNUTLS_RND_NONCE, data, size); + if (ret) { + kr_log_error(SYSTEM, "gnutls_rnd(): %s\n", gnutls_strerror(ret)); + abort(); + } +} +void kr_rnd_buffered(void *data, uint size) +{ + /* static circular buffer, from index _begin (inclusive) to _end (exclusive) */ + static uint8_t buf[512/8]; /* gnutls_rnd() works on blocks of 512 bits (chacha) */ + static uint buf_begin = sizeof(buf); + + if (unlikely(size > sizeof(buf))) { + rnd_noerror(data, size); + return; + } + /* Start with contiguous chunk, possibly until the end of buffer. */ + const uint size1 = MIN(size, sizeof(buf) - buf_begin); + uint8_t *d = data; + memcpy(d, buf + buf_begin, size1); + if (size1 == size) { + buf_begin += size1; + return; + } + d += size1; + size -= size1; + /* Refill the whole buffer, and finish by another contiguous chunk. */ + rnd_noerror(buf, sizeof(buf)); + memcpy(d, buf, size); + buf_begin = size; +} + +void kr_rrset_init(knot_rrset_t *rrset, knot_dname_t *owner, + uint16_t type, uint16_t rclass, uint32_t ttl) +{ + if (kr_fails_assert(rrset)) return; + knot_rrset_init(rrset, owner, type, rclass, ttl); +} +bool kr_pkt_has_wire(const knot_pkt_t *pkt) +{ + return pkt->size != KR_PKT_SIZE_NOWIRE; +} +bool kr_pkt_has_dnssec(const knot_pkt_t *pkt) +{ + return knot_pkt_has_dnssec(pkt); +} +uint16_t kr_pkt_qclass(const knot_pkt_t *pkt) +{ + return knot_pkt_qclass(pkt); +} +uint16_t kr_pkt_qtype(const knot_pkt_t *pkt) +{ + return knot_pkt_qtype(pkt); +} +uint32_t kr_rrsig_sig_inception(const knot_rdata_t *rdata) +{ + return knot_rrsig_sig_inception(rdata); +} +uint32_t kr_rrsig_sig_expiration(const knot_rdata_t *rdata) +{ + return knot_rrsig_sig_expiration(rdata); +} +uint16_t kr_rrsig_type_covered(const knot_rdata_t *rdata) +{ + return knot_rrsig_type_covered(rdata); +} + +time_t kr_file_mtime (const char* fname) { + struct stat fstat; + + if (stat(fname, &fstat) != 0) { + return 0; + } + + return fstat.st_mtime; +} + +long long kr_fssize(const char *path) +{ + if (!path) + return kr_error(EINVAL); + + struct statvfs buf; + if (statvfs(path, &buf) != 0) + return kr_error(errno); + + return buf.f_frsize * buf.f_blocks; +} + +const char * kr_dirent_name(const struct dirent *de) +{ + return de ? de->d_name : NULL; +} + diff --git a/lib/utils.h b/lib/utils.h new file mode 100644 index 0000000..0d1d845 --- /dev/null +++ b/lib/utils.h @@ -0,0 +1,608 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <dirent.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <unistd.h> + +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +#include <libknot/libknot.h> +#include <libknot/packet/pkt.h> +#include <libknot/rrset.h> +#include <libknot/rrtype/rrsig.h> +#include <uv.h> + +#include "kresconfig.h" +#include "contrib/mempattern.h" +#include "lib/defines.h" +#include "lib/generic/array.h" +#include "lib/log.h" + +/** When knot_pkt is passed from cache without ->wire, this is the ->size. */ +static const size_t KR_PKT_SIZE_NOWIRE = -1; + +/** Maximum length (excluding null-terminator) of a presentation-form address + * returned by `kr_straddr`. */ +#define KR_STRADDR_MAXLEN 109 + +/** Used for reserving enough space for the `kr_sockaddr_key` function + * output. */ +struct kr_sockaddr_key_storage { + char bytes[sizeof(struct sockaddr_storage)]; +}; + + +/* + * Logging and debugging. + */ + +/** @brief Callback for request events. */ +typedef void (*trace_callback_f)(struct kr_request *request); +/** + * @brief Callback for request logging handler. + * @param[in] msg Log message. Pointer is not valid after handler returns. */ +typedef void (*trace_log_f)(const struct kr_request *request, const char *msg); + +/** Assert() but always, regardless of -DNDEBUG. See also kr_assert(). */ +#define kr_require(expression) do { if (!(expression)) { \ + kr_fail(true, #expression, __func__, __FILE__, __LINE__); \ + __builtin_unreachable(); /* aid code analysis */ \ + } } while (false) + +/** Check an assertion that's recoverable. Return the true if it fails and needs handling. + * + * If the check fails, optionally fork()+abort() to generate coredump + * and continue running in parent process. Return value must be handled to + * ensure safe recovery from error. Use kr_require() for unrecoverable checks. + * The errno variable is not mangled, e.g. you can: if (kr_fails_assert(...)) return errno; + */ +#define kr_fails_assert(expression) !kr_assert_func((expression), #expression, \ + __func__, __FILE__, __LINE__) + +/** Kresd assertion without a return value. + * + * These can be turned on or off, for mandatory unrecoverable checks, use kr_require(). + * For recoverable checks, use kr_fails_assert(). + * */ +#define kr_assert(expression) (void)!kr_fails_assert((expression)) + +/** Whether kr_assert() and kr_fails_assert() checks should abort. */ +KR_EXPORT extern bool kr_dbg_assertion_abort; + +/** How often kr_assert() should fork the process before issuing abort (if configured). + * + * This can be useful for debugging rare edge-cases in production. + * if (kr_debug_assertion_abort && kr_debug_assertion_fork), it is + * possible to both obtain a coredump (from forked child) and recover from the + * non-fatal error in the parent process. + * + * == 0 (false): no forking + * > 0: minimum delay between forks + * (in milliseconds, each instance separately, randomized +-25%) + * < 0: no rate-limiting (not recommended) + */ +KR_EXPORT extern int kr_dbg_assertion_fork; + +/** Use kr_require(), kr_assert() or kr_fails_assert() instead of directly this function. */ +KR_EXPORT KR_COLD void kr_fail(bool is_fatal, const char* expr, const char *func, + const char *file, int line); + +/** Use kr_require(), kr_assert() or kr_fails_assert() instead of directly this function. */ +__attribute__ ((warn_unused_result)) +static inline bool kr_assert_func(bool result, const char *expr, const char *func, + const char *file, int line) +{ + if (!result) + kr_fail(false, expr, func, file, line); + return result; +} + +#define KR_DNAME_GET_STR(dname_str, dname) \ + char dname_str[KR_DNAME_STR_MAXLEN]; \ + knot_dname_to_str(dname_str, (dname), sizeof(dname_str)); \ + dname_str[sizeof(dname_str) - 1] = 0; + +#define KR_RRTYPE_GET_STR(rrtype_str, rrtype) \ + char rrtype_str[KR_RRTYPE_STR_MAXLEN]; \ + knot_rrtype_to_string((rrtype), rrtype_str, sizeof(rrtype_str)); \ + rrtype_str[sizeof(rrtype_str) - 1] = 0; + +// Use this for allocations with mm. +// Use mm_alloc for allocations into mempool + +/** A strcmp() variant directly usable for qsort() on an array of strings. */ +static inline int strcmp_p(const void *p1, const void *p2) +{ + return strcmp(*(char * const *)p1, *(char * const *)p2); +} + +/** Get current working directory with fallback value. */ +static inline void get_workdir(char *out, size_t len) { + if(getcwd(out, len) == NULL) { + static const char errprefix[] = "<invalid working directory>"; + strncpy(out, errprefix, len); + } +} + +/** @cond internal Array types */ +struct kr_context; + +struct ranked_rr_array_entry { + uint32_t qry_uid; + uint8_t rank; /**< enum kr_rank */ + uint8_t revalidation_cnt; + bool cached : 1; /**< Set to true if the entry was written into cache */ + bool yielded : 1; + bool to_wire : 1; /**< whether to be put into the answer */ + bool expiring : 1; /**< low remaining TTL; see is_expiring; only used in cache ATM */ + bool in_progress : 1; /**< build of RRset in progress, i.e. different format of RR data */ + bool dont_cache : 1; /**< avoid caching; useful e.g. for generated data */ + knot_rrset_t *rr; +}; +typedef struct ranked_rr_array_entry ranked_rr_array_entry_t; + +/** Array of RRsets coming from multiple queries; for struct kr_request. + * + * Notes: + * - RRSIGs are only considered to form an RRset when the types covered match; + * cache-related code relies on that! + * - RRsets from the same packet (qry_uid) get merged. + */ +typedef array_t(ranked_rr_array_entry_t *) ranked_rr_array_t; +/* @endcond */ + +typedef struct kr_http_header_array_entry { + char* name; + char* value; +} kr_http_header_array_entry_t; + +/** Array of HTTP headers for DoH. */ +typedef array_t(kr_http_header_array_entry_t) kr_http_header_array_t; + +/** Concatenate N strings. */ +KR_EXPORT +char* kr_strcatdup(unsigned n, ...); + +/** Construct absolute file path, without resolving symlinks. + * \return malloc-ed string or NULL (+errno in that case) */ +KR_EXPORT +char * kr_absolutize_path(const char *dirname, const char *fname); + +/** You probably want kr_rand_* convenience functions instead. + * This is a buffered version of gnutls_rnd(GNUTLS_RND_NONCE, ..) */ +KR_EXPORT +void kr_rnd_buffered(void *data, unsigned int size); + +/** Return a few random bytes. */ +KR_EXPORT inline +uint64_t kr_rand_bytes(unsigned int size) +{ + uint64_t result; + if (size <= 0 || size > sizeof(result)) { + kr_log_error(SYSTEM, "kr_rand_bytes(): EINVAL\n"); + abort(); + } + uint8_t data[sizeof(result)]; + kr_rnd_buffered(data, size); + /* I would have liked to dump the random data into a size_t directly, + * but that would work well only on little-endian machines, + * so instead I hope that the compiler will optimize this out. + * (Tested via reading assembly from usual gcc -O2 setup.) + * Alternatively we could waste more rnd bytes, but that seemed worse. */ + result = 0; + for (unsigned int i = 0; i < size; ++ i) { + result |= ((uint64_t)data[i]) << (i * 8); + } + return result; +} + +/** Throw a pseudo-random coin, succeeding approximately with probability nomin/denomin. + * - low precision, only one byte of randomness (or none with extreme parameters) + * - tip: use !kr_rand_coin() to get the complementary probability + */ +static inline bool kr_rand_coin(unsigned int nomin, unsigned int denomin) +{ + /* This function might be called with non-constant values + * so we try to handle odd corner cases instead of crash. */ + if (nomin >= denomin) + return true; + else if (nomin <= 0) + return false; + + /* threshold = how many parts from 256 are a success */ + unsigned int threshold = (nomin * 256 + /*rounding*/ denomin / 2) / denomin; + if (threshold == 0) threshold = 1; + if (threshold == 256) threshold = 255; + return (kr_rand_bytes(1) < threshold); +} + +/** Memory reservation routine for knot_mm_t */ +KR_EXPORT +int kr_memreserve(void *baton, void **mem, size_t elm_size, size_t want, size_t *have); + +/** @internal Fast packet reset. */ +KR_EXPORT +int kr_pkt_recycle(knot_pkt_t *pkt); + +/** @internal Clear packet payload. */ +KR_EXPORT +int kr_pkt_clear_payload(knot_pkt_t *pkt); + +/** Construct and put record to packet. */ +KR_EXPORT +int kr_pkt_put(knot_pkt_t *pkt, const knot_dname_t *name, uint32_t ttl, + uint16_t rclass, uint16_t rtype, const uint8_t *rdata, uint16_t rdlen); + +/** Set packet header suitable for authoritative answer. (for policy module) */ +KR_EXPORT +void kr_pkt_make_auth_header(knot_pkt_t *pkt); + +/** Get pointer to the in-header QNAME. + * + * That's normally not lower-cased. However, when receiving packets from upstream + * we xor-apply the secret during packet-parsing, so it would get lower-cased + * after that point if the case was right. + */ +static inline knot_dname_t * kr_pkt_qname_raw(const knot_pkt_t *pkt) +{ + if (pkt == NULL || pkt->qname_size == 0) { + return NULL; + } + return pkt->wire + KNOT_WIRE_HEADER_SIZE; +} + +/** Simple storage for IPx address and their ports or AF_UNSPEC. */ +union kr_sockaddr { + struct sockaddr ip; + struct sockaddr_in ip4; + struct sockaddr_in6 ip6; +}; + +/** Simple storage for IPx addresses. */ +union kr_in_addr { + struct in_addr ip4; + struct in6_addr ip6; +}; + +/* TODO: rename kr_inaddr functions to kr_sockaddr */ +/** Address bytes for given family. */ +KR_EXPORT KR_PURE +const char *kr_inaddr(const struct sockaddr *addr); +/** Address family. */ +KR_EXPORT KR_PURE +int kr_inaddr_family(const struct sockaddr *addr); +/** Address length for given family, i.e. sizeof(struct in*_addr). */ +KR_EXPORT KR_PURE +int kr_inaddr_len(const struct sockaddr *addr); +/** Sockaddr length for given family, i.e. sizeof(struct sockaddr_in*). */ +KR_EXPORT KR_PURE +int kr_sockaddr_len(const struct sockaddr *addr); + +/** Creates a packed structure from the specified `addr`, safe for use as a key + * in containers like `trie_t`, and writes it into `dst`. On success, returns + * the actual length of the key. + * + * Returns `kr_error(EAFNOSUPPORT)` if the family of `addr` is unsupported. */ +KR_EXPORT +ssize_t kr_sockaddr_key(struct kr_sockaddr_key_storage *dst, + const struct sockaddr *addr); + +/** Creates a `struct sockaddr` from the specified `key` created using the + * `kr_sockaddr_key()` function. */ +KR_EXPORT +struct sockaddr *kr_sockaddr_from_key(struct sockaddr_storage *dst, + const char *key); + +/** Checks whether the two keys represent the same address; + * does NOT compare the ports. */ +KR_EXPORT +bool kr_sockaddr_key_same_addr(const char *key_a, const char *key_b); + +/** Compare two given sockaddr. + * return 0 - addresses are equal, error code otherwise. + */ +KR_EXPORT KR_PURE +int kr_sockaddr_cmp(const struct sockaddr *left, const struct sockaddr *right); +/** Port. */ +KR_EXPORT KR_PURE +uint16_t kr_inaddr_port(const struct sockaddr *addr); +/** Set port. */ +KR_EXPORT +void kr_inaddr_set_port(struct sockaddr *addr, uint16_t port); + +/** Write string representation for given address as "<addr>#<port>". + * \param[in] addr the raw address + * \param[out] buf the buffer for output string + * \param[in,out] buflen the available(in) and utilized(out) length, including \0 */ +KR_EXPORT +int kr_inaddr_str(const struct sockaddr *addr, char *buf, size_t *buflen); + +/** Write string representation for given address as "<addr>#<port>". + * It's the same as kr_inaddr_str(), but the input address is input in native format + * like for inet_ntop() (4 or 16 bytes) and port must be separate parameter. */ +KR_EXPORT +int kr_ntop_str(int family, const void *src, uint16_t port, char *buf, size_t *buflen); + +/** @internal Create string representation addr#port. + * @return pointer to a *static* string, i.e. each call will overwrite it + */ +KR_EXPORT +char *kr_straddr(const struct sockaddr *addr); + +/** Return address type for string. */ +KR_EXPORT KR_PURE +int kr_straddr_family(const char *addr); +/** Return address length in given family (struct in*_addr). */ +KR_EXPORT KR_CONST +int kr_family_len(int family); + +/** Create a sockaddr* from string+port representation. + * Also accepts IPv6 link-local and AF_UNIX starting with "/" (ignoring port) */ +KR_EXPORT +struct sockaddr * kr_straddr_socket(const char *addr, int port, knot_mm_t *pool); + +/** Parse address and return subnet length (bits). + * @warning 'dst' must be at least `sizeof(struct in6_addr)` long. */ +KR_EXPORT +int kr_straddr_subnet(void *dst, const char *addr); + +/** Splits ip address specified as "addr@port" or "addr#port" into addr and port. + * \param[in] instr zero-terminated input, e.g. "192.0.2.1#12345\0" + * \param[out] ipaddr working buffer for the port-less prefix of instr; + * length >= INET6_ADDRSTRLEN + 1. + * \param[out] port written in case it's specified in instr + * \return error code + * \note Typically you follow this by kr_straddr_socket(). + * \note Only internet addresses are supported, i.e. no AF_UNIX sockets. + */ +KR_EXPORT +int kr_straddr_split(const char *instr, char ipaddr[static restrict (INET6_ADDRSTRLEN + 1)], + uint16_t *port); + +/** Formats ip address and port in "addr#port" format. + * and performs validation. + * @note Port always formatted as five-character string with leading zeros. + * @return kr_error(EINVAL) - addr or buf is NULL or buflen is 0 or + * addr doesn't contain a valid ip address + * kr_error(ENOSP) - buflen is too small + */ +KR_EXPORT +int kr_straddr_join(const char *addr, uint16_t port, char *buf, size_t *buflen); + +/** Compare memory bitwise. The semantics is "the same" as for memcmp(). + * The partial byte is considered with more-significant bits first, + * so this is e.g. suitable for comparing IP prefixes. */ +KR_EXPORT KR_PURE +int kr_bitcmp(const char *a, const char *b, int bits); + +/** Masks bits. The specified number of bits in `a` from the left (network order) + * will remain their original value, while the rest will be set to zero. + * This is useful for storing network addresses in a trie. */ +KR_EXPORT +void kr_bitmask(unsigned char *a, size_t a_len, int bits); + +/** Check whether `addr` points to an `AF_INET6` address and whether the address + * is link-local. */ +static inline bool kr_sockaddr_link_local(const struct sockaddr *addr) +{ + if (addr->sa_family != AF_INET6) + return false; + + /* Link-local: https://tools.ietf.org/html/rfc4291#section-2.4 */ + const uint8_t prefix[] = { 0xFE, 0x80 }; + const struct sockaddr_in6 *ip6 = (const struct sockaddr_in6 *) addr; + return kr_bitcmp((char *) ip6->sin6_addr.s6_addr, (char *) prefix, 10) == 0; +} + +/* Stash key = {[5] class, [1-255] owner, [5] type, [5] additional, [1] \x00 } */ +#define KR_RRKEY_LEN (16 + KNOT_DNAME_MAXLEN) +/** Create unique null-terminated string key for RR. + * @param key Destination buffer for key size, MUST be KR_RRKEY_LEN or larger. + * @param class RR class. + * @param owner RR owner name. + * @param type RR type. + * @param additional flags (for instance can be used for storing covered type + * when RR type is RRSIG). + * @return key length if successful or an error + * */ +KR_EXPORT +int kr_rrkey(char *key, uint16_t class, const knot_dname_t *owner, + uint16_t type, uint16_t additional); + +/** Add RRSet copy to a ranked RR array. + * + * To convert to standard RRs inside, you need to call _finalize() afterwards, + * and the memory of rr->rrs.rdata has to remain until then. + * + * \return array index (>= 0) or error code (< 0) + */ +KR_EXPORT +int kr_ranked_rrarray_add(ranked_rr_array_t *array, const knot_rrset_t *rr, + uint8_t rank, bool to_wire, uint32_t qry_uid, knot_mm_t *pool); +/** Finalize in_progress sets - all with matching qry_uid. */ +KR_EXPORT +int kr_ranked_rrarray_finalize(ranked_rr_array_t *array, uint32_t qry_uid, knot_mm_t *pool); + +/** @internal Mark the RRSets from particular query as + * "have (not) to be recorded in the final answer". + * @param array RRSet array. + * @param to_wire Records must be\must not be recorded in final answer. + * @param qry_uid Query uid. + * @param check_dups When to_wire is true, try to avoid duplicate RRSets. + * @param extraCheck optional function checking whether to consider the record + * @return 0 or an error + */ +int kr_ranked_rrarray_set_wire(ranked_rr_array_t *array, bool to_wire, + uint32_t qry_uid, bool check_dups, + bool (*extraCheck)(const ranked_rr_array_entry_t *)); + + +/** Style used by the kr_*_text() functions. */ +KR_EXPORT extern +const knot_dump_style_t KR_DUMP_STYLE_DEFAULT; + +/** + * @return Newly allocated string representation of packet. + * Caller has to free() returned string. + */ +KR_EXPORT +char *kr_pkt_text(const knot_pkt_t *pkt); + +KR_PURE +char *kr_rrset_text(const knot_rrset_t *rr); + +KR_PURE +static inline char *kr_dname_text(const knot_dname_t *name) { + return knot_dname_to_str_alloc(name); +} + +KR_CONST +static inline char *kr_rrtype_text(const uint16_t rrtype) { + char type_str[32] = {0}; + knot_rrtype_to_string(rrtype, type_str, sizeof(type_str)); + return strdup(type_str); +} + +/** + * Call module property. + */ +KR_EXPORT +char *kr_module_call(struct kr_context *ctx, const char *module, const char *prop, const char *input); + +/** Swap two places. Note: the parameters need to be without side effects. */ +#define SWAP(x, y) do { /* http://stackoverflow.com/a/3982430/587396 */ \ + unsigned char swap_temp[sizeof(x) == sizeof(y) ? (ssize_t)sizeof(x) : -1]; \ + memcpy(swap_temp, &y, sizeof(x)); \ + memcpy(&y, &x, sizeof(x)); \ + memcpy(&x, swap_temp, sizeof(x)); \ + } while(0) + +/** Return the (covered) type of an nonempty RRset. */ +static inline uint16_t kr_rrset_type_maysig(const knot_rrset_t *rr) +{ + kr_require(rr && rr->rrs.count && rr->rrs.rdata); + uint16_t type = rr->type; + if (type == KNOT_RRTYPE_RRSIG) + type = knot_rrsig_type_covered(rr->rrs.rdata); + return type; +} + +/** The current time in monotonic milliseconds. + * + * \note it may be outdated in case of long callbacks; see uv_now(). + */ +KR_EXPORT +uint64_t kr_now(); + +/** Call free(handle->data); it's useful e.g. as a callback in uv_close(). */ +KR_EXPORT void kr_uv_free_cb(uv_handle_t* handle); + +/** Convert name from lookup format to wire. See knot_dname_lf + * + * \note len bytes are read and len+1 are written with *normal* LF, + * but it's also allowed that the final zero byte is omitted in LF. + * \return the number of bytes written (>0) or error code (<0) + */ +int knot_dname_lf2wire(knot_dname_t *dst, uint8_t len, const uint8_t *lf); + +/** Patched knot_dname_lf. LF for "." has length zero instead of one, for consistency. + * (TODO: consistency?) + * \param add_wildcard append the wildcard label + * \note packet is always NULL + */ +static inline int kr_dname_lf(uint8_t *dst, const knot_dname_t *src, bool add_wildcard) +{ + knot_dname_storage_t right_aligned_dst; + uint8_t *right_aligned_dname_start = knot_dname_lf(src, right_aligned_dst); + if (!right_aligned_dname_start) { + return kr_error(EINVAL); + } + int len = right_aligned_dname_start[0]; + if (kr_fails_assert(right_aligned_dname_start + 1 + len - KNOT_DNAME_MAXLEN == right_aligned_dst)) + return kr_error(EINVAL); + memcpy(dst + 1, right_aligned_dname_start + 1, len); + if (add_wildcard) { + if (len + 2 > KNOT_DNAME_MAXLEN) + return kr_error(ENOSPC); + dst[len + 1] = '*'; + dst[len + 2] = '\0'; + len += 2; + } + dst[0] = len; + return KNOT_EOK; +} + + +/** Timer, i.e stop-watch. */ +typedef struct timespec kr_timer_t; + +/** Start, i.e. set the reference point. */ +static inline void kr_timer_start(kr_timer_t *start) +{ + /* The call should be very reliable, but let's check it in _start() at least. */ + kr_require(start && clock_gettime(CLOCK_MONOTONIC, start) == 0); +} + +/** Get elapsed time in floating-point seconds. */ +static inline double kr_timer_elapsed(kr_timer_t *start) +{ + kr_require(start); + kr_timer_t end = { 0 }; + (void)clock_gettime(CLOCK_MONOTONIC, &end); + return (end.tv_sec - start->tv_sec) + (double)(end.tv_nsec - start->tv_nsec) / 1e9; +} + +/** Get elapsed time in micro-seconds. */ +static inline uint64_t kr_timer_elapsed_us(kr_timer_t *start) +{ + kr_require(start); + kr_timer_t end = { 0 }; + (void)clock_gettime(CLOCK_MONOTONIC, &end); + // avoid negative differences, because of integer division + if (end.tv_nsec - start->tv_nsec < 0) { + end.tv_nsec += 1000*1000*1000; + end.tv_sec -= 1; + } + return (uint64_t)(end.tv_sec - start->tv_sec) * 1000000 + // adding 500 gives us rounding + + (end.tv_nsec - start->tv_nsec + 500) / 1000; +} + + +/** + * Difference between two calendar times specified as strings. + * \param[in] format format for strptime + * \param[out] diff result from C difftime(time1, time0) + */ +KR_EXPORT +const char *kr_strptime_diff(const char *format, const char *time1_str, + const char *time0_str, double *diff); + +/* Trivial non-inline wrappers, to be used in lua. */ +KR_EXPORT void kr_rrset_init(knot_rrset_t *rrset, knot_dname_t *owner, + uint16_t type, uint16_t rclass, uint32_t ttl); +KR_EXPORT bool kr_pkt_has_wire(const knot_pkt_t *pkt); +KR_EXPORT bool kr_pkt_has_dnssec(const knot_pkt_t *pkt); +KR_EXPORT uint16_t kr_pkt_qclass(const knot_pkt_t *pkt); +KR_EXPORT uint16_t kr_pkt_qtype(const knot_pkt_t *pkt); +KR_EXPORT uint32_t kr_rrsig_sig_inception(const knot_rdata_t *rdata); +KR_EXPORT uint32_t kr_rrsig_sig_expiration(const knot_rdata_t *rdata); +KR_EXPORT uint16_t kr_rrsig_type_covered(const knot_rdata_t *rdata); + +KR_EXPORT time_t kr_file_mtime (const char* fname); +/** Return filesystem size in bytes. */ +KR_EXPORT long long kr_fssize(const char *path); +/** Simply return de->dname. (useful from Lua) */ +KR_EXPORT const char * kr_dirent_name(const struct dirent *de); + diff --git a/lib/zonecut.c b/lib/zonecut.c new file mode 100644 index 0000000..4ec4036 --- /dev/null +++ b/lib/zonecut.c @@ -0,0 +1,590 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "lib/zonecut.h" + +#include "contrib/cleanup.h" +#include "lib/defines.h" +#include "lib/generic/pack.h" +#include "lib/resolve.h" +#include "lib/rplan.h" + +#include <libknot/descriptor.h> +#include <libknot/packet/wire.h> +#include <libknot/rrtype/rdname.h> + +#define VERBOSE_MSG(qry, ...) kr_log_q(qry, ZCUT, __VA_ARGS__) + +/** Information for one NS name + address type. */ +typedef enum { + AI_UNINITED = 0, + AI_REPUT, /**< Don't use this addrset, due to: cache_rep, NO_IPV6, ... + * cache_rep approximates various problems when fetching the RRset. */ + AI_CYCLED, /**< Skipped due to cycle detection; see implementation for details. */ + AI_LAST_BAD = AI_CYCLED, /** bad states: <= AI_LAST_BAD */ + AI_UNKNOWN, /**< Don't know status of this RRset; various reasons. */ + AI_EMPTY, /**< No usable address (may mean e.g. just NODATA). */ + AI_OK, /**< At least one usable address. + * LATER: we might be interested whether it's only glue. */ +} addrset_info_t; + + +static void update_cut_name(struct kr_zonecut *cut, const knot_dname_t *name) +{ + if (knot_dname_is_equal(name, cut->name)) { + return; + } + knot_dname_t *next_name = knot_dname_copy(name, cut->pool); + mm_free(cut->pool, cut->name); + cut->name = next_name; +} + +int kr_zonecut_init(struct kr_zonecut *cut, const knot_dname_t *name, knot_mm_t *pool) +{ + if (!cut || !name) { + return kr_error(EINVAL); + } + + memset(cut, 0, sizeof(*cut)); + cut->name = knot_dname_copy(name, pool); + cut->pool = pool; + cut->nsset = trie_create(pool); + return cut->name && cut->nsset ? kr_ok() : kr_error(ENOMEM); +} + +/** Completely free a pack_t. */ +static inline void free_addr_set(pack_t *pack, knot_mm_t *pool) +{ + if (kr_fails_assert(pack)) { + /* promised we don't store NULL packs */ + return; + } + pack_clear_mm(*pack, mm_free, pool); + mm_free(pool, pack); +} +/** Trivial wrapper for use in trie_apply, due to ugly casting. */ +static int free_addr_set_cb(trie_val_t *v, void *pool) +{ + free_addr_set(*v, pool); + return kr_ok(); +} + +void kr_zonecut_deinit(struct kr_zonecut *cut) +{ + if (!cut) { + return; + } + mm_free(cut->pool, cut->name); + if (cut->nsset) { + trie_apply(cut->nsset, free_addr_set_cb, cut->pool); + trie_free(cut->nsset); + } + knot_rrset_free(cut->key, cut->pool); + knot_rrset_free(cut->trust_anchor, cut->pool); +} + +void kr_zonecut_move(struct kr_zonecut *to, const struct kr_zonecut *from) +{ + kr_require(to && from); + kr_zonecut_deinit(to); + memcpy(to, from, sizeof(*to)); +} + +void kr_zonecut_set(struct kr_zonecut *cut, const knot_dname_t *name) +{ + if (!cut || !name) { + return; + } + knot_rrset_t *key, *ta; + key = cut->key; cut->key = NULL; + ta = cut->trust_anchor; cut->trust_anchor = NULL; + kr_zonecut_deinit(cut); + kr_zonecut_init(cut, name, cut->pool); + cut->key = key; + cut->trust_anchor = ta; +} + +int kr_zonecut_copy(struct kr_zonecut *dst, const struct kr_zonecut *src) +{ + if (!dst || !src) { + return kr_error(EINVAL); + } + if (!dst->nsset) { + dst->nsset = trie_create(dst->pool); + } + /* Copy the contents, one by one. */ + int ret = kr_ok(); + trie_it_t *it; + for (it = trie_it_begin(src->nsset); !trie_it_finished(it); trie_it_next(it)) { + size_t klen; + const char * const k = trie_it_key(it, &klen); + pack_t **new_pack = (pack_t **)trie_get_ins(dst->nsset, k, klen); + if (!new_pack) { + ret = kr_error(ENOMEM); + break; + } + const pack_t *old_pack = *trie_it_val(it); + ret = pack_clone(new_pack, old_pack, dst->pool); + if (ret) break; + } + trie_it_free(it); + return ret; +} + +int kr_zonecut_copy_trust(struct kr_zonecut *dst, const struct kr_zonecut *src) +{ + knot_rrset_t *key_copy = NULL; + knot_rrset_t *ta_copy = NULL; + + if (src->key) { + key_copy = knot_rrset_copy(src->key, dst->pool); + if (!key_copy) { + return kr_error(ENOMEM); + } + } + + if (src->trust_anchor) { + ta_copy = knot_rrset_copy(src->trust_anchor, dst->pool); + if (!ta_copy) { + knot_rrset_free(key_copy, dst->pool); + return kr_error(ENOMEM); + } + } + + knot_rrset_free(dst->key, dst->pool); + dst->key = key_copy; + knot_rrset_free(dst->trust_anchor, dst->pool); + dst->trust_anchor = ta_copy; + + return kr_ok(); +} + +int kr_zonecut_add(struct kr_zonecut *cut, const knot_dname_t *ns, const void *data, int len) +{ + if (kr_fails_assert(cut && ns && cut->nsset && (!data || len > 0))) + return kr_error(EINVAL); + /* Disabled; add_reverse_pair() misuses this for domain name in rdata. */ + if (false && data && len != sizeof(struct in_addr) + && len != sizeof(struct in6_addr)) { + kr_assert(!EINVAL); + return kr_error(EINVAL); + } + + /* Get a pack_t for the ns. */ + pack_t **pack = (pack_t **)trie_get_ins(cut->nsset, (const char *)ns, knot_dname_size(ns)); + if (!pack) return kr_error(ENOMEM); + if (*pack == NULL) { + *pack = mm_alloc(cut->pool, sizeof(pack_t)); + if (*pack == NULL) return kr_error(ENOMEM); + pack_init(**pack); + } + /* Insert data (if has any) */ + if (data == NULL) { + return kr_ok(); + } + /* Check for duplicates */ + if (pack_obj_find(*pack, data, len)) { + return kr_ok(); + } + /* Push new address */ + int ret = pack_reserve_mm(**pack, 1, len, kr_memreserve, cut->pool); + if (ret != 0) { + return kr_error(ENOMEM); + } + return pack_obj_push(*pack, data, len); +} + +int kr_zonecut_del(struct kr_zonecut *cut, const knot_dname_t *ns, const void *data, int len) +{ + if (!cut || !ns || (data && len <= 0)) { + return kr_error(EINVAL); + } + + /* Find the address list. */ + int ret = kr_ok(); + pack_t *pack = kr_zonecut_find(cut, ns); + if (pack == NULL) { + return kr_error(ENOENT); + } + /* Remove address from the pack. */ + if (data) { + ret = pack_obj_del(pack, data, len); + } + /* No servers left, remove NS from the set. */ + if (pack->len == 0) { + free_addr_set(pack, cut->pool); + ret = trie_del(cut->nsset, (const char *)ns, knot_dname_size(ns), NULL); + if (kr_fails_assert(ret == 0)) /* only KNOT_ENOENT and that *can't* happen */ + return kr_error(ret); + return kr_ok(); + } + + return ret; +} + +int kr_zonecut_del_all(struct kr_zonecut *cut, const knot_dname_t *ns) +{ + if (!cut || !ns) { + return kr_error(EINVAL); + } + + /* Find the address list; then free and remove it. */ + pack_t *pack; + int ret = trie_del(cut->nsset, (const char *)ns, knot_dname_size(ns), + (trie_val_t *)&pack); + if (ret) { /* deletion failed */ + kr_assert(ret == KNOT_ENOENT); + return kr_error(ENOENT); + } + free_addr_set(pack, cut->pool); + return kr_ok(); +} + +pack_t *kr_zonecut_find(struct kr_zonecut *cut, const knot_dname_t *ns) +{ + if (!cut || !ns) { + return NULL; + } + trie_val_t *val = trie_get_try(cut->nsset, (const char *)ns, knot_dname_size(ns)); + /* we get pointer to the pack_t pointer */ + return val ? (pack_t *)*val : NULL; +} + +static int has_address(trie_val_t *v, void *baton_) +{ + const pack_t *pack = *v; + const bool found = pack != NULL && pack->len != 0; + return found; +} + +bool kr_zonecut_is_empty(struct kr_zonecut *cut) +{ + if (kr_fails_assert(cut && cut->nsset)) + return true; + return !trie_apply(cut->nsset, has_address, NULL); +} + +int kr_zonecut_set_sbelt(struct kr_context *ctx, struct kr_zonecut *cut) +{ + if (!ctx || !cut || !ctx->root_hints.nsset) { + return kr_error(EINVAL); + } + + trie_apply(cut->nsset, free_addr_set_cb, cut->pool); + trie_clear(cut->nsset); + + const uint8_t *const dname_root = (const uint8_t *)/*sign-cast*/(""); + update_cut_name(cut, dname_root); + /* Copy root hints from resolution context. */ + return kr_zonecut_copy(cut, &ctx->root_hints); +} + +/** Fetch address for zone cut. Any rank is accepted (i.e. glue as well). */ +static addrset_info_t fetch_addr(pack_t *addrs, const knot_dname_t *ns, uint16_t rrtype, + int *addr_budget, + knot_mm_t *mm_pool, const struct kr_query *qry) +// LATER(optim.): excessive data copying +{ + int rdlen; + switch (rrtype) { + case KNOT_RRTYPE_A: + rdlen = 4; + break; + case KNOT_RRTYPE_AAAA: + rdlen = 16; + break; + default: + kr_assert(!EINVAL); + return AI_UNKNOWN; + } + + struct kr_context *ctx = qry->request->ctx; + struct kr_cache_p peek; + if (kr_cache_peek_exact(&ctx->cache, ns, rrtype, &peek) != 0) { + return AI_UNKNOWN; + } + int32_t new_ttl = kr_cache_ttl(&peek, qry, ns, rrtype); + if (new_ttl < 0) { + return AI_UNKNOWN; + } + + knot_rrset_t cached_rr; + knot_rrset_init(&cached_rr, /*const-cast*/(knot_dname_t *)ns, rrtype, + KNOT_CLASS_IN, new_ttl); + if (kr_cache_materialize(&cached_rr.rrs, &peek, mm_pool) < 0) { + return AI_UNKNOWN; + } + + *addr_budget -= cached_rr.rrs.count - 1; + if (*addr_budget < 0) { + cached_rr.rrs.count += *addr_budget; + *addr_budget = 0; + } + + /* Reserve memory in *addrs. Implementation detail: + * pack_t cares for lengths, so we don't store those in the data. */ + const size_t pack_extra_size = cached_rr.rrs.size + - cached_rr.rrs.count * offsetof(knot_rdata_t, len); + int ret = pack_reserve_mm(*addrs, cached_rr.rrs.count, pack_extra_size, + kr_memreserve, mm_pool); + kr_require(ret == 0); /* ENOMEM "probably" */ + + int usable_cnt = 0; + addrset_info_t result = AI_EMPTY; + knot_rdata_t *rd = cached_rr.rrs.rdata; + for (uint16_t i = 0; i < cached_rr.rrs.count; ++i, rd = knot_rdataset_next(rd)) { + if (unlikely(rd->len != rdlen)) { + VERBOSE_MSG(qry, "bad NS address length %d for rrtype %d, skipping\n", + (int)rd->len, (int)rrtype); + continue; + } + result = AI_OK; + ++usable_cnt; + + ret = pack_obj_push(addrs, rd->data, rd->len); + kr_assert(!ret); /* didn't fit because of incorrectly reserved memory */ + /* LATER: for now we lose quite some information here, + * as keeping it would need substantial changes on other places, + * and it turned out to be premature optimization (most likely). + * We might e.g. skip adding unusable addresses, + * and either keep some rtt information associated + * or even finish up choosing the set to send packets to. + * Overall there's some overlap with nsrep.c functionality. + */ + } + if (usable_cnt != cached_rr.rrs.count) { + VERBOSE_MSG(qry, "usable NS addresses: %d/%d\n", + usable_cnt, cached_rr.rrs.count); + } + return result; +} + +/** Fetch best NS for zone cut. */ +static int fetch_ns(struct kr_context *ctx, struct kr_zonecut *cut, + const knot_dname_t *name, const struct kr_query *qry, + uint8_t * restrict rank) +{ + struct kr_cache_p peek; + int ret = kr_cache_peek_exact(&ctx->cache, name, KNOT_RRTYPE_NS, &peek); + if (ret != 0) { + return ret; + } + /* Note: we accept *any* rank from the cache. We assume that nothing + * completely untrustworthy could get into the cache, e.g out-of-bailiwick + * records that weren't validated. + */ + *rank = peek.rank; + + int32_t new_ttl = kr_cache_ttl(&peek, qry, name, KNOT_RRTYPE_NS); + if (new_ttl < 0) { + return kr_error(ESTALE); + } + /* Materialize the rdataset temporarily, for simplicity. */ + knot_rdataset_t ns_rds = { 0 }; + ret = kr_cache_materialize(&ns_rds, &peek, cut->pool); + if (ret < 0) { + return ret; + } + + /* Consider at most 13 first NSs (like root). It's a trivial approach + * to limit our resources when choosing NSs. Otherwise DoS might be viable. + * We're not aware of any reasonable use case for having many NSs. */ + if (ns_rds.count > 13) { + if (kr_log_is_debug_qry(ZCUT, qry)) { + auto_free char *name_txt = kr_dname_text(name); + VERBOSE_MSG(qry, "NS %s too large, reducing from %d names\n", + name_txt, (int)ns_rds.count); + } + ns_rds.count = 13; + } + /* Also trivially limit the total address count: + * first A and first AAAA are for free per NS, + * but the rest get a shared small limit and get skipped if exhausted. */ + int addr_budget = 8; + + /* Insert name servers for this zone cut, addresses will be looked up + * on-demand (either from cache or iteratively) */ + bool all_bad = true; /**< All NSs (seen so far) are in a bad state. */ + knot_rdata_t *rdata_i = ns_rds.rdata; + for (unsigned i = 0; i < ns_rds.count; + ++i, rdata_i = knot_rdataset_next(rdata_i)) { + const knot_dname_t *ns_name = knot_ns_name(rdata_i); + const size_t ns_size = knot_dname_size(ns_name); + + /* Get a new pack within the nsset. */ + pack_t **pack = (pack_t **)trie_get_ins(cut->nsset, + (const char *)ns_name, ns_size); + if (!pack) return kr_error(ENOMEM); + kr_assert(!*pack); /* not critical, really */ + *pack = mm_alloc(cut->pool, sizeof(pack_t)); + if (!*pack) return kr_error(ENOMEM); + pack_init(**pack); + + addrset_info_t infos[2]; + + /* Fetch NS reputation and decide whether to prefetch A/AAAA records. */ + infos[0] = fetch_addr(*pack, ns_name, KNOT_RRTYPE_A, &addr_budget, + cut->pool, qry); + infos[1] = fetch_addr(*pack, ns_name, KNOT_RRTYPE_AAAA, &addr_budget, + cut->pool, qry); + + #if 0 /* rather unlikely to be useful unless changing some zcut code */ + if (kr_log_is_debug_qry(ZCUT, qry)) { + auto_free char *ns_name_txt = kr_dname_text(ns_name); + VERBOSE_MSG(qry, "NS %s infos: %d, %d\n", + ns_name_txt, (int)infos[0], (int)infos[1]); + } + #endif + + /* AI_CYCLED checks. + * If an ancestor query has its zone cut in the state that + * it's looking for name or address(es) of some NS(s), + * we want to avoid doing so with a NS that lies under its cut. + * Instead we need to consider such names unusable in the cut (for now). */ + if (infos[0] != AI_UNKNOWN && infos[1] != AI_UNKNOWN) { + /* Optimization: the following loop would be pointless. */ + all_bad = false; + continue; + } + for (const struct kr_query *aq = qry; aq->parent; aq = aq->parent) { + const struct kr_qflags *aqpf = &aq->parent->flags; + if ( (aqpf->AWAIT_CUT && aq->stype == KNOT_RRTYPE_NS) + || (aqpf->AWAIT_IPV4 && aq->stype == KNOT_RRTYPE_A) + || (aqpf->AWAIT_IPV6 && aq->stype == KNOT_RRTYPE_AAAA)) { + if (knot_dname_in_bailiwick(ns_name, + aq->parent->zone_cut.name)) { + for (int j = 0; j < 2; ++j) + if (infos[j] == AI_UNKNOWN) + infos[j] = AI_CYCLED; + break; + } + } else { + /* This ancestor waits for other reason that + * NS name or address, so we're out of a direct cycle. */ + break; + } + } + all_bad = all_bad && infos[0] <= AI_LAST_BAD && infos[1] <= AI_LAST_BAD; + } + + if (all_bad && kr_log_is_debug_qry(ZCUT, qry)) { + auto_free char *name_txt = kr_dname_text(name); + VERBOSE_MSG(qry, "cut %s: all NSs bad, count = %d\n", + name_txt, (int)ns_rds.count); + } + + kr_assert(addr_budget >= 0); + if (addr_budget <= 0 && kr_log_is_debug_qry(ZCUT, qry)) { + auto_free char *name_txt = kr_dname_text(name); + VERBOSE_MSG(qry, "NS %s have too many addresses together, reduced\n", + name_txt); + } + + knot_rdataset_clear(&ns_rds, cut->pool); + return all_bad ? ELOOP : kr_ok(); +} + +/** + * Fetch secure RRSet of given type. + */ +static int fetch_secure_rrset(knot_rrset_t **rr, struct kr_cache *cache, + const knot_dname_t *owner, uint16_t type, knot_mm_t *pool, + const struct kr_query *qry) +{ + if (kr_fails_assert(rr)) + return kr_error(EINVAL); + /* peek, check rank and TTL */ + struct kr_cache_p peek; + int ret = kr_cache_peek_exact(cache, owner, type, &peek); + if (ret != 0) + return ret; + if (!kr_rank_test(peek.rank, KR_RANK_SECURE)) + return kr_error(ENOENT); + int32_t new_ttl = kr_cache_ttl(&peek, qry, owner, type); + if (new_ttl < 0) + return kr_error(ESTALE); + /* materialize a new RRset */ + knot_rrset_free(*rr, pool); + *rr = mm_alloc(pool, sizeof(knot_rrset_t)); + if (*rr == NULL) + return kr_error(ENOMEM); + owner = knot_dname_copy(/*const-cast*/(knot_dname_t *)owner, pool); + if (!owner) { + mm_free(pool, *rr); + *rr = NULL; + return kr_error(ENOMEM); + } + knot_rrset_init(*rr, /*const-cast*/(knot_dname_t *)owner, type, + KNOT_CLASS_IN, new_ttl); + ret = kr_cache_materialize(&(*rr)->rrs, &peek, pool); + if (ret < 0) { + knot_rrset_free(*rr, pool); + *rr = NULL; + return ret; + } + + return kr_ok(); +} + +int kr_zonecut_find_cached(struct kr_context *ctx, struct kr_zonecut *cut, + const knot_dname_t *name, const struct kr_query *qry, + bool * restrict secured) +{ + if (!ctx || !cut || !name) + return kr_error(EINVAL); + /* I'm not sure whether the caller always passes a clean state; + * mixing doesn't seem to make sense in any case, so let's clear it. + * We don't bother freeing the packs, as they're on mempool. */ + trie_clear(cut->nsset); + /* Copy name as it may overlap with cut name that is to be replaced. */ + knot_dname_t *qname = knot_dname_copy(name, cut->pool); + if (!qname) { + return kr_error(ENOMEM); + } + /* Start at QNAME. */ + int ret; + const knot_dname_t *label = qname; + while (true) { + /* Fetch NS first and see if it's insecure. */ + uint8_t rank = 0; + const bool is_root = (label[0] == '\0'); + ret = fetch_ns(ctx, cut, label, qry, &rank); + if (ret == 0) { + /* Flag as insecure if cached as this */ + if (kr_rank_test(rank, KR_RANK_INSECURE)) { + *secured = false; + } + /* Fetch DS and DNSKEY if caller wants secure zone cut */ + int ret_ds = 1, ret_dnskey = 1; + if (*secured || is_root) { + ret_ds = fetch_secure_rrset(&cut->trust_anchor, &ctx->cache, + label, KNOT_RRTYPE_DS, cut->pool, qry); + ret_dnskey = fetch_secure_rrset(&cut->key, &ctx->cache, + label, KNOT_RRTYPE_DNSKEY, cut->pool, qry); + } + update_cut_name(cut, label); + if (kr_log_is_debug_qry(ZCUT, qry)) { + auto_free char *label_str = kr_dname_text(label); + VERBOSE_MSG(qry, + "found cut: %s (rank 0%.2o return codes: DS %d, DNSKEY %d)\n", + label_str, rank, ret_ds, ret_dnskey); + } + ret = kr_ok(); + break; + } /* else */ + + trie_clear(cut->nsset); + /* Subtract label from QNAME. */ + if (!is_root) { + label = knot_wire_next_label(label, NULL); + } else { + ret = kr_error(ENOENT); + break; + } + } + + kr_cache_commit(&ctx->cache); + mm_free(cut->pool, qname); + return ret; +} diff --git a/lib/zonecut.h b/lib/zonecut.h new file mode 100644 index 0000000..9c960ec --- /dev/null +++ b/lib/zonecut.h @@ -0,0 +1,164 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "lib/cache/api.h" +#include "lib/defines.h" +#include "lib/generic/pack.h" +#include "lib/generic/trie.h" + + +struct kr_rplan; +struct kr_context; + +/** + * Current zone cut representation. +*/ +struct kr_zonecut { + knot_dname_t *name; /**< Zone cut name. */ + knot_rrset_t* key; /**< Zone cut DNSKEY. */ + knot_rrset_t* trust_anchor; /**< Current trust anchor. */ + struct kr_zonecut *parent; /**< Parent zone cut. */ + trie_t *nsset; /**< Map of nameserver => address_set (pack_t). */ + knot_mm_t *pool; /**< Memory pool. */ +}; + +/** + * Populate root zone cut with SBELT. + * @param cut zone cut + * @param name + * @param pool + * @return 0 or error code + */ +KR_EXPORT +int kr_zonecut_init(struct kr_zonecut *cut, const knot_dname_t *name, knot_mm_t *pool); + +/** + * Clear the structure and free the address set. + * @param cut zone cut + */ +KR_EXPORT +void kr_zonecut_deinit(struct kr_zonecut *cut); + +/** + * Move a zonecut, transferring ownership of any pointed-to memory. + * @param to the target - it gets deinit-ed + * @param from the source - not modified, but shouldn't be used afterward + */ +KR_EXPORT +void kr_zonecut_move(struct kr_zonecut *to, const struct kr_zonecut *from); + +/** + * Reset zone cut to given name and clear address list. + * @note This clears the address list even if the name doesn't change. TA and DNSKEY don't change. + * @param cut zone cut to be set + * @param name new zone cut name + */ +KR_EXPORT +void kr_zonecut_set(struct kr_zonecut *cut, const knot_dname_t *name); + +/** + * Copy zone cut, including all data. Does not copy keys and trust anchor. + * @param dst destination zone cut + * @param src source zone cut + * @return 0 or an error code; If it fails with kr_error(ENOMEM), + * it may be in a half-filled state, but it's safe to deinit... + * @note addresses for names in `src` get replaced and others are left as they were. + */ +KR_EXPORT +int kr_zonecut_copy(struct kr_zonecut *dst, const struct kr_zonecut *src); + +/** + * Copy zone trust anchor and keys. + * @param dst destination zone cut + * @param src source zone cut + * @return 0 or an error code + */ +KR_EXPORT +int kr_zonecut_copy_trust(struct kr_zonecut *dst, const struct kr_zonecut *src); + +/** + * Add address record to the zone cut. + * + * The record will be merged with existing data, + * it may be either A/AAAA type. + * + * @param cut zone cut to be populated + * @param ns nameserver name + * @param data typically knot_rdata_t::data + * @param len typically knot_rdata_t::len + * @return 0 or error code + */ +KR_EXPORT +int kr_zonecut_add(struct kr_zonecut *cut, const knot_dname_t *ns, const void *data, int len); + +/** + * Delete nameserver/address pair from the zone cut. + * @param cut + * @param ns name server name + * @param data typically knot_rdata_t::data + * @param len typically knot_rdata_t::len + * @return 0 or error code + */ +KR_EXPORT +int kr_zonecut_del(struct kr_zonecut *cut, const knot_dname_t *ns, const void *data, int len); + +/** + * Delete all addresses associated with the given name. + * @param cut + * @param ns name server name + * @return 0 or error code + */ +KR_EXPORT +int kr_zonecut_del_all(struct kr_zonecut *cut, const knot_dname_t *ns); + +/** + * Find nameserver address list in the zone cut. + * + * @note This can be used for membership test, a non-null pack is returned + * if the nameserver name exists. + * + * @param cut + * @param ns name server name + * @return pack of addresses or NULL + */ +KR_EXPORT KR_PURE +pack_t *kr_zonecut_find(struct kr_zonecut *cut, const knot_dname_t *ns); + +/** + * Populate zone cut with a root zone using SBELT :rfc:`1034` + * + * @param ctx resolution context (to fetch root hints) + * @param cut zone cut to be populated + * @return 0 or error code + */ +KR_EXPORT +int kr_zonecut_set_sbelt(struct kr_context *ctx, struct kr_zonecut *cut); + +/** + * Populate zone cut address set from cache. + * + * The size is limited to avoid possibility of doing too much CPU work. + * + * @param ctx resolution context (to fetch data from LRU caches) + * @param cut zone cut to be populated + * @param name QNAME to start finding zone cut for + * @param qry query for timestamp and stale-serving decisions + * @param secured set to true if want secured zone cut, will return false if it is provably insecure + * @return 0 or error code (ENOENT if it doesn't find anything) + */ +KR_EXPORT +int kr_zonecut_find_cached(struct kr_context *ctx, struct kr_zonecut *cut, + const knot_dname_t *name, const struct kr_query *qry, + bool * restrict secured); +/** + * Check if any address is present in the zone cut. + * + * @param cut zone cut to check + * @return true/false + */ +KR_EXPORT +bool kr_zonecut_is_empty(struct kr_zonecut *cut); + |