summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/README.rst313
-rw-r--r--lib/cache/README.rst69
-rw-r--r--lib/cache/api.c1029
-rw-r--r--lib/cache/api.h194
-rw-r--r--lib/cache/cdb_api.h97
-rw-r--r--lib/cache/cdb_lmdb.c868
-rw-r--r--lib/cache/cdb_lmdb.h16
-rw-r--r--lib/cache/entry_list.c301
-rw-r--r--lib/cache/entry_pkt.c206
-rw-r--r--lib/cache/entry_rr.c115
-rw-r--r--lib/cache/impl.h439
-rw-r--r--lib/cache/knot_pkt.c94
-rw-r--r--lib/cache/nsec1.c488
-rw-r--r--lib/cache/nsec3.c481
-rw-r--r--lib/cache/overflow.test.integr/deckard.yaml22
-rw-r--r--lib/cache/overflow.test.integr/kresd_config.j291
-rw-r--r--lib/cache/overflow.test.integr/world_cz_vutbr_www.rpl298
-rw-r--r--lib/cache/peek.c774
-rw-r--r--lib/cache/test.integr/cache_minimal_nsec3.rpl4120
-rw-r--r--lib/cache/test.integr/deckard.yaml13
-rw-r--r--lib/cache/test.integr/kresd_config.j269
-rw-r--r--lib/cache/util.h4
-rw-r--r--lib/cookies/alg_containers.c59
-rw-r--r--lib/cookies/alg_containers.h37
-rw-r--r--lib/cookies/alg_sha.c110
-rw-r--r--lib/cookies/alg_sha.h18
-rw-r--r--lib/cookies/control.h37
-rw-r--r--lib/cookies/helper.c268
-rw-r--r--lib/cookies/helper.h74
-rw-r--r--lib/cookies/lru_cache.c58
-rw-r--r--lib/cookies/lru_cache.h57
-rw-r--r--lib/cookies/nonce.c20
-rw-r--r--lib/cookies/nonce.h31
-rw-r--r--lib/defines.h106
-rw-r--r--lib/dnssec.c601
-rw-r--r--lib/dnssec.h191
-rw-r--r--lib/dnssec/nsec.c315
-rw-r--r--lib/dnssec/nsec.h69
-rw-r--r--lib/dnssec/nsec3.c722
-rw-r--r--lib/dnssec/nsec3.h83
-rw-r--r--lib/dnssec/signature.c304
-rw-r--r--lib/dnssec/signature.h29
-rw-r--r--lib/dnssec/ta.c154
-rw-r--r--lib/dnssec/ta.h61
-rw-r--r--lib/generic/README.rst48
-rw-r--r--lib/generic/array.h157
-rw-r--r--lib/generic/lru.c249
-rw-r--r--lib/generic/lru.h240
-rw-r--r--lib/generic/pack.h221
-rw-r--r--lib/generic/queue.c140
-rw-r--r--lib/generic/queue.h230
-rw-r--r--lib/generic/test_array.c99
-rw-r--r--lib/generic/test_lru.c111
-rw-r--r--lib/generic/test_pack.c68
-rw-r--r--lib/generic/test_queue.c71
-rw-r--r--lib/generic/test_trie.c154
-rw-r--r--lib/generic/trie.c923
-rw-r--r--lib/generic/trie.h150
-rw-r--r--lib/generic/trie.spdx10
-rw-r--r--lib/layer.h107
-rw-r--r--lib/layer/cache.c20
-rw-r--r--lib/layer/iterate.c1235
-rw-r--r--lib/layer/iterate.h25
-rw-r--r--lib/layer/mode.rst26
-rw-r--r--lib/layer/test.integr/deckard.yaml13
-rw-r--r--lib/layer/test.integr/iter_cname_length.rpl226
-rw-r--r--lib/layer/test.integr/iter_limit_bad_glueless.rpl220
-rw-r--r--lib/layer/test.integr/iter_limit_refuse.rpl150
-rw-r--r--lib/layer/test.integr/kresd_config.j2107
-rw-r--r--lib/layer/validate.c1366
-rw-r--r--lib/layer/validate.test.integr/deckard.yaml10
-rw-r--r--lib/layer/validate.test.integr/fwd_insecure_but_rrsig_signer_invalid.rpl294
-rw-r--r--lib/layer/validate.test.integr/kresd_config.j252
-rw-r--r--lib/log.c328
-rw-r--r--lib/log.h278
-rw-r--r--lib/meson.build121
-rw-r--r--lib/module.c148
-rw-r--r--lib/module.h112
-rw-r--r--lib/resolve.c1695
-rw-r--r--lib/resolve.h420
-rw-r--r--lib/rplan.c291
-rw-r--r--lib/rplan.h221
-rw-r--r--lib/selection.c795
-rw-r--r--lib/selection.h269
-rw-r--r--lib/selection_forward.c136
-rw-r--r--lib/selection_forward.h17
-rw-r--r--lib/selection_iter.c378
-rw-r--r--lib/selection_iter.h14
-rw-r--r--lib/test_module.c39
-rw-r--r--lib/test_rplan.c75
-rw-r--r--lib/test_utils.c147
-rw-r--r--lib/test_zonecut.c58
-rw-r--r--lib/utils.c1393
-rw-r--r--lib/utils.h608
-rw-r--r--lib/zonecut.c590
-rw-r--r--lib/zonecut.h164
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(&params, 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, &params, 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(&params);
+ 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(&params, nsec3);
+ if (ret != 0)
+ goto fail;
+
+ ret = hash_name(&name_hash, &params, 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(&params);
+ 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(&params, nsec3);
+ if (ret != 0)
+ goto fail;
+
+ ret = hash_name(&name_hash, &params, 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(&params);
+ 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, &timestamp);
+ 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 = &param->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);
+